Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Status on GraalVM native image compatibility #786

Open
3 of 6 tasks
DarkAtra opened this issue Mar 19, 2023 · 13 comments
Open
3 of 6 tasks

Status on GraalVM native image compatibility #786

DarkAtra opened this issue Mar 19, 2023 · 13 comments
Assignees
Labels
enhancement New feature or request feature on hold Something is preventing this issue from being resolved

Comments

@DarkAtra
Copy link

DarkAtra commented Mar 19, 2023

Support GraalVM native images

In addition to supporting Kotlin/JS and Kotlin/Native (See #69) we should provide Support for GraalVM native image.

Tasks

Experimental builds

Currently, we provide an experimental Graal build (See #787), this build is not fully tested and not recommended for production use

You can obtain it using the feature-graal-SNAPSHOT version. See the Project README for more information

Original Issue

I saw that you're working on kotlin native/multiplatform support and was wondering if GraalVM Native Image is also something that you're interested in supporting in the future.

Some context:
I'm currently trying to build a native image for one of my side projects that uses kord and there are a lot of runtime hints required to get everything working. I was hoping that kord could provide such runtime hints so that i don't have to maintain it if something changes under the hood.

@DRSchlaubi
Copy link
Member

Never used Graal before but as the main guy of working on MPP here I am definitely interested in it. However I don't know what the problem here is as I haven't tried it yet.

What happened when you try to build a Graal image?

Another thing is about dependency compatibility. I only know that ktor server supports Graal not sure about the client

@DarkAtra
Copy link
Author

Never used Graal before but as the main guy of working on MPP here I am definitely interested in it.

Great to hear that you're interested ^^

What happened when you try to build a Graal image?

Some things wont work out of the box when an application is compiled using GraalVM Native Image. All limitations are described here: https://www.graalvm.org/22.1/reference-manual/native-image/Limitations/

As kord relies on kotlinx-serialization, i currently need to provide reflection hints for all @Serializable annotated classes. One example would be:

reflect-config.json:

[
  {
    "name": "dev.kord.common.entity.ApplicationCommandPermissionType",
    "allDeclaredFields": true,
    "allDeclaredConstructors": true,
    "queryAllDeclaredMethods": true,
    "methods": [
      {
        "name": "getValue",
        "parameterTypes": [ ]
      }
    ]
  },
  {
    "name": "dev.kord.common.entity.ApplicationCommandPermissionType$Companion",
    "methods": [
      {
        "name": "serializer",
        "parameterTypes": [ ]
      }
    ]
  }
]

The above configuration would tell GraalVM that it should expect reflection calls for fields, methods and constructors of ApplicationCommandPermissionType and also for the generated serializer method of its companion object. All of these calls are required during serialization. Without those hints, you'd see NoSuchFieldExceptions (or similar depending on the reflection call).


The same also applies to ktor-client to some extend. However, I'd expect the ktor team to provide compatibility for GraalVM in this case as there are no kord specific classes involved. Only for completeness, the following hints were required in my project:

// required by kotlin coroutines
.registerType(TypeReference.of("kotlin.internal.jdk8.JDK8PlatformImplementations"), MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)
// required by ktor
.registerType(InterestSuspensionsMap::class.java, MemberCategory.DECLARED_FIELDS)
.registerType(DefaultPool::class.java, MemberCategory.DECLARED_FIELDS)

(The above code is using Spring Frameworks RuntimeHintsRegistrar to generate json format that GraalVM expects during compilation.)


Please note, that i'm still very early in the testing phase and that there are a lot of hints still missing (not only for kord but also for other libraries). I'm currently still trying to get the application to boot, which also involves connecting to the discord gateway. That's the main reason why i've started with runtime hints for kord :D


And again, just for completeness, this is the branch i'm currently working on: https://github.com/DarkAtra/v-rising-discord-bot/tree/next

@DRSchlaubi
Copy link
Member

// required by kotlin coroutines
.registerType(TypeReference.of("kotlin.internal.jdk8.JDK8PlatformImplementations"), MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)

This should be fixed in Kotlin 1.9 and is tracked in KT-51579

@DRSchlaubi
Copy link
Member

DRSchlaubi commented Mar 19, 2023

I've created a test project and I can run using the following config

reflect-config.json
[
  {
    "name": "kotlin.internal.jdk8.JDK8PlatformImplementations",
    "allPublicMethods": true,
    "allDeclaredFields": true,
    "allDeclaredMethods": true,
    "allDeclaredConstructors": true
  },
  {
    "name": "io.ktor.network.selector.InterestSuspensionsMap",
    "allDeclaredFields": true
  },
  {
    "name": "io.ktor.utils.io.pool.DefaultPool",
    "allDeclaredFields": true
  }
]
Main.kt
package dev.kord.core

import dev.kord.core.event.message.MessageCreateEvent
import dev.kord.gateway.Intent
import dev.kord.gateway.PrivilegedIntent

suspend fun main(args: Array<String>) {
    val kord = Kord(args.firstOrNull() ?: error("token required"))

    kord.on<MessageCreateEvent> {
        if (message.author?.isBot == true) return@on
        if (message.content == "!ping") message.channel.createMessage("pong")
    }

    kord.login {
        presence { playing("!ping to pong") }

        @OptIn(PrivilegedIntent::class)
        intents += Intent.MessageContent
    }
}
build.gradle.kts
plugins {
    `kord-internal-module`
    application
    id("org.graalvm.buildtools.native") version "0.9.20"
}

dependencies {
    implementation(projects.core)
    implementation(libs.slf4j.simple)
}

application {
    mainClass.set("dev.kord.core.MainKt")
}

graalvmNative {
    binaries {
        named("main") {

            javaLauncher.set(javaToolchains.launcherFor {
                languageVersion.set(JavaLanguageVersion.of(19))
                vendor.set(JvmVendorSpec.matching("GraalVM Community"))
            })
        }
    }
}

Could you share an example of what causes issues with serialization?

Also, would you mind joining the Support Discord, so we can discuss this further?

After further investigation, the only method using reflection is serializer(), which should be easy to generate

{
   "name":"package.name.YourClassName",
   "fields":[{"name":"Companion"}]
},
{
   "name":"package.name.YourClassName$Companion",
   "methods":[{"name":"serializer","parameterTypes":[] }]
}

@DRSchlaubi
Copy link
Member

Could you please try the version feature-graal-SNAPSHOT and tell me what issues you encounter?

@DarkAtra
Copy link
Author

I've created a test project and I can run using the following config
reflect-config.json
Main.kt
build.gradle.kts

Could you share an example of what causes issues with serialization?

Also, would you mind joining the Support Discord, so we can discuss this further?

After further investigation, the only method using reflection is serializer(), which should be easy to generate

{
   "name":"package.name.YourClassName",
   "fields":[{"name":"Companion"}]
},
{
   "name":"package.name.YourClassName$Companion",
   "methods":[{"name":"serializer","parameterTypes":[] }]
}

joined discord in case you still want to discuss something.

Could you please try the version feature-graal-SNAPSHOT and tell me what issues you encounter?

i'll try the snapshot now

@DarkAtra
Copy link
Author

DarkAtra commented Mar 19, 2023

with the snapshot it does no longer require runtime hints for:

ApplicationCommandData::class.java,
AutoModerationRuleData::class.java,
ChannelData::class.java,
EmojiData::class.java,
GuildData::class.java,
MemberData::class.java,
MessageData::class.java,
PresenceData::class.java,
RoleData::class.java,
StickerData::class.java,
ThreadMemberData::class.java,
UserData::class.java,
VoiceStateData::class.java,
WebhookData::class.java

but the following hints are still required (probably because i am registering GlobalApplicationCommands on startup):

{
  "name": "dev.kord.core.cache.data.GuildApplicationCommandPermissionsData"
},
{
  "name": "dev.kord.core.cache.data.StickerPackData"
},

without i'm getting the followng exception:

kotlin.reflect.jvm.internal.KotlinReflectionInternalError: Unresolved class: class dev.kord.core.cache.data.GuildApplicationCommandPermissionsData
        at kotlin.reflect.jvm.internal.KClassImpl.reportUnresolvedClass(KClassImpl.kt:328) ~[v-rising-discord-bot:1.8.10-release-430(1.8.10)]
        at kotlin.reflect.jvm.internal.KClassImpl.access$reportUnresolvedClass(KClassImpl.kt:44) ~[v-rising-discord-bot:1.8.10-release-430(1.8.10)]
        at kotlin.reflect.jvm.internal.KClassImpl$Data$descriptor$2.invoke(KClassImpl.kt:56) ~[na:na]
        at kotlin.reflect.jvm.internal.KClassImpl$Data$descriptor$2.invoke(KClassImpl.kt:48) ~[na:na]
        at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:93) ~[na:na]
        at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:32) ~[v-rising-discord-bot:1.8.10-release-430(1.8.10)]
        at kotlin.reflect.jvm.internal.KClassImpl$Data.getDescriptor(KClassImpl.kt:48) ~[v-rising-discord-bot:1.8.10-release-430(1.8.10)]
        at kotlin.reflect.jvm.internal.KClassImpl.getDescriptor(KClassImpl.kt:182) ~[v-rising-discord-bot:1.8.10-release-430(1.8.10)]
        at kotlin.reflect.jvm.internal.KClassImpl.getDescriptor(KClassImpl.kt:44) ~[v-rising-discord-bot:1.8.10-release-430(1.8.10)]
        at kotlin.reflect.full.KClassifiers.createType(KClassifiers.kt:47) ~[na:na]
        at kotlin.reflect.jvm.internal.CachesKt$CACHE_FOR_BASE_CLASSIFIERS$1.invoke(caches.kt:37) ~[na:na]
        at kotlin.reflect.jvm.internal.CachesKt$CACHE_FOR_BASE_CLASSIFIERS$1.invoke(caches.kt:36) ~[na:na]
        at kotlin.reflect.jvm.internal.ComputableClassValue.computeValue(CacheByClass.kt:48) ~[na:na]
        at kotlin.reflect.jvm.internal.ComputableClassValue.computeValue(CacheByClass.kt:46) ~[na:na]
        at [email protected]/java.lang.ClassValue.get(JavaLangSubstitutions.java:590) ~[v-rising-discord-bot:na]
        at kotlin.reflect.jvm.internal.ClassValueCache.get(CacheByClass.kt:61) ~[na:na]
        at kotlin.reflect.jvm.internal.CachesKt.getOrCreateKType(caches.kt:55) ~[na:na]
        at kotlin.reflect.jvm.internal.ReflectionFactoryImpl.typeOf(ReflectionFactoryImpl.java:123) ~[v-rising-discord-bot:1.8.10-release-430(1.8.10)]
        at kotlin.jvm.internal.Reflection.typeOf(Reflection.java:128) ~[na:na]
        at dev.kord.core.cache.data.GuildApplicationCommandPermissionsData.<clinit>(GuildApplicationCommandPermissionsData.kt:35) ~[na:na]
        at dev.kord.core.cache.DataCacheExtensionsKt.registerKordData(DataCacheExtensions.kt:23) ~[na:na]
        at dev.kord.core.builder.kord.KordBuilder.build(KordBuilder.kt:276) ~[na:na]
        at dev.kord.core.builder.kord.KordBuilder$build$1.invokeSuspend(KordBuilder.kt) ~[na:na]

Runtime hints for ktor and kotlin coroutines are also still required but that is totally fine as it's not something i'd expect kord to provide.

@DRSchlaubi DRSchlaubi pinned this issue Mar 20, 2023
@DRSchlaubi DRSchlaubi changed the title Compatibility with GraalVM Native Image Status on GraalVM native image compatibility Mar 20, 2023
@DRSchlaubi DRSchlaubi self-assigned this Mar 20, 2023
@DRSchlaubi DRSchlaubi added enhancement New feature or request feature on hold Something is preventing this issue from being resolved labels Mar 20, 2023
DarkAtra added a commit to DarkAtra/v-rising-discord-bot that referenced this issue Apr 21, 2023
* pre-release versions are usually suffixed with `-next.1`, `-dev.1` or something similar. we're excluding those from the dependency updates
* we're currently relying on the graalvm snapshot build. waiting until there's progress on kordlib/kord#786
DarkAtra added a commit to DarkAtra/v-rising-discord-bot that referenced this issue Apr 23, 2023
DarkAtra added a commit to DarkAtra/v-rising-discord-bot that referenced this issue Apr 23, 2023
github-actions bot pushed a commit to DarkAtra/v-rising-discord-bot that referenced this issue Apr 23, 2023
## [2.1.2](v2.1.1...v2.1.2) (2023-04-23)

### Bug Fixes

* add runtime hints for kord until kordlib/kord/issues/786 is merged ([#67](#67)) ([f6c41a8](f6c41a8))
@DRSchlaubi
Copy link
Member

We will revisit this after #855

@DRSchlaubi
Copy link
Member

Since #928 got merged, the Ktor reflection hints might no longer be needed, as those seem to be CIO specific

@DarkAtra
Copy link
Author

DarkAtra commented Mar 21, 2024

Since #928 got merged, the Ktor reflection hints might no longer be needed, as those seem to be CIO specific

Seems like the hint for DefaultPool is still required. The one for InterestSuspensionsMap can be omitted though. E.g.:

reflect-config.json (see DarkAtra/v-rising-discord-bot@fd1d786):

[
  {
    "name": "kotlin.internal.jdk8.JDK8PlatformImplementations",
    "allPublicMethods": true,
    "allDeclaredFields": true,
    "allDeclaredMethods": true,
    "allDeclaredConstructors": true
  },
  {
    "name": "io.ktor.utils.io.pool.DefaultPool",
    "allDeclaredFields": true
  }
]

@DRSchlaubi
Copy link
Member

kotlin.internal.jdk8.JDK8PlatformImplementations

should no longer be needed as of 1.9.0

@DarkAtra
Copy link
Author

kotlin.internal.jdk8.JDK8PlatformImplementations

should no longer be needed as of 1.9.0

In the project i linked i'm stuck with Kotlin 1.8.x for the time being due to oracle/graal#7089. However, i validated that a different project (which already uses kotlin 1.9.23) runs fine without JDK8PlatformImplementations hints.

@DarkAtra
Copy link
Author

DarkAtra commented Jul 2, 2024

Small update...
It seems like Kord also needs to provide reflection hints for the following two classes when using newer GraalVM versions:

  • Optional.Null.Companion::class
  • Optional.Missing.Companion::class

Additionally, kotlinx.serialization also requires a few reflection hints for (in newer GraalVM versions):

  • JsonArray.Companion::class
  • JsonObject.Companion::class

(see oracle/graal#7089)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request feature on hold Something is preventing this issue from being resolved
Projects
None yet
Development

No branches or pull requests

2 participants