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