-
Notifications
You must be signed in to change notification settings - Fork 339
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
It's far from working, but it already properly calls back from a WebAssembly instance to the host for VFS operations. We also want the same for virtual WAL and we'll have a solid foundation for running libSQL in a Wasm runtime.
- Loading branch information
Showing
4 changed files
with
412 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
[package] | ||
name = "libsql-wasi-demo" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
anyhow = "1.0.75" | ||
wasmtime = "15.0.0" | ||
wasmtime-wasi = "15.0.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,330 @@ | ||
mod memory; | ||
|
||
use anyhow::Context; | ||
use wasmtime::{Caller, Engine, Linker, Memory, Module, Store}; | ||
use wasmtime_wasi::{WasiCtx, WasiCtxBuilder}; | ||
|
||
// FIXME: add any state we need to pass - WasiCtx is here to get free println! and stuff | ||
type State = WasiCtx; | ||
|
||
fn get_memory(caller: &mut Caller<'_, State>) -> Memory { | ||
caller.get_export("memory").unwrap().into_memory().unwrap() | ||
} | ||
|
||
fn host_open_fd( | ||
mut caller: Caller<'_, State>, | ||
name: i32, | ||
flags: i32, | ||
) -> anyhow::Result<i64> { | ||
let memory = get_memory(&mut caller); | ||
let (memory, state) = memory.data_and_store_mut(&mut caller); | ||
|
||
let name = memory::read_cstr(memory, name)?; | ||
|
||
println!("HOST OPEN_FD CALLED: {name:?} {flags:0o}"); | ||
|
||
let file = std::fs::OpenOptions::new() | ||
.read(true) | ||
.write(true) | ||
.create(true) | ||
.open(name)?; | ||
let file = Box::new(file); | ||
|
||
Ok(Box::into_raw(file) as i64) | ||
} | ||
|
||
fn host_delete( | ||
mut caller: Caller<'_, State>, | ||
vfs: i32, | ||
name: i32, | ||
sync_dir: i32, | ||
) -> anyhow::Result<i32> { | ||
let memory = get_memory(&mut caller); | ||
let (memory, state) = memory.data_and_store_mut(&mut caller); | ||
|
||
println!("HOST DELETE CALLED"); | ||
Ok(0) | ||
} | ||
|
||
fn host_access( | ||
mut caller: Caller<'_, State>, | ||
vfs: i32, | ||
name: i32, | ||
flags: i32, | ||
res_out: i32, | ||
) -> anyhow::Result<i32> { | ||
let memory = get_memory(&mut caller); | ||
let (memory, state) = memory.data_and_store_mut(&mut caller); | ||
|
||
println!("HOST ACCESS CALLED"); | ||
Ok(0) | ||
} | ||
|
||
fn host_full_pathname( | ||
mut caller: Caller<'_, State>, | ||
vfs: i32, | ||
name: i32, | ||
n_out: i32, | ||
out: i32, | ||
) -> anyhow::Result<i32> { | ||
let memory = get_memory(&mut caller); | ||
let (memory, state) = memory.data_and_store_mut(&mut caller); | ||
|
||
let name = memory::read_cstr(memory, name)?; | ||
let out = memory::slice_mut(memory, out, n_out as usize)?; | ||
|
||
out[..name.len()].copy_from_slice(name.as_bytes()); | ||
Ok(0) | ||
} | ||
|
||
fn host_randomness( | ||
mut caller: Caller<'_, State>, | ||
vfs: i32, | ||
n_byte: i32, | ||
out: i32, | ||
) -> anyhow::Result<i32> { | ||
let memory = get_memory(&mut caller); | ||
let (memory, state) = memory.data_and_store_mut(&mut caller); | ||
|
||
println!("HOST RANDOMNESS CALLED"); | ||
Ok(0) | ||
} | ||
|
||
fn host_sleep(mut caller: Caller<'_, State>, vfs: i32, microseconds: i32) -> anyhow::Result<i32> { | ||
let memory = get_memory(&mut caller); | ||
let (memory, state) = memory.data_and_store_mut(&mut caller); | ||
|
||
println!("HOST SLEEP CALLED"); | ||
Ok(0) | ||
} | ||
|
||
fn host_current_time(mut caller: Caller<'_, State>, vfs: i32, out: i32) -> anyhow::Result<i32> { | ||
let memory = get_memory(&mut caller); | ||
let (memory, state) = memory.data_and_store_mut(&mut caller); | ||
|
||
println!("HOST CURRENT TIME CALLED"); | ||
Ok(0) | ||
} | ||
|
||
fn host_get_last_error( | ||
mut caller: Caller<'_, State>, | ||
vfs: i32, | ||
i: i32, | ||
out: i32, | ||
) -> anyhow::Result<i32> { | ||
let memory = get_memory(&mut caller); | ||
let (memory, state) = memory.data_and_store_mut(&mut caller); | ||
|
||
println!("HOST GET LAST ERROR CALLED"); | ||
Ok(0) | ||
} | ||
|
||
fn host_current_time_64(mut caller: Caller<'_, State>, vfs: i32, out: i32) -> anyhow::Result<i32> { | ||
let memory = get_memory(&mut caller); | ||
let (memory, state) = memory.data_and_store_mut(&mut caller); | ||
|
||
println!("HOST CURRENT TIME 64 CALLED"); | ||
Ok(0) | ||
} | ||
|
||
/* | ||
Based on C: | ||
LIBSQL_IMPORT("close") int libsql_wasi_close(sqlite3_file*); | ||
LIBSQL_IMPORT("read") int libsql_wasi_read(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); | ||
LIBSQL_IMPORT("write") int libsql_wasi_write(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst); | ||
LIBSQL_IMPORT("truncate") int libsql_wasi_truncate(sqlite3_file*, sqlite3_int64 size); | ||
LIBSQL_IMPORT("sync") int libsql_wasi_sync(sqlite3_file*, int flags); | ||
LIBSQL_IMPORT("file_size") int libsql_wasi_file_size(sqlite3_file*, sqlite3_int64 *pSize); | ||
LIBSQL_IMPORT("lock") int libsql_wasi_lock(sqlite3_file*, int); | ||
LIBSQL_IMPORT("unlock") int libsql_wasi_unlock(sqlite3_file*, int); | ||
LIBSQL_IMPORT("check_reserved_lock") int libsql_wasi_check_reserved_lock(sqlite3_file*, int *pResOut); | ||
LIBSQL_IMPORT("file_control") int libslq_wasi_file_control(sqlite3_file*, int op, void *pArg); | ||
LIBSQL_IMPORT("sector_size") int libsql_wasi_sector_size(sqlite3_file*); | ||
LIBSQL_IMPORT("device_characteristics") int libsql_wasi_device_characteristics(sqlite3_file*); | ||
*/ | ||
|
||
fn host_close(mut caller: Caller<'_, State>, file: i32) -> anyhow::Result<i32> { | ||
let memory = get_memory(&mut caller); | ||
let (memory, state) = memory.data_and_store_mut(&mut caller); | ||
// TODO: read the file pointer from guest memory and feed it to Box::from_raw | ||
println!("HOST CLOSE CALLED"); | ||
|
||
Ok(0) | ||
} | ||
|
||
fn host_read( | ||
mut caller: Caller<'_, State>, | ||
file: i32, | ||
buf: i32, | ||
amt: i32, | ||
offset: i64, | ||
) -> anyhow::Result<i32> { | ||
let memory = get_memory(&mut caller); | ||
let (memory, state) = memory.data_and_store_mut(&mut caller); | ||
|
||
println!("HOST READ CALLED"); | ||
Ok(0) | ||
} | ||
|
||
fn host_write( | ||
mut caller: Caller<'_, State>, | ||
file: i32, | ||
buf: i32, | ||
amt: i32, | ||
offset: i64, | ||
) -> anyhow::Result<i32> { | ||
let memory = get_memory(&mut caller); | ||
let (memory, state) = memory.data_and_store_mut(&mut caller); | ||
|
||
println!("HOST WRITE CALLED"); | ||
Ok(0) | ||
} | ||
|
||
fn host_truncate(mut caller: Caller<'_, State>, file: i32, size: i64) -> anyhow::Result<i32> { | ||
let memory = get_memory(&mut caller); | ||
let (memory, state) = memory.data_and_store_mut(&mut caller); | ||
println!("HOST TRUNCATE CALLED"); | ||
Ok(0) | ||
} | ||
|
||
fn host_sync(mut caller: Caller<'_, State>, file: i32, flags: i32) -> anyhow::Result<i32> { | ||
let memory = get_memory(&mut caller); | ||
let (memory, state) = memory.data_and_store_mut(&mut caller); | ||
println!("HOST SYNC CALLED"); | ||
Ok(0) | ||
} | ||
|
||
fn host_file_size(mut caller: Caller<'_, State>, file: i32, size: i32) -> anyhow::Result<i32> { | ||
let memory = get_memory(&mut caller); | ||
let (memory, state) = memory.data_and_store_mut(&mut caller); | ||
println!("HOST FILE SIZE CALLED"); | ||
Ok(0) | ||
} | ||
|
||
fn host_lock(mut caller: Caller<'_, State>, file: i32, lock: i32) -> anyhow::Result<i32> { | ||
let memory = get_memory(&mut caller); | ||
let (memory, state) = memory.data_and_store_mut(&mut caller); | ||
println!("HOST LOCK CALLED"); | ||
Ok(0) | ||
} | ||
|
||
fn host_unlock(mut caller: Caller<'_, State>, file: i32, lock: i32) -> anyhow::Result<i32> { | ||
let memory = get_memory(&mut caller); | ||
let (memory, state) = memory.data_and_store_mut(&mut caller); | ||
println!("HOST UNLOCK CALLED"); | ||
Ok(0) | ||
} | ||
|
||
fn host_check_reserved_lock( | ||
mut caller: Caller<'_, State>, | ||
file: i32, | ||
reserved_lock: i32, | ||
) -> anyhow::Result<i32> { | ||
let memory = get_memory(&mut caller); | ||
let (memory, state) = memory.data_and_store_mut(&mut caller); | ||
println!("HOST CHECK RESERVED LOCK CALLED"); | ||
Ok(0) | ||
} | ||
|
||
fn host_file_control( | ||
mut caller: Caller<'_, State>, | ||
file: i32, | ||
op: i32, | ||
arg: i32, | ||
) -> anyhow::Result<i32> { | ||
let memory = get_memory(&mut caller); | ||
let (memory, state) = memory.data_and_store_mut(&mut caller); | ||
|
||
println!("HOST FILE CONTROL CALLED"); | ||
Ok(0) | ||
} | ||
|
||
fn host_sector_size(mut caller: Caller<'_, State>, file: i32) -> anyhow::Result<i32> { | ||
let memory = get_memory(&mut caller); | ||
let (memory, state) = memory.data_and_store_mut(&mut caller); | ||
|
||
println!("HOST SECTOR SIZE CALLED"); | ||
Ok(0) | ||
} | ||
|
||
fn host_device_characteristics(mut caller: Caller<'_, State>, file: i32) -> anyhow::Result<i32> { | ||
let memory = get_memory(&mut caller); | ||
let (memory, state) = memory.data_and_store_mut(&mut caller); | ||
|
||
println!("HOST DEVICE CHARACTERISTICS CALLED"); | ||
Ok(0) | ||
} | ||
|
||
fn link_host_functions(linker: &mut Linker<State>) -> anyhow::Result<()> { | ||
linker.func_wrap("libsql_host", "open_fd", host_open_fd)?; | ||
linker.func_wrap("libsql_host", "delete", host_delete)?; | ||
linker.func_wrap("libsql_host", "access", host_access)?; | ||
linker.func_wrap("libsql_host", "full_pathname", host_full_pathname)?; | ||
linker.func_wrap("libsql_host", "randomness", host_randomness)?; | ||
linker.func_wrap("libsql_host", "sleep", host_sleep)?; | ||
linker.func_wrap("libsql_host", "current_time", host_current_time)?; | ||
linker.func_wrap("libsql_host", "get_last_error", host_get_last_error)?; | ||
linker.func_wrap("libsql_host", "current_time_64", host_current_time_64)?; | ||
|
||
linker.func_wrap("libsql_host", "close", host_close)?; | ||
linker.func_wrap("libsql_host", "read", host_read)?; | ||
linker.func_wrap("libsql_host", "write", host_write)?; | ||
linker.func_wrap("libsql_host", "truncate", host_truncate)?; | ||
linker.func_wrap("libsql_host", "sync", host_sync)?; | ||
linker.func_wrap("libsql_host", "file_size", host_file_size)?; | ||
linker.func_wrap("libsql_host", "lock", host_lock)?; | ||
linker.func_wrap("libsql_host", "unlock", host_unlock)?; | ||
linker.func_wrap("libsql_host", "check_reserved_lock", host_check_reserved_lock)?; | ||
linker.func_wrap("libsql_host", "file_control", host_file_control)?; | ||
linker.func_wrap("libsql_host", "sector_size", host_sector_size)?; | ||
linker.func_wrap("libsql_host", "device_characteristics", host_device_characteristics)?; | ||
Ok(()) | ||
} | ||
|
||
fn main() -> anyhow::Result<()> { | ||
let engine = Engine::default(); | ||
|
||
let libsql_module = Module::from_file(&engine, "../../libsql.wasm")?; | ||
|
||
let mut linker = Linker::new(&engine); | ||
link_host_functions(&mut linker)?; | ||
wasmtime_wasi::add_to_linker(&mut linker, |s| s)?; | ||
|
||
std::fs::create_dir("/tmp/wasm-demo").ok(); | ||
// FIXME: we might as well not need it with VFS and virtual WAL, it's here to make stuff easier for debugging | ||
let wasi = WasiCtxBuilder::new() | ||
.inherit_stdio() | ||
.inherit_args()? | ||
.build(); | ||
|
||
let mut store = Store::new(&engine, wasi); | ||
let instance = linker.instantiate(&mut store, &libsql_module)?; | ||
|
||
let malloc = instance.get_typed_func::<i32, i32>(&mut store, "malloc")?; | ||
let free = instance.get_typed_func::<i32, ()>(&mut store, "free")?; | ||
|
||
let memory = instance | ||
.get_memory(&mut store, "memory") | ||
.context("memory export not found")?; | ||
|
||
let db_path = malloc.call(&mut store, 16)?; | ||
memory.write(&mut store, db_path as usize, b"/tmp/wasm-demo.db\0")?; | ||
|
||
let libsql_wasi_init = instance.get_typed_func::<(), ()>(&mut store, "libsql_wasi_init")?; | ||
let open_func = instance.get_typed_func::<i32, i32>(&mut store, "libsql_wasi_open_db")?; | ||
let exec_func = instance.get_typed_func::<(i32, i32), i32>(&mut store, "libsql_wasi_exec")?; | ||
let close_func = instance.get_typed_func::<i32, i32>(&mut store, "sqlite3_close")?; | ||
|
||
libsql_wasi_init.call(&mut store, ())?; | ||
let db = open_func.call(&mut store, db_path)?; | ||
let sql = malloc.call(&mut store, 64)?; | ||
memory.write(&mut store, sql as usize, b"PRAGMA journal_mode=WAL;\0")?; | ||
let rc = exec_func.call(&mut store, (db, sql))?; | ||
let _ = free.call(&mut store, sql)?; | ||
let _ = close_func.call(&mut store, db)?; | ||
let _ = free.call(&mut store, db_path)?; | ||
|
||
println!("rc: {rc}"); | ||
|
||
Ok(()) | ||
} |
Oops, something went wrong.