diff --git a/wasm-play/README.md b/wasm-play/README.md new file mode 100644 index 0000000000..2ba25333bc --- /dev/null +++ b/wasm-play/README.md @@ -0,0 +1,26 @@ +# WASM experiment + +## Compile JS to WASM + +```bash +npx javy-cli@latest compile samples/hello.js -o samples/hello.wasm --wit samples/index.wit -n index-world +``` + +## Executing + +using wasmtime cli: +install cli: + +```bash +curl https://wasmtime.dev/install.sh -sSf | bash +``` + +```bash +echo '{"foo": "bar"}' | wasmtime --invoke run samples/hello.wasm +``` + +using Go: + +```bash +echo '{}' | go run main.go +``` diff --git a/wasm-play/go.mod b/wasm-play/go.mod new file mode 100644 index 0000000000..90f15d4196 --- /dev/null +++ b/wasm-play/go.mod @@ -0,0 +1,5 @@ +module rudder-transformer/wasm-play + +go 1.21.5 + +require github.com/bytecodealliance/wasmtime-go v1.0.0 diff --git a/wasm-play/go.sum b/wasm-play/go.sum new file mode 100644 index 0000000000..7ef73c4849 --- /dev/null +++ b/wasm-play/go.sum @@ -0,0 +1,2 @@ +github.com/bytecodealliance/wasmtime-go v1.0.0 h1:9u9gqaUiaJeN5IoD1L7egD8atOnTGyJcNp8BhkL9cUU= +github.com/bytecodealliance/wasmtime-go v1.0.0/go.mod h1:jjlqQbWUfVSbehpErw3UoWFndBXRRMvfikYH6KsCwOg= diff --git a/wasm-play/index.js b/wasm-play/index.js new file mode 100644 index 0000000000..ceb5ce7a0c --- /dev/null +++ b/wasm-play/index.js @@ -0,0 +1,22 @@ +// Assume add.wasm file exists that contains a single function adding 2 provided arguments +import fs from 'node:fs'; + +const wasmBuffer = fs.readFileSync('./samples/sample1.wasm'); +const wasmModule = await WebAssembly.instantiate(wasmBuffer, { + imports: { + print(arg) { + console.log(arg); + }, + }, + env: { + abortStackOverflow: () => { throw new Error('overflow'); }, + table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' }), + __table_base: 0, + memory: new WebAssembly.Memory({ initial: 256 }), + __memory_base: 1024, + STACKTOP: 0, + STACK_MAX: 0, + } +}); + +console.log(wasmModule.instance) \ No newline at end of file diff --git a/wasm-play/main.go b/wasm-play/main.go new file mode 100644 index 0000000000..f730780d16 --- /dev/null +++ b/wasm-play/main.go @@ -0,0 +1,58 @@ +package main + +import ( + "log" + "os" + + "github.com/bytecodealliance/wasmtime-go" +) + +func main() { + engine := wasmtime.NewEngine() + // Almost all operations in wasmtime require a contextual `store` + // argument to share, so create that first + store := wasmtime.NewStore(engine) + + wasm, err := os.ReadFile("./samples/hello.wasm") + check(err) + // Once we have our binary `wasm` we can compile that into a `*Module` + // which represents compiled JIT code. + module, err := wasmtime.NewModule(engine, wasm) + check(err) + + // Create a linker with WASI functions defined within it + linker := wasmtime.NewLinker(engine) + check(err) + + err = linker.DefineWasi() + check(err) + + // Configure WASI imports to write stdout into a file, and then create + // a `Store` using this wasi configuration. + wasiConfig := wasmtime.NewWasiConfig() + wasiConfig.InheritStdout() + wasiConfig.InheritStderr() + wasiConfig.InheritStdin() + store.SetWasi(wasiConfig) + + instance, err := linker.Instantiate(store, module) + if err != nil { + log.Fatal(err) + } + + // After we've instantiated we can lookup our `run` function and call + // it. + run := instance.GetFunc(store, "run") + if run == nil { + panic("not a function") + } + _, err = run.Call(store) + check(err) + +} + +func check(e error) { + if e != nil { + panic(e) + } +} diff --git a/wasm-play/samples/hello.js b/wasm-play/samples/hello.js new file mode 100644 index 0000000000..71df1e4644 --- /dev/null +++ b/wasm-play/samples/hello.js @@ -0,0 +1,59 @@ + + + +// The transform function. +function transform(input) { + return { foo: input.n + 1, newBar: input.bar + "!" }; +} + +// SUPPORT CODE: + + +// Read input from stdin +function readInput() { + const chunkSize = 1024; + const inputChunks = []; + let totalBytes = 0; + + // Read all the available bytes + while (1) { + const buffer = new Uint8Array(chunkSize); + // Stdin file descriptor + const fd = 0; + const bytesRead = Javy.IO.readSync(fd, buffer); + + totalBytes += bytesRead; + if (bytesRead === 0) { + break; + } + inputChunks.push(buffer.subarray(0, bytesRead)); + } + + // Assemble input into a single Uint8Array + const { finalBuffer } = inputChunks.reduce((context, chunk) => { + context.finalBuffer.set(chunk, context.bufferOffset); + context.bufferOffset += chunk.length; + return context; + }, { bufferOffset: 0, finalBuffer: new Uint8Array(totalBytes) }); + + return JSON.parse(new TextDecoder().decode(finalBuffer)); +} + +// Write output to stdout +function writeOutput(output) { + const encodedOutput = new TextEncoder().encode(JSON.stringify(output)); + const buffer = new Uint8Array(encodedOutput); + // Stdout file descriptor + const fd = 1; + Javy.IO.writeSync(fd, buffer); +} + + +export function run() { + // Read input from stdin + const input = readInput(); + // Call the function with the input + const result = transform(input); + // Write the result to stdout + writeOutput(result); +} \ No newline at end of file diff --git a/wasm-play/samples/hello.wasm b/wasm-play/samples/hello.wasm new file mode 100644 index 0000000000..ba420ee431 Binary files /dev/null and b/wasm-play/samples/hello.wasm differ diff --git a/wasm-play/samples/index.wit b/wasm-play/samples/index.wit new file mode 100644 index 0000000000..fe5ff3d2cf --- /dev/null +++ b/wasm-play/samples/index.wit @@ -0,0 +1,5 @@ +package local:main; + +world index-world { + export run: func(); +} diff --git a/wasm-play/wrapper.js b/wasm-play/wrapper.js new file mode 100644 index 0000000000..e69de29bb2