From a3de67de286ba8e87502c240c8f75b3f22976f15 Mon Sep 17 00:00:00 2001 From: playX Date: Mon, 18 Oct 2021 10:56:21 +0300 Subject: [PATCH] js: support WASM interoperability using `wasm_import`/`wasm_export` fn tags (#12212) --- vlib/v/gen/js/fn.v | 40 ++++++++++++++++++++++- vlib/v/gen/js/js.v | 72 ++++++++++++++++++++++++++++++++++++++---- vlib/v/parser/fn.v | 1 + vlib/v/parser/parser.v | 2 +- 4 files changed, 107 insertions(+), 8 deletions(-) diff --git a/vlib/v/gen/js/fn.v b/vlib/v/gen/js/fn.v index f6edb996d8..6b3bb776d1 100644 --- a/vlib/v/gen/js/fn.v +++ b/vlib/v/gen/js/fn.v @@ -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 {} } } diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index fbc39bde16..3c2a757f85 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -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)) diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index 79dbee2bc8..941ee3a23b 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -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 } diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 4c8d34466a..1552c66d6a 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -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 }