How to use Base.Libc.errno

The docs for errno in Julia states:

The value of errno is only valid immediately after a ccall to a C library routine that sets it. Specifically, you cannot call errno at the next prompt in a REPL, because lots of code is executed between prompts.

What does “immediately after” mean? Isn’t the compiler generally free to reorder things or interrupt a task to run GC, finalizers, or other tasks wherever it wants?

Is there some special way to force the compiler to run errno “immediately” after a ccall, or should the errno function just be avoided in Julia?

No not quite there are some rules.
The task scheduler is cooperative, so a task has to call yield (either explicitly or implicitly through calling into the runtime, or calling wait) to be switched away from.

Similar with GC, you have to perform an allocation or hit a safepoint (which actually could be an issue since every function has a safepoint at the beginning)

And the compiler can’t reorder things that have arbitrary effects. So since errno and the ccall are not “pure” they have to be executed in order and the compiler can’t move things past them.

One potential issue might be interrupts, but that’s also an issue on C afaik.

So immediately really means:

call()
errno()

Any expression in between could cause the issue you are worried about.

2 Likes

Thank you for the explanation, I didn’t know about the not “pure” thing.

Okay, so something like the following is good?
I initially tried to do e = Base.Libc.errno(), but that adds a call to Base.getproperty

@noinline function rename(oldpath::AbstractString, newpath::AbstractString)
    errno = Base.Libc.errno
    e = @ccall rename(oldpath::Cstring, newpath::Cstring)::Cint
    er = errno()
    if !iszero(e)
        error("rename error $(Base.Libc.strerror(er))")
    end
    newpath
end
julia> @code_lowered rename("","")
CodeInfo(
1 ─       nothing
│   %2  = Base.Libc
│         errno = Base.getproperty(%2, :errno)
│   %4  = Base.cconvert(Main.Cstring, oldpath)
│   %5  = Base.cconvert(Main.Cstring, newpath)
│   %6  = Base.unsafe_convert(Main.Cstring, %4)
│   %7  = Base.unsafe_convert(Main.Cstring, %5)
│         e = $(Expr(:foreigncall, :(:rename), Int32, svec(Cstring, Cstring), 0, :(:ccall), :(%6), :(%7), :(%5), :(%4)))
│         er = (errno)()
│   %10 = Main.iszero(e)
│   %11 = !%10
└──       goto #3 if not %11
2 ─ %13 = Base.Libc
│   %14 = Base.getproperty(%13, :strerror)
│   %15 = (%14)(er)
│   %16 = Base.string("rename error ", %15)
└──       Main.error(%16)
3 ┄       return newpath
)

Yeah that’s correct.

I would simply import Base.Libc: errno, but thats a matter of style.

Also check code_typed since we actually need to inline errno