• No results found

G EPPETTO 2: Examples

We discuss three examples: implementation of code coverage, realizing method wrappers and a full meta-class based MOP.

5.4.1 Code Coverage

In Section 4.4.1 we discussed how sub-method reflection is useful for tool-building. We illustrated this with an example implementation of a simple code-coverage tool using TREENURSE. Now we show how we can achieve the same thing with partial behavioral reflection. It is possible as we can reify the node that a link is installed on as the meta-object as shown in Figure 5.4.

source code (AST) instruction is meta-object links

Figure 5.4: Nodes as Meta-objects

For code-coverage, we define a link that callsmarkExecutedon the node where it is installed:

link := GPLink new metaObject: #node;

selector: #markExecuted.

The methodmarkExecutedannotates the node:

Node>>markExecuted

(self annotationAt: ExecutedAnnotation key) increment

When we install the link on the node representing methods, we can see coverage at a method level: all methods tagged have been executed. But with our sub-method model, we can go a level deeper and install the link for example on all blocks or even on all nodes of the tree.

To speed up execution, it is even possible to remove the tagging-link at runtime just after tagging the node. In this way, the method would, at the next execution, be recompiled to only callmarkExecutedon those nodes that have not yet been executed before. In addition, we can leverage the advanced control that the link provides to only activate in special cases, for example to only tag a node when executed, for example, when code is executed due to the unit-test framework.

5.4.2 Method Wrappers

Method wrappers [24] are a common technique to implement behavioral reflection. They have been used for dynamic analysis, to realize dynamic Aspect Oriented Programming (AOP) [77] and Context Oriented Programming (COP) [78]. We used method wrappers already as an application to validate our BYTESURGEONframework in Section 3.2.5.

Method wrappers define before and after code to be called around the original method execution. For this, the original method is replaced by a stub-method sending the message#valueWithReceiver:arguments: to the wrapper, which in turn calls the before and after method:

MethodWrapper>>valueWithReceiver: anObject arguments: args self beforeMethod.

^ [clientMethod valueWithReceiver: anObject arguments: args]

ensure: [self afterMethod]

Users of method wrappers implement a subclass of theMethodWrapper class and provide their own before and after methods.

Instead of generating a method that forwards to #valueWithRe-ceiver:arguments:, when realizing method wrappers with GEPPETTO, we can define links that call the before and after methods and install these links on the original method:

GPMethodWrapper>>install

(self class includesSelector:: #beforeMethod) ifTrue: [ beforeLink := GPLink new metaObject: self;

selector: #beforeMethod;

control: #before.

self methodNode link: beforeLink].

(self class includesSelector:: #afterMethod) ifTrue: [ afterLink := GPLink new metaObject: self;

selector: #afterMethod;

control: #after.

self methodNode link: afterLink].

We only install a link when there is a before/after method defined in the wrapper. This is especially important for the after method, as we wrap the complete method in an exception handler to make sure that the after part is executed in any case.

The resulting code is more efficient than the original MethodWrapper implementation, as the call to the before and after methods are inlined in the wrapped method. The code is only slightly less efficient then the solution based on BYTESURGEONpresented in Section 3.2.5. With BYTE -SURGEON, we inline the code of the before/after methods into the wrapped

Method Wrapper Installation Runtime implementation time (ms) factor time (ms) factor

Hancoded - - 10161 1

Standard 1102 1.0 28443 2.8

BYTESURGEON 13835 12.55 10305 1.01

GEPPETTO2 3354 3.04 10917 1.07

Figure 5.5: Comparing installation and runtime performance of method wrapper implementations.

methods, whereas with GEPPETTOwe inline calls to these methods.

Benchmarks for MethodWrappers

To show the performance of our new solution, we repeat the benchmark as presented in Section 3.2.5. Figure 5.5 shows the result. Our new im-plementation is faster by a factor of four compared to the BYTESURGEON

implementation while the execution speed remains practically the same.

5.4.3 Meta-class MOP

In an MOP like MetaClassTalk [17], the class is the behavioral meta-object. It defines the semantics of message sends, for example.

To realize a meta-class based behavioral MOP with GEPPETTO, we need to reify message sends and let the class object do the message send instead of letting it be executed by the virtual machine. As classes are objects, they have a meta-class that defines this behavior for the class.

We thus define a link where the meta-object is the class of the object the link is installed in. The link calls the methodsend:to:with:and provides it with all information needed to reflectively do the send:

link := GPLink new metaObject: #class;

selector: #send:to:with:

arguments: #(selector object arguments)

The classBehaviorprovides a default implementation forsend:to:with:that reflectively does the message send:

Behavior>>send: selector to: receiver with: arguments

^receiver perform: selector withArguments: arguments.

We can now override this method in the specific meta-class to change the semantics of message sends. For instance variable, we have to provide

two links: one for instance-variable read access, one for assignments.

The link for reading an instance variable is defined like this:

link := GPLink new metaObject: #class;

selector: #iVarAt:In:

arguments: #(offset object)

The other link for instance variable assignments:

link := GPLink new metaObject: #class;

selector: #iVarAt:In:put:

arguments: #(offset object newvalue)

Both the methods iVarAt:In: and iVarAt:In:Put: are provided as default method in classBehavior, we can override these methods on a concrete meta-class, for example to make the state of all objects of this class persistent.

Optimized links for sends. There are two optimizations possible: we should generate specific links for message sends with up to four arguments.

A link for the case of zero arguments:

link := GPLink new metaObject: #class;

selector: #send:to:

arguments: #(selector object)

Specializing links for message sends helps to reduce the number of object allocations at runtime. In this case we do not need to create an array for the arguments.

Leveraging partial behavioral reflection.Forwarding message sends and instance variable accesses to the meta-class even when the default behavior is not overridden makes no sense. MetaClassTalk, for example, only com-piles to a reflective call for those classes with changed behavior. We can realize this optimization by statically installing links only in those classes with overridden behavior. In all cases where the behavior is not changed, we use the default implementation provided by the virtual machine.