Skip to content

Commit

Permalink
Add wasmer web assembly engine as POC
Browse files Browse the repository at this point in the history
This adds the wasmer-d submodule and an engine which can run in d at
runtime. Currently the Engine is only run in the unit tests in
`WasmEngine.d` to experiment with running Web Assembly programs.
`dtest=WasmEngine dub test`
  • Loading branch information
hewison-chris committed Apr 12, 2022
1 parent c4769ec commit b5cfa4d
Show file tree
Hide file tree
Showing 10 changed files with 222 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/documentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ jobs:
sudo apt-get install libsodium-dev
sudo apt-get install clang
- name: Install Wasmer
run: |
curl https://get.wasmer.io -sSfL | WASMER_DIR=${HOME}/.wasmer sh
echo 'WASMER_DIR=${HOME}/.wasmer' >> $GITHUB_ENV
- name: Build documentation
run: |
AGORA_VERSION=HEAD dub build -b ddox
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ jobs:
sudo apt-get update
sudo apt-get install libsodium-dev libsqlite3-dev clang
- name: Install Wasmer
run: |
curl https://get.wasmer.io -sSfL | WASMER_DIR=${HOME}/.wasmer sh
echo 'WASMER_DIR=${HOME}/.wasmer' >> $GITHUB_ENV
- name: Checkout faucet
uses: actions/checkout@v2
with:
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,17 @@ jobs:
brew install coreutils libsodium pkg-config
echo "LIBRARY_PATH=${LD_LIBRARY_PATH-}:/usr/local/lib/" >> $GITHUB_ENV
echo "PKG_CONFIG_PATH=/usr/local/opt/sqlite/lib/pkgconfig:/usr/local/opt/[email protected]/lib/pkgconfig/" >> $GITHUB_ENV
env | grep HOME
curl https://get.wasmer.io -sSfL | WASMER_DIR=${HOME}/.wasmer sh
echo 'WASMER_DIR=${HOME}/.wasmer' >> $GITHUB_ENV
- name: '[Linux] Install dependencies & setup environment'
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install libsodium-dev libsqlite3-dev clang
curl https://get.wasmer.io -sSfL | WASMER_DIR=${HOME}/.wasmer sh
echo 'WASMER_DIR=${HOME}/.wasmer' >> $GITHUB_ENV
- name: '[Windows] Install dependencies & setup environment'
if: runner.os == 'Windows'
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,6 @@
[submodule "tracyd"]
path = submodules/tracyd
url = https://github.com/bosagora/tracyd.git
[submodule "wasmer-d"]
path = submodules/wasmer-d
url = https://github.com/chances/wasmer-d.git
8 changes: 8 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ ARG AGORA_VERSION="HEAD"
ADD . /root/agora/
WORKDIR /root/agora/talos/
RUN if [ -z ${AGORA_STANDALONE+x} ]; then npm ci && npm run build; else mkdir -p build; fi
WORKDIR /
RUN wget https://github.com/wasmerio/wasmer/releases/download/2.1.1/wasmer-linux-musl-amd64.tar.gz
RUN tar -zxvf wasmer-linux-musl-amd64.tar.gz
RUN wasmer -V
WORKDIR /root/agora/
# Build Agora
RUN AGORA_VERSION=${AGORA_VERSION} dub build --skip-registry=all --compiler=ldc2 ${DUB_OPTIONS}
Expand All @@ -20,6 +24,10 @@ RUN if [ -z ${AGORA_STANDALONE+x} ]; then dub build --skip-registry=all --compil
# Uses edge as we need the same `ldc-runtime` as the LDC that compiled Agora,
# and `bosagora/agora-builder:latest` uses edge.
FROM alpine:3.15
WORKDIR /
RUN wget https://github.com/wasmerio/wasmer/releases/download/2.1.1/wasmer-linux-musl-amd64.tar.gz
RUN tar -zxvf wasmer-linux-musl-amd64.tar.gz
RUN wasmer -V
# The following makes debugging Agora much easier on server
# Since it's a tiny configuration file read by GDB at init, it won't affect release build
COPY devel/dotgdbinit /root/.gdbinit
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Additionally, the following are dependencies:
- `openssl`: Binary (to detect the version) and development library
- `sqlite3`: Development library
- `zlib`: Development library
- `wasmer`: Web Assembly Engine - [Install](https://github.com/wasmerio/wasmer-install#readme)

Additionally, on OSX, `PKG_CONFIG_PATH` needs to be properly set to pick up `sqlite3` and `openssl`.
Provided you installed those packages via `brew`, you can run the following command prior to building:
Expand Down
3 changes: 2 additions & 1 deletion dub.json
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@
"localrest": { "version": "*", "dflags" : [ "-preview=in", "-revert=dtorfields" ] },
"serialization": { "version": "*", "dflags" : [ "-preview=in", "-revert=dtorfields" ] },
"vibe-d": { "version": "*", "dflags" : [ "-preview=in", "-revert=dtorfields" ] },
"my-ip": { "version": "*", "dflags" : [ "-preview=in", "-revert=dtorfields" ] }
"my-ip": { "version": "*", "dflags" : [ "-preview=in", "-revert=dtorfields" ] },
"wasmer": { "version": "*", "dflags" : [ "-preview=in", "-revert=dtorfields" ] }
}
}
3 changes: 2 additions & 1 deletion dub.selections.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"vibe-core": {"path":"submodules/vibe-core/"},
"vibe-d": {"path":"submodules/vibe.d/"},
"my-ip": {"path":"submodules/my-ip/"},
"tracyd": {"path":"submodules/tracyd/"}
"tracyd": {"path":"submodules/tracyd/"},
"wasmer": {"path":"submodules/wasmer-d/"}
}
}
190 changes: 190 additions & 0 deletions source/agora/node/WasmEngine.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*******************************************************************************
Contains the wasmer WebAssembly engine to be used for Trust Contracts.
The submodule `wasmer-d` provides a `D` wrapper of the `wamser` runtime.
(https://chances.github.io/wasmer-d/wasmer.html)
Copyright:
Copyright (c) 2021 BOS Platform Foundation Korea
All rights reserved.
License:
MIT License. See LICENSE for details.
*******************************************************************************/

module agora.node.WasmEngine;

import agora.consensus.state.Ledger;
import agora.utils.Log;

import wasmer;

mixin AddLogger!();

/// Ditto
public class WasmEngine
{
/// The JIT engine for executing WebAssembly Programs (WASM)
Engine engine;

/// The global state that can be manipulated by WebAssembly programs
/// https://webassembly.github.io/spec/core/exec/runtime.html#syntax-store
Store store;

Ledger ledger;

public void start (ref Ledger ledger)
{
log.info("Starting WasmEngine");
engine = new Engine();
store = new Store(engine);
this.ledger = ledger;
log.info("Started WasmEngine");
}

public void stop ()
{
log.info("Stopping WasmEngine");
destroy(store);
destroy(engine);
log.info("Stopped WasmEngine");
}

public void exampleTime ()
{
log.info("Running {} using WasmEngine", __FUNCTION__);
const wat_callback_module_time = "(module
(type $FUNCSIG$ii (func (param i32) (result i32)))
(import \"env\" \"time\" (func $time (param i32) (result i32)))
(table 0 anyfunc)
(memory $0 1)
(export \"memory\" (memory $0))
(export \"main\" (func $main))
(func $main (; 1 ;) (result i32)
(call $time
(i32.const 0)
)
)
)";
log.trace("wat (text representation of wasm)");
log.trace("==============================");
log.trace("{}", wat_callback_module_time);
log.trace("==============================");
auto module_ = Module.from(store, wat_callback_module_time);
assert(module_.valid, "Error compiling module!");

auto print = (Module module_, int value)
{
return value;
};
auto imports = [new Function(store, module_, print.toDelegate).asExtern];
auto instance = module_.instantiate(imports);
assert(instance.valid, "Could not instantiate module!");
auto runFunc = Function.from(instance.exports[1]);
assert(instance.exports[1].name == "main", "Failed to get the `main` function!");
assert(runFunc.valid, "`main` function invalid!");
Value[] results;
assert(runFunc.call(results), "Error calling the `main` function!");
assert(results.length == 1, "length of results should be 1!");
// assert(results[0].value.of.i32 > 0, "time should be greater than 0");
log.info("Used web assembly engine to get time. Result = {}", results[0].value.of.i32);
destroy(instance);
destroy(module_);
}


public void exampleAdd ()
{
const wat_callback_module_add = `
(module
(func $myFunc (import "" "myFunc") (result i64))
(func (export "run") (param $x i32) (param $y i32) (result i64)
(call $myFunc)
return
)
)`;
log.info("Running {} using WasmEngine", __FUNCTION__);
log.trace("wat (text representation of wasm)");
log.trace("==============================");
log.trace("{}", wat_callback_module_add);
log.trace("==============================");

auto module_ = Module.from(store, wat_callback_module_add);
assert(module_.valid, "Error compiling module!");

// This is for addFromWasm
// auto funcType = wasm_functype_new_2_1(wasm_valtype_new_i32(),
// wasm_valtype_new_i32(), wasm_valtype_new_i32());
auto funcType = wasm_functype_new_0_1(wasm_valtype_new_i64());

//auto imports = [new Function(store, funcType, &addFromWasm).asExtern];
auto imports = [new Function(store, funcType, &getLedgerValidators, cast(void*) this.ledger).asExtern];
auto instance = module_.instantiate(imports);
assert(instance.valid, "Could not instantiate module!");
auto runFunc = Function.from(instance.exports[0]);
assert(instance.exports[0].name == "run" && runFunc.valid, "Failed to get the `run` function!");

//{
// Here we run the "main" function called "run"
auto three = new Value(3);
auto four = new Value(4);
Value[] results;
assert(runFunc.call([three, four], results), "Error calling the `run` function!");
assert(results.length == 1);
//assert(results[0].value.of.i32 == 7);
log.info("Used web assembly engine to add 3 and 4. Result = {}", results[0].value.of.i32);
//}

// Cleanup
destroy(three);
destroy(four);
destroy(instance);
destroy(module_);
}
}

unittest
{
import std.stdio;
import std.format;

Log.root.level(LogLevel.Trace, true);
auto output = stdout.lockingTextWriter();
try
{
auto wasmEngine = new WasmEngine();
Ledger ledger = new Ledger();
wasmEngine.start(ledger);
wasmEngine.exampleAdd();
//wasmEngine.exampleTime();
wasmEngine.stop();
}
catch (Exception e)
{
output.formattedWrite("%s", e);
}
// print logs of the work thread
CircularAppender!()().print(output);
}

extern(C) wasm_trap_t* addFromWasm (const wasm_val_vec_t* args, wasm_val_vec_t* results)
{
//int addFromWasm (int a, int b)
int a = args.data[0].of.i32;
int b = args.data[1].of.i32;
results.data[0].kind = WASM_I32;
results.data[0].of.i32 = (a + b);
log.info("addFromWasm called with: {} + {}", a, b);
return null;
}

extern(C) wasm_trap_t* getLedgerValidators (void* env, const wasm_val_vec_t* args, wasm_val_vec_t* results)
{
log.info("getLedgerValidators called");

Ledger ledger = cast(Ledger) env;
results.data[0].kind = WASM_I64;
results.data[0].of.i64 = ledger.getValidators(ledger.height() + 1).length;
return null;
}
1 change: 1 addition & 0 deletions submodules/wasmer-d
Submodule wasmer-d added at c94c72

0 comments on commit b5cfa4d

Please sign in to comment.