SlideShare una empresa de Scribd logo
1 de 64
Descargar para leer sin conexión
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 (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);
Problem #1:
Verbosity
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);
...
• 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 == 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);
...
Problem #3:
MethodType wrangling
public Object tryFinally(MethodHandle target, MethodHandle post) throws Throwable {

try {

return target.invoke();

} finally {

post.invoke();

}

}
ETOOMUCHCODE
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
public Object tryFinally(MethodHandle target, MethodHandle post) throws Throwable {

try {

return target.invoke();

} finally {

post.invoke();

}

}
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);
Problem #4:
Complicated forms
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
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-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
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
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
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!
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
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
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
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
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
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
MethodHandle handle = Binder

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

.tryFinally(post)

.invokeStatic(LOOKUP, BinderTest.class, "setZeroToFoo");
• 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)
/**

* 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));

}
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);

}

}
BUT WAIT THERE’S MORE!
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
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 operations: collect, drop, permute
• Conversion: signature1.to(signature2) => permute offsets
• Type checking, pretty toString, other niceties
Signature sig = Signature

.returning(String.class)

.asFold(Object.class);


assertEquals(Object.class, sig.type().returnType());
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());
private static final Signature stringObjectInt = Signature

.returning(String.class)

.appendArg("obj", Object.class)

.appendArg("num", int.class);
Signature sig = stringObjectInt

.appendArg("flo", float.class)

.appendArg("dub", double.class)

.permute("obj", "dub");



assertEquals("(Object obj, double dub)String", sig.toString());

int[] permuteInts = stringObjectInt

.appendArg("flo", float.class)

.appendArg("dub", double.class)

.to(stringObjectInt);



assertArrayEquals(new int[] {0, 1}, permuteInts);
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);
Signature sig = stringObjectInt

.appendArg("flo", float.class)

.appendArg("dub", double.class)

.exclude("obj", "dub");



assertEquals("(int num, float flo)String", sig.toString());
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));
SmartBinderBinder Signature
SmartBinder
• All operations from Binder, but Signature-aware
• Variable-length argument lists
• Regex-based transforms
• Result is a SmartHandle (MethodHandle + Signature)
MethodHandle StringIntegersStringHandle = Binder

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

.invokeStaticQuiet(LOOKUP, Subjects.class, "stringIntegersString");
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);
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);
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();
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)
SmartBinder.from(site.signature)

.permute("context", "arg.*")
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));
Status
• Binder supports nearly all j.l.i.MethodHandle(s) operations
• Signature has a wide array of transforms
• SmartBinder has what I needed
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)
Thank you!
• "Charles Oliver Nutter" <headius@headius.com>
• @headius
• https://github.com/headius/invokebinder
• Maven: com.headius:invokebinder

Más contenido relacionado

La actualidad más candente

Java script questions
Java script questionsJava script questions
Java script questions
Srikanth
 
Java fundamentals
Java fundamentalsJava fundamentals
Java fundamentals
HCMUTE
 
Recommending Method Invocation Context Changes
Recommending Method Invocation Context ChangesRecommending Method Invocation Context Changes
Recommending Method Invocation Context Changes
Beat Fluri
 
Joose @jsconf
Joose @jsconfJoose @jsconf
Joose @jsconf
malteubl
 

La actualidad más candente (20)

Let's refine your Scala Code
Let's refine your Scala CodeLet's refine your Scala Code
Let's refine your Scala Code
 
Introduction to Client-Side Javascript
Introduction to Client-Side JavascriptIntroduction to Client-Side Javascript
Introduction to Client-Side Javascript
 
7 rules of simple and maintainable code
7 rules of simple and maintainable code7 rules of simple and maintainable code
7 rules of simple and maintainable code
 
Objective-c Runtime
Objective-c RuntimeObjective-c Runtime
Objective-c Runtime
 
Java script questions
Java script questionsJava script questions
Java script questions
 
Object oriented programming with python
Object oriented programming with pythonObject oriented programming with python
Object oriented programming with python
 
Clean code
Clean codeClean code
Clean code
 
Clean code and Code Smells
Clean code and Code SmellsClean code and Code Smells
Clean code and Code Smells
 
Clean code
Clean codeClean code
Clean code
 
Solid principles
Solid principlesSolid principles
Solid principles
 
Java fundamentals
Java fundamentalsJava fundamentals
Java fundamentals
 
Clean Code: Chapter 3 Function
Clean Code: Chapter 3 FunctionClean Code: Chapter 3 Function
Clean Code: Chapter 3 Function
 
Recommending Method Invocation Context Changes
Recommending Method Invocation Context ChangesRecommending Method Invocation Context Changes
Recommending Method Invocation Context Changes
 
Object oriented programming in python
Object oriented programming in pythonObject oriented programming in python
Object oriented programming in python
 
Swift for TensorFlow - CoreML Personalization
Swift for TensorFlow - CoreML PersonalizationSwift for TensorFlow - CoreML Personalization
Swift for TensorFlow - CoreML Personalization
 
Unit2wt
Unit2wtUnit2wt
Unit2wt
 
php&mysql with Ethical Hacking
php&mysql with Ethical Hackingphp&mysql with Ethical Hacking
php&mysql with Ethical Hacking
 
Joose @jsconf
Joose @jsconfJoose @jsconf
Joose @jsconf
 
Javascript
JavascriptJavascript
Javascript
 
Groovy 2.0 webinar
Groovy 2.0 webinarGroovy 2.0 webinar
Groovy 2.0 webinar
 

Similar a InvokeBinder: Fluent Programming for Method Handles

Taxonomy of Scala
Taxonomy of ScalaTaxonomy of Scala
Taxonomy of Scala
shinolajla
 
Core java by a introduction sandesh sharma
Core java by a introduction sandesh sharmaCore java by a introduction sandesh sharma
Core java by a introduction sandesh sharma
Sandesh Sharma
 

Similar a InvokeBinder: Fluent Programming for Method Handles (20)

Reflection
ReflectionReflection
Reflection
 
Reflection
ReflectionReflection
Reflection
 
Reflection
ReflectionReflection
Reflection
 
Reflection
ReflectionReflection
Reflection
 
Reflection
ReflectionReflection
Reflection
 
Reflection
ReflectionReflection
Reflection
 
Taxonomy of Scala
Taxonomy of ScalaTaxonomy of Scala
Taxonomy of Scala
 
Developer testing 101: Become a Testing Fanatic
Developer testing 101: Become a Testing FanaticDeveloper testing 101: Become a Testing Fanatic
Developer testing 101: Become a Testing Fanatic
 
Google Guava for cleaner code
Google Guava for cleaner codeGoogle Guava for cleaner code
Google Guava for cleaner code
 
JRuby and Invokedynamic - Japan JUG 2015
JRuby and Invokedynamic - Japan JUG 2015JRuby and Invokedynamic - Japan JUG 2015
JRuby and Invokedynamic - Japan JUG 2015
 
Session 08 - Arrays and Methods
Session 08 - Arrays and MethodsSession 08 - Arrays and Methods
Session 08 - Arrays and Methods
 
Diifeerences In C#
Diifeerences In C#Diifeerences In C#
Diifeerences In C#
 
For Beginners - C#
For Beginners - C#For Beginners - C#
For Beginners - C#
 
Runtime Tools
Runtime ToolsRuntime Tools
Runtime Tools
 
Scala for Java Programmers
Scala for Java ProgrammersScala for Java Programmers
Scala for Java Programmers
 
Java method present by showrov ahamed
Java method present by showrov ahamedJava method present by showrov ahamed
Java method present by showrov ahamed
 
Wrapper classes
Wrapper classes Wrapper classes
Wrapper classes
 
Core java by a introduction sandesh sharma
Core java by a introduction sandesh sharmaCore java by a introduction sandesh sharma
Core java by a introduction sandesh sharma
 
Leveraging Scala Macros for Better Validation
Leveraging Scala Macros for Better ValidationLeveraging Scala Macros for Better Validation
Leveraging Scala Macros for Better Validation
 
Kpi driven-java-development-fn conf
Kpi driven-java-development-fn confKpi driven-java-development-fn conf
Kpi driven-java-development-fn conf
 

Más de Charles Nutter

Más de Charles Nutter (20)

The Year of JRuby - RubyC 2018
The Year of JRuby - RubyC 2018The Year of JRuby - RubyC 2018
The Year of JRuby - RubyC 2018
 
Down the Rabbit Hole: An Adventure in JVM Wonderland
Down the Rabbit Hole: An Adventure in JVM WonderlandDown the Rabbit Hole: An Adventure in JVM Wonderland
Down the Rabbit Hole: An Adventure in JVM Wonderland
 
Ruby Performance - The Last Mile - RubyConf India 2016
Ruby Performance - The Last Mile - RubyConf India 2016Ruby Performance - The Last Mile - RubyConf India 2016
Ruby Performance - The Last Mile - RubyConf India 2016
 
JRuby 9000 - Optimizing Above the JVM
JRuby 9000 - Optimizing Above the JVMJRuby 9000 - Optimizing Above the JVM
JRuby 9000 - Optimizing Above the JVM
 
JRuby 9000 - Taipei Ruby User's Group 2015
JRuby 9000 - Taipei Ruby User's Group 2015JRuby 9000 - Taipei Ruby User's Group 2015
JRuby 9000 - Taipei Ruby User's Group 2015
 
Fast as C: How to Write Really Terrible Java
Fast as C: How to Write Really Terrible JavaFast as C: How to Write Really Terrible Java
Fast as C: How to Write Really Terrible Java
 
Open Source Software Needs You!
Open Source Software Needs You!Open Source Software Needs You!
Open Source Software Needs You!
 
Over 9000: JRuby in 2015
Over 9000: JRuby in 2015Over 9000: JRuby in 2015
Over 9000: JRuby in 2015
 
Doing Open Source the Right Way
Doing Open Source the Right WayDoing Open Source the Right Way
Doing Open Source the Right Way
 
JRuby: The Hard Parts
JRuby: The Hard PartsJRuby: The Hard Parts
JRuby: The Hard Parts
 
Bringing Concurrency to Ruby - RubyConf India 2014
Bringing Concurrency to Ruby - RubyConf India 2014Bringing Concurrency to Ruby - RubyConf India 2014
Bringing Concurrency to Ruby - RubyConf India 2014
 
Beyond JVM - YOW! Sydney 2013
Beyond JVM - YOW! Sydney 2013Beyond JVM - YOW! Sydney 2013
Beyond JVM - YOW! Sydney 2013
 
Beyond JVM - YOW! Brisbane 2013
Beyond JVM - YOW! Brisbane 2013Beyond JVM - YOW! Brisbane 2013
Beyond JVM - YOW! Brisbane 2013
 
Beyond JVM - YOW Melbourne 2013
Beyond JVM - YOW Melbourne 2013Beyond JVM - YOW Melbourne 2013
Beyond JVM - YOW Melbourne 2013
 
Down the Rabbit Hole
Down the Rabbit HoleDown the Rabbit Hole
Down the Rabbit Hole
 
The Future of JRuby - Baruco 2013
The Future of JRuby - Baruco 2013The Future of JRuby - Baruco 2013
The Future of JRuby - Baruco 2013
 
High Performance Ruby - E4E Conference 2013
High Performance Ruby - E4E Conference 2013High Performance Ruby - E4E Conference 2013
High Performance Ruby - E4E Conference 2013
 
Invokedynamic in 45 Minutes
Invokedynamic in 45 MinutesInvokedynamic in 45 Minutes
Invokedynamic in 45 Minutes
 
Invokedynamic: Tales from the Trenches
Invokedynamic: Tales from the TrenchesInvokedynamic: Tales from the Trenches
Invokedynamic: Tales from the Trenches
 
Why JRuby? - RubyConf 2012
Why JRuby? - RubyConf 2012Why JRuby? - RubyConf 2012
Why JRuby? - RubyConf 2012
 

Último

Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
WSO2
 

Último (20)

Apidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbu
Apidays Singapore 2024 - Modernizing Securities Finance by Madhu SubbuApidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbu
Apidays Singapore 2024 - Modernizing Securities Finance by Madhu Subbu
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Script
 
A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
A Beginners Guide to Building a RAG App Using Open Source Milvus
A Beginners Guide to Building a RAG App Using Open Source MilvusA Beginners Guide to Building a RAG App Using Open Source Milvus
A Beginners Guide to Building a RAG App Using Open Source Milvus
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 
Corporate and higher education May webinar.pptx
Corporate and higher education May webinar.pptxCorporate and higher education May webinar.pptx
Corporate and higher education May webinar.pptx
 
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
 
MINDCTI Revenue Release Quarter One 2024
MINDCTI Revenue Release Quarter One 2024MINDCTI Revenue Release Quarter One 2024
MINDCTI Revenue Release Quarter One 2024
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...
 
AXA XL - Insurer Innovation Award Americas 2024
AXA XL - Insurer Innovation Award Americas 2024AXA XL - Insurer Innovation Award Americas 2024
AXA XL - Insurer Innovation Award Americas 2024
 
FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024
 
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
 
Artificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : UncertaintyArtificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : Uncertainty
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 
MS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectorsMS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectors
 
GenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdfGenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdf
 

InvokeBinder: Fluent Programming for Method Handles

  • 3. Me • "Charles Oliver Nutter" <headius@headius.com> • @headius • Red Hat, JBoss, “Research and Prototyping” (nee Polyglot)
  • 6. Argument and return value manipulation
  • 7. Flow control and exception handling
  • 8. And the JVM optimizes it away (Eventually…sometimes…)
  • 9. So it’s all good, right?
  • 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);
  • 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. • insert • drop • cast • invoke Have to start at the target and work back
  • 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); ...
  • 17. public Object tryFinally(MethodHandle target, MethodHandle post) throws Throwable {
 try {
 return target.invoke();
 } finally {
 post.invoke();
 }
 }
  • 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. public Object tryFinally(MethodHandle target, MethodHandle post) throws Throwable {
 try {
 return target.invoke();
 } finally {
 post.invoke();
 }
 }
  • 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);
  • 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
  • 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. MethodHandle handle = Binder
 .from(void.class, String[].class)
 .tryFinally(post)
 .invokeStatic(LOOKUP, BinderTest.class, "setZeroToFoo");
  • 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. /**
 * 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. 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);
 }
 }
  • 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. Need a representation of an argument list…not just types
  • 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. Signature sig = Signature
 .returning(String.class)
 .asFold(Object.class); 
 assertEquals(Object.class, sig.type().returnType());
  • 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. private static final Signature stringObjectInt = Signature
 .returning(String.class)
 .appendArg("obj", Object.class)
 .appendArg("num", int.class);
  • 48. Signature sig = stringObjectInt
 .appendArg("flo", float.class)
 .appendArg("dub", double.class)
 .permute("obj", "dub");
 
 assertEquals("(Object obj, double dub)String", sig.toString());

  • 49. int[] permuteInts = stringObjectInt
 .appendArg("flo", float.class)
 .appendArg("dub", double.class)
 .to(stringObjectInt);
 
 assertArrayEquals(new int[] {0, 1}, permuteInts);
  • 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. Signature sig = stringObjectInt
 .appendArg("flo", float.class)
 .appendArg("dub", double.class)
 .exclude("obj", "dub");
 
 assertEquals("(int num, float flo)String", sig.toString());
  • 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));
  • 54. SmartBinder • All operations from Binder, but Signature-aware • Variable-length argument lists • Regex-based transforms • Result is a SmartHandle (MethodHandle + Signature)
  • 55. MethodHandle StringIntegersStringHandle = Binder
 .from(String.class, String.class, Integer[].class, String.class)
 .invokeStaticQuiet(LOOKUP, Subjects.class, "stringIntegersString");
  • 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. 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. 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. 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)
  • 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. Status • Binder supports nearly all j.l.i.MethodHandle(s) operations • Signature has a wide array of transforms • SmartBinder has what I needed
  • 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. Thank you! • "Charles Oliver Nutter" <headius@headius.com> • @headius • https://github.com/headius/invokebinder • Maven: com.headius:invokebinder