blob: b090ac736bdd9b7ae1e22454e5617ebdace43e70 [file] [edit]
#!/usr/bin/env python3
"""Projects bindings.py into its generated files; --check diffs instead of
writing so test lanes can gate on drift."""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from bindings import NAMESPACES, PODS
PACKAGES = Path(__file__).resolve().parents[4]
HEADER_PATH = PACKAGES / 'runtime/include/rive/wasm/rive_bindings_v1.h'
HOST_PATH = PACKAGES / 'runtime/src/wasm/wasm_natives_gen.hpp'
ELEM_SIZES = {
'char': 1,
'uint8_t': 1,
'uint16_t': 2,
'int32_t': 4,
'uint32_t': 4,
'float': 4,
}
WEB_HOST_PATH = PACKAGES / 'runtime/src/wasm/wasm_natives_web_gen.hpp'
STUBS_PATH = PACKAGES / 'luau_wasm/tests/vm_host_stubs.mjs'
AS_PATH = PACKAGES / 'rasc/animascript/std/rive/bindings.as'
AS_NATIVES_PATH = PACKAGES / 'rasc/animascript/std/rive/natives.as'
WEB_PATH = PACKAGES / 'runtime/src/wasm/web/rive_module_imports.mjs'
GENERATED_NOTE = 'Generated by src/wasm/idl/generate.py from bindings.py.' \
' Do not edit.'
def camel(snake):
parts = snake.split('_')
return parts[0] + ''.join(p.capitalize() for p in parts[1:])
def pascal(snake):
out = camel(snake)
return out[:1].upper() + out[1:]
# Bytes per element of a buffer: a scalar, or a pod of u32 and f32 fields.
def elem_size(elem):
if elem in ELEM_SIZES:
return ELEM_SIZES[elem]
for p in PODS:
if pod_ctype(p['name']) == elem or p['name'] == elem:
return 4 * len(p['fields'])
raise ValueError(elem)
def pod_ctype(podname):
return 'rive_%s_v1' % podname
def pod_struct_lines():
lines = []
for p in PODS:
lines.append('typedef struct %s' % pod_ctype(p['name']))
lines.append('{')
for field in p['fields']:
ctype = 'float' if field['kind'] == 'f32' else 'uint32_t'
lines.append(' %s %s;' % (ctype, field['name']))
lines.append('} %s;' % pod_ctype(p['name']))
lines.append('')
return lines
def impl_name(namespace, op):
return camel(namespace['short'] + '_' + op['name'])
def c_symbol(namespace, op):
return 'rive_' + namespace['short'] + '_' + op['name']
def c_params(params):
out = []
for p in params:
kind = p['kind']
if kind in ('u32', 'handle'):
out.append('uint32_t ' + p['name'])
elif kind == 'i32':
out.append('int32_t ' + p['name'])
elif kind == 'f32':
out.append('float ' + p['name'])
elif kind == 'f64':
out.append('double ' + p['name'])
elif kind == 'buf':
out.append('const %s* %s' % (p['elem'], p['name']))
out.append('uint32_t ' + p['count'])
elif kind == 'mutbuf':
out.append('%s* %s' % (p['elem'], p['name']))
out.append('uint32_t ' + p['count'])
elif kind == 'addr':
out.append('const %s* %s' % (p['elem'], p['name']))
elif kind == 'str':
out.append('const char* ' + p['name'])
out.append('uint32_t ' + p['count'])
elif kind == 'pod':
out.append('const %s* %s' % (pod_ctype(p['pod']), p['name']))
out.append('uint32_t %sByteCount' % p['name'])
else:
raise ValueError(kind)
return out
def host_params(params):
out = []
for p in params:
kind = p['kind']
if kind in ('u32', 'handle'):
out.append('uint32_t ' + p['name'])
elif kind == 'i32':
out.append('int32_t ' + p['name'])
elif kind == 'f32':
out.append('float ' + p['name'])
elif kind == 'f64':
out.append('double ' + p['name'])
elif kind == 'buf':
out.append('const %s* %s' % (p['elem'], p['name']))
out.append('uint32_t ' + p['count'])
elif kind == 'mutbuf':
out.append('%s* %s' % (p['elem'], p['name']))
out.append('uint32_t ' + p['count'])
elif kind == 'addr':
# Raw guest address; the host validates and translates itself.
out.append('uint32_t ' + p['name'])
elif kind == 'str':
out.append('const char* ' + p['name'])
out.append('uint32_t ' + p['count'])
elif kind == 'pod':
out.append('const %s* %s' % (pod_ctype(p['pod']), p['name']))
out.append('uint32_t %sByteCount' % p['name'])
else:
raise ValueError(kind)
return out
def host_args(params):
out = []
for p in params:
kind = p['kind']
if kind in ('u32', 'handle', 'i32', 'f32', 'f64', 'addr'):
out.append(p['name'])
elif kind == 'str':
out.append('%sUtf8.data()' % p['name'])
out.append('%sUtf8.size()' % p['name'])
elif kind in ('buf', 'mutbuf'):
out.append(p['name'])
out.append(p['count'])
elif kind == 'pod':
out.append(p['name'])
out.append('%sByteCount' % p['name'])
else:
raise ValueError(kind)
return out
def host_string_prologue(params):
# A module that passes its strings as UTF-16 has them transcoded here,
# so the impl cores only ever see UTF-8.
return [' WasmStringArg %sUtf8(vm, %s, %s);' %
(p['name'], p['name'], p['count'])
for p in params if p['kind'] == 'str']
# A count named in bytes is WAMR's own `~` length; any other buffer count is
# in elements, so WAMR has checked only count bytes of it.
def count_is_bytes(p):
return p['count'].endswith('ByteCount') or p['count'].endswith('Bytes')
def host_bounds_prologue(op):
# Widens WAMR's check to the whole buffer; a failed check traps.
lines = []
for p in op['params']:
if p['kind'] not in ('buf', 'mutbuf') or count_is_bytes(p):
continue
size = elem_size(p['elem'])
if size == 1:
continue
lines += [' if (%s != 0 && !wasm_runtime_validate_native_addr('
'wasm_runtime_get_module_inst(env), (void*)%s, '
'(uint64_t)%s * %d))' % (p['count'], p['name'], p['count'],
size),
' {',
' return%s;' % ('' if op['ret'] is None else ' {}'),
' }']
return lines
def guarded(op, lines):
if not op.get('guard'):
return lines
return ['#ifdef ' + op['guard']] + lines + ['#endif']
def check_arg_budget(namespace, op):
# Beyond seven integer wire args (plus the implicit exec_env) arguments
# spill to the native stack, which WAMR's generic invocation drops.
# Wide ops must pack into descriptor structs instead.
ints = 0
for p in op['params']:
if p['kind'] in ('u32', 'handle', 'i32', 'addr'):
ints += 1
elif p['kind'] in ('buf', 'mutbuf', 'str', 'pod'):
ints += 2
if ints > 7:
raise SystemExit('%s.%s has %d integer wire args; max is 7' %
(namespace['module'], op['name'], ints))
def wamr_signature(op):
sig = '('
for p in op['params']:
kind = p['kind']
if kind in ('u32', 'handle', 'i32', 'addr'):
sig += 'i'
elif kind == 'f32':
sig += 'f'
elif kind == 'f64':
sig += 'F'
elif kind in ('buf', 'mutbuf', 'str', 'pod'):
sig += '*~'
else:
raise ValueError(kind)
sig += ')'
if op['ret'] == 'f32':
sig += 'f'
elif op['ret'] == 'f64':
sig += 'F'
elif op['ret'] is not None:
sig += 'i'
return sig
RET_CTYPES = {'f32': 'float', 'f64': 'double'}
def ret_ctype(op):
if op['ret'] is None:
return 'void'
return RET_CTYPES.get(op['ret'], 'uint32_t')
def check_all_ops():
for namespace in NAMESPACES:
for op in namespace['ops']:
check_arg_budget(namespace, op)
def emit_header():
lines = [
'#ifndef _RIVE_BINDINGS_V1_H_',
'#define _RIVE_BINDINGS_V1_H_',
'// %s' % GENERATED_NOTE,
'//',
'// The wasm import contract a script module compiles against: every',
'// native contact is one of these calls, objects cross as u32',
'// handles. Any language that can declare wasm imports can target',
'// this surface directly.',
'// clang-format off',
'',
'#include <stdint.h>',
'',
'#define RIVE_BINDINGS_ABI_VERSION 1',
'',
'#define RIVE_BINDING_IMPORT(module, name) \\',
' __attribute__((import_module(module), import_name(name)))',
'',
'#ifdef __cplusplus',
'extern "C" {',
'#endif',
'',
]
lines += pod_struct_lines()
for namespace in NAMESPACES:
lines.append('// %s' % namespace['module'])
for op in namespace['ops']:
lines.append('RIVE_BINDING_IMPORT("%s", "%s")' %
(namespace['module'], op['name']))
params = ', '.join(c_params(op['params'])) or 'void'
lines.append('%s %s(%s);' %
(ret_ctype(op), c_symbol(namespace, op), params))
lines.append('')
lines += [
'#ifdef __cplusplus',
'}',
'#endif',
'',
'#endif',
'',
]
return '\n'.join(lines)
def emit_host():
lines = [
'// %s' % GENERATED_NOTE,
'//',
'// Included inside wasm_scripting_vm.cpp\'s anonymous namespace before',
'// the native implementations; the declarations pin each hand-written',
'// impl core to the contract signature (a mismatch makes the table',
'// cast ambiguous). The WAMR wrappers adapt exec_env to the VM the',
'// cores share with the browser lane\'s generated C shims.',
'// clang-format off',
'',
]
lines += pod_struct_lines()
for namespace in NAMESPACES:
for op in namespace['ops']:
params = ['WasmScriptingVM* vm'] + host_params(op['params'])
lines += guarded(op, ['%s %sImpl(%s);' %
(ret_ctype(op), impl_name(namespace, op),
', '.join(params))])
lines.append('')
for namespace in NAMESPACES:
for op in namespace['ops']:
name = impl_name(namespace, op)
params = ['wasm_exec_env_t env'] + host_params(op['params'])
args = ['vm'] + host_args(op['params'])
call = '%sImpl(%s)' % (name, ', '.join(args))
body = ['%s %s(%s)' % (ret_ctype(op), name, ', '.join(params)),
'{',
' WasmScriptingVM* vm = %s(env);' %
('bootVmFromEnv' if op['boot'] else 'vmFromEnv')]
if op['boot']:
assert op['ret'] is None, op['name']
body += [' if (vm == nullptr)',
' {',
' wasm_runtime_set_exception('
'wasm_runtime_get_module_inst(env), "%s");' %
op['boot'],
' return;',
' }']
body += host_bounds_prologue(op)
body += host_string_prologue(op['params'])
body += [' %s%s;' %
('' if op['ret'] is None else 'return ', call),
'}']
lines += guarded(op, body)
lines.append('')
for namespace in NAMESPACES:
table = 'k%sNatives' % pascal(namespace['short'])
lines.append('NativeSymbol %s[] = {' % table)
for op in namespace['ops']:
attachment = ('(void*)&wasm_runtime_rive_leaf_native'
if op.get('leaf') else 'nullptr')
lines += guarded(op, [' {"%s", (void*)%s, "%s", %s},' %
(op['name'], impl_name(namespace, op),
wamr_signature(op), attachment)])
lines.append('};')
lines.append('')
lines.append('inline bool registerRiveBindingNatives()')
lines.append('{')
checks = []
for namespace in NAMESPACES:
table = 'k%sNatives' % pascal(namespace['short'])
checks.append(
'wasm_runtime_register_natives(\n'
' "%s",\n'
' %s,\n'
' sizeof(%s) / sizeof(NativeSymbol))' %
(namespace['module'], table, table))
lines.append(' return ' + ' &&\n '.join(checks) + ';')
lines.append('}')
lines.append('')
return '\n'.join(lines)
def emit_web_host():
lines = [
'// %s' % GENERATED_NOTE,
'//',
'// Browser-lane host entry points: one exported C symbol per op,',
'// taking the owning VM as a u32 handle plus the native-convention',
'// args (pointers are librive-heap pointers, exactly what',
'// web/rive_module_imports.mjs stages into). Included at the end of',
'// wasm_scripting_vm.cpp so the impl cores are visible; expands to',
'// nothing off emscripten.',
'// clang-format off',
'',
'#ifdef __EMSCRIPTEN__',
'',
'#include <emscripten/emscripten.h>',
'',
'extern "C" {',
'',
]
for namespace in NAMESPACES:
for op in namespace['ops']:
sym = 'rive_web_' + namespace['short'] + '_' + op['name']
params = ['uint32_t vmHandle'] + host_params(op['params'])
args = ['vm'] + host_args(op['params'])
call = '%sImpl(%s)' % (impl_name(namespace, op), ', '.join(args))
body = ['EMSCRIPTEN_KEEPALIVE',
'%s %s(%s)' % (ret_ctype(op), sym, ', '.join(params)),
'{',
' auto vm = (WasmScriptingVM*)(uintptr_t)vmHandle;']
body += host_string_prologue(op['params'])
body += [' %s%s;' %
('' if op['ret'] is None else 'return ', call),
'}']
lines += guarded(op, body)
lines += [
'',
'} // extern "C"',
'',
'#endif // __EMSCRIPTEN__',
'',
]
return '\n'.join(lines)
def emit_stubs():
lines = [
'// %s' % GENERATED_NOTE,
'//',
'// Test lane import namespaces: handle returning ops mint counters,',
'// void ops are no-ops, hook ops delegate to the harness.',
'export function createRiveStubs(hooks = {}) {',
' let nextHandle = 1;',
' const mint = () => nextHandle++;',
' const noop = () => {};',
' const zero = () => 0;',
' return {',
]
for namespace in NAMESPACES:
lines.append(' %s: {' % namespace['module'])
for op in namespace['ops']:
if op['stub'] == 'hook':
value = 'hooks.%s ?? noop' % camel(op['name'])
elif op['stub'] == 'zero' or op['ret'] in ('f32', 'f64'):
value = 'zero'
elif op['ret'] is not None:
value = 'mint'
else:
value = 'noop'
lines.append(' %s: %s,' % (op['name'], value))
lines.append(' },')
lines += [
' };',
'}',
'',
]
return '\n'.join(lines)
def as_params(params):
out = []
for p in params:
kind = p['kind']
if kind in ('u32', 'handle'):
out.append('%s: u32' % p['name'])
elif kind == 'i32':
out.append('%s: i32' % p['name'])
elif kind == 'f32':
out.append('%s: f32' % p['name'])
elif kind == 'f64':
out.append('%s: f64' % p['name'])
elif kind in ('buf', 'mutbuf', 'str'):
out.append('%s: usize' % p['name'])
out.append('%s: u32' % p['count'])
elif kind == 'addr':
out.append('%s: usize' % p['name'])
elif kind == 'pod':
out.append('%s: usize' % p['name'])
out.append('%sByteCount: u32' % p['name'])
else:
raise ValueError(kind)
return out
def as_ret(op):
if op['ret'] is None:
return 'void'
return op['ret'] if op['ret'] in ('f32', 'f64') else 'u32'
def emit_as():
lines = [
'// %s' % GENERATED_NOTE,
'//',
'// The wasm import contract for AnimaScript modules, under the',
'// same symbols as the C header: one ambient namespace whose',
'// members carry the import namespace and wire name (which may be',
'// a reserved word) via @external. The unmanaged POD classes',
'// mirror the descriptor structs byte for byte. Ergonomic wrappers',
'// live beside this file, never in it.',
'',
]
for p in PODS:
lines.append('@unmanaged')
lines.append('export class %s {' % pod_ctype(p['name']))
for field in p['fields']:
astype = 'f32' if field['kind'] == 'f32' else 'u32'
lines.append(' %s: %s;' % (field['name'], astype))
lines.append('}')
lines.append('export const %s_size: u32 = %d;' %
(pod_ctype(p['name']), len(p['fields']) * 4))
lines.append('')
lines.append('export declare namespace abi {')
for namespace in NAMESPACES:
lines.append(' // %s' % namespace['module'])
for op in namespace['ops']:
params = ', '.join(as_params(op['params']))
lines.append(' @external("%s", "%s")' %
(namespace['module'], op['name']))
lines.append(' function %s(%s): %s;' %
(c_symbol(namespace, op), params, as_ret(op)))
lines.append('}')
lines.append('')
return '\n'.join(lines)
# Projection 5: one typed class per namespace over the handle helpers. A
# namespace whose ops lead with a handle of its own kind (path, paint, ...)
# wraps that handle; the rest are static. Strings encode to UTF-8 on the
# way in; buffers stay raw address plus count for the ergonomic layer to
# type.
# Handle tag -> HandleKind in handles.as, for the owning factories below.
AS_HANDLE_KINDS = {
'path': 'path',
'paint': 'paint',
'shader': 'renderShader',
'buffer': 'renderBuffer',
'mesh_instances': 'meshInstances',
'image': 'image',
'artboard': 'artboard',
'measure': 'measure',
'font': 'font',
'text': 'text',
}
# Static ops that mint a handle of the namespace's own kind; their wrappers
# own the handle through the frame collector's mirror.
AS_OWNING_FACTORIES = {
('path', 'new'),
('paint', 'new'),
('buffer', 'new'),
('mesh_instances', 'new'),
('image', 'from_asset'),
('shader', 'linear'),
('shader', 'radial'),
('measure', 'path_new'),
('measure', 'contours_new'),
('font', 'from_asset'),
('font', 'decode'),
('font', 'with_options'),
('text', 'new'),
}
def as_class_name(namespace):
return pascal(namespace['short']) + 'Native'
def as_self_tag(namespace):
for op in namespace['ops']:
params = op['params']
if params and params[0]['kind'] == 'handle' and \
params[0]['tag'] == namespace['short']:
return namespace['short']
return None
def as_wrapper_params(params):
"""(declaration list, call argument list, needs unsafe)."""
decl = []
args = []
unsafe = False
for p in params:
kind = p['kind']
name = camel(p['name'])
if kind in ('u32', 'handle'):
decl.append('%s: u32' % name)
args.append(name)
elif kind == 'i32':
decl.append('%s: i32' % name)
args.append(name)
elif kind == 'f32':
decl.append('%s: f32' % name)
args.append(name)
elif kind == 'f64':
decl.append('%s: f64' % name)
args.append(name)
elif kind == 'str':
decl.append('%s: string' % name)
args.append('changetype<usize>(%s)' % name)
args.append('<u32>(%s.length << 1)' % name)
unsafe = True
elif kind in ('buf', 'mutbuf'):
count = camel(p['count'])
decl.append('%s: usize' % name)
decl.append('%s: u32' % count)
args.append(name)
args.append(count)
elif kind == 'addr':
decl.append('%s: usize' % name)
args.append(name)
elif kind == 'pod':
decl.append('%s: usize' % name)
decl.append('%sByteCount: u32' % name)
args.append(name)
args.append('%sByteCount' % name)
else:
raise ValueError(kind)
return decl, args, unsafe
def as_string_prologue(params):
# Strings cross as they are, UTF-16, announced by __riveUtf16Strings.
return []
def emit_as_natives():
lines = [
'// %s' % GENERATED_NOTE,
'//',
'// Typed classes over the import contract, one per namespace:',
'// instance methods where the namespace owns the leading handle,',
'// statics elsewhere. Strings cross as they sit, UTF-16; buffers stay raw',
'// address plus count so the ergonomic layer picks the element',
'// type. Owning factories mirror their handle for the frame',
'// collector; adopt() wraps a host owned handle without ownership.',
'',
'import { abi } from "./bindings";',
'import { HandleKind, mirrorAddOwned, mirrorRemove } from "./handles";',
'',
]
for namespace in NAMESPACES:
short = namespace['short']
cls = as_class_name(namespace)
self_tag = as_self_tag(namespace)
lines.append('// %s' % namespace['module'])
lines.append('export class %s {' % cls)
if self_tag is not None:
lines.append(' handle: u32;')
lines.append('')
lines.append(' constructor(handle: u32) {')
lines.append(' this.handle = handle;')
lines.append(' }')
lines.append('')
lines.append(' static adopt(handle: u32): %s {' % cls)
lines.append(' return new %s(handle);' % cls)
lines.append(' }')
lines.append('')
for op in namespace['ops']:
params = op['params']
is_method = self_tag is not None and params and \
params[0]['kind'] == 'handle' and params[0]['tag'] == self_tag
rest = params[1:] if is_method else params
decl, args, unsafe = as_wrapper_params(rest)
if is_method:
args = ['this.handle'] + args
call = 'abi.%s(%s)' % (c_symbol(namespace, op), ', '.join(args))
name = camel(op['name'])
owning = (short, op['name']) in AS_OWNING_FACTORIES
if op['name'] == 'release' and is_method:
lines.append(' release(): void {')
lines.append(' if (this.handle == 0) {')
lines.append(' return;')
lines.append(' }')
lines.append(' mirrorRemove(this.handle);')
lines.append(' %s;' % call)
lines.append(' this.handle = 0;')
lines.append(' }')
lines.append('')
continue
ret = as_ret(op)
if owning:
ret = cls
head = ' %s%s%s(%s): %s {' % (
'unsafe ' if unsafe or owning else '',
'' if is_method else 'static ',
name,
', '.join(decl),
ret)
lines.append(head)
lines += as_string_prologue(rest)
if owning:
lines.append(' let handle = %s;' % call)
lines.append(' let wrapper = new %s(handle);' % cls)
lines.append(' mirrorAddOwned(handle, HandleKind.%s, '
'wrapper);' % AS_HANDLE_KINDS[short])
lines.append(' return wrapper;')
elif op['ret'] is None:
lines.append(' %s;' % call)
else:
lines.append(' return %s;' % call)
lines.append(' }')
lines.append('')
if lines[-1] == '':
lines.pop()
lines.append('}')
lines.append('')
return '\n'.join(lines)
def web_op(namespace, op):
"""One import-object entry: scalars forward, memory-crossing params
stage through the host heap."""
sym = c_symbol(namespace, op)
staged = [p for p in op['params']
if p['kind'] in ('str', 'buf', 'mutbuf', 'pod')]
addr = [p for p in op['params'] if p['kind'] == 'addr']
if addr:
return (' %s: unsupported(%r),' % (op['name'], sym), False)
if not staged:
return (' %s: calls.%s,' % (op['name'], sym), False)
args = []
pre = []
post = []
js_params = []
for p in op['params']:
kind = p['kind']
name = camel(p['name'])
if kind in ('u32', 'i32', 'f32', 'f64', 'handle'):
js_params.append(name)
args.append(name)
elif kind in ('str', 'buf', 'mutbuf'):
count = camel(p['count'])
js_params.append(name)
js_params.append(count)
# Staging copies bytes.
size = (1 if kind == 'str' or count_is_bytes(p) else
elem_size(p['elem']))
byte_count = count if size == 1 else '%s * %d' % (count, size)
pre.append('const %s_p = stageIn(%s, %s);' %
(name, name, byte_count))
args.append('%s_p' % name)
args.append(count)
if kind == 'mutbuf':
post.append('copyOut(%s_p, %s, %s);' %
(name, name, byte_count))
elif kind == 'pod':
js_params.append(name)
js_params.append('%sByteCount' % name)
pre.append('const %s_p = stageIn(%s, %sByteCount);' %
(name, name, name))
args.append('%s_p' % name)
args.append('%sByteCount' % name)
else:
raise ValueError(kind)
body = [' %s: (%s) => {' % (op['name'], ', '.join(js_params)),
' stackReset();']
body += [' %s' % line for line in pre]
call = 'calls.%s(%s)' % (sym, ', '.join(args))
if op['ret'] is not None:
body.append(' const ret = %s;' % call)
else:
body.append(' %s;' % call)
body += [' %s' % line for line in post]
if op['ret'] is not None:
body.append(' return ret;')
body.append(' },')
return ('\n'.join(body), True)
def emit_web_imports():
lines = [
'// %s' % GENERATED_NOTE,
'//',
'// The runtime import object for script modules executing on the',
'// browser engine. Scalars forward straight into the host call',
'// table; str/buf/pod params stage through the host heap because',
'// the host cannot dereference module memory. `addr` ops throw',
'// until their per-op audit lands.',
'//',
'// host contract:',
'// calls per-op host entry points (native-pointer convention)',
'// heapU8() Uint8Array over the host heap (refresh per call)',
'// stackAlloc(size) -> host heap ptr valid until stackReset()',
'// stackReset()',
'// moduleMemory: late-bound accessor for the script module memory,',
'// set after instantiation.',
'export function createRiveModuleImports(host, moduleMemory) {',
' const calls = host.calls;',
' const stackReset = host.stackReset;',
' const stageIn = (ptr, len) => {',
' const dst = host.stackAlloc(len);',
' host.heapU8().set(',
' new Uint8Array(moduleMemory().buffer, ptr, len),',
' dst',
' );',
' return dst;',
' };',
' const copyOut = (src, ptr, len) => {',
' new Uint8Array(moduleMemory().buffer, ptr, len).set(',
' host.heapU8().subarray(src, src + len)',
' );',
' };',
' const unsupported = (name) => () => {',
" throw new Error('web lane does not carry ' + name + ' yet');",
' };',
' return {',
]
for namespace in NAMESPACES:
lines.append(' %s: {' % namespace['module'])
for op in namespace['ops']:
text, _ = web_op(namespace, op)
lines.append(text)
lines.append(' },')
lines += [
' };',
'}',
'',
]
return '\n'.join(lines)
def main():
check_all_ops()
outputs = {
HEADER_PATH: emit_header(),
HOST_PATH: emit_host(),
WEB_HOST_PATH: emit_web_host(),
STUBS_PATH: emit_stubs(),
WEB_PATH: emit_web_imports(),
AS_PATH: emit_as(),
AS_NATIVES_PATH: emit_as_natives(),
}
check = '--check' in sys.argv
stale = []
for path, content in outputs.items():
current = path.read_text() if path.exists() else None
if current == content:
continue
if check:
stale.append(path)
else:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content)
print('wrote %s' % path.relative_to(PACKAGES))
if stale:
for path in stale:
print('stale: %s' % path.relative_to(PACKAGES))
print('run src/wasm/idl/generate.py to regenerate')
return 1
if check:
print('binding IDL projections up to date')
return 0
if __name__ == '__main__':
sys.exit(main())