Class: Lilac::CLI::WasmMrbcDriver
- Inherits:
-
Object
- Object
- Lilac::CLI::WasmMrbcDriver
- Defined in:
- lib/lilac/cli/build/wasm_mrbc_driver.rb
Overview
Drives mrbc-host.wasm from the host Ruby via wasmtime-rb to
produce mruby bytecode in place of an external mrbc binary.
Wire-level ABI (defined in runtime/mruby-host-compile/src/host_compile.c):
compile_source(src_ptr, src_len,
out_ptr_outp, out_len_outp,
err_ptr_outp, err_len_outp) -> i32 status
mrbc_alloc(len) -> i32 ptr (host writes/reads through this buffer)
mrbc_free(ptr) (host frees both input and output)
Status codes:
0 ok — irep bytes in out_ptr / out_len
1 compile fail — utf-8 message in err_ptr / err_len
2 no compiler — wasm was built without mruby-compiler
Lifecycle: the engine + module + instance are created once on the
first compile call and reused across subsequent calls within a
single BytecodeBuilder invocation. mruby's mrb_state itself is
opened/closed inside the wasm per call (see compile_source in
host_compile.c) — this matches mrbc binary's process-per-invocation
semantics and keeps symbol-table growth from leaking across builds.
Defined Under Namespace
Classes: CompileError, Error, WasmExportMissingError, WasmtimeMissingError
Constant Summary collapse
- REQUIRED_EXPORTS =
%w[compile_source mrbc_alloc mrbc_free memory].freeze
Instance Attribute Summary collapse
-
#wasm_path ⇒ Object
readonly
Returns the value of attribute wasm_path.
Class Method Summary collapse
-
.available?(wasm_path:) ⇒ Boolean
Test whether a usable backend can be constructed from
wasm_path.
Instance Method Summary collapse
-
#compile(ruby_source) ⇒ Object
Compile a Ruby source string and return the raw mruby bytecode (binary string, starts with "RITE" magic).
-
#initialize(wasm_path:) ⇒ WasmMrbcDriver
constructor
A new instance of WasmMrbcDriver.
Constructor Details
#initialize(wasm_path:) ⇒ WasmMrbcDriver
Returns a new instance of WasmMrbcDriver.
69 70 71 |
# File 'lib/lilac/cli/build/wasm_mrbc_driver.rb', line 69 def initialize(wasm_path:) @wasm_path = wasm_path end |
Instance Attribute Details
#wasm_path ⇒ Object (readonly)
Returns the value of attribute wasm_path.
73 74 75 |
# File 'lib/lilac/cli/build/wasm_mrbc_driver.rb', line 73 def wasm_path @wasm_path end |
Class Method Details
.available?(wasm_path:) ⇒ Boolean
Test whether a usable backend can be constructed from wasm_path.
Returns true only when wasmtime-rb is loadable AND the module
parses + instantiates AND all required exports are present.
Used by BytecodeBuilder#resolve_backend! to decide between the
wasm-driven and binary mrbc paths; also surfaced via lilac doctor
so users can see exactly why the wasm path was/wasn't picked.
wasmtime-rb's Module doesn't expose exports directly, so we
actually instantiate to enumerate the exports. The cost (~200-500ms
cold) is paid once per lilac build, which is acceptable for a
build-time tool.
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/lilac/cli/build/wasm_mrbc_driver.rb', line 47 def self.available?(wasm_path:) return false unless wasm_path && File.file?(wasm_path) begin require 'wasmtime' rescue LoadError return false end begin engine = Wasmtime::Engine.new(wasm_exceptions: true) mod = Wasmtime::Module.from_file(engine, wasm_path) linker = Wasmtime::Linker.new(engine) Wasmtime::WASI::P1.add_to_linker_sync(linker) store = Wasmtime::Store.new(engine, wasi_p1_config: Wasmtime::WasiConfig.new) instance = linker.instantiate(store, mod) REQUIRED_EXPORTS.all? { |name| !instance.export(name).nil? } rescue Wasmtime::Error, ArgumentError false end end |
Instance Method Details
#compile(ruby_source) ⇒ Object
Compile a Ruby source string and return the raw mruby bytecode (binary string, starts with "RITE" magic). Raises CompileError on syntax/semantic errors (carrying the parser's error buffer text) or WasmExportMissingError / WasmtimeMissingError on environment problems.
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/lilac/cli/build/wasm_mrbc_driver.rb', line 80 def compile(ruby_source) ensure_instance! # mrbc_alloc returns 0 on size <= 0 or malloc failure. We allocate # six buffers per call: # src_buf — caller-written source bytes # out_p/out_l — out-params written by compile_source on success # err_p/err_l — out-params written by compile_source on failure # The three out-param pairs are 4-byte i32 each. src_bytes = ruby_source.b src_buf = @alloc.call(src_bytes.bytesize) raise CompileError, 'mrbc_alloc returned 0 for src buffer' if src_buf.zero? out_p = @alloc.call(4) out_l = @alloc.call(4) err_p = @alloc.call(4) err_l = @alloc.call(4) if [out_p, out_l, err_p, err_l].any?(&:zero?) [src_buf, out_p, out_l, err_p, err_l].each { |p| @free.call(p) unless p.zero? } raise CompileError, 'mrbc_alloc returned 0 for out-param buffers' end @memory.write(src_buf, src_bytes) status = @compile.call(src_buf, src_bytes.bytesize, out_p, out_l, err_p, err_l) irep_ptr = read_i32(out_p) irep_len = read_i32(out_l) err_ptr = read_i32(err_p) err_len = read_i32(err_l) case status when 0 bytecode = @memory.read(irep_ptr, irep_len) @free.call(irep_ptr) bytecode when 1 = err_len.positive? ? @memory.read(err_ptr, err_len).force_encoding('UTF-8') : '(no message)' @free.call(err_ptr) unless err_ptr.zero? raise CompileError, when 2 raise WasmExportMissingError, 'mrbc-host.wasm was built without mruby-compiler — rebuild with build_config/mrbc-host.rb' else raise CompileError, "compile_source returned unexpected status=#{status}" end ensure # Free the input buffer and the four out-param scratch buffers. # irep_ptr / err_ptr were freed inside the case branches because # they're the wasm's own malloc'd outputs (separate from the # 4-byte holders the host allocated for the pointer values). [src_buf, out_p, out_l, err_p, err_l].compact.each do |p| @free.call(p) unless p.zero? end end |