Context: I've been benchmarking the difference between using invokedynamic and manually generating bytecode (this is in the context of deciding whether a compiler targeting the JVM should emit more verbose "traditional" bytecode or just an invokedynamic call with a clever bootstrap method). In doing this, it has been pretty straightforward to map bytecode into MethodHandles combinators that are at least as fast, with the exception of tableswitch.
Question: Is there a trick to mimic tableswitch using MethodHandle? I tried mimicking it with a jump table: using a constant MethodHandle[], indexing into that with arrayElementGetter, then calling the found handle with MethodHandles.invoker. However, that ended up being around 50% slower than the original bytecode when I ran it through JMH.
Here's the code for producing the method handle:
private static MethodHandle makeProductElement(Class<?> receiverClass, List<MethodHandle> getters) {
MethodHandle[] boxedGetters = getters
.stream()
.map(getter -> getter.asType(getter.type().changeReturnType(java.lang.Object.class)))
.toArray(MethodHandle[]::new);
MethodHandle getGetter = MethodHandles // (I)H
.arrayElementGetter(MethodHandle[].class)
.bindTo(boxedGetters);
MethodHandle invokeGetter = MethodHandles.permuteArguments( // (RH)O
MethodHandles.invoker(MethodType.methodType(java.lang.Object.class, receiverClass)),
MethodType.methodType(java.lang.Object.class, receiverClass, MethodHandle.class),
1,
0
);
return MethodHandles.filterArguments(invokeGetter, 1, getGetter);
}
Here's the initial bytecode (which I'm trying to replace with one invokedynamic call)
public java.lang.Object productElement(int);
descriptor: (I)Ljava/lang/Object;
flags: (0x0001) ACC_PUBLIC
Code:
stack=3, locals=3, args_size=2
0: iload_1
1: istore_2
2: iload_2
3: tableswitch { // 0 to 2
0: 28
1: 38
2: 45
default: 55
}
28: aload_0
29: invokevirtual #62 // Method i:()I
32: invokestatic #81 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
35: goto 67
38: aload_0
39: invokevirtual #65 // Method s:()Ljava/lang/String;
42: goto 67
45: aload_0
46: invokevirtual #68 // Method l:()J
49: invokestatic #85 // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
52: goto 67
55: new #87 // class java/lang/IndexOutOfBoundsException
58: dup
59: iload_1
60: invokestatic #93 // Method java/lang/Integer.toString:(I)Ljava/lang/String;
63: invokespecial #96 // Method java/lang/IndexOutOfBoundsException."<init>":(Ljava/lang/String;)V
66: athrow
67: areturn
The good thing about invokedynamic is that it allows to postpone the decision, how to implement the operation to the actual runtime. This is the trick behind LambdaMetafactory or StringConcatFactory which may return composed method handles, like in your example code, or dynamically generated code, at the particular implementation’s discretion.
There’s even a combined approach possible, generate classes which you compose to an operation, e.g. settling on the already existing LambdaMetafactory:
private static MethodHandle makeProductElement(
MethodHandles.Lookup lookup, Class<?> receiverClass, List<MethodHandle> getters)
throws Throwable {
Function[] boxedGetters = new Function[getters.size()];
MethodType factory = MethodType.methodType(Function.class);
for(int ix = 0; ix < boxedGetters.length; ix++) {
MethodHandle mh = getters.get(ix);
MethodType actual = mh.type().wrap(), generic = actual.erase();
boxedGetters[ix] = (Function)LambdaMetafactory.metafactory(lookup,
"apply", factory, generic, mh, actual).getTarget().invokeExact();
}
Object switcher = new Object() {
final Object get(Object receiver, int index) {
return boxedGetters[index].apply(receiver);
}
};
return lookup.bind(switcher, "get",
MethodType.methodType(Object.class, Object.class, int.class))
.asType(MethodType.methodType(Object.class, receiverClass, int.class));
}
This uses the LambdaMetafactory to generate a Function instance for each getter, similar to equivalent method references. Then, an actual class calling the right Function’s apply method is instantiated and a method handle to its get method returned.
This is a similar composition as your method handles, but with the reference implementation, no handles but fully materialized classes are used. I’d expect the composed handles and this approach to converge to the same performance for a very large number of invocations, but the materialized classes having a headstart for a medium number of invocations.
I added a first parameter MethodHandles.Lookup lookup which should be the lookup object received by the bootstrap method for the invokedynamic instruction. If used that way, the generated functions can access all methods the same way as the code containing the invokedynamic instruction, including private methods of that class.
Alternatively, you can generate a class containing a real switch instruction yourself. Using the ASM library, it may look like:
private static MethodHandle makeProductElement(
MethodHandles.Lookup lookup, Class<?> receiverClass, List<MethodHandle> getters)
throws ReflectiveOperationException {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
cw.visit(V1_8, ACC_INTERFACE|ACC_ABSTRACT,
lookup.lookupClass().getName().replace('.', '/')+"$Switch", null,
"java/lang/Object", null);
MethodType type = MethodType.methodType(Object.class, receiverClass, int.class);
MethodVisitor mv = cw.visitMethod(ACC_STATIC|ACC_PUBLIC, "get",
type.toMethodDescriptorString(), null, null);
mv.visitCode();
Label defaultCase = new Label();
Label[] cases = new Label[getters.size()];
for(int ix = 0; ix < cases.length; ix++) cases[ix] = new Label();
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ILOAD, 1);
mv.visitTableSwitchInsn(0, cases.length - 1, defaultCase, cases);
String owner = receiverClass.getName().replace('.', '/');
for(int ix = 0; ix < cases.length; ix++) {
mv.visitLabel(cases[ix]);
MethodHandle mh = getters.get(ix);
mv.visitMethodInsn(INVOKEVIRTUAL, owner, lookup.revealDirect(mh).getName(),
mh.type().dropParameterTypes(0, 1).toMethodDescriptorString(), false);
if(mh.type().returnType().isPrimitive()) {
Class<?> boxed = mh.type().wrap().returnType();
MethodType box = MethodType.methodType(boxed, mh.type().returnType());
mv.visitMethodInsn(INVOKESTATIC, boxed.getName().replace('.', '/'),
"valueOf", box.toMethodDescriptorString(), false);
}
mv.visitInsn(ARETURN);
}
mv.visitLabel(defaultCase);
mv.visitTypeInsn(NEW, "java/lang/IndexOutOfBoundsException");
mv.visitInsn(DUP);
mv.visitVarInsn(ILOAD, 1);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/String",
"valueOf", "(I)Ljava/lang/String;", false);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IndexOutOfBoundsException",
"<init>", "(Ljava/lang/String;)V", false);
mv.visitInsn(ATHROW);
mv.visitMaxs(-1, -1);
mv.visitEnd();
cw.visitEnd();
lookup = lookup.defineHiddenClass(
cw.toByteArray(), true, MethodHandles.Lookup.ClassOption.NESTMATE);
return lookup.findStatic(lookup.lookupClass(), "get", type);
}
This generates a new class with a static method containing the tableswitch instruction and the invocations (as well as the boxing conversions we now have to do ourselves). Also, it has the necessary code to create and throw an exception for out-of-bounds values. After generating the class, it returns a handle to that static method.
I don't know of your timeline. But it is likely there will be a MethodHandles.tableSwitch operation in Java 17. It is currently being integrated via https://github.com/openjdk/jdk/pull/3401/
Some more discussion about it here:
https://mail.openjdk.java.net/pipermail/core-libs-dev/2021-April/076105.html
The things is, tableswitch isn't always compiled to a jump table. For a small number of labels, like in your example, it's likely to act as a binary search. Thus using a tree of regular "if-then" MethodHandles will be the closest equivalent.
I using Jacoco 0.8.6 and I have an enum with 3 values.
I have this when shown on the image below and I have a test for each branch but Jacoco is still saying that I am missing a branch, also my var is non-null. What is wrong?
JaCoCo performs analysis of bytecode. For Example.kt
object Example {
var type: Type = Type.SETUP_LOGIN
set(value) {
field = value
when (value) {
Type.SETUP_LOGIN -> nop()
Type.CHANGE_PIN -> nop()
Type.CHANGE_FINGERPRINT -> nop()
}
}
fun nop() {
}
enum class Type {
SETUP_LOGIN, CHANGE_PIN, CHANGE_FINGERPRINT
}
}
compiled by
kotlinc-1.4.30/bin/kotlinc Example.kt
execution of
java -v -p Example.class
shows following bytecode for setType method
public final void setType(Example$Type);
descriptor: (LExample$Type;)V
flags: ACC_PUBLIC, ACC_FINAL
Code:
stack=2, locals=2, args_size=2
0: aload_1
1: ldc #17 // String value
3: invokestatic #23 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
6: aload_1
7: putstatic #11 // Field type:LExample$Type;
10: aload_1
11: getstatic #29 // Field Example$WhenMappings.$EnumSwitchMapping$0:[I
14: swap
15: invokevirtual #35 // Method Example$Type.ordinal:()I
18: iaload
19: tableswitch { // 1 to 3
1: 44
2: 51
3: 58
default: 65
}
44: aload_0
45: invokevirtual #39 // Method nop:()V
48: goto 65
51: aload_0
52: invokevirtual #39 // Method nop:()V
55: goto 65
58: aload_0
59: invokevirtual #39 // Method nop:()V
62: goto 65
65: return
LineNumberTable:
line 4: 6
line 5: 10
line 6: 44
line 7: 51
line 8: 58
line 9: 65
line 10: 65
where as you can see tableswitch has 4 branches and this is exactly what you see in the JaCoCo report.
Looking at such bytecode only (as like JaCoCo does) there is no way to realize that default branch of tableswitch can't be reached - add one more value to enum, without changing anything else, recompile and you'll see exactly the same bytecode for this setType method.
You can use exhaustive
val <T> T.exhaustive: T
get() = this
then
when (value) {
Type.SETUP_LOGIN -> nop()
Type.CHANGE_PIN -> nop()
Type.CHANGE_FINGERPRINT -> nop()
}.exhaustive
I've been trying to convert a python project to nxc using the IDE: Brixc Command Center, so that it'll read a text file and split down the information into components where it can process it.
The main road block for me is the split string method which I cannot find/figure out.
in python it would be easy like Data1 = RawData.split("\n") where it would split it into a array and where I can sort through it like this:
Data1[nth position in array][character in nth position in selected value in array]
I tried doing repeating the same method in nxc but it doesn't work
1: #import "RawData.txt" Data0
2: string Data1[];
3: "task main(){
4: Data1 = Data0.split("\n");
5: if(Data1[1][0]=="a"){
6: TextOut(10,10,"its an a!");
7: }else{
8: TextOut(10,10,Data1[1][0]);
9: }
10: Wait(5000);
11:}
12:
the output should be the display of the first character of the second line in this case. surprisingly not, it doesn't work. and it spits out a few errors (I'm new to nxc after all).
line 3: Error: Datatypes are not compatible
line 3: Error: ';' expected
line 3: Error: Unmatched close parenthesis
line 4: Error: Unmatched close parenthesis
Just change the "a" to 'a'.
1: #import "RawData.txt" Data0
2: string Data1[];
3: task main(){
4: Data1 = Data0.split("\n");
5: if(Data1[1][0]=='a'){
6: TextOut(10,10,"its an a!");
7: }else{
8: TextOut(10,10,Data1[1][0]);
9: }
10: Wait(5000);
11:}
12:
" denotes a string (which is an array of characters) where as ' denotes a single character.
I'm using gcov to get code coverage for our project, but it frequently reports 50% conditional coverage for plain function calls. It doesn't make any difference if the function takes any parameters or returns any data or not. I'm using gcovr and Cobertura with Jenkins, but a simple gcov file gives the same result.
The actual tested code is attached below together with the stubbed functions, all in gcov format.
Any ideas why gcov threats these function calls as branches?
-: 146:/*****************************************************************************/
function _Z12mw_log_clearv called 2 returned 100% blocks executed 100%
2: 147:void mw_log_clear( void )
2: 147-block 0
-: 148:{
2: 149: uint8_t i = 0;
2: 150: uint8_t clear_tuple[EE_PAGE_SIZE] = { 0xff };
-: 151:
66: 152: for (i = 0; i < (int16_t)EE_PAGE_SIZE; i++)
2: 152-block 0
64: 152-block 1
66: 152-block 2
branch 0 taken 97%
branch 1 taken 3% (fallthrough)
-: 153: {
64: 154: clear_tuple[i] = 0xff;
-: 155: }
-: 156:
-: 157: /* Write pending data */
2: 158: mw_eeprom_write_blocking();
2: 158-block 0
call 0 returned 100%
branch 1 taken 100% (fallthrough) <---- This is a plain function call, not a branch
branch 2 taken 0% (throw) <---- This is a plain function call, not a branch
-: 159:
26: 160: for (i = 0; i < (RESERVED_PAGES_PER_PAREMETER_SET - POPULATED_PAGES_PER_PAREMETER_SET); i++)
2: 160-block 0
24: 160-block 1
26: 160-block 2
branch 0 taken 96%
branch 1 taken 4% (fallthrough)
-: 161: {
25: 162: if (status_ok != mw_eeprom_write(LOG_TUPLE_START_ADDRESS + i * EE_PAGE_SIZE, clear_tuple, sizeof(clear_tuple)))
25: 162-block 0
call 0 returned 100%
branch 1 taken 100% (fallthrough) <---- This is a plain function call, not a branch
branch 2 taken 0% (throw) <---- This is a plain function call, not a branch
25: 162-block 1
branch 3 taken 4% (fallthrough)
branch 4 taken 96%
-: 163: {
1: 164: mw_error_handler_add(mw_error_eeprom_busy);
1: 164-block 0
call 0 returned 100%
branch 1 taken 100% (fallthrough) <---- This is a plain function call, not a branch
branch 2 taken 0% (throw) <---- This is a plain function call, not a branch
1: 165: break;
1: 165-block 0
-: 166: }
-: 167:
24: 168: mw_eeprom_write_blocking();
24: 168-block 0
call 0 returned 100%
branch 1 taken 100% (fallthrough) <---- This is a plain function call, not a branch
branch 2 taken 0% (throw) <---- This is a plain function call, not a branch
-: 169: }
2: 170:}
2: 170-block 0
-: 171:
-: 172:/*****************************************************************************/
/*****************************************************************************/
void mw_eeprom_write_blocking(void)
{
stub_data.eeprom_write_blocking_calls++;
}
/*****************************************************************************/
void mw_error_handler_add(mw_error_code_t error_code)
{
EXPECT_EQ(error_code, stub_data.expected_error_code);
stub_data.registered_error_code = error_code;
}
/*****************************************************************************/
status_t mw_eeprom_write(
const uint32_t eeprom_start_index,
void *const source_start_address,
const uint32_t length)
{
stub_data.eeprom_write_start_index = eeprom_start_index;
stub_data.eeprom_write_length = length;
stub_data.eeprom_write_called = true;
EXPECT_NE(NULL, (uint32_t)source_start_address);
EXPECT_NE(0, length);
EXPECT_LE(eeprom_start_index + length, EEPROM_SIZE);
if (status_ok == stub_data.eeprom_write_status)
memcpy(&stub_data.eeprom[eeprom_start_index], source_start_address, length);
return stub_data.eeprom_write_status;
}
Solved!
Found the answer in this thread:
Why gcc 4.1 + gcov reports 100% branch coverage and newer (4.4, 4.6, 4.8) reports 50% for "p = new class;" line?
Seems like gcov reacted on some "invisible" exception handling code for these function calls, so adding "-fno-exceptions" to g++ made all these missing branches to disappear.
I'm trying to invoke a local method, pow(), as follows.
My class looks like this.
.class public helloworld
.super java/lang/Object
.method public pow(II)I
...
.end method
.method public foo()V
...
ldc 1
ldc 2
invokevirtual helloworld/pow(II)I
return
.end method
However, this results in the following error:
java.lang.VerifyError: (class: helloworld, method: foo signature: ()V) Unable to pop operand off an empty stack
What am I doing wrong here?
EDIT: My full code is below.
Note that pow() is a method that raises the first value to power of the second value:
val pow (val v, val pow) {
val result = v;
for (val i = 1; i < pow; i++ ) {
result = result * v;
}
return result;
}
Complete class:
.class public helloworld
.super java/lang/Object
.method public <init>()V
aload_0
invokenonvirtual java/lang/Object/<init>()V
return
.end method
.method public pow(II)I
.limit stack 9
.limit locals 5
iload 1
istore 3
ldc 1
istore 4
label16:
iload 4
iload 2
if_icmplt label22
iconst_0
goto label24
label22:
iconst_1
label24:
ifeq label36
goto label30
label27:
iinc 4 1
goto label16
label30:
iload 3
iload 1
imul
istore 3
goto label27
label36:
ireturn
.end method
.method public foo()V
.limit stack 3
.limit locals 3
ldc 1
ldc 2
invokevirtual helloworld/pow(II)I
return
.end method
.method public static main([Ljava/lang/String;)V
.limit stack 3
.limit locals 2
return
.end method
When you call an instance method, you have to put the object on the stack too.
aload 0
ldc 1
ldc 2
invokevirtual helloworld/pow(II)I