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

Make use of GHC call-stack simulation for the bounds-checked partial functions #184

Closed

Conversation

leftaroundabout
Copy link
Contributor

Until now, Vector's (!), head etc., like other partial functions in Haskell, simply crashed the program without giving useful information as to where the problem originated – unless compiling with -fprof, which gives stack traces for everything but is horrible for performance.

GHC-8 has added the HasCallStack constraint, which allows such functions to give meaningful error diagnosis without requiring global profiling. I enabled this on this library's checked-partial functions.

`(!)` etc. are of course only safer than their `unsafe` pendants in the sense that
errors are raised as proper exceptions, but when used in sizable applications
that by itself does not make the errors easy to find.

GHC-8 has added support for simulated call stack which can actually give some
information on the use site of such errors. In this commit, I added the
necessary constraint to the partial functions which perform bounds checks.
@cartazio
Copy link
Contributor

cartazio commented Sep 27, 2017 via email

import GHC.Stack (HasCallStack)
#define CHECK(f) (withFrozenCallStack Ck.f __FILE__ __LINE__)
#else
#define HasCallStack (Eq ())
Copy link
Contributor Author

@leftaroundabout leftaroundabout Sep 27, 2017

Choose a reason for hiding this comment

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

That Eq () fallback constraint was just a rough hack, to replace HasCallStack with something as simple as possible on GHC versions that don't support it yet. It turns out even that super-simple hack doesn't work just like that, it would require tossing in FlexibleContexts:

Data/Vector/Internal/Check.hs:122:15:
    Non type-variable argument in the constraint: Eq ()
    (Use FlexibleContexts to permit this)
    In the type signature for ‘checkIndex’:
      checkIndex :: (Eq ()) =>
                    String -> Int -> Checks -> String -> Int -> Int -> a -> a

Any ideas how this could be properly done, short of cluttering every signature with an #if MIN_VERSION_base?

Copy link
Member

Choose a reason for hiding this comment

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

I would do what the call-stack library does:

#if MIN_VERSION_base(4,9,0)
import           GHC.Stack (HasCallStack)
#elif MIN_VERSION_base(4,8,1)
type HasCallStack = (?callStack :: GHC.CallStack)
#else
import GHC.Exts (Constraint)
type HasCallStack = (() :: Constraint)
#endif

This requires base-4.5 (GHC 7.4) or later, but those are precisely the version bounds that vector has. Moreover, this shouldn't require the use of FlexibleContexts.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't suppose it would be ok for vector to actually depend on call-stack?

@leftaroundabout
Copy link
Contributor Author

Sure, I agree. My idea was to only add HasCallStack to functions which are anyway “slow”, e.g. to (!) but not to unsafeIndex, so that any possible performance cost won't take action in spots that are deemed crucial for performance. But you're right of course that it should be checked that there are no unforeseen performance ramifications, I just don't know how how to go about this... I suppose it would be best to benchmark a whole sizable application before-and-after these changes?

@bgamari
Copy link
Contributor

bgamari commented Sep 27, 2017

I suspect that as far as performance is concerned the callstacks generally shouldn't be a problem so long as the function is inlined. In this case I would expect that the callstack allocation will be floated in to the failure branch if one is necessary at all. I would look at the simplified core (i.e. -ddump-simpl) to verify that this is the case.

@UnkindPartition
Copy link

FWIW, I rebased @leftaroundabout's patches on top of the current master here: https://github.com/feuerbach/vector/tree/feature/ghc-callstack

Let me know if there's anything else I can do to help this get merged.

@cartazio
Copy link
Contributor

cartazio commented Apr 15, 2020 via email

@UnkindPartition
Copy link

Thanks @cartazio.

I've also added some missing HasCallStack annotations on my branch.

Annotations are still missing in the Fusion/* modules (Data/Vector/Fusion/Stream/Monadic.hs, Data/Vector/Fusion/Bundle.hs, Data/Vector/Fusion/Bundle/Monadic.hs); I'm not sure whether they should be there or not.

@Bodigrim
Copy link
Contributor

@feuerbach The idea looks good to me, but I do not like stacktracetools.h dangling around. I would very much prefer to copy-paste the incantation in every file, it's not that long. @lehins @Shimuuar what do you think?

@Shimuuar
Copy link
Contributor

On other hand repeating same thing is asking for problems when someone will forget to edit one of files or make a mistake. Still it doesn't solve duplication completely. There's CHECK defined in both vector.h and stacktrace.h

@lehins
Copy link
Contributor

lehins commented Aug 4, 2021

My opinion on this PR is that the direction of relying on HasCallStack is the right way to go, however adding more CPP is not the right approach to get to the goal.

I've just submitted a PR that reduces CPP usage in favor of HasCallStack and uses call-stack package for ghc-7.10 to get backwards compatibility: #397 For now I only addressed the functions that directly throw errors, next step will be propagating HasCallStack constraint to the rest of partial functions in vector. Any feedback is appreciated.

My sincere apologies for this PR to be dragged around for 4 years. I do think that this PR was an improvement, and should have gotten more attention.

@Shimuuar
Copy link
Contributor

Closing since #397 is merged

@Shimuuar Shimuuar closed this Aug 10, 2021
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.

8 participants