Skip to content

Commit

Permalink
Merge branch 'master' into perf/liveness2
Browse files Browse the repository at this point in the history
  • Loading branch information
charles-cooper authored Oct 21, 2024
2 parents 87af6df + b3ea663 commit 66bd73e
Show file tree
Hide file tree
Showing 18 changed files with 168 additions and 45 deletions.
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/bug.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
name: Bug Report
about: Any general feedback or bug reports about the Vyper Compiler. No new features proposals.
labels: ["needs triage"]
---

### Version Information
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/vip.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
name: Vyper Improvement Proposal (VIP)
about: This is the suggested template for new VIPs.
labels: ["needs triage"]
---
## Simple Summary
"If you can't explain it simply, you don't understand it well enough." Provide a simplified and layman-accessible explanation of the VIP.
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ jobs:
# need to fetch unshallow so that setuptools_scm can infer the version
fetch-depth: 0

# debug
- name: Git shorthash
run: git rev-parse --short HEAD

- name: Python
uses: actions/setup-python@v5
with:
Expand Down Expand Up @@ -60,6 +64,10 @@ jobs:
# need to fetch unshallow so that setuptools_scm can infer the version
fetch-depth: 0

# debug
- name: Git shorthash
run: git rev-parse --short HEAD

- name: Python
uses: actions/setup-python@v5
with:
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/release-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ jobs:

steps:
- uses: actions/checkout@v4
with:
# fetch unshallow so commit hash matches github release.
# see https://github.com/vyperlang/vyper/blob/8f9a8cac49aafb3fbc9dde78f0f6125c390c32f0/.github/workflows/build.yml#L27-L32
fetch-depth: 0

# debug
- name: Git shorthash
run: git rev-parse --short HEAD

- name: Python
uses: actions/setup-python@v5
Expand Down
4 changes: 2 additions & 2 deletions tests/functional/codegen/features/test_clampers.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ def foo(b: int128[6][1][2]) -> int128[6][1][2]:

c = get_contract(code)
with tx_failed():
_make_tx(env, c.address, "foo(int128[6][1][2]])", values)
_make_tx(env, c.address, "foo(int128[6][1][2])", values)


@pytest.mark.parametrize("value", [0, 1, -1, 2**127 - 1, -(2**127)])
Expand All @@ -453,7 +453,7 @@ def test_int128_dynarray_clamper_failing(env, tx_failed, get_contract, bad_value
# ensure the invalid value is detected at all locations in the array
code = """
@external
def foo(b: int128[5]) -> int128[5]:
def foo(b: DynArray[int128, 5]) -> DynArray[int128, 5]:
return b
"""

Expand Down
28 changes: 28 additions & 0 deletions tests/functional/codegen/modules/test_interface_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,31 @@ def foo() -> bool:
c = get_contract(main, input_bundle=input_bundle)

assert c.foo() is True


def test_import_interface_flags(make_input_bundle, get_contract):
ifaces = """
flag Foo:
BOO
MOO
POO
interface IFoo:
def foo() -> Foo: nonpayable
"""

contract = """
import ifaces
implements: ifaces
@external
def foo() -> ifaces.Foo:
return ifaces.Foo.POO
"""

input_bundle = make_input_bundle({"ifaces.vyi": ifaces})

c = get_contract(contract, input_bundle=input_bundle)

assert c.foo() == 4
13 changes: 11 additions & 2 deletions tests/functional/syntax/test_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,13 +381,22 @@ def test_interfaces_success(good_code):


def test_imports_and_implements_within_interface(make_input_bundle):
interface_code = """
ibar_code = """
@external
def foobar():
...
"""
ifoo_code = """
import bar
input_bundle = make_input_bundle({"foo.vyi": interface_code})
implements: bar
@external
def foobar():
...
"""

input_bundle = make_input_bundle({"foo.vyi": ifoo_code, "bar.vyi": ibar_code})

code = """
import foo as Foo
Expand Down
24 changes: 24 additions & 0 deletions tests/unit/cli/vyper_json/test_compile_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,3 +295,27 @@ def test_relative_import_paths(input_json):
input_json["sources"]["contracts/potato/baz/potato.vy"] = {"content": "from . import baz"}
input_json["sources"]["contracts/potato/footato.vy"] = {"content": "from baz import baz"}
compile_from_input_dict(input_json)


def test_compile_json_with_abi_top(make_input_bundle):
stream = """
{
"abi": [
{
"name": "validate",
"inputs": [
{ "name": "creator", "type": "address" },
{ "name": "token", "type": "address" },
{ "name": "amount_per_second", "type": "uint256" },
{ "name": "reason", "type": "bytes" }
],
"outputs": [{ "name": "max_stream_life", "type": "uint256" }]
}
]
}
"""
code = """
from . import stream
"""
input_bundle = make_input_bundle({"stream.json": stream, "code.vy": code})
vyper.compiler.compile_code(code, input_bundle=input_bundle)
27 changes: 27 additions & 0 deletions tests/unit/compiler/venom/test_algebraic_optimizer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pytest

import vyper
from vyper.venom.analysis import IRAnalysesCache
from vyper.venom.basicblock import IRBasicBlock, IRLabel
from vyper.venom.context import IRContext
Expand Down Expand Up @@ -176,3 +177,29 @@ def test_offsets():
offset_count += 1

assert offset_count == 3


# Test the case of https://github.com/vyperlang/vyper/issues/4288
def test_ssa_after_algebraic_optimization():
code = """
@internal
def _do_math(x: uint256) -> uint256:
value: uint256 = x
result: uint256 = 0
if (x >> 128 != 0):
x >>= 128
if (x >> 64 != 0):
x >>= 64
if 1 < value:
result = 1
return result
@external
def run() -> uint256:
return self._do_math(10)
"""

vyper.compile_code(code, output_formats=["bytecode"])
2 changes: 2 additions & 0 deletions vyper/compiler/input_bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ class ABIInput(CompilerInput):
def try_parse_abi(file_input: FileInput) -> CompilerInput:
try:
s = json.loads(file_input.source_code)
if isinstance(s, dict) and "abi" in s:
s = s["abi"]
return ABIInput(**asdict(file_input), abi=s)
except (ValueError, TypeError):
return file_input
Expand Down
1 change: 1 addition & 0 deletions vyper/semantics/analysis/imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ def _load_import_helper(
file = self.input_bundle.load_file(path.with_suffix(".vyi"))
assert isinstance(file, FileInput) # mypy hint
module_ast = self._ast_from_file(file)
self.resolve_imports(module_ast)

# language does not yet allow recursion for vyi files
# self.resolve_imports(module_ast)
Expand Down
57 changes: 29 additions & 28 deletions vyper/semantics/types/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from vyper.semantics.types.base import TYPE_T, VyperType, is_type_t
from vyper.semantics.types.function import ContractFunctionT
from vyper.semantics.types.primitives import AddressT
from vyper.semantics.types.user import EventT, StructT, _UserType
from vyper.semantics.types.user import EventT, FlagT, StructT, _UserType
from vyper.utils import OrderedSet

if TYPE_CHECKING:
Expand All @@ -45,27 +45,29 @@ def __init__(
functions: dict,
events: dict,
structs: dict,
flags: dict,
) -> None:
validate_unique_method_ids(list(functions.values()))

members = functions | events | structs
members = functions | events | structs | flags

# sanity check: by construction, there should be no duplicates.
assert len(members) == len(functions) + len(events) + len(structs)
assert len(members) == len(functions) + len(events) + len(structs) + len(flags)

super().__init__(functions)

self._helper = VyperType(events | structs)
self._helper = VyperType(events | structs | flags)
self._id = _id
self._helper._id = _id
self.functions = functions
self.events = events
self.structs = structs
self.flags = flags

self.decl_node = decl_node

def get_type_member(self, attr, node):
# get an event or struct from this interface
# get an event, struct or flag from this interface
return TYPE_T(self._helper.get_member(attr, node))

@property
Expand Down Expand Up @@ -159,12 +161,14 @@ def _from_lists(
interface_name: str,
decl_node: Optional[vy_ast.VyperNode],
function_list: list[tuple[str, ContractFunctionT]],
event_list: list[tuple[str, EventT]],
struct_list: list[tuple[str, StructT]],
event_list: Optional[list[tuple[str, EventT]]] = None,
struct_list: Optional[list[tuple[str, StructT]]] = None,
flag_list: Optional[list[tuple[str, FlagT]]] = None,
) -> "InterfaceT":
functions = {}
events = {}
structs = {}
functions: dict[str, ContractFunctionT] = {}
events: dict[str, EventT] = {}
structs: dict[str, StructT] = {}
flags: dict[str, FlagT] = {}

seen_items: dict = {}

Expand All @@ -175,19 +179,20 @@ def _mark_seen(name, item):
raise NamespaceCollision(msg, item.decl_node, prev_decl=prev_decl)
seen_items[name] = item

for name, function in function_list:
_mark_seen(name, function)
functions[name] = function
def _process(dst_dict, items):
if items is None:
return

for name, event in event_list:
_mark_seen(name, event)
events[name] = event
for name, item in items:
_mark_seen(name, item)
dst_dict[name] = item

for name, struct in struct_list:
_mark_seen(name, struct)
structs[name] = struct
_process(functions, function_list)
_process(events, event_list)
_process(structs, struct_list)
_process(flags, flag_list)

return cls(interface_name, decl_node, functions, events, structs)
return cls(interface_name, decl_node, functions, events, structs, flags)

@classmethod
def from_json_abi(cls, name: str, abi: dict) -> "InterfaceT":
Expand All @@ -214,8 +219,7 @@ def from_json_abi(cls, name: str, abi: dict) -> "InterfaceT":
for item in [i for i in abi if i.get("type") == "event"]:
events.append((item["name"], EventT.from_abi(item)))

structs: list = [] # no structs in json ABI (as of yet)
return cls._from_lists(name, None, functions, events, structs)
return cls._from_lists(name, None, functions, events)

@classmethod
def from_ModuleT(cls, module_t: "ModuleT") -> "InterfaceT":
Expand Down Expand Up @@ -247,8 +251,9 @@ def from_ModuleT(cls, module_t: "ModuleT") -> "InterfaceT":
# these are accessible via import, but they do not show up
# in the ABI json
structs = [(node.name, node._metadata["struct_type"]) for node in module_t.struct_defs]
flags = [(node.name, node._metadata["flag_type"]) for node in module_t.flag_defs]

return cls._from_lists(module_t._id, module_t.decl_node, funcs, events, structs)
return cls._from_lists(module_t._id, module_t.decl_node, funcs, events, structs, flags)

@classmethod
def from_InterfaceDef(cls, node: vy_ast.InterfaceDef) -> "InterfaceT":
Expand All @@ -265,11 +270,7 @@ def from_InterfaceDef(cls, node: vy_ast.InterfaceDef) -> "InterfaceT":
)
functions.append((func_ast.name, ContractFunctionT.from_InterfaceDef(func_ast)))

# no structs or events in InterfaceDefs
events: list = []
structs: list = []

return cls._from_lists(node.name, node, functions, events, structs)
return cls._from_lists(node.name, node, functions)


# Datatype to store all module information.
Expand Down
7 changes: 7 additions & 0 deletions vyper/venom/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ def _run_passes(fn: IRFunction, optimize: OptimizationLevel) -> None:
StoreElimination(ac, fn).run_pass()
SimplifyCFGPass(ac, fn).run_pass()
AlgebraicOptimizationPass(ac, fn).run_pass()
# NOTE: MakeSSA is after algebraic optimization it currently produces
# smaller code by adding some redundant phi nodes. This is not a
# problem for us, but we need to be aware of it, and should be
# removed when the dft pass is fixed to produce the smallest code
# without making the code generation more expensive by running
# MakeSSA again.
MakeSSA(ac, fn).run_pass()
BranchOptimizationPass(ac, fn).run_pass()
RemoveUnusedVariablesPass(ac, fn).run_pass()

Expand Down
5 changes: 4 additions & 1 deletion vyper/venom/analysis/cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ def _topsort_r(self, bb):
self._topsort_r(next_bb)

def invalidate(self):
from vyper.venom.analysis import DominatorTreeAnalysis, LivenessAnalysis
from vyper.venom.analysis import DFGAnalysis, DominatorTreeAnalysis, LivenessAnalysis

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

# be conservative - assume cfg invalidation invalidates dfg
self.analyses_cache.invalidate_analysis(DFGAnalysis)
6 changes: 6 additions & 0 deletions vyper/venom/basicblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@

CFG_ALTERING_INSTRUCTIONS = frozenset(["jmp", "djmp", "jnz"])

COMMUTATIVE_INSTRUCTIONS = frozenset(["add", "mul", "smul", "or", "xor", "and", "eq"])

if TYPE_CHECKING:
from vyper.venom.function import IRFunction

Expand Down Expand Up @@ -235,6 +237,10 @@ def __init__(
def is_volatile(self) -> bool:
return self.opcode in VOLATILE_INSTRUCTIONS

@property
def is_commutative(self) -> bool:
return self.opcode in COMMUTATIVE_INSTRUCTIONS

@property
def is_bb_terminator(self) -> bool:
return self.opcode in BB_TERMINATORS
Expand Down
Loading

0 comments on commit 66bd73e

Please sign in to comment.