Skip to content

Commit

Permalink
First working Go example for issue #14
Browse files Browse the repository at this point in the history
  • Loading branch information
cretz committed Oct 4, 2018
1 parent b079b4a commit 890cfd8
Show file tree
Hide file tree
Showing 9 changed files with 208 additions and 6 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
/examples/c-simple/build
/examples/go-simple/bin
/examples/go-simple/build
/examples/go-util/bin
/examples/go-util/build
/examples/rust-simple/Cargo.lock
/examples/rust-simple/bin
/examples/rust-simple/build
Expand Down
6 changes: 5 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ project(':examples') {
// args 'help', 'compile'
def outFile = 'build/wasm-classes/' + wasmCompiledClassName.replace('.', '/') + '.class'
file(outFile).parentFile.mkdirs()
args 'compile', 'build/lib.wasm', wasmCompiledClassName, '-out', outFile, '-log', 'debug'
args 'compile', 'build/lib.wasm', wasmCompiledClassName, '-out', outFile
}
}

Expand Down Expand Up @@ -189,6 +189,7 @@ project(':examples:go-simple') {
apply plugin: 'application'
ext.wasmCompiledClassName = 'asmble.generated.GoSimple'
dependencies {
compile project(':examples:go-util')
compile files('build/wasm-classes')
}
compileJava {
Expand All @@ -197,6 +198,9 @@ project(':examples:go-simple') {
mainClassName = 'asmble.examples.gosimple.Main'
}

project(':examples:go-util') {
}

project(':examples:rust-regex') {
apply plugin: 'application'
apply plugin: 'me.champeau.gradle.jmh'
Expand Down
6 changes: 6 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

Below are some examples.

### Go

Compile Go to WASM and then to the JVM. In order of complexity:

* [go-simple](go-simple)

### Rust

Compile Rust to WASM and then to the JVM. In order of complexity:
Expand Down
11 changes: 11 additions & 0 deletions examples/go-simple/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
### Example: Go Simple

This runs a simple "Hello, World" in Go. To execute, from the base of the repo run:

path/to/gradle :examples:go-simple:run

This will create a WASM file in `build/lib.wasm`. Then it will compile it to a JVM class named
`asmble.generated.GoSimple`. Finally it will execute `asmble.examples.gosimple.Main::main` which, with some help from
[go-util](../go-util), will run it. Output is:

Hello, World
6 changes: 1 addition & 5 deletions examples/go-simple/simple.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
package main

import (
"fmt"
)

func main() {
fmt.Println("Hello, World!")
println("Hello, World!")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package asmble.examples.gosimple;

import asmble.examples.goutil.Executor;
import asmble.generated.GoSimple;

public class Main {
public static void main(String[] args) {
Integer exitCode = new Executor<>(GoSimple::new).run(GoSimple::run, "test-app");
if (exitCode == null) throw new IllegalStateException("Did not get exit code");
if (exitCode != 0) throw new IllegalStateException("Expected exit code 0, got: " + exitCode);
}
}
4 changes: 4 additions & 0 deletions examples/go-util/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
### goutil

This is helper code for other examples. Specifically it contains `asmble.examples.goutil.Executor` which helps bootstrap
and provide runtime for Go code.
166 changes: 166 additions & 0 deletions examples/go-util/src/main/java/asmble/examples/goutil/Executor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package asmble.examples.goutil;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.time.Instant;
import java.util.*;
import java.util.function.IntConsumer;

public class Executor<T> {
@FunctionalInterface
public interface ConstructorNoJs<T> {
T create(ByteBuffer mem, MethodHandle debug, MethodHandle runtimeWasmExit, MethodHandle runtimeWasmWrite,
MethodHandle runtimeNanotime, MethodHandle runtimeWalltime, MethodHandle runtimeScheduleCallback,
MethodHandle runtimeClearScheduledCallback, MethodHandle runtimeGetRandomData);
}

@FunctionalInterface
public interface Run<T> {
void run(T instance, int argc, int argv);
}

protected static final int PAGE_SIZE = 65536;

public Random random = new SecureRandom();
public OutputStream out = System.out;

// Always little endian
protected final ByteBuffer mem;
protected final T instance;
protected IntConsumer debug;
protected Integer exitCode;

public Executor(ConstructorNoJs<T> constructor) {
this(16384, constructor);
}

public Executor(int maxMemPages, ConstructorNoJs<T> constructor) {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType singleIntParam = MethodType.methodType(Void.TYPE, Integer.TYPE);
try {
// Set to little endian by the constructor, not us
mem = ByteBuffer.allocateDirect(maxMemPages * PAGE_SIZE);
instance = constructor.create(
mem,
lookup.bind(this, "debug", singleIntParam),
lookup.bind(this, "runtimeWasmExit", singleIntParam),
lookup.bind(this, "runtimeWasmWrite", singleIntParam),
lookup.bind(this, "runtimeNanotime", singleIntParam),
lookup.bind(this, "runtimeWalltime", singleIntParam),
lookup.bind(this, "runtimeScheduleCallback", singleIntParam),
lookup.bind(this, "runtimeClearScheduledCallback", singleIntParam),
lookup.bind(this, "runtimeGetRandomData", singleIntParam)
);
} catch (NoSuchMethodException|IllegalAccessException e) {
throw new RuntimeException(e);
}
}

public Integer run(Run<T> run, String... args) {
return run(run, args, Collections.emptyMap());
}

public Integer run(Run<T> run, String[] args, Map<String, String> env) {
// Argc + argv, then env appended to argv as key=val
int offset = 4096;
int argc = args.length;
List<Integer> strPtrs = new ArrayList<>();
for (String arg : args) {
strPtrs.add(offset);
offset += newString(arg, offset);
}
for (Map.Entry<String, String> var : env.entrySet()) {
strPtrs.add(offset);
offset += newString(var.getKey() + "=" + var.getValue(), offset);
}
int argv = offset;
for (int strPtr : strPtrs) {
mem.putLong(offset, strPtr);
offset += 8;
}
// Run and return exit code
run.run(instance, argc, argv);
return exitCode;
}

// Returns size, aligned to 8
protected int newString(String str, int ptr) {
byte[] bytes = (str + '\0').getBytes(StandardCharsets.UTF_8);
putBytes(ptr, bytes);
return bytes.length + (8 - (bytes.length % 8));
}

protected byte[] getBytes(int offset, byte[] bytes) {
ByteBuffer buf = mem.duplicate();
buf.position(offset);
buf.get(bytes);
return bytes;
}

protected void putBytes(int offset, byte[] bytes) {
ByteBuffer buf = mem.duplicate();
buf.position(offset);
buf.put(bytes);
}

protected void debug(int v) {
if (debug != null) debug.accept(v);
}

protected void runtimeWasmExit(int sp) {
exitCode = mem.getInt(sp + 8);
}

protected void wasmExit(int exitCode) {
this.exitCode = exitCode;
}

protected void runtimeWasmWrite(int sp) {
long fd = mem.getLong(sp + 8);
long ptr = mem.getLong(sp + 16);
int len = mem.getInt(sp + 24);
wasmWrite(fd, getBytes((int) ptr, new byte[len]));
}

protected void wasmWrite(long fd, byte[] bytes) {
if (fd != 2) throw new UnsupportedOperationException("Only fd 2 support on write, got " + fd);
if (out != null) {
try {
out.write(bytes);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

protected void runtimeNanotime(int sp) {
mem.putLong(sp + 8, System.nanoTime());
}

protected void runtimeWalltime(int sp) {
Instant now = Instant.now();
mem.putLong(sp + 8, now.getEpochSecond());
mem.putInt(sp + 16, now.getNano());
}

protected void runtimeScheduleCallback(int sp) {
throw new UnsupportedOperationException("runtime.scheduleCallback");
}

protected void runtimeClearScheduledCallback(int sp) {
throw new UnsupportedOperationException("runtime.clearScheduledCallback");
}

protected void runtimeGetRandomData(int sp) {
long len = mem.getLong(sp + 16);
byte[] bytes = new byte[(int) len];
random.nextBytes(bytes);
putBytes((int) mem.getLong(sp + 8), bytes);
}
}
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ include 'annotations',
'compiler',
'examples:c-simple',
'examples:go-simple',
'examples:go-util',
'examples:rust-regex',
'examples:rust-simple',
'examples:rust-string'

0 comments on commit 890cfd8

Please sign in to comment.