The Trunk: Compiler-eem.353.mcz

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

The Trunk: Compiler-eem.353.mcz

commits-2
Eliot Miranda uploaded a new version of Compiler to project The Trunk:
http://source.squeak.org/trunk/Compiler-eem.353.mcz

==================== Summary ====================

Name: Compiler-eem.353
Author: eem
Time: 25 April 2017, 6:43:55.473745 pm
UUID: 76c9fea6-d855-42cd-aeb5-9cb0753f6f9d
Ancestors: Compiler-ul.352

Fix the order-of-evaluation bug with inlined to:[by:]do: loops.

Fix the decompiler to correctly decompile the new ordering.
Use isFoo methods instead of isMemberOf: in the new decompiler code.
Nuke some obsolete decompilation methods.

Make the postscript re3compile all senders of to:do: or to:by:do:.

=============== Diff against Compiler-ul.352 ===============

Item was changed:
  ----- Method: AssignmentNode>>toDoIncrement: (in category 'initialize-release') -----
  toDoIncrement: var
+ ^(var = variable
+   and: [value isMessageNode]) ifTrue:
+ [value toDoIncrement: var]!
- var = variable ifFalse: [^ nil].
- (value isMemberOf: MessageNode)
- ifTrue: [^ value toDoIncrement: var]
- ifFalse: [^ nil]!

Item was changed:
  InstructionStream subclass: #Decompiler
+ instanceVariableNames: 'constructor method instVars tempVars constTable stack statements lastPc exit caseExits lastJumpPc lastReturnPc limit hasValue blockStackBase numLocalTemps blockStartsToTempVars tempVarCount lastJumpIfPcStack tempReadCounts'
- instanceVariableNames: 'constructor method instVars tempVars constTable stack statements lastPc exit caseExits lastJumpPc lastReturnPc limit hasValue blockStackBase numLocalTemps blockStartsToTempVars tempVarCount lastJumpIfPcStack'
  classVariableNames: 'ArgumentFlag CascadeFlag CaseFlag IfNilFlag'
  poolDictionaries: ''
  category: 'Compiler-Kernel'!
 
  !Decompiler commentStamp: 'nice 2/3/2011 22:54' prior: 0!
  I decompile a method in three phases:
  Reverser: postfix byte codes -> prefix symbolic codes (nodes and atoms)
  Parser: prefix symbolic codes -> node tree (same as the compiler)
  Printer: node tree -> text (done by the nodes)
 
 
  instance vars:
 
  constructor <DecompilerConstructor> an auxiliary knowing how to generate Abstract Syntax Tree (node tree)
  method <CompiledMethod> the method being decompiled
  instVars <Array of: String> the instance variables of the class implementing method
  tempVars <String | (OrderedCollection of: String)> hold the names of temporary variables (if known)
  NOTE: POLYMORPHISM WILL BE RESOLVED IN #initSymbols:
  constTable <Collection of: ParseNode> parse node associated with byte encoded constants (nil true false 0 1 -1 etc...)
  stack <OrderedCollection of: (ParseNode | String | Integer) > multipurpose...
  statements <OrderedCollection of: ParseNode> the statements of the method being decompiled
  lastPc <Integer>
  exit <Integer>
  caseExits <OrderedCollection of: Integer> - stack of exit addresses that have been seen in the branches of caseOf:'s
  lastJumpPc <Integer>
  lastReturnPc <Integer>
  limit <Integer>
  hasValue <Boolean>
  blockStackBase <Integer>
  numLocaltemps <Integer | Symbol> - number of temps local to a block; also a flag indicating decompiling a block
  blockStartsToTempVars <Dictionary key: Integer value: (OrderedCollection of: String)>
  tempVarCount <Integer> number of temp vars used by the method
  lastJumpIfPcStack <OrderedCollection of: Integer> the value of program counter just before the last encountered conditional jumps!

Item was removed:
- ----- Method: Decompiler>>blockScopeRefersOnlyOnceToTemp: (in category 'private') -----
- blockScopeRefersOnlyOnceToTemp: offset
- | nRefs byteCode extension scanner scan |
- scanner := InstructionStream on: method.
- nRefs := 0.
- scan := offset <= 15
- ifTrue:
- [byteCode := 16 + offset.
- [:instr |
-  instr = byteCode ifTrue:
- [nRefs := nRefs + 1].
-  nRefs > 1]]
- ifFalse:
- [extension := 64 + offset.
- [:instr |
-  (instr = 128 and: [scanner followingByte = extension]) ifTrue:
- [nRefs := nRefs + 1].
-   nRefs > 1]].
- self scanBlockScopeFor: pc from: method initialPC to: method endPC with: scan scanner: scanner.
- ^nRefs = 1!

Item was removed:
- ----- Method: Decompiler>>convertToDoLoop (in category 'private') -----
- convertToDoLoop
- "If statements contains the pattern
- var := startExpr.
- [var <= limit] whileTrue: [...statements... var := var + incConst]
- then replace this by
- startExpr to: limit by: incConst do: [:var | ...statements...]"
- | leaveOnStack initStmt toDoStmt limitStmt |
- leaveOnStack := false.
- (stack notEmpty
- and: [stack last isAssignmentNode])
- ifTrue:
- [initStmt := stack last.
- (toDoStmt := statements last toDoFromWhileWithInit: initStmt) ifNil:
- [^self].
- stack removeLast.
- statements removeLast; addLast: toDoStmt.
- leaveOnStack := true]
- ifFalse:
- [statements size < 2 ifTrue:
- [^self].
- initStmt := statements at: statements size-1.
- (toDoStmt := statements last toDoFromWhileWithInit: initStmt) ifNil:
- [^self].
- statements removeLast; removeLast; addLast: toDoStmt].
- initStmt variable scope: -1.  "Flag arg as block temp"
-
- "Attempt further conversion of the pattern
- limitVar := limitExpr.
- startExpr to: limitVar by: incConst do: [:var | ...statements...]
- to
- startExpr to: limitExpr by: incConst do: [:var | ...statements...].
- The complication here is that limitVar := limitExpr's value may be used, in which case it'll
- be statements last, or may not be used, in which case it'll be statements nextToLast."
- statements size < 2 ifTrue:
- [leaveOnStack ifTrue:
- [stack addLast: statements removeLast].
- ^self].
- limitStmt := statements last.
- ((limitStmt isMemberOf: AssignmentNode)
- and: [limitStmt variable isTemp
- and: [limitStmt variable == toDoStmt arguments first]]) ifFalse:
- [limitStmt := statements at: statements size-1.
- ((limitStmt isMemberOf: AssignmentNode)
- and: [limitStmt variable isTemp
- and: [limitStmt variable == toDoStmt arguments first]]) ifFalse:
- [leaveOnStack ifTrue:
- [stack addLast: statements removeLast].
- ^self]].
-
- (self blockScopeRefersOnlyOnceToTemp: limitStmt variable fieldOffset) ifFalse:
- [^self].
- toDoStmt arguments at: 1 put: limitStmt value.
- limitStmt variable scope: -2.  "Flag limit var so it won't print"
- statements last == limitStmt
- ifTrue: [statements removeLast]
- ifFalse: [statements removeLast; removeLast; addLast: toDoStmt]!

Item was added:
+ ----- Method: Decompiler>>convertToDoLoop: (in category 'private') -----
+ convertToDoLoop: blockBodyTempCounts
+ "If statements contains the pattern
+ var := startExpr.
+ [var <= limit] whileTrue: [...statements... var := var + incConst]
+ or
+ var := startExpr.
+ limit := limitExpr.
+ [var <= limit] whileTrue: [...statements... var := var + incConst]
+ then replace this by
+ startExpr to: limit by: incConst do: [:var | ...statements...]
+ and answer true."
+ | whileStmt incrStmt initStmt limitStmt toDoStmt |
+ whileStmt := statements last.
+ incrStmt := whileStmt arguments first statements last.
+ incrStmt isAssignmentNode ifFalse:
+ [^false].
+ (self startAndLimitFor: incrStmt variable from: stack into:
+ [:startExpr :limitExpr| initStmt := startExpr. limitStmt := limitExpr])
+ ifTrue:
+ [| limitInStatements |
+ limitInStatements := limitStmt isNil
+    and: [statements size > 1
+    and: [self startAndLimitFor: incrStmt variable from: { stack last. (statements last: 2) first } into:
+ [:startExpr :limitExpr| limitStmt := limitExpr]]].
+ (toDoStmt := statements last toDoFromWhileWithCounts: blockBodyTempCounts init: initStmt limit: limitStmt) ifNil:
+ [^false].
+ limitInStatements
+ ifTrue:
+ [stack
+ removeLast;
+ addLast: toDoStmt.
+ statements removeLast: 2]
+ ifFalse:
+ [stack
+ removeLast: (limitStmt ifNil: [1] ifNotNil: [2]);
+ addLast: toDoStmt.
+ statements removeLast]]
+ ifFalse:
+ [(self startAndLimitFor: incrStmt variable from: statements allButLast into:
+ [:startExpr :limitExpr| initStmt := startExpr. limitStmt := limitExpr]) ifFalse:
+ [^false].
+ (toDoStmt := statements last toDoFromWhileWithCounts: blockBodyTempCounts init: initStmt limit: limitStmt) ifNil:
+ [^false].
+ statements
+ removeLast: (limitStmt ifNil: [2] ifNotNil: [3]);
+ addLast: toDoStmt].
+ self markTemp: initStmt variable asOutOfScope: -1. "Flag arg as out of scope"
+ initStmt variable beBlockArg.
+ limitStmt ifNotNil:
+ [self markTemp: limitStmt variable asOutOfScope: -2.
+ toDoStmt arguments at: 1 put: limitStmt value]. "Flag limit as hidden"
+ ^true!

Item was changed:
  ----- Method: Decompiler>>initSymbols: (in category 'initialize-release') -----
  initSymbols: aClass
  constructor method: method class: aClass literals: method literals.
  constTable := constructor codeConstants.
  instVars := Array new: aClass instSize.
  tempVarCount := method numTemps.
  "(tempVars isNil
  and: [method holdsTempNames]) ifTrue:
  [tempVars := method tempNamesString]."
  tempVars isString
  ifTrue:
  [blockStartsToTempVars := self mapFromBlockStartsIn: method
  toTempVarsFrom: tempVars
  constructor: constructor.
  tempVars := blockStartsToTempVars at: method initialPC]
  ifFalse:
  [| namedTemps |
  namedTemps := tempVars ifNil: [(1 to: tempVarCount) collect: [:i| 't', i printString]].
  tempVars := (1 to: tempVarCount) collect:
  [:i | i <= namedTemps size
  ifTrue: [constructor codeTemp: i - 1 named: (namedTemps at: i)]
  ifFalse: [constructor codeTemp: i - 1]]].
  1 to: method numArgs do:
  [:i|
+ (tempVars at: i) beMethodArg].
+ tempReadCounts := Dictionary new!
- (tempVars at: i) beMethodArg]!

Item was changed:
  ----- Method: Decompiler>>jump:if: (in category 'instruction decoding') -----
  jump: dist if: condition
 
  | savePc sign elsePc elseStart end cond ifExpr thenBlock elseBlock
+  thenJump elseJump condHasValue isIfNil saveStack |
-  thenJump elseJump condHasValue isIfNil saveStack blockBody blockArgs |
  lastJumpIfPcStack addLast: lastPc.
  stack last == CascadeFlag ifTrue: [^ [self case: dist] ensure: [lastJumpIfPcStack removeLast]].
  elsePc := lastPc.
  elseStart := pc + dist.
  end := limit.
  "Check for bfp-jmp to invert condition.
  Don't be fooled by a loop with a null body."
  sign := condition.
  savePc := pc.
  self interpretJump ifNotNil:
  [:elseDist|
  (elseDist >= 0 and: [elseStart = pc]) ifTrue:
  [sign := sign not.  elseStart := pc + elseDist]].
  pc := savePc.
  ifExpr := stack removeLast.
  (isIfNil := stack size > 0 and: [stack last == IfNilFlag]) ifTrue:
  [stack removeLast].
  saveStack := stack.
  stack := OrderedCollection new.
  thenBlock := self blockTo: elseStart.
  condHasValue := hasValue or: [isIfNil].
  "ensure jump is within block (in case thenExpr returns)"
  thenJump := exit <= end ifTrue: [exit] ifFalse: [elseStart].
  "if jump goes back, then it's a loop"
  thenJump < elseStart
  ifTrue:
+ [| blockBody blockArgs savedReadCounts blockBodyReadCounts selector |
+ "Must be a while loop...
- ["Must be a while loop...
   thenJump will jump to the beginning of the while expr.  In the case of while's
   with a block in the condition, the while expr should include more than just
+  the last expression: find all the statements needed by searching for the node
+  with the relevant pc."
-  the last expression: find all the statements needed by re-decompiling."
  stack := saveStack.
+ savedReadCounts := tempReadCounts copy.
  pc := thenJump.
  blockBody := self statementsTo: elsePc.
+ blockBodyReadCounts := tempReadCounts.
+ savedReadCounts keysAndValuesDo:
+ [:temp :count|
+ blockBodyReadCounts at: temp put: (blockBodyReadCounts at: temp) - count].
+ tempReadCounts := savedReadCounts.
  "discard unwanted statements from block"
  blockBody size - 1 timesRepeat: [statements removeLast].
  blockArgs := thenBlock statements = constructor codeEmptyBlock statements
  ifTrue: [#()]
  ifFalse: [{ thenBlock }].
+ selector := blockArgs isEmpty
+ ifTrue: [sign ifTrue: [#whileFalse] ifFalse: [#whileTrue]]
+ ifFalse: [sign ifTrue: [#whileFalse:] ifFalse: [#whileTrue:]].
  statements addLast:
  (constructor
  codeMessage: (constructor codeBlock: blockBody returns: false)
+ selector: (constructor codeSelector: selector code: #macro)
- selector: (constructor
- codeSelector: (blockArgs isEmpty
- ifTrue:
- [sign
- ifTrue: [#whileFalse]
- ifFalse: [#whileTrue]]
- ifFalse:
- [sign
- ifTrue: [#whileFalse:]
- ifFalse: [#whileTrue:]])
- code: #macro)
  arguments: blockArgs).
  pc := elseStart.
+ selector == #whileTrue: ifTrue:
+ [self convertToDoLoop: blockBodyReadCounts]]
- self convertToDoLoop]
  ifFalse:
  ["Must be a conditional..."
  elseBlock := self blockTo: thenJump.
  elseJump := exit.
  "if elseJump is backwards, it is not part of the elseExpr"
  elseJump < elsePc ifTrue:
  [pc := lastPc].
  cond := isIfNil
  ifTrue:
  [constructor
  codeMessage: ifExpr ifNilReceiver
  selector: (constructor
  codeSelector: (sign ifTrue: [#ifNotNil:] ifFalse: [#ifNil:])
  code: #macro)
  arguments: (Array with: thenBlock)]
  ifFalse:
  [constructor
  codeMessage: ifExpr
  selector: (constructor codeSelector: #ifTrue:ifFalse: code: #macro)
  arguments: (sign
  ifTrue: [{elseBlock. thenBlock}]
  ifFalse: [{thenBlock. elseBlock}])].
  stack := saveStack.
  condHasValue
  ifTrue: [stack addLast: cond]
  ifFalse: [statements addLast: cond]].
  lastJumpIfPcStack removeLast.!

Item was added:
+ ----- Method: Decompiler>>markTemp:asOutOfScope: (in category 'private') -----
+ markTemp: tempVarNode asOutOfScope: scopeFlag
+ tempVarNode scope: scopeFlag.
+ tempReadCounts removeKey: tempVarNode ifAbsent: []!

Item was changed:
  ----- Method: Decompiler>>popIntoTemporaryVariable: (in category 'instruction decoding') -----
  popIntoTemporaryVariable: offset
  | maybeTVTag tempVector start |
  maybeTVTag := stack last.
  ((maybeTVTag isMemberOf: Association)
  and: [maybeTVTag key == #pushNewArray]) ifTrue:
  [blockStartsToTempVars notNil "implies we were intialized with temp names."
  ifTrue: "Use the provided temps"
  [self assert: ((tempVector := tempVars at: offset + 1 ifAbsent: [ParseNode basicNew]) isTemp
  and: [tempVector isIndirectTempVector
  and: [tempVector remoteTemps size = maybeTVTag value size]])]
  ifFalse: "Synthesize some remote temps"
  [tempVector := maybeTVTag value.
  offset + 1 <= tempVars size
  ifTrue:
  [start := 2.
  tempVector at: 1 put: (tempVars at: offset + 1)]
  ifFalse:
  [tempVars := (Array new: offset + 1)
  replaceFrom: 1
  to: tempVars size
  with: tempVars.
  start := 1].
  start to: tempVector size do:
  [:i|
  tempVector
  at: i
  put: (constructor
  codeTemp: numLocalTemps + offset + i - 1
  named: 't', (tempVarCount + i) printString)].
  tempVars at: offset + 1 put: (constructor codeRemoteTemp: offset + 1 remoteTemps: tempVector)].
  tempVarCount := tempVarCount + maybeTVTag value size.
  stack removeLast.
  ^self].
+ stack addLast: (offset >= tempVars size
+ ifTrue: "Handle the case of chained LiteralVariableBinding assigments"
+ [stack at: (offset + 1 - tempVars size)]
+ ifFalse: "A regular argument or temporary"
+ [tempVars at: offset + 1]).
+ self doStore: statements!
- self pushTemporaryVariable: offset; doStore: statements!

Item was changed:
  ----- Method: Decompiler>>pushTemporaryVariable: (in category 'instruction decoding') -----
  pushTemporaryVariable: offset
+ | node |
+ offset >= tempVars size
+ ifTrue: "Handle the case of chained LiteralVariableBinding assigments"
+ [self halt.
+ node := stack at: offset + 1 - tempVars size]
+ ifFalse: "A regular argument or temporary"
+ [node := tempVars at: offset + 1.
+ node isArg ifFalse: "count temp reads for the whileTrue: => to:do: transformation."
+ [tempReadCounts at: node put: (tempReadCounts at: node ifAbsent: [0]) + 1]].
+ stack addLast: node!
-
- stack addLast: (offset >= tempVars size
- ifTrue:
- ["Handle the case of chained LiteralVariableBinding assigments"
- stack at: (offset + 1 - tempVars size)]
- ifFalse:
- ["A regular argument or temporary"
- tempVars at: offset + 1])!

Item was removed:
- ----- Method: Decompiler>>scanBlockScopeFor:from:to:with:scanner: (in category 'private') -----
- scanBlockScopeFor: refpc from: startpc to: endpc with: scan scanner: scanner
- | bsl maybeBlockSize |
- bsl := BlockStartLocator new.
- scanner pc: startpc.
- [scanner pc <= endpc] whileTrue:
- [refpc = scanner pc ifTrue:
- [scanner pc: startpc.
- [scanner pc <= endpc] whileTrue:
- [(scan value: scanner firstByte) ifTrue:
- [^endpc].
- (maybeBlockSize := scanner interpretNextInstructionFor: bsl) isInteger ifTrue:
- [scanner pc: scanner pc + maybeBlockSize]].
-   ^self].
- (maybeBlockSize := scanner interpretNextInstructionFor: bsl) isInteger ifTrue:
- [refpc <= (scanner pc + maybeBlockSize)
- ifTrue: [^self scanBlockScopeFor: refpc from: scanner pc to: scanner pc + maybeBlockSize with: scan scanner: scanner]
- ifFalse: [scanner pc: scanner pc + maybeBlockSize]]]!

Item was added:
+ ----- Method: Decompiler>>startAndLimitFor:from:into: (in category 'private') -----
+ startAndLimitFor: incrVar from: aStack into: binaryBlock
+ "If incrVar matches the increment of a whileLoop at the end of statements
+ evaluate binaryBlock with the init statement for incrVar and the init statement
+ for the block's limit, if any, and answer true.  Otherwise answer false.  Used to
+ help convert whileTrue: loops into to:[by:]do: loops."
+ | guard initExpr limitInit size |
+ ((size := aStack size) >= 1
+ and: [(initExpr := aStack at: size) isAssignmentNode]) ifFalse:
+ [^false].
+ initExpr variable == incrVar ifTrue:
+ [binaryBlock value: initExpr value: nil.
+ ^true].
+ limitInit := initExpr.
+ (size >= 2
+ and: [(initExpr := aStack at: size - 1) isAssignmentNode
+ and: [initExpr variable == incrVar
+ and: [(guard := statements last receiver) isBlockNode
+ and: [guard statements size = 1
+ and: [(guard := guard statements first) isMessageNode
+ and: [guard receiver == incrVar
+ and: [guard arguments first == limitInit variable]]]]]]]) ifTrue:
+ [binaryBlock value: initExpr value: limitInit.
+ ^true].
+ ^false!

Item was changed:
  ----- Method: Decompiler>>statementsTo: (in category 'control') -----
  statementsTo: end
  "Decompile the method from pc up to end and return an array of
  expressions. If at run time this block will leave a value on the stack,
  set hasValue to true. If the block ends with a jump or return, set exit
  to the destination of the jump, or the end of the method; otherwise, set
  exit = end. Leave pc = end."
 
+ | encoderClass blockPos stackPos |
+ encoderClass := method encoderClass.
- | blockPos stackPos t |
  blockPos := statements size.
  stackPos := stack size.
  [pc < end]
  whileTrue:
  [lastPc := pc.  limit := end.  "for performs"
+ "If you want instrumentation replace the following statement with this one,
+  and edit the implementation:
+ self interpretNextInstructionFor: self"
+ encoderClass interpretNextInstructionFor: self in: self].
- self interpretNextInstructionFor: self].
  "If there is an additional item on the stack, it will be the value
  of this block."
  (hasValue := stack size > stackPos)
  ifTrue:
  [statements addLast: stack removeLast].
  lastJumpPc = lastPc ifFalse: [exit := pc].
  ^self popTo: blockPos!

Item was changed:
  ----- Method: Decompiler>>storeIntoTemporaryVariable: (in category 'instruction decoding') -----
  storeIntoTemporaryVariable: offset
+ stack addLast: (offset >= tempVars size
+ ifTrue: "Handle the case of chained LiteralVariableBinding assigments"
+ [stack at: (offset + 1 - tempVars size)]
+ ifFalse: "A regular argument or temporary"
+ [tempVars at: offset + 1]).
+ self doStore: stack!
-
- self pushTemporaryVariable: offset; doStore: stack!

Item was removed:
- ----- Method: DecompilerConstructor>>codeArguments:block: (in category 'constructor') -----
- codeArguments: args block: block
-
- ^block arguments: args!

Item was changed:
  ----- Method: MessageNode>>emitCodeForToDo:encoder:value: (in category 'code generation') -----
  emitCodeForToDo: stack encoder: encoder value: forValue
  " var := rcvr. L1: [var <= arg1] Bfp(L2) [block body. var := var + inc] Jmp(L1) L2: "
  | loopSize initStmt limitInit test block incStmt blockSize |
  initStmt := arguments at: 4.
  limitInit := arguments at: 7.
  test := arguments at: 5.
  block := arguments at: 3.
  incStmt := arguments at: 6.
  blockSize := sizes at: 1.
  loopSize := sizes at: 2.
- limitInit == nil
- ifFalse: [limitInit emitCodeForEffect: stack encoder: encoder].
 
  "This will return the receiver of to:do: which is the initial value of the loop"
  forValue
+ ifTrue: [initStmt emitCodeForValue: stack encoder: encoder]
- ifTrue: [initStmt emitCodeForValue: stack encoder: encoder.]
  ifFalse: [initStmt emitCodeForEffect: stack encoder: encoder].
+ limitInit ifNotNil:
+ [limitInit emitCodeForEffect: stack encoder: encoder].
  test emitCodeForValue: stack encoder: encoder.
  self emitCodeForBranchOn: false dist: blockSize pop: stack encoder: encoder.
  pc := encoder methodStreamPosition.
  block emitCodeForEvaluatedEffect: stack encoder: encoder.
  incStmt emitCodeForEffect: stack encoder: encoder.
+ self emitCodeForJump: 0 - loopSize encoder: encoder!
- self emitCodeForJump: 0 - loopSize encoder: encoder.!

Item was added:
+ ----- Method: MessageNode>>toDoFromWhileWithCounts:init:limit: (in category 'decompiling') -----
+ toDoFromWhileWithCounts: blockBodyTempCounts init: incrInit limit: limitInitOrNil
+ "If the receiver, a whileTrue: loop, represents a to:[by:]do: loop
+ then answer the replacement to:[by:]do:, otherwise answer nil."
+ | variable increment limit toDoBlock body test |
+ self assert: (selector key == #whileTrue:
+ and: [incrInit isAssignmentNode]).
+ (limitInitOrNil notNil "limit should not be referenced within the loop"
+  and: [(blockBodyTempCounts at: limitInitOrNil variable ifAbsent: [0]) ~= 1]) ifTrue:
+ [^nil].
+ body := arguments last statements.
+ (variable := incrInit variable) isTemp ifFalse:
+ [^nil].
+ (increment := body last toDoIncrement: variable) ifNil:
+ [^nil].
+ receiver statements size ~= 1 ifTrue:
+ [^nil].
+ test := receiver statements first.
+ "Note: test should really be checked that <= or >= comparison
+ jibes with the sign of the (constant) increment"
+ (test isMessageNode
+ and: [(limit := test toDoLimit: variable) notNil]) ifFalse:
+ [^nil].
+ "The block must not overwrite the limit"
+ (limit isVariableNode and: [body anySatisfy: [:e | e isAssignmentNode and: [e variable = limit]]]) ifTrue:
+ [^nil].
+ toDoBlock := BlockNode statements: body allButLast returns: false.
+ toDoBlock arguments: {variable}.
+ ^MessageNode new
+ receiver: incrInit value
+ selector: (SelectorNode new key: #to:by:do: code: #macro)
+ arguments: (Array with: limit with: increment with: toDoBlock)
+ precedence: precedence!

Item was removed:
- ----- Method: MessageNode>>toDoFromWhileWithInit: (in category 'macro transformations') -----
- toDoFromWhileWithInit: initStmt
- "Return nil, or a to:do: expression equivalent to this whileTrue:"
- | variable increment limit toDoBlock body test |
- (selector key == #whileTrue:
- and: [initStmt isAssignmentNode
- and: [initStmt variable isTemp]]) ifFalse:
- [^nil].
- body := arguments last statements.
- variable := initStmt variable.
- increment := body last toDoIncrement: variable.
- (increment == nil
- or: [receiver statements size ~= 1]) ifTrue:
- [^nil].
- test := receiver statements first.
- "Note: test chould really be checked that <= or >= comparison
- jibes with the sign of the (constant) increment"
- (test isMessageNode
- and: [(limit := test toDoLimit: variable) notNil]) ifFalse:
- [^nil].
- "The block must not overwrite the limit"
- (limit isVariableNode and: [body anySatisfy: [:e | e isAssignmentNode and: [e variable = limit]]])
- ifTrue: [^nil].
- toDoBlock := BlockNode statements: body allButLast returns: false.
- toDoBlock arguments: (Array with: variable).
- variable scope: -1.
- variable beBlockArg.
- ^MessageNode new
- receiver: initStmt value
- selector: (SelectorNode new key: #to:by:do: code: #macro)
- arguments: (Array with: limit with: increment with: toDoBlock)
- precedence: precedence!

Item was added:
+ ----- Method: MessageNode>>toDoFromWhileWithInit:withLimit: (in category 'decompiling') -----
+ toDoFromWhileWithInit: incrInit withLimit: limitInitOrNil
+ "If the receiver, a whileTrue: loop, represents a to:[by:]do: loop
+ then answer the replacement to:[by:]do:, otherwise answer nil."
+ | variable increment limit toDoBlock body test |
+ self assert: (selector key == #whileTrue:
+ and: [incrInit isAssignmentNode]).
+ body := arguments last statements.
+ (variable := incrInit variable) isTemp ifFalse:
+ [^nil].
+ (increment := body last toDoIncrement: variable) ifNil:
+ [^nil].
+ receiver statements size ~= 1 ifTrue:
+ [^nil].
+ test := receiver statements first.
+ "Note: test should really be checked that <= or >= comparison
+ jibes with the sign of the (constant) increment"
+ (test isMessageNode
+ and: [(limit := test toDoLimit: variable) notNil]) ifFalse:
+ [^nil].
+ "The block must not overwrite the limit"
+ (limit isVariableNode and: [body anySatisfy: [:e | e isAssignmentNode and: [e variable = limit]]]) ifTrue:
+ [^nil].
+ toDoBlock := BlockNode statements: body allButLast returns: false.
+ toDoBlock arguments: {variable}.
+ ^MessageNode new
+ receiver: incrInit value
+ selector: (SelectorNode new key: #to:by:do: code: #macro)
+ arguments: (Array with: limit with: increment with: toDoBlock)
+ precedence: precedence!

Item was changed:
  ----- Method: MessageNode>>toDoIncrement: (in category 'testing') -----
  toDoIncrement: variable
+ ^(receiver = variable
+   and: [selector key = #+
+   and: [arguments first isConstantNumber]]) ifTrue:
+ [arguments first]!
- (receiver = variable and: [selector key = #+])
- ifFalse: [^ nil].
- arguments first isConstantNumber
- ifTrue: [^ arguments first]
- ifFalse: [^ nil]!

Item was changed:
  ----- Method: MessageNode>>toDoLimit: (in category 'testing') -----
  toDoLimit: variable
+ ^(receiver = variable
+   and: [selector key = #<= or: [selector key = #>=]]) ifTrue:
+ [arguments first]!
- (receiver = variable and: [selector key = #<= or: [selector key = #>=]])
- ifTrue: [^ arguments first]
- ifFalse: [^ nil]!

Item was changed:
+ (PackageInfo named: 'Compiler') postscript: '"below, add code to be run after the loading of this package"
+ "Make sure all methods using to:do: and to:by:do: are recompiled"
- (PackageInfo named: 'Compiler') postscript: '"Make sure all affected methods are recompiled"
  UIManager default
+ informUser: ''Recompiling methods sending to:do: and to:by:do:''
- informUser: ''Recompiling affected methods''
  during:
  [(self systemNavigation allMethodsSelect:
+ [:m|
+ #(to:do: to:by:do:) anySatisfy: [:l| m refersToLiteral: l]]) do:
+ [:mr| mr actualClass recompile: mr selector]]'!
- [:m| | ebc | "All affected methods send one of these optimized selectors..."
- (#(to:do: to:by:do: ifNotNil: ifNil:ifNotNil: ifNotNil:ifNil:) anySatisfy: [:l| m refersToLiteral: l])
- "but the textDomain properties confuse method comparison below..."
- and: [(m propertyValueAt: #textDomain ifAbsent: nil) isNil
- and: [m numTemps > m numArgs "and have non-argument temporaries in them..."
-  or: [(ebc := m embeddedBlockClosures) notEmpty
- and: [ebc anySatisfy: [:bc| bc numTemps > bc numArgs]]]]]]) do:
- [:mr| | old new |
- old := mr compiledMethod.
- "do a test recompile of the method..."
- new := (mr actualClass compile: old getSource asString notifying: nil trailer: old trailer ifFail: nil) method.
- "and if it changed, report it to the transcript and really recompile it..."
- old ~= new ifTrue:
- [Transcript cr. old printReferenceOn: Transcript. Transcript flush.
- mr actualClass recompile: old selector]]]'!