Giter VIP home page Giter VIP logo

chicory's Introduction

Chicory Runtime

Test Results

Chicory is a JVM native WebAssembly runtime. It allows you to run WebAssembly programs with zero native dependencies or JNI. Chicory can run Wasm anywhere that the JVM can go. It is designed with simplicity and safety in mind. See the development section for a better idea of what we are trying to achieve and why.

Reach out to us: Chicory is very early in development and there will be rough edges. We're hoping to talk to some early adopters and contributors before we formally announce it a beta to the world. Please join our team Zulip chat with this invite link if you're interested in providing feedback or contributing. Or just keeping up with development.

Getting Started (as a user)

Install Dependency

To use the runtime, you need to add the com.dylibso.chicory:runtime dependency to your dependency management system.

Maven

<dependency>
  <groupId>com.dylibso.chicory</groupId>
  <artifactId>runtime</artifactId>
  <version>0.0.10</version>
</dependency>

Gradle

implementation 'com.dylibso.chicory:runtime:0.0.10'

Install the CLI (experimental)

The Java generator CLI is available for download on Maven at the link:

https://repo1.maven.org/maven2/com/dylibso/chicory/cli/<version>/cli-<version>.sh

you can download the latest version and use it locally with few lines:

export VERSION=$(wget -q -O - https://api.github.com/repos/dylibso/chicory/tags --header "Accept: application/json" | jq -r '.[0].name')
wget -O chicory https://repo1.maven.org/maven2/com/dylibso/chicory/cli/${VERSION}/cli-${VERSION}.sh
chmod a+x chicory
./chicory

Loading and Instantiating Code

First your Wasm module must be loaded from disk and then "instantiated". Let's download a test module . This module contains some code to compute factorial:

Download from the link or with curl:

curl https://raw.githubusercontent.com/dylibso/chicory/main/wasm-corpus/src/main/resources/compiled/iterfact.wat.wasm > factorial.wasm

Now let's load this module and instantiate it:

import com.dylibso.chicory.runtime.ExportFunction;
import com.dylibso.chicory.wasm.types.Value;
import com.dylibso.chicory.runtime.Module;
import com.dylibso.chicory.runtime.Instance;
import java.io.File;

// point this to your path on disk
Module module = Module.builder(new File("./factorial.wasm")).build();
Instance instance = module.instantiate();

You can think of the module as the inert code and the instance as a virtual machine loaded with the code and ready to execute.

Invoking an Export Function

Wasm modules, like all code modules, can export functions to the outside world. This module exports a function called "iterFact". We can get a handle to this function using Instance#export(String):

ExportFunction iterFact = instance.export("iterFact");

iterFact can be invoked with the apply() method. We must map any java types to a wasm type and do the reverse when we want to go back to Java. This export function takes an i32 argument. We can use a method like Value#asInt() on the return value to get back the Java integer:

Value result = iterFact.apply(Value.i32(5))[0];
System.out.println("Result: " + result.asInt()); // should print 120 (5!)

Note: Functions in Wasm can have multiple returns but here we're just taking the first returned value.

Memory and Complex Types

Wasm only understands basic integer and float primitives. So passing more complex types across the boundary involves passing pointers. To read, write, or allocate memory in a module, Chicory gives you the Memory class. Let's look at an example where we have a module count_vowels.wasm, written in rust, that takes a string input and counts the number of vowels in the string:

curl https://raw.githubusercontent.com/dylibso/chicory/main/wasm-corpus/src/main/resources/compiled/count_vowels.rs.wasm > count_vowels.wasm

Build and instantiate this module:

Instance instance = Module.builder(new File("./count_vowels.wasm")).build().instantiate();
ExportFunction countVowels = instance.export("count_vowels");

To pass it a string, we first need to put the string in the module's memory. To make this easier and safe, the module gives us some extra exports to allow us to allocate and deallocate memory:

ExportFunction alloc = instance.export("alloc");
ExportFunction dealloc = instance.export("dealloc");

Let's allocate Wasm memory for a string and put in the instance's memory. We can do this with Memory#put:

import com.dylibso.chicory.runtime.Memory;
Memory memory = instance.memory();
String message = "Hello, World!";
int len = message.getBytes().length;
// allocate {len} bytes of memory, this returns a pointer to that memory
int ptr = alloc.apply(Value.i32(len))[0].asInt();
// We can now write the message to the module's memory:
memory.writeString(ptr, message);

Now we can call countVowels with this pointer to the string. It will do it's job and return the count. We will call dealloc to free that memory in the module. Though the module could do this itself if you want:

Value result = countVowels.apply(Value.i32(ptr), Value.i32(len))[0];
dealloc.apply(Value.i32(ptr), Value.i32(len));
assert(3 == result.asInt()); // 3 vowels in Hello, World!

Host Functions

On its own, Wasm can't do anything but compute. It cannot affect the outside world. This might seem like a weakness but it's actually Wasm's greatest strength. By default, programs are sandboxed and have no capabilities. If you want a program to have capabilities, you must provide them. This puts you in the seat of the operating system. A module can ask for a capability by listing an "import" function in it's bytecode format. You can fulfill this import with a host function written in Java. Regardless of the language of the module, it can call this Java function when it needs. If it helps, you can think of host functions like syscalls or a languages standard library but you decide what they are and how they behave and it's written in Java.

Let's download another example module to demonstrate this:

curl https://raw.githubusercontent.com/dylibso/chicory/main/wasm-corpus/src/main/resources/compiled/host-function.wat.wasm > logger.wasm

This module expects us to fulfil an import with the name console.log which will allow the module to log to the stdout. Let's write that host function:

import com.dylibso.chicory.runtime.HostFunction;
import com.dylibso.chicory.wasm.types.ValueType;
var func = new HostFunction(
    (Instance instance, Value... args) -> { // decompiled is: console_log(13, 0);
        var len = args[0].asInt();
        var offset = args[1].asInt();
        var message = instance.memory().readString(offset, len);
        println(message);
        return null;
    },
    "console",
    "log",
    List.of(ValueType.I32, ValueType.I32),
    List.of());

Again we're dealing with pointers here. The module calls console.log with the length of the string and the pointer (offset) in its memory. We again use the Memory class but this time we're pulling a string out of memory. We can then print that to stdout on behalf of our Wasm program.

Note that the HostFunction needs 3 things:

  1. A lambda to call when the Wasm module invokes the import
  2. The namespace and function name of the import (in our case it's console and log respectively)
  3. The Wasm type signature (this function takes 2 i32s as arguments and returns nothing)

Now we just need to pass this host function in during our instantiation phase:

import com.dylibso.chicory.runtime.HostImports;
var imports = new HostImports(new HostFunction[] {func});
var instance = Module.builder(new File("./logger.wasm")).withHostImports(imports).build().instantiate();
var logIt = instance.export("logIt");
logIt.apply();
// should print "Hello, World!" 10 times

Development

Why is this needed?

If you'd prefer to watch a video instead of reading, see our 2024 Wasm I/O on the subject:

Wasm I/O Chicory talk

There are a number of mature Wasm runtimes to choose from to execute a Wasm module. To name a few v8, wasmtime, wasmer, wasmedge, etc.

Although these can be great choices for running a Wasm application, embedding them into your existing Java application has some downsides. Because these runtimes are written in C/C++/Rust/etc, they must be distributed and run as native code. This causes two main friction points:

1. Distribution

If you're distributing a Java library (jar, war, etc), you must now distribute along with it a native object targeting the correct architecture and operating system. This matrix can become quite large. This eliminates a lot of the simplicity and original benefit of shipping Java code.

2. Runtime

At runtime, you must use FFI to execute the module. While there might be performance benefits to doing this for some modules, when you do, you're effectively escaping the safety and observability of the JVM. Having a pure JVM runtime means all your security and memory guarantees, and your tools, can stay in place.

Goals

  • Be as safe as possible
    • In that we are willing to sacrifice things like performance for safety and simplicity
  • Make it easy to run Wasm in any JVM environment without native code, including very restrictive environments.
  • Fully support the core Wasm spec
  • Make integration with Java (and other host languages) easy and idiomatic.

Non-Goals:

  • Be a standalone runtime
  • Be the fastest runtime
  • Be the right choice for every JVM project

Roadmap

Chicory development was started in September, 2023. The following are the milestones we're aiming for. These are subject to change but represent our best guesses with current information. These are not necessarily sequential and some may be happening in parallel. Unless specified, any unchecked box is still not planned or started. If you have an interest in working on any of these please reach out in Zulip!

Bootstrap a bytecode interpreter and test suite (EOY 2023)

  • Wasm binary parser link
  • Simple bytecode interpreter
  • Establish basic coding and testing patterns
  • Generate JUnit tests from wasm test suite link

Make the interpreter production ready (Summer 2024)

  • Make all tests green with the interpreter (important for correctness)
    • Almost complete
  • Implement validation logic (important for safety)
  • Draft of the v1.0 API (important for stability and dx)

Make it fast (EOY 2024)

The primary goal here is to create an AOT compiler that generates JVM bytecode as interpreting bytecode can only be so fast.

  • Decouple interpreter and create separate compiler and interpreter "engines"
    • Started by @danielperano
  • Proof of concept AOT compiler (run some subset of modules)
  • AOT engine passes all the same specs as interpreter (stretch goal)
  • Off-heap linear memory (stretch goal)

Make it compatible (EOY 2024)

Prior Art

Building the Runtime

Contributors and other advanced users may want to build the runtime from source. To do so, you'll need to have Maven installed. Java version 11+ required for a proper build. You can download and install Java 11 Temurin

Basic steps:

  • mvn clean install to run all of the project's tests and install the library in your local repo
  • mvn spotless:apply to autoformat the code
  • ./scripts/compile-resources.sh will recompile and regenerate the resources/compiled folders

NOTE: The install target relies on the wabt library to compile the test suite. This is not currently released for ARM (e.g. new Macs with Apple Silicon). However, wabt is available from Homebrew, so brew install wabt before running mvn clean install should work.

logging

For maximum compatibility and to avoid external dependencies we use, by default, the JDK Platform Logging (JEP 264). You can configure it by providing a logging.properties using the java.util.logging.config.file property and here you can find the possible configurations.

For more advanced configuration scenarios we encourage you to provide an alternative, compatible, adapter:

It's also possible to provide a custom com.dylibso.chicory.log.Logger implementation if JDK Platform Logging is not available or doesn't fit.

chicory's People

Contributors

andreatp avatar appleflavored avatar bhelx avatar danielperano avatar dependabot[bot] avatar dmlloyd avatar electrum avatar enebo avatar illarionov avatar kittylyst avatar lburgazzoli avatar mariofusco avatar mstepan avatar nilslice avatar starksm64 avatar thomasdarimont avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

chicory's Issues

Wasm 1.0 specs

We need to hook up the test suite to the runtime. There are two directions we can go here:

  1. Generate java code (Junit tests) from the wast files
  2. Write some java code which can parse and "interpret" the json objects output by wast2json

1. generate java code

This is the direction I have been going down. It looks something like this:

This is kind of nice because you have individual lines of codes that you can debug or comment out. But the multiple levels of indirection make this kind of tricky.

2. interpret the wast json files

What we might want to consider instead is skipping the ruby part and writing java code that reads the JSON files directly. This would remove the level of indirection with the ruby code generation but it might require a little more thought on how to make the developer experience of dealing with failing tests a little better.

How wast works

In order to decide and finish this work, I think it's important to explain how wast tests work.

The test suite is a series of wast files. wast is an extension of the wat format. A wast file contains wasm modules followed by some assertion statements. Example:

(module
  (func (export "add") (param $x i32) (param $y i32) (result i32) (i32.add (local.get $x) (local.get $y)))
  ;; ...
)

(assert_return (invoke "add" (i32.const 1) (i32.const 1)) (i32.const 2))
(assert_return (invoke "add" (i32.const 1) (i32.const 0)) (i32.const 1))
;; ....

wast2json turns this wast file into a json file which roughly describes the parsed AST of the wast file, and it compiles and generates each module into a wasm file. It will do this in order using the naming convention:
<testname>.<index>.wasm. So running wast2json on i32.wast generates i32.json as well as many modules such as i32.0.wasm, i32.1.wasm, etc.

The json file looks something like this:

"commands": [
  {"type": "module", "line": 3, "filename": "i32.0.wasm"}, 
  {"type": "assert_return", "line": 37, "action": {"type": "invoke", "field": "add", "args": [{"type": "i32", "value": "1"}, {"type": "i32", "value": "1"}]}, "expected": [{"type": "i32", "value": "2"}]}, 
  {"type": "assert_return", "line": 38, "action": {"type": "invoke", "field": "add", "args": [{"type": "i32", "value": "1"}, {"type": "i32", "value": "0"}]}, "expected": [{"type": "i32", "value": "1"}]}, 
]

Right now i only support assert_return (which i effectively turn into assertEquals junit tests). We need to support assert_invalid, assert_malformed, etc. However for the time being, I think we can skip these and focus less on validation and more on correctness of the happy path. I figure i can implement validation once the code stabilizes a bit and we pass all the happy path tests.

Host Functions not very composable

I've had this issue where working with host functions and imports can be awkward. For example. if i want to support both wasi and some custom host functions, i need to compose them like this:

HostFunction[] extra = createMyCustomHostFunctions();
HostFunction[]  wasiFuncs = wasi.toHostFunctions();
HostFunction[] allImports = new HostFunction[wasiFuncs.length + extra.length];
System.arraycopy(wasiFuncs, 0, allImports, 0, wasiFuncs.length);
System.arraycopy(extra, 0, allImports, wasiFuncs.length, extra.length);

var imports = new HostImports(allImports);

I can think of a number of ways to address this. A quick fix might be to allow the HostImports constructor to take an arraylist. then we turn it into an array in the constructor. Or there may be a better interface to build and compose these that we should consider.
Now might be a good time to create a builder interface for host modules. I think wazero has a decent interface for this: https://github.com/tetratelabs/wazero/blob/main/examples/import-go/age-calculator.go#L41 but of course we could do it in a more idomatic Java way. I can easily do the first solution but I felt I'd give an opportunity for some of the experienced Java developers on the team to bring their opinion on a good interface.

wasm-objdump seems to break in the IDE

Running a single test from the IDE (IntelliJ), for example SpecV1MemorySizeTest.test2, I get this stack trace (note that I have already run the tests from the terminal with mvn spotless:apply clean install):

Generating WASM ObjectDump for failed test: SpecV1MemorySizeTest.test2
wasm-objdump for reference: SpecV1MemorySizeTest.test2 module: file:/Users/aperuffo/workspace/chicory/runtime/target/compiled-wast/memory_size/spec.0.wasm
Oct 31, 2023 2:00:51 PM org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor lambda$invokeTestWatchers$3
WARNING: Failed to invoke TestWatcher [com.dylibso.chicory.testing.ChicoryTestWatcher] for method [com.dylibso.chicory.test.gen.SpecV1MemorySizeTest#test2()] with display name [test2()]
java.lang.RuntimeException: java.io.IOException: Cannot run program "wasm-objdump" (in directory "."): error=2, No such file or directory
	at com.dylibso.chicory.testing.WasmDumper.startProcess(WasmDumper.java:82)
	at com.dylibso.chicory.testing.WasmDumper.objectDump(WasmDumper.java:43)
	at com.dylibso.chicory.testing.ChicoryTestWatcher.createWasmObjectDumpForFailedTest(ChicoryTestWatcher.java:56)
	at com.dylibso.chicory.testing.ChicoryTestWatcher.testFailed(ChicoryTestWatcher.java:28)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$nodeFinished$15(TestMethodTestDescriptor.java:307)
	at org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor.lambda$invokeTestWatchers$3(MethodBasedTestDescriptor.java:130)
	at org.junit.platform.commons.util.CollectionUtils.forEachInReverseOrder(CollectionUtils.java:217)
	at org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor.invokeTestWatchers(MethodBasedTestDescriptor.java:144)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.nodeFinished(TestMethodTestDescriptor.java:298)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.nodeFinished(TestMethodTestDescriptor.java:69)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.reportCompletion(NodeTestTask.java:188)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:198)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:169)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:93)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:58)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:141)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:57)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:103)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:85)
	at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:47)
	at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:63)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
	at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
Caused by: java.io.IOException: Cannot run program "wasm-objdump" (in directory "."): error=2, No such file or directory
	at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1143)
	at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1073)
	at com.dylibso.chicory.testing.WasmDumper.startProcess(WasmDumper.java:59)
	... 50 more
Caused by: java.io.IOException: error=2, No such file or directory
	at java.base/java.lang.ProcessImpl.forkAndExec(Native Method)
	at java.base/java.lang.ProcessImpl.<init>(ProcessImpl.java:314)
	at java.base/java.lang.ProcessImpl.start(ProcessImpl.java:244)
	at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1110)
	... 52 more

Missing specs

Editable WIP tracking issue.
Tick the checkbox if you start working on something, remove the line when is done:

  • elem.wast
  • linking.wast

Update to Java 17 or 21?

Newer Java language features such as records, pattern matching, sealed classes, etc., improve the developer experience. What do we think about updating Chicory to a newer Java version?

Complete table support

There are a few specs related to table that we should fix:

table-sub.wast,
table_copy.wast,
table_fill.wast,
table_get.wast,
table_grow.wast,
table_init.wast,
table_set.wast,
table_size.wast

Fix host fuction -> import mapping code

The code to map host functions to imports during the instantiation process doesn't quite work. We've gotten away with it because it works well enough for simple use cases: https://github.com/dylibso/chicory/blob/main/runtime/src/main/java/com/dylibso/chicory/runtime/Module.java#L181

In short, we should be iterating over the imports and trying to satisfy those and not the other way around. Also the host functions need to be ordered in the way that the imports expect. index of the import is essentially it's function id as the imports come first in the function list.

I'll tackle this one, I have an idea how it should work.

Enable all `assert_invalid` tests

Opening this issue to gather as much help as possible from the community ๐Ÿ™‚

asset_invalid tests are, mostly, exercising the static type checking capabilities of the Wasm runtime.
Those tests are important to verify that we will not be trying to execute invalid modules and, ultimately, hitting undefined behaviors.
They will make Chicory much more safe!

The section of the spec where you find those rules is here.
We started the implementation following the steps of wazero which is performing this validation, mostly, in the func_validation.go file.
In this codebase we started implementing the same checks in the TypeValidator.
It's structured as a stack based interpreter like the actual runtime but, instead of checking the values, it verifies that the types are matching i.e. if you load an I32 on the Stack and the subsequent instruction is supposed to pop an F64 we have to throw an exception accordingly.

The process to work on this issue is iterative:

  1. remove/comment one wast file name from this list in the runtime/pom.xml (the complexity of tackling those vary a lot, check a different file if you are getting stuck too much or the required code increases a lot)
  2. run a full mvn spotless:apply clean install
  3. check the failures as they are, likely, going to be related with the type validation logic
  4. fix the tests! If enabling an entire wast file turns out to be a lot of work, you can disable specific tests adding the names in this section of the pom.xml
  5. submit a PR! ๐ŸŽ‰

We will be grateful of any contribution!
If you start to work on something, you can mention the file/files you are working on with a comment on this Issue, to decrease the chances that we spend duplicated effort.

Where to put test wasm modules

We have a folder with all the test wasm modules: https://github.com/dylibso/chicory/tree/main/runtime/src/test/resources/wasm
This has the source code (.wat, .c, .rs) as well as the binaries (.wasm) checked into the source.
We have a script to compile and manage them: https://github.com/dylibso/chicory/blob/main/runtime/scripts/compile-tests.sh

These are both repeated in the wasm submodule project because they were historically 2 projects. We should look at one single / simple way to manage these.

implement some kind of instruction/call limit

I am looking for something that would allow me to eg: call a WASM main function. have it execute 100k instructions and then return to java where I can then later call eg: .resume() where it would run another 100k instructions (or until it exits/returns)

Reproducible compiled wasm

When merging the WASI support we struggled with the compatibility and reproducibility of the compiled wasm files.

Problems:

  • the container image is for X86 and seems to have issues on ARM
  • the wasi compiled modules are generated without the _start export

Exception passthrough

It would be nice to have some way to pass (most) exceptions through, instead of having them wrapped in WASMMachineException.

In particular because WASMMachineException is extremely slow for the things this little special constructor enables one to do. The specific pattern is usually called "non-local control flow".

Granted, one can still use Error for this purpose, since Error has similar semantics to RuntimeException and also includes the appropriate constructor. And using Error for "normal occurrences" isn't completely unheard of.

Initial benchmark setup

It is advisable to have solid benchmarks from the beginning to quickly and easily assess the performance impact of different implementations.

For this we could leverage jmh.
Some example JMH integrations can be found here: https://github.com/FasterXML/jackson-benchmarks/tree/2.15

The idea of this issue is to provide an initial setup to enable the project to write easy and reliable micro benchmarks.

Stack unwinds incorrectly when jumping from `if` block to the outermost block using a `br_if`

Given the following code:

(module
  (type $t0 (func (param i32) (result i32)))

  (func $innerFunc (type $t0) (param $arg0 i32) (result i32)
    (local $resultCode i32)
    block $outerBlock
      i32.const -1 ;; These values must be dropped from the stack
      i32.const -2 ;; when the br_if jump is triggered

      i32.const -3
      if (result i32)
        local.get $arg0
        local.tee $resultCode
        br_if $outerBlock
        i32.const 100
      else
        i32.const 200
      end
      local.set $resultCode

      drop
      drop
    end
    local.get $resultCode)

  (func $main (export "main") (type $t0) (param $arg0 i32) (result i32)
    i32.const 0
    local.get $arg0
    call $innerFunc
    i32.add)

  (memory $M0 1))

When executing 'main' on Chicory with the argument '5', the function returns the incorrect result of 3.
In the "innerFunc", the br_if jump to the outer block is triggered and after its execution the values -1 and -2 remain on the stack:

stack = {ArrayDeque@2296}  size = 4
 0 = {Value@2311} "5@i32"
 1 = {Value@2312} "-2@i32"
 2 = {Value@2313} "-1@i32"
 3 = {Value@2314} "0@i32"

When executed on wasm-interp the result is correct:

$ wasm-interp test.wasm -r "main" -a "i32:5"
main(i32:5) => i32:5

Flaky code generation

At times the test code generator fails with errors even in CI, we should be able to narrow down the issues and have a more stable build.

AOT tidy-up

As per our Zulip conversation, I merged #312 as-is. I'm creating this issue to track the tidy-up that @andreaTP suggested.

Alternative method for single returns

I initially made the ExportFunction have a single apply method which represents the real life interface of wasm functions (so it supports multiple returns). However, 99% of the time I just pull the first element. How can we make this more ergonomic?

One idea I've had is to just add another method that returns the first element:

public interface ExportFunction {
    Value[] apply(Value... args) throws ChicoryException;
    Value applySingle(Value... args) throws ChicoryException;
}

Unsure what we'd call the method, applyOne or applySingle are my only ideas. But, perhaps there is a better way to solve this. Wanted to post this to get some suggestions.

Review the numbers we show in the test badge

The testing badge at the top of the Readme is being generated here:

status: '${{ fromJSON( steps.test-results.outputs.json ).formatted.stats.tests }} tests, ${{ fromJSON( steps.test-results.outputs.json ).formatted.stats.runs }} runs: ${{ fromJSON( steps.test-results.outputs.json ).conclusion }}'

I think that the numbers are a bit off after we added the aot module, we should check and eventually fix any finding.

Text format support for a Java WebAssembly runtime

The task is to construct a Parser which converts Text format WASM Module and Generating output through AST using Domain classes already available in the project.
ANTLR is a suggested Parsing framework for the task.

Generated tests are not running

The generated tests are not running, probably because of changes in the latest wabt version that is getting downloaded, or in the testsuite ...

Wast2JsonWrapper#resolveOrInstallWast2Json does not work with wast2json 1.0.34+

I'm building the chicory project on an m1 max osx system and the downloaded wast2json binary fails to load because the x86_64 arch binary fails to load dependent libs like openssl which have only been installed as arm64 arch binaries. Instead of trying to install a separate set of x86_64 libs using brew under Rosetta, I just rebuilt the wabt repo natively.

The Wast2JsonWrapper#resolveOrInstallWast2Json fails to locate the system version of the wast2json because the lastest wast2json command expects a filename or an option like --version or --help:

(base) starksm@Scotts-Mac-Studio runtime % wast2json
wast2json: expected filename argument.
Try '--help' for more information.
(base) starksm@Scotts-Mac-Studio runtime % wast2json --version
1.0.34 (git~1.0.34-22-g9fdd0242)

I have a simple 1 line PR to add the --version option to the system command check.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.