/* * 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. */////// This phase lowers a high-level `Ram.RamProgram` into a low-level `ExecutableRam.RamProgram`/// executable by the Interpreter.////// Concretely:////// All `Boxed` values are replaced with representative `Int64` values according to the `Boxing`.////// For relations with multiple indexes,/// ```/// MergeInto(newRel, otherRel)/// ```/// becomes:/// ```/// MergeInto(newRel, otherRelIndex1)/// MergeInto(newRel, otherRelIndex2)/// .../// ```/// This ensures that all indexes are updated.////// Similarly:/// ```/// Purge(rel)/// ```/// becomes:/// ```/// Purge(relIndex1)/// Purge(relIndex2)/// .../// ```////// For:/// ```/// Swap(newRel, deltaRel)/// ```/// it is transformed into:/// ```/// Purge(deltaRelIndex2), Purge(deltaRelIndex3), .../// MergeInto(newRel, deltaRelIndex2), MergeInto(newRel, deltaRelIndex3), .../// Swap(newRel, deltaRelIndex1)/// ```/// The above transformation relies on `newRel` having exactly one index.////// Statements that previously referenced a `RelSym` are instead augmented with explicit/// index information (currently as `Int32`) indicating which index to use.////// `NotBot`s are removed entirely from the AST. The interpreter handles this explicitly/// whenever it encounters a `Search` or `Query` with `Latticenal`.////// Every `RowVar` is assigned a unique position in memory (though not strictly necessary).////// Finally instead of a query evaluating terms the terms are written to an array which/// is kept up to date with the current query values.////// As an example the program:/// ```/// search a ∈ A do/// query {b ∈ B | b[0] = a[0]} do/// project (a[0]) into R/// end/// end/// ```/// would be transformed to/// ```/// search a ∈ A | [lookupArrForB[0] := a[0]] do/// query {b ∈ B | lookupArrForB } do/// project (a[0]) into R/// end/// end/// ```/// Whenever we bind `a` we update the query-value of `B`.////// An alternative approach would be to have `query` of the form/// `query {b ∈ B | searchTerms }`. Then `searchTerms` would be evaluated to generate the/// restriction for the query. This would create a new object every time the `query` is/// met. Furthermore it would have to be evaluated every time `query {b ∈ B | lookupArrForB }`/// is met. If the query is deeply nested with some values bound higher up in the nesting,/// we do not need to evaluate these every time.///pubmod Fixpoint3.Phase.Lowering {use Fixpoint3.Ast.ExecutableRamuse Fixpoint3.Ast.ExecutableRam.WriteTupleuse Fixpoint3.Ast.Ramuse Fixpoint3.Ast.Ram.{arityOf, arityOfNonLat, IndexInformation, Predicates, RelSym, RowVar, Search, toDenotation, toId}use Fixpoint3.Ast.Shared.{Denotation, PredSym}use Fixpoint3.Boxeduse Fixpoint3.Boxinguse Fixpoint3.BoxingType.Boxinguse Fixpoint3.Predicate.{getRelSymAsType, PredType}use Fixpoint3.TypeInfouse Fixpoint3.TypeInfo.{getTypeOf, TypeInformation}use Fixpoint3.UniqueIntsuse Fixpoint3.Util.getOrCrash////// Maps `RowVar`s to their memory position (position in an array the interpreter/// will use).///typealiasIdToIndex[r: Region] = UniqueInts[RowVar, r]////// Maps `RowVar`s to a list of writes that should be performed whenever the `RowVar` is/// rebound.///typealiasWriteTos[r: Region] = MutMap[RowVar, List[WriteTuple], r]////// Maps a memory position to writes.////// For example, `[2 => [0 => 38i64]]` means that the first attribute of the `RowVar`,/// placed at position 2, should have value 38.///typealiasConstWrites[r: Region] = MutMap[Int32, MutMap[Int32, Int64, r], r]////// Contains information in relation to computing meets. For `(list, disjoint)`, `list`/// will contain the currently bound `RowVar`s in reverse order. `disjoint` contains/// information about which `RowVar`s' lattice values should be meeted.////// For example, consider:/// ```/// search x ∈ A do/// search y ∈ B do/// project (0; Meet(x, y)) into ΔC'/// end/// end/// ```/// Initially, `MeetWithMap` is `(Nil, {})`. When the `RelOp` `search y ∈ B do` is/// lowered, `x` is registered, and `MeetWithMap` becomes `(x :: Nil, {})`. When the/// `project` is lowered, it `(y :: x :: Nil, {})`. Finally, `Meet(x, y)` is lowered,/// which updates `disjoint` to `{y = x}`.////// The above code snippet is lowered to:/// ```/// search x ∈ A do/// search y ∈ B | x_y_meet = Meet(x, y) do/// project (0; x_y_meet) into ΔC'/// end/// end/// ```/// Note that the lattice value of `y` is not actually stored, only the meet.///typealiasMeetWithMap[r: Region] = (List[RowVar], MutDisjointSets[RowVar, r])////// Lower `program` from `Ram` to `ExecutableRam`.////// Returns the result along with the `Boxing` used for the program///pubdeflowerProgram(rc: Region[r], withProv: Bool, program: Ram.RamProgram): (ExecutableRam.RamProgram[r], Boxing[r]) \ r = matchprogram {case Ram.RamProgram.Program(stmt, _, meta, indexInfo, typeInfo) =>letidToIndex = UniqueInts.empty(rc);let (boxing, newFacts) = Boxing.initialize(rc, withProv, program);letwriteTo = (MutMap.empty(rc), MutMap.empty(rc));letloweredStmt = lowerStmt(rc, idToIndex, boxing, writeTo, indexInfo, meta, withProv, stmt);letconstWrites = snd(writeTo) |> MutMap.foldWithKey(acc1 -> outerPos -> inner ->MutMap.foldLeftWithKey(acc2 -> innerPos -> value -> (outerPos, innerPos, value) :: acc2, acc1, inner), Nil ) |> List.toVector;// Compute aritiesletarityInformation = rowVarArityOf(program);letnumOfArities = UniqueInts.get(idToIndex);letarities = Array.empty(rc, numOfArities);foreach ((rv, arity) <- arityInformation) {Array.put(arity, UniqueInts.createIndex(rv, idToIndex), arities) }; (ExecutableRam.RamProgram.Program(loweredStmt, newFacts, meta, indexInfo, (Array.toVector(arities), constWrites), typeInfo), boxing) }////// Lowers `stmt` from `Ram` to `ExecutableRam`.////// `idToIndex` assigns `RowVar`s to memory positions.////// `boxingInfo` is used to find the boxing information of `Boxed` values and to compute/// the `Int64` representative.////// `writeTo` contains write information. See `WriteTos` and `ConstWrites`.////// `indexInfo` is used to replace `RelSym` with an indexes.///deflowerStmt(rc: Region[r],idToIndex: IdToIndex[r],boxing: Boxing[r],writeTo: (WriteTos[r], ConstWrites[r]),indexInfo: IndexInformation,predicates: Predicates,withProv: Bool,stmt: Ram.RamStmt ): ExecutableRam.RamStmt \ r =letlowerStmtRec = lowerStmt(rc, idToIndex, boxing, writeTo, indexInfo, predicates, withProv);matchstmt {case Ram.RamStmt.Insert(op) =>letnewOp = lowerOp(rc, idToIndex, boxing, writeTo, indexInfo, (Nil, MutDisjointSets.empty(rc)), predicates, withProv, op); ExecutableRam.RamStmt.Insert(newOp)case Ram.RamStmt.MergeInto(newRel, otherRel) =>// `MergeInto` should simply be repeated for all indexes built on `deltaRel`.let (constructed, placements) = indexInfo;letnewRelPos = getOrCrash(Map.get((newRel, 0), placements));letlowerToMerge = index -> ExecutableRam.RamStmt.MergeInto(newRelPos, index, toDenotation(newRel));letdeltaRels = Vector.length(getOrCrash(Map.get(otherRel, constructed)));letmerges = Vector.map(index -> lowerToMerge(getOrCrash(Map.get((otherRel, index), placements))), Vector.range(0, deltaRels));matchVector.length(merges) {case 1 => Vector.get(0, merges)case_ => ExecutableRam.RamStmt.Par(merges) }case Ram.RamStmt.Swap(newRel, deltaRel) =>let (constructed, placements) = indexInfo;letnewRelPos = getOrCrash(Map.get((newRel, 0), placements));letlowerToMerge = index -> ExecutableRam.RamStmt.MergeInto(newRelPos, index, toDenotation(newRel));letlowerToPurge = index -> ExecutableRam.RamStmt.Purge(index);letdeltaRels = Vector.length(getOrCrash(Map.get(deltaRel, constructed)));letswapPart = ExecutableRam.RamStmt.Swap(newRelPos, getOrCrash(Map.get((deltaRel, 0), placements)));matchVector.length(getOrCrash(Map.get(deltaRel, constructed))) {// If there is only 1 index on `deltaRel` we can simply swap.case 1 => swapPartcase_ =>// We can still swap `newRel` and `deltaRelIndex[0]`. All other indexes will// instead treated as a `Purge(deltaRel[i])` followed by a `MergeInto(newRel, deltaRel[i])`.// Purges are cheap and performed sequentially. Merges can be expensive and are performed in// parallel.letmerges = Vector.range(1, deltaRels) |> Vector.map(index -> lowerToMerge(getOrCrash(Map.get((deltaRel, index), placements))));letpurges = Vector.range(1, deltaRels) |> Vector.map(index -> lowerToPurge(getOrCrash(Map.get((deltaRel, index), placements))));if (Vector.length(merges) ==1) { ExecutableRam.RamStmt.Seq(Vector#{Vector.get(0, merges), swapPart}) } else { ExecutableRam.RamStmt.Seq( Vector#{ ExecutableRam.RamStmt.Seq(purges), ExecutableRam.RamStmt.Par(merges),swapPart } ) } }case Ram.RamStmt.Purge(rel) =>// `Purge` should simply be repeated for all indexes built on `rel`.let (constructed, placements) = indexInfo;letindexNums = Vector.range(0, Vector.length(getOrCrash(Map.get(rel, constructed))));letpurges = Vector.map(i -> ExecutableRam.RamStmt.Purge(getOrCrash(Map.get((rel, i), placements))), indexNums);if (Vector.length(purges) ==1) {Vector.get(0, purges) } else { ExecutableRam.RamStmt.Seq(purges) }case Ram.RamStmt.Seq(xs) => ExecutableRam.RamStmt.Seq(Vector.map(x -> lowerStmtRec(x), xs))case Ram.RamStmt.Par(xs) => ExecutableRam.RamStmt.Par(Vector.map(x -> lowerStmtRec(x), xs))case Ram.RamStmt.Until(test, body) =>letnewTests = test |> Vector.filterMap(lowerBool(idToIndex, boxing, indexInfo, (Nil, MutDisjointSets.empty(rc))));letnewBody = lowerStmtRec(body); ExecutableRam.RamStmt.Until(newTests, newBody)case Ram.RamStmt.Comment(s) => ExecutableRam.RamStmt.Comment(s) }////// Lower `op` from `Ram` to `ExecutableRam`.////// See the description of the module and `lowerStmt`.///deflowerOp(rc: Region[r],idToIndex: IdToIndex[r],boxing: Boxing[r],writeTo: (WriteTos[r], ConstWrites[r]),indexInfo: IndexInformation,meetWithMap: MeetWithMap[r],predicates: Predicates,withProv: Bool,op: Ram.RelOp ): ExecutableRam.RelOp \ r =letlowerOpRec = lowerOp(rc, idToIndex, boxing, writeTo, indexInfo, meetWithMap, predicates, withProv);letlowerOpRecNewRowVar = rv -> lowerOp(rc, idToIndex, boxing, writeTo, indexInfo, addRowVarToMeetWithMap(rv, meetWithMap), predicates, withProv);matchop {case Ram.RelOp.Search(rv, relSym, _, body) =>letden = toDenotation(relSym);letloweredBody = lowerOpRecNewRowVar(rv, body);letthisWriteTo = getWriteTo(rv, writeTo);letplacements = snd(indexInfo);letrelPos = getOrCrash(Map.get((relSym, 0), placements));letoldPos = computeMeetWithPos(rv, relSym, idToIndex, meetWithMap); ExecutableRam.RelOp.Search(lowerRowVar(rv, idToIndex), relPos, oldPos, den, thisWriteTo, loweredBody)case Ram.RelOp.Query(rv, relSym, tests, index, _, body) =>letden = toDenotation(relSym);letloweredBody = lowerOpRecNewRowVar(rv, body);letoldPos = computeMeetWithPos(rv, relSym, idToIndex, meetWithMap);letthisWriteTo = getWriteTo(rv, writeTo);letotherBools = Vector.filterMap(x -> matchx {case Ram.BoolExp.Eq(Ram.RamTerm.RowLoad(rv1, i1, _, _), Ram.RamTerm.RowLoad(rv2, i2, _, _)) ifrv==rv1 =>addWriteTo(rv2, i2, rv1, i1, idToIndex, writeTo); Nonecase Ram.BoolExp.Eq(Ram.RamTerm.RowLoad(rv2, i2, _, _), Ram.RamTerm.RowLoad(rv1, i1, _, _)) ifrv==rv1 =>addWriteTo(rv2, i2, rv1, i1, idToIndex, writeTo); Nonecase Ram.BoolExp.Eq(Ram.RamTerm.RowLoad(rv1, i, _, _), Ram.RamTerm.Lit(val, ty)) ifrv==rv1 =>letunboxed = Boxing.unboxWith(val, ty, boxing);addConstWriteTo(unboxed, rv, i, idToIndex, writeTo, rc); Nonecase Ram.BoolExp.Eq(Ram.RamTerm.Lit(val, ty), Ram.RamTerm.RowLoad(rv1, i, _, _)) ifrv==rv1 =>letunboxed = Boxing.unboxWith(val, ty, boxing);addConstWriteTo(unboxed, rv, i, idToIndex, writeTo, rc); Nonecase_ => Some(x) },tests );if (Vector.length(otherBools) !=0) {unchecked_cast(bug!("Bug in Fixpoint.Lowering: Bools except equality in query") as _ \ r) }; ExecutableRam.RelOp.Query(lowerRowVar(rv, idToIndex), index, oldPos, den, thisWriteTo, loweredBody)case Ram.RelOp.Functional(rv, f, terms, body, _, thisType) =>letloweredBody = lowerOpRec(body);letthisWriteTo = getWriteTo(rv, writeTo); ExecutableRam.RelOp.Functional(lowerRowVar(rv, idToIndex),f,Vector.map(lowerTerm(idToIndex, boxing, meetWithMap), terms),thisWriteTo,loweredBody,thisType )case Ram.RelOp.Project(terms, s, _) =>let (_, placements) = indexInfo;letnewRelPos = getOrCrash(Map.get((s, 0), placements));letden = toDenotation(s);letloweredTerms = Vector.map(lowerTerm(idToIndex, boxing, meetWithMap), terms);if (notwithProv) { ExecutableRam.RelOp.Project(loweredTerms, newRelPos, den) } else {// Have to get the full relation to be able to get the depth of it.letrelPos = getOrCrash(Map.get((getRelSymAsType(s, PredType.Full, predicates), 0), placements)); ExecutableRam.RelOp.ProvProject(loweredTerms, newRelPos, relPos) }case Ram.RelOp.If(boolExps, body) => ExecutableRam.RelOp.If(Vector.filterMap(lowerBool(idToIndex, boxing, indexInfo, meetWithMap), boolExps), lowerOpRec(body)) }////// Lower `term` from `Ram` to `ExecutableRam`.////// Computes unification for `Meet`. See `lowerMeet`.////// The following transformations are the most important.////// 1. `Lit(boxedVal)` is augmented with the `Int64` representative of `boxedVal`./// 2. `RowVar`s are replaced by memory positions./// 3. `RowLoad(rv, i, rel)` is replaced by `LoadFromTuple` or `LoadLatVar` depending/// on whether `RowLoad(rv, i, rel)` refers to the lattice value or a 'normal' value./// 4. `AMeet(_)` is replaced by `LoadLatVar`.///deflowerTerm(idToIndex: IdToIndex[r],boxingInfo: Boxing[r],meetWithMap: MeetWithMap[r],term: Ram.RamTerm ): ExecutableRam.RamTerm \ r =letlowerT = lowerTerm(idToIndex, boxingInfo, meetWithMap);letboxing = boxingInfo;matchterm {case Ram.RamTerm.Lit(val, ty) => ExecutableRam.RamTerm.Lit(Boxing.unboxWith(val, ty, boxing), val)case Ram.RamTerm.ProvMax(vec) =>vec |> Vector.map(match (rv, i) -> (UniqueInts.createIndex(rv, idToIndex), i)) |> ExecutableRam.RamTerm.ProvMaxcase Ram.RamTerm.RowLoad(rv, index, ty, RelSym.Symbol(_, arity, den)) =>matchden {case Denotation.Relational => ExecutableRam.RamTerm.LoadFromTuple(UniqueInts.createIndex(rv, idToIndex), index, ty)case Denotation.Latticenal(_, _, _, _) =>if (index<arity-1) { ExecutableRam.RamTerm.LoadFromTuple(UniqueInts.createIndex(rv, idToIndex), index, ty) } else { ExecutableRam.RamTerm.LoadLatVar(UniqueInts.createIndex(rv, idToIndex), ty) } }case Ram.RamTerm.Meet(_, _, _, ty) =>unifyMeet(term, meetWithMap);letrepresentingRowVar = getAnyMeetRowVar(term);letpos = computeLatticeMeetPos(representingRowVar, idToIndex, meetWithMap); ExecutableRam.RamTerm.LoadLatVar(pos, ty)case Ram.RamTerm.App1(f, t1, ty) => ExecutableRam.RamTerm.App1(f, lowerT(t1), ty)case Ram.RamTerm.App2(f, t1, t2, ty) => ExecutableRam.RamTerm.App2(f, lowerT(t1), lowerT(t2), ty)case Ram.RamTerm.App3(f, t1, t2, t3, ty) => ExecutableRam.RamTerm.App3(f, lowerT(t1), lowerT(t2), lowerT(t3), ty)case Ram.RamTerm.App4(f, t1, t2, t3, t4, ty) => ExecutableRam.RamTerm.App4(f, lowerT(t1), lowerT(t2), lowerT(t3), lowerT(t4), ty)case Ram.RamTerm.App5(f, t1, t2, t3, t4, t5, ty) => ExecutableRam.RamTerm.App5(f, lowerT(t1), lowerT(t2), lowerT(t3), lowerT(t4), lowerT(t5), ty) }////// Return any `RowVar` involved in the meet of `term`.////// `term` must be a `RamTerm.Meet` or a `RamTerm.RowLoad`.///defgetAnyMeetRowVar(term: Ram.RamTerm ): RowVar = matchterm {case Ram.RamTerm.RowLoad(rv, _, _, _) =>rvcase Ram.RamTerm.Meet(_, _, (rv, _), _) =>rvcase_ => unreachable!() }////// Unify every `RowVar` met during unpacking of `term` in `meetWithMap`.////// `term` must be a `RamTerm.Meet` or a `RamTerm.RowLoad`.///defunifyMeet(term: Ram.RamTerm,meetWithMap: MeetWithMap[r] ): Unit \ r = {letunifyingRv = getAnyMeetRowVar(term);defloop(t) = matcht {case Ram.RamTerm.RowLoad(rv, _, _, _) =>let (_, meetDisjointSet) = meetWithMap;MutDisjointSets.union(rv, unifyingRv, meetDisjointSet)case Ram.RamTerm.Meet(_, t1, (rv, _), _) =>let (_, meetDisjointSet) = meetWithMap;MutDisjointSets.union(rv, unifyingRv, meetDisjointSet);loop(t1)case_ => unreachable!() };loop(term) }////// Lower `boolExp` from `Ram` to `ExecutableRam`.////// Returns `Some(lowered)` for `boolExp != NotBot(_)`.////// Returns `None` for `NotBot` as it is handled directly by the interpreter.////// Primarily lowers terms and replaces `RelSym`s with a concrete index.///deflowerBool(idToIndex: IdToIndex[r],boxingInfo: Boxing[r],indexInfo: IndexInformation,meetWithMap: MeetWithMap[r],boolExp: Ram.BoolExp ): Option[ExecutableRam.BoolExp] \ r =letlowerT = lowerTerm(idToIndex, boxingInfo, meetWithMap);matchboolExp {case Ram.BoolExp.Not(s) =>lowerBool(idToIndex, boxingInfo, indexInfo, meetWithMap, s) |> Option.map(ExecutableRam.BoolExp.Not)case Ram.BoolExp.IsEmpty(s) =>let (_, placements) = indexInfo;letsPos = getOrCrash(Map.get((s, 0), placements)); Some(ExecutableRam.BoolExp.IsEmpty(sPos))case Ram.BoolExp.NotMemberOf(terms, s, _) =>let (_, placements) = indexInfo;letden = toDenotation(s);letsPos = getOrCrash(Map.get((s, 0), placements)); Some(ExecutableRam.BoolExp.NotMemberOf(Vector.map(lowerT, terms), sPos, den))case Ram.BoolExp.NotBot(t1, _, _) =>unifyMeet(t1, meetWithMap); Nonecase Ram.BoolExp.Leq(box, rv, relSym) =>letleq = matchtoDenotation(relSym) {case Denotation.Relational => _ -> _ -> bug!("Using lattice logic on normal relation")case Denotation.Latticenal(_, leq, _, _) => leq }; Some(ExecutableRam.BoolExp.Leq(box, leq, lowerRowVar(rv, idToIndex)))case Ram.BoolExp.Eq(term1, term2) => Some(ExecutableRam.BoolExp.Eq(lowerT(term1), lowerT(term2)))case Ram.BoolExp.Guard1(f, term1) => Some(ExecutableRam.BoolExp.Guard1(f, lowerT(term1)))case Ram.BoolExp.Guard2(f, term1, term2) => Some(ExecutableRam.BoolExp.Guard2(f, lowerT(term1), lowerT(term2)))case Ram.BoolExp.Guard3(f, term1, term2, term3) => Some(ExecutableRam.BoolExp.Guard3(f, lowerT(term1), lowerT(term2), lowerT(term3)))case Ram.BoolExp.Guard4(f, term1, term2, term3, term4) => Some(ExecutableRam.BoolExp.Guard4(f, lowerT(term1), lowerT(term2), lowerT(term3), lowerT(term4)))case Ram.BoolExp.Guard5(f, term1, term2, term3, term4, term5) => Some(ExecutableRam.BoolExp.Guard5(f, lowerT(term1), lowerT(term2), lowerT(term3), lowerT(term4), lowerT(term5))) }////// Lower `rowVar` to a memory position.////// Note: every `RowVar` is assigned a unique memory position.///deflowerRowVar(rowVar: RowVar, idToIndex: IdToIndex[r]): Int32 \ r =UniqueInts.createIndex(rowVar, idToIndex)////// Add `rv` to `meetWithMap`.////// Concretely, for `meetWithMap = (seenRV, equalitySet)`, return/// `(rv:: seen, equalitySet')` where `equalitySet` is simply updated with `rv` as a/// possible value.///defaddRowVarToMeetWithMap(rv: RowVar, meetWithMap: MeetWithMap[r]): MeetWithMap[r] \ r =let (prevRowVars, meetDisjointSet) = meetWithMap;MutDisjointSets.makeSet(rv, meetDisjointSet); (rv :: prevRowVars, meetDisjointSet)////// Compute the memory position of the value that the lattice value associated with/// `rv` should be meeted (greatest lower bound) with.////// Returns -1 for 'normal' relations. Returns `idToIndex[rv]` when `rv` is not/// supposed to be meeted with anything. Otherwise, returns the memory position/// of the value that `rv` should be meeted with.////// For example, the program:/// ```/// R(;y) :- A(;x), B(;x), ... ./// ```/// is compiled to:/// ```/// search a ∈ A do/// search b ∈ B | meet_a_b = meet(a, b) do/// .../// end/// end/// ```/// where `A` and `B`. If `A` and `B` are lattice relations then for `RowVar` `a`/// it will return `idToIndex[a]` signalling that it should not meeted, but is a lattice-relation.////// For `RowVar` `b`, it also returns `idToIndex[a]`, but this is not the same as/// `idToIndex[b]` signalling that a meet should be performed with the value at/// `idToIndex[a]`.///defcomputeMeetWithPos(rv: RowVar,relSym: RelSym,idToIndex: IdToIndex[r],meetWithMap: MeetWithMap[r] ): Int32 \ r = matchtoDenotation(relSym) {case Denotation.Relational => -1case Denotation.Latticenal(_, _, _, _) => computeLatticeMeetPos(rv, idToIndex, meetWithMap) }////// Returns the memory position of the first value equivalent to `rv`, w.r.t./// `meetWithMap`. If no such value exists, returns the memory position of `rv`.////// See `computeMeetWithPos`.///defcomputeLatticeMeetPos(rv: RowVar,idToIndex: IdToIndex[r],meetWithMap: MeetWithMap[r] ): Int32 \ r = {let (prevRowVars, meetDisjointSet) = meetWithMap;letfirstMatching = List.findLeft(x -> MutDisjointSets.equivalent(rv, x, meetDisjointSet), prevRowVars);matchfirstMatching {case None => lowerRowVar(rv, idToIndex)case Some(v) => lowerRowVar(v, idToIndex) } }////// Return a `Vector` of writes that should be performed when `rowVar` is bound.///defgetWriteTo(rowVar: RowVar, writeTo: (WriteTos[r], ConstWrites[r])): Vector[WriteTuple] \ r =letvarWrite = fst(writeTo);List.toVector(Option.getWithDefault(Nil, MutMap.get(rowVar, varWrite)))////// Register that when `fromRowVar` is bound it should perform a write to `toRowVar`.////// Concretely, we need to preserve `fromRowVar[fromIndex] == toRowVar[toIndex]`.///defaddWriteTo(fromRowVar: RowVar,fromIndex: Int32,toRowVar: RowVar,toIndex: Int32,idToIndex: IdToIndex[r],writeTo: (WriteTos[r], ConstWrites[r]) ): Unit \ r =letvarWrite = fst(writeTo);letwritten = (fromIndex, lowerRowVar(toRowVar, idToIndex), toIndex);MutMap.putWith(_ -> old -> written :: old, fromRowVar, written :: Nil, varWrite)////// Register that `toRowVar` has the value `value`.////// Concretely, maintains that `toRowVar[toIndex] == value`.///defaddConstWriteTo(value: Int64,toRowVar: RowVar,toIndex: Int32,idToIndex: IdToIndex[r],writeTo: (WriteTos[r], ConstWrites[r]),rc: Region[r] ): Unit \ r =letconstWrite = snd(writeTo);lettoTuplePos = lowerRowVar(toRowVar, idToIndex);MutMap.getOrElsePut(toTuplePos, MutMap.empty(rc), constWrite) |> MutMap.putWith(v1 -> v2 ->if (v1!=v2)bug!("Bug in Fixpoint.Lowering: Different value for position. Should have been removed by Hoisting")elsev1,toIndex,value )////// Returns a list `(RowVar, arity)` in `program` in the order of the nesting.///defrowVarArityOf(program: Ram.RamProgram): List[(RowVar, Int32)] =rowVarRelSymOf(program) |> List.map(match (rv, RelSym.Symbol(_, arity, _)) -> (rv, arity))////// Returns a list of pairs `(RowVar, RelSym)` in `program` in the order of the nesting.///defrowVarRelSymOf(program: Ram.RamProgram): List[(RowVar, RelSym)] = matchprogram {case Ram.RamProgram.Program(stmt, _, _, _, _) => rowVarRelSymOfStmt(stmt) }////// Returns a list of pairs `(RowVar, RelSym)` in `stmt`.///defrowVarRelSymOfStmt(stmt: Ram.RamStmt): List[(RowVar, RelSym)] \ r = matchstmt {case Ram.RamStmt.Insert(op) => rowVarRelSymOfOp(op)case Ram.RamStmt.MergeInto(_, _) => Nilcase Ram.RamStmt.Swap(_, _) => Nilcase Ram.RamStmt.Purge(_) => Nilcase Ram.RamStmt.Seq(stmts) => Vector.foldMap(rowVarRelSymOfStmt, stmts)case Ram.RamStmt.Par(stmts) => Vector.foldMap(rowVarRelSymOfStmt, stmts)case Ram.RamStmt.Until(_, rest) => rowVarRelSymOfStmt(rest)case Ram.RamStmt.Comment(_) => Nil }////// Returns a list of pairs `(RowVar, RelSym)` in `op` in the order of nesting.///defrowVarRelSymOfOp(op: Ram.RelOp): List[(RowVar, RelSym)] \ r = matchop {case Ram.RelOp.Search(rv, relSym, _, rest) => (rv, relSym) :: rowVarRelSymOfOp(rest)case Ram.RelOp.Query(rv, relSym, _, _, _, rest) => (rv, relSym) :: rowVarRelSymOfOp(rest)case Ram.RelOp.Functional(_, _, _, rest, _, _) => rowVarRelSymOfOp(rest)case Ram.RelOp.Project(_, _, _) => Nilcase Ram.RelOp.If(_, rest) => rowVarRelSymOfOp(rest) }}