Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/vyperlang/vyper into fix/…
Browse files Browse the repository at this point in the history
…slice_folded
  • Loading branch information
tserg committed Nov 21, 2024
2 parents 9c4722a + e0fc53a commit 716de02
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 50 deletions.
7 changes: 6 additions & 1 deletion docs/compiling-a-contract.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Include the ``-f`` flag to specify which output formats to return. Use ``vyper -

.. code:: shell
$ vyper -f abi,abi_python,bytecode,bytecode_runtime,blueprint_bytecode,interface,external_interface,ast,annotated_ast,integrity,ir,ir_json,ir_runtime,asm,opcodes,opcodes_runtime,source_map,source_map_runtime,archive,solc_json,method_identifiers,userdoc,devdoc,metadata,combined_json,layout yourFileName.vy
$ vyper -f abi,abi_python,bb,bb_runtime,bytecode,bytecode_runtime,blueprint_bytecode,cfg,cfg_runtime,interface,external_interface,ast,annotated_ast,integrity,ir,ir_json,ir_runtime,asm,opcodes,opcodes_runtime,source_map,source_map_runtime,archive,solc_json,method_identifiers,userdoc,devdoc,metadata,combined_json,layout yourFileName.vy
.. note::
The ``opcodes`` and ``opcodes_runtime`` output of the compiler has been returning incorrect opcodes since ``0.2.0`` due to a lack of 0 padding (patched via `PR 3735 <https://github.com/vyperlang/vyper/pull/3735>`_). If you rely on these functions for debugging, please use the latest patched versions.
Expand Down Expand Up @@ -134,6 +134,11 @@ In codesize optimized mode, the compiler will try hard to minimize codesize by
* out-lining code, and
* using more loops for data copies.

Enabling Experimental Code Generation
===========================

When compiling, you can use the CLI flag ``--experimental-codegen`` or its alias ``--venom`` to activate the new `Venom IR <https://github.com/vyperlang/vyper/blob/master/vyper/venom/README.md>`_.
Venom IR is inspired by LLVM IR and enables new advanced analysis and optimizations.

.. _evm-version:

Expand Down
10 changes: 10 additions & 0 deletions docs/structure-of-a-contract.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ EVM Version

The EVM version can be set with the ``evm-version`` pragma, which is documented in :ref:`evm-version`.

Experimental Code Generation
-----------------
The new experimental code generation feature can be activated using the following directive:

.. code-block:: vyper
#pragma experimental-codegen
Alternatively, you can use the alias ``"venom"`` instead of ``"experimental-codegen"`` to enable this feature.

Imports
=======

Expand Down
6 changes: 5 additions & 1 deletion vyper/cli/vyper_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,17 @@
layout - Storage layout of a Vyper contract
ast - AST (not yet annotated) in JSON format
annotated_ast - Annotated AST in JSON format
cfg - Control flow graph of deployable bytecode
cfg_runtime - Control flow graph of runtime bytecode
interface - Vyper interface of a contract
external_interface - External interface of a contract, used for outside contract calls
opcodes - List of opcodes as a string
opcodes_runtime - List of runtime opcodes as a string
ir - Intermediate representation in list format
ir_json - Intermediate representation in JSON format
ir_runtime - Intermediate representation of runtime bytecode in list format
bb - Basic blocks of Venom IR for deployable bytecode
bb_runtime - Basic blocks of Venom IR for runtime bytecode
asm - Output the EVM assembly of the deployable bytecode
integrity - Output the integrity hash of the source code
archive - Output the build as an archive file
Expand Down Expand Up @@ -177,7 +181,7 @@ def _parse_args(argv):
parser.add_argument(
"--experimental-codegen",
"--venom",
help="The compiler use the new IR codegen. This is an experimental feature.",
help="The compiler uses the new IR codegen. This is an experimental feature.",
action="store_true",
dest="experimental_codegen",
)
Expand Down
2 changes: 1 addition & 1 deletion vyper/venom/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ An operand can be a label, a variable, or a literal.
By convention, variables have a `%-` prefix, e.g. `%1` is a valid variable. However, the prefix is not required.

## Instructions
To enable Venom IR in Vyper, use the `--experimental-codegen` flag. To view the Venom IR output, use `-f bb_runtime` for the runtime code, or `-f bb` to see the deploy code. To get a dot file (for use e.g. with `xdot -`), use `-f cfg` or `-f cfg_runtime`.
To enable Venom IR in Vyper, use the `--experimental-codegen` CLI flag or its alias `--venom`, or the corresponding pragma statements (e.g. `#pragma experimental-codegen`). To view the Venom IR output, use `-f bb_runtime` for the runtime code, or `-f bb` to see the deploy code. To get a dot file (for use e.g. with `xdot -`), use `-f cfg` or `-f cfg_runtime`.

Assembly can be inspected with `-f asm`, whereas an opcode view of the final bytecode can be seen with `-f opcodes` or `-f opcodes_runtime`, respectively.

Expand Down
25 changes: 25 additions & 0 deletions vyper/venom/basicblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@

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

COMPARATOR_INSTRUCTIONS = ("gt", "lt", "sgt", "slt")

if TYPE_CHECKING:
from vyper.venom.function import IRFunction

Expand Down Expand Up @@ -230,6 +232,14 @@ def is_volatile(self) -> bool:
def is_commutative(self) -> bool:
return self.opcode in COMMUTATIVE_INSTRUCTIONS

@property
def is_comparator(self) -> bool:
return self.opcode in COMPARATOR_INSTRUCTIONS

@property
def flippable(self) -> bool:
return self.is_commutative or self.is_comparator

@property
def is_bb_terminator(self) -> bool:
return self.opcode in BB_TERMINATORS
Expand Down Expand Up @@ -282,6 +292,21 @@ def get_outputs(self) -> list[IROperand]:
"""
return [self.output] if self.output else []

def flip(self):
"""
Flip operands for commutative or comparator opcodes
"""
assert self.flippable
self.operands.reverse()

if self.is_commutative:
return

if self.opcode in ("gt", "sgt"):
self.opcode = self.opcode.replace("g", "l")
else:
self.opcode = self.opcode.replace("l", "g")

def replace_operands(self, replacements: dict) -> None:
"""
Update operands with replacements.
Expand Down
86 changes: 39 additions & 47 deletions vyper/venom/passes/dft.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,72 +2,56 @@

import vyper.venom.effects as effects
from vyper.utils import OrderedSet
from vyper.venom.analysis import DFGAnalysis, IRAnalysesCache, LivenessAnalysis
from vyper.venom.analysis import DFGAnalysis, LivenessAnalysis
from vyper.venom.basicblock import IRBasicBlock, IRInstruction
from vyper.venom.function import IRFunction
from vyper.venom.passes.base_pass import IRPass


class DFTPass(IRPass):
function: IRFunction
inst_offspring: dict[IRInstruction, OrderedSet[IRInstruction]]
data_offspring: dict[IRInstruction, OrderedSet[IRInstruction]]
visited_instructions: OrderedSet[IRInstruction]
ida: dict[IRInstruction, OrderedSet[IRInstruction]]

def __init__(self, analyses_cache: IRAnalysesCache, function: IRFunction):
super().__init__(analyses_cache, function)
self.inst_offspring = {}
# "data dependency analysis"
dda: dict[IRInstruction, OrderedSet[IRInstruction]]
# "effect dependency analysis"
eda: dict[IRInstruction, OrderedSet[IRInstruction]]

def run_pass(self) -> None:
self.inst_offspring = {}
self.data_offspring = {}
self.visited_instructions: OrderedSet[IRInstruction] = OrderedSet()

self.dfg = self.analyses_cache.request_analysis(DFGAnalysis)
basic_blocks = list(self.function.get_basic_blocks())

self.function.clear_basic_blocks()
for bb in basic_blocks:
for bb in self.function.get_basic_blocks():
self._process_basic_block(bb)

self.analyses_cache.invalidate_analysis(LivenessAnalysis)

def _process_basic_block(self, bb: IRBasicBlock) -> None:
self.function.append_basic_block(bb)

self._calculate_dependency_graphs(bb)
self.instructions = list(bb.pseudo_instructions)
non_phi_instructions = list(bb.non_phi_instructions)

self.visited_instructions = OrderedSet()
for inst in non_phi_instructions:
self._calculate_instruction_offspring(inst)
for inst in bb.instructions:
self._calculate_data_offspring(inst)

# Compute entry points in the graph of instruction dependencies
entry_instructions: OrderedSet[IRInstruction] = OrderedSet(non_phi_instructions)
for inst in non_phi_instructions:
to_remove = self.ida.get(inst, OrderedSet())
if len(to_remove) > 0:
entry_instructions.dropmany(to_remove)
to_remove = self.dda.get(inst, OrderedSet()) | self.eda.get(inst, OrderedSet())
entry_instructions.dropmany(to_remove)

entry_instructions_list = list(entry_instructions)

# Move the terminator instruction to the end of the list
self._move_terminator_to_end(entry_instructions_list)

self.visited_instructions = OrderedSet()
for inst in entry_instructions_list:
self._process_instruction_r(self.instructions, inst)

bb.instructions = self.instructions
assert bb.is_terminated, f"Basic block should be terminated {bb}"

def _move_terminator_to_end(self, instructions: list[IRInstruction]) -> None:
terminator = next((inst for inst in instructions if inst.is_bb_terminator), None)
if terminator is None:
raise ValueError(f"Basic block should have a terminator instruction {self.function}")
instructions.remove(terminator)
instructions.append(terminator)

def _process_instruction_r(self, instructions: list[IRInstruction], inst: IRInstruction):
if inst in self.visited_instructions:
return
Expand All @@ -76,14 +60,23 @@ def _process_instruction_r(self, instructions: list[IRInstruction], inst: IRInst
if inst.is_pseudo:
return

children = list(self.ida[inst])
children = list(self.dda[inst] | self.eda[inst])

def key(x):
cost = inst.operands.index(x.output) if x.output in inst.operands else 0
return cost - len(self.inst_offspring[x]) * 0.5
def cost(x: IRInstruction) -> int | float:
if x in self.eda[inst] or inst.flippable:
ret = -1 * int(len(self.data_offspring[x]) > 0)
else:
assert x in self.dda[inst] # sanity check
assert x.output is not None # help mypy
ret = inst.operands.index(x.output)
return ret

# heuristic: sort by size of child dependency graph
children.sort(key=key)
orig_children = children.copy()
children.sort(key=cost)

if inst.flippable and (orig_children != children):
inst.flip()

for dep_inst in children:
self._process_instruction_r(instructions, dep_inst)
Expand All @@ -92,7 +85,8 @@ def key(x):

def _calculate_dependency_graphs(self, bb: IRBasicBlock) -> None:
# ida: instruction dependency analysis
self.ida = defaultdict(OrderedSet)
self.dda = defaultdict(OrderedSet)
self.eda = defaultdict(OrderedSet)

non_phis = list(bb.non_phi_instructions)

Expand All @@ -106,33 +100,31 @@ def _calculate_dependency_graphs(self, bb: IRBasicBlock) -> None:
for op in inst.operands:
dep = self.dfg.get_producing_instruction(op)
if dep is not None and dep.parent == bb:
self.ida[inst].add(dep)
self.dda[inst].add(dep)

write_effects = inst.get_write_effects()
read_effects = inst.get_read_effects()

for write_effect in write_effects:
if write_effect in last_read_effects:
self.ida[inst].add(last_read_effects[write_effect])
self.eda[inst].add(last_read_effects[write_effect])
last_write_effects[write_effect] = inst

for read_effect in read_effects:
if read_effect in last_write_effects and last_write_effects[read_effect] != inst:
self.ida[inst].add(last_write_effects[read_effect])
self.eda[inst].add(last_write_effects[read_effect])
last_read_effects[read_effect] = inst

def _calculate_instruction_offspring(self, inst: IRInstruction):
if inst in self.inst_offspring:
return self.inst_offspring[inst]
def _calculate_data_offspring(self, inst: IRInstruction):
if inst in self.data_offspring:
return self.data_offspring[inst]

self.inst_offspring[inst] = self.ida[inst].copy()
self.data_offspring[inst] = self.dda[inst].copy()

deps = self.ida[inst]
deps = self.dda[inst]
for dep_inst in deps:
assert inst.parent == dep_inst.parent
if dep_inst.opcode == "store":
continue
res = self._calculate_instruction_offspring(dep_inst)
self.inst_offspring[inst] |= res
res = self._calculate_data_offspring(dep_inst)
self.data_offspring[inst] |= res

return self.inst_offspring[inst]
return self.data_offspring[inst]

0 comments on commit 716de02

Please sign in to comment.