P5.7 Step B1: remove the compiler_call IR op + the hook Registry

The compiler_call op + #compiler hook mechanism was fully superseded by
abi(.compiler) VM-native dispatch (P5.5) — no sx code emits it anymore.

Remove: the compiler_call op variant + CompilerCall struct (inst.zig); the
Builder.compilerCall emitter (module.zig); the two dead producer blocks in
lower/call.zig (compiler_expr-bodied free fns + methods); every consumer
switch arm (emit_llvm, ops.emitCompilerCall, print, interp dispatch); the
interp.hooks field + init/deinit. Strip compiler_hooks.zig down to the still-
live BuildConfig / BuildHooks / AssetDir (delete HookError/HookFn/Registry/
registerDefaults + all hookXxx, and the now-unused interp/Value imports).

Test refs that used compiler_call as a sample unported op now use vec_splat.

501/501 unit + 706/0 corpus.
This commit is contained in:
agra
2026-06-19 16:54:38 +03:00
parent 5d25e23143
commit e2971f272c
9 changed files with 10 additions and 524 deletions

View File

@@ -1459,11 +1459,6 @@ pub const Ops = struct {
}
}
pub fn emitCompilerCall(self: Ops, instruction: *const Inst) void {
// Compiler hooks are comptime-only; if one reaches emission, produce undef
self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty)));
}
pub fn emitCallClosure(self: Ops, instruction: *const Inst, call_op: CallIndirect) void {
// Closure: { fn_ptr, env }.
//

View File

@@ -1,13 +1,12 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const interp_mod = @import("interp.zig");
const Value = interp_mod.Value;
const Interpreter = interp_mod.Interpreter;
const inst = @import("inst.zig");
const FuncId = inst.FuncId;
// ── BuildConfig ─────────────────────────────────────────────────────────
// Mutable build configuration accumulated by #run blocks via #compiler methods.
// Mutable build configuration accumulated by the sx-driven build pipeline
// (`default_pipeline` / an `on_build` callback) running on the comptime VM,
// which reads/writes these fields via the `abi(.compiler)` primitives.
/// `(src_dir, dest_in_bundle)` pair recorded by
/// `BuildOptions.add_asset_dir(src, dest)`. The sx bundler walks the
@@ -144,440 +143,3 @@ pub const BuildHooks = struct {
) anyerror!void,
};
// ── Hook system ─────────────────────────────────────────────────────────
pub const HookError = error{
CannotEvalComptime,
TypeError,
};
/// Hook function signature. Receives the interpreter (for heap/string access),
/// resolved argument values, and the mutable build config.
pub const HookFn = *const fn (
interp: *const Interpreter,
args: []const Value,
bc: *BuildConfig,
alloc: Allocator,
) HookError!Value;
pub const Registry = struct {
hooks: std.StringHashMap(HookFn),
pub fn init(alloc: Allocator) Registry {
return .{ .hooks = std.StringHashMap(HookFn).init(alloc) };
}
pub fn deinit(self: *Registry) void {
self.hooks.deinit();
}
pub fn get(self: *const Registry, name: []const u8) ?HookFn {
return self.hooks.get(name);
}
/// Register all built-in compiler hooks.
pub fn registerDefaults(self: *Registry) void {
self.hooks.put("build_options", &hookBuildOptions) catch {};
self.hooks.put("BuildOptions.add_link_flag", &hookAddLinkFlag) catch {};
self.hooks.put("BuildOptions.add_framework", &hookAddFramework) catch {};
self.hooks.put("BuildOptions.add_asset_dir", &hookAddAssetDir) catch {};
self.hooks.put("BuildOptions.asset_dir_count", &hookAssetDirCount) catch {};
self.hooks.put("BuildOptions.asset_dir_src_at", &hookAssetDirSrcAt) catch {};
self.hooks.put("BuildOptions.asset_dir_dest_at", &hookAssetDirDestAt) catch {};
self.hooks.put("BuildOptions.set_output_path", &hookSetOutputPath) catch {};
self.hooks.put("BuildOptions.set_wasm_shell", &hookSetWasmShell) catch {};
self.hooks.put("BuildOptions.set_post_link_callback", &hookSetPostLinkCallback) catch {};
self.hooks.put("BuildOptions.set_post_link_module", &hookSetPostLinkModule) catch {};
self.hooks.put("BuildOptions.binary_path", &hookGetBinaryPath) catch {};
// Bundling setters
self.hooks.put("BuildOptions.set_bundle_path", &hookSetBundlePath) catch {};
self.hooks.put("BuildOptions.set_bundle_id", &hookSetBundleId) catch {};
self.hooks.put("BuildOptions.set_codesign_identity", &hookSetCodesignIdentity) catch {};
self.hooks.put("BuildOptions.set_provisioning_profile", &hookSetProvisioningProfile) catch {};
// Bundling accessors
self.hooks.put("BuildOptions.bundle_path", &hookGetBundlePath) catch {};
self.hooks.put("BuildOptions.bundle_id", &hookGetBundleId) catch {};
self.hooks.put("BuildOptions.codesign_identity", &hookGetCodesignIdentity) catch {};
self.hooks.put("BuildOptions.provisioning_profile", &hookGetProvisioningProfile) catch {};
// Target accessors — mirror TargetConfig.is{MacOS,IOS,IOSDevice,IOSSimulator,Android}()
self.hooks.put("BuildOptions.target_triple", &hookGetTargetTriple) catch {};
self.hooks.put("BuildOptions.is_macos", &hookIsMacOS) catch {};
self.hooks.put("BuildOptions.is_ios", &hookIsIOS) catch {};
self.hooks.put("BuildOptions.is_ios_device", &hookIsIOSDevice) catch {};
self.hooks.put("BuildOptions.is_ios_simulator", &hookIsIOSSimulator) catch {};
self.hooks.put("BuildOptions.is_android", &hookIsAndroid) catch {};
// Android-specific setters + accessors
self.hooks.put("BuildOptions.set_manifest_path", &hookSetManifestPath) catch {};
self.hooks.put("BuildOptions.manifest_path", &hookGetManifestPath) catch {};
self.hooks.put("BuildOptions.set_keystore_path", &hookSetKeystorePath) catch {};
self.hooks.put("BuildOptions.keystore_path", &hookGetKeystorePath) catch {};
// #jni_main class emissions, exposed by index so bundle.sx can iterate.
self.hooks.put("BuildOptions.jni_main_count", &hookJniMainCount) catch {};
self.hooks.put("BuildOptions.jni_main_runtime_path_at", &hookJniMainRuntimePathAt) catch {};
self.hooks.put("BuildOptions.jni_main_java_source_at", &hookJniMainJavaSourceAt) catch {};
// Framework list accessors (for `.app/Frameworks/` embedding)
self.hooks.put("BuildOptions.framework_count", &hookFrameworkCount) catch {};
self.hooks.put("BuildOptions.framework_at", &hookFrameworkAt) catch {};
self.hooks.put("BuildOptions.framework_path_count", &hookFrameworkPathCount) catch {};
self.hooks.put("BuildOptions.framework_path_at", &hookFrameworkPathAt) catch {};
}
};
// ── build_options() hook ────────────────────────────────────────────────
fn hookBuildOptions(
_: *const Interpreter,
_: []const Value,
_: *BuildConfig,
_: Allocator,
) HookError!Value {
// build_options() returns a sentinel value; the real work happens
// when methods like add_link_flag/set_output_path are called on it.
return .void_val;
}
// ── BuildOptions hooks ──────────────────────────────────────────────────
fn hookAddLinkFlag(
interp: *const Interpreter,
args: []const Value,
bc: *BuildConfig,
alloc: Allocator,
) HookError!Value {
// args: [self (BuildOptions value), flag_string]
if (args.len < 2) return .void_val;
const str_val = args[1];
if (str_val.asString(interp)) |s| {
bc.link_flags.append(alloc, alloc.dupe(u8, s) catch return error.CannotEvalComptime) catch return error.CannotEvalComptime;
}
return .void_val;
}
fn hookAddFramework(
interp: *const Interpreter,
args: []const Value,
bc: *BuildConfig,
alloc: Allocator,
) HookError!Value {
// args: [self (BuildOptions value), framework_name]
if (args.len < 2) return .void_val;
const str_val = args[1];
if (str_val.asString(interp)) |s| {
bc.frameworks.append(alloc, alloc.dupe(u8, s) catch return error.CannotEvalComptime) catch return error.CannotEvalComptime;
}
return .void_val;
}
fn hookAddAssetDir(
interp: *const Interpreter,
args: []const Value,
bc: *BuildConfig,
alloc: Allocator,
) HookError!Value {
// args: [self (BuildOptions value), src_path, dest_path_in_bundle]
if (args.len < 3) return .void_val;
const src = args[1].asString(interp) orelse return error.TypeError;
const dest = args[2].asString(interp) orelse return error.TypeError;
const src_dup = alloc.dupe(u8, src) catch return error.CannotEvalComptime;
const dest_dup = alloc.dupe(u8, dest) catch return error.CannotEvalComptime;
bc.asset_dirs.append(alloc, .{ .src = src_dup, .dest = dest_dup }) catch return error.CannotEvalComptime;
return .void_val;
}
fn hookAssetDirCount(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
return Value{ .int = @intCast(bc.asset_dirs.items.len) };
}
fn hookAssetDirSrcAt(_: *const Interpreter, args: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
if (args.len < 2) return Value{ .string = "" };
const idx = args[1].asInt() orelse return error.TypeError;
if (idx < 0 or @as(usize, @intCast(idx)) >= bc.asset_dirs.items.len) return Value{ .string = "" };
return Value{ .string = bc.asset_dirs.items[@intCast(idx)].src };
}
fn hookAssetDirDestAt(_: *const Interpreter, args: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
if (args.len < 2) return Value{ .string = "" };
const idx = args[1].asInt() orelse return error.TypeError;
if (idx < 0 or @as(usize, @intCast(idx)) >= bc.asset_dirs.items.len) return Value{ .string = "" };
return Value{ .string = bc.asset_dirs.items[@intCast(idx)].dest };
}
fn hookSetOutputPath(
interp: *const Interpreter,
args: []const Value,
bc: *BuildConfig,
alloc: Allocator,
) HookError!Value {
// args: [self (BuildOptions value), path_string]
if (args.len < 2) return .void_val;
const str_val = args[1];
if (str_val.asString(interp)) |s| {
bc.output_path = alloc.dupe(u8, s) catch return error.CannotEvalComptime;
}
return .void_val;
}
fn hookSetWasmShell(
interp: *const Interpreter,
args: []const Value,
bc: *BuildConfig,
alloc: Allocator,
) HookError!Value {
// args: [self (BuildOptions value), path_string]
if (args.len < 2) return .void_val;
const str_val = args[1];
if (str_val.asString(interp)) |s| {
bc.wasm_shell_path = alloc.dupe(u8, s) catch return error.CannotEvalComptime;
}
return .void_val;
}
fn hookSetPostLinkCallback(
_: *const Interpreter,
args: []const Value,
bc: *BuildConfig,
_: Allocator,
) HookError!Value {
// args: [self (BuildOptions value), fn_value]. We accept a function
// value (.func_ref) and stash the FuncId so `main.zig` can re-enter
// the interpreter after linking.
if (args.len < 2) return .void_val;
switch (args[1]) {
.func_ref => |id| bc.post_link_callback_fn = id,
else => return error.TypeError,
}
return .void_val;
}
fn hookSetPostLinkModule(
interp: *const Interpreter,
args: []const Value,
bc: *BuildConfig,
alloc: Allocator,
) HookError!Value {
if (args.len < 2) return .void_val;
if (args[1].asString(interp)) |s| {
bc.post_link_module = alloc.dupe(u8, s) catch return error.CannotEvalComptime;
}
return .void_val;
}
/// Read the linked-binary path that main.zig populated right before
/// invoking the post-link callback. Returns the fat-string aggregate
/// the interpreter normally hands out for sx `string` values.
fn hookGetBinaryPath(
interp: *const Interpreter,
_: []const Value,
bc: *BuildConfig,
_: Allocator,
) HookError!Value {
_ = interp;
const path = bc.binary_path orelse "";
return Value{ .string = path };
}
// ── Bundling setters & accessors ─────────────────────────────────────
// Same pattern as set_output_path: take a string arg, dupe into the
// long-lived allocator, store on BuildConfig. The companion accessor
// reads back the same field; empty string when unset.
fn hookSetBundlePath(
interp: *const Interpreter,
args: []const Value,
bc: *BuildConfig,
alloc: Allocator,
) HookError!Value {
if (args.len < 2) return .void_val;
if (args[1].asString(interp)) |s| {
bc.bundle_path = alloc.dupe(u8, s) catch return error.CannotEvalComptime;
}
return .void_val;
}
fn hookSetBundleId(
interp: *const Interpreter,
args: []const Value,
bc: *BuildConfig,
alloc: Allocator,
) HookError!Value {
if (args.len < 2) return .void_val;
if (args[1].asString(interp)) |s| {
bc.bundle_id = alloc.dupe(u8, s) catch return error.CannotEvalComptime;
}
return .void_val;
}
fn hookSetCodesignIdentity(
interp: *const Interpreter,
args: []const Value,
bc: *BuildConfig,
alloc: Allocator,
) HookError!Value {
if (args.len < 2) return .void_val;
if (args[1].asString(interp)) |s| {
bc.codesign_identity = alloc.dupe(u8, s) catch return error.CannotEvalComptime;
}
return .void_val;
}
fn hookSetProvisioningProfile(
interp: *const Interpreter,
args: []const Value,
bc: *BuildConfig,
alloc: Allocator,
) HookError!Value {
if (args.len < 2) return .void_val;
if (args[1].asString(interp)) |s| {
bc.provisioning_profile = alloc.dupe(u8, s) catch return error.CannotEvalComptime;
}
return .void_val;
}
fn hookGetBundlePath(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
return Value{ .string = bc.bundle_path orelse "" };
}
fn hookGetBundleId(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
return Value{ .string = bc.bundle_id orelse "" };
}
fn hookGetCodesignIdentity(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
return Value{ .string = bc.codesign_identity orelse "" };
}
fn hookGetProvisioningProfile(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
return Value{ .string = bc.provisioning_profile orelse "" };
}
// ── Target accessors ──────────────────────────────────────────────────
// These look at the target_triple that main.zig populates and answer
// the same questions TargetConfig's helpers do for Zig callers.
fn tripleContains(triple: ?[]const u8, needle: []const u8) bool {
const t = triple orelse return false;
return std.mem.indexOf(u8, t, needle) != null;
}
fn isIOSTriple(triple: ?[]const u8) bool {
return tripleContains(triple, "apple-ios");
}
fn hookGetTargetTriple(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
return Value{ .string = bc.target_triple orelse "" };
}
fn hookIsMacOS(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
if (isIOSTriple(bc.target_triple)) return Value{ .boolean = false };
const t = bc.target_triple orelse "";
const is_mac = std.mem.indexOf(u8, t, "apple-macosx") != null or
std.mem.indexOf(u8, t, "apple-macos") != null or
std.mem.indexOf(u8, t, "apple-darwin") != null;
return Value{ .boolean = is_mac };
}
fn hookIsIOS(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
return Value{ .boolean = isIOSTriple(bc.target_triple) };
}
fn hookIsIOSDevice(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
const ios = isIOSTriple(bc.target_triple);
const sim = tripleContains(bc.target_triple, "simulator");
return Value{ .boolean = ios and !sim };
}
fn hookIsIOSSimulator(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
const ios = isIOSTriple(bc.target_triple);
const sim = tripleContains(bc.target_triple, "simulator");
return Value{ .boolean = ios and sim };
}
fn hookIsAndroid(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
return Value{ .boolean = tripleContains(bc.target_triple, "android") };
}
// ── Android-specific bundling setters + accessors ─────────────────────
fn hookSetManifestPath(interp: *const Interpreter, args: []const Value, bc: *BuildConfig, alloc: Allocator) HookError!Value {
if (args.len < 2) return .void_val;
if (args[1].asString(interp)) |s| {
bc.manifest_path = alloc.dupe(u8, s) catch return error.CannotEvalComptime;
}
return .void_val;
}
fn hookGetManifestPath(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
return Value{ .string = bc.manifest_path orelse "" };
}
fn hookSetKeystorePath(interp: *const Interpreter, args: []const Value, bc: *BuildConfig, alloc: Allocator) HookError!Value {
if (args.len < 2) return .void_val;
if (args[1].asString(interp)) |s| {
bc.keystore_path = alloc.dupe(u8, s) catch return error.CannotEvalComptime;
}
return .void_val;
}
fn hookGetKeystorePath(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
return Value{ .string = bc.keystore_path orelse "" };
}
// ── #jni_main emission accessors ──────────────────────────────────────
// The Android bundler walks these as `0..jni_main_count()` and reads
// each entry's `(runtime_path, java_source)` pair so it can write a
// `.java` file per decl, compile via javac, and produce classes.dex
// via d8 before zipping into the APK.
fn hookJniMainCount(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
return Value{ .int = @intCast(bc.jni_main_runtime_paths.len) };
}
fn hookJniMainRuntimePathAt(_: *const Interpreter, args: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
if (args.len < 2) return Value{ .string = "" };
const idx = args[1].asInt() orelse return error.TypeError;
if (idx < 0 or @as(usize, @intCast(idx)) >= bc.jni_main_runtime_paths.len) return Value{ .string = "" };
return Value{ .string = bc.jni_main_runtime_paths[@intCast(idx)] };
}
fn hookJniMainJavaSourceAt(_: *const Interpreter, args: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
if (args.len < 2) return Value{ .string = "" };
const idx = args[1].asInt() orelse return error.TypeError;
if (idx < 0 or @as(usize, @intCast(idx)) >= bc.jni_main_java_sources.len) return Value{ .string = "" };
return Value{ .string = bc.jni_main_java_sources[@intCast(idx)] };
}
// ── Framework list accessors ──────────────────────────────────────────
// The Apple .app bundler in `library/modules/platform/bundle.sx` walks
// the framework list to recursively copy each `<Name>.framework`
// directory from the user's -F search paths into `<bundle>/Frameworks/`.
// Slice-of-string returns aren't natively expressible as a Value, so we
// expose count + indexed lookups instead.
fn intValue(n: i64) Value {
return Value{ .int = n };
}
fn hookFrameworkCount(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
return intValue(@intCast(bc.target_frameworks.len));
}
fn hookFrameworkAt(_: *const Interpreter, args: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
if (args.len < 2) return Value{ .string = "" };
const idx_i64 = args[1].asInt() orelse return error.TypeError;
if (idx_i64 < 0 or @as(usize, @intCast(idx_i64)) >= bc.target_frameworks.len) {
return Value{ .string = "" };
}
return Value{ .string = bc.target_frameworks[@intCast(idx_i64)] };
}
fn hookFrameworkPathCount(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
return intValue(@intCast(bc.target_framework_paths.len));
}
fn hookFrameworkPathAt(_: *const Interpreter, args: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
if (args.len < 2) return Value{ .string = "" };
const idx_i64 = args[1].asInt() orelse return error.TypeError;
if (idx_i64 < 0 or @as(usize, @intCast(idx_i64)) >= bc.target_framework_paths.len) {
return Value{ .string = "" };
}
return Value{ .string = bc.target_framework_paths[@intCast(idx_i64)] };
}

View File

@@ -1314,12 +1314,12 @@ test "comptime_vm tryEval: pure function → Value; unsupported → null" {
const v = vm.tryEval(alloc, &module, ok_id, null, null) orelse return error.VmShouldHaveHandledIt;
try std.testing.expectEqual(@as(i64, 42), v.int);
// fn bad() { compiler_call() } → an unported op → tryEval yields null (caller
// falls back to legacy). (box_any/unbox_any are now VM-native; compiler_call is
// still unported until Phase 4D.)
// fn bad() { vec_splat(...) } → an unported op → tryEval yields null. The VM
// bails loudly on any op it does not model (never a silent default); vec_splat
// is a stable example of one.
var fb2 = Fb.init(alloc, &.{}, .void);
const c0 = fb2.block(&.{});
_ = fb2.add(c0, inst(.{ .compiler_call = .{ .name = 0, .args = &.{} } }, .void));
_ = fb2.add(c0, inst(.{ .vec_splat = .{ .operand = ref(0) } }, .void));
_ = fb2.add(c0, inst(.ret_void, .void));
const bad_id = module.addFunction(fb2.func);
@@ -1341,18 +1341,18 @@ test "comptime_vm exec: division by zero and unsupported op bail loudly" {
try std.testing.expectEqual(@as(i64, 4), toI64(try v.run(&fb.func, &.{ fromI64(12), fromI64(3) })));
try std.testing.expectError(error.DivisionByZero, v.run(&fb.func, &.{ fromI64(12), fromI64(0) }));
}
// A not-yet-ported op (compiler_call) → Unsupported with the op name in `detail`.
// A not-yet-ported op (vec_splat) → Unsupported with the op name in `detail`.
{
var fb = Fb.init(std.testing.allocator, &.{}, .void);
defer fb.deinit();
const b0 = fb.block(&.{});
_ = fb.add(b0, inst(.{ .compiler_call = .{ .name = 0, .args = &.{} } }, .void));
_ = fb.add(b0, inst(.{ .vec_splat = .{ .operand = ref(0) } }, .void));
_ = fb.add(b0, inst(.ret_void, .void));
var v = vm.Vm.init(std.testing.allocator);
defer v.deinit();
try std.testing.expectError(error.Unsupported, v.run(&fb.func, &.{}));
try std.testing.expectEqualStrings("compiler_call", v.detail.?);
try std.testing.expectEqualStrings("vec_splat", v.detail.?);
}
}

View File

@@ -1632,7 +1632,6 @@ pub const LLVMEmitter = struct {
// ── Call extensions ───────────────────────────────────────
.call_builtin => |bi| self.ops().emitCallBuiltin(instruction, bi),
.compiler_call => self.ops().emitCompilerCall(instruction),
.call_closure => |call_op| self.ops().emitCallClosure(instruction, call_op),
// ── Tuple ops ────────────────────────────────────────────

View File

@@ -207,7 +207,6 @@ pub const Op = union(enum) {
call_indirect: CallIndirect,
call_closure: CallIndirect,
call_builtin: BuiltinCall,
compiler_call: CompilerCall,
/// `#objc_call(ReturnT)(recv, sel, args...)` — dispatched through
/// `objc_msgSend`. emit_llvm.zig synthesizes a per-call-site LLVM
@@ -473,11 +472,6 @@ pub const BuiltinId = enum(u16) {
type_info,
};
pub const CompilerCall = struct {
name: u32, // StringPool id for qualified name (e.g. "BuildOptions.add_link_flag")
args: []const Ref,
};
pub const ClosureCreate = struct {
func: FuncId, // trampoline function
env: Ref, // allocated env pointer (or Ref.none for no captures)

View File

@@ -200,9 +200,6 @@ pub const Interpreter = struct {
// Mutable build configuration — set by LLVMEmitter, written by #run blocks
build_config: ?*BuildConfig = null,
// Compiler hook registry for #compiler methods
hooks: compiler_hooks.Registry,
// First op tag that bailed with InterpError, captured the first
// time the interpreter unwinds so callers can surface "op=foo at
// <file>:<offset>" alongside the bare error name. Static so it
@@ -234,15 +231,12 @@ pub const Interpreter = struct {
}
pub fn init(module: *const Module, alloc: Allocator) Interpreter {
var hooks = compiler_hooks.Registry.init(alloc);
hooks.registerDefaults();
return .{
.module = module,
.alloc = alloc,
.output = std.ArrayList(u8).empty,
.heap = std.ArrayList([]u8).empty,
.global_values = std.AutoHashMap(u32, Value).init(alloc),
.hooks = hooks,
};
}
@@ -268,7 +262,6 @@ pub const Interpreter = struct {
self.output.deinit(self.alloc);
self.call_chain.deinit(self.alloc);
self.global_values.deinit();
self.hooks.deinit();
}
/// Write `val` to the raw host address `addr` using exactly the
@@ -1126,31 +1119,6 @@ pub const Interpreter = struct {
return self.execBuiltin(bi, frame, instruction.ty);
},
// ── Compiler hook calls (#compiler methods) ────────
.compiler_call => |cc| {
const name = self.module.types.getString(@enumFromInt(cc.name));
if (self.hooks.get(name)) |hook| {
// Resolve args from Ref to Value
var resolved_args = std.ArrayList(Value).empty;
defer resolved_args.deinit(self.alloc);
for (cc.args) |arg| {
resolved_args.append(self.alloc, frame.getRef(arg)) catch return error.CannotEvalComptime;
}
if (self.build_config) |bc| {
const result = hook(self, resolved_args.items, bc, self.alloc) catch return bailDetail("#compiler hook returned an error (see hook impl)");
return .{ .value = result };
}
return .{ .value = .void_val };
}
if (last_bail_detail == null) {
// Capture which hook name failed so the host diag
// surfaces "compiler_call: unknown hook 'X'" instead
// of a bare CannotEvalComptime.
last_bail_detail = "#compiler hook not registered (likely a target-specific BuildOptions setter)";
}
return error.CannotEvalComptime;
},
// ── Struct GEP (field pointer) ─────────────────────
.struct_gep => |fa| {
const base = frame.getRef(fa.base);

View File

@@ -494,14 +494,6 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
return self.lowerGenericCall(fd, func_name, c, args.items);
}
}
// Check for #compiler free functions
if (self.program_index.fn_ast_map.get(func_name)) |fd_check| {
if (fd_check.body.data == .compiler_expr) {
const ret_ty = if (fd_check.return_type) |rt| type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map) else TypeId.void;
return self.builder.compilerCall(func_name, args.items, ret_ty);
}
}
// Look up declared/extern function — try lazy lowering if not yet lowered
{
// First attempt: function may already be declared (from scanDecls)
@@ -907,17 +899,6 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
// Try direct qualified name: StructName.method
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ sname, fa.field }) catch fa.field;
// Generic #compiler method dispatch
if (self.program_index.fn_ast_map.get(qualified)) |method_fd| {
if (method_fd.body.data == .compiler_expr) {
const ret_ty = if (method_fd.return_type) |rt|
type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map)
else
.void;
return self.builder.compilerCall(qualified, method_args.items, ret_ty);
}
}
// Generic-struct instance method: select the body via the
// instance's STAMPED author (CP-4), so the dispatched method is
// the one authored alongside this instance's layout — never the

View File

@@ -570,12 +570,6 @@ pub const Builder = struct {
return self.emit(.{ .call_builtin = .{ .builtin = builtin, .args = owned } }, ret_ty);
}
pub fn compilerCall(self: *Builder, name: []const u8, args: []const Ref, ret_ty: TypeId) Ref {
const name_id = self.module.types.strings.intern(self.module.alloc, name);
const owned = self.module.slice_arena.allocator().dupe(Ref, args) catch unreachable;
return self.emit(.{ .compiler_call = .{ .name = @intFromEnum(name_id), .args = owned } }, ret_ty);
}
// ── Closure ─────────────────────────────────────────────────────
pub fn closureCreate(self: *Builder, func_id: FuncId, env: Ref, ty: TypeId) Ref {

View File

@@ -336,13 +336,6 @@ fn printInst(instruction: *const Inst, ref_idx: u32, tt: *const TypeTable, write
a.clobbers.len,
});
},
.compiler_call => |cc| {
const name = tt.getString(@enumFromInt(cc.name));
try writer.print("compiler_call \"{s}\"(", .{name});
try writeArgs(cc.args, writer);
try writer.writeAll(") : ");
},
// ── Closure ─────────────────────────────────────────────
.closure_create => |cc| {
try writer.print("closure_create @{d}", .{cc.func.index()});