aot: marshal compiled namespaces to bytecode images
Adds src/jolt/aot.janet: save-ns / load-ns-image marshal a namespace's compiled var cells to a Janet bytecode image and load them back, skipping parse/analyze/emit/compile on reload. Compiled functions close over core fns (cfunctions) that can't marshal by value, so we marshal against a dictionary built from the baked-in runtime env (env-lookup jolt-runtime-env, which chains to the Janet boot env): core fns and builtins are referenced by name, only user bytecode and its var cells are serialized. The runtime env is identical at save and load time (same binary), so the dictionaries match. Test round-trips a namespace (constant, fn over it, core-fn user, recursion) into a fresh context and confirms the loaded vars run and stay redefinable.
This commit is contained in:
parent
62c5060093
commit
877373b7e6
2 changed files with 90 additions and 0 deletions
47
src/jolt/aot.janet
Normal file
47
src/jolt/aot.janet
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# Ahead-of-time images for compiled namespaces.
|
||||
#
|
||||
# Compile-by-default turns each form into Janet bytecode at load time. AOT skips
|
||||
# that work on subsequent runs by serializing a namespace's compiled vars to a
|
||||
# bytecode image and loading them back.
|
||||
#
|
||||
# The trick is the marshal dictionary. A compiled jolt function closes over core
|
||||
# fns (core-map, +, …) and var cells; those core fns are Janet cfunctions/closures
|
||||
# that can't be marshaled by value. But the runtime env that holds them is baked
|
||||
# into the binary and is byte-for-byte identical at save and load time, so we
|
||||
# marshal *against* it: core fns are referenced by name, and only the user's
|
||||
# bytecode plus its var cells are actually serialized.
|
||||
|
||||
(use ./compiler) # jolt-runtime-env
|
||||
(use ./types)
|
||||
|
||||
# Forward dict (key -> value) for unmarshal; reverse (value -> key) for marshal.
|
||||
# Built from the runtime env, which chains to the Janet boot env, so both core fns
|
||||
# and Janet builtins resolve by name.
|
||||
(defn- fwd-dict [] (env-lookup jolt-runtime-env))
|
||||
(defn- rev-dict [] (invert (env-lookup jolt-runtime-env)))
|
||||
|
||||
(defn marshal-ns
|
||||
"Marshal namespace `ns-name`'s var mappings to a byte buffer. The whole mappings
|
||||
table is marshaled in one call so var cells shared between defs stay shared."
|
||||
[ctx ns-name]
|
||||
(marshal ((ctx-find-ns ctx ns-name) :mappings) (rev-dict)))
|
||||
|
||||
(defn unmarshal-ns!
|
||||
"Install mappings produced by marshal-ns into `ns-name` in ctx, overwriting
|
||||
same-named vars. Returns ns-name."
|
||||
[ctx ns-name bytes]
|
||||
(let [mappings (unmarshal bytes (fwd-dict))
|
||||
ns (ctx-find-ns ctx ns-name)]
|
||||
(each [sym v] (pairs mappings) (put (ns :mappings) sym v))
|
||||
ns-name))
|
||||
|
||||
(defn save-ns
|
||||
"Write an AOT image of compiled namespace `ns-name` to `path`."
|
||||
[ctx ns-name path]
|
||||
(spit path (marshal-ns ctx ns-name)))
|
||||
|
||||
(defn load-ns-image
|
||||
"Read an AOT image written by save-ns back into ctx under `ns-name`. Skips
|
||||
parse/analyze/emit/compile entirely — the bytecode is already built."
|
||||
[ctx ns-name path]
|
||||
(unmarshal-ns! ctx ns-name (slurp path)))
|
||||
Loading…
Add table
Add a link
Reference in a new issue