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