1//===-- HTMLLogger.js -----------------------------------------------------===// 2// 3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4// See https://llvm.org/LICENSE.txt for license information. 5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6// 7//===----------------------------------------------------------------------===// 8 9// Based on selected objects, hide/show sections & populate data from templates. 10// 11// For example, if the selection is {bb="BB4", elt="BB4.6" iter="BB4:2"}: 12// - show the "block" and "element" sections 13// - re-render templates within these sections (if selection changed) 14// - apply "bb-select" to items with class class "BB4", etc 15let selection = {}; 16function updateSelection(changes, data) { 17 Object.assign(selection, changes); 18 19 data = Object.create(data); 20 data.selection = selection; 21 for (root of document.querySelectorAll('[data-selection]')) 22 updateSection(root, data); 23 24 for (var k in changes) 25 applyClassIf(k + '-select', classSelector(changes[k])); 26} 27 28// Given <section data-selection="x,y">: 29// - hide section if selections x or y are null 30// - re-render templates if x or y have changed 31function updateSection(root, data) { 32 let changed = root.selection == null; 33 root.selection ||= {}; 34 for (key of root.dataset.selection.split(',')) { 35 if (!key) continue; 36 if (data.selection[key] != root.selection[key]) { 37 root.selection[key] = data.selection[key]; 38 changed = true; 39 } 40 if (data.selection[key] == null) { 41 root.hidden = true; 42 return; 43 } 44 } 45 if (changed) { 46 root.hidden = false; 47 for (tmpl of root.getElementsByTagName('template')) 48 reinflate(tmpl, data); 49 } 50} 51 52// Expands template `tmpl` based on input `data`: 53// - interpolates {{expressions}} in text and attributes 54// - <template> tags can modify expansion: if, for etc 55// Outputs to `parent` element, inserting before `next`. 56function inflate(tmpl, data, parent, next) { 57 // We use eval() as our expression language in templates! 58 // The templates are static and trusted. 59 let evalExpr = (expr, data) => eval('with (data) { ' + expr + ' }'); 60 let interpolate = (str, data) => 61 str.replace(/\{\{(.*?)\}\}/g, (_, expr) => evalExpr(expr, data)) 62 // Anything other than <template> tag: copy, interpolate, recursively inflate. 63 if (tmpl.nodeName != 'TEMPLATE') { 64 let clone = tmpl.cloneNode(); 65 clone.inflated = true; 66 if (clone instanceof Text) 67 clone.textContent = interpolate(clone.textContent, data); 68 if (clone instanceof Element) { 69 for (attr of clone.attributes) 70 attr.value = interpolate(attr.value, data); 71 for (c of tmpl.childNodes) 72 inflate(c, data, clone, /*next=*/null); 73 } 74 return parent.insertBefore(clone, next); 75 } 76 // data-use="xyz": use <template id="xyz"> instead. (Allows recursion.) 77 if ('use' in tmpl.dataset) 78 return inflate(document.getElementById(tmpl.dataset.use), data, parent, next); 79 // <template> tag handling. Base case: recursively inflate. 80 function handle(data) { 81 for (c of tmpl.content.childNodes) 82 inflate(c, data, parent, next); 83 } 84 // Directives on <template> tags modify behavior. 85 const directives = { 86 // data-for="x in expr": expr is enumerable, bind x to each in turn 87 'for': (nameInExpr, data, proceed) => { 88 let [name, expr] = nameInExpr.split(' in '); 89 let newData = Object.create(data); 90 let index = 0; 91 for (val of evalExpr(expr, data) || []) { 92 newData[name] = val; 93 newData[name + '_index'] = index++; 94 proceed(newData); 95 } 96 }, 97 // data-if="expr": only include contents if expression is truthy 98 'if': (expr, data, proceed) => { if (evalExpr(expr, data)) proceed(data); }, 99 // data-let="x = expr": bind x to value of expr 100 'let': (nameEqExpr, data, proceed) => { 101 let [name, expr] = nameEqExpr.split(' = '); 102 let newData = Object.create(data); 103 newData[name] = evalExpr(expr, data); 104 proceed(newData); 105 }, 106 } 107 // Compose directive handlers on top of the base handler. 108 for (let [dir, value] of Object.entries(tmpl.dataset).reverse()) { 109 if (dir in directives) { 110 let proceed = handle; 111 handle = (data) => directives[dir](value, data, proceed); 112 } 113 } 114 handle(data); 115} 116// Expand a template, after first removing any prior expansion of it. 117function reinflate(tmpl, data) { 118 // Clear previously rendered template contents. 119 while (tmpl.nextSibling && tmpl.nextSibling.inflated) 120 tmpl.parentNode.removeChild(tmpl.nextSibling); 121 inflate(tmpl, data, tmpl.parentNode, tmpl.nextSibling); 122} 123 124// Handle a mouse event on a region containing selectable items. 125// This might end up changing the hover state or the selection state. 126// 127// targetSelector describes what target HTML element is selectable. 128// targetToID specifies how to determine the selection from it: 129// hover: a function from target to the class name to highlight 130// bb: a function from target to the basic-block name to select (BB4) 131// elt: a function from target to the CFG element name to select (BB4.5) 132// iter: a function from target to the BB iteration to select (BB4:2) 133// If an entry is missing, the selection is unmodified. 134// If an entry is null, the selection is always cleared. 135function mouseEventHandler(event, targetSelector, targetToID, data) { 136 var target = event.type == "mouseout" ? null : event.target.closest(targetSelector); 137 let selTarget = k => (target && targetToID[k]) ? targetToID[k](target) : null; 138 if (event.type == "click") { 139 let newSel = {}; 140 for (var k in targetToID) { 141 if (k == 'hover') continue; 142 let t = selTarget(k); 143 newSel[k] = t; 144 } 145 updateSelection(newSel, data); 146 } else if ("hover" in targetToID) { 147 applyClassIf("hover", classSelector(selTarget("hover"))); 148 } 149} 150function watch(rootSelector, targetSelector, targetToID, data) { 151 var root = document.querySelector(rootSelector); 152 for (event of ['mouseout', 'mousemove', 'click']) 153 root.addEventListener(event, e => mouseEventHandler(e, targetSelector, targetToID, data)); 154} 155function watchSelection(data) { 156 let lastIter = (bb) => `${bb}:${data.cfg[bb].iters}`; 157 watch('#code', '.c', { 158 hover: e => e.dataset.elt, 159 bb: e => e.dataset.bb, 160 elt: e => e.dataset.elt, 161 // If we're already viewing an iteration of this BB, stick with the same. 162 iter: e => (selection.iter && selection.bb == e.dataset.bb) ? selection.iter : lastIter(e.dataset.bb), 163 }, data); 164 watch('#cfg', '.bb', { 165 hover: e => e.id, 166 bb: e => e.id, 167 elt: e => e.id + ".0", 168 iter: e => lastIter(e.id), 169 }, data); 170 watch('#timeline', '.entry', { 171 hover: e => [e.id, e.dataset.bb], 172 bb: e => e.dataset.bb, 173 elt: e => e.dataset.bb + ".0", 174 iter: e => e.id, 175 }, data); 176 watch('#bb-elements', 'tr', { 177 hover: e => e.id, 178 elt: e => e.id, 179 }, data); 180 watch('#iterations', '.chooser', { 181 hover: e => e.dataset.iter, 182 iter: e => e.dataset.iter, 183 }, data); 184 updateSelection({}, data); 185} 186function applyClassIf(cls, query) { 187 document.querySelectorAll('.' + cls).forEach(elt => elt.classList.remove(cls)); 188 document.querySelectorAll(query).forEach(elt => elt.classList.add(cls)); 189} 190// Turns a class name into a CSS selector matching it, with some wrinkles: 191// - we treat id="foo" just like class="foo" to avoid repetition in the HTML 192// - cls can be an array of strings, we match them all 193function classSelector(cls) { 194 if (cls == null) return null; 195 if (Array.isArray(cls)) return cls.map(classSelector).join(', '); 196 var escaped = cls.replace('.', '\\.').replace(':', '\\:'); 197 // don't require id="foo" class="foo" 198 return '.' + escaped + ", #" + escaped; 199} 200 201// Add a stylesheet defining colors for n basic blocks. 202function addBBColors(n) { 203 let sheet = new CSSStyleSheet(); 204 // hex values to subtract from fff to get a base color 205 options = [0x001, 0x010, 0x011, 0x100, 0x101, 0x110, 0x111]; 206 function color(hex) { 207 return "#" + hex.toString(16).padStart(3, "0"); 208 } 209 function add(selector, property, hex) { 210 sheet.insertRule(`${selector} { ${property}: ${color(hex)}; }`) 211 } 212 for (var i = 0; i < n; ++i) { 213 let opt = options[i%options.length]; 214 add(`.B${i}`, 'background-color', 0xfff - 2*opt); 215 add(`#B${i} polygon`, 'fill', 0xfff - 2*opt); 216 add(`#B${i} polygon`, 'stroke', 0x888 - 4*opt); 217 } 218 document.adoptedStyleSheets.push(sheet); 219} 220