-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Rewrite printf functionality #32859
Rewrite printf functionality #32859
Conversation
You may already have fixed this, but note #16977. |
This code is so impressively bonkers 😂. Thank you for doing this, @quinnj! |
wow great work. Does this make it any easier to implement a non-macro |
@StefanKarpinski, I agree :). It's interesting to go back and look at all this code that was written so early on, like a peek into an "old world" of sorts. I think it's a big testament to how much the compiler and code generation "mental model" has improved: I think reaching for meta-programming used to be so much more necessary in order to hand-craft code, whereas today you can rely so much on the compiler that you can just write the code much more "naturally" and everything just works. @musm, yes, the approach in this PR basically follows what we do for macro printf(io, f, args...)
fmt = Printf.Format(f)
return esc(:(Printf.format($io, $fmt, $(args...)))
end |
contrib/generate_precompile.jl
Outdated
@@ -137,7 +137,9 @@ function generate_precompile_statements() | |||
# Extract the precompile statements from stderr | |||
statements = Set{String}() | |||
for statement in eachline(precompile_file_h) | |||
occursin("Main.", statement) && continue | |||
(occursin("Main.", statement) || | |||
occursin("Printf.", statement) || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why disable all precompile in the printf stdlib?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I was going to mention it to you, it seems we run into an unfortunate bug w/ how literally we're generating the precompile statements:
Generating precompile statements...┌ Error: Failed to precompile precompile(Tuple{typeof(Base.Printf.format), Array{UInt8, 1}, Int64, Base.Printf.Format{Base.CodeUnits{UInt8, String}, Tuple{Base.Printf.Spec{Base.Val{Char(0x64000000)}}, Base.Printf.Spec{Base.Val{Char(0x73000000)}}, Base.Printf.Spec{Base.Val{Char(0x73000000)}}}}, Float64, String, String})
└ @ Main.anonymous /Users/jacobquinn/juliasrc/contrib/generate_precompile.jl:167
ERROR: LoadError: LoadError: Base.CodePointError{UInt32}(0x64000000)
Stacktrace:
[1] code_point_err(::UInt32) at ./char.jl:86
[2] Char(::UInt32) at ./char.jl:160
[3] top-level scope at string:1
[4] include_string(::Module, ::String, ::String) at ./loading.jl:1064
[5] include_string at ./loading.jl:1068 [inlined]
[6] macro expansion at /Users/jacobquinn/juliasrc/contrib/generate_precompile.jl:165 [inlined]
[7] macro expansion at ./util.jl:212 [inlined]
[8] (::getfield(Main.anonymous, Symbol("##2#6")){Float64,Base.DevNull})(::String, ::IOStream) at /Users/jacobquinn/juliasrc/contrib/generate_precompile.jl:159
[9] mktemp(::getfield(Main.anonymous, Symbol("##2#6")){Float64,Base.DevNull}, ::String) at ./file.jl:577
[10] mktemp at ./file.jl:575 [inlined]
[11] generate_precompile_statements() at /Users/jacobquinn/juliasrc/contrib/generate_precompile.jl:70
[12] top-level scope at /Users/jacobquinn/juliasrc/contrib/generate_precompile.jl:180
in expression starting at string:1
in expression starting at /Users/jacobquinn/juliasrc/contrib/generate_precompile.jl:8
*** This error is usually fixed by running `make clean`. If the error persists, try `make cleanall`. ***
make[1]: *** [/Users/jacobquinn/juliasrc/usr/lib/julia/sys-o.a] Error 1
make: *** [julia-sysimg-release] Error 2
I also added the other check because I ran into:
Generating precompile statements...┌ Error: Failed to precompile precompile(Tuple{typeof(Base.io_has_tvar_name), Base.IOContext{REPL.Terminals.TTYTerminal}, Symbol, Type{Vararg{Any, N} where N}})
└ @ Main.anonymous /Users/jacobquinn/juliasrc/contrib/generate_precompile.jl:171
ERROR: LoadError: LoadError: TypeError: in Type, in parameter, expected Type, got Vararg{Any,N} where N
Stacktrace:
[1] top-level scope at string:1
[2] include_string(::Module, ::String, ::String) at ./loading.jl:1064
[3] include_string at ./loading.jl:1068 [inlined]
[4] macro expansion at /Users/jacobquinn/juliasrc/contrib/generate_precompile.jl:169 [inlined]
[5] macro expansion at ./util.jl:212 [inlined]
[6] (::getfield(Main.anonymous, Symbol("##2#6")){Float64,Base.DevNull})(::String, ::IOStream) at /Users/jacobquinn/juliasrc/contrib/generate_precompile.jl:163
[7] mktemp(::getfield(Main.anonymous, Symbol("##2#6")){Float64,Base.DevNull}, ::String) at ./file.jl:577
[8] mktemp at ./file.jl:575 [inlined]
[9] generate_precompile_statements() at /Users/jacobquinn/juliasrc/contrib/generate_precompile.jl:70
[10] top-level scope at /Users/jacobquinn/juliasrc/contrib/generate_precompile.jl:184
in expression starting at string:1
in expression starting at /Users/jacobquinn/juliasrc/contrib/generate_precompile.jl:8
*** This error is usually fixed by running `make clean`. If the error persists, try `make cleanall`. ***
make[1]: *** [/Users/jacobquinn/juliasrc/usr/lib/julia/sys-o.a] Error 1
make: *** [julia-sysimg-release] Error 2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first precompile statement errors because Char(0x64000000)
throws an error, but it should just be Char('d')
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#32610 should fix the Char problem.
Blacklisting the whole stdlib seems a bit heavy handed. Can't we just list the methods that fail?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean, the whole stdlib is @printf
and @sprintf
, which lower to Printf.format
, so......no, there aren't any other methods that fail, because there's only one. Can we expedite #32610?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see. If #32610 fixes the whole thing it would be good to just merge that. Otherwise, I guess we can just blacklist the module but this feels like a method that would be quite nice to precompile so we should try not to forget about it.
036d510
to
6c5d329
Compare
Just a status update here: tests are all passing, so I feel like #32799 and this PR could be merged at any time. Happy to add a NEWS entry if people would like, though there should be no user-facing changes. The one awkwardness in this PR is the skipping of precompiling for the Printf module, since there seems to be some lower/precompile-statement-generation bug, but it sounds like there might be a fix for that. |
Looking at this somewhat quickly, I'm a bit more concerned about the implementation of this and leaves me wondering what we should do before the freeze. I'd be somewhat interested to know if the compile-time dispatch is essential, because that seems to be adding a lot of complexity here and performance cliffs here. It also looked liked you had to hard-code all of the type behaviors so that it's not really extensible anymore. Finally, is anyone else currently extending Printf.jl that'll be broken by this? While I get that it was hard to extend the old code, it was possible, so someone might be depending on that, but I just don't know. We could move the existing Printf (and Grisu) code inside the printf module and move it to Compat to preserve that API as a down-grade version option as a preparatory step. |
DecFP.jl extends Base.Printf.fix_dec and Base.Printf.ini_dec to allow |
Yeah, it looks like ArbNumerics.jl, QuadMath.jl, DoubleFloats.jl, and DecFP.jl are the only packages that extended Printf. I'll look into their usages to see what we can do for compat here. |
Quick update on Printf package extenders:
@vtjnash, I looked into removing the conversion specifier dispatching, but the result wasn't very satisfying. You end up with So all-in-all, I'm inclined to keep the dispatch mechanics since it's a nice performance boost, while the "world" of possible types is still pretty small. |
Ok, I've cleaned things up a bit more and added a new # for DoubleFloats.jl
Printf.tofloat(x::Double64) = Float64(x)
Printf.tofloat(x::Double32) = Float32(x)
Printf.tofloat(x::Double16) = Float16(x)
# for ArbNumerics.jl
Printf.tofloat(x::ArbFloat) = BigFloat(x) For QuadMath.jl, I modified the function Printf.fmt(buf, pos, arg::Float128, spec::Printf.Spec{T}) where {T <: Printf.Floats}
ptr = pointer(buf, pos)
newpos = ccall((:quadmath_snprintf, : libquadmath), Cint, (Ptr{UInt8}, Csize_t, Ptr{UInt8}, Cfloat128), ptr, length(buf), string(spec; modifier="Q"), d)
return pos + newpos
end So those both seem like good solutions. I'll keep noodling on the DecFP.jl case. |
The 32-bit windows build failed w/ something about reinterpretarray.jl or higherorderfns.jl crashes? Have we seen anything like that before? The freebsd build failed on something related to libgit2. It doesn't seem like either of those issues are related to changes here, but speak up if you think otherwise. |
Ok, I slept on the DecFP.jl situation and I think I have a plan that could work well, it looks something like:
We already have most of this logic lying around, so I don't think it will be too much lift to add it in and get it working. |
Ok, I pushed some compat code in the form of |
That, and also the dispatch penalty of putting all of this into type-parameters computed from runtime values. Just doesn't seem like a great trade-off. |
@StefanKarpinski, do you still want to review this before merging? Looks like it needs a rebase, but I'm happy to merge if you don't think you'll be able to get to it any time soon. |
I think this PR might have broken ProgressMeter: timholy/ProgressMeter.jl#167 It looks like the offending line in the ProgressMeter source code might be this line. Is this something that needs to be fixed in Julia or in the ProgressMeter package? |
Seems to be a bug in the new printf code: julia> @sprintf "%%%s" "a" # ok
"%%a"
julia> @sprintf "%s%%%s" "a" # should require two args
"a%%%s"
julia> @sprintf "%s%%%s" "a" "b" # should produce "a%%b"
ERROR: ArgumentError: mismatch between # of format specifiers and provided args: 1 != 2 |
Addresses #32859 (comment). The issue here was advancing the parsing position too far when an escaped `'%'` was encountered after the first format specifier. If an escaped `'%'` was then followed by another specifier, it was ignored due to being classified as an "escaped" character.
Addresses #32859 (comment). The issue here was advancing the parsing position too far when an escaped `'%'` was encountered after the first format specifier. If an escaped `'%'` was then followed by another specifier, it was ignored due to being classified as an "escaped" character.
Ok, fix for problem w/ escaped |
Ref. JuliaLang/julia#32859, JuliaMath/DoubleFloats.jl#125. This is causing warnings downstream in CUDA.jl.
Based on #32799.
The main commits unique here rewrite the internal
base/printf.jl
functionality for about half the code, with 2-3x perf improvements. It utilizes the new ryu float printing mechanics in #32799. These commits also remove all the grisu code.Status: this is pretty far along, I haven't done the
%a
/%A
type specifier yet, or made it work forBigFloat
either, and there's still a good chunk of tests we need, but it's pretty useable and worth starting to get reviews on.