Skip to content

Commit

Permalink
[broken] Add WASM gas metering
Browse files Browse the repository at this point in the history
  • Loading branch information
JerwuQu committed Dec 29, 2023
1 parent 72d5ec8 commit 99d9520
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 11 deletions.
1 change: 1 addition & 0 deletions cli/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ function withCommonRunOptions (cmd) {
.env("W4_NO_QR")
.default(false)
)
.option("--metering", "Inject gas metering into WASM", false)
.option("--hot", "Enable hot swapping. When the cart is reloaded, the console memory will be preserved, allowing code changes to the cart without resetting.", false);
}

Expand Down
17 changes: 11 additions & 6 deletions cli/lib/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const { Server: WebSocketServer } = require("ws");
const open = require("open");
const process = require("process");
const { buffer } = require('node:stream/consumers');
const metering = require("wasm-metering");


async function start (cartFile, opts) {
Expand All @@ -16,23 +17,27 @@ async function start (cartFile, opts) {
if (cartFile === "-") {
// Filename "-" means read from standard in.
// This can only be read once, so must be cached.
let cart_data = await buffer(process.stdin);
const cartRawWasm = await buffer(process.stdin);
const cartWasm = opts.metering ? metering.meterWASM(cartRawWasm) : cartRawWasm;

app.get("/cart.wasm", (req, res) => {
res.send(cart_data);
res.send(cartWasm);
});
} else if (!(await fs.stat(cartFile)).isFile()) {
// If the file is not a regular file, such as a fifo, input stream etc.
// we must also cache the data.
let cart_data = await fs.readFile(cartFile);
const cartRawWasm = await fs.readFile(cartFile);
const cartWasm = opts.metering ? metering.meterWASM(cartRawWasm) : cartRawWasm;

app.get("/cart.wasm", (req, res) => {
res.send(cart_data);
res.send(cartWasm);
});
} else {
// otherwise it's a regular file, and can be read from disk every time.
app.get("/cart.wasm", (req, res) => {
res.sendFile(path.resolve(cartFile));
app.get("/cart.wasm", async (req, res) => {
const cartRawWasm = await fs.readFile(cartFile);
const cartWasm = opts.metering ? metering.meterWASM(cartRawWasm) : cartRawWasm;
res.send(cartWasm);
});
app.get("/cart.wasm.map", (req, res) => {
res.sendFile(path.resolve(cartFile+".map"));
Expand Down
110 changes: 110 additions & 0 deletions cli/package-lock.json

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

1 change: 1 addition & 0 deletions cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"pngjs": "^6.0.0",
"qrcode": "^1.4.4",
"recursive-copy": "^2.0.13",
"wasm-metering": "^0.2.1",
"ws": "^7.5.3"
},
"engines": {
Expand Down
5 changes: 5 additions & 0 deletions devtools/web/src/components/devtools/devtools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export class Wasm4Devtools extends LitElement {
private _renderGeneralView = ({
memoryView,
fps,
gasUsed,
wasmBufferByteLen,
}: UpdateControllerState) => {
const drawColors = memoryView.drawColors ?? 0;
Expand Down Expand Up @@ -110,6 +111,10 @@ export class Wasm4Devtools extends LitElement {
<h4>fps</h4>
<span class="info-box text-primary">${fps}</span>
</section>
<section class="inline-section">
<h4>gas</h4>
<span class="info-box text-primary">${gasUsed.toLocaleString('en-US')}</span>
</section>
<section class="cart-size-wrapper">
<h4>cartridge size ${wasmBufferByteLen > MAX_CART_SIZE ? `⚠️` : ''}</h4>
<div class="size-box">
Expand Down
2 changes: 2 additions & 0 deletions devtools/web/src/controllers/UpdateController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface UpdateControllerState {
memoryView: MemoryView;
storedValue: string | null;
fps: number;
gasUsed: number;
wasmBufferByteLen: number;
}

Expand All @@ -31,6 +32,7 @@ export class UpdateController implements ReactiveController {
memoryView: detail.memory,
storedValue: detail.storedValue ?? null,
fps: detail.fps,
gasUsed: detail.gasUsed,
wasmBufferByteLen: detail.wasmBufferByteLen,
};

Expand Down
7 changes: 5 additions & 2 deletions devtools/web/src/devtools-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class BufferedRuntimeData implements BufferedData {
interface RuntimeInfo {
data: DataView;
wasmBufferByteLen: number;
gasUsed: number;
}

export class DevtoolsManager {
Expand Down Expand Up @@ -85,7 +86,8 @@ export class DevtoolsManager {
this._notifyUpdateCompleted(
runtimeInfo.data,
runtimeInfo.wasmBufferByteLen,
this._calcAvgFPS()
this._calcAvgFPS(),
runtimeInfo.gasUsed,
);
}
};
Expand Down Expand Up @@ -113,12 +115,13 @@ export class DevtoolsManager {
};

private _notifyUpdateCompleted = throttle(
(dataView: DataView, wasmBufferByteLen: number, fps: number) => {
(dataView: DataView, wasmBufferByteLen: number, fps: number, gasUsed: number) => {
window.dispatchEvent(
createUpdateCompletedEvent({
dataView,
wasmBufferByteLen,
fps,
gasUsed,
bufferedData: this._bufferedData.flush(),
})
);
Expand Down
5 changes: 4 additions & 1 deletion devtools/web/src/events/update-completed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const updateCompletedEventType = 'wasm4-update-completed';
export interface UpdateCompletedDetails {
memory: MemoryView;
fps: number;
gasUsed: number;
wasmBufferByteLen: number;
storedValue: string | null;
}
Expand All @@ -28,6 +29,7 @@ function getStoredValue(): string | null {
export interface UpdateCompletedData {
dataView: DataView;
fps: number;
gasUsed: number;
bufferedData: BufferedMemoryData;
wasmBufferByteLen: number;
}
Expand All @@ -42,14 +44,15 @@ export interface UpdateCompletedData {
* @returns
*/
export function createUpdateCompletedEvent(
{ dataView, fps, bufferedData, wasmBufferByteLen }: UpdateCompletedData,
{ dataView, fps, gasUsed, bufferedData, wasmBufferByteLen }: UpdateCompletedData,
eventInit: EventInit = { bubbles: true }
): Wasm4UpdateCompletedEvent {
return new CustomEvent(updateCompletedEventType, {
...eventInit,
detail: {
memory: new MemoryView(dataView, bufferedData),
fps,
gasUsed,
wasmBufferByteLen,
storedValue: getStoredValue(),
},
Expand Down
13 changes: 11 additions & 2 deletions runtimes/web/src/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export class Runtime {
diskName: string;
diskBuffer: ArrayBuffer;
diskSize: number;
gasUsed: number;

constructor (diskName: string) {
const canvas = document.createElement("canvas");
Expand All @@ -39,7 +40,7 @@ export class Runtime {
}

this.compositor = new WebGLCompositor(gl);

this.apu = new APU();

this.diskName = diskName;
Expand Down Expand Up @@ -67,6 +68,7 @@ export class Runtime {

this.pauseState = 0;
this.wasmBufferByteLen = 0;
this.gasUsed = 0;
}

async init () {
Expand Down Expand Up @@ -159,8 +161,14 @@ export class Runtime {
tracef: this.tracef.bind(this),
};

const metering = {
usegas: (gas: number) => {
this.gasUsed += gas;
},
};

await this.bluescreenOnError(async () => {
const module = await WebAssembly.instantiate(wasmBuffer, { env });
const module = await WebAssembly.instantiate(wasmBuffer, { env, metering });
this.wasm = module.instance;

// Call the WASI _start/_initialize function (different from WASM-4's start callback!)
Expand Down Expand Up @@ -335,6 +343,7 @@ export class Runtime {
this.framebuffer.clear();
}

this.gasUsed = 0;
let update_function = this.wasm!.exports["update"];
if (typeof update_function === "function") {
this.bluescreenOnError(update_function);
Expand Down

0 comments on commit 99d9520

Please sign in to comment.