js: support WASM interoperability using `wasm_import`/`wasm_export` fn tags (#12212)
parent
2070839722
commit
a3de67de28
|
@ -405,6 +405,17 @@ fn (mut g JsGen) is_used_by_main(node ast.FnDecl) bool {
|
|||
fn (mut g JsGen) gen_fn_decl(it ast.FnDecl) {
|
||||
res := g.fn_gen_type(it)
|
||||
if it.language == .js {
|
||||
for attr in it.attrs {
|
||||
match attr.name {
|
||||
'wasm_import' {
|
||||
mut x := g.wasm_export[attr.arg] or { []string{} }
|
||||
x << it.name
|
||||
g.wasm_import[attr.arg] = x
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
if g.inside_builtin {
|
||||
|
@ -535,12 +546,39 @@ fn (mut g JsGen) gen_method_decl(it ast.FnDecl, typ FnGenType) {
|
|||
// g.write(')')
|
||||
}
|
||||
g.writeln('')
|
||||
|
||||
for attr in it.attrs {
|
||||
match attr.name {
|
||||
'export' {
|
||||
g.writeln('globalThis.$attr.arg = ${g.js_name(it.name)};')
|
||||
}
|
||||
'wasm_export' {
|
||||
mut x := g.wasm_export[attr.arg] or { []string{} }
|
||||
g.write('function \$wasm${g.js_name(it.name)}(')
|
||||
g.fn_args(args, it.is_variadic)
|
||||
g.writeln(') {')
|
||||
g.write('\treturn $name (')
|
||||
for i, arg in args {
|
||||
is_varg := i == args.len - 1 && it.is_variadic
|
||||
arg_name := g.js_name(arg.name)
|
||||
if is_varg {
|
||||
g.write('...$arg_name')
|
||||
} else {
|
||||
g.gen_cast_tmp(arg_name, arg.typ)
|
||||
}
|
||||
if i != args.len - 1 {
|
||||
g.write(',')
|
||||
}
|
||||
}
|
||||
g.writeln(').valueOf();')
|
||||
g.writeln('}')
|
||||
x << it.name
|
||||
g.wasm_export[attr.arg] = x
|
||||
}
|
||||
'wasm_import' {
|
||||
mut x := g.wasm_export[attr.arg] or { []string{} }
|
||||
x << name
|
||||
g.wasm_import[attr.arg] = x
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,6 +84,8 @@ mut:
|
|||
defer_ifdef string
|
||||
out strings.Builder = strings.new_builder(128)
|
||||
array_sort_fn map[string]bool
|
||||
wasm_export map[string][]string
|
||||
wasm_import map[string][]string
|
||||
}
|
||||
|
||||
fn (mut g JsGen) write_tests_definitions() {
|
||||
|
@ -211,14 +213,38 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string {
|
|||
}
|
||||
}
|
||||
}
|
||||
g.write('js_main();')
|
||||
if !g.pref.is_shared {
|
||||
g.write('loadRoutine().then(_ => js_main());')
|
||||
}
|
||||
g.escape_namespace()
|
||||
// resolve imports
|
||||
deps_resolved := graph.resolve()
|
||||
nodes := deps_resolved.nodes
|
||||
// deps_resolved := graph.resolve()
|
||||
// nodes := deps_resolved.nodes
|
||||
|
||||
mut out := g.definitions.str() + g.hashes()
|
||||
|
||||
out += '\nlet wasmExportObject;\n'
|
||||
out += 'const loadRoutine = async () => {\n'
|
||||
for mod, functions in g.wasm_import {
|
||||
if g.pref.backend == .js_browser {
|
||||
out += '\nawait fetch("$mod").then(respone => respone.arrayBuffer()).then(bytes => '
|
||||
out += 'WebAssembly.instantiate(bytes,'
|
||||
exports := g.wasm_export[mod]
|
||||
out += '{ imports: { \n'
|
||||
for i, exp in exports {
|
||||
out += g.js_name(exp) + ':' + '\$wasm' + g.js_name(exp)
|
||||
if i != exports.len - 1 {
|
||||
out += ',\n'
|
||||
}
|
||||
}
|
||||
out += '}})).then(obj => wasmExportObject = obj.instance.exports);\n'
|
||||
for fun in functions {
|
||||
out += 'globalThis.${g.js_name(fun)} = wasmExportObject.${g.js_name(fun)};\n'
|
||||
}
|
||||
} else {
|
||||
verror('WebAssembly export is supported only for browser backend at the moment')
|
||||
}
|
||||
}
|
||||
out += '}\n'
|
||||
// equality check for js objects
|
||||
// TODO: Fix msvc bug that's preventing $embed_file('fast_deep_equal.js')
|
||||
// unsafe {
|
||||
|
@ -226,12 +252,12 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string {
|
|||
// out += eq_fn.data().vstring()
|
||||
//}
|
||||
out += fast_deep_eq_fn
|
||||
|
||||
/*
|
||||
if pref.is_shared {
|
||||
// Export, through CommonJS, the module of the entry file if `-shared` was passed
|
||||
export := nodes[nodes.len - 1].name
|
||||
out += 'if (typeof module === "object" && module.exports) module.exports = $export;\n'
|
||||
}
|
||||
}*/
|
||||
out += '\n'
|
||||
|
||||
out += g.out.str()
|
||||
|
@ -2982,6 +3008,40 @@ fn (mut g JsGen) gen_typeof_expr(it ast.TypeOf) {
|
|||
}
|
||||
}
|
||||
|
||||
fn (mut g JsGen) gen_cast_tmp(tmp string, typ_ ast.Type) {
|
||||
// Skip cast if type is the same as the parrent caster
|
||||
tsym := g.table.get_final_type_symbol(typ_)
|
||||
if tsym.kind == .i64 || tsym.kind == .u64 {
|
||||
g.write('new ')
|
||||
|
||||
g.write('$tsym.kind.str()')
|
||||
g.write('(BigInt(')
|
||||
g.write(tmp)
|
||||
g.write('n))')
|
||||
return
|
||||
}
|
||||
g.cast_stack << typ_
|
||||
typ := g.typ(typ_)
|
||||
|
||||
if typ_.is_ptr() {
|
||||
g.write('new \$ref(')
|
||||
}
|
||||
|
||||
g.write('new ')
|
||||
g.write('${typ}(')
|
||||
g.write(tmp)
|
||||
if typ == 'string' {
|
||||
g.write('.toString()')
|
||||
}
|
||||
|
||||
g.write(')')
|
||||
if typ_.is_ptr() {
|
||||
g.write(')')
|
||||
}
|
||||
|
||||
g.cast_stack.delete_last()
|
||||
}
|
||||
|
||||
fn (mut g JsGen) gen_type_cast_expr(it ast.CastExpr) {
|
||||
is_literal := ((it.expr is ast.IntegerLiteral && it.typ in ast.integer_type_idxs)
|
||||
|| (it.expr is ast.FloatLiteral && it.typ in ast.float_type_idxs))
|
||||
|
|
|
@ -195,6 +195,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
|
|||
'direct_array_access' { is_direct_arr = true }
|
||||
'keep_args_alive' { is_keep_alive = true }
|
||||
'export' { is_exported = true }
|
||||
'wasm_export' { is_exported = true }
|
||||
'unsafe' { is_unsafe = true }
|
||||
'trusted' { is_trusted = true }
|
||||
'c2v_variadic' { is_c2v_variadic = true }
|
||||
|
|
|
@ -1502,7 +1502,7 @@ fn (mut p Parser) attributes() {
|
|||
for p.tok.kind != .rsbr {
|
||||
start_pos := p.tok.position()
|
||||
attr := p.parse_attr()
|
||||
if p.attrs.contains(attr.name) {
|
||||
if p.attrs.contains(attr.name) && attr.name != 'wasm_export' {
|
||||
p.error_with_pos('duplicate attribute `$attr.name`', start_pos.extend(p.prev_tok.position()))
|
||||
return
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue