Skip to content

Proposal: Local Rewrites#2371

Merged
facundominguez merged 4 commits into
ucsd-progsys:developfrom
AlecsFerra:patch-1
Oct 18, 2024
Merged

Proposal: Local Rewrites#2371
facundominguez merged 4 commits into
ucsd-progsys:developfrom
AlecsFerra:patch-1

Conversation

@AlecsFerra

@AlecsFerra AlecsFerra commented Oct 8, 2024

Copy link
Copy Markdown
Contributor

Rendered proposal here

@facundominguez facundominguez left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @AlecsFerra! I shared some additional notes from our discussion.


data Term where
{-@ MkId :: Prop (Term id) @-}
MkId :: Term

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This example would work fine with non-GADT syntax, right? That is

{-@ MkId :: Prop (Term id) @-}
data Term = MkId

- Extract the rewrites during the construction of the bindings (in the
`withAssm` function in `PLE.hs`) by inspecting each binding to determine if it
was generated by a case split and extracting equalities in the same way as
described in Solution 1.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This solution can be broken in four steps.

First, extract equalities from bindings that are about the same terms. e.g.

{v:_ | prop v = Term id && someOtherPredicate v }
{v:_ | prop v = Term f}
{k:_ | k = Term g && k = Term h}

would produce equalities

prop v = Term id
prop v = Term f
k = Term g
k = Term h

Second, infer equalities of data constructors by transitivity.

Term id = Term f
Term g = Term h

Third, infer rewrites on the arguments of data constructors, obtaining

id ~> f
f ~> id
g ~> h
h ~> g

Fourth, discard sufficient rewrites to ensure termination of rewriting.

f ~> id
g ~> h

Now f can be replaced with identity whenever found.

The fourth step is so far underspecified. Users of LF need to indicate what variables are ok to appear in lhs and the rhs of the final rewrites (hence the attempt to decide this based on whether variables are local or global).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We don't generate the unification id ~> f as id is not a "local" and it has a definition

was generated by a case split and extracting equalities in the same way as
described in Solution 1.

**Challenges:**

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

An unknown to this approach: While the criterion to cut loops is not settled, it could happen that LF needs to be more restrictive on what rewrites are accepted, because it has less context on how the rewrites are produced.

If the local rewrites were produced by LH instead, LH could produce more rewrites known to be terminating.

This point arouse when discussing the criterion to use in the fourth step of Solution 2. It could be unsafe to allow local variables in the right-hand side of rewrites, but forbidding local variables there would also discard many innocuous rewrites.

[#705](https://github.com/ucsd-progsys/liquid-fixpoint/pull/705), but it was
removed because the solution was too dependent on the variable naming scheme
used by Liquid Haskell. This in LH is a non issue as GHC Variables are
annotated with extra information.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

And there is a third approach that is to lean on rest-rewrite to apply the local rewrites, so we don't need to ensure termination separately.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You can add it in the proposal the only thing I'm not sure about is if there is a way to encode lambda into REST terms

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We also mentioned that the integration to REST will be much slower because REST is checking for termination, which we do not really need in this case.

@nikivazou

Copy link
Copy Markdown
Member

@AlecsFerra this is how you can understand which variables are local and which global:
https://github.com/ucsd-progsys/liquidhaskell/blob/develop/liquidhaskell-boot/src/Language/Haskell/Liquid/Types/Types.hs#L1918

@AlecsFerra

Copy link
Copy Markdown
Contributor Author

@AlecsFerra this is how you can understand which variables are local and which global: https://github.com/ucsd-progsys/liquidhaskell/blob/develop/liquidhaskell-boot/src/Language/Haskell/Liquid/Types/Types.hs#L1918

This is a strong argument for doing it inside LH

@ranjitjhala

Copy link
Copy Markdown
Member

hi @AlecsFerra -- sorry for the delay - i'm traveling this week, but I'll try to get you some feedback soon, thanks! Ranjit.

@AlecsFerra

AlecsFerra commented Oct 14, 2024

Copy link
Copy Markdown
Contributor Author

In the mean time I've started implementing proposal 2:

And the example produces the following .fq file [local at line 69]

The local rewrites and "dependant cases" case can be tried using my forks of liquid-haskell and liquid-fixpoint

The only missing features at the moment are:

  • Parsing of the horn files (This will require deciding how the horn format is specified)
  • Parsing of the .fq files (Done)

This proposal suggests the following steps:

- Introduce a mechanism to provide local definitions in the `.fq` file format.
For example: `define n f = {(Example.id)}`, where `n` is the binding ID that

@facundominguez facundominguez Oct 14, 2024

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm just noticing that if the new define form allowed parameters, as the old form does, this mechanism could be reused to unfold local definitions as well.

For instance,

{-@ reflect map @-}
map f e = go
  where
    go [] = e
    go (x:xs) = f x : go xs

{-@ lemma :: f:_ -> e:_ -> { map f e [] == [] } @-}
lemma _ _ = ()

Oh, well, it would have to be spelled out how the binding of go is introduced when doing PLE. So it is not something we could address immediately, but I think it is an interesting derivation of the current effort.

@AlecsFerra AlecsFerra Oct 14, 2024

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You're right but first we need to allow lambdas in reflected function as GHC is generating lambda when using where clauses to define functions

Edit: I was wrong is another error regarding recursive definitions

@AlecsFerra

AlecsFerra commented Oct 18, 2024

Copy link
Copy Markdown
Contributor Author

In the final solution, option 2 was chosen. Liquid Fixpoint now includes a flag --localrewrites that enables this feature. The .fq file format was updated to support the following directive:

defineLocal bid [ id := expr; ...]
  • bid: The binding identifier where the rewrite is relevant.
  • id: The identifier to be rewritten.
  • expr: The expression to which id will be rewritten.
  • The list can contain an arbitrary number of rewrites.

It is the responsibility of the user of liquid fixpoint to ensure that the rewrites do not cause infinite loops, as the surface language typically has more contextual information and can make more accurate, less conservative approximations.

Currently, all the modifications required for Liquid Fixpoint, except the Horn format parser (we are waiting for a final specification), have already been merged into the develop branch.

The feature that utilizes local rewrites in Liquid Haskell is also close to being merged.

@facundominguez facundominguez marked this pull request as ready for review October 18, 2024 13:40
Comment thread proposals/LocalRewrites.md
@facundominguez

Copy link
Copy Markdown
Collaborator

The implementation has been merged in #2384 and ucsd-progsys/liquid-fixpoint#710.

I'm merging this proposal for the record.

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.

4 participants