Use js marine runtime instead of wasm-bindgen (#209)

This commit is contained in:
Valery Antopol 2022-02-18 19:04:40 +03:00 committed by GitHub
parent 78a2bc9d58
commit 39da1c98bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 72 additions and 10936 deletions

View File

@ -1,43 +0,0 @@
name: Run tests for avm client (js)
defaults:
run:
working-directory: ./avm/client
on:
push:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x, 15.x]
steps:
- uses: actions/checkout@v2
- uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-v1-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-v1-node-${{ matrix.node-version }}
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
continue-on-error: true
- run: ./build_wasm.sh
- run: npm install
- name: test
run: npm run test
env:
CI: true

View File

@ -97,34 +97,6 @@ jobs:
echo "FINAL_VERSION=$MAX_VERSION" | tee -a $GITHUB_ENV
echo "JS_PKG_NAME=$JS_PKG_NAME" | tee -a $GITHUB_ENV
### === JavaScript package release ===
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
continue-on-error: true
- name: Build npm package
working-directory: avm/client
run: |
./build_wasm.sh
npm i
npm run build
### Set version
- name: Set version to ${{ env.FINAL_VERSION }}
run: yarn version --new-version ${{ env.FINAL_VERSION }} --no-git-tag-version
working-directory: avm/client
### Publish to NPM registry
- uses: actions/setup-node@v1
with:
node-version: "14"
registry-url: "https://registry.npmjs.org"
- run: npm publish --access public
working-directory: avm/client
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
### === Rust package release ===
- name: Install marine
run: cargo install marine || true
@ -175,3 +147,31 @@ jobs:
- [${{ env.FINAL_VERSION }} @ crates.io](https://crates.io/crates/${{ env.PKG_NAME }}/${{ env.FINAL_VERSION }})
draft: false
prerelease: false
### === JavaScript package release ===
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
continue-on-error: true
- name: Build npm package
working-directory: avm/client
run: |
./build_wasm.sh
npm i
npm run build
### Set version
- name: Set version to ${{ env.FINAL_VERSION }}
run: yarn version --new-version ${{ env.FINAL_VERSION }} --no-git-tag-version
working-directory: avm/client
### Publish to NPM registry
- uses: actions/setup-node@v1
with:
node-version: "14"
registry-url: "https://registry.npmjs.org"
- run: npm publish --access public
working-directory: avm/client
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@ -121,33 +121,6 @@ jobs:
echo "PKG_NAME=$PKG_NAME" | tee -a $GITHUB_ENV
echo "JS_PKG_NAME=$JS_PKG_NAME" | tee -a $GITHUB_ENV
### === JavaScript package release ===
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
continue-on-error: true
- name: Build npm package
working-directory: avm/client
run: |
./build_wasm.sh
npm i
npm run build
### Set version to FINAL_VERSION
- run: yarn version --new-version ${{ env.FINAL_VERSION }} --no-git-tag-version || true
working-directory: avm/client
### Publish to NPM registry
- uses: actions/setup-node@v1
with:
node-version: "14"
registry-url: "https://registry.npmjs.org"
- run: npm publish --access public --tag=beta
working-directory: avm/client
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
### === Rust package release ===
- name: Install marine
run: cargo install marine || true
@ -186,3 +159,30 @@ jobs:
- name: Publish to crates.io
run: cargo publish --allow-dirty
working-directory: crates/interpreter-wasm
### === JavaScript package release ===
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
continue-on-error: true
- name: Build npm package
working-directory: avm/client
run: |
./build_wasm.sh
npm i
npm run build
### Set version to FINAL_VERSION
- run: yarn version --new-version ${{ env.FINAL_VERSION }} --no-git-tag-version || true
working-directory: avm/client
### Publish to NPM registry
- uses: actions/setup-node@v1
with:
node-version: "14"
registry-url: "https://registry.npmjs.org"
- run: npm publish --access public --tag=beta
working-directory: avm/client
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@ -1,7 +0,0 @@
New-Item -ItemType Directory -Force -Path ./wasm
wasm-pack build ../../air-interpreter --no-typescript --release -d ../avm/client/wasm
New-Item -ItemType Directory -Force -Path ./dist
cp wasm/air_interpreter_client_bg.wasm dist/avm.wasm
cp dist/avm.wasm src/__test__/

View File

@ -1,14 +1,9 @@
#!/bin/sh
## requires wasm-pack
## > curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
(
cd ../..;
mkdir -p ./avm/client/wasm || exit;
wasm-pack build ./air-interpreter --no-typescript --release -d ../avm/client/wasm
cd ../../air-interpreter;
marine build --release --features marine
)
mkdir -p ./dist/
cp wasm/air_interpreter_client_bg.wasm dist/avm.wasm
cp dist/avm.wasm src/__test__/
cp ../../target/wasm32-wasi/release/air_interpreter_server.wasm ./dist/avm.wasm

View File

@ -1,5 +0,0 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testPathIgnorePatterns: ['dist'],
};

File diff suppressed because it is too large Load Diff

View File

@ -2,8 +2,7 @@
"name": "@fluencelabs/avm",
"description": "Aquamarine VM",
"version": "0.0.0",
"main": "./dist/index.js",
"typings": "./dist/index.d.ts",
"main": "./dist/avm.wasm",
"repository": "https://github.com/fluencelabs/air",
"author": "Fluence Labs",
"license": "Apache 2.0",
@ -14,16 +13,12 @@
"copy-avm": "./dist/copyAvm.js"
},
"scripts": {
"build": "tsc",
"test": "jest"
"build": "tsc && ./build_wasm.sh"
},
"private": false,
"dependencies": {},
"devDependencies": {
"@types/jest": "^26.0.23",
"@types/node": "^14.0.0",
"jest": "^27.2.4",
"ts-jest": "^27.0.5",
"typescript": "^4.0.0"
"typescript": "^4.0.0",
"@types/node": "^14.0.0"
}
}

View File

@ -1,40 +0,0 @@
import { AirInterpreter } from '..';
import { readFileSync } from 'fs';
import path from 'path';
const vmPeerId = '12D3KooWNzutuy8WHXDKFqFsATvCR6j9cj2FijYbnd47geRKaQZS';
const createTestInterpreter = async () => {
const file = readFileSync(path.resolve(__dirname, './avm.wasm'));
const module = await WebAssembly.compile(file);
return AirInterpreter.create(module, 'off', (level, message) => {
console.log(`level: ${level}, message=${message}`);
});
};
const b = (s: string) => {
return Buffer.from(s);
};
describe('Tests', () => {
it('should work', async () => {
// arrange
const i = await createTestInterpreter();
const s = `(seq
(par
(call "${vmPeerId}" ("local_service_id" "local_fn_name") [] result_1)
(call "remote_peer_id" ("service_id" "fn_name") [] g)
)
(call "${vmPeerId}" ("local_service_id" "local_fn_name") [] result_2)
)`;
// act
const params = { initPeerId: vmPeerId, currentPeerId: vmPeerId };
const res = i.invoke(s, b(''), b(''), params, []);
// assert
console.log(res);
expect(res).not.toBeUndefined();
});
});

View File

@ -1,270 +0,0 @@
/*
* Copyright 2021 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { getStringFromWasm0, invoke } from './wrapper';
export type LogLevel = 'info' | 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'off';
export type LogFunction = (level: LogLevel, message: string) => void;
export interface CallServiceResult {
retCode: number;
result: string;
}
export interface CallRequest {
serviceId: string;
functionName: string;
arguments: any[];
tetraplets: SecurityTetraplet[][];
}
export type CallRequestsArray = Array<[key: number, callRequest: CallRequest]>;
export type CallResultsArray = Array<[key: number, callServiceResult: CallServiceResult]>;
export interface InterpreterResult {
retCode: number;
errorMessage: string;
data: Uint8Array;
nextPeerPks: Array<string>;
callRequests: CallRequestsArray;
}
export interface ResolvedTriplet {
peer_pk: string;
service_id: string;
function_name: string;
}
export interface SecurityTetraplet extends ResolvedTriplet {
json_path: string;
}
type Exports = any;
type Instance = any;
type ExportValue = any;
type LogImport = {
log_utf8_string: (level: any, target: any, offset: any, size: any) => void;
};
type ImportObject = {
host: LogImport;
};
class HostImportsConfig {
exports: Exports | undefined;
newImportObject: () => ImportObject;
constructor(create: (cfg: HostImportsConfig) => ImportObject) {
this.exports = undefined;
this.newImportObject = () => create(this);
}
setExports(exports: Exports) {
this.exports = exports;
}
}
/**
* Instantiates WebAssembly runtime with AIR interpreter module
*/
async function interpreterInstance(
module: WebAssembly.Module,
cfg: HostImportsConfig,
logFunction: LogFunction,
): Promise<Instance> {
// create host imports that use module exports internally
let imports = cfg.newImportObject();
// instantiate interpreter
let interpreter_module = module;
let instance: Instance = await WebAssembly.instantiate(interpreter_module, imports);
// set exports, so host imports can use them
cfg.setExports(instance.exports);
// trigger interpreter initialization (i.e., call main function)
call_export(instance.exports.main, logFunction);
return instance;
}
/**
* If export is a function, call it. Otherwise log a warning.
* NOTE: any here is unavoidable, see Function interface definition
*/
function call_export(f: ExportValue, logFunction: LogFunction): any {
if (typeof f === 'function') {
return f();
} else {
logFunction('error', `can't call export ${f}: it is not a function, but ${typeof f}`);
}
}
function log_import(cfg: HostImportsConfig, logFunction: LogFunction): LogImport {
return {
log_utf8_string: (level: any, target: any, offset: any, size: any) => {
let wasm = cfg.exports;
try {
let str = getStringFromWasm0(wasm, offset, size);
let levelStr: LogLevel;
switch (level) {
case 1:
levelStr = 'error';
break;
case 2:
levelStr = 'warn';
break;
case 3:
levelStr = 'info';
break;
case 4:
levelStr = 'debug';
break;
case 6:
levelStr = 'trace';
break;
default:
return;
}
logFunction(levelStr, str);
} finally {
}
},
};
}
/**
* Returns import object that describes host functions called by AIR interpreter
*/
function newImportObject(cfg: HostImportsConfig, logFunction: LogFunction): ImportObject {
return {
host: log_import(cfg, logFunction),
};
}
const decoder = new TextDecoder();
const encoder = new TextEncoder();
export class AirInterpreter {
private wasmWrapper;
private logLevel: LogLevel;
constructor(wasmWrapper) {
this.wasmWrapper = wasmWrapper;
}
static async create(module: WebAssembly.Module, logLevel: LogLevel, logFunction: LogFunction) {
const cfg = new HostImportsConfig((cfg) => {
return newImportObject(cfg, logFunction);
});
const instance = await interpreterInstance(module, cfg, logFunction);
const res = new AirInterpreter(instance);
res.logLevel = logLevel;
return res;
}
invoke(
air: string,
prevData: Uint8Array,
data: Uint8Array,
params: { initPeerId: string; currentPeerId: string },
callResults: CallResultsArray,
): InterpreterResult {
const callResultsToPass: any = {};
for (let [k, v] of callResults) {
callResultsToPass[k] = {
ret_code: v.retCode,
result: v.result,
};
}
const paramsToPass = encoder.encode(
JSON.stringify({
init_peer_id: params.initPeerId,
current_peer_id: params.currentPeerId,
}),
);
const rawResult = invoke(
// force new line
this.wasmWrapper.exports,
air,
prevData,
data,
paramsToPass,
encoder.encode(JSON.stringify(callResultsToPass)),
this.logLevel,
);
let result: any;
try {
result = JSON.parse(rawResult);
} catch (ex) {}
const callRequestsStr = decoder.decode(new Uint8Array(result.call_requests));
let parsedCallRequests;
try {
if (callRequestsStr.length === 0) {
parsedCallRequests = {};
} else {
parsedCallRequests = JSON.parse(callRequestsStr);
}
} catch (e) {
throw "Couldn't parse call requests: " + e + '. Original string is: ' + callRequestsStr;
}
let resultCallRequests: Array<[key: number, callRequest: CallRequest]> = [];
for (const k in parsedCallRequests) {
const v = parsedCallRequests[k];
let arguments_;
let tetraplets;
try {
arguments_ = JSON.parse(v.arguments);
} catch (e) {
throw "Couldn't parse arguments: " + e + '. Original string is: ' + arguments_;
}
try {
tetraplets = JSON.parse(v.tetraplets);
} catch (e) {
throw "Couldn't parse tetraplets: " + e + '. Original string is: ' + tetraplets;
}
resultCallRequests.push([
k as any,
{
serviceId: v.service_id,
functionName: v.function_name,
arguments: arguments_,
tetraplets: tetraplets,
},
]);
}
return {
retCode: result.ret_code,
errorMessage: result.error_message,
data: result.data,
nextPeerPks: result.next_peer_pks,
callRequests: resultCallRequests,
};
}
}

View File

@ -1,151 +0,0 @@
/*
* Copyright 2021 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
*
* This is generated and patched code. All functions are using local wasm as an argument for now, not a global wasm file.
*
*/
/**
*/
export function main(wasm) {
wasm.main();
}
let WASM_VECTOR_LEN = 0;
let cachegetUint8Memory0 = null;
function getUint8Memory0(wasm) {
if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) {
cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
}
return cachegetUint8Memory0;
}
const lTextEncoder = typeof TextEncoder === 'undefined' ? module.require('util').TextEncoder : TextEncoder;
let cachedTextEncoder = new lTextEncoder('utf-8');
const encodeString =
typeof cachedTextEncoder.encodeInto === 'function'
? function (arg, view) {
return cachedTextEncoder.encodeInto(arg, view);
}
: function (arg, view) {
const buf = cachedTextEncoder.encode(arg);
view.set(buf);
return {
read: arg.length,
written: buf.length,
};
};
function passStringToWasm0(wasm, arg, malloc, realloc) {
if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length);
getUint8Memory0(wasm)
.subarray(ptr, ptr + buf.length)
.set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}
let len = arg.length;
let ptr = malloc(len);
const mem = getUint8Memory0(wasm);
let offset = 0;
for (; offset < len; offset++) {
const code = arg.charCodeAt(offset);
if (code > 0x7f) break;
mem[ptr + offset] = code;
}
if (offset !== len) {
if (offset !== 0) {
arg = arg.slice(offset);
}
ptr = realloc(ptr, len, (len = offset + arg.length * 3));
const view = getUint8Memory0(wasm).subarray(ptr + offset, ptr + len);
const ret = encodeString(arg, view);
offset += ret.written;
}
WASM_VECTOR_LEN = offset;
return ptr;
}
function passArray8ToWasm0(wasm, arg, malloc) {
const ptr = malloc(arg.length * 1);
getUint8Memory0(wasm).set(arg, ptr / 1);
WASM_VECTOR_LEN = arg.length;
return ptr;
}
let cachegetInt32Memory0 = null;
function getInt32Memory0(wasm) {
if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) {
cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer);
}
return cachegetInt32Memory0;
}
const lTextDecoder = typeof TextDecoder === 'undefined' ? module.require('util').TextDecoder : TextDecoder;
let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true });
cachedTextDecoder.decode();
export function getStringFromWasm0(wasm, ptr, len) {
return cachedTextDecoder.decode(getUint8Memory0(wasm).subarray(ptr, ptr + len));
}
/**
* @param {} wasm wrapper
* @param {string} air
* @param {Uint8Array} prev_data
* @param {Uint8Array} data
* @param {Uint8Array} params
* @param {Uint8Array} call_results
* @param {string} log_level
* @returns {string}
*/
export function invoke(wasm, air, prev_data, data, params, call_results, log_level) {
try {
var ptr0 = passStringToWasm0(wasm, air, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
var ptr1 = passArray8ToWasm0(wasm, prev_data, wasm.__wbindgen_malloc);
var len1 = WASM_VECTOR_LEN;
var ptr2 = passArray8ToWasm0(wasm, data, wasm.__wbindgen_malloc);
var len2 = WASM_VECTOR_LEN;
var ptr3 = passArray8ToWasm0(wasm, params, wasm.__wbindgen_malloc);
var len3 = WASM_VECTOR_LEN;
var ptr4 = passArray8ToWasm0(wasm, call_results, wasm.__wbindgen_malloc);
var len4 = WASM_VECTOR_LEN;
var ptr5 = passStringToWasm0(wasm, log_level, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len5 = WASM_VECTOR_LEN;
wasm.invoke(8, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4, ptr5, len5);
var r0 = getInt32Memory0(wasm)[8 / 4 + 0];
var r1 = getInt32Memory0(wasm)[8 / 4 + 1];
return getStringFromWasm0(wasm, r0, r1);
} finally {
wasm.__wbindgen_free(r0, r1);
}
}