/* * Copyright 2021 Benjamin Dahse * Copyright 2025 Casper Dalgaard Nielsen * Adam Yasser Tallouzi * * Use of this source code is governed by the Apache 2.0 license * that can be found in the LICENSE.md file. *//// Phase that simplifies and optimizes RAM programs.pubmod Fixpoint3.Phase.Simplifier {use Fixpoint3.Ast.Ram.{BoolExp, Facts, Predicates, RamProgram, RamStmt, RelOp, RelSym, RowVar}use Fixpoint3.Ast.Ram.RamTerm.RowLoaduse Fixpoint3.Predicate.{getRelSymAsType, PredType}////// Optimize and simplify `stmt` by deleting redundant code and reordering code./// Examples of redundancy include `x[i] == x[i]`./// Reordering means `(0, 1) ∉ Path ∧ x[1] = y[0]` would be swapped `x[1] = y[0] ∧ (0, 1) ∉ Path`./// A simple static analysis also reveals that the following join-loop is redundant/// in stratum `j` if `C` is computed in stratum `i` and `i < j`:/// ```/// search x ∈ B do/// search y ∈ ΔC do/// if (x[0] == y[0] ∧ (x[0]) ∉ A ∧ (x[0]) ∉ R) then/// project (x[0]) into ΔR'/// end/// end/// end/// ```////// The returned RAM program is always the same size or smaller.///pubdefsimplifyProgram(program: RamProgram): RamProgram = unsafeIO {matchprogram {case RamProgram.Program(stmt, facts, predicates, index, typeInfo) =>// Compute the a set of all relations which are initialized with facts, i.e. have some EDB facts.letfilterOutEmpty = fact ->fact |> Map.filter(x -> (notBPlusTree.isEmpty(x))) |> Map.foldRightWithKey(k -> _ -> Set.insert(k), Set#{}) |> Set.map(rel -> getRelSymAsType(rel, PredType.Full, predicates));letnonEmpty = filterOutEmpty(fst(facts)) `Set.union`filterOutEmpty(snd(facts)); RamProgram.Program(simplifyStmt(stmt, nonEmpty, predicates), facts, predicates, index, typeInfo) } }////// Simplifies `stmt` by removing redundant code.////// `nonEmpty` is the set of `RelSym` whose relation is non-empty.////// `Predicates` is a structure for interacting with predicate symbols.///pubdefsimplifyStmt(stmt: RamStmt, nonEmpty: Set[RelSym], predicates: Predicates): RamStmt =Option.getWithDefault(RamStmt.Seq(Vector#{}), simplifyHelper(nonEmpty, predicates, false, stmt))////// Helper function for `simplifyStmt`.////// `nonEmpty` is the set of `RelSym` whose relation is non-empty.////// `Predicates` is a structure for interacting with predicate symbols.////// `inUntil` is true iff `stmt` is in the body of an `Until` stmt.////// Returns `None` when `stmt` is discovered to be redundant.////// Returns `Some(v)` where `v` semantically equivalent to `stmt` if we cannot/// conclude that `stmt` is redundant.////// The redundant examples handled directly by `simplifyHelper` is restricted to `Until(test, body)` where/// `body` can be simplified to only contain `Merge`, `Swap` and `Purge`.////// A simplification of an `Insert` can also lead to `None`, though this is handled by `simplifyOp`.///defsimplifyHelper(nonEmpty: Set[RelSym], predicates: Predicates, inUntil: Bool, stmt: RamStmt): Option[RamStmt] = matchstmt {case RamStmt.Insert(op) =>simplifyOp(nonEmpty, predicates, inUntil, Set#{}, op) |> Option.map(RamStmt.Insert)case RamStmt.MergeInto(_, _) => Some(stmt)case RamStmt.Swap(_, _) => Some(stmt)case RamStmt.Purge(_) => Some(stmt)case RamStmt.Seq(xs) =>Vector.filterMap(simplifyHelper(nonEmpty, predicates, inUntil), xs) |> RamStmt.Seq |> Somecase RamStmt.Par(xs) =>Vector.filterMap(simplifyHelper(nonEmpty, predicates, inUntil), xs) |> RamStmt.Par |> Somecase RamStmt.Until(test, body) =>matchsimplifyHelper(nonEmpty, predicates, true, body) {case None => Nonecase Some(simplifiedBody) =>if (isOnlyMergeSwapPurge(simplifiedBody)) { None } else { Some(RamStmt.Until(test, simplifiedBody)) } }case RamStmt.Comment(_) => Some(stmt) }////// Simplify `op` by removing redundant code. See `simplifyStmt`.////// `inUntil` is true if `op` appears in an `Until` statement.////// `seenPreds` is the set of `RelSym` seen so far.////// Returns `None` when children are simplified to `None` or for certain `Project`/// statements not in `Until` bodies depending on themselves. Consider the example/// ```/// search x ∈ B do/// search y ∈ A do/// project (x[0], y[0]) into ΔA'/// end/// end;/// Merge(ΔA', A);/// Swap(ΔA', ΔA);/// Until(...) {...}/// ```////// Assuming `A` is empty/has no EDB facts then the naive loop is redundant./// This optimization is sound as `A` is only written to in merge statements and they always occur/// after the naive `Project` statements generated by the rules with `A` as head. We cannot remove any part of/// the semi-naive `Until`-loop, as other rules could populate `A`/`ΔA`.////// Returns `Some(v)` otherwise, where `v` is a simplifcation of `op`.///defsimplifyOp(nonEmpty: Set[RelSym], predicates: Predicates, inUntil: Bool, seenPreds: Set[RelSym], op: RelOp): Option[RelOp] =letrecurseNewRel = rel -> simplifyOp(nonEmpty, predicates, inUntil, Set.insert(rel, seenPreds));letrecurse = simplifyOp(nonEmpty, predicates, inUntil, seenPreds);matchop {case RelOp.Search(rowVar, relSym, t, body) =>recurseNewRel(relSym, body) |> Option.map(newBody -> RelOp.Search(rowVar, relSym, t, newBody))case RelOp.Query(rowVar, relSym, qry, _, t, body) =>recurseNewRel(relSym, body) |> Option.map(RelOp.Query(rowVar, relSym, qry, -1, t))case RelOp.Functional(rowVar, f, terms, body, arity, t) =>recurse(body) |> Option.map(newBody -> RelOp.Functional(rowVar, f, terms, newBody, arity, t))case RelOp.Project(_, rel, _) =>// If we are not in an until and we insert in the new relation for `rel`,// we depend on `rel`, and `rel` is empty beforehand the code is dead.// See the example above in the doc comment for what this removes.letfullRel = getRelSymAsType(rel, PredType.Full, predicates);letdependOnRel = Set.memberOf(fullRel, seenPreds);letrelIsEmpty = (notSet.memberOf(fullRel, nonEmpty));letisDead = (notinUntil) anddependOnRelandrelIsEmpty;if (isDead) None else Some(op)case RelOp.If(test, then) =>let (memberOf, rest) = test// Delete checks of the form `x[i] == x[i]`. |> Vector.filter(e -> matche {case BoolExp.Eq(RowLoad(lhs1, lhs2, _, _), RowLoad(rhs1, rhs2, _, _)) => (lhs1, lhs2) != (rhs1, rhs2)case_ => true } )// Partition into membership tests and rest. |> Vector.partition(e -> matche {case BoolExp.NotMemberOf(_, _, _) => truecase_ => false } );letnewTest = rest ++ memberOf;// Simplify `if () then body` to `body`.matchrecurse(then) {case None => Nonecase Some(newThen) =>if (Vector.isEmpty(newTest)) Some(newThen)else Some(RelOp.If(newTest, newThen)) } }////// Returns true if `r` is a `Merge`, `Swap`, or `Purge` statement or contains any one/// of them.///defisOnlyMergeSwapPurge(r: RamStmt): Bool = matchr {case RamStmt.Insert(_) => falsecase RamStmt.MergeInto(_, _) => truecase RamStmt.Swap(_, _) => truecase RamStmt.Purge(_) => truecase RamStmt.Seq(xs) => Vector.forAll(isOnlyMergeSwapPurge, xs)case RamStmt.Par(xs) => Vector.forAll(isOnlyMergeSwapPurge, xs)case RamStmt.Until(_, _) => falsecase RamStmt.Comment(_) => true }}