From 2446df2fbcb461c579de9698144cc2d9d185a599 Mon Sep 17 00:00:00 2001 From: mlugg Date: Fri, 19 Jan 2024 01:56:45 +0000 Subject: [PATCH] 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 | 384 +++++++++++++++++++++--------------- src/Autodoc.zig | 3 + src/InternPool.zig | 2 - src/Module.zig | 476 ++++++++++++++++++++++----------------------- src/Sema.zig | 32 +-- src/Zir.zig | 400 +++++++++++++++++++------------------ src/main.zig | 17 +- src/print_zir.zig | 189 +++++++----------- 8 files changed, 764 insertions(+), 739 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index f709751fde3c..caa0efa85132 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); - - { - 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 decl_gz.addBreak(.break_inline, decl_inst, func_inst); + + 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); - } - 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 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); } + + 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); - - { - 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 decl_block.addBreak(.break_inline, decl_inst, namespace_inst); + + 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); - - { - 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 decl_block.addBreak(.break_inline, decl_inst, func_inst); + + 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 a03ed351789d..838f8174d18d 100644 --- a/src/Autodoc.zig +++ b/src/Autodoc.zig @@ -4078,6 +4078,9 @@ fn analyzeAllDecls( priv_decl_indexes: *std.ArrayListUnmanaged(usize), call_ctx: ?*const CallContext, ) AutodocErrors!usize { + //if (true) @compileError("TODO AUTODOC"); + if (true) @panic("TODO AUTODOC"); + const first_decl_indexes_slot = decl_indexes.items.len; const original_it = file.zir.declIterator(parent_inst); diff --git a/src/InternPool.zig b/src/InternPool.zig index 0844e0f7a586..1c5703b05564 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 8f6f0e34d1b1..1b7d80f9cb9e 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,23 +465,12 @@ 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, - }; + /// 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; + } /// This name is relative to the containing namespace of the decl. /// The memory is owned by the containing File ZIR. @@ -497,10 +480,14 @@ pub const Decl = struct { } 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); + return switch (decl.getDeclaration(zir).name) { + Zir.Inst.Declaration.comptime_name, + Zir.Inst.Declaration.usingnamespace_name, + Zir.Inst.Declaration.unnamed_test_name, + Zir.Inst.Declaration.decltest_name, + => null, + else => |name_index| zir.nullTerminatedString(name_index), + }; } pub fn contentsHash(decl: Decl, mod: *Module) std.zig.SrcHash { @@ -509,43 +496,22 @@ pub const Decl = struct { } 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]); + const declaration = decl.getDeclaration(zir); + const src_hash_arr: [4]u32 = .{ + declaration.src_hash_0, + declaration.src_hash_1, + declaration.src_hash_2, + declaration.src_hash_3, + }; + return @bitCast(src_hash_arr); } - 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 +2981,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 +3012,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 +3060,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 +3070,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 unnamed_test_idx: u32 = 0; + var comptime_decl_idx: u32 = 0; + var usingnamespace_decl_idx: u32 = 0; + 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); - - 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], - }); + 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 +3485,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; @@ -3557,7 +3583,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; @@ -3631,11 +3656,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; @@ -3752,13 +3775,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", }); @@ -3778,15 +3801,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; @@ -4125,52 +4148,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, @@ -4178,119 +4181,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; + + 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; - // 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 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, + }; + }, + .@"usingnamespace" => info: { + const i = iter.usingnamespace_index; + iter.usingnamespace_index += 1; + break :info .{ + try ip.getOrPutStringFmt(gpa, "usingnamespace_{d}", .{i}), + .@"usingnamespace", + false, + }; }, - .unnamed_test_decl => name: { + .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) { @@ -4299,46 +4295,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 }); @@ -4718,8 +4710,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 44a04e7d256c..eac66f2ad1d8 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1223,6 +1223,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) { @@ -2692,7 +2696,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, @@ -2929,7 +2935,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; @@ -3219,7 +3227,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); @@ -3282,7 +3291,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); @@ -36252,9 +36262,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 }; } @@ -36715,9 +36723,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; @@ -37710,10 +37716,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 3737467e47be..4462083b1f72 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 fd650384f913..211f2b461aa9 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 a6e3ca91a89d..32904d3a0aac 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");