diff --git a/Druid-Tests/DRPiABCDDeadBranchEliminationTest.class.st b/Druid-Tests/DRPiABCDDeadBranchEliminationTest.class.st new file mode 100644 index 00000000..a3bb327f --- /dev/null +++ b/Druid-Tests/DRPiABCDDeadBranchEliminationTest.class.st @@ -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. + + 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 [ + + " + /->-|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" + + 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. + +] diff --git a/Druid-Tests/DRPiNodesInsertionTest.class.st b/Druid-Tests/DRPiNodesInsertionTest.class.st index 792d06f8..95d00d6b 100644 --- a/Druid-Tests/DRPiNodesInsertionTest.class.st +++ b/Druid-Tests/DRPiNodesInsertionTest.class.st @@ -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 [ diff --git a/Druid-Tests/DRPiNodesTest.class.st b/Druid-Tests/DRPiNodesTest.class.st index 1ca974b8..864a12ee 100644 --- a/Druid-Tests/DRPiNodesTest.class.st +++ b/Druid-Tests/DRPiNodesTest.class.st @@ -23,9 +23,9 @@ DRPiNodesTest >> insertPiNodes: cfg [ DRPiNodesTest >> optimize: cfg with: optimisations [ self insertPiNodes: cfg. - + cfg inspect. cfg applyOptimisation: ((optimisations collect: [ :o | o new ]) reduce: [ :o1 :o2 | o1 then: o2 ]). - + cfg inspect. cfg applyOptimisation: DRCopyPropagation new. cfg applyOptimisation: DRDeadCodeElimination new. ] diff --git a/Druid/DRABCDConstraintSolver.class.st b/Druid/DRABCDConstraintSolver.class.st new file mode 100644 index 00000000..0166d735 --- /dev/null +++ b/Druid/DRABCDConstraintSolver.class.st @@ -0,0 +1,188 @@ +Class { + #name : 'DRABCDConstraintSolver', + #superclass : 'DRConstraintSolver', + #instVars : [ + 'nodes', + 'edges', + 'worklist', + 'cfg', + 'memo' + ], + #category : 'Druid-IR-Paths', + #package : 'Druid', + #tag : 'IR-Paths' +} + +{ #category : 'instance creation' } +DRABCDConstraintSolver class >> for: aDRControlFlowGraph [ + + ^ self new initializeFor: aDRControlFlowGraph +] + +{ #category : 'building - graph' } +DRABCDConstraintSolver >> buildGraph [ + + | graph | + cfg instructionsDo: [ :i | + i isMandatoryInstruction ifFalse: [ + nodes add: i. + self processValue: i ] ]. + + "To draw it" + "graph := AIGraphWeightedFixtureStructure new. + graph nodes: nodes. + graph edges: edges. + graph + inspect; + halt" +] + +{ #category : 'building - graph' } +DRABCDConstraintSolver >> drawGraph [ + + AIGraphWeightedFixtureStructure new + nodes: nodes; + edges: edges; + inspect. +] + +{ #category : 'initialization' } +DRABCDConstraintSolver >> initializeFor: aDRControlFlowGraph [ + + cfg := aDRControlFlowGraph. + + nodes := Set new. + edges := Set new. + + memo := Dictionary new. + + self buildGraph. + +] + +{ #category : 'testing' } +DRABCDConstraintSolver >> isSatisfiable: aDRPiNode [ + + "TODO - for unsupported constraints, return true + - do we need different ABCD subclasses for upper/lower bounds checks?" + + | shortestPath | + ((aDRPiNode constraint isKindOf: DRGreaterOrEqualsConstraint) or: + (aDRPiNode constraint isKindOf: DREqualsConstraint)) ifFalse: [ ^true. ]. + "TODO podemos hacer por > tambien creo, pidiendo que la distancia sea 1 mas/menos/yoquese" + + shortestPath := AIShortestPathWithMaxAndMinNodes new. + shortestPath nodes: nodes. + shortestPath + edges: edges + from: #first + to: #second + weight: #third. + + shortestPath start: (self piNodesOriginalVariable: aDRPiNode constraint constantValue); + isMaxNodePredicate: [ :n | n isPhiFunction ]; + memoDictionary: memo. + shortestPath run. + + "Distance < 0 means the other side is a tautology => this side is unsatisfiable + >= 0 means this side isn't tautology => this side is satisfiable" + ^ (shortestPath distanceTo: aDRPiNode operand isLessThan: 1) = -1. + +] + +{ #category : 'building - graph' } +DRABCDConstraintSolver >> piNodesOriginalVariable: aDRValue [ + + | val | + + val := aDRValue asDRValue. + [ val isPiNode ] whileTrue: [ val := val operand ]. + + ^ val +] + +{ #category : 'building - graph' } +DRABCDConstraintSolver >> processAdd: aDRValue [ + + | ops | + ops := aDRValue operands. + ops first isNumberValue ifTrue: [ ops := ops reversed ]. + ops second isNumberValue ifFalse: [ ^ nil ]. + + edges add: { + ops first. + aDRValue. + ops second value } +] + +{ #category : 'building - graph' } +DRABCDConstraintSolver >> processCopy: aDRValue [ + "Remember a PiNode is a Copy too" + + edges add: { + aDRValue operand asDRValue. + aDRValue. + 0 }. + + nodes add: aDRValue operand asDRValue. + + aDRValue isPiNode ifTrue: [ self processPiNode: aDRValue ] +] + +{ #category : 'building - graph' } +DRABCDConstraintSolver >> processPhi: aDRPhiFunction [ + + aDRPhiFunction operands do: [ :op | + nodes add: op. + edges add: { + op. + aDRPhiFunction. + 0 } ] +] + +{ #category : 'building - graph' } +DRABCDConstraintSolver >> processPiNode: aDRPiNode [ + + | constraint | + constraint := aDRPiNode constraint. + nodes add: constraint constantValue asDRValue. + + ((constraint isKindOf: DRLessOrEqualsConstraint) or: + (constraint isKindOf: DREqualsConstraint)) ifTrue: [ + ^ edges add: { + constraint constantValue asDRValue. "According to the paper, the edge source should be the pi-node associated to the constraint, + but I don't think there's a difference here, and this is easier" + aDRPiNode. + 0 } ]. + (constraint isKindOf: DRLessConstraint) ifTrue: [ + edges add: { + constraint constantValue asDRValue. "According to the paper, the edge source should be the pi-node associated to the constraint, + but I don't think there's a difference here, and this is easier" + aDRPiNode. + -1 "I'm assuming integers only, not sure if that's right"} ]. +] + +{ #category : 'building - graph' } +DRABCDConstraintSolver >> processSub: aDRValue [ + + | ops | + ops := aDRValue operands. + ops second isNumberValue ifFalse: [ ^ nil ]. + + edges add: { + ops first. + aDRValue. + ops second value negated } +] + +{ #category : 'building - graph' } +DRABCDConstraintSolver >> processValue: aDRValue [ + + aDRValue isAdd ifTrue: [ ^ self processAdd: aDRValue ]. + + aDRValue isSubtract ifTrue: [ ^ self processSub: aDRValue ]. + + aDRValue isCopy ifTrue: [ ^ self processCopy: aDRValue ]. + + aDRValue isPhiFunction ifTrue: [ ^ self processPhi: aDRValue ] +] diff --git a/Druid/DRConstantConstraintSolver.class.st b/Druid/DRConstantConstraintSolver.class.st index 2f95fe97..4ab75057 100644 --- a/Druid/DRConstantConstraintSolver.class.st +++ b/Druid/DRConstantConstraintSolver.class.st @@ -35,16 +35,12 @@ DRConstantConstraintSolver >> collectConstraintsFrom: aDRValue [ ^ DRFullConstraint new ] -{ #category : 'initialization' } -DRConstantConstraintSolver >> initialize [ - - visited := Set new. - -] - { #category : 'testing' } DRConstantConstraintSolver >> isSatisfiable: aDRPiNode [ | collectedConstraint | + + visited := Set new. collectedConstraint := self collectConstraintsFrom: aDRPiNode operand. + ^ (collectedConstraint negated includes: aDRPiNode constraint) not. ] diff --git a/Druid/DRPiABCDDeadBranchElimination.class.st b/Druid/DRPiABCDDeadBranchElimination.class.st new file mode 100644 index 00000000..17acf799 --- /dev/null +++ b/Druid/DRPiABCDDeadBranchElimination.class.st @@ -0,0 +1,14 @@ +Class { + #name : 'DRPiABCDDeadBranchElimination', + #superclass : 'DRPiDeadBranchElimination', + #category : 'Druid-Optimizations', + #package : 'Druid', + #tag : 'Optimizations' +} + +{ #category : 'as yet unclassified' } +DRPiABCDDeadBranchElimination >> applyTo: cfg [ + + solver := DRABCDConstraintSolver for: cfg. + super applyTo: cfg. +] diff --git a/Druid/DRPiConstantDeadBranchElimination.class.st b/Druid/DRPiConstantDeadBranchElimination.class.st index a1ecbc56..867002e1 100644 --- a/Druid/DRPiConstantDeadBranchElimination.class.st +++ b/Druid/DRPiConstantDeadBranchElimination.class.st @@ -6,9 +6,9 @@ Class { #tag : 'Optimizations' } -{ #category : 'testing' } -DRPiConstantDeadBranchElimination >> isReachable: aDRBasicBlock [ +{ #category : 'initialization' } +DRPiConstantDeadBranchElimination >> initialize [ - ^ aDRBasicBlock piNodes allSatisfy: [ :pi | - DRConstantConstraintSolver new isSatisfiable: pi ] + super initialize. + solver := DRConstantConstraintSolver new. ] diff --git a/Druid/DRPiDeadBranchElimination.class.st b/Druid/DRPiDeadBranchElimination.class.st index cbbc9e7a..95615a9c 100644 --- a/Druid/DRPiDeadBranchElimination.class.st +++ b/Druid/DRPiDeadBranchElimination.class.st @@ -3,26 +3,42 @@ Class { #superclass : 'DROptimisation', #instVars : [ 'worklist', - 'visited' + 'visited', + 'solver' ], #category : 'Druid-Optimizations', #package : 'Druid', #tag : 'Optimizations' } +{ #category : 'as yet unclassified' } +DRPiDeadBranchElimination >> addSuccessorsToWorklist: aDRBasicBlock [ + + aDRBasicBlock successors + select: [ :succ | (visited includes: succ) not ] + thenDo: [ :succ | + worklist add: (DREdge withSource: aDRBasicBlock andDestination: succ). + visited add: succ. + ]. +] + { #category : 'accessing' } DRPiDeadBranchElimination >> applyTo: cfg [ | deadEdges | - worklist add: (DREdge - withSource: cfg initialBasicBlock - andDestination: cfg initialBasicBlock). + visited := Set new. + worklist := OrderedCollection with: (DREdge + withSource: cfg initialBasicBlock + andDestination: cfg initialBasicBlock). "The source doesn't matter in the initial edge, it'll be never be dead anyways" deadEdges := self computeDeadeEdges. deadEdges ifNotEmpty: [ self removeUnnecessaryConditionalJumps: deadEdges. + cfg + invalidateDominatorTree; + invalidatePostDominatorTree. cfg applyOptimisation: DRDeadBlockElimination new ] ] @@ -36,24 +52,17 @@ DRPiDeadBranchElimination >> computeDeadeEdges [ | currentEdge | currentEdge := worklist removeFirst. (self isReachable: currentEdge destination) - ifTrue: [ self updateWorklistFrom: currentEdge destination ] + ifTrue: [ self addSuccessorsToWorklist: currentEdge destination ] ifFalse: [ deadEdges add: currentEdge ] ]. "TODO if a block is unreachable, then its sibling must be reachable, so we can remove it from the worklist" ^ deadEdges ] -{ #category : 'initialization' } -DRPiDeadBranchElimination >> initialize [ - - worklist := OrderedCollection new. - visited := Set new. -] - { #category : 'as yet unclassified' } DRPiDeadBranchElimination >> isReachable: aDRBasicBlock [ - self subclassResponsibility + ^ aDRBasicBlock piNodes allSatisfy: [ :pi | solver isSatisfiable: pi ] ] { #category : 'removing' } @@ -61,14 +70,3 @@ DRPiDeadBranchElimination >> removeUnnecessaryConditionalJumps: deadEdges [ deadEdges do: [ :edge | edge kill ]. ] - -{ #category : 'as yet unclassified' } -DRPiDeadBranchElimination >> updateWorklistFrom: aDRBasicBlock [ - - aDRBasicBlock successors - select: [ :succ | (visited includes: succ) not ] - thenDo: [ :succ | - worklist add: (DREdge withSource: aDRBasicBlock andDestination: succ). - visited add: succ. - ]. -] diff --git a/Druid/DRPiNodeBuilder.class.st b/Druid/DRPiNodeBuilder.class.st index a720bac7..0e13b287 100644 --- a/Druid/DRPiNodeBuilder.class.st +++ b/Druid/DRPiNodeBuilder.class.st @@ -70,6 +70,12 @@ DRPiNodeBuilder >> visitGreaterOrEqualsThan: aDRCondition inBranch: aDRBranchIfC self visitComparison: aDRBranchIfCondition withConstraint: DRGreaterOrEqualsConstraint. ] +{ #category : 'visiting' } +DRPiNodeBuilder >> visitGreaterThan: aDRGreaterThanComparison inBranch: aDRBranchIfCondition [ + + self visitComparison: aDRBranchIfCondition withConstraint: DRGreaterConstraint. +] + { #category : 'visiting' } DRPiNodeBuilder >> visitLessOrEqualsThan: aDRCondition inBranch: aDRBranchIfCondition [