diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCDeclarationsGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCDeclarationsGenerator.java index dbd845a52..a4601b738 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCDeclarationsGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCDeclarationsGenerator.java @@ -15,6 +15,7 @@ */ package org.teavm.backend.wasm.generate.gc; +import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; import org.teavm.backend.wasm.BaseWasmFunctionRepository; @@ -46,6 +47,7 @@ public class WasmGCDeclarationsGenerator { public final WasmFunctionTypes functionTypes; private final WasmGCClassGenerator classGenerator; private final WasmGCMethodGenerator methodGenerator; + private List initializerContributors = new ArrayList<>(); public WasmGCDeclarationsGenerator( WasmModule module, @@ -77,7 +79,8 @@ public class WasmGCDeclarationsGenerator { diagnostics, customGenerators, intrinsics, - strict + strict, + initializerContributors::add ); var tags = new TagRegistry(classes, hierarchy); var metadataRequirements = new ClassMetadataRequirements(dependencyInfo); @@ -132,7 +135,8 @@ public class WasmGCDeclarationsGenerator { } public void contributeToInitializer(WasmFunction function) { - var contributors = List.of(classGenerator, classGenerator.strings); + var contributors = new ArrayList<>(List.of(classGenerator, classGenerator.strings)); + contributors.addAll(initializerContributors); for (var contributor : contributors) { contributor.contributeToInitializerDefinitions(function); } diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationContext.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationContext.java index d87586291..94cc5e203 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationContext.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationContext.java @@ -21,10 +21,12 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; import org.teavm.backend.wasm.BaseWasmFunctionRepository; import org.teavm.backend.wasm.WasmFunctionTypes; import org.teavm.backend.wasm.gc.vtable.WasmGCVirtualTableProvider; import org.teavm.backend.wasm.generate.common.methods.BaseWasmGenerationContext; +import org.teavm.backend.wasm.generate.gc.WasmGCInitializerContributor; import org.teavm.backend.wasm.generate.gc.WasmGCNameProvider; import org.teavm.backend.wasm.generate.gc.classes.WasmGCClassInfoProvider; import org.teavm.backend.wasm.generate.gc.classes.WasmGCStandardClasses; @@ -63,6 +65,7 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext { private Map> interfaceImplementors; private WasmGCNameProvider names; private boolean strict; + private Consumer initializerContributors; public WasmGCGenerationContext(WasmModule module, WasmGCVirtualTableProvider virtualTables, WasmGCTypeMapper typeMapper, WasmFunctionTypes functionTypes, ListableClassReaderSource classes, @@ -70,7 +73,7 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext { WasmGCSupertypeFunctionProvider supertypeFunctions, WasmGCClassInfoProvider classInfoProvider, WasmGCStandardClasses standardClasses, WasmGCStringProvider strings, WasmGCCustomGeneratorProvider customGenerators, WasmGCIntrinsicProvider intrinsics, - WasmGCNameProvider names, boolean strict) { + WasmGCNameProvider names, boolean strict, Consumer initializerContributors) { this.module = module; this.virtualTables = virtualTables; this.typeMapper = typeMapper; @@ -87,6 +90,7 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext { this.intrinsics = intrinsics; this.names = names; this.strict = strict; + this.initializerContributors = initializerContributors; } public WasmGCClassInfoProvider classInfoProvider() { @@ -222,4 +226,17 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext { } } } + + public void addToInitializer(Consumer initializer) { + initializerContributors.accept(new WasmGCInitializerContributor() { + @Override + public void contributeToInitializerDefinitions(WasmFunction function) { + } + + @Override + public void contributeToInitializer(WasmFunction function) { + initializer.accept(function); + } + }); + } } diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java index 755f90b41..e570540eb 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java @@ -17,6 +17,7 @@ package org.teavm.backend.wasm.generate.gc.methods; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; import java.util.function.Supplier; import org.teavm.ast.ArrayFromDataExpr; import org.teavm.ast.ArrayType; @@ -816,5 +817,10 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor { public WasmTag exceptionTag() { return context.getExceptionTag(); } + + @Override + public void addToInitializer(Consumer initializerContributor) { + context.addToInitializer(initializerContributor); + } }; } diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCMethodGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCMethodGenerator.java index aa7a2fe5f..6237f6f22 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCMethodGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCMethodGenerator.java @@ -24,6 +24,7 @@ import java.util.Map; import java.util.Objects; import java.util.Queue; import java.util.Set; +import java.util.function.Consumer; import org.teavm.ast.RegularMethodNode; import org.teavm.ast.decompilation.Decompiler; import org.teavm.backend.wasm.BaseWasmFunctionRepository; @@ -32,6 +33,7 @@ import org.teavm.backend.wasm.gc.PreciseTypeInference; import org.teavm.backend.wasm.gc.PreciseValueType; import org.teavm.backend.wasm.gc.WasmGCVariableCategoryProvider; import org.teavm.backend.wasm.gc.vtable.WasmGCVirtualTableProvider; +import org.teavm.backend.wasm.generate.gc.WasmGCInitializerContributor; import org.teavm.backend.wasm.generate.gc.WasmGCNameProvider; import org.teavm.backend.wasm.generate.gc.classes.WasmGCClassInfoProvider; import org.teavm.backend.wasm.generate.gc.classes.WasmGCStandardClasses; @@ -86,6 +88,7 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository { private WasmGCStandardClasses standardClasses; private WasmGCStringProvider strings; private boolean strict; + private Consumer initializerContributors; public WasmGCMethodGenerator( WasmModule module, @@ -99,7 +102,8 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository { Diagnostics diagnostics, WasmGCCustomGeneratorProvider customGenerators, WasmGCIntrinsicProvider intrinsics, - boolean strict + boolean strict, + Consumer initializerContributors ) { this.module = module; this.hierarchy = hierarchy; @@ -113,6 +117,7 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository { this.customGenerators = customGenerators; this.intrinsics = intrinsics; this.strict = strict; + this.initializerContributors = initializerContributors; } public void setTypeMapper(WasmGCTypeMapper typeMapper) { @@ -356,7 +361,8 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository { customGenerators, intrinsics, names, - strict + strict, + initializerContributors ); } return context; diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsicContext.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsicContext.java index 1574c989a..c88ae2532 100644 --- a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsicContext.java +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsicContext.java @@ -15,6 +15,7 @@ */ package org.teavm.backend.wasm.intrinsics.gc; +import java.util.function.Consumer; import org.teavm.ast.Expr; import org.teavm.backend.wasm.BaseWasmFunctionRepository; import org.teavm.backend.wasm.WasmFunctionTypes; @@ -25,6 +26,7 @@ import org.teavm.backend.wasm.generate.gc.WasmGCNameProvider; import org.teavm.backend.wasm.generate.gc.classes.WasmGCClassInfoProvider; import org.teavm.backend.wasm.generate.gc.classes.WasmGCTypeMapper; import org.teavm.backend.wasm.generate.gc.strings.WasmGCStringProvider; +import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmModule; import org.teavm.backend.wasm.model.WasmTag; import org.teavm.backend.wasm.model.expression.WasmExpression; @@ -58,4 +60,6 @@ public interface WasmGCIntrinsicContext { ClassLoader classLoader(); WasmTag exceptionTag(); + + void addToInitializer(Consumer initializerContributor); } diff --git a/core/src/main/resources/org/teavm/backend/wasm/wasm-gc-runtime.js b/core/src/main/resources/org/teavm/backend/wasm/wasm-gc-runtime.js index c09f739c1..a9531e5a7 100644 --- a/core/src/main/resources/org/teavm/backend/wasm/wasm-gc-runtime.js +++ b/core/src/main/resources/org/teavm/backend/wasm/wasm-gc-runtime.js @@ -17,6 +17,9 @@ var TeaVM = TeaVM || {}; TeaVM.wasm = function() { let exports; + let getGlobalName = function(name) { + return eval("return " + {name}); + } function defaults(imports) { let stderr = ""; let stdout = ""; @@ -92,18 +95,51 @@ TeaVM.wasm = function() { } return weakRef; }, - deref(weakRef) { - return weakRef.deref(); - }, + deref: weakRef => weakRef.deref(), createStringWeakRef(value, heldValue) { let weakRef = new WeakRef(value); stringFinalizationRegistry.register(value, heldValue) return weakRef; }, - stringDeref(weakRef) { - return weakRef.deref(); - } + stringDeref: weakRef => weakRef.deref() }; + function identity(value) { + return value; + } + imports.teavmJso = { + emptyString: () => "", + stringFromCharCode: code => String.fromCharCode(code), + concatStrings: (a, b) => a + b, + stringLength: s => s.length, + charAt: (s, index) => s.charCodeAt(index), + emptyArray: () => [], + appendToArray: (array, e) => array.push(e), + unwrapBoolean: value => value ? 1 : 0, + wrapBoolean: value => !!value, + getProperty: (obj, prop) => obj[prop], + getPropertyPure: (obj, prop) => obj[prop], + setProperty: (obj, prop, value) => obj[prop] = value, + setPropertyPure: (obj, prop) => obj[prop] = value, + global: getGlobalName + }; + for (let name of ["wrapByte", "wrapShort", "wrapChar", "wrapInt", "wrapFloat", "wrapDouble", "unwrapByte", + "unwrapShort", "unwrapChar", "unwrapInt", "unwrapFloat", "unwrapDouble"]) { + imports.teavmJso[name] = identity; + } + for (let i = 0; i < 32; ++i) { + imports.teavmJso["createFunction" + i] = function() { + return new Function(...arguments); + }; + imports.teavmJso["callFunction" + i] = function(fn, ...args) { + return fn(...args); + }; + imports.teavmJso["callMethod" + i] = function(instance, method, ...args) { + return instance[method](...args); + }; + imports.teavmJso["construct" + i] = function(constructor, ...args) { + return new constructor(...args); + }; + } imports.teavmMath = Math; } 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 03ec6ff9d..1a3481cdd 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 @@ -19,6 +19,7 @@ import java.lang.reflect.Array; import org.teavm.backend.javascript.spi.GeneratedBy; import org.teavm.backend.javascript.spi.InjectedBy; import org.teavm.dependency.PluggableDependency; +import org.teavm.interop.Import; import org.teavm.interop.NoSideEffects; import org.teavm.jso.JSBody; import org.teavm.jso.JSObject; @@ -28,7 +29,7 @@ import org.teavm.jso.core.JSBoolean; import org.teavm.jso.core.JSNumber; import org.teavm.jso.core.JSString; -final class JS { +public final class JS { private JS() { } @@ -77,30 +78,37 @@ final class JS { @InjectedBy(JSNativeInjector.class) @NoSideEffects + @Import(name = "wrapByte", module = "teavmJso") public static native JSObject wrap(byte value); @InjectedBy(JSNativeInjector.class) @NoSideEffects + @Import(name = "wrapShort", module = "teavmJso") public static native JSObject wrap(short value); @InjectedBy(JSNativeInjector.class) @NoSideEffects + @Import(name = "wrapInt", module = "teavmJso") public static native JSObject wrap(int value); @InjectedBy(JSNativeInjector.class) @NoSideEffects + @Import(name = "wrapChar", module = "teavmJso") public static native JSObject wrap(char value); @InjectedBy(JSNativeInjector.class) @NoSideEffects + @Import(name = "wrapFloat", module = "teavmJso") public static native JSObject wrap(float value); @InjectedBy(JSNativeInjector.class) @NoSideEffects + @Import(name = "wrapDouble", module = "teavmJso") public static native JSObject wrap(double value); @InjectedBy(JSNativeInjector.class) @NoSideEffects + @Import(name = "wrapBoolean", module = "teavmJso") public static native JSObject wrap(boolean value); @InjectedBy(JSNativeInjector.class) @@ -109,30 +117,37 @@ final class JS { @InjectedBy(JSNativeInjector.class) @NoSideEffects + @Import(name = "unwrapByte", module = "teavmJso") public static native byte unwrapByte(JSObject value); @InjectedBy(JSNativeInjector.class) @NoSideEffects + @Import(name = "unwrapChar", module = "teavmJso") public static native char unwrapCharacter(JSObject value); @InjectedBy(JSNativeInjector.class) @NoSideEffects + @Import(name = "unwrapShort", module = "teavmJso") public static native short unwrapShort(JSObject value); @InjectedBy(JSNativeInjector.class) @NoSideEffects + @Import(name = "unwrapInt", module = "teavmJso") public static native int unwrapInt(JSObject value); @InjectedBy(JSNativeInjector.class) @NoSideEffects + @Import(name = "unwrapFloat", module = "teavmJso") public static native float unwrapFloat(JSObject value); @InjectedBy(JSNativeInjector.class) @NoSideEffects + @Import(name = "unwrapDouble", module = "teavmJso") public static native double unwrapDouble(JSObject value); @InjectedBy(JSNativeInjector.class) @NoSideEffects + @Import(name = "unwrapBoolean", module = "teavmJso") public static native boolean unwrapBoolean(JSObject value); @InjectedBy(JSNativeInjector.class) @@ -466,68 +481,82 @@ final class JS { @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "callMethod0", module = "teavmJso") public static native JSObject invoke(JSObject instance, JSObject method); @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "callMethod1", module = "teavmJso") public static native JSObject invoke(JSObject instance, JSObject method, JSObject a); @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "callMethod2", module = "teavmJso") public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b); @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "callMethod3", module = "teavmJso") public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c); @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "callMethod4", module = "teavmJso") public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, JSObject d); @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "callMethod5", module = "teavmJso") public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e); @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "callMethod6", module = "teavmJso") public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f); @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "callMethod7", module = "teavmJso") public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f, JSObject g); @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "callMethod8", module = "teavmJso") public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f, JSObject g, JSObject h); @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "callMethod9", module = "teavmJso") public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f, JSObject g, JSObject h, JSObject i); @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "callMethod10", module = "teavmJso") public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f, JSObject g, JSObject h, JSObject i, JSObject j); @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "callMethod11", module = "teavmJso") public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f, JSObject g, JSObject h, JSObject i, JSObject j, JSObject k); @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "callMethod12", module = "teavmJso") public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f, JSObject g, JSObject h, JSObject i, JSObject j, JSObject k, JSObject l); @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "callMethod13", module = "teavmJso") public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f, JSObject g, JSObject h, JSObject i, JSObject j, JSObject k, JSObject l, JSObject m); @@ -608,85 +637,103 @@ final class JS { @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "construct0", module = "teavmJso") public static native JSObject construct(JSObject cls); @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "construct1", module = "teavmJso") public static native JSObject construct(JSObject cls, JSObject a); @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "construct2", module = "teavmJso") public static native JSObject construct(JSObject cls, JSObject a, JSObject b); @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "construct3", module = "teavmJso") public static native JSObject construct(JSObject cls, JSObject a, JSObject b, JSObject c); @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "construct4", module = "teavmJso") public static native JSObject construct(JSObject cls, JSObject a, JSObject b, JSObject c, JSObject d); @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "construct5", module = "teavmJso") public static native JSObject construct(JSObject cls, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e); @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "construct6", module = "teavmJso") public static native JSObject construct(JSObject cls, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f); @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "construct7", module = "teavmJso") public static native JSObject construct(JSObject cls, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f, JSObject g); @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "construct8", module = "teavmJso") public static native JSObject construct(JSObject cls, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f, JSObject g, JSObject h); @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "construct9", module = "teavmJso") public static native JSObject construct(JSObject cls, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f, JSObject g, JSObject h, JSObject i); @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "construct10", module = "teavmJso") public static native JSObject construct(JSObject cls, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f, JSObject g, JSObject h, JSObject i, JSObject j); @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "construct11", module = "teavmJso") public static native JSObject construct(JSObject cls, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f, JSObject g, JSObject h, JSObject i, JSObject j, JSObject k); @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "construct12", module = "teavmJso") public static native JSObject construct(JSObject cls, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f, JSObject g, JSObject h, JSObject i, JSObject j, JSObject k, JSObject l); @InjectedBy(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "construct13", module = "teavmJso") public static native JSObject construct(JSObject cls, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f, JSObject g, JSObject h, JSObject i, JSObject j, JSObject k, JSObject l, JSObject m); @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) @JSBody(params = { "instance", "index", "obj" }, script = "instance[index] = obj;") + @Import(name = "setProperty", module = "teavmJso") public static native void set(JSObject instance, JSObject index, JSObject obj); @InjectedBy(JSNativeInjector.class) @JSBody(params = { "instance", "index", "obj" }, script = "instance[index] = obj;") @NoSideEffects + @Import(name = "setPropertyPure", module = "teavmJso") public static native void setPure(JSObject instance, JSObject index, JSObject obj); @GeneratedBy(JSNativeGenerator.class) @@ -699,6 +746,7 @@ final class JS { @InjectedBy(JSNativeInjector.class) @NoSideEffects + @Import(name = "global", module = "teavmJso") public static native JSObject global(String name); @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 5b37b3a36..5ca0a81ef 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 @@ -26,22 +26,39 @@ import org.teavm.backend.javascript.spi.GeneratorContext; import org.teavm.backend.javascript.spi.InjectorContext; import org.teavm.model.MethodReference; -class JSBodyAstEmitter implements JSBodyEmitter { +public class JSBodyAstEmitter implements JSBodyEmitter { private boolean isStatic; - private AstNode ast; - private AstNode rootAst; + private final MethodReference method; + public final AstNode ast; + public final AstNode rootAst; private String[] parameterNames; private JsBodyImportInfo[] imports; - JSBodyAstEmitter(boolean isStatic, AstNode ast, AstNode rootAst, String[] parameterNames, + JSBodyAstEmitter(boolean isStatic, MethodReference method, AstNode ast, AstNode rootAst, String[] parameterNames, JsBodyImportInfo[] imports) { this.isStatic = isStatic; + this.method = method; this.ast = ast; this.rootAst = rootAst; this.parameterNames = parameterNames; this.imports = imports; } + @Override + public MethodReference method() { + return method; + } + + @Override + public boolean isStatic() { + return isStatic; + } + + @Override + public String[] parameterNames() { + return parameterNames.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 19d740ba8..c7e5ce14b 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 @@ -20,10 +20,10 @@ import org.teavm.backend.javascript.spi.GeneratorContext; import org.teavm.backend.javascript.spi.InjectorContext; import org.teavm.model.MethodReference; -class JSBodyBloatedEmitter implements JSBodyEmitter { +public class JSBodyBloatedEmitter implements JSBodyEmitter { private boolean isStatic; private MethodReference method; - private String script; + public final String script; private String[] parameterNames; private JsBodyImportInfo[] imports; @@ -36,6 +36,21 @@ class JSBodyBloatedEmitter implements JSBodyEmitter { this.imports = imports; } + @Override + public MethodReference method() { + return method; + } + + @Override + public boolean isStatic() { + return isStatic; + } + + @Override + public String[] parameterNames() { + return parameterNames.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 4cb5fc7e5..2abec8005 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 @@ -20,8 +20,14 @@ import org.teavm.backend.javascript.spi.GeneratorContext; import org.teavm.backend.javascript.spi.InjectorContext; import org.teavm.model.MethodReference; -interface JSBodyEmitter { +public interface JSBodyEmitter { + MethodReference method(); + void emit(InjectorContext context); void emit(GeneratorContext context, SourceWriter writer, MethodReference methodRef); + + String[] parameterNames(); + + boolean isStatic(); } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyRepository.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyRepository.java index b93db9ba0..a14807bfd 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyRepository.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyRepository.java @@ -21,12 +21,12 @@ import java.util.Map; import java.util.Set; import org.teavm.model.MethodReference; -class JSBodyRepository { - final Map emitters = new HashMap<>(); - final Map imports = new HashMap<>(); - final Map methodMap = new HashMap<>(); - final Set processedMethods = new HashSet<>(); - final Set inlineMethods = new HashSet<>(); - final Map callbackCallees = new HashMap<>(); - final Map> callbackMethods = new HashMap<>(); +public class JSBodyRepository { + public final Map emitters = new HashMap<>(); + public final Map imports = new HashMap<>(); + public final Map methodMap = new HashMap<>(); + public final Set processedMethods = new HashSet<>(); + public final Set inlineMethods = new HashSet<>(); + public final Map callbackCallees = new HashMap<>(); + public final Map> callbackMethods = new HashMap<>(); } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java index f072060dc..b6da7c20d 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java @@ -227,7 +227,7 @@ class JSClassProcessor { for (int i = 0; i < signature.length; ++i) { staticSignature[i + 1] = signature[i]; } - staticSignature[0] = ValueType.object(method.getClassName()); + staticSignature[0] = ValueType.object(JSObject.class.getName()); return staticSignature; } @@ -1139,7 +1139,8 @@ class JSClassProcessor { expr = body; } javaInvocationProcessor.process(location, expr); - var emitter = new JSBodyAstEmitter(isStatic, expr, rootNode, parameterNames, imports); + var emitter = new JSBodyAstEmitter(isStatic, methodToProcess.getReference(), expr, rootNode, + parameterNames, imports); repository.emitters.put(proxyMethod, emitter); } if (imports.length > 0) { diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSOPlugin.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSOPlugin.java index 1828b8f61..5ef5e7784 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSOPlugin.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSOPlugin.java @@ -16,8 +16,10 @@ package org.teavm.jso.impl; import org.teavm.backend.javascript.TeaVMJavaScriptHost; +import org.teavm.backend.wasm.gc.TeaVMWasmGCHost; import org.teavm.jso.JSExceptions; import org.teavm.jso.JSObject; +import org.teavm.jso.impl.wasmgc.WasmGCJso; import org.teavm.model.MethodReference; import org.teavm.platform.plugin.PlatformPlugin; import org.teavm.vm.TeaVMPluginUtil; @@ -29,19 +31,36 @@ import org.teavm.vm.spi.TeaVMPlugin; public class JSOPlugin implements TeaVMPlugin { @Override public void install(TeaVMHost host) { - TeaVMJavaScriptHost jsHost = host.getExtension(TeaVMJavaScriptHost.class); - if (jsHost == null) { - return; - } - JSBodyRepository repository = new JSBodyRepository(); host.registerService(JSBodyRepository.class, repository); host.add(new JSObjectClassTransformer(repository)); JSDependencyListener dependencyListener = new JSDependencyListener(repository); - JSAliasRenderer aliasRenderer = new JSAliasRenderer(); host.add(dependencyListener); host.add(new JSExceptionsDependencyListener()); + var wrapperDependency = new JSWrapperDependency(); + host.add(new MethodReference(JSWrapper.class, "jsToWrapper", JSObject.class, JSWrapper.class), + wrapperDependency); + host.add(new MethodReference(JSWrapper.class, "dependencyJavaToJs", Object.class, JSObject.class), + wrapperDependency); + host.add(new MethodReference(JSWrapper.class, "dependencyJsToJava", JSObject.class, Object.class), + wrapperDependency); + + TeaVMPluginUtil.handleNatives(host, JS.class); + + var jsHost = host.getExtension(TeaVMJavaScriptHost.class); + if (jsHost != null) { + installForJS(jsHost); + } + + var wasmGCHost = host.getExtension(TeaVMWasmGCHost.class); + if (wasmGCHost != null) { + WasmGCJso.install(host, wasmGCHost, repository); + } + } + + private void installForJS(TeaVMJavaScriptHost jsHost) { + var aliasRenderer = new JSAliasRenderer(); jsHost.add(aliasRenderer); jsHost.addGeneratorProvider(new GeneratorAnnotationInstaller<>(new JSBodyGenerator(), DynamicGenerator.class.getName())); @@ -50,7 +69,7 @@ public class JSOPlugin implements TeaVMPlugin { jsHost.addVirtualMethods(aliasRenderer); jsHost.addForcedFunctionMethods(new JSExportedMethodAsFunction()); - JSExceptionsGenerator exceptionsGenerator = new JSExceptionsGenerator(); + var exceptionsGenerator = new JSExceptionsGenerator(); jsHost.add(new MethodReference(JSExceptions.class, "getJavaException", JSObject.class, Throwable.class), exceptionsGenerator); jsHost.add(new MethodReference(JSExceptions.class, "getJSException", Throwable.class, JSObject.class), @@ -75,14 +94,5 @@ public class JSOPlugin implements TeaVMPlugin { wrapperGenerator); jsHost.add(new MethodReference(JSWrapper.class, "isJSImplementation", Object.class, boolean.class), wrapperGenerator); - - host.add(new MethodReference(JSWrapper.class, "jsToWrapper", JSObject.class, JSWrapper.class), - wrapperGenerator); - host.add(new MethodReference(JSWrapper.class, "dependencyJavaToJs", Object.class, JSObject.class), - wrapperGenerator); - host.add(new MethodReference(JSWrapper.class, "dependencyJsToJava", JSObject.class, Object.class), - wrapperGenerator); - - TeaVMPluginUtil.handleNatives(host, JS.class); } } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapperDependency.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapperDependency.java new file mode 100644 index 000000000..cbe880fde --- /dev/null +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapperDependency.java @@ -0,0 +1,47 @@ +/* + * Copyright 2023 konsoletyper. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.jso.impl; + +import org.teavm.dependency.DependencyAgent; +import org.teavm.dependency.DependencyNode; +import org.teavm.dependency.DependencyPlugin; +import org.teavm.dependency.MethodDependency; + +public class JSWrapperDependency implements DependencyPlugin { + private DependencyNode externalClassesNode; + + @Override + public void methodReached(DependencyAgent agent, MethodDependency method) { + switch (method.getMethod().getName()) { + case "jsToWrapper": + method.getResult().propagate(agent.getType(JSWrapper.class.getName())); + break; + case "dependencyJavaToJs": + method.getVariable(1).connect(getExternalClassesNode(agent)); + break; + case "dependencyJsToJava": + getExternalClassesNode(agent).connect(method.getResult()); + break; + } + } + + private DependencyNode getExternalClassesNode(DependencyAgent agent) { + if (externalClassesNode == null) { + externalClassesNode = agent.createNode(); + } + return externalClassesNode; + } +} diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JsBodyImportInfo.java b/jso/impl/src/main/java/org/teavm/jso/impl/JsBodyImportInfo.java index 3c4200b12..471bfd448 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JsBodyImportInfo.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JsBodyImportInfo.java @@ -15,11 +15,11 @@ */ package org.teavm.jso.impl; -class JsBodyImportInfo { - final String alias; - final String fromModule; +public class JsBodyImportInfo { + public final String alias; + public final String fromModule; - JsBodyImportInfo(String alias, String fromModule) { + public JsBodyImportInfo(String alias, String fromModule) { this.alias = alias; this.fromModule = fromModule; } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCBodyGenerator.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCBodyGenerator.java new file mode 100644 index 000000000..a3c3af3d3 --- /dev/null +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCBodyGenerator.java @@ -0,0 +1,108 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.jso.impl.wasmgc; + +import static org.teavm.jso.impl.wasmgc.WasmGCJSConstants.STRING_TO_JS; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import org.teavm.backend.javascript.rendering.AstWriter; +import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsicContext; +import org.teavm.backend.wasm.model.WasmFunction; +import org.teavm.backend.wasm.model.WasmGlobal; +import org.teavm.backend.wasm.model.WasmType; +import org.teavm.backend.wasm.model.expression.WasmCall; +import org.teavm.backend.wasm.model.expression.WasmGetGlobal; +import org.teavm.backend.wasm.model.expression.WasmNullConstant; +import org.teavm.backend.wasm.model.expression.WasmSetGlobal; +import org.teavm.jso.impl.JSBodyAstEmitter; +import org.teavm.jso.impl.JSBodyBloatedEmitter; +import org.teavm.jso.impl.JSBodyEmitter; + +class WasmGCBodyGenerator { + private WasmGCJSFunctions jsFunctions; + private boolean initialized; + private List> initializerParts = new ArrayList<>(); + + WasmGCBodyGenerator(WasmGCJSFunctions jsFunctions) { + this.jsFunctions = jsFunctions; + } + + private void initialize(WasmGCIntrinsicContext context) { + if (initialized) { + return; + } + initialized = true; + context.addToInitializer(this::writeToInitializer); + } + + + private void writeToInitializer(WasmFunction function) { + for (var part : initializerParts) { + part.accept(function); + } + } + + WasmGlobal addBody(WasmGCIntrinsicContext context, JSBodyEmitter emitter, boolean inlined) { + initialize(context); + var paramCount = emitter.method().parameterCount(); + if (!emitter.isStatic()) { + paramCount++; + } + var global = new WasmGlobal(context.names().suggestForMethod(emitter.method()), + WasmType.Reference.EXTERN, new WasmNullConstant(WasmType.Reference.EXTERN)); + context.module().globals.add(global); + var body = ""; + if (emitter instanceof JSBodyBloatedEmitter) { + body = ((JSBodyBloatedEmitter) emitter).script; + } else if (emitter instanceof JSBodyAstEmitter) { + var writer = new WasmGCJSBodyWriter(); + if (inlined) { + writer.sb.append("return "); + } + var astEmitter = (JSBodyAstEmitter) emitter; + var astWriter = new AstWriter(writer, name -> (w, prec) -> w.append(name)); + if (!emitter.isStatic()) { + astWriter.declareNameEmitter("this", (w, prec) -> w.append("__this__")); + } + astWriter.print(astEmitter.ast); + if (inlined) { + writer.sb.append(";"); + } + body = writer.sb.toString(); + } else { + throw new IllegalArgumentException(); + } + + var constructor = new WasmCall(jsFunctions.getFunctionConstructor(context, + paramCount)); + var stringToJs = context.functions().forStaticMethod(STRING_TO_JS); + var paramNames = new ArrayList(); + if (!emitter.isStatic()) { + paramNames.add("__this__"); + } + paramNames.addAll(List.of(emitter.parameterNames())); + for (var parameter : paramNames) { + var paramName = new WasmGetGlobal(context.strings().getStringConstant(parameter).global); + constructor.getArguments().add(new WasmCall(stringToJs, paramName)); + } + var functionBody = new WasmGetGlobal(context.strings().getStringConstant(body).global); + constructor.getArguments().add(new WasmCall(stringToJs, functionBody)); + initializerParts.add(initializer -> initializer.getBody().add(new WasmSetGlobal(global, constructor))); + + return global; + } +} diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCBodyIntrinsic.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCBodyIntrinsic.java new file mode 100644 index 000000000..ee4d0ad76 --- /dev/null +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCBodyIntrinsic.java @@ -0,0 +1,54 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.jso.impl.wasmgc; + +import org.teavm.ast.InvocationExpr; +import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsic; +import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsicContext; +import org.teavm.backend.wasm.model.WasmGlobal; +import org.teavm.backend.wasm.model.expression.WasmCall; +import org.teavm.backend.wasm.model.expression.WasmExpression; +import org.teavm.backend.wasm.model.expression.WasmGetGlobal; +import org.teavm.jso.impl.JSBodyEmitter; + +class WasmGCBodyIntrinsic implements WasmGCIntrinsic { + private JSBodyEmitter emitter; + private boolean inlined; + private WasmGCBodyGenerator bodyGenerator; + private WasmGlobal global; + private WasmGCJSFunctions jsFunctions; + + WasmGCBodyIntrinsic(JSBodyEmitter emitter, boolean inlined, WasmGCBodyGenerator bodyGenerator, + WasmGCJSFunctions jsFunctions) { + this.emitter = emitter; + this.inlined = inlined; + this.bodyGenerator = bodyGenerator; + this.jsFunctions = jsFunctions; + } + + @Override + public WasmExpression apply(InvocationExpr invocation, WasmGCIntrinsicContext context) { + if (global == null) { + global = bodyGenerator.addBody(context, emitter, inlined); + } + var call = new WasmCall(jsFunctions.getFunctionCaller(context, invocation.getArguments().size())); + call.getArguments().add(new WasmGetGlobal(global)); + for (var arg : invocation.getArguments()) { + call.getArguments().add(context.generate(arg)); + } + return call; + } +} diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSBodyRenderer.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSBodyRenderer.java new file mode 100644 index 000000000..c22e341a9 --- /dev/null +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSBodyRenderer.java @@ -0,0 +1,44 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.jso.impl.wasmgc; + +import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsic; +import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsicFactory; +import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsicFactoryContext; +import org.teavm.jso.impl.JSBodyRepository; +import org.teavm.model.MethodReference; + +class WasmGCJSBodyRenderer implements WasmGCIntrinsicFactory { + private JSBodyRepository repository; + private WasmGCJSFunctions jsFunctions; + private WasmGCBodyGenerator bodyGenerator; + + WasmGCJSBodyRenderer(JSBodyRepository repository) { + this.repository = repository; + jsFunctions = new WasmGCJSFunctions(); + bodyGenerator = new WasmGCBodyGenerator(jsFunctions); + } + + @Override + public WasmGCIntrinsic createIntrinsic(MethodReference methodRef, WasmGCIntrinsicFactoryContext context) { + var emitter = repository.emitters.get(methodRef); + if (emitter == null) { + return null; + } + var inlined = repository.inlineMethods.contains(emitter.method()); + return new WasmGCBodyIntrinsic(emitter, inlined, bodyGenerator, jsFunctions); + } +} diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSBodyWriter.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSBodyWriter.java new file mode 100644 index 000000000..87351a625 --- /dev/null +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSBodyWriter.java @@ -0,0 +1,188 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.jso.impl.wasmgc; + +import org.teavm.backend.javascript.codegen.SourceWriter; +import org.teavm.model.FieldReference; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodReference; + +class WasmGCJSBodyWriter extends SourceWriter { + final StringBuilder sb = new StringBuilder(); + + @Override + public SourceWriter append(char value) { + sb.append(value); + return this; + } + + @Override + public SourceWriter append(CharSequence csq, int start, int end) { + sb.append(csq, start, end); + return this; + } + + @Override + public SourceWriter appendClass(String cls) { + return this; + } + + @Override + public SourceWriter appendField(FieldReference field) { + return this; + } + + @Override + public SourceWriter appendStaticField(FieldReference field) { + return this; + } + + @Override + public SourceWriter appendVirtualMethod(MethodDescriptor method) { + return this; + } + + @Override + public SourceWriter appendMethod(MethodReference method) { + return this; + } + + @Override + public SourceWriter appendFunction(String name) { + return this; + } + + @Override + public SourceWriter startFunctionDeclaration() { + return this; + } + + @Override + public SourceWriter startVariableDeclaration() { + return this; + } + + @Override + public SourceWriter endDeclaration() { + return this; + } + + @Override + public SourceWriter declareVariable() { + return this; + } + + @Override + public SourceWriter appendGlobal(String name) { + sb.append(name); + return this; + } + + @Override + public SourceWriter appendInit(MethodReference method) { + return this; + } + + @Override + public SourceWriter appendClassInit(String className) { + return this; + } + + @Override + public SourceWriter newLine() { + sb.append('\n'); + return this; + } + + @Override + public SourceWriter ws() { + return this; + } + + @Override + public SourceWriter sameLineWs() { + return this; + } + + @Override + public SourceWriter tokenBoundary() { + return this; + } + + @Override + public SourceWriter softNewLine() { + return this; + } + + @Override + public SourceWriter indent() { + return this; + } + + @Override + public SourceWriter outdent() { + return this; + } + + @Override + public SourceWriter emitLocation(String fileName, int line) { + return this; + } + + @Override + public SourceWriter enterLocation() { + return this; + } + + @Override + public SourceWriter exitLocation() { + return this; + } + + @Override + public SourceWriter emitStatementStart() { + return this; + } + + @Override + public SourceWriter emitVariables(String[] names, String jsName) { + return this; + } + + @Override + public void emitMethod(MethodDescriptor method) { + } + + @Override + public void emitClass(String className) { + } + + @Override + public void markClassStart(String className) { + } + + @Override + public void markClassEnd() { + } + + @Override + public void markSectionStart(int id) { + } + + @Override + public void markSectionEnd() { + } +} diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSConstants.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSConstants.java new file mode 100644 index 000000000..c1d154865 --- /dev/null +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSConstants.java @@ -0,0 +1,29 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.jso.impl.wasmgc; + +import org.teavm.jso.JSObject; +import org.teavm.model.MethodReference; + +final class WasmGCJSConstants { + private WasmGCJSConstants() { + } + + static final MethodReference STRING_TO_JS = new MethodReference(WasmGCJSRuntime.class, + "stringToJs", String.class, JSObject.class); + static final MethodReference JS_TO_STRING = new MethodReference(WasmGCJSRuntime.class, + "jsToString", JSObject.class, String.class); +} diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSDependencies.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSDependencies.java new file mode 100644 index 000000000..df7a8512e --- /dev/null +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSDependencies.java @@ -0,0 +1,34 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.jso.impl.wasmgc; + +import static org.teavm.jso.impl.wasmgc.WasmGCJSConstants.JS_TO_STRING; +import static org.teavm.jso.impl.wasmgc.WasmGCJSConstants.STRING_TO_JS; +import org.teavm.dependency.AbstractDependencyListener; +import org.teavm.dependency.DependencyAgent; + +class WasmGCJSDependencies extends AbstractDependencyListener { + @Override + public void started(DependencyAgent agent) { + agent.linkMethod(STRING_TO_JS) + .propagate(1, agent.getType("java.lang.String")) + .use(); + + var jsToString = agent.linkMethod(JS_TO_STRING); + jsToString.getResult().propagate(agent.getType("java.lang.String")); + jsToString.use(); + } +} 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 new file mode 100644 index 000000000..e73713940 --- /dev/null +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSFunctions.java @@ -0,0 +1,61 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.jso.impl.wasmgc; + +import java.util.Arrays; +import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsicContext; +import org.teavm.backend.wasm.model.WasmFunction; +import org.teavm.backend.wasm.model.WasmType; + +class WasmGCJSFunctions { + private WasmFunction[] constructors = new WasmFunction[32]; + private WasmFunction[] callers = new WasmFunction[32]; + + WasmFunction getFunctionConstructor(WasmGCIntrinsicContext context, int index) { + var function = constructors[index]; + if (function == null) { + var extern = WasmType.SpecialReferenceKind.EXTERN.asNonNullType(); + var constructorParamTypes = new WasmType[index + 1]; + Arrays.fill(constructorParamTypes, extern); + var functionType = context.functionTypes().of(extern, constructorParamTypes); + function = new WasmFunction(functionType); + function.setName(context.names().topLevel("teavm.js:createFunction" + index)); + function.setImportModule("teavmJso"); + function.setImportName("createFunction" + index); + context.module().functions.add(function); + constructors[index] = function; + } + return function; + } + + WasmFunction getFunctionCaller(WasmGCIntrinsicContext context, int index) { + var function = callers[index]; + if (function == null) { + var extern = WasmType.SpecialReferenceKind.EXTERN.asNonNullType(); + var paramTypes = new WasmType[index + 1]; + Arrays.fill(paramTypes, extern); + paramTypes[0] = WasmType.Reference.EXTERN; + var functionType = context.functionTypes().of(extern, paramTypes); + function = new WasmFunction(functionType); + function.setName(context.names().topLevel("teavm.js:callFunction" + index)); + function.setImportModule("teavmJso"); + function.setImportName("callFunction" + index); + context.module().functions.add(function); + callers[index] = function; + } + return function; + } +} 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 new file mode 100644 index 000000000..05c91a800 --- /dev/null +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSIntrinsic.java @@ -0,0 +1,42 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.jso.impl.wasmgc; + +import static org.teavm.jso.impl.wasmgc.WasmGCJSConstants.JS_TO_STRING; +import static org.teavm.jso.impl.wasmgc.WasmGCJSConstants.STRING_TO_JS; +import org.teavm.ast.InvocationExpr; +import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsic; +import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsicContext; +import org.teavm.backend.wasm.model.expression.WasmCall; +import org.teavm.backend.wasm.model.expression.WasmExpression; + +class WasmGCJSIntrinsic implements WasmGCIntrinsic { + @Override + public WasmExpression apply(InvocationExpr invocation, WasmGCIntrinsicContext context) { + switch (invocation.getMethod().getName()) { + case "wrap": { + var function = context.functions().forStaticMethod(STRING_TO_JS); + return new WasmCall(function, context.generate(invocation.getArguments().get(0))); + } + case "unwrapString": { + var function = context.functions().forStaticMethod(JS_TO_STRING); + return new WasmCall(function, context.generate(invocation.getArguments().get(0))); + } + default: + throw new IllegalArgumentException(); + } + } +} diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSRuntime.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSRuntime.java new file mode 100644 index 000000000..9ad78bc4d --- /dev/null +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSRuntime.java @@ -0,0 +1,68 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.jso.impl.wasmgc; + +import org.teavm.interop.Import; +import org.teavm.jso.JSObject; + +final class WasmGCJSRuntime { + private WasmGCJSRuntime() { + } + + static JSObject stringToJs(String str) { + if (str.isEmpty()) { + return emptyString(); + } + var jsStr = stringFromCharCode(str.charAt(0)); + for (var i = 1; i < str.length(); ++i) { + jsStr = concatStrings(jsStr, stringFromCharCode(str.charAt(i))); + } + return jsStr; + } + + static String jsToString(JSObject obj) { + var length = stringLength(obj); + if (length == 0) { + return ""; + } + var chars = new char[length]; + for (var i = 0; i < length; ++i) { + chars[i] = charAt(obj, i); + } + return new String(chars); + } + + @Import(name = "emptyString", module = "teavmJso") + static native JSObject emptyString(); + + @Import(name = "stringFromCharCode", module = "teavmJso") + static native JSObject stringFromCharCode(char c); + + @Import(name = "concatStrings", module = "teavmJso") + static native JSObject concatStrings(JSObject a, JSObject b); + + @Import(name = "emptyArray", module = "teavmJso") + static native JSObject emptyArray(); + + @Import(name = "appendToArray", module = "teavmJso") + static native JSObject appendToArray(JSObject array, JSObject element); + + @Import(name = "stringLength", module = "teavmJso") + static native int stringLength(JSObject str); + + @Import(name = "charAt", module = "teavmJso") + static native char charAt(JSObject str, int index); +} diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSTypeMapper.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSTypeMapper.java new file mode 100644 index 000000000..24e359af4 --- /dev/null +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSTypeMapper.java @@ -0,0 +1,37 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.jso.impl.wasmgc; + +import org.teavm.backend.wasm.generate.gc.classes.WasmGCCustomTypeMapper; +import org.teavm.backend.wasm.generate.gc.classes.WasmGCCustomTypeMapperFactory; +import org.teavm.backend.wasm.generate.gc.classes.WasmGCCustomTypeMapperFactoryContext; +import org.teavm.backend.wasm.model.WasmType; +import org.teavm.jso.JSObject; + +class WasmGCJSTypeMapper implements WasmGCCustomTypeMapper, WasmGCCustomTypeMapperFactory { + @Override + public WasmType map(String className) { + if (className.equals(JSObject.class.getName())) { + return WasmType.SpecialReferenceKind.EXTERN.asNonNullType(); + } + return null; + } + + @Override + public WasmGCCustomTypeMapper createTypeMapper(WasmGCCustomTypeMapperFactoryContext context) { + return this; + } +} 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 new file mode 100644 index 000000000..7ba1ce328 --- /dev/null +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJso.java @@ -0,0 +1,39 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.jso.impl.wasmgc; + +import org.teavm.backend.wasm.gc.TeaVMWasmGCHost; +import org.teavm.jso.JSObject; +import org.teavm.jso.impl.JS; +import org.teavm.jso.impl.JSBodyRepository; +import org.teavm.model.MethodReference; +import org.teavm.vm.spi.TeaVMHost; + +public final class WasmGCJso { + private WasmGCJso() { + } + + public static void install(TeaVMHost host, TeaVMWasmGCHost wasmGCHost, JSBodyRepository jsBodyRepository) { + host.add(new WasmGCJSDependencies()); + wasmGCHost.addCustomTypeMapperFactory(new WasmGCJSTypeMapper()); + wasmGCHost.addIntrinsicFactory(new WasmGCJSBodyRenderer(jsBodyRepository)); + + var jsIntrinsic = new WasmGCJSIntrinsic(); + wasmGCHost.addIntrinsic(new MethodReference(JS.class, "wrap", String.class, JSObject.class), jsIntrinsic); + wasmGCHost.addIntrinsic(new MethodReference(JS.class, "unwrapString", JSObject.class, String.class), + jsIntrinsic); + } +} diff --git a/tests/src/test/java/org/teavm/jso/test/AnnotationsTest.java b/tests/src/test/java/org/teavm/jso/test/AnnotationsTest.java index e1834de3d..6a3c75aee 100644 --- a/tests/src/test/java/org/teavm/jso/test/AnnotationsTest.java +++ b/tests/src/test/java/org/teavm/jso/test/AnnotationsTest.java @@ -30,7 +30,7 @@ import org.teavm.junit.TestPlatform; @RunWith(TeaVMTestRunner.class) @SkipJVM -@OnlyPlatform(TestPlatform.JAVASCRIPT) +@OnlyPlatform({TestPlatform.JAVASCRIPT, TestPlatform.WEBASSEMBLY_GC}) @EachTestCompiledSeparately public class AnnotationsTest { @Test diff --git a/tests/src/test/java/org/teavm/jso/test/ConversionTest.java b/tests/src/test/java/org/teavm/jso/test/ConversionTest.java index 0e1e92afb..e32029e97 100644 --- a/tests/src/test/java/org/teavm/jso/test/ConversionTest.java +++ b/tests/src/test/java/org/teavm/jso/test/ConversionTest.java @@ -29,12 +29,13 @@ import org.teavm.jso.core.JSString; import org.teavm.junit.EachTestCompiledSeparately; import org.teavm.junit.OnlyPlatform; import org.teavm.junit.SkipJVM; +import org.teavm.junit.SkipPlatform; import org.teavm.junit.TeaVMTestRunner; import org.teavm.junit.TestPlatform; @RunWith(TeaVMTestRunner.class) @SkipJVM -@OnlyPlatform(TestPlatform.JAVASCRIPT) +@OnlyPlatform({TestPlatform.JAVASCRIPT, TestPlatform.WEBASSEMBLY_GC}) @EachTestCompiledSeparately public class ConversionTest { @Test @@ -57,6 +58,7 @@ public class ConversionTest { } @Test + @SkipPlatform(TestPlatform.WEBASSEMBLY_GC) public void convertsPrimitiveArraysToJavaScript() { assertEquals("true:2:3:64:4:5.5:6.5:foo", combinePrimitiveArrays(new boolean[] { true }, new byte[] { 2 }, new short[] { 3 }, new char[] { '@' }, new int[] { 4 }, new float[] { 5.5F }, new double[] { 6.5 }, @@ -64,6 +66,7 @@ public class ConversionTest { } @Test + @SkipPlatform(TestPlatform.WEBASSEMBLY_GC) public void convertsPrimitiveArraysToJava() { PrimitiveArrays arrays = getPrimitiveArrays(); @@ -81,6 +84,7 @@ public class ConversionTest { } @Test + @SkipPlatform(TestPlatform.WEBASSEMBLY_GC) public void convertsPrimitiveArrays2ToJavaScript() { assertEquals("true:2:3:64:4:5.5:6.5:foo", combinePrimitiveArrays2(new boolean[][] {{ true }}, new byte[][] {{ 2 }}, new short[][] {{ 3 }}, new char[][] {{ '@' }}, new int[][] {{ 4 }}, @@ -88,6 +92,7 @@ public class ConversionTest { } @Test + @SkipPlatform(TestPlatform.WEBASSEMBLY_GC) public void convertsPrimitiveArrays2ToJava() { PrimitiveArrays2 arrays = getPrimitiveArrays2(); @@ -106,6 +111,7 @@ public class ConversionTest { } @Test + @SkipPlatform(TestPlatform.WEBASSEMBLY_GC) public void convertsPrimitiveArrays4ToJavaScript() { assertEquals("true:2:3:64:4:5.5:6.5:foo", combinePrimitiveArrays4(new boolean[][][][] {{{{ true }}}}, new byte[][][][] {{{{ 2 }}}}, new short[][][][] {{{{ 3 }}}}, new char[][][][] {{{{ '@' }}}}, @@ -114,6 +120,7 @@ public class ConversionTest { } @Test + @SkipPlatform(TestPlatform.WEBASSEMBLY_GC) public void convertsPrimitiveArrays4ToJava() { PrimitiveArrays4 arrays = getPrimitiveArrays4(); @@ -137,6 +144,7 @@ public class ConversionTest { } @Test + @SkipPlatform(TestPlatform.WEBASSEMBLY_GC) public void convertsArrayOfJSObject() { assertEquals("(foo)", surround(new JSString[] { JSString.valueOf("foo") })[0].stringValue()); assertEquals("(foo)", surround(new JSString[][] {{ JSString.valueOf("foo") }})[0][0].stringValue()); @@ -145,6 +153,7 @@ public class ConversionTest { } @Test + @SkipPlatform(TestPlatform.WEBASSEMBLY_GC) public void copiesArray() { int[] array = { 23 }; assertEquals(24, mutate(array)); @@ -152,6 +161,7 @@ public class ConversionTest { } @Test + @SkipPlatform(TestPlatform.WEBASSEMBLY_GC) public void passesArrayByRef() { int[] array = { 23, 42 }; @@ -165,6 +175,7 @@ public class ConversionTest { } @Test + @SkipPlatform(TestPlatform.WEBASSEMBLY_GC) public void returnsArrayByRef() { int[] first = { 23, 42 }; int[] second = rewrap(first); diff --git a/tests/src/test/java/org/teavm/jso/test/ImplementationTest.java b/tests/src/test/java/org/teavm/jso/test/ImplementationTest.java index fdde8da67..464a79cd5 100644 --- a/tests/src/test/java/org/teavm/jso/test/ImplementationTest.java +++ b/tests/src/test/java/org/teavm/jso/test/ImplementationTest.java @@ -23,12 +23,13 @@ import org.teavm.jso.JSObject; import org.teavm.junit.EachTestCompiledSeparately; import org.teavm.junit.OnlyPlatform; import org.teavm.junit.SkipJVM; +import org.teavm.junit.SkipPlatform; import org.teavm.junit.TeaVMTestRunner; import org.teavm.junit.TestPlatform; @RunWith(TeaVMTestRunner.class) @SkipJVM -@OnlyPlatform(TestPlatform.JAVASCRIPT) +@OnlyPlatform({TestPlatform.JAVASCRIPT, TestPlatform.WEBASSEMBLY_GC}) @EachTestCompiledSeparately public class ImplementationTest { @Test @@ -44,6 +45,7 @@ public class ImplementationTest { static native int mul(int a, int b); @Test + @SkipPlatform(TestPlatform.WEBASSEMBLY_GC) public void inliningUsageCounterWorksProperly() { ForInliningTest instance = ForInliningTest.create(); wrongInlineCandidate(instance.foo());