From d8fb42501de036790c6da44b78b0fc710ad98580 Mon Sep 17 00:00:00 2001 From: agra Date: Fri, 19 Jun 2026 22:14:57 +0300 Subject: [PATCH] comptime VM: support optional-of-word extern returns + string/any struct_init Two host-FFI gaps surfaced by the sx Android bundler running on the VM (default_pipeline calls env() -> getenv() -> ?cstring, and from_cstring builds a string literal): - callHostExtern: an extern returning an OPTIONAL whose child is a single register word (e.g. getenv() -> ?cstring) now wraps the bare C payload word into the {payload@0, has@sizeof(child)} optional aggregate (present iff non-null), mirroring emit_llvm's char*->?cstring handling. Previously bailed 'non-word return'. The non-word bail now names the symbol + return type. - struct_init: the builtin two-word aggregates string ({ptr,len}) and any ({tag,value}) can now be struct_init'd (e.g. string.{ ptr=, len= } in from_cstring). Previously bailed 'struct_init at a builtin result type'. These let the full Android .apk bundling pipeline (javac/d8/aapt2/zipalign/ apksigner) run on the comptime VM. 709/0 corpus + 476/476 unit. --- src/ir/comptime_vm.zig | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/ir/comptime_vm.zig b/src/ir/comptime_vm.zig index 6a081c7a..042bf18b 100644 --- a/src/ir/comptime_vm.zig +++ b/src/ir/comptime_vm.zig @@ -673,6 +673,18 @@ pub const Vm = struct { .struct_init => |agg| { const table = try self.requireTable(); const sty = ins.ty; + // `string`/`any` are builtin TWO-WORD aggregates (`{ptr@0, len@8}` / + // `{tag@0, value@8}`) — a literal like `string.{ ptr = p, len = n }` + // (e.g. `from_cstring`) struct_inits one. Lay each operand as an + // 8-byte word; the other builtins have no aggregate literal form. + if (sty == .string or sty == .any) { + const a = self.machine.allocBytes(16, 8); + for (agg.fields, 0..) |fr, i| { + if (i >= 2) break; + try self.machine.writeWord(a + @as(Addr, @intCast(i)) * 8, 8, frame.get(fr.index())); + } + return .{ .value = a }; + } if (sty.isBuiltin()) return self.failMsg("comptime VM: struct_init at a builtin result type"); const addr = self.machine.allocBytes(table.typeSizeBytes(sty), table.typeAlignBytes(sty)); // `struct_init` is the generic aggregate-literal op — its result @@ -1330,12 +1342,26 @@ pub const Vm = struct { host_ffi.callVoidRet(symbol, argv) catch return self.failMsg("comptime extern call failed (void)"); return @as(Reg, 0); } - if (kindOf(table, ret) != .word) - return self.failMsg("comptime extern call: non-word (aggregate/string/float) return not yet supported on the VM"); + // The C function returns a single register word. For a plain word return + // that word IS the result. For an OPTIONAL whose child is itself a single + // word (e.g. `getenv() -> ?cstring`, a `char*` the sx side treats as a + // nullable handle), the C returns the bare payload and we wrap it into the + // `{payload@0, has@sizeof(child)}` aggregate below (present iff non-null) — + // mirroring emit_llvm's wrapping of an extern `char*`→`?cstring` return. + const opt_child: ?TypeId = if (!ret.isBuiltin() and table.get(ret) == .optional) blk: { + const ch = table.get(ret).optional.child; + // An optional with a SENTINEL (pointer) child is itself a word and is + // handled by the plain-word path; only the `{payload, has}` aggregate + // form (kindOf == .aggregate) needs wrapping here. + break :blk if (kindOf(table, ret) == .aggregate and kindOf(table, ch) == .word and !isFloat(ch)) ch else null; + } else null; + const word_ty: TypeId = opt_child orelse ret; + if (kindOf(table, word_ty) != .word or isFloat(word_ty)) + return self.failFmt("comptime extern call '{s}': non-word (aggregate/string/float) return ({s}) not yet supported on the VM", .{ name, table.typeName(ret) }); // A pointer-ish return goes through callPtrRet (void* ABI); an integer-ish // return through callIntRet (i64 ABI). Either way the result is a single // word — a returned pointer is already a valid absolute `Addr`. - const r: u64 = if (isPointerish(table, ret)) blk: { + const r: u64 = if (isPointerish(table, word_ty)) blk: { break :blk if (variadic) host_ffi.callPtrRetVar(symbol, fixed, argv) catch return self.failMsg("comptime extern call failed (ptr)") else @@ -1347,6 +1373,13 @@ pub const Vm = struct { host_ffi.callIntRet(symbol, argv) catch return self.failMsg("comptime extern call failed (int)"); break :blk @bitCast(v); }; + if (opt_child) |child| { + // Wrap the bare payload word into the `{payload, has}` optional aggregate. + const addr = self.machine.allocBytes(table.typeSizeBytes(ret), table.typeAlignBytes(ret)); + try self.writeField(table, addr, child, r); + try self.machine.writeWord(addr + table.typeSizeBytes(child), 1, @intFromBool(r != 0)); + return @as(Reg, addr); + } return @as(Reg, r); }