43
Advice Weaving in AspectJ Paper by Erik Hilsdale Jim Hugunin Presented by Shay Cohen

Advice Weaving in AspectJ Paper by Erik Hilsdale Jim Hugunin Presented by Shay Cohen

Embed Size (px)

Citation preview

Advice Weaving in AspectJ

Paper byErik HilsdaleJim Hugunin

Presented byShay Cohen

Introduction

Implementation of advice weaving in AspectJ – ver 1.1

The Compilation Process Join Points Shadows Matching Weaving Compile time performance Performance of woven code

The Compilation Process – Source File Compilation Each advice declaration is compiled into a standard Java

methodbefore(String s): execution(void go(*)) && args(s){

System.out.println(s);} Is compiled into a method encapsulating the body of the

advicepublic void ajc$before$A$a9();0: getstatic [java/lang/System.out]3: aload_14: invokevirtual [java/io/PrintStream.println]7: return

Source File Compilation – cont. Reflection is made by three special variables

added to the advice declaration.(…, JoinPoint thisJoinPoint,JoinPoint.StaticPart thisJoinPointStaticPart,JoinPoint.StaticPartthisEncolsingJoinPointStaticPart) Optimization - variables which are not referred

to within the body of the advice are removed from the signature

Optimization - determine if all uses of thisJoinPoint can be replaced with thisJoinPointStaticPart.

Source File Compilation – cont. Around advice in AspectJ uses the

special form proceed Generating a method which takes

in all of the original arguments to the around method plus an additional AroundClosure object

The body of the proceed method will call a method on the AroundClosure

Source File Compilation – cont./* ******************************************************************* * Copyright (c) 1999-2001 Xerox Corporation, * 2002 Palo Alto Research Center, Incorporated (PARC). * All rights reserved. * This program and the accompanying materials are made available * under the terms of the Common Public License v1.0 * which accompanies this distribution and is available at * http://www.eclipse.org/legal/cpl-v10.html * * Contributors: * Xerox/PARC initial implementation * ******************************************************************/

package org.aspectj.runtime.internal;

public abstract class AroundClosure { //private Object[] state;

public AroundClosure(/* Object[] state */) { // this.state = state; } protected Object[] state; protected Object[] preInitializationState; public AroundClosure(Object[] state) { this.state = state; }

public Object[] getPreInitializationState() {return preInitializationState;

}

/** * This takes in the same arguments as are passed to the proceed * call in the around advice (with primitives coerced to Object types) */

public abstract Object run(Object[] args) throws Throwable;}

The Compilation Process – Bytecode transformation “static shadow” - certain principled places

in bytecode represent possible join points The weaver must instrument the bytecode

to insert calls to the an advice whenever a condition is met.

For each such static shadow, it checks each piece of advice in the system and determines if the advice's pointcut could match that static shadow.

Bytecode transformation - cont.

void go(java/lang/String):0: return

void go(java/lang/String):0: invokestatic [A.aspectOf]3: invokevirtual [A.ajc$before$A$a3]6: return

Transformed

Bytecode transformation - cont.

void go(java/lang/Object):0: return

void go(java/lang/Object):0: aload_1 # copy first argument into1: astore_2 # a temporary frame location2: aload_2 # check whether argument3: instanceof [String] # is actually a String6: ifeq 19 # if not, skip advice9: invokestatic [A.aspectOf] # get aspect12: aload_2 # load argument again13: checkcast [String] # guaranteed to succeed16: invokevirtual [A.ajc$before$A$a3] # run advice19: return

Transformed

before(String s): execution(void go(*)) && args(s)

{System.out.println(s);

}

Bytecode transformation - cont. Instanceof – If objectref is not null and is an

instance of the resolved class or array or implements the resolved interface, the instanceof instruction pushes an int result of 1 as an int on the operand stack. Otherwise it pushes an int result of 0.

The Java Virtual Machine Specification, Second Edition

Tim Lindholm, Franck Yellin

Bytecode transformation - cont.

public aspect WeavingAspect {

before (String s): execution(void go(*)) && args(s){

System.out.println("before (String s): execution(void go(*)) && args(s) " + s); }

before (Object s): execution(void go(*)) && args(s){

System.out.println("before (Object s): execution(void go(*)) && args(s) " + s); }

}

public class WeavingObject {

public static void main(String[] args) {WeavingObject obj = new

WeavingObject();

String s = null; obj.go(s);

System.out.println();

s = new String("String");obj.go(s);

}

public void go(Object s) {System.out.println("go(Object s) "

+ s); }

}

before (Object s): execution(void go(*)) && args(s) nullgo(Object s) null

before (String s): execution(void go(*)) && args(s) Stringbefore (Object s): execution(void go(*)) && args(s) Stringgo(Object s) String

Bytecode transformation - cont.

Join point shadows – What defines a join point shadow A join point is a point in the dynamic call

graph of a running program where the behavior of the program can be modified by advice.

Every dynamic join point has a corresponding static shadow in the source code or bytecode of the program.

a shadow represents a region of bytecode associated with a join point

What defines a join point shadow - cont.

kind signature this target args Bytecode shadow

Method-execution method ALOAD_0 or none Same as this Local vars Entire code segment of methodMethod-call method ALOAD_0 or none From stack From stack Invokeinterface, invokespecial (only for privates),

invokestatic, invokevirtualConstructor-exec constructor ALOAD_0 Same as this Local vars Code segment of <init> after call to superConstructor-call constructor ALOAD_0 or none None From stack Invokespecial (plus some extra pieces)

Field-get Field ALOAD_0 or none From stack none Getfield or getstaticField-set Field ALOAD_0 or none From stack From stack Putfield or putstatic

Advice-execution None ALOAD_0 Same as this Local vars Code segment of corresponding method

Initialization Corresp. ALOAD_0 Same as this Complex Requires inlining of all constructors in a given class constructor into oneStatic-initialization Typename None None None Code segment of <clinit>Pre-initialization Corresp. None None Local vars Code segment of <init> before call to super,

constructor this may require in-lining

Exception-handler Typename ALOAD_0 or none None From stack Start is found from exception handler table. execution of exception (only before advice allowed because

end is poorly defined in bytecode)

The shadows of “hello world”, Method call

0: getstatic [java/lang/System.out]3: ldc [String hello world]5: invokevirtual [java/io/PrintStream.println]8: return

0: getstatic [java/lang/System.out]3: ldc [String hello world]5: invokestatic [A.aspectOf]8: invokevirtual [A.ajc$before$A$15a]11: invokevirtual [java/io/PrintStream.println]14: return

Transformed

The shadows of “hello world”, Exposing method call - cont.

0: getstatic [java/lang/System.out]3: ldc [String hello world]5: invokevirtual [java/io/PrintStream.println]8: return

0: getstatic [java/lang/System.out]3: ldc [String hello world]5: astore_16: astore_27: invokestatic [A.aspectOf]10: aload_211: invokevirtual [A.ajc$before$A$15a]14: aload_215: aload_116: invokevirtual [java/io/PrintStream.println]19: return

Transformed

Matching – Residues Advice and other advice-like entities are

represented by shadow munger objects, each of which contains a pointcut designator (PCD)

When the PCDs depend on the dynamic state at the join point this mismatch is resolved by adding a dynamic test that captures the dynamic part of the matching. This dynamic test is the residue of the match.

Residues – cont.0: getstatic [java/lang/System.out]3: ldc [String hello world]5: invokevirtual [java/io/PrintStream.println]8: return

0: invokestatic [A.ajc$if_0] # dynamic test3: ifeq 126: invokestatic [A.aspectOf]9: invokevirtual [Method A.ajc$before$A$a6]12: getstatic [java/lang/System.out]15: ldc ["hello world"]17: invokevirtual java/io/PrintStream.println]20: return

Transformedbefore(): execution(void main(*))

&& if(Tracing.level == 1) {

System.out.println("got here");

}

Residues, Cflow – cont.

In AspectJ-1.1 this matching is implemented entirely as a dynamic test on the join point.

The current implementation of cflow uses a thread-local stack.

Fastmatch A “regular” Match is done by if a

shadow munger is defined for each of the static shadow.

Matching every join point shadow in every class file can be a time consuming process

In fastmatch, every shadow munger is matched to the constant pool information in each class file.

Synthetic methods and matching Compilers for the Java language

generate methods and fields that are not in the original source code

Fortunately, they can be generally recognized by the SYNTHETIC attribute in the Java bytecode

There are many additional synthetic methods added by the AspectJ compiler - AJ_SYNTHETIC

Weaving - Context exposure

collect up the desired state from each matching munger

modify code so desired state is in temporaries

astore_3 # arg2astore_4 # arg1astore_5 # targetaload_5 # repushaload_4 # repushaload_3 # repush

mungers needtarget, arg1, andarg2

Shadow Munger Implementation

Each shadow munger transforms the shadow by adding code inside the boundary of the shadow.

apply mungers to shadow in inverse precedence order

before

after throwing

before

before advice

insert code at beginning of shadow code pushes aspect,

then arguments to advice, then invokes the advice method

guard the inserted code with residual test, if necessary

aload_5 # targetinstanceof [Foo]ifeq end:invokestatic [A.aspectOf][push args to advice]invokevirtual [A.before379]end:

before withresidualtarget(Foo)

weaving

after returning advice

first, process shadow to ensure exactly one exit point:

replace all return (or ireturn, freturn, etc) bytecodes with goto shadow’s end

add single return (or ireturn, freturn, etc) at end of shadow if there was one inside shadow

returngoto

weaving

after returning advice – cont.

then add residual and advice code to end of shadow

returngoto

advice

weaving

Factorial

class Factorial { static int fact(int n) { if (n == 0) return 1; else return n * fact(n-1); }

public static void main(String[] args) { System.out.println(fact(3)); }}

after returning advice – cont.

static int fact(int);0: iload_01: ifne 64: iconst_15: ireturn6: iload_07: iconst_18: isub9: invokestatic

[fact]12: iload_013: imul14: ireturn

static int fact(int);0: iload_01: ifne 84: iconst_15: goto 198: iload_09: iconst_110: isub11: invokestatic [fact]14: iload_015: imul16: goto 1919: dup20: istore_121: invokestatic [A.aspectOf]24: iload_125: invokevirtual

[A.ajc$afterReturning$A$ff]

28: ireturn

after() returning(int i):execution(int fact(int))

around advice

first, extract code from shadow into its own method in containing class

replace shadow code with call to new method

invokevirtual or

invokestatic

weaving

around advice – cont.

create new class, whose run method is just a call to extracted method

invokevirtual or

invokestatic

run

Closure837

weaving

Why we don’t inline advice code The primary performance overhead of

AspectJ code is caused by the aspect-instance lookup and method call

Inlining can be done much more effectively by a JIT

need to either increase the visibility of the accessed members, which would let anyone see them

Inlining is used in default around advice and privileged aspects

Compile time performance

time required to compile the xalan xslt processor from apache.org

Compile time performance – cont.

Improvements can be achieved by: Basic engineering work. improving the fastmatch algorithm

to handle more cases support incremental recompilation

Performance of woven code we measured the compilation time of a

single large tool, the Xalan XSLT processor from apache.org (paper talks about compile-time performance)

for performance evaluation we used the XSLTMark benchmark

we chose to benchmark logging as it is the most invasive of the common AspectJ applications

with logging enabled, both AspectJ and a hand-rolled logger had a 60,000% overhead all following measurements are with logging

disabled

benchmarks

Performance of woven code - hand-rolled logging

to get a baseline we produced a hand-rolled implementation of a logging policy add a log to each of the 826 classes

static Logger log = Logger.getLogger(“xalan”); add a call to each of the 7711

methodslog.entering(“<ClassName>”,

“<MethodName>”)

benchmarks

Performance of woven code - an unfortunate surprise

Logging enabled

L

benchmarks

public aspect Trace { private static Logger log = Logger.getLogger(“xalan”); pointcut traced(): execution(* *(..)); before(): traced() { Signature s = thisJoinPointStaticPart.getSignature(); log.entering( s.getDeclaringType().getName(), s.getname()); }}

Performance of woven code - an unfortunate surprise

Logging disabled 2900% overhead (!)

benchmarks

public aspect Trace { private static Logger log = Logger.getLogger(“xalan”); pointcut traced(): execution(* *(..)); before(): traced() { Signature s = thisJoinPointStaticPart.getSignature(); log.entering( s.getDeclaringType().getName(), s.getname()); }}

0

5

10

15

20

25

30

35

no logging hand-coded naïve AspectJ

Lo

gg

ing

Ove

rhea

d

Use Signature.getDeclaringClassName()

Performance of woven code - guarding with isLoggable()

22% over hand-coded

benchmarks

public aspect Trace { private static Logger log = Logger.getLogger(“xalan”); pointcut traced(): execution(* *(..)); before(): traced() { if (!log.isLoggable(Level.FINER)) return; Signature s = thisJoinPointStaticPart.getSignature(); log.entering( s.getDeclaringType().getName(), s.getname()); }}

0

0.2

0.4

0.6

0.8

1

1.2

1.4

no logging hand-coded AspectJloggable

Ove

rhea

d

Performance of woven code - using the if pointcut

8% over hand-coded

benchmarks

public aspect Trace { private static Logger log = Logger.getLogger(“xalan”); pointcut traced(): execution(* *(..)) && if (log.isLoggable(Level.FINER));

before(): traced() { Signature s = thisJoinPointStaticPart.getSignature(); log.entering( s.getDeclaringType().getName(), s.getname()); }}

0

0.2

0.4

0.6

0.8

1

1.2

1.4

no logging hand-coded AspectJloggable

AspectJif(loggable)

Ove

rhea

d

Performance of woven code - using a field

8% under hand-coded cross-cutting

optimization

benchmarks

public aspect Trace { private static Logger log = Logger.getLogger(“xalan”); static boolean enabled; pointcut traced(): execution(* *(..)) && if(enabled) && if(log.isLoggable(Level.FINER)); before(): traced() { Signature s = thisJoinPointStaticPart.getSignature(); log.entering( s.getDeclaringType().getName(), s.getname()); }}

0

0.2

0.4

0.6

0.8

1

1.2

1.4

no logging hand-coded AspectJloggable

AspectJif(loggable)

AspectJif(enabled)

Ove

rhea

d

(a late addition)

Conclusions

Complier Implementation is a major issue in performance

The complier performance has an impact on aspects users community

Aspects code writing is a major issue in performance

AspectJ is getting faster by the minute

The end