Skip to content

Commit

Permalink
wasm gc: fix exporting classes to JS
Browse files Browse the repository at this point in the history
  • Loading branch information
konsoletyper committed Oct 10, 2024
1 parent f61d893 commit 14a4a99
Show file tree
Hide file tree
Showing 10 changed files with 201 additions and 144 deletions.
182 changes: 84 additions & 98 deletions core/src/main/resources/org/teavm/backend/wasm/wasm-gc-runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -263,15 +263,23 @@ TeaVM.wasmGC = TeaVM.wasmGC || function() {
params.push("p" + i);
}
let paramsAsString = params.length === 0 ? "" : params.join(", ");
return new Function("rethrowJavaAsJs", "fn", `
return function(${paramsAsString}) {
try {
return fn(${paramsAsString});
} catch (e) {
rethrowJavaAsJs(e);
}
};
`)(rethrowJavaAsJs, fn);
return new Function("rethrowJavaAsJs", "fn",
`return function(${paramsAsString}) {\n` +
` try {\n` +
` return fn(${paramsAsString});\n` +
` } catch (e) {\n` +
` rethrowJavaAsJs(e);\n` +
` }\n` +
`};`
)(rethrowJavaAsJs, fn);
}
function renameConstructor(name, c) {
return new Function(
"constructor",
`return function ${name}(marker, javaObject) {\n` +
` return constructor.call(this, marker, javaObject);\n` +
`}\n`
)(c);
}
imports.teavmJso = {
emptyString: () => "",
Expand All @@ -295,64 +303,41 @@ TeaVM.wasmGC = TeaVM.wasmGC || function() {
}
},
createClass(name, parent, constructor) {
name = sanitizeName(name);
name = sanitizeName(name || "JavaObject");
let action;
if (parent === null) {
let fn = new Function(
"javaObjectSymbol",
"functionsSymbol",
"wrapperCallMarker",
"constructor",
"rethrowJavaAsJs",
`let fn;
fn = function ${name}(marker, javaObject) {
if (marker === wrapperCallMarker) {
this[javaObjectSymbol] = javaObject;
this[functionsSymbol] = null;
} else if (constructor === null) {
throw new Error("This class can't be instantiated directly");
} else {
try {
return fn(wrapperCallMarker, constructor(arguments));
} catch (e) {
rethrowJavaAsJs(e);
}
}
};
let boundFn = function(javaObject) { return fn.call(this, wrapperCallMarker, javaObject); };
boundFn[wrapperCallMarker] = fn;
boundFn.prototype = fn.prototype;
return boundFn;`
);
return fn(javaObjectSymbol, functionsSymbol, wrapperCallMarkerSymbol, constructor, rethrowJavaAsJs);
action = function (javaObject) {
this[javaObjectSymbol] = javaObject;
this[functionsSymbol] = null;
};
} else {
let fn = new Function(
"parent",
"wrapperCallMarker",
"constructor",
"rethrowJavaAsJs",
`let fn
fn = function ${name}(marker, javaObject) {
if (marker === wrapperCallMarker) {
parent.call(this, javaObject);
} else if (constructor === null) {
throw new Error("This class can't be instantiated directly");
} else {
try {
return fn(wrapperCallMarker, constructor(arguments));
} catch (e) {
rethrowJavaAsJs(e);
}
}
};
fn.prototype = Object.create(parent);
fn.prototype.constructor = parent;
let boundFn = function(javaObject) { return fn.call(this, wrapperCallMarker, javaObject); };
boundFn[wrapperCallMarker] = fn;
boundFn.prototype = fn.prototype;
return fn;`
);
return fn(parent, wrapperCallMarkerSymbol, constructor, rethrowJavaAsJs);
action = function (javaObject) {
parent.call(this, javaObject);
};
fn.prototype = Object.create(parent);
fn.prototype.constructor = parent;
}
let fn = renameConstructor(name, function (marker, javaObject) {
if (marker === wrapperCallMarkerSymbol) {
action.call(this, javaObject);
} else if (constructor === null) {
throw new Error("This class can't be instantiated directly");
} else {
try {
return constructor.apply(null, arguments);
} catch (e) {
rethrowJavaAsJs(e);
}
}
});
fn.prototype = Object.create(parent || Object.prototype);
fn.prototype.constructor = fn;
let boundFn = renameConstructor(name, function(javaObject) {
return fn.call(this, wrapperCallMarkerSymbol, javaObject);
});
boundFn[wrapperCallMarkerSymbol] = fn;
boundFn.prototype = fn.prototype;
return boundFn;
},
exportClass(cls) {
return cls[wrapperCallMarkerSymbol];
Expand All @@ -363,15 +348,15 @@ TeaVM.wasmGC = TeaVM.wasmGC || function() {
params.push("p" + i);
}
let paramsAsString = params.length === 0 ? "" : params.join(", ");
cls.prototype[name] = new Function("rethrowJavaAsJs", "fn", `
return function(${paramsAsString}) {
try {
return fn(${['this', params].join(", ")});
} catch (e) {
rethrowJavaAsJs(e);
}
};
`)(rethrowJavaAsJs, fn);
cls.prototype[name] = new Function("rethrowJavaAsJs", "fn",
`return function(${paramsAsString}) {\n` +
` try {\n` +
` return fn(${['this', params].join(", ")});\n` +
` } catch (e) {\n` +
` rethrowJavaAsJs(e);\n` +
` }\n` +
`};`
)(rethrowJavaAsJs, fn);
},
defineStaticMethod(cls, name, fn) {
cls[name] = defineFunction(fn);
Expand Down Expand Up @@ -554,32 +539,33 @@ TeaVM.wasmGC = TeaVM.wasmGC || function() {
for (let i = 0; i < 32; ++i) {
let args = argumentList.length === 0 ? "" : argumentList.join(", ");
let argsAndBody = [...argumentList, "body"].join(", ");
imports.teavmJso["createFunction" + i] = new Function("wrapCallFromJavaToJs", ...argumentList, "body", `
return new Function('wrapCallFromJavaToJs', ${argsAndBody}).bind(this, wrapCallFromJavaToJs);
`).bind(null, wrapCallFromJavaToJs);
imports.teavmJso["callFunction" + i] = new Function("rethrowJsAsJava", "fn", ...argumentList, `
try {
return fn(${args});
} catch (e) {
rethrowJsAsJava(e);
}
`).bind(null, rethrowJsAsJava);
imports.teavmJso["createFunction" + i] = new Function("wrapCallFromJavaToJs", ...argumentList, "body",
`return new Function('wrapCallFromJavaToJs', ${argsAndBody}).bind(this, wrapCallFromJavaToJs);`
).bind(null, wrapCallFromJavaToJs);
imports.teavmJso["callFunction" + i] = new Function("rethrowJsAsJava", "fn", ...argumentList,
`try {\n` +
` return fn(${args});\n` +
`} catch (e) {\n` +
` rethrowJsAsJava(e);\n` +
`}`
).bind(null, rethrowJsAsJava);
imports.teavmJso["callMethod" + i] = new Function("rethrowJsAsJava", "getGlobalName", "instance",
"method", ...argumentList, `
try {
return instance !== null
? instance[method](${args})
: getGlobalName(method)(${args});
} catch (e) {
rethrowJsAsJava(e);
}`).bind(null, rethrowJsAsJava, getGlobalName);
imports.teavmJso["construct" + i] = new Function("rethrowJsAsJava", "constructor", ...argumentList, `
try {
return new constructor(${args});
} catch (e) {
rethrowJsAsJava(e);
}
`).bind(null, rethrowJsAsJava);
"method", ...argumentList,
`try {\n`+
` return instance !== null\n` +
` ? instance[method](${args})\n` +
` : getGlobalName(method)(${args});\n` +
`} catch (e) {\n` +
` rethrowJsAsJava(e);\n` +
`}`
).bind(null, rethrowJsAsJava, getGlobalName);
imports.teavmJso["construct" + i] = new Function("rethrowJsAsJava", "constructor", ...argumentList,
`try {\n` +
` return new constructor(${args});\n` +
`} catch (e) {\n` +
` rethrowJsAsJava(e);\n` +
`}`
).bind(null, rethrowJsAsJava);
imports.teavmJso["arrayOf" + i] = new Function(...argumentList, "return [" + args + "]");

let param = "p" + (i + 1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,7 @@ public void transformClass(ClassHolder cls, ClassHolderTransformerContext contex
createWrapperMethod.setLevel(AccessLevel.PUBLIC);
createWrapperMethod.getModifiers().add(ElementModifier.NATIVE);
cls.addMethod(createWrapperMethod);

if (!isJavaScriptClass(cls)) {
cls.getInterfaces().add(JSMethods.JS_MARSHALLABLE);
}
cls.getInterfaces().add(JSMethods.JS_MARSHALLABLE);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ static String jsToString(JSObject obj) {
@Import(name = "charAt", module = "teavmJso")
static native char charAt(JSObject str, int index);

static native JSObject wrapObject(Object obj);

static Throwable wrapException(JSObject obj) {
return new WasmGCExceptionWrapper(obj);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2024 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.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.expression.WasmCall;
import org.teavm.backend.wasm.model.expression.WasmExpression;
import org.teavm.backend.wasm.model.expression.WasmGetGlobal;

class WasmGCJSRuntimeIntrinsic implements WasmGCIntrinsic {
private WasmGCJsoCommonGenerator commonGen;

WasmGCJSRuntimeIntrinsic(WasmGCJsoCommonGenerator commonGen) {
this.commonGen = commonGen;
}

@Override
public WasmExpression apply(InvocationExpr invocation, WasmGCIntrinsicContext context) {
var jsoContext = WasmGCJsoContext.wrap(context);
var wrapperClass = commonGen.getDefaultWrapperClass(jsoContext);
var wrapperFunction = commonGen.javaObjectToJSFunction(jsoContext);
return new WasmCall(wrapperFunction, context.generate(invocation.getArguments().get(0)),
new WasmGetGlobal(wrapperClass));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
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;
Expand All @@ -41,17 +40,6 @@ public WasmExpression apply(InvocationExpr invocation, WasmGCIntrinsicContext co
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)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.teavm.model.ElementModifier;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodHolder;
import org.teavm.model.ValueType;
import org.teavm.model.emit.ProgramEmitter;

class WasmGCJSWrapperTransformer implements ClassHolderTransformer {
Expand All @@ -33,6 +34,12 @@ public void transformClass(ClassHolder cls, ClassHolderTransformerContext contex
if (cls.getName().equals(JSWrapper.class.getName())) {
transformMarshallMethod(cls.getMethod(new MethodDescriptor("marshallJavaToJs", Object.class,
JSObject.class)), context);
transformDirectJavaToJs(cls.getMethod(new MethodDescriptor("directJavaToJs", Object.class,
JSObject.class)), context);
transformDirectJavaToJs(cls.getMethod(new MethodDescriptor("dependencyJavaToJs", Object.class,
JSObject.class)), context);
transformDirectJsToJava(cls.getMethod(new MethodDescriptor("dependencyJsToJava", JSObject.class,
Object.class)), context);
transformWrapMethod(cls.getMethod(new MethodDescriptor("wrap", JSObject.class, Object.class)));
transformIsJava(cls.getMethod(new MethodDescriptor("isJava", Object.class, boolean.class)), context);
addCreateWrapperMethod(cls, context);
Expand All @@ -43,9 +50,26 @@ private void transformMarshallMethod(MethodHolder method, ClassHolderTransformer
method.getModifiers().remove(ElementModifier.NATIVE);
var pe = ProgramEmitter.create(method, context.getHierarchy());
var obj = pe.var(1, Object.class);
pe.when(obj.instanceOf(ValueType.parse(JSMarshallable.class)).isFalse()).thenDo(() -> {
pe.invoke(WasmGCJSRuntime.class, "wrapObject", JSObject.class, obj).returnValue();
});
obj.cast(JSMarshallable.class).invokeVirtual("marshallToJs", JSObject.class).returnValue();
}

private void transformDirectJavaToJs(MethodHolder method, ClassHolderTransformerContext context) {
method.getModifiers().remove(ElementModifier.NATIVE);
var pe = ProgramEmitter.create(method, context.getHierarchy());
var obj = pe.var(1, Object.class);
pe.invoke(JSWrapper.class, "marshallJavaToJs", JSObject.class, obj).returnValue();
}

private void transformDirectJsToJava(MethodHolder method, ClassHolderTransformerContext context) {
method.getModifiers().remove(ElementModifier.NATIVE);
var pe = ProgramEmitter.create(method, context.getHierarchy());
var obj = pe.var(1, JSObject.class);
pe.invoke(JSWrapper.class, "unmarshallJavaFromJs", Object.class, obj).returnValue();
}

private void transformWrapMethod(MethodHolder method) {
method.getModifiers().add(ElementModifier.NATIVE);
method.setProgram(null);
Expand Down
11 changes: 3 additions & 8 deletions jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJso.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,10 @@ public static void install(TeaVMHost host, TeaVMWasmGCHost wasmGCHost, JSBodyRep
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);

wasmGCHost.addIntrinsic(new MethodReference(WasmGCJSRuntime.class, "wrapObject", Object.class,
JSObject.class), new WasmGCJSRuntimeIntrinsic(commonGen));
}
}
Loading

0 comments on commit 14a4a99

Please sign in to comment.