From b019a19b5546d51865175359ec1ae8e5aa3f4128 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 11 Jan 2022 16:09:17 -0700 Subject: [PATCH 1/2] Sema: comptime loads and stores for `elem_ptr` The index is checked against actual array lengths, and now handles coerced or casted pointers to single items. --- src/Sema.zig | 218 ++++++++++++++++++++------------- test/behavior/array_llvm.zig | 12 ++ test/behavior/array_stage1.zig | 12 -- 3 files changed, 146 insertions(+), 96 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 165f629aab..9cb87bb678 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -14085,88 +14085,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 +14320,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; diff --git a/test/behavior/array_llvm.zig b/test/behavior/array_llvm.zig index 8e65045210..3fca269034 100644 --- a/test/behavior/array_llvm.zig +++ b/test/behavior/array_llvm.zig @@ -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); +} diff --git a/test/behavior/array_stage1.zig b/test/behavior/array_stage1.zig index 771f08acb3..d4b9221fe0 100644 --- a/test/behavior/array_stage1.zig +++ b/test/behavior/array_stage1.zig @@ -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]; } From 336d0c97feabad4c93525ba6ef73a6b6163f49c7 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 12 Jan 2022 13:31:50 -0700 Subject: [PATCH 2/2] stage2: detection of comptime array literals Introduce `validate_array_init_comptime`, similar to `validate_struct_init_comptime` introduced in 713d2a9b3883942491b40738245232680877cc66. `zirValidateArrayInit` is improved to detect comptime array literals and emit AIR accordingly. This code is very similar to the changes introduced in that same commit for `zirValidateStructInit`. The C backend needed some improvements to continue passing the same set of tests: * `resolveInst` for arrays now will add a local `static const` with the array value and so then `elem_val` instructions reference that local. It memoizes accesses using `value_map`, which is changed to use `Air.Inst.Ref` as the key rather than `Air.Inst.Index`. * This required a mechanism for writing to a "header" which is lines that appear at the beginning of a function body, before everything else. * dbg_stmt output comments rather than `#line` directives. TODO comment reproduced here: We need to 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? `Value.elemValue` is improved to support `elem_ptr` values. --- src/AstGen.zig | 9 +++- src/Sema.zig | 94 +++++++++++++++++++++++++++++++++++++++-- src/Zir.zig | 5 +++ src/codegen/c.zig | 76 +++++++++++++++++++++++++++------ src/print_zir.zig | 1 + src/value.zig | 7 ++- test/behavior/array.zig | 7 +++ test/behavior/for.zig | 6 +++ 8 files changed, 187 insertions(+), 18 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index ed57f5a3cd..7c855fb62a 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -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, diff --git a/src/Sema.zig b/src/Sema.zig index 9cb87bb678..7504352576 100644 --- a/src/Sema.zig +++ b/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( diff --git a/src/Zir.zig b/src/Zir.zig index 68c1b9df48..a1b5fa2188 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -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, diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 05ec1b1a88..58bf919b1f 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -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; } diff --git a/src/print_zir.zig b/src/print_zir.zig index a8b9ba3800..e367cf056d 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -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), diff --git a/src/value.zig b/src/value.zig index 1e7f713307..1993a853cd 100644 --- a/src/value.zig +++ b/src/value.zig @@ -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, diff --git a/test/behavior/array.zig b/test/behavior/array.zig index cd74640bea..cd2d029e22 100644 --- a/test/behavior/array.zig +++ b/test/behavior/array.zig @@ -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")); diff --git a/test/behavior/for.zig b/test/behavior/for.zig index c6d8eeac57..e784e96699 100644 --- a/test/behavior/for.zig +++ b/test/behavior/for.zig @@ -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;