-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
A proof of concept to create standalone C library #3
base: main
Are you sure you want to change the base?
Changes from all commits
59c1481
cedbc1b
477bdee
326b37f
2d1a713
e3c66d0
9040a75
dcb463d
6f3b5ec
0944730
62ea06c
ce4e659
b9c2a38
c5e3981
4864ab4
1add678
629bd92
d188e50
4a7b54d
183a3c1
9236862
bd6b3dc
292a4a2
802cd88
67f1534
67e6f9e
7301089
4a0bf3b
592a58e
bb145de
432fff6
2de6e03
ad27e87
fad9323
6ddaf4f
1815454
c5c7aea
0aae4da
24b9d9c
6e335be
72a772d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,43 +1,4 @@ | ||
use { | ||
anyhow::Result, | ||
std::{env, fs, path::PathBuf, process::Command}, | ||
wasmtime::{Config, Engine}, | ||
wit_component::ComponentEncoder, | ||
}; | ||
|
||
fn main() -> Result<()> { | ||
let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); | ||
|
||
assert!(Command::new("cargo") | ||
.current_dir("guest") | ||
.env("CARGO_TARGET_DIR", &out_dir) | ||
.args(["build", "--release", "--target", "wasm32-wasi"]) | ||
.status()? | ||
.success()); | ||
|
||
let adapter = &reqwest::blocking::get( | ||
"https://github.com/bytecodealliance/wasmtime/releases/download/\ | ||
v13.0.0/wasi_snapshot_preview1.command.wasm", | ||
)? | ||
.error_for_status()? | ||
.bytes()?; | ||
|
||
let component = &ComponentEncoder::default() | ||
.module(&fs::read(out_dir.join("wasm32-wasi/release/guest.wasm"))?)? | ||
.validate(true) | ||
.adapter("wasi_snapshot_preview1", adapter)? | ||
.encode()?; | ||
|
||
let mut config = Config::new(); | ||
config.wasm_component_model(true); | ||
|
||
let engine = &Engine::new(&config)?; | ||
|
||
let cwasm = engine.precompile_component(component)?; | ||
|
||
fs::write(out_dir.join("wasm32-wasi/release/guest.cwasm"), cwasm)?; | ||
|
||
println!("cargo:rerun-if-changed=guest"); | ||
|
||
Ok(()) | ||
fn main() { | ||
println!(r"cargo:rustc-link-search=/Users/ondrej/repos/cwasm-standalone/mylib/target/release/"); | ||
println!(r"cargo:rustc-link-lib=mylib"); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
#! /bin/bash | ||
|
||
set -ex | ||
|
||
cd src | ||
lfortran $HOME/repos/lfortran/lfortran/examples/expr2.f90 --backend=wasm -o guest.wasm | ||
wasmtime compile guest.wasm -o guest.cwasm | ||
cd .. | ||
|
||
cd mylib | ||
cargo build --release --offline | ||
cd .. | ||
|
||
strip mylib/target/release/libmylib.a | ||
|
||
cargo run --release --offline | ||
|
||
bin2c guest < src/guest.cwasm > guest.c | ||
clang -c guest.c -o guest.o | ||
clang -c driver.c -o driver.o | ||
clang -o driver driver.o guest.o mylib/target/release/libmylib.a | ||
ls -h driver | ||
strip driver | ||
ls -h driver |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
#include <stdint.h> | ||
#include <stdio.h> | ||
|
||
extern int32_t run_wasm_native_binary(size_t len, uint8_t *guest); | ||
|
||
int main() { | ||
extern const char guest[]; | ||
extern const size_t guest_len; | ||
int32_t result = run_wasm_native_binary(guest_len, (uint8_t*)guest); | ||
return result; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
[package] | ||
name = "mylib" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[dependencies] | ||
anyhow = "1.0.75" | ||
clap = { version = "4.4.5", features = ["derive"] } | ||
wasmtime = { version = "13.0.0", default-features = false } | ||
wasmtime-wasi = "13.0.0" | ||
|
||
[build-dependencies] | ||
anyhow = "1.0.75" | ||
reqwest = { version = "0.11.20", features = ["blocking"] } | ||
wasmtime = { version = "13.0.0" } | ||
|
||
[lib] | ||
crate-type = ["staticlib"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
use { | ||
anyhow::{anyhow, Result}, | ||
clap::Parser, | ||
wasmtime::{Config, Engine, Linker, Module, Store}, | ||
wasmtime_wasi::{Dir, I32Exit, WasiCtx, WasiCtxBuilder}, | ||
}; | ||
|
||
fn parse_mapdir(s: &str) -> Result<(String, String)> { | ||
if let Some((guest_dir, host_dir)) = s.split_once("::") { | ||
Ok((guest_dir.into(), host_dir.into())) | ||
} else { | ||
Err(anyhow!( | ||
"expected string of form GUEST_DIR::HOST_DIR; got {s}" | ||
)) | ||
} | ||
} | ||
|
||
fn parse_env(s: &str) -> Result<(String, String)> { | ||
if let Some((name, value)) = s.split_once('=') { | ||
Ok((name.into(), value.into())) | ||
} else { | ||
Err(anyhow!("expected string of form NAME=VALUE; got {s}")) | ||
} | ||
} | ||
|
||
#[derive(Parser)] | ||
struct Options { | ||
#[clap(long, value_name = "GUEST_DIR::HOST_DIR", value_parser = parse_mapdir)] | ||
mapdir: Vec<(String, String)>, | ||
|
||
#[clap(long, value_name = "NAME=VALUE", value_parser = parse_env)] | ||
env: Vec<(String, String)>, | ||
} | ||
|
||
struct Ctx { | ||
wasi: WasiCtx, | ||
} | ||
|
||
fn ignore_successful_proc_exit_trap(guest_err: anyhow::Error) -> Result<()> { | ||
match guest_err.root_cause().downcast_ref::<I32Exit>() { | ||
Some(trap) => match trap.0 { | ||
0 => Ok(()), | ||
_ => Err(guest_err), | ||
}, | ||
None => Err(guest_err), | ||
} | ||
} | ||
|
||
fn run_internal(guest: &[u8]) -> Result<()> { | ||
let options = Options::parse(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This probably won't do anything useful, since the Rust There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah I see. Actually I do care about that, although I think we have not implemented that yet in our WASM backend. This poses an interesting problem --- in our LLVM backend we allow access to the whole file system. Here we have a nice option to actually restrict it, but it's not clear to me right now what the default should be. |
||
|
||
let engine = &Engine::new(&Config::new())?; | ||
|
||
let mut linker = Linker::<Ctx>::new(engine); | ||
wasmtime_wasi::add_to_linker(&mut linker, |ctx| &mut ctx.wasi)?; | ||
|
||
let mut wasi = WasiCtxBuilder::new(); | ||
wasi.inherit_stdio(); | ||
|
||
for (guest_dir, host_dir) in options.mapdir { | ||
wasi.preopened_dir( | ||
Dir::from_std_file(std::fs::File::open(host_dir)?), | ||
guest_dir, | ||
)?; | ||
} | ||
|
||
for (name, value) in options.env { | ||
wasi.env(&name, &value)?; | ||
} | ||
|
||
let wasi = wasi.build(); | ||
let mut store = Store::new(engine, Ctx { wasi }); | ||
|
||
let instance = linker.instantiate(&mut store, &unsafe { | ||
Module::deserialize( | ||
engine, | ||
guest, | ||
) | ||
}?)?; | ||
|
||
let start = instance | ||
.get_func(&mut store, "_start") | ||
.ok_or_else(|| anyhow!("unable to find `_start` function"))?; | ||
|
||
start | ||
.call(&mut store, &[], &mut []) | ||
.or_else(ignore_successful_proc_exit_trap)?; | ||
|
||
Ok(()) | ||
} | ||
|
||
#[no_mangle] | ||
pub extern "C" fn run_wasm_native_binary(len: usize, guest: *const u8) -> i32 { | ||
let guest_slice: &[u8] = unsafe { | ||
std::slice::from_raw_parts(guest, len) | ||
}; | ||
|
||
match run_internal(guest_slice) { | ||
Ok(_) => 0, | ||
Err(_) => 1, | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,112 +1,18 @@ | ||
use { | ||
anyhow::{anyhow, Result}, | ||
clap::Parser, | ||
wasmtime::{ | ||
component::{Component, Linker}, | ||
Config, Engine, Store, | ||
}, | ||
wasmtime_wasi::{ | ||
preview2::{ | ||
command::{self, sync::Command}, | ||
DirPerms, FilePerms, Table, WasiCtx, WasiCtxBuilder, WasiView, | ||
}, | ||
Dir, | ||
}, | ||
}; | ||
use std::io; | ||
|
||
fn parse_mapdir(s: &str) -> Result<(String, String)> { | ||
if let Some((guest_dir, host_dir)) = s.split_once("::") { | ||
Ok((guest_dir.into(), host_dir.into())) | ||
} else { | ||
Err(anyhow!( | ||
"expected string of form GUEST_DIR::HOST_DIR; got {s}" | ||
)) | ||
} | ||
} | ||
|
||
fn parse_env(s: &str) -> Result<(String, String)> { | ||
if let Some((name, value)) = s.split_once('=') { | ||
Ok((name.into(), value.into())) | ||
} else { | ||
Err(anyhow!("expected string of form NAME=VALUE; got {s}")) | ||
} | ||
} | ||
|
||
#[derive(Parser)] | ||
pub struct Options { | ||
#[clap(long, value_name = "GUEST_DIR::HOST_DIR", value_parser = parse_mapdir)] | ||
mapdir: Vec<(String, String)>, | ||
|
||
#[clap(long, value_name = "NAME=VALUE", value_parser = parse_env)] | ||
env: Vec<(String, String)>, | ||
} | ||
|
||
struct Ctx { | ||
wasi: WasiCtx, | ||
table: Table, | ||
} | ||
|
||
impl WasiView for Ctx { | ||
fn ctx(&self) -> &WasiCtx { | ||
&self.wasi | ||
} | ||
fn ctx_mut(&mut self) -> &mut WasiCtx { | ||
&mut self.wasi | ||
} | ||
fn table(&self) -> &Table { | ||
&self.table | ||
} | ||
fn table_mut(&mut self) -> &mut Table { | ||
&mut self.table | ||
} | ||
extern "C" { | ||
fn run_wasm_native_binary(len: usize, guest: *const u8) -> i32; | ||
} | ||
|
||
fn main() -> Result<()> { | ||
let options = Options::parse(); | ||
|
||
let mut config = Config::new(); | ||
config.wasm_component_model(true); | ||
|
||
let engine = &Engine::new(&config)?; | ||
|
||
let mut linker = Linker::new(engine); | ||
command::sync::add_to_linker(&mut linker)?; | ||
fn main() -> Result<(), std::io::Error> { | ||
let guest: Vec<u8> = include_bytes!("guest.cwasm").to_vec(); | ||
|
||
let mut table = Table::new(); | ||
let mut wasi = WasiCtxBuilder::new(); | ||
wasi.inherit_stdio(); | ||
let result: i32 = unsafe { | ||
run_wasm_native_binary(guest.len(), guest.as_ptr()) | ||
}; | ||
|
||
for (guest_dir, host_dir) in options.mapdir { | ||
wasi.preopened_dir( | ||
Dir::from_std_file(std::fs::File::open(host_dir)?), | ||
DirPerms::all(), | ||
FilePerms::all(), | ||
guest_dir, | ||
); | ||
match result { | ||
0 => Ok(()), | ||
_ => Err(io::ErrorKind::Other.into()), | ||
} | ||
|
||
for (name, value) in options.env { | ||
wasi.env(name, value); | ||
} | ||
|
||
let wasi = wasi.build(&mut table)?; | ||
let mut store = Store::new(engine, Ctx { wasi, table }); | ||
|
||
let (command, _) = Command::instantiate( | ||
&mut store, | ||
&unsafe { | ||
Component::deserialize( | ||
engine, | ||
include_bytes!(concat!(env!("OUT_DIR"), "/wasm32-wasi/release/guest.cwasm")), | ||
) | ||
}?, | ||
&linker, | ||
)?; | ||
|
||
command | ||
.wasi_cli_run() | ||
.call_run(&mut store)? | ||
.map_err(|()| anyhow!("guest command returned error"))?; | ||
|
||
Ok(()) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think
reqwest
is used anymore, so you could remove this.