| // Copyright (C) 2018 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| import {defer} from '../base/deferred'; |
| import {assertExists, assertTrue} from '../base/logging'; |
| import initTraceProcessor from '../gen/trace_processor'; |
| |
| // The Initialize() call will allocate a buffer of REQ_BUF_SIZE bytes which |
| // will be used to copy the input request data. This is to avoid passing the |
| // input data on the stack, which has a limited (~1MB) size. |
| // The buffer will be allocated by the C++ side and reachable at |
| // HEAPU8[reqBufferAddr, +REQ_BUFFER_SIZE]. |
| const REQ_BUF_SIZE = 32 * 1024 * 1024; |
| |
| // The end-to-end interaction between JS and Wasm is as follows: |
| // - [JS] Inbound data received by the worker (onmessage() in engine/index.ts). |
| // - [JS] onRpcDataReceived() (this file) |
| // - [C++] trace_processor_on_rpc_request (wasm_bridge.cc) |
| // - [C++] some TraceProcessor::method() |
| // for (batch in result_rows) |
| // - [C++] RpcResponseFunction(bytes) (wasm_bridge.cc) |
| // - [JS] onReply() (this file) |
| // - [JS] postMessage() (this file) |
| export class WasmBridge { |
| // When this promise has resolved it is safe to call callWasm. |
| whenInitialized: Promise<void>; |
| |
| private aborted: boolean; |
| private connection: initTraceProcessor.Module; |
| private reqBufferAddr = 0; |
| private lastStderr: string[] = []; |
| private messagePort?: MessagePort; |
| |
| constructor() { |
| this.aborted = false; |
| const deferredRuntimeInitialized = defer<void>(); |
| this.connection = initTraceProcessor({ |
| locateFile: (s: string) => s, |
| print: (line: string) => console.log(line), |
| printErr: (line: string) => this.appendAndLogErr(line), |
| onRuntimeInitialized: () => deferredRuntimeInitialized.resolve(), |
| }); |
| this.whenInitialized = deferredRuntimeInitialized.then(() => { |
| const fn = this.connection.addFunction(this.onReply.bind(this), 'vii'); |
| this.reqBufferAddr = |
| this.connection.ccall( |
| 'trace_processor_rpc_init', |
| /* return=*/ 'number', |
| /* args=*/ ['number', 'number'], |
| [fn, REQ_BUF_SIZE], |
| ) >>> 0; // >>> 0 = static_cast<uint32_t> (see comment in onReply()). |
| }); |
| } |
| |
| initialize(port: MessagePort) { |
| // Ensure that initialize() is called only once. |
| assertTrue(this.messagePort === undefined); |
| this.messagePort = port; |
| // Note: setting .onmessage implicitly calls port.start() and dispatches the |
| // queued messages. addEventListener('message') doesn't. |
| this.messagePort.onmessage = this.onMessage.bind(this); |
| } |
| |
| onMessage(msg: MessageEvent) { |
| if (this.aborted) { |
| throw new Error('Wasm module crashed'); |
| } |
| assertTrue(msg.data instanceof Uint8Array); |
| const data = msg.data as Uint8Array; |
| let wrSize = 0; |
| // If the request data is larger than our JS<>Wasm interop buffer, split it |
| // into multiple writes. The RPC channel is byte-oriented and is designed to |
| // deal with arbitrary fragmentations. |
| while (wrSize < data.length) { |
| const sliceLen = Math.min(data.length - wrSize, REQ_BUF_SIZE); |
| const dataSlice = data.subarray(wrSize, wrSize + sliceLen); |
| this.connection.HEAPU8.set(dataSlice, this.reqBufferAddr); |
| wrSize += sliceLen; |
| try { |
| this.connection.ccall( |
| 'trace_processor_on_rpc_request', // C function name. |
| 'void', // Return type. |
| ['number'], // Arg types. |
| [sliceLen], // Args. |
| ); |
| } catch (err) { |
| this.aborted = true; |
| let abortReason = `${err}`; |
| if (err instanceof Error) { |
| abortReason = `${err.name}: ${err.message}\n${err.stack}`; |
| } |
| abortReason += '\n\nstderr: \n' + this.lastStderr.join('\n'); |
| throw new Error(abortReason); |
| } |
| } // while(wrSize < data.length) |
| } |
| |
| // This function is bound and passed to Initialize and is called by the C++ |
| // code while in the ccall(trace_processor_on_rpc_request). |
| private onReply(heapPtr: number, size: number) { |
| // Force heapPtr to be a positive using an unsigned right shift. |
| // The issue here is the following: the matching code in wasm_bridge.cc |
| // invokes this function passing arguments as uint32_t. However, in the |
| // wasm<>JS interop bindings, the uint32 args become Js numbers. If the |
| // pointer is > 2GB, this number will be negative, which causes the wrong |
| // behaviour on slice(). |
| heapPtr = heapPtr >>> 0; // This is static_cast<uint32_t>(heapPtr). |
| size = size >>> 0; |
| const data = this.connection.HEAPU8.slice(heapPtr, heapPtr + size); |
| assertExists(this.messagePort).postMessage(data, [data.buffer]); |
| } |
| |
| private appendAndLogErr(line: string) { |
| console.warn(line); |
| // Keep the last N lines in the |lastStderr| buffer. |
| this.lastStderr.push(line); |
| if (this.lastStderr.length > 512) { |
| this.lastStderr.shift(); |
| } |
| } |
| } |