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

JuliaLang / julia / #37800

07 Jun 2024 02:14AM UTC coverage: 87.474% (-0.009%) from 87.483%
#37800

push

local

web-flow
inference: follow up #54323, override ssaflags with new cycle effects (#54689)

JuliaLang/julia#54323 ensures that all frames within a cycle have the
same, cycle valid effects. However, `src.ssaflags` is calculated using
partial effects, so when the effects of a `frame` within the cycle are
updated, there would be an inconsistency between `frame.ipo_effects` and
`frame.src.ssaflags`. Due to this inconsistency, JuliaLang/julia#54323
breaks the test cases from JuliaLang/julia#51092, when backported to
v1.11. On the surface this is because JuliaLang/julia#52999 hasn't been
backported to v1.11, but the fundamental issue lies in this
inconsistency between cycle effects and `ssaflags`.

To resolve this issue, this commit traverses `cycle_backedges` to visit
statements involved in the cycle, and updates each `ssaflags` according
to new cycle valid effects if necessary.

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

250 existing lines in 12 files now uncovered.

77014 of 88042 relevant lines covered (87.47%)

14983808.11 hits per line

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

96.83
/stdlib/InteractiveUtils/src/codeview.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
# highlighting settings
4
const highlighting = Dict{Symbol, Bool}(
5
    :warntype => true,
6
    :llvm => true,
7
    :native => true,
8
)
9

10
const llstyle = Dict{Symbol, Tuple{Bool, Union{Symbol, Int}}}(
11
    :default     => (false, :normal), # e.g. comma, equal sign, unknown token
12
    :comment     => (false, :light_black),
13
    :label       => (false, :light_red),
14
    :instruction => ( true, :light_cyan),
15
    :type        => (false, :cyan),
16
    :number      => (false, :yellow),
17
    :bracket     => (false, :yellow),
18
    :variable    => (false, :normal), # e.g. variable, register
19
    :keyword     => (false, :light_magenta),
20
    :funcname    => (false, :light_yellow),
21
)
22

23
function printstyled_ll(io::IO, x, s::Symbol, trailing_spaces="")
1,049✔
24
    printstyled(io, x, bold=llstyle[s][1], color=llstyle[s][2])
2,624✔
25
    print(io, trailing_spaces)
1,049✔
26
end
27

28
# displaying type warnings
29

30
function warntype_type_printer(io::IO; @nospecialize(type), used::Bool, show_type::Bool=true, _...)
609✔
31
    (show_type && used) || return nothing
446✔
32
    str = "::$type"
262✔
33
    if !highlighting[:warntype]
262✔
34
        print(io, str)
6✔
35
    elseif type isa Union && is_expected_union(type)
256✔
36
        Base.emphasize(io, str, Base.warn_color()) # more mild user notification
14✔
37
    elseif type isa Type && (!Base.isdispatchelem(type) || type == Core.Box)
396✔
38
        Base.emphasize(io, str)
7✔
39
    else
40
        Base.printstyled(io, str, color=:cyan) # show the "good" type
235✔
41
    end
42
    return nothing
262✔
43
end
44

45
# True if one can be pretty certain that the compiler handles this union well,
46
# i.e. must be small with concrete types.
47
function is_expected_union(u::Union)
22✔
48
    Base.unionlen(u) < 4 || return false
22✔
49
    for x in Base.uniontypes(u)
22✔
50
        if !Base.isdispatchelem(x) || x == Core.Box
89✔
51
            return false
3✔
52
        end
53
    end
43✔
54
    return true
19✔
55
end
56

57
function print_warntype_codeinfo(io::IO, src::Core.CodeInfo, @nospecialize(rettype), nargs::Int; lineprinter)
54✔
58
    if src.slotnames !== nothing
27✔
59
        slotnames = Base.sourceinfo_slotnames(src)
27✔
60
        io = IOContext(io, :SOURCE_SLOTNAMES => slotnames)
27✔
61
        slottypes = src.slottypes
27✔
62
        nargs > 0 && println(io, "Arguments")
27✔
63
        for i = 1:length(slotnames)
27✔
64
            if i == nargs + 1
77✔
65
                println(io, "Locals")
6✔
66
            end
67
            print(io, "  ", slotnames[i])
77✔
68
            if isa(slottypes, Vector{Any})
77✔
69
                warntype_type_printer(io; type=slottypes[i], used=true)
75✔
70
            end
71
            println(io)
151✔
72
        end
127✔
73
    end
74
    print(io, "Body")
27✔
75
    warntype_type_printer(io; type=rettype, used=true)
27✔
76
    println(io)
52✔
77
    irshow_config = Base.IRShow.IRShowConfig(lineprinter(src), warntype_type_printer)
27✔
78
    Base.IRShow.show_ir(io, src, irshow_config)
27✔
79
    println(io)
27✔
80
end
81

82
function print_warntype_mi(io::IO, mi::Core.MethodInstance)
26✔
83
    println(io, mi)
26✔
84
    print(io, "  from ")
26✔
85
    println(io, mi.def)
26✔
86
    if !isempty(mi.sparam_vals)
26✔
87
        println(io, "Static Parameters")
4✔
88
        sig = mi.def.sig
4✔
89
        warn_color = Base.warn_color() # more mild user notification
4✔
90
        for i = 1:length(mi.sparam_vals)
4✔
91
            sig = sig::UnionAll
8✔
92
            name = sig.var.name
8✔
93
            val = mi.sparam_vals[i]
8✔
94
            print_highlighted(io::IO, v::String, color::Symbol) =
17✔
95
                if highlighting[:warntype]
96
                    Base.printstyled(io, v; color)
9✔
97
                else
98
                    Base.print(io, v)
×
99
                end
100
            if val isa TypeVar
8✔
101
                if val.lb === Union{}
3✔
102
                    print(io, "  ", name, " <: ")
1✔
103
                    print_highlighted(io, "$(val.ub)", warn_color)
1✔
104
                elseif val.ub === Any
2✔
105
                    print(io, "  ", sig.var.name, " >: ")
1✔
106
                    print_highlighted(io, "$(val.lb)", warn_color)
1✔
107
                else
108
                    print(io, "  ")
1✔
109
                    print_highlighted(io, "$(val.lb)", warn_color)
1✔
110
                    print(io, " <: ", sig.var.name, " <: ")
1✔
111
                    print_highlighted(io, "$(val.ub)", warn_color)
1✔
112
                end
113
            elseif val isa typeof(Vararg)
5✔
114
                print(io, "  ", name, "::")
1✔
115
                print_highlighted(io, "Int", warn_color)
1✔
116
            else
117
                print(io, "  ", sig.var.name, " = ")
4✔
118
                print_highlighted(io, "$(val)", :cyan) # show the "good" type
4✔
119
            end
120
            println(io)
8✔
121
            sig = sig.body
8✔
122
        end
8✔
123
    end
124
end
125

126
"""
127
    code_warntype([io::IO], f, types; debuginfo=:default)
128

129
Prints lowered and type-inferred ASTs for the methods matching the given generic function
130
and type signature to `io` which defaults to `stdout`. The ASTs are annotated in such a way
131
as to cause "non-leaf" types which may be problematic for performance to be emphasized
132
(if color is available, displayed in red). This serves as a warning of potential type instability.
133

134
Not all non-leaf types are particularly problematic for performance, and the performance
135
characteristics of a particular type is an implementation detail of the compiler.
136
`code_warntype` will err on the side of coloring types red if they might be a performance
137
concern, so some types may be colored red even if they do not impact performance.
138
Small unions of concrete types are usually not a concern, so these are highlighted in yellow.
139

140
Keyword argument `debuginfo` may be one of `:source` or `:none` (default), to specify the verbosity of code comments.
141

142
See the [`@code_warntype`](@ref man-code-warntype) section in the Performance Tips page of the manual for more information.
143

144
See also: [`@code_warntype`](@ref), [`code_typed`](@ref), [`code_lowered`](@ref), [`code_llvm`](@ref), [`code_native`](@ref).
145
"""
146
function code_warntype(io::IO, @nospecialize(f), @nospecialize(tt=Base.default_tt(f));
56✔
147
                       world=Base.get_world_counter(),
148
                       interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world),
149
                       debuginfo::Symbol=:default, optimize::Bool=false, kwargs...)
150
    (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) &&
27✔
151
        error("code reflection cannot be used from generated functions")
152
    debuginfo = Base.IRShow.debuginfo(debuginfo)
27✔
153
    lineprinter = Base.IRShow.__debuginfo[debuginfo]
27✔
154
    nargs::Int = 0
27✔
155
    if isa(f, Core.OpaqueClosure)
27✔
156
        isa(f.source, Method) && (nargs = f.source.nargs)
2✔
157
        print_warntype_codeinfo(io, Base.code_typed_opaque_closure(f, tt)[1]..., nargs; lineprinter)
1✔
158
        return nothing
1✔
159
    end
160
    tt = Base.signature_type(f, tt)
26✔
161
    matches = Core.Compiler.findall(tt, Core.Compiler.method_table(interp))
26✔
162
    matches === nothing && Base.raise_match_failure(:code_warntype, tt)
26✔
163
    for match in matches.matches
26✔
164
        match = match::Core.MethodMatch
26✔
165
        src = Core.Compiler.typeinf_code(interp, match, optimize)
26✔
166
        mi = Core.Compiler.specialize_method(match)
26✔
167
        mi.def isa Method && (nargs = (mi.def::Method).nargs)
26✔
168
        print_warntype_mi(io, mi)
26✔
169
        if src isa Core.CodeInfo
26✔
170
            print_warntype_codeinfo(io, src, src.rettype, nargs; lineprinter)
26✔
171
        else
UNCOV
172
            println(io, "  inference not successful")
×
173
        end
174
    end
26✔
175
    nothing
26✔
176
end
UNCOV
177
code_warntype(args...; kwargs...) = (@nospecialize; code_warntype(stdout, args...; kwargs...))
×
178

179
using Base: CodegenParams
180

181
const GENERIC_SIG_WARNING = "; WARNING: This code may not match what actually runs.\n"
182
const OC_MISMATCH_WARNING =
183
"""
184
; WARNING: The pre-inferred opaque closure is not callable with the given arguments
185
;          and will error on dispatch with this signature.
186
"""
187

188
# Printing code representations in IR and assembly
189

190
function _dump_function(@nospecialize(f), @nospecialize(t), native::Bool, wrapper::Bool,
148✔
191
                        raw::Bool, dump_module::Bool, syntax::Symbol,
192
                        optimize::Bool, debuginfo::Symbol, binary::Bool,
193
                        params::CodegenParams=CodegenParams(debug_info_kind=Cint(0), debug_info_level=Cint(2), safepoint_on_entry=raw, gcstack_arg=raw))
194
    ccall(:jl_is_in_pure_context, Bool, ()) && error("code reflection cannot be used from generated functions")
148✔
195
    if isa(f, Core.Builtin)
143✔
196
        throw(ArgumentError("argument is not a generic function"))
2✔
197
    end
198
    warning = ""
141✔
199
    # get the MethodInstance for the method match
200
    if !isa(f, Core.OpaqueClosure)
141✔
201
        world = Base.get_world_counter()
137✔
202
        match = Base._which(signature_type(f, t); world)
137✔
203
        mi = Core.Compiler.specialize_method(match)
134✔
204
        # TODO: use jl_is_cacheable_sig instead of isdispatchtuple
205
        isdispatchtuple(mi.specTypes) || (warning = GENERIC_SIG_WARNING)
134✔
206
    else
207
        world = UInt64(f.world)
4✔
208
        tt = Base.to_tuple_type(t)
4✔
209
        if !isdefined(f.source, :source)
4✔
210
            # OC was constructed from inferred source. There's only one
211
            # specialization and we can't infer anything more precise either.
212
            world = f.source.primary_world
4✔
213
            mi = f.source.specializations::Core.MethodInstance
4✔
214
            Core.Compiler.hasintersect(typeof(f).parameters[1], tt) || (warning = OC_MISMATCH_WARNING)
4✔
215
        else
UNCOV
216
            mi = Core.Compiler.specialize_method(f.source, Tuple{typeof(f.captures), tt.parameters...}, Core.svec())
×
UNCOV
217
            isdispatchtuple(mi.specTypes) || (warning = GENERIC_SIG_WARNING)
×
218
        end
219
    end
220
    # get the code for it
221
    if debuginfo === :default
138✔
222
        debuginfo = :source
93✔
223
    elseif debuginfo !== :source && debuginfo !== :none
45✔
UNCOV
224
        throw(ArgumentError("'debuginfo' must be either :source or :none"))
×
225
    end
226
    if native
138✔
227
        if syntax !== :att && syntax !== :intel
30✔
UNCOV
228
            throw(ArgumentError("'syntax' must be either :intel or :att"))
×
229
        end
230
        str = ""
30✔
231
        if !dump_module
30✔
232
            # if we don't want the module metadata, attempt to disassemble what our JIT has
233
            str = _dump_function_native_disassembly(mi, world, wrapper, syntax, debuginfo, binary)
4✔
234
        end
235
        if isempty(str)
30✔
236
            # if that failed (or we want metadata), use LLVM to generate more accurate assembly output
237
            if !isa(f, Core.OpaqueClosure)
26✔
238
                src = Core.Compiler.typeinf_code(Core.Compiler.NativeInterpreter(world), mi, true)
52✔
239
            else
UNCOV
240
                src, rt = Base.get_oc_code_rt(f, tt, true)
×
241
            end
242
            src isa Core.CodeInfo || error("failed to infer source for $mi")
26✔
243
            str = _dump_function_native_assembly(mi, src, wrapper, syntax, debuginfo, binary, raw, params)
26✔
244
        end
245
    else
246
        if !isa(f, Core.OpaqueClosure)
108✔
247
            src = Core.Compiler.typeinf_code(Core.Compiler.NativeInterpreter(world), mi, true)
208✔
248
        else
249
            src, rt = Base.get_oc_code_rt(f, tt, true)
4✔
250
        end
251
        src isa Core.CodeInfo || error("failed to infer source for $mi")
108✔
252
        str = _dump_function_llvm(mi, src, wrapper, !raw, dump_module, optimize, debuginfo, params)
108✔
253
    end
254
    str = warning * str
137✔
255
    return str
137✔
256
end
257

258
function _dump_function_native_disassembly(mi::Core.MethodInstance, world::UInt,
259
                                           wrapper::Bool, syntax::Symbol,
260
                                           debuginfo::Symbol, binary::Bool)
261
    str = @ccall jl_dump_method_asm(mi::Any, world::UInt, false::Bool, wrapper::Bool,
4✔
262
                                    syntax::Ptr{UInt8}, debuginfo::Ptr{UInt8},
263
                                    binary::Bool)::Ref{String}
264
    return str
4✔
265
end
266

267
struct LLVMFDump
268
    tsm::Ptr{Cvoid} # opaque
269
    f::Ptr{Cvoid} # opaque
270
end
271

272
function _dump_function_native_assembly(mi::Core.MethodInstance, src::Core.CodeInfo,
26✔
273
                                        wrapper::Bool, syntax::Symbol, debuginfo::Symbol,
274
                                        binary::Bool, raw::Bool, params::CodegenParams)
275
    llvmf_dump = Ref{LLVMFDump}()
26✔
276
    @ccall jl_get_llvmf_defn(llvmf_dump::Ptr{LLVMFDump}, mi::Any, src::Any, wrapper::Bool,
26✔
277
                             true::Bool, params::CodegenParams)::Cvoid
278
    llvmf_dump[].f == C_NULL && error("could not compile the specified method")
26✔
279
    str = @ccall jl_dump_function_asm(llvmf_dump::Ptr{LLVMFDump}, false::Bool,
25✔
280
                                      syntax::Ptr{UInt8}, debuginfo::Ptr{UInt8},
281
                                      binary::Bool, raw::Bool)::Ref{String}
282
    return str
25✔
283
end
284

285
function _dump_function_llvm(
108✔
286
        mi::Core.MethodInstance, src::Core.CodeInfo, wrapper::Bool,
287
        strip_ir_metadata::Bool, dump_module::Bool,
288
        optimize::Bool, debuginfo::Symbol,
289
        params::CodegenParams)
290
    llvmf_dump = Ref{LLVMFDump}()
108✔
291
    @ccall jl_get_llvmf_defn(llvmf_dump::Ptr{LLVMFDump}, mi::Any, src::Any,
108✔
292
                             wrapper::Bool, optimize::Bool, params::CodegenParams)::Cvoid
293
    llvmf_dump[].f == C_NULL && error("could not compile the specified method")
108✔
294
    str = @ccall jl_dump_function_ir(llvmf_dump::Ptr{LLVMFDump}, strip_ir_metadata::Bool,
108✔
295
                                     dump_module::Bool, debuginfo::Ptr{UInt8})::Ref{String}
296
    return str
108✔
297
end
298

299
"""
300
    code_llvm([io=stdout,], f, types; raw=false, dump_module=false, optimize=true, debuginfo=:default)
301

302
Prints the LLVM bitcodes generated for running the method matching the given generic
303
function and type signature to `io`.
304

305
If the `optimize` keyword is unset, the code will be shown before LLVM optimizations.
306
All metadata and dbg.* calls are removed from the printed bitcode. For the full IR, set the `raw` keyword to true.
307
To dump the entire module that encapsulates the function (with declarations), set the `dump_module` keyword to true.
308
Keyword argument `debuginfo` may be one of source (default) or none, to specify the verbosity of code comments.
309

310
See also: [`@code_llvm`](@ref), [`code_warntype`](@ref), [`code_typed`](@ref), [`code_lowered`](@ref), [`code_native`](@ref).
311
"""
312
function code_llvm(io::IO, @nospecialize(f), @nospecialize(types=Base.default_tt(f));
74✔
313
                   raw::Bool=false, dump_module::Bool=false, optimize::Bool=true, debuginfo::Symbol=:default,
314
                   params::CodegenParams=CodegenParams(debug_info_kind=Cint(0), debug_info_level=Cint(2), safepoint_on_entry=raw, gcstack_arg=raw))
315
    d = _dump_function(f, types, false, false, raw, dump_module, :intel, optimize, debuginfo, false, params)
70✔
316
    if highlighting[:llvm] && get(io, :color, false)::Bool
66✔
317
        print_llvm(io, d)
1✔
318
    else
319
        print(io, d)
65✔
320
    end
321
end
322
code_llvm(args...; kwargs...) = (@nospecialize; code_llvm(stdout, args...; kwargs...))
12✔
323

324
"""
325
    code_native([io=stdout,], f, types; syntax=:intel, debuginfo=:default, binary=false, dump_module=true)
326

327
Prints the native assembly instructions generated for running the method matching the given
328
generic function and type signature to `io`.
329

330
* Set assembly syntax by setting `syntax` to `:intel` (default) for intel syntax or `:att` for AT&T syntax.
331
* Specify verbosity of code comments by setting `debuginfo` to `:source` (default) or `:none`.
332
* If `binary` is `true`, also print the binary machine code for each instruction precedented by an abbreviated address.
333
* If `dump_module` is `false`, do not print metadata such as rodata or directives.
334
* If `raw` is `false`, uninteresting instructions (like the safepoint function prologue) are elided.
335

336
See also: [`@code_native`](@ref), [`code_warntype`](@ref), [`code_typed`](@ref), [`code_lowered`](@ref), [`code_llvm`](@ref).
337
"""
338
function code_native(io::IO, @nospecialize(f), @nospecialize(types=Base.default_tt(f));
39✔
339
                     dump_module::Bool=true, syntax::Symbol=:intel, raw::Bool=false,
340
                     debuginfo::Symbol=:default, binary::Bool=false,
341
                     params::CodegenParams=CodegenParams(debug_info_kind=Cint(0), debug_info_level=Cint(2), safepoint_on_entry=raw, gcstack_arg=raw))
342
    d = _dump_function(f, types, true, false, raw, dump_module, syntax, true, debuginfo, binary, params)
35✔
343
    if highlighting[:native] && get(io, :color, false)::Bool
29✔
344
        print_native(io, d)
1✔
345
    else
346
        print(io, d)
28✔
347
    end
348
end
349
code_native(args...; kwargs...) = (@nospecialize; code_native(stdout, args...; kwargs...))
14✔
350

351
## colorized IR and assembly printing
352

353
const num_regex = r"^(?:\$?-?\d+|0x[0-9A-Fa-f]+|-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)$"
354

355
function print_llvm(io::IO, code::String)
36✔
356
    buf = IOBuffer(code)
72✔
357
    for line in eachline(buf)
72✔
358
        m = match(r"^(\s*)((?:[^;]|;\")*)(.*)$", line)
45✔
359
        m === nothing && continue
45✔
360
        indent, tokens, comment = m.captures
45✔
361
        print(io, indent)
90✔
362
        print_llvm_tokens(io, tokens)
90✔
363
        printstyled_ll(io, comment, :comment)
90✔
364
        println(io)
45✔
365
    end
45✔
366
end
367

368
const llvm_types =
369
    r"^(?:void|half|float|double|x86_\w+|ppc_\w+|label|metadata|type|opaque|token|i\d+)$"
370
const llvm_cond = r"^(?:[ou]?eq|[ou]?ne|[uso][gl][te]|ord|uno)$" # true|false
371

372
function print_llvm_tokens(io, tokens)
45✔
373
    m = match(r"^((?:[^\"\s:]+:|\"[^\"]*\":)?)(\s*)(.*)", tokens)
45✔
374
    if m !== nothing
45✔
375
        label, spaces, tokens = m.captures
45✔
376
        printstyled_ll(io, label, :label, spaces)
90✔
377
    end
378
    m = match(r"^(%[^\s=]+)(\s*)=(\s*)(.*)", tokens)
45✔
379
    if m !== nothing
45✔
380
        result, spaces, spaces2, tokens = m.captures
22✔
381
        printstyled_ll(io, result, :variable, spaces)
44✔
382
        printstyled_ll(io, '=', :default, spaces2)
44✔
383
    end
384
    m = match(r"^([a-z]\w*)(\s*)(.*)", tokens)
45✔
385
    if m !== nothing
45✔
386
        inst, spaces, tokens = m.captures
35✔
387
        iskeyword = occursin(r"^(?:define|declare|type)$", inst) || occursin("=", tokens)
66✔
388
        printstyled_ll(io, inst, iskeyword ? :keyword : :instruction, spaces)
70✔
389
    end
390

391
    print_llvm_operands(io, tokens)
45✔
392
end
393

UNCOV
394
function print_llvm_operands(io, tokens)
×
395
    while !isempty(tokens)
246✔
396
        tokens = print_llvm_operand(io, tokens)
108✔
397
    end
108✔
398
    return tokens
69✔
399
end
400

401
function print_llvm_operand(io, tokens)
108✔
402
    islabel = false
108✔
403
    while !isempty(tokens)
642✔
404
        m = match(r"^,(\s*)(.*)", tokens)
269✔
405
        if m !== nothing
269✔
406
            spaces, tokens = m.captures
32✔
407
            printstyled_ll(io, ',', :default, spaces)
64✔
408
            break
32✔
409
        end
410
        m = match(r"^(\*+|=)(\s*)(.*)", tokens)
237✔
411
        if m !== nothing
237✔
412
            sym, spaces, tokens = m.captures
16✔
413
            printstyled_ll(io, sym, :default, spaces)
32✔
414
            continue
16✔
415
        end
416
        m = match(r"^(\"[^\"]*\")(\s*)(.*)", tokens)
221✔
417
        if m !== nothing
221✔
418
            str, spaces, tokens = m.captures
3✔
419
            printstyled_ll(io, str, :variable, spaces)
6✔
420
            continue
3✔
421
        end
422
        m = match(r"^([({\[<])(\s*)(.*)", tokens)
218✔
423
        if m !== nothing
218✔
424
            bracket, spaces, tokens = m.captures
24✔
425
            printstyled_ll(io, bracket, :bracket, spaces)
48✔
426
            tokens = print_llvm_operands(io, tokens) # enter
48✔
427
            continue
24✔
428
        end
429
        m = match(r"^([)}\]>])(\s*)(.*)", tokens)
194✔
430
        if m !== nothing
194✔
431
            bracket, spaces, tokens = m.captures
24✔
432
            printstyled_ll(io, bracket, :bracket, spaces)
48✔
433
            break # leave
24✔
434
        end
435

436
        m = match(r"^([^\s,*=(){}\[\]<>]+)(\s*)(.*)", tokens)
170✔
437
        m === nothing && break
170✔
438
        token, spaces, tokens = m.captures
170✔
439
        if occursin(llvm_types, token)
170✔
440
            printstyled_ll(io, token, :type)
52✔
441
            islabel = token == "label"
52✔
442
        elseif occursin(llvm_cond, token) # condition code is instruction-level
118✔
443
            printstyled_ll(io, token, :instruction)
1✔
444
        elseif occursin(num_regex, token)
117✔
445
            printstyled_ll(io, token, :number)
31✔
446
        elseif occursin(r"^@.+$", token)
86✔
447
            printstyled_ll(io, token, :funcname)
5✔
448
        elseif occursin(r"^%.+$", token)
81✔
449
            islabel |= occursin(r"^%[^\d].*$", token) & occursin(r"^\]", tokens)
38✔
450
            printstyled_ll(io, token, islabel ? :label : :variable)
38✔
451
            islabel = false
38✔
452
        elseif occursin(r"^[a-z]\w+$", token)
43✔
453
            printstyled_ll(io, token, :keyword)
38✔
454
        else
455
            printstyled_ll(io, token, :default)
5✔
456
        end
457
        print(io, spaces)
170✔
458
    end
213✔
459
    return tokens
108✔
460
end
461

462
function print_native(io::IO, code::String, arch::Symbol=sys_arch_category())
58✔
463
    archv = Val(arch)
59✔
464
    buf = IOBuffer(code)
116✔
465
    for line in eachline(buf)
116✔
466
        m = match(r"^(\s*)((?:[^;#/]|#\S|;\"|/[^/])*)(.*)$", line)
94✔
467
        m === nothing && continue
94✔
468
        indent, tokens, comment = m.captures
94✔
469
        print(io, indent)
188✔
470
        print_native_tokens(io, tokens, archv)
94✔
471
        printstyled_ll(io, comment, :comment)
188✔
472
        println(io)
94✔
473
    end
94✔
474
end
475

476
function sys_arch_category()
477
    if Sys.ARCH === :x86_64 || Sys.ARCH === :i686
1✔
478
        :x86
UNCOV
479
    elseif Sys.ARCH === :aarch64 || startswith(string(Sys.ARCH), "arm")
×
480
        :arm
481
    else
482
        :unsupported
483
    end
484
end
485

486
print_native_tokens(io, line, ::Val) = print(io, line)
1✔
487

488
const x86_ptr = r"^(?:(?:[xyz]mm|[dq])?word|byte|ptr|offset)$"
489
const avx512flags = r"^(?:z|r[nduz]-sae|sae|1to1?\d)$"
490
const arm_cond = r"^(?:eq|ne|cs|ho|cc|lo|mi|pl|vs|vc|hi|ls|[lg][te]|al|nv)$"
491
const arm_keywords = r"^(?:lsl|lsr|asr|ror|rrx|!|/[zm])$"
492

493
function print_native_tokens(io, tokens, arch::Union{Val{:x86}, Val{:arm}})
93✔
494
    x86 = arch isa Val{:x86}
93✔
495
    m = match(r"^((?:[^\s:]+:|\"[^\"]+\":)?)(\s*)(.*)", tokens)
93✔
496
    if m !== nothing
93✔
497
        label, spaces, tokens = m.captures
93✔
498
        printstyled_ll(io, label, :label, spaces)
186✔
499
    end
500
    haslabel = false
93✔
501
    m = match(r"^([a-z][\w.]*)(\s*)(.*)", tokens)
93✔
502
    if m !== nothing
93✔
503
        instruction, spaces, tokens = m.captures
57✔
504
        printstyled_ll(io, instruction, :instruction, spaces)
114✔
505
        haslabel = occursin(r"^(?:bl?|bl?\.\w{2,5}|[ct]bn?z)?$", instruction)
57✔
506
    end
507

508
    isfuncname = false
93✔
509
    while !isempty(tokens)
900✔
510
        m = match(r"^([,:*])(\s*)(.*)", tokens)
357✔
511
        if m !== nothing
357✔
512
            sym, spaces, tokens = m.captures
88✔
513
            printstyled_ll(io, sym, :default, spaces)
176✔
514
            isfuncname = false
88✔
515
            continue
88✔
516
        end
517
        m = match(r"^([(){}\[\]])(\s*)(.*)", tokens)
269✔
518
        if m !== nothing
269✔
519
            bracket, spaces, tokens = m.captures
56✔
520
            printstyled_ll(io, bracket, :bracket, spaces)
112✔
521
            continue
56✔
522
        end
523
        m = match(r"^#([0-9a-fx.-]+)(\s*)(.*)", tokens)
213✔
524
        if !x86 && m !== nothing && occursin(num_regex, m.captures[1])
213✔
525
            num, spaces, tokens = m.captures
6✔
526
            printstyled_ll(io, "#" * num, :number, spaces)
12✔
527
            continue
6✔
528
        end
529

530
        m = match(r"^([^\s,:*(){}\[\]][^\s,:*/(){}\[\]]*)(\s*)(.*)", tokens)
207✔
531
        m === nothing && break
207✔
532
        token, spaces, tokens = m.captures
207✔
533
        if occursin(num_regex, token)
207✔
534
            printstyled_ll(io, token, :number)
22✔
535
        elseif x86 && occursin(x86_ptr, token) || occursin(avx512flags, token)
310✔
536
            printstyled_ll(io, token, :keyword)
22✔
537
            isfuncname = token == "offset"
22✔
538
        elseif !x86 && (occursin(arm_keywords, token) || occursin(arm_cond, token))
202✔
539
            printstyled_ll(io, token, :keyword)
7✔
540
        elseif occursin(r"^L.+$", token)
156✔
541
            printstyled_ll(io, token, :label)
5✔
542
        elseif occursin(r"^\$.+$", token)
151✔
543
            printstyled_ll(io, token, :funcname)
2✔
544
        elseif occursin(r"^%?(?:[a-z][\w.]+|\"[^\"]+\")$", token)
149✔
545
            islabel = haslabel & !occursin(',', tokens)
188✔
546
            printstyled_ll(io, token, islabel ? :label : isfuncname ? :funcname : :variable)
228✔
547
            isfuncname = false
114✔
548
        else
549
            printstyled_ll(io, token, :default)
35✔
550
        end
551
        print(io, spaces)
207✔
552
    end
357✔
553
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