gioser

tawasuyu/brahman

Rust 533TOML 203Markdown 58JSON 197650 Sutichaykuna

⎇ bce4abd8cc7cf59daef42c8901d705ed8bb16a92 renaser/kernel/src/wasm/mod.rs Rust · 211 siq'ikuna · 9.6 KB Ñawpa willay Pi imata llamkarqan ch'uya

feat(renaser): Fase 7a — el userspace nace del Grafo de Objetos bce4abd8c · 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, 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/// Techo de memoria lineal por aplicacion: 4 MiB. Un modulo que intente crecer
36/// su memoria mas alla es desalojado — el aislamiento ESPACIAL del userspace,
37/// gemelo del techo TEMPORAL que impone el combustible.
38///
39/// Desde la Fase 7 el techo es POR-APP: cada `EntradaApp` del manifiesto
40/// lleva el suyo. Esta constante es el valor por DEFECTO — el que usan las
41/// apps de genesis (ver `manifiesto::genesis`).
42pub(crate) const TECHO_MEMORIA: usize = 4 * 1024 * 1024;
43
44/// Por que el kernel da por terminada —desaloja— una aplicacion WASM.
45#[derive(Clone, Copy)]
46pub enum FallaApp {
47    /// El modulo no se pudo cargar, validar, enlazar o instanciar.
48    Carga,
49    /// La aplicacion agoto su combustible dentro de un `tick`: bucle infinito
50    /// o trabajo desmedido. El guardarrail TEMPORAL en accion.
51    SinCombustible,
52    /// La aplicacion intento crecer su memoria lineal mas alla de su cuota.
53    /// El guardarrail ESPACIAL en accion.
54    SinMemoria,
55    /// La aplicacion ejecuto una trampa: acceso fuera de limites, instruccion
56    /// `unreachable`, una capacidad violada... su propio codigo la abortó.
57    Trampa,
58}
59
60impl FallaApp {
61    /// El color de la baliza de desalojo segun la causa de la falla: amarillo
62    /// palido si reviento su techo de memoria, purpura para cualquier otra.
63    pub fn color_baliza(self) -> Color {
64        match self {
65            FallaApp::SinMemoria => Color::DESALOJO_MEMORIA,
66            _ => Color::DESALOJO,
67        }
68    }
69}
70
71/// Una aplicacion WebAssembly viva: su estado PERSISTE entre fotogramas. A
72/// diferencia de la Fase 4 —que instanciaba y cedia el control de un gesto—,
73/// aqui la instancia se conserva y el kernel la hace avanzar `tick` a `tick`.
74pub struct AplicacionWasm {
75    /// El almacen: todo el estado de ESTA instancia — su memoria lineal, sus
76    /// globales y el contexto de capacidades con su region de pantalla.
77    almacen: Store<ContextoCapacidades>,
78    /// El punto de entrada de fotograma, ya resuelto y con seguridad de tipos.
79    /// `TypedFunc` es un asa autosuficiente dentro del `Store`: conservada esta,
80    /// el handle de la `Instance` no aporta nada y no se retiene.
81    func_tick: TypedFunc<(), ()>,
82    /// La region de pantalla de la app — su ventana, y donde se tatua su baliza
83    /// de desalojo si llega a fallar.
84    region: RegionPantalla,
85}
86
87impl AplicacionWasm {
88    /// Carga, valida, instancia y arranca una aplicacion WASM aislada, ligada a
89    /// una region de pantalla. Si algo falla en el camino, se devuelve la falla
90    /// en lugar de incendiar el kernel.
91    ///
92    /// El nuevo ABI del userspace exige dos exportaciones: `init` —invocada una
93    /// sola vez, aqui— y `tick` —un fotograma de trabajo, invocada despues por
94    /// el reactor en cada pulso del reloj.
95    ///
96    /// `techo_memoria` es la cuota de memoria lineal de ESTA app, en bytes —
97    /// desde la Fase 7 la dicta su `EntradaApp` del manifiesto.
98    pub fn cargar(
99        bytecode: &[u8],
100        region: RegionPantalla,
101        techo_memoria: usize,
102    ) -> Result<AplicacionWasm, FallaApp> {
103        // 1. El motor, con metricas de combustible y compilacion ANTICIPADA: la
104        //    traduccion del modulo ocurre ahora, de modo que el `fuel` mida
105        //    despues solo EJECUCION, jamas compilacion diferida.
106        let mut config = Config::default();
107        config.consume_fuel(true);
108        config.compilation_mode(CompilationMode::Eager);
109        let motor = Engine::new(&config);
110
111        // 2. Validar y traducir el modulo — ya instrumentado con fuel.
112        let modulo = Module::new(&motor, bytecode).map_err(|_| FallaApp::Carga)?;
113
114        // 3. El almacen, con el contexto de capacidades de ESTA app: su region
115        //    de pantalla, su canal de teclado y su techo de memoria. El canal
116        //    se crea ahora pero se inscribe en la difusion de la IRQ1 al final,
117        //    ya con la app cargada: una carga fallida no deja canales huerfanos.
118        let canal = crate::async_system::teclado::crear_canal();
119        let limites = StoreLimitsBuilder::new()
120            .memory_size(techo_memoria)
121            // Una expansion denegada se convierte en TRAMPA, no en un -1 que la
122            // app pudiera ignorar: asi el kernel la captura y la desaloja.
123            .trap_on_grow_failure(true)
124            .build();
125        let mut almacen = Store::new(
126            &motor,
127            ContextoCapacidades {
128                region,
129                canal,
130                limites,
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            region,
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    /// La region de pantalla asignada a la aplicacion.
198    pub fn region(&self) -> RegionPantalla {
199        self.region
200    }
201}
202
203/// Reconciliacion del ciclo de vida. Cuando una `AplicacionWasm` muere —porque
204/// fue desalojada y su tarea concluyo—, su canal de teclado debe darse de baja
205/// de la difusion de la IRQ1. Sin esto, el manejador de interrupciones seguiria
206/// empujando scancodes a una cola muerta: una fuga lenta pero segura.
207impl Drop for AplicacionWasm {
208    fn drop(&mut self) {
209        crate::async_system::teclado::cerrar_canal(&self.almacen.data().canal);
210    }
211}