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

JuliaLang / julia / #37527

pending completion
#37527

push

local

web-flow
make `IRShow.method_name` inferrable (#49607)

18 of 18 new or added lines in 3 files covered. (100.0%)

68710 of 81829 relevant lines covered (83.97%)

33068903.12 hits per line

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

86.24
/base/compiler/ssair/show.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
# This file is not loaded into `Core.Compiler` but rather loaded into the context of
4
# `Base.IRShow` and thus does not participate in bootstrapping.
5

6
@nospecialize
7

8
if Pair != Base.Pair
9
import Base: Base, IOContext, string, join, sprint
10
IOContext(io::IO, KV::Pair) = IOContext(io, Base.Pair(KV[1], KV[2]))
×
11
length(s::String) = Base.length(s)
×
12
^(s::String, i::Int) = Base.:^(s, i)
×
13
end
14

15
import Base: show_unquoted
16
using Base: printstyled, with_output_color, prec_decl, @invoke
17

18
function Base.show(io::IO, cfg::CFG)
1✔
19
    print(io, "CFG with $(length(cfg.blocks)) blocks:")
1✔
20
    for (idx, block) in enumerate(cfg.blocks)
2✔
21
        print(io, "\n  bb ", idx)
3✔
22
        if block.stmts.start == block.stmts.stop
3✔
23
            print(io, " (stmt ", block.stmts.start, ")")
2✔
24
        else
25
            print(io, " (stmts ", block.stmts.start, ":", block.stmts.stop, ")")
1✔
26
        end
27
        if !isempty(block.succs)
3✔
28
            print(io, " → bb ")
1✔
29
            join(io, block.succs, ", ")
1✔
30
        end
31
    end
3✔
32
end
33

34
function print_stmt(io::IO, idx::Int, @nospecialize(stmt), used::BitSet, maxlength_idx::Int, color::Bool, show_type::Bool)
15,459✔
35
    if idx in used
15,467✔
36
        idx_s = string(idx)
6,442✔
37
        pad = " "^(maxlength_idx - length(idx_s) + 1)
6,442✔
38
        print(io, "%", idx_s, pad, "= ")
6,442✔
39
    else
40
        print(io, " "^(maxlength_idx + 4))
9,017✔
41
    end
42
    # TODO: `indent` is supposed to be the full width of the leader for correct alignment
43
    indent = 16
15,459✔
44
    if !color && stmt isa PiNode
15,459✔
45
        # when the outer context is already colored (green, for pending nodes), don't use the usual coloring printer
46
        print(io, "π (")
2✔
47
        show_unquoted(io, stmt.val, indent)
2✔
48
        print(io, ", ")
2✔
49
        print(io, stmt.typ)
2✔
50
        print(io, ")")
2✔
51
    elseif isexpr(stmt, :invoke) && length(stmt.args) >= 2 && isa(stmt.args[1], MethodInstance)
16,979✔
52
        stmt = stmt::Expr
36✔
53
        # TODO: why is this here, and not in Base.show_unquoted
54
        print(io, "invoke ")
36✔
55
        linfo = stmt.args[1]::Core.MethodInstance
36✔
56
        show_unquoted(io, stmt.args[2], indent)
36✔
57
        print(io, "(")
36✔
58
        # XXX: this is wrong if `sig` is not a concretetype method
59
        # more correct would be to use `fieldtype(sig, i)`, but that would obscure / discard Varargs information in show
60
        sig = linfo.specTypes == Tuple ? Core.svec() : Base.unwrap_unionall(linfo.specTypes).parameters::Core.SimpleVector
72✔
61
        print_arg(i) = sprint(; context=io) do io
80✔
62
            show_unquoted(io, stmt.args[i], indent)
44✔
63
            if (i - 1) <= length(sig)
44✔
64
                print(io, "::", sig[i - 1])
44✔
65
            end
66
        end
67
        join(io, (print_arg(i) for i = 3:length(stmt.args)), ", ")
36✔
68
        print(io, ")")
36✔
69
    # given control flow information, we prefer to print these with the basic block #, instead of the ssa %
70
    elseif isexpr(stmt, :enter) && length((stmt::Expr).args) == 1 && (stmt::Expr).args[1] isa Int
16,943✔
71
        print(io, "\$(Expr(:enter, #", (stmt::Expr).args[1]::Int, "))")
6✔
72
    elseif stmt isa GotoNode
15,415✔
73
        print(io, "goto #", stmt.label)
270✔
74
    elseif stmt isa PhiNode
15,145✔
75
        show_unquoted_phinode(io, stmt, indent, "#")
211✔
76
    elseif stmt isa GotoIfNot
14,934✔
77
        show_unquoted_gotoifnot(io, stmt, indent, "#")
495✔
78
    elseif stmt isa TypedSlot
14,439✔
79
        # call `show` with the type set to Any so it will not be shown, since
80
        # we will show the type ourselves.
81
        show_unquoted(io, SlotNumber(stmt.id), indent, show_type ? prec_decl : 0)
8✔
82
    # everything else in the IR, defer to the generic AST printer
83
    else
84
        show_unquoted(io, stmt, indent, show_type ? prec_decl : 0)
14,431✔
85
    end
86
    nothing
15,459✔
87
end
88

89
show_unquoted(io::IO, val::Argument, indent::Int, prec::Int) = show_unquoted(io, Core.SlotNumber(val.n), indent, prec)
577✔
90

91
show_unquoted(io::IO, stmt::PhiNode, indent::Int, ::Int) = show_unquoted_phinode(io, stmt, indent, "%")
1✔
92
function show_unquoted_phinode(io::IO, stmt::PhiNode, indent::Int, prefix::String)
212✔
93
    args = String[let
424✔
94
        e = stmt.edges[i]
748✔
95
        v = !isassigned(stmt.values, i) ? "#undef" :
1,496✔
96
            sprint(; context=io) do io′
97
                show_unquoted(io′, stmt.values[i], indent)
748✔
98
            end
99
        "$prefix$e => $v"
1,284✔
100
        end for i in 1:length(stmt.edges)
101
    ]
102
    print(io, "φ ", '(')
212✔
103
    join(io, args, ", ")
212✔
104
    print(io, ')')
212✔
105
end
106

107
function show_unquoted(io::IO, stmt::PhiCNode, indent::Int, ::Int)
1✔
108
    print(io, "φᶜ (")
1✔
109
    first = true
1✔
110
    for v in stmt.values
1✔
111
        first ? (first = false) : print(io, ", ")
3✔
112
        show_unquoted(io, v, indent)
2✔
113
    end
3✔
114
    print(io, ")")
1✔
115
end
116

117
function show_unquoted(io::IO, stmt::PiNode, indent::Int, ::Int)
×
118
    print(io, "π (")
×
119
    show_unquoted(io, stmt.val, indent)
×
120
    print(io, ", ")
×
121
    printstyled(io, stmt.typ, color=:cyan)
×
122
    print(io, ")")
×
123
end
124

125
function show_unquoted(io::IO, stmt::UpsilonNode, indent::Int, ::Int)
1✔
126
    print(io, "ϒ (")
1✔
127
    isdefined(stmt, :val) ?
1✔
128
        show_unquoted(io, stmt.val, indent) :
129
        print(io, "#undef")
130
    print(io, ")")
1✔
131
end
132

133
function show_unquoted(io::IO, stmt::ReturnNode, indent::Int, ::Int)
293✔
134
    if !isdefined(stmt, :val)
293✔
135
        print(io, "unreachable")
26✔
136
    else
137
        print(io, "return ")
267✔
138
        show_unquoted(io, stmt.val, indent)
267✔
139
    end
140
end
141

142
show_unquoted(io::IO, stmt::GotoIfNot, indent::Int, ::Int) = show_unquoted_gotoifnot(io, stmt, indent, "%")
9✔
143
function show_unquoted_gotoifnot(io::IO, stmt::GotoIfNot, indent::Int, prefix::String)
504✔
144
    print(io, "goto ", prefix, stmt.dest, " if not ")
504✔
145
    show_unquoted(io, stmt.cond, indent)
504✔
146
end
147

148
function compute_inlining_depth(linetable::Vector, iline::Int32)
14,897✔
149
    iline == 0 && return 1
14,897✔
150
    depth = -1
14,897✔
151
    while iline != 0
69,936✔
152
        depth += 1
55,039✔
153
        lineinfo = linetable[iline]::LineInfoNode
55,039✔
154
        iline = lineinfo.inlined_at
55,039✔
155
    end
55,039✔
156
    return depth
14,897✔
157
end
158

159
function should_print_ssa_type(@nospecialize node)
15,416✔
160
    if isa(node, Expr)
15,416✔
161
        return !(node.head in (:gc_preserve_begin, :gc_preserve_end, :meta, :enter, :leave))
13,903✔
162
    end
163
    return !isa(node, PiNode)   && !isa(node, GotoIfNot) &&
1,513✔
164
           !isa(node, GotoNode) && !isa(node, ReturnNode) &&
165
           !isa(node, QuoteNode)
166
end
167

168
function default_expr_type_printer(io::IO; @nospecialize(type), used::Bool, show_type::Bool=true, _...)
30,184✔
169
    show_type || return nothing
16,081✔
170
    printstyled(io, "::", type, color=(used ? :cyan : :light_black))
14,103✔
171
    return nothing
14,103✔
172
end
173

174
function normalize_method_name(m)
×
175
    if m isa Method
899,249✔
176
        return m.name
×
177
    elseif m isa MethodInstance
899,249✔
178
        return (m.def::Method).name
606,247✔
179
    elseif m isa Symbol
293,002✔
180
        return m
293,002✔
181
    else
182
        return Symbol("")
×
183
    end
184
end
185
@noinline method_name(m::LineInfoNode) = normalize_method_name(m.method)
899,249✔
186

187
# converts the linetable for line numbers
188
# into a list in the form:
189
#   1 outer-most-frame
190
#   2   inlined-frame
191
#   3     innermost-frame
192
function compute_loc_stack(linetable::Vector, line::Int32)
14,897✔
193
    stack = Int[]
14,897✔
194
    while line != 0
69,936✔
195
        entry = linetable[line]::LineInfoNode
55,039✔
196
        pushfirst!(stack, line)
55,039✔
197
        line = entry.inlined_at
55,039✔
198
    end
55,039✔
199
    return stack
14,897✔
200
end
201

202
"""
203
    Compute line number annotations for an IRCode
204

205
This functions compute three sets of annotations for each IR line. Take the following
206
example (taken from `@code_typed sin(1.0)`):
207

208
```
209
    **                                                    ***         **********
210
    35 6 ── %10  = :(Base.mul_float)(%%2, %%2)::Float64   │╻╷         sin_kernel
211
       │    %11  = :(Base.mul_float)(%10, %10)::Float64   ││╻          *
212
```
213

214
The three annotations are indicated with `*`. The first one is the line number of the
215
active function (printed once whenever the outer most line number changes). The second
216
is the inlining indicator. The number of lines indicate the level of nesting, with a
217
half-size line (╷) indicating the start of a scope and a full size line (│) indicating
218
a continuing scope. The last annotation is the most complicated one. It is a heuristic
219
way to print the name of the entered scope. What it attempts to do is print the outermost
220
scope that hasn't been printed before. Let's work a number of examples to see the impacts
221
and tradeoffs involved.
222

223
```
224
f() = leaf_function() # Deliberately not defined to end up in the IR verbatim
225
g() = f()
226
h() = g()
227
top_function() = h()
228
```
229

230
After inlining, we end up with:
231
```
232
1 1 ─ %1 = :(Main.leaf_function)()::Any   │╻╷╷ h
233
  └──      return %1                      │
234
```
235

236
We see that the only function printed is the outermost function. This certainly loses
237
some information, but the idea is that the outermost function would have the most
238
semantic meaning (in the context of the function we're looking at).
239

240
On the other hand, let's see what happens when we redefine f:
241
```
242
function f()
243
    leaf_function()
244
    leaf_function()
245
    leaf_function()
246
end
247
```
248

249
We get:
250
```
251
1 1 ─      :(Main.leaf_function)()::Any   │╻╷╷ h
252
  │        :(Main.leaf_function)()::Any   ││┃│  g
253
  │   %3 = :(Main.leaf_function)()::Any   │││┃   f
254
  └──      return %3                      │
255
```
256

257
Even though we were in the `f` scope since the first statement, it tooks us two statements
258
to catch up and print the intermediate scopes. Which scope is printed is indicated both
259
by the indentation of the method name and by an increased thickness of the appropriate
260
line for the scope.
261
"""
262
function compute_ir_line_annotations(code::IRCode)
65✔
263
    loc_annotations = String[]
65✔
264
    loc_methods = String[]
65✔
265
    loc_lineno = String[]
65✔
266
    cur_group = 1
65✔
267
    last_lineno = 0
65✔
268
    last_stack = Int[]
65✔
269
    last_printed_depth = 0
65✔
270
    linetable = code.linetable
65✔
271
    lines = code.stmts.line
65✔
272
    last_line = zero(eltype(lines))
65✔
273
    for idx in 1:length(lines)
130✔
274
        buf = IOBuffer()
14,897✔
275
        line = lines[idx]
14,897✔
276
        print(buf, "│")
14,897✔
277
        depth = compute_inlining_depth(linetable, line)
29,794✔
278
        iline = line
14,897✔
279
        lineno = 0
14,897✔
280
        loc_method = ""
14,897✔
281
        if line != 0
14,897✔
282
            stack = compute_loc_stack(linetable, line)
14,897✔
283
            lineno = linetable[stack[1]].line
14,897✔
284
            x = min(length(last_stack), length(stack))
14,897✔
285
            if length(stack) != 0
14,897✔
286
                # Compute the last depth that was in common
287
                first_mismatch = let last_stack=last_stack
14,897✔
288
                    findfirst(i->last_stack[i] != stack[i], 1:x)
114,767✔
289
                end
290
                # If the first mismatch is the last stack frame, that might just
291
                # be a line number mismatch in inner most frame. Ignore those
292
                if length(last_stack) == length(stack) && first_mismatch == length(stack)
19,922✔
293
                    last_entry, entry = linetable[last_stack[end]], linetable[stack[end]]
5,016✔
294
                    if method_name(last_entry) === method_name(entry) && last_entry.file === entry.file
5,016✔
295
                        first_mismatch = nothing
3,213✔
296
                    end
297
                end
298
                last_depth = something(first_mismatch, x+1)-1
19,632✔
299
                if min(depth, last_depth) > last_printed_depth
14,897✔
300
                    printing_depth = min(depth, last_printed_depth + 1)
3,844✔
301
                    last_printed_depth = printing_depth
3,844✔
302
                elseif length(stack) > length(last_stack) || first_mismatch !== nothing
21,240✔
303
                    printing_depth = min(depth, last_depth + 1)
5,470✔
304
                    last_printed_depth = printing_depth
5,470✔
305
                else
306
                    printing_depth = 0
5,583✔
307
                end
308
                stole_one = false
14,897✔
309
                if printing_depth != 0
14,897✔
310
                    for _ in 1:(printing_depth-1)
17,692✔
311
                        print(buf, "│")
20,109✔
312
                    end
31,713✔
313
                    if printing_depth <= last_depth-1 && first_mismatch === nothing
9,187✔
314
                        print(buf, "┃")
×
315
                        for _ in printing_depth+1:min(depth, last_depth)
×
316
                            print(buf, "│")
×
317
                        end
×
318
                    else
319
                        stole_one = true
9,187✔
320
                        print(buf, "╻")
18,374✔
321
                    end
322
                else
323
                    for _ in 1:min(depth, last_depth)
10,163✔
324
                        print(buf, "│")
10,456✔
325
                    end
10,456✔
326
                end
327
                print(buf, "╷"^max(0, depth - last_depth - stole_one))
14,897✔
328
                if printing_depth != 0
14,897✔
329
                    if length(stack) == printing_depth
9,187✔
330
                        loc_method = line
×
331
                    else
332
                        loc_method = stack[printing_depth + 1]
9,187✔
333
                    end
334
                    loc_method = method_name(linetable[loc_method])
18,374✔
335
                end
336
                loc_method = string(" "^printing_depth, loc_method)
24,084✔
337
            end
338
            last_stack = stack
14,897✔
339
            entry = linetable[line]
14,897✔
340
        end
341
        push!(loc_annotations, String(take!(buf)))
14,897✔
342
        push!(loc_lineno, (lineno != 0 && lineno != last_lineno) ? string(lineno) : "")
14,897✔
343
        push!(loc_methods, loc_method)
14,897✔
344
        last_line = line
14,897✔
345
        (lineno != 0) && (last_lineno = lineno)
14,897✔
346
        nothing
14,897✔
347
    end
29,729✔
348
    return (loc_annotations, loc_methods, loc_lineno)
65✔
349
end
350

351
Base.show(io::IO, code::Union{IRCode, IncrementalCompact}) = show_ir(io, code)
63✔
352

353
lineinfo_disabled(io::IO, linestart::String, idx::Int) = ""
215✔
354

355
function DILineInfoPrinter(linetable::Vector, showtypes::Bool=false)
22✔
356
    context = LineInfoNode[]
22✔
357
    context_depth = Ref(0)
11✔
358
    indent(s::String) = s^(max(context_depth[], 1) - 1)
285✔
359
    function emit_lineinfo_update(io::IO, linestart::String, lineidx::Int32)
235✔
360
        # internal configuration options:
361
        linecolor = :yellow
224✔
362
        collapse = showtypes ? false : true
224✔
363
        indent_all = true
224✔
364
        # convert lineidx to a vector
365
        if lineidx == typemin(Int32)
224✔
366
            # sentinel value: reset internal (and external) state
367
            pops = indent("└")
15✔
368
            if !isempty(pops)
15✔
369
                print(io, linestart)
1✔
370
                printstyled(io, pops; color=linecolor)
1✔
371
                println(io)
1✔
372
            end
373
            empty!(context)
15✔
374
            context_depth[] = 0
15✔
375
        elseif lineidx > 0 # just skip over lines with no debug info at all
209✔
376
            DI = LineInfoNode[]
63✔
377
            while lineidx != 0
135✔
378
                entry = linetable[lineidx]::LineInfoNode
72✔
379
                push!(DI, entry)
72✔
380
                lineidx = entry.inlined_at
72✔
381
            end
72✔
382
            # FOR DEBUGGING, or if you just like very excessive output:
383
            # this prints out the context in full for every statement
384
            #empty!(context)
385
            #context_depth[] = 0
386
            nframes = length(DI)
63✔
387
            nctx::Int = 0
63✔
388
            pop_skips = 0
63✔
389
            # compute the size of the matching prefix in the inlining information stack
390
            for i = 1:min(length(context), nframes)
119✔
391
                CtxLine = context[i]
61✔
392
                FrameLine = DI[nframes - i + 1]
61✔
393
                CtxLine === FrameLine || break
61✔
394
                nctx = i
40✔
395
            end
45✔
396
            update_line_only::Bool = false
63✔
397
            if collapse
63✔
398
                if nctx > 0
63✔
399
                    # check if we're adding more frames with the same method name,
400
                    # if so, drop all existing calls to it from the top of the context
401
                    # AND check if instead the context was previously printed that way
402
                    # but now has removed the recursive frames
403
                    let method = method_name(context[nctx]) # last matching frame
40✔
404
                        if (nctx < nframes && method_name(DI[nframes - nctx]) === method) ||
80✔
405
                           (nctx < length(context) && method_name(context[nctx + 1]) === method)
406
                            update_line_only = true
×
407
                            while nctx > 0 && method_name(context[nctx]) === method
×
408
                                nctx -= 1
×
409
                            end
×
410
                        end
411
                    end
412
                end
413
                # look at the first non-matching element to see if we are only changing the line number
414
                if !update_line_only && nctx < length(context) && nctx < nframes
63✔
415
                    let CtxLine = context[nctx + 1],
21✔
416
                        FrameLine = DI[nframes - nctx]
417
                        if method_name(CtxLine) === method_name(FrameLine)
21✔
418
                            update_line_only = true
21✔
419
                        end
420
                    end
421
                end
422
            elseif nctx < length(context) && nctx < nframes
×
423
                # look at the first non-matching element to see if we are only changing the line number
424
                let CtxLine = context[nctx + 1],
×
425
                    FrameLine = DI[nframes - nctx]
426
                    if CtxLine.file === FrameLine.file &&
×
427
                            method_name(CtxLine) === method_name(FrameLine)
428
                        update_line_only = true
×
429
                    end
430
                end
431
            end
432
            # examine what frames we're returning from
433
            if nctx < length(context)
63✔
434
                # compute the new inlining depth
435
                if collapse
24✔
436
                    npops = 1
24✔
437
                    let Prev = method_name(context[nctx + 1])
24✔
438
                        for i = (nctx + 2):length(context)
24✔
439
                            Next = method_name(context[i])
×
440
                            Prev === Next || (npops += 1)
×
441
                            Prev = Next
×
442
                        end
×
443
                    end
444
                else
445
                    npops = length(context) - nctx
×
446
                end
447
                resize!(context, nctx)
48✔
448
                update_line_only && (npops -= 1)
24✔
449
                if npops > 0
24✔
450
                    context_depth[] -= npops
3✔
451
                    print(io, linestart)
3✔
452
                    printstyled(io, indent("│"), "└"^npops; color=linecolor)
3✔
453
                    println(io)
3✔
454
                end
455
            end
456
            # now print the new frames
457
            while nctx < nframes
95✔
458
                frame::LineInfoNode = DI[nframes - nctx]
32✔
459
                nctx += 1
32✔
460
                started::Bool = false
32✔
461
                if !update_line_only && showtypes && !isa(frame.method, Symbol) && nctx != 1
32✔
462
                    print(io, linestart)
×
463
                    Base.with_output_color(linecolor, io) do io
×
464
                        print(io, indent("│"))
465
                        print(io, "┌ invoke ", frame.method)
466
                        println(io)
467
                    end
468
                    started = true
×
469
                end
470
                print(io, linestart)
32✔
471
                Base.with_output_color(linecolor, io) do io
32✔
472
                    print(io, indent("│"))
32✔
473
                    push!(context, frame)
32✔
474
                    if update_line_only
32✔
475
                        update_line_only = false
21✔
476
                    else
477
                        context_depth[] += 1
11✔
478
                        nctx != 1 && print(io, started ? "│" : "┌")
11✔
479
                    end
480
                    print(io, " @ ", frame.file)
32✔
481
                    if frame.line != typemax(frame.line) && frame.line != 0
32✔
482
                        print(io, ":", frame.line)
30✔
483
                    end
484
                    print(io, " within `", method_name(frame), "`")
32✔
485
                    if collapse
32✔
486
                        method = method_name(frame)
32✔
487
                        while nctx < nframes
32✔
488
                            frame = DI[nframes - nctx]
2✔
489
                            method_name(frame) === method || break
2✔
490
                            nctx += 1
×
491
                            push!(context, frame)
×
492
                            print(io, " @ ", frame.file, ":", frame.line)
×
493
                        end
32✔
494
                    end
495
                end
496
                println(io)
32✔
497
            end
32✔
498
            # FOR DEBUGGING `collapse`:
499
            # this double-checks the computation of context_depth
500
            #let Prev = method_name(context[1]),
501
            #    depth2 = 1
502
            #    for i = 2:nctx
503
            #        Next = method_name(context[i])
504
            #        (collapse && Prev === Next) || (depth2 += 1)
505
            #        Prev = Next
506
            #    end
507
            #    @assert context_depth[] == depth2
508
            #end
509
        end
510
        indent_all || return ""
224✔
511
        return sprint(io -> printstyled(io, indent("│"), color=linecolor), context=io)
448✔
512
    end
513
    return emit_lineinfo_update
11✔
514
end
515

516
"""
517
    IRShowConfig
518

519
- `line_info_preprinter(io::IO, indent::String, idx::Int)`` may print relevant info
520
  at the beginning of the line, and should at least print `indent`. It returns a
521
  string that will be printed after the final basic-block annotation.
522
- `line_info_postprinter(io::IO; type, used::Bool, show_type::Bool, idx::Int)` prints
523
  relevant information like type-annotation at the end of the statement
524
- `should_print_stmt(idx::Int) -> Bool`: whether the statement at index `idx` should be
525
  printed as part of the IR or not
526
- `bb_color`: color used for printing the basic block brackets on the left
527
"""
528
struct IRShowConfig
529
    line_info_preprinter
530
    line_info_postprinter
531
    should_print_stmt
532
    bb_color::Symbol
533
    function IRShowConfig(line_info_preprinter, line_info_postprinter=default_expr_type_printer;
280✔
534
                          should_print_stmt=Returns(true), bb_color::Symbol=:light_black)
535
        return new(line_info_preprinter, line_info_postprinter, should_print_stmt, bb_color)
102✔
536
    end
537
end
538

539
struct _UNDEF
540
    global const UNDEF = _UNDEF.instance
541
end
542

543
function _stmt(code::IRCode, idx::Int)
14,860✔
544
    stmts = code.stmts
14,860✔
545
    return isassigned(stmts.inst, idx) ? stmts[idx][:inst] : UNDEF
14,860✔
546
end
547
function _stmt(compact::IncrementalCompact, idx::Int)
177✔
548
    stmts = compact.result
177✔
549
    return isassigned(stmts.inst, idx) ? stmts[idx][:inst] : UNDEF
177✔
550
end
551
function _stmt(code::CodeInfo, idx::Int)
389✔
552
    code = code.code
389✔
553
    return isassigned(code, idx) ? code[idx] : UNDEF
389✔
554
end
555

556
function _type(code::IRCode, idx::Int)
14,860✔
557
    stmts = code.stmts
14,860✔
558
    return isassigned(stmts.type, idx) ? stmts[idx][:type] : UNDEF
14,995✔
559
end
560
function _type(compact::IncrementalCompact, idx::Int)
177✔
561
    stmts = compact.result
177✔
562
    return isassigned(stmts.type, idx) ? stmts[idx][:type] : UNDEF
177✔
563
end
564
function _type(code::CodeInfo, idx::Int)
389✔
565
    types = code.ssavaluetypes
389✔
566
    types isa Vector{Any} || return nothing
432✔
567
    return isassigned(types, idx) ? types[idx] : UNDEF
346✔
568
end
569

570
function statement_indices_to_labels(stmt, cfg::CFG)
389✔
571
    # convert statement index to labels, as expected by print_stmt
572
    if stmt isa Expr
389✔
573
        if stmt.head === :enter && length(stmt.args) == 1 && stmt.args[1] isa Int
258✔
574
            stmt = Expr(:enter, block_for_inst(cfg, stmt.args[1]::Int))
1✔
575
        end
576
    elseif isa(stmt, GotoIfNot)
131✔
577
        stmt = GotoIfNot(stmt.cond, block_for_inst(cfg, stmt.dest))
29✔
578
    elseif stmt isa GotoNode
102✔
579
        stmt = GotoNode(block_for_inst(cfg, stmt.label))
13✔
580
    elseif stmt isa PhiNode
89✔
581
        e = stmt.edges
4✔
582
        stmt = PhiNode(Int32[block_for_inst(cfg, Int(e[i])) for i in 1:length(e)], stmt.values)
4✔
583
    end
584
    return stmt
389✔
585
end
586

587
# Show a single statement, code.stmts[idx]/code.code[idx], in the context of the whole IRCode/CodeInfo.
588
# Returns the updated value of bb_idx.
589
# pop_new_node!(idx::Int; attach_after=false) -> (node_idx, new_node_inst, new_node_type)
590
#   may return a new node at the current index `idx`, which is printed before the statement
591
#   at index `idx`. This function is repeatedly called until it returns `nothing`.
592
#   to iterate nodes that are to be inserted after the statement, set `attach_after=true`.
593
function show_ir_stmt(io::IO, code::Union{IRCode, CodeInfo, IncrementalCompact}, idx::Int, config::IRShowConfig,
30,852✔
594
                      used::BitSet, cfg::CFG, bb_idx::Int; pop_new_node! = Returns(nothing), only_after::Bool=false)
595
    return show_ir_stmt(io, code, idx, config.line_info_preprinter, config.line_info_postprinter,
15,426✔
596
                        used, cfg, bb_idx; pop_new_node!, only_after, config.bb_color)
597
end
598

599
function show_ir_stmt(io::IO, code::Union{IRCode, CodeInfo, IncrementalCompact}, idx::Int, line_info_preprinter, line_info_postprinter,
30,852✔
600
                      used::BitSet, cfg::CFG, bb_idx::Int; pop_new_node! = Returns(nothing), only_after::Bool=false, bb_color=:light_black)
601
    stmt = _stmt(code, idx)
15,426✔
602
    type = _type(code, idx)
15,561✔
603
    max_bb_idx_size = length(string(length(cfg.blocks)))
15,426✔
604

605
    if isempty(used)
15,426✔
606
        maxlength_idx = 0
8✔
607
    else
608
        maxused = maximum(used)
30,836✔
609
        maxlength_idx = length(string(maxused))
15,418✔
610
    end
611

612
    if stmt === UNDEF
15,426✔
613
        # This is invalid, but do something useful rather
614
        # than erroring, to make debugging easier
615
        printstyled(io, "#UNDEF\n", color=:red)
×
616
        return bb_idx
×
617
    end
618

619
    i = 1
15,426✔
620
    function print_indentation(final::Bool=true)
30,885✔
621
        # Compute BB guard rail
622
        if bb_idx > length(cfg.blocks)
15,459✔
623
            # If invariants are violated, print a special leader
624
            linestart = " "^(max_bb_idx_size + 2) # not inside a basic block bracket
1✔
625
            inlining_indent = line_info_preprinter(io, linestart, i == 1 ? idx : 0)
1✔
626
            printstyled(io, "!!! ", "─"^max_bb_idx_size, color=bb_color)
1✔
627
        else
628
            bbrange = cfg.blocks[bb_idx].stmts
15,458✔
629
            # Print line info update
630
            linestart = idx == first(bbrange) ? "  " : sprint(io -> printstyled(io, "│ ", color=bb_color), context=io)
43,990✔
631
            linestart *= " "^max_bb_idx_size
15,458✔
632
            # idx == 0 means only indentation is printed, so we don't print linfos
633
            # multiple times if the are new nodes
634
            inlining_indent = line_info_preprinter(io, linestart, i == 1 ? idx : 0)
15,458✔
635

636
            if i == 1 && idx == first(bbrange)
15,458✔
637
                bb_idx_str = string(bb_idx)
1,162✔
638
                bb_pad = max_bb_idx_size - length(bb_idx_str)
1,162✔
639
                bb_type = length(cfg.blocks[bb_idx].preds) <= 1 ? "─" : "┄"
1,162✔
640
                printstyled(io, bb_idx_str, " ", bb_type, "─"^bb_pad, color=bb_color)
1,162✔
641
            elseif final && idx == last(bbrange) # print separator
14,296✔
642
                printstyled(io, "└", "─"^(1 + max_bb_idx_size), color=bb_color)
994✔
643
            else
644
                printstyled(io, "│ ", " "^max_bb_idx_size, color=bb_color)
13,302✔
645
            end
646
        end
647
        print(io, inlining_indent, " ")
15,459✔
648
    end
649

650
    # first, print new nodes that are to be inserted before the current statement
651
    function print_new_node(node; final::Bool=true)
15,548✔
652
        print_indentation(final)
61✔
653

654
        node_idx, new_node_inst, new_node_type = node
61✔
655
        @assert new_node_inst !== UNDEF # we filtered these out earlier
61✔
656
        show_type = should_print_ssa_type(new_node_inst)
79✔
657
        let maxlength_idx=maxlength_idx, show_type=show_type
61✔
658
            with_output_color(:green, io) do io′
61✔
659
                print_stmt(io′, node_idx, new_node_inst, used, maxlength_idx, false, show_type)
61✔
660
            end
661
        end
662

663
        if new_node_type === UNDEF # try to be robust against errors
61✔
664
            printstyled(io, "::#UNDEF", color=:red)
×
665
        else
666
            line_info_postprinter(io; type = new_node_type, used = node_idx in used, show_type, idx = node_idx)
61✔
667
        end
668
        println(io)
61✔
669
    end
670
    while (next = pop_new_node!(idx)) !== nothing
15,470✔
671
        only_after || print_new_node(next; final=false)
88✔
672
        i += 1
44✔
673
    end
44✔
674

675
    # peek at the nodes to be inserted after the current statement
676
    # (to determine of the statement itself is the final one)
677
    next = pop_new_node!(idx; attach_after=true)
15,426✔
678

679
    # then, print the current statement
680
    # FIXME: `only_after` is hack so that we can call this function to print uncompacted
681
    #        attach-after nodes when the current node has already been compated already
682
    if !only_after
15,426✔
683
        print_indentation(next===nothing)
15,398✔
684
        if code isa CodeInfo
15,398✔
685
            stmt = statement_indices_to_labels(stmt, cfg)
389✔
686
        end
687
        show_type = type !== nothing && should_print_ssa_type(stmt)
16,893✔
688
        print_stmt(io, idx, stmt, used, maxlength_idx, true, show_type)
15,398✔
689
        if type !== nothing # ignore types for pre-inference code
15,398✔
690
            if type === UNDEF
15,355✔
691
                # This is an error, but can happen if passes don't update their type information
692
                printstyled(io, "::#UNDEF", color=:red)
135✔
693
            else
694
                line_info_postprinter(io; type, used = idx in used, show_type, idx)
15,220✔
695
            end
696
        end
697
        println(io)
15,398✔
698
    end
699
    i += 1
15,426✔
700

701
    # finally, print new nodes that are to be inserted after the current statement
702
    while next !== nothing
15,443✔
703
        print_new_node(next)
17✔
704
        i += 1
17✔
705
        next = pop_new_node!(idx; attach_after=true)
17✔
706
    end
17✔
707

708
    # increment the basic block counter
709
    if bb_idx <= length(cfg.blocks)
15,426✔
710
        bbrange = cfg.blocks[bb_idx].stmts
15,419✔
711
        if bb_idx <= length(cfg.blocks) && idx == last(bbrange)
15,419✔
712
            bb_idx += 1
1,159✔
713
        end
714
    end
715

716
    return bb_idx
15,426✔
717
end
718

719
function _new_nodes_iter(stmts, new_nodes, new_nodes_info, new_nodes_idx)
270✔
720
    new_nodes_perm = filter(i -> isassigned(new_nodes.inst, i), 1:length(new_nodes))
872✔
721
    sort!(new_nodes_perm, by = x -> (x = new_nodes_info[x]; (x.pos, x.attach_after)))
1,610✔
722

723
    # separate iterators for the nodes that are inserted before resp. after each statement
724
    before_iter = Ref(1)
270✔
725
    after_iter = Ref(1)
270✔
726

727
    return function get_new_node(idx::Int; attach_after=false)
61,328✔
728
        iter = attach_after ? after_iter : before_iter
45,826✔
729
        iter[] <= length(new_nodes_perm) || return nothing
60,414✔
730
        node_idx = new_nodes_perm[iter[]]
644✔
731

732
        # skip nodes
733
        while node_idx < new_nodes_idx ||                           # already compacted
2,196✔
734
              idx > new_nodes_info[node_idx].pos ||                 # not interested in
735
              new_nodes_info[node_idx].attach_after != attach_after
736
            iter[] += 1
738✔
737
            iter[] > length(new_nodes_perm) && return nothing
738✔
738
            node_idx = new_nodes_perm[iter[]]
527✔
739
        end
527✔
740

741
        if new_nodes_info[node_idx].pos != idx ||
564✔
742
           new_nodes_info[node_idx].attach_after != attach_after
743
            return nothing
302✔
744
        end
745

746
        iter[] += 1
131✔
747
        new_node = new_nodes[node_idx]
131✔
748
        new_node_inst = isassigned(new_nodes.inst, node_idx) ? new_node[:inst] : UNDEF
131✔
749
        new_node_type = isassigned(new_nodes.type, node_idx) ? new_node[:type] : UNDEF
131✔
750
        node_idx += length(stmts)
131✔
751
        return node_idx, new_node_inst, new_node_type
131✔
752
    end
753
end
754

755
function new_nodes_iter(ir::IRCode, new_nodes_idx=1)
411✔
756
    stmts = ir.stmts
411✔
757
    new_nodes = ir.new_nodes.stmts
227✔
758
    new_nodes_info = ir.new_nodes.info
227✔
759
    return _new_nodes_iter(stmts, new_nodes, new_nodes_info, new_nodes_idx)
227✔
760
end
761

762
function new_nodes_iter(compact::IncrementalCompact)
43✔
763
    stmts = compact.result
43✔
764
    new_nodes = compact.new_new_nodes.stmts
43✔
765
    new_nodes_info = compact.new_new_nodes.info
43✔
766
    return _new_nodes_iter(stmts, new_nodes, new_nodes_info, 1)
43✔
767
end
768

769
# print only line numbers on the left, some of the method names and nesting depth on the right
770
function inline_linfo_printer(code::IRCode)
64✔
771
    loc_annotations, loc_methods, loc_lineno = compute_ir_line_annotations(code)
64✔
772
    max_loc_width = maximum(length, loc_annotations)
64✔
773
    max_lineno_width = maximum(length, loc_lineno)
64✔
774
    max_method_width = maximum(length, loc_methods)
64✔
775

776
    function (io::IO, indent::String, idx::Int)
15,165✔
777
        cols = (displaysize(io)::Tuple{Int,Int})[2]
15,101✔
778

779
        if idx == 0
15,101✔
780
            annotation = ""
121✔
781
            loc_method = ""
121✔
782
            lineno = ""
121✔
783
        elseif idx <= length(loc_annotations)
14,980✔
784
            # N.B.: The line array length not matching is invalid,
785
            # but let's be robust here
786
            annotation = loc_annotations[idx]
14,937✔
787
            loc_method = loc_methods[idx]
14,937✔
788
            lineno = loc_lineno[idx]
14,937✔
789
        else
790
            annotation = "!"
43✔
791
            loc_method = ""
43✔
792
            lineno = ""
43✔
793
        end
794
        # Print location information right aligned. If the line below is too long, it'll overwrite this,
795
        # but that's what we want.
796
        if get(io, :color, false)::Bool
15,101✔
797
            method_start_column = cols - max_method_width - max_loc_width - 2
×
798
            filler = " "^(max_loc_width-length(annotation))
×
799
            printstyled(io, "\e[$(method_start_column)G$(annotation)$(filler)$(loc_method)\e[1G", color = :light_black)
×
800
        end
801
        printstyled(io, lineno, " "^(max_lineno_width - length(lineno) + 1); color = :light_black)
15,101✔
802
        return ""
15,101✔
803
    end
804
end
805

806
_strip_color(s::String) = replace(s, r"\e\[\d+m"a => "")
×
807

808
function statementidx_lineinfo_printer(f, code::IRCode)
1✔
809
    printer = f(code.linetable)
1✔
810
    function (io::IO, indent::String, idx::Int)
35✔
811
        printer(io, indent, idx > 0 ? code.stmts[idx][:line] : typemin(Int32))
34✔
812
    end
813
end
814
function statementidx_lineinfo_printer(f, code::CodeInfo)
10✔
815
    printer = f(code.linetable)
10✔
816
    function (io::IO, indent::String, idx::Int)
200✔
817
        printer(io, indent, idx > 0 ? code.codelocs[idx] : typemin(Int32))
190✔
818
    end
819
end
820
statementidx_lineinfo_printer(code) = statementidx_lineinfo_printer(DILineInfoPrinter, code)
11✔
821

822
function stmts_used(io::IO, code::IRCode, warn_unset_entry=true)
130✔
823
    stmts = code.stmts
130✔
824
    used = BitSet()
130✔
825
    for stmt in stmts
65✔
826
        scan_ssa_use!(push!, used, stmt[:inst])
14,913✔
827
    end
14,978✔
828
    new_nodes = code.new_nodes.stmts
65✔
829
    for nn in 1:length(new_nodes)
105✔
830
        if isassigned(new_nodes.inst, nn)
134✔
831
            scan_ssa_use!(push!, used, new_nodes[nn][:inst])
134✔
832
        elseif warn_unset_entry
×
833
            printstyled(io, "ERROR: New node array has unset entry\n", color=:red)
×
834
            warn_unset_entry = false
×
835
        end
836
    end
228✔
837
    return used
65✔
838
end
839

840
function stmts_used(::IO, code::CodeInfo)
37✔
841
    stmts = code.code
37✔
842
    used = BitSet()
74✔
843
    for stmt in stmts
37✔
844
        scan_ssa_use!(push!, used, stmt)
389✔
845
    end
426✔
846
    return used
37✔
847
end
848

849
function default_config(code::IRCode; verbose_linetable=false)
130✔
850
    return IRShowConfig(verbose_linetable ? statementidx_lineinfo_printer(code)
66✔
851
                                          : inline_linfo_printer(code);
852
                        bb_color=:normal)
853
end
854
default_config(code::CodeInfo) = IRShowConfig(statementidx_lineinfo_printer(code))
×
855

856
function show_ir_stmts(io::IO, ir::Union{IRCode, CodeInfo, IncrementalCompact}, inds, config::IRShowConfig,
290✔
857
                       used::BitSet, cfg::CFG, bb_idx::Int; pop_new_node! = Returns(nothing))
858
    for idx in inds
145✔
859
        if config.should_print_stmt(ir, idx, used)
15,398✔
860
            bb_idx = show_ir_stmt(io, ir, idx, config, used, cfg, bb_idx; pop_new_node!)
15,964✔
861
        elseif bb_idx <= length(cfg.blocks) && idx == cfg.blocks[bb_idx].stmts.stop
×
862
            bb_idx += 1
×
863
        end
864
    end
15,398✔
865
    return bb_idx
145✔
866
end
867

868
function finish_show_ir(io::IO, cfg::CFG, config::IRShowConfig)
102✔
869
    max_bb_idx_size = length(string(length(cfg.blocks)))
102✔
870
    config.line_info_preprinter(io, " "^(max_bb_idx_size + 2), 0)
102✔
871
    return nothing
102✔
872
end
873

874
function show_ir(io::IO, ir::IRCode, config::IRShowConfig=default_config(ir);
65✔
875
                 pop_new_node! = new_nodes_iter(ir))
876
    used = stmts_used(io, ir)
22✔
877
    cfg = ir.cfg
22✔
878
    maxssaid = length(ir.stmts) + Core.Compiler.length(ir.new_nodes)
22✔
879
    let io = IOContext(io, :maxssaid=>maxssaid)
22✔
880
        show_ir_stmts(io, ir, 1:length(ir.stmts), config, used, cfg, 1; pop_new_node!)
22✔
881
    end
882
    finish_show_ir(io, cfg, config)
22✔
883
end
884

885
function show_ir(io::IO, ci::CodeInfo, config::IRShowConfig=default_config(ci);
74✔
886
                 pop_new_node! = Returns(nothing))
887
    used = stmts_used(io, ci)
37✔
888
    cfg = compute_basic_blocks(ci.code)
37✔
889
    let io = IOContext(io, :maxssaid=>length(ci.code))
37✔
890
        show_ir_stmts(io, ci, 1:length(ci.code), config, used, cfg, 1; pop_new_node!)
37✔
891
    end
892
    finish_show_ir(io, cfg, config)
37✔
893
end
894

895
function show_ir(io::IO, compact::IncrementalCompact, config::IRShowConfig=default_config(compact.ir))
86✔
896
    cfg = compact.ir.cfg
86✔
897

898

899
    # First print everything that has already been compacted
900

901
    # merge uses in uncompacted region into compacted uses
902
    used_compacted = BitSet(i for (i, x) in pairs(compact.used_ssas) if x != 0)
86✔
903
    used_uncompacted = stmts_used(io, compact.ir)
43✔
904
    for (i, ssa) = enumerate(compact.ssa_rename)
86✔
905
        if isa(ssa, SSAValue) && ssa.id in used_uncompacted
7,521✔
906
            push!(used_compacted, i)
3,107✔
907
        end
908
    end
14,999✔
909

910
    # while compacting, the end of the active result bb will not have been determined
911
    # (this is done post-hoc by `finish_current_bb!`), so determine it here from scratch.
912
    result_bbs = copy(compact.cfg_transform.result_bbs)
43✔
913
    if compact.active_result_bb <= length(result_bbs)
43✔
914
        # count the total number of nodes we'll add to this block
915
        input_bb_idx = block_for_inst(compact.ir.cfg, compact.idx)
37✔
916
        input_bb = compact.ir.cfg.blocks[input_bb_idx]
37✔
917
        count = 0
37✔
918
        for input_idx in input_bb.stmts.start:input_bb.stmts.stop
69✔
919
            pop_new_node! = new_nodes_iter(compact.ir)
162✔
920
            while pop_new_node!(input_idx) !== nothing
216✔
921
                count += 1
54✔
922
            end
54✔
923
            while pop_new_node!(input_idx; attach_after=true) !== nothing
178✔
924
                count += 1
16✔
925
            end
16✔
926
        end
292✔
927

928
        still_to_be_inserted = (last(input_bb.stmts) - compact.idx) + count
37✔
929

930
        result_bb = result_bbs[compact.active_result_bb]
37✔
931
        result_bbs[compact.active_result_bb] = Core.Compiler.BasicBlock(result_bb,
37✔
932
            Core.Compiler.StmtRange(first(result_bb.stmts), compact.result_idx+still_to_be_inserted))
933
    end
934
    compact_cfg = CFG(result_bbs, Int[first(result_bbs[i].stmts) for i in 2:length(result_bbs)])
486✔
935

936
    pop_new_node! = new_nodes_iter(compact)
43✔
937
    maxssaid = length(compact.result) + Core.Compiler.length(compact.new_new_nodes)
43✔
938
    bb_idx = let io = IOContext(io, :maxssaid=>maxssaid)
43✔
939
        show_ir_stmts(io, compact, 1:compact.result_idx-1, config, used_compacted,
56✔
940
                      compact_cfg, 1; pop_new_node!)
941
    end
942

943

944
    # Print uncompacted nodes from the original IR
945

946
    # print a separator
947
    (_, width) = displaysize(io)
43✔
948
    stmts = compact.ir.stmts
43✔
949
    indent = length(string(length(stmts)))
43✔
950
    # config.line_info_preprinter(io, "", compact.idx)
951
    printstyled(io, "─"^(width-indent-1), '\n', color=:red)
43✔
952

953
    # while compacting, the start of the active uncompacted bb will have been overwritten.
954
    # this manifests as a stmt range end that is less than the start, so correct that.
955
    inputs_bbs = copy(cfg.blocks)
43✔
956
    for (i, bb) in enumerate(inputs_bbs)
86✔
957
        if bb.stmts.stop < bb.stmts.start
520✔
958
            inputs_bbs[i] = Core.Compiler.BasicBlock(bb,
5✔
959
                Core.Compiler.StmtRange(last(bb.stmts), last(bb.stmts)))
960
            # this is not entirely correct, and will result in the bb starting again,
961
            # but is the best we can do without changing how `finish_current_bb!` works.
962
        end
963
    end
997✔
964
    uncompacted_cfg = CFG(inputs_bbs, Int[first(inputs_bbs[i].stmts) for i in 2:length(inputs_bbs)])
486✔
965

966
    pop_new_node! = new_nodes_iter(compact.ir, compact.new_nodes_idx)
43✔
967
    maxssaid = length(compact.ir.stmts) + Core.Compiler.length(compact.ir.new_nodes)
43✔
968
    let io = IOContext(io, :maxssaid=>maxssaid)
43✔
969
        # first show any new nodes to be attached after the last compacted statement
970
        if compact.idx > 1
43✔
971
            show_ir_stmt(io, compact.ir, compact.idx-1, config, used_uncompacted,
28✔
972
                        uncompacted_cfg, bb_idx; pop_new_node!, only_after=true)
973
        end
974

975
        # then show the actual uncompacted IR
976
        show_ir_stmts(io, compact.ir, compact.idx:length(stmts), config, used_uncompacted,
43✔
977
                      uncompacted_cfg, bb_idx; pop_new_node!)
978
    end
979

980
    finish_show_ir(io, uncompacted_cfg, config)
43✔
981
end
982

983
function effectbits_letter(effects::Effects, name::Symbol, suffix::Char)
×
984
    ft = fieldtype(Effects, name)
×
985
    if ft === UInt8
×
986
        prefix = getfield(effects, name) === ALWAYS_TRUE ? '+' :
×
987
                 getfield(effects, name) === ALWAYS_FALSE ? '!' : '?'
988
    elseif ft === Bool
×
989
        prefix = getfield(effects, name) ? '+' : '!'
×
990
    else
991
        error("unsupported effectbits type given")
×
992
    end
993
    return string(prefix, suffix)
×
994
end
995

996
function effectbits_color(effects::Effects, name::Symbol)
×
997
    ft = fieldtype(Effects, name)
×
998
    if ft === UInt8
×
999
        color = getfield(effects, name) === ALWAYS_TRUE ? :green :
×
1000
                getfield(effects, name) === ALWAYS_FALSE ? :red : :yellow
1001
    elseif ft === Bool
×
1002
        color = getfield(effects, name) ? :green : :red
×
1003
    else
1004
        error("unsupported effectbits type given")
×
1005
    end
1006
    return color
×
1007
end
1008

1009
function Base.show(io::IO, e::Effects)
×
1010
    print(io, "(")
×
1011
    printstyled(io, effectbits_letter(e, :consistent,  'c'); color=effectbits_color(e, :consistent))
×
1012
    print(io, ',')
×
1013
    printstyled(io, effectbits_letter(e, :effect_free, 'e'); color=effectbits_color(e, :effect_free))
×
1014
    print(io, ',')
×
1015
    printstyled(io, effectbits_letter(e, :nothrow,     'n'); color=effectbits_color(e, :nothrow))
×
1016
    print(io, ',')
×
1017
    printstyled(io, effectbits_letter(e, :terminates,  't'); color=effectbits_color(e, :terminates))
×
1018
    print(io, ',')
×
1019
    printstyled(io, effectbits_letter(e, :notaskstate, 's'); color=effectbits_color(e, :notaskstate))
×
1020
    print(io, ',')
×
1021
    printstyled(io, effectbits_letter(e, :inaccessiblememonly, 'm'); color=effectbits_color(e, :inaccessiblememonly))
×
1022
    print(io, ',')
×
1023
    printstyled(io, effectbits_letter(e, :noinbounds, 'i'); color=effectbits_color(e, :noinbounds))
×
1024
    print(io, ')')
×
1025
    e.nonoverlayed || printstyled(io, '′'; color=:red)
×
1026
end
1027

1028
@specialize
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

© 2025 Coveralls, Inc