Typeconst proposal

The warning used to be a lot less alarming but then people complained when redefining const bindings caused problem, so it was made more alarming.

3 Likes

A load of global is currently unordered which is even weaker than the weakest monotonic ordering we can use in the Julia programs. monotonic is called relaxed in C++ and it doesn’t have any power of “cross-thread communication” on its own (it basically only guarantees that no load/store tearing happen). Furthermore, it’s formally proven that various compiler optimizations are legal under C++ memory model (which does not necessarily mean LLVM does that for us [1]). In particular, hoisting out relaxed loads out of the loop is allowed (unless there are stronger atomic operations in the loop). Since unordered is weaker than monotonic, the load can be hoisted out. Indeed, in 1.7.1:

julia> mutable struct Var{X}
           x::X
       end

julia> const V = Var(Int[]);

julia> function f(n)
           a = nothing
           for x in 1:max(1, n)
               a = V.x
           end
           a
       end;

julia> @code_llvm debuginfo=:none f(1)
define nonnull {}* @julia_f_137(i64 signext %0) #0 {
top:
  %1 = load atomic {}*, {}** inttoptr (i64 139758282988048 to {}**) unordered, align 16
  ret {}* %1
}

(But no, I’m not recommending global mutable states.)


  1. CppCon 2016: JF Bastien “No Sane Compiler Would Optimize Atomics" - YouTube ↩︎

2 Likes

You are right, we currently only emit unorderd loads for global bindings.

I don’t think this example actually illustrates it though, since T is a constant and constants are special-cased in codegen so that the actual load is elided. All you are seeing here is the load from V of the field x, not the actual lookup of the global binding V from the module, right?

I’m using getfield instead of GlobalRef to illustrate my point since I was too lazy to build your branch. Once the codegen emits the unordered load in the loop, what I pointed out holds independent of what syntax was used in the frontend. For example, I could have used

function f(v::Var, n)
    a = nothing
    for x in 1:max(1, n)
        a = v.x
    end
    a
end;

as an example. (A more important point is that Var holds a GC-manged object.)

Yes, this is exactly what I wanted to show.

1 Like

Ah, I see! I think I was just a bit confused since you made V a constant global.

Indeed, you can actually observe the same behavior with #43671:

julia> v::Int = 1;

julia> function f(n)
           a = 0
           for x in 1:max(1, n)
               a = v
           end
           a
       end;

julia> @code_llvm debuginfo=:none f(10)
define i64 @julia_f_1262(i64 signext %0) #0 {
top:
  %1 = load atomic i64*, i64** inttoptr (i64 140085820903256 to i64**) unordered, align 8
  %2 = load i64, i64* %1, align 8
  ret i64 %2
}
1 Like

Thanks for checking this! And yes, I agree it was confusing now that I think that this thread has been discussing const vs global. I should’ve used f(v::Var, n) as an example.

I submitted https://github.com/JuliaLang/julia/pull/44073

Thanks for asking! I don’t think you’re missing anything but it’s clear we have different perspectives (probably related to our disagreement on stacked enviroments). This has been on my mind for a while, I’ll probably write a blog post at some point, but here’s a first attempt to explain my point of view:

It’s hard to write reliable software, let’s say, software that has very low probability of behaving wrongly. There are so many ways to make mistakes and so many things to keep in mind while coding. Compilers and other tools can find problems, and of course writing tests helps a lot, but for the rest it’s on me: when writing serious code I’m always thinking “did I think of every possibility? is there anything that can go wrong here?” etc. It’s a huge mental load.

In this context, the most valuable thing a language or tool can give me is “Don’t worry about that, it can’t happen!”. But this only works if correctness comes first. Not as a tradeoff with convience and performance. Imagine a FastLock type that is 50% faster and works correctly 99.999% of the time… I’m not interested at all. Zero percent. So back to our const rebinding, “typical that everything works” is a big no-no.

About the warning, I was happy when it was improved. I remember when I discovered the const rebinding feature it used to say simply WARNING: redefining constant and I felt bad in the stomach… I wanted Julia to be perfect, and here was a case where the compiler finds a problem that can cause undefined behavior, and I just get a casual warning? I felt like “no way! how could the devs be so careless about correctness?” The new warning however suggests that more weight is given to correctness.

Maybe that’s what bothers me the most: what the feature communicates about the language’s priorities. It suggests that a convenient REPL experience is more important than a correct program. Worse: the feature is also available in package code, so it seems that restricting the feature to interactive use was deemed not worth the effort (or the weight of convenience was too pressing to wait).

Somehow this really hurt my sensibilities… My perspective is influenced by a background in fields that put a premium on reliability (industrial robotics and drone control software), but I’m probably not alone. Maybe Julia is losing something here in terms of contributions: I can imagine that a number of software engineers have looked at the language and were turned off by the relatively small consideration given to reliability. They might be precisely the people who would contribute to make important packages more “professional” in terms of reliability, documentation, etc. (no offense to the developers of these packages, their work is much appreciated!).

To end on a positive note: I have the feeling that things are moving in a good direction. The most offensive footguns are progressively retired (cf. the scary warning we’re discussing here and the documentation changes here and here for @pure).

1 Like

To elaborate on the const rebinding itself:

I don’t really see the value of the feature when as the warning says the correctness of the program is not guaranteed afterwards. Even for development, I would be annoyed when anything goes wrong that I must wonder if maybe that’s because of a const rebinding. And in the odd case where I want to copy-paste const definitions several times in the REPL, it’s not that hard to wrap the whole thing in module XXX ... end. Not worth having the language seem so careless about correctness.

An option to make the warning an error doesn’t make much sense for me, but the opposite, having an opt-in flag to allow const rebinding would be an improvement. As would be restricting the feature to interactive use.

I don’t mind if we allow executing const a = 4 several times. There’s no problem as long as the new assignment is === identical to the previous one.

It gets trickier with something like const A = [1,2,3]. I suppose even in this simple case a reassignment can produce an invalid program?

I like the idea of @StefanKarpinski of having full support for const rebinding, i.e. as a heavy operation that recompiles all necessary code, for interactive use. It still contradicts the semantics of const and would be terrible for readability, so I guess it should produce an error in non-interactive use.

1 Like

I really do not understand how do you shift the blame to the devs, if you use this feature, the burden is on you, just do not use it. Also, undefined behavior was always (in my C/C++ knowledge) exactly this undefined behaviour, the compiler is free to do whatever, but in this case, instead, it guarantees that you will receive an warning.

It suggests that a convenient REPL experience is more important than a correct program.

Again, you can never use this feature. I use it. It is a probability thing. I redefine the const, re-run the code, and if what I have changed does exactly what I expected, then I know no problem happened, and carry on; otherwise, I reload the REPL. It is not even that I do not care about reliability, it is that there is a test suit that will check things for me anyway at the end of the development cycle; I do not understand what I would gain from being slowed down in a previous step of the development cycle.

1 Like

Sorry for the confusion! With this:

How could the devs be so careless about correctness?

I was trying to reproduce an inner dialogue to describe how I felt, almost emotionally you could say, years ago on that day when I first stumbled on this feature. It’s not meant as an actual criticism of the developers. I have slightly edited my post to clarify it hopefullly.

if you use this feature, the burden is on you, just do not use it

In general this doesn’t scale well to a team and even worse to an ecosystem that I don’t control. But in this particular case that might actually work… Assuming I’m also warned when const is “misused” in one of my dependencies, and assuming the warning doesn’t get lost in precompilation messages or whatever.

Anyway the point of my post was to answer @StefanKarpinski who was trying to understand my point of view. I actually worked a long time on that post trying to analyze and articulate my position and still apparently just produced more misunderstanding. Sorry.

2 Likes

Is there any package out there that actually redefines a constant? I agree that would be bad, but I have never seen that in the wild, which makes this a purely theoretical concern to me.

3 Likes

Agreed, I think it’s quite unlikely. If it ever happens I guess it will be found quickly by users that see the warning. So it seems like one of those case where the “if you don’t like it, don’t do it” idea can work.

Thank you for taking the time to articulate it. I think the very high order reassurance that I can offer is this: you only need to worry if code prints a warning. No warning, no worry. If there are any warnings when your code runs, read them and take them seriously.

6 Likes