From 034713d2784e724272b5653fe42ced24ffb4de74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomek=20Drwi=C4=99ga?= Date: Mon, 6 Jan 2025 10:59:13 +0100 Subject: [PATCH] Alternative approach to args decoder. (#21) * Fix args decoder. * Fix according to GP. * Fix immediate limit. --- assembly/api-generic.ts | 19 +++++--------- assembly/arguments.ts | 57 +++++++++++++++++++++-------------------- assembly/interpreter.ts | 16 +++--------- assembly/program.ts | 29 ++++++++++++++------- 4 files changed, 59 insertions(+), 62 deletions(-) diff --git a/assembly/api-generic.ts b/assembly/api-generic.ts index 0bfb356..1b8d537 100644 --- a/assembly/api-generic.ts +++ b/assembly/api-generic.ts @@ -53,21 +53,15 @@ export function getAssembly(p: Program): string { v += changetype(iData.namePtr); v += `(${instruction})`; - const argsLen = p.mask.argsLen(i); - const end = i + 1 + argsLen; - if (end > len) { - const name = changetype(iData.namePtr); - const intro = "Invalid program - code is not long enough"; - throw new Error(`${intro} Expected: ${argsLen} for ${name} at ${i} (${end} > ${len})`); - } - - const args = decodeArguments(iData.kind, p.code.subarray(i + 1, end)); - const argsArray = args === null ? [0, 0, 0, 0] : [args.a, args.b, args.c, args.d]; + const skipBytes = p.mask.bytesToNextInstruction(i); + const args = decodeArguments(iData.kind, p.code.subarray(i + 1), skipBytes); + const argsArray = [args.a, args.b, args.c, args.d]; const relevantArgs = RELEVANT_ARGS[iData.kind]; for (let i = 0; i < relevantArgs; i++) { v += ` ${argsArray[i]}, `; } - i += argsLen; + i += skipBytes; + console.log(`${v}, nextPC: ${i}`); } return v; } @@ -101,9 +95,10 @@ export function runVm(input: VmInput, logs: boolean = false): VmOutput { 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]; + const skipBytes = p.mask.bytesToNextInstruction(int.pc); const name = changetype(iData.namePtr); console.log(`INSTRUCTION = ${name} (${instruction})`); - const args = resolveArguments(iData.kind, int.program.code.subarray(int.pc + 1), int.registers); + const args = resolveArguments(iData.kind, int.program.code.subarray(int.pc + 1), skipBytes, int.registers); if (args !== null) { console.log(`ARGUMENTS: ${args.a} (${args.decoded.a}) = 0x${u64(args.a).toString(16)}, diff --git a/assembly/arguments.ts b/assembly/arguments.ts index 97ea28a..898162c 100644 --- a/assembly/arguments.ts +++ b/assembly/arguments.ts @@ -36,81 +36,81 @@ function asArgs(a: u32, b: u32, c: u32, d: u32): Args { return x; } -type ArgsDecoder = (data: Uint8Array) => Args; +type ArgsDecoder = (data: Uint8Array, immLimit: u32) => Args; -function twoImm(data: Uint8Array): Args { +function twoImm(data: Uint8Array, lim: u32): Args { const n = nibbles(data[0]); const split = Math.min(4, n.low) + 1; - const first = decodeI32(data.subarray(1, split)); - const second = decodeI32(data.subarray(split)); + const first = decodeI32(data, 1, split); + const second = decodeI32(data, split, lim); return asArgs(first, second, 0, 0); } export const DECODERS: ArgsDecoder[] = [ // DECODERS[Arguments.Zero] = - (_) => { + (_d, _l) => { return asArgs(0, 0, 0, 0); }, // DECODERS[Arguments.OneImm] = - (data: Uint8Array) => { - return asArgs(decodeI32(data), 0, 0, 0); + (data, lim) => { + return asArgs(decodeI32(data, 0, lim), 0, 0, 0); }, // DECODERS[Arguments.TwoImm] = - (data: Uint8Array) => twoImm(data), + (data, lim) => twoImm(data, lim), // DECODERS[Arguments.OneOff] = - (data: Uint8Array) => { - return asArgs(decodeI32(data), 0, 0, 0); + (data, lim) => { + return asArgs(decodeI32(data, 0, lim), 0, 0, 0); }, // DECODERS[Arguments.OneRegOneImm] = - (data: Uint8Array) => { - return asArgs(nibbles(data[0]).low, decodeI32(data.subarray(1)), 0, 0); + (data, lim) => { + return asArgs(nibbles(data[0]).low, decodeI32(data, 1, lim), 0, 0); }, // DECODERS[Arguments.OneRegOneExtImm] = - (data: Uint8Array) => { + (data, _lim) => { const a = nibbles(data[0]).low; const b = decodeU32(data.subarray(1)); const c = decodeU32(data.subarray(5)); return asArgs(a, b, c, 0); }, //DECODERS[Arguments.OneRegTwoImm] = - (data: Uint8Array) => { + (data, lim) => { const n = nibbles(data[0]); const split = Math.min(4, n.hig) + 1; - const immA = decodeI32(data.subarray(1, split)); - const immB = decodeI32(data.subarray(split)); + const immA = decodeI32(data, 1, split); + const immB = decodeI32(data, split, lim); return asArgs(n.low, immA, immB, 0); }, // DECODERS[Arguments.OneRegOneImmOneOff] = - (data: Uint8Array) => { + (data, lim) => { const n = nibbles(data[0]); const split = Math.min(4, n.hig) + 1; - const immA = decodeI32(data.subarray(1, split)); - const offs = decodeI32(data.subarray(split)); + const immA = decodeI32(data, 1, split); + const offs = decodeI32(data, split, lim); return asArgs(n.low, immA, offs, 0); }, // DECODERS[Arguments.TwoReg] = - (data: Uint8Array) => { + (data, _lim) => { const n = nibbles(data[0]); return asArgs(n.hig, n.low, 0, 0); }, // DECODERS[Arguments.TwoRegOneImm] = - (data: Uint8Array) => { + (data, lim) => { const n = nibbles(data[0]); - return asArgs(n.hig, n.low, decodeI32(data.subarray(1)), 0); + return asArgs(n.hig, n.low, decodeI32(data, 1, lim), 0); }, // DECODERS[Arguments.TwoRegOneOff] = - (data: Uint8Array) => { + (data, lim) => { const n = nibbles(data[0]); - return asArgs(n.hig, n.low, decodeI32(data.subarray(1)), 0); + return asArgs(n.hig, n.low, decodeI32(data, 1, lim), 0); }, // DECODERS[Arguments.TwoRegTwoImm] = - (data: Uint8Array) => { + (data, lim) => { const n = nibbles(data[0]); - const result = twoImm(data.subarray(1)); + const result = twoImm(data.subarray(1), lim > 1 ? lim - 1 : 0); return asArgs(n.hig, n.low, result.a, result.b); }, // DECODERS[Arguments.ThreeReg] = - (data: Uint8Array) => { + (data, _lim) => { const a = nibbles(data[0]); const b = nibbles(data[1]); return asArgs(a.hig, a.low, b.low, 0); @@ -134,7 +134,8 @@ export function nibbles(byte: u8): Nibbles { } //@inline -function decodeI32(data: Uint8Array): u32 { +function decodeI32(input: Uint8Array, start: u32, end: u32): u32 { + const data = input.subarray(start, end > start ? end : start); const len = Math.min(4, data.length); let num = 0; for (let i: u32 = 0; i < len; i++) { diff --git a/assembly/interpreter.ts b/assembly/interpreter.ts index dfdd624..afd655e 100644 --- a/assembly/interpreter.ts +++ b/assembly/interpreter.ts @@ -80,18 +80,8 @@ export class Interpreter { } // get args and invoke instruction - const argsLen = this.program.mask.argsLen(pc); - const end = pc + 1 + argsLen; - if (end > this.program.code.length) { - this.status = Status.PANIC; - return false; - } - - const args = decodeArguments(iData.kind, this.program.code.subarray(pc + 1, end)); - if (args === null) { - this.status = Status.PANIC; - return false; - } + const skipBytes = this.program.mask.bytesToNextInstruction(pc); + const args = decodeArguments(iData.kind, this.program.code.subarray(pc + 1), skipBytes); // additional gas cost of sbrk if (iData === SBRK) { @@ -157,7 +147,7 @@ export class Interpreter { } case Outcome.Ok: { // by default move to next instruction. - this.pc += 1 + argsLen; + this.pc += 1 + skipBytes; return true; } } diff --git a/assembly/program.ts b/assembly/program.ts index 5e617fe..c4960b5 100644 --- a/assembly/program.ts +++ b/assembly/program.ts @@ -64,11 +64,11 @@ export function decodeProgram(program: Uint8Array): Program { export class Mask { // NOTE: might be longer than code (bit-alignment) - readonly bytesToSkip: StaticArray; + readonly bytesToSkip: StaticArray; constructor(packedMask: Uint8Array, codeLength: i32) { - this.bytesToSkip = new StaticArray(codeLength); - let lastInstructionOffset: u8 = 0; + this.bytesToSkip = new StaticArray(codeLength); + let lastInstructionOffset: u32 = 0; for (let i: i32 = packedMask.length - 1; i >= 0; i -= 1) { let bits = packedMask[i]; const index = i * 8; @@ -91,10 +91,11 @@ export class Mask { return this.bytesToSkip[index] === 0; } - argsLen(i: u32): u8 { + bytesToNextInstruction(i: u32): u32 { if (i + 1 < this.bytesToSkip.length) { return this.bytesToSkip[i + 1]; } + return 0; } @@ -198,11 +199,16 @@ export class Program { } } -export function decodeArguments(kind: Arguments, data: Uint8Array): Args | null { +export function decodeArguments(kind: Arguments, data: Uint8Array, lim: u32): Args { if (data.length < REQUIRED_BYTES[kind]) { - return null; + // in case we have less data than needed we extend the data with zeros. + const extended = new Uint8Array(REQUIRED_BYTES[kind]); + for (let i = 0; i < data.length; i++) { + extended[i] = data[i]; + } + return DECODERS[kind](extended, lim); } - return DECODERS[kind](data); + return DECODERS[kind](data, lim); } class ResolvedArguments { @@ -213,8 +219,13 @@ class ResolvedArguments { decoded: Args = new Args(); } -export function resolveArguments(kind: Arguments, data: Uint8Array, registers: Registers): ResolvedArguments | null { - const args = decodeArguments(kind, data); +export function resolveArguments( + kind: Arguments, + data: Uint8Array, + lim: u32, + registers: Registers, +): ResolvedArguments | null { + const args = decodeArguments(kind, data, lim); if (args === null) { return null; }