Skip to content

Commit

Permalink
fix: ensure expressions are only evaluated once
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel-makerx committed Nov 12, 2024
1 parent 395fb6d commit db415ae
Showing 1 changed file with 34 additions and 4 deletions.
38 changes: 34 additions & 4 deletions src/puya/ir/builder/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ def __init__(
self.context = context.for_function(function, subroutine, self)
self._itxn = InnerTransactionBuilder(self.context)
self._single_eval_cache = dict[awst_nodes.SingleEvaluation, TExpression]()
self._visited_exprs = dict[int, TExpression]()

@classmethod
def build_body(
Expand Down Expand Up @@ -1022,7 +1023,7 @@ def visit_loop_continue(self, statement: awst_nodes.LoopContinue) -> TStatement:

def visit_expression_statement(self, statement: awst_nodes.ExpressionStatement) -> TStatement:
# NOTE: popping of ignored return values should happen at code gen time
result = statement.expr.accept(self)
result = self._visit_and_check_for_double_eval(statement.expr)
if result is None:
wtype = statement.expr.wtype
match wtype:
Expand Down Expand Up @@ -1195,18 +1196,47 @@ def visit_and_materialise_single(
def visit_and_materialise(
self, expr: awst_nodes.Expression, temp_description: str | Sequence[str] = "tmp"
) -> Sequence[Value]:
value_provider = self.visit_expr(expr)
return self.materialise_value_provider(value_provider, description=temp_description)
value_seq_or_provider = self._visit_and_check_for_double_eval(expr, temp_description)
if value_seq_or_provider is None:
raise InternalError(
"No value produced by expression IR conversion", expr.source_location
)
return self.materialise_value_provider(value_seq_or_provider, description=temp_description)

def visit_expr(self, expr: awst_nodes.Expression) -> ValueProvider:
"""Visit the expression and ensure result is not None"""
value_seq_or_provider = expr.accept(self)
value_seq_or_provider = self._visit_and_check_for_double_eval(expr)
if value_seq_or_provider is None:
raise InternalError(
"No value produced by expression IR conversion", expr.source_location
)
return value_seq_or_provider

def _visit_and_check_for_double_eval(
self, expr: awst_nodes.Expression, desc: str | Sequence[str] | None = None
) -> ValueProvider | None:
try:
result = self._visited_exprs[id(expr)]
except KeyError:
pass
else:
if isinstance(result, ValueProvider) and not isinstance(result, ValueTuple | Value):
raise InternalError(
"double evaluation of expression without materialization", expr.source_location
)
return result
source = expr.accept(self)
if desc is None or source is None or not source.types or isinstance(source, Value):
result = source
else:
values = self.materialise_value_provider(source, description=desc)
if len(values) == 1:
(result,) = values
else:
result = ValueTuple(values=values, source_location=expr.source_location)
self._visited_exprs[id(expr)] = result
return result

def materialise_value_provider(
self, provider: ValueProvider, description: str | Sequence[str]
) -> list[Value]:
Expand Down

0 comments on commit db415ae

Please sign in to comment.