diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2022-01-14 00:23:27 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-01-14 00:23:27 -0500 |
| commit | ec58ddf46c4e1ac060333c6d0780955acae22442 (patch) | |
| tree | 16b1f97c8962817dccdcc90ffbd7169bb8171ef7 /src | |
| parent | 0d45c72d3e4f38029a453443ae6a34c398f5c530 (diff) | |
| parent | 336d0c97feabad4c93525ba6ef73a6b6163f49c7 (diff) | |
| download | zig-ec58ddf46c4e1ac060333c6d0780955acae22442.tar.gz zig-ec58ddf46c4e1ac060333c6d0780955acae22442.zip | |
Merge pull request #10582 from ziglang/stage2-arrays
stage2: detection of comptime array literals
Diffstat (limited to 'src')
| -rw-r--r-- | src/AstGen.zig | 9 | ||||
| -rw-r--r-- | src/Sema.zig | 304 | ||||
| -rw-r--r-- | src/Zir.zig | 5 | ||||
| -rw-r--r-- | src/codegen/c.zig | 76 | ||||
| -rw-r--r-- | src/print_zir.zig | 1 | ||||
| -rw-r--r-- | src/value.zig | 7 |
6 files changed, 304 insertions, 98 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 165f629aab..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( @@ -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(); + 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(); + + 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); - 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); + 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); + 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(); + 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); - 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]); - } + return ComptimePtrMutationKit{ + .decl_ref_mut = parent.decl_ref_mut, + .val = &elems[elem_ptr.index], + .ty = elem_ty, + }; + }, - parent.val.* = try Value.Tag.array.create(arena, elems); + .array => return ComptimePtrMutationKit{ + .decl_ref_mut = parent.decl_ref_mut, + .val = &parent.val.castTag(.array).?.data[elem_ptr.index], + .ty = elem_ty, + }, - return ComptimePtrMutationKit{ - .decl_ref_mut = parent.decl_ref_mut, - .val = &elems[elem_ptr.index], - .ty = elem_ty, - }; + else => unreachable, + } }, - .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); - + 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, }; }, - - .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; 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, |
