Skip to content

Commit

Permalink
Merge pull request #12 from FluffyLabs/debug
Browse files Browse the repository at this point in the history
Debugger WIP
  • Loading branch information
krystian50 authored Aug 8, 2024
2 parents 88fa970 + 62b709b commit 4446fb0
Show file tree
Hide file tree
Showing 37 changed files with 193 additions and 2,555 deletions.
126 changes: 69 additions & 57 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import "./App.css";
import { Button } from "@/components/ui/button";
import { useState } from "react";
import { useMemo, useState } from "react";
import { ProgramUpload } from "./components/ProgramUpload";
import { Instructions } from "./components/Instructions";
import { InitialParams } from "./components/InitialParams";
import { ExpectedState, InitialState } from "./types/pvm";
import { Status } from "../node_modules/typeberry/packages/pvm/status";
import { Pvm } from "../node_modules/typeberry/packages/pvm/pvm";
import { ExpectedState, InitialState, PageMapItem } from "./types/pvm";
import { DiffChecker } from "./components/DiffChecker";
import { Pvm } from "../node_modules/typeberry/packages/pvm/pvm";

// The only files we need from PVM repo:
import { ProgramDecoder } from "./pvm-packages/pvm/program-decoder/program-decoder";
import { ArgsDecoder } from "./pvm-packages/pvm/args-decoder/args-decoder";
import { byteToOpCodeMap } from "./pvm-packages/pvm/assemblify";
import { CurrentInstruction, initPvm, nextInstruction } from "./components/Debugger/debug";
import { Play, RefreshCcw, StepForward } from "lucide-react";
import { Status } from "../node_modules/typeberry/packages/pvm/status";
import { disassemblify } from "./pvm-packages/pvm/disassemblify";

function App() {
const [program, setProgram] = useState([0, 0, 3, 8, 135, 9, 249]);
Expand All @@ -23,55 +22,46 @@ function App() {
memory: [],
gas: 10000,
});
const [programPreviewResult, setProgramPreviewResult] = useState<unknown[]>();
const [programRunResult, setProgramRunResult] = useState<unknown>();
const [programPreviewResult, setProgramPreviewResult] = useState<CurrentInstruction[]>([]);
const [expectedResult, setExpectedResult] = useState<ExpectedState>();
const [currentInstruction, setCurrentInstruction] = useState<CurrentInstruction>();

const [pvm, setPvm] = useState<Pvm>();
const [isDebugFinished, setIsDebugFinished] = useState(false);

const handleClick = () => {
window.scrollTo(0, 0);

const pvm = new Pvm(new Uint8Array(program), initialState);
const programDecoder = new ProgramDecoder(new Uint8Array(program));
const code = programDecoder.getCode();
const mask = programDecoder.getMask();
const argsDecoder = new ArgsDecoder(code, mask);

const newProgramPreviewResult = [];

do {
const currentInstruction = code[pvm.getPC()];

let args;

try {
args = argsDecoder.getArgs(pvm.getPC()) as any;

const currentInstructionDebug = {
instructionCode: currentInstruction,
...byteToOpCodeMap[currentInstruction],
args: {
...args,
immediate: args.immediateDecoder?.getUnsigned(),
},
};
newProgramPreviewResult.push(currentInstructionDebug);
} catch (e) {
// The last iteration goes here since there's no instruction to proces and we didn't check if there's a next operation
break;
// newProgramPreviewResult.push({ instructionCode: currentInstruction, ...byteToOpCodeMap[currentInstruction], error: "Cannot get arguments from args decoder" });
}
} while (pvm.nextStep() === Status.OK);

setProgramRunResult({
pc: pvm.getPC(),
regs: Array.from(pvm.getRegisters()),
gas: pvm.getGas(),
pageMap: pvm.getMemory(),
memory: pvm.getMemory(),
status: pvm.getStatus(),
});

setProgramPreviewResult(newProgramPreviewResult);
const result = disassemblify(new Uint8Array(program));
console.log(result);
setProgramPreviewResult(result);
// setProgramRunResult(result.programRunResult);
};

const currentState = useMemo(
() =>
pvm && {
pc: pvm.getPC(),
regs: Array.from(pvm.getRegisters()) as [number, number, number, number, number, number, number, number, number, number, number, number, number],
gas: pvm.getGas(),
pageMap: pvm.getMemory() as unknown as PageMapItem[],
memory: pvm.getMemory(),
status: pvm.getStatus() as unknown as "trap" | "halt",
},
[pvm],
);

const onNext = () => {
if (!pvm) return;

const result = nextInstruction(pvm, program);

setCurrentInstruction(result);

if (pvm.nextStep() !== Status.OK) {
setIsDebugFinished(true);
setPvm(undefined);
}
};

return (
Expand All @@ -84,20 +74,42 @@ function App() {
setExpectedResult(expected);
setInitialState(initial);
setProgram(program);

setIsDebugFinished(false);
setPvm(initPvm(program, initial));

const result = disassemblify(new Uint8Array(program));
setProgramPreviewResult(result);
}}
/>

<div className="text-right">
<Button className="my-2" onClick={handleClick}>
Check program
<div className="flex justify-end align-middle my-4">
<Button
className="mx-2"
onClick={() => {
setIsDebugFinished(false);
setPvm(initPvm(program, initialState));
}}
>
<RefreshCcw />
Restart
</Button>
<Button className="mx-2" onClick={handleClick}>
<Play />
Run
</Button>
<Button className="mx-2" onClick={onNext} disabled={isDebugFinished}>
<StepForward /> Step
</Button>
</div>

<InitialParams program={program} setProgram={setProgram} initialState={initialState} setInitialState={setInitialState} />
</div>

<Instructions programPreviewResult={programPreviewResult} />
<DiffChecker actual={programRunResult} expected={expectedResult} />
<div className="col-span-6 h-100">
<Instructions programPreviewResult={programPreviewResult} currentInstruction={currentInstruction} />
</div>
<DiffChecker actual={currentState} expected={expectedResult} />
</div>
</div>
</>
Expand Down
76 changes: 76 additions & 0 deletions src/components/Debugger/debug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { InitialState } from "@/types/pvm";
import { Pvm } from "../../../node_modules/typeberry/packages/pvm/pvm";
import { Status } from "../../../node_modules/typeberry/packages/pvm/status";

// The only files we need from PVM repo:
import { ProgramDecoder } from "../../pvm-packages/pvm/program-decoder/program-decoder";
import { ArgsDecoder } from "../../pvm-packages/pvm/args-decoder/args-decoder";
import { byteToOpCodeMap } from "../../pvm-packages/pvm/assemblify";

export const initPvm = (program: number[], initialState: InitialState) => {
const pvm = new Pvm(new Uint8Array(program), initialState);

return pvm;
};

export const runAllInstructions = (pvm: Pvm, program: number[]) => {
const programPreviewResult = [];

do {
const result = nextInstruction(pvm, program);
programPreviewResult.push(result);
} while (pvm.nextStep() === Status.OK);

return {
programRunResult: {
pc: pvm.getPC(),
regs: Array.from(pvm.getRegisters()),
gas: pvm.getGas(),
pageMap: pvm.getMemory(),
memory: pvm.getMemory(),
status: pvm.getStatus(),
},
programPreviewResult,
};
};

export type CurrentInstruction =
| {
args: any;
name: string;
gas: number;
instructionCode: number;
}
| {
error: string;
name: string;
gas: number;
instructionCode: number;
};
export const nextInstruction = (pvm: Pvm, program: number[]) => {
const programDecoder = new ProgramDecoder(new Uint8Array(program));
const code = programDecoder.getCode();
const mask = programDecoder.getMask();
const argsDecoder = new ArgsDecoder(code, mask);
const currentInstruction = code[pvm.getPC()];

let args;

try {
args = argsDecoder.getArgs(pvm.getPC()) as any;

const currentInstructionDebug = {
instructionCode: currentInstruction,
...byteToOpCodeMap[currentInstruction],
args: {
...args,
immediate: args.immediateDecoder?.getUnsigned(),
},
};
return currentInstructionDebug;
} catch (e) {
// The last iteration goes here since there's no instruction to proces and we didn't check if there's a next operation
return { instructionCode: currentInstruction, ...byteToOpCodeMap[currentInstruction], error: "Cannot get arguments from args decoder" };
// newProgramPreviewResult.push({ instructionCode: currentInstruction, ...byteToOpCodeMap[currentInstruction], error: "Cannot get arguments from args decoder" });
}
};
15 changes: 2 additions & 13 deletions src/components/DiffChecker.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,12 @@
import { Pvm } from "@/pvm-packages/pvm/pvm";
import { ExpectedState } from "@/types/pvm";
import ReactJsonViewCompare from "react-json-view-compare";

const actualToExpected = (actual: ReturnType<Pvm["getState"]>): ExpectedState | undefined => {
if (!actual) return;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { pageMap, ...rest } = actual;
return rest as unknown as ExpectedState;
};

export const DiffChecker = (args: { expected?: ExpectedState; actual: ReturnType<Pvm["getState"]> }) => {
const actual = actualToExpected(args.actual);

export const DiffChecker = (args: { expected?: unknown; actual?: unknown }) => {
if (!args.actual) return null;

return (
<div className="col-span-3 container py-3 text-left text-xs">
<div className="p-3 bg-slate-100 rounded-md">
<ReactJsonViewCompare oldData={args.expected} newData={actual} />
<ReactJsonViewCompare oldData={args.expected} newData={args.actual} />
</div>
</div>
);
Expand Down
14 changes: 8 additions & 6 deletions src/components/Instructions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "@
import { Label } from "@/components/ui/label.tsx";
import { mapInstructionsArgsByType } from "./utils";
import { Button } from "@/components/ui/button";
import { CurrentInstruction } from "../Debugger/debug";

export const Instructions = ({ programPreviewResult }: { programPreviewResult: unknown[] | undefined }) => {
export const Instructions = ({ programPreviewResult, currentInstruction }: { programPreviewResult: CurrentInstruction[] | undefined; currentInstruction: CurrentInstruction | undefined }) => {
const isActive = (programRow: CurrentInstruction) => currentInstruction?.name === programRow.name && (!("args" in programRow) || !("args" in currentInstruction) || currentInstruction?.args?.immediate === programRow?.args?.immediate);
return (
<div className="col-span-6 container py-3 font-mono">
<div className="container py-3 font-mono h-2/4 overflow-auto scroll-auto">
<Label>Instructions:</Label>
<Table>
<TableHeader>
Expand All @@ -24,20 +26,20 @@ export const Instructions = ({ programPreviewResult }: { programPreviewResult: u
</TableHeader>
<TableBody>
{!!programPreviewResult?.length &&
programPreviewResult.map((programRow: any) => (
programPreviewResult.map((programRow) => (
<Collapsible asChild>
<>
<TableRow>
<TableRow style={{ background: isActive(programRow) ? "gray" : undefined }}>
<TableCell>{programRow.instructionCode}</TableCell>
<TableCell>
<HoverCard>
<HoverCardTrigger>
<span className="uppercase font-bold">{programRow.name}</span>
&nbsp;
<span className="font-bold">{mapInstructionsArgsByType(programRow.args)}</span>
<span className="font-bold">{"args" in programRow && mapInstructionsArgsByType(programRow.args)}</span>
</HoverCardTrigger>
<HoverCardContent>
<pre>{JSON.stringify(programRow.args, null, 2)}</pre>
<pre>{"args" in programRow && JSON.stringify(programRow.args, null, 2)}</pre>
</HoverCardContent>
</HoverCard>
</TableCell>
Expand Down
38 changes: 38 additions & 0 deletions src/pvm-packages/pvm/disassemblify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ArgsDecoder } from "./args-decoder/args-decoder";
import { byteToOpCodeMap } from "./assemblify";
import { ProgramDecoder } from "./program-decoder/program-decoder";

export function disassemblify(rawProgram: Uint8Array) {
const programDecoder = new ProgramDecoder(rawProgram);
const code = programDecoder.getCode();
const mask = programDecoder.getMask();
const argsDecoder = new ArgsDecoder(code, mask);
let i = 0;
const printableProgram = [];

while (i < code.length) {
const currentInstruction = code[i];
let args;

try {
args = argsDecoder.getArgs(i) as any;
i += args.noOfInstructionsToSkip;
} catch (e) {
printableProgram.push({ instructionCode: currentInstruction, ...byteToOpCodeMap[currentInstruction], error: "Cannot get arguments from args decoder" });
return printableProgram;
}

const currentInstructionDebug = {
instructionCode: currentInstruction,
...byteToOpCodeMap[currentInstruction],
args: {
...args,
immediate: args.immediateDecoder?.getUnsigned() as number,
},
};

printableProgram.push(currentInstructionDebug);
}

return printableProgram;
}
15 changes: 0 additions & 15 deletions src/pvm-packages/pvm/instruction-gas-map.test.ts

This file was deleted.

9 changes: 0 additions & 9 deletions src/pvm-packages/pvm/instruction-gas-map.ts

This file was deleted.

3 changes: 0 additions & 3 deletions src/pvm-packages/pvm/instruction-result.ts

This file was deleted.

7 changes: 0 additions & 7 deletions src/pvm-packages/pvm/ops-dispatchers/index.ts

This file was deleted.

Loading

0 comments on commit 4446fb0

Please sign in to comment.