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.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user