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

Send serialized VmData to webworkers #95

Merged
merged 4 commits into from
Apr 20, 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
2 changes: 2 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions fidget/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ ordered-float = "3"
static_assertions = "1"
thiserror = "1"
workspace-hack = { version = "0.1", path = "../workspace-hack" }
serde = { version = "1.0", features = ["derive"] }

# JIT
dynasmrt = { version = "2.0", optional = true }
Expand Down
6 changes: 4 additions & 2 deletions fidget/src/core/compiler/op.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use serde::{Deserialize, Serialize};

/// Macro to generate a set of opcodes, using the given type for registers
macro_rules! opcodes {
(
Expand Down Expand Up @@ -139,7 +141,7 @@ opcodes!(
/// - RHS register (or immediate for `*Imm`)
///
/// Each "register" represents an SSA slot, which is never reused.
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub enum SsaOp<u32> {
// default variants
}
Expand Down Expand Up @@ -250,7 +252,7 @@ opcodes!(
///
/// We have a maximum of 256 registers, though some tapes (e.g. ones
/// targeting physical hardware) may choose to use fewer.
#[derive(Copy, Clone, Debug, PartialEq)]
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum RegOp<u8> {
// default variants
/// Read from a memory slot to a register
Expand Down
3 changes: 2 additions & 1 deletion fidget/src/core/compiler/reg_tape.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
//! Tape used for evaluation
use crate::compiler::{RegOp, RegisterAllocator, SsaTape};
use serde::{Deserialize, Serialize};

/// Low-level tape for use with the Fidget virtual machine (or to be lowered
/// further into machine instructions).
#[derive(Clone, Default)]
#[derive(Clone, Default, Serialize, Deserialize)]
pub struct RegTape {
tape: Vec<RegOp>,

Expand Down
3 changes: 2 additions & 1 deletion fidget/src/core/compiler/ssa_tape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::{
context::{BinaryOpcode, Node, Op, UnaryOpcode},
Context, Error,
};
use serde::{Deserialize, Serialize};

use std::collections::{HashMap, HashSet};

Expand All @@ -16,7 +17,7 @@ use std::collections::{HashMap, HashSet};
/// - 4-byte RHS register (or immediate `f32`)
///
/// All register addressing is absolute.
#[derive(Clone, Debug, Default)]
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct SsaTape {
/// The tape is stored in reverse order, such that the root of the tree is
/// the first item in the tape.
Expand Down
3 changes: 2 additions & 1 deletion fidget/src/core/vm/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::{
vm::Choice,
Error,
};
use serde::{Deserialize, Serialize};

/// A flattened math expression, ready for evaluation or further compilation.
///
Expand Down Expand Up @@ -57,7 +58,7 @@ use crate::{
/// Despite this peek at its internals, users are unlikely to touch `VmData`
/// directly; a [`VmShape`](crate::vm::VmShape) wraps the `VmData` and
/// implements our common traits.
#[derive(Default)]
#[derive(Default, Serialize, Deserialize)]
pub struct VmData<const N: usize = { u8::MAX as usize }> {
ssa: SsaTape,
asm: RegTape,
Expand Down
6 changes: 6 additions & 0 deletions fidget/src/core/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ impl AsRef<[Choice]> for VmTrace {
#[derive(Clone)]
pub struct GenericVmShape<const N: usize>(Arc<VmData<N>>);

impl<const N: usize> From<VmData<N>> for GenericVmShape<N> {
fn from(d: VmData<N>) -> Self {
Self(d.into())
}
}

impl<const N: usize> GenericVmShape<N> {
pub(crate) fn simplify_inner(
&self,
Expand Down
32 changes: 32 additions & 0 deletions wasm-demo/Cargo.lock

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

1 change: 1 addition & 0 deletions wasm-demo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ edition = "2021"
crate-type = ["cdylib"]

[dependencies]
bincode = "1.3.3"
fidget = {path = "../fidget", default-features = false, features = ["rhai", "mesh", "render"]}
wasm-bindgen = "0.2.92"
nalgebra = "0.31"
Expand Down
22 changes: 12 additions & 10 deletions wasm-demo/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { defaultKeymap } from "@codemirror/commands";

import {
ResponseKind,
ScriptRequest,
ShapeRequest,
StartRequest,
WorkerResponse,
WorkerRequest,
Expand Down Expand Up @@ -55,16 +55,9 @@ class App {

onScriptChanged(text: string) {
let shape = null;
let result = null;
let result = "Ok(..)";
try {
const shapeTree = fidget.eval_script(text);
result = "Ok(..)";

this.start_time = performance.now();
this.workers_done = 0;
this.workers.forEach((w) => {
w.postMessage(new ScriptRequest(text));
});
shape = fidget.eval_script(text);
} catch (error) {
// Do some string formatting to make errors cleaner
result = error
Expand All @@ -74,6 +67,15 @@ class App {
.replace(" (expecting ", "\n(expecting ");
}
this.output.setText(result);

if (shape) {
const tape = fidget.serialize_into_tape(shape);
this.start_time = performance.now();
this.workers_done = 0;
this.workers.forEach((w) => {
w.postMessage(new ShapeRequest(tape));
});
}
}

onWorkerMessage(i: number, req: WorkerResponse) {
Expand Down
16 changes: 8 additions & 8 deletions wasm-demo/message.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export enum RequestKind {
Start,
Script,
Shape,
}

export class StartRequest {
Expand All @@ -13,17 +13,17 @@ export class StartRequest {
}
}

export class ScriptRequest {
kind: RequestKind.Script;
script: string;
export class ShapeRequest {
kind: RequestKind.Shape;
tape: Uint8Array;

constructor(s: string) {
this.script = s;
this.kind = RequestKind.Script;
constructor(tape: Uint8Array) {
this.tape = tape;
this.kind = RequestKind.Shape;
}
}

export type WorkerRequest = ScriptRequest | StartRequest;
export type WorkerRequest = ShapeRequest | StartRequest;

////////////////////////////////////////////////////////////////////////////////

Expand Down
50 changes: 22 additions & 28 deletions wasm-demo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use fidget::{
eval::MathShape,
render::{BitRenderMode, RenderConfig},
shape::Bounds,
vm::VmShape,
vm::{VmData, VmShape},
Error,
};
use wasm_bindgen::prelude::*;
Expand All @@ -12,35 +12,32 @@ use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct JsTree(Tree);

#[derive(Clone)]
#[wasm_bindgen]
pub struct JsVmShape(VmShape);

#[wasm_bindgen]
pub fn eval_script(s: &str) -> Result<JsTree, String> {
let mut engine = fidget::rhai::Engine::new();
let out = engine.eval(s);
out.map(JsTree).map_err(|e| format!("{e}"))
}

/// Serializes a `JsTree` into a `bincode`-packed `VmData`
#[wasm_bindgen]
pub fn render(t: JsTree, image_size: usize) -> Result<Vec<u8>, String> {
fn inner(t: Tree, image_size: usize) -> Result<Vec<u8>, Error> {
let mut ctx = Context::new();
let root = ctx.import(&t);

let cfg = RenderConfig::<2> {
image_size,
..RenderConfig::default()
};
pub fn serialize_into_tape(t: JsTree) -> Result<Vec<u8>, String> {
let mut ctx = Context::new();
let root = ctx.import(&t.0);
let shape = VmShape::new(&ctx, root).map_err(|e| format!("{e}"))?;
bincode::serialize(shape.data()).map_err(|e| format!("{e}"))
}

let shape = VmShape::new(&ctx, root)?;
let out = cfg.run(shape, &BitRenderMode)?;
Ok(out
.into_iter()
.flat_map(|b| {
let b = b as u8 * u8::MAX;
[b, b, b, 255]
})
.collect())
}
inner(t.0, image_size).map_err(|e| format!("{e}"))
/// Deserialize a `bincode`-packed `VmData` into a `VmShape`
#[wasm_bindgen]
pub fn deserialize_tape(data: Vec<u8>) -> Result<JsVmShape, String> {
let d: VmData<255> =
bincode::deserialize(&data).map_err(|e| format!("{e}"))?;
Ok(JsVmShape(VmShape::from(d)))
}

/// Renders a subregion of an image, for webworker-based multithreading
Expand All @@ -49,7 +46,7 @@ pub fn render(t: JsTree, image_size: usize) -> Result<Vec<u8>, String> {
/// into `0 <= pos < workers_per_side^2` tiles.
#[wasm_bindgen]
pub fn render_region(
t: JsTree,
shape: JsVmShape,
image_size: usize,
index: usize,
workers_per_side: usize,
Expand All @@ -63,14 +60,11 @@ pub fn render_region(
);
}
fn inner(
t: Tree,
shape: VmShape,
image_size: usize,
index: usize,
workers_per_side: usize,
) -> Result<Vec<u8>, Error> {
let mut ctx = Context::new();
let root = ctx.import(&t);

// Corner position in [0, workers_per_side] coordinates
let mut corner = nalgebra::Vector2::new(
index / workers_per_side,
Expand All @@ -95,7 +89,6 @@ pub fn render_region(
..RenderConfig::default()
};

let shape = VmShape::new(&ctx, root)?;
let out = cfg.run(shape, &BitRenderMode)?;
Ok(out
.into_iter()
Expand All @@ -105,5 +98,6 @@ pub fn render_region(
})
.collect())
}
inner(t.0, image_size, index, workers_per_side).map_err(|e| format!("{e}"))
inner(shape.0, image_size, index, workers_per_side)
.map_err(|e| format!("{e}"))
}
12 changes: 6 additions & 6 deletions wasm-demo/worker.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
ImageResponse,
RequestKind,
ScriptRequest,
ShapeRequest,
StartRequest,
StartedResponse,
WorkerRequest,
Expand All @@ -19,10 +19,10 @@ class Worker {
this.index = req.index;
}

render(s: ScriptRequest) {
const tree = fidget.eval_script(s.script);
render(s: ShapeRequest) {
const shape = fidget.deserialize_tape(s.tape);
const out = fidget.render_region(
tree,
shape,
RENDER_SIZE,
this.index,
WORKERS_PER_SIDE,
Expand All @@ -42,8 +42,8 @@ async function run() {
worker = new Worker(req as StartRequest);
break;
}
case RequestKind.Script: {
worker!.render(req as ScriptRequest);
case RequestKind.Shape: {
worker!.render(req as ShapeRequest);
break;
}
default:
Expand Down
Loading