androidbytecodeandroid-runtime

Why register on the call-site is different from the implementation-site?


Let's consider a simple function:

fun main() {
    greet("world")    
}

fun greet(name: String?) {
    if (name != null) {
        println("Hello $name!")
    } else {
        println("Hello guest!")
    }
}

If I transform it to dex using D8, the bytecode will the following:

[000240] DemoKt.main:()V
0000: const-string v0, "world"
0002: invoke-static {v0}, LDemoKt;.greet:(Ljava/lang/String;)V
0005: return-void

[0001dc] DemoKt.greet:(Ljava/lang/String;)V
0000: if-eqz v2, 0021 // +0021
0002:   new-instance v0, Ljava/lang/StringBuilder;
0004:   invoke-direct {v0}, Ljava/lang/StringBuilder;.<init>:()V
0007:   const-string v1, "Hello "
0009:   invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
000c:   move-result-object v0
000d:   invoke-virtual {v0, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
0010:   move-result-object v0
0011:   const/16 v1, #int 33 // #21
0013:   invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;.append:(C)Ljava/lang/StringBuilder;
0016:   move-result-object v0
0017:   invoke-virtual {v0}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String;
001a:   move-result-object v0
001b:   sget-object v1, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0000
001d:   invoke-virtual {v1, v0}, Ljava/io/PrintStream;.println:(Ljava/lang/Object;)V
0020:   goto 0028 // +0008
0021: const-string v0, "Hello guest!"
0023: sget-object v1, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0000
0025: invoke-virtual {v1, v0}, Ljava/io/PrintStream;.println:(Ljava/lang/Object;)V
0028: return-void

I can see that "guest" constant string is loaded into register v0, but the greet function operates with v2. Why is that? Is there some sort of register remapping?


Solution

  • The N arguments to a method land in the LAST N registers of the method's invocation frame, in order.

    https://source.android.com/docs/core/runtime/dalvik-bytecode

    Your disassembly output doesn't say how many registers were allocated for the "greet" method, but based on the results, there must be three -- v0, v1, and v2.

    Every stack frame gets its own registers; the contents of those registers for one frame have no effect on what's going on in other frames. In other words, "world" remains in v0 for the "main" method, but is placed in v2 for the "greet" method, because that's how it's defined to work.