diff --git a/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyCodeListener.java b/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyCodeListener.java index 2fe2cc975..11c1785ce 100644 --- a/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyCodeListener.java +++ b/core/src/main/java/org/teavm/backend/wasm/disasm/DisassemblyCodeListener.java @@ -188,6 +188,12 @@ public class DisassemblyCodeListener extends BaseDisassemblyListener implements case IS_NULL: writer.write("ref.is_null"); break; + case EXTERN_TO_ANY: + writer.write("any.convert_extern"); + break; + case ANY_TO_EXTERN: + writer.write("extern.convert_any"); + break; } writer.eol(); } diff --git a/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmDefaultExpressionVisitor.java b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmDefaultExpressionVisitor.java index 2e14c2b2e..d73644a47 100644 --- a/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmDefaultExpressionVisitor.java +++ b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmDefaultExpressionVisitor.java @@ -277,6 +277,11 @@ public class WasmDefaultExpressionVisitor implements WasmExpressionVisitor { expression.getValue().acceptVisitor(this); } + @Override + public void visit(WasmExternConversion expression) { + expression.getValue().acceptVisitor(this); + } + @Override public void visit(WasmStructNew expression) { for (var initializer : expression.getInitializers()) { diff --git a/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmExpressionVisitor.java b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmExpressionVisitor.java index e1e512a75..c6358d9e6 100644 --- a/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmExpressionVisitor.java +++ b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmExpressionVisitor.java @@ -102,6 +102,8 @@ public interface WasmExpressionVisitor { void visit(WasmCast expression); + void visit(WasmExternConversion expression); + void visit(WasmTest expression); void visit(WasmStructNew expression); diff --git a/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmExternConversion.java b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmExternConversion.java index bdd4e27f8..163db071b 100644 --- a/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmExternConversion.java +++ b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmExternConversion.java @@ -15,5 +15,35 @@ */ package org.teavm.backend.wasm.model.expression; -public class WasmExternConversion { +import java.util.Objects; + +public class WasmExternConversion extends WasmExpression { + private WasmExternConversionType type; + private WasmExpression value; + + public WasmExternConversion(WasmExternConversionType type, WasmExpression value) { + this.type = Objects.requireNonNull(type); + this.value = Objects.requireNonNull(value); + } + + public WasmExternConversionType getType() { + return type; + } + + public void setType(WasmExternConversionType type) { + this.type = Objects.requireNonNull(type); + } + + public WasmExpression getValue() { + return value; + } + + public void setValue(WasmExpression value) { + this.value = Objects.requireNonNull(value); + } + + @Override + public void acceptVisitor(WasmExpressionVisitor visitor) { + visitor.visit(this); + } } diff --git a/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmExternConversionType.java b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmExternConversionType.java index 379ad8b22..3a394e26f 100644 --- a/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmExternConversionType.java +++ b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmExternConversionType.java @@ -16,6 +16,6 @@ package org.teavm.backend.wasm.model.expression; public enum WasmExternConversionType { - EXTERN_TO_OBJECT, - OBJECT_TO_EXTERN + EXTERN_TO_ANY, + ANY_TO_EXTERN } diff --git a/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmReplacingExpressionVisitor.java b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmReplacingExpressionVisitor.java index 679c98d84..801fafd3b 100644 --- a/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmReplacingExpressionVisitor.java +++ b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmReplacingExpressionVisitor.java @@ -22,8 +22,7 @@ import org.teavm.backend.wasm.model.WasmFunction; public class WasmReplacingExpressionVisitor implements WasmExpressionVisitor { private Function mapper; - public WasmReplacingExpressionVisitor( - Function mapper) { + public WasmReplacingExpressionVisitor(Function mapper) { this.mapper = mapper; } @@ -332,6 +331,12 @@ public class WasmReplacingExpressionVisitor implements WasmExpressionVisitor { expression.setValue(mapper.apply(expression.getValue())); } + @Override + public void visit(WasmExternConversion expression) { + expression.getValue().acceptVisitor(this); + expression.setValue(mapper.apply(expression.getValue())); + } + @Override public void visit(WasmStructNew expression) { replaceExpressions(expression.getInitializers()); diff --git a/core/src/main/java/org/teavm/backend/wasm/parser/CodeParser.java b/core/src/main/java/org/teavm/backend/wasm/parser/CodeParser.java index b97a76029..ec9766862 100644 --- a/core/src/main/java/org/teavm/backend/wasm/parser/CodeParser.java +++ b/core/src/main/java/org/teavm/backend/wasm/parser/CodeParser.java @@ -719,6 +719,13 @@ public class CodeParser extends BaseSectionParser { parseCastBranch(false); return true; + case 26: + codeListener.opcode(Opcode.EXTERN_TO_ANY); + return true; + case 27: + codeListener.opcode(Opcode.ANY_TO_EXTERN); + return true; + case 28: codeListener.int31Reference(); return true; diff --git a/core/src/main/java/org/teavm/backend/wasm/parser/Opcode.java b/core/src/main/java/org/teavm/backend/wasm/parser/Opcode.java index 4cca9d0d2..c324a7cf3 100644 --- a/core/src/main/java/org/teavm/backend/wasm/parser/Opcode.java +++ b/core/src/main/java/org/teavm/backend/wasm/parser/Opcode.java @@ -22,5 +22,7 @@ public enum Opcode { DROP, REF_EQ, ARRAY_LENGTH, - IS_NULL + IS_NULL, + ANY_TO_EXTERN, + EXTERN_TO_ANY } diff --git a/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderingVisitor.java b/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderingVisitor.java index 5ce43a9a7..d2682c930 100644 --- a/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderingVisitor.java +++ b/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderingVisitor.java @@ -47,6 +47,7 @@ import org.teavm.backend.wasm.model.expression.WasmDefaultExpressionVisitor; import org.teavm.backend.wasm.model.expression.WasmDrop; import org.teavm.backend.wasm.model.expression.WasmExpression; import org.teavm.backend.wasm.model.expression.WasmExpressionVisitor; +import org.teavm.backend.wasm.model.expression.WasmExternConversion; import org.teavm.backend.wasm.model.expression.WasmFill; import org.teavm.backend.wasm.model.expression.WasmFloat32Constant; import org.teavm.backend.wasm.model.expression.WasmFloat64Constant; @@ -1171,6 +1172,22 @@ class WasmBinaryRenderingVisitor implements WasmExpressionVisitor { popLocation(); } + @Override + public void visit(WasmExternConversion expression) { + pushLocation(expression); + expression.getValue().acceptVisitor(this); + writer.writeByte(0xfb); + switch (expression.getType()) { + case EXTERN_TO_ANY: + writer.writeByte(26); + break; + case ANY_TO_EXTERN: + writer.writeByte(27); + break; + } + popLocation(); + } + @Override public void visit(WasmStructNew expression) { pushLocation(expression); diff --git a/core/src/main/java/org/teavm/backend/wasm/render/WasmCRenderingVisitor.java b/core/src/main/java/org/teavm/backend/wasm/render/WasmCRenderingVisitor.java index 5e1a7a064..264f939b3 100644 --- a/core/src/main/java/org/teavm/backend/wasm/render/WasmCRenderingVisitor.java +++ b/core/src/main/java/org/teavm/backend/wasm/render/WasmCRenderingVisitor.java @@ -46,6 +46,7 @@ import org.teavm.backend.wasm.model.expression.WasmCopy; import org.teavm.backend.wasm.model.expression.WasmDrop; import org.teavm.backend.wasm.model.expression.WasmExpression; import org.teavm.backend.wasm.model.expression.WasmExpressionVisitor; +import org.teavm.backend.wasm.model.expression.WasmExternConversion; import org.teavm.backend.wasm.model.expression.WasmFill; import org.teavm.backend.wasm.model.expression.WasmFloat32Constant; import org.teavm.backend.wasm.model.expression.WasmFloat64Constant; @@ -1197,6 +1198,11 @@ class WasmCRenderingVisitor implements WasmExpressionVisitor { unsupported(); } + @Override + public void visit(WasmExternConversion expression) { + unsupported(); + } + @Override public void visit(WasmStructNew expression) { unsupported(); diff --git a/core/src/main/java/org/teavm/backend/wasm/render/WasmRenderingVisitor.java b/core/src/main/java/org/teavm/backend/wasm/render/WasmRenderingVisitor.java index d31fb0de8..e07182e1f 100644 --- a/core/src/main/java/org/teavm/backend/wasm/render/WasmRenderingVisitor.java +++ b/core/src/main/java/org/teavm/backend/wasm/render/WasmRenderingVisitor.java @@ -43,6 +43,7 @@ import org.teavm.backend.wasm.model.expression.WasmDefaultExpressionVisitor; import org.teavm.backend.wasm.model.expression.WasmDrop; import org.teavm.backend.wasm.model.expression.WasmExpression; import org.teavm.backend.wasm.model.expression.WasmExpressionVisitor; +import org.teavm.backend.wasm.model.expression.WasmExternConversion; import org.teavm.backend.wasm.model.expression.WasmFill; import org.teavm.backend.wasm.model.expression.WasmFloat32Constant; import org.teavm.backend.wasm.model.expression.WasmFloat64Constant; @@ -772,6 +773,21 @@ class WasmRenderingVisitor implements WasmExpressionVisitor { close(); } + @Override + public void visit(WasmExternConversion expression) { + open(); + switch (expression.getType()) { + case EXTERN_TO_ANY: + append("any.convert_extern"); + break; + case ANY_TO_EXTERN: + append("extern.convert_any"); + break; + } + line(expression.getValue()); + close(); + } + @Override public void visit(WasmStructNew expression) { open().append("struct.new "); diff --git a/core/src/main/java/org/teavm/backend/wasm/render/WasmTypeInference.java b/core/src/main/java/org/teavm/backend/wasm/render/WasmTypeInference.java index 2544f0318..5929bfada 100644 --- a/core/src/main/java/org/teavm/backend/wasm/render/WasmTypeInference.java +++ b/core/src/main/java/org/teavm/backend/wasm/render/WasmTypeInference.java @@ -34,6 +34,7 @@ import org.teavm.backend.wasm.model.expression.WasmConversion; import org.teavm.backend.wasm.model.expression.WasmCopy; import org.teavm.backend.wasm.model.expression.WasmDrop; import org.teavm.backend.wasm.model.expression.WasmExpressionVisitor; +import org.teavm.backend.wasm.model.expression.WasmExternConversion; import org.teavm.backend.wasm.model.expression.WasmFill; import org.teavm.backend.wasm.model.expression.WasmFloat32Constant; import org.teavm.backend.wasm.model.expression.WasmFloat64Constant; @@ -317,6 +318,18 @@ public class WasmTypeInference implements WasmExpressionVisitor { result = WasmType.INT32; } + @Override + public void visit(WasmExternConversion expression) { + switch (expression.getType()) { + case EXTERN_TO_ANY: + result = WasmType.Reference.ANY; + break; + case ANY_TO_EXTERN: + result = WasmType.Reference.EXTERN; + break; + } + } + @Override public void visit(WasmStructNew expression) { result = expression.getType().getReference(); diff --git a/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java b/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java index 94bfb7a85..fa8921c9f 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java +++ b/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java @@ -278,7 +278,6 @@ public abstract class DependencyAnalyzer implements DependencyInfo { public void addDependencyListener(DependencyListener listener) { listeners.add(listener); - listener.started(agent); } public void addClassTransformer(ClassHolderTransformer transformer) { @@ -663,6 +662,12 @@ public abstract class DependencyAnalyzer implements DependencyInfo { } } + public void initDependencies() { + for (var listener : listeners) { + listener.started(agent); + } + } + public void processDependencies() { interrupted = false; processQueue(); diff --git a/core/src/main/java/org/teavm/model/instructions/BranchingInstruction.java b/core/src/main/java/org/teavm/model/instructions/BranchingInstruction.java index 797b35579..de30fdebc 100644 --- a/core/src/main/java/org/teavm/model/instructions/BranchingInstruction.java +++ b/core/src/main/java/org/teavm/model/instructions/BranchingInstruction.java @@ -41,6 +41,10 @@ public class BranchingInstruction extends Instruction { return condition; } + public void setCondition(BranchingCondition condition) { + this.condition = condition; + } + public BasicBlock getConsequent() { return consequent; } diff --git a/core/src/main/java/org/teavm/vm/TeaVM.java b/core/src/main/java/org/teavm/vm/TeaVM.java index 92a7ddcf8..b50e1c5b6 100644 --- a/core/src/main/java/org/teavm/vm/TeaVM.java +++ b/core/src/main/java/org/teavm/vm/TeaVM.java @@ -318,11 +318,11 @@ public class TeaVM implements TeaVMHost, ServiceRepository { return; } - var mainMethod = cls.getMethod(MAIN_METHOD_DESC) != null - ? dependencyAnalyzer.linkMethod(new MethodReference(entryPoint, - "main", ValueType.parse(String[].class), ValueType.VOID)) - : null; dependencyAnalyzer.defer(() -> { + var mainMethod = cls.getMethod(MAIN_METHOD_DESC) != null + ? dependencyAnalyzer.linkMethod(new MethodReference(entryPoint, + "main", ValueType.parse(String[].class), ValueType.VOID)) + : null; dependencyAnalyzer.linkClass(entryPoint).initClass(null); if (mainMethod != null) { mainMethod.getVariable(1).propagate(dependencyAnalyzer.getType("[Ljava/lang/String;")); @@ -388,6 +388,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository { cancelled |= progressListener.progressReached(progress) != TeaVMProgressFeedback.CONTINUE; return !cancelled; }); + dependencyAnalyzer.initDependencies(); target.contributeDependencies(dependencyAnalyzer); if (target.needsSystemArrayCopyOptimization()) { dependencyAnalyzer.addDependencyListener(new StdlibDependencyListener()); 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 7e71d7bcf..1daabcfa4 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 @@ -20,21 +20,16 @@ TeaVM.wasm = function() { let getGlobalName = function(name) { return eval(name); } - let javaObjectSymbol = Symbol("javaObject"); - let functionsSymbol = Symbol("functions"); - let functionOriginSymbol = Symbol("functionOrigin"); - let javaWrappers = new WeakMap(); + function defaults(imports) { - let stderr = ""; - let stdout = ""; - let finalizationRegistry = new FinalizationRegistry(heldValue => { - if (typeof exports.reportGarbageCollectedValue === "function") { - exports.reportGarbageCollectedValue(heldValue) - } - }); - let stringFinalizationRegistry = new FinalizationRegistry(heldValue => { - exports.reportGarbageCollectedString(heldValue); - }); + dateImports(imports); + consoleImports(imports); + coreImports(imports); + jsoImports(imports); + imports.teavmMath = Math; + } + + function dateImports(imports) { imports.teavmDate = { currentTimeMillis: () => new Date().getTime(), dateToString: timestamp => stringToJava(new Date(timestamp).toString()), @@ -59,6 +54,11 @@ TeaVM.wasm = function() { create: (year, month, date, hrs, min, sec) => new Date(year, month, date, hrs, min, sec).getTime(), createFromUTC: (year, month, date, hrs, min, sec) => Date.UTC(year, month, date, hrs, min, sec) }; + } + + function consoleImports(imports) { + let stderr = ""; + let stdout = ""; imports.teavmConsole = { putcharStderr(c) { if (c === 10) { @@ -77,6 +77,9 @@ TeaVM.wasm = function() { } }, }; + } + + function coreImports(imports) { imports.teavm = { createWeakRef(value, heldValue) { let weakRef = new WeakRef(value); @@ -93,6 +96,37 @@ TeaVM.wasm = function() { }, stringDeref: weakRef => weakRef.deref() }; + } + + function jsoImports(imports) { + new FinalizationRegistry(heldValue => { + if (typeof exports.reportGarbageCollectedValue === "function") { + exports.reportGarbageCollectedValue(heldValue) + } + }); + new FinalizationRegistry(heldValue => { + exports.reportGarbageCollectedString(heldValue); + }); + + let javaObjectSymbol = Symbol("javaObject"); + let functionsSymbol = Symbol("functions"); + let functionOriginSymbol = Symbol("functionOrigin"); + + let jsWrappers = new WeakMap(); + let javaWrappers = new WeakMap(); + let primitiveWrappers = new Map(); + let primitiveFinalization = new FinalizationRegistry(token => primitiveFinalization.delete(token)); + let hashCodes = new WeakMap(); + let lastHashCode = 2463534242; + let nextHashCode = () => { + let x = lastHashCode; + x ^= x << 13; + x ^= x >>> 17; + x ^= x << 5; + lastHashCode = x; + return x; + } + function identity(value) { return value; } @@ -157,7 +191,7 @@ TeaVM.wasm = function() { Object.defineProperty(cls.prototype, name, descriptor); }, javaObjectToJS(instance, cls) { - let existing = javaWrappers.get(instance); + let existing = jsWrappers.get(instance); if (typeof existing != "undefined") { let result = existing.deref(); if (typeof result !== "undefined") { @@ -165,7 +199,7 @@ TeaVM.wasm = function() { } } let obj = new cls(instance); - javaWrappers.set(instance, new WeakRef(obj)); + jsWrappers.set(instance, new WeakRef(obj)); return obj; }, unwrapJavaObject(instance) { @@ -196,10 +230,64 @@ TeaVM.wasm = function() { } } return { [property]: fn }; + }, + wrapObject(obj) { + if (obj === null) { + return null; + } + if (typeof obj === "object" || typeof obj === "function" || typeof "obj" === "symbol") { + let result = obj[javaObjectSymbol]; + if (typeof result === "object") { + return result; + } + result = javaWrappers.get(obj); + if (result !== void 0) { + result = result.deref(); + if (result !== void 0) { + return result; + } + } + result = exports["teavm.jso.createWrapper"](obj); + javaWrappers.set(obj, new WeakRef(result)); + return result; + } else { + let result = primitiveWrappers.get(obj); + if (result !== void 0) { + result = result.deref(); + if (result !== void 0) { + return result; + } + } + result = exports["teavm.jso.createWrapper"](obj); + primitiveWrappers.set(obj, new WeakRef(result)); + primitiveFinalization.register(result, obj); + return result; + } + }, + isPrimitive: (value, type) => typeof value === type, + sameRef: (a, b) => a === b, + hashCode: (obj) => { + if (typeof obj === "object" || typeof obj === "function" || typeof obj === "symbol") { + let code = hashCodes.get(obj); + if (typeof code === "number") { + return code; + } + code = nextHashCode(); + hashCodes.set(obj, code); + return code; + } else if (typeof obj === "number") { + return obj | 0; + } else if (typeof obj === "bigint") { + return BigInt.asIntN(obj, 32); + } else if (typeof obj === "boolean") { + return obj ? 1 : 0; + } else { + return 0; + } } }; for (let name of ["wrapByte", "wrapShort", "wrapChar", "wrapInt", "wrapFloat", "wrapDouble", "unwrapByte", - "unwrapShort", "unwrapChar", "unwrapInt", "unwrapFloat", "unwrapDouble"]) { + "unwrapShort", "unwrapChar", "unwrapInt", "unwrapFloat", "unwrapDouble"]) { imports.teavmJso[name] = identity; } for (let i = 0; i < 32; ++i) { @@ -208,7 +296,6 @@ TeaVM.wasm = function() { imports.teavmJso["callMethod" + i] = (instance, method, ...args) => instance[method](...args); imports.teavmJso["construct" + i] = (constructor, ...args) => new constructor(...args); } - imports.teavmMath = Math; } function load(path, options) { 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 20f49aef7..807c4483c 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 @@ -764,6 +764,7 @@ public final class JS { @InjectedBy(JSNativeInjector.class) @NoSideEffects + @Import(name = "isPrimitive", module = "teavmJso") public static native boolean isPrimitive(JSObject obj, JSObject primitive); @InjectedBy(JSNativeInjector.class) @@ -773,4 +774,13 @@ public final class JS { @InjectedBy(JSNativeInjector.class) @NoSideEffects public static native JSObject argumentsBeginningAt(int index); + + @InjectedBy(JSNativeInjector.class) + @NoSideEffects + @Import(name = "sameRef", module = "teavmJso") + public static native boolean sameRef(JSObject a, JSObject b); + + @InjectedBy(JSNativeInjector.class) + @NoSideEffects + public static native boolean isNull(JSObject o); } 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 380263efc..c274fd50d 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 @@ -62,6 +62,9 @@ import org.teavm.model.ValueType; import org.teavm.model.Variable; import org.teavm.model.instructions.ArrayElementType; import org.teavm.model.instructions.AssignInstruction; +import org.teavm.model.instructions.BinaryBranchingInstruction; +import org.teavm.model.instructions.BranchingCondition; +import org.teavm.model.instructions.BranchingInstruction; import org.teavm.model.instructions.CastInstruction; import org.teavm.model.instructions.ClassConstantInstruction; import org.teavm.model.instructions.ConstructArrayInstruction; @@ -333,6 +336,10 @@ class JSClassProcessor { if (nativeConstructedObjects[index]) { assign.delete(); } + } else if (insn instanceof BinaryBranchingInstruction) { + processReferenceEquality((BinaryBranchingInstruction) insn); + } else if (insn instanceof BranchingInstruction) { + processReferenceEquality((BranchingInstruction) insn); } } } @@ -463,6 +470,106 @@ class JSClassProcessor { } } + private void processReferenceEquality(BinaryBranchingInstruction instruction) { + if (!wasmGC) { + return; + } + + boolean equal; + switch (instruction.getCondition()) { + case REFERENCE_EQUAL: + equal = true; + break; + case REFERENCE_NOT_EQUAL: + equal = false; + break; + default: + return; + } + + var first = types.typeOf(instruction.getFirstOperand()); + var second = types.typeOf(instruction.getSecondOperand()); + if (first == JSType.JS || second == JSType.JS) { + var call = new InvokeInstruction(); + call.setType(InvocationType.SPECIAL); + call.setLocation(instruction.getLocation()); + var conditionVar = program.createVariable(); + if (first == JSType.NULL || second == JSType.NULL) { + call.setMethod(new MethodReference(JS.class, "isNull", JSObject.class, boolean.class)); + call.setArguments(first == JSType.NULL + ? instruction.getSecondOperand() + : instruction.getFirstOperand()); + call.setReceiver(conditionVar); + instruction.insertPrevious(call); + } else { + var firstOperand = instruction.getFirstOperand(); + var secondOperand = instruction.getSecondOperand(); + if (first != JSType.JS) { + firstOperand = convertToJs(firstOperand, instruction); + } + if (second != JSType.JS) { + secondOperand = convertToJs(secondOperand, instruction); + } + call.setMethod(new MethodReference(JS.class, "sameRef", JSObject.class, JSObject.class, + boolean.class)); + call.setArguments(firstOperand, secondOperand); + call.setReceiver(conditionVar); + instruction.insertPrevious(call); + } + + var newCondition = new BranchingInstruction(equal + ? BranchingCondition.NOT_EQUAL + : BranchingCondition.EQUAL); + newCondition.setOperand(call.getReceiver()); + newCondition.setConsequent(instruction.getConsequent()); + newCondition.setAlternative(instruction.getAlternative()); + newCondition.setLocation(instruction.getLocation()); + instruction.replace(newCondition); + } + } + + private void processReferenceEquality(BranchingInstruction instruction) { + if (!wasmGC) { + return; + } + + boolean equal; + switch (instruction.getCondition()) { + case NULL: + equal = true; + break; + case NOT_NULL: + equal = false; + break; + default: + return; + } + + var type = types.typeOf(instruction.getOperand()); + if (type == JSType.JS) { + var call = new InvokeInstruction(); + call.setType(InvocationType.SPECIAL); + call.setLocation(instruction.getLocation()); + call.setMethod(new MethodReference(JS.class, "isNull", JSObject.class, boolean.class)); + call.setArguments(instruction.getOperand()); + call.setReceiver(program.createVariable()); + instruction.insertPrevious(call); + instruction.setOperand(call.getReceiver()); + instruction.setCondition(equal ? BranchingCondition.NOT_EQUAL : BranchingCondition.EQUAL); + } + } + + private Variable convertToJs(Variable value, Instruction instruction) { + var call = new InvokeInstruction(); + call.setType(InvocationType.SPECIAL); + call.setMethod(new MethodReference(JS.class, "directJavaToJs", Object.class, JSObject.class)); + call.setArguments(value); + call.setReceiver(program.createVariable()); + call.setLocation(instruction.getLocation()); + instruction.insertPrevious(call); + return call.getReceiver(); + } + private ValueType processType(ValueType type) { return processType(typeHelper, type); } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapper.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapper.java index e8a7545e2..940c335c1 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapper.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapper.java @@ -15,6 +15,7 @@ */ package org.teavm.jso.impl; +import org.teavm.classlib.PlatformDetector; import org.teavm.interop.Import; import org.teavm.interop.NoSideEffects; import org.teavm.jso.JSBody; @@ -226,15 +227,40 @@ public final class JSWrapper { } public static boolean isPrimitive(Object o, JSObject primitive) { + if (PlatformDetector.isWebAssemblyGC()) { + JSObject js; + if (o instanceof JSWrapper) { + js = ((JSWrapper) o).js; + } else if (o instanceof JSMarshallable) { + js = ((JSMarshallable) o).marshallToJs(); + } else { + return false; + } + return JS.isPrimitive(js, primitive); + } return isJs(o) && JS.isPrimitive(maybeUnwrap(o), primitive); } public static boolean instanceOf(Object o, JSObject type) { + if (PlatformDetector.isWebAssemblyGC()) { + JSObject js; + if (o instanceof JSWrapper) { + js = ((JSWrapper) o).js; + } else if (o instanceof JSMarshallable) { + js = ((JSMarshallable) o).marshallToJs(); + } else { + return false; + } + return JS.instanceOf(js, type); + } return isJs(o) && JS.instanceOf(maybeUnwrap(o), type); } @Override public int hashCode() { + if (PlatformDetector.isWebAssemblyGC()) { + return wasmGcHashCode(js); + } var type = JSObjects.typeOf(js); if (type.equals("object") || type.equals("symbol") || type.equals("function")) { var code = Helper.hashCodes.get(js); @@ -261,6 +287,9 @@ public final class JSWrapper { } } + @Import(name = "hashCode", module = "teavmJso") + private static native int wasmGcHashCode(JSObject o); + @JSBody(params = "bigint", script = "return BigInt.asIntN(bigint, 32);") @NoSideEffects private static native int bigintTruncate(JSObject bigint); 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 index 7c697633a..da2adfbd8 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapperDependency.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapperDependency.java @@ -51,6 +51,10 @@ public class JSWrapperDependency extends AbstractDependencyListener { case "unmarshallJavaFromJs": externalClassesNode.connect(method.getResult()); break; + case "wrap": + method.getResult().propagate(agent.getType(JSWrapper.class.getName())); + externalClassesNode.connect(method.getResult()); + break; } } } 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 index df7a8512e..68c89fc90 100644 --- 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 @@ -19,6 +19,9 @@ 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; +import org.teavm.jso.JSObject; +import org.teavm.jso.impl.JSWrapper; +import org.teavm.model.MethodReference; class WasmGCJSDependencies extends AbstractDependencyListener { @Override @@ -30,5 +33,8 @@ class WasmGCJSDependencies extends AbstractDependencyListener { var jsToString = agent.linkMethod(JS_TO_STRING); jsToString.getResult().propagate(agent.getType("java.lang.String")); jsToString.use(); + + agent.linkMethod(new MethodReference(JSWrapper.class, "createWrapper", JSObject.class, Object.class)) + .use(); } } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSIntrinsic.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSIntrinsic.java index 94d903552..a98d0e054 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSIntrinsic.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSIntrinsic.java @@ -22,8 +22,13 @@ import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsic; import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsicContext; import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmType; +import org.teavm.backend.wasm.model.expression.WasmBlock; +import org.teavm.backend.wasm.model.expression.WasmBranch; import org.teavm.backend.wasm.model.expression.WasmCall; import org.teavm.backend.wasm.model.expression.WasmExpression; +import org.teavm.backend.wasm.model.expression.WasmIsNull; +import org.teavm.backend.wasm.model.expression.WasmThrow; +import org.teavm.backend.wasm.runtime.gc.WasmGCSupport; import org.teavm.jso.JSObject; import org.teavm.jso.impl.JS; import org.teavm.model.MethodReference; @@ -47,6 +52,10 @@ class WasmGCJSIntrinsic implements WasmGCIntrinsic { var name = new WasmCall(stringToJs, context.generate(invocation.getArguments().get(0))); return new WasmCall(getGlobalFunction(context), name); } + case "throwCCEIfFalse": + return throwCCEIfFalse(context, invocation); + case "isNull": + return new WasmIsNull(context.generate(invocation.getArguments().get(0))); default: throw new IllegalArgumentException(); } @@ -64,4 +73,24 @@ class WasmGCJSIntrinsic implements WasmGCIntrinsic { } return globalFunction; } + + private WasmExpression throwCCEIfFalse(WasmGCIntrinsicContext context, InvocationExpr invocation) { + var block = new WasmBlock(false); + block.setType(WasmType.Reference.EXTERN); + + var innerBlock = new WasmBlock(false); + block.getBody().add(innerBlock); + var br = new WasmBranch(context.generate(invocation.getArguments().get(0)), innerBlock); + innerBlock.getBody().add(br); + + var cceFunction = context.functions().forStaticMethod(new MethodReference( + WasmGCSupport.class, "cce", ClassCastException.class)); + var cce = new WasmCall(cceFunction); + var throwExpr = new WasmThrow(context.exceptionTag()); + throwExpr.getArguments().add(cce); + innerBlock.getBody().add(throwExpr); + + block.getBody().add(context.generate(invocation.getArguments().get(1))); + return block; + } } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSWrapperIntrinsic.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSWrapperIntrinsic.java new file mode 100644 index 000000000..1c2a5086d --- /dev/null +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSWrapperIntrinsic.java @@ -0,0 +1,81 @@ +/* + * 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.WasmFunction; +import org.teavm.backend.wasm.model.WasmType; +import org.teavm.backend.wasm.model.expression.WasmCall; +import org.teavm.backend.wasm.model.expression.WasmCast; +import org.teavm.backend.wasm.model.expression.WasmExpression; +import org.teavm.backend.wasm.model.expression.WasmExternConversion; +import org.teavm.backend.wasm.model.expression.WasmExternConversionType; +import org.teavm.backend.wasm.model.expression.WasmTest; +import org.teavm.jso.JSObject; +import org.teavm.jso.impl.JSWrapper; +import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; + +class WasmGCJSWrapperIntrinsic implements WasmGCIntrinsic { + private WasmFunction wrapFunction; + + @Override + public WasmExpression apply(InvocationExpr invocation, WasmGCIntrinsicContext context) { + switch (invocation.getMethod().getName()) { + case "wrap": { + var function = getWrapFunction(context); + return new WasmCall(function, context.generate(invocation.getArguments().get(0))); + } + case "dependencyJavaToJs": + case "directJavaToJs": + return new WasmExternConversion(WasmExternConversionType.ANY_TO_EXTERN, + context.generate(invocation.getArguments().get(0))); + case "dependencyJsToJava": + case "directJsToJava": { + var any = new WasmExternConversion(WasmExternConversionType.EXTERN_TO_ANY, + context.generate(invocation.getArguments().get(0))); + var objectType = context.typeMapper().mapType(ValueType.parse(Object.class)); + return new WasmCast(any, (WasmType.Reference) objectType); + } + case "isJava": { + var convert = new WasmExternConversion(WasmExternConversionType.EXTERN_TO_ANY, + context.generate(invocation.getArguments().get(0))); + var objectType = context.typeMapper().mapType(ValueType.parse(Object.class)); + return new WasmTest(convert, (WasmType.Reference) objectType); + } + + default: + throw new IllegalArgumentException(); + } + } + + private WasmFunction getWrapFunction(WasmGCIntrinsicContext context) { + if (wrapFunction == null) { + var objectType = context.typeMapper().mapType(ValueType.parse(Object.class)); + wrapFunction = new WasmFunction(context.functionTypes().of(objectType, WasmType.Reference.EXTERN)); + wrapFunction.setImportName("wrapObject"); + wrapFunction.setImportModule("teavmJso"); + context.module().functions.add(wrapFunction); + + var createWrapperFunction = context.functions().forStaticMethod(new MethodReference( + JSWrapper.class, "createWrapper", JSObject.class, Object.class)); + createWrapperFunction.setExportName("teavm.jso.createWrapper"); + } + return wrapFunction; + } +} diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSWrapperTransformer.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSWrapperTransformer.java index 151b4f9b2..3c80c0471 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSWrapperTransformer.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSWrapperTransformer.java @@ -18,6 +18,7 @@ package org.teavm.jso.impl.wasmgc; import org.teavm.jso.JSObject; import org.teavm.jso.impl.JSMarshallable; import org.teavm.jso.impl.JSWrapper; +import org.teavm.model.AccessLevel; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderTransformer; import org.teavm.model.ClassHolderTransformerContext; @@ -33,8 +34,11 @@ class WasmGCJSWrapperTransformer implements ClassHolderTransformer { if (cls.getName().equals(JSWrapper.class.getName())) { transformMarshallMethod(cls.getMethod(new MethodDescriptor("marshallJavaToJs", Object.class, JSObject.class)), context); + transformWrapMethod(cls.getMethod(new MethodDescriptor("wrap", JSObject.class, Object.class))); transformIsJsImplementation(cls.getMethod(new MethodDescriptor("isJSImplementation", Object.class, boolean.class)), context); + transformIsJava(cls.getMethod(new MethodDescriptor("isJava", Object.class, boolean.class)), context); + addCreateWrapperMethod(cls, context); } } @@ -45,10 +49,30 @@ class WasmGCJSWrapperTransformer implements ClassHolderTransformer { obj.cast(JSMarshallable.class).invokeVirtual("marshallToJs", JSObject.class).returnValue(); } + private void transformWrapMethod(MethodHolder method) { + method.getModifiers().add(ElementModifier.NATIVE); + method.setProgram(null); + } + private void transformIsJsImplementation(MethodHolder method, ClassHolderTransformerContext context) { method.getModifiers().remove(ElementModifier.NATIVE); var pe = ProgramEmitter.create(method, context.getHierarchy()); var obj = pe.var(1, JSObject.class); obj.instanceOf(ValueType.parse(JSMarshallable.class)).returnValue(); } + + private void addCreateWrapperMethod(ClassHolder cls, ClassHolderTransformerContext context) { + var method = new MethodHolder(new MethodDescriptor("createWrapper", JSObject.class, Object.class)); + method.getModifiers().add(ElementModifier.STATIC); + method.setLevel(AccessLevel.PUBLIC); + var pe = ProgramEmitter.create(method, context.getHierarchy()); + pe.construct(JSWrapper.class, pe.var(1, JSObject.class)).returnValue(); + cls.addMethod(method); + } + + private void transformIsJava(MethodHolder method, ClassHolderTransformerContext context) { + method.getModifiers().remove(ElementModifier.NATIVE); + var pe = ProgramEmitter.create(method, context.getHierarchy()); + pe.constant(1).returnValue(); + } } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJso.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJso.java index 0a38aedae..c004e7055 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJso.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJso.java @@ -19,6 +19,7 @@ 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.jso.impl.JSWrapper; import org.teavm.model.MethodReference; import org.teavm.vm.spi.TeaVMHost; @@ -40,5 +41,22 @@ public final class WasmGCJso { wasmGCHost.addIntrinsic(new MethodReference(JS.class, "unwrapString", JSObject.class, String.class), jsIntrinsic); wasmGCHost.addIntrinsic(new MethodReference(JS.class, "global", String.class, JSObject.class), jsIntrinsic); + wasmGCHost.addIntrinsic(new MethodReference(JS.class, "throwCCEIfFalse", boolean.class, JSObject.class, + JSObject.class), jsIntrinsic); + wasmGCHost.addIntrinsic(new MethodReference(JS.class, "isNull", JSObject.class, boolean.class), jsIntrinsic); + + var wrapperIntrinsic = new WasmGCJSWrapperIntrinsic(); + wasmGCHost.addIntrinsic(new MethodReference(JSWrapper.class, "wrap", JSObject.class, Object.class), + wrapperIntrinsic); + wasmGCHost.addIntrinsic(new MethodReference(JSWrapper.class, "dependencyJavaToJs", Object.class, + JSObject.class), wrapperIntrinsic); + wasmGCHost.addIntrinsic(new MethodReference(JSWrapper.class, "directJavaToJs", Object.class, JSObject.class), + wrapperIntrinsic); + wasmGCHost.addIntrinsic(new MethodReference(JSWrapper.class, "dependencyJsToJava", JSObject.class, + Object.class), wrapperIntrinsic); + wasmGCHost.addIntrinsic(new MethodReference(JSWrapper.class, "directJsToJava", JSObject.class, Object.class), + wrapperIntrinsic); + wasmGCHost.addIntrinsic(new MethodReference(JSWrapper.class, "isJava", JSObject.class, boolean.class), + wrapperIntrinsic); } } diff --git a/samples/benchmark/build.gradle.kts b/samples/benchmark/build.gradle.kts index 43924de99..db9565d10 100644 --- a/samples/benchmark/build.gradle.kts +++ b/samples/benchmark/build.gradle.kts @@ -42,11 +42,19 @@ val generatedCSources = File(buildDir, "generated/teavm-c") val executableFile = File(buildDir, "dist/teavm_benchmark") teavm { + all { + outOfProcess = true + processMemory = 1024 + } js { addedToWebApp = true mainClass = "org.teavm.samples.benchmark.teavm.BenchmarkStarter" debugInformation = true } + wasmGC { + addedToWebApp = true + mainClass = "org.teavm.samples.benchmark.teavm.BenchmarkStarter" + } wasm { addedToWebApp = true mainClass = "org.teavm.samples.benchmark.teavm.WasmBenchmarkStarter" diff --git a/samples/benchmark/src/main/webapp/index.html b/samples/benchmark/src/main/webapp/index.html index eacc12f51..6fc21db19 100644 --- a/samples/benchmark/src/main/webapp/index.html +++ b/samples/benchmark/src/main/webapp/index.html @@ -25,6 +25,7 @@
  • TeaVM
  • GWT
  • TeaVM (experimental WebAssembly backend)
  • +
  • TeaVM (experimental WebAssembly GC backend)
  • \ No newline at end of file diff --git a/samples/benchmark/src/main/webapp/teavm-wasm-gc.html b/samples/benchmark/src/main/webapp/teavm-wasm-gc.html new file mode 100644 index 000000000..461e3698b --- /dev/null +++ b/samples/benchmark/src/main/webapp/teavm-wasm-gc.html @@ -0,0 +1,54 @@ + + + + + + TeaVM jbox2d benchmark + + + + +

    TeaVM performance

    +
    + +
    +
    + + +
    + + + + + + + + + + + + + + + +
    SecondTime spent computing, ms
    Average
    + + \ No newline at end of file diff --git a/tests/src/test/java/org/teavm/jso/test/FunctorTest.java b/tests/src/test/java/org/teavm/jso/test/FunctorTest.java index 4b59fe9eb..921ed1f37 100644 --- a/tests/src/test/java/org/teavm/jso/test/FunctorTest.java +++ b/tests/src/test/java/org/teavm/jso/test/FunctorTest.java @@ -46,7 +46,6 @@ public class FunctorTest { } @Test - @SkipPlatform(TestPlatform.WEBASSEMBLY_GC) public void functorIdentityPreserved() { JSBiFunction javaFunction = (a, b) -> a + b; JSObject firstRef = getFunction(javaFunction); diff --git a/tests/src/test/java/org/teavm/jso/test/JSWrapperTest.java b/tests/src/test/java/org/teavm/jso/test/JSWrapperTest.java index cf4578586..c12cf6c68 100644 --- a/tests/src/test/java/org/teavm/jso/test/JSWrapperTest.java +++ b/tests/src/test/java/org/teavm/jso/test/JSWrapperTest.java @@ -36,12 +36,13 @@ import org.teavm.jso.core.JSUndefined; 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 JSWrapperTest { private List list = new ArrayList<>(); @@ -294,6 +295,7 @@ public class JSWrapperTest { } @Test + @SkipPlatform(TestPlatform.WEBASSEMBLY_GC) public void createArray() { var array = new J[] { new JImpl(23), @@ -305,6 +307,7 @@ public class JSWrapperTest { } @Test + @SkipPlatform(TestPlatform.WEBASSEMBLY_GC) public void createArrayAndReturnToJS() { assertEquals("23,42", concatFoo(() -> new J[] { new JImpl(23),