Skip to content

evm: Load a whole CALLDATALOAD word directly from the input - #1776

Open
chfast wants to merge 1 commit into
masterfrom
evm/calldataload
Open

chfast wants to merge 1 commit into
masterfrom
evm/calldataload

Conversation

@chfast

@chfast chfast commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

CALLDATALOAD copied every word through a zeroed 32-byte buffer, which GCC and Clang compile to a memcpy call, also when the whole word is in the input. That is nearly every CALLDATALOAD: 98.5% of them in a profile of 1000 recent mainnet blocks. Now this case is checked first, on the index's 64-bit words instead of with a 256-bit comparison, and the word is loaded in place. The index past the input and the word that the input ends inside stay inline, in the unlikely branch.

Instructions per CALLDATALOAD in the Baseline interpreter built for the default x86-64-v2, dispatch included, measured with callgrind on a loop of each case:

  • GCC 15: whole word 59 -> 29, input ending inside the word 58 -> 47, index past the input 27 -> 19, 4-byte input 62 -> 51.
  • Clang 21: whole word 60 -> 29, input ending inside the word 59 -> 48, index past the input 28 -> 22, 4-byte input 63 -> 52.

The Baseline loop's code is 23 bytes smaller with GCC and 16 bytes larger with Clang. The library's code is smaller with both, by 64 and 48 bytes.

The new test loads the words at the end of the input. The input is followed by a non-zero byte, so a load that reads past its end gives a wrong result. The test also loads at the largest 64-bit index, where the index plus 32 overflows 64 bits, and at three indexes whose low word is inside the input while one of the high words is not zero.

@codspeed

codspeed Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will improve performance by 1.01%

1 warning

⚠️ Base and head ran on different runner types

Some benchmarks with significant performance changes were compared across different runner types, so the changes might not come from the code. Run base and head on the same runner type, or use CodSpeed Macro Runners for more stable results.

Learn more about Macro Runners

Open the report in CodSpeed to investigate

⚡ 4 improved benchmarks
✅ 895 untouched benchmarks

Performance Changes

Benchmark BASE HEAD Efficiency
⚡ test_calldataload[fork_Osaka-blockchain_test-zero_data_False-calldata_size_32-benchmark-gas-value_1M] 2.6 ms 2.5 ms +1.01%
⚡ test_calldataload[fork_Osaka-blockchain_test-zero_data_False-calldata_size_256-benchmark-gas-value_1M] 2.6 ms 2.6 ms +1.01%
⚡ test_calldataload[fork_Osaka-blockchain_test-zero_data_True-calldata_size_256-benchmark-gas-value_1M] 2.6 ms 2.6 ms +1.01%
⚡ test_calldataload[fork_Osaka-blockchain_test-zero_data_True-calldata_size_32-benchmark-gas-value_1M] 2.6 ms 2.6 ms +1.01%

Tip

Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.


Comparing evm/calldataload (37ba282) with master (ffb01f2)

Open in CodSpeed

@codecov

codecov Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.23%. Comparing base (ffb01f2) to head (37ba282).

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1776   +/-   ##
=======================================
  Coverage   98.23%   98.23%           
=======================================
  Files         185      185           
  Lines       17131    17150   +19     
  Branches     3912     3914    +2     
=======================================
+ Hits        16829    16848   +19     
  Misses        233      233           
  Partials       69       69           
Flag Coverage Δ
eest-develop-gmp 26.90% <42.85%> (-0.02%) ⬇️
eest-legacy 17.15% <35.71%> (-0.02%) ⬇️
eest-libsecp256k1 29.06% <42.85%> (-0.02%) ⬇️
eest-stable 84.26% <100.00%> (+<0.01%) ⬆️
evmone-unittests 94.10% <100.00%> (+<0.01%) ⬆️
riscv64 94.81% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
core 96.61% <100.00%> (+<0.01%) ⬆️
tooling 94.87% <ø> (ø)
tests 99.82% <100.00%> (+<0.01%) ⬆️
Files with missing lines Coverage Δ
lib/evmone/instructions.hpp 100.00% <100.00%> (ø)
test/unittests/evm_test.cpp 100.00% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

CALLDATALOAD copied every word through a zeroed 32-byte buffer, which GCC and Clang compile to a memcpy call. Check first, on the index's 64-bit words, whether the whole word is in the input and load it in place. The other cases stay inline in the unlikely branch.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant