diff --git a/package.json b/package.json index 0dab90d..db93586 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "test": "npm run test:all", "test:all": "npm run test:prebuild && npm run test:ava", "test:ava": "ava", + "test:integration": "npx ts-node ./tests/integration/precision.ts", "test:example": "npx ts-node ./tests/examples/example.ts", "coverage:test:ava": "c8 --src src/ --all ava" }, diff --git a/src/messaging/Message.ts b/src/messaging/Message.ts index ba7e439..261b605 100644 --- a/src/messaging/Message.ts +++ b/src/messaging/Message.ts @@ -7,6 +7,7 @@ import {SourceMap} from '../sourcemap/SourceMap'; import {readFileSync} from 'fs'; import {CompileOutput, CompilerFactory} from '../manage/Compiler'; import {WABT} from '../util/env'; +import {encodeSLEB128, encodeULEB128} from '@thi.ng/leb128'; import Interrupt = WARDuino.Interrupt; import State = WARDuino.State; import Value = WASM.Value; @@ -139,7 +140,7 @@ export namespace Message { export function updateModule(wasm: string): Request { function payload(binary: Buffer): string { const w = new Uint8Array(binary); - const sizeHex: string = WASM.leb128(BigInt(w.length)); + const sizeHex = Buffer.from(encodeULEB128(w.length)).toString('hex'); const sizeBuffer = Buffer.allocUnsafe(4); sizeBuffer.writeUint32BE(w.length); const wasmHex = Buffer.from(w).toString('hex'); @@ -181,8 +182,12 @@ export namespace Message { const buff = Buffer.alloc(arg.type === Float.f32 ? 4 : 8); write(buff, Number(arg.value), 0, true, arg.type === Float.f32 ? 23 : 52, buff.length); // todo fix precision loss payload += buff.toString('hex'); + } else if (arg.type === WASM.Integer.i32 || arg.type === WASM.Integer.i64) { + payload += Buffer.from(encodeSLEB128((arg.value as WASM.WasmInt).toBigInt())).toString('hex'); + } else if (arg.type === WASM.Integer.u32 || arg.type === WASM.Integer.u64) { + payload += Buffer.from(encodeULEB128((arg.value as WASM.WasmInt).toBigInt())).toString('hex'); } else { - payload += WASM.leb128(arg.value); + throw Error(`Cannot invoke a function with a ${arg.type} argument.`); } }); return payload; @@ -190,7 +195,7 @@ export namespace Message { return { type: Interrupt.invoke, - payload: (map: SourceMap.Mapping) => `${WASM.leb128(BigInt(fidx(map, func)))}${convert(args)}`, + payload: (map: SourceMap.Mapping) => `${Buffer.from(encodeULEB128(fidx(map, func))).toString('hex')}${convert(args)}`, parser: invokeParser } } diff --git a/src/sourcemap/Wasm.ts b/src/sourcemap/Wasm.ts index e3515fb..2b8b076 100644 --- a/src/sourcemap/Wasm.ts +++ b/src/sourcemap/Wasm.ts @@ -178,22 +178,4 @@ export namespace WASM { bytes: Uint8Array; } - export function leb128(value: bigint | number): string { // TODO can only handle 32 bit - let a = Number(value); - a |= 0; - const result = []; - while (true) { - const byte_ = a & 0x7f; - a >>= 7; - if ( - (a === 0 && (byte_ & 0x40) === 0) || - (a === -1 && (byte_ & 0x40) !== 0) - ) { - result.push(byte_.toString(16).padStart(2, '0')); - return result.join('').toUpperCase(); - } - result.push((byte_ | 0x80).toString(16).padStart(2, '0')); - } - } - -} \ No newline at end of file +} diff --git a/tests/integration/precision.ts b/tests/integration/precision.ts new file mode 100644 index 0000000..b2946ba --- /dev/null +++ b/tests/integration/precision.ts @@ -0,0 +1,28 @@ +import { + EmulatorSpecification, + Framework, + Invoker, + Verbosity, + WASM +} from '../../src/index'; + +const framework = Framework.getImplementation(); + +const precision = framework.suite('Test i64 precision'); + +precision.testee('emulator[:8100]', new EmulatorSpecification(8100)); + +precision.test({ + title: 'i64 rem_s returns zero for INT64_MIN modulo -1', + program: 'tests/integration/precision.wast', + dependencies: [], + steps: [ + new Invoker('rem_s', [ + WASM.i64(-9223372036854775808n), + WASM.i64(-1n) + ], WASM.i64(0n)) + ] +}); + +framework.reporter.verbosity(Verbosity.more); +framework.analyse([precision]); diff --git a/tests/integration/precision.wast b/tests/integration/precision.wast new file mode 100644 index 0000000..97da996 --- /dev/null +++ b/tests/integration/precision.wast @@ -0,0 +1,37 @@ +(module + (func (export "add") (param $x i64) (param $y i64) (result i64) (i64.add (local.get $x) (local.get $y))) + (func (export "sub") (param $x i64) (param $y i64) (result i64) (i64.sub (local.get $x) (local.get $y))) + (func (export "mul") (param $x i64) (param $y i64) (result i64) (i64.mul (local.get $x) (local.get $y))) + (func (export "div_s") (param $x i64) (param $y i64) (result i64) (i64.div_s (local.get $x) (local.get $y))) + (func (export "div_u") (param $x i64) (param $y i64) (result i64) (i64.div_u (local.get $x) (local.get $y))) + (func (export "rem_s") (param $x i64) (param $y i64) (result i64) (i64.rem_s (local.get $x) (local.get $y))) + (func (export "rem_u") (param $x i64) (param $y i64) (result i64) (i64.rem_u (local.get $x) (local.get $y))) + (func (export "and") (param $x i64) (param $y i64) (result i64) (i64.and (local.get $x) (local.get $y))) + (func (export "or") (param $x i64) (param $y i64) (result i64) (i64.or (local.get $x) (local.get $y))) + (func (export "xor") (param $x i64) (param $y i64) (result i64) (i64.xor (local.get $x) (local.get $y))) + (func (export "shl") (param $x i64) (param $y i64) (result i64) (i64.shl (local.get $x) (local.get $y))) + (func (export "shr_s") (param $x i64) (param $y i64) (result i64) (i64.shr_s (local.get $x) (local.get $y))) + (func (export "shr_u") (param $x i64) (param $y i64) (result i64) (i64.shr_u (local.get $x) (local.get $y))) + (func (export "rotl") (param $x i64) (param $y i64) (result i64) (i64.rotl (local.get $x) (local.get $y))) + (func (export "rotr") (param $x i64) (param $y i64) (result i64) (i64.rotr (local.get $x) (local.get $y))) + (func (export "clz") (param $x i64) (result i64) (i64.clz (local.get $x))) + (func (export "ctz") (param $x i64) (result i64) (i64.ctz (local.get $x))) + (func (export "popcnt") (param $x i64) (result i64) (i64.popcnt (local.get $x))) + (func (export "extend8_s") (param $x i64) (result i64) (i64.extend8_s (local.get $x))) + (func (export "extend16_s") (param $x i64) (result i64) (i64.extend16_s (local.get $x))) + (func (export "extend32_s") (param $x i64) (result i64) (i64.extend32_s (local.get $x))) + (func (export "eqz") (param $x i64) (result i32) (i64.eqz (local.get $x))) + (func (export "eq") (param $x i64) (param $y i64) (result i32) (i64.eq (local.get $x) (local.get $y))) + (func (export "ne") (param $x i64) (param $y i64) (result i32) (i64.ne (local.get $x) (local.get $y))) + (func (export "lt_s") (param $x i64) (param $y i64) (result i32) (i64.lt_s (local.get $x) (local.get $y))) + (func (export "lt_u") (param $x i64) (param $y i64) (result i32) (i64.lt_u (local.get $x) (local.get $y))) + (func (export "le_s") (param $x i64) (param $y i64) (result i32) (i64.le_s (local.get $x) (local.get $y))) + (func (export "le_u") (param $x i64) (param $y i64) (result i32) (i64.le_u (local.get $x) (local.get $y))) + (func (export "gt_s") (param $x i64) (param $y i64) (result i32) (i64.gt_s (local.get $x) (local.get $y))) + (func (export "gt_u") (param $x i64) (param $y i64) (result i32) (i64.gt_u (local.get $x) (local.get $y))) + (func (export "ge_s") (param $x i64) (param $y i64) (result i32) (i64.ge_s (local.get $x) (local.get $y))) + (func (export "ge_u") (param $x i64) (param $y i64) (result i32) (i64.ge_u (local.get $x) (local.get $y))) + (func (export "i64_get_max") (result i64) (i64.const 9223372036854775807)) + (func (export "i64_eq_max") (param i64) (result i32) (i64.eq (local.get 0) (i64.const 9223372036854775807))) + (func (export "i64_eq_max_wrong") (param i64) (result i32) (i64.eq (local.get 0) (i64.const 9223372036854776000))) +) diff --git a/tests/unit/messaging.test.ts b/tests/unit/messaging.test.ts index 9f64070..6134f39 100644 --- a/tests/unit/messaging.test.ts +++ b/tests/unit/messaging.test.ts @@ -1,8 +1,26 @@ import test from 'ava'; import {MessageQueue} from '../../src/messaging/MessageQueue'; +import {Message} from '../../src/messaging/Message'; +import {WASM} from '../../src/sourcemap/Wasm'; +import {SourceMap} from '../../src/sourcemap/SourceMap'; const alphanumerical = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'.split(''); +test('[Message.invoke] : encode 64-bit integer arguments without precision loss', t => { + const mapping = new SourceMap.Mapping().init([], [{ + index: 0, + name: 'rem_s', + arguments: [], + locals: [] + }], [], []); + const request = Message.invoke('rem_s', [ + WASM.i64(-9223372036854775808n), + WASM.i64(-1n) + ]); + + t.is(request.payload!(mapping), '008080808080808080807f7f'); +}); + test('[MessageQueue] : test EOM detection', t => { const fuzzer = fuzzy(alphanumerical); const newline = new MessageQueue('\n'); @@ -75,4 +93,4 @@ const fuzzy = (characters: string[]): (n: number) => string => { } return result; }; -} \ No newline at end of file +} diff --git a/tests/unit/sourcemap.test.ts b/tests/unit/sourcemap.test.ts index 3f31b04..e0574f4 100644 --- a/tests/unit/sourcemap.test.ts +++ b/tests/unit/sourcemap.test.ts @@ -1,5 +1,4 @@ import test from 'ava'; -import {WASM} from '../../src/sourcemap/Wasm'; import {WatMapper} from '../../src/sourcemap/SourceMapper'; import {SourceMap} from '../../src/sourcemap/SourceMap'; import {WABT} from '../../src/util/env'; @@ -7,19 +6,6 @@ import {copyFileSync, mkdtempSync, readFileSync, rmSync} from 'fs'; const artifacts = `${__dirname}/../../../tests/artifacts`; -/** - * Check LEB 128 encoding - */ -test('[leb128] : test encoding', t => { - t.is(WASM.leb128(0n), '00'); - t.is(WASM.leb128(1n), '01'); - t.is(WASM.leb128(8n), '08'); - t.is(WASM.leb128(32n), '20'); - t.is(WASM.leb128(64n), 'C000'); - t.is(WASM.leb128(128n), '8001'); - t.is(WASM.leb128(1202n), 'B209'); -}); - test('[extractLineInfo] : test against artifacts (1)', async t => { await check(`${artifacts}/compile.output`, (mapping: SourceMap.Mapping) => { // check line information @@ -81,4 +67,4 @@ function initialize(): string { const tmp: string = mkdtempSync('test'); copyFileSync(`${artifacts}/upload.wasm`, `${tmp}/upload.wasm`); return tmp; -} \ No newline at end of file +}