Skip to content

Commit

Permalink
wasm gc: add support for imports from JS
Browse files Browse the repository at this point in the history
  • Loading branch information
konsoletyper committed Oct 16, 2024
1 parent 1fadc71 commit 7f03cce
Show file tree
Hide file tree
Showing 16 changed files with 242 additions and 28 deletions.
30 changes: 28 additions & 2 deletions core/src/main/java/org/teavm/backend/wasm/model/WasmGlobal.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ public class WasmGlobal extends WasmEntity {
private WasmExpression initialValue;
private boolean immutable;
private String exportName;
private String importName;
private String importModule;

public WasmGlobal(String name, WasmType type, WasmExpression initialValue) {
this.name = name;
this.type = Objects.requireNonNull(type);
this.initialValue = Objects.requireNonNull(initialValue);
this.initialValue = initialValue;
}

public String getName() {
Expand All @@ -48,7 +50,7 @@ public WasmExpression getInitialValue() {
}

public void setInitialValue(WasmExpression initialValue) {
this.initialValue = Objects.requireNonNull(initialValue);
this.initialValue = initialValue;
}

public boolean isImmutable() {
Expand All @@ -66,4 +68,28 @@ public String getExportName() {
public void setExportName(String exportName) {
this.exportName = exportName;
}

public String getImportName() {
return importName;
}

public void setImportName(String importName) {
this.importName = importName;
if (collection != null) {
collection.invalidateIndexes();
}
}

public String getImportModule() {
return importModule;
}

public void setImportModule(String importModule) {
this.importModule = importModule;
}

@Override
boolean isImported() {
return importName != null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.teavm.backend.wasm.generate.DwarfGenerator;
import org.teavm.backend.wasm.model.WasmCustomSection;
import org.teavm.backend.wasm.model.WasmFunction;
import org.teavm.backend.wasm.model.WasmGlobal;
import org.teavm.backend.wasm.model.WasmMemorySegment;
import org.teavm.backend.wasm.model.WasmModule;
import org.teavm.backend.wasm.model.WasmStructure;
Expand Down Expand Up @@ -138,33 +139,52 @@ private void renderTypes(WasmModule module) {
}

private void renderImports(WasmModule module) {
List<WasmFunction> functions = new ArrayList<>();
var functions = new ArrayList<WasmFunction>();
for (var function : module.functions) {
if (function.getImportName() == null) {
continue;
}
functions.add(function);
}
if (functions.isEmpty()) {

var globals = new ArrayList<WasmGlobal>();
for (var global : module.globals) {
if (global.getImportName() == null) {
continue;
}
globals.add(global);
}

if (functions.isEmpty() && globals.isEmpty()) {
return;
}

WasmBinaryWriter section = new WasmBinaryWriter();

section.writeLEB(functions.size());
section.writeLEB(functions.size() + globals.size());
for (WasmFunction function : functions) {
int signatureIndex = module.types.indexOf(function.getType());
String moduleName = function.getImportModule();
if (moduleName == null) {
moduleName = "";
}
section.writeAsciiString(moduleName);

section.writeAsciiString(function.getImportName());

section.writeByte(EXTERNAL_KIND_FUNCTION);
section.writeLEB(signatureIndex);
}
for (var global : globals) {
var moduleName = global.getImportModule();
if (moduleName == null) {
moduleName = "";
}
section.writeAsciiString(moduleName);
section.writeAsciiString(global.getImportName());
section.writeByte(EXTERNAL_KIND_GLOBAL);
section.writeType(global.getType(), module);
section.writeByte(global.isImmutable() ? 0 : 1);
}

writeSection(SECTION_IMPORT, "import", section.getData());
}
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/js/wasm-gc-runtime/module-wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
*/

include();
export { load, defaults };
export { load, defaults, wrapImport };
75 changes: 63 additions & 12 deletions core/src/main/js/wasm-gc-runtime/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,15 @@ function defaults(imports) {

let javaExceptionSymbol = Symbol("javaException");
class JavaError extends Error {
constructor(javaException) {
#context

constructor(context, javaException) {
super();
this.#context = context;
this[javaExceptionSymbol] = javaException;
}
get message() {
let exceptionMessage = exports["teavm.exceptionMessage"];
let exceptionMessage = this.#context.exports["teavm.exceptionMessage"];
if (typeof exceptionMessage === "function") {
let message = exceptionMessage(this[javaExceptionSymbol]);
if (message != null) {
Expand Down Expand Up @@ -247,7 +250,7 @@ function jsoImports(imports, context) {
return wrapper;
}
}
let wrapper = new JavaError(javaException);
let wrapper = new JavaError(context, javaException);
javaExceptionWrappers.set(javaException, new WeakRef(wrapper));
return wrapper;
}
Expand Down Expand Up @@ -312,7 +315,6 @@ function jsoImports(imports, context) {
unwrapBoolean: value => value ? 1 : 0,
wrapBoolean: value => !!value,
getProperty: getProperty,
getPropertyPure: getProperty,
setProperty: setProperty,
setPropertyPure: setProperty,
global(name) {
Expand Down Expand Up @@ -565,6 +567,7 @@ function jsoImports(imports, context) {
imports.teavmJso["createFunction" + i] = new Function("wrapCallFromJavaToJs", ...argumentList, "body",
`return new Function('wrapCallFromJavaToJs', ${argsAndBody}).bind(this, wrapCallFromJavaToJs);`
).bind(null, wrapCallFromJavaToJs);
imports.teavmJso["bindFunction" + i] = (f, args) => f.bind(null, args);
imports.teavmJso["callFunction" + i] = new Function("rethrowJsAsJava", "fn", ...argumentList,
`try {\n` +
` return fn(${args});\n` +
Expand Down Expand Up @@ -596,25 +599,73 @@ function jsoImports(imports, context) {
}
}

function wrapImport(importObj) {
return new Proxy(importObj, {
get(target, prop) {
let result = target[prop];
return new WebAssembly.Global({ value: "externref", mutable: false }, result);
}
});
}

async function wrapImports(wasmModule, imports) {
let promises = [];
let propertiesToAdd = {};
for (let { module, name, kind } of WebAssembly.Module.imports(wasmModule)) {
if (kind !== "global" || module in imports) {
continue;
}
let names = propertiesToAdd[module];
if (names === void 0) {
let namesByModule = [];
names = namesByModule;
propertiesToAdd[name] = names;
promises.push(async() => {
let moduleInstance = await import(module);
let importsByModule = {
__self__: moduleInstance
};
for (let name of namesByModule) {
let importedName = moduleInstance[name];
importsByModule[name] = new WebAssembly.Global(
{ value: "externref", mutable: false },
importedName
);
}
imports[module] = importsByModule;
});
}
names.push(name);
}
if (promises.length === 0) {
return;
}
await Promise.all(promises);
}

async function load(path, options) {
if (!options) {
options = {};
}

const importObj = {};
const defaultsResult = defaults(importObj);
if (typeof options.installImports !== "undefined") {
options.installImports(importObj);
}

let deobfuscatorOptions = options.stackDeobfuscator || {};
let debugInfoLocation = deobfuscatorOptions.infoLocation || "auto";
let [deobfuscatorFactory, { module, instance }, debugInfo] = await Promise.all([
let [deobfuscatorFactory, module, debugInfo] = await Promise.all([
deobfuscatorOptions.enabled ? getDeobfuscator(path, deobfuscatorOptions) : Promise.resolve(null),
WebAssembly.instantiateStreaming(fetch(path), importObj),
WebAssembly.compileStreaming(fetch(path)),
fetchExternalDebugInfo(path, debugInfoLocation, deobfuscatorOptions)
]);

const importObj = {};
const defaultsResult = defaults(importObj);
if (typeof options.installImports !== "undefined") {
options.installImports(importObj);
}
if (!options.noAutoImports) {
await wrapImports(module, importObj);
}
let instance = new WebAssembly.Instance(module, importObj);

defaultsResult.supplyExports(instance.exports);
if (deobfuscatorFactory) {
let moduleToPass = debugInfoLocation === "auto" || debugInfoLocation === "embedded" ? module : null;
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/js/wasm-gc-runtime/simple-wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@
var TeaVM = TeaVM || {};
TeaVM.wasmGC = TeaVM.wasmGC || (() => {
include();
return { load, defaults };
return { load, defaults, wrapImport };
})();

2 changes: 0 additions & 2 deletions jso/impl/src/main/java/org/teavm/jso/impl/JS.java
Original file line number Diff line number Diff line change
Expand Up @@ -731,13 +731,11 @@ public static native JSObject construct(JSObject cls, JSObject a, JSObject b, JS

@InjectedBy(JSNativeInjector.class)
@JSBody(params = { "instance", "index" }, script = "return instance[index];")
@Import(name = "getProperty", module = "teavmJso")
public static native JSObject get(JSObject instance, JSObject index);

@InjectedBy(JSNativeInjector.class)
@JSBody(params = { "instance", "index" }, script = "return instance[index];")
@NoSideEffects
@Import(name = "getPropertyPure", module = "teavmJso")
public static native JSObject getPure(JSObject instance, JSObject index);

@InjectedBy(JSNativeInjector.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ public String[] parameterNames() {
return parameterNames.clone();
}

@Override
public JsBodyImportInfo[] imports() {
return imports.clone();
}

@Override
public void emit(InjectorContext context) {
var astWriter = new AstWriter(context.getWriter(), new DefaultGlobalNameWriter());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ public String[] parameterNames() {
return parameterNames.clone();
}

@Override
public JsBodyImportInfo[] imports() {
return imports.clone();
}

@Override
public void emit(InjectorContext context) {
emit(context.getWriter(), new EmissionStrategy() {
Expand Down
2 changes: 2 additions & 0 deletions jso/impl/src/main/java/org/teavm/jso/impl/JSBodyEmitter.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,7 @@ public interface JSBodyEmitter {

String[] parameterNames();

JsBodyImportInfo[] imports();

boolean isStatic();
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@

class WasmGCJSFunctions {
private WasmFunction[] constructors = new WasmFunction[32];
private WasmFunction[] binds = new WasmFunction[32];
private WasmFunction[] callers = new WasmFunction[32];
private WasmFunction getFunction;

WasmFunction getFunctionConstructor(WasmGCJsoContext context, int index) {
var function = constructors[index];
Expand All @@ -40,6 +42,37 @@ WasmFunction getFunctionConstructor(WasmGCJsoContext context, int index) {
return function;
}

WasmFunction getBind(WasmGCJsoContext context, int index) {
var function = binds[index];
if (function == null) {
var extern = WasmType.SpecialReferenceKind.EXTERN.asNonNullType();
var constructorParamTypes = new WasmType[index + 1];
Arrays.fill(constructorParamTypes, WasmType.Reference.EXTERN);
var functionType = context.functionTypes().of(extern, constructorParamTypes);
function = new WasmFunction(functionType);
function.setName(context.names().topLevel("teavm.js:bindFunction" + index));
function.setImportModule("teavmJso");
function.setImportName("bindFunction" + index);
context.module().functions.add(function);
binds[index] = function;
}
return function;
}

WasmFunction getGet(WasmGCJsoContext context) {
var function = getFunction;
if (function == null) {
var functionType = context.functionTypes().of(WasmType.Reference.EXTERN, WasmType.Reference.EXTERN);
function = new WasmFunction(functionType);
function.setName(context.names().topLevel("teavm.js:getProperty"));
function.setImportModule("teavmJso");
function.setImportName("getProperty");
context.module().functions.add(function);
getFunction = function;
}
return function;
}

WasmFunction getFunctionCaller(WasmGCJsoContext context, int index) {
var function = callers[index];
if (function == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,16 @@
class WasmGCJSIntrinsic implements WasmGCIntrinsic {
private WasmFunction globalFunction;
private WasmGCJsoCommonGenerator commonGen;
private WasmGCJSFunctions functions;

WasmGCJSIntrinsic(WasmGCJsoCommonGenerator commonGen) {
WasmGCJSIntrinsic(WasmGCJsoCommonGenerator commonGen, WasmGCJSFunctions functions) {
this.commonGen = commonGen;
this.functions = functions;
}

@Override
public WasmExpression apply(InvocationExpr invocation, WasmGCIntrinsicContext context) {
var jsoContext = WasmGCJsoContext.wrap(context);
switch (invocation.getMethod().getName()) {
case "wrap":
return wrapString(invocation.getArguments().get(0), context);
Expand All @@ -64,6 +67,10 @@ public WasmExpression apply(InvocationExpr invocation, WasmGCIntrinsicContext co
return new WasmIsNull(context.generate(invocation.getArguments().get(0)));
case "jsArrayItem":
return arrayItem(invocation, context);
case "get":
case "getPure":
return new WasmCall(functions.getGet(jsoContext), context.generate(invocation.getArguments().get(0)),
context.generate(invocation.getArguments().get(1)));
default:
throw new IllegalArgumentException();
}
Expand Down
Loading

0 comments on commit 7f03cce

Please sign in to comment.