1*6dbdd20aSAndroid Build Coastguard Worker// Copyright (C) 2018 The Android Open Source Project 2*6dbdd20aSAndroid Build Coastguard Worker// 3*6dbdd20aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); 4*6dbdd20aSAndroid Build Coastguard Worker// you may not use this file except in compliance with the License. 5*6dbdd20aSAndroid Build Coastguard Worker// You may obtain a copy of the License at 6*6dbdd20aSAndroid Build Coastguard Worker// 7*6dbdd20aSAndroid Build Coastguard Worker// http://www.apache.org/licenses/LICENSE-2.0 8*6dbdd20aSAndroid Build Coastguard Worker// 9*6dbdd20aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software 10*6dbdd20aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, 11*6dbdd20aSAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*6dbdd20aSAndroid Build Coastguard Worker// See the License for the specific language governing permissions and 13*6dbdd20aSAndroid Build Coastguard Worker// limitations under the License. 14*6dbdd20aSAndroid Build Coastguard Worker 15*6dbdd20aSAndroid Build Coastguard Workerimport {defer} from '../base/deferred'; 16*6dbdd20aSAndroid Build Coastguard Workerimport {assertExists, assertTrue} from '../base/logging'; 17*6dbdd20aSAndroid Build Coastguard Workerimport initTraceProcessor from '../gen/trace_processor'; 18*6dbdd20aSAndroid Build Coastguard Worker 19*6dbdd20aSAndroid Build Coastguard Worker// The Initialize() call will allocate a buffer of REQ_BUF_SIZE bytes which 20*6dbdd20aSAndroid Build Coastguard Worker// will be used to copy the input request data. This is to avoid passing the 21*6dbdd20aSAndroid Build Coastguard Worker// input data on the stack, which has a limited (~1MB) size. 22*6dbdd20aSAndroid Build Coastguard Worker// The buffer will be allocated by the C++ side and reachable at 23*6dbdd20aSAndroid Build Coastguard Worker// HEAPU8[reqBufferAddr, +REQ_BUFFER_SIZE]. 24*6dbdd20aSAndroid Build Coastguard Workerconst REQ_BUF_SIZE = 32 * 1024 * 1024; 25*6dbdd20aSAndroid Build Coastguard Worker 26*6dbdd20aSAndroid Build Coastguard Worker// The end-to-end interaction between JS and Wasm is as follows: 27*6dbdd20aSAndroid Build Coastguard Worker// - [JS] Inbound data received by the worker (onmessage() in engine/index.ts). 28*6dbdd20aSAndroid Build Coastguard Worker// - [JS] onRpcDataReceived() (this file) 29*6dbdd20aSAndroid Build Coastguard Worker// - [C++] trace_processor_on_rpc_request (wasm_bridge.cc) 30*6dbdd20aSAndroid Build Coastguard Worker// - [C++] some TraceProcessor::method() 31*6dbdd20aSAndroid Build Coastguard Worker// for (batch in result_rows) 32*6dbdd20aSAndroid Build Coastguard Worker// - [C++] RpcResponseFunction(bytes) (wasm_bridge.cc) 33*6dbdd20aSAndroid Build Coastguard Worker// - [JS] onReply() (this file) 34*6dbdd20aSAndroid Build Coastguard Worker// - [JS] postMessage() (this file) 35*6dbdd20aSAndroid Build Coastguard Workerexport class WasmBridge { 36*6dbdd20aSAndroid Build Coastguard Worker // When this promise has resolved it is safe to call callWasm. 37*6dbdd20aSAndroid Build Coastguard Worker whenInitialized: Promise<void>; 38*6dbdd20aSAndroid Build Coastguard Worker 39*6dbdd20aSAndroid Build Coastguard Worker private aborted: boolean; 40*6dbdd20aSAndroid Build Coastguard Worker private connection: initTraceProcessor.Module; 41*6dbdd20aSAndroid Build Coastguard Worker private reqBufferAddr = 0; 42*6dbdd20aSAndroid Build Coastguard Worker private lastStderr: string[] = []; 43*6dbdd20aSAndroid Build Coastguard Worker private messagePort?: MessagePort; 44*6dbdd20aSAndroid Build Coastguard Worker 45*6dbdd20aSAndroid Build Coastguard Worker constructor() { 46*6dbdd20aSAndroid Build Coastguard Worker this.aborted = false; 47*6dbdd20aSAndroid Build Coastguard Worker const deferredRuntimeInitialized = defer<void>(); 48*6dbdd20aSAndroid Build Coastguard Worker this.connection = initTraceProcessor({ 49*6dbdd20aSAndroid Build Coastguard Worker locateFile: (s: string) => s, 50*6dbdd20aSAndroid Build Coastguard Worker print: (line: string) => console.log(line), 51*6dbdd20aSAndroid Build Coastguard Worker printErr: (line: string) => this.appendAndLogErr(line), 52*6dbdd20aSAndroid Build Coastguard Worker onRuntimeInitialized: () => deferredRuntimeInitialized.resolve(), 53*6dbdd20aSAndroid Build Coastguard Worker }); 54*6dbdd20aSAndroid Build Coastguard Worker this.whenInitialized = deferredRuntimeInitialized.then(() => { 55*6dbdd20aSAndroid Build Coastguard Worker const fn = this.connection.addFunction(this.onReply.bind(this), 'vii'); 56*6dbdd20aSAndroid Build Coastguard Worker this.reqBufferAddr = 57*6dbdd20aSAndroid Build Coastguard Worker this.connection.ccall( 58*6dbdd20aSAndroid Build Coastguard Worker 'trace_processor_rpc_init', 59*6dbdd20aSAndroid Build Coastguard Worker /* return=*/ 'number', 60*6dbdd20aSAndroid Build Coastguard Worker /* args=*/ ['number', 'number'], 61*6dbdd20aSAndroid Build Coastguard Worker [fn, REQ_BUF_SIZE], 62*6dbdd20aSAndroid Build Coastguard Worker ) >>> 0; // >>> 0 = static_cast<uint32_t> (see comment in onReply()). 63*6dbdd20aSAndroid Build Coastguard Worker }); 64*6dbdd20aSAndroid Build Coastguard Worker } 65*6dbdd20aSAndroid Build Coastguard Worker 66*6dbdd20aSAndroid Build Coastguard Worker initialize(port: MessagePort) { 67*6dbdd20aSAndroid Build Coastguard Worker // Ensure that initialize() is called only once. 68*6dbdd20aSAndroid Build Coastguard Worker assertTrue(this.messagePort === undefined); 69*6dbdd20aSAndroid Build Coastguard Worker this.messagePort = port; 70*6dbdd20aSAndroid Build Coastguard Worker // Note: setting .onmessage implicitly calls port.start() and dispatches the 71*6dbdd20aSAndroid Build Coastguard Worker // queued messages. addEventListener('message') doesn't. 72*6dbdd20aSAndroid Build Coastguard Worker this.messagePort.onmessage = this.onMessage.bind(this); 73*6dbdd20aSAndroid Build Coastguard Worker } 74*6dbdd20aSAndroid Build Coastguard Worker 75*6dbdd20aSAndroid Build Coastguard Worker onMessage(msg: MessageEvent) { 76*6dbdd20aSAndroid Build Coastguard Worker if (this.aborted) { 77*6dbdd20aSAndroid Build Coastguard Worker throw new Error('Wasm module crashed'); 78*6dbdd20aSAndroid Build Coastguard Worker } 79*6dbdd20aSAndroid Build Coastguard Worker assertTrue(msg.data instanceof Uint8Array); 80*6dbdd20aSAndroid Build Coastguard Worker const data = msg.data as Uint8Array; 81*6dbdd20aSAndroid Build Coastguard Worker let wrSize = 0; 82*6dbdd20aSAndroid Build Coastguard Worker // If the request data is larger than our JS<>Wasm interop buffer, split it 83*6dbdd20aSAndroid Build Coastguard Worker // into multiple writes. The RPC channel is byte-oriented and is designed to 84*6dbdd20aSAndroid Build Coastguard Worker // deal with arbitrary fragmentations. 85*6dbdd20aSAndroid Build Coastguard Worker while (wrSize < data.length) { 86*6dbdd20aSAndroid Build Coastguard Worker const sliceLen = Math.min(data.length - wrSize, REQ_BUF_SIZE); 87*6dbdd20aSAndroid Build Coastguard Worker const dataSlice = data.subarray(wrSize, wrSize + sliceLen); 88*6dbdd20aSAndroid Build Coastguard Worker this.connection.HEAPU8.set(dataSlice, this.reqBufferAddr); 89*6dbdd20aSAndroid Build Coastguard Worker wrSize += sliceLen; 90*6dbdd20aSAndroid Build Coastguard Worker try { 91*6dbdd20aSAndroid Build Coastguard Worker this.connection.ccall( 92*6dbdd20aSAndroid Build Coastguard Worker 'trace_processor_on_rpc_request', // C function name. 93*6dbdd20aSAndroid Build Coastguard Worker 'void', // Return type. 94*6dbdd20aSAndroid Build Coastguard Worker ['number'], // Arg types. 95*6dbdd20aSAndroid Build Coastguard Worker [sliceLen], // Args. 96*6dbdd20aSAndroid Build Coastguard Worker ); 97*6dbdd20aSAndroid Build Coastguard Worker } catch (err) { 98*6dbdd20aSAndroid Build Coastguard Worker this.aborted = true; 99*6dbdd20aSAndroid Build Coastguard Worker let abortReason = `${err}`; 100*6dbdd20aSAndroid Build Coastguard Worker if (err instanceof Error) { 101*6dbdd20aSAndroid Build Coastguard Worker abortReason = `${err.name}: ${err.message}\n${err.stack}`; 102*6dbdd20aSAndroid Build Coastguard Worker } 103*6dbdd20aSAndroid Build Coastguard Worker abortReason += '\n\nstderr: \n' + this.lastStderr.join('\n'); 104*6dbdd20aSAndroid Build Coastguard Worker throw new Error(abortReason); 105*6dbdd20aSAndroid Build Coastguard Worker } 106*6dbdd20aSAndroid Build Coastguard Worker } // while(wrSize < data.length) 107*6dbdd20aSAndroid Build Coastguard Worker } 108*6dbdd20aSAndroid Build Coastguard Worker 109*6dbdd20aSAndroid Build Coastguard Worker // This function is bound and passed to Initialize and is called by the C++ 110*6dbdd20aSAndroid Build Coastguard Worker // code while in the ccall(trace_processor_on_rpc_request). 111*6dbdd20aSAndroid Build Coastguard Worker private onReply(heapPtr: number, size: number) { 112*6dbdd20aSAndroid Build Coastguard Worker // Force heapPtr to be a positive using an unsigned right shift. 113*6dbdd20aSAndroid Build Coastguard Worker // The issue here is the following: the matching code in wasm_bridge.cc 114*6dbdd20aSAndroid Build Coastguard Worker // invokes this function passing arguments as uint32_t. However, in the 115*6dbdd20aSAndroid Build Coastguard Worker // wasm<>JS interop bindings, the uint32 args become Js numbers. If the 116*6dbdd20aSAndroid Build Coastguard Worker // pointer is > 2GB, this number will be negative, which causes the wrong 117*6dbdd20aSAndroid Build Coastguard Worker // behaviour on slice(). 118*6dbdd20aSAndroid Build Coastguard Worker heapPtr = heapPtr >>> 0; // This is static_cast<uint32_t>(heapPtr). 119*6dbdd20aSAndroid Build Coastguard Worker size = size >>> 0; 120*6dbdd20aSAndroid Build Coastguard Worker const data = this.connection.HEAPU8.slice(heapPtr, heapPtr + size); 121*6dbdd20aSAndroid Build Coastguard Worker assertExists(this.messagePort).postMessage(data, [data.buffer]); 122*6dbdd20aSAndroid Build Coastguard Worker } 123*6dbdd20aSAndroid Build Coastguard Worker 124*6dbdd20aSAndroid Build Coastguard Worker private appendAndLogErr(line: string) { 125*6dbdd20aSAndroid Build Coastguard Worker console.warn(line); 126*6dbdd20aSAndroid Build Coastguard Worker // Keep the last N lines in the |lastStderr| buffer. 127*6dbdd20aSAndroid Build Coastguard Worker this.lastStderr.push(line); 128*6dbdd20aSAndroid Build Coastguard Worker if (this.lastStderr.length > 512) { 129*6dbdd20aSAndroid Build Coastguard Worker this.lastStderr.shift(); 130*6dbdd20aSAndroid Build Coastguard Worker } 131*6dbdd20aSAndroid Build Coastguard Worker } 132*6dbdd20aSAndroid Build Coastguard Worker} 133