{-# 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]

wrapUnsafeDataAsConstr:: BuiltinData -> BuiltinPair BuiltinInteger (BuiltinList BuiltinData)
wrapUnsafeDataAsConstr :: BuiltinData -> BuiltinPair BuiltinInteger (BuiltinList BuiltinData)
wrapUnsafeDataAsConstr = BuiltinData -> BuiltinPair BuiltinInteger (BuiltinList BuiltinData)
BI.unsafeDataAsConstr
{-# NOINLINE wrapUnsafeDataAsConstr #-}

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
{-# NOINLINE 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)`.
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 #-}

{- 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 NOINLINE 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.

-}