blob: 01e19a1c18f4e787513b75325453961b129d81bd [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
/**
* Runtime environment that provide js libaries calls.
*/
import { Pointer } from "./ctypes";
import { LibraryProvider } from "./types";
import { assert } from "./support";
import * as ctypes from "./ctypes";
/**
* Detect library provider from the importObject.
*
* @param importObject The import object.
*/
function detectLibraryProvider(
importObject: Record<string, any>
): LibraryProvider | undefined {
if (
importObject["wasmLibraryProvider"] &&
importObject["wasmLibraryProvider"]["start"] &&
importObject["wasmLibraryProvider"]["imports"] !== undefined
) {
const item = importObject as { wasmLibraryProvider: LibraryProvider };
// create provider so that we capture imports in the provider.
return {
imports: item.wasmLibraryProvider.imports,
start: (inst: WebAssembly.Instance): void => {
item.wasmLibraryProvider.start(inst);
},
};
} else if (importObject["imports"] && importObject["start"] !== undefined) {
return importObject as LibraryProvider;
} else if (importObject["wasiImport"] && importObject["start"] !== undefined) {
// WASI
return {
imports: {
"wasi_snapshot_preview1": importObject["wasiImport"],
},
start: (inst: WebAssembly.Instance): void => {
importObject["start"](inst);
}
};
} else {
return undefined;
}
}
/**
* Environment to impelement most of the JS library functions.
*/
export class Environment implements LibraryProvider {
logger: (msg: string) => void;
imports: Record<string, any>;
/**
* Maintains a table of FTVMWasmPackedCFunc that the C part
* can call via TVMWasmPackedCFunc.
*
* We maintain a separate table so that we can have un-limited amount
* of functions that do not maps to the address space.
*/
packedCFuncTable: Array<ctypes.FTVMFFIWasmSafeCallType | undefined> = [
undefined,
];
/**
* Free table index that can be recycled.
*/
packedCFuncTableFreeId: Array<number> = [];
private libProvider?: LibraryProvider;
constructor(
importObject: Record<string, any> = {},
logger: (msg: string) => void = console.log
) {
this.logger = logger;
this.libProvider = detectLibraryProvider(importObject);
// get imports from the provider
if (this.libProvider !== undefined) {
this.imports = this.libProvider.imports;
} else {
this.imports = importObject;
}
// update with more functions
this.imports.env = this.environment(this.imports.env);
}
/** Mark the start of the instance. */
start(inst: WebAssembly.Instance): void {
if (this.libProvider !== undefined) {
this.libProvider.start(inst);
}
}
private environment(initEnv: Record<string, any>): Record<string, any> {
// default env can be overriden by libraries.
const defaultEnv = {
"__cxa_thread_atexit": (): void => {},
// eslint-disable-next-line @typescript-eslint/no-unused-vars
"emscripten_notify_memory_growth": (index: number): void => {}
};
const wasmSafeCall: ctypes.FTVMFFIWasmSafeCallType = (
self: Pointer,
args: Pointer,
num_args: number,
result: Pointer
): number => {
const cfunc = this.packedCFuncTable[self];
assert(cfunc !== undefined);
return cfunc(self, args, num_args, result);
};
const wasmFunctionDeleter: ctypes.FTVMFFIWasmFunctionDeleter = (
self: Pointer
): void => {
this.packedCFuncTable[self] = undefined;
this.packedCFuncTableFreeId.push(self);
};
const newEnv = {
"TVMFFIWasmSafeCall": wasmSafeCall,
"TVMFFIWasmFunctionDeleter": wasmFunctionDeleter,
"__console_log": (msg: string): void => {
this.logger(msg);
}
};
return Object.assign(defaultEnv, initEnv, newEnv);
}
}