Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sketch Linker implementation. #9

Closed
wants to merge 12 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,14 @@
import com.dylibso.chicory.aot.AotMachine;
import com.dylibso.chicory.runtime.Module;

class ManifestModuleMapper {
private final Manifest manifest;
public class ChicoryModule {
evacchi marked this conversation as resolved.
Show resolved Hide resolved

ManifestModuleMapper(Manifest manifest) {
this.manifest = manifest;
public static Module.Builder builderFrom(ManifestWasm m, Manifest.Options opts) {
Module.Builder mb = fromWasm(m);
return withOptions(mb, opts);
}

Module.Builder toModuleBuilder() {
if (manifest.wasms.length > 1) {
throw new UnsupportedOperationException(
"Manifests of multiple wasm files are not supported yet!");
}
Module.Builder mb = wasmToModuleBuilder(manifest.wasms[0]);
return withOptions(mb, manifest.options);
}

private Module.Builder wasmToModuleBuilder(ManifestWasm m) {
private static Module.Builder fromWasm(ManifestWasm m) {
if (m instanceof ManifestWasmBytes) {
ManifestWasmBytes mwb = (ManifestWasmBytes) m;
return Module.builder(mwb.bytes);
Expand All @@ -37,7 +28,7 @@ private Module.Builder wasmToModuleBuilder(ManifestWasm m) {
}
}

private Module.Builder withOptions(Module.Builder mb, Manifest.Options opts) {
private static Module.Builder withOptions(Module.Builder mb, Manifest.Options opts) {
if (opts == null) {
return mb;
}
Expand All @@ -49,5 +40,4 @@ private Module.Builder withOptions(Module.Builder mb, Manifest.Options opts) {
}
return mb;
}

}
14 changes: 7 additions & 7 deletions src/main/java/org/extism/chicory/sdk/Kernel.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.dylibso.chicory.wasm.types.Value;
import com.dylibso.chicory.wasm.types.ValueType;
import com.dylibso.chicory.runtime.Memory;

import java.util.HashMap;
import java.util.List;

Expand Down Expand Up @@ -39,17 +40,16 @@ public class Kernel {
private final ExportFunction errorGet;
private final ExportFunction memoryBytes;

public Kernel() {
this(new SystemLogger());
}

public Kernel(Logger logger) {
public Kernel(Logger logger, Manifest.Options opts) {
var kernelStream = getClass().getClassLoader().getResourceAsStream("extism-runtime.wasm");

var moduleBuilder = Module.builder(kernelStream).withLogger(logger);

// uncomment for AOT mode
//moduleBuilder = moduleBuilder.withMachineFactory(AotMachine::new);
if (opts != null) {
if (opts.aot) {
moduleBuilder = moduleBuilder.withMachineFactory(AotMachine::new);
}
}

Instance kernel = moduleBuilder.build().instantiate();
memory = kernel.memory();
Expand Down
141 changes: 141 additions & 0 deletions src/main/java/org/extism/chicory/sdk/Linker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package org.extism.chicory.sdk;

import com.dylibso.chicory.log.Logger;
import com.dylibso.chicory.runtime.HostFunction;
import com.dylibso.chicory.runtime.HostImports;
import com.dylibso.chicory.runtime.Instance;
import com.dylibso.chicory.runtime.Module;
import com.dylibso.chicory.wasi.WasiOptions;
import com.dylibso.chicory.wasi.WasiPreview1;

/**
* Links together the modules in the given manifest with the given host functions
* and predefined support modules (e.g. the {@link Kernel}.
* <p>
* Returns a {@link LinkedModules} instance, which in turn can be converted
* into a {@link Plugin}.
*/
class Linker {
public static final String EXTISM_NS = "extism:host/env";
private final Manifest manifest;
private final HostFunction[] hostFunctions;
private final Logger logger;

Linker(Manifest manifest, HostFunction[] hostFunctions, Logger logger) {
this.manifest = manifest;
this.hostFunctions = hostFunctions;
this.logger = logger;
}

/*
* Try to find the main module:
* - There is always one main module
* - If a Wasm value has the Name field set to "main" then use that module
* - If there is only one module in the manifest then that is the main module by default
* - Otherwise the last module listed is the main module
*
*/
public LinkedModules link() {
evacchi marked this conversation as resolved.
Show resolved Hide resolved
WasiPreview1 wasip1 = wasip1();
Kernel kernel = kernel();

HostFunction[] allHostFs = concat(kernel.toHostFunctions(), this.hostFunctions, wasip1.toHostFunctions());
HostImports hostImports = new HostImports(allHostFs);


ManifestWasm[] wasms = this.manifest.wasms;
Module.Builder[] moduleBuilders = new Module.Builder[wasms.length];
int mainModule = -1;

for (int i = 0; i < wasms.length; i++) {
ManifestWasm wasm = wasms[i];
boolean isLast = i == wasms.length - 1;
String moduleName = wasm.name;
Module.Builder mb = ChicoryModule.builderFrom(wasm, this.manifest.options)
.withLogger(logger)
.withHostImports(hostImports);

if ((moduleName == null || moduleName.isEmpty() || isLast) && mainModule < 0) {
moduleName = "main";
mainModule = i;
}

checkCollision(moduleName, wasms);
checkHash(moduleName, wasm);

moduleBuilders[i] = mb;
}

return new LinkedModules(kernel, moduleBuilders, mainModule);
}

private WasiPreview1 wasip1() {
var options = WasiOptions.builder().build();
var wasi = new WasiPreview1(logger, options);
return wasi;
}

private Kernel kernel() {
var kernel = new Kernel(logger, manifest.options);
return kernel;
}

private static HostFunction[] concat(
HostFunction[] kernelFuncs, HostFunction[] hostFunctions, HostFunction[] wasiHostFunctions) {
// concat list of host functions
var hostFuncList = new HostFunction[hostFunctions.length + kernelFuncs.length + wasiHostFunctions.length];
System.arraycopy(kernelFuncs, 0, hostFuncList, 0, kernelFuncs.length);
System.arraycopy(hostFunctions, 0, hostFuncList, kernelFuncs.length, hostFunctions.length);
System.arraycopy(wasiHostFunctions, 0, hostFuncList, kernelFuncs.length + hostFunctions.length, wasiHostFunctions.length);
return hostFuncList;
}


/**
* @throws ExtismException on name collision.
*/
private void checkCollision(String moduleName, ManifestWasm[] wasms) {
// FIXME: check both host imports and modules.
if (moduleName.equals(EXTISM_NS)) {
throw new ExtismException(String.format("Module name collision: %s", moduleName));
}

// FIXME: check collision on already processed modules
}

private void checkHash(String moduleName, ManifestWasm wasm) {
// FIXME: add hash check.
}
evacchi marked this conversation as resolved.
Show resolved Hide resolved


}

/**
* A collection of modules that have been successfully linked together.
* LinkedModules can be converted to a Plugin using the {@link #toPlugin()}
* method.
*/
class LinkedModules {

private final Kernel kernel;
private final Module.Builder[] moduleBuilders;
private final int mainModule;

LinkedModules(Kernel kernel, Module.Builder[] moduleBuilders, int mainModule) {
this.kernel = kernel;
this.moduleBuilders = moduleBuilders;
this.mainModule = mainModule;
}

public Plugin toPlugin() {
Instance[] instances = new Instance[moduleBuilders.length];
Module.Builder[] builders = this.moduleBuilders;
for (int i = 0; i < builders.length; i++) {
Module.Builder mb = builders[i];
Module m = mb.build();
instances[i] = m.instantiate();
}

return new Plugin(kernel, instances, mainModule);
}
}
68 changes: 23 additions & 45 deletions src/main/java/org/extism/chicory/sdk/Plugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@
import com.dylibso.chicory.log.Logger;
import com.dylibso.chicory.log.SystemLogger;
import com.dylibso.chicory.runtime.HostFunction;
import com.dylibso.chicory.runtime.HostImports;
import com.dylibso.chicory.runtime.Instance;
import com.dylibso.chicory.wasi.WasiOptions;
import com.dylibso.chicory.wasi.WasiPreview1;

/**
* A Plugin instance.
*
* Plugins can be instantiated using a {@link Plugin.Builder}, returned
* by {@link Plugin#ofManifest(Manifest)}. The Builder allows to set options
* on the Plugin, such as {@link HostFunction}s and the {@link Logger}.
*
*/
public class Plugin {

public static Builder ofManifest(Manifest manifest) {
return new Builder(manifest);
}
Expand All @@ -34,55 +40,27 @@ public Builder withLogger(Logger logger) {
}

public Plugin build() {
return new Plugin(manifest, hostFunctions, logger);
var logger = this.logger == null ? new SystemLogger() : this.logger;
Linker linker = new Linker(this.manifest, this.hostFunctions, logger);
LinkedModules linked = linker.link();
return linked.toPlugin();
}
}

private final Manifest manifest;
private final Instance instance;
private final HostImports imports;
private final Kernel kernel;

private Plugin(Manifest manifest) {
this(manifest, new HostFunction[]{}, null);
}

private Plugin(Manifest manifest, HostFunction[] hostFunctions, Logger logger) {
if (logger == null) {
logger = new SystemLogger();
}

this.kernel = new Kernel(logger);
this.manifest = manifest;

// TODO: Expand WASI Support here
var options = WasiOptions.builder().build();
var wasi = new WasiPreview1(logger, options);
var wasiHostFunctions = wasi.toHostFunctions();

var hostFuncList = getHostFunctions(kernel.toHostFunctions(), hostFunctions, wasiHostFunctions);
this.imports = new HostImports(hostFuncList);

var moduleBuilder = new ManifestModuleMapper(manifest)
.toModuleBuilder()
.withLogger(logger)
.withHostImports(imports);

this.instance = moduleBuilder.build().instantiate();
}

private static HostFunction[] getHostFunctions(
HostFunction[] kernelFuncs, HostFunction[] hostFunctions, HostFunction[] wasiHostFunctions) {
// concat list of host functions
var hostFuncList = new HostFunction[hostFunctions.length + kernelFuncs.length + wasiHostFunctions.length];
System.arraycopy(kernelFuncs, 0, hostFuncList, 0, kernelFuncs.length);
System.arraycopy(hostFunctions, 0, hostFuncList, kernelFuncs.length, hostFunctions.length);
System.arraycopy(wasiHostFunctions, 0, hostFuncList, kernelFuncs.length + hostFunctions.length, wasiHostFunctions.length);
return hostFuncList;
private final Instance[] instances;
private final int mainModule;
private final Instance mainInstance;

Plugin(Kernel kernel, Instance[] instances, int mainModule) {
this.kernel = kernel;
this.instances = instances;
this.mainModule = mainModule;
this.mainInstance = instances[mainModule];
}

public byte[] call(String funcName, byte[] input) {
var func = instance.export(funcName);
var func = mainInstance.export(funcName);
kernel.setInput(input);
var result = func.apply()[0].asInt();
if (result == 0) {
Expand Down