From 18362ebe13ece2ea7c4f57303ec4687f55d2dba5 Mon Sep 17 00:00:00 2001 From: mlugg Date: Tue, 17 Dec 2024 00:41:01 +0000 Subject: [PATCH 1/3] Zir: refactor `declaration` instruction representation The new representation is often more compact. It is also more straightforward to understand: for instance, `extern` is represented on the `declaration` instruction itself rather than using a special instruction. The same applies to `var`, making both of these far more compact. This commit also separates the type and value bodies of a `declaration` instruction. This is a prerequisite for #131. In general, `declaration` now directly encodes details of the syntax form used, and the embedded ZIR bodies are for actual expressions. The only exception to this is functions, where ZIR is effectively designed as if we had #1717. `extern fn` declarations are modeled as `extern const` with a function type, and normal `fn` definitions are modeled as `const` with a `func{,_fancy,_inferred}` instruction. This may change in the future, but improving on this was out of scope for this commit. --- lib/std/zig/AstGen.zig | 1118 +++++++++-------- lib/std/zig/Zir.zig | 487 +++++-- src/InternPool.zig | 7 - src/Sema.zig | 264 +--- src/Zcu.zig | 79 +- src/Zcu/PerThread.zig | 236 ++-- src/codegen.zig | 2 +- src/codegen/llvm.zig | 3 +- src/link/Dwarf.zig | 60 +- src/link/Wasm/ZigObject.zig | 2 +- src/print_zir.zig | 98 +- ...ress_of_threadlocal_not_comptime_known.zig | 3 +- .../type_variables_must_be_constant.zig | 4 +- ..._invalid_number_literal_as_array_index.zig | 4 +- 14 files changed, 1248 insertions(+), 1119 deletions(-) diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index fcec69ed8f5e..4a9b948beb53 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 f2aceb14ae52..0f9611aa308a 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/InternPool.zig b/src/InternPool.zig index f923332f88e7..920f0df4ee49 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -2018,7 +2018,6 @@ pub const Key = union(enum) { ty: Index, init: Index, owner_nav: Nav.Index, - lib_name: OptionalNullTerminatedString, is_threadlocal: bool, is_weak_linkage: bool, }; @@ -2741,7 +2740,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; }, @@ -5573,9 +5571,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) { @@ -6928,7 +6923,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, } }; @@ -7575,7 +7569,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, diff --git a/src/Sema.zig b/src/Sema.zig index 110476adf9e3..75d8be47590b 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", .{}); } @@ -9525,8 +9518,8 @@ fn zirFunc( } else sema.owner.unwrap().cau; 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 zir_decl = sema.code.getDeclaration(decl_inst); + break :exported zir_decl.linkage == .@"export"; }; if (fn_is_exported) { break :cc target.cCallingConvention() orelse { @@ -9557,10 +9550,8 @@ fn zirFunc( ret_ty, false, inferred_error_set, - false, has_body, src_locs, - null, 0, false, ); @@ -9619,7 +9610,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 +9834,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,7 +9987,6 @@ 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); @@ -10050,32 +10038,6 @@ 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(), @@ -26711,135 +26673,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 +26690,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; @@ -26895,8 +26721,8 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A 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) { + const zir_decl = sema.code.getDeclaration(decl_inst.resolve(&zcu.intern_pool) orelse return error.AnalysisFail); + 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 +26784,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 +26794,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, ); @@ -27467,7 +27290,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, @@ -29881,7 +29704,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, @@ -38843,13 +38666,6 @@ fn getOwnerCauNav(sema: *Sema) InternPool.Nav.Index { 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 { diff --git a/src/Zcu.zig b/src/Zcu.zig index 771bfdcf6108..bb6a25802e28 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -2679,24 +2679,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 +2697,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 +2705,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 +2721,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; }, }; @@ -3353,20 +3342,20 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv 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); + const want_analysis = switch (decl.kind) { .@"usingnamespace" => unreachable, + .@"const", .@"var" => unreachable, .@"comptime" => true, - else => a: { + .unnamed_test => comp.config.is_test and file.mod == zcu.main_mod, + .@"test", .decltest => 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 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; break :a true; }, }; @@ -3388,8 +3377,8 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv 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 decl = zir.getDeclaration(inst_info.inst); + if (decl.linkage == .@"export") { const unit = AnalUnit.wrap(.{ .cau = cau }); if (!result.contains(unit)) { log.debug("type '{}': ref cau %{}", .{ @@ -3407,8 +3396,8 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv 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 decl = zir.getDeclaration(inst_info.inst); + if (decl.linkage == .@"export") { const unit = AnalUnit.wrap(.{ .cau = cau }); if (!result.contains(unit)) { log.debug("type '{}': ref cau %{}", .{ @@ -3522,9 +3511,7 @@ 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 { diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index ca2e76bcf70a..2cf6e3670c49 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, @@ -1252,10 +1244,7 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult { }; 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) }; - }; + const zir_decl = zir.getDeclaration(inst_info.inst); // 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 @@ -1265,7 +1254,134 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult { .nav => |nav| ip.getNav(nav), }; - const result_ref = try sema.resolveInlineBody(&block, decl_bodies.value_body, inst_info.inst); + 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 }); + + // 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. + + const decl_ty: Type, const early_val: ?Value = if (zir_decl.type_body) |type_body| ty: { + // We evaluate only the type now; no need for the value yet. + const uncoerced_type_ref = try sema.resolveInlineBody(&block, type_body, inst_info.inst); + const type_ref = try sema.coerce(&block, .type, uncoerced_type_ref, ty_src); + break :ty .{ .fromInterned(type_ref.toInterned().?), null }; + } else ty: { + // We don't have a type body, so we need to evaluate the value immediately. + const value_body = zir_decl.value_body.?; + const result_ref = try sema.resolveInlineBody(&block, value_body, inst_info.inst); + const val = try sema.resolveFinalDeclValue(&block, init_src, result_ref); + break :ty .{ val.typeOf(zcu), val }; + }; + + switch (zir_decl.kind) { + .unnamed_test, .@"test", .decltest => assert(decl_ty.zigTypeTag(zcu) == .@"fn"), + .@"comptime" => assert(decl_ty.toIntern() == .void_type), + .@"usingnamespace" => {}, + .@"const" => {}, + .@"var" => try sema.validateVarType( + &block, + if (zir_decl.type_body != null) ty_src else init_src, + decl_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 alignment: InternPool.Alignment = a: { + const align_body = zir_decl.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 = zir_decl.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 (zir_decl.kind) { + .@"var" => .variable, + else => switch (decl_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, inst_info.inst); + break :as try sema.analyzeAsAddressSpace(&block, addrspace_src, addrspace_ref, addrspace_ctx); + }; + + // Lastly, we must evaluate the value if we have not already done so. Note, however, that extern declarations + // don't have an associated value body. + + const final_val: ?Value = early_val orelse if (zir_decl.value_body) |value_body| val: { + // Put the resolved type into `inst_map` to be used as the result type of the init. + try sema.inst_map.ensureSpaceForInstructions(gpa, &.{inst_info.inst}); + sema.inst_map.putAssumeCapacity(inst_info.inst, Air.internedToRef(decl_ty.toIntern())); + const uncoerced_result_ref = try sema.resolveInlineBody(&block, value_body, inst_info.inst); + assert(sema.inst_map.remove(inst_info.inst)); + + const result_ref = try sema.coerce(&block, decl_ty, uncoerced_result_ref, init_src); + break :val try sema.resolveFinalDeclValue(&block, init_src, result_ref); + } else null; + + // TODO: missing validation? + + const decl_val: Value = switch (zir_decl.linkage) { + .normal, .@"export" => switch (zir_decl.kind) { + .@"var" => .fromInterned(try pt.intern(.{ .variable = .{ + .ty = decl_ty.toIntern(), + .init = final_val.?.toIntern(), + .owner_nav = cau.owner.unwrap().nav, + .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_info.name, + .ty = decl_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 = alignment, + .@"addrspace" = @"addrspace", + .zir_index = cau.zir_index, // `declaration` instruction + .owner_nav = undefined, // ignored by `getExtern` + })); + }, + }; const nav_index = switch (cau.owner.unwrap()) { .none => { @@ -1282,15 +1398,6 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult { .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 @@ -1331,50 +1438,10 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult { }; // 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())) { + 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 decl_ty.comptimeOnlySema(pt)) { @@ -1383,13 +1450,13 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult { .func => "function alias", // slightly clearer message, since you *can* specify these on function *declarations* else => "comptime-only type", }; - if (decl_bodies.align_body != null) { + if (zir_decl.align_body != null) { return sema.fail(&block, align_src, "cannot specify alignment of {s}", .{reason}); } - if (decl_bodies.linksection_body != null) { + if (zir_decl.linksection_body != null) { return sema.fail(&block, section_src, "cannot specify linksection of {s}", .{reason}); } - if (decl_bodies.addrspace_body != null) { + if (zir_decl.addrspace_body != null) { return sema.fail(&block, addrspace_src, "cannot specify addrspace of {s}", .{reason}); } } @@ -1404,9 +1471,9 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult { // 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).?); + 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_index); } @@ -1919,13 +1986,11 @@ 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) { + const maybe_name: InternPool.OptionalNullTerminatedString, const kind: Kind, const is_named_test: bool = switch (decl.kind) { .@"comptime" => info: { if (iter.pass != .unnamed) return; break :info .{ @@ -1954,21 +2019,22 @@ const ScanDeclIter = struct { false, }; }, - _ => if (declaration.name.isNamedTest(zir)) info: { + .@"test", .decltest => |kind| info: { // 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"; + const prefix = @tagName(kind); break :info .{ - (try iter.avoidNameConflict("{s}.{s}", .{ prefix, zir.nullTerminatedString(declaration.name.toString(zir).?) })).toOptional(), + (try iter.avoidNameConflict("{s}.{s}", .{ prefix, zir.nullTerminatedString(decl.name) })).toOptional(), .@"test", true, }; - } else info: { + }, + .@"const", .@"var" => info: { 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, {}); @@ -2030,7 +2096,7 @@ const ScanDeclIter = struct { 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); @@ -2056,7 +2122,7 @@ const ScanDeclIter = struct { break :a true; }, .named => a: { - if (declaration.flags.is_pub) { + if (decl.is_pub) { try namespace.pub_decls.putContext(gpa, nav, {}, .{ .zcu = zcu }); } else { try namespace.priv_decls.putContext(gpa, nav, {}, .{ .zcu = zcu }); @@ -2068,7 +2134,7 @@ const ScanDeclIter = struct { }, }; - if (existing_cau == null and (want_analysis or declaration.flags.is_export)) { + if (existing_cau == 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 }, diff --git a/src/codegen.zig b/src/codegen.zig index 2b179979f0e5..898629d69b22 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -853,7 +853,7 @@ 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 }, + .variable => |variable| .{ variable.owner_nav, false, .none, variable.is_threadlocal }, .@"extern" => |@"extern"| .{ @"extern".owner_nav, true, @"extern".lib_name, @"extern".is_threadlocal }, else => .{ ref_nav_index, false, .none, false }, }; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index b94ea0799573..154a7114cf76 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -2939,7 +2939,6 @@ pub const Object = struct { 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 }, }; @@ -4803,7 +4802,7 @@ pub const NavGen = struct { const resolved = nav.status.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 }, }; diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index 426b9d21c943..20db035c93f0 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); 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 }; @@ -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); 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 }; @@ -2341,24 +2319,13 @@ pub fn initWipNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool.Nav.In }, .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); 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 }; @@ -2585,12 +2552,11 @@ 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. @@ -2601,7 +2567,7 @@ pub fn updateComptimeNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool const parent_namespace_ptr = ip.namespacePtr(ip.getCau(cau).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 }; @@ -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/Wasm/ZigObject.zig b/src/link/Wasm/ZigObject.zig index 0b5d2efb47a0..0166b28743f8 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 diff --git a/src/print_zir.zig b/src/print_zir.zig index f8f7db89e8d9..80fbb908b678 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/cases/compile_errors/address_of_threadlocal_not_comptime_known.zig b/test/cases/compile_errors/address_of_threadlocal_not_comptime_known.zig index d2d62c82abde..1a6b649094ab 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/type_variables_must_be_constant.zig b/test/cases/compile_errors/type_variables_must_be_constant.zig index 1dbddc126cae..4789ea3e920e 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 437d100c0ef1..b1d101efaa7f 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 From 40aafcd6a85d3c517f445f17149c17523c832420 Mon Sep 17 00:00:00 2001 From: mlugg Date: Sun, 22 Dec 2024 21:16:29 +0000 Subject: [PATCH 2/3] compiler: remove Cau The `Cau` abstraction originated from noting that one of the two primary roles of the legacy `Decl` type was to be the subject of comptime semantic analysis. However, the data stored in `Cau` has always had some level of redundancy. While preparing for #131, I went to remove that redundany, and realised that `Cau` now had exactly one field: `owner`. This led me to conclude that `Cau` is, in fact, an unnecessary level of abstraction over what are in reality *fundamentally different* kinds of analysis unit (`AnalUnit`). Types, `Nav` vals, and `comptime` declarations are all analyzed in different ways, and trying to treat them as the same thing is counterproductive! So, these 3 cases are now different alternatives in `AnalUnit`. To avoid stealing bits from `InternPool`-based IDs, which are already a little starved for bits due to the sharding datastructures, `AnalUnit` is expanded to 64 bits (30 of which are currently unused). This doesn't impact memory usage too much by default, because we don't store `AnalUnit`s all too often; however, we do store them a lot under `-fincremental`, so a non-trivial bump to peak RSS can be observed there. This will be improved in the future when I made `InternPool.DepEntry` less memory-inefficient. `Zcu.PerThread.ensureCauAnalyzed` is split into 3 functions, for each of the 3 new types of `AnalUnit`. The new logic is much easier to understand, because it avoids conflating the logic of these fundamentally different cases. --- src/Compilation.zig | 73 +- src/InternPool.zig | 429 ++++----- src/Sema.zig | 228 ++--- src/Type.zig | 4 +- src/Zcu.zig | 182 ++-- src/Zcu/PerThread.zig | 1913 ++++++++++++++++++++--------------------- src/link/Dwarf.zig | 16 +- 7 files changed, 1321 insertions(+), 1524 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 8b158390b660..6f9b2e18d62b 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, @@ -3141,8 +3144,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| 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 +3379,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| 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 +3644,13 @@ 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_val, + .type, + => .{ .analyze_comptime_unit = outdated }, + }); continue; } } @@ -3667,8 +3673,8 @@ 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; } @@ -3688,36 +3694,47 @@ 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_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.resolved.val); } }, .resolve_type_fully => |ty| { diff --git a/src/InternPool.zig b/src/InternPool.zig index 920f0df4ee49..41019ea9d989 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -363,33 +363,53 @@ 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, + 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 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 +420,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 +460,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,13 +472,16 @@ 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 value of this `Nav` is resolved. resolved: struct { @@ -544,17 +505,16 @@ pub const Nav = struct { /// 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. + // A `Nav` which does not undergo analysis always has a resolved value. return switch (ip.indexToKey(nav.status.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,11 +560,13 @@ 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, + // 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 == .resolved`. val: InternPool.Index, /// Populated only if `bits.status == .resolved`. @@ -625,7 +587,13 @@ 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, @@ -646,7 +614,8 @@ pub const Nav = struct { return .{ .name = nav.name, .fqn = nav.fqn, - .analysis_owner = nav.analysis_owner, + .analysis_namespace = if (nav.analysis) |a| a.namespace.toOptional() else .none, + .analysis_zir_index = if (nav.analysis) |a| a.zir_index.toOptional() else .none, .val = switch (nav.status) { .unresolved => .none, .resolved => |r| r.val, @@ -862,8 +831,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 +845,8 @@ const Local = struct { tracked_insts: TrackedInsts, files: List(File), maps: Maps, - caus: Caus, navs: Navs, + comptime_units: ComptimeUnits, namespaces: Namespaces, @@ -899,8 +868,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 +1244,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, }; } @@ -3052,8 +3021,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. @@ -3370,7 +3337,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, @@ -3387,8 +3353,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, @@ -3979,7 +3943,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").?]; @@ -4066,7 +4029,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, @@ -4083,7 +4045,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").?]); @@ -4130,7 +4091,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", @@ -4153,9 +4113,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. @@ -4232,21 +4189,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 = .{ @@ -4272,21 +4223,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 = .{ @@ -5256,7 +5201,6 @@ pub const Tag = enum(u8) { .payload = EnumExplicit, .trailing = struct { owner_union: Index, - cau: ?Cau.Index, captures: ?[]CaptureValue, type_hash: ?u64, field_names: []NullTerminatedString, @@ -5302,7 +5246,6 @@ pub const Tag = enum(u8) { .payload = EnumAuto, .trailing = struct { owner_union: ?Index, - cau: ?Cau.Index, captures: ?[]CaptureValue, type_hash: ?u64, field_names: []NullTerminatedString, @@ -5679,7 +5622,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, @@ -5710,7 +5652,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, @@ -5758,7 +5699,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, @@ -6088,11 +6028,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. @@ -6115,10 +6054,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. @@ -6408,32 +6346,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, }, }); @@ -6506,7 +6444,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); @@ -6525,8 +6464,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); @@ -6711,14 +6648,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, } }, } }; @@ -6735,14 +6672,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, } }, } }; @@ -8323,7 +8260,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) { @@ -8375,7 +8311,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").?, } }; } @@ -8384,7 +8319,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( @@ -8400,18 +8334,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; @@ -8510,7 +8437,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` @@ -8555,7 +8481,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").?, } }; }, @@ -8578,7 +8503,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, @@ -8647,7 +8571,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").?, } }; } @@ -9383,7 +9306,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}", .{ @@ -9429,7 +9352,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, @@ -9449,13 +9371,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); } @@ -9556,7 +9476,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{ @@ -9577,8 +9496,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))}), @@ -9591,7 +9508,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), @@ -9616,7 +9532,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 @@ -9643,8 +9558,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))}), @@ -9661,7 +9574,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), @@ -9858,7 +9770,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").?, }, }; @@ -9974,7 +9885,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, @@ -10037,7 +9947,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, @@ -11058,12 +10967,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(); @@ -11077,51 +10980,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, @@ -11143,7 +11029,7 @@ pub fn createNav( try navs.append(Nav.pack(.{ .name = opts.name, .fqn = opts.fqn, - .analysis_owner = .none, + .analysis = null, .status = .{ .resolved = .{ .val = opts.val, .alignment = opts.alignment, @@ -11155,10 +11041,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, @@ -11168,36 +11053,28 @@ 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 value of a `Nav` with an analysis owner. @@ -11220,12 +11097,14 @@ pub fn resolveNavValue( const navs = local.shared.navs.view(); - const nav_analysis_owners = navs.items(.analysis_owner); + const nav_analysis_namespace = navs.items(.analysis_namespace); + const nav_analysis_zir_index = navs.items(.analysis_zir_index); const nav_vals = navs.items(.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); diff --git a/src/Sema.zig b/src/Sema.zig index 75d8be47590b..a41762f530ac 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2870,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. @@ -2900,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 }, ); } @@ -2922,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( @@ -3100,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. @@ -3136,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( @@ -3155,7 +3151,6 @@ fn zirEnumDecl( tracked_inst, new_namespace_index, type_name, - new_cau_index, small, body, tag_type_ref, @@ -3245,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. @@ -3275,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 }, ); } @@ -3297,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( @@ -3382,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( @@ -6547,7 +6540,10 @@ 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, + .type, + => return, // does nothing outside a function }; ip.funcSetDisableInstrumentation(func); sema.allow_memoize = false; @@ -6868,11 +6864,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_val => |nav| nav, }; var i: usize = 0; while (i < candidates.items.len) { @@ -7132,7 +7125,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_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; @@ -7747,11 +7740,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 @@ -7759,7 +7750,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, ); @@ -7769,7 +7760,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, @@ -7780,7 +7771,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, }; @@ -7795,7 +7786,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 @@ -7904,7 +7895,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) { @@ -8016,7 +8007,7 @@ fn analyzeCall( if (call_dbg_node) |some| try sema.zirDbgStmt(block, some); switch (sema.owner.unwrap()) { - .cau => {}, + .@"comptime", .nav_val, .type => {}, .func => |owner_func| if (Type.fromInterned(func_ty_info.return_type).isError(zcu)) { ip.funcSetCallsOrAwaitsErrorableFn(owner_func); }, @@ -8268,10 +8259,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()); @@ -8312,11 +8302,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); @@ -8481,7 +8471,7 @@ fn instantiateGenericCall( if (call_dbg_node) |some| try sema.zirDbgStmt(block, some); switch (sema.owner.unwrap()) { - .cau => {}, + .@"comptime", .nav_val, .type => {}, .func => |owner_func| if (Type.fromInterned(func_ty_info.return_type).isError(zcu)) { ip.funcSetCallsOrAwaitsErrorableFn(owner_func); }, @@ -9510,14 +9500,11 @@ 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 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"; }; @@ -9991,7 +9978,7 @@ fn funcCommon( 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, @@ -10040,7 +10027,7 @@ fn funcCommon( 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, @@ -17664,7 +17651,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; } @@ -18155,7 +18142,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 => {}, @@ -19321,10 +19308,8 @@ 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); @@ -21187,14 +21172,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, }; @@ -21618,7 +21602,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_val, .type => {}, } return Air.internedToRef(try pt.intern(.{ .opt = .{ .ty = opt_ptr_stack_trace_ty.toIntern(), @@ -22296,7 +22280,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)); @@ -22505,11 +22489,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; @@ -22811,8 +22793,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; @@ -22822,7 +22802,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( @@ -23170,8 +23150,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; @@ -23181,7 +23159,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 { @@ -26713,15 +26691,13 @@ 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); + 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 @@ -27108,8 +27084,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_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` }); @@ -32670,28 +32654,25 @@ pub fn ensureNavResolved(sema: *Sema, src: LazySrcLoc, nav_index: InternPool.Nav const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); - - const cau_index = nav.analysis_owner.unwrap() orelse { + if (nav.analysis == null) { assert(nav.status == .resolved); return; - }; + } - // Note that even if `nav.status == .resolved`, we must still trigger `ensureCauAnalyzed` + // Note that even if `nav.status == .resolved`, we must still trigger `ensureNavValUpToDate` // to make sure the value is up-to-date on incremental updates. - assert(ip.getCau(cau_index).owner.unwrap().nav == nav_index); - - const anal_unit = AnalUnit.wrap(.{ .cau = cau_index }); + const anal_unit: AnalUnit = .wrap(.{ .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); + return pt.ensureNavValUpToDate(nav_index); } fn optRefValue(sema: *Sema, opt_val: ?Value) !Value { @@ -35641,7 +35622,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); @@ -35684,7 +35665,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; @@ -35831,15 +35812,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, @@ -35971,7 +35950,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)); @@ -36011,7 +35990,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) { @@ -36126,7 +36105,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); @@ -36149,7 +36128,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 => {}, @@ -36184,7 +36163,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; @@ -36210,7 +36189,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; @@ -36239,7 +36218,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 => {}, @@ -36315,7 +36294,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` @@ -36472,8 +36451,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; @@ -36671,8 +36649,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); @@ -38474,13 +38451,11 @@ pub fn declareDependency(sema: *Sema, dependee: InternPool.Dependee) !void { // just result in over-analysis since `Zcu.findOutdatedToAnalyze` would never be able to resolve // the loop. 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 => {}, + else => {}, } try zcu.intern_pool.addDependency(sema.gpa, sema.owner, dependee); @@ -38659,38 +38634,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 `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. @@ -38701,7 +38644,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, @@ -38719,7 +38661,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(); @@ -38943,6 +38885,6 @@ 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().?); + try pt.ensureNavValUpToDate(nav); return Air.internedToRef(ip.getNav(nav).status.resolved.val); } diff --git a/src/Type.zig b/src/Type.zig index 850e50c82ed1..b47a45d1fc30 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/Zcu.zig b/src/Zcu.zig index bb6a25802e28..d374a0aa0c09 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -192,7 +192,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`. @@ -344,9 +344,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; @@ -2436,11 +2439,9 @@ 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 }), + .type => |ty| try zcu.markPoDependeeUpToDate(.{ .interned = ty }), .func => |func| try zcu.markPoDependeeUpToDate(.{ .interned = func }), } } @@ -2451,11 +2452,9 @@ 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 }, // TODO: also `nav_ref` deps when introduced + .type => |ty| .{ .interned = ty }, .func => |func_index| .{ .interned = func_index }, // IES }; log.debug("potentially outdated dependee: {}", .{zcu.fmtDependee(dependee)}); @@ -2512,14 +2511,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 +2529,28 @@ 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 }, }); 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 +2564,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 @@ -3019,9 +3013,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.*); @@ -3304,23 +3298,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) { @@ -3335,24 +3328,35 @@ 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 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, .@"const", .@"var" => unreachable, - .@"comptime" => true, - .unnamed_test => comp.config.is_test and file.mod == zcu.main_mod, + .@"comptime" => unreachable, + .unnamed_test => true, .@"test", .decltest => a: { - if (!comp.config.is_test) break :a false; - if (file.mod != zcu.main_mod) break :a false; - const nav = ip.getCau(cau).owner.unwrap().nav; - const fqn_slice = ip.getNav(nav).fqn.toSlice(ip); + 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; @@ -3360,28 +3364,25 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv }, }; 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 decl = zir.getDeclaration(inst_info.inst); if (decl.linkage == .@"export") { - const unit = AnalUnit.wrap(.{ .cau = cau }); + 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), }); @@ -3391,16 +3392,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 decl = zir.getDeclaration(inst_info.inst); if (decl.linkage == .@"export") { - const unit = AnalUnit.wrap(.{ .cau = cau }); + 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), }); @@ -3411,13 +3411,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; @@ -3527,12 +3525,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 } }; } @@ -3545,19 +3537,17 @@ 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=)"); - }, + .@"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=)"); } }, + .nav_val => |nav| return writer.print("nav_val('{}')", .{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)}); diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 2cf6e3670c49..0d7ca0eb260a 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -545,144 +545,173 @@ 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); - } else { - // We can trust the current information about this `Cau`. - if (prev_failed) { - return error.AnalysisFail; - } - // 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, + // `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 unit. + if (zcu.failed_analysis.contains(anal_unit)) return error.AnalysisFail; + if (zcu.transitive_failed_analysis.contains(anal_unit)) return error.AnalysisFail; + 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) { - error.AnalysisFail => res: { + 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 `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 }; + return error.AnalysisFail; }, - 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) { - // 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 (analysis_fail) return error.AnalysisFail; } -fn ensureCauAnalyzedInner( - pt: Zcu.PerThread, - cau_index: InternPool.Cau.Index, - cau_outdated: bool, -) Zcu.SemaError!SemaCauResult { +/// 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 cau = ip.getCau(cau_index); - const anal_unit = AnalUnit.wrap(.{ .cau = cau_index }); + 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(); +} - const inst_info = cau.zir_index.resolveFull(ip) orelse return error.AnalysisFail; +/// 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: @@ -692,821 +721,826 @@ fn ensureCauAnalyzedInner( // * 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) + // * 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 - if (zcu.fileByIndex(inst_info.file).status != .success_zir) { - return error.AnalysisFail; - } - - // `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. - zcu.deleteUnitExports(anal_unit); - zcu.deleteUnitReferences(anal_unit); - if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| { - kv.value.destroy(zcu.gpa); - } - _ = zcu.transitive_failed_analysis.swapRemove(anal_unit); - } - - 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(); - - return pt.semaCau(cau_index) catch |err| switch (err) { - error.GenericPoison, error.ComptimeBreak, error.ComptimeReturn => unreachable, - error.AnalysisFail, error.OutOfMemory => |e| return e, - }; -} - -pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: InternPool.Index) Zcu.SemaError!void { - dev.check(.sema); - - const tracy = trace(@src()); - defer tracy.end(); - const zcu = pt.zcu; const gpa = zcu.gpa; const ip = &zcu.intern_pool; - // 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(.{ .nav_val = nav_id }); + const nav = ip.getNav(nav_id); - log.debug("ensureFuncBodyAnalyzed {}", .{zcu.fmtAnalUnit(anal_unit)}); + log.debug("ensureNavUpToDate {}", .{zcu.fmtAnalUnit(anal_unit)}); - const func = zcu.funcInfo(maybe_coerced_func_index); + // 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 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); + 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); - } 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 + 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 unit. + if (prev_failed) return error.AnalysisFail; + if (nav.status == .resolved) return; } - const ies_outdated, const analysis_fail = if (pt.ensureFuncBodyAnalyzedInner(func_index, func_outdated)) |result| - .{ result.ies_outdated, 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 sema_result: SemaNavResult, 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. + .invalidate_nav_val = result.invalidate_nav_val or prev_failed, + .invalidate_nav_ref = result.invalidate_nav_ref or prev_failed, + }, + false, + }; + } else |err| switch (err) { error.AnalysisFail => res: { if (!zcu.failed_analysis.contains(anal_unit)) { - // If this function 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 the IES to be outdated if the function previously succeeded analysis; 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). - break :res .{ !prev_failed, true }; + break :res .{ .{ + .invalidate_nav_val = !prev_failed, + .invalidate_nav_ref = !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; + }, + error.GenericPoison => unreachable, + error.ComptimeReturn => unreachable, + error.ComptimeBreak => unreachable, }; - if (func_outdated) { - if (ies_outdated) { - try zcu.markDependeeOutdated(.marked_po, .{ .interned = func_index }); + if (was_outdated) { + // TODO: we do not yet have separate dependencies for Nav values vs types. + const invalidate = sema_result.invalidate_nav_val or sema_result.invalidate_nav_ref; + const dependee: InternPool.Dependee = .{ .nav_val = nav_id }; + if (invalidate) { + // 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 { - try zcu.markPoDependeeUpToDate(.{ .interned = func_index }); + // 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 (analysis_fail) return error.AnalysisFail; + if (new_failed) return error.AnalysisFail; } -fn ensureFuncBodyAnalyzedInner( - pt: Zcu.PerThread, - func_index: InternPool.Index, - func_outdated: bool, -) Zcu.SemaError!struct { ies_outdated: bool } { +const SemaNavResult = packed struct { + /// Whether the value of a `decl_val` of the corresponding Nav changed. + invalidate_nav_val: bool, + /// Whether the type of a `decl_ref` of the corresponding Nav changed. + invalidate_nav_ref: bool, +}; + +fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileError!SemaNavResult { const zcu = pt.zcu; const gpa = zcu.gpa; const ip = &zcu.intern_pool; - const func = zcu.funcInfo(func_index); - const anal_unit = AnalUnit.wrap(.{ .func = func_index }); + const anal_unit: AnalUnit = .wrap(.{ .nav_val = nav_id }); + const old_nav = ip.getNav(nav_id); - // Make sure that this function is still owned by the same `Nav`. Otherwise, analyzing - // it would be a waste of time in the best case, and could cause codegen to give bogus - // results in the worst case. + log.debug("analyzeNavVal {}", .{zcu.fmtAnalUnit(anal_unit)}); - 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) { - // 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; - } - } 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) { - // 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; - } - } + 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'll want to remember what the IES used to be before the update for - // dependency invalidation purposes. - const old_resolved_ies = if (func.analysisUnordered(ip).inferred_error_set) - func.resolvedErrorSetUnordered(ip) - else - .none; + // We are about to re-analyze this unit; drop its depenndencies. + zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit); - 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); - } + try zcu.analysis_in_progress.put(gpa, anal_unit, {}); + errdefer _ = zcu.analysis_in_progress.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 - } - } + var analysis_arena: std.heap.ArenaAllocator = .init(gpa); + defer analysis_arena.deinit(); - log.debug("analyze and generate fn body {}; reason='{s}'", .{ - zcu.fmtAnalUnit(anal_unit), - if (func_outdated) "outdated" else "never analyzed", - }); + var comptime_err_ret_trace: std.ArrayList(Zcu.LazySrcLoc) = .init(gpa); + defer comptime_err_ret_trace.deinit(); - var air = try pt.analyzeFnBody(func_index); - errdefer air.deinit(gpa); + 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(); - const ies_outdated = func_outdated and - (!func.analysisUnordered(ip).inferred_error_set or func.resolvedErrorSetUnordered(ip) != old_resolved_ies); + // The comptime unit declares on the source of the corresponding declaration. + try sema.declareDependency(.{ .src_hash = old_nav.analysis.?.zir_index }); - const comp = zcu.comp; + 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 dump_air = build_options.enable_debug_extensions and comp.verbose_air; - const dump_llvm_ir = build_options.enable_debug_extensions and (comp.verbose_llvm_ir != null or comp.verbose_llvm_bc != null); + const zir_decl = zir.getDeclaration(inst_resolved.inst); - if (comp.bin_file == null and zcu.llvm_object == null and !dump_air and !dump_llvm_ir) { - air.deinit(gpa); - return .{ .ies_outdated = ies_outdated }; - } + assert(old_nav.is_usingnamespace == (zir_decl.kind == .@"usingnamespace")); - // This job depends on any resolve_type_fully jobs queued up before it. - try comp.queueJob(.{ .codegen_func = .{ - .func = func_index, - .air = air, - } }); + 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 }); - return .{ .ies_outdated = ies_outdated }; -} + // 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. -/// Takes ownership of `air`, even on error. -/// If any types referenced by `air` are unresolved, marks the codegen as failed. -pub fn linkerUpdateFunc(pt: Zcu.PerThread, func_index: InternPool.Index, air: Air) Allocator.Error!void { - const zcu = pt.zcu; - const gpa = zcu.gpa; - const ip = &zcu.intern_pool; - const comp = zcu.comp; + const nav_ty: Type, const early_val: ?Value = if (zir_decl.type_body) |type_body| ty: { + // We evaluate only the type now; no need for the value yet. + 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().?), null }; + } else ty: { + // We don't have a type body, so we need to evaluate the value immediately. + const value_body = zir_decl.value_body.?; + const result_ref = try sema.resolveInlineBody(&block, value_body, inst_resolved.inst); + const val = try sema.resolveFinalDeclValue(&block, init_src, result_ref); + break :ty .{ val.typeOf(zcu), val }; + }; - defer { - var air_mut = air; - air_mut.deinit(gpa); + 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", + ), } - const func = zcu.funcInfo(func_index); - const nav_index = func.owner_nav; - const nav = ip.getNav(nav_index); - - var liveness = try Liveness.analyze(gpa, air, ip); - defer liveness.deinit(gpa); + // Now that we know the type, we can evaluate the alignment, linksection, and addrspace, to determine + // the full pointer type of this declaration. - if (build_options.enable_debug_extensions and comp.verbose_air) { - std.debug.print("# Begin Function AIR: {}:\n", .{nav.fqn.fmt(ip)}); - @import("../print_air.zig").dump(pt, air, liveness); - std.debug.print("# End Function AIR: {}\n\n", .{nav.fqn.fmt(ip)}); - } + 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, inst_resolved.inst); + break :a try sema.analyzeAsAlign(&block, align_src, align_ref); + }; - if (std.debug.runtime_safety) { - var verify: Liveness.Verify = .{ - .gpa = gpa, - .air = air, - .liveness = liveness, - .intern_pool = ip, - }; - defer verify.deinit(); + 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, inst_resolved.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); + }; - verify.verify() catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => { - try zcu.failed_codegen.putNoClobber(gpa, nav_index, try Zcu.ErrorMsg.create( - gpa, - zcu.navSrcLoc(nav_index), - "invalid liveness: {s}", - .{@errorName(err)}, - )); - return; + 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, inst_resolved.inst); + break :as try sema.analyzeAsAddressSpace(&block, addrspace_src, addrspace_ref, addrspace_ctx); + }; + + // Lastly, we must evaluate the value if we have not already done so. Note, however, that extern declarations + // don't have an associated value body. + + const final_val: ?Value = early_val orelse if (zir_decl.value_body) |value_body| val: { + // 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(nav_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, nav_ty, uncoerced_result_ref, init_src); + break :val try sema.resolveFinalDeclValue(&block, init_src, result_ref); + } else null; + + 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 = alignment, + .@"addrspace" = @"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 => {}, } - const codegen_prog_node = zcu.codegen_prog_node.start(nav.fqn.toSlice(ip), 0); - defer codegen_prog_node.end(); + // 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); - if (!air.typesFullyResolved(zcu)) { - // A type we depend on failed to resolve. This is a transitive failure. - // Correcting this failure will involve changing a type this function - // depends on, hence triggering re-analysis of this function, so this - // interacts correctly with incremental compilation. - // TODO: do we need to mark this failure anywhere? I don't think so, since compilation - // will fail due to the type error anyway. - } else if (comp.bin_file) |lf| { - lf.updateFunc(pt, func_index, air, liveness) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => { - assert(zcu.failed_codegen.contains(nav_index)); - }, - else => { - try zcu.failed_codegen.putNoClobber(gpa, nav_index, try Zcu.ErrorMsg.create( - gpa, - zcu.navSrcLoc(nav_index), - "unable to codegen: {s}", - .{@errorName(err)}, - )); - try zcu.retryable_failures.append(zcu.gpa, AnalUnit.wrap(.{ .func = func_index })); - }, - }; - } else if (zcu.llvm_object) |llvm_object| { - llvm_object.updateFunc(pt, func_index, air, liveness) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, + // 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 .{ + .invalidate_nav_val = true, + .invalidate_nav_ref = true, }; } -} -/// https://github.com/ziglang/zig/issues/14307 -pub fn semaPkg(pt: Zcu.PerThread, pkg: *Module) !void { - dev.check(.sema); - const import_file_result = try pt.importPkg(pkg); - const root_type = pt.zcu.fileRootType(import_file_result.file_index); - if (root_type == .none) { - return pt.semaFile(import_file_result.file_index); - } -} + 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 }, + }; -fn createFileRootStruct( - pt: Zcu.PerThread, - file_index: Zcu.File.Index, - namespace_index: Zcu.Namespace.Index, - replace_existing: bool, -) Allocator.Error!InternPool.Index { - const zcu = pt.zcu; - const gpa = zcu.gpa; - const ip = &zcu.intern_pool; - const file = zcu.fileByIndex(file_index); - const extended = file.zir.instructions.items(.data)[@intFromEnum(Zir.Inst.Index.main_struct_inst)].extended; - assert(extended.opcode == .struct_decl); - const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small); - assert(!small.has_captures_len); - assert(!small.has_backing_int); - assert(small.layout == .auto); - var extra_index: usize = extended.operand + @typeInfo(Zir.Inst.StructDecl).@"struct".fields.len; - const fields_len = if (small.has_fields_len) blk: { - const fields_len = file.zir.extra[extra_index]; - extra_index += 1; - break :blk fields_len; - } else 0; - const decls_len = if (small.has_decls_len) blk: { - const decls_len = file.zir.extra[extra_index]; - extra_index += 1; - break :blk decls_len; - } else 0; - const decls = file.zir.bodySlice(extra_index, decls_len); - extra_index += decls_len; + 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}); + } + } - const tracked_inst = try ip.trackZir(gpa, pt.tid, .{ - .file = file_index, - .inst = .main_struct_inst, + ip.resolveNavValue(nav_id, .{ + .val = nav_val.toIntern(), + .alignment = alignment, + .@"linksection" = @"linksection", + .@"addrspace" = @"addrspace", }); - const wip_ty = switch (try ip.getStructType(gpa, pt.tid, .{ - .layout = .auto, - .fields_len = fields_len, - .known_non_opv = small.known_non_opv, - .requires_comptime = if (small.known_comptime_only) .yes else .unknown, - .any_comptime_fields = small.any_comptime_fields, - .any_default_inits = small.any_default_inits, - .inits_resolved = false, - .any_aligned_fields = small.any_aligned_fields, - .key = .{ .declared = .{ - .zir_index = tracked_inst, - .captures = &.{}, - } }, - }, replace_existing)) { - .existing => unreachable, // we wouldn't be analysing the file root if this type existed - .wip => |wip| wip, - }; - errdefer wip_ty.cancel(ip, pt.tid); - - 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 }), - .{ .src_hash = tracked_inst }, - ); - } + // Mark the unit as completed before evaluating the export! + assert(zcu.analysis_in_progress.swapRemove(anal_unit)); - try pt.scanNamespace(namespace_index, decls); - try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); - codegen_type: { - if (zcu.comp.config.use_llvm) break :codegen_type; - if (file.mod.strip) break :codegen_type; - // This job depends on any resolve_type_fully jobs queued up before it. - try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index }); + 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); } - zcu.setFileRootType(file_index, wip_ty.index); - return wip_ty.finish(ip, new_cau_index.toOptional(), namespace_index); -} -/// Re-scan the namespace of a file's root struct type on an incremental update. -/// The file must have successfully populated ZIR. -/// If the file's root struct type is not populated (the file is unreferenced), nothing is done. -/// This is called by `updateZirRefs` for all updated files before the main work loop. -/// This function does not perform any semantic analysis. -fn updateFileNamespace(pt: Zcu.PerThread, file_index: Zcu.File.Index) Allocator.Error!void { - const zcu = pt.zcu; + try sema.flushExports(); - const file = zcu.fileByIndex(file_index); - assert(file.status == .success_zir); - const file_root_type = zcu.fileRootType(file_index); - if (file_root_type == .none) return; + queue_codegen: { + if (!queue_linker_work) break :queue_codegen; - log.debug("updateFileNamespace mod={s} sub_file_path={s}", .{ - file.mod.fully_qualified_name, - file.sub_file_path, - }); + if (!try nav_ty.hasRuntimeBitsSema(pt)) { + if (zcu.comp.config.use_llvm) break :queue_codegen; + if (file.mod.strip) break :queue_codegen; + } - const namespace_index = Type.fromInterned(file_root_type).getNamespaceIndex(zcu); - const decls = decls: { - const extended = file.zir.instructions.items(.data)[@intFromEnum(Zir.Inst.Index.main_struct_inst)].extended; - const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small); + // This job depends on any resolve_type_fully jobs queued up before it. + try zcu.comp.queueJob(.{ .codegen_nav = nav_id }); + } - var extra_index: usize = extended.operand + @typeInfo(Zir.Inst.StructDecl).@"struct".fields.len; - extra_index += @intFromBool(small.has_fields_len); - const decls_len = if (small.has_decls_len) blk: { - const decls_len = file.zir.extra[extra_index]; - extra_index += 1; - break :blk decls_len; - } else 0; - break :decls file.zir.bodySlice(extra_index, decls_len); - }; - try pt.scanNamespace(namespace_index, decls); - zcu.namespacePtr(namespace_index).generation = zcu.generation; + switch (old_nav.status) { + .unresolved => return .{ + .invalidate_nav_val = true, + .invalidate_nav_ref = true, + }, + .resolved => |old| { + const new = ip.getNav(nav_id).status.resolved; + return .{ + .invalidate_nav_val = new.val != old.val, + .invalidate_nav_ref = ip.typeOf(new.val) != ip.typeOf(old.val) or + new.alignment != old.alignment or + new.@"linksection" != old.@"linksection" or + new.@"addrspace" != old.@"addrspace", + }; + }, + } } -fn semaFile(pt: Zcu.PerThread, file_index: Zcu.File.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()); defer tracy.end(); const zcu = pt.zcu; const gpa = zcu.gpa; - const file = zcu.fileByIndex(file_index); - assert(zcu.fileRootType(file_index) == .none); + const ip = &zcu.intern_pool; - if (file.status != .success_zir) { - return error.AnalysisFail; - } - assert(file.zir_loaded); + // 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 new_namespace_index = try pt.createNamespace(.{ - .parent = .none, - .owner_type = undefined, // set in `createFileRootStruct` - .file_scope = file_index, - .generation = zcu.generation, - }); - const struct_ty = try pt.createFileRootStruct(file_index, new_namespace_index, false); - errdefer zcu.intern_pool.remove(pt.tid, struct_ty); + log.debug("ensureFuncBodyUpToDate {}", .{zcu.fmtAnalUnit(anal_unit)}); - switch (zcu.comp.cache_use) { - .whole => |whole| if (whole.cache_manifest) |man| { - const source = file.getSource(gpa) catch |err| { - try pt.reportRetryableFileError(file_index, "unable to load source: {s}", .{@errorName(err)}); - return error.AnalysisFail; - }; + const func = zcu.funcInfo(maybe_coerced_func_index); - const resolved_path = std.fs.path.resolve(gpa, &.{ - file.mod.root.root_dir.path orelse ".", - file.mod.root.sub_path, - file.sub_file_path, - }) catch |err| { - try pt.reportRetryableFileError(file_index, "unable to resolve path: {s}", .{@errorName(err)}); - return error.AnalysisFail; - }; - errdefer gpa.free(resolved_path); + const was_outdated = zcu.outdated.swapRemove(anal_unit) or + zcu.potentially_outdated.swapRemove(anal_unit); - whole.cache_manifest_mutex.lock(); - defer whole.cache_manifest_mutex.unlock(); - man.addFilePostContents(resolved_path, source.bytes, source.stat) catch |err| switch (err) { - error.OutOfMemory => |e| return e, - else => { - try pt.reportRetryableFileError(file_index, "unable to update cache: {s}", .{@errorName(err)}); - return error.AnalysisFail; - }, - }; + 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); + } + _ = 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 + } + } + + 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)) { + // If this function 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 the IES to be outdated if the function previously succeeded analysis; 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). + break :res .{ !prev_failed, true }; }, - .incremental => {}, + 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 (was_outdated) { + if (ies_outdated) { + try zcu.markDependeeOutdated(.marked_po, .{ .interned = func_index }); + } else { + try zcu.markPoDependeeUpToDate(.{ .interned = func_index }); + } } -} -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, -}; + if (new_failed) return error.AnalysisFail; +} -/// 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 { +fn analyzeFuncBody( + pt: Zcu.PerThread, + func_index: InternPool.Index, +) Zcu.SemaError!struct { ies_outdated: bool } { 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; - } + const func = zcu.funcInfo(func_index); + const anal_unit = AnalUnit.wrap(.{ .func = func_index }); - // We are about to re-analyze this `Cau`; drop its depenndencies. - zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit); + // Make sure that this function is still owned by the same `Nav`. Otherwise, analyzing + // it would be a waste of time in the best case, and could cause codegen to give bogus + // results in the worst case. - 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, - }; - }, + if (func.generic_owner == .none) { + // Among another things, this ensures that the function's `zir_body_inst` is correct. + try pt.ensureNavValUpToDate(func.owner_nav); + if (ip.getNav(func.owner_nav).status.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; + } + } 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.ensureNavValUpToDate(go_nav); + if (ip.getNav(go_nav).status.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; + } } - const is_usingnamespace = switch (cau.owner.unwrap()) { - .nav => |nav| ip.getNav(nav).is_usingnamespace, - .none, .type => false, - }; + // We'll want to remember what the IES used to be before the update for + // dependency invalidation purposes. + const old_resolved_ies = if (func.analysisUnordered(ip).inferred_error_set) + func.resolvedErrorSetUnordered(ip) + else + .none; - log.debug("semaCau {}", .{zcu.fmtAnalUnit(anal_unit)}); + log.debug("analyze and generate fn body {}", .{zcu.fmtAnalUnit(anal_unit)}); - try zcu.analysis_in_progress.put(gpa, anal_unit, {}); - errdefer _ = zcu.analysis_in_progress.swapRemove(anal_unit); + var air = try pt.analyzeFnBodyInner(func_index); + errdefer air.deinit(gpa); - var analysis_arena = std.heap.ArenaAllocator.init(gpa); - defer analysis_arena.deinit(); + const ies_outdated = !func.analysisUnordered(ip).inferred_error_set or + func.resolvedErrorSetUnordered(ip) != old_resolved_ies; - var comptime_err_ret_trace = std.ArrayList(Zcu.LazySrcLoc).init(gpa); - defer comptime_err_ret_trace.deinit(); + const comp = zcu.comp; - 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(); + const dump_air = build_options.enable_debug_extensions and comp.verbose_air; + const dump_llvm_ir = build_options.enable_debug_extensions and (comp.verbose_llvm_ir != null or comp.verbose_llvm_bc != null); - // Every `Cau` has a dependency on the source of its own ZIR instruction. - try sema.declareDependency(.{ .src_hash = cau.zir_index }); + if (comp.bin_file == null and zcu.llvm_object == null and !dump_air and !dump_llvm_ir) { + air.deinit(gpa); + return .{ .ies_outdated = ies_outdated }; + } - 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); + // This job depends on any resolve_type_fully jobs queued up before it. + try comp.queueJob(.{ .codegen_func = .{ + .func = func_index, + .air = air, + } }); - const zir_decl = zir.getDeclaration(inst_info.inst); + return .{ .ies_outdated = ies_outdated }; +} - // 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), - }; +/// Takes ownership of `air`, even on error. +/// If any types referenced by `air` are unresolved, marks the codegen as failed. +pub fn linkerUpdateFunc(pt: Zcu.PerThread, func_index: InternPool.Index, air: Air) Allocator.Error!void { + const zcu = pt.zcu; + const gpa = zcu.gpa; + const ip = &zcu.intern_pool; + const comp = zcu.comp; - 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 }); + defer { + var air_mut = air; + air_mut.deinit(gpa); + } - // 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. + const func = zcu.funcInfo(func_index); + const nav_index = func.owner_nav; + const nav = ip.getNav(nav_index); - const decl_ty: Type, const early_val: ?Value = if (zir_decl.type_body) |type_body| ty: { - // We evaluate only the type now; no need for the value yet. - const uncoerced_type_ref = try sema.resolveInlineBody(&block, type_body, inst_info.inst); - const type_ref = try sema.coerce(&block, .type, uncoerced_type_ref, ty_src); - break :ty .{ .fromInterned(type_ref.toInterned().?), null }; - } else ty: { - // We don't have a type body, so we need to evaluate the value immediately. - const value_body = zir_decl.value_body.?; - const result_ref = try sema.resolveInlineBody(&block, value_body, inst_info.inst); - const val = try sema.resolveFinalDeclValue(&block, init_src, result_ref); - break :ty .{ val.typeOf(zcu), val }; - }; + var liveness = try Liveness.analyze(gpa, air, ip); + defer liveness.deinit(gpa); - switch (zir_decl.kind) { - .unnamed_test, .@"test", .decltest => assert(decl_ty.zigTypeTag(zcu) == .@"fn"), - .@"comptime" => assert(decl_ty.toIntern() == .void_type), - .@"usingnamespace" => {}, - .@"const" => {}, - .@"var" => try sema.validateVarType( - &block, - if (zir_decl.type_body != null) ty_src else init_src, - decl_ty, - zir_decl.linkage == .@"extern", - ), + if (build_options.enable_debug_extensions and comp.verbose_air) { + std.debug.print("# Begin Function AIR: {}:\n", .{nav.fqn.fmt(ip)}); + @import("../print_air.zig").dump(pt, air, liveness); + std.debug.print("# End Function AIR: {}\n\n", .{nav.fqn.fmt(ip)}); } - // Now that we know the type, we can evaluate the alignment, linksection, and addrspace, to determine - // the full pointer type of this declaration. + if (std.debug.runtime_safety) { + var verify: Liveness.Verify = .{ + .gpa = gpa, + .air = air, + .liveness = liveness, + .intern_pool = ip, + }; + defer verify.deinit(); - 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, inst_info.inst); - break :a try sema.analyzeAsAlign(&block, align_src, align_ref); - }; + verify.verify() catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => { + try zcu.failed_codegen.putNoClobber(gpa, nav_index, try Zcu.ErrorMsg.create( + gpa, + zcu.navSrcLoc(nav_index), + "invalid liveness: {s}", + .{@errorName(err)}, + )); + return; + }, + }; + } - 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, 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 codegen_prog_node = zcu.codegen_prog_node.start(nav.fqn.toSlice(ip), 0); + defer codegen_prog_node.end(); - const @"addrspace": std.builtin.AddressSpace = as: { - const addrspace_ctx: Sema.AddressSpaceContext = switch (zir_decl.kind) { - .@"var" => .variable, - else => switch (decl_ty.zigTypeTag(zcu)) { - .@"fn" => .function, - else => .constant, + if (!air.typesFullyResolved(zcu)) { + // A type we depend on failed to resolve. This is a transitive failure. + // Correcting this failure will involve changing a type this function + // depends on, hence triggering re-analysis of this function, so this + // interacts correctly with incremental compilation. + // TODO: do we need to mark this failure anywhere? I don't think so, since compilation + // will fail due to the type error anyway. + } else if (comp.bin_file) |lf| { + lf.updateFunc(pt, func_index, air, liveness) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.AnalysisFail => { + assert(zcu.failed_codegen.contains(nav_index)); + }, + else => { + try zcu.failed_codegen.putNoClobber(gpa, nav_index, try Zcu.ErrorMsg.create( + gpa, + zcu.navSrcLoc(nav_index), + "unable to codegen: {s}", + .{@errorName(err)}, + )); + try zcu.retryable_failures.append(zcu.gpa, AnalUnit.wrap(.{ .func = func_index })); }, }; - 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, + } else if (zcu.llvm_object) |llvm_object| { + llvm_object.updateFunc(pt, func_index, air, liveness) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, }; - const addrspace_ref = try sema.resolveInlineBody(&block, addrspace_body, inst_info.inst); - break :as try sema.analyzeAsAddressSpace(&block, addrspace_src, addrspace_ref, addrspace_ctx); - }; - - // Lastly, we must evaluate the value if we have not already done so. Note, however, that extern declarations - // don't have an associated value body. - - const final_val: ?Value = early_val orelse if (zir_decl.value_body) |value_body| val: { - // Put the resolved type into `inst_map` to be used as the result type of the init. - try sema.inst_map.ensureSpaceForInstructions(gpa, &.{inst_info.inst}); - sema.inst_map.putAssumeCapacity(inst_info.inst, Air.internedToRef(decl_ty.toIntern())); - const uncoerced_result_ref = try sema.resolveInlineBody(&block, value_body, inst_info.inst); - assert(sema.inst_map.remove(inst_info.inst)); + } +} - const result_ref = try sema.coerce(&block, decl_ty, uncoerced_result_ref, init_src); - break :val try sema.resolveFinalDeclValue(&block, init_src, result_ref); - } else null; +/// https://github.com/ziglang/zig/issues/14307 +pub fn semaPkg(pt: Zcu.PerThread, pkg: *Module) !void { + dev.check(.sema); + const import_file_result = try pt.importPkg(pkg); + const root_type = pt.zcu.fileRootType(import_file_result.file_index); + if (root_type == .none) { + return pt.semaFile(import_file_result.file_index); + } +} - // TODO: missing validation? +fn createFileRootStruct( + pt: Zcu.PerThread, + file_index: Zcu.File.Index, + namespace_index: Zcu.Namespace.Index, + replace_existing: bool, +) Allocator.Error!InternPool.Index { + const zcu = pt.zcu; + const gpa = zcu.gpa; + const ip = &zcu.intern_pool; + const file = zcu.fileByIndex(file_index); + const extended = file.zir.instructions.items(.data)[@intFromEnum(Zir.Inst.Index.main_struct_inst)].extended; + assert(extended.opcode == .struct_decl); + const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small); + assert(!small.has_captures_len); + assert(!small.has_backing_int); + assert(small.layout == .auto); + var extra_index: usize = extended.operand + @typeInfo(Zir.Inst.StructDecl).@"struct".fields.len; + const fields_len = if (small.has_fields_len) blk: { + const fields_len = file.zir.extra[extra_index]; + extra_index += 1; + break :blk fields_len; + } else 0; + const decls_len = if (small.has_decls_len) blk: { + const decls_len = file.zir.extra[extra_index]; + extra_index += 1; + break :blk decls_len; + } else 0; + const decls = file.zir.bodySlice(extra_index, decls_len); + extra_index += decls_len; - const decl_val: Value = switch (zir_decl.linkage) { - .normal, .@"export" => switch (zir_decl.kind) { - .@"var" => .fromInterned(try pt.intern(.{ .variable = .{ - .ty = decl_ty.toIntern(), - .init = final_val.?.toIntern(), - .owner_nav = cau.owner.unwrap().nav, - .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_info.name, - .ty = decl_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 = alignment, - .@"addrspace" = @"addrspace", - .zir_index = cau.zir_index, // `declaration` instruction - .owner_nav = undefined, // ignored by `getExtern` - })); - }, + const tracked_inst = try ip.trackZir(gpa, pt.tid, .{ + .file = file_index, + .inst = .main_struct_inst, + }); + const wip_ty = switch (try ip.getStructType(gpa, pt.tid, .{ + .layout = .auto, + .fields_len = fields_len, + .known_non_opv = small.known_non_opv, + .requires_comptime = if (small.known_comptime_only) .yes else .unknown, + .any_comptime_fields = small.any_comptime_fields, + .any_default_inits = small.any_default_inits, + .inits_resolved = false, + .any_aligned_fields = small.any_aligned_fields, + .key = .{ .declared = .{ + .zir_index = tracked_inst, + .captures = &.{}, + } }, + }, replace_existing)) { + .existing => unreachable, // we wouldn't be analysing the file root if this type existed + .wip => |wip| wip, }; + errdefer wip_ty.cancel(ip, pt.tid); - 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. - }; + wip_ty.setName(ip, try file.internFullyQualifiedName(pt)); + ip.namespacePtr(namespace_index).owner_type = wip_ty.index; - switch (decl_val.toIntern()) { - .generic_poison => unreachable, // assertion failure - .unreachable_value => unreachable, // assertion failure - else => {}, + if (zcu.comp.incremental) { + try ip.addDependency( + gpa, + .wrap(.{ .type = wip_ty.index }), + .{ .src_hash = tracked_inst }, + ); } - // 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, - }; + try pt.scanNamespace(namespace_index, decls); + try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); + codegen_type: { + if (zcu.comp.config.use_llvm) break :codegen_type; + if (file.mod.strip) break :codegen_type; + // This job depends on any resolve_type_fully jobs queued up before it. + try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index }); } + zcu.setFileRootType(file_index, wip_ty.index); + return wip_ty.finish(ip, namespace_index); +} - 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`. +/// Re-scan the namespace of a file's root struct type on an incremental update. +/// The file must have successfully populated ZIR. +/// If the file's root struct type is not populated (the file is unreferenced), nothing is done. +/// This is called by `updateZirRefs` for all updated files before the main work loop. +/// This function does not perform any semantic analysis. +fn updateFileNamespace(pt: Zcu.PerThread, file_index: Zcu.File.Index) Allocator.Error!void { + const zcu = pt.zcu; - 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 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 (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}); - } - } + const file = zcu.fileByIndex(file_index); + assert(file.status == .success_zir); + const file_root_type = zcu.fileRootType(file_index); + if (file_root_type == .none) return; - ip.resolveNavValue(nav_index, .{ - .val = decl_val.toIntern(), - .alignment = alignment, - .@"linksection" = @"linksection", - .@"addrspace" = @"addrspace", + log.debug("updateFileNamespace mod={s} sub_file_path={s}", .{ + file.mod.fully_qualified_name, + file.sub_file_path, }); - // Mark the `Cau` as completed before evaluating the export! - assert(zcu.analysis_in_progress.swapRemove(anal_unit)); - - 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_index); - } + const namespace_index = Type.fromInterned(file_root_type).getNamespaceIndex(zcu); + const decls = decls: { + const extended = file.zir.instructions.items(.data)[@intFromEnum(Zir.Inst.Index.main_struct_inst)].extended; + const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small); - try sema.flushExports(); + var extra_index: usize = extended.operand + @typeInfo(Zir.Inst.StructDecl).@"struct".fields.len; + extra_index += @intFromBool(small.has_fields_len); + const decls_len = if (small.has_decls_len) blk: { + const decls_len = file.zir.extra[extra_index]; + extra_index += 1; + break :blk decls_len; + } else 0; + break :decls file.zir.bodySlice(extra_index, decls_len); + }; + try pt.scanNamespace(namespace_index, decls); + zcu.namespacePtr(namespace_index).generation = zcu.generation; +} - queue_codegen: { - if (!queue_linker_work) break :queue_codegen; +fn semaFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void { + const tracy = trace(@src()); + defer tracy.end(); - if (!try decl_ty.hasRuntimeBitsSema(pt)) { - if (zcu.comp.config.use_llvm) break :queue_codegen; - if (file.mod.strip) break :queue_codegen; - } + const zcu = pt.zcu; + const gpa = zcu.gpa; + const file = zcu.fileByIndex(file_index); + assert(zcu.fileRootType(file_index) == .none); - // This job depends on any resolve_type_fully jobs queued up before it. - try zcu.comp.queueJob(.{ .codegen_nav = nav_index }); + if (file.status != .success_zir) { + return error.AnalysisFail; } + assert(file.zir_loaded); - 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", + const new_namespace_index = try pt.createNamespace(.{ + .parent = .none, + .owner_type = undefined, // set in `createFileRootStruct` + .file_scope = file_index, + .generation = zcu.generation, + }); + const struct_ty = try pt.createFileRootStruct(file_index, new_namespace_index, false); + errdefer zcu.intern_pool.remove(pt.tid, struct_ty); + + switch (zcu.comp.cache_use) { + .whole => |whole| if (whole.cache_manifest) |man| { + const source = file.getSource(gpa) catch |err| { + try pt.reportRetryableFileError(file_index, "unable to load source: {s}", .{@errorName(err)}); + return error.AnalysisFail; + }; + + const resolved_path = std.fs.path.resolve(gpa, &.{ + file.mod.root.root_dir.path orelse ".", + file.mod.root.sub_path, + file.sub_file_path, + }) catch |err| { + try pt.reportRetryableFileError(file_index, "unable to resolve path: {s}", .{@errorName(err)}); + return error.AnalysisFail; + }; + errdefer gpa.free(resolved_path); + + whole.cache_manifest_mutex.lock(); + defer whole.cache_manifest_mutex.unlock(); + man.addFilePostContents(resolved_path, source.bytes, source.stat) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + else => { + try pt.reportRetryableFileError(file_index, "unable to update cache: {s}", .{@errorName(err)}); + return error.AnalysisFail; + }, }; }, + .incremental => {}, } } @@ -1880,45 +1914,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; @@ -1928,7 +1959,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, @@ -1950,7 +1982,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 }, @@ -1988,48 +2020,30 @@ const ScanDeclIter = struct { 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 (decl.kind) { - .@"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(); }, - .@"test", .decltest => |kind| 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 = @tagName(kind); - break :info .{ - (try iter.avoidNameConflict("{s}.{s}", .{ prefix, zir.nullTerminatedString(decl.name) })).toOptional(), - .@"test", - true, - }; + break :name (try iter.avoidNameConflict("{s}.{s}", .{ prefix, zir.nullTerminatedString(decl.name) })).toOptional(); }, - .@"const", .@"var" => info: { + .@"const", .@"var" => name: { if (iter.pass != .named) return; const name = try ip.getOrPutString( gpa, @@ -2038,11 +2052,7 @@ const ScanDeclIter = struct { .no_embedded_nulls, ); try iter.seen_decls.putNoClobber(gpa, name, {}); - break :info .{ - name.toOptional(), - .named, - false, - }; + break :name name.toOptional(); }, }; @@ -2051,46 +2061,44 @@ 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) { @@ -2103,8 +2111,9 @@ const ScanDeclIter = struct { } 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 @@ -2112,7 +2121,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; @@ -2121,7 +2130,7 @@ const ScanDeclIter = struct { try zcu.test_functions.put(gpa, nav, {}); break :a true; }, - .named => a: { + .@"const", .@"var" => a: { if (decl.is_pub) { try namespace.pub_decls.putContext(gpa, nav, {}, .{ .zcu = zcu }); } else { @@ -2130,23 +2139,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 decl.linkage == .@"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(); @@ -2168,21 +2177,14 @@ fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError! 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); @@ -2216,7 +2218,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) { @@ -2236,11 +2238,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); @@ -2542,10 +2544,10 @@ 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, @@ -2593,15 +2595,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, }; @@ -2622,8 +2623,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)) { @@ -2748,8 +2748,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. @@ -3255,7 +3255,7 @@ 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"); + pt.ensureNavValUpToDate(builtin_nav) catch @panic("std.builtin is corrupt"); const builtin_type = Type.fromInterned(ip.getNav(builtin_nav).status.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); @@ -3307,68 +3307,45 @@ pub fn navAlignment(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) InternPo /// 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; @@ -3405,8 +3382,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, @@ -3428,17 +3404,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); @@ -3448,8 +3423,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; @@ -3488,8 +3463,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; @@ -3526,22 +3500,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; @@ -3610,8 +3583,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; @@ -3637,12 +3609,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( @@ -3652,7 +3622,6 @@ fn recreateEnumType( key.zir_index, namespace_index, enum_obj.name, - new_cau_index, small, body, tag_type_ref, diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index 20db035c93f0..d132b5232990 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -2261,8 +2261,8 @@ pub fn initWipNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool.Nav.In assert(file.zir_loaded); 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, if (decl.is_pub) DW.ACCESS.public else DW.ACCESS.private, @@ -2292,8 +2292,8 @@ pub fn initWipNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool.Nav.In assert(file.zir_loaded); 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, if (decl.is_pub) DW.ACCESS.public else DW.ACCESS.private, @@ -2321,8 +2321,8 @@ pub fn initWipNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool.Nav.In assert(file.zir_loaded); 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, if (decl.is_pub) DW.ACCESS.public else DW.ACCESS.private, @@ -2563,8 +2563,8 @@ pub fn updateComptimeNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool 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.is_pub) DW.ACCESS.public else DW.ACCESS.private, From 3afda4322c34dedc2319701fdfac3505c8d311e9 Mon Sep 17 00:00:00 2001 From: mlugg Date: Mon, 23 Dec 2024 20:39:19 +0000 Subject: [PATCH 3/3] compiler: analyze type and value of global declaration separately This commit separates semantic analysis of the annotated type vs value of a global declaration, therefore allowing recursive and mutually recursive values to be declared. Every `Nav` which undergoes analysis now has *two* corresponding `AnalUnit`s: `.{ .nav_val = n }` and `.{ .nav_ty = n }`. The `nav_val` unit is responsible for *fully resolving* the `Nav`: determining its value, linksection, addrspace, etc. The `nav_ty` unit, on the other hand, resolves only the information necessary to construct a *pointer* to the `Nav`: its type, addrspace, etc. (It does also analyze its linksection, but that could be moved to `nav_val` I think; it doesn't make any difference). Analyzing a `nav_ty` for a declaration with no type annotation will just mark a dependency on the `nav_val`, analyze it, and finish. Conversely, analyzing a `nav_val` for a declaration *with* a type annotation will first mark a dependency on the `nav_ty` and analyze it, using this as the result type when evaluating the value body. The `nav_val` and `nav_ty` units always have references to one another: so, if a `Nav`'s type is referenced, its value implicitly is too, and vice versa. However, these dependencies are trivial, so, to save memory, are only known implicitly by logic in `resolveReferences`. In general, analyzing ZIR `decl_val` will only analyze `nav_ty` of the corresponding `Nav`. There are two exceptions to this. If the declaration is an `extern` declaration, then we immediately ensure the `Nav` value is resolved (which doesn't actually require any more analysis, since such a declaration has no value body anyway). Additionally, if the resolved type has type tag `.@"fn"`, we again immediately resolve the `Nav` value. The latter restriction is in place for two reasons: * Functions are special, in that their externs are allowed to trivially alias; i.e. with a declaration `extern fn foo(...)`, you can write `const bar = foo;`. This is not allowed for non-function externs, and it means that function types are the only place where it is possible for a declaration `Nav` to have a `.@"extern"` value without actually being declared `extern`. We need to identify this situation immediately so that the `decl_ref` can create a pointer to the *real* extern `Nav`, not this alias. * In certain situations, such as taking a pointer to a `Nav`, Sema needs to queue analysis of a runtime function if the value is a function. To do this, the function value needs to be known, so we need to resolve the value immediately upon `&foo` where `foo` is a function. This restriction is simple to codify into the eventual language specification, and doesn't limit the utility of this feature in practice. A consequence of this commit is that codegen and linking logic needs to be more careful when looking at `Nav`s. In general: * When `updateNav` or `updateFunc` is called, it is safe to assume that the `Nav` being updated (the owner `Nav` for `updateFunc`) is fully resolved. * Any `Nav` whose value is/will be an `@"extern"` or a function is fully resolved; see `Nav.getExtern` for a helper for a common case here. * Any other `Nav` may only have its type resolved. This didn't seem to be too tricky to satisfy in any of the existing codegen/linker backends. Resolves: #131 --- src/Compilation.zig | 14 +- src/InternPool.zig | 255 ++++++++-- src/Sema.zig | 203 ++++++-- src/Sema/comptime_ptr_access.zig | 5 +- src/Value.zig | 7 +- src/Zcu.zig | 81 ++- src/Zcu/PerThread.zig | 476 +++++++++++++----- src/arch/wasm/CodeGen.zig | 10 +- src/codegen.zig | 17 +- src/codegen/c.zig | 61 +-- src/codegen/llvm.zig | 64 ++- src/codegen/spirv.zig | 29 +- src/link.zig | 2 +- src/link/C.zig | 20 +- src/link/Coff.zig | 17 +- src/link/Dwarf.zig | 10 +- src/link/Elf/ZigObject.zig | 25 +- src/link/MachO/ZigObject.zig | 23 +- src/link/Plan9.zig | 4 +- src/link/Wasm/ZigObject.zig | 13 +- test/behavior/globals.zig | 96 ++++ .../self_reference_missing_const.zig | 11 + 22 files changed, 1033 insertions(+), 410 deletions(-) create mode 100644 test/cases/compile_errors/self_reference_missing_const.zig diff --git a/src/Compilation.zig b/src/Compilation.zig index 6f9b2e18d62b..28c5efab6c9a 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2906,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, @@ -2949,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()), @@ -2979,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())); @@ -3145,7 +3149,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle { const file_index = switch (anal_unit.unwrap()) { .@"comptime" => |cu| ip.getComptimeUnit(cu).zir_index.resolveFile(ip), - .nav_val => |nav| ip.getNav(nav).analysis.?.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), }; @@ -3380,7 +3384,7 @@ pub fn addModuleErrorMsg( defer gpa.free(rt_file_path); const name = switch (ref.referencer.unwrap()) { .@"comptime" => "comptime", - .nav_val => |nav| ip.getNav(nav).name.toSlice(ip), + .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), }; @@ -3647,6 +3651,7 @@ fn performAllTheWorkInner( try comp.queueJob(switch (outdated.unwrap()) { .func => |f| .{ .analyze_func = f }, .@"comptime", + .nav_ty, .nav_val, .type, => .{ .analyze_comptime_unit = outdated }, @@ -3679,7 +3684,7 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre return; } } - assert(nav.status == .resolved); + assert(nav.status == .fully_resolved); comp.dispatchCodegenTask(tid, .{ .codegen_nav = nav_index }); }, .codegen_func => |func| { @@ -3709,6 +3714,7 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre 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, @@ -3734,7 +3740,7 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre // 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).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 41019ea9d989..64cf95c7b24f 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, @@ -371,6 +375,7 @@ pub const AnalUnit = packed struct(u64) { pub const Kind = enum(u32) { @"comptime", nav_val, + nav_ty, type, func, }; @@ -380,6 +385,8 @@ pub const AnalUnit = packed struct(u64) { @"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, @@ -483,8 +490,20 @@ pub const Nav = struct { status: union(enum) { /// 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, @@ -492,14 +511,81 @@ 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), + }; } - /// Asserts that `status == .resolved`. - pub fn isExtern(nav: Nav, ip: *const InternPool) bool { - return ip.indexToKey(nav.status.resolved.val) == .@"extern"; + /// 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 != .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. @@ -509,7 +595,7 @@ pub const Nav = struct { return a.zir_index; } // A `Nav` which does not undergo analysis always has a resolved value. - return switch (ip.indexToKey(nav.status.resolved.val)) { + return switch (ip.indexToKey(nav.status.fully_resolved.val)) { .func => |func| { // Since `analysis` was not populated, this must be an instantiation. // Go up to the generic owner and consult *its* `analysis` field. @@ -567,19 +653,22 @@ pub const Nav = struct { // 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 == .resolved`. - val: InternPool.Index, - /// Populated only if `bits.status == .resolved`. + /// 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, }; @@ -597,8 +686,17 @@ pub const Nav = struct { .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", @@ -616,13 +714,15 @@ pub const Nav = struct { .fqn = nav.fqn, .analysis_namespace = if (nav.analysis) |a| a.namespace.toOptional() else .none, .analysis_zir_index = if (nav.analysis) |a| a.zir_index.toOptional() else .none, - .val = switch (nav.status) { + .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 => .{ @@ -630,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, }, }, }; @@ -646,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, @@ -695,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), @@ -732,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, @@ -2079,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); } @@ -5755,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, @@ -5763,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 { @@ -6419,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); @@ -6875,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, } }; @@ -8794,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, @@ -8903,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, @@ -9099,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, @@ -9197,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, @@ -9316,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. @@ -11030,7 +11133,7 @@ pub fn createNav( .name = opts.name, .fqn = opts.fqn, .analysis = null, - .status = .{ .resolved = .{ + .status = .{ .fully_resolved = .{ .val = opts.val, .alignment = opts.alignment, .@"linksection" = opts.@"linksection", @@ -11077,6 +11180,50 @@ pub fn createDeclNav( 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. /// If its status is already `resolved`, the old value is discarded. pub fn resolveNavValue( @@ -11099,7 +11246,7 @@ pub fn resolveNavValue( const nav_analysis_namespace = navs.items(.analysis_namespace); const nav_analysis_zir_index = navs.items(.analysis_zir_index); - const nav_vals = navs.items(.val); + const nav_vals = navs.items(.type_or_val); const nav_linksections = navs.items(.@"linksection"); const nav_bits = navs.items(.bits); @@ -11110,7 +11257,7 @@ pub fn resolveNavValue( @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 a41762f530ac..ccc9f63a5699 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6495,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, @@ -6520,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", .{}); } @@ -6542,6 +6542,7 @@ fn zirDisableInstrumentation(sema: *Sema) CompileError!void { .func => |func| func, .@"comptime", .nav_val, + .nav_ty, .type, => return, // does nothing outside a function }; @@ -6854,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, {}); } @@ -6865,7 +6866,7 @@ fn lookupInNamespace( ignore_self: { const skip_nav = switch (sema.owner.unwrap()) { .@"comptime", .type, .func => break :ignore_self, - .nav_val => |nav| nav, + .nav_ty, .nav_val => |nav| nav, }; var i: usize = 0; while (i < candidates.items.len) { @@ -7125,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()) { - .@"comptime", .type, .nav_val => 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; @@ -7686,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 => {}, } @@ -8007,7 +8009,7 @@ fn analyzeCall( if (call_dbg_node) |some| try sema.zirDbgStmt(block, some); switch (sema.owner.unwrap()) { - .@"comptime", .nav_val, .type => {}, + .@"comptime", .nav_ty, .nav_val, .type => {}, .func => |owner_func| if (Type.fromInterned(func_ty_info.return_type).isError(zcu)) { ip.funcSetCallsOrAwaitsErrorableFn(owner_func); }, @@ -8046,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 => {}, @@ -8243,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; @@ -8471,7 +8476,7 @@ fn instantiateGenericCall( if (call_dbg_node) |some| try sema.zirDbgStmt(block, some); switch (sema.owner.unwrap()) { - .@"comptime", .nav_val, .type => {}, + .@"comptime", .nav_ty, .nav_val, .type => {}, .func => |owner_func| if (Type.fromInterned(func_ty_info.return_type).isError(zcu)) { ip.funcSetCallsOrAwaitsErrorableFn(owner_func); }, @@ -19311,8 +19316,8 @@ fn typeInfoNamespaceDecls( 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); } } @@ -21602,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); }, - .@"comptime", .nav_val, .type => {}, + .@"comptime", .nav_ty, .nav_val, .type => {}, } return Air.internedToRef(try pt.intern(.{ .opt = .{ .ty = opt_ptr_stack_trace_ty.toIntern(), @@ -27086,7 +27091,7 @@ fn zirBuiltinExtern( .zir_index = switch (sema.owner.unwrap()) { .@"comptime" => |cu| ip.getComptimeUnit(cu).zir_index, .type => |owner_ty| Type.fromInterned(owner_ty).typeDeclInst(zcu).?, - .nav_val => |nav| ip.getNav(nav).analysis.?.zir_index, + .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: { @@ -27741,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; } @@ -32648,21 +32653,29 @@ 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); if (nav.analysis == null) { - assert(nav.status == .resolved); + assert(nav.status == .fully_resolved); return; } + try sema.declareDependency(switch (kind) { + .type => .{ .nav_ty = nav_index }, + .fully => .{ .nav_val = 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(.{ .nav_val = nav_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)) { @@ -32672,7 +32685,13 @@ pub fn ensureNavResolved(sema: *Sema, src: LazySrcLoc, nav_index: InternPool.Nav }, "dependency loop detected", .{})); } - return pt.ensureNavValUpToDate(nav_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 { @@ -32691,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_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 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 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 = .{ @@ -32731,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()); } @@ -38450,11 +38488,16 @@ 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()) { .nav_val => |this_nav| switch (dependee) { .nav_val => |other_nav| if (this_nav == other_nav) return, else => {}, }, + .nav_ty => |this_nav| switch (dependee) { + .nav_ty => |other_nav| if (this_nav == other_nav) return, + else => {}, + }, else => {}, } @@ -38873,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; @@ -38886,5 +38929,73 @@ fn getBuiltin(sema: *Sema, name: []const u8) SemaError!Air.Inst.Ref { const ip = &zcu.intern_pool; const nav = try pt.getBuiltinNav(name); try pt.ensureNavValUpToDate(nav); - return Air.internedToRef(ip.getNav(nav).status.resolved.val); + 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 10e81d7a9efc..ceddb9457df9 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/Value.zig b/src/Value.zig index 59fbdf67d5bc..25f5b50166d0 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 d374a0aa0c09..8b3125039ae2 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) = .{}, @@ -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, }; } @@ -2241,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| { @@ -2441,6 +2451,7 @@ pub fn markPoDependeeUpToDate(zcu: *Zcu, dependee: InternPool.Dependee) !void { switch (depender.unwrap()) { .@"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 }), } @@ -2453,7 +2464,8 @@ fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: AnalUni const ip = &zcu.intern_pool; const dependee: InternPool.Dependee = switch (maybe_outdated.unwrap()) { .@"comptime" => return, // analysis of a comptime decl can't outdate any dependencies - .nav_val => |nav| .{ .nav_val = nav }, // TODO: also `nav_ref` deps when introduced + .nav_val => |nav| .{ .nav_val = nav }, + .nav_ty => |nav| .{ .nav_ty = nav }, .type => |ty| .{ .interned = ty }, .func => |func_index| .{ .interned = func_index }, // IES }; @@ -2540,6 +2552,7 @@ pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?AnalUnit { .@"comptime" => continue, // a `comptime` block can't even be depended on so it is a terrible choice .type => |ty| .{ .interned = ty }, .nav_val => |nav| .{ .nav_val = nav }, + .nav_ty => |nav| .{ .nav_ty = nav }, }); while (it.next()) |_| n += 1; @@ -2780,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 { @@ -3424,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| { @@ -3513,7 +3562,7 @@ pub fn navSrcLine(zcu: *Zcu, nav_index: InternPool.Nav.Index) u32 { } 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 { @@ -3547,6 +3596,7 @@ fn formatAnalUnit(data: struct { unit: AnalUnit, zcu: *Zcu }, comptime fmt: []co } }, .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; @@ -3572,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)}), @@ -3749,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 0d7ca0eb260a..21908f769f50 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -731,10 +731,12 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) 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("ensureNavUpToDate {}", .{zcu.fmtAnalUnit(anal_unit)}); + 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* @@ -763,19 +765,19 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu } else { // We can trust the current information about this unit. if (prev_failed) return error.AnalysisFail; - if (nav.status == .resolved) return; + switch (nav.status) { + .unresolved, .type_resolved => {}, + .fully_resolved => return, + } } const unit_prog_node = zcu.sema_prog_node.start(nav.fqn.toSlice(ip), 0); defer unit_prog_node.end(); - const sema_result: SemaNavResult, const new_failed: bool = if (pt.analyzeNavVal(nav_id)) |result| res: { + 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. - .invalidate_nav_val = result.invalidate_nav_val or prev_failed, - .invalidate_nav_ref = result.invalidate_nav_ref or prev_failed, - }, + // 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) { @@ -786,10 +788,7 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu try zcu.transitive_failed_analysis.put(gpa, anal_unit, {}); log.debug("mark transitive analysis failure for {}", .{zcu.fmtAnalUnit(anal_unit)}); } - break :res .{ .{ - .invalidate_nav_val = !prev_failed, - .invalidate_nav_ref = !prev_failed, - }, true }; + break :res .{ !prev_failed, true }; }, error.OutOfMemory => { // TODO: it's unclear how to gracefully handle this. @@ -806,10 +805,8 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu }; if (was_outdated) { - // TODO: we do not yet have separate dependencies for Nav values vs types. - const invalidate = sema_result.invalidate_nav_val or sema_result.invalidate_nav_ref; const dependee: InternPool.Dependee = .{ .nav_val = nav_id }; - if (invalidate) { + 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. @@ -824,14 +821,7 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu if (new_failed) return error.AnalysisFail; } -const SemaNavResult = packed struct { - /// Whether the value of a `decl_val` of the corresponding Nav changed. - invalidate_nav_val: bool, - /// Whether the type of a `decl_ref` of the corresponding Nav changed. - invalidate_nav_ref: bool, -}; - -fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileError!SemaNavResult { +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; @@ -875,9 +865,13 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr }; defer sema.deinit(); - // The comptime unit declares on the source of the corresponding declaration. + // 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, @@ -891,31 +885,44 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr 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 ty_src = block.src(.{ .node_offset_var_decl_ty = 0 }); - const init_src = block.src(.{ .node_offset_var_decl_init = 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. - const nav_ty: Type, const early_val: ?Value = if (zir_decl.type_body) |type_body| ty: { - // We evaluate only the type now; no need for the value yet. - 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().?), null }; - } else ty: { - // We don't have a type body, so we need to evaluate the value immediately. - const value_body = zir_decl.value_body.?; - const result_ref = try sema.resolveInlineBody(&block, value_body, inst_resolved.inst); - const val = try sema.resolveFinalDeclValue(&block, init_src, result_ref); - break :ty .{ val.typeOf(zcu), val }; - }; - switch (zir_decl.kind) { .@"comptime" => unreachable, // this is not a Nav .unnamed_test, .@"test", .decltest => assert(nav_ty.zigTypeTag(zcu) == .@"fn"), @@ -932,58 +939,24 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr // Now that we know the type, we can evaluate the alignment, linksection, and addrspace, to determine // the full pointer type of this declaration. - 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, inst_resolved.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, inst_resolved.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 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", }, - }; - 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, inst_resolved.inst); - break :as try sema.analyzeAsAddressSpace(&block, addrspace_src, addrspace_ref, addrspace_ctx); + } + } 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 evaluate the value if we have not already done so. Note, however, that extern declarations - // don't have an associated value body. - - const final_val: ?Value = early_val orelse if (zir_decl.value_body) |value_body| val: { - // 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(nav_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, nav_ty, uncoerced_result_ref, init_src); - break :val try sema.resolveFinalDeclValue(&block, init_src, result_ref); - } else null; + // 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) { @@ -1013,8 +986,8 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr .is_threadlocal = zir_decl.is_threadlocal, .is_weak_linkage = false, .is_dll_import = false, - .alignment = alignment, - .@"addrspace" = @"addrspace", + .alignment = modifiers.alignment, + .@"addrspace" = modifiers.@"addrspace", .zir_index = old_nav.analysis.?.zir_index, // `declaration` instruction .owner_nav = undefined, // ignored by `getExtern` })); @@ -1047,10 +1020,7 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr }); // TODO: usingnamespace cannot participate in incremental compilation assert(zcu.analysis_in_progress.swapRemove(anal_unit)); - return .{ - .invalidate_nav_val = true, - .invalidate_nav_ref = true, - }; + return .{ .val_changed = true }; } const queue_linker_work, const is_owned_fn = switch (ip.indexToKey(nav_val.toIntern())) { @@ -1087,14 +1057,22 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr ip.resolveNavValue(nav_id, .{ .val = nav_val.toIntern(), - .alignment = alignment, - .@"linksection" = @"linksection", - .@"addrspace" = @"addrspace", + .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); @@ -1117,21 +1095,246 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr } switch (old_nav.status) { - .unresolved => return .{ - .invalidate_nav_val = true, - .invalidate_nav_ref = true, + .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(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 unit_prog_node = zcu.sema_prog_node.start(nav.fqn.toSlice(ip), 0); + defer unit_prog_node.end(); + + 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 }; }, - .resolved => |old| { - const new = ip.getNav(nav_id).status.resolved; - return .{ - .invalidate_nav_val = new.val != old.val, - .invalidate_nav_ref = ip.typeOf(new.val) != ip.typeOf(old.val) or - new.alignment != old.alignment or - new.@"linksection" != old.@"linksection" or - new.@"addrspace" != old.@"addrspace", - }; + 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 ensureFuncBodyUpToDate(pt: Zcu.PerThread, maybe_coerced_func_index: InternPool.Index) Zcu.SemaError!void { @@ -1144,6 +1347,8 @@ pub fn ensureFuncBodyUpToDate(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 }); @@ -1171,11 +1376,7 @@ pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, maybe_coerced_func_index: Inter 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 func_prog_node = zcu.sema_prog_node.start(ip.getNav(func.owner_nav).fqn.toSlice(ip), 0); @@ -1236,7 +1437,7 @@ fn analyzeFuncBody( if (func.generic_owner == .none) { // Among another things, this ensures that the function's `zir_body_inst` is correct. try pt.ensureNavValUpToDate(func.owner_nav); - if (ip.getNav(func.owner_nav).status.resolved.val != func_index) { + 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; @@ -1245,7 +1446,7 @@ fn analyzeFuncBody( 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.ensureNavValUpToDate(go_nav); - if (ip.getNav(go_nav).status.resolved.val != func.generic_owner) { + 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; @@ -2172,7 +2373,7 @@ fn analyzeFnBodyInner(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaE 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); } @@ -2550,8 +2751,8 @@ fn processExportsInner( 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") { @@ -3256,30 +3457,29 @@ pub fn getBuiltinNav(pt: Zcu.PerThread, name: []const u8) Allocator.Error!Intern 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.ensureNavValUpToDate(builtin_nav) catch @panic("std.builtin is corrupt"); - const builtin_type = Type.fromInterned(ip.getNav(builtin_nav).status.resolved.val); + 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, }, }); } @@ -3299,9 +3499,13 @@ 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. diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index ccdf38a4741e..49961042bc82 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 898629d69b22..7b607f13f935 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, .none, 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 c3e3c7fbdc11..2368f202da3f 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 154a7114cf76..5b36644019c8 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,14 +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())) { - .@"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; @@ -3063,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( @@ -3249,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; @@ -4528,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 @@ -4551,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)); @@ -4799,7 +4791,7 @@ 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, .none, variable.is_threadlocal, variable.is_weak_linkage, false, false, variable.init, variable.owner_nav }, @@ -5765,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 16b4a6dfbdb5..91e2c4f7e7ca 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 f0f6e9b01df9..58c5cf35af09 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 f42a467ee8e6..d84f29eb4b5b 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 e5b717ce1bcc..f13863cfb9b1 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 d132b5232990..5af6f804107b 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -2281,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); @@ -2313,7 +2313,7 @@ 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)); }, @@ -2388,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().?); @@ -2952,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)); }, @@ -2977,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); diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index fd6eceb556d7..5a2a7a8009b7 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 e18bc078dff4..511cb6839de7 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 3144b2ac1083..8e27e20ec775 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 0166b28743f8..09d764773018 100644 --- a/src/link/Wasm/ZigObject.zig +++ b/src/link/Wasm/ZigObject.zig @@ -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/test/behavior/globals.zig b/test/behavior/globals.zig index 89dc20c5c786..7c5645be1932 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/self_reference_missing_const.zig b/test/cases/compile_errors/self_reference_missing_const.zig new file mode 100644 index 000000000000..72b0ac1561be --- /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