Hey! Working on a Kotlin/Native binding for Godot'...
# kotlin-native
r
Hey! Working on a Kotlin/Native binding for Godot's GDExtension API ๐ŸŽฎ (codegen from
extension_api.json
, ~1000 classes, ~25+ functions per class) Trying to validate my current architectural decisions before we go too far. A few questions, any partial answers appreciated: 1. Extension functions on companion objects vs members inside companion. We're generating static-style factory methods. Plan is to emit a bare
companion object
and put all static methods as top-level
fun ClassName.Companion.method()
in separate files rather than inside the companion body. Is this actually better for DCE / IR pipeline, or equivalent? Is a companion object treated as a separate singleton class in the IR, meaning members inside it get dragged together regardless of usage? 2. Properties without backing fields. We're generating computed properties (getter/setter, value is a cinterop call). From the compiler's perspective, is a property with no backing field essentially equivalent to a plain function, or is there any overhead in the IR / metadata? 3. What's "huge" for a class? Is there a rough threshold (methods, lines, IR nodes) where a single class becomes costly for the compiler or linker? We're splitting non-virtual methods into extension functions in separate files specifically to avoid this โ€” is that the right mitigation, or is file count / declaration count per file also a factor? 4. Kotlin enum vs value class for C-style enums. We need to represent Godot enums (C-style, backed by a long constant). Is a Kotlin
enum class
with a
val value: Long
meaningfully worse than a
value class
wrapping Long, in terms of flatten/unboxed memory representation and compiler output? Does the compiler eliminate the wrapper in the value class case always? 5. Top-level declarations โ€” can a file also become "huge"? If we put, say, 200 top-level functions in a single file (no top-level state/properties), is each declaration treated individually by the compiler/linker, or does the file boundary matter at all for IR or compilation cost? 6. Kotlin Scripting + Kotlin/Native. Obvious one, but just to confirm: Kotlin Scripting can't currently be used with Kotlin/Native, right? Is there any roadmap or tracking issue for that? Thanks in advance, happy coding! kodee happy PS. Currently with 65 files compiles fine < 30s (debug type)
๐Ÿ‘€ 1
r
A lot of these questions seem like you might have better luck in #C7L3JB43G, FWIW
โž• 1
i
1.
Is this actually better for DCE / IR pipeline, or equivalent
I would say it is equivalent in terms of DCE. When we link a class, we load everything that is inside and when we link top-level function, then only this function is loaded. But in the end, DCE will optimize both cases the same way (we will remove everything that is unused).
Is a companion object treated as a separate singleton class in the IR, meaning members inside it get dragged together regardless of usage?
Yes. Companion objects are compiled as a separate classes with all the methods inside. Unless it is JVM and method is marked with
@JvmStatic
. I am not sure how you are going to use it, but generally I would recommend putting everything inside companion object. Extensions on companion should be used if you have no control over companion itself (for example, it is located in the lib). With extensions you must import every function separately and this creates unnecessary noise. Also please take a look and this document about improving companions. This should help you, but it will take some time to finalize the design and implement it. 2.
From the compiler's perspective, is a property with no backing field essentially equivalent to a plain function, or is there any overhead in the IR / metadata?
If a property is public, then you will see it in metadata. I am not sure I can call it "overhead". You can inspect metadata of klib by using klib tool. It is located in the Kotlin compiler
kotlin-native/klib/src/org/jetbrains/kotlin/cli/klib/main.kt
. Build compiler by calling
gw dist
and the check the tool
kotlin-native/dist/bin/klib --help
3. I would recommend for you to experiment with it (and post there the results ๐Ÿ™‚). Personally, I don't know. I would say that only the amount (and size) of functions is important, but no concrete numbers. 4. I would say that value class is better for performance, but more or less the same for memory footprint. There is an old idea to be able to compile/optimize enums using value classes but we don't have strong use cases to continue. You can describe your problem in more details and we will take a look if this optimization actually help in your case. 5. I would say that from compiler perspective 200 declaration inside a file and inside a class is the same thing. But you can easily test it by creating such artificial file and running Native compiler on it
kotlin-native/dist/bin/kotlinc-native hello.kt -p library
. 6.
Kotlin Scripting can't currently be used with Kotlin/Native, right?
Only JVM.
โค๏ธ 1
r
Wow, thank you so much for covering all my questions so thoroughly! @Ivan Kylchik This is exactly the kind of clarity I needed to move forward confidently. Really appreciate the pointer on companion objects. I'll keep the static methods inside and revisit once the companion improvements land. I'll definitely experiment with the class/file size question and report back with results once I have something concrete to share. The project is a Kotlin/Native binding for Godot's GDExtension API, so there will be plenty of real-world data to work with K Thanks again, will keep the community posted!
๐Ÿ˜Š 1
kodee happy 1
I wanted to give more context on my use case of enum class/value class. Godot defines its enums in C, including bitmasks and similar patterns. In my case I decided to go with
enum class
because pattern matching is a better experience for Kotlin devs and aligns well with how other Godot language bindings feel. However I'm aware the object overhead is unnecessary at runtime, it's purely a compile-time/API convenience, not something I need in the actual native output. I evaluated
value class
as an alternative, but without compiler-supported pattern matching (the way Rust handles it, for example) I'd have to implement that manually, which defeats the purpose. So for now
enum class
stays, the DX wins over the marginal runtime cost. Just wanted to close the loop on that point in case it's useful context for the potential enum+value class optimization you mentioned. Thanks blob smile happy
๐Ÿ‘Œ 1
thank you color 1
Hey @Ivan Kylchik the project already finish generation of API, have 1091 files generated + 14 files hand made, for runtime module 21 files generated + 14 files hand made, plus ffi module generated with cinterop. I disable BTA mode because have poor performance. The linking tasks is the worst part, but I change release build type to only by flag runs. I attach some exceptions I has with incremental compilation here https://youtrack.jetbrains.com/issue/KT-71279 Repo: https://github.com/kingg22/kogot I hope Jetbrains team can find this useful to have metrics and improve compile time of kotlin native compiler K
thank you color 1
๐Ÿ‘€ 2