-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Order of 'mc' synthetic methods is not deterministic #17330
Comments
Hi @raboof, I'm afraid I am not able to reproduce this issue. Running javap on public int apply$mcII$sp(int);
public int apply$mcIJ$sp(long);
public int apply$mcIF$sp(float);
public int apply$mcID$sp(double);
public double apply$mcDI$sp(int);
public double apply$mcDJ$sp(long);
public double apply$mcDF$sp(float);
public double apply$mcDD$sp(double);
public long apply$mcJI$sp(int);
public long apply$mcJJ$sp(long);
public long apply$mcJF$sp(float);
public long apply$mcJD$sp(double);
public void apply$mcVI$sp(int);
public void apply$mcVJ$sp(long);
public void apply$mcVF$sp(float);
public void apply$mcVD$sp(double);
public boolean apply$mcZI$sp(int);
public boolean apply$mcZJ$sp(long);
public boolean apply$mcZF$sp(float);
public boolean apply$mcZD$sp(double);
public float apply$mcFI$sp(int);
public float apply$mcFJ$sp(long);
public float apply$mcFF$sp(float);
public float apply$mcFD$sp(double); Same with public static double apply$mcDD$sp(double);
public static double apply$mcDF$sp(float);
public static double apply$mcDI$sp(int);
public static double apply$mcDJ$sp(long);
public static float apply$mcFD$sp(double);
public static float apply$mcFF$sp(float);
public static float apply$mcFI$sp(int);
public static float apply$mcFJ$sp(long);
public static int apply$mcID$sp(double);
public static int apply$mcIF$sp(float);
public static int apply$mcII$sp(int);
public static int apply$mcIJ$sp(long);
public static long apply$mcJD$sp(double);
public static long apply$mcJF$sp(float);
public static long apply$mcJI$sp(int);
public static long apply$mcJJ$sp(long);
public static void apply$mcVD$sp(double);
public static void apply$mcVF$sp(float);
public static void apply$mcVI$sp(int);
public static void apply$mcVJ$sp(long);
public static boolean apply$mcZD$sp(double);
public static boolean apply$mcZF$sp(float);
public static boolean apply$mcZI$sp(int);
public static boolean apply$mcZJ$sp(long); For the record, I also tried comparing those via a |
Interesting. I definitely get different ones, for example:
vs
I'm building with SBT, I haven't tried with the standalone Scala compiler yet. I've shared the project (and 2 class files from separate compilations) at https://codeberg.org/raboof/scala-3-reproducible-apply-mc-forwarders |
Invoking scalac (testing with 3.2.2) directly I did see it once:
... but not consistently: it's giving the same order (a different one from the ones posted above...) each time now. (I added that |
Changing (or adding or removing) files could affect this, but does that spec expect that not to affect the output? We store the filenames in a global names map (for reasons unknown to me), which impacts the hash order of later names, which can impact method iteration order. Some derivative operations handle this, but not all. |
I expect the spec doesn't dictate any particular ordering, but it would still be good if the compiler produced a stable ordering. It's interesting to note that the static forwarders do explicitly get a stable ordering (https://github.com/lampepfl/dotty/blob/main/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala#L489). I'm not sure where the non-forwarder method iteration order gets determined, but it's interesting that so far it seems it's only the |
I would like to provide some additional context here. This feature is particularly useful for projects that are part of the ASF i.e. Apache Software Foundation which in our case is Pekko but this would also apply to any Scala project in the ASF. Currently in order to make a release in the ASF we have to do it manually, that is someone on a local machine (technically speaking owner controlled hardware) needs to produce and publish the artifacts (typically Ideally we would love to be able to just use github actions CI to create release artifacts which is what is typical with almost all Scala OS projects (i.e. push a git tag which triggers a build that pushes to Maven). This has already been setup for snapshots in Pekko however due to ASF processes we cannot do this for actual releases. There are many reasons why (i.e. one is git tags being mutable but this is now hopefully resolved with git tag protection rules however the other main blocker is reproducible builds. More specifically when RC's are published by the CI, other release managers need to confirm that the build can be reproduced by matching the hashes to a build that they create locally to confirm there hasn't been any tampering/mistakes in the artifacts generated by the CI. While other Scala OS projects may not have as strict requirements, they would nonetheless benefit from a such a feature if they desire. Onto the technicals, while I understand there may be arguments against doing this for Scala 3 (i.e. not part of the specification, potential performance implications, etc etc) I believe this can be reasonably resolved by putting it behind flag (i.e. |
In my book, for a compiler, stability of the output is a standard feature. I don't think it's controversial that we should fix this. |
Perfect, it would also be fantastic if this would be back-ported to the Scala 3.3 LTS series since that is what most of the Scala OS libraries will target (Pekko inclusive) and this would mean we can update the Scala version without breaking users. |
Bug fixes that are forward and backward source and binary compatible will be backported to LTS. That's the definition of LTS. Things that don't follow that policy won't. |
LTS stands for Long Term Support which can mean different things to different projects, i.e. in context of Scala 3 it could have been looser in regards to which forwards/backwards compatible bug fixes would be backported but its good to know this is the definition! |
I meant that it is our (Scala 3's) definition of LTS. See https://virtuslab.com/blog/the-scala-3-compatibility-story/ for the most recently published details. |
Yup, it's all clear. Thanks for clarifying. |
The slight confusion here is that the problem is not the forwarders (= static methods added to the mirror class without the The problem is the ordering of the specialized methods in the module itself. |
Sorry, I didn't mean the Scala spec, I meant the Reproducible Builds spec: does it not expect that changing the order of inputs changes the output? Run |
If the ordering of the inputs to |
Correct. So it might be the case that this is about how the build tool discovers and passes those files to the compiler - in a way that is cross-OS compatible. I'm mostly being dotc's lawyer here - but I'm not against just sorting them like we do for the static forwarders. I'm even more of a fan of someone looking at whether we can stop adding the filenames in a global names and avoid that impact. |
Aah, right: indeed the Reproducible Builds spec just says "building the same source should produce the same output". Indeed it would be totally acceptable for dotc to only produce the same output when invoked with the same parameters in the same order. However, then for the whole sbt build to be reproducible it would shift the responsibility to sbt to always invoke the compoiler with the same parameters in the same order.
That is actually not the case, though: I just ran |
I.e. the mixin forwarders are created in a different order. Here is a simpler reproducer:
|
The undefined order comes from The non-determinism comes from |
So, one solution would be to sort in |
Fixes scala#17330 Co-Authored-By: Nicolas Stucki <nicolas.stucki@gmail.com>
Is there anything I can/should do to make that happen or should it be 'automatic'? |
It's been added to https://github.com/orgs/lampepfl/projects/6, so it's going to be "automatic". |
❤️ thanks! (that project appears to be private btw - but I believe you ;) ) |
Follow-up on apache#377 Should get us the fix for scala/scala3#17330
Follow-up on apache#195 Should get us the fix for scala/scala3#17330
Compiler version
Tested with 3.2.2 and 3.3.1-RC1-bin-20230422-49879ac-NIGHTLY
Minimized code
Output
Compiling this object several time and looking at the compiled output, the order of
long apply$mcJI$sp(int)
,long apply$mcJJ$sp(long)
,long apply$mcJF$sp(float)
etc is not deterministic.Expectation
I would expect the order to be the same each time.
Making the compiler output deterministic helps quickly verifying two independently-built executables are equivalent, which can be a useful security property. It also may help with cache performance and for making it easier to find the 'meaningful' differences when comparing two class files that have more differences. See also Reproducible Builds and #7661 .
The text was updated successfully, but these errors were encountered: