Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 96 additions & 13 deletions .github/workflows/integration-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,16 @@ jobs:
contents: read
pull-requests: write
packages: read
runs-on: solx-linux-amd64-self-hosted
container:
image: ghcr.io/nomicfoundation/solx-ci-runner@sha256:a3e9312d6442e028a4b9eed17ea597380ae86abf3ff4d45ba957d5a10a805790
options: -m 110g
# HACK (temporary, for testing PR #524 on large macOS runners): run the
# integration suite on GitHub's large macOS runners instead of the
# self-hosted Linux container. macOS runners can't use a job `container:`,
# so it is dropped and LLVM/solc are built from source on the runner (same
# build-llvm/build-solc actions used by test.yaml's macOS legs).
strategy:
fail-fast: false
matrix:
runner: [macos-15-large, macos-15-xlarge]
runs-on: ${{ matrix.runner }}
steps:
- name: Checkout PR
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
Expand Down Expand Up @@ -84,6 +90,83 @@ jobs:
with:
working-directory: temp-solx-main

# LLVM is built from source here (up to twice: PR + main baseline) and
# will exhaust the runner disk without this. Copied verbatim from
# test.yaml's macOS legs.
- name: Free disk space (macOS)
if: runner.os == 'macOS'
shell: bash
run: |
set -euo pipefail
echo "=== Before macOS cleanup ===" && df -h .

# 1. Simulator runtimes — stored on read-only APFS snapshot volumes,
# so plain `rm` fails. `simctl runtime delete all` unmounts them.
# May warn on already-deleted runtimes; that is harmless.
echo "--- Removing simulator runtimes ---"
xcrun simctl delete all 2>&1 || true
xcrun simctl runtime delete all 2>&1 || true

# 2. Xcode — remove every versioned copy EXCEPT the one that
# xcode-select points to (we need its toolchain for C/C++ builds).
# Active Xcode path looks like /Applications/Xcode_16.2.app/Contents/Developer.
echo "--- Removing inactive Xcode versions ---"
ACTIVE_XCODE="$(xcode-select -p 2>/dev/null | sed 's|/Contents/Developer/*$||' || true)"
ACTIVE_XCODE="${ACTIVE_XCODE%/}"
removed=0
if [ -z "${ACTIVE_XCODE}" ] || [[ "${ACTIVE_XCODE}" != /Applications/Xcode*.app ]]; then
echo " warning: active Xcode path '${ACTIVE_XCODE}' is not an Xcode app; skipping Xcode removal"
else
echo "Active Xcode (keeping): ${ACTIVE_XCODE}"
to_remove=()
for app in /Applications/Xcode_*.app; do
[ -d "$app" ] || continue
if [ "$app" = "$ACTIVE_XCODE" ]; then
echo " skip (active): $app"
else
echo " removing: $app"
to_remove+=("$app")
fi
done
# Each Xcode bundle is ~15 GB of small files; `rm -rf` is I/O-bound
# per inode, so run them concurrently (one worker per bundle).
# Soft-fail: a stray rm error shouldn't sink the whole job — rm's
# stderr will pinpoint the bad path above.
#
# `removed` reflects the attempt count, not per-bundle success:
# xargs returns non-zero if *any* child failed, so gating the
# count on xargs success would print "Removed 0" even after
# ~30 GB was freed. The count is off by the number of failed
# bundles — usually one in practice — which is still a much
# better signal than zero.
if [ "${#to_remove[@]}" -gt 0 ]; then
removed=${#to_remove[@]}
if ! printf '%s\0' "${to_remove[@]}" \
| xargs -0 -n1 -P "${#to_remove[@]}" sudo rm -rf; then
echo " warning: one or more Xcode removals failed (see rm stderr above)"
fi
fi
fi
echo "Removed ${removed} inactive Xcode version(s)"

# 3. Remaining large packages that this project never uses.
# Each path is removed individually so a missing path doesn't
# mask a real permission error on another.
echo "--- Removing unused SDKs and caches ---"
for dir in \
/Library/Developer/CoreSimulator \
/usr/local/lib/android \
"${RUNNER_TOOL_CACHE:-/Users/runner/hostedtoolcache}"; do
if [ -d "$dir" ]; then
echo " removing: $dir"
sudo rm -rf "$dir" || echo " warning: failed to remove $dir"
else
echo " not found (skipped): $dir"
fi
done

echo "=== After macOS cleanup ===" && df -h .

- name: Setup SFW
uses: ./.github/actions/setup-sfw

Expand Down Expand Up @@ -281,16 +364,16 @@ jobs:
id: solx-tester-report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: solx-tester-report
name: solx-tester-report-${{ matrix.runner }}
path: solx-tester-report.xlsx

- name: Post solx-tester report comment
if: always() && steps.solx-tester-report.outcome == 'success'
uses: mshick/add-pr-comment@ec328af66588ab8f77cdeb2c264f14aba45bbf59 # v3.12
with:
message-id: 'solx-tester-report'
message-id: 'solx-tester-report-${{ matrix.runner }}'
message: |
📊 **solx Tester Report**
📊 **solx Tester Report** (`${{ matrix.runner }}`)

➡️ [**Download**](${{ steps.solx-tester-report.outputs.artifact-url }})

Expand Down Expand Up @@ -323,16 +406,16 @@ jobs:
id: hardhat-report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: hardhat-report
name: hardhat-report-${{ matrix.runner }}
path: ./temp-hardhat-reports/hardhat-report.xlsx

- name: Post Hardhat report comment
if: always() && steps.hardhat-report.outcome == 'success'
uses: mshick/add-pr-comment@ec328af66588ab8f77cdeb2c264f14aba45bbf59 # v3.12
with:
message-id: 'hardhat-report'
message-id: 'hardhat-report-${{ matrix.runner }}'
message: |
📊 **Hardhat Projects Report**
📊 **Hardhat Projects Report** (`${{ matrix.runner }}`)

➡️ [**Download**](${{ steps.hardhat-report.outputs.artifact-url }})

Expand All @@ -346,16 +429,16 @@ jobs:
id: foundry-report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: foundry-report
name: foundry-report-${{ matrix.runner }}
path: ./temp-foundry-reports/foundry-report.xlsx

- name: Post Foundry report comment
if: always() && steps.foundry-report.outcome == 'success'
uses: mshick/add-pr-comment@ec328af66588ab8f77cdeb2c264f14aba45bbf59 # v3.12
with:
message-id: 'foundry-report'
message-id: 'foundry-report-${{ matrix.runner }}'
message: |
📊 **Foundry Projects Report**
📊 **Foundry Projects Report** (`${{ matrix.runner }}`)

➡️ [**Download**](${{ steps.foundry-report.outputs.artifact-url }})

Expand Down
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ features = ["http-rustls-tls", "test", "signing"]
# LLVM
[workspace.dependencies.inkwell]
git = "https://github.com/NomicFoundation/inkwell"
rev = "4f9f86e15f43dc8555b8b51d4dda01e60f756f22"
branch = "az-reset-option-occurrences"
default-features = false
features = [
"llvm21-1",
Expand Down
12 changes: 4 additions & 8 deletions solx-codegen-evm/src/codegen/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,14 +323,10 @@ impl<'ctx> Context<'ctx> {
&& self.optimizer.settings().is_fallback_to_size_enabled()
{
crate::codegen::IS_SIZE_FALLBACK
.compare_exchange(
false,
true,
std::sync::atomic::Ordering::Relaxed,
std::sync::atomic::Ordering::Relaxed,
)
.expect("Failed to set the global size fallback flag");
self.optimizer = Optimizer::new(OptimizerSettings::size());
.store(true, std::sync::atomic::Ordering::Relaxed);
let mut size_fallback_settings = OptimizerSettings::size();
size_fallback_settings.metadata_size = self.optimizer.settings().metadata_size;
self.optimizer = Optimizer::new(size_fallback_settings);
self.module = module_size_fallback;
for function in self.module.get_functions() {
Function::set_size_attributes(self.llvm, function);
Expand Down
11 changes: 5 additions & 6 deletions solx-codegen-evm/src/context/traits/evmla_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,12 @@ pub trait IEVMLAStack<'ctx>: IContext<'ctx> + Sized {
for position in 0..depth {
if let ShadowSlot::Memory(index) =
self.evmla().expect("Always exists").shadow_peek(position)
&& index != position
{
if index != position {
let value = self.evmla_stack_read(position)?;
self.evmla_mut()
.expect("Always exists")
.shadow_write(position, value);
}
let value = self.evmla_stack_read(position)?;
self.evmla_mut()
.expect("Always exists")
.shadow_write(position, value);
}
}
for position in 0..depth {
Expand Down
12 changes: 7 additions & 5 deletions solx-codegen-evm/src/target_machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@ impl TargetMachine {
/// `-evm-stack-region-offset <value>`
/// `-evm-metadata-size <value>`
///
/// LLVM command line options are process-global, so their occurrences are reset before
/// each parse: a unit never inherits an option set by a previous one in the same worker.
///
pub fn new(
optimizer_settings: &OptimizerSettings,
llvm_options: &[String],
) -> anyhow::Result<Self> {
let mut arguments = Vec::with_capacity(1 + llvm_options.len());
let mut arguments = Vec::with_capacity(4 + llvm_options.len());
arguments.push(Self::TARGET.to_string());
arguments.extend_from_slice(llvm_options);
if let Some(size) = optimizer_settings.spill_area_size {
Expand All @@ -44,10 +47,9 @@ impl TargetMachine {
if let Some(size) = optimizer_settings.metadata_size {
arguments.push(format!("-evm-metadata-size={size}"));
}
if arguments.len() > 1 {
let arguments: Vec<&str> = arguments.iter().map(|argument| argument.as_str()).collect();
inkwell::support::parse_command_line_options(arguments.as_slice(), "LLVM options");
}
let arguments: Vec<&str> = arguments.iter().map(|argument| argument.as_str()).collect();
inkwell::support::reset_all_option_occurrences();
inkwell::support::parse_command_line_options(arguments.as_slice(), "LLVM options");

let target_machine = inkwell::targets::Target::from_name(Self::TARGET.to_string().as_str())
.ok_or_else(|| anyhow::anyhow!("LLVM target machine `{}` not found", Self::TARGET))?
Expand Down
6 changes: 3 additions & 3 deletions solx-core/src/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,15 +244,15 @@ pub struct Arguments {
#[arg(long, help_heading = "Debug Options")]
pub llvm_debug_logging: bool,

/// Run this process recursively and provide JSON input to compile a single contract.
/// Run this process as a persistent worker compiling contracts fed via `stdin`.
/// Only for usage from within the compiler.
#[arg(long, hide = true)]
pub recursive_process: bool,
}

impl Arguments {
/// Expected argument count for `--recursive-process` (binary name + flag + value).
const RECURSIVE_PROCESS_MAX_ARGS: usize = 3;
/// Expected argument count for `--recursive-process` (binary name + flag).
const RECURSIVE_PROCESS_MAX_ARGS: usize = 2;

/// Expected argument count for `--version` (binary name + flag).
const VERSION_MAX_ARGS: usize = 2;
Expand Down
25 changes: 14 additions & 11 deletions solx-core/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,25 @@ impl<'arguments> Compiler<'arguments> {
}

///
/// Initialize the compiler runtime: rayon thread pool, LLVM stack trace, and
/// EVM target.
/// Initialize the compiler runtime: LLVM stack trace, EVM target, and
/// rayon thread pool.
///
/// If `arguments.recursive_process` is set, runs the subprocess handler and
/// If `arguments.recursive_process` is set, runs the worker subprocess loop and
/// returns `Ok(true)` -- the caller should return immediately.
/// Otherwise returns `Ok(false)`.
///
/// The rayon thread pool is built after the worker branch: workers compile
/// one translation unit at a time and never use it.
///
pub fn initialize(&self) -> anyhow::Result<bool> {
inkwell::support::enable_llvm_pretty_stack_trace();
solx_codegen_evm::initialize_target();

if self.arguments.recursive_process {
crate::run_subprocess()?;
return Ok(true);
}

let mut thread_pool_builder = rayon::ThreadPoolBuilder::new();
if let Some(threads) = self.arguments.threads {
thread_pool_builder = thread_pool_builder.num_threads(threads);
Expand All @@ -46,14 +57,6 @@ impl<'arguments> Compiler<'arguments> {
.build_global()
.expect("rayon thread pool parameters are valid");

inkwell::support::enable_llvm_pretty_stack_trace();
solx_codegen_evm::initialize_target();

if self.arguments.recursive_process {
crate::run_subprocess()?;
return Ok(true);
}

Ok(false)
}

Expand Down
6 changes: 4 additions & 2 deletions solx-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ pub use self::error::Error;
pub use self::error::stack_too_deep::StackTooDeep as StackTooDeepError;
pub use self::frontend::Frontend;
pub use self::process::EXECUTABLE;
pub use self::process::input::Input as EVMProcessInput;
pub use self::process::child::run as run_subprocess;
pub use self::process::job::Job as EVMProcessJob;
pub use self::process::output::Output as EVMProcessOutput;
pub use self::process::run as run_subprocess;
pub use self::process::pool::Pool as EVMProcessPool;
pub use self::process::session::Session as EVMProcessSession;
pub use self::project::Project;
pub use self::project::contract::Contract as ProjectContract;

Expand Down
Loading
Loading