InstrumentingCompiler.st
author Claus Gittinger <cg@exept.de>
Tue, 05 Jul 2011 13:36:04 +0200
changeset 2528 e57cc8caf6f9
parent 2391 2bd90c14cd75
child 2569 e9fcb54ac3d4
permissions -rw-r--r--
dont instrument the instrumentation code

"{ Package: 'stx:libcomp' }"

ByteCodeCompiler subclass:#InstrumentingCompiler
	instanceVariableNames:'method methodEntryInfo blockInvocationInfo'
	classVariableNames:''
	poolDictionaries:''
	category:'System-Compiler-Instrumentation'
!

Object subclass:#InstrumentationInfo
	instanceVariableNames:'owningMethod'
	classVariableNames:''
	poolDictionaries:''
	privateIn:InstrumentingCompiler
!

InstrumentingCompiler::InstrumentationInfo subclass:#StatementExecutionInfo
	instanceVariableNames:'startPosition count'
	classVariableNames:''
	poolDictionaries:''
	privateIn:InstrumentingCompiler
!

InstrumentingCompiler::InstrumentationInfo subclass:#MethodInvocationInfo
	instanceVariableNames:'infoPerReceiverClass'
	classVariableNames:''
	poolDictionaries:''
	privateIn:InstrumentingCompiler
!

Object subclass:#MethodInvocationInfoPerReceiverClass
	instanceVariableNames:'infoPerSendingMethod'
	classVariableNames:''
	poolDictionaries:''
	privateIn:InstrumentingCompiler::MethodInvocationInfo
!

Object subclass:#MethodInvocationInfoPerSendingMethod
	instanceVariableNames:'countPerSendersClass invokedViaPerform'
	classVariableNames:''
	poolDictionaries:''
	privateIn:InstrumentingCompiler::MethodInvocationInfo::MethodInvocationInfoPerReceiverClass
!

InstrumentingCompiler::StatementExecutionInfo subclass:#BlockExecutionInfo
	instanceVariableNames:'endPosition'
	classVariableNames:''
	poolDictionaries:''
	privateIn:InstrumentingCompiler
!

!InstrumentingCompiler class methodsFor:'documentation'!

documentation
"
    an experiment - recompile classes using this compiler adds instrumentation
    code. This remembers who calls a method (methodInvocationInfo) and how often
    basic blocks are invoked (blockExecutionInfo).
"
! !

!InstrumentingCompiler class methodsFor:'compilation-public'!

compileClass:aClass
    "compile all methods of aClass with instrumentation"

    aClass methodsDo:[:eachMethod |
        self compileMethod:eachMethod
    ].
!

compileMethod:aMethod
    "compile a single method with instrumentation"

    self 
        compile:(aMethod source)
        forClass:(aMethod mclass) 
        inCategory:(aMethod category)
! !

!InstrumentingCompiler class methodsFor:'special'!

allInstrumentedMethods
    ^ InstrumentedMethod allInstances 

    "
     self allInstrumentedMethods
    "

    "Modified: / 27-04-2010 / 13:37:51 / cg"
!

callersOf:aMethod do:aBlock
    |info|

    info := aMethod methodInvocationInfo.
    info notNil ifTrue:[
        info callingMethodsDo:[:callingMethod |
            aBlock value:callingMethod
        ]
    ]

    "Modified: / 27-04-2010 / 13:35:00 / cg"
!

isInstrumented:aMethod
    ^ aMethod isInstrumented

    "
     self methodInfoOf:aMethod
    "

    "Modified: / 27-04-2010 / 13:35:21 / cg"
!

methodInfoOf:aMethod
    <resource: #obsolete>

    self obsoleteMethodWarning:'please use Method >> methodInvocationInfo'.
    ^ aMethod methodInvocationInfo

    "
     self methodInfoOf:aMethod
    "

    "Modified: / 27-04-2010 / 13:37:31 / cg"
!

methodInvocationInfoOf:aMethod
    self obsoleteMethodWarning:'please use method>>methodInvocationInfo'.
    ^ aMethod methodInvocationInfo

    "
     self methodInfoOf:aMethod
    "

    "Created: / 27-04-2010 / 12:28:56 / cg"
    "Modified: / 27-04-2010 / 13:34:36 / cg"
! !

!InstrumentingCompiler methodsFor:'code generation helpers'!

addBlockCounterTo:aBlockNode
    |countCode blockEntryInfo|

    blockInvocationInfo isNil ifTrue:[
        blockInvocationInfo := OrderedCollection new.
    ].
    blockEntryInfo := BlockExecutionInfo new cleanInfo.
    blockEntryInfo startPosition:aBlockNode startPosition endPosition:aBlockNode endPosition.
    blockInvocationInfo add:blockEntryInfo.

    countCode := 
        StatementNode 
            expression:(MessageNode 
                            receiver:(ConstantNode value:blockEntryInfo)
                            selector:#entry:
                            arg:(VariableNode type:#ThisContext context:contextToEvaluateIn)).

    aBlockNode isEmptyBlock ifTrue:[
        "/ q: are we interested in empty blocks ?
        aBlockNode statements:countCode.
    ] ifFalse:[
        countCode nextStatement:aBlockNode statements.
        aBlockNode statements:countCode.
    ].

    "Modified: / 28-04-2010 / 15:55:30 / cg"
!

addBlockCountersToEachBlockIn:aCollection
    aCollection do:[:eachNode |
        eachNode isBlockNode ifTrue:[
            self addBlockCounterTo:eachNode
        ]
    ].

    "Created: / 27-04-2010 / 11:47:56 / cg"
!

createMethod
    method := super createMethod.

    methodEntryInfo notNil ifTrue:[
        methodEntryInfo owningMethod:method    
    ].
    blockInvocationInfo notNil ifTrue:[
        blockInvocationInfo do:[:eachInfo | eachInfo owningMethod:method]
    ].

    ^ method

    "Modified: / 27-04-2010 / 14:04:50 / cg"
!

methodClass
    ^ InstrumentedMethod

    "Created: / 27-04-2010 / 12:17:22 / cg"
!

statementCounterBefore:aStatementNode
    |countCode statementEntryInfo|

    blockInvocationInfo isNil ifTrue:[
        blockInvocationInfo := OrderedCollection new.
    ].
    statementEntryInfo := StatementExecutionInfo new cleanInfo.
    statementEntryInfo startPosition:aStatementNode startPosition.
    blockInvocationInfo add:statementEntryInfo.

    countCode := 
        StatementNode 
            expression:(MessageNode 
                            receiver:(ConstantNode value:statementEntryInfo)
                            selector:#entry:
                            arg:(VariableNode type:#ThisContext context:contextToEvaluateIn)).

    countCode nextStatement:aStatementNode.
    ^ countCode
! !

!InstrumentingCompiler methodsFor:'code generation-hooks'!

blockNodeRewriteHookFor:aBlockNode
    "/ add a counter for the block
    self addBlockCounterTo:aBlockNode.
    ^ aBlockNode

    "Created: / 28-04-2010 / 14:21:27 / cg"
!

messageNodeRewriteHookFor:aMessageNode
"/ see blockNodeRewriter...

"/    "/ argument could be a constantNode (due to contant-folding optimization)
"/    aMessageNode isConstant ifTrue:[^ aMessageNode].
"/    (
"/        #( 
"/            ifTrue: 
"/            ifFalse:
"/            ifTrue:ifFalse: 
"/            ifFalse:ifTrue: 
"/        )
"/        includes:aMessageNode selector
"/    ) ifTrue:[
"/        "/ add a counter for the block
"/        self addBlockCountersToEachBlockIn:(aMessageNode arguments)
"/    ].
    ^ aMessageNode

    "Created: / 27-04-2010 / 11:43:22 / cg"
    "Modified: / 28-04-2010 / 14:22:05 / cg"
!

startCodeGenerationHookOn:codeStream
    methodEntryInfo := MethodInvocationInfo new.

    (StatementNode 
        expression:(MessageNode 
                        receiver:(ConstantNode value:methodEntryInfo)
                        selector:#entry:
                        arg:(VariableNode type:#ThisContext context:contextToEvaluateIn)))
            codeForSideEffectOn:codeStream inBlock:nil for:self.

    "Modified: / 27-04-2010 / 11:50:48 / cg"
!

statementListRewriteHookFor:firstStatement
    "/ care for blocks which return...
    |thisStatement prevStatement countStatement mustInsertExecutionInfo|

    mustInsertExecutionInfo := false.
    prevStatement := nil.

    thisStatement := firstStatement.
    thisStatement isNil ifTrue:[^ firstStatement].

    [true] whileTrue:[
        mustInsertExecutionInfo ifTrue:[
            "/ prev-stat had a return in it (a block with a return);
            "/ insert a statement-entry here.
            thisStatement notNil ifTrue:[
                countStatement := self statementCounterBefore:thisStatement.
                prevStatement nextStatement:countStatement.
                mustInsertExecutionInfo := false.
                prevStatement := countStatement.
            ]
        ].
        thisStatement isNil ifTrue:[^ firstStatement].

        thisStatement isReturnNode ifFalse:[
            thisStatement containsReturn ifTrue:[
                mustInsertExecutionInfo := true    
            ]
        ].
        prevStatement := thisStatement.
        thisStatement := thisStatement nextStatement.
    ].
! !

!InstrumentingCompiler::InstrumentationInfo class methodsFor:'cleanup'!

allInfosDo:aBlock
    InstrumentedMethod allInstancesDo:[:m |
        m literalsDo:[:l |
            (l isKindOf:self) ifTrue:[
                aBlock value:l.
            ].
        ]
    ].

    "Created: / 27-04-2010 / 12:09:03 / cg"
!

cleanAllInfo
    self allInfosDo:[:l |
        l cleanInfo.
    ].

    "
     InstrumentingCompiler::InstrumentationInfo cleanAllInfo
     InstrumentingCompiler::MethodInvocationInfo cleanAllInfo
    "

    "Modified: / 27-04-2010 / 12:10:07 / cg"
! !

!InstrumentingCompiler::InstrumentationInfo methodsFor:'accessing'!

owningMethod:something
    owningMethod := something.
! !

!InstrumentingCompiler::InstrumentationInfo methodsFor:'instrumentation probe calls'!

entry:callingContext
    "probe entry from instrumented code"

    "Created: / 27-04-2010 / 12:06:48 / cg"
! !

!InstrumentingCompiler::StatementExecutionInfo methodsFor:'accessing'!

callCount
    ^ count

    "Created: / 27-04-2010 / 13:45:15 / cg"
!

characterPosition
    ^ startPosition

    "Created: / 23-06-2006 / 13:31:19 / cg"
    "Modified: / 28-04-2010 / 15:54:24 / cg"
!

characterPosition:something
    startPosition := something.

    "Created: / 23-06-2006 / 13:31:19 / cg"
    "Modified: / 28-04-2010 / 15:54:30 / cg"
!

count
    ^ count

    "Created: / 23-06-2006 / 13:31:28 / cg"
!

startPosition
    ^ startPosition

    "Created: / 28-04-2010 / 15:54:26 / cg"
!

startPosition:something
    startPosition := something.
! !

!InstrumentingCompiler::StatementExecutionInfo methodsFor:'cleanup'!

cleanInfo
    count := 0
! !

!InstrumentingCompiler::StatementExecutionInfo methodsFor:'instrumentation calls'!

entry:callingContext
    "invoked by instrumented code;
     called whenever the block is entered"

    |p|

    count := count + 1.
    count == 1 ifTrue:[
        "/ the very first time, send a change notification
        p := Processor activeProcess.
        (p environmentAt:#inInstrument ifAbsent:false) ifFalse:[
            p withThreadVariable:#inInstrument boundTo:true do:[
                owningMethod changed:#methodInfo
            ].
        ]
    ].

    "Created: / 23-06-2006 / 13:31:16 / cg"
    "Modified: / 05-07-2011 / 13:35:25 / cg"
! !

!InstrumentingCompiler::StatementExecutionInfo methodsFor:'queries'!

hasBeenExecuted
    ^ count > 0

    "Created: / 28-04-2010 / 14:39:46 / cg"
! !

!InstrumentingCompiler::MethodInvocationInfo methodsFor:'accessing'!

callingMethodsDo:aBlock
    infoPerReceiverClass notNil ifTrue:[
        infoPerReceiverClass do:[:each |
            each callingMethodsDo:aBlock
        ]
    ]
!

hasBeenCalled
    ^ infoPerReceiverClass notEmptyOrNil

    "Created: / 27-04-2010 / 13:43:15 / cg"
!

invokedViaPerform
    ^ infoPerReceiverClass contains:[:someInfo | someInfo invokedViaPerform].

    "Created: / 27-04-2010 / 18:21:56 / cg"
!

invokingMethods
    |setOfMethods|

    setOfMethods := IdentitySet new.
    self invokingMethodsDo:[:m | setOfMethods add:m].
    ^ setOfMethods.
!

invokingMethodsDo:aBlock
    infoPerReceiverClass isNil ifTrue:[ ^ self].
    ^ infoPerReceiverClass do:[:eachInfo | eachInfo invokingMethodsDo:aBlock].
!

numberOfInvocations
    infoPerReceiverClass isNil ifTrue:[ ^ 0].
    ^ infoPerReceiverClass inject:0 into:[:sumSoFar :info | sumSoFar + info numberOfInvocations] 
!

numberOfInvocationsFromExternalOf:aClass
    |sum|

    infoPerReceiverClass isNil ifTrue:[ ^ 0].

    sum := 0.
    infoPerReceiverClass keysAndValuesDo:[:recClass :info |
        sum := sum + (info numberOfInvocationsFromExternalOf:aClass)
    ]. 
    ^ sum
!

numberOfInvocationsFromInternalOf:aClass
    |sum|

    infoPerReceiverClass isNil ifTrue:[ ^ 0].

    sum := 0.
    infoPerReceiverClass keysAndValuesDo:[:recClass :info |
        sum := sum + (info numberOfInvocationsFromInternalOf:aClass)
    ]. 
    ^ sum
! !

!InstrumentingCompiler::MethodInvocationInfo methodsFor:'cleanup'!

cleanInfo
    infoPerReceiverClass := nil.
! !

!InstrumentingCompiler::MethodInvocationInfo methodsFor:'instrumentation probe calls'!

entry:aContext
    "invoked by instrumented compiled code, upon method entry"

    |p receiversClass infoPerReceiver|

    p := Processor activeProcess.
    (p environmentAt:#inInstrument ifAbsent:false) ifTrue:[^ self].
    p withThreadVariable:#inInstrument boundTo:true do:[
        receiversClass := aContext receiver class.
        infoPerReceiverClass isNil ifTrue:[
            infoPerReceiverClass := IdentityDictionary new.
        ].
        infoPerReceiver := infoPerReceiverClass 
                            at:receiversClass 
                            ifAbsentPut:[ MethodInvocationInfoPerReceiverClass new ].
        infoPerReceiver entry:aContext
    ]

    "Modified: / 05-07-2011 / 13:35:42 / cg"
! !

!InstrumentingCompiler::MethodInvocationInfo::MethodInvocationInfoPerReceiverClass methodsFor:'accessing'!

callingMethodsDo:aBlock
    infoPerSendingMethod keysAndValuesDo:[:m :eachInfo |
        aBlock value:m
    ]
!

invokedViaPerform
    ^ infoPerSendingMethod contains:[:someInfo | someInfo invokedViaPerform].

    "Created: / 27-04-2010 / 18:21:36 / cg"
!

invokingMethodsDo:aBlock
    infoPerSendingMethod isNil ifTrue:[ ^ self].
    ^ infoPerSendingMethod keysDo:aBlock.
!

numberOfInvocations
    infoPerSendingMethod isNil ifTrue:[ ^ 0].
    ^ infoPerSendingMethod inject:0 into:[:sumSoFar :info | sumSoFar + info numberOfInvocations] 
!

numberOfInvocationsFromExternalOf:aClass
    |sum|

    infoPerSendingMethod isNil ifTrue:[ ^ 0].

    sum := 0.
    infoPerSendingMethod keysAndValuesDo:[:sendingMethod :info |
        |sendingMethodsClass|

        sendingMethodsClass := sendingMethod mclass.
        sendingMethodsClass notNil ifTrue:[
            ((sendingMethodsClass isSubclassOf:aClass) 
            or:[(aClass isSubclassOf:sendingMethodsClass )]) ifFalse:[
                sum := sum + (info numberOfInvocations)
            ]
        ]
    ]. 
    ^ sum
!

numberOfInvocationsFromInternalOf:aClass
    |sum|

    infoPerSendingMethod isNil ifTrue:[ ^ 0].

    sum := 0.
    infoPerSendingMethod keysAndValuesDo:[:sendingMethod :info |
        |sendingMethodsClass|

        sendingMethodsClass := sendingMethod mclass.
        sendingMethodsClass notNil ifTrue:[
            ((sendingMethodsClass isSubclassOf:aClass) 
            or:[(aClass isSubclassOf:sendingMethodsClass)]) ifTrue:[
                sum := sum + (info numberOfInvocations)
            ]
        ]
    ]. 
    ^ sum
! !

!InstrumentingCompiler::MethodInvocationInfo::MethodInvocationInfoPerReceiverClass methodsFor:'instrumentation calls'!

entry:aContext
    "invoked by instrumented compiled code, upon method entry"

    |sender sendingMethod infoPerMethod viaPerform|

    aContext selector == #doesNotUnderstand: ifTrue:[
        sender := aContext sender sender methodHome.
    ] ifFalse:[
        sender := aContext sender methodHome.
    ].
    sender isNil ifTrue:[
        ^ self.
    ] ifFalse:[
        sendingMethod := sender method.
        sendingMethod isNil ifTrue:[^ self].

        viaPerform := false.
        (sendingMethod mclass == Object 
        and:[ sendingMethod selector startsWith:'perform:'] ) ifTrue:[
            "/ Transcript showCR:('%1 [info]: skipping #perform' bindWith:self class nameWithoutPrefix).
            sender := sender sender methodHome.
            sendingMethod := sender method.
            viaPerform := true.
        ].
    ].

    infoPerSendingMethod isNil ifTrue:[
        infoPerSendingMethod := IdentityDictionary new.
    ].
    infoPerMethod := infoPerSendingMethod 
                        at:sendingMethod 
                        ifAbsentPut:[ MethodInvocationInfoPerSendingMethod new ].

    infoPerMethod entry:aContext viaPerform:viaPerform

    "Modified: / 28-04-2010 / 16:09:04 / cg"
! !

!InstrumentingCompiler::MethodInvocationInfo::MethodInvocationInfoPerReceiverClass::MethodInvocationInfoPerSendingMethod methodsFor:'accessing'!

invokedViaPerform
    ^ invokedViaPerform ? false

    "Created: / 27-04-2010 / 18:20:05 / cg"
!

numberOfInvocations
    countPerSendersClass isNil ifTrue:[ ^ 0].
    ^ countPerSendersClass inject:0 into:[:sumSoFar :count | sumSoFar + count] 
! !

!InstrumentingCompiler::MethodInvocationInfo::MethodInvocationInfoPerReceiverClass::MethodInvocationInfoPerSendingMethod methodsFor:'instrumentation calls'!

entry:aContext viaPerform:viaPerformBoolean
    "invoked by instrumented compiled code, upon method entry"

    |sendersClass count|

    sendersClass := aContext sender receiver class.

    countPerSendersClass isNil ifTrue:[
        countPerSendersClass := IdentityDictionary new.
    ].
    count := countPerSendersClass at:sendersClass ifAbsentPut:0.
    countPerSendersClass at:sendersClass put:(count + 1).
    viaPerformBoolean ifTrue:[ invokedViaPerform := true ].

    "Created: / 27-04-2010 / 18:17:27 / cg"
! !

!InstrumentingCompiler::BlockExecutionInfo methodsFor:'accessing'!

endPosition
    ^ endPosition

    "Created: / 28-04-2010 / 15:57:14 / cg"
!

startPosition:startArg endPosition:endArg
    startPosition := startArg.
    endPosition := endArg.

    "Created: / 28-04-2010 / 15:54:47 / cg"
! !

!InstrumentingCompiler class methodsFor:'documentation'!

version_CVS
    ^ '$Header: /cvs/stx/stx/libcomp/InstrumentingCompiler.st,v 1.7 2011-07-05 11:36:04 cg Exp $'
! !