/* * 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. */pubmod Fixpoint3.Ast.Ram {use Fixpoint3.Ast.Shared.{BoxedDenotation => Denotation, PredSym}use Fixpoint3.Ast.Shared.Denotation.{Relational}use Fixpoint3.Boxeduse Fixpoint3.PredSymsOfuse Fixpoint3.SubstitutePredSymuse Fixpoint3.TypeInfo.{provType, RelType, Type, TypeInformation}/////////////////////////////////////////////////////////////////////////////// RamProgram ///////////////////////////////////////////////////////////////////////////////////// A `RamProgram` represents a compiled Datalog program.///pubenumRamProgram {case Program(RamStmt, FactTuple, Predicates, IndexInformation, TypeInformation) }////// Contains a list of `RelSym` representing the names of the relations in the program,/// a list of Full `RelSym` and 1 + the maximal identifier of the Full `RelSym`s.///pubtypealiasPredicates = (List[RelSym], List[RelSym], Int64)////// The extensible database (EDB) of a program.///pubtypealiasFacts = Map[RelSym, BPlusTree[Vector[Boxed], Boxed, Static]]////// `FactsTuple` consists of `(oldFacts, newFacts)`, where `oldFacts` are given through/// joining a model, while `newFacts` are EDB facts written in the Datalog program.///pubtypealiasFactTuple = (Facts, Facts)////// A `Search` describes the order in which entries in a tuple are ordered. These are/// created during the index selection phase and are used by indexes to store tuples/// for efficient retrieval in range queries.////// For example, the Datalog rule////// A(x) :- B(x), C(_, _, x).////// produces the search `Vector#{2, 0, 1}` for relation `C`. This means the tuples in/// `C` are ordered by the third element, then the first and finally the second./// During the index selection phase, searches are collected and are expanded to/// describe the order of the whole tuple, i.e., the above search is collected as/// `Vector#{2}` but gets expanded to `Vector#{2, 0, 1}`. Thus, the final search used/// by an index has length exactly equal to the relation's arity.///pubtypealiasSearch = Vector[Int32]////// Maps to the set of `Search` for a `RelSym` in the Datalog program. It represents/// the indexes that will be created for `RelSym` during program evaluation.///pubtypealiasIndexes = Map[RelSym, Vector[Search]]////// Maps a `RelSym` and its place in `Indexes[RelSym]` to its final position in memory.///pubtypealiasIndexPositions = Map[(RelSym, Int32), Int32]////// The indexes to be created during program evaluation and their positions in memory.///pubtypealiasIndexInformation = (Indexes, IndexPositions)/////////////////////////////////////////////////////////////////////////////// RamStmt ///////////////////////////////////////////////////////////////////////////////////// `Insert(body)` executes the RelOp `body`.////// `MergeInto(r1, r2)` merges the facts of `r1` into `r2`.////// `Swap(newRel, deltaRel)` swaps the facts of `deltaRel` and `newRel`. For/// multi-indexed relations, the only guarantee is that all indexes on `deltaRel` will/// contain all facts of `newRel`.////// `Purge(rel)` removes all facts from all indexes on `rel`.////// `Seq(body)` is sequence of statements to be executed sequentially.////// `Par(body)` is a sequence of statments to be executed in parallel.////// `Until(condition, body)` checks if `condition` is false and repeadetly executes/// `body` until `condition` is `true`.////// `Comment(string)` is a comment for debugging.///pubenumRamStmt {case Insert(RelOp)case MergeInto(RelSym, RelSym)case Swap(RelSym, RelSym)case Purge(RelSym)case Seq(Vector[RamStmt])case Par(Vector[RamStmt])case Until(Vector[BoolExp], RamStmt)case Comment(String) }instanceToString[RamStmt] {pubdeftoString(stmt: RamStmt): String =letnl = String.lineSeparator();matchstmt {case RamStmt.Insert(op) => ToString.toString(op)case RamStmt.MergeInto(src, dst) => "Merge ${src} into ${dst}"case RamStmt.Swap(lhs, rhs) => "Swap ${lhs} and ${rhs}"case RamStmt.Purge(relSym) => "Purge ${relSym}"case RamStmt.Seq(xs) => Vector.join(";${nl}", xs)case RamStmt.Par(xs) => Vector.join("|${nl}", xs)case RamStmt.Until(test, body) =>lettst = test |> Vector.join(" && ");"until(${tst}) do${nl}${String.indent(4, "${body}")}end"case RamStmt.Comment(comment) => "/* ${comment} */" } }/////////////////////////////////////////////////////////////////////////////// RelOp ///////////////////////////////////////////////////////////////////////////////////// `Search(rv, relSym, relType, body)` iterates through the facts in relation/// `relSym`, saving them as RowVar `rv` and executes `body`.////// `Query(rv, relSym, bools, indexPos, relType, body)` iterates through the facts in relation/// `relSym`, saving them as `rv` and executes `body` if `bools` is/// true. The index saved at `indexPos` will be used.////// `Functional(rv, f, input, body, arity, relType)` evaluates `f(input)`, for each result,/// saves it as `rv` and executes `body`. `arity` is the arity of the output tuples.////// `Project(terms, relSym, ruleNum)` constructs a tuple from `terms` and inserts it as a fact/// for relation `relSym`. `relSym` should be a new relation. `ruleNum` is a unique number assigned/// to the rule the project statement is associated with.////// `If(bools, body)` evaluates `body` if `bools` is true.///pubenumRelOp {case Search(RowVar, RelSym, RelType, RelOp)case Query(RowVar, RelSym, Vector[BoolExp], Int32, RelType, RelOp)case Functional(RowVar, Vector[Boxed] -> Vector[Vector[Boxed]], Vector[RamTerm], RelOp, Int32, RelType)case Project(Vector[RamTerm], RelSym, Int32)case If(Vector[BoolExp], RelOp) }instanceToString[RelOp] {pubdeftoString(op: RelOp): String =letnl = String.lineSeparator();matchop {case RelOp.Search(var, relSym, _, body) =>"search ${var} ∈ ${relSym} do${nl}${String.indent(4, "${body}")}end"case RelOp.Query(var, relSym, prefixQuery, _, _, body) =>letqry = Vector.joinWith(matchterm -> {ToString.toString(term) }," ∧ ",prefixQuery );"query {${var} ∈ ${relSym} | ${qry}} do${nl}${String.indent(4, "${body}")}end"case RelOp.Functional(RowVar.Named(id), _, terms, body, _, _) =>"loop(x${id} <- f(${terms |> Vector.join(", ")})) do${nl}${String.indent(4, "${body}")}end"case RelOp.Project(terms, relSym, _) =>"project (${terms |> Vector.join(", ")}) into ${relSym}"case RelOp.If(test, then) =>lettst = test |> Vector.join(" ∧ ");"if (${tst}) then${nl}${String.indent(4, "${then}")}end" } }/////////////////////////////////////////////////////////////////////////////// BoolExp ///////////////////////////////////////////////////////////////////////////////////// `Not(b)`: True if `b` is false.////// `IsEmpty(relSym)`: True if there are no facts in relation `relSym`.////// `NotMemberOf(terms, relSym, repNegative)`: True if the tuple constructed from `terms` is not/// in relation `relSym`. `repNegative` is true if `NotMemberOf` is introduced by a `not A(...)`.////// `NotBot(term, leq, bot)`: True if `term` is not a `bot`./// `term` must be a Meet or RowLoad of a lattice var.////// `Leq(const, rv, relSym)`: True if `const` is less than the lattice element of `rv`.////// `Eq(term1, term2)`: True if `term1 == term2`.////// `Guard1(f, term1)`: True if `f(term1) == true`./// `Guard2(f, term1, term2)`: True if `f(term1, term2) == true`./// `Guard3(f, term1, term2, term3)`: True if `f(term1, term2, term3) == true`./// `Guard4(f, term1, term2, term3, term4)`: True if `f(term1, term2, term3, term4) == true`./// `Guard5(f, term1, term2, term3, term4, term5)`: True if `f(term1, term2, term3, term4, term5) == true`.///pubenumBoolExp {case Not(BoolExp)case IsEmpty(RelSym)case NotMemberOf(Vector[RamTerm], RelSym, Bool)case NotBot(RamTerm, Boxed -> Boxed -> Bool, Boxed)case Leq(Boxed, RowVar, RelSym)case Eq(RamTerm, RamTerm)case Guard1(Boxed -> Bool, RamTerm)case Guard2(Boxed -> Boxed -> Bool, RamTerm, RamTerm)case Guard3(Boxed -> Boxed -> Boxed -> Bool, RamTerm, RamTerm, RamTerm)case Guard4(Boxed -> Boxed -> Boxed -> Boxed -> Bool, RamTerm, RamTerm, RamTerm, RamTerm)case Guard5(Boxed -> Boxed -> Boxed -> Boxed -> Boxed -> Bool, RamTerm, RamTerm, RamTerm, RamTerm, RamTerm) }instanceToString[BoolExp] {pubdeftoString(exp: BoolExp): String =matchexp {case BoolExp.Not(boolExp) => "not (${boolExp})"case BoolExp.IsEmpty(relSym) => "${relSym} = ∅"case BoolExp.NotMemberOf(terms, relSym, _) => "(${terms |> Vector.join(", ")}) ∉ ${relSym}"case BoolExp.NotBot(term, _, _) => "(${term}) ≠ ⊥"case BoolExp.Leq(elem, _, term) => "${elem} ≤ (${term})"case BoolExp.Eq(lhs, rhs) => "${lhs} = ${rhs}"case BoolExp.Guard1(_, v) => "<clo>(${v})"case BoolExp.Guard2(_, v1, v2) => "<clo>(${v1}, ${v2})"case BoolExp.Guard3(_, v1, v2, v3) => "<clo>(${v1}, ${v2}, ${v3})"case BoolExp.Guard4(_, v1, v2, v3, v4) => "<clo>(${v1}, ${v2}, ${v3}, ${v4})"case BoolExp.Guard5(_, v1, v2, v3, v4, v5) => "<clo>(${v1}, ${v2}, ${v3}, ${v4}, ${v5})" } }/////////////////////////////////////////////////////////////////////////////// RamTerm ///////////////////////////////////////////////////////////////////////////////////// Represents a Relational Algebra Machine (RAM) term.////// `Lit(val, id)` represents the literal `val`.////// `ProvMax([(rv1, i1), (rv2, i2), (rv3, i3), ...])` where represents computing/// `max(rv1[i1], rv2[i2], rv3[i3], ...)`.////// `RowLoad(x, i, type, relSym)` represents an index into the tuple/// bound to the local variable `x`, i.e. `x[i]`. `x` is a tuple/// from `relSym`.////// `Meet(meet, term, (rv, relSym), id)` represents the meet of `term` with the lattice/// element of `rv`.////// `App1(f, term1)` represents `f(term1)`.////// `App2(f, term1, term2)` represents `f(term1, term2)`.////// `App3(f, term1, term2, term3)` represents `f(term1, term2, term3)`.////// `App4(f, term1, term2, term3, term4)` represents `f(term1, term2, term3, term4)`.////// `App5(f, term1, term2, term3, term4, term5)` represents `f(term1, term2, term3, term4, term4)`.///pubenumRamTerm {case Lit(Boxed, Type)case ProvMax(Vector[(RowVar, Int32)])case RowLoad(RowVar, Int32, Type, RelSym)case Meet(Boxed -> Boxed -> Boxed, RamTerm, (RowVar, RelSym), Type)case App1(Boxed -> Boxed, RamTerm, Type)case App2(Boxed -> Boxed -> Boxed, RamTerm, RamTerm, Type)case App3(Boxed -> Boxed -> Boxed -> Boxed, RamTerm, RamTerm, RamTerm, Type)case App4(Boxed -> Boxed -> Boxed -> Boxed -> Boxed, RamTerm, RamTerm, RamTerm, RamTerm, Type)case App5(Boxed -> Boxed -> Boxed -> Boxed -> Boxed -> Boxed, RamTerm, RamTerm, RamTerm, RamTerm, RamTerm, Type) }instanceToString[RamTerm] {pubdeftoString(term: RamTerm): String = matchterm {case RamTerm.Lit(v, _) => Debug.stringify(v)case RamTerm.ProvMax(loads) =>letloadsStr = Vector.joinWith(match (rv, i) -> "${rv}[${i}]", ", ", loads);"max(${loadsStr})"case RamTerm.RowLoad(var, index, _, _) => "${var}[${index}]"case RamTerm.Meet(_, lhs, rhs, _) => "(${lhs} ⊓ ${rhs})"case RamTerm.App1(_, v, _) => "<clo>(${v})"case RamTerm.App2(_, v1, v2, _) => "<clo>(${v1}, ${v2})"case RamTerm.App3(_, v1, v2, v3, _) => "<clo>(${v1}, ${v2}, ${v3})"case RamTerm.App4(_, v1, v2, v3, v4, _) => "<clo>(${v1}, ${v2}, ${v3}, ${v4})"case RamTerm.App5(_, v1, v2, v3, v4, v5, _) => "<clo>(${v1}, ${v2}, ${v3}, ${v4}, ${v5})" } }/////////////////////////////////////////////////////////////////////////////// RelSym ///////////////////////////////////////////////////////////////////////////////////// `RelSym(predSym, arity, den)` represents the relation of `predSym`/// which has arity `arity` and denotation `den`.///pubenumRelSym {case Symbol(PredSym, Int32, Denotation) }instanceEq[RelSym] {pubdefeq(a: RelSym, b: RelSym): Bool = match (a, b) {case (RelSym.Symbol(s1, _, _), RelSym.Symbol(s2, _, _)) => s1==s2 } }instanceOrder[RelSym] {pubdefcompare(a: RelSym, b: RelSym): Comparison = match (a, b) {case (RelSym.Symbol(s1, _, _), RelSym.Symbol(s2, _, _)) => s1<=>s2 } }instancePredSymsOf[RelSym] {pubdefpredSymsOf(x: RelSym): Set[PredSym] = matchx {case RelSym.Symbol(predSym, _, _) => Set.singleton(predSym) } }instanceSubstitutePredSym[RelSym] {pubdefsubstitute(x: RelSym, s: Map[PredSym, PredSym]): RelSym = matchx {case RelSym.Symbol(predSym, arity, den) =>letnewSym = Map.getWithDefault(predSym, predSym, s); RelSym.Symbol(newSym, arity, den) } }instanceToString[RelSym] {pubdeftoString(relSym: RelSym): String = matchrelSym {case RelSym.Symbol(sym, _, _) => "${sym}" } }////// Returns a `RelSym` wrapper of the `predSym`.////// The returned `RelSym` is equivalent to a 'real' `RelSym` with respect/// to the `Order` on `RelSym`.////// Used for accessing values in maps.///pubdefmakeFakeRelSym(predSym: PredSym): RelSym = RelSym.Symbol(predSym, -1, Relational)////// Returns the arity of `relSym`.///pubdefarityOf(relSym: RelSym): Int32 = matchrelSym {case RelSym.Symbol(_, arity, _) => arity }////// Returns the arity of the non-lattice part of `relSym`.///pubdefarityOfNonLat(relSym: RelSym): Int32 = matchrelSym {case RelSym.Symbol(_, arity, den) => matchden {case Relational => aritycase_ => arity-1 } }////// Returns the denotation of `relSym`.///pubdeftoDenotation(relSym: RelSym): Denotation = matchrelSym {case RelSym.Symbol(_, _, den) => den }////// Returns the identifier of `relSym`.///pubdeftoId(relSym: RelSym): Int64 = matchrelSym {case RelSym.Symbol(PredSym.PredSym(_, id), _, _) => id }/////////////////////////////////////////////////////////////////////////////// RowVar ///////////////////////////////////////////////////////////////////////////////////// `Named(id)` represents the unique identifier, `id`, of some row variable///////// For a program:/// search e1 ∈ Edge do/// search e2 ∈ Edge do/// .../// `e1` and `e2` would be the identifiers. Identifiers will only be defined/// once in a program, but can be reffered to from elsewhere.pubenumRowVarwithEq {case Named(Int32) }instanceToString[RowVar] {pubdeftoString(var: RowVar): String = matchvar {case RowVar.Named(id) => "x${id}" } }instanceOrder[RowVar] {pubdefcompare(a: RowVar, b: RowVar): Comparison = match (a, b) {case (RowVar.Named(i1), RowVar.Named(i2)) => i1<=>i2 } }}