flix / flix

The Flix Programming Language
https://flix.dev/
Other
2.16k stars 151 forks source link

Widening with Datalog Semantics #5388

Open magnus-madsen opened 1 year ago

magnus-madsen commented 1 year ago
A(k1, k2, widen(old, new)) :- A(k1, k2, old), <original rule>
magnus-madsen commented 1 year ago
enum Interval with Eq, Order, ToString {
                case Top,

    case Range(BigInt, BigInt),

                case Bot
}

instance LowerBound[Interval] {
    pub def minValue(_: Unit): Interval = Interval.Bot
}

instance PartialOrder[Interval] {
    pub def lessEqual(x: Interval, y: Interval): Bool = match (x, y) {
        case (Bot, _)                       => true
        case (Range(b1, e1), Range(b2, e2)) => b2 <= b1 and e1 <= e2
        case (_, Top)                       => true
        case _                              => false
    }
}

instance JoinLattice[Interval] {
    pub def leastUpperBound(x: Interval, y: Interval): Interval = match (x, y) {
        case (Bot, _)                       => y
        case (_, Bot)                       => x
        case (Range(b1, e1), Range(b2, e2)) => Range(BigInt.min(b1, b2), BigInt.max(e1, e2))
        case _                              => Top
    }
}

instance MeetLattice[Interval] {
    pub def greatestLowerBound(x: Interval, y: Interval): Interval = match (x, y) {
        case (Top, _)                       => y
        case (_, Top)                       => x
        case (Range(b1, e1), Range(b2, e2)) => Range(BigInt.max(b1, b2), BigInt.min(e1, e2))
        case _                              => Bot
    }
}

pub def alpha(i: BigInt): Interval = Range(i, i)

pub def inc(x: Interval): Interval = match x {
    case Bot            => Bot
    case Range(b, e)    => debug!!(Range(b + 1ii, e + 1ii))
    case Top            => Top
}

///
/// The naive widen function only looks at a single lattice element.
///
def naiveWiden(x: Interval): Interval = match x {
    case Bot            => Bot
    case Range(b, e)    =>
        // Jump to top if the width has become more than 25.
        if (e - b <= 25ii)
            Range(b, e)
        else
            Top
    case Top            => Top
}

///
/// The general widen function takes both the old and new lattice elements.
/// This is often called nabla (upside triangle.)
///
// Note this is not strict in oldElm which is a problem.
def smartWiden(oldElm: Interval, newElm: Interval): Interval = match (oldElm, newElm) {
    case (Range(oldB, oldE), Range(newB, newE))    =>
        // Compute the length of each interval.
        let oldLen = oldE - oldB;
        let newLen = newE - newB;
        // If the interval only increased by 1 then we jump to top.
        let delta = debug!!(newLen - oldLen);
        if (delta <= 1ii)
            debug!!(Top)
        else
            newElm
    case _            => newElm
}

def main(): Unit \ IO =
    let pr = #{
        //
        //      x = 42
        //      A
        // inc  | \       widen if span > 100
        //      B--C
        //      |
        //      D
        //
        Edge("A", "B").
        Edge("B", "C").
        Edge("C", "A").
        Edge("B", "D").

        // To avoid some issues with bottom, we assume the constant 42 is everywhere.
        L("A", "x"; alpha(42ii)).
        L("B", "x"; alpha(42ii)).
        L("C", "x"; alpha(42ii)).
        L("D", "x"; alpha(42ii)).

        // Transfer function from A-B with naive widening.
        //L("B", x; naiveWiden(inc(v))) :- L("A", x; v).
        // Note how the above rule requires several iterations through the cycle.

        // Transfer function from A-B with smart widening.
        L("B", x; smartWiden(vold, inc(v))) :- L("A", x; v), L("B", x; vold).  // Implicit assumption that vold is not bottom.
        // Note how this reaches the fixpoint faster.

        // IDEA: We need strict and non-strict atoms and then an analysis to sort them out.
        // By sorting, I mean to ensure that non-strict atoms do not bind variables.
        // Then we can write:
        // L("B", x; smartWiden(vold, inc(v))) :- strict L("A", x; v), nonstrict L("B", x; vold).
        // This also requires new semantics in the engine.
        // Is this the same as the modedness?

        // Q2: Do we want a transform on programs where you can write: `widen P with smartWiden on L`? - as program transformation?

        // Propagate function (excluding A-B)
        L(succ, x; v) :- Edge(pred, succ), L(pred, x; v), if pred != "A".
    };
    query pr select (pp, x, v) from L(pp, x; v) |> println