Skip to content

Commit

Permalink
Add ecalli handling for other pvms (#285)
Browse files Browse the repository at this point in the history
* Add ecalli handling for other pvms

* remove unused

* Add new registers func

* fix loadInto

* Set registers after host call

* fix memory size

* block extra step

* fix gas

* Update src/packages/web-worker/command-handlers/host-call.ts

Co-authored-by: Tomek Drwięga <[email protected]>

* remove memory size

* Remove gas parsing

* Update src/packages/web-worker/command-handlers/host-call.ts

Co-authored-by: Tomek Drwięga <[email protected]>

* fix gas issue

* set registers

* fix two host calls

---------

Co-authored-by: Tomek Drwięga <[email protected]>
  • Loading branch information
krystian50 and tomusdrw authored Jan 17, 2025
1 parent 747d3c1 commit 7d522d4
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 65 deletions.
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
"@radix-ui/react-tooltip": "^1.1.2",
"@reduxjs/toolkit": "^2.2.8",
"@tanstack/react-virtual": "^3.10.9",
"@typeberry/block": "^0.0.1-b25856a",
"@typeberry/jam-host-calls": "0.0.1-b25856a",
"@typeberry/block": "0.0.1-447d5c4",
"@typeberry/jam-host-calls": "0.0.1-459ce0b",
"@typeberry/pvm-debugger-adapter": "0.1.0-459ce0b",
"@typeberry/spectool-wasm": "0.18.1",
"@uiw/react-codemirror": "^4.23.6",
Expand Down
135 changes: 116 additions & 19 deletions src/packages/web-worker/command-handlers/host-call.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { HostCallIdentifiers } from "@/types/pvm";
import { CommandStatus, PvmApiInterface, Storage } from "../types";
import { read, write } from "@typeberry/jam-host-calls";
import { read, Registers, write, Memory } from "@typeberry/jam-host-calls";
import { WriteAccounts } from "@/packages/host-calls/write";
import { isInternalPvm } from "../utils";
import { ReadAccounts } from "@/packages/host-calls/read";
import { tryAsServiceId } from "@typeberry/block";
import { Gas, MemoryIndex, tryAsGas } from "@typeberry/pvm-debugger-adapter";
import { WasmPvmShellInterface } from "../wasmBindgenShell";

type HostCallParams = {
pvm: PvmApiInterface | null;
Expand All @@ -26,9 +28,115 @@ type HostCallResponse =
error?: unknown;
};

type ExecuteParams = Parameters<write.Write["execute"]>;
type RegistersType = ExecuteParams[1];
type MemoryType = ExecuteParams[2];
class SimpleGas {
pvm!: WasmPvmShellInterface;

get(): Gas {
return tryAsGas(this.pvm.getGasLeft());
}
set(gas: Gas) {
if (this.pvm.setGasLeft) {
this.pvm.setGasLeft(BigInt(gas));
}
}
sub(v: Gas) {
const current = this.get();
if (current > v) {
this.set(tryAsGas(BigInt(current) - BigInt(v)));
return false;
}
// underflow
this.set(tryAsGas(0));
return true;
}
}

const getGasCounter = (pvm: PvmApiInterface) => {
if (isInternalPvm(pvm)) {
return pvm.getInterpreter().getGasCounter();
}

const gas = new SimpleGas();
gas.pvm = pvm;

return gas;
};

class SimpleRegisters implements Registers {
flatRegisters!: Uint8Array;
pvm!: WasmPvmShellInterface;

getU32(registerIndex: number): number {
return Number(this.getU64(registerIndex) & 0xffff_ffffn);
}
getI32(registerIndex: number): number {
return Number(this.getU32(registerIndex)) >> 0;
}
setU32(registerIndex: number, value: number): void {
this.setU64(registerIndex, BigInt(value));
}
setI32(registerIndex: number, value: number): void {
this.setI64(registerIndex, BigInt(value));
}
getU64(registerIndex: number): bigint {
return new BigUint64Array(this.flatRegisters.buffer)[registerIndex];
}
getI64(registerIndex: number): bigint {
return new BigInt64Array(this.flatRegisters.buffer)[registerIndex];
}
setU64(registerIndex: number, value: bigint): void {
new BigUint64Array(this.flatRegisters.buffer)[registerIndex] = value;
this.pvm.setRegisters(this.flatRegisters);
}
setI64(registerIndex: number, value: bigint): void {
new BigInt64Array(this.flatRegisters.buffer)[registerIndex] = value;
this.pvm.setRegisters(this.flatRegisters);
}
}

const getRegisters = (pvm: PvmApiInterface) => {
if (isInternalPvm(pvm)) {
return pvm.getInterpreter().getRegisters();
}

const registers = new SimpleRegisters();
registers.flatRegisters = pvm.getRegisters();
registers.pvm = pvm;

return registers;
};

class SimpleMemory implements Memory {
pvm!: WasmPvmShellInterface;
memorySize: number = 4096;

loadInto(result: Uint8Array, startAddress: MemoryIndex) {
const memoryDump = this.pvm.getPageDump(startAddress / this.memorySize);
result.set(memoryDump.subarray(0, result.length));

return null;
}
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
isWriteable(_destinationStart: MemoryIndex, _length: number) {
return true;
}

storeFrom(address: MemoryIndex, bytes: Uint8Array) {
// TODO [ToDr] Either change the API to require handling multi-page writes or change this code to split the write into multiple pages.
this.pvm.setMemory(address, bytes);
return null;
}
}

const getMemory = (pvm: PvmApiInterface) => {
if (isInternalPvm(pvm)) {
return pvm.getInterpreter().getMemory();
}
const memory = new SimpleMemory();
memory.pvm = pvm;

return memory;
};

const hostCall = async ({
pvm,
Expand All @@ -41,32 +149,21 @@ const hostCall = async ({
storage: Storage;
serviceId: number;
}): Promise<HostCallResponse> => {
if (!isInternalPvm(pvm)) {
return { hostCallIdentifier, status: CommandStatus.ERROR, error: new Error("External PVM not supported") };
}

if (hostCallIdentifier === HostCallIdentifiers.READ) {
const readAccounts = new ReadAccounts(storage);
const jamHostCall = new read.Read(readAccounts);
// TODO the types are the same, but exported from different packages and lost track of the type
jamHostCall.currentServiceId = tryAsServiceId(serviceId) as unknown as typeof jamHostCall.currentServiceId;

await jamHostCall.execute(
pvm.getInterpreter().getGasCounter(),
pvm.getInterpreter().getRegisters() as unknown as RegistersType,
pvm.getInterpreter().getMemory() as unknown as MemoryType,
);
const registers = getRegisters(pvm);
await jamHostCall.execute(getGasCounter(pvm), registers, getMemory(pvm));

return { hostCallIdentifier, status: CommandStatus.SUCCESS };
} else if (hostCallIdentifier === HostCallIdentifiers.WRITE) {
const writeAccounts = new WriteAccounts(storage);
const jamHostCall = new write.Write(writeAccounts);

await jamHostCall.execute(
pvm.getInterpreter().getGasCounter(),
pvm.getInterpreter().getRegisters() as unknown as RegistersType,
pvm.getInterpreter().getMemory() as unknown as MemoryType,
);
await jamHostCall.execute(getGasCounter(pvm), getRegisters(pvm), getMemory(pvm));

return { hostCallIdentifier, storage, status: CommandStatus.SUCCESS };
}

Expand Down
2 changes: 1 addition & 1 deletion src/packages/web-worker/command-handlers/memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export type MemoryResponse = {
error?: unknown;
};

const getMemoryPage = (pageNumber: number, pvm: PvmApiInterface | null) => {
export const getMemoryPage = (pageNumber: number, pvm: PvmApiInterface | null) => {
if (!pvm) {
return new Uint8Array();
}
Expand Down
12 changes: 2 additions & 10 deletions src/packages/web-worker/command-handlers/step.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { CurrentInstruction, ExpectedState, Status } from "@/types/pvm";
import { nextInstruction } from "../pvm";
import { getState } from "../utils";
import { CommandStatus, PvmApiInterface, Storage } from "../types";
import { runHostCall } from "./host-call";

export type StepParams = {
program: Uint8Array;
Expand All @@ -20,20 +19,13 @@ export type StepResponse = {
isFinished: boolean;
};

const step = async ({ pvm, program, stepsToPerform, storage, serviceId }: StepParams) => {
const step = async ({ pvm, program, stepsToPerform }: StepParams) => {
if (!pvm) {
throw new Error("PVM is uninitialized.");
}

let isFinished = stepsToPerform > 1 ? !pvm.nSteps(stepsToPerform) : !pvm.nextStep();
let state = getState(pvm);

if (state.status === Status.HOST && storage !== null && serviceId !== null) {
const hostCallIdentifier = pvm.getExitArg();
await runHostCall({ pvm, hostCallIdentifier, storage, serviceId });
// pvm.nextStep();
state = getState(pvm);
}
const state = getState(pvm);

// It's not really finished if we're in host status
if (isFinished && state.status === Status.HOST) {
Expand Down
46 changes: 21 additions & 25 deletions src/store/workers/workersSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,33 +288,29 @@ export const handleHostCall = createAsyncThunk("workers/handleHostCall", async (
}

await Promise.all(
state.workers
// [KF] Remove or change this condition when we support host calls on more PVMs.
.filter(({ id }) => id === "typeberry")
.map(async (worker) => {
const resp = await asyncWorkerPostMessage(worker.id, worker.worker, {
command: Commands.HOST_CALL,
payload: { hostCallIdentifier: worker.exitArg as HostCallIdentifiers },
});

if (
resp.payload.hostCallIdentifier === HostCallIdentifiers.WRITE &&
resp.payload.storage &&
// Remove if we decide to make storage initialization optional
state.debugger.storage
) {
const newStorage = mergePVMAndDebuggerEcalliStorage(resp.payload.storage, state.debugger.storage);
dispatch(setStorage(newStorage));
}
state.workers.map(async (worker) => {
const resp = await asyncWorkerPostMessage(worker.id, worker.worker, {
command: Commands.HOST_CALL,
payload: { hostCallIdentifier: worker.exitArg as HostCallIdentifiers },
});
if (
resp.payload.hostCallIdentifier === HostCallIdentifiers.WRITE &&
resp.payload.storage &&
// Remove if we decide to make storage initialization optional
state.debugger.storage
) {
const newStorage = mergePVMAndDebuggerEcalliStorage(resp.payload.storage, state.debugger.storage);
dispatch(setStorage(newStorage));
}

if ((getState() as RootState).debugger.isRunMode) {
dispatch(continueAllWorkers());
}
if ((getState() as RootState).debugger.isRunMode) {
dispatch(continueAllWorkers());
}

if (hasCommandStatusError(resp)) {
throw new Error(resp.error.message);
}
}),
if (hasCommandStatusError(resp)) {
throw new Error(resp.error.message);
}
}),
);

if (selectShouldContinueRunning(state)) {
Expand Down

0 comments on commit 7d522d4

Please sign in to comment.