This document serves as a guide for users familiar with akarin.Expr who are transitioning to llvmexpr.Expr.
While llvmexpr is designed for full syntax compatibility with akarin's RPN, its enhanced feature set introduce new possibilities and some important distinctions.
Differences
The most significant changes when migrating from akarin.Expr to llvmexpr stem from fundamental differences in the evaluation engine and math library implementations.
1. Evaluation Mode: Always-Float vs. Type-Aware
The primary difference lies in their evaluation modes. llvmexpr operates exclusively in a 32-bit always-float mode. In contrast, akarin.Expr uses a type-aware, integer-preserving mode that only promotes values to floating-point when an operation requires it. The opt parameter in akarin switches how this mode treats pixel data loaded from integer-format clips.
Integer Wrap-around Behavior
akarin's engine tracks the type of each value. If an operation involves only integers, it performs the calculation using 32-bit signed two's complement arithmetic, which enables integer wrap-around.
- In akarin: Wrap-around occurs whenever an operation's inputs are all integers. This is true for integer constants even with opt=0.
- '2147483647 1 +' → int + int → wraps to -2147483648.
llvmexpr does not have this type-aware behavior. It promotes all numeric inputs to 32-bit floats at the earliest opportunity.
- In llvmexpr: The expression '2147483647 1 +' will always be evaluated using floating-point math, resulting in 2147483648.0. Wrap-around is impossible.
- Warning
- Implication: Any expression that relies on integer overflow as a mechanism is not directly portable and will produce fundamentally different results in llvmexpr.
Integer Precision & The Critical Role of akarin's opt Parameter
A standard 32-bit float can only represent all consecutive integers exactly up to ** \(2^{24}\)**.
The opt parameter in akarin controls the initial type of pixel data loaded from integer-format clips (x, y, etc.):
- opt=0 (default): Loads integer pixel data as floats. In this mode, if a clip has more than 24 bits of integer precision, that extra precision is lost immediately upon loading, before any operations are performed.
- opt=1: Loads integer pixel data as integers. This mode preserves the full bit-depth of the source clip, allowing for precise calculations on values up to 32 bits.
This leads to a stark difference in capabilities:
| Feature | llvmexpr | akarin.Expr (opt=0) | akarin.Expr (opt=1) |
| Model | Always-Float | Type-Aware | Type-Aware, Integer-Preserving |
| Loading x from a 32-bit integer clip | Converted to float, precision lost | Converted to float, precision lost | Loaded as 32-bit integer, full precision |
| Bitwise Ops on >24bit x | Incorrect (operates on imprecise float) | Incorrect (operates on imprecise float) | Correct (operates on precise integer) |
| Arithmetic on >24bit x | Imprecise float arithmetic | Imprecise float arithmetic | Precise integer arithmetic (with wrap-around) |
Conclusion for Migration:
- llvmexpr: everything is a float, and precision is always limited to 24 bits for integer values.
- akarin with opt=0 behaves like llvmexpr when dealing with pixel data, as the initial conversion to float limits precision. However, it still performs integer math on integer constants.
- akarin with opt=1 unlocks a integer-only, high-precision pipeline that llvmexpr cannot replicate.
- Important
- Migration Consideration: Users migrating from akarin.Expr must be mindful of this core difference. Expressions that depend on the precise representation of integers greater than 24 bits (especially for bitwise operations) or on integer wrap-around behavior will not work as expected in llvmexpr and may require algorithmic changes.
2. pow Function and Approximate Math
Behavior of the pow function and approximate math differs significantly:
- akarin.Expr uses approximate math for the pow function.
- llvmexpr does not implement an approximate pow, as the precise version always offers better performance. It will always use the precise implementation.
This means that even when forcing approximate math in llvmexpr (approx_math=1), calculations involving pow may produce different results compared to akarin.Expr. It is impossible to fully replicate akarin.Expr's pow behavior (unless you write the approximate pow logic yourself in the expression).
Beyond pow, llvmexpr provides more explicit control over mathematical precision. With approx_math=2 (the default auto mode) or approx_math=0 (forced precise), llvmexpr will (or may) use precise mathematical functions. This can lead to different results compared to akarin.Expr, which use approximate math only.
3. Decimal Separator
llvmexpr always uses a period (.) as the decimal separator for floating-point numbers, regardless of system locale. In contrast, akarin.Expr's parser is locale-dependent and may expect a comma (,) in certain regions. This can cause expressions to fail when moved between systems with different locale configurations. llvmexpr provides consistent parsing by ignoring locale for decimal separators.
4. Incompatible Positional Arguments
- Attention
- When migrating from akarin.Expr, do not perform a simple find-and-replace on the function name, especially if you use positional arguments. The optional parameters for akarin.Expr and llvmexpr.Expr are in a different order, which can lead to silent errors or immediate script failure.
5. Parameter Signature Mismatch
A side-by-side comparison reveals the incompatibility:
| Index | akarin.Expr | llvmexpr.Expr | Compatibility |
| 1 | clips | clips | ✅ Match |
| 2 | expr | expr | ✅ Match |
| 3 | format (optional) | format (optional) | ✅ Match |
| 4 | opt (optional) | boundary (optional) | ❌ MISMATCH |
| 5 | boundary (optional) | dump_ir (optional) | ❌ MISMATCH |
6. Different Error Messages
When an RPN fails to compile, llvmexpr will raise a different error message than akarin.Expr.
For example, akarin.Expr('invalid') raises vapoursynth.Error: Expr: failed to convert 'invalid' to float, while llvmexpr.Expr('invalid') raises vapoursynth.Error: Expr: Invalid token: invalid (idx 0). akarin.Expr('+') raises vapoursynth.Error: Expr: insufficient values on stack: + while llvmexpr.Expr('+') raises vapoursynth.Error: Expr: Stack underflow before executing block 0: depth_in = 0, min_needed = 1. start token '+' (idx 0)..
If your code relies on the exact error message, you will need to adjust it.
Enhancements
llvmexpr introduces major new features and usability improvements that significantly expand upon the capabilities of akarin.Expr.
1. Major New Capabilities
These are fundamental features in llvmexpr that enable entirely new types of operations not possible in akarin.Expr.
- Turing-Complete Control Flow: llvmexpr allows for complex, iterative algorithms using labels and conditional jumps, making the expression engine Turing-complete.
- Labels (#label_name): Defines a destination for a jump.
- Conditional Jumps (label_name#): Pops a value from the stack. If the value is true (> 0), execution jumps to the corresponding label. This enables the creation of loops and complex conditional logic.
- Direct Output Control: Provides operators to write to arbitrary pixel locations and suppress the default output, allowing for advanced spatial operations like rotations or custom warps directly within an expression.
- @[]: Pops a value and x, y coordinates (val absX absY @[]), and writes the value to the output frame at [absX, absY].
- ^exit^: A special marker that suppresses the default write to the current pixel [X, Y] if it (or a variable it was assigned to) is the only item left on the stack after evaluation.
2. Expanded Syntax and Function Library
llvmexpr extends akarin's syntax with a significantly larger set of mathematical functions and more flexible syntax.
- Expanded Mathematical Functions:
- Trigonometry: tan, asin, acos, atan, atan2
- Hyperbolic: sinh, cosh, tanh
- Logarithmic: log2, log10
- Exponential: exp2
- Rounding: ceil
- Miscellaneous: copysign, fma (fused multiply-add), neg, sgn (signum).
- Enhanced Pixel Access Modifiers: Extends absolute pixel access with explicit boundary mode control.
- absX absY clip[]:m: Forces mirrored boundary for absolute access.
- absX absY clip[]:b: Forces the use of the filter's global boundary parameter for absolute access.
- absX absY clip[]:c: Forces clamped boundary for absolute access (default when not specified, same as akarin.Expr).
- Relaxed Offset Range for Static Pixel Access:: While akarin.Expr requires -width < relX < width && -height < relY < height in x[relX,relY] syntax, llvmexpr allows relX and relY to be any integer value.
3. Infix (C-Style) Syntax Support
llvmexpr introduces a major usability enhancement not available in akarin.Expr: support for C-style infix notation.
- infix=1 Parameter: By setting infix=1, you can write expressions in a familiar, algebraic style. The plugin automatically converts these expressions to its internal postfix representation at runtime.
- Improved Readability: Infix notation makes complex mathematical and logical expressions significantly easier to write, read, and maintain compared to RPN.
- akarin.Expr Compatibility: akarin.Expr exclusively uses postfix (RPN) syntax. This feature is unique to llvmexpr.
Example:
An expression to calculate the Euclidean distance in RPN would be:
x X - 2 pow y Y - 2 pow + sqrt
With infix=1, the same logic can be expressed more intuitively:
RESULT = sqrt(pow($x - $X, 2) + pow($y - $Y, 2))
4. Core Engine and Usability Enhancements
- Advanced Function Parameters: Offers more fine-grained control over compilation and execution:
- dump_ir: Dumps the intermediate LLVM IR for debugging and performance analysis.
- opt_level: Provides explicit control over the LLVM optimization level.
- approx_math: An intelligent mode for using fast, approximate math functions, with an automatic fallback to precise versions if the LLVM vectorizer fails.
5. GPU Acceleration (VkExpr)
llvmexpr provides a dedicated Vulkan-based backend (llvmexpr.VkExpr) that enables expression evaluation on the GPU.
- Seamless Portability: llvmexpr.VkExpr supports the same syntax and semantics as llvmexpr.Expr (including both postfix and infix modes). This means expressions written for the CPU are directly runnable on the GPU without any modification.