diff --git a/fs.js b/fs.js index e2282a4..d914c79 100644 --- a/fs.js +++ b/fs.js @@ -5,6 +5,24 @@ if(typeof exports !== 'undefined'){ var bfs = BrowserFS.BFSRequire('fs'); var bfs_utils = BrowserFS.BFSRequire('bfs_utils'); +// Sub-worker, collecting incoming messages from JS side jsaddle +var jsaddleListener = new Worker('jsaddle_listener.js'); + +// SharedArrayBuffer to communicate between wasm and jsaddleListener workers +// Just for incoming messages/SYS_read +// The write can happen via non-blocking postMessage +// +// The wasm worker is supposed to read one incoming message at a time +// So this buffer will contain only one message at a time. + +// First UInt (32 bits), indicate buffer data size +// Followed by data +var jsaddleMsgSharedBuf = new SharedArrayBuffer(1024*1024); +var jsaddleMsgBufArray = new Uint8Array(jsaddleMsgSharedBuf); +var jsaddleMsgBufArray32 = new Uint32Array(jsaddleMsgSharedBuf); + +var JSADDLE_INOUT_FD = 4; +var JSADDLE_INOUT_DEV = "/dev/jsaddle_inout"; // Certain directories are required by POSIX (and musl). In case there is an // in memory filesystem under root we need to take care to create these manually. @@ -96,11 +114,27 @@ function catchApiError(f) { } var fs = { + + initFsJSaddle: function (jsaddleChannelPort) { + jsaddleListener.postMessage( + {type: 'init' + , jsaddleMsgBufArray32: jsaddleMsgBufArray32 + , jsaddleMsgBufArray: jsaddleMsgBufArray + , jsaddleChannelPort: jsaddleChannelPort} + , [jsaddleChannelPort]); + }, + openat: function (dirfd, pathname, flags, mode) { - if (dirfd !== AT_FDCWD) { - utils.warn('openat: TODO: dirfd other than AT_FDCWD, ignoring'); + if (pathname == JSADDLE_INOUT_DEV) { + // FIXME set a valid value here + JSADDLE_INOUT_FD = 4; + return JSADDLE_INOUT_FD; + } else { + if (dirfd !== AT_FDCWD) { + utils.warn('openat: TODO: dirfd other than AT_FDCWD, ignoring'); + } + return catchApiError(() => bfs.openSync(pathname, flagsToString(flags), mode)); } - return catchApiError(() => bfs.openSync(pathname, flagsToString(flags), mode)); }, creat: function (pathname, mode) { @@ -108,16 +142,51 @@ var fs = { }, read: function (fd, buf, offset, count) { - var b = bfs_utils.uint8Array2Buffer(buf); - return catchApiError(() => bfs.readSync(fd, b, offset, count, null)); + if (fd === JSADDLE_INOUT_FD) { + var bytes_read = 0; + var bytes_available = jsaddleMsgBufArray32[0]; + if (bytes_available > 0) { + if (bytes_available > count) { + var i = count; + bytes_read = i; + while (i--) buf[offset + i] = jsaddleMsgBufArray[i + 4]; + + // Shift the remaining contents, and set size + var target = 4; + var start = count + 4 + 1; + var len = bytes_available - count; + jsaddleMsgBufArray.copyWithin(target, start, len); + jsaddleMsgBufArray32[0] = len; + } else { + var i = bytes_available; + bytes_read = bytes_available; + while (i--) buf[offset + i] = jsaddleMsgBufArray[i + 4]; + //buf[offset + bytes_available + 1] = 0; + + // Set remaining bytes to 0 + jsaddleMsgBufArray32[0] = 0; + // Tell jsaddle listener that buffer has been read + jsaddleListener.postMessage({type: 'read'}); + } + } + return bytes_read; + } else { + var b = bfs_utils.uint8Array2Buffer(buf); + return catchApiError(() => bfs.readSync(fd, b, offset, count, null)); + } }, write: function (fd, buf, offset, count) { - // stdout is handled specially here, we may have a more robust solution - // in the future that does not assume stdout is connected to console - if (fd === 1) { + // stdout and stderr are handled specially here, we may have a more robust solution + // in the future that does not assume they are connected to console + if ((fd === 1) || (fd === 2)) { utils.stdout__write(utils.bufToStr(buf, offset, offset + count)); return count; + } else if (fd === JSADDLE_INOUT_FD) { + var a = new Uint8Array(buf.slice(offset, offset + count)); + var b = a.buffer; + jsaddleListener.postMessage({type: 'write', buf: b}, [b]); + return count; } else { var b = bfs_utils.uint8Array2Buffer(buf); return catchApiError(() => bfs.writeSync(fd, b, offset, count, null)); @@ -198,9 +267,24 @@ var fs = { ftruncate: function (fd, length) { return catchApiError(() => bfs.ftruncate(fd, length)); + }, + + fstat64: function (fd, statbuf_) { + if (fd === JSADDLE_INOUT_FD) { + // Set device type in st_mode field + // S_IFCHR 0020000 character device (octal) + // sizeof (st_dev) 8 + // sizeof (st_ino) 8 + // sizeof (st_mode) 4 + st_mode_ptr = (statbuf_ + 16) / 4; + heap_uint32[st_mode_ptr] = 8192; + return 0; + } else { + throw "SYS_fstat64 NYI"; + } } }; -if(typeof exports !== 'undefined'){ +if (typeof exports !== 'undefined'){ module.exports = fs; } diff --git a/jsaddle.js b/jsaddle.js new file mode 100644 index 0000000..9140e88 --- /dev/null +++ b/jsaddle.js @@ -0,0 +1,307 @@ +var dec = new TextDecoder(); +var enc = new TextEncoder(); +var channel = new MessageChannel(); +channel.port1.onmessage = jsaddleHandler; +var msgCount = 0; + + +// initState +var jsaddle_values = new Map(); +var jsaddle_free = new Map(); +jsaddle_values.set(0, null); +jsaddle_values.set(1, undefined); +jsaddle_values.set(2, false); +jsaddle_values.set(3, true); +jsaddle_values.set(4, window); +var jsaddle_index = 100; +var expectedBatch = 1; +var lastResults = [0, {"tag": "Success", "contents": [[], []]}]; +var inCallback = 0; +var asyncBatch = null; + +function sendAPI (msg) { + var str = JSON.stringify(msg); + var a = enc.encode(str); + var b = a.buffer; + channel.port1.postMessage({buf: b}, [b]); +} +// runBatch :: (ByteString -> ByteString) -> Maybe (ByteString -> ByteString) -> ByteString +function jsaddleHandler(msg) { + var m = dec.decode(msg.data.buf); + var batch = JSON.parse(m); + var runBatch = function (firstBatch, initialSyncDepth) { + var processBatch = function(timestamp) { + var batch = firstBatch; + var callbacksToFree = []; + var results = []; + inCallback++; + try { + var syncDepth = initialSyncDepth || 0; + for(;;){ + if(batch[2] === expectedBatch) { + expectedBatch++; + var nCommandsLength = batch[0].length; + for (var nCommand = 0; nCommand != nCommandsLength; nCommand++) { + var cmd = batch[0][nCommand]; + if (cmd.Left) { + var d = cmd.Left; + switch (d.tag) { + case "FreeRef": + var refsToFree = jsaddle_free.get(d.contents[0]) || []; + refsToFree.push(d.contents[1]); + jsaddle_free.set(d.contents[0], refsToFree); + break; + case "FreeRefs": + var refsToFree = jsaddle_free.get(d.contents) || []; + for(var nRef = 0; nRef != refsToFree.length; nRef++) + jsaddle_values.delete(refsToFree[nRef]); + jsaddle_free.delete(d.contents); + break; + case "SetPropertyByName": + jsaddle_values.get(d.contents[0])[d.contents[1]]=jsaddle_values.get(d.contents[2]); + break; + case "SetPropertyAtIndex": + jsaddle_values.get(d.contents[0])[d.contents[1]]=jsaddle_values.get(d.contents[2]); + break; + case "EvaluateScript": + var n = d.contents[1]; + jsaddle_values.set(n, eval(d.contents[0])); + break; + case "StringToValue": + var n = d.contents[1]; + jsaddle_values.set(n, d.contents[0]); + break; + case "JSONValueToValue": + var n = d.contents[1]; + jsaddle_values.set(n, d.contents[0]); + break; + case "GetPropertyByName": + var n = d.contents[2]; + jsaddle_values.set(n, jsaddle_values.get(d.contents[0])[d.contents[1]]); + break; + case "GetPropertyAtIndex": + var n = d.contents[2]; + jsaddle_values.set(n, jsaddle_values.get(d.contents[0])[d.contents[1]]); + break; + case "NumberToValue": + var n = d.contents[1]; + jsaddle_values.set(n, d.contents[0]); + break; + case "NewEmptyObject": + var n = d.contents; + jsaddle_values.set(n, {}); + break; + case "NewAsyncCallback": + (function() { + var nFunction = d.contents; + var func = function() { + var nFunctionInFunc = ++jsaddle_index; + jsaddle_values.set(nFunctionInFunc, func); + var nThis = ++jsaddle_index; + jsaddle_values.set(nThis, this); + var args = []; + for (var i = 0; i != arguments.length; i++) { + var nArg = ++jsaddle_index; + jsaddle_values.set(nArg, arguments[i]); + args[i] = nArg; + } + sendAPI ({"tag": "Callback", "contents": [lastResults[0], lastResults[1], nFunction, nFunctionInFunc, nThis, args]}); + }; + jsaddle_values.set(nFunction, func); + })(); + break; + case "NewSyncCallback": + (function() { + var nFunction = d.contents; + var func = function() { + var nFunctionInFunc = ++jsaddle_index; + jsaddle_values.set(nFunctionInFunc, func); + var nThis = ++jsaddle_index; + jsaddle_values.set(nThis, this); + var args = []; + for (var i = 0; i != arguments.length; i++) { + var nArg = ++jsaddle_index; + jsaddle_values.set(nArg, arguments[i]); + args[i] = nArg; + } + sendAPI ({"tag": "Callback", "contents": [lastResults[0], lastResults[1], nFunction, nFunctionInFunc, nThis, args]}); + }; + jsaddle_values.set(nFunction, func); + })(); + break; + case "FreeCallback": + callbacksToFree.push(d.contents); + break; + case "CallAsFunction": + var n = d.contents[3]; + jsaddle_values.set(n, + jsaddle_values.get(d.contents[0]).apply(jsaddle_values.get(d.contents[1]), + d.contents[2].map(function(arg){return jsaddle_values.get(arg);}))); + break; + case "CallAsConstructor": + var n = d.contents[2]; + var r; + var f = jsaddle_values.get(d.contents[0]); + var a = d.contents[1].map(function(arg){return jsaddle_values.get(arg);}); + switch(a.length) { + case 0 : r = new f(); break; + case 1 : r = new f(a[0]); break; + case 2 : r = new f(a[0],a[1]); break; + case 3 : r = new f(a[0],a[1],a[2]); break; + case 4 : r = new f(a[0],a[1],a[2],a[3]); break; + case 5 : r = new f(a[0],a[1],a[2],a[3],a[4]); break; + case 6 : r = new f(a[0],a[1],a[2],a[3],a[4],a[5]); break; + case 7 : r = new f(a[0],a[1],a[2],a[3],a[4],a[5],a[6]); break; + default: + var ret; + var temp = function() { + ret = f.apply(this, a); + }; + temp.prototype = f.prototype; + var i = new temp(); + if(ret instanceof Object) + r = ret; + else { + i.constructor = f; + r = i; + } + } + jsaddle_values.set(n, r); + break; + case "NewArray": + var n = d.contents[1]; + jsaddle_values.set(n, d.contents[0].map(function(v){return jsaddle_values.get(v);})); + break; + case "SyncWithAnimationFrame": + var n = d.contents; + jsaddle_values.set(n, timestamp); + break; + case "StartSyncBlock": + syncDepth++; + break; + case "EndSyncBlock": + syncDepth--; + break; + default: + sendAPI ({"tag": "ProtocolError", "contents": e.data}); + return; + } + } else { + var d = cmd.Right; + switch (d.tag) { + case "ValueToString": + var val = jsaddle_values.get(d.contents); + var s = val === null ? "null" : val === undefined ? "undefined" : val.toString(); + results.push({"tag": "ValueToStringResult", "contents": s}); + break; + case "ValueToBool": + results.push({"tag": "ValueToBoolResult", "contents": jsaddle_values.get(d.contents) ? true : false}); + break; + case "ValueToNumber": + results.push({"tag": "ValueToNumberResult", "contents": Number(jsaddle_values.get(d.contents))}); + break; + case "ValueToJSON": + var s = jsaddle_values.get(d.contents) === undefined ? "" : JSON.stringify(jsaddle_values.get(d.contents)); + results.push({"tag": "ValueToJSONResult", "contents": s}); + break; + case "ValueToJSONValue": + results.push({"tag": "ValueToJSONValueResult", "contents": jsaddle_values.get(d.contents)}); + break; + case "DeRefVal": + var n = d.contents; + var v = jsaddle_values.get(n); + var c = (v === null ) ? [0, ""] : + (v === undefined ) ? [1, ""] : + (v === false ) ? [2, ""] : + (v === true ) ? [3, ""] : + (typeof v === "number") ? [-1, v.toString()] : + (typeof v === "string") ? [-2, v] + : [-3, ""]; + results.push({"tag": "DeRefValResult", "contents": c}); + break; + case "IsNull": + results.push({"tag": "IsNullResult", "contents": jsaddle_values.get(d.contents) === null}); + break; + case "IsUndefined": + results.push({"tag": "IsUndefinedResult", "contents": jsaddle_values.get(d.contents) === undefined}); + break; + case "InstanceOf": + results.push({"tag": "InstanceOfResult", "contents": jsaddle_values.get(d.contents[0]) instanceof jsaddle_values.get(d.contents[1])}); + break; + case "StrictEqual": + results.push({"tag": "StrictEqualResult", "contents": jsaddle_values.get(d.contents[0]) === jsaddle_values.get(d.contents[1])}); + break; + case "PropertyNames": + var result = []; + for (name in jsaddle_values.get(d.contents)) { result.push(name); } + results.push({"tag": "PropertyNamesResult", "contents": result}); + break; + case "Sync": + results.push({"tag": "SyncResult", "contents": []}); + break; + default: + results.push({"tag": "ProtocolError", "contents": e.data}); + } + } + } + if(syncDepth <= 0) { + lastResults = [batch[2], {"tag": "Success", "contents": [callbacksToFree, results]}]; + sendAPI ({"tag": "BatchResults", "contents": [lastResults[0], lastResults[1]]}); + break; + } else { + sendAPI ({"tag": "BatchResults", "contents": [batch[2], {"tag": "Success", "contents": [callbacksToFree, results]}]}); + break; + } + } else { + if(syncDepth <= 0) { + break; + } else { + sendAPI ({"tag": "Duplicate", "contents": [batch[2], expectedBatch]}); + break; + } + } + } + } + catch (err) { + var n = ++jsaddle_index; + jsaddle_values.set(n, err); + sendAPI ({"tag": "BatchResults", "contents": [batch[2], {"tag": "Failure", "contents": [callbacksToFree, results, n]}]}); + } + if(inCallback == 1) { + while(asyncBatch !== null) { + var b = asyncBatch; + asyncBatch = null; + if(b[2] == expectedBatch) runBatch(b); + } + } + inCallback--; + }; + if(batch[1] && (initialSyncDepth || 0) === 0) { + window.requestAnimationFrame(processBatch); + } + else { + processBatch(window.performance ? window.performance.now() : null); + } + }; + + runBatch(batch); +} + +function handleMessageFromWasm(msg) { + // var d = utils.bufToStr(heap_uint8, bufPtr, bufPtr + count); + // console.log('JSADDLE_OUT: "' + d + '"'); + // console.log('incoming message from wasm, msg:', msg); + var m = dec.decode(msg.data.buf); + // console.log('message m:', m); + // jsaddleJs.exec(msg.data.buf); + msgCount++; + var reply = m + msgCount; + var a = enc.encode(reply); + var b = a.buffer; + + channel.port1.postMessage({buf: b}, [b]); +} + +function jsaddleInit() { + return channel.port2; +} diff --git a/jsaddle_listener.js b/jsaddle_listener.js new file mode 100644 index 0000000..436b1db --- /dev/null +++ b/jsaddle_listener.js @@ -0,0 +1,67 @@ +var jsaddleChannelPort; +var jsaddle_input_msg_queue = []; +var jsaddleMsgBufArray; +var jsaddleMsgBufArray32; +var dec = new TextDecoder(); + + +function handleMessageFromJSaddle (msg) { + // var m = dec.decode(msg.data.buf); + // console.log('jsaddle_listener m:', m); + + var bb = new Uint8Array (msg.data.buf); + + // Add directly to shared buffer if it is empty, else push to queue + var isMsgBufEmpty = jsaddleMsgBufArray32[0] === 0; + if (isMsgBufEmpty) { + // console.log('Adding message to array'); + var len = bb.byteLength; + var i = len; + while (i--) jsaddleMsgBufArray[i + 4] = bb[i]; + jsaddleMsgBufArray32[0] = len; + + // var check = new Uint8Array(len); + // i = len; + // while (i--) check[i] = jsaddleMsgBufArray[i + 4]; + // // console.log(bb); + // // while (i--) check[i] = bb[i]; + // var checkstr = dec.decode(check); + + // console.log('Copied ', checkstr, check); + } else { + // console.log('pushing message on queue'); + jsaddle_input_msg_queue.push(bb); + } +} + +onmessage = function(msg) { + switch (msg.data.type) { + case 'init': + jsaddleChannelPort = msg.data.jsaddleChannelPort; + jsaddleChannelPort.onmessage = handleMessageFromJSaddle; + jsaddleMsgBufArray32 = msg.data.jsaddleMsgBufArray32; + jsaddleMsgBufArray = msg.data.jsaddleMsgBufArray; + break; + + case 'write': + jsaddleChannelPort.postMessage( + {type: 'a', buf: msg.data.buf} + , [msg.data.buf]); + break; + + case 'read': + // console.log('Current queue length:', jsaddle_input_msg_queue.length); + if (jsaddle_input_msg_queue.length > 0) { + var b = jsaddle_input_msg_queue.shift(); + var len = b.byteLength; + var i = len; + while (i--) jsaddleMsgBufArray[i + 4] = b[i]; + jsaddleMsgBufArray32[0] = len; + // console.log('Added shared buf msg :', len); + } + break; + + default: + throw 'Invalid msg type for jsaddle_listener'; + } +} diff --git a/kernel.js b/kernel.js index 992f31a..6ae3b07 100644 --- a/kernel.js +++ b/kernel.js @@ -1,4 +1,11 @@ function kernel(progName, options) { const worker = new Worker("wasm.js"); - worker.postMessage({ progName: progName, options: options }); + + // The JS side of JSaddle + // The channel is used for bidirectional communication via jsaddleListener worker + var channelPort2 = jsaddleInit(); + + worker.postMessage( + { progName: progName, options: options, jsaddleChannelPort: channelPort2 } + , [channelPort2]); } diff --git a/util.js b/util.js index 7976ae7..b29a446 100644 --- a/util.js +++ b/util.js @@ -38,12 +38,15 @@ if(typeof exports === 'undefined'){ }; utils.stdout__write = function (str) { var i = str.lastIndexOf("\n"); - if (i >= 0) { - console.log(stdout__buf + str.substring(0, i)); - stdout__buf = str.substring(i + 1); - } else { - stdout__buf += str; - } + // XXX Error is not printed, with below algo + // So till that is fixed, directly print + console.log(str); + // if (i >= 0) { + // console.log(stdout__buf + str.substring(0, i)); + // stdout__buf = str.substring(i + 1); + // } else { + // stdout__buf += str; + // } }; } else { exports.bufToStr = function (buf, ptr, end) { diff --git a/wasm.js b/wasm.js index 65d8e0e..6359d37 100644 --- a/wasm.js +++ b/wasm.js @@ -1404,8 +1404,8 @@ syscall_fns = { }, 197: { name: "SYS_fstat64", - fn: function() { - throw "SYS_fstat64 NYI"; + fn: function(fd, statbuf_) { + return fs.fstat64(fd, statbuf_); } }, 198: { @@ -1537,14 +1537,15 @@ syscall_fns = { 219: { name: "SYS_madvise", fn: function() { - throw "SYS_madvise NYI"; + utils.warn("SYS_madvise being ignored"); + return 0; } }, 219: { name: "SYS_madvise1", fn: function() { - // not implemented in the Linux kernel - throw "SYS_madvise1 NYI"; + utils.warn("SYS_madvise1 being ignored"); + return 0; } }, 220: { @@ -2331,7 +2332,7 @@ syscall_fns = { function syscall(sys, arg1, arg2, arg3, arg4, arg5, arg6) { if (debugSyscalls) { - utils.infoMessage(syscall_fns[sys].name, arg1, arg2, arg3, arg4, arg5, arg6); + console.debug(syscall_fns[sys].name, arg1, arg2, arg3, arg4, arg5, arg6); } return syscall_fns[sys].fn(arg1, arg2, arg3, arg4, arg5, arg6); } @@ -2451,7 +2452,7 @@ if(typeof exports !== 'undefined'){ if (msg.data.options && msg.data.options.debugSyscalls) { debugSyscalls = true; } + fs.initFsJSaddle(msg.data.jsaddleChannelPort); execve(msg.data.progName, [msg.data.progName], []); }; } -