From 06d2bdeb54a6205e7904471dbcd4d9299d2e403e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Mon, 22 Jul 2024 13:32:24 +0200 Subject: [PATCH 1/5] Object compiler tests for nested identical objects --- ...identical_subobjects_creation_deployed.yul | 107 +++++++++++ .../identical_subobjects_full_debug_info.yul | 114 ++++++++++++ .../identical_subobjects_no_debug_info.yul | 163 +++++++++++++++++ ...dentical_subobjects_partial_debug_info.yul | 151 ++++++++++++++++ ...bobjects_partial_debug_info_no_use_src.yul | 169 ++++++++++++++++++ ...cal_subobjects_with_subject_references.yul | 166 +++++++++++++++++ 6 files changed, 870 insertions(+) create mode 100644 test/libyul/objectCompiler/identical_subobjects_creation_deployed.yul create mode 100644 test/libyul/objectCompiler/identical_subobjects_full_debug_info.yul create mode 100644 test/libyul/objectCompiler/identical_subobjects_no_debug_info.yul create mode 100644 test/libyul/objectCompiler/identical_subobjects_partial_debug_info.yul create mode 100644 test/libyul/objectCompiler/identical_subobjects_partial_debug_info_no_use_src.yul create mode 100644 test/libyul/objectCompiler/identical_subobjects_with_subject_references.yul diff --git a/test/libyul/objectCompiler/identical_subobjects_creation_deployed.yul b/test/libyul/objectCompiler/identical_subobjects_creation_deployed.yul new file mode 100644 index 000000000000..5eb079db18d0 --- /dev/null +++ b/test/libyul/objectCompiler/identical_subobjects_creation_deployed.yul @@ -0,0 +1,107 @@ +// All objects have identical unoptimized code, but at 200 runs the creation objects will optimized +// differently from deployed objects. + +/// @use-src 0:"A.sol" +object "A" { + code { + function load(i) -> r { r := calldataload(i) } + let x := add(shl(255, 1), shl(127, not(0))) + sstore(load(x), load(1)) + } + + /// @use-src 0:"B.sol" + object "B_deployed" { + code { + function load(i) -> r { r := calldataload(i) } + let x := add(shl(255, 1), shl(127, not(0))) + sstore(load(x), load(1)) + } + + /// @use-src 0:"A.sol" + object "A" { + code { + function load(i) -> r { r := calldataload(i) } + let x := add(shl(255, 1), shl(127, not(0))) + sstore(load(x), load(1)) + } + + /// @use-src 0:"B.sol" + object "B_deployed" { + code { + function load(i) -> r { r := calldataload(i) } + let x := add(shl(255, 1), shl(127, not(0))) + sstore(load(x), load(1)) + } + } + + /// @use-src 0:"C.sol" + object "C" { + code { + function load(i) -> r { r := calldataload(i) } + let x := add(shl(255, 1), shl(127, not(0))) + sstore(load(x), load(1)) + } + } + } + } + + /// @use-src 0:"C.sol" + object "C_deployed" { + code { + function load(i) -> r { r := calldataload(i) } + let x := add(shl(255, 1), shl(127, not(0))) + sstore(load(x), load(1)) + } + } + + /// @use-src 0:"D.sol" + object "D" { + code { + function load(i) -> r { r := calldataload(i) } + let x := add(shl(255, 1), shl(127, not(0))) + sstore(load(x), load(1)) + } + } +} +// ==== +// optimizationPreset: full +// ---- +// Assembly: +// sstore(calldataload(sub(shl(0xff, 0x01), shl(0x7f, 0x01))), calldataload(0x01)) +// stop +// stop +// +// sub_0: assembly { +// sstore(calldataload(shl(0x7f, 0xffffffffffffffffffffffffffffffff)), calldataload(0x01)) +// stop +// stop +// +// sub_0: assembly { +// sstore(calldataload(sub(shl(0xff, 0x01), shl(0x7f, 0x01))), calldataload(0x01)) +// stop +// stop +// +// sub_0: assembly { +// sstore(calldataload(shl(0x7f, 0xffffffffffffffffffffffffffffffff)), calldataload(0x01)) +// stop +// } +// +// sub_1: assembly { +// sstore(calldataload(sub(shl(0xff, 0x01), shl(0x7f, 0x01))), calldataload(0x01)) +// stop +// } +// } +// } +// +// sub_1: assembly { +// sstore(calldataload(shl(0x7f, 0xffffffffffffffffffffffffffffffff)), calldataload(0x01)) +// stop +// } +// +// sub_2: assembly { +// sstore(calldataload(sub(shl(0xff, 0x01), shl(0x7f, 0x01))), calldataload(0x01)) +// stop +// } +// Bytecode: 6001356001607f1b600160ff1b03355500fe +// Opcodes: PUSH1 0x1 CALLDATALOAD PUSH1 0x1 PUSH1 0x7F SHL PUSH1 0x1 PUSH1 0xFF SHL SUB CALLDATALOAD SSTORE STOP INVALID +// SourceMappings: :::-:0;;;;;;;;;;; diff --git a/test/libyul/objectCompiler/identical_subobjects_full_debug_info.yul b/test/libyul/objectCompiler/identical_subobjects_full_debug_info.yul new file mode 100644 index 000000000000..74574be13c3f --- /dev/null +++ b/test/libyul/objectCompiler/identical_subobjects_full_debug_info.yul @@ -0,0 +1,114 @@ +// All objects have identical unoptimized code, but with different debug annotations. +// The optimized Yul will be identical only between objects that have identical debug info. + +/// @use-src 0:"A.sol" +object "A" { + code { + function load(i) -> r { r := calldataload(i) } + /// @src 0:10:20 + sstore(load(0), load(1)) + } + + /// @use-src 0:"B.sol" + object "B" { + code { + function load(i) -> r { r := calldataload(i) } + /// @src 0:10:20 + sstore(load(0), load(1)) + } + + /// @use-src 0:"A.sol" + object "A" { + code { + function load(i) -> r { r := calldataload(i) } + /// @src 0:10:20 + sstore(load(0), load(1)) + } + + /// @use-src 0:"B.sol" + object "B" { + code { + function load(i) -> r { r := calldataload(i) } + /// @src 0:10:20 + sstore(load(0), load(1)) + } + } + + /// @use-src 0:"C.sol" + object "C" { + code { + function load(i) -> r { r := calldataload(i) } + /// @src 0:10:20 + sstore(load(0), load(1)) + } + } + } + } + + /// @use-src 0:"C.sol" + object "C" { + code { + function load(i) -> r { r := calldataload(i) } + /// @src 0:10:20 + sstore(load(0), load(1)) + } + } + + /// @use-src 0:"D.sol" + object "D" { + code { + function load(i) -> r { r := calldataload(i) } + /// @src 0:10:20 + sstore(load(0), load(1)) + } + } +} +// ==== +// optimizationPreset: full +// ---- +// Assembly: +// /* "A.sol":10:20 */ +// sstore(calldataload(0x00), calldataload(0x01)) +// stop +// stop +// +// sub_0: assembly { +// /* "B.sol":10:20 */ +// sstore(calldataload(0x00), calldataload(0x01)) +// stop +// stop +// +// sub_0: assembly { +// /* "A.sol":10:20 */ +// sstore(calldataload(0x00), calldataload(0x01)) +// stop +// stop +// +// sub_0: assembly { +// /* "B.sol":10:20 */ +// sstore(calldataload(0x00), calldataload(0x01)) +// stop +// } +// +// sub_1: assembly { +// /* "C.sol":10:20 */ +// sstore(calldataload(0x00), calldataload(0x01)) +// stop +// } +// } +// } +// +// sub_1: assembly { +// /* "C.sol":10:20 */ +// sstore(calldataload(0x00), calldataload(0x01)) +// stop +// } +// +// sub_2: assembly { +// /* "D.sol":10:20 */ +// sstore(calldataload(0x00), calldataload(0x01)) +// stop +// } +// Bytecode: 6001355f355500fe +// Opcodes: PUSH1 0x1 CALLDATALOAD PUSH0 CALLDATALOAD SSTORE STOP INVALID +// SourceMappings: 10:10::-:0;-1:-1;10:10;-1:-1;10:10;-1:-1 diff --git a/test/libyul/objectCompiler/identical_subobjects_no_debug_info.yul b/test/libyul/objectCompiler/identical_subobjects_no_debug_info.yul new file mode 100644 index 000000000000..aa20ac6799b1 --- /dev/null +++ b/test/libyul/objectCompiler/identical_subobjects_no_debug_info.yul @@ -0,0 +1,163 @@ +// All objects have identical unoptimized code. +// After optimizations we should end up with identical code for all of them. + +object "A" { + code { + function load(i) -> r { r := calldataload(i) } + sstore(load(0), load(1)) + } + + object "B" { + code { + function load(i) -> r { r := calldataload(i) } + sstore(load(0), load(1)) + } + + object "A" { + code { + function load(i) -> r { r := calldataload(i) } + sstore(load(0), load(1)) + } + + object "B" { + code { + function load(i) -> r { r := calldataload(i) } + sstore(load(0), load(1)) + } + } + + object "C" { + code { + function load(i) -> r { r := calldataload(i) } + sstore(load(0), load(1)) + } + } + } + } + + object "C" { + code { + function load(i) -> r { r := calldataload(i) } + sstore(load(0), load(1)) + } + } + + object "D" { + code { + function load(i) -> r { r := calldataload(i) } + sstore(load(0), load(1)) + } + } +} +// ==== +// optimizationPreset: full +// ---- +// Assembly: +// /* "source":83:84 */ +// 0x01 +// /* "source":70:85 */ +// calldataload +// /* "source":66:67 */ +// 0x00 +// /* "source":53:68 */ +// calldataload +// /* "source":46:86 */ +// sstore +// /* "source":22:102 */ +// stop +// stop +// +// sub_0: assembly { +// /* "source":202:203 */ +// 0x01 +// /* "source":189:204 */ +// calldataload +// /* "source":185:186 */ +// 0x00 +// /* "source":172:187 */ +// calldataload +// /* "source":165:205 */ +// sstore +// /* "source":133:229 */ +// stop +// stop +// +// sub_0: assembly { +// /* "source":345:346 */ +// 0x01 +// /* "source":332:347 */ +// calldataload +// /* "source":328:329 */ +// 0x00 +// /* "source":315:330 */ +// calldataload +// /* "source":308:348 */ +// sstore +// /* "source":268:380 */ +// stop +// stop +// +// sub_0: assembly { +// /* "source":512:513 */ +// 0x01 +// /* "source":499:514 */ +// calldataload +// /* "source":495:496 */ +// 0x00 +// /* "source":482:497 */ +// calldataload +// /* "source":475:515 */ +// sstore +// /* "source":427:555 */ +// stop +// } +// +// sub_1: assembly { +// /* "source":701:702 */ +// 0x01 +// /* "source":688:703 */ +// calldataload +// /* "source":684:685 */ +// 0x00 +// /* "source":671:686 */ +// calldataload +// /* "source":664:704 */ +// sstore +// /* "source":616:744 */ +// stop +// } +// } +// } +// +// sub_1: assembly { +// /* "source":874:875 */ +// 0x01 +// /* "source":861:876 */ +// calldataload +// /* "source":857:858 */ +// 0x00 +// /* "source":844:859 */ +// calldataload +// /* "source":837:877 */ +// sstore +// /* "source":805:901 */ +// stop +// } +// +// sub_2: assembly { +// /* "source":1007:1008 */ +// 0x01 +// /* "source":994:1009 */ +// calldataload +// /* "source":990:991 */ +// 0x00 +// /* "source":977:992 */ +// calldataload +// /* "source":970:1010 */ +// sstore +// /* "source":938:1034 */ +// stop +// } +// Bytecode: 6001355f355500fe +// Opcodes: PUSH1 0x1 CALLDATALOAD PUSH0 CALLDATALOAD SSTORE STOP INVALID +// SourceMappings: 83:1:0:-:0;70:15;66:1;53:15;46:40;22:80 diff --git a/test/libyul/objectCompiler/identical_subobjects_partial_debug_info.yul b/test/libyul/objectCompiler/identical_subobjects_partial_debug_info.yul new file mode 100644 index 000000000000..9b63bdc1b12c --- /dev/null +++ b/test/libyul/objectCompiler/identical_subobjects_partial_debug_info.yul @@ -0,0 +1,151 @@ +// All objects have identical unoptimized code, but don't necessarily contain the same location comments. +// The optimized Yul will be identical only between objects that have identical debug info. +// Note that when @use-src is missing, the parser ignores location comments, so they do not become +// a part of the debug info. + +/// @use-src 0:"A.sol" +object "A" { + code { + function load(i) -> r { r := calldataload(i) } + /// @src 0:10:20 + sstore(load(0), load(1)) + } + + /// @use-src 0:"B.sol" + object "B" { + code { + function load(i) -> r { r := calldataload(i) } + /// @src 0:10:20 + sstore(load(0), load(1)) + } + + object "A" { + code { + function load(i) -> r { r := calldataload(i) } + sstore(load(0), load(1)) + } + + object "B" { + code { + function load(i) -> r { r := calldataload(i) } + sstore(load(0), load(1)) + } + } + + /// @use-src 0:"C.sol" + object "C" { + code { + function load(i) -> r { r := calldataload(i) } + /// @src 0:10:20 + sstore(load(0), load(1)) + } + } + } + } + + /// @use-src 0:"C.sol" + object "C" { + code { + function load(i) -> r { r := calldataload(i) } + /// @src 0:10:20 + sstore(load(0), load(1)) + } + } + + /// @use-src 0:"C.sol" + object "D" { + code { + function load(i) -> r { r := calldataload(i) } + sstore(load(0), load(1)) + } + } + + object "E" { + code { + function load(i) -> r { r := calldataload(i) } + /// @src 0:10:20 + sstore(load(0), load(1)) + } + } +} +// ==== +// optimizationPreset: full +// ---- +// Assembly: +// /* "A.sol":10:20 */ +// sstore(calldataload(0x00), calldataload(0x01)) +// stop +// stop +// +// sub_0: assembly { +// /* "B.sol":10:20 */ +// sstore(calldataload(0x00), calldataload(0x01)) +// stop +// stop +// +// sub_0: assembly { +// /* "source":621:622 */ +// 0x01 +// /* "source":608:623 */ +// calldataload +// /* "source":604:605 */ +// 0x00 +// /* "source":591:606 */ +// calldataload +// /* "source":584:624 */ +// sstore +// /* "source":544:656 */ +// stop +// stop +// +// sub_0: assembly { +// /* "source":788:789 */ +// 0x01 +// /* "source":775:790 */ +// calldataload +// /* "source":771:772 */ +// 0x00 +// /* "source":758:773 */ +// calldataload +// /* "source":751:791 */ +// sstore +// /* "source":703:831 */ +// stop +// } +// +// sub_1: assembly { +// /* "C.sol":10:20 */ +// sstore(calldataload(0x00), calldataload(0x01)) +// stop +// } +// } +// } +// +// sub_1: assembly { +// /* "C.sol":10:20 */ +// sstore(calldataload(0x00), calldataload(0x01)) +// stop +// } +// +// sub_2: assembly { +// sstore(calldataload(0x00), calldataload(0x01)) +// stop +// } +// +// sub_3: assembly { +// /* "source":1743:1744 */ +// 0x01 +// /* "source":1730:1745 */ +// calldataload +// /* "source":1726:1727 */ +// 0x00 +// /* "source":1713:1728 */ +// calldataload +// /* "source":1706:1746 */ +// sstore +// /* "source":1674:1770 */ +// stop +// } +// Bytecode: 6001355f355500fe +// Opcodes: PUSH1 0x1 CALLDATALOAD PUSH0 CALLDATALOAD SSTORE STOP INVALID +// SourceMappings: 10:10::-:0;-1:-1;10:10;-1:-1;10:10;-1:-1 diff --git a/test/libyul/objectCompiler/identical_subobjects_partial_debug_info_no_use_src.yul b/test/libyul/objectCompiler/identical_subobjects_partial_debug_info_no_use_src.yul new file mode 100644 index 000000000000..d288503babbf --- /dev/null +++ b/test/libyul/objectCompiler/identical_subobjects_partial_debug_info_no_use_src.yul @@ -0,0 +1,169 @@ +// All objects have identical unoptimized code, but don't necessarily contain the same location comments. +// After optimizations we should end up with identical code for all of them, because location +// comments will be ignored due to missing @use-src annotations and won't end up in the AST. + +object "A" { + code { + function load(i) -> r { r := calldataload(i) } + /// @src 0:10:20 + sstore(load(0), load(1)) + } + + object "B" { + code { + function load(i) -> r { r := calldataload(i) } + /// @src 0:10:20 + sstore(load(0), load(1)) + } + + object "A" { + code { + function load(i) -> r { r := calldataload(i) } + sstore(load(0), load(1)) + } + + object "B" { + code { + function load(i) -> r { r := calldataload(i) } + sstore(load(0), load(1)) + } + } + + object "C" { + code { + function load(i) -> r { r := calldataload(i) } + /// @src 0:10:20 + sstore(load(0), load(1)) + } + } + } + } + + object "C" { + code { + function load(i) -> r { r := calldataload(i) } + /// @src 0:10:20 + sstore(load(0), load(1)) + } + } + + object "D" { + code { + function load(i) -> r { r := calldataload(i) } + /// @src 0:10:20 + sstore(load(0), load(1)) + } + } +} +// ==== +// optimizationPreset: full +// ---- +// Assembly: +// /* "source":83:84 */ +// 0x01 +// /* "source":70:85 */ +// calldataload +// /* "source":66:67 */ +// 0x00 +// /* "source":53:68 */ +// calldataload +// /* "source":46:86 */ +// sstore +// /* "source":22:102 */ +// stop +// stop +// +// sub_0: assembly { +// /* "source":202:203 */ +// 0x01 +// /* "source":189:204 */ +// calldataload +// /* "source":185:186 */ +// 0x00 +// /* "source":172:187 */ +// calldataload +// /* "source":165:205 */ +// sstore +// /* "source":133:229 */ +// stop +// stop +// +// sub_0: assembly { +// /* "source":345:346 */ +// 0x01 +// /* "source":332:347 */ +// calldataload +// /* "source":328:329 */ +// 0x00 +// /* "source":315:330 */ +// calldataload +// /* "source":308:348 */ +// sstore +// /* "source":268:380 */ +// stop +// stop +// +// sub_0: assembly { +// /* "source":512:513 */ +// 0x01 +// /* "source":499:514 */ +// calldataload +// /* "source":495:496 */ +// 0x00 +// /* "source":482:497 */ +// calldataload +// /* "source":475:515 */ +// sstore +// /* "source":427:555 */ +// stop +// } +// +// sub_1: assembly { +// /* "source":701:702 */ +// 0x01 +// /* "source":688:703 */ +// calldataload +// /* "source":684:685 */ +// 0x00 +// /* "source":671:686 */ +// calldataload +// /* "source":664:704 */ +// sstore +// /* "source":616:744 */ +// stop +// } +// } +// } +// +// sub_1: assembly { +// /* "source":874:875 */ +// 0x01 +// /* "source":861:876 */ +// calldataload +// /* "source":857:858 */ +// 0x00 +// /* "source":844:859 */ +// calldataload +// /* "source":837:877 */ +// sstore +// /* "source":805:901 */ +// stop +// } +// +// sub_2: assembly { +// /* "source":1007:1008 */ +// 0x01 +// /* "source":994:1009 */ +// calldataload +// /* "source":990:991 */ +// 0x00 +// /* "source":977:992 */ +// calldataload +// /* "source":970:1010 */ +// sstore +// /* "source":938:1034 */ +// stop +// } +// Bytecode: 6001355f355500fe +// Opcodes: PUSH1 0x1 CALLDATALOAD PUSH0 CALLDATALOAD SSTORE STOP INVALID +// SourceMappings: 83:1:0:-:0;70:15;66:1;53:15;46:40;22:80 diff --git a/test/libyul/objectCompiler/identical_subobjects_with_subject_references.yul b/test/libyul/objectCompiler/identical_subobjects_with_subject_references.yul new file mode 100644 index 000000000000..e14b010083cb --- /dev/null +++ b/test/libyul/objectCompiler/identical_subobjects_with_subject_references.yul @@ -0,0 +1,166 @@ +// All objects have unoptimized code that's identical except for subobject and data object naming. +// The optimized Yul code does not depend on the content of subobjects and data objects, +// only on their names. EVM assembly, on the other hand, is not affected by names - it uses indices +// for subobjects and hashes for data objects. + +/// @use-src 0:"A.sol" +object "A" { + code { + function load(i) -> r { r := calldataload(i) } + sstore(load(0), load(dataoffset("B"))) + } + + /// @use-src 0:"B.sol" + object "B" { + code { + function load(i) -> r { r := calldataload(i) } + sstore(load(0), load(dataoffset("A"))) + } + + /// @use-src 0:"A.sol" + object "A" { + code { + function load(i) -> r { r := calldataload(i) } + sstore(load(0), load(dataoffset("B"))) + } + + /// @use-src 0:"B.sol" + object "B" { + code { + function load(i) -> r { r := calldataload(i) } + sstore(load(0), load(dataoffset("A"))) + } + + /// @use-src 0:"A.sol" + object "A" { + code { + function load(i) -> r { r := calldataload(i) } + sstore(load(0), load(dataoffset("B"))) + } + + data "B" "0xbb" + } + } + + /// @use-src 0:"C.sol" + object "C" { + code { + function load(i) -> r { r := calldataload(i) } + sstore(load(0), load(dataoffset("A"))) + } + + data "A" "0xaa" + } + + /// @use-src 0:"D.sol" + object "D" { + code { + function load(i) -> r { r := calldataload(i) } + sstore(load(0), load(dataoffset("A"))) + } + + data "A" "0xaaaaaa" + } + } + } + + /// @use-src 0:"C.sol" + object "C" { + code { + function load(i) -> r { r := calldataload(i) } + sstore(load(0), load(dataoffset("A"))) + } + + data "A" "0xaaaa" + } + + /// @use-src 0:"D.sol" + object "D" { + code { + function load(i) -> r { r := calldataload(i) } + sstore(load(0), load(dataoffset("B"))) + } + + data "B" "0xbbbb" + } + + /// @use-src 0:"E.sol" + object "E" { + code { + function load(i) -> r { r := calldataload(i) } + sstore(load(0), load(dataoffset("B"))) + } + + data "B" "0xbbbb" + } +} +// ==== +// optimizationPreset: full +// ---- +// Assembly: +// sstore(calldataload(0x00), calldataload(dataOffset(sub_0))) +// stop +// stop +// +// sub_0: assembly { +// sstore(calldataload(0x00), calldataload(dataOffset(sub_0))) +// stop +// stop +// +// sub_0: assembly { +// sstore(calldataload(0x00), calldataload(dataOffset(sub_0))) +// stop +// stop +// +// sub_0: assembly { +// sstore(calldataload(0x00), calldataload(dataOffset(sub_0))) +// stop +// stop +// +// sub_0: assembly { +// sstore(calldataload(0x00), calldataload(data_736ddcdd19b41ff3aa09bd89628fc69562c2e39bdb07c1971217d2e374ce6e27)) +// stop +// stop +// data_736ddcdd19b41ff3aa09bd89628fc69562c2e39bdb07c1971217d2e374ce6e27 30786262 +// } +// } +// +// sub_1: assembly { +// sstore(calldataload(0x00), calldataload(data_89b1fd0f9a40d0a598af5f997daf99fc3d5b98ef4eb429e81755ae0ee49e194e)) +// stop +// stop +// data_89b1fd0f9a40d0a598af5f997daf99fc3d5b98ef4eb429e81755ae0ee49e194e 30786161 +// } +// +// sub_2: assembly { +// sstore(calldataload(0x00), calldataload(data_d8bbdaa6a33092aa88c90c124bdeea5160229ede56dd7d897c2413f9ba7e4f85)) +// stop +// stop +// data_d8bbdaa6a33092aa88c90c124bdeea5160229ede56dd7d897c2413f9ba7e4f85 3078616161616161 +// } +// } +// } +// +// sub_1: assembly { +// sstore(calldataload(0x00), calldataload(data_79f4f313d7d500fd317e5283225bdfe6ea44da3fb73f1fdbf19929f3164bfc1a)) +// stop +// stop +// data_79f4f313d7d500fd317e5283225bdfe6ea44da3fb73f1fdbf19929f3164bfc1a 307861616161 +// } +// +// sub_2: assembly { +// sstore(calldataload(0x00), calldataload(data_257dd53638d790f0311f866044a2856def338f9a4d22fce0bdeed5b2d53851a9)) +// stop +// stop +// data_257dd53638d790f0311f866044a2856def338f9a4d22fce0bdeed5b2d53851a9 307862626262 +// } +// +// sub_3: assembly { +// sstore(calldataload(0x00), calldataload(data_257dd53638d790f0311f866044a2856def338f9a4d22fce0bdeed5b2d53851a9)) +// stop +// stop +// data_257dd53638d790f0311f866044a2856def338f9a4d22fce0bdeed5b2d53851a9 307862626262 +// } +// Bytecode: 6008355f355500fe6008355f355500fe6008355f355500fe6008355f355500fe6008355f355500fe30786262 +// Opcodes: PUSH1 0x8 CALLDATALOAD PUSH0 CALLDATALOAD SSTORE STOP INVALID PUSH1 0x8 CALLDATALOAD PUSH0 CALLDATALOAD SSTORE STOP INVALID PUSH1 0x8 CALLDATALOAD PUSH0 CALLDATALOAD SSTORE STOP INVALID PUSH1 0x8 CALLDATALOAD PUSH0 CALLDATALOAD SSTORE STOP INVALID PUSH1 0x8 CALLDATALOAD PUSH0 CALLDATALOAD SSTORE STOP INVALID ADDRESS PUSH25 0x62620000000000000000000000000000000000000000000000 +// SourceMappings: :::-:0;;;;; From 8f237deb99b2202672262d374259ce184ef648eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Wed, 12 Jun 2024 17:20:57 +0200 Subject: [PATCH 2/5] Object: Add missing `override` specifier to overridden methods --- libyul/Object.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libyul/Object.h b/libyul/Object.h index 41ca74e9f321..fe946f1bd102 100644 --- a/libyul/Object.h +++ b/libyul/Object.h @@ -99,9 +99,9 @@ struct Object: public ObjectNode AsmPrinter::TypePrinting printingMode = AsmPrinter::TypePrinting::Full, langutil::DebugInfoSelection const& _debugInfoSelection = langutil::DebugInfoSelection::Default(), langutil::CharStreamProvider const* _soliditySourceProvider = nullptr - ) const; + ) const override; /// @returns a compact JSON representation of the AST. - Json toJson() const; + Json toJson() const override; /// @returns the set of names of data objects accessible from within the code of /// this object, including the name of object itself /// Handles all names containing dots as reserved identifiers, not accessible as data. From 6654e87735d11c265de2e86553a96b865873a239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Wed, 12 Jun 2024 17:43:53 +0200 Subject: [PATCH 3/5] ObjectDebugData: Expose a helper for formatting the @use-src comment --- libyul/Object.cpp | 31 ++++++++++++++++++++----------- libyul/Object.h | 2 ++ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/libyul/Object.cpp b/libyul/Object.cpp index c4e7971ab4f7..57c783b83f39 100644 --- a/libyul/Object.cpp +++ b/libyul/Object.cpp @@ -53,16 +53,6 @@ std::string Object::toString( yulAssert(hasCode(), "No code"); yulAssert(debugData, "No debug data"); - std::string useSrcComment; - - if (debugData->sourceNames) - useSrcComment = - "/// @use-src " + - joinHumanReadable(ranges::views::transform(*debugData->sourceNames, [](auto&& _pair) { - return std::to_string(_pair.first) + ":" + util::escapeAndQuoteString(*_pair.second); - })) + - "\n"; - std::string inner = "code " + AsmPrinter( _printingMode, _dialect, @@ -74,7 +64,11 @@ std::string Object::toString( for (auto const& obj: subObjects) inner += "\n" + obj->toString(_dialect, _printingMode, _debugInfoSelection, _soliditySourceProvider); - return useSrcComment + "object \"" + name + "\" {\n" + indent(inner) + "\n}"; + return + debugData->formatUseSrcComment() + + "object \"" + name + "\" {\n" + + indent(inner) + "\n" + + "}"; } Json Data::toJson() const @@ -85,6 +79,21 @@ Json Data::toJson() const return ret; } +std::string ObjectDebugData::formatUseSrcComment() const +{ + if (!sourceNames) + return ""; + + auto formatIdNamePair = [](auto&& _pair) { + return std::to_string(_pair.first) + ":" + util::escapeAndQuoteString(*_pair.second); + }; + + std::string serializedSourceNames = joinHumanReadable( + ranges::views::transform(*sourceNames, formatIdNamePair) + ); + return "/// @use-src " + serializedSourceNames + "\n"; +} + Json Object::toJson() const { yulAssert(hasCode(), "No code"); diff --git a/libyul/Object.h b/libyul/Object.h index fe946f1bd102..fa9ef96eebc7 100644 --- a/libyul/Object.h +++ b/libyul/Object.h @@ -84,6 +84,8 @@ struct Data: public ObjectNode struct ObjectDebugData { std::optional sourceNames = {}; + + std::string formatUseSrcComment() const; }; From 27daf268e1f680662910957dca3f3a31c88545ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Wed, 31 Jul 2024 20:31:29 +0200 Subject: [PATCH 4/5] Caching of optimized IR --- Changelog.md | 1 + libsolidity/interface/CompilerStack.cpp | 7 +- libsolidity/interface/CompilerStack.h | 3 + libyul/CMakeLists.txt | 2 + libyul/ObjectOptimizer.cpp | 167 ++++++++++++++++++++++++ libyul/ObjectOptimizer.h | 95 ++++++++++++++ libyul/YulStack.cpp | 112 ++++++---------- libyul/YulStack.h | 13 +- 8 files changed, 319 insertions(+), 81 deletions(-) create mode 100644 libyul/ObjectOptimizer.cpp create mode 100644 libyul/ObjectOptimizer.h diff --git a/Changelog.md b/Changelog.md index 2a1249c98a27..034be57733bc 100644 --- a/Changelog.md +++ b/Changelog.md @@ -19,6 +19,7 @@ Compiler Features: * Standard JSON Interface: Do not perform IR optimization when only unoptimized IR is requested. * Standard JSON Interface: Add ``transientStorageLayout`` output. * Yul: Drop the deprecated typed Yul dialect that was only accessible via ``--yul`` in the CLI. + * Yul Optimizer: Caching of optimized IR to speed up optimization of contracts with bytecode dependencies. * Yul Optimizer: The optimizer now treats some previously unrecognized identical literals as identical. * Commandline Interface: Allow the use of ``--asm-json`` output option in assembler mode to export EVM assembly of the contracts in JSON format. diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 0ea7131db140..1f432db40fca 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -111,6 +111,7 @@ static int g_compilerStackCounts = 0; CompilerStack::CompilerStack(ReadCallback::Callback _readFile): m_readFile{std::move(_readFile)}, + m_objectOptimizer(std::make_shared()), m_errorReporter{m_errorList} { // Because TypeProvider is currently a singleton API, we must ensure that @@ -1493,7 +1494,8 @@ void CompilerStack::generateIR(ContractDefinition const& _contract, bool _unopti m_eofVersion, YulStack::Language::StrictAssembly, m_optimiserSettings, - m_debugInfoSelection + m_debugInfoSelection, + m_objectOptimizer ); bool yulAnalysisSuccessful = stack.parseAndAnalyze("", compiledContract.yulIR); solAssert( @@ -1530,7 +1532,8 @@ void CompilerStack::generateEVMFromIR(ContractDefinition const& _contract) m_eofVersion, yul::YulStack::Language::StrictAssembly, m_optimiserSettings, - m_debugInfoSelection + m_debugInfoSelection, + m_objectOptimizer ); bool analysisSuccessful = stack.parseAndAnalyze("", compiledContract.yulIROptimized); solAssert(analysisSuccessful); diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 159a14d8d84d..6ca792296f27 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -49,6 +49,8 @@ #include #include +#include + #include #include #include @@ -552,6 +554,7 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac std::shared_ptr m_globalContext; std::vector m_sourceOrder; std::map m_contracts; + std::shared_ptr m_objectOptimizer; langutil::ErrorList m_errorList; langutil::ErrorReporter m_errorReporter; diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index ee78d525dfc7..dbf3bfdcd6a1 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -29,6 +29,8 @@ add_library(yul FunctionReferenceResolver.h Object.cpp Object.h + ObjectOptimizer.cpp + ObjectOptimizer.h ObjectParser.cpp ObjectParser.h Scope.cpp diff --git a/libyul/ObjectOptimizer.cpp b/libyul/ObjectOptimizer.cpp new file mode 100644 index 000000000000..eaaac97d56a4 --- /dev/null +++ b/libyul/ObjectOptimizer.cpp @@ -0,0 +1,167 @@ +/* + 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 . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include +#include + +using namespace solidity; +using namespace solidity::langutil; +using namespace solidity::util; +using namespace solidity::yul; + + +Dialect const& yul::languageToDialect(Language _language, EVMVersion _version) +{ + switch (_language) + { + case Language::Assembly: + case Language::StrictAssembly: + return EVMDialect::strictAssemblyForEVMObjects(_version); + } + util::unreachable(); +} + +void ObjectOptimizer::optimize(Object& _object, Settings const& _settings) +{ + yulAssert(_object.subId == std::numeric_limits::max(), "Not a top-level object."); + + optimize(_object, _settings, true /* _isCreation */); +} + +void ObjectOptimizer::optimize(Object& _object, Settings const& _settings, bool _isCreation) +{ + yulAssert(_object.code()); + yulAssert(_object.debugData); + + for (auto& subNode: _object.subObjects) + if (auto subObject = dynamic_cast(subNode.get())) + { + bool isCreation = !boost::ends_with(subObject->name, "_deployed"); + optimize( + *subObject, + _settings, + isCreation + ); + } + + Dialect const& dialect = languageToDialect(_settings.language, _settings.evmVersion); + std::unique_ptr meter; + if (EVMDialect const* evmDialect = dynamic_cast(&dialect)) + meter = std::make_unique(*evmDialect, _isCreation, _settings.expectedExecutionsPerDeployment); + + std::optional cacheKey = calculateCacheKey(_object.code()->root(), *_object.debugData, _settings, _isCreation); + if (cacheKey.has_value() && m_cachedObjects.count(*cacheKey) != 0) + { + overwriteWithOptimizedObject(*cacheKey, _object); + return; + } + + OptimiserSuite::run( + dialect, + meter.get(), + _object, + _settings.optimizeStackAllocation, + _settings.yulOptimiserSteps, + _settings.yulOptimiserCleanupSteps, + _isCreation ? std::nullopt : std::make_optional(_settings.expectedExecutionsPerDeployment), + {} + ); + + if (cacheKey.has_value()) + storeOptimizedObject(*cacheKey, _object, dialect); +} + +void ObjectOptimizer::storeOptimizedObject(util::h256 _cacheKey, Object const& _optimizedObject, Dialect const& _dialect) +{ + m_cachedObjects[_cacheKey] = CachedObject{ + std::make_shared(ASTCopier{}.translate(_optimizedObject.code()->root())), + &_dialect, + }; +} + +void ObjectOptimizer::overwriteWithOptimizedObject(util::h256 _cacheKey, Object& _object) const +{ + yulAssert(m_cachedObjects.count(_cacheKey) != 0); + CachedObject const& cachedObject = m_cachedObjects.at(_cacheKey); + + yulAssert(cachedObject.optimizedAST); + _object.setCode(std::make_shared(ASTCopier{}.translate(*cachedObject.optimizedAST))); + yulAssert(_object.code()); + + // There's no point in caching AnalysisInfo because it references AST nodes. It can't be shared + // by multiple ASTs and it's easier to recalculate it than properly clone it. + yulAssert(cachedObject.dialect); + _object.analysisInfo = std::make_shared( + AsmAnalyzer::analyzeStrictAssertCorrect( + *cachedObject.dialect, + _object + ) + ); + + // NOTE: Source name index is included in the key so it must be identical. No need to store and restore it. +} + +std::optional ObjectOptimizer::calculateCacheKey( + Block const& _ast, + ObjectDebugData const& _debugData, + Settings const& _settings, + bool _isCreation +) +{ + AsmPrinter asmPrinter( + AsmPrinter::TypePrinting::OmitDefault, + languageToDialect(_settings.language, _settings.evmVersion), + _debugData.sourceNames, + DebugInfoSelection::All() + ); + + bytes rawKey; + // NOTE: AsmPrinter never prints nativeLocations included in debug data, so ASTs differing only + // in that regard are considered equal here. This is fine because the optimizer does not keep + // them up to date across AST transformations anyway so in any use where they need to be reliable, + // we just regenerate them by reparsing the object. + rawKey += keccak256(asmPrinter(_ast)).asBytes(); + rawKey += keccak256(_debugData.formatUseSrcComment()).asBytes(); + rawKey += h256(u256(_settings.language)).asBytes(); + rawKey += FixedHash<1>(uint8_t(_settings.optimizeStackAllocation ? 0 : 1)).asBytes(); + rawKey += h256(u256(_settings.expectedExecutionsPerDeployment)).asBytes(); + rawKey += FixedHash<1>(uint8_t(_isCreation ? 0 : 1)).asBytes(); + rawKey += keccak256(_settings.evmVersion.name()).asBytes(); + rawKey += keccak256(_settings.yulOptimiserSteps).asBytes(); + rawKey += keccak256(_settings.yulOptimiserCleanupSteps).asBytes(); + + return h256(keccak256(rawKey)); +} diff --git a/libyul/ObjectOptimizer.h b/libyul/ObjectOptimizer.h new file mode 100644 index 000000000000..89e5739be7da --- /dev/null +++ b/libyul/ObjectOptimizer.h @@ -0,0 +1,95 @@ +/* + 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 . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#include +#include + +#include + +#include + +#include +#include +#include + +namespace solidity::yul +{ + +enum class Language +{ + Assembly, + StrictAssembly, +}; + +Dialect const& languageToDialect(Language _language, langutil::EVMVersion _version); + +/// Encapsulates logic for applying @a yul::OptimiserSuite to a whole hierarchy of Yul objects. +/// Also, acts as a transparent cache for optimized objects. +/// +/// The cache is designed to allow sharing its instances widely across the compiler, without the +/// need to invalidate entries due to changing settings or context. +/// Caching is performed at the granularity of individual ASTs rather than whole object trees, +/// which means that reuse is possible even within a single hierarchy, e.g. when creation and +/// deployed objects have common dependencies. +class ObjectOptimizer +{ +public: + /// Optimization settings and context information. + /// This information becomes a part of the cache key and, together with the object content, + /// must uniquely determine the result of optimization. + struct Settings + { + Language language; + langutil::EVMVersion evmVersion; + bool optimizeStackAllocation; + std::string yulOptimiserSteps; + std::string yulOptimiserCleanupSteps; + size_t expectedExecutionsPerDeployment; + }; + + /// Recursively optimizes a Yul object with given settings, reusing cached ASTs where possible + /// or caching the result otherwise. The object is modified in-place. + /// Automatically accounts for the difference between creation and deployed objects. + /// @warning Does not ensure that nativeLocations in the resulting AST match the optimized code. + void optimize(Object& _object, Settings const& _settings); + +private: + struct CachedObject + { + std::shared_ptr optimizedAST; + Dialect const* dialect; + }; + + void optimize(Object& _object, Settings const& _settings, bool _isCreation); + + void storeOptimizedObject(util::h256 _cacheKey, Object const& _optimizedObject, Dialect const& _dialect); + void overwriteWithOptimizedObject(util::h256 _cacheKey, Object& _object) const; + + static std::optional calculateCacheKey( + Block const& _ast, + ObjectDebugData const& _debugData, + Settings const& _settings, + bool _isCreation + ); + + std::map m_cachedObjects; +}; + +} diff --git a/libyul/YulStack.cpp b/libyul/YulStack.cpp index 8166ed3cfda6..eac84ae3a13e 100644 --- a/libyul/YulStack.cpp +++ b/libyul/YulStack.cpp @@ -24,14 +24,12 @@ #include #include #include -#include #include #include #include #include #include #include -#include #include @@ -43,22 +41,6 @@ using namespace solidity::yul; using namespace solidity::langutil; using namespace solidity::util; -namespace -{ -Dialect const& languageToDialect(YulStack::Language _language, EVMVersion _version) -{ - switch (_language) - { - case YulStack::Language::Assembly: - case YulStack::Language::StrictAssembly: - return EVMDialect::strictAssemblyForEVMObjects(_version); - } - yulAssert(false); - util::unreachable(); -} - -} - CharStream const& YulStack::charStream(std::string const& _sourceName) const { yulAssert(m_charStream, ""); @@ -114,8 +96,44 @@ void YulStack::optimize() ) return; + auto [optimizeStackAllocation, yulOptimiserSteps, yulOptimiserCleanupSteps] = [&]() -> std::tuple + { + if (!m_optimiserSettings.runYulOptimiser) + { + // Yul optimizer disabled, but empty sequence (:) explicitly provided + if (OptimiserSuite::isEmptyOptimizerSequence(m_optimiserSettings.yulOptimiserSteps + ":" + m_optimiserSettings.yulOptimiserCleanupSteps)) + return std::make_tuple(true, "", ""); + // Yul optimizer disabled, and no sequence explicitly provided (assumes default sequence) + else + { + yulAssert( + m_optimiserSettings.yulOptimiserSteps == OptimiserSettings::DefaultYulOptimiserSteps && + m_optimiserSettings.yulOptimiserCleanupSteps == OptimiserSettings::DefaultYulOptimiserCleanupSteps + ); + // Defaults are the minimum necessary to avoid running into "Stack too deep" constantly. + return std::make_tuple(true, "u", ""); + } + } + return std::make_tuple( + m_optimiserSettings.optimizeStackAllocation, + m_optimiserSettings.yulOptimiserSteps, + m_optimiserSettings.yulOptimiserCleanupSteps + ); + }(); + m_stackState = Parsed; - optimize(*m_parserResult, true); + solAssert(m_objectOptimizer); + m_objectOptimizer->optimize( + *m_parserResult, + ObjectOptimizer::Settings{ + m_language, + m_evmVersion, + optimizeStackAllocation, + yulOptimiserSteps, + yulOptimiserCleanupSteps, + m_optimiserSettings.expectedExecutionsPerDeployment + } + ); // Optimizer does not maintain correct native source locations in the AST. // We can work around it by regenerating the AST from scratch from optimized IR. @@ -186,60 +204,6 @@ void YulStack::compileEVM(AbstractAssembly& _assembly, bool _optimize) const EVMObjectCompiler::compile(*m_parserResult, _assembly, *dialect, _optimize, m_eofVersion); } -void YulStack::optimize(Object& _object, bool _isCreation) -{ - yulAssert(_object.hasCode(), ""); - yulAssert(_object.analysisInfo, ""); - for (auto& subNode: _object.subObjects) - if (auto subObject = dynamic_cast(subNode.get())) - { - bool isCreation = !boost::ends_with(subObject->name, "_deployed"); - optimize(*subObject, isCreation); - } - - Dialect const& dialect = languageToDialect(m_language, m_evmVersion); - std::unique_ptr meter; - if (EVMDialect const* evmDialect = dynamic_cast(&dialect)) - meter = std::make_unique(*evmDialect, _isCreation, m_optimiserSettings.expectedExecutionsPerDeployment); - - auto [optimizeStackAllocation, yulOptimiserSteps, yulOptimiserCleanupSteps] = [&]() -> std::tuple - { - if (!m_optimiserSettings.runYulOptimiser) - { - // Yul optimizer disabled, but empty sequence (:) explicitly provided - if (OptimiserSuite::isEmptyOptimizerSequence(m_optimiserSettings.yulOptimiserSteps + ":" + m_optimiserSettings.yulOptimiserCleanupSteps)) - return std::make_tuple(true, "", ""); - // Yul optimizer disabled, and no sequence explicitly provided (assumes default sequence) - else - { - yulAssert( - m_optimiserSettings.yulOptimiserSteps == OptimiserSettings::DefaultYulOptimiserSteps && - m_optimiserSettings.yulOptimiserCleanupSteps == OptimiserSettings::DefaultYulOptimiserCleanupSteps - ); - return std::make_tuple(true, "u", ""); - } - - } - return std::make_tuple( - m_optimiserSettings.optimizeStackAllocation, - m_optimiserSettings.yulOptimiserSteps, - m_optimiserSettings.yulOptimiserCleanupSteps - ); - }(); - - OptimiserSuite::run( - dialect, - meter.get(), - _object, - // Defaults are the minimum necessary to avoid running into "Stack too deep" constantly. - optimizeStackAllocation, - yulOptimiserSteps, - yulOptimiserCleanupSteps, - _isCreation ? std::nullopt : std::make_optional(m_optimiserSettings.expectedExecutionsPerDeployment), - {} - ); -} - void YulStack::reparse() { yulAssert(m_parserResult); @@ -250,7 +214,7 @@ void YulStack::reparse() // are not stored in the AST and the other info that is (location, AST ID, etc) will still be present. std::string source = print(nullptr /* _soliditySourceProvider */); - YulStack cleanStack(m_evmVersion, m_eofVersion, m_language, m_optimiserSettings, m_debugInfoSelection); + YulStack cleanStack(m_evmVersion, m_eofVersion, m_language, m_optimiserSettings, m_debugInfoSelection, m_objectOptimizer); bool reanalysisSuccessful = cleanStack.parseAndAnalyze(m_charStream->name(), source); yulAssert( reanalysisSuccessful, diff --git a/libyul/YulStack.h b/libyul/YulStack.h index b09fa7975859..69c0f01656f9 100644 --- a/libyul/YulStack.h +++ b/libyul/YulStack.h @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -65,7 +66,7 @@ struct MachineAssemblyObject class YulStack: public langutil::CharStreamProvider { public: - enum class Language { Assembly, StrictAssembly }; + using Language = yul::Language; enum class Machine { EVM }; enum State { Empty, @@ -88,14 +89,16 @@ class YulStack: public langutil::CharStreamProvider std::optional _eofVersion, Language _language, solidity::frontend::OptimiserSettings _optimiserSettings, - langutil::DebugInfoSelection const& _debugInfoSelection + langutil::DebugInfoSelection const& _debugInfoSelection, + std::shared_ptr _objectOptimizer = nullptr ): m_language(_language), m_evmVersion(_evmVersion), m_eofVersion(_eofVersion), m_optimiserSettings(std::move(_optimiserSettings)), m_debugInfoSelection(_debugInfoSelection), - m_errorReporter(m_errors) + m_errorReporter(m_errors), + m_objectOptimizer(_objectOptimizer ? std::move(_objectOptimizer) : std::make_shared()) {} /// @returns the char stream used during parsing @@ -150,8 +153,6 @@ class YulStack: public langutil::CharStreamProvider void compileEVM(yul::AbstractAssembly& _assembly, bool _optimize) const; - void optimize(yul::Object& _object, bool _isCreation); - /// Prints the Yul object stored internally and parses it again. /// This ensures that the debug info in the AST matches the source that printing would produce /// rather than the initial source. @@ -173,6 +174,8 @@ class YulStack: public langutil::CharStreamProvider std::shared_ptr m_parserResult; langutil::ErrorList m_errors; langutil::ErrorReporter m_errorReporter; + + std::shared_ptr m_objectOptimizer; }; } From 5c7fc0409d3f7d579571eedc8af18945cd08312b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Tue, 30 Jul 2024 15:07:04 +0200 Subject: [PATCH 5/5] OptimizedIRCachingTest --- libsolidity/interface/CompilerStack.h | 2 + libyul/ObjectOptimizer.h | 2 + test/CMakeLists.txt | 2 + test/InteractiveTests.h | 2 + test/libsolidity/OptimizedIRCachingTest.cpp | 50 +++++++++++++++++ test/libsolidity/OptimizedIRCachingTest.h | 54 +++++++++++++++++++ .../bytecode_dependency_creation.sol | 7 +++ ...tecode_dependency_creation_and_runtime.sol | 12 +++++ ...dependency_creation_and_runtime_shared.sol | 11 ++++ .../bytecode_dependency_runtime.sol | 9 ++++ ...ependency_shared_by_multiple_contracts.sol | 19 +++++++ .../optimizedIRCaching/multiple_contracts.sol | 5 ++ .../optimizedIRCaching/no_contracts.sol | 2 + .../optimizedIRCaching/single_contract.sol | 3 ++ test/tools/CMakeLists.txt | 2 + 15 files changed, 182 insertions(+) create mode 100644 test/libsolidity/OptimizedIRCachingTest.cpp create mode 100644 test/libsolidity/OptimizedIRCachingTest.h create mode 100644 test/libsolidity/optimizedIRCaching/bytecode_dependency_creation.sol create mode 100644 test/libsolidity/optimizedIRCaching/bytecode_dependency_creation_and_runtime.sol create mode 100644 test/libsolidity/optimizedIRCaching/bytecode_dependency_creation_and_runtime_shared.sol create mode 100644 test/libsolidity/optimizedIRCaching/bytecode_dependency_runtime.sol create mode 100644 test/libsolidity/optimizedIRCaching/bytecode_dependency_shared_by_multiple_contracts.sol create mode 100644 test/libsolidity/optimizedIRCaching/multiple_contracts.sol create mode 100644 test/libsolidity/optimizedIRCaching/no_contracts.sol create mode 100644 test/libsolidity/optimizedIRCaching/single_contract.sol diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 6ca792296f27..25863b6d0690 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -381,6 +381,8 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac return VersionIsRelease ? MetadataFormat::WithReleaseVersionTag : MetadataFormat::WithPrereleaseVersionTag; } + yul::ObjectOptimizer const& objectOptimizer() const { return *m_objectOptimizer; } + private: /// The state per source unit. Filled gradually during parsing. struct Source diff --git a/libyul/ObjectOptimizer.h b/libyul/ObjectOptimizer.h index 89e5739be7da..e570d69c4b8a 100644 --- a/libyul/ObjectOptimizer.h +++ b/libyul/ObjectOptimizer.h @@ -70,6 +70,8 @@ class ObjectOptimizer /// @warning Does not ensure that nativeLocations in the resulting AST match the optimized code. void optimize(Object& _object, Settings const& _settings); + size_t size() const { return m_cachedObjects.size(); } + private: struct CachedObject { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0d19d77960d2..5308a55f7826 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -88,6 +88,8 @@ set(libsolidity_sources libsolidity/MemoryGuardTest.h libsolidity/NatspecJSONTest.cpp libsolidity/NatspecJSONTest.h + libsolidity/OptimizedIRCachingTest.cpp + libsolidity/OptimizedIRCachingTest.h libsolidity/SemanticTest.cpp libsolidity/SemanticTest.h libsolidity/SemVerMatcher.cpp diff --git a/test/InteractiveTests.h b/test/InteractiveTests.h index 42e20d7e3919..4c2077576cfa 100644 --- a/test/InteractiveTests.h +++ b/test/InteractiveTests.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -82,6 +83,7 @@ Testsuite const g_interactiveTestsuites[] = { {"Memory Guard", "libsolidity", "memoryGuardTests", false, false, &MemoryGuardTest::create}, {"AST Properties", "libsolidity", "astPropertyTests", false, false, &ASTPropertyTest::create}, {"Function Dependency Graph", "libsolidity", "functionDependencyGraphTests", false, false, &FunctionDependencyGraphTest::create}, + {"Optimized IR Caching", "libsolidity", "optimizedIRCaching", false, false, &OptimizedIRCachingTest::create}, }; } diff --git a/test/libsolidity/OptimizedIRCachingTest.cpp b/test/libsolidity/OptimizedIRCachingTest.cpp new file mode 100644 index 000000000000..d1e735eeb7b9 --- /dev/null +++ b/test/libsolidity/OptimizedIRCachingTest.cpp @@ -0,0 +1,50 @@ +/* + 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 . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include +#include + +#include + +#include + +using namespace solidity::util; +using namespace solidity::langutil; +using namespace solidity::frontend; +using namespace solidity::frontend::test; + +void OptimizedIRCachingTest::setupCompiler(CompilerStack& _compiler) +{ + AnalysisFramework::setupCompiler(_compiler); + _compiler.setOptimiserSettings(true); + _compiler.setViaIR(true); +} + +TestCase::TestResult OptimizedIRCachingTest::run(std::ostream& _stream, std::string const& _linePrefix, bool _formatted) +{ + soltestAssert(compiler().objectOptimizer().size() == 0); + + if (!runFramework(m_source, PipelineStage::Compilation)) + { + printPrefixed(_stream, formatErrors(filteredErrors(), _formatted), _linePrefix); + return TestResult::FatalError; + } + + m_obtainedResult = "cachedObjects: " + toString(compiler().objectOptimizer().size()) + "\n"; + return checkResult(_stream, _linePrefix, _formatted); +} diff --git a/test/libsolidity/OptimizedIRCachingTest.h b/test/libsolidity/OptimizedIRCachingTest.h new file mode 100644 index 000000000000..8681346ca884 --- /dev/null +++ b/test/libsolidity/OptimizedIRCachingTest.h @@ -0,0 +1,54 @@ +/* + 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 . +*/ +// SPDX-License-Identifier: GPL-3.0 +/** + * Unit tests for the optimized IR caching in CompilerStack. + */ + +#pragma once + +#include +#include + +#include +#include + +namespace solidity::frontend::test +{ + +class OptimizedIRCachingTest: public AnalysisFramework, public EVMVersionRestrictedTestCase +{ +public: + OptimizedIRCachingTest(std::string const& _filename): + EVMVersionRestrictedTestCase(_filename) + { + m_source = m_reader.source(); + m_expectation = m_reader.simpleExpectations(); + } + + static std::unique_ptr create(Config const& _config) + { + return std::make_unique(_config.filename); + } + + TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool _formatted = false) override; + +protected: + void setupCompiler(CompilerStack& _compiler) override; +}; + +} diff --git a/test/libsolidity/optimizedIRCaching/bytecode_dependency_creation.sol b/test/libsolidity/optimizedIRCaching/bytecode_dependency_creation.sol new file mode 100644 index 000000000000..6e603ee731a7 --- /dev/null +++ b/test/libsolidity/optimizedIRCaching/bytecode_dependency_creation.sol @@ -0,0 +1,7 @@ +contract A {} + +contract C { + A a = new A(); +} +// ---- +// cachedObjects: 4 diff --git a/test/libsolidity/optimizedIRCaching/bytecode_dependency_creation_and_runtime.sol b/test/libsolidity/optimizedIRCaching/bytecode_dependency_creation_and_runtime.sol new file mode 100644 index 000000000000..40ab2993c536 --- /dev/null +++ b/test/libsolidity/optimizedIRCaching/bytecode_dependency_creation_and_runtime.sol @@ -0,0 +1,12 @@ +contract A {} +contract B {} + +contract C { + A a = new A(); + + function f() public returns (B) { + return new B(); + } +} +// ---- +// cachedObjects: 6 diff --git a/test/libsolidity/optimizedIRCaching/bytecode_dependency_creation_and_runtime_shared.sol b/test/libsolidity/optimizedIRCaching/bytecode_dependency_creation_and_runtime_shared.sol new file mode 100644 index 000000000000..86df1b37e0b0 --- /dev/null +++ b/test/libsolidity/optimizedIRCaching/bytecode_dependency_creation_and_runtime_shared.sol @@ -0,0 +1,11 @@ +contract A {} + +contract C { + A a = new A(); + + function f() public returns (A) { + return new A(); + } +} +// ---- +// cachedObjects: 4 diff --git a/test/libsolidity/optimizedIRCaching/bytecode_dependency_runtime.sol b/test/libsolidity/optimizedIRCaching/bytecode_dependency_runtime.sol new file mode 100644 index 000000000000..6cc4d6bdecab --- /dev/null +++ b/test/libsolidity/optimizedIRCaching/bytecode_dependency_runtime.sol @@ -0,0 +1,9 @@ +contract A {} + +contract C { + function f() public returns (A) { + return new A(); + } +} +// ---- +// cachedObjects: 4 diff --git a/test/libsolidity/optimizedIRCaching/bytecode_dependency_shared_by_multiple_contracts.sol b/test/libsolidity/optimizedIRCaching/bytecode_dependency_shared_by_multiple_contracts.sol new file mode 100644 index 000000000000..cd0f97f9546d --- /dev/null +++ b/test/libsolidity/optimizedIRCaching/bytecode_dependency_shared_by_multiple_contracts.sol @@ -0,0 +1,19 @@ +contract A {} + +contract C { + A a = new A(); + + function f() public returns (A) { + return new A(); + } +} + +contract D { + A a = new A(); + + function f() public returns (A) { + return new A(); + } +} +// ---- +// cachedObjects: 6 diff --git a/test/libsolidity/optimizedIRCaching/multiple_contracts.sol b/test/libsolidity/optimizedIRCaching/multiple_contracts.sol new file mode 100644 index 000000000000..b80c99b6528b --- /dev/null +++ b/test/libsolidity/optimizedIRCaching/multiple_contracts.sol @@ -0,0 +1,5 @@ +contract C {} +contract D {} +contract E {} +// ---- +// cachedObjects: 6 diff --git a/test/libsolidity/optimizedIRCaching/no_contracts.sol b/test/libsolidity/optimizedIRCaching/no_contracts.sol new file mode 100644 index 000000000000..c080768cdfd0 --- /dev/null +++ b/test/libsolidity/optimizedIRCaching/no_contracts.sol @@ -0,0 +1,2 @@ +// ---- +// cachedObjects: 0 diff --git a/test/libsolidity/optimizedIRCaching/single_contract.sol b/test/libsolidity/optimizedIRCaching/single_contract.sol new file mode 100644 index 000000000000..6f3ab6df8853 --- /dev/null +++ b/test/libsolidity/optimizedIRCaching/single_contract.sol @@ -0,0 +1,3 @@ +contract C {} +// ---- +// cachedObjects: 2 diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt index 532a08d9fc55..44a5e2aa701e 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -26,6 +26,8 @@ add_executable(isoltest ../libsolidity/GasTest.cpp ../libsolidity/MemoryGuardTest.cpp ../libsolidity/NatspecJSONTest.cpp + ../libsolidity/OptimizedIRCachingTest.cpp + ../libsolidity/OptimizedIRCachingTest.h ../libsolidity/SyntaxTest.cpp ../libsolidity/SemanticTest.cpp ../libsolidity/AnalysisFramework.cpp