-
Notifications
You must be signed in to change notification settings - Fork 479
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
[prototype] Support base
's classes and methods in Plutus Tx
#5219
base: master
Are you sure you want to change the base?
Conversation
Great work, is this GHC patch going to be mainlined? If so is there a MR to track it? |
@@ -11,7 +11,7 @@ steps: | |||
remove_redundant: false | |||
|
|||
- trailing_whitespace: {} | |||
columns: 100 | |||
columns: 99 |
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.
?
@@ -32,6 +32,7 @@ packages: doc/read-the-docs-site | |||
prettyprinter-configurable | |||
word-array | |||
stubs/plutus-ghc-stub | |||
with-compiler: /home/zliu41/ghc/_build/stage1/bin/ghc |
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.
Maybe we can integrate the GHC patch with Nix inside a local .nix file?
instance Haskell.Eq ScriptContext where | ||
{-# INLINABLE (==) #-} | ||
(==) = (==) |
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.
suggestion: use some module qualification to make it look less like a loop
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.
Also, what is the reasoning behind this explicit instance instead of the derived one? I am not saying it is wrong, but since we are transitioning to Haskell's builtin Eq,Ord,etc then why not use its builtin deriving as well?
@@ -161,6 +161,7 @@ mkSimplPass flags logger = | |||
, GHC.sm_uf_opts = GHC.defaultUnfoldingOpts | |||
, GHC.sm_dflags = flags | |||
, GHC.sm_rules = False | |||
, GHC.sm_builtin_rules = False |
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.
Nice! Add a comment here or a note on why we have this set to False (default for ghc being True)
@@ -174,13 +177,69 @@ stableModuleCmp m1 m2 = | |||
-- See Note [Stable name comparisons] | |||
(GHC.moduleUnit m1 `GHC.stableUnitCmp` GHC.moduleUnit m2) | |||
|
|||
class Monad m => MonadCoreM m where |
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.
You forgot thelookupThing
method.
Wouldn't it be better to just add some orphan instances of the MonadThings
class instead? Then only the thNameToGhcName
method would be missing, but then you could create a subclass:
class MonadThings m => MonadCoreM m where
thNameToGhcName = ...
https://hackage.haskell.org/package/ghc-9.2.7/docs/GHC-Types-TyThing.html#t:MonadThings
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 implementation seems very fiddly at the moment, I'd be keen to try and reduce that. I also think we probably shouldn't actually merge this unless we can optimize away the worse codegen for basic equality and comparison operations. But it's relatively clear how to do that at least in the monomorphic case, which is most important.
@@ -706,8 +715,6 @@ compileExpr e = withContextM 2 (sdToTxt $ "Compiling expr:" GHC.<+> GHC.ppr e) $ | |||
GHC.Var (isErrorId -> True) `GHC.App` GHC.Type t `GHC.App` _ -> | |||
PIR.TyInst annMayInline <$> errorFunc <*> compileTypeNorm t | |||
|
|||
-- See Note [Uses of Eq] | |||
GHC.Var n | GHC.getName n == GHC.eqName -> throwPlain $ UnsupportedError "Use of == from the Haskell Eq typeclass" | |||
GHC.Var n | GHC.getName n == GHC.integerEqName -> throwPlain $ UnsupportedError "Use of Haskell Integer equality, possibly via the Haskell Eq typeclass" |
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 guess this is still bad? i.e. if you get the non-typeclass integer equality function, we still want to die?
Maybe (m (PIRTerm uni fun)) | ||
isKnownApp fun args = Map.lookup (splitGhcName (GHC.varName fun)) knownApps >>= ($ args) | ||
|
||
-- | A list of applications that should be compiled in specific ways. |
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.
This is big and complicated, let's put it in its own module.
-- | A list of applications that should be compiled in specific ways. | ||
knownApps :: | ||
CompilingDefault uni fun m ann => | ||
Map (Maybe String, String) ([GHC.CoreExpr] -> Maybe (m (PIRTerm 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.
Why not just define this as a function?
mkKnownApp :: Maybe String -> String -> [GHC.CoreExpr] -> Maybe (m (PIRTerm uni fun))
I think that would make it more readable, and generally I think if you have a statically known map that's only used to do lookups it should probably just be a function?
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.
Haven't reviewed this in full, I think it'll be a lot more readable as a function 😇
Map (Maybe String, String) ([GHC.CoreExpr] -> Maybe (m (PIRTerm uni fun))) | ||
knownApps = | ||
Map.fromListWithKey (\n -> error ("knownApps: key defined more than once: " <> show n)) | ||
. fmap (first splitThName) |
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.
This is rather odd: you split the name here and also to look up into it... why not just compare the names for equality?
compileExpr . GHC.Var =<< lookupId =<< thNameToGhcNameOrFail 'Builtins.equalsInteger | ||
[GHC.Type ty, _numDict, GHC.Lit (GHC.LitNumber _ i), GHC.Lit (GHC.LitNumber _ j)] | ||
| ty `GHC.eqType` GHC.integerTy -> Just $ do | ||
-- If both arguments of `(==) @Integer` are literals, we perform constant folding. |
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 agree with this at all. Keep codegen stupid! Write optimization passes to do optimizations!
@@ -23,7 +30,103 @@ letrec | |||
!acc : Bool = go xs | |||
in | |||
Bool_match | |||
(ifThenElse {Bool} (lessThanEqualsInteger 1 x) False True) | |||
((let |
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.
wow this is terrible D:
in | ||
Ord_match | ||
{integer} | ||
v |
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.
A context-based inlining heuristic would help here: inline if the thing you are inlining is a constructor application and the variable is the scrutinee of a pattern match!
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.
Also need case-of-known-constructor.
@@ -270,6 +270,82 @@ letrec | |||
{all dead. dead}) | |||
{all dead. dead} | |||
in | |||
let |
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 unsure why this one changed so much?
Error: Reference to a name which is not a local, a builtin, or an external INLINABLE function: Variable PlutusTx.Builtins.Internal.$fEqBuiltinByteString |
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 guess we should do the trick for ==
too, but we also probably want to keep the old fallback error which was helpful.
Error: Reference to a name which is not a local, a builtin, or an external INLINABLE function: Variable $c/= |
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.
ditto
|
||
max, min :: Integer -> Integer -> Integer | ||
{-# INLINEABLE max #-} | ||
max x y = if x <= y then y else 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.
wow, I assumed that max
is "left-biased" like min
, but this is not the case; I learned something today!
Ofcourse you need a "wrong" Eq instance to observe this bias:
data D = D1 | D2
instance Eq D where
_ == _ = True -- foul instance
deriving instance Ord D
deriving instance Show D
-- >>> D1 == D2
-- True
-- >>> min D1 D2 (leftbiased)
-- D1
-- >>> max D1 D2 (rightbiased)
-- D2
knownApps :: | ||
CompilingDefault uni fun m ann => | ||
Map (Maybe String, String) ([GHC.CoreExpr] -> Maybe (m (PIRTerm uni fun))) | ||
knownApps = |
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 function could be code-deduplicated slightly
I am not sure if this is still relevant, but here I paste an old comment of mine when I was debugging an older patched GHC (not this one):
|
a161078
to
db5cabb
Compare
This PR requires a patched GHC, so it won't be mergeable for a while. But it is reviewable and I'm looking to get some early feedback.
This PR adds support for Haskell's
Eq
,Ord
andNum
. Other classes can be supported in a similar way. The benefits of doing so include:base
, and having to make sure things are imported from the right places.enumFromTo
, but is easily fixable)Unsupported: (==) @Int
rather thanUnsupported: I#
.User migration will be easy because one can freely mix Haskell's and PlutusTx's typeclasses.
I've updated the modules in
plutus-benchmark
to use Haskell's typeclasses whenever possible. There are some cost increases, and as far as I can tell, they are all due to the fact that Haskell'sEq
has two methods. This can be fixed separately in multiple ways, e.g., with a better inliner, or with some tricks in the plugin itself.The main changes are in
PlutusTx.Compiler.Expr
andPlutusTx.Compiler.Dictionary.*
. Other changes are mostly boilerplate.The GHC patch is a fairly simple one: it adds a field in
SimplMode
and a GHC flag to turn off builtin rules, which stops GHC from turning(==) @Integer
into$fEqInteger_$c==
.