1// Copyright (C) 2018 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import {defer} from '../base/deferred'; 16import {assertExists, assertTrue} from '../base/logging'; 17import * as init_trace_processor from '../gen/trace_processor'; 18 19// The Initialize() call will allocate a buffer of REQ_BUF_SIZE bytes which 20// will be used to copy the input request data. This is to avoid passing the 21// input data on the stack, which has a limited (~1MB) size. 22// The buffer will be allocated by the C++ side and reachable at 23// HEAPU8[reqBufferAddr, +REQ_BUFFER_SIZE]. 24const REQ_BUF_SIZE = 32 * 1024 * 1024; 25 26export interface WasmBridgeRequest { 27 id: number; 28 methodName: string; 29 data: Uint8Array; 30} 31 32export interface WasmBridgeResponse { 33 id: number; 34 data: Uint8Array; 35} 36 37export class WasmBridge { 38 // When this promise has resolved it is safe to call callWasm. 39 whenInitialized: Promise<void>; 40 41 private aborted: boolean; 42 private currentRequestResult: WasmBridgeResponse|null; 43 private connection: init_trace_processor.Module; 44 private reqBufferAddr = 0; 45 private lastStderr: string[] = []; 46 47 constructor(init: init_trace_processor.InitWasm) { 48 this.aborted = false; 49 this.currentRequestResult = null; 50 51 const deferredRuntimeInitialized = defer<void>(); 52 this.connection = init({ 53 locateFile: (s: string) => s, 54 print: (line: string) => console.log(line), 55 printErr: (line: string) => this.appendAndLogErr(line), 56 onRuntimeInitialized: () => deferredRuntimeInitialized.resolve(), 57 }); 58 this.whenInitialized = deferredRuntimeInitialized.then(() => { 59 const fn = this.connection.addFunction(this.onReply.bind(this), 'vii'); 60 this.reqBufferAddr = this.connection.ccall( 61 'Initialize', 62 /*return=*/ 'number', 63 /*args=*/['number', 'number'], 64 [fn, REQ_BUF_SIZE]); 65 }); 66 } 67 68 callWasm(req: WasmBridgeRequest): WasmBridgeResponse { 69 if (this.aborted) { 70 throw new Error('Wasm module crashed'); 71 } 72 assertTrue(req.data.length <= REQ_BUF_SIZE); 73 const endAddr = this.reqBufferAddr + req.data.length; 74 this.connection.HEAPU8.subarray(this.reqBufferAddr, endAddr).set(req.data); 75 try { 76 this.connection.ccall( 77 req.methodName, // C method name. 78 'void', // Return type. 79 ['number'], // Arg types. 80 [req.data.length] // Args. 81 ); 82 const result = assertExists(this.currentRequestResult); 83 this.currentRequestResult = null; 84 result.id = req.id; 85 return result; 86 } catch (err) { 87 this.aborted = true; 88 let abortReason = `${err}`; 89 if (err instanceof Error) { 90 abortReason = `${err.name}: ${err.message}\n${err.stack}`; 91 } 92 abortReason += '\n\nstderr: \n' + this.lastStderr.join('\n'); 93 throw new Error(abortReason); 94 } 95 } 96 97 // This is invoked from ccall in the same call stack as callWasm. 98 private onReply(heapPtr: number, size: number) { 99 const data = this.connection.HEAPU8.slice(heapPtr, heapPtr + size); 100 this.currentRequestResult = { 101 id: 0, // Will be set by callWasm()'s epilogue. 102 data, 103 }; 104 } 105 106 private appendAndLogErr(line: string) { 107 console.warn(line); 108 // Keep the last N lines in the |lastStderr| buffer. 109 this.lastStderr.push(line); 110 if (this.lastStderr.length > 512) { 111 this.lastStderr.shift(); 112 } 113 } 114} 115