Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add breakpoints #86

Merged
merged 5 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading