From 4f3bc13730e36815b517e949811929c2517cd6b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomek=20Drwi=C4=99ga?= Date: Tue, 31 Dec 2024 14:43:54 +0100 Subject: [PATCH] Fix sbrk gas calculation. (#15) * Fix sbrk gas calculation. * wrap as program.: * mul upper test program * Fix mul_upper * Fix mul upper and mask generation. * Fix mul_upper_s_s --------- Co-authored-by: Mateusz Sikora --- assembly/api-generic.ts | 2 + assembly/api.ts | 2 +- assembly/instructions-exe.ts | 2 +- assembly/interpreter.ts | 3 +- assembly/math.ts | 32 +++++++++----- assembly/program-build.ts | 11 ++--- bin/fuzz.js | 85 +++++++++++++++++++++++++++--------- bin/index.js | 8 ++-- bin/wrap.js | 11 +++++ package-lock.json | 8 ++-- package.json | 5 ++- 11 files changed, 116 insertions(+), 53 deletions(-) create mode 100755 bin/wrap.js diff --git a/assembly/api-generic.ts b/assembly/api-generic.ts index 28d1730..0bfb356 100644 --- a/assembly/api-generic.ts +++ b/assembly/api-generic.ts @@ -89,6 +89,7 @@ export function runVm(input: VmInput, logs: boolean = false): VmOutput { for (;;) { if (!isOk) { if (logs) console.log(`REGISTERS = ${registers.join(", ")} (final)`); + if (logs) console.log(`REGISTERS = ${registers.map((x: u64) => `0x${x.toString(16)}`).join(", ")} (final)`); if (logs) console.log(`Finished with status: ${int.status}`); break; } @@ -96,6 +97,7 @@ export function runVm(input: VmInput, logs: boolean = false): VmOutput { if (logs) console.log(`PC = ${int.pc}`); if (logs) console.log(`STATUS = ${int.status}`); if (logs) console.log(`REGISTERS = ${registers.join(", ")}`); + if (logs) console.log(`REGISTERS = ${registers.map((x: u64) => `0x${x.toString(16)}`).join(", ")}`); if (logs) { const instruction = int.pc < u32(int.program.code.length) ? int.program.code[int.pc] : 0; const iData = instruction >= INSTRUCTIONS.length ? MISSING_INSTRUCTION : INSTRUCTIONS[instruction]; diff --git a/assembly/api.ts b/assembly/api.ts index f871c63..265d0da 100644 --- a/assembly/api.ts +++ b/assembly/api.ts @@ -44,7 +44,7 @@ export function nextStep(): boolean { return false; } -export function run(steps: u32): boolean { +export function nSteps(steps: u32): boolean { if (interpreter !== null) { const int = interpreter; let isOk = true; diff --git a/assembly/instructions-exe.ts b/assembly/instructions-exe.ts index 31131e4..c93df35 100644 --- a/assembly/instructions-exe.ts +++ b/assembly/instructions-exe.ts @@ -46,7 +46,7 @@ export const RUN: InstructionRun[] = [ // 20 // LOAD_IMM_64 (args, registers) => { - registers[reg(args.a)] = u64(args.c) + (u64(args.b) << 32); + registers[reg(args.a)] = u64(args.b) + (u64(args.c) << 32); return ok(); }, INVALID, diff --git a/assembly/interpreter.ts b/assembly/interpreter.ts index 5cd4595..dfdd624 100644 --- a/assembly/interpreter.ts +++ b/assembly/interpreter.ts @@ -95,7 +95,7 @@ export class Interpreter { // additional gas cost of sbrk if (iData === SBRK) { - const alloc = u32(this.registers[reg(args.a)]); + const alloc = u64(u32(this.registers[reg(args.a)])); const gas = ((alloc + PAGE_SIZE - 1) >> PAGE_SIZE_SHIFT) * 16; if (this.gas.sub(gas)) { this.status = Status.OOG; @@ -158,7 +158,6 @@ export class Interpreter { case Outcome.Ok: { // by default move to next instruction. this.pc += 1 + argsLen; - return true; } } diff --git a/assembly/math.ts b/assembly/math.ts index ccb3d45..de6752e 100644 --- a/assembly/math.ts +++ b/assembly/math.ts @@ -32,23 +32,33 @@ export function mulUpperUnsigned(a: u64, b: u64): u64 { * Same as [mulUpperUnsigned] but treat the arguments as signed (two-complement) 64-bit numbers and the result alike. */ export function mulUpperSigned(a: i64, b: i64): u64 { - const aSign = a < 0 ? 1 : -1; - const bSign = b < 0 ? 1 : -1; - const sign = aSign * bSign; - const aAbs = a < 0 ? ~a + 1 : a; - const bAbs = b < 0 ? ~b + 1 : b; + let isResultNegative = false; + let aAbs = a; + let bAbs = b; + if (a < 0) { + isResultNegative = !isResultNegative; + aAbs = ~a + 1; + } + if (b < 0) { + isResultNegative = !isResultNegative; + bAbs = ~b + 1; + } - if (sign < 0) { - return ~mulUpperUnsigned(aAbs, bAbs) + 1; + if (isResultNegative) { + const upper = mulUpperUnsigned(aAbs, bAbs); + const lower = aAbs * bAbs; + return ~upper + (lower === 0 ? 1 : 0); } + return mulUpperUnsigned(aAbs, bAbs); } export function mulUpperSignedUnsigned(a: i64, b: u64): u64 { - const aSign = a < 0 ? 1 : -1; - if (aSign < 0) { - const aAbs = ~a + 1; - return ~mulUpperUnsigned(aAbs, b) + 1; + if (a < 0) { + const aAbs: u64 = ~a + 1; + const upper = mulUpperUnsigned(aAbs, b); + const lower = aAbs * b; + return ~upper + (lower === 0 ? 1 : 0); } return mulUpperUnsigned(a, b); } diff --git a/assembly/program-build.ts b/assembly/program-build.ts index 7c46138..d817e9a 100644 --- a/assembly/program-build.ts +++ b/assembly/program-build.ts @@ -83,8 +83,7 @@ function buildMask(bytecode: Uint8Array): u8[] { const requiredBytes = REQUIRED_BYTES[iData.kind]; if (i + 1 + requiredBytes <= bytecode.length) { - const skip = skipBytes(iData.kind, bytecode.subarray(i + 1)); - i += skip; + i += skipBytes(iData.kind, bytecode.subarray(i + 1)); } } // pack mask @@ -92,12 +91,10 @@ function buildMask(bytecode: Uint8Array): u8[] { for (let i = 0; i < mask.length; i += 8) { let byte: u8 = 0; for (let j = i; j < i + 8; j++) { - if (j < mask.length) { - byte |= mask[j] ? 1 : 0; - } else { - byte |= 1; + byte >>= 1; + if (j < mask.length && mask[j]) { + byte |= 0b1000_0000; } - byte << 1; } packed.push(byte); } diff --git a/bin/fuzz.js b/bin/fuzz.js index 2846ca1..ebf7719 100755 --- a/bin/fuzz.js +++ b/bin/fuzz.js @@ -17,7 +17,7 @@ export function fuzz(data) { pc, gas, ); - pvm.run(100); + while(pvm.nSteps(100)) {} const printDebugInfo = false; const registers = Array(13).join(',').split(',').map(() => BigInt(0)); @@ -30,25 +30,31 @@ export function fuzz(data) { program, }, printDebugInfo); - assert(pvm.getStatus(), normalizeStatus(output.status), 'status'); - assert(pvm.getGasLeft(), output.gas, 'gas'); - assert(pvm.getRegisters().toString(), output.registers.toString(), 'registers'); - // assert(pvm.getProgramCounter(), output.pc, 'pc'); + collectErrors((assert) => { + assert(pvm.getStatus(), normalizeStatus(output.status), 'status'); + assert(pvm.getGasLeft(), output.gas, 'gas'); + assert(Array.from(pvm.getRegisters()), output.registers, 'registers'); + assert(pvm.getProgramCounter(), output.pc, 'pc'); + }); - writeTestCase( - program, - { - pc, - gas, - registers, - }, - { - status: pvm.getStatus(), - gasLeft: pvm.getGasLeft(), - pc: pvm.getProgramCounter(), - registers: pvm.getRegisters() - }, - ); + try { + writeTestCase( + program, + { + pc, + gas, + registers, + }, + { + status: pvm.getStatus(), + gasLeft: pvm.getGasLeft(), + pc: pvm.getProgramCounter(), + registers: pvm.getRegisters() + }, + ); + } catch (e) { + console.warn('Unable to write file', e); + } } catch (e) { const hex = programHex(program); console.log(program); @@ -73,13 +79,50 @@ function normalizeStatus(status) { } function assert(tb, an, comment = '') { - if (tb !== an) { - throw new Error(`Diverging value: (typeberry) ${tb} vs ${an} (ananas). ${comment}`); + let condition = tb !== an; + if (Array.isArray(tb) && Array.isArray(an)) { + condition = tb.toString() !== an.toString(); + } + + if (condition) { + const alsoAsHex = (f) => { + if (Array.isArray(f)) { + return `${f.map(alsoAsHex).join(', ')}`; + } + + if (typeof f === 'number' || typeof f === 'bigint') { + if (BigInt(f) !== 0n) { + return `${f} | 0x${f.toString(16)}`; + } + return `${f}`; + } + return f; + }; + + throw new Error(`Diverging value: ${comment} +\t(typeberry) ${alsoAsHex(tb)} +\t(ananas) ${alsoAsHex(an)}`); + } +} + +function collectErrors(cb) { + const errors = []; + cb((...args) => { + try { + assert(...args); + } catch (e) { + errors.push(e); + } + }); + + if (errors.length > 0) { + throw new Error(errors.join('\n')); } } function writeTestCase(program, initial, expected) { const hex = programHex(program); + fs.mkdirSync(`../tests/length_${hex.length}`, { recursive: true }); fs.writeFileSync(`../tests/length_${hex.length}/${hex}.json`, JSON.stringify({ name: linkTo(hex), "initial-regs": initial.registers, diff --git a/bin/index.js b/bin/index.js index 10741a4..8aa17ec 100755 --- a/bin/index.js +++ b/bin/index.js @@ -133,7 +133,7 @@ function main() { if (status.fail.length) { console.error('Failures:'); for (const e of status.fail) { - console.error(`❗ ${e}`); + console.error(`❗ ${e.filePath} (${e.name})`); } process.exit(-1) } @@ -151,7 +151,7 @@ function processFile(IS_DEBUG, status, filePath) { // Parse the JSON content jsonData = JSON.parse(fileContent); } catch (error) { - status.fail.push(filePath); + status.fail.push({ filePath, name: '' }); console.error(`Error reading file: ${filePath}`); console.error(error.message); return; @@ -160,9 +160,9 @@ function processFile(IS_DEBUG, status, filePath) { try { // Process the parsed JSON processJson(jsonData, IS_DEBUG); - status.ok.push(filePath); + status.ok.push({ filePath, name: jsonData.name }); } catch (error) { - status.fail.push(filePath); + status.fail.push({ filePath, name: jsonData.name }); console.error(`Error running test: ${filePath}`); console.error(error.message); } diff --git a/bin/wrap.js b/bin/wrap.js new file mode 100755 index 0000000..0e48f94 --- /dev/null +++ b/bin/wrap.js @@ -0,0 +1,11 @@ +#!/usr/bin/env node + +// wrap input as program + +import {fuzz} from './fuzz.js' + +const program = fuzz([ + 20,8,0, 0, 0, 0, 255, 255, 255, 255,20,7,0, 0, 0, 0, 1, 0, 0, 0,193,135,9,194,135,10,195,135,11 +]); + +console.log(program); diff --git a/package-lock.json b/package-lock.json index 1d06c56..1edc981 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "devDependencies": { "@biomejs/biome": "^1.9.4", "@jazzer.js/core": "^2.1.0", - "@typeberry/pvm-debugger-adapter": "^0.0.1-1bce842", + "@typeberry/pvm-debugger-adapter": "0.1.0-5b611f4", "assemblyscript": "^0.27.31" } }, @@ -588,9 +588,9 @@ } }, "node_modules/@typeberry/pvm-debugger-adapter": { - "version": "0.0.1-1bce842", - "resolved": "https://registry.npmjs.org/@typeberry/pvm-debugger-adapter/-/pvm-debugger-adapter-0.0.1-1bce842.tgz", - "integrity": "sha512-y+7bpcXMu/jgiSNOEvAFSNqaF2/MYfQGpQL6UBmnCy2MdDSE9YxevJgkwrrtclyWOilWQzxWALRVeMBRw2cYdg==", + "version": "0.1.0-5b611f4", + "resolved": "https://registry.npmjs.org/@typeberry/pvm-debugger-adapter/-/pvm-debugger-adapter-0.1.0-5b611f4.tgz", + "integrity": "sha512-UXO3TJC4p/QP6/8NaRh3m4t7K5qejojwVZwoXGyGXhRJi23gUVoxnFKKY4fh9zq8L20l0R99lvOEFSpzA7htKg==", "dev": true, "license": "MPL-2.0" }, diff --git a/package.json b/package.json index 89ac7e4..6319681 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "prestart": "npm run build", "preweb": "npm run build", "web": "npx live-server ./web", - "start": "node ./bin/index.js" + "start": "node ./bin/index.js", + "fuzz": "npx jazzer --sync ./bin/fuzz" }, "keywords": [], "author": "Fluffy Labs", @@ -24,7 +25,7 @@ "devDependencies": { "@biomejs/biome": "^1.9.4", "@jazzer.js/core": "^2.1.0", - "@typeberry/pvm-debugger-adapter": "^0.0.1-1bce842", + "@typeberry/pvm-debugger-adapter": "0.1.0-5b611f4", "assemblyscript": "^0.27.31" }, "type": "module",