erlang: migrate BIFs onto registry, delete cond dispatchers (600/600)
This commit is contained in:
@@ -1239,16 +1239,98 @@
|
||||
:else (er-mk-atom "undefined")))
|
||||
:else (error "Erlang: ets:info: arity"))))
|
||||
|
||||
(define
|
||||
er-apply-ets-bif
|
||||
(fn
|
||||
(name vs)
|
||||
(cond
|
||||
(= name "new") (er-bif-ets-new vs)
|
||||
(= name "insert") (er-bif-ets-insert vs)
|
||||
(= name "lookup") (er-bif-ets-lookup vs)
|
||||
(= name "delete") (er-bif-ets-delete vs)
|
||||
(= name "tab2list") (er-bif-ets-tab2list vs)
|
||||
(= name "info") (er-bif-ets-info vs)
|
||||
:else (error
|
||||
(str "Erlang: undefined 'ets:" name "/" (len vs) "'")))))
|
||||
|
||||
;; ── builtin BIF registrations (Phase 8 migration) ────────────────
|
||||
;; Populates `er-bif-registry` with every existing built-in BIF. Each
|
||||
;; entry is keyed by "Module/Name/Arity"; multi-arity BIFs register
|
||||
;; once per arity. Called eagerly at the end of runtime.sx so the
|
||||
;; registry is ready before any erlang-eval-ast call.
|
||||
(define er-register-builtin-bifs!
|
||||
(fn ()
|
||||
;; erlang module — type predicates (all pure)
|
||||
(er-register-pure-bif! "erlang" "is_integer" 1 er-bif-is-integer)
|
||||
(er-register-pure-bif! "erlang" "is_atom" 1 er-bif-is-atom)
|
||||
(er-register-pure-bif! "erlang" "is_list" 1 er-bif-is-list)
|
||||
(er-register-pure-bif! "erlang" "is_tuple" 1 er-bif-is-tuple)
|
||||
(er-register-pure-bif! "erlang" "is_number" 1 er-bif-is-number)
|
||||
(er-register-pure-bif! "erlang" "is_float" 1 er-bif-is-float)
|
||||
(er-register-pure-bif! "erlang" "is_boolean" 1 er-bif-is-boolean)
|
||||
(er-register-pure-bif! "erlang" "is_pid" 1 er-bif-is-pid)
|
||||
(er-register-pure-bif! "erlang" "is_reference" 1 er-bif-is-reference)
|
||||
(er-register-pure-bif! "erlang" "is_binary" 1 er-bif-is-binary)
|
||||
(er-register-pure-bif! "erlang" "is_function" 1 er-bif-is-function)
|
||||
(er-register-pure-bif! "erlang" "is_function" 2 er-bif-is-function)
|
||||
;; erlang module — pure data ops
|
||||
(er-register-pure-bif! "erlang" "length" 1 er-bif-length)
|
||||
(er-register-pure-bif! "erlang" "hd" 1 er-bif-hd)
|
||||
(er-register-pure-bif! "erlang" "tl" 1 er-bif-tl)
|
||||
(er-register-pure-bif! "erlang" "element" 2 er-bif-element)
|
||||
(er-register-pure-bif! "erlang" "tuple_size" 1 er-bif-tuple-size)
|
||||
(er-register-pure-bif! "erlang" "byte_size" 1 er-bif-byte-size)
|
||||
(er-register-pure-bif! "erlang" "atom_to_list" 1 er-bif-atom-to-list)
|
||||
(er-register-pure-bif! "erlang" "list_to_atom" 1 er-bif-list-to-atom)
|
||||
(er-register-pure-bif! "erlang" "abs" 1 er-bif-abs)
|
||||
(er-register-pure-bif! "erlang" "min" 2 er-bif-min)
|
||||
(er-register-pure-bif! "erlang" "max" 2 er-bif-max)
|
||||
(er-register-pure-bif! "erlang" "tuple_to_list" 1 er-bif-tuple-to-list)
|
||||
(er-register-pure-bif! "erlang" "list_to_tuple" 1 er-bif-list-to-tuple)
|
||||
(er-register-pure-bif! "erlang" "integer_to_list" 1 er-bif-integer-to-list)
|
||||
(er-register-pure-bif! "erlang" "list_to_integer" 1 er-bif-list-to-integer)
|
||||
;; erlang module — process / runtime (side-effecting)
|
||||
(er-register-bif! "erlang" "self" 0 er-bif-self)
|
||||
(er-register-bif! "erlang" "spawn" 1 er-bif-spawn)
|
||||
(er-register-bif! "erlang" "spawn" 3 er-bif-spawn)
|
||||
(er-register-bif! "erlang" "exit" 1 er-bif-exit)
|
||||
(er-register-bif! "erlang" "exit" 2 er-bif-exit)
|
||||
(er-register-bif! "erlang" "make_ref" 0 er-bif-make-ref)
|
||||
(er-register-bif! "erlang" "link" 1 er-bif-link)
|
||||
(er-register-bif! "erlang" "unlink" 1 er-bif-unlink)
|
||||
(er-register-bif! "erlang" "monitor" 2 er-bif-monitor)
|
||||
(er-register-bif! "erlang" "demonitor" 1 er-bif-demonitor)
|
||||
(er-register-bif! "erlang" "process_flag" 2 er-bif-process-flag)
|
||||
(er-register-bif! "erlang" "register" 2 er-bif-register)
|
||||
(er-register-bif! "erlang" "unregister" 1 er-bif-unregister)
|
||||
(er-register-bif! "erlang" "whereis" 1 er-bif-whereis)
|
||||
(er-register-bif! "erlang" "registered" 0 er-bif-registered)
|
||||
;; erlang module — exception raising (modelled as side-effecting)
|
||||
(er-register-bif! "erlang" "throw" 1
|
||||
(fn (vs) (raise (er-mk-throw-marker (er-bif-arg1 vs "throw")))))
|
||||
(er-register-bif! "erlang" "error" 1
|
||||
(fn (vs) (raise (er-mk-error-marker (er-bif-arg1 vs "error")))))
|
||||
;; lists module — all pure
|
||||
(er-register-pure-bif! "lists" "reverse" 1 er-bif-lists-reverse)
|
||||
(er-register-pure-bif! "lists" "map" 2 er-bif-lists-map)
|
||||
(er-register-pure-bif! "lists" "foldl" 3 er-bif-lists-foldl)
|
||||
(er-register-pure-bif! "lists" "seq" 2 er-bif-lists-seq)
|
||||
(er-register-pure-bif! "lists" "seq" 3 er-bif-lists-seq)
|
||||
(er-register-pure-bif! "lists" "sum" 1 er-bif-lists-sum)
|
||||
(er-register-pure-bif! "lists" "nth" 2 er-bif-lists-nth)
|
||||
(er-register-pure-bif! "lists" "last" 1 er-bif-lists-last)
|
||||
(er-register-pure-bif! "lists" "member" 2 er-bif-lists-member)
|
||||
(er-register-pure-bif! "lists" "append" 2 er-bif-lists-append)
|
||||
(er-register-pure-bif! "lists" "filter" 2 er-bif-lists-filter)
|
||||
(er-register-pure-bif! "lists" "any" 2 er-bif-lists-any)
|
||||
(er-register-pure-bif! "lists" "all" 2 er-bif-lists-all)
|
||||
(er-register-pure-bif! "lists" "duplicate" 2 er-bif-lists-duplicate)
|
||||
;; io module — side-effecting (writes to io buffer)
|
||||
(er-register-bif! "io" "format" 1 er-bif-io-format)
|
||||
(er-register-bif! "io" "format" 2 er-bif-io-format)
|
||||
;; ets module — side-effecting (mutates table state)
|
||||
(er-register-bif! "ets" "new" 2 er-bif-ets-new)
|
||||
(er-register-bif! "ets" "insert" 2 er-bif-ets-insert)
|
||||
(er-register-bif! "ets" "lookup" 2 er-bif-ets-lookup)
|
||||
(er-register-bif! "ets" "delete" 1 er-bif-ets-delete)
|
||||
(er-register-bif! "ets" "delete" 2 er-bif-ets-delete)
|
||||
(er-register-bif! "ets" "tab2list" 1 er-bif-ets-tab2list)
|
||||
(er-register-bif! "ets" "info" 2 er-bif-ets-info)
|
||||
;; code module — side-effecting (mutates module registry, kills procs)
|
||||
(er-register-bif! "code" "load_binary" 3 er-bif-code-load-binary)
|
||||
(er-register-bif! "code" "purge" 1 er-bif-code-purge)
|
||||
(er-register-bif! "code" "soft_purge" 1 er-bif-code-soft-purge)
|
||||
(er-register-bif! "code" "which" 1 er-bif-code-which)
|
||||
(er-register-bif! "code" "is_loaded" 1 er-bif-code-is-loaded)
|
||||
(er-register-bif! "code" "all_loaded" 0 er-bif-code-all-loaded)
|
||||
(er-mk-atom "ok")))
|
||||
|
||||
;; Register everything at load time.
|
||||
(er-register-builtin-bifs!)
|
||||
|
||||
@@ -211,6 +211,11 @@
|
||||
(er-rt-test "reset clears" (len (er-list-bifs)) 0)
|
||||
(er-rt-test "reset lookup nil" (er-lookup-bif "fake" "echo" 1) nil)
|
||||
|
||||
;; Re-populate built-in BIFs so subsequent test files (ring, ping-pong, etc.)
|
||||
;; can call length/spawn/etc. The migration onto the registry means a reset
|
||||
;; here would otherwise break the rest of the conformance suite.
|
||||
(er-register-builtin-bifs!)
|
||||
|
||||
|
||||
(define
|
||||
er-rt-test-summary
|
||||
|
||||
@@ -669,97 +669,23 @@
|
||||
|
||||
(define
|
||||
er-apply-bif
|
||||
(fn
|
||||
(name vs)
|
||||
(cond
|
||||
(= name "is_integer") (er-bif-is-integer vs)
|
||||
(= name "is_atom") (er-bif-is-atom vs)
|
||||
(= name "is_list") (er-bif-is-list vs)
|
||||
(= name "is_tuple") (er-bif-is-tuple vs)
|
||||
(= name "is_number") (er-bif-is-number vs)
|
||||
(= name "is_float") (er-bif-is-float vs)
|
||||
(= name "is_boolean") (er-bif-is-boolean vs)
|
||||
(= name "length") (er-bif-length vs)
|
||||
(= name "hd") (er-bif-hd vs)
|
||||
(= name "tl") (er-bif-tl vs)
|
||||
(= name "element") (er-bif-element vs)
|
||||
(= name "tuple_size") (er-bif-tuple-size vs)
|
||||
(= name "atom_to_list") (er-bif-atom-to-list vs)
|
||||
(= name "list_to_atom") (er-bif-list-to-atom vs)
|
||||
(= name "is_pid") (er-bif-is-pid vs)
|
||||
(= name "is_reference") (er-bif-is-reference vs)
|
||||
(= name "is_binary") (er-bif-is-binary vs)
|
||||
(= name "byte_size") (er-bif-byte-size vs)
|
||||
(= name "abs") (er-bif-abs vs)
|
||||
(= name "min") (er-bif-min vs)
|
||||
(= name "max") (er-bif-max vs)
|
||||
(= name "tuple_to_list") (er-bif-tuple-to-list vs)
|
||||
(= name "list_to_tuple") (er-bif-list-to-tuple vs)
|
||||
(= name "integer_to_list") (er-bif-integer-to-list vs)
|
||||
(= name "list_to_integer") (er-bif-list-to-integer vs)
|
||||
(= name "is_function") (er-bif-is-function vs)
|
||||
(= name "self") (er-bif-self vs)
|
||||
(= name "spawn") (er-bif-spawn vs)
|
||||
(= name "exit") (er-bif-exit vs)
|
||||
(= name "make_ref") (er-bif-make-ref vs)
|
||||
(= name "link") (er-bif-link vs)
|
||||
(= name "unlink") (er-bif-unlink vs)
|
||||
(= name "monitor") (er-bif-monitor vs)
|
||||
(= name "demonitor") (er-bif-demonitor vs)
|
||||
(= name "process_flag") (er-bif-process-flag vs)
|
||||
(= name "register") (er-bif-register vs)
|
||||
(= name "unregister") (er-bif-unregister vs)
|
||||
(= name "whereis") (er-bif-whereis vs)
|
||||
(= name "registered") (er-bif-registered vs)
|
||||
(= name "throw") (raise (er-mk-throw-marker (er-bif-arg1 vs "throw")))
|
||||
(= name "error") (raise (er-mk-error-marker (er-bif-arg1 vs "error")))
|
||||
:else (error
|
||||
(str "Erlang: undefined function '" name "/" (len vs) "'")))))
|
||||
(fn (name vs)
|
||||
(let ((entry (er-lookup-bif "erlang" name (len vs))))
|
||||
(if (not (= entry nil))
|
||||
((get entry :fn) vs)
|
||||
(error (str "Erlang: undefined function '" name "/" (len vs) "'"))))))
|
||||
|
||||
(define
|
||||
er-apply-remote-bif
|
||||
(fn
|
||||
(mod name vs)
|
||||
(fn (mod name vs)
|
||||
(cond
|
||||
(dict-has? (er-modules-get) mod)
|
||||
(er-apply-user-module mod name vs)
|
||||
(= mod "lists") (er-apply-lists-bif name vs)
|
||||
(= mod "io") (er-apply-io-bif name vs)
|
||||
(= mod "erlang") (er-apply-bif name vs)
|
||||
(= mod "ets") (er-apply-ets-bif name vs)
|
||||
(= mod "code") (er-apply-code-bif name vs)
|
||||
:else (error
|
||||
(str "Erlang: undefined module '" mod "'")))))
|
||||
|
||||
(define
|
||||
er-apply-lists-bif
|
||||
(fn
|
||||
(name vs)
|
||||
(cond
|
||||
(= name "reverse") (er-bif-lists-reverse vs)
|
||||
(= name "map") (er-bif-lists-map vs)
|
||||
(= name "foldl") (er-bif-lists-foldl vs)
|
||||
(= name "seq") (er-bif-lists-seq vs)
|
||||
(= name "sum") (er-bif-lists-sum vs)
|
||||
(= name "nth") (er-bif-lists-nth vs)
|
||||
(= name "last") (er-bif-lists-last vs)
|
||||
(= name "member") (er-bif-lists-member vs)
|
||||
(= name "append") (er-bif-lists-append vs)
|
||||
(= name "filter") (er-bif-lists-filter vs)
|
||||
(= name "any") (er-bif-lists-any vs)
|
||||
(= name "all") (er-bif-lists-all vs)
|
||||
(= name "duplicate") (er-bif-lists-duplicate vs)
|
||||
:else (error
|
||||
(str "Erlang: undefined 'lists:" name "/" (len vs) "'")))))
|
||||
|
||||
(define
|
||||
er-apply-io-bif
|
||||
(fn
|
||||
(name vs)
|
||||
(cond
|
||||
(= name "format") (er-bif-io-format vs)
|
||||
:else (error
|
||||
(str "Erlang: undefined 'io:" name "/" (len vs) "'")))))
|
||||
(er-apply-user-module mod name vs)
|
||||
:else
|
||||
(let ((entry (er-lookup-bif mod name (len vs))))
|
||||
(if (not (= entry nil))
|
||||
((get entry :fn) vs)
|
||||
(error (str "Erlang: undefined remote function '" mod ":" name "/" (len vs) "'")))))))
|
||||
|
||||
(define
|
||||
er-bif-arg1
|
||||
@@ -2083,21 +2009,4 @@
|
||||
(range 0 (len ks)))
|
||||
out)))
|
||||
|
||||
(define er-apply-code-bif
|
||||
(fn (name vs)
|
||||
(cond
|
||||
(and (= name "load_binary") (= (len vs) 3))
|
||||
(er-bif-code-load-binary vs)
|
||||
(and (= name "purge") (= (len vs) 1))
|
||||
(er-bif-code-purge vs)
|
||||
(and (= name "soft_purge") (= (len vs) 1))
|
||||
(er-bif-code-soft-purge vs)
|
||||
(and (= name "which") (= (len vs) 1))
|
||||
(er-bif-code-which vs)
|
||||
(and (= name "is_loaded") (= (len vs) 1))
|
||||
(er-bif-code-is-loaded vs)
|
||||
(and (= name "all_loaded") (= (len vs) 0))
|
||||
(er-bif-code-all-loaded vs)
|
||||
:else (error
|
||||
(str "Erlang: undefined function 'code:" name "/" (len vs) "'")))))
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ Driven by **fed-sx** (see `plans/fed-sx-design.md` §17.5): federated modules mu
|
||||
Replace today's hardcoded BIF dispatch (`er-apply-bif`/`er-apply-remote-bif` in `transpile.sx`) with a runtime-extensible **BIF registry**. Each registry entry is `{:module :name :arity :fn :pure?}`. Standard libs are then registered at boot, and fed-sx can register new BIFs from `.sx` files. Includes the marshalling layer (Erlang term ↔ SX value) so wrappers stay one-liners.
|
||||
|
||||
- [x] BIF registry: `er-bif-registry` global dict keyed by `"Module/Name/Arity"`, with `er-register-bif!`/`er-register-pure-bif!`/`er-lookup-bif`/`er-list-bifs`/`er-bif-registry-reset!` helpers — **+18 runtime tests** (600/600 total). Entries are `{:module :name :arity :fn :pure?}`. Arity is part of the key so `m:f/1` and `m:f/2` are independent. Re-registering the same key replaces the previous entry; reset clears.
|
||||
- [ ] Migrate existing local + remote BIFs (length/hd/tl/lists:*/io:format/ets:*/etc.) onto the registry; delete the giant `cond` dispatch in `er-apply-bif`/`er-apply-remote-bif`. Conformance must stay 530/530 after migration
|
||||
- [x] Migrate existing local + remote BIFs (length/hd/tl/lists:*/io:format/ets:*/etc.) onto the registry; delete the giant `cond` dispatch in `er-apply-bif`/`er-apply-remote-bif`. Conformance held at **600/600** after migration (baseline was 600, not the plan-text's 530 — the text was authored before Phase 7 work added rows). 67 builtin registrations across `erlang`/`lists`/`io`/`ets`/`code` modules; multi-arity BIFs (`is_function`, `spawn`, `exit`, `io:format`, `lists:seq`, `ets:delete`) register once per arity, all pointing at the same impl which dispatches on `(len vs)` internally. The four per-module cond dispatchers (`er-apply-lists-bif`, `er-apply-io-bif`, `er-apply-ets-bif`, `er-apply-code-bif`) are deleted. `er-apply-bif` and `er-apply-remote-bif` are now ~5-line registry lookups; user modules still win precedence over the registry.
|
||||
- [ ] Term-marshalling helpers: `er-of-sx` and `er-to-sx` (atom ↔ symbol, tuple ↔ list-of-elements, Erlang list ↔ SX list, binary ↔ string, dict ↔ map). Round-trip tests
|
||||
- [ ] `crypto:hash/2` — `sha256`, `sha512`, `blake3`; takes a binary, returns a binary. Uses the SX-host hash primitive
|
||||
- [ ] `cid:from_bytes/1`, `cid:to_string/1` — content-address an arbitrary binary
|
||||
@@ -126,6 +126,8 @@ Replace today's hardcoded BIF dispatch (`er-apply-bif`/`er-apply-remote-bif` in
|
||||
|
||||
_Newest first._
|
||||
|
||||
- **2026-05-14 BIF registry migration complete — cond chains gone** — `er-register-builtin-bifs!` at the end of `runtime.sx` populates the registry with all 67 built-in BIFs in five module namespaces. Pure ops (`length`, `hd`, `tl`, `element`, predicates, arithmetic, list/atom/integer conversions, all of `lists`) registered via `er-register-pure-bif!`; side-effecting ops (`spawn`, `self`, `exit`, `link`/`monitor`/`register`, `process_flag`, `make_ref`, `throw`/`error`, `io:format`, all of `ets`, all of `code`) via `er-register-bif!`. Multi-arity entries: `is_function/1`/`/2`, `spawn/1`/`/3`, `exit/1`/`/2`, `io:format/1`/`/2`, `lists:seq/2`/`/3`, `ets:delete/1`/`/2` — six pairs, twelve registrations, all pointing at the existing arity-dispatching impl. `throw` and `error` are registered with a tiny inline `(fn (vs) (raise ...))` lambda because the original code chained directly through `raise` inside the cond instead of an `er-bif-*` helper. `er-apply-bif` shrinks from a 44-line cond chain to a 5-line registry lookup. `er-apply-remote-bif` becomes a 7-line dispatcher (user-modules-first → registry → error). All four per-module dispatchers (`er-apply-lists-bif`, `er-apply-io-bif`, `er-apply-ets-bif`, `er-apply-code-bif`) deleted — net reduction ~110 lines of cond machinery. One subtle wrinkle: `tests/runtime.sx` calls `er-bif-registry-reset!` near the end of its BIF-registry tests, which would have left subsequent test files (ring, ping-pong, etc.) unable to call `length`/`spawn`/etc. Fix: re-call `er-register-builtin-bifs!` at the bottom of `tests/runtime.sx` to repopulate. Total **600/600** unchanged.
|
||||
|
||||
- **2026-05-14 Phase 8 BIF registry foundation** — `lib/erlang/runtime.sx` gains `er-bif-registry` (a `(list {})` mutable cell, same shape as `er-modules`) and five helpers: `er-bif-registry-get`/`er-bif-registry-reset!` (access + reset), `er-bif-key` (format `"Module/Name/Arity"`), `er-register-bif!` and `er-register-pure-bif!` (both upsert; differ only in the `:pure?` flag — pure ones are safe to inline, side-effecting ones go through normal IO), `er-lookup-bif` (returns the entry dict or nil), `er-list-bifs` (registered keys). Entries are `{:module :name :arity :fn :pure?}`. Lookup miss → nil; arity is part of the key so `m:f/1` and `m:f/2` are distinct; re-registering the same key replaces in-place (count stays the same); reset clears. Registry sits alongside `er-modules` in runtime.sx so any other piece of the system can register BIFs without touching the dispatcher — the migration onto this registry (the next checkbox) will rip out the giant cond chains in `er-apply-bif`/`er-apply-remote-bif`. 18 new runtime tests in `tests/runtime.sx`: empty-state, lookup-miss, register-grows-count, lookup-hit-fields (module/name/arity/pure?), fn-invocable, re-register-replaces, pure-flag-true, arity-disambiguation (3 entries for `fake:echo/1`, `fake:echo/2`, `fake:pure/2`), reset-clears, reset-lookup-nil. Total **600/600** (+18 runtime).
|
||||
|
||||
- **2026-05-14 Phase 7 capstone green — full hot-reload ladder works end-to-end** — Wires everything from the previous five iterations into one test program: load cap v1 with `start/0` (spawn-from-inside-module) + `loop/0` + `tag/0` → spawn Pid1 (running v1) → load cap v2 → assert `cap:tag()` returns v2 (cross-module dispatch hits `:current`) → spawn Pid2 (running v2) → `code:soft_purge(cap)` returns `false` (refuses while Pid1 is alive on v1's env) → `code:purge(cap)` returns `true` (kills Pid1, clears `:old`) → `code:soft_purge(cap)` returns `true` (clean — no `:old` left). To make this work, `er-procs-on-env` was extended with a new helper `er-env-derived-from?`: a process counts as "running on" mod-env if its `:initial-fun`'s `:env` IS mod-env directly OR contains at least one binding whose value is a fun closed over mod-env. Reason: `er-apply-fun-clauses` always `er-env-copy`s the closure-env before binding params, so a fun created inside a module body has a `:env` that's a *copy* of mod-env, not mod-env itself — the copy still contains the module's other functions as values, each pointing back to the canonical mod-env. The whole ladder runs as a single `erlang-eval-ast` invocation because each call to `ev` resets the scheduler via `er-sched-init!`, wiping any cross-call Pids. 5 capstone tests: v1 tag, v2 tag (cross-mod after reload), soft_purge-refuses, hard purge, soft_purge-clean-after-hard. Total **582/582** (+5 eval). Phase 7 fully ticked.
|
||||
|
||||
Reference in New Issue
Block a user