xref: /aosp_15_r20/external/perfetto/ui/src/engine/wasm_bridge.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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