本文已参与「新人创作礼」活动,一起开启掘金创作之路。
字节码看起来是什么样子?
举个例子:
Kotlin代码:
fun printSimpleSum() {
val sum = v1() + v2()
println("sum of values was $sum")
}
而这个函数的字节码为:
1. INVOKESTATIC myapp/RunnerKt.v1 ()I
2. INVOKESTATIC myapp/RunnerKt.v2 ()I
3. IADD
4. ISTORE 1
5. GETSTATIC j/l/System.out : Lj/io/PrintStream;
6. NEW j/l/StringBuilder
7. DUP
8. INVOKESPECIAL j/l/StringBuilder.<init> ()V
9. LDC "sum of values was "
10. INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder;
11. ILOAD 1
12. INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder;
13. INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String;
14. INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V
字节码的执行过程解析及说明如下:
- 返回了函数
v1()的值并入栈 //v1() - 返回了函数
v2()的值并入栈 //v2() - 执行了
+操作并将v1()和v2()的返回值弹出栈执行加操作并将和入栈 //+ - 执行了
val sum =, 将栈中的和出栈并赋值给sum//val sum = - 将标准输出
stdout PrintStream入栈 //System.out - 将
StringBuilder对象入栈 //StringBuilder x - 再入栈一个
StringBuilder对象 //StringBuilder - 调用栈顶
StringBuilder的构建函数 //new StringBuilder() - 读取字面值常量
sum of values was//sum of values was - 调用
append函数, 将字面值写入栈 //x.append("sum of values was ") - 加载
sum值 //sum - 再次调用
append函数, 将sum值拼接 //x.append(sum) - 调用
toString函数, 将栈顶的StringBuilder弹出, 将String入栈 //y = x.toString() - 将栈顶的
String出栈, 作为参数调用println函数 //println(y)
由此可见, 上面Kotlin代码其实的编译成Java代码为:
public void printSimpleSum() {
int sum = v1() + v2()
StringBuilder sb = new StringBuilder()//字符串的拼接编译成StringBuilder进行操作
sb.append("sum of values was ")
sb.append(sum)
System.out.println(sb.toString())
}
记住我们的目标
我们要做的是将
fun prime(n: Int): Long {
println("⇢ prime(n=$n)")
val startTime = System.currentTimeMillis()
val result = primeNumberSequence.take(n).last()
val timeToRun = System.currentTimeMillis() - startTime
println("⇠ prime [ran in $timeToRun ms]")
return result
}
复制代码
转化为如下所示:
@DebugLog fun prime(n: Int): Long = primeNumberSequence.take(n).last()
再回到我们的MethodVisitor
return object : MethodVisitor(Opcodes.ASM5, original) {
override fun visitCode() {
super.visitCode()
InstructionAdapter(this).apply {
TODO("on method entry")
}
}
override fun visitInsn(opcode: Int) {
when (opcode) {
RETURN , ARETURN, IRETURN -> {
InstructionAdapter(this).apply { TODO("on method exit") }
}
}
super.visitInsn(opcode)
}
}
对于TODO("on method entry"), 我们要在进入方法的时候, 执行代码:
println("⇢ prime(n=$n)")
val startTime = System.currentTimeMillis()
这些行函数的字节码操作为:
InstructionAdapter(this).apply { // method-trace-printing code
getstatic("j/l/System", "out", "Ljava/io/PrintStream;") // `System.out`
anew("j/l/StringBuilder") // `new StringBuilder()`
dup() // `StringBuilder`
invokespecial("j/l/StringBuilder", "<init>", "()V") // `StringBuilder()`
visitLdcInsn("⇢ ${function.name}(") // 加载常量 "⇢ ${function.name}("
invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/StringBuilder;") // 调用 `append("⇢ ${function.name}(")`
function.valueParameters.forEachIndexed { i, param ->
visitLdcInsn(" ${param.name}=") // 加载常量 " ${param.name}="
invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/SB;") // `append(" ${param.name}=")`
visitVarInsn(ALOAD, i + 1) // 加载 常量 `i + 1`
invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/SB;") // 'append(i + 1)'
}
invokevirtual("j/l/StringBuilder", "toString", "()Lj/l/String;") // `StringBuilder.toString`
invokevirtual("j/io/PrintStream", "println", "(Lj/l/String;)V") // `PrintStream.println(toString())`
}
以上打印了函数及其参数列表, 属于函数追踪打印的部分.
下面要添加函数计时的部分.
InstructionAdapter(this).apply { // timestamp-storing code
// ... method-trace-printing code
invokestatic("j/l/System", "currentTimeMillis", "()J") // `System.currentTimeMillis()`
store(9001, LONG_TYPE) // 赋值 `val startTime =`
}
以上为时间戳保存代码. 此时函数进入时的代码已经完成, 即:
override fun visitCode() {
super.visitCode()
InstructionAdapter(this).apply {
// ... method-trace-printing code
// ... timestamp-storing code
}
}
对于TODO("on method exit"), 我们要在退出方法之前, 执行代码:
val timeToRun = System.currentTimeMillis() - startTime
println("⇠ prime [ran in $timeToRun ms]")
这些函数的字节码执行过程为:
InstructionAdapter(this).apply { //benchmark-printing code
getstatic("j/l/System", "out", "Lj/io/PrintStream;") // `System.out`
anew("java/lang/StringBuilder") // `StringBuilder x `
dup() // ` = new StringBuilder()`
invokespecial("j/l/StringBuilder", "<init>", "()V") // `StringBuilder()`
visitLdcInsn("⇠ ${function.name} [ran in ") // load constant "⇠ ${function.name} [ran in "
invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") // `append("⇠ ${function.name} [ran in ")`
invokestatic("j/l/System", "currentTimeMillis", "()J") // `System.currentTimeMillis()`
load(9001, LONG_TYPE) // 将上面产生的时间戳入栈
sub(LONG_TYPE) // 将栈中的时间戳减去之前保存的时间戳
invokevirtual("j/l/StringBuilder", "append", "(J)Lj/l/StringBuilder;") // `append(时间差值)`
visitLdcInsn(" ms]") // load constant " ms]"
invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/SB;") // `append(" ms]")`
invokevirtual("j/l/StringBuilder", "toString", "()Lj/l/String;") // `StringBuilder.toString()`
invokevirtual("j/io/PrintStream", "println", "(Lj/l/String;)V") // `PrintStream.println(toString())`
}
以上打印了基准及时间差, 执行时机为函数返回之前, 即:
override fun visitInsn(opcode: Int) {
when (opcode) {
RETURN , ARETURN, IRETURN -> {
InstructionAdapter(this).apply { //...benchmark-printingGcode }
}
}
super.visitInsn(opcode)
}
好了, KCP完成了!