diff --git a/src/App.tsx b/src/App.tsx index e9f5369..a55f545 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,7 @@ import "./App.css"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea.tsx"; -import { InitialState, Pvm } from "@/pvm/pvm.ts"; +import { InitialState, Pvm } from "@/pvm-packages/pvm/pvm.ts"; import { useState } from "react"; function App() { @@ -22,7 +22,7 @@ function App() { // const program = [0, 0, 3, 8, 135, 9, 249] const handleClick = () => { - const pvm = new Pvm(program, initialState); + const pvm = new Pvm(new Uint8Array(program), initialState); // console.log(pvm.printProgram()) pvm.runProgram(); // console.log(pvm.getState()) diff --git a/src/pvm-packages/bytes.test.ts b/src/pvm-packages/bytes.test.ts new file mode 100644 index 0000000..75afbdf --- /dev/null +++ b/src/pvm-packages/bytes.test.ts @@ -0,0 +1,42 @@ +import assert from "node:assert"; +import { test } from "node:test"; +import { Bytes, BytesBlob } from "./bytes"; + +test("BytesBlob", async (t) => { + await t.test("should fail if 0x is missing", () => { + try { + BytesBlob.parseBlob("ff2f"); + assert.fail("Should throw an exception"); + } catch (e) { + assert.strictEqual(`${e}`, "Error: Invalid hex string: ff2f."); + } + }); + + await t.test("parse 0x-prefixed hex string into blob of bytes", () => { + const input = "0x2fa3f686df876995167e7c2e5d74c4c7b6e48f8068fe0e44208344d480f7904c"; + const result = BytesBlob.parseBlob(input); + + assert.deepStrictEqual(new Uint8Array(result.buffer), new Uint8Array([47, 163, 246, 134, 223, 135, 105, 149, 22, 126, 124, 46, 93, 116, 196, 199, 182, 228, 143, 128, 104, 254, 14, 68, 32, 131, 68, 212, 128, 247, 144, 76])); + }); +}); + +test("Bytes", async (t) => { + await t.test("should fail in case of length mismatch", () => { + const input = "0x9c2d3bce7aa0a5857c67a85247365d2035f7d9daec2b515e86086584ad5e8644"; + + try { + Bytes.parseBytes(input, 16); + assert.fail("Should throw an exception"); + } catch (e) { + assert.strictEqual(`${e}`, "Error: Input string too long. Expected 16, got 32"); + } + }); + + await t.test("parse 0x-prefixed, fixed length bytes vector", () => { + const input = "0x9c2d3bce7aa0a5857c67a85247365d2035f7d9daec2b515e86086584ad5e8644"; + + const bytes = Bytes.parseBytes(input, 32); + + assert.deepStrictEqual(new Uint8Array(bytes.view.buffer), new Uint8Array([156, 45, 59, 206, 122, 160, 165, 133, 124, 103, 168, 82, 71, 54, 93, 32, 53, 247, 217, 218, 236, 43, 81, 94, 134, 8, 101, 132, 173, 94, 134, 68])); + }); +}); diff --git a/src/pvm-packages/bytes.ts b/src/pvm-packages/bytes.ts new file mode 100644 index 0000000..02b8f29 --- /dev/null +++ b/src/pvm-packages/bytes.ts @@ -0,0 +1,67 @@ +function assert(value: boolean, description: string): asserts value is true { + if (!value) throw new Error(description); +} + +function bufferToHexString(buffer: ArrayBuffer): string { + // TODO [ToDr] consider using TextDecoder API? + let s = "0x"; + const asUint = new Uint8Array(buffer); + for (const v of asUint) { + s += v.toString(16).padStart(2, "0"); + } + return s; +} + +export class BytesBlob { + readonly buffer: ArrayBuffer = new ArrayBuffer(0); + readonly length: number = 0; + + constructor(buffer: ArrayBuffer) { + this.buffer = buffer; + this.length = buffer.byteLength; + } + + toString() { + return bufferToHexString(this.buffer); + } + + static parseBlob(v: string): BytesBlob { + const len = v.length; + if (len % 2 === 1 || !v.startsWith("0x")) { + throw new Error(`Invalid hex string: ${v}.`); + } + // NOTE [ToDr] alloc + const buffer = new ArrayBuffer(len / 2 - 1); + const bytes = new Uint8Array(buffer); + for (let i = 2; i < len - 1; i += 2) { + const c = v.substring(i, i + 2); + bytes[i / 2 - 1] = Number.parseInt(c, 16); + } + + return new BytesBlob(buffer); + } +} + +export class Bytes { + readonly view: DataView = new DataView(new ArrayBuffer(0)); + readonly length: T; + + constructor(view: DataView, len: T) { + assert(view.byteLength === len, `Given buffer has incorrect size ${view.byteLength} vs expected ${len}`); + this.view = view; + this.length = len; + } + + toString() { + return bufferToHexString(this.view.buffer); + } + + static parseBytes(v: string, len: X): Bytes { + if (v.length > 2 * len + 2) { + throw new Error(`Input string too long. Expected ${len}, got ${v.length / 2 - 1}`); + } + + const blob = BytesBlob.parseBlob(v); + return new Bytes(new DataView(blob.buffer), len); + } +} diff --git a/src/pvm-packages/crypto.ts b/src/pvm-packages/crypto.ts new file mode 100644 index 0000000..dfe416c --- /dev/null +++ b/src/pvm-packages/crypto.ts @@ -0,0 +1,6 @@ +import type { Bytes } from "./bytes"; +import type { Opaque } from "./opaque"; + +export type Ed25519Key = Opaque, "ed25519">; +export type BandersnatchKey = Opaque, "BandersnatchKey">; +export type BlsKey = Opaque, "bls">; diff --git a/src/pvm-packages/hash.ts b/src/pvm-packages/hash.ts new file mode 100644 index 0000000..390d72b --- /dev/null +++ b/src/pvm-packages/hash.ts @@ -0,0 +1,5 @@ +import type { Bytes } from "./bytes"; +import type { Opaque } from "./opaque"; + +export type Hash = Bytes<32>; +export type EntropyHash = Opaque; diff --git a/src/pvm-packages/jam-codec/decode-natural-number.test.ts b/src/pvm-packages/jam-codec/decode-natural-number.test.ts new file mode 100644 index 0000000..60d673e --- /dev/null +++ b/src/pvm-packages/jam-codec/decode-natural-number.test.ts @@ -0,0 +1,226 @@ +import assert from "node:assert"; +import { test } from "node:test"; + +import { decodeNaturalNumber } from "./decode-natural-number"; + +test("decodeNaturalNumber", async (t) => { + await t.test("decode 0", () => { + const encodedBytes = new Uint8Array([0]); + const expectedValue = 0n; + + const result = decodeNaturalNumber(encodedBytes); + + assert.strictEqual(result.value, expectedValue); + assert.strictEqual(result.bytesToSkip, encodedBytes.length); + }); + + await t.test("decode single byte min value", () => { + const encodedBytes = new Uint8Array([1]); + const expectedValue = 1n; + + const result = decodeNaturalNumber(encodedBytes); + + assert.strictEqual(result.value, expectedValue); + assert.strictEqual(result.bytesToSkip, encodedBytes.length); + }); + + await t.test("decode single byte max value", () => { + const encodedBytes = new Uint8Array([127]); + const expectedValue = 127n; + + const result = decodeNaturalNumber(encodedBytes); + + assert.strictEqual(result.value, expectedValue); + assert.strictEqual(result.bytesToSkip, encodedBytes.length); + }); + + await t.test("decode 2 bytes min value", () => { + const encodedBytes = new Uint8Array([128, 128]); + const expectedValue = 128n; + + const result = decodeNaturalNumber(encodedBytes); + + assert.strictEqual(result.value, expectedValue); + assert.strictEqual(result.bytesToSkip, encodedBytes.length); + }); + + await t.test("decode 2 bytes max value", () => { + const encodedBytes = new Uint8Array([191, 255]); + const expectedValue = 2n ** 14n - 1n; + + const result = decodeNaturalNumber(encodedBytes); + + assert.strictEqual(result.value, expectedValue); + assert.strictEqual(result.bytesToSkip, encodedBytes.length); + }); + + await t.test("decode 3 bytes min value", () => { + const encodedBytes = new Uint8Array([192, 0, 0x40]); + const expectedValue = 2n ** 14n; + + const result = decodeNaturalNumber(encodedBytes); + + assert.strictEqual(result.value, expectedValue); + assert.strictEqual(result.bytesToSkip, encodedBytes.length); + }); + + await t.test("decode 3 bytes max value", () => { + const encodedBytes = new Uint8Array([192 + 31, 0xff, 0xff]); + const expectedValue = 2n ** 21n - 1n; + + const result = decodeNaturalNumber(encodedBytes); + + assert.strictEqual(result.value, expectedValue); + assert.strictEqual(result.bytesToSkip, encodedBytes.length); + }); + + await t.test("decode 4 bytes min value", () => { + const encodedBytes = new Uint8Array([0xe0, 0, 0, 0x20]); + const expectedValue = 2n ** 21n; + + const result = decodeNaturalNumber(encodedBytes); + + assert.strictEqual(result.value, expectedValue); + assert.strictEqual(result.bytesToSkip, encodedBytes.length); + }); + + await t.test("decode 4 bytes max value", () => { + const encodedBytes = new Uint8Array([0xe0 + 15, 0xff, 0xff, 0xff]); + const expectedValue = 2n ** 28n - 1n; + + const result = decodeNaturalNumber(encodedBytes); + + assert.strictEqual(result.value, expectedValue); + assert.strictEqual(result.bytesToSkip, encodedBytes.length); + }); + + await t.test("decode 5 bytes min value", () => { + const encodedBytes = new Uint8Array([256 - 16, 0, 0, 0, 0x10]); + const expectedValue = 2n ** 28n; + + const result = decodeNaturalNumber(encodedBytes); + + assert.strictEqual(result.value, expectedValue); + assert.strictEqual(result.bytesToSkip, encodedBytes.length); + }); + + await t.test("decode 5 bytes max value", () => { + const encodedBytes = new Uint8Array([256 - 16 + 7, 0xff, 0xff, 0xff, 0xff]); + const expectedValue = 2n ** 35n - 1n; + + const result = decodeNaturalNumber(encodedBytes); + + assert.strictEqual(result.value, expectedValue); + assert.strictEqual(result.bytesToSkip, encodedBytes.length); + }); + + await t.test("decode 6 bytes min value", () => { + const encodedBytes = new Uint8Array([256 - 8, 0, 0, 0, 0, 0x08]); + const expectedValue = 2n ** 35n; + + const result = decodeNaturalNumber(encodedBytes); + + assert.strictEqual(result.value, expectedValue); + assert.strictEqual(result.bytesToSkip, encodedBytes.length); + }); + + await t.test("decode 6 bytes max value", () => { + const encodedBytes = new Uint8Array([256 - 8 + 3, 0xff, 0xff, 0xff, 0xff, 0xff]); + const expectedValue = 2n ** 42n - 1n; + + const result = decodeNaturalNumber(encodedBytes); + + assert.strictEqual(result.value, expectedValue); + assert.strictEqual(result.bytesToSkip, encodedBytes.length); + }); + + await t.test("decode 7 bytes min value", () => { + const encodedBytes = new Uint8Array([256 - 4, 0, 0, 0, 0, 0, 0x04]); + const expectedValue = 2n ** 42n; + + const result = decodeNaturalNumber(encodedBytes); + + assert.strictEqual(result.value, expectedValue); + assert.strictEqual(result.bytesToSkip, encodedBytes.length); + }); + + await t.test("decode 7 bytes max value", () => { + const encodedBytes = new Uint8Array([256 - 4 + 1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]); + const expectedValue = 2n ** 49n - 1n; + + const result = decodeNaturalNumber(encodedBytes); + + assert.strictEqual(result.value, expectedValue); + assert.strictEqual(result.bytesToSkip, encodedBytes.length); + }); + + await t.test("decode 8 bytes min value", () => { + const encodedBytes = new Uint8Array([256 - 2, 0, 0, 0, 0, 0, 0, 0x02]); + const expectedValue = 2n ** 49n; + + const result = decodeNaturalNumber(encodedBytes); + + assert.strictEqual(result.value, expectedValue); + assert.strictEqual(result.bytesToSkip, encodedBytes.length); + }); + + await t.test("decode 8 bytes max value", () => { + const encodedBytes = new Uint8Array([256 - 2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]); + const expectedValue = 2n ** 56n - 1n; + + const result = decodeNaturalNumber(encodedBytes); + + assert.strictEqual(result.value, expectedValue); + assert.strictEqual(result.bytesToSkip, encodedBytes.length); + }); + + await t.test("decode 9 bytes min value", () => { + const encodedBytes = new Uint8Array([255, 0, 0, 0, 0, 0, 0, 0, 0x01]); + const expectedValue = 2n ** 56n; + + const result = decodeNaturalNumber(encodedBytes); + + assert.strictEqual(result.value, expectedValue); + assert.strictEqual(result.bytesToSkip, encodedBytes.length); + }); + + await t.test("decode 9 bytes max value", () => { + const encodedBytes = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255, 255]); + const expectedValue = 2n ** 64n - 1n; + + const result = decodeNaturalNumber(encodedBytes); + + assert.strictEqual(result.value, expectedValue); + assert.strictEqual(result.bytesToSkip, encodedBytes.length); + }); + + await t.test("decode 0 with extra bytes", () => { + const encodedBytes = new Uint8Array([0, 1, 2, 3]); + const expectedValue = 0n; + + const result = decodeNaturalNumber(encodedBytes); + + assert.strictEqual(result.value, expectedValue); + assert.strictEqual(result.bytesToSkip, 1); + }); + + await t.test("decode 7 bytes number with extra bytes ", () => { + const encodedBytes = new Uint8Array([256 - 4 + 1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1, 0x2]); + const expectedValue = 2n ** 49n - 1n; + + const result = decodeNaturalNumber(encodedBytes); + + assert.strictEqual(result.value, expectedValue); + assert.strictEqual(result.bytesToSkip, 7); + }); + + await t.test("decode 9 bytes number with extra bytes", () => { + const encodedBytes = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255, 255, 1, 2, 3]); + const expectedValue = 2n ** 64n - 1n; + + const result = decodeNaturalNumber(encodedBytes); + + assert.strictEqual(result.value, expectedValue); + assert.strictEqual(result.bytesToSkip, 9); + }); +}); diff --git a/src/pvm-packages/jam-codec/decode-natural-number.ts b/src/pvm-packages/jam-codec/decode-natural-number.ts new file mode 100644 index 0000000..1ecbc76 --- /dev/null +++ b/src/pvm-packages/jam-codec/decode-natural-number.ts @@ -0,0 +1,43 @@ +import { LittleEndianDecoder } from "./little-endian-decoder"; + +const MASKS = [0xff, 0xfe, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80]; + +type Result = { + bytesToSkip: number; + value: bigint; +}; + +export function decodeNaturalNumber(bytes: Uint8Array): Result { + const littleEndianDecoder = new LittleEndianDecoder(); + const l = decodeLengthAfterFirstByte(bytes[0]); + const bytesToSkip = l + 1; + + if (l === 8) { + return { + value: littleEndianDecoder.decode(bytes.subarray(1, 9)), + bytesToSkip, + }; + } + + if (l === 0) { + return { value: BigInt(bytes[0]), bytesToSkip }; + } + + const restBytesValue = littleEndianDecoder.decode(bytes.subarray(1, l + 1)); + const firstByteValue = BigInt(bytes[0]) + 2n ** (8n - BigInt(l)) - 2n ** 8n; + + return { + value: restBytesValue + (firstByteValue << (8n * BigInt(l))), + bytesToSkip, + }; +} + +function decodeLengthAfterFirstByte(firstByte: number) { + for (let i = 0; i < MASKS.length; i++) { + if (firstByte >= MASKS[i]) { + return 8 - i; + } + } + + return 0; +} diff --git a/src/pvm-packages/jam-codec/little-endian-decoder.test.ts b/src/pvm-packages/jam-codec/little-endian-decoder.test.ts new file mode 100644 index 0000000..bceef55 --- /dev/null +++ b/src/pvm-packages/jam-codec/little-endian-decoder.test.ts @@ -0,0 +1,61 @@ +import assert from "node:assert"; +import { test } from "node:test"; + +import { LittleEndianDecoder } from "./little-endian-decoder"; + +test("LittleEndianDecoder", async (t) => { + await t.test("Empty bytes array", () => { + const decoder = new LittleEndianDecoder(); + + const encodedBytes = new Uint8Array([]); + const expectedValue = 0n; + + const result = decoder.decode(encodedBytes); + + assert.strictEqual(result, expectedValue); + }); + + await t.test("1 byte number", () => { + const decoder = new LittleEndianDecoder(); + + const encodedBytes = new Uint8Array([0xff]); + const expectedValue = 255n; + + const result = decoder.decode(encodedBytes); + + assert.strictEqual(result, expectedValue); + }); + + await t.test("2 bytes number", () => { + const decoder = new LittleEndianDecoder(); + + const encodedBytes = new Uint8Array([0xff, 0x01]); + const expectedValue = 511n; + + const result = decoder.decode(encodedBytes); + + assert.strictEqual(result, expectedValue); + }); + + await t.test("4 bytes number", () => { + const decoder = new LittleEndianDecoder(); + + const encodedBytes = new Uint8Array([0xff, 0x56, 0x34, 0x12]); + const expectedValue = 305420031n; + + const result = decoder.decode(encodedBytes); + + assert.strictEqual(result, expectedValue); + }); + + await t.test("8 bytes number", () => { + const decoder = new LittleEndianDecoder(); + + const encodedBytes = new Uint8Array([0xff, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12]); + const expectedValue = 1311768467463790335n; + + const result = decoder.decode(encodedBytes); + + assert.strictEqual(result, expectedValue); + }); +}); diff --git a/src/pvm-packages/jam-codec/little-endian-decoder.ts b/src/pvm-packages/jam-codec/little-endian-decoder.ts new file mode 100644 index 0000000..c537687 --- /dev/null +++ b/src/pvm-packages/jam-codec/little-endian-decoder.ts @@ -0,0 +1,22 @@ +const BUFFER_SIZE = 8; + +export class LittleEndianDecoder { + private buffer = new ArrayBuffer(BUFFER_SIZE); + private valueArray = new BigUint64Array(this.buffer); + private view = new DataView(this.buffer); + + decode(bytes: Uint8Array) { + const n = bytes.length; + const noOfBytes = Math.min(n, BUFFER_SIZE); + + for (let i = 0; i < noOfBytes; i++) { + this.view.setUint8(i, bytes[i]); + } + + for (let i = n; i < BUFFER_SIZE; i++) { + this.view.setUint8(i, 0x00); + } + + return this.valueArray[0]; + } +} diff --git a/src/pvm-packages/opaque.ts b/src/pvm-packages/opaque.ts new file mode 100644 index 0000000..847a8a0 --- /dev/null +++ b/src/pvm-packages/opaque.ts @@ -0,0 +1,27 @@ +/** + * @fileoverview `Opaque` constructs a unique type which is a subset of Type with a + * specified unique token Token. It means that base type cannot be assigned to unique type by accident. + * Good examples of opaque types include: + * - JWTs or other tokens - these are special kinds of string used for authorization purposes. + * If your app uses multiple types of tokens each should be a separate opaque type to avoid confusion + * - Specific currencies - amount of different currencies shouldn't be mixed + * - Bitcoin address - special kind of string + * + * `type GithubAccessToken = Opaque;` + * `type USD = Opaque;` + * `type PositiveNumber = Opaque; + * + * More: https://github.com/ts-essentials/ts-essentials/blob/master/lib/opaque/README.md + * + * Copyright (c) 2018-2019 Chris Kaczor (github.com/krzkaczor) + */ + +type StringLiteral = Type extends string ? (string extends Type ? never : Type) : never; + +declare const __OPAQUE_TYPE__: unique symbol; + +export type WithOpaque = { + readonly [__OPAQUE_TYPE__]: Token; +}; + +export type Opaque = Token extends StringLiteral ? Type & WithOpaque : never; diff --git a/src/pvm/args-decoder/args-decoder.test.ts b/src/pvm-packages/pvm/args-decoder/args-decoder.test.ts similarity index 53% rename from src/pvm/args-decoder/args-decoder.test.ts rename to src/pvm-packages/pvm/args-decoder/args-decoder.test.ts index a771234..121a9c6 100644 --- a/src/pvm/args-decoder/args-decoder.test.ts +++ b/src/pvm-packages/pvm/args-decoder/args-decoder.test.ts @@ -1,25 +1,19 @@ import assert from "node:assert"; import { test } from "node:test"; import { Instruction } from "../instruction"; +import { Mask } from "../program-decoder/mask"; import { ArgsDecoder } from "./args-decoder"; +import { ArgumentType } from "./argument-type"; import { ImmediateDecoder } from "./decoders/immediate-decoder"; test("ArgsDecoder", async (t) => { await t.test("return empty result for instruction without args", () => { - const code = [Instruction.TRAP]; - const mask = [0b1111_1111]; + const code = new Uint8Array([Instruction.TRAP]); + const mask = new Mask(new Uint8Array([0b1111_1111])); const argsDecoder = new ArgsDecoder(code, mask); const expectedResult = { noOfInstructionsToSkip: code.length, - - firstRegisterIndex: null, - secondRegisterIndex: null, - thirdRegisterIndex: null, - - immediateDecoder1: null, - immediateDecoder2: null, - - offset: null, + type: ArgumentType.NO_ARGUMENTS, }; const result = argsDecoder.getArgs(0); @@ -28,20 +22,37 @@ test("ArgsDecoder", async (t) => { }); await t.test("return correct result for instruction with 3 regs", () => { - const code = [Instruction.ADD, 0x12, 0x03]; - const mask = [0b1111_1001]; + const code = new Uint8Array([Instruction.ADD, 0x12, 0x03]); + const mask = new Mask(new Uint8Array([0b1111_1001])); const argsDecoder = new ArgsDecoder(code, mask); const expectedResult = { noOfInstructionsToSkip: code.length, + type: ArgumentType.THREE_REGISTERS, firstRegisterIndex: 1, secondRegisterIndex: 2, thirdRegisterIndex: 3, + }; + + const result = argsDecoder.getArgs(0); + + assert.deepStrictEqual(result, expectedResult); + }); + + await t.test("return correct result for instruction with 2 regs and 1 immediate", () => { + const code = new Uint8Array([Instruction.ADD_IMM, 0x12, 0xff]); + const mask = new Mask(new Uint8Array([0b1111_1001])); + const argsDecoder = new ArgsDecoder(code, mask); + const expectedImmediateDecoder = new ImmediateDecoder(); + expectedImmediateDecoder.setBytes(new Uint8Array([0xff])); + const expectedResult = { + noOfInstructionsToSkip: code.length, + type: ArgumentType.TWO_REGISTERS_ONE_IMMEDIATE, - immediateDecoder1: null, - immediateDecoder2: null, + firstRegisterIndex: 1, + secondRegisterIndex: 2, - offset: null, + immediateDecoder1: expectedImmediateDecoder, }; const result = argsDecoder.getArgs(0); @@ -50,22 +61,19 @@ test("ArgsDecoder", async (t) => { }); await t.test("return correct result for instruction with 2 regs and 1 immediate", () => { - const code = [Instruction.ADD_IMM, 0x12, 0xff]; - const mask = [0b1111_1001]; + const code = new Uint8Array([Instruction.ADD_IMM, 0x12, 0xff]); + const mask = new Mask(new Uint8Array([0b1111_1001])); const argsDecoder = new ArgsDecoder(code, mask); const expectedImmediateDecoder = new ImmediateDecoder(); expectedImmediateDecoder.setBytes(new Uint8Array([0xff])); const expectedResult = { noOfInstructionsToSkip: code.length, + type: ArgumentType.TWO_REGISTERS_ONE_IMMEDIATE, firstRegisterIndex: 1, secondRegisterIndex: 2, - thirdRegisterIndex: null, immediateDecoder1: expectedImmediateDecoder, - immediateDecoder2: null, - - offset: null, }; const result = argsDecoder.getArgs(0); diff --git a/src/pvm/args-decoder/args-decoder.ts b/src/pvm-packages/pvm/args-decoder/args-decoder.ts similarity index 51% rename from src/pvm/args-decoder/args-decoder.ts rename to src/pvm-packages/pvm/args-decoder/args-decoder.ts index a0350ca..28c4031 100644 --- a/src/pvm/args-decoder/args-decoder.ts +++ b/src/pvm-packages/pvm/args-decoder/args-decoder.ts @@ -1,21 +1,33 @@ +import type { Instruction } from "../instruction"; +import type { Mask } from "../program-decoder/mask"; +import { createResults } from "./args-decoding-results"; import { ArgumentType } from "./argument-type"; import { ImmediateDecoder } from "./decoders/immediate-decoder"; import { RegisterIndexDecoder } from "./decoders/register-index-decoder"; import { instructionArgumentTypeMap } from "./instruction-argument-type-map"; -type NullablePartial = { [P in keyof T]?: T[P] | null }; - export type NoArgumentsResult = { + type: ArgumentType.NO_ARGUMENTS; noOfInstructionsToSkip: number; }; + export type ThreeRegistersResult = { + type: ArgumentType.THREE_REGISTERS; noOfInstructionsToSkip: number; firstRegisterIndex: number; secondRegisterIndex: number; thirdRegisterIndex: number; }; +export type TwoRegistersResult = { + type: ArgumentType.TWO_REGISTERS; + noOfInstructionsToSkip: number; + firstRegisterIndex: number; + secondRegisterIndex: number; +}; + export type TwoRegistersOneImmediateResult = { + type: ArgumentType.TWO_REGISTERS_ONE_IMMEDIATE; noOfInstructionsToSkip: number; firstRegisterIndex: number; secondRegisterIndex: number; @@ -23,6 +35,7 @@ export type TwoRegistersOneImmediateResult = { }; export type TwoRegistersTwoImmediatesResult = { + type: ArgumentType.TWO_REGISTERS_TWO_IMMEDIATE; noOfInstructionsToSkip: number; firstRegisterIndex: number; secondRegisterIndex: number; @@ -31,85 +44,35 @@ export type TwoRegistersTwoImmediatesResult = { }; export type OneOffsetResult = { + type: ArgumentType.ONE_OFFSET; noOfInstructionsToSkip: number; offset: unknown; }; -type Result = NoArgumentsResult | ThreeRegistersResult | TwoRegistersOneImmediateResult | TwoRegistersTwoImmediatesResult | OneOffsetResult; - -type AllResults = NoArgumentsResult & ThreeRegistersResult & TwoRegistersOneImmediateResult & TwoRegistersTwoImmediatesResult & OneOffsetResult; - -const createResult = (): NullablePartial => ({ - noOfInstructionsToSkip: 1, - - firstRegisterIndex: null, - secondRegisterIndex: null, - thirdRegisterIndex: null, - - immediateDecoder1: null, - immediateDecoder2: null, - - offset: null, -}); - -const MAX_ARGS_LENGTH = 24; +type Result = NoArgumentsResult | TwoRegistersResult | ThreeRegistersResult | TwoRegistersOneImmediateResult | TwoRegistersTwoImmediatesResult | OneOffsetResult; export class ArgsDecoder { private registerIndexDecoder = new RegisterIndexDecoder(); private immediateDecoder1 = new ImmediateDecoder(); // private immediateDecoder2 = new ImmediateDecoder(); - private result = createResult(); // [MaSi] because I don't want to allocate memory for each instruction + private results = createResults(); // [MaSi] because I don't want to allocate memory for each instruction constructor( - private code: number[], - private mask: number[], + private code: Uint8Array, + private mask: Mask, ) {} - private isInstruction(counter: number) { - const byteNumber = Math.floor(counter / 8); - const bitNumber = counter % 8; - const mask = 1 << bitNumber; - return (this.mask[byteNumber] & mask) > 0; - } - - private getBytesToNextInstruction(counter: number) { - let noOfBytes = 0; - for (let i = counter + 1; i <= counter + MAX_ARGS_LENGTH; i++) { - if (this.isInstruction(i)) { - break; - } - - noOfBytes++; - } - - return noOfBytes; - } - - private resetResult() { - this.result.noOfInstructionsToSkip = 1; - - this.result.firstRegisterIndex = null; - this.result.secondRegisterIndex = null; - this.result.thirdRegisterIndex = null; - - this.result.immediateDecoder1 = null; - this.result.immediateDecoder2 = null; - - this.result.offset = null; - } - getArgs(pc: number): Result { - this.resetResult(); - - const instruction = this.code[pc]; + const instruction: Instruction = this.code[pc]; const argsType = instructionArgumentTypeMap[instruction]; switch (argsType) { case ArgumentType.NO_ARGUMENTS: - return this.result as NoArgumentsResult; + return this.results[argsType]; case ArgumentType.THREE_REGISTERS: { - const result = this.result as ThreeRegistersResult; + const result = this.results[argsType]; + result.type = ArgumentType.THREE_REGISTERS; result.noOfInstructionsToSkip = 3; const firstByte = this.code[pc + 1]; const secondByte = this.code[pc + 2]; @@ -118,27 +81,34 @@ export class ArgsDecoder { result.secondRegisterIndex = this.registerIndexDecoder.getSecondIndex(); this.registerIndexDecoder.setByte(secondByte); result.thirdRegisterIndex = this.registerIndexDecoder.getSecondIndex(); - return this.result as ThreeRegistersResult; + return result; } case ArgumentType.TWO_REGISTERS_ONE_IMMEDIATE: { - const result = this.result as TwoRegistersOneImmediateResult; - + const result = this.results[argsType]; + result.type = ArgumentType.TWO_REGISTERS_ONE_IMMEDIATE; const firstByte = this.code[pc + 1]; this.registerIndexDecoder.setByte(firstByte); result.firstRegisterIndex = this.registerIndexDecoder.getFirstIndex(); result.secondRegisterIndex = this.registerIndexDecoder.getSecondIndex(); - const immediateBytes = this.getBytesToNextInstruction(pc + 1) + 1; - this.result.noOfInstructionsToSkip = 1 + immediateBytes; + const immediateBytes = this.mask.getNoOfBytesToNextInstruction(pc + 1); + result.noOfInstructionsToSkip = 1 + immediateBytes; - this.immediateDecoder1.setBytes( - new Uint8Array( - this.code.slice(pc + 2, pc + 2 + immediateBytes + 1), // TODO [MaSi] remove allocation - ), - ); + this.immediateDecoder1.setBytes(this.code.subarray(pc + 2, pc + 2 + immediateBytes + 1)); result.immediateDecoder1 = this.immediateDecoder1; - return this.result as TwoRegistersOneImmediateResult; + return result; + } + + case ArgumentType.TWO_REGISTERS: { + const result = this.results[argsType]; + result.type = ArgumentType.TWO_REGISTERS; + result.noOfInstructionsToSkip = 2; + const firstByte = this.code[pc + 1]; + this.registerIndexDecoder.setByte(firstByte); + result.firstRegisterIndex = this.registerIndexDecoder.getFirstIndex(); + result.secondRegisterIndex = this.registerIndexDecoder.getSecondIndex(); + return result; } default: diff --git a/src/pvm-packages/pvm/args-decoder/args-decoding-results.ts b/src/pvm-packages/pvm/args-decoder/args-decoding-results.ts new file mode 100644 index 0000000..375d575 --- /dev/null +++ b/src/pvm-packages/pvm/args-decoder/args-decoding-results.ts @@ -0,0 +1,54 @@ +import type { NoArgumentsResult, OneOffsetResult, ThreeRegistersResult, TwoRegistersOneImmediateResult, TwoRegistersResult, TwoRegistersTwoImmediatesResult } from "./args-decoder"; +import { ArgumentType } from "./argument-type"; +import type { ImmediateDecoder } from "./decoders/immediate-decoder"; + +const ARGUMENT_TYPE_LENGTH = Object.keys(ArgumentType).length / 2; + +type Results = [ + NoArgumentsResult, + undefined, // 1 imm + undefined, // 2 imms + OneOffsetResult, + undefined, // 1 reg 1 imm + undefined, // 1 reg 2 imms + undefined, // 1 reg 1 imm 1 offset + TwoRegistersResult, + TwoRegistersOneImmediateResult, + undefined, // 2 regs 1 offset + TwoRegistersTwoImmediatesResult, + ThreeRegistersResult, +]; + +export const createResults = () => { + const results = new Array(ARGUMENT_TYPE_LENGTH) as Results; + + results[ArgumentType.NO_ARGUMENTS] = { + type: ArgumentType.NO_ARGUMENTS, + noOfInstructionsToSkip: 1, + }; + + results[ArgumentType.TWO_REGISTERS] = { + type: ArgumentType.TWO_REGISTERS, + noOfInstructionsToSkip: 1, + firstRegisterIndex: 0, + secondRegisterIndex: 0, + }; + + results[ArgumentType.THREE_REGISTERS] = { + type: ArgumentType.THREE_REGISTERS, + noOfInstructionsToSkip: 1, + firstRegisterIndex: 0, + secondRegisterIndex: 0, + thirdRegisterIndex: 0, + }; + + results[ArgumentType.TWO_REGISTERS_ONE_IMMEDIATE] = { + type: ArgumentType.TWO_REGISTERS_ONE_IMMEDIATE, + noOfInstructionsToSkip: 1, + firstRegisterIndex: 0, + secondRegisterIndex: 0, + immediateDecoder1: null as unknown as ImmediateDecoder, + }; + + return results; +}; diff --git a/src/pvm/args-decoder/argument-type.ts b/src/pvm-packages/pvm/args-decoder/argument-type.ts similarity index 100% rename from src/pvm/args-decoder/argument-type.ts rename to src/pvm-packages/pvm/args-decoder/argument-type.ts diff --git a/src/pvm/args-decoder/decoders/immediate-decoder.test.ts b/src/pvm-packages/pvm/args-decoder/decoders/immediate-decoder.test.ts similarity index 100% rename from src/pvm/args-decoder/decoders/immediate-decoder.test.ts rename to src/pvm-packages/pvm/args-decoder/decoders/immediate-decoder.test.ts diff --git a/src/pvm/args-decoder/decoders/immediate-decoder.ts b/src/pvm-packages/pvm/args-decoder/decoders/immediate-decoder.ts similarity index 100% rename from src/pvm/args-decoder/decoders/immediate-decoder.ts rename to src/pvm-packages/pvm/args-decoder/decoders/immediate-decoder.ts diff --git a/src/pvm/args-decoder/decoders/register-index-decoder.test.ts b/src/pvm-packages/pvm/args-decoder/decoders/register-index-decoder.test.ts similarity index 100% rename from src/pvm/args-decoder/decoders/register-index-decoder.test.ts rename to src/pvm-packages/pvm/args-decoder/decoders/register-index-decoder.test.ts diff --git a/src/pvm/args-decoder/decoders/register-index-decoder.ts b/src/pvm-packages/pvm/args-decoder/decoders/register-index-decoder.ts similarity index 100% rename from src/pvm/args-decoder/decoders/register-index-decoder.ts rename to src/pvm-packages/pvm/args-decoder/decoders/register-index-decoder.ts diff --git a/src/pvm/args-decoder/instruction-argument-type-map.test.ts b/src/pvm-packages/pvm/args-decoder/instruction-argument-type-map.test.ts similarity index 100% rename from src/pvm/args-decoder/instruction-argument-type-map.test.ts rename to src/pvm-packages/pvm/args-decoder/instruction-argument-type-map.test.ts diff --git a/src/pvm/args-decoder/instruction-argument-type-map.ts b/src/pvm-packages/pvm/args-decoder/instruction-argument-type-map.ts similarity index 100% rename from src/pvm/args-decoder/instruction-argument-type-map.ts rename to src/pvm-packages/pvm/args-decoder/instruction-argument-type-map.ts diff --git a/src/pvm/assemblify.ts b/src/pvm-packages/pvm/assemblify.ts similarity index 88% rename from src/pvm/assemblify.ts rename to src/pvm-packages/pvm/assemblify.ts index 9fd3554..9c545b1 100644 --- a/src/pvm/assemblify.ts +++ b/src/pvm-packages/pvm/assemblify.ts @@ -1,4 +1,5 @@ import { Instruction } from "./instruction"; +import type { Mask } from "./program-decoder/mask"; type Byte = number; type Name = string; @@ -54,7 +55,7 @@ const instructionsWithOneRegisterOneImmediateAndOneOffset: InstructionTuple[] = ]; const instructionsWithTwoRegisters: InstructionTuple[] = [ - [Instruction.MOVE_REG, "move_reg", 0], + [Instruction.MOVE_REG, "move_reg", 2], [Instruction.SBRK, "sbrk", 0], ]; @@ -74,19 +75,19 @@ const instructionsWithTwoRegistersAndOneImmediate: InstructionTuple[] = [ [Instruction.MUL_IMM, "mul_imm", 2], [Instruction.MUL_UPPER_S_S_IMM, "mul_upper_s_s_imm", 0], [Instruction.MUL_UPPER_U_U_IMM, "mul_upper_u_u_imm", 0], - [Instruction.SET_LT_U_IMM, "set_lt_u_imm", 0], - [Instruction.SET_LT_S_IMM, "set_lt_s_imm", 0], + [Instruction.SET_LT_U_IMM, "set_lt_u_imm", 2], + [Instruction.SET_LT_S_IMM, "set_lt_s_imm", 2], [Instruction.SHLO_L_IMM, "shlo_l_imm", 2], [Instruction.SHLO_R_IMM, "shlo_r_imm", 2], [Instruction.SHAR_R_IMM, "shar_r_imm", 2], [Instruction.NEG_ADD_IMM, "neg_add_imm", 2], - [Instruction.SET_GT_U_IMM, "set_gt_u_imm", 0], - [Instruction.SET_GT_S_IMM, "set_gt_s_imm", 0], + [Instruction.SET_GT_U_IMM, "set_gt_u_imm", 2], + [Instruction.SET_GT_S_IMM, "set_gt_s_imm", 2], [Instruction.SHLO_L_IMM_ALT, "shlo_l_imm_alt", 2], [Instruction.SHLO_R_IMM_ALT, "shlo_r_imm_alt", 2], [Instruction.SHAR_R_IMM_ALT, "shar_r_imm_alt", 2], - [Instruction.CMOV_IZ_IMM, "cmov_iz_imm", 0], - [Instruction.CMOV_NZ_IMM, "cmov_nz_imm", 0], + [Instruction.CMOV_IZ_IMM, "cmov_iz_imm", 2], + [Instruction.CMOV_NZ_IMM, "cmov_nz_imm", 2], ]; const instructionsWithTwoRegistersAndOneOffset: InstructionTuple[] = [ @@ -114,13 +115,13 @@ const instructionsWithThreeRegisters: InstructionTuple[] = [ [Instruction.DIV_S, "div_s", 2], [Instruction.REM_U, "rem_u", 2], [Instruction.REM_S, "rem_s", 2], - [Instruction.SET_LT_U, "set_lt_u", 0], - [Instruction.SET_LT_S, "set_lt_s", 0], + [Instruction.SET_LT_U, "set_lt_u", 2], + [Instruction.SET_LT_S, "set_lt_s", 2], [Instruction.SHLO_L, "shlo_l", 2], [Instruction.SHLO_R, "shlo_r", 2], [Instruction.SHAR_R, "shar_r", 2], - [Instruction.CMOV_IZ, "cmov_iz", 0], - [Instruction.CMOV_NZ, "cmov_nz", 0], + [Instruction.CMOV_IZ, "cmov_iz", 2], + [Instruction.CMOV_NZ, "cmov_nz", 2], ]; const instructions: InstructionTuple[] = [...instructionsWithoutArgs, ...instructionsWithOneImmediate, ...instructionsWithTwoImmediates, ...instructionsWithOneOffset, ...instructionsWithOneRegisterAndOneImmediate, ...instructionsWithOneRegisterAndTwoImmediate, ...instructionsWithOneRegisterOneImmediateAndOneOffset, ...instructionsWithTwoRegisters, ...instructionsWithTwoRegistersAndOneImmediate, ...instructionsWithTwoRegistersAndOneOffset, ...instructionWithTwoRegistersAndTwoImmediates, ...instructionsWithThreeRegisters]; @@ -140,14 +141,10 @@ export const byteToOpCodeMap = instructions.reduce((acc, instruction) => { return acc; }, {} as ByteToOpCodeMap); -export function assemblify(program: number[], k: number[]) { +export function assemblify(program: Uint8Array, mask: Mask) { const printableProgram = program.reduce( (acc, byte, index) => { - const byteNumber = Math.floor(index / 8); - const bitNumber = index % 8; - const mask = 1 << bitNumber; - const isOpCode = (k[byteNumber] & mask) > 0; - if (isOpCode) { + if (mask.isInstruction(index)) { const instruction = byteToOpCodeMap[byte]; acc.push([instruction.name]); } else { diff --git a/src/pvm/instruction-gas-map.test.ts b/src/pvm-packages/pvm/instruction-gas-map.test.ts similarity index 100% rename from src/pvm/instruction-gas-map.test.ts rename to src/pvm-packages/pvm/instruction-gas-map.test.ts diff --git a/src/pvm/instruction-gas-map.ts b/src/pvm-packages/pvm/instruction-gas-map.ts similarity index 100% rename from src/pvm/instruction-gas-map.ts rename to src/pvm-packages/pvm/instruction-gas-map.ts diff --git a/src/pvm/instruction.ts b/src/pvm-packages/pvm/instruction.ts similarity index 100% rename from src/pvm/instruction.ts rename to src/pvm-packages/pvm/instruction.ts diff --git a/src/pvm-packages/pvm/ops-dispatchers/index.ts b/src/pvm-packages/pvm/ops-dispatchers/index.ts new file mode 100644 index 0000000..fdd441e --- /dev/null +++ b/src/pvm-packages/pvm/ops-dispatchers/index.ts @@ -0,0 +1,3 @@ +export { ThreeRegsDispatcher } from "./three-regs-dispatcher"; +export { TwoRegsOneImmDispatcher } from "./two-regs-one-imm-dispatcher"; +export { TwoRegsDispatcher } from "./two-regs-dispatcher"; diff --git a/src/pvm-packages/pvm/ops-dispatchers/three-regs-dispatcher.ts b/src/pvm-packages/pvm/ops-dispatchers/three-regs-dispatcher.ts new file mode 100644 index 0000000..c9a82dc --- /dev/null +++ b/src/pvm-packages/pvm/ops-dispatchers/three-regs-dispatcher.ts @@ -0,0 +1,96 @@ +import type { ThreeRegistersResult } from "../args-decoder/args-decoder"; +import { Instruction } from "../instruction"; +import type { BitOps, BooleanOps, MathOps, MoveOps, ShiftOps } from "../ops"; + +export class ThreeRegsDispatcher { + constructor( + private mathOps: MathOps, + private shiftOps: ShiftOps, + private bitOps: BitOps, + private booleanOps: BooleanOps, + private moveOps: MoveOps, + ) {} + + dispatch(instruction: Instruction, args: ThreeRegistersResult) { + switch (instruction) { + case Instruction.ADD: + this.mathOps.add(args.firstRegisterIndex, args.secondRegisterIndex, args.thirdRegisterIndex); + break; + + case Instruction.MUL: + this.mathOps.mul(args.firstRegisterIndex, args.secondRegisterIndex, args.thirdRegisterIndex); + break; + + case Instruction.MUL_UPPER_U_U: + this.mathOps.mulUpperUU(args.firstRegisterIndex, args.secondRegisterIndex, args.thirdRegisterIndex); + break; + + case Instruction.MUL_UPPER_S_S: + this.mathOps.mulUpperSS(args.firstRegisterIndex, args.secondRegisterIndex, args.thirdRegisterIndex); + break; + + case Instruction.MUL_UPPER_S_U: + this.mathOps.mulUpperSU(args.firstRegisterIndex, args.secondRegisterIndex, args.thirdRegisterIndex); + break; + + case Instruction.SUB: + this.mathOps.sub(args.firstRegisterIndex, args.secondRegisterIndex, args.thirdRegisterIndex); + break; + + case Instruction.DIV_S: + this.mathOps.divSigned(args.firstRegisterIndex, args.secondRegisterIndex, args.thirdRegisterIndex); + break; + + case Instruction.DIV_U: + this.mathOps.divUnsigned(args.firstRegisterIndex, args.secondRegisterIndex, args.thirdRegisterIndex); + break; + + case Instruction.REM_S: + this.mathOps.remSigned(args.firstRegisterIndex, args.secondRegisterIndex, args.thirdRegisterIndex); + break; + + case Instruction.REM_U: + this.mathOps.remUnsigned(args.firstRegisterIndex, args.secondRegisterIndex, args.thirdRegisterIndex); + break; + + case Instruction.SHLO_L: + this.shiftOps.shiftLogicalLeft(args.firstRegisterIndex, args.secondRegisterIndex, args.thirdRegisterIndex); + break; + + case Instruction.SHLO_R: + this.shiftOps.shiftLogicalRight(args.firstRegisterIndex, args.secondRegisterIndex, args.thirdRegisterIndex); + break; + + case Instruction.SHAR_R: + this.shiftOps.shiftArithmeticRight(args.firstRegisterIndex, args.secondRegisterIndex, args.thirdRegisterIndex); + break; + + case Instruction.OR: + this.bitOps.or(args.firstRegisterIndex, args.secondRegisterIndex, args.thirdRegisterIndex); + break; + + case Instruction.AND: + this.bitOps.and(args.firstRegisterIndex, args.secondRegisterIndex, args.thirdRegisterIndex); + break; + + case Instruction.XOR: + this.bitOps.xor(args.firstRegisterIndex, args.secondRegisterIndex, args.thirdRegisterIndex); + break; + + case Instruction.SET_LT_S: + this.booleanOps.setLessThanSigned(args.firstRegisterIndex, args.secondRegisterIndex, args.thirdRegisterIndex); + break; + + case Instruction.SET_LT_U: + this.booleanOps.setLessThanUnsigned(args.firstRegisterIndex, args.secondRegisterIndex, args.thirdRegisterIndex); + break; + + case Instruction.CMOV_IZ: + this.moveOps.cmovIfZero(args.firstRegisterIndex, args.secondRegisterIndex, args.thirdRegisterIndex); + break; + case Instruction.CMOV_NZ: + this.moveOps.cmovIfNotZero(args.firstRegisterIndex, args.secondRegisterIndex, args.thirdRegisterIndex); + break; + } + } +} diff --git a/src/pvm-packages/pvm/ops-dispatchers/two-regs-dispatcher.ts b/src/pvm-packages/pvm/ops-dispatchers/two-regs-dispatcher.ts new file mode 100644 index 0000000..bc1337f --- /dev/null +++ b/src/pvm-packages/pvm/ops-dispatchers/two-regs-dispatcher.ts @@ -0,0 +1,16 @@ +import type { TwoRegistersResult } from "../args-decoder/args-decoder"; +import { Instruction } from "../instruction"; +import type { MoveOps } from "../ops"; + +export class TwoRegsDispatcher { + constructor(private moveOps: MoveOps) {} + + dispatch(instruction: Instruction, args: TwoRegistersResult) { + switch (instruction) { + case Instruction.MOVE_REG: { + this.moveOps.moveRegister(args.firstRegisterIndex, args.secondRegisterIndex); + break; + } + } + } +} diff --git a/src/pvm-packages/pvm/ops-dispatchers/two-regs-one-imm-dispatcher.ts b/src/pvm-packages/pvm/ops-dispatchers/two-regs-one-imm-dispatcher.ts new file mode 100644 index 0000000..f225bc7 --- /dev/null +++ b/src/pvm-packages/pvm/ops-dispatchers/two-regs-one-imm-dispatcher.ts @@ -0,0 +1,97 @@ +import type { TwoRegistersOneImmediateResult } from "../args-decoder/args-decoder"; +import { Instruction } from "../instruction"; +import type { BitOps, BooleanOps, MathOps, MoveOps, ShiftOps } from "../ops"; + +export class TwoRegsOneImmDispatcher { + constructor( + private mathOps: MathOps, + private shiftOps: ShiftOps, + private bitOps: BitOps, + private booleanOps: BooleanOps, + private moveOps: MoveOps, + ) {} + + dispatch(instruction: Instruction, args: TwoRegistersOneImmediateResult) { + switch (instruction) { + case Instruction.ADD_IMM: + this.mathOps.addImmediate(args.firstRegisterIndex, args.immediateDecoder1.getUnsigned(), args.secondRegisterIndex); + break; + + case Instruction.MUL_IMM: + this.mathOps.mulImmediate(args.firstRegisterIndex, args.immediateDecoder1.getSigned(), args.secondRegisterIndex); + break; + + case Instruction.MUL_UPPER_U_U_IMM: + this.mathOps.mulImmediate(args.firstRegisterIndex, args.immediateDecoder1.getUnsigned(), args.secondRegisterIndex); + break; + + case Instruction.MUL_UPPER_S_S_IMM: + this.mathOps.mulImmediate(args.firstRegisterIndex, args.immediateDecoder1.getSigned(), args.secondRegisterIndex); + break; + + case Instruction.NEG_ADD_IMM: + this.mathOps.negAddImmediate(args.firstRegisterIndex, args.immediateDecoder1.getUnsigned(), args.secondRegisterIndex); + break; + + case Instruction.SHLO_L_IMM: + this.shiftOps.shiftLogicalLeftImmediate(args.firstRegisterIndex, args.immediateDecoder1.getUnsigned(), args.secondRegisterIndex); + break; + + case Instruction.SHLO_L_IMM_ALT: + this.shiftOps.shiftLogicalLeftImmediateAlternative(args.firstRegisterIndex, args.immediateDecoder1.getUnsigned(), args.secondRegisterIndex); + break; + + case Instruction.SHLO_R_IMM: + this.shiftOps.shiftLogicalRightImmediate(args.firstRegisterIndex, args.immediateDecoder1.getUnsigned(), args.secondRegisterIndex); + break; + + case Instruction.SHLO_R_IMM_ALT: + this.shiftOps.shiftLogicalRightImmediateAlternative(args.firstRegisterIndex, args.immediateDecoder1.getUnsigned(), args.secondRegisterIndex); + break; + + case Instruction.SHAR_R_IMM: + this.shiftOps.shiftArithmeticRightImmediate(args.firstRegisterIndex, args.immediateDecoder1.getSigned(), args.secondRegisterIndex); + break; + + case Instruction.SHAR_R_IMM_ALT: + this.shiftOps.shiftArithmeticRightImmediateAlternative(args.firstRegisterIndex, args.immediateDecoder1.getSigned(), args.secondRegisterIndex); + break; + + case Instruction.OR_IMM: + this.bitOps.orImmediate(args.firstRegisterIndex, args.immediateDecoder1.getUnsigned(), args.secondRegisterIndex); + break; + + case Instruction.AND_IMM: + this.bitOps.andImmediate(args.firstRegisterIndex, args.immediateDecoder1.getUnsigned(), args.secondRegisterIndex); + break; + + case Instruction.XOR_IMM: + this.bitOps.xorImmediate(args.firstRegisterIndex, args.immediateDecoder1.getUnsigned(), args.secondRegisterIndex); + break; + + case Instruction.SET_LT_S_IMM: + this.booleanOps.setLessThanSignedImmediate(args.firstRegisterIndex, args.immediateDecoder1.getSigned(), args.secondRegisterIndex); + break; + + case Instruction.SET_LT_U_IMM: + this.booleanOps.setLessThanUnsignedImmediate(args.firstRegisterIndex, args.immediateDecoder1.getUnsigned(), args.secondRegisterIndex); + break; + + case Instruction.SET_GT_S_IMM: + this.booleanOps.setGreaterThanSignedImmediate(args.firstRegisterIndex, args.immediateDecoder1.getSigned(), args.secondRegisterIndex); + break; + + case Instruction.SET_GT_U_IMM: + this.booleanOps.setGreaterThanUnsignedImmediate(args.firstRegisterIndex, args.immediateDecoder1.getUnsigned(), args.secondRegisterIndex); + break; + + case Instruction.CMOV_IZ_IMM: + this.moveOps.cmovIfZeroImmediate(args.firstRegisterIndex, args.immediateDecoder1.getUnsigned(), args.secondRegisterIndex); + break; + + case Instruction.CMOV_NZ_IMM: + this.moveOps.cmovIfNotZeroImmediate(args.firstRegisterIndex, args.immediateDecoder1.getUnsigned(), args.secondRegisterIndex); + break; + } + } +} diff --git a/src/pvm/ops/base-ops.ts b/src/pvm-packages/pvm/ops/base-ops.ts similarity index 100% rename from src/pvm/ops/base-ops.ts rename to src/pvm-packages/pvm/ops/base-ops.ts diff --git a/src/pvm/ops/bit-ops.test.ts b/src/pvm-packages/pvm/ops/bit-ops.test.ts similarity index 100% rename from src/pvm/ops/bit-ops.test.ts rename to src/pvm-packages/pvm/ops/bit-ops.test.ts diff --git a/src/pvm/ops/bit-ops.ts b/src/pvm-packages/pvm/ops/bit-ops.ts similarity index 100% rename from src/pvm/ops/bit-ops.ts rename to src/pvm-packages/pvm/ops/bit-ops.ts diff --git a/src/pvm-packages/pvm/ops/boolean-ops.test.ts b/src/pvm-packages/pvm/ops/boolean-ops.test.ts new file mode 100644 index 0000000..53624fe --- /dev/null +++ b/src/pvm-packages/pvm/ops/boolean-ops.test.ts @@ -0,0 +1,190 @@ +import assert from "node:assert"; +import { test } from "node:test"; + +import { Registers } from "../registers"; +import { BooleanOps } from "./boolean-ops"; + +const FIRST_REGISTER = 0; +const RESULT_REGISTER = 1; +const SECOND_REGISTER = 2; +const getRegisters = (data: number[]) => { + const regs = new Registers(); + + for (const [i, byte] of data.entries()) { + regs.asUnsigned[i] = byte; + } + + return regs; +}; + +test("BooleanOps", async (t) => { + await t.test("setLessThanUnsignedImmediate - true", () => { + const firstValue = 1; + const secondValue = 2; + const initialResultRegister = 3; + const resultValue = 1; + const regs = getRegisters([firstValue, initialResultRegister]); + const bitOps = new BooleanOps(regs); + + bitOps.setLessThanUnsignedImmediate(FIRST_REGISTER, secondValue, RESULT_REGISTER); + + assert.strictEqual(regs.asUnsigned[RESULT_REGISTER], resultValue); + }); + + await t.test("setLessThanUnsignedImmediate - false", () => { + const firstValue = 3; + const secondValue = 2; + const initialResultRegister = 3; + const resultValue = 0; + const regs = getRegisters([firstValue, initialResultRegister]); + const bitOps = new BooleanOps(regs); + + bitOps.setLessThanUnsignedImmediate(FIRST_REGISTER, secondValue, RESULT_REGISTER); + + assert.strictEqual(regs.asUnsigned[RESULT_REGISTER], resultValue); + }); + + await t.test("setGreaterThanUnsignedImmediate - true", () => { + const firstValue = 3; + const secondValue = 2; + const initialResultRegister = 3; + const resultValue = 1; + const regs = getRegisters([firstValue, initialResultRegister]); + const bitOps = new BooleanOps(regs); + + bitOps.setGreaterThanUnsignedImmediate(FIRST_REGISTER, secondValue, RESULT_REGISTER); + + assert.strictEqual(regs.asUnsigned[RESULT_REGISTER], resultValue); + }); + + await t.test("setGreaterThanUnsignedImmediate - false", () => { + const firstValue = 1; + const secondValue = 2; + const initialResultRegister = 3; + const resultValue = 0; + const regs = getRegisters([firstValue, initialResultRegister]); + const bitOps = new BooleanOps(regs); + + bitOps.setGreaterThanUnsignedImmediate(FIRST_REGISTER, secondValue, RESULT_REGISTER); + + assert.strictEqual(regs.asUnsigned[RESULT_REGISTER], resultValue); + }); + + await t.test("setLessThanSignedImmediate - true", () => { + const firstValue = -3; + const secondValue = -2; + const initialResultRegister = 3; + const resultValue = 1; + const regs = new Registers(); + regs.asSigned[FIRST_REGISTER] = firstValue; + regs.asSigned[RESULT_REGISTER] = initialResultRegister; + const bitOps = new BooleanOps(regs); + + bitOps.setLessThanSignedImmediate(FIRST_REGISTER, secondValue, RESULT_REGISTER); + + assert.strictEqual(regs.asUnsigned[RESULT_REGISTER], resultValue); + }); + + await t.test("setLessThanSignedImmediate - false", () => { + const firstValue = -1; + const secondValue = -2; + const initialResultRegister = 3; + const resultValue = 0; + const regs = new Registers(); + regs.asSigned[FIRST_REGISTER] = firstValue; + regs.asSigned[RESULT_REGISTER] = initialResultRegister; + const bitOps = new BooleanOps(regs); + + bitOps.setLessThanSignedImmediate(FIRST_REGISTER, secondValue, RESULT_REGISTER); + + assert.strictEqual(regs.asUnsigned[RESULT_REGISTER], resultValue); + }); + + await t.test("setGreaterThanSignedImmediate - true", () => { + const firstValue = -1; + const secondValue = -2; + const initialResultRegister = 3; + const resultValue = 1; + const regs = new Registers(); + regs.asSigned[FIRST_REGISTER] = firstValue; + regs.asSigned[RESULT_REGISTER] = initialResultRegister; + const bitOps = new BooleanOps(regs); + + bitOps.setGreaterThanSignedImmediate(FIRST_REGISTER, secondValue, RESULT_REGISTER); + + assert.strictEqual(regs.asUnsigned[RESULT_REGISTER], resultValue); + }); + + await t.test("setGreaterThanSignedImmediate - false", () => { + const firstValue = -3; + const secondValue = -2; + const initialResultRegister = 3; + const resultValue = 0; + const regs = new Registers(); + regs.asSigned[FIRST_REGISTER] = firstValue; + regs.asSigned[RESULT_REGISTER] = initialResultRegister; + const bitOps = new BooleanOps(regs); + + bitOps.setGreaterThanSignedImmediate(FIRST_REGISTER, secondValue, RESULT_REGISTER); + + assert.strictEqual(regs.asUnsigned[RESULT_REGISTER], resultValue); + }); + + await t.test("setLessThanUnsigned - true", () => { + const firstValue = 2; + const secondValue = 1; + const initialResultRegister = 3; + const resultValue = 1; + const regs = getRegisters([firstValue, initialResultRegister, secondValue]); + const bitOps = new BooleanOps(regs); + + bitOps.setLessThanUnsigned(FIRST_REGISTER, SECOND_REGISTER, RESULT_REGISTER); + + assert.strictEqual(regs.asUnsigned[RESULT_REGISTER], resultValue); + }); + + await t.test("setLessThanUnsigned - false", () => { + const firstValue = 2; + const secondValue = 3; + const initialResultRegister = 3; + const resultValue = 0; + const regs = getRegisters([firstValue, initialResultRegister, secondValue]); + const bitOps = new BooleanOps(regs); + + bitOps.setLessThanUnsigned(FIRST_REGISTER, SECOND_REGISTER, RESULT_REGISTER); + + assert.strictEqual(regs.asUnsigned[RESULT_REGISTER], resultValue); + }); + + await t.test("setLessThanSigned - true", () => { + const firstValue = -2; + const secondValue = -3; + const initialResultRegister = 3; + const resultValue = 1; + const regs = new Registers(); + regs.asSigned[FIRST_REGISTER] = firstValue; + regs.asSigned[SECOND_REGISTER] = secondValue; + regs.asSigned[RESULT_REGISTER] = initialResultRegister; + const bitOps = new BooleanOps(regs); + + bitOps.setLessThanSigned(FIRST_REGISTER, SECOND_REGISTER, RESULT_REGISTER); + + assert.strictEqual(regs.asUnsigned[RESULT_REGISTER], resultValue); + }); + + await t.test("setLessThanSigned - false", () => { + const firstValue = -2; + const secondValue = -1; + const initialResultRegister = 3; + const resultValue = 0; + const regs = new Registers(); + regs.asSigned[FIRST_REGISTER] = firstValue; + regs.asSigned[SECOND_REGISTER] = secondValue; + regs.asSigned[RESULT_REGISTER] = initialResultRegister; + const bitOps = new BooleanOps(regs); + + bitOps.setLessThanSigned(FIRST_REGISTER, SECOND_REGISTER, RESULT_REGISTER); + + assert.strictEqual(regs.asUnsigned[RESULT_REGISTER], resultValue); + }); +}); diff --git a/src/pvm-packages/pvm/ops/boolean-ops.ts b/src/pvm-packages/pvm/ops/boolean-ops.ts new file mode 100644 index 0000000..63c3bf7 --- /dev/null +++ b/src/pvm-packages/pvm/ops/boolean-ops.ts @@ -0,0 +1,27 @@ +import { BaseOps } from "./base-ops"; + +export class BooleanOps extends BaseOps { + setLessThanSignedImmediate(firstIndex: number, immediateValue: number, resultIndex: number) { + this.regs.asUnsigned[resultIndex] = this.regs.asSigned[firstIndex] < immediateValue ? 1 : 0; + } + + setLessThanUnsignedImmediate(firstIndex: number, immediateValue: number, resultIndex: number) { + this.regs.asUnsigned[resultIndex] = this.regs.asUnsigned[firstIndex] < immediateValue ? 1 : 0; + } + + setLessThanSigned(firstIndex: number, secondIndex: number, resultIndex: number) { + this.setLessThanSignedImmediate(secondIndex, this.regs.asSigned[firstIndex], resultIndex); + } + + setLessThanUnsigned(firstIndex: number, secondIndex: number, resultIndex: number) { + this.setLessThanUnsignedImmediate(secondIndex, this.regs.asUnsigned[firstIndex], resultIndex); + } + + setGreaterThanSignedImmediate(firstIndex: number, immediateValue: number, resultIndex: number) { + this.regs.asUnsigned[resultIndex] = this.regs.asSigned[firstIndex] > immediateValue ? 1 : 0; + } + + setGreaterThanUnsignedImmediate(firstIndex: number, immediateValue: number, resultIndex: number) { + this.regs.asUnsigned[resultIndex] = this.regs.asUnsigned[firstIndex] > immediateValue ? 1 : 0; + } +} diff --git a/src/pvm-packages/pvm/ops/index.ts b/src/pvm-packages/pvm/ops/index.ts new file mode 100644 index 0000000..1d864a6 --- /dev/null +++ b/src/pvm-packages/pvm/ops/index.ts @@ -0,0 +1,5 @@ +export { BitOps } from "./bit-ops"; +export { ShiftOps } from "./shift-ops"; +export { MathOps } from "./math-ops"; +export { BooleanOps } from "./boolean-ops"; +export { MoveOps } from "./move-ops"; diff --git a/src/pvm/ops/math-consts.ts b/src/pvm-packages/pvm/ops/math-consts.ts similarity index 100% rename from src/pvm/ops/math-consts.ts rename to src/pvm-packages/pvm/ops/math-consts.ts diff --git a/src/pvm/ops/math-ops.test.ts b/src/pvm-packages/pvm/ops/math-ops.test.ts similarity index 100% rename from src/pvm/ops/math-ops.test.ts rename to src/pvm-packages/pvm/ops/math-ops.test.ts diff --git a/src/pvm/ops/math-ops.ts b/src/pvm-packages/pvm/ops/math-ops.ts similarity index 100% rename from src/pvm/ops/math-ops.ts rename to src/pvm-packages/pvm/ops/math-ops.ts diff --git a/src/pvm-packages/pvm/ops/move-ops.test.ts b/src/pvm-packages/pvm/ops/move-ops.test.ts new file mode 100644 index 0000000..b09001c --- /dev/null +++ b/src/pvm-packages/pvm/ops/move-ops.test.ts @@ -0,0 +1,128 @@ +import assert from "node:assert"; +import { test } from "node:test"; + +import { Registers } from "../registers"; +import { MoveOps } from "./move-ops"; + +const FIRST_REGISTER = 0; +const SECOND_REGISTER = 1; +const RESULT_REGISTER = 12; + +const getRegisters = (data: number[]) => { + const regs = new Registers(); + + for (const [i, byte] of data.entries()) { + regs.asUnsigned[i] = byte; + } + + return regs; +}; + +test("MoveOps", async (t) => { + await t.test("moveRegister", () => { + const firstValue = 5; + const resultValue = firstValue; + const regs = getRegisters([firstValue]); + const moveOps = new MoveOps(regs); + + moveOps.moveRegister(FIRST_REGISTER, RESULT_REGISTER); + + assert.strictEqual(regs.asUnsigned[RESULT_REGISTER], resultValue); + }); + + await t.test("cmovIfZero (condition satisfied)", () => { + const firstValue = 0; + const secondValue = 5; + const resultValue = secondValue; + const regs = getRegisters([firstValue, secondValue]); + const moveOps = new MoveOps(regs); + + moveOps.cmovIfZero(FIRST_REGISTER, SECOND_REGISTER, RESULT_REGISTER); + + assert.strictEqual(regs.asUnsigned[RESULT_REGISTER], resultValue); + }); + + await t.test("cmovIfZero (condition not satisfied)", () => { + const firstValue = 3; + const secondValue = 5; + const resultValue = 0; + const regs = getRegisters([firstValue, secondValue]); + const moveOps = new MoveOps(regs); + + moveOps.cmovIfZero(FIRST_REGISTER, SECOND_REGISTER, RESULT_REGISTER); + + assert.strictEqual(regs.asUnsigned[RESULT_REGISTER], resultValue); + }); + + await t.test("cmovIfNotZero (condition satisfied)", () => { + const firstValue = 3; + const secondValue = 5; + const resultValue = secondValue; + const regs = getRegisters([firstValue, secondValue]); + const moveOps = new MoveOps(regs); + + moveOps.cmovIfNotZero(FIRST_REGISTER, SECOND_REGISTER, RESULT_REGISTER); + + assert.strictEqual(regs.asUnsigned[RESULT_REGISTER], resultValue); + }); + + await t.test("cmovIfNotZero (condition not satisfied)", () => { + const firstValue = 0; + const secondValue = 5; + const resultValue = 0; + const regs = getRegisters([firstValue, secondValue]); + const moveOps = new MoveOps(regs); + + moveOps.cmovIfNotZero(FIRST_REGISTER, SECOND_REGISTER, RESULT_REGISTER); + + assert.strictEqual(regs.asUnsigned[RESULT_REGISTER], resultValue); + }); + + await t.test("cmovIfZeroImmediate (condition satisfied)", () => { + const firstValue = 0; + const secondValue = 5; + const resultValue = secondValue; + const regs = getRegisters([firstValue]); + const moveOps = new MoveOps(regs); + + moveOps.cmovIfZeroImmediate(FIRST_REGISTER, secondValue, RESULT_REGISTER); + + assert.strictEqual(regs.asUnsigned[RESULT_REGISTER], resultValue); + }); + + await t.test("cmovIfZeroImmediate (condition not satisfied)", () => { + const firstValue = 3; + const secondValue = 5; + const resultValue = 0; + const regs = getRegisters([firstValue]); + const moveOps = new MoveOps(regs); + + moveOps.cmovIfZeroImmediate(FIRST_REGISTER, secondValue, RESULT_REGISTER); + + assert.strictEqual(regs.asUnsigned[RESULT_REGISTER], resultValue); + }); + + await t.test("cmovIfNotZeroImmediate (condition satisfied)", () => { + const firstValue = 3; + const secondValue = 5; + const resultValue = secondValue; + const regs = getRegisters([firstValue]); + const moveOps = new MoveOps(regs); + + moveOps.cmovIfNotZeroImmediate(FIRST_REGISTER, secondValue, RESULT_REGISTER); + + assert.strictEqual(regs.asUnsigned[RESULT_REGISTER], resultValue); + }); + + await t.test("cmovIfNotZeroImmediate (condition not satisfied)", () => { + const firstValue = 0; + const secondValue = 5; + const resultValue = 0; + const regs = getRegisters([firstValue]); + const moveOps = new MoveOps(regs); + + moveOps.cmovIfNotZeroImmediate(FIRST_REGISTER, secondValue, RESULT_REGISTER); + + assert.strictEqual(regs.asUnsigned[RESULT_REGISTER], resultValue); + }); +}); diff --git a/src/pvm-packages/pvm/ops/move-ops.ts b/src/pvm-packages/pvm/ops/move-ops.ts new file mode 100644 index 0000000..20e6435 --- /dev/null +++ b/src/pvm-packages/pvm/ops/move-ops.ts @@ -0,0 +1,27 @@ +import { BaseOps } from "./base-ops"; + +export class MoveOps extends BaseOps { + cmovIfZeroImmediate(firstIndex: number, immediateValue: number, resultIndex: number) { + if (this.regs.asUnsigned[firstIndex] === 0) { + this.regs.asUnsigned[resultIndex] = immediateValue; + } + } + + cmovIfNotZeroImmediate(firstIndex: number, immediateValue: number, resultIndex: number) { + if (this.regs.asUnsigned[firstIndex] !== 0) { + this.regs.asUnsigned[resultIndex] = immediateValue; + } + } + + cmovIfZero(firstIndex: number, secondIndex: number, resultIndex: number) { + this.cmovIfZeroImmediate(firstIndex, this.regs.asUnsigned[secondIndex], resultIndex); + } + + cmovIfNotZero(firstIndex: number, secondIndex: number, resultIndex: number) { + this.cmovIfNotZeroImmediate(firstIndex, this.regs.asUnsigned[secondIndex], resultIndex); + } + + moveRegister(firstIndex: number, resultIndex: number) { + this.regs.asUnsigned[resultIndex] = this.regs.asUnsigned[firstIndex]; + } +} diff --git a/src/pvm/ops/shift-ops.test.ts b/src/pvm-packages/pvm/ops/shift-ops.test.ts similarity index 100% rename from src/pvm/ops/shift-ops.test.ts rename to src/pvm-packages/pvm/ops/shift-ops.test.ts diff --git a/src/pvm/ops/shift-ops.ts b/src/pvm-packages/pvm/ops/shift-ops.ts similarity index 100% rename from src/pvm/ops/shift-ops.ts rename to src/pvm-packages/pvm/ops/shift-ops.ts diff --git a/src/pvm-packages/pvm/program-decoder/mask.test.ts b/src/pvm-packages/pvm/program-decoder/mask.test.ts new file mode 100644 index 0000000..9d0b680 --- /dev/null +++ b/src/pvm-packages/pvm/program-decoder/mask.test.ts @@ -0,0 +1,85 @@ +import assert from "node:assert"; +import { test } from "node:test"; + +import { Mask } from "./mask"; + +test("Mask - isInstruction", async (t) => { + await t.test("should return true (single byte)", () => { + const input = [0b1111_1001]; + const index = 0; + const expectedResult = true; + const mask = new Mask(new Uint8Array(input)); + + const result = mask.isInstruction(index); + + assert.strictEqual(result, expectedResult); + }); + + await t.test("should return false (single byte)", () => { + const input = [0b1111_1001]; + const index = 1; + const expectedResult = false; + const mask = new Mask(new Uint8Array(input)); + + const result = mask.isInstruction(index); + + assert.strictEqual(result, expectedResult); + }); + + await t.test("should return true (2 bytes)", () => { + const input = [0x0, 0b1111_1001]; + const index = 8; + const expectedResult = true; + const mask = new Mask(new Uint8Array(input)); + + const result = mask.isInstruction(index); + + assert.strictEqual(result, expectedResult); + }); + + await t.test("should return false (2 bytes)", () => { + const input = [0xff, 0b1111_1001]; + const index = 10; + const expectedResult = false; + const mask = new Mask(new Uint8Array(input)); + + const result = mask.isInstruction(index); + + assert.strictEqual(result, expectedResult); + }); +}); + +test("Mask - getNoOfBytesToNextInstruction", async (t) => { + await t.test("number of 0s between two 1 in single byte", () => { + const input = [0b1111_1001]; + const index = 0; + const expectedResult = 3; + const mask = new Mask(new Uint8Array(input)); + + const result = mask.getNoOfBytesToNextInstruction(index); + + assert.strictEqual(result, expectedResult); + }); + + await t.test("number of 0s from current index (that is 0) to next 1", () => { + const input = [0b1111_1001]; + const index = 1; + const expectedResult = 2; + const mask = new Mask(new Uint8Array(input)); + + const result = mask.getNoOfBytesToNextInstruction(index); + + assert.strictEqual(result, expectedResult); + }); + + await t.test("number of 0s between two 1 in single byte", () => { + const input = [0b0001_1001, 0b0001_1000]; + const index = 4; + const expectedResult = 7; + const mask = new Mask(new Uint8Array(input)); + + const result = mask.getNoOfBytesToNextInstruction(index); + + assert.strictEqual(result, expectedResult); + }); +}); diff --git a/src/pvm-packages/pvm/program-decoder/mask.ts b/src/pvm-packages/pvm/program-decoder/mask.ts new file mode 100644 index 0000000..c95603a --- /dev/null +++ b/src/pvm-packages/pvm/program-decoder/mask.ts @@ -0,0 +1,25 @@ +const MAX_ARGS_LENGTH = 24; + +export class Mask { + constructor(private mask: Uint8Array) {} + + isInstruction(index: number) { + const byteNumber = Math.floor(index / 8); + const bitNumber = index % 8; + const singleBitMask = 1 << bitNumber; + return (this.mask[byteNumber] & singleBitMask) > 0; + } + + getNoOfBytesToNextInstruction(index: number) { + let noOfBytes = 0; + for (let i = index + 1; i <= index + MAX_ARGS_LENGTH; i++) { + noOfBytes++; + + if (this.isInstruction(i)) { + break; + } + } + + return noOfBytes; + } +} diff --git a/src/pvm-packages/pvm/program-decoder/program-decoder.test.ts b/src/pvm-packages/pvm/program-decoder/program-decoder.test.ts new file mode 100644 index 0000000..66b4923 --- /dev/null +++ b/src/pvm-packages/pvm/program-decoder/program-decoder.test.ts @@ -0,0 +1,29 @@ +import assert from "node:assert"; +import { test } from "node:test"; + +import { Mask } from "./mask"; +import { ProgramDecoder } from "./program-decoder"; + +const code = [4, 7, 246, 4, 8, 10, 41, 135, 4, 0, 4, 7, 239, 190, 173, 222]; + +const bitMask = [73, 6]; + +const program = [0, 0, 16, ...code, ...bitMask]; + +test("ProgramDecoder", async (t) => { + await t.test("should corectly decode instructions", () => { + const programDecoder = new ProgramDecoder(new Uint8Array(program)); + + const result = programDecoder.getCode(); + + assert.deepStrictEqual(result, new Uint8Array(code)); + }); + + await t.test("should corectly decode mask", () => { + const programDecoder = new ProgramDecoder(new Uint8Array(program)); + + const result = programDecoder.getMask(); + + assert.deepStrictEqual(result, new Mask(new Uint8Array(bitMask))); + }); +}); diff --git a/src/pvm-packages/pvm/program-decoder/program-decoder.ts b/src/pvm-packages/pvm/program-decoder/program-decoder.ts new file mode 100644 index 0000000..885aaaf --- /dev/null +++ b/src/pvm-packages/pvm/program-decoder/program-decoder.ts @@ -0,0 +1,42 @@ +import { decodeNaturalNumber } from "../../jam-codec/decode-natural-number"; +import { Mask } from "./mask"; + +export class ProgramDecoder { + private code: Uint8Array; + private mask: Mask; + + constructor(rawProgram: Uint8Array) { + const { code, mask } = this.decodeProgram(rawProgram); + + this.code = new Uint8Array(code); + this.mask = new Mask(mask); + } + + private decodeProgram(program: Uint8Array) { + const { value: jumpTableLength, bytesToSkip: firstNumberLength } = decodeNaturalNumber(program); + const jumpTableItemSize = program[firstNumberLength]; + const { value: codeLength, bytesToSkip: thirdNumberLenght } = decodeNaturalNumber(program.subarray(firstNumberLength + 1)); + const jumpTableFirstByteIndex = firstNumberLength + 1 + thirdNumberLenght; + const jumpTableLengthInBytes = Number(jumpTableLength) * jumpTableItemSize; + const jumpTable = program.subarray(jumpTableFirstByteIndex, jumpTableFirstByteIndex + jumpTableLengthInBytes); + const codeFirstIndex = jumpTableFirstByteIndex + jumpTableLengthInBytes; + const code = program.subarray(codeFirstIndex, codeFirstIndex + Number(codeLength)); + const maskFirstIndex = codeFirstIndex + Number(codeLength); + const maskLengthInBytes = Math.ceil(Number(codeLength) / 8); + const mask = program.subarray(maskFirstIndex, maskFirstIndex + maskLengthInBytes); + + return { + mask, + code, + jumpTable, + }; + } + + getMask() { + return this.mask; + } + + getCode() { + return this.code; + } +} diff --git a/src/pvm-packages/pvm/pvm.ts b/src/pvm-packages/pvm/pvm.ts new file mode 100644 index 0000000..6c73063 --- /dev/null +++ b/src/pvm-packages/pvm/pvm.ts @@ -0,0 +1,131 @@ +import { ArgsDecoder } from "./args-decoder/args-decoder"; +import { ArgumentType } from "./args-decoder/argument-type"; +import { assemblify } from "./assemblify"; +import { Instruction } from "./instruction"; +import { instructionGasMap } from "./instruction-gas-map"; +import { BitOps, BooleanOps, MathOps, MoveOps, ShiftOps } from "./ops"; +import { ThreeRegsDispatcher, TwoRegsDispatcher, TwoRegsOneImmDispatcher } from "./ops-dispatchers"; +import type { Mask } from "./program-decoder/mask"; +import { ProgramDecoder } from "./program-decoder/program-decoder"; +import { NO_OF_REGISTERS, Registers } from "./registers"; + +export type InitialState = { + regs?: RegistersArray; + pc?: number; + pageMap?: PageMapItem[]; + memory?: MemoryChunkItem[]; + gas?: number; +}; + +type MemoryChunkItem = { + address: number; + contents: number[]; +}; + +type PageMapItem = { + address: number; + length: number; + "is-writable": boolean; +}; + +type GrowToSize = A["length"] extends N ? A : GrowToSize; + +type FixedArray = GrowToSize; + +export type RegistersArray = FixedArray; + +export class Pvm { + private pc = 0; + private registers = new Registers(); + private gas: number; + private pageMap: PageMapItem[]; + private memory: MemoryChunkItem[]; + private status: "trap" | "halt" = "trap"; + private argsDecoder: ArgsDecoder; + private code: Uint8Array; + private mask: Mask; + private threeRegsDispatcher: ThreeRegsDispatcher; + private twoRegsOneImmDispatcher: TwoRegsOneImmDispatcher; + private twoRegsDispatcher: TwoRegsDispatcher; + + constructor(rawProgram: Uint8Array, initialState: InitialState = {}) { + const programDecoder = new ProgramDecoder(rawProgram); + this.code = programDecoder.getCode(); + this.mask = programDecoder.getMask(); + + this.pc = initialState.pc ?? 0; + + for (let i = 0; i < NO_OF_REGISTERS; i++) { + this.registers.asUnsigned[i] = initialState.regs?.[i] ?? 0; + } + this.gas = initialState.gas ?? 0; + this.pageMap = initialState.pageMap ?? []; + this.memory = initialState.memory ?? []; + this.argsDecoder = new ArgsDecoder(this.code, this.mask); + const mathOps = new MathOps(this.registers); + const shiftOps = new ShiftOps(this.registers); + const bitOps = new BitOps(this.registers); + const booleanOps = new BooleanOps(this.registers); + const moveOps = new MoveOps(this.registers); + + this.threeRegsDispatcher = new ThreeRegsDispatcher(mathOps, shiftOps, bitOps, booleanOps, moveOps); + this.twoRegsOneImmDispatcher = new TwoRegsOneImmDispatcher(mathOps, shiftOps, bitOps, booleanOps, moveOps); + this.twoRegsDispatcher = new TwoRegsDispatcher(moveOps); + } + + printProgram() { + const p = assemblify(this.code, this.mask); + console.table(p); + + return p; + } + + runProgram() { + while (this.pc < this.code.length) { + const currentInstruction = this.code[this.pc]; + this.gas -= instructionGasMap[currentInstruction]; + + if (this.gas < 0) { + // TODO [MaSi]: to handle + } + const args = this.argsDecoder.getArgs(this.pc); + + switch (args.type) { + case ArgumentType.NO_ARGUMENTS: + if (currentInstruction === Instruction.TRAP) { + this.status = "trap"; + return; + } + break; + case ArgumentType.TWO_REGISTERS: + this.twoRegsDispatcher.dispatch(currentInstruction, args); + break; + case ArgumentType.THREE_REGISTERS: + this.threeRegsDispatcher.dispatch(currentInstruction, args); + break; + case ArgumentType.TWO_REGISTERS_ONE_IMMEDIATE: + this.twoRegsOneImmDispatcher.dispatch(currentInstruction, args); + break; + } + + this.pc += args.noOfInstructionsToSkip; + } + } + + getState() { + const regs = Array(NO_OF_REGISTERS); + + for (let i = 0; i < NO_OF_REGISTERS; i++) { + regs[i] = Number(this.registers.asUnsigned[i]); + } + + return { + pc: this.pc, + regs, + gas: this.gas, + pageMap: this.pageMap, + memory: this.memory, + status: this.status, + }; + } +} diff --git a/src/pvm/registers.test.ts b/src/pvm-packages/pvm/registers.test.ts similarity index 100% rename from src/pvm/registers.test.ts rename to src/pvm-packages/pvm/registers.test.ts diff --git a/src/pvm/registers.ts b/src/pvm-packages/pvm/registers.ts similarity index 100% rename from src/pvm/registers.ts rename to src/pvm-packages/pvm/registers.ts diff --git a/src/pvm/result.ts b/src/pvm-packages/pvm/result.ts similarity index 100% rename from src/pvm/result.ts rename to src/pvm-packages/pvm/result.ts diff --git a/src/pvm/fixed-array.ts b/src/pvm/fixed-array.ts deleted file mode 100644 index b92c2b6..0000000 --- a/src/pvm/fixed-array.ts +++ /dev/null @@ -1,3 +0,0 @@ -type GrowToSize = A["length"] extends N ? A : GrowToSize; - -export type FixedArray = GrowToSize; diff --git a/src/pvm/pvm.ts b/src/pvm/pvm.ts deleted file mode 100644 index 73a5d21..0000000 --- a/src/pvm/pvm.ts +++ /dev/null @@ -1,278 +0,0 @@ -import * as $ from "scale-codec"; -import type { FixedArray } from "./fixed-array"; -import { ArgsDecoder, type ThreeRegistersResult, type TwoRegistersOneImmediateResult } from "./args-decoder/args-decoder"; -import { assemblify } from "./assemblify"; -import { Instruction } from "./instruction"; -import { instructionGasMap } from "./instruction-gas-map"; -import { BitOps } from "./ops/bit-ops"; -import { MathOps } from "./ops/math-ops"; -import { ShiftOps } from "./ops/shift-ops"; -import { NO_OF_REGISTERS, Registers } from "./registers"; - -export type InitialState = { - regs?: FixedArray; - pc?: number; - pageMap?: PageMapItem[]; - memory?: MemoryChunkItem[]; - gas?: number; -}; - -type MemoryChunkItem = { - address: number; - contents: number[]; -}; - -type PageMapItem = { - address: number; - length: number; - "is-writable": boolean; -}; - -type Program = { - c: number[]; - k: number[]; - jLength: number; - z: number; - cLength: number; -}; - -/* -// dynamic jump table -j = Vec -// number of octets of every index in dynamic jump table -z = 1, 2, 3, 4, 5, 6, 7, 8 - -p = len(j) - ++ z - ++ len(c) - ++ [...] - ++ c - ++ k (len(k) == len(c)) -*/ - -export class Pvm { - private program: Program; - private pc = 0; - private registers = new Registers(); - private mathOps = new MathOps(this.registers); - private shiftOps = new ShiftOps(this.registers); - private bitOps = new BitOps(this.registers); - private gas: number; - private pageMap: PageMapItem[]; - private memory: MemoryChunkItem[]; - private status: "trap" | "halt" = "trap"; - private argsDecoder: ArgsDecoder; - - constructor(rawProgram: number[], initialState: InitialState = {}) { - const [jLength, z, cLength, c, k] = this.decodeProgram(new Uint8Array(rawProgram)); - this.program = { cLength, jLength, z, c, k }; - this.pc = initialState.pc ?? 0; - - for (let i = 0; i < NO_OF_REGISTERS; i++) { - this.registers.asUnsigned[i] = initialState.regs?.[i] ?? 0; - } - this.gas = initialState.gas ?? 0; - this.pageMap = initialState.pageMap ?? []; - this.memory = initialState.memory ?? []; - this.argsDecoder = new ArgsDecoder(c, k); - } - - private decodeProgram(program: Uint8Array) { - const first3Numbers = $.tuple($.u8, $.u8, $.u8); // TODO [MaSi] according to GP - [0] and [2] should be compact int - but there is a single byte in tests - const [jLength, z, cLength] = first3Numbers.decode(program); - const jSize = z <= 8 ? 8 : z <= 16 ? 16 : 32; - const jumpTable = jLength > 0 ? [$.sizedArray($.int(false, jSize), jLength)] : []; - return $.tuple($.u8, $.u8, $.u8, ...jumpTable, $.sizedArray($.u8, cLength), $.sizedArray($.u8, Math.ceil(cLength / 8))).decode(program); - } - - printProgram() { - const p = assemblify(this.program.c, this.program.k); - console.table(p); - - return p; - } - - runProgram() { - while (this.pc < this.program.cLength) { - const currentInstruction = this.program.c[this.pc]; - const args = this.argsDecoder.getArgs(this.pc); - - switch (currentInstruction) { - case Instruction.ADD: { - const { firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex } = args as ThreeRegistersResult; - this.mathOps.add(firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex); - break; - } - case Instruction.ADD_IMM: { - const { firstRegisterIndex, secondRegisterIndex, immediateDecoder1 } = args as TwoRegistersOneImmediateResult; - this.mathOps.addImmediate(firstRegisterIndex, immediateDecoder1.getUnsigned(), secondRegisterIndex); - break; - } - case Instruction.MUL: { - const { firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex } = args as ThreeRegistersResult; - this.mathOps.mul(firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex); - break; - } - case Instruction.MUL_UPPER_U_U: { - const { firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex } = args as ThreeRegistersResult; - this.mathOps.mulUpperUU(firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex); - break; - } - case Instruction.MUL_UPPER_S_S: { - const { firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex } = args as ThreeRegistersResult; - this.mathOps.mulUpperSS(firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex); - break; - } - case Instruction.MUL_UPPER_S_U: { - const { firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex } = args as ThreeRegistersResult; - this.mathOps.mulUpperSU(firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex); - break; - } - case Instruction.MUL_IMM: { - const { firstRegisterIndex, secondRegisterIndex, immediateDecoder1 } = args as TwoRegistersOneImmediateResult; - this.mathOps.mulImmediate(firstRegisterIndex, immediateDecoder1.getSigned(), secondRegisterIndex); - break; - } - case Instruction.MUL_UPPER_U_U_IMM: { - const { firstRegisterIndex, secondRegisterIndex, immediateDecoder1 } = args as TwoRegistersOneImmediateResult; - this.mathOps.mulImmediate(firstRegisterIndex, immediateDecoder1.getUnsigned(), secondRegisterIndex); - break; - } - case Instruction.MUL_UPPER_S_S_IMM: { - const { firstRegisterIndex, secondRegisterIndex, immediateDecoder1 } = args as TwoRegistersOneImmediateResult; - this.mathOps.mulImmediate(firstRegisterIndex, immediateDecoder1.getSigned(), secondRegisterIndex); - break; - } - case Instruction.SUB: { - const { firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex } = args as ThreeRegistersResult; - this.mathOps.sub(firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex); - break; - } - case Instruction.NEG_ADD_IMM: { - const { firstRegisterIndex, immediateDecoder1, secondRegisterIndex } = args as TwoRegistersOneImmediateResult; - this.mathOps.negAddImmediate(firstRegisterIndex, immediateDecoder1.getUnsigned(), secondRegisterIndex); - break; - } - case Instruction.DIV_S: { - const { firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex } = args as ThreeRegistersResult; - this.mathOps.divSigned(firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex); - break; - } - case Instruction.DIV_U: { - const { firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex } = args as ThreeRegistersResult; - this.mathOps.divUnsigned(firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex); - break; - } - case Instruction.REM_S: { - const { firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex } = args as ThreeRegistersResult; - this.mathOps.remSigned(firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex); - break; - } - case Instruction.REM_U: { - const { firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex } = args as ThreeRegistersResult; - this.mathOps.remUnsigned(firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex); - break; - } - case Instruction.SHLO_L: { - const { firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex } = args as ThreeRegistersResult; - this.shiftOps.shiftLogicalLeft(firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex); - break; - } - case Instruction.SHLO_L_IMM: { - const { firstRegisterIndex, secondRegisterIndex, immediateDecoder1 } = args as TwoRegistersOneImmediateResult; - this.shiftOps.shiftLogicalLeftImmediate(firstRegisterIndex, immediateDecoder1.getUnsigned(), secondRegisterIndex); - break; - } - case Instruction.SHLO_L_IMM_ALT: { - const { firstRegisterIndex, secondRegisterIndex, immediateDecoder1 } = args as TwoRegistersOneImmediateResult; - this.shiftOps.shiftLogicalLeftImmediateAlternative(firstRegisterIndex, immediateDecoder1.getUnsigned(), secondRegisterIndex); - break; - } - case Instruction.SHLO_R: { - const { firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex } = args as ThreeRegistersResult; - this.shiftOps.shiftLogicalRight(firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex); - break; - } - case Instruction.SHLO_R_IMM: { - const { firstRegisterIndex, secondRegisterIndex, immediateDecoder1 } = args as TwoRegistersOneImmediateResult; - this.shiftOps.shiftLogicalRightImmediate(firstRegisterIndex, immediateDecoder1.getUnsigned(), secondRegisterIndex); - break; - } - case Instruction.SHLO_R_IMM_ALT: { - const { firstRegisterIndex, secondRegisterIndex, immediateDecoder1 } = args as TwoRegistersOneImmediateResult; - this.shiftOps.shiftLogicalRightImmediateAlternative(firstRegisterIndex, immediateDecoder1.getUnsigned(), secondRegisterIndex); - break; - } - case Instruction.SHAR_R: { - const { firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex } = args as ThreeRegistersResult; - this.shiftOps.shiftArithmeticRight(firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex); - break; - } - case Instruction.SHAR_R_IMM: { - const { firstRegisterIndex, secondRegisterIndex, immediateDecoder1 } = args as TwoRegistersOneImmediateResult; - this.shiftOps.shiftArithmeticRightImmediate(firstRegisterIndex, immediateDecoder1.getSigned(), secondRegisterIndex); - break; - } - case Instruction.SHAR_R_IMM_ALT: { - const { firstRegisterIndex, secondRegisterIndex, immediateDecoder1 } = args as TwoRegistersOneImmediateResult; - this.shiftOps.shiftArithmeticRightImmediateAlternative(firstRegisterIndex, immediateDecoder1.getSigned(), secondRegisterIndex); - break; - } - case Instruction.OR: { - const { firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex } = args as ThreeRegistersResult; - this.bitOps.or(firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex); - break; - } - case Instruction.OR_IMM: { - const { firstRegisterIndex, secondRegisterIndex, immediateDecoder1 } = args as TwoRegistersOneImmediateResult; - this.bitOps.orImmediate(firstRegisterIndex, immediateDecoder1.getUnsigned(), secondRegisterIndex); - break; - } - case Instruction.AND: { - const { firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex } = args as ThreeRegistersResult; - this.bitOps.and(firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex); - break; - } - case Instruction.AND_IMM: { - const { firstRegisterIndex, secondRegisterIndex, immediateDecoder1 } = args as TwoRegistersOneImmediateResult; - this.bitOps.andImmediate(firstRegisterIndex, immediateDecoder1.getUnsigned(), secondRegisterIndex); - break; - } - case Instruction.XOR: { - const { firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex } = args as ThreeRegistersResult; - this.bitOps.xor(firstRegisterIndex, secondRegisterIndex, thirdRegisterIndex); - break; - } - case Instruction.XOR_IMM: { - const { firstRegisterIndex, secondRegisterIndex, immediateDecoder1 } = args as TwoRegistersOneImmediateResult; - this.bitOps.xorImmediate(firstRegisterIndex, immediateDecoder1.getUnsigned(), secondRegisterIndex); - break; - } - case Instruction.TRAP: { - this.status = "trap"; - this.gas -= instructionGasMap[currentInstruction]; - return; - } - } - this.gas -= instructionGasMap[currentInstruction]; - this.pc += args.noOfInstructionsToSkip; - } - } - - getState() { - const regs = Array(NO_OF_REGISTERS); - - for (let i = 0; i < NO_OF_REGISTERS; i++) { - regs[i] = Number(this.registers.asUnsigned[i]); - } - - return { - pc: this.pc, - regs, - gas: this.gas, - pageMap: this.pageMap, - memory: this.memory, - status: this.status, - }; - } -}