Arboricx.hs (5575B)
1 {-# LANGUAGE BangPatterns #-} 2 3 module ContentStore.Arboricx 4 ( merkleNodeDomain 5 , putNode 6 , getNode 7 , treeTermDomain 8 , encodeTreeTerm 9 , decodeTreeTerm 10 , decodeTreeTermShared 11 , putTreeTerm 12 , getTreeTerm 13 , putTree 14 , getTree 15 ) where 16 17 import ContentStore.Filesystem 18 import ContentStore.Object 19 import Runtime 20 21 import qualified Data.ByteString as BS 22 import qualified Data.ByteString.Lazy as BL 23 import Data.ByteString.Builder (word8, toLazyByteString) 24 import qualified NodeTable as Nodes 25 26 merkleNodeDomain :: Domain 27 merkleNodeDomain = Domain "arboricx.merkle.node.v1" 28 29 treeTermDomain :: Domain 30 treeTermDomain = Domain "arboricx.tree-term.v1" 31 32 putNode :: StorePath -> Node -> IO ObjectHash 33 putNode store node = putObject store merkleNodeDomain (serializeNode node) 34 35 getNode :: StorePath -> ObjectHash -> IO (Maybe Node) 36 getNode store h = fmap deserializeNode <$> getVerifiedObject store merkleNodeDomain h 37 38 -- | Store a complete normal tree as one content object. Merkle nodes remain 39 -- available for DAG use cases, but module executable exports use this object 40 -- kind to avoid filesystem writes for every subtree of large normal forms. 41 encodeTreeTerm :: T -> BS.ByteString 42 encodeTreeTerm tree = BL.toStrict (toLazyByteString (go [tree])) 43 where 44 -- Canonical preorder over logical occurrences, without repeatedly copying 45 -- encoded subtrees. The explicit work stack does not intern or apply trees. 46 go [] = mempty 47 go (Leaf : rest) = word8 0 <> go rest 48 go (Stem child : rest) = word8 1 <> go (child : rest) 49 go (Fork left right : rest) = word8 2 <> go (left : right : rest) 50 51 decodeTreeTerm :: BS.ByteString -> Either String T 52 decodeTreeTerm payload = do 53 (term, rest) <- getTerm payload 54 if BS.null rest 55 then Right term 56 else Left "trailing bytes after tree term" 57 where 58 getTerm bs = case BS.uncons bs of 59 Nothing -> Left "unexpected end of tree term" 60 Just (0x00, rest) -> Right (Leaf, rest) 61 Just (0x01, rest) -> do 62 (child, afterChild) <- getTerm rest 63 Right (Stem child, afterChild) 64 Just (0x02, rest) -> do 65 (left, afterLeft) <- getTerm rest 66 (right, afterRight) <- getTerm afterLeft 67 Right (Fork left right, afterRight) 68 Just (tag, _) -> Left $ "unknown tree term tag: " ++ show tag 69 70 -- ReadTreeTerm transports executable libraries. Raw canonical bytes repeat 71 -- logical subtrees; allocating each occurrence loses the sharing that keeps 72 -- large stable compiler libraries practical. Intern only within this decode, 73 -- using exact constructor/child IDs (never tree equality, hashes or evaluation). 74 -- Other raw data readers keep the ordinary decoder: a mostly unique data tree 75 -- can cost more with an intern table. Space is O(unique nodes + input + depth), 76 -- time O(logical bytes * min(word bits, unique nodes)); the table is not globally retained. 77 data SharedDecodeFrame 78 = SharedStem 79 | SharedForkLeft 80 | SharedForkRight !(Int, T) 81 82 decodeTreeTermShared :: BS.ByteString -> Either String T 83 decodeTreeTermShared payload = readNode 0 [] Nodes.empty 1 84 where 85 !lengthBytes = BS.length payload 86 readNode !offset frames !table !next 87 | offset >= lengthBytes = Left "unexpected end of tree term" 88 | otherwise = case BS.index payload offset of 89 0 -> deliver (0, Leaf) (offset + 1) frames table next 90 1 -> readNode (offset + 1) (SharedStem : frames) table next 91 2 -> readNode (offset + 1) (SharedForkLeft : frames) table next 92 tag -> Left $ "unknown tree term tag: " ++ show tag 93 deliver (_, tree) !offset [] _ _ 94 | offset == lengthBytes = Right tree 95 | otherwise = Left "trailing bytes after tree term" 96 deliver (childId, child) !offset (SharedStem : frames) table next = 97 intern (Nodes.StemKey childId) (Stem child) offset frames table next 98 deliver left !offset (SharedForkLeft : frames) table next = 99 readNode offset (SharedForkRight left : frames) table next 100 deliver (rightId, right) !offset (SharedForkRight (leftId, left) : frames) table next = 101 intern (Nodes.ForkKey leftId rightId) (Fork left right) offset frames table next 102 intern key tree offset frames table next = case Nodes.lookup key table of 103 Just node -> deliver node offset frames table next 104 Nothing -> 105 let !node = (next, tree) 106 !updated = Nodes.insert key node table 107 in deliver node offset frames updated (next + 1) 108 109 putTreeTerm :: StorePath -> T -> IO ObjectHash 110 putTreeTerm store = putObject store treeTermDomain . encodeTreeTerm 111 112 getTreeTerm :: StorePath -> ObjectHash -> IO (Maybe T) 113 getTreeTerm store h = do 114 mPayload <- getVerifiedObject store treeTermDomain h 115 case mPayload of 116 Nothing -> pure Nothing 117 Just payload -> case decodeTreeTerm payload of 118 Left err -> fail $ "invalid tree term " ++ show h ++ ": " ++ err 119 Right term -> pure (Just term) 120 121 putTree :: StorePath -> T -> IO ObjectHash 122 putTree store = go 123 where 124 go Leaf = putNode store NLeaf 125 go (Stem t) = do 126 child <- go t 127 putNode store (NStem child) 128 go (Fork l r) = do 129 left <- go l 130 right <- go r 131 putNode store (NFork left right) 132 133 getTree :: StorePath -> ObjectHash -> IO (Maybe T) 134 getTree store root = do 135 mNode <- getNode store root 136 case mNode of 137 Nothing -> return Nothing 138 Just node -> case node of 139 NLeaf -> return (Just Leaf) 140 NStem child -> fmap Stem <$> getTree store child 141 NFork left right -> do 142 ml <- getTree store left 143 mr <- getTree store right 144 return $ Fork <$> ml <*> mr