{-# LANGUAGE Strict #-}
{-# OPTIONS_GHC -fexpose-all-unfoldings #-}

{-| Functions in this module are for internal compiler use only, and should not
be used elsewhere. -}
module PlutusTx.AsData.Internal where

import PlutusTx.Builtins.Internal as BI

-- See Note [Compiling AsData Matchers and Their Invocations]

-- This may no longer be needed post the van Rossem HF. It needs to be examined and verified.
wrapUnsafeDataAsConstr :: BuiltinData -> BuiltinPair BuiltinInteger (BuiltinList BuiltinData)
wrapUnsafeDataAsConstr :: BuiltinData -> BuiltinPair BuiltinInteger (BuiltinList BuiltinData)
wrapUnsafeDataAsConstr = BuiltinData -> BuiltinPair BuiltinInteger (BuiltinList BuiltinData)
BI.unsafeDataAsConstr
{-# OPAQUE wrapUnsafeDataAsConstr #-}

-- This will no longer be needed post the van Rossem HF, since we will be using
-- `case` to unpack builtin lists.
wrapTail :: BuiltinList a -> BuiltinList a
wrapTail :: forall a. BuiltinList a -> BuiltinList a
wrapTail = BuiltinList a -> BuiltinList a
forall a. BuiltinList a -> BuiltinList a
BI.tail
{-# OPAQUE wrapTail #-}

-- Like `unsafeUncons` but uses `wrapTail` instead of the regular `tail`.
--
-- This function can be inlined, because the Plinth compiler does not rely on it being present.
--
-- We don't need a `wrapHead`, because there are no strict dead bindings of the form
-- `let !y = head xs`. The dead bindings are only in the form of `let !ys = tail xs`
-- and `let !y = unsafeFromBuiltinData (head xs)`.
--
-- This will no longer be needed post the van Rossem HF, since we will be using
-- `case` to unpack builtin lists.
wrapUnsafeUncons :: BuiltinList a -> (a, BuiltinList a)
wrapUnsafeUncons :: forall a. BuiltinList a -> (a, BuiltinList a)
wrapUnsafeUncons BuiltinList a
l = (BuiltinList a -> a
forall a. BuiltinList a -> a
BI.head BuiltinList a
l, BuiltinList a -> BuiltinList a
forall a. BuiltinList a -> BuiltinList a
wrapTail BuiltinList a
l)
{-# INLINE wrapUnsafeUncons #-}

-- See Note [Dropping redundant unsafeCaseList calls produced by AsData]
droppableUnsafeCaseList :: forall a r. (a -> BuiltinList a -> r) -> BuiltinList a -> r
droppableUnsafeCaseList :: forall a r. (a -> BuiltinList a -> r) -> BuiltinList a -> r
droppableUnsafeCaseList = (a -> BuiltinList a -> r) -> BuiltinList a -> r
forall a r. (a -> BuiltinList a -> r) -> BuiltinList a -> r
BI.unsafeCaseList
{-# OPAQUE droppableUnsafeCaseList #-}

{- Note [Compiling AsData Matchers and Their Invocations]

The AsData matchers, such as `AsData.Budget.Types.$mInts`, eagerly unpack all fields,
even for unused fields. This leads to strict dead bindings. Since they are strict and
the inliner doesn't know that they are effect-free, it can't remove them.

It is not uncommon that only one or a small number of fields in `ScriptContext` are used,
so this could cause severe performance penalties.

The matches are generated by GHC for pattern synonyms, not written by hand. As a result,
they are unfortunately not customizable - for example, by attaching certain annotations
to certain variables as signals for the Plinth compiler.

The workaround are the wrapper functions in this module. `wrapUnsafeDataAsConstr`
is marked OPAQUE so that GHC doesn't inline it, yet its unfolding is made available
via `-fexpose-all-unfoldings`. `wrapUnsafeDataAsConstr` is supposed to be used by
AsData only, and not elsewhere, and the Plinth compiler relies on it to identify
which functions are AsData matchers.

An AsData matcher unpacks the fields and passes them to a continuation. What we need is
to mark the variables bound in the continuation as safe to inline. For instance, if the
continuation is `\x y z w -> f x` (indicating a data type with 4 fields, of which only the
first is used), marking `x`, `y`, `z` and `w` as safe to inline allows the inliner to
drop the unused ones.

This eliminates unused field accessors, but there are still the `tailList` dead bindings, which
are intermediate artifacts in generating the field accessors. This is what `wrapTail` is for.
`wrapTail` appears in `case` scrutinees, as in `case wrapTail @BuiltinData l of l' -> ...`.
So the compiler detects such `case` expressions, and mark the binder, `l'`, as
safe to inline.

`wrapUnsafeUncons` and `wrapTail` will no longer be needed post the van Rossem HF, since
we will be using `case` to unpack builtin lists. We may no longer need `wrapUnsafeDataAsConstr`
either, but this needs to be examined and verified.

-}

{- Note [Dropping redundant unsafeCaseList calls produced by AsData]

AsData generates `unsafeCaseList` calls to unpack lists in the Data. It unpacks a list
completely, obtaining all fields, then calls the continuation with the fields. This
can lead to unnecessary `unsafeCaseList` calls, if the continuation does not access
all fields.

In general, we cannot drop the unnecessary `unsafeCaseList` calls, because doing so may
change the semantics: `unsafeCaseList` throws an error if the list is empty, and dropping
it would eliminate the error.

However, we know that the `unsafeCaseList` calls generated from AsData are never applied
to empty lists, so redundant ones can be dropped safely. To do so, we annotate the
`case` expressions with `SafeToDrop`, and use a PIR simplifier pass, DeadCase, to drop the
redundant ones.

-}