xref: /freebsd-src/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/HTMLLogger.js (revision 06c3fb2749bda94cb5201f81ffdb8fa6c3161b2e)
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