As a android developer that only writes kotlin cod...
# compiler
u
As a android developer that only writes kotlin code. what
jvmTarget
should I use? Is there anything to gain when bumping ˙1.8` to
11
, when java language features are irrelevant to me? Performance?
Kotlin stdlib did make some different APIs available with different targets, such as https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/use.html (1.7+), I can't think of anything that's only 9+ though
u
so, in theory by bumping to jvm11, I get a few new instructions, making it more performant in the given use cases?
e
that's really more of an Android question than a Kotlin one
u
sure, but if I were to target say jvm6, then my lambdas would be anonynmous classes, right? by being jvm7 target I'd get the invokedynamic instruction making it more optimized do I get it right?
e
in general JVM terms, the invoke-dynamic stuff is results in smaller generated code (with possibly slightly faster build times), but it basically trades off startup/runtime time. so there's really not much measurable difference
in Android terms, you can't use invoke-dynamic if you have minSdk<26; if your code does, D8 will desugar it back to classes
u
I'm mostly writing because of a library author who bumped the library to jvm11, and now I'm not sure what to do Are the reasons mostly "purity", because java8 is EOL?
is there a map of android api vs java version? can't seem to find it does jvm target imply a minSdk as well?
e
the Android API doesn't map to Java versions, period
u
nn I mean what version of java does each platform version include
e
Android S has a few Java 11 APIs but not all. Android T will add more Java 11 APIs, Android U will add some Java 17 APIs, but none of it is going to exactly match
that's just on the API side. on the bytecode side, DEX bytecode versions don't match with Java bytecode versions either
u
okay so what is my jvmTarget limited by .. by what is D8 able to ingest?
e
pretty much, yes
u
so why did he chose 11, he could have gone even for 17, no?
e
there are some Java 11 bytecode changes that d8 can't handle, such as https://openjdk.org/jeps/309, but mostly it works
actually looks like that particular case is fixed https://issuetracker.google.com/issues/178172809
u
so would you say the reason to increase the jvm target just academic, as to "move the industry forward" and not depend on EOL software?
e
Android Studio Electric Eel ships with JBR 11 and Android Studio Flamingo will ship with JBR 17, so it's not like it's depending on EOL software anyway
u
whats a jbr
jre?
u
I see. But the author bumped the version of the runtime as well, not just the compiler plugin
(its a kotlin ksp processor + some small runtime stuff for the moshi json parser - giving it ability to serialize nested classes via the annotations, which you cannot do with the stock library's codegen) bumping the compiler I would understand, as that runs on my machine, same as AS, but the runtime, that I didn't get) But I now tried to raise the jvm target in my android application, and everything seems to work fine Soo..idk
e
bumping the target in the compiler is actually possibly more annoying for non-Android users: Gradle still supports running on Java 8, and when it does, it can't load plugins built for newer targets
but for Android that doesn't matter because the Android Gradle Plugin needs to run on Java 11+ anyway
u
hmm okay so is there any guarantee jvm11 target works on say api21 android?
e
that's really an Android question. but it's not even guaranteed that all Java 8 code works - in fact, not all of it does. but a useful subset of it does. ditto Java 11 and 17.
j
in Android terms, you can't use invoke-dynamic if you have minSdk<26; if your code does, D8 will desugar it back to classes
Android does not ship LambdaMetaFactory. Even if you have min API 33 you get lambdas desugared to classes at dexing time.
so would you say the reason to increase the jvm target just academic, as to "move the industry forward" and not depend on EOL software?
yes. there's no reason to do this in this specific instance.
e
Android does not ship LambdaMetaFactory. Even if you have min API 33 you get lambdas desugared to classes at dexing time.
oh, I saw https://cs.android.com/android/platform/superproject/+/master:libcore/ojluni/src/lambda/java/java/lang/invoke/LambdaMetafactory.java but I never checked if it's actually on device
u
Im still kinda confused, what kind of "java" is on devices then? is it basically java 6?
j
no. it is an entirely custom VM with OpenJDK sources plucked from many different versions of Java compiled as part of the Android OS
u
if so then why no native lambdas?
j
it is like 99% Java 6, 95% Java 8, and like 91% Java 11
because the custom VM is really bad at spinning bytecodes at runtime whereas the JVM is really good at it
e
and a number of Android-specific changes to the OpenJDK implementations too
u
got it, so java lambda syntax was a developer quality of life thing, therefore we bumped jvm target to 1.7, therefore d8 desugar kicks to enable that which means it only makes sense to raise jvm target to get new language features which is however irrelevant for kotlin
e
(I know it's not that simple but) if GraalVM native-image can handle LambdaMetaFactory and StringConcatFactory (at least the way the compiler uses them), why can't ART handle that too blob shrug
anyhow, in my projects, I find the biggest reason to bump JVM targets is when dependencies do
j
it can handle it. they choose not to
u
anyhow, in my projects, I find the biggest reason to bump JVM targets is when dependencies do
java source dependency, right? kotlin doesnt really care right?
e
Gradle cares, and also Kotlin cares if you want to use
inline fun
j
the source version is the version of Java source being compiled. the target version is the bytecode version embedded in the classfile that you consume
u
but that applies mostly to java world no? i.e. to ingest java8 source and output java6 bytecode on android it seems it doesnt matter what version the bytecode is, only what d8 can handle?
or am I making it easier for d8 if I were to output say java6 bytecode?
j
There's no reason to use java6 or 7 anywhere.
kotlinc
won't even emit Java 6 bytecode anymore and neither will
javac
u
okay then say source 11, target 8, i.e. to make them not the same number
j
javac
stopped supporting different source and target versions in... Java 6? They have to match now.
u
I see.
j
In fact, you shouldn't even use source/target anymore with
javac
. Since Java 9 there's a
-release
flag which sets the bootstrap classpath (basically the built-in APIs), the source, and the target with a single number
it allows any version of
javac
to cross-compile to other versions. The latest, 19, supports 8-19
actually it may still support 7. i honestly can't remember, but it definitely doesn't support 6 for a few versions now
e
kotlinc has
-Xjdk-release
(but neither it nor
--release
are relevant for Android since it's not using the JDK's classpath anyway)
u
so I should not explicitly set these?
Copy code
compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
j
that looks like the Android ones. the default is 8 so that's not really doing anything
there's also this whole complication where Gradle doesn't have APIs to set
-release
and so you still have to set source/target
it will infer that you're trying to cross-compile and use
-release
u
I see. Btw why would they choose not to do native lambdas? Is the anon class instantiation not a penalty?
Or did they rather make instantiation cheaper
j
the justification is that things like vertical and horizontal class merging offers a greater size savings and better performance
u
oh so the invokedynamic is just about physical code size?
j
otherwise you have to spin all these classes at runtime and persist that dalvik bytecode somewhere and then load it back in so it can participate in the interpreter/JIT/AOT pipeline. the CPU/battery overhead is reportedly much higher than just doing it at compile-time when you have a computer plugged into a wall with infinite CPU
u
I though it somehow avoided instantiation
j
no change. it's always a class.
u
I see, devilish question.. does a VM make sense at all nowadays on Android, the way things shaped up?
I always though JVM was about multiplatformness
write java source, produce one binary and then push all the complexity to vm vendors to implement platform native
now, its a single OS, single cpu abi.. so..does it make sense?
e
Android is still multiplatform (x86, arm, riscv)
u
obviously hindsight is 2020 and library ecosysyem is a big thing
yea but is it really? if x86 is 1% Ill eat my hat 😀
e
count how many chromebooks with play store support there are
j
nvidia shield and chromebooks
yeah
e
u
werent android apps on chromebooks sort of a sideeffect? cant be 1% of all android users
e
I might agree that creating your own VM doesn't make as much sense now as it did back when Android started
u
I mean Im not sure what else should they have chosen, c++ is for aliens, dynamicly typed languages are the worst seems every OS creates its own language, c#, swift
maybe keep the java source because of libraries and deveopers and skip the vm and go straight to native from bytecode but that would blow the binary size up right? ios binaries are like 10x
e
bytecode is typically smaller than native code, yes. and we have DEX for a variety of reasons, partly to be even more compact than Java bytecode (but also because J2ME is rubbish and J2SE licensing was not as good back then, I believe)
j
yeah register-based bytecode saves a bunch of space compared to Java's stack-based bytecode
u
and to make use of all the size gains, lets pull in 30 dependencies 😀
j
apps were really small until the post-Honeycomb era
e
stats I found for 2021: 0.98 billion android devices sold, 37.3 million chromebooks even if a majority of those aren't x86 or don't support Android, I think there's a pretty good chance that's over 1%
u
but I believe bundling everything with the app was instrumental, like now, single compose version all the way back to api21 .. sometimes I have to work on ios and they never adopt new stuff, forbid its a bank of sorts, youll be lucky to even use swift because the cant bump from ios6
j
that was also a post-Honeycomb change, although it took Google a few years to really understand how much they needed to unbundle
u
but now you get a compose bytecode copy per app right? I think play store has a thing where they could share the library
j
there is no sharing
u
but how would that work even, different process/heap?
j
there are too many versions to share, and it breaks your ability to run R8. it was proposed about 6 years ago as a potential solution but the problem has only gotten worse that it's effectively impossible. you would just be wasting space.
u
yea, only downside for say compose is the debug performance, always catches me off guard how choppy the scrolling it, that taught me to ignore it, and now I wouldnt even spot a perf. issue if I had one 😀
e
https://chromium.googlesource.com/chromium/src.git/+/master/docs/android_native_libraries.md does manage to share code into different apps, but it's special
u
simply building release builds is still not up to par with views as they say
only when JIT kicks in then then its nice
lol monochrome 💯, and they say naming is hard
Aaaand as we were talking about it, java 17 features introduced in android 14 do we get real pattern matching now? record classes are just data classes, not real value types right?
e
pattern matching is a Java language feature, doesn't have any new bytecode or APIs. Android doesn't need to do anything.
records need to be desugared but d8 can do it. Android version doesn't matter. https://issuetracker.google.com/issues/197081367
u
oh I see, I thought kotlin was waiting for some jvm features to implement pattern matching
j
Efficient pattern matching requires invokedynamic and constant dynamic
u
efficient in dimension of bytecode size?
u
btw how far along are real value types in jvm?
I remember there being talk, then it died down, then records came up .. did they ditch it?
e
https://jdk.java.net/valhalla/ still work in progress
u
if they were implemented, and assuming there are no new instructions added to the bytecode grammar, how would that then look like on android? We would only need D8 to desugar it?
now that I think about it, why do we even need D8 😄 in order to desugar new bytecode, but why, since say
invoke dynamic
is unknown to ART?
okay I'll rephrase, if there is new bytecode, android will evaluate whether they can desugar, however if they cannot, then ART changes are needed, and therefore it implying minSdk?
e
invokedynamic can potentially build new things at runtime. its use in compiled lambdas is effectively static, so d8 can desugar it. other uses, not so much.
u
yea okay so they chose to not implement invoke dynamic, because they can get away with not implementing it but if a key feature like value types is impossible with their current instruction set, then what?
e
valhalla is still in flux; the changes so far aren't so much to the bytecode instructions, but instead change the object model
u
I see, but lets assume my assumption, i.e. it being impossible in current ART grammar, then the only solution is to build it into the runtime, and therefore implying a new minSdk?
e
yes
u
but there was the thing .... project mainline or something? probably could distribute it bit lower, but I think that was android 9+
but yea, thanks for clarifying!
e
there isn't a way to declare "my app only works on devices with this APEX module version or higher", afaik
u
so project mainline is not everywhere? atleast everywhere with play store?
btw does ART (lates version) have
invokedynamic
at all? I'm hearing conflicting things
j
yes they're called invoke-custom and invoke-polymorphic
u
and those are 26+?
j
probably 24. i would have to check
u
26
okay so they did introduce new bytecode in 28, but when implementing lambdas, they chose not to use it as desugared bytecode was somehow superior?
j
well, they added support for lambdas in like 2015 with the Jack compiler
and in order to target the versions of devices people actually wanted to use they had no choice but to desugar them to classes
u
I do get the desugaring bit pre-26, but why desugar on the runtime that effectively supports invokedynamic
j
i think we already covered that. 1. the OS doesn't ship LambdaMetafactory 2. the performance of the bytecode instruction is slower than classes 3. classes can be horizontally and vertically merged
u
I see, thanks!
tangent -- if I were to update to jvm target version 11, if it compiles then it should all work right? or do I need to test runtime as well?
j
if that is all you are changing then i suspect you're fine if it compiles
u
yes no new java api usage, just the emitted bytecode change
kk thanks