Registers extension-opcode-id from sx_vm_extensions.ml module init. Lives downstream of both sx_primitives and sx_vm to avoid a build cycle. Accepts a string or symbol; returns Integer id when the opcode is registered, Nil otherwise. Compilers (lib/compiler.sx) call this to emit extension opcodes by name. Returning Nil rather than failing on unknown names lets a port's optimization opt in per-build — missing extensions degrade to slower correct execution. Tests: 5 new foundation cases — registered lookup, unknown → nil, symbol arg, zero-arg + integer-arg rejection. +5 pass vs Phase B baseline, no regressions across 11 conformance suites.
109 lines
3.9 KiB
OCaml
109 lines
3.9 KiB
OCaml
(** {1 VM extension registry}
|
|
|
|
Holds the live registry of extension opcodes and installs the
|
|
[dispatch] function into [Sx_vm.extension_dispatch_ref] at module
|
|
init time, replacing Phase A's stub.
|
|
|
|
See [plans/sx-vm-opcode-extension.md] and [Sx_vm_extension] for the
|
|
extension interface. *)
|
|
|
|
open Sx_vm_extension
|
|
|
|
(** The opcode range an extension is allowed to claim.
|
|
Mirrors the partition comment in [Sx_vm]. *)
|
|
let extension_min = 200
|
|
let extension_max = 247
|
|
|
|
(** opcode_id → handler *)
|
|
let by_id : (int, handler) Hashtbl.t = Hashtbl.create 64
|
|
|
|
(** opcode_name → opcode_id *)
|
|
let by_name : (string, int) Hashtbl.t = Hashtbl.create 64
|
|
|
|
(** extension_name → state *)
|
|
let states : (string, extension_state) Hashtbl.t = Hashtbl.create 8
|
|
|
|
(** Registered extension names, newest first. *)
|
|
let extensions : string list ref = ref []
|
|
|
|
(** Dispatch an extension opcode to its registered handler. Raises
|
|
[Sx_vm.Invalid_opcode] if no handler is registered for [op]. *)
|
|
let dispatch op vm frame =
|
|
match Hashtbl.find_opt by_id op with
|
|
| Some handler -> handler vm frame
|
|
| None -> raise (Sx_vm.Invalid_opcode op)
|
|
|
|
(** Register an extension. Fails if the extension name is already
|
|
registered, or if any opcode_id is outside the extension range or
|
|
collides with an already-registered opcode. *)
|
|
let register (m : (module EXTENSION)) =
|
|
let module M = (val m) in
|
|
if Hashtbl.mem states M.name then
|
|
failwith (Printf.sprintf
|
|
"Sx_vm_extensions: extension %S already registered" M.name);
|
|
let st = M.init () in
|
|
let ops = M.opcodes st in
|
|
List.iter (fun (id, opname, _h) ->
|
|
if id < extension_min || id > extension_max then
|
|
failwith (Printf.sprintf
|
|
"Sx_vm_extensions: opcode %d (%s) outside extension range %d-%d"
|
|
id opname extension_min extension_max);
|
|
if Hashtbl.mem by_id id then
|
|
failwith (Printf.sprintf
|
|
"Sx_vm_extensions: opcode %d (%s) already registered" id opname);
|
|
if Hashtbl.mem by_name opname then
|
|
failwith (Printf.sprintf
|
|
"Sx_vm_extensions: opcode name %S already registered" opname)
|
|
) ops;
|
|
Hashtbl.add states M.name st;
|
|
List.iter (fun (id, opname, h) ->
|
|
Hashtbl.add by_id id h;
|
|
Hashtbl.add by_name opname id
|
|
) ops;
|
|
extensions := M.name :: !extensions
|
|
|
|
(** Look up the opcode_id for an opcode_name. Returns [None] if no
|
|
extension provides that opcode. *)
|
|
let id_of_name name = Hashtbl.find_opt by_name name
|
|
|
|
(** Look up the state of an extension by name. Returns [None] if the
|
|
extension is not registered. *)
|
|
let state_of_extension name = Hashtbl.find_opt states name
|
|
|
|
(** Names of all registered extensions, newest first. *)
|
|
let registered_extensions () = !extensions
|
|
|
|
(** Test-only: clear the registry. Used by unit tests to isolate
|
|
extensions between test cases. The dispatch_ref is left in place. *)
|
|
let _reset_for_tests () =
|
|
Hashtbl.clear by_id;
|
|
Hashtbl.clear by_name;
|
|
Hashtbl.clear states;
|
|
extensions := []
|
|
|
|
(** Install our [dispatch] into [Sx_vm.extension_dispatch_ref], replacing
|
|
the Phase A stub. Idempotent. Called automatically at module init. *)
|
|
let install_dispatch () =
|
|
Sx_vm.extension_dispatch_ref := dispatch
|
|
|
|
let () = install_dispatch ()
|
|
|
|
(** Compiler-side opcode lookup: register the [extension-opcode-id]
|
|
primitive. Compilers ([lib/compiler.sx]) call this to emit
|
|
extension opcodes by name. Returns [Integer id] when registered,
|
|
[Nil] otherwise — so missing extensions degrade to a fallback
|
|
rather than failure. *)
|
|
let () =
|
|
Sx_primitives.register "extension-opcode-id" (fun args ->
|
|
match args with
|
|
| [Sx_types.String name] ->
|
|
(match id_of_name name with
|
|
| Some id -> Sx_types.Integer id
|
|
| None -> Sx_types.Nil)
|
|
| [Sx_types.Symbol name] ->
|
|
(match id_of_name name with
|
|
| Some id -> Sx_types.Integer id
|
|
| None -> Sx_types.Nil)
|
|
| _ -> raise (Sx_types.Eval_error
|
|
"extension-opcode-id: expected one string or symbol"))
|