diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /devtools/client/shared/source-map-loader/wasm-dwarf | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/shared/source-map-loader/wasm-dwarf')
6 files changed, 570 insertions, 0 deletions
diff --git a/devtools/client/shared/source-map-loader/wasm-dwarf/convertToJSON.js b/devtools/client/shared/source-map-loader/wasm-dwarf/convertToJSON.js new file mode 100644 index 0000000000..9faae5c901 --- /dev/null +++ b/devtools/client/shared/source-map-loader/wasm-dwarf/convertToJSON.js @@ -0,0 +1,66 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ + +/* eslint camelcase: 0*/ + +"use strict"; + +const { + getDwarfToWasmData, +} = require("resource://devtools/client/shared/source-map-loader/wasm-dwarf/wasmAsset.js"); + +let cachedWasmModule; +let utf8Decoder; + +function convertDwarf(wasm, instance) { + const { memory, alloc_mem, free_mem, convert_dwarf } = instance.exports; + const wasmPtr = alloc_mem(wasm.byteLength); + new Uint8Array(memory.buffer, wasmPtr, wasm.byteLength).set( + new Uint8Array(wasm) + ); + const resultPtr = alloc_mem(12); + const enableXScopes = true; + const success = convert_dwarf( + wasmPtr, + wasm.byteLength, + resultPtr, + resultPtr + 4, + enableXScopes + ); + free_mem(wasmPtr); + const resultView = new DataView(memory.buffer, resultPtr, 12); + const outputPtr = resultView.getUint32(0, true), + outputLen = resultView.getUint32(4, true); + free_mem(resultPtr); + if (!success) { + throw new Error("Unable to convert from DWARF sections"); + } + if (!utf8Decoder) { + utf8Decoder = new TextDecoder("utf-8"); + } + const output = utf8Decoder.decode( + new Uint8Array(memory.buffer, outputPtr, outputLen) + ); + free_mem(outputPtr); + return output; +} + +async function convertToJSON(buffer) { + // Note: We don't 'await' here because it could mean that multiple + // calls to 'convertToJSON' could cause multiple fetches to be started. + cachedWasmModule = cachedWasmModule || loadConverterModule(); + + return convertDwarf(buffer, await cachedWasmModule); +} + +async function loadConverterModule() { + const wasm = await getDwarfToWasmData(); + const imports = {}; + const { instance } = await WebAssembly.instantiate(wasm, imports); + return instance; +} + +module.exports = { + convertToJSON, +}; diff --git a/devtools/client/shared/source-map-loader/wasm-dwarf/dwarf_to_json.wasm b/devtools/client/shared/source-map-loader/wasm-dwarf/dwarf_to_json.wasm Binary files differnew file mode 100644 index 0000000000..c58b189035 --- /dev/null +++ b/devtools/client/shared/source-map-loader/wasm-dwarf/dwarf_to_json.wasm diff --git a/devtools/client/shared/source-map-loader/wasm-dwarf/moz.build b/devtools/client/shared/source-map-loader/wasm-dwarf/moz.build new file mode 100644 index 0000000000..8bc40676fa --- /dev/null +++ b/devtools/client/shared/source-map-loader/wasm-dwarf/moz.build @@ -0,0 +1,12 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DevToolsModules( + "convertToJSON.js", + "wasmAsset.js", + "wasmDwarfExpressions.js", + "wasmXScopes.js", +) diff --git a/devtools/client/shared/source-map-loader/wasm-dwarf/wasmAsset.js b/devtools/client/shared/source-map-loader/wasm-dwarf/wasmAsset.js new file mode 100644 index 0000000000..b4de822ffd --- /dev/null +++ b/devtools/client/shared/source-map-loader/wasm-dwarf/wasmAsset.js @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ + +"use strict"; + +async function getDwarfToWasmData(name) { + const response = await fetch( + "resource://devtools/client/shared/source-map-loader/wasm-dwarf/dwarf_to_json.wasm" + ); + + return response.arrayBuffer(); +} + +module.exports = { + getDwarfToWasmData, +}; diff --git a/devtools/client/shared/source-map-loader/wasm-dwarf/wasmDwarfExpressions.js b/devtools/client/shared/source-map-loader/wasm-dwarf/wasmDwarfExpressions.js new file mode 100644 index 0000000000..c2f124e99f --- /dev/null +++ b/devtools/client/shared/source-map-loader/wasm-dwarf/wasmDwarfExpressions.js @@ -0,0 +1,260 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ + +/* eslint camelcase: 0*/ +/* eslint-disable no-inline-comments */ + +"use strict"; + +class Value { + val; + + constructor(val) { + this.val = val; + } + toString() { + return `${this.val}`; + } +} + +const Int32Formatter = { + fromAddr(addr) { + return `(new DataView(memory0.buffer).getInt32(${addr}, true))`; + }, + fromValue(value) { + return `${value.val}`; + }, +}; + +const Uint32Formatter = { + fromAddr(addr) { + return `(new DataView(memory0.buffer).getUint32(${addr}, true))`; + }, + fromValue(value) { + return `(${value.val} >>> 0)`; + }, +}; + +function createPieceFormatter(bytes) { + let getter; + switch (bytes) { + case 0: + case 1: + getter = "getUint8"; + break; + case 2: + getter = "getUint16"; + break; + case 3: + case 4: + default: + // FIXME 64-bit + getter = "getUint32"; + break; + } + const mask = (1 << (8 * bytes)) - 1; + return { + fromAddr(addr) { + return `(new DataView(memory0.buffer).${getter}(${addr}, true))`; + }, + fromValue(value) { + return `((${value.val} & ${mask}) >>> 0)`; + }, + }; +} + +// eslint-disable-next-line complexity +function toJS(buf, typeFormatter, frame_base = "fp()") { + const readU8 = function () { + return buf[i++]; + }; + const readS8 = function () { + return (readU8() << 24) >> 24; + }; + const readU16 = function () { + const w = buf[i] | (buf[i + 1] << 8); + i += 2; + return w; + }; + const readS16 = function () { + return (readU16() << 16) >> 16; + }; + const readS32 = function () { + const w = + buf[i] | (buf[i + 1] << 8) | (buf[i + 2] << 16) | (buf[i + 3] << 24); + i += 4; + return w; + }; + const readU32 = function () { + return readS32() >>> 0; + }; + const readU = function () { + let n = 0, + shift = 0, + b; + while ((b = readU8()) & 0x80) { + n |= (b & 0x7f) << shift; + shift += 7; + } + return n | (b << shift); + }; + const readS = function () { + let n = 0, + shift = 0, + b; + while ((b = readU8()) & 0x80) { + n |= (b & 0x7f) << shift; + shift += 7; + } + n |= b << shift; + shift += 7; + return shift > 32 ? (n << (32 - shift)) >> (32 - shift) : n; + }; + const popValue = function (formatter) { + const loc = stack.pop(); + if (loc instanceof Value) { + return formatter.fromValue(loc); + } + return formatter.fromAddr(loc); + }; + let i = 0, + a, + b; + const stack = [frame_base]; + while (i < buf.length) { + const code = buf[i++]; + switch (code) { + case 0x03 /* DW_OP_addr */: + stack.push(Uint32Formatter.fromAddr(readU32())); + break; + case 0x08 /* DW_OP_const1u 0x08 1 1-byte constant */: + stack.push(readU8()); + break; + case 0x09 /* DW_OP_const1s 0x09 1 1-byte constant */: + stack.push(readS8()); + break; + case 0x0a /* DW_OP_const2u 0x0a 1 2-byte constant */: + stack.push(readU16()); + break; + case 0x0b /* DW_OP_const2s 0x0b 1 2-byte constant */: + stack.push(readS16()); + break; + case 0x0c /* DW_OP_const2u 0x0a 1 2-byte constant */: + stack.push(readU32()); + break; + case 0x0d /* DW_OP_const2s 0x0b 1 2-byte constant */: + stack.push(readS32()); + break; + case 0x10 /* DW_OP_constu 0x10 1 ULEB128 constant */: + stack.push(readU()); + break; + case 0x11 /* DW_OP_const2s 0x0b 1 2-byte constant */: + stack.push(readS()); + break; + + case 0x1c /* DW_OP_minus */: + b = stack.pop(); + a = stack.pop(); + stack.push(`${a} - ${b}`); + break; + + case 0x22 /* DW_OP_plus */: + b = stack.pop(); + a = stack.pop(); + stack.push(`${a} + ${b}`); + break; + + case 0x23 /* DW_OP_plus_uconst */: + b = readU(); + a = stack.pop(); + stack.push(`${a} + ${b}`); + break; + + case 0x30 /* DW_OP_lit0 */: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3a: + case 0x3b: + case 0x3c: + case 0x3d: + case 0x3e: + case 0x3f: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4a: + case 0x4b: + case 0x4c: + case 0x4d: + case 0x4e: + case 0x4f: + stack.push(`${code - 0x30}`); + break; + + case 0x93 /* DW_OP_piece */: { + a = readU(); + const formatter = createPieceFormatter(a); + stack.push(popValue(formatter)); + break; + } + + case 0x9f /* DW_OP_stack_value */: + stack.push(new Value(stack.pop())); + break; + + case 0xf6 /* WASM ext (old, FIXME phase out) */: + case 0xed /* WASM ext */: + b = readU(); + a = readS(); + switch (b) { + case 0: + stack.push(`var${a}`); + break; + case 1: + stack.push(`global${a}`); + break; + default: + stack.push(`ti${b}(${a})`); + break; + } + break; + + default: + // Unknown encoding, baling out + return null; + } + } + // FIXME use real DWARF type information + return popValue(typeFormatter); +} + +function decodeExpr(expr) { + if (expr.includes("//")) { + expr = expr.slice(0, expr.indexOf("//")).trim(); + } + const code = new Uint8Array(expr.length >> 1); + for (let i = 0; i < code.length; i++) { + code[i] = parseInt(expr.substr(i << 1, 2), 16); + } + const typeFormatter = Int32Formatter; + return toJS(code, typeFormatter) || `dwarf("${expr}")`; +} + +module.exports = { + decodeExpr, +}; diff --git a/devtools/client/shared/source-map-loader/wasm-dwarf/wasmXScopes.js b/devtools/client/shared/source-map-loader/wasm-dwarf/wasmXScopes.js new file mode 100644 index 0000000000..82faa51dbe --- /dev/null +++ b/devtools/client/shared/source-map-loader/wasm-dwarf/wasmXScopes.js @@ -0,0 +1,215 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ + +/* eslint camelcase: 0*/ + +"use strict"; + +const { + decodeExpr, +} = require("resource://devtools/client/shared/source-map-loader/wasm-dwarf/wasmDwarfExpressions"); + +const xScopes = new Map(); + +function indexLinkingNames(items) { + const result = new Map(); + let queue = [...items]; + while (queue.length) { + const item = queue.shift(); + if ("uid" in item) { + result.set(item.uid, item); + } else if ("linkage_name" in item) { + // TODO the linkage_name string value is used for compatibility + // with old format. Remove in favour of the uid referencing. + result.set(item.linkage_name, item); + } + if ("children" in item) { + queue = [...queue, ...item.children]; + } + } + return result; +} + +function getIndexedItem(index, key) { + if (typeof key === "object" && key != null) { + return index.get(key.uid); + } + if (typeof key === "string") { + return index.get(key); + } + return null; +} + +async function getXScopes(sourceId, getSourceMap) { + if (xScopes.has(sourceId)) { + return xScopes.get(sourceId); + } + const map = await getSourceMap(sourceId); + if (!map || !map.xScopes) { + xScopes.set(sourceId, null); + return null; + } + const { code_section_offset, debug_info } = map.xScopes; + const xScope = { + code_section_offset, + debug_info, + idIndex: indexLinkingNames(debug_info), + sources: map.sources, + }; + xScopes.set(sourceId, xScope); + return xScope; +} + +function isInRange(item, pc) { + if ("ranges" in item) { + return item.ranges.some(r => r[0] <= pc && pc < r[1]); + } + if ("high_pc" in item) { + return item.low_pc <= pc && pc < item.high_pc; + } + return false; +} + +function decodeExprAt(expr, pc) { + if (typeof expr === "string") { + return decodeExpr(expr); + } + const foundAt = expr.find(i => i.range[0] <= pc && pc < i.range[1]); + return foundAt ? decodeExpr(foundAt.expr) : null; +} + +function getVariables(scope, pc) { + const vars = scope.children + ? scope.children.reduce((result, item) => { + switch (item.tag) { + case "variable": + case "formal_parameter": + result.push({ + name: item.name || "", + expr: item.location ? decodeExprAt(item.location, pc) : null, + }); + break; + case "lexical_block": + // FIXME build scope blocks (instead of combining) + const tmp = getVariables(item, pc); + result = [...tmp.vars, ...result]; + break; + } + return result; + }, []) + : []; + const frameBase = scope.frame_base ? decodeExpr(scope.frame_base) : null; + return { + vars, + frameBase, + }; +} + +function filterScopes(items, pc, lastItem, index) { + if (!items) { + return []; + } + return items.reduce((result, item) => { + switch (item.tag) { + case "compile_unit": + if (isInRange(item, pc)) { + result = [ + ...result, + ...filterScopes(item.children, pc, lastItem, index), + ]; + } + break; + case "namespace": + case "structure_type": + case "union_type": + result = [ + ...result, + ...filterScopes(item.children, pc, lastItem, index), + ]; + break; + case "subprogram": + if (isInRange(item, pc)) { + const s = { + id: item.linkage_name, + name: item.name, + variables: getVariables(item, pc), + }; + result = [...result, s, ...filterScopes(item.children, pc, s, index)]; + } + break; + case "inlined_subroutine": + if (isInRange(item, pc)) { + const linkedItem = getIndexedItem(index, item.abstract_origin); + const s = { + id: item.abstract_origin, + name: linkedItem ? linkedItem.name : void 0, + variables: getVariables(item, pc), + }; + if (lastItem) { + lastItem.file = item.call_file; + lastItem.line = item.call_line; + } + result = [...result, s, ...filterScopes(item.children, pc, s, index)]; + } + break; + } + return result; + }, []); +} + +class XScope { + xScope; + sourceMapContext; + + constructor(xScopeData, sourceMapContext) { + this.xScope = xScopeData; + this.sourceMapContext = sourceMapContext; + } + + search(generatedLocation) { + const { code_section_offset, debug_info, sources, idIndex } = this.xScope; + const pc = generatedLocation.line - (code_section_offset || 0); + const scopes = filterScopes(debug_info, pc, null, idIndex); + scopes.reverse(); + + return scopes.map(i => { + if (!("file" in i)) { + return { + displayName: i.name || "", + variables: i.variables, + }; + } + const sourceId = this.sourceMapContext.generatedToOriginalId( + generatedLocation.sourceId, + sources[i.file || 0] + ); + return { + displayName: i.name || "", + variables: i.variables, + location: { + line: i.line || 0, + sourceId, + }, + }; + }); + } +} + +async function getWasmXScopes(sourceId, sourceMapContext) { + const { getSourceMap } = sourceMapContext; + const xScopeData = await getXScopes(sourceId, getSourceMap); + if (!xScopeData) { + return null; + } + return new XScope(xScopeData, sourceMapContext); +} + +function clearWasmXScopes() { + xScopes.clear(); +} + +module.exports = { + getWasmXScopes, + clearWasmXScopes, +}; |