Semantics-Preserving Inlining for Metaprogramming
Nicolas Stucki, Aggelos Biboudis, Sébastien Doeraene, Martin Odersky
LAMP
Overview
- Syntactic / Semantic Inlining
- Inlining & Metaprogramming in Scala
- Semantics preservation
- Inlining & OO
- Transparent inlining
Syntactic
- Macro preprocessors
- Inlines and then types checks
- Extremely flexible
- Hard to reason about semantics
#define getmax(a,b) ((a)>(b)?(a):(b))
getmax(i, j) // ((i)>(j)?(i):(j))
getmax("a", 2) // (("a")>(2)?("a"):(2))
Semantic
- Type checks and then inlines
- Equivalent to a normal function call
- Inline semantics ≈ β-reduction
- Guaranteed or best effort
inline def max(a: Int, b: Int): Int =
if (a > b) a else b
max(i, j) // if (x > y) x else y
max("a", 2) // error
Why inline for metaprogramming?
// Scala 3 macro
inline def power(x: D, n: D): Int =
${ powerCode('x, 'n) }
def powerCode(using Ctx)(x: Expr[D], n: Expr[I])(...): Expr[D] =
...
// Scala 2 macro
def power(x: D, n: I): D =
macro impl
def impl(c: Ctx)(x: c.Expr[D], n: c.Expr[I]): c.Expr[D] =
...
Inlining a function
inline def max(a: Int, b: Int): Int =
if (a > b) a else b
max(x, y)
// becomes
val a = x
val b = y
if (a > b) a else b
- val ensures correct call semantics
- may be optimized away
Other parameters
inline def max(a: => Int, inline b: Int): Int =
if (a > b) a else b
max(x, y)
// becomes
def a = x
if (a > y) a else y
- Semantically equivalent
- by-name: guaranties no duplication of argument
- inline: fine grained control of generated code
Recursion
inline def power(x: Double, inline n: Int): Double =
inline if (n == 0) 1.0
else inline if (n > 0) x * power(x, n - 1)
else error("n was not a know positive integer")
power(f(5), 3)
// becomes
val x = f(5)
x * x * x * 1.0
- Recursive call to power
- Constant folded conditions
- inline if: ensures one branch is eliminated
- error: if not eliminated emit error message
Overloading
- Initial code elaboration does not change
- Overloads
- Implicit resolution
- Implicit converisons
- Can perform devirtualization
def log(x: Int): Unit = println("Log Int: " + x)
def log[T](x: T): Unit = println("Log: " + x)
inline def ilog[T](inline x: T): Unit = log[T](x)
ilog(3) // log[Int](3)
Private access
- Accessors automatically generated
class Num(a: Int) {
inline def max(b: Int): Int =
if (a > b) a else b
def a$inline: Int = a
}
num.max(4)
// becomes
val b = 3
if (num.a$inline > b) num.a$inline else b
Can we implement or override a method?
trait Num {
def max(n: Int): Int
}
trait Nat extends Num {
inline def max(n: Int): Int = ...
}
- Yes, but we need to retain Num.max
- Nat.max is inlined
- Num.max called at runtime
Overriding and Abstracting
Can we override an inline method?
trait Num {
inline def max(n: Int): Int = ...
}
trait Nat extends Num {
override def max(n: Int): Int = ...
}
- No
- Num.max would never call Nat.max
- Inline definitions are effectively final
Overriding and Abstracting
Rules
- Inline methods are final
- If a methods implements/overrides a normal method then it must be retained
- Retained methods cannot have inline parameters
- Abstract inline methods can only be implemented by inline methods
Overriding and Abstracting
Transparent Inline
val map1: TreeSet[Int] = setFor[Int]
val map2: HashSet[Int => Int] = setFor[Int => Int]
transparent inline def setFor[T]: Set[T] =
summonFrom {
case ord: Ordering[T] => new TreeSet[T](ord)
case _ => new HashSet[T]
}
- Provides a way to influence typing at call-site
- transparent refines the type after inlining
- summonFrom summons a value after inlining
Thank you
Semantics-Preserving Inlining for Metaprogramming
By Nicolas Stucki
Semantics-Preserving Inlining for Metaprogramming
- 487