⎇ e94023d8af790ded497f740d7abe811ce0523be9
renaser/kernel/src/wasm/mod.rs
History
Who touched what
raw
feat(renaser): Fase 8a — el compositor teselante
1// =============================================================================
2// renaser :: kernel/src/wasm — Fase 4/5 :: el escudo de aislamiento (WASM)
3// -----------------------------------------------------------------------------
4// Aqui renaser sustituye las costosas fronteras de hardware (la MMU, los
5// anillos de la CPU) por limites MATEMATICOS sobre el bytecode. Una aplicacion
6// WebAssembly se ejecuta en su propia memoria lineal; sus unicas puertas al
7// exterior son las capacidades que el enlazador del host le concede. Lo que
8// no este importado no existe: no hay camino fisico para ejecutarlo.
9//
10// FASE 5 :: el aislamiento deja de ser solo ESPACIAL (memoria) y pasa a ser
11// tambien TEMPORAL (tiempo de CPU). Cada `tick` se ejecuta con un presupuesto
12// estricto de COMBUSTIBLE (fuel): si una app lo agota —un bucle infinito, un
13// trabajo desmedido—, el runtime lanza una trampa, el kernel recupera el mando
14// y la desaloja. Ningun modulo, por discolo que sea, secuestra el procesador.
15// =============================================================================
16
17pub mod env;
18
19use wasmi::{
20 CompilationMode, Config, Engine, Linker, Module, Store, StoreLimitsBuilder, TrapCode, TypedFunc,
21};
22
23use crate::grafico::{Color, RegionPantalla};
24use env::ContextoCapacidades;
25
26/// Combustible concedido a `init`. Cubre con holgura el pintado inicial del
27/// fondo de una region a pantalla casi completa — un gasto unico, de arranque.
28const FUEL_ARRANQUE: u64 = 20_000_000;
29
30/// Combustible concedido a cada `tick`. Sobra para un fotograma honesto (unos
31/// cientos de miles de operaciones); una app en bucle infinito lo agota en
32/// milisegundos y es desalojada. Este numero ES el techo temporal del userspace.
33const FUEL_FOTOGRAMA: u64 = 2_000_000;
34
35/// Por que el kernel da por terminada —desaloja— una aplicacion WASM.
36#[derive(Clone, Copy)]
37pub enum FallaApp {
38 /// El modulo no se pudo cargar, validar, enlazar o instanciar.
39 Carga,
40 /// La aplicacion agoto su combustible dentro de un `tick`: bucle infinito
41 /// o trabajo desmedido. El guardarrail TEMPORAL en accion.
42 SinCombustible,
43 /// La aplicacion intento crecer su memoria lineal mas alla de su cuota.
44 /// El guardarrail ESPACIAL en accion.
45 SinMemoria,
46 /// La aplicacion ejecuto una trampa: acceso fuera de limites, instruccion
47 /// `unreachable`, una capacidad violada... su propio codigo la abortó.
48 Trampa,
49}
50
51impl FallaApp {
52 /// El color de la baliza de desalojo segun la causa de la falla: amarillo
53 /// palido si reviento su techo de memoria, purpura para cualquier otra.
54 pub fn color_baliza(self) -> Color {
55 match self {
56 FallaApp::SinMemoria => Color::DESALOJO_MEMORIA,
57 _ => Color::DESALOJO,
58 }
59 }
60}
61
62/// Una aplicacion WebAssembly viva: su estado PERSISTE entre fotogramas. A
63/// diferencia de la Fase 4 —que instanciaba y cedia el control de un gesto—,
64/// aqui la instancia se conserva y el kernel la hace avanzar `tick` a `tick`.
65pub struct AplicacionWasm {
66 /// El almacen: todo el estado de ESTA instancia — su memoria lineal, sus
67 /// globales y el contexto de capacidades con su region de pantalla.
68 almacen: Store<ContextoCapacidades>,
69 /// El punto de entrada de fotograma, ya resuelto y con seguridad de tipos.
70 /// `TypedFunc` es un asa autosuficiente dentro del `Store`: conservada esta,
71 /// el handle de la `Instance` no aporta nada y no se retiene.
72 func_tick: TypedFunc<(), ()>,
73 /// El marco que el compositor asigno a la app — su ventana en pantalla, y
74 /// donde se tatua su baliza de desalojo si llega a fallar.
75 marco: RegionPantalla,
76}
77
78impl AplicacionWasm {
79 /// Carga, valida, instancia y arranca una aplicacion WASM aislada, ligada a
80 /// una region de pantalla. Si algo falla en el camino, se devuelve la falla
81 /// en lugar de incendiar el kernel.
82 ///
83 /// El nuevo ABI del userspace exige dos exportaciones: `init` —invocada una
84 /// sola vez, aqui— y `tick` —un fotograma de trabajo, invocada despues por
85 /// el reactor en cada pulso del reloj.
86 ///
87 /// `marco` es el rectangulo que el compositor (Fase 8) asigno a la app;
88 /// `natural_ancho`/`natural_alto`, el tamaño de su lienzo. `techo_memoria`
89 /// es su cuota de memoria lineal —la dicta su `EntradaApp` del manifiesto—,
90 /// e `indice_app` su posicion en el: su identidad para las capacidades de
91 /// estado persistido (Fase 7c).
92 pub fn cargar(
93 bytecode: &[u8],
94 marco: RegionPantalla,
95 natural_ancho: usize,
96 natural_alto: usize,
97 techo_memoria: usize,
98 indice_app: usize,
99 ) -> Result<AplicacionWasm, FallaApp> {
100 // 1. El motor, con metricas de combustible y compilacion ANTICIPADA: la
101 // traduccion del modulo ocurre ahora, de modo que el `fuel` mida
102 // despues solo EJECUCION, jamas compilacion diferida.
103 let mut config = Config::default();
104 config.consume_fuel(true);
105 config.compilation_mode(CompilationMode::Eager);
106 let motor = Engine::new(&config);
107
108 // 2. Validar y traducir el modulo — ya instrumentado con fuel.
109 let modulo = Module::new(&motor, bytecode).map_err(|_| FallaApp::Carga)?;
110
111 // 3. El almacen, con el contexto de capacidades de ESTA app: su region
112 // de pantalla, su canal de teclado y su techo de memoria. El canal
113 // se crea ahora pero se inscribe en la difusion de la IRQ1 al final,
114 // ya con la app cargada: una carga fallida no deja canales huerfanos.
115 let canal = crate::async_system::teclado::crear_canal();
116 let limites = StoreLimitsBuilder::new()
117 .memory_size(techo_memoria)
118 // Una expansion denegada se convierte en TRAMPA, no en un -1 que la
119 // app pudiera ignorar: asi el kernel la captura y la desaloja.
120 .trap_on_grow_failure(true)
121 .build();
122 let mut almacen = Store::new(
123 &motor,
124 ContextoCapacidades {
125 marco,
126 natural_ancho,
127 natural_alto,
128 canal,
129 limites,
130 indice_app,
131 },
132 );
133 // Ligar el limitador de recursos: `wasmi` lo consultara en cada
134 // `memory.grow`, tambien durante la instanciacion.
135 almacen.limiter(|contexto| &mut contexto.limites);
136 // Dotar de combustible ANTES de instanciar: la instanciacion no debe
137 // quedarse a cero y abortar.
138 almacen.set_fuel(FUEL_ARRANQUE).map_err(|_| FallaApp::Carga)?;
139
140 // 4. El enlazador y la matriz de capacidades (ver `env`).
141 let mut enlazador: Linker<ContextoCapacidades> = Linker::new(&motor);
142 env::enlazar_capacidades(&mut enlazador).map_err(|_| FallaApp::Carga)?;
143
144 // 5. Instanciar, resolviendo las importaciones contra las capacidades.
145 let instancia = enlazador
146 .instantiate_and_start(&mut almacen, &modulo)
147 .map_err(|_| FallaApp::Carga)?;
148
149 // 6. Resolver los dos puntos del ABI de fotograma: `init` y `tick`.
150 let func_init = instancia
151 .get_typed_func::<(), ()>(&almacen, "init")
152 .map_err(|_| FallaApp::Carga)?;
153 let func_tick = instancia
154 .get_typed_func::<(), ()>(&almacen, "tick")
155 .map_err(|_| FallaApp::Carga)?;
156
157 // 7. Arranque unico: `init` prepara el estado inicial de la aplicacion.
158 almacen.set_fuel(FUEL_ARRANQUE).map_err(|_| FallaApp::Carga)?;
159 func_init
160 .call(&mut almacen, ())
161 .map_err(|_| FallaApp::Carga)?;
162
163 // 8. Con la app ya cargada e instanciada, inscribir su canal de teclado
164 // en la difusion de la IRQ1: desde aqui recibe cada pulsacion.
165 crate::async_system::teclado::registrar_canal(&almacen.data().canal);
166
167 Ok(AplicacionWasm {
168 almacen,
169 func_tick,
170 marco,
171 })
172 }
173
174 /// Hace avanzar la aplicacion un fotograma. Recarga su presupuesto de
175 /// combustible y le cede el control con `tick`. Si la app lo agota o ejecuta
176 /// una trampa, el kernel recupera el mando y la falla se devuelve para que
177 /// la tarea proceda al desalojo. El kernel nunca pierde el control.
178 pub fn tick(&mut self) -> Result<(), FallaApp> {
179 // Recargar el deposito: cada fotograma parte con su techo intacto.
180 self.almacen
181 .set_fuel(FUEL_FOTOGRAMA)
182 .map_err(|_| FallaApp::Trampa)?;
183
184 match self.func_tick.call(&mut self.almacen, ()) {
185 Ok(()) => Ok(()),
186 // `as_trap_code` da un codigo publico y univoco para cada causa:
187 // `OutOfFuel` pliega toda variante de agotamiento de combustible;
188 // `GrowthOperationLimited` es la cuota de memoria denegada.
189 Err(error) => match error.as_trap_code() {
190 Some(TrapCode::OutOfFuel) => Err(FallaApp::SinCombustible),
191 Some(TrapCode::GrowthOperationLimited) => Err(FallaApp::SinMemoria),
192 _ => Err(FallaApp::Trampa),
193 },
194 }
195 }
196
197 /// El marco de pantalla que el compositor asigno a la aplicacion — donde
198 /// se tatua su baliza si el kernel llega a desalojarla.
199 pub fn marco(&self) -> RegionPantalla {
200 self.marco
201 }
202}
203
204/// Reconciliacion del ciclo de vida. Cuando una `AplicacionWasm` muere —porque
205/// fue desalojada y su tarea concluyo—, su canal de teclado debe darse de baja
206/// de la difusion de la IRQ1. Sin esto, el manejador de interrupciones seguiria
207/// empujando scancodes a una cola muerta: una fuga lenta pero segura.
208impl Drop for AplicacionWasm {
209 fn drop(&mut self) {
210 crate::async_system::teclado::cerrar_canal(&self.almacen.data().canal);
211 }
212}