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

Support WebAssembly #5

Open
2 of 8 tasks
DrSensor opened this issue Sep 8, 2022 · 9 comments
Open
2 of 8 tasks

Support WebAssembly #5

DrSensor opened this issue Sep 8, 2022 · 9 comments
Labels
data binding Everything related to data binding enhancement New feature or request experimental Breaking changes can happen at any time priority: high Although it's not part of the milestone, it's urgent and must be resolved

Comments

@DrSensor
Copy link
Owner

DrSensor commented Sep 8, 2022

prototype (stalled)

Warning: may not relevant anymore
see comment at the bottom for the current approach

Unlike Javascript module that export class, setter in WebAssembly doesn't automatically update html attribute. You need to specify which bounded property to update.

//! lib.zig -> script.wasm

/// update property which bounded to several attributes 
extern fn update([]const u8) void; // 👈👇
/// most likely explicit update may not needed since
/// the update are automatic after event handler is executed

/// get instance id number
/// throw error if not an instance (i.e called inside static method)
extern fn lc() u8; // link count
// extern lc: u8 // according to spec, wasm support global value import

var count: [4]u8 = undefined; // limit instance to 4
export fn @"get count"() u8 {
    return count[lc()];
}
export fn @"set count"(value: u8) void {
    count[lc()] = value;
    update("count");
}

export fn constructor() { // called every time when <script> attached into <render-scope>
  count[lc()] = 0;
}

export fn static() { // called only once
  // do something when the wasm module loaded
}

var static_count: u8 = 0;
export fn @"static get count"() u8 {
    return static_count;
}
export fn @"static set count"(value: u8) void {
    static_count = value;
    update("static count");
}

export fn increment() void {
    count[lc()] += 1;
    @"set count"(count);

    static_count += 1;
    @"static set count"(static_count);
}

Note that the setter not really necessary in this example. You can rid them and just call update function. I use setter just for demonstration that this feature indeed support a setter.

<!-- page.html -->
<render-scope>
  <button onclick:="increment">

  <script type="application/wasm" src="script.wasm"></script>
  <input type="button" value:="^count">
  <input type="button" value:="count">
</render-scope>

<render-scope>

  <script type="application/wasm" src="script.wasm"></script>
  <input type="button" value:="^count">
  <input type="button" value:="count">
</render-scope>

🤔 Food for Thought
Maybe consider storing the property, getter/setter operation, and others as ArrayBuffer for maximum flexibility
(inspired from WASM-4 Memory Map)

@DrSensor DrSensor added enhancement New feature or request priority: medium Something that should be tackled data binding Everything related to data binding labels Sep 8, 2022
@DrSensor DrSensor added the experimental Breaking changes can happen at any time label Nov 1, 2022
@DrSensor
Copy link
Owner Author

@DrSensor
Copy link
Owner Author

DrSensor commented Feb 2, 2023

TBD

import { Attribute, Bind, ColonFor } from "../query.ts";
import bind from "./common.ts";

const cache: Record<string, Module> = {};
let count = 0;

export default (attrs: Attribute[]) => async (path: string) => {
  let linkCount = count;
  (await source).url;
  const module = await WebAssembly.instantiateStreaming(source, {
    env: { lc: () => linkCount },
  });
  count++;
};

/** WebAssembly module **instance** */
interface Module {
  /** instantiate module */
  ["constructor"](): void;
  /** instance accessor for accessing instance value */
  [key: `get ${string}`]: () => unknown;
  /** instance accessor for mutating instance value */
  [key: `set ${string}`]: (value: unknown) => unknown;
  /** instance method that might access/mutate instance and/or global value */
  [key: string]: (...args: unknown[]) => unknown;

  /** static block which run only once */
  // ["static"](): void; // wasm $startFunction serve the same purpose
  /** static accessor for accessing global value */
  [key: `static get ${string}`]: () => unknown;
  /** static accessor for mutating global value */
  [key: `static set ${string}`]: (value: unknown) => unknown;
  /** static method that can't access/mutate instance value */
  [key: `static ${string}`]: (...args: unknown[]) => unknown;
}

@DrSensor DrSensor added priority: high Although it's not part of the milestone, it's urgent and must be resolved and removed priority: medium Something that should be tackled labels Feb 13, 2023
@DrSensor
Copy link
Owner Author

DrSensor commented Mar 6, 2023

Some references

@DrSensor
Copy link
Owner Author

DrSensor commented Apr 1, 2023

Data Type compatibility across languages

I'm thinking to use Apache Arrow format to solve this. Also, ecmascript data type still fully supported and may automatically inferred when the module is in JavaScript, but only partially (and no infer value) when in WebAssembly.
By using this columnar format, it's possible for the module to run on multiple threads whether it's main, worker, or even gpu.

In javascript it might look like

import { Map, Set, Date, ... } from "nusa/std/type"
import { Utf8, Float16, Date64, ... } from "nusa/arrow/type"

Another approach is to use objectbuffer by Bnaya Peretz

I'm in favor of Apache Arrow because the interoperability between languages are more consistent. For example, objectbuffer use ES string which is UTF-16 while most programming languages use UTF-8.

@DrSensor
Copy link
Owner Author

DrSensor commented Apr 19, 2023

@DrSensor
Copy link
Owner Author

Arquero, a JavaScript library for query processing and transformation of Apache Arrow columns

@DrSensor
Copy link
Owner Author

DrSensor commented Jul 6, 2023

Based on wasm component model and the output of wit-bindgen, the size of <link href=module.wasm> can be reduced by splitting arrow data type into different components/chunks and import it when <render-scope> is visible based on the declared imports in module.wasm.

So which language should be used for implementing that?

C23
  • ✅ It support wasm multi-value return
  • ✔️ Can manipulate wasm table (only clang 18 (unstable), stable release (clang 16) doesn't have that yet 🥲)
  • ❌ Can't import wasm table
  • ❌ Can't import mutable-globals
Rust
  • ✅ It support wasm multi-value return
  • ❌ Can't manipulate wasm table
  • ❌ Can't import wasm table
  • ❌ Can't import mutable-globals
Odin TBD most likely same limitation as Rust 🤔
Zig
  • ❌ Doesn't support wasm multi-value return
  • ❌ Can't manipulate wasm table
  • ❌ Can't import wasm table
  • ❌ Can't import mutable-globals
AssemblyScript
  • ❌ Doesn't support wasm multi-value return
  • ❌ Can't manipulate wasm table
  • ❌ Can't import wasm table
  • ✅ Can import mutable-globals

@DrSensor
Copy link
Owner Author

DrSensor commented Aug 26, 2023

I've written some of [component].wasm in frankenstein Rust 😂


What the heck is frankenstein Rust?

It's Rust without Cargo that use voodoo technique like manually linking to hand-knitted assembly code and using bunch compiler flags or tools other than rustc


Seems I will continue to go on this path 🧟‍♂️

@DrSensor
Copy link
Owner Author

DrSensor commented Sep 13, 2023

Since not all wasm language can import a function with multi-value return, I've added a fallback function with cABI prefix which pack all return values into 64bit single value. For example,

(import "nusa" "accessor" (func $a (result i32 i32)))

the fallback would be

(import "nusa" "cABIaccessor" (func $a (result i64)))

The language binding must use cABI* function instead of normal one only if they can't produce wasm binary with multivalue return.

@DrSensor DrSensor pinned this issue Sep 13, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
data binding Everything related to data binding enhancement New feature or request experimental Breaking changes can happen at any time priority: high Although it's not part of the milestone, it's urgent and must be resolved
Development

No branches or pull requests

1 participant