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() } diff --git a/test/yulPhaser/AlgorithmRunner.cpp b/test/yulPhaser/AlgorithmRunner.cpp index 8dd6e7564..042cd4c2b 100644 --- a/test/yulPhaser/AlgorithmRunner.cpp +++ b/test/yulPhaser/AlgorithmRunner.cpp @@ -37,6 +37,7 @@ using namespace boost::unit_test::framework; using namespace boost::test_tools; using namespace solidity::langutil; using namespace solidity::util; +using namespace solidity::yul; namespace fs = boost::filesystem; @@ -299,8 +300,8 @@ BOOST_FIXTURE_TEST_CASE(run_should_print_cache_stats_if_requested, AlgorithmRunn make_shared(programs[1]), }; shared_ptr fitnessMetric = make_shared(vector>{ - make_shared(nullopt, caches[0]), - make_shared(nullopt, caches[1]), + make_shared(nullopt, caches[0], CodeWeights{}), + make_shared(nullopt, caches[1], CodeWeights{}), }); Population population = Population::makeRandom(fitnessMetric, 2, 0, 5); diff --git a/test/yulPhaser/FitnessMetrics.cpp b/test/yulPhaser/FitnessMetrics.cpp index 524ef6357..e668b54e9 100644 --- a/test/yulPhaser/FitnessMetrics.cpp +++ b/test/yulPhaser/FitnessMetrics.cpp @@ -62,12 +62,12 @@ protected: Program optimisedProgram(Program _program) const { - [[maybe_unused]] size_t originalSize = _program.codeSize(); + [[maybe_unused]] size_t originalSize = _program.codeSize(m_weights); Program result = move(_program); result.optimise(m_chromosome.optimisationSteps()); // Make sure that the program and the chromosome we have chosen are suitable for the test - assert(result.codeSize() != originalSize); + assert(result.codeSize(m_weights) != originalSize); return result; } @@ -77,15 +77,16 @@ protected: Program m_program = get(Program::load(m_sourceStream)); Program m_optimisedProgram = optimisedProgram(m_program); shared_ptr m_programCache = make_shared(m_program); + static constexpr CodeWeights m_weights{}; }; class FitnessMetricCombinationFixture: public ProgramBasedMetricFixture { protected: vector> m_simpleMetrics = { - make_shared(m_program, nullptr, 1), - make_shared(m_program, nullptr, 2), - make_shared(m_program, nullptr, 3), + make_shared(m_program, nullptr, m_weights, 1), + make_shared(m_program, nullptr, m_weights, 2), + make_shared(m_program, nullptr, m_weights, 3), }; vector m_fitness = { m_simpleMetrics[0]->evaluate(m_chromosome), @@ -100,7 +101,7 @@ BOOST_AUTO_TEST_SUITE(ProgramBasedMetricTest) BOOST_FIXTURE_TEST_CASE(optimisedProgram_should_return_optimised_program_even_if_cache_not_available, ProgramBasedMetricFixture) { - string code = toString(DummyProgramBasedMetric(m_program, nullptr).optimisedProgram(m_chromosome)); + string code = toString(DummyProgramBasedMetric(m_program, nullptr, m_weights).optimisedProgram(m_chromosome)); BOOST_TEST(code != toString(m_program)); BOOST_TEST(code == toString(m_optimisedProgram)); @@ -108,7 +109,7 @@ BOOST_FIXTURE_TEST_CASE(optimisedProgram_should_return_optimised_program_even_if BOOST_FIXTURE_TEST_CASE(optimisedProgram_should_use_cache_if_available, ProgramBasedMetricFixture) { - string code = toString(DummyProgramBasedMetric(nullopt, m_programCache).optimisedProgram(m_chromosome)); + string code = toString(DummyProgramBasedMetric(nullopt, m_programCache, m_weights).optimisedProgram(m_chromosome)); BOOST_TEST(code != toString(m_program)); BOOST_TEST(code == toString(m_optimisedProgram)); @@ -117,7 +118,7 @@ BOOST_FIXTURE_TEST_CASE(optimisedProgram_should_use_cache_if_available, ProgramB BOOST_FIXTURE_TEST_CASE(optimisedProgramNoCache_should_return_optimised_program_even_if_cache_not_available, ProgramBasedMetricFixture) { - string code = toString(DummyProgramBasedMetric(m_program, nullptr).optimisedProgramNoCache(m_chromosome)); + string code = toString(DummyProgramBasedMetric(m_program, nullptr, m_weights).optimisedProgramNoCache(m_chromosome)); BOOST_TEST(code != toString(m_program)); BOOST_TEST(code == toString(m_optimisedProgram)); @@ -125,7 +126,7 @@ BOOST_FIXTURE_TEST_CASE(optimisedProgramNoCache_should_return_optimised_program_ BOOST_FIXTURE_TEST_CASE(optimisedProgramNoCache_should_not_use_cache_even_if_available, ProgramBasedMetricFixture) { - string code = toString(DummyProgramBasedMetric(nullopt, m_programCache).optimisedProgramNoCache(m_chromosome)); + string code = toString(DummyProgramBasedMetric(nullopt, m_programCache, m_weights).optimisedProgramNoCache(m_chromosome)); BOOST_TEST(code != toString(m_program)); BOOST_TEST(code == toString(m_optimisedProgram)); @@ -137,18 +138,18 @@ BOOST_AUTO_TEST_SUITE(ProgramSizeTest) BOOST_FIXTURE_TEST_CASE(evaluate_should_compute_size_of_the_optimised_program, ProgramBasedMetricFixture) { - size_t fitness = ProgramSize(m_program, nullptr).evaluate(m_chromosome); + size_t fitness = ProgramSize(m_program, nullptr, m_weights).evaluate(m_chromosome); - BOOST_TEST(fitness != m_program.codeSize()); - BOOST_TEST(fitness == m_optimisedProgram.codeSize()); + BOOST_TEST(fitness != m_program.codeSize(m_weights)); + BOOST_TEST(fitness == m_optimisedProgram.codeSize(m_weights)); } BOOST_FIXTURE_TEST_CASE(evaluate_should_be_able_to_use_program_cache_if_available, ProgramBasedMetricFixture) { - size_t fitness = ProgramSize(nullopt, m_programCache).evaluate(m_chromosome); + size_t fitness = ProgramSize(nullopt, m_programCache, m_weights).evaluate(m_chromosome); - BOOST_TEST(fitness != m_program.codeSize()); - BOOST_TEST(fitness == m_optimisedProgram.codeSize()); + BOOST_TEST(fitness != m_program.codeSize(m_weights)); + BOOST_TEST(fitness == m_optimisedProgram.codeSize(m_weights)); BOOST_TEST(m_programCache->size() == m_chromosome.length()); } @@ -157,21 +158,21 @@ BOOST_FIXTURE_TEST_CASE(evaluate_should_repeat_the_optimisation_specified_number Program const& programOptimisedOnce = m_optimisedProgram; Program programOptimisedTwice = optimisedProgram(programOptimisedOnce); - ProgramSize metric(m_program, nullptr, 2); + ProgramSize metric(m_program, nullptr, m_weights, 2); size_t fitness = metric.evaluate(m_chromosome); - BOOST_TEST(fitness != m_program.codeSize()); - BOOST_TEST(fitness != programOptimisedOnce.codeSize()); - BOOST_TEST(fitness == programOptimisedTwice.codeSize()); + BOOST_TEST(fitness != m_program.codeSize(m_weights)); + BOOST_TEST(fitness != programOptimisedOnce.codeSize(m_weights)); + BOOST_TEST(fitness == programOptimisedTwice.codeSize(m_weights)); } BOOST_FIXTURE_TEST_CASE(evaluate_should_not_optimise_if_number_of_repetitions_is_zero, ProgramBasedMetricFixture) { - ProgramSize metric(m_program, nullptr, 0); + ProgramSize metric(m_program, nullptr, m_weights, 0); size_t fitness = metric.evaluate(m_chromosome); - BOOST_TEST(fitness == m_program.codeSize()); - BOOST_TEST(fitness != m_optimisedProgram.codeSize()); + BOOST_TEST(fitness == m_program.codeSize(m_weights)); + BOOST_TEST(fitness != m_optimisedProgram.codeSize(m_weights)); } BOOST_AUTO_TEST_SUITE_END() @@ -179,12 +180,18 @@ BOOST_AUTO_TEST_SUITE(RelativeProgramSizeTest) BOOST_FIXTURE_TEST_CASE(evaluate_should_compute_the_size_ratio_between_optimised_program_and_original_program, ProgramBasedMetricFixture) { - BOOST_TEST(RelativeProgramSize(m_program, nullptr, 3).evaluate(m_chromosome) == round(1000.0 * m_optimisedProgram.codeSize() / m_program.codeSize())); + BOOST_TEST( + RelativeProgramSize(m_program, nullptr, 3, m_weights).evaluate(m_chromosome) == + round(1000.0 * m_optimisedProgram.codeSize(m_weights) / m_program.codeSize(m_weights)) + ); } BOOST_FIXTURE_TEST_CASE(evaluate_should_be_able_to_use_program_cache_if_available, ProgramBasedMetricFixture) { - BOOST_TEST(RelativeProgramSize(nullopt, m_programCache, 3).evaluate(m_chromosome) == round(1000.0 * m_optimisedProgram.codeSize() / m_program.codeSize())); + BOOST_TEST( + RelativeProgramSize(nullopt, m_programCache, 3, m_weights).evaluate(m_chromosome) == + round(1000.0 * m_optimisedProgram.codeSize(m_weights) / m_program.codeSize(m_weights)) + ); BOOST_TEST(m_programCache->size() == m_chromosome.length()); } @@ -193,17 +200,17 @@ BOOST_FIXTURE_TEST_CASE(evaluate_should_repeat_the_optimisation_specified_number Program const& programOptimisedOnce = m_optimisedProgram; Program programOptimisedTwice = optimisedProgram(programOptimisedOnce); - RelativeProgramSize metric(m_program, nullptr, 3, 2); + RelativeProgramSize metric(m_program, nullptr, 3, m_weights, 2); size_t fitness = metric.evaluate(m_chromosome); BOOST_TEST(fitness != 1000); - BOOST_TEST(fitness != RelativeProgramSize(programOptimisedTwice, nullptr, 3, 1).evaluate(m_chromosome)); - BOOST_TEST(fitness == round(1000.0 * programOptimisedTwice.codeSize() / m_program.codeSize())); + BOOST_TEST(fitness != RelativeProgramSize(programOptimisedTwice, nullptr, 3, m_weights, 1).evaluate(m_chromosome)); + BOOST_TEST(fitness == round(1000.0 * programOptimisedTwice.codeSize(m_weights) / m_program.codeSize(m_weights))); } BOOST_FIXTURE_TEST_CASE(evaluate_should_return_one_if_number_of_repetitions_is_zero, ProgramBasedMetricFixture) { - RelativeProgramSize metric(m_program, nullptr, 3, 0); + RelativeProgramSize metric(m_program, nullptr, 3, m_weights, 0); BOOST_TEST(metric.evaluate(m_chromosome) == 1000); } @@ -213,7 +220,7 @@ BOOST_FIXTURE_TEST_CASE(evaluate_should_return_one_if_the_original_program_size_ CharStream sourceStream = CharStream("{}", ""); Program program = get(Program::load(sourceStream)); - RelativeProgramSize metric(program, nullptr, 3); + RelativeProgramSize metric(program, nullptr, 3, m_weights); BOOST_TEST(metric.evaluate(m_chromosome) == 1000); BOOST_TEST(metric.evaluate(Chromosome("")) == 1000); @@ -222,12 +229,12 @@ BOOST_FIXTURE_TEST_CASE(evaluate_should_return_one_if_the_original_program_size_ BOOST_FIXTURE_TEST_CASE(evaluate_should_multiply_the_result_by_scaling_factor, ProgramBasedMetricFixture) { - double sizeRatio = static_cast(m_optimisedProgram.codeSize()) / m_program.codeSize(); - BOOST_TEST(RelativeProgramSize(m_program, nullptr, 0).evaluate(m_chromosome) == round(1.0 * sizeRatio)); - BOOST_TEST(RelativeProgramSize(m_program, nullptr, 1).evaluate(m_chromosome) == round(10.0 * sizeRatio)); - BOOST_TEST(RelativeProgramSize(m_program, nullptr, 2).evaluate(m_chromosome) == round(100.0 * sizeRatio)); - BOOST_TEST(RelativeProgramSize(m_program, nullptr, 3).evaluate(m_chromosome) == round(1000.0 * sizeRatio)); - BOOST_TEST(RelativeProgramSize(m_program, nullptr, 4).evaluate(m_chromosome) == round(10000.0 * sizeRatio)); + double sizeRatio = static_cast(m_optimisedProgram.codeSize(m_weights)) / m_program.codeSize(m_weights); + BOOST_TEST(RelativeProgramSize(m_program, nullptr, 0, m_weights).evaluate(m_chromosome) == round(1.0 * sizeRatio)); + BOOST_TEST(RelativeProgramSize(m_program, nullptr, 1, m_weights).evaluate(m_chromosome) == round(10.0 * sizeRatio)); + BOOST_TEST(RelativeProgramSize(m_program, nullptr, 2, m_weights).evaluate(m_chromosome) == round(100.0 * sizeRatio)); + BOOST_TEST(RelativeProgramSize(m_program, nullptr, 3, m_weights).evaluate(m_chromosome) == round(1000.0 * sizeRatio)); + BOOST_TEST(RelativeProgramSize(m_program, nullptr, 4, m_weights).evaluate(m_chromosome) == round(10000.0 * sizeRatio)); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/yulPhaser/Phaser.cpp b/test/yulPhaser/Phaser.cpp index c381ba623..5cef03778 100644 --- a/test/yulPhaser/Phaser.cpp +++ b/test/yulPhaser/Phaser.cpp @@ -32,6 +32,7 @@ using namespace std; using namespace solidity::util; using namespace solidity::langutil; +using namespace solidity::yul; namespace fs = boost::filesystem; @@ -86,6 +87,7 @@ protected: /* relativeMetricScale = */ 5, /* chromosomeRepetitions = */ 1, }; + CodeWeights const m_weights{}; }; class PoulationFactoryFixture @@ -183,7 +185,7 @@ BOOST_FIXTURE_TEST_CASE(build_should_create_metric_of_the_right_type, FitnessMet { m_options.metric = MetricChoice::RelativeCodeSize; m_options.metricAggregator = MetricAggregatorChoice::Sum; - unique_ptr metric = FitnessMetricFactory::build(m_options, {m_programs[0]}, {nullptr}); + unique_ptr metric = FitnessMetricFactory::build(m_options, {m_programs[0]}, {nullptr}, m_weights); BOOST_REQUIRE(metric != nullptr); auto sumMetric = dynamic_cast(metric.get()); @@ -201,7 +203,7 @@ BOOST_FIXTURE_TEST_CASE(build_should_respect_chromosome_repetitions_option, Fitn m_options.metric = MetricChoice::CodeSize; m_options.metricAggregator = MetricAggregatorChoice::Average; m_options.chromosomeRepetitions = 5; - unique_ptr metric = FitnessMetricFactory::build(m_options, {m_programs[0]}, {nullptr}); + unique_ptr metric = FitnessMetricFactory::build(m_options, {m_programs[0]}, {nullptr}, m_weights); BOOST_REQUIRE(metric != nullptr); auto averageMetric = dynamic_cast(metric.get()); @@ -219,7 +221,7 @@ BOOST_FIXTURE_TEST_CASE(build_should_set_relative_metric_scale, FitnessMetricFac m_options.metric = MetricChoice::RelativeCodeSize; m_options.metricAggregator = MetricAggregatorChoice::Average; m_options.relativeMetricScale = 10; - unique_ptr metric = FitnessMetricFactory::build(m_options, {m_programs[0]}, {nullptr}); + unique_ptr metric = FitnessMetricFactory::build(m_options, {m_programs[0]}, {nullptr}, m_weights); BOOST_REQUIRE(metric != nullptr); auto averageMetric = dynamic_cast(metric.get()); @@ -237,7 +239,8 @@ BOOST_FIXTURE_TEST_CASE(build_should_create_metric_for_each_input_program, Fitne unique_ptr metric = FitnessMetricFactory::build( m_options, m_programs, - vector>(m_programs.size(), nullptr) + vector>(m_programs.size(), nullptr), + m_weights ); BOOST_REQUIRE(metric != nullptr); @@ -256,7 +259,7 @@ BOOST_FIXTURE_TEST_CASE(build_should_pass_program_caches_to_metrics, FitnessMetr }; m_options.metric = MetricChoice::RelativeCodeSize; - unique_ptr metric = FitnessMetricFactory::build(m_options, m_programs, caches); + unique_ptr metric = FitnessMetricFactory::build(m_options, m_programs, caches, m_weights); BOOST_REQUIRE(metric != nullptr); auto combinedMetric = dynamic_cast(metric.get()); diff --git a/test/yulPhaser/Program.cpp b/test/yulPhaser/Program.cpp index e69f0fc5c..0a7520dd7 100644 --- a/test/yulPhaser/Program.cpp +++ b/test/yulPhaser/Program.cpp @@ -398,7 +398,7 @@ BOOST_AUTO_TEST_CASE(codeSize) CharStream sourceStream(sourceCode, current_test_case().p_name); Program program = get(Program::load(sourceStream)); - BOOST_TEST(program.codeSize() == CodeSize::codeSizeIncludingFunctions(program.ast())); + BOOST_TEST(program.codeSize(CodeWeights{}) == CodeSize::codeSizeIncludingFunctions(program.ast())); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/yulPhaser/ProgramCache.cpp b/test/yulPhaser/ProgramCache.cpp index 1ed6024bc..3b652e3fb 100644 --- a/test/yulPhaser/ProgramCache.cpp +++ b/test/yulPhaser/ProgramCache.cpp @@ -18,6 +18,8 @@ #include #include +#include + #include #include @@ -212,11 +214,11 @@ BOOST_FIXTURE_TEST_CASE(startRound_should_remove_entries_older_than_two_rounds, BOOST_FIXTURE_TEST_CASE(gatherStats_should_return_cache_statistics, ProgramCacheFixture) { - size_t sizeI = optimisedProgram(m_program, "I").codeSize(); - size_t sizeIu = optimisedProgram(m_program, "Iu").codeSize(); - size_t sizeIuO = optimisedProgram(m_program, "IuO").codeSize(); - size_t sizeL = optimisedProgram(m_program, "L").codeSize(); - size_t sizeLT = optimisedProgram(m_program, "LT").codeSize(); + size_t sizeI = optimisedProgram(m_program, "I").codeSize(CacheStats::StorageWeights); + size_t sizeIu = optimisedProgram(m_program, "Iu").codeSize(CacheStats::StorageWeights); + size_t sizeIuO = optimisedProgram(m_program, "IuO").codeSize(CacheStats::StorageWeights); + size_t sizeL = optimisedProgram(m_program, "L").codeSize(CacheStats::StorageWeights); + size_t sizeLT = optimisedProgram(m_program, "LT").codeSize(CacheStats::StorageWeights); m_programCache.optimiseProgram("L"); m_programCache.optimiseProgram("Iu"); diff --git a/tools/yulPhaser/FitnessMetrics.cpp b/tools/yulPhaser/FitnessMetrics.cpp index 5279cea72..134c13dfc 100644 --- a/tools/yulPhaser/FitnessMetrics.cpp +++ b/tools/yulPhaser/FitnessMetrics.cpp @@ -23,6 +23,7 @@ using namespace std; using namespace solidity::util; +using namespace solidity::yul; using namespace solidity::phaser; Program const& ProgramBasedMetric::program() const @@ -55,18 +56,18 @@ Program ProgramBasedMetric::optimisedProgramNoCache(Chromosome const& _chromosom size_t ProgramSize::evaluate(Chromosome const& _chromosome) { - return optimisedProgram(_chromosome).codeSize(); + return optimisedProgram(_chromosome).codeSize(codeWeights()); } size_t RelativeProgramSize::evaluate(Chromosome const& _chromosome) { size_t const scalingFactor = pow(10, m_fixedPointPrecision); - size_t unoptimisedSize = optimisedProgram(Chromosome("")).codeSize(); + size_t unoptimisedSize = optimisedProgram(Chromosome("")).codeSize(codeWeights()); if (unoptimisedSize == 0) return scalingFactor; - size_t optimisedSize = optimisedProgram(_chromosome).codeSize(); + size_t optimisedSize = optimisedProgram(_chromosome).codeSize(codeWeights()); return static_cast(round( static_cast(optimisedSize) / unoptimisedSize * scalingFactor diff --git a/tools/yulPhaser/FitnessMetrics.h b/tools/yulPhaser/FitnessMetrics.h index 72e811152..50f4c896f 100644 --- a/tools/yulPhaser/FitnessMetrics.h +++ b/tools/yulPhaser/FitnessMetrics.h @@ -24,6 +24,8 @@ #include #include +#include + #include #include @@ -64,10 +66,12 @@ public: explicit ProgramBasedMetric( std::optional _program, std::shared_ptr _programCache, + yul::CodeWeights const& _codeWeights, size_t _repetitionCount = 1 ): m_program(std::move(_program)), m_programCache(std::move(_programCache)), + m_codeWeights(_codeWeights), m_repetitionCount(_repetitionCount) { assert(m_program.has_value() == (m_programCache == nullptr)); @@ -75,6 +79,7 @@ public: Program const& program() const; ProgramCache const* programCache() const { return m_programCache.get(); } + yul::CodeWeights const& codeWeights() const { return m_codeWeights; } size_t repetitionCount() const { return m_repetitionCount; } Program optimisedProgram(Chromosome const& _chromosome); @@ -83,6 +88,7 @@ public: private: std::optional m_program; std::shared_ptr m_programCache; + yul::CodeWeights m_codeWeights; size_t m_repetitionCount; }; @@ -111,9 +117,10 @@ public: std::optional _program, std::shared_ptr _programCache, size_t _fixedPointPrecision, + yul::CodeWeights const& _weights, size_t _repetitionCount = 1 ): - ProgramBasedMetric(std::move(_program), std::move(_programCache), _repetitionCount), + ProgramBasedMetric(std::move(_program), std::move(_programCache), _weights, _repetitionCount), m_fixedPointPrecision(_fixedPointPrecision) {} size_t fixedPointPrecision() const { return m_fixedPointPrecision; } diff --git a/tools/yulPhaser/Phaser.cpp b/tools/yulPhaser/Phaser.cpp index d5b3bdda7..731560fe6 100644 --- a/tools/yulPhaser/Phaser.cpp +++ b/tools/yulPhaser/Phaser.cpp @@ -39,6 +39,7 @@ using namespace std; using namespace solidity; using namespace solidity::langutil; using namespace solidity::util; +using namespace solidity::yul; using namespace solidity::phaser; namespace po = boost::program_options; @@ -188,6 +189,27 @@ unique_ptr GeneticAlgorithmFactory::build( } } +CodeWeights CodeWeightFactory::buildFromCommandLine(po::variables_map const& _arguments) +{ + return { + _arguments["expression-statement-cost"].as(), + _arguments["assignment-cost"].as(), + _arguments["variable-declaration-cost"].as(), + _arguments["function-definition-cost"].as(), + _arguments["if-cost"].as(), + _arguments["switch-cost"].as(), + _arguments["case-cost"].as(), + _arguments["for-loop-cost"].as(), + _arguments["break-cost"].as(), + _arguments["continue-cost"].as(), + _arguments["leave-cost"].as(), + _arguments["block-cost"].as(), + _arguments["function-call-cost"].as(), + _arguments["identifier-cost"].as(), + _arguments["literal-cost"].as(), + }; +} + FitnessMetricFactory::Options FitnessMetricFactory::Options::fromCommandLine(po::variables_map const& _arguments) { return { @@ -201,7 +223,8 @@ FitnessMetricFactory::Options FitnessMetricFactory::Options::fromCommandLine(po: unique_ptr FitnessMetricFactory::build( Options const& _options, vector _programs, - vector> _programCaches + vector> _programCaches, + CodeWeights const& _weights ) { assert(_programCaches.size() == _programs.size()); @@ -216,6 +239,7 @@ unique_ptr FitnessMetricFactory::build( metrics.push_back(make_unique( _programCaches[i] != nullptr ? optional{} : move(_programs[i]), move(_programCaches[i]), + _weights, _options.chromosomeRepetitions )); @@ -228,6 +252,7 @@ unique_ptr FitnessMetricFactory::build( _programCaches[i] != nullptr ? optional{} : move(_programs[i]), move(_programCaches[i]), _options.relativeMetricScale, + _weights, _options.chromosomeRepetitions )); break; @@ -653,6 +678,28 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription() ; keywordDescription.add(metricsDescription); + po::options_description metricWeightDescription("METRIC WEIGHTS", lineLength, minDescriptionLength); + metricWeightDescription.add_options() + // TODO: We need to figure out the best set of weights for the phaser. + // This one is just a stopgap to make sure no statement or expression has zero cost. + ("expression-statement-cost", po::value()->value_name("")->default_value(1)) + ("assignment-cost", po::value()->value_name("")->default_value(1)) + ("variable-declaration-cost", po::value()->value_name("")->default_value(1)) + ("function-definition-cost", po::value()->value_name("")->default_value(1)) + ("if-cost", po::value()->value_name("")->default_value(2)) + ("switch-cost", po::value()->value_name("")->default_value(1)) + ("case-cost", po::value()->value_name("")->default_value(2)) + ("for-loop-cost", po::value()->value_name("")->default_value(3)) + ("break-cost", po::value()->value_name("")->default_value(2)) + ("continue-cost", po::value()->value_name("")->default_value(2)) + ("leave-cost", po::value()->value_name("")->default_value(2)) + ("block-cost", po::value()->value_name("")->default_value(1)) + ("function-call-cost", po::value()->value_name("")->default_value(1)) + ("identifier-cost", po::value()->value_name("")->default_value(1)) + ("literal-cost", po::value()->value_name("")->default_value(1)) + ; + keywordDescription.add(metricWeightDescription); + po::options_description cacheDescription("CACHE", lineLength, minDescriptionLength); cacheDescription.add_options() ( @@ -762,8 +809,13 @@ void Phaser::runPhaser(po::variables_map const& _arguments) vector programs = ProgramFactory::build(programOptions); vector> programCaches = ProgramCacheFactory::build(cacheOptions, programs); - - unique_ptr fitnessMetric = FitnessMetricFactory::build(metricOptions, programs, programCaches); + CodeWeights codeWeights = CodeWeightFactory::buildFromCommandLine(_arguments); + unique_ptr fitnessMetric = FitnessMetricFactory::build( + metricOptions, + programs, + programCaches, + codeWeights + ); Population population = PopulationFactory::build(populationOptions, move(fitnessMetric)); if (_arguments["mode"].as() == PhaserMode::RunAlgorithm) diff --git a/tools/yulPhaser/Phaser.h b/tools/yulPhaser/Phaser.h index 9c1c5ef75..8ba5fc938 100644 --- a/tools/yulPhaser/Phaser.h +++ b/tools/yulPhaser/Phaser.h @@ -39,6 +39,13 @@ class CharStream; } +namespace solidity::yul +{ + +struct CodeWeights; + +} + namespace solidity::phaser { @@ -125,6 +132,17 @@ public: ); }; +/** + * Builds and validates instances of @a CodeWeights. + */ +class CodeWeightFactory +{ +public: + static yul::CodeWeights buildFromCommandLine( + boost::program_options::variables_map const& _arguments + ); +}; + /** * Builds and validates instances of @a FitnessMetric and its derived classes. */ @@ -144,7 +162,8 @@ public: static std::unique_ptr build( Options const& _options, std::vector _programs, - std::vector> _programCaches + std::vector> _programCaches, + yul::CodeWeights const& _weights ); }; diff --git a/tools/yulPhaser/Program.cpp b/tools/yulPhaser/Program.cpp index a457750d2..2fd29f51a 100644 --- a/tools/yulPhaser/Program.cpp +++ b/tools/yulPhaser/Program.cpp @@ -207,7 +207,7 @@ unique_ptr Program::applyOptimisationSteps( return _ast; } -size_t Program::computeCodeSize(Block const& _ast) +size_t Program::computeCodeSize(Block const& _ast, CodeWeights const& _weights) { - return CodeSize::codeSizeIncludingFunctions(_ast); + return CodeSize::codeSizeIncludingFunctions(_ast, _weights); } diff --git a/tools/yulPhaser/Program.h b/tools/yulPhaser/Program.h index 10fed6de2..b96843ee4 100644 --- a/tools/yulPhaser/Program.h +++ b/tools/yulPhaser/Program.h @@ -41,6 +41,7 @@ namespace solidity::yul struct AsmAnalysisInfo; struct Dialect; +struct CodeWeights; } @@ -78,7 +79,7 @@ public: static std::variant load(langutil::CharStream& _sourceCode); void optimise(std::vector const& _optimisationSteps); - size_t codeSize() const { return computeCodeSize(*m_ast); } + size_t codeSize(yul::CodeWeights const& _weights) const { return computeCodeSize(*m_ast, _weights); } yul::Block const& ast() const { return *m_ast; } friend std::ostream& operator<<(std::ostream& _stream, Program const& _program); @@ -113,7 +114,7 @@ private: std::unique_ptr _ast, std::vector const& _optimisationSteps ); - static size_t computeCodeSize(yul::Block const& _ast); + static size_t computeCodeSize(yul::Block const& _ast, yul::CodeWeights const& _weights); std::unique_ptr m_ast; yul::Dialect const& m_dialect; diff --git a/tools/yulPhaser/ProgramCache.cpp b/tools/yulPhaser/ProgramCache.cpp index 7acec16bd..b174ad07f 100644 --- a/tools/yulPhaser/ProgramCache.cpp +++ b/tools/yulPhaser/ProgramCache.cpp @@ -17,6 +17,8 @@ #include +#include + #include using namespace std; @@ -133,7 +135,7 @@ size_t ProgramCache::calculateTotalCachedCodeSize() const { size_t size = 0; for (auto const& pair: m_entries) - size += pair.second.program.codeSize(); + size += pair.second.program.codeSize(CacheStats::StorageWeights); return size; } diff --git a/tools/yulPhaser/ProgramCache.h b/tools/yulPhaser/ProgramCache.h index 8cc830098..25c796a3d 100644 --- a/tools/yulPhaser/ProgramCache.h +++ b/tools/yulPhaser/ProgramCache.h @@ -19,6 +19,8 @@ #include +#include + #include #include @@ -44,6 +46,29 @@ struct CacheEntry */ struct CacheStats { + /// Weights used to compute totalCodeSize. + /// The goal here is to get a result proportional to the amount of memory taken by the AST. + /// Each statement/expression gets 1 just for existing. We add more if it contains any extra + /// data that won't be visited separately by ASTWalker. + static yul::CodeWeights constexpr StorageWeights = { + /* expressionStatementCost = */ 1, + /* assignmentCost = */ 1, + /* variableDeclarationCost = */ 1, + /* functionDefinitionCost = */ 1, + /* ifCost = */ 1, + /* switchCost = */ 1, + /* caseCost = */ 1, + /* forLoopCost = */ 1, + /* breakCost = */ 1, + /* continueCost = */ 1, + /* leaveCost = */ 1, + /* blockCost = */ 1, + + /* functionCallCost = */ 1, + /* identifierCost = */ 1, + /* literalCost = */ 1, + }; + size_t hits; size_t misses; size_t totalCodeSize;