• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / #37611

05 Sep 2023 05:43AM UTC coverage: 86.055% (-0.4%) from 86.433%
#37611

push

local

web-flow
optimizer: Do not run if we know the function is ConstABI eligible (#51143)

If we can determine that a function is sufficiently pure and returns a
constant, we have special ABI that lets us throw away the code for it
and just return the constant. However, we were still going through all
the steps of actually computing the code, including running the
optimizer on it, compressing it in preparation for caching, etc. We can
just stop doing that and bypass the optimizer entirely.

The actual change to do this is pretty tiny, but there's some unexpected
fallout which this needs to cleanup:
1. Various tests expect code_* queries of things inferred to ConstABI to
still return reasonable code. Fix this by manually emitting a ReturnNode
at the end of inference if the caller is a reflection function.
2. This kinda wreaks havoc on the EscapeAnalysis tests, which work by
using a side-effect of the optimizer, but a lot of the functions are
ConstABI eligible, so the optimizer doesn't run any more. Fortunately,
I'm in the middle of looking at overhauling EscapeAnalysis, so I'll have
some chance to figure out how to change the test system to actually do
this properly, rather than exfiltrating side effects. That said, since
EscapeAnalysis is not in the default pipeline, I don't think we should
hold the perf improvement until that is done.

Benchmarked to be about 10% faster locally, but we'll see what
nanosoldier thinks.

---------

Co-authored-by: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com>

44 of 44 new or added lines in 1 file covered. (100.0%)

73307 of 85186 relevant lines covered (86.06%)

12747443.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.31
/stdlib/Dates/src/parse.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
### Parsing utilities
4

5
_directives(::Type{DateFormat{S,T}}) where {S,T} = T.parameters
245✔
6

7
character_codes(df::Type{DateFormat{S,T}}) where {S,T} = character_codes(_directives(df))
167✔
8
function character_codes(directives::Core.SimpleVector)
245✔
9
    letters = sizehint!(Char[], length(directives))
245✔
10
    for (i, directive) in enumerate(directives)
490✔
11
        if directive <: DatePart
1,579✔
12
            letter = first(directive.parameters)
938✔
13
            push!(letters, letter)
938✔
14
        end
15
    end
2,913✔
16
    return letters
245✔
17
end
18

19
genvar(t::DataType) = Symbol(lowercase(string(nameof(t))))
1,960✔
20

21
"""
22
    tryparsenext_core(str::AbstractString, pos::Int, len::Int, df::DateFormat, raise=false)
23

24
Parse the string according to the directives within the `DateFormat`. Parsing will start at
25
character index `pos` and will stop when all directives are used or we have parsed up to
26
the end of the string, `len`. When a directive cannot be parsed the returned value
27
will be `nothing` if `raise` is false otherwise an exception will be thrown.
28

29
If successful, return a 3-element tuple `(values, pos, num_parsed)`:
30
* `values::Tuple`: A tuple which contains a value
31
  for each `DatePart` within the `DateFormat` in the order
32
  in which they occur. If the string ends before we finish parsing all the directives
33
  the missing values will be filled in with default values.
34
* `pos::Int`: The character index at which parsing stopped.
35
* `num_parsed::Int`: The number of values which were parsed and stored within `values`.
36
  Useful for distinguishing parsed values from default values.
37
"""
38
@generated function tryparsenext_core(str::AbstractString, pos::Int, len::Int,
226✔
39
                                      df::DateFormat, raise::Bool=false)
40
    directives = _directives(df)
78✔
41
    letters = character_codes(directives)
78✔
42

43
    tokens = Type[CONVERSION_SPECIFIERS[letter] for letter in letters]
156✔
44
    value_names = Symbol[genvar(t) for t in tokens]
156✔
45
    value_defaults = Tuple(CONVERSION_DEFAULTS[t] for t in tokens)
78✔
46

47
    # Pre-assign variables to defaults. Allows us to use `@goto done` without worrying about
48
    # unassigned variables.
49
    assign_defaults = Expr[]
78✔
50
    for (name, default) in zip(value_names, value_defaults)
156✔
51
        push!(assign_defaults, quote
302✔
52
            $name = $default
789✔
53
        end)
54
    end
526✔
55

56
    vi = 1
78✔
57
    parsers = Expr[]
78✔
58
    for i = 1:length(directives)
78✔
59
        if directives[i] <: DatePart
511✔
60
            name = value_names[vi]
302✔
61
            vi += 1
302✔
62
            push!(parsers, quote
302✔
63
                pos > len && @goto done
734✔
64
                let val = tryparsenext(directives[$i], str, pos, len, locale)
1,429✔
65
                    val === nothing && @goto error
731✔
66
                    $name, pos = val
720✔
67
                end
68
                num_parsed += 1
720✔
69
                directive_index += 1
720✔
70
            end)
71
        else
72
            push!(parsers, quote
209✔
73
                pos > len && @goto done
504✔
74
                let val = tryparsenext(directives[$i], str, pos, len, locale)
984✔
75
                    val === nothing && @goto error
485✔
76
                    delim, pos = val
474✔
77
                end
78
                directive_index += 1
474✔
79
            end)
80
        end
81
    end
511✔
82

83
    return quote
78✔
84
        directives = df.tokens
226✔
85
        locale::DateLocale = df.locale
226✔
86

87
        num_parsed = 0
226✔
88
        directive_index = 1
226✔
89

90
        $(assign_defaults...)
×
91
        $(parsers...)
×
92

93
        pos > len || @goto error
182✔
94

95
        @label done
×
96
        return $(Expr(:tuple, value_names...)), pos, num_parsed
198✔
97

98
        @label error
×
99
        if raise
28✔
100
            if directive_index > length(directives)
27✔
101
                throw(ArgumentError("Found extra characters at the end of date time string"))
6✔
102
            else
103
                d = directives[directive_index]
21✔
104
                throw(ArgumentError("Unable to parse date time. Expected directive $d at char $pos"))
21✔
105
            end
106
        end
107
        return nothing
1✔
108
    end
109
end
110

111
"""
112
    tryparsenext_internal(::Type{<:TimeType}, str, pos, len, df::DateFormat, raise=false)
113

114
Parse the string according to the directives within the `DateFormat`. The specified `TimeType`
115
type determines the type of and order of tokens returned. If the given `DateFormat` or string
116
does not provide a required token a default value will be used. When the string cannot be
117
parsed the returned value will be `nothing` if `raise` is false otherwise an exception will
118
be thrown.
119

120
If successful, returns a 2-element tuple `(values, pos)`:
121
* `values::Tuple`: A tuple which contains a value
122
  for each token as specified by the passed in type.
123
* `pos::Int`: The character index at which parsing stopped.
124
"""
125
@generated function tryparsenext_internal(::Type{T}, str::AbstractString, pos::Int, len::Int,
399✔
126
                                          df::DateFormat, raise::Bool=false) where T<:TimeType
127
    letters = character_codes(df)
324✔
128

129
    tokens = Type[CONVERSION_SPECIFIERS[letter] for letter in letters]
324✔
130
    value_names = Symbol[genvar(t) for t in tokens]
324✔
131

132
    output_tokens = CONVERSION_TRANSLATIONS[T]
162✔
133
    output_names = Symbol[genvar(t) for t in output_tokens]
1,212✔
134
    output_defaults = Tuple(CONVERSION_DEFAULTS[t] for t in output_tokens)
162✔
135

136
    # Pre-assign output variables to defaults. Ensures that all output variables are
137
    # assigned as the value tuple returned from `tryparsenext_core` may not include all
138
    # of the required variables.
139
    assign_defaults = Expr[
1,212✔
140
        quote
141
            $name = $default
1,108✔
142
        end
143
        for (name, default) in zip(output_names, output_defaults)
144
    ]
145

146
    # Unpacks the value tuple returned by `tryparsenext_core` into separate variables.
147
    value_tuple = Expr(:tuple, value_names...)
162✔
148

149
    return quote
162✔
150
        val = tryparsenext_core(str, pos, len, df, raise)
213✔
151
        val === nothing && return nothing
187✔
152
        values, pos, num_parsed = val
186✔
153
        $(assign_defaults...)
×
154
        $value_tuple = values
186✔
155
        return $(Expr(:tuple, output_names...)), pos
186✔
156
    end
157
end
158

159
@inline function tryparsenext_base10(str::AbstractString, i::Int, len::Int, min_width::Int=1, max_width::Int=0)
777✔
160
    i > len && return nothing
797✔
161
    min_pos = min_width <= 0 ? i : i + min_width - 1
1,423✔
162
    max_pos = max_width <= 0 ? len : min(i + max_width - 1, len)
811✔
163
    d::Int64 = 0
757✔
164
    @inbounds while i <= max_pos
757✔
165
        c, ii = iterate(str, i)::Tuple{Char, Int}
4,555✔
166
        if '0' <= c <= '9'
2,279✔
167
            d = d * 10 + (c - '0')
1,802✔
168
        else
169
            break
477✔
170
        end
171
        i = ii
1,802✔
172
    end
1,802✔
173
    if i <= min_pos
757✔
174
        return nothing
3✔
175
    else
176
        return d, i
754✔
177
    end
178
end
179

180
@inline function tryparsenext_word(str::AbstractString, i, len, locale, maxchars=0)
35✔
181
    word_start, word_end = i, 0
35✔
182
    max_pos = maxchars <= 0 ? len : min(len, nextind(str, i, maxchars-1))
41✔
183
    @inbounds while i <= max_pos
35✔
184
        c, ii = iterate(str, i)::Tuple{Char, Int}
275✔
185
        if isletter(c)
139✔
186
            word_end = i
111✔
187
        else
188
            break
28✔
189
        end
190
        i = ii
111✔
191
    end
111✔
192
    if word_end == 0
35✔
193
        return nothing
2✔
194
    else
195
        return SubString(str, word_start, word_end), i
33✔
196
    end
197
end
198

199
function Base.parse(::Type{DateTime}, s::AbstractString, df::typeof(ISODateTimeFormat))
24✔
200
    i, end_pos = firstindex(s), lastindex(s)
24✔
201
    i > end_pos && throw(ArgumentError("Cannot parse an empty string as a DateTime"))
24✔
202

203
    local dy
×
204
    dm = dd = Int64(1)
20✔
205
    th = tm = ts = tms = Int64(0)
20✔
206

207
    let val = tryparsenext_base10(s, i, end_pos, 1)
40✔
208
        val === nothing && @goto error
20✔
209
        dy, i = val
20✔
210
        i > end_pos && @goto error
20✔
211
    end
212

213
    c, i = iterate(s, i)::Tuple{Char, Int}
40✔
214
    c != '-' && @goto error
20✔
215
    i > end_pos && @goto done
20✔
216

217
    let val = tryparsenext_base10(s, i, end_pos, 1, 2)
40✔
218
        val === nothing && @goto error
20✔
219
        dm, i = val
20✔
220
        i > end_pos && @goto done
20✔
221
    end
222

223
    c, i = iterate(s, i)::Tuple{Char, Int}
40✔
224
    c != '-' && @goto error
20✔
225
    i > end_pos && @goto done
20✔
226

227
    let val = tryparsenext_base10(s, i, end_pos, 1, 2)
40✔
228
        val === nothing && @goto error
20✔
229
        dd, i = val
20✔
230
        i > end_pos && @goto done
20✔
231
    end
232

233
    c, i = iterate(s, i)::Tuple{Char, Int}
20✔
234
    c != 'T' && @goto error
10✔
235
    i > end_pos && @goto done
10✔
236

237
    let val = tryparsenext_base10(s, i, end_pos, 1, 2)
20✔
238
        val === nothing && @goto error
10✔
239
        th, i = val
10✔
240
        i > end_pos && @goto done
10✔
241
    end
242

243
    c, i = iterate(s, i)::Tuple{Char, Int}
20✔
244
    c != ':' && @goto error
10✔
245
    i > end_pos && @goto done
10✔
246

247
    let val = tryparsenext_base10(s, i, end_pos, 1, 2)
20✔
248
        val === nothing && @goto error
10✔
249
        tm, i = val
10✔
250
        i > end_pos && @goto done
10✔
251
    end
252

253
    c, i = iterate(s, i)::Tuple{Char, Int}
20✔
254
    c != ':' && @goto error
10✔
255
    i > end_pos && @goto done
10✔
256

257
    let val = tryparsenext_base10(s, i, end_pos, 1, 2)
20✔
258
        val === nothing && @goto error
10✔
259
        ts, i = val
10✔
260
        i > end_pos && @goto done
10✔
261
    end
262

263
    c, i = iterate(s, i)::Tuple{Char, Int}
2✔
264
    c != '.' && @goto error
1✔
265
    i > end_pos && @goto done
1✔
266

267
    let val = tryparsenext_base10(s, i, end_pos, 1, 3)
2✔
268
        val === nothing && @goto error
1✔
269
        tms, j = val
1✔
270
        tms *= 10 ^ (3 - (j - i))
1✔
271
        j > end_pos || @goto error
1✔
272
    end
273

274
    @label done
×
275
    return DateTime(dy, dm, dd, th, tm, ts, tms)
20✔
276

277
    @label error
×
278
    throw(ArgumentError("Invalid DateTime string"))
×
279
end
280

281
function Base.parse(::Type{T}, str::AbstractString, df::DateFormat=default_format(T)) where T<:TimeType
236✔
282
    pos, len = firstindex(str), lastindex(str)
236✔
283
    pos > len && throw(ArgumentError("Cannot parse an empty string as a Date or Time"))
227✔
284
    val = tryparsenext_internal(T, str, pos, len, df, true)
394✔
285
    @assert val !== nothing
184✔
286
    values, endpos = val
184✔
287
    return T(values...)::T
184✔
288
end
289

290
function Base.tryparse(::Type{T}, str::AbstractString, df::DateFormat=default_format(T)) where T<:TimeType
13✔
291
    pos, len = firstindex(str), lastindex(str)
13✔
292
    pos > len && return nothing
9✔
293
    res = tryparsenext_internal(T, str, pos, len, df, false)
5✔
294
    res === nothing && return nothing
3✔
295
    values, endpos = res
2✔
296
    if validargs(T, values...) === nothing
2✔
297
        # TODO: validargs gets called twice, since it's called again in the T constructor
298
        return T(values...)::T
1✔
299
    end
300
    return nothing
1✔
301
end
302

303
"""
304
    parse_components(str::AbstractString, df::DateFormat) -> Array{Any}
305

306
Parse the string into its components according to the directives in the `DateFormat`.
307
Each component will be a distinct type, typically a subtype of Period. The order of the
308
components will match the order of the `DatePart` directives within the `DateFormat`. The
309
number of components may be less than the total number of `DatePart`.
310
"""
311
@generated function parse_components(str::AbstractString, df::DateFormat)
13✔
312
    letters = character_codes(df)
10✔
313
    tokens = Type[CONVERSION_SPECIFIERS[letter] for letter in letters]
10✔
314

315
    return quote
5✔
316
        pos, len = firstindex(str), lastindex(str)
13✔
317
        val = tryparsenext_core(str, pos, len, df, #=raise=#true)
13✔
318
        @assert val !== nothing
12✔
319
        values, pos, num_parsed = val
12✔
320
        types = $(Expr(:tuple, tokens...))
12✔
321
        result = Vector{Any}(undef, num_parsed)
12✔
322
        for (i, typ) in enumerate(types)
12✔
323
            i > num_parsed && break
53✔
324
            result[i] = typ(values[i])  # Constructing types takes most of the time
53✔
325
        end
53✔
326
        return result
12✔
327
    end
328
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc