diff --git a/jsaddleJS/jsaddle.js b/jsaddleJS/jsaddle.js index b8bdf2f..1cf82c3 100644 --- a/jsaddleJS/jsaddle.js +++ b/jsaddleJS/jsaddle.js @@ -1,3 +1,7 @@ +// JSaddle JS code +// The code is copied from jsaddle/src/Language/Javascript/JSaddle/Run/Files.hs + +// @@@@ START of JSaddle JS code @@@@ var dec = new TextDecoder(); var enc = new TextEncoder(); @@ -279,10 +283,134 @@ function jsaddleHandler(msg) { runBatch(batch); } +// ghcjs helper functions +function h$isNumber(o) { + return typeof(o) === 'number'; +} + +// returns true for null, but not for functions and host objects +function h$isObject(o) { + return typeof(o) === 'object'; +} + +function h$isString(o) { + return typeof(o) === 'string'; +} + +function h$isSymbol(o) { + return typeof(o) === 'symbol'; +} + +function h$isBoolean(o) { + return typeof(o) === 'boolean'; +} + +function h$isFunction(o) { + return typeof(o) === 'function'; +} + +function h$jsTypeOf(o) { + var t = typeof(o); + if(t === 'undefined') return 0; + if(t === 'object') return 1; + if(t === 'boolean') return 2; + if(t === 'number') return 3; + if(t === 'string') return 4; + if(t === 'symbol') return 5; + if(t === 'function') return 6; + return 7; // other, host object etc +} + +function h$jsonTypeOf(o) { + if (!(o instanceof Object)) { + if (o == null) { + return 0; + } else if (typeof o == 'number') { + if (h$isInteger(o)) { + return 1; + } else { + return 2; + } + } else if (typeof o == 'boolean') { + return 3; + } else { + return 4; + } + } else { + if (Object.prototype.toString.call(o) == '[object Array]') { + // it's an array + return 5; + } else if (!o) { + // null + return 0; + } else { + // it's an object + return 6; + } + } + +} +function h$roundUpToMultipleOf(n,m) { + var rem = n % m; + return rem === 0 ? n : n - rem + m; +} + +function h$newByteArray(len) { + var len0 = Math.max(h$roundUpToMultipleOf(len, 8), 8); + var buf = new ArrayBuffer(len0); + return { buf: buf + , len: len + , i3: new Int32Array(buf) + , u8: new Uint8Array(buf) + , u1: new Uint16Array(buf) + , f3: new Float32Array(buf) + , f6: new Float64Array(buf) + , dv: new DataView(buf) + } +} +function h$wrapBuffer(buf, unalignedOk, offset, length) { + if(!unalignedOk && offset && offset % 8 !== 0) { + throw ("h$wrapBuffer: offset not aligned:" + offset); + } + if(!buf || !(buf instanceof ArrayBuffer)) + throw "h$wrapBuffer: not an ArrayBuffer" + if(!offset) { offset = 0; } + if(!length || length < 0) { length = buf.byteLength - offset; } + return { buf: buf + , len: length + , i3: (offset%4) ? null : new Int32Array(buf, offset, length >> 2) + , u8: new Uint8Array(buf, offset, length) + , u1: (offset%2) ? null : new Uint16Array(buf, offset, length >> 1) + , f3: (offset%4) ? null : new Float32Array(buf, offset, length >> 2) + , f6: (offset%8) ? null : new Float64Array(buf, offset, length >> 3) + , dv: new DataView(buf, offset, length) + }; +} +function h$newByteArrayFromBase64String(base64) { + var bin = window.atob(base64); + var ba = h$newByteArray(bin.length); + var u8 = ba.u8; + for (var i = 0; i < bin.length; i++) { + u8[i] = bin.charCodeAt(i); + } + return ba; +} +function h$byteArrayToBase64String(off, len, ba) { + var bin = ''; + var u8 = ba.u8; + var end = off + len; + for (var i = off; i < end; i++) { + bin += String.fromCharCode(u8[i]); + } + return window.btoa(bin); +} + +// @@@@ END of JSaddle JS code @@@@ + // Communication with JSaddleDevice running in the webabi webworker // Webabi Device -> JS -// channel is used to receive messages for each SYS_Write call +// MessageChannel is used to receive messages for each SYS_Write call // This is a non-blocking call on the webabi side // var channel = new MessageChannel(); @@ -315,19 +443,21 @@ function jsaddleHandlerMsgs (msgs) { // JS -> Webabi Device // SharedArrayBuffer is used to communicate back to JSaddleDevice in wasm side. -// Since the jsaddle-wasm will do a SYS_read whenever it is free -// append all the messages in this buffer. +// The jsaddle-wasm will do a SYS_read to read the data. // -// First UInt (32 bits), hold a lock to the read/write of this shared buffer +// The first Int (32 bits) hold a lock to the read/write of this shared buffer // and this value should be read/written with atomic operations. -// Second UInt (32 bits), indicate size of payload currently in this buffer +// The second UInt (32 bits) indicates the total size of payload currently in the buffer // After that buffer contains the payload // Note: the payload can contain multiple encoded messages -// There for each message is prepended with its own size. +// Each message is prepended with its own size. var jsaddleMsgSharedBuf = new SharedArrayBuffer(10*1024*1024); var jsaddleMsgBufArray = new Uint8Array(jsaddleMsgSharedBuf); var jsaddleMsgBufArray32 = new Uint32Array(jsaddleMsgSharedBuf); +// Atomics.wait need Int32 +var jsaddleMsgBufArrayInt32 = new Int32Array(jsaddleMsgSharedBuf); +var jsaddle_sendMsgWorker = new Worker('jsaddle_sendMsgWorker.js'); function sendAPI (msg) { var str = JSON.stringify(msg); @@ -338,32 +468,19 @@ function sendAPI (msg) { dataview.setUint32(0, size); const uint8 = new Uint8Array(b); uint8.set(a, 4); - // non-blocking - appendMsgToSharedBuf(uint8); -} - -async function appendMsgToSharedBuf(buf) { - var isAlreadyLocked = Atomics.compareExchange(jsaddleMsgBufArray32, 0, 0, 1); - if (isAlreadyLocked === 1) { - Atomics.wait(jsaddleMsgBufArray32, 0, 0); - appendMsgToSharedBuf(buf); - } else { - var len = buf.byteLength; - var prevLen = jsaddleMsgBufArray32[1]; - var totalLen = len + prevLen; - var startOffset = prevLen + 8; // Two 32 bit uint - var i = len; - while (i--) jsaddleMsgBufArray[startOffset + i] = buf[i]; - jsaddleMsgBufArray32[1] = totalLen; - // Release the lock - jsaddleMsgBufArray32[0] = 0; - } + jsaddle_sendMsgWorker.postMessage({ + buf: b, + jsaddleMsgBufArrayInt32: jsaddleMsgBufArrayInt32, + jsaddleMsgBufArray32: jsaddleMsgBufArray32, + jsaddleMsgBufArray: jsaddleMsgBufArray + }, [b]); } function jsaddleJsInit() { return { jsaddleListener: channel.port2, jsaddleMsgBufArray: jsaddleMsgBufArray, - jsaddleMsgBufArray32: jsaddleMsgBufArray32 + jsaddleMsgBufArray32: jsaddleMsgBufArray32, + jsaddleMsgBufArrayInt32: jsaddleMsgBufArrayInt32 }; } diff --git a/jsaddleJS/jsaddle_sendMsgWorker.js b/jsaddleJS/jsaddle_sendMsgWorker.js new file mode 100644 index 0000000..bc3b010 --- /dev/null +++ b/jsaddleJS/jsaddle_sendMsgWorker.js @@ -0,0 +1,42 @@ +// Atomics.wait is not possible on main thread +// so this worker takes the messages from jsaddle.js and appends to the SharedArrayBuffer + +// The payload (msg) contains 4 bytes for length + actual message +// The whole payload is written to the SharedArrayBuffer in one go +// but it might be read by the other side in pieces +// While the HS side is reading the messages, it will keep the lock (the value of 0 index will be non-zero) +onmessage = function (msg) { + var jsaddleMsgBufArrayInt32 = msg.data.jsaddleMsgBufArrayInt32; + var jsaddleMsgBufArray32 = msg.data.jsaddleMsgBufArray32; + var jsaddleMsgBufArray = msg.data.jsaddleMsgBufArray; + const uint8 = new Uint8Array(msg.data.buf); + var appendMsgToSharedBuf = function () { + var isAlreadyLocked = Atomics.compareExchange(jsaddleMsgBufArrayInt32, 0, 0, 1); + if (isAlreadyLocked !== 0) { + Atomics.wait(jsaddleMsgBufArrayInt32, 0, 0, 50); + return false; + } else { + var len = uint8.length; + var prevLen = jsaddleMsgBufArray32[1]; + var totalLen = len + prevLen; + if (totalLen > 10 * 1024 * 1024) { // Protection against over filling the SharedArrayBuffer + console.log("JSaddle.js warning: SharedArrayBuffer overflow!"); + // Release the lock + jsaddleMsgBufArrayInt32[0] = 0; + return false; + } + var startOffset = prevLen + 8; // Two 32 bit uint + var i = len; + while (i--) jsaddleMsgBufArray[startOffset + i] = uint8[i]; + jsaddleMsgBufArray32[1] = totalLen; + // Release the lock + jsaddleMsgBufArrayInt32[0] = 0; + Atomics.notify(jsaddleMsgBufArrayInt32, 0); + return true; + } + }; + var done = false; + while (done === false) { + done = appendMsgToSharedBuf(); + }; +} diff --git a/package-lock.json b/package-lock.json index 4a89bbf..9c88655 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1577,7 +1577,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -1992,7 +1993,8 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -2048,6 +2050,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2091,12 +2094,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, diff --git a/src/JSaddleDevice.ts b/src/JSaddleDevice.ts index 77b1ba2..e405c1e 100644 --- a/src/JSaddleDevice.ts +++ b/src/JSaddleDevice.ts @@ -11,9 +11,11 @@ export class JSaddleDevice implements Device { constructor( jsaddleListener: MessagePort, jsaddleMsgBufArray: Uint8Array, - jsaddleMsgBufArray32: Uint32Array) { + jsaddleMsgBufArray32: Uint32Array, + jsaddleMsgBufArrayInt32: Int32Array + ) { this._file = new JSaddleDeviceFile(this, jsaddleListener - , jsaddleMsgBufArray, jsaddleMsgBufArray32); + , jsaddleMsgBufArray, jsaddleMsgBufArray32, jsaddleMsgBufArrayInt32); } public open(flag: FileFlag): File { @@ -31,7 +33,8 @@ export class JSaddleDeviceFile extends BaseFile implements File { private _Device: JSaddleDevice, private _jsaddleListener: MessagePort, private _jsaddleMsgBufArray: Uint8Array, - private _jsaddleMsgBufArray32: Uint32Array) { + private _jsaddleMsgBufArray32: Uint32Array, + private _jsaddleMsgBufArrayInt32: Int32Array) { super(); } public getPos(): number | undefined { @@ -94,31 +97,43 @@ export class JSaddleDeviceFile extends BaseFile implements File { } public readSync(buffer: Buffer, offset: number, length: number, position: number | null): number { var bytes_read = 0; - var isAlreadyLocked = Atomics.compareExchange(this._jsaddleMsgBufArray32, 0, 0, 1); - if (isAlreadyLocked === 0) { - var bytes_available = this._jsaddleMsgBufArray32[1]; - if (bytes_available > 0) { - if (bytes_available > length) { - let i : number = length; - bytes_read = i; - while (i--) buffer[offset + i] = this._jsaddleMsgBufArray[i + 8]; - - // Shift the remaining contents, and set size - var target = 8; - var start = length + 8 + 1; - var len = bytes_available - length; - this._jsaddleMsgBufArray.copyWithin(target, start, len); - this._jsaddleMsgBufArray32[1] = len; + var lockValue = Atomics.compareExchange(this._jsaddleMsgBufArrayInt32, 0, 0, 2); + if (lockValue === 1) { // Locked by appendMsgToSharedBuf + Atomics.wait(this._jsaddleMsgBufArrayInt32, 0, 0, 50); + bytes_read = this.readSync(buffer, offset, length, position); + } else { + var releaseLock = true; + var payloadSize = this._jsaddleMsgBufArray32[1]; + if (payloadSize > 0) { + var startCopyFrom = 4; + var prependSizeBytes = 4; + if (lockValue === 3) { // continue append of data + startCopyFrom = 8; + prependSizeBytes = 0; + } + if ((prependSizeBytes + payloadSize) > length) { + bytes_read = length; + releaseLock = false; } else { - var i = bytes_available; - bytes_read = bytes_available; - while (i--) buffer[offset + i] = this._jsaddleMsgBufArray[i + 8]; - // Set remaining bytes to 0 - this._jsaddleMsgBufArray32[1] = 0; + bytes_read = prependSizeBytes + payloadSize; } + buffer.set(this._jsaddleMsgBufArray.subarray(startCopyFrom, startCopyFrom + bytes_read), offset); + + // Shift the remaining contents, and set size + if ((prependSizeBytes + payloadSize) > length) { + this._jsaddleMsgBufArray.copyWithin(8, startCopyFrom + length, payloadSize + 8); + } + this._jsaddleMsgBufArray32[1] = (prependSizeBytes + payloadSize) - bytes_read; + } + if (releaseLock) { + // Release the lock + this._jsaddleMsgBufArrayInt32[0] = 0; + // @ts-ignore + Atomics.notify(this._jsaddleMsgBufArrayInt32, 0); + } else { + // Keep the lock, and continue append of data on next readSync call + this._jsaddleMsgBufArrayInt32[0] = 3; } - // Release the lock - this._jsaddleMsgBufArray32[0] = 0; } return bytes_read; } diff --git a/src/process.ts b/src/process.ts index 754de3a..8cc467a 100644 --- a/src/process.ts +++ b/src/process.ts @@ -536,8 +536,7 @@ export class Process { mmap2(addr: number, len: number, prot: number, flags: number, fd: number, offset: number): number { // Ignore prot and flags var currentSize = this.memoryEnd; - if ((fd === -1) && (offset === 0) - && ((addr === 0) || (addr === currentSize))) { + if ((fd === -1) && (offset === 0)) { var newSize = this.brk(currentSize + len); return currentSize; } else { diff --git a/src/worker_runner.ts b/src/worker_runner.ts index 746b56a..1a0baf6 100644 --- a/src/worker_runner.ts +++ b/src/worker_runner.ts @@ -9,7 +9,8 @@ connectParent({ onMessage: async msg1 => { const jsaddleDevice = new JSaddleDevice( msg.jsaddleVals.jsaddleListener, msg.jsaddleVals.jsaddleMsgBufArray, - msg.jsaddleVals.jsaddleMsgBufArray32); + msg.jsaddleVals.jsaddleMsgBufArray32, + msg.jsaddleVals.jsaddleMsgBufArrayInt32); const fs = await configureFileSystem({ devices: { ["/jsaddle_inout"]: jsaddleDevice } }); (await Process.instantiateProcess(fs, msg.url)).start([],[]); }}); diff --git a/www/jsaddle_sendMsgWorker.js b/www/jsaddle_sendMsgWorker.js new file mode 120000 index 0000000..36abc25 --- /dev/null +++ b/www/jsaddle_sendMsgWorker.js @@ -0,0 +1 @@ +../jsaddleJS/jsaddle_sendMsgWorker.js \ No newline at end of file