diff options
| author | Matthew Lugg <mlugg@mlugg.co.uk> | 2024-12-25 02:58:27 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-12-25 02:58:27 +0000 |
| commit | 497592c9b45a94fb7b6028bf45b80f183e395a9b (patch) | |
| tree | 467873c408750cb4223f3ccf31775e42ec9fbd5c | |
| parent | af5e731729592af4a5716edd3b1e03264d66ea46 (diff) | |
| parent | 3afda4322c34dedc2319701fdfac3505c8d311e9 (diff) | |
| download | zig-497592c9b45a94fb7b6028bf45b80f183e395a9b.tar.gz zig-497592c9b45a94fb7b6028bf45b80f183e395a9b.zip | |
Merge pull request #22303 from mlugg/131-new
compiler: analyze type and value of global declarations separately
29 files changed, 3086 insertions, 2537 deletions
diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index fcec69ed8f..4a9b948beb 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -106,7 +106,6 @@ 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"), @@ -1317,12 +1316,45 @@ fn fnProtoExpr( return astgen.failTok(some, "function type cannot have a name", .{}); } + if (fn_proto.ast.align_expr != 0) { + return astgen.failNode(fn_proto.ast.align_expr, "function type cannot have an alignment", .{}); + } + + if (fn_proto.ast.addrspace_expr != 0) { + return astgen.failNode(fn_proto.ast.addrspace_expr, "function type cannot have an addrspace", .{}); + } + + if (fn_proto.ast.section_expr != 0) { + return astgen.failNode(fn_proto.ast.section_expr, "function type cannot have a linksection", .{}); + } + + const maybe_bang = tree.firstToken(fn_proto.ast.return_type) - 1; + const is_inferred_error = token_tags[maybe_bang] == .bang; + if (is_inferred_error) { + return astgen.failTok(maybe_bang, "function type cannot have an inferred error set", .{}); + } + const is_extern = blk: { const maybe_extern_token = fn_proto.extern_export_inline_token orelse break :blk false; break :blk token_tags[maybe_extern_token] == .keyword_extern; }; assert(!is_extern); + return fnProtoExprInner(gz, scope, ri, node, fn_proto, false); +} + +fn fnProtoExprInner( + gz: *GenZir, + scope: *Scope, + ri: ResultInfo, + node: Ast.Node.Index, + fn_proto: Ast.full.FnProto, + implicit_ccc: bool, +) InnerError!Zir.Inst.Ref { + const astgen = gz.astgen; + const tree = astgen.tree; + const token_tags = tree.tokens.items(.tag); + var block_scope = gz.makeSubBlock(scope); defer block_scope.unstack(); @@ -1386,18 +1418,6 @@ fn fnProtoExpr( break :is_var_args false; }; - if (fn_proto.ast.align_expr != 0) { - return astgen.failNode(fn_proto.ast.align_expr, "function type cannot have an alignment", .{}); - } - - if (fn_proto.ast.addrspace_expr != 0) { - return astgen.failNode(fn_proto.ast.addrspace_expr, "function type cannot have an addrspace", .{}); - } - - if (fn_proto.ast.section_expr != 0) { - return astgen.failNode(fn_proto.ast.section_expr, "function type cannot have a linksection", .{}); - } - const cc: Zir.Inst.Ref = if (fn_proto.ast.callconv_expr != 0) try expr( &block_scope, @@ -1405,14 +1425,11 @@ fn fnProtoExpr( .{ .rl = .{ .coerced_ty = try block_scope.addBuiltinValue(fn_proto.ast.callconv_expr, .calling_convention) } }, fn_proto.ast.callconv_expr, ) + else if (implicit_ccc) + try block_scope.addBuiltinValue(node, .calling_convention_c) else - Zir.Inst.Ref.none; + .none; - const maybe_bang = tree.firstToken(fn_proto.ast.return_type) - 1; - const is_inferred_error = token_tags[maybe_bang] == .bang; - if (is_inferred_error) { - return astgen.failTok(maybe_bang, "function type cannot have an inferred error set", .{}); - } const ret_ty = try expr(&block_scope, scope, coerced_type_ri, fn_proto.ast.return_type); const result = try block_scope.addFunc(.{ @@ -1428,11 +1445,8 @@ fn fnProtoExpr( .param_block = block_inst, .body_gz = null, - .lib_name = .empty, .is_var_args = is_var_args, .is_inferred_error = false, - .is_test = false, - .is_extern = false, .is_noinline = false, .noalias_bits = noalias_bits, @@ -4121,17 +4135,6 @@ fn fnDecl( const saved_cursor = astgen.saveSourceCursor(); - var decl_gz: GenZir = .{ - .is_comptime = true, - .decl_node_index = fn_proto.ast.proto_node, - .decl_line = astgen.source_line, - .parent = scope, - .astgen = astgen, - .instructions = gz.instructions, - .instructions_top = gz.instructions.items.len, - }; - defer decl_gz.unstack(); - const decl_column = astgen.source_column; // Set this now, since parameter types, return type, etc may be generic. @@ -4152,12 +4155,140 @@ fn fnDecl( const maybe_inline_token = fn_proto.extern_export_inline_token orelse break :blk false; break :blk token_tags[maybe_inline_token] == .keyword_inline; }; + const lib_name = if (fn_proto.lib_name) |lib_name_token| blk: { + const lib_name_str = try astgen.strLitAsString(lib_name_token); + const lib_name_slice = astgen.string_bytes.items[@intFromEnum(lib_name_str.index)..][0..lib_name_str.len]; + if (mem.indexOfScalar(u8, lib_name_slice, 0) != null) { + return astgen.failTok(lib_name_token, "library name cannot contain null bytes", .{}); + } else if (lib_name_str.len == 0) { + return astgen.failTok(lib_name_token, "library name cannot be empty", .{}); + } + break :blk lib_name_str.index; + } else .empty; + if (fn_proto.ast.callconv_expr != 0 and has_inline_keyword) { + return astgen.failNode( + fn_proto.ast.callconv_expr, + "explicit callconv incompatible with inline keyword", + .{}, + ); + } + const maybe_bang = tree.firstToken(fn_proto.ast.return_type) - 1; + const is_inferred_error = token_tags[maybe_bang] == .bang; + if (body_node == 0) { + if (!is_extern) { + return astgen.failTok(fn_proto.ast.fn_token, "non-extern function has no body", .{}); + } + if (is_inferred_error) { + return astgen.failTok(maybe_bang, "function prototype may not have inferred error set", .{}); + } + } else { + assert(!is_extern); // validated by parser (TODO why???) + } + + wip_members.nextDecl(decl_inst); + + var type_gz: GenZir = .{ + .is_comptime = true, + .decl_node_index = fn_proto.ast.proto_node, + .decl_line = astgen.source_line, + .parent = scope, + .astgen = astgen, + .instructions = gz.instructions, + .instructions_top = gz.instructions.items.len, + }; + defer type_gz.unstack(); + + if (is_extern) { + // We include a function *type*, not a value. + const type_inst = try fnProtoExprInner(&type_gz, &type_gz.base, .{ .rl = .none }, decl_node, fn_proto, true); + _ = try type_gz.addBreakWithSrcNode(.break_inline, decl_inst, type_inst, decl_node); + } + + var align_gz = type_gz.makeSubBlock(scope); + defer align_gz.unstack(); + + if (fn_proto.ast.align_expr != 0) { + astgen.restoreSourceCursor(saved_cursor); + const inst = try expr(&align_gz, &align_gz.base, coerced_align_ri, fn_proto.ast.align_expr); + _ = try align_gz.addBreakWithSrcNode(.break_inline, decl_inst, inst, decl_node); + } + + var linksection_gz = align_gz.makeSubBlock(scope); + defer linksection_gz.unstack(); + + if (fn_proto.ast.section_expr != 0) { + astgen.restoreSourceCursor(saved_cursor); + const inst = try expr(&linksection_gz, &linksection_gz.base, coerced_linksection_ri, fn_proto.ast.section_expr); + _ = try linksection_gz.addBreakWithSrcNode(.break_inline, decl_inst, inst, decl_node); + } + + var addrspace_gz = linksection_gz.makeSubBlock(scope); + defer addrspace_gz.unstack(); + + if (fn_proto.ast.addrspace_expr != 0) { + astgen.restoreSourceCursor(saved_cursor); + const addrspace_ty = try addrspace_gz.addBuiltinValue(fn_proto.ast.addrspace_expr, .address_space); + const inst = try expr(&addrspace_gz, &addrspace_gz.base, .{ .rl = .{ .coerced_ty = addrspace_ty } }, fn_proto.ast.section_expr); + _ = try addrspace_gz.addBreakWithSrcNode(.break_inline, decl_inst, inst, decl_node); + } + + var value_gz = addrspace_gz.makeSubBlock(scope); + defer value_gz.unstack(); + + if (!is_extern) { + // We include a function *value*, not a type. + astgen.restoreSourceCursor(saved_cursor); + try astgen.fnDeclInner(&value_gz, &value_gz.base, saved_cursor, decl_inst, decl_node, body_node, fn_proto); + } + + // *Now* we can incorporate the full source code into the hasher. + astgen.src_hasher.update(tree.getNodeSource(decl_node)); + + var hash: std.zig.SrcHash = undefined; + astgen.src_hasher.final(&hash); + try setDeclaration(decl_inst, .{ + .src_hash = hash, + .src_line = type_gz.decl_line, + .src_column = decl_column, + + .kind = .@"const", + .name = try astgen.identAsString(fn_name_token), + .is_pub = is_pub, + .is_threadlocal = false, + .linkage = if (is_extern) .@"extern" else if (is_export) .@"export" else .normal, + .lib_name = lib_name, + + .type_gz = &type_gz, + .align_gz = &align_gz, + .linksection_gz = &linksection_gz, + .addrspace_gz = &addrspace_gz, + .value_gz = &value_gz, + }); +} + +fn fnDeclInner( + astgen: *AstGen, + decl_gz: *GenZir, + scope: *Scope, + saved_cursor: SourceCursor, + decl_inst: Zir.Inst.Index, + decl_node: Ast.Node.Index, + body_node: Ast.Node.Index, + fn_proto: Ast.full.FnProto, +) InnerError!void { + const tree = astgen.tree; + const token_tags = tree.tokens.items(.tag); + const is_noinline = blk: { const maybe_noinline_token = fn_proto.extern_export_inline_token orelse break :blk false; break :blk token_tags[maybe_noinline_token] == .keyword_noinline; }; - - wip_members.nextDecl(decl_inst); + const has_inline_keyword = blk: { + const maybe_inline_token = fn_proto.extern_export_inline_token orelse break :blk false; + break :blk token_tags[maybe_inline_token] == .keyword_inline; + }; + const maybe_bang = tree.firstToken(fn_proto.ast.return_type) - 1; + const is_inferred_error = token_tags[maybe_bang] == .bang; // Note that the capacity here may not be sufficient, as this does not include `anytype` parameters. var param_insts: std.ArrayListUnmanaged(Zir.Inst.Index) = try .initCapacity(astgen.arena, fn_proto.ast.params.len); @@ -4192,11 +4323,9 @@ fn fnDecl( break :blk .empty; const param_name = try astgen.identAsString(name_token); - if (!is_extern) { - try astgen.detectLocalShadowing(params_scope, param_name, name_token, name_bytes, .@"function parameter"); - } + try astgen.detectLocalShadowing(params_scope, param_name, name_token, name_bytes, .@"function parameter"); break :blk param_name; - } else if (!is_extern) { + } else { if (param.anytype_ellipsis3) |tok| { return astgen.failTok(tok, "missing parameter name", .{}); } else { @@ -4225,7 +4354,7 @@ fn fnDecl( } return astgen.failNode(param.type_expr, "missing parameter name", .{}); } - } else .empty; + }; const param_inst = if (is_anytype) param: { const name_token = param.name_token orelse param.anytype_ellipsis3.?; @@ -4251,12 +4380,12 @@ fn fnDecl( break :param param_inst.toRef(); }; - if (param_name == .empty or is_extern) continue; + if (param_name == .empty) continue; const sub_scope = try astgen.arena.create(Scope.LocalVal); sub_scope.* = .{ .parent = params_scope, - .gen_zir = &decl_gz, + .gen_zir = decl_gz, .name = param_name, .inst = param_inst, .token_src = param.name_token.?, @@ -4268,23 +4397,9 @@ fn fnDecl( break :is_var_args false; }; - const lib_name = if (fn_proto.lib_name) |lib_name_token| blk: { - const lib_name_str = try astgen.strLitAsString(lib_name_token); - const lib_name_slice = astgen.string_bytes.items[@intFromEnum(lib_name_str.index)..][0..lib_name_str.len]; - if (mem.indexOfScalar(u8, lib_name_slice, 0) != null) { - return astgen.failTok(lib_name_token, "library name cannot contain null bytes", .{}); - } else if (lib_name_str.len == 0) { - return astgen.failTok(lib_name_token, "library name cannot be empty", .{}); - } - break :blk lib_name_str.index; - } else .empty; - - const maybe_bang = tree.firstToken(fn_proto.ast.return_type) - 1; - const is_inferred_error = token_tags[maybe_bang] == .bang; - // After creating the function ZIR instruction, it will need to update the break - // instructions inside the expression blocks for align, addrspace, cc, and ret_ty - // to use the function instruction as the "block" to break from. + // instructions inside the expression blocks for cc and ret_ty to use the function + // instruction as the body to break from. var ret_gz = decl_gz.makeSubBlock(params_scope); defer ret_gz.unstack(); @@ -4309,13 +4424,6 @@ fn fnDecl( defer cc_gz.unstack(); const cc_ref: Zir.Inst.Ref = blk: { if (fn_proto.ast.callconv_expr != 0) { - if (has_inline_keyword) { - return astgen.failNode( - fn_proto.ast.callconv_expr, - "explicit callconv incompatible with inline keyword", - .{}, - ); - } const inst = try expr( &cc_gz, scope, @@ -4328,10 +4436,6 @@ fn fnDecl( } _ = try cc_gz.addBreak(.break_inline, @enumFromInt(0), inst); break :blk inst; - } else if (is_extern) { - const inst = try cc_gz.addBuiltinValue(decl_node, .calling_convention_c); - _ = try cc_gz.addBreak(.break_inline, @enumFromInt(0), inst); - break :blk inst; } else if (has_inline_keyword) { const inst = try cc_gz.addBuiltinValue(decl_node, .calling_convention_inline); _ = try cc_gz.addBreak(.break_inline, @enumFromInt(0), inst); @@ -4341,167 +4445,86 @@ fn fnDecl( } }; - const func_inst: Zir.Inst.Ref = if (body_node == 0) func: { - if (!is_extern) { - return astgen.failTok(fn_proto.ast.fn_token, "non-extern function has no body", .{}); - } - if (is_inferred_error) { - return astgen.failTok(maybe_bang, "function prototype may not have inferred error set", .{}); - } - break :func try decl_gz.addFunc(.{ - .src_node = decl_node, - .cc_ref = cc_ref, - .cc_gz = &cc_gz, - .ret_ref = ret_ref, - .ret_gz = &ret_gz, - .ret_param_refs = ret_body_param_refs, - .param_block = decl_inst, - .param_insts = param_insts.items, - .body_gz = null, - .lib_name = lib_name, - .is_var_args = is_var_args, - .is_inferred_error = false, - .is_test = false, - .is_extern = true, - .is_noinline = is_noinline, - .noalias_bits = noalias_bits, - .proto_hash = undefined, // ignored for `body_gz == null` - }); - } else func: { - var body_gz: GenZir = .{ - .is_comptime = false, - .decl_node_index = fn_proto.ast.proto_node, - .decl_line = decl_gz.decl_line, - .parent = params_scope, - .astgen = astgen, - .instructions = gz.instructions, - .instructions_top = gz.instructions.items.len, - }; - defer body_gz.unstack(); - - // We want `params_scope` to be stacked like this: - // body_gz (top) - // param2 - // param1 - // param0 - // decl_gz (bottom) - - // Construct the prototype hash. - // Leave `astgen.src_hasher` unmodified; this will be used for hashing - // the *whole* function declaration, including its body. - var proto_hasher = astgen.src_hasher; - const proto_node = tree.nodes.items(.data)[decl_node].lhs; - proto_hasher.update(tree.getNodeSource(proto_node)); - var proto_hash: std.zig.SrcHash = undefined; - proto_hasher.final(&proto_hash); - - const prev_fn_block = astgen.fn_block; - const prev_fn_ret_ty = astgen.fn_ret_ty; - defer { - astgen.fn_block = prev_fn_block; - astgen.fn_ret_ty = prev_fn_ret_ty; - } - astgen.fn_block = &body_gz; - astgen.fn_ret_ty = if (is_inferred_error or ret_ref.toIndex() != null) r: { - // We're essentially guaranteed to need the return type at some point, - // since the return type is likely not `void` or `noreturn` so there - // will probably be an explicit return requiring RLS. Fetch this - // return type now so the rest of the function can use it. - break :r try body_gz.addNode(.ret_type, decl_node); - } else ret_ref; - - const prev_var_args = astgen.fn_var_args; - astgen.fn_var_args = is_var_args; - defer astgen.fn_var_args = prev_var_args; - - astgen.advanceSourceCursorToNode(body_node); - const lbrace_line = astgen.source_line - decl_gz.decl_line; - const lbrace_column = astgen.source_column; - - _ = try fullBodyExpr(&body_gz, &body_gz.base, .{ .rl = .none }, body_node, .allow_branch_hint); - try checkUsed(gz, scope, params_scope); - - if (!body_gz.endsWithNoReturn()) { - // As our last action before the return, "pop" the error trace if needed - _ = try body_gz.addRestoreErrRetIndex(.ret, .always, decl_node); - - // Add implicit return at end of function. - _ = try body_gz.addUnTok(.ret_implicit, .void_value, tree.lastToken(body_node)); - } - - break :func try decl_gz.addFunc(.{ - .src_node = decl_node, - .cc_ref = cc_ref, - .cc_gz = &cc_gz, - .ret_ref = ret_ref, - .ret_gz = &ret_gz, - .ret_param_refs = ret_body_param_refs, - .lbrace_line = lbrace_line, - .lbrace_column = lbrace_column, - .param_block = decl_inst, - .param_insts = param_insts.items, - .body_gz = &body_gz, - .lib_name = lib_name, - .is_var_args = is_var_args, - .is_inferred_error = is_inferred_error, - .is_test = false, - .is_extern = false, - .is_noinline = is_noinline, - .noalias_bits = noalias_bits, - .proto_hash = proto_hash, - }); + var body_gz: GenZir = .{ + .is_comptime = false, + .decl_node_index = fn_proto.ast.proto_node, + .decl_line = decl_gz.decl_line, + .parent = params_scope, + .astgen = astgen, + .instructions = decl_gz.instructions, + .instructions_top = decl_gz.instructions.items.len, }; + defer body_gz.unstack(); + + // The scope stack looks like this: + // body_gz (top) + // param2 + // param1 + // param0 + // decl_gz (bottom) + + // Construct the prototype hash. + // Leave `astgen.src_hasher` unmodified; this will be used for hashing + // the *whole* function declaration, including its body. + var proto_hasher = astgen.src_hasher; + const proto_node = tree.nodes.items(.data)[decl_node].lhs; + proto_hasher.update(tree.getNodeSource(proto_node)); + var proto_hash: std.zig.SrcHash = undefined; + proto_hasher.final(&proto_hash); - // Before we stack more stuff onto `decl_gz`, add its final instruction. - _ = try decl_gz.addBreak(.break_inline, decl_inst, func_inst); + const prev_fn_block = astgen.fn_block; + const prev_fn_ret_ty = astgen.fn_ret_ty; + defer { + astgen.fn_block = prev_fn_block; + astgen.fn_ret_ty = prev_fn_ret_ty; + } + astgen.fn_block = &body_gz; + astgen.fn_ret_ty = if (is_inferred_error or ret_ref.toIndex() != null) r: { + // We're essentially guaranteed to need the return type at some point, + // since the return type is likely not `void` or `noreturn` so there + // will probably be an explicit return requiring RLS. Fetch this + // return type now so the rest of the function can use it. + break :r try body_gz.addNode(.ret_type, decl_node); + } else ret_ref; - // Now that `cc_gz,` `ret_gz`, and `body_gz` are unstacked, we evaluate align, addrspace, and linksection. + const prev_var_args = astgen.fn_var_args; + astgen.fn_var_args = is_var_args; + defer astgen.fn_var_args = prev_var_args; - // We're jumping back in source, so restore the cursor. - astgen.restoreSourceCursor(saved_cursor); + astgen.advanceSourceCursorToNode(body_node); + const lbrace_line = astgen.source_line - decl_gz.decl_line; + const lbrace_column = astgen.source_column; - var align_gz = decl_gz.makeSubBlock(scope); - defer align_gz.unstack(); - if (fn_proto.ast.align_expr != 0) { - const inst = try expr(&decl_gz, &decl_gz.base, coerced_align_ri, fn_proto.ast.align_expr); - _ = try align_gz.addBreak(.break_inline, decl_inst, inst); - } + _ = try fullBodyExpr(&body_gz, &body_gz.base, .{ .rl = .none }, body_node, .allow_branch_hint); + try checkUsed(decl_gz, scope, params_scope); - var section_gz = align_gz.makeSubBlock(scope); - defer section_gz.unstack(); - if (fn_proto.ast.section_expr != 0) { - const inst = try expr(&decl_gz, scope, .{ .rl = .{ .coerced_ty = .slice_const_u8_type } }, fn_proto.ast.section_expr); - _ = try section_gz.addBreak(.break_inline, decl_inst, inst); - } + if (!body_gz.endsWithNoReturn()) { + // As our last action before the return, "pop" the error trace if needed + _ = try body_gz.addRestoreErrRetIndex(.ret, .always, decl_node); - var addrspace_gz = section_gz.makeSubBlock(scope); - defer addrspace_gz.unstack(); - if (fn_proto.ast.addrspace_expr != 0) { - const addrspace_ty = try decl_gz.addBuiltinValue(fn_proto.ast.addrspace_expr, .address_space); - const inst = try expr(&decl_gz, scope, .{ .rl = .{ .coerced_ty = addrspace_ty } }, fn_proto.ast.addrspace_expr); - _ = try addrspace_gz.addBreak(.break_inline, decl_inst, inst); + // Add implicit return at end of function. + _ = try body_gz.addUnTok(.ret_implicit, .void_value, tree.lastToken(body_node)); } - // *Now* we can incorporate the full source code into the hasher. - astgen.src_hasher.update(tree.getNodeSource(decl_node)); - - var hash: std.zig.SrcHash = undefined; - astgen.src_hasher.final(&hash); - try setDeclaration( - decl_inst, - hash, - .{ .named = fn_name_token }, - decl_gz.decl_line, - decl_column, - is_pub, - is_export, - &decl_gz, - .{ - .align_gz = &align_gz, - .linksection_gz = §ion_gz, - .addrspace_gz = &addrspace_gz, - }, - ); + const func_inst = try decl_gz.addFunc(.{ + .src_node = decl_node, + .cc_ref = cc_ref, + .cc_gz = &cc_gz, + .ret_ref = ret_ref, + .ret_gz = &ret_gz, + .ret_param_refs = ret_body_param_refs, + .lbrace_line = lbrace_line, + .lbrace_column = lbrace_column, + .param_block = decl_inst, + .param_insts = param_insts.items, + .body_gz = &body_gz, + .is_var_args = is_var_args, + .is_inferred_error = is_inferred_error, + .is_noinline = is_noinline, + .noalias_bits = noalias_bits, + .proto_hash = proto_hash, + }); + _ = try decl_gz.addBreakWithSrcNode(.break_inline, decl_inst, func_inst, decl_node); } fn globalVarDecl( @@ -4522,26 +4545,7 @@ fn globalVarDecl( astgen.src_hasher.update(std.mem.asBytes(&astgen.source_column)); 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 decl_inst = try gz.makeDeclaration(node); - const name_token = var_decl.ast.mut_token + 1; - astgen.advanceSourceCursorToNode(node); - - var block_scope: GenZir = .{ - .parent = scope, - .decl_node_index = node, - .decl_line = astgen.source_line, - .astgen = astgen, - .is_comptime = true, - .instructions = gz.instructions, - .instructions_top = gz.instructions.items.len, - }; - defer block_scope.unstack(); - - const decl_column = astgen.source_column; - const is_pub = var_decl.visib_token != null; const is_export = blk: { const maybe_export_token = var_decl.extern_export_token orelse break :blk false; @@ -4551,15 +4555,12 @@ fn globalVarDecl( const maybe_extern_token = var_decl.extern_export_token orelse break :blk false; break :blk token_tags[maybe_extern_token] == .keyword_extern; }; - wip_members.nextDecl(decl_inst); - const is_threadlocal = if (var_decl.threadlocal_token) |tok| blk: { if (!is_mutable) { return astgen.failTok(tok, "threadlocal variable cannot be constant", .{}); } break :blk true; } else false; - const lib_name = if (var_decl.lib_name) |lib_name_token| blk: { const lib_name_str = try astgen.strLitAsString(lib_name_token); const lib_name_slice = astgen.string_bytes.items[@intFromEnum(lib_name_str.index)..][0..lib_name_str.len]; @@ -4571,9 +4572,14 @@ fn globalVarDecl( break :blk lib_name_str.index; } else .empty; - assert(var_decl.comptime_token == null); // handled by parser + astgen.advanceSourceCursorToNode(node); + + const decl_column = astgen.source_column; + + const decl_inst = try gz.makeDeclaration(node); + wip_members.nextDecl(decl_inst); - const var_inst: Zir.Inst.Ref = if (var_decl.ast.init_node != 0) vi: { + if (var_decl.ast.init_node != 0) { if (is_extern) { return astgen.failNode( var_decl.ast.init_node, @@ -4581,102 +4587,91 @@ fn globalVarDecl( .{}, ); } + } else { + if (!is_extern) { + return astgen.failNode(node, "variables must be initialized", .{}); + } + } - const type_inst: Zir.Inst.Ref = if (var_decl.ast.type_node != 0) - try expr( - &block_scope, - &block_scope.base, - coerced_type_ri, - var_decl.ast.type_node, - ) - else - .none; - - block_scope.anon_name_strategy = .parent; + if (is_extern and var_decl.ast.type_node == 0) { + return astgen.failNode(node, "unable to infer variable type", .{}); + } - const init_inst = try expr( - &block_scope, - &block_scope.base, - if (type_inst != .none) .{ .rl = .{ .ty = type_inst } } else .{ .rl = .none }, - var_decl.ast.init_node, - ); + assert(var_decl.comptime_token == null); // handled by parser - if (is_mutable) { - const var_inst = try block_scope.addVar(.{ - .var_type = type_inst, - .lib_name = .empty, - .align_inst = .none, // passed via the decls data - .init = init_inst, - .is_extern = false, - .is_const = !is_mutable, - .is_threadlocal = is_threadlocal, - }); - break :vi var_inst; - } else { - break :vi init_inst; - } - } else if (!is_extern) { - return astgen.failNode(node, "variables must be initialized", .{}); - } else if (var_decl.ast.type_node != 0) vi: { - // Extern variable which has an explicit type. - const type_inst = try typeExpr(&block_scope, &block_scope.base, var_decl.ast.type_node); - - block_scope.anon_name_strategy = .parent; - - const var_inst = try block_scope.addVar(.{ - .var_type = type_inst, - .lib_name = lib_name, - .align_inst = .none, // passed via the decls data - .init = .none, - .is_extern = true, - .is_const = !is_mutable, - .is_threadlocal = is_threadlocal, - }); - break :vi var_inst; - } else { - return astgen.failNode(node, "unable to infer variable type", .{}); + var type_gz: GenZir = .{ + .parent = scope, + .decl_node_index = node, + .decl_line = astgen.source_line, + .astgen = astgen, + .is_comptime = true, + .instructions = gz.instructions, + .instructions_top = gz.instructions.items.len, }; + defer type_gz.unstack(); + + if (var_decl.ast.type_node != 0) { + const type_inst = try expr(&type_gz, &type_gz.base, coerced_type_ri, var_decl.ast.type_node); + _ = try type_gz.addBreakWithSrcNode(.break_inline, decl_inst, type_inst, node); + } - // 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, decl_inst, var_inst, node); + var align_gz = type_gz.makeSubBlock(scope); + defer align_gz.unstack(); - var align_gz = block_scope.makeSubBlock(scope); if (var_decl.ast.align_node != 0) { - const align_inst = try fullBodyExpr(&align_gz, &align_gz.base, coerced_align_ri, var_decl.ast.align_node, .normal); + 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); } - var linksection_gz = align_gz.makeSubBlock(scope); + var linksection_gz = type_gz.makeSubBlock(scope); + defer linksection_gz.unstack(); + if (var_decl.ast.section_node != 0) { - const linksection_inst = try fullBodyExpr(&linksection_gz, &linksection_gz.base, coerced_linksection_ri, var_decl.ast.section_node, .normal); + 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); + var addrspace_gz = type_gz.makeSubBlock(scope); + defer addrspace_gz.unstack(); + if (var_decl.ast.addrspace_node != 0) { const addrspace_ty = try addrspace_gz.addBuiltinValue(var_decl.ast.addrspace_node, .address_space); - const addrspace_inst = try fullBodyExpr(&addrspace_gz, &addrspace_gz.base, .{ .rl = .{ .coerced_ty = addrspace_ty } }, var_decl.ast.addrspace_node, .normal); + const addrspace_inst = try expr(&addrspace_gz, &addrspace_gz.base, .{ .rl = .{ .coerced_ty = addrspace_ty } }, var_decl.ast.addrspace_node); _ = try addrspace_gz.addBreakWithSrcNode(.break_inline, decl_inst, addrspace_inst, node); } + var init_gz = type_gz.makeSubBlock(scope); + defer init_gz.unstack(); + + if (var_decl.ast.init_node != 0) { + init_gz.anon_name_strategy = .parent; + const init_ri: ResultInfo = if (var_decl.ast.type_node != 0) .{ + .rl = .{ .coerced_ty = decl_inst.toRef() }, + } else .{ .rl = .none }; + const init_inst = try expr(&init_gz, &init_gz.base, init_ri, var_decl.ast.init_node); + _ = try init_gz.addBreakWithSrcNode(.break_inline, decl_inst, init_inst, node); + } + var hash: std.zig.SrcHash = undefined; astgen.src_hasher.final(&hash); - try setDeclaration( - decl_inst, - hash, - .{ .named = name_token }, - block_scope.decl_line, - decl_column, - is_pub, - is_export, - &block_scope, - .{ - .align_gz = &align_gz, - .linksection_gz = &linksection_gz, - .addrspace_gz = &addrspace_gz, - }, - ); + try setDeclaration(decl_inst, .{ + .src_hash = hash, + .src_line = type_gz.decl_line, + .src_column = decl_column, + + .kind = if (is_mutable) .@"var" else .@"const", + .name = try astgen.identAsString(name_token), + .is_pub = is_pub, + .is_threadlocal = is_threadlocal, + .linkage = if (is_extern) .@"extern" else if (is_export) .@"export" else .normal, + .lib_name = lib_name, + + .type_gz = &type_gz, + .align_gz = &align_gz, + .linksection_gz = &linksection_gz, + .addrspace_gz = &addrspace_gz, + .value_gz = &init_gz, + }); } fn comptimeDecl( @@ -4702,37 +4697,45 @@ fn comptimeDecl( wip_members.nextDecl(decl_inst); astgen.advanceSourceCursorToNode(node); - var decl_block: GenZir = .{ + // This is just needed for the `setDeclaration` call. + var dummy_gz = gz.makeSubBlock(scope); + defer dummy_gz.unstack(); + + var comptime_gz: GenZir = .{ .is_comptime = true, .decl_node_index = node, .decl_line = astgen.source_line, .parent = scope, .astgen = astgen, - .instructions = gz.instructions, - .instructions_top = gz.instructions.items.len, + .instructions = dummy_gz.instructions, + .instructions_top = dummy_gz.instructions.items.len, }; - defer decl_block.unstack(); + defer comptime_gz.unstack(); const decl_column = astgen.source_column; - const block_result = try fullBodyExpr(&decl_block, &decl_block.base, .{ .rl = .none }, body_node, .normal); - if (decl_block.isEmpty() or !decl_block.refIsNoReturn(block_result)) { - _ = try decl_block.addBreak(.break_inline, decl_inst, .void_value); + const block_result = try fullBodyExpr(&comptime_gz, &comptime_gz.base, .{ .rl = .none }, body_node, .normal); + if (comptime_gz.isEmpty() or !comptime_gz.refIsNoReturn(block_result)) { + _ = try comptime_gz.addBreak(.break_inline, decl_inst, .void_value); } var hash: std.zig.SrcHash = undefined; astgen.src_hasher.final(&hash); - try setDeclaration( - decl_inst, - hash, - .@"comptime", - decl_block.decl_line, - decl_column, - false, - false, - &decl_block, - null, - ); + try setDeclaration(decl_inst, .{ + .src_hash = hash, + .src_line = comptime_gz.decl_line, + .src_column = decl_column, + .kind = .@"comptime", + .name = .empty, + .is_pub = false, + .is_threadlocal = false, + .linkage = .normal, + .type_gz = &dummy_gz, + .align_gz = &dummy_gz, + .linksection_gz = &dummy_gz, + .addrspace_gz = &dummy_gz, + .value_gz = &comptime_gz, + }); } fn usingnamespaceDecl( @@ -4764,7 +4767,11 @@ fn usingnamespaceDecl( wip_members.nextDecl(decl_inst); astgen.advanceSourceCursorToNode(node); - var decl_block: GenZir = .{ + // This is just needed for the `setDeclaration` call. + var dummy_gz = gz.makeSubBlock(scope); + defer dummy_gz.unstack(); + + var usingnamespace_gz: GenZir = .{ .is_comptime = true, .decl_node_index = node, .decl_line = astgen.source_line, @@ -4773,26 +4780,30 @@ fn usingnamespaceDecl( .instructions = gz.instructions, .instructions_top = gz.instructions.items.len, }; - defer decl_block.unstack(); + defer usingnamespace_gz.unstack(); const decl_column = astgen.source_column; - const namespace_inst = try typeExpr(&decl_block, &decl_block.base, type_expr); - _ = try decl_block.addBreak(.break_inline, decl_inst, namespace_inst); + const namespace_inst = try typeExpr(&usingnamespace_gz, &usingnamespace_gz.base, type_expr); + _ = try usingnamespace_gz.addBreak(.break_inline, decl_inst, namespace_inst); var hash: std.zig.SrcHash = undefined; astgen.src_hasher.final(&hash); - try setDeclaration( - decl_inst, - hash, - .@"usingnamespace", - decl_block.decl_line, - decl_column, - is_pub, - false, - &decl_block, - null, - ); + try setDeclaration(decl_inst, .{ + .src_hash = hash, + .src_line = usingnamespace_gz.decl_line, + .src_column = decl_column, + .kind = .@"usingnamespace", + .name = .empty, + .is_pub = is_pub, + .is_threadlocal = false, + .linkage = .normal, + .type_gz = &dummy_gz, + .align_gz = &dummy_gz, + .linksection_gz = &dummy_gz, + .addrspace_gz = &dummy_gz, + .value_gz = &usingnamespace_gz, + }); } fn testDecl( @@ -4819,14 +4830,18 @@ fn testDecl( wip_members.nextDecl(decl_inst); astgen.advanceSourceCursorToNode(node); + // This is just needed for the `setDeclaration` call. + var dummy_gz: GenZir = gz.makeSubBlock(scope); + defer dummy_gz.unstack(); + var decl_block: GenZir = .{ .is_comptime = true, .decl_node_index = node, .decl_line = astgen.source_line, .parent = scope, .astgen = astgen, - .instructions = gz.instructions, - .instructions_top = gz.instructions.items.len, + .instructions = dummy_gz.instructions, + .instructions_top = dummy_gz.instructions.items.len, }; defer decl_block.unstack(); @@ -4835,11 +4850,21 @@ fn testDecl( const main_tokens = tree.nodes.items(.main_token); const token_tags = tree.tokens.items(.tag); const test_token = main_tokens[node]; + const test_name_token = test_token + 1; - const test_name: DeclarationName = switch (token_tags[test_name_token]) { - else => .unnamed_test, - .string_literal => .{ .named_test = test_name_token }, - .identifier => blk: { + const test_name: Zir.NullTerminatedString = switch (token_tags[test_name_token]) { + else => .empty, + .string_literal => name: { + const name = try astgen.strLitAsString(test_name_token); + const slice = astgen.string_bytes.items[@intFromEnum(name.index)..][0..name.len]; + if (mem.indexOfScalar(u8, slice, 0) != null) { + return astgen.failTok(test_name_token, "test name cannot contain null bytes", .{}); + } else if (slice.len == 0) { + return astgen.failTok(test_name_token, "empty test name must be omitted", .{}); + } + break :name name.index; + }, + .identifier => name: { 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", .{}); @@ -4909,7 +4934,7 @@ fn testDecl( return astgen.failTok(test_name_token, "use of undeclared identifier '{s}'", .{ident_name}); } - break :blk .{ .decltest = test_name_token }; + break :name try astgen.identAsString(test_name_token); }, }; @@ -4965,11 +4990,8 @@ fn testDecl( .lbrace_column = lbrace_column, .param_block = decl_inst, .body_gz = &fn_block, - .lib_name = .empty, .is_var_args = false, .is_inferred_error = false, - .is_test = true, - .is_extern = false, .is_noinline = false, .noalias_bits = 0, @@ -4981,17 +5003,27 @@ fn testDecl( var hash: std.zig.SrcHash = undefined; astgen.src_hasher.final(&hash); - try setDeclaration( - decl_inst, - hash, - test_name, - decl_block.decl_line, - decl_column, - false, - false, - &decl_block, - null, - ); + try setDeclaration(decl_inst, .{ + .src_hash = hash, + .src_line = decl_block.decl_line, + .src_column = decl_column, + + .kind = switch (token_tags[test_name_token]) { + .string_literal => .@"test", + .identifier => .decltest, + else => .unnamed_test, + }, + .name = test_name, + .is_pub = false, + .is_threadlocal = false, + .linkage = .normal, + + .type_gz = &dummy_gz, + .align_gz = &dummy_gz, + .linksection_gz = &dummy_gz, + .addrspace_gz = &dummy_gz, + .value_gz = &decl_block, + }); } fn structDeclInner( @@ -5882,7 +5914,8 @@ fn containerMember( try addFailedDeclaration( wip_members, gz, - .{ .named = full.name_token.? }, + .@"const", + try astgen.identAsString(full.name_token.?), full.ast.proto_node, full.visib_token != null, ); @@ -5904,7 +5937,8 @@ fn containerMember( try addFailedDeclaration( wip_members, gz, - .{ .named = full.ast.mut_token + 1 }, + .@"const", // doesn't really matter + try astgen.identAsString(full.ast.mut_token + 1), member_node, full.visib_token != null, ); @@ -5922,6 +5956,7 @@ fn containerMember( wip_members, gz, .@"comptime", + .empty, member_node, false, ); @@ -5938,6 +5973,7 @@ fn containerMember( wip_members, gz, .@"usingnamespace", + .empty, member_node, is_pub: { const main_tokens = tree.nodes.items(.main_token); @@ -5962,6 +5998,7 @@ fn containerMember( wip_members, gz, .unnamed_test, + .empty, member_node, false, ); @@ -11670,23 +11707,6 @@ fn strLitNodeAsString(astgen: *AstGen, node: Ast.Node.Index) !IndexSlice { }; } -fn testNameString(astgen: *AstGen, str_lit_token: Ast.TokenIndex) !Zir.NullTerminatedString { - const gpa = astgen.gpa; - const string_bytes = &astgen.string_bytes; - const str_index: u32 = @intCast(string_bytes.items.len); - const token_bytes = astgen.tree.tokenSlice(str_lit_token); - try string_bytes.append(gpa, 0); // Indicates this is a test. - try astgen.parseStrLit(str_lit_token, string_bytes, token_bytes, 0); - const slice = string_bytes.items[str_index + 1 ..]; - if (mem.indexOfScalar(u8, slice, 0) != null) { - return astgen.failTok(str_lit_token, "test name cannot contain null bytes", .{}); - } else if (slice.len == 0) { - return astgen.failTok(str_lit_token, "empty test name must be omitted", .{}); - } - try string_bytes.append(gpa, 0); - return @enumFromInt(str_index); -} - const Scope = struct { tag: Tag, @@ -12077,12 +12097,9 @@ const GenZir = struct { cc_ref: Zir.Inst.Ref, ret_ref: Zir.Inst.Ref, - lib_name: Zir.NullTerminatedString, noalias_bits: u32, is_var_args: bool, is_inferred_error: bool, - is_test: bool, - is_extern: bool, is_noinline: bool, /// Ignored if `body_gz == null`. @@ -12150,9 +12167,8 @@ const GenZir = struct { const body_len = astgen.countBodyLenAfterFixupsExtraRefs(body, args.param_insts); - const tag: Zir.Inst.Tag, const payload_index: u32 = if (args.cc_ref != .none or args.lib_name != .empty or - args.is_var_args or args.is_test or args.is_extern or - args.noalias_bits != 0 or args.is_noinline) + const tag: Zir.Inst.Tag, const payload_index: u32 = if (args.cc_ref != .none or + args.is_var_args or args.noalias_bits != 0 or args.is_noinline) inst_info: { try astgen.extra.ensureUnusedCapacity( gpa, @@ -12160,7 +12176,6 @@ const GenZir = struct { fancyFnExprExtraLen(astgen, &.{}, cc_body, args.cc_ref) + fancyFnExprExtraLen(astgen, args.ret_param_refs, ret_body, ret_ref) + body_len + src_locs_and_hash.len + - @intFromBool(args.lib_name != .empty) + @intFromBool(args.noalias_bits != 0), ); const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.FuncFancy{ @@ -12169,10 +12184,7 @@ const GenZir = struct { .bits = .{ .is_var_args = args.is_var_args, .is_inferred_error = args.is_inferred_error, - .is_test = args.is_test, - .is_extern = args.is_extern, .is_noinline = args.is_noinline, - .has_lib_name = args.lib_name != .empty, .has_any_noalias = args.noalias_bits != 0, .has_cc_ref = args.cc_ref != .none, @@ -12182,9 +12194,6 @@ const GenZir = struct { .has_ret_ty_body = ret_body.len != 0, }, }); - if (args.lib_name != .empty) { - astgen.extra.appendAssumeCapacity(@intFromEnum(args.lib_name)); - } const zir_datas = astgen.instructions.items(.data); if (cc_body.len != 0) { @@ -12279,61 +12288,6 @@ const GenZir = struct { @intFromBool(main_body.len > 0 or ref != .none); } - fn addVar(gz: *GenZir, args: struct { - align_inst: Zir.Inst.Ref, - lib_name: Zir.NullTerminatedString, - var_type: Zir.Inst.Ref, - init: Zir.Inst.Ref, - is_extern: bool, - is_const: bool, - is_threadlocal: bool, - }) !Zir.Inst.Ref { - const astgen = gz.astgen; - const gpa = astgen.gpa; - - try gz.instructions.ensureUnusedCapacity(gpa, 1); - try astgen.instructions.ensureUnusedCapacity(gpa, 1); - - try astgen.extra.ensureUnusedCapacity( - gpa, - @typeInfo(Zir.Inst.ExtendedVar).@"struct".fields.len + - @intFromBool(args.lib_name != .empty) + - @intFromBool(args.align_inst != .none) + - @intFromBool(args.init != .none), - ); - const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.ExtendedVar{ - .var_type = args.var_type, - }); - if (args.lib_name != .empty) { - astgen.extra.appendAssumeCapacity(@intFromEnum(args.lib_name)); - } - if (args.align_inst != .none) { - astgen.extra.appendAssumeCapacity(@intFromEnum(args.align_inst)); - } - if (args.init != .none) { - astgen.extra.appendAssumeCapacity(@intFromEnum(args.init)); - } - - const new_index: Zir.Inst.Index = @enumFromInt(astgen.instructions.len); - astgen.instructions.appendAssumeCapacity(.{ - .tag = .extended, - .data = .{ .extended = .{ - .opcode = .variable, - .small = @bitCast(Zir.Inst.ExtendedVar.Small{ - .has_lib_name = args.lib_name != .empty, - .has_align = args.align_inst != .none, - .has_init = args.init != .none, - .is_extern = args.is_extern, - .is_const = args.is_const, - .is_threadlocal = args.is_threadlocal, - }), - .operand = payload_index, - } }, - }); - gz.instructions.appendAssumeCapacity(new_index); - return new_index.toRef(); - } - fn addInt(gz: *GenZir, integer: u64) !Zir.Inst.Ref { return gz.add(.{ .tag = .int, @@ -13909,14 +13863,18 @@ const DeclarationName = union(enum) { fn addFailedDeclaration( wip_members: *WipMembers, gz: *GenZir, - name: DeclarationName, + kind: Zir.Inst.Declaration.Unwrapped.Kind, + name: Zir.NullTerminatedString, src_node: Ast.Node.Index, is_pub: bool, ) !void { const decl_inst = try gz.makeDeclaration(src_node); wip_members.nextDecl(decl_inst); - var decl_gz = gz.makeSubBlock(&gz.base); // scope doesn't matter here - _ = try decl_gz.add(.{ + + var dummy_gz = gz.makeSubBlock(&gz.base); + + var value_gz = gz.makeSubBlock(&gz.base); // scope doesn't matter here + _ = try value_gz.add(.{ .tag = .extended, .data = .{ .extended = .{ .opcode = .astgen_error, @@ -13924,110 +13882,198 @@ fn addFailedDeclaration( .operand = undefined, } }, }); - try setDeclaration( - decl_inst, - @splat(0), // use a fixed hash to represent an AstGen failure; we don't care about source changes if AstGen still failed! - name, - gz.astgen.source_line, - gz.astgen.source_column, - is_pub, - false, // we don't care about exports since semantic analysis will fail - &decl_gz, - null, - ); + + try setDeclaration(decl_inst, .{ + .src_hash = @splat(0), // use a fixed hash to represent an AstGen failure; we don't care about source changes if AstGen still failed! + .src_line = gz.astgen.source_line, + .src_column = gz.astgen.source_column, + .kind = kind, + .name = name, + .is_pub = is_pub, + .is_threadlocal = false, + .linkage = .normal, + .type_gz = &dummy_gz, + .align_gz = &dummy_gz, + .linksection_gz = &dummy_gz, + .addrspace_gz = &dummy_gz, + .value_gz = &value_gz, + }); } /// Sets all extra data for a `declaration` instruction. -/// Unstacks `value_gz`, `align_gz`, `linksection_gz`, and `addrspace_gz`. +/// Unstacks `type_gz`, `align_gz`, `linksection_gz`, `addrspace_gz`, and `value_gz`. fn setDeclaration( decl_inst: Zir.Inst.Index, - src_hash: std.zig.SrcHash, - name: DeclarationName, - src_line: u32, - src_column: u32, - is_pub: bool, - is_export: bool, - 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`. + args: struct { + src_hash: std.zig.SrcHash, + src_line: u32, + src_column: u32, + + kind: Zir.Inst.Declaration.Unwrapped.Kind, + name: Zir.NullTerminatedString, + is_pub: bool, + is_threadlocal: bool, + linkage: Zir.Inst.Declaration.Unwrapped.Linkage, + lib_name: Zir.NullTerminatedString = .empty, + + type_gz: *GenZir, + /// Must be stacked on `type_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. + /// Must be stacked on `linksection_gz`. addrspace_gz: *GenZir, + /// Must be stacked on `addrspace_gz` and have nothing stacked on top of it. + value_gz: *GenZir, }, ) !void { - const astgen = value_gz.astgen; + const astgen = args.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 type_body = args.type_gz.instructionsSliceUpto(args.align_gz); + const align_body = args.align_gz.instructionsSliceUpto(args.linksection_gz); + const linksection_body = args.linksection_gz.instructionsSliceUpto(args.addrspace_gz); + const addrspace_body = args.addrspace_gz.instructionsSliceUpto(args.value_gz); + const value_body = args.value_gz.instructionsSlice(); + + const has_name = args.name != .empty; + const has_lib_name = args.lib_name != .empty; + const has_type_body = type_body.len != 0; + const has_special_body = align_body.len != 0 or linksection_body.len != 0 or addrspace_body.len != 0; + const has_value_body = value_body.len != 0; + + const id: Zir.Inst.Declaration.Flags.Id = switch (args.kind) { + .unnamed_test => .unnamed_test, + .@"test" => .@"test", + .decltest => .decltest, + .@"comptime" => .@"comptime", + .@"usingnamespace" => if (args.is_pub) .pub_usingnamespace else .@"usingnamespace", + .@"const" => switch (args.linkage) { + .normal => if (args.is_pub) id: { + if (has_special_body) break :id .pub_const; + if (has_type_body) break :id .pub_const_typed; + break :id .pub_const_simple; + } else id: { + if (has_special_body) break :id .@"const"; + if (has_type_body) break :id .const_typed; + break :id .const_simple; + }, + .@"extern" => if (args.is_pub) id: { + if (has_lib_name) break :id .pub_extern_const; + if (has_special_body) break :id .pub_extern_const; + break :id .pub_extern_const_simple; + } else id: { + if (has_lib_name) break :id .extern_const; + if (has_special_body) break :id .extern_const; + break :id .extern_const_simple; + }, + .@"export" => if (args.is_pub) .pub_export_const else .export_const, + }, + .@"var" => switch (args.linkage) { + .normal => if (args.is_pub) id: { + if (args.is_threadlocal) break :id .pub_var_threadlocal; + if (has_special_body) break :id .pub_var; + if (has_type_body) break :id .pub_var; + break :id .pub_var_simple; + } else id: { + if (args.is_threadlocal) break :id .var_threadlocal; + if (has_special_body) break :id .@"var"; + if (has_type_body) break :id .@"var"; + break :id .var_simple; + }, + .@"extern" => if (args.is_pub) id: { + if (args.is_threadlocal) break :id .pub_extern_var_threadlocal; + break :id .pub_extern_var; + } else id: { + if (args.is_threadlocal) break :id .extern_var_threadlocal; + break :id .extern_var; + }, + .@"export" => if (args.is_pub) id: { + if (args.is_threadlocal) break :id .pub_export_var_threadlocal; + break :id .pub_export_var; + } else id: { + if (args.is_threadlocal) break :id .export_var_threadlocal; + break :id .export_var; + }, + }, + }; - const value_len = astgen.countBodyLenAfterFixups(value_body); + assert(id.hasTypeBody() or !has_type_body); + assert(id.hasSpecialBodies() or !has_special_body); + assert(id.hasValueBody() == has_value_body); + assert(id.linkage() == args.linkage); + assert(id.hasName() == has_name); + assert(id.hasLibName() or !has_lib_name); + assert(id.isPub() == args.is_pub); + assert(id.isThreadlocal() == args.is_threadlocal); + + const type_len = astgen.countBodyLenAfterFixups(type_body); const align_len = astgen.countBodyLenAfterFixups(align_body); const linksection_len = astgen.countBodyLenAfterFixups(linksection_body); const addrspace_len = astgen.countBodyLenAfterFixups(addrspace_body); + const value_len = astgen.countBodyLenAfterFixups(value_body); + + const src_hash_arr: [4]u32 = @bitCast(args.src_hash); + const flags: Zir.Inst.Declaration.Flags = .{ + .src_line = @intCast(args.src_line), + .src_column = @intCast(args.src_column), + .id = id, + }; + const flags_arr: [2]u32 = @bitCast(flags); - const src_hash_arr: [4]u32 = @bitCast(src_hash); + const need_extra: usize = + @typeInfo(Zir.Inst.Declaration).@"struct".fields.len + + @as(usize, @intFromBool(id.hasName())) + + @as(usize, @intFromBool(id.hasLibName())) + + @as(usize, @intFromBool(id.hasTypeBody())) + + 3 * @as(usize, @intFromBool(id.hasSpecialBodies())) + + @as(usize, @intFromBool(id.hasValueBody())) + + type_len + align_len + linksection_len + addrspace_len + value_len; + + try astgen.extra.ensureUnusedCapacity(gpa, need_extra); 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))), - .decltest => |tok| @enumFromInt(str_idx: { - const idx = astgen.string_bytes.items.len; - try astgen.string_bytes.append(gpa, 0); // indicates this is a test - try astgen.appendIdentStr(tok, &astgen.string_bytes); - try astgen.string_bytes.append(gpa, 0); // end of the string - break :str_idx idx; - }), - .unnamed_test => .unnamed_test, - .@"comptime" => .@"comptime", - .@"usingnamespace" => .@"usingnamespace", - }, - .src_line = src_line, - .src_column = src_column, - .flags = .{ - .value_body_len = @intCast(value_len), - .is_pub = is_pub, - .is_export = is_export, - .test_is_decltest = name == .decltest, - .has_align_linksection_addrspace = align_len != 0 or linksection_len != 0 or addrspace_len != 0, - }, + .flags_0 = flags_arr[0], + .flags_1 = flags_arr[1], }; - astgen.instructions.items(.data)[@intFromEnum(decl_inst)].declaration.payload_index = try astgen.addExtra(extra); - if (extra.flags.has_align_linksection_addrspace) { - try astgen.extra.appendSlice(gpa, &.{ + astgen.instructions.items(.data)[@intFromEnum(decl_inst)].declaration.payload_index = + astgen.addExtraAssumeCapacity(extra); + + if (id.hasName()) { + astgen.extra.appendAssumeCapacity(@intFromEnum(args.name)); + } + if (id.hasLibName()) { + astgen.extra.appendAssumeCapacity(@intFromEnum(args.lib_name)); + } + if (id.hasTypeBody()) { + astgen.extra.appendAssumeCapacity(type_len); + } + if (id.hasSpecialBodies()) { + astgen.extra.appendSliceAssumeCapacity(&.{ 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 (id.hasValueBody()) { + astgen.extra.appendAssumeCapacity(value_len); } - if (extra_gzs) |e| { - e.addrspace_gz.unstack(); - e.linksection_gz.unstack(); - e.align_gz.unstack(); - } - value_gz.unstack(); + astgen.appendBodyWithFixups(type_body); + astgen.appendBodyWithFixups(align_body); + astgen.appendBodyWithFixups(linksection_body); + astgen.appendBodyWithFixups(addrspace_body); + astgen.appendBodyWithFixups(value_body); + + args.value_gz.unstack(); + args.addrspace_gz.unstack(); + args.linksection_gz.unstack(); + args.align_gz.unstack(); + args.type_gz.unstack(); } /// Given a list of instructions, returns a list of all instructions which are a `ref` of one of the originals, diff --git a/lib/std/zig/Zir.zig b/lib/std/zig/Zir.zig index f2aceb14ae..0f9611aa30 100644 --- a/lib/std/zig/Zir.zig +++ b/lib/std/zig/Zir.zig @@ -1868,10 +1868,6 @@ pub const Inst = struct { /// Rarer instructions are here; ones that do not fit in the 8-bit `Tag` enum. /// `noreturn` instructions may not go here; they must be part of the main `Tag` enum. pub const Extended = enum(u16) { - /// Declares a global variable. - /// `operand` is payload index to `ExtendedVar`. - /// `small` is `ExtendedVar.Small`. - variable, /// A struct type definition. Contains references to ZIR instructions for /// the field types, defaults, and alignments. /// `operand` is payload index to `StructDecl`. @@ -2493,26 +2489,25 @@ pub const Inst = struct { }; /// Trailing: - /// 0. lib_name: NullTerminatedString, // null terminated string index, if has_lib_name is set /// if (has_cc_ref and !has_cc_body) { - /// 1. cc: Ref, + /// 0. cc: Ref, /// } /// if (has_cc_body) { - /// 2. cc_body_len: u32 - /// 3. cc_body: u32 // for each cc_body_len + /// 1. cc_body_len: u32 + /// 2. cc_body: u32 // for each cc_body_len /// } /// if (has_ret_ty_ref and !has_ret_ty_body) { - /// 4. ret_ty: Ref, + /// 3. ret_ty: Ref, /// } /// if (has_ret_ty_body) { - /// 5. ret_ty_body_len: u32 - /// 6. ret_ty_body: u32 // for each ret_ty_body_len + /// 4. ret_ty_body_len: u32 + /// 5. ret_ty_body: u32 // for each ret_ty_body_len /// } - /// 7. noalias_bits: u32 // if has_any_noalias + /// 6. noalias_bits: u32 // if has_any_noalias /// - each bit starting with LSB corresponds to parameter indexes - /// 8. body: Index // for each body_len - /// 9. src_locs: Func.SrcLocs // if body_len != 0 - /// 10. proto_hash: std.zig.SrcHash // if body_len != 0; hash of function prototype + /// 7. body: Index // for each body_len + /// 8. src_locs: Func.SrcLocs // if body_len != 0 + /// 9. proto_hash: std.zig.SrcHash // if body_len != 0; hash of function prototype 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. @@ -2522,38 +2517,16 @@ pub const Inst = struct { /// If both has_cc_ref and has_cc_body are false, it means auto calling convention. /// If both has_ret_ty_ref and has_ret_ty_body are false, it means void return type. - pub const Bits = packed struct { + pub const Bits = packed struct(u32) { is_var_args: bool, is_inferred_error: bool, - is_test: bool, - is_extern: bool, is_noinline: bool, has_cc_ref: bool, has_cc_body: bool, has_ret_ty_ref: bool, has_ret_ty_body: bool, - has_lib_name: bool, has_any_noalias: bool, - _: u21 = undefined, - }; - }; - - /// Trailing: - /// 0. lib_name: NullTerminatedString, // null terminated string index, if has_lib_name is set - /// 1. align: Ref, // if has_align is set - /// 2. init: Ref // if has_init is set - /// The source node is obtained from the containing `block_inline`. - pub const ExtendedVar = struct { - var_type: Ref, - - pub const Small = packed struct { - has_lib_name: bool, - has_align: bool, - has_init: bool, - is_extern: bool, - is_const: bool, - is_threadlocal: bool, - _: u10 = undefined, + _: u24 = undefined, }; }; @@ -2582,39 +2555,301 @@ pub const Inst = struct { }; /// Trailing: - /// 0. align_body_len: u32 // if `has_align_linksection_addrspace`; 0 means no `align` - /// 1. linksection_body_len: u32 // if `has_align_linksection_addrspace`; 0 means no `linksection` - /// 2. addrspace_body_len: u32 // if `has_align_linksection_addrspace`; 0 means no `addrspace` - /// 3. value_body_inst: Zir.Inst.Index - /// - for each `value_body_len` + /// 0. name: NullTerminatedString // if `flags.id.hasName()` + /// 1. lib_name: NullTerminatedString // if `flags.id.hasLibName()` + /// 2. type_body_len: u32 // if `flags.id.hasTypeBody()` + /// 3. align_body_len: u32 // if `flags.id.hasSpecialBodies()` + /// 4. linksection_body_len: u32 // if `flags.id.hasSpecialBodies()` + /// 5. addrspace_body_len: u32 // if `flags.id.hasSpecialBodies()` + /// 6. value_body_len: u32 // if `flags.id.hasValueBody()` + /// 7. type_body_inst: Zir.Inst.Index + /// - for each `type_body_len` /// - body to be exited via `break_inline` to this `declaration` instruction - /// 4. align_body_inst: Zir.Inst.Index + /// 8. align_body_inst: Zir.Inst.Index /// - for each `align_body_len` /// - body to be exited via `break_inline` to this `declaration` instruction - /// 5. linksection_body_inst: Zir.Inst.Index + /// 9. linksection_body_inst: Zir.Inst.Index /// - for each `linksection_body_len` /// - body to be exited via `break_inline` to this `declaration` instruction - /// 6. addrspace_body_inst: Zir.Inst.Index + /// 10. addrspace_body_inst: Zir.Inst.Index /// - for each `addrspace_body_len` /// - body to be exited via `break_inline` to this `declaration` instruction + /// 11. value_body_inst: Zir.Inst.Index + /// - for each `value_body_len` + /// - body to be exited via `break_inline` to this `declaration` instruction + /// - within this body, the `declaration` instruction refers to the resolved type from the type body 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, - src_line: u32, - src_column: u32, - flags: Flags, + // These fields should be concatenated and reinterpreted as a `Flags`. + flags_0: u32, + flags_1: u32, + + pub const Unwrapped = struct { + pub const Kind = enum { + unnamed_test, + @"test", + decltest, + @"comptime", + @"usingnamespace", + @"const", + @"var", + }; - pub const Flags = packed struct(u32) { - value_body_len: u28, + pub const Linkage = enum { + normal, + @"extern", + @"export", + }; + + src_node: Ast.Node.Index, + + src_line: u32, + src_column: u32, + + kind: Kind, + /// Always `.empty` for `kind` of `unnamed_test`, `.@"comptime"`, `.@"usingnamespace"`. + name: NullTerminatedString, + /// Always `false` for `kind` of `unnamed_test`, `.@"test"`, `.decltest`, `.@"comptime"`. is_pub: bool, - is_export: bool, - test_is_decltest: bool, - has_align_linksection_addrspace: bool, + /// Always `false` for `kind != .@"var"`. + is_threadlocal: bool, + /// Always `.normal` for `kind != .@"const" and kind != .@"var"`. + linkage: Linkage, + /// Always `.empty` for `linkage != .@"extern"`. + lib_name: NullTerminatedString, + + /// Always populated for `linkage == .@"extern". + type_body: ?[]const Inst.Index, + align_body: ?[]const Inst.Index, + linksection_body: ?[]const Inst.Index, + addrspace_body: ?[]const Inst.Index, + /// Always populated for `linkage != .@"extern". + value_body: ?[]const Inst.Index, + }; + + pub const Flags = packed struct(u64) { + src_line: u30, + src_column: u29, + id: Id, + + pub const Id = enum(u5) { + unnamed_test, + @"test", + decltest, + @"comptime", + + @"usingnamespace", + pub_usingnamespace, + + const_simple, + const_typed, + @"const", + pub_const_simple, + pub_const_typed, + pub_const, + + extern_const_simple, + extern_const, + pub_extern_const_simple, + pub_extern_const, + + export_const, + pub_export_const, + + var_simple, + @"var", + var_threadlocal, + pub_var_simple, + pub_var, + pub_var_threadlocal, + + extern_var, + extern_var_threadlocal, + pub_extern_var, + pub_extern_var_threadlocal, + + export_var, + export_var_threadlocal, + pub_export_var, + pub_export_var_threadlocal, + + pub fn hasName(id: Id) bool { + return switch (id) { + .unnamed_test, + .@"comptime", + .@"usingnamespace", + .pub_usingnamespace, + => false, + else => true, + }; + } + + pub fn hasLibName(id: Id) bool { + return switch (id) { + .extern_const, + .pub_extern_const, + .extern_var, + .extern_var_threadlocal, + .pub_extern_var, + .pub_extern_var_threadlocal, + => true, + else => false, + }; + } + + pub fn hasTypeBody(id: Id) bool { + return switch (id) { + .unnamed_test, + .@"test", + .decltest, + .@"comptime", + .@"usingnamespace", + .pub_usingnamespace, + => false, // these constructs are untyped + .const_simple, + .pub_const_simple, + .var_simple, + .pub_var_simple, + => false, // these reprs omit type bodies + else => true, + }; + } + + pub fn hasValueBody(id: Id) bool { + return switch (id) { + .extern_const_simple, + .extern_const, + .pub_extern_const_simple, + .pub_extern_const, + .extern_var, + .extern_var_threadlocal, + .pub_extern_var, + .pub_extern_var_threadlocal, + => false, // externs do not have values + else => true, + }; + } + + pub fn hasSpecialBodies(id: Id) bool { + return switch (id) { + .unnamed_test, + .@"test", + .decltest, + .@"comptime", + .@"usingnamespace", + .pub_usingnamespace, + => false, // these constructs are untyped + .const_simple, + .const_typed, + .pub_const_simple, + .pub_const_typed, + .extern_const_simple, + .pub_extern_const_simple, + .var_simple, + .pub_var_simple, + => false, // these reprs omit special bodies + else => true, + }; + } + + pub fn linkage(id: Id) Declaration.Unwrapped.Linkage { + return switch (id) { + .extern_const_simple, + .extern_const, + .pub_extern_const_simple, + .pub_extern_const, + .extern_var, + .extern_var_threadlocal, + .pub_extern_var, + .pub_extern_var_threadlocal, + => .@"extern", + .export_const, + .pub_export_const, + .export_var, + .export_var_threadlocal, + .pub_export_var, + .pub_export_var_threadlocal, + => .@"export", + else => .normal, + }; + } + + pub fn kind(id: Id) Declaration.Unwrapped.Kind { + return switch (id) { + .unnamed_test => .unnamed_test, + .@"test" => .@"test", + .decltest => .decltest, + .@"comptime" => .@"comptime", + .@"usingnamespace", .pub_usingnamespace => .@"usingnamespace", + .const_simple, + .const_typed, + .@"const", + .pub_const_simple, + .pub_const_typed, + .pub_const, + .extern_const_simple, + .extern_const, + .pub_extern_const_simple, + .pub_extern_const, + .export_const, + .pub_export_const, + => .@"const", + .var_simple, + .@"var", + .var_threadlocal, + .pub_var_simple, + .pub_var, + .pub_var_threadlocal, + .extern_var, + .extern_var_threadlocal, + .pub_extern_var, + .pub_extern_var_threadlocal, + .export_var, + .export_var_threadlocal, + .pub_export_var, + .pub_export_var_threadlocal, + => .@"var", + }; + } + + pub fn isPub(id: Id) bool { + return switch (id) { + .pub_usingnamespace, + .pub_const_simple, + .pub_const_typed, + .pub_const, + .pub_extern_const_simple, + .pub_extern_const, + .pub_export_const, + .pub_var_simple, + .pub_var, + .pub_var_threadlocal, + .pub_extern_var, + .pub_extern_var_threadlocal, + .pub_export_var, + .pub_export_var_threadlocal, + => true, + else => false, + }; + } + + pub fn isThreadlocal(id: Id) bool { + return switch (id) { + .var_threadlocal, + .pub_var_threadlocal, + .extern_var_threadlocal, + .pub_extern_var_threadlocal, + .export_var_threadlocal, + .pub_export_var_threadlocal, + => true, + else => false, + }; + } + }; }; pub const Name = enum(u32) { @@ -2647,17 +2882,24 @@ pub const Inst = struct { }; pub const Bodies = struct { - value_body: []const Index, + type_body: ?[]const Index, align_body: ?[]const Index, linksection_body: ?[]const Index, addrspace_body: ?[]const Index, + value_body: ?[]const Index, }; pub fn getBodies(declaration: Declaration, extra_end: u32, zir: Zir) Bodies { var extra_index: u32 = extra_end; - const value_body_len = declaration.flags.value_body_len; + const value_body_len = declaration.value_body_len; + const type_body_len: u32 = len: { + if (!declaration.flags().kind.hasTypeBody()) break :len 0; + const len = zir.extra[extra_index]; + extra_index += 1; + break :len len; + }; const align_body_len, const linksection_body_len, const addrspace_body_len = lens: { - if (!declaration.flags.has_align_linksection_addrspace) { + if (!declaration.flags.kind.hasSpecialBodies()) { break :lens .{ 0, 0, 0 }; } const lens = zir.extra[extra_index..][0..3].*; @@ -2665,21 +2907,30 @@ pub const Inst = struct { break :lens lens; }; return .{ - .value_body = b: { - defer extra_index += value_body_len; - break :b zir.bodySlice(extra_index, value_body_len); + .type_body = if (type_body_len == 0) null else b: { + const b = zir.bodySlice(extra_index, type_body_len); + extra_index += type_body_len; + break :b b; }, .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); + const b = zir.bodySlice(extra_index, align_body_len); + extra_index += align_body_len; + break :b b; }, .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); + const b = zir.bodySlice(extra_index, linksection_body_len); + extra_index += linksection_body_len; + break :b b; }, .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); + const b = zir.bodySlice(extra_index, addrspace_body_len); + extra_index += addrspace_body_len; + break :b b; + }, + .value_body = if (value_body_len == 0) null else b: { + const b = zir.bodySlice(extra_index, value_body_len); + extra_index += value_body_len; + break :b b; }, }; } @@ -3711,18 +3962,18 @@ pub const DeclContents = struct { pub fn findTrackable(zir: Zir, gpa: Allocator, contents: *DeclContents, decl_inst: Zir.Inst.Index) !void { contents.clear(); - const declaration, const extra_end = zir.getDeclaration(decl_inst); - const bodies = declaration.getBodies(extra_end, zir); + const decl = zir.getDeclaration(decl_inst); // `defer` instructions duplicate the same body arbitrarily many times, but we only want to traverse // their contents once per defer. So, we store the extra index of the body here to deduplicate. var found_defers: std.AutoHashMapUnmanaged(u32, void) = .empty; defer found_defers.deinit(gpa); - try zir.findTrackableBody(gpa, contents, &found_defers, bodies.value_body); - if (bodies.align_body) |b| try zir.findTrackableBody(gpa, contents, &found_defers, b); - if (bodies.linksection_body) |b| try zir.findTrackableBody(gpa, contents, &found_defers, b); - if (bodies.addrspace_body) |b| try zir.findTrackableBody(gpa, contents, &found_defers, b); + if (decl.type_body) |b| try zir.findTrackableBody(gpa, contents, &found_defers, b); + if (decl.align_body) |b| try zir.findTrackableBody(gpa, contents, &found_defers, b); + if (decl.linksection_body) |b| try zir.findTrackableBody(gpa, contents, &found_defers, b); + if (decl.addrspace_body) |b| try zir.findTrackableBody(gpa, contents, &found_defers, b); + if (decl.value_body) |b| try zir.findTrackableBody(gpa, contents, &found_defers, b); } /// Like `findTrackable`, but only considers the `main_struct_inst` instruction. This may return more than @@ -3991,7 +4242,6 @@ fn findTrackableInner( .value_placeholder => unreachable, // Once again, we start with the boring tags. - .variable, .this, .ret_addr, .builtin_src, @@ -4237,7 +4487,6 @@ fn findTrackableInner( const inst_data = datas[@intFromEnum(inst)].pl_node; const extra = zir.extraData(Inst.FuncFancy, inst_data.payload_index); var extra_index: usize = extra.end; - extra_index += @intFromBool(extra.data.bits.has_lib_name); if (extra.data.bits.has_cc_body) { const body_len = zir.extra[extra_index]; @@ -4470,8 +4719,7 @@ pub fn getParamBody(zir: Zir, fn_inst: Inst.Index) []const Zir.Inst.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; + return zir.getDeclaration(param_block_index).value_body.?; }, else => unreachable, } @@ -4526,7 +4774,6 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { var ret_ty_ref: Inst.Ref = .void_type; var ret_ty_body: []const Inst.Index = &.{}; - extra_index += @intFromBool(extra.data.bits.has_lib_name); if (extra.data.bits.has_cc_body) { extra_index += zir.extra[extra_index] + 1; } else if (extra.data.bits.has_cc_ref) { @@ -4555,17 +4802,7 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { }, else => unreachable, }; - 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, - }; + const param_body = zir.getParamBody(fn_inst); var total_params_len: u32 = 0; for (param_body) |inst| { switch (tags[@intFromEnum(inst)]) { @@ -4585,13 +4822,74 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { }; } -pub fn getDeclaration(zir: Zir, inst: Zir.Inst.Index) struct { Inst.Declaration, u32 } { +pub fn getDeclaration(zir: Zir, inst: Zir.Inst.Index) Inst.Declaration.Unwrapped { assert(zir.instructions.items(.tag)[@intFromEnum(inst)] == .declaration); const pl_node = zir.instructions.items(.data)[@intFromEnum(inst)].declaration; const extra = zir.extraData(Inst.Declaration, pl_node.payload_index); + + const flags_vals: [2]u32 = .{ extra.data.flags_0, extra.data.flags_1 }; + const flags: Inst.Declaration.Flags = @bitCast(flags_vals); + + var extra_index = extra.end; + + const name: NullTerminatedString = if (flags.id.hasName()) name: { + const name = zir.extra[extra_index]; + extra_index += 1; + break :name @enumFromInt(name); + } else .empty; + + const lib_name: NullTerminatedString = if (flags.id.hasLibName()) lib_name: { + const lib_name = zir.extra[extra_index]; + extra_index += 1; + break :lib_name @enumFromInt(lib_name); + } else .empty; + + const type_body_len: u32 = if (flags.id.hasTypeBody()) len: { + const len = zir.extra[extra_index]; + extra_index += 1; + break :len len; + } else 0; + const align_body_len: u32, const linksection_body_len: u32, const addrspace_body_len: u32 = lens: { + if (!flags.id.hasSpecialBodies()) break :lens .{ 0, 0, 0 }; + const lens = zir.extra[extra_index..][0..3].*; + extra_index += 3; + break :lens lens; + }; + const value_body_len: u32 = if (flags.id.hasValueBody()) len: { + const len = zir.extra[extra_index]; + extra_index += 1; + break :len len; + } else 0; + + const type_body = zir.bodySlice(extra_index, type_body_len); + extra_index += type_body_len; + const align_body = zir.bodySlice(extra_index, align_body_len); + extra_index += align_body_len; + const linksection_body = zir.bodySlice(extra_index, linksection_body_len); + extra_index += linksection_body_len; + const addrspace_body = zir.bodySlice(extra_index, addrspace_body_len); + extra_index += addrspace_body_len; + const value_body = zir.bodySlice(extra_index, value_body_len); + extra_index += value_body_len; + return .{ - extra.data, - @intCast(extra.end), + .src_node = pl_node.src_node, + + .src_line = flags.src_line, + .src_column = flags.src_column, + + .kind = flags.id.kind(), + .name = name, + .is_pub = flags.id.isPub(), + .is_threadlocal = flags.id.isThreadlocal(), + .linkage = flags.id.linkage(), + .lib_name = lib_name, + + .type_body = if (type_body_len == 0) null else type_body, + .align_body = if (align_body_len == 0) null else align_body, + .linksection_body = if (linksection_body_len == 0) null else linksection_body, + .addrspace_body = if (addrspace_body_len == 0) null else addrspace_body, + .value_body = if (value_body_len == 0) null else value_body, }; } @@ -4636,7 +4934,6 @@ pub fn getAssociatedSrcHash(zir: Zir, inst: Zir.Inst.Index) ?std.zig.SrcHash { } const bits = extra.data.bits; var extra_index = extra.end; - extra_index += @intFromBool(bits.has_lib_name); if (bits.has_cc_body) { const body_len = zir.extra[extra_index]; extra_index += 1 + body_len; diff --git a/src/Compilation.zig b/src/Compilation.zig index 8b158390b6..28c5efab6c 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -348,12 +348,15 @@ const Job = union(enum) { /// Corresponds to the task in `link.Task`. /// Only needed for backends that haven't yet been updated to not race against Sema. codegen_type: InternPool.Index, - /// The `Cau` must be semantically analyzed (and possibly export itself). + /// The `AnalUnit`, which is *not* a `func`, must be semantically analyzed. + /// This may be its first time being analyzed, or it may be outdated. + /// If the unit is a function, a `codegen_func` job will then be queued. + analyze_comptime_unit: InternPool.AnalUnit, + /// This function must be semantically analyzed. /// This may be its first time being analyzed, or it may be outdated. - analyze_cau: InternPool.Cau.Index, - /// Analyze the body of a runtime function. /// After analysis, a `codegen_func` job will be queued. /// These must be separate jobs to ensure any needed type resolution occurs *before* codegen. + /// This job is separate from `analyze_comptime_unit` because it has a different priority. analyze_func: InternPool.Index, /// The main source file for the module needs to be analyzed. analyze_mod: *Package.Module, @@ -2903,6 +2906,7 @@ const Header = extern struct { file_deps_len: u32, src_hash_deps_len: u32, nav_val_deps_len: u32, + nav_ty_deps_len: u32, namespace_deps_len: u32, namespace_name_deps_len: u32, first_dependency_len: u32, @@ -2946,6 +2950,7 @@ pub fn saveState(comp: *Compilation) !void { .file_deps_len = @intCast(ip.file_deps.count()), .src_hash_deps_len = @intCast(ip.src_hash_deps.count()), .nav_val_deps_len = @intCast(ip.nav_val_deps.count()), + .nav_ty_deps_len = @intCast(ip.nav_ty_deps.count()), .namespace_deps_len = @intCast(ip.namespace_deps.count()), .namespace_name_deps_len = @intCast(ip.namespace_name_deps.count()), .first_dependency_len = @intCast(ip.first_dependency.count()), @@ -2976,6 +2981,8 @@ pub fn saveState(comp: *Compilation) !void { addBuf(&bufs, mem.sliceAsBytes(ip.src_hash_deps.values())); addBuf(&bufs, mem.sliceAsBytes(ip.nav_val_deps.keys())); addBuf(&bufs, mem.sliceAsBytes(ip.nav_val_deps.values())); + addBuf(&bufs, mem.sliceAsBytes(ip.nav_ty_deps.keys())); + addBuf(&bufs, mem.sliceAsBytes(ip.nav_ty_deps.values())); addBuf(&bufs, mem.sliceAsBytes(ip.namespace_deps.keys())); addBuf(&bufs, mem.sliceAsBytes(ip.namespace_deps.values())); addBuf(&bufs, mem.sliceAsBytes(ip.namespace_name_deps.keys())); @@ -3141,8 +3148,10 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle { } const file_index = switch (anal_unit.unwrap()) { - .cau => |cau| zcu.namespacePtr(ip.getCau(cau).namespace).file_scope, - .func => |ip_index| (zcu.funcInfo(ip_index).zir_body_inst.resolveFull(ip) orelse continue).file, + .@"comptime" => |cu| ip.getComptimeUnit(cu).zir_index.resolveFile(ip), + .nav_val, .nav_ty => |nav| ip.getNav(nav).analysis.?.zir_index.resolveFile(ip), + .type => |ty| Type.fromInterned(ty).typeDeclInst(zcu).?.resolveFile(ip), + .func => |ip_index| zcu.funcInfo(ip_index).zir_body_inst.resolveFile(ip), }; // Skip errors for AnalUnits within files that had a parse failure. @@ -3374,11 +3383,9 @@ pub fn addModuleErrorMsg( const rt_file_path = try src.file_scope.fullPath(gpa); defer gpa.free(rt_file_path); const name = switch (ref.referencer.unwrap()) { - .cau => |cau| switch (ip.getCau(cau).owner.unwrap()) { - .nav => |nav| ip.getNav(nav).name.toSlice(ip), - .type => |ty| Type.fromInterned(ty).containerTypeName(ip).toSlice(ip), - .none => "comptime", - }, + .@"comptime" => "comptime", + .nav_val, .nav_ty => |nav| ip.getNav(nav).name.toSlice(ip), + .type => |ty| Type.fromInterned(ty).containerTypeName(ip).toSlice(ip), .func => |f| ip.getNav(zcu.funcInfo(f).owner_nav).name.toSlice(ip), }; try ref_traces.append(gpa, .{ @@ -3641,10 +3648,14 @@ fn performAllTheWorkInner( // If there's no work queued, check if there's anything outdated // which we need to work on, and queue it if so. if (try zcu.findOutdatedToAnalyze()) |outdated| { - switch (outdated.unwrap()) { - .cau => |cau| try comp.queueJob(.{ .analyze_cau = cau }), - .func => |func| try comp.queueJob(.{ .analyze_func = func }), - } + try comp.queueJob(switch (outdated.unwrap()) { + .func => |f| .{ .analyze_func = f }, + .@"comptime", + .nav_ty, + .nav_val, + .type, + => .{ .analyze_comptime_unit = outdated }, + }); continue; } } @@ -3667,13 +3678,13 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre .codegen_nav => |nav_index| { const zcu = comp.zcu.?; const nav = zcu.intern_pool.getNav(nav_index); - if (nav.analysis_owner.unwrap()) |cau| { - const unit = InternPool.AnalUnit.wrap(.{ .cau = cau }); + if (nav.analysis != null) { + const unit: InternPool.AnalUnit = .wrap(.{ .nav_val = nav_index }); if (zcu.failed_analysis.contains(unit) or zcu.transitive_failed_analysis.contains(unit)) { return; } } - assert(nav.status == .resolved); + assert(nav.status == .fully_resolved); comp.dispatchCodegenTask(tid, .{ .codegen_nav = nav_index }); }, .codegen_func => |func| { @@ -3688,36 +3699,48 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid)); defer pt.deactivate(); - pt.ensureFuncBodyAnalyzed(func) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, + + pt.ensureFuncBodyUpToDate(func) catch |err| switch (err) { + error.OutOfMemory => |e| return e, error.AnalysisFail => return, }; }, - .analyze_cau => |cau_index| { + .analyze_comptime_unit => |unit| { + const named_frame = tracy.namedFrame("analyze_comptime_unit"); + defer named_frame.end(); + const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid)); defer pt.deactivate(); - pt.ensureCauAnalyzed(cau_index) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, + + const maybe_err: Zcu.SemaError!void = switch (unit.unwrap()) { + .@"comptime" => |cu| pt.ensureComptimeUnitUpToDate(cu), + .nav_ty => |nav| pt.ensureNavTypeUpToDate(nav), + .nav_val => |nav| pt.ensureNavValUpToDate(nav), + .type => |ty| if (pt.ensureTypeUpToDate(ty)) |_| {} else |err| err, + .func => unreachable, + }; + maybe_err catch |err| switch (err) { + error.OutOfMemory => |e| return e, error.AnalysisFail => return, }; + queue_test_analysis: { if (!comp.config.is_test) break :queue_test_analysis; + const nav = switch (unit.unwrap()) { + .nav_val => |nav| nav, + else => break :queue_test_analysis, + }; // Check if this is a test function. const ip = &pt.zcu.intern_pool; - const cau = ip.getCau(cau_index); - const nav_index = switch (cau.owner.unwrap()) { - .none, .type => break :queue_test_analysis, - .nav => |nav| nav, - }; - if (!pt.zcu.test_functions.contains(nav_index)) { + if (!pt.zcu.test_functions.contains(nav)) { break :queue_test_analysis; } // Tests are always emitted in test binaries. The decl_refs are created by // Zcu.populateTestFunctions, but this will not queue body analysis, so do // that now. - try pt.zcu.ensureFuncBodyAnalysisQueued(ip.getNav(nav_index).status.resolved.val); + try pt.zcu.ensureFuncBodyAnalysisQueued(ip.getNav(nav).status.fully_resolved.val); } }, .resolve_type_fully => |ty| { diff --git a/src/InternPool.zig b/src/InternPool.zig index f923332f88..64cf95c7b2 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -34,6 +34,9 @@ src_hash_deps: std.AutoArrayHashMapUnmanaged(TrackedInst.Index, DepEntry.Index), /// Dependencies on the value of a Nav. /// Value is index into `dep_entries` of the first dependency on this Nav value. nav_val_deps: std.AutoArrayHashMapUnmanaged(Nav.Index, DepEntry.Index), +/// Dependencies on the type of a Nav. +/// Value is index into `dep_entries` of the first dependency on this Nav value. +nav_ty_deps: std.AutoArrayHashMapUnmanaged(Nav.Index, DepEntry.Index), /// Dependencies on an interned value, either: /// * a runtime function (invalidated when its IES changes) /// * a container type requiring resolution (invalidated when the type must be recreated at a new index) @@ -80,6 +83,7 @@ pub const empty: InternPool = .{ .file_deps = .empty, .src_hash_deps = .empty, .nav_val_deps = .empty, + .nav_ty_deps = .empty, .interned_deps = .empty, .namespace_deps = .empty, .namespace_name_deps = .empty, @@ -363,33 +367,56 @@ pub fn rehashTrackedInsts( } /// Analysis Unit. Represents a single entity which undergoes semantic analysis. -/// This is either a `Cau` or a runtime function. -/// The LSB is used as a tag bit. /// This is the "source" of an incremental dependency edge. -pub const AnalUnit = packed struct(u32) { - kind: enum(u1) { cau, func }, - index: u31, - pub const Unwrapped = union(enum) { - cau: Cau.Index, +pub const AnalUnit = packed struct(u64) { + kind: Kind, + id: u32, + + pub const Kind = enum(u32) { + @"comptime", + nav_val, + nav_ty, + type, + func, + }; + + pub const Unwrapped = union(Kind) { + /// This `AnalUnit` analyzes the body of the given `comptime` declaration. + @"comptime": ComptimeUnit.Id, + /// This `AnalUnit` resolves the value of the given `Nav`. + nav_val: Nav.Index, + /// This `AnalUnit` resolves the type of the given `Nav`. + nav_ty: Nav.Index, + /// This `AnalUnit` resolves the given `struct`/`union`/`enum` type. + /// Generated tag enums are never used here (they do not undergo type resolution). + type: InternPool.Index, + /// This `AnalUnit` analyzes the body of the given runtime function. func: InternPool.Index, }; - pub fn unwrap(as: AnalUnit) Unwrapped { - return switch (as.kind) { - .cau => .{ .cau = @enumFromInt(as.index) }, - .func => .{ .func = @enumFromInt(as.index) }, + + pub fn unwrap(au: AnalUnit) Unwrapped { + return switch (au.kind) { + inline else => |tag| @unionInit( + Unwrapped, + @tagName(tag), + @enumFromInt(au.id), + ), }; } pub fn wrap(raw: Unwrapped) AnalUnit { return switch (raw) { - .cau => |cau| .{ .kind = .cau, .index = @intCast(@intFromEnum(cau)) }, - .func => |func| .{ .kind = .func, .index = @intCast(@intFromEnum(func)) }, + inline else => |id, tag| .{ + .kind = tag, + .id = @intFromEnum(id), + }, }; } + pub fn toOptional(as: AnalUnit) Optional { - return @enumFromInt(@as(u32, @bitCast(as))); + return @enumFromInt(@as(u64, @bitCast(as))); } - pub const Optional = enum(u32) { - none = std.math.maxInt(u32), + pub const Optional = enum(u64) { + none = std.math.maxInt(u64), _, pub fn unwrap(opt: Optional) ?AnalUnit { return switch (opt) { @@ -400,97 +427,30 @@ pub const AnalUnit = packed struct(u32) { }; }; -/// Comptime Analysis Unit. This is the "subject" of semantic analysis where the root context is -/// comptime; every `Sema` is owned by either a `Cau` or a runtime function (see `AnalUnit`). -/// The state stored here is immutable. -/// -/// * Every ZIR `declaration` has a `Cau` (post-instantiation) to analyze the declaration body. -/// * Every `struct`, `union`, and `enum` has a `Cau` for type resolution. -/// -/// The analysis status of a `Cau` is known only from state in `Zcu`. -/// An entry in `Zcu.failed_analysis` indicates an analysis failure with associated error message. -/// An entry in `Zcu.transitive_failed_analysis` indicates a transitive analysis failure. -/// -/// 12 bytes. -pub const Cau = struct { - /// The `declaration`, `struct_decl`, `enum_decl`, or `union_decl` instruction which this `Cau` analyzes. +pub const ComptimeUnit = extern struct { zir_index: TrackedInst.Index, - /// The namespace which this `Cau` should be analyzed within. namespace: NamespaceIndex, - /// This field essentially tells us what to do with the information resulting from - /// semantic analysis. See `Owner.Unwrapped` for details. - owner: Owner, - - /// See `Owner.Unwrapped` for details. In terms of representation, the `InternPool.Index` - /// or `Nav.Index` is cast to a `u31` and stored in `index`. As a special case, if - /// `@as(u32, @bitCast(owner)) == 0xFFFF_FFFF`, then the value is treated as `.none`. - pub const Owner = packed struct(u32) { - kind: enum(u1) { type, nav }, - index: u31, - - pub const Unwrapped = union(enum) { - /// This `Cau` exists in isolation. It is a global `comptime` declaration, or (TODO ANYTHING ELSE?). - /// After semantic analysis completes, the result is discarded. - none, - /// This `Cau` is owned by the given type for type resolution. - /// This is a `struct`, `union`, or `enum` type. - type: InternPool.Index, - /// This `Cau` is owned by the given `Nav` to resolve its value. - /// When analyzing the `Cau`, the resulting value is stored as the value of this `Nav`. - nav: Nav.Index, - }; - pub fn unwrap(owner: Owner) Unwrapped { - if (@as(u32, @bitCast(owner)) == std.math.maxInt(u32)) { - return .none; - } - return switch (owner.kind) { - .type => .{ .type = @enumFromInt(owner.index) }, - .nav => .{ .nav = @enumFromInt(owner.index) }, - }; - } - - fn wrap(raw: Unwrapped) Owner { - return switch (raw) { - .none => @bitCast(@as(u32, std.math.maxInt(u32))), - .type => |ty| .{ .kind = .type, .index = @intCast(@intFromEnum(ty)) }, - .nav => |nav| .{ .kind = .nav, .index = @intCast(@intFromEnum(nav)) }, - }; - } - }; + comptime { + assert(std.meta.hasUniqueRepresentation(ComptimeUnit)); + } - pub const Index = enum(u32) { + pub const Id = enum(u32) { _, - pub const Optional = enum(u32) { - none = std.math.maxInt(u32), - _, - pub fn unwrap(opt: Optional) ?Cau.Index { - return switch (opt) { - .none => null, - _ => @enumFromInt(@intFromEnum(opt)), - }; - } - - const debug_state = InternPool.debug_state; - }; - pub fn toOptional(i: Cau.Index) Optional { - return @enumFromInt(@intFromEnum(i)); - } const Unwrapped = struct { tid: Zcu.PerThread.Id, index: u32, - - fn wrap(unwrapped: Unwrapped, ip: *const InternPool) Cau.Index { + fn wrap(unwrapped: Unwrapped, ip: *const InternPool) ComptimeUnit.Id { assert(@intFromEnum(unwrapped.tid) <= ip.getTidMask()); - assert(unwrapped.index <= ip.getIndexMask(u31)); - return @enumFromInt(@as(u32, @intFromEnum(unwrapped.tid)) << ip.tid_shift_31 | + assert(unwrapped.index <= ip.getIndexMask(u32)); + return @enumFromInt(@as(u32, @intFromEnum(unwrapped.tid)) << ip.tid_shift_32 | unwrapped.index); } }; - fn unwrap(cau_index: Cau.Index, ip: *const InternPool) Unwrapped { + fn unwrap(id: Id, ip: *const InternPool) Unwrapped { return .{ - .tid = @enumFromInt(@intFromEnum(cau_index) >> ip.tid_shift_31 & ip.getTidMask()), - .index = @intFromEnum(cau_index) & ip.getIndexMask(u31), + .tid = @enumFromInt(@intFromEnum(id) >> ip.tid_shift_32 & ip.getTidMask()), + .index = @intFromEnum(id) & ip.getIndexMask(u31), }; } @@ -507,6 +467,11 @@ pub const Cau = struct { /// * Generic instances have a `Nav` corresponding to the instantiated function. /// * `@extern` calls create a `Nav` whose value is a `.@"extern"`. /// +/// This data structure is optimized for the `analysis_info != null` case, because this is much more +/// common in practice; the other case is used only for externs and for generic instances. At the time +/// of writing, in the compiler itself, around 74% of all `Nav`s have `analysis_info != null`. +/// (Specifically, 104225 / 140923) +/// /// `Nav.Repr` is the in-memory representation. pub const Nav = struct { /// The unqualified name of this `Nav`. Namespace lookups use this name, and error messages may use it. @@ -514,16 +479,31 @@ pub const Nav = struct { name: NullTerminatedString, /// The fully-qualified name of this `Nav`. fqn: NullTerminatedString, - /// If the value of this `Nav` is resolved by semantic analysis, it is within this `Cau`. - /// If this is `.none`, then `status == .resolved` always. - analysis_owner: Cau.Index.Optional, + /// This field is populated iff this `Nav` is resolved by semantic analysis. + /// If this is `null`, then `status == .resolved` always. + analysis: ?struct { + namespace: NamespaceIndex, + zir_index: TrackedInst.Index, + }, /// TODO: this is a hack! If #20663 isn't accepted, let's figure out something a bit better. is_usingnamespace: bool, status: union(enum) { - /// This `Nav` is pending semantic analysis through `analysis_owner`. + /// This `Nav` is pending semantic analysis. unresolved, + /// The type of this `Nav` is resolved; the value is queued for resolution. + type_resolved: struct { + type: InternPool.Index, + alignment: Alignment, + @"linksection": OptionalNullTerminatedString, + @"addrspace": std.builtin.AddressSpace, + is_const: bool, + is_threadlocal: bool, + /// This field is whether this `Nav` is a literal `extern` definition. + /// It does *not* tell you whether this might alias an extern fn (see #21027). + is_extern_decl: bool, + }, /// The value of this `Nav` is resolved. - resolved: struct { + fully_resolved: struct { val: InternPool.Index, alignment: Alignment, @"linksection": OptionalNullTerminatedString, @@ -531,30 +511,96 @@ pub const Nav = struct { }, }, - /// Asserts that `status == .resolved`. + /// Asserts that `status != .unresolved`. pub fn typeOf(nav: Nav, ip: *const InternPool) InternPool.Index { - return ip.typeOf(nav.status.resolved.val); + return switch (nav.status) { + .unresolved => unreachable, + .type_resolved => |r| r.type, + .fully_resolved => |r| ip.typeOf(r.val), + }; + } + + /// Always returns `null` for `status == .type_resolved`. This function is inteded + /// to be used by code generation, since semantic analysis will ensure that any `Nav` + /// which is potentially `extern` is fully resolved. + /// Asserts that `status != .unresolved`. + pub fn getExtern(nav: Nav, ip: *const InternPool) ?Key.Extern { + return switch (nav.status) { + .unresolved => unreachable, + .type_resolved => null, + .fully_resolved => |r| switch (ip.indexToKey(r.val)) { + .@"extern" => |e| e, + else => null, + }, + }; } - /// Asserts that `status == .resolved`. - pub fn isExtern(nav: Nav, ip: *const InternPool) bool { - return ip.indexToKey(nav.status.resolved.val) == .@"extern"; + /// Asserts that `status != .unresolved`. + pub fn getAddrspace(nav: Nav) std.builtin.AddressSpace { + return switch (nav.status) { + .unresolved => unreachable, + .type_resolved => |r| r.@"addrspace", + .fully_resolved => |r| r.@"addrspace", + }; + } + + /// Asserts that `status != .unresolved`. + pub fn getAlignment(nav: Nav) Alignment { + return switch (nav.status) { + .unresolved => unreachable, + .type_resolved => |r| r.alignment, + .fully_resolved => |r| r.alignment, + }; + } + + /// Asserts that `status != .unresolved`. + pub fn isThreadlocal(nav: Nav, ip: *const InternPool) bool { + return switch (nav.status) { + .unresolved => unreachable, + .type_resolved => |r| r.is_threadlocal, + .fully_resolved => |r| switch (ip.indexToKey(r.val)) { + .@"extern" => |e| e.is_threadlocal, + .variable => |v| v.is_threadlocal, + else => false, + }, + }; + } + + /// If this returns `true`, then a pointer to this `Nav` might actually be encoded as a pointer + /// to some other `Nav` due to an extern definition or extern alias (see #21027). + /// This query is valid on `Nav`s for whom only the type is resolved. + /// Asserts that `status != .unresolved`. + pub fn isExternOrFn(nav: Nav, ip: *const InternPool) bool { + return switch (nav.status) { + .unresolved => unreachable, + .type_resolved => |r| { + if (r.is_extern_decl) return true; + const tag = ip.zigTypeTagOrPoison(r.type) catch unreachable; + if (tag == .@"fn") return true; + return false; + }, + .fully_resolved => |r| { + if (ip.indexToKey(r.val) == .@"extern") return true; + const tag = ip.zigTypeTagOrPoison(ip.typeOf(r.val)) catch unreachable; + if (tag == .@"fn") return true; + return false; + }, + }; } /// Get the ZIR instruction corresponding to this `Nav`, used to resolve source locations. /// This is a `declaration`. pub fn srcInst(nav: Nav, ip: *const InternPool) TrackedInst.Index { - if (nav.analysis_owner.unwrap()) |cau| { - return ip.getCau(cau).zir_index; + if (nav.analysis) |a| { + return a.zir_index; } - // A `Nav` with no corresponding `Cau` always has a resolved value. - return switch (ip.indexToKey(nav.status.resolved.val)) { + // A `Nav` which does not undergo analysis always has a resolved value. + return switch (ip.indexToKey(nav.status.fully_resolved.val)) { .func => |func| { - // Since there was no `analysis_owner`, this must be an instantiation. - // Go up to the generic owner and consult *its* `analysis_owner`. + // Since `analysis` was not populated, this must be an instantiation. + // Go up to the generic owner and consult *its* `analysis` field. const go_nav = ip.getNav(ip.indexToKey(func.generic_owner).func.owner_nav); - const go_cau = ip.getCau(go_nav.analysis_owner.unwrap().?); - return go_cau.zir_index; + return go_nav.analysis.?.zir_index; }, .@"extern" => |@"extern"| @"extern".zir_index, // extern / @extern else => unreachable, @@ -600,24 +646,29 @@ pub const Nav = struct { }; /// The compact in-memory representation of a `Nav`. - /// 18 bytes. + /// 26 bytes. const Repr = struct { name: NullTerminatedString, fqn: NullTerminatedString, - analysis_owner: Cau.Index.Optional, - /// Populated only if `bits.status == .resolved`. - val: InternPool.Index, - /// Populated only if `bits.status == .resolved`. + // The following 1 fields are either both populated, or both `.none`. + analysis_namespace: OptionalNamespaceIndex, + analysis_zir_index: TrackedInst.Index.Optional, + /// Populated only if `bits.status != .unresolved`. + type_or_val: InternPool.Index, + /// Populated only if `bits.status != .unresolved`. @"linksection": OptionalNullTerminatedString, bits: Bits, const Bits = packed struct(u16) { - status: enum(u1) { unresolved, resolved }, - /// Populated only if `bits.status == .resolved`. + status: enum(u2) { unresolved, type_resolved, fully_resolved, type_resolved_extern_decl }, + /// Populated only if `bits.status != .unresolved`. alignment: Alignment, - /// Populated only if `bits.status == .resolved`. + /// Populated only if `bits.status != .unresolved`. @"addrspace": std.builtin.AddressSpace, - _: u3 = 0, + /// Populated only if `bits.status == .type_resolved`. + is_const: bool, + /// Populated only if `bits.status == .type_resolved`. + is_threadlocal: bool, is_usingnamespace: bool, }; @@ -625,12 +676,27 @@ pub const Nav = struct { return .{ .name = repr.name, .fqn = repr.fqn, - .analysis_owner = repr.analysis_owner, + .analysis = if (repr.analysis_namespace.unwrap()) |namespace| .{ + .namespace = namespace, + .zir_index = repr.analysis_zir_index.unwrap().?, + } else a: { + assert(repr.analysis_zir_index == .none); + break :a null; + }, .is_usingnamespace = repr.bits.is_usingnamespace, .status = switch (repr.bits.status) { .unresolved => .unresolved, - .resolved => .{ .resolved = .{ - .val = repr.val, + .type_resolved, .type_resolved_extern_decl => .{ .type_resolved = .{ + .type = repr.type_or_val, + .alignment = repr.bits.alignment, + .@"linksection" = repr.@"linksection", + .@"addrspace" = repr.bits.@"addrspace", + .is_const = repr.bits.is_const, + .is_threadlocal = repr.bits.is_threadlocal, + .is_extern_decl = repr.bits.status == .type_resolved_extern_decl, + } }, + .fully_resolved => .{ .fully_resolved = .{ + .val = repr.type_or_val, .alignment = repr.bits.alignment, .@"linksection" = repr.@"linksection", .@"addrspace" = repr.bits.@"addrspace", @@ -646,14 +712,17 @@ pub const Nav = struct { return .{ .name = nav.name, .fqn = nav.fqn, - .analysis_owner = nav.analysis_owner, - .val = switch (nav.status) { + .analysis_namespace = if (nav.analysis) |a| a.namespace.toOptional() else .none, + .analysis_zir_index = if (nav.analysis) |a| a.zir_index.toOptional() else .none, + .type_or_val = switch (nav.status) { .unresolved => .none, - .resolved => |r| r.val, + .type_resolved => |r| r.type, + .fully_resolved => |r| r.val, }, .@"linksection" = switch (nav.status) { .unresolved => .none, - .resolved => |r| r.@"linksection", + .type_resolved => |r| r.@"linksection", + .fully_resolved => |r| r.@"linksection", }, .bits = switch (nav.status) { .unresolved => .{ @@ -661,12 +730,24 @@ pub const Nav = struct { .alignment = .none, .@"addrspace" = .generic, .is_usingnamespace = nav.is_usingnamespace, + .is_const = false, + .is_threadlocal = false, + }, + .type_resolved => |r| .{ + .status = if (r.is_extern_decl) .type_resolved_extern_decl else .type_resolved, + .alignment = r.alignment, + .@"addrspace" = r.@"addrspace", + .is_usingnamespace = nav.is_usingnamespace, + .is_const = r.is_const, + .is_threadlocal = r.is_threadlocal, }, - .resolved => |r| .{ - .status = .resolved, + .fully_resolved => |r| .{ + .status = .fully_resolved, .alignment = r.alignment, .@"addrspace" = r.@"addrspace", .is_usingnamespace = nav.is_usingnamespace, + .is_const = false, + .is_threadlocal = false, }, }, }; @@ -677,6 +758,7 @@ pub const Dependee = union(enum) { file: FileIndex, src_hash: TrackedInst.Index, nav_val: Nav.Index, + nav_ty: Nav.Index, interned: Index, namespace: TrackedInst.Index, namespace_name: NamespaceNameKey, @@ -726,6 +808,7 @@ pub fn dependencyIterator(ip: *const InternPool, dependee: Dependee) DependencyI .file => |x| ip.file_deps.get(x), .src_hash => |x| ip.src_hash_deps.get(x), .nav_val => |x| ip.nav_val_deps.get(x), + .nav_ty => |x| ip.nav_ty_deps.get(x), .interned => |x| ip.interned_deps.get(x), .namespace => |x| ip.namespace_deps.get(x), .namespace_name => |x| ip.namespace_name_deps.get(x), @@ -763,6 +846,7 @@ pub fn addDependency(ip: *InternPool, gpa: Allocator, depender: AnalUnit, depend .file => ip.file_deps, .src_hash => ip.src_hash_deps, .nav_val => ip.nav_val_deps, + .nav_ty => ip.nav_ty_deps, .interned => ip.interned_deps, .namespace => ip.namespace_deps, .namespace_name => ip.namespace_name_deps, @@ -862,8 +946,8 @@ const Local = struct { tracked_insts: ListMutate, files: ListMutate, maps: ListMutate, - caus: ListMutate, navs: ListMutate, + comptime_units: ListMutate, namespaces: BucketListMutate, } align(std.atomic.cache_line), @@ -876,8 +960,8 @@ const Local = struct { tracked_insts: TrackedInsts, files: List(File), maps: Maps, - caus: Caus, navs: Navs, + comptime_units: ComptimeUnits, namespaces: Namespaces, @@ -899,8 +983,8 @@ const Local = struct { const Strings = List(struct { u8 }); const TrackedInsts = List(struct { TrackedInst.MaybeLost }); const Maps = List(struct { FieldMap }); - const Caus = List(struct { Cau }); const Navs = List(Nav.Repr); + const ComptimeUnits = List(struct { ComptimeUnit }); const namespaces_bucket_width = 8; const namespaces_bucket_mask = (1 << namespaces_bucket_width) - 1; @@ -1275,21 +1359,21 @@ const Local = struct { }; } - pub fn getMutableCaus(local: *Local, gpa: Allocator) Caus.Mutable { + pub fn getMutableNavs(local: *Local, gpa: Allocator) Navs.Mutable { return .{ .gpa = gpa, .arena = &local.mutate.arena, - .mutate = &local.mutate.caus, - .list = &local.shared.caus, + .mutate = &local.mutate.navs, + .list = &local.shared.navs, }; } - pub fn getMutableNavs(local: *Local, gpa: Allocator) Navs.Mutable { + pub fn getMutableComptimeUnits(local: *Local, gpa: Allocator) ComptimeUnits.Mutable { return .{ .gpa = gpa, .arena = &local.mutate.arena, - .mutate = &local.mutate.navs, - .list = &local.shared.navs, + .mutate = &local.mutate.comptime_units, + .list = &local.shared.comptime_units, }; } @@ -2018,7 +2102,6 @@ pub const Key = union(enum) { ty: Index, init: Index, owner_nav: Nav.Index, - lib_name: OptionalNullTerminatedString, is_threadlocal: bool, is_weak_linkage: bool, }; @@ -2111,36 +2194,36 @@ pub const Key = union(enum) { return @atomicLoad(FuncAnalysis, func.analysisPtr(ip), .unordered); } - pub fn setAnalysisState(func: Func, ip: *InternPool, state: FuncAnalysis.State) void { + pub fn setCallsOrAwaitsErrorableFn(func: Func, ip: *InternPool, value: bool) void { const extra_mutex = &ip.getLocal(func.tid).mutate.extra.mutex; extra_mutex.lock(); defer extra_mutex.unlock(); const analysis_ptr = func.analysisPtr(ip); var analysis = analysis_ptr.*; - analysis.state = state; + analysis.calls_or_awaits_errorable_fn = value; @atomicStore(FuncAnalysis, analysis_ptr, analysis, .release); } - pub fn setCallsOrAwaitsErrorableFn(func: Func, ip: *InternPool, value: bool) void { + pub fn setBranchHint(func: Func, ip: *InternPool, hint: std.builtin.BranchHint) void { const extra_mutex = &ip.getLocal(func.tid).mutate.extra.mutex; extra_mutex.lock(); defer extra_mutex.unlock(); const analysis_ptr = func.analysisPtr(ip); var analysis = analysis_ptr.*; - analysis.calls_or_awaits_errorable_fn = value; + analysis.branch_hint = hint; @atomicStore(FuncAnalysis, analysis_ptr, analysis, .release); } - pub fn setBranchHint(func: Func, ip: *InternPool, hint: std.builtin.BranchHint) void { + pub fn setAnalyzed(func: Func, ip: *InternPool) void { const extra_mutex = &ip.getLocal(func.tid).mutate.extra.mutex; extra_mutex.lock(); defer extra_mutex.unlock(); const analysis_ptr = func.analysisPtr(ip); var analysis = analysis_ptr.*; - analysis.branch_hint = hint; + analysis.is_analyzed = true; @atomicStore(FuncAnalysis, analysis_ptr, analysis, .release); } @@ -2741,7 +2824,6 @@ pub const Key = union(enum) { return a_info.owner_nav == b_info.owner_nav and a_info.ty == b_info.ty and a_info.init == b_info.init and - a_info.lib_name == b_info.lib_name and a_info.is_threadlocal == b_info.is_threadlocal and a_info.is_weak_linkage == b_info.is_weak_linkage; }, @@ -3054,8 +3136,6 @@ pub const LoadedUnionType = struct { // TODO: the non-fqn will be needed by the new dwarf structure /// The name of this union type. name: NullTerminatedString, - /// The `Cau` within which type resolution occurs. - cau: Cau.Index, /// Represents the declarations inside this union. namespace: NamespaceIndex, /// The enum tag type. @@ -3372,7 +3452,6 @@ pub fn loadUnionType(ip: *const InternPool, index: Index) LoadedUnionType { .tid = unwrapped_index.tid, .extra_index = data, .name = type_union.data.name, - .cau = type_union.data.cau, .namespace = type_union.data.namespace, .enum_tag_ty = type_union.data.tag_ty, .field_types = field_types, @@ -3389,8 +3468,6 @@ pub const LoadedStructType = struct { // TODO: the non-fqn will be needed by the new dwarf structure /// The name of this struct type. name: NullTerminatedString, - /// The `Cau` within which type resolution occurs. - cau: Cau.Index, namespace: NamespaceIndex, /// Index of the `struct_decl` or `reify` ZIR instruction. zir_index: TrackedInst.Index, @@ -3981,7 +4058,6 @@ pub fn loadStructType(ip: *const InternPool, index: Index) LoadedStructType { switch (item.tag) { .type_struct => { const name: NullTerminatedString = @enumFromInt(extra_items[item.data + std.meta.fieldIndex(Tag.TypeStruct, "name").?]); - const cau: Cau.Index = @enumFromInt(extra_items[item.data + std.meta.fieldIndex(Tag.TypeStruct, "cau").?]); const namespace: NamespaceIndex = @enumFromInt(extra_items[item.data + std.meta.fieldIndex(Tag.TypeStruct, "namespace").?]); const zir_index: TrackedInst.Index = @enumFromInt(extra_items[item.data + std.meta.fieldIndex(Tag.TypeStruct, "zir_index").?]); const fields_len = extra_items[item.data + std.meta.fieldIndex(Tag.TypeStruct, "fields_len").?]; @@ -4068,7 +4144,6 @@ pub fn loadStructType(ip: *const InternPool, index: Index) LoadedStructType { .tid = unwrapped_index.tid, .extra_index = item.data, .name = name, - .cau = cau, .namespace = namespace, .zir_index = zir_index, .layout = if (flags.is_extern) .@"extern" else .auto, @@ -4085,7 +4160,6 @@ pub fn loadStructType(ip: *const InternPool, index: Index) LoadedStructType { }, .type_struct_packed, .type_struct_packed_inits => { const name: NullTerminatedString = @enumFromInt(extra_items[item.data + std.meta.fieldIndex(Tag.TypeStructPacked, "name").?]); - const cau: Cau.Index = @enumFromInt(extra_items[item.data + std.meta.fieldIndex(Tag.TypeStructPacked, "cau").?]); const zir_index: TrackedInst.Index = @enumFromInt(extra_items[item.data + std.meta.fieldIndex(Tag.TypeStructPacked, "zir_index").?]); const fields_len = extra_items[item.data + std.meta.fieldIndex(Tag.TypeStructPacked, "fields_len").?]; const namespace: NamespaceIndex = @enumFromInt(extra_items[item.data + std.meta.fieldIndex(Tag.TypeStructPacked, "namespace").?]); @@ -4132,7 +4206,6 @@ pub fn loadStructType(ip: *const InternPool, index: Index) LoadedStructType { .tid = unwrapped_index.tid, .extra_index = item.data, .name = name, - .cau = cau, .namespace = namespace, .zir_index = zir_index, .layout = .@"packed", @@ -4155,9 +4228,6 @@ pub const LoadedEnumType = struct { // TODO: the non-fqn will be needed by the new dwarf structure /// The name of this enum type. name: NullTerminatedString, - /// The `Cau` within which type resolution occurs. - /// `null` if this is a generated tag type. - cau: Cau.Index.Optional, /// Represents the declarations inside this enum. namespace: NamespaceIndex, /// An integer type which is used for the numerical value of the enum. @@ -4234,21 +4304,15 @@ pub fn loadEnumType(ip: *const InternPool, index: Index) LoadedEnumType { .type_enum_auto => { const extra = extraDataTrail(extra_list, EnumAuto, item.data); var extra_index: u32 = @intCast(extra.end); - const cau: Cau.Index.Optional = if (extra.data.zir_index == .none) cau: { + if (extra.data.zir_index == .none) { extra_index += 1; // owner_union - break :cau .none; - } else cau: { - const cau: Cau.Index = @enumFromInt(extra_list.view().items(.@"0")[extra_index]); - extra_index += 1; // cau - break :cau cau.toOptional(); - }; + } const captures_len = if (extra.data.captures_len == std.math.maxInt(u32)) c: { extra_index += 2; // type_hash: PackedU64 break :c 0; } else extra.data.captures_len; return .{ .name = extra.data.name, - .cau = cau, .namespace = extra.data.namespace, .tag_ty = extra.data.int_tag_type, .names = .{ @@ -4274,21 +4338,15 @@ pub fn loadEnumType(ip: *const InternPool, index: Index) LoadedEnumType { }; const extra = extraDataTrail(extra_list, EnumExplicit, item.data); var extra_index: u32 = @intCast(extra.end); - const cau: Cau.Index.Optional = if (extra.data.zir_index == .none) cau: { + if (extra.data.zir_index == .none) { extra_index += 1; // owner_union - break :cau .none; - } else cau: { - const cau: Cau.Index = @enumFromInt(extra_list.view().items(.@"0")[extra_index]); - extra_index += 1; // cau - break :cau cau.toOptional(); - }; + } const captures_len = if (extra.data.captures_len == std.math.maxInt(u32)) c: { extra_index += 2; // type_hash: PackedU64 break :c 0; } else extra.data.captures_len; return .{ .name = extra.data.name, - .cau = cau, .namespace = extra.data.namespace, .tag_ty = extra.data.int_tag_type, .names = .{ @@ -5258,7 +5316,6 @@ pub const Tag = enum(u8) { .payload = EnumExplicit, .trailing = struct { owner_union: Index, - cau: ?Cau.Index, captures: ?[]CaptureValue, type_hash: ?u64, field_names: []NullTerminatedString, @@ -5304,7 +5361,6 @@ pub const Tag = enum(u8) { .payload = EnumAuto, .trailing = struct { owner_union: ?Index, - cau: ?Cau.Index, captures: ?[]CaptureValue, type_hash: ?u64, field_names: []NullTerminatedString, @@ -5573,9 +5629,6 @@ pub const Tag = enum(u8) { /// May be `none`. init: Index, owner_nav: Nav.Index, - /// Library name if specified. - /// For example `extern "c" var stderrp = ...` would have 'c' as library name. - lib_name: OptionalNullTerminatedString, flags: Flags, pub const Flags = packed struct(u32) { @@ -5684,7 +5737,6 @@ pub const Tag = enum(u8) { size: u32, /// Only valid after .have_layout padding: u32, - cau: Cau.Index, namespace: NamespaceIndex, /// The enum that provides the list of field names and values. tag_ty: Index, @@ -5715,7 +5767,6 @@ pub const Tag = enum(u8) { /// 5. init: Index for each fields_len // if tag is type_struct_packed_inits pub const TypeStructPacked = struct { name: NullTerminatedString, - cau: Cau.Index, zir_index: TrackedInst.Index, fields_len: u32, namespace: NamespaceIndex, @@ -5763,7 +5814,6 @@ pub const Tag = enum(u8) { /// 8. field_offset: u32 // for each field in declared order, undef until layout_resolved pub const TypeStruct = struct { name: NullTerminatedString, - cau: Cau.Index, zir_index: TrackedInst.Index, namespace: NamespaceIndex, fields_len: u32, @@ -5820,7 +5870,7 @@ pub const Tag = enum(u8) { /// equality or hashing, except for `inferred_error_set` which is considered /// to be part of the type of the function. pub const FuncAnalysis = packed struct(u32) { - state: State, + is_analyzed: bool, branch_hint: std.builtin.BranchHint, is_noinline: bool, calls_or_awaits_errorable_fn: bool, @@ -5828,20 +5878,7 @@ pub const FuncAnalysis = packed struct(u32) { inferred_error_set: bool, disable_instrumentation: bool, - _: u23 = 0, - - pub const State = enum(u2) { - /// The runtime function has never been referenced. - /// As such, it has never been analyzed, nor is it queued for analysis. - unreferenced, - /// The runtime function has been referenced, but has not yet been analyzed. - /// Its semantic analysis is queued. - queued, - /// The runtime function has been (or is currently being) semantically analyzed. - /// To know if analysis succeeded, consult `zcu.[transitive_]failed_analysis`. - /// To know if analysis is up-to-date, consult `zcu.[potentially_]outdated`. - analyzed, - }; + _: u24 = 0, }; pub const Bytes = struct { @@ -6093,11 +6130,10 @@ pub const Array = struct { /// Trailing: /// 0. owner_union: Index // if `zir_index == .none` -/// 1. cau: Cau.Index // if `zir_index != .none` -/// 2. capture: CaptureValue // for each `captures_len` -/// 3. type_hash: PackedU64 // if reified (`captures_len == std.math.maxInt(u32)`) -/// 4. field name: NullTerminatedString for each fields_len; declaration order -/// 5. tag value: Index for each fields_len; declaration order +/// 1. capture: CaptureValue // for each `captures_len` +/// 2. type_hash: PackedU64 // if reified (`captures_len == std.math.maxInt(u32)`) +/// 3. field name: NullTerminatedString for each fields_len; declaration order +/// 4. tag value: Index for each fields_len; declaration order pub const EnumExplicit = struct { name: NullTerminatedString, /// `std.math.maxInt(u32)` indicates this type is reified. @@ -6120,10 +6156,9 @@ pub const EnumExplicit = struct { /// Trailing: /// 0. owner_union: Index // if `zir_index == .none` -/// 1. cau: Cau.Index // if `zir_index != .none` -/// 2. capture: CaptureValue // for each `captures_len` -/// 3. type_hash: PackedU64 // if reified (`captures_len == std.math.maxInt(u32)`) -/// 4. field name: NullTerminatedString for each fields_len; declaration order +/// 1. capture: CaptureValue // for each `captures_len` +/// 2. type_hash: PackedU64 // if reified (`captures_len == std.math.maxInt(u32)`) +/// 3. field name: NullTerminatedString for each fields_len; declaration order pub const EnumAuto = struct { name: NullTerminatedString, /// `std.math.maxInt(u32)` indicates this type is reified. @@ -6413,32 +6448,32 @@ pub fn init(ip: *InternPool, gpa: Allocator, available_threads: usize) !void { ip.locals = try gpa.alloc(Local, used_threads); @memset(ip.locals, .{ .shared = .{ - .items = Local.List(Item).empty, - .extra = Local.Extra.empty, - .limbs = Local.Limbs.empty, - .strings = Local.Strings.empty, - .tracked_insts = Local.TrackedInsts.empty, - .files = Local.List(File).empty, - .maps = Local.Maps.empty, - .caus = Local.Caus.empty, - .navs = Local.Navs.empty, - - .namespaces = Local.Namespaces.empty, + .items = .empty, + .extra = .empty, + .limbs = .empty, + .strings = .empty, + .tracked_insts = .empty, + .files = .empty, + .maps = .empty, + .navs = .empty, + .comptime_units = .empty, + + .namespaces = .empty, }, .mutate = .{ .arena = .{}, - .items = Local.ListMutate.empty, - .extra = Local.ListMutate.empty, - .limbs = Local.ListMutate.empty, - .strings = Local.ListMutate.empty, - .tracked_insts = Local.ListMutate.empty, - .files = Local.ListMutate.empty, - .maps = Local.ListMutate.empty, - .caus = Local.ListMutate.empty, - .navs = Local.ListMutate.empty, + .items = .empty, + .extra = .empty, + .limbs = .empty, + .strings = .empty, + .tracked_insts = .empty, + .files = .empty, + .maps = .empty, + .navs = .empty, + .comptime_units = .empty, - .namespaces = Local.BucketListMutate.empty, + .namespaces = .empty, }, }); @@ -6486,6 +6521,7 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void { ip.file_deps.deinit(gpa); ip.src_hash_deps.deinit(gpa); ip.nav_val_deps.deinit(gpa); + ip.nav_ty_deps.deinit(gpa); ip.interned_deps.deinit(gpa); ip.namespace_deps.deinit(gpa); ip.namespace_name_deps.deinit(gpa); @@ -6511,7 +6547,8 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void { namespace.priv_decls.deinit(gpa); namespace.pub_usingnamespace.deinit(gpa); namespace.priv_usingnamespace.deinit(gpa); - namespace.other_decls.deinit(gpa); + namespace.comptime_decls.deinit(gpa); + namespace.test_decls.deinit(gpa); } }; const maps = local.getMutableMaps(gpa); @@ -6530,8 +6567,6 @@ pub fn activate(ip: *const InternPool) void { _ = OptionalString.debug_state; _ = NullTerminatedString.debug_state; _ = OptionalNullTerminatedString.debug_state; - _ = Cau.Index.debug_state; - _ = Cau.Index.Optional.debug_state; _ = Nav.Index.debug_state; _ = Nav.Index.Optional.debug_state; std.debug.assert(debug_state.intern_pool == null); @@ -6716,14 +6751,14 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { if (extra.data.captures_len == std.math.maxInt(u32)) { break :ns .{ .reified = .{ .zir_index = zir_index, - .type_hash = extraData(extra_list, PackedU64, extra.end + 1).get(), + .type_hash = extraData(extra_list, PackedU64, extra.end).get(), } }; } break :ns .{ .declared = .{ .zir_index = zir_index, .captures = .{ .owned = .{ .tid = unwrapped_index.tid, - .start = extra.end + 1, + .start = extra.end, .len = extra.data.captures_len, } }, } }; @@ -6740,14 +6775,14 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { if (extra.data.captures_len == std.math.maxInt(u32)) { break :ns .{ .reified = .{ .zir_index = zir_index, - .type_hash = extraData(extra_list, PackedU64, extra.end + 1).get(), + .type_hash = extraData(extra_list, PackedU64, extra.end).get(), } }; } break :ns .{ .declared = .{ .zir_index = zir_index, .captures = .{ .owned = .{ .tid = unwrapped_index.tid, - .start = extra.end + 1, + .start = extra.end, .len = extra.data.captures_len, } }, } }; @@ -6928,7 +6963,6 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { .ty = extra.ty, .init = extra.init, .owner_nav = extra.owner_nav, - .lib_name = extra.lib_name, .is_threadlocal = extra.flags.is_threadlocal, .is_weak_linkage = extra.flags.is_weak_linkage, } }; @@ -6944,8 +6978,8 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { .is_threadlocal = extra.flags.is_threadlocal, .is_weak_linkage = extra.flags.is_weak_linkage, .is_dll_import = extra.flags.is_dll_import, - .alignment = nav.status.resolved.alignment, - .@"addrspace" = nav.status.resolved.@"addrspace", + .alignment = nav.status.fully_resolved.alignment, + .@"addrspace" = nav.status.fully_resolved.@"addrspace", .zir_index = extra.zir_index, .owner_nav = extra.owner_nav, } }; @@ -7575,7 +7609,6 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All .ty = variable.ty, .init = variable.init, .owner_nav = variable.owner_nav, - .lib_name = variable.lib_name, .flags = .{ .is_const = false, .is_threadlocal = variable.is_threadlocal, @@ -8330,7 +8363,6 @@ pub fn getUnionType( .size = std.math.maxInt(u32), .padding = std.math.maxInt(u32), .name = undefined, // set by `finish` - .cau = undefined, // set by `finish` .namespace = undefined, // set by `finish` .tag_ty = ini.enum_tag_ty, .zir_index = switch (ini.key) { @@ -8382,7 +8414,6 @@ pub fn getUnionType( .tid = tid, .index = gop.put(), .type_name_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeUnion, "name").?, - .cau_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeUnion, "cau").?, .namespace_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeUnion, "namespace").?, } }; } @@ -8391,7 +8422,6 @@ pub const WipNamespaceType = struct { tid: Zcu.PerThread.Id, index: Index, type_name_extra_index: u32, - cau_extra_index: ?u32, namespace_extra_index: u32, pub fn setName( @@ -8407,18 +8437,11 @@ pub const WipNamespaceType = struct { pub fn finish( wip: WipNamespaceType, ip: *InternPool, - analysis_owner: Cau.Index.Optional, namespace: NamespaceIndex, ) Index { const extra = ip.getLocalShared(wip.tid).extra.acquire(); const extra_items = extra.view().items(.@"0"); - if (wip.cau_extra_index) |i| { - extra_items[i] = @intFromEnum(analysis_owner.unwrap().?); - } else { - assert(analysis_owner == .none); - } - extra_items[wip.namespace_extra_index] = @intFromEnum(namespace); return wip.index; @@ -8517,7 +8540,6 @@ pub fn getStructType( ini.fields_len); // inits const extra_index = addExtraAssumeCapacity(extra, Tag.TypeStructPacked{ .name = undefined, // set by `finish` - .cau = undefined, // set by `finish` .zir_index = zir_index, .fields_len = ini.fields_len, .namespace = undefined, // set by `finish` @@ -8562,7 +8584,6 @@ pub fn getStructType( .tid = tid, .index = gop.put(), .type_name_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeStructPacked, "name").?, - .cau_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeStructPacked, "cau").?, .namespace_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeStructPacked, "namespace").?, } }; }, @@ -8585,7 +8606,6 @@ pub fn getStructType( 1); // names_map const extra_index = addExtraAssumeCapacity(extra, Tag.TypeStruct{ .name = undefined, // set by `finish` - .cau = undefined, // set by `finish` .zir_index = zir_index, .namespace = undefined, // set by `finish` .fields_len = ini.fields_len, @@ -8654,7 +8674,6 @@ pub fn getStructType( .tid = tid, .index = gop.put(), .type_name_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeStruct, "name").?, - .cau_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeStruct, "cau").?, .namespace_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeStruct, "namespace").?, } }; } @@ -8878,7 +8897,7 @@ pub fn getFuncDecl( const func_decl_extra_index = addExtraAssumeCapacity(extra, Tag.FuncDecl{ .analysis = .{ - .state = .unreferenced, + .is_analyzed = false, .branch_hint = .none, .is_noinline = key.is_noinline, .calls_or_awaits_errorable_fn = false, @@ -8987,7 +9006,7 @@ pub fn getFuncDeclIes( const func_decl_extra_index = addExtraAssumeCapacity(extra, Tag.FuncDecl{ .analysis = .{ - .state = .unreferenced, + .is_analyzed = false, .branch_hint = .none, .is_noinline = key.is_noinline, .calls_or_awaits_errorable_fn = false, @@ -9183,7 +9202,7 @@ pub fn getFuncInstance( const func_extra_index = addExtraAssumeCapacity(extra, Tag.FuncInstance{ .analysis = .{ - .state = .unreferenced, + .is_analyzed = false, .branch_hint = .none, .is_noinline = arg.is_noinline, .calls_or_awaits_errorable_fn = false, @@ -9281,7 +9300,7 @@ pub fn getFuncInstanceIes( const func_extra_index = addExtraAssumeCapacity(extra, Tag.FuncInstance{ .analysis = .{ - .state = .unreferenced, + .is_analyzed = false, .branch_hint = .none, .is_noinline = arg.is_noinline, .calls_or_awaits_errorable_fn = false, @@ -9390,7 +9409,7 @@ fn finishFuncInstance( func_extra_index: u32, ) Allocator.Error!void { const fn_owner_nav = ip.getNav(ip.funcDeclInfo(generic_owner).owner_nav); - const fn_namespace = ip.getCau(fn_owner_nav.analysis_owner.unwrap().?).namespace; + const fn_namespace = fn_owner_nav.analysis.?.namespace; // TODO: improve this name const nav_name = try ip.getOrPutStringFmt(gpa, tid, "{}__anon_{d}", .{ @@ -9400,9 +9419,9 @@ fn finishFuncInstance( .name = nav_name, .fqn = try ip.namespacePtr(fn_namespace).internFullyQualifiedName(ip, gpa, tid, nav_name), .val = func_index, - .alignment = fn_owner_nav.status.resolved.alignment, - .@"linksection" = fn_owner_nav.status.resolved.@"linksection", - .@"addrspace" = fn_owner_nav.status.resolved.@"addrspace", + .alignment = fn_owner_nav.status.fully_resolved.alignment, + .@"linksection" = fn_owner_nav.status.fully_resolved.@"linksection", + .@"addrspace" = fn_owner_nav.status.fully_resolved.@"addrspace", }); // Populate the owner_nav field which was left undefined until now. @@ -9436,7 +9455,6 @@ pub const WipEnumType = struct { index: Index, tag_ty_index: u32, type_name_extra_index: u32, - cau_extra_index: u32, namespace_extra_index: u32, names_map: MapIndex, names_start: u32, @@ -9456,13 +9474,11 @@ pub const WipEnumType = struct { pub fn prepare( wip: WipEnumType, ip: *InternPool, - analysis_owner: Cau.Index, namespace: NamespaceIndex, ) void { const extra = ip.getLocalShared(wip.tid).extra.acquire(); const extra_items = extra.view().items(.@"0"); - extra_items[wip.cau_extra_index] = @intFromEnum(analysis_owner); extra_items[wip.namespace_extra_index] = @intFromEnum(namespace); } @@ -9563,7 +9579,6 @@ pub fn getEnumType( .reified => 2, // type_hash: PackedU64 } + // zig fmt: on - 1 + // cau ini.fields_len); // field types const extra_index = addExtraAssumeCapacity(extra, EnumAuto{ @@ -9584,8 +9599,6 @@ pub fn getEnumType( .tag = .type_enum_auto, .data = extra_index, }); - const cau_extra_index = extra.view().len; - extra.appendAssumeCapacity(undefined); // `cau` will be set by `finish` switch (ini.key) { .declared => |d| extra.appendSliceAssumeCapacity(.{@ptrCast(d.captures)}), .declared_owned_captures => |d| extra.appendSliceAssumeCapacity(.{@ptrCast(d.captures.get(ip))}), @@ -9598,7 +9611,6 @@ pub fn getEnumType( .index = gop.put(), .tag_ty_index = extra_index + std.meta.fieldIndex(EnumAuto, "int_tag_type").?, .type_name_extra_index = extra_index + std.meta.fieldIndex(EnumAuto, "name").?, - .cau_extra_index = @intCast(cau_extra_index), .namespace_extra_index = extra_index + std.meta.fieldIndex(EnumAuto, "namespace").?, .names_map = names_map, .names_start = @intCast(names_start), @@ -9623,7 +9635,6 @@ pub fn getEnumType( .reified => 2, // type_hash: PackedU64 } + // zig fmt: on - 1 + // cau ini.fields_len + // field types ini.fields_len * @intFromBool(ini.has_values)); // field values @@ -9650,8 +9661,6 @@ pub fn getEnumType( }, .data = extra_index, }); - const cau_extra_index = extra.view().len; - extra.appendAssumeCapacity(undefined); // `cau` will be set by `finish` switch (ini.key) { .declared => |d| extra.appendSliceAssumeCapacity(.{@ptrCast(d.captures)}), .declared_owned_captures => |d| extra.appendSliceAssumeCapacity(.{@ptrCast(d.captures.get(ip))}), @@ -9668,7 +9677,6 @@ pub fn getEnumType( .index = gop.put(), .tag_ty_index = extra_index + std.meta.fieldIndex(EnumExplicit, "int_tag_type").?, .type_name_extra_index = extra_index + std.meta.fieldIndex(EnumExplicit, "name").?, - .cau_extra_index = @intCast(cau_extra_index), .namespace_extra_index = extra_index + std.meta.fieldIndex(EnumExplicit, "namespace").?, .names_map = names_map, .names_start = @intCast(names_start), @@ -9865,7 +9873,6 @@ pub fn getOpaqueType( .tid = tid, .index = gop.put(), .type_name_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeOpaque, "name").?, - .cau_extra_index = null, // opaques do not undergo type resolution .namespace_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeOpaque, "namespace").?, }, }; @@ -9981,7 +9988,6 @@ fn addExtraAssumeCapacity(extra: Local.Extra.Mutable, item: anytype) u32 { inline for (@typeInfo(@TypeOf(item)).@"struct".fields) |field| { extra.appendAssumeCapacity(.{switch (field.type) { Index, - Cau.Index, Nav.Index, NamespaceIndex, OptionalNamespaceIndex, @@ -10044,7 +10050,6 @@ fn extraDataTrail(extra: Local.Extra, comptime T: type, index: u32) struct { dat const extra_item = extra_items[extra_index]; @field(result, field.name) = switch (field.type) { Index, - Cau.Index, Nav.Index, NamespaceIndex, OptionalNamespaceIndex, @@ -11065,12 +11070,6 @@ pub fn dumpGenericInstancesFallible(ip: *const InternPool, allocator: Allocator) try bw.flush(); } -pub fn getCau(ip: *const InternPool, index: Cau.Index) Cau { - const unwrapped = index.unwrap(ip); - const caus = ip.getLocalShared(unwrapped.tid).caus.acquire(); - return caus.view().items(.@"0")[unwrapped.index]; -} - pub fn getNav(ip: *const InternPool, index: Nav.Index) Nav { const unwrapped = index.unwrap(ip); const navs = ip.getLocalShared(unwrapped.tid).navs.acquire(); @@ -11084,51 +11083,34 @@ pub fn namespacePtr(ip: *InternPool, namespace_index: NamespaceIndex) *Zcu.Names return &namespaces_bucket[unwrapped_namespace_index.index]; } -/// Create a `Cau` associated with the type at the given `InternPool.Index`. -pub fn createTypeCau( +/// Create a `ComptimeUnit`, forming an `AnalUnit` for a `comptime` declaration. +pub fn createComptimeUnit( ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, zir_index: TrackedInst.Index, namespace: NamespaceIndex, - owner_type: InternPool.Index, -) Allocator.Error!Cau.Index { - const caus = ip.getLocal(tid).getMutableCaus(gpa); - const index_unwrapped: Cau.Index.Unwrapped = .{ +) Allocator.Error!ComptimeUnit.Id { + const comptime_units = ip.getLocal(tid).getMutableComptimeUnits(gpa); + const id_unwrapped: ComptimeUnit.Id.Unwrapped = .{ .tid = tid, - .index = caus.mutate.len, + .index = comptime_units.mutate.len, }; - try caus.append(.{.{ + try comptime_units.append(.{.{ .zir_index = zir_index, .namespace = namespace, - .owner = Cau.Owner.wrap(.{ .type = owner_type }), }}); - return index_unwrapped.wrap(ip); + return id_unwrapped.wrap(ip); } -/// Create a `Cau` for a `comptime` declaration. -pub fn createComptimeCau( - ip: *InternPool, - gpa: Allocator, - tid: Zcu.PerThread.Id, - zir_index: TrackedInst.Index, - namespace: NamespaceIndex, -) Allocator.Error!Cau.Index { - const caus = ip.getLocal(tid).getMutableCaus(gpa); - const index_unwrapped: Cau.Index.Unwrapped = .{ - .tid = tid, - .index = caus.mutate.len, - }; - try caus.append(.{.{ - .zir_index = zir_index, - .namespace = namespace, - .owner = Cau.Owner.wrap(.none), - }}); - return index_unwrapped.wrap(ip); +pub fn getComptimeUnit(ip: *const InternPool, id: ComptimeUnit.Id) ComptimeUnit { + const unwrapped = id.unwrap(ip); + const comptime_units = ip.getLocalShared(unwrapped.tid).comptime_units.acquire(); + return comptime_units.view().items(.@"0")[unwrapped.index]; } -/// Create a `Nav` not associated with any `Cau`. -/// Since there is no analysis owner, the `Nav`'s value must be known at creation time. +/// Create a `Nav` which does not undergo semantic analysis. +/// Since it is never analyzed, the `Nav`'s value must be known at creation time. pub fn createNav( ip: *InternPool, gpa: Allocator, @@ -11150,8 +11132,8 @@ pub fn createNav( try navs.append(Nav.pack(.{ .name = opts.name, .fqn = opts.fqn, - .analysis_owner = .none, - .status = .{ .resolved = .{ + .analysis = null, + .status = .{ .fully_resolved = .{ .val = opts.val, .alignment = opts.alignment, .@"linksection" = opts.@"linksection", @@ -11162,10 +11144,9 @@ pub fn createNav( return index_unwrapped.wrap(ip); } -/// Create a `Cau` and `Nav` which are paired. The value of the `Nav` is -/// determined by semantic analysis of the `Cau`. The value of the `Nav` -/// is initially unresolved. -pub fn createPairedCauNav( +/// Create a `Nav` which undergoes semantic analysis because it corresponds to a source declaration. +/// The value of the `Nav` is initially unresolved. +pub fn createDeclNav( ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, @@ -11175,36 +11156,72 @@ pub fn createPairedCauNav( namespace: NamespaceIndex, /// TODO: this is hacky! See `Nav.is_usingnamespace`. is_usingnamespace: bool, -) Allocator.Error!struct { Cau.Index, Nav.Index } { - const caus = ip.getLocal(tid).getMutableCaus(gpa); +) Allocator.Error!Nav.Index { const navs = ip.getLocal(tid).getMutableNavs(gpa); - try caus.ensureUnusedCapacity(1); try navs.ensureUnusedCapacity(1); - const cau = Cau.Index.Unwrapped.wrap(.{ - .tid = tid, - .index = caus.mutate.len, - }, ip); const nav = Nav.Index.Unwrapped.wrap(.{ .tid = tid, .index = navs.mutate.len, }, ip); - caus.appendAssumeCapacity(.{.{ - .zir_index = zir_index, - .namespace = namespace, - .owner = Cau.Owner.wrap(.{ .nav = nav }), - }}); navs.appendAssumeCapacity(Nav.pack(.{ .name = name, .fqn = fqn, - .analysis_owner = cau.toOptional(), + .analysis = .{ + .namespace = namespace, + .zir_index = zir_index, + }, .status = .unresolved, .is_usingnamespace = is_usingnamespace, })); - return .{ cau, nav }; + return nav; +} + +/// Resolve the type of a `Nav` with an analysis owner. +/// If its status is already `resolved`, the old value is discarded. +pub fn resolveNavType( + ip: *InternPool, + nav: Nav.Index, + resolved: struct { + type: InternPool.Index, + alignment: Alignment, + @"linksection": OptionalNullTerminatedString, + @"addrspace": std.builtin.AddressSpace, + is_const: bool, + is_threadlocal: bool, + is_extern_decl: bool, + }, +) void { + const unwrapped = nav.unwrap(ip); + + const local = ip.getLocal(unwrapped.tid); + local.mutate.extra.mutex.lock(); + defer local.mutate.extra.mutex.unlock(); + + const navs = local.shared.navs.view(); + + const nav_analysis_namespace = navs.items(.analysis_namespace); + const nav_analysis_zir_index = navs.items(.analysis_zir_index); + const nav_types = navs.items(.type_or_val); + const nav_linksections = navs.items(.@"linksection"); + const nav_bits = navs.items(.bits); + + assert(nav_analysis_namespace[unwrapped.index] != .none); + assert(nav_analysis_zir_index[unwrapped.index] != .none); + + @atomicStore(InternPool.Index, &nav_types[unwrapped.index], resolved.type, .release); + @atomicStore(OptionalNullTerminatedString, &nav_linksections[unwrapped.index], resolved.@"linksection", .release); + + var bits = nav_bits[unwrapped.index]; + bits.status = if (resolved.is_extern_decl) .type_resolved_extern_decl else .type_resolved; + bits.alignment = resolved.alignment; + bits.@"addrspace" = resolved.@"addrspace"; + bits.is_const = resolved.is_const; + bits.is_threadlocal = resolved.is_threadlocal; + @atomicStore(Nav.Repr.Bits, &nav_bits[unwrapped.index], bits, .release); } /// Resolve the value of a `Nav` with an analysis owner. @@ -11227,18 +11244,20 @@ pub fn resolveNavValue( const navs = local.shared.navs.view(); - const nav_analysis_owners = navs.items(.analysis_owner); - const nav_vals = navs.items(.val); + const nav_analysis_namespace = navs.items(.analysis_namespace); + const nav_analysis_zir_index = navs.items(.analysis_zir_index); + const nav_vals = navs.items(.type_or_val); const nav_linksections = navs.items(.@"linksection"); const nav_bits = navs.items(.bits); - assert(nav_analysis_owners[unwrapped.index] != .none); + assert(nav_analysis_namespace[unwrapped.index] != .none); + assert(nav_analysis_zir_index[unwrapped.index] != .none); @atomicStore(InternPool.Index, &nav_vals[unwrapped.index], resolved.val, .release); @atomicStore(OptionalNullTerminatedString, &nav_linksections[unwrapped.index], resolved.@"linksection", .release); var bits = nav_bits[unwrapped.index]; - bits.status = .resolved; + bits.status = .fully_resolved; bits.alignment = resolved.alignment; bits.@"addrspace" = resolved.@"addrspace"; @atomicStore(Nav.Repr.Bits, &nav_bits[unwrapped.index], bits, .release); diff --git a/src/Sema.zig b/src/Sema.zig index 110476adf9..ccc9f63a56 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1284,7 +1284,6 @@ fn analyzeBodyInner( const extended = datas[@intFromEnum(inst)].extended; break :ext switch (extended.opcode) { // zig fmt: off - .variable => try sema.zirVarExtended( block, extended), .struct_decl => try sema.zirStructDecl( block, extended, inst), .enum_decl => try sema.zirEnumDecl( block, extended, inst), .union_decl => try sema.zirUnionDecl( block, extended, inst), @@ -2114,13 +2113,33 @@ pub fn setupErrorReturnTrace(sema: *Sema, block: *Block, last_arg_index: usize) } /// Return the Value corresponding to a given AIR ref, or `null` if it refers to a runtime value. -/// InternPool key `variable` is considered a runtime value. /// Generic poison causes `error.GenericPoison` to be returned. fn resolveValue(sema: *Sema, inst: Air.Inst.Ref) CompileError!?Value { - const val = (try sema.resolveValueAllowVariables(inst)) orelse return null; - if (val.isGenericPoison()) return error.GenericPoison; - if (sema.pt.zcu.intern_pool.isVariable(val.toIntern())) return null; - return val; + const zcu = sema.pt.zcu; + assert(inst != .none); + + if (try sema.typeHasOnePossibleValue(sema.typeOf(inst))) |opv| { + return opv; + } + + if (inst.toInterned()) |ip_index| { + const val: Value = .fromInterned(ip_index); + + assert(val.getVariable(zcu) == null); + if (val.isPtrRuntimeValue(zcu)) return null; + if (val.isGenericPoison()) return error.GenericPoison; + + return val; + } else { + // Runtime-known value. + const air_tags = sema.air_instructions.items(.tag); + switch (air_tags[@intFromEnum(inst.toIndex().?)]) { + .inferred_alloc => unreachable, // assertion failure + .inferred_alloc_comptime => unreachable, // assertion failure + else => {}, + } + return null; + } } /// Like `resolveValue`, but emits an error if the value is not comptime-known. @@ -2183,35 +2202,6 @@ fn resolveValueIntable(sema: *Sema, inst: Air.Inst.Ref) CompileError!?Value { return try sema.resolveLazyValue(val); } -/// Returns all InternPool keys representing values, including `variable`, `undef`, and `generic_poison`. -fn resolveValueAllowVariables(sema: *Sema, inst: Air.Inst.Ref) CompileError!?Value { - const pt = sema.pt; - assert(inst != .none); - // First section of indexes correspond to a set number of constant values. - if (@intFromEnum(inst) < InternPool.static_len) { - return Value.fromInterned(@as(InternPool.Index, @enumFromInt(@intFromEnum(inst)))); - } - - const air_tags = sema.air_instructions.items(.tag); - if (try sema.typeHasOnePossibleValue(sema.typeOf(inst))) |opv| { - if (inst.toInterned()) |ip_index| { - const val = Value.fromInterned(ip_index); - if (val.getVariable(pt.zcu) != null) return val; - } - return opv; - } - const ip_index = inst.toInterned() orelse { - switch (air_tags[@intFromEnum(inst.toIndex().?)]) { - .inferred_alloc => unreachable, - .inferred_alloc_comptime => unreachable, - else => return null, - } - }; - const val = Value.fromInterned(ip_index); - if (val.isPtrRuntimeValue(pt.zcu)) return null; - return val; -} - /// Value Tag may be `undef` or `variable`. pub fn resolveFinalDeclValue( sema: *Sema, @@ -2221,8 +2211,13 @@ pub fn resolveFinalDeclValue( ) CompileError!Value { const zcu = sema.pt.zcu; - const val = try sema.resolveValueAllowVariables(air_ref) orelse { - const value_comptime_reason: ?[]const u8 = if (air_ref.toInterned()) |_| + const val = try sema.resolveValue(air_ref) orelse { + const is_runtime_ptr = rt_ptr: { + const ip_index = air_ref.toInterned() orelse break :rt_ptr false; + const val: Value = .fromInterned(ip_index); + break :rt_ptr val.isPtrRuntimeValue(zcu); + }; + const value_comptime_reason: ?[]const u8 = if (is_runtime_ptr) "thread local and dll imported variables have runtime-known addresses" else null; @@ -2232,10 +2227,8 @@ pub fn resolveFinalDeclValue( .value_comptime_reason = value_comptime_reason, }); }; - if (val.isGenericPoison()) return error.GenericPoison; - const init_val: Value = if (val.getVariable(zcu)) |v| .fromInterned(v.init) else val; - if (init_val.canMutateComptimeVarState(zcu)) { + if (val.canMutateComptimeVarState(zcu)) { return sema.fail(block, src, "global variable contains reference to comptime var", .{}); } @@ -2877,7 +2870,7 @@ fn zirStructDecl( }; const wip_ty = switch (try ip.getStructType(gpa, pt.tid, struct_init, false)) { .existing => |ty| { - const new_ty = try pt.ensureTypeUpToDate(ty, false); + const new_ty = try pt.ensureTypeUpToDate(ty); // Make sure we update the namespace if the declaration is re-analyzed, to pick // up on e.g. changed comptime decls. @@ -2907,12 +2900,10 @@ fn zirStructDecl( }); errdefer pt.destroyNamespace(new_namespace_index); - const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, new_namespace_index, wip_ty.index); - if (pt.zcu.comp.incremental) { try ip.addDependency( sema.gpa, - AnalUnit.wrap(.{ .cau = new_cau_index }), + AnalUnit.wrap(.{ .type = wip_ty.index }), .{ .src_hash = tracked_inst }, ); } @@ -2929,7 +2920,7 @@ fn zirStructDecl( } try sema.declareDependency(.{ .interned = wip_ty.index }); try sema.addTypeReferenceEntry(src, wip_ty.index); - return Air.internedToRef(wip_ty.finish(ip, new_cau_index.toOptional(), new_namespace_index)); + return Air.internedToRef(wip_ty.finish(ip, new_namespace_index)); } fn createTypeName( @@ -3107,7 +3098,7 @@ fn zirEnumDecl( }; const wip_ty = switch (try ip.getEnumType(gpa, pt.tid, enum_init, false)) { .existing => |ty| { - const new_ty = try pt.ensureTypeUpToDate(ty, false); + const new_ty = try pt.ensureTypeUpToDate(ty); // Make sure we update the namespace if the declaration is re-analyzed, to pick // up on e.g. changed comptime decls. @@ -3143,16 +3134,14 @@ fn zirEnumDecl( }); errdefer if (!done) pt.destroyNamespace(new_namespace_index); - const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, new_namespace_index, wip_ty.index); - try pt.scanNamespace(new_namespace_index, decls); try sema.declareDependency(.{ .interned = wip_ty.index }); try sema.addTypeReferenceEntry(src, wip_ty.index); // We've finished the initial construction of this type, and are about to perform analysis. - // Set the Cau and namespace appropriately, and don't destroy anything on failure. - wip_ty.prepare(ip, new_cau_index, new_namespace_index); + // Set the namespace appropriately, and don't destroy anything on failure. + wip_ty.prepare(ip, new_namespace_index); done = true; try Sema.resolveDeclaredEnum( @@ -3162,7 +3151,6 @@ fn zirEnumDecl( tracked_inst, new_namespace_index, type_name, - new_cau_index, small, body, tag_type_ref, @@ -3252,7 +3240,7 @@ fn zirUnionDecl( }; const wip_ty = switch (try ip.getUnionType(gpa, pt.tid, union_init, false)) { .existing => |ty| { - const new_ty = try pt.ensureTypeUpToDate(ty, false); + const new_ty = try pt.ensureTypeUpToDate(ty); // Make sure we update the namespace if the declaration is re-analyzed, to pick // up on e.g. changed comptime decls. @@ -3282,12 +3270,10 @@ fn zirUnionDecl( }); errdefer pt.destroyNamespace(new_namespace_index); - const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, new_namespace_index, wip_ty.index); - if (pt.zcu.comp.incremental) { try zcu.intern_pool.addDependency( gpa, - AnalUnit.wrap(.{ .cau = new_cau_index }), + AnalUnit.wrap(.{ .type = wip_ty.index }), .{ .src_hash = tracked_inst }, ); } @@ -3304,7 +3290,7 @@ fn zirUnionDecl( } try sema.declareDependency(.{ .interned = wip_ty.index }); try sema.addTypeReferenceEntry(src, wip_ty.index); - return Air.internedToRef(wip_ty.finish(ip, new_cau_index.toOptional(), new_namespace_index)); + return Air.internedToRef(wip_ty.finish(ip, new_namespace_index)); } fn zirOpaqueDecl( @@ -3389,7 +3375,7 @@ fn zirOpaqueDecl( try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index }); } try sema.addTypeReferenceEntry(src, wip_ty.index); - return Air.internedToRef(wip_ty.finish(ip, .none, new_namespace_index)); + return Air.internedToRef(wip_ty.finish(ip, new_namespace_index)); } fn zirErrorSetDecl( @@ -6509,9 +6495,9 @@ pub fn analyzeExport( if (options.linkage == .internal) return; - try sema.ensureNavResolved(src, orig_nav_index); + try sema.ensureNavResolved(src, orig_nav_index, .fully); - const exported_nav_index = switch (ip.indexToKey(ip.getNav(orig_nav_index).status.resolved.val)) { + const exported_nav_index = switch (ip.indexToKey(ip.getNav(orig_nav_index).status.fully_resolved.val)) { .variable => |v| v.owner_nav, .@"extern" => |e| e.owner_nav, .func => |f| f.owner_nav, @@ -6534,7 +6520,7 @@ pub fn analyzeExport( } // TODO: some backends might support re-exporting extern decls - if (exported_nav.isExtern(ip)) { + if (exported_nav.getExtern(ip) != null) { return sema.fail(block, src, "export target cannot be extern", .{}); } @@ -6554,7 +6540,11 @@ fn zirDisableInstrumentation(sema: *Sema) CompileError!void { const ip = &zcu.intern_pool; const func = switch (sema.owner.unwrap()) { .func => |func| func, - .cau => return, // does nothing outside a function + .@"comptime", + .nav_val, + .nav_ty, + .type, + => return, // does nothing outside a function }; ip.funcSetDisableInstrumentation(func); sema.allow_memoize = false; @@ -6865,8 +6855,8 @@ fn lookupInNamespace( } for (usingnamespaces.items) |sub_ns_nav| { - try sema.ensureNavResolved(src, sub_ns_nav); - const sub_ns_ty = Type.fromInterned(ip.getNav(sub_ns_nav).status.resolved.val); + try sema.ensureNavResolved(src, sub_ns_nav, .fully); + const sub_ns_ty = Type.fromInterned(ip.getNav(sub_ns_nav).status.fully_resolved.val); const sub_ns = zcu.namespacePtr(sub_ns_ty.getNamespaceIndex(zcu)); try checked_namespaces.put(gpa, sub_ns, {}); } @@ -6875,11 +6865,8 @@ fn lookupInNamespace( ignore_self: { const skip_nav = switch (sema.owner.unwrap()) { - .func => break :ignore_self, - .cau => |cau| switch (ip.getCau(cau).owner.unwrap()) { - .none, .type => break :ignore_self, - .nav => |nav| nav, - }, + .@"comptime", .type, .func => break :ignore_self, + .nav_ty, .nav_val => |nav| nav, }; var i: usize = 0; while (i < candidates.items.len) { @@ -7139,7 +7126,7 @@ fn zirCall( const call_inst = try sema.analyzeCall(block, func, func_ty, callee_src, call_src, modifier, ensure_result_used, args_info, call_dbg_node, .call); switch (sema.owner.unwrap()) { - .cau => input_is_error = false, + .@"comptime", .type, .nav_ty, .nav_val => input_is_error = false, .func => |owner_func| if (!zcu.intern_pool.funcAnalysisUnordered(owner_func).calls_or_awaits_errorable_fn) { // No errorable fn actually called; we have no error return trace input_is_error = false; @@ -7700,12 +7687,13 @@ fn analyzeCall( .ptr => |ptr| blk: { switch (ptr.base_addr) { .nav => |nav_index| if (ptr.byte_offset == 0) { + try sema.ensureNavResolved(call_src, nav_index, .fully); const nav = ip.getNav(nav_index); - if (nav.isExtern(ip)) + if (nav.getExtern(ip) != null) return sema.fail(block, call_src, "{s} call of extern function pointer", .{ if (is_comptime_call) "comptime" else "inline", }); - break :blk nav.status.resolved.val; + break :blk nav.status.fully_resolved.val; }, else => {}, } @@ -7754,11 +7742,9 @@ fn analyzeCall( // The call site definitely depends on the function's signature. try sema.declareDependency(.{ .src_hash = module_fn.zir_body_inst }); - // This is not a function instance, so the function's `Nav` has a - // `Cau` -- we don't need to check `generic_owner`. + // This is not a function instance, so the function's `Nav` has analysis + // state -- we don't need to check `generic_owner`. const fn_nav = ip.getNav(module_fn.owner_nav); - const fn_cau_index = fn_nav.analysis_owner.unwrap().?; - const fn_cau = ip.getCau(fn_cau_index); // We effectively want a child Sema here, but can't literally do that, because we need AIR // to be shared. InlineCallSema is a wrapper which handles this for us. While `ics` is in @@ -7766,7 +7752,7 @@ fn analyzeCall( // whenever performing an operation where the difference matters. var ics = InlineCallSema.init( sema, - zcu.cauFileScope(fn_cau_index).zir, + zcu.navFileScope(module_fn.owner_nav).zir, module_fn_index, block.error_return_trace_index, ); @@ -7776,7 +7762,7 @@ fn analyzeCall( .parent = null, .sema = sema, // The function body exists in the same namespace as the corresponding function declaration. - .namespace = fn_cau.namespace, + .namespace = fn_nav.analysis.?.namespace, .instructions = .{}, .label = null, .inlining = &inlining, @@ -7787,7 +7773,7 @@ fn analyzeCall( .runtime_cond = block.runtime_cond, .runtime_loop = block.runtime_loop, .runtime_index = block.runtime_index, - .src_base_inst = fn_cau.zir_index, + .src_base_inst = fn_nav.analysis.?.zir_index, .type_name_ctx = fn_nav.fqn, }; @@ -7802,7 +7788,7 @@ fn analyzeCall( // mutate comptime state. // TODO: comptime call memoization is currently not supported under incremental compilation // since dependencies are not marked on callers. If we want to keep this around (we should - // check that it's worthwhile first!), each memoized call needs a `Cau`. + // check that it's worthwhile first!), each memoized call needs an `AnalUnit`. var should_memoize = !zcu.comp.incremental; // If it's a comptime function call, we need to memoize it as long as no external @@ -7911,7 +7897,7 @@ fn analyzeCall( // Since we're doing an inline call, we depend on the source code of the whole // function declaration. - try sema.declareDependency(.{ .src_hash = fn_cau.zir_index }); + try sema.declareDependency(.{ .src_hash = fn_nav.analysis.?.zir_index }); new_fn_info.return_type = sema.fn_ret_ty.toIntern(); if (!is_comptime_call and !block.is_typeof) { @@ -8023,7 +8009,7 @@ fn analyzeCall( if (call_dbg_node) |some| try sema.zirDbgStmt(block, some); switch (sema.owner.unwrap()) { - .cau => {}, + .@"comptime", .nav_ty, .nav_val, .type => {}, .func => |owner_func| if (Type.fromInterned(func_ty_info.return_type).isError(zcu)) { ip.funcSetCallsOrAwaitsErrorableFn(owner_func); }, @@ -8062,7 +8048,10 @@ fn analyzeCall( switch (zcu.intern_pool.indexToKey(func_val.toIntern())) { .func => break :skip_safety, .ptr => |ptr| if (ptr.byte_offset == 0) switch (ptr.base_addr) { - .nav => |nav| if (!ip.getNav(nav).isExtern(ip)) break :skip_safety, + .nav => |nav| { + try sema.ensureNavResolved(call_src, nav, .fully); + if (ip.getNav(nav).getExtern(ip) == null) break :skip_safety; + }, else => {}, }, else => {}, @@ -8259,7 +8248,7 @@ fn instantiateGenericCall( }); const generic_owner = switch (zcu.intern_pool.indexToKey(func_val.toIntern())) { .func => func_val.toIntern(), - .ptr => |ptr| ip.getNav(ptr.base_addr.nav).status.resolved.val, + .ptr => |ptr| ip.getNav(ptr.base_addr.nav).status.fully_resolved.val, else => unreachable, }; const generic_owner_func = zcu.intern_pool.indexToKey(generic_owner).func; @@ -8275,10 +8264,9 @@ fn instantiateGenericCall( // The actual monomorphization happens via adding `func_instance` to // `InternPool`. - // Since we are looking at the generic owner here, it has a `Cau`. + // Since we are looking at the generic owner here, it has analysis state. const fn_nav = ip.getNav(generic_owner_func.owner_nav); - const fn_cau = ip.getCau(fn_nav.analysis_owner.unwrap().?); - const fn_zir = zcu.namespacePtr(fn_cau.namespace).fileScope(zcu).zir; + const fn_zir = zcu.navFileScope(generic_owner_func.owner_nav).zir; const fn_info = fn_zir.getFnInfo(generic_owner_func.zir_body_inst.resolve(ip) orelse return error.AnalysisFail); const comptime_args = try sema.arena.alloc(InternPool.Index, args_info.count()); @@ -8319,11 +8307,11 @@ fn instantiateGenericCall( var child_block: Block = .{ .parent = null, .sema = &child_sema, - .namespace = fn_cau.namespace, + .namespace = fn_nav.analysis.?.namespace, .instructions = .{}, .inlining = null, .is_comptime = true, - .src_base_inst = fn_cau.zir_index, + .src_base_inst = fn_nav.analysis.?.zir_index, .type_name_ctx = fn_nav.fqn, }; defer child_block.instructions.deinit(gpa); @@ -8488,7 +8476,7 @@ fn instantiateGenericCall( if (call_dbg_node) |some| try sema.zirDbgStmt(block, some); switch (sema.owner.unwrap()) { - .cau => {}, + .@"comptime", .nav_ty, .nav_val, .type => {}, .func => |owner_func| if (Type.fromInterned(func_ty_info.return_type).isError(zcu)) { ip.funcSetCallsOrAwaitsErrorableFn(owner_func); }, @@ -9517,16 +9505,13 @@ fn zirFunc( // the callconv based on whether it is exported. Otherwise, the callconv defaults // to `.auto`. const cc: std.builtin.CallingConvention = if (has_body) cc: { - const func_decl_cau = if (sema.generic_owner != .none) cau: { - const generic_owner_fn = zcu.funcInfo(sema.generic_owner); - // The generic owner definitely has a `Cau` for the corresponding function declaration. - const generic_owner_nav = ip.getNav(generic_owner_fn.owner_nav); - break :cau generic_owner_nav.analysis_owner.unwrap().?; - } else sema.owner.unwrap().cau; + const func_decl_nav = if (sema.generic_owner != .none) nav: { + break :nav zcu.funcInfo(sema.generic_owner).owner_nav; + } else sema.owner.unwrap().nav_val; const fn_is_exported = exported: { - const decl_inst = ip.getCau(func_decl_cau).zir_index.resolve(ip) orelse return error.AnalysisFail; - const zir_decl = sema.code.getDeclaration(decl_inst)[0]; - break :exported zir_decl.flags.is_export; + const decl_inst = ip.getNav(func_decl_nav).analysis.?.zir_index.resolve(ip) orelse return error.AnalysisFail; + const zir_decl = sema.code.getDeclaration(decl_inst); + break :exported zir_decl.linkage == .@"export"; }; if (fn_is_exported) { break :cc target.cCallingConvention() orelse { @@ -9557,10 +9542,8 @@ fn zirFunc( ret_ty, false, inferred_error_set, - false, has_body, src_locs, - null, 0, false, ); @@ -9619,7 +9602,7 @@ fn resolveGenericBody( /// respective `Decl` (either `ExternFn` or `Var`). /// The liveness of the duped library name is tied to liveness of `Zcu`. /// To deallocate, call `deinit` on the respective `Decl` (`ExternFn` or `Var`). -fn handleExternLibName( +pub fn handleExternLibName( sema: *Sema, block: *Block, src_loc: LazySrcLoc, @@ -9843,10 +9826,8 @@ fn funcCommon( bare_return_type: Type, var_args: bool, inferred_error_set: bool, - is_extern: bool, has_body: bool, src_locs: Zir.Inst.Func.SrcLocs, - opt_lib_name: ?[]const u8, noalias_bits: u32, is_noinline: bool, ) CompileError!Air.Inst.Ref { @@ -9998,12 +9979,11 @@ fn funcCommon( } if (inferred_error_set) { - assert(!is_extern); assert(has_body); if (!ret_poison) try sema.validateErrorUnionPayloadType(block, bare_return_type, ret_ty_src); const func_index = try ip.getFuncDeclIes(gpa, pt.tid, .{ - .owner_nav = sema.getOwnerCauNav(), + .owner_nav = sema.owner.unwrap().nav_val, .param_types = param_types, .noalias_bits = noalias_bits, @@ -10050,35 +10030,9 @@ fn funcCommon( .is_noinline = is_noinline, }); - if (is_extern) { - assert(comptime_bits == 0); - assert(!is_generic); - if (opt_lib_name) |lib_name| try sema.handleExternLibName(block, block.src(.{ - .node_offset_lib_name = src_node_offset, - }), lib_name); - const extern_func_index = try sema.resolveExternDecl(block, .fromInterned(func_ty), opt_lib_name, true, false); - return finishFunc( - sema, - block, - extern_func_index, - func_ty, - ret_poison, - bare_return_type, - ret_ty_src, - cc, - is_source_decl, - ret_ty_requires_comptime, - func_inst, - cc_src, - is_noinline, - is_generic, - final_is_generic, - ); - } - if (has_body) { const func_index = try ip.getFuncDecl(gpa, pt.tid, .{ - .owner_nav = sema.getOwnerCauNav(), + .owner_nav = sema.owner.unwrap().nav_val, .ty = func_ty, .cc = cc, .is_noinline = is_noinline, @@ -17702,7 +17656,7 @@ fn zirAsm( if (is_volatile) { return sema.fail(block, src, "volatile keyword is redundant on module-level assembly", .{}); } - try zcu.addGlobalAssembly(sema.owner.unwrap().cau, asm_source); + try zcu.addGlobalAssembly(sema.owner, asm_source); return .void_value; } @@ -18193,7 +18147,7 @@ fn zirThis( _ = extended; const pt = sema.pt; const namespace = pt.zcu.namespacePtr(block.namespace); - const new_ty = try pt.ensureTypeUpToDate(namespace.owner_type, false); + const new_ty = try pt.ensureTypeUpToDate(namespace.owner_type); switch (pt.zcu.intern_pool.indexToKey(new_ty)) { .struct_type, .union_type, .enum_type => try sema.declareDependency(.{ .interned = new_ty }), .opaque_type => {}, @@ -19359,13 +19313,11 @@ fn typeInfoNamespaceDecls( } for (namespace.pub_usingnamespace.items) |nav| { - if (ip.getNav(nav).analysis_owner.unwrap()) |cau| { - if (zcu.analysis_in_progress.contains(AnalUnit.wrap(.{ .cau = cau }))) { - continue; - } + if (zcu.analysis_in_progress.contains(.wrap(.{ .nav_val = nav }))) { + continue; } - try sema.ensureNavResolved(src, nav); - const namespace_ty = Type.fromInterned(ip.getNav(nav).status.resolved.val); + try sema.ensureNavResolved(src, nav, .fully); + const namespace_ty = Type.fromInterned(ip.getNav(nav).status.fully_resolved.val); try sema.typeInfoNamespaceDecls(block, src, namespace_ty.getNamespaceIndex(zcu).toOptional(), declaration_ty, decl_vals, seen_namespaces); } } @@ -21225,14 +21177,13 @@ fn structInitAnon( .file_scope = block.getFileScopeIndex(zcu), .generation = zcu.generation, }); - const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, new_namespace_index, wip.index); try zcu.comp.queueJob(.{ .resolve_type_fully = wip.index }); codegen_type: { if (zcu.comp.config.use_llvm) break :codegen_type; if (block.ownerModule().strip) break :codegen_type; try zcu.comp.queueJob(.{ .codegen_type = wip.index }); } - break :ty wip.finish(ip, new_cau_index.toOptional(), new_namespace_index); + break :ty wip.finish(ip, new_namespace_index); }, .existing => |ty| ty, }; @@ -21656,7 +21607,7 @@ fn getErrorReturnTrace(sema: *Sema, block: *Block) CompileError!Air.Inst.Ref { .func => |func| if (ip.funcAnalysisUnordered(func).calls_or_awaits_errorable_fn and block.ownerModule().error_tracing) { return block.addTy(.err_return_trace, opt_ptr_stack_trace_ty); }, - .cau => {}, + .@"comptime", .nav_ty, .nav_val, .type => {}, } return Air.internedToRef(try pt.intern(.{ .opt = .{ .ty = opt_ptr_stack_trace_ty.toIntern(), @@ -22334,7 +22285,7 @@ fn zirReify( }); try sema.addTypeReferenceEntry(src, wip_ty.index); - return Air.internedToRef(wip_ty.finish(ip, .none, new_namespace_index)); + return Air.internedToRef(wip_ty.finish(ip, new_namespace_index)); }, .@"union" => { const struct_type = ip.loadStructType(ip.typeOf(union_val.val)); @@ -22543,11 +22494,9 @@ fn reifyEnum( .generation = zcu.generation, }); - const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, new_namespace_index, wip_ty.index); - try sema.declareDependency(.{ .interned = wip_ty.index }); try sema.addTypeReferenceEntry(src, wip_ty.index); - wip_ty.prepare(ip, new_cau_index, new_namespace_index); + wip_ty.prepare(ip, new_namespace_index); wip_ty.setTagTy(ip, tag_ty.toIntern()); done = true; @@ -22849,8 +22798,6 @@ fn reifyUnion( .generation = zcu.generation, }); - const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, new_namespace_index, wip_ty.index); - try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); codegen_type: { if (zcu.comp.config.use_llvm) break :codegen_type; @@ -22860,7 +22807,7 @@ fn reifyUnion( } try sema.declareDependency(.{ .interned = wip_ty.index }); try sema.addTypeReferenceEntry(src, wip_ty.index); - return Air.internedToRef(wip_ty.finish(ip, new_cau_index.toOptional(), new_namespace_index)); + return Air.internedToRef(wip_ty.finish(ip, new_namespace_index)); } fn reifyTuple( @@ -23208,8 +23155,6 @@ fn reifyStruct( .generation = zcu.generation, }); - const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, new_namespace_index, wip_ty.index); - try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); codegen_type: { if (zcu.comp.config.use_llvm) break :codegen_type; @@ -23219,7 +23164,7 @@ fn reifyStruct( } try sema.declareDependency(.{ .interned = wip_ty.index }); try sema.addTypeReferenceEntry(src, wip_ty.index); - return Air.internedToRef(wip_ty.finish(ip, new_cau_index.toOptional(), new_namespace_index)); + return Air.internedToRef(wip_ty.finish(ip, new_namespace_index)); } fn resolveVaListRef(sema: *Sema, block: *Block, src: LazySrcLoc, zir_ref: Zir.Inst.Ref) CompileError!Air.Inst.Ref { @@ -26711,135 +26656,6 @@ fn zirAwaitNosuspend( return sema.failWithUseOfAsync(block, src); } -fn zirVarExtended( - sema: *Sema, - block: *Block, - extended: Zir.Inst.Extended.InstData, -) CompileError!Air.Inst.Ref { - const pt = sema.pt; - const zcu = pt.zcu; - const ip = &zcu.intern_pool; - const extra = sema.code.extraData(Zir.Inst.ExtendedVar, extended.operand); - const ty_src = block.src(.{ .node_offset_var_decl_ty = 0 }); - const init_src = block.src(.{ .node_offset_var_decl_init = 0 }); - const small: Zir.Inst.ExtendedVar.Small = @bitCast(extended.small); - - var extra_index: usize = extra.end; - - const lib_name = if (small.has_lib_name) lib_name: { - const lib_name_index: Zir.NullTerminatedString = @enumFromInt(sema.code.extra[extra_index]); - const lib_name = sema.code.nullTerminatedString(lib_name_index); - extra_index += 1; - try sema.handleExternLibName(block, ty_src, lib_name); - break :lib_name lib_name; - } else null; - - // ZIR supports encoding this information but it is not used; the information - // is encoded via the Decl entry. - assert(!small.has_align); - - const uncasted_init: Air.Inst.Ref = if (small.has_init) blk: { - const init_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]); - extra_index += 1; - break :blk try sema.resolveInst(init_ref); - } else .none; - - const have_ty = extra.data.var_type != .none; - const var_ty = if (have_ty) - try sema.resolveType(block, ty_src, extra.data.var_type) - else - sema.typeOf(uncasted_init); - - const init_val = if (uncasted_init != .none) blk: { - const init = if (have_ty) - try sema.coerce(block, var_ty, uncasted_init, init_src) - else - uncasted_init; - - break :blk ((try sema.resolveValue(init)) orelse { - return sema.failWithNeededComptime(block, init_src, .{ - .needed_comptime_reason = "container level variable initializers must be comptime-known", - }); - }).toIntern(); - } else .none; - - try sema.validateVarType(block, ty_src, var_ty, small.is_extern); - - if (small.is_extern) { - const extern_val = try sema.resolveExternDecl(block, var_ty, lib_name, small.is_const, small.is_threadlocal); - return Air.internedToRef(extern_val); - } - assert(!small.is_const); // non-const non-extern variable is not legal - return Air.internedToRef(try pt.intern(.{ .variable = .{ - .ty = var_ty.toIntern(), - .init = init_val, - .owner_nav = sema.getOwnerCauNav(), - .lib_name = try ip.getOrPutStringOpt(sema.gpa, pt.tid, lib_name, .no_embedded_nulls), - .is_threadlocal = small.is_threadlocal, - .is_weak_linkage = false, - } })); -} - -fn resolveExternDecl( - sema: *Sema, - block: *Block, - ty: Type, - opt_lib_name: ?[]const u8, - is_const: bool, - is_threadlocal: bool, -) CompileError!InternPool.Index { - const pt = sema.pt; - const zcu = pt.zcu; - const ip = &zcu.intern_pool; - - // We need to resolve the alignment and addrspace early. - // Keep in sync with logic in `Zcu.PerThread.semaCau`. - const align_src = block.src(.{ .node_offset_var_decl_align = 0 }); - const addrspace_src = block.src(.{ .node_offset_var_decl_addrspace = 0 }); - - const decl_inst, const decl_bodies = decl: { - const decl_inst = sema.getOwnerCauDeclInst().resolve(ip) orelse return error.AnalysisFail; - const zir_decl, const extra_end = sema.code.getDeclaration(decl_inst); - break :decl .{ decl_inst, zir_decl.getBodies(extra_end, sema.code) }; - }; - - const alignment: InternPool.Alignment = a: { - const align_body = decl_bodies.align_body orelse break :a .none; - const align_ref = try sema.resolveInlineBody(block, align_body, decl_inst); - break :a try sema.analyzeAsAlign(block, align_src, align_ref); - }; - - const @"addrspace": std.builtin.AddressSpace = as: { - const addrspace_ctx: Sema.AddressSpaceContext = switch (ip.indexToKey(ty.toIntern())) { - .func_type => .function, - else => .variable, - }; - const target = zcu.getTarget(); - const addrspace_body = decl_bodies.addrspace_body orelse break :as 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.resolveInlineBody(block, addrspace_body, decl_inst); - break :as try sema.analyzeAsAddressSpace(block, addrspace_src, addrspace_ref, addrspace_ctx); - }; - - return pt.getExtern(.{ - .name = sema.getOwnerCauNavName(), - .ty = ty.toIntern(), - .lib_name = try ip.getOrPutStringOpt(sema.gpa, pt.tid, opt_lib_name, .no_embedded_nulls), - .is_const = is_const, - .is_threadlocal = is_threadlocal, - .is_weak_linkage = false, - .is_dll_import = false, - .alignment = alignment, - .@"addrspace" = @"addrspace", - .zir_index = sema.getOwnerCauDeclInst(), // `declaration` instruction - .owner_nav = undefined, // ignored by `getExtern` - }); -} - fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); @@ -26857,13 +26673,6 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A var extra_index: usize = extra.end; - const lib_name: ?[]const u8 = if (extra.data.bits.has_lib_name) blk: { - const lib_name_index: Zir.NullTerminatedString = @enumFromInt(sema.code.extra[extra_index]); - const lib_name = sema.code.nullTerminatedString(lib_name_index); - extra_index += 1; - break :blk lib_name; - } else null; - const cc: std.builtin.CallingConvention = if (extra.data.bits.has_cc_body) blk: { const body_len = sema.code.extra[extra_index]; extra_index += 1; @@ -26887,16 +26696,14 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A break :blk try sema.analyzeValueAsCallconv(block, cc_src, cc_val); } else cc: { if (has_body) { - const decl_inst = if (sema.generic_owner != .none) decl_inst: { + const func_decl_nav = if (sema.generic_owner != .none) nav: { // Generic instance -- use the original function declaration to // look for the `export` syntax. - const nav = zcu.intern_pool.getNav(zcu.funcInfo(sema.generic_owner).owner_nav); - const cau = zcu.intern_pool.getCau(nav.analysis_owner.unwrap().?); - break :decl_inst cau.zir_index; - } else sema.getOwnerCauDeclInst(); // not an instantiation so we're analyzing a function declaration Cau - - const zir_decl = sema.code.getDeclaration(decl_inst.resolve(&zcu.intern_pool) orelse return error.AnalysisFail)[0]; - if (zir_decl.flags.is_export) { + break :nav zcu.funcInfo(sema.generic_owner).owner_nav; + } else sema.owner.unwrap().nav_val; + const func_decl_inst = ip.getNav(func_decl_nav).analysis.?.zir_index.resolve(&zcu.intern_pool) orelse return error.AnalysisFail; + const zir_decl = sema.code.getDeclaration(func_decl_inst); + if (zir_decl.linkage == .@"export") { break :cc target.cCallingConvention() orelse { // This target has no default C calling convention. We sometimes trigger a similar // error by trying to evaluate `std.builtin.CallingConvention.c`, so for consistency, @@ -26958,7 +26765,6 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A const is_var_args = extra.data.bits.is_var_args; const is_inferred_error = extra.data.bits.is_inferred_error; - const is_extern = extra.data.bits.is_extern; const is_noinline = extra.data.bits.is_noinline; return sema.funcCommon( @@ -26969,10 +26775,8 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A ret_ty, is_var_args, is_inferred_error, - is_extern, has_body, src_locs, - lib_name, noalias_bits, is_noinline, ); @@ -27285,8 +27089,16 @@ fn zirBuiltinExtern( // `builtin_extern` doesn't provide enough information, and isn't currently tracked. // So, for now, just use our containing `declaration`. .zir_index = switch (sema.owner.unwrap()) { - .cau => sema.getOwnerCauDeclInst(), - .func => sema.getOwnerFuncDeclInst(), + .@"comptime" => |cu| ip.getComptimeUnit(cu).zir_index, + .type => |owner_ty| Type.fromInterned(owner_ty).typeDeclInst(zcu).?, + .nav_ty, .nav_val => |nav| ip.getNav(nav).analysis.?.zir_index, + .func => |func| zir_index: { + const func_info = zcu.funcInfo(func); + const owner_func_info = if (func_info.generic_owner != .none) owner: { + break :owner zcu.funcInfo(func_info.generic_owner); + } else func_info; + break :zir_index ip.getNav(owner_func_info.owner_nav).analysis.?.zir_index; + }, }, .owner_nav = undefined, // ignored by `getExtern` }); @@ -27467,7 +27279,7 @@ fn requireRuntimeBlock(sema: *Sema, block: *Block, src: LazySrcLoc, runtime_src: } /// Emit a compile error if type cannot be used for a runtime variable. -fn validateVarType( +pub fn validateVarType( sema: *Sema, block: *Block, src: LazySrcLoc, @@ -27934,7 +27746,7 @@ fn preparePanicId(sema: *Sema, block: *Block, src: LazySrcLoc, panic_id: Zcu.Pan error.GenericPoison, error.ComptimeReturn, error.ComptimeBreak => unreachable, error.OutOfMemory => |e| return e, }).?; - try sema.ensureNavResolved(src, msg_nav_index); + try sema.ensureNavResolved(src, msg_nav_index, .fully); zcu.panic_messages[@intFromEnum(panic_id)] = msg_nav_index.toOptional(); return msg_nav_index; } @@ -29881,7 +29693,7 @@ fn elemPtrSlice( return block.addSliceElemPtr(slice, elem_index, elem_ptr_ty); } -fn coerce( +pub fn coerce( sema: *Sema, block: *Block, dest_ty_unresolved: Type, @@ -32841,34 +32653,45 @@ fn addTypeReferenceEntry( try zcu.addTypeReference(sema.owner, referenced_type, src); } -pub fn ensureNavResolved(sema: *Sema, src: LazySrcLoc, nav_index: InternPool.Nav.Index) CompileError!void { +pub fn ensureNavResolved(sema: *Sema, src: LazySrcLoc, nav_index: InternPool.Nav.Index, kind: enum { type, fully }) CompileError!void { const pt = sema.pt; const zcu = pt.zcu; const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); - - const cau_index = nav.analysis_owner.unwrap() orelse { - assert(nav.status == .resolved); + if (nav.analysis == null) { + assert(nav.status == .fully_resolved); return; - }; + } - // Note that even if `nav.status == .resolved`, we must still trigger `ensureCauAnalyzed` - // to make sure the value is up-to-date on incremental updates. + try sema.declareDependency(switch (kind) { + .type => .{ .nav_ty = nav_index }, + .fully => .{ .nav_val = nav_index }, + }); - assert(ip.getCau(cau_index).owner.unwrap().nav == nav_index); + // Note that even if `nav.status == .resolved`, we must still trigger `ensureNavValUpToDate` + // to make sure the value is up-to-date on incremental updates. - const anal_unit = AnalUnit.wrap(.{ .cau = cau_index }); + const anal_unit: AnalUnit = .wrap(switch (kind) { + .type => .{ .nav_ty = nav_index }, + .fully => .{ .nav_val = nav_index }, + }); try sema.addReferenceEntry(src, anal_unit); if (zcu.analysis_in_progress.contains(anal_unit)) { return sema.failWithOwnedErrorMsg(null, try sema.errMsg(.{ - .base_node_inst = ip.getCau(cau_index).zir_index, + .base_node_inst = nav.analysis.?.zir_index, .offset = LazySrcLoc.Offset.nodeOffset(0), }, "dependency loop detected", .{})); } - return pt.ensureCauAnalyzed(cau_index); + switch (kind) { + .type => { + try zcu.ensureNavValAnalysisQueued(nav_index); + return pt.ensureNavTypeUpToDate(nav_index); + }, + .fully => return pt.ensureNavValUpToDate(nav_index), + } } fn optRefValue(sema: *Sema, opt_val: ?Value) !Value { @@ -32887,36 +32710,44 @@ fn analyzeNavRef(sema: *Sema, src: LazySrcLoc, nav_index: InternPool.Nav.Index) return sema.analyzeNavRefInner(src, nav_index, true); } -/// Analyze a reference to the `Nav` at the given index. Ensures the underlying `Nav` is analyzed, but -/// only triggers analysis for function bodies if `analyze_fn_body` is true. If it's possible for a -/// decl_ref to end up in runtime code, the function body must be analyzed: `analyzeNavRef` wraps -/// this function with `analyze_fn_body` set to true. -fn analyzeNavRefInner(sema: *Sema, src: LazySrcLoc, orig_nav_index: InternPool.Nav.Index, analyze_fn_body: bool) CompileError!Air.Inst.Ref { +/// Analyze a reference to the `Nav` at the given index. Ensures the underlying `Nav` is analyzed. +/// If this pointer will be used directly, `is_ref` must be `true`. +/// If this pointer will be immediately loaded (i.e. a `decl_val` instruction), `is_ref` must be `false`. +fn analyzeNavRefInner(sema: *Sema, src: LazySrcLoc, orig_nav_index: InternPool.Nav.Index, is_ref: bool) CompileError!Air.Inst.Ref { const pt = sema.pt; const zcu = pt.zcu; const ip = &zcu.intern_pool; - // TODO: if this is a `decl_ref` of a non-variable Nav, only depend on Nav type - try sema.declareDependency(.{ .nav_val = orig_nav_index }); - try sema.ensureNavResolved(src, orig_nav_index); + try sema.ensureNavResolved(src, orig_nav_index, if (is_ref) .type else .fully); + + const nav_index = nav: { + if (ip.getNav(orig_nav_index).isExternOrFn(ip)) { + // Getting a pointer to this `Nav` might mean we actually get a pointer to something else! + // We need to resolve the value to know for sure. + if (is_ref) try sema.ensureNavResolved(src, orig_nav_index, .fully); + switch (ip.indexToKey(ip.getNav(orig_nav_index).status.fully_resolved.val)) { + .func => |f| break :nav f.owner_nav, + .@"extern" => |e| break :nav e.owner_nav, + else => {}, + } + } + break :nav orig_nav_index; + }; - const nav_val = zcu.navValue(orig_nav_index); - const nav_index, const is_const = switch (ip.indexToKey(nav_val.toIntern())) { - .variable => |v| .{ v.owner_nav, false }, - .func => |f| .{ f.owner_nav, true }, - .@"extern" => |e| .{ e.owner_nav, e.is_const }, - else => .{ orig_nav_index, true }, + const ty, const alignment, const @"addrspace", const is_const = switch (ip.getNav(nav_index).status) { + .unresolved => unreachable, + .type_resolved => |r| .{ r.type, r.alignment, r.@"addrspace", r.is_const }, + .fully_resolved => |r| .{ ip.typeOf(r.val), r.alignment, r.@"addrspace", zcu.navValIsConst(r.val) }, }; - const nav_info = ip.getNav(nav_index).status.resolved; const ptr_ty = try pt.ptrTypeSema(.{ - .child = nav_val.typeOf(zcu).toIntern(), + .child = ty, .flags = .{ - .alignment = nav_info.alignment, + .alignment = alignment, .is_const = is_const, - .address_space = nav_info.@"addrspace", + .address_space = @"addrspace", }, }); - if (analyze_fn_body) { + if (is_ref) { try sema.maybeQueueFuncBodyAnalysis(src, nav_index); } return Air.internedToRef((try pt.intern(.{ .ptr = .{ @@ -32927,11 +32758,22 @@ fn analyzeNavRefInner(sema: *Sema, src: LazySrcLoc, orig_nav_index: InternPool.N } fn maybeQueueFuncBodyAnalysis(sema: *Sema, src: LazySrcLoc, nav_index: InternPool.Nav.Index) !void { - const zcu = sema.pt.zcu; + const pt = sema.pt; + const zcu = pt.zcu; const ip = &zcu.intern_pool; + + // To avoid forcing too much resolution, let's first resolve the type, and check if it's a function. + // If it is, we can resolve the *value*, and queue analysis as needed. + + try sema.ensureNavResolved(src, nav_index, .type); + const nav_ty: Type = .fromInterned(ip.getNav(nav_index).typeOf(ip)); + if (nav_ty.zigTypeTag(zcu) != .@"fn") return; + if (!try nav_ty.fnHasRuntimeBitsSema(pt)) return; + + try sema.ensureNavResolved(src, nav_index, .fully); const nav_val = zcu.navValue(nav_index); if (!ip.isFuncBody(nav_val.toIntern())) return; - if (!try nav_val.typeOf(zcu).fnHasRuntimeBitsSema(sema.pt)) return; + try sema.addReferenceEntry(src, AnalUnit.wrap(.{ .func = nav_val.toIntern() })); try zcu.ensureFuncBodyAnalysisQueued(nav_val.toIntern()); } @@ -35818,7 +35660,7 @@ pub fn resolveStructAlignment( const ip = &zcu.intern_pool; const target = zcu.getTarget(); - assert(sema.owner.unwrap().cau == struct_type.cau); + assert(sema.owner.unwrap().type == ty); assert(struct_type.layout != .@"packed"); assert(struct_type.flagsUnordered(ip).alignment == .none); @@ -35861,7 +35703,7 @@ pub fn resolveStructLayout(sema: *Sema, ty: Type) SemaError!void { const ip = &zcu.intern_pool; const struct_type = zcu.typeToStruct(ty) orelse return; - assert(sema.owner.unwrap().cau == struct_type.cau); + assert(sema.owner.unwrap().type == ty.toIntern()); if (struct_type.haveLayout(ip)) return; @@ -36008,15 +35850,13 @@ fn backingIntType( const gpa = zcu.gpa; const ip = &zcu.intern_pool; - const cau_index = struct_type.cau; - var analysis_arena = std.heap.ArenaAllocator.init(gpa); defer analysis_arena.deinit(); var block: Block = .{ .parent = null, .sema = sema, - .namespace = ip.getCau(cau_index).namespace, + .namespace = struct_type.namespace, .instructions = .{}, .inlining = null, .is_comptime = true, @@ -36148,7 +35988,7 @@ pub fn resolveUnionAlignment( const ip = &zcu.intern_pool; const target = zcu.getTarget(); - assert(sema.owner.unwrap().cau == union_type.cau); + assert(sema.owner.unwrap().type == ty.toIntern()); assert(!union_type.haveLayout(ip)); @@ -36188,7 +36028,7 @@ pub fn resolveUnionLayout(sema: *Sema, ty: Type) SemaError!void { // Load again, since the tag type might have changed due to resolution. const union_type = ip.loadUnionType(ty.ip_index); - assert(sema.owner.unwrap().cau == union_type.cau); + assert(sema.owner.unwrap().type == ty.toIntern()); const old_flags = union_type.flagsUnordered(ip); switch (old_flags.status) { @@ -36303,7 +36143,7 @@ pub fn resolveStructFully(sema: *Sema, ty: Type) SemaError!void { const ip = &zcu.intern_pool; const struct_type = zcu.typeToStruct(ty).?; - assert(sema.owner.unwrap().cau == struct_type.cau); + assert(sema.owner.unwrap().type == ty.toIntern()); if (struct_type.setFullyResolved(ip)) return; errdefer struct_type.clearFullyResolved(ip); @@ -36326,7 +36166,7 @@ pub fn resolveUnionFully(sema: *Sema, ty: Type) SemaError!void { const ip = &zcu.intern_pool; const union_obj = zcu.typeToUnion(ty).?; - assert(sema.owner.unwrap().cau == union_obj.cau); + assert(sema.owner.unwrap().type == ty.toIntern()); switch (union_obj.flagsUnordered(ip).status) { .none, .have_field_types, .field_types_wip, .layout_wip, .have_layout => {}, @@ -36361,7 +36201,7 @@ pub fn resolveStructFieldTypes( const zcu = pt.zcu; const ip = &zcu.intern_pool; - assert(sema.owner.unwrap().cau == struct_type.cau); + assert(sema.owner.unwrap().type == ty); if (struct_type.haveFieldTypes(ip)) return; @@ -36387,7 +36227,7 @@ pub fn resolveStructFieldInits(sema: *Sema, ty: Type) SemaError!void { const ip = &zcu.intern_pool; const struct_type = zcu.typeToStruct(ty) orelse return; - assert(sema.owner.unwrap().cau == struct_type.cau); + assert(sema.owner.unwrap().type == ty.toIntern()); // Inits can start as resolved if (struct_type.haveFieldInits(ip)) return; @@ -36416,7 +36256,7 @@ pub fn resolveUnionFieldTypes(sema: *Sema, ty: Type, union_type: InternPool.Load const zcu = pt.zcu; const ip = &zcu.intern_pool; - assert(sema.owner.unwrap().cau == union_type.cau); + assert(sema.owner.unwrap().type == ty.toIntern()); switch (union_type.flagsUnordered(ip).status) { .none => {}, @@ -36492,7 +36332,7 @@ fn resolveInferredErrorSet( // In this case we are dealing with the actual InferredErrorSet object that // corresponds to the function, not one created to track an inline/comptime call. try sema.addReferenceEntry(src, AnalUnit.wrap(.{ .func = func_index })); - try pt.ensureFuncBodyAnalyzed(func_index); + try pt.ensureFuncBodyUpToDate(func_index); } // This will now have been resolved by the logic at the end of `Zcu.analyzeFnBody` @@ -36649,8 +36489,7 @@ fn structFields( const zcu = pt.zcu; const gpa = zcu.gpa; const ip = &zcu.intern_pool; - const cau_index = struct_type.cau; - const namespace_index = ip.getCau(cau_index).namespace; + const namespace_index = struct_type.namespace; const zir = zcu.namespacePtr(namespace_index).fileScope(zcu).zir; const zir_index = struct_type.zir_index.resolve(ip) orelse return error.AnalysisFail; @@ -36848,8 +36687,7 @@ fn structFieldInits( assert(!struct_type.haveFieldInits(ip)); - const cau_index = struct_type.cau; - const namespace_index = ip.getCau(cau_index).namespace; + const namespace_index = struct_type.namespace; const zir = zcu.namespacePtr(namespace_index).fileScope(zcu).zir; const zir_index = struct_type.zir_index.resolve(ip) orelse return error.AnalysisFail; const fields_len, _, var extra_index = structZirInfo(zir, zir_index); @@ -38650,14 +38488,17 @@ pub fn declareDependency(sema: *Sema, dependee: InternPool.Dependee) !void { // of a type and they use `@This()`. This dependency would be unnecessary, and in fact would // just result in over-analysis since `Zcu.findOutdatedToAnalyze` would never be able to resolve // the loop. + // Note that this also disallows a `nav_val` switch (sema.owner.unwrap()) { - .cau => |cau| switch (dependee) { - .nav_val => |nav| if (zcu.intern_pool.getNav(nav).analysis_owner == cau.toOptional()) { - return; - }, + .nav_val => |this_nav| switch (dependee) { + .nav_val => |other_nav| if (this_nav == other_nav) return, else => {}, }, - .func => {}, + .nav_ty => |this_nav| switch (dependee) { + .nav_ty => |other_nav| if (this_nav == other_nav) return, + else => {}, + }, + else => {}, } try zcu.intern_pool.addDependency(sema.gpa, sema.owner, dependee); @@ -38836,45 +38677,6 @@ pub fn flushExports(sema: *Sema) !void { } } -/// Given that this `Sema` is owned by the `Cau` of a `declaration`, fetches -/// the corresponding `Nav`. -fn getOwnerCauNav(sema: *Sema) InternPool.Nav.Index { - const cau = sema.owner.unwrap().cau; - return sema.pt.zcu.intern_pool.getCau(cau).owner.unwrap().nav; -} - -/// Given that this `Sema` is owned by the `Cau` of a `declaration`, fetches -/// the declaration name from its corresponding `Nav`. -fn getOwnerCauNavName(sema: *Sema) InternPool.NullTerminatedString { - const nav = sema.getOwnerCauNav(); - return sema.pt.zcu.intern_pool.getNav(nav).name; -} - -/// Given that this `Sema` is owned by the `Cau` of a `declaration`, fetches -/// the `TrackedInst` corresponding to this `declaration` instruction. -fn getOwnerCauDeclInst(sema: *Sema) InternPool.TrackedInst.Index { - const ip = &sema.pt.zcu.intern_pool; - const cau = ip.getCau(sema.owner.unwrap().cau); - assert(cau.owner.unwrap() == .nav); - return cau.zir_index; -} - -/// Given that this `Sema` is owned by a runtime function, fetches the -/// `TrackedInst` corresponding to its `declaration` instruction. -fn getOwnerFuncDeclInst(sema: *Sema) InternPool.TrackedInst.Index { - const zcu = sema.pt.zcu; - const ip = &zcu.intern_pool; - const func = sema.owner.unwrap().func; - const func_info = zcu.funcInfo(func); - const cau = if (func_info.generic_owner == .none) cau: { - break :cau ip.getNav(func_info.owner_nav).analysis_owner.unwrap().?; - } else cau: { - const generic_owner = zcu.funcInfo(func_info.generic_owner); - break :cau ip.getNav(generic_owner.owner_nav).analysis_owner.unwrap().?; - }; - return ip.getCau(cau).zir_index; -} - /// Called as soon as a `declared` enum type is created. /// Resolves the tag type and field inits. /// Marks the `src_inst` dependency on the enum's declaration, so call sites need not do this. @@ -38885,7 +38687,6 @@ pub fn resolveDeclaredEnum( tracked_inst: InternPool.TrackedInst.Index, namespace: InternPool.NamespaceIndex, type_name: InternPool.NullTerminatedString, - enum_cau: InternPool.Cau.Index, small: Zir.Inst.EnumDecl.Small, body: []const Zir.Inst.Index, tag_type_ref: Zir.Inst.Ref, @@ -38903,7 +38704,7 @@ pub fn resolveDeclaredEnum( const src: LazySrcLoc = .{ .base_node_inst = tracked_inst, .offset = LazySrcLoc.Offset.nodeOffset(0) }; const tag_ty_src: LazySrcLoc = .{ .base_node_inst = tracked_inst, .offset = .{ .node_offset_container_tag = 0 } }; - const anal_unit = AnalUnit.wrap(.{ .cau = enum_cau }); + const anal_unit = AnalUnit.wrap(.{ .type = wip_ty.index }); var arena = std.heap.ArenaAllocator.init(gpa); defer arena.deinit(); @@ -39115,8 +38916,8 @@ fn getBuiltinInnerType( const nav = opt_nav orelse return sema.fail(block, src, "std.builtin.{s} missing {s}", .{ compile_error_parent_name, inner_name, }); - try sema.ensureNavResolved(src, nav); - const val = Value.fromInterned(ip.getNav(nav).status.resolved.val); + try sema.ensureNavResolved(src, nav, .fully); + const val = Value.fromInterned(ip.getNav(nav).status.fully_resolved.val); const ty = val.toType(); try ty.resolveFully(pt); return ty; @@ -39127,6 +38928,74 @@ fn getBuiltin(sema: *Sema, name: []const u8) SemaError!Air.Inst.Ref { const zcu = pt.zcu; const ip = &zcu.intern_pool; const nav = try pt.getBuiltinNav(name); - try pt.ensureCauAnalyzed(ip.getNav(nav).analysis_owner.unwrap().?); - return Air.internedToRef(ip.getNav(nav).status.resolved.val); + try pt.ensureNavValUpToDate(nav); + return Air.internedToRef(ip.getNav(nav).status.fully_resolved.val); +} + +pub const NavPtrModifiers = struct { + alignment: Alignment, + @"linksection": InternPool.OptionalNullTerminatedString, + @"addrspace": std.builtin.AddressSpace, +}; + +pub fn resolveNavPtrModifiers( + sema: *Sema, + block: *Block, + zir_decl: Zir.Inst.Declaration.Unwrapped, + decl_inst: Zir.Inst.Index, + nav_ty: Type, +) CompileError!NavPtrModifiers { + const pt = sema.pt; + const zcu = pt.zcu; + const gpa = zcu.gpa; + const ip = &zcu.intern_pool; + + const align_src = block.src(.{ .node_offset_var_decl_align = 0 }); + const section_src = block.src(.{ .node_offset_var_decl_section = 0 }); + const addrspace_src = block.src(.{ .node_offset_var_decl_addrspace = 0 }); + + const alignment: InternPool.Alignment = a: { + const align_body = zir_decl.align_body orelse break :a .none; + const align_ref = try sema.resolveInlineBody(block, align_body, decl_inst); + break :a try sema.analyzeAsAlign(block, align_src, align_ref); + }; + + const @"linksection": InternPool.OptionalNullTerminatedString = ls: { + const linksection_body = zir_decl.linksection_body orelse break :ls .none; + const linksection_ref = try sema.resolveInlineBody(block, linksection_body, decl_inst); + const bytes = try sema.toConstString(block, section_src, linksection_ref, .{ + .needed_comptime_reason = "linksection must be comptime-known", + }); + if (std.mem.indexOfScalar(u8, bytes, 0) != null) { + return sema.fail(block, section_src, "linksection cannot contain null bytes", .{}); + } else if (bytes.len == 0) { + return sema.fail(block, section_src, "linksection cannot be empty", .{}); + } + break :ls try ip.getOrPutStringOpt(gpa, pt.tid, bytes, .no_embedded_nulls); + }; + + const @"addrspace": std.builtin.AddressSpace = as: { + const addrspace_ctx: Sema.AddressSpaceContext = switch (zir_decl.kind) { + .@"var" => .variable, + else => switch (nav_ty.zigTypeTag(zcu)) { + .@"fn" => .function, + else => .constant, + }, + }; + const target = zcu.getTarget(); + const addrspace_body = zir_decl.addrspace_body orelse break :as 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.resolveInlineBody(block, addrspace_body, decl_inst); + break :as try sema.analyzeAsAddressSpace(block, addrspace_src, addrspace_ref, addrspace_ctx); + }; + + return .{ + .alignment = alignment, + .@"linksection" = @"linksection", + .@"addrspace" = @"addrspace", + }; } diff --git a/src/Sema/comptime_ptr_access.zig b/src/Sema/comptime_ptr_access.zig index 10e81d7a9e..ceddb9457d 100644 --- a/src/Sema/comptime_ptr_access.zig +++ b/src/Sema/comptime_ptr_access.zig @@ -219,9 +219,8 @@ fn loadComptimePtrInner( const base_val: MutableValue = switch (ptr.base_addr) { .nav => |nav| val: { - try sema.declareDependency(.{ .nav_val = nav }); - try sema.ensureNavResolved(src, nav); - const val = ip.getNav(nav).status.resolved.val; + try sema.ensureNavResolved(src, nav, .fully); + const val = ip.getNav(nav).status.fully_resolved.val; switch (ip.indexToKey(val)) { .variable => return .runtime_load, // We let `.@"extern"` through here if it's a function. diff --git a/src/Type.zig b/src/Type.zig index 850e50c82e..b47a45d1fc 100644 --- a/src/Type.zig +++ b/src/Type.zig @@ -3851,7 +3851,7 @@ fn resolveStructInner( const gpa = zcu.gpa; const struct_obj = zcu.typeToStruct(ty).?; - const owner = InternPool.AnalUnit.wrap(.{ .cau = struct_obj.cau }); + const owner: InternPool.AnalUnit = .wrap(.{ .type = ty.toIntern() }); if (zcu.failed_analysis.contains(owner) or zcu.transitive_failed_analysis.contains(owner)) { return error.AnalysisFail; @@ -3905,7 +3905,7 @@ fn resolveUnionInner( const gpa = zcu.gpa; const union_obj = zcu.typeToUnion(ty).?; - const owner = InternPool.AnalUnit.wrap(.{ .cau = union_obj.cau }); + const owner: InternPool.AnalUnit = .wrap(.{ .type = ty.toIntern() }); if (zcu.failed_analysis.contains(owner) or zcu.transitive_failed_analysis.contains(owner)) { return error.AnalysisFail; diff --git a/src/Value.zig b/src/Value.zig index 59fbdf67d5..25f5b50166 100644 --- a/src/Value.zig +++ b/src/Value.zig @@ -1343,7 +1343,12 @@ pub fn isLazySize(val: Value, zcu: *Zcu) bool { pub fn isPtrRuntimeValue(val: Value, zcu: *Zcu) bool { const ip = &zcu.intern_pool; const nav = ip.getBackingNav(val.toIntern()).unwrap() orelse return false; - return switch (ip.indexToKey(ip.getNav(nav).status.resolved.val)) { + const nav_val = switch (ip.getNav(nav).status) { + .unresolved => unreachable, + .type_resolved => |r| return r.is_threadlocal, + .fully_resolved => |r| r.val, + }; + return switch (ip.indexToKey(nav_val)) { .@"extern" => |e| e.is_threadlocal or e.is_dll_import, .variable => |v| v.is_threadlocal, else => false, diff --git a/src/Zcu.zig b/src/Zcu.zig index 771bfdcf61..8b3125039a 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -170,6 +170,9 @@ outdated_ready: std.AutoArrayHashMapUnmanaged(AnalUnit, void) = .empty, /// it as outdated. retryable_failures: std.ArrayListUnmanaged(AnalUnit) = .empty, +func_body_analysis_queued: std.AutoArrayHashMapUnmanaged(InternPool.Index, void) = .empty, +nav_val_analysis_queued: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, void) = .empty, + /// These are the modules which we initially queue for analysis in `Compilation.update`. /// `resolveReferences` will use these as the root of its reachability traversal. analysis_roots: std.BoundedArray(*Package.Module, 3) = .{}, @@ -192,7 +195,7 @@ compile_log_text: std.ArrayListUnmanaged(u8) = .empty, test_functions: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, void) = .empty, -global_assembly: std.AutoArrayHashMapUnmanaged(InternPool.Cau.Index, []u8) = .empty, +global_assembly: std.AutoArrayHashMapUnmanaged(AnalUnit, []u8) = .empty, /// Key is the `AnalUnit` *performing* the reference. This representation allows /// incremental updates to quickly delete references caused by a specific `AnalUnit`. @@ -282,7 +285,11 @@ pub const Exported = union(enum) { pub fn getAlign(exported: Exported, zcu: *Zcu) Alignment { return switch (exported) { - .nav => |nav| zcu.intern_pool.getNav(nav).status.resolved.alignment, + .nav => |nav| switch (zcu.intern_pool.getNav(nav).status) { + .unresolved => unreachable, + .type_resolved => |r| r.alignment, + .fully_resolved => |r| r.alignment, + }, .uav => .none, }; } @@ -344,9 +351,12 @@ pub const Namespace = struct { pub_usingnamespace: std.ArrayListUnmanaged(InternPool.Nav.Index) = .empty, /// All `usingnamespace` declarations in this namespace which are *not* marked `pub`. priv_usingnamespace: std.ArrayListUnmanaged(InternPool.Nav.Index) = .empty, - /// All `comptime` and `test` declarations in this namespace. We store these purely so that - /// incremental compilation can re-use the existing `Cau`s when a namespace changes. - other_decls: std.ArrayListUnmanaged(InternPool.Cau.Index) = .empty, + /// All `comptime` declarations in this namespace. We store these purely so that incremental + /// compilation can re-use the existing `ComptimeUnit`s when a namespace changes. + comptime_decls: std.ArrayListUnmanaged(InternPool.ComptimeUnit.Id) = .empty, + /// All `test` declarations in this namespace. We store these purely so that incremental + /// compilation can re-use the existing `Nav`s when a namespace changes. + test_decls: std.ArrayListUnmanaged(InternPool.Nav.Index) = .empty, pub const Index = InternPool.NamespaceIndex; pub const OptionalIndex = InternPool.OptionalNamespaceIndex; @@ -2238,6 +2248,9 @@ pub fn deinit(zcu: *Zcu) void { zcu.outdated_ready.deinit(gpa); zcu.retryable_failures.deinit(gpa); + zcu.func_body_analysis_queued.deinit(gpa); + zcu.nav_val_analysis_queued.deinit(gpa); + zcu.test_functions.deinit(gpa); for (zcu.global_assembly.values()) |s| { @@ -2436,11 +2449,10 @@ pub fn markPoDependeeUpToDate(zcu: *Zcu, dependee: InternPool.Dependee) !void { // If this is a Decl, we must recursively mark dependencies on its tyval // as no longer PO. switch (depender.unwrap()) { - .cau => |cau| switch (zcu.intern_pool.getCau(cau).owner.unwrap()) { - .nav => |nav| try zcu.markPoDependeeUpToDate(.{ .nav_val = nav }), - .type => |ty| try zcu.markPoDependeeUpToDate(.{ .interned = ty }), - .none => {}, - }, + .@"comptime" => {}, + .nav_val => |nav| try zcu.markPoDependeeUpToDate(.{ .nav_val = nav }), + .nav_ty => |nav| try zcu.markPoDependeeUpToDate(.{ .nav_ty = nav }), + .type => |ty| try zcu.markPoDependeeUpToDate(.{ .interned = ty }), .func => |func| try zcu.markPoDependeeUpToDate(.{ .interned = func }), } } @@ -2451,11 +2463,10 @@ pub fn markPoDependeeUpToDate(zcu: *Zcu, dependee: InternPool.Dependee) !void { fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: AnalUnit) !void { const ip = &zcu.intern_pool; const dependee: InternPool.Dependee = switch (maybe_outdated.unwrap()) { - .cau => |cau| switch (ip.getCau(cau).owner.unwrap()) { - .nav => |nav| .{ .nav_val = nav }, // TODO: also `nav_ref` deps when introduced - .type => |ty| .{ .interned = ty }, - .none => return, // analysis of this `Cau` can't outdate any dependencies - }, + .@"comptime" => return, // analysis of a comptime decl can't outdate any dependencies + .nav_val => |nav| .{ .nav_val = nav }, + .nav_ty => |nav| .{ .nav_ty = nav }, + .type => |ty| .{ .interned = ty }, .func => |func_index| .{ .interned = func_index }, // IES }; log.debug("potentially outdated dependee: {}", .{zcu.fmtDependee(dependee)}); @@ -2512,14 +2523,14 @@ pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?AnalUnit { } // There is no single AnalUnit which is ready for re-analysis. Instead, we must assume that some - // Cau with PO dependencies is outdated -- e.g. in the above example we arbitrarily pick one of - // A or B. We should select a Cau, since a Cau is definitely responsible for the loop in the - // dependency graph (since IES dependencies can't have loops). We should also, of course, not - // select a Cau owned by a `comptime` declaration, since you can't depend on those! + // AnalUnit with PO dependencies is outdated -- e.g. in the above example we arbitrarily pick one of + // A or B. We should definitely not select a function, since a function can't be responsible for the + // loop (IES dependencies can't have loops). We should also, of course, not select a `comptime` + // declaration, since you can't depend on those! - // The choice of this Cau could have a big impact on how much total analysis we perform, since + // The choice of this unit could have a big impact on how much total analysis we perform, since // if analysis concludes any dependencies on its result are up-to-date, then other PO AnalUnit - // may be resolved as up-to-date. To hopefully avoid doing too much work, let's find a Decl + // may be resolved as up-to-date. To hopefully avoid doing too much work, let's find a unit // which the most things depend on - the idea is that this will resolve a lot of loops (but this // is only a heuristic). @@ -2530,33 +2541,29 @@ pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?AnalUnit { const ip = &zcu.intern_pool; - var chosen_cau: ?InternPool.Cau.Index = null; - var chosen_cau_dependers: u32 = undefined; + var chosen_unit: ?AnalUnit = null; + var chosen_unit_dependers: u32 = undefined; inline for (.{ zcu.outdated.keys(), zcu.potentially_outdated.keys() }) |outdated_units| { for (outdated_units) |unit| { - const cau = switch (unit.unwrap()) { - .cau => |cau| cau, - .func => continue, // a `func` definitely can't be causing the loop so it is a bad choice - }; - const cau_owner = ip.getCau(cau).owner; - var n: u32 = 0; - var it = ip.dependencyIterator(switch (cau_owner.unwrap()) { - .none => continue, // there can be no dependencies on this `Cau` so it is a terrible choice + var it = ip.dependencyIterator(switch (unit.unwrap()) { + .func => continue, // a `func` definitely can't be causing the loop so it is a bad choice + .@"comptime" => continue, // a `comptime` block can't even be depended on so it is a terrible choice .type => |ty| .{ .interned = ty }, - .nav => |nav| .{ .nav_val = nav }, + .nav_val => |nav| .{ .nav_val = nav }, + .nav_ty => |nav| .{ .nav_ty = nav }, }); while (it.next()) |_| n += 1; - if (chosen_cau == null or n > chosen_cau_dependers) { - chosen_cau = cau; - chosen_cau_dependers = n; + if (chosen_unit == null or n > chosen_unit_dependers) { + chosen_unit = unit; + chosen_unit_dependers = n; } } } - if (chosen_cau == null) { + if (chosen_unit == null) { for (zcu.outdated.keys(), zcu.outdated.values()) |o, opod| { const func = o.unwrap().func; const nav = zcu.funcInfo(func).owner_nav; @@ -2570,11 +2577,11 @@ pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?AnalUnit { } log.debug("findOutdatedToAnalyze: heuristic returned '{}' ({d} dependers)", .{ - zcu.fmtAnalUnit(AnalUnit.wrap(.{ .cau = chosen_cau.? })), - chosen_cau_dependers, + zcu.fmtAnalUnit(chosen_unit.?), + chosen_unit_dependers, }); - return AnalUnit.wrap(.{ .cau = chosen_cau.? }); + return chosen_unit.?; } /// During an incremental update, before semantic analysis, call this to flush all values from @@ -2679,24 +2686,14 @@ pub fn mapOldZirToNew( { var old_decl_it = old_zir.declIterator(match_item.old_inst); while (old_decl_it.next()) |old_decl_inst| { - const old_decl, _ = old_zir.getDeclaration(old_decl_inst); - switch (old_decl.name) { + const old_decl = old_zir.getDeclaration(old_decl_inst); + switch (old_decl.kind) { .@"comptime" => try comptime_decls.append(gpa, old_decl_inst), .@"usingnamespace" => try usingnamespace_decls.append(gpa, old_decl_inst), .unnamed_test => 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)) { - if (old_decl.flags.test_is_decltest) { - try named_decltests.put(gpa, name, old_decl_inst); - } else { - try named_tests.put(gpa, name, old_decl_inst); - } - } else { - try named_decls.put(gpa, name, old_decl_inst); - } - }, + .@"test" => try named_tests.put(gpa, old_zir.nullTerminatedString(old_decl.name), old_decl_inst), + .decltest => try named_decltests.put(gpa, old_zir.nullTerminatedString(old_decl.name), old_decl_inst), + .@"const", .@"var" => try named_decls.put(gpa, old_zir.nullTerminatedString(old_decl.name), old_decl_inst), } } } @@ -2707,7 +2704,7 @@ pub fn mapOldZirToNew( 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); + 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"`) and decltests (`test foo`), we also match based on name. @@ -2715,7 +2712,7 @@ pub fn mapOldZirToNew( // * 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) { + const old_decl_inst = switch (new_decl.kind) { .@"comptime" => inst: { if (comptime_decl_idx == comptime_decls.items.len) continue; defer comptime_decl_idx += 1; @@ -2731,18 +2728,17 @@ pub fn mapOldZirToNew( defer unnamed_test_idx += 1; break :inst unnamed_tests.items[unnamed_test_idx]; }, - _ => inst: { - const name_nts = new_decl.name.toString(new_zir).?; - const name = new_zir.nullTerminatedString(name_nts); - if (new_decl.name.isNamedTest(new_zir)) { - if (new_decl.flags.test_is_decltest) { - break :inst named_decltests.get(name) orelse continue; - } else { - break :inst named_tests.get(name) orelse continue; - } - } else { - break :inst named_decls.get(name) orelse continue; - } + .@"test" => inst: { + const name = new_zir.nullTerminatedString(new_decl.name); + break :inst named_tests.get(name) orelse continue; + }, + .decltest => inst: { + const name = new_zir.nullTerminatedString(new_decl.name); + break :inst named_decltests.get(name) orelse continue; + }, + .@"const", .@"var" => inst: { + const name = new_zir.nullTerminatedString(new_decl.name); + break :inst named_decls.get(name) orelse continue; }, }; @@ -2797,14 +2793,39 @@ pub fn ensureFuncBodyAnalysisQueued(zcu: *Zcu, func_index: InternPool.Index) !vo const ip = &zcu.intern_pool; const func = zcu.funcInfo(func_index); - switch (func.analysisUnordered(ip).state) { - .unreferenced => {}, // We're the first reference! - .queued => return, // Analysis is already queued. - .analyzed => return, // Analysis is complete; if it's out-of-date, it'll be re-analyzed later this update. + if (zcu.func_body_analysis_queued.contains(func_index)) return; + + if (func.analysisUnordered(ip).is_analyzed) { + if (!zcu.outdated.contains(.wrap(.{ .func = func_index })) and + !zcu.potentially_outdated.contains(.wrap(.{ .func = func_index }))) + { + // This function has been analyzed before and is definitely up-to-date. + return; + } } + try zcu.func_body_analysis_queued.ensureUnusedCapacity(zcu.gpa, 1); try zcu.comp.queueJob(.{ .analyze_func = func_index }); - func.setAnalysisState(ip, .queued); + zcu.func_body_analysis_queued.putAssumeCapacityNoClobber(func_index, {}); +} + +pub fn ensureNavValAnalysisQueued(zcu: *Zcu, nav_id: InternPool.Nav.Index) !void { + const ip = &zcu.intern_pool; + + if (zcu.nav_val_analysis_queued.contains(nav_id)) return; + + if (ip.getNav(nav_id).status == .fully_resolved) { + if (!zcu.outdated.contains(.wrap(.{ .nav_val = nav_id })) and + !zcu.potentially_outdated.contains(.wrap(.{ .nav_val = nav_id }))) + { + // This `Nav` has been analyzed before and is definitely up-to-date. + return; + } + } + + try zcu.nav_val_analysis_queued.ensureUnusedCapacity(zcu.gpa, 1); + try zcu.comp.queueJob(.{ .analyze_comptime_unit = .wrap(.{ .nav_val = nav_id }) }); + zcu.nav_val_analysis_queued.putAssumeCapacityNoClobber(nav_id, {}); } pub const ImportFileResult = struct { @@ -3030,9 +3051,9 @@ pub fn handleUpdateExports( }; } -pub fn addGlobalAssembly(zcu: *Zcu, cau: InternPool.Cau.Index, source: []const u8) !void { +pub fn addGlobalAssembly(zcu: *Zcu, unit: AnalUnit, source: []const u8) !void { const gpa = zcu.gpa; - const gop = try zcu.global_assembly.getOrPut(gpa, cau); + const gop = try zcu.global_assembly.getOrPut(gpa, unit); if (gop.found_existing) { const new_value = try std.fmt.allocPrint(gpa, "{s}\n{s}", .{ gop.value_ptr.*, source }); gpa.free(gop.value_ptr.*); @@ -3315,23 +3336,22 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv log.debug("handle type '{}'", .{Type.fromInterned(ty).containerTypeName(ip).fmt(ip)}); - // If this type has a `Cau` for resolution, it's automatically referenced. - const resolution_cau: InternPool.Cau.Index.Optional = switch (ip.indexToKey(ty)) { - .struct_type => ip.loadStructType(ty).cau.toOptional(), - .union_type => ip.loadUnionType(ty).cau.toOptional(), - .enum_type => ip.loadEnumType(ty).cau, - .opaque_type => .none, + // If this type undergoes type resolution, the corresponding `AnalUnit` is automatically referenced. + const has_resolution: bool = switch (ip.indexToKey(ty)) { + .struct_type, .union_type => true, + .enum_type => |k| k != .generated_tag, + .opaque_type => false, else => unreachable, }; - if (resolution_cau.unwrap()) |cau| { + if (has_resolution) { // this should only be referenced by the type - const unit = AnalUnit.wrap(.{ .cau = cau }); + const unit: AnalUnit = .wrap(.{ .type = ty }); assert(!result.contains(unit)); try unit_queue.putNoClobber(gpa, unit, referencer); } // If this is a union with a generated tag, its tag type is automatically referenced. - // We don't add this reference for non-generated tags, as those will already be referenced via the union's `Cau`, with a better source location. + // We don't add this reference for non-generated tags, as those will already be referenced via the union's type resolution, with a better source location. if (zcu.typeToUnion(Type.fromInterned(ty))) |union_obj| { const tag_ty = union_obj.enum_tag_ty; if (tag_ty != .none) { @@ -3346,53 +3366,61 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv // Queue any decls within this type which would be automatically analyzed. // Keep in sync with analysis queueing logic in `Zcu.PerThread.ScanDeclIter.scanDecl`. const ns = Type.fromInterned(ty).getNamespace(zcu).unwrap().?; - for (zcu.namespacePtr(ns).other_decls.items) |cau| { - // These are `comptime` and `test` declarations. - // `comptime` decls are always analyzed; `test` declarations are analyzed depending on the test filter. - const inst_info = ip.getCau(cau).zir_index.resolveFull(ip) orelse continue; + for (zcu.namespacePtr(ns).comptime_decls.items) |cu| { + // `comptime` decls are always analyzed. + const unit: AnalUnit = .wrap(.{ .@"comptime" = cu }); + if (!result.contains(unit)) { + log.debug("type '{}': ref comptime %{}", .{ + Type.fromInterned(ty).containerTypeName(ip).fmt(ip), + @intFromEnum(ip.getComptimeUnit(cu).zir_index.resolve(ip) orelse continue), + }); + try unit_queue.put(gpa, unit, referencer); + } + } + for (zcu.namespacePtr(ns).test_decls.items) |nav_id| { + const nav = ip.getNav(nav_id); + // `test` declarations are analyzed depending on the test filter. + const inst_info = nav.analysis.?.zir_index.resolveFull(ip) orelse continue; const file = zcu.fileByIndex(inst_info.file); // If the file failed AstGen, the TrackedInst refers to the old ZIR. const zir = if (file.status == .success_zir) file.zir else file.prev_zir.?.*; - const declaration = zir.getDeclaration(inst_info.inst)[0]; - const want_analysis = switch (declaration.name) { + const decl = zir.getDeclaration(inst_info.inst); + + if (!comp.config.is_test or file.mod != zcu.main_mod) continue; + + const want_analysis = switch (decl.kind) { .@"usingnamespace" => unreachable, - .@"comptime" => true, - else => a: { - if (!comp.config.is_test) break :a false; - if (file.mod != zcu.main_mod) break :a false; - if (declaration.name.isNamedTest(zir)) { - const nav = ip.getCau(cau).owner.unwrap().nav; - const fqn_slice = ip.getNav(nav).fqn.toSlice(ip); - for (comp.test_filters) |test_filter| { - if (std.mem.indexOf(u8, fqn_slice, test_filter) != null) break; - } else break :a false; - } + .@"const", .@"var" => unreachable, + .@"comptime" => unreachable, + .unnamed_test => true, + .@"test", .decltest => a: { + const fqn_slice = nav.fqn.toSlice(ip); + for (comp.test_filters) |test_filter| { + if (std.mem.indexOf(u8, fqn_slice, test_filter) != null) break; + } else break :a false; break :a true; }, }; if (want_analysis) { - const unit = AnalUnit.wrap(.{ .cau = cau }); - if (!result.contains(unit)) { - log.debug("type '{}': ref cau %{}", .{ - Type.fromInterned(ty).containerTypeName(ip).fmt(ip), - @intFromEnum(inst_info.inst), - }); - try unit_queue.put(gpa, unit, referencer); - } + log.debug("type '{}': ref test %{}", .{ + Type.fromInterned(ty).containerTypeName(ip).fmt(ip), + @intFromEnum(inst_info.inst), + }); + const unit: AnalUnit = .wrap(.{ .nav_val = nav_id }); + try unit_queue.put(gpa, unit, referencer); } } for (zcu.namespacePtr(ns).pub_decls.keys()) |nav| { // These are named declarations. They are analyzed only if marked `export`. - const cau = ip.getNav(nav).analysis_owner.unwrap().?; - const inst_info = ip.getCau(cau).zir_index.resolveFull(ip) orelse continue; + const inst_info = ip.getNav(nav).analysis.?.zir_index.resolveFull(ip) orelse continue; const file = zcu.fileByIndex(inst_info.file); // If the file failed AstGen, the TrackedInst refers to the old ZIR. const zir = if (file.status == .success_zir) file.zir else file.prev_zir.?.*; - const declaration = zir.getDeclaration(inst_info.inst)[0]; - if (declaration.flags.is_export) { - const unit = AnalUnit.wrap(.{ .cau = cau }); + const decl = zir.getDeclaration(inst_info.inst); + if (decl.linkage == .@"export") { + const unit: AnalUnit = .wrap(.{ .nav_val = nav }); if (!result.contains(unit)) { - log.debug("type '{}': ref cau %{}", .{ + log.debug("type '{}': ref named %{}", .{ Type.fromInterned(ty).containerTypeName(ip).fmt(ip), @intFromEnum(inst_info.inst), }); @@ -3402,16 +3430,15 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv } for (zcu.namespacePtr(ns).priv_decls.keys()) |nav| { // These are named declarations. They are analyzed only if marked `export`. - const cau = ip.getNav(nav).analysis_owner.unwrap().?; - const inst_info = ip.getCau(cau).zir_index.resolveFull(ip) orelse continue; + const inst_info = ip.getNav(nav).analysis.?.zir_index.resolveFull(ip) orelse continue; const file = zcu.fileByIndex(inst_info.file); // If the file failed AstGen, the TrackedInst refers to the old ZIR. const zir = if (file.status == .success_zir) file.zir else file.prev_zir.?.*; - const declaration = zir.getDeclaration(inst_info.inst)[0]; - if (declaration.flags.is_export) { - const unit = AnalUnit.wrap(.{ .cau = cau }); + const decl = zir.getDeclaration(inst_info.inst); + if (decl.linkage == .@"export") { + const unit: AnalUnit = .wrap(.{ .nav_val = nav }); if (!result.contains(unit)) { - log.debug("type '{}': ref cau %{}", .{ + log.debug("type '{}': ref named %{}", .{ Type.fromInterned(ty).containerTypeName(ip).fmt(ip), @intFromEnum(inst_info.inst), }); @@ -3422,13 +3449,11 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv // Incremental compilation does not support `usingnamespace`. // These are only included to keep good reference traces in non-incremental updates. for (zcu.namespacePtr(ns).pub_usingnamespace.items) |nav| { - const cau = ip.getNav(nav).analysis_owner.unwrap().?; - const unit = AnalUnit.wrap(.{ .cau = cau }); + const unit: AnalUnit = .wrap(.{ .nav_val = nav }); if (!result.contains(unit)) try unit_queue.put(gpa, unit, referencer); } for (zcu.namespacePtr(ns).priv_usingnamespace.items) |nav| { - const cau = ip.getNav(nav).analysis_owner.unwrap().?; - const unit = AnalUnit.wrap(.{ .cau = cau }); + const unit: AnalUnit = .wrap(.{ .nav_val = nav }); if (!result.contains(unit)) try unit_queue.put(gpa, unit, referencer); } continue; @@ -3437,6 +3462,17 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv const unit = kv.key; try result.putNoClobber(gpa, unit, kv.value); + // `nav_val` and `nav_ty` reference each other *implicitly* to save memory. + queue_paired: { + const other: AnalUnit = .wrap(switch (unit.unwrap()) { + .nav_val => |n| .{ .nav_ty = n }, + .nav_ty => |n| .{ .nav_val = n }, + .@"comptime", .type, .func => break :queue_paired, + }); + if (result.contains(other)) break :queue_paired; + try unit_queue.put(gpa, other, kv.value); // same reference location + } + log.debug("handle unit '{}'", .{zcu.fmtAnalUnit(unit)}); if (zcu.reference_table.get(unit)) |first_ref_idx| { @@ -3522,13 +3558,11 @@ pub fn navSrcLine(zcu: *Zcu, nav_index: InternPool.Nav.Index) u32 { const ip = &zcu.intern_pool; const inst_info = ip.getNav(nav_index).srcInst(ip).resolveFull(ip).?; const zir = zcu.fileByIndex(inst_info.file).zir; - const inst = zir.instructions.get(@intFromEnum(inst_info.inst)); - assert(inst.tag == .declaration); - return zir.extraData(Zir.Inst.Declaration, inst.data.declaration.payload_index).data.src_line; + return zir.getDeclaration(inst_info.inst).src_line; } pub fn navValue(zcu: *const Zcu, nav_index: InternPool.Nav.Index) Value { - return Value.fromInterned(zcu.intern_pool.getNav(nav_index).status.resolved.val); + return Value.fromInterned(zcu.intern_pool.getNav(nav_index).status.fully_resolved.val); } pub fn navFileScopeIndex(zcu: *Zcu, nav: InternPool.Nav.Index) File.Index { @@ -3540,12 +3574,6 @@ pub fn navFileScope(zcu: *Zcu, nav: InternPool.Nav.Index) *File { return zcu.fileByIndex(zcu.navFileScopeIndex(nav)); } -pub fn cauFileScope(zcu: *Zcu, cau: InternPool.Cau.Index) *File { - const ip = &zcu.intern_pool; - const file_index = ip.getCau(cau).zir_index.resolveFile(ip); - return zcu.fileByIndex(file_index); -} - pub fn fmtAnalUnit(zcu: *Zcu, unit: AnalUnit) std.fmt.Formatter(formatAnalUnit) { return .{ .data = .{ .unit = unit, .zcu = zcu } }; } @@ -3558,19 +3586,18 @@ fn formatAnalUnit(data: struct { unit: AnalUnit, zcu: *Zcu }, comptime fmt: []co const zcu = data.zcu; const ip = &zcu.intern_pool; switch (data.unit.unwrap()) { - .cau => |cau_index| { - const cau = ip.getCau(cau_index); - switch (cau.owner.unwrap()) { - .nav => |nav| return writer.print("cau(decl='{}')", .{ip.getNav(nav).fqn.fmt(ip)}), - .type => |ty| return writer.print("cau(ty='{}')", .{Type.fromInterned(ty).containerTypeName(ip).fmt(ip)}), - .none => if (cau.zir_index.resolveFull(ip)) |resolved| { - const file_path = zcu.fileByIndex(resolved.file).sub_file_path; - return writer.print("cau(inst=('{s}', %{}))", .{ file_path, @intFromEnum(resolved.inst) }); - } else { - return writer.writeAll("cau(inst=<lost>)"); - }, + .@"comptime" => |cu_id| { + const cu = ip.getComptimeUnit(cu_id); + if (cu.zir_index.resolveFull(ip)) |resolved| { + const file_path = zcu.fileByIndex(resolved.file).sub_file_path; + return writer.print("comptime(inst=('{s}', %{}))", .{ file_path, @intFromEnum(resolved.inst) }); + } else { + return writer.writeAll("comptime(inst=<list>)"); } }, + .nav_val => |nav| return writer.print("nav_val('{}')", .{ip.getNav(nav).fqn.fmt(ip)}), + .nav_ty => |nav| return writer.print("nav_ty('{}')", .{ip.getNav(nav).fqn.fmt(ip)}), + .type => |ty| return writer.print("ty('{}')", .{Type.fromInterned(ty).containerTypeName(ip).fmt(ip)}), .func => |func| { const nav = zcu.funcInfo(func).owner_nav; return writer.print("func('{}')", .{ip.getNav(nav).fqn.fmt(ip)}); @@ -3595,7 +3622,11 @@ fn formatDependee(data: struct { dependee: InternPool.Dependee, zcu: *Zcu }, com }, .nav_val => |nav| { const fqn = ip.getNav(nav).fqn; - return writer.print("nav('{}')", .{fqn.fmt(ip)}); + return writer.print("nav_val('{}')", .{fqn.fmt(ip)}); + }, + .nav_ty => |nav| { + const fqn = ip.getNav(nav).fqn; + return writer.print("nav_ty('{}')", .{fqn.fmt(ip)}); }, .interned => |ip_index| switch (ip.indexToKey(ip_index)) { .struct_type, .union_type, .enum_type => return writer.print("type('{}')", .{Type.fromInterned(ip_index).containerTypeName(ip).fmt(ip)}), @@ -3772,3 +3803,12 @@ pub fn callconvSupported(zcu: *Zcu, cc: std.builtin.CallingConvention) union(enu if (!backend_ok) return .{ .bad_backend = backend }; return .ok; } + +/// Given that a `Nav` has value `val`, determine if a ref of that `Nav` gives a `const` pointer. +pub fn navValIsConst(zcu: *const Zcu, val: InternPool.Index) bool { + return switch (zcu.intern_pool.indexToKey(val)) { + .variable => false, + .@"extern" => |e| e.is_const, + else => true, + }; +} diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index ca2e76bcf7..21908f769f 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -469,12 +469,8 @@ pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void { { var it = old_zir.declIterator(old_inst); while (it.next()) |decl_inst| { - const decl_name = old_zir.getDeclaration(decl_inst)[0].name; - switch (decl_name) { - .@"comptime", .@"usingnamespace", .unnamed_test => continue, - _ => if (decl_name.isNamedTest(old_zir)) continue, - } - const name_zir = decl_name.toString(old_zir).?; + const name_zir = old_zir.getDeclaration(decl_inst).name; + if (name_zir == .empty) continue; const name_ip = try zcu.intern_pool.getOrPutString( zcu.gpa, pt.tid, @@ -488,12 +484,8 @@ pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void { { var it = new_zir.declIterator(new_inst); while (it.next()) |decl_inst| { - const decl_name = new_zir.getDeclaration(decl_inst)[0].name; - switch (decl_name) { - .@"comptime", .@"usingnamespace", .unnamed_test => continue, - _ => if (decl_name.isNamedTest(new_zir)) continue, - } - const name_zir = decl_name.toString(new_zir).?; + const name_zir = new_zir.getDeclaration(decl_inst).name; + if (name_zir == .empty) continue; const name_ip = try zcu.intern_pool.getOrPutString( zcu.gpa, pt.tid, @@ -553,118 +545,268 @@ pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void { pub fn ensureFileAnalyzed(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void { const file_root_type = pt.zcu.fileRootType(file_index); if (file_root_type != .none) { - _ = try pt.ensureTypeUpToDate(file_root_type, false); + _ = try pt.ensureTypeUpToDate(file_root_type); } else { return pt.semaFile(file_index); } } -/// This ensures that the state of the `Cau`, and of its corresponding `Nav` or type, -/// is fully up-to-date. Note that the type of the `Nav` may not be fully resolved. -/// Returns `error.AnalysisFail` if the `Cau` has an error. -pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu.SemaError!void { +/// Ensures that the state of the given `ComptimeUnit` is fully up-to-date, performing re-analysis +/// if necessary. Returns `error.AnalysisFail` if an analysis error is encountered; the caller is +/// free to ignore this, since the error is already registered. +pub fn ensureComptimeUnitUpToDate(pt: Zcu.PerThread, cu_id: InternPool.ComptimeUnit.Id) Zcu.SemaError!void { const tracy = trace(@src()); defer tracy.end(); const zcu = pt.zcu; const gpa = zcu.gpa; - const ip = &zcu.intern_pool; - const anal_unit = AnalUnit.wrap(.{ .cau = cau_index }); - const cau = ip.getCau(cau_index); + const anal_unit: AnalUnit = .wrap(.{ .@"comptime" = cu_id }); - log.debug("ensureCauAnalyzed {}", .{zcu.fmtAnalUnit(anal_unit)}); + log.debug("ensureComptimeUnitUpToDate {}", .{zcu.fmtAnalUnit(anal_unit)}); assert(!zcu.analysis_in_progress.contains(anal_unit)); - // Determine whether or not this Cau is outdated, i.e. requires re-analysis - // even if `complete`. If a Cau is PO, we pessismistically assume that it - // *does* require re-analysis, to ensure that the Cau is definitely - // up-to-date when this function returns. - - // If analysis occurs in a poor order, this could result in over-analysis. - // We do our best to avoid this by the other dependency logic in this file - // which tries to limit re-analysis to Caus whose previously listed - // dependencies are all up-to-date. + // Determine whether or not this `ComptimeUnit` is outdated. For this kind of `AnalUnit`, that's + // the only indicator as to whether or not analysis is required; when a `ComptimeUnit` is first + // created, it's marked as outdated. + // + // Note that if the unit is PO, we pessimistically assume that it *does* require re-analysis, to + // ensure that the unit is definitely up-to-date when this function returns. This mechanism could + // result in over-analysis if analysis occurs in a poor order; we do our best to avoid this by + // carefully choosing which units to re-analyze. See `Zcu.findOutdatedToAnalyze`. - const cau_outdated = zcu.outdated.swapRemove(anal_unit) or + const was_outdated = zcu.outdated.swapRemove(anal_unit) or zcu.potentially_outdated.swapRemove(anal_unit); - const prev_failed = zcu.failed_analysis.contains(anal_unit) or zcu.transitive_failed_analysis.contains(anal_unit); - - if (cau_outdated) { + if (was_outdated) { _ = zcu.outdated_ready.swapRemove(anal_unit); + // `was_outdated` can be true in the initial update for comptime units, so this isn't a `dev.check`. + if (dev.env.supports(.incremental)) { + zcu.deleteUnitExports(anal_unit); + zcu.deleteUnitReferences(anal_unit); + if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| { + kv.value.destroy(gpa); + } + _ = zcu.transitive_failed_analysis.swapRemove(anal_unit); + } } else { - // We can trust the current information about this `Cau`. - if (prev_failed) { + // We can trust the current information about this unit. + if (zcu.failed_analysis.contains(anal_unit)) return error.AnalysisFail; + if (zcu.transitive_failed_analysis.contains(anal_unit)) return error.AnalysisFail; + return; + } + + const unit_prog_node = zcu.sema_prog_node.start("comptime", 0); + defer unit_prog_node.end(); + + return pt.analyzeComptimeUnit(cu_id) catch |err| switch (err) { + error.AnalysisFail => { + if (!zcu.failed_analysis.contains(anal_unit)) { + // If this unit caused the error, it would have an entry in `failed_analysis`. + // Since it does not, this must be a transitive failure. + try zcu.transitive_failed_analysis.put(gpa, anal_unit, {}); + log.debug("mark transitive analysis failure for {}", .{zcu.fmtAnalUnit(anal_unit)}); + } return error.AnalysisFail; + }, + error.OutOfMemory => { + // TODO: it's unclear how to gracefully handle this. + // To report the error cleanly, we need to add a message to `failed_analysis` and a + // corresponding entry to `retryable_failures`; but either of these things is quite + // likely to OOM at this point. + // If that happens, what do we do? Perhaps we could have a special field on `Zcu` + // for reporting OOM errors without allocating. + return error.OutOfMemory; + }, + error.GenericPoison => unreachable, + error.ComptimeReturn => unreachable, + error.ComptimeBreak => unreachable, + }; +} + +/// Re-analyzes a `ComptimeUnit`. The unit has already been determined to be out-of-date, and old +/// side effects (exports/references/etc) have been dropped. If semantic analysis fails, this +/// function will return `error.AnalysisFail`, and it is the caller's reponsibility to add an entry +/// to `transitive_failed_analysis` if necessary. +fn analyzeComptimeUnit(pt: Zcu.PerThread, cu_id: InternPool.ComptimeUnit.Id) Zcu.CompileError!void { + const zcu = pt.zcu; + const gpa = zcu.gpa; + const ip = &zcu.intern_pool; + + const anal_unit: AnalUnit = .wrap(.{ .@"comptime" = cu_id }); + const comptime_unit = ip.getComptimeUnit(cu_id); + + log.debug("analyzeComptimeUnit {}", .{zcu.fmtAnalUnit(anal_unit)}); + + const inst_resolved = comptime_unit.zir_index.resolveFull(ip) orelse return error.AnalysisFail; + const file = zcu.fileByIndex(inst_resolved.file); + // TODO: stop the compiler ever reaching Sema if there are failed files. That way, this check is + // unnecessary, and we can move the below `removeDependenciesForDepender` call up with its friends + // in `ensureComptimeUnitUpToDate`. + if (file.status != .success_zir) return error.AnalysisFail; + const zir = file.zir; + + // We are about to re-analyze this unit; drop its depenndencies. + zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit); + + try zcu.analysis_in_progress.put(gpa, anal_unit, {}); + defer assert(zcu.analysis_in_progress.swapRemove(anal_unit)); + + var analysis_arena: std.heap.ArenaAllocator = .init(gpa); + defer analysis_arena.deinit(); + + var comptime_err_ret_trace: std.ArrayList(Zcu.LazySrcLoc) = .init(gpa); + defer comptime_err_ret_trace.deinit(); + + var sema: Sema = .{ + .pt = pt, + .gpa = gpa, + .arena = analysis_arena.allocator(), + .code = zir, + .owner = anal_unit, + .func_index = .none, + .func_is_naked = false, + .fn_ret_ty = .void, + .fn_ret_ty_ies = null, + .comptime_err_ret_trace = &comptime_err_ret_trace, + }; + defer sema.deinit(); + + // The comptime unit declares on the source of the corresponding `comptime` declaration. + try sema.declareDependency(.{ .src_hash = comptime_unit.zir_index }); + + var block: Sema.Block = .{ + .parent = null, + .sema = &sema, + .namespace = comptime_unit.namespace, + .instructions = .{}, + .inlining = null, + .is_comptime = true, + .src_base_inst = comptime_unit.zir_index, + .type_name_ctx = try ip.getOrPutStringFmt(gpa, pt.tid, "{}.comptime", .{ + Type.fromInterned(zcu.namespacePtr(comptime_unit.namespace).owner_type).containerTypeName(ip).fmt(ip), + }, .no_embedded_nulls), + }; + defer block.instructions.deinit(gpa); + + const zir_decl = zir.getDeclaration(inst_resolved.inst); + assert(zir_decl.kind == .@"comptime"); + assert(zir_decl.type_body == null); + assert(zir_decl.align_body == null); + assert(zir_decl.linksection_body == null); + assert(zir_decl.addrspace_body == null); + const value_body = zir_decl.value_body.?; + + const result_ref = try sema.resolveInlineBody(&block, value_body, inst_resolved.inst); + assert(result_ref == .void_value); // AstGen should always uphold this + + // Nothing else to do -- for a comptime decl, all we care about are the side effects. + // Just make sure to `flushExports`. + try sema.flushExports(); +} + +/// Ensures that the resolved value of the given `Nav` is fully up-to-date, performing re-analysis +/// if necessary. Returns `error.AnalysisFail` if an analysis error is encountered; the caller is +/// free to ignore this, since the error is already registered. +pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.SemaError!void { + const tracy = trace(@src()); + defer tracy.end(); + + // TODO: document this elsewhere mlugg! + // For my own benefit, here's how a namespace update for a normal (non-file-root) type works: + // `const S = struct { ... };` + // We are adding or removing a declaration within this `struct`. + // * `S` registers a dependency on `.{ .src_hash = (declaration of S) }` + // * Any change to the `struct` body -- including changing a declaration -- invalidates this + // * `S` is re-analyzed, but notes: + // * there is an existing struct instance (at this `TrackedInst` with these captures) + // * the struct's resolution is up-to-date (because nothing about the fields changed) + // * so, it uses the same `struct` + // * but this doesn't stop it from updating the namespace! + // * we basically do `scanDecls`, updating the namespace as needed + // * so everyone lived happily ever after + + const zcu = pt.zcu; + const gpa = zcu.gpa; + const ip = &zcu.intern_pool; + + _ = zcu.nav_val_analysis_queued.swapRemove(nav_id); + + const anal_unit: AnalUnit = .wrap(.{ .nav_val = nav_id }); + const nav = ip.getNav(nav_id); + + log.debug("ensureNavValUpToDate {}", .{zcu.fmtAnalUnit(anal_unit)}); + + // Determine whether or not this `Nav`'s value is outdated. This also includes checking if the + // status is `.unresolved`, which indicates that the value is outdated because it has *never* + // been analyzed so far. + // + // Note that if the unit is PO, we pessimistically assume that it *does* require re-analysis, to + // ensure that the unit is definitely up-to-date when this function returns. This mechanism could + // result in over-analysis if analysis occurs in a poor order; we do our best to avoid this by + // carefully choosing which units to re-analyze. See `Zcu.findOutdatedToAnalyze`. + + const was_outdated = zcu.outdated.swapRemove(anal_unit) or + zcu.potentially_outdated.swapRemove(anal_unit); + + const prev_failed = zcu.failed_analysis.contains(anal_unit) or + zcu.transitive_failed_analysis.contains(anal_unit); + + if (was_outdated) { + dev.check(.incremental); + _ = zcu.outdated_ready.swapRemove(anal_unit); + zcu.deleteUnitExports(anal_unit); + zcu.deleteUnitReferences(anal_unit); + if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| { + kv.value.destroy(gpa); } - // If it wasn't failed and wasn't marked outdated, then either... - // * it is a type and is up-to-date, or - // * it is a `comptime` decl and is up-to-date, or - // * it is another decl and is EITHER up-to-date OR never-referenced (so unresolved) - // We just need to check for that last case. - switch (cau.owner.unwrap()) { - .type, .none => return, - .nav => |nav| if (ip.getNav(nav).status == .resolved) return, + _ = zcu.transitive_failed_analysis.swapRemove(anal_unit); + } else { + // We can trust the current information about this unit. + if (prev_failed) return error.AnalysisFail; + switch (nav.status) { + .unresolved, .type_resolved => {}, + .fully_resolved => return, } } - const sema_result: SemaCauResult, const analysis_fail = if (pt.ensureCauAnalyzedInner(cau_index, cau_outdated)) |result| - // This `Cau` has gone from failed to success, so even if the value of the owner `Nav` didn't actually - // change, we need to invalidate the dependencies anyway. - .{ .{ - .invalidate_decl_val = result.invalidate_decl_val or prev_failed, - .invalidate_decl_ref = result.invalidate_decl_ref or prev_failed, - }, false } - else |err| switch (err) { + const unit_prog_node = zcu.sema_prog_node.start(nav.fqn.toSlice(ip), 0); + defer unit_prog_node.end(); + + const invalidate_value: bool, const new_failed: bool = if (pt.analyzeNavVal(nav_id)) |result| res: { + break :res .{ + // If the unit has gone from failed to success, we still need to invalidate the dependencies. + result.val_changed or prev_failed, + false, + }; + } else |err| switch (err) { error.AnalysisFail => res: { if (!zcu.failed_analysis.contains(anal_unit)) { - // If this `Cau` caused the error, it would have an entry in `failed_analysis`. + // If this unit caused the error, it would have an entry in `failed_analysis`. // Since it does not, this must be a transitive failure. try zcu.transitive_failed_analysis.put(gpa, anal_unit, {}); log.debug("mark transitive analysis failure for {}", .{zcu.fmtAnalUnit(anal_unit)}); } - // We consider this `Cau` to be outdated if: - // * Previous analysis succeeded; in this case, we need to re-analyze dependants to ensure - // they hit a transitive error here, rather than reporting a different error later (which - // may now be invalid). - // * The `Cau` is a type; in this case, the declaration site may require re-analysis to - // construct a valid type. - const outdated = !prev_failed or cau.owner.unwrap() == .type; - break :res .{ .{ - .invalidate_decl_val = outdated, - .invalidate_decl_ref = outdated, - }, true }; + break :res .{ !prev_failed, true }; }, - error.OutOfMemory => res: { - try zcu.failed_analysis.ensureUnusedCapacity(gpa, 1); - try zcu.retryable_failures.ensureUnusedCapacity(gpa, 1); - const msg = try Zcu.ErrorMsg.create( - gpa, - .{ .base_node_inst = cau.zir_index, .offset = Zcu.LazySrcLoc.Offset.nodeOffset(0) }, - "unable to analyze: OutOfMemory", - .{}, - ); - zcu.retryable_failures.appendAssumeCapacity(anal_unit); - zcu.failed_analysis.putAssumeCapacityNoClobber(anal_unit, msg); - break :res .{ .{ - .invalidate_decl_val = true, - .invalidate_decl_ref = true, - }, true }; + error.OutOfMemory => { + // TODO: it's unclear how to gracefully handle this. + // To report the error cleanly, we need to add a message to `failed_analysis` and a + // corresponding entry to `retryable_failures`; but either of these things is quite + // likely to OOM at this point. + // If that happens, what do we do? Perhaps we could have a special field on `Zcu` + // for reporting OOM errors without allocating. + return error.OutOfMemory; }, + error.GenericPoison => unreachable, + error.ComptimeReturn => unreachable, + error.ComptimeBreak => unreachable, }; - if (cau_outdated) { - // TODO: we do not yet have separate dependencies for decl values vs types. - const invalidate = sema_result.invalidate_decl_val or sema_result.invalidate_decl_ref; - const dependee: InternPool.Dependee = switch (cau.owner.unwrap()) { - .none => return, // there are no dependencies on a `comptime` decl! - .nav => |nav_index| .{ .nav_val = nav_index }, - .type => |ty| .{ .interned = ty }, - }; - - if (invalidate) { + if (was_outdated) { + const dependee: InternPool.Dependee = .{ .nav_val = nav_id }; + if (invalidate_value) { // This dependency was marked as PO, meaning dependees were waiting // on its analysis result, and it has turned out to be outdated. // Update dependees accordingly. @@ -676,67 +818,526 @@ pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu } } - if (analysis_fail) return error.AnalysisFail; + if (new_failed) return error.AnalysisFail; } -fn ensureCauAnalyzedInner( - pt: Zcu.PerThread, - cau_index: InternPool.Cau.Index, - cau_outdated: bool, -) Zcu.SemaError!SemaCauResult { +fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileError!struct { val_changed: bool } { const zcu = pt.zcu; + const gpa = zcu.gpa; const ip = &zcu.intern_pool; - const cau = ip.getCau(cau_index); - const anal_unit = AnalUnit.wrap(.{ .cau = cau_index }); + const anal_unit: AnalUnit = .wrap(.{ .nav_val = nav_id }); + const old_nav = ip.getNav(nav_id); - const inst_info = cau.zir_index.resolveFull(ip) orelse return error.AnalysisFail; + log.debug("analyzeNavVal {}", .{zcu.fmtAnalUnit(anal_unit)}); - // TODO: document this elsewhere mlugg! - // For my own benefit, here's how a namespace update for a normal (non-file-root) type works: - // `const S = struct { ... };` - // We are adding or removing a declaration within this `struct`. - // * `S` registers a dependency on `.{ .src_hash = (declaration of S) }` - // * Any change to the `struct` body -- including changing a declaration -- invalidates this - // * `S` is re-analyzed, but notes: - // * there is an existing struct instance (at this `TrackedInst` with these captures) - // * the struct's `Cau` is up-to-date (because nothing about the fields changed) - // * so, it uses the same `struct` - // * but this doesn't stop it from updating the namespace! - // * we basically do `scanDecls`, updating the namespace as needed - // * so everyone lived happily ever after + const inst_resolved = old_nav.analysis.?.zir_index.resolveFull(ip) orelse return error.AnalysisFail; + const file = zcu.fileByIndex(inst_resolved.file); + // TODO: stop the compiler ever reaching Sema if there are failed files. That way, this check is + // unnecessary, and we can move the below `removeDependenciesForDepender` call up with its friends + // in `ensureComptimeUnitUpToDate`. + if (file.status != .success_zir) return error.AnalysisFail; + const zir = file.zir; - if (zcu.fileByIndex(inst_info.file).status != .success_zir) { - return error.AnalysisFail; + // We are about to re-analyze this unit; drop its depenndencies. + zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit); + + try zcu.analysis_in_progress.put(gpa, anal_unit, {}); + errdefer _ = zcu.analysis_in_progress.swapRemove(anal_unit); + + var analysis_arena: std.heap.ArenaAllocator = .init(gpa); + defer analysis_arena.deinit(); + + var comptime_err_ret_trace: std.ArrayList(Zcu.LazySrcLoc) = .init(gpa); + defer comptime_err_ret_trace.deinit(); + + var sema: Sema = .{ + .pt = pt, + .gpa = gpa, + .arena = analysis_arena.allocator(), + .code = zir, + .owner = anal_unit, + .func_index = .none, + .func_is_naked = false, + .fn_ret_ty = .void, + .fn_ret_ty_ies = null, + .comptime_err_ret_trace = &comptime_err_ret_trace, + }; + defer sema.deinit(); + + // Every `Nav` declares a dependency on the source of the corresponding declaration. + try sema.declareDependency(.{ .src_hash = old_nav.analysis.?.zir_index }); + + // In theory, we would also add a reference to the corresponding `nav_val` unit here: there are + // always references in both directions between a `nav_val` and `nav_ty`. However, to save memory, + // these references are known implicitly. See logic in `Zcu.resolveReferences`. + + var block: Sema.Block = .{ + .parent = null, + .sema = &sema, + .namespace = old_nav.analysis.?.namespace, + .instructions = .{}, + .inlining = null, + .is_comptime = true, + .src_base_inst = old_nav.analysis.?.zir_index, + .type_name_ctx = old_nav.fqn, + }; + defer block.instructions.deinit(gpa); + + const zir_decl = zir.getDeclaration(inst_resolved.inst); + assert(old_nav.is_usingnamespace == (zir_decl.kind == .@"usingnamespace")); + + const ty_src = block.src(.{ .node_offset_var_decl_ty = 0 }); + const init_src = block.src(.{ .node_offset_var_decl_init = 0 }); + const align_src = block.src(.{ .node_offset_var_decl_align = 0 }); + const section_src = block.src(.{ .node_offset_var_decl_section = 0 }); + const addrspace_src = block.src(.{ .node_offset_var_decl_addrspace = 0 }); + + const maybe_ty: ?Type = if (zir_decl.type_body != null) ty: { + // Since we have a type body, the type is resolved separately! + // Of course, we need to make sure we depend on it properly. + try sema.declareDependency(.{ .nav_ty = nav_id }); + try pt.ensureNavTypeUpToDate(nav_id); + break :ty .fromInterned(ip.getNav(nav_id).status.type_resolved.type); + } else null; + + const final_val: ?Value = if (zir_decl.value_body) |value_body| val: { + if (maybe_ty) |ty| { + // Put the resolved type into `inst_map` to be used as the result type of the init. + try sema.inst_map.ensureSpaceForInstructions(gpa, &.{inst_resolved.inst}); + sema.inst_map.putAssumeCapacity(inst_resolved.inst, Air.internedToRef(ty.toIntern())); + const uncoerced_result_ref = try sema.resolveInlineBody(&block, value_body, inst_resolved.inst); + assert(sema.inst_map.remove(inst_resolved.inst)); + + const result_ref = try sema.coerce(&block, ty, uncoerced_result_ref, init_src); + break :val try sema.resolveFinalDeclValue(&block, init_src, result_ref); + } else { + // Just analyze the value; we have no type to offer. + const result_ref = try sema.resolveInlineBody(&block, value_body, inst_resolved.inst); + break :val try sema.resolveFinalDeclValue(&block, init_src, result_ref); + } + } else null; + + const nav_ty: Type = maybe_ty orelse final_val.?.typeOf(zcu); + + // First, we must resolve the declaration's type. To do this, we analyze the type body if available, + // or otherwise, we analyze the value body, populating `early_val` in the process. + + switch (zir_decl.kind) { + .@"comptime" => unreachable, // this is not a Nav + .unnamed_test, .@"test", .decltest => assert(nav_ty.zigTypeTag(zcu) == .@"fn"), + .@"usingnamespace" => {}, + .@"const" => {}, + .@"var" => try sema.validateVarType( + &block, + if (zir_decl.type_body != null) ty_src else init_src, + nav_ty, + zir_decl.linkage == .@"extern", + ), + } + + // Now that we know the type, we can evaluate the alignment, linksection, and addrspace, to determine + // the full pointer type of this declaration. + + const modifiers: Sema.NavPtrModifiers = if (zir_decl.type_body != null) m: { + // `analyzeNavType` (from the `ensureNavTypeUpToDate` call above) has already populated this data into + // the `Nav`. Load the new one, and pull the modifiers out. + switch (ip.getNav(nav_id).status) { + .unresolved => unreachable, // `analyzeNavType` will never leave us in this state + inline .type_resolved, .fully_resolved => |r| break :m .{ + .alignment = r.alignment, + .@"linksection" = r.@"linksection", + .@"addrspace" = r.@"addrspace", + }, + } + } else m: { + // `analyzeNavType` is essentially a stub which calls us. We are responsible for resolving this data. + break :m try sema.resolveNavPtrModifiers(&block, zir_decl, inst_resolved.inst, nav_ty); + }; + + // Lastly, we must figure out the actual interned value to store to the `Nav`. + // This isn't necessarily the same as `final_val`! + + const nav_val: Value = switch (zir_decl.linkage) { + .normal, .@"export" => switch (zir_decl.kind) { + .@"var" => .fromInterned(try pt.intern(.{ .variable = .{ + .ty = nav_ty.toIntern(), + .init = final_val.?.toIntern(), + .owner_nav = nav_id, + .is_threadlocal = zir_decl.is_threadlocal, + .is_weak_linkage = false, + } })), + else => final_val.?, + }, + .@"extern" => val: { + assert(final_val == null); // extern decls do not have a value body + const lib_name: ?[]const u8 = if (zir_decl.lib_name != .empty) l: { + break :l zir.nullTerminatedString(zir_decl.lib_name); + } else null; + if (lib_name) |l| { + const lib_name_src = block.src(.{ .node_offset_lib_name = 0 }); + try sema.handleExternLibName(&block, lib_name_src, l); + } + break :val .fromInterned(try pt.getExtern(.{ + .name = old_nav.name, + .ty = nav_ty.toIntern(), + .lib_name = try ip.getOrPutStringOpt(gpa, pt.tid, lib_name, .no_embedded_nulls), + .is_const = zir_decl.kind == .@"const", + .is_threadlocal = zir_decl.is_threadlocal, + .is_weak_linkage = false, + .is_dll_import = false, + .alignment = modifiers.alignment, + .@"addrspace" = modifiers.@"addrspace", + .zir_index = old_nav.analysis.?.zir_index, // `declaration` instruction + .owner_nav = undefined, // ignored by `getExtern` + })); + }, + }; + + switch (nav_val.toIntern()) { + .generic_poison => unreachable, // assertion failure + .unreachable_value => unreachable, // assertion failure + else => {}, + } + + // This resolves the type of the resolved value, not that value itself. If `nav_val` is a struct type, + // this resolves the type `type` (which needs no resolution), not the struct itself. + try nav_ty.resolveLayout(pt); + + // TODO: this is jank. If #20663 is rejected, let's think about how to better model `usingnamespace`. + if (zir_decl.kind == .@"usingnamespace") { + if (nav_ty.toIntern() != .type_type) { + return sema.fail(&block, ty_src, "expected type, found {}", .{nav_ty.fmt(pt)}); + } + if (nav_val.toType().getNamespace(zcu) == .none) { + return sema.fail(&block, ty_src, "type {} has no namespace", .{nav_val.toType().fmt(pt)}); + } + ip.resolveNavValue(nav_id, .{ + .val = nav_val.toIntern(), + .alignment = .none, + .@"linksection" = .none, + .@"addrspace" = .generic, + }); + // TODO: usingnamespace cannot participate in incremental compilation + assert(zcu.analysis_in_progress.swapRemove(anal_unit)); + return .{ .val_changed = true }; + } + + const queue_linker_work, const is_owned_fn = switch (ip.indexToKey(nav_val.toIntern())) { + .func => |f| .{ true, f.owner_nav == nav_id }, // note that this lets function aliases reach codegen + .variable => |v| .{ v.owner_nav == nav_id, false }, + .@"extern" => |e| .{ + false, + Type.fromInterned(e.ty).zigTypeTag(zcu) == .@"fn" and zir_decl.linkage == .@"extern", + }, + else => .{ true, false }, + }; + + if (is_owned_fn) { + // linksection etc are legal, except some targets do not support function alignment. + if (zir_decl.align_body != null and !target_util.supportsFunctionAlignment(zcu.getTarget())) { + return sema.fail(&block, align_src, "target does not support function alignment", .{}); + } + } else if (try nav_ty.comptimeOnlySema(pt)) { + // alignment, linksection, addrspace annotations are not allowed for comptime-only types. + const reason: []const u8 = switch (ip.indexToKey(nav_val.toIntern())) { + .func => "function alias", // slightly clearer message, since you *can* specify these on function *declarations* + else => "comptime-only type", + }; + if (zir_decl.align_body != null) { + return sema.fail(&block, align_src, "cannot specify alignment of {s}", .{reason}); + } + if (zir_decl.linksection_body != null) { + return sema.fail(&block, section_src, "cannot specify linksection of {s}", .{reason}); + } + if (zir_decl.addrspace_body != null) { + return sema.fail(&block, addrspace_src, "cannot specify addrspace of {s}", .{reason}); + } + } + + ip.resolveNavValue(nav_id, .{ + .val = nav_val.toIntern(), + .alignment = modifiers.alignment, + .@"linksection" = modifiers.@"linksection", + .@"addrspace" = modifiers.@"addrspace", + }); + + // Mark the unit as completed before evaluating the export! + assert(zcu.analysis_in_progress.swapRemove(anal_unit)); + + if (zir_decl.type_body == null) { + // In this situation, it's possible that we were triggered by `analyzeNavType` up the stack. In that + // case, we must also signal that the *type* is now populated to make this export behave correctly. + // An alternative strategy would be to just put something on the job queue to perform the export, but + // this is a little more straightforward, if perhaps less elegant. + _ = zcu.analysis_in_progress.swapRemove(.wrap(.{ .nav_ty = nav_id })); + } + + if (zir_decl.linkage == .@"export") { + const export_src = block.src(.{ .token_offset = @intFromBool(zir_decl.is_pub) }); + const name_slice = zir.nullTerminatedString(zir_decl.name); + const name_ip = try ip.getOrPutString(gpa, pt.tid, name_slice, .no_embedded_nulls); + try sema.analyzeExport(&block, export_src, .{ .name = name_ip }, nav_id); + } + + try sema.flushExports(); + + queue_codegen: { + if (!queue_linker_work) break :queue_codegen; + + if (!try nav_ty.hasRuntimeBitsSema(pt)) { + if (zcu.comp.config.use_llvm) break :queue_codegen; + if (file.mod.strip) break :queue_codegen; + } + + // This job depends on any resolve_type_fully jobs queued up before it. + try zcu.comp.queueJob(.{ .codegen_nav = nav_id }); } - // `cau_outdated` can be true in the initial update for `comptime` declarations, - // so this isn't a `dev.check`. - if (cau_outdated and dev.env.supports(.incremental)) { - // The exports this `Cau` performs will be re-discovered, so we remove them here - // prior to re-analysis. + switch (old_nav.status) { + .unresolved, .type_resolved => return .{ .val_changed = true }, + .fully_resolved => |old| return .{ .val_changed = old.val != nav_val.toIntern() }, + } +} + +pub fn ensureNavTypeUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.SemaError!void { + const tracy = trace(@src()); + defer tracy.end(); + + const zcu = pt.zcu; + const gpa = zcu.gpa; + const ip = &zcu.intern_pool; + + const anal_unit: AnalUnit = .wrap(.{ .nav_ty = nav_id }); + const nav = ip.getNav(nav_id); + + log.debug("ensureNavTypeUpToDate {}", .{zcu.fmtAnalUnit(anal_unit)}); + + // Determine whether or not this `Nav`'s type is outdated. This also includes checking if the + // status is `.unresolved`, which indicates that the value is outdated because it has *never* + // been analyzed so far. + // + // Note that if the unit is PO, we pessimistically assume that it *does* require re-analysis, to + // ensure that the unit is definitely up-to-date when this function returns. This mechanism could + // result in over-analysis if analysis occurs in a poor order; we do our best to avoid this by + // carefully choosing which units to re-analyze. See `Zcu.findOutdatedToAnalyze`. + + const was_outdated = zcu.outdated.swapRemove(anal_unit) or + zcu.potentially_outdated.swapRemove(anal_unit); + + const prev_failed = zcu.failed_analysis.contains(anal_unit) or + zcu.transitive_failed_analysis.contains(anal_unit); + + if (was_outdated) { + dev.check(.incremental); + _ = zcu.outdated_ready.swapRemove(anal_unit); zcu.deleteUnitExports(anal_unit); zcu.deleteUnitReferences(anal_unit); if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| { - kv.value.destroy(zcu.gpa); + kv.value.destroy(gpa); } _ = zcu.transitive_failed_analysis.swapRemove(anal_unit); + } else { + // We can trust the current information about this unit. + if (prev_failed) return error.AnalysisFail; + switch (nav.status) { + .unresolved => {}, + .type_resolved, .fully_resolved => return, + } } - const decl_prog_node = zcu.sema_prog_node.start(switch (cau.owner.unwrap()) { - .nav => |nav| ip.getNav(nav).fqn.toSlice(ip), - .type => |ty| Type.fromInterned(ty).containerTypeName(ip).toSlice(ip), - .none => "comptime", - }, 0); - defer decl_prog_node.end(); + const unit_prog_node = zcu.sema_prog_node.start(nav.fqn.toSlice(ip), 0); + defer unit_prog_node.end(); - return pt.semaCau(cau_index) catch |err| switch (err) { - error.GenericPoison, error.ComptimeBreak, error.ComptimeReturn => unreachable, - error.AnalysisFail, error.OutOfMemory => |e| return e, + const invalidate_type: bool, const new_failed: bool = if (pt.analyzeNavType(nav_id)) |result| res: { + break :res .{ + // If the unit has gone from failed to success, we still need to invalidate the dependencies. + result.type_changed or prev_failed, + false, + }; + } else |err| switch (err) { + error.AnalysisFail => res: { + if (!zcu.failed_analysis.contains(anal_unit)) { + // If this unit caused the error, it would have an entry in `failed_analysis`. + // Since it does not, this must be a transitive failure. + try zcu.transitive_failed_analysis.put(gpa, anal_unit, {}); + log.debug("mark transitive analysis failure for {}", .{zcu.fmtAnalUnit(anal_unit)}); + } + break :res .{ !prev_failed, true }; + }, + error.OutOfMemory => { + // TODO: it's unclear how to gracefully handle this. + // To report the error cleanly, we need to add a message to `failed_analysis` and a + // corresponding entry to `retryable_failures`; but either of these things is quite + // likely to OOM at this point. + // If that happens, what do we do? Perhaps we could have a special field on `Zcu` + // for reporting OOM errors without allocating. + return error.OutOfMemory; + }, + error.GenericPoison => unreachable, + error.ComptimeReturn => unreachable, + error.ComptimeBreak => unreachable, + }; + + if (was_outdated) { + const dependee: InternPool.Dependee = .{ .nav_ty = nav_id }; + if (invalidate_type) { + // This dependency was marked as PO, meaning dependees were waiting + // on its analysis result, and it has turned out to be outdated. + // Update dependees accordingly. + try zcu.markDependeeOutdated(.marked_po, dependee); + } else { + // This dependency was previously PO, but turned out to be up-to-date. + // We do not need to queue successive analysis. + try zcu.markPoDependeeUpToDate(dependee); + } + } + + if (new_failed) return error.AnalysisFail; +} + +fn analyzeNavType(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileError!struct { type_changed: bool } { + const zcu = pt.zcu; + const gpa = zcu.gpa; + const ip = &zcu.intern_pool; + + const anal_unit: AnalUnit = .wrap(.{ .nav_ty = nav_id }); + const old_nav = ip.getNav(nav_id); + + log.debug("analyzeNavType {}", .{zcu.fmtAnalUnit(anal_unit)}); + + const inst_resolved = old_nav.analysis.?.zir_index.resolveFull(ip) orelse return error.AnalysisFail; + const file = zcu.fileByIndex(inst_resolved.file); + // TODO: stop the compiler ever reaching Sema if there are failed files. That way, this check is + // unnecessary, and we can move the below `removeDependenciesForDepender` call up with its friends + // in `ensureComptimeUnitUpToDate`. + if (file.status != .success_zir) return error.AnalysisFail; + const zir = file.zir; + + // We are about to re-analyze this unit; drop its depenndencies. + zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit); + + try zcu.analysis_in_progress.put(gpa, anal_unit, {}); + defer _ = zcu.analysis_in_progress.swapRemove(anal_unit); + + var analysis_arena: std.heap.ArenaAllocator = .init(gpa); + defer analysis_arena.deinit(); + + var comptime_err_ret_trace: std.ArrayList(Zcu.LazySrcLoc) = .init(gpa); + defer comptime_err_ret_trace.deinit(); + + var sema: Sema = .{ + .pt = pt, + .gpa = gpa, + .arena = analysis_arena.allocator(), + .code = zir, + .owner = anal_unit, + .func_index = .none, + .func_is_naked = false, + .fn_ret_ty = .void, + .fn_ret_ty_ies = null, + .comptime_err_ret_trace = &comptime_err_ret_trace, }; + defer sema.deinit(); + + // Every `Nav` declares a dependency on the source of the corresponding declaration. + try sema.declareDependency(.{ .src_hash = old_nav.analysis.?.zir_index }); + + // In theory, we would also add a reference to the corresponding `nav_val` unit here: there are + // always references in both directions between a `nav_val` and `nav_ty`. However, to save memory, + // these references are known implicitly. See logic in `Zcu.resolveReferences`. + + var block: Sema.Block = .{ + .parent = null, + .sema = &sema, + .namespace = old_nav.analysis.?.namespace, + .instructions = .{}, + .inlining = null, + .is_comptime = true, + .src_base_inst = old_nav.analysis.?.zir_index, + .type_name_ctx = old_nav.fqn, + }; + defer block.instructions.deinit(gpa); + + const zir_decl = zir.getDeclaration(inst_resolved.inst); + assert(old_nav.is_usingnamespace == (zir_decl.kind == .@"usingnamespace")); + + const type_body = zir_decl.type_body orelse { + // The type of this `Nav` is inferred from the value. + // In other words, this `nav_ty` depends on the corresponding `nav_val`. + try sema.declareDependency(.{ .nav_val = nav_id }); + try pt.ensureNavValUpToDate(nav_id); + // Note that the above call, if it did any work, has removed our `analysis_in_progress` entry for us. + // (Our `defer` will run anyway, but it does nothing in this case.) + + // There's not a great way for us to know whether the type actually changed. + // For instance, perhaps the `nav_val` was already up-to-date, but this `nav_ty` is being + // analyzed because this declaration had a type annotation on the *previous* update. + // However, such cases are rare, and it's not unreasonable to re-analyze in them; and in + // other cases where we get here, it's because the `nav_val` was already re-analyzed and + // is outdated. + return .{ .type_changed = true }; + }; + + const ty_src = block.src(.{ .node_offset_var_decl_ty = 0 }); + + const resolved_ty: Type = ty: { + const uncoerced_type_ref = try sema.resolveInlineBody(&block, type_body, inst_resolved.inst); + const type_ref = try sema.coerce(&block, .type, uncoerced_type_ref, ty_src); + break :ty .fromInterned(type_ref.toInterned().?); + }; + + // In the case where the type is specified, this function is also responsible for resolving + // the pointer modifiers, i.e. alignment, linksection, addrspace. + const modifiers = try sema.resolveNavPtrModifiers(&block, zir_decl, inst_resolved.inst, resolved_ty); + + // Usually, we can infer this information from the resolved `Nav` value; see `Zcu.navValIsConst`. + // However, since we don't have one, we need to quickly check the ZIR to figure this out. + const is_const = switch (zir_decl.kind) { + .@"comptime" => unreachable, + .unnamed_test, .@"test", .decltest, .@"usingnamespace", .@"const" => true, + .@"var" => false, + }; + + const is_extern_decl = zir_decl.linkage == .@"extern"; + + // Now for the question of the day: are the type and modifiers the same as before? + // If they are, then we should actually keep the `Nav` as `fully_resolved` if it currently is. + // That's because `analyzeNavVal` will later want to look at the resolved value to figure out + // whether it's changed: if we threw that data away now, it would have to assume that the value + // had changed, potentially spinning off loads of unnecessary re-analysis! + const changed = switch (old_nav.status) { + .unresolved => true, + .type_resolved => |r| r.type != resolved_ty.toIntern() or + r.alignment != modifiers.alignment or + r.@"linksection" != modifiers.@"linksection" or + r.@"addrspace" != modifiers.@"addrspace" or + r.is_const != is_const or + r.is_extern_decl != is_extern_decl, + .fully_resolved => |r| ip.typeOf(r.val) != resolved_ty.toIntern() or + r.alignment != modifiers.alignment or + r.@"linksection" != modifiers.@"linksection" or + r.@"addrspace" != modifiers.@"addrspace" or + zcu.navValIsConst(r.val) != is_const or + (old_nav.getExtern(ip) != null) != is_extern_decl, + }; + + if (!changed) return .{ .type_changed = false }; + + ip.resolveNavType(nav_id, .{ + .type = resolved_ty.toIntern(), + .alignment = modifiers.alignment, + .@"linksection" = modifiers.@"linksection", + .@"addrspace" = modifiers.@"addrspace", + .is_const = is_const, + .is_threadlocal = zir_decl.is_threadlocal, + .is_extern_decl = is_extern_decl, + }); + + return .{ .type_changed = true }; } -pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: InternPool.Index) Zcu.SemaError!void { +pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, maybe_coerced_func_index: InternPool.Index) Zcu.SemaError!void { dev.check(.sema); const tracy = trace(@src()); @@ -746,35 +1347,43 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter const gpa = zcu.gpa; const ip = &zcu.intern_pool; + _ = zcu.func_body_analysis_queued.swapRemove(maybe_coerced_func_index); + // We only care about the uncoerced function. const func_index = ip.unwrapCoercedFunc(maybe_coerced_func_index); - const anal_unit = AnalUnit.wrap(.{ .func = func_index }); + const anal_unit: AnalUnit = .wrap(.{ .func = func_index }); - log.debug("ensureFuncBodyAnalyzed {}", .{zcu.fmtAnalUnit(anal_unit)}); + log.debug("ensureFuncBodyUpToDate {}", .{zcu.fmtAnalUnit(anal_unit)}); const func = zcu.funcInfo(maybe_coerced_func_index); - const func_outdated = zcu.outdated.swapRemove(anal_unit) or + const was_outdated = zcu.outdated.swapRemove(anal_unit) or zcu.potentially_outdated.swapRemove(anal_unit); const prev_failed = zcu.failed_analysis.contains(anal_unit) or zcu.transitive_failed_analysis.contains(anal_unit); - if (func_outdated) { + if (was_outdated) { + dev.check(.incremental); _ = zcu.outdated_ready.swapRemove(anal_unit); + zcu.deleteUnitExports(anal_unit); + zcu.deleteUnitReferences(anal_unit); + if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| { + kv.value.destroy(gpa); + } + _ = zcu.transitive_failed_analysis.swapRemove(anal_unit); } else { // We can trust the current information about this function. if (prev_failed) { return error.AnalysisFail; } - switch (func.analysisUnordered(ip).state) { - .unreferenced => {}, // this is the first reference - .queued => {}, // we're waiting on first-time analysis - .analyzed => return, // up-to-date - } + if (func.analysisUnordered(ip).is_analyzed) return; } - const ies_outdated, const analysis_fail = if (pt.ensureFuncBodyAnalyzedInner(func_index, func_outdated)) |result| - .{ result.ies_outdated, false } + const func_prog_node = zcu.sema_prog_node.start(ip.getNav(func.owner_nav).fqn.toSlice(ip), 0); + defer func_prog_node.end(); + + const ies_outdated, const new_failed = if (pt.analyzeFuncBody(func_index)) |result| + .{ prev_failed or result.ies_outdated, false } else |err| switch (err) { error.AnalysisFail => res: { if (!zcu.failed_analysis.contains(anal_unit)) { @@ -788,10 +1397,18 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter // a different error later (which may now be invalid). break :res .{ !prev_failed, true }; }, - error.OutOfMemory => return error.OutOfMemory, // TODO: graceful handling like `ensureCauAnalyzed` + error.OutOfMemory => { + // TODO: it's unclear how to gracefully handle this. + // To report the error cleanly, we need to add a message to `failed_analysis` and a + // corresponding entry to `retryable_failures`; but either of these things is quite + // likely to OOM at this point. + // If that happens, what do we do? Perhaps we could have a special field on `Zcu` + // for reporting OOM errors without allocating. + return error.OutOfMemory; + }, }; - if (func_outdated) { + if (was_outdated) { if (ies_outdated) { try zcu.markDependeeOutdated(.marked_po, .{ .interned = func_index }); } else { @@ -799,13 +1416,12 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter } } - if (analysis_fail) return error.AnalysisFail; + if (new_failed) return error.AnalysisFail; } -fn ensureFuncBodyAnalyzedInner( +fn analyzeFuncBody( pt: Zcu.PerThread, func_index: InternPool.Index, - func_outdated: bool, ) Zcu.SemaError!struct { ies_outdated: bool } { const zcu = pt.zcu; const gpa = zcu.gpa; @@ -820,8 +1436,8 @@ fn ensureFuncBodyAnalyzedInner( if (func.generic_owner == .none) { // Among another things, this ensures that the function's `zir_body_inst` is correct. - try pt.ensureCauAnalyzed(ip.getNav(func.owner_nav).analysis_owner.unwrap().?); - if (ip.getNav(func.owner_nav).status.resolved.val != func_index) { + try pt.ensureNavValUpToDate(func.owner_nav); + if (ip.getNav(func.owner_nav).status.fully_resolved.val != func_index) { // This function is no longer referenced! There's no point in re-analyzing it. // Just mark a transitive failure and move on. return error.AnalysisFail; @@ -829,8 +1445,8 @@ fn ensureFuncBodyAnalyzedInner( } else { const go_nav = zcu.funcInfo(func.generic_owner).owner_nav; // Among another things, this ensures that the function's `zir_body_inst` is correct. - try pt.ensureCauAnalyzed(ip.getNav(go_nav).analysis_owner.unwrap().?); - if (ip.getNav(go_nav).status.resolved.val != func.generic_owner) { + try pt.ensureNavValUpToDate(go_nav); + if (ip.getNav(go_nav).status.fully_resolved.val != func.generic_owner) { // The generic owner is no longer referenced, so this function is also unreferenced. // There's no point in re-analyzing it. Just mark a transitive failure and move on. return error.AnalysisFail; @@ -844,38 +1460,13 @@ fn ensureFuncBodyAnalyzedInner( else .none; - if (func_outdated) { - dev.check(.incremental); - zcu.deleteUnitExports(anal_unit); - zcu.deleteUnitReferences(anal_unit); - if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| { - kv.value.destroy(gpa); - } - _ = zcu.transitive_failed_analysis.swapRemove(anal_unit); - } - - if (!func_outdated) { - // We can trust the current information about this function. - if (zcu.failed_analysis.contains(anal_unit) or zcu.transitive_failed_analysis.contains(anal_unit)) { - return error.AnalysisFail; - } - switch (func.analysisUnordered(ip).state) { - .unreferenced => {}, // this is the first reference - .queued => {}, // we're waiting on first-time analysis - .analyzed => return .{ .ies_outdated = false }, // up-to-date - } - } - - log.debug("analyze and generate fn body {}; reason='{s}'", .{ - zcu.fmtAnalUnit(anal_unit), - if (func_outdated) "outdated" else "never analyzed", - }); + log.debug("analyze and generate fn body {}", .{zcu.fmtAnalUnit(anal_unit)}); - var air = try pt.analyzeFnBody(func_index); + var air = try pt.analyzeFnBodyInner(func_index); errdefer air.deinit(gpa); - const ies_outdated = func_outdated and - (!func.analysisUnordered(ip).inferred_error_set or func.resolvedErrorSetUnordered(ip) != old_resolved_ies); + const ies_outdated = !func.analysisUnordered(ip).inferred_error_set or + func.resolvedErrorSetUnordered(ip) != old_resolved_ies; const comp = zcu.comp; @@ -1043,12 +1634,11 @@ fn createFileRootStruct( wip_ty.setName(ip, try file.internFullyQualifiedName(pt)); ip.namespacePtr(namespace_index).owner_type = wip_ty.index; - const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, namespace_index, wip_ty.index); if (zcu.comp.incremental) { try ip.addDependency( gpa, - AnalUnit.wrap(.{ .cau = new_cau_index }), + .wrap(.{ .type = wip_ty.index }), .{ .src_hash = tracked_inst }, ); } @@ -1062,7 +1652,7 @@ fn createFileRootStruct( try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index }); } zcu.setFileRootType(file_index, wip_ty.index); - return wip_ty.finish(ip, new_cau_index.toOptional(), namespace_index); + return wip_ty.finish(ip, namespace_index); } /// Re-scan the namespace of a file's root struct type on an incremental update. @@ -1155,294 +1745,6 @@ fn semaFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void { } } -const SemaCauResult = packed struct { - /// Whether the value of a `decl_val` of the corresponding Nav changed. - invalidate_decl_val: bool, - /// Whether the type of a `decl_ref` of the corresponding Nav changed. - invalidate_decl_ref: bool, -}; - -/// Performs semantic analysis on the given `Cau`, storing results to its owner `Nav` if needed. -/// If analysis fails, returns `error.AnalysisFail`, storing an error in `zcu.failed_analysis` unless -/// the error is transitive. -/// On success, returns information about whether the `Nav` value changed. -fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult { - const zcu = pt.zcu; - const gpa = zcu.gpa; - const ip = &zcu.intern_pool; - - const anal_unit = AnalUnit.wrap(.{ .cau = cau_index }); - - const cau = ip.getCau(cau_index); - const inst_info = cau.zir_index.resolveFull(ip) orelse return error.AnalysisFail; - const file = zcu.fileByIndex(inst_info.file); - const zir = file.zir; - - if (file.status != .success_zir) { - return error.AnalysisFail; - } - - // We are about to re-analyze this `Cau`; drop its depenndencies. - zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit); - - switch (cau.owner.unwrap()) { - .none => {}, // `comptime` decl -- we will re-analyze its body. - .nav => {}, // Other decl -- we will re-analyze its value. - .type => |ty| { - // This is an incremental update, and this type is being re-analyzed because it is outdated. - // Create a new type in its place, and mark the old one as outdated so that use sites will - // be re-analyzed and discover an up-to-date type. - const new_ty = try pt.ensureTypeUpToDate(ty, true); - assert(new_ty != ty); - return .{ - .invalidate_decl_val = true, - .invalidate_decl_ref = true, - }; - }, - } - - const is_usingnamespace = switch (cau.owner.unwrap()) { - .nav => |nav| ip.getNav(nav).is_usingnamespace, - .none, .type => false, - }; - - log.debug("semaCau {}", .{zcu.fmtAnalUnit(anal_unit)}); - - try zcu.analysis_in_progress.put(gpa, anal_unit, {}); - errdefer _ = zcu.analysis_in_progress.swapRemove(anal_unit); - - var analysis_arena = std.heap.ArenaAllocator.init(gpa); - defer analysis_arena.deinit(); - - var comptime_err_ret_trace = std.ArrayList(Zcu.LazySrcLoc).init(gpa); - defer comptime_err_ret_trace.deinit(); - - var sema: Sema = .{ - .pt = pt, - .gpa = gpa, - .arena = analysis_arena.allocator(), - .code = zir, - .owner = anal_unit, - .func_index = .none, - .func_is_naked = false, - .fn_ret_ty = Type.void, - .fn_ret_ty_ies = null, - .comptime_err_ret_trace = &comptime_err_ret_trace, - }; - defer sema.deinit(); - - // Every `Cau` has a dependency on the source of its own ZIR instruction. - try sema.declareDependency(.{ .src_hash = cau.zir_index }); - - var block: Sema.Block = .{ - .parent = null, - .sema = &sema, - .namespace = cau.namespace, - .instructions = .{}, - .inlining = null, - .is_comptime = true, - .src_base_inst = cau.zir_index, - .type_name_ctx = switch (cau.owner.unwrap()) { - .nav => |nav| ip.getNav(nav).fqn, - .type => |ty| Type.fromInterned(ty).containerTypeName(ip), - .none => try ip.getOrPutStringFmt(gpa, pt.tid, "{}.comptime", .{ - Type.fromInterned(zcu.namespacePtr(cau.namespace).owner_type).containerTypeName(ip).fmt(ip), - }, .no_embedded_nulls), - }, - }; - defer block.instructions.deinit(gpa); - - const zir_decl: Zir.Inst.Declaration, const decl_bodies: Zir.Inst.Declaration.Bodies = decl: { - const decl, const extra_end = zir.getDeclaration(inst_info.inst); - break :decl .{ decl, decl.getBodies(extra_end, zir) }; - }; - - // We have to fetch this state before resolving the body because of the `nav_already_populated` - // case below. We might change the language in future so that align/linksection/etc for functions - // work in a way more in line with other declarations, in which case that logic will go away. - const old_nav_info = switch (cau.owner.unwrap()) { - .none, .type => undefined, // we'll never use `old_nav_info` - .nav => |nav| ip.getNav(nav), - }; - - const result_ref = try sema.resolveInlineBody(&block, decl_bodies.value_body, inst_info.inst); - - const nav_index = switch (cau.owner.unwrap()) { - .none => { - // This is a `comptime` decl, so we are done -- the side effects are all we care about. - // Just make sure to `flushExports`. - try sema.flushExports(); - assert(zcu.analysis_in_progress.swapRemove(anal_unit)); - return .{ - .invalidate_decl_val = false, - .invalidate_decl_ref = false, - }; - }, - .nav => |nav| nav, // We will resolve this `Nav` below. - .type => unreachable, // Handled at top of function. - }; - - const align_src = block.src(.{ .node_offset_var_decl_align = 0 }); - const section_src = block.src(.{ .node_offset_var_decl_section = 0 }); - const addrspace_src = block.src(.{ .node_offset_var_decl_addrspace = 0 }); - const ty_src = block.src(.{ .node_offset_var_decl_ty = 0 }); - const init_src = block.src(.{ .node_offset_var_decl_init = 0 }); - - const decl_val = try sema.resolveFinalDeclValue(&block, init_src, result_ref); - const decl_ty = decl_val.typeOf(zcu); - - switch (decl_val.toIntern()) { - .generic_poison => unreachable, // assertion failure - .unreachable_value => unreachable, // assertion failure - else => {}, - } - - // This resolves the type of the resolved value, not that value itself. If `decl_val` is a struct type, - // this resolves the type `type` (which needs no resolution), not the struct itself. - try decl_ty.resolveLayout(pt); - - // TODO: this is jank. If #20663 is rejected, let's think about how to better model `usingnamespace`. - if (is_usingnamespace) { - if (decl_ty.toIntern() != .type_type) { - return sema.fail(&block, ty_src, "expected type, found {}", .{decl_ty.fmt(pt)}); - } - if (decl_val.toType().getNamespace(zcu) == .none) { - return sema.fail(&block, ty_src, "type {} has no namespace", .{decl_val.toType().fmt(pt)}); - } - ip.resolveNavValue(nav_index, .{ - .val = decl_val.toIntern(), - .alignment = .none, - .@"linksection" = .none, - .@"addrspace" = .generic, - }); - // TODO: usingnamespace cannot participate in incremental compilation - assert(zcu.analysis_in_progress.swapRemove(anal_unit)); - return .{ - .invalidate_decl_val = true, - .invalidate_decl_ref = true, - }; - } - - const queue_linker_work, const is_owned_fn = switch (ip.indexToKey(decl_val.toIntern())) { - .func => |f| .{ true, f.owner_nav == nav_index }, // note that this lets function aliases reach codegen - .variable => |v| .{ v.owner_nav == nav_index, false }, - .@"extern" => |e| .{ false, Type.fromInterned(e.ty).zigTypeTag(zcu) == .@"fn" }, - else => .{ true, false }, - }; - - // Keep in sync with logic in `Sema.zirVarExtended`. - const alignment: InternPool.Alignment = a: { - const align_body = decl_bodies.align_body orelse break :a .none; - const align_ref = try sema.resolveInlineBody(&block, align_body, inst_info.inst); - break :a try sema.analyzeAsAlign(&block, align_src, align_ref); - }; - - const @"linksection": InternPool.OptionalNullTerminatedString = ls: { - const linksection_body = decl_bodies.linksection_body orelse break :ls .none; - const linksection_ref = try sema.resolveInlineBody(&block, linksection_body, inst_info.inst); - const bytes = try sema.toConstString(&block, section_src, linksection_ref, .{ - .needed_comptime_reason = "linksection must be comptime-known", - }); - if (std.mem.indexOfScalar(u8, bytes, 0) != null) { - return sema.fail(&block, section_src, "linksection cannot contain null bytes", .{}); - } else if (bytes.len == 0) { - return sema.fail(&block, section_src, "linksection cannot be empty", .{}); - } - break :ls try ip.getOrPutStringOpt(gpa, pt.tid, bytes, .no_embedded_nulls); - }; - - const @"addrspace": std.builtin.AddressSpace = as: { - const addrspace_ctx: Sema.AddressSpaceContext = switch (ip.indexToKey(decl_val.toIntern())) { - .func => .function, - .variable => .variable, - .@"extern" => |e| if (ip.indexToKey(e.ty) == .func_type) - .function - else - .variable, - else => .constant, - }; - const target = zcu.getTarget(); - const addrspace_body = decl_bodies.addrspace_body orelse break :as 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.resolveInlineBody(&block, addrspace_body, inst_info.inst); - break :as try sema.analyzeAsAddressSpace(&block, addrspace_src, addrspace_ref, addrspace_ctx); - }; - - if (is_owned_fn) { - // linksection etc are legal, except some targets do not support function alignment. - if (decl_bodies.align_body != null and !target_util.supportsFunctionAlignment(zcu.getTarget())) { - return sema.fail(&block, align_src, "target does not support function alignment", .{}); - } - } else if (try decl_ty.comptimeOnlySema(pt)) { - // alignment, linksection, addrspace annotations are not allowed for comptime-only types. - const reason: []const u8 = switch (ip.indexToKey(decl_val.toIntern())) { - .func => "function alias", // slightly clearer message, since you *can* specify these on function *declarations* - else => "comptime-only type", - }; - if (decl_bodies.align_body != null) { - return sema.fail(&block, align_src, "cannot specify alignment of {s}", .{reason}); - } - if (decl_bodies.linksection_body != null) { - return sema.fail(&block, section_src, "cannot specify linksection of {s}", .{reason}); - } - if (decl_bodies.addrspace_body != null) { - return sema.fail(&block, addrspace_src, "cannot specify addrspace of {s}", .{reason}); - } - } - - ip.resolveNavValue(nav_index, .{ - .val = decl_val.toIntern(), - .alignment = alignment, - .@"linksection" = @"linksection", - .@"addrspace" = @"addrspace", - }); - - // Mark the `Cau` as completed before evaluating the export! - assert(zcu.analysis_in_progress.swapRemove(anal_unit)); - - if (zir_decl.flags.is_export) { - const export_src = block.src(.{ .token_offset = @intFromBool(zir_decl.flags.is_pub) }); - const name_slice = zir.nullTerminatedString(zir_decl.name.toString(zir).?); - const name_ip = try ip.getOrPutString(gpa, pt.tid, name_slice, .no_embedded_nulls); - try sema.analyzeExport(&block, export_src, .{ .name = name_ip }, nav_index); - } - - try sema.flushExports(); - - queue_codegen: { - if (!queue_linker_work) break :queue_codegen; - - if (!try decl_ty.hasRuntimeBitsSema(pt)) { - if (zcu.comp.config.use_llvm) break :queue_codegen; - if (file.mod.strip) break :queue_codegen; - } - - // This job depends on any resolve_type_fully jobs queued up before it. - try zcu.comp.queueJob(.{ .codegen_nav = nav_index }); - } - - switch (old_nav_info.status) { - .unresolved => return .{ - .invalidate_decl_val = true, - .invalidate_decl_ref = true, - }, - .resolved => |old| { - const new = ip.getNav(nav_index).status.resolved; - return .{ - .invalidate_decl_val = new.val != old.val, - .invalidate_decl_ref = ip.typeOf(new.val) != ip.typeOf(old.val) or - new.alignment != old.alignment or - new.@"linksection" != old.@"linksection" or - new.@"addrspace" != old.@"addrspace", - }; - }, - } -} - pub fn importPkg(pt: Zcu.PerThread, mod: *Module) !Zcu.ImportFileResult { const zcu = pt.zcu; const gpa = zcu.gpa; @@ -1813,45 +2115,42 @@ pub fn scanNamespace( // For incremental updates, `scanDecl` wants to look up existing decls by their ZIR index rather // than their name. We'll build an efficient mapping now, then discard the current `decls`. - // We map to the `Cau`, since not every declaration has a `Nav`. - var existing_by_inst: std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, InternPool.Cau.Index) = .empty; + // We map to the `AnalUnit`, since not every declaration has a `Nav`. + var existing_by_inst: std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, InternPool.AnalUnit) = .empty; defer existing_by_inst.deinit(gpa); try existing_by_inst.ensureTotalCapacity(gpa, @intCast( namespace.pub_decls.count() + namespace.priv_decls.count() + namespace.pub_usingnamespace.items.len + namespace.priv_usingnamespace.items.len + - namespace.other_decls.items.len, + namespace.comptime_decls.items.len + + namespace.test_decls.items.len, )); for (namespace.pub_decls.keys()) |nav| { - const cau_index = ip.getNav(nav).analysis_owner.unwrap().?; - const zir_index = ip.getCau(cau_index).zir_index; - existing_by_inst.putAssumeCapacityNoClobber(zir_index, cau_index); + const zir_index = ip.getNav(nav).analysis.?.zir_index; + existing_by_inst.putAssumeCapacityNoClobber(zir_index, .wrap(.{ .nav_val = nav })); } for (namespace.priv_decls.keys()) |nav| { - const cau_index = ip.getNav(nav).analysis_owner.unwrap().?; - const zir_index = ip.getCau(cau_index).zir_index; - existing_by_inst.putAssumeCapacityNoClobber(zir_index, cau_index); + const zir_index = ip.getNav(nav).analysis.?.zir_index; + existing_by_inst.putAssumeCapacityNoClobber(zir_index, .wrap(.{ .nav_val = nav })); } for (namespace.pub_usingnamespace.items) |nav| { - const cau_index = ip.getNav(nav).analysis_owner.unwrap().?; - const zir_index = ip.getCau(cau_index).zir_index; - existing_by_inst.putAssumeCapacityNoClobber(zir_index, cau_index); + const zir_index = ip.getNav(nav).analysis.?.zir_index; + existing_by_inst.putAssumeCapacityNoClobber(zir_index, .wrap(.{ .nav_val = nav })); } for (namespace.priv_usingnamespace.items) |nav| { - const cau_index = ip.getNav(nav).analysis_owner.unwrap().?; - const zir_index = ip.getCau(cau_index).zir_index; - existing_by_inst.putAssumeCapacityNoClobber(zir_index, cau_index); - } - for (namespace.other_decls.items) |cau_index| { - const cau = ip.getCau(cau_index); - existing_by_inst.putAssumeCapacityNoClobber(cau.zir_index, cau_index); - // If this is a test, it'll be re-added to `test_functions` later on - // if still alive. Remove it for now. - switch (cau.owner.unwrap()) { - .none, .type => {}, - .nav => |nav| _ = zcu.test_functions.swapRemove(nav), - } + const zir_index = ip.getNav(nav).analysis.?.zir_index; + existing_by_inst.putAssumeCapacityNoClobber(zir_index, .wrap(.{ .nav_val = nav })); + } + for (namespace.comptime_decls.items) |cu| { + const zir_index = ip.getComptimeUnit(cu).zir_index; + existing_by_inst.putAssumeCapacityNoClobber(zir_index, .wrap(.{ .@"comptime" = cu })); + } + for (namespace.test_decls.items) |nav| { + const zir_index = ip.getNav(nav).analysis.?.zir_index; + existing_by_inst.putAssumeCapacityNoClobber(zir_index, .wrap(.{ .nav_val = nav })); + // This test will be re-added to `test_functions` later on if it's still alive. Remove it for now. + _ = zcu.test_functions.swapRemove(nav); } var seen_decls: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void) = .empty; @@ -1861,7 +2160,8 @@ pub fn scanNamespace( namespace.priv_decls.clearRetainingCapacity(); namespace.pub_usingnamespace.clearRetainingCapacity(); namespace.priv_usingnamespace.clearRetainingCapacity(); - namespace.other_decls.clearRetainingCapacity(); + namespace.comptime_decls.clearRetainingCapacity(); + namespace.test_decls.clearRetainingCapacity(); var scan_decl_iter: ScanDeclIter = .{ .pt = pt, @@ -1883,7 +2183,7 @@ const ScanDeclIter = struct { pt: Zcu.PerThread, namespace_index: Zcu.Namespace.Index, seen_decls: *std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void), - existing_by_inst: *const std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, InternPool.Cau.Index), + existing_by_inst: *const std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, InternPool.AnalUnit), /// Decl scanning is run in two passes, so that we can detect when a generated /// name would clash with an explicit name and use a different one. pass: enum { named, unnamed }, @@ -1919,64 +2219,41 @@ const ScanDeclIter = struct { const zir = file.zir; const ip = &zcu.intern_pool; - const inst_data = zir.instructions.items(.data)[@intFromEnum(decl_inst)].declaration; - const extra = zir.extraData(Zir.Inst.Declaration, inst_data.payload_index); - const declaration = extra.data; + const decl = zir.getDeclaration(decl_inst); - const Kind = enum { @"comptime", @"usingnamespace", @"test", named }; - - const maybe_name: InternPool.OptionalNullTerminatedString, const kind: Kind, const is_named_test: bool = switch (declaration.name) { - .@"comptime" => info: { + const maybe_name: InternPool.OptionalNullTerminatedString = switch (decl.kind) { + .@"comptime" => name: { if (iter.pass != .unnamed) return; - break :info .{ - .none, - .@"comptime", - false, - }; + break :name .none; }, - .@"usingnamespace" => info: { + .@"usingnamespace" => name: { if (iter.pass != .unnamed) return; const i = iter.usingnamespace_index; iter.usingnamespace_index += 1; - break :info .{ - (try iter.avoidNameConflict("usingnamespace_{d}", .{i})).toOptional(), - .@"usingnamespace", - false, - }; + break :name (try iter.avoidNameConflict("usingnamespace_{d}", .{i})).toOptional(); }, - .unnamed_test => info: { + .unnamed_test => name: { if (iter.pass != .unnamed) return; const i = iter.unnamed_test_index; iter.unnamed_test_index += 1; - break :info .{ - (try iter.avoidNameConflict("test_{d}", .{i})).toOptional(), - .@"test", - false, - }; + break :name (try iter.avoidNameConflict("test_{d}", .{i})).toOptional(); }, - _ => if (declaration.name.isNamedTest(zir)) info: { + .@"test", .decltest => |kind| name: { // We consider these to be unnamed since the decl name can be adjusted to avoid conflicts if necessary. if (iter.pass != .unnamed) return; - const prefix = if (declaration.flags.test_is_decltest) "decltest" else "test"; - break :info .{ - (try iter.avoidNameConflict("{s}.{s}", .{ prefix, zir.nullTerminatedString(declaration.name.toString(zir).?) })).toOptional(), - .@"test", - true, - }; - } else info: { + const prefix = @tagName(kind); + break :name (try iter.avoidNameConflict("{s}.{s}", .{ prefix, zir.nullTerminatedString(decl.name) })).toOptional(); + }, + .@"const", .@"var" => name: { if (iter.pass != .named) return; const name = try ip.getOrPutString( gpa, pt.tid, - zir.nullTerminatedString(declaration.name.toString(zir).?), + zir.nullTerminatedString(decl.name), .no_embedded_nulls, ); try iter.seen_decls.putNoClobber(gpa, name, {}); - break :info .{ - name.toOptional(), - .named, - false, - }; + break :name name.toOptional(); }, }; @@ -1985,60 +2262,59 @@ const ScanDeclIter = struct { .inst = decl_inst, }); - const existing_cau = iter.existing_by_inst.get(tracked_inst); + const existing_unit = iter.existing_by_inst.get(tracked_inst); - const cau, const want_analysis = switch (kind) { - .@"comptime" => cau: { - const cau = existing_cau orelse try ip.createComptimeCau(gpa, pt.tid, tracked_inst, namespace_index); + const unit, const want_analysis = switch (decl.kind) { + .@"comptime" => unit: { + const cu = if (existing_unit) |eu| + eu.unwrap().@"comptime" + else + try ip.createComptimeUnit(gpa, pt.tid, tracked_inst, namespace_index); - try namespace.other_decls.append(gpa, cau); + const unit: AnalUnit = .wrap(.{ .@"comptime" = cu }); - if (existing_cau == null) { - // For a `comptime` declaration, whether to analyze is based solely on whether the - // `Cau` is outdated. So, add this one to `outdated` and `outdated_ready` if not already. - const unit = AnalUnit.wrap(.{ .cau = cau }); - if (zcu.potentially_outdated.fetchSwapRemove(unit)) |kv| { - try zcu.outdated.ensureUnusedCapacity(gpa, 1); - try zcu.outdated_ready.ensureUnusedCapacity(gpa, 1); - zcu.outdated.putAssumeCapacityNoClobber(unit, kv.value); - if (kv.value == 0) { // no PO deps - zcu.outdated_ready.putAssumeCapacityNoClobber(unit, {}); - } - } else if (!zcu.outdated.contains(unit)) { - try zcu.outdated.ensureUnusedCapacity(gpa, 1); - try zcu.outdated_ready.ensureUnusedCapacity(gpa, 1); - zcu.outdated.putAssumeCapacityNoClobber(unit, 0); - zcu.outdated_ready.putAssumeCapacityNoClobber(unit, {}); - } + try namespace.comptime_decls.append(gpa, cu); + + if (existing_unit == null) { + // For a `comptime` declaration, whether to analyze is based solely on whether the unit + // is outdated. So, add this fresh one to `outdated` and `outdated_ready`. + try zcu.outdated.ensureUnusedCapacity(gpa, 1); + try zcu.outdated_ready.ensureUnusedCapacity(gpa, 1); + zcu.outdated.putAssumeCapacityNoClobber(unit, 0); + zcu.outdated_ready.putAssumeCapacityNoClobber(unit, {}); } - break :cau .{ cau, true }; + break :unit .{ unit, true }; }, - else => cau: { + else => unit: { const name = maybe_name.unwrap().?; const fqn = try namespace.internFullyQualifiedName(ip, gpa, pt.tid, name); - const cau, const nav = if (existing_cau) |cau_index| cau_nav: { - const nav_index = ip.getCau(cau_index).owner.unwrap().nav; - const nav = ip.getNav(nav_index); - assert(nav.name == name); - assert(nav.fqn == fqn); - break :cau_nav .{ cau_index, nav_index }; - } else try ip.createPairedCauNav(gpa, pt.tid, name, fqn, tracked_inst, namespace_index, kind == .@"usingnamespace"); - const want_analysis = switch (kind) { + const nav = if (existing_unit) |eu| + eu.unwrap().nav_val + else + try ip.createDeclNav(gpa, pt.tid, name, fqn, tracked_inst, namespace_index, decl.kind == .@"usingnamespace"); + + const unit: AnalUnit = .wrap(.{ .nav_val = nav }); + + assert(ip.getNav(nav).name == name); + assert(ip.getNav(nav).fqn == fqn); + + const want_analysis = switch (decl.kind) { .@"comptime" => unreachable, .@"usingnamespace" => a: { if (comp.incremental) { @panic("'usingnamespace' is not supported by incremental compilation"); } - if (declaration.flags.is_pub) { + if (decl.is_pub) { try namespace.pub_usingnamespace.append(gpa, nav); } else { try namespace.priv_usingnamespace.append(gpa, nav); } break :a true; }, - .@"test" => a: { - try namespace.other_decls.append(gpa, cau); + .unnamed_test, .@"test", .decltest => a: { + const is_named = decl.kind != .unnamed_test; + try namespace.test_decls.append(gpa, nav); // TODO: incremental compilation! // * remove from `test_functions` if no longer matching filter // * add to `test_functions` if newly passing filter @@ -2046,7 +2322,7 @@ const ScanDeclIter = struct { // Perhaps we should add all test indiscriminately and filter at the end of the update. if (!comp.config.is_test) break :a false; if (file.mod != zcu.main_mod) break :a false; - if (is_named_test and comp.test_filters.len > 0) { + if (is_named and comp.test_filters.len > 0) { const fqn_slice = fqn.toSlice(ip); for (comp.test_filters) |test_filter| { if (std.mem.indexOf(u8, fqn_slice, test_filter) != null) break; @@ -2055,8 +2331,8 @@ const ScanDeclIter = struct { try zcu.test_functions.put(gpa, nav, {}); break :a true; }, - .named => a: { - if (declaration.flags.is_pub) { + .@"const", .@"var" => a: { + if (decl.is_pub) { try namespace.pub_decls.putContext(gpa, nav, {}, .{ .zcu = zcu }); } else { try namespace.priv_decls.putContext(gpa, nav, {}, .{ .zcu = zcu }); @@ -2064,23 +2340,23 @@ const ScanDeclIter = struct { break :a false; }, }; - break :cau .{ cau, want_analysis }; + break :unit .{ unit, want_analysis }; }, }; - if (existing_cau == null and (want_analysis or declaration.flags.is_export)) { + if (existing_unit == null and (want_analysis or decl.linkage == .@"export")) { log.debug( - "scanDecl queue analyze_cau file='{s}' cau_index={d}", - .{ namespace.fileScope(zcu).sub_file_path, cau }, + "scanDecl queue analyze_comptime_unit file='{s}' unit={}", + .{ namespace.fileScope(zcu).sub_file_path, zcu.fmtAnalUnit(unit) }, ); - try comp.queueJob(.{ .analyze_cau = cau }); + try comp.queueJob(.{ .analyze_comptime_unit = unit }); } // TODO: we used to do line number updates here, but this is an inappropriate place for this logic to live. } }; -fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError!Air { +fn analyzeFnBodyInner(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError!Air { const tracy = trace(@src()); defer tracy.end(); @@ -2097,26 +2373,19 @@ fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError! try zcu.analysis_in_progress.put(gpa, anal_unit, {}); errdefer _ = zcu.analysis_in_progress.swapRemove(anal_unit); - func.setAnalysisState(ip, .analyzed); + func.setAnalyzed(ip); if (func.analysisUnordered(ip).inferred_error_set) { func.setResolvedErrorSet(ip, .none); } - // This is the `Cau` corresponding to the `declaration` instruction which the function or its generic owner originates from. - const decl_cau = ip.getCau(cau: { - const orig_nav = if (func.generic_owner == .none) - func.owner_nav - else - zcu.funcInfo(func.generic_owner).owner_nav; - - break :cau ip.getNav(orig_nav).analysis_owner.unwrap().?; - }); + // This is the `Nau` corresponding to the `declaration` instruction which the function or its generic owner originates from. + const decl_nav = ip.getNav(if (func.generic_owner == .none) + func.owner_nav + else + zcu.funcInfo(func.generic_owner).owner_nav); const func_nav = ip.getNav(func.owner_nav); - const decl_prog_node = zcu.sema_prog_node.start(func_nav.fqn.toSlice(ip), 0); - defer decl_prog_node.end(); - zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit); var analysis_arena = std.heap.ArenaAllocator.init(gpa); @@ -2150,7 +2419,7 @@ fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError! // Every runtime function has a dependency on the source of the Decl it originates from. // It also depends on the value of its owner Decl. - try sema.declareDependency(.{ .src_hash = decl_cau.zir_index }); + try sema.declareDependency(.{ .src_hash = decl_nav.analysis.?.zir_index }); try sema.declareDependency(.{ .nav_val = func.owner_nav }); if (func.analysisUnordered(ip).inferred_error_set) { @@ -2170,11 +2439,11 @@ fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError! var inner_block: Sema.Block = .{ .parent = null, .sema = &sema, - .namespace = decl_cau.namespace, + .namespace = decl_nav.analysis.?.namespace, .instructions = .{}, .inlining = null, .is_comptime = false, - .src_base_inst = decl_cau.zir_index, + .src_base_inst = decl_nav.analysis.?.zir_index, .type_name_ctx = func_nav.fqn, }; defer inner_block.instructions.deinit(gpa); @@ -2476,14 +2745,14 @@ fn processExportsInner( .nav => |nav_index| if (failed: { const nav = ip.getNav(nav_index); if (zcu.failed_codegen.contains(nav_index)) break :failed true; - if (nav.analysis_owner.unwrap()) |cau| { - const cau_unit = AnalUnit.wrap(.{ .cau = cau }); - if (zcu.failed_analysis.contains(cau_unit)) break :failed true; - if (zcu.transitive_failed_analysis.contains(cau_unit)) break :failed true; + if (nav.analysis != null) { + const unit: AnalUnit = .wrap(.{ .nav_val = nav_index }); + if (zcu.failed_analysis.contains(unit)) break :failed true; + if (zcu.transitive_failed_analysis.contains(unit)) break :failed true; } const val = switch (nav.status) { - .unresolved => break :failed true, - .resolved => |r| Value.fromInterned(r.val), + .unresolved, .type_resolved => break :failed true, + .fully_resolved => |r| Value.fromInterned(r.val), }; // If the value is a function, we also need to check if that function succeeded analysis. if (val.typeOf(zcu).zigTypeTag(zcu) == .@"fn") { @@ -2527,15 +2796,14 @@ pub fn populateTestFunctions( Zcu.Namespace.NameAdapter{ .zcu = zcu }, ).?; { - // We have to call `ensureCauAnalyzed` here in case `builtin.test_functions` + // We have to call `ensureNavValUpToDate` here in case `builtin.test_functions` // was not referenced by start code. zcu.sema_prog_node = main_progress_node.start("Semantic Analysis", 0); defer { zcu.sema_prog_node.end(); zcu.sema_prog_node = std.Progress.Node.none; } - const cau_index = ip.getNav(nav_index).analysis_owner.unwrap().?; - pt.ensureCauAnalyzed(cau_index) catch |err| switch (err) { + pt.ensureNavValUpToDate(nav_index) catch |err| switch (err) { error.AnalysisFail => return, error.OutOfMemory => return error.OutOfMemory, }; @@ -2556,8 +2824,7 @@ pub fn populateTestFunctions( { // The test declaration might have failed; if that's the case, just return, as we'll // be emitting a compile error anyway. - const cau = test_nav.analysis_owner.unwrap().?; - const anal_unit: AnalUnit = .wrap(.{ .cau = cau }); + const anal_unit: AnalUnit = .wrap(.{ .nav_val = test_nav_index }); if (zcu.failed_analysis.contains(anal_unit) or zcu.transitive_failed_analysis.contains(anal_unit)) { @@ -2682,8 +2949,8 @@ pub fn linkerUpdateNav(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) error "unable to codegen: {s}", .{@errorName(err)}, )); - if (nav.analysis_owner.unwrap()) |cau| { - try zcu.retryable_failures.append(zcu.gpa, AnalUnit.wrap(.{ .cau = cau })); + if (nav.analysis != null) { + try zcu.retryable_failures.append(zcu.gpa, .wrap(.{ .nav_val = nav_index })); } else { // TODO: we don't have a way to indicate that this failure is retryable! // Since these are really rare, we could as a cop-out retry the whole build next update. @@ -3189,31 +3456,30 @@ pub fn getBuiltinNav(pt: Zcu.PerThread, name: []const u8) Allocator.Error!Intern const builtin_str = try ip.getOrPutString(gpa, pt.tid, "builtin", .no_embedded_nulls); const builtin_nav = std_namespace.pub_decls.getKeyAdapted(builtin_str, Zcu.Namespace.NameAdapter{ .zcu = zcu }) orelse @panic("lib/std.zig is corrupt and missing 'builtin'"); - pt.ensureCauAnalyzed(ip.getNav(builtin_nav).analysis_owner.unwrap().?) catch @panic("std.builtin is corrupt"); - const builtin_type = Type.fromInterned(ip.getNav(builtin_nav).status.resolved.val); + pt.ensureNavValUpToDate(builtin_nav) catch @panic("std.builtin is corrupt"); + const builtin_type = Type.fromInterned(ip.getNav(builtin_nav).status.fully_resolved.val); const builtin_namespace = zcu.namespacePtr(builtin_type.getNamespace(zcu).unwrap() orelse @panic("std.builtin is corrupt")); const name_str = try ip.getOrPutString(gpa, pt.tid, name, .no_embedded_nulls); return builtin_namespace.pub_decls.getKeyAdapted(name_str, Zcu.Namespace.NameAdapter{ .zcu = zcu }) orelse @panic("lib/std/builtin.zig is corrupt"); } -pub fn navPtrType(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) Allocator.Error!Type { +pub fn navPtrType(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Allocator.Error!Type { const zcu = pt.zcu; const ip = &zcu.intern_pool; - const r = ip.getNav(nav_index).status.resolved; - const ty = Value.fromInterned(r.val).typeOf(zcu); + const ty, const alignment, const @"addrspace", const is_const = switch (ip.getNav(nav_id).status) { + .unresolved => unreachable, + .type_resolved => |r| .{ r.type, r.alignment, r.@"addrspace", r.is_const }, + .fully_resolved => |r| .{ ip.typeOf(r.val), r.alignment, r.@"addrspace", zcu.navValIsConst(r.val) }, + }; return pt.ptrType(.{ - .child = ty.toIntern(), + .child = ty, .flags = .{ - .alignment = if (r.alignment == ty.abiAlignment(zcu)) + .alignment = if (alignment == Type.fromInterned(ty).abiAlignment(zcu)) .none else - r.alignment, - .address_space = r.@"addrspace", - .is_const = switch (ip.indexToKey(r.val)) { - .variable => false, - .@"extern" => |e| e.is_const, - else => true, - }, + alignment, + .address_space = @"addrspace", + .is_const = is_const, }, }); } @@ -3233,76 +3499,57 @@ pub fn getExtern(pt: Zcu.PerThread, key: InternPool.Key.Extern) Allocator.Error! // TODO: this shouldn't need a `PerThread`! Fix the signature of `Type.abiAlignment`. pub fn navAlignment(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) InternPool.Alignment { const zcu = pt.zcu; - const r = zcu.intern_pool.getNav(nav_index).status.resolved; - if (r.alignment != .none) return r.alignment; - return Value.fromInterned(r.val).typeOf(zcu).abiAlignment(zcu); + const ty: Type, const alignment = switch (zcu.intern_pool.getNav(nav_index).status) { + .unresolved => unreachable, + .type_resolved => |r| .{ .fromInterned(r.type), r.alignment }, + .fully_resolved => |r| .{ Value.fromInterned(r.val).typeOf(zcu), r.alignment }, + }; + if (alignment != .none) return alignment; + return ty.abiAlignment(zcu); } /// Given a container type requiring resolution, ensures that it is up-to-date. /// If not, the type is recreated at a new `InternPool.Index`. /// The new index is returned. This is the same as the old index if the fields were up-to-date. -/// If `already_updating` is set, assumes the type is already outdated and undergoing re-analysis rather than checking `zcu.outdated`. -pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index, already_updating: bool) Zcu.SemaError!InternPool.Index { +pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index) Zcu.SemaError!InternPool.Index { const zcu = pt.zcu; + const gpa = zcu.gpa; const ip = &zcu.intern_pool; + + const anal_unit: AnalUnit = .wrap(.{ .type = ty }); + const outdated = zcu.outdated.swapRemove(anal_unit) or + zcu.potentially_outdated.swapRemove(anal_unit); + + if (!outdated) return ty; + + // We will recreate the type at a new `InternPool.Index`. + + _ = zcu.outdated_ready.swapRemove(anal_unit); + try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty }); + + // Delete old state which is no longer in use. Technically, this is not necessary: these exports, + // references, etc, will be ignored because the type itself is unreferenced. However, it allows + // reusing the memory which is currently being used to track this state. + zcu.deleteUnitExports(anal_unit); + zcu.deleteUnitReferences(anal_unit); + if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| { + kv.value.destroy(gpa); + } + _ = zcu.transitive_failed_analysis.swapRemove(anal_unit); + zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit); + switch (ip.indexToKey(ty)) { - .struct_type => |key| { - const struct_obj = ip.loadStructType(ty); - const outdated = already_updating or o: { - const anal_unit = AnalUnit.wrap(.{ .cau = struct_obj.cau }); - const o = zcu.outdated.swapRemove(anal_unit) or - zcu.potentially_outdated.swapRemove(anal_unit); - if (o) { - _ = zcu.outdated_ready.swapRemove(anal_unit); - try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty }); - } - break :o o; - }; - if (!outdated) return ty; - return pt.recreateStructType(key, struct_obj); - }, - .union_type => |key| { - const union_obj = ip.loadUnionType(ty); - const outdated = already_updating or o: { - const anal_unit = AnalUnit.wrap(.{ .cau = union_obj.cau }); - const o = zcu.outdated.swapRemove(anal_unit) or - zcu.potentially_outdated.swapRemove(anal_unit); - if (o) { - _ = zcu.outdated_ready.swapRemove(anal_unit); - try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty }); - } - break :o o; - }; - if (!outdated) return ty; - return pt.recreateUnionType(key, union_obj); - }, - .enum_type => |key| { - const enum_obj = ip.loadEnumType(ty); - const outdated = already_updating or o: { - const anal_unit = AnalUnit.wrap(.{ .cau = enum_obj.cau.unwrap().? }); - const o = zcu.outdated.swapRemove(anal_unit) or - zcu.potentially_outdated.swapRemove(anal_unit); - if (o) { - _ = zcu.outdated_ready.swapRemove(anal_unit); - try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty }); - } - break :o o; - }; - if (!outdated) return ty; - return pt.recreateEnumType(key, enum_obj); - }, - .opaque_type => { - assert(!already_updating); - return ty; - }, + .struct_type => |key| return pt.recreateStructType(ty, key), + .union_type => |key| return pt.recreateUnionType(ty, key), + .enum_type => |key| return pt.recreateEnumType(ty, key), else => unreachable, } } fn recreateStructType( pt: Zcu.PerThread, + old_ty: InternPool.Index, full_key: InternPool.Key.NamespaceType, - struct_obj: InternPool.LoadedStructType, ) Zcu.SemaError!InternPool.Index { const zcu = pt.zcu; const gpa = zcu.gpa; @@ -3339,8 +3586,7 @@ fn recreateStructType( if (captures_len != key.captures.owned.len) return error.AnalysisFail; - // The old type will be unused, so drop its dependency information. - ip.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .cau = struct_obj.cau })); + const struct_obj = ip.loadStructType(old_ty); const wip_ty = switch (try ip.getStructType(gpa, pt.tid, .{ .layout = small.layout, @@ -3362,17 +3608,16 @@ fn recreateStructType( errdefer wip_ty.cancel(ip, pt.tid); wip_ty.setName(ip, struct_obj.name); - const new_cau_index = try ip.createTypeCau(gpa, pt.tid, key.zir_index, struct_obj.namespace, wip_ty.index); try ip.addDependency( gpa, - AnalUnit.wrap(.{ .cau = new_cau_index }), + .wrap(.{ .type = wip_ty.index }), .{ .src_hash = key.zir_index }, ); zcu.namespacePtr(struct_obj.namespace).owner_type = wip_ty.index; // No need to re-scan the namespace -- `zirStructDecl` will ultimately do that if the type is still alive. try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); - const new_ty = wip_ty.finish(ip, new_cau_index.toOptional(), struct_obj.namespace); + const new_ty = wip_ty.finish(ip, struct_obj.namespace); if (inst_info.inst == .main_struct_inst) { // This is the root type of a file! Update the reference. zcu.setFileRootType(inst_info.file, new_ty); @@ -3382,8 +3627,8 @@ fn recreateStructType( fn recreateUnionType( pt: Zcu.PerThread, + old_ty: InternPool.Index, full_key: InternPool.Key.NamespaceType, - union_obj: InternPool.LoadedUnionType, ) Zcu.SemaError!InternPool.Index { const zcu = pt.zcu; const gpa = zcu.gpa; @@ -3422,8 +3667,7 @@ fn recreateUnionType( if (captures_len != key.captures.owned.len) return error.AnalysisFail; - // The old type will be unused, so drop its dependency information. - ip.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .cau = union_obj.cau })); + const union_obj = ip.loadUnionType(old_ty); const namespace_index = union_obj.namespace; @@ -3460,22 +3704,21 @@ fn recreateUnionType( errdefer wip_ty.cancel(ip, pt.tid); wip_ty.setName(ip, union_obj.name); - const new_cau_index = try ip.createTypeCau(gpa, pt.tid, key.zir_index, namespace_index, wip_ty.index); try ip.addDependency( gpa, - AnalUnit.wrap(.{ .cau = new_cau_index }), + .wrap(.{ .type = wip_ty.index }), .{ .src_hash = key.zir_index }, ); zcu.namespacePtr(namespace_index).owner_type = wip_ty.index; // No need to re-scan the namespace -- `zirUnionDecl` will ultimately do that if the type is still alive. try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); - return wip_ty.finish(ip, new_cau_index.toOptional(), namespace_index); + return wip_ty.finish(ip, namespace_index); } fn recreateEnumType( pt: Zcu.PerThread, + old_ty: InternPool.Index, full_key: InternPool.Key.NamespaceType, - enum_obj: InternPool.LoadedEnumType, ) Zcu.SemaError!InternPool.Index { const zcu = pt.zcu; const gpa = zcu.gpa; @@ -3544,8 +3787,7 @@ fn recreateEnumType( if (bag != 0) break true; } else false; - // The old type will be unused, so drop its dependency information. - ip.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .cau = enum_obj.cau.unwrap().? })); + const enum_obj = ip.loadEnumType(old_ty); const namespace_index = enum_obj.namespace; @@ -3571,12 +3813,10 @@ fn recreateEnumType( wip_ty.setName(ip, enum_obj.name); - const new_cau_index = try ip.createTypeCau(gpa, pt.tid, key.zir_index, namespace_index, wip_ty.index); - zcu.namespacePtr(namespace_index).owner_type = wip_ty.index; // No need to re-scan the namespace -- `zirEnumDecl` will ultimately do that if the type is still alive. - wip_ty.prepare(ip, new_cau_index, namespace_index); + wip_ty.prepare(ip, namespace_index); done = true; Sema.resolveDeclaredEnum( @@ -3586,7 +3826,6 @@ fn recreateEnumType( key.zir_index, namespace_index, enum_obj.name, - new_cau_index, small, body, tag_type_ref, diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index ccdf38a474..49961042bc 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -3218,15 +3218,7 @@ fn lowerNavRef(func: *CodeGen, nav_index: InternPool.Nav.Index, offset: u32) Inn const zcu = pt.zcu; const ip = &zcu.intern_pool; - // check if decl is an alias to a function, in which case we - // want to lower the actual decl, rather than the alias itself. - const owner_nav = switch (ip.indexToKey(zcu.navValue(nav_index).toIntern())) { - .func => |function| function.owner_nav, - .variable => |variable| variable.owner_nav, - .@"extern" => |@"extern"| @"extern".owner_nav, - else => nav_index, - }; - const nav_ty = ip.getNav(owner_nav).typeOf(ip); + const nav_ty = ip.getNav(nav_index).typeOf(ip); if (!ip.isFunctionType(nav_ty) and !Type.fromInterned(nav_ty).hasRuntimeBitsIgnoreComptime(zcu)) { return .{ .imm32 = 0xaaaaaaaa }; } diff --git a/src/codegen.zig b/src/codegen.zig index 2b179979f0..7b607f13f9 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -817,7 +817,7 @@ fn genNavRef( pt: Zcu.PerThread, src_loc: Zcu.LazySrcLoc, val: Value, - ref_nav_index: InternPool.Nav.Index, + nav_index: InternPool.Nav.Index, target: std.Target, ) CodeGenError!GenResult { const zcu = pt.zcu; @@ -851,14 +851,15 @@ fn genNavRef( } } - const nav_index, const is_extern, const lib_name, const is_threadlocal = switch (ip.indexToKey(zcu.navValue(ref_nav_index).toIntern())) { - .func => |func| .{ func.owner_nav, false, .none, false }, - .variable => |variable| .{ variable.owner_nav, false, variable.lib_name, variable.is_threadlocal }, - .@"extern" => |@"extern"| .{ @"extern".owner_nav, true, @"extern".lib_name, @"extern".is_threadlocal }, - else => .{ ref_nav_index, false, .none, false }, - }; + const nav = ip.getNav(nav_index); + + const is_extern, const lib_name, const is_threadlocal = if (nav.getExtern(ip)) |e| + .{ true, e.lib_name, e.is_threadlocal } + else + .{ false, .none, nav.isThreadlocal(ip) }; + const single_threaded = zcu.navFileScope(nav_index).mod.single_threaded; - const name = ip.getNav(nav_index).name; + const name = nav.name; if (lf.cast(.elf)) |elf_file| { const zo = elf_file.zigObjectPtr().?; if (is_extern) { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index c3e3c7fbdc..2368f202da 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -770,11 +770,14 @@ pub const DeclGen = struct { const ctype_pool = &dg.ctype_pool; // Chase function values in order to be able to reference the original function. - const owner_nav = switch (ip.indexToKey(zcu.navValue(nav_index).toIntern())) { - .variable => |variable| variable.owner_nav, - .func => |func| func.owner_nav, - .@"extern" => |@"extern"| @"extern".owner_nav, - else => nav_index, + const owner_nav = switch (ip.getNav(nav_index).status) { + .unresolved => unreachable, + .type_resolved => nav_index, // this can't be an extern or a function + .fully_resolved => |r| switch (ip.indexToKey(r.val)) { + .func => |f| f.owner_nav, + .@"extern" => |e| e.owner_nav, + else => nav_index, + }, }; // Render an undefined pointer if we have a pointer to a zero-bit or comptime type. @@ -2237,7 +2240,7 @@ pub const DeclGen = struct { Type.fromInterned(nav.typeOf(ip)), .{ .nav = nav_index }, CQualifiers.init(.{ .@"const" = flags.is_const }), - nav.status.resolved.alignment, + nav.getAlignment(), .complete, ); try fwd.writeAll(";\n"); @@ -2246,19 +2249,19 @@ pub const DeclGen = struct { fn renderNavName(dg: *DeclGen, writer: anytype, nav_index: InternPool.Nav.Index) !void { const zcu = dg.pt.zcu; const ip = &zcu.intern_pool; - switch (ip.indexToKey(zcu.navValue(nav_index).toIntern())) { - .@"extern" => |@"extern"| try writer.print("{ }", .{ + const nav = ip.getNav(nav_index); + if (nav.getExtern(ip)) |@"extern"| { + try writer.print("{ }", .{ fmtIdent(ip.getNav(@"extern".owner_nav).name.toSlice(ip)), - }), - else => { - // MSVC has a limit of 4095 character token length limit, and fmtIdent can (worst case), - // expand to 3x the length of its input, but let's cut it off at a much shorter limit. - const fqn_slice = ip.getNav(nav_index).fqn.toSlice(ip); - try writer.print("{}__{d}", .{ - fmtIdent(fqn_slice[0..@min(fqn_slice.len, 100)]), - @intFromEnum(nav_index), - }); - }, + }); + } else { + // MSVC has a limit of 4095 character token length limit, and fmtIdent can (worst case), + // expand to 3x the length of its input, but let's cut it off at a much shorter limit. + const fqn_slice = ip.getNav(nav_index).fqn.toSlice(ip); + try writer.print("{}__{d}", .{ + fmtIdent(fqn_slice[0..@min(fqn_slice.len, 100)]), + @intFromEnum(nav_index), + }); } } @@ -2826,7 +2829,7 @@ pub fn genLazyFn(o: *Object, lazy_ctype_pool: *const CType.Pool, lazy_fn: LazyFn const fwd = o.dg.fwdDeclWriter(); try fwd.print("static zig_{s} ", .{@tagName(key)}); - try o.dg.renderFunctionSignature(fwd, fn_val, ip.getNav(fn_nav_index).status.resolved.alignment, .forward, .{ + try o.dg.renderFunctionSignature(fwd, fn_val, ip.getNav(fn_nav_index).getAlignment(), .forward, .{ .fmt_ctype_pool_string = fn_name, }); try fwd.writeAll(";\n"); @@ -2867,13 +2870,13 @@ pub fn genFunc(f: *Function) !void { try o.dg.renderFunctionSignature( fwd, nav_val, - nav.status.resolved.alignment, + nav.status.fully_resolved.alignment, .forward, .{ .nav = nav_index }, ); try fwd.writeAll(";\n"); - if (nav.status.resolved.@"linksection".toSlice(ip)) |s| + if (nav.status.fully_resolved.@"linksection".toSlice(ip)) |s| try o.writer().print("zig_linksection_fn({s}) ", .{fmtStringLiteral(s, null)}); try o.dg.renderFunctionSignature( o.writer(), @@ -2952,7 +2955,7 @@ pub fn genDecl(o: *Object) !void { const nav_ty = Type.fromInterned(nav.typeOf(ip)); if (!nav_ty.isFnOrHasRuntimeBitsIgnoreComptime(zcu)) return; - switch (ip.indexToKey(nav.status.resolved.val)) { + switch (ip.indexToKey(nav.status.fully_resolved.val)) { .@"extern" => |@"extern"| { if (!ip.isFunctionType(nav_ty.toIntern())) return o.dg.renderFwdDecl(o.dg.pass.nav, .{ .is_extern = true, @@ -2965,8 +2968,8 @@ pub fn genDecl(o: *Object) !void { try fwd.writeAll("zig_extern "); try o.dg.renderFunctionSignature( fwd, - Value.fromInterned(nav.status.resolved.val), - nav.status.resolved.alignment, + Value.fromInterned(nav.status.fully_resolved.val), + nav.status.fully_resolved.alignment, .forward, .{ .@"export" = .{ .main_name = nav.name, @@ -2985,14 +2988,14 @@ pub fn genDecl(o: *Object) !void { const w = o.writer(); if (variable.is_weak_linkage) try w.writeAll("zig_weak_linkage "); if (variable.is_threadlocal and !o.dg.mod.single_threaded) try w.writeAll("zig_threadlocal "); - if (nav.status.resolved.@"linksection".toSlice(&zcu.intern_pool)) |s| + if (nav.status.fully_resolved.@"linksection".toSlice(&zcu.intern_pool)) |s| try w.print("zig_linksection({s}) ", .{fmtStringLiteral(s, null)}); try o.dg.renderTypeAndName( w, nav_ty, .{ .nav = o.dg.pass.nav }, .{}, - nav.status.resolved.alignment, + nav.status.fully_resolved.alignment, .complete, ); try w.writeAll(" = "); @@ -3002,10 +3005,10 @@ pub fn genDecl(o: *Object) !void { }, else => try genDeclValue( o, - Value.fromInterned(nav.status.resolved.val), + Value.fromInterned(nav.status.fully_resolved.val), .{ .nav = o.dg.pass.nav }, - nav.status.resolved.alignment, - nav.status.resolved.@"linksection", + nav.status.fully_resolved.alignment, + nav.status.fully_resolved.@"linksection", ), } } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index b94ea07995..5b36644019 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1476,7 +1476,7 @@ pub const Object = struct { } }, &o.builder); } - if (nav.status.resolved.@"linksection".toSlice(ip)) |section| + if (nav.status.fully_resolved.@"linksection".toSlice(ip)) |section| function_index.setSection(try o.builder.string(section), &o.builder); var deinit_wip = true; @@ -1684,7 +1684,7 @@ pub const Object = struct { const file = try o.getDebugFile(file_scope); const line_number = zcu.navSrcLine(func.owner_nav) + 1; - const is_internal_linkage = ip.indexToKey(nav.status.resolved.val) != .@"extern"; + const is_internal_linkage = ip.indexToKey(nav.status.fully_resolved.val) != .@"extern"; const debug_decl_type = try o.lowerDebugType(fn_ty); const subprogram = try o.builder.debugSubprogram( @@ -2928,9 +2928,7 @@ pub const Object = struct { const gpa = o.gpa; const nav = ip.getNav(nav_index); const owner_mod = zcu.navFileScope(nav_index).mod; - const resolved = nav.status.resolved; - const val = Value.fromInterned(resolved.val); - const ty = val.typeOf(zcu); + const ty: Type = .fromInterned(nav.typeOf(ip)); const gop = try o.nav_map.getOrPut(gpa, nav_index); if (gop.found_existing) return gop.value_ptr.ptr(&o.builder).kind.function; @@ -2938,15 +2936,14 @@ pub const Object = struct { const target = owner_mod.resolved_target.result; const sret = firstParamSRet(fn_info, zcu, target); - const is_extern, const lib_name = switch (ip.indexToKey(val.toIntern())) { - .variable => |variable| .{ false, variable.lib_name }, - .@"extern" => |@"extern"| .{ true, @"extern".lib_name }, - else => .{ false, .none }, - }; + const is_extern, const lib_name = if (nav.getExtern(ip)) |@"extern"| + .{ true, @"extern".lib_name } + else + .{ false, .none }; const function_index = try o.builder.addFunction( try o.lowerType(ty), try o.builder.strtabString((if (is_extern) nav.name else nav.fqn).toSlice(ip)), - toLlvmAddressSpace(resolved.@"addrspace", target), + toLlvmAddressSpace(nav.getAddrspace(), target), ); gop.value_ptr.* = function_index.ptrConst(&o.builder).global; @@ -3064,8 +3061,8 @@ pub const Object = struct { } } - if (resolved.alignment != .none) - function_index.setAlignment(resolved.alignment.toLlvm(), &o.builder); + if (nav.getAlignment() != .none) + function_index.setAlignment(nav.getAlignment().toLlvm(), &o.builder); // Function attributes that are independent of analysis results of the function body. try o.addCommonFnAttributes( @@ -3250,17 +3247,21 @@ pub const Object = struct { const zcu = pt.zcu; const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); - const resolved = nav.status.resolved; - const is_extern, const is_threadlocal, const is_weak_linkage, const is_dll_import = switch (ip.indexToKey(resolved.val)) { - .variable => |variable| .{ false, variable.is_threadlocal, variable.is_weak_linkage, false }, - .@"extern" => |@"extern"| .{ true, @"extern".is_threadlocal, @"extern".is_weak_linkage, @"extern".is_dll_import }, - else => .{ false, false, false, false }, + const is_extern, const is_threadlocal, const is_weak_linkage, const is_dll_import = switch (nav.status) { + .unresolved => unreachable, + .fully_resolved => |r| switch (ip.indexToKey(r.val)) { + .variable => |variable| .{ false, variable.is_threadlocal, variable.is_weak_linkage, false }, + .@"extern" => |@"extern"| .{ true, @"extern".is_threadlocal, @"extern".is_weak_linkage, @"extern".is_dll_import }, + else => .{ false, false, false, false }, + }, + // This means it's a source declaration which is not `extern`! + .type_resolved => |r| .{ false, r.is_threadlocal, false, false }, }; const variable_index = try o.builder.addVariable( try o.builder.strtabString((if (is_extern) nav.name else nav.fqn).toSlice(ip)), try o.lowerType(Type.fromInterned(nav.typeOf(ip))), - toLlvmGlobalAddressSpace(resolved.@"addrspace", zcu.getTarget()), + toLlvmGlobalAddressSpace(nav.getAddrspace(), zcu.getTarget()), ); gop.value_ptr.* = variable_index.ptrConst(&o.builder).global; @@ -4529,20 +4530,10 @@ pub const Object = struct { const zcu = pt.zcu; const ip = &zcu.intern_pool; - // In the case of something like: - // fn foo() void {} - // const bar = foo; - // ... &bar; - // `bar` is just an alias and we actually want to lower a reference to `foo`. - const owner_nav_index = switch (ip.indexToKey(zcu.navValue(nav_index).toIntern())) { - .func => |func| func.owner_nav, - .@"extern" => |@"extern"| @"extern".owner_nav, - else => nav_index, - }; - const owner_nav = ip.getNav(owner_nav_index); + const nav = ip.getNav(nav_index); - const nav_ty = Type.fromInterned(owner_nav.typeOf(ip)); - const ptr_ty = try pt.navPtrType(owner_nav_index); + const nav_ty = Type.fromInterned(nav.typeOf(ip)); + const ptr_ty = try pt.navPtrType(nav_index); const is_fn_body = nav_ty.zigTypeTag(zcu) == .@"fn"; if ((!is_fn_body and !nav_ty.hasRuntimeBits(zcu)) or @@ -4552,13 +4543,13 @@ pub const Object = struct { } const llvm_global = if (is_fn_body) - (try o.resolveLlvmFunction(owner_nav_index)).ptrConst(&o.builder).global + (try o.resolveLlvmFunction(nav_index)).ptrConst(&o.builder).global else - (try o.resolveGlobalNav(owner_nav_index)).ptrConst(&o.builder).global; + (try o.resolveGlobalNav(nav_index)).ptrConst(&o.builder).global; const llvm_val = try o.builder.convConst( llvm_global.toConst(), - try o.builder.ptrType(toLlvmAddressSpace(owner_nav.status.resolved.@"addrspace", zcu.getTarget())), + try o.builder.ptrType(toLlvmAddressSpace(nav.getAddrspace(), zcu.getTarget())), ); return o.builder.convConst(llvm_val, try o.lowerType(ptr_ty)); @@ -4800,10 +4791,10 @@ pub const NavGen = struct { const ip = &zcu.intern_pool; const nav_index = ng.nav_index; const nav = ip.getNav(nav_index); - const resolved = nav.status.resolved; + const resolved = nav.status.fully_resolved; const is_extern, const lib_name, const is_threadlocal, const is_weak_linkage, const is_dll_import, const is_const, const init_val, const owner_nav = switch (ip.indexToKey(resolved.val)) { - .variable => |variable| .{ false, variable.lib_name, variable.is_threadlocal, variable.is_weak_linkage, false, false, variable.init, variable.owner_nav }, + .variable => |variable| .{ false, .none, variable.is_threadlocal, variable.is_weak_linkage, false, false, variable.init, variable.owner_nav }, .@"extern" => |@"extern"| .{ true, @"extern".lib_name, @"extern".is_threadlocal, @"extern".is_weak_linkage, @"extern".is_dll_import, @"extern".is_const, .none, @"extern".owner_nav }, else => .{ false, .none, false, false, false, true, resolved.val, nav_index }, }; @@ -5766,7 +5757,7 @@ pub const FuncGen = struct { const msg_nav_index = zcu.panic_messages[@intFromEnum(panic_id)].unwrap().?; const msg_nav = ip.getNav(msg_nav_index); const msg_len = Type.fromInterned(msg_nav.typeOf(ip)).childType(zcu).arrayLen(zcu); - const msg_ptr = try o.lowerValue(msg_nav.status.resolved.val); + const msg_ptr = try o.lowerValue(msg_nav.status.fully_resolved.val); const null_opt_addr_global = try fg.resolveNullOptUsize(); const target = zcu.getTarget(); const llvm_usize = try o.lowerType(Type.usize); diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 16b4a6dfbd..91e2c4f7e7 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -268,7 +268,7 @@ pub const Object = struct { // TODO: Extern fn? const kind: SpvModule.Decl.Kind = if (ip.isFunctionType(nav.typeOf(ip))) .func - else switch (nav.status.resolved.@"addrspace") { + else switch (nav.getAddrspace()) { .generic => .invocation_global, else => .global, }; @@ -1279,17 +1279,20 @@ const NavGen = struct { const ip = &zcu.intern_pool; const ty_id = try self.resolveType(ty, .direct); const nav = ip.getNav(nav_index); - const nav_val = zcu.navValue(nav_index); - const nav_ty = nav_val.typeOf(zcu); - - switch (ip.indexToKey(nav_val.toIntern())) { - .func => { - // TODO: Properly lower function pointers. For now we are going to hack around it and - // just generate an empty pointer. Function pointers are represented by a pointer to usize. - return try self.spv.constUndef(ty_id); + const nav_ty: Type = .fromInterned(nav.typeOf(ip)); + + switch (nav.status) { + .unresolved => unreachable, + .type_resolved => {}, // this is not a function or extern + .fully_resolved => |r| switch (ip.indexToKey(r.val)) { + .func => { + // TODO: Properly lower function pointers. For now we are going to hack around it and + // just generate an empty pointer. Function pointers are represented by a pointer to usize. + return try self.spv.constUndef(ty_id); + }, + .@"extern" => if (ip.isFunctionType(nav_ty.toIntern())) @panic("TODO"), + else => {}, }, - .@"extern" => assert(!ip.isFunctionType(nav_ty.toIntern())), // TODO - else => {}, } if (!nav_ty.isFnOrHasRuntimeBitsIgnoreComptime(zcu)) { @@ -1305,7 +1308,7 @@ const NavGen = struct { .global, .invocation_global => spv_decl.result_id, }; - const storage_class = self.spvStorageClass(nav.status.resolved.@"addrspace"); + const storage_class = self.spvStorageClass(nav.getAddrspace()); try self.addFunctionDep(spv_decl_index, storage_class); const decl_ptr_ty_id = try self.ptrType(nav_ty, storage_class); @@ -3182,7 +3185,7 @@ const NavGen = struct { }; assert(maybe_init_val == null); // TODO - const storage_class = self.spvStorageClass(nav.status.resolved.@"addrspace"); + const storage_class = self.spvStorageClass(nav.getAddrspace()); assert(storage_class != .Generic); // These should be instance globals const ptr_ty_id = try self.ptrType(ty, storage_class); diff --git a/src/link.zig b/src/link.zig index f0f6e9b01d..58c5cf35af 100644 --- a/src/link.zig +++ b/src/link.zig @@ -692,7 +692,7 @@ pub const File = struct { /// May be called before or after updateExports for any given Nav. pub fn updateNav(base: *File, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) UpdateNavError!void { const nav = pt.zcu.intern_pool.getNav(nav_index); - assert(nav.status == .resolved); + assert(nav.status == .fully_resolved); switch (base.tag) { inline else => |tag| { dev.check(tag.devFeature()); diff --git a/src/link/C.zig b/src/link/C.zig index f42a467ee8..d84f29eb4b 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -217,7 +217,7 @@ pub fn updateFunc( .mod = zcu.navFileScope(func.owner_nav).mod, .error_msg = null, .pass = .{ .nav = func.owner_nav }, - .is_naked_fn = zcu.navValue(func.owner_nav).typeOf(zcu).fnCallingConvention(zcu) == .naked, + .is_naked_fn = Type.fromInterned(func.ty).fnCallingConvention(zcu) == .naked, .fwd_decl = fwd_decl.toManaged(gpa), .ctype_pool = ctype_pool.*, .scratch = .{}, @@ -320,11 +320,11 @@ pub fn updateNav(self: *C, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) ! const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); - const nav_init = switch (ip.indexToKey(nav.status.resolved.val)) { + const nav_init = switch (ip.indexToKey(nav.status.fully_resolved.val)) { .func => return, .@"extern" => .none, .variable => |variable| variable.init, - else => nav.status.resolved.val, + else => nav.status.fully_resolved.val, }; if (nav_init != .none and !Value.fromInterned(nav_init).typeOf(zcu).hasRuntimeBits(zcu)) return; @@ -499,7 +499,7 @@ pub fn flushModule(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: av_block, self.exported_navs.getPtr(nav), export_names, - if (ip.indexToKey(zcu.navValue(nav).toIntern()) == .@"extern") + if (ip.getNav(nav).getExtern(ip) != null) ip.getNav(nav).name.toOptional() else .none, @@ -544,13 +544,11 @@ pub fn flushModule(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: }, self.getString(av_block.code), ); - for (self.navs.keys(), self.navs.values()) |nav, av_block| f.appendCodeAssumeCapacity( - if (self.exported_navs.contains(nav)) .default else switch (ip.indexToKey(zcu.navValue(nav).toIntern())) { - .@"extern" => .zig_extern, - else => .static, - }, - self.getString(av_block.code), - ); + for (self.navs.keys(), self.navs.values()) |nav, av_block| f.appendCodeAssumeCapacity(storage: { + if (self.exported_navs.contains(nav)) break :storage .default; + if (ip.getNav(nav).getExtern(ip) != null) break :storage .zig_extern; + break :storage .static; + }, self.getString(av_block.code)); const file = self.base.file.?; try file.setEndPos(f.file_size); diff --git a/src/link/Coff.zig b/src/link/Coff.zig index e5b717ce1b..f13863cfb9 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1110,6 +1110,8 @@ pub fn updateFunc(coff: *Coff, pt: Zcu.PerThread, func_index: InternPool.Index, const atom_index = try coff.getOrCreateAtomForNav(func.owner_nav); coff.freeRelocations(atom_index); + coff.navs.getPtr(func.owner_nav).?.section = coff.text_section_index.?; + var code_buffer = std.ArrayList(u8).init(gpa); defer code_buffer.deinit(); @@ -1223,6 +1225,8 @@ pub fn updateNav( coff.freeRelocations(atom_index); const atom = coff.getAtom(atom_index); + coff.navs.getPtr(nav_index).?.section = coff.getNavOutputSection(nav_index); + var code_buffer = std.ArrayList(u8).init(gpa); defer code_buffer.deinit(); @@ -1342,7 +1346,8 @@ pub fn getOrCreateAtomForNav(coff: *Coff, nav_index: InternPool.Nav.Index) !Atom if (!gop.found_existing) { gop.value_ptr.* = .{ .atom = try coff.createAtom(), - .section = coff.getNavOutputSection(nav_index), + // If necessary, this will be modified by `updateNav` or `updateFunc`. + .section = coff.rdata_section_index.?, .exports = .{}, }; } @@ -1355,7 +1360,7 @@ fn getNavOutputSection(coff: *Coff, nav_index: InternPool.Nav.Index) u16 { const nav = ip.getNav(nav_index); const ty = Type.fromInterned(nav.typeOf(ip)); const zig_ty = ty.zigTypeTag(zcu); - const val = Value.fromInterned(nav.status.resolved.val); + const val = Value.fromInterned(nav.status.fully_resolved.val); const index: u16 = blk: { if (val.isUndefDeep(zcu)) { // TODO in release-fast and release-small, we should put undef in .bss @@ -2348,10 +2353,10 @@ pub fn getNavVAddr( const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); log.debug("getNavVAddr {}({d})", .{ nav.fqn.fmt(ip), nav_index }); - const sym_index = switch (ip.indexToKey(nav.status.resolved.val)) { - .@"extern" => |@"extern"| try coff.getGlobalSymbol(nav.name.toSlice(ip), @"extern".lib_name.toSlice(ip)), - else => coff.getAtom(try coff.getOrCreateAtomForNav(nav_index)).getSymbolIndex().?, - }; + const sym_index = if (nav.getExtern(ip)) |e| + try coff.getGlobalSymbol(nav.name.toSlice(ip), e.lib_name.toSlice(ip)) + else + coff.getAtom(try coff.getOrCreateAtomForNav(nav_index)).getSymbolIndex().?; const atom_index = coff.getAtomIndexForSymbol(.{ .sym_index = reloc_info.parent.atom_index, .file = null, diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index 426b9d21c9..5af6f80410 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -2259,24 +2259,13 @@ pub fn initWipNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool.Nav.In switch (ip.indexToKey(nav_val.toIntern())) { else => { assert(file.zir_loaded); - const decl = file.zir.getDeclaration(inst_info.inst)[0]; + const decl = file.zir.getDeclaration(inst_info.inst); - const parent_type, const accessibility: u8 = if (nav.analysis_owner.unwrap()) |cau| parent: { - const parent_namespace_ptr = ip.namespacePtr(ip.getCau(cau).namespace); + const parent_type, const accessibility: u8 = if (nav.analysis) |a| parent: { + const parent_namespace_ptr = ip.namespacePtr(a.namespace); break :parent .{ parent_namespace_ptr.owner_type, - switch (decl.name) { - .@"comptime", - .@"usingnamespace", - .unnamed_test, - => DW.ACCESS.private, - _ => if (decl.name.isNamedTest(file.zir)) - DW.ACCESS.private - else if (decl.flags.is_pub) - DW.ACCESS.public - else - DW.ACCESS.private, - }, + if (decl.is_pub) DW.ACCESS.public else DW.ACCESS.private, }; } else .{ zcu.fileRootType(inst_info.file), DW.ACCESS.private }; @@ -2292,7 +2281,7 @@ pub fn initWipNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool.Nav.In const nav_ty = nav_val.typeOf(zcu); const nav_ty_reloc_index = try wip_nav.refForward(); try wip_nav.infoExprloc(.{ .addr = .{ .sym = sym_index } }); - try uleb128(diw, nav.status.resolved.alignment.toByteUnits() orelse + try uleb128(diw, nav.status.fully_resolved.alignment.toByteUnits() orelse nav_ty.abiAlignment(zcu).toByteUnits().?); try diw.writeByte(@intFromBool(false)); wip_nav.finishForward(nav_ty_reloc_index); @@ -2301,24 +2290,13 @@ pub fn initWipNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool.Nav.In }, .variable => |variable| { assert(file.zir_loaded); - const decl = file.zir.getDeclaration(inst_info.inst)[0]; + const decl = file.zir.getDeclaration(inst_info.inst); - const parent_type, const accessibility: u8 = if (nav.analysis_owner.unwrap()) |cau| parent: { - const parent_namespace_ptr = ip.namespacePtr(ip.getCau(cau).namespace); + const parent_type, const accessibility: u8 = if (nav.analysis) |a| parent: { + const parent_namespace_ptr = ip.namespacePtr(a.namespace); break :parent .{ parent_namespace_ptr.owner_type, - switch (decl.name) { - .@"comptime", - .@"usingnamespace", - .unnamed_test, - => DW.ACCESS.private, - _ => if (decl.name.isNamedTest(file.zir)) - DW.ACCESS.private - else if (decl.flags.is_pub) - DW.ACCESS.public - else - DW.ACCESS.private, - }, + if (decl.is_pub) DW.ACCESS.public else DW.ACCESS.private, }; } else .{ zcu.fileRootType(inst_info.file), DW.ACCESS.private }; @@ -2335,30 +2313,19 @@ pub fn initWipNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool.Nav.In try wip_nav.refType(ty); const addr: Loc = .{ .addr = .{ .sym = sym_index } }; try wip_nav.infoExprloc(if (variable.is_threadlocal) .{ .form_tls_address = &addr } else addr); - try uleb128(diw, nav.status.resolved.alignment.toByteUnits() orelse + try uleb128(diw, nav.status.fully_resolved.alignment.toByteUnits() orelse ty.abiAlignment(zcu).toByteUnits().?); try diw.writeByte(@intFromBool(false)); }, .func => |func| { assert(file.zir_loaded); - const decl = file.zir.getDeclaration(inst_info.inst)[0]; + const decl = file.zir.getDeclaration(inst_info.inst); - const parent_type, const accessibility: u8 = if (nav.analysis_owner.unwrap()) |cau| parent: { - const parent_namespace_ptr = ip.namespacePtr(ip.getCau(cau).namespace); + const parent_type, const accessibility: u8 = if (nav.analysis) |a| parent: { + const parent_namespace_ptr = ip.namespacePtr(a.namespace); break :parent .{ parent_namespace_ptr.owner_type, - switch (decl.name) { - .@"comptime", - .@"usingnamespace", - .unnamed_test, - => DW.ACCESS.private, - _ => if (decl.name.isNamedTest(file.zir)) - DW.ACCESS.private - else if (decl.flags.is_pub) - DW.ACCESS.public - else - DW.ACCESS.private, - }, + if (decl.is_pub) DW.ACCESS.public else DW.ACCESS.private, }; } else .{ zcu.fileRootType(inst_info.file), DW.ACCESS.private }; @@ -2421,7 +2388,7 @@ pub fn initWipNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool.Nav.In wip_nav.func_high_pc = @intCast(wip_nav.debug_info.items.len); try diw.writeInt(u32, 0, dwarf.endian); const target = file.mod.resolved_target.result; - try uleb128(diw, switch (nav.status.resolved.alignment) { + try uleb128(diw, switch (nav.status.fully_resolved.alignment) { .none => target_info.defaultFunctionAlignment(target), else => |a| a.maxStrict(target_info.minFunctionAlignment(target)), }.toByteUnits().?); @@ -2585,23 +2552,22 @@ pub fn updateComptimeNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool const inst_info = nav.srcInst(ip).resolveFull(ip).?; const file = zcu.fileByIndex(inst_info.file); assert(file.zir_loaded); - const decl = file.zir.getDeclaration(inst_info.inst)[0]; + const decl = file.zir.getDeclaration(inst_info.inst); - const is_test = switch (decl.name) { - .unnamed_test => true, - .@"comptime", .@"usingnamespace" => false, - _ => decl.name.isNamedTest(file.zir), + const is_test = switch (decl.kind) { + .unnamed_test, .@"test", .decltest => true, + .@"comptime", .@"usingnamespace", .@"const", .@"var" => false, }; if (is_test) { // This isn't actually a comptime Nav! It's a test, so it'll definitely never be referenced at comptime. return; } - const parent_type, const accessibility: u8 = if (nav.analysis_owner.unwrap()) |cau| parent: { - const parent_namespace_ptr = ip.namespacePtr(ip.getCau(cau).namespace); + const parent_type, const accessibility: u8 = if (nav.analysis) |a| parent: { + const parent_namespace_ptr = ip.namespacePtr(a.namespace); break :parent .{ parent_namespace_ptr.owner_type, - if (decl.flags.is_pub) DW.ACCESS.public else DW.ACCESS.private, + if (decl.is_pub) DW.ACCESS.public else DW.ACCESS.private, }; } else .{ zcu.fileRootType(inst_info.file), DW.ACCESS.private }; @@ -2986,7 +2952,7 @@ pub fn updateComptimeNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool const nav_ty = nav_val.typeOf(zcu); try wip_nav.refType(nav_ty); try wip_nav.blockValue(nav_src_loc, nav_val); - try uleb128(diw, nav.status.resolved.alignment.toByteUnits() orelse + try uleb128(diw, nav.status.fully_resolved.alignment.toByteUnits() orelse nav_ty.abiAlignment(zcu).toByteUnits().?); try diw.writeByte(@intFromBool(false)); }, @@ -3011,7 +2977,7 @@ pub fn updateComptimeNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool try wip_nav.strp(nav.name.toSlice(ip)); try wip_nav.strp(nav.fqn.toSlice(ip)); const nav_ty_reloc_index = try wip_nav.refForward(); - try uleb128(diw, nav.status.resolved.alignment.toByteUnits() orelse + try uleb128(diw, nav.status.fully_resolved.alignment.toByteUnits() orelse nav_ty.abiAlignment(zcu).toByteUnits().?); try diw.writeByte(@intFromBool(false)); if (has_runtime_bits) try wip_nav.blockValue(nav_src_loc, nav_val); @@ -4198,9 +4164,7 @@ pub fn updateNavLineNumber(dwarf: *Dwarf, zcu: *Zcu, nav_index: InternPool.Nav.I assert(inst_info.inst != .main_struct_inst); const file = zcu.fileByIndex(inst_info.file); - const inst = file.zir.instructions.get(@intFromEnum(inst_info.inst)); - assert(inst.tag == .declaration); - const line = file.zir.extraData(Zir.Inst.Declaration, inst.data.declaration.payload_index).data.src_line; + const line = file.zir.getDeclaration(inst_info.inst).src_line; var line_buf: [4]u8 = undefined; std.mem.writeInt(u32, &line_buf, line, dwarf.endian); diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index fd6eceb556..5a2a7a8009 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -925,14 +925,11 @@ pub fn getNavVAddr( const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); log.debug("getNavVAddr {}({d})", .{ nav.fqn.fmt(ip), nav_index }); - const this_sym_index = switch (ip.indexToKey(nav.status.resolved.val)) { - .@"extern" => |@"extern"| try self.getGlobalSymbol( - elf_file, - nav.name.toSlice(ip), - @"extern".lib_name.toSlice(ip), - ), - else => try self.getOrCreateMetadataForNav(zcu, nav_index), - }; + const this_sym_index = if (nav.getExtern(ip)) |@"extern"| try self.getGlobalSymbol( + elf_file, + nav.name.toSlice(ip), + @"extern".lib_name.toSlice(ip), + ) else try self.getOrCreateMetadataForNav(zcu, nav_index); const this_sym = self.symbol(this_sym_index); const vaddr = this_sym.address(.{}, elf_file); switch (reloc_info.parent) { @@ -1107,15 +1104,13 @@ pub fn freeNav(self: *ZigObject, elf_file: *Elf, nav_index: InternPool.Nav.Index pub fn getOrCreateMetadataForNav(self: *ZigObject, zcu: *Zcu, nav_index: InternPool.Nav.Index) !Symbol.Index { const gpa = zcu.gpa; + const ip = &zcu.intern_pool; const gop = try self.navs.getOrPut(gpa, nav_index); if (!gop.found_existing) { const symbol_index = try self.newSymbolWithAtom(gpa, 0); - const nav_val = Value.fromInterned(zcu.intern_pool.getNav(nav_index).status.resolved.val); const sym = self.symbol(symbol_index); - if (nav_val.getVariable(zcu)) |variable| { - if (variable.is_threadlocal and zcu.comp.config.any_non_single_threaded) { - sym.flags.is_tls = true; - } + if (ip.getNav(nav_index).isThreadlocal(ip) and zcu.comp.config.any_non_single_threaded) { + sym.flags.is_tls = true; } gop.value_ptr.* = .{ .symbol_index = symbol_index }; } @@ -1547,7 +1542,7 @@ pub fn updateNav( log.debug("updateNav {}({d})", .{ nav.fqn.fmt(ip), nav_index }); - const nav_init = switch (ip.indexToKey(nav.status.resolved.val)) { + const nav_init = switch (ip.indexToKey(nav.status.fully_resolved.val)) { .func => .none, .variable => |variable| variable.init, .@"extern" => |@"extern"| { @@ -1560,7 +1555,7 @@ pub fn updateNav( self.symbol(sym_index).flags.is_extern_ptr = true; return; }, - else => nav.status.resolved.val, + else => nav.status.fully_resolved.val, }; if (nav_init != .none and Value.fromInterned(nav_init).typeOf(zcu).hasRuntimeBits(zcu)) { diff --git a/src/link/MachO/ZigObject.zig b/src/link/MachO/ZigObject.zig index e18bc078df..511cb6839d 100644 --- a/src/link/MachO/ZigObject.zig +++ b/src/link/MachO/ZigObject.zig @@ -608,14 +608,11 @@ pub fn getNavVAddr( const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); log.debug("getNavVAddr {}({d})", .{ nav.fqn.fmt(ip), nav_index }); - const sym_index = switch (ip.indexToKey(nav.status.resolved.val)) { - .@"extern" => |@"extern"| try self.getGlobalSymbol( - macho_file, - nav.name.toSlice(ip), - @"extern".lib_name.toSlice(ip), - ), - else => try self.getOrCreateMetadataForNav(macho_file, nav_index), - }; + const sym_index = if (nav.getExtern(ip)) |@"extern"| try self.getGlobalSymbol( + macho_file, + nav.name.toSlice(ip), + @"extern".lib_name.toSlice(ip), + ) else try self.getOrCreateMetadataForNav(macho_file, nav_index); const sym = self.symbols.items[sym_index]; const vaddr = sym.getAddress(.{}, macho_file); switch (reloc_info.parent) { @@ -882,7 +879,7 @@ pub fn updateNav( const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); - const nav_init = switch (ip.indexToKey(nav.status.resolved.val)) { + const nav_init = switch (ip.indexToKey(nav.status.fully_resolved.val)) { .func => .none, .variable => |variable| variable.init, .@"extern" => |@"extern"| { @@ -895,7 +892,7 @@ pub fn updateNav( sym.flags.is_extern_ptr = true; return; }, - else => nav.status.resolved.val, + else => nav.status.fully_resolved.val, }; if (nav_init != .none and Value.fromInterned(nav_init).typeOf(zcu).hasRuntimeBits(zcu)) { @@ -1561,11 +1558,7 @@ fn isThreadlocal(macho_file: *MachO, nav_index: InternPool.Nav.Index) bool { if (!macho_file.base.comp.config.any_non_single_threaded) return false; const ip = &macho_file.base.comp.zcu.?.intern_pool; - return switch (ip.indexToKey(ip.getNav(nav_index).status.resolved.val)) { - .variable => |variable| variable.is_threadlocal, - .@"extern" => |@"extern"| @"extern".is_threadlocal, - else => false, - }; + return ip.getNav(nav_index).isThreadlocal(ip); } fn addAtom(self: *ZigObject, allocator: Allocator) !Atom.Index { diff --git a/src/link/Plan9.zig b/src/link/Plan9.zig index 3144b2ac10..8e27e20ec7 100644 --- a/src/link/Plan9.zig +++ b/src/link/Plan9.zig @@ -1021,7 +1021,7 @@ pub fn seeNav(self: *Plan9, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) const atom_idx = gop.value_ptr.index; // handle externs here because they might not get updateDecl called on them const nav = ip.getNav(nav_index); - if (ip.indexToKey(nav.status.resolved.val) == .@"extern") { + if (nav.getExtern(ip) != null) { // this is a "phantom atom" - it is never actually written to disk, just convenient for us to store stuff about externs if (nav.name.eqlSlice("etext", ip)) { self.etext_edata_end_atom_indices[0] = atom_idx; @@ -1370,7 +1370,7 @@ pub fn getNavVAddr( const ip = &pt.zcu.intern_pool; const nav = ip.getNav(nav_index); log.debug("getDeclVAddr for {}", .{nav.name.fmt(ip)}); - if (ip.indexToKey(nav.status.resolved.val) == .@"extern") { + if (nav.getExtern(ip) != null) { if (nav.name.eqlSlice("etext", ip)) { try self.addReloc(reloc_info.parent.atom_index, .{ .target = undefined, diff --git a/src/link/Wasm/ZigObject.zig b/src/link/Wasm/ZigObject.zig index 0b5d2efb47..09d7647730 100644 --- a/src/link/Wasm/ZigObject.zig +++ b/src/link/Wasm/ZigObject.zig @@ -241,7 +241,7 @@ pub fn updateNav( const nav_val = zcu.navValue(nav_index); const is_extern, const lib_name, const nav_init = switch (ip.indexToKey(nav_val.toIntern())) { - .variable => |variable| .{ false, variable.lib_name, Value.fromInterned(variable.init) }, + .variable => |variable| .{ false, .none, Value.fromInterned(variable.init) }, .func => return, .@"extern" => |@"extern"| if (ip.isFunctionType(nav.typeOf(ip))) return @@ -734,15 +734,14 @@ pub fn getNavVAddr( const target_atom_index = try zig_object.getOrCreateAtomForNav(wasm, pt, nav_index); const target_atom = wasm.getAtom(target_atom_index); const target_symbol_index = @intFromEnum(target_atom.sym_index); - switch (ip.indexToKey(nav.status.resolved.val)) { - .@"extern" => |@"extern"| try zig_object.addOrUpdateImport( + if (nav.getExtern(ip)) |@"extern"| { + try zig_object.addOrUpdateImport( wasm, nav.name.toSlice(ip), target_atom.sym_index, @"extern".lib_name.toSlice(ip), null, - ), - else => {}, + ); } std.debug.assert(reloc_info.parent.atom_index != 0); @@ -945,8 +944,8 @@ pub fn freeNav(zig_object: *ZigObject, wasm: *Wasm, nav_index: InternPool.Nav.In segment.name = &.{}; // Ensure no accidental double free } - const nav_val = zcu.navValue(nav_index).toIntern(); - if (ip.indexToKey(nav_val) == .@"extern") { + const nav = ip.getNav(nav_index); + if (nav.getExtern(ip) != null) { std.debug.assert(zig_object.imports.remove(atom.sym_index)); } std.debug.assert(wasm.symbol_atom.remove(atom.symbolLoc())); @@ -960,7 +959,7 @@ pub fn freeNav(zig_object: *ZigObject, wasm: *Wasm, nav_index: InternPool.Nav.In if (sym.isGlobal()) { std.debug.assert(zig_object.global_syms.remove(atom.sym_index)); } - if (ip.isFunctionType(ip.typeOf(nav_val))) { + if (ip.isFunctionType(nav.typeOf(ip))) { zig_object.functions_free_list.append(gpa, sym.index) catch {}; std.debug.assert(zig_object.atom_types.remove(atom_index)); } else { diff --git a/src/print_zir.zig b/src/print_zir.zig index f8f7db89e8..80fbb908b6 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -542,7 +542,6 @@ const Writer = struct { .@"asm" => try self.writeAsm(stream, extended, false), .asm_expr => try self.writeAsm(stream, extended, true), - .variable => try self.writeVarExtended(stream, extended), .alloc => try self.writeAllocExtended(stream, extended), .compile_log => try self.writeNodeMultiOp(stream, extended), @@ -2347,7 +2346,6 @@ const Writer = struct { inferred_error_set, false, false, - false, .none, &.{}, @@ -2371,13 +2369,6 @@ const Writer = struct { var ret_ty_ref: Zir.Inst.Ref = .none; var ret_ty_body: []const Zir.Inst.Index = &.{}; - if (extra.data.bits.has_lib_name) { - const lib_name = self.code.nullTerminatedString(@enumFromInt(self.code.extra[extra_index])); - extra_index += 1; - try stream.print("lib_name=\"{}\", ", .{std.zig.fmtEscapes(lib_name)}); - } - try self.writeFlag(stream, "test, ", extra.data.bits.is_test); - if (extra.data.bits.has_cc_body) { const body_len = self.code.extra[extra_index]; extra_index += 1; @@ -2414,7 +2405,6 @@ const Writer = struct { stream, extra.data.bits.is_inferred_error, extra.data.bits.is_var_args, - extra.data.bits.is_extern, extra.data.bits.is_noinline, cc_ref, cc_body, @@ -2427,36 +2417,6 @@ const Writer = struct { ); } - fn writeVarExtended(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void { - const extra = self.code.extraData(Zir.Inst.ExtendedVar, extended.operand); - const small = @as(Zir.Inst.ExtendedVar.Small, @bitCast(extended.small)); - - try self.writeInstRef(stream, extra.data.var_type); - - var extra_index: usize = extra.end; - if (small.has_lib_name) { - const lib_name_index: Zir.NullTerminatedString = @enumFromInt(self.code.extra[extra_index]); - const lib_name = self.code.nullTerminatedString(lib_name_index); - extra_index += 1; - try stream.print(", lib_name=\"{}\"", .{std.zig.fmtEscapes(lib_name)}); - } - const align_inst: Zir.Inst.Ref = if (!small.has_align) .none else blk: { - const align_inst = @as(Zir.Inst.Ref, @enumFromInt(self.code.extra[extra_index])); - extra_index += 1; - break :blk align_inst; - }; - const init_inst: Zir.Inst.Ref = if (!small.has_init) .none else blk: { - const init_inst = @as(Zir.Inst.Ref, @enumFromInt(self.code.extra[extra_index])); - extra_index += 1; - break :blk init_inst; - }; - try self.writeFlag(stream, ", is_extern", small.is_extern); - try self.writeFlag(stream, ", is_threadlocal", small.is_threadlocal); - try self.writeOptionalInstRef(stream, ", align=", align_inst); - try self.writeOptionalInstRef(stream, ", init=", init_inst); - try stream.writeAll("))"); - } - fn writeAllocExtended(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void { const extra = self.code.extraData(Zir.Inst.AllocExtended, extended.operand); const small = @as(Zir.Inst.AllocExtended.Small, @bitCast(extended.small)); @@ -2604,7 +2564,6 @@ const Writer = struct { stream: anytype, inferred_error_set: bool, var_args: bool, - is_extern: bool, is_noinline: bool, cc_ref: Zir.Inst.Ref, cc_body: []const Zir.Inst.Index, @@ -2618,7 +2577,6 @@ const Writer = struct { try self.writeOptionalInstRefOrBody(stream, "cc=", cc_ref, cc_body); try self.writeOptionalInstRefOrBody(stream, "ret_ty=", ret_ty_ref, ret_ty_body); try self.writeFlag(stream, "vargs, ", var_args); - try self.writeFlag(stream, "extern, ", is_extern); try self.writeFlag(stream, "inferror, ", inferred_error_set); try self.writeFlag(stream, "noinline, ", is_noinline); @@ -2664,56 +2622,58 @@ const Writer = struct { } fn writeDeclaration(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].declaration; - const extra = self.code.extraData(Zir.Inst.Declaration, inst_data.payload_index); + const decl = self.code.getDeclaration(inst); const prev_parent_decl_node = self.parent_decl_node; defer self.parent_decl_node = prev_parent_decl_node; - self.parent_decl_node = inst_data.src_node; + self.parent_decl_node = decl.src_node; - if (extra.data.flags.is_pub) try stream.writeAll("pub "); - if (extra.data.flags.is_export) try stream.writeAll("export "); - switch (extra.data.name) { + if (decl.is_pub) try stream.writeAll("pub "); + switch (decl.linkage) { + .normal => {}, + .@"export" => try stream.writeAll("export "), + .@"extern" => try stream.writeAll("extern "), + } + switch (decl.kind) { .@"comptime" => try stream.writeAll("comptime"), .@"usingnamespace" => try stream.writeAll("usingnamespace"), .unnamed_test => try stream.writeAll("test"), - _ => { - const name = extra.data.name.toString(self.code).?; - const prefix = if (extra.data.name.isNamedTest(self.code)) p: { - break :p if (extra.data.flags.test_is_decltest) "decltest " else "test "; - } else ""; - try stream.print("{s}'{s}'", .{ prefix, self.code.nullTerminatedString(name) }); + .@"test", .decltest, .@"const", .@"var" => { + try stream.print("{s} '{s}'", .{ @tagName(decl.kind), self.code.nullTerminatedString(decl.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.src_line, std.fmt.fmtSliceHexLower(&src_hash_bytes) }); + const src_hash = self.code.getAssociatedSrcHash(inst).?; + try stream.print(" line({d}) column({d}) hash({})", .{ + decl.src_line, + decl.src_column, + std.fmt.fmtSliceHexLower(&src_hash), + }); { - const bodies = extra.data.getBodies(@intCast(extra.end), self.code); - - try stream.writeAll(" value="); - try self.writeBracedDecl(stream, bodies.value_body); + if (decl.type_body) |b| { + try stream.writeAll(" type="); + try self.writeBracedDecl(stream, b); + } - if (bodies.align_body) |b| { + if (decl.align_body) |b| { try stream.writeAll(" align="); try self.writeBracedDecl(stream, b); } - if (bodies.linksection_body) |b| { + if (decl.linksection_body) |b| { try stream.writeAll(" linksection="); try self.writeBracedDecl(stream, b); } - if (bodies.addrspace_body) |b| { + if (decl.addrspace_body) |b| { try stream.writeAll(" addrspace="); try self.writeBracedDecl(stream, b); } + + if (decl.value_body) |b| { + try stream.writeAll(" value="); + try self.writeBracedDecl(stream, b); + } } try stream.writeAll(") "); diff --git a/test/behavior/globals.zig b/test/behavior/globals.zig index 89dc20c5c7..7c5645be19 100644 --- a/test/behavior/globals.zig +++ b/test/behavior/globals.zig @@ -66,3 +66,99 @@ test "global loads can affect liveness" { S.f(); try std.testing.expect(y.a == 1); } + +test "global const can be self-referential" { + const S = struct { + self: *const @This(), + x: u32, + + const foo: @This() = .{ .self = &foo, .x = 123 }; + }; + + try std.testing.expect(S.foo.x == 123); + try std.testing.expect(S.foo.self.x == 123); + try std.testing.expect(S.foo.self.self.x == 123); + try std.testing.expect(S.foo.self == &S.foo); + try std.testing.expect(S.foo.self.self == &S.foo); +} + +test "global var can be self-referential" { + const S = struct { + self: *@This(), + x: u32, + + var foo: @This() = .{ .self = &foo, .x = undefined }; + }; + + S.foo.x = 123; + + try std.testing.expect(S.foo.x == 123); + try std.testing.expect(S.foo.self.x == 123); + try std.testing.expect(S.foo.self == &S.foo); + + S.foo.self.x = 456; + + try std.testing.expect(S.foo.x == 456); + try std.testing.expect(S.foo.self.x == 456); + try std.testing.expect(S.foo.self == &S.foo); + + S.foo.self.self.x = 789; + + try std.testing.expect(S.foo.x == 789); + try std.testing.expect(S.foo.self.x == 789); + try std.testing.expect(S.foo.self == &S.foo); +} + +test "global const can be indirectly self-referential" { + const S = struct { + other: *const @This(), + x: u32, + + const foo: @This() = .{ .other = &bar, .x = 123 }; + const bar: @This() = .{ .other = &foo, .x = 456 }; + }; + + try std.testing.expect(S.foo.x == 123); + try std.testing.expect(S.foo.other.x == 456); + try std.testing.expect(S.foo.other.other.x == 123); + try std.testing.expect(S.foo.other.other.other.x == 456); + try std.testing.expect(S.foo.other == &S.bar); + try std.testing.expect(S.foo.other.other == &S.foo); + + try std.testing.expect(S.bar.x == 456); + try std.testing.expect(S.bar.other.x == 123); + try std.testing.expect(S.bar.other.other.x == 456); + try std.testing.expect(S.bar.other.other.other.x == 123); + try std.testing.expect(S.bar.other == &S.foo); + try std.testing.expect(S.bar.other.other == &S.bar); +} + +test "global var can be indirectly self-referential" { + const S = struct { + other: *@This(), + x: u32, + + var foo: @This() = .{ .other = &bar, .x = undefined }; + var bar: @This() = .{ .other = &foo, .x = undefined }; + }; + + S.foo.other.x = 123; // bar.x + S.foo.other.other.x = 456; // foo.x + + try std.testing.expect(S.foo.x == 456); + try std.testing.expect(S.foo.other.x == 123); + try std.testing.expect(S.foo.other.other.x == 456); + try std.testing.expect(S.foo.other.other.other.x == 123); + try std.testing.expect(S.foo.other == &S.bar); + try std.testing.expect(S.foo.other.other == &S.foo); + + S.bar.other.x = 111; // foo.x + S.bar.other.other.x = 222; // bar.x + + try std.testing.expect(S.bar.x == 222); + try std.testing.expect(S.bar.other.x == 111); + try std.testing.expect(S.bar.other.other.x == 222); + try std.testing.expect(S.bar.other.other.other.x == 111); + try std.testing.expect(S.bar.other == &S.foo); + try std.testing.expect(S.bar.other.other == &S.bar); +} diff --git a/test/cases/compile_errors/address_of_threadlocal_not_comptime_known.zig b/test/cases/compile_errors/address_of_threadlocal_not_comptime_known.zig index d2d62c82ab..1a6b649094 100644 --- a/test/cases/compile_errors/address_of_threadlocal_not_comptime_known.zig +++ b/test/cases/compile_errors/address_of_threadlocal_not_comptime_known.zig @@ -10,4 +10,5 @@ pub export fn entry() void { // target=native // // :2:36: error: unable to resolve comptime value -// :2:36: note: container level variable initializers must be comptime-known +// :2:36: note: global variable initializer must be comptime-known +// :2:36: note: thread local and dll imported variables have runtime-known addresses diff --git a/test/cases/compile_errors/self_reference_missing_const.zig b/test/cases/compile_errors/self_reference_missing_const.zig new file mode 100644 index 0000000000..72b0ac1561 --- /dev/null +++ b/test/cases/compile_errors/self_reference_missing_const.zig @@ -0,0 +1,11 @@ +const S = struct { self: *S, x: u32 }; +const s: S = .{ .self = &s, .x = 123 }; + +comptime { + _ = s; +} + +// error +// +// :2:18: error: expected type '*tmp.S', found '*const tmp.S' +// :2:18: note: cast discards const qualifier diff --git a/test/cases/compile_errors/type_variables_must_be_constant.zig b/test/cases/compile_errors/type_variables_must_be_constant.zig index 1dbddc126c..4789ea3e92 100644 --- a/test/cases/compile_errors/type_variables_must_be_constant.zig +++ b/test/cases/compile_errors/type_variables_must_be_constant.zig @@ -7,5 +7,5 @@ export fn entry() foo { // backend=stage2 // target=native // -// :1:5: error: variable of type 'type' must be const or comptime -// :1:5: note: types are not available at runtime +// :1:11: error: variable of type 'type' must be const or comptime +// :1:11: note: types are not available at runtime diff --git a/test/cases/compile_errors/use_invalid_number_literal_as_array_index.zig b/test/cases/compile_errors/use_invalid_number_literal_as_array_index.zig index 437d100c0e..b1d101efaa 100644 --- a/test/cases/compile_errors/use_invalid_number_literal_as_array_index.zig +++ b/test/cases/compile_errors/use_invalid_number_literal_as_array_index.zig @@ -8,5 +8,5 @@ export fn entry() void { // backend=stage2 // target=native // -// :1:5: error: variable of type 'comptime_int' must be const or comptime -// :1:5: note: to modify this variable at runtime, it must be given an explicit fixed-size number type +// :1:9: error: variable of type 'comptime_int' must be const or comptime +// :1:9: note: to modify this variable at runtime, it must be given an explicit fixed-size number type |
