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

JuliaLang / julia / #37948

08 Nov 2024 02:27AM UTC coverage: 87.488% (-0.04%) from 87.529%
#37948

push

local

web-flow
Make Compiler an independent package (that lives in the julia repo) (#56409)

This is a further extension to #56128 to make the compiler into a proper
stdlib, useable outside of `Base` as `using Compiler` in the same way
that `JuliaSyntax` works already. There's a few remaining questions
around how loading works, but mechanically, this PR is complete. For
those remaining questions, I'll probably put up a separate PR that would
migrate JuliaSyntax to it and then simply adopt that here once we've
figured out the correct semantics.

237 of 327 new or added lines in 22 files covered. (72.48%)

136 existing lines in 20 files now uncovered.

65541 of 74914 relevant lines covered (87.49%)

10164344.25 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,054✔
24
    printstyled(io, x, bold=llstyle[s][1], color=llstyle[s][2])
2,637✔
25
    print(io, trailing_spaces)
1,054✔
26
end
27

28
# displaying type warnings
29

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

57
function print_warntype_codeinfo(io::IO, src::Core.CodeInfo, @nospecialize(rettype), nargs::Int; lineprinter, label_dynamic_calls)
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
83✔
65
                println(io, "Locals")
7✔
66
            end
67
            print(io, "  ", slotnames[i])
83✔
68
            if isa(slottypes, Vector{Any})
83✔
69
                warntype_type_printer(io; type=slottypes[i], used=true)
83✔
70
            end
71
            println(io)
83✔
72
        end
139✔
73
    end
74
    print(io, "Body")
27✔
75
    warntype_type_printer(io; type=rettype, used=true)
27✔
76
    println(io)
27✔
77
    irshow_config = Base.IRShow.IRShowConfig(lineprinter(src), warntype_type_printer; label_dynamic_calls)
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-concrete 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-concrete 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::Base.Compiler.AbstractInterpreter=Base.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;
1✔
158
                                lineprinter, label_dynamic_calls = optimize)
159
        return nothing
1✔
160
    end
161
    tt = Base.signature_type(f, tt)
26✔
162
    matches = findall(tt, Base.Compiler.method_table(interp))
26✔
163
    matches === nothing && Base.raise_match_failure(:code_warntype, tt)
26✔
164
    for match in matches.matches
26✔
165
        match = match::Core.MethodMatch
26✔
166
        src = Base.Compiler.typeinf_code(interp, match, optimize)
26✔
167
        mi = Base.Compiler.specialize_method(match)
26✔
168
        mi.def isa Method && (nargs = (mi.def::Method).nargs)
26✔
169
        print_warntype_mi(io, mi)
26✔
170
        if src isa Core.CodeInfo
26✔
171
            print_warntype_codeinfo(io, src, src.rettype, nargs;
26✔
172
                                    lineprinter, label_dynamic_calls = optimize)
173
        else
174
            println(io, "  inference not successful")
×
175
        end
176
    end
26✔
177
    nothing
26✔
178
end
179
code_warntype(args...; kwargs...) = (@nospecialize; code_warntype(stdout, args...; kwargs...))
×
180

181
using Base: CodegenParams
182

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

190
# Printing code representations in IR and assembly
191

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

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

269
struct LLVMFDump
270
    tsm::Ptr{Cvoid} # opaque
271
    f::Ptr{Cvoid} # opaque
272
end
273

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

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

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

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

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

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

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

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

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

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

353
## colorized IR and assembly printing
354

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

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

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

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

393
    print_llvm_operands(io, tokens)
45✔
394
end
395

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

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

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

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

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

488
print_native_tokens(io, line, ::Val) = print(io, line)
1✔
489

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

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

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

532
        m = match(r"^([^\s,:*(){}\[\]][^\s,:*/(){}\[\]]*)(\s*)(.*)", tokens)
210✔
533
        m === nothing && break
210✔
534
        token, spaces, tokens = m.captures
210✔
535
        if occursin(num_regex, token)
210✔
536
            printstyled_ll(io, token, :number)
22✔
537
        elseif x86 && occursin(x86_ptr, token) || occursin(avx512flags, token)
316✔
538
            printstyled_ll(io, token, :keyword)
22✔
539
            isfuncname = token == "offset"
22✔
540
        elseif !x86 && (occursin(arm_keywords, token) || occursin(arm_cond, token))
205✔
541
            printstyled_ll(io, token, :keyword)
7✔
542
        elseif occursin(r"^L.+$", token)
159✔
543
            printstyled_ll(io, token, :label)
5✔
544
        elseif occursin(r"^\$.+$", token)
154✔
545
            printstyled_ll(io, token, :funcname)
2✔
546
        elseif occursin(r"^%?(?:[a-z][\w.]+|\"[^\"]+\")$", token)
152✔
547
            islabel = haslabel & !occursin(',', tokens)
190✔
548
            printstyled_ll(io, token, islabel ? :label : isfuncname ? :funcname : :variable)
230✔
549
            isfuncname = false
115✔
550
        else
551
            printstyled_ll(io, token, :default)
37✔
552
        end
553
        print(io, spaces)
210✔
554
    end
362✔
555
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