# 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`).
