wasm gc: support wrapping JS values in Java

This commit is contained in:
Alexey Andreev 2024-10-01 20:18:36 +02:00
parent 2340760647
commit a291eb3026
30 changed files with 610 additions and 31 deletions

View File

@ -188,6 +188,12 @@ public class DisassemblyCodeListener extends BaseDisassemblyListener implements
case IS_NULL: case IS_NULL:
writer.write("ref.is_null"); writer.write("ref.is_null");
break; break;
case EXTERN_TO_ANY:
writer.write("any.convert_extern");
break;
case ANY_TO_EXTERN:
writer.write("extern.convert_any");
break;
} }
writer.eol(); writer.eol();
} }

View File

@ -277,6 +277,11 @@ public class WasmDefaultExpressionVisitor implements WasmExpressionVisitor {
expression.getValue().acceptVisitor(this); expression.getValue().acceptVisitor(this);
} }
@Override
public void visit(WasmExternConversion expression) {
expression.getValue().acceptVisitor(this);
}
@Override @Override
public void visit(WasmStructNew expression) { public void visit(WasmStructNew expression) {
for (var initializer : expression.getInitializers()) { for (var initializer : expression.getInitializers()) {

View File

@ -102,6 +102,8 @@ public interface WasmExpressionVisitor {
void visit(WasmCast expression); void visit(WasmCast expression);
void visit(WasmExternConversion expression);
void visit(WasmTest expression); void visit(WasmTest expression);
void visit(WasmStructNew expression); void visit(WasmStructNew expression);

View File

@ -15,5 +15,35 @@
*/ */
package org.teavm.backend.wasm.model.expression; 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);
}
} }

View File

@ -16,6 +16,6 @@
package org.teavm.backend.wasm.model.expression; package org.teavm.backend.wasm.model.expression;
public enum WasmExternConversionType { public enum WasmExternConversionType {
EXTERN_TO_OBJECT, EXTERN_TO_ANY,
OBJECT_TO_EXTERN ANY_TO_EXTERN
} }

View File

@ -22,8 +22,7 @@ import org.teavm.backend.wasm.model.WasmFunction;
public class WasmReplacingExpressionVisitor implements WasmExpressionVisitor { public class WasmReplacingExpressionVisitor implements WasmExpressionVisitor {
private Function<WasmExpression, WasmExpression> mapper; private Function<WasmExpression, WasmExpression> mapper;
public WasmReplacingExpressionVisitor( public WasmReplacingExpressionVisitor(Function<WasmExpression, WasmExpression> mapper) {
Function<WasmExpression, WasmExpression> mapper) {
this.mapper = mapper; this.mapper = mapper;
} }
@ -332,6 +331,12 @@ public class WasmReplacingExpressionVisitor implements WasmExpressionVisitor {
expression.setValue(mapper.apply(expression.getValue())); expression.setValue(mapper.apply(expression.getValue()));
} }
@Override
public void visit(WasmExternConversion expression) {
expression.getValue().acceptVisitor(this);
expression.setValue(mapper.apply(expression.getValue()));
}
@Override @Override
public void visit(WasmStructNew expression) { public void visit(WasmStructNew expression) {
replaceExpressions(expression.getInitializers()); replaceExpressions(expression.getInitializers());

View File

@ -719,6 +719,13 @@ public class CodeParser extends BaseSectionParser {
parseCastBranch(false); parseCastBranch(false);
return true; return true;
case 26:
codeListener.opcode(Opcode.EXTERN_TO_ANY);
return true;
case 27:
codeListener.opcode(Opcode.ANY_TO_EXTERN);
return true;
case 28: case 28:
codeListener.int31Reference(); codeListener.int31Reference();
return true; return true;

View File

@ -22,5 +22,7 @@ public enum Opcode {
DROP, DROP,
REF_EQ, REF_EQ,
ARRAY_LENGTH, ARRAY_LENGTH,
IS_NULL IS_NULL,
ANY_TO_EXTERN,
EXTERN_TO_ANY
} }

View File

@ -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.WasmDrop;
import org.teavm.backend.wasm.model.expression.WasmExpression; import org.teavm.backend.wasm.model.expression.WasmExpression;
import org.teavm.backend.wasm.model.expression.WasmExpressionVisitor; 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.WasmFill;
import org.teavm.backend.wasm.model.expression.WasmFloat32Constant; import org.teavm.backend.wasm.model.expression.WasmFloat32Constant;
import org.teavm.backend.wasm.model.expression.WasmFloat64Constant; import org.teavm.backend.wasm.model.expression.WasmFloat64Constant;
@ -1171,6 +1172,22 @@ class WasmBinaryRenderingVisitor implements WasmExpressionVisitor {
popLocation(); 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 @Override
public void visit(WasmStructNew expression) { public void visit(WasmStructNew expression) {
pushLocation(expression); pushLocation(expression);

View File

@ -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.WasmDrop;
import org.teavm.backend.wasm.model.expression.WasmExpression; import org.teavm.backend.wasm.model.expression.WasmExpression;
import org.teavm.backend.wasm.model.expression.WasmExpressionVisitor; 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.WasmFill;
import org.teavm.backend.wasm.model.expression.WasmFloat32Constant; import org.teavm.backend.wasm.model.expression.WasmFloat32Constant;
import org.teavm.backend.wasm.model.expression.WasmFloat64Constant; import org.teavm.backend.wasm.model.expression.WasmFloat64Constant;
@ -1197,6 +1198,11 @@ class WasmCRenderingVisitor implements WasmExpressionVisitor {
unsupported(); unsupported();
} }
@Override
public void visit(WasmExternConversion expression) {
unsupported();
}
@Override @Override
public void visit(WasmStructNew expression) { public void visit(WasmStructNew expression) {
unsupported(); unsupported();

View File

@ -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.WasmDrop;
import org.teavm.backend.wasm.model.expression.WasmExpression; import org.teavm.backend.wasm.model.expression.WasmExpression;
import org.teavm.backend.wasm.model.expression.WasmExpressionVisitor; 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.WasmFill;
import org.teavm.backend.wasm.model.expression.WasmFloat32Constant; import org.teavm.backend.wasm.model.expression.WasmFloat32Constant;
import org.teavm.backend.wasm.model.expression.WasmFloat64Constant; import org.teavm.backend.wasm.model.expression.WasmFloat64Constant;
@ -772,6 +773,21 @@ class WasmRenderingVisitor implements WasmExpressionVisitor {
close(); 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 @Override
public void visit(WasmStructNew expression) { public void visit(WasmStructNew expression) {
open().append("struct.new "); open().append("struct.new ");

View File

@ -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.WasmCopy;
import org.teavm.backend.wasm.model.expression.WasmDrop; import org.teavm.backend.wasm.model.expression.WasmDrop;
import org.teavm.backend.wasm.model.expression.WasmExpressionVisitor; 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.WasmFill;
import org.teavm.backend.wasm.model.expression.WasmFloat32Constant; import org.teavm.backend.wasm.model.expression.WasmFloat32Constant;
import org.teavm.backend.wasm.model.expression.WasmFloat64Constant; import org.teavm.backend.wasm.model.expression.WasmFloat64Constant;
@ -317,6 +318,18 @@ public class WasmTypeInference implements WasmExpressionVisitor {
result = WasmType.INT32; 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 @Override
public void visit(WasmStructNew expression) { public void visit(WasmStructNew expression) {
result = expression.getType().getReference(); result = expression.getType().getReference();

View File

@ -278,7 +278,6 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
public void addDependencyListener(DependencyListener listener) { public void addDependencyListener(DependencyListener listener) {
listeners.add(listener); listeners.add(listener);
listener.started(agent);
} }
public void addClassTransformer(ClassHolderTransformer transformer) { 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() { public void processDependencies() {
interrupted = false; interrupted = false;
processQueue(); processQueue();

View File

@ -41,6 +41,10 @@ public class BranchingInstruction extends Instruction {
return condition; return condition;
} }
public void setCondition(BranchingCondition condition) {
this.condition = condition;
}
public BasicBlock getConsequent() { public BasicBlock getConsequent() {
return consequent; return consequent;
} }

View File

@ -318,11 +318,11 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
return; return;
} }
dependencyAnalyzer.defer(() -> {
var mainMethod = cls.getMethod(MAIN_METHOD_DESC) != null var mainMethod = cls.getMethod(MAIN_METHOD_DESC) != null
? dependencyAnalyzer.linkMethod(new MethodReference(entryPoint, ? dependencyAnalyzer.linkMethod(new MethodReference(entryPoint,
"main", ValueType.parse(String[].class), ValueType.VOID)) "main", ValueType.parse(String[].class), ValueType.VOID))
: null; : null;
dependencyAnalyzer.defer(() -> {
dependencyAnalyzer.linkClass(entryPoint).initClass(null); dependencyAnalyzer.linkClass(entryPoint).initClass(null);
if (mainMethod != null) { if (mainMethod != null) {
mainMethod.getVariable(1).propagate(dependencyAnalyzer.getType("[Ljava/lang/String;")); 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; cancelled |= progressListener.progressReached(progress) != TeaVMProgressFeedback.CONTINUE;
return !cancelled; return !cancelled;
}); });
dependencyAnalyzer.initDependencies();
target.contributeDependencies(dependencyAnalyzer); target.contributeDependencies(dependencyAnalyzer);
if (target.needsSystemArrayCopyOptimization()) { if (target.needsSystemArrayCopyOptimization()) {
dependencyAnalyzer.addDependencyListener(new StdlibDependencyListener()); dependencyAnalyzer.addDependencyListener(new StdlibDependencyListener());

View File

@ -20,21 +20,16 @@ TeaVM.wasm = function() {
let getGlobalName = function(name) { let getGlobalName = function(name) {
return eval(name); return eval(name);
} }
let javaObjectSymbol = Symbol("javaObject");
let functionsSymbol = Symbol("functions");
let functionOriginSymbol = Symbol("functionOrigin");
let javaWrappers = new WeakMap();
function defaults(imports) { function defaults(imports) {
let stderr = ""; dateImports(imports);
let stdout = ""; consoleImports(imports);
let finalizationRegistry = new FinalizationRegistry(heldValue => { coreImports(imports);
if (typeof exports.reportGarbageCollectedValue === "function") { jsoImports(imports);
exports.reportGarbageCollectedValue(heldValue) imports.teavmMath = Math;
} }
});
let stringFinalizationRegistry = new FinalizationRegistry(heldValue => { function dateImports(imports) {
exports.reportGarbageCollectedString(heldValue);
});
imports.teavmDate = { imports.teavmDate = {
currentTimeMillis: () => new Date().getTime(), currentTimeMillis: () => new Date().getTime(),
dateToString: timestamp => stringToJava(new Date(timestamp).toString()), 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(), 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) createFromUTC: (year, month, date, hrs, min, sec) => Date.UTC(year, month, date, hrs, min, sec)
}; };
}
function consoleImports(imports) {
let stderr = "";
let stdout = "";
imports.teavmConsole = { imports.teavmConsole = {
putcharStderr(c) { putcharStderr(c) {
if (c === 10) { if (c === 10) {
@ -77,6 +77,9 @@ TeaVM.wasm = function() {
} }
}, },
}; };
}
function coreImports(imports) {
imports.teavm = { imports.teavm = {
createWeakRef(value, heldValue) { createWeakRef(value, heldValue) {
let weakRef = new WeakRef(value); let weakRef = new WeakRef(value);
@ -93,6 +96,37 @@ TeaVM.wasm = function() {
}, },
stringDeref: weakRef => weakRef.deref() 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) { function identity(value) {
return value; return value;
} }
@ -157,7 +191,7 @@ TeaVM.wasm = function() {
Object.defineProperty(cls.prototype, name, descriptor); Object.defineProperty(cls.prototype, name, descriptor);
}, },
javaObjectToJS(instance, cls) { javaObjectToJS(instance, cls) {
let existing = javaWrappers.get(instance); let existing = jsWrappers.get(instance);
if (typeof existing != "undefined") { if (typeof existing != "undefined") {
let result = existing.deref(); let result = existing.deref();
if (typeof result !== "undefined") { if (typeof result !== "undefined") {
@ -165,7 +199,7 @@ TeaVM.wasm = function() {
} }
} }
let obj = new cls(instance); let obj = new cls(instance);
javaWrappers.set(instance, new WeakRef(obj)); jsWrappers.set(instance, new WeakRef(obj));
return obj; return obj;
}, },
unwrapJavaObject(instance) { unwrapJavaObject(instance) {
@ -196,6 +230,60 @@ TeaVM.wasm = function() {
} }
} }
return { [property]: fn }; 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", for (let name of ["wrapByte", "wrapShort", "wrapChar", "wrapInt", "wrapFloat", "wrapDouble", "unwrapByte",
@ -208,7 +296,6 @@ TeaVM.wasm = function() {
imports.teavmJso["callMethod" + i] = (instance, method, ...args) => instance[method](...args); imports.teavmJso["callMethod" + i] = (instance, method, ...args) => instance[method](...args);
imports.teavmJso["construct" + i] = (constructor, ...args) => new constructor(...args); imports.teavmJso["construct" + i] = (constructor, ...args) => new constructor(...args);
} }
imports.teavmMath = Math;
} }
function load(path, options) { function load(path, options) {

View File

@ -764,6 +764,7 @@ public final class JS {
@InjectedBy(JSNativeInjector.class) @InjectedBy(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
@Import(name = "isPrimitive", module = "teavmJso")
public static native boolean isPrimitive(JSObject obj, JSObject primitive); public static native boolean isPrimitive(JSObject obj, JSObject primitive);
@InjectedBy(JSNativeInjector.class) @InjectedBy(JSNativeInjector.class)
@ -773,4 +774,13 @@ public final class JS {
@InjectedBy(JSNativeInjector.class) @InjectedBy(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
public static native JSObject argumentsBeginningAt(int index); 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);
} }

View File

@ -62,6 +62,9 @@ import org.teavm.model.ValueType;
import org.teavm.model.Variable; import org.teavm.model.Variable;
import org.teavm.model.instructions.ArrayElementType; import org.teavm.model.instructions.ArrayElementType;
import org.teavm.model.instructions.AssignInstruction; 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.CastInstruction;
import org.teavm.model.instructions.ClassConstantInstruction; import org.teavm.model.instructions.ClassConstantInstruction;
import org.teavm.model.instructions.ConstructArrayInstruction; import org.teavm.model.instructions.ConstructArrayInstruction;
@ -333,6 +336,10 @@ class JSClassProcessor {
if (nativeConstructedObjects[index]) { if (nativeConstructedObjects[index]) {
assign.delete(); 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) { private ValueType processType(ValueType type) {
return processType(typeHelper, type); return processType(typeHelper, type);
} }

View File

@ -15,6 +15,7 @@
*/ */
package org.teavm.jso.impl; package org.teavm.jso.impl;
import org.teavm.classlib.PlatformDetector;
import org.teavm.interop.Import; import org.teavm.interop.Import;
import org.teavm.interop.NoSideEffects; import org.teavm.interop.NoSideEffects;
import org.teavm.jso.JSBody; import org.teavm.jso.JSBody;
@ -226,15 +227,40 @@ public final class JSWrapper {
} }
public static boolean isPrimitive(Object o, JSObject primitive) { 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); return isJs(o) && JS.isPrimitive(maybeUnwrap(o), primitive);
} }
public static boolean instanceOf(Object o, JSObject type) { 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); return isJs(o) && JS.instanceOf(maybeUnwrap(o), type);
} }
@Override @Override
public int hashCode() { public int hashCode() {
if (PlatformDetector.isWebAssemblyGC()) {
return wasmGcHashCode(js);
}
var type = JSObjects.typeOf(js); var type = JSObjects.typeOf(js);
if (type.equals("object") || type.equals("symbol") || type.equals("function")) { if (type.equals("object") || type.equals("symbol") || type.equals("function")) {
var code = Helper.hashCodes.get(js); 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);") @JSBody(params = "bigint", script = "return BigInt.asIntN(bigint, 32);")
@NoSideEffects @NoSideEffects
private static native int bigintTruncate(JSObject bigint); private static native int bigintTruncate(JSObject bigint);

View File

@ -51,6 +51,10 @@ public class JSWrapperDependency extends AbstractDependencyListener {
case "unmarshallJavaFromJs": case "unmarshallJavaFromJs":
externalClassesNode.connect(method.getResult()); externalClassesNode.connect(method.getResult());
break; break;
case "wrap":
method.getResult().propagate(agent.getType(JSWrapper.class.getName()));
externalClassesNode.connect(method.getResult());
break;
} }
} }
} }

View File

@ -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 static org.teavm.jso.impl.wasmgc.WasmGCJSConstants.STRING_TO_JS;
import org.teavm.dependency.AbstractDependencyListener; import org.teavm.dependency.AbstractDependencyListener;
import org.teavm.dependency.DependencyAgent; 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 { class WasmGCJSDependencies extends AbstractDependencyListener {
@Override @Override
@ -30,5 +33,8 @@ class WasmGCJSDependencies extends AbstractDependencyListener {
var jsToString = agent.linkMethod(JS_TO_STRING); var jsToString = agent.linkMethod(JS_TO_STRING);
jsToString.getResult().propagate(agent.getType("java.lang.String")); jsToString.getResult().propagate(agent.getType("java.lang.String"));
jsToString.use(); jsToString.use();
agent.linkMethod(new MethodReference(JSWrapper.class, "createWrapper", JSObject.class, Object.class))
.use();
} }
} }

View File

@ -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.intrinsics.gc.WasmGCIntrinsicContext;
import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmFunction;
import org.teavm.backend.wasm.model.WasmType; 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.WasmCall;
import org.teavm.backend.wasm.model.expression.WasmExpression; 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.JSObject;
import org.teavm.jso.impl.JS; import org.teavm.jso.impl.JS;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
@ -47,6 +52,10 @@ class WasmGCJSIntrinsic implements WasmGCIntrinsic {
var name = new WasmCall(stringToJs, context.generate(invocation.getArguments().get(0))); var name = new WasmCall(stringToJs, context.generate(invocation.getArguments().get(0)));
return new WasmCall(getGlobalFunction(context), name); return new WasmCall(getGlobalFunction(context), name);
} }
case "throwCCEIfFalse":
return throwCCEIfFalse(context, invocation);
case "isNull":
return new WasmIsNull(context.generate(invocation.getArguments().get(0)));
default: default:
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
@ -64,4 +73,24 @@ class WasmGCJSIntrinsic implements WasmGCIntrinsic {
} }
return globalFunction; 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;
}
} }

View File

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

View File

@ -18,6 +18,7 @@ package org.teavm.jso.impl.wasmgc;
import org.teavm.jso.JSObject; import org.teavm.jso.JSObject;
import org.teavm.jso.impl.JSMarshallable; import org.teavm.jso.impl.JSMarshallable;
import org.teavm.jso.impl.JSWrapper; import org.teavm.jso.impl.JSWrapper;
import org.teavm.model.AccessLevel;
import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolder;
import org.teavm.model.ClassHolderTransformer; import org.teavm.model.ClassHolderTransformer;
import org.teavm.model.ClassHolderTransformerContext; import org.teavm.model.ClassHolderTransformerContext;
@ -33,8 +34,11 @@ class WasmGCJSWrapperTransformer implements ClassHolderTransformer {
if (cls.getName().equals(JSWrapper.class.getName())) { if (cls.getName().equals(JSWrapper.class.getName())) {
transformMarshallMethod(cls.getMethod(new MethodDescriptor("marshallJavaToJs", Object.class, transformMarshallMethod(cls.getMethod(new MethodDescriptor("marshallJavaToJs", Object.class,
JSObject.class)), context); JSObject.class)), context);
transformWrapMethod(cls.getMethod(new MethodDescriptor("wrap", JSObject.class, Object.class)));
transformIsJsImplementation(cls.getMethod(new MethodDescriptor("isJSImplementation", transformIsJsImplementation(cls.getMethod(new MethodDescriptor("isJSImplementation",
Object.class, boolean.class)), context); 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(); 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) { private void transformIsJsImplementation(MethodHolder method, ClassHolderTransformerContext context) {
method.getModifiers().remove(ElementModifier.NATIVE); method.getModifiers().remove(ElementModifier.NATIVE);
var pe = ProgramEmitter.create(method, context.getHierarchy()); var pe = ProgramEmitter.create(method, context.getHierarchy());
var obj = pe.var(1, JSObject.class); var obj = pe.var(1, JSObject.class);
obj.instanceOf(ValueType.parse(JSMarshallable.class)).returnValue(); 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();
}
} }

View File

@ -19,6 +19,7 @@ import org.teavm.backend.wasm.gc.TeaVMWasmGCHost;
import org.teavm.jso.JSObject; import org.teavm.jso.JSObject;
import org.teavm.jso.impl.JS; import org.teavm.jso.impl.JS;
import org.teavm.jso.impl.JSBodyRepository; import org.teavm.jso.impl.JSBodyRepository;
import org.teavm.jso.impl.JSWrapper;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import org.teavm.vm.spi.TeaVMHost; 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), wasmGCHost.addIntrinsic(new MethodReference(JS.class, "unwrapString", JSObject.class, String.class),
jsIntrinsic); jsIntrinsic);
wasmGCHost.addIntrinsic(new MethodReference(JS.class, "global", String.class, JSObject.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);
} }
} }

View File

@ -42,11 +42,19 @@ val generatedCSources = File(buildDir, "generated/teavm-c")
val executableFile = File(buildDir, "dist/teavm_benchmark") val executableFile = File(buildDir, "dist/teavm_benchmark")
teavm { teavm {
all {
outOfProcess = true
processMemory = 1024
}
js { js {
addedToWebApp = true addedToWebApp = true
mainClass = "org.teavm.samples.benchmark.teavm.BenchmarkStarter" mainClass = "org.teavm.samples.benchmark.teavm.BenchmarkStarter"
debugInformation = true debugInformation = true
} }
wasmGC {
addedToWebApp = true
mainClass = "org.teavm.samples.benchmark.teavm.BenchmarkStarter"
}
wasm { wasm {
addedToWebApp = true addedToWebApp = true
mainClass = "org.teavm.samples.benchmark.teavm.WasmBenchmarkStarter" mainClass = "org.teavm.samples.benchmark.teavm.WasmBenchmarkStarter"

View File

@ -25,6 +25,7 @@
<li><a href="teavm.html">TeaVM</a></li> <li><a href="teavm.html">TeaVM</a></li>
<li><a href="gwt.html">GWT</a></li> <li><a href="gwt.html">GWT</a></li>
<li><a href="teavm-wasm.html">TeaVM (experimental WebAssembly backend)</a></li> <li><a href="teavm-wasm.html">TeaVM (experimental WebAssembly backend)</a></li>
<li><a href="teavm-wasm-gc.html">TeaVM (experimental WebAssembly GC backend)</a></li>
</ul> </ul>
</body> </body>
</html> </html>

View File

@ -0,0 +1,54 @@
<!--
Copyright 2014 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.
-->
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<title>TeaVM jbox2d benchmark</title>
<script type="text/javascript" charset="utf-8" src="wasm-gc/benchmark.wasm-runtime.js"></script>
</head>
<script type="text/javascript">
function launch() {
TeaVM.wasm.load("wasm-gc/benchmark.wasm").then(teavm => teavm.main());
}
</script>
<body onload="launch()">
<h1>TeaVM performance</h1>
<div>
<canvas id="benchmark-canvas" width="600" height="600"></canvas>
</div>
<div>
<input type="checkbox" id="display-animation-checkbox" checked>
<label for="display-animation-checkbox">display animation</label>
</div>
<table>
<thead>
<tr>
<th>Second</th>
<th>Time spent computing, ms</th>
</tr>
</thead>
<tbody id="result-table-body">
</tbody>
<tfoot>
<tr>
<th>Average</th>
<td id="average-time"></td>
<tr>
</tfoot>
</table>
</body>
</html>

View File

@ -46,7 +46,6 @@ public class FunctorTest {
} }
@Test @Test
@SkipPlatform(TestPlatform.WEBASSEMBLY_GC)
public void functorIdentityPreserved() { public void functorIdentityPreserved() {
JSBiFunction javaFunction = (a, b) -> a + b; JSBiFunction javaFunction = (a, b) -> a + b;
JSObject firstRef = getFunction(javaFunction); JSObject firstRef = getFunction(javaFunction);

View File

@ -36,12 +36,13 @@ import org.teavm.jso.core.JSUndefined;
import org.teavm.junit.EachTestCompiledSeparately; import org.teavm.junit.EachTestCompiledSeparately;
import org.teavm.junit.OnlyPlatform; import org.teavm.junit.OnlyPlatform;
import org.teavm.junit.SkipJVM; import org.teavm.junit.SkipJVM;
import org.teavm.junit.SkipPlatform;
import org.teavm.junit.TeaVMTestRunner; import org.teavm.junit.TeaVMTestRunner;
import org.teavm.junit.TestPlatform; import org.teavm.junit.TestPlatform;
@RunWith(TeaVMTestRunner.class) @RunWith(TeaVMTestRunner.class)
@SkipJVM @SkipJVM
@OnlyPlatform(TestPlatform.JAVASCRIPT) @OnlyPlatform({TestPlatform.JAVASCRIPT, TestPlatform.WEBASSEMBLY_GC})
@EachTestCompiledSeparately @EachTestCompiledSeparately
public class JSWrapperTest { public class JSWrapperTest {
private List<Object> list = new ArrayList<>(); private List<Object> list = new ArrayList<>();
@ -294,6 +295,7 @@ public class JSWrapperTest {
} }
@Test @Test
@SkipPlatform(TestPlatform.WEBASSEMBLY_GC)
public void createArray() { public void createArray() {
var array = new J[] { var array = new J[] {
new JImpl(23), new JImpl(23),
@ -305,6 +307,7 @@ public class JSWrapperTest {
} }
@Test @Test
@SkipPlatform(TestPlatform.WEBASSEMBLY_GC)
public void createArrayAndReturnToJS() { public void createArrayAndReturnToJS() {
assertEquals("23,42", concatFoo(() -> new J[] { assertEquals("23,42", concatFoo(() -> new J[] {
new JImpl(23), new JImpl(23),