UNPKG

wasmux

Version:

WebAssembly toolchain for compiling libc and kernel into system-level WASM modules

561 lines (509 loc) 16.5 kB
/* * MIT License * * Copyright (c) 2026 Yurii Yakubin (yurii.yakubin@gmail.com) * * Permission is granted to use, copy, modify, and distribute this software * under the MIT License. See LICENSE file for details. */ "use strict"; const WEB_KERNEL_MEMORY_ID = 1; const WEB_KERNEL_MODULE_ID = 2; const WEI_WORKER_CTOR_ID = 5; const WEI_FALSE_BOOLEAN_ID = 6; const WEI_TRUE_BOOLEAN_ID = 7; const WEB_NULL_OBJECT_ID = 8; const WEB_GLOBAL_THIS_ID = 9; const WEB_SCRIPT_URL_ID = 10; const WEB_ConsoleWrite = 11; const WEB_ObjectRelease = 12; const WEB_NumberCreate = 13; const WEB_UserInstanceStart = 14; const WEB_PostMessage2 = 15; const WEB_PostMessage3 = 16; const WEB_PostMessage4 = 17; const WEB_PostMessage5 = 18; const WEB_PostMessage6 = 19; const WEB_WorkerInstance = 20; const WEB_MemoryCopy = 21; const WEB_MemorySize = 22; const WEB_MemoryGrow = 23; const WEB_MemoryStrnlen = 24; const WEB_MemoryStrncpy = 25; const WEB_MemoryFill = 26; const WEB_StringCreate = 27; const WEB_StringCreateByArgs = 28; const WEB_CallObjectMethod = 29; const WEB_CallIntegerMethod = 30; const WEB_ObjectCreate = 31; const WEB_GetObjectProperty = 32; const WEB_GetIntegerProperty = 33; const WEB_SetObjectProperty = 34; const WEB_SetIntegerProperty = 35; const WEB_AddEventListener = 36; const WEB_PromiseThen = 37; const WEB_SetTimeout = 38; const WEB_SetInterval = 39; const WEB_ClearTimeout = 40; const WEB_ClearInterval = 41; const WEB_ObjectRetain = 42; const WEB_RemoveEventListener = 43; const WEB_WorkerPerform2 = 44; const WEB_WorkerPerform3 = 45; const WEB_WorkerPerform4 = 46; const WEB_WorkerPerform5 = 47; function sys_NotImplemented() { console.error('Not Implemented'); return -1; } function sys_ConsoleWrite(level, msg_id) { const message = this._objects[msg_id]; switch (level) { case 3: console.error(message); break; case 4: console.warn(message); break; case 6: console.info(message); break; case 7: console.debug(message); break; default: console.log(message); break; } return message.length; } function sys_MemoryCopy(dst_mid, dst_offset, src_mid, src_offset, size) { const dstObj = this._objects[dst_mid]; const srcObj = this._objects[src_mid]; let dstBuf = dstObj.buffer; if (!dstBuf) // Not WebAssembly.Memory dstBuf = dstObj; // FIXME: ArrayBuffer, SharedBuffer, etc let srcBuf = srcObj.buffer; if (!srcBuf) // Not WebAssembly.Memory srcBuf = srcObj; // FIXME: ArrayBuffer, SharedBuffer, etc const dst = new Uint8Array(dstBuf, dst_offset, size); const src = new Uint8Array(srcBuf, src_offset, size); for (let i = 0; i < size; i++) { dst[i] = src[i]; } return 0; } function sys_MemoryFill(mid, offset, value, length) { const bytes = new Uint8Array(this._objects[mid].buffer, offset, length); bytes.fill(value); return 0; } function sys_MemoryStrnlen(mid, offset, length) { // TODO: On exception, returns 0 const bytes = new Uint8Array(this._objects[mid].buffer, offset, length); let len = 0; while (len < bytes.length && bytes[len] != 0) { len++; } return len + 1; } function sys_MemoryStrncpy(dstMemory, dstOffset, srcMemory, srcOffset, count) { const dstObj = this._objects[dstMemory]; const srcObj = this._objects[srcMemory]; const dst = new Uint8Array(dstObj.buffer, dstOffset, count); const src = new Uint8Array(srcObj.buffer, srcOffset, count); let result = 0; while (result < count) { if (!(dst[result] = src[result])) break; result++; } return result; } function sys_MemorySize(memoryId) { const buffer = this._objects[memoryId].buffer; return buffer.byteLength; } function sys_MemoryGrow(memoryId, delta) { const memory = this._objects[memoryId]; return memory.grow(delta); } function sys_UserInstanceStart(moduleId, memoryId) { const module = this._objects[moduleId]; const memory = this._objects[memoryId]; const imports = { env: { memory }, kernel: this._kernel, }; this._uinst = new WebAssembly.Instance(module, imports); // TODO: return instanceId this._uinst.exports._start(); } function sys_WorkerInstance(workerId) { const worker = this._objects[workerId]; worker.onmessage = (event) => { this.perform(...event.data); }; worker.onerror = (event) => { console.log("<<<", event); }; worker.postMessage({ type: 'instance', kernelModule: this._kernelModule, kernelMemory: this._kernelMemory, }); return 0; } function sys_ClearInterval(intervalId) { clearInterval(intervalId); } function sys_ClearTimeout(timeoutId) { clearTimeout(timeoutId); } function sys_SetInterval(callback, userdata, delayMs) { return setInterval(this._kernel.perform, delayMs, 2, callback, userdata); } function sys_SetTimeout(callback, userdata, delayMs) { return setTimeout(this._kernel.perform, delayMs, 2, callback, userdata); } function sys_ObjectRetain(objectId) { if (!objectId) return 0; const object = this._objects[objectId]; return this.createObjectId(object); } function sys_ObjectRelease(id) { if (!this._objects.hasOwnProperty(id)) return -1; this.deleteObjectId(id); return 0; } function sys_StringCreate(memoryId, offset, size) { const buffer = this._objects[memoryId].buffer; const bytes = new Uint8Array(buffer, offset, size); const str = String.fromCharCode.apply(String, bytes); return this.createObjectId(str); } function sys_StringCreateByArgs() { const len = arguments[0] & 0xff; if (len >= arguments.length * 4) { return -1; // Error: Invalid argument } let index = 0; let shift = 8; let bytes = new Uint8Array(len); for (let i = 0; i < len; i++) { bytes[i] = (arguments[index] >> shift) & 0xff; shift += 8; if (shift >= 32) { shift = 0; index++; } } const decoder = new TextDecoder("utf-8"); const str = decoder.decode(bytes); return this.createObjectId(str); } function sys_CallObjectMethod(objectId, methodId) { const object = this._objects[objectId]; const method = this._objects[methodId]; const args = []; for (let i = 2; i < arguments.length; i++) { const id = arguments[i]; if (!id) { break; } args.push(this._objects[id]); } const result = object[method](...args); return this.createObjectId(result); } function sys_CallIntegerMethod(objectId, methodId) { const object = this._objects[objectId]; const method = this._objects[methodId]; const args = []; for (let i = 2; i < arguments.length; i++) { const id = arguments[i]; if (!id) { break; } args.push(this._objects[id]); } return object[method](...args); } function sys_ObjectCreate(classId) { const cls = this._objects[classId]; const args = []; for (let i = 1; i < arguments.length; i++) { const id = arguments[i]; if (!id) { break; } args.push(this._objects[id]); } const object = new cls(...args); return this.createObjectId(object); } function sys_NumberCreate(value) { return this.createObjectId(value); } function sys_GetObjectProperty(objectId, propId) { const object = this._objects[objectId]; const prop = this._objects[propId]; const result = object[prop]; if (result === undefined) return 0; return this.createObjectId(result); } function sys_GetIntegerProperty(objectId, propId) { const object = this._objects[objectId]; const prop = this._objects[propId]; return object[prop]; } function sys_SetObjectProperty(objectId, propId, valueId) { const object = this._objects[objectId]; const prop = this._objects[propId]; const value = this._objects[valueId]; object[prop] = value; return 0; } function sys_SetIntegerProperty(objectId, propId, value) { const object = this._objects[objectId]; const prop = this._objects[propId]; object[prop] = value; return 0; } function sys_AddEventListener(objectId, typeId, callback, userdata) { const object = this._objects[objectId]; const type = this._objects[typeId]; const listener = (event) => { const eventId = this.createObjectId(event); this._kernel.perform(3, callback, userdata, eventId); this.deleteObjectId(eventId); }; object.addEventListener(type, listener); return this.createObjectId(listener); } function sys_RemoveEventListener(objectId, typeId, listenerId) { const object = this._objects[objectId]; const type = this._objects[typeId]; const listener = this._objects[listenerId]; object.removeEventListener(type, listener); this.deleteObjectId(listenerId); return 0; } function sys_PromiseThen(objectId, resultCallback, errorCallback, userdata) { const object = this._objects[objectId]; object.then( (value) => { const resultId = this.createObjectId(value); this._kernel.perform(3, resultCallback, userdata, resultId); this.deleteObjectId(resultId); }, (reason) => { const reasonId = this.createObjectId(reason); this._kernel.perform(3, errorCallback, userdata, reasonId); this.deleteObjectId(reasonId); }, ); return 0; } function sys_WorkerPerform2(workerId, callback, userdata) { const worker = this._objects[workerId]; worker.postMessage({ type: 'perform', args: [ callback, userdata, ] }); } function sys_WorkerPerform3(workerId, callback, userdata, arg1) { const worker = this._objects[workerId]; worker.postMessage({ type: 'perform', args: [ callback, userdata, arg1 ? this._objects[arg1] : undefined, ] }); } function sys_WorkerPerform4(workerId, callback, userdata, arg1, arg2) { const worker = this._objects[workerId]; worker.postMessage({ type: 'perform', args: [ callback, userdata, arg1 ? this._objects[arg1] : undefined, arg2 ? this._objects[arg2] : undefined, ] }); } function sys_WorkerPerform5(workerId, callback, userdata, arg1, arg2, arg3) { const worker = this._objects[workerId]; worker.postMessage({ type: 'perform', args: [ callback, userdata, arg1 ? this._objects[arg1] : undefined, arg2 ? this._objects[arg2] : undefined, arg3 ? this._objects[arg3] : undefined, ] }); } function sys_PostMessage2(callback, userdata) { self.postMessage([ callback, userdata, ]); } function sys_PostMessage3(callback, userdata, arg1) { self.postMessage([ callback, userdata, arg1 ? this._objects[arg1] : undefined, ]); } function sys_PostMessage4(callback, userdata, arg1, arg2) { self.postMessage([ callback, userdata, arg1 ? this._objects[arg1] : undefined, arg2 ? this._objects[arg2] : undefined, ]); } function sys_PostMessage5(callback, userdata, arg1, arg2, arg3) { self.postMessage([ callback, userdata, arg1 ? this._objects[arg1] : undefined, arg2 ? this._objects[arg2] : undefined, arg3 ? this._objects[arg3] : undefined, ]); } function sys_PostMessage6(callback, userdata, arg1, arg2, arg3, arg4) { self.postMessage([ callback, userdata, arg1 ? this._objects[arg1] : undefined, arg2 ? this._objects[arg2] : undefined, arg3 ? this._objects[arg3] : undefined, arg4 ? this._objects[arg4] : undefined, ]); } function WasmuxRuntime(kernelModule, kernelMemory, isWorker, scriptUrl, WorkerCtor) { this._objects = {}; this._objectCount = 0; this._webcalls = {}; this._objectCount = 0x00000100; this._kernelModule = kernelModule; this._kernelMemory = kernelMemory; if (WorkerCtor === undefined && typeof globalThis.Worker === "function") { WorkerCtor = globalThis.Worker; } this._objects[WEB_KERNEL_MEMORY_ID] = kernelMemory; this._objects[WEB_KERNEL_MODULE_ID] = kernelModule; this._objects[3] = null; this._objects[4] = null; this._objects[WEI_WORKER_CTOR_ID] = WorkerCtor; this._objects[WEI_FALSE_BOOLEAN_ID] = false; this._objects[WEI_TRUE_BOOLEAN_ID] = true; this._objects[WEB_NULL_OBJECT_ID] = null; this._objects[WEB_GLOBAL_THIS_ID] = globalThis; this._objects[WEB_SCRIPT_URL_ID] = scriptUrl; this._objects[WEB_ConsoleWrite] = sys_ConsoleWrite; this._objects[WEB_ObjectRelease] = sys_ObjectRelease; this._objects[WEB_NumberCreate] = sys_NumberCreate; this._objects[WEB_MemoryCopy] = sys_MemoryCopy; this._objects[WEB_MemorySize] = sys_MemorySize; this._objects[WEB_MemoryGrow] = sys_MemoryGrow; this._objects[WEB_MemoryStrnlen] = sys_MemoryStrnlen; this._objects[WEB_MemoryStrncpy] = sys_MemoryStrncpy; this._objects[WEB_MemoryFill] = sys_MemoryFill; this._objects[WEB_StringCreate] = sys_StringCreate; this._objects[WEB_StringCreateByArgs] = sys_StringCreateByArgs; this._objects[WEB_CallObjectMethod] = sys_CallObjectMethod; this._objects[WEB_CallIntegerMethod] = sys_CallIntegerMethod; this._objects[WEB_ObjectCreate] = sys_ObjectCreate; this._objects[WEB_GetObjectProperty] = sys_GetObjectProperty; this._objects[WEB_GetIntegerProperty] = sys_GetIntegerProperty; this._objects[WEB_SetObjectProperty] = sys_SetObjectProperty; this._objects[WEB_SetIntegerProperty] = sys_SetIntegerProperty; this._objects[WEB_AddEventListener] = sys_AddEventListener; this._objects[WEB_PromiseThen] = sys_PromiseThen; this._objects[WEB_SetTimeout] = sys_SetTimeout; this._objects[WEB_SetInterval] = sys_SetInterval; this._objects[WEB_ClearTimeout] = sys_ClearTimeout; this._objects[WEB_ClearInterval] = sys_ClearInterval; this._objects[WEB_ObjectRetain] = sys_ObjectRetain; this._objects[WEB_RemoveEventListener] = sys_RemoveEventListener; this._objects[WEB_WorkerInstance] = isWorker ? sys_NotImplemented : sys_WorkerInstance; this._objects[WEB_WorkerPerform2] = isWorker ? sys_NotImplemented : sys_WorkerPerform2; this._objects[WEB_WorkerPerform3] = isWorker ? sys_NotImplemented : sys_WorkerPerform3; this._objects[WEB_WorkerPerform4] = isWorker ? sys_NotImplemented : sys_WorkerPerform4; this._objects[WEB_WorkerPerform5] = isWorker ? sys_NotImplemented : sys_WorkerPerform5; this._objects[WEB_UserInstanceStart] = isWorker ? sys_UserInstanceStart : sys_NotImplemented; this._objects[WEB_PostMessage2] = isWorker ? sys_PostMessage2 : sys_NotImplemented; this._objects[WEB_PostMessage3] = isWorker ? sys_PostMessage3 : sys_NotImplemented; this._objects[WEB_PostMessage4] = isWorker ? sys_PostMessage4 : sys_NotImplemented; this._objects[WEB_PostMessage5] = isWorker ? sys_PostMessage5 : sys_NotImplemented; this._objects[WEB_PostMessage6] = isWorker ? sys_PostMessage6 : sys_NotImplemented; } WasmuxRuntime.prototype.makeImports = function() { return { env: { memory: this._kernelMemory, _math_random: Math.random, _date_now: Date.now, _performance_now: performance.now.bind(performance), _webcall: (number, arg1, arg2, arg3, arg4, arg5, arg6) => this._objects[number].call(this, arg1, arg2, arg3, arg4, arg5, arg6), }, }; } WasmuxRuntime.prototype.init = async function() { // WebAssembly.Instance is disallowed on the main thread, if the buffer size is larger than 8MB const kinst = await WebAssembly.instantiate(this._kernelModule, this.makeImports()); this._kernel = kinst.exports; } WasmuxRuntime.prototype.getStringByMid = function(mid, offset, size) { const buffer = this._objects[mid].buffer; const bytes = new Uint8Array(buffer, offset, size); return String.fromCharCode.apply(String, bytes); } WasmuxRuntime.prototype.createObjectId = function(object) { const objectId = this._objectCount++; this._objects[objectId] = object; return objectId; } WasmuxRuntime.prototype.deleteObjectId = function(objectId) { delete this._objects[objectId]; } WasmuxRuntime.prototype.perform = function(callback, userdata, ...args) { const argsIds = args.map((o) => o !== undefined ? this.createObjectId(o) : 0); this._kernel.perform(2 + argsIds.length, callback, userdata, ...argsIds); argsIds.forEach(i => i && this.deleteObjectId(i)); } let system = null; if (typeof WorkerGlobalScope !== 'undefined' && globalThis instanceof WorkerGlobalScope) { globalThis.addEventListener("message", (event) => { const { data } = event; switch (data.type) { case 'instance': system = new WasmuxRuntime(data.kernelModule, data.kernelMemory, true, null); system.init(); break; case 'perform': system.perform(...data.args); break; } }); if (self !== globalThis) { throw "Variable self is not globalThis"; } } else { module.exports = WasmuxRuntime; }