/* * 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. */////// The purpose of this file is to represent every `Boxed` value with an `Int64`.////// For all primitive values the `Int64` representation is simply the raw bits,/// possibly extended to form an `Int64`.////// For `BoxedObject` some `Int64` value is computed to represent it with the/// promise that equality is maintained with respect to `Order` on the `BoxedObject`.////// Consider the following program:/// ```/// A(x, y) :- B(x), C(y)./// ```/// We know that A[0] and B[0] should share the same value, and if we project the/// representing `Int64` from `B` to `A` then we should be able to compute the/// `Boxed` value. We therefore unify the two, effectively declaring that the types/// are the same. The same is done for `A[1]` and `C[0]`. On the other hand unifying/// `A[0]` and `A[1]` is not secure, as it could mix two unrelated types.////// The `initialize` method computes a unification and transforms all `Boxed` facts/// to `Int64` and returns a `BoxingType.Boxing`.////// See `BoxingType.flix` for data types and an example of how `BoxedObject`s are treated.////// The module operates on `Ram.Program`, so it uses, `Eq(x, y)`, `Meet(x, y)`, `Project(_)`/// and so on to compute the equality constraints.///pubmod Fixpoint3.Boxing {import java.lang.{Float, Double, Object, Character, Runtime}use Fixpoint3.Ast.Datalog.Dataloguse Fixpoint3.Ast.ExecutableRam.{Facts => EFacts}use Fixpoint3.Ast.Ramuse Fixpoint3.Ast.Ram.{arityOf, BoolExp, RamProgram, RamStmt, RelSym, RamTerm, RelOp, toDenotation}use Fixpoint3.Ast.Shared.{PredSym, isRelational, Denotation}use Fixpoint3.Boxableuse Fixpoint3.Boxeduse Fixpoint3.Boxed.{BoxedBool, BoxedChar, BoxedFloat32, BoxedFloat64, BoxedInt8, BoxedInt16, BoxedInt32, BoxedInt64, BoxedObject}use Fixpoint3.BoxingTypeuse Fixpoint3.BoxingType.{Boxing, setType, Types, TypeInfo, UnifiedTypePos}use Fixpoint3.Options.usedArityuse Fixpoint3.Predicate.{fullRelSymsOfProgram, relSymsOfProgram}use Fixpoint3.ReadWriteLockuse Fixpoint3.TypeInfouse Fixpoint3.Util.getOrCrash////// Returns a `(boxing, facts, idToBoxing)` where `Boxing` is information as described above,/// `facts` are the EDB of `program` as `Int64`, with respect to `boxing`.///pubdefinitialize(rc: Region[r], withProv: Bool, program: RamProgram): (Boxing[r], EFacts[r]) \ r =letboxingInfo = initializeInternal(rc, program, withProv);letfacts = unsafeIO { initializeFacts(rc, program, boxingInfo) }; (boxingInfo, facts)////// Returns `(facts, boxing)` where `facts` consists of the `Int64` representatives of/// the facts in `program`, with respect to `boxing`. Note that the old `boxingInfo` is/// invalid for the returned `facts`.///definitializeFacts(rc1: Region[r], program: RamProgram, boxingInfo: Boxing[r]): EFacts[r] \ r + IO =let RamProgram.Program(_, facts, _, (indexes, _), typeInfo) = program;let (f1, f2) = facts;letnewFacts = MutMap.empty(rc1);foreach ((relSym, relFacts) <- f1) {let RelSym.Symbol(PredSym.PredSym(_, id), _, _) = relSym;// Get some search. This means that we have to create one less index in the interpreter.letsearch = getOrCrash(Map.get(relSym, indexes)) |> Vector.get(0);letnewTree = BPlusTree.emptyWithArityAndSearch(rc1, usedArity(), search);// `id` will always be a full `id`.mapAllFacts(relFacts, boxingInfo, TypeInfo.getTypeOf(id, typeInfo), newTree);MutMap.put(relSym, newTree, newFacts) };foreach ((relSym, relFacts) <- f2) {let RelSym.Symbol(PredSym.PredSym(_, id), _, _) = relSym;// Get some search. This means that we have to create one less index in the interpreter.letsearch = getOrCrash(Map.get(relSym, indexes)) |> Vector.get(0);letnewTree = MutMap.getOrElsePut(relSym, BPlusTree.emptyWithArityAndSearch(rc1, usedArity(), search), newFacts);// `id` will always be a full `id`.mapAllFacts(relFacts, boxingInfo, TypeInfo.getTypeOf(id, typeInfo), newTree) };newFacts |> MutMap.toMapdefmapAllFacts(facts: BPlusTree[Vector[Boxed], Boxed, Static],boxingInfo: Boxing[r],mapping: Vector[Int32],newTree: BPlusTree[Vector[Int64], Boxed, r] ): Unit \ r + IO =unchecked_cast({facts |> BPlusTree.forEach(tuple -> lat -> {// `facts` and `newTree` must have same the same region. Cast to achieve this.unchecked_cast(({letsavedVec = tuple |> Vector.mapWithIndex(index -> boxedVal ->letunifiedPos = Vector.get(index, mapping);unboxWith(boxedVal, unifiedPos, boxingInfo) );BPlusTree.put(savedVec, lat, newTree) }: _ \ r) as _ \ IO) } )// This is to signal that we indeed modify the region `r`. } as _ \ IO + r)////// Construct a `Boxing` from `program`.///definitializeInternal(rc: Region[r], program: RamProgram, withProv: Bool): Boxing[r] \ r =let RamProgram.Program(_, _, _, _, typeInfo) = program;letmax = 1+Vector.map(v -> Option.getWithDefault(0, Vector.maximum(v)), typeInfo) |> Vector.maximum |> Option.getWithDefault(-1);letintToBox = Vector.init(_ -> MutList.empty(rc), max);letboxToInt = Vector.init(_ -> BPlusTree.empty(rc), max);letlocks = Vector.init(_ -> ReadWriteLock.mkLock(rc), max);letposToIndex = Array.repeat(rc, max, Types.TyUnknown);letinfo = (intToBox, boxToInt, posToIndex, locks);letrelSyms = fullRelSymsOfProgram(program);if (withProv) {setType(Types.TyInt64, TypeInfo.provType(), posToIndex) } else {() };// Add bot first to make sure it corresponds to 0.foreach (x <- relSyms) {matchx {case RelSym.Symbol(PredSym.PredSym(_, index), arity, Denotation.Latticenal(bot, _, _, _)) =>discardunboxWith(bot, TypeInfo.getType(index, arity-1, typeInfo), info)case_ => () } };info////// Returns the `Boxed` value associated with `value` for the type information/// at `index` in `info`.////// Thread safe.///defdeMarshalObject(value: Int64, index: UnifiedTypePos, info: Boxing[r]): Boxed \ r = {let (objectList, _, _, locks) = info;letlock = Vector.get(index, locks);ReadWriteLock.readLock(lock);letres = MutList.get(getOrCrash(Int64.tryToInt32(value)), Vector.get(index, objectList));ReadWriteLock.unlockRead(lock);res }////// Returns the `Int64` value associated with `box` for the type information/// at `index` in `info`.////// Thread safe.///defmarshallObject(box: Boxed, index: UnifiedTypePos, info: Boxing[r]): Int64 \ r = {let (objectLists, vecObjectMap, _, locks) = info;letobjectList = Vector.get(index, objectLists);letobjectMap = Vector.get(index, vecObjectMap);letlock = Vector.get(index, locks);BPlusTree.computeIfAbsent(() -> {ReadWriteLock.writeLock(lock);letid = getNextIndex(objectList);MutList.insert(box, id, objectList);ReadWriteLock.unlockWrite(lock);Int32.toInt64(id) },box,objectMap ) }////// Return the position that the next value inserted in `list` will get.///defgetNextIndex(list: MutList[Boxed, r]): Int32 \ r = MutList.size(list)////// Unbox `v` given the type information stored at `typeInfo[index]` in `info`./// Returns `v` unboxed.///pubdefunboxWith(v: Boxed, index: UnifiedTypePos, info: Boxing[r]): Int64 \ r =let (_, _, symInfo, _) = info;matchv {case BoxedBool(x) =>setType(Types.TyBool, index, symInfo);if (x) 1i64else0i64case BoxedChar(x) =>setType(Types.TyChar, index, symInfo);Int32.toInt64(Char.toBmpCodePoint(x))case BoxedInt8(x) =>setType(Types.TyInt8, index, symInfo);Int8.toInt64(x)case BoxedInt16(x) =>setType(Types.TyInt16, index, symInfo);Int16.toInt64(x)case BoxedInt32(x) =>setType(Types.TyInt32, index, symInfo);Int32.toInt64(x)case BoxedInt64(x) =>setType(Types.TyInt64, index, symInfo);xcase BoxedFloat32(x) =>setType(Types.TyFloat32, index, symInfo); Int32.toInt64(Float.floatToRawIntBits(Float32.valueOf(x).floatValue()))case BoxedFloat64(x) =>setType(Types.TyFloat64, index, symInfo); Double.doubleToRawLongBits(Float64.valueOf(x).doubleValue())case BoxedObject(_, _) =>setType(Types.TyObject, index, symInfo);// We remap to low, so this only goes wrong when dealing with more// than 2^32 different RelSymsmarshallObject(v, index, info)case Boxed.NoValue => bug!("NoValue should never be in a position to be unboxed unboxed") }////// Boxes `v` given its type information at `typeInfo[index]` in `info`./// Returns `v` boxed.///pubdefboxWith(v: Int64, index: UnifiedTypePos, info: Boxing[r]): Boxed \ r =let (_, _, typeInfo, _) = info;matchBoxingType.getType(index, typeInfo) {case Types.TyBool => Boxed.BoxedBool(not (v==0i64))case Types.TyChar => BoxedChar(unsafeIO { Array.get(0, Character.toChars(getOrCrash(Int64.tryToInt32(v)))) })case Types.TyInt8 => BoxedInt8(getOrCrash(Int64.tryToInt8(v)))case Types.TyInt16 => BoxedInt16(getOrCrash(Int64.tryToInt16(v)))case Types.TyInt32 => BoxedInt32(getOrCrash(Int64.tryToInt32(v)))case Types.TyInt64 => BoxedInt64(v)case Types.TyFloat32 => BoxedFloat32(Float.intBitsToFloat(getOrCrash(Int64.tryToInt32(v))))case Types.TyFloat64 => BoxedFloat64(Double.longBitsToDouble(v))case Types.TyObject => deMarshalObject(v, index, info)case Types.TyUnknown => bug!("Unormalizing value, which has never been normalized") }}