Skip to content

Commit

Permalink
Merge pull request #139 from Peefy/feat-wasm-lib-kcl-runtime-error-me…
Browse files Browse the repository at this point in the history
…ssage

feat: kcl runtime error message impl and add more unit tests
  • Loading branch information
Peefy authored Sep 9, 2024
2 parents f8b008f + d3295e3 commit ffa8e28
Show file tree
Hide file tree
Showing 16 changed files with 335 additions and 27 deletions.
19 changes: 19 additions & 0 deletions .github/workflows/wasm-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,32 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 20.x
- name: Install rust nightly toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: 1.79
override: true
components: clippy, rustfmt
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.22

- name: Install dependencies
run: npm install

- name: Build
run: npm run build

- name: Test
run: npm run test

- name: Rust example e2e tests
run: cd examples/rust && cargo test -r

- name: Go example e2e tests
run: cd examples/go && go mod tidy && go run main.go

- name: Publish Dry Run
if: "startsWith(github.ref, 'refs/tags/') && contains(github.ref, '-')"
# Since this command will not exit with non-zero code when file missing,
Expand Down
Binary file modified wasm/examples/browser/kcl.wasm
100644 → 100755
Binary file not shown.
13 changes: 11 additions & 2 deletions wasm/examples/browser/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { load, invokeKCLRun } from "@kcl-lang/wasm-lib";
import { load, invokeKCLRun, invokeKCLFmt } from "@kcl-lang/wasm-lib";

const inst = await load();

async function main() {
const inst = await load();
const result = invokeKCLRun(inst, {
filename: "test.k",
source: `
Expand All @@ -11,6 +12,14 @@ schema Person:
p = Person {name = "Alice"}`,
});
console.log(result);
const fmtResult = invokeKCLFmt(inst, {
source: `
schema Person:
name: str
p = Person {name = "Alice"}`,
});
console.log(fmtResult);
}

main();
4 changes: 2 additions & 2 deletions wasm/examples/browser/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"compilerOptions": {
"target": "es2022",
"module": "commonjs",
"moduleResolution": "node",
"module": "es2022",
"lib": ["es2022", "dom"],
"moduleResolution": "node",
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
Expand Down
7 changes: 7 additions & 0 deletions wasm/examples/go/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,11 @@ func main() {
panic(err)
}
fmt.Println(result)
result, err = m.Fmt(&module.FmtOptions{
Source: "a = 1",
})
if err != nil {
panic(err)
}
fmt.Println(result)
}
38 changes: 38 additions & 0 deletions wasm/examples/go/pkg/module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,18 @@ type RunOptions struct {
Source string
}

type FmtOptions struct {
Source string
}

type KCLModule struct {
Instance *wasmtime.Instance
Store *wasmtime.Store
Memory *wasmtime.Memory
KclMalloc *wasmtime.Func
KclFree *wasmtime.Func
KclRun *wasmtime.Func
KclFmt *wasmtime.Func
}

func New(path string) (*KCLModule, error) {
Expand Down Expand Up @@ -55,13 +60,15 @@ func New(path string) (*KCLModule, error) {
malloc := instance.GetFunc(store, "kcl_malloc")
free := instance.GetFunc(store, "kcl_free")
run := instance.GetFunc(store, "kcl_run")
fmt := instance.GetFunc(store, "kcl_fmt")
return &KCLModule{
Instance: instance,
Store: store,
Memory: memory,
KclMalloc: malloc,
KclFree: free,
KclRun: run,
KclFmt: fmt,
}, nil
}

Expand Down Expand Up @@ -106,3 +113,34 @@ func (m *KCLModule) Run(opts *RunOptions) (string, error) {

return result, nil
}

func (m *KCLModule) Fmt(opts *FmtOptions) (string, error) {
sourcePtr, sourceLen, err := copyStringToWasmMemory(m.Store, m.KclMalloc, m.Memory, opts.Source)
if err != nil {
return "", err
}
defer func() {
err := freeMemory(m.Store, m.KclFree, sourcePtr, sourceLen)
if err != nil {
fmt.Println("Failed to free source memory:", err)
}
}()

resultPtr, err := m.KclFmt.Call(m.Store, sourcePtr)
if err != nil {
return "", err
}

result, _, err := copyCStrFromWasmMemory(m.Store, m.Memory, resultPtr.(int32))
if err != nil {
return "", err
}
defer func() {
err := freeMemory(m.Store, m.KclFree, resultPtr.(int32), int32(len(result)))
if err != nil {
fmt.Println("Failed to free result memory:", err)
}
}()

return result, nil
}
19 changes: 16 additions & 3 deletions wasm/examples/node/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { load, invokeKCLRun } from "@kcl-lang/wasm-lib";
import { load, invokeKCLRun, invokeKCLFmt } from "@kcl-lang/wasm-lib";

async function main() {
async function run() {
const inst = await load();
const result = invokeKCLRun(inst, {
filename: "test.k",
Expand All @@ -13,4 +13,17 @@ p = Person {name = "Alice"}`,
console.log(result);
}

main();
async function fmt() {
const inst = await load();
const result = invokeKCLFmt(inst, {
source: `
schema Person:
name: str
p = Person {name = "Alice"}`,
});
console.log(result);
}

run();
fmt();
58 changes: 55 additions & 3 deletions wasm/examples/rust/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::path::Path;
#[cfg(test)]
mod tests;

use anyhow::Result;
use std::path::Path;
use wasmtime::*;
use wasmtime_wasi::{
preview1::{self, WasiP1Ctx},
Expand All @@ -19,18 +21,26 @@ impl State {
}
}

#[derive(Debug)]
pub struct RunOptions {
pub filename: String,
pub source: String,
}

#[derive(Debug)]
pub struct FmtOptions {
pub source: String,
}

pub struct KCLModule {
pub instance: Instance,
store: Store<State>,
memory: Memory,
malloc: TypedFunc<i32, i32>,
free: TypedFunc<(i32, i32), ()>,
run: TypedFunc<(i32, i32), i32>,
fmt: TypedFunc<i32, i32>,
runtime_err: TypedFunc<(i32, i32), i32>,
}

impl KCLModule {
Expand All @@ -54,13 +64,17 @@ impl KCLModule {
let malloc = instance.get_typed_func::<i32, i32>(&mut store, "kcl_malloc")?;
let free = instance.get_typed_func::<(i32, i32), ()>(&mut store, "kcl_free")?;
let run = instance.get_typed_func::<(i32, i32), i32>(&mut store, "kcl_run")?;
let fmt = instance.get_typed_func::<i32, i32>(&mut store, "kcl_fmt")?;
let runtime_err = instance.get_typed_func::<(i32, i32), i32>(&mut store, "kcl_runtime_err")?;
Ok(KCLModule {
instance,
store,
memory,
malloc,
free,
run,
fmt,
runtime_err,
})
}

Expand All @@ -70,10 +84,39 @@ impl KCLModule {
copy_string_to_wasm_memory(&mut self.store, &self.malloc, self.memory, &opts.filename)?;
let (source_ptr, source_len) =
copy_string_to_wasm_memory(&mut self.store, &self.malloc, self.memory, &opts.source)?;
let result_ptr = self.run.call(&mut self.store, (filename_ptr, source_ptr))?;
let runtime_err_len = 1024;
let (runtime_err_ptr, _) = malloc_bytes_from_wasm_memory(&mut self.store, &self.malloc, runtime_err_len)?;
let result_str = match self.run.call(&mut self.store, (filename_ptr, source_ptr)) {
Ok(result_ptr) => {
let (result_str, result_len) =
copy_cstr_from_wasm_memory(&mut self.store, self.memory, result_ptr as usize)?;
free_memory(&mut self.store, &self.free, result_ptr, result_len)?;
result_str
},
Err(err) => {
self.runtime_err.call(&mut self.store, (runtime_err_ptr, runtime_err_len))?;
let (runtime_err_str, runtime_err_len) =
copy_cstr_from_wasm_memory(&mut self.store, self.memory, runtime_err_ptr as usize)?;
free_memory(&mut self.store, &self.free, runtime_err_ptr, runtime_err_len)?;
if runtime_err_str.is_empty() {
return Err(err)
} else {
runtime_err_str
}
},
};
free_memory(&mut self.store, &self.free, filename_ptr, filename_len)?;
free_memory(&mut self.store, &self.free, source_ptr, source_len)?;
Ok(result_str)
}

/// Run with the wasm module and options.
pub fn fmt(&mut self, opts: &FmtOptions) -> Result<String> {
let (source_ptr, source_len) =
copy_string_to_wasm_memory(&mut self.store, &self.malloc, self.memory, &opts.source)?;
let result_ptr = self.fmt.call(&mut self.store, source_ptr)?;
let (result_str, result_len) =
copy_cstr_from_wasm_memory(&mut self.store, self.memory, result_ptr as usize)?;
free_memory(&mut self.store, &self.free, filename_ptr, filename_len)?;
free_memory(&mut self.store, &self.free, source_ptr, source_len)?;
free_memory(&mut self.store, &self.free, result_ptr, result_len)?;

Expand All @@ -98,6 +141,15 @@ fn copy_string_to_wasm_memory<T>(
Ok((ptr, length as usize))
}

fn malloc_bytes_from_wasm_memory<T>(
store: &mut Store<T>,
malloc: &TypedFunc<i32, i32>,
length: i32,
) -> Result<(i32, usize)> {
let ptr = malloc.call(&mut *store, length)?;
Ok((ptr, length as usize))
}

fn copy_cstr_from_wasm_memory<T>(
store: &mut Store<T>,
memory: Memory,
Expand Down
68 changes: 68 additions & 0 deletions wasm/examples/rust/src/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use crate::{FmtOptions, KCLModule, RunOptions};
use anyhow::Result;

const WASM_PATH: &str = "../../kcl.wasm";
const BENCH_COUNT: usize = 20;

#[test]
fn test_run() -> Result<()> {
let opts = RunOptions {
filename: "test.k".to_string(),
source: "a = 1".to_string(),
};
let mut module = KCLModule::from_path(WASM_PATH)?;
for _ in 0..BENCH_COUNT {
let result = module.run(&opts)?;
println!("{}", result);
}
Ok(())
}

#[test]
fn test_run_parse_error() -> Result<()> {
let opts = RunOptions {
filename: "test.k".to_string(),
source: "a = ".to_string(),
};
let mut module = KCLModule::from_path(WASM_PATH)?;
let result = module.run(&opts)?;
println!("{}", result);
Ok(())
}

#[test]
fn test_run_type_error() -> Result<()> {
let opts = RunOptions {
filename: "test.k".to_string(),
source: "a: str = 1".to_string(),
};
let mut module = KCLModule::from_path(WASM_PATH)?;
let result = module.run(&opts)?;
println!("{}", result);
Ok(())
}

#[test]
fn test_run_runtime_error() -> Result<()> {
let opts = RunOptions {
filename: "test.k".to_string(),
source: "a = [][0]".to_string(),
};
let mut module = KCLModule::from_path(WASM_PATH)?;
let result = module.run(&opts)?;
println!("{}", result);
Ok(())
}

#[test]
fn test_fmt() -> Result<()> {
let opts = FmtOptions {
source: "a = 1".to_string(),
};
let mut module = KCLModule::from_path(WASM_PATH)?;
for _ in 0..BENCH_COUNT {
let result = module.fmt(&opts)?;
println!("{}", result);
}
Ok(())
}
11 changes: 11 additions & 0 deletions wasm/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { Config } from "jest";

const config: Config = {
verbose: true,
transform: { "^.+\\.ts?$": "ts-jest" },
testEnvironment: "node",
testRegex: "/tests/.*\\.(test|spec)?\\.(ts|tsx)$",
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
};

export default config;
Binary file modified wasm/kcl.wasm
100644 → 100755
Binary file not shown.
Loading

0 comments on commit ffa8e28

Please sign in to comment.