diff --git a/.gitignore b/.gitignore index 8ca5f86e7e43..aafe93f97a0e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,7 @@ *.o *.obj *.pyc -__pycache__ +__pycache__/ # Precompiled Headers *.gch @@ -36,12 +36,15 @@ __pycache__ *.app # Build directory -/build* +/build*/ emscripten_build/ -/docs/_build +/docs/_build/ /docs/_static/robots.txt -/deps -/reports +/deps/ + +# Reports and benchmarks +/reports/ +/benchmarks/ # vim stuff [._]*.sw[a-p] diff --git a/scripts/common_cmdline.sh b/scripts/common_cmdline.sh index 5e32a76dc9ed..819a4b1c8eaf 100644 --- a/scripts/common_cmdline.sh +++ b/scripts/common_cmdline.sh @@ -169,3 +169,11 @@ function stripEmptyLines { sed -e '/^\s*$/d' } + +# Calculates the total size of bytecode of one or more contracts in bytes. +# Expects the output from `solc --bin` on standard input. +function bytecode_size { + local bytecode_chars + bytecode_chars=$(stripCLIDecorations | stripEmptyLines | wc --chars) + echo $(( bytecode_chars / 2 )) +} diff --git a/test/benchmarks/external-setup.sh b/test/benchmarks/external-setup.sh new file mode 100755 index 000000000000..2316d83d586e --- /dev/null +++ b/test/benchmarks/external-setup.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash + +#------------------------------------------------------------------------------ +# Downloads and configures external projects used for benchmarking by external.sh. +# +# By default the download location is the benchmarks/ dir at the repository root. +# A different directory can be provided via the BENCHMARK_DIR variable. +# +# Dependencies: foundry, git. +# ------------------------------------------------------------------------------ +# This file is part of solidity. +# +# solidity is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# solidity is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with solidity. If not, see +# +# (c) 2024 solidity contributors. +#------------------------------------------------------------------------------ + +set -euo pipefail + +repo_root=$(cd "$(dirname "$0")/../../" && pwd) +BENCHMARK_DIR="${BENCHMARK_DIR:-${repo_root}/benchmarks}" + +function neutralize_version_pragmas { + find . -name '*.sol' -type f -print0 | xargs -0 \ + sed -i -E -e 's/pragma solidity [^;]+;/pragma solidity *;/' +} + +function neutralize_via_ir { + sed -i '/^via_ir\s*=.*$/d' foundry.toml +} + +mkdir -p "$BENCHMARK_DIR" +cd "$BENCHMARK_DIR" + +if [[ ! -e openzeppelin/ ]]; then + git clone --depth=1 https://github.com/OpenZeppelin/openzeppelin-contracts openzeppelin/ --branch v5.0.2 + pushd openzeppelin/ + forge install + neutralize_via_ir + popd +else + echo "Skipped openzeppelin/. Already exists." +fi + +if [[ ! -e uniswap-v4/ ]]; then + git clone --single-branch https://github.com/Uniswap/v4-core uniswap-v4/ + pushd uniswap-v4/ + git checkout ae86975b058d386c9be24e8994236f662affacdb # branch main as of 2024-06-06 + forge install + neutralize_via_ir + popd +else + echo "Skipped uniswap-v4/. Already exists." +fi + +if [[ ! -e seaport/ ]]; then + git clone --single-branch https://github.com/ProjectOpenSea/seaport + pushd seaport/ + # NOTE: Can't select the tag with `git clone` because a branch of the same name exists. + git checkout tags/1.6 + forge install + neutralize_via_ir + neutralize_version_pragmas + popd +else + echo "Skipped seaport/. Already exists." +fi + +if [[ ! -e eigenlayer/ ]]; then + git clone --depth=1 https://github.com/Layr-Labs/eigenlayer-contracts eigenlayer/ --branch v0.3.0-holesky-rewards + pushd eigenlayer/ + neutralize_via_ir + forge install + popd +else + echo "Skipped eigenlayer/. Already exists." +fi + +if [[ ! -e sablier-v2/ ]]; then + git clone --depth=1 https://github.com/sablier-labs/v2-core sablier-v2/ --branch v1.1.2 + pushd sablier-v2/ + # NOTE: To avoid hard-coding dependency versions here we'd have to install them from npm + forge install --no-commit \ + foundry-rs/forge-std@v1.5.6 \ + OpenZeppelin/openzeppelin-contracts@v4.9.2 \ + PaulRBerg/prb-math@v4.0.2 \ + PaulRBerg/prb-test@v0.6.4 \ + evmcheb/solarray@a547630 \ + Vectorized/solady@v0.0.129 + cat < remappings.txt +@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ +forge-std/=lib/forge-std/ +@prb/math/=lib/prb-math/ +@prb/test/=lib/prb-test/ +solarray/=lib/solarray/ +solady/=lib/solady/ +EOF + neutralize_via_ir + popd +else + echo "Skipped sablier-v2/. Already exists." +fi diff --git a/test/benchmarks/external.sh b/test/benchmarks/external.sh new file mode 100755 index 000000000000..b37c9105d610 --- /dev/null +++ b/test/benchmarks/external.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash + +#------------------------------------------------------------------------------ +# Benchmarks a solc binary by compiling several external projects, with and without IR. +# +# The script expects each project to be already downloaded and set up by external-setup.sh. +# A different directory can be provided via the BENCHMARK_DIR variable. +# +# The script will by default attempt to use a solc from the default build directory, +# relative to the script directory. To use a different binary you can provide a different +# location of the build directory (via SOLIDITY_BUILD_DIR variable) or simply specify +# the full path to the binary as the script argument. +# +# Dependencies: foundry, time. +# ------------------------------------------------------------------------------ +# This file is part of solidity. +# +# solidity is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# solidity is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with solidity. If not, see +# +# (c) 2024 solidity contributors. +#------------------------------------------------------------------------------ + +set -euo pipefail + +repo_root=$(cd "$(dirname "$0")/../../" && pwd) +SOLIDITY_BUILD_DIR=${SOLIDITY_BUILD_DIR:-${repo_root}/build} +BENCHMARK_DIR="${BENCHMARK_DIR:-${repo_root}/benchmarks}" + +# shellcheck source=scripts/common.sh +source "${repo_root}/scripts/common.sh" +# shellcheck source=scripts/common_cmdline.sh +source "${repo_root}/scripts/common_cmdline.sh" + +(( $# <= 1 )) || fail "Too many arguments. Usage: external.sh []" + +solc="${1:-${SOLIDITY_BUILD_DIR}/solc/solc}" +command_available "$solc" --version + +function benchmark_project { + local pipeline="$1" + local project="$2" + [[ $pipeline == legacy || $pipeline == ir ]] || assertFail + + cd "$project" + local foundry_command=(forge build --use "$solc" --optimize --offline --no-cache) + [[ $pipeline == ir ]] && foundry_command+=(--via-ir) + local time_file="../time-and-status-${project}-${pipeline}.txt" + + # NOTE: The pipeline may fail with "Stack too deep" in some cases. That's fine. + # We note the exit code and will later show full output. + "$time_bin_path" \ + --output "$time_file" \ + --quiet \ + --format '%e s | %x' \ + "${foundry_command[@]}" \ + > /dev/null \ + 2> "../stderr-${project}-${pipeline}.log" || true + + printf '| %-20s | %8s | %21s |\n' \ + "$project" \ + "$pipeline" \ + "$(cat "$time_file")" + cd .. +} + +benchmarks=( + # Fastest ones first so that we get *some* output quickly + openzeppelin + uniswap-v4 + eigenlayer + seaport + sablier-v2 +) +time_bin_path=$(type -P time) + +mkdir -p "$BENCHMARK_DIR" +cd "$BENCHMARK_DIR" + +echo "| Project | Pipeline | Time | Exit code |" +echo "|----------------------|----------|----------:|----------:|" + +for project in "${benchmarks[@]}"; do + benchmark_project legacy "$project" + benchmark_project ir "$project" +done + +for project in "${benchmarks[@]}"; do + for pipeline in legacy ir; do + if [[ -s stderr-${project}-${pipeline}.log ]]; then + echo + echo "==================================" + echo "stderr for ${project} via ${pipeline}" + echo "==================================" + cat "stderr-${project}-${pipeline}.log" + fi + done +done diff --git a/test/benchmarks/run.sh b/test/benchmarks/local.sh similarity index 93% rename from test/benchmarks/run.sh rename to test/benchmarks/local.sh index 1784b3700bde..16a0c3afe32c 100755 --- a/test/benchmarks/run.sh +++ b/test/benchmarks/local.sh @@ -31,7 +31,7 @@ source "${REPO_ROOT}/scripts/common.sh" # shellcheck source=scripts/common_cmdline.sh source "${REPO_ROOT}/scripts/common_cmdline.sh" -(( $# <= 1 )) || fail "Too many arguments. Usage: run.sh []" +(( $# <= 1 )) || fail "Too many arguments. Usage: local.sh []" solc="${1:-${SOLIDITY_BUILD_DIR}/solc/solc}" command_available "$solc" --version @@ -45,12 +45,6 @@ function cleanup() { trap cleanup SIGINT SIGTERM -function bytecode_size { - local bytecode_chars - bytecode_chars=$(stripCLIDecorations | stripEmptyLines | wc --chars) - echo $(( bytecode_chars / 2 )) -} - function benchmark_contract { local pipeline="$1" local input_path="$2"