如何写一个KCP? -2

618 阅读1分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

字节码看起来是什么样子?

举个例子:

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

字节码的执行过程解析及说明如下:

  1. 返回了函数v1()的值并入栈 // v1()
  2. 返回了函数v2()的值并入栈 // v2()
  3. 执行了+操作并将v1()v2()的返回值弹出栈执行加操作并将和入栈 // +
  4. 执行了val sum = , 将栈中的和出栈并赋值给sum // val sum =
  5. 将标准输出stdout PrintStream入栈 // System.out
  6. StringBuilder对象入栈 // StringBuilder x
  7. 再入栈一个StringBuilder对象 // StringBuilder
  8. 调用栈顶StringBuilder的构建函数 // new StringBuilder()
  9. 读取字面值常量sum of values was // sum of values was
  10. 调用append函数, 将字面值写入栈 // x.append("sum of values was ")
  11. 加载sum值 // sum
  12. 再次调用append函数, 将sum值拼接 // x.append(sum)
  13. 调用toString函数, 将栈顶的StringBuilder弹出, 将String入栈 //y = x.toString()
  14. 将栈顶的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完成了!