mirror of
https://github.com/compiler-explorer/compiler-explorer.git
synced 2025-12-27 07:04:04 -05:00
## What Adds support for seeing Yul (Solidity IR) as intermediate output when compiling Solidity. This PR also enables that view for the Resolc compiler. ### Main Additions - [x] Support viewing Yul in a supplementary view - Solidity compilers can enable this by setting `this.compiler.supportsYulView = true` in the compiler's constructor - If custom processing of the Yul output or the Yul output filename is needed, the compiler can override `processYulOutput()` or `getYulOutputFilename()` - [x] Enable the Yul view for Resolc - [x] Implement a Yul backend option for filtering out debug info from the output ### Notes Source mappings are currently not handled for Yul -> Solidity. ## Overall Usage ### Steps * Choose Solidity as the language * Choose a Resolc compiler * View intermediate results: * Yul * (Hide/show debug info by toggling "Hide Debug Info" in the Yul view filters) ## Screenshots <img width="1502" height="903" alt="ce-yul-view" src="https://github.com/user-attachments/assets/ccc897e2-cd8d-4c33-962c-522d60b63134" />
308 lines
12 KiB
TypeScript
308 lines
12 KiB
TypeScript
// 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(path.dirname(inputFilename)))).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.getObjdumpInputFilename(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);
|
|
});
|
|
});
|
|
});
|