diff options
author | Mats Larsen <me@supergrecko.com> | 2021-09-26 23:33:09 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-27 00:33:09 +0200 |
commit | 278d28b313005effbc39519a833ddb8d92fea66d (patch) | |
tree | 507ff8cd30f331685519130a701742e6a75aeb2b | |
parent | e7b9cbda9174b45121fdf625d806809de45525f4 (diff) | |
download | compiler-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.js | 42 | ||||
-rw-r--r-- | lib/compilers/rust.js | 2 | ||||
-rw-r--r-- | static/components.js | 20 | ||||
-rw-r--r-- | static/hub.js | 9 | ||||
-rw-r--r-- | static/panes/compiler.js | 36 | ||||
-rw-r--r-- | static/panes/rustmacroexp-view.ts | 122 | ||||
-rw-r--r-- | static/tsconfig.json | 1 | ||||
-rw-r--r-- | views/templates.pug | 10 |
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 |