Skip to content
Open
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
1 change: 1 addition & 0 deletions .github/workflows/main-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ jobs:
- name: Build and test Component Model runtime
run: |
pnpm --filter '@bytecodealliance/jco-cm-runtime' run build
pnpm --filter '@bytecodealliance/jco-cm-runtime' run types:check
pnpm --filter '@bytecodealliance/jco-cm-runtime' run test

- name: Upload Component Model runtime build output
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ jobs:
- name: Build and test Component Model runtime
run: |
pnpm --filter '@bytecodealliance/jco-cm-runtime' run build
pnpm --filter '@bytecodealliance/jco-cm-runtime' run types:check
pnpm --filter '@bytecodealliance/jco-cm-runtime' run test

- name: Upload Component Model runtime build output
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,9 @@ jobs:
cd /tmp/test-jco-cm-runtime
echo '{"private":true,"type":"module"}' > package.json
pnpm install "$PACKAGE_PATH"
node --input-type=module -e "import { runtime, RUNTIME_ABI_VERSION } from '@bytecodealliance/jco-cm-runtime'; const instance = runtime.create({ requestedAbiVersion: RUNTIME_ABI_VERSION, strict: false, flagsAsBigInt: false, nodejsCompat: true, asyncDeterminism: 'random' }); const entry = instance.intrinsics.resource.tableGet([1 << 30, 0, 7, 42 | (1 << 30)], 1); if (entry.rep !== 42 || !entry.own) throw new Error('runtime smoke test failed');"
cp "$GH_WORKSPACE/scripts/test-jco-cm-runtime-release.mjs" ./smoke.mjs
cp -r "$GH_WORKSPACE/scripts/cm-runtime-conformance" ./cm-runtime-conformance
node smoke.mjs

- name: Test built jco-node-fs NPM package
if: ${{ needs.meta.outputs.project == 'jco-node-fs' }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ world js-component-bindgen {
/// both use utf16 (they are validated by default)
perf-strings-skip-copy-utf16-validation: option<bool>,

/// ES module providing the Component Model runtime implementation.
/// Experimental Component Model runtime package or directory base.
/// Imports named runtime and intrinsics/* subpaths; directory/URL bases use .js files.
cm-runtime-es-module: option<string>,

/// Represent WIT flags as bigint values instead of objects of booleans.
Expand Down
13 changes: 8 additions & 5 deletions crates/js-component-bindgen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,14 @@ $ cargo add js-component-bindgen
```

Generated JavaScript includes its runtime intrinsics inline by default. To
select an external runtime provider, set `TranspileOpts::cm_runtime_es_module` to
`@bytecodealliance/jco-cm-runtime` or another module specifier. Make the selected
module resolvable from the generated output; it must export a `runtime` provider.
Currently, external providers implement `resource.tableGet`, while the remaining
intrinsics are generated inline.
select an external runtime, set `TranspileOpts::cm_runtime_es_module` to
`@bytecodealliance/jco-cm-runtime` or another package/directory base. Generated bindings
import `CM_RUNTIME_API_VERSION` from `<base>/runtime` and `tableGet` from
`<base>/intrinsics/resource`; directory and file/HTTP(S) URL bases append `.js`.
Relative bases resolve from the generated output. `tableGet` receives an explicit
trap context, table and handle; other intrinsics remain inline. Legacy provider
files and `runtime.create` must migrate to the
[named-module contract](../../packages/jco-cm-runtime/docs/runtime.md).

# License

Expand Down
108 changes: 56 additions & 52 deletions crates/js-component-bindgen/src/intrinsics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ use wasmtime_environ::Trap;
use crate::source::Source;
use crate::{TranspileOpts, uwrite, uwriteln};

pub(crate) const CM_RUNTIME_API_VERSION: u32 = 2;
pub(crate) const CM_RUNTIME_PROVIDER_LOCAL_NAME: &str = "_jcoRuntimeProvider";
const CM_RUNTIME_LOCAL_NAME: &str = "_jcoRuntime";
const CM_RUNTIME_INTRINSICS_LOCAL_NAME: &str = "_jcoIntrinsics";
pub(crate) mod runtime;
use runtime::{
CM_RUNTIME_API_VERSION, RuntimeContext, RuntimeImport, TABLE_GET_CONTEXT, VERSION_LOCAL,
};

pub(crate) mod conversion;
use conversion::ConversionIntrinsic;
Expand Down Expand Up @@ -207,16 +207,36 @@ pub(crate) enum IntrinsicBinding {
},
Runtime {
local_name: &'static str,
path: &'static str,
import: RuntimeImport,
},
}

impl IntrinsicBinding {
fn local_name(self) -> &'static str {
pub(crate) fn local_name(self) -> &'static str {
match self {
Self::Inline { local_name } | Self::Runtime { local_name, .. } => local_name,
}
}

/// Render a call, inserting only the context required by this operation.
pub(crate) fn call(self, operands: &str) -> String {
let name = self.local_name();
match self {
Self::Runtime {
import:
RuntimeImport {
context: Some(context),
..
},
..
} => {
let context = context.local_name();
let separator = if operands.is_empty() { "" } else { ", " };
format!("{name}({context}{separator}{operands})")
}
_ => format!("{name}({operands})"),
}
}
}

macro_rules! impl_from_intrinsic {
Expand Down Expand Up @@ -2540,6 +2560,14 @@ pub struct RenderIntrinsicsArgs<'a> {
}

impl RenderIntrinsicsArgs<'_> {
pub(crate) fn intrinsic_call(&self, intrinsic: impl Into<Intrinsic>, operands: &str) -> String {
let intrinsic = intrinsic.into();
self.require_intrinsic(intrinsic);
intrinsic
.binding(self.transpile_opts.cm_runtime_es_module.is_some())
.call(operands)
}

/// Retrieve an intrinsic's name and register it as a dependency of the
/// intrinsic currently being rendered.
pub fn require_intrinsic(&self, intrinsic: impl Into<Intrinsic>) -> &'static str {
Expand Down Expand Up @@ -2606,52 +2634,25 @@ fn render_intrinsics_discovered(args: &mut RenderIntrinsicsArgs<'_>) -> Source {
uwriteln!(
output,
r#"
if (typeof {CM_RUNTIME_PROVIDER_LOCAL_NAME}?.create !== 'function') {{
throw new TypeError('Jco Component Model runtime provider must define create(options)');
}}
if ({CM_RUNTIME_PROVIDER_LOCAL_NAME}.apiVersion !== {CM_RUNTIME_API_VERSION}) {{
throw new Error(`incompatible Jco JavaScript Component Model runtime API: requested {CM_RUNTIME_API_VERSION}, supported ${{{CM_RUNTIME_PROVIDER_LOCAL_NAME}.apiVersion}}`);
}}
const {CM_RUNTIME_LOCAL_NAME} = {CM_RUNTIME_PROVIDER_LOCAL_NAME}.create({{
requestedApiVersion: {CM_RUNTIME_API_VERSION},
strict: {strict},
flagsAsBigInt: {flags_as_bigint},
nodejsCompat: {nodejs_compat},
asyncDeterminism: '{determinism}',
platform: {{ WebAssembly }},
}});
if ({CM_RUNTIME_LOCAL_NAME}?.apiVersion !== {CM_RUNTIME_API_VERSION}) {{
throw new Error(`incompatible Jco JavaScript Component Model runtime API instance: requested {CM_RUNTIME_API_VERSION}, received ${{{CM_RUNTIME_LOCAL_NAME}?.apiVersion}}`);
if ({VERSION_LOCAL} !== {CM_RUNTIME_API_VERSION}) {{
throw new Error(`incompatible Jco JavaScript Component Model runtime API: requested {CM_RUNTIME_API_VERSION}, supported ${{{VERSION_LOCAL}}}`);
}}
const {CM_RUNTIME_INTRINSICS_LOCAL_NAME} = {CM_RUNTIME_LOCAL_NAME}.intrinsics;
"#,
strict = args.transpile_opts.strict,
flags_as_bigint = args.transpile_opts.flags_as_bigint,
nodejs_compat = !args.transpile_opts.nodejs_compat_disabled,
determinism = args.determinism_profile,
);

let mut contexts = BTreeSet::new();
for intrinsic in args.intrinsics.iter() {
let IntrinsicBinding::Runtime { local_name, path } =
intrinsic.binding(args.transpile_opts.cm_runtime_es_module.is_some())
else {
continue;
};
let (receiver_path, method) = path
.rsplit_once('.')
.expect("runtime intrinsic paths must name a method on a receiver");
output.push_str(&format!(
r#"
const {local_name} = (() => {{
const receiver = {CM_RUNTIME_INTRINSICS_LOCAL_NAME}?.{receiver_path};
const intrinsic = receiver?.{method};
if (typeof intrinsic !== 'function') {{
throw new TypeError('Jco Component Model runtime intrinsic {path} must be a function');
}}
return intrinsic.bind(receiver);
}})();
"#
));
if let IntrinsicBinding::Runtime {
import:
RuntimeImport {
context: Some(context),
..
},
..
} = intrinsic.binding(true)
&& contexts.insert(context.local_name())
{
context.render(&mut output);
}
}
}
if args
Expand Down Expand Up @@ -2774,7 +2775,11 @@ impl Intrinsic {
Self::Resource(ResourceIntrinsic::ResourceTableGet) if external_runtime => {
IntrinsicBinding::Runtime {
local_name: self.name(),
path: "resource.tableGet",
import: RuntimeImport {
suffix: "intrinsics/resource",
export: "tableGet",
context: Some(RuntimeContext::ResourceTableGet),
},
}
}
_ => IntrinsicBinding::Inline {
Expand Down Expand Up @@ -2826,9 +2831,8 @@ impl Intrinsic {
"imports",
"instantiateCore",
"isLE",
CM_RUNTIME_PROVIDER_LOCAL_NAME,
CM_RUNTIME_LOCAL_NAME,
CM_RUNTIME_INTRINSICS_LOCAL_NAME,
VERSION_LOCAL,
TABLE_GET_CONTEXT,
"scopeId",
"symbolCabiDispose",
"symbolCabiLower",
Expand Down
5 changes: 3 additions & 2 deletions crates/js-component-bindgen/src/intrinsics/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,8 @@ impl ResourceIntrinsic {
let resource_transfer_borrow_fn = self.name();
let handle_tables = render_args.require_intrinsic(Intrinsic::HandleTables);
let resource_borrows = render_args.require_intrinsic(Self::ResourceCallBorrows);
let rsc_table_get = render_args.require_intrinsic(Self::ResourceTableGet);
let table_get_call =
render_args.intrinsic_call(Self::ResourceTableGet, "fromTable, handle");
let rsc_table_create_borrow =
render_args.require_intrinsic(Self::ResourceTableCreateBorrow);
let scope_id = render_args.require_intrinsic(Intrinsic::ScopeId);
Expand All @@ -360,7 +361,7 @@ impl ResourceIntrinsic {
r#"
function {resource_transfer_borrow_fn}(handle, fromTid, toTid) {{
const fromTable = {handle_tables}[fromTid];
const {{ rep, own }} = {rsc_table_get}(fromTable, handle);
const {{ rep, own }} = {table_get_call};

let toTable = {handle_tables}[toTid];
if (!toTable) {{
Expand Down
Loading
Loading