Skip to content

A bug in exprIsHNF

Introduction

The function exprIsHNF checks whether a given CoreExpr is in head normal form. It uses the exprIsHNFlike to do the bulk of the work, which in turn is defined in terms of is_hnf_like which is where the bug is.

The particular buggy code path is when it checks if an application is in head normal form.

Running Example

As a running example we can take the example listed in a comment in this function:

f (x /# y)
-- where f has arity two

As a comment explains, usually partially applied functions are in HNF, but in this case the unboxed argument still needs evaluation, so f (x /# y) is not in HNF and exprIsHNF should return False.

The Bug

The relevant case of is_hnf_like is:

    is_hnf_like (App e a)
      | isValArg a               = app_is_value e 1
      | otherwise                = is_hnf_like e

Our running example matches this case with [e :-> f] and [a :-> x /# y]. The code checks isValArg (x /# y) which returns True (isValArg simply checks that its argument is not a type). So, we take the app_is_value f 1 case.

This is the bug, we throw away the a, or in the running example x /# y!

The relevant case of app_is_value is:

app_is_value (Var f)    nva = id_app_is_value f nva

So in the running example it will continue with id_app_is_value f 1.

And id_app_is_value is defined as:

    id_app_is_value id n_val_args
       = is_con id
       || idArity id > n_val_args

In the running example f is not a constructor and its arity is 2, so this ends up checking 2 > 1 which is True, not False as we expected!

The Solution

What needs to happen instead? The app_is_value function does have the required logic in the case for App (and that is also where the comment about f (x /# y) is):

    app_is_value (App f a)  nva
      | isValArg a              =
        app_is_value f (nva + 1) &&
        not (needsCaseBinding (exprType a) a)
          -- For example  f (x /# y)  where f has arity two, and the first
          -- argument is unboxed. This is not a value!
          -- But  f 34#  is a value.
          -- NB: Check app_is_value first, the arity check is cheaper

A quick fix then would be to change the App case of is_hnf_expr to:

    is_hnf_like (App e a)
      | isValArg a               = app_is_value (App e a) 0
      | otherwise                = is_hnf_like e

But in light of !10841 (comment 517254) I think there is more we need to do here around constructors with strict fields.

Edited by Jaro Reinders
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information