1
1
mirror of https://github.com/anoma/juvix.git synced 2025-01-05 22:46:08 +03:00

Capture scry id in the main function and use it when compiling anomaGet (#2705)

The purpose of this PR is to wrap the compiled main function with Nockma
code that captures the argument tuple for use when compiling `anomaGet`
calls.


* The [Anoma system
expects](c7f2d69d1e/lib/anoma/node/executor/worker.ex (L20))
to receive a function of type `ScryId -> Transaction`
 
* The ScryId is only used to construct the argument to the Scry
operation (i.e the anomaGet builtin in the Juvix frontend),
 
* When the Juvix developer writes a function to submit to Anoma they use
type `() -> Transaction`, the main function wrapper is used to capture
the ScryId argument into the subject which is then used to construct
OpScry arguments when anomaGet is compiled.
 
* If the Anoma system expectation changes then the wrapper code must be
changed.

We could add a transformation that checks that the main function in the
Anoma target has no arguments. However it is convenient to be able to
write functions with arguments for testing and debugging (for example
compiling directly to a logic function).

---------

Co-authored-by: Jan Mas Rovira <janmasrovira@gmail.com>
This commit is contained in:
Paul Cadman 2024-04-02 16:40:07 +01:00 committed by GitHub
parent d8e5f67c7a
commit 59f9b49a77
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 112 additions and 19 deletions

View File

@ -372,4 +372,4 @@ evalProfile inistack initerm =
Cell' typeFormula subFormula _ <- withCrumb (crumb crumbDecodeFirst) (asCell (c ^. operatorCellTerm))
void (evalArg crumbEvalFirst stack typeFormula)
key <- evalArg crumbEvalSecond stack subFormula
fromMaybeM (throwKeyNotInStorage key) (HashMap.lookup key <$> asks (^. storageKeyValueData))
fromMaybeM (throwKeyNotInStorage key) (HashMap.lookup (StorageKey key) <$> asks (^. storageKeyValueData))

View File

@ -146,7 +146,7 @@ instance (PrettyCode a, NockNatural a) => PrettyCode (KeyNotInStorage a) where
hashMapKvs <- vsep <$> mapM combineKeyValue (HashMap.toList (_keyNotInStorageStorage ^. storageKeyValueData))
return ("The key" <+> tm <+> "is not found in the storage." <> line <> "Storage contains the following key value pairs:" <> line <> hashMapKvs)
where
combineKeyValue :: (Term a, Term a) -> Sem r (Doc Ann)
combineKeyValue :: (StorageKey a, Term a) -> Sem r (Doc Ann)
combineKeyValue (t1, t2) = do
pt1 <- ppCode t1
pt2 <- ppCode t2

View File

@ -1,12 +1,55 @@
module Juvix.Compiler.Nockma.Evaluator.Storage where
import Juvix.Compiler.Nockma.Language
import Juvix.Compiler.Nockma.Pretty.Base
import Juvix.Prelude.Base
newtype Storage a = Storage
{_storageKeyValueData :: HashMap (Term a) (Term a)}
{_storageKeyValueData :: HashMap (StorageKey a) (Term a)}
emptyStorage :: (Hashable a) => Storage a
emptyStorage = Storage {_storageKeyValueData = mempty}
newtype StorageKey a = StorageKey {_storageKeyTerm :: Term a}
makeLenses ''Storage
makeLenses ''StorageKey
stripMeta :: Term a -> Term a
stripMeta = \case
TermAtom a ->
TermAtom
Atom
{ _atom = a ^. atom,
_atomInfo = emptyAtomInfo
}
TermCell c ->
TermCell
Cell'
{ _cellLeft = stripMeta (c ^. cellLeft),
_cellRight = stripMeta (c ^. cellRight),
_cellInfo = emptyCellInfo
}
instance (NockmaEq a) => NockmaEq (StorageKey a) where
nockmaEq (StorageKey s1) (StorageKey s2) = nockmaEq s1 s2
instance (NockmaEq a) => Eq (StorageKey a) where
(==) = nockmaEq
instance (NockmaEq a, Hashable a) => Hashable (StorageKey a) where
hashWithSalt salt k = goTerm (k ^. storageKeyTerm)
where
goCell :: Cell a -> Int
goCell c = hashWithSalt salt (c ^. cellLeft, c ^. cellRight)
goAtom :: Atom a -> Int
goAtom a = hashWithSalt salt (a ^. atom)
goTerm :: Term a -> Int
goTerm = \case
TermAtom a -> goAtom a
TermCell c -> goCell c
instance (PrettyCode a, NockNatural a) => PrettyCode (StorageKey a) where
ppCode k = ppCode (stripMeta (k ^. storageKeyTerm))
emptyStorage :: (NockmaEq a, Hashable a) => Storage a
emptyStorage = Storage {_storageKeyValueData = mempty}

View File

@ -133,6 +133,7 @@ data AnomaCallablePathId
| ClosureTotalArgsNum
| ClosureArgsNum
| ClosureArgs
| AnomaGetOrder
deriving stock (Enum, Bounded, Eq, Show)
-- | A closure has the following structure:
@ -301,6 +302,24 @@ anomaCallableClosureWrapper =
adjustArgs = OpIf # closureArgsIsEmpty # (opAddress "wrapperSubject" emptyPath) # appendAndReplaceArgsTuple
in opCall "closureWrapper" (closurePath RawCode) adjustArgs
mainFunctionWrapper :: Term Natural
mainFunctionWrapper =
-- 1. The Anoma system expects to receive a function of type `ScryId -> Transaction`
--
-- 2. The ScryId is only used to construct the argument to the Scry operation (i.e the anomaGet builtin in the Juvix frontend),
--
-- 3. When the Juvix developer writes a function to submit to Anoma they use
-- type `() -> Transaction`, this wrapper is used to capture the ScryId
-- argument into the subject which is then used to construct OpScry arguments
-- when anomaGet is compiled.
--
-- 4. If the Anoma system expectation changes then this code must be changed.
let captureAnomaGetOrder :: Term Natural
captureAnomaGetOrder = replaceSubject $ \case
AnomaGetOrder -> Just (getClosureFieldInSubject ArgsTuple)
_ -> Nothing
in opCall "mainFunctionWrapper" (closurePath RawCode) captureAnomaGetOrder
compile :: forall r. (Members '[Reader FunctionCtx, Reader CompilerCtx] r) => Tree.Node -> Sem r (Term Natural)
compile = \case
Tree.Binop b -> goBinop b
@ -413,7 +432,9 @@ compile = \case
Tree.OpFieldToInt -> fieldErr
goAnomaGet :: Term Natural -> Sem r (Term Natural)
goAnomaGet arg = return (OpScry # (OpQuote # nockNilTagged "OpScry-typehint") # arg)
goAnomaGet key = do
let arg = remakeList [getFieldInSubject AnomaGetOrder, key]
return (OpScry # (OpQuote # nockNilTagged "OpScry-typehint") # arg)
goTrace :: Term Natural -> Sem r (Term Natural)
goTrace arg = do
@ -463,6 +484,7 @@ compile = \case
ClosureTotalArgsNum -> nockNatLiteral farity
ClosureArgsNum -> nockIntegralLiteral (length args)
ClosureArgs -> remakeList args
AnomaGetOrder -> OpQuote # nockNilTagged "goAllocClosure-AnomaGetOrder"
goExtendClosure :: Tree.NodeExtendClosure -> Sem r (Term Natural)
goExtendClosure = extendClosure
@ -487,6 +509,7 @@ compile = \case
ClosureArgs -> Nothing
ClosureTotalArgsNum -> Nothing
ClosureArgsNum -> Nothing
AnomaGetOrder -> Nothing
return $ (opCall "callClosure" (closurePath WrapperCode) newSubject)
isZero :: Term Natural -> Term Natural
@ -575,6 +598,7 @@ extendClosure Tree.NodeExtendClosure {..} = do
FunctionsLibrary -> getClosureField FunctionsLibrary closure
TempStack -> getClosureField TempStack closure
StandardLibrary -> getClosureField StandardLibrary closure
AnomaGetOrder -> getClosureField AnomaGetOrder closure
-- Calling convention for Anoma stdlib
--
@ -673,10 +697,13 @@ remakeList :: (Foldable l) => l (Term Natural) -> Term Natural
remakeList ts = foldTerms (toList ts `prependList` pure (OpQuote # nockNilTagged "remakeList"))
runCompilerWith :: CompilerOptions -> ConstructorInfos -> [CompilerFunction] -> CompilerFunction -> AnomaResult
runCompilerWith opts constrs libFuns mainFun = makeAnomaFun
runCompilerWith opts constrs moduleFuns mainFun = makeAnomaFun
where
libFuns :: [CompilerFunction]
libFuns = moduleFuns ++ (builtinFunction <$> allElements)
allFuns :: NonEmpty CompilerFunction
allFuns = mainFun :| libFuns ++ (builtinFunction <$> allElements)
allFuns = mainFun :| libFuns
compilerCtx :: CompilerCtx
compilerCtx =
@ -686,11 +713,16 @@ runCompilerWith opts constrs libFuns mainFun = makeAnomaFun
_compilerOptions = opts
}
mainClosure :: Term Natural
mainClosure = makeMainFunction (runCompilerFunction compilerCtx mainFun)
compiledFuns :: NonEmpty (Term Natural)
compiledFuns =
makeLibraryFunction
<$> ( runCompilerFunction compilerCtx <$> allFuns
)
mainClosure
:| ( makeLibraryFunction
<$> ( runCompilerFunction compilerCtx <$> libFuns
)
)
exportEnv :: Term Natural
exportEnv = makeList compiledFuns
@ -708,6 +740,22 @@ runCompilerWith opts constrs libFuns mainFun = makeAnomaFun
ClosureTotalArgsNum -> nockNilHere
ClosureArgsNum -> nockNilHere
ClosureArgs -> nockNilHere
AnomaGetOrder -> nockNilHere
makeMainFunction :: Term Natural -> Term Natural
makeMainFunction c = makeClosure $ \p ->
let nockNilHere = nockNilTagged ("makeMainFunction-" <> show p)
in case p of
WrapperCode -> mainFunctionWrapper
ArgsTuple -> argsTuplePlaceholder "mainFunction"
FunctionsLibrary -> functionsLibraryPlaceHolder
RawCode -> c
TempStack -> nockNilHere
StandardLibrary -> stdlib
ClosureTotalArgsNum -> nockNilHere
ClosureArgsNum -> nockNilHere
ClosureArgs -> nockNilHere
AnomaGetOrder -> nockNilHere
functionInfos :: HashMap FunctionId FunctionInfo
functionInfos = hashMap (run (runInputNaturals (toList <$> userFunctions)))
@ -725,11 +773,9 @@ runCompilerWith opts constrs libFuns mainFun = makeAnomaFun
makeAnomaFun :: AnomaResult
makeAnomaFun =
let mainClosure :: Term Natural
mainClosure = head compiledFuns
in AnomaResult
{ _anomaClosure = substEnv mainClosure
}
AnomaResult
{ _anomaClosure = substEnv mainClosure
}
where
-- Replaces all instances of functionsLibraryPlaceHolder by the actual
-- functions library. Note that the functions library will have

View File

@ -526,12 +526,16 @@ allTests =
v1 :: Term Natural = [nock| 222 |]
k2 :: Term Natural = [nock| [1 2 3 nil] |]
v2 :: Term Natural = [nock| [4 5 6 nil] |]
-- The keys of the storage are of the form [id key nil].
-- The id is captured from the arguments tuple of the function.
sk1 :: Term Natural = [nock| [[333 1 2 3 nil] 333 nil] |]
sk2 :: Term Natural = [nock| [[333 1 2 3 nil] [1 2 3 nil] nil] |]
in mkAnomaCallTest'
True
( Storage
( HashMap.fromList
[ (k1, v1),
(k2, v2)
[ (StorageKey sk1, v1),
(StorageKey sk2, v2)
]
)
)

View File

@ -123,7 +123,7 @@ anomaTest n mainFun args _testCheck _evalInterceptStdlibCalls =
in Test {..}
testWithStorage :: [(Term Natural, Term Natural)] -> Text -> Term Natural -> Term Natural -> Check () -> Test
testWithStorage s = Test defaultEvalOptions Nothing (Storage (HashMap.fromList s))
testWithStorage s = Test defaultEvalOptions Nothing (Storage (HashMap.fromList (first StorageKey <$> s)))
test :: Text -> Term Natural -> Term Natural -> Check () -> Test
test = testWithStorage []