Skip to content

Commit

Permalink
wasm gc: support JS exceptions
Browse files Browse the repository at this point in the history
  • Loading branch information
konsoletyper committed Oct 4, 2024
1 parent 1d47146 commit 3218a00
Show file tree
Hide file tree
Showing 10 changed files with 270 additions and 41 deletions.
134 changes: 115 additions & 19 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 @@ -110,16 +110,17 @@ TeaVM.wasm = function() {
}

function jsoImports(imports) {

let javaObjectSymbol = Symbol("javaObject");
let functionsSymbol = Symbol("functions");
let functionOriginSymbol = Symbol("functionOrigin");
let javaExceptionSymbol = Symbol("javaException");

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 javaExceptionWrappers = new WeakMap();
let lastHashCode = 2463534242;
let nextHashCode = () => {
let x = lastHashCode;
Expand Down Expand Up @@ -156,6 +157,53 @@ TeaVM.wasm = function() {
obj[prop] = value;
}
}
function javaExceptionToJs(e) {
if (e instanceof WebAssembly.Exception) {
let tag = exports["javaException"];
if (e.is(tag)) {
let javaException = e.getArg(tag, 0);
let extracted = extractException(javaException);
if (extracted !== null) {
return extracted;
}
let wrapperRef = javaExceptionWrappers.get(javaException);
if (typeof wrapperRef != "undefined") {
let wrapper = wrapperRef.deref();
if (typeof wrapper !== "undefined") {
return wrapper;
}
}
let wrapper = new Error();
javaExceptionWrappers.set(javaException, new WeakRef(wrapper));
wrapper[javaExceptionSymbol] = javaException;
return wrapper;
}
}
return e;
}
function jsExceptionAsJava(e) {
if (javaExceptionSymbol in e) {
return e[javaExceptionSymbol];
} else {
return exports["teavm.js.wrapException"](e);
}
}
function rethrowJsAsJava(e) {
exports["teavm.js.throwException"](jsExceptionAsJava(e));
}
function extractException(e) {
return exports["teavm.js.extractException"](e);
}
function rethrowJavaAsJs(e) {
throw javaExceptionToJs(e);
}
function getProperty(obj, prop) {
try {
return obj !== null ? obj[prop] : getGlobalName(prop)
} catch (e) {
rethrowJsAsJava(e);
}
}
imports.teavmJso = {
emptyString: () => "",
stringFromCharCode: code => String.fromCharCode(code),
Expand All @@ -166,11 +214,17 @@ TeaVM.wasm = function() {
appendToArray: (array, e) => array.push(e),
unwrapBoolean: value => value ? 1 : 0,
wrapBoolean: value => !!value,
getProperty: (obj, prop) => obj !== null ? obj[prop] : getGlobalName(prop),
getPropertyPure: (obj, prop) => obj !== null ? obj[prop] : getGlobalName(prop),
getProperty: getProperty,
getPropertyPure: getProperty,
setProperty: setProperty,
setPropertyPure: setProperty,
global: getGlobalName,
global(name) {
try {
return getGlobalName(name);
} catch (e) {
rethrowJsAsJava(e);
}
},
createClass(name) {
let fn = new Function(
"javaObjectSymbol",
Expand All @@ -184,18 +238,30 @@ TeaVM.wasm = function() {
},
defineMethod(cls, name, fn) {
cls.prototype[name] = function(...args) {
return fn(this, ...args);
try {
return fn(this, ...args);
} catch (e) {
rethrowJavaAsJs(e);
}
}
},
defineProperty(cls, name, getFn, setFn) {
let descriptor = {
get() {
return getFn(this);
try {
return getFn(this);
} catch (e) {
rethrowJavaAsJs(e);
}
}
};
if (setFn !== null) {
descriptor.set = function(value) {
setFn(this, value);
try {
setFn(this, value);
} catch (e) {
rethrowJavaAsJs(e);
}
}
}
Object.defineProperty(cls.prototype, name, descriptor);
Expand Down Expand Up @@ -239,7 +305,15 @@ TeaVM.wasm = function() {
return origin;
}
}
return { [property]: fn };
return {
[property]: function(...args) {
try {
return fn(...args);
} catch (e) {
rethrowJavaAsJs(e);
}
}
};
},
wrapObject(obj) {
if (obj === null) {
Expand Down Expand Up @@ -298,25 +372,47 @@ TeaVM.wasm = function() {
}
},
apply: (instance, method, args) => {
if (instance === null) {
let fn = getGlobalName(method);
return fn(...args);
} else {
return instance[method](...args);
try {
if (instance === null) {
let fn = getGlobalName(method);
return fn(...args);
} else {
return instance[method](...args);
}
} catch (e) {
rethrowJsAsJava(e);
}
},
concatArray: (a, b) => a.concat(b)
concatArray: (a, b) => a.concat(b),
getJavaException: e => e[javaExceptionSymbol]
};
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) {
imports.teavmJso["createFunction" + i] = (...args) => new Function(...args);
imports.teavmJso["callFunction" + i] = (fn, ...args) => fn(...args);
imports.teavmJso["callMethod" + i] = (instance, method, ...args) =>
instance !== null ? instance[method](...args) : getGlobalName(method)(...args);
imports.teavmJso["construct" + i] = (constructor, ...args) => new constructor(...args);
imports.teavmJso["callFunction" + i] = (fn, ...args) => {
try {
return fn(...args);
} catch (e) {
rethrowJsAsJava(e);
}
};
imports.teavmJso["callMethod" + i] = (instance, method, ...args) => {
try {
return instance !== null ? instance[method](...args) : getGlobalName(method)(...args);
} catch (e) {
rethrowJsAsJava(e);
}
}
imports.teavmJso["construct" + i] = (constructor, ...args) => {
try {
return new constructor(...args);
} catch (e) {
rethrowJsAsJava(e);
}
}
imports.teavmJso["arrayOf" + i] = (...args) => args
}
}
Expand Down
1 change: 1 addition & 0 deletions jso/core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ configurations {

dependencies {
"teavm"(project(":jso:impl"))
compileOnly(project(":interop:core"))
}

teavmPublish {
Expand Down
3 changes: 3 additions & 0 deletions jso/core/src/main/java/org/teavm/jso/JSExceptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@
*/
package org.teavm.jso;

import org.teavm.interop.Import;

public final class JSExceptions {
private JSExceptions() {
}

@Import(name = "getJavaException", module = "teavmJso")
public static native Throwable getJavaException(JSObject e);

public static native JSObject getJSException(Throwable e);
Expand Down
22 changes: 12 additions & 10 deletions jso/impl/src/main/java/org/teavm/jso/impl/JSValueMarshaller.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Variable wrapArgument(CallLocation location, Variable var, ValueType type, JSTyp
String className = ((ValueType.Object) type).getClassName();
ClassReader cls = classSource.get(className);
if (cls != null && cls.getAnnotations().get(JSFunctor.class.getName()) != null) {
return wrapFunctor(location, var, cls);
return wrapFunctor(location, var, cls, jsType);
}
}
return wrap(var, type, jsType, location.getSourceLocation(), byRef);
Expand All @@ -83,20 +83,22 @@ boolean isProperFunctor(ClassReader type) {
.count() == 1;
}

private Variable wrapFunctor(CallLocation location, Variable var, ClassReader type) {
private Variable wrapFunctor(CallLocation location, Variable var, ClassReader type, JSType jsType) {
if (!isProperFunctor(type)) {
diagnostics.error(location, "Wrong functor: {{c0}}", type.getName());
return var;
}

var unwrapNative = new InvokeInstruction();
unwrapNative.setLocation(location.getSourceLocation());
unwrapNative.setType(InvocationType.SPECIAL);
unwrapNative.setMethod(JSMethods.UNWRAP);
unwrapNative.setArguments(var);
unwrapNative.setReceiver(program.createVariable());
replacement.add(unwrapNative);
var = unwrapNative.getReceiver();
if (jsType == JSType.JAVA) {
var unwrapNative = new InvokeInstruction();
unwrapNative.setLocation(location.getSourceLocation());
unwrapNative.setType(InvocationType.SPECIAL);
unwrapNative.setMethod(JSMethods.UNWRAP);
unwrapNative.setArguments(var);
unwrapNative.setReceiver(program.createVariable());
replacement.add(unwrapNative);
var = unwrapNative.getReceiver();
}

String name = type.getMethods().stream()
.filter(method -> method.hasModifier(ElementModifier.ABSTRACT))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2024 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.jso.impl.wasmgc;

import org.teavm.jso.JSObject;
import org.teavm.jso.core.JSError;

class WasmGCExceptionWrapper extends RuntimeException {
final JSObject jsException;

WasmGCExceptionWrapper(JSObject jsException) {
this.jsException = jsException;
}

@Override
public String getMessage() {
var message = jsException instanceof JSError
? ((JSError) jsException).getMessage()
: jsException.toString();
return "(JavaScript) Error: " + message;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,7 @@
class WasmGCJSDependencies extends AbstractDependencyListener {
@Override
public void started(DependencyAgent agent) {
agent.linkMethod(STRING_TO_JS)
.propagate(1, agent.getType("java.lang.String"))
.use();

var jsToString = agent.linkMethod(JS_TO_STRING);
jsToString.getResult().propagate(agent.getType("java.lang.String"));
jsToString.use();

agent.linkMethod(new MethodReference(JSWrapper.class, "createWrapper", JSObject.class, Object.class))
.use();
reachUtilities(agent);
}

@Override
Expand All @@ -46,6 +37,26 @@ public void methodReached(DependencyAgent agent, MethodDependency method) {
if (method.getMethod().getName().equals("jsArrayItem")) {
method.getVariable(1).getArrayItem().connect(method.getResult());
}
} else if (method.getMethod().getOwnerName().equals(JSWrapper.class.getName())) {
if (method.getMethod().getName().equals("wrap")) {
agent.linkMethod(new MethodReference(JSWrapper.class, "createWrapper", JSObject.class, Object.class))
.use();
}
}
}

private void reachUtilities(DependencyAgent agent) {
agent.linkMethod(STRING_TO_JS)
.propagate(1, agent.getType("java.lang.String"))
.use();

var jsToString = agent.linkMethod(JS_TO_STRING);
jsToString.getResult().propagate(agent.getType("java.lang.String"));
jsToString.use();

agent.linkMethod(new MethodReference(WasmGCJSRuntime.class, "wrapException", JSObject.class, Throwable.class))
.use();
agent.linkMethod(new MethodReference(WasmGCJSRuntime.class, "extractException", Throwable.class,
JSObject.class)).use();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,12 @@ static String jsToString(JSObject obj) {

@Import(name = "charAt", module = "teavmJso")
static native char charAt(JSObject str, int index);

static Throwable wrapException(JSObject obj) {
return new WasmGCExceptionWrapper(obj);
}

static JSObject extractException(Throwable e) {
return e instanceof WasmGCExceptionWrapper ? ((WasmGCExceptionWrapper) e).jsException : null;
}
}
Loading

0 comments on commit 3218a00

Please sign in to comment.