Skip to content

Commit

Permalink
Pass selected outputs down to CompilerStack to avoid generating them …
Browse files Browse the repository at this point in the history
…when not requested
  • Loading branch information
cameel committed Oct 1, 2024
1 parent a4fa886 commit a9d9143
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 93 deletions.
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Language Features:
Compiler Features:
* Code Generator: Transient storage value type state variables are now supported.
* General: Generate JSON representations of Yul ASTs only on demand to reduce memory usage.
* Standard JSON Interface: Bytecode or IR can now be requested for a subset of all contracts without triggering unnecessary code generation for other contracts.


Bugfixes:
Expand Down
62 changes: 41 additions & 21 deletions libsolidity/interface/CompilerStack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,12 @@ void CompilerStack::setModelCheckerSettings(ModelCheckerSettings _settings)
m_modelCheckerSettings = _settings;
}

void CompilerStack::selectContracts(ContractSelection const& _selectedContracts)
{
solAssert(m_stackState < ParsedAndImported, "Must request outputs before parsing.");
m_selectedContracts = _selectedContracts;
}

void CompilerStack::setLibraries(std::map<std::string, util::h160> const& _libraries)
{
solAssert(m_stackState < ParsedAndImported, "Must set libraries before parsing.");
Expand Down Expand Up @@ -315,8 +321,7 @@ void CompilerStack::reset(bool _keepSettings)
m_evmVersion = langutil::EVMVersion();
m_eofVersion.reset();
m_modelCheckerSettings = ModelCheckerSettings{};
m_requestedContractNames.clear();
m_irOutputSelection = IROutputSelection::None;
m_selectedContracts.clear();
m_revertStrings = RevertStrings::Default;
m_optimiserSettings = OptimiserSettings::minimal();
m_metadataLiteralSources = false;
Expand Down Expand Up @@ -690,28 +695,50 @@ bool CompilerStack::parseAndAnalyze(State _stopAfter)
bool CompilerStack::isRequestedSource(std::string const& _sourceName) const
{
return
m_requestedContractNames.empty() ||
m_requestedContractNames.count("") ||
m_requestedContractNames.count(_sourceName);
m_selectedContracts.empty() ||
m_selectedContracts.count("") ||
m_selectedContracts.count(_sourceName);
}

bool CompilerStack::isRequestedContract(ContractDefinition const& _contract) const
{
/// In case nothing was specified in outputSelection.
if (m_requestedContractNames.empty())
/// In case nothing was specified in selectedContracts.
if (m_selectedContracts.empty())
return true;

for (auto const& key: std::vector<std::string>{"", _contract.sourceUnitName()})
{
auto const& it = m_requestedContractNames.find(key);
if (it != m_requestedContractNames.end())
auto const& it = m_selectedContracts.find(key);
if (it != m_selectedContracts.end())
if (it->second.count(_contract.name()) || it->second.count(""))
return true;
}

return false;
}

CompilerStack::PipelineConfig CompilerStack::requestedPipelineConfig(ContractDefinition const& _contract) const
{
static PipelineConfig constexpr defaultPipelineConfig = PipelineConfig{
false, // irCodegen
false, // irOptimization
true, // bytecode
};

// If nothing was explicitly selected, all contracts are selected by default.
if (m_selectedContracts.empty())
return defaultPipelineConfig;

PipelineConfig combinedConfig;
for (std::string const& sourceUnitName: {""s, _contract.sourceUnitName()})
if (m_selectedContracts.count(sourceUnitName) != 0)
for (std::string const& contractName: {""s, _contract.name()})
if (m_selectedContracts.at(sourceUnitName).count(contractName) != 0)
combinedConfig = combinedConfig | m_selectedContracts.at(sourceUnitName).at(contractName);

return combinedConfig;
}

bool CompilerStack::compile(State _stopAfter)
{
m_stopAfter = _stopAfter;
Expand All @@ -730,20 +757,13 @@ bool CompilerStack::compile(State _stopAfter)
if (auto contract = dynamic_cast<ContractDefinition const*>(node.get()))
if (isRequestedContract(*contract))
{
PipelineConfig pipelineConfig = requestedPipelineConfig(*contract);

try
{
// NOTE: Bytecode generation via IR always uses Contract::yulIROptimized.
// When optimization is not enabled, that member simply contains unoptimized code.
bool needIROutput =
(m_generateEvmBytecode && m_viaIR) ||
m_irOutputSelection != IROutputSelection::None;
bool needUnoptimizedIROutputOnly =
!(m_generateEvmBytecode && m_viaIR) &&
m_irOutputSelection != IROutputSelection::UnoptimizedAndOptimized;

if (needIROutput)
generateIR(*contract, needUnoptimizedIROutputOnly);
if (m_generateEvmBytecode)
if (pipelineConfig.needIR(m_viaIR))
generateIR(*contract, pipelineConfig.needIRCodegenOnly(m_viaIR));
if (pipelineConfig.needBytecode())
{
if (m_viaIR)
generateEVMFromIR(*contract);
Expand Down
86 changes: 59 additions & 27 deletions libsolidity/interface/CompilerStack.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,54 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac
SolidityAST,
};

enum class IROutputSelection {
None,
UnoptimizedOnly,
UnoptimizedAndOptimized,
/// Indicates which stages of the compilation pipeline were explicitly requested and provides
/// logic to determine which ones are effectively needed to accomplish that.
/// Note that parsing and analysis are not selectable, since they cannot be skipped.
struct PipelineConfig
{
bool irCodegen = false; ///< Want IR output straight from code generator.
bool irOptimization = false; ///< Want reparsed IR that went through YulStack. May be optimized or not, depending on settings.
bool bytecode = false; ///< Want EVM-level outputs, especially EVM assembly and bytecode. May be optimized or not, depending on settings.

bool needIR(bool _viaIR) const
{
return
irCodegen ||
irOptimization ||
(bytecode && _viaIR);
}

bool needIRCodegenOnly(bool _viaIR) const
{
return !(bytecode && _viaIR) && !irOptimization;
}

bool needBytecode() const
{
return bytecode;
}

PipelineConfig operator|(PipelineConfig const& _other) const
{
return {
irCodegen || _other.irCodegen,
irOptimization || _other.irOptimization,
bytecode || _other.bytecode,
};
}

bool operator!=(PipelineConfig const& _other) const { return !(*this == _other); }
bool operator==(PipelineConfig const& _other) const
{
return
irCodegen == _other.irCodegen &&
irOptimization == _other.irOptimization &&
bytecode == _other.bytecode;
}
};

using ContractSelection = std::map<std::string, std::map<std::string, CompilerStack::PipelineConfig>>;

/// Creates a new compiler stack.
/// @param _readFile callback used to read files for import statements. Must return
/// and must not emit exceptions.
Expand Down Expand Up @@ -193,26 +235,13 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac
/// Set model checker settings.
void setModelCheckerSettings(ModelCheckerSettings _settings);

/// Sets the requested contract names by source.
/// If empty, no filtering is performed and every contract
/// found in the supplied sources is compiled.
/// Names are cleared iff @a _contractNames is missing.
void setRequestedContractNames(std::map<std::string, std::set<std::string>> const& _contractNames = std::map<std::string, std::set<std::string>>{})
{
m_requestedContractNames = _contractNames;
}

/// Enable EVM Bytecode generation. This is enabled by default.
void enableEvmBytecodeGeneration(bool _enable = true) { m_generateEvmBytecode = _enable; }

/// Enable generation of Yul IR code so that IR output can be safely requested for all contracts.
/// Note that IR may also be implicitly generated when not requested. In particular
/// @a setViaIR(true) requires access to the IR outputs for bytecode generation.
void requestIROutputs(IROutputSelection _selection = IROutputSelection::UnoptimizedAndOptimized)
{
solAssert(m_stackState < ParsedAndImported);
m_irOutputSelection = _selection;
}
/// Sets names of the contracts from each source that should be compiled.
/// If empty, no filtering is performed and every contract found in the supplied sources goes
/// through the default pipeline stages (bytecode-only, no IR).
/// Source/contract names are not validated - ones that do not exist are ignored.
/// The empty source/contract name can be used as a wildcard that matches all sources/contracts.
/// If a contract matches more than one entry, the pipeline selection from all matches is combined.
void selectContracts(ContractSelection const& _selectedContracts);

/// @arg _metadataLiteralSources When true, store sources as literals in the contract metadata.
/// Must be set before parsing.
Expand Down Expand Up @@ -450,6 +479,11 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac
/// @returns true if the contract is requested to be compiled.
bool isRequestedContract(ContractDefinition const& _contract) const;

/// @returns The effective pipeline configuration for a given contract.
/// Applies defaults for contracts that were not explicitly selected and combines
/// multiple entries if the contact is matched by wildcards.
PipelineConfig requestedPipelineConfig(ContractDefinition const& _contract) const;

/// Perform the analysis steps of legacy language mode.
/// @returns false on error.
bool analyzeLegacy(bool _noErrorsSoFar);
Expand Down Expand Up @@ -554,9 +588,7 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac
langutil::EVMVersion m_evmVersion;
std::optional<uint8_t> m_eofVersion;
ModelCheckerSettings m_modelCheckerSettings;
std::map<std::string, std::set<std::string>> m_requestedContractNames;
bool m_generateEvmBytecode = true;
IROutputSelection m_irOutputSelection = IROutputSelection::None;
ContractSelection m_selectedContracts;
std::map<std::string, util::h160> m_libraries;
ImportRemapper m_importRemapper;
std::map<std::string const, Source> m_sources;
Expand Down
69 changes: 35 additions & 34 deletions libsolidity/interface/StandardCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,21 +150,6 @@ Json formatErrorWithException(
return error;
}

std::map<std::string, std::set<std::string>> requestedContractNames(Json const& _outputSelection)
{
std::map<std::string, std::set<std::string>> contracts;
for (auto const& [sourceName, _]: _outputSelection.items())
{
std::string key = (sourceName == "*") ? "" : sourceName;
for (auto const& [contractName, _]: _outputSelection[sourceName].items())
{
std::string value = (contractName == "*") ? "" : contractName;
contracts[key].insert(value);
}
}
return contracts;
}

/// Returns true iff @a _hash (hex with 0x prefix) is the Keccak256 hash of the binary data in @a _content.
bool hashMatchesContent(std::string const& _hash, std::string const& _content)
{
Expand Down Expand Up @@ -298,26 +283,45 @@ bool isEvmBytecodeRequested(Json const& _outputSelection)
return false;
}

/// @returns The IR output selection for CompilerStack, based on outputs requested in the JSON.
/// @returns The set of selected contracts, along with their compiler pipeline configuration, based
/// on outputs requested in the JSON. Translates wildcards to the ones understood by CompilerStack.
/// Note that as an exception, '*' does not yet match "ir", "irAst", "irOptimized" or "irOptimizedAst".
CompilerStack::IROutputSelection irOutputSelection(Json const& _outputSelection)
CompilerStack::ContractSelection pipelineConfig(
Json const& _jsonOutputSelection
)
{
if (!_outputSelection.is_object())
return CompilerStack::IROutputSelection::None;
if (!_jsonOutputSelection.is_object())
return {};

CompilerStack::IROutputSelection selection = CompilerStack::IROutputSelection::None;
for (auto const& fileRequests: _outputSelection)
for (auto const& requests: fileRequests)
for (auto const& request: requests)
CompilerStack::ContractSelection contractSelection;
for (auto const& [sourceUnitName, jsonOutputSelectionForSource]: _jsonOutputSelection.items())
{
solAssert(jsonOutputSelectionForSource.is_object());
for (auto const& [contractName, jsonOutputSelectionForContract]: jsonOutputSelectionForSource.items())
{
solAssert(jsonOutputSelectionForContract.is_array());
CompilerStack::PipelineConfig pipelineForContract;
for (Json const& request: jsonOutputSelectionForContract)
{
if (request == "irOptimized" || request == "irOptimizedAst" || request == "yulCFGJson")
return CompilerStack::IROutputSelection::UnoptimizedAndOptimized;

if (request == "ir" || request == "irAst")
selection = CompilerStack::IROutputSelection::UnoptimizedOnly;
solAssert(request.is_string());
pipelineForContract.irOptimization =
pipelineForContract.irOptimization ||
request == "irOptimized" ||
request == "irOptimizedAst" ||
request == "yulCFGJson";
pipelineForContract.irCodegen =
pipelineForContract.irCodegen ||
pipelineForContract.irOptimization ||
request == "ir" ||
request == "irAst";
pipelineForContract.bytecode = isEvmBytecodeRequested(_jsonOutputSelection);
}

return selection;
std::string key = (sourceUnitName == "*") ? "" : sourceUnitName;
std::string value = (contractName == "*") ? "" : contractName;
contractSelection[key][value] = pipelineForContract;
}
}
return contractSelection;
}

Json formatLinkReferences(std::map<size_t, std::string> const& linkReferences)
Expand Down Expand Up @@ -1321,12 +1325,9 @@ Json StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inpu
compilerStack.useMetadataLiteralSources(_inputsAndSettings.metadataLiteralSources);
compilerStack.setMetadataFormat(_inputsAndSettings.metadataFormat);
compilerStack.setMetadataHash(_inputsAndSettings.metadataHash);
compilerStack.setRequestedContractNames(requestedContractNames(_inputsAndSettings.outputSelection));
compilerStack.selectContracts(pipelineConfig(_inputsAndSettings.outputSelection));
compilerStack.setModelCheckerSettings(_inputsAndSettings.modelCheckerSettings);

compilerStack.enableEvmBytecodeGeneration(isEvmBytecodeRequested(_inputsAndSettings.outputSelection));
compilerStack.requestIROutputs(irOutputSelection(_inputsAndSettings.outputSelection));

Json errors = std::move(_inputsAndSettings.errors);

bool const binariesRequested = isBinaryRequested(_inputsAndSettings.outputSelection);
Expand Down
23 changes: 13 additions & 10 deletions solc/CommandLineInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -889,14 +889,16 @@ void CommandLineInterface::compile()
if (m_options.output.debugInfoSelection.has_value())
m_compiler->selectDebugInfo(m_options.output.debugInfoSelection.value());

CompilerStack::IROutputSelection irOutputSelection = CompilerStack::IROutputSelection::None;
if (m_options.compiler.outputs.irOptimized || m_options.compiler.outputs.irOptimizedAstJson || m_options.compiler.outputs.yulCFGJson)
irOutputSelection = CompilerStack::IROutputSelection::UnoptimizedAndOptimized;
else if (m_options.compiler.outputs.ir || m_options.compiler.outputs.irAstJson)
irOutputSelection = CompilerStack::IROutputSelection::UnoptimizedOnly;

m_compiler->requestIROutputs(irOutputSelection);
m_compiler->enableEvmBytecodeGeneration(
CompilerStack::PipelineConfig pipelineConfig;
pipelineConfig.irOptimization =
m_options.compiler.outputs.irOptimized ||
m_options.compiler.outputs.irOptimizedAstJson ||
m_options.compiler.outputs.yulCFGJson;
pipelineConfig.irCodegen =
pipelineConfig.irOptimization ||
m_options.compiler.outputs.ir ||
m_options.compiler.outputs.irAstJson;
pipelineConfig.bytecode =
m_options.compiler.estimateGas ||
m_options.compiler.outputs.asm_ ||
m_options.compiler.outputs.asmJson ||
Expand All @@ -914,8 +916,9 @@ void CommandLineInterface::compile()
m_options.compiler.combinedJsonRequests->srcMapRuntime ||
m_options.compiler.combinedJsonRequests->funDebug ||
m_options.compiler.combinedJsonRequests->funDebugRuntime
))
);
));

m_compiler->selectContracts({{"", {{"", pipelineConfig}}}});

m_compiler->setOptimiserSettings(m_options.optimiserSettings());

Expand Down
1 change: 0 additions & 1 deletion test/libsolidity/SolidityExecutionFramework.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ bytes SolidityExecutionFramework::multiSourceCompileContract(
m_compiler.setEVMVersion(m_evmVersion);
m_compiler.setEOFVersion(m_eofVersion);
m_compiler.setOptimiserSettings(m_optimiserSettings);
m_compiler.enableEvmBytecodeGeneration(true);
m_compiler.setViaIR(m_compileViaYul);
m_compiler.setRevertStringBehaviour(m_revertStrings);
if (!m_appendCBORMetadata) {
Expand Down

0 comments on commit a9d9143

Please sign in to comment.