diff options
Diffstat (limited to 'runtime/js')
-rw-r--r-- | runtime/js/10_dispatch_buffer.js | 150 | ||||
-rw-r--r-- | runtime/js/10_dispatch_minimal.js | 115 | ||||
-rw-r--r-- | runtime/js/11_timers.js | 4 | ||||
-rw-r--r-- | runtime/js/12_io.js | 10 | ||||
-rw-r--r-- | runtime/js/99_main.js | 8 |
5 files changed, 158 insertions, 129 deletions
diff --git a/runtime/js/10_dispatch_buffer.js b/runtime/js/10_dispatch_buffer.js new file mode 100644 index 000000000..091fce504 --- /dev/null +++ b/runtime/js/10_dispatch_buffer.js @@ -0,0 +1,150 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +"use strict"; + +((window) => { + const core = window.Deno.core; + + function assert(cond) { + if (!cond) { + throw Error("assert"); + } + } + + //////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////// General async handling ////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////// + + // General Async response handling + let nextRequestId = 1; + const promiseTable = {}; + + function opAsync(opName, opRequestBuilder, opResultParser) { + // Make sure requests of this type are handled by the asyncHandler + // The asyncHandler's role is to call the "promiseTable[requestId]" function + core.setAsyncHandlerByName(opName, (bufUi8, _) => { + const [requestId, result, error] = opResultParser(bufUi8, true); + if (error !== null) { + promiseTable[requestId][1](error); + } else { + promiseTable[requestId][0](result); + } + delete promiseTable[requestId]; + }); + + const requestId = nextRequestId++; + + // Create and store promise + const promise = new Promise((resolve, reject) => { + promiseTable[requestId] = [resolve, reject]; + }); + + // Synchronously dispatch async request + core.dispatchByName(opName, ...opRequestBuilder(requestId)); + + // Wait for async response + return promise; + } + + function opSync(opName, opRequestBuilder, opResultParser) { + const rawResult = core.dispatchByName(opName, ...opRequestBuilder()); + + const [_, result, error] = opResultParser(rawResult, false); + if (error !== null) throw error; + return result; + } + + //////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////// Error handling ///////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////// + + function handleError(className, message) { + const [ErrorClass, args] = core.getErrorClassAndArgs(className); + if (!ErrorClass) { + return new Error( + `Unregistered error class: "${className}"\n` + + ` ${message}\n` + + ` Classes of errors returned from ops should be registered via Deno.core.registerErrorClass().`, + ); + } + return new ErrorClass(message, ...args); + } + + //////////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////// Buffer ops handling ////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////// + + const scratchBytes = new ArrayBuffer(3 * 4); + const scratchView = new DataView( + scratchBytes, + scratchBytes.byteOffset, + scratchBytes.byteLength, + ); + + function bufferOpBuildRequest(requestId, argument, zeroCopy) { + scratchView.setBigUint64(0, BigInt(requestId), true); + scratchView.setUint32(8, argument, true); + return [scratchView, ...zeroCopy]; + } + + function bufferOpParseResult(bufUi8, isCopyNeeded) { + // Decode header value from ui8 buffer + const headerByteLength = 4 * 4; + assert(bufUi8.byteLength >= headerByteLength); + assert(bufUi8.byteLength % 4 == 0); + const view = new DataView( + bufUi8.buffer, + bufUi8.byteOffset + bufUi8.byteLength - headerByteLength, + headerByteLength, + ); + + const requestId = Number(view.getBigUint64(0, true)); + const status = view.getUint32(8, true); + const result = view.getUint32(12, true); + + // Error handling + if (status !== 0) { + const className = core.decode(bufUi8.subarray(0, result)); + const message = core.decode(bufUi8.subarray(result, -headerByteLength)) + .trim(); + + return [requestId, null, handleError(className, message)]; + } + + if (bufUi8.byteLength === headerByteLength) { + return [requestId, result, null]; + } + + // Rest of response buffer is passed as reference or as a copy + let respBuffer = null; + if (isCopyNeeded) { + // Copy part of the response array (if sent through shared array buf) + respBuffer = bufUi8.slice(0, result); + } else { + // Create view on existing array (if sent through overflow) + respBuffer = bufUi8.subarray(0, result); + } + + return [requestId, respBuffer, null]; + } + + function bufferOpAsync(opName, argument = 0, ...zeroCopy) { + return opAsync( + opName, + (requestId) => bufferOpBuildRequest(requestId, argument, zeroCopy), + bufferOpParseResult, + ); + } + + function bufferOpSync(opName, argument = 0, ...zeroCopy) { + return opSync( + opName, + () => bufferOpBuildRequest(0, argument, zeroCopy), + bufferOpParseResult, + ); + } + + window.__bootstrap.dispatchBuffer = { + bufferOpSync, + bufferOpAsync, + }; +})(this); diff --git a/runtime/js/10_dispatch_minimal.js b/runtime/js/10_dispatch_minimal.js deleted file mode 100644 index e74f8c393..000000000 --- a/runtime/js/10_dispatch_minimal.js +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -"use strict"; - -((window) => { - const core = window.Deno.core; - const util = window.__bootstrap.util; - - // Using an object without a prototype because `Map` was causing GC problems. - const promiseTableMin = Object.create(null); - - const decoder = new TextDecoder(); - - // Note it's important that promiseId starts at 1 instead of 0, because sync - // messages are indicated with promiseId 0. If we ever add wrap around logic for - // overflows, this should be taken into account. - let _nextPromiseId = 1; - - function nextPromiseId() { - return _nextPromiseId++; - } - - function recordFromBufMinimal(ui8) { - const headerLen = 12; - const header = ui8.subarray(0, headerLen); - const buf32 = new Int32Array( - header.buffer, - header.byteOffset, - header.byteLength / 4, - ); - const promiseId = buf32[0]; - const arg = buf32[1]; - const result = buf32[2]; - let err; - - if (arg < 0) { - err = { - className: decoder.decode(ui8.subarray(headerLen, headerLen + result)), - message: decoder.decode(ui8.subarray(headerLen + result)), - }; - } else if (ui8.length != 12) { - throw new TypeError("Malformed response message"); - } - - return { - promiseId, - arg, - result, - err, - }; - } - - function unwrapResponse(res) { - if (res.err != null) { - const [ErrorClass, args] = core.getErrorClassAndArgs(res.err.className); - if (!ErrorClass) { - throw new Error( - `Unregistered error class: "${res.err.className}"\n ${res.err.message}\n Classes of errors returned from ops should be registered via Deno.core.registerErrorClass().`, - ); - } - throw new ErrorClass(res.err.message, ...args); - } - return res.result; - } - - const scratch32 = new Int32Array(3); - const scratchBytes = new Uint8Array( - scratch32.buffer, - scratch32.byteOffset, - scratch32.byteLength, - ); - util.assert(scratchBytes.byteLength === scratch32.length * 4); - - function asyncMsgFromRust(ui8) { - const record = recordFromBufMinimal(ui8); - const { promiseId } = record; - const promise = promiseTableMin[promiseId]; - delete promiseTableMin[promiseId]; - util.assert(promise); - promise.resolve(record); - } - - async function sendAsync(opName, arg, zeroCopy) { - const promiseId = nextPromiseId(); // AKA cmdId - scratch32[0] = promiseId; - scratch32[1] = arg; - scratch32[2] = 0; // result - const promise = util.createResolvable(); - const buf = core.dispatchByName(opName, scratchBytes, zeroCopy); - if (buf != null) { - const record = recordFromBufMinimal(buf); - // Sync result. - promise.resolve(record); - } else { - // Async result. - promiseTableMin[promiseId] = promise; - } - - const res = await promise; - return unwrapResponse(res); - } - - function sendSync(opName, arg, zeroCopy) { - scratch32[0] = 0; // promiseId 0 indicates sync - scratch32[1] = arg; - const res = core.dispatchByName(opName, scratchBytes, zeroCopy); - const resRecord = recordFromBufMinimal(res); - return unwrapResponse(resRecord); - } - - window.__bootstrap.dispatchMinimal = { - asyncMsgFromRust, - sendSync, - sendAsync, - }; -})(this); diff --git a/runtime/js/11_timers.js b/runtime/js/11_timers.js index 4c693aa4a..f07622388 100644 --- a/runtime/js/11_timers.js +++ b/runtime/js/11_timers.js @@ -4,7 +4,7 @@ ((window) => { const assert = window.__bootstrap.util.assert; const core = window.Deno.core; - const { sendSync } = window.__bootstrap.dispatchMinimal; + const { bufferOpSync } = window.__bootstrap.dispatchBuffer; function opStopGlobalTimer() { core.jsonOpSync("op_global_timer_stop"); @@ -20,7 +20,7 @@ const nowBytes = new Uint8Array(8); function opNow() { - sendSync("op_now", 0, nowBytes); + bufferOpSync("op_now", 0, nowBytes); return new DataView(nowBytes.buffer).getFloat64(); } diff --git a/runtime/js/12_io.js b/runtime/js/12_io.js index 3818069c1..09e87f990 100644 --- a/runtime/js/12_io.js +++ b/runtime/js/12_io.js @@ -7,7 +7,7 @@ ((window) => { const DEFAULT_BUFFER_SIZE = 32 * 1024; - const { sendSync, sendAsync } = window.__bootstrap.dispatchMinimal; + const { bufferOpSync, bufferOpAsync } = window.__bootstrap.dispatchBuffer; // Seek whence values. // https://golang.org/pkg/io/#pkg-constants const SeekMode = { @@ -81,7 +81,7 @@ return 0; } - const nread = sendSync("op_read", rid, buffer); + const nread = bufferOpSync("op_read_sync", rid, buffer); if (nread < 0) { throw new Error("read error"); } @@ -97,7 +97,7 @@ return 0; } - const nread = await sendAsync("op_read", rid, buffer); + const nread = await bufferOpAsync("op_read_async", rid, buffer); if (nread < 0) { throw new Error("read error"); } @@ -106,7 +106,7 @@ } function writeSync(rid, data) { - const result = sendSync("op_write", rid, data); + const result = bufferOpSync("op_write_sync", rid, data); if (result < 0) { throw new Error("write error"); } @@ -115,7 +115,7 @@ } async function write(rid, data) { - const result = await sendAsync("op_write", rid, data); + const result = await bufferOpAsync("op_write_async", rid, data); if (result < 0) { throw new Error("write error"); } diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js index d96aaaaae..233c5cd43 100644 --- a/runtime/js/99_main.js +++ b/runtime/js/99_main.js @@ -11,7 +11,6 @@ delete Object.prototype.__proto__; const eventTarget = window.__bootstrap.eventTarget; const globalInterfaces = window.__bootstrap.globalInterfaces; const location = window.__bootstrap.location; - const dispatchMinimal = window.__bootstrap.dispatchMinimal; const build = window.__bootstrap.build; const version = window.__bootstrap.version; const errorStack = window.__bootstrap.errorStack; @@ -142,12 +141,7 @@ delete Object.prototype.__proto__; } function runtimeStart(runtimeOptions, source) { - const opsMap = core.ops(); - for (const [name, opId] of Object.entries(opsMap)) { - if (name === "op_write" || name === "op_read") { - core.setAsyncHandler(opId, dispatchMinimal.asyncMsgFromRust); - } - } + core.ops(); core.setMacrotaskCallback(timers.handleTimerMacrotask); version.setVersions( |