Skip to content

Commit

Permalink
Cache and share Wasm-to-JS function adapter call targets.
Browse files Browse the repository at this point in the history
  • Loading branch information
woess committed Oct 2, 2024
1 parent c026701 commit 96e9010
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,8 @@ public enum BuiltinFunctionKey {
*/
private final SharedRootNode sharedRootNode;

private final JSWebAssemblyInstance.Cache webAssemblyCache;

static {
MethodHandles.Lookup lookup = MethodHandles.lookup();
try {
Expand Down Expand Up @@ -763,6 +765,8 @@ protected JSContext(Evaluator evaluator, JavaScriptLanguage lang, JSLanguageOpti
this.regexValidateOptions = regexOptions.isEmpty() ? REGEX_OPTION_VALIDATE : REGEX_OPTION_VALIDATE + "," + regexOptions;

this.supportedImportAttributes = (languageOptions.importAttributes() || languageOptions.importAssertions()) ? Set.of(TYPE_IMPORT_ATTRIBUTE) : Set.of();

this.webAssemblyCache = new JSWebAssemblyInstance.Cache();
}

public final Evaluator getEvaluator() {
Expand Down Expand Up @@ -1925,6 +1929,10 @@ public static TruffleString getTypeImportAttribute() {
return TYPE_IMPORT_ATTRIBUTE;
}

public JSWebAssemblyInstance.Cache getWebAssemblyCache() {
return webAssemblyCache;
}

void updateStableOptions(JSContextOptions contextOptions, StableContextOptionValue.UpdateKind kind) {
optionRegexpStaticResult.update(contextOptions, kind);
optionV8CompatibilityMode.update(contextOptions, kind);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,11 @@ public static void setFunctionLength(JSDynamicObject functionObj, Number length)
JSObject.defineOwnProperty(functionObj, JSFunction.LENGTH, PropertyDescriptor.createData(length, false, false, true));
}

@TruffleBoundary
public static void setFunctionName(JSDynamicObject functionObj, TruffleString name) {
JSObject.defineOwnProperty(functionObj, JSFunction.NAME, PropertyDescriptor.createData(name, false, false, true));
}

@TruffleBoundary
public static void setBoundFunctionName(JSDynamicObject boundFunction, TruffleString targetName) {
JSObject.defineOwnProperty(boundFunction, JSFunction.NAME, PropertyDescriptor.createData(Strings.concat(Strings.BOUND_SPC, targetName), false, false, true));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@
*/
package com.oracle.truffle.js.runtime.builtins.wasm;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.exception.AbstractTruffleException;
import com.oracle.truffle.api.frame.VirtualFrame;
Expand Down Expand Up @@ -93,6 +95,7 @@ public final class JSWebAssemblyInstance extends JSNonProxy implements JSConstru
public static final TruffleString WEB_ASSEMBLY_INSTANCE = Strings.constant("WebAssembly.Instance");

public static final JSWebAssemblyInstance INSTANCE = new JSWebAssemblyInstance();
static final TruffleString FUNCTION_ADAPTER_NAME = Strings.constant("wasm-function-adapter");

@Override
public TruffleString getClassName() {
Expand Down Expand Up @@ -186,96 +189,124 @@ public static Object exportFunction(JSContext context, JSRealm realm, Object exp
return embedderData;
}

WasmFunctionTypeInfo typeInfoKey = parseWasmFunctionTypeInfo(context, typeInfo);
TruffleString name = Strings.substring(context, typeInfo, 0, Strings.indexOf(typeInfo, '('));
JSFunctionData functionData = getOrCreateExportedWasmFunctionAdapter(context, typeInfoKey);

JSFunctionObject result = JSFunction.create(realm, functionData);
JSFunction.setFunctionName(result, name);
JSObjectUtil.putHiddenProperty(result, JSWebAssembly.FUNCTION_ADDRESS, export);
JSWebAssembly.setEmbedderData(realm, export, result);
return result;
}

private static WasmFunctionTypeInfo parseWasmFunctionTypeInfo(JSContext context, TruffleString typeInfo) {
int idxOpen = Strings.indexOf(typeInfo, '(');
int idxClose = Strings.indexOf(typeInfo, ')');
TruffleString name = Strings.substring(context, typeInfo, 0, idxOpen);
TruffleString argTypes = Strings.lazySubstring(typeInfo, idxOpen + 1, idxClose - (idxOpen + 1));
TruffleString returnTypes = Strings.lazySubstring(typeInfo, idxClose + 1);
TruffleString[] paramTypes = !Strings.isEmpty(argTypes) ? Strings.split(context, argTypes, Strings.SPACE) : new TruffleString[0];
TruffleString[] resultTypes = !Strings.isEmpty(returnTypes) ? Strings.split(context, returnTypes, Strings.SPACE) : new TruffleString[0];
int argCount = paramTypes.length;
int returnLength = resultTypes.length;
TruffleString[] paramTypes = parseTypeSequence(context, argTypes);
TruffleString[] resultTypes = parseTypeSequence(context, returnTypes);
boolean anyReturnTypeIsI64 = Strings.indexOf(typeInfo, JSWebAssemblyValueTypes.I64, idxClose + 1) >= 0;
boolean anyArgTypeIsI64 = Strings.indexOf(typeInfo, JSWebAssemblyValueTypes.I64, idxOpen + 1, idxClose) >= 0;
boolean anyReturnTypeIsV128 = Strings.indexOf(typeInfo, JSWebAssemblyValueTypes.V128, idxClose + 1) >= 0;
boolean anyArgTypeIsV128 = Strings.indexOf(typeInfo, JSWebAssemblyValueTypes.V128, idxOpen + 1, idxClose) >= 0;
return new WasmFunctionTypeInfo(paramTypes, resultTypes, anyReturnTypeIsI64 || anyArgTypeIsI64, anyReturnTypeIsV128 || anyArgTypeIsV128);
}

CallTarget callTarget = createExportAdapterCallTarget(context, paramTypes, anyReturnTypeIsI64, anyArgTypeIsI64, anyReturnTypeIsV128, anyArgTypeIsV128, argCount, returnLength);
private static TruffleString[] parseTypeSequence(JSContext context, TruffleString typeString) {
return Strings.split(context, typeString, Strings.SPACE);
}

JSFunctionData functionData = JSFunctionData.createCallOnly(context, callTarget, argCount, name);
JSFunctionObject result = JSFunction.create(realm, functionData);
JSObjectUtil.putHiddenProperty(result, JSWebAssembly.FUNCTION_ADDRESS, export);
JSWebAssembly.setEmbedderData(realm, export, result);
return result;
private static JSFunctionData getOrCreateExportedWasmFunctionAdapter(JSContext context, WasmFunctionTypeInfo funcType) {
Map<WasmFunctionTypeInfo, JSFunctionData> cache = context.getWebAssemblyCache().wasmFunctionAdapterCache;
JSFunctionData functionData = cache.get(funcType);
if (functionData == null) {
functionData = cache.computeIfAbsent(funcType, k -> {
CallTarget callTarget = new WasmToJSFunctionAdapterRootNode(context, funcType).getCallTarget();
return JSFunctionData.createCallOnly(context, callTarget, funcType.paramLength(), FUNCTION_ADAPTER_NAME);
});
}
return functionData;
}

private static CallTarget createExportAdapterCallTarget(JSContext context, TruffleString[] paramTypes, boolean anyReturnTypeIsI64, boolean anyArgTypeIsI64,
boolean anyReturnTypeIsV128, boolean anyArgTypeIsV128, int argCount, int returnLength) {
CallTarget callTarget = new JavaScriptRootNode(context.getLanguage(), null, null) {
@CompilationFinal(dimensions = 1) private final TruffleString[] argTypesArray = paramTypes;

@Child ToWebAssemblyValueNode toWebAssemblyValueNode = ToWebAssemblyValueNodeGen.create();
@Child ToJSValueNode toJSValueNode = ToJSValueNodeGen.create();
private final BranchProfile errorBranch = BranchProfile.create();
@Child InteropLibrary exportFunctionLib = InteropLibrary.getFactory().createDispatched(JSConfig.InteropLibraryLimit);
@Child InteropLibrary readArrayElementLib = InteropLibrary.getFactory().createDispatched(JSConfig.InteropLibraryLimit);
@Child DynamicObjectLibrary getExportedFunctionLib = JSObjectUtil.createDispatched(JSWebAssembly.FUNCTION_ADDRESS);

@Override
public Object execute(VirtualFrame frame) {
if ((!context.getLanguageOptions().wasmBigInt() && (anyReturnTypeIsI64 || anyArgTypeIsI64)) || (anyReturnTypeIsV128 || anyArgTypeIsV128)) {
errorBranch.enter();
throw Errors.createTypeError("wasm function signature contains illegal type");
}
private static class WasmToJSFunctionAdapterRootNode extends JavaScriptRootNode {
private final JSContext context;
private final WasmFunctionTypeInfo type;

@Child ToWebAssemblyValueNode toWebAssemblyValueNode = ToWebAssemblyValueNodeGen.create();
@Child ToJSValueNode toJSValueNode = ToJSValueNodeGen.create();
private final BranchProfile errorBranch = BranchProfile.create();
@Child InteropLibrary exportFunctionLib = InteropLibrary.getFactory().createDispatched(JSConfig.InteropLibraryLimit);
@Child InteropLibrary readArrayElementLib = InteropLibrary.getFactory().createDispatched(JSConfig.InteropLibraryLimit);
@Child DynamicObjectLibrary getExportedFunctionLib = JSObjectUtil.createDispatched(JSWebAssembly.FUNCTION_ADDRESS);

WasmToJSFunctionAdapterRootNode(JSContext context, WasmFunctionTypeInfo type) {
super(context.getLanguage(), null, null);
this.context = context;
this.type = type;
}

Object[] frameArguments = frame.getArguments();
int userArgumentCount = JSArguments.getUserArgumentCount(frameArguments);
Object[] wasmArgs = new Object[argCount];
for (int i = 0; i < argCount; i++) {
Object wasmArg;
if (i < userArgumentCount) {
wasmArg = JSArguments.getUserArgument(frameArguments, i);
} else {
wasmArg = Undefined.instance;
}
wasmArgs[i] = toWebAssemblyValueNode.execute(wasmArg, argTypesArray[i]);
@Override
public Object execute(VirtualFrame frame) {
if ((!context.getLanguageOptions().wasmBigInt() && type.anyTypeIsI64()) || type.anyTypeIsV128()) {
errorBranch.enter();
throw Errors.createTypeError("wasm function signature contains illegal type");
}
int argCount = type.paramLength();
int returnLength = type.resultLength();

Object[] frameArguments = frame.getArguments();
int userArgumentCount = JSArguments.getUserArgumentCount(frameArguments);
Object[] wasmArgs = new Object[argCount];
for (int i = 0; i < argCount; i++) {
Object wasmArg;
if (i < userArgumentCount) {
wasmArg = JSArguments.getUserArgument(frameArguments, i);
} else {
wasmArg = Undefined.instance;
}
wasmArgs[i] = toWebAssemblyValueNode.execute(wasmArg, type.paramTypes()[i]);
}

Object export = getExportedFunctionLib.getOrDefault(JSFrameUtil.getFunctionObject(frame), JSWebAssembly.FUNCTION_ADDRESS, null);
Object export = getExportedFunctionLib.getOrDefault(JSFrameUtil.getFunctionObject(frame), JSWebAssembly.FUNCTION_ADDRESS, null);
try {
Object wasmResult;
try {
Object wasmResult;
try {
wasmResult = exportFunctionLib.execute(export, wasmArgs);
} catch (GraalJSException jsex) {
errorBranch.enter();
throw jsex;
} catch (AbstractTruffleException tex) {
errorBranch.enter();
ExceptionType type = InteropLibrary.getUncached(tex).getExceptionType(tex);
if (type == ExceptionType.INTERRUPT || type == ExceptionType.EXIT) {
throw tex;
} else {
throw Errors.createRuntimeError(tex, this);
}
wasmResult = exportFunctionLib.execute(export, wasmArgs);
} catch (GraalJSException jsex) {
errorBranch.enter();
throw jsex;
} catch (AbstractTruffleException tex) {
errorBranch.enter();
ExceptionType exceptionType = InteropLibrary.getUncached(tex).getExceptionType(tex);
if (exceptionType == ExceptionType.INTERRUPT || exceptionType == ExceptionType.EXIT) {
throw tex;
} else {
throw Errors.createRuntimeError(tex, this);
}
}

if (returnLength == 0) {
return Undefined.instance;
} else if (returnLength == 1) {
return toJSValueNode.execute(wasmResult);
} else {
Object[] values = new Object[returnLength];
for (int i = 0; i < returnLength; i++) {
values[i] = toJSValueNode.execute(readArrayElementLib.readArrayElement(wasmResult, i));
}
return JSArray.createConstantObjectArray(context, getRealm(), values);
if (returnLength == 0) {
return Undefined.instance;
} else if (returnLength == 1) {
return toJSValueNode.execute(wasmResult);
} else {
Object[] values = new Object[returnLength];
for (int i = 0; i < returnLength; i++) {
values[i] = toJSValueNode.execute(readArrayElementLib.readArrayElement(wasmResult, i));
}
} catch (InteropException ex) {
throw Errors.shouldNotReachHere(ex);
return JSArray.createConstantObjectArray(context, getRealm(), values);
}
} catch (InteropException ex) {
throw Errors.shouldNotReachHere(ex);
}
}.getCallTarget();
return callTarget;
}

@Override
public String toString() {
return FUNCTION_ADAPTER_NAME.toJavaStringUncached() + ":" + type.toString();
}
}

@CompilerDirectives.TruffleBoundary
Expand Down Expand Up @@ -311,7 +342,7 @@ public static Object transformImportObject(JSContext context, JSRealm realm, Obj
wasmValue = JSWebAssembly.getExportedFunction((JSDynamicObject) value);
} else {
TruffleString typeInfo = asTString(descriptorInterop.readMember(descriptor, "type"));
wasmValue = createHostFunction(context, value, typeInfo);
wasmValue = createHostFunction(value, parseWasmFunctionTypeInfo(context, typeInfo));
}
} else if (Strings.equals(Strings.GLOBAL, externType)) {
boolean isNumber = JSRuntime.isNumber(value);
Expand Down Expand Up @@ -379,8 +410,8 @@ private static JSException createLinkErrorImport(long index, TruffleString modul
}

@TruffleBoundary
private static Object createHostFunction(JSContext context, Object fn, TruffleString typeInfo) {
return new WebAssemblyHostFunction(context, fn, typeInfo);
private static Object createHostFunction(Object fn, WasmFunctionTypeInfo typeInfo) {
return new WebAssemblyHostFunction(fn, typeInfo);
}

private static TruffleString asTString(Object string) {
Expand All @@ -390,4 +421,7 @@ private static TruffleString asTString(Object string) {
return Strings.interopAsTruffleString(string);
}

public static final class Cache {
final Map<WasmFunctionTypeInfo, JSFunctionData> wasmFunctionAdapterCache = new ConcurrentHashMap<>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or
* data (collectively the "Software"), free of charge and under any and all
* copyright rights in the Software, and any and all patent rights owned or
* freely licensable by each licensor hereunder covering either (i) the
* unmodified Software as contributed to or provided by such licensor, or (ii)
* the Larger Works (as defined below), to deal in both
*
* (a) the Software, and
*
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
*
* The above copyright notice and either this complete permission notice or at a
* minimum a reference to the UPL must be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.oracle.truffle.js.runtime.builtins.wasm;

import java.util.Arrays;
import java.util.Objects;

import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.strings.TruffleString;

public record WasmFunctionTypeInfo(
@CompilationFinal(dimensions = 1) TruffleString[] paramTypes,
@CompilationFinal(dimensions = 1) TruffleString[] resultTypes,
boolean anyTypeIsI64,
boolean anyTypeIsV128) {

public int paramLength() {
return paramTypes.length;
}

public int resultLength() {
return resultTypes.length;
}

@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
return obj instanceof WasmFunctionTypeInfo that &&
Arrays.equals(this.paramTypes, that.paramTypes) &&
Arrays.equals(this.resultTypes, that.resultTypes) &&
this.anyTypeIsI64 == that.anyTypeIsI64 &&
this.anyTypeIsV128 == that.anyTypeIsV128;
}

@Override
public int hashCode() {
return Objects.hash(Arrays.hashCode(paramTypes), Arrays.hashCode(resultTypes), anyTypeIsI64, anyTypeIsV128);
}

@Override
public String toString() {
return "(" + String.join(" ", toString(paramTypes)) + ")" + String.join(" ", toString(resultTypes));
}

private static String[] toString(TruffleString[] types) {
return Arrays.stream(types).map(Object::toString).toArray(String[]::new);
}
}
Loading

0 comments on commit 96e9010

Please sign in to comment.