diff --git a/static/graph-layout-core.ts b/static/graph-layout-core.ts index c35b9deea..9c6809845 100644 --- a/static/graph-layout-core.ts +++ b/static/graph-layout-core.ts @@ -160,7 +160,10 @@ export class GraphLayoutCore { edgeRows: (RowDescriptor & EdgeRowMetadata)[]; readonly layoutTime: number; - constructor(cfg: AnnotatedCfgDescriptor) { + constructor( + cfg: AnnotatedCfgDescriptor, + readonly centerParents: boolean, + ) { this.populate_graph(cfg); const start = performance.now(); @@ -358,9 +361,14 @@ export class GraphLayoutCore { boundingBox.rows++; block.boundingBox = boundingBox; block.row = 0; - // between immediate children - const [left, right] = [this.blocks[block.treeEdges[0]], this.blocks[block.treeEdges[1]]]; - block.col = Math.floor((left.col + right.col) / 2); // TODO + if (this.centerParents) { + // center of bounding box + block.col = Math.floor(Math.max(boundingBox.cols - 2, 0) / 2); + } else { + // center between immediate children + const [left, right] = [this.blocks[block.treeEdges[0]], this.blocks[block.treeEdges[1]]]; + block.col = Math.floor((left.col + right.col) / 2); + } } } diff --git a/static/panes/cfg-view.interfaces.ts b/static/panes/cfg-view.interfaces.ts index c411650bd..12ee09cde 100644 --- a/static/panes/cfg-view.interfaces.ts +++ b/static/panes/cfg-view.interfaces.ts @@ -25,6 +25,7 @@ export interface CfgState { selectedFunction: string | null; isircfg?: boolean; + centerparents?: boolean; } /* diff --git a/static/panes/cfg-view.ts b/static/panes/cfg-view.ts index 69e9923c4..98f7d952d 100644 --- a/static/panes/cfg-view.ts +++ b/static/panes/cfg-view.ts @@ -48,6 +48,7 @@ import {CompilerInfo} from '../../types/compiler.interfaces.js'; import {assert, unwrap} from '../assert.js'; import {GraphLayoutCore} from '../graph-layout-core.js'; import * as MonacoConfig from '../monaco-config.js'; +import {Toggles} from '../widgets/toggles.js'; const ColorTable = { red: '#FE5D5D', @@ -107,6 +108,7 @@ export class Cfg extends Pane { graphContainer: HTMLElement; graphElement: HTMLElement; infoElement: HTMLElement; + centerParentsButton: JQuery; exportPNGButton: JQuery; estimatedPNGSize: Element; exportSVGButton: JQuery; @@ -118,6 +120,8 @@ export class Cfg extends Pane { functionSelector: TomSelect; resetViewButton: JQuery; zoomOutButton: JQuery; + toggles: Toggles; + results: CFGResult; state: CfgState & PaneState; layout: GraphLayoutCore; @@ -201,10 +205,13 @@ export class Cfg extends Pane { this.graphContainer = this.domRoot.find('.graph-container')[0]; this.graphElement = this.domRoot.find('.graph')[0]; this.infoElement = this.domRoot.find('.cfg-info')[0]; + this.centerParentsButton = this.domRoot.find('.center-parents'); this.exportPNGButton = this.domRoot.find('.export-png').first(); this.estimatedPNGSize = unwrap(this.exportPNGButton[0].querySelector('.estimated-export-size')); this.exportSVGButton = this.domRoot.find('.export-svg').first(); this.setupFictitiousGraphContainer(); + + this.toggles = new Toggles(this.domRoot.find('.options'), state as unknown as Record); } setupFictitiousGraphContainer() { @@ -271,6 +278,7 @@ export class Cfg extends Pane { } e.preventDefault(); }); + this.toggles.on('change', this.onToggleChange.bind(this)); this.exportPNGButton.on('click', () => { this.exportPNG(); }); @@ -290,6 +298,12 @@ export class Cfg extends Pane { }); } + onToggleChange() { + this.state = this.getCurrentState(); + this.updateState(); + this.selectFunction(this.state.selectedFunction); + } + async exportPNG() { fileSaver.saveAs(await this.createPNG(), 'cfg.png'); } @@ -519,7 +533,7 @@ export class Cfg extends Pane { const fn = this.results[name]; this.bbMap = {}; await this.createBasicBlocks(fn); - this.layout = new GraphLayoutCore(fn as AnnotatedCfgDescriptor); + this.layout = new GraphLayoutCore(fn as AnnotatedCfgDescriptor, !!this.state.centerparents); this.applyLayout(); this.drawEdges(); this.infoElement.innerHTML = `Layout time: ${Math.round(this.layout.layoutTime)}ms
Basic blocks: ${ @@ -695,6 +709,7 @@ export class Cfg extends Pane { treeid: this.compilerInfo.treeId, selectedFunction: this.state.selectedFunction, isircfg: this.state.isircfg, + centerparents: this.toggles.get().centerparents, }; this.paneRenaming.addState(state); return state; diff --git a/static/styles/explorer.scss b/static/styles/explorer.scss index dda6d05bf..a51c4cedd 100644 --- a/static/styles/explorer.scss +++ b/static/styles/explorer.scss @@ -504,6 +504,10 @@ pre.content.wrap * { font-size: x-small; font-style: italic; } + + .options button { + height: 100%; + } } .change-language { diff --git a/views/templates/panes/cfg.pug b/views/templates/panes/cfg.pug index 0cb36bdb5..9bae3ff83 100644 --- a/views/templates/panes/cfg.pug +++ b/views/templates/panes/cfg.pug @@ -2,6 +2,15 @@ .top-bar.btn-toolbar.bg-light.cfg-toolbar(role="toolbar") .btn-group.btn-group-sm(role="group") select.function-selector + .btn-group.btn-group-sm.filters(role="group") + button.btn.btn-sm.btn-light.dropdown-toggle(type="button" title="Control flow graph options" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label="Change how the control flow graph is rendered") + span.fas.fa-cog + span.hideable Layout Options + .dropdown-menu.options + .button-checkbox + button.dropdown-item.btn.btn-sm.btn-light.center-parents(type="button" title="Center Parents" data-bind="centerparents" aria-pressed="false" aria-label="Center Parents") + span Center Parents + input.d-none(type="checkbox" checked=false) .btn-group.btn-group-sm(role="group" aria-label="CFG Export") button.btn.btn-sm.btn-light.dropdown-toggle(type="button" title="LLVM Opt Pass Options" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label="Set output options") span.fas.fa-arrow-down