add Stack Usage Viewer (#5160)

For #4834
This commit is contained in:
Guo Ci
2023-06-19 21:26:15 -04:00
committed by GitHub
parent c8b131cf2d
commit 7651874a98
19 changed files with 515 additions and 6 deletions

View File

@@ -6,6 +6,7 @@ import {assertNoConsoleOutput, stubConsoleOutput} from '../support/utils';
const PANE_DATA_MAP = {
executor: {name: 'Executor', selector: 'create-executor'},
opt: {name: 'Opt Viewer', selector: 'view-optimization'},
stackusage: {name: 'Stack Usage Viewer', selector: 'view-stack-usage'},
preprocessor: {name: 'Preprocessor', selector: 'view-pp'},
ast: {name: 'Ast Viewer', selector: 'view-ast'},
llvmir: {name: 'LLVM IR', selector: 'view-ir'},

View File

@@ -74,6 +74,7 @@ import {languages} from './languages.js';
import {LlvmAstParser} from './llvm-ast.js';
import {LlvmIrParser} from './llvm-ir.js';
import * as compilerOptInfo from './llvm-opt-transformer.js';
import * as StackUsageTransformer from './stack-usage-transformer.js';
import {logger} from './logger.js';
import {getObjdumperTypeByKey} from './objdumper/index.js';
import {Packager} from './packager.js';
@@ -1064,6 +1065,9 @@ export class BaseCompiler implements ICompiler {
if (this.compiler.supportsOptOutput && backendOptions.produceOptInfo) {
options = options.concat(unwrap(this.compiler.optArg));
}
if (this.compiler.supportsStackUsageOutput && backendOptions.produceStackUsageInfo) {
options = options.concat(unwrap(this.compiler.stackUsageArg));
}
const toolchainPath = this.getDefaultOrOverridenToolchainPath(backendOptions.overrides || []);
@@ -2128,14 +2132,21 @@ export class BaseCompiler implements ICompiler {
}
asmResult.tools = toolsResult;
if (this.compiler.supportsOptOutput && this.optOutputRequested(options)) {
if (this.compiler.supportsOptOutput && backendOptions.produceOptInfo) {
const optPath = path.join(dirPath, `${this.outputFilebase}.opt.yaml`);
if (await fs.pathExists(optPath)) {
asmResult.hasOptOutput = true;
asmResult.optPath = optPath;
}
}
if (this.compiler.supportsStackUsageOutput && backendOptions.produceStackUsageInfo) {
const suPath = path.join(dirPath, `${this.outputFilebase}.su`);
if (await fs.pathExists(suPath)) {
asmResult.hasStackUsageOutput = true;
asmResult.stackUsagePath = suPath;
}
}
if (astResult) {
asmResult.hasAstOutput = true;
asmResult.astOutput = astResult;
@@ -2438,6 +2449,7 @@ export class BaseCompiler implements ICompiler {
}
const optOutput = undefined;
const stackUsageOutput = undefined;
await this.afterCompilation(
fullResult.result,
false,
@@ -2448,6 +2460,7 @@ export class BaseCompiler implements ICompiler {
cacheKey.filters,
libsAndOptions.options,
optOutput,
stackUsageOutput,
bypassCache,
path.join(dirPath, 'build'),
);
@@ -2587,7 +2600,7 @@ export class BaseCompiler implements ICompiler {
}
const inputFilename = writeSummary.inputFilename;
const [result, optOutput] = await this.doCompilation(
const [result, optOutput, stackUsageOutput] = await this.doCompilation(
inputFilename,
dirPath,
key,
@@ -2608,6 +2621,7 @@ export class BaseCompiler implements ICompiler {
filters,
options,
optOutput,
stackUsageOutput,
bypassCache,
);
})();
@@ -2626,6 +2640,7 @@ export class BaseCompiler implements ICompiler {
filters,
options,
optOutput,
stackUsageOutput,
bypassCache: BypassCache,
customBuildPath?,
) {
@@ -2637,6 +2652,11 @@ export class BaseCompiler implements ICompiler {
result.optOutput = optOutput;
}
if (result.hasStackUsageOutput) {
delete result.stackUsagePath;
result.stackUsageOutput = stackUsageOutput;
}
const compilationInfo = this.getCompilationInfo(key, result, customBuildPath);
result.tools = _.union(
@@ -2748,6 +2768,26 @@ export class BaseCompiler implements ICompiler {
return output;
}
async processStackUsageOutput(suPath) {
const output = StackUsageTransformer.parse(await fs.readFile(suPath, 'utf-8'));
if (this.compiler.demangler) {
const result = JSON.stringify(output, null, 4);
try {
const demangleResult = await this.exec(
this.compiler.demangler,
[...this.compiler.demanglerArgs, '-n', '-p'],
{input: result},
);
return JSON.parse(demangleResult.stdout);
} catch (exception) {
// swallow exception and return non-demangled output
logger.warn(`Caught exception ${exception} during stack usage demangle parsing`);
}
}
return output;
}
couldSupportASTDump(version: string) {
const versionRegex = /version (\d+.\d+)/;
@@ -2897,6 +2937,7 @@ but nothing was dumped. Possible causes are:
const postProcess = _.compact(this.compiler.postProcess);
const maxSize = this.env.ceProps('max-asm-size', 64 * 1024 * 1024);
const optPromise = result.hasOptOutput ? this.processOptOutput(result.optPath) : '';
const stackUsagePromise = result.hasStackUsageOutput ? this.processStackUsageOutput(result.stackUsagePath) : '';
const asmPromise =
(filters.binary || filters.binaryObject) && this.supportsObjdump()
? this.objdump(
@@ -2928,7 +2969,7 @@ but nothing was dumped. Possible causes are:
return result;
}
})();
return Promise.all([asmPromise, optPromise]);
return Promise.all([asmPromise, optPromise, stackUsagePromise]);
}
handlePostProcessResult(result, postResult): CompilationResult {

View File

@@ -187,6 +187,9 @@ export class ClientStateNormalizer {
this.addSpecialOutputToCompiler(component.componentState.id, 'ast', component.componentState.editorid);
} else if (component.componentName === 'opt') {
this.addSpecialOutputToCompiler(component.componentState.id, 'opt', component.componentState.editorid);
} else if (component.componentName === 'stackusage') {
this.addSpecialOutputToCompiler(component.componentState.id, 'stackusage',
component.componentState.editorid);
} else if (component.componentName === 'cfg') {
this.addSpecialOutputToCompiler(component.componentState.id, 'cfg', component.componentState.editorid);
} else if (component.componentName === 'gccdump') {
@@ -321,6 +324,18 @@ class GoldenLayoutComponents {
reorderEnabled: true,
};
}
createStackUsageComponent(session, compilerIndex, customSessionId?) {
return {
type: 'component',
componentName: 'stackusage',
componentState: {
id: compilerIndex,
editorid: customSessionId || (session ? session.id : undefined),
},
isClosable: true,
reorderEnabled: true,
};
}
createCfgComponent(session, compilerIndex, customSessionId?) {
return {
@@ -505,6 +520,8 @@ class GoldenLayoutComponents {
return this.createAstComponent(session, idxCompiler + 1, customSessionId);
} else if (viewtype === 'opt') {
return this.createOptComponent(session, idxCompiler + 1, customSessionId);
} else if (viewtype === 'stackusage') {
return this.createStackUsageComponent(session, idxCompiler + 1, customSessionId);
} else if (viewtype === 'cfg') {
return this.createCfgComponent(session, idxCompiler + 1, customSessionId);
} else if (viewtype === 'gccdump') {
@@ -519,6 +536,8 @@ class GoldenLayoutComponents {
return this.createAstComponent(null, idxCompiler + 1, false);
} else if (viewtype === 'opt') {
return this.createOptComponent(null, idxCompiler + 1, false);
} else if (viewtype === 'stackusage') {
return this.createOptComponent(null, idxCompiler + 1, false);
} else if (viewtype === 'cfg') {
return this.createCfgComponent(null, idxCompiler + 1, false);
} else if (viewtype === 'gccdump') {

View File

@@ -161,6 +161,10 @@ export class GCCParser extends BaseParser {
if (this.hasSupport(options, '-masm=')) {
await this.checkAndSetMasmIntelIfSupported(compiler);
}
if (this.hasSupport(options, '-fstack-usage')) {
compiler.compiler.stackUsageArg = '-fstack-usage';
compiler.compiler.supportsStackUsageOutput = true;
}
if (this.hasSupport(options, '-fdiagnostics-color')) {
if (compiler.compiler.options) compiler.compiler.options += ' ';
compiler.compiler.options += '-fdiagnostics-color=always';
@@ -255,6 +259,11 @@ export class ClangParser extends BaseParser {
compiler.compiler.optArg = '-fsave-optimization-record';
compiler.compiler.supportsOptOutput = true;
}
if (this.hasSupport(options, '-fstack-usage')) {
compiler.compiler.stackUsageArg = '-fstack-usage';
compiler.compiler.supportsStackUsageOutput = true;
}
if (this.hasSupport(options, '-emit-llvm')) {
compiler.compiler.supportsIrView = true;
compiler.compiler.irArg = ['-Xclang', '-emit-llvm', '-fsyntax-only'];

View File

@@ -226,7 +226,7 @@ export class GolangCompiler extends BaseCompiler {
result.asm = this.convertNewGoL(out);
result.stderr = [];
result.stdout = utils.parseOutput(logging, result.inputFilename);
return Promise.all([result, '']);
return Promise.all([result, '', '']);
}
override getSharedLibraryPathsAsArguments() {

View File

@@ -110,7 +110,7 @@ export class NvccCompiler extends BaseCompiler {
result.asm = typeof asm === 'string' ? asm : asm.asm;
return result;
});
return Promise.all([asmPromise, optPromise]);
return Promise.all([asmPromise, optPromise, '']);
}
override async extractDeviceCode(result, filters, compilationInfo: CompilationInfo) {

View File

@@ -0,0 +1,57 @@
// Copyright (c) 2023, Compiler Explorer Authors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
type Path = string;
type StackInfo = {
displayString: string;
}
export type StackUsageInfo = StackInfo & {
DebugLoc: DebugLoc;
Function: string;
BytesUsed: number;
Qualifier: 'static' | 'dynamic' | 'dynamic,bounded';
}
type DebugLoc = {
File: Path;
Line: number;
Column: number;
}
export function parse(suText: string) {
const output: StackUsageInfo[] = [];
suText.split('\n').filter(e => e)
.forEach(line => {
const c = line.split('\t');
const pathLocName = c[0].split(':');
const lineNumber = +pathLocName[1];
const qualifier = c.at(-1);
const su = {
DebugLoc: {File: pathLocName[0], Line: lineNumber, Column: 0},
Function: pathLocName.at(-1),
Qualifier: qualifier,
BytesUsed: parseInt(c[1]),
displayString: c[1] + ' bytes, ' + qualifier,
};
output.push(su as StackUsageInfo);
});
return output;
}

View File

@@ -37,6 +37,7 @@ export const TOOL_COMPONENT_NAME = 'tool';
export const TOOL_INPUT_VIEW_COMPONENT_NAME = 'toolInputView';
export const DIFF_VIEW_COMPONENT_NAME = 'diff';
export const OPT_VIEW_COMPONENT_NAME = 'opt';
export const STACK_USAGE_VIEW_COMPONENT_NAME = 'stackusage';
export const FLAGS_VIEW_COMPONENT_NAME = 'flags';
export const PP_VIEW_COMPONENT_NAME = 'pp';
export const AST_VIEW_COMPONENT_NAME = 'ast';
@@ -144,6 +145,15 @@ export type PopulatedOptViewState = StateWithId &
treeid: number;
};
export type EmptyStackUsageViewState = EmptyState;
export type PopulatedStackUsageViewState = StateWithId &
StateWithEditor & {
suOutput: unknown;
compilerName: string;
editorid: number;
treeid: number;
};
export type EmptyFlagsViewState = EmptyState;
export type PopulatedFlagsViewState = StateWithId & {
compilerName: string;

View File

@@ -84,6 +84,7 @@ import {
TOOL_INPUT_VIEW_COMPONENT_NAME,
DIFF_VIEW_COMPONENT_NAME,
OPT_VIEW_COMPONENT_NAME,
STACK_USAGE_VIEW_COMPONENT_NAME,
FLAGS_VIEW_COMPONENT_NAME,
PP_VIEW_COMPONENT_NAME,
AST_VIEW_COMPONENT_NAME,
@@ -103,6 +104,7 @@ import {
LLVM_OPT_PIPELINE_VIEW_COMPONENT_NAME,
EmptyLLVMOptPipelineViewState,
PopulatedLLVMOptPipelineViewState,
PopulatedStackUsageViewState, EmptyStackUsageViewState,
} from './components.interfaces.js';
import {ConfiguredOverrides} from './compilation/compiler-overrides.interfaces.js';
@@ -387,6 +389,35 @@ export function getOptViewWith(
};
}
export function getStackUsageView(): ComponentConfig<EmptyStackUsageViewState> {
return {
type: 'component',
componentName: STACK_USAGE_VIEW_COMPONENT_NAME,
componentState: {},
};
}
export function getStackUsageViewWith(
id: number,
source: string,
suOutput: unknown,
compilerName: string,
editorid: number,
treeid: number,
): ComponentConfig<PopulatedStackUsageViewState> {
return {
type: 'component',
componentName: STACK_USAGE_VIEW_COMPONENT_NAME,
componentState: {
id,
source,
suOutput,
compilerName,
editorid,
treeid,
},
};
}
/** Get an empty flags view component. */
export function getFlagsView(): ComponentConfig<EmptyFlagsViewState> {
return {

View File

@@ -126,6 +126,8 @@ export type EventMap = {
newSource: (editorId: number, newSource: string) => void;
optViewClosed: (compilerId: number) => void;
optViewOpened: (compilerId: number) => void;
stackUsageViewClosed: (compilerId: number) => void;
stackUsageViewOpened: (compilerId: number) => void;
outputClosed: (compilerId: number) => void;
outputOpened: (compilerId: number) => void;
panesLinkLine: (

View File

@@ -44,6 +44,7 @@ import {
IR_VIEW_COMPONENT_NAME,
LLVM_OPT_PIPELINE_VIEW_COMPONENT_NAME,
OPT_VIEW_COMPONENT_NAME,
STACK_USAGE_VIEW_COMPONENT_NAME,
OUTPUT_COMPONENT_NAME,
PP_VIEW_COMPONENT_NAME,
RUST_HIR_VIEW_COMPONENT_NAME,
@@ -63,6 +64,7 @@ import {Tool} from './panes/tool.js';
import {Diff} from './panes/diff.js';
import {ToolInputView} from './panes/tool-input-view.js';
import {Opt as OptView} from './panes/opt-view.js';
import {StackUsage as StackUsageView} from './panes/stack-usage-view.js';
import {Flags as FlagsView} from './panes/flags-view.js';
import {PP as PreProcessorView} from './panes/pp-view.js';
import {Ast as AstView} from './panes/ast-view.js';
@@ -121,6 +123,7 @@ export class Hub {
layout.registerComponent(TOOL_INPUT_VIEW_COMPONENT_NAME, (c, s) => this.toolInputViewFactory(c, s));
layout.registerComponent(DIFF_VIEW_COMPONENT_NAME, (c, s) => this.diffFactory(c, s));
layout.registerComponent(OPT_VIEW_COMPONENT_NAME, (c, s) => this.optViewFactory(c, s));
layout.registerComponent(STACK_USAGE_VIEW_COMPONENT_NAME, (c, s) => this.stackUsageViewFactory(c, s));
layout.registerComponent(FLAGS_VIEW_COMPONENT_NAME, (c, s) => this.flagsViewFactory(c, s));
layout.registerComponent(PP_VIEW_COMPONENT_NAME, (c, s) => this.ppViewFactory(c, s));
layout.registerComponent(AST_VIEW_COMPONENT_NAME, (c, s) => this.astViewFactory(c, s));
@@ -426,6 +429,11 @@ export class Hub {
return new OptView(this, container, state);
}
public stackUsageViewFactory(container: GoldenLayout.Container,
state: ConstructorParameters<typeof StackUsageView>[2]): StackUsageView {
return new StackUsageView(this, container, state);
}
public flagsViewFactory(
container: GoldenLayout.Container,
state: ConstructorParameters<typeof FlagsView>[2],

View File

@@ -179,6 +179,7 @@ export class Compiler extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Co
private nextCMakeRequest: CompilationRequest | null;
private flagsViewOpen: boolean;
private optViewOpen: boolean;
private stackUsageViewOpen: boolean;
private cfgViewOpen: boolean;
private wantOptInfo?: boolean;
private readonly decorations: Decorations;
@@ -195,6 +196,7 @@ export class Compiler extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Co
private currentLangId: string | null;
private filters: Toggles;
private optButton: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>;
private stackUsageButton: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>;
private flagsButton?: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>;
private ppButton: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>;
private astButton: JQuery<HTMLElementTagNameMap[keyof HTMLElementTagNameMap]>;
@@ -475,6 +477,16 @@ export class Compiler extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Co
this.sourceTreeId ?? 0,
);
};
const createStackUsageView = () => {
return Components.getStackUsageViewWith(
this.id,
this.source,
this.lastResult?.optOutput,
this.getCompilerName(),
this.sourceEditorId ?? 0,
this.sourceTreeId ?? 0,
);
};
const createFlagsView = () => {
return Components.getFlagsViewWith(this.id, this.getCompilerName(), this.optionsField.val());
@@ -696,6 +708,17 @@ export class Compiler extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Co
insertPoint.addChild(createOptView());
});
(this.container.layoutManager
.createDragSource(this.stackUsageButton, createStackUsageView as any) as any)
._dragListener.on('dragStart', togglePannerAdder);
this.stackUsageButton.on('click', () => {
const insertPoint =
this.hub.findParentRowOrColumn(this.container.parent) ||
this.container.layoutManager.root.contentItems[0];
insertPoint.addChild(createStackUsageView());
});
const popularArgumentsMenu = this.domRoot.find('div.populararguments div.dropdown-menu');
if (this.flagsButton) {
this.container.layoutManager
@@ -1217,6 +1240,7 @@ export class Compiler extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Co
dumpFlags: this.dumpFlags,
},
produceOptInfo: this.wantOptInfo ?? false,
produceStackUsageInfo: this.stackUsageViewOpen,
produceCfg: this.cfgViewOpen,
produceGnatDebugTree: this.gnatDebugTreeViewOpen,
produceGnatDebug: this.gnatDebugViewOpen,
@@ -1972,6 +1996,12 @@ export class Compiler extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Co
this.optButton.prop('disabled', this.optViewOpen);
}
}
onStackUsageViewClosed(id: number): void {
if (this.id === id) {
this.stackUsageViewOpen = false;
this.stackUsageButton.prop('disabled', this.stackUsageViewOpen);
}
}
onFlagsViewClosed(id: number, compilerFlags: string): void {
if (this.id === id) {
@@ -2297,6 +2327,13 @@ export class Compiler extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Co
this.compile();
}
}
onStackUsageViewOpened(id: number): void {
if (this.id === id) {
this.stackUsageViewOpen = true;
this.stackUsageButton.prop('disabled', this.stackUsageViewOpen);
this.compile();
}
}
onFlagsViewOpened(id: number): void {
if (this.id === id) {
@@ -2368,6 +2405,7 @@ export class Compiler extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Co
this.filters = new Toggles(this.domRoot.find('.filters'), patchOldFilters(state.filters));
this.optButton = this.domRoot.find('.btn.view-optimization');
this.stackUsageButton = this.domRoot.find('.btn.view-stack-usage');
this.flagsButton = this.domRoot.find('div.populararguments div.dropdown-menu button');
this.ppButton = this.domRoot.find('.btn.view-pp');
this.astButton = this.domRoot.find('.btn.view-ast');
@@ -2665,6 +2703,7 @@ export class Compiler extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Co
// many executors as you want.
this.optButton.toggle(!!this.compiler.supportsOptOutput);
this.stackUsageButton.toggle(!!this.compiler.supportsStackUsageOutput);
this.ppButton.toggle(!!this.compiler.supportsPpView);
this.astButton.toggle(!!this.compiler.supportsAstView);
this.irButton.toggle(!!this.compiler.supportsIrView);
@@ -2819,6 +2858,8 @@ export class Compiler extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Co
this.eventHub.on('optViewOpened', this.onOptViewOpened, this);
this.eventHub.on('optViewClosed', this.onOptViewClosed, this);
this.eventHub.on('stackUsageViewOpened', this.onStackUsageViewOpened, this);
this.eventHub.on('stackUsageViewClosed', this.onStackUsageViewClosed, this);
this.eventHub.on('flagsViewOpened', this.onFlagsViewOpened, this);
this.eventHub.on('flagsViewClosed', this.onFlagsViewClosed, this);
this.eventHub.on('ppViewOpened', this.onPpViewOpened, this);

View File

@@ -0,0 +1,43 @@
// Copyright (c) 2023, Compiler Explorer Authors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
export interface StackUsageState {
suOutput?: suCodeEntry[];
source: any; // TODO
}
type SourceLocation = {
File: string;
Line: number;
Column: number;
};
export type suCodeEntry = {
DebugLoc: SourceLocation;
Function: string;
// https://github.com/gcc-mirror/gcc/blob/master/gcc/toplev.cc#L773-L775
Qualifier: 'static' | 'dynamic' | 'dynamic,bounded';
BytesUsed: number;
displayString: string;
};

View File

@@ -0,0 +1,171 @@
// Copyright (c) 2023, Compiler Explorer Authors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
import $ from 'jquery';
import _ from 'underscore';
import * as monaco from 'monaco-editor';
import {Container} from 'golden-layout';
import {MonacoPane} from './pane.js';
import {StackUsageState, suCodeEntry} from './stack-usage-view.interfaces.js';
import {MonacoPaneState} from './pane.interfaces.js';
import {ga} from '../analytics.js';
import {extendConfig} from '../monaco-config.js';
import {Hub} from '../hub.js';
export class StackUsage extends MonacoPane<monaco.editor.IStandaloneCodeEditor, StackUsageState> {
currentDecorations: string[] = [];
// Note: bool | undef here instead of just bool because of an issue with field initialization order
isCompilerSupported?: boolean;
constructor(hub: Hub, container: Container, state: StackUsageState & MonacoPaneState) {
super(hub, container, state);
if (state.suOutput) {
this.showStackUsageResults(state.suOutput);
}
this.eventHub.emit('stackUsageViewOpened', this.compilerInfo.compilerId);
}
override getInitialHTML(): string {
return $('#stackusage').html();
}
override createEditor(editorRoot: HTMLElement): monaco.editor.IStandaloneCodeEditor {
return monaco.editor.create(
editorRoot,
extendConfig({
language: 'plaintext',
readOnly: true,
glyphMargin: true,
}),
);
}
override registerOpeningAnalyticsEvent() {
ga.proxy('send', {
hitType: 'event',
eventCategory: 'OpenViewPane',
eventAction: 'StackUsage',
});
}
override registerCallbacks() {
this.eventHub.emit('requestSettings');
this.eventHub.emit('findCompilers');
this.container.on('shown', this.resize, this);
const cursorSelectionThrottledFunction = _.throttle(this.onDidChangeCursorSelection.bind(this), 500);
this.editor.onDidChangeCursorSelection(e => {
cursorSelectionThrottledFunction(e);
});
}
override onCompileResult(id: number, compiler, result) {
if (this.compilerInfo.compilerId !== id || !this.isCompilerSupported) return;
this.editor.setValue(result.source);
if (result.hasStackUsageOutput) {
this.showStackUsageResults(result.stackUsageOutput);
}
// TODO: This is inelegant again. Previously took advantage of fourth argument for the compileResult event.
const lang = compiler.lang === 'c++' ? 'cpp' : compiler.lang;
const model = this.editor.getModel();
if (model != null && this.getCurrentEditorLanguage() !== lang) {
monaco.editor.setModelLanguage(model, lang);
}
if (!this.isAwaitingInitialResults) {
if (this.selection) {
this.editor.setSelection(this.selection);
this.editor.revealLinesInCenter(this.selection.startLineNumber, this.selection.endLineNumber);
}
this.isAwaitingInitialResults = true;
}
}
// Monaco language id of the current editor
getCurrentEditorLanguage() {
return this.editor.getModel()?.getLanguageId();
}
override getDefaultPaneName() {
return 'Stack Usage Viewer';
}
getDisplayableOpt(optResult: suCodeEntry) {
return {
value: optResult.displayString,
isTrusted: false,
};
}
showStackUsageResults(results: suCodeEntry[]) {
const su: monaco.editor.IModelDeltaDecoration[] = [];
const groupedResults = _.groupBy(results, x => x.DebugLoc.Line);
for (const [key, value] of Object.entries(groupedResults)) {
const linenumber = Number(key);
const className = value.reduce((acc, x) => {
// reuse CSS in opt-view.ts
if (x.Qualifier === 'static' || acc === 'static') {
return 'Missed';
} else if (x.Qualifier === 'dynamic' || acc === 'dynamic') {
return 'Passed';
}
return 'Mixed';
}, '');
const contents = value.map(this.getDisplayableOpt);
su.push({
range: new monaco.Range(linenumber, 1, linenumber, Infinity),
options: {
isWholeLine: true,
glyphMarginClassName: 'opt-decoration.' + className.toLowerCase(),
hoverMessage: contents,
glyphMarginHoverMessage: contents,
},
});
}
this.currentDecorations = this.editor.deltaDecorations(this.currentDecorations, su);
}
override onCompiler(id: number, compiler) {
if (id === this.compilerInfo.compilerId) {
this.compilerInfo.compilerName = compiler ? compiler.name : '';
this.updateTitle();
this.isCompilerSupported = compiler ? compiler.supportsStackUsageOutput : false;
if (!this.isCompilerSupported) {
this.editor.setValue('<Stack usage output is not supported for this compiler>');
}
}
}
override close() {
this.eventHub.unsubscribe();
this.eventHub.emit('stackUsageViewClosed', this.compilerInfo.compilerId);
this.editor.dispose();
}
}

View File

@@ -0,0 +1,65 @@
// Copyright (c) 2023, Compiler Explorer Authors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import {parse} from '../lib/stack-usage-transformer.js';
describe('stack usage transformer', () => {
it('should work', async () => {
const doc = `example.cpp:2:5:int square(int)\t16\tstatic
example.cpp:6:5:int f()\t32\tdynamic
example.cpp:7:5:int h()\t64\tdynamic,bounded
`;
const output = parse(doc);
output.should.deep.equal([
{
BytesUsed: 16,
DebugLoc: {
File: 'example.cpp',
Line: 2,
Column: 0,
},
Function: 'int square(int)',
Qualifier: 'static',
displayString: '16 bytes, static',
},
{
BytesUsed: 32,
DebugLoc: {
File: 'example.cpp',
Line: 6,
Column: 0,
},
Function: 'int f()',
Qualifier: 'dynamic',
displayString: '32 bytes, dynamic',
},
{
BytesUsed: 64,
DebugLoc: {
File: 'example.cpp',
Line: 7,
Column: 0,
},
Function: 'int h()',
Qualifier: 'dynamic,bounded',
displayString: '64 bytes, dynamic,bounded',
},
]);
});
});

View File

@@ -26,6 +26,7 @@ import {BuildEnvDownloadInfo} from '../../lib/buildenvsetup/buildenv.interfaces.
import {IAsmParser} from '../../lib/parsers/asm-parser.interfaces.js';
import type {GccDumpViewSelectedPass} from '../../static/panes/gccdump-view.interfaces.js';
import type {PPOptions} from '../../static/panes/pp-view.interfaces.js';
import {suCodeEntry} from '../../static/panes/stack-usage-view.interfaces.js';
import {CompilerInfo} from '../compiler.interfaces.js';
import {BasicExecutionResult} from '../execution/execution.interfaces.js';
import {ParseFiltersAndOutputOptions} from '../features/filters.interfaces.js';
@@ -68,6 +69,7 @@ export type CompilationRequestOptions = {
dumpFlags: any;
};
produceOptInfo?: boolean;
produceStackUsageInfo?: boolean;
produceCfg?: boolean;
produceGnatDebugTree?: boolean;
produceGnatDebug?: boolean;
@@ -155,6 +157,10 @@ export type CompilationResult = {
optOutput?: any;
optPath?: string;
hasStackUsageOutput?: boolean;
stackUsageOutput?: suCodeEntry[];
stackUsagePath?: string;
hasAstOutput?: boolean;
astOutput?: any;

View File

@@ -69,6 +69,7 @@ export type CompilerInfo = {
supportsGccDump?: boolean;
supportsFiltersInBinary?: boolean;
supportsOptOutput?: boolean;
supportsStackUsageOutput?: boolean;
supportsPpView?: boolean;
supportsAstView?: boolean;
supportsIrView?: boolean;
@@ -123,6 +124,7 @@ export type CompilerInfo = {
possibleOverrides?: AllCompilerOverrideOptions;
disabledFilters: string[];
optArg?: string;
stackUsageArg?: string;
externalparser: any;
removeEmptyGccDump?: boolean;
irArg?: string[];

View File

@@ -55,6 +55,7 @@ mixin newPaneButton(classId, text, title, icon)
+newPaneButton("add-compiler", "Clone Compiler", "Clone this compiler window (click or drag)", "far fa-clone")
+newPaneButton("create-executor", "Executor From This", "Create executor from this compiler", "fas fa-microchip")
+newPaneButton("view-optimization", "Optimization", "Show optimization output", "fas fa-weight")
+newPaneButton("view-stack-usage", "Stack Usage", "Show stack usage", "fas fa-layer-group")
+newPaneButton("view-pp", "Preprocessor", "Show preprocessor output", "fas fa-hashtag")
+newPaneButton("view-ast", "AST", "Show AST output", "fas fa-leaf")
+newPaneButton("view-ir", "LLVM IR", "Show LLVM Intermediate Representation", "fas fa-align-center")

View File

@@ -54,6 +54,8 @@ mixin monacopane(id)
include widgets/overrides-favorite-tpl
+monacopane("stackusage")
+monacopane("opt")
+monacopane("flags")