Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes to the communication channel code #14

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
171 changes: 144 additions & 27 deletions jsaddleJS/jsaddle.js
Original file line number Diff line number Diff line change
@@ -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();

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand All @@ -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
};
}
42 changes: 42 additions & 0 deletions jsaddleJS/jsaddle_sendMsgWorker.js
Original file line number Diff line number Diff line change
@@ -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();
};
}
13 changes: 9 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 40 additions & 25 deletions src/JSaddleDevice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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;
}
Expand Down
3 changes: 1 addition & 2 deletions src/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading