AVM client: move embedded wasm into a separate file (#200)

This commit is contained in:
Pavel 2021-12-28 17:43:54 +03:00 committed by GitHub
parent 63160dd0f0
commit 91021d8b40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 5876 additions and 52 deletions

View File

@ -6,3 +6,4 @@ wasm
# this file is auto-generated # this file is auto-generated
src/wasm.js src/wasm.js
src/importObject.ts src/importObject.ts
src/avm.wasm

View File

@ -2,11 +2,6 @@
New-Item -ItemType Directory -Force -Path ./wasm New-Item -ItemType Directory -Force -Path ./wasm
wasm-pack build ../../air-interpreter --no-typescript --release -d ../avm/client/wasm wasm-pack build ../../air-interpreter --no-typescript --release -d ../avm/client/wasm
$base64string = [Convert]::ToBase64String([IO.File]::ReadAllBytes('./wasm/air_interpreter_client_bg.wasm')) New-Item -ItemType Directory -Force -Path ./dist
cp wasm/air_interpreter_client_bg.wasm dist/avm.wasm
$data = "// auto-generated cp dist/avm.wasm src/__test__/
module.exports = `"${base64string}`""
$data | Out-File "./src/wasm.js"

View File

@ -9,16 +9,6 @@
wasm-pack build ./air-interpreter --no-typescript --release -d ../avm/client/wasm wasm-pack build ./air-interpreter --no-typescript --release -d ../avm/client/wasm
) )
## base64 on MacOS doesn't have -w option mkdir -p ./dist/
if echo | base64 -w0 > /dev/null 2>&1; cp wasm/air_interpreter_client_bg.wasm dist/avm.wasm
then cp dist/avm.wasm src/__test__/
BASE64=$(base64 -w0 wasm/air_interpreter_client_bg.wasm)
else
BASE64=$(base64 wasm/air_interpreter_client_bg.wasm)
fi
cat << EOF > ./src/wasm.js
// auto-generated
module.exports = "$BASE64";
EOF

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -10,14 +10,15 @@
"files": [ "files": [
"dist/*" "dist/*"
], ],
"bin": {
"copy-avm": "./dist/copyAvm.js"
},
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"test": "jest" "test": "jest"
}, },
"private": false, "private": false,
"dependencies": { "dependencies": {},
"base64-js": "1.5.1"
},
"devDependencies": { "devDependencies": {
"@types/jest": "^26.0.23", "@types/jest": "^26.0.23",
"@types/node": "^14.0.0", "@types/node": "^14.0.0",

View File

@ -1,9 +1,13 @@
import { AirInterpreter } from '..'; import { AirInterpreter } from '..';
import { readFileSync } from 'fs';
import path from 'path';
const vmPeerId = '12D3KooWNzutuy8WHXDKFqFsATvCR6j9cj2FijYbnd47geRKaQZS'; const vmPeerId = '12D3KooWNzutuy8WHXDKFqFsATvCR6j9cj2FijYbnd47geRKaQZS';
const createTestInterpreter = async () => { const createTestInterpreter = async () => {
return AirInterpreter.create('off', (level, message) => { 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}`); console.log(`level: ${level}, message=${message}`);
}); });
}; };

33
avm/client/src/copyAvm.ts Normal file
View File

@ -0,0 +1,33 @@
#! /usr/bin/env node
import fs from 'fs';
import path from 'path';
const firstArgument = process.argv[2];
if (!firstArgument) {
console.log(`Expected exactly 1 argument, got 0. Usage: ${path.basename(process.argv[1])} <destination directory>`);
process.exit(1);
}
let destPath = firstArgument;
if (!path.isAbsolute(destPath)) {
destPath = path.join(process.cwd(), destPath);
}
const wasmName = 'avm.wasm';
const packageName = '@fluencelabs/avm';
const modulePath = require.resolve(packageName);
const source = path.join(path.dirname(modulePath), wasmName);
const dest = path.join(destPath, wasmName);
console.log('ensure directory exists: ', destPath);
fs.mkdirSync(destPath, { recursive: true });
console.log('copying AVM wasm');
console.log('from: ', source);
console.log('to: ', dest);
fs.copyFileSync(source, dest);
console.log('done!');

View File

@ -14,9 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { toByteArray } from 'base64-js';
import { getStringFromWasm0, invoke } from './wrapper'; import { getStringFromWasm0, invoke } from './wrapper';
import wasmBs64 from './wasm';
export type LogLevel = 'info' | 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'off'; export type LogLevel = 'info' | 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'off';
@ -82,28 +80,34 @@ class HostImportsConfig {
} }
} }
const interpreter_wasm = toByteArray(wasmBs64); /**
* Instantiates WebAssembly runtime with AIR interpreter module
/// Instantiates WebAssembly runtime with AIR interpreter module */
async function interpreterInstance(cfg: HostImportsConfig, logFunction: LogFunction): Promise<Instance> { async function interpreterInstance(
/// Create host imports that use module exports internally module: WebAssembly.Module,
cfg: HostImportsConfig,
logFunction: LogFunction,
): Promise<Instance> {
// create host imports that use module exports internally
let imports = cfg.newImportObject(); let imports = cfg.newImportObject();
/// Instantiate interpreter // instantiate interpreter
let interpreter_module = await WebAssembly.compile(interpreter_wasm); let interpreter_module = module;
let instance: Instance = await WebAssembly.instantiate(interpreter_module, imports); let instance: Instance = await WebAssembly.instantiate(interpreter_module, imports);
/// Set exports, so host imports can use them // set exports, so host imports can use them
cfg.setExports(instance.exports); cfg.setExports(instance.exports);
/// Trigger interpreter initialization (i.e., call main function) // trigger interpreter initialization (i.e., call main function)
call_export(instance.exports.main, logFunction); call_export(instance.exports.main, logFunction);
return instance; return instance;
} }
/// If export is a function, call it. Otherwise log a warning. /**
/// NOTE: any here is unavoidable, see Function interface definition * 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 { function call_export(f: ExportValue, logFunction: LogFunction): any {
if (typeof f === 'function') { if (typeof f === 'function') {
return f(); return f();
@ -146,13 +150,18 @@ function log_import(cfg: HostImportsConfig, logFunction: LogFunction): LogImport
}; };
} }
/// Returns import object that describes host functions called by AIR interpreter /**
* Returns import object that describes host functions called by AIR interpreter
*/
function newImportObject(cfg: HostImportsConfig, logFunction: LogFunction): ImportObject { function newImportObject(cfg: HostImportsConfig, logFunction: LogFunction): ImportObject {
return { return {
host: log_import(cfg, logFunction), host: log_import(cfg, logFunction),
}; };
} }
const decoder = new TextDecoder();
const encoder = new TextEncoder();
export class AirInterpreter { export class AirInterpreter {
private wasmWrapper; private wasmWrapper;
private logLevel: LogLevel; private logLevel: LogLevel;
@ -161,12 +170,12 @@ export class AirInterpreter {
this.wasmWrapper = wasmWrapper; this.wasmWrapper = wasmWrapper;
} }
static async create(logLevel: LogLevel, logFunction: LogFunction) { static async create(module: WebAssembly.Module, logLevel: LogLevel, logFunction: LogFunction) {
const cfg = new HostImportsConfig((cfg) => { const cfg = new HostImportsConfig((cfg) => {
return newImportObject(cfg, logFunction); return newImportObject(cfg, logFunction);
}); });
const instance = await interpreterInstance(cfg, logFunction); const instance = await interpreterInstance(module, cfg, logFunction);
const res = new AirInterpreter(instance); const res = new AirInterpreter(instance);
res.logLevel = logLevel; res.logLevel = logLevel;
return res; return res;
@ -187,7 +196,7 @@ export class AirInterpreter {
}; };
} }
const paramsToPass = Buffer.from( const paramsToPass = encoder.encode(
JSON.stringify({ JSON.stringify({
init_peer_id: params.initPeerId, init_peer_id: params.initPeerId,
current_peer_id: params.currentPeerId, current_peer_id: params.currentPeerId,
@ -201,7 +210,7 @@ export class AirInterpreter {
prevData, prevData,
data, data,
paramsToPass, paramsToPass,
Buffer.from(JSON.stringify(callResultsToPass)), encoder.encode(JSON.stringify(callResultsToPass)),
this.logLevel, this.logLevel,
); );
@ -210,7 +219,7 @@ export class AirInterpreter {
result = JSON.parse(rawResult); result = JSON.parse(rawResult);
} catch (ex) {} } catch (ex) {}
const callRequestsStr = new TextDecoder().decode(Buffer.from(result.call_requests)); const callRequestsStr = decoder.decode(new Uint8Array(result.call_requests));
let parsedCallRequests; let parsedCallRequests;
try { try {
if (callRequestsStr.length === 0) { if (callRequestsStr.length === 0) {