Add default values to compiler arguments (#451)

## Description

Adds default values to the compiler arguments below when parsed by
`clap`. This does not change previous user-facing behavior as these
defaults were implemented using different logic, but improves the UX and
the visibility of the exact defaults used.

**Example output from `resolc --help`:**
(Only affected arguments are shown here, and some of their descriptions
have been omitted here.)
```
Options:

  -O, --optimization <OPTIMIZATION>
          Set the optimization parameter -O[0 | 1 | 2 | 3 | s | z]. Use `3` for best performance and `z` for minimal size
          
          [default: z]

      --metadata-hash <METADATA_HASH>
          Set the metadata hash type. Available types: `none`, `ipfs`, `keccak256`
          
          [default: keccak256]

      --heap-size <HEAP_SIZE>
          The emulated EVM linear heap memory static buffer size in bytes.
          ...
          
          [default: 131072]

      --stack-size <STACK_SIZE>
          The contracts total stack size in bytes.
          ...
          
          [default: 131072]
```

## Resolved Issues

Closes #446
This commit is contained in:
LJ authored and GitHub committed 2026-01-14 19:22:03 +01:00
1 parent 3bcb62ac0b
commit f5fe3faa71
4 files changed
+166 -65

No files matched your search

@@ -64,14 +64,7 @@ impl Settings {
/// Creates settings from a CLI optimization parameter. /// Creates settings from a CLI optimization parameter.
pub fn try_from_cli(value: char) -> anyhow::Result<Self> { pub fn try_from_cli(value: char) -> anyhow::Result<Self> {
Ok(match value { Ok(match value {
'0' => Self::new( '0' => Self::none(),
// The middle-end optimization level.
inkwell::OptimizationLevel::None,
// The middle-end size optimization level.
SizeLevel::Zero,
// The back-end optimization level.
inkwell::OptimizationLevel::None,
),
'1' => Self::new( '1' => Self::new(
inkwell::OptimizationLevel::Less, inkwell::OptimizationLevel::Less,
SizeLevel::Zero, SizeLevel::Zero,
@@ -84,23 +77,14 @@ impl Settings {
// The back-end does not currently distinguish between O1, O2, and O3. // The back-end does not currently distinguish between O1, O2, and O3.
inkwell::OptimizationLevel::Default, inkwell::OptimizationLevel::Default,
), ),
'3' => Self::new( '3' => Self::cycles(),
inkwell::OptimizationLevel::Aggressive,
SizeLevel::Zero,
inkwell::OptimizationLevel::Aggressive,
),
's' => Self::new( 's' => Self::new(
// The middle-end optimization level is ignored when SizeLevel is set. // The middle-end optimization level is ignored when SizeLevel is set.
inkwell::OptimizationLevel::Default, inkwell::OptimizationLevel::Default,
SizeLevel::S, SizeLevel::S,
inkwell::OptimizationLevel::Aggressive, inkwell::OptimizationLevel::Aggressive,
), ),
'z' => Self::new( 'z' => Self::size(),
// The middle-end optimization level is ignored when SizeLevel is set.
inkwell::OptimizationLevel::Default,
SizeLevel::Z,
inkwell::OptimizationLevel::Aggressive,
),
char => anyhow::bail!("Unexpected optimization option '{}'", char), char => anyhow::bail!("Unexpected optimization option '{}'", char),
}) })
} }
@@ -126,6 +110,7 @@ impl Settings {
/// Returns the settings for the optimal size. /// Returns the settings for the optimal size.
pub fn size() -> Self { pub fn size() -> Self {
Self::new( Self::new(
// The middle-end optimization level is ignored when SizeLevel is set.
inkwell::OptimizationLevel::Default, inkwell::OptimizationLevel::Default,
SizeLevel::Z, SizeLevel::Z,
inkwell::OptimizationLevel::Aggressive, inkwell::OptimizationLevel::Aggressive,
+42 -23
View File
@@ -4,10 +4,12 @@ use std::collections::BTreeSet;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use clap::Parser; use clap::{parser::ValueSource, ArgMatches, CommandFactory, Parser};
use path_slash::PathExt; use path_slash::PathExt;
use revive_common::MetadataHash; use revive_common::MetadataHash;
use revive_solc_json_interface::SolcStandardJsonOutputError; use revive_solc_json_interface::{
PolkaVMDefaultHeapMemorySize, PolkaVMDefaultStackMemorySize, SolcStandardJsonOutputError,
};
/// Compiles the provided Solidity input files (or use the standard input if no files /// Compiles the provided Solidity input files (or use the standard input if no files
/// are given or "-" is specified as a file name). Outputs the components based on the /// are given or "-" is specified as a file name). Outputs the components based on the
@@ -57,8 +59,8 @@ pub struct Arguments {
/// Set the optimization parameter -O[0 | 1 | 2 | 3 | s | z]. /// Set the optimization parameter -O[0 | 1 | 2 | 3 | s | z].
/// Use `3` for best performance and `z` for minimal size. /// Use `3` for best performance and `z` for minimal size.
#[arg(short = 'O', long = "optimization")] #[arg(short = 'O', long = "optimization", default_value = "z")]
pub optimization: Option<char>, pub optimization: char,
/// Disable the `solc` optimizer. /// Disable the `solc` optimizer.
/// Use it if your project uses the `MSIZE` instruction, or in other cases. /// Use it if your project uses the `MSIZE` instruction, or in other cases.
@@ -116,9 +118,8 @@ pub struct Arguments {
/// Set the metadata hash type. /// Set the metadata hash type.
/// Available types: `none`, `ipfs`, `keccak256`. /// Available types: `none`, `ipfs`, `keccak256`.
/// The default is `keccak256`. #[arg(long, default_value_t = MetadataHash::Keccak256)]
#[arg(long)] pub metadata_hash: MetadataHash,
pub metadata_hash: Option<String>,
/// Output PolkaVM assembly of the contracts. /// Output PolkaVM assembly of the contracts.
#[arg(long = "asm")] #[arg(long = "asm")]
@@ -185,9 +186,9 @@ pub struct Arguments {
/// You are incentiviced to keep this value as small as possible: /// You are incentiviced to keep this value as small as possible:
/// 1.Increasing the heap size will increase startup costs. /// 1.Increasing the heap size will increase startup costs.
/// 2.The heap size contributes to the total memory size a contract can use, /// 2.The heap size contributes to the total memory size a contract can use,
/// which includes the contracts code size /// which includes the contracts code size.
#[arg(long = "heap-size")] #[arg(long = "heap-size", default_value_t = PolkaVMDefaultHeapMemorySize)]
pub heap_size: Option<u32>, pub heap_size: u32,
/// The contracts total stack size in bytes. /// The contracts total stack size in bytes.
/// ///
@@ -198,16 +199,17 @@ pub struct Arguments {
/// eventually revert execution at runtime! /// eventually revert execution at runtime!
/// ///
/// You are incentiviced to keep this value as small as possible: /// You are incentiviced to keep this value as small as possible:
/// 1.Increasing the heap size will increase startup costs. /// 1.Increasing the stack size will increase startup costs.
/// 2.The stack size contributes to the total memory size a contract can use, /// 2.The stack size contributes to the total memory size a contract can use,
/// which includes the contracts code size /// which includes the contracts code size.
#[arg(long = "stack-size")] #[arg(long = "stack-size", default_value_t = PolkaVMDefaultStackMemorySize)]
pub stack_size: Option<u32>, pub stack_size: u32,
} }
impl Arguments { impl Arguments {
/// Validates the arguments. /// Validates the arguments.
pub fn validate(&self) -> Vec<SolcStandardJsonOutputError> { pub fn validate(&self) -> Vec<SolcStandardJsonOutputError> {
let argument_matches = Self::command().get_matches();
let mut messages = Vec::new(); let mut messages = Vec::new();
if self.version && std::env::args().count() > 2 { if self.version && std::env::args().count() > 2 {
@@ -226,9 +228,13 @@ impl Arguments {
)); ));
} }
if self.metadata_hash == Some(MetadataHash::IPFS.to_string()) { if self.metadata_hash == MetadataHash::IPFS {
messages.push(SolcStandardJsonOutputError::new_error( messages.push(SolcStandardJsonOutputError::new_error(
"`IPFS` metadata hash type is not supported. Please use `keccak256` instead.", format!(
"`{}` metadata hash type is not supported. Please use `{}` instead.",
MetadataHash::IPFS,
MetadataHash::Keccak256,
),
None, None,
None, None,
)); ));
@@ -350,34 +356,34 @@ impl Arguments {
} }
if self.disable_solc_optimizer { if self.disable_solc_optimizer {
messages.push(SolcStandardJsonOutputError::new_error( messages.push(SolcStandardJsonOutputError::new_error(
"Disabling the solc optimizer must specified in standard JSON input settings.", "Disabling the solc optimizer must be specified in standard JSON input settings.",
None, None,
None, None,
)); ));
} }
if self.optimization.is_some() { if Self::is_argument("optimization", &argument_matches) {
messages.push(SolcStandardJsonOutputError::new_error( messages.push(SolcStandardJsonOutputError::new_error(
"LLVM optimizations must specified in standard JSON input settings.", "LLVM optimizations must be specified in standard JSON input settings.",
None, None,
None, None,
)); ));
} }
if self.metadata_hash.is_some() { if Self::is_argument("metadata_hash", &argument_matches) {
messages.push(SolcStandardJsonOutputError::new_error( messages.push(SolcStandardJsonOutputError::new_error(
"Metadata hash mode must specified in standard JSON input settings.", "Metadata hash mode must be specified in standard JSON input settings.",
None, None,
None, None,
)); ));
} }
if self.heap_size.is_some() { if Self::is_argument("heap_size", &argument_matches) {
messages.push(SolcStandardJsonOutputError::new_error( messages.push(SolcStandardJsonOutputError::new_error(
"Heap size must be specified in standard JSON input polkavm memory settings.", "Heap size must be specified in standard JSON input polkavm memory settings.",
None, None,
None, None,
)); ));
} }
if self.stack_size.is_some() { if Self::is_argument("stack_size", &argument_matches) {
messages.push(SolcStandardJsonOutputError::new_error( messages.push(SolcStandardJsonOutputError::new_error(
"Stack size must be specified in standard JSON input polkavm memory settings.", "Stack size must be specified in standard JSON input polkavm memory settings.",
None, None,
@@ -403,6 +409,19 @@ impl Arguments {
messages messages
} }
/// Checks whether the value from the argument with the given `argument_id`
/// came from the command line.
///
/// This can be used for determining if a user has explicitly provided an
/// argument, even if it is the same value as the default one.
///
/// Panics if the `id` is not a valid argument id.
fn is_argument(argument_id: &str, argument_matches: &ArgMatches) -> bool {
argument_matches
.value_source(argument_id)
.is_some_and(|source| source == ValueSource::CommandLine)
}
/// Returns remappings from input paths. /// Returns remappings from input paths.
pub fn split_input_files_and_remappings( pub fn split_input_files_and_remappings(
&self, &self,
+11 -20
View File
@@ -1,13 +1,10 @@
//! Solidity to PolkaVM compiler binary. //! Solidity to PolkaVM compiler binary.
use std::str::FromStr;
use std::{io::Write, path::PathBuf}; use std::{io::Write, path::PathBuf};
use clap::error::ErrorKind; use clap::error::ErrorKind;
use resolc::Process; use resolc::Process;
use revive_common::{ use revive_common::{deserialize_from_str, EVMVersion, EXIT_CODE_FAILURE, EXIT_CODE_SUCCESS};
deserialize_from_str, EVMVersion, MetadataHash, EXIT_CODE_FAILURE, EXIT_CODE_SUCCESS,
};
use revive_llvm_context::{initialize_llvm, DebugConfig, OptimizerSettings, PolkaVMTarget}; use revive_llvm_context::{initialize_llvm, DebugConfig, OptimizerSettings, PolkaVMTarget};
use revive_solc_json_interface::{ use revive_solc_json_interface::{
ResolcWarning, SolcStandardJsonInputSettingsPolkaVMMemory, ResolcWarning, SolcStandardJsonInputSettingsPolkaVMMemory,
@@ -32,7 +29,7 @@ fn main() -> anyhow::Result<()> {
let is_standard_json = arguments.standard_json.is_some(); let is_standard_json = arguments.standard_json.is_some();
let mut messages = arguments.validate(); let mut messages = arguments.validate();
if messages.iter().all(|error| error.severity != "error") { if messages.iter().all(|error| !error.is_error()) {
if !is_standard_json { if !is_standard_json {
std::io::stderr() std::io::stderr()
.write_all( .write_all(
@@ -170,27 +167,21 @@ fn main_inner(
None => None, None => None,
}; };
let mut optimizer_settings = match arguments.optimization { let mut optimizer_settings = OptimizerSettings::try_from_cli(arguments.optimization)?;
Some(mode) => OptimizerSettings::try_from_cli(mode)?,
None => OptimizerSettings::size(),
};
optimizer_settings.is_verify_each_enabled = arguments.llvm_verify_each; optimizer_settings.is_verify_each_enabled = arguments.llvm_verify_each;
optimizer_settings.is_debug_logging_enabled = arguments.llvm_debug_logging; optimizer_settings.is_debug_logging_enabled = arguments.llvm_debug_logging;
let metadata_hash = match arguments.metadata_hash { let memory_config = SolcStandardJsonInputSettingsPolkaVMMemory::new(
Some(ref hash_type) => MetadataHash::from_str(hash_type.as_str())?, Some(arguments.heap_size),
None => MetadataHash::Keccak256, Some(arguments.stack_size),
}; );
let memory_config =
SolcStandardJsonInputSettingsPolkaVMMemory::new(arguments.heap_size, arguments.stack_size);
let build = if arguments.yul { let build = if arguments.yul {
resolc::yul( resolc::yul(
&solc, &solc,
input_files.as_slice(), input_files.as_slice(),
arguments.libraries.as_slice(), arguments.libraries.as_slice(),
metadata_hash, arguments.metadata_hash,
messages, messages,
optimizer_settings, optimizer_settings,
debug_config, debug_config,
@@ -200,7 +191,7 @@ fn main_inner(
} else if let Some(standard_json) = arguments.standard_json { } else if let Some(standard_json) = arguments.standard_json {
resolc::standard_json( resolc::standard_json(
&solc, &solc,
metadata_hash, arguments.metadata_hash,
messages, messages,
standard_json.map(PathBuf::from), standard_json.map(PathBuf::from),
arguments.base_path, arguments.base_path,
@@ -215,7 +206,7 @@ fn main_inner(
&solc, &solc,
input_files.as_slice(), input_files.as_slice(),
arguments.libraries.as_slice(), arguments.libraries.as_slice(),
metadata_hash, arguments.metadata_hash,
messages, messages,
evm_version, evm_version,
format, format,
@@ -240,7 +231,7 @@ fn main_inner(
&solc, &solc,
input_files.as_slice(), input_files.as_slice(),
arguments.libraries.as_slice(), arguments.libraries.as_slice(),
metadata_hash, arguments.metadata_hash,
messages, messages,
evm_version, evm_version,
!arguments.disable_solc_optimizer, !arguments.disable_solc_optimizer,
+109 -3
View File
@@ -1,6 +1,8 @@
//! The tests for running resolc with standard JSON option. //! The tests for running resolc with standard JSON option.
use revive_solc_json_interface::SolcStandardJsonOutput; use revive_solc_json_interface::{
PolkaVMDefaultHeapMemorySize, PolkaVMDefaultStackMemorySize, SolcStandardJsonOutput,
};
use crate::cli_utils::{ use crate::cli_utils::{
assert_command_success, assert_equal_exit_codes, execute_resolc_with_stdin_input, assert_command_success, assert_equal_exit_codes, execute_resolc_with_stdin_input,
@@ -10,12 +12,31 @@ use crate::cli_utils::{
const JSON_OPTION: &str = "--standard-json"; const JSON_OPTION: &str = "--standard-json";
/// Asserts that the standard JSON output has at least one error with the given `error_message`.
fn assert_standard_json_errors_contain(output: &SolcStandardJsonOutput, error_message: &str) {
assert!(
output
.errors
.iter()
.any(|error| error.is_error() && error.message.contains(error_message)),
"the standard JSON output should contain the error message `{error_message}`"
);
}
/// Converts valid JSON text to `SolcStandardJsonOutput`.
fn to_solc_standard_json_output(json_text: &str) -> SolcStandardJsonOutput {
serde_json::from_str(json_text).unwrap()
}
#[test] #[test]
fn runs_with_valid_input_file() { fn runs_with_valid_input_file() {
let arguments = &[JSON_OPTION]; let arguments = &[JSON_OPTION];
let resolc_result = execute_resolc_with_stdin_input(arguments, STANDARD_JSON_CONTRACTS_PATH); let resolc_result = execute_resolc_with_stdin_input(arguments, STANDARD_JSON_CONTRACTS_PATH);
assert_command_success(&resolc_result, "Providing a valid input file to stdin"); assert_command_success(&resolc_result, "Providing a valid input file to stdin");
let resolc_output = to_solc_standard_json_output(&resolc_result.stdout);
assert!(!resolc_output.errors.iter().any(|error| error.is_error()));
assert!( assert!(
resolc_result.stdout.contains("contracts"), resolc_result.stdout.contains("contracts"),
"Expected the output to contain a `contracts` field." "Expected the output to contain a `contracts` field."
@@ -30,7 +51,7 @@ fn no_evm_codegen_requested() {
let result = execute_resolc_with_stdin_input(&[JSON_OPTION], STANDARD_JSON_NO_EVM_CODEGEN_PATH); let result = execute_resolc_with_stdin_input(&[JSON_OPTION], STANDARD_JSON_NO_EVM_CODEGEN_PATH);
assert_command_success(&result, "EVM codegen std json input fixture should build"); assert_command_success(&result, "EVM codegen std json input fixture should build");
let output: SolcStandardJsonOutput = serde_json::from_str(&result.stdout).unwrap(); let output = to_solc_standard_json_output(&result.stdout);
assert!(!output.errors.iter().any(|msg| msg.severity == "error")) assert!(!output.errors.iter().any(|msg| msg.severity == "error"))
} }
@@ -49,6 +70,91 @@ fn no_evm_codegen_requested_complex() {
"the EVM codegen std json complext input fixture should build fine", "the EVM codegen std json complext input fixture should build fine",
); );
let output: SolcStandardJsonOutput = serde_json::from_str(&result.stdout).unwrap(); let output = to_solc_standard_json_output(&result.stdout);
assert!(!output.errors.iter().any(|msg| msg.severity == "error")) assert!(!output.errors.iter().any(|msg| msg.severity == "error"))
} }
#[test]
fn invalid_extra_arguments() {
struct TestCase<'a> {
arguments: Vec<&'a str>,
error_message: &'static str,
}
let default_heap_size_string = PolkaVMDefaultHeapMemorySize.to_string();
let default_stack_size_string = PolkaVMDefaultStackMemorySize.to_string();
let cases = vec![
TestCase {
arguments: vec![JSON_OPTION, "--heap-size", &default_heap_size_string],
error_message:
"Heap size must be specified in standard JSON input polkavm memory settings",
},
TestCase {
arguments: vec![JSON_OPTION, "--stack-size", &default_stack_size_string],
error_message:
"Stack size must be specified in standard JSON input polkavm memory settings",
},
TestCase {
arguments: vec![JSON_OPTION, "--optimization", "z"],
error_message: "LLVM optimizations must be specified in standard JSON input settings",
},
TestCase {
arguments: vec![JSON_OPTION, "-Oz"],
error_message: "LLVM optimizations must be specified in standard JSON input settings",
},
TestCase {
arguments: vec![JSON_OPTION, "--metadata-hash", "ipfs"],
error_message:
"`ipfs` metadata hash type is not supported. Please use `keccak256` instead",
},
TestCase {
arguments: vec![JSON_OPTION, "--metadata-hash", "keccak256"],
error_message: "Metadata hash mode must be specified in standard JSON input settings",
},
TestCase {
arguments: vec![JSON_OPTION, "--asm"],
error_message: "Cannot output assembly or binary outside of JSON in standard JSON mode",
},
TestCase {
arguments: vec![JSON_OPTION, "--bin"],
error_message: "Cannot output assembly or binary outside of JSON in standard JSON mode",
},
TestCase {
arguments: vec![JSON_OPTION, "--libraries", "myLib=0x0000"],
error_message: "Libraries must be passed via standard JSON input",
},
TestCase {
arguments: vec![JSON_OPTION, "--evm-version", "osaka"],
error_message: "EVM version must be passed via standard JSON input",
},
TestCase {
arguments: vec![JSON_OPTION, "--output-dir", "tmp"],
error_message: "Output directory cannot be used in standard JSON mode",
},
TestCase {
arguments: vec![JSON_OPTION, "--overwrite"],
error_message: "Overwriting flag cannot be used in standard JSON mode",
},
TestCase {
arguments: vec![JSON_OPTION, "--disable-solc-optimizer"],
error_message:
"Disabling the solc optimizer must be specified in standard JSON input settings",
},
TestCase {
arguments: vec![JSON_OPTION, "-g"],
error_message: "Debug info must be requested in standard JSON input polkavm settings",
},
TestCase {
arguments: vec![JSON_OPTION, "--llvm-arg=-riscv"],
error_message:
"LLVM arguments must be configured in standard JSON input polkavm settings",
},
];
for case in cases {
let result = execute_resolc_with_stdin_input(&case.arguments, STANDARD_JSON_CONTRACTS_PATH);
let output = to_solc_standard_json_output(&result.stdout);
assert_standard_json_errors_contain(&output, case.error_message);
}
}