Skip to content

GlslInterval.kt

src/jvmMain/kotlin/afield/lang/stacksl/cpu/GlslInterval.kt · 493 lines

package afield.lang.stacksl.cpu

import afield.math.functions.FnRange
import afield.math.functions.FnRangeMath

Interval arithmetic for the GLSL interpreter: a shader run over a whole region of fragUV at once, returning a bound on every colour any pixel in it can produce.

GlslIntervalScalars is the third GlslScalars instance, beside the binary32 reference and the reverse-mode one, so it runs the emitted GLSL through the interpreter's one copy of the control flow. Nothing about a StackSL word is restated here.

What is contained

The contract is the one afield.math.functions.FnInterval states for Fn, moved to binary32: for every input inside the intervals, the value the Float reference path computes lies inside the result (or is NaN, and the result says NaN is possible). Each operation is bounded by FnRangeMath in Double and the endpoints rounded outward to Float. That is sound for the correctly rounded operations because rounding to nearest is monotone, and because a Double product or sum of two floats rounds to Float the way the Float operation does (the argument GlslAd.kt makes for its primal). The library transcendentals are already widened by FnRangeMath, and the Float path rounds their Double result to nearest, which is monotone too.

The composite builtins (mod, fract, mix, smoothstep, dot, length) are bounded as the Float path spells them, operation by operation, so the bound is of that arithmetic and not of a real-number ideal. fract(x) gets the one structural rule FnInterval also has: inside one unit cell it is endpoint-exact, across a boundary it is [0, 1].

Branches

A comparison of two overlapping intervals is neither true nor false. truth answers GlslTruth.UNKNOWN, and GlslMachine then runs both arms from the same state and joins what each wrote, with join. The machine refuses (with GlslUnsupported) the cases a join cannot express: arms that leave by different routes (one returns, the other falls through), and a loop whose condition is undecided. An integer index must be a single value.

What is refused

Builtins with no rule here are refused by name, never guessed: textures, cross, reflect, refract, faceforward, the matrix builtins, tan, the inverse trigonometric and hyperbolic functions except tanh, and the bit operators.

internal data class Iv(val lo: Float, val hi: Float, val nan: Boolean = false)

One component: every Float in [lo, hi], plus NaN when nan. Empty (only NaN) when lo > hi.

val isEmpty: Boolean get() = !(lo <= hi)

val isPoint: Boolean get() = lo == hi && !nan

fun hull(o: Iv): Iv

    fun hull(o: Iv): Iv = when {
        isEmpty -> o.copy(nan = o.nan || nan)
        o.isEmpty -> copy(nan = nan || o.nan)
        else -> Iv(minOf(lo, o.lo), maxOf(hi, o.hi), nan || o.nan)
    }

operator fun contains(v: Float): Boolean

    operator fun contains(v: Float): Boolean = if (v.isNaN()) nan else !isEmpty && v >= lo && v <= hi

fun toRange(): FnRange = FnRange(lo.toDouble(), hi.toDouble(), nan)

override fun toString(): String

    override fun toString(): String = (if (isEmpty) "∅" else if (lo == hi) "$lo" else "[$lo, $hi]") + if (nan) "∪NaN" else ""

companion object

fun point(v: Float): Iv

        fun point(v: Float): Iv = if (v.isNaN()) Iv(Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, true) else Iv(v, v)

val UNIT = Iv(0f, 1f)

val TRUE = Iv(1f, 1f)

val FALSE = Iv(0f, 0f)

fun of(r: FnRange): Iv

The outward Float rounding of a Double range.

        fun of(r: FnRange): Iv {
            if (r.isEmpty) return Iv(Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, r.maybeNaN)
            return Iv(down(r.lo), up(r.hi), r.maybeNaN)
        }

        private fun down(d: Double): Float {
            val f = d.toFloat()
            return if (f.toDouble() > d) Math.nextDown(f) else f
        }

        private fun up(d: Double): Float {
            val f = d.toFloat()
            return if (f.toDouble() < d) Math.nextUp(f) else f
        }
    }
}

sqrt, correctly rounded and monotone on [0, inf]; NaN for any part below zero.

private fun sqrtRange(r: FnRange): FnRange {
    if (r.isEmpty) return r
    val nan = r.maybeNaN || r.lo < 0.0
    if (r.hi < 0.0) return FnRange(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, true)
    return FnRange(Math.sqrt(maxOf(r.lo, 0.0)), Math.sqrt(r.hi), nan)
}

internal class IntervalValue(val type: GlslType, val c: Array<Iv>)

A GLSL value whose components are Ivs: the interval GlslValue.

init

    init {
        require(c.size == type.components) { "$type needs ${type.components} components, got ${c.size}" }
    }

operator fun contains(v: GlslValue): Boolean

Whether v's components each lie inside this value's.

    operator fun contains(v: GlslValue): Boolean = v.c.size == c.size && v.c.indices.all { v.c[it] in c[it] }

override fun toString(): String = "iv:$type(${c.joinToString()})"

internal object GlslIntervalScalars : GlslScalars<IntervalValue>

The interval instance of GlslScalars. Stateless, so one instance serves every thread.

internal object GlslIntervalScalars : GlslScalars<IntervalValue> {

component rules

    private inline fun r1(a: Iv, f: (FnRange) -> FnRange): Iv = Iv.of(f(a.toRange()))
    private inline fun r2(a: Iv, b: Iv, f: (FnRange, FnRange) -> FnRange): Iv = Iv.of(f(a.toRange(), b.toRange()))

A point is computed exactly as the reference path computes it. Rounding a point's result outward would leave a one-ulp interval, and a hash's fract of a large product turns one ulp into most of [0, 1], so a single pixel of noise would never be a single value.

    private fun bothPoints(a: Iv, b: Iv) = a.isPoint && b.isPoint

f at a point, as f1 in GlslValue.kt computes it: in Double, rounded to Float once.

    private inline fun pointOr(a: Iv, f: (Double) -> Double, range: (FnRange) -> FnRange): Iv =
        if (a.isPoint) Iv.point(f(a.lo.toDouble()).toFloat()) else r1(a, range)

    private fun add(a: Iv, b: Iv) = if (bothPoints(a, b)) Iv.point(a.lo + b.lo) else r2(a, b, FnRangeMath::add)
    private fun sub(a: Iv, b: Iv) = if (bothPoints(a, b)) Iv.point(a.lo - b.lo) else r2(a, b, FnRangeMath::sub)
    private fun mul(a: Iv, b: Iv) = if (bothPoints(a, b)) Iv.point(a.lo * b.lo) else r2(a, b, FnRangeMath::mul)

x * x for one value — only where the two factors are provably the same value.

    private fun sq(a: Iv) = if (a.isPoint) Iv.point(a.lo * a.lo) else r1(a, FnRangeMath::square)
    private fun div(a: Iv, b: Iv) = if (bothPoints(a, b)) Iv.point(a.lo / b.lo) else r2(a, b, FnRangeMath::div)
    private fun neg(a: Iv) = Iv(-a.hi, -a.lo, a.nan)
    private fun floor(a: Iv) = r1(a, FnRangeMath::floor)
    private fun minIv(a: Iv, b: Iv) = r2(a, b, FnRangeMath::min)
    private fun maxIv(a: Iv, b: Iv) = r2(a, b, FnRangeMath::max)
    private fun k(v: Float) = Iv.point(v)

x - floor(x) in Float, endpoint-exact inside a unit cell.

    private fun fract(a: Iv): Iv {
        if (a.isEmpty) return a
        if (a.lo.isInfinite() || a.hi.isInfinite()) return Iv(0f, 1f, true)
        val kl = kotlin.math.floor(a.lo)
        return if (kl == kotlin.math.floor(a.hi)) Iv(a.lo - kl, a.hi - kl, a.nan) else Iv(0f, 1f, a.nan)
    }

A monotone non-decreasing map, evaluated at the endpoints exactly as the Float path would.

source — 41 lines, from line 145
    private inline fun monotone(a: Iv, f: (Float) -> Float): Iv =
        if (a.isEmpty) a else Iv(f(a.lo), f(a.hi), a.nan)

    private fun less(a: Iv, b: Iv): Iv = when {
        a.isEmpty || b.isEmpty -> Iv.FALSE
        a.hi < b.lo && !a.nan && !b.nan -> Iv.TRUE
        a.lo >= b.hi -> Iv.FALSE
        else -> Iv.UNIT
    }

    private fun lessEq(a: Iv, b: Iv): Iv = when {
        a.isEmpty || b.isEmpty -> Iv.FALSE
        a.hi <= b.lo && !a.nan && !b.nan -> Iv.TRUE
        a.lo > b.hi -> Iv.FALSE
        else -> Iv.UNIT
    }

    private fun eq(a: Iv, b: Iv): Iv = when {
        a.isPoint && b.isPoint -> if (a.lo == b.lo) Iv.TRUE else Iv.FALSE
        a.isEmpty || b.isEmpty || a.hi < b.lo || b.hi < a.lo -> Iv.FALSE
        else -> Iv.UNIT
    }

    private fun not(a: Iv): Iv = Iv(1f - a.hi, 1f - a.lo)

    private fun select(pick: Iv, whenFalse: Iv, whenTrue: Iv): Iv = when {
        pick.lo == 1f -> whenTrue
        pick.hi == 0f -> whenFalse
        else -> whenFalse.hull(whenTrue)
    }

    private fun clampIv(x: Iv, lo: Iv, hi: Iv) = minIv(maxIv(x, lo), hi)

    private fun mixIv(x: Iv, y: Iv, a: Iv) = add(mul(x, sub(k(1f), a)), mul(y, a))

    private fun smoothstepIv(e0: Iv, e1: Iv, x: Iv): Iv {
        val t = clampIv(div(sub(x, e0), sub(e1, e0)), k(0f), k(1f))
        // Op by op, as GlslMath.smoothstep rounds. FnRangeMath.cubicSmoothstep's monotone rule is
        // argued for Double rounding; Float's is 2^29 times coarser, so it does not transfer.
        return mul(sq(t), sub(k(3f), mul(k(2f), t)))
    }

whole values

    private fun scalar(v: Iv) = IntervalValue(GlslType.FLOAT, arrayOf(v))

    private inline fun unary(v: IntervalValue, f: (Iv) -> Iv) = IntervalValue(v.type, Array(v.c.size) { f(v.c[it]) })

    private inline fun pair(a: IntervalValue, b: IntervalValue, f: (Iv, Iv) -> Iv): IntervalValue {
        val n = maxOf(a.c.size, b.c.size)
        val type = if (a.c.size >= b.c.size) a.type else b.type
        return IntervalValue(type, Array(n) { f(a.c[if (a.c.size == 1) 0 else it], b.c[if (b.c.size == 1) 0 else it]) })
    }

    private inline fun pairBool(a: IntervalValue, b: IntervalValue, f: (Iv, Iv) -> Iv): IntervalValue {
        val n = maxOf(a.c.size, b.c.size)
        return IntervalValue(GlslType.of(GlslBase.BOOL, n), Array(n) { f(a.c[if (a.c.size == 1) 0 else it], b.c[if (b.c.size == 1) 0 else it]) })
    }

    private inline fun triple(a: List<IntervalValue>, f: (Iv, Iv, Iv) -> Iv): IntervalValue {
        val n = maxOf(a[0].c.size, a[1].c.size, a[2].c.size)
        val type = listOf(a[0].type, a[1].type, a[2].type).maxByOrNull { it.components }!!
        return IntervalValue(if (type.components == n) type else GlslType.of(GlslBase.FLOAT, n), Array(n) { i ->
            f(a[0].c[if (a[0].c.size == 1) 0 else i], a[1].c[if (a[1].c.size == 1) 0 else i], a[2].c[if (a[2].c.size == 1) 0 else i])
        })
    }

    private fun dot(a: IntervalValue, b: IntervalValue): Iv {
        if (a.c.size != b.c.size) throw GlslEvalError("dot(${a.type}, ${b.type})")
        var s = k(0f)
        for (i in a.c.indices) s = add(s, mul(a.c[i], b.c[i]))
        return s
    }

    private fun length(v: IntervalValue): Iv = r1(dotSquare(v), ::sqrtRange)

    private fun requirePoint(v: Iv, what: String): Float {
        if (!v.isPoint) throw GlslUnsupported("$what must be a single value under interval evaluation, got $v")
        return v.lo
    }

override fun lift(v: GlslValue): IntervalValue

    override fun lift(v: GlslValue): IntervalValue = IntervalValue(v.type, Array(v.c.size) { Iv.point(v.c[it]) })

fun region(type: GlslType, lo: FloatArray, hi: FloatArray): IntervalValue

The interval [lo, hi] per component — a region of inputs, rather than one.

    fun region(type: GlslType, lo: FloatArray, hi: FloatArray): IntervalValue =
        IntervalValue(type, Array(type.components) { Iv(lo[it], hi[it]) })

override fun typeOf(v: IntervalValue): GlslType = v.type

override fun zeros(type: GlslType) = IntervalValue(type, Array(type.components) { Iv.FALSE })

override fun ones(type: GlslType) = IntervalValue(type, Array(type.components) { Iv.TRUE })

override fun boolOf(b: Boolean)

    override fun boolOf(b: Boolean) = IntervalValue(GlslType.BOOL, arrayOf(if (b) Iv.TRUE else Iv.FALSE))

override fun asBool(v: IntervalValue): Boolean

    override fun asBool(v: IntervalValue): Boolean = when (truth(v)) {
        GlslTruth.TRUE -> true
        GlslTruth.FALSE -> false
        GlslTruth.UNKNOWN -> throw GlslUnsupported("an undecided condition reached a place that needs a definite one")
    }

override fun truth(v: IntervalValue): GlslTruth

    override fun truth(v: IntervalValue): GlslTruth {
        val x = v.c[0]
        return when {
            !x.nan && (x.hi < 0f || x.lo > 0f) -> GlslTruth.TRUE
            !x.nan && x.lo == 0f && x.hi == 0f -> GlslTruth.FALSE
            else -> GlslTruth.UNKNOWN
        }
    }

override fun boolOfTruth(t: GlslTruth)

    override fun boolOfTruth(t: GlslTruth) = IntervalValue(GlslType.BOOL, arrayOf(when (t) {
        GlslTruth.TRUE -> Iv.TRUE
        GlslTruth.FALSE -> Iv.FALSE
        GlslTruth.UNKNOWN -> Iv.UNIT
    }))

override fun equalTruth(a: IntervalValue, b: IntervalValue): GlslTruth

    override fun equalTruth(a: IntervalValue, b: IntervalValue): GlslTruth {
        if (a.c.size != b.c.size) return GlslTruth.FALSE
        val each = a.c.indices.map { eq(a.c[it], b.c[it]) }
        return when {
            each.any { it == Iv.FALSE } -> GlslTruth.FALSE
            each.all { it == Iv.TRUE } -> GlslTruth.TRUE
            else -> GlslTruth.UNKNOWN
        }
    }

override fun join(a: IntervalValue, b: IntervalValue): IntervalValue

    override fun join(a: IntervalValue, b: IntervalValue): IntervalValue {
        if (a.type != b.type) throw GlslEvalError("cannot join ${a.type} with ${b.type}")
        return IntervalValue(a.type, Array(a.c.size) { a.c[it].hull(b.c[it]) })
    }

override fun asInt(v: IntervalValue): Int = Math.round(requirePoint(v.c[0], "an index"))

override fun negate(v: IntervalValue) = unary(v) { neg(it) }

override fun binary(op: String, a: IntervalValue, b: IntervalValue): IntervalValue

    override fun binary(op: String, a: IntervalValue, b: IntervalValue): IntervalValue = when (op) {
        "+" -> pair(a, b) { x, y -> add(x, y) }
        "-" -> pair(a, b) { x, y -> sub(x, y) }
        "*" -> matmul(a, b)
        "/" -> if (a.type.base == GlslBase.INT && b.type.base == GlslBase.INT) {
            pair(a, b) { x, y -> k((requirePoint(x, "int /").toInt() / requirePoint(y, "int /").toInt()).toFloat()) }
        } else {
            pair(a, b) { x, y -> div(x, y) }
        }
        "%" -> pair(a, b) { x, y -> modIv(x, y) }
        "<" -> pairBool(a, b) { x, y -> less(x, y) }
        ">" -> pairBool(a, b) { x, y -> less(y, x) }
        "<=" -> pairBool(a, b) { x, y -> lessEq(x, y) }
        ">=" -> pairBool(a, b) { x, y -> lessEq(y, x) }
        "^^" -> boolOfTruth(truth(a) xor truth(b))
        else -> throw GlslUnsupported("operator '$op' has no interval rule")
    }

    private infix fun GlslTruth.xor(o: GlslTruth): GlslTruth =
        if (this == GlslTruth.UNKNOWN || o == GlslTruth.UNKNOWN) GlslTruth.UNKNOWN
        else if (this != o) GlslTruth.TRUE else GlslTruth.FALSE

    private fun modIv(x: Iv, y: Iv): Iv = sub(x, mul(y, floor(div(x, y))))

override fun equalComponents(a: IntervalValue, b: IntervalValue): Boolean

    override fun equalComponents(a: IntervalValue, b: IntervalValue): Boolean = when (equalTruth(a, b)) {
        GlslTruth.TRUE -> true
        GlslTruth.FALSE -> false
        GlslTruth.UNKNOWN -> throw GlslUnsupported("an undecided == reached a place that needs a definite one")
    }

override fun construct(type: GlslType, args: List<IntervalValue>): IntervalValue

    override fun construct(type: GlslType, args: List<IntervalValue>): IntervalValue {
        if (args.isEmpty()) throw GlslEvalError("$type() needs arguments")
        fun toInt(v: Iv): Iv = monotone(v) { it.toInt().toFloat() }
        if (args.size == 1 && args[0].type.isScalar) {
            val v = args[0].c[0]
            if (type.isMatrix) {
                val out = Array(type.components) { Iv.FALSE }
                for (i in 0 until type.rows) out[i * type.rows + i] = v
                return IntervalValue(type, out)
            }
            val broadcast = if (type.base == GlslBase.INT) toInt(v) else v
            return IntervalValue(type, Array(type.components) { broadcast })
        }
        if (args.size == 1 && args[0].type.isMatrix && type.isMatrix) {
            val src = args[0]
            val out = Array(type.components) { Iv.FALSE }
            for (col in 0 until type.cols) for (row in 0 until type.rows) {
                out[col * type.rows + row] = when {
                    col < src.type.cols && row < src.type.rows -> src.c[col * src.type.rows + row]
                    col == row -> Iv.TRUE
                    else -> Iv.FALSE
                }
            }
            return IntervalValue(type, out)
        }
        val flat = ArrayList<Iv>(type.components)
        args.forEach { a -> a.c.forEach { flat += it } }
        if (flat.size < type.components) throw GlslEvalError("$type(...) got ${flat.size} components, needs ${type.components}")
        return IntervalValue(type, Array(type.components) { if (type.base == GlslBase.INT) toInt(flat[it]) else flat[it] })
    }

override fun swizzle(v: IntervalValue, field: String): IntervalValue

    override fun swizzle(v: IntervalValue, field: String): IntervalValue {
        val idx = swizzleIndices(field, v.c.size)
        return IntervalValue(GlslType.of(v.type.base, idx.size), Array(idx.size) { v.c[idx[it]] })
    }

override fun component(v: IntervalValue, index: Int)

    override fun component(v: IntervalValue, index: Int) = IntervalValue(GlslType.of(v.type.base, 1), arrayOf(v.c[index]))

override fun column(v: IntervalValue, index: Int): IntervalValue

    override fun column(v: IntervalValue, index: Int): IntervalValue {
        val rows = v.type.rows
        return IntervalValue(GlslType.of(v.type.base, rows), Array(rows) { v.c[index * rows + it] })
    }

override fun withComponents(v: IntervalValue, indices: IntArray, src: IntervalValue): IntervalValue

    override fun withComponents(v: IntervalValue, indices: IntArray, src: IntervalValue): IntervalValue {
        val next = v.c.copyOf()
        for (i in indices.indices) next[indices[i]] = src.c[if (src.c.size == 1) 0 else i]
        return IntervalValue(v.type, next)
    }

override fun withComponent(v: IntervalValue, index: Int, src: IntervalValue): IntervalValue

    override fun withComponent(v: IntervalValue, index: Int, src: IntervalValue): IntervalValue {
        val next = v.c.copyOf()
        next[index] = src.c[0]
        return IntervalValue(v.type, next)
    }

override fun coerce(v: IntervalValue, type: GlslType): IntervalValue

    override fun coerce(v: IntervalValue, type: GlslType): IntervalValue = when {
        v.type == type -> v
        v.type.components == type.components && v.type.cols == type.cols ->
            if (type.base == GlslBase.INT) unary(v) { x -> monotone(x) { it.toInt().toFloat() } }.let { IntervalValue(type, it.c) }
            else IntervalValue(type, v.c.copyOf())
        v.type.isScalar -> construct(type, listOf(v))
        else -> throw GlslEvalError("cannot store ${v.type} in a $type")
    }

override fun callBuiltin(name: String, args: List<IntervalValue>, samplers: GlslSamplers): IntervalValue?

body — 67 lines, from line 371
    override fun callBuiltin(name: String, args: List<IntervalValue>, samplers: GlslSamplers): IntervalValue? {
        val a = args
        return when (name) {
            "radians" -> unary(a[0]) { x -> pointOr(x, { it * Math.PI / 180.0 }) { FnRangeMath.mul(it, FnRange.point(Math.PI / 180.0)) } }
            "degrees" -> unary(a[0]) { x -> pointOr(x, { it * 180.0 / Math.PI }) { FnRangeMath.mul(it, FnRange.point(180.0 / Math.PI)) } }
            "sin" -> unary(a[0]) { pointOr(it, Math::sin, FnRangeMath::sin) }
            "cos" -> unary(a[0]) { pointOr(it, Math::cos, FnRangeMath::cos) }
            "tanh" -> unary(a[0]) { pointOr(it, Math::tanh, FnRangeMath::tanh) }
            "exp" -> unary(a[0]) { pointOr(it, Math::exp, FnRangeMath::exp) }
            "log" -> unary(a[0]) { pointOr(it, Math::log, FnRangeMath::ln) }
            "sqrt" -> unary(a[0]) { pointOr(it, Math::sqrt, ::sqrtRange) }
            "atan" -> if (a.size == 2) pair(a[0], a[1]) { y, x ->
                if (bothPoints(y, x)) Iv.point(Math.atan2(y.lo.toDouble(), x.lo.toDouble()).toFloat()) else r2(y, x, FnRangeMath::atan2)
            } else unary(a[0]) { pointOr(it, Math::atan, FnRangeMath::atan) }
            "pow" -> pair(a[0], a[1]) { x, y ->
                if (bothPoints(x, y)) Iv.point(Math.pow(x.lo.toDouble(), y.lo.toDouble()).toFloat()) else r2(x, y, FnRangeMath::pow)
            }
            "abs" -> unary(a[0]) { r1(it, FnRangeMath::abs) }
            "sign" -> unary(a[0]) { x -> monotone(x) { GlslMath.sign(it) } }
            "floor" -> unary(a[0]) { floor(it) }
            "ceil" -> unary(a[0]) { x -> monotone(x) { kotlin.math.ceil(it) } }
            "trunc" -> unary(a[0]) { x -> monotone(x) { kotlin.math.truncate(it) } }
            "round" -> unary(a[0]) { x -> monotone(x) { Math.round(it).toFloat() } }
            "fract" -> unary(a[0]) { fract(it) }
            "mod" -> pair(a[0], a[1]) { x, y -> modIv(x, y) }
            "min" -> pair(a[0], a[1]) { x, y -> minIv(x, y) }
            "max" -> pair(a[0], a[1]) { x, y -> maxIv(x, y) }
            "clamp" -> triple(a) { x, lo, hi -> clampIv(x, lo, hi) }
            "mix" -> if (a[2].type.base == GlslBase.BOOL) {
                val n = maxOf(a[0].c.size, a[1].c.size, a[2].c.size)
                IntervalValue(GlslType.of(GlslBase.FLOAT, n), Array(n) { i ->
                    select(a[2].c[if (a[2].c.size == 1) 0 else i], a[0].c[if (a[0].c.size == 1) 0 else i], a[1].c[if (a[1].c.size == 1) 0 else i])
                })
            } else {
                triple(a) { x, y, t -> mixIv(x, y, t) }
            }
            "step" -> pair(a[0], a[1]) { e, x -> select(less(x, e), Iv.TRUE, Iv.FALSE) }
            "smoothstep" -> triple(a) { e0, e1, x -> smoothstepIv(e0, e1, x) }
            "length" -> scalar(length(a[0]))
            "distance" -> scalar(length(pair(a[0], a[1]) { x, y -> sub(x, y) }))
            "dot" -> scalar(if (a[0] === a[1]) dotSquare(a[0]) else dot(a[0], a[1]))
            "normalize" -> { val len = length(a[0]); unary(a[0]) { div(it, len) } }
            "lessThan" -> pairBool(a[0], a[1]) { x, y -> less(x, y) }
            "lessThanEqual" -> pairBool(a[0], a[1]) { x, y -> lessEq(x, y) }
            "greaterThan" -> pairBool(a[0], a[1]) { x, y -> less(y, x) }
            "greaterThanEqual" -> pairBool(a[0], a[1]) { x, y -> lessEq(y, x) }
            "equal" -> pairBool(a[0], a[1]) { x, y -> eq(x, y) }
            "notEqual" -> pairBool(a[0], a[1]) { x, y -> not(eq(x, y)) }
            "any" -> boolOfTruth(a[0].c.fold(GlslTruth.FALSE) { acc, x -> or(acc, bitTruth(x)) })
            "all" -> boolOfTruth(a[0].c.fold(GlslTruth.TRUE) { acc, x -> and(acc, bitTruth(x)) })
            "not" -> unary(a[0]) { not(it) }.let { IntervalValue(GlslType.of(GlslBase.BOOL, it.c.size), it.c) }
            "isnan" -> IntervalValue(GlslType.of(GlslBase.BOOL, a[0].c.size), Array(a[0].c.size) { i ->
                val x = a[0].c[i]
                when { !x.nan -> Iv.FALSE; x.isEmpty -> Iv.TRUE; else -> Iv.UNIT }
            })
            "isinf" -> IntervalValue(GlslType.of(GlslBase.BOOL, a[0].c.size), Array(a[0].c.size) { i ->
                val x = a[0].c[i]
                when {
                    x.isEmpty || (x.lo.isFinite() && x.hi.isFinite()) -> Iv.FALSE
                    x.isPoint -> Iv.TRUE
                    else -> Iv.UNIT
                }
            })
            in GlslBuiltins.names -> throw GlslUnsupported("'$name' has no interval rule yet")
            else -> null
        }
    }

* with the reference path's accumulation order: s = 0; s += a * b per term.

source — 53 lines, from line 440
    private fun matmul(a: IntervalValue, b: IntervalValue): IntervalValue {
        fun sum(n: Int, term: (Int) -> Iv): Iv { var s = k(0f); for (i in 0 until n) s = add(s, term(i)); return s }
        return when {
            a.type.isMatrix && b.type.isMatrix -> {
                if (a.type.cols != b.type.rows) throw GlslEvalError("${a.type} * ${b.type}")
                val rows = a.type.rows
                IntervalValue(GlslType.of(GlslBase.FLOAT, rows, b.type.cols), Array(rows * b.type.cols) { i ->
                    val col = i / rows; val row = i % rows
                    sum(a.type.cols) { kk -> mul(a.c[kk * rows + row], b.c[col * b.type.rows + kk]) }
                })
            }
            a.type.isMatrix && b.type.isScalar -> unary(a) { mul(it, b.c[0]) }
            a.type.isScalar && b.type.isMatrix -> unary(b) { mul(it, a.c[0]) }
            a.type.isMatrix -> {
                if (a.type.cols != b.c.size) throw GlslEvalError("${a.type} * ${b.type}")
                IntervalValue(GlslType.of(GlslBase.FLOAT, a.type.rows), Array(a.type.rows) { row ->
                    sum(a.type.cols) { kk -> mul(a.c[kk * a.type.rows + row], b.c[kk]) }
                })
            }
            b.type.isMatrix -> {
                if (b.type.rows != a.c.size) throw GlslEvalError("${a.type} * ${b.type}")
                IntervalValue(GlslType.of(GlslBase.FLOAT, b.type.cols), Array(b.type.cols) { col ->
                    sum(b.type.rows) { kk -> mul(a.c[kk], b.c[col * b.type.rows + kk]) }
                })
            }
            else -> pair(a, b) { x, y -> mul(x, y) }
        }
    }

    private fun dotSquare(v: IntervalValue): Iv {
        var s = k(0f)
        for (x in v.c) s = add(s, sq(x))
        return s
    }

    private fun bitTruth(x: Iv): GlslTruth = when {
        x == Iv.TRUE -> GlslTruth.TRUE
        x == Iv.FALSE -> GlslTruth.FALSE
        else -> GlslTruth.UNKNOWN
    }

    private fun or(a: GlslTruth, b: GlslTruth): GlslTruth = when {
        a == GlslTruth.TRUE || b == GlslTruth.TRUE -> GlslTruth.TRUE
        a == GlslTruth.FALSE && b == GlslTruth.FALSE -> GlslTruth.FALSE
        else -> GlslTruth.UNKNOWN
    }

    private fun and(a: GlslTruth, b: GlslTruth): GlslTruth = when {
        a == GlslTruth.FALSE || b == GlslTruth.FALSE -> GlslTruth.FALSE
        a == GlslTruth.TRUE && b == GlslTruth.TRUE -> GlslTruth.TRUE
        else -> GlslTruth.UNKNOWN
    }
}