wasm gc: add support for imports from JS

This commit is contained in:
Alexey Andreev 2024-10-16 20:46:27 +02:00
parent 1fadc71536
commit e4a2550cc6
21 changed files with 297 additions and 38 deletions

View File

@ -51,6 +51,7 @@ public final class Disassembler {
private WasmHollowFunctionType[] functionTypes; private WasmHollowFunctionType[] functionTypes;
private int[] functionTypeRefs; private int[] functionTypeRefs;
private int importFunctionCount; private int importFunctionCount;
private int importGlobalCount;
private Map<String, DebugSectionParser> debugSectionParsers = new HashMap<>(); private Map<String, DebugSectionParser> debugSectionParsers = new HashMap<>();
private DebugLinesParser debugLines; private DebugLinesParser debugLines;
private LineInfo lineInfo; private LineInfo lineInfo;
@ -143,6 +144,7 @@ public final class Disassembler {
var parser = new ImportSectionParser(importListener); var parser = new ImportSectionParser(importListener);
parser.parse(AddressListener.EMPTY, bytes); parser.parse(AddressListener.EMPTY, bytes);
importFunctionCount = importListener.functionCount(); importFunctionCount = importListener.functionCount();
importGlobalCount = importListener.globalCount();
}; };
} else if (code == 3) { } else if (code == 3) {
return bytes -> { return bytes -> {
@ -165,6 +167,7 @@ public final class Disassembler {
var globalWriter = new DisassemblyGlobalSectionListener(writer, nameProvider); var globalWriter = new DisassemblyGlobalSectionListener(writer, nameProvider);
writer.setAddressOffset(pos); writer.setAddressOffset(pos);
var sectionParser = new GlobalSectionParser(globalWriter); var sectionParser = new GlobalSectionParser(globalWriter);
sectionParser.setGlobalIndexOffset(importGlobalCount);
sectionParser.parse(writer.addressListener, bytes); sectionParser.parse(writer.addressListener, bytes);
writer.flush(); writer.flush();
}; };

View File

@ -17,12 +17,14 @@ package org.teavm.backend.wasm.disasm;
import org.teavm.backend.wasm.parser.ImportSectionListener; import org.teavm.backend.wasm.parser.ImportSectionListener;
import org.teavm.backend.wasm.parser.WasmHollowFunctionType; import org.teavm.backend.wasm.parser.WasmHollowFunctionType;
import org.teavm.backend.wasm.parser.WasmHollowType;
public class DisassemblyImportSectionListener extends BaseDisassemblyListener implements ImportSectionListener { public class DisassemblyImportSectionListener extends BaseDisassemblyListener implements ImportSectionListener {
private WasmHollowFunctionType[] functionTypes; private WasmHollowFunctionType[] functionTypes;
private String currentModule; private String currentModule;
private String currentName; private String currentName;
private int functionIndex; private int functionIndex;
private int globalIndex;
public DisassemblyImportSectionListener(DisassemblyWriter writer, NameProvider nameProvider, public DisassemblyImportSectionListener(DisassemblyWriter writer, NameProvider nameProvider,
WasmHollowFunctionType[] functionTypes) { WasmHollowFunctionType[] functionTypes) {
@ -34,6 +36,10 @@ public class DisassemblyImportSectionListener extends BaseDisassemblyListener im
return functionIndex; return functionIndex;
} }
public int globalCount() {
return globalIndex;
}
@Override @Override
public void startEntry(String module, String name) { public void startEntry(String module, String name) {
currentModule = module; currentModule = module;
@ -81,4 +87,21 @@ public class DisassemblyImportSectionListener extends BaseDisassemblyListener im
functionIndex++; 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;
}
} }

View File

@ -24,11 +24,13 @@ public class WasmGlobal extends WasmEntity {
private WasmExpression initialValue; private WasmExpression initialValue;
private boolean immutable; private boolean immutable;
private String exportName; private String exportName;
private String importName;
private String importModule;
public WasmGlobal(String name, WasmType type, WasmExpression initialValue) { public WasmGlobal(String name, WasmType type, WasmExpression initialValue) {
this.name = name; this.name = name;
this.type = Objects.requireNonNull(type); this.type = Objects.requireNonNull(type);
this.initialValue = Objects.requireNonNull(initialValue); this.initialValue = initialValue;
} }
public String getName() { public String getName() {
@ -48,7 +50,7 @@ public class WasmGlobal extends WasmEntity {
} }
public void setInitialValue(WasmExpression initialValue) { public void setInitialValue(WasmExpression initialValue) {
this.initialValue = Objects.requireNonNull(initialValue); this.initialValue = initialValue;
} }
public boolean isImmutable() { public boolean isImmutable() {
@ -66,4 +68,28 @@ public class WasmGlobal extends WasmEntity {
public void setExportName(String exportName) { public void setExportName(String exportName) {
this.exportName = 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;
}
} }

View File

@ -18,12 +18,17 @@ package org.teavm.backend.wasm.parser;
public class GlobalSectionParser extends BaseSectionParser { public class GlobalSectionParser extends BaseSectionParser {
private final GlobalSectionListener listener; private final GlobalSectionListener listener;
private CodeParser codeParser; private CodeParser codeParser;
private int globalIndexOffset;
public GlobalSectionParser(GlobalSectionListener listener) { public GlobalSectionParser(GlobalSectionListener listener) {
this.listener = listener; this.listener = listener;
codeParser = new CodeParser(); codeParser = new CodeParser();
} }
public void setGlobalIndexOffset(int globalIndexOffset) {
this.globalIndexOffset = globalIndexOffset;
}
@Override @Override
protected void parseContent() { protected void parseContent() {
var count = readLEB(); var count = readLEB();
@ -31,7 +36,7 @@ public class GlobalSectionParser extends BaseSectionParser {
reportAddress(); reportAddress();
var type = reader.readType(); var type = reader.readType();
var mutable = reader.data[reader.ptr++] != 0; 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) { if (codeListener == null) {
codeListener = CodeListener.EMPTY; codeListener = CodeListener.EMPTY;
} }

View File

@ -22,6 +22,9 @@ public interface ImportSectionListener {
default void function(int typeIndex) { default void function(int typeIndex) {
} }
default void global(WasmHollowType type) {
}
default void endEntry() { default void endEntry() {
} }
} }

View File

@ -37,11 +37,19 @@ public class ImportSectionParser extends BaseSectionParser {
listener.startEntry(module, name); listener.startEntry(module, name);
reportAddress(); reportAddress();
var type = reader.data[reader.ptr++]; var type = reader.data[reader.ptr++];
if (type == 0) { switch (type) {
var typeIndex = readLEB(); case 0: {
listener.function(typeIndex); var typeIndex = readLEB();
} else { listener.function(typeIndex);
throw new ParseException("Unsupported import type", reader.ptr); break;
}
case 3: {
var valueType = reader.readType();
listener.global(valueType);
break;
}
default:
throw new ParseException("Unsupported import type", reader.ptr);
} }
listener.endEntry(); listener.endEntry();
} }

View File

@ -27,6 +27,7 @@ import org.teavm.backend.wasm.generate.DwarfClassGenerator;
import org.teavm.backend.wasm.generate.DwarfGenerator; import org.teavm.backend.wasm.generate.DwarfGenerator;
import org.teavm.backend.wasm.model.WasmCustomSection; import org.teavm.backend.wasm.model.WasmCustomSection;
import org.teavm.backend.wasm.model.WasmFunction; 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.WasmMemorySegment;
import org.teavm.backend.wasm.model.WasmModule; import org.teavm.backend.wasm.model.WasmModule;
import org.teavm.backend.wasm.model.WasmStructure; import org.teavm.backend.wasm.model.WasmStructure;
@ -138,20 +139,29 @@ public class WasmBinaryRenderer {
} }
private void renderImports(WasmModule module) { private void renderImports(WasmModule module) {
List<WasmFunction> functions = new ArrayList<>(); var functions = new ArrayList<WasmFunction>();
for (var function : module.functions) { for (var function : module.functions) {
if (function.getImportName() == null) { if (function.getImportName() == null) {
continue; continue;
} }
functions.add(function); functions.add(function);
} }
if (functions.isEmpty()) {
var globals = new ArrayList<WasmGlobal>();
for (var global : module.globals) {
if (global.getImportName() == null) {
continue;
}
globals.add(global);
}
if (functions.isEmpty() && globals.isEmpty()) {
return; return;
} }
WasmBinaryWriter section = new WasmBinaryWriter(); WasmBinaryWriter section = new WasmBinaryWriter();
section.writeLEB(functions.size()); section.writeLEB(functions.size() + globals.size());
for (WasmFunction function : functions) { for (WasmFunction function : functions) {
int signatureIndex = module.types.indexOf(function.getType()); int signatureIndex = module.types.indexOf(function.getType());
String moduleName = function.getImportModule(); String moduleName = function.getImportModule();
@ -159,12 +169,22 @@ public class WasmBinaryRenderer {
moduleName = ""; moduleName = "";
} }
section.writeAsciiString(moduleName); section.writeAsciiString(moduleName);
section.writeAsciiString(function.getImportName()); section.writeAsciiString(function.getImportName());
section.writeByte(EXTERNAL_KIND_FUNCTION); section.writeByte(EXTERNAL_KIND_FUNCTION);
section.writeLEB(signatureIndex); 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()); writeSection(SECTION_IMPORT, "import", section.getData());
} }
@ -211,16 +231,19 @@ public class WasmBinaryRenderer {
} }
private void renderGlobals(WasmModule module) { 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; return;
} }
var section = new WasmBinaryWriter(); var section = new WasmBinaryWriter();
var visitor = new WasmBinaryRenderingVisitor(section, module, null, null, 0); var visitor = new WasmBinaryRenderingVisitor(section, module, null, null, 0);
section.writeLEB(module.globals.size()); section.writeLEB(globals.size());
for (var global : module.globals) { for (var global : globals) {
section.writeType(global.getType(), module); section.writeType(global.getType(), module);
section.writeByte(global.isImmutable() ? 0 : 1); // mutable section.writeByte(global.isImmutable() ? 0 : 1);
global.getInitialValue().acceptVisitor(visitor); global.getInitialValue().acceptVisitor(visitor);
section.writeByte(0x0b); section.writeByte(0x0b);
} }

View File

@ -15,4 +15,4 @@
*/ */
include(); include();
export { load, defaults }; export { load, defaults, wrapImport };

View File

@ -52,12 +52,15 @@ function defaults(imports) {
let javaExceptionSymbol = Symbol("javaException"); let javaExceptionSymbol = Symbol("javaException");
class JavaError extends Error { class JavaError extends Error {
constructor(javaException) { #context
constructor(context, javaException) {
super(); super();
this.#context = context;
this[javaExceptionSymbol] = javaException; this[javaExceptionSymbol] = javaException;
} }
get message() { get message() {
let exceptionMessage = exports["teavm.exceptionMessage"]; let exceptionMessage = this.#context.exports["teavm.exceptionMessage"];
if (typeof exceptionMessage === "function") { if (typeof exceptionMessage === "function") {
let message = exceptionMessage(this[javaExceptionSymbol]); let message = exceptionMessage(this[javaExceptionSymbol]);
if (message != null) { if (message != null) {
@ -247,7 +250,7 @@ function jsoImports(imports, context) {
return wrapper; return wrapper;
} }
} }
let wrapper = new JavaError(javaException); let wrapper = new JavaError(context, javaException);
javaExceptionWrappers.set(javaException, new WeakRef(wrapper)); javaExceptionWrappers.set(javaException, new WeakRef(wrapper));
return wrapper; return wrapper;
} }
@ -312,7 +315,6 @@ function jsoImports(imports, context) {
unwrapBoolean: value => value ? 1 : 0, unwrapBoolean: value => value ? 1 : 0,
wrapBoolean: value => !!value, wrapBoolean: value => !!value,
getProperty: getProperty, getProperty: getProperty,
getPropertyPure: getProperty,
setProperty: setProperty, setProperty: setProperty,
setPropertyPure: setProperty, setPropertyPure: setProperty,
global(name) { global(name) {
@ -565,6 +567,7 @@ function jsoImports(imports, context) {
imports.teavmJso["createFunction" + i] = new Function("wrapCallFromJavaToJs", ...argumentList, "body", imports.teavmJso["createFunction" + i] = new Function("wrapCallFromJavaToJs", ...argumentList, "body",
`return new Function('wrapCallFromJavaToJs', ${argsAndBody}).bind(this, wrapCallFromJavaToJs);` `return new Function('wrapCallFromJavaToJs', ${argsAndBody}).bind(this, wrapCallFromJavaToJs);`
).bind(null, wrapCallFromJavaToJs); ).bind(null, wrapCallFromJavaToJs);
imports.teavmJso["bindFunction" + i] = (f, ...args) => f.bind(null, ...args);
imports.teavmJso["callFunction" + i] = new Function("rethrowJsAsJava", "fn", ...argumentList, imports.teavmJso["callFunction" + i] = new Function("rethrowJsAsJava", "fn", ...argumentList,
`try {\n` + `try {\n` +
` return fn(${args});\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) { async function load(path, options) {
if (!options) { if (!options) {
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 importObj = {};
const defaultsResult = defaults(importObj); const defaultsResult = defaults(importObj);
if (typeof options.installImports !== "undefined") { if (typeof options.installImports !== "undefined") {
options.installImports(importObj); options.installImports(importObj);
} }
if (!options.noAutoImports) {
let deobfuscatorOptions = options.stackDeobfuscator || {}; await wrapImports(module, importObj);
let debugInfoLocation = deobfuscatorOptions.infoLocation || "auto"; }
let [deobfuscatorFactory, { module, instance }, debugInfo] = await Promise.all([ let instance = new WebAssembly.Instance(module, importObj);
deobfuscatorOptions.enabled ? getDeobfuscator(path, deobfuscatorOptions) : Promise.resolve(null),
WebAssembly.instantiateStreaming(fetch(path), importObj),
fetchExternalDebugInfo(path, debugInfoLocation, deobfuscatorOptions)
]);
defaultsResult.supplyExports(instance.exports); defaultsResult.supplyExports(instance.exports);
if (deobfuscatorFactory) { if (deobfuscatorFactory) {

View File

@ -17,6 +17,6 @@
var TeaVM = TeaVM || {}; var TeaVM = TeaVM || {};
TeaVM.wasmGC = TeaVM.wasmGC || (() => { TeaVM.wasmGC = TeaVM.wasmGC || (() => {
include(); include();
return { load, defaults }; return { load, defaults, wrapImport };
})(); })();

View File

@ -731,13 +731,11 @@ public final class JS {
@InjectedBy(JSNativeInjector.class) @InjectedBy(JSNativeInjector.class)
@JSBody(params = { "instance", "index" }, script = "return instance[index];") @JSBody(params = { "instance", "index" }, script = "return instance[index];")
@Import(name = "getProperty", module = "teavmJso")
public static native JSObject get(JSObject instance, JSObject index); public static native JSObject get(JSObject instance, JSObject index);
@InjectedBy(JSNativeInjector.class) @InjectedBy(JSNativeInjector.class)
@JSBody(params = { "instance", "index" }, script = "return instance[index];") @JSBody(params = { "instance", "index" }, script = "return instance[index];")
@NoSideEffects @NoSideEffects
@Import(name = "getPropertyPure", module = "teavmJso")
public static native JSObject getPure(JSObject instance, JSObject index); public static native JSObject getPure(JSObject instance, JSObject index);
@InjectedBy(JSNativeInjector.class) @InjectedBy(JSNativeInjector.class)

View File

@ -59,6 +59,11 @@ public class JSBodyAstEmitter implements JSBodyEmitter {
return parameterNames.clone(); return parameterNames.clone();
} }
@Override
public JsBodyImportInfo[] imports() {
return imports.clone();
}
@Override @Override
public void emit(InjectorContext context) { public void emit(InjectorContext context) {
var astWriter = new AstWriter(context.getWriter(), new DefaultGlobalNameWriter()); var astWriter = new AstWriter(context.getWriter(), new DefaultGlobalNameWriter());

View File

@ -51,6 +51,11 @@ public class JSBodyBloatedEmitter implements JSBodyEmitter {
return parameterNames.clone(); return parameterNames.clone();
} }
@Override
public JsBodyImportInfo[] imports() {
return imports.clone();
}
@Override @Override
public void emit(InjectorContext context) { public void emit(InjectorContext context) {
emit(context.getWriter(), new EmissionStrategy() { emit(context.getWriter(), new EmissionStrategy() {

View File

@ -29,5 +29,7 @@ public interface JSBodyEmitter {
String[] parameterNames(); String[] parameterNames();
JsBodyImportInfo[] imports();
boolean isStatic(); boolean isStatic();
} }

View File

@ -21,7 +21,9 @@ import org.teavm.backend.wasm.model.WasmType;
class WasmGCJSFunctions { class WasmGCJSFunctions {
private WasmFunction[] constructors = new WasmFunction[32]; private WasmFunction[] constructors = new WasmFunction[32];
private WasmFunction[] binds = new WasmFunction[32];
private WasmFunction[] callers = new WasmFunction[32]; private WasmFunction[] callers = new WasmFunction[32];
private WasmFunction getFunction;
WasmFunction getFunctionConstructor(WasmGCJsoContext context, int index) { WasmFunction getFunctionConstructor(WasmGCJsoContext context, int index) {
var function = constructors[index]; var function = constructors[index];
@ -40,6 +42,38 @@ class WasmGCJSFunctions {
return function; 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) { WasmFunction getFunctionCaller(WasmGCJsoContext context, int index) {
var function = callers[index]; var function = callers[index];
if (function == null) { if (function == null) {

View File

@ -40,13 +40,16 @@ import org.teavm.model.ValueType;
class WasmGCJSIntrinsic implements WasmGCIntrinsic { class WasmGCJSIntrinsic implements WasmGCIntrinsic {
private WasmFunction globalFunction; private WasmFunction globalFunction;
private WasmGCJsoCommonGenerator commonGen; private WasmGCJsoCommonGenerator commonGen;
private WasmGCJSFunctions functions;
WasmGCJSIntrinsic(WasmGCJsoCommonGenerator commonGen) { WasmGCJSIntrinsic(WasmGCJsoCommonGenerator commonGen, WasmGCJSFunctions functions) {
this.commonGen = commonGen; this.commonGen = commonGen;
this.functions = functions;
} }
@Override @Override
public WasmExpression apply(InvocationExpr invocation, WasmGCIntrinsicContext context) { public WasmExpression apply(InvocationExpr invocation, WasmGCIntrinsicContext context) {
var jsoContext = WasmGCJsoContext.wrap(context);
switch (invocation.getMethod().getName()) { switch (invocation.getMethod().getName()) {
case "wrap": case "wrap":
return wrapString(invocation.getArguments().get(0), context); return wrapString(invocation.getArguments().get(0), context);
@ -64,6 +67,10 @@ class WasmGCJSIntrinsic implements WasmGCIntrinsic {
return new WasmIsNull(context.generate(invocation.getArguments().get(0))); return new WasmIsNull(context.generate(invocation.getArguments().get(0)));
case "jsArrayItem": case "jsArrayItem":
return arrayItem(invocation, context); 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: default:
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }

View File

@ -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, "wrap", String.class, JSObject.class), jsIntrinsic);
wasmGCHost.addIntrinsic(new MethodReference(JS.class, "unwrapString", JSObject.class, String.class), wasmGCHost.addIntrinsic(new MethodReference(JS.class, "unwrapString", JSObject.class, String.class),
jsIntrinsic); 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, "isNull", JSObject.class, boolean.class), jsIntrinsic);
wasmGCHost.addIntrinsic(new MethodReference(JS.class, "jsArrayItem", Object.class, int.class, Object.class), wasmGCHost.addIntrinsic(new MethodReference(JS.class, "jsArrayItem", Object.class, int.class, Object.class),
jsIntrinsic); 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(); var wrapperIntrinsic = new WasmGCJSWrapperIntrinsic();
wasmGCHost.addIntrinsic(new MethodReference(JSWrapper.class, "wrap", JSObject.class, Object.class), wasmGCHost.addIntrinsic(new MethodReference(JSWrapper.class, "wrap", JSObject.class, Object.class),

View File

@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.teavm.backend.javascript.rendering.AstWriter; import org.teavm.backend.javascript.rendering.AstWriter;
import org.teavm.backend.wasm.generate.gc.WasmGCNameProvider; import org.teavm.backend.wasm.generate.gc.WasmGCNameProvider;
@ -66,6 +67,7 @@ class WasmGCJsoCommonGenerator {
private WasmFunction javaObjectToJSFunction; private WasmFunction javaObjectToJSFunction;
private WasmGlobal defaultWrapperClass; private WasmGlobal defaultWrapperClass;
private Map<String, WasmGlobal> definedClasses = new HashMap<>(); private Map<String, WasmGlobal> definedClasses = new HashMap<>();
private Map<ImportDecl, WasmGlobal> importGlobals = new HashMap<>();
WasmGCJsoCommonGenerator(WasmGCJSFunctions jsFunctions) { WasmGCJsoCommonGenerator(WasmGCJSFunctions jsFunctions) {
this.jsFunctions = jsFunctions; this.jsFunctions = jsFunctions;
@ -97,6 +99,9 @@ class WasmGCJsoCommonGenerator {
if (!emitter.isStatic()) { if (!emitter.isStatic()) {
paramCount++; paramCount++;
} }
var imports = emitter.imports();
paramCount += imports.length;
var global = new WasmGlobal(context.names().suggestForMethod(emitter.method()), var global = new WasmGlobal(context.names().suggestForMethod(emitter.method()),
WasmType.Reference.EXTERN, new WasmNullConstant(WasmType.Reference.EXTERN)); WasmType.Reference.EXTERN, new WasmNullConstant(WasmType.Reference.EXTERN));
context.module().globals.add(global); context.module().globals.add(global);
@ -124,6 +129,9 @@ class WasmGCJsoCommonGenerator {
var constructor = new WasmCall(jsFunctions.getFunctionConstructor(context, paramCount)); var constructor = new WasmCall(jsFunctions.getFunctionConstructor(context, paramCount));
var paramNames = new ArrayList<String>(); var paramNames = new ArrayList<String>();
for (var importDecl : imports) {
paramNames.add(importDecl.alias);
}
if (!emitter.isStatic()) { if (!emitter.isStatic()) {
paramNames.add("__this__"); paramNames.add("__this__");
} }
@ -134,11 +142,35 @@ class WasmGCJsoCommonGenerator {
} }
var functionBody = new WasmGetGlobal(context.strings().getStringConstant(body).global); var functionBody = new WasmGetGlobal(context.strings().getStringConstant(body).global);
constructor.getArguments().add(stringToJs(context, functionBody)); 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; 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) { private WasmFunction stringToJsFunction(WasmGCJsoContext context) {
return context.functions().forStaticMethod(STRING_TO_JS); return context.functions().forStaticMethod(STRING_TO_JS);
} }
@ -506,4 +538,31 @@ class WasmGCJsoCommonGenerator {
} }
return name; 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);
}
}
} }

View File

@ -31,7 +31,7 @@ import org.teavm.junit.TestPlatform;
@RunWith(TeaVMTestRunner.class) @RunWith(TeaVMTestRunner.class)
@SkipJVM @SkipJVM
@OnlyPlatform(TestPlatform.JAVASCRIPT) @OnlyPlatform({TestPlatform.JAVASCRIPT, TestPlatform.WEBASSEMBLY_GC})
@EachTestCompiledSeparately @EachTestCompiledSeparately
public class ImportModuleTest { public class ImportModuleTest {
@Test @Test
@ -39,12 +39,14 @@ public class ImportModuleTest {
"org/teavm/jso/test/amd.js", "org/teavm/jso/test/amd.js",
"org/teavm/jso/test/amdModule.js" "org/teavm/jso/test/amdModule.js"
}) })
@OnlyPlatform(TestPlatform.JAVASCRIPT)
public void amd() { public void amd() {
assertEquals(23, runTestFunction()); assertEquals(23, runTestFunction());
} }
@Test @Test
@AttachJavaScript("org/teavm/jso/test/commonjs.js") @AttachJavaScript("org/teavm/jso/test/commonjs.js")
@OnlyPlatform(TestPlatform.JAVASCRIPT)
public void commonjs() { public void commonjs() {
assertEquals(23, runTestFunction()); assertEquals(23, runTestFunction());
} }
@ -52,6 +54,7 @@ public class ImportModuleTest {
@Test @Test
@JsModuleTest @JsModuleTest
@ServeJS(from = "org/teavm/jso/test/es2015.js", as = "testModule.js") @ServeJS(from = "org/teavm/jso/test/es2015.js", as = "testModule.js")
@OnlyPlatform({TestPlatform.JAVASCRIPT, TestPlatform.WEBASSEMBLY_GC})
public void es2015() { public void es2015() {
assertEquals(23, runTestFunction()); assertEquals(23, runTestFunction());
} }
@ -59,6 +62,7 @@ public class ImportModuleTest {
@Test @Test
@JsModuleTest @JsModuleTest
@ServeJS(from = "org/teavm/jso/test/classWithConstructorInModule.js", as = "testModule.js") @ServeJS(from = "org/teavm/jso/test/classWithConstructorInModule.js", as = "testModule.js")
@OnlyPlatform(TestPlatform.JAVASCRIPT)
public void classConstructor() { public void classConstructor() {
var o = new ClassWithConstructorInModule(); var o = new ClassWithConstructorInModule();
assertEquals(99, o.getFoo()); assertEquals(99, o.getFoo());
@ -71,6 +75,7 @@ public class ImportModuleTest {
@Test @Test
@JsModuleTest @JsModuleTest
@ServeJS(from = "org/teavm/jso/test/classWithConstructorInModule.js", as = "testModule.js") @ServeJS(from = "org/teavm/jso/test/classWithConstructorInModule.js", as = "testModule.js")
@OnlyPlatform(TestPlatform.JAVASCRIPT)
public void topLevel() { public void topLevel() {
assertEquals("top level", ClassWithConstructorInModule.topLevelFunction()); assertEquals("top level", ClassWithConstructorInModule.topLevelFunction());
assertEquals("top level prop", ClassWithConstructorInModule.getTopLevelProperty()); assertEquals("top level prop", ClassWithConstructorInModule.getTopLevelProperty());

View File

@ -55,7 +55,7 @@ final class TestUtil {
if (properties.isEmpty()) { if (properties.isEmpty()) {
try (InputStream input = TeaVMTestRunner.class.getClassLoader().getResourceAsStream(resource); try (InputStream input = TeaVMTestRunner.class.getClassLoader().getResourceAsStream(resource);
OutputStream output = new BufferedOutputStream(new FileOutputStream(file))) { OutputStream output = new BufferedOutputStream(new FileOutputStream(file))) {
IOUtils.copy(input, output); input.transferTo(output);
} }
} else { } else {
String content; String content;

View File

@ -188,7 +188,7 @@ class WebAssemblyGCPlatformSupport extends TestPlatformSupport<WasmGCTarget> {
getExtension() + "-deobfuscator.wasm"); getExtension() + "-deobfuscator.wasm");
try { try {
TestUtil.resourceToFile("org/teavm/backend/wasm/wasm-gc-runtime.js", testPath, Map.of()); 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) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }