Skip to content

ABCD dead branch elimination (upper bounds checks) #191

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 187 additions & 0 deletions Druid-Tests/DRPiABCDDeadBranchEliminationTest.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
Class {
#name : 'DRPiABCDDeadBranchEliminationTest',
#superclass : 'DRPiNodesTest',
#category : 'Druid-Tests-Optimizations',
#package : 'Druid-Tests',
#tag : 'Optimizations'
}

{ #category : 'tests' }
DRPiABCDDeadBranchEliminationTest >> testABCD1 [

"
/->-|b_end0|
|b_i| -> |b_if1| -> |b_end1| (unreachable)
"

| cfg b_end0 b_end1 b_i x y b_if1 |
cfg := DRControlFlowGraph new.

b_end0 := cfg newBasicBlockWith: [ :block | block storeSInt64: 2 at: 777 ].
b_end1 := cfg newBasicBlockWith: [ :block | block storeSInt64: 2 at: 888 ].

b_i := cfg newBasicBlockWith: [ :block |
x := block loadSInt64: 111.
y := block subtract: 1 from: x.
].

b_if1 := cfg newBasicBlockWith: [ :block | |cmp|
cmp := block less: y than: x.
block jumpIf: cmp to: b_end0 ifFalseTo: b_end1
].

cfg initialBasicBlock jumpTo: b_i.
b_i jumpTo: b_if1.


self optimize: cfg with: { DRPiABCDDeadBranchElimination }.


self assert: (cfg blocks includes: b_end0).
self assert: (cfg blocks includes: b_end1) not.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
self assert: (cfg blocks includes: b_end1) not.
self deny: (cfg blocks includes: b_end1).


self assert: b_if1 endInstruction isJump.
self assert: b_if1 endInstruction isConditionalBranch not.
self assert: b_if1 endInstruction target equals: b_end0.

]

{ #category : 'tests' }
DRPiABCDDeadBranchEliminationTest >> testABCD2 [
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the difference with testABCD1? Can we choose better names? :P


"
/->-|b_end0|
|b_i| -> |b_if1| -> |b_end1| (unreachable)
"

| cfg b_end0 b_end1 b_i x y b_if1 |
cfg := DRControlFlowGraph new.

b_end0 := cfg newBasicBlockWith: [ :block | block storeSInt64: 2 at: 777 ].
b_end1 := cfg newBasicBlockWith: [ :block | block storeSInt64: 2 at: 888 ].

b_i := cfg newBasicBlockWith: [ :block |
x := block loadSInt64: 111.
y := block add: 1 to: x.
].

b_if1 := cfg newBasicBlockWith: [ :block | |cmp|
cmp := block less: y than: x.
block jumpIf: cmp to: b_end0 ifFalseTo: b_end1
].

cfg initialBasicBlock jumpTo: b_i.
b_i jumpTo: b_if1.


self optimize: cfg with: { DRPiABCDDeadBranchElimination }.


self assert: (cfg blocks includes: b_end0).
self assert: (cfg blocks includes: b_end1) not.

self assert: b_if1 endInstruction isJump.
self assert: b_if1 endInstruction isConditionalBranch not.
self assert: b_if1 endInstruction target equals: b_end0.

]

{ #category : 'tests' }
DRPiABCDDeadBranchEliminationTest >> testABCDGraph [
| cfg b_i b_end limit0 limit1 st0 st1 b_while b_for j1 b_if1 st3 limit3 j0 b_if2 t0 b_if2_after_check2 b_if2_after_check1 j4 cmp_for dummyOperand1 jmp_for b_check_failed |

"This is the running example in the ABCD paper, reproduced it just to have something to validate our results agains
The code is kinda ugly, I don't know if there's a nicer way to build this self-referencial/cyclic IRs"

" /->------------------------------------------------------------------------------------->-\
/ V--<-------------------<-\ /->---------------------/->--\
|b_i| -> |b_while| -> |b_if1| -> |b_for| -> |b_if2| -> |b_if2_after_check1| -> |b_if2_after_check2| -> |b_end|
\-<------------------------------------------------------<-/
"

"Check Figure 3 in ABCD: Eliminating Array Bounds Checks on Demand for it to make *a bit* more sense"
Comment on lines +96 to +102
Copy link
Contributor

Choose a reason for hiding this comment

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

👏


cfg := DRControlFlowGraph new.

b_end := cfg newBasicBlockWith: [ :block | block storeSInt64: 2 at: 888 ].
b_check_failed := cfg newBasicBlockWith: [ :block | block storeSInt64: 2 at: 777 ].

b_i := cfg newBasicBlockWith: [ :block |
dummyOperand1 := block copy: 1234.
limit0 := block loadFramePointer.
st0 := block copy: -1.
].
cfg initialBasicBlock jumpTo: b_i.

b_if2_after_check2 := cfg newBasicBlockWith: [ :block |
j4 := block add: 1 to: dummyOperand1 "j1".
].

b_if2_after_check1 := cfg newBasicBlockWith: [ :block | |check|
t0 := block add: 1 to: dummyOperand1 "j1".
check := block less: t0 than: limit0.
block jumpIf: check to: b_if2_after_check2 ifFalseTo: b_check_failed.
].
b_if2 := cfg newBasicBlockWith: [ :block | |check|
check := block less: dummyOperand1 "j1" than: limit0.
block jumpIf: check to: b_if2_after_check1 ifFalseTo: b_check_failed.
].

b_for := cfg newBasicBlock.

b_if2_after_check2 jumpTo: b_for.

b_if1 := cfg newBasicBlockWith: [ :block |
st3 := block add: 1 to: dummyOperand1 "st1".
limit3 := block subtract: 1 from: dummyOperand1 "limit1".
j0 := block copy: st3.
block jumpTo: b_for.
].

cmp_for := b_for less: dummyOperand1 than: limit3.
jmp_for := b_for jumpIf: cmp_for to: b_if2 ifFalseTo: b_i.

j1 := b_for phiWithVariables: {1234 asDRValue . 4567 asDRValue }.
cmp_for replaceOperand: dummyOperand1 by: j1.

b_if2_after_check2 instructions first replaceOperand: dummyOperand1 by: j1.
b_if2_after_check1 instructions first replaceOperand: dummyOperand1 by: j1.
b_if2 instructions first replaceOperand: dummyOperand1 by: j1.

j1 replaceOperand: j1 operands first by: j0.
j1 replaceOperand: j1 operands second by: j4.

b_while := cfg newBasicBlockWith: [ :block | |cmp|
limit1 := block phiWithVariables: {}.
st1 := block phiWithVariables: {}.
cmp := block less: st1 than: limit1.
block jumpIf: cmp to: b_if1 ifFalseTo: b_end
].

b_if1 instructions first replaceOperand: dummyOperand1 by: st1.
b_if1 instructions second replaceOperand: dummyOperand1 by: limit1.

b_i jumpTo: b_while.

jmp_for falseBranch removePredecessor: jmp_for basicBlock.
jmp_for newFalseBranch: b_while.
jmp_for basicBlock predecessors add: jmp_for basicBlock predecessors removeFirst.

limit1 operands: {limit0. limit3}.
st1 operands: {st0. st3}.

cfg validate.


self optimize: cfg with: { DRPiABCDDeadBranchElimination }.


self assert: b_if2 successors size equals: 1.
self assert: b_if2 successors unique ~= b_check_failed.

self assert: b_if2_after_check1 successors size equals: 1.
self assert: b_if2 successors unique ~= b_check_failed.

self assert: (cfg blocks includes: b_check_failed) not.

]
86 changes: 0 additions & 86 deletions Druid-Tests/DRPiNodesInsertionTest.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -6,92 +6,6 @@ Class {
#tag : 'Optimizations'
}

{ #category : 'tests' }
DRPiNodesInsertionTest >> testABCDGraph [
| cfg b_i b_end limit0 limit1 st0 st1 b_while b_for j1 b_if1 st3 limit3 j0 b_if2 t0 b_if2_after_check2 b_if2_after_check1 j4 cmp_for dummyOperand1 jmp_for |

"This is the running example in the ABCD paper, reproduced it just to have something to validate our results agains
The code is kinda ugly, I don't know if there's a nicer way to build this self-referencial/cyclic IRs"

" /->------------------------------------------------------------------------------------->-\
/ V--<-------------------<-\ /->---------------------/->--\
|b_i| -> |b_while| -> |b_if1| -> |b_for| -> |b_if2| -> |b_if2_after_check1| -> |b_if2_after_check2| -> |b_end|
\-<------------------------------------------------------<-/
"

"Check Figure 3 in ABCD: Eliminating Array Bounds Checks on Demand for it to make *a bit* more sense"

cfg := DRControlFlowGraph new.

b_end := cfg newBasicBlockWith: [ :block | block storeSInt64: 2 at: 888 ].

b_i := cfg newBasicBlockWith: [ :block |
dummyOperand1 := block copy: 1234.
limit0 := block loadFramePointer.
st0 := block copy: -1.
].
cfg initialBasicBlock jumpTo: b_i.

b_if2_after_check2 := cfg newBasicBlockWith: [ :block |
j4 := block add: 10 to: dummyOperand1 "j1".
].

b_if2_after_check1 := cfg newBasicBlockWith: [ :block | |check|
t0 := block add: 11 to: dummyOperand1 "j1".
check := block less: t0 than: limit0.
block jumpIf: check to: b_if2_after_check2 ifFalseTo: b_end.
].
b_if2 := cfg newBasicBlockWith: [ :block | |check|
check := block less: dummyOperand1 "j1" than: limit0.
block jumpIf: check to: b_if2_after_check1 ifFalseTo: b_end.
].

b_for := cfg newBasicBlock.

b_if2_after_check2 jumpTo: b_for.

cmp_for := b_for less: dummyOperand1 than: limit0.
jmp_for := b_for jumpIf: cmp_for to: b_if2 ifFalseTo: b_i.

j1 := b_for phiWithVariables: {1234 asDRValue . 4567 asDRValue }.
cmp_for replaceOperand: dummyOperand1 by: j1.

b_if2_after_check2 instructions first replaceOperand: dummyOperand1 by: j1.
b_if2_after_check1 instructions first replaceOperand: dummyOperand1 by: j1.
b_if2 instructions first replaceOperand: dummyOperand1 by: j1.

b_if1 := cfg newBasicBlockWith: [ :block |
st3 := block add: 12 to: dummyOperand1 "st1".
limit3 := block subtract: 1 from: dummyOperand1 "limit1".
j0 := block copy: st3.
block jumpTo: b_for.
].

j1 replaceOperand: j1 operands first by: j0.
j1 replaceOperand: j1 operands second by: j4.

b_while := cfg newBasicBlockWith: [ :block | |cmp|
limit1 := block phiWith: {limit0. limit3}.
st1 := block phiWith: {st0. st3}.
cmp := block less: st1 than: limit1.
block jumpIf: cmp to: b_if1 ifFalseTo: b_end
].

b_if1 instructions first replaceOperand: dummyOperand1 by: st1.
b_if1 instructions second replaceOperand: dummyOperand1 by: limit1.

b_i jumpTo: b_while.

jmp_for falseBranch removePredecessor: jmp_for basicBlock.
jmp_for newFalseBranch: b_while.
jmp_for basicBlock predecessors add: jmp_for basicBlock predecessors removeFirst.


cfg validate.
self insertPiNodes: cfg.

]

{ #category : 'tests' }
DRPiNodesInsertionTest >> testInsertPiNodesEqualsCondition [

Expand Down
4 changes: 2 additions & 2 deletions Druid-Tests/DRPiNodesTest.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ DRPiNodesTest >> insertPiNodes: cfg [
DRPiNodesTest >> optimize: cfg with: optimisations [

self insertPiNodes: cfg.

cfg inspect.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
cfg inspect.

cfg applyOptimisation: ((optimisations collect: [ :o | o new ]) reduce: [ :o1 :o2 | o1 then: o2 ]).

cfg inspect.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
cfg inspect.

cfg applyOptimisation: DRCopyPropagation new.
cfg applyOptimisation: DRDeadCodeElimination new.
]
Expand Down
Loading
Loading