From d199fc537ba1796c584817073ca30ab93e47b2ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Sat, 29 Feb 2020 19:53:58 +0100 Subject: [PATCH] Add configurable weights to CodeSize metric --- libyul/optimiser/Metrics.cpp | 79 ++++++++----- libyul/optimiser/Metrics.h | 57 ++++++++-- test/libyul/Metrics.cpp | 209 ++++++++++++++++++++++++++++++++++- 3 files changed, 304 insertions(+), 41 deletions(-) diff --git a/libyul/optimiser/Metrics.cpp b/libyul/optimiser/Metrics.cpp index bcd723464..5a7162294 100644 --- a/libyul/optimiser/Metrics.cpp +++ b/libyul/optimiser/Metrics.cpp @@ -36,30 +36,71 @@ using namespace solidity; using namespace solidity::yul; using namespace solidity::util; -size_t CodeSize::codeSize(Statement const& _statement) +size_t CodeWeights::costOf(Statement const& _statement) const { - CodeSize cs; + if (holds_alternative(_statement)) + return expressionStatementCost; + else if (holds_alternative(_statement)) + return assignmentCost; + else if (holds_alternative(_statement)) + return variableDeclarationCost; + else if (holds_alternative(_statement)) + return functionDefinitionCost; + else if (holds_alternative(_statement)) + return ifCost; + else if (holds_alternative(_statement)) + return switchCost + caseCost * std::get(_statement).cases.size(); + else if (holds_alternative(_statement)) + return forLoopCost; + else if (holds_alternative(_statement)) + return breakCost; + else if (holds_alternative(_statement)) + return continueCost; + else if (holds_alternative(_statement)) + return leaveCost; + else if (holds_alternative(_statement)) + return blockCost; + else + yulAssert(false, "If you add a new statement type, you must update CodeWeights."); +} + +size_t CodeWeights::costOf(Expression const& _expression) const +{ + if (holds_alternative(_expression)) + return functionCallCost; + else if (holds_alternative(_expression)) + return identifierCost; + else if (holds_alternative(_expression)) + return literalCost; + else + yulAssert(false, "If you add a new expression type, you must update CodeWeights."); +} + + +size_t CodeSize::codeSize(Statement const& _statement, CodeWeights const& _weights) +{ + CodeSize cs(true, _weights); cs.visit(_statement); return cs.m_size; } -size_t CodeSize::codeSize(Expression const& _expression) +size_t CodeSize::codeSize(Expression const& _expression, CodeWeights const& _weights) { - CodeSize cs; + CodeSize cs(true, _weights); cs.visit(_expression); return cs.m_size; } -size_t CodeSize::codeSize(Block const& _block) +size_t CodeSize::codeSize(Block const& _block, CodeWeights const& _weights) { - CodeSize cs; + CodeSize cs(true, _weights); cs(_block); return cs.m_size; } -size_t CodeSize::codeSizeIncludingFunctions(Block const& _block) +size_t CodeSize::codeSizeIncludingFunctions(Block const& _block, CodeWeights const& _weights) { - CodeSize cs(false); + CodeSize cs(false, _weights); cs(_block); return cs.m_size; } @@ -68,32 +109,14 @@ void CodeSize::visit(Statement const& _statement) { if (holds_alternative(_statement) && m_ignoreFunctions) return; - else if ( - holds_alternative(_statement) || - holds_alternative(_statement) || - holds_alternative(_statement) || - holds_alternative(_statement) - ) - m_size += 2; - else if (holds_alternative(_statement)) - m_size += 3; - else if (holds_alternative(_statement)) - m_size += 1 + 2 * std::get(_statement).cases.size(); - else if (!( - holds_alternative(_statement) || - holds_alternative(_statement) || - holds_alternative(_statement) || - holds_alternative(_statement) - )) - ++m_size; + m_size += m_weights.costOf(_statement); ASTWalker::visit(_statement); } void CodeSize::visit(Expression const& _expression) { - if (!holds_alternative(_expression)) - ++m_size; + m_size += m_weights.costOf(_expression); ASTWalker::visit(_expression); } diff --git a/libyul/optimiser/Metrics.h b/libyul/optimiser/Metrics.h index 0b1087de2..19ca5616f 100644 --- a/libyul/optimiser/Metrics.h +++ b/libyul/optimiser/Metrics.h @@ -30,33 +30,67 @@ struct Dialect; struct EVMDialect; /** - * Metric for the size of code. - * More specifically, the number of AST nodes. - * Ignores function definitions while traversing the AST by default. - * If you want to know the size of a function, you have to invoke this on its body. + * Weights to be assigned to specific yul statements and expressions by a metric. * - * As an exception, the following AST elements have a cost of zero: + * The default values are meant to reflect specifically the number of AST nodes. + * + * The following AST elements have a default cost of zero (because the cleanup phase would + * remove them anyway or they are just wrappers around something else will be counted instead): * - expression statement (only the expression inside has a cost) * - block (only the statements inside have a cost) * - variable references * - variable declarations (only the right hand side has a cost) * - assignments (only the value has a cost) * - * As another exception, each statement incurs and additional cost of one + * Each statement incurs and additional cost of one * per jump/branch. This means if, break and continue statements have a cost of 2, * switch statements have a cost of 1 plus the number of cases times two, * and for loops cost 3. +*/ +struct CodeWeights +{ + // Statements + size_t expressionStatementCost = 0; + size_t assignmentCost = 0; + size_t variableDeclarationCost = 0; + size_t functionDefinitionCost = 1; + size_t ifCost = 2; + size_t switchCost = 1; + size_t caseCost = 2; + size_t forLoopCost = 3; + size_t breakCost = 2; + size_t continueCost = 2; + size_t leaveCost = 2; + size_t blockCost = 0; + + // Expressions + size_t functionCallCost = 1; + size_t identifierCost = 0; + size_t literalCost = 1; + + size_t costOf(Statement const& _statement) const; + size_t costOf(Expression const& _expression) const; +}; + +/** + * Metric for the size of code. + * Ignores function definitions while traversing the AST by default. + * If you want to know the size of a function, you have to invoke this on its body. + * + * The cost of each statement and expression type is configurable via CodeWeights. */ class CodeSize: public ASTWalker { public: - static size_t codeSize(Statement const& _statement); - static size_t codeSize(Expression const& _expression); - static size_t codeSize(Block const& _block); - static size_t codeSizeIncludingFunctions(Block const& _block); + static size_t codeSize(Statement const& _statement, CodeWeights const& _weights = {}); + static size_t codeSize(Expression const& _expression, CodeWeights const& _weights = {}); + static size_t codeSize(Block const& _block, CodeWeights const& _weights = {}); + static size_t codeSizeIncludingFunctions(Block const& _block, CodeWeights const& _weights = {}); private: - CodeSize(bool _ignoreFunctions = true): m_ignoreFunctions(_ignoreFunctions) {} + CodeSize(bool _ignoreFunctions = true, CodeWeights const& _weights = {}): + m_ignoreFunctions(_ignoreFunctions), + m_weights(_weights) {} void visit(Statement const& _statement) override; void visit(Expression const& _expression) override; @@ -64,6 +98,7 @@ private: private: bool m_ignoreFunctions; size_t m_size = 0; + CodeWeights m_weights; }; /** diff --git a/test/libyul/Metrics.cpp b/test/libyul/Metrics.cpp index 1841abc67..2d1cc1c4e 100644 --- a/test/libyul/Metrics.cpp +++ b/test/libyul/Metrics.cpp @@ -36,15 +36,38 @@ namespace solidity::yul::test namespace { -size_t codeSize(string const& _source) +size_t codeSize(string const& _source, CodeWeights const _weights = {}) { shared_ptr ast = parse(_source, false).first; BOOST_REQUIRE(ast); - return CodeSize::codeSize(*ast); + return CodeSize::codeSize(*ast, _weights); } } +class CustomWeightFixture +{ +protected: + CodeWeights m_weights{ + /* expressionStatementCost = */ 1, + /* assignmentCost = */ 2, + /* variableDeclarationCost = */ 3, + /* functionDefinitionCost = */ 4, + /* ifCost = */ 5, + /* switchCost = */ 6, + /* caseCost = */ 7, + /* forLoopCost = */ 8, + /* breakCost = */ 9, + /* continueCost = */ 10, + /* leaveCost = */ 11, + /* blockCost = */ 12, + + /* functionCallCost = */ 13, + /* identifierCost = */ 14, + /* literalCost = */ 15, + }; +}; + BOOST_AUTO_TEST_SUITE(YulCodeSize) BOOST_AUTO_TEST_CASE(empty_code) @@ -52,41 +75,103 @@ BOOST_AUTO_TEST_CASE(empty_code) BOOST_CHECK_EQUAL(codeSize("{}"), 0); } +BOOST_FIXTURE_TEST_CASE(empty_code_custom_weights, CustomWeightFixture) +{ + BOOST_CHECK_EQUAL(codeSize("{}", m_weights), 0); +} + BOOST_AUTO_TEST_CASE(nested_blocks) { BOOST_CHECK_EQUAL(codeSize("{ {} {} {{ }} }"), 0); } +BOOST_FIXTURE_TEST_CASE(nested_blocks_custom_weights, CustomWeightFixture) +{ + BOOST_CHECK_EQUAL(codeSize("{ {} {} {{ }} }", m_weights), 4 * m_weights.blockCost); +} + BOOST_AUTO_TEST_CASE(instruction) { BOOST_CHECK_EQUAL(codeSize("{ pop(calldatasize()) }"), 2); } +BOOST_FIXTURE_TEST_CASE(instruction_custom_weights, CustomWeightFixture) +{ + BOOST_CHECK_EQUAL( + codeSize("{ pop(calldatasize()) }", m_weights), + 2 * m_weights.functionCallCost + + 1 * m_weights.expressionStatementCost + ); +} + BOOST_AUTO_TEST_CASE(variables_are_free) { BOOST_CHECK_EQUAL(codeSize("{ let x let y let a, b, c }"), 0); } +BOOST_FIXTURE_TEST_CASE(variables_custom_weights, CustomWeightFixture) +{ + BOOST_CHECK_EQUAL( + codeSize("{ let x let y let a, b, c }", m_weights), + 3 * m_weights.variableDeclarationCost + ); +} + BOOST_AUTO_TEST_CASE(constants_cost_one) { BOOST_CHECK_EQUAL(codeSize("{ let x := 3 }"), 1); } +BOOST_FIXTURE_TEST_CASE(constants_custom_weights, CustomWeightFixture) +{ + BOOST_CHECK_EQUAL( + codeSize("{ let x := 3 }", m_weights), + 1 * m_weights.variableDeclarationCost + + 1 * m_weights.literalCost + ); +} + BOOST_AUTO_TEST_CASE(functions_are_skipped) { BOOST_CHECK_EQUAL(codeSize("{ function f(x) -> r { r := mload(x) } }"), 0); } +BOOST_FIXTURE_TEST_CASE(functions_are_skipped_custom_weights, CustomWeightFixture) +{ + BOOST_CHECK_EQUAL(codeSize("{ function f(x) -> r { r := mload(x) } }", m_weights), 0); +} + BOOST_AUTO_TEST_CASE(function_with_arguments) { BOOST_CHECK_EQUAL(codeSize("{ function f(x) { sstore(x, 2) } f(2) }"), 2); } +BOOST_FIXTURE_TEST_CASE(function_with_arguments_custom_weights, CustomWeightFixture) +{ + BOOST_CHECK_EQUAL( + codeSize("{ function f(x) { sstore(x, 2) } f(2) }", m_weights), + 1 * m_weights.expressionStatementCost + + 1 * m_weights.functionCallCost + + 1 * m_weights.literalCost + ); +} + BOOST_AUTO_TEST_CASE(function_with_variables_as_arguments) { BOOST_CHECK_EQUAL(codeSize("{ function f(x) { sstore(x, 2) } let y f(y) }"), 1); } +BOOST_FIXTURE_TEST_CASE(function_with_variables_as_arguments_custom_weights, CustomWeightFixture) +{ + BOOST_CHECK_EQUAL( + codeSize("{ function f(x) { sstore(x, 2) } let y f(y) }", m_weights), + 1 * m_weights.variableDeclarationCost + + 1 * m_weights.expressionStatementCost + + 1 * m_weights.functionCallCost + + 1 * m_weights.identifierCost + ); +} + BOOST_AUTO_TEST_CASE(function_with_variables_and_constants_as_arguments) { BOOST_CHECK_EQUAL(codeSize( @@ -94,21 +179,69 @@ BOOST_AUTO_TEST_CASE(function_with_variables_and_constants_as_arguments) ), 2); } +BOOST_FIXTURE_TEST_CASE( + function_with_variables_and_constants_as_arguments_custom_weights, + CustomWeightFixture +) +{ + BOOST_CHECK_EQUAL( + codeSize( + "{ function f(x, r) -> z { sstore(x, r) z := r } let y let t := f(y, 2) }", + m_weights + ), + 2 * m_weights.variableDeclarationCost + + 1 * m_weights.functionCallCost + + 1 * m_weights.identifierCost + + 1 * m_weights.literalCost + ); +} + BOOST_AUTO_TEST_CASE(assignment) { BOOST_CHECK_EQUAL(codeSize("{ let a a := 3 }"), 1); } +BOOST_FIXTURE_TEST_CASE(assignment_custom_weights, CustomWeightFixture) +{ + BOOST_CHECK_EQUAL( + codeSize("{ let a a := 3 }", m_weights), + 1 * m_weights.variableDeclarationCost + + 1 * m_weights.assignmentCost + + 1 * m_weights.literalCost + ); +} + BOOST_AUTO_TEST_CASE(assignments_between_vars_are_free) { BOOST_CHECK_EQUAL(codeSize("{ let a let b := a a := b }"), 0); } +BOOST_FIXTURE_TEST_CASE(assignments_between_vars_are_free_custom_weights, CustomWeightFixture) +{ + BOOST_CHECK_EQUAL( + codeSize("{ let a let b := a a := b }", m_weights), + 2 * m_weights.variableDeclarationCost + + 1 * m_weights.assignmentCost + + 2 * m_weights.identifierCost + ); +} + BOOST_AUTO_TEST_CASE(assignment_complex) { BOOST_CHECK_EQUAL(codeSize("{ let a let x := mload(a) a := sload(x) }"), 2); } +BOOST_FIXTURE_TEST_CASE(assignment_complex_custom_weights, CustomWeightFixture) +{ + BOOST_CHECK_EQUAL( + codeSize("{ let a let x := mload(a) a := sload(x) }", m_weights), + 2 * m_weights.variableDeclarationCost + + 1 * m_weights.assignmentCost + + 2 * m_weights.identifierCost + + 2 * m_weights.functionCallCost + ); +} + BOOST_AUTO_TEST_CASE(empty_for_loop) { BOOST_CHECK_EQUAL(codeSize( @@ -116,6 +249,15 @@ BOOST_AUTO_TEST_CASE(empty_for_loop) ), 4); } +BOOST_FIXTURE_TEST_CASE(empty_for_loop_custom_weights, CustomWeightFixture) +{ + BOOST_CHECK_EQUAL( + codeSize("{ for {} 1 {} {} }", m_weights), + 1 * m_weights.forLoopCost + + 1 * m_weights.literalCost + ); +} + BOOST_AUTO_TEST_CASE(break_statement) { BOOST_CHECK_EQUAL(codeSize( @@ -123,6 +265,16 @@ BOOST_AUTO_TEST_CASE(break_statement) ), 6); } +BOOST_FIXTURE_TEST_CASE(break_statement_custom_weights, CustomWeightFixture) +{ + BOOST_CHECK_EQUAL( + codeSize("{ for {} 1 {} { break } }", m_weights), + 1 * m_weights.forLoopCost + + 1 * m_weights.literalCost + + 1 * m_weights.breakCost + ); +} + BOOST_AUTO_TEST_CASE(continue_statement) { BOOST_CHECK_EQUAL(codeSize( @@ -130,6 +282,16 @@ BOOST_AUTO_TEST_CASE(continue_statement) ), 6); } +BOOST_FIXTURE_TEST_CASE(continue_statement_custom_weights, CustomWeightFixture) +{ + BOOST_CHECK_EQUAL( + codeSize("{ for {} 1 {} { continue } }", m_weights), + 1 * m_weights.forLoopCost + + 1 * m_weights.literalCost + + 1 * m_weights.continueCost + ); +} + BOOST_AUTO_TEST_CASE(regular_for_loop) { BOOST_CHECK_EQUAL(codeSize( @@ -137,6 +299,20 @@ BOOST_AUTO_TEST_CASE(regular_for_loop) ), 10); } +BOOST_FIXTURE_TEST_CASE(regular_for_loop_custom_weights, CustomWeightFixture) +{ + BOOST_CHECK_EQUAL( + codeSize("{ for { let x := 0 } lt(x, 10) { x := add(x, 1) } { mstore(x, 1) } }", m_weights), + 1 * m_weights.forLoopCost + + 1 * m_weights.variableDeclarationCost + + 1 * m_weights.assignmentCost + + 3 * m_weights.functionCallCost + + 4 * m_weights.literalCost + + 3 * m_weights.identifierCost + + 1 * m_weights.expressionStatementCost + ); +} + BOOST_AUTO_TEST_CASE(if_statement) { BOOST_CHECK_EQUAL(codeSize( @@ -144,6 +320,15 @@ BOOST_AUTO_TEST_CASE(if_statement) ), 3); } +BOOST_FIXTURE_TEST_CASE(if_statement_custom_weights, CustomWeightFixture) +{ + BOOST_CHECK_EQUAL( + codeSize("{ if 1 {} }", m_weights), + 1 * m_weights.ifCost + + 1 * m_weights.literalCost + ); +} + BOOST_AUTO_TEST_CASE(switch_statement_tiny) { BOOST_CHECK_EQUAL(codeSize( @@ -158,6 +343,16 @@ BOOST_AUTO_TEST_CASE(switch_statement_small) ), 6); } +BOOST_FIXTURE_TEST_CASE(switch_statement_small_custom_weights, CustomWeightFixture) +{ + BOOST_CHECK_EQUAL( + codeSize("{ switch calldatasize() case 0 {} default {} }", m_weights), + 1 * m_weights.functionCallCost + + 1 * m_weights.switchCost + + 2 * m_weights.caseCost + ); +} + BOOST_AUTO_TEST_CASE(switch_statement_medium) { BOOST_CHECK_EQUAL(codeSize( @@ -172,6 +367,16 @@ BOOST_AUTO_TEST_CASE(switch_statement_large) ), 10); } +BOOST_FIXTURE_TEST_CASE(switch_statement_large_custom_weights, CustomWeightFixture) +{ + BOOST_CHECK_EQUAL( + codeSize("{ switch calldatasize() case 0 {} case 1 {} case 2 {} default {} }", m_weights), + 1 * m_weights.functionCallCost + + 1 * m_weights.switchCost + + 4 * m_weights.caseCost + ); +} + BOOST_AUTO_TEST_SUITE_END() }