Writing Your First Kotlin Compiler Plugin Kevin Most A brief intro - - PowerPoint PPT Presentation

writing your first kotlin compiler plugin
SMART_READER_LITE
LIVE PREVIEW

Writing Your First Kotlin Compiler Plugin Kevin Most A brief intro - - PowerPoint PPT Presentation

Writing Your First Kotlin Compiler Plugin Kevin Most A brief intro Are these basically annotation processors? Annotation Processors: Compiler Plugins: Your code runs at compile-time Your code runs at compile-time Public,


slide-1
SLIDE 1

Writing Your First Kotlin Compiler Plugin

Kevin Most

slide-2
SLIDE 2

A brief intro

slide-3
SLIDE 3

Are these basically annotation processors?

  • Annotation Processors:
  • Your code runs at compile-time
  • Public, documented API
  • Emit Java source code
  • Works on Kotlin/Java source code
  • Multiplatform not supported
  • Compiler Plugins:
  • Your code runs at compile-time
  • Private, undocumented API
  • Emit Java bytecode (or LLVM IR)
  • Works on Kotlin source code only
  • Multiplatform supported
slide-4
SLIDE 4

Why write compiler plugins?

  • Incredibly powerful API; you can modify function/class internals
  • Enables you to solve new classes of metaprogramming problems
  • Annotation processors are JVM-only, while compiler plugins aren't
slide-5
SLIDE 5

Why NOT write compiler plugins?

  • Annotation processors are much easier to write (if you only care about JVM)
  • Compiler plugins are a lot of work. You need to write:
  • An IntelliJ plugin (if creating synthetic members)
  • A Gradle (or Maven, or other build tool) plugin
  • Slightly different extensions for JVM, JS, and Native targets
slide-6
SLIDE 6

Examples of compiler plugins

  • allopen: Modifies annotated class to be open
  • noarg: Modifies annotated class to have a zero-argument constructor
  • android-extensions: findViewById(R.id.foo) aliased, and automatic

Parcelable impl generation via @Parcelize

  • kotlin-serialization: Automatic generation of Serializable impl
  • First multiplatform-ready plugin (generates LLVM IR for native too)
slide-7
SLIDE 7
  • All existing compiler plugins are 1st party (github.com/JetBrains/kotlin)
  • plugins/{name}/... for the actual plugin business logic
  • libraries/tools/kotlin-{name}/... for the Gradle wrappers
  • libraries/tools/kotlin-maven-{name}/... for the Maven wrappers

Examples of compiler plugins

slide-8
SLIDE 8

Plugin Architecture

sPlugin

Subplugin CommandLineProcessor ComponentRegistrar Extension Extension

slide-9
SLIDE 9

Plugin Architecture

Plugin Subplugin CommandLineProcessor ComponentRegistrar Extension Extension

slide-10
SLIDE 10

Plugin Architecture

Plugin Subplugin CommandLineProcessor ComponentRegistrar Extension Extension

  • Gradle API (totally unrelated to

Kotlin)

  • Provides an entry point from a

build.gradle script

  • Allows configuration via Gradle

extensions

slide-11
SLIDE 11

Plugin Architecture

Plugin Subplugin CommandLineProcessor ComponentRegistrar Extension Extension

  • The interface between the Gradle

and Kotlin APIs

  • Read Gradle extension options
  • Write out Kotlin SubpluginOptions
  • Define the compiler plugin's ID

(internal unique key)

  • Define the Kotlin plugin's Maven

coordinates so the compiler can download it

slide-12
SLIDE 12

Plugin Architecture

Plugin Subplugin CommandLineProcessor ComponentRegistrar Extension Extension

  • Reads kotlinc -Xplugin args
  • Subplugin options actually get

passed through this pipeline

  • Write CompilerConfigurationKeys
slide-13
SLIDE 13

Plugin Architecture

Plugin Subplugin CommandLineProcessor ComponentRegistrar Extension Extension

  • Read CompilerConfigurationKeys
  • Register Extensions
slide-14
SLIDE 14

Plugin Architecture

Plugin Subplugin CommandLineProcessor ComponentRegistrar Extension Extension

  • Generates code (finally!)
  • Multiple types of extensions, such as:
  • ExpressionCodegenExtension
  • ClassBuilderInterceptorExtension
  • StorageComponentContainerContributor
  • IrGenerationExtension (!!)
  • Write bytecode (or LLVM IR!!)
slide-15
SLIDE 15

Let's build our own!

slide-16
SLIDE 16

Let's build our own!

  • We'll build a compiler plugin that traces method calls
  • A method annotated with a debug-log annotation will have its method body

modified to include logging

  • Could not be an annotation processor; modifies the function body
  • Prior art:
  • Hugo: github.com/jakewharton/hugo
  • Firebase Performance Monitoring: firebase.google.com/docs/perf-mon
  • Both use AspectJ bytecode weaving + Android Gradle Transform API
slide-17
SLIDE 17

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 }

The goal

slide-18
SLIDE 18

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 }

The goal

slide-19
SLIDE 19

@DebugLog fun prime(n: Int): Long = primeNumberSequence.take(n).last() 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 }

The goal

slide-20
SLIDE 20

Let's build our own!

slide-21
SLIDE 21

Plugin Subplugin CommandLineProcessor ComponentRegistrar Extension Extension

slide-22
SLIDE 22

gradle-plugin/build.gradle

apply plugin: "java-gradle-plugin" apply plugin: "org.jetbrains.kotlin.jvm" apply plugin: "kotlin-kapt" gradlePlugin { plugins { simplePlugin { id = "debuglog.plugin" implementationClass = "debuglog.DebugLogGradlePlugin" } } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$ktVersion" implementation "org.jetbrains.kotlin:kotlin-gradle-plugin-api:$ktVersion" compileOnly "com.google.auto.service:auto-service:1.0-rc4" kapt "com.google.auto.service:auto-service:1.0-rc4" }

slide-23
SLIDE 23

apply plugin: "java-gradle-plugin" apply plugin: "org.jetbrains.kotlin.jvm" apply plugin: "kotlin-kapt" gradlePlugin { plugins { simplePlugin { id = "debuglog.plugin" implementationClass = "debuglog.DebugLogGradlePlugin" } } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$ktVersion" implementation "org.jetbrains.kotlin:kotlin-gradle-plugin-api:$ktVersion" compileOnly "com.google.auto.service:auto-service:1.0-rc4" kapt "com.google.auto.service:auto-service:1.0-rc4" }

gradle-plugin/build.gradle

slide-24
SLIDE 24

apply plugin: "java-gradle-plugin" apply plugin: "org.jetbrains.kotlin.jvm" apply plugin: "kotlin-kapt" gradlePlugin { plugins { simplePlugin { id = "debuglog.plugin" // `apply plugin: "debuglog.plugin"` implementationClass = "debuglog.DebugLogGradlePlugin" } } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$ktVersion" implementation "org.jetbrains.kotlin:kotlin-gradle-plugin-api:$ktVersion" compileOnly "com.google.auto.service:auto-service:1.0-rc4" kapt "com.google.auto.service:auto-service:1.0-rc4" }

gradle-plugin/build.gradle

slide-25
SLIDE 25

apply plugin: "java-gradle-plugin" apply plugin: "org.jetbrains.kotlin.jvm" apply plugin: "kotlin-kapt" gradlePlugin { plugins { simplePlugin { id = "debuglog.plugin" // `apply plugin: "debuglog.plugin"` implementationClass = "debuglog.DebugLogGradlePlugin" // entry-point class } } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$ktVersion" implementation "org.jetbrains.kotlin:kotlin-gradle-plugin-api:$ktVersion" compileOnly "com.google.auto.service:auto-service:1.0-rc4" kapt "com.google.auto.service:auto-service:1.0-rc4" }

gradle-plugin/build.gradle

slide-26
SLIDE 26

gradle-plugin/build.gradle

apply plugin: "java-gradle-plugin" apply plugin: "org.jetbrains.kotlin.jvm" apply plugin: "kotlin-kapt" gradlePlugin { plugins { simplePlugin { id = "debuglog.plugin" // `apply plugin: "debuglog.plugin"` implementationClass = "debuglog.DebugLogGradlePlugin" // entry-point class } } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$ktVersion" implementation "org.jetbrains.kotlin:kotlin-gradle-plugin-api:$ktVersion" compileOnly "com.google.auto.service:auto-service:1.0-rc4" kapt "com.google.auto.service:auto-service:1.0-rc4" }

slide-27
SLIDE 27

gradle-plugin/build.gradle

apply plugin: "java-gradle-plugin" apply plugin: "org.jetbrains.kotlin.jvm" apply plugin: "kotlin-kapt" gradlePlugin { plugins { simplePlugin { id = "debuglog.plugin" // `apply plugin: "debuglog.plugin"` implementationClass = "debuglog.DebugLogGradlePlugin" // entry-point class } } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$ktVersion" implementation "org.jetbrains.kotlin:kotlin-gradle-plugin-api:$ktVersion" compileOnly "com.google.auto.service:auto-service:1.0-rc4" kapt "com.google.auto.service:auto-service:1.0-rc4" }

slide-28
SLIDE 28

Plugin Subplugin CommandLineProcessor ComponentRegistrar Extension Extension

slide-29
SLIDE 29

class DebugLogGradlePlugin : org.gradle.api.Plugin<Project> {

  • verride fun apply(project: Project) {

project.extensions.create( "debugLog", DebugLogGradleExtension::class.java ) } }

  • pen class DebugLogGradleExtension {

var enabled: Boolean = true var annotations: List<String> = emptyList() }

slide-30
SLIDE 30

Plugin Subplugin CommandLineProcessor ComponentRegistrar Extension Extension

slide-31
SLIDE 31

@AutoService(KotlinGradleSubplugin::class) // don't forget! class DebugLogGradleSubplugin : KotlinGradleSubplugin<AbstractCompile> {

  • verride fun isApplicable(

project: Project, task: AbstractCompile ): Boolean = TODO()

  • verride fun getCompilerPluginId(): String = TODO()
  • verride fun getPluginArtifact(): SubpluginArtifact = TODO()
  • verride fun apply(project: Project, /*...*/): List<SubpluginOption> {

TODO() } }

slide-32
SLIDE 32

@AutoService(KotlinGradleSubplugin::class) // don't forget! class DebugLogGradleSubplugin : KotlinGradleSubplugin<AbstractCompile> {

  • verride fun isApplicable(

project: Project, task: AbstractCompile ): Boolean = TODO()

  • verride fun getCompilerPluginId(): String = TODO()
  • verride fun getPluginArtifact(): SubpluginArtifact = TODO()
  • verride fun apply(project: Project, /*...*/): List<SubpluginOption> {

TODO() } }

slide-33
SLIDE 33

@AutoService(KotlinGradleSubplugin::class) // don't forget! class DebugLogGradleSubplugin : KotlinGradleSubplugin<AbstractCompile> {

  • verride fun isApplicable(

project: Project, task: AbstractCompile ): Boolean = project.plugins.hasPlugin(DebugLogGradlePlugin::class.java)

  • verride fun getCompilerPluginId(): String = TODO()
  • verride fun getPluginArtifact(): SubpluginArtifact = TODO()
  • verride fun apply(project: Project, /*...*/): List<SubpluginOption> {

TODO() } }

slide-34
SLIDE 34

@AutoService(KotlinGradleSubplugin::class) // don't forget! class DebugLogGradleSubplugin : KotlinGradleSubplugin<AbstractCompile> {

  • verride fun isApplicable(

project: Project, task: AbstractCompile ): Boolean = project.plugins.hasPlugin(DebugLogGradlePlugin::class.java)

  • verride fun getCompilerPluginId(): String = TODO()
  • verride fun getPluginArtifact(): SubpluginArtifact = TODO()
  • verride fun apply(project: Project, /*...*/): List<SubpluginOption> {

TODO() } }

slide-35
SLIDE 35

@AutoService(KotlinGradleSubplugin::class) // don't forget! class DebugLogGradleSubplugin : KotlinGradleSubplugin<AbstractCompile> {

  • verride fun isApplicable(

project: Project, task: AbstractCompile ): Boolean = project.plugins.hasPlugin(DebugLogGradlePlugin::class.java)

  • verride fun getCompilerPluginId(): String = TODO()
  • verride fun getPluginArtifact(): SubpluginArtifact = TODO()
  • verride fun apply(project: Project, /*...*/): List<SubpluginOption> {

TODO() } }

slide-36
SLIDE 36

@AutoService(KotlinGradleSubplugin::class) // don't forget! class DebugLogGradleSubplugin : KotlinGradleSubplugin<AbstractCompile> {

  • verride fun isApplicable(

project: Project, task: AbstractCompile ): Boolean = project.plugins.hasPlugin(DebugLogGradlePlugin::class.java)

  • verride fun getCompilerPluginId(): String = "debuglog"
  • verride fun getPluginArtifact(): SubpluginArtifact = TODO()
  • verride fun apply(project: Project, /*...*/): List<SubpluginOption> {

TODO() } }

slide-37
SLIDE 37

@AutoService(KotlinGradleSubplugin::class) // don't forget! class DebugLogGradleSubplugin : KotlinGradleSubplugin<AbstractCompile> {

  • verride fun isApplicable(

project: Project, task: AbstractCompile ): Boolean = project.plugins.hasPlugin(DebugLogGradlePlugin::class.java)

  • verride fun getCompilerPluginId(): String = "debuglog"
  • verride fun getPluginArtifact(): SubpluginArtifact = TODO()
  • verride fun apply(project: Project, /*...*/): List<SubpluginOption> {

TODO() } }

slide-38
SLIDE 38

@AutoService(KotlinGradleSubplugin::class) // don't forget! class DebugLogGradleSubplugin : KotlinGradleSubplugin<AbstractCompile> {

  • verride fun isApplicable(

project: Project, task: AbstractCompile ): Boolean = project.plugins.hasPlugin(DebugLogGradlePlugin::class.java)

  • verride fun getCompilerPluginId(): String = "debuglog"
  • verride fun getPluginArtifact(): SubpluginArtifact = TODO()
  • verride fun apply(project: Project, /*...*/): List<SubpluginOption> {

TODO() }2 }1

slide-39
SLIDE 39

@AutoService(KotlinGradleSubplugin::class) // don't forget! class DebugLogGradleSubplugin : KotlinGradleSubplugin<AbstractCompile> {

  • verride fun isApplicable(

project: Project, task: AbstractCompile ): Boolean = project.plugins.hasPlugin(DebugLogGradlePlugin::class.java)

  • verride fun getCompilerPluginId(): String = "debuglog"
  • verride fun getPluginArtifact(): SubpluginArtifact = SubpluginArtifact(

groupId = "debuglog", artifactId = "kotlin-plugin", version = "0.0.1" )

  • verride fun apply(project: Project, /*...*/): List<SubpluginOption> {

TODO() }2 }1

slide-40
SLIDE 40

@AutoService(KotlinGradleSubplugin::class) // don't forget! class DebugLogGradleSubplugin : KotlinGradleSubplugin<AbstractCompile> {

  • verride fun isApplicable(

project: Project, task: AbstractCompile ): Boolean = project.plugins.hasPlugin(DebugLogGradlePlugin::class.java)

  • verride fun getCompilerPluginId(): String = "debuglog"
  • verride fun getPluginArtifact(): SubpluginArtifact = SubpluginArtifact(

groupId = "debuglog", artifactId = "kotlin-plugin", version = "0.0.1" )

  • verride fun apply(project: Project, /*...*/): List<SubpluginOption> {

TODO() } }

slide-41
SLIDE 41

@AutoService(KotlinGradleSubplugin::class) // don't forget! class DebugLogGradleSubplugin1:1KotlinGradleSubplugin<AbstractCompile>1{1

  • verride fun isApplicable(

project: Project, task: AbstractCompile ): Boolean = project.plugins.hasPlugin(DebugLogGradlePlugin::class.java)

  • verride fun getCompilerPluginId(): String = "debuglog"
  • verride fun getPluginArtifact(): SubpluginArtifact = SubpluginArtifact(

groupId = "debuglog", artifactId = "kotlin-plugin", version = "0.0.1" )

  • verride fun apply(project: Project, /*...*/): List<SubpluginOption>1{

TODO() }2 }1

slide-42
SLIDE 42

@AutoService(KotlinGradleSubplugin::class) // don't forget! class DebugLogGradleSubplugin1:1KotlinGradleSubplugin<AbstractCompile>1{1 // other method impls

  • verride fun apply(project: Project, /*...*/): List<SubpluginOption>1{

TODO()Z }2 }1

slide-43
SLIDE 43

@AutoService(KotlinGradleSubplugin::class) // don't forget! class DebugLogGradleSubplugin1:1KotlinGradleSubplugin<AbstractCompile>1{1 // other method impls

  • verride fun apply(project: Project, /*...*/): List<SubpluginOption>1{

val extension = project.extensions.findByType<DebugLogGradleExtension>() ?: DebugLogGradleExtension() if (extension.enabled && extension.annotations.isEmpty()) error("DebugLog is enabled, but no annotations were set") val annotationOptions = extension.annotations .map { SubpluginOption(key = "debugLogAnnotation", value = it) } val enabledOption = SubpluginOption( key = "enabled", value = extension.enabled.toString()) return annotationOptions + enabledOption }2 }1

slide-44
SLIDE 44

@AutoService(KotlinGradleSubplugin::class) // don't forget! class DebugLogGradleSubplugin1:1KotlinGradleSubplugin<AbstractCompile>1{1 // other method impls

  • verride fun apply(project: Project, /*...*/): List<SubpluginOption>1{

val extension = project.extensions.findByType<DebugLogGradleExtension>() ?: DebugLogGradleExtension() if (extension.enabled && extension.annotations.isEmpty()) error("DebugLog is enabled, but no annotations were set") val annotationOptions = extension.annotations .map { SubpluginOption(key = "debugLogAnnotation", value = it) } val enabledOption = SubpluginOption( key = "enabled", value = extension.enabled.toString()) return annotationOptions + enabledOption }2 }1

slide-45
SLIDE 45

@AutoService(KotlinGradleSubplugin::class) // don't forget! class DebugLogGradleSubplugin1:1KotlinGradleSubplugin<AbstractCompile>1{1 // other method impls

  • verride fun apply(project: Project, /*...*/): List<SubpluginOption>1{

val extension = project.extensions.findByType<DebugLogGradleExtension>() ?: DebugLogGradleExtension() if (extension.enabled && extension.annotations.isEmpty()) error("DebugLog is enabled, but no annotations were set") val annotationOptions = extension.annotations .map { SubpluginOption(key = "debugLogAnnotation", value = it) } val enabledOption = SubpluginOption( key = "enabled", value = extension.enabled.toString()) return annotationOptions + enabledOption }2 }1

slide-46
SLIDE 46

@AutoService(KotlinGradleSubplugin::class) // don't forget! class DebugLogGradleSubplugin1:1KotlinGradleSubplugin<AbstractCompile>1{1 // other method impls

  • verride fun apply(project: Project, /*...*/): List<SubpluginOption>1{

val extension = project.extensions.findByType<DebugLogGradleExtension>() ?: DebugLogGradleExtension() if (extension.enabled && extension.annotations.isEmpty()) error("DebugLog is enabled, but no annotations were set") val annotationOptions = extension.annotations .map { SubpluginOption(key = "debugLogAnnotation", value = it) } val enabledOption = SubpluginOption( key = "enabled", value = extension.enabled.toString()) return annotationOptions + enabledOption }2 }1

slide-47
SLIDE 47

Plugin Subplugin CommandLineProcessor ComponentRegistrar Extension Extension

slide-48
SLIDE 48

kotlin-plugin/build.gradle

apply plugin: "org.jetbrains.kotlin.jvm" apply plugin: "kotlin-kapt" dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$ktVersion" compileOnly "org.jetbrains.kotlin:kotlin-compiler-embeddable:$ktVersion" compileOnly "com.google.auto.service:auto-service:1.0-rc4" kapt "com.google.auto.service:auto-service:1.0-rc4" }

slide-49
SLIDE 49

kotlin-plugin/build.gradle

apply plugin: "org.jetbrains.kotlin.jvm" apply plugin: "kotlin-kapt" dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$ktVersion" compileOnly "org.jetbrains.kotlin:kotlin-compiler-embeddable:$ktVersion" compileOnly "com.google.auto.service:auto-service:1.0-rc4" kapt "com.google.auto.service:auto-service:1.0-rc4" }

slide-50
SLIDE 50

kotlin-plugin/build.gradle

apply plugin: "org.jetbrains.kotlin.jvm" apply plugin: "kotlin-kapt" dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$ktVersion" compileOnly "org.jetbrains.kotlin:kotlin-compiler-embeddable:$ktVersion" compileOnly "com.google.auto.service:auto-service:1.0-rc4" kapt "com.google.auto.service:auto-service:1.0-rc4" }

slide-51
SLIDE 51

kotlin-plugin/build.gradle

apply plugin: "org.jetbrains.kotlin.jvm" apply plugin: "kotlin-kapt" dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$ktVersion" compileOnly "org.jetbrains.kotlin:kotlin-compiler-embeddable:$ktVersion" compileOnly "com.google.auto.service:auto-service:1.0-rc4" kapt "com.google.auto.service:auto-service:1.0-rc4" }

slide-52
SLIDE 52

Plugin Subplugin CommandLineProcessor ComponentRegistrar Extension Extension

slide-53
SLIDE 53

@AutoService(CommandLineProcessor::class) class DebugLogCommandLineProcessor : CommandLineProcessor {

  • verride val pluginId: String = TODO()
  • verride val pluginOptions: Collection<CliOption> = listOf(

)

  • verride fun processOption(
  • ption: CliOption,

value: String, configuration: CompilerConfiguration ) { TODO() } }

slide-54
SLIDE 54

@AutoService(CommandLineProcessor::class) class DebugLogCommandLineProcessor : CommandLineProcessor {

  • verride val pluginId: String = TODO()
  • verride val pluginOptions: Collection<CliOption> = listOf(

)

  • verride fun processOption(
  • ption: CliOption,

value: String, configuration: CompilerConfiguration ) { TODO() } }

slide-55
SLIDE 55

@AutoService(CommandLineProcessor::class) class DebugLogCommandLineProcessor : CommandLineProcessor {

  • verride val pluginId: String = "debuglog" // same as ID from subplugin
  • verride val pluginOptions: Collection<CliOption> = listOf(

)

  • verride fun processOption(
  • ption: CliOption,

value: String, configuration: CompilerConfiguration ) { TODO() } }

slide-56
SLIDE 56

@AutoService(CommandLineProcessor::class) class DebugLogCommandLineProcessor : CommandLineProcessor {

  • verride val pluginId: String = "debuglog" // same as ID from subplugin
  • verride val pluginOptions: Collection<CliOption> = listOf(

)

  • verride fun processOption(
  • ption: CliOption,

value: String, configuration: CompilerConfiguration ) { TODO() } }

slide-57
SLIDE 57

@AutoService(CommandLineProcessor::class) class DebugLogCommandLineProcessor : CommandLineProcessor {

  • verride val pluginId: String = "debuglog" // same as ID from subplugin
  • verride val pluginOptions: Collection<CliOption> = listOf(

)P

  • verride fun processOption(
  • ption: CliOption,

value: String, configuration: CompilerConfiguration ) { TODO() }2 }1

slide-58
SLIDE 58

@AutoService(CommandLineProcessor::class) class DebugLogCommandLineProcessor : CommandLineProcessor {

  • verride val pluginId: String = "debuglog" // same as ID from subplugin
  • verride val pluginOptions: Collection<CliOption> = listOf(

CliOption("enabled", "<true|false>", "whether plugin is enabled"), CliOption( "debugLogAnnotation", "<fqname>", "debug-log annotation names", required = true, allowMultipleOccurrences = true))P

  • verride fun processOption(
  • ption: CliOption,

value: String, configuration: CompilerConfiguration ) { TODO() }2 }1

slide-59
SLIDE 59

@AutoService(CommandLineProcessor::class) class DebugLogCommandLineProcessor : CommandLineProcessor {

  • verride val pluginId: String = "debuglog" // same as ID from subplugin
  • verride val pluginOptions: Collection<CliOption> = listOf(

CliOption("enabled", "<true|false>", "whether plugin is enabled"), CliOption( "debugLogAnnotation", "<fqname>", "debug-log annotation names", required = true, allowMultipleOccurrences = true))P

  • verride fun processOption(
  • ption: CliOption,

value: String, configuration: CompilerConfiguration ) { TODO() } }

slide-60
SLIDE 60

@AutoService(CommandLineProcessor::class) class DebugLogCommandLineProcessor : CommandLineProcessor {

  • verride val pluginId: String = "debuglog" // same as ID from subplugin
  • verride val pluginOptions: Collection<CliOption> = listOf(

CliOption("enabled", "<true|false>", "whether plugin is enabled"), CliOption( "debugLogAnnotation", "<fqname>", "debug-log annotation names", required = true, allowMultipleOccurrences = true))P

  • verride fun processOption(
  • ption: CliOption,

value: String, configuration: CompilerConfiguration ) { TODO() } }

slide-61
SLIDE 61

@AutoService(CommandLineProcessor::class) class DebugLogCommandLineProcessor : CommandLineProcessor {

  • verride val pluginId: String = "debuglog" // same as ID from subplugin
  • verride val pluginOptions: Collection<CliOption> = listOf(

CliOption("enabled", "<true|false>", "whether plugin is enabled"), CliOption( "debugLogAnnotation", "<fqname>", "debug-log annotation names", required = true, allowMultipleOccurrences = true))P

  • verride fun processOption(
  • ption: CliOption,

value: String, configuration: CompilerConfiguration ) = when (option.name) { } }

slide-62
SLIDE 62

@AutoService(CommandLineProcessor::class) class DebugLogCommandLineProcessor : CommandLineProcessor {

  • verride val pluginId: String = "debuglog" // same as ID from subplugin
  • verride val pluginOptions: Collection<CliOption> = listOf(

CliOption("enabled", "<true|false>", "whether plugin is enabled"), CliOption( "debugLogAnnotation", "<fqname>", "debug-log annotation names", required = true, allowMultipleOccurrences = true))P

  • verride fun processOption(
  • ption: CliOption,

value: String, configuration: CompilerConfiguration ) = when (option.name) { "enabled" -> configuration.put(KEY_ENABLED, value.toBoolean()) }2 }1

slide-63
SLIDE 63

@AutoService(CommandLineProcessor::class) class DebugLogCommandLineProcessor : CommandLineProcessor {

  • verride val pluginId: String = "debuglog" // same as ID from subplugin
  • verride val pluginOptions: Collection<CliOption> = listOf(

CliOption("enabled", "<true|false>", "whether plugin is enabled"), CliOption( "debugLogAnnotation", "<fqname>", "debug-log annotation names", required = true, allowMultipleOccurrences = true))P

  • verride fun processOption(
  • ption: CliOption,

value: String, configuration: CompilerConfiguration ) = when (option.name) { "enabled" -> configuration.put(KEY_ENABLED, value.toBoolean()) "debugLogAnnotation" -> configuration.appendList(KEY_ANNOTATIONS, value) }2 }1

slide-64
SLIDE 64

@AutoService(CommandLineProcessor::class) class DebugLogCommandLineProcessor : CommandLineProcessor {

  • verride val pluginId: String = "debuglog" // same as ID from subplugin
  • verride val pluginOptions: Collection<CliOption> = listOf(

CliOption("enabled", "<true|false>", "whether plugin is enabled"), CliOption( "debugLogAnnotation", "<fqname>", "debug-log annotation names", required = true, allowMultipleOccurrences = true))P

  • verride fun processOption(
  • ption: CliOption,

value: String, configuration: CompilerConfiguration ) = when (option.name) { "enabled" -> configuration.put(KEY_ENABLED, value.toBoolean()) "debugLogAnnotation" -> configuration.appendList(KEY_ANNOTATIONS, value) else -> error("Unexpected config option ${option.name}") }2 }1

slide-65
SLIDE 65

Plugin Subplugin CommandLineProcessor ComponentRegistrar Extension Extension

slide-66
SLIDE 66

@AutoService(ComponentRegistrar::class) class DebugLogComponentRegistrar : ComponentRegistrar {

  • verride fun registerProjectComponents(

project: MockProject, configuration: CompilerConfiguration ) { if (configuration[KEY_ENABLED] == false) { return } ClassBuilderInterceptorExtension.registerExtension( project, DebugLogClassGenerationInterceptor( debugLogAnnotations = configuration[KEY_ANNOTATIONS] ?: error("debuglog plugin requires at least one annotation class option passed to it") ) ) } }

slide-67
SLIDE 67

@AutoService(ComponentRegistrar::class) class DebugLogComponentRegistrar : ComponentRegistrar {

  • verride fun registerProjectComponents(

project: MockProject, configuration: CompilerConfiguration ) { if (configuration[KEY_ENABLED] == false) { return } ClassBuilderInterceptorExtension.registerExtension( project, DebugLogClassGenerationInterceptor( debugLogAnnotations = configuration[KEY_ANNOTATIONS] ?: error("debuglog plugin requires at least one annotation class option passed to it") ) ) } }

slide-68
SLIDE 68

Plugin Subplugin CommandLineProcessor ComponentRegistrar Extension Extension

slide-69
SLIDE 69

class DebugLogClassGenerationInterceptor( val debugLogAnnotations: List<String> ) : ClassBuilderInterceptorExtension {

  • verride fun interceptClassBuilderFactory(

interceptedFactory: ClassBuilderFactory, bindingContext: BindingContext, diagnostics: DiagnosticSink ): ClassBuilderFactory = TODO() }

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassGenerationInterceptor.kt

slide-70
SLIDE 70

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassGenerationInterceptor.kt

class DebugLogClassGenerationInterceptor( val debugLogAnnotations: List<String> ) : ClassBuilderInterceptorExtension {

  • verride fun interceptClassBuilderFactory(

interceptedFactory: ClassBuilderFactory, bindingContext: BindingContext, diagnostics: DiagnosticSink ): ClassBuilderFactory = object: ClassBuilderFactory by interceptedFactory }1

slide-71
SLIDE 71

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassGenerationInterceptor.kt

class DebugLogClassGenerationInterceptor( val debugLogAnnotations: List<String> ) : ClassBuilderInterceptorExtension {

  • verride fun interceptClassBuilderFactory(

interceptedFactory: ClassBuilderFactory, bindingContext: BindingContext, diagnostics: DiagnosticSink ): ClassBuilderFactory = object: ClassBuilderFactory by interceptedFactory {

  • verride fun newClassBuilder(origin: JvmDeclarationOrigin) =

DebugLogClassBuilder( annotations = debugLogAnnotations, delegateBuilder = interceptedFactory.newClassBuilder(origin)) } }1

slide-72
SLIDE 72

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

private class DebugLogClassBuilder( val annotations: List<String>, delegateBuilder: ClassBuilder ) : DelegatingClassBuilder(delegateBuilder) {

  • verride fun newMethod(
  • rigin: JvmDeclarationOrigin, access: Int,

name: String, desc: String, signature: String?, exceptions: Array<out String>? ): MethodVisitor { }1 }2

slide-73
SLIDE 73

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

private class DebugLogClassBuilder( val annotations: List<String>, delegateBuilder: ClassBuilder ) : DelegatingClassBuilder(delegateBuilder) {

  • verride fun newMethod(
  • rigin: JvmDeclarationOrigin,...

): MethodVisitor { }1 }2

slide-74
SLIDE 74

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

private class DebugLogClassBuilder( val annotations: List<String>, delegateBuilder: ClassBuilder ) : DelegatingClassBuilder(delegateBuilder) {

  • verride fun newMethod(
  • rigin: JvmDeclarationOrigin,...

): MethodVisitor { val original = super.newMethod(origin, ...) val function = origin.descriptor as? FunctionDescriptor ?: return original if (annotations.none { descriptor.annotations.hasAnnotation(it) }) { return original } }1 }2

slide-75
SLIDE 75

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

private class DebugLogClassBuilder( val annotations: List<String>, delegateBuilder: ClassBuilder ) : DelegatingClassBuilder(delegateBuilder) {

  • verride fun newMethod(
  • rigin: JvmDeclarationOrigin,...

): MethodVisitor { val original = super.newMethod(origin, ...) val function = origin.descriptor as? FunctionDescriptor ?: return original if (annotations.none { descriptor.annotations.hasAnnotation(it) }) { return original } return object : MethodVisitor(Opcodes.ASM5, original) { }3 }1 }2

slide-76
SLIDE 76

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

return object : MethodVisitor(Opcodes.ASM5, original) { }3

slide-77
SLIDE 77

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

return object : MethodVisitor(Opcodes.ASM5, original) {

  • verride fun visitCode() {

super.visitCode() InstructionAdapter(this).apply { TODO("on method entry") } }4 }3

slide-78
SLIDE 78

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

return object : MethodVisitor(Opcodes.ASM5, original) {

  • verride fun visitCode() {

super.visitCode() InstructionAdapter(this).apply { TODO("on method entry") } }4

  • verride fun visitInsn(opcode: Int) {

when (opcode) { RETURN /* void */, ARETURN /* object */, IRETURN /* int */ -> { InstructionAdapter(this).apply { TODO("on method exit") } } } super.visitInsn(opcode) } }3

slide-79
SLIDE 79

What now?

  • You write bytecode
  • Uses the ObjectWeb ASM API
  • Neither related to ASM (assembly) or Web ASM (wasm) in any way
  • An API for modifying JVM bytecode
  • The JVM is a stack machine
  • One stack that methods operate upon
  • You can also read arbitrary variables from the Local Variable Array
slide-80
SLIDE 80

What does bytecode look like?

fun printSimpleSum() { val sum = v1() + v2() println("sum of values was $sum") }

INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V

slide-81
SLIDE 81

What does bytecode look like?

fun printSimpleSum() { val sum = v1() + v2() println("sum of values was $sum") }

INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V

slide-82
SLIDE 82

What does bytecode look like?

fun printSimpleSum() { val sum = v1() + v2() println("sum of values was $sum") }

INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V

return value of v1()

slide-83
SLIDE 83

What does bytecode look like?

fun printSimpleSum() { val sum = v1() + v2() println("sum of values was $sum") }

INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V

return value of v1() return value of v2()

slide-84
SLIDE 84

What does bytecode look like?

fun printSimpleSum() { val sum = v1() + v2() println("sum of values was $sum") }

INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V

return value of v1() return value of v2() v1() + v2()

slide-85
SLIDE 85

What does bytecode look like?

fun printSimpleSum() { val sum = v1() + v2() println("sum of values was $sum") }

INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V

v1() + v2()

slide-86
SLIDE 86

What does bytecode look like?

fun printSimpleSum() { val sum = v1() + v2() println("sum of values was $sum") }

INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V

stdout PrintStream

slide-87
SLIDE 87

What does bytecode look like?

fun printSimpleSum() { val sum = v1() + v2() println("sum of values was $sum") }

INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V

stdout PrintStream StringBuilder

slide-88
SLIDE 88

What does bytecode look like?

fun printSimpleSum() { val sum = v1() + v2() println("sum of values was $sum") }

INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V

stdout PrintStream StringBuilder StringBuilder

slide-89
SLIDE 89

What does bytecode look like?

fun printSimpleSum() { val sum = v1() + v2() println("sum of values was $sum") }

INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V

stdout PrintStream StringBuilder StringBuilder

slide-90
SLIDE 90

What does bytecode look like?

fun printSimpleSum() { val sum = v1() + v2() println("sum of values was $sum") }

INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V

stdout PrintStream StringBuilder

"sum of values was "

slide-91
SLIDE 91

What does bytecode look like?

fun printSimpleSum() { val sum = v1() + v2() println("sum of values was $sum") }

INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V

stdout PrintStream StringBuilder

"sum of values was "

slide-92
SLIDE 92

What does bytecode look like?

fun printSimpleSum() { val sum = v1() + v2() println("sum of values was $sum") }

INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V

stdout PrintStream StringBuilder

slide-93
SLIDE 93

What does bytecode look like?

fun printSimpleSum() { val sum = v1() + v2() println("sum of values was $sum") }

INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V

stdout PrintStream StringBuilder v1() + v2()

slide-94
SLIDE 94

What does bytecode look like?

fun printSimpleSum() { val sum = v1() + v2() println("sum of values was $sum") }

INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V

stdout PrintStream StringBuilder v1() + v2()

slide-95
SLIDE 95

What does bytecode look like?

fun printSimpleSum() { val sum = v1() + v2() println("sum of values was $sum") }

INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V

stdout PrintStream StringBuilder

slide-96
SLIDE 96

What does bytecode look like?

fun printSimpleSum() { val sum = v1() + v2() println("sum of values was $sum") }

INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V

stdout PrintStream StringBuilder String

slide-97
SLIDE 97

What does bytecode look like?

fun printSimpleSum() { val sum = v1() + v2() println("sum of values was $sum") }

INVOKESTATIC myapp/RunnerKt.v1 ()I INVOKESTATIC myapp/RunnerKt.v2 ()I IADD ISTORE 1 GETSTATIC j/l/System.out : Lj/io/PrintStream; NEW j/l/StringBuilder DUP INVOKESPECIAL j/l/StringBuilder.<init> ()V LDC "sum of values was " INVOKEVIRTUAL j/l/StringBuilder.append (Lj/l/String;)Lj/l/StringBuilder; ILOAD 1 INVOKEVIRTUAL j/l/StringBuilder.append (I)Lj/l/StringBuilder; INVOKEVIRTUAL j/l/StringBuilder.toString ()Lj/l/String; INVOKEVIRTUAL j/io/PrintStream.println (Lj/l/String;)V

stdout PrintStream String

slide-98
SLIDE 98

@DebugLog fun prime(n: Int): Long = primeNumberSequence.take(n).last() 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 }

Remember the goal

slide-99
SLIDE 99

Back to our MethodVisitor!

slide-100
SLIDE 100

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

return object : MethodVisitor(Opcodes.ASM5, original) {

  • verride fun visitCode() {

super.visitCode() InstructionAdapter(this).apply { TODO("on method entry") } }4

  • verride fun visitInsn(opcode: Int) {

when (opcode) { RETURN , ARETURN, IRETURN -> { InstructionAdapter(this).apply { TODO("on method exit") } } } super.visitInsn(opcode) } }3

slide-101
SLIDE 101

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

return object : MethodVisitor(Opcodes.ASM5, original) {

  • verride fun visitCode() {

super.visitCode() InstructionAdapter(this).apply { TODO("on method entry") } }

  • verride fun visitInsn(opcode: Int) {

when (opcode) { RETURN , ARETURN, IRETURN -> { InstructionAdapter(this).apply { TODO("on method exit") } } } super.visitInsn(opcode) } }

slide-102
SLIDE 102

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).apply { TODO("on method entry") }1

slide-103
SLIDE 103

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).apply { getstatic("j/l/System", "out", "Ljava/io/PrintStream;") }1

stdout PrintStream

slide-104
SLIDE 104

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).apply { getstatic("j/l/System", "out", "Ljava/io/PrintStream;") anew("j/l/StringBuilder") }1

stdout PrintStream StringBuilder

slide-105
SLIDE 105

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).apply { getstatic("j/l/System", "out", "Ljava/io/PrintStream;") anew("j/l/StringBuilder") dup() }1

stdout PrintStream StringBuilder StringBuilder

slide-106
SLIDE 106

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).apply { getstatic("j/l/System", "out", "Ljava/io/PrintStream;") anew("j/l/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") }1

stdout PrintStream StringBuilder StringBuilder

slide-107
SLIDE 107

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).apply { getstatic("j/l/System", "out", "Ljava/io/PrintStream;") anew("j/l/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇢ ${function.name}(") }1

stdout PrintStream StringBuilder "⇢ prime("

slide-108
SLIDE 108

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).apply { getstatic("j/l/System", "out", "Ljava/io/PrintStream;") anew("j/l/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇢ ${function.name}(") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/StringBuilder;") }1

stdout PrintStream StringBuilder "⇢ prime("

slide-109
SLIDE 109

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).apply { getstatic("j/l/System", "out", "Ljava/io/PrintStream;") anew("j/l/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇢ ${function.name}(") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/StringBuilder;") }1

stdout PrintStream StringBuilder

slide-110
SLIDE 110

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).apply { getstatic("j/l/System", "out", "Ljava/io/PrintStream;") anew("j/l/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇢ ${function.name}(") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/StringBuilder;") }1

stdout PrintStream StringBuilder

slide-111
SLIDE 111

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).apply { getstatic("j/l/System", "out", "Ljava/io/PrintStream;") anew("j/l/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇢ ${function.name}(") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/StringBuilder;") function.valueParameters.forEachIndexed { i, param -> visitLdcInsn(" ${param.name}=") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/SB;") visitVarInsn(ALOAD, i + 1) invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/SB;") } }1

stdout PrintStream StringBuilder

slide-112
SLIDE 112

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).apply { getstatic("j/l/System", "out", "Ljava/io/PrintStream;") anew("j/l/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇢ ${function.name}(") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/StringBuilder;") function.valueParameters.forEachIndexed { i, param -> visitLdcInsn(" ${param.name}=") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/SB;") visitVarInsn(ALOAD, i + 1) invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/SB;") } }1

stdout PrintStream StringBuilder " n="

slide-113
SLIDE 113

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).apply { getstatic("j/l/System", "out", "Ljava/io/PrintStream;") anew("j/l/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇢ ${function.name}(") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/StringBuilder;") function.valueParameters.forEachIndexed { i, param -> visitLdcInsn(" ${param.name}=") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/SB;") visitVarInsn(ALOAD, i + 1) invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/SB;") } }1

stdout PrintStream StringBuilder " n="

slide-114
SLIDE 114

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).apply { getstatic("j/l/System", "out", "Ljava/io/PrintStream;") anew("j/l/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇢ ${function.name}(") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/StringBuilder;") function.valueParameters.forEachIndexed { i, param -> visitLdcInsn(" ${param.name}=") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/SB;") visitVarInsn(ALOAD, i + 1) invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/SB;") } }1

stdout PrintStream StringBuilder

slide-115
SLIDE 115

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).apply { getstatic("j/l/System", "out", "Ljava/io/PrintStream;") anew("j/l/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇢ ${function.name}(") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/StringBuilder;") function.valueParameters.forEachIndexed { i, param -> visitLdcInsn(" ${param.name}=") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/SB;") visitVarInsn(ALOAD, i + 1) invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/SB;") } }1

stdout PrintStream StringBuilder value of n

slide-116
SLIDE 116

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).apply { getstatic("j/l/System", "out", "Ljava/io/PrintStream;") anew("j/l/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇢ ${function.name}(") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/StringBuilder;") function.valueParameters.forEachIndexed { i, param -> visitLdcInsn(" ${param.name}=") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/SB;") visitVarInsn(ALOAD, i + 1) invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/SB;") } }1

stdout PrintStream StringBuilder value of n

slide-117
SLIDE 117

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).apply { getstatic("j/l/System", "out", "Ljava/io/PrintStream;") anew("j/l/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇢ ${function.name}(") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/StringBuilder;") function.valueParameters.forEachIndexed { i, param -> visitLdcInsn(" ${param.name}=") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/SB;") visitVarInsn(ALOAD, i + 1) invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/SB;") } }1

stdout PrintStream StringBuilder

slide-118
SLIDE 118

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).apply { getstatic("j/l/System", "out", "Ljava/io/PrintStream;") anew("j/l/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇢ ${function.name}(") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/StringBuilder;") function.valueParameters.forEachIndexed { i, param -> visitLdcInsn(" ${param.name}=") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/SB;") visitVarInsn(ALOAD, i + 1) invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/SB;") }R }1

stdout PrintStream StringBuilder

slide-119
SLIDE 119

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).apply { getstatic("j/l/System", "out", "Ljava/io/PrintStream;") anew("j/l/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇢ ${function.name}(") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/StringBuilder;") function.valueParameters.forEachIndexed { i, param -> visitLdcInsn(" ${param.name}=") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/SB;") visitVarInsn(ALOAD, i + 1) invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/SB;") }R invokevirtual("j/l/StringBuilder", "toString", "()Lj/l/String;") }1

stdout PrintStream StringBuilder String

slide-120
SLIDE 120

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).apply { getstatic("j/l/System", "out", "Ljava/io/PrintStream;") anew("j/l/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇢ ${function.name}(") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/StringBuilder;") function.valueParameters.forEachIndexed { i, param -> visitLdcInsn(" ${param.name}=") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/SB;") visitVarInsn(ALOAD, i + 1) invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/SB;") }R invokevirtual("j/l/StringBuilder", "toString", "()Lj/l/String;") invokevirtual("j/io/PrintStream", "println", "(Lj/l/String;)V") }1

stdout PrintStream String

slide-121
SLIDE 121

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).apply {L getstatic("j/l/System", "out", "Ljava/io/PrintStream;") anew("j/l/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇢ ${function.name}(") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/StringBuilder;") function.valueParameters.forEachIndexed { i, param -> visitLdcInsn(" ${param.name}=") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/SB;") visitVarInsn(ALOAD, i + 1) invokevirtual("j/l/StringBuilder", "append", "(Lj/l/Object;)Lj/l/SB;") }R invokevirtual("j/l/StringBuilder", "toString", "()Lj/l/String;") invokevirtual("j/io/PrintStream", "println", "(Lj/l/String;)V") }1

slide-122
SLIDE 122

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).apply {L // ... method-trace-printing code }1

slide-123
SLIDE 123

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).apply {L // ... method-trace-printing code invokestatic("j/l/System", "currentTimeMillis", "()J") }1

currentTimeMillis

slide-124
SLIDE 124

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).apply {L // ... method-trace-printing code invokestatic("j/l/System", "currentTimeMillis", "()J") store(9001, LONG_TYPE) }1

currentTimeMillis

slide-125
SLIDE 125

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).applyL{L // ... method-trace-printing code invokestatic("j/l/System", "currentTimeMillis", "()J") store(9001, LONG_TYPE) }1

slide-126
SLIDE 126

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).applyL{L // ... method-trace-printing code // ... timestamp-storing code }W

slide-127
SLIDE 127

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

return object : MethodVisitor(Opcodes.ASM5, original) {

  • verride fun visitCode() {

super.visitCode() InstructionAdapter(this).applyL{L // ... method-trace-printing code // ... timestamp-storing code }W }4

  • verride fun visitInsn(opcode: Int) {

when (opcode) { RETURN , ARETURN, IRETURN -> { InstructionAdapter(this).apply { TODO("on method exit") } } } super.visitInsn(opcode) } }3

slide-128
SLIDE 128

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

return object : MethodVisitor(Opcodes.ASM5, original) {

  • verride fun visitCode() {

super.visitCode() InstructionAdapter(this).applyL{L // ... method-trace-printing code // ... timestamp-storing code }W }4

  • verride fun visitInsn(opcode: Int) {

when (opcode) { RETURN , ARETURN, IRETURN -> { InstructionAdapter(this).apply { TODO("on method exit") } } } super.visitInsn(opcode) } }3

slide-129
SLIDE 129

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

return object : MethodVisitor(Opcodes.ASM5, original) {

  • verride fun visitCode() {

super.visitCode() InstructionAdapter(this).apply { // ... method-trace-printing code // ... timestamp-storing code }W }4

  • verride fun visitInsn(opcode: Int) {

when (opcode) { RETURN , ARETURN, IRETURN -> { InstructionAdapter(this).applyM{MTODO("on method exit")M}M } } super.visitInsn(opcode) } }3

slide-130
SLIDE 130

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).applyM{M TODO("on method exit") }M

slide-131
SLIDE 131

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).applyM{M getstatic("j/l/System", "out", "Lj/io/PrintStream;") }M

stdout PrintStream

slide-132
SLIDE 132

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).applyM{M getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") }M

stdout PrintStream StringBuilder

slide-133
SLIDE 133

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).applyM{M getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() }M

stdout PrintStream StringBuilder StringBuilder

slide-134
SLIDE 134

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).applyM{M getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") }M

stdout PrintStream StringBuilder StringBuilder

slide-135
SLIDE 135

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).applyM{M getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠ ${function.name} [ran in ") }M

stdout PrintStream StringBuilder "⇠ prime [ran in "

slide-136
SLIDE 136

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).applyM{M getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠ ${function.name} [ran in ") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") }M

stdout PrintStream StringBuilder "⇠ prime [ran in "

slide-137
SLIDE 137

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).applyM{M getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠ ${function.name} [ran in ") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") }M

stdout PrintStream StringBuilder

slide-138
SLIDE 138

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).applyM{M getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠ ${function.name} [ran in ") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") invokestatic("j/l/System", "currentTimeMillis", "()J") }M

stdout PrintStream StringBuilder current timestamp

slide-139
SLIDE 139

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).applyM{M getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠ ${function.name} [ran in ") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") invokestatic("j/l/System", "currentTimeMillis", "()J") load(9001, LONG_TYPE) }M

stdout PrintStream StringBuilder current timestamp start timestamp

slide-140
SLIDE 140

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).applyM{M getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠ ${function.name} [ran in ") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") invokestatic("j/l/System", "currentTimeMillis", "()J") load(9001, LONG_TYPE) sub(LONG_TYPE) }M

stdout PrintStream StringBuilder current timestamp start timestamp

slide-141
SLIDE 141

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).applyM{M getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠ ${function.name} [ran in ") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") invokestatic("j/l/System", "currentTimeMillis", "()J") load(9001, LONG_TYPE) sub(LONG_TYPE) }M

stdout PrintStream StringBuilder (current - start) time

slide-142
SLIDE 142

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).applyM{M getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠ ${function.name} [ran in ") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") invokestatic("j/l/System", "currentTimeMillis", "()J") load(9001, LONG_TYPE) sub(LONG_TYPE) invokevirtual("j/l/StringBuilder", "append", "(J)Lj/l/StringBuilder;") }M

stdout PrintStream StringBuilder (current - start) time

slide-143
SLIDE 143

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).applyM{M getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠ ${function.name} [ran in ") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") invokestatic("j/l/System", "currentTimeMillis", "()J") load(9001, LONG_TYPE) sub(LONG_TYPE) invokevirtual("j/l/StringBuilder", "append", "(J)Lj/l/StringBuilder;") }M

stdout PrintStream StringBuilder

slide-144
SLIDE 144

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).applyM{M getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠ ${function.name} [ran in ") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") invokestatic("j/l/System", "currentTimeMillis", "()J") load(9001, LONG_TYPE) sub(LONG_TYPE) invokevirtual("j/l/StringBuilder", "append", "(J)Lj/l/StringBuilder;") visitLdcInsn(" ms]") }M

stdout PrintStream StringBuilder " ms]"

slide-145
SLIDE 145

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).applyM{M getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠ ${function.name} [ran in ") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") invokestatic("j/l/System", "currentTimeMillis", "()J") load(9001, LONG_TYPE) sub(LONG_TYPE) invokevirtual("j/l/StringBuilder", "append", "(J)Lj/l/StringBuilder;") visitLdcInsn(" ms]") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/SB;") }M

stdout PrintStream StringBuilder " ms]"

slide-146
SLIDE 146

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).applyM{M getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠ ${function.name} [ran in ") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") invokestatic("j/l/System", "currentTimeMillis", "()J") load(9001, LONG_TYPE) sub(LONG_TYPE) invokevirtual("j/l/StringBuilder", "append", "(J)Lj/l/StringBuilder;") visitLdcInsn(" ms]") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/SB;") }M

stdout PrintStream StringBuilder

slide-147
SLIDE 147

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).applyM{M getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠ ${function.name} [ran in ") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") invokestatic("j/l/System", "currentTimeMillis", "()J") load(9001, LONG_TYPE) sub(LONG_TYPE) invokevirtual("j/l/StringBuilder", "append", "(J)Lj/l/StringBuilder;") visitLdcInsn(" ms]") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/SB;") invokevirtual("j/l/StringBuilder", "toString", "()Lj/l/String;") }M

stdout PrintStream StringBuilder String

slide-148
SLIDE 148

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).applyM{M getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠ ${function.name} [ran in ") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") invokestatic("j/l/System", "currentTimeMillis", "()J") load(9001, LONG_TYPE) sub(LONG_TYPE) invokevirtual("j/l/StringBuilder", "append", "(J)Lj/l/StringBuilder;") visitLdcInsn(" ms]") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/SB;") invokevirtual("j/l/StringBuilder", "toString", "()Lj/l/String;") invokevirtual("j/io/PrintStream", "println", "(Lj/l/String;)V") }M

stdout PrintStream String

slide-149
SLIDE 149

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).applyM{M getstatic("j/l/System", "out", "Lj/io/PrintStream;") anew("java/lang/StringBuilder") dup() invokespecial("j/l/StringBuilder", "<init>", "()V") visitLdcInsn("⇠ ${function.name} [ran in ") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/StringBuilder;") invokestatic("j/l/System", "currentTimeMillis", "()J") load(9001, LONG_TYPE) sub(LONG_TYPE) invokevirtual("j/l/StringBuilder", "append", "(J)Lj/l/StringBuilder;") visitLdcInsn(" ms]") invokevirtual("j/l/StringBuilder", "append", "(Lj/l/String;)Lj/l/SB;") invokevirtual("j/l/StringBuilder", "toString", "()Lj/l/String;") invokevirtual("j/io/PrintStream", "println", "(Lj/l/String;)V") }M

slide-150
SLIDE 150

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

InstructionAdapter(this).applyM{M //G...Gbenchmark-printingGcode }M

slide-151
SLIDE 151

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

return object : MethodVisitor(Opcodes.ASM5, original) {

  • verride fun visitCode() {

super.visitCode() InstructionAdapter(this).applyL{L // ... method-trace-printing code // ... timestamp-storing code }W }4

  • verride fun visitInsn(opcode: Int) {

when (opcode) { RETURN , ARETURN, IRETURN -> { InstructionAdapter(this).applyM{M//G...Gbenchmark-printingGcode }M } } super.visitInsn(opcode) } }3

slide-152
SLIDE 152

kotlin-plugin/src/main/kotlin/debuglog/DebugLogClassBuilder.kt

return object : MethodVisitor(Opcodes.ASM5, original) {

  • verride fun visitCode() {

super.visitCode() InstructionAdapter(this).applyL{L // ... method-trace-printing code // ... timestamp-storing code }W }4

  • verride fun visitInsn(opcode: Int) {

when (opcode) { RETURN , ARETURN, IRETURN -> { InstructionAdapter(this).applyM{M//G...Gbenchmark-printingGcode }M } } super.visitInsn(opcode) } }3

slide-153
SLIDE 153

Done! Demo time!!!

slide-154
SLIDE 154

Resources

  • https://github.com/JetBrains/kotlin/tree/master/plugins. Specifically:
  • noarg: One of the simplest ones
  • android-extensions: Good prior art for many of the extension types
  • kotlin-serialization: Newest one, documented well, generates LLVM
  • https://github.com/JetBrains/kotlin/tree/master/libraries/tools
  • All look pretty similar; noarg and allopen are the simplest to grok
slide-155
SLIDE 155

Thank you!

Kevin Most