Skip to content

Commit

Permalink
enable running generated wasm module in chrome (#148)
Browse files Browse the repository at this point in the history
* enable running generated wasm module in chrome

---------

Signed-off-by: Su Yihan <[email protected]>
  • Loading branch information
yviansu authored Mar 5, 2024
1 parent d0b8cd8 commit 7359949
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 27 deletions.
4 changes: 3 additions & 1 deletion tests/benchmark/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ These benchmarks are based on some open source efforts to measure performance of
# run specific benchmark
node run_benchmark.js --benchmark binarytrees
# run specific runtime mode
node run_benchmark.js --runtime wamr-aot # (wamr-aot | wamr-interp | qjs)
node run_benchmark.js --runtimes wamr-aot # (wamr-aot | wamr-interp | qjs | node)
# get result after multiple times warm up
node run_benchmark.js --warmup 3
```

## Validate benchmark result
Expand Down
88 changes: 69 additions & 19 deletions tests/benchmark/run_benchmark.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const wamr_stack_size = args['--stack-size'] ? parseInt(args['--stack-size']) :
const wamr_gc_heap = args['--gc-heap'] ? parseInt(args['--gc-heap']) : 40960000;
const specifed_benchmarks = args['--benchmarks'] ? args['--benchmarks'].split(',') : null;
const specified_runtimes = args['--runtimes'] ? args['--runtimes'].split(',') : null;
const warm_up_times = args['--warmup'] ? parseInt(args['--warmup']) : 0;

const default_gc_size_option = `--gc-heap-size=${wamr_gc_heap}`
const stack_size_option = `--stack-size=${wamr_stack_size}`
Expand All @@ -80,9 +81,27 @@ try {
}
}

let ts_times = [];
let js_times = [];
let aot_times = [];
let node_cmd;
try {
node_cmd = execSync('which node').toString().trim();
} catch (error) {
if (process.env.NODE_PATH) {
node_cmd = process.env.NODE_PATH;
} else {
const default_node_path = '/usr/local/bin/node';
if (fs.existsSync(default_node_path)) {
node_cmd = default_node_path;
} else {
console.error("Error: NODE_PATH is not defined, and no default node path is provided.");
process.exit(1);
}
}
}

let wamr_interp_times = [];
let qjs_js_times = [];
let wamr_aot_times = [];
let v8_js_times = [];
let prefixs = [];

let benchmark_options = {
Expand Down Expand Up @@ -118,6 +137,7 @@ function collect_benchmark_options(options) {

console.log(`\x1b[33m======================== options ========================\x1b[0m`);
console.log(`QJS_PATH: ${qjs}`);
console.log(`NODE_PATH: ${node_cmd}`);
console.log(`strategy: run ${multirun} times and get average`);
console.log(`clean generated files: ${shouldClean ? 'true' : 'false'}`);
console.log(`\x1b[33m======================== running ========================\x1b[0m`);
Expand All @@ -127,6 +147,9 @@ function run_multiple_times(cmd) {
let elapse_arr = [];

try {
for (let i = 0; i < warm_up_times; i++) {
execSync(cmd);
}
for (let i = 0; i < multirun; i++) {
let start = performance.now();
let ret = execSync(cmd);
Expand Down Expand Up @@ -192,7 +215,7 @@ for (let benchmark of benchmarks) {
else {
process.stdout.write(`WAMR interpreter ... \t`);
elapsed = run_multiple_times(`${iwasm_gc} ${collect_benchmark_options(benchmark_options[prefix]?.wamr_option)} -f main ${prefix}.wasm`);
ts_times.push(elapsed);
wamr_interp_times.push(elapsed);
console.log(`${elapsed.toFixed(2)}ms`);
}

Expand All @@ -202,7 +225,7 @@ for (let benchmark of benchmarks) {
else {
process.stdout.write(`WAMR AoT ... \t\t`);
elapsed = run_multiple_times(`${iwasm_gc} ${collect_benchmark_options(benchmark_options[prefix]?.wamr_option)} -f main ${prefix}.aot`);
aot_times.push(elapsed);
wamr_aot_times.push(elapsed);
console.log(`${elapsed.toFixed(2)}ms`);
}

Expand All @@ -212,7 +235,17 @@ for (let benchmark of benchmarks) {
else {
process.stdout.write(`QuickJS ... \t\t`);
elapsed = run_multiple_times(`${qjs} ${js_file}`);
js_times.push(elapsed);
qjs_js_times.push(elapsed);
console.log(`${elapsed.toFixed(2)}ms`);
}

if (specified_runtimes && !specified_runtimes.includes('node')) {
console.log(`\x1b[33mSkip Node due to argument filter.\x1b[0m`);
}
else {
process.stdout.write(`Node ... \t\t`);
elapsed = run_multiple_times(`${node_cmd} ${js_file}`);
v8_js_times.push(elapsed);
console.log(`${elapsed.toFixed(2)}ms`);
}

Expand All @@ -229,38 +262,55 @@ console.log(`\x1b[32m====================== results ======================\x1b[0
let results = [];

for (let i = 0; i < executed_benchmarks; i++) {
let ts_time = ts_times[i];
let js_time = js_times[i];
let aot_time = aot_times[i];
let wamr_interp_time = wamr_interp_times[i];
let qjs_js_time = qjs_js_times[i];
let wamr_aot_time = wamr_aot_times[i];
let v8_js_time = v8_js_times[i];

let r = {
benchmark: prefixs[i]
}

if (ts_time) {
r['WAMR_interpreter'] = ts_time.toFixed(2) + 'ms';
if (wamr_interp_time) {
r['WAMR_interpreter'] = wamr_interp_time.toFixed(2) + 'ms';
}

if (aot_time) {
r['WAMR_aot'] = aot_time.toFixed(2) + 'ms';
if (wamr_aot_time) {
r['WAMR_aot'] = wamr_aot_time.toFixed(2) + 'ms';
}

if (js_time) {
r['QuickJS'] = js_time.toFixed(2) + 'ms';
if (qjs_js_time) {
r['QuickJS'] = qjs_js_time.toFixed(2) + 'ms';
}

if (ts_time && js_time) {
let ratio = ts_time / js_time;
if (v8_js_time) {
r['Node'] = v8_js_time.toFixed(2) + 'ms';
}

if (wamr_interp_time && qjs_js_time) {
let ratio = wamr_interp_time / qjs_js_time;
let formatted_result = ratio.toFixed(2);
r['WAMR_interpreter/qjs'] = formatted_result;
}

if (aot_time && js_time) {
let ratio_aot = aot_time / js_time;
if (wamr_aot_time && qjs_js_time) {
let ratio_aot = wamr_aot_time / qjs_js_time;
let formatted_result_aot = ratio_aot.toFixed(2);
r['WAMR_aot/qjs'] = formatted_result_aot;
}

if (wamr_interp_time && v8_js_time) {
let ratio = wamr_interp_time / v8_js_time;
let formatted_result = ratio.toFixed(2);
r['WAMR_interpreter/node'] = formatted_result;
}

if (wamr_aot_time && v8_js_time) {
let ratio_aot = wamr_aot_time / v8_js_time;
let formatted_result_aot = ratio_aot.toFixed(2);
r['WAMR_aot/node'] = formatted_result_aot;
}

results.push(r);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,18 +286,20 @@ const importObject = {
}
},
env: {
console_log: (obj) => {
Console_log: (obj) => {
/** TODO: cant log reference type variable */
console.log(obj);
},
console_constructor: (obj) => {},
Console_constructor: (obj) => {},
strcmp(a, b) {
let lhs = cstringToJsString(a);
let rhs = cstringToJsString(b);
return lhs.localeCompare(rhs);
},
setTimeout: (obj) => {},
clearTimeout: (obj) => {},
malloc: (size)=>{},
free: (size)=>{},

array_push_generic: (ctx, obj, elem) => {},
array_pop_f64: (ctx, obj) => {},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# Run generated WASM module on Node.js
# Run generated WASM module

This document describes how to execute WASM module on Node.js.
This document describes how to execute WASM module on node.js and on chrome.

> Note: Wasmnizer-ts follows the latest WasmGC spec, which requires `V8 v11.9+`, but the latest nodejs (v21.5.0) is using `V8 11.8.172.17`, so currently the generated WASM module can't execute on any nodejs releases.
> If you do want to try on nodejs, you can reset to commit `94cf9929421d47a9976fa6edf74b25ef2a00ee12` to build the compiler, which is compatible to older V8 versions.
## Prerequisites
## Run module on node

### Prerequisites
- node.js version 20.0.0 or higher

to enable support for `stringref` feature, node.js version 20.0 or higher is necessary.
Expand All @@ -16,7 +18,7 @@ This document describes how to execute WASM module on Node.js.
- `--experimental-wasm-gc`: This flag is required to enable support for the WASM GC feature.
- `--experimental-wasm-stringref`: This flag is needed to enable support for the `stringref` feature.

## How to Run
### How to Run

To run your WebAssembly file, use the following command:

Expand All @@ -31,7 +33,7 @@ This document describes how to execute WASM module on Node.js.
- `-f`: specify the exported WASM function you want to execute in Node.js.
- `-s`: specify to execute the `_start` WASM function to initialize global variables if necessary.

## Example
### Example

Here is an example.

Expand All @@ -52,3 +54,14 @@ This document describes how to execute WASM module on Node.js.
```

it will output `1`.

## Run module on chrome

### Prerequisites
- Set chrome flags by `chrome://flags`, should set these flags as enabled:
- Experimental WebAssembly
- WebAssembly Garbage Collection
- WebAssembly Stringref

### How to Run
Start a server, open the `run_module_on_chrome.html` on chrome, fill in with the wasm path, the wasm function name, and arguments(must be separated by commas), then click `submit` button, and the result will be print on the page.
62 changes: 62 additions & 0 deletions tools/validate/run_module/run_module_on_chrome.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<!--
Copyright (C) 2023 Intel Corporation. All rights reserved.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-->

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>run wasm module</title>
</head>
<body>
<form id="processForm">
<table>
<tr>
<td><label for="pathInput">path:</label></td>
<td><input type="text" id="pathInput" required style="width: 500px;"></td>
</tr>
<tr>
<td><label for="funcNameInput">function name:</label></td>
<td><input type="text" id="funcNameInput" style="width: 500px;"></td>
</tr>
<tr>
<td><label for="argsInput">arguments:</label></td>
<td><input type="text" id="argsInput" style="width: 500px;"></td>
</tr>
<tr>
<td><label for="warmupTimes">warmup times:</label></td>
<td><input type="text" id="warmupTimes" style="width: 500px;"></td>
</tr>
<tr>
<td><label for="targetSelect">run target:</label></td>
<td>
<select id="targetSelect" style="width: 500px;">
<option value="wasm">WASM</option>
<option value="js">JS</option>
</select>
</td>
</tr>
</table>
<p></p>
<button type="submit">submit</button>
</form>

<script type="module">
import { run_wasm_module } from "./run_module_on_chrome.js";
document.getElementById("processForm").addEventListener("submit", function(event) {
event.preventDefault();

var path = document.getElementById("pathInput").value;
var funcName = document.getElementById("funcNameInput").value;
var args = document.getElementById("argsInput").value.split(",");
var warmupTimes= document.getElementById("warmupTimes").value;
var runTarget = document.getElementById("targetSelect").value;

run_wasm_module(path, funcName, warmupTimes, runTarget, ...args);
});
</script>
<p id="result"></p>
<p id="time"></p>
</body>
</html>
68 changes: 68 additions & 0 deletions tools/validate/run_module/run_module_on_chrome.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (C) 2023 Intel Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
*/

import { importObject, setWasmMemory } from './import_object.js';

export function run_wasm_module(filePath, funcName, warmupTimes, runTarget, ...funcArgs) {
const parts = filePath.split(".");
const extension = parts[parts.length - 1];
if (runTarget === 'js') {
if (extension !== 'js') {
const resultElement = document.getElementById('result');
resultElement.innerHTML = `Error: filePath must end with ".js`;
}
fetch(filePath)
.then(response => response.text())
.then(script => {
if (warmupTimes) {
for (let i = 0; i < parseInt(warmupTimes); i++) {
eval(script);
}
}
const start_time = performance.now();
let res = eval(script);
if (funcName) {
res = window[funcName](...funcArgs);
}
const end_time = performance.now();
if (typeof res !== 'object' || res === null) {
const resultElement = document.getElementById('result');
resultElement.innerHTML = `The result is: ${res}`;
}
const timeElement = document.getElementById('time');
timeElement.innerHTML = `Execution time is: ${end_time - start_time}`;
});
} else if (runTarget === 'wasm') {
if (extension !== 'wasm') {
const resultElement = document.getElementById('result');
resultElement.innerHTML = `Error: filePath must end with ".wasm`;
}
fetch(filePath)
.then((response) => response.arrayBuffer())
.then((bytes) => WebAssembly.instantiate(bytes, importObject))
.then((results) => {
const exports = results.instance.exports;
setWasmMemory(exports.default);
const startFunc = exports._entry;
const exportedFunc = exports[funcName];
if (warmupTimes) {
for (let i = 0; i < parseInt(warmupTimes); i++) {
startFunc();
exportedFunc(...funcArgs);
}
}
const start_time = performance.now();
startFunc();
const res = exportedFunc(...funcArgs);
const end_time = performance.now();
if (typeof res !== 'object' || res === null) {
const resultElement = document.getElementById('result');
resultElement.innerHTML = `The result is: ${res}`;
}
const timeElement = document.getElementById('time');
timeElement.innerHTML = `Execution time is: ${end_time - start_time}`;
});
}
}

0 comments on commit 7359949

Please sign in to comment.