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

[KIP] - Lift the Binding Restriction on :=K #2477

Open
JKTKops opened this issue Mar 10, 2022 · 6 comments
Open

[KIP] - Lift the Binding Restriction on :=K #2477

JKTKops opened this issue Mar 10, 2022 · 6 comments
Labels

Comments

@JKTKops
Copy link

JKTKops commented Mar 10, 2022

Motivation

This is the binding restriction on :=K:

However, due to current limitations, these variables are NOT bound in the rest of the term. The user is thus encouraged to use anonymous variables only, although this is not required.

This feature would make it possible to describe View Patterns in K. The link is for haskell but has motivating examples. I am unsure what a special syntax sugar for such patterns would be but it also doesn't matter too much since using :=K is already pretty quiet.

My motivating use case for this is pattern matching on bitstrings. @ehildenb tells me that the Plutus semantics share this use case with me. Consider bitpatterns in erlang. Say I want to write some binary decoding logic for a machine language. In Erlang I can write something like this:

case Encoding of
  << 0:2, Size:2, Opcode:4, RegA:3, RegB:3, 0:2 >> -> dasmRR(Opcode, Size, RegA, RegB)
  << 1:2, Size:2, Opcode:4, RegA:3, Imm:5       >> -> dasmRI(Opcode, Size, RegA, Imm)
  << 4:3, Sgn:1, Opcode:4, Disp:8               >> -> dasmJmp(Opcode, Sgn, Disp)
end.

In Haskell, with a bit of work to define the bits ViewPattern, I can write it like this:

{-# LANGUAGE ViewPatterns #-}

decode :: ByteString -> Maybe Instruction
decode (bits [2,2,4,3,3,2] -> [0,size,op,regA,regB,0]) = dasmRR op size regA regB
decode (bits [2,2,4,3,5]   -> [1,size,op,regA,imm])    = dasmRI op size regA imm
decode (bits [3,1,4,8]     -> [4, sgn, op, disp])      = dasmJmp op sgn disp
decode _ = Nothing

However to define a similar function in K, I have to figure out how to encode the match myself as a combination of &Int and ==Int so that I can "lift" it to the outer level. I can't call a bits function on the input, pattern match on the result, and keep trying other decode patterns if that match fails.

Example K Code

Suppose K allowed us to bind variables with :=K. This function could look something like this:

module DECODE
  imports INT
  imports BYTES

  syntax Ints ::= List{Int, ","}
  syntax BitPattern ::= "<<" Ints ">>"
                      | "bits" "(" "[" Ints "]" "," Bytes ")" [function]
  //omitted for brevity
  //rule bits(...) => ...

  rule decode(BS) => dasmRR(OP, SIZE, RegA, RegB)
    requires << 0, SIZE, OP, RegA, RegB, 0 >> :=K bits([2,2,4,3,3,2], BS)
  rule decode(BS) => dasmRI(OP, SIZE, RegA, RegB)
    requires << 1, SIZE, OP, RegA, Imm >> :=K bits([2,2,4,3,5], BS)
  rule decode(BS) => dasmJmp(OP, SGN, DISP)
    requires << 4, SGN, OP, DISP >> :=K bits([3,1,4,8], BS)
endmodule

It is also worth mentioning that this feature would also enable a simple syntactic sugar for where clauses ala #1685:

  rule foo => bar
    where PAT = baz

translates directly into

  rule foo => bar
    requires PAT :=K baz

This would also allow PAT to be more complicated than a single variable.

Documentation

Remove the sentences about the restriction from the documentation of the pattern matching operator. Under the same section, a new paragraph like this could be added:

Since the pattern matching operator can bind variables, it can be used to implement a generalized form of pattern matching called view patterns. Using the pattern matching operator on the result of a function symbol makes it possible to match against an arbitrary "view" of the data, binding variables out of the view, rather than on the data itself.

Potential Alternatives/Work-arounds

Work-arounds for this feature tend to be very use-specific and involve brittle utilities, code duplication, or both. It becomes necessary to write the logic associated with the view pattern manually, losing out on the benefits of built-in pattern matching in K.

I think it might be possible to emulate the continue-on-fail behavior of a regular pattern match in K today with the #STUCK strategy, by I haven't tried that as it would be far more confusing and hard to understand than the custom logic for pattern matching in my case.

@JKTKops JKTKops added the kip label Mar 10, 2022
@radumereuta
Copy link
Contributor

@dwightguth you weren't in the meeting, and we could use your input on this.
Do we need backend support to implement this feature?

@dwightguth
Copy link
Collaborator

Yes, you are going to need backend support in order to implement this. Right now the symbol is implemented by means of creating a new function that performs the matching and returns a boolean, but there is no way to propagate the binding out of the function and back to the original rule with this approach. You will have to actually implement native support for this symbol in the backend if you want this capability. I'm not entirely sure how best it should work if that's what you want to do. A where clause on rules might be your best bet rather than trying to implicitly extract the terms from the rule using the existing syntax. If you go that route, then the decision tree generation algorithm in the llvm backend would have to become aware of these bindings, and the code generator would have to be modified in order to correctly produce those values when the side condition is evaluated. You would also have to figure out how to pass the values from the side condition to the rhs, because currently, side condition functions in the llvm backend simply return a boolean, which is not enough information in this case.

@JKTKops
Copy link
Author

JKTKops commented Mar 17, 2022

By "boolean" I presume you mean an LLVM i1 and not a K Bool?

In that case, does the LLVM backend already have some matching function (generator, possibly) for regular matches that produces a structure with binding information? I'd think it would be possible to use the same method used for regular matches and treat the results the same way a regular side condition is treated in the decision tree. The new needed support would be a way to gather bindings from it. Perhaps this is exactly what you're suggesting, I'm not sure.

Also probably worth mentioning that Everett pointed out to me that you don't quite get where bindings for free with this. If the pattern in the LHS of the where binding doesn't match, the semantics here would be that the case fails to match rather than a crash.

@dwightguth
Copy link
Collaborator

I mean, the llvm backend represents the Bool type as an i1, but yes, I do mean a native boolean. You might be right that we could just modify the decision tree generator to be aware of the binding symbol and directly bind the variables during matching. That's probably simpler than what I had in mind, but it is contingent on having an explicit guard syntax on sentences rather than trying to treat them as regular functions in the rhs or side condition.

@david-k
Copy link

david-k commented Sep 9, 2022

Here is an example that shows the advantage that using :=K would have over #let.

syntax Prog ::= "convert" String | "result" Int
configuration <Top>
                <k> $PGM:Prog </k>
                <sum> 0 </sum>
                <brg> 1 </brg>
                <another> 2 </another>
                <another1> 2 </another1>
              </Top>

If the binding restriction on :=K is lifted we could a rule like the following:

rule <k> convert STR => result I ... </k>
     <sum> S => S +Int B +Int I </sum>
     <brg> B </brg>
  requires I :=K String2Int(STR)

Note that I is used in multiple cells, but only in the LHS. Also, in the rule that prompted this example, the term that is bound to I is more complex, and binding it to a variable greatly increases readability of the rule.

Trying to rewrite the above rule using #let resulted in the following:

rule <Top>
       <k> convert STR ~> K </k>
       <sum> S:Int </sum> // Read and modified
       <brg> B </brg>     // Only read
       A:AnotherCell
       A1:Another1Cell
     </Top>
     =>
     #let I = String2Int(STR) #in
     <Top>
       <k> result I ~> K </k>
       <sum> S +Int B +Int I </sum>
       <brg> B </brg>
       A
       A1
     </Top>
  • First, it seems that I have to hoist => to the very top, because #let cannot contain rewrites.
  • Also, I need to have a single top cell in the rule because otherwise I get the following error: NoSuchElementException: key not found: #cells.
  • Because I use the top cell, I need to match all child cells. However, explicitly enumerating all cells would be a bit annoying in KEVM.
  • If I replace A and A1 with a single variable REST, I get the following error: [Error] Compiler: Could not find sorts: [Bag]. Importing the KCELLS module in which Bag seems to be defined results in more kompile errors.

So using #let is quite inconvenient in this case. Everett provided a quite readable alternative using a helper rule:

rule <k> convert STR => convert_2 String2Int(STR) ... </k>

rule <k> convert_2 I => result I ... </k>
     <sum> S => S +Int B +Int I </sum>
     <brg> B </brg>

The downside is that this it is somewhat less "obvious" what is going and increases the cognitive load a bit, but in the meantime this looks like a good solution.

@ehildenb
Copy link
Member

ehildenb commented Nov 21, 2022

For the frontend:

  • Add a new syntax for where WHERE_CLAUSES (and WHERE_CLAUSES can only be a bunch of _andBool_ of a bunch of _:=K_).
  • The WHERE_CLAUSES rhs should only contain variables which are already bound by either the LHS of the rule, or a prior clause in the WHERE_CLAUSE.
  • The frontend needs to sort the WHERE_CLAUSES and check for no-cycles.

For the LLVM backend:

  • The decision tree generation process needs to be modified in the following way:
    • The DT leaf for each rule with a where-clause needs to change the leaf of the DT (where it goes to "apply the rule"), to instead jump to the the code which evaluates the RHS of the where clause, which then should jump to the DT for matching the LHS of the where clause.
    • Iteratively chain all the where-clauses like this (if any fail, proceed to next rule).
  • Only the Scala code should need to be changed (the code which generates the DTs and chains them together). Maybe some small changes needed to instruct the LLVM backend to otherwise discard the where clauses.

For the Haskell backend:

  • The WHERE_CLAUSES can be compiled to normal equalities that stay on teh LHS of the rule. This will probably not be the most efficient way to do this, but will send terms to bottom that need to go to bottom.

h0nzZik pushed a commit to h0nzZik/k that referenced this issue Nov 24, 2022
…untimeverification#1830)

* haskell-backend/src/main/native/haskell-backend: 79703e11 - Update dependency: deps/k_release

* haskell-backend/src/main/native/haskell-backend: 8428df45 - LTS Haskell 17.4

* haskell-backend/src/main/native/haskell-backend: 2e714c56 - Add an EquationVariableName to RewritingVariableName

* haskell-backend/src/main/native/haskell-backend: c5cc46be - Update dependency: deps/k_release

* haskell-backend/src/main/native/haskell-backend: 0c4ad203 - Update dependency: deps/k_release

* haskell-backend/src/main/native/haskell-backend: a182994f - Update dependency: deps/k_release

* haskell-backend/src/main/native/haskell-backend: 9460590a - Simplify side conditions (runtimeverification#2393)

* haskell-backend/src/main/native/haskell-backend: 4fb83523 - Fix `variable` to `RewritingVariableName` in `Kore.Equation.Application` (runtimeverification#2325)

* haskell-backend/src/main/native/haskell-backend: f41cba23 - Revert "Revert runtimeverification#2315 (runtimeverification#2385)" (runtimeverification#2389)

* haskell-backend/src/main/native/haskell-backend: 44eed8d7 - Run Test workflow on macOS (runtimeverification#2420)

* haskell-backend/src/main/native/haskell-backend: 904f5322 - kore-repl: Print human-readable error messages (runtimeverification#2441)

* haskell-backend/src/main/native/haskell-backend: b49fdd3c - Make Conditional's term a strict field (runtimeverification#2442)

* haskell-backend/src/main/native/haskell-backend: 01f0402d - adding match for And (runtimeverification#2435)

* haskell-backend/src/main/native/haskell-backend: 3243d1e5 - kore-0.41.0.0 (runtimeverification#2444)

* haskell-backend/src/main/native/haskell-backend: 28913344 - Update dependency: deps/k_release

* haskell-backend/src/main/native/haskell-backend: abf0292b - Use unit and element symbol attributes

* haskell-backend/src/main/native/haskell-backend: 7d3ee1bc - Inefficient Equation renaming (runtimeverification#2438)

* haskell-backend/src/main/native/haskell-backend: 95b6c107 - Stop simplifying the left-hand side of equations (runtimeverification#2392)

* haskell-backend/src/main/native/haskell-backend: 21959d95 - Add instructions for running one test in Docker (runtimeverification#2456)

* haskell-backend/src/main/native/haskell-backend: 4195d948 - Removes sort checks for Map and Set during internalization (runtimeverification#2440)

* Predicates in the condition are now simplified

* haskell-backend/src/main/native/haskell-backend: c35bfa56 - Remove defined marker (runtimeverification#2380)

* haskell-backend/src/main/native/haskell-backend: 57ad3292 - Remove cabal.project.freeze (runtimeverification#2450)

* haskell-backend/src/main/native/haskell-backend: 66158c67 - Support the --bug-report and --no-bug-report options in kore-repl (#2462)

* haskell-backend/src/main/native/haskell-backend: f0d93ac5 - Use stderr for errors (runtimeverification#2458)

* haskell-backend/src/main/native/haskell-backend: 0427fae7 - Distinguish \bottom from stuck states (runtimeverification#2451)

* haskell-backend/src/main/native/haskell-backend: d136446e - Fix Strict performance (runtimeverification#2447)

* haskell-backend/src/main/native/haskell-backend: a4e55b2a - Remove IMP fragment tests (runtimeverification#2467)

* haskell-backend/src/main/native/haskell-backend: 1f61e5a6 - Revert PRs related to InternalAc, Unit, Element & Concat symbols changes (runtimeverification#2463)

* haskell-backend/src/main/native/haskell-backend: fcfc2d52 - Remove the EvaluatedF marker from TermLike (runtimeverification#2469)

* haskell-backend/src/main/native/haskell-backend: fb56d719 - Revert PR runtimeverification#2334 (runtimeverification#2473)

* haskell-backend/src/main/native/haskell-backend: bb258f14 - Add ref = "main"; to default.nix (runtimeverification#2475)

* haskell-backend/src/main/native/haskell-backend: 964e64e7 - Update dependency: deps/k_release (runtimeverification#2464)

* haskell-backend/src/main/native/haskell-backend: abf14770 - Emit a warning when kore-exec exceeds the depth limit (runtimeverification#2461)

* haskell-backend/src/main/native/haskell-backend: 003a8424 - Update dependency: deps/k_release (runtimeverification#2478)

* haskell-backend/src/main/native/haskell-backend: 485bd89a - kore-0.42.0.0 (runtimeverification#2477)

* Add definedness to lookup rules, regenerate tests

* haskell-backend/src/main/native/haskell-backend: 78515d10 - Update dependency: deps/k_release (runtimeverification#2486)

* Revert changes to domains.md and test output

* haskell-backend/src/main/native/haskell-backend: 4f3a751c - Format code with fourmolu (runtimeverification#2382)

* haskell-backend/src/main/native/haskell-backend: de80594b - Update dependency: deps/k_release (runtimeverification#2490)

* Update map symbolic Haskell tests

* haskell-backend/src/main/native/haskell-backend: 9c9ef914 - Update dependency: deps/k_release (runtimeverification#2495)

Co-authored-by: ana-pantilie <[email protected]>
Co-authored-by: Thomas Tuegel <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants