-
Notifications
You must be signed in to change notification settings - Fork 23
User Guide 0.5
Comprehensive API documentation in Javadoc format is available.
If you used the version 0.4 of the library, see What's new in 0.5.
There are several use cases for this library:
- To dynamically invoke other code from your code, either
- from Java, using a purely library approach, or
- from bytecode you generate, in case you're writing your own language runtime that emits bytecode.
- To let code for your own custom language runtime on the JVM be invoked in such a dynamic manner.
Dynamically invoking other code means getting and setting properties and invoking methods on objects without needing to know their type at compile time. The library provides you with a very low-barrier entry point, in the form of the class org.dynalang.dynalink.DefaultBootstrapper and its publicBootstrap
method.
Let's illustrate the easiest usage with an example; setting and getting properties from Java using the default bootstrapper. For this, we'll first define two very simple classes:
public class ColoredByField {
public String color;
}
public class ColoredByProperty {
private String color;
public String getColor() { return color; }
public void setColor(String color) { this.color = color; }
}
You'll notice that one class exposes a public field, while the other hides the field and exposes a getter and a setter instead. Due to static typing rules in Java, you couldn't be setting and getting the color property of these objects in a unified manner even though intuitively you understand what does such property setting and getting mean. Let's presume you have an array of these objects and want to set all their colors to blue. In Java, you'd have to do something like this:
public static void setAllColorToBlue(Object[] coloredObjects) {
for(Object o: coloredObjects) {
if(o instanceof ColoredByField) {
((ColoredByField)o).color = "blue";
} else if(o instanceof ColoredByProperty) {
((ColoredByProperty)o).setColor("blue");
}
}
}
Okay, how's that different with Dynalink?
With Dynalink, DefaultBootstrapper
is giving you java.lang.invoke.CallSite
objects that can intuitively handle any kind of object you throw at them:
import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import org.dynalang.dynalink.DefaultBootstrapper;
Object[] coloredObjects = { new ColoredByField(), new ColoredByProperty() };
CallSite setColorSite = DefaultBootstrapper.publicBootstrap(null, "dyn:setProp:color", MethodType.methodType(void.class, Object.class, String.class));
MethodHandle setColor = setColorSite.dynamicInvoker();
for(Object o: coloredObjects) {
setColor.invokeExact(o, "blue"); // all the magic happens here
}
setColor.invokeExact
will activate Dynalink's dynamic invocation machinery, and it will analyze and recognize for each object what's the correct way to set the color property. By the way, you asked it to create a property setter by specifying "dyn:setProp:color"
as the operation argument to publicBootstrap
. Since you will be creating several such operations, you can refactor the creation of that dynamic invoker into a handy method:
public static MethodHandle createDynamicInvoker(String operation, Class returnType, Class... parameterTypes) {
return DefaultBootstrapper.publicBootstrap(null, operation,
MethodType.methodType(returnType, parameterTypes)).dynamicInvoker();
}
Armed with this, let's now write code that will both set all objects in an array to blue, and then print their colors to confirm it succeeded:
MethodHandle setColor = createDynamicInvoker("dyn:setProp:color", void.class, Object.class, String.class);
MethodHandle getColor = createDynamicInvoker("dyn:getProp:color", String.class, Object.class);
Object[] coloredObjects = { new ColoredByField(), new ColoredByProperty() };
for(Object o: coloredObjects) {
setColor.invokeExact(o, "blue");
}
for(Object o: coloredObjects) {
String color = (String)getColor.invokeExact(o);
System.out.println(o.getClass().getName() + ": " + color);
}
This should print:
ColoredByField: blue
ColoredByProperty: blue
The big deal here is that you have a single call site in your code for setting the color on objects of arbitrary type, and a single call site for getting it. Your code doesn't have to be aware of the types of the objects at all! And this is just the tip of the iceberg - read on for all the rest.
When you have a system that emits bytecode, and want to use dynamic invocation from it, you'll need to emit INVOKEDYNAMIC
instructions that delegate their bootstrapping to Dynalink. In the most basic case, you'll use Dynalink's DefaultBootstrapper
class, just as the previous Java example did.
Let's suppose that you use the ASM 4 library to generate the JVM bytecode of your classes, and you want to emit an invokedynamic call for a property getter "color" that you'd expect to return a string. Here's what you do when generating code with ASM:
import org.objectweb.asm.Handle;
import org.objectweb.asm.MethodVisitor;
import static org.objectweb.asm.Opcodes.H_INVOKESTATIC;
import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
...
MethodVisitor mv = ...;
...
mv.visitInvokeDynamicInsn(
"dyn:getProp:color", // The operation is "get the property named 'color'".
"(Ljava/lang/Object;)Ljava/lang/String;", // It should take an object, and return a string.
new Handle( // For dynamic linking it should delegate to...
H_INVOKESTATIC, // ... a static method...
"org/dynalang/dynalink/DefaultBootstrapper", // ... in Dynalink's DefaultBootstrapper class...
"publicBootstrap", // ... named "publicBootstrap"...
MethodType.methodType(
CallSite.class, // ...that will return a CallSite object, ...
MethodHandles.Lookup.class, // ... when given a lookup object, ...
String.class, // ... the operation name, ...
MethodType.class, // ... and the signature at the call site.
).toMethodDescriptorString()
),
new Object[0] // We have no additional static arguments at the call site.
);
Of course, if you do emit more than one dynamic call site (i.e. want to retrieve both "color" and "shape" properties dynamically), you might want to extract the bootstrap method handle to be used multiple times as:
import org.objectweb.asm.MethodVisitor;
import static org.objectweb.asm.Opcodes.H_INVOKESTATIC;
import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
...
private static final Handle BOOTSTRAP =
new Handle( // For dynamic linking it should delegate to...
H_INVOKESTATIC, // ... a static method...
"org/dynalang/dynalink/DefaultBootstrapper", // ... in Dynalink's DefaultBootstrapper class...
"publicBootstrap", // ... named "publicBootstrap"...
MethodType.methodType(
CallSite.class, // ...that will return a CallSite object, ...
MethodHandles.Lookup.class, // ... when given a lookup object, ...
String.class, // ... the operation name, ...
MethodType.class, // ... and the signature at the call site.
).toMethodDescriptorString()
);
private static final Object[] BOOTSTRAP_ARGS = new Object[0];
...
mv.visitInvokeDynamicInsn("dyn:getProp:color", "(Ljava/lang/Object;)Ljava/lang/String;", BOOTSTRAP, BOOTSTRAP_ARGS);
mv.visitInvokeDynamicInsn("dyn:getProp:shape", "(Ljava/lang/Object;)Ljava/lang/String;", BOOTSTRAP, BOOTSTRAP_ARGS);
That's all there is to it! The system will do all the heavy lifting associated with finding and linking the exact code for the property getters based on the type of the argument passed in. Subsequent invocations with the same type will be fast as they'll go to the already linked method, and if the call site encounters a different type of an argument, it will silently repeat the linking process for the new type for you. Your code should equally query the color and the shape of a Ruby, JavaScript, or Python object, or for that matter, any Plain Old Java Object that happens to have a getColor() and getShape() methods, or public fields named "color" and "shape".
If you look at the JavaDoc for the DefaultBootstrapper
class, you'll see that it exposes two methods: bootstrap
and publicBootstrap
. The difference between the two is that bootstrap
passes down the access context (the MethodHandles.Lookup
object) it receives from the call site to the linkers, while publicBootstrap
ignores it and always uses MethodHandles.publicLookup()
instead. If your language runtime is happy with only accessing public methods, fields, and properties on POJOs, publicBootstrap
gives you somewhat more lightweight call sites. Note that currently we don't even expose non-public members in the POJO linker; we do plan on doing it in the future, and there's no reason an existing language runtime couldn't already do it, so we already provide for the distinction on the bootstrap level.
Dynalink predefines a set of conventions for common operations on objects. This predefined set of conventions is referred to as its metaobject protocol (MOP). Below you'll find the catalogue of available operations and their expected method types. You can use them either when creating dynamic invokers from Java, or in INVOKEDYNAMIC instructions in bytecode you generate.
For purposes of interoperability, we'll reserve the method namespace dyn
for the commonly-understood MOP, meaning every method name will start with dyn:
. Also note that when we use the INVOKEDYNAMIC
instruction, for sake of brevity we omit the business of specifying a bootstrap method that we already explained how to do previously. Each operation is explained in terms of a template, followed by an example in an imagined higher-level dynamic language source code (i.e. what would the operation look like in JavaScript, Python, Ruby, or like), an example of usage from Java, and an example in bytecode:
The following operations are defined:
-
Get property of an object with a constant name
Template:
"dyn:getProp:${name}"(any-object-type)any-type
Example:
- Dynamic langage source code:
obj.temperature
- Java:
java MethodHandle getTemperature = createDynamicInvoker("dyn:getProp:temperature", Number.class, Object.class); int temp = ((Number)getTemperature.invokeExact(obj)).intValue();
- Bytecode:
ALOAD 2 # assume obj is in 2nd local variable INVOKEDYNAMIC "dyn:getProp:temperature"(Ljava/lang/Object;)Ljava/lang/Number;
- Dynamic langage source code:
-
Set property of an object with a constant name
Template:
"dyn:setProp:${name}"(any-object-type,any-type)V
Example:
- Dynamic langage source code:
obj.temperature = 1;
- Java:
java MethodHandle setTemperature = createDynamicInvoker("dyn:setProp:temperature", void.class, Object.class, Integer.class); setTemperature.invokeExact(obj, 1);
- Bytecode:
ALOAD 2 ICONST_1 INVOKEDYNAMIC "dyn:setProp:temperature"(Ljava/lang/Object;I)V;
- Dynamic langage source code:
-
Get property of an object with a non-constant identifier
Template:
"dyn:getProp"(any-object-type,any-type)any-type
Example:
- Dynamic langage source code:
var a = "temperature"; obj[a]
- Java:
java String a = "temperature" MethodHandle getProperty = createDynamicInvoker("dyn:getProp", Number.class, Object.class, String.class); int temp = ((Number)getProperty.invokeExact(obj, a)).intValue();
- Bytecode:
ALOAD 2 # assume 'obj' is in 2nd slot ALOAD 3 # assume 'a' is in 3rd slot INVOKEDYNAMIC "dyn:getProp"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Number;
This operation is useful as a property getter for a name that can change between invocations, and which is passed in the arguments to the method handle. The identifier argument is in fact allowed to be of any type and is not restricted to ajava.lang.String
. The reasoning behind this is that not every language can prove the value will be a string at invocation time, and the language semantics can actually allow for, say, numeric IDs. Consider this in JavaScript:function x(d) { var arrayAndDict = ["arrayElement"]; arrayAndDict.customProperty = "namedProperty"; return arrayAndDict[d ? 0 : "customProperty"]; }
x(true)
returns"arrayElement"
,x(false)
returns"namedProperty"
. At the point of invocation, the type of the property identifier is not known in advance. - Dynamic langage source code:
-
Set property of an object with a non-constant identifier
Template:
"dyn:setProp"(any-object-type,any-type,any-type)V
Example:
- Dynamic langage source code:
var a = "temperature"; obj[a] = 1
- Java:
java String a = "temperature"; MethodHandle setProperty = createDynamicInvoker("dyn:setProp", void.class, Object.class, String.class, Integer.class); setProperty.invokeExact(obj, "temperature", 1);
- Bytecode:
ALOAD 2 # assume 'obj' is in 2nd slot ALOAD 3 # assume 'a' is in 3rd slot ICONST_1 INVOKEDYNAMIC "dyn:setProp"(Ljava/lang/Object;Ljava/lang/Object;I)V
- Dynamic langage source code:
-
Get element of a container object
Template:
"dyn:getElem"(any-object-type,any-type)any-type
Example:
- Source code:
var a = "temperature"; obj[a]
- Java:
java String a = "temperature" MethodHandle getElem = createDynamicInvoker("dyn:getElem", Number.class, Object.class, String.class); int temp = ((Number)getElem.invokeExact(obj, a)).intValue();
- Bytecode:
ALOAD 2 # assume 'obj' is in 2nd slot ALOAD 3 # assume 'a' is in 3rd slot INVOKEDYNAMIC "dyn:getElem"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Number;
Very similar todyn:getProp
, except it can be used by languages that distinguish between namespaces of properties and keyspaces of container objects (arrays, lists, maps). All considerations in 3 apply. Note that you can also specify a fixed element ID i.e.dyn:getElem:blue
; its signature would be(any-object-type)any-type
.
- Source code:
-
Set element of a container object
Template:
"dyn:setElem"(any-object-type,any-type,any-type)V
Example:
- Source code:
var a = "temperature"; obj[a] = 1
- Java:
java String a = "temperature"; MethodHandle setElem = createDynamicInvoker("dyn:setElem", void.class, Object.class, String.class, Integer.class); setElem.invokeExact(obj, "temperature", 1);
- Bytecode:
ALOAD 2 # assume 'obj' is in 2nd slot ALOAD 3 # assume 'a' is in 3rd slot ICONST_1 INVOKEDYNAMIC "dyn:setElem"(Ljava/lang/Object;Ljava/lang/Object;I)V
Very similar todyn:setProp
, except it can be used by languages that distinguish between namespaces of properties and keyspaces of container objects (arrays, lists, maps). All considerations in 3 and 4 apply. Note that you can also specify a fixed element ID i.e.dyn:setElem:blue
; its signature would be(any-object-type,any-type)V
.
- Source code:
-
Get length of a container object
Template:
"dyn:getLength"(any-object-type)I
Example:
- Source code:
a.length
- Java:
java MethodHandle getLength = createDynamicInvoker("dyn:getLength", int.class, Object.class); int len = getLength.invokeExact(a);
- Bytecode:
ALOAD 2 # assume 'a' is in 2nd slot INVOKEDYNAMIC "dyn:getLength"(Ljava/lang/Object)I
Returns the length of a container object. Expected to work on Java arrays, collections, and maps, as well as any other languages' container types.
- Source code:
-
Get method of an object with a constant name
Template:
"dyn:getMethod:${name}"(any-object-type)any-type
Example:
- Source code:
var fn = obj.doSomething
- Java:
java MethodHandle getDoSomething = createDynamicInvoker("dyn:getMethod:doSomething", Object.class, Object.class); Object fn = getDoSomething.invokeExact(obj);
- Bytecode:
ALOAD 2 # assume obj is in 2nd local variable INVOKEDYNAMIC "dyn:getMethod:doSomething"(Ljava/lang/Object;)Ljava/lang/Object;
Very similar todyn:getProp
, except it can be used by languages that distinguish between namespaces of properties and methods. All considerations fordyn:getProp
apply.
- Source code:
-
Get method of an object with a variable name
Template:
"dyn:getMethod"(any-object-type,any-type)any-type
Example:
-
Source code:
var fnName = "doSomething" var fn = obj[fnName]
-
Java:
java String fnName = "doSomething"; MethodHandle getMethod = createDynamicInvoker("dyn:getMethod", Object.class, Object.class, String.class); Object fn = getDoSomething.invokeExact(obj, fnName);
-
Bytecode:
ALOAD 2 # assume obj is in 2nd local variable ALOAD 3 # assume fnName is in 3nd local variable INVOKEDYNAMIC "dyn:getMethod"(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;
Same as previous, but the name is passed as the argument and is not constant.
-
-
Create new instance
Template:
"dyn:new"(constructor[, any-arguments])Ljava/lang/Object;
Example:
- Source code:
new Something(any-arguments)
- Java:
java MethodHandle invokeCtor = createDynamicInvoker("dyn:new", Object.class, Object.class[, Object.class...]); Object newInstance = invokeCtor.invokeExact(Something[, args...]);
- Bytecode:
ALOAD 2 # assume 'Something' is in 2nd slot ... various LOADs for other arguments... INVOKEDYNAMIC "dyn:new"(any-arguments)Ljava/lang/Object;
Constructs a new object. The first argument is an object that can construct new objects. When trying to dynamically create instances of Java classes, make sure you understand the distinction between Dynalink'sStaticClass
object andjava.lang.Class
objects, there's a section below on this.
- Source code:
-
Call a method on an object
Template:
"dyn:callMethod:${name}"(any-arguments)any-return-type
Example:
- Source code:
obj.foo(args)
- Java:
java MethodHandle callFoo = createDynamicInvoker("dyn:callMethod:foo", Object.class, Object.class[, Object.class...]); Object retval = callFoo.invokeExact(obj[, args...]);
- Bytecode:
ALOAD 2 # assume 'obj' is in 2nd slot ... various LOADs for other arguments... INVOKEDYNAMIC "dyn:callMethod:foo"(any-arguments)any-return-type;
Invokes a method on an object. The first argument is the object on which the method is invoked.
- Source code:
-
Call a callable object
Template:
"dyn:call"(any-object-type[, any-arguments])any-return-type
Example:
- Source code:
fn(args)
- Java:
java MethodHandle call = createDynamicInvoker("dyn:call", Object.class, Object.class[, Object.class...]); Object retval = call.invokeExact(fn[, args...]);
- Bytecode:
ALOAD 2 # assume 'fn' is in 2nd slot ALOAD 3 # assume an implicit receiver is in 3nd slot ... various LOADs for other arguments... INVOKEDYNAMIC "dyn:call"(any-object-type[, any-arguments])any-return-type;
Calls a callable object. An example of a callable object is an object returned from adyn:getMethod
operation. If a callable object is itself an instance method of a class (and thus expects a "this" argument), you'll have to pass the "this" argument as the second argument:
call.invokeExact(methodObj, thiz[, otherArgs...])
- Source code:
The metaobject protocol allows for composite operations. I.e. let's presume you're implementing a JavaScript runtime; JavaScript is notorious for not distinguishing the property, element, and method namespaces, but you still want to be able to integrate with POJOs and preserve the ability to either address POJO properties, methods, or Map elements with the JavaScript property access syntax. Then what you would do is emit these composite operations:
-
obj.prop
would be compiled todyn:getProp|getElem|getMethod:prop
-
obj[x]
would be compiled todyn:getElem|getProp|getMethod
-
obj.prop()
would be compiled todyn:getMethod|getProp|getElem:prop
(followed bydyn:call
) -
obj[x]()
would be compiled todyn:getMethod|getElem|getProp
(followed bydyn:call
).
Composite operations can often be linked just as efficiently as non-composite operations, because the linker can just ignore the operations it knows can never succeed, and stop looking after it finds an operation that will always succeed.
The utility class CallSiteDescriptorFactory
has a helper method for tokenizing a composite operation specification into its component operations.
The library ships with a linker for POJOs, which is used to link operations on objects that don't belong to any other dynamic language runtime. The usual method invocation and property getters/setters work on any Java object as you would expect them to. Fields can be accessed as properties unless there are explicit getter or setter methods for a property of the same name as the field. Additionally, dyn:getElem
, dyn:setElem
, and dyn:getLength
operations work on Java arrays and java.util
lists and maps (dyn:getLength
works on any collection). dyn:getMethod:${name}
retrieves objects that encapsulate all overloads with the specified name, and dyn:call
on these objects is linked with overload resolution.
The POJO linker only exposes public members of public classes. Furthermore, when JVM is running under security manager, the linker will prevent access to classes in restricted packages. Non-public (or restricted) classes can still be partially accessed and manipulated, though: if they implement public interfaces or have public superclasses, then their members declared through these public ancestors are accessible.
Additionally, java.lang.Class
objects have a virtual property named static
that provides you with access to static fields and methods on that class. Static methods conforming to property getter and setter signatures are also exposed as properties on the static
objects and will hide static fields of the same name. This so-called "static facet" (hey, we can only use the word "interface" for so many things) of classes is internally represented by instances of class org.dynalang.dynalink.beans.StaticClass
. Currently, only public static fields and methods are accessible through the POJO linker. The library provides full transparent overloaded method resolution on static property setters, methods, and constructors. You can access the objects exposing the static facet of classes from Java using StaticClass.forName(Class)
.
Note that construction of instances is also a static concern of a class, and as such the POJO linker won't link a dyn:new
request on java.lang.Class
instances, it will instead link a dyn:new
request on StaticClass
instances. Think of the distinction as follows: the Java language expression java.lang.Integer
maps to a StaticClass
instance and will expose the constructors, the MIN_VALUE
, MAX_VALUE
, TYPE
fields, the highestOneBit
method and so on, as well as respond to dyn:new
as new java.lang.Integer(1)
in Java would. On the other hand, java.lang.Integer.class
returns the java.lang.Class
object, which is practically just a POJO, part of JVM's exposure of runtime type information, a.k.a. reflection. It does not represent the static facet of the class, and we don't want to mix the two.
As a convenience, StaticClass
instances for array classes also support construction (even though these classes don't actually have constructors in JVM as their instances are created using the NEWARRAY
/ANEWARRAY
/MULTIANEWARRAY
bytecode instructions instead of other objects' NEW
) -- they expose a constructor that takes a single argument and creates an array of the expected type.
As a further convenience, StaticClass
instances expose inner classes as properties. If you have a static class for, say, java.util.Map
interface, then doing a dyn:getProp:Entry
will return the static class for java.util.Map$Entry
interface. Both static inner classes and non-static inner classes are exposed in suc a manner. The only thing you have to pay attention to is that if you want to create an instance of a non-static inner class using dyn:new
, you will need to pass an instance of the outer class explicitly as the first parameter to dyn:new
.
Dynalink strives to always be able to pick out the best overloaded method for the given arguments; a failure to unambiguously pick one when a human could is considered a bug (we don't know of any such case). However, sometimes, for performance reasons, a developer might want to pick out an overload themselves. In these cases, it is possible to address a Java method with specific signature, by appending the exact formal types of parameters in the method name. For example, if you know that you'll definitely be invoking System.out.println
with a String
argument, you can use
dyn:callMethod:println(String)
or
dyn:getMethod:println(String)
You have to separate multiple argument types with commas, and can use both primitive type names (int
, long
, etc.) unqualified class names, or qualified class names. The resolver is smart enough to only need unqualified class names as it is matching them against candidate formal argument class names, i.e. dyn:getMethod:read(InputStream)
is sufficient instead of dyn:getMethod:read(java.io.InputStream)
. It is only when you have two overloads of a method that have two different classes in the same argument position that have the same unqualified name but are in different packages that you need to use qualified names; this scenario should be extremely rare, i.e.:
public class TrickOrTreat {
public void fillBagWithCandy(com.acme.simple.Bag c) ...
public void fillBagWithCandy(com.acme.multi.Bag c) ...
}
In this case, if you wanted to use manual overload selection for, you couldn't use dyn:getMethod:fillBagWithCandy(Bag)
as there are two overloads that take an argument in the first place of a type named "Bag", but they're different types coming from different packages. As we said, this should be extremely rare.
The default bootstrapper is the simplest possible API to Dynalink. It is possible though that you might need to customize it. If you look at the source code for the default bootstrapper you'll see that it's just a thin wrapper around something called a DynamicFactory
, and that it uses an implementation of a call site named MonomorphicCallSite
. You are encouraged to explore the documentation for DynamicLinkerFactory
to understand how can you create customized dynamic linkers, as well as the documentation for documentation for RelinkableCallSite
and its subclasses (e.g. ChainedCallSite
) to understand what other options do you have in this space.
The rest of this guide concerns the things you need to know and do if you're writing a language runtime that exposes its own kinds of objects. You will still want to generate all your call sites as above. However, in addition to being able to link dynamically to code in any language, you will also want the ability for any call sites to link to operations on your own objects. In simple terms, you want your runtime (as well as other users of Dynalink) to be able to query your objects for their color and shape...
To achieve that, you need to look at the org.dynalang.dynalink.linker package, and specifically you need to implement the GuardingDynamicLinker interface. Additionally, when you package your language runtime in a JAR file, you will need to add the fully qualified class name of your implementation to the file named META-INF/services/org.dynalang.dynalink.linker.GuardingDynamicLinker
file, as Dynalink uses the JAR service mechanism to discover and automatically load all language-specific linkers in the classpath.
You can keep using the DefaultBootstrapper
, as Dynalink will find your own linker and load it if it is declared in the JAR file, and link your code with it. However, when you are creating a linker for your own use, you might want to explicitly manage your own DynamicLinker
instance. DynamicLinker
is an object that ties together all loaded GuardingDynamicLinker
implementations in the JVM, and is used internally by the DefaultBootstrapper
to perform linking. You can, however, create your own customized instance too, and make sure to configure it so that your own language linker has the highest priority (is tried first when linking). That can give your code a performance edge. To do that, you will need to have code like this somewhere in your language runtime to provide the DefaultBootstrapper
replacement functionality:
import org.dynalang.dynalink.*;
import org.dynalang.dynalink.linker.*;
public class AwesomeBootstrapper {
private static final DynamicLinker dynamicLinker;
static {
final DynamicLinkerFactory factory = new DynamicLinkerFactory();
final GuardingDynamicLinker awesomeLinker = new AwesomeLinker();
factory.setPrioritizedLinker(awesomeLinker);
dynamicLinker = factory.createLinker();
}
public static CallSite bootstrap(MethodHandles.Lookup caller, String name, MethodType type) {
return dynamicLinker.link(
new MonomorphicCallSite(CallSiteDescriptorFactory.create(caller, name, type));
}
The factory is smart enough that even if it discovers the AwesomeLinker
class through the JAR service mechanism, it will ignore it if you supplied a pre-created prioritized instance. Now all you have to do is use your org/awesomelang/AwesomeBootstrapper
class name instead of org/dynalang/dynalink/DefaultBootstrapper
class name when specifying bootstrap method names in call sites (i.e. in the above ASM 4 example).
Yes, the interface is named GuardingDynamicLinker
. It has a sole method with this signature:
public GuardedInvocation getGuardedInvocation(LinkRequest linkRequest,
LinkerServices linkerServices);
It is invoked for a particular invocation at particular call site. It needs to inspect both the call site (mostly for its method name and types) and the actual arguments and figure out whether it can produce a MethodHandle as the target for the call site. The call site descriptor and the arguments are passed in the LinkRequest
object. In ordinary circumstances, you'll check something along the lines of:
if(linkRequest.getReceiver() instanceof AwesomeObject)
If not, return null -- the master linker will then ask the next (if any) guarding linker. This is the base requirement for cross-language interoperability; you only deal with what you know, and pass on what you don't. On the other hand, if you know what to do with the receiver object, then you'll produce a method handle for handling the call and a guard method handle.
Actually, the GuardedInvocation
class above is nothing more than a value class, a triple of two method handles (one for the invocation, one for the guard condition) and a java.lang.invoke.SwitchPoint
. Since your method handle is only valid under certain conditions (i.e. linkRequest.getReceiver() instanceof AwesomeObject
), you will want to create a guard expressing this condition. The master linker will pass the guard and the invocation to the call site, which will compose them into a new method handle according to its inline caching strategy. I.e. the MonomorphicCallSite
will create a guardWithTest()
of the guard and the invocation, with fallback to the master linker's relink()
method when the guard fails or switch point is invalidated. The main takeaway is that you needn't deal with any of that; just need to provide the invocation and the guard and/or a switch point.
You can use the switch point in your linker implementation if you want the ability to invalidate the guarded invocations asynchronously when some external condition changes. You just need to pass the switch point in your guarded invocation, and in the chosen event, invalidate it. You don't need to worry about invoking SwitchPoint.guardWithTest()
; it is the job of the call site implementation to compose your invocation, the guard, and the switch point into a composite method handle that behaves according to the call site semantics (i.e. the MonomorphicCallSite
class will relink itself on next invocation after you invalidate the currently linked method's switch point).
As for the MonomorphicCallSite
: it's a call site that has a monomorphic inline cache, meaning it'll always just keep the last linked method until it's invalidated, and then relink. It's the simplest linking strategy possible, but it's not always the fastest in the long run. The library also comes with another implementation, ChainedCallSite
which maintains a cascading list of several method handles to try, and usually results in better performance, but YMMV.
The library uses the concept of a "call site descriptor" throughout. All call site implementations have them, and the linkers will expect them. A call site descriptor is an immutable value representing the name of the operation at the call site, the signature at the call site, and the lookup that was used. It's basically a value tying together all the values passed to a bootstrap method. CallSiteDescriptor
is an interface, and there is a CallSiteDescriptorFactory that creates default implementations. It canonicalizes instances, so all call sites that express the "get the property color using public lookup with signature Object(Object)" operation will receive the same descriptor. It is recommended that you use this factory, unless the call sites in the bytecode emitted by your language runtime have additional bootstrap arguments in them. If they do, you'll need to create your own call site descriptor implementations to store these additional arguments in them.
You can declare that your linker is the authoritative linker for all objects of a certain type. To do that, you need to implement the TypeBasedGuardingDynamicLinker
interface that adds another method to the GuardingDynamicLinker
interface:
public class AwesomeLinker implements TypeBasedGuardingDynamicLinker {
public boolean canLinkType(Class<?> type) {
return AwesomeObject.class.isAssignableFrom(type);
}
public GuardedInvocation getGuardedInvocation(LinkRequest linkRequest, LinkerService linkerServices) {
final Object object = linkRequest.getArguments()[0];
if(!(object instanceof AwesomeObject)) {
return null;
}
AwesomeObject target = (AwesomeObject)object;
...
}
}
Note that you still need to check whether you received a correct kind of object in your getGuardedInvocation
method. The canLinkType
can be used by the bacground framework as a dispatch optimization, but it is not guaranteed it will be.
It's an interface provided to your linker with some extra methods your linker might need. Currently it provides you with a asType()
method that looks much like MethodHandle.asType()
, except it will also inject language specific implicit type conversions when they are available in addition to the JVM specific ones. It also provides other methods: getTypeConverter()
, compareConversions()
, canConvert()
-- more about those in a bit.
Sure thing. Just have your GuardingDynamicLinker
also implement the optional GuardingTypeConverterFactory
interface. The linker framework will pick it up and do the rest of its magic to make sure it ends up in the call path when needed, as optimally as possible.
public class AwesomeLinker implements TypeBasedGuardingDynamicLinker, GuardingTypeConverterFactory {
private static final MethodHandle CONVERT_AWESOME_TO_BOOLEAN = ...;
private static final GuardedInvocation GUARDED_CONVERT_AWESOME_TO_BOOLEAN =
new GuardedInvocation(
CONVERT_AWESOME_TO_BOOLEAN,
Guards.isInstance(AwesomeObject.class, CONVERT_AWESOME_TO_BOOLEAN.type());
public GuardedInvocation convertToType(Class<?> sourceType, Class<?> targetType) {
if(AwesomeObject.class.isAssignableFrom(sourceType) && Boolean.class == targetType) {
return GUARDED_CONVERT_AWESOME_TO_BOOLEAN;
}
return null;
}
}
Also, if you define your own type conversions, you'll almost certainly want to also implement the ConversionComparator
interface. While Dynalink is great at finding the best applicable method among overloaded methods when linking to POJOs, if you provide your own language-specific conversions, then it sometimes might be able to apply your language's values to several types that are unrelated in Java. In that case, you need to help it by resolving the relative priority of possible type conversions. As a practical example, consider JavaScript. Say that you can pass a function in place of a single-method interface. Let's assume we're trying to pass a Runnable to a Thread constructor:
new Thread(function() {
print("Look Ma, I'm running asynchronously!")
}).start()
If your JavaScript runtime provides conversion from any value to String (as it should, all JavaScript objects are convertible to strings) and also from any function to any single-method interface, the poor POJO linker will get confused because now the above function object can be applied to both new Thread(String name)
and new Thread(Runnable target)
. You'll need to have your linker also implement ConversionComparator
to resolve this conflict, obviously telling the POJO linker to favor functions being converted into single-method interfaces instead of to strings.
Below are listed some considerations for how to implement the various metaobject protocol operations in your language linker.
- For all operations, you have to ensure that the return value from
GuardingDynamicLinker.getGuardedInvocation()
conforms to the call site method type. You can do it automatically withGuardedInvocation.asType(LinkerServices, MethodType)
, but it's up to you to use it; the framework doesn't call it automatically in order to not mask type mismatch errors accidentally. - For
dyn:getProp
,dyn:setProp
,dyn:getElem
, anddyn:setElem
without a fixed identifier, yourGuardingDynamicLinker
should recognize them as a property/element getter/setter for a name/index that can change between invocations, and which is passed in the arguments to the method handle. You probably shouldn't return a method handle that is fixed for the current value of the identifier/index (albeit you could if you also build the assumption into the guard). The expectation is that the variability of the identifier is high, and this will result in too frequent relinking, so you'd rather return a method handle that uses the value of the name on each invocation. Note how the identifier argument can be of any type and is not restricted to ajava.lang.String
,java.lang.Integer
, etc. The reasoning behind this is that not every language can prove the value will be only a string or only a numeral at invocation time, and the language semantics can actually allow for, say, numeric IDs; see the JavaScript example in the earlier section on the metaobject protocol. - If your language doesn't have distinct namespaces for properties, elements, and methods, you should treat the operations on them identically within the semantic constraints of your language. As a typical example, a JavaScript implementation will provide basically same linkage for
dyn:getProp
,dyn:getElem
, anddyn:getMethod
, since for JavaScript objects, all three live in the same namespace. Small semantic deviations can occur, obviously, asdyn:getMethod:foo
can return a linkage for__noSuchMethod__
in case the addressed object has no property associated with the requested name, whiledyn:getProp:foo
could return__noSuchProperty__
, etc. - Likewise, if your language doesn't have distinct namespaces for properties, elements, and methods, then when you're generating call sites, you should use composite methods to indicate operation on any kind of value (property, element, method) is acceptable; see the section on composite operations for details.
- If you can, pay attention to the value of
LinkRequest.isCallSiteUnstable()
. The framework uses it to inform you that a call site has been relinked too many times (dynamic linker factory has a setter for specifying how many times is too many; by default it's 8); your linker implementation can use this flag to use an alternative linkage that would otherwise be less optimal for the actual arguments at the time of linkage, but has a broader guard and thus will relink less often. This way, your language runtime can detect call sites that are practically megamorphic, and provide linkage that adapts to the megamorphic nature of that call site.
Some language runtimes pass "context" on stack. That is, each call site they emit will have one or more additional arguments that represent language runtime specific state at the point of invocation. This is normally thread-specific state that is accessible through a thread local too, but is more optimal when passed on stack. If you have such a language runtime, you should add the context arguments at the start of the argument list, after this but before any other arguments, and you should also make sure to invoke the setNativeContextArgCount
method on the DynamicLinkerFactory
to make it aware that the first few arguments in your call sites are runtime context.
In your GuardingDynamicLinker
implementations, you should prepare for encountering both expected and unexpected context arguments in the link requests. If your runtime has a runtime context in the call sites, check for it, and link accordingly when you see it. It is best to pass a single runtime context argument, and make its type be a class not used for anything else, so its presence is easy to identify. If your linker is asked to link against a call site that does not expose your expected context (or your linker does not expect any runtime contexts at all), invoke LinkRequest.withoutRuntimeContext()
to obtain a request with all runtime context arguments stripped and link against that. The DynamicLinker
implementation is smart enough to notice that your linker returned a guarded invocation for a context-stripped link request, and will successfully link it into the call site by dropping the context arguments.
Also prepare for a situation when your linker is invoked for linking a call site that is not emitted by your own language runtime, and does not have the context arguments in the link request. You will have to make sure that your objects' methods are correctly invokable even in absence of the context -- they should be able to reacquire the context from a thread local when needed.
The library contains a small amount of utilities in the org.dynalang.dynalink.support
package that it itself uses, and you might find them helpful.
We specifically want to draw your attention to the Guards class that helps you create typical guards. It's conscious of class loader visibility issues and will make sure that when needed, things are only cached using weak references.
Another useful class is TypeUtilities which contains type conversion rules from Java Language Specification and similar goodies.
Yet another useful class is Lookup, which is a wrapper around MethodHandles.Lookup
, and provides convenient methods such as findOwnStaticMethod
. Typically, when classes need a method handle to some own static method, you need to write this:
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle handle = lookup.findStatic(lookup.lookupClass(), "add", MethodType.methodType(int.class, int.class, int.class);
Compare it with:
MethodHandle handle = Lookup.findOwnStatic(MethodHandles.lookup(), "add", int.class, int.class, int.class)
Admittedly, not much of a big deal, but still much less line noise; you don't need to spell out either lookup.lookupClass
or MethodType.methodType
.