Skip to content

Commit

Permalink
feat: add breakpoints (#86)
Browse files Browse the repository at this point in the history
* feat: add breakpoints

* Add linter to precommit hooks

* Fix type issues

* Change prettier precommit config

* Fix prettier issue
  • Loading branch information
wkwiatek authored Aug 22, 2024
1 parent aede398 commit 8910424
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 44 deletions.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@
"vite": "^5.3.4"
},
"lint-staged": {
"**/*": "prettier --write --ignore-unknown"
"**/*": [
"npm run format",
"npm run lint"
]
}
}
45 changes: 27 additions & 18 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ function App() {
const [instructionMode, setInstructionMode] = useState<InstructionMode>(InstructionMode.ASM);
const [currentState, setCurrentState] = useState<ExpectedState>(initialState as ExpectedState);
const [previousState, setPreviousState] = useState<ExpectedState>(initialState as ExpectedState);
const [breakpointAddresses, setBreakpointAddresses] = useState<(number | undefined)[]>([]);
const [isRunMode, setIsRunMode] = useState(false);

const [isDebugFinished, setIsDebugFinished] = useState(false);
const [pvmInitialized, setPvmInitialized] = useState(false);
Expand All @@ -63,36 +65,32 @@ function App() {
}

worker.onmessage = (e: MessageEvent<TargerOnMessageParams>) => {
if (e.data.command === Commands.STEP || e.data.command === Commands.RUN) {
const { state, isFinished } = e.data.payload;
if (e.data.command === Commands.STEP) {
const { state, isFinished, isRunMode } = e.data.payload;
setCurrentState((prevState) => {
setPreviousState(prevState);
return state;
});

if (e.data.command === Commands.STEP) {
setCurrentInstruction(e.data.payload.result);
} else if (e.data.command === Commands.RUN) {
let counter = 0;
const result = programPreviewResult.find((x) => {
if (counter === state.pc) {
return true;
}

if (!("error" in x)) {
counter += x.instructionBytes?.length ?? 0;
}
});
setCurrentInstruction(result ?? null);
}

if (isRunMode && !isFinished && !breakpointAddresses.includes(state.pc)) {
worker.postMessage({ command: "step", payload: { program } });
}

if (isRunMode && breakpointAddresses.includes(state.pc)) {
worker.postMessage({ command: "stop", payload: { program } });
setIsRunMode(false);
}

if (isFinished) {
setIsDebugFinished(true);
}
}
};
console.log("Message posted to worker");
}, [setCurrentInstruction, programPreviewResult]);
}, [isRunMode, breakpointAddresses, currentState, program, setCurrentInstruction, programPreviewResult]);

const startProgram = (initialState: ExpectedState, program: number[]) => {
setInitialState(initialState);
Expand Down Expand Up @@ -126,6 +124,8 @@ function App() {
};

const onNext = () => {
setIsRunMode(false);

if (!pvmInitialized) {
startProgram(initialState, program);
}
Expand All @@ -143,9 +143,9 @@ function App() {
if (!pvmInitialized) {
startProgram(initialState, program);
}
setIsProgramEditMode(false);
setIsDebugFinished(true);
setIsRunMode(true);
worker.postMessage({ command: "run", payload: { program } });
worker.postMessage({ command: "step", payload: { program } });
};

const restartProgram = (state: InitialState) => {
Expand All @@ -156,6 +156,13 @@ function App() {
worker.postMessage({ command: "init", payload: { program, initialState: state } });
};

const handleBreakpointClick = (address: number) => {
if (breakpointAddresses.includes(address)) {
setBreakpointAddresses(breakpointAddresses.filter((x) => x !== address));
} else {
setBreakpointAddresses([...breakpointAddresses, address]);
}
};
const onInstructionClick = useCallback((row: CurrentInstruction) => {
setClickedInstruction(row);
}, []);
Expand Down Expand Up @@ -234,6 +241,8 @@ function App() {
currentState={currentState}
programPreviewResult={programPreviewResult}
instructionMode={instructionMode}
onAddressClick={handleBreakpointClick}
breakpointAddresses={breakpointAddresses}
onInstructionClick={onInstructionClick}
/>
</>
Expand Down
51 changes: 44 additions & 7 deletions src/components/Instructions/InstructionItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import classNames from "classnames";
import { getStatusColor } from "../Registers/utils";
import { Status } from "@/types/pvm";
import { InstructionMode } from "./types";
import { useCallback, useContext, useRef } from "react";
import { useCallback, useContext, useRef, useState } from "react";
import { NumeralSystemContext } from "@/context/NumeralSystemProvider";
import { TableCell, TableRow } from "../ui/table";
import { ProgramRow } from ".";
Expand All @@ -15,20 +15,53 @@ function getBackgroundColor(status: Status | undefined, isHighlighted: boolean)

return getStatusColor(status);
}

const AddressCell = ({
breakpointAddresses,
programRow,
onAddressClick,
}: {
breakpointAddresses: (number | undefined)[];
programRow: ProgramRow;
onAddressClick: (address: number) => void;
}) => {
const [isHover, setIsHover] = useState(false);

return (
<TableCell
className="p-1.5 border-transparent cursor-pointer relative"
onMouseEnter={() => setIsHover(true)}
onMouseLeave={() => setIsHover(false)}
>
<div
className={classNames("w-[4px] absolute h-[100%] left-0 top-0", {
"bg-red-600": breakpointAddresses.includes(programRow.counter),
"bg-red-400": isHover,
})}
></div>
<span onClick={() => onAddressClick(programRow.counter)}>{programRow.addressEl}</span>
</TableCell>
);
};

export const InstructionItem = ({
status,
isLast,
currentPc,
instructionMode,
programRow,
onClick,
onAddressClick,
breakpointAddresses,
}: {
status?: Status;
isLast: boolean;
programRow: ProgramRow;
currentPc: number | undefined;
instructionMode: InstructionMode;
onClick: (r: ProgramRow) => void;
onAddressClick: (address: number) => void;
breakpointAddresses: (number | undefined)[];
}) => {
const { numeralSystem } = useContext(NumeralSystemContext);
const ref = useRef<HTMLTableRowElement>(null);
Expand All @@ -52,9 +85,11 @@ export const InstructionItem = ({
>
{instructionMode === InstructionMode.BYTECODE && (
<>
<TableCell className="p-1.5">
<span>{programRow.addressEl}</span>
</TableCell>
<AddressCell
breakpointAddresses={breakpointAddresses}
programRow={programRow}
onAddressClick={onAddressClick}
/>
<TableCell className="p-1.5">
{"instructionBytes" in programRow && programRow.instructionBytes && (
<span className="text-gray-500">
Expand All @@ -68,9 +103,11 @@ export const InstructionItem = ({
)}
{instructionMode === InstructionMode.ASM && (
<>
<TableCell className="p-1.5">
<span>{programRow.addressEl}</span>
</TableCell>
<AddressCell
breakpointAddresses={breakpointAddresses}
programRow={programRow}
onAddressClick={onAddressClick}
/>
<TableCell className="p-1.5">
<a onClick={fillSearch} className="cursor-pointer">
<span className="uppercase font-bold">{programRow.name}</span>
Expand Down
10 changes: 8 additions & 2 deletions src/components/Instructions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,24 @@ import { ReactNode, useContext, useMemo } from "react";
import { CurrentInstruction, ExpectedState, Status } from "@/types/pvm";
import { InstructionItem } from "./InstructionItem";

export type ProgramRow = CurrentInstruction & { addressEl: ReactNode };
export type ProgramRow = CurrentInstruction & { addressEl: ReactNode; counter: number };

export const Instructions = ({
status,
programPreviewResult,
currentState,
instructionMode,
onInstructionClick,
onAddressClick,
breakpointAddresses,
}: {
status?: Status;
programPreviewResult: CurrentInstruction[] | undefined;
currentState: ExpectedState;
instructionMode: InstructionMode;
onInstructionClick: (r: ProgramRow) => void;
onAddressClick: (address: number) => void;
breakpointAddresses: (number | undefined)[];
}) => {
const { numeralSystem } = useContext(NumeralSystemContext);

Expand All @@ -45,7 +49,7 @@ export const Instructions = ({
);
};
const programRows = programPreviewResult?.map((result) => {
Object.assign(result, { addressEl: getAddressEl(result.address) });
Object.assign(result, { addressEl: getAddressEl(result.address), counter: result.address });
return result;
});
return programRows as ProgramRow[];
Expand All @@ -65,6 +69,8 @@ export const Instructions = ({
key={i}
programRow={programRow}
currentPc={currentState.pc}
onAddressClick={onAddressClick}
breakpointAddresses={breakpointAddresses}
/>
))}
</TableBody>
Expand Down
7 changes: 5 additions & 2 deletions src/components/Instructions/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NumeralSystem } from "@/context/NumeralSystem.tsx";
import { ArgumentType } from "@/packages/pvm/pvm/args-decoder/argument-type";
import { Args } from "@/types/pvm";
import { ImmediateDecoder } from "@/packages/pvm/pvm/args-decoder/decoders/immediate-decoder.ts";

export const valueToNumeralSystem = (
value: number | undefined,
Expand All @@ -13,14 +14,16 @@ export const valueToNumeralSystem = (
};

// TODO remove type casting when PVM changed
const getUnsignedImmediate = (immediateDecoder: any) => immediateDecoder?.unsignedImmediate[0];
const getUnsignedImmediate = (immediateDecoder: ImmediateDecoder) => immediateDecoder?.unsignedImmediate[0];

export const mapInstructionsArgsByType = (args: Args | null, numeralSystem: NumeralSystem) => {
switch (args?.type) {
case ArgumentType.NO_ARGUMENTS:
return "";
case ArgumentType.ONE_IMMEDIATE:
return <span>{valueToNumeralSystem(getUnsignedImmediate(args), numeralSystem)}</span>;
return (
<span>{valueToNumeralSystem(getUnsignedImmediate(args as unknown as ImmediateDecoder), numeralSystem)}</span>
);
case ArgumentType.TWO_IMMEDIATES:
return (
<span>
Expand Down
5 changes: 5 additions & 0 deletions src/components/Registers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ export const Registers = ({
}) => {
const { numeralSystem } = useContext(NumeralSystemContext);

console.log({
currentState,
previousState,
});

return (
<div className="border-2 rounded-md h-full">
<div className="p-3">
Expand Down
10 changes: 5 additions & 5 deletions src/packages/pvm/pvm/args-decoder/decoders/immediate-decoder.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
const IMMEDIATE_SIZE = 4;

export class ImmediateDecoder {
private buffer = new ArrayBuffer(IMMEDIATE_SIZE);
private unsignedImmediate = new Uint32Array(this.buffer);
private signedImmediate = new Int32Array(this.buffer);
private view = new DataView(this.buffer);
private bytes = new Uint8Array(this.buffer);
public buffer = new ArrayBuffer(IMMEDIATE_SIZE);
public unsignedImmediate = new Uint32Array(this.buffer);
public signedImmediate = new Int32Array(this.buffer);
public view = new DataView(this.buffer);
public bytes = new Uint8Array(this.buffer);

setBytes(bytes: Uint8Array) {
const n = bytes.length;
Expand Down
26 changes: 17 additions & 9 deletions src/packages/web-worker/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,26 @@ export enum Commands {
INIT = "init",
STEP = "step",
RUN = "run",
STOP = "stop",
}

let pvm: typeof PvmInstance | null = null;
let isRunMode = false;

export type TargerOnMessageParams =
| { command: Commands.INIT; payload: { program: number[]; initialState: InitialState } }
| { command: Commands.STEP; payload: { state: ExpectedState; result: CurrentInstruction; isFinished: boolean } }
| { command: Commands.RUN; payload: { state: ExpectedState; isFinished: boolean } };
| {
command: Commands.STEP;
payload: { state: ExpectedState; result: CurrentInstruction; isFinished: boolean; isRunMode: boolean };
}
| { command: Commands.RUN; payload: { state: ExpectedState; isFinished: boolean; isRunMode: boolean } }
| { command: Commands.STOP; payload: { isRunMode: boolean } };

export type WorkerOnMessageParams =
| { command: Commands.INIT; payload: { program: number[]; initialState: InitialState } }
| { command: Commands.STEP; payload: { program: number[] } }
| { command: Commands.RUN };
| { command: Commands.RUN }
| { command: Commands.STOP };

onmessage = async (e: MessageEvent<WorkerOnMessageParams>) => {
if (!e.data?.command) {
Expand All @@ -31,12 +38,10 @@ onmessage = async (e: MessageEvent<WorkerOnMessageParams>) => {
let isFinished;
switch (e.data.command) {
case Commands.INIT:
console.log("webworker init");
pvm = initPvm(e.data.payload.program, e.data.payload.initialState);
postMessage({ command: Commands.INIT, result: "success" });
break;
case Commands.STEP:
console.log("webworker step");
isFinished = pvm.nextStep() !== Status.OK;
result = nextInstruction(pvm, e.data.payload.program);
state = {
Expand All @@ -48,10 +53,10 @@ onmessage = async (e: MessageEvent<WorkerOnMessageParams>) => {
status: pvm.getStatus() as unknown as Status,
};

postMessage({ command: Commands.STEP, payload: { result, state, isFinished } });
postMessage({ command: Commands.STEP, payload: { result, state, isFinished, isRunMode } });
break;
case Commands.RUN:
pvm.runProgram();
isRunMode = true;
state = {
pc: pvm.getPC(),
regs: Array.from(pvm.getRegisters()) as RegistersArray,
Expand All @@ -60,8 +65,11 @@ onmessage = async (e: MessageEvent<WorkerOnMessageParams>) => {
memory: pvm.getMemory(),
status: pvm.getStatus() as unknown as Status,
};

postMessage({ command: Commands.RUN, payload: { state, isFinished: true } });
postMessage({ command: Commands.RUN, payload: { isRunMode, state, isFinished: true } });
break;
case Commands.STOP:
isRunMode = false;
postMessage({ command: Commands.RUN, payload: { isRunMode } });
break;
default:
break;
Expand Down

0 comments on commit 8910424

Please sign in to comment.