O slideshow foi denunciado.
Utilizamos seu perfil e dados de atividades no LinkedIn para personalizar e exibir anúncios mais relevantes. Altere suas preferências de anúncios quando desejar.
InvokeBinder
Fluent Programming for MethodHandles
InvokeBinder
The Missing java.lang.invoke API
Me
• "Charles Oliver Nutter" <headius@headius.com>
• @headius
• Red Hat, JBoss, “Research and Prototyping” (nee Polyglot)
MethodHandle
Function and field pointers
Argument and return value
manipulation
Flow control and exception
handling
And the JVM optimizes it away
(Eventually…sometimes…)
So it’s all good, right?
MethodHandle nativeTarget = MethodHandles.findVirtual(targetClass, targetName, targetType);
// handle return value

if (na...
Problem #1:
Verbosity
MethodHandle nativeTarget = findVirtual(targetClass, targetName, targetType);
// handle return value

if (nativeReturn == ...
• insert
• drop
• cast
• invoke Have to start at the target and work back
Problem #2:
Composition in reverse
MethodHandle nativeTarget = findVirtual(targetClass, targetName, targetType);
// handle return value

if (nativeReturn == ...
Problem #3:
MethodType wrangling
public Object tryFinally(MethodHandle target, MethodHandle post) throws Throwable {

try {

return target.invoke();

} fin...
ETOOMUCHCODE
try/finally
1. Like javac, finally path must be duplicated
2. Normal path invokes post, returns result of body
3. Exceptiona...
public Object tryFinally(MethodHandle target, MethodHandle post) throws Throwable {

try {

return target.invoke();

} fin...
MethodHandle exceptionHandler = Binder

.from(target.type().insertParameterTypes(0, Throwable.class).changeReturnType(void...
Problem #4:
Complicated forms
The Problems
• Verbosity
• Composition in reverse
• MethodType must match exactly all the way through
• Many common patter...
The Solution
InvokeBinder
https://github.com/headius/invokebinder
com.headius:invokebinder
com.headius.invokebinder
Binder
• Binder.from(type) produces a new Binder (which is immutable)
• Binder#drop, insert, permute, etc act in call-forw...
String value1 = System.getProperty("foo");
MethodHandle m1 = lookup
        .findStatic(System.class, "getProperty",
Metho...
PrintStream out1 = System.out;
MethodHandle m3 = lookup
        .findStaticGetter(System.class, "out", PrintStream.class);...
class MyStruct {
    public String name;
}
MyStruct ms = new MyStruct();
MethodHandle m5 = lookup
        .findSetter(MySt...
MethodHandle m9 = lookup
        .findStatic(Demo1.class, "twoArgs",
                MethodType.methodType(String.class, S...
MethodHandle m11 = lookup
        .findStatic(Demo1.class, "twoArgs",
                MethodType.methodType(String.class, ...
MethodHandle m13 = lookup
        .findStatic(Demo1.class, "threeArgs",
                MethodType.methodType(String.class...
MethodHandle m15 = lookup
        .findStatic(Demo1.class, "twoArgs",
                MethodType.methodType(String.class, ...
MethodHandle m21 = Binder.from(String.class, int.class, String.class)
        .branch(
                Binder.from(boolean...
Debugging!
handle = Binder

.from(String.class, Object.class, double.class, String.class)

.dropFirst(2)

.printType()

.i...
MethodHandle handle = Binder

.from(void.class, String[].class)

.tryFinally(post)

.invokeStatic(LOOKUP, BinderTest.class...
• Binder aggregates MethodType, list of transforms
• While building
• MethodType is transformed (and verified or cast)
• Tr...
/**

* Filter incoming arguments, starting at the given index, replacing
* each with the result of calling the associated ...
public class Filter extends Transform {



private final int index;

private final MethodHandle[] functions;



public Fil...
BUT WAIT THERE’S MORE!
Argument juggling sucks
MethodHandle handle2 = Binder

.from(Subjects.StringIntegerIntegerIntegerString.type())

.collect(...
Need a representation of an
argument list…not just types
Signature
• Combines MethodType with argument names
• Perform argument manipulations by name, not offset
• Regex-based ope...
Signature sig = Signature

.returning(String.class)

.asFold(Object.class);


assertEquals(Object.class, sig.type().return...
Signature sig = Signature

.returning(String.class)

.appendArg("obj", Object.class)

.appendArg("num", int.class)

.inser...
private static final Signature stringObjectInt = Signature

.returning(String.class)

.appendArg("obj", Object.class)

.ap...
Signature sig = stringObjectInt

.appendArg("flo", float.class)

.appendArg("dub", double.class)

.permute("obj", "dub");
...
int[] permuteInts = stringObjectInt

.appendArg("flo", float.class)

.appendArg("dub", double.class)

.to(stringObjectInt)...
int[] permuteInts = stringObjectInt

.appendArg("flo", float.class)

.appendArg("dub", double.class)

.to(".*o.*");



ass...
Signature sig = stringObjectInt

.appendArg("flo", float.class)

.appendArg("dub", double.class)

.exclude("obj", "dub");
...
public static final Signature StringIntegerIntegerIntegerString = Signature

.returning(String.class)

.appendArg("a", Str...
SmartBinderBinder Signature
SmartBinder
• All operations from Binder, but Signature-aware
• Variable-length argument lists
• Regex-based transforms
• ...
MethodHandle StringIntegersStringHandle = Binder

.from(String.class, String.class, Integer[].class, String.class)

.invok...
MethodHandle StringIntegersStringHandle = Binder

.from(String.class, String.class, Integer[].class, String.class)

.invok...
Signature oldSig = Subjects.StringIntegerIntegerIntegerString;



SmartHandle handle = SmartBinder.from(oldSig)

.drop("b1...
MethodHandle target = Subjects.concatHandle();

MethodHandle filter = MethodHandles.insertArguments(Subjects.concatHandle(...
Signature.returning(IRubyObject.class)

.appendArg("context", ThreadContext.class)

.appendArg("caller", IRubyObject.class...
SmartBinder.from(site.signature)

.permute("context", "arg.*")
MethodHandle stringInt = Binder

.from(String.class, int.class)

.invokeStaticQuiet(LOOKUP, Integer.class, “toString");
Me...
Status
• Binder supports nearly all j.l.i.MethodHandle(s) operations
• Signature has a wide array of transforms
• SmartBin...
TODO
• Analyze transform chain to emit more efficient handles
• Fill out missing functionality, tests, docs
• More utility ...
Thank you!
• "Charles Oliver Nutter" <headius@headius.com>
• @headius
• https://github.com/headius/invokebinder
• Maven: c...
Próximos SlideShares
Carregando em…5
×

InvokeBinder: Fluent Programming for Method Handles

The MethodHandle API provides a powerful way to build optimized function pointers that do a wide variety of call-path adaptations. Unfortunately, the API as designed requires mental contortions to write anything more than trivial handle chains by hand. Adaptations must be made backwards from the target, argument-list modifications are done by index and length, and debugging failed adaptations can be a terrific challenge. InvokeBinder seeks to make method handles fun and easy to use by providing a fluent API, building adaptations from the call forward, allowing argument-list manipulation by name and wildcard, and much more. We'll explore everything InvokeBinder does today and talk about improvements for the future.

  • Seja o primeiro a comentar

InvokeBinder: Fluent Programming for Method Handles

  1. 1. InvokeBinder Fluent Programming for MethodHandles
  2. 2. InvokeBinder The Missing java.lang.invoke API
  3. 3. Me • "Charles Oliver Nutter" <headius@headius.com> • @headius • Red Hat, JBoss, “Research and Prototyping” (nee Polyglot)
  4. 4. MethodHandle
  5. 5. Function and field pointers
  6. 6. Argument and return value manipulation
  7. 7. Flow control and exception handling
  8. 8. And the JVM optimizes it away (Eventually…sometimes…)
  9. 9. So it’s all good, right?
  10. 10. MethodHandle nativeTarget = MethodHandles.findVirtual(targetClass, targetName, targetType); // handle return value
 if (nativeReturn == long.class) {
 MethodHandle returnFilter = MethodHandles.insertArguments(
 MethodHandles.findStatic(RubyFixnum.class, “newFixnum", MethodType.methodType(RubyFixnum.class, Ruby.class, long.class)),
 0,
 runtime); nativeTarget = MethodHandles.filterReturnValue(nativeTarget, returnFilter);
  11. 11. Problem #1: Verbosity
  12. 12. MethodHandle nativeTarget = findVirtual(targetClass, targetName, targetType); // handle return value
 if (nativeReturn == long.class) {
 // native integral type, produce a Fixnum
 MethodHandle returnFilter = insertArguments(
 findStatic(RubyFixnum.class, “newFixnum", methodType(RubyFixnum.class, Ruby.class, long.class)),
 0,
 runtime); nativeTarget = filterReturnValue(nativeTarget, returnFilter); ...
  13. 13. • insert • drop • cast • invoke Have to start at the target and work back
  14. 14. Problem #2: Composition in reverse
  15. 15. MethodHandle nativeTarget = findVirtual(targetClass, targetName, targetType); // handle return value
 if (nativeReturn == long.class
 || nativeReturn == short.class
 || nativeReturn == char.class
 || nativeReturn == int.class
 || nativeReturn == long.class) {
 // native integral type, produce a Fixnum
 nativeTarget = explicitCastArguments(nativeTarget, methodType(long.class, targetArgs));
 MethodHandle returnFilter = insertArguments(
 findStatic(RubyFixnum.class, “newFixnum", methodType(RubyFixnum.class, Ruby.class, long.class)),
 0,
 runtime); nativeTarget = filterReturnValue(nativeTarget, returnFilter); ...
  16. 16. Problem #3: MethodType wrangling
  17. 17. public Object tryFinally(MethodHandle target, MethodHandle post) throws Throwable {
 try {
 return target.invoke();
 } finally {
 post.invoke();
 }
 }
  18. 18. ETOOMUCHCODE
  19. 19. try/finally 1. Like javac, finally path must be duplicated 2. Normal path invokes post, returns result of body 3. Exceptional path drops return, invokes post, re-raises exception 4. Now do this entirely on call stack, with no temp vars
  20. 20. public Object tryFinally(MethodHandle target, MethodHandle post) throws Throwable {
 try {
 return target.invoke();
 } finally {
 post.invoke();
 }
 }
  21. 21. MethodHandle exceptionHandler = Binder
 .from(target.type().insertParameterTypes(0, Throwable.class).changeReturnType(void.class))
 .drop(0)
 .invoke(post);
 
 MethodHandle rethrow = Binder
 .from(target.type().insertParameterTypes(0, Throwable.class))
 .fold(exceptionHandler)
 .drop(1, target.type().parameterCount())
 .throwException();
 
 target = MethodHandles.catchException(target, Throwable.class, rethrow);
 
 // if target returns a value, we must return it regardless of post
 MethodHandle realPost = post;
 if (target.type().returnType() != void.class) {
 // modify post to ignore return value
 MethodHandle newPost = Binder
 .from(target.type().insertParameterTypes(0, target.type().returnType()).changeReturnType(void.class))
 .drop(0)
 .invoke(post);
 
 // fold post into an identity chain that only returns the value
 realPost = Binder
 .from(target.type().insertParameterTypes(0, target.type().returnType()))
 .fold(newPost)
 .drop(1, target.type().parameterCount())
 .identity();
 }
 
 return MethodHandles.foldArguments(realPost, target);
  22. 22. Problem #4: Complicated forms
  23. 23. The Problems • Verbosity • Composition in reverse • MethodType must match exactly all the way through • Many common patterns are complicated to represent • Argument manipulation is by absolute offsets • Varargs and argument collection often need fixed size • Transforms can’t be easily shared across signatures
  24. 24. The Solution
  25. 25. InvokeBinder https://github.com/headius/invokebinder com.headius:invokebinder
  26. 26. com.headius.invokebinder
  27. 27. Binder • Binder.from(type) produces a new Binder (which is immutable) • Binder#drop, insert, permute, etc act in call-forward direction • Binder#fold, filter, catchException, etc take additional handles • Endpoints: invoke*, set/getField, arraySet/Get, constant, identity, nop, branch (guardWithTest) • Utilities for larger constructs
  28. 28. String value1 = System.getProperty("foo"); MethodHandle m1 = lookup         .findStatic(System.class, "getProperty", MethodType.methodType(String.class, String.class)); MethodHandle m2 = Binder.from(String.class, String.class)         .invokeStatic(lookup, System.class, "getProperty"); Static Method Lookup can also be passed into Binder#withLookup, eliminating this param
  29. 29. PrintStream out1 = System.out; MethodHandle m3 = lookup         .findStaticGetter(System.class, "out", PrintStream.class); MethodHandle m4 = Binder.from(PrintStream.class)         .getStatic(lookup, System.class, "out"); Static Field Get
  30. 30. class MyStruct {     public String name; } MyStruct ms = new MyStruct(); MethodHandle m5 = lookup         .findSetter(MyStruct.class, "name", String.class); MethodHandle m6 = Binder.from(void.class, MyStruct.class, String.class)         .setField(lookup, "name"); Instance Field Set No target type; Binder knows it already!
  31. 31. MethodHandle m9 = lookup         .findStatic(Demo1.class, "twoArgs",                 MethodType.methodType(String.class, String.class, String.class)); m9 = MethodHandles.dropArguments(m9, 2, String.class); MethodHandle m10 = Binder.from(String.class, String.class, String.class, String.class)         .drop(2)         .invokeStatic(lookup, Demo1.class, "twoArgs"); m10.invoke("one", "two", "three"); // => "[one,two]" Deleting Args
  32. 32. MethodHandle m11 = lookup         .findStatic(Demo1.class, "twoArgs",                 MethodType.methodType(String.class, String.class, String.class)); m11 = MethodHandles.permuteArguments(        m11,         MethodType.methodType(String.class, String.class, String.class, int.class),         1, 0); MethodHandle m12 = Binder.from(String.class, String.class, String.class, int.class)        .permute(1, 0)         .invokeStatic(lookup, Demo1.class, "initials"); m12.invoke("one", "two", 3); // => "[two,one]" Permute Args
  33. 33. MethodHandle m13 = lookup         .findStatic(Demo1.class, "threeArgs",                 MethodType.methodType(String.class, String.class, String.class, String.class)); MethodHandle combiner = lookup         .findStatic(Demo1.class, "initials",                 MethodType.methodType(String.class, String.class, String.class)); m13 = MethodHandles.foldArguments(m13, combiner); MethodHandle m14 = Binder.from(String.class, String.class, String.class)         .fold(                 Binder                         .from(String.class, String.class, String.class)                         .invokeStatic(lookup, Demo1.class, "initials")         )         .invokeStatic(lookup, Demo1.class, "threeArgs"); m14.invoke("Charles", "Nutter"); // => ["CN", "Charles", "Nutter"] Fold
  34. 34. MethodHandle m15 = lookup         .findStatic(Demo1.class, "twoArgs",                 MethodType.methodType(String.class, String.class, String.class)); MethodHandle filter = lookup         .findStatic(Demo1.class, "upcase",                 MethodType.methodType(String.class, String.class)); m15 = MethodHandles.filterArguments(m15, 0, filter, filter); MethodHandle m16 = Binder.from(String.class, String.class, String.class)         .filter(0,                  Binder.from(String.class, String.class)                         .invokeStatic(lookup, Demo1.class, "upcase")         )         .invokeStatic(lookup, Demo1.class, "twoArgs"); m16.invoke("hello", "world"); // => ["HELLO", "WORLD"] Filter
  35. 35. MethodHandle m21 = Binder.from(String.class, int.class, String.class)         .branch(                 Binder.from(boolean.class, int.class, String.class)                         .drop(1)                         .invokeStatic(lookup, Demo1.class, "upOrDown"),                 Binder.from(String.class, int.class, String.class)                         .drop(0)                         .invokeStatic(lookup, Demo1.class, "upcase"),                 Binder.from(String.class, int.class, String.class)                         .drop(0)                         .invokeStatic(lookup, Demo1.class, "downcase")         ); m21.invoke(1, "MyString"); // => "MYSTRING" m21.invoke(0, "MyString"); // => "mystring" Boolean Branch
  36. 36. Debugging! handle = Binder
 .from(String.class, Object.class, double.class, String.class)
 .dropFirst(2)
 .printType()
 .insert(1, "world")
 .invoke(target); No-op, just prints out current type
  37. 37. MethodHandle handle = Binder
 .from(void.class, String[].class)
 .tryFinally(post)
 .invokeStatic(LOOKUP, BinderTest.class, "setZeroToFoo");
  38. 38. • Binder aggregates MethodType, list of transforms • While building • MethodType is transformed (and verified or cast) • Transform stack pushes down a transform • At endpoint transform stack is played in reverse • And verified or cast (perhaps redundant)
  39. 39. /**
 * Filter incoming arguments, starting at the given index, replacing * each with the result of calling the associated function in the * given list.
 *
 * @param index the index of the first argument to filter
 * @param functions the array of functions to transform the arguments
 * @return a new Binder
 */
 public Binder filter(int index, MethodHandle... functions) {
 return new Binder(this, new Filter(index, functions));
 }
  40. 40. public class Filter extends Transform {
 
 private final int index;
 private final MethodHandle[] functions;
 
 public Filter(int index, MethodHandle... functions) {
 this.index = index;
 this.functions = functions;
 }
 
 public MethodHandle up(MethodHandle target) {
 return MethodHandles.filterArguments(target, index, functions);
 }
 
 public MethodType down(MethodType type) {
 for (int i = 0; i < functions.length; i++) {
 type = type.changeParameterType(index + i, functions[i].type().returnType());
 }
 return type;
 }
 
 public String toString() {
 return "fold args from " + index + " with " + Arrays.toString(functions);
 }
 }
  41. 41. BUT WAIT THERE’S MORE!
  42. 42. Argument juggling sucks MethodHandle handle2 = Binder
 .from(Subjects.StringIntegerIntegerIntegerString.type())
 .collect(1, 3, Integer[].class)
 .invoke(Subjects.StringIntegersStringHandle); If I need to change signature, all my offsets break
  43. 43. Need a representation of an argument list…not just types
  44. 44. Signature • Combines MethodType with argument names • Perform argument manipulations by name, not offset • Regex-based operations: collect, drop, permute • Conversion: signature1.to(signature2) => permute offsets • Type checking, pretty toString, other niceties
  45. 45. Signature sig = Signature
 .returning(String.class)
 .asFold(Object.class); 
 assertEquals(Object.class, sig.type().returnType());
  46. 46. Signature sig = Signature
 .returning(String.class)
 .appendArg("obj", Object.class)
 .appendArg("num", int.class)
 .insertArg("num", "flo", float.class);
 
 assertEquals("(Object obj, float flo, int num)String", sig.toString());
  47. 47. private static final Signature stringObjectInt = Signature
 .returning(String.class)
 .appendArg("obj", Object.class)
 .appendArg("num", int.class);
  48. 48. Signature sig = stringObjectInt
 .appendArg("flo", float.class)
 .appendArg("dub", double.class)
 .permute("obj", "dub");
 
 assertEquals("(Object obj, double dub)String", sig.toString());

  49. 49. int[] permuteInts = stringObjectInt
 .appendArg("flo", float.class)
 .appendArg("dub", double.class)
 .to(stringObjectInt);
 
 assertArrayEquals(new int[] {0, 1}, permuteInts);
  50. 50. int[] permuteInts = stringObjectInt
 .appendArg("flo", float.class)
 .appendArg("dub", double.class)
 .to(".*o.*");
 
 assertArrayEquals(new int[] {0, 2}, permuteInts);
 
 permuteInts = stringObjectInt
 .appendArg("flo", float.class)
 .appendArg("dub", double.class)
 .to("num", "dub");
 
 assertArrayEquals(new int[] {1, 3}, permuteInts);
  51. 51. Signature sig = stringObjectInt
 .appendArg("flo", float.class)
 .appendArg("dub", double.class)
 .exclude("obj", "dub");
 
 assertEquals("(int num, float flo)String", sig.toString());
  52. 52. public static final Signature StringIntegerIntegerIntegerString = Signature
 .returning(String.class)
 .appendArg("a", String.class)
 .appendArg("b1", Integer.class)
 .appendArg("b2", Integer.class)
 .appendArg("b3", Integer.class)
 .appendArg("c", String.class); Signature oldSig = Subjects.StringIntegerIntegerIntegerString;
 Signature newSig = oldSig.collect("bs", "b.*");
 
 assertEquals(Integer[].class, newSig.argType(1));
 assertEquals("bs", newSig.argName(1));
 assertEquals(3, newSig.argCount());
 assertEquals("c", newSig.argName(2));
  53. 53. SmartBinderBinder Signature
  54. 54. SmartBinder • All operations from Binder, but Signature-aware • Variable-length argument lists • Regex-based transforms • Result is a SmartHandle (MethodHandle + Signature)
  55. 55. MethodHandle StringIntegersStringHandle = Binder
 .from(String.class, String.class, Integer[].class, String.class)
 .invokeStaticQuiet(LOOKUP, Subjects.class, "stringIntegersString");
  56. 56. MethodHandle StringIntegersStringHandle = Binder
 .from(String.class, String.class, Integer[].class, String.class)
 .invokeStaticQuiet(LOOKUP, Subjects.class, "stringIntegersString"); Signature oldSig = Subjects.StringIntegerIntegerIntegerString;
 
 SmartHandle handle = SmartBinder
 .from(oldSig)
 .collect("bs", "b.*")
 .invoke(Subjects.StringIntegersStringHandle);
  57. 57. Signature oldSig = Subjects.StringIntegerIntegerIntegerString;
 
 SmartHandle handle = SmartBinder.from(oldSig)
 .drop("b1")
 .drop("b2")
 .drop("b3")
 .insert(1, "bs", new Integer[]{1, 2, 3})
 .invoke(Subjects.StringIntegersStringHandle);
  58. 58. MethodHandle target = Subjects.concatHandle();
 MethodHandle filter = MethodHandles.insertArguments(Subjects.concatHandle(), 1, "goodbye");
 MethodHandle handle = SmartBinder
 .from(String.class, arrayOf(“arg1", “arg2”), String.class, String.class)
 .filter("arg.*", filter)
 .invoke(target).handle();
  59. 59. Signature.returning(IRubyObject.class)
 .appendArg("context", ThreadContext.class)
 .appendArg("caller", IRubyObject.class)
 .appendArg("self", IRubyObject.class)
 .appendArg("arg0", IRubyObject.class)
 .appendArg("arg1", IRubyObject.class)
 .appendArg("arg2", IRubyObject.class)
  60. 60. SmartBinder.from(site.signature)
 .permute("context", "arg.*")
  61. 61. MethodHandle stringInt = Binder
 .from(String.class, int.class)
 .invokeStaticQuiet(LOOKUP, Integer.class, “toString"); MethodHandle handle = SmartBinder
 .from(String.class, "i", int.class)
 .fold("s", stringInt)
 .dropLast()
 .identity()
 .handle();
 
 assertEquals(MethodType.methodType(String.class, int.class), handle.type());
 assertEquals("15", (String)handle.invokeExact(15));
  62. 62. Status • Binder supports nearly all j.l.i.MethodHandle(s) operations • Signature has a wide array of transforms • SmartBinder has what I needed
  63. 63. TODO • Analyze transform chain to emit more efficient handles • Fill out missing functionality, tests, docs • More utility forms • Generate bytecode or .java callable for non-indy • “Interpreted” [Smart]Binder#call like LFs for non-bytecode-loading • More extensive debugging (current transform stack, visual graph)
  64. 64. Thank you! • "Charles Oliver Nutter" <headius@headius.com> • @headius • https://github.com/headius/invokebinder • Maven: com.headius:invokebinder

×