vm-ext: phase D — extensions/ subtree + test_ext + opcode_name lookup
lib/extensions/ becomes the new home for VM extensions, wired in via (include_subdirs unqualified). README documents the registration pattern, opcode-ID range conventions (200-209 guest_vm, 210-219 inline test, 220-229 test_ext, 230-247 ports), and naming rules. extensions/test_ext.ml is the canonical worked example — two operand-less opcodes (220 push 42, 221 double TOS) carrying a per- extension state slot (TestExtState invocation counter). Test_ext.register called from run_tests.ml at the start of the Phase D suite, on top of the inline test_reg from earlier suites (disjoint opcode IDs). Sx_vm.opcode_name now consults extension_opcode_name_ref (forward ref in the same style as extension_dispatch_ref), so disassemble shows extension opcodes by name instead of UNKNOWN_n. Registry maintains name_of_id_table and installs the lookup at module init. Tests: 5 new foundation cases — primitive resolves test_ext name, end-to-end bytecode (push + double + return → 84), disassemble shows "test_ext.OP_TEST_PUSH_42" / "test_ext.OP_TEST_DOUBLE_TOS", unregistered ext opcodes still fall back to UNKNOWN_n, invocation counter records the two dispatches. +5 pass vs Phase C baseline, no regressions across 11 conformance suites.
This commit is contained in:
71
hosts/ocaml/lib/extensions/README.md
Normal file
71
hosts/ocaml/lib/extensions/README.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# SX VM extensions
|
||||
|
||||
Each `*.ml` file here is a VM extension — a first-class OCaml module that
|
||||
registers specialized bytecode opcodes with `Sx_vm_extensions`. See
|
||||
[`plans/sx-vm-opcode-extension.md`](../../../../plans/sx-vm-opcode-extension.md)
|
||||
for the design.
|
||||
|
||||
## Pattern
|
||||
|
||||
```ocaml
|
||||
(* lib/extensions/myport.ml *)
|
||||
open Sx_types
|
||||
|
||||
type Sx_vm_extension.extension_state += MyportState of { ... }
|
||||
|
||||
module M : Sx_vm_extension.EXTENSION = struct
|
||||
let name = "myport"
|
||||
let init () = MyportState { ... }
|
||||
let opcodes _st = [
|
||||
(id, "myport.OP_NAME", handler);
|
||||
...
|
||||
]
|
||||
end
|
||||
|
||||
let register () = Sx_vm_extensions.register (module M)
|
||||
```
|
||||
|
||||
Then call `Myport.register ()` once at startup from any binary that
|
||||
should have the extension loaded.
|
||||
|
||||
## Opcode-ID allocation
|
||||
|
||||
Range 200-247 (per `Sx_vm_extensions.extension_min` /
|
||||
`extension_max`). Conventions:
|
||||
|
||||
| Range | Use |
|
||||
|---------|-------------------------------------------------------------------------|
|
||||
| 200-209 | reserved for `lib/guest/vm/` shared opcodes (chiselled out on 2nd use) |
|
||||
| 210-219 | inline test extensions defined in `bin/run_tests.ml` |
|
||||
| 220-229 | this directory's `test_ext` (the canonical template) |
|
||||
| 230-247 | first-come-first-served by language ports (Erlang first) |
|
||||
|
||||
When a port claims a contiguous block, document it in the table above.
|
||||
The registry rejects collisions at startup with a loud error — there is
|
||||
no silent shadowing.
|
||||
|
||||
## Naming
|
||||
|
||||
Always prefix opcode names with the extension name plus a dot:
|
||||
`myport.OP_<NAME>`. The prefix is a hard convention so that multiple
|
||||
extensions can share the global opcode-name namespace cleanly.
|
||||
|
||||
## State
|
||||
|
||||
`extension_state` is an extensible variant. Add your case (e.g.
|
||||
`MyportState of { ... }`) at the top of your file, return it from
|
||||
`init`, and pattern-match it inside your handlers. Other extensions
|
||||
cannot see your state — the variant case is private to your module.
|
||||
|
||||
## Testing
|
||||
|
||||
`test_ext.ml` is the canonical worked example. `bin/run_tests.ml`
|
||||
calls `Test_ext.register ()`, then drives bytecode that exercises the
|
||||
opcodes end-to-end (push, double, dispatch, disassemble, invocation
|
||||
counter). Mirror this shape when adding a real port's extension.
|
||||
|
||||
## Build wiring
|
||||
|
||||
`lib/dune` has `(include_subdirs unqualified)`, so any `.ml` you drop
|
||||
in here is automatically part of the `sx` library. Module name follows
|
||||
the filename verbatim (`test_ext.ml` → `Test_ext`).
|
||||
Reference in New Issue
Block a user