issue-0037 fixed: ptr↔int conversion in coerceToType / bitcast emit
109/109 regression tests pass; chess Android + iOS-sim still
build clean.
Root cause: sx's `xx <ptr>` cast targeting an integer type
(common pattern: `xx u64 = xx @some_global`) lowered to a no-op
because `coerceToType` had branches for int↔float and same-kind
widen/narrow, but nothing for pointer↔integer. The cast left the
value as a pointer Ref, and `emitInst`'s `.ret` arm tried to
coerce a `ptr` value to an `i64` slot — coerceArg had no
ptr↔int branch either, fell through to undef.
Why it worked in main but failed in helpers: an
`alloca u64`+`store ptr @g, alloca`+`load i64, alloca` sequence
preserves the address bits as raw memory, so the
"store-then-load through an alloca" workaround happened to do
the right thing without a real cast. A `ret i64 <ptr>` has no
such intermediate slot and triggers an LLVM type mismatch.
Fix layered into two existing IR opcodes:
lower.zig (coerceToType):
new branch — when src and dst types are ptr↔int, emit a
`bitcast` IR opcode with the right from/to. Mirrors how
int↔float emits `.int_to_float` / `.float_to_int`.
emit_llvm.zig (.bitcast arm):
dispatch ptr→int to `LLVMBuildPtrToInt` (+ trunc/zext if the
target int width != 64), int→ptr to `LLVMBuildIntToPtr`. The
"real bitcast" path stays for same-kind type punning.
Modern LLVM's BuildBitCast rejects ptr↔int directly, hence
the dispatch.
The fix also closes a quiet behavior gap that affected non-`#foreign`
globals (any `xx @<global>` from a helper fn). Surfaced while
investigating issue-0037; verified independently with a
non-`#foreign` sx-side global of type `s64`.
File mechanics: issue-0037 promoted to a focused feature example
per CLAUDE.md's resolution flow:
examples/issue-0037.sx -> examples/102-foreign-global-from-helper.sx
tests/expected/issue-0037.{txt,exit} -> tests/expected/102-foreign-global-from-helper.{txt,exit}
ffi-objc-call-03 + ffi-objc-call-06 IR snapshots updated to
reflect the ptr→int store-via-ptrtoint shape that's now correct
at the LLVM-IR level (same bits in memory, but properly typed).
This commit is contained in:
28
examples/102-foreign-global-from-helper.sx
Normal file
28
examples/102-foreign-global-from-helper.sx
Normal file
@@ -0,0 +1,28 @@
|
||||
// `xx @<foreign_global>` round-trips through a non-main helper
|
||||
// function: the helper's `xx @__stdinp` cast lowers to a `bitcast`
|
||||
// IR opcode that emit_llvm.zig dispatches to `LLVMBuildPtrToInt`
|
||||
// (BitCast doesn't accept ptr↔int on modern LLVM with opaque
|
||||
// pointers), and a `u64`-returning function correctly returns
|
||||
// the address.
|
||||
//
|
||||
// Was issue-0037 — the helper used to emit `ret i64 undef` because
|
||||
// `coerceToType` had no pointer↔integer case, so the explicit
|
||||
// `xx ptr` cast produced an unchanged pointer value handed to a
|
||||
// `ret i64` slot.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
__stdinp : *void #foreign;
|
||||
|
||||
stdinp_addr_via_helper :: () -> u64 {
|
||||
xx @__stdinp;
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
direct : u64 = xx @__stdinp;
|
||||
via_helper := stdinp_addr_via_helper();
|
||||
print("direct non-null = {}\n", direct != 0);
|
||||
print("helper non-null = {}\n", via_helper != 0);
|
||||
print("eq = {}\n", direct == via_helper);
|
||||
0;
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// `@__stdinp` (address-of a `#foreign` global) lowers to `undef` /
|
||||
// 0 when accessed from inside a non-main function body. From `main`
|
||||
// directly it works:
|
||||
//
|
||||
// single file:
|
||||
// __stdinp : *void #foreign;
|
||||
// stdinp_addr :: () -> u64 { xx @__stdinp; }
|
||||
// main :: () -> s32 {
|
||||
// a : u64 = xx @__stdinp; // a = <real symbol address>
|
||||
// b := stdinp_addr(); // b = 0
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// The emitted IR for the helper is just `ret i64 undef`, suggesting
|
||||
// the `address_of(identifier=__stdinp)` branch in `lower.zig`
|
||||
// (~line 1719) doesn't see `__stdinp` in `global_names` at the time
|
||||
// the helper's body is being lowered — even though the same lookup
|
||||
// succeeds inside main.
|
||||
//
|
||||
// Likely cause: lazy/lazy-deferred body lowering ordering vs. the
|
||||
// pass that registers extern global decls into `global_names`. Or
|
||||
// global_names is scoped per-function (?). Worth verifying which
|
||||
// before fixing.
|
||||
//
|
||||
// Expected when fixed: the helper returns the same address as the
|
||||
// direct read, prints `eq = true`. Today: prints `eq = false`.
|
||||
//
|
||||
// Filed during PLAN-FFI step 0.10 (cross-file `#foreign` global —
|
||||
// the cross-file dimension surfaced the bug, but it reproduces in
|
||||
// a single file too).
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
__stdinp : *void #foreign;
|
||||
|
||||
stdinp_addr_via_helper :: () -> u64 {
|
||||
xx @__stdinp;
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
direct : u64 = xx @__stdinp;
|
||||
via_helper := stdinp_addr_via_helper();
|
||||
print("direct non-null = {}\n", direct != 0);
|
||||
print("helper non-null = {}\n", via_helper != 0);
|
||||
print("eq = {}\n", direct == via_helper);
|
||||
0;
|
||||
}
|
||||
@@ -1006,7 +1006,26 @@ pub const LLVMEmitter = struct {
|
||||
.bitcast => |conv| {
|
||||
const operand = self.resolveRef(conv.operand);
|
||||
const to_ty = self.toLLVMType(conv.to);
|
||||
self.mapRef(c.LLVMBuildBitCast(self.builder, operand, to_ty, "bitcast"));
|
||||
// LLVMBuildBitCast doesn't accept ptr↔int on modern
|
||||
// LLVM. Dispatch to PtrToInt / IntToPtr when needed —
|
||||
// lower.zig emits a `bitcast` IR op for both shapes.
|
||||
const from_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(operand));
|
||||
const to_kind = c.LLVMGetTypeKind(to_ty);
|
||||
if (from_kind == c.LLVMPointerTypeKind and to_kind == c.LLVMIntegerTypeKind) {
|
||||
const i64_val = c.LLVMBuildPtrToInt(self.builder, operand, self.cached_i64, "pti");
|
||||
const w = c.LLVMGetIntTypeWidth(to_ty);
|
||||
if (w == 64) {
|
||||
self.mapRef(i64_val);
|
||||
} else if (w < 64) {
|
||||
self.mapRef(c.LLVMBuildTrunc(self.builder, i64_val, to_ty, "pti.tr"));
|
||||
} else {
|
||||
self.mapRef(c.LLVMBuildZExt(self.builder, i64_val, to_ty, "pti.ext"));
|
||||
}
|
||||
} else if (from_kind == c.LLVMIntegerTypeKind and to_kind == c.LLVMPointerTypeKind) {
|
||||
self.mapRef(c.LLVMBuildIntToPtr(self.builder, operand, to_ty, "itp"));
|
||||
} else {
|
||||
self.mapRef(c.LLVMBuildBitCast(self.builder, operand, to_ty, "bitcast"));
|
||||
}
|
||||
},
|
||||
.int_to_float => |conv| {
|
||||
const operand = self.resolveRef(conv.operand);
|
||||
|
||||
@@ -9290,6 +9290,8 @@ pub const Lowering = struct {
|
||||
const dst_float = isFloat(dst_ty);
|
||||
const src_int = self.isIntEx(src_ty);
|
||||
const dst_int = self.isIntEx(dst_ty);
|
||||
const src_ptr = !src_ty.isBuiltin() and self.module.types.get(src_ty) == .pointer;
|
||||
const dst_ptr = !dst_ty.isBuiltin() and self.module.types.get(dst_ty) == .pointer;
|
||||
|
||||
// Int → Float
|
||||
if (src_int and dst_float) {
|
||||
@@ -9299,6 +9301,13 @@ pub const Lowering = struct {
|
||||
if (src_float and dst_int) {
|
||||
return self.builder.emit(.{ .float_to_int = .{ .operand = val, .from = src_ty, .to = dst_ty } }, dst_ty);
|
||||
}
|
||||
// Ptr ↔ Int — explicit `xx ptr` to/from an integer-typed slot.
|
||||
// Emits a `bitcast` IR op; emit_llvm.zig's bitcast arm dispatches
|
||||
// to LLVMBuildPtrToInt / LLVMBuildIntToPtr at the LLVM level
|
||||
// since LLVMBuildBitCast itself doesn't accept ptr↔int.
|
||||
if ((src_ptr and dst_int) or (src_int and dst_ptr)) {
|
||||
return self.builder.emit(.{ .bitcast = .{ .operand = val, .from = src_ty, .to = dst_ty } }, dst_ty);
|
||||
}
|
||||
// Same kind — widen/narrow based on bit width
|
||||
const src_bits = self.typeBitsEx(src_ty);
|
||||
const dst_bits = self.typeBitsEx(dst_ty);
|
||||
|
||||
3
tests/expected/102-foreign-global-from-helper.txt
Normal file
3
tests/expected/102-foreign-global-from-helper.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
direct non-null = true
|
||||
helper non-null = true
|
||||
eq = true
|
||||
@@ -107,7 +107,8 @@ entry:
|
||||
store { ptr, i64 } undef, ptr %allocaN, align 8
|
||||
%loadN = load ptr, ptr %allocaN, align 8
|
||||
%gep = getelementptr inbounds { ptr, i64 }, ptr %allocaN, i32 0, i32 0
|
||||
store ptr %loadN, ptr %gep, align 8
|
||||
%pti = ptrtoint ptr %loadN to i64
|
||||
store i64 %pti, ptr %gep, align 8
|
||||
%loadN = load i64, ptr %alloca, align 8
|
||||
%gepN = getelementptr inbounds { ptr, i64 }, ptr %allocaN, i32 0, i32 1
|
||||
store i64 %loadN, ptr %gepN, align 8
|
||||
|
||||
@@ -267,7 +267,8 @@ entry:
|
||||
store { ptr, i64 } undef, ptr %allocaN, align 8
|
||||
%loadN = load ptr, ptr %allocaN, align 8
|
||||
%gep = getelementptr inbounds { ptr, i64 }, ptr %allocaN, i32 0, i32 0
|
||||
store ptr %loadN, ptr %gep, align 8
|
||||
%pti = ptrtoint ptr %loadN to i64
|
||||
store i64 %pti, ptr %gep, align 8
|
||||
%loadN = load i64, ptr %alloca, align 8
|
||||
%gepN = getelementptr inbounds { ptr, i64 }, ptr %allocaN, i32 0, i32 1
|
||||
store i64 %loadN, ptr %gepN, align 8
|
||||
@@ -2371,7 +2372,8 @@ entry:
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca i64, align 8
|
||||
%load = load ptr, ptr %alloca, align 8
|
||||
store ptr %load, ptr %allocaN, align 8
|
||||
%pti = ptrtoint ptr %load to i64
|
||||
store i64 %pti, ptr %allocaN, align 8
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%icmp = icmp eq i64 %loadN, 0
|
||||
br i1 %icmp, label %if.then.139, label %if.else.140
|
||||
@@ -2398,7 +2400,8 @@ entry:
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca i64, align 8
|
||||
%load = load ptr, ptr %alloca, align 8
|
||||
store ptr %load, ptr %allocaN, align 8
|
||||
%pti = ptrtoint ptr %load to i64
|
||||
store i64 %pti, ptr %allocaN, align 8
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%icmp = icmp eq i64 %loadN, 0
|
||||
br i1 %icmp, label %if.then.163, label %if.else.164
|
||||
@@ -2425,7 +2428,8 @@ entry:
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca i64, align 8
|
||||
%load = load ptr, ptr %alloca, align 8
|
||||
store ptr %load, ptr %allocaN, align 8
|
||||
%pti = ptrtoint ptr %load to i64
|
||||
store i64 %pti, ptr %allocaN, align 8
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%icmp = icmp eq i64 %loadN, 0
|
||||
br i1 %icmp, label %if.then.166, label %if.else.167
|
||||
@@ -2452,7 +2456,8 @@ entry:
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca i64, align 8
|
||||
%load = load ptr, ptr %alloca, align 8
|
||||
store ptr %load, ptr %allocaN, align 8
|
||||
%pti = ptrtoint ptr %load to i64
|
||||
store i64 %pti, ptr %allocaN, align 8
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%icmp = icmp eq i64 %loadN, 0
|
||||
br i1 %icmp, label %if.then.169, label %if.else.170
|
||||
@@ -2506,7 +2511,8 @@ entry:
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca i64, align 8
|
||||
%load = load ptr, ptr %alloca, align 8
|
||||
store ptr %load, ptr %allocaN, align 8
|
||||
%pti = ptrtoint ptr %load to i64
|
||||
store i64 %pti, ptr %allocaN, align 8
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%icmp = icmp eq i64 %loadN, 0
|
||||
br i1 %icmp, label %if.then.175, label %if.else.176
|
||||
@@ -2533,7 +2539,8 @@ entry:
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca i64, align 8
|
||||
%load = load ptr, ptr %alloca, align 8
|
||||
store ptr %load, ptr %allocaN, align 8
|
||||
%pti = ptrtoint ptr %load to i64
|
||||
store i64 %pti, ptr %allocaN, align 8
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%icmp = icmp eq i64 %loadN, 0
|
||||
br i1 %icmp, label %if.then.178, label %if.else.179
|
||||
@@ -2560,7 +2567,8 @@ entry:
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca i64, align 8
|
||||
%load = load ptr, ptr %alloca, align 8
|
||||
store ptr %load, ptr %allocaN, align 8
|
||||
%pti = ptrtoint ptr %load to i64
|
||||
store i64 %pti, ptr %allocaN, align 8
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%icmp = icmp eq i64 %loadN, 0
|
||||
br i1 %icmp, label %if.then.181, label %if.else.182
|
||||
@@ -2587,7 +2595,8 @@ entry:
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca i64, align 8
|
||||
%load = load ptr, ptr %alloca, align 8
|
||||
store ptr %load, ptr %allocaN, align 8
|
||||
%pti = ptrtoint ptr %load to i64
|
||||
store i64 %pti, ptr %allocaN, align 8
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%icmp = icmp eq i64 %loadN, 0
|
||||
br i1 %icmp, label %if.then.184, label %if.else.185
|
||||
@@ -2614,7 +2623,8 @@ entry:
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca i64, align 8
|
||||
%load = load ptr, ptr %alloca, align 8
|
||||
store ptr %load, ptr %allocaN, align 8
|
||||
%pti = ptrtoint ptr %load to i64
|
||||
store i64 %pti, ptr %allocaN, align 8
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%icmp = icmp eq i64 %loadN, 0
|
||||
br i1 %icmp, label %if.then.187, label %if.else.188
|
||||
@@ -2641,7 +2651,8 @@ entry:
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca i64, align 8
|
||||
%load = load ptr, ptr %alloca, align 8
|
||||
store ptr %load, ptr %allocaN, align 8
|
||||
%pti = ptrtoint ptr %load to i64
|
||||
store i64 %pti, ptr %allocaN, align 8
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%icmp = icmp eq i64 %loadN, 0
|
||||
br i1 %icmp, label %if.then.190, label %if.else.191
|
||||
@@ -2668,7 +2679,8 @@ entry:
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca i64, align 8
|
||||
%load = load ptr, ptr %alloca, align 8
|
||||
store ptr %load, ptr %allocaN, align 8
|
||||
%pti = ptrtoint ptr %load to i64
|
||||
store i64 %pti, ptr %allocaN, align 8
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%icmp = icmp eq i64 %loadN, 0
|
||||
br i1 %icmp, label %if.then.193, label %if.else.194
|
||||
@@ -2695,7 +2707,8 @@ entry:
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca i64, align 8
|
||||
%load = load ptr, ptr %alloca, align 8
|
||||
store ptr %load, ptr %allocaN, align 8
|
||||
%pti = ptrtoint ptr %load to i64
|
||||
store i64 %pti, ptr %allocaN, align 8
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%icmp = icmp eq i64 %loadN, 0
|
||||
br i1 %icmp, label %if.then.196, label %if.else.197
|
||||
@@ -2722,7 +2735,8 @@ entry:
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca i64, align 8
|
||||
%load = load ptr, ptr %alloca, align 8
|
||||
store ptr %load, ptr %allocaN, align 8
|
||||
%pti = ptrtoint ptr %load to i64
|
||||
store i64 %pti, ptr %allocaN, align 8
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%icmp = icmp eq i64 %loadN, 0
|
||||
br i1 %icmp, label %if.then.199, label %if.else.200
|
||||
@@ -2749,7 +2763,8 @@ entry:
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca i64, align 8
|
||||
%load = load ptr, ptr %alloca, align 8
|
||||
store ptr %load, ptr %allocaN, align 8
|
||||
%pti = ptrtoint ptr %load to i64
|
||||
store i64 %pti, ptr %allocaN, align 8
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%icmp = icmp eq i64 %loadN, 0
|
||||
br i1 %icmp, label %if.then.202, label %if.else.203
|
||||
@@ -2776,7 +2791,8 @@ entry:
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca i64, align 8
|
||||
%load = load ptr, ptr %alloca, align 8
|
||||
store ptr %load, ptr %allocaN, align 8
|
||||
%pti = ptrtoint ptr %load to i64
|
||||
store i64 %pti, ptr %allocaN, align 8
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%icmp = icmp eq i64 %loadN, 0
|
||||
br i1 %icmp, label %if.then.205, label %if.else.206
|
||||
@@ -2803,7 +2819,8 @@ entry:
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca i64, align 8
|
||||
%load = load ptr, ptr %alloca, align 8
|
||||
store ptr %load, ptr %allocaN, align 8
|
||||
%pti = ptrtoint ptr %load to i64
|
||||
store i64 %pti, ptr %allocaN, align 8
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%icmp = icmp eq i64 %loadN, 0
|
||||
br i1 %icmp, label %if.then.208, label %if.else.209
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
direct non-null = true
|
||||
helper non-null = false
|
||||
eq = false
|
||||
Reference in New Issue
Block a user