scalus.uplc.transform

Members list

Type members

Classlikes

class CaseConstrApply(logger: Logger = ...) extends Optimizer

Replace nested Apply with Case/Constr

Replace nested Apply with Case/Constr

For example, replace (apply (apply (apply f a) b) c) with (case (constr 0 [a, b, c]) f). This is more memory/cpu efficient than nested Apply at least in Plutus V3 Plomin HF, protocol version 10.

With current machine costs, Apply costs 100 memory and 16000 cpu, same for Case/Constr. Hence (case (constr 0 [a, b, c]) f) costs 200 memory and 32000 cpu, while (apply (apply (apply f a) b) c) costs 300 memory and 48000 cpu.

Attributes

Companion
object
Supertypes
trait Optimizer
class Object
trait Matchable
class Any

Attributes

Companion
class
Supertypes
class Object
trait Matchable
class Any
Self type
class EtaReduce(logger: Logger = ...) extends Optimizer

Performs eta-reduction on a term.

Performs eta-reduction on a term.

Eta-reduction is the process of removing redundant lambda abstractions from a term. For example, the term λx. f x can be eta-reduced to f but only if

  • x is not free in f
  • f is a pure expression

Purity checking is handled by TermAnalysis.isPure. A term is pure if it does not contain any side effects, such as Error, Force of non-delayed terms, or saturated builtin applications. See TermAnalysis.isPure for comprehensive documentation on purity semantics.

Attributes

See also

TermAnalysis.isPure for purity semantics

Companion
object
Supertypes
trait Optimizer
class Object
trait Matchable
class Any
object EtaReduce

Attributes

Companion
class
Supertypes
class Object
trait Matchable
class Any
Self type
EtaReduce.type
class ForcedBuiltinsExtractor(logger: Logger = ..., exceptBuiltins: Set[DefaultFun] = ...) extends Optimizer

Extract forced builtins to top level

Extract forced builtins to top level

For example, replace (force (force (builtin fstPair))) with (lam builtin_FstPair (builtin_FstPair (pair true false)) (! (! __builtin_FstPair))). This is more memory/cpu efficient than nested Force at least in Plutus V3 Plomin HF, protocol version 10.

With current machine costs, Force costs 100 memory and 16000 cpu, same for Builtin. Hence (lam builtin_FstPair (builtin_FstPair (pair true false)) (! (! __builtin_FstPair))) costs 200 memory and 32000 cpu, while (force (force (builtin fstPair))) costs 300 memory and 48000 cpu.

Attributes

Companion
object
Supertypes
trait Optimizer
class Object
trait Matchable
class Any

Attributes

Companion
class
Supertypes
class Object
trait Matchable
class Any
Self type
class Inliner(logger: Logger = ...) extends Optimizer

Optimizer that performs function inlining, beta-reduction, and dead code elimination.

Optimizer that performs function inlining, beta-reduction, and dead code elimination.

The Inliner performs several transformations:

  • '''Beta-reduction''': Replaces function application with direct substitution when safe
  • '''Identity function inlining''': Eliminates identity functions like λx.x
  • '''Dead code elimination''': Removes unused lambda parameters when the argument is pure
  • '''Small value inlining''': Inlines variables, small constants, and builtins
  • '''Force/Delay elimination''': Simplifies Force(Delay(t)) to t

==Inlining Strategy==

The inliner uses occurrence counting and purity analysis to decide what is safe to inline:

  • Variables, builtins, and small constants (≤64 bits) can be duplicated safely
  • Larger values are only inlined if used once
  • Pure unused arguments are eliminated entirely

==Example==

// Input: (λx. λy. x) 42 100
// After inlining identity and dead code elimination:
// Output: 42

val inliner = new Inliner()
val optimized = inliner(term)
// Check what was optimized
println(inliner.logs.mkString("\n"))

==Implementation Details==

The inliner performs capture-avoiding substitution to prevent variable capture during beta-reduction. It maintains an environment of inlined bindings and recursively processes the term tree.

Value parameters

logger

Logger for tracking inlining operations (defaults to new Log())

Attributes

See also

TermAnalysis.isPure for purity analysis used in dead code elimination

Optimizer for the base optimizer trait

Companion
object
Supertypes
trait Optimizer
class Object
trait Matchable
class Any
object Inliner

Companion object providing convenient factory methods for the Inliner.

Companion object providing convenient factory methods for the Inliner.

Attributes

Companion
class
Supertypes
class Object
trait Matchable
class Any
Self type
Inliner.type
object NoopOptimizer extends Optimizer

No-op optimizer that returns the term unchanged without any optimizations.

No-op optimizer that returns the term unchanged without any optimizations.

This is useful as a default when optimization is disabled or as a placeholder in testing.

Attributes

Supertypes
trait Optimizer
class Object
trait Matchable
class Any
Self type
trait Optimizer

Base trait for UPLC term optimizers.

Base trait for UPLC term optimizers.

An optimizer transforms a UPLC (Untyped Plutus Core) term into an equivalent but more efficient term. Optimizers can perform various transformations such as:

  • Dead code elimination
  • Function inlining and beta-reduction
  • Eta-reduction (removing redundant lambda abstractions)
  • Constant folding
  • Conversion of lazy evaluation to strict evaluation when safe

==Usage==

Optimizers are typically used in optimization pipelines (see V1V2Optimizer and V3Optimizer) where multiple optimization passes are applied sequentially:

val optimizer = new StrictIf()
val optimizedTerm = optimizer(term)
println(s"Optimizations applied: ${optimizer.logs.mkString(", ")}")

==Implementation Notes==

Optimizer implementations should:

  • Accept a scalus.uplc.eval.Logger as constructor parameter for logging optimizations
  • Be deterministic: applying the same optimizer to the same term should always produce the same result
  • Preserve semantics: the optimized term must be equivalent to the original term
  • Log all applied optimizations for debugging and analysis

Attributes

See also

StrictIf for lazy-to-strict if-then-else conversion

EtaReduce for eta-reduction optimization

Inliner for function inlining and dead code elimination

ForcedBuiltinsExtractor for extracting forced builtins to top level

CaseConstrApply for optimizing nested Apply with Case/Constr (Plutus V3)

Supertypes
class Object
trait Matchable
class Any
Known subtypes
class EtaReduce
class Inliner
object NoopOptimizer
class StrictIf
class V3Optimizer
Show all
class StrictIf(logger: Logger = ...) extends Optimizer

Optimizes if-then-else expressions by converting lazy branches to strict evaluation when safe.

Optimizes if-then-else expressions by converting lazy branches to strict evaluation when safe.

==Background==

UPLC (Untyped Plutus Core) uses strict evaluation: all function arguments are evaluated before the function is applied. However, for conditional expressions using IfThenElse, we need lazy evaluation to avoid evaluating both branches.

The standard compilation generates:

Force(Apply(Apply(Apply(Force(Builtin(IfThenElse)), condition), Delay(thenBranch)), Delay(elseBranch)))

This pattern has a cost:

  • 2 Delay nodes to suspend branch evaluation
  • 1 Force node to evaluate the selected branch
  • Total: '''3 extra term node evaluations per if-then-else'''

==The Optimization==

When both branches are "simple values" that evaluate to exactly 1 term, we can safely evaluate them strictly, removing the Delay/Force overhead:

Apply(Apply(Apply(Force(Builtin(IfThenElse)), condition), thenBranch), elseBranch)

This saves 3 term node evaluations, improving performance without changing semantics.

==Simple Values (Single-Term Evaluation)==

A term is considered "simple" if it evaluates to exactly 1 term:

  • Var: Variable lookup (1 term)
  • Const: Constant value (1 term)
  • LamAbs: Lambda abstraction - not executed until applied (1 term)
  • Builtin: Builtin function reference - not executed until applied (1 term)
  • Delay: Suspended computation - not executed (1 term)
  • Constr(_, Nil): Empty constructor (1 term)

Terms that are NOT simple (multi-term evaluation):

  • Apply: Requires evaluating function + argument (3+ terms)
  • Force: Requires evaluating the forced term (2+ terms)
  • Case: Requires evaluating scrutinee + pattern matching (2+ terms)
  • Constr(_, args) with args: Requires evaluating each argument (1 + n terms)
  • Error: Will fail immediately

==Example==

// Original: if condition then 42 else 100
Force(Apply(Apply(Apply(Force(Builtin(IfThenElse)), condition), Delay(Const(42))), Delay(Const(100))))
// Cost: 5 term evaluations (Force + Apply + Apply + Apply + Force + 2 Delay + selected Const)

// Optimized: both branches are constants (simple values)
Apply(Apply(Apply(Force(Builtin(IfThenElse)), condition), Const(42)), Const(100))
// Cost: 2 term evaluations (Force + Apply + Apply + Apply + selected Const)
// Savings: 3 term evaluations (2 Delay + 1 Force)

Attributes

See also

scalus.uplc.transform.EtaReduce for another UPLC optimization

scalus.uplc.transform.Inliner for beta-reduction and dead code elimination

Companion
object
Supertypes
trait Optimizer
class Object
trait Matchable
class Any
object StrictIf

Attributes

Companion
class
Supertypes
class Object
trait Matchable
class Any
Self type
StrictIf.type
object TermAnalysis

Static analysis utilities for UPLC terms.

Static analysis utilities for UPLC terms.

Provides analysis methods for determining properties of UPLC terms that are useful for optimization and transformation passes.

Attributes

Supertypes
class Object
trait Matchable
class Any
Self type
class V1V2Optimizer extends Optimizer

Attributes

Supertypes
trait Optimizer
class Object
trait Matchable
class Any
class V3Optimizer extends Optimizer

Attributes

Supertypes
trait Optimizer
class Object
trait Matchable
class Any