mirror of
https://github.com/ziglang/zig.git
synced 2026-01-03 20:13:21 +00:00
Merge pull request #10582 from ziglang/stage2-arrays
stage2: detection of comptime array literals
This commit is contained in:
commit
ec58ddf46c
@ -1418,7 +1418,13 @@ fn arrayInitExprRlPtrInner(
|
||||
extra_index += 1;
|
||||
_ = try expr(gz, scope, .{ .ptr = elem_ptr }, elem_init);
|
||||
}
|
||||
_ = try gz.addPlNodePayloadIndex(.validate_array_init, node, payload_index);
|
||||
|
||||
const tag: Zir.Inst.Tag = if (gz.force_comptime)
|
||||
.validate_array_init_comptime
|
||||
else
|
||||
.validate_array_init;
|
||||
|
||||
_ = try gz.addPlNodePayloadIndex(tag, node, payload_index);
|
||||
return .void_value;
|
||||
}
|
||||
|
||||
@ -2317,6 +2323,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
|
||||
.validate_struct_init,
|
||||
.validate_struct_init_comptime,
|
||||
.validate_array_init,
|
||||
.validate_array_init_comptime,
|
||||
.set_align_stack,
|
||||
.set_cold,
|
||||
.set_float_mode,
|
||||
|
||||
312
src/Sema.zig
312
src/Sema.zig
@ -836,7 +836,12 @@ pub fn analyzeBody(
|
||||
continue;
|
||||
},
|
||||
.validate_array_init => {
|
||||
try sema.zirValidateArrayInit(block, inst);
|
||||
try sema.zirValidateArrayInit(block, inst, false);
|
||||
i += 1;
|
||||
continue;
|
||||
},
|
||||
.validate_array_init_comptime => {
|
||||
try sema.zirValidateArrayInit(block, inst, true);
|
||||
i += 1;
|
||||
continue;
|
||||
},
|
||||
@ -2815,13 +2820,18 @@ fn validateStructInit(
|
||||
}
|
||||
}
|
||||
|
||||
fn zirValidateArrayInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
|
||||
fn zirValidateArrayInit(
|
||||
sema: *Sema,
|
||||
block: *Block,
|
||||
inst: Zir.Inst.Index,
|
||||
is_comptime: bool,
|
||||
) CompileError!void {
|
||||
const validate_inst = sema.code.instructions.items(.data)[inst].pl_node;
|
||||
const init_src = validate_inst.src();
|
||||
const validate_extra = sema.code.extraData(Zir.Inst.Block, validate_inst.payload_index);
|
||||
const instrs = sema.code.extra[validate_extra.end..][0..validate_extra.data.body_len];
|
||||
const elem_ptr_data = sema.code.instructions.items(.data)[instrs[0]].pl_node;
|
||||
const elem_ptr_extra = sema.code.extraData(Zir.Inst.ElemPtrImm, elem_ptr_data.payload_index).data;
|
||||
const first_elem_ptr_data = sema.code.instructions.items(.data)[instrs[0]].pl_node;
|
||||
const elem_ptr_extra = sema.code.extraData(Zir.Inst.ElemPtrImm, first_elem_ptr_data.payload_index).data;
|
||||
const array_ptr = sema.resolveInst(elem_ptr_extra.ptr);
|
||||
const array_ty = sema.typeOf(array_ptr).childType();
|
||||
const array_len = array_ty.arrayLen();
|
||||
@ -2831,6 +2841,82 @@ fn zirValidateArrayInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Compil
|
||||
array_len, instrs.len,
|
||||
});
|
||||
}
|
||||
|
||||
if (is_comptime or block.is_comptime) {
|
||||
// In this case the comptime machinery will have evaluated the store instructions
|
||||
// at comptime and we have nothing to do here.
|
||||
return;
|
||||
}
|
||||
|
||||
var array_is_comptime = true;
|
||||
var first_block_index: usize = std.math.maxInt(u32);
|
||||
|
||||
// Collect the comptime element values in case the array literal ends up
|
||||
// being comptime-known.
|
||||
const element_vals = try sema.arena.alloc(Value, instrs.len);
|
||||
const opt_opv = try sema.typeHasOnePossibleValue(block, init_src, array_ty);
|
||||
const air_tags = sema.air_instructions.items(.tag);
|
||||
const air_datas = sema.air_instructions.items(.data);
|
||||
|
||||
for (instrs) |elem_ptr, i| {
|
||||
const elem_ptr_data = sema.code.instructions.items(.data)[elem_ptr].pl_node;
|
||||
const elem_src: LazySrcLoc = .{ .node_offset = elem_ptr_data.src_node };
|
||||
|
||||
// Determine whether the value stored to this pointer is comptime-known.
|
||||
|
||||
if (opt_opv) |opv| {
|
||||
element_vals[i] = opv;
|
||||
continue;
|
||||
}
|
||||
|
||||
const elem_ptr_air_ref = sema.inst_map.get(elem_ptr).?;
|
||||
const elem_ptr_air_inst = Air.refToIndex(elem_ptr_air_ref).?;
|
||||
// Find the block index of the elem_ptr so that we can look at the next
|
||||
// instruction after it within the same block.
|
||||
// Possible performance enhancement: save the `block_index` between iterations
|
||||
// of the for loop.
|
||||
const next_air_inst = inst: {
|
||||
var block_index = block.instructions.items.len - 1;
|
||||
while (block.instructions.items[block_index] != elem_ptr_air_inst) {
|
||||
block_index -= 1;
|
||||
}
|
||||
first_block_index = @minimum(first_block_index, block_index);
|
||||
break :inst block.instructions.items[block_index + 1];
|
||||
};
|
||||
|
||||
// If the next instructon is a store with a comptime operand, this element
|
||||
// is comptime.
|
||||
switch (air_tags[next_air_inst]) {
|
||||
.store => {
|
||||
const bin_op = air_datas[next_air_inst].bin_op;
|
||||
if (bin_op.lhs != elem_ptr_air_ref) {
|
||||
array_is_comptime = false;
|
||||
continue;
|
||||
}
|
||||
if (try sema.resolveMaybeUndefValAllowVariables(block, elem_src, bin_op.rhs)) |val| {
|
||||
element_vals[i] = val;
|
||||
} else {
|
||||
array_is_comptime = false;
|
||||
}
|
||||
continue;
|
||||
},
|
||||
else => {
|
||||
array_is_comptime = false;
|
||||
continue;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (array_is_comptime) {
|
||||
// Our task is to delete all the `elem_ptr` and `store` instructions, and insert
|
||||
// instead a single `store` to the array_ptr with a comptime struct value.
|
||||
|
||||
block.instructions.shrinkRetainingCapacity(first_block_index);
|
||||
|
||||
const array_val = try Value.Tag.array.create(sema.arena, element_vals);
|
||||
const array_init = try sema.addConstant(array_ty, array_val);
|
||||
try sema.storePtr2(block, init_src, array_ptr, init_src, array_init, init_src, .store);
|
||||
}
|
||||
}
|
||||
|
||||
fn failWithBadMemberAccess(
|
||||
@ -14085,88 +14171,112 @@ fn beginComptimePtrMutation(
|
||||
.elem_ptr => {
|
||||
const elem_ptr = ptr_val.castTag(.elem_ptr).?.data;
|
||||
var parent = try beginComptimePtrMutation(sema, block, src, elem_ptr.array_ptr);
|
||||
const elem_ty = parent.ty.childType();
|
||||
switch (parent.val.tag()) {
|
||||
.undef => {
|
||||
// An array has been initialized to undefined at comptime and now we
|
||||
// are for the first time setting an element. We must change the representation
|
||||
// of the array from `undef` to `array`.
|
||||
const arena = parent.beginArena(sema.gpa);
|
||||
defer parent.finishArena();
|
||||
|
||||
const array_len_including_sentinel =
|
||||
try sema.usizeCast(block, src, parent.ty.arrayLenIncludingSentinel());
|
||||
const elems = try arena.alloc(Value, array_len_including_sentinel);
|
||||
mem.set(Value, elems, Value.undef);
|
||||
|
||||
parent.val.* = try Value.Tag.array.create(arena, elems);
|
||||
|
||||
return ComptimePtrMutationKit{
|
||||
.decl_ref_mut = parent.decl_ref_mut,
|
||||
.val = &elems[elem_ptr.index],
|
||||
.ty = elem_ty,
|
||||
};
|
||||
},
|
||||
.bytes => {
|
||||
// An array is memory-optimized to store a slice of bytes, but we are about
|
||||
// to modify an individual field and the representation has to change.
|
||||
// If we wanted to avoid this, there would need to be special detection
|
||||
// elsewhere to identify when writing a value to an array element that is stored
|
||||
// using the `bytes` tag, and handle it without making a call to this function.
|
||||
const arena = parent.beginArena(sema.gpa);
|
||||
defer parent.finishArena();
|
||||
|
||||
const bytes = parent.val.castTag(.bytes).?.data;
|
||||
const dest_len = parent.ty.arrayLenIncludingSentinel();
|
||||
// bytes.len may be one greater than dest_len because of the case when
|
||||
// assigning `[N:S]T` to `[N]T`. This is allowed; the sentinel is omitted.
|
||||
assert(bytes.len >= dest_len);
|
||||
const elems = try arena.alloc(Value, @intCast(usize, dest_len));
|
||||
for (elems) |*elem, i| {
|
||||
elem.* = try Value.Tag.int_u64.create(arena, bytes[i]);
|
||||
switch (parent.ty.zigTypeTag()) {
|
||||
.Array, .Vector => {
|
||||
const check_len = parent.ty.arrayLenIncludingSentinel();
|
||||
if (elem_ptr.index >= check_len) {
|
||||
// TODO have the parent include the decl so we can say "declared here"
|
||||
return sema.fail(block, src, "comptime store of index {d} out of bounds of array length {d}", .{
|
||||
elem_ptr.index, check_len,
|
||||
});
|
||||
}
|
||||
const elem_ty = parent.ty.childType();
|
||||
switch (parent.val.tag()) {
|
||||
.undef => {
|
||||
// An array has been initialized to undefined at comptime and now we
|
||||
// are for the first time setting an element. We must change the representation
|
||||
// of the array from `undef` to `array`.
|
||||
const arena = parent.beginArena(sema.gpa);
|
||||
defer parent.finishArena();
|
||||
|
||||
parent.val.* = try Value.Tag.array.create(arena, elems);
|
||||
const array_len_including_sentinel =
|
||||
try sema.usizeCast(block, src, parent.ty.arrayLenIncludingSentinel());
|
||||
const elems = try arena.alloc(Value, array_len_including_sentinel);
|
||||
mem.set(Value, elems, Value.undef);
|
||||
|
||||
parent.val.* = try Value.Tag.array.create(arena, elems);
|
||||
|
||||
return ComptimePtrMutationKit{
|
||||
.decl_ref_mut = parent.decl_ref_mut,
|
||||
.val = &elems[elem_ptr.index],
|
||||
.ty = elem_ty,
|
||||
};
|
||||
},
|
||||
.bytes => {
|
||||
// An array is memory-optimized to store a slice of bytes, but we are about
|
||||
// to modify an individual field and the representation has to change.
|
||||
// If we wanted to avoid this, there would need to be special detection
|
||||
// elsewhere to identify when writing a value to an array element that is stored
|
||||
// using the `bytes` tag, and handle it without making a call to this function.
|
||||
const arena = parent.beginArena(sema.gpa);
|
||||
defer parent.finishArena();
|
||||
|
||||
const bytes = parent.val.castTag(.bytes).?.data;
|
||||
const dest_len = parent.ty.arrayLenIncludingSentinel();
|
||||
// bytes.len may be one greater than dest_len because of the case when
|
||||
// assigning `[N:S]T` to `[N]T`. This is allowed; the sentinel is omitted.
|
||||
assert(bytes.len >= dest_len);
|
||||
const elems = try arena.alloc(Value, @intCast(usize, dest_len));
|
||||
for (elems) |*elem, i| {
|
||||
elem.* = try Value.Tag.int_u64.create(arena, bytes[i]);
|
||||
}
|
||||
|
||||
parent.val.* = try Value.Tag.array.create(arena, elems);
|
||||
|
||||
return ComptimePtrMutationKit{
|
||||
.decl_ref_mut = parent.decl_ref_mut,
|
||||
.val = &elems[elem_ptr.index],
|
||||
.ty = elem_ty,
|
||||
};
|
||||
},
|
||||
.repeated => {
|
||||
// An array is memory-optimized to store only a single element value, and
|
||||
// that value is understood to be the same for the entire length of the array.
|
||||
// However, now we want to modify an individual field and so the
|
||||
// representation has to change. If we wanted to avoid this, there would
|
||||
// need to be special detection elsewhere to identify when writing a value to an
|
||||
// array element that is stored using the `repeated` tag, and handle it
|
||||
// without making a call to this function.
|
||||
const arena = parent.beginArena(sema.gpa);
|
||||
defer parent.finishArena();
|
||||
|
||||
const repeated_val = try parent.val.castTag(.repeated).?.data.copy(arena);
|
||||
const array_len_including_sentinel =
|
||||
try sema.usizeCast(block, src, parent.ty.arrayLenIncludingSentinel());
|
||||
const elems = try arena.alloc(Value, array_len_including_sentinel);
|
||||
mem.set(Value, elems, repeated_val);
|
||||
|
||||
parent.val.* = try Value.Tag.array.create(arena, elems);
|
||||
|
||||
return ComptimePtrMutationKit{
|
||||
.decl_ref_mut = parent.decl_ref_mut,
|
||||
.val = &elems[elem_ptr.index],
|
||||
.ty = elem_ty,
|
||||
};
|
||||
},
|
||||
|
||||
.array => return ComptimePtrMutationKit{
|
||||
.decl_ref_mut = parent.decl_ref_mut,
|
||||
.val = &parent.val.castTag(.array).?.data[elem_ptr.index],
|
||||
.ty = elem_ty,
|
||||
},
|
||||
|
||||
else => unreachable,
|
||||
}
|
||||
},
|
||||
else => {
|
||||
if (elem_ptr.index != 0) {
|
||||
// TODO include a "declared here" note for the decl
|
||||
return sema.fail(block, src, "out of bounds comptime store of index {d}", .{
|
||||
elem_ptr.index,
|
||||
});
|
||||
}
|
||||
return ComptimePtrMutationKit{
|
||||
.decl_ref_mut = parent.decl_ref_mut,
|
||||
.val = &elems[elem_ptr.index],
|
||||
.ty = elem_ty,
|
||||
.val = parent.val,
|
||||
.ty = parent.ty,
|
||||
};
|
||||
},
|
||||
.repeated => {
|
||||
// An array is memory-optimized to store only a single element value, and
|
||||
// that value is understood to be the same for the entire length of the array.
|
||||
// However, now we want to modify an individual field and so the
|
||||
// representation has to change. If we wanted to avoid this, there would
|
||||
// need to be special detection elsewhere to identify when writing a value to an
|
||||
// array element that is stored using the `repeated` tag, and handle it
|
||||
// without making a call to this function.
|
||||
const arena = parent.beginArena(sema.gpa);
|
||||
defer parent.finishArena();
|
||||
|
||||
const repeated_val = try parent.val.castTag(.repeated).?.data.copy(arena);
|
||||
const array_len_including_sentinel =
|
||||
try sema.usizeCast(block, src, parent.ty.arrayLenIncludingSentinel());
|
||||
const elems = try arena.alloc(Value, array_len_including_sentinel);
|
||||
mem.set(Value, elems, repeated_val);
|
||||
|
||||
parent.val.* = try Value.Tag.array.create(arena, elems);
|
||||
|
||||
return ComptimePtrMutationKit{
|
||||
.decl_ref_mut = parent.decl_ref_mut,
|
||||
.val = &elems[elem_ptr.index],
|
||||
.ty = elem_ty,
|
||||
};
|
||||
},
|
||||
|
||||
.array => return ComptimePtrMutationKit{
|
||||
.decl_ref_mut = parent.decl_ref_mut,
|
||||
.val = &parent.val.castTag(.array).?.data[elem_ptr.index],
|
||||
.ty = elem_ty,
|
||||
},
|
||||
|
||||
else => unreachable,
|
||||
}
|
||||
},
|
||||
.field_ptr => {
|
||||
@ -14296,15 +14406,41 @@ fn beginComptimePtrLoad(
|
||||
.elem_ptr => {
|
||||
const elem_ptr = ptr_val.castTag(.elem_ptr).?.data;
|
||||
const parent = try beginComptimePtrLoad(sema, block, src, elem_ptr.array_ptr);
|
||||
const elem_ty = parent.ty.childType();
|
||||
const elem_size = elem_ty.abiSize(target);
|
||||
return ComptimePtrLoadKit{
|
||||
.root_val = parent.root_val,
|
||||
.val = try parent.val.elemValue(sema.arena, elem_ptr.index),
|
||||
.ty = elem_ty,
|
||||
.byte_offset = try sema.usizeCast(block, src, parent.byte_offset + elem_size * elem_ptr.index),
|
||||
.is_mutable = parent.is_mutable,
|
||||
};
|
||||
switch (parent.ty.zigTypeTag()) {
|
||||
.Array, .Vector => {
|
||||
const check_len = parent.ty.arrayLenIncludingSentinel();
|
||||
if (elem_ptr.index >= check_len) {
|
||||
// TODO have the parent include the decl so we can say "declared here"
|
||||
return sema.fail(block, src, "comptime load of index {d} out of bounds of array length {d}", .{
|
||||
elem_ptr.index, check_len,
|
||||
});
|
||||
}
|
||||
const elem_ty = parent.ty.childType();
|
||||
const elem_size = elem_ty.abiSize(target);
|
||||
return ComptimePtrLoadKit{
|
||||
.root_val = parent.root_val,
|
||||
.val = try parent.val.elemValue(sema.arena, elem_ptr.index),
|
||||
.ty = elem_ty,
|
||||
.byte_offset = try sema.usizeCast(block, src, parent.byte_offset + elem_size * elem_ptr.index),
|
||||
.is_mutable = parent.is_mutable,
|
||||
};
|
||||
},
|
||||
else => {
|
||||
if (elem_ptr.index != 0) {
|
||||
// TODO have the parent include the decl so we can say "declared here"
|
||||
return sema.fail(block, src, "out of bounds comptime load of index {d}", .{
|
||||
elem_ptr.index,
|
||||
});
|
||||
}
|
||||
return ComptimePtrLoadKit{
|
||||
.root_val = parent.root_val,
|
||||
.val = parent.val,
|
||||
.ty = parent.ty,
|
||||
.byte_offset = parent.byte_offset,
|
||||
.is_mutable = parent.is_mutable,
|
||||
};
|
||||
},
|
||||
}
|
||||
},
|
||||
.field_ptr => {
|
||||
const field_ptr = ptr_val.castTag(.field_ptr).?.data;
|
||||
|
||||
@ -663,6 +663,9 @@ pub const Inst = struct {
|
||||
/// because it must use one of them to find out the array type.
|
||||
/// Uses the `pl_node` field. Payload is `Block`.
|
||||
validate_array_init,
|
||||
/// Same as `validate_array_init` but additionally communicates that the
|
||||
/// resulting array initialization value is within a comptime scope.
|
||||
validate_array_init_comptime,
|
||||
/// A struct literal with a specified type, with no fields.
|
||||
/// Uses the `un_node` field.
|
||||
struct_init_empty,
|
||||
@ -1087,6 +1090,7 @@ pub const Inst = struct {
|
||||
.validate_struct_init,
|
||||
.validate_struct_init_comptime,
|
||||
.validate_array_init,
|
||||
.validate_array_init_comptime,
|
||||
.struct_init_empty,
|
||||
.struct_init,
|
||||
.struct_init_ref,
|
||||
@ -1341,6 +1345,7 @@ pub const Inst = struct {
|
||||
.validate_struct_init = .pl_node,
|
||||
.validate_struct_init_comptime = .pl_node,
|
||||
.validate_array_init = .pl_node,
|
||||
.validate_array_init_comptime = .pl_node,
|
||||
.struct_init_empty = .un_node,
|
||||
.field_type = .pl_node,
|
||||
.field_type_ref = .pl_node,
|
||||
|
||||
@ -44,7 +44,7 @@ const BlockData = struct {
|
||||
result: CValue,
|
||||
};
|
||||
|
||||
pub const CValueMap = std.AutoHashMap(Air.Inst.Index, CValue);
|
||||
pub const CValueMap = std.AutoHashMap(Air.Inst.Ref, CValue);
|
||||
pub const TypedefMap = std.ArrayHashMap(
|
||||
Type,
|
||||
struct { name: []const u8, rendered: []u8 },
|
||||
@ -110,11 +110,29 @@ pub const Function = struct {
|
||||
func: *Module.Fn,
|
||||
|
||||
fn resolveInst(f: *Function, inst: Air.Inst.Ref) !CValue {
|
||||
if (f.air.value(inst)) |_| {
|
||||
return CValue{ .constant = inst };
|
||||
const gop = try f.value_map.getOrPut(inst);
|
||||
if (gop.found_existing) return gop.value_ptr.*;
|
||||
|
||||
const val = f.air.value(inst).?;
|
||||
const ty = f.air.typeOf(inst);
|
||||
switch (ty.zigTypeTag()) {
|
||||
.Array => {
|
||||
const writer = f.object.code_header.writer();
|
||||
const decl_c_value = f.allocLocalValue();
|
||||
gop.value_ptr.* = decl_c_value;
|
||||
try writer.writeAll("static ");
|
||||
try f.object.dg.renderTypeAndName(writer, ty, decl_c_value, .Const);
|
||||
try writer.writeAll(" = ");
|
||||
try f.object.dg.renderValue(writer, ty, val);
|
||||
try writer.writeAll(";\n ");
|
||||
return decl_c_value;
|
||||
},
|
||||
else => {
|
||||
const result = CValue{ .constant = inst };
|
||||
gop.value_ptr.* = result;
|
||||
return result;
|
||||
},
|
||||
}
|
||||
const index = Air.refToIndex(inst).?;
|
||||
return f.value_map.get(index).?; // Assertion means instruction does not dominate usage.
|
||||
}
|
||||
|
||||
fn allocLocalValue(f: *Function) CValue {
|
||||
@ -154,6 +172,8 @@ pub const Function = struct {
|
||||
pub const Object = struct {
|
||||
dg: DeclGen,
|
||||
code: std.ArrayList(u8),
|
||||
/// Goes before code. Initialized and deinitialized in `genFunc`.
|
||||
code_header: std.ArrayList(u8) = undefined,
|
||||
indent_writer: IndentWriter(std.ArrayList(u8).Writer),
|
||||
|
||||
fn writer(o: *Object) IndentWriter(std.ArrayList(u8).Writer).Writer {
|
||||
@ -218,12 +238,18 @@ pub const DeclGen = struct {
|
||||
// Determine if we must pointer cast.
|
||||
if (ty.eql(decl.ty)) {
|
||||
try writer.writeByte('&');
|
||||
} else {
|
||||
try writer.writeAll("(");
|
||||
try dg.renderType(writer, ty);
|
||||
try writer.writeAll(")&");
|
||||
try dg.renderDeclName(decl, writer);
|
||||
return;
|
||||
}
|
||||
|
||||
try writer.writeAll("((");
|
||||
try dg.renderType(writer, ty);
|
||||
try writer.writeAll(")&");
|
||||
try dg.renderDeclName(decl, writer);
|
||||
try writer.writeByte(')');
|
||||
return;
|
||||
}
|
||||
|
||||
try dg.renderDeclName(decl, writer);
|
||||
}
|
||||
|
||||
@ -1010,6 +1036,10 @@ pub fn genFunc(f: *Function) !void {
|
||||
defer tracy.end();
|
||||
|
||||
const o = &f.object;
|
||||
|
||||
o.code_header = std.ArrayList(u8).init(f.object.dg.gpa);
|
||||
defer o.code_header.deinit();
|
||||
|
||||
const is_global = o.dg.module.decl_exports.contains(f.func.owner_decl);
|
||||
const fwd_decl_writer = o.dg.fwd_decl.writer();
|
||||
if (is_global) {
|
||||
@ -1020,12 +1050,26 @@ pub fn genFunc(f: *Function) !void {
|
||||
|
||||
try o.indent_writer.insertNewline();
|
||||
try o.dg.renderFunctionSignature(o.writer(), is_global);
|
||||
|
||||
try o.writer().writeByte(' ');
|
||||
|
||||
// In case we need to use the header, populate it with a copy of the function
|
||||
// signature here. We anticipate a brace, newline, and space.
|
||||
try o.code_header.ensureUnusedCapacity(o.code.items.len + 3);
|
||||
o.code_header.appendSliceAssumeCapacity(o.code.items);
|
||||
o.code_header.appendSliceAssumeCapacity("{\n ");
|
||||
const empty_header_len = o.code_header.items.len;
|
||||
|
||||
const main_body = f.air.getMainBody();
|
||||
try genBody(f, main_body);
|
||||
|
||||
try o.indent_writer.insertNewline();
|
||||
|
||||
// If we have a header to insert, append the body to the header
|
||||
// and then return the result, freeing the body.
|
||||
if (o.code_header.items.len > empty_header_len) {
|
||||
try o.code_header.appendSlice(o.code.items[empty_header_len..]);
|
||||
mem.swap(std.ArrayList(u8), &o.code, &o.code_header);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn genDecl(o: *Object) !void {
|
||||
@ -1289,7 +1333,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
|
||||
};
|
||||
switch (result_value) {
|
||||
.none => {},
|
||||
else => try f.value_map.putNoClobber(inst, result_value),
|
||||
else => try f.value_map.putNoClobber(Air.indexToRef(inst), result_value),
|
||||
}
|
||||
}
|
||||
|
||||
@ -2189,7 +2233,15 @@ fn airCall(f: *Function, inst: Air.Inst.Index) !CValue {
|
||||
fn airDbgStmt(f: *Function, inst: Air.Inst.Index) !CValue {
|
||||
const dbg_stmt = f.air.instructions.items(.data)[inst].dbg_stmt;
|
||||
const writer = f.object.writer();
|
||||
try writer.print("#line {d}\n", .{dbg_stmt.line + 1});
|
||||
// TODO re-evaluate whether to emit these or not. If we naively emit
|
||||
// these directives, the output file will report bogus line numbers because
|
||||
// every newline after the #line directive adds one to the line.
|
||||
// We also don't print the filename yet, so the output is strictly unhelpful.
|
||||
// If we wanted to go this route, we would need to go all the way and not output
|
||||
// newlines until the next dbg_stmt occurs.
|
||||
// Perhaps an additional compilation option is in order?
|
||||
//try writer.print("#line {d}\n", .{dbg_stmt.line + 1});
|
||||
try writer.print("/* file:{d}:{d} */\n", .{ dbg_stmt.line + 1, dbg_stmt.column + 1 });
|
||||
return CValue.none;
|
||||
}
|
||||
|
||||
|
||||
@ -369,6 +369,7 @@ const Writer = struct {
|
||||
.validate_struct_init,
|
||||
.validate_struct_init_comptime,
|
||||
.validate_array_init,
|
||||
.validate_array_init_comptime,
|
||||
.c_import,
|
||||
=> try self.writePlNodeBlock(stream, inst),
|
||||
|
||||
|
||||
@ -1817,8 +1817,13 @@ pub const Value = extern union {
|
||||
|
||||
.decl_ref => return val.castTag(.decl_ref).?.data.val.elemValueAdvanced(index, arena, buffer),
|
||||
.decl_ref_mut => return val.castTag(.decl_ref_mut).?.data.decl.val.elemValueAdvanced(index, arena, buffer),
|
||||
.elem_ptr => {
|
||||
const data = val.castTag(.elem_ptr).?.data;
|
||||
return data.array_ptr.elemValueAdvanced(index + data.index, arena, buffer);
|
||||
},
|
||||
|
||||
// The child type of arrays which have only one possible value need to have only one possible value itself.
|
||||
// The child type of arrays which have only one possible value need
|
||||
// to have only one possible value itself.
|
||||
.the_only_possible_value => return val,
|
||||
|
||||
else => unreachable,
|
||||
|
||||
@ -114,6 +114,13 @@ test "void arrays" {
|
||||
}
|
||||
|
||||
test "nested arrays" {
|
||||
if (builtin.zig_backend == .stage2_wasm) {
|
||||
// TODO this is a recent stage2 test case regression due to an enhancement;
|
||||
// now arrays are properly detected as comptime. This exercised a new code
|
||||
// path in the wasm backend that is not yet implemented.
|
||||
return error.SkipZigTest;
|
||||
}
|
||||
|
||||
const array_of_strings = [_][]const u8{ "hello", "this", "is", "my", "thing" };
|
||||
for (array_of_strings) |s, i| {
|
||||
if (i == 0) try expect(mem.eql(u8, s, "hello"));
|
||||
|
||||
@ -33,3 +33,15 @@ test "read/write through global variable array of struct fields initialized via
|
||||
};
|
||||
try S.doTheTest();
|
||||
}
|
||||
|
||||
test "implicit cast single-item pointer" {
|
||||
try testImplicitCastSingleItemPtr();
|
||||
comptime try testImplicitCastSingleItemPtr();
|
||||
}
|
||||
|
||||
fn testImplicitCastSingleItemPtr() !void {
|
||||
var byte: u8 = 100;
|
||||
const slice = @as(*[1]u8, &byte)[0..];
|
||||
slice[0] += 1;
|
||||
try expect(byte == 101);
|
||||
}
|
||||
|
||||
@ -4,18 +4,6 @@ const mem = std.mem;
|
||||
const expect = testing.expect;
|
||||
const expectEqual = testing.expectEqual;
|
||||
|
||||
test "implicit cast single-item pointer" {
|
||||
try testImplicitCastSingleItemPtr();
|
||||
comptime try testImplicitCastSingleItemPtr();
|
||||
}
|
||||
|
||||
fn testImplicitCastSingleItemPtr() !void {
|
||||
var byte: u8 = 100;
|
||||
const slice = @as(*[1]u8, &byte)[0..];
|
||||
slice[0] += 1;
|
||||
try expect(byte == 101);
|
||||
}
|
||||
|
||||
fn testArrayByValAtComptime(b: [2]u8) u8 {
|
||||
return b[0];
|
||||
}
|
||||
|
||||
@ -62,6 +62,12 @@ test "ignore lval with underscore (for loop)" {
|
||||
}
|
||||
|
||||
test "basic for loop" {
|
||||
if (@import("builtin").zig_backend == .stage2_wasm) {
|
||||
// TODO this is a recent stage2 test case regression due to an enhancement;
|
||||
// now arrays are properly detected as comptime. This exercised a new code
|
||||
// path in the wasm backend that is not yet implemented.
|
||||
return error.SkipZigTest;
|
||||
}
|
||||
const expected_result = [_]u8{ 9, 8, 7, 6, 0, 1, 2, 3 } ** 3;
|
||||
|
||||
var buffer: [expected_result.len]u8 = undefined;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user