Smalltalk createPackage: 'Compiler-Inlining'! NodeVisitor subclass: #ASTPreInliner instanceVariableNames: '' package: 'Compiler-Inlining'! !ASTPreInliner methodsFor: 'visiting'! visitSendNode: aNode aNode superSend ifFalse: [ (IRSendInliner inlinedSelectors includes: aNode selector) ifTrue: [ aNode shouldBeInlined: true. aNode receiver ifNotNil: [ :receiver | receiver shouldBeAliased: true ] ] ]. ^ super visitSendNode: aNode ! ! IRClosure subclass: #IRInlinedClosure instanceVariableNames: '' package: 'Compiler-Inlining'! !IRInlinedClosure commentStamp! I represent an inlined closure instruction.! !IRInlinedClosure methodsFor: 'testing'! isInlined ^ true ! ! !IRInlinedClosure methodsFor: 'visiting'! acceptDagVisitor: aVisitor aVisitor visitIRInlinedClosure: self ! ! IRSend subclass: #IRInlinedSend instanceVariableNames: '' package: 'Compiler-Inlining'! !IRInlinedSend commentStamp! I am the abstract super class of inlined message send instructions.! !IRInlinedSend methodsFor: 'accessing'! internalVariables "Answer a collection of internal variables required to perform the inlining" ^ #() ! ! !IRInlinedSend methodsFor: 'testing'! isInlined ^ true ! ! !IRInlinedSend methodsFor: 'visiting'! acceptDagVisitor: aVisitor aVisitor visitInlinedSend: self ! ! IRInlinedSend subclass: #IRInlinedIfFalse instanceVariableNames: '' package: 'Compiler-Inlining'! !IRInlinedIfFalse commentStamp! I represent an inlined `#ifFalse:` message send instruction.! !IRInlinedIfFalse methodsFor: 'visiting'! acceptDagVisitor: aVisitor aVisitor visitIRInlinedIfFalse: self ! ! IRInlinedSend subclass: #IRInlinedIfNilIfNotNil instanceVariableNames: '' package: 'Compiler-Inlining'! !IRInlinedIfNilIfNotNil commentStamp! I represent an inlined `#ifNil:ifNotNil:` message send instruction.! !IRInlinedIfNilIfNotNil methodsFor: 'accessing'! internalVariables ^ Array with: self receiverInternalVariable ! receiverInternalVariable ^ IRVariable new variable: (AliasVar new name: self receiverInternalVariableName); yourself. ! receiverInternalVariableName ^ '$receiver' ! ! !IRInlinedIfNilIfNotNil methodsFor: 'visiting'! acceptDagVisitor: aVisitor aVisitor visitIRInlinedIfNilIfNotNil: self ! ! IRInlinedSend subclass: #IRInlinedIfTrue instanceVariableNames: '' package: 'Compiler-Inlining'! !IRInlinedIfTrue commentStamp! I represent an inlined `#ifTrue:` message send instruction.! !IRInlinedIfTrue methodsFor: 'visiting'! acceptDagVisitor: aVisitor aVisitor visitIRInlinedIfTrue: self ! ! IRInlinedSend subclass: #IRInlinedIfTrueIfFalse instanceVariableNames: '' package: 'Compiler-Inlining'! !IRInlinedIfTrueIfFalse commentStamp! I represent an inlined `#ifTrue:ifFalse:` message send instruction.! !IRInlinedIfTrueIfFalse methodsFor: 'visiting'! acceptDagVisitor: aVisitor aVisitor visitIRInlinedIfTrueIfFalse: self ! ! IRBlockSequence subclass: #IRInlinedSequence instanceVariableNames: '' package: 'Compiler-Inlining'! !IRInlinedSequence commentStamp! I represent a (block) sequence inside an inlined closure instruction (instance of `IRInlinedClosure`).! !IRInlinedSequence methodsFor: 'testing'! isInlined ^ true ! ! !IRInlinedSequence methodsFor: 'visiting'! acceptDagVisitor: aVisitor aVisitor visitIRInlinedSequence: self ! ! IRVisitor subclass: #IRInliner instanceVariableNames: '' package: 'Compiler-Inlining'! !IRInliner commentStamp! I visit an IR tree, inlining message sends and block closures. Message selectors that can be inlined are answered by `IRSendInliner >> #inlinedSelectors`! !IRInliner methodsFor: 'factory'! assignmentInliner ^ IRAssignmentInliner new translator: self; yourself ! returnInliner ^ IRReturnInliner new translator: self; yourself ! sendInliner ^ IRSendInliner new translator: self; yourself ! ! !IRInliner methodsFor: 'testing'! shouldInlineAssignment: anIRAssignment ^ anIRAssignment isInlined not and: [ anIRAssignment right isSend and: [ self shouldInlineSend: anIRAssignment right ]] ! shouldInlineReturn: anIRReturn ^ anIRReturn isInlined not and: [ anIRReturn expression isSend and: [ self shouldInlineSend: anIRReturn expression ]] ! shouldInlineSend: anIRSend ^ anIRSend isInlined not and: [ IRSendInliner shouldInline: anIRSend ] ! ! !IRInliner methodsFor: 'visiting'! visitIRAssignment: anIRAssignment ^ (self shouldInlineAssignment: anIRAssignment) ifTrue: [ self assignmentInliner inlineAssignment: anIRAssignment ] ifFalse: [ super visitIRAssignment: anIRAssignment ] ! visitIRNonLocalReturn: anIRNonLocalReturn | localReturn | anIRNonLocalReturn scope canInlineNonLocalReturns ifTrue: [ anIRNonLocalReturn scope methodScope removeNonLocalReturn: anIRNonLocalReturn scope. localReturn := IRReturn new scope: anIRNonLocalReturn scope; yourself. anIRNonLocalReturn dagChildren do: [ :each | localReturn add: each ]. anIRNonLocalReturn replaceWith: localReturn. ^ self visitIRReturn: localReturn ]. ^ super visitIRNonLocalReturn: anIRNonLocalReturn ! visitIRReturn: anIRReturn ^ (self shouldInlineReturn: anIRReturn) ifTrue: [ self returnInliner inlineReturn: anIRReturn ] ifFalse: [ super visitIRReturn: anIRReturn ] ! visitIRSend: anIRSend ^ (self shouldInlineSend: anIRSend) ifTrue: [ self sendInliner inlineSend: anIRSend ] ifFalse: [ super visitIRSend: anIRSend ] ! ! IRJSTranslator subclass: #IRInliningJSTranslator instanceVariableNames: '' package: 'Compiler-Inlining'! !IRInliningJSTranslator commentStamp! I am a specialized JavaScript translator able to write inlined IR instructions to JavaScript stream (`JSStream` instance).! !IRInliningJSTranslator methodsFor: 'visiting'! visitIRInlinedClosure: anIRInlinedClosure self stream nextPutVars: (anIRInlinedClosure tempDeclarations collect: [ :each | each name asVariableName ]). self visitAll: anIRInlinedClosure dagChildren ! visitIRInlinedIfFalse: anIRInlinedIfFalse self stream nextPutIf: [ self stream nextPutAll: '!!$core.assert('. self visit: anIRInlinedIfFalse dagChildren first. self stream nextPutAll: ')' ] then: [ self visit: anIRInlinedIfFalse dagChildren last ] ! visitIRInlinedIfNilIfNotNil: anIRInlinedIfNilIfNotNil self stream nextPutIf: [ | recvVarName | recvVarName := anIRInlinedIfNilIfNotNil receiverInternalVariableName. self stream nextPutAll: '(', recvVarName, ' = '. self visit: anIRInlinedIfNilIfNotNil dagChildren first. self stream nextPutAll: ') == null || ', recvVarName, '.a$nil' ] then: [ self visit: anIRInlinedIfNilIfNotNil dagChildren second ] else: [ self visit: anIRInlinedIfNilIfNotNil dagChildren third ] ! visitIRInlinedIfTrue: anIRInlinedIfTrue self stream nextPutIf: [ self stream nextPutAll: '$core.assert('. self visit: anIRInlinedIfTrue dagChildren first. self stream nextPutAll: ')' ] then: [ self visit: anIRInlinedIfTrue dagChildren last ] ! visitIRInlinedIfTrueIfFalse: anIRInlinedIfTrueIfFalse self stream nextPutIf: [ self stream nextPutAll: '$core.assert('. self visit: anIRInlinedIfTrueIfFalse dagChildren first. self stream nextPutAll: ')' ] then: [ self visit: anIRInlinedIfTrueIfFalse dagChildren second ] else: [ self visit: anIRInlinedIfTrueIfFalse dagChildren third ] ! ! Object subclass: #IRSendInliner instanceVariableNames: 'send translator' package: 'Compiler-Inlining'! !IRSendInliner commentStamp! I inline some message sends and block closure arguments. I heavily rely on #perform: to dispatch inlining methods.! !IRSendInliner methodsFor: 'accessing'! send ^ send ! send: anIRSend send := anIRSend ! translator ^ translator ! translator: anASTTranslator translator := anASTTranslator ! ! !IRSendInliner methodsFor: 'error handling'! inliningError: aString InliningError signal: aString ! ! !IRSendInliner methodsFor: 'factory'! inlinedClosure ^ IRInlinedClosure new ! inlinedSequence ^ IRInlinedSequence new ! ! !IRSendInliner methodsFor: 'inlining'! ifFalse: anIRInstruction ^ self inlinedSend: IRInlinedIfFalse new withBlock: anIRInstruction ! ifFalse: anIRInstruction ifTrue: anotherIRInstruction ^ self perform: #ifTrue:ifFalse: withArguments: { anotherIRInstruction. anIRInstruction } ! ifNil: anIRInstruction ^ self inlinedSend: IRInlinedIfNilIfNotNil new withBlock: anIRInstruction withBlock: (IRClosure new scope: anIRInstruction scope copy; add: (IRBlockSequence new add: self send receiver; yourself); yourself) ! ifNil: anIRInstruction ifNotNil: anotherIRInstruction ^ self inlinedSend: IRInlinedIfNilIfNotNil new withBlock: anIRInstruction withBlock: anotherIRInstruction ! ifNotNil: anIRInstruction ^ self inlinedSend: IRInlinedIfNilIfNotNil new withBlock: (IRClosure new scope: anIRInstruction scope copy; add: (IRBlockSequence new add: self send receiver; yourself); yourself) withBlock: anIRInstruction ! ifNotNil: anIRInstruction ifNil: anotherIRInstruction ^ self inlinedSend: IRInlinedIfNilIfNotNil new withBlock: anotherIRInstruction withBlock: anIRInstruction ! ifTrue: anIRInstruction ^ self inlinedSend: IRInlinedIfTrue new withBlock: anIRInstruction ! ifTrue: anIRInstruction ifFalse: anotherIRInstruction ^ self inlinedSend: IRInlinedIfTrueIfFalse new withBlock: anIRInstruction withBlock: anotherIRInstruction ! inlineClosure: anIRClosure | inlinedClosure sequence statements | inlinedClosure := self inlinedClosure. inlinedClosure scope: anIRClosure scope; parent: anIRClosure parent. "Add the possible temp declarations" anIRClosure tempDeclarations do: [ :each | inlinedClosure add: each ]. "Add a block sequence" sequence := self inlinedSequence. "Map the closure arguments to the receiver of the message send" anIRClosure arguments do: [ :each | inlinedClosure add: (IRTempDeclaration new name: each; yourself). sequence add: (IRAssignment new add: (IRVariable new variable: (AliasVar new scope: inlinedClosure scope; name: each; yourself)); add: (IRVariable new variable: (AliasVar new scope: inlinedClosure scope; name: '$receiver'; yourself)); yourself) ]. "To ensure the correct order of the closure instructions: first the temps then the sequence" inlinedClosure add: sequence. "Get all the statements" statements := anIRClosure sequence dagChildren. statements ifNotEmpty: [ statements allButLast do: [ :each | sequence add: each ]. "Inlined closures change local returns into result value itself" sequence add: statements last asInlinedBlockResult ]. ^ inlinedClosure ! inlineSend: anIRSend self send: anIRSend. ^ self perform: self send selector withArguments: self send arguments ! ! !IRSendInliner methodsFor: 'private'! inlineSend: anIRSend andReplace: anIRInstruction anIRInstruction replaceWith: anIRSend. ^ self inlineSend: anIRSend ! inlinedSend: inlinedSend withBlock: anIRInstruction | inlinedClosure | anIRInstruction isClosure ifFalse: [ self inliningError: 'Message argument should be a block' ]. anIRInstruction arguments size = 0 ifFalse: [ self inliningError: 'Inlined block should have zero argument' ]. inlinedClosure := self translator visit: (self inlineClosure: anIRInstruction). inlinedSend add: self send receiver; add: inlinedClosure. self send replaceWith: inlinedSend. inlinedSend method internalVariables addAll: inlinedSend internalVariables. ^ inlinedSend ! inlinedSend: inlinedSend withBlock: anIRInstruction withBlock: anotherIRInstruction | inlinedClosure1 inlinedClosure2 | anIRInstruction isClosure ifFalse: [ self inliningError: 'Message argument should be a block' ]. anotherIRInstruction isClosure ifFalse: [ self inliningError: 'Message argument should be a block' ]. inlinedClosure1 := self translator visit: (self inlineClosure: anIRInstruction). inlinedClosure2 := self translator visit: (self inlineClosure: anotherIRInstruction). inlinedSend add: self send receiver; add: inlinedClosure1; add: inlinedClosure2. self send replaceWith: inlinedSend. inlinedSend method internalVariables addAll: inlinedSend internalVariables. ^ inlinedSend ! ! !IRSendInliner class methodsFor: 'accessing'! inlinedSelectors ^ #('ifTrue:' 'ifFalse:' 'ifTrue:ifFalse:' 'ifFalse:ifTrue:' 'ifNil:' 'ifNotNil:' 'ifNil:ifNotNil:' 'ifNotNil:ifNil:') ! shouldInline: anIRSend (self inlinedSelectors includes: anIRSend selector) ifFalse: [ ^ false ]. ^ anIRSend arguments allSatisfy: [ :each | each isClosure ] ! ! IRSendInliner subclass: #IRAssignmentInliner instanceVariableNames: 'target' package: 'Compiler-Inlining'! !IRAssignmentInliner commentStamp! I inline message sends together with assignments by moving them around into the inline closure instructions. ##Example foo | a | a := true ifTrue: [ 1 ] Will produce: if($core.assert(true) { a = 1; };! !IRAssignmentInliner methodsFor: 'accessing'! target ^ target ! target: anObject target := anObject ! ! !IRAssignmentInliner methodsFor: 'inlining'! inlineAssignment: anIRAssignment self target: anIRAssignment left. ^ self inlineSend: anIRAssignment right andReplace: anIRAssignment ! inlineClosure: anIRClosure | closure sequence statements | closure := super inlineClosure: anIRClosure. sequence := closure sequence. statements := sequence dagChildren. statements ifNotEmpty: [ | final | final := statements last. final yieldsValue ifTrue: [ sequence replace: final with: (IRAssignment new add: self target; add: final copy; yourself) ] ]. ^ closure ! ! IRSendInliner subclass: #IRReturnInliner instanceVariableNames: '' package: 'Compiler-Inlining'! !IRReturnInliner commentStamp! I inline message sends with inlined closure together with a return instruction.! !IRReturnInliner methodsFor: 'inlining'! inlineClosure: anIRClosure | closure sequence statements | closure := super inlineClosure: anIRClosure. sequence := closure sequence. statements := sequence dagChildren. statements ifNotEmpty: [ | final | final := statements last. final yieldsValue ifTrue: [ sequence replace: final with: (IRReturn new add: final copy; yourself) ] ]. ^ closure ! inlineReturn: anIRReturn ^ self inlineSend: anIRReturn expression andReplace: anIRReturn ! ! CodeGenerator subclass: #InliningCodeGenerator instanceVariableNames: '' package: 'Compiler-Inlining'! !InliningCodeGenerator commentStamp! I am a specialized code generator that uses inlining to produce more optimized JavaScript output! !InliningCodeGenerator methodsFor: 'compiling'! inliner ^ IRInliner new ! irTranslatorClass ^ IRInliningJSTranslator ! preInliner ^ ASTPreInliner new ! transformersDictionary ^ transformersDictionary ifNil: [ transformersDictionary := super transformersDictionary at: '3000-inlinerTagging' put: self preInliner; at: '6000-inliner' put: self inliner; at: '8000-irToJs' put: self irTranslator; yourself ] ! ! SemanticError subclass: #InliningError instanceVariableNames: '' package: 'Compiler-Inlining'! !InliningError commentStamp! Instances of InliningError are signaled when using an `InliningCodeGenerator`in a `Compiler`.! SemanticAnalyzer subclass: #InliningSemanticAnalyzer instanceVariableNames: '' package: 'Compiler-Inlining'! !InliningSemanticAnalyzer methodsFor: 'visiting'! visitSendNode: aNode aNode superSend ifFalse: [ (IRSendInliner inlinedSelectors includes: aNode selector) ifTrue: [ aNode shouldBeInlined: true. aNode receiver ifNotNil: [ :receiver | receiver shouldBeAliased: true ] ] ]. super visitSendNode: aNode ! ! !IRBlockReturn methodsFor: '*Compiler-Inlining'! asInlinedBlockResult ^ self expression ! ! !IRInstruction methodsFor: '*Compiler-Inlining'! asInlinedBlockResult ^ self ! !