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

JuliaLang / julia / #37776

11 May 2024 04:45AM UTC coverage: 86.279% (-1.1%) from 87.427%
#37776

push

local

web-flow
Move nargs/isva to CodeInfo (#54341)

This changes the canonical source of truth for va handling from `Method`
to `CodeInfo`. There are multiple goals for this change:

1. This addresses a longstanding complaint about the way that
CodeInfo-returning generated functions work. Previously, the va-ness or
not of the returned CodeInfo always had to match that of the generator.
For Cassette-like transforms that generally have one big generator
function that is varargs (while then looking up lowered code that is not
varargs), this could become quite annoying. It's possible to workaround,
but there is really no good reason to tie the two together. As we
observed when we implemented OpaqueClosures, the vararg-ness of the
signature and the `vararg arguments`->`tuple` transformation are mostly
independent concepts. With this PR, generated functions can return
CodeInfos with whatever combination of nargs/isva is convenient.

2. This change requires clarifying where the va processing boundary is
in inference. #54076 was already moving in that direction for irinterp,
and this essentially does much of the same for regular inference. As a
consequence the constprop cache is now using non-va-cooked signatures,
which I think is preferable.

3. This further decouples codegen from the presence of a `Method` (which
is already not assumed, since the code being generated could be a
toplevel thunk, but some codegen features are only available to things
that come from Methods). There are a number of upcoming features that
will require codegen of things that are not quite method specializations
(See design doc linked in #52797 and things like #50641). This helps
pave the road for that.

4. I've previously considered expanding the kinds of vararg signatures
that can be described (see e.g. #53851), which also requires a
decoupling of the signature and ast notions of vararg. This again lays
the groundwork for that, although I have no immediate plans... (continued)

91 of 100 new or added lines in 9 files covered. (91.0%)

1301 existing lines in 49 files now uncovered.

75382 of 87370 relevant lines covered (86.28%)

15956665.03 hits per line

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

32.79
/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

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

28
# displaying type warnings
29

30
function warntype_type_printer(io::IO; @nospecialize(type), used::Bool, show_type::Bool=true, _...)
206✔
31
    (show_type && used) || return nothing
141✔
32
    str = "::$type"
75✔
33
    if !highlighting[:warntype]
75✔
UNCOV
34
        print(io, str)
×
35
    elseif type isa Union && is_expected_union(type)
75✔
36
        Base.emphasize(io, str, Base.warn_color()) # more mild user notification
12✔
37
    elseif type isa Type && (!Base.isdispatchelem(type) || type == Core.Box)
116✔
UNCOV
38
        Base.emphasize(io, str)
×
39
    else
40
        Base.printstyled(io, str, color=:cyan) # show the "good" type
63✔
41
    end
42
    return nothing
75✔
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)
12✔
48
    Base.unionlen(u) < 4 || return false
12✔
49
    for x in Base.uniontypes(u)
12✔
50
        if !Base.isdispatchelem(x) || x == Core.Box
48✔
UNCOV
51
            return false
×
52
        end
53
    end
24✔
54
    return true
12✔
55
end
56

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

82
function print_warntype_mi(io::IO, mi::Core.MethodInstance)
2✔
83
    println(io, mi)
2✔
84
    print(io, "  from ")
2✔
85
    println(io, mi.def)
2✔
86
    if !isempty(mi.sparam_vals)
2✔
UNCOV
87
        println(io, "Static Parameters")
×
UNCOV
88
        sig = mi.def.sig
×
UNCOV
89
        warn_color = Base.warn_color() # more mild user notification
×
UNCOV
90
        for i = 1:length(mi.sparam_vals)
×
UNCOV
91
            sig = sig::UnionAll
×
UNCOV
92
            name = sig.var.name
×
UNCOV
93
            val = mi.sparam_vals[i]
×
UNCOV
94
            print_highlighted(io::IO, v::String, color::Symbol) =
×
95
                if highlighting[:warntype]
UNCOV
96
                    Base.printstyled(io, v; color)
×
97
                else
98
                    Base.print(io, v)
×
99
                end
UNCOV
100
            if val isa TypeVar
×
UNCOV
101
                if val.lb === Union{}
×
UNCOV
102
                    print(io, "  ", name, " <: ")
×
UNCOV
103
                    print_highlighted(io, "$(val.ub)", warn_color)
×
UNCOV
104
                elseif val.ub === Any
×
UNCOV
105
                    print(io, "  ", sig.var.name, " >: ")
×
UNCOV
106
                    print_highlighted(io, "$(val.lb)", warn_color)
×
107
                else
UNCOV
108
                    print(io, "  ")
×
UNCOV
109
                    print_highlighted(io, "$(val.lb)", warn_color)
×
UNCOV
110
                    print(io, " <: ", sig.var.name, " <: ")
×
UNCOV
111
                    print_highlighted(io, "$(val.ub)", warn_color)
×
112
                end
UNCOV
113
            elseif val isa typeof(Vararg)
×
UNCOV
114
                print(io, "  ", name, "::")
×
UNCOV
115
                print_highlighted(io, "Int", warn_color)
×
116
            else
UNCOV
117
                print(io, "  ", sig.var.name, " = ")
×
UNCOV
118
                print_highlighted(io, "$(val)", :cyan) # show the "good" type
×
119
            end
UNCOV
120
            println(io)
×
UNCOV
121
            sig = sig.body
×
UNCOV
122
        end
×
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));
4✔
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)) &&
2✔
151
        error("code reflection cannot be used from generated functions")
152
    debuginfo = Base.IRShow.debuginfo(debuginfo)
2✔
153
    lineprinter = Base.IRShow.__debuginfo[debuginfo]
2✔
154
    nargs::Int = 0
2✔
155
    if isa(f, Core.OpaqueClosure)
2✔
UNCOV
156
        isa(f.source, Method) && (nargs = f.source.nargs)
×
UNCOV
157
        print_warntype_codeinfo(io, Base.code_typed_opaque_closure(f, tt)[1]..., nargs; lineprinter)
×
UNCOV
158
        return nothing
×
159
    end
160
    tt = Base.signature_type(f, tt)
2✔
161
    matches = Core.Compiler.findall(tt, Core.Compiler.method_table(interp))
2✔
162
    matches === nothing && Base.raise_match_failure(:code_warntype, tt)
2✔
163
    for match in matches.matches
2✔
164
        match = match::Core.MethodMatch
2✔
165
        (src, rettype) = Core.Compiler.typeinf_code(interp, match, optimize)
2✔
166
        mi = Core.Compiler.specialize_method(match)
2✔
167
        mi.def isa Method && (nargs = (mi.def::Method).nargs)
2✔
168
        print_warntype_mi(io, mi)
2✔
169
        print_warntype_codeinfo(io, src, rettype, nargs; lineprinter)
2✔
170
    end
2✔
171
    nothing
2✔
172
end
173
code_warntype(args...; kwargs...) = (@nospecialize; code_warntype(stdout, args...; kwargs...))
×
174

175
using Base: CodegenParams
176

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

184
# Printing code representations in IR and assembly
185

186
function _dump_function(@nospecialize(f), @nospecialize(t), native::Bool, wrapper::Bool,
75✔
187
                        raw::Bool, dump_module::Bool, syntax::Symbol,
188
                        optimize::Bool, debuginfo::Symbol, binary::Bool,
189
                        params::CodegenParams=CodegenParams(debug_info_kind=Cint(0), debug_info_level=Cint(2), safepoint_on_entry=raw, gcstack_arg=raw))
190
    ccall(:jl_is_in_pure_context, Bool, ()) && error("code reflection cannot be used from generated functions")
75✔
191
    if isa(f, Core.Builtin)
70✔
UNCOV
192
        throw(ArgumentError("argument is not a generic function"))
×
193
    end
194
    warning = ""
70✔
195
    # get the MethodInstance for the method match
196
    if !isa(f, Core.OpaqueClosure)
70✔
197
        world = Base.get_world_counter()
68✔
198
        match = Base._which(signature_type(f, t); world)
68✔
199
        mi = Core.Compiler.specialize_method(match)
68✔
200
        # TODO: use jl_is_cacheable_sig instead of isdispatchtuple
201
        isdispatchtuple(mi.specTypes) || (warning = GENERIC_SIG_WARNING)
68✔
202
    else
203
        world = UInt64(f.world)
2✔
204
        tt = Base.to_tuple_type(t)
2✔
205
        if !isdefined(f.source, :source)
2✔
206
            # OC was constructed from inferred source. There's only one
207
            # specialization and we can't infer anything more precise either.
208
            world = f.source.primary_world
2✔
209
            mi = f.source.specializations::Core.MethodInstance
2✔
210
            Core.Compiler.hasintersect(typeof(f).parameters[1], tt) || (warning = OC_MISMATCH_WARNING)
2✔
211
        else
212
            mi = Core.Compiler.specialize_method(f.source, Tuple{typeof(f.captures), tt.parameters...}, Core.svec())
×
213
            actual = isdispatchtuple(mi.specTypes)
×
214
            isdispatchtuple(mi.specTypes) || (warning = GENERIC_SIG_WARNING)
×
215
        end
216
    end
217
    # get the code for it
218
    if debuginfo === :default
70✔
219
        debuginfo = :source
27✔
220
    elseif debuginfo !== :source && debuginfo !== :none
43✔
221
        throw(ArgumentError("'debuginfo' must be either :source or :none"))
×
222
    end
223
    if native
70✔
224
        if syntax !== :att && syntax !== :intel
1✔
225
            throw(ArgumentError("'syntax' must be either :intel or :att"))
×
226
        end
227
        if dump_module
1✔
228
            # we want module metadata, so use LLVM to generate assembly output
UNCOV
229
            str = _dump_function_native_assembly(mi, world, wrapper, syntax, debuginfo, binary, raw, params)
×
230
        else
231
            # if we don't want the module metadata, just disassemble what our JIT has
232
            str = _dump_function_native_disassembly(mi, world, wrapper, syntax, debuginfo, binary)
1✔
233
        end
234
    else
235
        str = _dump_function_llvm(mi, world, wrapper, !raw, dump_module, optimize, debuginfo, params)
69✔
236
    end
237
    str = warning * str
70✔
238
    return str
70✔
239
end
240

241
function _dump_function_native_disassembly(mi::Core.MethodInstance, world::UInt,
242
                                           wrapper::Bool, syntax::Symbol,
243
                                           debuginfo::Symbol, binary::Bool)
244
    str = @ccall jl_dump_method_asm(mi::Any, world::UInt, false::Bool, wrapper::Bool,
1✔
245
                                    syntax::Ptr{UInt8}, debuginfo::Ptr{UInt8},
246
                                    binary::Bool)::Ref{String}
247
    return str
1✔
248
end
249

250
struct LLVMFDump
251
    tsm::Ptr{Cvoid} # opaque
252
    f::Ptr{Cvoid} # opaque
253
end
254

UNCOV
255
function _dump_function_native_assembly(mi::Core.MethodInstance, world::UInt,
×
256
                                        wrapper::Bool, syntax::Symbol, debuginfo::Symbol,
257
                                        binary::Bool, raw::Bool, params::CodegenParams)
UNCOV
258
    llvmf_dump = Ref{LLVMFDump}()
×
UNCOV
259
    @ccall jl_get_llvmf_defn(llvmf_dump::Ptr{LLVMFDump},mi::Any, world::UInt, wrapper::Bool,
×
260
                             true::Bool, params::CodegenParams)::Cvoid
UNCOV
261
    llvmf_dump[].f == C_NULL && error("could not compile the specified method")
×
UNCOV
262
    str = @ccall jl_dump_function_asm(llvmf_dump::Ptr{LLVMFDump}, false::Bool,
×
263
                                      syntax::Ptr{UInt8}, debuginfo::Ptr{UInt8},
264
                                      binary::Bool, raw::Bool)::Ref{String}
UNCOV
265
    return str
×
266
end
267

268
function _dump_function_llvm(
69✔
269
        mi::Core.MethodInstance, world::UInt, wrapper::Bool,
270
        strip_ir_metadata::Bool, dump_module::Bool,
271
        optimize::Bool, debuginfo::Symbol,
272
        params::CodegenParams)
273
    llvmf_dump = Ref{LLVMFDump}()
69✔
274
    @ccall jl_get_llvmf_defn(llvmf_dump::Ptr{LLVMFDump}, mi::Any, world::UInt,
69✔
275
                             wrapper::Bool, optimize::Bool, params::CodegenParams)::Cvoid
276
    llvmf_dump[].f == C_NULL && error("could not compile the specified method")
69✔
277
    str = @ccall jl_dump_function_ir(llvmf_dump::Ptr{LLVMFDump}, strip_ir_metadata::Bool,
69✔
278
                                     dump_module::Bool, debuginfo::Ptr{UInt8})::Ref{String}
279
    return str
69✔
280
end
281

282
"""
283
    code_llvm([io=stdout,], f, types; raw=false, dump_module=false, optimize=true, debuginfo=:default)
284

285
Prints the LLVM bitcodes generated for running the method matching the given generic
286
function and type signature to `io`.
287

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

293
See also: [`@code_llvm`](@ref), [`code_warntype`](@ref), [`code_typed`](@ref), [`code_lowered`](@ref), [`code_native`](@ref).
294
"""
295
function code_llvm(io::IO, @nospecialize(f), @nospecialize(types=Base.default_tt(f));
29✔
296
                   raw::Bool=false, dump_module::Bool=false, optimize::Bool=true, debuginfo::Symbol=:default,
297
                   params::CodegenParams=CodegenParams(debug_info_kind=Cint(0), debug_info_level=Cint(2), safepoint_on_entry=raw, gcstack_arg=raw))
298
    d = _dump_function(f, types, false, false, raw, dump_module, :intel, optimize, debuginfo, false, params)
29✔
299
    if highlighting[:llvm] && get(io, :color, false)::Bool
27✔
UNCOV
300
        print_llvm(io, d)
×
301
    else
302
        print(io, d)
27✔
303
    end
304
end
305
code_llvm(args...; kwargs...) = (@nospecialize; code_llvm(stdout, args...; kwargs...))
6✔
306

307
"""
308
    code_native([io=stdout,], f, types; syntax=:intel, debuginfo=:default, binary=false, dump_module=true)
309

310
Prints the native assembly instructions generated for running the method matching the given
311
generic function and type signature to `io`.
312

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

319
See also: [`@code_native`](@ref), [`code_warntype`](@ref), [`code_typed`](@ref), [`code_lowered`](@ref), [`code_llvm`](@ref).
320
"""
321
function code_native(io::IO, @nospecialize(f), @nospecialize(types=Base.default_tt(f));
3✔
322
                     dump_module::Bool=true, syntax::Symbol=:intel, raw::Bool=false,
323
                     debuginfo::Symbol=:default, binary::Bool=false,
324
                     params::CodegenParams=CodegenParams(debug_info_kind=Cint(0), debug_info_level=Cint(2), safepoint_on_entry=raw, gcstack_arg=raw))
325
    d = _dump_function(f, types, true, false, raw, dump_module, syntax, true, debuginfo, binary, params)
3✔
326
    if highlighting[:native] && get(io, :color, false)::Bool
1✔
UNCOV
327
        print_native(io, d)
×
328
    else
329
        print(io, d)
1✔
330
    end
331
end
332
code_native(args...; kwargs...) = (@nospecialize; code_native(stdout, args...; kwargs...))
4✔
333

334
## colorized IR and assembly printing
335

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

UNCOV
338
function print_llvm(io::IO, code::String)
×
UNCOV
339
    buf = IOBuffer(code)
×
UNCOV
340
    for line in eachline(buf)
×
UNCOV
341
        m = match(r"^(\s*)((?:[^;]|;\")*)(.*)$", line)
×
UNCOV
342
        m === nothing && continue
×
UNCOV
343
        indent, tokens, comment = m.captures
×
UNCOV
344
        print(io, indent)
×
UNCOV
345
        print_llvm_tokens(io, tokens)
×
UNCOV
346
        printstyled_ll(io, comment, :comment)
×
UNCOV
347
        println(io)
×
UNCOV
348
    end
×
349
end
350

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

UNCOV
355
function print_llvm_tokens(io, tokens)
×
UNCOV
356
    m = match(r"^((?:[^\s:]+:)?)(\s*)(.*)", tokens)
×
UNCOV
357
    if m !== nothing
×
UNCOV
358
        label, spaces, tokens = m.captures
×
UNCOV
359
        printstyled_ll(io, label, :label, spaces)
×
360
    end
UNCOV
361
    m = match(r"^(%[^\s=]+)(\s*)=(\s*)(.*)", tokens)
×
UNCOV
362
    if m !== nothing
×
UNCOV
363
        result, spaces, spaces2, tokens = m.captures
×
UNCOV
364
        printstyled_ll(io, result, :variable, spaces)
×
UNCOV
365
        printstyled_ll(io, '=', :default, spaces2)
×
366
    end
UNCOV
367
    m = match(r"^([a-z]\w*)(\s*)(.*)", tokens)
×
UNCOV
368
    if m !== nothing
×
UNCOV
369
        inst, spaces, tokens = m.captures
×
UNCOV
370
        iskeyword = occursin(r"^(?:define|declare|type)$", inst) || occursin("=", tokens)
×
UNCOV
371
        printstyled_ll(io, inst, iskeyword ? :keyword : :instruction, spaces)
×
372
    end
373

UNCOV
374
    print_llvm_operands(io, tokens)
×
375
end
376

377
function print_llvm_operands(io, tokens)
×
UNCOV
378
    while !isempty(tokens)
×
UNCOV
379
        tokens = print_llvm_operand(io, tokens)
×
UNCOV
380
    end
×
UNCOV
381
    return tokens
×
382
end
383

UNCOV
384
function print_llvm_operand(io, tokens)
×
UNCOV
385
    islabel = false
×
UNCOV
386
    while !isempty(tokens)
×
UNCOV
387
        m = match(r"^,(\s*)(.*)", tokens)
×
UNCOV
388
        if m !== nothing
×
UNCOV
389
            spaces, tokens = m.captures
×
UNCOV
390
            printstyled_ll(io, ',', :default, spaces)
×
UNCOV
391
            break
×
392
        end
UNCOV
393
        m = match(r"^(\*+|=)(\s*)(.*)", tokens)
×
UNCOV
394
        if m !== nothing
×
UNCOV
395
            sym, spaces, tokens = m.captures
×
UNCOV
396
            printstyled_ll(io, sym, :default, spaces)
×
UNCOV
397
            continue
×
398
        end
UNCOV
399
        m = match(r"^(\"[^\"]*\")(\s*)(.*)", tokens)
×
UNCOV
400
        if m !== nothing
×
UNCOV
401
            str, spaces, tokens = m.captures
×
UNCOV
402
            printstyled_ll(io, str, :variable, spaces)
×
UNCOV
403
            continue
×
404
        end
UNCOV
405
        m = match(r"^([({\[<])(\s*)(.*)", tokens)
×
UNCOV
406
        if m !== nothing
×
UNCOV
407
            bracket, spaces, tokens = m.captures
×
UNCOV
408
            printstyled_ll(io, bracket, :bracket, spaces)
×
UNCOV
409
            tokens = print_llvm_operands(io, tokens) # enter
×
UNCOV
410
            continue
×
411
        end
UNCOV
412
        m = match(r"^([)}\]>])(\s*)(.*)", tokens)
×
UNCOV
413
        if m !== nothing
×
UNCOV
414
            bracket, spaces, tokens = m.captures
×
UNCOV
415
            printstyled_ll(io, bracket, :bracket, spaces)
×
UNCOV
416
            break # leave
×
417
        end
418

UNCOV
419
        m = match(r"^([^\s,*=(){}\[\]<>]+)(\s*)(.*)", tokens)
×
UNCOV
420
        m === nothing && break
×
UNCOV
421
        token, spaces, tokens = m.captures
×
UNCOV
422
        if occursin(llvm_types, token)
×
UNCOV
423
            printstyled_ll(io, token, :type)
×
UNCOV
424
            islabel = token == "label"
×
UNCOV
425
        elseif occursin(llvm_cond, token) # condition code is instruction-level
×
UNCOV
426
            printstyled_ll(io, token, :instruction)
×
UNCOV
427
        elseif occursin(num_regex, token)
×
UNCOV
428
            printstyled_ll(io, token, :number)
×
UNCOV
429
        elseif occursin(r"^@.+$", token)
×
UNCOV
430
            printstyled_ll(io, token, :funcname)
×
UNCOV
431
        elseif occursin(r"^%.+$", token)
×
UNCOV
432
            islabel |= occursin(r"^%[^\d].*$", token) & occursin(r"^\]", tokens)
×
UNCOV
433
            printstyled_ll(io, token, islabel ? :label : :variable)
×
UNCOV
434
            islabel = false
×
UNCOV
435
        elseif occursin(r"^[a-z]\w+$", token)
×
UNCOV
436
            printstyled_ll(io, token, :keyword)
×
437
        else
UNCOV
438
            printstyled_ll(io, token, :default)
×
439
        end
UNCOV
440
        print(io, spaces)
×
UNCOV
441
    end
×
UNCOV
442
    return tokens
×
443
end
444

UNCOV
445
function print_native(io::IO, code::String, arch::Symbol=sys_arch_category())
×
UNCOV
446
    archv = Val(arch)
×
UNCOV
447
    buf = IOBuffer(code)
×
UNCOV
448
    for line in eachline(buf)
×
UNCOV
449
        m = match(r"^(\s*)((?:[^;#/]|#\S|;\"|/[^/])*)(.*)$", line)
×
UNCOV
450
        m === nothing && continue
×
UNCOV
451
        indent, tokens, comment = m.captures
×
UNCOV
452
        print(io, indent)
×
UNCOV
453
        print_native_tokens(io, tokens, archv)
×
UNCOV
454
        printstyled_ll(io, comment, :comment)
×
UNCOV
455
        println(io)
×
UNCOV
456
    end
×
457
end
458

UNCOV
459
function sys_arch_category()
×
UNCOV
460
    if Sys.ARCH === :x86_64 || Sys.ARCH === :i686
×
UNCOV
461
        :x86
×
462
    elseif Sys.ARCH === :aarch64 || startswith(string(Sys.ARCH), "arm")
×
UNCOV
463
        :arm
×
464
    else
UNCOV
465
        :unsupported
×
466
    end
467
end
468

UNCOV
469
print_native_tokens(io, line, ::Val) = print(io, line)
×
470

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

UNCOV
476
function print_native_tokens(io, tokens, arch::Union{Val{:x86}, Val{:arm}})
×
UNCOV
477
    x86 = arch isa Val{:x86}
×
UNCOV
478
    m = match(r"^((?:[^\s:]+:|\"[^\"]+\":)?)(\s*)(.*)", tokens)
×
UNCOV
479
    if m !== nothing
×
UNCOV
480
        label, spaces, tokens = m.captures
×
UNCOV
481
        printstyled_ll(io, label, :label, spaces)
×
482
    end
UNCOV
483
    haslabel = false
×
UNCOV
484
    m = match(r"^([a-z][\w.]*)(\s*)(.*)", tokens)
×
UNCOV
485
    if m !== nothing
×
UNCOV
486
        instruction, spaces, tokens = m.captures
×
UNCOV
487
        printstyled_ll(io, instruction, :instruction, spaces)
×
UNCOV
488
        haslabel = occursin(r"^(?:bl?|bl?\.\w{2,5}|[ct]bn?z)?$", instruction)
×
489
    end
490

UNCOV
491
    isfuncname = false
×
UNCOV
492
    while !isempty(tokens)
×
UNCOV
493
        m = match(r"^([,:*])(\s*)(.*)", tokens)
×
UNCOV
494
        if m !== nothing
×
UNCOV
495
            sym, spaces, tokens = m.captures
×
UNCOV
496
            printstyled_ll(io, sym, :default, spaces)
×
UNCOV
497
            isfuncname = false
×
UNCOV
498
            continue
×
499
        end
UNCOV
500
        m = match(r"^([(){}\[\]])(\s*)(.*)", tokens)
×
UNCOV
501
        if m !== nothing
×
UNCOV
502
            bracket, spaces, tokens = m.captures
×
UNCOV
503
            printstyled_ll(io, bracket, :bracket, spaces)
×
UNCOV
504
            continue
×
505
        end
UNCOV
506
        m = match(r"^#([0-9a-fx.-]+)(\s*)(.*)", tokens)
×
UNCOV
507
        if !x86 && m !== nothing && occursin(num_regex, m.captures[1])
×
UNCOV
508
            num, spaces, tokens = m.captures
×
UNCOV
509
            printstyled_ll(io, "#" * num, :number, spaces)
×
UNCOV
510
            continue
×
511
        end
512

UNCOV
513
        m = match(r"^([^\s,:*(){}\[\]][^\s,:*/(){}\[\]]*)(\s*)(.*)", tokens)
×
UNCOV
514
        m === nothing && break
×
UNCOV
515
        token, spaces, tokens = m.captures
×
UNCOV
516
        if occursin(num_regex, token)
×
UNCOV
517
            printstyled_ll(io, token, :number)
×
UNCOV
518
        elseif x86 && occursin(x86_ptr, token) || occursin(avx512flags, token)
×
UNCOV
519
            printstyled_ll(io, token, :keyword)
×
UNCOV
520
            isfuncname = token == "offset"
×
UNCOV
521
        elseif !x86 && (occursin(arm_keywords, token) || occursin(arm_cond, token))
×
UNCOV
522
            printstyled_ll(io, token, :keyword)
×
UNCOV
523
        elseif occursin(r"^L.+$", token)
×
UNCOV
524
            printstyled_ll(io, token, :label)
×
UNCOV
525
        elseif occursin(r"^\$.+$", token)
×
UNCOV
526
            printstyled_ll(io, token, :funcname)
×
UNCOV
527
        elseif occursin(r"^%?(?:[a-z][\w.]+|\"[^\"]+\")$", token)
×
UNCOV
528
            islabel = haslabel & !occursin(',', tokens)
×
UNCOV
529
            printstyled_ll(io, token, islabel ? :label : isfuncname ? :funcname : :variable)
×
UNCOV
530
            isfuncname = false
×
531
        else
UNCOV
532
            printstyled_ll(io, token, :default)
×
533
        end
UNCOV
534
        print(io, spaces)
×
UNCOV
535
    end
×
536
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