diff options
| author | mlugg <mlugg@mlugg.co.uk> | 2025-05-26 22:59:01 +0100 |
|---|---|---|
| committer | Matthew Lugg <mlugg@mlugg.co.uk> | 2025-05-27 19:23:11 +0100 |
| commit | 92c63126e8a58235625cceaf0ea9e3d4eacfe513 (patch) | |
| tree | ab36da0c91e39622bdd8a9c49e2f636ee341878d /src/Sema.zig | |
| parent | 3ed9155f10b725e6d24c01f5ecbebf359f3599a7 (diff) | |
| download | zig-92c63126e8a58235625cceaf0ea9e3d4eacfe513.tar.gz zig-92c63126e8a58235625cceaf0ea9e3d4eacfe513.zip | |
compiler: tlv pointers are not comptime-known
Pointers to thread-local variables do not have their addresses known
until runtime, so it is nonsensical for them to be comptime-known. There
was logic in the compiler which was essentially attempting to treat them
as not being comptime-known despite the pointer being an interned value.
This was a bit of a mess, the check was frequent enough to actually show
up in compiler profiles, and it was very awkward for backends to deal
with, because they had to grapple with the fact that a "constant" they
were lowering might actually require runtime operations.
So, instead, do not consider these pointers to be comptime-known in
*any* way. Never intern such a pointer; instead, when the address of a
threadlocal is taken, emit an AIR instruction which computes the pointer
at runtime. This avoids lots of special handling for TLVs across
basically all codegen backends; of all somewhat-functional backends, the
only one which wasn't improved by this change was the LLVM backend,
because LLVM pretends this complexity around threadlocals doesn't exist.
This change simplifies Sema and codegen, avoids a potential source of
bugs, and potentially improves Sema performance very slightly by
avoiding a non-trivial check on a hot path.
Diffstat (limited to 'src/Sema.zig')
| -rw-r--r-- | src/Sema.zig | 71 |
1 files changed, 37 insertions, 34 deletions
diff --git a/src/Sema.zig b/src/Sema.zig index e7f47a1791..46dc8cf857 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2223,10 +2223,7 @@ fn resolveValue(sema: *Sema, inst: Air.Inst.Ref) CompileError!?Value { if (inst.toInterned()) |ip_index| { const val: Value = .fromInterned(ip_index); - assert(val.getVariable(zcu) == null); - if (val.isPtrRuntimeValue(zcu)) return null; - return val; } else { // Runtime-known value. @@ -2295,31 +2292,12 @@ pub fn resolveFinalDeclValue( air_ref: Air.Inst.Ref, ) CompileError!Value { const zcu = sema.pt.zcu; - - 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); - }; - - switch (sema.failWithNeededComptime(block, src, .{ .simple = .container_var_init })) { - error.AnalysisFail => |e| { - if (sema.err != null and is_runtime_ptr) { - try sema.errNote(src, sema.err.?, "threadlocal and dll imported variables have runtime-known addresses", .{}); - } - return e; - }, - else => |e| return e, - } - }; - + const val = try sema.resolveConstValue(block, src, air_ref, .{ .simple = .container_var_init }); if (val.canMutateComptimeVarState(zcu)) { const ip = &zcu.intern_pool; const nav = ip.getNav(sema.owner.unwrap().nav_val); return sema.failWithContainsReferenceToComptimeVar(block, src, nav.name, "global variable", val); } - return val; } @@ -26506,6 +26484,7 @@ fn zirBuiltinExtern( const zcu = pt.zcu; const ip = &zcu.intern_pool; const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data; + const src = block.nodeOffset(extra.node); const ty_src = block.builtinCallArgSrc(extra.node, 0); const options_src = block.builtinCallArgSrc(extra.node, 1); @@ -26560,17 +26539,15 @@ fn zirBuiltinExtern( }, .owner_nav = undefined, // ignored by `getExtern` }); - const extern_nav = ip.indexToKey(extern_val).@"extern".owner_nav; - return Air.internedToRef((try pt.getCoerced(Value.fromInterned(try pt.intern(.{ .ptr = .{ - .ty = switch (ip.indexToKey(ty.toIntern())) { - .ptr_type => ty.toIntern(), - .opt_type => |child_type| child_type, - else => unreachable, - }, - .base_addr = .{ .nav = extern_nav }, - .byte_offset = 0, - } })), ty)).toIntern()); + const uncasted_ptr = try sema.analyzeNavRef(block, src, ip.indexToKey(extern_val).@"extern".owner_nav); + // We want to cast to `ty`, but that isn't necessarily an allowed coercion. + if (try sema.resolveValue(uncasted_ptr)) |uncasted_ptr_val| { + const casted_ptr_val = try pt.getCoerced(uncasted_ptr_val, ty); + return Air.internedToRef(casted_ptr_val.toIntern()); + } else { + return block.addBitCast(ty, uncasted_ptr); + } } fn zirWorkItem( @@ -32037,7 +32014,20 @@ fn analyzeNavRefInner(sema: *Sema, block: *Block, src: LazySrcLoc, orig_nav_inde break :nav orig_nav_index; }; - const ty, const alignment, const @"addrspace", const is_const = switch (ip.getNav(nav_index).status) { + const nav_status = ip.getNav(nav_index).status; + + const is_tlv_or_dllimport = switch (nav_status) { + .unresolved => unreachable, + // dllimports go straight to `fully_resolved`; the only option is threadlocal + .type_resolved => |r| r.is_threadlocal, + .fully_resolved => |r| switch (ip.indexToKey(r.val)) { + .@"extern" => |e| e.is_threadlocal or e.is_dll_import, + .variable => |v| v.is_threadlocal, + else => false, + }, + }; + + const ty, const alignment, const @"addrspace", const is_const = switch (nav_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) }, @@ -32050,9 +32040,22 @@ fn analyzeNavRefInner(sema: *Sema, block: *Block, src: LazySrcLoc, orig_nav_inde .address_space = @"addrspace", }, }); + + if (is_tlv_or_dllimport) { + // This pointer is runtime-known; we need to emit an AIR instruction to create it. + return block.addInst(.{ + .tag = .tlv_dllimport_ptr, + .data = .{ .ty_nav = .{ + .ty = ptr_ty.toIntern(), + .nav = nav_index, + } }, + }); + } + if (is_ref) { try sema.maybeQueueFuncBodyAnalysis(block, src, nav_index); } + return Air.internedToRef((try pt.intern(.{ .ptr = .{ .ty = ptr_ty.toIntern(), .base_addr = .{ .nav = nav_index }, |
