aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMats Larsen <me@supergrecko.com>2021-09-26 23:33:09 +0100
committerGitHub <noreply@github.com>2021-09-27 00:33:09 +0200
commit278d28b313005effbc39519a833ddb8d92fea66d (patch)
tree507ff8cd30f331685519130a701742e6a75aeb2b
parente7b9cbda9174b45121fdf625d806809de45525f4 (diff)
downloadcompiler-explorer-278d28b313005effbc39519a833ddb8d92fea66d.tar.gz
compiler-explorer-278d28b313005effbc39519a833ddb8d92fea66d.zip
Add rustc Macro Expansion view (#2932)
Adds a new pane for producing the macro expansion of Rust code compiled with rustc.
-rw-r--r--lib/base-compiler.js42
-rw-r--r--lib/compilers/rust.js2
-rw-r--r--static/components.js20
-rw-r--r--static/hub.js9
-rw-r--r--static/panes/compiler.js36
-rw-r--r--static/panes/rustmacroexp-view.ts122
-rw-r--r--static/tsconfig.json1
-rw-r--r--views/templates.pug10
8 files changed, 238 insertions, 4 deletions
diff --git a/lib/base-compiler.js b/lib/base-compiler.js
index ce4d887ae..5074d541b 100644
--- a/lib/base-compiler.js
+++ b/lib/base-compiler.js
@@ -637,10 +637,11 @@ export class BaseCompiler {
const execOptions = this.getDefaultExecOptions();
// TODO: reconsider this value
execOptions.maxOutput = 1024 ** 3;
+ const mirPath = this.getRustMirOutputFilename(inputFilename);
const rustcOptions = [
inputFilename,
'-o',
- this.getRustMirOutputFilename(inputFilename),
+ mirPath,
'--emit=mir',
'--crate-type',
'rlib',
@@ -651,7 +652,6 @@ export class BaseCompiler {
if (output.code !== 0) {
return [{text: 'Failed to run compiler to get Rust MIR'}];
}
- const mirPath = this.getRustMirOutputFilename(inputFilename);
if (await fs.exists(mirPath)) {
const content = await fs.readFile(mirPath, 'utf-8');
return content.split('\n').map((line) => ({
@@ -661,6 +661,32 @@ export class BaseCompiler {
return [{text: 'Internal error; unable to open output path'}];
}
+ async generateRustMacroExpansion(inputFilename) {
+ const execOptions = this.getDefaultExecOptions();
+ const macroExpPath = this.getRustMacroExpansionOutputFilename(inputFilename);
+ const rustcOptions = [
+ inputFilename,
+ '-o',
+ macroExpPath,
+ '-Zunpretty=expanded',
+ '--crate-type',
+ 'rlib',
+ ];
+
+ const output = await this.runCompiler(this.compiler.exe, rustcOptions, this.filename(inputFilename),
+ execOptions);
+ if (output.code !== 0) {
+ return [{text: 'Failed to run compiler to get Rust Macro Expansion'}];
+ }
+ if (await fs.exists(macroExpPath)) {
+ const content = await fs.readFile(macroExpPath, 'utf-8');
+ return content.split('\n').map((line) => ({
+ text: line,
+ }));
+ }
+ return [{text: 'Internal error; unable to open output path'}];
+ }
+
getIrOutputFilename(inputFilename) {
return inputFilename.replace(path.extname(inputFilename), '.ll');
}
@@ -669,6 +695,10 @@ export class BaseCompiler {
return inputFilename.replace(path.extname(inputFilename), '.mir');
}
+ getRustMacroExpansionOutputFilename(inputFilename) {
+ return inputFilename.replace(path.extname(inputFilename), '.expanded.rs');
+ }
+
getOutputFilename(dirPath, outputFilebase, key) {
let filename;
if (key && key.backendOptions && key.backendOptions.customOutputFilename) {
@@ -1087,16 +1117,18 @@ export class BaseCompiler {
const makeAst = backendOptions.produceAst && this.compiler.supportsAstView;
const makeIr = backendOptions.produceIr && this.compiler.supportsIrView;
const makeRustMir = backendOptions.produceRustMir && this.compiler.supportsRustMirView;
+ const makeRustMacroExp = backendOptions.produceRustMacroExp && this.compiler.supportsRustMacroExpView;
const makeGccDump = backendOptions.produceGccDump && backendOptions.produceGccDump.opened
&& this.compiler.supportsGccDump;
const downloads = await buildEnvironment;
- const [asmResult, astResult, gccDumpResult, irResult, rustMirResult, toolsResult] = await Promise.all([
+ const [asmResult, astResult, gccDumpResult, irResult, rustMirResult, rustMacroExpResult, toolsResult] = await Promise.all([
this.runCompiler(this.compiler.exe, options, inputFilenameSafe, execOptions),
(makeAst ? this.generateAST(inputFilename, options) : ''),
(makeGccDump ? this.generateGccDump(inputFilename, options, backendOptions.produceGccDump) : ''),
(makeIr ? this.generateIR(inputFilename, options, filters) : ''),
(makeRustMir ? this.generateRustMir(inputFilename, options) : ''),
+ (makeRustMacroExp ? this.generateRustMacroExpansion(inputFilename, options) : ''),
Promise.all(this.runToolsOfType(tools, 'independent', this.getCompilationInfo(key, {
inputFilename,
dirPath,
@@ -1137,6 +1169,10 @@ export class BaseCompiler {
asmResult.hasRustMirOutput = true;
asmResult.rustMirOutput = rustMirResult;
}
+ if (rustMacroExpResult) {
+ asmResult.hasRustMacroExpOutput = true;
+ asmResult.rustMacroExpOutput = rustMacroExpResult;
+ }
return this.checkOutputFileAndDoPostProcess(asmResult, outputFilename, filters);
}
diff --git a/lib/compilers/rust.js b/lib/compilers/rust.js
index d6be3b5c7..3072e8db4 100644
--- a/lib/compilers/rust.js
+++ b/lib/compilers/rust.js
@@ -39,6 +39,8 @@ export class RustCompiler extends BaseCompiler {
this.compiler.supportsIntel = true;
this.compiler.supportsIrView = true;
this.compiler.supportsRustMirView = true;
+ // Macro expansion through -Zunpretty=expanded is only available for Nightly
+ this.compiler.supportsRustMacroExpView = info.name === 'nightly' || info.semver === 'nightly';
this.compiler.irArg = ['--emit', 'llvm-ir'];
this.linker = this.compilerProps('linker');
}
diff --git a/static/components.js b/static/components.js
index 4a23e7264..bc7360485 100644
--- a/static/components.js
+++ b/static/components.js
@@ -310,6 +310,26 @@ module.exports = {
},
};
},
+ getRustMacroExpView: function() {
+ return {
+ type: 'component',
+ componentName: 'rustmacroexp',
+ componentState: {},
+ };
+ },
+ getRustMacroExpViewWith: function (id, source, rustMacroExpOutput, compilerName, editorid) {
+ return {
+ type: 'component',
+ componentName: 'rustmacroexp',
+ componentState: {
+ id: id,
+ source: source,
+ rustMacroExpOutput: rustMacroExpOutput,
+ compilerName: compilerName,
+ editorid: editorid,
+ },
+ };
+ },
getDeviceView: function () {
return {
type: 'component',
diff --git a/static/hub.js b/static/hub.js
index edd3854de..5327450d6 100644
--- a/static/hub.js
+++ b/static/hub.js
@@ -41,6 +41,7 @@ var astView = require('./panes/ast-view');
var irView = require('./panes/ir-view');
var deviceView = require('./panes/device-view');
var rustMirView = require('./panes/rustmir-view');
+var rustMacroExpView = require('./panes/rustmacroexp-view');
var gccDumpView = require('./panes/gccdump-view');
var cfgView = require('./panes/cfg-view');
var conformanceView = require('./panes/conformance-view');
@@ -143,6 +144,10 @@ function Hub(layout, subLangId, defaultLangId) {
function (container, state) {
return self.rustMirViewFactory(container, state);
});
+ layout.registerComponent(Components.getRustMacroExpView().componentName,
+ function (container, state) {
+ return self.rustMacroExpViewFactory(container, state);
+ });
layout.registerComponent(Components.getGccDumpView().componentName,
function (container, state) {
return self.gccDumpViewFactory(container, state);
@@ -317,6 +322,10 @@ Hub.prototype.rustMirViewFactory = function (container, state) {
return new rustMirView.RustMir(this, container, state);
};
+Hub.prototype.rustMacroExpViewFactory = function (container, state) {
+ return new rustMacroExpView.RustMacroExp(this, container, state);
+};
+
Hub.prototype.gccDumpViewFactory = function (container, state) {
return new gccDumpView.GccDump(this, container, state);
};
diff --git a/static/panes/compiler.js b/static/panes/compiler.js
index 87520592c..e0ab3a0f9 100644
--- a/static/panes/compiler.js
+++ b/static/panes/compiler.js
@@ -258,6 +258,11 @@ Compiler.prototype.initPanerButtons = function () {
this.getCompilerName(), this.sourceEditorId);
}, this);
+ var createRustMacroExpView = _.bind(function () {
+ return Components.getRustMacroExpViewWith(this.id, this.source, this.lastResult.rustMacroExpOutput,
+ this.getCompilerName(), this.sourceEditorId);
+ }, this);
+
var createGccDumpView = _.bind(function () {
return Components.getGccDumpViewWith(this.id, this.getCompilerName(), this.sourceEditorId,
this.lastResult.gccDumpOutput);
@@ -362,6 +367,16 @@ Compiler.prototype.initPanerButtons = function () {
}, this));
this.container.layoutManager
+ .createDragSource(this.rustMacroExpButton, createRustMacroExpView)
+ ._dragListener.on('dragStart', togglePannerAdder);
+
+ this.rustMacroExpButton.click(_.bind(function () {
+ var insertPoint = this.hub.findParentRowOrColumn(this.container) ||
+ this.container.layoutManager.root.contentItems[0];
+ insertPoint.addChild(createRustMacroExpView);
+ }, this));
+
+ this.container.layoutManager
.createDragSource(this.gccDumpButton, createGccDumpView)
._dragListener.on('dragStart', togglePannerAdder);
@@ -672,6 +687,7 @@ Compiler.prototype.compile = function (bypassCache, newTools) {
produceIr: this.irViewOpen,
produceDevice: this.deviceViewOpen,
produceRustMir: this.rustMirViewOpen,
+ produceRustMacroExp: this.rustMacroExpViewOpen,
},
filters: this.getEffectiveFilters(),
tools: this.getActiveTools(newTools),
@@ -1242,6 +1258,21 @@ Compiler.prototype.onRustMirViewClosed = function (id) {
}
};
+Compiler.prototype.onRustMacroExpViewOpened = function (id) {
+ if (this.id === id) {
+ this.rustMacroExpButton.prop('disabled', true);
+ this.rustMacroExpViewOpen = true;
+ this.compile();
+ }
+};
+
+Compiler.prototype.onRustMacroExpViewClosed = function (id) {
+ if (this.id === id) {
+ this.rustMacroExpButton.prop('disabled', false);
+ this.rustMacroExpViewOpen = false;
+ }
+};
+
Compiler.prototype.onGccDumpUIInit = function (id) {
if (this.id === id) {
this.compile();
@@ -1379,6 +1410,7 @@ Compiler.prototype.initButtons = function (state) {
this.irButton = this.domRoot.find('.btn.view-ir');
this.deviceButton = this.domRoot.find('.btn.view-device');
this.rustMirButton = this.domRoot.find('.btn.view-rustmir');
+ this.rustMacroExpButton = this.domRoot.find('.btn.view-rustmacroexp');
this.gccDumpButton = this.domRoot.find('.btn.view-gccdump');
this.cfgButton = this.domRoot.find('.btn.view-cfg');
this.executorButton = this.domRoot.find('.create-executor');
@@ -1591,6 +1623,7 @@ Compiler.prototype.updateButtons = function () {
this.irButton.prop('disabled', this.irViewOpen);
this.deviceButton.prop('disabled', this.deviceViewOpen);
this.rustMirButton.prop('disabled', this.rustMirViewOpen);
+ this.rustMacroExpButton.prop('disabled', this.rustMacroExpViewOpen);
this.cfgButton.prop('disabled', this.cfgViewOpen);
this.gccDumpButton.prop('disabled', this.gccDumpViewOpen);
// The executorButton does not need to be changed here, because you can create however
@@ -1601,6 +1634,7 @@ Compiler.prototype.updateButtons = function () {
this.irButton.toggle(!!this.compiler.supportsIrView);
this.deviceButton.toggle(!!this.compiler.supportsDeviceAsmView);
this.rustMirButton.toggle(!!this.compiler.supportsRustMirView);
+ this.rustMacroExpButton.toggle(!!this.compiler.supportsRustMacroExpView);
this.cfgButton.toggle(!!this.compiler.supportsCfg);
this.gccDumpButton.toggle(!!this.compiler.supportsGccDump);
this.executorButton.toggle(!!this.compiler.supportsExecute);
@@ -1688,6 +1722,8 @@ Compiler.prototype.initListeners = function () {
this.eventHub.on('deviceViewClosed', this.onDeviceViewClosed, this);
this.eventHub.on('rustMirViewOpened', this.onRustMirViewOpened, this);
this.eventHub.on('rustMirViewClosed', this.onRustMirViewClosed, this);
+ this.eventHub.on('rustMacroExpViewOpened', this.onRustMacroExpViewOpened, this);
+ this.eventHub.on('rustMacroExpViewClosed', this.onRustMacroExpViewClosed, this);
this.eventHub.on('outputOpened', this.onOutputOpened, this);
this.eventHub.on('outputClosed', this.onOutputClosed, this);
diff --git a/static/panes/rustmacroexp-view.ts b/static/panes/rustmacroexp-view.ts
new file mode 100644
index 000000000..b81099638
--- /dev/null
+++ b/static/panes/rustmacroexp-view.ts
@@ -0,0 +1,122 @@
+// Copyright (c) 2021, 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 'underscore';
+import * as monaco from 'monaco-editor';
+import { Container } from 'golden-layout';
+
+import { Pane } from './pane';
+import { BasePaneState } from './pane.interfaces';
+
+import ga from '../analytics';
+import { extendConfig } from '../monaco-config';
+
+export interface RustMacroExpState extends BasePaneState {
+ rustMacroExpOutput: any;
+}
+
+export class RustMacroExp extends Pane<monaco.editor.IStandaloneCodeEditor> {
+ constructor(hub: any, container: Container, state: RustMacroExpState) {
+ super(hub, container, state);
+ if (state && state.rustMacroExpOutput) {
+ this.showRustMacroExpResults(state.rustMacroExpOutput);
+ }
+ }
+
+ override initializeDOMRoot(): void {
+ this.domRoot.html($('#rustmacroexp').html());
+ }
+
+ override createEditor(editorRoot: HTMLElement): void {
+ this.editor = monaco.editor.create(editorRoot, extendConfig({
+ language: 'rust',
+ readOnly: true,
+ glyphMargin: true,
+ lineNumbersMinChars: 3,
+ }))
+ }
+
+ override registerOpeningAnalyticsEvent(): void {
+ ga.proxy('send', {
+ hitType: 'event',
+ eventCategory: 'OpenViewPane',
+ eventAction: 'RustMacroExp',
+ });
+ }
+
+ override getPaneName(): string {
+ return `Rust Macro Expansion Viewer ${this.compilerInfo.compilerName}` +
+ `(Editor #${this.compilerInfo.editorId}, ` +
+ `Compiler #${this.compilerInfo.compilerId})`;
+ }
+
+ override registerCallbacks(): void {
+ const throttleFunction = _.throttle((event) => this.onDidChangeCursorSelection(event), 500);
+ this.editor.onDidChangeCursorSelection((event) => throttleFunction(event));
+ this.eventHub.emit('rustMacroExpViewOpened', this.compilerInfo.compilerId);
+ this.eventHub.emit('requestSettings');
+ }
+
+ override onCompileResult(id: unknown, compiler: unknown, result: any): void {
+ if (this.compilerInfo.compilerId !== id) return;
+ if (result.hasRustMacroExpOutput) {
+ this.showRustMacroExpResults(result.rustMacroExpOutput);
+ } else {
+ this.showRustMacroExpResults([{text: '<No output>'}]);
+ }
+ }
+
+ override onCompiler(id: number, compiler: any, options: any, editorId: number): void {
+ if (this.compilerInfo.compilerId === id) {
+ this.compilerInfo.compilerName = compiler ? compiler.name : '';
+ this.compilerInfo.editorId = editorId;
+ this.setTitle();
+ if (compiler && !compiler.supportsRustMacroExpView) {
+ this.editor.setValue('<Rust Macro Expansion output is not supported for this compiler>');
+ }
+ }
+ }
+
+ showRustMacroExpResults(result: any[]): void {
+ if (!this.editor) return;
+ this.editor.getModel().setValue(result.length
+ ? _.pluck(result, 'text').join('\n')
+ : '<No Rust Macro Expansion generated>');
+
+ if (!this.isAwaitingInitialResults) {
+ if (this.selection) {
+ this.editor.setSelection(this.selection);
+ this.editor.revealLinesInCenter(this.selection.selectionStartLineNumber,
+ this.selection.endLineNumber);
+ }
+ this.isAwaitingInitialResults = true;
+ }
+ }
+
+ override close(): void {
+ this.eventHub.unsubscribe();
+ this.eventHub.emit('rustMacroExpViewClosed', this.compilerInfo.compilerId);
+ this.editor.dispose();
+ }
+}
diff --git a/static/tsconfig.json b/static/tsconfig.json
index d9d4096bb..238b655c9 100644
--- a/static/tsconfig.json
+++ b/static/tsconfig.json
@@ -19,6 +19,7 @@
"panes/tree.ts",
"panes/pane.ts",
"panes/pane.interfaces.ts",
+ "panes/rustmacroexp-view.ts",
"panes/rustmir-view.ts",
"settings.interfaces.ts",
"sharing.ts",
diff --git a/views/templates.pug b/views/templates.pug
index 0a5496aa8..08cdd32db 100644
--- a/views/templates.pug
+++ b/views/templates.pug
@@ -104,6 +104,9 @@
button.dropdown-item.btn.btn-sm.btn-light.view-rustmir(title="Show Rust Mid-level Intermediate Representation")
span.dropdown-icon.fas.fa-water
| Rust MIR output
+ button.dropdown-item.btn.btn-sm.btn-light.view-rustmacroexp(title="Show Rust Macro Expansion")
+ span.dropdown-icon.fas.fa-arrows-alt
+ | Rust Macro Expansion output
button.dropdown-item.btn.btn-sm.btn-light.view-gccdump(title="Show Tree/RTL dump (GCC only)")
span.dropdown-icon.fas.fa-tree
| GCC Tree/RTL output
@@ -286,7 +289,12 @@
#rustmir
.top-bar.btn-toolbar.bg-light(role="toolbar")
- include font-size.pug
+ include font-size
+ .monaco-placeholder
+
+ #rustmacroexp
+ .top-bar.btn-toolbar.bg-light(role="toolbar")
+ include font-size
.monaco-placeholder
#gccdump