⎇ d6b8f18b43c9bd9bf7268e48b61392a2d62bb229
crates/ente-wasm/src/lib.rs
Historia
Quién tocó qué
crudo
Pausa: 11 crates del fractal Ente #0 con cerebro completo
1//! Encarnación de Payload::Wasm vía wasmi.
2//!
3//! Cada Ente Wasm corre en un hilo dedicado (wasmi es síncrono) que se
4//! comunica con el grafo vía un identificador propio. El thread::JoinHandle
5//! se descarta — el ciclo de vida del Wasm se controla por su `entry`
6//! function: cuando retorna, el Ente se considera disuelto.
7//!
8//! ## Host imports expuestos
9//! - `ente.log(ptr: i32, len: i32)` imprime una string UTF-8
10//! - `ente.exit(code: i32)` solicita salida del Ente
11//!
12//! Más adelante: `ente.bus_call`, `ente.cap_invoke`, etc.
13
14use ente_card::EntityCard;
15use std::sync::atomic::{AtomicI32, Ordering};
16use std::sync::Arc;
17use tracing::{error, info, warn};
18use wasmi::{Caller, Engine, Linker, Memory, Module, Store};
19
20/// Estado por instancia Wasm. Se accede tanto desde host imports (vía
21/// `Caller::data()`) como desde el thread runner para estado de salida.
22pub struct WasmEnte {
23 pub id: ulid::Ulid,
24 pub label: String,
25 pub exit_code: Arc<AtomicI32>,
26}
27
28/// Encarna un payload Wasm en un hilo dedicado. Devuelve un identificador
29/// no-PID que el grafo trata como Ente Virtual con cuerpo de cómputo.
30pub fn incarnate_wasm(card: &EntityCard, module_bytes: Vec<u8>, entry: String) -> anyhow::Result<()> {
31 let label = card.label.clone();
32 let id = card.id;
33 let exit_code = Arc::new(AtomicI32::new(0));
34 let exit_code_handle = exit_code.clone();
35
36 std::thread::Builder::new()
37 .name(format!("wasm-{label}"))
38 .spawn(move || {
39 if let Err(e) = run_wasm(WasmEnte { id, label: label.clone(), exit_code: exit_code_handle.clone() }, &module_bytes, &entry) {
40 error!(?e, %label, "Wasm ente terminó con error");
41 exit_code_handle.store(-1, Ordering::Relaxed);
42 }
43 })?;
44 Ok(())
45}
46
47fn run_wasm(ente: WasmEnte, module_bytes: &[u8], entry: &str) -> anyhow::Result<()> {
48 let engine = Engine::default();
49 let module = Module::new(&engine, module_bytes)
50 .map_err(|e| anyhow::anyhow!("Wasm module compile: {e}"))?;
51 let mut store = Store::new(&engine, ente);
52 let mut linker = <Linker<WasmEnte>>::new(&engine);
53
54 linker.func_wrap("ente", "log", |caller: Caller<'_, WasmEnte>, ptr: i32, len: i32| {
55 host_log(caller, ptr, len);
56 })?;
57
58 linker.func_wrap("ente", "exit", |mut caller: Caller<'_, WasmEnte>, code: i32| {
59 caller.data_mut().exit_code.store(code, Ordering::Relaxed);
60 })?;
61
62 let pre = linker.instantiate(&mut store, &module)
63 .map_err(|e| anyhow::anyhow!("Wasm instantiate: {e}"))?;
64 let instance = pre.start(&mut store)
65 .map_err(|e| anyhow::anyhow!("Wasm start: {e}"))?;
66
67 let func = instance.get_typed_func::<(), ()>(&store, entry)
68 .map_err(|e| anyhow::anyhow!("Wasm get_func {entry}: {e}"))?;
69
70 info!(label = %store.data().label, %entry, "Wasm ente ejecutando");
71 func.call(&mut store, ()).map_err(|e| anyhow::anyhow!("Wasm call {entry}: {e}"))?;
72 let code = store.data().exit_code.load(Ordering::Relaxed);
73 info!(label = %store.data().label, code, "Wasm ente terminó");
74 Ok(())
75}
76
77fn host_log(caller: Caller<'_, WasmEnte>, ptr: i32, len: i32) {
78 let memory = match caller.get_export("memory").and_then(|e| e.into_memory()) {
79 Some(m) => m,
80 None => {
81 warn!("Wasm ente sin memoria exportada — log ignorado");
82 return;
83 }
84 };
85 let data = read_memory(&caller, memory, ptr, len);
86 match std::str::from_utf8(&data) {
87 Ok(s) => info!(label = %caller.data().label, "[wasm] {s}"),
88 Err(_) => warn!(label = %caller.data().label, "Wasm log con bytes no UTF-8"),
89 }
90}
91
92fn read_memory(caller: &Caller<'_, WasmEnte>, memory: Memory, ptr: i32, len: i32) -> Vec<u8> {
93 let ptr = ptr.max(0) as usize;
94 let len = len.max(0) as usize;
95 let data = memory.data(caller);
96 if ptr.saturating_add(len) > data.len() {
97 return Vec::new();
98 }
99 data[ptr..ptr + len].to_vec()
100}
101
102/// Módulo WAT mínimo de demostración. Llama a `ente.log` con "hola fractal".
103/// Compilado a binario Wasm en runtime con `wat`.
104pub fn demo_module_bytes() -> anyhow::Result<Vec<u8>> {
105 let wat = r#"
106(module
107 (import "ente" "log" (func $log (param i32 i32)))
108 (import "ente" "exit" (func $exit (param i32)))
109 (memory (export "memory") 1)
110 (data (i32.const 0) "hola fractal desde wasm")
111 (func (export "_start")
112 (call $log (i32.const 0) (i32.const 23))
113 (call $exit (i32.const 0))
114 )
115)
116"#;
117 Ok(wat::parse_str(wat)?)
118}