gioser
⎇ 5c462e6d307addd58614a8c4495f4becda04e4b2 renaser/kernel/src/wasm/mod.rs Rust · 203 lines · 9.4 KB History Who touched what raw

feat(renaser): Fases 8b y 8c — el escritorio interactivo 5c462e6d3 · sergio ·

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;
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 identidad e indice.
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}
74
75impl AplicacionWasm {
76    /// Carga, valida, instancia y arranca una aplicacion WASM aislada. Si algo
77    /// falla en el camino, se devuelve la falla en lugar de incendiar el kernel.
78    ///
79    /// El ABI del userspace exige dos exportaciones: `init` —invocada una sola
80    /// vez, aqui— y `tick` —un fotograma de trabajo, invocada despues por el
81    /// reactor en cada pulso del reloj.
82    ///
83    /// `natural_ancho`/`natural_alto` son el tamaño del lienzo de la app;
84    /// `techo_memoria`, su cuota de memoria lineal —la dicta su `EntradaApp` del
85    /// manifiesto—; e `indice_app`, su identidad: la posicion con que el
86    /// compositor halla su ventana y las capacidades de estado su ranura.
87    pub fn cargar(
88        bytecode: &[u8],
89        natural_ancho: usize,
90        natural_alto: usize,
91        techo_memoria: usize,
92        indice_app: usize,
93    ) -> Result<AplicacionWasm, FallaApp> {
94        // 1. El motor, con metricas de combustible y compilacion ANTICIPADA: la
95        //    traduccion del modulo ocurre ahora, de modo que el `fuel` mida
96        //    despues solo EJECUCION, jamas compilacion diferida.
97        let mut config = Config::default();
98        config.consume_fuel(true);
99        config.compilation_mode(CompilationMode::Eager);
100        let motor = Engine::new(&config);
101
102        // 2. Validar y traducir el modulo — ya instrumentado con fuel.
103        let modulo = Module::new(&motor, bytecode).map_err(|_| FallaApp::Carga)?;
104
105        // 3. El almacen, con el contexto de capacidades de ESTA app: su lienzo
106        //    natural, su canal de teclado y su techo de memoria. El canal se
107        //    crea ahora pero se inscribe en el censo de la IRQ1 al final, ya con
108        //    la app cargada: una carga fallida no deja canales huerfanos.
109        let canal = crate::async_system::teclado::crear_canal();
110        let limites = StoreLimitsBuilder::new()
111            .memory_size(techo_memoria)
112            // Una expansion denegada se convierte en TRAMPA, no en un -1 que la
113            // app pudiera ignorar: asi el kernel la captura y la desaloja.
114            .trap_on_grow_failure(true)
115            .build();
116        let mut almacen = Store::new(
117            &motor,
118            ContextoCapacidades {
119                natural_ancho,
120                natural_alto,
121                canal,
122                limites,
123                indice_app,
124            },
125        );
126        // Ligar el limitador de recursos: `wasmi` lo consultara en cada
127        // `memory.grow`, tambien durante la instanciacion.
128        almacen.limiter(|contexto| &mut contexto.limites);
129        // Dotar de combustible ANTES de instanciar: la instanciacion no debe
130        // quedarse a cero y abortar.
131        almacen.set_fuel(FUEL_ARRANQUE).map_err(|_| FallaApp::Carga)?;
132
133        // 4. El enlazador y la matriz de capacidades (ver `env`).
134        let mut enlazador: Linker<ContextoCapacidades> = Linker::new(&motor);
135        env::enlazar_capacidades(&mut enlazador).map_err(|_| FallaApp::Carga)?;
136
137        // 5. Instanciar, resolviendo las importaciones contra las capacidades.
138        let instancia = enlazador
139            .instantiate_and_start(&mut almacen, &modulo)
140            .map_err(|_| FallaApp::Carga)?;
141
142        // 6. Resolver los dos puntos del ABI de fotograma: `init` y `tick`.
143        let func_init = instancia
144            .get_typed_func::<(), ()>(&almacen, "init")
145            .map_err(|_| FallaApp::Carga)?;
146        let func_tick = instancia
147            .get_typed_func::<(), ()>(&almacen, "tick")
148            .map_err(|_| FallaApp::Carga)?;
149
150        // 7. Arranque unico: `init` prepara el estado inicial de la aplicacion.
151        almacen.set_fuel(FUEL_ARRANQUE).map_err(|_| FallaApp::Carga)?;
152        func_init
153            .call(&mut almacen, ())
154            .map_err(|_| FallaApp::Carga)?;
155
156        // 8. Con la app ya cargada e instanciada, inscribir su canal de teclado
157        //    en el censo de la IRQ1, en la ranura de su `indice_app`: desde
158        //    aqui recibe las teclas cuando el compositor le da el foco.
159        crate::async_system::teclado::registrar_canal(indice_app, &almacen.data().canal);
160
161        Ok(AplicacionWasm { almacen, func_tick })
162    }
163
164    /// Hace avanzar la aplicacion un fotograma. Recarga su presupuesto de
165    /// combustible y le cede el control con `tick`. Si la app lo agota o ejecuta
166    /// una trampa, el kernel recupera el mando y la falla se devuelve para que
167    /// la tarea proceda al desalojo. El kernel nunca pierde el control.
168    pub fn tick(&mut self) -> Result<(), FallaApp> {
169        // Recargar el deposito: cada fotograma parte con su techo intacto.
170        self.almacen
171            .set_fuel(FUEL_FOTOGRAMA)
172            .map_err(|_| FallaApp::Trampa)?;
173
174        match self.func_tick.call(&mut self.almacen, ()) {
175            Ok(()) => Ok(()),
176            // `as_trap_code` da un codigo publico y univoco para cada causa:
177            // `OutOfFuel` pliega toda variante de agotamiento de combustible;
178            // `GrowthOperationLimited` es la cuota de memoria denegada.
179            Err(error) => match error.as_trap_code() {
180                Some(TrapCode::OutOfFuel) => Err(FallaApp::SinCombustible),
181                Some(TrapCode::GrowthOperationLimited) => Err(FallaApp::SinMemoria),
182                _ => Err(FallaApp::Trampa),
183            },
184        }
185    }
186
187    /// El indice de la aplicacion — su identidad en el escritorio del
188    /// compositor. Lo usa la tarea de la app para decirle al compositor que
189    /// ventana desalojar si la app llega a fallar.
190    pub fn indice(&self) -> usize {
191        self.almacen.data().indice_app
192    }
193}
194
195/// Reconciliacion del ciclo de vida. Cuando una `AplicacionWasm` muere —porque
196/// fue desalojada y su tarea concluyo—, su canal de teclado debe darse de baja
197/// de la difusion de la IRQ1. Sin esto, el manejador de interrupciones seguiria
198/// empujando scancodes a una cola muerta: una fuga lenta pero segura.
199impl Drop for AplicacionWasm {
200    fn drop(&mut self) {
201        crate::async_system::teclado::cerrar_canal(self.almacen.data().indice_app);
202    }
203}