mirror of
https://github.com/compiler-explorer/compiler-explorer.git
synced 2025-12-27 09:23:52 -05:00
Add Resolc 0.4.0 compiler for Solidity and Yul (#8164)
## What
Adds [Revive's Resolc](https://github.com/paritytech/revive) compiler
for compiling Solidity and Yul (Solidity IR) to RISC-V and PolkaVM
assembly.
### Main Additions
- [x] Implement new `ResolcCompiler`
- [x] Implement Yul language definition and config for Monaco
- [x] Add Resolc as a compiler for the Solidity and Yul languages
- The `ResolcCompiler` handles both kinds of language input
- [x] Implement initial `PolkaVMAsmParser` (no source mappings)
- [x] Enable viewing LLVM IR in a supplementary view
- [x] Implement a new LLVM IR backend option for toggling between
optimized and unoptimized ll
- Affects non-resolc files ([see
commit](606bab9a59))
- Disabled by default
- (Enable by setting `this.compiler.supportsIrViewOptToggleOption =
true` in a compiler's constructor)
- The compiler's `getIrOutputFilename()` will receive the LLVM IR
backend options
### CE Infra
Accompanying CE Infra PR:
https://github.com/compiler-explorer/infra/pull/1855
## Overall Usage
### Steps
(See screenshots)
* Choose between two input languages:
* Solidity
* Yul (Solidity IR)
* Choose a Resolc compiler
* View assembly:
* PolkaVM assembly (if enabling "Compile to binary")
* RISC-V (64 bits) assembly
* View intermediate results:
* Optimized LLVM IR (if enabling "Show Optimized" in the LLVM IR view)
* Unoptimized LLVM IR
### Notes
Source mappings currently only exist between:
- Yul and RISC-V
- Yul and LLVM-IR
## Screenshots
<img width="1502" height="903" alt="CE Yul RISC-V LLVM IR"
src="https://github.com/user-attachments/assets/7503b9b5-0f2c-4ddf-9405-669e4bdcd02d"
/>
<img width="1502" height="903" alt="CE Solidity PolkaVM"
src="https://github.com/user-attachments/assets/eeb51c99-3eaa-4dda-b13c-ac7783e66cb8"
/>
---------
Co-authored-by: Matt Godbolt <matt@godbolt.org>
This commit is contained in:
8
.github/labeler.yml
vendored
8
.github/labeler.yml
vendored
@@ -371,6 +371,7 @@
|
||||
- 'lib/compilers/solidity.ts'
|
||||
- 'lib/compilers/solidity-zksync.ts'
|
||||
- 'lib/compilers/solx.ts'
|
||||
- 'lib/compilers/resolc.ts'
|
||||
- 'etc/config/solidity.*.properties'
|
||||
|
||||
'lang-spice':
|
||||
@@ -447,6 +448,13 @@
|
||||
- 'etc/config/wasm.*.properties'
|
||||
- 'static/modes/wat-mode.ts'
|
||||
|
||||
'lang-yul':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'lib/compilers/resolc.ts'
|
||||
- 'etc/config/yul.*.properties'
|
||||
- 'static/modes/yul-mode.ts'
|
||||
|
||||
'lang-zig':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
|
||||
@@ -165,4 +165,5 @@ From oldest to newest contributor, we would like to thank:
|
||||
- [Florian Freitag](https://github.com/flofriday)
|
||||
- [Trevor Gross](https://github.com/tgross35)
|
||||
- [Alex Trotta](https://github.com/Ahajha)
|
||||
- [natinusala](https://github.com/natinusala)
|
||||
- [natinusala](https://github.com/natinusala)
|
||||
- [LJ](https://github.com/elle-j)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
compilers=&solc:&zksolc:&solx
|
||||
compilers=&solc:&zksolc:&solx:&resolc
|
||||
defaultCompiler=solc0830
|
||||
supportsLibraryCodeFilter=true
|
||||
|
||||
group.solc.compilers=solc0426:solc0517:solc0612:solc076:solc0821:solc0829:solc0830
|
||||
group.solc.compilerType=solidity
|
||||
@@ -58,6 +59,24 @@ compiler.solx012.exe=/opt/compiler-explorer/solx-0.1.2/solx
|
||||
compiler.solx012.semver=0.1.2
|
||||
compiler.solx012.name=solx 0.1.2
|
||||
|
||||
# The Resolc compiler supports compiling both Solidity and Yul (Solidity IR), thus
|
||||
# the same compiler is used for both languages. The compiler config IDs will clash
|
||||
# if they are not unique, therefore the IDs contain either 'yul' or 'sol'.
|
||||
group.resolc.compilers=resolc040_sol
|
||||
group.resolc.compilerType=resolc
|
||||
group.resolc.objdumper=/opt/compiler-explorer/clang-21.1.0/bin/llvm-objdump
|
||||
group.resolc.objdumperType=llvm
|
||||
group.resolc.isSemver=true
|
||||
group.resolc.versionFlag=--version
|
||||
group.resolc.intelAsm=false
|
||||
group.resolc.supportsBinaryObject=true
|
||||
group.resolc.supportsBinary=false
|
||||
group.resolc.supportsExecute=false
|
||||
compiler.resolc040_sol.exe=/opt/compiler-explorer/resolc-0.4.0/resolc
|
||||
compiler.resolc040_sol.semver=0.4.0
|
||||
compiler.resolc040_sol.name=resolc 0.4.0
|
||||
compiler.resolc040_sol.instructionSet=riscv64
|
||||
|
||||
#################################
|
||||
#################################
|
||||
# Installed libs (See c++.amazon.properties for a scheme of libs group)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
compilers=solc:zksolc:solx
|
||||
compilers=solc:zksolc:solx:&resolc
|
||||
compilerType=solidity
|
||||
defaultCompiler=solc
|
||||
supportsLibraryCodeFilter=true
|
||||
|
||||
compiler.solc.exe=/usr/bin/solc
|
||||
compiler.solc.semver=0.8.21
|
||||
@@ -23,6 +24,21 @@ compiler.solx.compilerType=solx
|
||||
compiler.solx.instructionSet=evm
|
||||
compiler.solx.isSemVer=true
|
||||
|
||||
group.resolc.compilers=resolc040_sol
|
||||
group.resolc.compilerType=resolc
|
||||
group.resolc.objdumper=/usr/local/bin/llvm-objdump
|
||||
group.resolc.objdumperType=llvm
|
||||
group.resolc.isSemver=true
|
||||
group.resolc.versionFlag=--version
|
||||
group.resolc.intelAsm=false
|
||||
group.resolc.supportsBinaryObject=true
|
||||
group.resolc.supportsBinary=false
|
||||
group.resolc.supportsExecute=false
|
||||
compiler.resolc040_sol.exe=/opt/compiler-explorer/resolc-0.4.0/resolc
|
||||
compiler.resolc040_sol.semver=0.4.0
|
||||
compiler.resolc040_sol.name=resolc 0.4.0
|
||||
compiler.resolc040_sol.instructionSet=riscv64
|
||||
|
||||
#################################
|
||||
#################################
|
||||
# Installed libs (See c++.amazon.properties for a scheme of libs group)
|
||||
|
||||
32
etc/config/yul.amazon.properties
Normal file
32
etc/config/yul.amazon.properties
Normal file
@@ -0,0 +1,32 @@
|
||||
compilers=&resolc
|
||||
defaultCompiler=resolc040_yul
|
||||
supportsLibraryCodeFilter=true
|
||||
|
||||
# The Resolc compiler supports compiling both Solidity and Yul (Solidity IR), thus
|
||||
# the same compiler is used for both languages. The compiler config IDs will clash
|
||||
# if they are not unique, therefore the IDs contain either 'yul' or 'sol'.
|
||||
group.resolc.compilers=resolc040_yul
|
||||
group.resolc.compilerType=resolc
|
||||
group.resolc.objdumper=/opt/compiler-explorer/clang-21.1.0/bin/llvm-objdump
|
||||
group.resolc.objdumperType=llvm
|
||||
group.resolc.isSemver=true
|
||||
group.resolc.versionFlag=--version
|
||||
group.resolc.intelAsm=false
|
||||
group.resolc.supportsBinaryObject=true
|
||||
group.resolc.supportsBinary=false
|
||||
group.resolc.supportsExecute=false
|
||||
compiler.resolc040_yul.exe=/opt/compiler-explorer/resolc-0.4.0/resolc
|
||||
compiler.resolc040_yul.semver=0.4.0
|
||||
compiler.resolc040_yul.name=resolc 0.4.0
|
||||
compiler.resolc040_yul.instructionSet=riscv64
|
||||
|
||||
#################################
|
||||
#################################
|
||||
# Installed libs (See c++.amazon.properties for a scheme of libs group)
|
||||
libs=
|
||||
|
||||
#################################
|
||||
#################################
|
||||
# Installed tools
|
||||
|
||||
tools=
|
||||
24
etc/config/yul.defaults.properties
Normal file
24
etc/config/yul.defaults.properties
Normal file
@@ -0,0 +1,24 @@
|
||||
compilers=&resolc
|
||||
compilerType=resolc
|
||||
defaultCompiler=resolc040_yul
|
||||
supportsLibraryCodeFilter=true
|
||||
|
||||
group.resolc.compilers=resolc040_yul
|
||||
group.resolc.compilerType=resolc
|
||||
group.resolc.objdumper=/usr/local/bin/llvm-objdump
|
||||
group.resolc.objdumperType=llvm
|
||||
group.resolc.isSemver=true
|
||||
group.resolc.versionFlag=--version
|
||||
group.resolc.intelAsm=false
|
||||
group.resolc.supportsBinaryObject=true
|
||||
group.resolc.supportsBinary=false
|
||||
group.resolc.supportsExecute=false
|
||||
compiler.resolc040_yul.exe=/opt/compiler-explorer/resolc-0.4.0/resolc
|
||||
compiler.resolc040_yul.semver=0.4.0
|
||||
compiler.resolc040_yul.name=resolc 0.4.0
|
||||
compiler.resolc040_yul.instructionSet=riscv64
|
||||
|
||||
#################################
|
||||
#################################
|
||||
# Installed libs (See c++.amazon.properties for a scheme of libs group)
|
||||
libs=
|
||||
43
examples/yul/default.yul
Normal file
43
examples/yul/default.yul
Normal file
@@ -0,0 +1,43 @@
|
||||
object "Square" {
|
||||
code {
|
||||
{
|
||||
let _1 := memoryguard(0x80)
|
||||
mstore(64, _1)
|
||||
if callvalue() { revert(0, 0) }
|
||||
let _2 := datasize("Square_deployed")
|
||||
codecopy(_1, dataoffset("Square_deployed"), _2)
|
||||
return(_1, _2)
|
||||
}
|
||||
}
|
||||
object "Square_deployed" {
|
||||
code {
|
||||
{
|
||||
let _1 := memoryguard(0x80)
|
||||
mstore(64, _1)
|
||||
if iszero(lt(calldatasize(), 4))
|
||||
{
|
||||
if eq(0xd27b3841, shr(224, calldataload(0)))
|
||||
{
|
||||
if callvalue() { revert(0, 0) }
|
||||
if slt(add(calldatasize(), not(3)), 32) { revert(0, 0) }
|
||||
let value := calldataload(4)
|
||||
let _2 := and(value, 0xffffffff)
|
||||
if iszero(eq(value, _2)) { revert(0, 0) }
|
||||
let product_raw := mul(_2, _2)
|
||||
let product := and(product_raw, 0xffffffff)
|
||||
if iszero(eq(product, product_raw))
|
||||
{
|
||||
mstore(0, shl(224, 0x4e487b71))
|
||||
mstore(4, 0x11)
|
||||
revert(0, 0x24)
|
||||
}
|
||||
mstore(_1, product)
|
||||
return(_1, 32)
|
||||
}
|
||||
}
|
||||
revert(0, 0)
|
||||
}
|
||||
}
|
||||
data ".metadata" hex"a26469706673582212209b2b1b86ce0e1a75faa800884ba155bd6bc6a6bc71f210370f818535dcfc5ee364736f6c634300081e0033"
|
||||
}
|
||||
}
|
||||
@@ -1455,7 +1455,7 @@ export class BaseCompiler {
|
||||
asm: ParsedAsmResultLine[];
|
||||
languageId: string;
|
||||
}> {
|
||||
const irPath = this.getIrOutputFilename(output.inputFilename!, filters);
|
||||
const irPath = this.getIrOutputFilename(output.inputFilename!, filters, irOptions);
|
||||
if (await utils.fileExists(irPath)) {
|
||||
const output = await fs.readFile(irPath, 'utf8');
|
||||
return await this.llvmIr.process(output, irOptions);
|
||||
@@ -1701,8 +1701,18 @@ export class BaseCompiler {
|
||||
return [{text: 'Internal error; unable to open output path'}];
|
||||
}
|
||||
|
||||
getIrOutputFilename(inputFilename: string, filters?: ParseFiltersAndOutputOptions): string {
|
||||
// filters are passed because rust needs to know whether a binary is being produced or not
|
||||
/**
|
||||
* Get the LLVM IR output filename.
|
||||
*
|
||||
* @param inputFilename Input filename.
|
||||
* @param filters Can be used if this base method is overridden. E.g. in order to know whether a binary is being produced (used by Rust).
|
||||
* @param irOptions Can be used if this base method is overridden. E.g. in order return an output file based on `irOptions` (used by Resolc).
|
||||
*/
|
||||
getIrOutputFilename(
|
||||
inputFilename: string,
|
||||
filters?: ParseFiltersAndOutputOptions,
|
||||
irOptions?: LLVMIrBackendOptions,
|
||||
): string {
|
||||
return utils.changeExtension(inputFilename, '.ll');
|
||||
}
|
||||
|
||||
|
||||
@@ -133,6 +133,7 @@ export {QNXCompiler} from './qnx.js';
|
||||
export {R8Compiler} from './r8.js';
|
||||
export {RacketCompiler} from './racket.js';
|
||||
export {RakuCompiler} from './raku.js';
|
||||
export {ResolcCompiler} from './resolc.js';
|
||||
export {RGACompiler} from './rga.js';
|
||||
export {RubyCompiler} from './ruby.js';
|
||||
export {RustCompiler} from './rust.js';
|
||||
|
||||
@@ -942,6 +942,13 @@ export class SolxParser extends RustParser {
|
||||
}
|
||||
}
|
||||
|
||||
export class ResolcParser extends BaseParser {
|
||||
override async parse() {
|
||||
await this.getOptions('--help');
|
||||
return this.compiler;
|
||||
}
|
||||
}
|
||||
|
||||
export class MrustcParser extends BaseParser {
|
||||
override async parse() {
|
||||
await this.getOptions('--help');
|
||||
|
||||
318
lib/compilers/resolc.ts
Normal file
318
lib/compilers/resolc.ts
Normal file
@@ -0,0 +1,318 @@
|
||||
// Copyright (c) 2025, 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 fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import type {ParsedAsmResult, ParsedAsmResultLine} from '../../types/asmresult/asmresult.interfaces.js';
|
||||
import type {CompilationResult} from '../../types/compilation/compilation.interfaces.js';
|
||||
import type {LLVMIrBackendOptions} from '../../types/compilation/ir.interfaces.js';
|
||||
import type {ParseFiltersAndOutputOptions} from '../../types/features/filters.interfaces.js';
|
||||
import type {Language} from '../../types/languages.interfaces.js';
|
||||
import {assert} from '../assert.js';
|
||||
import {BaseCompiler} from '../base-compiler.js';
|
||||
import {PolkaVMAsmParser} from '../parsers/asm-parser-polkavm.js';
|
||||
import {ResolcRiscVAsmParser} from '../parsers/asm-parser-resolc-riscv.js';
|
||||
import {changeExtension} from '../utils.js';
|
||||
import {type BaseParser, ResolcParser} from './argument-parsers.js';
|
||||
|
||||
/**
|
||||
* The kind of input provided by the user.
|
||||
* This is determined by the language chosen as Resolc
|
||||
* supports both Solidity and Yul (Solidity IR).
|
||||
*
|
||||
* @note
|
||||
* The enum value must exactly match the {@link Language.id}.
|
||||
*/
|
||||
enum InputKind {
|
||||
Solidity = 'solidity',
|
||||
Yul = 'yul',
|
||||
}
|
||||
|
||||
/**
|
||||
* The kind of output requested by the user.
|
||||
*/
|
||||
enum OutputKind {
|
||||
PolkaVM = 'pvm',
|
||||
RiscV = 'risc-v',
|
||||
}
|
||||
|
||||
export class ResolcCompiler extends BaseCompiler {
|
||||
static get key() {
|
||||
return 'resolc';
|
||||
}
|
||||
|
||||
private readonly pvmAsmParser: PolkaVMAsmParser;
|
||||
|
||||
/**
|
||||
* @note
|
||||
* Needs to coincide with the [infrastructure configs](https://github.com/compiler-explorer/infra/blob/main/bin/yaml/solidity.yaml).
|
||||
*/
|
||||
static get solcExe() {
|
||||
return '/opt/compiler-explorer/solc-0.8.30/solc';
|
||||
}
|
||||
|
||||
constructor(...args: ConstructorParameters<typeof BaseCompiler>) {
|
||||
super(...args);
|
||||
|
||||
this.asm = new ResolcRiscVAsmParser(this.compilerProps);
|
||||
this.pvmAsmParser = new PolkaVMAsmParser();
|
||||
|
||||
// The arg producing LLVM IR (among other output) is already
|
||||
// included in optionsForFilter(), but irArg needs to be set.
|
||||
this.compiler.irArg = [];
|
||||
this.compiler.supportsIrView = true;
|
||||
this.compiler.supportsIrViewOptToggleOption = true;
|
||||
}
|
||||
|
||||
override getSharedLibraryPathsAsArguments(): string[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
override getArgumentParserClass(): typeof BaseParser {
|
||||
return ResolcParser;
|
||||
}
|
||||
|
||||
override optionsForFilter(filters: ParseFiltersAndOutputOptions): string[] {
|
||||
filters.binaryObject = this.reinterpretBinaryObjectFilter(filters.binaryObject);
|
||||
filters.intel = false;
|
||||
|
||||
const options = ['-g', '--solc', ResolcCompiler.solcExe, '--overwrite', '--debug-output-dir', 'artifacts'];
|
||||
if (this.inputIs(InputKind.Yul)) {
|
||||
options.push('--yul');
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
override isCfgCompiler(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
override getOutputFilename(dirPath: string): string {
|
||||
return this.getOutputFilenameWithExtension(dirPath, '.pvmasm');
|
||||
}
|
||||
|
||||
override getIrOutputFilename(
|
||||
inputFilename: string,
|
||||
_filters?: ParseFiltersAndOutputOptions,
|
||||
irOptions?: LLVMIrBackendOptions,
|
||||
): string {
|
||||
const extension =
|
||||
irOptions?.showOptimized && this.compiler.supportsIrViewOptToggleOption
|
||||
? '.optimized.ll'
|
||||
: '.unoptimized.ll';
|
||||
|
||||
return this.getOutputFilenameWithExtension(path.dirname(inputFilename), extension);
|
||||
}
|
||||
|
||||
override getObjdumpOutputFilename(defaultOutputFilename: string): string {
|
||||
return changeExtension(defaultOutputFilename, '.o');
|
||||
}
|
||||
|
||||
private getOutputFilenameWithExtension(dirPath: string, extension: string): string {
|
||||
const basenamePrefix = dirPath.split(path.sep).join('_');
|
||||
const contractName = this.inputIs(InputKind.Solidity)
|
||||
? this.getSolidityContractName(dirPath)
|
||||
: this.getYulContractName(dirPath);
|
||||
const basename = `${basenamePrefix}_${this.compileFilename}.${contractName}${extension}`;
|
||||
|
||||
return path.join(dirPath, 'artifacts', basename);
|
||||
}
|
||||
|
||||
override async processAsm(
|
||||
result: CompilationResult,
|
||||
filters: ParseFiltersAndOutputOptions,
|
||||
): Promise<ParsedAsmResult> {
|
||||
return this.outputIs(OutputKind.PolkaVM, filters)
|
||||
? this.pvmAsmParser.process(result.asm as string, filters)
|
||||
: this.asm.process(result.asm as string, filters);
|
||||
}
|
||||
|
||||
override async postProcessAsm(
|
||||
result: ParsedAsmResult,
|
||||
filters?: ParseFiltersAndOutputOptions,
|
||||
): Promise<ParsedAsmResult> {
|
||||
result = await super.postProcessAsm(result, filters);
|
||||
result = this.removeOrphanedLabels(result, filters);
|
||||
this.maybeRemoveSourceMappings(result, filters);
|
||||
this.addOutputHeader(result, filters);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove orphaned labels.
|
||||
*
|
||||
* @example
|
||||
* Before:
|
||||
* ```
|
||||
* memset:
|
||||
* .LBB35_2:
|
||||
* .LBB35_3:
|
||||
* __entry:
|
||||
* addi sp, sp, -0x10
|
||||
* ```
|
||||
*
|
||||
* After:
|
||||
* ```
|
||||
* __entry:
|
||||
* addi sp, sp, -0x10
|
||||
* ```
|
||||
*/
|
||||
private removeOrphanedLabels(result: ParsedAsmResult, filters?: ParseFiltersAndOutputOptions): ParsedAsmResult {
|
||||
// Orphaned RISC-V labels may be produced by the AsmParser when library code is skipped.
|
||||
if (!this.outputIs(OutputKind.RiscV, filters) || !filters?.libraryCode || !result.labelDefinitions) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const {asm, labelDefinitions} = result;
|
||||
result.asm = asm.filter((currentLine, index) => {
|
||||
const nextLine = asm[index + 1];
|
||||
const currentIsLabel = this.isLabel(currentLine, labelDefinitions);
|
||||
const nextIsLabel = nextLine && this.isLabel(nextLine, labelDefinitions);
|
||||
const isOrphaned = currentIsLabel && (nextIsLabel || !nextLine);
|
||||
return !isOrphaned;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current source mappings from RISC-V only map to the Yul line numbers. When
|
||||
* a Solidity source file is used, the mappings shown in CE are thus misleading.
|
||||
*/
|
||||
private maybeRemoveSourceMappings(result: ParsedAsmResult, filters?: ParseFiltersAndOutputOptions): void {
|
||||
const inputIsSolidity = this.inputIs(InputKind.Solidity);
|
||||
const {asm, labelDefinitions} = result;
|
||||
|
||||
if (this.outputIs(OutputKind.RiscV, filters)) {
|
||||
for (const line of asm) {
|
||||
if (inputIsSolidity) {
|
||||
line.source = null;
|
||||
}
|
||||
if (!this.isLabel(line, labelDefinitions)) {
|
||||
line.text = ' ' + line.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the parsed asm result line represents a label.
|
||||
*/
|
||||
private isLabel(line: ParsedAsmResultLine, labelDefinitions: ParsedAsmResult['labelDefinitions']): boolean {
|
||||
return line.text.endsWith(':') && !!labelDefinitions && line.text.slice(0, -1) in labelDefinitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the provided input kind matches the language used.
|
||||
*/
|
||||
private inputIs(kind: InputKind): boolean {
|
||||
return this.lang.id === kind.valueOf();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the provided output kind matches the output requested.
|
||||
*/
|
||||
private outputIs(kind: OutputKind, filters?: ParseFiltersAndOutputOptions): boolean {
|
||||
switch (kind) {
|
||||
case OutputKind.PolkaVM:
|
||||
return !filters?.binaryObject;
|
||||
case OutputKind.RiscV:
|
||||
return !!filters?.binaryObject;
|
||||
default:
|
||||
throw new Error('Unexpected output kind.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reinterpret the user-provided binary object filter to show the PolkaVM
|
||||
* assembly if selected, otherwise the RISC-V assembly.
|
||||
*
|
||||
* Users who select "Compile to binary object" should see the disassembled
|
||||
* PVM plob and not RISC-V. However, to see the RISC-V output, the binary
|
||||
* object filter needs to be reset to `true` in order to pass the binary
|
||||
* object (which will already exist after the first compilation) to the
|
||||
* objdumper during post-processing of the compilation result.
|
||||
*/
|
||||
private reinterpretBinaryObjectFilter(binaryObjectFilter?: boolean): boolean {
|
||||
return !binaryObjectFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Solidity contract name used in the compile file.
|
||||
*
|
||||
* @example
|
||||
* ```solidity
|
||||
* contract Square { ... } // Name = Square
|
||||
* ```
|
||||
*/
|
||||
private getSolidityContractName(dirPath: string): string {
|
||||
const nameRe = /contract[\s\n]+(?<name>[\w$]+)[\s\n]*{/;
|
||||
|
||||
return this.getContractName(dirPath, nameRe);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Yul contract name used in the compile file.
|
||||
*
|
||||
* @example
|
||||
* ```
|
||||
* object "Square" { ... } // Name = Square
|
||||
* ```
|
||||
*/
|
||||
private getYulContractName(dirPath: string): string {
|
||||
const nameRe = /object[\s\n]+"(?<name>[\w$.]+)"[\s\n]*{/;
|
||||
|
||||
return this.getContractName(dirPath, nameRe);
|
||||
}
|
||||
|
||||
private getContractName(dirPath: string, nameRe: RegExp): string {
|
||||
const source = fs.readFileSync(path.join(dirPath, this.compileFilename), {encoding: 'utf8'});
|
||||
const match = source.match(nameRe);
|
||||
assert(match?.groups?.name, 'Expected to find a contract name in the source file.');
|
||||
|
||||
return match.groups.name;
|
||||
}
|
||||
|
||||
private addOutputHeader(result: ParsedAsmResult, filters?: ParseFiltersAndOutputOptions): void {
|
||||
const pvmHeader =
|
||||
'// PolkaVM Assembly:\n' +
|
||||
'// --------------------------\n' +
|
||||
'// To see the RISC-V assembly instead,\n' +
|
||||
'// disable "Compile to binary object".\n' +
|
||||
'// --------------------------';
|
||||
|
||||
const riscvHeader =
|
||||
'; RISC-V (64 bits) Assembly:\n' +
|
||||
'; --------------------------\n' +
|
||||
'; To see the PolkaVM assembly instead,\n' +
|
||||
'; enable "Compile to binary object".\n' +
|
||||
'; --------------------------';
|
||||
|
||||
const header = this.outputIs(OutputKind.PolkaVM, filters) ? pvmHeader : riscvHeader;
|
||||
result.asm.unshift(...header.split('\n').map(line => ({text: line})));
|
||||
}
|
||||
}
|
||||
@@ -972,6 +972,17 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
|
||||
previewFilter: null,
|
||||
monacoDisassembly: null,
|
||||
},
|
||||
yul: {
|
||||
name: 'Yul (Solidity IR)',
|
||||
monaco: 'yul',
|
||||
extensions: ['.yul'],
|
||||
alias: [],
|
||||
logoFilename: 'solidity.svg',
|
||||
logoFilenameDark: null,
|
||||
formatter: null,
|
||||
previewFilter: null,
|
||||
monacoDisassembly: null,
|
||||
},
|
||||
zig: {
|
||||
name: 'Zig',
|
||||
monaco: 'zig',
|
||||
|
||||
177
lib/parsers/asm-parser-polkavm.ts
Normal file
177
lib/parsers/asm-parser-polkavm.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
// Copyright (c) 2025, 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 {ParsedAsmResult, ParsedAsmResultLine} from '../../types/asmresult/asmresult.interfaces.js';
|
||||
import {ParseFiltersAndOutputOptions} from '../../types/features/filters.interfaces.js';
|
||||
import {assert} from '../assert.js';
|
||||
import {deltaTimeNanoToMili, splitLines} from '../utils.js';
|
||||
import {IAsmParser} from './asm-parser.interfaces.js';
|
||||
import {AsmRegex} from './asmregex.js';
|
||||
|
||||
/**
|
||||
* The parser for PolkaVM assembly.
|
||||
*
|
||||
* @note
|
||||
* There are currently no source mappings from PolkaVM.
|
||||
*/
|
||||
export class PolkaVMAsmParser implements IAsmParser {
|
||||
/**
|
||||
* @example ` 64: ra = 6, jump @42`
|
||||
*/
|
||||
protected instructionRe = /^\s+(?<address>\d+):\s+(?<disasm>.*)$/;
|
||||
/**
|
||||
* @example `<deploy>:`
|
||||
*/
|
||||
protected labelRe = /^<(?<label>[^\s\n]+)>:$/;
|
||||
/**
|
||||
* @example `// Stack size = 32768 bytes`
|
||||
*/
|
||||
protected headerCommentRe = /^\/\//;
|
||||
/**
|
||||
* @example ` // This is a comment`
|
||||
*/
|
||||
protected commentOnlyRe = /^\s*\/\//;
|
||||
/**
|
||||
* @example ` : @16 (gas: 5)`
|
||||
*/
|
||||
protected jumpTargetRe = /^\s*: (?<targetLine>@\d+.*)$/;
|
||||
protected maxAsmLines = 5000;
|
||||
protected indentation = ' ';
|
||||
|
||||
process(asmResult: string, filters: ParseFiltersAndOutputOptions): ParsedAsmResult {
|
||||
const startTime = process.hrtime.bigint();
|
||||
|
||||
let asmLines = splitLines(asmResult);
|
||||
const originalLineCount = asmLines.length;
|
||||
|
||||
if (filters.preProcessLines) {
|
||||
asmLines = filters.preProcessLines(asmLines);
|
||||
}
|
||||
|
||||
const {asm, labelDefinitions} = this.processLines(asmLines, filters);
|
||||
|
||||
const endTime = process.hrtime.bigint();
|
||||
|
||||
return {
|
||||
asm,
|
||||
labelDefinitions,
|
||||
parsingTime: deltaTimeNanoToMili(startTime, endTime),
|
||||
filteredCount: originalLineCount - asm.length,
|
||||
};
|
||||
}
|
||||
|
||||
processLines(
|
||||
asmLines: string[],
|
||||
filters: ParseFiltersAndOutputOptions,
|
||||
): {
|
||||
asm: ParsedAsmResultLine[];
|
||||
labelDefinitions: Record<string, number>;
|
||||
} {
|
||||
const parsedAsm: ParsedAsmResultLine[] = [];
|
||||
const labelDefinitions: Record<string, number> = {};
|
||||
|
||||
for (const line of asmLines) {
|
||||
if (parsedAsm.length >= this.maxAsmLines) {
|
||||
parsedAsm.push({
|
||||
text: '[truncated; too many lines]',
|
||||
source: null,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
let match: RegExpMatchArray | null = null;
|
||||
|
||||
if ((match = line.match(this.instructionRe))) {
|
||||
this.parseInstruction(parsedAsm, match, filters);
|
||||
} else if ((match = line.match(this.jumpTargetRe))) {
|
||||
this.parseJumpTarget(parsedAsm, match, labelDefinitions);
|
||||
} else if ((match = line.match(this.labelRe))) {
|
||||
this.parseLabel(parsedAsm, match, labelDefinitions);
|
||||
} else if ((match = line.match(this.commentOnlyRe))) {
|
||||
this.parseComment(parsedAsm, match, line, filters);
|
||||
}
|
||||
}
|
||||
|
||||
return {asm: parsedAsm, labelDefinitions};
|
||||
}
|
||||
|
||||
private parseInstruction(
|
||||
parsedAsm: ParsedAsmResultLine[],
|
||||
match: RegExpMatchArray,
|
||||
filters: ParseFiltersAndOutputOptions,
|
||||
): void {
|
||||
assert(match.groups);
|
||||
const address = parseInt(match.groups.address, 10);
|
||||
const disassembly = this.indentation + AsmRegex.filterAsmLine(match.groups.disasm, filters);
|
||||
parsedAsm.push({
|
||||
address,
|
||||
text: disassembly,
|
||||
source: null,
|
||||
});
|
||||
}
|
||||
|
||||
private parseJumpTarget(
|
||||
parsedAsm: ParsedAsmResultLine[],
|
||||
match: RegExpMatchArray,
|
||||
filters: ParseFiltersAndOutputOptions,
|
||||
): void {
|
||||
assert(match.groups);
|
||||
const targetLine = this.indentation + AsmRegex.filterAsmLine(match.groups.targetLine, filters);
|
||||
parsedAsm.push({
|
||||
text: targetLine,
|
||||
source: null,
|
||||
});
|
||||
}
|
||||
|
||||
private parseLabel(
|
||||
parsedAsm: ParsedAsmResultLine[],
|
||||
match: RegExpMatchArray,
|
||||
labelDefinitions: Record<string, number>,
|
||||
): void {
|
||||
assert(match.groups);
|
||||
const label = match.groups.label;
|
||||
parsedAsm.push({
|
||||
text: label + ':',
|
||||
source: null,
|
||||
});
|
||||
labelDefinitions[label] = parsedAsm.length;
|
||||
}
|
||||
|
||||
private parseComment(
|
||||
parsedAsm: ParsedAsmResultLine[],
|
||||
match: RegExpMatchArray,
|
||||
line: string,
|
||||
filters: ParseFiltersAndOutputOptions,
|
||||
): void {
|
||||
if (!filters.commentOnly) {
|
||||
const comment = line.match(this.headerCommentRe)
|
||||
? match.input?.trim()!
|
||||
: this.indentation + match.input?.trim()!;
|
||||
parsedAsm.push({
|
||||
text: comment,
|
||||
source: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
35
lib/parsers/asm-parser-resolc-riscv.ts
Normal file
35
lib/parsers/asm-parser-resolc-riscv.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) 2025, 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 {PropertyGetter} from '../properties.interfaces.js';
|
||||
import {AsmParser} from './asm-parser.js';
|
||||
|
||||
export class ResolcRiscVAsmParser extends AsmParser {
|
||||
constructor(compilerProps?: PropertyGetter) {
|
||||
super(compilerProps);
|
||||
|
||||
// Example: "; artifacts/_var_folders_fj_1p_1d_T_compiler-explorer-compilerJzYPPi_example.yul.Square.yul:1"
|
||||
this.lineRe = /^;\s+(?<file>\S+):(?<line>\d+)$/;
|
||||
}
|
||||
}
|
||||
@@ -72,4 +72,5 @@ import './tablegen-mode';
|
||||
import './v-mode';
|
||||
import './vala-mode';
|
||||
import './wat-mode';
|
||||
import './yul-mode';
|
||||
import './zig-mode';
|
||||
|
||||
161
static/modes/yul-mode.ts
Normal file
161
static/modes/yul-mode.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
// Copyright (c) 2025, 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 * as monaco from 'monaco-editor';
|
||||
|
||||
export function definition(): monaco.languages.IMonarchLanguage {
|
||||
return {
|
||||
// Set defaultToken to 'invalid' to see what is not yet tokenized.
|
||||
defaultToken: 'invalid',
|
||||
|
||||
keywords: [
|
||||
'break',
|
||||
'case',
|
||||
'code',
|
||||
'continue',
|
||||
'data',
|
||||
'default',
|
||||
'false',
|
||||
'for',
|
||||
'function',
|
||||
'hex',
|
||||
'if',
|
||||
'leave',
|
||||
'let',
|
||||
'object',
|
||||
'switch',
|
||||
'true',
|
||||
],
|
||||
|
||||
operators: [':='],
|
||||
|
||||
brackets: [
|
||||
{open: '{', close: '}', token: 'delimiter.curly'},
|
||||
{open: '(', close: ')', token: 'delimiter.parenthesis'},
|
||||
{open: '[', close: ']', token: 'delimiter.square'},
|
||||
],
|
||||
|
||||
symbols: /[:=]+/,
|
||||
|
||||
escapes: /\\(?:['"\\nrt\n\r]|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4})/,
|
||||
|
||||
tokenizer: {
|
||||
root: [
|
||||
// Identifiers and keywords
|
||||
[
|
||||
/[a-z][a-zA-Z0-9_]*/,
|
||||
{
|
||||
cases: {
|
||||
'@keywords': 'keyword',
|
||||
'@default': 'identifier',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
[/[a-zA-Z_$][a-zA-Z_$0-9.]*/, 'type.identifier'],
|
||||
|
||||
// Whitespace
|
||||
{include: '@whitespace'},
|
||||
|
||||
// Delimiters and operators
|
||||
[/[()[\]{}]/, '@brackets'],
|
||||
[/[.,:]/, 'delimiter'],
|
||||
|
||||
[
|
||||
/@symbols/,
|
||||
{
|
||||
cases: {
|
||||
'@operators': 'operator',
|
||||
'@default': '',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
// Numbers
|
||||
[/0x[0-9a-fA-F]+/, 'number.hex'],
|
||||
[/\d+/, 'number'],
|
||||
|
||||
// Strings
|
||||
[/"([^"\\]|\\.)*$/, 'string.invalid'], // non-terminated string
|
||||
[/"/, 'string', '@string'],
|
||||
],
|
||||
|
||||
// Whitespace and comments
|
||||
whitespace: [
|
||||
[/[ \t\r\n]+/, 'white'],
|
||||
[/\/\*/, 'comment', '@comment'],
|
||||
[/\/\/.*$/, 'comment'],
|
||||
],
|
||||
|
||||
comment: [
|
||||
[/[^/*]+/, 'comment'],
|
||||
[/\/\*/, 'comment', '@push'], // Nested comment
|
||||
['\\*/', 'comment', '@pop'],
|
||||
[/[/*]/, 'comment'],
|
||||
],
|
||||
|
||||
// Strings
|
||||
string: [
|
||||
[/[^\\"]+/, 'string'],
|
||||
[/@escapes/, 'string.escape'],
|
||||
[/\\./, 'string.escape.invalid'],
|
||||
[/"/, 'string', '@pop'],
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function configuration(): monaco.languages.LanguageConfiguration {
|
||||
return {
|
||||
comments: {
|
||||
lineComment: '//',
|
||||
blockComment: ['/*', '*/'],
|
||||
},
|
||||
|
||||
brackets: [
|
||||
['{', '}'],
|
||||
['[', ']'],
|
||||
['(', ')'],
|
||||
],
|
||||
|
||||
autoClosingPairs: [
|
||||
{open: '{', close: '}'},
|
||||
{open: '[', close: ']'},
|
||||
{open: '(', close: ')'},
|
||||
{open: '"', close: '"', notIn: ['string']},
|
||||
{open: '/*', close: ' */', notIn: ['string']},
|
||||
],
|
||||
|
||||
surroundingPairs: [
|
||||
{open: '{', close: '}'},
|
||||
{open: '[', close: ']'},
|
||||
{open: '(', close: ')'},
|
||||
{open: '"', close: '"'},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
monaco.languages.register({id: 'yul'});
|
||||
monaco.languages.setMonarchTokensProvider('yul', definition());
|
||||
monaco.languages.setLanguageConfiguration('yul', configuration());
|
||||
@@ -33,4 +33,5 @@ export interface IrState {
|
||||
'filter-instruction-metadata'?: boolean;
|
||||
'filter-attributes'?: boolean;
|
||||
'filter-comments'?: boolean;
|
||||
'show-optimized'?: boolean;
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ export class Ir extends MonacoPane<monaco.editor.IStandaloneCodeEditor, IrState>
|
||||
filterComments: true,
|
||||
noDiscardValueNames: true,
|
||||
demangle: true,
|
||||
showOptimized: true,
|
||||
};
|
||||
private cfgButton: JQuery;
|
||||
private wrapButton: JQuery<HTMLElement>;
|
||||
@@ -307,6 +308,7 @@ export class Ir extends MonacoPane<monaco.editor.IStandaloneCodeEditor, IrState>
|
||||
filterComments: filters['filter-comments'],
|
||||
noDiscardValueNames: options['-fno-discard-value-names'],
|
||||
demangle: options['demangle-symbols'],
|
||||
showOptimized: options['show-optimized'],
|
||||
};
|
||||
let changed = false;
|
||||
for (const k in newOptions) {
|
||||
@@ -346,6 +348,8 @@ export class Ir extends MonacoPane<monaco.editor.IStandaloneCodeEditor, IrState>
|
||||
if (compiler && !compiler.supportsIrView) {
|
||||
this.editor.setValue('<LLVM IR output is not supported for this compiler>');
|
||||
}
|
||||
|
||||
this.options.enableToggle('show-optimized', !!compiler?.supportsIrViewOptToggleOption);
|
||||
}
|
||||
|
||||
showIrResults(result: any): void {
|
||||
|
||||
@@ -25,7 +25,11 @@
|
||||
import {describe, expect, it} from 'vitest';
|
||||
import {AsmParser} from '../lib/parsers/asm-parser.js';
|
||||
import {MlirAsmParser} from '../lib/parsers/asm-parser-mlir.js';
|
||||
import {PolkaVMAsmParser} from '../lib/parsers/asm-parser-polkavm.js';
|
||||
import {PTXAsmParser} from '../lib/parsers/asm-parser-ptx.js';
|
||||
import {ResolcRiscVAsmParser} from '../lib/parsers/asm-parser-resolc-riscv.js';
|
||||
import type {ParsedAsmResult} from '../types/asmresult/asmresult.interfaces.js';
|
||||
import type {ParseFiltersAndOutputOptions} from '../types/features/filters.interfaces.js';
|
||||
|
||||
describe('AsmParser tests', () => {
|
||||
const parser = new AsmParser();
|
||||
@@ -293,3 +297,191 @@ module {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ResolcRiscVAsmParser tests', () => {
|
||||
const parser = new ResolcRiscVAsmParser();
|
||||
|
||||
function expectParsedAsmResult(result: ParsedAsmResult, expected: ParsedAsmResult): void {
|
||||
expect(result.labelDefinitions).toEqual(expected.labelDefinitions);
|
||||
expect(result.asm.length).toEqual(expected.asm.length);
|
||||
for (let i = 0; i < result.asm.length; i++) {
|
||||
expect(result.asm[i]).toMatchObject(expected.asm[i]);
|
||||
}
|
||||
}
|
||||
|
||||
it.skipIf(process.platform === 'win32')('should identify RISC-V instruction info and source line numbers', () => {
|
||||
const filters: Partial<ParseFiltersAndOutputOptions> = {binaryObject: true};
|
||||
const riscv = `
|
||||
000000000000027a <__entry>:
|
||||
; __entry():
|
||||
; path/to/example.sol.Square.yul:1
|
||||
27a: 41 11 addi sp, sp, -0x10
|
||||
|
||||
000000000000028c <.Lpcrel_hi4>:
|
||||
; .Lpcrel_hi4():
|
||||
28c: 97 05 00 00 auipc a1, 0x0
|
||||
; path/to/example.sol.Square.yul:1
|
||||
2a8: 1d 71 addi sp, sp, -0x60
|
||||
2aa: 86 ec sd ra, 0x58(sp)
|
||||
; path/to/example.sol.Square.yul:7
|
||||
2ca: e7 80 00 00 jalr ra <.Lpcrel_hi4+0x3a>`;
|
||||
|
||||
const expected: ParsedAsmResult = {
|
||||
asm: [
|
||||
{
|
||||
text: '__entry:',
|
||||
source: null,
|
||||
},
|
||||
{
|
||||
text: ' addi sp, sp, -0x10',
|
||||
address: 0x27a,
|
||||
opcodes: ['41', '11'],
|
||||
source: {
|
||||
line: 1,
|
||||
file: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
text: '.Lpcrel_hi4:',
|
||||
source: null,
|
||||
},
|
||||
{
|
||||
text: ' auipc a1, 0x0',
|
||||
address: 0x28c,
|
||||
opcodes: ['97', '05', '00', '00'],
|
||||
source: {
|
||||
line: 1,
|
||||
file: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
text: ' addi sp, sp, -0x60',
|
||||
address: 0x2a8,
|
||||
opcodes: ['1d', '71'],
|
||||
source: {
|
||||
line: 1,
|
||||
file: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
text: ' sd ra, 0x58(sp)',
|
||||
address: 0x2aa,
|
||||
opcodes: ['86', 'ec'],
|
||||
source: {
|
||||
line: 1,
|
||||
file: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
text: ' jalr ra <.Lpcrel_hi4+0x3a>',
|
||||
address: 0x2ca,
|
||||
opcodes: ['e7', '80', '00', '00'],
|
||||
source: {
|
||||
line: 7,
|
||||
file: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
labelDefinitions: {
|
||||
__entry: 1,
|
||||
['.Lpcrel_hi4']: 3,
|
||||
},
|
||||
};
|
||||
|
||||
const result = parser.processAsm(riscv, filters);
|
||||
expectParsedAsmResult(result, expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PolkaVMAsmParser tests', () => {
|
||||
const parser = new PolkaVMAsmParser();
|
||||
|
||||
function expectParsedAsmResult(result: ParsedAsmResult, expected: ParsedAsmResult): void {
|
||||
expect(result.labelDefinitions).toEqual(expected.labelDefinitions);
|
||||
expect(result.asm.length).toEqual(expected.asm.length);
|
||||
for (let i = 0; i < result.asm.length; i++) {
|
||||
expect(result.asm[i]).toMatchObject(expected.asm[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Note: We currently have no source mappings from PVM.
|
||||
it('should identify PVM instruction info', () => {
|
||||
const filters: Partial<ParseFiltersAndOutputOptions> = {
|
||||
binaryObject: false,
|
||||
commentOnly: false,
|
||||
};
|
||||
const pvm = `
|
||||
// Code size = 1078 bytes
|
||||
|
||||
<__entry>:
|
||||
: @0 (gas: 6)
|
||||
0: sp = sp + 0xfffffffffffffff0
|
||||
3: u64 [sp + 0x8] = ra
|
||||
6: u64 [sp] = s0
|
||||
8: s0 = a0 & 0x1
|
||||
11: ecalli 2 // 'call_data_size'
|
||||
13: fallthrough
|
||||
: @1 (gas: 2)
|
||||
14: u32 [0x20000] = a0`;
|
||||
|
||||
const expected: ParsedAsmResult = {
|
||||
asm: [
|
||||
{
|
||||
text: '// Code size = 1078 bytes',
|
||||
source: null,
|
||||
},
|
||||
{
|
||||
text: '__entry:',
|
||||
source: null,
|
||||
},
|
||||
{
|
||||
text: ' @0 (gas: 6)',
|
||||
source: null,
|
||||
},
|
||||
{
|
||||
text: ' sp = sp + 0xfffffffffffffff0',
|
||||
address: 0,
|
||||
source: null,
|
||||
},
|
||||
{
|
||||
text: ' u64 [sp + 0x8] = ra',
|
||||
address: 3,
|
||||
source: null,
|
||||
},
|
||||
{
|
||||
text: ' u64 [sp] = s0',
|
||||
address: 6,
|
||||
source: null,
|
||||
},
|
||||
{
|
||||
text: ' s0 = a0 & 0x1',
|
||||
address: 8,
|
||||
source: null,
|
||||
},
|
||||
{
|
||||
text: " ecalli 2 // 'call_data_size'",
|
||||
address: 11,
|
||||
source: null,
|
||||
},
|
||||
{
|
||||
text: ' fallthrough',
|
||||
address: 13,
|
||||
source: null,
|
||||
},
|
||||
{
|
||||
text: ' @1 (gas: 2)',
|
||||
source: null,
|
||||
},
|
||||
{
|
||||
text: ' u32 [0x20000] = a0',
|
||||
address: 14,
|
||||
source: null,
|
||||
},
|
||||
],
|
||||
labelDefinitions: {__entry: 2},
|
||||
};
|
||||
|
||||
const result = parser.process(pvm, filters);
|
||||
expectParsedAsmResult(result, expected);
|
||||
});
|
||||
});
|
||||
|
||||
307
test/resolc-tests.ts
Normal file
307
test/resolc-tests.ts
Normal file
@@ -0,0 +1,307 @@
|
||||
// Copyright (c) 2025, 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 path from 'node:path';
|
||||
|
||||
import {beforeAll, describe, expect, it} from 'vitest';
|
||||
|
||||
import type {CompilationEnvironment} from '../lib/compilation-env.js';
|
||||
import {ResolcParser} from '../lib/compilers/argument-parsers.js';
|
||||
import {ResolcCompiler} from '../lib/compilers/index.js';
|
||||
import type {ParsedAsmResult, ParsedAsmResultLine} from '../types/asmresult/asmresult.interfaces.js';
|
||||
import type {CompilerInfo} from '../types/compiler.interfaces.js';
|
||||
import type {ParseFiltersAndOutputOptions} from '../types/features/filters.interfaces.js';
|
||||
import type {LanguageKey} from '../types/languages.interfaces.js';
|
||||
import {makeCompilationEnvironment, makeFakeCompilerInfo, makeFakeLlvmIrBackendOptions} from './utils.js';
|
||||
|
||||
const languages = {
|
||||
solidity: {id: 'solidity' as LanguageKey},
|
||||
yul: {id: 'yul' as LanguageKey},
|
||||
};
|
||||
|
||||
describe('Resolc', () => {
|
||||
let env: CompilationEnvironment;
|
||||
const expectedSolcExe = '/opt/compiler-explorer/solc-0.8.30/solc';
|
||||
|
||||
beforeAll(() => {
|
||||
env = makeCompilationEnvironment({languages});
|
||||
});
|
||||
|
||||
function makeCompiler(compilerInfo: Partial<CompilerInfo>): ResolcCompiler {
|
||||
return new ResolcCompiler(makeFakeCompilerInfo(compilerInfo), env);
|
||||
}
|
||||
|
||||
function expectCorrectOutputFilenames(
|
||||
compiler: ResolcCompiler,
|
||||
inputFilename: string,
|
||||
expectedFilenameWithoutExtension: string,
|
||||
): void {
|
||||
const defaultOutputFilename = `${expectedFilenameWithoutExtension}.pvmasm`;
|
||||
expect(compiler.getOutputFilename(path.normalize('test/resolc'))).toEqual(defaultOutputFilename);
|
||||
|
||||
let llvmIrBackendOptions = makeFakeLlvmIrBackendOptions({showOptimized: true});
|
||||
expect(compiler.getIrOutputFilename(inputFilename, undefined, llvmIrBackendOptions)).toEqual(
|
||||
`${expectedFilenameWithoutExtension}.optimized.ll`,
|
||||
);
|
||||
|
||||
llvmIrBackendOptions = makeFakeLlvmIrBackendOptions({showOptimized: false});
|
||||
expect(compiler.getIrOutputFilename(inputFilename, undefined, llvmIrBackendOptions)).toEqual(
|
||||
`${expectedFilenameWithoutExtension}.unoptimized.ll`,
|
||||
);
|
||||
|
||||
expect(compiler.getObjdumpOutputFilename(defaultOutputFilename)).toEqual(
|
||||
`${expectedFilenameWithoutExtension}.o`,
|
||||
);
|
||||
}
|
||||
|
||||
describe('Common', () => {
|
||||
it('should return correct key', () => {
|
||||
expect(ResolcCompiler.key).toEqual('resolc');
|
||||
});
|
||||
|
||||
it('should return Solc executable dependency path', () => {
|
||||
expect(ResolcCompiler.solcExe).toEqual(expectedSolcExe);
|
||||
});
|
||||
});
|
||||
|
||||
describe('From Solidity', () => {
|
||||
const compilerInfo = {
|
||||
exe: 'resolc',
|
||||
lang: languages.solidity.id,
|
||||
};
|
||||
|
||||
it('should instantiate successfully', () => {
|
||||
const compiler = makeCompiler(compilerInfo);
|
||||
expect(compiler.lang.id).toEqual(compilerInfo.lang);
|
||||
});
|
||||
|
||||
it('should use Resolc argument parser', () => {
|
||||
const compiler = makeCompiler(compilerInfo);
|
||||
expect(compiler.getArgumentParserClass()).toBe(ResolcParser);
|
||||
});
|
||||
|
||||
it('should use debug options', () => {
|
||||
const compiler = makeCompiler(compilerInfo);
|
||||
expect(compiler.optionsForFilter({})).toEqual([
|
||||
'-g',
|
||||
'--solc',
|
||||
expectedSolcExe,
|
||||
'--overwrite',
|
||||
'--debug-output-dir',
|
||||
'artifacts',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should generate output filenames', () => {
|
||||
const compiler = makeCompiler(compilerInfo);
|
||||
const filenameWithoutExtension = path.normalize('test/resolc/artifacts/test_resolc_example.sol.Square');
|
||||
const inputFilename = path.normalize('test/resolc/example.sol');
|
||||
expectCorrectOutputFilenames(compiler, inputFilename, filenameWithoutExtension);
|
||||
});
|
||||
|
||||
describe('To RISC-V', () => {
|
||||
const filters: Partial<ParseFiltersAndOutputOptions> = {
|
||||
binaryObject: true,
|
||||
libraryCode: true,
|
||||
};
|
||||
|
||||
function getExpectedParsedOutputHeader(): ParsedAsmResultLine[] {
|
||||
const header =
|
||||
'; RISC-V (64 bits) Assembly:\n' +
|
||||
'; --------------------------\n' +
|
||||
'; To see the PolkaVM assembly instead,\n' +
|
||||
'; enable "Compile to binary object".\n' +
|
||||
'; --------------------------';
|
||||
|
||||
return header.split('\n').map(line => ({text: line}));
|
||||
}
|
||||
|
||||
it('should remove orphaned labels', async () => {
|
||||
const compiler = makeCompiler(compilerInfo);
|
||||
|
||||
const parsedAsm: ParsedAsmResult = {
|
||||
asm: [
|
||||
{
|
||||
// Orphan
|
||||
text: 'memmove:',
|
||||
},
|
||||
{
|
||||
// Orphan
|
||||
text: '.LBB34_5:',
|
||||
},
|
||||
{
|
||||
// Orphan
|
||||
text: 'memset:',
|
||||
},
|
||||
{
|
||||
// Orphan
|
||||
text: '.LBB35_2:',
|
||||
},
|
||||
{
|
||||
text: '__entry:',
|
||||
},
|
||||
{
|
||||
text: ' addi sp, sp, -0x10',
|
||||
},
|
||||
{
|
||||
text: ' sd ra, 0x8(sp)',
|
||||
},
|
||||
{
|
||||
// Orphan
|
||||
text: '__last:',
|
||||
},
|
||||
],
|
||||
labelDefinitions: {
|
||||
memmove: 1,
|
||||
['.LBB34_5']: 2,
|
||||
memset: 3,
|
||||
['.LBB35_2']: 4,
|
||||
__entry: 5,
|
||||
__last: 8,
|
||||
},
|
||||
};
|
||||
|
||||
const expected: ParsedAsmResult = {
|
||||
asm: [
|
||||
...getExpectedParsedOutputHeader(),
|
||||
{
|
||||
text: '__entry:',
|
||||
},
|
||||
{
|
||||
text: ' addi sp, sp, -0x10',
|
||||
},
|
||||
{
|
||||
text: ' sd ra, 0x8(sp)',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = await compiler.postProcessAsm(parsedAsm, filters);
|
||||
expect(result.asm.length).toEqual(expected.asm.length);
|
||||
expect(result.asm).toMatchObject(expected.asm);
|
||||
});
|
||||
|
||||
it('should remove invalid Solidity <--> RISC-V source mappings', async () => {
|
||||
const compiler = makeCompiler(compilerInfo);
|
||||
|
||||
const parsedAsm: ParsedAsmResult = {
|
||||
asm: [
|
||||
{
|
||||
text: '.Lpcrel_hi4:',
|
||||
source: null,
|
||||
},
|
||||
{
|
||||
text: ' auipc a1, 0x0',
|
||||
source: null,
|
||||
},
|
||||
{
|
||||
text: ' addi sp, sp, -0x60',
|
||||
source: {
|
||||
line: 1,
|
||||
file: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
text: ' sd ra, 0x58(sp)',
|
||||
source: {
|
||||
line: 1,
|
||||
file: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
text: ' jalr ra <.Lpcrel_hi4+0x3a>',
|
||||
source: {
|
||||
line: 7,
|
||||
file: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
labelDefinitions: {['.Lpcrel_hi4']: 1},
|
||||
};
|
||||
|
||||
const expected: ParsedAsmResult = {
|
||||
asm: [
|
||||
...getExpectedParsedOutputHeader(),
|
||||
{
|
||||
text: '.Lpcrel_hi4:',
|
||||
source: null,
|
||||
},
|
||||
{
|
||||
text: ' auipc a1, 0x0',
|
||||
source: null,
|
||||
},
|
||||
{
|
||||
text: ' addi sp, sp, -0x60',
|
||||
source: null,
|
||||
},
|
||||
{
|
||||
text: ' sd ra, 0x58(sp)',
|
||||
source: null,
|
||||
},
|
||||
{
|
||||
text: ' jalr ra <.Lpcrel_hi4+0x3a>',
|
||||
source: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = await compiler.postProcessAsm(parsedAsm, filters);
|
||||
expect(result.asm.length).toEqual(expected.asm.length);
|
||||
expect(result.asm).toMatchObject(expected.asm);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('From Yul', () => {
|
||||
const compilerInfo = {
|
||||
exe: 'resolc',
|
||||
lang: languages.yul.id,
|
||||
};
|
||||
|
||||
it('should instantiate successfully', () => {
|
||||
const compiler = makeCompiler(compilerInfo);
|
||||
expect(compiler.lang.id).toEqual(compilerInfo.lang);
|
||||
});
|
||||
|
||||
it('should use debug options', () => {
|
||||
const compiler = makeCompiler(compilerInfo);
|
||||
expect(compiler.optionsForFilter({})).toEqual([
|
||||
'-g',
|
||||
'--solc',
|
||||
expectedSolcExe,
|
||||
'--overwrite',
|
||||
'--debug-output-dir',
|
||||
'artifacts',
|
||||
'--yul',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should generate output filenames', () => {
|
||||
const compiler = makeCompiler(compilerInfo);
|
||||
const filenameWithoutExtension = path.normalize('test/resolc/artifacts/test_resolc_example.yul.Square');
|
||||
const inputFilename = path.normalize('test/resolc/example.yul');
|
||||
expectCorrectOutputFilenames(compiler, inputFilename, filenameWithoutExtension);
|
||||
});
|
||||
});
|
||||
});
|
||||
8
test/resolc/example.sol
Normal file
8
test/resolc/example.sol
Normal file
@@ -0,0 +1,8 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity >=0.4.0;
|
||||
|
||||
contract Square {
|
||||
function square(uint32 num) public pure returns (uint32) {
|
||||
return num * num;
|
||||
}
|
||||
}
|
||||
43
test/resolc/example.yul
Normal file
43
test/resolc/example.yul
Normal file
@@ -0,0 +1,43 @@
|
||||
object "Square" {
|
||||
code {
|
||||
{
|
||||
let _1 := memoryguard(0x80)
|
||||
mstore(64, _1)
|
||||
if callvalue() { revert(0, 0) }
|
||||
let _2 := datasize("Square_deployed")
|
||||
codecopy(_1, dataoffset("Square_deployed"), _2)
|
||||
return(_1, _2)
|
||||
}
|
||||
}
|
||||
object "Square_deployed" {
|
||||
code {
|
||||
{
|
||||
let _1 := memoryguard(0x80)
|
||||
mstore(64, _1)
|
||||
if iszero(lt(calldatasize(), 4))
|
||||
{
|
||||
if eq(0xd27b3841, shr(224, calldataload(0)))
|
||||
{
|
||||
if callvalue() { revert(0, 0) }
|
||||
if slt(add(calldatasize(), not(3)), 32) { revert(0, 0) }
|
||||
let value := calldataload(4)
|
||||
let _2 := and(value, 0xffffffff)
|
||||
if iszero(eq(value, _2)) { revert(0, 0) }
|
||||
let product_raw := mul(_2, _2)
|
||||
let product := and(product_raw, 0xffffffff)
|
||||
if iszero(eq(product, product_raw))
|
||||
{
|
||||
mstore(0, shl(224, 0x4e487b71))
|
||||
mstore(4, 0x11)
|
||||
revert(0, 0x24)
|
||||
}
|
||||
mstore(_1, product)
|
||||
return(_1, 32)
|
||||
}
|
||||
}
|
||||
revert(0, 0)
|
||||
}
|
||||
}
|
||||
data ".metadata" hex"a26469706673582212209b2b1b86ce0e1a75faa800884ba155bd6bc6a6bc71f210370f818535dcfc5ee364736f6c634300081e0033"
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,11 @@ import {AsmEWAVRParser} from '../lib/parsers/asm-parser-ewavr.js';
|
||||
import {PTXAsmParser} from '../lib/parsers/asm-parser-ptx.js';
|
||||
import {SassAsmParser} from '../lib/parsers/asm-parser-sass.js';
|
||||
import {VcAsmParser} from '../lib/parsers/asm-parser-vc.js';
|
||||
import {CompilerProps, fakeProps} from '../lib/properties.js';
|
||||
import {LLVMIrBackendOptions} from '../types/compilation/ir.interfaces.js';
|
||||
import {CompilerInfo} from '../types/compiler.interfaces.js';
|
||||
import {ParseFiltersAndOutputOptions} from '../types/features/filters.interfaces.js';
|
||||
import {Language} from '../types/languages.interfaces.js';
|
||||
|
||||
// Test helper class that extends AsmParser to allow setting protected properties for testing
|
||||
class AsmParserForTest extends AsmParser {
|
||||
@@ -48,11 +53,6 @@ class AsmParserForTest extends AsmParser {
|
||||
}
|
||||
}
|
||||
|
||||
import {CompilerProps, fakeProps} from '../lib/properties.js';
|
||||
import {CompilerInfo} from '../types/compiler.interfaces.js';
|
||||
import {ParseFiltersAndOutputOptions} from '../types/features/filters.interfaces.js';
|
||||
import {Language} from '../types/languages.interfaces.js';
|
||||
|
||||
function ensureTempCleanup() {
|
||||
// Sometimes we're called from inside a test, sometimes from outside. Handle both.
|
||||
afterEach(async () => await temp.cleanup());
|
||||
@@ -85,6 +85,10 @@ export function makeFakeParseFiltersAndOutputOptions(
|
||||
return options as ParseFiltersAndOutputOptions;
|
||||
}
|
||||
|
||||
export function makeFakeLlvmIrBackendOptions(options: Partial<LLVMIrBackendOptions>): LLVMIrBackendOptions {
|
||||
return options as LLVMIrBackendOptions;
|
||||
}
|
||||
|
||||
// This combines a should assert and a type guard
|
||||
// Example:
|
||||
//
|
||||
|
||||
@@ -29,4 +29,5 @@ export type LLVMIrBackendOptions = {
|
||||
filterComments: boolean;
|
||||
noDiscardValueNames?: boolean;
|
||||
demangle: boolean;
|
||||
showOptimized?: boolean;
|
||||
};
|
||||
|
||||
@@ -82,6 +82,7 @@ export type CompilerInfo = {
|
||||
supportsPpView?: boolean;
|
||||
supportsAstView?: boolean;
|
||||
supportsIrView?: boolean;
|
||||
supportsIrViewOptToggleOption?: boolean;
|
||||
supportsClangirView?: boolean;
|
||||
supportsRustMirView?: boolean;
|
||||
supportsRustMacroExpView?: boolean;
|
||||
|
||||
@@ -108,8 +108,9 @@ export type LanguageKey =
|
||||
| 'vb'
|
||||
| 'vyper'
|
||||
| 'wasm'
|
||||
| 'zig'
|
||||
| 'ylc';
|
||||
| 'ylc'
|
||||
| 'yul'
|
||||
| 'zig';
|
||||
|
||||
export interface Language {
|
||||
/** Id of language. Added programmatically based on CELanguages key */
|
||||
|
||||
@@ -19,6 +19,7 @@ mixin optionButton(bind, isActive, text, title)
|
||||
.dropdown-menu
|
||||
+optionButton("demangle-symbols", true, "Demangle Symbols", "Demangle symbols")
|
||||
+optionButton("-fno-discard-value-names", true, "-fno-discard-value-names", "Keep value names instead of LLVM value numbers")
|
||||
+optionButton("show-optimized", true, "Show Optimized", "Show the optimized LLVM IR output")
|
||||
.btn-group.btn-group-sm.filters(role="group")
|
||||
button.btn.btn-sm.btn-light.dropdown-toggle(type="button" title="LLVM Opt Pass Filters" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label="Set output filters")
|
||||
span.fas.fa-filter
|
||||
|
||||
Reference in New Issue
Block a user