diff --git a/package-lock.json b/package-lock.json index d0b25c6..d9f3421 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "@reduxjs/toolkit": "^2.2.8", "@tanstack/react-virtual": "^3.10.9", "@typeberry/block": "^0.0.1-1a02906", - "@typeberry/pvm-debugger-adapter": "0.0.1-3038a52", + "@typeberry/pvm-debugger-adapter": "0.0.1-4978606", "@typeberry/spectool-wasm": "^0.13.0", "@uiw/react-codemirror": "^4.23.6", "class-variance-authority": "^0.7.0", @@ -2803,9 +2803,9 @@ "license": "MPL-2.0" }, "node_modules/@typeberry/pvm-debugger-adapter": { - "version": "0.0.1-3038a52", - "resolved": "https://registry.npmjs.org/@typeberry/pvm-debugger-adapter/-/pvm-debugger-adapter-0.0.1-3038a52.tgz", - "integrity": "sha512-tlsoIcruIm9aMMNjc8c+IEPoKFHrURg3gh/zX360OnJkYcfNKogqp9wmaP77ZmNVGt0pznjrAsZ2CFuL//WlXw==", + "version": "0.0.1-4978606", + "resolved": "https://registry.npmjs.org/@typeberry/pvm-debugger-adapter/-/pvm-debugger-adapter-0.0.1-4978606.tgz", + "integrity": "sha512-ZhDk0UjNU6H5kcKOk97PyNiqb5yQpRX5D7zzquxVSLjGWtfoOBTdXvsPFGYyvR2GOppHoFx4FUSczFvyUHDUwg==", "license": "MPL-2.0" }, "node_modules/@typeberry/spectool-wasm": { diff --git a/package.json b/package.json index f5aa2bc..1c5d499 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "@reduxjs/toolkit": "^2.2.8", "@tanstack/react-virtual": "^3.10.9", "@typeberry/block": "^0.0.1-1a02906", - "@typeberry/pvm-debugger-adapter": "0.0.1-3038a52", + "@typeberry/pvm-debugger-adapter": "0.0.1-4978606", "@typeberry/spectool-wasm": "^0.13.0", "@uiw/react-codemirror": "^4.23.6", "class-variance-authority": "^0.7.0", diff --git a/src/components/Instructions/InstructionItem.tsx b/src/components/Instructions/InstructionItem.tsx index ae9b1d9..525ee2c 100644 --- a/src/components/Instructions/InstructionItem.tsx +++ b/src/components/Instructions/InstructionItem.tsx @@ -22,14 +22,6 @@ const getWorkerValueFromState = ( ? (worker[state][propName] as RegistersArray)[propNameIndex] : (worker[state][propName] as number); -function getBackgroundColor(status: Status | undefined, isHighlighted: boolean) { - if (status === Status.OK && isHighlighted) { - return getStatusColor(); - } - - return getStatusColor(status); -} - const AddressCell = ({ breakpointAddresses, programRow, @@ -83,24 +75,13 @@ export const InstructionItem = forwardRef( const { numeralSystem } = useContext(NumeralSystemContext); const workers = useAppSelector(selectWorkers); - const pcInAllWorkers = (state: "currentState" | "previousState") => - workers.map((worker) => getWorkerValueFromState(worker, state, "pc")); const workersWithCurrentPc = workers.filter((worker) => worker.currentState.pc === programRow.address); - const isActive = (programRow: ProgramRow) => { - return pcInAllWorkers("currentState").includes(programRow.address); - }; - const fillSearch = useCallback(() => { onClick(programRow); }, [programRow, onClick]); - const isHighlighted = isActive(programRow); - const bgColor = getBackgroundColor(status, isHighlighted).toUpperCase(); - - const bgOpacity = - pcInAllWorkers("currentState").filter((pc) => pc === programRow.address).length / - pcInAllWorkers("currentState").length; + const { backgroundColor, hasTooltip } = getHighlightStatus(workers, programRow, status); const renderContent = () => { return ( @@ -108,7 +89,11 @@ export const InstructionItem = forwardRef( ref={ref} className={classNames("hover:bg-gray-300", { "opacity-50": isLast })} test-id="instruction-item" - style={{ backgroundColor: isHighlighted ? `rgba(${hexToRgb(bgColor)}, ${bgOpacity})` : "initial" }} + style={{ + backgroundColor, + borderTop: programRow.block.isStart ? "2px solid #bbb" : undefined, + }} + title={programRow.block.name} > {instructionMode === InstructionMode.BYTECODE && ( <> @@ -152,7 +137,7 @@ export const InstructionItem = forwardRef( ); }; - return bgOpacity > 0 && bgOpacity < 1 ? ( + return hasTooltip ? ( {renderContent()} @@ -175,3 +160,35 @@ export const InstructionItem = forwardRef( ); }, ); + +function getHighlightStatus(workers: WorkerState[], programRow: ProgramRow, status?: Status) { + const pcInAllWorkers = (state: "currentState" | "previousState") => + workers.map((worker) => getWorkerValueFromState(worker, state, "pc")); + + const isActive = (programRow: ProgramRow) => { + return pcInAllWorkers("currentState").includes(programRow.address); + }; + + const isHighlighted = isActive(programRow); + const bgColor = getBackgroundForStatus(status, isHighlighted).toUpperCase(); + + const bgOpacity = + pcInAllWorkers("currentState").filter((pc) => pc === programRow.address).length / + pcInAllWorkers("currentState").length; + + const blockBackground = programRow.block.number % 2 === 0 ? "#fff" : "#efefef"; + const backgroundColor = isHighlighted ? `rgba(${hexToRgb(bgColor)}, ${bgOpacity})` : blockBackground; + + return { + backgroundColor, + hasTooltip: bgOpacity > 0 && bgOpacity < 1, + }; +} + +function getBackgroundForStatus(status: Status | undefined, isHighlighted: boolean) { + if (status === Status.OK && isHighlighted) { + return getStatusColor(); + } + + return getStatusColor(status); +} diff --git a/src/index.d.ts b/src/index.d.ts index 9f437f8..ffc8f43 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -1,222 +1,5 @@ declare module "react-json-view-compare"; -declare module "@typeberry/pvm-debugger-adapter" { - export class Mask { - isInstruction(index: number): boolean; - } - - export class ProgramDecoder { - constructor(rawProgram: Uint8Array); - getCode(): Uint8Array; - getMask(): Mask; - } - - export enum ArgumentType { - NO_ARGUMENTS = 0, - ONE_IMMEDIATE = 1, - TWO_IMMEDIATES = 2, - ONE_OFFSET = 3, - ONE_REGISTER_ONE_IMMEDIATE = 4, - ONE_REGISTER_TWO_IMMEDIATES = 5, - ONE_REGISTER_ONE_IMMEDIATE_ONE_OFFSET = 6, - TWO_REGISTERS = 7, - TWO_REGISTERS_ONE_IMMEDIATE = 8, - TWO_REGISTERS_ONE_OFFSET = 9, - TWO_REGISTERS_TWO_IMMEDIATES = 10, - THREE_REGISTERS = 11, - } - - type ImmediateDecoder = { - getUnsigned(); - getSigned(); - }; - - export type EmptyArgs = { - type: ArgumentType.NO_ARGUMENTS; - noOfBytesToSkip: number; - }; - - export type OneImmediateArgs = { - type: ArgumentType.ONE_IMMEDIATE; - noOfBytesToSkip: number; - immediateDecoder: ImmediateDecoder; - }; - - export type ThreeRegistersArgs = { - type: ArgumentType.THREE_REGISTERS; - noOfBytesToSkip: number; - firstRegisterIndex: number; - secondRegisterIndex: number; - thirdRegisterIndex: number; - }; - - export type TwoRegistersArgs = { - type: ArgumentType.TWO_REGISTERS; - noOfBytesToSkip: number; - firstRegisterIndex: number; - secondRegisterIndex: number; - }; - - export type TwoRegistersOneImmediateArgs = { - type: ArgumentType.TWO_REGISTERS_ONE_IMMEDIATE; - noOfBytesToSkip: number; - firstRegisterIndex: number; - secondRegisterIndex: number; - immediateDecoder: ImmediateDecoder; - }; - - export type OneRegisterOneImmediateArgs = { - type: ArgumentType.ONE_REGISTER_ONE_IMMEDIATE; - noOfBytesToSkip: number; - registerIndex: number; - immediateDecoder: ImmediateDecoder; - }; - - export type TwoRegistersTwoImmediatesArgs = { - type: ArgumentType.TWO_REGISTERS_TWO_IMMEDIATES; - noOfBytesToSkip: number; - firstRegisterIndex: number; - secondRegisterIndex: number; - firstImmediateDecoder: ImmediateDecoder; - secondImmediateDecoder: ImmediateDecoder; - }; - - export type TwoImmediatesArgs = { - type: ArgumentType.TWO_IMMEDIATES; - noOfBytesToSkip: number; - firstImmediateDecoder: ImmediateDecoder; - secondImmediateDecoder: ImmediateDecoder; - }; - - export type TwoRegistersOneOffsetArgs = { - type: ArgumentType.TWO_REGISTERS_ONE_OFFSET; - noOfBytesToSkip: number; - firstRegisterIndex: number; - secondRegisterIndex: number; - nextPc: number; - }; - - export type OneRegisterOneImmediateOneOffsetArgs = { - type: ArgumentType.ONE_REGISTER_ONE_IMMEDIATE_ONE_OFFSET; - noOfBytesToSkip: number; - registerIndex: number; - immediateDecoder: ImmediateDecoder; - nextPc: number; - }; - - export type OneRegisterTwoImmediatesArgs = { - type: ArgumentType.ONE_REGISTER_TWO_IMMEDIATES; - noOfBytesToSkip: number; - registerIndex: number; - firstImmediateDecoder: ImmediateDecoder; - secondImmediateDecoder: ImmediateDecoder; - }; - - export type OneOffsetArgs = { - type: ArgumentType.ONE_OFFSET; - noOfBytesToSkip: number; - nextPc: number; - }; - - export type Args = - | EmptyArgs - | OneImmediateArgs - | TwoRegistersArgs - | ThreeRegistersArgs - | TwoRegistersOneImmediateArgs - | TwoRegistersTwoImmediatesArgs - | OneRegisterOneImmediateOneOffsetArgs - | TwoRegistersOneOffsetArgs - | OneRegisterOneImmediateArgs - | OneOffsetArgs - | TwoImmediatesArgs - | OneRegisterTwoImmediatesArgs; - - export class ArgsDecoder { - reset(code: Uint8Array, mask: Mask); - fillArgs(pc: number, result: T): void; - } - - export class Registers { - reset(): void; - copyFrom(regs: Registers | Uint32Array): void; - } - - type Results = [ - EmptyArgs, - OneImmediateArgs, - TwoImmediatesArgs, - OneOffsetArgs, - OneRegisterOneImmediateArgs, - OneRegisterTwoImmediatesArgs, - OneRegisterOneImmediateOneOffsetArgs, - TwoRegistersArgs, - TwoRegistersOneImmediateArgs, - TwoRegistersOneOffsetArgs, - TwoRegistersTwoImmediatesArgs, - ThreeRegistersArgs, - ]; - - export function createResults(): Results; - - type InitialState = { - regs?: RegistersArray; - pc?: number; - memory?: Memory; - gas?: number; - }; - - class Memory {} - - enum Status { - OK = 255, - HALT = 0, - PANIC = 1, - FAULT = 2, - HOST = 3, - OUT_OF_GAS = 4, - } - - export class Pvm { - reset(program: Uint8Array, pc: number, gas: bigint, registers: Registers, memory: Memory); - nextStep(): Status; - getRegisters(): Uint32Array; - getPC(): number; - getGas(): bigint; - getStatus(): Status; - getMemoryPage(pageNumber: number): null | Uint8Array; - setNextPC(nextPc: number); - setGasLeft(gas: bigint); - } - - export class MemoryBuilder { - setWriteable(startPageIndex: number, endPageIndex: number, data: Uint8Array); - setReadable(startPageIndex: number, endPageIndex: number, data: Uint8Array); - setData(address: number, data: Uint8Array); - finalize(heapStartIndex: number, heapEndIndex: number): Memory; - } - - export const decodeStandardProgram: (program: Uint8Array, args: Uint8Array) => DecodedProgram; - - export type MemorySegment = { - start: number; - end: number; - data?: Uint8Array | null; - }; - - export type DecodedProgram = { - code: Uint8Array; - memory: { - readable: MemorySegment[]; - writeable: MemorySegment[]; - sbrkIndex: number; - }; - registers: Uint32Array; - }; - - export const instructionArgumentTypeMap: Array; -} - declare module "path-browserify" { import path from "path"; export default path; diff --git a/src/packages/pvm/pvm/disassemblify.ts b/src/packages/pvm/pvm/disassemblify.ts index 17896b4..2f2ffe5 100644 --- a/src/packages/pvm/pvm/disassemblify.ts +++ b/src/packages/pvm/pvm/disassemblify.ts @@ -6,18 +6,23 @@ import { instructionArgumentTypeMap, createResults, ArgumentType, + BasicBlocks, } from "@typeberry/pvm-debugger-adapter"; import { Instruction } from "./instruction"; +import { CurrentInstruction } from "@/types/pvm"; -export function disassemblify(rawProgram: Uint8Array) { +export function disassemblify(rawProgram: Uint8Array): CurrentInstruction[] { const programDecoder = new ProgramDecoder(rawProgram); const code = programDecoder.getCode(); const mask = programDecoder.getMask(); + const blocks = new BasicBlocks(); + blocks.reset(code, mask); const argsDecoder = new ArgsDecoder(); argsDecoder.reset(code, mask); let i = 0; const printableProgram = []; let address = 0; + let currentBlockNumber = -1; while (i < code.length) { const currentInstruction = code[i]; @@ -26,6 +31,8 @@ export function disassemblify(rawProgram: Uint8Array) { ? instructionArgumentTypeMap[currentInstruction] : ArgumentType.NO_ARGUMENTS; const args = createResults()[argumentType]; + const isBlockStart = blocks.isBeginningOfBasicBlock(i); + const isBlockEnd = blocks.isBeginningOfBasicBlock(i + 1); try { argsDecoder.fillArgs(i, args); @@ -38,10 +45,20 @@ export function disassemblify(rawProgram: Uint8Array) { address, ...byteToOpCodeMap[currentInstruction], error: "Cannot get arguments from args decoder", + block: { + isStart: isBlockStart, + isEnd: isBlockEnd, + name: "block", + number: currentBlockNumber, + }, }); return printableProgram; } + if (isBlockStart) { + currentBlockNumber++; + } + const currentInstructionDebug = { instructionCode: currentInstruction, ...byteToOpCodeMap[currentInstruction], @@ -49,6 +66,12 @@ export function disassemblify(rawProgram: Uint8Array) { instructionBytes: code.slice(i - (args.noOfBytesToSkip ?? 0), i), address, args, + block: { + isStart: isBlockStart, + isEnd: isBlockEnd, + name: `block${currentBlockNumber}`, + number: currentBlockNumber, + }, }; printableProgram.push(currentInstructionDebug); diff --git a/src/packages/web-worker/command-handlers/init.ts b/src/packages/web-worker/command-handlers/init.ts index b42213a..03143d8 100644 --- a/src/packages/web-worker/command-handlers/init.ts +++ b/src/packages/web-worker/command-handlers/init.ts @@ -21,7 +21,6 @@ const init = async ({ pvm, program, initialState }: InitParams) => { } if (isInternalPvm(pvm)) { logger.info("PVM init internal", pvm); - // TODO Fix type guards initPvm(pvm, program, initialState); } else { logger.info("PVM init external", pvm); diff --git a/src/packages/web-worker/command-handlers/memory.ts b/src/packages/web-worker/command-handlers/memory.ts index 60530d0..2b73bf9 100644 --- a/src/packages/web-worker/command-handlers/memory.ts +++ b/src/packages/web-worker/command-handlers/memory.ts @@ -1,5 +1,4 @@ import { CommandStatus, PvmApiInterface } from "../types"; -import { isInternalPvm } from "../utils"; // Max memory size defined by the Gray paper (4GB) const MAX_ADDRESS = Math.pow(2, 32); @@ -22,9 +21,6 @@ const getMemoryPage = (pageNumber: number, pvm: PvmApiInterface | null) => { return new Uint8Array(); } - if (isInternalPvm(pvm)) { - return pvm.getMemoryPage(pageNumber) || new Uint8Array(); - } return pvm.getPageDump(pageNumber) || new Uint8Array(); }; diff --git a/src/packages/web-worker/pvm.ts b/src/packages/web-worker/pvm.ts index 704eb52..30d8ac8 100644 --- a/src/packages/web-worker/pvm.ts +++ b/src/packages/web-worker/pvm.ts @@ -1,5 +1,10 @@ import { InitialState, Pvm as InternalPvm, Status } from "@/types/pvm"; -import { createResults, instructionArgumentTypeMap, ProgramDecoder } from "@typeberry/pvm-debugger-adapter"; +import { + createResults, + instructionArgumentTypeMap, + ProgramDecoder, + tryAsMemoryIndex, +} from "@typeberry/pvm-debugger-adapter"; import { ArgsDecoder, Registers } from "@typeberry/pvm-debugger-adapter"; import { byteToOpCodeMap } from "../../packages/pvm/pvm/assemblify"; import { Pvm as InternalPvmInstance, MemoryBuilder as InternalPvmMemoryBuilder } from "@typeberry/pvm-debugger-adapter"; @@ -9,8 +14,8 @@ export const initPvm = (pvm: InternalPvmInstance, program: Uint8Array, initialSt const memoryBuilder = new InternalPvmMemoryBuilder(); for (const page of pageMap) { - const startPageIndex = page.address; - const endPageIndex = startPageIndex + page.length; + const startPageIndex = tryAsMemoryIndex(page.address); + const endPageIndex = tryAsMemoryIndex(startPageIndex + page.length); const isWriteable = page["is-writable"]; if (isWriteable) { @@ -21,11 +26,12 @@ export const initPvm = (pvm: InternalPvmInstance, program: Uint8Array, initialSt } for (const memoryChunk of initialMemory) { - memoryBuilder.setData(memoryChunk.address, new Uint8Array(memoryChunk.contents)); + const idx = tryAsMemoryIndex(memoryChunk.address); + memoryBuilder.setData(idx, new Uint8Array(memoryChunk.contents)); } //const HEAP_START_PAGE = 4 * 2 ** 16; - const HEAP_END_PAGE = 2 ** 32 - 2 * 2 ** 16 - 2 ** 24; + const HEAP_END_PAGE = tryAsMemoryIndex(2 ** 32 - 2 * 2 ** 16 - 2 ** 24); // TODO [ToDr] [#216] Since we don't have examples yet of the // PVM program allocating more memory, we disallow expanding @@ -42,19 +48,19 @@ export const runAllInstructions = (pvm: InternalPvm, program: Uint8Array) => { const programPreviewResult = []; do { - const pc = pvm.getPC(); + const pc = pvm.getProgramCounter(); const result = nextInstruction(pc, program); programPreviewResult.push(result); } while (pvm.nextStep() === Status.OK); return { programRunResult: { - pc: pvm.getPC(), + pc: pvm.getProgramCounter(), regs: Array.from(pvm.getRegisters()), - gas: pvm.getGas(), + gas: pvm.getGasLeft(), pageMap: [], memory: [], - status: pvm.getStatus(), + status: pvm.getStatus() as Status, }, programPreviewResult, }; diff --git a/src/packages/web-worker/utils.ts b/src/packages/web-worker/utils.ts index 5a03898..ddbba77 100644 --- a/src/packages/web-worker/utils.ts +++ b/src/packages/web-worker/utils.ts @@ -1,5 +1,5 @@ -import { ExpectedState, Pvm as InternalPvm, MemoryChunkItem, PageMapItem, RegistersArray } from "@/types/pvm.ts"; -import { Pvm as InternalPvmInstance } from "@typeberry/pvm-debugger-adapter"; +import { ExpectedState, MemoryChunkItem, PageMapItem, RegistersArray } from "@/types/pvm.ts"; +import { Pvm as InternalPvm } from "@typeberry/pvm-debugger-adapter"; import { createWasmPvmShell } from "@/packages/web-worker/wasmBindgenShell.ts"; import "./goWasmExec.js"; import "./goWasmExec.d.ts"; @@ -15,23 +15,16 @@ export enum SupportedLangs { } export function getState(pvm: PvmApiInterface): ExpectedState { - let newState: ExpectedState; - if (isInternalPvm(pvm)) { - newState = { - pc: pvm.getPC(), - regs: Array.from(pvm.getRegisters()) as RegistersArray, - gas: pvm.getGas(), - status: pvm.getStatus(), - }; - } else { - newState = { - pc: pvm.getProgramCounter(), - regs: uint8asRegs(pvm.getRegisters()), - gas: pvm.getGasLeft(), - status: pvm.getStatus(), - }; - } - return newState; + const regs = isInternalPvm(pvm) + ? (Array.from(pvm.getRegisters()) as RegistersArray) + : uint8asRegs(pvm.getRegisters()); + + return { + pc: pvm.getProgramCounter(), + regs, + gas: pvm.getGasLeft(), + status: pvm.getStatus(), + }; } export function regsAsUint8(regs?: RegistersArray): Uint8Array { @@ -70,7 +63,7 @@ export function uint8asRegs(arr: Uint8Array): RegistersArray { } export function isInternalPvm(pvm: PvmApiInterface): pvm is InternalPvm { - return pvm instanceof InternalPvmInstance; + return pvm instanceof InternalPvm; } export async function loadArrayBufferAsWasm(bytes: ArrayBuffer, lang?: SupportedLangs): Promise { @@ -109,7 +102,7 @@ export function getMemorySize(pvm: PvmApiInterface | null) { return null; } - const page = isInternalPvm(pvm) ? pvm.getMemoryPage(0) : pvm.getPageDump(0); + const page = pvm.getPageDump(0); if (!page) { logger.warn("PVM memory page is null"); diff --git a/src/store/workers/workersSlice.ts b/src/store/workers/workersSlice.ts index 40d22de..c246919 100644 --- a/src/store/workers/workersSlice.ts +++ b/src/store/workers/workersSlice.ts @@ -377,7 +377,7 @@ export const destroyWorker = createAsyncThunk("workers/destroyWorker", async (id return id; }); -const getWorker = (state: WorkerState[], id: string) => state.find((worker) => worker.id === id); +const getWorker = (state: T[], id: string) => state.find((worker) => worker.id === id); const workers = createSlice({ name: "workers", diff --git a/src/types/pvm.ts b/src/types/pvm.ts index 581edb6..f24d641 100644 --- a/src/types/pvm.ts +++ b/src/types/pvm.ts @@ -1,4 +1,5 @@ -import { Args, Memory, Registers } from "@typeberry/pvm-debugger-adapter"; +import { Args } from "@typeberry/pvm-debugger-adapter"; +export { Pvm } from "@typeberry/pvm-debugger-adapter"; type GrowToSize = A["length"] extends N ? A : GrowToSize; type FixedArray = GrowToSize; @@ -30,23 +31,18 @@ export enum Status { PANIC = 1, FAULT = 2, HOST = 3, - OUT_OF_GAS = 4, + OOG = 4 /* out of gas */, } export type ExpectedState = InitialState & { status?: Status; }; -export type Pvm = { - nextStep: () => Status; - getRegisters: () => Uint32Array; - getPC: () => number; - getGas: () => bigint; - getStatus: () => Status; - getMemoryPage: (pageNumber: number) => Uint8Array | null; - setNextPC(nextPc: number): void; - setGasLeft(gas: bigint): void; - reset(program: Uint8Array, pc: number, gas: bigint, registers: Registers, memory: Memory): void; +export type Block = { + isStart: boolean; + isEnd: boolean; + name: string; + number: number; }; export type CurrentInstruction = @@ -55,6 +51,7 @@ export type CurrentInstruction = args: Args; name: string; gas: number; + block: Block; instructionCode: number; instructionBytes: Uint8Array; } @@ -63,6 +60,7 @@ export type CurrentInstruction = error: string; name: string; gas: number; + block: Block; instructionCode: number; }; diff --git a/src/utils/virtualTrapInstruction.ts b/src/utils/virtualTrapInstruction.ts index 25756ee..511a738 100644 --- a/src/utils/virtualTrapInstruction.ts +++ b/src/utils/virtualTrapInstruction.ts @@ -7,4 +7,10 @@ export const virtualTrapInstruction: CurrentInstruction = { gas: 0, instructionCode: 0, instructionBytes: new Uint8Array(0), + block: { + isStart: true, + isEnd: true, + name: "end of program", + number: -1, + }, };