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 int[] functionTypeRefs;
private int importFunctionCount;
private int importGlobalCount;
private Map<String, DebugSectionParser> 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();
};

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.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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

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

View File

@ -37,10 +37,18 @@ public class ImportSectionParser extends BaseSectionParser {
listener.startEntry(module, name);
reportAddress();
var type = reader.data[reader.ptr++];
if (type == 0) {
switch (type) {
case 0: {
var typeIndex = readLEB();
listener.function(typeIndex);
} else {
break;
}
case 3: {
var valueType = reader.readType();
listener.global(valueType);
break;
}
default:
throw new ParseException("Unsupported import type", reader.ptr);
}
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.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<WasmFunction> functions = new ArrayList<>();
var functions = new ArrayList<WasmFunction>();
for (var function : module.functions) {
if (function.getImportName() == null) {
continue;
}
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;
}
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);
}

View File

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

View File

@ -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) {

View File

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

View File

@ -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)

View File

@ -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());

View File

@ -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() {

View File

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

View File

@ -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) {

View File

@ -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();
}

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, "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),

View File

@ -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<String, WasmGlobal> definedClasses = new HashMap<>();
private Map<ImportDecl, WasmGlobal> 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<String>();
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);
}
}
}

View File

@ -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());

View File

@ -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;

View File

@ -188,7 +188,7 @@ class WebAssemblyGCPlatformSupport extends TestPlatformSupport<WasmGCTarget> {
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);
}