diff --git a/core/src/main/java/org/teavm/backend/wasm/disasm/Disassembler.java b/core/src/main/java/org/teavm/backend/wasm/disasm/Disassembler.java index ea7538318..e5f18a945 100644 --- a/core/src/main/java/org/teavm/backend/wasm/disasm/Disassembler.java +++ b/core/src/main/java/org/teavm/backend/wasm/disasm/Disassembler.java @@ -51,6 +51,7 @@ public final class Disassembler { private WasmHollowFunctionType[] functionTypes; private int[] functionTypeRefs; private int importFunctionCount; + private int importGlobalCount; private Map debugSectionParsers = new HashMap<>(); private DebugLinesParser debugLines; private LineInfo lineInfo; @@ -143,6 +144,7 @@ public final class Disassembler { var parser = new ImportSectionParser(importListener); parser.parse(AddressListener.EMPTY, bytes); importFunctionCount = importListener.functionCount(); + importGlobalCount = importListener.globalCount(); }; } else if (code == 3) { return bytes -> { @@ -165,6 +167,7 @@ public final class Disassembler { var globalWriter = new DisassemblyGlobalSectionListener(writer, nameProvider); writer.setAddressOffset(pos); var sectionParser = new GlobalSectionParser(globalWriter); + sectionParser.setGlobalIndexOffset(importGlobalCount); sectionParser.parse(writer.addressListener, bytes); writer.flush(); }; diff --git a/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyImportSectionListener.java b/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyImportSectionListener.java index 068eab298..d6e08b5ca 100644 --- a/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyImportSectionListener.java +++ b/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyImportSectionListener.java @@ -17,12 +17,14 @@ package org.teavm.backend.wasm.disasm; import org.teavm.backend.wasm.parser.ImportSectionListener; import org.teavm.backend.wasm.parser.WasmHollowFunctionType; +import org.teavm.backend.wasm.parser.WasmHollowType; public class DisassemblyImportSectionListener extends BaseDisassemblyListener implements ImportSectionListener { private WasmHollowFunctionType[] functionTypes; private String currentModule; private String currentName; private int functionIndex; + private int globalIndex; public DisassemblyImportSectionListener(DisassemblyWriter writer, NameProvider nameProvider, WasmHollowFunctionType[] functionTypes) { @@ -34,6 +36,10 @@ public class DisassemblyImportSectionListener extends BaseDisassemblyListener im return functionIndex; } + public int globalCount() { + return globalIndex; + } + @Override public void startEntry(String module, String name) { currentModule = module; @@ -81,4 +87,21 @@ public class DisassemblyImportSectionListener extends BaseDisassemblyListener im functionIndex++; } + + @Override + public void global(WasmHollowType type) { + writer.address().write("(import \"").write(currentModule).write("\" \"") + .write(currentName).write("\" "); + writer.write("(global "); + writer.startLinkTarget("g" + globalIndex).write("(; " + globalIndex + " ;)"); + var name = nameProvider.global(globalIndex); + if (name != null) { + writer.write(" $").write(name); + } + writer.endLinkTarget(); + writer.write(" (type "); + writeType(type); + writer.write("))").eol(); + ++globalIndex; + } } diff --git a/core/src/main/java/org/teavm/backend/wasm/model/WasmGlobal.java b/core/src/main/java/org/teavm/backend/wasm/model/WasmGlobal.java index 9548f15d2..ad47ee94c 100644 --- a/core/src/main/java/org/teavm/backend/wasm/model/WasmGlobal.java +++ b/core/src/main/java/org/teavm/backend/wasm/model/WasmGlobal.java @@ -24,11 +24,13 @@ public class WasmGlobal extends WasmEntity { private WasmExpression initialValue; private boolean immutable; private String exportName; + private String importName; + private String importModule; public WasmGlobal(String name, WasmType type, WasmExpression initialValue) { this.name = name; this.type = Objects.requireNonNull(type); - this.initialValue = Objects.requireNonNull(initialValue); + this.initialValue = initialValue; } public String getName() { @@ -48,7 +50,7 @@ public class WasmGlobal extends WasmEntity { } public void setInitialValue(WasmExpression initialValue) { - this.initialValue = Objects.requireNonNull(initialValue); + this.initialValue = initialValue; } public boolean isImmutable() { @@ -66,4 +68,28 @@ public class WasmGlobal extends WasmEntity { public void setExportName(String exportName) { this.exportName = exportName; } + + public String getImportName() { + return importName; + } + + public void setImportName(String importName) { + this.importName = importName; + if (collection != null) { + collection.invalidateIndexes(); + } + } + + public String getImportModule() { + return importModule; + } + + public void setImportModule(String importModule) { + this.importModule = importModule; + } + + @Override + boolean isImported() { + return importName != null; + } } diff --git a/core/src/main/java/org/teavm/backend/wasm/parser/GlobalSectionParser.java b/core/src/main/java/org/teavm/backend/wasm/parser/GlobalSectionParser.java index 696c78424..59cf01647 100644 --- a/core/src/main/java/org/teavm/backend/wasm/parser/GlobalSectionParser.java +++ b/core/src/main/java/org/teavm/backend/wasm/parser/GlobalSectionParser.java @@ -18,12 +18,17 @@ package org.teavm.backend.wasm.parser; public class GlobalSectionParser extends BaseSectionParser { private final GlobalSectionListener listener; private CodeParser codeParser; + private int globalIndexOffset; public GlobalSectionParser(GlobalSectionListener listener) { this.listener = listener; codeParser = new CodeParser(); } + public void setGlobalIndexOffset(int globalIndexOffset) { + this.globalIndexOffset = globalIndexOffset; + } + @Override protected void parseContent() { var count = readLEB(); @@ -31,7 +36,7 @@ public class GlobalSectionParser extends BaseSectionParser { reportAddress(); var type = reader.readType(); var mutable = reader.data[reader.ptr++] != 0; - var codeListener = listener.startGlobal(i, type, mutable); + var codeListener = listener.startGlobal(i + globalIndexOffset, type, mutable); if (codeListener == null) { codeListener = CodeListener.EMPTY; } diff --git a/core/src/main/java/org/teavm/backend/wasm/parser/ImportSectionListener.java b/core/src/main/java/org/teavm/backend/wasm/parser/ImportSectionListener.java index aaddcffbe..cc997d8f0 100644 --- a/core/src/main/java/org/teavm/backend/wasm/parser/ImportSectionListener.java +++ b/core/src/main/java/org/teavm/backend/wasm/parser/ImportSectionListener.java @@ -22,6 +22,9 @@ public interface ImportSectionListener { default void function(int typeIndex) { } + default void global(WasmHollowType type) { + } + default void endEntry() { } } diff --git a/core/src/main/java/org/teavm/backend/wasm/parser/ImportSectionParser.java b/core/src/main/java/org/teavm/backend/wasm/parser/ImportSectionParser.java index 256da35a7..4b2e2fbf4 100644 --- a/core/src/main/java/org/teavm/backend/wasm/parser/ImportSectionParser.java +++ b/core/src/main/java/org/teavm/backend/wasm/parser/ImportSectionParser.java @@ -37,11 +37,19 @@ public class ImportSectionParser extends BaseSectionParser { listener.startEntry(module, name); reportAddress(); var type = reader.data[reader.ptr++]; - if (type == 0) { - var typeIndex = readLEB(); - listener.function(typeIndex); - } else { - throw new ParseException("Unsupported import type", reader.ptr); + switch (type) { + case 0: { + var typeIndex = readLEB(); + listener.function(typeIndex); + break; + } + case 3: { + var valueType = reader.readType(); + listener.global(valueType); + break; + } + default: + throw new ParseException("Unsupported import type", reader.ptr); } listener.endEntry(); } diff --git a/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java b/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java index a91e99ee8..d5e11c41d 100644 --- a/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java +++ b/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java @@ -27,6 +27,7 @@ import org.teavm.backend.wasm.generate.DwarfClassGenerator; import org.teavm.backend.wasm.generate.DwarfGenerator; import org.teavm.backend.wasm.model.WasmCustomSection; import org.teavm.backend.wasm.model.WasmFunction; +import org.teavm.backend.wasm.model.WasmGlobal; import org.teavm.backend.wasm.model.WasmMemorySegment; import org.teavm.backend.wasm.model.WasmModule; import org.teavm.backend.wasm.model.WasmStructure; @@ -138,20 +139,29 @@ public class WasmBinaryRenderer { } private void renderImports(WasmModule module) { - List functions = new ArrayList<>(); + var functions = new ArrayList(); for (var function : module.functions) { if (function.getImportName() == null) { continue; } functions.add(function); } - if (functions.isEmpty()) { + + var globals = new ArrayList(); + for (var global : module.globals) { + if (global.getImportName() == null) { + continue; + } + globals.add(global); + } + + if (functions.isEmpty() && globals.isEmpty()) { return; } WasmBinaryWriter section = new WasmBinaryWriter(); - section.writeLEB(functions.size()); + section.writeLEB(functions.size() + globals.size()); for (WasmFunction function : functions) { int signatureIndex = module.types.indexOf(function.getType()); String moduleName = function.getImportModule(); @@ -159,12 +169,22 @@ public class WasmBinaryRenderer { moduleName = ""; } section.writeAsciiString(moduleName); - section.writeAsciiString(function.getImportName()); section.writeByte(EXTERNAL_KIND_FUNCTION); section.writeLEB(signatureIndex); } + for (var global : globals) { + var moduleName = global.getImportModule(); + if (moduleName == null) { + moduleName = ""; + } + section.writeAsciiString(moduleName); + section.writeAsciiString(global.getImportName()); + section.writeByte(EXTERNAL_KIND_GLOBAL); + section.writeType(global.getType(), module); + section.writeByte(global.isImmutable() ? 0 : 1); + } writeSection(SECTION_IMPORT, "import", section.getData()); } @@ -211,16 +231,19 @@ public class WasmBinaryRenderer { } private void renderGlobals(WasmModule module) { - if (module.globals.isEmpty()) { + var globals = module.globals.stream() + .filter(global -> global.getImportName() == null) + .collect(Collectors.toList()); + if (globals.isEmpty()) { return; } var section = new WasmBinaryWriter(); var visitor = new WasmBinaryRenderingVisitor(section, module, null, null, 0); - section.writeLEB(module.globals.size()); - for (var global : module.globals) { + section.writeLEB(globals.size()); + for (var global : globals) { section.writeType(global.getType(), module); - section.writeByte(global.isImmutable() ? 0 : 1); // mutable + section.writeByte(global.isImmutable() ? 0 : 1); global.getInitialValue().acceptVisitor(visitor); section.writeByte(0x0b); } diff --git a/core/src/main/js/wasm-gc-runtime/module-wrapper.js b/core/src/main/js/wasm-gc-runtime/module-wrapper.js index 4abbfc5c7..cb0ddaa69 100644 --- a/core/src/main/js/wasm-gc-runtime/module-wrapper.js +++ b/core/src/main/js/wasm-gc-runtime/module-wrapper.js @@ -15,4 +15,4 @@ */ include(); -export { load, defaults }; \ No newline at end of file +export { load, defaults, wrapImport }; \ No newline at end of file diff --git a/core/src/main/js/wasm-gc-runtime/runtime.js b/core/src/main/js/wasm-gc-runtime/runtime.js index 689df177e..5aee92941 100644 --- a/core/src/main/js/wasm-gc-runtime/runtime.js +++ b/core/src/main/js/wasm-gc-runtime/runtime.js @@ -52,12 +52,15 @@ function defaults(imports) { let javaExceptionSymbol = Symbol("javaException"); class JavaError extends Error { - constructor(javaException) { + #context + + constructor(context, javaException) { super(); + this.#context = context; this[javaExceptionSymbol] = javaException; } get message() { - let exceptionMessage = exports["teavm.exceptionMessage"]; + let exceptionMessage = this.#context.exports["teavm.exceptionMessage"]; if (typeof exceptionMessage === "function") { let message = exceptionMessage(this[javaExceptionSymbol]); if (message != null) { @@ -247,7 +250,7 @@ function jsoImports(imports, context) { return wrapper; } } - let wrapper = new JavaError(javaException); + let wrapper = new JavaError(context, javaException); javaExceptionWrappers.set(javaException, new WeakRef(wrapper)); return wrapper; } @@ -312,7 +315,6 @@ function jsoImports(imports, context) { unwrapBoolean: value => value ? 1 : 0, wrapBoolean: value => !!value, getProperty: getProperty, - getPropertyPure: getProperty, setProperty: setProperty, setPropertyPure: setProperty, global(name) { @@ -565,6 +567,7 @@ function jsoImports(imports, context) { imports.teavmJso["createFunction" + i] = new Function("wrapCallFromJavaToJs", ...argumentList, "body", `return new Function('wrapCallFromJavaToJs', ${argsAndBody}).bind(this, wrapCallFromJavaToJs);` ).bind(null, wrapCallFromJavaToJs); + imports.teavmJso["bindFunction" + i] = (f, ...args) => f.bind(null, ...args); imports.teavmJso["callFunction" + i] = new Function("rethrowJsAsJava", "fn", ...argumentList, `try {\n` + ` return fn(${args});\n` + @@ -596,24 +599,70 @@ function jsoImports(imports, context) { } } +function wrapImport(importObj) { + return new Proxy(importObj, { + get(target, prop) { + let result = target[prop]; + return new WebAssembly.Global({ value: "externref", mutable: false }, result); + } + }); +} + +async function wrapImports(wasmModule, imports) { + let promises = []; + let propertiesToAdd = {}; + for (let { module, name, kind } of WebAssembly.Module.imports(wasmModule)) { + if (kind !== "global" || module in imports) { + continue; + } + let names = propertiesToAdd[module]; + if (names === void 0) { + let namesByModule = []; + names = namesByModule; + propertiesToAdd[name] = names; + promises.push((async () => { + let moduleInstance = await import(module); + let importsByModule = {}; + for (let name of namesByModule) { + let importedName = name === "__self__" ? moduleInstance : moduleInstance[name]; + importsByModule[name] = new WebAssembly.Global( + { value: "externref", mutable: false }, + importedName + ); + } + imports[module] = importsByModule; + })()); + } + names.push(name); + } + if (promises.length === 0) { + return; + } + await Promise.all(promises); +} + async function load(path, options) { if (!options) { options = {}; } + let deobfuscatorOptions = options.stackDeobfuscator || {}; + let debugInfoLocation = deobfuscatorOptions.infoLocation || "auto"; + let [deobfuscatorFactory, module, debugInfo] = await Promise.all([ + deobfuscatorOptions.enabled ? getDeobfuscator(path, deobfuscatorOptions) : Promise.resolve(null), + WebAssembly.compileStreaming(fetch(path)), + fetchExternalDebugInfo(path, debugInfoLocation, deobfuscatorOptions) + ]); + const importObj = {}; const defaultsResult = defaults(importObj); if (typeof options.installImports !== "undefined") { options.installImports(importObj); } - - let deobfuscatorOptions = options.stackDeobfuscator || {}; - let debugInfoLocation = deobfuscatorOptions.infoLocation || "auto"; - let [deobfuscatorFactory, { module, instance }, debugInfo] = await Promise.all([ - deobfuscatorOptions.enabled ? getDeobfuscator(path, deobfuscatorOptions) : Promise.resolve(null), - WebAssembly.instantiateStreaming(fetch(path), importObj), - fetchExternalDebugInfo(path, debugInfoLocation, deobfuscatorOptions) - ]); + if (!options.noAutoImports) { + await wrapImports(module, importObj); + } + let instance = new WebAssembly.Instance(module, importObj); defaultsResult.supplyExports(instance.exports); if (deobfuscatorFactory) { diff --git a/core/src/main/js/wasm-gc-runtime/simple-wrapper.js b/core/src/main/js/wasm-gc-runtime/simple-wrapper.js index c3845bf89..09f6c9f8f 100644 --- a/core/src/main/js/wasm-gc-runtime/simple-wrapper.js +++ b/core/src/main/js/wasm-gc-runtime/simple-wrapper.js @@ -17,6 +17,6 @@ var TeaVM = TeaVM || {}; TeaVM.wasmGC = TeaVM.wasmGC || (() => { include(); - return { load, defaults }; + return { load, defaults, wrapImport }; })(); diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JS.java b/jso/impl/src/main/java/org/teavm/jso/impl/JS.java index d4e561381..d39782d27 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JS.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JS.java @@ -731,13 +731,11 @@ public final class JS { @InjectedBy(JSNativeInjector.class) @JSBody(params = { "instance", "index" }, script = "return instance[index];") - @Import(name = "getProperty", module = "teavmJso") public static native JSObject get(JSObject instance, JSObject index); @InjectedBy(JSNativeInjector.class) @JSBody(params = { "instance", "index" }, script = "return instance[index];") @NoSideEffects - @Import(name = "getPropertyPure", module = "teavmJso") public static native JSObject getPure(JSObject instance, JSObject index); @InjectedBy(JSNativeInjector.class) diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyAstEmitter.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyAstEmitter.java index 5ca0a81ef..1147b955d 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyAstEmitter.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyAstEmitter.java @@ -59,6 +59,11 @@ public class JSBodyAstEmitter implements JSBodyEmitter { return parameterNames.clone(); } + @Override + public JsBodyImportInfo[] imports() { + return imports.clone(); + } + @Override public void emit(InjectorContext context) { var astWriter = new AstWriter(context.getWriter(), new DefaultGlobalNameWriter()); diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyBloatedEmitter.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyBloatedEmitter.java index c7e5ce14b..626532fb8 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyBloatedEmitter.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyBloatedEmitter.java @@ -51,6 +51,11 @@ public class JSBodyBloatedEmitter implements JSBodyEmitter { return parameterNames.clone(); } + @Override + public JsBodyImportInfo[] imports() { + return imports.clone(); + } + @Override public void emit(InjectorContext context) { emit(context.getWriter(), new EmissionStrategy() { diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyEmitter.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyEmitter.java index 2abec8005..a6c1acab0 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyEmitter.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyEmitter.java @@ -29,5 +29,7 @@ public interface JSBodyEmitter { String[] parameterNames(); + JsBodyImportInfo[] imports(); + boolean isStatic(); } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSFunctions.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSFunctions.java index e4a18b0f1..c0a87a536 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSFunctions.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSFunctions.java @@ -21,7 +21,9 @@ import org.teavm.backend.wasm.model.WasmType; class WasmGCJSFunctions { private WasmFunction[] constructors = new WasmFunction[32]; + private WasmFunction[] binds = new WasmFunction[32]; private WasmFunction[] callers = new WasmFunction[32]; + private WasmFunction getFunction; WasmFunction getFunctionConstructor(WasmGCJsoContext context, int index) { var function = constructors[index]; @@ -40,6 +42,38 @@ class WasmGCJSFunctions { return function; } + WasmFunction getBind(WasmGCJsoContext context, int index) { + var function = binds[index]; + if (function == null) { + var extern = WasmType.SpecialReferenceKind.EXTERN.asNonNullType(); + var constructorParamTypes = new WasmType[index + 1]; + Arrays.fill(constructorParamTypes, WasmType.Reference.EXTERN); + var functionType = context.functionTypes().of(extern, constructorParamTypes); + function = new WasmFunction(functionType); + function.setName(context.names().topLevel("teavm.js:bindFunction" + index)); + function.setImportModule("teavmJso"); + function.setImportName("bindFunction" + index); + context.module().functions.add(function); + binds[index] = function; + } + return function; + } + + WasmFunction getGet(WasmGCJsoContext context) { + var function = getFunction; + if (function == null) { + var functionType = context.functionTypes().of(WasmType.Reference.EXTERN, WasmType.Reference.EXTERN, + WasmType.Reference.EXTERN); + function = new WasmFunction(functionType); + function.setName(context.names().topLevel("teavm.js:getProperty")); + function.setImportModule("teavmJso"); + function.setImportName("getProperty"); + context.module().functions.add(function); + getFunction = function; + } + return function; + } + WasmFunction getFunctionCaller(WasmGCJsoContext context, int index) { var function = callers[index]; if (function == null) { diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSIntrinsic.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSIntrinsic.java index 8c91fa433..bc5badff3 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSIntrinsic.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSIntrinsic.java @@ -40,13 +40,16 @@ import org.teavm.model.ValueType; class WasmGCJSIntrinsic implements WasmGCIntrinsic { private WasmFunction globalFunction; private WasmGCJsoCommonGenerator commonGen; + private WasmGCJSFunctions functions; - WasmGCJSIntrinsic(WasmGCJsoCommonGenerator commonGen) { + WasmGCJSIntrinsic(WasmGCJsoCommonGenerator commonGen, WasmGCJSFunctions functions) { this.commonGen = commonGen; + this.functions = functions; } @Override public WasmExpression apply(InvocationExpr invocation, WasmGCIntrinsicContext context) { + var jsoContext = WasmGCJsoContext.wrap(context); switch (invocation.getMethod().getName()) { case "wrap": return wrapString(invocation.getArguments().get(0), context); @@ -64,6 +67,10 @@ class WasmGCJSIntrinsic implements WasmGCIntrinsic { return new WasmIsNull(context.generate(invocation.getArguments().get(0))); case "jsArrayItem": return arrayItem(invocation, context); + case "get": + case "getPure": + return new WasmCall(functions.getGet(jsoContext), context.generate(invocation.getArguments().get(0)), + context.generate(invocation.getArguments().get(1))); default: throw new IllegalArgumentException(); } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJso.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJso.java index 60ee3b709..2072e64a3 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJso.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJso.java @@ -43,7 +43,7 @@ public final class WasmGCJso { } }); - var jsIntrinsic = new WasmGCJSIntrinsic(commonGen); + var jsIntrinsic = new WasmGCJSIntrinsic(commonGen, jsFunctions); wasmGCHost.addIntrinsic(new MethodReference(JS.class, "wrap", String.class, JSObject.class), jsIntrinsic); wasmGCHost.addIntrinsic(new MethodReference(JS.class, "unwrapString", JSObject.class, String.class), jsIntrinsic); @@ -53,6 +53,10 @@ public final class WasmGCJso { wasmGCHost.addIntrinsic(new MethodReference(JS.class, "isNull", JSObject.class, boolean.class), jsIntrinsic); wasmGCHost.addIntrinsic(new MethodReference(JS.class, "jsArrayItem", Object.class, int.class, Object.class), jsIntrinsic); + wasmGCHost.addIntrinsic(new MethodReference(JS.class, "get", JSObject.class, JSObject.class, JSObject.class), + jsIntrinsic); + wasmGCHost.addIntrinsic(new MethodReference(JS.class, "getPure", JSObject.class, JSObject.class, + JSObject.class), jsIntrinsic); var wrapperIntrinsic = new WasmGCJSWrapperIntrinsic(); wasmGCHost.addIntrinsic(new MethodReference(JSWrapper.class, "wrap", JSObject.class, Object.class), diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJsoCommonGenerator.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJsoCommonGenerator.java index 5460c6bb5..7d0a9e482 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJsoCommonGenerator.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJsoCommonGenerator.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.Consumer; import org.teavm.backend.javascript.rendering.AstWriter; import org.teavm.backend.wasm.generate.gc.WasmGCNameProvider; @@ -66,6 +67,7 @@ class WasmGCJsoCommonGenerator { private WasmFunction javaObjectToJSFunction; private WasmGlobal defaultWrapperClass; private Map definedClasses = new HashMap<>(); + private Map importGlobals = new HashMap<>(); WasmGCJsoCommonGenerator(WasmGCJSFunctions jsFunctions) { this.jsFunctions = jsFunctions; @@ -97,6 +99,9 @@ class WasmGCJsoCommonGenerator { if (!emitter.isStatic()) { paramCount++; } + var imports = emitter.imports(); + paramCount += imports.length; + var global = new WasmGlobal(context.names().suggestForMethod(emitter.method()), WasmType.Reference.EXTERN, new WasmNullConstant(WasmType.Reference.EXTERN)); context.module().globals.add(global); @@ -124,6 +129,9 @@ class WasmGCJsoCommonGenerator { var constructor = new WasmCall(jsFunctions.getFunctionConstructor(context, paramCount)); var paramNames = new ArrayList(); + for (var importDecl : imports) { + paramNames.add(importDecl.alias); + } if (!emitter.isStatic()) { paramNames.add("__this__"); } @@ -134,11 +142,35 @@ class WasmGCJsoCommonGenerator { } var functionBody = new WasmGetGlobal(context.strings().getStringConstant(body).global); constructor.getArguments().add(stringToJs(context, functionBody)); - initializerParts.add(initializer -> initializer.getBody().add(new WasmSetGlobal(global, constructor))); + WasmExpression value = constructor; + if (imports.length > 0) { + var bind = new WasmCall(jsFunctions.getBind(context, imports.length)); + bind.getArguments().add(value); + for (var importDecl : imports) { + var importGlobal = getImportGlobal(context, importDecl.fromModule, "__self__"); + bind.getArguments().add(new WasmGetGlobal(importGlobal)); + } + value = bind; + } + var result = value; + initializerParts.add(initializer -> initializer.getBody().add(new WasmSetGlobal(global, result))); return global; } + WasmGlobal getImportGlobal(WasmGCJsoContext context, String module, String id) { + return importGlobals.computeIfAbsent(new ImportDecl(module, id), m -> { + var name = context.names().topLevel(WasmGCNameProvider.sanitize("teavm.js@imports:" + module + "#" + id)); + var global = new WasmGlobal(name, WasmType.Reference.EXTERN, + new WasmNullConstant(WasmType.Reference.EXTERN)); + global.setImmutable(true); + context.module().globals.add(global); + global.setImportModule(module); + global.setImportName(id); + return global; + }); + } + private WasmFunction stringToJsFunction(WasmGCJsoContext context) { return context.functions().forStaticMethod(STRING_TO_JS); } @@ -506,4 +538,31 @@ class WasmGCJsoCommonGenerator { } return name; } + + private static class ImportDecl { + final String module; + final String name; + + ImportDecl(String module, String name) { + this.module = module; + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ImportDecl)) { + return false; + } + var that = (ImportDecl) o; + return Objects.equals(module, that.module) && Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(module, name); + } + } } diff --git a/tests/src/test/java/org/teavm/jso/test/ImportModuleTest.java b/tests/src/test/java/org/teavm/jso/test/ImportModuleTest.java index 285f04911..fa4fbf98a 100644 --- a/tests/src/test/java/org/teavm/jso/test/ImportModuleTest.java +++ b/tests/src/test/java/org/teavm/jso/test/ImportModuleTest.java @@ -31,7 +31,7 @@ import org.teavm.junit.TestPlatform; @RunWith(TeaVMTestRunner.class) @SkipJVM -@OnlyPlatform(TestPlatform.JAVASCRIPT) +@OnlyPlatform({TestPlatform.JAVASCRIPT, TestPlatform.WEBASSEMBLY_GC}) @EachTestCompiledSeparately public class ImportModuleTest { @Test @@ -39,12 +39,14 @@ public class ImportModuleTest { "org/teavm/jso/test/amd.js", "org/teavm/jso/test/amdModule.js" }) + @OnlyPlatform(TestPlatform.JAVASCRIPT) public void amd() { assertEquals(23, runTestFunction()); } @Test @AttachJavaScript("org/teavm/jso/test/commonjs.js") + @OnlyPlatform(TestPlatform.JAVASCRIPT) public void commonjs() { assertEquals(23, runTestFunction()); } @@ -52,6 +54,7 @@ public class ImportModuleTest { @Test @JsModuleTest @ServeJS(from = "org/teavm/jso/test/es2015.js", as = "testModule.js") + @OnlyPlatform({TestPlatform.JAVASCRIPT, TestPlatform.WEBASSEMBLY_GC}) public void es2015() { assertEquals(23, runTestFunction()); } @@ -59,6 +62,7 @@ public class ImportModuleTest { @Test @JsModuleTest @ServeJS(from = "org/teavm/jso/test/classWithConstructorInModule.js", as = "testModule.js") + @OnlyPlatform(TestPlatform.JAVASCRIPT) public void classConstructor() { var o = new ClassWithConstructorInModule(); assertEquals(99, o.getFoo()); @@ -71,6 +75,7 @@ public class ImportModuleTest { @Test @JsModuleTest @ServeJS(from = "org/teavm/jso/test/classWithConstructorInModule.js", as = "testModule.js") + @OnlyPlatform(TestPlatform.JAVASCRIPT) public void topLevel() { assertEquals("top level", ClassWithConstructorInModule.topLevelFunction()); assertEquals("top level prop", ClassWithConstructorInModule.getTopLevelProperty()); diff --git a/tools/junit/src/main/java/org/teavm/junit/TestUtil.java b/tools/junit/src/main/java/org/teavm/junit/TestUtil.java index bba757612..c8d757f4e 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TestUtil.java +++ b/tools/junit/src/main/java/org/teavm/junit/TestUtil.java @@ -55,7 +55,7 @@ final class TestUtil { if (properties.isEmpty()) { try (InputStream input = TeaVMTestRunner.class.getClassLoader().getResourceAsStream(resource); OutputStream output = new BufferedOutputStream(new FileOutputStream(file))) { - IOUtils.copy(input, output); + input.transferTo(output); } } else { String content; diff --git a/tools/junit/src/main/java/org/teavm/junit/WebAssemblyGCPlatformSupport.java b/tools/junit/src/main/java/org/teavm/junit/WebAssemblyGCPlatformSupport.java index 6901b0705..568e27133 100644 --- a/tools/junit/src/main/java/org/teavm/junit/WebAssemblyGCPlatformSupport.java +++ b/tools/junit/src/main/java/org/teavm/junit/WebAssemblyGCPlatformSupport.java @@ -188,7 +188,7 @@ class WebAssemblyGCPlatformSupport extends TestPlatformSupport { getExtension() + "-deobfuscator.wasm"); try { TestUtil.resourceToFile("org/teavm/backend/wasm/wasm-gc-runtime.js", testPath, Map.of()); - TestUtil.resourceToFile("deobfuscator.wasm", testDeobfuscatorPath, Map.of()); + TestUtil.resourceToFile("org/teavm/backend/wasm/deobfuscator.wasm", testDeobfuscatorPath, Map.of()); } catch (IOException e) { throw new RuntimeException(e); }