Interpreter implementation of advice weaving

423 views
314 views

Published on

Published in: Technology, Business
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
423
On SlideShare
0
From Embeds
0
Number of Embeds
4
Actions
Shares
0
Downloads
4
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Interpreter implementation of advice weaving

  1. 1. Interpreter  implementation  of Immad Naseer,  advice weaving Ryan Golbeck and Gregor Kiczales. University of British Columbia 1
  2. 2. Aspect­oriented programming (AOP) provides linguistic mechanisms for modularizing  crosscutting concerns. 2
  3. 3. Let's look at an example Consider a web application with two kinds of users: administrators and regular  users. We have to ensure that certain functionality can only be accessed by  administrators and the rest only by a logged in user. 3
  4. 4. public class AdminController { public void handleCreateUser() { if(getLoggedInUser() == null || !getLoggedInUser().isAdmin()) throw new AccessViolationException(); // rest of code } public void handleRemoveUser() { if(getLoggedInUser() == null || !getLoggedInUser().isAdmin()) throw new AccessViolationException(); // rest of code } } public class ProductController { public void handleBuyProduct() { if(getLoggedInUser() == null) throw new AccessViolationException(); // rest of code } public void handleSellProduct() { if(getLoggedInUser() == null) throw new AccessViolationException(); // rest of code } } 4
  5. 5. Access authorization public aspect AccessAuthorization { before(): call(* *Controller.handle*(..)) { ... } before(): call(* AdminController.handle*(..)) { ... } after returning(User user): call(void User.login(..)) && target(user) { ... } after returning(User user): call(void User.logout(..)) && target(user) { ... } } 5
  6. 6. Access authorization public aspect AccessAuthorization { before(): call(* *Controller.handle*(..)) { ... } before(): call(* AdminController.handle*(..)) { ... } after returning(User user): call(void User.login(..)) && target(user) { ... } after returning(User user): call(void User.logout(..)) && target(user) { ... } } 6
  7. 7. Access authorization public aspect AccessAuthorization { before(): call(* *Controller.handle*(..)) { ... } before(): call(* AdminController.handle*(..)) { ... } after returning(User user): call(void User.login(..)) && target(user) { ... } after returning(User user): call(void User.logout(..)) && target(user) { ... } } 7
  8. 8. Access authorization public aspect AccessAuthorization { before(): call(* *Controller.handle*(..)) { ... } before(): call(* AdminController.handle*(..)) { ... } after returning(User user): call(void User.login(..)) && target(user) { ... } after returning(User user): call(void User.logout(..)) && target(user) { ... } } 8
  9. 9. Terminology  advice before(): call(* User.login(..)) { ... } pointcut public void testProductBuy() { u = User.find(“joe”); u.login(“pass”); ... Product.buy(u, 12); join point shadows ... } 9
  10. 10. Terminology  advice before(): call(* User.login(..)) { ... } pointcut public void testProductBuy() { u = User.find(“joe”); u.login(“pass”); ... Product.buy(u, 12); join point shadows ... } 10
  11. 11. Advice weaving coordinates execution of advice around, before or  after applicable join points. 11
  12. 12. Rewriting based advice weaving approaches rewrite the code  public void testProductBuy() { at potentially advised join  u = User.find(“joe”); point shadows (JPS) to  before_advice(); u.login(“pass”); invoke the advice (guarded  ... by residual dynamic checks) if (some_guard()) some_other_before_advice(); Product.buy(u, 12); } } 12
  13. 13. Rewriting based advice weaving approaches rewrite the code  public void testProductBuy() { at potentially advised join  u = User.find(“joe”); point shadows (JPS) to  before_advice(); u.login(“pass”); invoke the advice (guarded  ... by residual dynamic checks) if (some_guard()) some_other_before_advice(); Product.buy(u, 12); } } 13
  14. 14. Rewriting based advice weaving approaches rewrite the code  public void testProductBuy() { at potentially advised join  u = User.find(“joe”); point shadows (JPS) to  before_advice(); u.login(“pass”); invoke the advice (guarded  ... by residual dynamic checks) if (some_guard()) some_other_before_advice(); Product.buy(u, 12); } } 14
  15. 15. When (and how) to weave? interpreter  compile time load time      JIT runtime boundary 15
  16. 16. When (and how) to weave? interpreter  compile time load time      JIT eg. ajc Rewrites the bytecode or source code of the application at compile time + no scanning and rewriting overhead at runtime ­ no “late weaving” ­ requires a global scan of the program ==>                                 not suited for incremental development 16
  17. 17. When (and how) to weave? interpreter  compile time load time      JIT eg. ajc ltw Rewrites the bytecode as part of class loading + allows “late weaving” ­ scanning and rewriting overhead at runtime ==>            leads to slower applications startup ­ not suited for incremental development 17
  18. 18. When (and how) to weave? interpreter  compile time load time      JIT Rewriting based weaving can impact application startup. 18
  19. 19. When (and how) to weave? interpreter  compile time load time      JIT Non­rewriting based weaving can lead to faster startup but at the  cost of reduced execution efficiency. It has to do advice lookup and potential execution at each join point  (note: NOT join point shadow!) 19
  20. 20. When (and how) to weave? interpreter  compile time load time      JIT VMs face a similar tradeoff between application startup and good  steady state performance which is why they use the interpreter/JIT  combination. 20
  21. 21. When (and how) to weave? interpreter  compile time load time      JIT We believe that having a combination of interpreter based non­ rewriting weaving and JIT based rewriting weaving might balance the  startup/steady state efficiency trade off for aspect­oriented programs. 21
  22. 22. When (and how) to weave? interpreter  compile time load time      JIT In this talk, we focus on the interpreter part of the equation and present  the design, implementation and evaluation of an interpreter based non­ rewriting weaver. 22
  23. 23. Interpreter based weaving ­ should require minimum to no work from the static compiler ­ should require minimum work from the class loader ­ should “fail fast” (as data shows most join points are not advised) 23
  24. 24. An example before(): call(* *Controller.handle*(..)) { ... } advice before(): call(* AdminController.handle*(..)) { ... } after returning(User u): call(* User.login(..) && target(u) { ... } after returning(User u): call(* User.logout(..)) && target(u) { ... } invokevirtual void Database.establishConnection(); joinpoint  invokevirtual void PrintStream.println(..); invokevirtual void User.login(..); stream invokevirtual void AdminController.handleCreateUser(); invokevirtual void Logger.warn(); 24
  25. 25. An example before(): call(* *Controller.handle*(..)) { ... } before(): call(* AdminController.handle*(..)) { ... } after returning(User u): call(* User.login(..) && target(u) { ... } after returning(User u): call(* User.logout(..)) && target(u) { ... } invokevirtual void Database.establishConnection(); invokevirtual void PrintStream.println(..); invokevirtual void User.login(..); invokevirtual void AdminController.handleCreateUser(); invokevirtual void Logger.warn(); 25
  26. 26. An example before(): call(* *Controller.handle*(..)) { ... } before(): call(* AdminController.handle*(..)) { ... } after returning(User u): call(* User.login(..) && target(u) { ... } after returning(User u): call(* User.logout(..)) && target(u) { ... } invokevirtual void Database.establishConnection(); invokevirtual void PrintStream.println(..); invokevirtual void User.login(..); invokevirtual void AdminController.handleCreateUser(); invokevirtual void Logger.warn(); 26
  27. 27. Looking up advice at a join point before(): call(* *Controller.handle*(..)) { ... } before(): call(* AdminController.handle*(..)) { ... } after returning(User u): call(* User.login(..) && target(u) { ... } after returning(User u): call(* User.logout(..)) && target(u) { ... } invokevirtual void Database.establishConnection(); invokevirtual void PrintStream.println(..); invokevirtual void User.login(..); invokevirtual void AdminController.handleCreateUser(); invokevirtual void Logger.warn(); 27
  28. 28. What property to dispatch on? before(): call(* *Controller.handle*(..)) { ... } Static type of  before(): call(* AdminController.handle*(..)) { ... } target type  after returning(User u): call(* User.login(..) pattern && target(u) { ... } after returning(User u): call(* User.logout(..)) && target(u) { ... } invokevirtual void Database.establishConnection(); Static type of  invokevirtual void PrintStream.println(..); target invokevirtual void User.login(..); invokevirtual void AdminController.handleCreateUser(); invokevirtual void Logger.warn(); 28
  29. 29. What property to dispatch on? before(): call(* *Controller.handle*(..)) { ... } Target name  before(): call(* AdminController.handle*(..)) { ... } pattern after returning(User u): call(* User.login(..) && target(u) { ... } after returning(User u): call(* User.logout(..)) && target(u) { ... } invokevirtual void Database.establishConnection(); Target name invokevirtual void PrintStream.println(..); invokevirtual void User.login(..); invokevirtual void AdminController.handleCreateUser(); invokevirtual void Logger.warn(); 29
  30. 30. What property to dispatch on? before(): call(* *Controller.handle*(..)) { ... } Return type  before(): call(* AdminController.handle*(..)) { ... } pattern after returning(User u): call(* User.login(..) && target(u) { ... } after returning(User u): call(* User.logout(..)) && target(u) { ... } invokevirtual void Database.establishConnection(); Return type invokevirtual void PrintStream.println(..); invokevirtual void User.login(..); invokevirtual void AdminController.handleCreateUser(); invokevirtual void Logger.warn(); 30
  31. 31. We chose to dispatch on the  static type of target (STT) as it is available on all but one kind of join point. 31
  32. 32. Associating advice with types Database before(): call(* *Controller.handle*(..)) { ... } PrintStream before(): call(* AdminController.handle*(..)) { ... } Logger after returning(User u): call(* User.login(..)) && target(u) { ... } User after returning(User u): call(* User.logout(..)) && target(u) { ... } AdminController 32
  33. 33. Associating advice with types point to no advice Database before(): call(* *Controller.handle*(..)) { ... } PrintStream before(): call(* AdminController.handle*(..)) { ... } Logger after returning(User u): call(* User.login(..)) && target(u) { ... } User after returning(User u): call(* User.logout(..)) && target(u) { ... } AdminController 33
  34. 34. Associating advice with types Database before(): call(* *Controller.handle*(..)) { ... } PrintStream before(): call(* AdminController.handle*(..)) { ... } Logger after returning(User u): call(* User.login(..)) && target(u) { ... } User after returning(User u): call(* User.logout(..)) && target(u) { ... } AdminController 34
  35. 35. Associating advice with types Database before(): call(* *Controller.handle*(..)) { ... } PrintStream before(): call(* AdminController.handle*(..)) { ... } Logger after returning(User u): call(* User.login(..)) && target(u) { ... } User after returning(User u): call(* User.logout(..)) && target(u) { ... } AdminController 35
  36. 36. When do we associate advice with  types? Database before(): call(* *Controller.handle*(..)) { ... } PrintStream before(): call(* AdminController.handle*(..)) { ... } Logger after returning(User u): call(* User.login(..)) && target(u) { ... } User after returning(User u): call(* User.logout(..)) && target(u) { ... } AdminController 36
  37. 37. When do we associate advice with  types? Type Pattern Tables (TPTs) Database before(): call(* *Controller.handle*(..)) { ... } PrintStream before(): call(* AdminController.handle*(..)) { ... } Logger after returning(User u): call(* User.login(..)) && target(u) { ... } User after returning(User u): call(* User.logout(..)) && target(u) { ... } AdminController 37
  38. 38. Type Pattern Table Lists (TPTL) are constructed for each type as it is loaded by the VM  and point to all those TPTs whose type pattern match  the type's name 38
  39. 39. *.tnp list Consider the following advice before(): execution(* *.main(String)) { ... } Wildcard STT type pattern Non­wildcard target name pattern 39
  40. 40. We store the list of applicable advice (or no advice) in a per method shadow cache so that we don't have to do the advice lookup when  seeing the same join point shadow for the second time 40
  41. 41. class AdminController TPT List  AdminController TPT *Controller TPT before(): call(* ... before(): call(* ... public void createUser(String userName, String password) { createUser's shadow cache       ...  4        invokevirtual Database.establishConnection();   4 | NOT_INITIALIZED       ... 12 | NULL  12      invokevirtual AdminController.initialize(String) 18 | thunk for calling       ...        advice   18      invokevirtual AdminController.handleCreateUser(...)       ... 41 }
  42. 42. Implementation We implemented our design on JikesRVM 2.9.2 including  support for  ­ all five kinds of advice;   ­ all kinds of join points except the initialization    family of join points and advice­execution;   ­ and only the singleton aspect instantiation model We did not implement support for thisJoinPoint and inter­type  declarations. 42
  43. 43. Baseline compiler as interpreter model The machine code generated by a baseline compiler looks like the unrolling of the machine code that would be run by an  interpreter. We don't use any information that wouldn't be available to an  interpreter. 43
  44. 44. Evaluation setup An optimized compiled instance of JikesRVM 2.9.2 but with the  adaptive optimization system turned off. 44
  45. 45. Measuring the performance of different paths 45
  46. 46. Measuring the performance of different paths 46
  47. 47. Measuring the performance of different paths 47
  48. 48. Measuring the performance of different paths 48
  49. 49. Measuring the performance of different paths 49
  50. 50. Measuring the performance of different paths The per join point overhead ranges from 28% to 462%; since most  DJPs  are unadvised, the overhead will be closer to 28% more than  90% of  the time 50
  51. 51. Comparative micro benchmarks unadvised before call 51 cflow multiple advice
  52. 52. Comparative micro benchmarks unadvised 52
  53. 53. Comparative micro benchmarks before call 53
  54. 54. Comparative micro benchmarks before call in cflow 54
  55. 55. Comparative micro benchmarks  The startup times of the interpreter are shorter than for the ajc ltw by   600­700ms  across most of the benchmarks.   The infrastructure loading cost for ajc has not been factored out and is     loaded in the 600­700ms headstart.   55
  56. 56. Comparative micro benchmarks before call – lots of classes 56
  57. 57. Comparative micro benchmarks The “lots of classes” benchmark shows that the ajc ltw has to do a  noticeable amount of work for each class that is loaded. This benchmark is also better approximates real life applications where  not all the code that is loaded is run. 57
  58. 58. Conclusion We presented the design, implementation and evaluation of an  interpreter based non­rewriting weaving approach. Our evaluation clearly shows that interpreter based non­rewriting  weaving is feasible to implement.  While we cannot say anything definitive about the performance of the  complete hybrid system, the evaluation results do suggest that the  complete hybrid system might provide a good balance between faster  startup and good steady state performance. 58
  59. 59. Questions? 59
  60. 60. 60

×