blr-lang 0.1.0

A language implementation that provides type safe dataframes
Documentation
//! Execute a blr program that has been compiled to a wasm component.
//!
//! Blr has no host runtime and as such can be executed by any wasm component execution engine.

use std::env::home_dir;

use snafu::{OptionExt as _, ResultExt};
use wasmtime::{
    Cache, CacheConfig, Config, Engine, Store,
    component::{Component, HasSelf, Linker, Val},
};
use wasmtime_wasi::{DirPerms, FilePerms, ResourceTable, WasiCtx, WasiCtxView, WasiView};

pub async fn run_wasm(wasm_bytes: &[u8]) -> Result<Val, snafu::Whatever> {
    let cache = home_dir().map(|home| {
        let mut cache_config = CacheConfig::new();
        cache_config.with_directory(home.join(".blr").join("wasmtime_cache"));
        Cache::new(cache_config).unwrap()
    });

    let mut config = Config::new();
    config
        .wasm_component_model(true)
        .wasm_component_model_async(true)
        .wasm_function_references(true)
        .cache(cache)
        .async_support(true);
    let engine = Engine::new(&config)
        .with_whatever_context(|err| format!("failed to initialize wasm engine: {err}"))?;
    let component = Component::from_binary(&engine, wasm_bytes)
        .with_whatever_context(|err| format!("failed to parse component: {err}"))?;

    let mut builder = WasiCtx::builder();
    builder
        .preopened_dir(".", "/", DirPerms::all(), FilePerms::all())
        .unwrap();
    builder.inherit_stdio();
    let mut store = Store::new(
        &engine,
        State {
            ctx: builder.build(),
            table: ResourceTable::new(),
            df: Default::default(),
            math: Default::default(),
            fmt: Default::default(),
        },
    );
    let mut linker = Linker::<State>::new(&engine);
    add_blr_stdlib_to_linker(&mut linker)
        .with_whatever_context(|err| format!("failed to link blr std lib: {err}"))?;

    wasmtime_wasi::p2::add_to_linker_async(&mut linker).unwrap();
    let instance = linker
        .instantiate_async(&mut store, &component)
        .await
        .with_whatever_context(|err| format!("failed to instantiate component: {err}"))?;

    // Get the exported function
    let main = instance
        .get_func(&mut store, "main")
        .whatever_context("no main function")?;

    let mut results = [Val::Float64(0.0)];
    main.call_async(
        &mut store,
        // Pass _unit_ parameter into main
        &[Val::S64(-1)],
        &mut results,
    )
    .await
    .with_whatever_context(|err| format!("wasm execution failed: {err}"))?;

    Ok(results[0].clone())
}

struct State {
    ctx: WasiCtx,
    table: ResourceTable,

    df: blr_stdlib::df::Host,
    math: blr_stdlib::math::Host,
    fmt: blr_stdlib::fmt::Host,
}

impl WasiView for State {
    fn ctx(&mut self) -> WasiCtxView<'_> {
        WasiCtxView {
            ctx: &mut self.ctx,
            table: &mut self.table,
        }
    }
}

fn add_blr_stdlib_to_linker(linker: &mut Linker<State>) -> Result<(), snafu::Whatever> {
    blr_stdlib::df::Df::add_to_linker::<_, HasSelf<_>>(linker, |state| &mut state.df)
        .with_whatever_context(|err| format!("failed to link std::df: {err}"))?;
    blr_stdlib::math::Math::add_to_linker::<_, HasSelf<_>>(linker, |state| &mut state.math)
        .with_whatever_context(|err| format!("failed to link std::math: {err}"))?;
    blr_stdlib::fmt::Fmt::add_to_linker::<_, HasSelf<_>>(linker, |state| &mut state.fmt)
        .with_whatever_context(|err| format!("failed to link std::fmt: {err}"))?;
    Ok(())
}