Skip to content

Latest commit

 

History

History
122 lines (93 loc) · 4.76 KB

File metadata and controls

122 lines (93 loc) · 4.76 KB

Inline JavaScript (WEBCC_JS)

WEBCC_JS is WebCC's escape hatch: it lets you define a function whose body is raw JavaScript, for the rare cases the schema doesn't cover yet. It is the analog of Emscripten's EM_JS, built the WebCC way, so it stays consistent with the rest of the toolchain (linker-driven feature detection, tree-shaking, no runtime dispatch tables).

Prefer the schema. A schema command is typed, batched through the command buffer, and reusable across your codebase. Reach for WEBCC_JS only when you need a browser API that isn't in the schema yet, and consider contributing it to schema.def instead.

Usage

You declare the function once (a header is fine) and call it like any other C++ function. Arguments are passed by name, so the JS body refers to them by name (no $0/$1):

#include "webcc/system.h"   // any webcc header makes WEBCC_JS available

WEBCC_JS(void, set_title, (const char* title), {
    document.title = title;
});

WEBCC_JS(int, js_add, (int a, int b), {
    return a + b;
});

WEBCC_JS(double, viewport_area, (), {
    return window.innerWidth * window.innerHeight;
});

void demo() {
    set_title("Hello");           // called like a normal function
    int s = js_add(2, 3);
    double a = viewport_area();
}

The macro shape is WEBCC_JS(return_type, name, (params), { body }).

Calls are synchronous and flush() the command buffer first (exactly like webcc's other return-value calls), so the body observes the effects of any preceding batched commands.

Supported types (v1)

Parameters

C++ parameter In the JS body
int, integer handles a JS number
unsigned / uint32_t a JS number (reinterpreted as unsigned)
float, double a JS number
const char* auto-decoded to a JS string (read NUL-terminated, so string literals and webcc::string work)

Return type: void, int, float, or double. For a string return, write the result back yourself for now (e.g. through the scratch buffer); a typed string return may come later.

Use block comments (/* */) inside the body, not //, because the source is collapsed onto a single line.

How it works

The whole function, signature and body, is compiled into a wasm import whose import name is the source itself, in the module wjs_fn:

(import "wjs_fn" "set_title(const char* title){ document.title = title; }"
        (func (param i32)))

After linking, the webcc CLI reads these imports out of the linked module's import table (the same mechanism used for every other feature), parses each back into name / params / body, and emits a matching handler into app.js:

imports.wjs_fn = {
  "set_title(const char* title){ document.title = title; }":
      (title) => { title = __webcc_utf8(title);  document.title = title; },
};

The wasm engine resolves the import by name, so the import resolution is the dispatch: no id table, no per-call lookup. Calling set_title from C++ is a direct, typed wasm import call (arguments cross via the normal ABI), wrapped in a tiny forwarding stub that adds the automatic flush().

This buys two properties for free:

  • Exact tree-shaking. A function only appears in the import table if it's actually called. Unused WEBCC_JS functions are dead-stripped and emit no handler, so they cost nothing in the wasm or the JS.
  • No source scanning. Detection comes from the linked binary, never from grepping your .cc files, so it's immune to macros, aliases, and conditional compilation, consistent with the rest of WebCC's feature detection.

const char* parameters arrive as integer pointers; the generated handler runs them through a small __webcc_utf8 helper that reads the NUL-terminated UTF-8 string from linear memory. That helper is only emitted when a function actually takes a string parameter.

Performance

Every WEBCC_JS call is synchronous: it crosses the JS boundary and flushes the command buffer first, just like a schema return-value command. That's cheap for occasional use but not for tight loops. On hot paths, prefer a real schema command, which batches through the command buffer and crosses the boundary once per flush(). Keep WEBCC_JS for setup, one-off calls, and APIs the schema doesn't expose yet.

Comparison with Emscripten EM_JS

Both declare a named C/C++ function with a JavaScript body and extract that body at build time. The difference is the channel. Emscripten stores the body in a custom section and wires it up through generated glue; WebCC carries the whole source as the import name, so detection, tree-shaking, and dispatch all fall out of the ordinary wasm import table with no extra runtime machinery.