Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat[venom]: inline pass and call conv #4468

Draft
wants to merge 91 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
8238831
init
harkal Dec 26, 2024
c54273c
add fcg
harkal Dec 26, 2024
5565d4c
func inliner
harkal Dec 26, 2024
5f01394
params
harkal Dec 26, 2024
6b88eed
helpers
harkal Dec 26, 2024
2b19059
global opti
harkal Dec 26, 2024
7d144de
work
harkal Dec 26, 2024
fe9f39a
refactor
harkal Dec 27, 2024
051225a
float
harkal Dec 27, 2024
1997b25
codename `asylum`
harkal Dec 27, 2024
027a2eb
more conservative
harkal Dec 28, 2024
d79c6d9
disable load elimination
harkal Dec 28, 2024
ca1ba5f
export func for repl
harkal Dec 28, 2024
7508436
first function is entry
harkal Dec 28, 2024
1257fd4
debuging aids
harkal Dec 28, 2024
d4893bd
work
harkal Dec 28, 2024
a1f76b6
fix grammar
harkal Dec 28, 2024
b4b4ae6
test
harkal Dec 28, 2024
a4d4ea9
lint
harkal Dec 28, 2024
7c79be7
mem2var debug
charles-cooper Dec 28, 2024
29ece7c
update varname in mem2var
charles-cooper Dec 28, 2024
542c115
invalidate dfg
harkal Dec 28, 2024
a376e88
polish store elimination
charles-cooper Dec 28, 2024
33fd262
test update
harkal Dec 28, 2024
30e0c79
remove superflus passes
harkal Dec 28, 2024
c6c3c8a
testing
harkal Dec 28, 2024
020b9b2
Merge branch 'master' into feat/inline_pass
harkal Dec 28, 2024
2ef4df6
helper
harkal Dec 28, 2024
7341c04
handle reverts
harkal Dec 28, 2024
757563d
add tests
harkal Dec 29, 2024
bf0ebb3
cleanup
harkal Dec 29, 2024
289d683
bounds
harkal Dec 29, 2024
674fb50
refactors
harkal Dec 29, 2024
057246c
Merge branch 'master' into feat/inline_pass
harkal Dec 29, 2024
05adb1a
refactor, lint, cleanup
harkal Dec 29, 2024
4a2041f
debug
harkal Dec 29, 2024
4878398
work
harkal Dec 29, 2024
385e59f
recursive inline out
harkal Dec 30, 2024
af699c6
add has block method
harkal Dec 30, 2024
33d8ffd
make "consts".. "private"
harkal Dec 30, 2024
dab4060
temp
harkal Dec 30, 2024
476b9de
inliner
harkal Dec 30, 2024
6fd42ef
fix var equivalance
harkal Dec 30, 2024
4e602b9
lint
harkal Dec 30, 2024
5d0ac96
comment wip stuff
harkal Dec 30, 2024
828e623
make cost a function
harkal Dec 30, 2024
2d5d872
Merge branch 'master' into feat/inline_pass
harkal Jan 4, 2025
ad2e8b7
dfg equiv
harkal Jan 4, 2025
6bf03ec
drop var equivalence
harkal Jan 4, 2025
6ca95a6
lint
harkal Jan 4, 2025
82a5ebc
Merge branch 'master' into feat/inline_pass
harkal Jan 16, 2025
bb31c40
passthroug
harkal Jan 21, 2025
9d17a79
assert
harkal Jan 21, 2025
aba2afc
params
harkal Jan 21, 2025
2e6c79b
temp
harkal Jan 22, 2025
7380f52
Merge branch 'master' into feat/inline_pass
harkal Jan 22, 2025
29c1302
fix edge case in mem2var
harkal Jan 22, 2025
cac9d4c
post merge
harkal Jan 22, 2025
f04400c
temp
harkal Jan 22, 2025
7fe033d
tmep
harkal Jan 22, 2025
733e3b2
fix
harkal Jan 22, 2025
e54bd0f
comment out
harkal Jan 22, 2025
3cdbb48
dfg linting
harkal Jan 23, 2025
70ff390
lint
harkal Jan 23, 2025
eaef2a4
sccp fixes
harkal Jan 23, 2025
abd12cd
add back
harkal Jan 27, 2025
d98dbcd
enable more
harkal Jan 27, 2025
317cfb5
fix
harkal Jan 27, 2025
0234851
new failing test
harkal Jan 27, 2025
bd82d5c
venom -> evm changed
harkal Jan 27, 2025
abba8ca
internal update test case
harkal Jan 27, 2025
3552bae
get param by name
harkal Jan 27, 2025
b61009b
store
harkal Jan 27, 2025
7d44552
fix
harkal Jan 27, 2025
75d1a44
start
harkal Jan 28, 2025
9f3eac0
Revert "start"
harkal Jan 28, 2025
0b50b4b
new cc on all prims
harkal Jan 28, 2025
3093987
bugfix
harkal Jan 28, 2025
4e19321
fixes and cleanup
harkal Jan 29, 2025
fb21434
param support in inliner
harkal Jan 29, 2025
b1df259
failing test
harkal Jan 29, 2025
642dcb2
no return handling
harkal Jan 29, 2025
cf743a9
Merge branch 'master' into feat/inline_pass
harkal Jan 29, 2025
83134fa
lint
harkal Jan 29, 2025
678c80c
wrong paste
harkal Jan 29, 2025
6d43af9
wip
harkal Feb 2, 2025
6adc1c1
wip
harkal Feb 2, 2025
7d6a204
Revert "wip"
harkal Feb 2, 2025
a5e4ed8
Revert "wip"
harkal Feb 2, 2025
1941ced
Merge remote-tracking branch 'origin-vyper/master' into feat/inline_pass
harkal Feb 2, 2025
77cf70a
fix
harkal Feb 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion tests/unit/compiler/asm/test_asm_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,9 @@ def foo():
input_bundle = make_input_bundle({"library.vy": library})
res = compile_code(code, input_bundle=input_bundle, output_formats=["asm"])
asm = res["asm"]
assert "some_function()" in asm

if not experimental_codegen:
assert "some_function()" in asm # Venom function inliner will remove this

assert "unused1()" not in asm
assert "unused2()" not in asm
Expand Down
9 changes: 8 additions & 1 deletion tests/unit/compiler/test_source_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,23 @@ def test_jump_map(optimize, experimental_codegen):
jump_map = source_map["pc_jump_map"]

expected_jumps = 1
expected_internals = 2
if optimize == OptimizationLevel.NONE:
# some jumps which don't get optimized out when optimizer is off
# (slightly different behavior depending if venom pipeline is enabled):
if not experimental_codegen:
expected_jumps = 3
else:
expected_jumps = 2
else:
if not experimental_codegen:
expected_internals = 2
else:
expected_jumps = 0
expected_internals = 0

assert len([v for v in jump_map.values() if v == "o"]) == expected_jumps
assert len([v for v in jump_map.values() if v == "i"]) == 2
assert len([v for v in jump_map.values() if v == "i"]) == expected_internals

code_lines = [i + "\n" for i in TEST_CODE.split("\n")]
for pc in [k for k, v in jump_map.items() if v == "o"]:
Expand Down
112 changes: 112 additions & 0 deletions tests/unit/compiler/venom/test_call_conv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
def test_simple_call(get_contract):
code = """
@internal
def name(_name: uint256) -> uint256:
return _name + 1


@external
def foo(x: uint256) -> uint256:
u: uint256 = 1 + x
ret: uint256 = self.name(u + 10)
return ret
"""

c = get_contract(code)
assert c.foo(1) == 13


def test_simple_call_multiple_args_in_call(get_contract):
code = """
@internal
def bar(_name: uint256, _name2: uint256) -> uint256:
return _name + 10

@external
def foo(x: uint256) -> uint256:
ret: uint256 = self.bar(20, 10)
return ret
"""

c = get_contract(code)
assert c.foo(1) == 30


def test_simple_call_multiple_args(get_contract):
code = """
@internal
def bar(_name: uint256, _name2: uint256) -> (uint256, uint256):
return _name + 1, _name + 2

@external
def foo(x: uint256) -> (uint256, uint256):
ret: (uint256, uint256) = self.bar(20, 10)
return ret
"""

c = get_contract(code)
assert c.foo(1) == (21, 22)


def test_call_in_call(get_contract):
code = """
@internal
def _foo(a: uint256) -> uint256:
return a

@external
def foo() -> uint256:
a: uint256 = 1
return self._foo(a)
"""

c = get_contract(code)

assert c.foo() == 1


def test_2d_array_input_1(get_contract):
code = """
@internal
def test_input(arr: DynArray[DynArray[int128, 2], 1]) -> DynArray[DynArray[int128, 2], 1]:
return arr

@external
def test_values(arr: DynArray[DynArray[int128, 2], 1]) -> DynArray[DynArray[int128, 2], 1]:
return self.test_input(arr)
"""

c = get_contract(code)
assert c.test_values([[1, 2]]) == ([[1, 2]])


def test_call_with_unused_params(get_contract):
code = """
@internal
def _foo3(a: uint256, b: uint256) -> uint256:
return 42

@external
def foo() -> uint256:
return self._foo3(9, 11)
"""

c = get_contract(code)

assert c.foo() == 42


def test_internal_assign(get_contract):
code = """
@internal
def foo(x: uint256) -> uint256:
x = 10
return x

@external
def bar(x: uint256) -> uint256:
return self.foo(x)
"""
c = get_contract(code)

assert c.bar(70) == 10
64 changes: 64 additions & 0 deletions tests/unit/compiler/venom/test_inliner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import pytest

from tests.evm_backends.base_env import ExecutionReverted


def test_call_in_call(get_contract):
code = """
@internal
def _foo(a: uint256,) -> uint256:
return 1 + a

@internal
def _foo2() -> uint256:
return 4

@external
def foo() -> uint256:
return self._foo(self._foo2())
"""

c = get_contract(code)
assert c.foo() == 5


def test_call_in_call_with_raise(get_contract):
code = """
@internal
def sum(a: uint256) -> uint256:
if a > 1:
return a + 1
raise

@internal
def middle(a: uint256) -> uint256:
return self.sum(a)

@external
def test(a: uint256) -> uint256:
return self.middle(a)
"""

c = get_contract(code)

assert c.test(2) == 3

with pytest.raises(ExecutionReverted):
c.test(0)


def test_inliner_with_unused_param(get_contract):
code = """
data: public(uint256)

@internal
def _foo(start: uint256, length: uint256):
self.data = start

@external
def foo(x: uint256, y: uint256):
self._foo(x, y)
"""

c = get_contract(code)
c.foo(1, 2)
38 changes: 38 additions & 0 deletions tests/unit/compiler/venom/test_variable_equivalence.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import itertools

from tests.venom_utils import parse_from_basic_block
from vyper.venom.analysis import DFGAnalysis, IRAnalysesCache
from vyper.venom.basicblock import IRVariable
from vyper.venom.context import IRContext


def _entry_fn(ctx: "IRContext"):
# TODO: make this part of IRContext
return next(iter(ctx.functions.values()))


def test_variable_equivalence_dfg_order():
a_code = """
main:
%1 = 1
%2 = %1
%3 = %2
"""
# technically invalid code, but variable equivalence should handle
# it either way
b_code = """
main:
%3 = %2
%2 = %1
%1 = 1
"""
fn1 = _entry_fn(parse_from_basic_block(a_code))
fn2 = _entry_fn(parse_from_basic_block(b_code))

dfg1 = IRAnalysesCache(fn1).request_analysis(DFGAnalysis)
dfg2 = IRAnalysesCache(fn2).request_analysis(DFGAnalysis)

vars_ = map(IRVariable, ("%1", "%2", "%3"))
for var1, var2 in itertools.combinations(vars_, 2):
assert dfg1.are_equivalent(var1, var2)
assert dfg2.are_equivalent(var1, var2)
3 changes: 3 additions & 0 deletions vyper/codegen/function_definitions/internal_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ def generate_ir_for_internal_function(

ir_node = IRnode.from_list(["seq", body, cleanup_routine])

ir_node.passthrough_metadata["func_t"] = func_t
ir_node.passthrough_metadata["context"] = context

# tag gas estimate and frame info
func_t._ir_info.gas_estimate = ir_node.gas
tag_frame_info(func_t, context)
Expand Down
2 changes: 2 additions & 0 deletions vyper/codegen/self_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,6 @@ def ir_for_self_call(stmt_expr, context):
)
o.is_self_call = True
o.invoked_function_ir = func_t._ir_info.func_ir
o.passthrough_metadata["func_t"] = func_t
o.passthrough_metadata["args_ir"] = args_ir
return o
2 changes: 1 addition & 1 deletion vyper/compiler/phases.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ def generate_ir_nodes(global_ctx: ModuleT, settings: Settings) -> tuple[IRnode,

with anchor_settings(settings):
ir_nodes, ir_runtime = module.generate_ir_for_module(global_ctx)
if settings.optimize != OptimizationLevel.NONE:
if settings.optimize != OptimizationLevel.NONE and not settings.experimental_codegen:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ir_nodes = optimizer.optimize(ir_nodes)
ir_runtime = optimizer.optimize(ir_runtime)
return ir_nodes, ir_runtime
Expand Down
11 changes: 11 additions & 0 deletions vyper/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -693,3 +693,14 @@ def safe_relpath(path):
# on Windows, if path and curdir are on different drives, an exception
# can be thrown
return path


def all_nonempty(iter):
"""
This function checks if all elements in the given `iterable` are truthy,
similar to Python's built-in `all()` function. However, `all_nonempty`
diverges by returning `False` if the iterable is empty, whereas `all()`
would return `True` for an empty iterable.
"""
items = list(iter)
return len(items) > 0 and all(items)
24 changes: 19 additions & 5 deletions vyper/venom/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
BranchOptimizationPass,
DFTPass,
FloatAllocas,
FuncInlinerPass,
LoadElimination,
LowerDloadPass,
MakeSSA,
Expand Down Expand Up @@ -46,12 +47,10 @@ def generate_assembly_experimental(
return compiler.generate_evm(optimize == OptimizationLevel.NONE)


def _run_passes(fn: IRFunction, optimize: OptimizationLevel) -> None:
def _run_passes(fn: IRFunction, optimize: OptimizationLevel, ac: IRAnalysesCache) -> None:
# Run passes on Venom IR
# TODO: Add support for optimization levels

ac = IRAnalysesCache(fn)

FloatAllocas(ac, fn).run_pass()

SimplifyCFGPass(ac, fn).run_pass()
Expand Down Expand Up @@ -94,14 +93,29 @@ def _run_passes(fn: IRFunction, optimize: OptimizationLevel) -> None:
DFTPass(ac, fn).run_pass()


def run_passes_on(ctx: IRContext, optimize: OptimizationLevel):
def _run_global_passes(ctx: IRContext, optimize: OptimizationLevel, ir_analyses: dict) -> None:
FuncInlinerPass(ir_analyses, ctx).run_pass()


def run_passes_on(ctx: IRContext, optimize: OptimizationLevel) -> None:
ir_analyses = {}
for fn in ctx.functions.values():
_run_passes(fn, optimize)
ir_analyses[fn] = IRAnalysesCache(fn)

_run_global_passes(ctx, optimize, ir_analyses)

ir_analyses = {}
for fn in ctx.functions.values():
ir_analyses[fn] = IRAnalysesCache(fn)

for fn in ctx.functions.values():
_run_passes(fn, optimize, ir_analyses[fn])


def generate_ir(ir: IRnode, optimize: OptimizationLevel) -> IRContext:
# Convert "old" IR to "new" IR
ctx = ir_node_to_venom(ir)

run_passes_on(ctx, optimize)

return ctx
1 change: 0 additions & 1 deletion vyper/venom/analysis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@
from .cfg import CFGAnalysis
from .dfg import DFGAnalysis
from .dominators import DominatorTreeAnalysis
from .equivalent_vars import VarEquivalenceAnalysis
from .liveness import LivenessAnalysis
4 changes: 4 additions & 0 deletions vyper/venom/analysis/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,8 @@ def force_analysis(self, analysis_cls: Type[IRAnalysis], *args, **kwargs):
assert issubclass(analysis_cls, IRAnalysis), f"{analysis_cls} is not an IRAnalysis"
if analysis_cls in self.analyses_cache:
self.invalidate_analysis(analysis_cls)

for analysis in self.analyses_cache.values():
self.request_analysis(analysis.__class__)

return self.request_analysis(analysis_cls, *args, **kwargs)
11 changes: 7 additions & 4 deletions vyper/venom/analysis/cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,13 @@ def dfs_walk(self) -> Iterator[IRBasicBlock]:
def invalidate(self):
from vyper.venom.analysis import DFGAnalysis, DominatorTreeAnalysis, LivenessAnalysis

fn = self.function
for bb in fn.get_basic_blocks():
bb.cfg_in = OrderedSet()
bb.cfg_out = OrderedSet()
bb.out_vars = OrderedSet()

self.analyses_cache.invalidate_analysis(DominatorTreeAnalysis)
self.analyses_cache.invalidate_analysis(LivenessAnalysis)

self._dfs = None

# be conservative - assume cfg invalidation invalidates dfg
self.analyses_cache.invalidate_analysis(DFGAnalysis)
self._dfs = None
Loading
Loading