feat: integra renaser (kernel SASOS bare-metal) al monorepo

renaser —kernel asíncrono de espacio de direcciones único, no-POSIX,
`no_std` x86_64— entra al monorepo como su PROPIO workspace de Cargo,
no fusionado: usa toolchain nightly, target `x86_64-unknown-none` y
`panic = "abort"`, incompatibles con los perfiles globales de brahman.

- `renaser/` — copia del proyecto (sin su `.git`; el repo original
  conserva su historia standalone). Workspace propio con su
  `rust-toolchain.toml` y `.cargo/`.
- `exclude = ["renaser"]` en el workspace de brahman: Cargo lo trata
  como ajeno.
- El kernel de renaser path-depende `mirada-layout` cruzando la
  frontera de workspace — primer núcleo compartido. Semilla de la
  Fase 8 (compositor): geometría de teselado compartida, framebuffer
  nativo de renaser; smithay se queda en el lado Linux.

Verificado: `cargo build -p boot` compila kernel + imagen UEFI con
mirada-layout enlazado para bare-metal.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-22 14:37:14 +00:00
parent 1c6aafbc24
commit e2272c0ed3
55 changed files with 6668 additions and 0 deletions
+349
View File
@@ -0,0 +1,349 @@
// =============================================================================
// renaser :: kernel/src/wasm/env.rs — Fase 4/5/6 :: la matriz de capacidades
// -----------------------------------------------------------------------------
// El aislamiento de renaser no descansa en `int 0x80` ni en `sysenter`: no hay
// vectores de syscall. Una aplicacion WASM solo puede hacer aquello para lo
// que el kernel le haya inyectado una FUNCION DEL HOST. Esta matriz concede:
//
// * sys_render_frame — componer un fotograma en su region de pantalla;
// * sys_get_scancode — consultar su canal de teclado;
// * sys_object_put — grabar un objeto en el grafo (Fase 6.1c);
// * sys_object_datos — leer la carga util de un objeto;
// * sys_object_hijo — recorrer las aristas del DAG;
// * sys_object_raiz — leer la raiz del grafo;
// * sys_object_fijar_raiz — coronar un objeto como raiz.
//
// GUARDARRAIL: el kernel valida MATEMATICAMENTE todo puntero que el modulo le
// entrega contra los limites reales de su memoria lineal. No se confia en que
// el runtime lo haga; se verifica aqui, antes de leer o escribir un solo byte.
//
// DOS CLASES DE FALLO. Un puntero fuera de limites es CULPA DE LA APP: se
// devuelve un `Error` que la ABORTA (el kernel la captura y la desaloja). Un
// fallo del almacenamiento —disco, objeto inexistente— NO es culpa de la app:
// se le devuelve un codigo de error negativo, y la app decide que hacer.
// =============================================================================
use wasmi::{Caller, Error, Extern, Linker, Memory, StoreLimits};
use crate::almacen::Hash;
use crate::async_system::teclado::CanalTeclado;
use crate::grafico::RegionPantalla;
/// El estado del host adscrito al `Store` de una aplicacion: cuanto necesita
/// una capacidad para servir a ESA app y a ninguna otra — su region de pantalla,
/// su canal de teclado y sus cuotas de recursos. Dos apps jamas comparten nada.
pub(crate) struct ContextoCapacidades {
/// La sub-region de pantalla asignada a la aplicacion.
pub(crate) region: RegionPantalla,
/// El canal de teclado propio de la aplicacion.
pub(crate) canal: CanalTeclado,
/// El techo de recursos de la aplicacion — hoy, su memoria lineal maxima.
/// `wasmi` lo consulta en cada `memory.grow` via `Store::limiter`.
pub(crate) limites: StoreLimits,
}
/// Recupera la memoria lineal exportada por el modulo. Que no la exporte es un
/// modulo mal formado: se aborta.
fn obtener_memoria(caller: &Caller<'_, ContextoCapacidades>) -> Result<Memory, Error> {
caller
.get_export("memory")
.and_then(Extern::into_memory)
.ok_or_else(|| Error::new("WASM :: el modulo no exporta su memoria lineal"))
}
/// VALIDACION INFRANQUEABLE DE LIMITES. Comprueba que `[ptr, ptr + len)` cae
/// entera dentro de la memoria lineal `m` y devuelve ese sub-slice. Un rango
/// que se desborde aborta la app — el `Error` se traduce en una trampa de WASM.
fn rango<'a>(m: &'a [u8], ptr: u32, len: usize, fallo: &'static str) -> Result<&'a [u8], Error> {
let inicio = ptr as usize;
match inicio.checked_add(len) {
Some(fin) if fin <= m.len() => Ok(&m[inicio..fin]),
_ => Err(Error::new(fallo)),
}
}
/// Lee un hash de 32 bytes de la memoria lineal, con sus limites verificados.
fn leer_hash(m: &[u8], ptr: u32, fallo: &'static str) -> Result<Hash, Error> {
let bytes = rango(m, ptr, 32, fallo)?;
let mut hash = [0u8; 32];
hash.copy_from_slice(bytes);
Ok(hash)
}
/// Inyecta en el enlazador la matriz de capacidades del modulo WASM. Todo lo
/// que no se defina aqui le queda, al modulo, fisicamente fuera de alcance.
///
/// Devuelve `Err` si una capacidad no se pudo enlazar — un fallo del kernel,
/// no de la app; aun asi se propaga como `Result` para no incendiar nada.
pub(crate) fn enlazar_capacidades(
enlazador: &mut Linker<ContextoCapacidades>,
) -> Result<(), Error> {
// --- CAPACIDAD 1 :: sys_render_frame(ptr, len) ---
// El modulo entrega (ptr, len) hacia su PROPIA memoria lineal; el kernel
// valida esos limites y, solo entonces, compone el fotograma DENTRO de la
// region asignada a la app.
enlazador.func_wrap(
"renaser",
"sys_render_frame",
|caller: Caller<'_, ContextoCapacidades>, ptr: u32, len: u32| -> Result<(), Error> {
let region = caller.data().region;
// El fotograma debe medir EXACTAMENTE los pixeles de la region. Un
// tamaño distinto delata a una app que pinta fuera de su ventana:
// se aborta antes de tocar un byte.
let esperado = region.pixeles() * 4;
if len as usize != esperado {
return Err(Error::new(
"WASM :: sys_render_frame con un fotograma ajeno a la region asignada",
));
}
let memoria = obtener_memoria(&caller)?;
let datos: &[u8] = memoria.data(&caller);
// VALIDACION INFRANQUEABLE: si (ptr, len) se sale de la memoria
// lineal del modulo, se aborta la app —no el kernel—.
let marco = rango(
datos,
ptr,
len as usize,
"WASM :: sys_render_frame desbordo la memoria lineal del modulo",
)?;
// Limites verificados: la region es segura de leer y componer.
crate::volcar_marco_wasm(region, marco);
Ok(())
},
)?;
// --- CAPACIDAD 2 :: sys_get_scancode() -> u32 ---
// Expone, sin bloquear, el siguiente scancode del canal PROPIO de la app.
enlazador.func_wrap(
"renaser",
"sys_get_scancode",
|caller: Caller<'_, ContextoCapacidades>| -> u32 {
caller.data().canal.pop().unwrap_or(0) as u32
},
)?;
// --- CAPACIDAD 3 :: sys_object_put(datos, datos_len, hijos, hijos_cnt, salida) -> i32 ---
// Graba un objeto en el grafo. El modulo entrega, en su memoria lineal, la
// carga util y un arreglo de `hijos_cnt` hashes de 32 bytes (las aristas).
// El kernel escribe el hash resultante —la identidad del objeto— en
// `salida`. Devuelve 0 si el objeto se grabo (o ya existia), -1 si el
// almacenamiento fallo.
enlazador.func_wrap(
"renaser",
"sys_object_put",
|mut caller: Caller<'_, ContextoCapacidades>,
datos_ptr: u32,
datos_len: u32,
hijos_ptr: u32,
hijos_cnt: u32,
salida: u32|
-> Result<i32, Error> {
let memoria = obtener_memoria(&caller)?;
// --- Leer las entradas de la memoria lineal, con limites firmes. ---
let (datos, hijos) = {
let m = memoria.data(&caller);
let datos = rango(
m,
datos_ptr,
datos_len as usize,
"WASM :: sys_object_put desbordo la memoria lineal (datos)",
)?
.to_vec();
// El arreglo de hijos: `hijos_cnt` hashes contiguos de 32 bytes.
let bytes_hijos = (hijos_cnt as usize).checked_mul(32).ok_or_else(|| {
Error::new("WASM :: sys_object_put con un conteo de hijos imposible")
})?;
let crudo = rango(
m,
hijos_ptr,
bytes_hijos,
"WASM :: sys_object_put desbordo la memoria lineal (hijos)",
)?;
let mut hijos: alloc::vec::Vec<Hash> =
alloc::vec::Vec::with_capacity(hijos_cnt as usize);
for trozo in crudo.chunks_exact(32) {
let mut h = [0u8; 32];
h.copy_from_slice(trozo);
hijos.push(h);
}
// Verificar que el hash de salida cabe ANTES de tocar el disco.
rango(
m,
salida,
32,
"WASM :: sys_object_put desbordo la memoria lineal (salida)",
)?;
(datos, hijos)
};
// --- Grabar. Un fallo del almacen NO es culpa de la app: -1. ---
match crate::almacen::almacenar(datos, hijos) {
Ok(hash) => {
let m = memoria.data_mut(&mut caller);
m[salida as usize..salida as usize + 32].copy_from_slice(&hash);
Ok(0)
}
Err(_) => Ok(-1),
}
},
)?;
// --- CAPACIDAD 4 :: sys_object_datos(hash, salida, capacidad) -> i32 ---
// Copia la carga util del objeto `hash` en `salida`. Devuelve el numero de
// bytes copiados, o -1 si el objeto no existe, -2 si `capacidad` no basta,
// -3 si el almacenamiento fallo.
enlazador.func_wrap(
"renaser",
"sys_object_datos",
|mut caller: Caller<'_, ContextoCapacidades>,
hash_ptr: u32,
salida: u32,
capacidad: u32|
-> Result<i32, Error> {
let memoria = obtener_memoria(&caller)?;
let hash = {
let m = memoria.data(&caller);
leer_hash(
m,
hash_ptr,
"WASM :: sys_object_datos desbordo la memoria lineal (hash)",
)?
};
let objeto = match crate::almacen::recuperar(&hash) {
Ok(Some(objeto)) => objeto,
Ok(None) => return Ok(-1),
Err(_) => return Ok(-3),
};
if objeto.datos.len() > capacidad as usize {
return Ok(-2);
}
// Verificar que el destino cabe, y solo entonces copiar.
{
let m = memoria.data(&caller);
rango(
m,
salida,
objeto.datos.len(),
"WASM :: sys_object_datos desbordo la memoria lineal (salida)",
)?;
}
let n = objeto.datos.len();
let m = memoria.data_mut(&mut caller);
m[salida as usize..salida as usize + n].copy_from_slice(&objeto.datos);
Ok(n as i32)
},
)?;
// --- CAPACIDAD 5 :: sys_object_hijo(hash, indice, salida) -> i32 ---
// Recorre las aristas del DAG. Devuelve el NUMERO de hijos del objeto
// `hash`; si `indice` es valido, ademas escribe el hash de ese hijo en
// `salida`. Devuelve -1 si el objeto no existe, -3 si el almacen fallo.
enlazador.func_wrap(
"renaser",
"sys_object_hijo",
|mut caller: Caller<'_, ContextoCapacidades>,
hash_ptr: u32,
indice: u32,
salida: u32|
-> Result<i32, Error> {
let memoria = obtener_memoria(&caller)?;
let hash = {
let m = memoria.data(&caller);
leer_hash(
m,
hash_ptr,
"WASM :: sys_object_hijo desbordo la memoria lineal (hash)",
)?
};
let objeto = match crate::almacen::recuperar(&hash) {
Ok(Some(objeto)) => objeto,
Ok(None) => return Ok(-1),
Err(_) => return Ok(-3),
};
let total = objeto.hijos.len();
// Si el indice apunta a un hijo real, entregar su hash.
if let Some(hijo) = objeto.hijos.get(indice as usize) {
{
let m = memoria.data(&caller);
rango(
m,
salida,
32,
"WASM :: sys_object_hijo desbordo la memoria lineal (salida)",
)?;
}
let m = memoria.data_mut(&mut caller);
m[salida as usize..salida as usize + 32].copy_from_slice(hijo);
}
Ok(total as i32)
},
)?;
// --- CAPACIDAD 6 :: sys_object_raiz(salida) -> i32 ---
// Escribe en `salida` el hash de la raiz del grafo. Devuelve 1 si hay
// raiz, 0 si el grafo aun no tiene ninguna.
enlazador.func_wrap(
"renaser",
"sys_object_raiz",
|mut caller: Caller<'_, ContextoCapacidades>, salida: u32| -> Result<i32, Error> {
let memoria = obtener_memoria(&caller)?;
match crate::almacen::raiz() {
Some(hash) => {
{
let m = memoria.data(&caller);
rango(
m,
salida,
32,
"WASM :: sys_object_raiz desbordo la memoria lineal (salida)",
)?;
}
let m = memoria.data_mut(&mut caller);
m[salida as usize..salida as usize + 32].copy_from_slice(&hash);
Ok(1)
}
None => Ok(0),
}
},
)?;
// --- CAPACIDAD 7 :: sys_object_fijar_raiz(hash) -> i32 ---
// Corona el objeto `hash` como raiz del grafo. Devuelve 0 si se logro, -3
// si el almacenamiento fallo.
enlazador.func_wrap(
"renaser",
"sys_object_fijar_raiz",
|caller: Caller<'_, ContextoCapacidades>, hash_ptr: u32| -> Result<i32, Error> {
let memoria = obtener_memoria(&caller)?;
let hash = {
let m = memoria.data(&caller);
leer_hash(
m,
hash_ptr,
"WASM :: sys_object_fijar_raiz desbordo la memoria lineal (hash)",
)?
};
match crate::almacen::fijar_raiz(hash) {
Ok(()) => Ok(0),
Err(_) => Ok(-3),
}
},
)?;
Ok(())
}
+200
View File
@@ -0,0 +1,200 @@
// =============================================================================
// renaser :: kernel/src/wasm — Fase 4/5 :: el escudo de aislamiento (WASM)
// -----------------------------------------------------------------------------
// Aqui renaser sustituye las costosas fronteras de hardware (la MMU, los
// anillos de la CPU) por limites MATEMATICOS sobre el bytecode. Una aplicacion
// WebAssembly se ejecuta en su propia memoria lineal; sus unicas puertas al
// exterior son las capacidades que el enlazador del host le concede. Lo que
// no este importado no existe: no hay camino fisico para ejecutarlo.
//
// FASE 5 :: el aislamiento deja de ser solo ESPACIAL (memoria) y pasa a ser
// tambien TEMPORAL (tiempo de CPU). Cada `tick` se ejecuta con un presupuesto
// estricto de COMBUSTIBLE (fuel): si una app lo agota —un bucle infinito, un
// trabajo desmedido—, el runtime lanza una trampa, el kernel recupera el mando
// y la desaloja. Ningun modulo, por discolo que sea, secuestra el procesador.
// =============================================================================
pub mod env;
use wasmi::{
CompilationMode, Config, Engine, Linker, Module, Store, StoreLimitsBuilder, TrapCode, TypedFunc,
};
use crate::grafico::{Color, RegionPantalla};
use env::ContextoCapacidades;
/// Combustible concedido a `init`. Cubre con holgura el pintado inicial del
/// fondo de una region a pantalla casi completa — un gasto unico, de arranque.
const FUEL_ARRANQUE: u64 = 20_000_000;
/// Combustible concedido a cada `tick`. Sobra para un fotograma honesto (unos
/// cientos de miles de operaciones); una app en bucle infinito lo agota en
/// milisegundos y es desalojada. Este numero ES el techo temporal del userspace.
const FUEL_FOTOGRAMA: u64 = 2_000_000;
/// Techo de memoria lineal por aplicacion: 4 MiB. Un modulo que intente crecer
/// su memoria mas alla es desalojado — el aislamiento ESPACIAL del userspace,
/// gemelo del techo TEMPORAL que impone el combustible.
const TECHO_MEMORIA: usize = 4 * 1024 * 1024;
/// Por que el kernel da por terminada —desaloja— una aplicacion WASM.
#[derive(Clone, Copy)]
pub enum FallaApp {
/// El modulo no se pudo cargar, validar, enlazar o instanciar.
Carga,
/// La aplicacion agoto su combustible dentro de un `tick`: bucle infinito
/// o trabajo desmedido. El guardarrail TEMPORAL en accion.
SinCombustible,
/// La aplicacion intento crecer su memoria lineal mas alla de su cuota.
/// El guardarrail ESPACIAL en accion.
SinMemoria,
/// La aplicacion ejecuto una trampa: acceso fuera de limites, instruccion
/// `unreachable`, una capacidad violada... su propio codigo la abortó.
Trampa,
}
impl FallaApp {
/// El color de la baliza de desalojo segun la causa de la falla: amarillo
/// palido si reviento su techo de memoria, purpura para cualquier otra.
pub fn color_baliza(self) -> Color {
match self {
FallaApp::SinMemoria => Color::DESALOJO_MEMORIA,
_ => Color::DESALOJO,
}
}
}
/// Una aplicacion WebAssembly viva: su estado PERSISTE entre fotogramas. A
/// diferencia de la Fase 4 —que instanciaba y cedia el control de un gesto—,
/// aqui la instancia se conserva y el kernel la hace avanzar `tick` a `tick`.
pub struct AplicacionWasm {
/// El almacen: todo el estado de ESTA instancia — su memoria lineal, sus
/// globales y el contexto de capacidades con su region de pantalla.
almacen: Store<ContextoCapacidades>,
/// El punto de entrada de fotograma, ya resuelto y con seguridad de tipos.
/// `TypedFunc` es un asa autosuficiente dentro del `Store`: conservada esta,
/// el handle de la `Instance` no aporta nada y no se retiene.
func_tick: TypedFunc<(), ()>,
/// La region de pantalla de la app — su ventana, y donde se tatua su baliza
/// de desalojo si llega a fallar.
region: RegionPantalla,
}
impl AplicacionWasm {
/// Carga, valida, instancia y arranca una aplicacion WASM aislada, ligada a
/// una region de pantalla. Si algo falla en el camino, se devuelve la falla
/// en lugar de incendiar el kernel.
///
/// El nuevo ABI del userspace exige dos exportaciones: `init` —invocada una
/// sola vez, aqui— y `tick` —un fotograma de trabajo, invocada despues por
/// el reactor en cada pulso del reloj.
pub fn cargar(bytecode: &[u8], region: RegionPantalla) -> Result<AplicacionWasm, FallaApp> {
// 1. El motor, con metricas de combustible y compilacion ANTICIPADA: la
// traduccion del modulo ocurre ahora, de modo que el `fuel` mida
// despues solo EJECUCION, jamas compilacion diferida.
let mut config = Config::default();
config.consume_fuel(true);
config.compilation_mode(CompilationMode::Eager);
let motor = Engine::new(&config);
// 2. Validar y traducir el modulo — ya instrumentado con fuel.
let modulo = Module::new(&motor, bytecode).map_err(|_| FallaApp::Carga)?;
// 3. El almacen, con el contexto de capacidades de ESTA app: su region
// de pantalla, su canal de teclado y su techo de memoria. El canal
// se crea ahora pero se inscribe en la difusion de la IRQ1 al final,
// ya con la app cargada: una carga fallida no deja canales huerfanos.
let canal = crate::async_system::teclado::crear_canal();
let limites = StoreLimitsBuilder::new()
.memory_size(TECHO_MEMORIA)
// Una expansion denegada se convierte en TRAMPA, no en un -1 que la
// app pudiera ignorar: asi el kernel la captura y la desaloja.
.trap_on_grow_failure(true)
.build();
let mut almacen = Store::new(
&motor,
ContextoCapacidades {
region,
canal,
limites,
},
);
// Ligar el limitador de recursos: `wasmi` lo consultara en cada
// `memory.grow`, tambien durante la instanciacion.
almacen.limiter(|contexto| &mut contexto.limites);
// Dotar de combustible ANTES de instanciar: la instanciacion no debe
// quedarse a cero y abortar.
almacen.set_fuel(FUEL_ARRANQUE).map_err(|_| FallaApp::Carga)?;
// 4. El enlazador y la matriz de capacidades (ver `env`).
let mut enlazador: Linker<ContextoCapacidades> = Linker::new(&motor);
env::enlazar_capacidades(&mut enlazador).map_err(|_| FallaApp::Carga)?;
// 5. Instanciar, resolviendo las importaciones contra las capacidades.
let instancia = enlazador
.instantiate_and_start(&mut almacen, &modulo)
.map_err(|_| FallaApp::Carga)?;
// 6. Resolver los dos puntos del ABI de fotograma: `init` y `tick`.
let func_init = instancia
.get_typed_func::<(), ()>(&almacen, "init")
.map_err(|_| FallaApp::Carga)?;
let func_tick = instancia
.get_typed_func::<(), ()>(&almacen, "tick")
.map_err(|_| FallaApp::Carga)?;
// 7. Arranque unico: `init` prepara el estado inicial de la aplicacion.
almacen.set_fuel(FUEL_ARRANQUE).map_err(|_| FallaApp::Carga)?;
func_init
.call(&mut almacen, ())
.map_err(|_| FallaApp::Carga)?;
// 8. Con la app ya cargada e instanciada, inscribir su canal de teclado
// en la difusion de la IRQ1: desde aqui recibe cada pulsacion.
crate::async_system::teclado::registrar_canal(&almacen.data().canal);
Ok(AplicacionWasm {
almacen,
func_tick,
region,
})
}
/// Hace avanzar la aplicacion un fotograma. Recarga su presupuesto de
/// combustible y le cede el control con `tick`. Si la app lo agota o ejecuta
/// una trampa, el kernel recupera el mando y la falla se devuelve para que
/// la tarea proceda al desalojo. El kernel nunca pierde el control.
pub fn tick(&mut self) -> Result<(), FallaApp> {
// Recargar el deposito: cada fotograma parte con su techo intacto.
self.almacen
.set_fuel(FUEL_FOTOGRAMA)
.map_err(|_| FallaApp::Trampa)?;
match self.func_tick.call(&mut self.almacen, ()) {
Ok(()) => Ok(()),
// `as_trap_code` da un codigo publico y univoco para cada causa:
// `OutOfFuel` pliega toda variante de agotamiento de combustible;
// `GrowthOperationLimited` es la cuota de memoria denegada.
Err(error) => match error.as_trap_code() {
Some(TrapCode::OutOfFuel) => Err(FallaApp::SinCombustible),
Some(TrapCode::GrowthOperationLimited) => Err(FallaApp::SinMemoria),
_ => Err(FallaApp::Trampa),
},
}
}
/// La region de pantalla asignada a la aplicacion.
pub fn region(&self) -> RegionPantalla {
self.region
}
}
/// Reconciliacion del ciclo de vida. Cuando una `AplicacionWasm` muere —porque
/// fue desalojada y su tarea concluyo—, su canal de teclado debe darse de baja
/// de la difusion de la IRQ1. Sin esto, el manejador de interrupciones seguiria
/// empujando scancodes a una cola muerta: una fuga lenta pero segura.
impl Drop for AplicacionWasm {
fn drop(&mut self) {
crate::async_system::teclado::cerrar_canal(&self.almacen.data().canal);
}
}