carton/entrypoint/common.js

177 lines
5.4 KiB
JavaScript

// Copyright 2020 Carton contributors
//
// 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.
import { WASI } from "@wasmer/wasi";
import { WasmFs } from "@wasmer/wasmfs";
export const WasmRunner = (rawOptions, SwiftRuntime) => {
const options = defaultRunnerOptions(rawOptions);
let swift;
if (SwiftRuntime) {
swift = new SwiftRuntime();
}
const wasmFs = createWasmFS(
(stdout) => {
console.log(stdout);
options.onStdout(stdout);
},
(stderr) => {
console.error(stderr);
options.onStderr(stderr);
}
);
const wasi = new WASI({
args: options.args,
env: {},
bindings: {
...WASI.defaultBindings,
fs: wasmFs.fs,
},
});
const createWasmImportObject = (extraWasmImports, wasmModule) => {
const importObject = {
wasi_snapshot_preview1: wrapWASI(wasi, wasmModule),
};
if (swift) {
importObject.javascript_kit = swift.wasmImports;
}
if (extraWasmImports) {
// Shallow clone
for (const key in extraWasmImports) {
importObject[key] = extraWasmImports[key];
}
}
return importObject;
};
return {
async run(wasmBytes, extraWasmImports) {
if (!extraWasmImports) {
extraWasmImports = {};
}
extraWasmImports.__stack_sanitizer = {
report_stack_overflow: () => {
throw new Error("Detected stack buffer overflow.");
},
};
const module = await WebAssembly.compile(wasmBytes);
const importObject = createWasmImportObject(extraWasmImports, module);
const instance = await WebAssembly.instantiate(module, importObject);
if (swift && instance.exports.swjs_library_version) {
swift.setInstance(instance);
}
// Start the WebAssembly WASI instance
wasi.start(instance);
// Initialize and start Reactor
if (instance.exports._initialize) {
instance.exports._initialize();
if (typeof instance.exports.main === "function") {
instance.exports.main();
} else if (typeof instance.exports.__main_argc_argv === "function") {
// Swift 6.0 and later use `__main_argc_argv` instead of `main`.
instance.exports.__main_argc_argv(0, 0);
}
}
},
};
};
const defaultRunnerOptions = (options) => {
if (!options) return defaultRunnerOptions({});
if (!options.onStdout) {
options.onStdout = () => {};
}
if (!options.onStderr) {
options.onStderr = () => {};
}
if (!options.args) {
options.args = [];
}
return options;
};
const createWasmFS = (onStdout, onStderr) => {
// Instantiate a new WASI Instance
const wasmFs = new WasmFs();
// Output stdout and stderr to console
const originalWriteSync = wasmFs.fs.writeSync;
wasmFs.fs.writeSync = (fd, buffer, offset, length, position) => {
const text = new TextDecoder("utf-8").decode(buffer);
if (text !== "\n") {
switch (fd) {
case 1:
onStdout(text);
break;
case 2:
onStderr(text);
break;
}
}
return originalWriteSync(fd, buffer, offset, length, position);
};
return wasmFs;
};
const wrapWASI = (wasiObject, wasmModule) => {
// PATCH: @wasmer-js/wasi@0.x forgets to call `refreshMemory` in `clock_res_get`,
// which writes its result to memory view. Without the refresh the memory view,
// it accesses a detached array buffer if the memory is grown by malloc.
// But they wasmer team discarded the 0.x codebase at all and replaced it with
// a new implementation written in Rust. The new version 1.x is really unstable
// and not production-ready as far as katei investigated in Apr 2022.
// So override the broken implementation of `clock_res_get` here instead of
// fixing the wasi polyfill.
// Reference: https://github.com/wasmerio/wasmer-js/blob/55fa8c17c56348c312a8bd23c69054b1aa633891/packages/wasi/src/index.ts#L557
const original_clock_res_get = wasiObject.wasiImport["clock_res_get"];
wasiObject.wasiImport["clock_res_get"] = (clockId, resolution) => {
wasiObject.refreshMemory();
return original_clock_res_get(clockId, resolution);
};
// @wasmer-js/wasi polyfill does not support all WASI syscalls like `sock_accept`.
// So we need to insert a dummy function for unimplemented syscalls.
const __WASI_ERRNO_NOTSUP = 58;
for (const importEntry of WebAssembly.Module.imports(wasmModule)) {
const { module: importModule, name: importName, kind: importKind } = importEntry;
// Skip dummy import entries for non-WASI and already implemented syscalls.
if (
importModule !== "wasi_snapshot_preview1" ||
importKind !== "function" ||
wasiObject.wasiImport[importName]
) {
continue;
}
wasiObject.wasiImport[importName] = () => {
console.warn(`WASI syscall ${importModule}.${importName} is not supported, returning ENOTSUP.`);
return __WASI_ERRNO_NOTSUP;
}
}
return wasiObject.wasiImport;
};