From d30d3a2ab33d4231dce098024e009ac89e0f887f Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Mon, 15 Feb 2021 00:37:24 +0100 Subject: [PATCH 1/6] refactor: keep 4byte sig on stack --- vyper/parser/function_definitions/utils.py | 2 +- vyper/parser/parser.py | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/vyper/parser/function_definitions/utils.py b/vyper/parser/function_definitions/utils.py index 9df752120c..8f1cbd6ccf 100644 --- a/vyper/parser/function_definitions/utils.py +++ b/vyper/parser/function_definitions/utils.py @@ -10,7 +10,7 @@ def get_sig_statements(sig, pos): ["label", f"priv_{sig.method_id}"], pos=pos, annotation=f"{sig.sig}" ) else: - sig_compare = ["eq", ["mload", 0], method_id_node] + sig_compare = ["eq", "_func_sig", method_id_node] private_label = ["pass"] return sig_compare, private_label diff --git a/vyper/parser/parser.py b/vyper/parser/parser.py index b8dd89b3d4..bb3111b075 100644 --- a/vyper/parser/parser.py +++ b/vyper/parser/parser.py @@ -89,19 +89,20 @@ def parse_external_interfaces(external_interfaces, global_ctx): def parse_other_functions( o, otherfuncs, sigs, external_interfaces, global_ctx, default_function, is_contract_payable, ): - sub = ["seq", func_init_lll()] + func_sub = ["seq"] + main_seq = ["seq", func_init_lll(), ["with", "_func_sig", ["mload", 0], func_sub]] add_gas = func_init_lll().gas for _def in otherfuncs: - sub.append( + func_sub.append( parse_function( _def, {**{"self": sigs}, **external_interfaces}, global_ctx, is_contract_payable, ) ) - sub[-1].total_gas += add_gas + func_sub[-1].total_gas += add_gas add_gas += 30 for sig in sig_utils.generate_default_arg_sigs(_def, external_interfaces, global_ctx): - sig.gas = sub[-1].total_gas + sig.gas = func_sub[-1].total_gas sigs[sig.sig] = sig # Add fallback function @@ -115,9 +116,9 @@ def parse_other_functions( fallback = default_func else: fallback = LLLnode.from_list(["revert", 0, 0], typ=None, annotation="Default function") - sub.append(["seq_unchecked", ["label", "fallback"], fallback]) - o.append(["return", 0, ["lll", sub, 0]]) - return o, sub + main_seq.append(["seq_unchecked", ["label", "fallback"], fallback]) + o.append(["return", 0, ["lll", main_seq, 0]]) + return o, main_seq # Main python parse tree => LLL method From 9539e15011c3d7f6c543ffcc889a7c1b712b718e Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Mon, 15 Feb 2021 02:48:07 +0100 Subject: [PATCH 2/6] feat: place internal functions after fallback function in bytecode --- vyper/parser/parser.py | 44 +++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/vyper/parser/parser.py b/vyper/parser/parser.py index bb3111b075..882b906136 100644 --- a/vyper/parser/parser.py +++ b/vyper/parser/parser.py @@ -89,34 +89,46 @@ def parse_external_interfaces(external_interfaces, global_ctx): def parse_other_functions( o, otherfuncs, sigs, external_interfaces, global_ctx, default_function, is_contract_payable, ): - func_sub = ["seq"] - main_seq = ["seq", func_init_lll(), ["with", "_func_sig", ["mload", 0], func_sub]] + # generate LLL for regular functions + external_func_sub = ["seq"] + internal_func_sub = ["seq"] add_gas = func_init_lll().gas - for _def in otherfuncs: - func_sub.append( - parse_function( - _def, {**{"self": sigs}, **external_interfaces}, global_ctx, is_contract_payable, - ) + for func_node in otherfuncs: + func_lll = parse_function( + func_node, {**{"self": sigs}, **external_interfaces}, global_ctx, is_contract_payable, ) - func_sub[-1].total_gas += add_gas - add_gas += 30 - for sig in sig_utils.generate_default_arg_sigs(_def, external_interfaces, global_ctx): - sig.gas = func_sub[-1].total_gas + if func_lll.context.is_internal: + internal_func_sub.append(func_lll) + else: + external_func_sub.append(func_lll) + add_gas += 30 + func_lll.total_gas += add_gas + for sig in sig_utils.generate_default_arg_sigs(func_node, external_interfaces, global_ctx): + sig.gas = func_lll.total_gas sigs[sig.sig] = sig - # Add fallback function + # generate LLL for fallback function if default_function: - default_func = parse_function( + fallback_lll = parse_function( default_function[0], {**{"self": sigs}, **external_interfaces}, global_ctx, is_contract_payable, ) - fallback = default_func else: - fallback = LLLnode.from_list(["revert", 0, 0], typ=None, annotation="Default function") - main_seq.append(["seq_unchecked", ["label", "fallback"], fallback]) + fallback_lll = LLLnode.from_list(["revert", 0, 0], typ=None, annotation="Default function") + + # bytecode is organized by: external functions, fallback fn, internal functions + # this way we save gas and reduce bytecode by not jumping over internal functions + main_seq = [ + "seq", + func_init_lll(), + ["with", "_func_sig", ["mload", 0], external_func_sub], + ["seq_unchecked", ["label", "fallback"], fallback_lll], + internal_func_sub, + ] + o.append(["return", 0, ["lll", main_seq, 0]]) return o, main_seq From 06c2ea2216dd15482dc2fa217247e668530b4d44 Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Mon, 15 Feb 2021 02:48:31 +0100 Subject: [PATCH 3/6] feat: remove fn selector logic from internal functions --- .../parse_internal_function.py | 44 +++++++------------ 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/vyper/parser/function_definitions/parse_internal_function.py b/vyper/parser/function_definitions/parse_internal_function.py index 31519d1889..60868764a1 100644 --- a/vyper/parser/function_definitions/parse_internal_function.py +++ b/vyper/parser/function_definitions/parse_internal_function.py @@ -209,23 +209,16 @@ def parse_internal_function( _clampers = [["label", _post_callback_ptr]] # Function with default parameters. - o = LLLnode.from_list( + return LLLnode.from_list( [ "seq", sig_chain, - [ - "if", - 0, # can only be jumped into - [ - "seq", - ["seq"] - + nonreentrant_pre - + _clampers - + [parse_body(c, context) for c in code.body] - + nonreentrant_post - + stop_func, - ], - ], + ["seq"] + + nonreentrant_pre + + _clampers + + [parse_body(c, context) for c in code.body] + + nonreentrant_post + + stop_func, ], typ=None, pos=getpos(code), @@ -234,21 +227,14 @@ def parse_internal_function( else: # Function without default parameters. sig_compare, internal_label = get_sig_statements(sig, getpos(code)) - o = LLLnode.from_list( - [ - "if", - sig_compare, - ["seq"] - + [internal_label] - + nonreentrant_pre - + clampers - + [parse_body(c, context) for c in code.body] - + nonreentrant_post - + stop_func, - ], + return LLLnode.from_list( + ["seq"] + + [internal_label] + + nonreentrant_pre + + clampers + + [parse_body(c, context) for c in code.body] + + nonreentrant_post + + stop_func, typ=None, pos=getpos(code), ) - return o - - return o From 58f8dfaca7679782b12b46f815b4bec73ba13afb Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Mon, 15 Feb 2021 03:25:31 +0100 Subject: [PATCH 4/6] fix: gas estimation with new lll shape --- vyper/compiler/utils.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/vyper/compiler/utils.py b/vyper/compiler/utils.py index d187e71618..930f3d3979 100644 --- a/vyper/compiler/utils.py +++ b/vyper/compiler/utils.py @@ -9,10 +9,11 @@ def build_gas_estimates(lll_nodes: LLLnode) -> dict: if len(lll_nodes.args) > 0 and lll_nodes.args[-1].value == "return": lll_nodes = lll_nodes.args[-1].args[1].args[0] - assert lll_nodes.value == "seq" - for arg in lll_nodes.args: - if arg.func_name is not None: - gas_estimates[arg.func_name] = arg.total_gas + external_sub = next((i for i in lll_nodes.args if i.value == "with"), None) + if external_sub: + for func_lll in external_sub.args[-1].args: + if func_lll.func_name is not None: + gas_estimates[func_lll.func_name] = func_lll.total_gas return gas_estimates From 6598cd3af4edcec586458d4fdfb7c4c6c7918dfe Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Mon, 15 Feb 2021 13:20:50 +0100 Subject: [PATCH 5/6] fix: include stop instruction at end of default func lll --- vyper/parser/function_definitions/parse_external_function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/parser/function_definitions/parse_external_function.py b/vyper/parser/function_definitions/parse_external_function.py index 9a7d449203..2ff84f0245 100644 --- a/vyper/parser/function_definitions/parse_external_function.py +++ b/vyper/parser/function_definitions/parse_external_function.py @@ -106,7 +106,7 @@ def parse_external_function( # Is default function. elif sig.is_default_func(): o = LLLnode.from_list( - ["seq"] + clampers + [parse_body(code.body, context)], # type: ignore + ["seq"] + clampers + [parse_body(code.body, context)] + [["stop"]], # type: ignore pos=getpos(code), ) # Is a normal function. From 095d5f8f9e39a2f68c3841fbf8d169b199115c4c Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Mon, 15 Feb 2021 13:34:04 +0100 Subject: [PATCH 6/6] feat: streamline logic for jumping an external function with default args --- .../parse_external_function.py | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/vyper/parser/function_definitions/parse_external_function.py b/vyper/parser/function_definitions/parse_external_function.py index 2ff84f0245..77d76af1f2 100644 --- a/vyper/parser/function_definitions/parse_external_function.py +++ b/vyper/parser/function_definitions/parse_external_function.py @@ -193,23 +193,22 @@ def parse_external_function( ) # Function with default parameters. + function_jump_label = f"{sig.name}_{sig.method_id}_skip" o = LLLnode.from_list( [ "seq", sig_chain, [ - "if", - 0, # can only be jumped into - [ - "seq", - ["label", function_routine], - ["seq"] - + nonreentrant_pre - + clampers - + [parse_body(c, context) for c in code.body] - + nonreentrant_post - + [["stop"]], - ], + "seq", + ["goto", function_jump_label], + ["label", function_routine], + ["seq"] + + nonreentrant_pre + + clampers + + [parse_body(c, context) for c in code.body] + + nonreentrant_post + + [["stop"]], + ["label", function_jump_label], ], ], typ=None,