-
Notifications
You must be signed in to change notification settings - Fork 477
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PLT-7745: Basic rewrite rules for builtins #5584
Conversation
Can you take a first look? I am going to refactor it a bit and add comments. Also I am thinking to incorporate commuteFnWithConst pass as a rewrite rule, |
import Control.Lens.TH | ||
import Data.Kind | ||
import PlutusCore.Builtin | ||
import PlutusCore.Builtin qualified as PLC | ||
import PlutusPrelude (Default (..)) | ||
import PlutusIR qualified as PIR | ||
|
||
newtype RewriteRules uni fun = RewriteRules { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i am not fond of this newtype, but I had to do it to make makeLenses happy.
Also i could derive-newtype semigroup,monoid but then I had to write some boilerplate instances
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused by the makeLenses problem?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would have thought you could just derive semigroup and monoid via Dual Endo
whatever if that's what you need.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you try it out? RankNTypes get in the way for me both for makeLenses and for derive-newtype Semigroup,Monoid
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The lenses seem fine, deriving doesn't work because of the forall
s. I would either just write the instances by hand or have a list in there and evaluate it where you use it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
General approach looks fine
decodeEncode = \case | ||
BA DecodeUtf8 a1 a2 (BA EncodeUtf8 a3 a4 t) -> | ||
-- place the missed annotations inside the rewritten term | ||
(<> a1 <> a2 <> a3 <> a4) <$> t |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this works here but might not work in general. Probably what I would do is require the caller of userRewrite
to pass in an annotation to use for any newly generated terms that don't come from the source. Then we can pass in noProvenance
or whatever.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds complicated :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a function argument?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They could use mempty for that reason?
instance Ord a => Monoid (Provenance a) where
mempty = noProvenance
import Control.Lens.TH | ||
import Data.Kind | ||
import PlutusCore.Builtin | ||
import PlutusCore.Builtin qualified as PLC | ||
import PlutusPrelude (Default (..)) | ||
import PlutusIR qualified as PIR | ||
|
||
newtype RewriteRules uni fun = RewriteRules { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused by the makeLenses problem?
import Control.Lens.TH | ||
import Data.Kind | ||
import PlutusCore.Builtin | ||
import PlutusCore.Builtin qualified as PLC | ||
import PlutusPrelude (Default (..)) | ||
import PlutusIR qualified as PIR | ||
|
||
newtype RewriteRules uni fun = RewriteRules { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would have thought you could just derive semigroup and monoid via Dual Endo
whatever if that's what you need.
defaultUniRewriteRules :: RewriteRules DefaultUni DefaultFun | ||
defaultUniRewriteRules = RewriteRules $ combineRules | ||
-- rules are applied from left to right because of Dual | ||
[ decodeEncode |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed, I'm not 100% sure this is safe. Maybe we should do head (mkCons x xs) = let !_ = xs in x
or something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Y I am also not certain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Turns out that this rewrite is safe in Haskell because Text.fromString will handle gracefully non-unicode literal characters, see: https://hackage.haskell.org/package/text-2.1/docs/Data-Text.html#g:2
It would be safe for us if we:
- handle unicode text in plutus-tx (currently broken)
- handle non-unicode text gracefully in ghc-core like what is happening in Haskell . We have to replicate this behaviour in our plugin: https://hackage.haskell.org/package/text-2.1/docs/src/Data.Text.Internal.html#pack
fe39b51
to
9364099
Compare
9364099
to
3288132
Compare
a45e706
to
e023b19
Compare
3062a31
to
91155f9
Compare
. (MonadQuote m, Semigroup a) | ||
=> VarsInfo tyname name uni a | ||
-> PIR.Term tyname name uni fun a | ||
-> m (PIR.Term tyname name uni fun a) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zliu41 @effectfully You probably wonder why MonadQuote m and VarsInfo are here. It is needed by all further rewrite rules (which are not yet in this PR).
ac26414
to
83ac15d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me, comments are basically just nitpicking.
-- TODO: these could be Traversals | ||
-- | Get all the term variables in a term. | ||
vTerm :: Fold (Term tyname name uni fun ann) name | ||
vTerm = termSubtermsDeep . termVars |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you disagree with the TODO?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I tried it, but I was hit but cosmosOf
. CosmosOf requires Contravariant f, which Traversals cannot provide
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean, is it in principle possible to have a Traversal
focused on all the variables of a term? I think it is. If cosmosOf
is getting in the way, then it just means cosmosOf
isn't suitable for the job and we should use something else. Maybe we need to define this one in terms of deepOf
or something, I'm not intimately familiar with lens
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean, is it in principle possible to have a
Traversal
focused on all the variables of a term? I think it is.
Or do you disagree with that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do think it is possible, it's just we don't crucially need atm. Also it can be misused and lead to more variable capturing problems? Anyway, I will bring back the note in another PR but with a MAYBE tag.
plutus-core/plutus-ir/src/PlutusIR/Transform/RewriteRules/CommuteFnWithConst.hs
Show resolved
Hide resolved
{- | This rewrites: `(decodeUtf8 (encodeUtf8 x)) => x` | ||
|
||
This rewrite is safe because x is either valid by construction *In Haskell, but not in GHC-CORE* | ||
(see <https://hackage.haskell.org/package/text-2.1/docs/Data-Text.html#g:2>) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand the relevance of the link.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will address it in a new PR solely for this decode/encode rewriterule
decodeEncodeUtf8 = \case | ||
BA DecodeUtf8 a1 a2 (BA EncodeUtf8 a3 a4 t) -> | ||
-- place the missed annotations inside the rewritten term | ||
(<> a1 <> a2 <> a3 <> a4) <$> t |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just nitpicking, but I'd use sconcat :: NonEmpty a -> a
instead, because we have
data Provenance a =
<...>
| MultipleSources (S.Set (Provenance a))
and that would be a natural target for sconcat
.
... except no implementation for sconcat
is provided in
instance Ord a => Semigroup (Provenance a) where
x <> y = MultipleSources (toSet x `S.union` toSet y)
where
toSet = \case
MultipleSources ps -> ps
other -> S.singleton other
but I think it's just an oversight and so I'd provide an implementation for sconcat
and define <>
in terms of that instead.
Do feel free to ignore this comment.
t -> t | ||
|
||
pattern BA :: fun -> a -> a -> Term tyname name uni fun a -> Term tyname name uni fun a | ||
pattern BA b a1 a2 t <- Apply a1 (Builtin a2 b) t |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Too short of a global name IMO.
I'd just write Apply a1 (Builtin a2 b) t
directly, it's not that longer and it's much more readable, but do feel free to ignore that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test is disabled but the golden file exists?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will get rid of this rewrite rule completely for now
import PlutusCore.Default | ||
import PlutusIR | ||
|
||
{- | This rewrites: `(decodeUtf8 (encodeUtf8 x)) => x` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd add a property test that decodeUtf8 . encodeUtf8
is id
. Who knows what kind of horrible infernal UTF8 weirdness might be going on within those functions.
-- We collect `VarsInfo` on the whole program term and pass it on as arg to each RewriteRule. | ||
-- This has the limitation that any variables newly-introduced by the rules would | ||
-- not be accounted in `VarsInfo`. This is currently fine, because we only rely on VarsInfo | ||
-- for isPure; isPure is safe w.r.t "open" terms. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kinda nasty tbh, but I'm not sure what to do about it.
(goldenPir (runQuote . RewriteRules.userRewrite def) pTerm) | ||
[ "equalsInt" -- this tests that the function works on equalInteger | ||
, "divideInt" -- this tests that the function excludes not commutative functions | ||
, "multiplyInt" -- this tests that the function works on multiplyInteger |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd consider adding a property test that tests whether optimizing a term permutes all bulitin applications that are supposed to be permuted and leaves others intact, but this is quite a bit more work, so having several golden tests is probably enough indeed.
@@ -63,6 +65,8 @@ safeLift | |||
, PLC.Typecheckable uni fun | |||
, PrettyUni uni, Pretty fun | |||
, Default (PLC.CostingPart uni fun) | |||
, Default (PIR.BuiltinsInfo uni fun) | |||
, Default (PIR.RewriteRules uni fun) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Constraints in Plutus Tx are completely out of order.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By order you mean, i should stick to a certain e.g. alphabetical order accoss the file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I mean in the sense of "If something someone says or does is out of order, it is unpleasant or not suitable and it is likely to upset or offend people". They're way too bloated, but I don't know if we can have sensible type synonyms there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see the rule for constrData
and unConstrData
; are you going to add it separately?
Yes |
Make CommuteFnWithConst transformation a RewriteRule
83ac15d
to
82c38e0
Compare
This is something like GHC's RULES, but for PIR. The complication is that the rewrite pass is supposed to be parameterized over the universe, which requires some boilerplate similar to the BuiltinsInfo boilerplate.
More rewrite rules to come. I just want to get this into master so I can further add more rules.
Also turned the previous CommuteFnWithConst transformation to a RewriteRule
Pre-submit checklist: