Skip to content

Constant expression hoisting for iteration loops #60

Description

@djdarcy

Constant expression hoisting for iteration loops

Problem

When an expression like does_exist primesum(n,2) == trisum(666) is evaluated, the RHS trisum(666) contains no free variables — it's a constant. But the current evaluation engine recomputes it on every iteration of n, turning an O(1) computation into O(N) redundant calls.

For cheap functions this is negligible. For expensive ones like trisum(666) (which computes a 98-digit number by summing 36 rows of base-666 digit arrangements), the overhead is significant — especially when N could be in the billions.

Proposed solution

Constant folding / expression hoisting: Before entering the iteration loop, walk the AST and identify subexpressions that contain no free variables (no iteration variables like n, m, etc.). Evaluate these once and replace them with their computed values.

Detection strategy

def find_free_vars(node) -> set:
    """Return set of variable names referenced in this AST node."""
    if isinstance(node, Variable): return {node.name}
    if isinstance(node, FunctionCall): return union(find_free_vars(arg) for arg in node.args)
    if isinstance(node, BinaryOp): return find_free_vars(node.left) | find_free_vars(node.right)
    ...

def hoist_constants(expr, iter_vars):
    """Pre-evaluate subexpressions with no free iteration variables."""
    free = find_free_vars(expr)
    if not (free & iter_vars):  # No overlap with iteration variables
        return Literal(evaluate(expr))  # Compute once, replace with constant
    # Otherwise recurse into children
    ...

Examples

Expression Free vars Hoisted?
primesum(n,2) {n} No — depends on iteration var
trisum(666) {} Yes — computed once
primesum(n,2) == trisum(666) LHS: {n}, RHS: {} RHS hoisted
primesum(n, m) {n, m} No — both are iteration vars
tri(36) + 1 {} Yes — entire subexpression hoisted
primesum(n, 2) == 666 LHS: {n}, RHS: {} RHS already a literal (no change)

Implementation location

The hoisting pass should run in find_matches() (in prime-square-sum.py) after parsing and before the iteration loop begins. The AST is already available from the grammar parser.

Impact

  • Core use case: does_exist primesum(n,2) == trisum(666) — the project's central question. Without hoisting, this recomputes a 98-digit number billions of times.
  • General benefit: Any expression mixing iteration variables with constant function calls benefits automatically.
  • No API change: Pure internal optimization, transparent to users.

Related

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions