Skip to content
This repository has been archived by the owner on Apr 25, 2024. It is now read-only.

Refactoring the branching mechanism #1072

Closed
wants to merge 4 commits into from
Closed

Conversation

PetarMax
Copy link
Contributor

@PetarMax PetarMax commented Apr 9, 2024

This is a very draft PR that uses branching information from the backend (for which support was enabled by #942) to simplify the branching mechanism, potentially making the extract_branches obsolete and only creating NDBranches in the presence of true non-determinism.

I've done a preliminary testing in Kontrol, more info in comments below.

@PetarMax PetarMax added the enhancement New feature or request label Apr 9, 2024
@PetarMax PetarMax requested review from tothtamas28 and ehildenb April 9, 2024 12:11
@PetarMax PetarMax self-assigned this Apr 9, 2024
src/pyk/cterm/symbolic.py Outdated Show resolved Hide resolved
Comment on lines +123 to +129
next_states = tuple(
(
CTerm.from_kast(self.kore_to_kast(ns.kore)),
self.kore_to_kast(ns.rule_predicate) if ns.rule_predicate is not None else None,
)
for ns in resp_next_states
)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Propagating the branching constraints along with the CTerms, perhaps there is a better way of doing this?

Copy link
Collaborator

Choose a reason for hiding this comment

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

CTermExecute could be abstract with concrete subclasses for branching vs non-branching steps.

Comment on lines +225 to +235
# _branches = self.kcfg_semantics.extract_branches(_cterm)
# branches = []
# for constraint in _branches:
# kast = mlAnd(list(_cterm.constraints) + [constraint])
# kast, _ = self.cterm_symbolic.kast_simplify(kast)
# if not CTerm._is_bottom(kast):
# branches.append(constraint)
# if len(branches) > 1:
# constraint_strs = [self.pretty_print(bc) for bc in branches]
# log(f'{len(branches)} branches using heuristics: {node_id} -> {constraint_strs}')
# return Branch(branches, heuristic=True)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This does not seem to be necessary anymore.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's remove the comments, we can bring that code back at any time from the version history.

constraint_strs = [self.pretty_print(bc) for bc in branches]
log(f'{len(branches)} branches using heuristics: {node_id} -> {constraint_strs}')
assert len(next_cterms_with_branch_constraints) > 1
if all(branch_constraint for _, branch_constraint in next_cterms_with_branch_constraints):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This could be too strong, but feels safe at the moment.

@PetarMax
Copy link
Contributor Author

PetarMax commented Apr 9, 2024

As a first example, I have that the KCFG of the CSE execution of ArithmeticCallTest.test_double_add transforms from this

┌─ 1 (root, init)
│   k: #execute ~> CONTINUATION:K
│   pc: 0
│   callDepth: 0
│   statusCode: STATUSCODE:StatusCode
│
│  (1024 steps)
├─ 7 (split)
│   k: #execute ~> #return 128 32 ~> #pc [ STATICCALL ] ~> #execute ~> CONTINUATION:K
│   pc: 0
│   callDepth: 1
│   statusCode: STATUSCODE:StatusCode
┃
┃ (branch)
┣━━┓ constraint: ( maxUInt256 -Int VV1_y_114b9705:Int ) <Int VV0_x_114b9705:Int
┃  │
┃  ├─ 8
┃  │   k: #execute ~> #return 128 32 ~> #pc [ STATICCALL ] ~> #execute ~> CONTINUATION:K
┃  │   pc: 0
┃  │   callDepth: 1
┃  │   statusCode: STATUSCODE:StatusCode
┃  │
┃  │  (73 steps)
┃  └─ 10 (leaf, terminal)
┃      k: #halt ~> CONTINUATION:K
┃      pc: 2357
┃      callDepth: 0
┃      statusCode: EVMC_REVERT
┃
┗━━┓ constraint: VV0_x_114b9705:Int <=Int ( maxUInt256 -Int VV1_y_114b9705:Int )
   │
   ├─ 9
   │   k: #execute ~> #return 128 32 ~> #pc [ STATICCALL ] ~> #execute ~> CONTINUATION:K
   │   pc: 0
   │   callDepth: 1
   │   statusCode: STATUSCODE:StatusCode
   │
   │  (413 steps)
   ├─ 11
   │   k: #execute ~> #return 160 32 ~> #pc [ STATICCALL ] ~> #execute ~> CONTINUATION:K
   │   pc: 0
   │   callDepth: 1
   │   statusCode: EVMC_SUCCESS
   ┃
   ┃ (1 step)
   ┣━━┓
   ┃  │
   ┃  ├─ 12
   ┃  │   k: #halt ~> #return 160 32 ~> #pc [ STATICCALL ] ~> #execute ~> CONTINUATION:K
   ┃  │   pc: 550
   ┃  │   callDepth: 1
   ┃  │   statusCode: EVMC_REVERT
   ┃  │
   ┃  │  (72 steps)
   ┃  └─ 14 (leaf, terminal)
   ┃      k: #halt ~> CONTINUATION:K
   ┃      pc: 2474
   ┃      callDepth: 0
   ┃      statusCode: EVMC_REVERT
   ┃
   ┗━━┓
      │
      ├─ 13 (split)
      │   k: #halt ~> #return 160 32 ~> #pc [ STATICCALL ] ~> #execute ~> CONTINUATION:K
      │   pc: 128
      │   callDepth: 1
      │   statusCode: EVMC_SUCCESS
      ┃
      ┃ (branch)
      ┣━━┓ constraint: VV0_x_114b9705:Int <Int ( ( VV0_x_114b9705:Int +Int VV1_y_114b9705:Int ) +Int VV1_y_114b9705:Int )
      ┃  │
      ┃  ├─ 20
      ┃  │   k: #halt ~> #return 160 32 ~> #pc [ STATICCALL ] ~> #execute ~> CONTINUATION:K
      ┃  │   pc: 128
      ┃  │   callDepth: 1
      ┃  │   statusCode: EVMC_SUCCESS
      ┃  │
      ┃  │  (321 steps)
      ┃  ├─ 18 (terminal)
      ┃  │   k: #halt ~> CONTINUATION:K
      ┃  │   pc: 248
      ┃  │   callDepth: 0
      ┃  │   statusCode: EVMC_SUCCESS
      ┃  │
      ┃  ┊  constraint: true
      ┃  ┊  subst: OMITTED SUBST
      ┃  └─ 6 (leaf, target, terminal)
      ┃      k: #halt ~> CONTINUATION:K
      ┃      pc: PC_CELL_5d410f2a:Int
      ┃      callDepth: CALLDEPTH_CELL_5d410f2a:Int
      ┃      statusCode: STATUSCODE_FINAL:StatusCode
      ┃
      ┗━━┓ constraint: ( notBool VV0_x_114b9705:Int <Int ( ( VV0_x_114b9705:Int +Int VV1_y_114b9705:Int ) +Int VV1_y_114b9705:Int ) )
         │
         ├─ 21
         │   k: #halt ~> #return 160 32 ~> #pc [ STATICCALL ] ~> #execute ~> CONTINUATION:K
         │   pc: 128
         │   callDepth: 1
         │   statusCode: EVMC_SUCCESS
         │
         │  (331 steps)
         └─ 19 (leaf, terminal)
             k: #halt ~> CONTINUATION:K
             pc: 3736
             callDepth: 0
             statusCode: EVMC_REVERT

which is not great, and in which simultaneous applications of multiple specifications of the underlying functions cause non-deterministic branching, to this

┌─ 1 (root, init)
│   k: #execute ~> CONTINUATION:K
│   pc: 0
│   callDepth: 0
│   statusCode: STATUSCODE:StatusCode
│   src: test/ArithmeticCall.t.sol:6:33
│
│  (1024 steps)
├─ 7 (split)
│   k: #execute ~> #return 128 32 ~> #pc [ STATICCALL ] ~> #execute ~> CONTINUATION:K
│   pc: 0
│   callDepth: 1
│   statusCode: STATUSCODE:StatusCode
│   src: test/ArithmeticCall.t.sol:6:33
┃
┃ (branch)
┣━━┓ constraint: ( maxUInt256 -Int VV1_y_114b9705:Int ) <Int VV0_x_114b9705:Int
┃  │
┃  ├─ 9
┃  │   k: #execute ~> #return 128 32 ~> #pc [ STATICCALL ] ~> #execute ~> CONTINUATION:K
┃  │   pc: 0
┃  │   callDepth: 1
┃  │   statusCode: STATUSCODE:StatusCode
┃  │   src: test/ArithmeticCall.t.sol:6:33
┃  │
┃  │  (73 steps)
┃  └─ 11 (leaf, terminal)
┃      k: #halt ~> CONTINUATION:K
┃      pc: 2357
┃      callDepth: 0
┃      statusCode: EVMC_REVERT
┃
┣━━┓ constraints:
┃  ┃     ( VV0_x_114b9705:Int +Int VV1_y_114b9705:Int ) <Int pow256
┃  ┃     VV0_x_114b9705:Int <=Int ( maxUInt256 -Int VV1_y_114b9705:Int )
┃  ┃     ( maxUInt256 -Int VV1_y_114b9705:Int ) <Int ( VV0_x_114b9705:Int +Int VV1_y_114b9705:Int )
┃  │
┃  ├─ 21
┃  │   k: #execute ~> #return 128 32 ~> #pc [ STATICCALL ] ~> #execute ~> CONTINUATION:K
┃  │   pc: 0
┃  │   callDepth: 1
┃  │   statusCode: STATUSCODE:StatusCode
┃  │   src: test/ArithmeticCall.t.sol:6:33
┃  │
┃  │  (486 steps)
┃  └─ 15 (leaf, terminal)
┃      k: #halt ~> CONTINUATION:K
┃      pc: 2474
┃      callDepth: 0
┃      statusCode: EVMC_REVERT
┃
┣━━┓ constraints:
┃  ┃     ( VV0_x_114b9705:Int +Int VV1_y_114b9705:Int ) <Int pow256
┃  ┃     VV0_x_114b9705:Int <=Int ( maxUInt256 -Int VV1_y_114b9705:Int )
┃  ┃     VV0_x_114b9705:Int <Int ( ( VV0_x_114b9705:Int +Int VV1_y_114b9705:Int ) +Int VV1_y_114b9705:Int )
┃  ┃     ( VV0_x_114b9705:Int +Int VV1_y_114b9705:Int ) <=Int ( maxUInt256 -Int VV1_y_114b9705:Int )
┃  │
┃  ├─ 24
┃  │   k: #execute ~> #return 128 32 ~> #pc [ STATICCALL ] ~> #execute ~> CONTINUATION:K
┃  │   pc: 0
┃  │   callDepth: 1
┃  │   statusCode: STATUSCODE:StatusCode
┃  │   src: test/ArithmeticCall.t.sol:6:33
┃  │
┃  │  (735 steps)
┃  ├─ 18 (terminal)
┃  │   k: #halt ~> CONTINUATION:K
┃  │   pc: 248
┃  │   callDepth: 0
┃  │   statusCode: EVMC_SUCCESS
┃  │   src: test/ArithmeticCall.t.sol:9:12
┃  │
┃  ┊  constraint: true
┃  ┊  subst: OMITTED SUBST
┃  └─ 6 (leaf, target, terminal)
┃      k: #halt ~> CONTINUATION:K
┃      pc: PC_CELL_5d410f2a:Int
┃      callDepth: CALLDEPTH_CELL_5d410f2a:Int
┃      statusCode: STATUSCODE_FINAL:StatusCode
┃
┗━━┓ constraints:
   ┃     ( VV0_x_114b9705:Int +Int VV1_y_114b9705:Int ) <Int pow256
   ┃     VV0_x_114b9705:Int <=Int ( maxUInt256 -Int VV1_y_114b9705:Int )
   ┃     ( ( VV0_x_114b9705:Int +Int VV1_y_114b9705:Int ) +Int VV1_y_114b9705:Int ) <=Int VV0_x_114b9705:Int
   ┃     ( VV0_x_114b9705:Int +Int VV1_y_114b9705:Int ) <=Int ( maxUInt256 -Int VV1_y_114b9705:Int )
   │
   ├─ 25
   │   k: #execute ~> #return 128 32 ~> #pc [ STATICCALL ] ~> #execute ~> CONTINUATION:K
   │   pc: 0
   │   callDepth: 1
   │   statusCode: STATUSCODE:StatusCode
   │   src: test/ArithmeticCall.t.sol:6:33
   │
   │  (745 steps)
   └─ 19 (leaf, terminal)
       k: #halt ~> CONTINUATION:K
       pc: 3736
       callDepth: 0
       statusCode: EVMC_REVERT

where there are no non-deterministic branches and all branching conditions are present.

@PetarMax
Copy link
Contributor Author

PetarMax commented Apr 9, 2024

In a recent proof, also, the two branchings below were characterised as non-deterministic, but their constraints are now fully captured:

┃
┃ (branch)
┣━━┓ constraint: ( X:Int ==Int ( X:Int *Int Y:Int ) /Word Y:Int orBool Y:Int ==Int 0 )
┃  │
┃  ├─ 65
┃  │   k: JUMPI --- bool2Word ( ( X:Int ==Int ( X:Int *Int Y:Int ) /Word ...
┃  │   pc: ---
┃  │   callDepth: ---
┃  │   statusCode: EVMC_SUCCESS
┃  │
┃  ...
┃  ┃
┃  ┃ (branch)
┃  ┣━━┓ constraint: ( Z:Int ==Int ( Z:Int *Int T:Int ) /Word T:Int orBool T:Int ==Int 0 )
┃  ┃  │
┃  ┃  └─ 75 (leaf, refuted)
┃  ┃      k: JUMPI --- bool2Word ( ( Z:Int ==Int ( Z:Int *Int T:Int ) /Word ...
┃  ┃      pc: ---
┃  ┃      callDepth: ---
┃  ┃      statusCode: EVMC_SUCCESS
┃  ┃
┃  ┗━━┓ constraints:
┃     ┃     ( notBool T:Int ==K 0 )
┃     ┃     ( notBool Z:Int ==K ( Z:Int *Int T:Int ) /Word T:Int )
┃     │
┃     ...
┃
┗━━┓ constraints:
	 ┃     ( notBool Y:Int ==K 0 )
	 ┃     ( notBool X:Int ==K ( X:Int *Int Y:Int ) /Word Y:Int )
	 │
	 ├─ 66
	 │   k: JUMPI --- bool2Word ( ( X:Int ==Int ( X:Int *Int Y:Int ) /Word ...
	 │   pc: ---
	 │   callDepth: ---
	 │   statusCode: EVMC_SUCCESS
	 │
	 ...

Copy link
Collaborator

@tothtamas28 tothtamas28 left a comment

Choose a reason for hiding this comment

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

Looks good in general.

Before merging this, we should understand a bit better what guarantees the backends provide (#1073), and do extensive testing in evm-semantics, kontrol and ongoing engagements.

Comment on lines +123 to +129
next_states = tuple(
(
CTerm.from_kast(self.kore_to_kast(ns.kore)),
self.kore_to_kast(ns.rule_predicate) if ns.rule_predicate is not None else None,
)
for ns in resp_next_states
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

CTermExecute could be abstract with concrete subclasses for branching vs non-branching steps.

Comment on lines +225 to +235
# _branches = self.kcfg_semantics.extract_branches(_cterm)
# branches = []
# for constraint in _branches:
# kast = mlAnd(list(_cterm.constraints) + [constraint])
# kast, _ = self.cterm_symbolic.kast_simplify(kast)
# if not CTerm._is_bottom(kast):
# branches.append(constraint)
# if len(branches) > 1:
# constraint_strs = [self.pretty_print(bc) for bc in branches]
# log(f'{len(branches)} branches using heuristics: {node_id} -> {constraint_strs}')
# return Branch(branches, heuristic=True)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's remove the comments, we can bring that code back at any time from the version history.

@Baltoli
Copy link
Contributor

Baltoli commented Apr 15, 2024

@Baltoli Baltoli closed this Apr 15, 2024
@Baltoli Baltoli deleted the petar/efficient-branching branch April 15, 2024 09:07
rv-jenkins pushed a commit to runtimeverification/k that referenced this pull request Apr 24, 2024
Transfer of: runtimeverification/pyk#1072

This PR that uses branching information from the backend (for which
support was enabled by runtimeverification/pyk#942) to simplify the
branching mechanism, potentially making the `extract_branches` obsolete
and only creating `NDBranches` in the presence of true non-determinism.

I've done a preliminary testing in Kontrol, more info in comments below.

---------

Co-authored-by: Petar Maksimovic <[email protected]>
Co-authored-by: Petar Maksimović <[email protected]>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Revisiting branch handling in pyk
3 participants