From ae845a33c04fb287ae5a7445743c2b570e40ca1f Mon Sep 17 00:00:00 2001 From: mlugg Date: Fri, 19 Jan 2024 01:56:45 +0000 Subject: [PATCH 1/2] Zir: represent declarations via an instruction This commit changes how declarations (`const`, `fn`, `usingnamespace`, etc) are represented in ZIR. Previously, these were represented in the container type's extra data (e.g. as trailing data on a `struct_decl`). However, this introduced the complexity of the ZIR mapping logic having to also correlate some ZIR extra data indices. That isn't really a problem today, but it's tricky for the introduction of `TrackedInst` in the commit following this one. Instead, these type declarations now simply contain a trailing list of ZIR indices to `declaration` instructions, which directly encode all data related to the declaration (including containing the declaration's body). Additionally, the ZIR for `align` etc have been split out into their own bodies. This is not strictly necessary, but it's much simpler to understand for an insignificant cost in bytes, and will simplify the resolution of #131 (where we may need to evaluate the pointer type, including align etc, without immediately evaluating the value body). --- src/AstGen.zig | 376 ++++++++++++++++++++--------------- src/Autodoc.zig | 302 ++++++++++++----------------- src/InternPool.zig | 2 - src/Module.zig | 474 ++++++++++++++++++++------------------------- src/Sema.zig | 32 +-- src/Zir.zig | 400 ++++++++++++++++++++------------------ src/main.zig | 17 +- src/print_zir.zig | 189 +++++++----------- 8 files changed, 858 insertions(+), 934 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index f709751fde..caa0efa851 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -86,6 +86,7 @@ fn setExtra(astgen: *AstGen, index: usize, extra: anytype) void { Zir.Inst.Ref, Zir.Inst.Index, + Zir.Inst.Declaration.Name, Zir.NullTerminatedString, => @intFromEnum(@field(extra, field.name)), @@ -95,6 +96,7 @@ fn setExtra(astgen: *AstGen, index: usize, extra: anytype) void { Zir.Inst.SwitchBlock.Bits, Zir.Inst.SwitchBlockErrUnion.Bits, Zir.Inst.FuncFancy.Bits, + Zir.Inst.Declaration.Flags, => @bitCast(@field(extra, field.name)), else => @compileError("bad field type"), @@ -132,8 +134,8 @@ pub fn generate(gpa: Allocator, tree: Ast) Allocator.Error!Zir { }; defer astgen.deinit(gpa); - // String table indexes 0, 1, 2 are reserved for special meaning. - try astgen.string_bytes.appendSlice(gpa, &[_]u8{ 0, 0, 0 }); + // String table index 0 is reserved for `NullTerminatedString.empty`. + try astgen.string_bytes.append(gpa, 0); // We expect at least as many ZIR instructions and extra data items // as AST nodes. @@ -355,8 +357,13 @@ const ResultInfo = struct { }; }; +/// TODO: modify Sema to remove in favour of `coerced_align_ri` const align_ri: ResultInfo = .{ .rl = .{ .ty = .u29_type } }; const coerced_align_ri: ResultInfo = .{ .rl = .{ .coerced_ty = .u29_type } }; +/// TODO: modify Sema to remove in favour of `coerced_addrspace_ri` +const addrspace_ri: ResultInfo = .{ .rl = .{ .ty = .address_space_type } }; +const coerced_addrspace_ri: ResultInfo = .{ .rl = .{ .coerced_ty = .address_space_type } }; +const coerced_linksection_ri: ResultInfo = .{ .rl = .{ .coerced_ty = .slice_const_u8_type } }; const bool_ri: ResultInfo = .{ .rl = .{ .ty = .bool_type } }; const type_ri: ResultInfo = .{ .rl = .{ .ty = .type_type } }; const coerced_type_ri: ResultInfo = .{ .rl = .{ .coerced_ty = .type_type } }; @@ -2592,6 +2599,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As .block, .block_comptime, .block_inline, + .declaration, .suspend_block, .loop, .bool_br_and, @@ -3783,7 +3791,7 @@ fn ptrType( gz.astgen.source_line = source_line; gz.astgen.source_column = source_column; - addrspace_ref = try expr(gz, scope, .{ .rl = .{ .ty = .address_space_type } }, ptr_info.ast.addrspace_node); + addrspace_ref = try expr(gz, scope, addrspace_ri, ptr_info.ast.addrspace_node); trailing_count += 1; } if (ptr_info.ast.align_node != 0) { @@ -3899,8 +3907,6 @@ fn arrayTypeSentinel(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node. const WipMembers = struct { payload: *ArrayListUnmanaged(u32), payload_top: usize, - decls_start: u32, - decls_end: u32, field_bits_start: u32, fields_start: u32, fields_end: u32, @@ -3908,43 +3914,27 @@ const WipMembers = struct { field_index: u32 = 0, const Self = @This(); - /// struct, union, enum, and opaque decls all use same 4 bits per decl - const bits_per_decl = 4; - const decls_per_u32 = 32 / bits_per_decl; - /// struct, union, enum, and opaque decls all have maximum size of 11 u32 slots - /// (4 for src_hash + line + name + value + doc_comment + align + link_section + address_space ) - const max_decl_size = 11; fn init(gpa: Allocator, payload: *ArrayListUnmanaged(u32), decl_count: u32, field_count: u32, comptime bits_per_field: u32, comptime max_field_size: u32) Allocator.Error!Self { const payload_top: u32 = @intCast(payload.items.len); - const decls_start = payload_top + (decl_count + decls_per_u32 - 1) / decls_per_u32; - const field_bits_start = decls_start + decl_count * max_decl_size; + const field_bits_start = payload_top + decl_count; const fields_start = field_bits_start + if (bits_per_field > 0) blk: { const fields_per_u32 = 32 / bits_per_field; break :blk (field_count + fields_per_u32 - 1) / fields_per_u32; } else 0; const payload_end = fields_start + field_count * max_field_size; try payload.resize(gpa, payload_end); - return Self{ + return .{ .payload = payload, .payload_top = payload_top, - .decls_start = decls_start, .field_bits_start = field_bits_start, .fields_start = fields_start, - .decls_end = decls_start, .fields_end = fields_start, }; } - fn nextDecl(self: *Self, is_pub: bool, is_export: bool, has_align: bool, has_section_or_addrspace: bool) void { - const index = self.payload_top + self.decl_index / decls_per_u32; - assert(index < self.decls_start); - const bit_bag: u32 = if (self.decl_index % decls_per_u32 == 0) 0 else self.payload.items[index]; - self.payload.items[index] = (bit_bag >> bits_per_decl) | - (@as(u32, @intFromBool(is_pub)) << 28) | - (@as(u32, @intFromBool(is_export)) << 29) | - (@as(u32, @intFromBool(has_align)) << 30) | - (@as(u32, @intFromBool(has_section_or_addrspace)) << 31); + fn nextDecl(self: *Self, decl_inst: Zir.Inst.Index) void { + self.payload.items[self.payload_top + self.decl_index] = @intFromEnum(decl_inst); self.decl_index += 1; } @@ -3962,18 +3952,6 @@ const WipMembers = struct { self.field_index += 1; } - fn appendToDecl(self: *Self, data: u32) void { - assert(self.decls_end < self.field_bits_start); - self.payload.items[self.decls_end] = data; - self.decls_end += 1; - } - - fn appendToDeclSlice(self: *Self, data: []const u32) void { - assert(self.decls_end + data.len <= self.field_bits_start); - @memcpy(self.payload.items[self.decls_end..][0..data.len], data); - self.decls_end += @intCast(data.len); - } - fn appendToField(self: *Self, data: u32) void { assert(self.fields_end < self.payload.items.len); self.payload.items[self.fields_end] = data; @@ -3981,11 +3959,6 @@ const WipMembers = struct { } fn finishBits(self: *Self, comptime bits_per_field: u32) void { - const empty_decl_slots = decls_per_u32 - (self.decl_index % decls_per_u32); - if (self.decl_index > 0 and empty_decl_slots < decls_per_u32) { - const index = self.payload_top + self.decl_index / decls_per_u32; - self.payload.items[index] >>= @intCast(empty_decl_slots * bits_per_decl); - } if (bits_per_field > 0) { const fields_per_u32 = 32 / bits_per_field; const empty_field_slots = fields_per_u32 - (self.field_index % fields_per_u32); @@ -3997,7 +3970,7 @@ const WipMembers = struct { } fn declsSlice(self: *Self) []u32 { - return self.payload.items[self.payload_top..self.decls_end]; + return self.payload.items[self.payload_top..][0..self.decl_index]; } fn fieldsSlice(self: *Self) []u32 { @@ -4023,11 +3996,10 @@ fn fnDecl( // missing function name already happened in scanDecls() const fn_name_token = fn_proto.name_token orelse return error.AnalysisFail; - const fn_name_str_index = try astgen.identAsString(fn_name_token); // We insert this at the beginning so that its instruction index marks the // start of the top level declaration. - const block_inst = try gz.makeBlockInst(.block_inline, fn_proto.ast.proto_node); + const decl_inst = try gz.makeBlockInst(.declaration, fn_proto.ast.proto_node); astgen.advanceSourceCursorToNode(decl_node); var decl_gz: GenZir = .{ @@ -4072,8 +4044,7 @@ fn fnDecl( const doc_comment_index = try astgen.docCommentAsString(fn_proto.firstToken()); - // align, linksection, and addrspace is passed in the func instruction in this case. - wip_members.nextDecl(is_pub, is_export, false, false); + wip_members.nextDecl(decl_inst); var noalias_bits: u32 = 0; var params_scope = &fn_gz.base; @@ -4213,7 +4184,7 @@ fn fnDecl( var addrspace_gz = decl_gz.makeSubBlock(params_scope); defer addrspace_gz.unstack(); const addrspace_ref: Zir.Inst.Ref = if (fn_proto.ast.addrspace_expr == 0) .none else inst: { - const inst = try expr(&decl_gz, params_scope, .{ .rl = .{ .coerced_ty = .address_space_type } }, fn_proto.ast.addrspace_expr); + const inst = try expr(&decl_gz, params_scope, addrspace_ri, fn_proto.ast.addrspace_expr); if (addrspace_gz.instructionsSlice().len == 0) { // In this case we will send a len=0 body which can be encoded more efficiently. break :inst inst; @@ -4298,7 +4269,7 @@ fn fnDecl( .section_gz = §ion_gz, .addrspace_ref = addrspace_ref, .addrspace_gz = &addrspace_gz, - .param_block = block_inst, + .param_block = decl_inst, .body_gz = null, .lib_name = lib_name, .is_var_args = is_var_args, @@ -4349,7 +4320,7 @@ fn fnDecl( .addrspace_gz = &addrspace_gz, .lbrace_line = lbrace_line, .lbrace_column = lbrace_column, - .param_block = block_inst, + .param_block = decl_inst, .body_gz = &fn_gz, .lib_name = lib_name, .is_var_args = is_var_args, @@ -4363,20 +4334,21 @@ fn fnDecl( // We add this at the end so that its instruction index marks the end range // of the top level declaration. addFunc already unstacked fn_gz and ret_gz. - _ = try decl_gz.addBreak(.break_inline, block_inst, func_inst); - try decl_gz.setBlockBody(block_inst); + _ = try decl_gz.addBreak(.break_inline, decl_inst, func_inst); - { - const contents_hash align(@alignOf(u32)) = std.zig.hashSrc(tree.getNodeSource(decl_node)); - wip_members.appendToDeclSlice(std.mem.bytesAsSlice(u32, &contents_hash)); - } - { - const line_delta = decl_gz.decl_line - gz.decl_line; - wip_members.appendToDecl(line_delta); - } - wip_members.appendToDecl(@intFromEnum(fn_name_str_index)); - wip_members.appendToDecl(@intFromEnum(block_inst)); - wip_members.appendToDecl(@intFromEnum(doc_comment_index)); + try setDeclaration( + decl_inst, + std.zig.hashSrc(tree.getNodeSource(decl_node)), + .{ .named = fn_name_token }, + decl_gz.decl_line - gz.decl_line, + is_pub, + is_export, + doc_comment_index, + &decl_gz, + // align, linksection, and addrspace are passed in the func instruction in this case. + // TODO: move them from the function instruction to the declaration instruction? + null, + ); } fn globalVarDecl( @@ -4393,10 +4365,9 @@ fn globalVarDecl( const is_mutable = token_tags[var_decl.ast.mut_token] == .keyword_var; // We do this at the beginning so that the instruction index marks the range start // of the top level declaration. - const block_inst = try gz.makeBlockInst(.block_inline, node); + const decl_inst = try gz.makeBlockInst(.declaration, node); const name_token = var_decl.ast.mut_token + 1; - const name_str_index = try astgen.identAsString(name_token); astgen.advanceSourceCursorToNode(node); var block_scope: GenZir = .{ @@ -4420,17 +4391,7 @@ fn globalVarDecl( const maybe_extern_token = var_decl.extern_export_token orelse break :blk false; break :blk token_tags[maybe_extern_token] == .keyword_extern; }; - const align_inst: Zir.Inst.Ref = if (var_decl.ast.align_node == 0) .none else inst: { - break :inst try expr(&block_scope, &block_scope.base, align_ri, var_decl.ast.align_node); - }; - const addrspace_inst: Zir.Inst.Ref = if (var_decl.ast.addrspace_node == 0) .none else inst: { - break :inst try expr(&block_scope, &block_scope.base, .{ .rl = .{ .ty = .address_space_type } }, var_decl.ast.addrspace_node); - }; - const section_inst: Zir.Inst.Ref = if (var_decl.ast.section_node == 0) .none else inst: { - break :inst try comptimeExpr(&block_scope, &block_scope.base, .{ .rl = .{ .ty = .slice_const_u8_type } }, var_decl.ast.section_node); - }; - const has_section_or_addrspace = section_inst != .none or addrspace_inst != .none; - wip_members.nextDecl(is_pub, is_export, align_inst != .none, has_section_or_addrspace); + wip_members.nextDecl(decl_inst); const is_threadlocal = if (var_decl.threadlocal_token) |tok| blk: { if (!is_mutable) { @@ -4513,29 +4474,44 @@ fn globalVarDecl( } else { return astgen.failNode(node, "unable to infer variable type", .{}); }; + // We do this at the end so that the instruction index marks the end // range of a top level declaration. - _ = try block_scope.addBreakWithSrcNode(.break_inline, block_inst, var_inst, node); - try block_scope.setBlockBody(block_inst); + _ = try block_scope.addBreakWithSrcNode(.break_inline, decl_inst, var_inst, node); - { - const contents_hash align(@alignOf(u32)) = std.zig.hashSrc(tree.getNodeSource(node)); - wip_members.appendToDeclSlice(std.mem.bytesAsSlice(u32, &contents_hash)); + var align_gz = block_scope.makeSubBlock(scope); + if (var_decl.ast.align_node != 0) { + const align_inst = try expr(&align_gz, &align_gz.base, coerced_align_ri, var_decl.ast.align_node); + _ = try align_gz.addBreakWithSrcNode(.break_inline, decl_inst, align_inst, node); } - { - const line_delta = block_scope.decl_line - gz.decl_line; - wip_members.appendToDecl(line_delta); + + var linksection_gz = align_gz.makeSubBlock(scope); + if (var_decl.ast.section_node != 0) { + const linksection_inst = try expr(&linksection_gz, &linksection_gz.base, coerced_linksection_ri, var_decl.ast.section_node); + _ = try linksection_gz.addBreakWithSrcNode(.break_inline, decl_inst, linksection_inst, node); } - wip_members.appendToDecl(@intFromEnum(name_str_index)); - wip_members.appendToDecl(@intFromEnum(block_inst)); - wip_members.appendToDecl(@intFromEnum(doc_comment_index)); // doc_comment wip - if (align_inst != .none) { - wip_members.appendToDecl(@intFromEnum(align_inst)); - } - if (has_section_or_addrspace) { - wip_members.appendToDecl(@intFromEnum(section_inst)); - wip_members.appendToDecl(@intFromEnum(addrspace_inst)); + + var addrspace_gz = linksection_gz.makeSubBlock(scope); + if (var_decl.ast.addrspace_node != 0) { + const addrspace_inst = try expr(&addrspace_gz, &addrspace_gz.base, coerced_addrspace_ri, var_decl.ast.addrspace_node); + _ = try addrspace_gz.addBreakWithSrcNode(.break_inline, decl_inst, addrspace_inst, node); } + + try setDeclaration( + decl_inst, + std.zig.hashSrc(tree.getNodeSource(node)), + .{ .named = name_token }, + block_scope.decl_line - gz.decl_line, + is_pub, + is_export, + doc_comment_index, + &block_scope, + .{ + .align_gz = &align_gz, + .linksection_gz = &linksection_gz, + .addrspace_gz = &addrspace_gz, + }, + ); } fn comptimeDecl( @@ -4551,8 +4527,8 @@ fn comptimeDecl( // Up top so the ZIR instruction index marks the start range of this // top-level declaration. - const block_inst = try gz.makeBlockInst(.block_inline, node); - wip_members.nextDecl(false, false, false, false); + const decl_inst = try gz.makeBlockInst(.declaration, node); + wip_members.nextDecl(decl_inst); astgen.advanceSourceCursorToNode(node); var decl_block: GenZir = .{ @@ -4568,21 +4544,20 @@ fn comptimeDecl( const block_result = try expr(&decl_block, &decl_block.base, .{ .rl = .none }, body_node); if (decl_block.isEmpty() or !decl_block.refIsNoReturn(block_result)) { - _ = try decl_block.addBreak(.break_inline, block_inst, .void_value); + _ = try decl_block.addBreak(.break_inline, decl_inst, .void_value); } - try decl_block.setBlockBody(block_inst); - { - const contents_hash align(@alignOf(u32)) = std.zig.hashSrc(tree.getNodeSource(node)); - wip_members.appendToDeclSlice(std.mem.bytesAsSlice(u32, &contents_hash)); - } - { - const line_delta = decl_block.decl_line - gz.decl_line; - wip_members.appendToDecl(line_delta); - } - wip_members.appendToDecl(0); - wip_members.appendToDecl(@intFromEnum(block_inst)); - wip_members.appendToDecl(0); // no doc comments on comptime decls + try setDeclaration( + decl_inst, + std.zig.hashSrc(tree.getNodeSource(node)), + .@"comptime", + decl_block.decl_line - gz.decl_line, + false, + false, + .empty, + &decl_block, + null, + ); } fn usingnamespaceDecl( @@ -4604,8 +4579,8 @@ fn usingnamespaceDecl( }; // Up top so the ZIR instruction index marks the start range of this // top-level declaration. - const block_inst = try gz.makeBlockInst(.block_inline, node); - wip_members.nextDecl(is_pub, true, false, false); + const decl_inst = try gz.makeBlockInst(.declaration, node); + wip_members.nextDecl(decl_inst); astgen.advanceSourceCursorToNode(node); var decl_block: GenZir = .{ @@ -4620,20 +4595,19 @@ fn usingnamespaceDecl( defer decl_block.unstack(); const namespace_inst = try typeExpr(&decl_block, &decl_block.base, type_expr); - _ = try decl_block.addBreak(.break_inline, block_inst, namespace_inst); - try decl_block.setBlockBody(block_inst); + _ = try decl_block.addBreak(.break_inline, decl_inst, namespace_inst); - { - const contents_hash align(@alignOf(u32)) = std.zig.hashSrc(tree.getNodeSource(node)); - wip_members.appendToDeclSlice(std.mem.bytesAsSlice(u32, &contents_hash)); - } - { - const line_delta = decl_block.decl_line - gz.decl_line; - wip_members.appendToDecl(line_delta); - } - wip_members.appendToDecl(0); - wip_members.appendToDecl(@intFromEnum(block_inst)); - wip_members.appendToDecl(0); // no doc comments on usingnamespace decls + try setDeclaration( + decl_inst, + std.zig.hashSrc(tree.getNodeSource(node)), + .@"usingnamespace", + decl_block.decl_line - gz.decl_line, + is_pub, + false, + .empty, + &decl_block, + null, + ); } fn testDecl( @@ -4649,9 +4623,9 @@ fn testDecl( // Up top so the ZIR instruction index marks the start range of this // top-level declaration. - const block_inst = try gz.makeBlockInst(.block_inline, node); + const decl_inst = try gz.makeBlockInst(.declaration, node); - wip_members.nextDecl(false, false, false, false); + wip_members.nextDecl(decl_inst); astgen.advanceSourceCursorToNode(node); var decl_block: GenZir = .{ @@ -4669,12 +4643,10 @@ fn testDecl( const token_tags = tree.tokens.items(.tag); const test_token = main_tokens[node]; const test_name_token = test_token + 1; - const test_name_token_tag = token_tags[test_name_token]; - const is_decltest = test_name_token_tag == .identifier; - const test_name: Zir.NullTerminatedString = blk: { - if (test_name_token_tag == .string_literal) { - break :blk try astgen.testNameString(test_name_token); - } else if (test_name_token_tag == .identifier) { + const test_name: DeclarationName = switch (token_tags[test_name_token]) { + else => .unnamed_test, + .string_literal => .{ .named_test = test_name_token }, + .identifier => blk: { const ident_name_raw = tree.tokenSlice(test_name_token); if (mem.eql(u8, ident_name_raw, "_")) return astgen.failTok(test_name_token, "'_' used as an identifier without @\"_\" syntax", .{}); @@ -4744,10 +4716,8 @@ fn testDecl( return astgen.failTok(test_name_token, "use of undeclared identifier '{s}'", .{ident_name}); } - break :blk name_str_index; - } - // String table index 1 has a special meaning here of test decl with no name. - break :blk .unnamed_test_decl; + break :blk .{ .decltest = name_str_index }; + }, }; var fn_block: GenZir = .{ @@ -4795,7 +4765,7 @@ fn testDecl( .lbrace_line = lbrace_line, .lbrace_column = lbrace_column, - .param_block = block_inst, + .param_block = decl_inst, .body_gz = &fn_block, .lib_name = .empty, .is_var_args = false, @@ -4806,26 +4776,19 @@ fn testDecl( .noalias_bits = 0, }); - _ = try decl_block.addBreak(.break_inline, block_inst, func_inst); - try decl_block.setBlockBody(block_inst); + _ = try decl_block.addBreak(.break_inline, decl_inst, func_inst); - { - const contents_hash align(@alignOf(u32)) = std.zig.hashSrc(tree.getNodeSource(node)); - wip_members.appendToDeclSlice(std.mem.bytesAsSlice(u32, &contents_hash)); - } - { - const line_delta = decl_block.decl_line - gz.decl_line; - wip_members.appendToDecl(line_delta); - } - if (is_decltest) - wip_members.appendToDecl(2) // 2 here means that it is a decltest, look at doc comment for name - else - wip_members.appendToDecl(@intFromEnum(test_name)); - wip_members.appendToDecl(@intFromEnum(block_inst)); - if (is_decltest) - wip_members.appendToDecl(@intFromEnum(test_name)) // the doc comment on a decltest represents it's name - else - wip_members.appendToDecl(0); // no doc comments on test decls + try setDeclaration( + decl_inst, + std.zig.hashSrc(tree.getNodeSource(node)), + test_name, + decl_block.decl_line - gz.decl_line, + false, + false, + .empty, + &decl_block, + null, + ); } fn structDeclInner( @@ -13524,3 +13487,106 @@ fn lowerAstErrors(astgen: *AstGen) !void { try tree.renderError(parse_err, msg.writer(gpa)); try astgen.appendErrorTokNotesOff(parse_err.token, extra_offset, "{s}", .{msg.items}, notes.items); } + +const DeclarationName = union(enum) { + named: Ast.TokenIndex, + named_test: Ast.TokenIndex, + unnamed_test, + decltest: Zir.NullTerminatedString, + @"comptime", + @"usingnamespace", +}; + +/// Sets all extra data for a `declaration` instruction. +/// Unstacks `value_gz`, `align_gz`, `linksection_gz`, and `addrspace_gz`. +fn setDeclaration( + decl_inst: Zir.Inst.Index, + src_hash: std.zig.SrcHash, + name: DeclarationName, + line_offset: u32, + is_pub: bool, + is_export: bool, + doc_comment: Zir.NullTerminatedString, + value_gz: *GenZir, + /// May be `null` if all these blocks would be empty. + /// If `null`, then `value_gz` must have nothing stacked on it. + extra_gzs: ?struct { + /// Must be stacked on `value_gz`. + align_gz: *GenZir, + /// Must be stacked on `align_gz`. + linksection_gz: *GenZir, + /// Must be stacked on `linksection_gz`, and have nothing stacked on it. + addrspace_gz: *GenZir, + }, +) !void { + const astgen = value_gz.astgen; + const gpa = astgen.gpa; + + const empty_body: []Zir.Inst.Index = &.{}; + const value_body, const align_body, const linksection_body, const addrspace_body = if (extra_gzs) |e| .{ + value_gz.instructionsSliceUpto(e.align_gz), + e.align_gz.instructionsSliceUpto(e.linksection_gz), + e.linksection_gz.instructionsSliceUpto(e.addrspace_gz), + e.addrspace_gz.instructionsSlice(), + } else .{ value_gz.instructionsSlice(), empty_body, empty_body, empty_body }; + + const value_len = astgen.countBodyLenAfterFixups(value_body); + const align_len = astgen.countBodyLenAfterFixups(align_body); + const linksection_len = astgen.countBodyLenAfterFixups(linksection_body); + const addrspace_len = astgen.countBodyLenAfterFixups(addrspace_body); + + const true_doc_comment: Zir.NullTerminatedString = switch (name) { + .decltest => |test_name| test_name, + else => doc_comment, + }; + + const src_hash_arr: [4]u32 = @bitCast(src_hash); + + const extra: Zir.Inst.Declaration = .{ + .src_hash_0 = src_hash_arr[0], + .src_hash_1 = src_hash_arr[1], + .src_hash_2 = src_hash_arr[2], + .src_hash_3 = src_hash_arr[3], + .name = switch (name) { + .named => |tok| @enumFromInt(@intFromEnum(try astgen.identAsString(tok))), + .named_test => |tok| @enumFromInt(@intFromEnum(try astgen.testNameString(tok))), + .unnamed_test => .unnamed_test, + .decltest => .decltest, + .@"comptime" => .@"comptime", + .@"usingnamespace" => .@"usingnamespace", + }, + .line_offset = line_offset, + .flags = .{ + .value_body_len = @intCast(value_len), + .is_pub = is_pub, + .is_export = is_export, + .has_doc_comment = true_doc_comment != .empty, + .has_align_linksection_addrspace = align_len != 0 or linksection_len != 0 or addrspace_len != 0, + }, + }; + astgen.instructions.items(.data)[@intFromEnum(decl_inst)].pl_node.payload_index = try astgen.addExtra(extra); + if (extra.flags.has_doc_comment) { + try astgen.extra.append(gpa, @intFromEnum(true_doc_comment)); + } + if (extra.flags.has_align_linksection_addrspace) { + try astgen.extra.appendSlice(gpa, &.{ + align_len, + linksection_len, + addrspace_len, + }); + } + try astgen.extra.ensureUnusedCapacity(gpa, value_len + align_len + linksection_len + addrspace_len); + astgen.appendBodyWithFixups(value_body); + if (extra.flags.has_align_linksection_addrspace) { + astgen.appendBodyWithFixups(align_body); + astgen.appendBodyWithFixups(linksection_body); + astgen.appendBodyWithFixups(addrspace_body); + } + + if (extra_gzs) |e| { + e.addrspace_gz.unstack(); + e.linksection_gz.unstack(); + e.align_gz.unstack(); + } + value_gz.unstack(); +} diff --git a/src/Autodoc.zig b/src/Autodoc.zig index a03ed35178..b58093b9a1 100644 --- a/src/Autodoc.zig +++ b/src/Autodoc.zig @@ -2846,22 +2846,14 @@ fn walkInstruction( return res; }, .block_inline => { - return self.walkRef( + const pl_node = data[@intFromEnum(inst)].pl_node; + const extra = file.zir.extraData(Zir.Inst.Block, pl_node.payload_index); + return self.walkInlineBody( file, parent_scope, + try self.srcLocInfo(file, pl_node.src_node, parent_src), parent_src, - getBlockInlineBreak(file.zir, inst) orelse { - const res = DocData.WalkResult{ - .typeRef = .{ .type = @intFromEnum(Ref.type_type) }, - .expr = .{ .comptimeExpr = self.comptime_exprs.items.len }, - }; - const pl_node = data[@intFromEnum(inst)].pl_node; - const block_inline_expr = try self.getBlockSource(file, parent_src, pl_node.src_node); - try self.comptime_exprs.append(self.arena, .{ - .code = block_inline_expr, - }); - return res; - }, + file.zir.bodySlice(extra.end, extra.data.body_len), need_type, call_ctx, ); @@ -4084,19 +4076,11 @@ fn analyzeAllDecls( // First loop to discover decl names { var it = original_it; - while (it.next()) |d| { - const decl_name_index: Zir.NullTerminatedString = @enumFromInt(file.zir.extra[@intFromEnum(d.sub_index) + 5]); - switch (decl_name_index) { - .empty, - .unnamed_test_decl, - .decltest, - => continue, - _ => if (file.zir.nullTerminatedString(decl_name_index).len == 0) { - continue; - }, - } - - try scope.insertDeclRef(self.arena, decl_name_index, .Pending); + while (it.next()) |zir_index| { + const declaration, _ = file.zir.getDeclaration(zir_index); + if (declaration.name.isNamedTest(file.zir)) continue; + const decl_name = declaration.name.toString(file.zir) orelse continue; + try scope.insertDeclRef(self.arena, decl_name, .Pending); } } @@ -4104,147 +4088,114 @@ fn analyzeAllDecls( { var it = original_it; var decl_indexes_slot = first_decl_indexes_slot; - while (it.next()) |d| : (decl_indexes_slot += 1) { - const decl_name_index = file.zir.extra[@intFromEnum(d.sub_index) + 5]; - switch (decl_name_index) { - 0 => { - const is_exported = @as(u1, @truncate(d.flags >> 1)); - switch (is_exported) { - 0 => continue, // comptime decl - 1 => { - try self.analyzeUsingnamespaceDecl( - file, - scope, - parent_src, - decl_indexes, - priv_decl_indexes, - d, - call_ctx, - ); - }, - } - }, - else => continue, - } + while (it.next()) |zir_index| : (decl_indexes_slot += 1) { + const pl_node = file.zir.instructions.items(.data)[@intFromEnum(zir_index)].pl_node; + const extra = file.zir.extraData(Zir.Inst.Declaration, pl_node.payload_index); + if (extra.data.name != .@"usingnamespace") continue; + try self.analyzeUsingnamespaceDecl( + file, + scope, + try self.srcLocInfo(file, pl_node.src_node, parent_src), + decl_indexes, + priv_decl_indexes, + extra.data, + @intCast(extra.end), + call_ctx, + ); } } // Third loop to analyze all remaining decls - var it = original_it; - while (it.next()) |d| { - const decl_name_index = file.zir.extra[@intFromEnum(d.sub_index) + 5]; - switch (decl_name_index) { - 0, 1 => continue, // skip over usingnamespace decls - 2 => continue, // skip decltests - - else => if (file.zir.string_bytes[decl_name_index] == 0) { - continue; - }, + { + var it = original_it; + while (it.next()) |zir_index| { + const pl_node = file.zir.instructions.items(.data)[@intFromEnum(zir_index)].pl_node; + const extra = file.zir.extraData(Zir.Inst.Declaration, pl_node.payload_index); + switch (extra.data.name) { + .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue, + _ => if (extra.data.name.isNamedTest(file.zir)) continue, + } + try self.analyzeDecl( + file, + scope, + try self.srcLocInfo(file, pl_node.src_node, parent_src), + decl_indexes, + priv_decl_indexes, + zir_index, + extra.data, + @intCast(extra.end), + call_ctx, + ); } - - try self.analyzeDecl( - file, - scope, - parent_src, - decl_indexes, - priv_decl_indexes, - d, - call_ctx, - ); } // Fourth loop to analyze decltests - it = original_it; - while (it.next()) |d| { - const decl_name_index = file.zir.extra[@intFromEnum(d.sub_index) + 5]; - switch (decl_name_index) { - 0, 1 => continue, // skip over usingnamespace decls - 2 => {}, - else => continue, // skip tests and normal decls - } - + var it = original_it; + while (it.next()) |zir_index| { + const pl_node = file.zir.instructions.items(.data)[@intFromEnum(zir_index)].pl_node; + const extra = file.zir.extraData(Zir.Inst.Declaration, pl_node.payload_index); + if (extra.data.name != .decltest) continue; try self.analyzeDecltest( file, scope, - parent_src, - d, + try self.srcLocInfo(file, pl_node.src_node, parent_src), + extra.data, + @intCast(extra.end), ); } return it.extra_index; } +fn walkInlineBody( + autodoc: *Autodoc, + file: *File, + scope: *Scope, + block_src: SrcLocInfo, + parent_src: SrcLocInfo, + body: []const Zir.Inst.Index, + need_type: bool, + call_ctx: ?*const CallContext, +) AutodocErrors!DocData.WalkResult { + const tags = file.zir.instructions.items(.tag); + const break_inst = switch (tags[@intFromEnum(body[body.len - 1])]) { + .condbr_inline => { + // Unresolvable. + const res: DocData.WalkResult = .{ + .typeRef = .{ .type = @intFromEnum(Ref.type_type) }, + .expr = .{ .comptimeExpr = autodoc.comptime_exprs.items.len }, + }; + const source = (try file.getTree(autodoc.zcu.gpa)).getNodeSource(block_src.src_node); + try autodoc.comptime_exprs.append(autodoc.arena, .{ + .code = source, + }); + return res; + }, + .break_inline => body[body.len - 1], + else => unreachable, + }; + const break_data = file.zir.instructions.items(.data)[@intFromEnum(break_inst)].@"break"; + return autodoc.walkRef(file, scope, parent_src, break_data.operand, need_type, call_ctx); +} + // Asserts the given decl is public fn analyzeDecl( self: *Autodoc, file: *File, scope: *Scope, - parent_src: SrcLocInfo, + decl_src: SrcLocInfo, decl_indexes: *std.ArrayListUnmanaged(usize), priv_decl_indexes: *std.ArrayListUnmanaged(usize), - d: Zir.DeclIterator.Item, + decl_inst: Zir.Inst.Index, + declaration: Zir.Inst.Declaration, + extra_index: u32, call_ctx: ?*const CallContext, ) AutodocErrors!void { - const data = file.zir.instructions.items(.data); - const is_pub = @as(u1, @truncate(d.flags >> 0)) != 0; - // const is_exported = @truncate(u1, d.flags >> 1) != 0; - const has_align = @as(u1, @truncate(d.flags >> 2)) != 0; - const has_section_or_addrspace = @as(u1, @truncate(d.flags >> 3)) != 0; + const bodies = declaration.getBodies(extra_index, file.zir); + const name = file.zir.nullTerminatedString(declaration.name.toString(file.zir).?); - var extra_index = @intFromEnum(d.sub_index); - // const hash_u32s = file.zir.extra[extra_index..][0..4]; - - extra_index += 4; - // const line = file.zir.extra[extra_index]; - - extra_index += 1; - const decl_name_index: Zir.NullTerminatedString = @enumFromInt(file.zir.extra[extra_index]); - - extra_index += 1; - const value_index: Zir.Inst.Index = @enumFromInt(file.zir.extra[extra_index]); - - extra_index += 1; - const doc_comment_index: Zir.NullTerminatedString = @enumFromInt(file.zir.extra[extra_index]); - - extra_index += 1; - const align_inst: Zir.Inst.Ref = if (!has_align) .none else inst: { - const inst: Zir.Inst.Ref = @enumFromInt(file.zir.extra[extra_index]); - extra_index += 1; - break :inst inst; - }; - _ = align_inst; - - const section_inst: Zir.Inst.Ref = if (!has_section_or_addrspace) .none else inst: { - const inst: Zir.Inst.Ref = @enumFromInt(file.zir.extra[extra_index]); - extra_index += 1; - break :inst inst; - }; - _ = section_inst; - - const addrspace_inst: Zir.Inst.Ref = if (!has_section_or_addrspace) .none else inst: { - const inst: Zir.Inst.Ref = @enumFromInt(file.zir.extra[extra_index]); - extra_index += 1; - break :inst inst; - }; - _ = addrspace_inst; - - // This is known to work because decl values are always block_inlines - const value_pl_node = data[@intFromEnum(value_index)].pl_node; - const decl_src = try self.srcLocInfo(file, value_pl_node.src_node, parent_src); - - const name: []const u8 = switch (decl_name_index) { - .empty, .unnamed_test_decl, .decltest => unreachable, - _ => blk: { - if (decl_name_index == .empty) { - // test decl - unreachable; - } - break :blk file.zir.nullTerminatedString(decl_name_index); - }, - }; - - const doc_comment: ?[]const u8 = if (doc_comment_index != .empty) - file.zir.nullTerminatedString(doc_comment_index) + const doc_comment: ?[]const u8 = if (declaration.flags.has_doc_comment) + file.zir.nullTerminatedString(@enumFromInt(file.zir.extra[extra_index])) else null; @@ -4261,16 +4212,22 @@ fn analyzeDecl( break :idx idx; }; - const walk_result = try self.walkInstruction( + const walk_result = try self.walkInlineBody( file, scope, decl_src, - value_index, + decl_src, + bodies.value_body, true, call_ctx, ); - const kind: []const u8 = if (try self.declIsVar(file, value_pl_node.src_node, parent_src)) "var" else "const"; + const tree = try file.getTree(self.zcu.gpa); + const kind_token = tree.nodes.items(.main_token)[decl_src.src_node]; + const kind: []const u8 = switch (tree.tokens.items(.tag)[kind_token]) { + .keyword_var => "var", + else => "const", + }; const decls_slot_index = self.decls.items.len; try self.decls.append(self.arena, .{ @@ -4281,13 +4238,13 @@ fn analyzeDecl( .parent_container = scope.enclosing_type, }); - if (is_pub) { + if (declaration.flags.is_pub) { try decl_indexes.append(self.arena, decls_slot_index); } else { try priv_decl_indexes.append(self.arena, decls_slot_index); } - const decl_status_ptr = scope.resolveDeclName(decl_name_index, file, .none); + const decl_status_ptr = scope.resolveDeclName(declaration.name.toString(file.zir).?, file, .none); std.debug.assert(decl_status_ptr.* == .Pending); decl_status_ptr.* = .{ .Analyzed = decls_slot_index }; @@ -4296,7 +4253,7 @@ fn analyzeDecl( for (paths.items) |resume_info| { try self.tryResolveRefPath( resume_info.file, - value_index, + decl_inst, resume_info.ref_path, ); } @@ -4312,24 +4269,17 @@ fn analyzeUsingnamespaceDecl( self: *Autodoc, file: *File, scope: *Scope, - parent_src: SrcLocInfo, + decl_src: SrcLocInfo, decl_indexes: *std.ArrayListUnmanaged(usize), priv_decl_indexes: *std.ArrayListUnmanaged(usize), - d: Zir.DeclIterator.Item, + declaration: Zir.Inst.Declaration, + extra_index: u32, call_ctx: ?*const CallContext, ) AutodocErrors!void { - const data = file.zir.instructions.items(.data); + const bodies = declaration.getBodies(extra_index, file.zir); - const is_pub = @as(u1, @truncate(d.flags)) != 0; - const value_index: Zir.Inst.Index = @enumFromInt(file.zir.extra[@intFromEnum(d.sub_index) + 6]); - const doc_comment_index: Zir.NullTerminatedString = @enumFromInt(file.zir.extra[@intFromEnum(d.sub_index) + 7]); - - // This is known to work because decl values are always block_inlines - const value_pl_node = data[@intFromEnum(value_index)].pl_node; - const decl_src = try self.srcLocInfo(file, value_pl_node.src_node, parent_src); - - const doc_comment: ?[]const u8 = if (doc_comment_index != .empty) - file.zir.nullTerminatedString(doc_comment_index) + const doc_comment: ?[]const u8 = if (declaration.flags.has_doc_comment) + file.zir.nullTerminatedString(@enumFromInt(file.zir.extra[extra_index])) else null; @@ -4346,11 +4296,12 @@ fn analyzeUsingnamespaceDecl( break :idx idx; }; - const walk_result = try self.walkInstruction( + const walk_result = try self.walkInlineBody( file, scope, decl_src, - value_index, + decl_src, + bodies.value_body, true, call_ctx, ); @@ -4365,7 +4316,7 @@ fn analyzeUsingnamespaceDecl( .parent_container = scope.enclosing_type, }); - if (is_pub) { + if (declaration.flags.is_pub) { try decl_indexes.append(self.arena, decl_slot_index); } else { try priv_decl_indexes.append(self.arena, decl_slot_index); @@ -4376,18 +4327,14 @@ fn analyzeDecltest( self: *Autodoc, file: *File, scope: *Scope, - parent_src: SrcLocInfo, - d: Zir.DeclIterator.Item, + decl_src: SrcLocInfo, + declaration: Zir.Inst.Declaration, + extra_index: u32, ) AutodocErrors!void { - const data = file.zir.instructions.items(.data); + std.debug.assert(declaration.flags.has_doc_comment); + const decl_name_index: Zir.NullTerminatedString = @enumFromInt(file.zir.extra[extra_index]); - const value_index = file.zir.extra[@intFromEnum(d.sub_index) + 6]; - const decl_name_index: Zir.NullTerminatedString = @enumFromInt(file.zir.extra[@intFromEnum(d.sub_index) + 7]); - - const value_pl_node = data[value_index].pl_node; - const decl_src = try self.srcLocInfo(file, value_pl_node.src_node, parent_src); - - const test_source_code = try self.getBlockSource(file, parent_src, value_pl_node.src_node); + const test_source_code = (try file.getTree(self.zcu.gpa)).getNodeSource(decl_src.src_node); const decl_name: ?[]const u8 = if (decl_name_index != .empty) file.zir.nullTerminatedString(decl_name_index) @@ -5830,17 +5777,6 @@ fn walkRef( } } -fn getBlockInlineBreak(zir: Zir, inst: Zir.Inst.Index) ?Zir.Inst.Ref { - const tags = zir.instructions.items(.tag); - const data = zir.instructions.items(.data); - const pl_node = data[@intFromEnum(inst)].pl_node; - const extra = zir.extraData(Zir.Inst.Block, pl_node.payload_index); - const break_index = zir.extra[extra.end..][extra.data.body_len - 1]; - if (tags[break_index] == .condbr_inline) return null; - std.debug.assert(tags[break_index] == .break_inline); - return data[break_index].@"break".operand; -} - fn printWithContext( file: *File, inst: Zir.Inst.Index, diff --git a/src/InternPool.zig b/src/InternPool.zig index 0844e0f7a5..1c5703b055 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -6186,8 +6186,6 @@ fn finishFuncInstance( .generation = generation, .is_pub = fn_owner_decl.is_pub, .is_exported = fn_owner_decl.is_exported, - .has_linksection_or_addrspace = fn_owner_decl.has_linksection_or_addrspace, - .has_align = fn_owner_decl.has_align, .alive = true, .kind = .anon, }); diff --git a/src/Module.zig b/src/Module.zig index 7637e40798..02df2dac67 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -386,11 +386,9 @@ pub const Decl = struct { /// do not need to be loaded into memory in order to compute debug line numbers. /// This value is absolute. src_line: u32, - /// Index to ZIR `extra` array to the entry in the parent's decl structure - /// (the part that says "for every decls_len"). The first item at this index is - /// the contents hash, followed by line, name, etc. - /// For anonymous decls and also the root Decl for a File, this is `none`. - zir_decl_index: Zir.OptionalExtraIndex, + /// Index of the ZIR `declaration` instruction from which this `Decl` was created. + /// For the root `Decl` of a `File` and legacy anonymous decls, this is `.none`. + zir_decl_index: Zir.Inst.OptionalIndex, /// Represents the "shallow" analysis status. For example, for decls that are functions, /// the function type is analyzed with this set to `in_progress`, however, the semantic @@ -442,10 +440,6 @@ pub const Decl = struct { is_pub: bool, /// Whether the corresponding AST decl has a `export` keyword. is_exported: bool, - /// Whether the ZIR code provides an align instruction. - has_align: bool, - /// Whether the ZIR code provides a linksection and address space instruction. - has_linksection_or_addrspace: bool, /// Flag used by garbage collection to mark and sweep. /// Decls which correspond to an AST node always have this field set to `true`. /// Anonymous Decls are initialized with this field set to `false` and then it @@ -471,81 +465,19 @@ pub const Decl = struct { const Index = InternPool.DeclIndex; const OptionalIndex = InternPool.OptionalDeclIndex; - pub const DepsTable = std.AutoArrayHashMapUnmanaged(Decl.Index, DepType); - - /// Later types take priority; e.g. if a dependent decl has both `normal` - /// and `function_body` dependencies on another decl, it will be marked as - /// having a `function_body` dependency. - pub const DepType = enum { - /// The dependent references or uses the dependency's value, so must be - /// updated whenever it is changed. However, if the dependency is a - /// function and its type is unchanged, the dependent does not need to - /// be updated. - normal, - /// The dependent performs an inline or comptime call to the dependency, - /// or is a generic instantiation of it. It must therefore be updated - /// whenever the dependency is updated, even if the function type - /// remained the same. - function_body, - }; - - /// This name is relative to the containing namespace of the decl. - /// The memory is owned by the containing File ZIR. - pub fn getName(decl: Decl, mod: *Module) ?[:0]const u8 { - const zir = decl.getFileScope(mod).zir; - return decl.getNameZir(zir); + /// Asserts that `zir_decl_index` is not `.none`. + fn getDeclaration(decl: Decl, zir: Zir) Zir.Inst.Declaration { + const zir_index = decl.zir_decl_index.unwrap().?; + const pl_node = zir.instructions.items(.data)[@intFromEnum(zir_index)].pl_node; + return zir.extraData(Zir.Inst.Declaration, pl_node.payload_index).data; } - pub fn getNameZir(decl: Decl, zir: Zir) ?[:0]const u8 { - assert(decl.zir_decl_index != .none); - const name_index = zir.extra[@intFromEnum(decl.zir_decl_index) + 5]; - if (name_index <= 1) return null; - return zir.nullTerminatedString(name_index); - } - - pub fn contentsHash(decl: Decl, mod: *Module) std.zig.SrcHash { - const zir = decl.getFileScope(mod).zir; - return decl.contentsHashZir(zir); - } - - pub fn contentsHashZir(decl: Decl, zir: Zir) std.zig.SrcHash { - assert(decl.zir_decl_index != .none); - const hash_u32s = zir.extra[@intFromEnum(decl.zir_decl_index)..][0..4]; - const contents_hash = @as(std.zig.SrcHash, @bitCast(hash_u32s.*)); - return contents_hash; - } - - pub fn zirBlockIndex(decl: *const Decl, mod: *Module) Zir.Inst.Index { - assert(decl.zir_decl_index != .none); - const zir = decl.getFileScope(mod).zir; - return @enumFromInt(zir.extra[@intFromEnum(decl.zir_decl_index) + 6]); - } - - pub fn zirAlignRef(decl: Decl, mod: *Module) Zir.Inst.Ref { - if (!decl.has_align) return .none; - assert(decl.zir_decl_index != .none); - const zir = decl.getFileScope(mod).zir; - return @enumFromInt(zir.extra[@intFromEnum(decl.zir_decl_index) + 8]); - } - - pub fn zirLinksectionRef(decl: Decl, mod: *Module) Zir.Inst.Ref { - if (!decl.has_linksection_or_addrspace) return .none; - assert(decl.zir_decl_index != .none); - const zir = decl.getFileScope(mod).zir; - const extra_index = @intFromEnum(decl.zir_decl_index) + 8 + @intFromBool(decl.has_align); - return @enumFromInt(zir.extra[extra_index]); - } - - pub fn zirAddrspaceRef(decl: Decl, mod: *Module) Zir.Inst.Ref { - if (!decl.has_linksection_or_addrspace) return .none; - assert(decl.zir_decl_index != .none); - const zir = decl.getFileScope(mod).zir; - const extra_index = @intFromEnum(decl.zir_decl_index) + 8 + @intFromBool(decl.has_align) + 1; - return @enumFromInt(zir.extra[extra_index]); - } - - pub fn relativeToLine(decl: Decl, offset: u32) u32 { - return decl.src_line + offset; + pub fn zirBodies(decl: Decl, zcu: *Zcu) Zir.Inst.Declaration.Bodies { + const zir = decl.getFileScope(zcu).zir; + const zir_index = decl.zir_decl_index.unwrap().?; + const pl_node = zir.instructions.items(.data)[@intFromEnum(zir_index)].pl_node; + const extra = zir.extraData(Zir.Inst.Declaration, pl_node.payload_index); + return extra.data.getBodies(@intCast(extra.end), zir); } pub fn relativeToNodeIndex(decl: Decl, offset: i32) Ast.Node.Index { @@ -3015,16 +2947,12 @@ fn updateZirRefs(mod: *Module, file: *File, old_zir: Zir) !void { // The root decl will be null if the previous ZIR had AST errors. const root_decl = file.root_decl.unwrap() orelse return; - // Maps from old ZIR to new ZIR, struct_decl, enum_decl, etc. Any instruction which - // creates a namespace, gets mapped from old to new here. + // Maps from old ZIR to new ZIR, declaration, struct_decl, enum_decl, etc. Any instruction which + // creates a namespace, and any `declaration` instruction, gets mapped from old to new here. var inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{}; defer inst_map.deinit(gpa); - // Maps from old ZIR to new ZIR, the extra data index for the sub-decl item. - // e.g. the thing that Decl.zir_decl_index points to. - var extra_map: std.AutoHashMapUnmanaged(Zir.ExtraIndex, Zir.ExtraIndex) = .{}; - defer extra_map.deinit(gpa); - try mapOldZirToNew(gpa, old_zir, new_zir, &inst_map, &extra_map); + try mapOldZirToNew(gpa, old_zir, new_zir, &inst_map); // Walk the Decl graph, updating ZIR indexes, strings, and populating // the deleted and outdated lists. @@ -3050,7 +2978,7 @@ fn updateZirRefs(mod: *Module, file: *File, old_zir: Zir) !void { // Anonymous decls should not be marked outdated. They will be re-generated // if their owner decl is marked outdated. if (decl.zir_decl_index.unwrap()) |old_zir_decl_index| { - const new_zir_decl_index = extra_map.get(old_zir_decl_index) orelse { + const new_zir_decl_index = inst_map.get(old_zir_decl_index) orelse { try file.deleted_decls.append(gpa, decl_index); continue; }; @@ -3098,9 +3026,9 @@ pub fn mapOldZirToNew( old_zir: Zir, new_zir: Zir, inst_map: *std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index), - extra_map: *std.AutoHashMapUnmanaged(Zir.ExtraIndex, Zir.ExtraIndex), ) Allocator.Error!void { - // Contain ZIR indexes of declaration instructions. + // Contain ZIR indexes of namespace declaration instructions, e.g. struct_decl, union_decl, etc. + // Not `declaration`, as this does not create a namespace. const MatchedZirDecl = struct { old_inst: Zir.Inst.Index, new_inst: Zir.Inst.Index, @@ -3108,47 +3036,113 @@ pub fn mapOldZirToNew( var match_stack: ArrayListUnmanaged(MatchedZirDecl) = .{}; defer match_stack.deinit(gpa); - // Main struct inst is always the same + // Main struct inst is always matched try match_stack.append(gpa, .{ .old_inst = .main_struct_inst, .new_inst = .main_struct_inst, }); + // Used as temporary buffers for namespace declaration instructions var old_decls = std.ArrayList(Zir.Inst.Index).init(gpa); defer old_decls.deinit(); var new_decls = std.ArrayList(Zir.Inst.Index).init(gpa); defer new_decls.deinit(); while (match_stack.popOrNull()) |match_item| { + // Match the namespace declaration itself try inst_map.put(gpa, match_item.old_inst, match_item.new_inst); - // Maps name to extra index of decl sub item. - var decl_map: std.StringHashMapUnmanaged(Zir.ExtraIndex) = .{}; - defer decl_map.deinit(gpa); + // Maps decl name to `declaration` instruction. + var named_decls: std.StringHashMapUnmanaged(Zir.Inst.Index) = .{}; + defer named_decls.deinit(gpa); + // Maps test name to `declaration` instruction. + var named_tests: std.StringHashMapUnmanaged(Zir.Inst.Index) = .{}; + defer named_tests.deinit(gpa); + // All unnamed tests, in order, for a best-effort match. + var unnamed_tests: std.ArrayListUnmanaged(Zir.Inst.Index) = .{}; + defer unnamed_tests.deinit(gpa); + // All comptime declarations, in order, for a best-effort match. + var comptime_decls: std.ArrayListUnmanaged(Zir.Inst.Index) = .{}; + defer comptime_decls.deinit(gpa); + // All usingnamespace declarations, in order, for a best-effort match. + var usingnamespace_decls: std.ArrayListUnmanaged(Zir.Inst.Index) = .{}; + defer usingnamespace_decls.deinit(gpa); { var old_decl_it = old_zir.declIterator(match_item.old_inst); - while (old_decl_it.next()) |old_decl| { - try decl_map.put(gpa, old_decl.name, old_decl.sub_index); + while (old_decl_it.next()) |old_decl_inst| { + const old_decl, _ = old_zir.getDeclaration(old_decl_inst); + switch (old_decl.name) { + .@"comptime" => try comptime_decls.append(gpa, old_decl_inst), + .@"usingnamespace" => try usingnamespace_decls.append(gpa, old_decl_inst), + .unnamed_test, .decltest => try unnamed_tests.append(gpa, old_decl_inst), + _ => { + const name_nts = old_decl.name.toString(old_zir).?; + const name = old_zir.nullTerminatedString(name_nts); + if (old_decl.name.isNamedTest(old_zir)) { + try named_tests.put(gpa, name, old_decl_inst); + } else { + try named_decls.put(gpa, name, old_decl_inst); + } + }, + } } } - var new_decl_it = new_zir.declIterator(match_item.new_inst); - while (new_decl_it.next()) |new_decl| { - const old_extra_index = decl_map.get(new_decl.name) orelse continue; - const new_extra_index = new_decl.sub_index; - try extra_map.put(gpa, old_extra_index, new_extra_index); + var unnamed_test_idx: u32 = 0; + var comptime_decl_idx: u32 = 0; + var usingnamespace_decl_idx: u32 = 0; - try old_zir.findDecls(&old_decls, old_extra_index); - try new_zir.findDecls(&new_decls, new_extra_index); - var i: usize = 0; - while (true) : (i += 1) { - if (i >= old_decls.items.len) break; - if (i >= new_decls.items.len) break; - try match_stack.append(gpa, .{ - .old_inst = old_decls.items[i], - .new_inst = new_decls.items[i], - }); + var new_decl_it = new_zir.declIterator(match_item.new_inst); + while (new_decl_it.next()) |new_decl_inst| { + const new_decl, _ = new_zir.getDeclaration(new_decl_inst); + // Attempt to match this to a declaration in the old ZIR: + // * For named declarations (`const`/`var`/`fn`), we match based on name. + // * For named tests (`test "foo"`), we also match based on name. + // * For unnamed tests and decltests, we match based on order. + // * For comptime blocks, we match based on order. + // * For usingnamespace decls, we match based on order. + // If we cannot match this declaration, we can't match anything nested inside of it either, so we just `continue`. + const old_decl_inst = switch (new_decl.name) { + .@"comptime" => inst: { + if (comptime_decl_idx == comptime_decls.items.len) continue; + defer comptime_decl_idx += 1; + break :inst comptime_decls.items[comptime_decl_idx]; + }, + .@"usingnamespace" => inst: { + if (usingnamespace_decl_idx == usingnamespace_decls.items.len) continue; + defer usingnamespace_decl_idx += 1; + break :inst usingnamespace_decls.items[usingnamespace_decl_idx]; + }, + .unnamed_test, .decltest => inst: { + if (unnamed_test_idx == unnamed_tests.items.len) continue; + defer unnamed_test_idx += 1; + break :inst unnamed_tests.items[unnamed_test_idx]; + }, + _ => inst: { + const name_nts = new_decl.name.toString(old_zir).?; + const name = new_zir.nullTerminatedString(name_nts); + if (new_decl.name.isNamedTest(new_zir)) { + break :inst named_tests.get(name) orelse continue; + } else { + break :inst named_decls.get(name) orelse continue; + } + }, + }; + + // Match the `declaration` instruction + try inst_map.put(gpa, old_decl_inst, new_decl_inst); + + // Find namespace declarations within this declaration + try old_zir.findDecls(&old_decls, old_decl_inst); + try new_zir.findDecls(&new_decls, new_decl_inst); + + // We don't have any smart way of matching up these namespace declarations, so we always + // correlate them based on source order. + const n = @min(old_decls.items.len, new_decls.items.len); + try match_stack.ensureUnusedCapacity(gpa, n); + for (old_decls.items[0..n], new_decls.items[0..n]) |old_inst, new_inst| { + match_stack.appendAssumeCapacity(.{ .old_inst = old_inst, .new_inst = new_inst }); } } } @@ -3457,8 +3451,6 @@ pub fn semaFile(mod: *Module, file: *File) SemaError!void { new_decl.src_line = 0; new_decl.is_pub = true; new_decl.is_exported = false; - new_decl.has_align = false; - new_decl.has_linksection_or_addrspace = false; new_decl.ty = Type.type; new_decl.alignment = .none; new_decl.@"linksection" = .none; @@ -3561,7 +3553,6 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool { const gpa = mod.gpa; const zir = decl.getFileScope(mod).zir; - const zir_datas = zir.instructions.items(.data); const builtin_type_target_index: InternPool.Index = blk: { const std_mod = mod.std_mod; @@ -3639,11 +3630,9 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool { }; defer block_scope.instructions.deinit(gpa); - const zir_block_index = decl.zirBlockIndex(mod); - const inst_data = zir_datas[@intFromEnum(zir_block_index)].pl_node; - const extra = zir.extraData(Zir.Inst.Block, inst_data.payload_index); - const body = zir.extra[extra.end..][0..extra.data.body_len]; - const result_ref = (try sema.analyzeBodyBreak(&block_scope, @ptrCast(body))).?.operand; + const decl_bodies = decl.zirBodies(mod); + + const result_ref = (try sema.analyzeBodyBreak(&block_scope, decl_bodies.value_body)).?.operand; // We'll do some other bits with the Sema. Clear the type target index just // in case they analyze any type. sema.builtin_type_target_index = .none; @@ -3760,13 +3749,13 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool { decl.ty = decl_tv.ty; decl.val = Value.fromInterned((try decl_tv.val.intern(decl_tv.ty, mod))); decl.alignment = blk: { - const align_ref = decl.zirAlignRef(mod); - if (align_ref == .none) break :blk .none; + const align_body = decl_bodies.align_body orelse break :blk .none; + const align_ref = (try sema.analyzeBodyBreak(&block_scope, align_body)).?.operand; break :blk try sema.resolveAlign(&block_scope, align_src, align_ref); }; decl.@"linksection" = blk: { - const linksection_ref = decl.zirLinksectionRef(mod); - if (linksection_ref == .none) break :blk .none; + const linksection_body = decl_bodies.linksection_body orelse break :blk .none; + const linksection_ref = (try sema.analyzeBodyBreak(&block_scope, linksection_body)).?.operand; const bytes = try sema.resolveConstString(&block_scope, section_src, linksection_ref, .{ .needed_comptime_reason = "linksection must be comptime-known", }); @@ -3786,15 +3775,15 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool { }; const target = sema.mod.getTarget(); - break :blk switch (decl.zirAddrspaceRef(mod)) { - .none => switch (addrspace_ctx) { - .function => target_util.defaultAddressSpace(target, .function), - .variable => target_util.defaultAddressSpace(target, .global_mutable), - .constant => target_util.defaultAddressSpace(target, .global_constant), - else => unreachable, - }, - else => |addrspace_ref| try sema.analyzeAddressSpace(&block_scope, address_space_src, addrspace_ref, addrspace_ctx), + + const addrspace_body = decl_bodies.addrspace_body orelse break :blk switch (addrspace_ctx) { + .function => target_util.defaultAddressSpace(target, .function), + .variable => target_util.defaultAddressSpace(target, .global_mutable), + .constant => target_util.defaultAddressSpace(target, .global_constant), + else => unreachable, }; + const addrspace_ref = (try sema.analyzeBodyBreak(&block_scope, addrspace_body)).?.operand; + break :blk try sema.analyzeAddressSpace(&block_scope, address_space_src, addrspace_ref, addrspace_ctx); }; decl.has_tv = true; decl.analysis = .complete; @@ -4133,52 +4122,32 @@ fn newEmbedFile( } pub fn scanNamespace( - mod: *Module, + zcu: *Zcu, namespace_index: Namespace.Index, - extra_start: usize, - decls_len: u32, + decls: []const Zir.Inst.Index, parent_decl: *Decl, -) Allocator.Error!usize { +) Allocator.Error!void { const tracy = trace(@src()); defer tracy.end(); - const gpa = mod.gpa; - const namespace = mod.namespacePtr(namespace_index); - const zir = namespace.file_scope.zir; + const gpa = zcu.gpa; + const namespace = zcu.namespacePtr(namespace_index); - try mod.comp.work_queue.ensureUnusedCapacity(decls_len); - try namespace.decls.ensureTotalCapacity(gpa, decls_len); + try zcu.comp.work_queue.ensureUnusedCapacity(decls.len); + try namespace.decls.ensureTotalCapacity(gpa, decls.len); - const bit_bags_count = std.math.divCeil(usize, decls_len, 8) catch unreachable; - var extra_index = extra_start + bit_bags_count; - var bit_bag_index: usize = extra_start; - var cur_bit_bag: u32 = undefined; - var decl_i: u32 = 0; var scan_decl_iter: ScanDeclIter = .{ - .module = mod, + .zcu = zcu, .namespace_index = namespace_index, .parent_decl = parent_decl, }; - while (decl_i < decls_len) : (decl_i += 1) { - if (decl_i % 8 == 0) { - cur_bit_bag = zir.extra[bit_bag_index]; - bit_bag_index += 1; - } - const flags = @as(u4, @truncate(cur_bit_bag)); - cur_bit_bag >>= 4; - - const decl_sub_index = extra_index; - extra_index += 8; // src_hash(4) + line(1) + name(1) + value(1) + doc_comment(1) - extra_index += @as(u1, @truncate(flags >> 2)); // Align - extra_index += @as(u2, @as(u1, @truncate(flags >> 3))) * 2; // Link section or address space, consists of 2 Refs - - try scanDecl(&scan_decl_iter, decl_sub_index, flags); + for (decls) |decl_inst| { + try scanDecl(&scan_decl_iter, decl_inst); } - return extra_index; } const ScanDeclIter = struct { - module: *Module, + zcu: *Zcu, namespace_index: Namespace.Index, parent_decl: *Decl, usingnamespace_index: usize = 0, @@ -4186,119 +4155,112 @@ const ScanDeclIter = struct { unnamed_test_index: usize = 0, }; -fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) Allocator.Error!void { +fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void { const tracy = trace(@src()); defer tracy.end(); - const mod = iter.module; + const zcu = iter.zcu; const namespace_index = iter.namespace_index; - const namespace = mod.namespacePtr(namespace_index); - const gpa = mod.gpa; + const namespace = zcu.namespacePtr(namespace_index); + const gpa = zcu.gpa; const zir = namespace.file_scope.zir; - const ip = &mod.intern_pool; + const ip = &zcu.intern_pool; - // zig fmt: off - const is_pub = (flags & 0b0001) != 0; - const export_bit = (flags & 0b0010) != 0; - const has_align = (flags & 0b0100) != 0; - const has_linksection_or_addrspace = (flags & 0b1000) != 0; - // zig fmt: on + const pl_node = zir.instructions.items(.data)[@intFromEnum(decl_inst)].pl_node; + const extra = zir.extraData(Zir.Inst.Declaration, pl_node.payload_index); + const declaration = extra.data; - const line_off = zir.extra[decl_sub_index + 4]; - const line = iter.parent_decl.relativeToLine(line_off); - const decl_name_index: Zir.NullTerminatedString = @enumFromInt(zir.extra[decl_sub_index + 5]); - const decl_doccomment_index = zir.extra[decl_sub_index + 7]; - const decl_zir_index = zir.extra[decl_sub_index + 6]; - const decl_block_inst_data = zir.instructions.items(.data)[decl_zir_index].pl_node; - const decl_node = iter.parent_decl.relativeToNodeIndex(decl_block_inst_data.src_node); + const line = iter.parent_decl.src_line + declaration.line_offset; + const decl_node = iter.parent_decl.relativeToNodeIndex(pl_node.src_node); // Every Decl needs a name. - var is_named_test = false; - var kind: Decl.Kind = .named; - const decl_name: InternPool.NullTerminatedString = switch (decl_name_index) { - .empty => name: { - if (export_bit) { - const i = iter.usingnamespace_index; - iter.usingnamespace_index += 1; - kind = .@"usingnamespace"; - break :name try ip.getOrPutStringFmt(gpa, "usingnamespace_{d}", .{i}); - } else { - const i = iter.comptime_index; - iter.comptime_index += 1; - kind = .@"comptime"; - break :name try ip.getOrPutStringFmt(gpa, "comptime_{d}", .{i}); - } + const decl_name: InternPool.NullTerminatedString, const kind: Decl.Kind, const is_named_test: bool = switch (declaration.name) { + .@"comptime" => info: { + const i = iter.comptime_index; + iter.comptime_index += 1; + break :info .{ + try ip.getOrPutStringFmt(gpa, "comptime_{d}", .{i}), + .@"comptime", + false, + }; }, - .unnamed_test_decl => name: { + .@"usingnamespace" => info: { + const i = iter.usingnamespace_index; + iter.usingnamespace_index += 1; + break :info .{ + try ip.getOrPutStringFmt(gpa, "usingnamespace_{d}", .{i}), + .@"usingnamespace", + false, + }; + }, + .unnamed_test => info: { const i = iter.unnamed_test_index; iter.unnamed_test_index += 1; - kind = .@"test"; - break :name try ip.getOrPutStringFmt(gpa, "test_{d}", .{i}); + break :info .{ + try ip.getOrPutStringFmt(gpa, "test_{d}", .{i}), + .@"test", + false, + }; }, - .decltest => name: { - is_named_test = true; - const test_name = zir.nullTerminatedString(@enumFromInt(decl_doccomment_index)); - kind = .@"test"; - break :name try ip.getOrPutStringFmt(gpa, "decltest.{s}", .{test_name}); + .decltest => info: { + assert(declaration.flags.has_doc_comment); + const name = zir.nullTerminatedString(@enumFromInt(zir.extra[extra.end])); + break :info .{ + try ip.getOrPutStringFmt(gpa, "decltest.{s}", .{name}), + .@"test", + true, + }; }, - _ => name: { - const raw_name = zir.nullTerminatedString(decl_name_index); - if (raw_name.len == 0) { - is_named_test = true; - const test_name = zir.nullTerminatedString(@enumFromInt(@intFromEnum(decl_name_index) + 1)); - kind = .@"test"; - break :name try ip.getOrPutStringFmt(gpa, "test.{s}", .{test_name}); - } else { - break :name try ip.getOrPutString(gpa, raw_name); - } + _ => if (declaration.name.isNamedTest(zir)) .{ + try ip.getOrPutStringFmt(gpa, "test.{s}", .{zir.nullTerminatedString(declaration.name.toString(zir).?)}), + .@"test", + true, + } else .{ + try ip.getOrPutString(gpa, zir.nullTerminatedString(declaration.name.toString(zir).?)), + .named, + false, }, }; - const is_exported = export_bit and decl_name_index != .empty; if (kind == .@"usingnamespace") try namespace.usingnamespace_set.ensureUnusedCapacity(gpa, 1); // We create a Decl for it regardless of analysis status. const gop = try namespace.decls.getOrPutContextAdapted( gpa, decl_name, - DeclAdapter{ .mod = mod }, - Namespace.DeclContext{ .module = mod }, + DeclAdapter{ .mod = zcu }, + Namespace.DeclContext{ .module = zcu }, ); - const comp = mod.comp; + const comp = zcu.comp; if (!gop.found_existing) { - const new_decl_index = try mod.allocateNewDecl(namespace_index, decl_node, iter.parent_decl.src_scope); - const new_decl = mod.declPtr(new_decl_index); + const new_decl_index = try zcu.allocateNewDecl(namespace_index, decl_node, iter.parent_decl.src_scope); + const new_decl = zcu.declPtr(new_decl_index); new_decl.kind = kind; new_decl.name = decl_name; if (kind == .@"usingnamespace") { - namespace.usingnamespace_set.putAssumeCapacity(new_decl_index, is_pub); + namespace.usingnamespace_set.putAssumeCapacity(new_decl_index, declaration.flags.is_pub); } new_decl.src_line = line; gop.key_ptr.* = new_decl_index; // Exported decls, comptime decls, usingnamespace decls, and // test decls if in test mode, get analyzed. const decl_mod = namespace.file_scope.mod; - const want_analysis = is_exported or switch (decl_name_index) { - .empty => true, // comptime or usingnamespace decl - .unnamed_test_decl => blk: { - // test decl with no name. Skip the part where we check against - // the test name filter. - if (!comp.config.is_test) break :blk false; - if (decl_mod != mod.main_mod) break :blk false; - try mod.test_functions.put(gpa, new_decl_index, {}); - break :blk true; - }, - else => blk: { - if (!is_named_test) break :blk false; - if (!comp.config.is_test) break :blk false; - if (decl_mod != mod.main_mod) break :blk false; - if (comp.test_filter) |test_filter| { - if (mem.indexOf(u8, ip.stringToSlice(decl_name), test_filter) == null) { - break :blk false; + const want_analysis = declaration.flags.is_export or switch (kind) { + .anon => unreachable, + .@"comptime", .@"usingnamespace" => true, + .named => false, + .@"test" => a: { + if (!comp.config.is_test) break :a false; + if (decl_mod != zcu.main_mod) break :a false; + if (is_named_test) { + if (comp.test_filter) |test_filter| { + if (mem.indexOf(u8, ip.stringToSlice(decl_name), test_filter) == null) { + break :a false; + } } } - try mod.test_functions.put(gpa, new_decl_index, {}); - break :blk true; + try zcu.test_functions.put(gpa, new_decl_index, {}); + break :a true; }, }; if (want_analysis) { @@ -4307,46 +4269,42 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) Allocator.Err }); comp.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl_index }); } - new_decl.is_pub = is_pub; - new_decl.is_exported = is_exported; - new_decl.has_align = has_align; - new_decl.has_linksection_or_addrspace = has_linksection_or_addrspace; - new_decl.zir_decl_index = @enumFromInt(decl_sub_index); + new_decl.is_pub = declaration.flags.is_pub; + new_decl.is_exported = declaration.flags.is_export; + new_decl.zir_decl_index = decl_inst.toOptional(); new_decl.alive = true; // This Decl corresponds to an AST node and therefore always alive. return; } const decl_index = gop.key_ptr.*; - const decl = mod.declPtr(decl_index); + const decl = zcu.declPtr(decl_index); if (kind == .@"test") { const src_loc = SrcLoc{ - .file_scope = decl.getFileScope(mod), + .file_scope = decl.getFileScope(zcu), .parent_decl_node = decl.src_node, .lazy = .{ .token_offset = 1 }, }; const msg = try ErrorMsg.create(gpa, src_loc, "duplicate test name: {}", .{ - decl_name.fmt(&mod.intern_pool), + decl_name.fmt(ip), }); errdefer msg.destroy(gpa); - try mod.failed_decls.putNoClobber(gpa, decl_index, msg); + try zcu.failed_decls.putNoClobber(gpa, decl_index, msg); const other_src_loc = SrcLoc{ .file_scope = namespace.file_scope, .parent_decl_node = decl_node, .lazy = .{ .token_offset = 1 }, }; - try mod.errNoteNonLazy(other_src_loc, msg, "other test here", .{}); + try zcu.errNoteNonLazy(other_src_loc, msg, "other test here", .{}); } // Update the AST node of the decl; even if its contents are unchanged, it may // have been re-ordered. decl.src_node = decl_node; decl.src_line = line; - decl.is_pub = is_pub; - decl.is_exported = is_exported; + decl.is_pub = declaration.flags.is_pub; + decl.is_exported = declaration.flags.is_export; decl.kind = kind; - decl.has_align = has_align; - decl.has_linksection_or_addrspace = has_linksection_or_addrspace; - decl.zir_decl_index = @enumFromInt(decl_sub_index); - if (decl.getOwnedFunction(mod) != null) { + decl.zir_decl_index = decl_inst.toOptional(); + if (decl.getOwnedFunction(zcu) != null) { // TODO Look into detecting when this would be unnecessary by storing enough state // in `Decl` to notice that the line number did not change. comp.work_queue.writeItemAssumeCapacity(.{ .update_line_number = decl_index }); @@ -4730,8 +4688,6 @@ pub fn allocateNewDecl( .generation = 0, .is_pub = false, .is_exported = false, - .has_linksection_or_addrspace = false, - .has_align = false, .alive = false, .kind = .anon, }); diff --git a/src/Sema.zig b/src/Sema.zig index 4244dd9aed..fc6cf0c017 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1224,6 +1224,10 @@ fn analyzeBodyInner( .trap => break sema.zirTrap(block, inst), // zig fmt: on + // This instruction never exists in an analyzed body. It exists only in the declaration + // list for a container type. + .declaration => unreachable, + .extended => ext: { const extended = datas[@intFromEnum(inst)].extended; break :ext switch (extended.opcode) { @@ -2736,7 +2740,9 @@ pub fn getStructType( } } - extra_index = try mod.scanNamespace(namespace, extra_index, decls_len, mod.declPtr(decl)); + const decls = sema.code.bodySlice(extra_index, decls_len); + try mod.scanNamespace(namespace, decls, mod.declPtr(decl)); + extra_index += decls_len; const ty = try ip.getStructType(gpa, .{ .decl = decl, @@ -2973,7 +2979,9 @@ fn zirEnumDecl( const new_namespace = mod.namespacePtr(new_namespace_index); errdefer if (!done) mod.destroyNamespace(new_namespace_index); - extra_index = try mod.scanNamespace(new_namespace_index, extra_index, decls_len, new_decl); + const decls = sema.code.bodySlice(extra_index, decls_len); + try mod.scanNamespace(new_namespace_index, decls, new_decl); + extra_index += decls_len; const body = sema.code.bodySlice(extra_index, body_len); extra_index += body.len; @@ -3263,7 +3271,8 @@ fn zirUnionDecl( new_decl.val = Value.fromInterned(union_ty); new_namespace.ty = Type.fromInterned(union_ty); - _ = try mod.scanNamespace(new_namespace_index, extra_index, decls_len, new_decl); + const decls = sema.code.bodySlice(extra_index, decls_len); + try mod.scanNamespace(new_namespace_index, decls, new_decl); const decl_val = sema.analyzeDeclVal(block, src, new_decl_index); try mod.finalizeAnonDecl(new_decl_index); @@ -3326,7 +3335,8 @@ fn zirOpaqueDecl( new_decl.val = Value.fromInterned(opaque_ty); new_namespace.ty = Type.fromInterned(opaque_ty); - extra_index = try mod.scanNamespace(new_namespace_index, extra_index, decls_len, new_decl); + const decls = sema.code.bodySlice(extra_index, decls_len); + try mod.scanNamespace(new_namespace_index, decls, new_decl); const decl_val = sema.analyzeDeclVal(block, src, new_decl_index); try mod.finalizeAnonDecl(new_decl_index); @@ -36331,9 +36341,7 @@ fn structZirInfo(zir: Zir, zir_index: Zir.Inst.Index) struct { } // Skip over decls. - var decls_it = zir.declIteratorInner(extra_index, decls_len); - while (decls_it.next()) |_| {} - extra_index = decls_it.extra_index; + extra_index += decls_len; return .{ fields_len, small, extra_index }; } @@ -36802,9 +36810,7 @@ fn semaUnionFields(mod: *Module, arena: Allocator, union_type: InternPool.Key.Un } else 0; // Skip over decls. - var decls_it = zir.declIteratorInner(extra_index, decls_len); - while (decls_it.next()) |_| {} - extra_index = decls_it.extra_index; + extra_index += decls_len; const body = zir.bodySlice(extra_index, body_len); extra_index += body.len; @@ -37801,10 +37807,12 @@ pub fn analyzeAddressSpace( ctx: AddressSpaceContext, ) !std.builtin.AddressSpace { const mod = sema.mod; - const addrspace_tv = try sema.resolveInstConst(block, src, zir_ref, .{ + const air_ref = try sema.resolveInst(zir_ref); + const coerced = try sema.coerce(block, Type.fromInterned(.address_space_type), air_ref, src); + const addrspace_val = try sema.resolveConstDefinedValue(block, src, coerced, .{ .needed_comptime_reason = "address space must be comptime-known", }); - const address_space = mod.toEnum(std.builtin.AddressSpace, addrspace_tv.val); + const address_space = mod.toEnum(std.builtin.AddressSpace, addrspace_val); const target = sema.mod.getTarget(); const arch = target.cpu.arch; diff --git a/src/Zir.zig b/src/Zir.zig index 3737467e47..4462083b1f 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -30,7 +30,7 @@ instructions: std.MultiArrayList(Inst).Slice, /// is referencing the data here whether they want to store both index and length, /// thus allowing null bytes, or store only index, and use null-termination. The /// `string_bytes` array is agnostic to either usage. -/// Indexes 0 and 1 are reserved for special cases. +/// Index 0 is reserved for special cases. string_bytes: []u8, /// The meaning of this data is determined by `Inst.Tag` value. /// The first few indexes are reserved. See `ExtraIndex` for the values. @@ -60,21 +60,6 @@ pub const ExtraIndex = enum(u32) { imports, _, - - pub fn toOptional(i: ExtraIndex) OptionalExtraIndex { - return @enumFromInt(@intFromEnum(i)); - } -}; - -pub const OptionalExtraIndex = enum(u32) { - compile_errors, - imports, - none = std.math.maxInt(u32), - _, - - pub fn unwrap(oi: OptionalExtraIndex) ?ExtraIndex { - return if (oi == .none) null else @enumFromInt(@intFromEnum(oi)); - } }; fn ExtraData(comptime T: type) type { @@ -93,6 +78,7 @@ pub fn extraData(code: Zir, comptime T: type, index: usize) ExtraData(T) { Inst.Ref, Inst.Index, + Inst.Declaration.Name, NullTerminatedString, => @enumFromInt(code.extra[i]), @@ -102,6 +88,7 @@ pub fn extraData(code: Zir, comptime T: type, index: usize) ExtraData(T) { Inst.SwitchBlock.Bits, Inst.SwitchBlockErrUnion.Bits, Inst.FuncFancy.Bits, + Inst.Declaration.Flags, => @bitCast(code.extra[i]), else => @compileError("bad field type"), @@ -116,8 +103,6 @@ pub fn extraData(code: Zir, comptime T: type, index: usize) ExtraData(T) { pub const NullTerminatedString = enum(u32) { empty = 0, - unnamed_test_decl = 1, - decltest = 2, _, }; @@ -304,6 +289,12 @@ pub const Inst = struct { /// a noreturn instruction. /// Uses the `pl_node` union field. Payload is `Block`. block_inline, + /// This instruction may only ever appear in the list of declarations for a + /// namespace type, e.g. within a `struct_decl` instruction. It represents a + /// single source declaration (`const`/`var`/`fn`), containing the name, + /// attributes, type, and value of the declaration. + /// Uses the `pl_node` union field. Payload is `Declaration`. + declaration, /// Implements `suspend {...}`. /// Uses the `pl_node` union field. Payload is `Block`. suspend_block, @@ -1092,6 +1083,7 @@ pub const Inst = struct { .block, .block_comptime, .block_inline, + .declaration, .suspend_block, .loop, .bool_br_and, @@ -1405,6 +1397,7 @@ pub const Inst = struct { .block, .block_comptime, .block_inline, + .declaration, .suspend_block, .loop, .bool_br_and, @@ -1639,6 +1632,7 @@ pub const Inst = struct { .block = .pl_node, .block_comptime = .pl_node, .block_inline = .pl_node, + .declaration = .pl_node, .suspend_block = .pl_node, .bool_not = .un_node, .bool_br_and = .bool_br, @@ -2508,6 +2502,7 @@ pub const Inst = struct { /// If this is 1 it means return_type is a simple Ref ret_body_len: u32, /// Points to the block that contains the param instructions for this function. + /// If this is a `declaration`, it refers to the declaration's value body. param_block: Index, body_len: u32, @@ -2565,6 +2560,7 @@ pub const Inst = struct { /// 18. src_locs: Func.SrcLocs // if body_len != 0 pub const FuncFancy = struct { /// Points to the block that contains the param instructions for this function. + /// If this is a `declaration`, it refers to the declaration's value body. param_block: Index, body_len: u32, bits: Bits, @@ -2632,6 +2628,116 @@ pub const Inst = struct { body_len: u32, }; + /// Trailing: + /// 0. doc_comment: u32 // if `has_doc_comment`; null-terminated string index + /// 1. align_body_len: u32 // if `has_align_linksection_addrspace`; 0 means no `align` + /// 2. linksection_body_len: u32 // if `has_align_linksection_addrspace`; 0 means no `linksection` + /// 3. addrspace_body_len: u32 // if `has_align_linksection_addrspace`; 0 means no `addrspace` + /// 4. value_body_inst: Zir.Inst.Index + /// - for each `value_body_len` + /// - body to be exited via `break_inline` to this `declaration` instruction + /// 5. align_body_inst: Zir.Inst.Index + /// - for each `align_body_len` + /// - body to be exited via `break_inline` to this `declaration` instruction + /// 6. linksection_body_inst: Zir.Inst.Index + /// - for each `linksection_body_len` + /// - body to be exited via `break_inline` to this `declaration` instruction + /// 7. addrspace_body_inst: Zir.Inst.Index + /// - for each `addrspace_body_len` + /// - body to be exited via `break_inline` to this `declaration` instruction + pub const Declaration = struct { + // These fields should be concatenated and reinterpreted as a `std.zig.SrcHash`. + src_hash_0: u32, + src_hash_1: u32, + src_hash_2: u32, + src_hash_3: u32, + /// The name of this `Decl`. Also indicates whether it is a test, comptime block, etc. + name: Name, + /// This Decl's line number relative to that of its parent. + /// TODO: column must be encoded similarly to respect non-formatted code! + line_offset: u32, + flags: Flags, + + pub const Flags = packed struct(u32) { + value_body_len: u28, + is_pub: bool, + is_export: bool, + has_doc_comment: bool, + has_align_linksection_addrspace: bool, + }; + + pub const Name = enum(u32) { + @"comptime" = std.math.maxInt(u32), + @"usingnamespace" = std.math.maxInt(u32) - 1, + unnamed_test = std.math.maxInt(u32) - 2, + /// In this case, `has_doc_comment` will be true, and the doc + /// comment body is the identifier name. + decltest = std.math.maxInt(u32) - 3, + /// Other values are `NullTerminatedString` values, i.e. index into + /// `string_bytes`. If the byte referenced is 0, the decl is a named + /// test, and the actual name begins at the following byte. + _, + + pub fn isNamedTest(name: Name, zir: Zir) bool { + return switch (name) { + .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => false, + _ => zir.string_bytes[@intFromEnum(name)] == 0, + }; + } + pub fn toString(name: Name, zir: Zir) ?NullTerminatedString { + switch (name) { + .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => return null, + _ => {}, + } + const idx: u32 = @intFromEnum(name); + if (zir.string_bytes[idx] == 0) { + // Named test + return @enumFromInt(idx + 1); + } + return @enumFromInt(idx); + } + }; + + pub const Bodies = struct { + value_body: []const Index, + align_body: ?[]const Index, + linksection_body: ?[]const Index, + addrspace_body: ?[]const Index, + }; + + pub fn getBodies(declaration: Declaration, extra_end: u32, zir: Zir) Bodies { + var extra_index: u32 = extra_end; + extra_index += @intFromBool(declaration.flags.has_doc_comment); + const value_body_len = declaration.flags.value_body_len; + const align_body_len, const linksection_body_len, const addrspace_body_len = lens: { + if (!declaration.flags.has_align_linksection_addrspace) { + break :lens .{ 0, 0, 0 }; + } + const lens = zir.extra[extra_index..][0..3].*; + extra_index += 3; + break :lens lens; + }; + return .{ + .value_body = b: { + defer extra_index += value_body_len; + break :b zir.bodySlice(extra_index, value_body_len); + }, + .align_body = if (align_body_len == 0) null else b: { + defer extra_index += align_body_len; + break :b zir.bodySlice(extra_index, align_body_len); + }, + .linksection_body = if (linksection_body_len == 0) null else b: { + defer extra_index += linksection_body_len; + break :b zir.bodySlice(extra_index, linksection_body_len); + }, + .addrspace_body = if (addrspace_body_len == 0) null else b: { + defer extra_index += addrspace_body_len; + break :b zir.bodySlice(extra_index, addrspace_body_len); + }, + }; + } + }; + /// Stored inside extra, with trailing arguments according to `args_len`. /// Implicit 0. arg_0_start: u32, // always same as `args_len` /// 1. arg_end: u32, // for each `args_len` @@ -2913,37 +3019,14 @@ pub const Inst = struct { /// 3. backing_int_body_len: u32, // if has_backing_int /// 4. backing_int_ref: Ref, // if has_backing_int and backing_int_body_len is 0 /// 5. backing_int_body_inst: Inst, // if has_backing_int and backing_int_body_len is > 0 - /// 6. decl_bits: u32 // for every 8 decls - /// - sets of 4 bits: - /// 0b000X: whether corresponding decl is pub - /// 0b00X0: whether corresponding decl is exported - /// 0b0X00: whether corresponding decl has an align expression - /// 0bX000: whether corresponding decl has a linksection or an address space expression - /// 7. decl: { // for every decls_len - /// src_hash: [4]u32, // hash of source bytes - /// line: u32, // line number of decl, relative to parent - /// name: NullTerminatedString, // null terminated string index - /// - 0 means comptime or usingnamespace decl. - /// - if name == 0 `is_exported` determines which one: 0=comptime,1=usingnamespace - /// - 1 means test decl with no name. - /// - 2 means that the test is a decltest, doc_comment gives the name of the identifier - /// - if there is a 0 byte at the position `name` indexes, it indicates - /// this is a test decl, and the name starts at `name+1`. - /// value: Index, - /// doc_comment: u32, .empty if no doc comment, if this is a decltest, doc_comment references the decl name in the string table - /// align: Ref, // if corresponding bit is set - /// link_section_or_address_space: { // if corresponding bit is set. - /// link_section: Ref, - /// address_space: Ref, - /// } - /// } - /// 8. flags: u32 // for every 8 fields + /// 6. decl: Index, // for every decls_len; points to a `declaration` instruction + /// 7. flags: u32 // for every 8 fields /// - sets of 4 bits: /// 0b000X: whether corresponding field has an align expression /// 0b00X0: whether corresponding field has a default expression /// 0b0X00: whether corresponding field is comptime /// 0bX000: whether corresponding field has a type expression - /// 9. fields: { // for every fields_len + /// 8. fields: { // for every fields_len /// field_name: u32, // if !is_tuple /// doc_comment: NullTerminatedString, // .empty if no doc comment /// field_type: Ref, // if corresponding bit is not set. none means anytype. @@ -3009,33 +3092,11 @@ pub const Inst = struct { /// 2. body_len: u32, // if has_body_len /// 3. fields_len: u32, // if has_fields_len /// 4. decls_len: u32, // if has_decls_len - /// 5. decl_bits: u32 // for every 8 decls - /// - sets of 4 bits: - /// 0b000X: whether corresponding decl is pub - /// 0b00X0: whether corresponding decl is exported - /// 0b0X00: whether corresponding decl has an align expression - /// 0bX000: whether corresponding decl has a linksection or an address space expression - /// 6. decl: { // for every decls_len - /// src_hash: [4]u32, // hash of source bytes - /// line: u32, // line number of decl, relative to parent - /// name: NullTerminatedString, // null terminated string index - /// - 0 means comptime or usingnamespace decl. - /// - if name == 0 `is_exported` determines which one: 0=comptime,1=usingnamespace - /// - 1 means test decl with no name. - /// - if there is a 0 byte at the position `name` indexes, it indicates - /// this is a test decl, and the name starts at `name+1`. - /// value: Index, - /// doc_comment: u32, // .empty if no doc_comment - /// align: Ref, // if corresponding bit is set - /// link_section_or_address_space: { // if corresponding bit is set. - /// link_section: Ref, - /// address_space: Ref, - /// } - /// } - /// 7. inst: Index // for every body_len - /// 8. has_bits: u32 // for every 32 fields + /// 5. decl: Index, // for every decls_len; points to a `declaration` instruction + /// 6. inst: Index // for every body_len + /// 7. has_bits: u32 // for every 32 fields /// - the bit is whether corresponding field has an value expression - /// 9. fields: { // for every fields_len + /// 8. fields: { // for every fields_len /// field_name: u32, /// doc_comment: u32, // .empty if no doc_comment /// value: Ref, // if corresponding bit is set @@ -3059,37 +3120,15 @@ pub const Inst = struct { /// 2. body_len: u32, // if has_body_len /// 3. fields_len: u32, // if has_fields_len /// 4. decls_len: u32, // if has_decls_len - /// 5. decl_bits: u32 // for every 8 decls - /// - sets of 4 bits: - /// 0b000X: whether corresponding decl is pub - /// 0b00X0: whether corresponding decl is exported - /// 0b0X00: whether corresponding decl has an align expression - /// 0bX000: whether corresponding decl has a linksection or an address space expression - /// 6. decl: { // for every decls_len - /// src_hash: [4]u32, // hash of source bytes - /// line: u32, // line number of decl, relative to parent - /// name: NullTerminatedString, // null terminated string index - /// - 0 means comptime or usingnamespace decl. - /// - if name == 0 `is_exported` determines which one: 0=comptime,1=usingnamespace - /// - 1 means test decl with no name. - /// - if there is a 0 byte at the position `name` indexes, it indicates - /// this is a test decl, and the name starts at `name+1`. - /// value: Index, - /// doc_comment: NullTerminatedString, // .empty if no doc comment - /// align: Ref, // if corresponding bit is set - /// link_section_or_address_space: { // if corresponding bit is set. - /// link_section: Ref, - /// address_space: Ref, - /// } - /// } - /// 7. inst: Index // for every body_len - /// 8. has_bits: u32 // for every 8 fields + /// 5. decl: Index, // for every decls_len; points to a `declaration` instruction + /// 6. inst: Index // for every body_len + /// 7. has_bits: u32 // for every 8 fields /// - sets of 4 bits: /// 0b000X: whether corresponding field has a type expression /// 0b00X0: whether corresponding field has a align expression /// 0b0X00: whether corresponding field has a tag value expression /// 0bX000: unused - /// 9. fields: { // for every fields_len + /// 8. fields: { // for every fields_len /// field_name: NullTerminatedString, // null terminated string index /// doc_comment: NullTerminatedString, // .empty if no doc comment /// field_type: Ref, // if corresponding bit is set @@ -3121,29 +3160,7 @@ pub const Inst = struct { /// Trailing: /// 0. src_node: i32, // if has_src_node /// 1. decls_len: u32, // if has_decls_len - /// 2. decl_bits: u32 // for every 8 decls - /// - sets of 4 bits: - /// 0b000X: whether corresponding decl is pub - /// 0b00X0: whether corresponding decl is exported - /// 0b0X00: whether corresponding decl has an align expression - /// 0bX000: whether corresponding decl has a linksection or an address space expression - /// 3. decl: { // for every decls_len - /// src_hash: [4]u32, // hash of source bytes - /// line: u32, // line number of decl, relative to parent - /// name: NullTerminatedString, // null terminated string index - /// - 0 means comptime or usingnamespace decl. - /// - if name == 0 `is_exported` determines which one: 0=comptime,1=usingnamespace - /// - 1 means test decl with no name. - /// - if there is a 0 byte at the position `name` indexes, it indicates - /// this is a test decl, and the name starts at `name+1`. - /// value: Index, - /// doc_comment: NullTerminatedString, // .empty if no doc comment, - /// align: Ref, // if corresponding bit is set - /// link_section_or_address_space: { // if corresponding bit is set. - /// link_section: Ref, - /// address_space: Ref, - /// } - /// } + /// 2. decl: Index, // for every decls_len; points to a `declaration` instruction pub const OpaqueDecl = struct { pub const Small = packed struct { has_src_node: bool, @@ -3407,44 +3424,17 @@ pub const Inst = struct { pub const SpecialProng = enum { none, @"else", under }; pub const DeclIterator = struct { - extra_index: usize, - bit_bag_index: usize, - cur_bit_bag: u32, - decl_i: u32, - decls_len: u32, + extra_index: u32, + decls_remaining: u32, zir: Zir, - pub const Item = struct { - name: [:0]const u8, - sub_index: ExtraIndex, - flags: u4, - }; - - pub fn next(it: *DeclIterator) ?Item { - if (it.decl_i >= it.decls_len) return null; - - if (it.decl_i % 8 == 0) { - it.cur_bit_bag = it.zir.extra[it.bit_bag_index]; - it.bit_bag_index += 1; - } - it.decl_i += 1; - - const flags: u4 = @truncate(it.cur_bit_bag); - it.cur_bit_bag >>= 4; - - const sub_index: ExtraIndex = @enumFromInt(it.extra_index); - it.extra_index += 5; // src_hash(4) + line(1) - const name = it.zir.nullTerminatedString(@enumFromInt(it.zir.extra[it.extra_index])); - it.extra_index += 3; // name(1) + value(1) + doc_comment(1) - it.extra_index += @as(u1, @truncate(flags >> 2)); // align - it.extra_index += @as(u1, @truncate(flags >> 3)); // link_section - it.extra_index += @as(u1, @truncate(flags >> 3)); // address_space - - return Item{ - .sub_index = sub_index, - .name = name, - .flags = flags, - }; + pub fn next(it: *DeclIterator) ?Inst.Index { + if (it.decls_remaining == 0) return null; + const decl_inst: Zir.Inst.Index = @enumFromInt(it.zir.extra[it.extra_index]); + it.extra_index += 1; + it.decls_remaining -= 1; + assert(it.zir.instructions.items(.tag)[@intFromEnum(decl_inst)] == .declaration); + return decl_inst; } }; @@ -3454,14 +3444,18 @@ pub fn declIterator(zir: Zir, decl_inst: Zir.Inst.Index) DeclIterator { switch (tags[@intFromEnum(decl_inst)]) { // Functions are allowed and yield no iterations. // There is one case matching this in the extended instruction set below. - .func, .func_inferred, .func_fancy => return declIteratorInner(zir, 0, 0), + .func, .func_inferred, .func_fancy => return .{ + .extra_index = undefined, + .decls_remaining = 0, + .zir = zir, + }, .extended => { const extended = datas[@intFromEnum(decl_inst)].extended; switch (extended.opcode) { .struct_decl => { const small: Inst.StructDecl.Small = @bitCast(extended.small); - var extra_index: usize = extended.operand; + var extra_index: u32 = extended.operand; extra_index += @intFromBool(small.has_src_node); extra_index += @intFromBool(small.has_fields_len); const decls_len = if (small.has_decls_len) decls_len: { @@ -3480,11 +3474,15 @@ pub fn declIterator(zir: Zir, decl_inst: Zir.Inst.Index) DeclIterator { } } - return declIteratorInner(zir, extra_index, decls_len); + return .{ + .extra_index = extra_index, + .decls_remaining = decls_len, + .zir = zir, + }; }, .enum_decl => { const small: Inst.EnumDecl.Small = @bitCast(extended.small); - var extra_index: usize = extended.operand; + var extra_index: u32 = extended.operand; extra_index += @intFromBool(small.has_src_node); extra_index += @intFromBool(small.has_tag_type); extra_index += @intFromBool(small.has_body_len); @@ -3495,11 +3493,15 @@ pub fn declIterator(zir: Zir, decl_inst: Zir.Inst.Index) DeclIterator { break :decls_len decls_len; } else 0; - return declIteratorInner(zir, extra_index, decls_len); + return .{ + .extra_index = extra_index, + .decls_remaining = decls_len, + .zir = zir, + }; }, .union_decl => { const small: Inst.UnionDecl.Small = @bitCast(extended.small); - var extra_index: usize = extended.operand; + var extra_index: u32 = extended.operand; extra_index += @intFromBool(small.has_src_node); extra_index += @intFromBool(small.has_tag_type); extra_index += @intFromBool(small.has_body_len); @@ -3510,11 +3512,15 @@ pub fn declIterator(zir: Zir, decl_inst: Zir.Inst.Index) DeclIterator { break :decls_len decls_len; } else 0; - return declIteratorInner(zir, extra_index, decls_len); + return .{ + .extra_index = extra_index, + .decls_remaining = decls_len, + .zir = zir, + }; }, .opaque_decl => { const small: Inst.OpaqueDecl.Small = @bitCast(extended.small); - var extra_index: usize = extended.operand; + var extra_index: u32 = extended.operand; extra_index += @intFromBool(small.has_src_node); const decls_len = if (small.has_decls_len) decls_len: { const decls_len = zir.extra[extra_index]; @@ -3522,7 +3528,11 @@ pub fn declIterator(zir: Zir, decl_inst: Zir.Inst.Index) DeclIterator { break :decls_len decls_len; } else 0; - return declIteratorInner(zir, extra_index, decls_len); + return .{ + .extra_index = extra_index, + .decls_remaining = decls_len, + .zir = zir, + }; }, else => unreachable, } @@ -3531,25 +3541,17 @@ pub fn declIterator(zir: Zir, decl_inst: Zir.Inst.Index) DeclIterator { } } -pub fn declIteratorInner(zir: Zir, extra_index: usize, decls_len: u32) DeclIterator { - const bit_bags_count = std.math.divCeil(usize, decls_len, 8) catch unreachable; - return .{ - .zir = zir, - .extra_index = extra_index + bit_bags_count, - .bit_bag_index = extra_index, - .cur_bit_bag = undefined, - .decl_i = 0, - .decls_len = decls_len, - }; -} - /// The iterator would have to allocate memory anyway to iterate. So here we populate /// an ArrayList as the result. -pub fn findDecls(zir: Zir, list: *std.ArrayList(Inst.Index), decl_sub_index: ExtraIndex) !void { - const block_inst: Zir.Inst.Index = @enumFromInt(zir.extra[@intFromEnum(decl_sub_index) + 6]); +pub fn findDecls(zir: Zir, list: *std.ArrayList(Inst.Index), decl_inst: Zir.Inst.Index) !void { list.clearRetainingCapacity(); + const declaration, const extra_end = zir.getDeclaration(decl_inst); + const bodies = declaration.getBodies(extra_end, zir); - return zir.findDeclsInner(list, block_inst); + try zir.findDeclsBody(list, bodies.value_body); + if (bodies.align_body) |b| try zir.findDeclsBody(list, b); + if (bodies.linksection_body) |b| try zir.findDeclsBody(list, b); + if (bodies.addrspace_body) |b| try zir.findDeclsBody(list, b); } fn findDeclsInner( @@ -3791,8 +3793,17 @@ pub fn getParamBody(zir: Zir, fn_inst: Inst.Index) []const Zir.Inst.Index { else => unreachable, }; - const param_block = zir.extraData(Inst.Block, datas[@intFromEnum(param_block_index)].pl_node.payload_index); - return zir.bodySlice(param_block.end, param_block.data.body_len); + switch (tags[@intFromEnum(param_block_index)]) { + .block, .block_comptime, .block_inline => { + const param_block = zir.extraData(Inst.Block, datas[@intFromEnum(param_block_index)].pl_node.payload_index); + return zir.bodySlice(param_block.end, param_block.data.body_len); + }, + .declaration => { + const decl, const extra_end = zir.getDeclaration(param_block_index); + return decl.getBodies(extra_end, zir).value_body; + }, + else => unreachable, + } } pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { @@ -3888,12 +3899,17 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { }, else => unreachable, }; - switch (tags[@intFromEnum(info.param_block)]) { - .block, .block_comptime, .block_inline => {}, // OK - else => unreachable, // assertion failure - } - const param_block = zir.extraData(Inst.Block, datas[@intFromEnum(info.param_block)].pl_node.payload_index); - const param_body = zir.bodySlice(param_block.end, param_block.data.body_len); + const param_body = switch (tags[@intFromEnum(info.param_block)]) { + .block, .block_comptime, .block_inline => param_body: { + const param_block = zir.extraData(Inst.Block, datas[@intFromEnum(info.param_block)].pl_node.payload_index); + break :param_body zir.bodySlice(param_block.end, param_block.data.body_len); + }, + .declaration => param_body: { + const decl, const extra_end = zir.getDeclaration(info.param_block); + break :param_body decl.getBodies(extra_end, zir).value_body; + }, + else => unreachable, + }; var total_params_len: u32 = 0; for (param_body) |inst| { switch (tags[@intFromEnum(inst)]) { @@ -3912,3 +3928,13 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { .total_params_len = total_params_len, }; } + +pub fn getDeclaration(zir: Zir, inst: Zir.Inst.Index) struct { Inst.Declaration, u32 } { + assert(zir.instructions.items(.tag)[@intFromEnum(inst)] == .declaration); + const pl_node = zir.instructions.items(.data)[@intFromEnum(inst)].pl_node; + const extra = zir.extraData(Inst.Declaration, pl_node.payload_index); + return .{ + extra.data, + @intCast(extra.end), + }; +} diff --git a/src/main.zig b/src/main.zig index fd650384f9..211f2b461a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6856,10 +6856,7 @@ pub fn cmdChangelist( var inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{}; defer inst_map.deinit(gpa); - var extra_map: std.AutoHashMapUnmanaged(Zir.ExtraIndex, Zir.ExtraIndex) = .{}; - defer extra_map.deinit(gpa); - - try Module.mapOldZirToNew(gpa, old_zir, file.zir, &inst_map, &extra_map); + try Module.mapOldZirToNew(gpa, old_zir, file.zir, &inst_map); var bw = io.bufferedWriter(io.getStdOut().writer()); const stdout = bw.writer(); @@ -6868,16 +6865,8 @@ pub fn cmdChangelist( var it = inst_map.iterator(); while (it.next()) |entry| { try stdout.print(" %{d} => %{d}\n", .{ - entry.key_ptr.*, entry.value_ptr.*, - }); - } - } - { - try stdout.print("Extra mappings:\n", .{}); - var it = extra_map.iterator(); - while (it.next()) |entry| { - try stdout.print(" {d} => {d}\n", .{ - entry.key_ptr.*, entry.value_ptr.*, + @intFromEnum(entry.key_ptr.*), + @intFromEnum(entry.value_ptr.*), }); } } diff --git a/src/print_zir.zig b/src/print_zir.zig index a6e3ca91a8..32904d3a0a 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -521,6 +521,8 @@ const Writer = struct { .@"defer" => try self.writeDefer(stream, inst), .defer_err_code => try self.writeDeferErrCode(stream, inst), + .declaration => try self.writeDeclaration(stream, inst), + .extended => try self.writeExtended(stream, inst), } } @@ -1454,8 +1456,9 @@ const Writer = struct { try stream.writeAll("{\n"); self.indent += 2; - extra_index = try self.writeDecls(stream, decls_len, extra_index); + try self.writeBody(stream, self.code.bodySlice(extra_index, decls_len)); self.indent -= 2; + extra_index += decls_len; try stream.writeByteNTimes(' ', self.indent); try stream.writeAll("}, "); } @@ -1634,8 +1637,9 @@ const Writer = struct { try stream.writeAll("{\n"); self.indent += 2; - extra_index = try self.writeDecls(stream, decls_len, extra_index); + try self.writeBody(stream, self.code.bodySlice(extra_index, decls_len)); self.indent -= 2; + extra_index += decls_len; try stream.writeByteNTimes(' ', self.indent); try stream.writeAll("}"); } @@ -1727,124 +1731,6 @@ const Writer = struct { try self.writeSrcNode(stream, src_node); } - fn writeDecls(self: *Writer, stream: anytype, decls_len: u32, extra_start: usize) !usize { - const parent_decl_node = self.parent_decl_node; - const bit_bags_count = std.math.divCeil(usize, decls_len, 8) catch unreachable; - var extra_index = extra_start + bit_bags_count; - var bit_bag_index: usize = extra_start; - var cur_bit_bag: u32 = undefined; - var decl_i: u32 = 0; - while (decl_i < decls_len) : (decl_i += 1) { - if (decl_i % 8 == 0) { - cur_bit_bag = self.code.extra[bit_bag_index]; - bit_bag_index += 1; - } - const is_pub = @as(u1, @truncate(cur_bit_bag)) != 0; - cur_bit_bag >>= 1; - const is_exported = @as(u1, @truncate(cur_bit_bag)) != 0; - cur_bit_bag >>= 1; - const has_align = @as(u1, @truncate(cur_bit_bag)) != 0; - cur_bit_bag >>= 1; - const has_section_or_addrspace = @as(u1, @truncate(cur_bit_bag)) != 0; - cur_bit_bag >>= 1; - - const sub_index = extra_index; - - const hash_u32s = self.code.extra[extra_index..][0..4]; - extra_index += 4; - const line = self.code.extra[extra_index]; - extra_index += 1; - const decl_name_index = self.code.extra[extra_index]; - extra_index += 1; - const decl_index: Zir.Inst.Index = @enumFromInt(self.code.extra[extra_index]); - extra_index += 1; - const doc_comment_index: Zir.NullTerminatedString = @enumFromInt(self.code.extra[extra_index]); - extra_index += 1; - - const align_inst: Zir.Inst.Ref = if (!has_align) .none else inst: { - const inst = @as(Zir.Inst.Ref, @enumFromInt(self.code.extra[extra_index])); - extra_index += 1; - break :inst inst; - }; - const section_inst: Zir.Inst.Ref = if (!has_section_or_addrspace) .none else inst: { - const inst = @as(Zir.Inst.Ref, @enumFromInt(self.code.extra[extra_index])); - extra_index += 1; - break :inst inst; - }; - const addrspace_inst: Zir.Inst.Ref = if (!has_section_or_addrspace) .none else inst: { - const inst = @as(Zir.Inst.Ref, @enumFromInt(self.code.extra[extra_index])); - extra_index += 1; - break :inst inst; - }; - - const pub_str = if (is_pub) "pub " else ""; - const hash_bytes: [16]u8 = @bitCast(hash_u32s.*); - if (decl_name_index == 0) { - try stream.writeByteNTimes(' ', self.indent); - const name = if (is_exported) "usingnamespace" else "comptime"; - try stream.writeAll(pub_str); - try stream.writeAll(name); - } else if (decl_name_index == 1) { - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("test"); - } else if (decl_name_index == 2) { - try stream.writeByteNTimes(' ', self.indent); - try stream.print("[{d}] decltest {s}", .{ sub_index, self.code.nullTerminatedString(doc_comment_index) }); - } else { - const raw_decl_name = self.code.nullTerminatedString(@enumFromInt(decl_name_index)); - const decl_name = if (raw_decl_name.len == 0) - self.code.nullTerminatedString(@enumFromInt(decl_name_index + 1)) - else - raw_decl_name; - const test_str = if (raw_decl_name.len == 0) "test \"" else ""; - const export_str = if (is_exported) "export " else ""; - - try self.writeDocComment(stream, doc_comment_index); - - try stream.writeByteNTimes(' ', self.indent); - const endquote_if_test: []const u8 = if (raw_decl_name.len == 0) "\"" else ""; - try stream.print("[{d}] {s}{s}{s}{}{s}", .{ - sub_index, pub_str, test_str, export_str, std.zig.fmtId(decl_name), endquote_if_test, - }); - if (align_inst != .none) { - try stream.writeAll(" align("); - try self.writeInstRef(stream, align_inst); - try stream.writeAll(")"); - } - if (addrspace_inst != .none) { - try stream.writeAll(" addrspace("); - try self.writeInstRef(stream, addrspace_inst); - try stream.writeAll(")"); - } - if (section_inst != .none) { - try stream.writeAll(" linksection("); - try self.writeInstRef(stream, section_inst); - try stream.writeAll(")"); - } - } - - if (self.recurse_decls) { - const tag = self.code.instructions.items(.tag)[@intFromEnum(decl_index)]; - try stream.print(" line({d}) hash({}): %{d} = {s}(", .{ - line, std.fmt.fmtSliceHexLower(&hash_bytes), @intFromEnum(decl_index), @tagName(tag), - }); - - const decl_block_inst_data = self.code.instructions.items(.data)[@intFromEnum(decl_index)].pl_node; - const sub_decl_node_off = decl_block_inst_data.src_node; - self.parent_decl_node = self.relativeToNodeIndex(sub_decl_node_off); - try self.writePlNodeBlockWithoutSrc(stream, decl_index); - self.parent_decl_node = parent_decl_node; - try self.writeSrc(stream, decl_block_inst_data.src()); - try stream.writeAll("\n"); - } else { - try stream.print(" line({d}) hash({}): %{d} = ...\n", .{ - line, std.fmt.fmtSliceHexLower(&hash_bytes), @intFromEnum(decl_index), - }); - } - } - return extra_index; - } - fn writeEnumDecl(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void { const small = @as(Zir.Inst.EnumDecl.Small, @bitCast(extended.small)); var extra_index: usize = extended.operand; @@ -1891,8 +1777,9 @@ const Writer = struct { try stream.writeAll("{\n"); self.indent += 2; - extra_index = try self.writeDecls(stream, decls_len, extra_index); + try self.writeBody(stream, self.code.bodySlice(extra_index, decls_len)); self.indent -= 2; + extra_index += decls_len; try stream.writeByteNTimes(' ', self.indent); try stream.writeAll("}, "); } @@ -1988,7 +1875,7 @@ const Writer = struct { try stream.writeAll("{\n"); self.indent += 2; - _ = try self.writeDecls(stream, decls_len, extra_index); + try self.writeBody(stream, self.code.bodySlice(extra_index, decls_len)); self.indent -= 2; try stream.writeByteNTimes(' ', self.indent); try stream.writeAll("})"); @@ -2762,6 +2649,64 @@ const Writer = struct { try stream.writeByte(')'); } + fn writeDeclaration(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].pl_node; + const extra = self.code.extraData(Zir.Inst.Declaration, inst_data.payload_index); + const doc_comment: ?Zir.NullTerminatedString = if (extra.data.flags.has_doc_comment) dc: { + break :dc @enumFromInt(self.code.extra[extra.end]); + } else null; + if (extra.data.flags.is_pub) try stream.writeAll("pub "); + if (extra.data.flags.is_export) try stream.writeAll("export "); + switch (extra.data.name) { + .@"comptime" => try stream.writeAll("comptime"), + .@"usingnamespace" => try stream.writeAll("usingnamespace"), + .unnamed_test => try stream.writeAll("test"), + .decltest => try stream.print("decltest '{s}'", .{self.code.nullTerminatedString(doc_comment.?)}), + _ => { + const name = extra.data.name.toString(self.code).?; + const prefix = if (extra.data.name.isNamedTest(self.code)) "test " else ""; + try stream.print("{s}'{s}'", .{ prefix, self.code.nullTerminatedString(name) }); + }, + } + const src_hash_arr: [4]u32 = .{ + extra.data.src_hash_0, + extra.data.src_hash_1, + extra.data.src_hash_2, + extra.data.src_hash_3, + }; + const src_hash_bytes: [16]u8 = @bitCast(src_hash_arr); + try stream.print(" line(+{d}) hash({})", .{ extra.data.line_offset, std.fmt.fmtSliceHexLower(&src_hash_bytes) }); + + { + const prev_parent_decl_node = self.parent_decl_node; + defer self.parent_decl_node = prev_parent_decl_node; + self.parent_decl_node = self.relativeToNodeIndex(inst_data.src_node); + + const bodies = extra.data.getBodies(@intCast(extra.end), self.code); + + try stream.writeAll(" value="); + try self.writeBracedDecl(stream, bodies.value_body); + + if (bodies.align_body) |b| { + try stream.writeAll(" align="); + try self.writeBracedDecl(stream, b); + } + + if (bodies.linksection_body) |b| { + try stream.writeAll(" linksection="); + try self.writeBracedDecl(stream, b); + } + + if (bodies.addrspace_body) |b| { + try stream.writeAll(" addrspace="); + try self.writeBracedDecl(stream, b); + } + } + + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + fn writeInstRef(self: *Writer, stream: anytype, ref: Zir.Inst.Ref) !void { if (ref == .none) { return stream.writeAll(".none"); From 06d8bb32e3edfb4a26c6d3ecdf198574f4bd3f87 Mon Sep 17 00:00:00 2001 From: mlugg Date: Sun, 7 Jan 2024 00:42:30 +0000 Subject: [PATCH 2/2] InternPool: introduce TrackedInst It is problematic for the cached `InternPool` state to directly reference ZIR instruction indices, as these are not stable across incremental updates. The existing ZIR mapping logic attempts to handle this by iterating the existing Decl graph for a file after `AstGen` and update ZIR indices on `Decl`s, struct types, etc. However, this is unreliable due to generic instantiations, and relies on specialized logic for everything which may refer to a ZIR instruction (e.g. a struct's owner decl). I therefore determined that a prerequisite change for incremental compilation would be to rework how we store these indices. This commit introduces a `TrackedInst` type which provides a stable index (`TrackedInst.Index`) for a single ZIR instruction in the compilation. The `InternPool` now stores these values in place of ZIR instruction indices. This makes the ZIR mapping logic relatively trivial: after `AstGen` completes, we simply iterate all `TrackedInst` values and update those indices which have changed. In future, if the corresponding ZIR instruction has been removed, we must also invalidate any dependencies on this instruction to trigger any required re-analysis, however the dependency system does not yet exist. --- src/Compilation.zig | 5 +- src/InternPool.zig | 70 +++++++++++++++++++------- src/Module.zig | 120 +++++++++++++------------------------------- src/Sema.zig | 38 +++++++------- 4 files changed, 110 insertions(+), 123 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 58f56517c3..f5d63c3676 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2795,6 +2795,7 @@ const Header = extern struct { extra_len: u32, limbs_len: u32, string_bytes_len: u32, + tracked_insts_len: u32, }, }; @@ -2802,7 +2803,7 @@ const Header = extern struct { /// saved, such as the target and most CLI flags. A cache hit will only occur /// when subsequent compiler invocations use the same set of flags. pub fn saveState(comp: *Compilation) !void { - var bufs_list: [6]std.os.iovec_const = undefined; + var bufs_list: [7]std.os.iovec_const = undefined; var bufs_len: usize = 0; const lf = comp.bin_file orelse return; @@ -2815,6 +2816,7 @@ pub fn saveState(comp: *Compilation) !void { .extra_len = @intCast(ip.extra.items.len), .limbs_len = @intCast(ip.limbs.items.len), .string_bytes_len = @intCast(ip.string_bytes.items.len), + .tracked_insts_len = @intCast(ip.tracked_insts.count()), }, }; addBuf(&bufs_list, &bufs_len, mem.asBytes(&header)); @@ -2823,6 +2825,7 @@ pub fn saveState(comp: *Compilation) !void { addBuf(&bufs_list, &bufs_len, mem.sliceAsBytes(ip.items.items(.data))); addBuf(&bufs_list, &bufs_len, mem.sliceAsBytes(ip.items.items(.tag))); addBuf(&bufs_list, &bufs_len, ip.string_bytes.items); + addBuf(&bufs_list, &bufs_len, mem.sliceAsBytes(ip.tracked_insts.keys())); // TODO: compilation errors // TODO: files diff --git a/src/InternPool.zig b/src/InternPool.zig index 1c5703b055..88bd24ec93 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -54,6 +54,34 @@ string_table: std.HashMapUnmanaged( std.hash_map.default_max_load_percentage, ) = .{}, +/// An index into `tracked_insts` gives a reference to a single ZIR instruction which +/// persists across incremental updates. +tracked_insts: std.AutoArrayHashMapUnmanaged(TrackedInst, void) = .{}, + +pub const TrackedInst = extern struct { + path_digest: Cache.BinDigest, + inst: Zir.Inst.Index, + comptime { + // The fields should be tightly packed. See also serialiation logic in `Compilation.saveState`. + assert(@sizeOf(@This()) == Cache.bin_digest_len + @sizeOf(Zir.Inst.Index)); + } + pub const Index = enum(u32) { + _, + pub fn resolve(i: TrackedInst.Index, ip: *const InternPool) Zir.Inst.Index { + return ip.tracked_insts.keys()[@intFromEnum(i)].inst; + } + }; +}; + +pub fn trackZir(ip: *InternPool, gpa: Allocator, file: *Module.File, inst: Zir.Inst.Index) Allocator.Error!TrackedInst.Index { + const key: TrackedInst = .{ + .path_digest = file.path_digest, + .inst = inst, + }; + const gop = try ip.tracked_insts.getOrPut(gpa, key); + return @enumFromInt(gop.index); +} + const FieldMap = std.ArrayHashMapUnmanaged(void, void, std.array_hash_map.AutoContext(void), false); const builtin = @import("builtin"); @@ -62,11 +90,13 @@ const Allocator = std.mem.Allocator; const assert = std.debug.assert; const BigIntConst = std.math.big.int.Const; const BigIntMutable = std.math.big.int.Mutable; +const Cache = std.Build.Cache; const Limb = std.math.big.Limb; const Hash = std.hash.Wyhash; const InternPool = @This(); const Module = @import("Module.zig"); +const Zcu = Module; const Zir = @import("Zir.zig"); const KeyAdapter = struct { @@ -409,7 +439,7 @@ pub const Key = union(enum) { /// `none` when the struct has no declarations. namespace: OptionalNamespaceIndex, /// Index of the struct_decl ZIR instruction. - zir_index: Zir.Inst.Index, + zir_index: TrackedInst.Index, layout: std.builtin.Type.ContainerLayout, field_names: NullTerminatedString.Slice, field_types: Index.Slice, @@ -653,7 +683,7 @@ pub const Key = union(enum) { } /// Asserts the struct is not packed. - pub fn setZirIndex(s: @This(), ip: *InternPool, new_zir_index: Zir.Inst.Index) void { + pub fn setZirIndex(s: @This(), ip: *InternPool, new_zir_index: TrackedInst.Index) void { assert(s.layout != .Packed); const field_index = std.meta.fieldIndex(Tag.TypeStruct, "zir_index").?; ip.extra.items[s.extra_index + field_index] = @intFromEnum(new_zir_index); @@ -769,7 +799,7 @@ pub const Key = union(enum) { flags: Tag.TypeUnion.Flags, /// The enum that provides the list of field names and values. enum_tag_ty: Index, - zir_index: Zir.Inst.Index, + zir_index: TrackedInst.Index, /// The returned pointer expires with any addition to the `InternPool`. pub fn flagsPtr(self: @This(), ip: *const InternPool) *Tag.TypeUnion.Flags { @@ -1056,7 +1086,7 @@ pub const Key = union(enum) { /// the body. We store this rather than the body directly so that when ZIR /// is regenerated on update(), we can map this to the new corresponding /// ZIR instruction. - zir_body_inst: Zir.Inst.Index, + zir_body_inst: TrackedInst.Index, /// Relative to owner Decl. lbrace_line: u32, /// Relative to owner Decl. @@ -1082,7 +1112,7 @@ pub const Key = union(enum) { } /// Returns a pointer that becomes invalid after any additions to the `InternPool`. - pub fn zirBodyInst(func: *const Func, ip: *const InternPool) *Zir.Inst.Index { + pub fn zirBodyInst(func: *const Func, ip: *const InternPool) *TrackedInst.Index { return @ptrCast(&ip.extra.items[func.zir_body_inst_extra_index]); } @@ -1860,7 +1890,7 @@ pub const UnionType = struct { /// If this slice has length 0 it means all elements are `none`. field_aligns: Alignment.Slice, /// Index of the union_decl ZIR instruction. - zir_index: Zir.Inst.Index, + zir_index: TrackedInst.Index, /// Index into extra array of the `flags` field. flags_index: u32, /// Copied from `enum_tag_ty`. @@ -1954,10 +1984,10 @@ pub const UnionType = struct { } /// This does not mutate the field of UnionType. - pub fn setZirIndex(self: @This(), ip: *InternPool, new_zir_index: Zir.Inst.Index) void { + pub fn setZirIndex(self: @This(), ip: *InternPool, new_zir_index: TrackedInst.Index) void { const flags_field_index = std.meta.fieldIndex(Tag.TypeUnion, "flags").?; const zir_index_field_index = std.meta.fieldIndex(Tag.TypeUnion, "zir_index").?; - const ptr: *Zir.Inst.Index = + const ptr: *TrackedInst.Index = @ptrCast(&ip.extra.items[self.flags_index - flags_field_index + zir_index_field_index]); ptr.* = new_zir_index; } @@ -2976,7 +3006,7 @@ pub const Tag = enum(u8) { analysis: FuncAnalysis, owner_decl: DeclIndex, ty: Index, - zir_body_inst: Zir.Inst.Index, + zir_body_inst: TrackedInst.Index, lbrace_line: u32, rbrace_line: u32, lbrace_column: u32, @@ -3050,7 +3080,7 @@ pub const Tag = enum(u8) { namespace: NamespaceIndex, /// The enum that provides the list of field names and values. tag_ty: Index, - zir_index: Zir.Inst.Index, + zir_index: TrackedInst.Index, pub const Flags = packed struct(u32) { runtime_tag: UnionType.RuntimeTag, @@ -3072,7 +3102,7 @@ pub const Tag = enum(u8) { /// 2. init: Index for each fields_len // if tag is type_struct_packed_inits pub const TypeStructPacked = struct { decl: DeclIndex, - zir_index: Zir.Inst.Index, + zir_index: TrackedInst.Index, fields_len: u32, namespace: OptionalNamespaceIndex, backing_int_ty: Index, @@ -3119,7 +3149,7 @@ pub const Tag = enum(u8) { /// 7. field_offset: u32 // for each field in declared order, undef until layout_resolved pub const TypeStruct = struct { decl: DeclIndex, - zir_index: Zir.Inst.Index, + zir_index: TrackedInst.Index, fields_len: u32, flags: Flags, size: u32, @@ -3708,6 +3738,8 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void { ip.string_table.deinit(gpa); + ip.tracked_insts.deinit(gpa); + ip.* = undefined; } @@ -5358,7 +5390,7 @@ pub const UnionTypeInit = struct { flags: Tag.TypeUnion.Flags, decl: DeclIndex, namespace: NamespaceIndex, - zir_index: Zir.Inst.Index, + zir_index: TrackedInst.Index, fields_len: u32, enum_tag_ty: Index, /// May have length 0 which leaves the values unset until later. @@ -5430,7 +5462,7 @@ pub const StructTypeInit = struct { decl: DeclIndex, namespace: OptionalNamespaceIndex, layout: std.builtin.Type.ContainerLayout, - zir_index: Zir.Inst.Index, + zir_index: TrackedInst.Index, fields_len: u32, known_non_opv: bool, requires_comptime: RequiresComptime, @@ -5704,7 +5736,7 @@ pub fn getExternFunc(ip: *InternPool, gpa: Allocator, key: Key.ExternFunc) Alloc pub const GetFuncDeclKey = struct { owner_decl: DeclIndex, ty: Index, - zir_body_inst: Zir.Inst.Index, + zir_body_inst: TrackedInst.Index, lbrace_line: u32, rbrace_line: u32, lbrace_column: u32, @@ -5773,7 +5805,7 @@ pub const GetFuncDeclIesKey = struct { is_var_args: bool, is_generic: bool, is_noinline: bool, - zir_body_inst: Zir.Inst.Index, + zir_body_inst: TrackedInst.Index, lbrace_line: u32, rbrace_line: u32, lbrace_column: u32, @@ -6535,7 +6567,7 @@ fn addExtraAssumeCapacity(ip: *InternPool, extra: anytype) u32 { NullTerminatedString, OptionalNullTerminatedString, Tag.TypePointer.VectorIndex, - Zir.Inst.Index, + TrackedInst.Index, => @intFromEnum(@field(extra, field.name)), u32, @@ -6611,7 +6643,7 @@ fn extraDataTrail(ip: *const InternPool, comptime T: type, index: usize) struct NullTerminatedString, OptionalNullTerminatedString, Tag.TypePointer.VectorIndex, - Zir.Inst.Index, + TrackedInst.Index, => @enumFromInt(int32), u32, @@ -8317,7 +8349,7 @@ pub fn funcHasInferredErrorSet(ip: *const InternPool, i: Index) bool { return funcAnalysis(ip, i).inferred_error_set; } -pub fn funcZirBodyInst(ip: *const InternPool, i: Index) Zir.Inst.Index { +pub fn funcZirBodyInst(ip: *const InternPool, i: Index) TrackedInst.Index { assert(i != .none); const item = ip.items.get(@intFromEnum(i)); const zir_body_inst_field_index = std.meta.fieldIndex(Tag.FuncDecl, "zir_body_inst").?; diff --git a/src/Module.zig b/src/Module.zig index 02df2dac67..499eb02f1e 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -834,6 +834,9 @@ pub const File = struct { multi_pkg: bool = false, /// List of references to this file, used for multi-package errors. references: std.ArrayListUnmanaged(Reference) = .{}, + /// The hash of the path to this file, used to store `InternPool.TrackedInst`. + /// undefined until `zir_loaded == true`. + path_digest: Cache.BinDigest = undefined, /// Used by change detection algorithm, after astgen, contains the /// set of decls that existed in the previous ZIR but not in the new one. @@ -2594,7 +2597,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void { const stat = try source_file.stat(); const want_local_cache = file.mod == mod.main_mod; - const digest = hash: { + const bin_digest = hash: { var path_hash: Cache.HashHelper = .{}; path_hash.addBytes(build_options.version); path_hash.add(builtin.zig_backend); @@ -2603,7 +2606,19 @@ pub fn astGenFile(mod: *Module, file: *File) !void { path_hash.addBytes(file.mod.root.sub_path); } path_hash.addBytes(file.sub_file_path); - break :hash path_hash.final(); + var bin: Cache.BinDigest = undefined; + path_hash.hasher.final(&bin); + break :hash bin; + }; + file.path_digest = bin_digest; + const hex_digest = hex: { + var hex: Cache.HexDigest = undefined; + _ = std.fmt.bufPrint( + &hex, + "{s}", + .{std.fmt.fmtSliceHexLower(&bin_digest)}, + ) catch unreachable; + break :hex hex; }; const cache_directory = if (want_local_cache) mod.local_zir_cache else mod.global_zir_cache; const zir_dir = cache_directory.handle; @@ -2613,7 +2628,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void { .never_loaded, .retryable_failure => lock: { // First, load the cached ZIR code, if any. log.debug("AstGen checking cache: {s} (local={}, digest={s})", .{ - file.sub_file_path, want_local_cache, &digest, + file.sub_file_path, want_local_cache, &hex_digest, }); break :lock .shared; @@ -2640,7 +2655,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void { // version. Likewise if we're working on AstGen and another process asks for // the cached file, they'll get it. const cache_file = while (true) { - break zir_dir.createFile(&digest, .{ + break zir_dir.createFile(&hex_digest, .{ .read = true, .truncate = false, .lock = lock, @@ -2826,7 +2841,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void { }; cache_file.writevAll(&iovecs) catch |err| { log.warn("unable to write cached ZIR code for {}{s} to {}{s}: {s}", .{ - file.mod.root, file.sub_file_path, cache_directory, &digest, @errorName(err), + file.mod.root, file.sub_file_path, cache_directory, &hex_digest, @errorName(err), }); }; @@ -2935,89 +2950,22 @@ fn loadZirCacheBody(gpa: Allocator, header: Zir.Header, cache_file: std.fs.File) return zir; } -/// Patch ups: -/// * Struct.zir_index -/// * Decl.zir_index -/// * Fn.zir_body_inst -/// * Decl.zir_decl_index -fn updateZirRefs(mod: *Module, file: *File, old_zir: Zir) !void { - const gpa = mod.gpa; - const new_zir = file.zir; +fn updateZirRefs(zcu: *Module, file: *File, old_zir: Zir) !void { + const gpa = zcu.gpa; - // The root decl will be null if the previous ZIR had AST errors. - const root_decl = file.root_decl.unwrap() orelse return; - - // Maps from old ZIR to new ZIR, declaration, struct_decl, enum_decl, etc. Any instruction which - // creates a namespace, and any `declaration` instruction, gets mapped from old to new here. var inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{}; defer inst_map.deinit(gpa); - try mapOldZirToNew(gpa, old_zir, new_zir, &inst_map); + try mapOldZirToNew(gpa, old_zir, file.zir, &inst_map); - // Walk the Decl graph, updating ZIR indexes, strings, and populating - // the deleted and outdated lists. - - var decl_stack: ArrayListUnmanaged(Decl.Index) = .{}; - defer decl_stack.deinit(gpa); - - try decl_stack.append(gpa, root_decl); - - file.deleted_decls.clearRetainingCapacity(); - file.outdated_decls.clearRetainingCapacity(); - - // The root decl is always outdated; otherwise we would not have had - // to re-generate ZIR for the File. - try file.outdated_decls.append(gpa, root_decl); - - const ip = &mod.intern_pool; - - while (decl_stack.popOrNull()) |decl_index| { - const decl = mod.declPtr(decl_index); - // Anonymous decls and the root decl have this set to 0. We still need - // to walk them but we do not need to modify this value. - // Anonymous decls should not be marked outdated. They will be re-generated - // if their owner decl is marked outdated. - if (decl.zir_decl_index.unwrap()) |old_zir_decl_index| { - const new_zir_decl_index = inst_map.get(old_zir_decl_index) orelse { - try file.deleted_decls.append(gpa, decl_index); - continue; - }; - const old_hash = decl.contentsHashZir(old_zir); - decl.zir_decl_index = new_zir_decl_index.toOptional(); - const new_hash = decl.contentsHashZir(new_zir); - if (!std.zig.srcHashEql(old_hash, new_hash)) { - try file.outdated_decls.append(gpa, decl_index); - } - } - - if (!decl.owns_tv) continue; - - if (decl.getOwnedStruct(mod)) |struct_type| { - struct_type.setZirIndex(ip, inst_map.get(struct_type.zir_index) orelse { - try file.deleted_decls.append(gpa, decl_index); - continue; - }); - } - - if (decl.getOwnedUnion(mod)) |union_type| { - union_type.setZirIndex(ip, inst_map.get(union_type.zir_index) orelse { - try file.deleted_decls.append(gpa, decl_index); - continue; - }); - } - - if (decl.getOwnedFunction(mod)) |func| { - func.zirBodyInst(ip).* = inst_map.get(func.zir_body_inst) orelse { - try file.deleted_decls.append(gpa, decl_index); - continue; - }; - } - - if (decl.getOwnedInnerNamespace(mod)) |namespace| { - for (namespace.decls.keys()) |sub_decl| { - try decl_stack.append(gpa, sub_decl); - } - } + // TODO: this should be done after all AstGen workers complete, to avoid + // iterating over this full set for every updated file. + for (zcu.intern_pool.tracked_insts.keys()) |*ti| { + if (!std.mem.eql(u8, &ti.path_digest, &file.path_digest)) continue; + ti.inst = inst_map.get(ti.inst) orelse { + // TODO: invalidate this `TrackedInst` via the dependency mechanism + continue; + }; } } @@ -3494,7 +3442,7 @@ pub fn semaFile(mod: *Module, file: *File) SemaError!void { const struct_ty = sema.getStructType( new_decl_index, new_namespace_index, - .main_struct_inst, + try mod.intern_pool.trackZir(gpa, file, .main_struct_inst), ) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, }; @@ -4472,7 +4420,7 @@ pub fn analyzeFnBody(mod: *Module, func_index: InternPool.Index, arena: Allocato }; defer inner_block.instructions.deinit(gpa); - const fn_info = sema.code.getFnInfo(func.zirBodyInst(ip).*); + const fn_info = sema.code.getFnInfo(func.zirBodyInst(ip).resolve(ip)); // Here we are performing "runtime semantic analysis" for a function body, which means // we must map the parameter ZIR instructions to `arg` AIR instructions. @@ -6125,7 +6073,7 @@ pub fn getParamName(mod: *Module, func_index: InternPool.Index, index: u32) [:0] const tags = file.zir.instructions.items(.tag); const data = file.zir.instructions.items(.data); - const param_body = file.zir.getParamBody(func.zir_body_inst); + const param_body = file.zir.getParamBody(func.zir_body_inst.resolve(&mod.intern_pool)); const param = param_body[index]; return switch (tags[@intFromEnum(param)]) { diff --git a/src/Sema.zig b/src/Sema.zig index fc6cf0c017..7e0e317ffb 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2708,11 +2708,12 @@ pub fn getStructType( sema: *Sema, decl: InternPool.DeclIndex, namespace: InternPool.NamespaceIndex, - zir_index: Zir.Inst.Index, + tracked_inst: InternPool.TrackedInst.Index, ) !InternPool.Index { const mod = sema.mod; const gpa = sema.gpa; const ip = &mod.intern_pool; + const zir_index = tracked_inst.resolve(ip); const extended = sema.code.instructions.items(.data)[@intFromEnum(zir_index)].extended; assert(extended.opcode == .struct_decl); const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small); @@ -2747,7 +2748,7 @@ pub fn getStructType( const ty = try ip.getStructType(gpa, .{ .decl = decl, .namespace = namespace.toOptional(), - .zir_index = zir_index, + .zir_index = tracked_inst, .layout = small.layout, .known_non_opv = small.known_non_opv, .is_tuple = small.is_tuple, @@ -2797,7 +2798,8 @@ fn zirStructDecl( errdefer mod.destroyNamespace(new_namespace_index); const struct_ty = ty: { - const ty = try sema.getStructType(new_decl_index, new_namespace_index, inst); + const tracked_inst = try ip.trackZir(mod.gpa, block.getFileScope(mod), inst); + const ty = try sema.getStructType(new_decl_index, new_namespace_index, tracked_inst); if (sema.builtin_type_target_index != .none) { ip.resolveBuiltinType(sema.builtin_type_target_index, ty); break :ty sema.builtin_type_target_index; @@ -2856,7 +2858,7 @@ fn createAnonymousDeclTypeNamed( return new_decl_index; }, .func => { - const fn_info = sema.code.getFnInfo(ip.funcZirBodyInst(sema.func_index)); + const fn_info = sema.code.getFnInfo(ip.funcZirBodyInst(sema.func_index).resolve(ip)); const zir_tags = sema.code.instructions.items(.tag); var buf = std.ArrayList(u8).init(gpa); @@ -3252,7 +3254,7 @@ fn zirUnionDecl( }, .decl = new_decl_index, .namespace = new_namespace_index, - .zir_index = inst, + .zir_index = try mod.intern_pool.trackZir(gpa, block.getFileScope(mod), inst), .fields_len = fields_len, .enum_tag_ty = .none, .field_types = &.{}, @@ -7446,7 +7448,7 @@ fn analyzeCall( // the AIR instructions of the callsite. The callee could be a generic function // which means its parameter type expressions must be resolved in order and used // to successively coerce the arguments. - const fn_info = ics.callee().code.getFnInfo(module_fn.zir_body_inst); + const fn_info = ics.callee().code.getFnInfo(module_fn.zir_body_inst.resolve(ip)); try ics.callee().inst_map.ensureSpaceForInstructions(gpa, fn_info.param_body); var arg_i: u32 = 0; @@ -7494,7 +7496,7 @@ fn analyzeCall( // each of the parameters, resolving the return type and providing it to the child // `Sema` so that it can be used for the `ret_ptr` instruction. const ret_ty_inst = if (fn_info.ret_ty_body.len != 0) - try sema.resolveBody(&child_block, fn_info.ret_ty_body, module_fn.zir_body_inst) + try sema.resolveBody(&child_block, fn_info.ret_ty_body, module_fn.zir_body_inst.resolve(ip)) else try sema.resolveInst(fn_info.ret_ty_ref); const ret_ty_src: LazySrcLoc = .{ .node_offset_fn_type_ret_ty = 0 }; @@ -7885,7 +7887,7 @@ fn instantiateGenericCall( const namespace_index = fn_owner_decl.src_namespace; const namespace = mod.namespacePtr(namespace_index); const fn_zir = namespace.file_scope.zir; - const fn_info = fn_zir.getFnInfo(generic_owner_func.zir_body_inst); + const fn_info = fn_zir.getFnInfo(generic_owner_func.zir_body_inst.resolve(ip)); const comptime_args = try sema.arena.alloc(InternPool.Index, args_info.count()); @memset(comptime_args, .none); @@ -9467,7 +9469,7 @@ fn funcCommon( .is_generic = final_is_generic, .is_noinline = is_noinline, - .zir_body_inst = func_inst, + .zir_body_inst = try ip.trackZir(gpa, block.getFileScope(mod), func_inst), .lbrace_line = src_locs.lbrace_line, .rbrace_line = src_locs.rbrace_line, .lbrace_column = @as(u16, @truncate(src_locs.columns)), @@ -9545,7 +9547,7 @@ fn funcCommon( .ty = func_ty, .cc = cc, .is_noinline = is_noinline, - .zir_body_inst = func_inst, + .zir_body_inst = try ip.trackZir(gpa, block.getFileScope(mod), func_inst), .lbrace_line = src_locs.lbrace_line, .rbrace_line = src_locs.rbrace_line, .lbrace_column = @as(u16, @truncate(src_locs.columns)), @@ -21553,7 +21555,7 @@ fn zirReify( .namespace = new_namespace_index, .enum_tag_ty = enum_tag_ty, .fields_len = fields_len, - .zir_index = inst, + .zir_index = try ip.trackZir(gpa, block.getFileScope(mod), inst), // TODO: should reified types be handled differently? .flags = .{ .layout = layout, .status = .have_field_types, @@ -21721,7 +21723,7 @@ fn reifyStruct( const ty = try ip.getStructType(gpa, .{ .decl = new_decl_index, .namespace = .none, - .zir_index = inst, + .zir_index = try mod.intern_pool.trackZir(gpa, block.getFileScope(mod), inst), // TODO: should reified types be handled differently? .layout = layout, .known_non_opv = false, .fields_len = fields_len, @@ -35593,7 +35595,8 @@ fn semaBackingIntType(mod: *Module, struct_type: InternPool.Key.StructType) Comp break :blk accumulator; }; - const extended = zir.instructions.items(.data)[@intFromEnum(struct_type.zir_index)].extended; + const zir_index = struct_type.zir_index.resolve(ip); + const extended = zir.instructions.items(.data)[@intFromEnum(zir_index)].extended; assert(extended.opcode == .struct_decl); const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small); @@ -35613,7 +35616,7 @@ fn semaBackingIntType(mod: *Module, struct_type: InternPool.Key.StructType) Comp break :blk try sema.resolveType(&block, backing_int_src, backing_int_ref); } else { const body = zir.bodySlice(extra_index, backing_int_body_len); - const ty_ref = try sema.resolveBody(&block, body, struct_type.zir_index); + const ty_ref = try sema.resolveBody(&block, body, zir_index); break :blk try sema.analyzeAsType(&block, backing_int_src, ty_ref); } }; @@ -36357,7 +36360,7 @@ fn semaStructFields( const decl = mod.declPtr(decl_index); const namespace_index = struct_type.namespace.unwrap() orelse decl.src_namespace; const zir = mod.namespacePtr(namespace_index).file_scope.zir; - const zir_index = struct_type.zir_index; + const zir_index = struct_type.zir_index.resolve(ip); const fields_len, const small, var extra_index = structZirInfo(zir, zir_index); @@ -36628,7 +36631,7 @@ fn semaStructFieldInits( const decl = mod.declPtr(decl_index); const namespace_index = struct_type.namespace.unwrap() orelse decl.src_namespace; const zir = mod.namespacePtr(namespace_index).file_scope.zir; - const zir_index = struct_type.zir_index; + const zir_index = struct_type.zir_index.resolve(ip); const fields_len, const small, var extra_index = structZirInfo(zir, zir_index); var comptime_mutable_decls = std.ArrayList(InternPool.DeclIndex).init(gpa); @@ -36777,7 +36780,8 @@ fn semaUnionFields(mod: *Module, arena: Allocator, union_type: InternPool.Key.Un const ip = &mod.intern_pool; const decl_index = union_type.decl; const zir = mod.namespacePtr(union_type.namespace).file_scope.zir; - const extended = zir.instructions.items(.data)[@intFromEnum(union_type.zir_index)].extended; + const zir_index = union_type.zir_index.resolve(ip); + const extended = zir.instructions.items(.data)[@intFromEnum(zir_index)].extended; assert(extended.opcode == .union_decl); const small: Zir.Inst.UnionDecl.Small = @bitCast(extended.small); var extra_index: usize = extended.operand;