Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions docs/docs/index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
sidebar_position: 1
sidebar_label: Docs
title: Docs
---

## Getting Started
Expand Down Expand Up @@ -34,7 +35,7 @@ The Chicory 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:
you can download the latest version and use it locally by typing:

```bash
export VERSION=$(wget -q -O - https://api.github.com/repos/dylibso/chicory/tags --header "Accept: application/json" | jq -r '.[0].name')
Expand Down Expand Up @@ -82,19 +83,20 @@ Module module = Parser.parse(new File("./factorial.wasm"));
Instance instance = Instance.builder(module).build();
```

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.
You can think of the `module` as of inert code, and the `instance`
is the run-time representation of that code: a virtual machine 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)`:
world. This module exports a function called `"iterFact"`.
We can get a handle to this function using `Instance#export(String)`:

```java
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
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:

Expand Down
1 change: 1 addition & 0 deletions docs/docs/usage/_category_.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
label: Usage
95 changes: 59 additions & 36 deletions docs/docs/usage/host-functions.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,40 @@
---
id: Host functions
sidebar_position: 1
sidebar_label: Host functions
---
# Guests and Hosts

### Host Functions
In Wasm, the instance of a module is generally regarded as the **guest**,
and the surrounding runtime environment is usually called the **host**.

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:
For example, an **application** using Chicory as a **library** would be the **host**
to a Wasm module you have instantiated.

In previous section, we saw that Wasm modules may **export** functions, so that
they can be externally invoked. But Wasm modules may also **import** functions.
These imports have to be resolved at the time when a module is instantiated;
in other words, when a module is instantiated, the runtime has to provide
references for all the imports that a module declares.

The import/export mechanism is the way through which Wasm interacts
with the outside world: without imports, a Wasm module is "pure compute",
that is, it cannot perform any kind of I/O, nor can it interact with other
modules.

## Host Functions

One way to fulfill imports is providing a **host function** written in Java.
Regardless of the source language that originated your Wasm module,
it will be able to call this Java function when needed.

It is called a **host** function, because it is written in the language of the
**host** (in this case, a JVM). As opposed to any other Wasm function,
a **host function** is _unrestricted_ and it may interact with the surrounding
environment in any arbitrary way. This let you "escape the sandbox".

If it helps, you can think of host functions as similar to system calls
or the standard library in your favorite programming language. The key difference is that,
instead of relying on a default implementation, you use Java to define their behavior
and determine what they do.

Let's see it with another example. Download now the following Wasm binary:

```bash
curl https://raw.githubusercontent.com/dylibso/chicory/main/wasm-corpus/src/main/resources/compiled/host-function.wat.wasm > logger.wasm
Expand All @@ -32,15 +52,15 @@ docs.FileOps.copyFromWasmCorpus("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:
This module expects us to fulfil an import with the name `console.log`.
As the name implies, this function allows a caller to log a message to standard output.
We could write it as the host function:

<!--
```java
public String hostFunctionResult = "";
public void println(String value) {
hostFunctionResult += value + "\n";
}
System.setOut(new PrintStream(
new BufferedOutputStream(
new FileOutputStream("docs/usage/host-functions.md.result"))));
```
-->

Expand All @@ -59,35 +79,38 @@ var func = new HostFunction(
var len = (int) args[0];
var offset = (int) args[1];
var message = instance.memory().readString(offset, len);
println(message);
System.out.println(message);
return null;
});
```

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.
The module calls `console.log` with the length of the string and an index (offset) in its memory.
This is essentially a pointer into the Wasm's "linear memory".
The `Instance` class provides a reference to a `Memory` object instance, that allows
to pull a value out of memory. You can use the `readString()` convenience method
to read a buffer into a `String`; we can then print that to stdout on behalf of our Wasm program.

Note that the HostFunction needs 3 things:
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)
1. The namespace and function name of the import (in our case it's `console` and `log` respectively)
2. The Wasm type signature (this function takes 2 `i32`s as arguments and returns nothing)
3. A lambda to call when the Wasm module invokes the import

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

Now we just need to pass this host function when we instantiate the module. We can do so by using a `Store`.

```java
import com.dylibso.chicory.wasm.Parser;
import com.dylibso.chicory.runtime.ImportValues;
var hostFunctions = new ImportValues(new HostFunction[] {func});
var instance = Instance.builder(Parser.parse(new File("./logger.wasm"))).withImportValues(hostFunctions).build();
import com.dylibso.chicory.runtime.Store;

// instantiate the store
var store = new Store();
// registers `console.log` in the store
store.addFunction(func);
var instance = store.instantiate("logger", Parser.parse(new File("./logger.wasm")));
var logIt = instance.export("logIt");
logIt.apply();
// should print "Hello, World!" 10 times
```

<!--
```java
docs.FileOps.writeResult("docs/usage", "host-functions.md.result", hostFunctionResult);
```
-->
67 changes: 31 additions & 36 deletions docs/docs/usage/linking.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
---
id: Linking
sidebar_position: 1
sidebar_label: Linking
---
# Instantiating Modules

### Store and Instantiating Multiple Modules
In the [Host Functions section](host-functions.md) we met the `Store` for the first-time.

A [Store][spec] is an intermediate-level abstraction that collects Wasm function, global, memory, and table instances
as named entities.
as named entities. It simplifies creating instances, especially when there are a lot of interdependencies.

It simplifies creating instances when there are a lot of interdependencies, by collecting all the

In the simplest case, it allows to register single host functions, globals, memories and tables:
In the simplest case, it allows to register single host functions, globals, memories and tables. For instance, we already saw how to register a `console.log()` host function to the `Store`:

<!--
TODO: should we make this more explicit?
Expand All @@ -25,6 +19,15 @@ import com.dylibso.chicory.runtime.HostFunction;
import com.dylibso.chicory.runtime.ImportValues;
import com.dylibso.chicory.wasm.types.ValueType;

```
-->

```java
import com.dylibso.chicory.runtime.Instance;
import com.dylibso.chicory.runtime.HostFunction;
import com.dylibso.chicory.runtime.Store;
import com.dylibso.chicory.wasm.types.ValueType;

var func = new HostFunction(
"console",
"log",
Expand All @@ -37,50 +40,42 @@ var func = new HostFunction(
println(message);
return null;
});
var hostFunctions = new ImportValues(new HostFunction[] {func});
var instance = Instance.builder(Parser.parse(new File("./logger.wasm"))).withImportValues(hostFunctions).build();
```
-->

```java
import com.dylibso.chicory.runtime.Store;

// instantiate the store
var store = new Store();
// registers `console.log` in the store (see the previous section for the definition of `func`)
// registers `console.log` in the store
store.addFunction(func);
```

However, the store also automatically exposes the exports of a module to the other instances that are registered.
However, the store also automatically exposes the exports of a module to the other instances that are registered. In fact, in the [Host Functions section](host-functions.md), when we created our instance from the `logger.wasm` module, we also passed a string `"logger"`. This is the name of the instance:

```java
// registers the `instance` created earlier (see the previous section) with the name `logger`
store.register("logger", instance);
// now the exported function `logIt` can be imported by other modules as `logger.logIt`
// create a named `instance` with name `logger`
var instance = store.instantiate("logger", Parser.parse(new File("./logger.wasm")));
```

There is also a shorthand method to instantiate a module and register the resulting instance:
Because this instance is now named, now any exports in the `logger` module will be automatically qualified. For instance, the exported function `logIt` will be visible by other modules as `logger.logIt`.

```java
var logger2 = store.instantiate("logger2", Parser.parse(new File("./logger.wasm")));
```
## Notes

This is equivalent to:
- The invocation `store.instantiate("logger", ...)` is in fact equivalent to the lower-level sequence:

```java
var external = store.toImportValues();
var m = Parser.parse(new File("./logger.wasm"));
var instance = Instance.builder(m).withImportValues(external).build();
store.register("logger2", instance);
```
```java
var imports = store.toImportValues();
var m = Parser.parse(new File("./logger.wasm"));
var instance = Instance.builder(m).withImportValues(imports).build();
store.register("logger", instance);
```

However, in most cases we recommend to use the shorthand form.

Notice that registering two instances with the same name results in overwriting the
- Also notice that registering two instances with the same name results in overwriting the
functions, globals, memories, tables with matching names. In this case, the new `logger2.logIt` function
overwrote the old `logger2.logIt` function.

The current `Store` is a mutable object, not meant to be shared (it is not thread-safe).
- The current `Store` is a mutable object, not meant to be shared (it is not thread-safe).

A `Store` _does not_ resolve interdependencies between modules in itself: if your set of modules
- A `Store` _does not_ resolve interdependencies between modules in itself: if your set of modules
have interdependencies, you will have to instantiate and register them in the right order.

[spec]: https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#store%E2%91%A0
Expand Down
8 changes: 1 addition & 7 deletions docs/docs/usage/logging.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
---
id: Logging
sidebar_position: 1
sidebar_label: Logging
---

### Logging
# 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](https://docs.oracle.com/cd/E57471_01/bigData.100/data_processing_bdd/src/rdp_logging_config.html) you can find the possible configurations.
Expand Down
18 changes: 6 additions & 12 deletions docs/docs/usage/memory.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
---
id: Memory usage
sidebar_position: 1
sidebar_label: Memory usage
---

## Using the memory to share data
# Using Memory to Share Data

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
passing pointers. To read, write, or allocate memory in a module, Chicory provides 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:

```bash
Expand Down Expand Up @@ -39,15 +33,15 @@ Instance instance = Instance.builder(Parser.parse(new File("./count_vowels.wasm"
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,
To pass it a string, we first need to write the string into 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:

```java
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`:
Let's allocate Wasm memory for a string and write it into the instance memory. We can do this with `Memory#put`:

```java
import com.dylibso.chicory.runtime.Memory;
Expand All @@ -60,7 +54,7 @@ int ptr = (int) alloc.apply(len)[0];
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
Now we can call `countVowels` with this pointer to the string. It will do its 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:

```java
Expand Down
Loading
Loading