Skip to content
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

Hashable-independent Aeson instances #1047

Closed
wants to merge 168 commits into from
Closed

Hashable-independent Aeson instances #1047

wants to merge 168 commits into from

Conversation

larskuhtz
Copy link
Contributor

@larskuhtz larskuhtz commented Sep 16, 2022

Overview

  • Bump ghc to 9.6.1
  • Update other dependencies to modern versions
  • But preserve legacy encoding/decoding behavior
  • Check for performance regressions

Backward & forward compatibility

The dependencies with the greatest consequences for preserving legacy behavior are JSON encoding and text processing.

JSON

Modern aeson ( >= 2.0) uses a different algorithm for sorting the fields of objects than our older pinned version of aeson. A large part of this PR is making pact use the pact-json library for all serialization and deserialization. pact-json focuses on explicit, predictable encoding over programmer convenience, and it provides helpers for encoding into the legacy format.

Source parsing

Modern versions of text, trifecta and attoparsec bring various API changes and changes to internal logic. In particular the handling of unicode character counting changed when text moved from utf-16 to utf-8 for its internal representation. This PR introduces an abstracted parser that shims the new behavior to produce character counts compatible with the older versions.

Performance regressions

Recent runs of the pact benchmarks are available for this branch (ghc-9.6.1) and master (ghc-8.10.7). Most benchmarks are slightly faster in the newer version. The largest benchmark, which compiles and runs a small currency-exchange contract, goes from 3.7 to 1.7 seconds uncached, 0.22 to 0.18 seconds cached. Performance is worse for the new branch when using an in-memory database. These results need to be replicated on other machines.

TODO: check the time required to run replay on this branch, compared to master.

ghc-9.6.1:
ghc961

ghc-8.10.7:
ghc8107

@larskuhtz larskuhtz changed the title [WIP] work on aeson instances Hashable-independent Aeson instances Oct 3, 2022
@larskuhtz
Copy link
Contributor Author

A somewhat open question is the amount of inlining for the aeson instances. This affects compilation times.

I think, the aeson package itself removed all inlining in recent version, to reduce compilation times. On the other hand inlining can make a big difference in runtime performance.

The current version of the PR does not inline some larger instances in particular in cyclic data type dependencies.

instance ToJSON a => ToJSON (SigData a) where
toJSON (SigData h s c) = object $ concat
[ ["hash" .= h]
, ["sigs" .= object (map (\(k,ms) -> (unPublicKeyHex k, maybe Null (toJSON . _usSig) ms)) s)]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This instance seems to be in conflict with the comment above:

, _sigDataSigs :: [(PublicKeyHex, Maybe UserSig)]
  -- ^ This is not a map because the order must be the same as the signers inside the command.

Should that be fixed? Would it break backwards compatibility?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 80 to 81
, "sigs" .= HM.fromList (bimap unPublicKeyHex (fmap _usSig) <$> _sigDataSigs o)
-- FIXME fix order independently of hashable package
Copy link
Contributor Author

@larskuhtz larskuhtz Oct 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use LegacyHashMap for backward compatible behavior. Use list for pairs for behavior that complies with the comment on the _sigDataSigs constructor field.

@jmcardon what would be the correct behavior here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this falls under ApiReq (maybe grep to be sure) which means it doesn't show up on blockchain, thus low-stakes what we do here.

@larskuhtz larskuhtz marked this pull request as ready for review October 3, 2022 17:36
Copy link
Contributor

@sirlensalot sirlensalot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! Main themes in review:

  1. promote LegacyValue to JsonValue (and move to a Types file), see discussion on type.

  2. Can we consider a Generic solution that somehow leverages a JsonProperties value? Would cut code down.

  3. We have enough "elide on Foldable.null " cases to merit support a la Maybe support, let's consider generalizing.

  4. Are we removing enableToJSON in this PR?

  5. Consider rename also for LegacyHashMap although here it really is legacy ... at a minimum LegacyHashed is a good name.

pact.cabal Show resolved Hide resolved
@@ -46,6 +46,7 @@ import Data.ByteString (ByteString)
import qualified Data.ByteString.Lazy.Char8 as BSL
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this is good work, we should consider not migrating this just to make the PR smaller -- ApiReq never results in code on the blockchain, and while it's perhaps nice for Chainweaver and tools, we can't guarantee a particular coding order in JS clients etc.

src/Pact/Utils/Json.hs Outdated Show resolved Hide resolved
src/Pact/Types/PactValue/Arbitrary.hs Outdated Show resolved Hide resolved
src/Pact/Types/Term/Arbitrary.hs Outdated Show resolved Hide resolved
src/Pact/Types/Term.hs Show resolved Hide resolved
src/Pact/Types/Term.hs Show resolved Hide resolved
src/Pact/Utils/Json.hs Outdated Show resolved Hide resolved
src/Pact/Utils/Json.hs Outdated Show resolved Hide resolved
src/Pact/Types/Term/Internal.hs Outdated Show resolved Hide resolved
src-ghc/Pact/GasModel/Utils.hs Outdated Show resolved Hide resolved
src-ghc/Pact/PersistPactDb/Regression.hs Outdated Show resolved Hide resolved
src/Pact/Types/ChainMeta.hs Show resolved Hide resolved
src/Pact/Types/Exp.hs Outdated Show resolved Hide resolved
src/Pact/Types/Pretty.hs Outdated Show resolved Hide resolved
src/Pact/Types/Term/Internal.hs Show resolved Hide resolved
-- -------------------------------------------------------------------------- --
--

newtype LegacyValue = LegacyValue { getLegacyValue :: Value }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this. Legacy makes it seem like an artifact of the past that will soon be deprecated.

@imalsogreg
Copy link
Contributor

This has been merged via #1242

@imalsogreg imalsogreg closed this Aug 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants