Skip to content

Commit 54becd3

Browse files
committed
Add Gas metering, timeout
1 parent 778a74a commit 54becd3

File tree

3 files changed

+111
-69
lines changed

3 files changed

+111
-69
lines changed

README.md

+7-4
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ $ wasm-run --help
1919
wasm-run [options] <file> [args..]
2020

2121
Options:
22-
-i, --invoke Function to execute [string]
23-
--trace Trace imported function calls [boolean]
24-
--version Show version number [boolean]
25-
--help Show help [boolean]
22+
-i, --invoke Function to execute
23+
--trace Trace imported function calls
24+
-t, --timeout Execution timeout (ms)
25+
--gas-limit Gas limit [default: 100000]
26+
--version Show version number
27+
--help Show help
2628

2729
$ wasm-run ./test/fib32.wasm 32
2830
[tracer] Running fib(32)...
@@ -52,6 +54,7 @@ $ wasm-run --trace wasi-hello-world.wasm
5254
☑ Run `wasi-unstable` apps (compatibility layer)
5355
`i64` args, `multi-value`, `bulk-memory`, `tail-calls` support via experimental flags
5456
☑ Generic imports tracing
57+
☑ Gas metering
5558
☐ Compiled wasm caching (blocked by [#1](https://github.com/wasm3/node-wasm-run/issues/1))
5659
☐ WASI API and structures decoding (generate from witx?)
5760
☐ REPL mode

test/wasi/coremark.metered.wasm

40.7 KB
Binary file not shown.

wasm-run.js

+104-65
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,15 @@
22

33
/*
44
* TODO:
5-
* [ ] --timeout flag
6-
* [ ] --gas-limit flag
5+
* [ ] --inspect
6+
* [ ] check if imports are satisfied, print missing parts
7+
* [ ] auto-import memory
8+
* [ ] --validate flag
9+
* [ ] --imports flag
710
*/
811

912
"use strict";
1013

11-
/*
12-
* Respawn with experimental flags
13-
*/
14-
15-
if (process.argv[2] != "--respawn") {
16-
const { execFileSync, spawnSync } = require('child_process');
17-
const node = process.argv[0];
18-
const script = process.argv[1];
19-
const script_args = process.argv.slice(2);
20-
21-
let allFlags = execFileSync(node, ["--v8-options"]).toString();
22-
allFlags += execFileSync(node, ["--help"]).toString();
23-
24-
const nodeFlags = [ "--experimental-wasm-bigint",
25-
"--experimental-wasm-mv",
26-
"--experimental-wasm-return-call",
27-
"--experimental-wasm-bulk-memory",
28-
"--experimental-wasi-unstable-preview1",
29-
"--wasm-opt"].filter(x => allFlags.includes(x));
30-
31-
let res = spawnSync(node,
32-
[...nodeFlags, script, "--respawn", ...script_args],
33-
{ stdio: ['inherit', 'inherit', 'inherit'] });
34-
process.exit(res.status);
35-
}
36-
3714
/*
3815
* Author: Volodymyr Shymanskyy
3916
*/
@@ -64,10 +41,22 @@ const argv = require("yargs")
6441
describe: "Function to execute",
6542
nargs: 1
6643
},
44+
"timeout": {
45+
alias: "t",
46+
type: "int",
47+
describe: "Execution timeout (ms)",
48+
nargs: 1
49+
},
6750
"trace": {
6851
type: "boolean",
6952
describe: "Trace imported function calls",
7053
},
54+
"gas-limit": {
55+
type: "float",
56+
describe: "Gas limit",
57+
default: 100000,
58+
nargs: 1
59+
},
7160
})
7261
.string('_')
7362
.strict()
@@ -76,6 +65,38 @@ const argv = require("yargs")
7665
.wrap(null)
7766
.argv;
7867

68+
/*
69+
* Respawn with experimental flags
70+
*/
71+
72+
if (!argv.respawn)
73+
{
74+
const { execFileSync, spawnSync } = require('child_process');
75+
const node = process.argv[0];
76+
const script = process.argv[1];
77+
const script_args = process.argv.slice(2);
78+
79+
let allFlags = execFileSync(node, ["--v8-options"]).toString();
80+
allFlags += execFileSync(node, ["--help"]).toString();
81+
82+
const nodeFlags = [ "--experimental-wasm-bigint",
83+
"--experimental-wasm-mv",
84+
"--experimental-wasm-return-call",
85+
"--experimental-wasm-bulk-memory",
86+
"--experimental-wasi-unstable-preview1",
87+
"--wasm-opt"].filter(x => allFlags.includes(x));
88+
89+
let res = spawnSync(node,
90+
[...nodeFlags, script, "--respawn", ...script_args],
91+
{ stdio: ['inherit', 'inherit', 'inherit'],
92+
timeout: argv.timeout });
93+
94+
if (res.error) {
95+
fatal(res.error);
96+
}
97+
process.exit(res.status);
98+
}
99+
79100
/*
80101
* Helpers
81102
*/
@@ -279,7 +300,7 @@ async function parseWasmInfo(binary)
279300
/*
280301
* Compile
281302
*/
282-
303+
283304
/* TODO: caching
284305
const v8 = require('v8');
285306
const compiled = await WebAssembly.compile(binary);
@@ -308,12 +329,22 @@ async function parseWasmInfo(binary)
308329
* Prepare imports
309330
*/
310331

332+
let ctx = {
333+
gasCurrent: argv.gasLimit,
334+
};
335+
311336
let imports = {
312-
// TODO: add ability to define imports
337+
metering: {
338+
usegas: function(gas) {
339+
ctx.gasCurrent -= (gas/10000);
340+
if (ctx.gasCurrent < 0) {
341+
throw `Run out of gas`;
342+
}
343+
}
344+
}
313345
}
314346

315347
let wasi;
316-
let ctx = {};
317348
if (wasmInfo.wasiVersion)
318349
{
319350
const { WASI } = require('wasi');
@@ -435,44 +466,52 @@ async function parseWasmInfo(binary)
435466
* Execute
436467
*/
437468

438-
const instance = await WebAssembly.instantiate(module, imports);
439-
440-
// TODO: REPL mode
441-
442-
// If no WASI is detected, and no func specified -> try to run the only function
443-
if (!argv.invoke && !wasmInfo.wasiVersion && wasmInfo.exportedFuncs.length == 1) {
444-
argv.invoke = wasmInfo.exportedFuncs[0];
445-
}
469+
try {
470+
let instance = await WebAssembly.instantiate(module, imports);
446471

447-
if (argv.invoke) {
448-
if (!wasmInfo.exportedFuncs.includes(argv.invoke)) {
449-
fatal(`Function not found: ${argv.invoke}`);
472+
// If no WASI is detected, and no func specified -> try to run the only function
473+
if (!argv.invoke && !wasmInfo.wasiVersion && wasmInfo.exportedFuncs.length == 1) {
474+
argv.invoke = wasmInfo.exportedFuncs[0];
450475
}
451-
let args = argv._.slice(1)
452-
453-
let wasmInfo2 = await parseWasmInfo(binary);
454-
//console.log(JSON.stringify(wasmInfo2));
455-
let funcInfo = wasmInfo2.funcsByName[argv.invoke];
456-
457-
for (let i = 0; i < funcInfo.params.length; i++) {
458-
switch (funcInfo.params[i]) {
459-
case 'i32': args[i] = parseInt(args[i]); break;
460-
case 'i64': args[i] = BigInt(args[i]); break;
461-
case 'f32':
462-
case 'f64': args[i] = parseFloat(args[i]); break;
476+
477+
if (argv.invoke) {
478+
if (!wasmInfo.exportedFuncs.includes(argv.invoke)) {
479+
fatal(`Function not found: ${argv.invoke}`);
480+
}
481+
let args = argv._.slice(1)
482+
483+
let wasmInfo2 = await parseWasmInfo(binary);
484+
//console.log(JSON.stringify(wasmInfo2));
485+
let funcInfo = wasmInfo2.funcsByName[argv.invoke];
486+
487+
for (let i = 0; i < funcInfo.params.length; i++) {
488+
switch (funcInfo.params[i]) {
489+
case 'i32': args[i] = parseInt(args[i]); break;
490+
case 'i64': args[i] = BigInt(args[i]); break;
491+
case 'f32':
492+
case 'f64': args[i] = parseFloat(args[i]); break;
493+
}
463494
}
464-
}
465495

466-
log(`Running ${argv.invoke}(${args})...`);
467-
let func = instance.exports[argv.invoke];
468-
let result = func(...args);
469-
log(`Result: ${result}`);
470-
} else {
471-
ctx.memory = instance.exports.memory;
472-
let exitcode = wasi.start(instance);
473-
if (exitcode) {
474-
log(`Exit code: ${exitcode}`);
496+
log(`Running ${argv.invoke}(${args})...`);
497+
let func = instance.exports[argv.invoke];
498+
let result = func(...args);
499+
log(`Result: ${result}`);
500+
if (ctx.gasCurrent != argv.gasLimit) {
501+
log(`Gas used: ${argv.gasLimit - ctx.gasCurrent}`);
502+
}
503+
} else {
504+
ctx.memory = instance.exports.memory;
505+
let exitcode = wasi.start(instance);
506+
if (exitcode) {
507+
log(`Exit code: ${exitcode}`);
508+
}
509+
if (ctx.gasCurrent != argv.gasLimit) {
510+
log(`Gas used: ${(argv.gasLimit - ctx.gasCurrent).toFixed(4)}`);
511+
}
512+
process.exit(exitcode);
475513
}
476-
process.exit(exitcode);
514+
} catch (e) {
515+
fatal(e);
477516
}
478517
})();

0 commit comments

Comments
 (0)