...
Wiki Markup |
---|
Each itable is an array of the form {{methodOop target\[N\]}}, where {{N}} is the number of methods in the interface. |
...
Since the offset entries on the spine of the table have two words, one word of the terminating entry can have a sentinel value, and the other is free to point to a linked list of extension records. So we repurpose the second word head of a linked list of extension records: <NULL, extension>.
Wiki Markup |
---|
There is one extension record for one interface injection event. They are linked through "next" fields (in an arbitrary order), and the incoming interface {{klass}} is matched against their "key" fields. A matching extension record has N additional words, all methodOops; those N words are exactly like a statically defined embedded itable. The extension records are actually just system arrays (type {{Object\[\]}}) whose lengths are {{2+N}}, with key at element 0, "next" link at element 1, and the implementation methods (if any) at elements 2 through {{2+N-1}}. |
In order to pack these dynamic itables, MethodHandles must be lowered to methodOops. For the special case of direct method handles, the original methodOop can be reused. For other (adapted or bound) method handles, a new methodOop must be created to wrap the method handle. This is a dark and dirty secret that nobody but the JVM will know about. (See the auto-generation of invoke methods in methodOop.cpp of meth.patch.)
...
The meth.patch code already uses the oop-in-a-constant-pool technique for autogenerated MethodHandle.invoke
methods (of which there is an infinite variety).
Design decisions and considerations
- The need to be able to do dynamic invocations in
<clinit>
of an interface is less than minimal. It should be safe to reuse the_bootstrap_method
oop
ininstanceKlass
for storing the injector for injectable interfaces. - The injector for an injectable interface is defined by the
<clinit>
method on the injectable interface. This is done by invokingInterfaceInjector.setInjector(InterfaceInjector)
from<clinit>
. - How should an interface be marked as injectable? Options include:
- Adding a flag that marks an interface as injectable. The following flags have no meaning for classes yet:
0x0040
-JVM_ACC_VOLATILE
for fields,JVM_ACC_BRIDGE
for methods0x0080
-JVM_ACC_TRANSIENT
for fields,JVM_ACC_VARARGS
for methods0x0100
-JVM_ACC_NATIVE
for methods
- Adding an injectable annotation to the class file, and make sure that this annotation gets loaded early enough.
- Eagerly load all interfaces. The interfaces that define an injector during class initialization are defined as injectable.
- To reduce the need for eager loading the constant pool could be scanned to determine if the interface references the
setInjector
-method.
- To reduce the need for eager loading the constant pool could be scanned to determine if the interface references the
- Adding a flag that marks an interface as injectable. The following flags have no meaning for classes yet:
Other tricky parts
Retry path for invokeinterface
...
Wiki Markup Interface klasses which are not injectable (the vast majority) should have bits in their header which identifies them as such, so that instanceof can return false more quickly. A good way to do this may be to add a second {{Klass::secondary_super_cache}} just for injectable interfaces; then the secondary_super_offset for an interface will take one of two distinct values (instead of the single value it takes today), depending on which secondary_super_cache it uses. This simultaneously makes it easy to detect injectables (by {{secondary_super_offset == offsetof(&secondary_super_cache\[1\]))}} and also allocates a word in every {{klass}} to optimize the lookup of injected interfaces.
- (Can delay this for the POC.) Introduce a negative super type cache:
Klass::secondary_non_super_cache
. Use it as a first resort, to avoid upcalls on negative type tests of injectables.
...
Bottom line, per case:
1. always check for extension records
2. always use the negative cache (after fast positive tests), and customize the code in the JIT (GraphKit::gen_subtype_check
)
3. customize in the JIT; negative cache buys nothing
4. customize in the JIT if possible; negative cache buys nothing
5. let the JIT do its thing for intrinsics; use the negative cache for Class.isInstance
and Class.isAssignableFrom