sim-run-loaders 0.1.1

Feature-composable SIM bootloader loaders.
Documentation
use std::sync::Arc;

use sim_kernel::{
    Args, Callable, Cx, Expr, Linker, LoadCx, Object, ObjectCompat, Result, Symbol, Value,
};

use super::native::NativeGuest;

#[derive(Clone)]
struct NativeAbiSite {
    guest: Arc<dyn NativeGuest>,
    symbol: Symbol,
}

impl NativeAbiSite {
    fn new(guest: Arc<dyn NativeGuest>, symbol: Symbol) -> Self {
        Self { guest, symbol }
    }
}

impl Object for NativeAbiSite {
    fn display(&self, _cx: &mut Cx) -> Result<String> {
        Ok(format!("#<native-site {}>", self.symbol))
    }

    fn as_any(&self) -> &dyn std::any::Any {
        self
    }
}

impl ObjectCompat for NativeAbiSite {
    fn class(&self, cx: &mut Cx) -> Result<sim_kernel::ClassRef> {
        let symbol = Symbol::qualified("core", "Expr");
        if let Some(value) = cx.registry().class_by_symbol(&symbol) {
            return Ok(value.clone());
        }
        cx.factory()
            .class_stub(sim_kernel::CORE_EXPR_CLASS_ID, symbol)
    }

    fn as_expr(&self, _cx: &mut Cx) -> Result<Expr> {
        Ok(Expr::Symbol(self.symbol.clone()))
    }

    fn as_callable(&self) -> Option<&dyn Callable> {
        Some(self)
    }

    fn as_table(&self, cx: &mut Cx) -> Result<sim_kernel::TableRef> {
        cx.factory().table(vec![(
            Symbol::new("symbol"),
            cx.factory().string(self.symbol.to_string())?,
        )])
    }
}

impl Callable for NativeAbiSite {
    fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
        let expr_args = args
            .values()
            .iter()
            .map(|value| value.object().as_expr(cx))
            .collect::<Result<Vec<_>>>()?;
        let arg_bytes = sim_codec_binary::encode_frame(&Expr::List(expr_args))?.0;
        let bytes = self
            .guest
            .invoke(&format!("{}/realize", self.symbol), &arg_bytes)?;
        let (_, expr) = sim_codec_binary::decode_frame(sim_kernel::CodecId(0), &bytes)?;
        cx.factory().expr(expr)
    }
}

pub(super) fn register_native_site(
    cx: &mut LoadCx,
    linker: &mut Linker<'_>,
    guest: Arc<dyn NativeGuest>,
    symbol: Symbol,
) -> Result<()> {
    let site = cx
        .factory()
        .opaque(Arc::new(NativeAbiSite::new(guest, symbol.clone())))?;
    linker.site_value(symbol, site)?;
    Ok(())
}

#[cfg(test)]
mod tests {
    use std::sync::Arc;

    use sim_kernel::{
        AbiVersion, Cx, DefaultFactory, Export, Lib, LibManifest, LibTarget, NoopEvalPolicy,
        Version,
    };

    use super::*;

    struct StubNativeSiteLib {
        manifest: LibManifest,
        guest: Arc<dyn NativeGuest>,
    }

    impl Lib for StubNativeSiteLib {
        fn manifest(&self) -> LibManifest {
            self.manifest.clone()
        }

        fn load(&self, cx: &mut LoadCx, linker: &mut Linker<'_>) -> Result<()> {
            for export in &self.manifest.exports {
                if let Export::Site { symbol, .. } = export {
                    register_native_site(cx, linker, self.guest.clone(), symbol.clone())?;
                }
            }
            Ok(())
        }
    }

    struct MockGuest;

    impl NativeGuest for MockGuest {
        fn invoke(&self, op: &str, args: &[u8]) -> Result<Vec<u8>> {
            assert_eq!(op, "model/local/realize");
            let (_, expr) = sim_codec_binary::decode_frame(sim_kernel::CodecId(0), args)?;
            assert!(matches!(expr, Expr::List(_)));
            Ok(sim_codec_binary::encode_frame(&Expr::Symbol(Symbol::new("ok")))?.0)
        }
    }

    #[test]
    fn native_site_manifest_export_loads_and_registers_site_value() {
        let site_symbol = Symbol::qualified("model", "local");
        let lib = StubNativeSiteLib {
            manifest: LibManifest {
                id: Symbol::new("native-site-test"),
                version: Version("0.1.0".to_owned()),
                abi: AbiVersion { major: 0, minor: 1 },
                target: LibTarget::HostRegistered,
                requires: Vec::new(),
                capabilities: Vec::new(),
                exports: vec![Export::Site {
                    symbol: site_symbol.clone(),
                    runtime_id: None,
                }],
            },
            guest: Arc::new(MockGuest),
        };
        let mut cx = Cx::new(Arc::new(NoopEvalPolicy), Arc::new(DefaultFactory));

        cx.load_lib(&lib).unwrap();

        let site = cx.registry().site_by_symbol(&site_symbol).unwrap().clone();
        assert_eq!(
            site.object().as_expr(&mut cx).unwrap(),
            Expr::Symbol(site_symbol)
        );
        let result = site
            .object()
            .as_callable()
            .unwrap()
            .call(&mut cx, Args::default())
            .unwrap();
        assert_eq!(
            result.object().as_expr(&mut cx).unwrap(),
            Expr::Symbol(Symbol::new("ok"))
        );
    }
}