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

JuliaLang / julia / 1457

28 Feb 2026 11:58PM UTC coverage: 76.892% (-0.003%) from 76.895%
1457

push

buildkite

web-flow
inference: Propagate conditional type refinement to aliased slots (#61168)

Continuation from #61041 (especially this PR implements @vtjnash 's
idea:
https://github.com/JuliaLang/julia/pull/61041#discussion_r2842290067)

When slot `y` is assigned from slot `x` (`y = x`), both hold the same
value. If a `Conditional` subsequently narrows the type of `x` (e.g. via
`x isa Int`), the same refinement should apply to `y`. The same applies
to `typeassert`-based slot refinements.

This adds `bb_slot_aliases` to `InferenceState` to track slot aliasing
across basic blocks. `slot_aliases[i] == j` means slot `i` currently
holds the same value as slot `j`. The table is always kept flat (aliases
point directly to the root slot), so a single lookup suffices.

Aliasing is tracked at two granularities:

- Intra-BB tracking (`slot_aliases`): this local variable in
`typeinf_local` is updated per-statement and reset at each basic-block
boundary.
- Cross-BB tracking (`bb_slot_aliases`): populated lazily during the
main inference loop by `update_bbstate!`, which copies the exit alias
state to each successor BB on first visit and intersects (meet
operation) on subsequent visits. Only aliases present on _all_ incoming
paths are retained.

When a `Conditional` fires, `refine_aliases!` propagates the same type
narrowing to every slot that is currently aliased to the conditional's
slot. The similar refinements from `apply_refinement!` are also applied.

---

For example, this PR improves type stability in cases like the
following:
```julia
@test Base.infer_return_type((Any,)) do x
    y = x
    if x isa Int
        return sin(y)
    end
    error("x is not Int")
end == Float64
```

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

71 of 84 new or added lines in 2 files covered. (84.52%)

786 existing lines in 19 files now uncovered.

63560 of 82661 relevant lines covered (76.89%)

23477491.84 hits per line

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

96.89
/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
struct ArgInfo
24
    oc::Union{Core.OpaqueClosure,Nothing}
25
    tt::Type{<:Tuple}
26

27
    # Construct from a function object + argtypes
28
    function ArgInfo(@nospecialize(f), @nospecialize(t))
8✔
29
        if isa(f, Core.Builtin)
590✔
30
            throw(ArgumentError("argument is not a generic function"))
6✔
31
        elseif f isa Core.OpaqueClosure
584✔
32
            return new(f, Base.to_tuple_type(t))
21✔
33
        else
34
            return new(nothing, signature_type(f, t))
563✔
35
        end
36
    end
37

38
    # Construct from argtypes (incl. arg0)
39
    function ArgInfo(@nospecialize(argtypes::Union{Tuple,Type{<:Tuple}}))
40
        tt = Base.to_tuple_type(argtypes)
51✔
41
        return new(nothing, tt)
33✔
42
    end
43
end
44

45
function printstyled_ll(io::IO, x, s::Symbol, trailing_spaces="")
41,344✔
46
    printstyled(io, x, bold=llstyle[s][1], color=llstyle[s][2])
106,952✔
47
    print(io, trailing_spaces)
41,344✔
48
end
49

50
# displaying type warnings
51

52
function warntype_type_printer(io::IO; @nospecialize(type), used::Bool, show_type::Bool=true, _...)
2,520✔
53
    (show_type && used) || return nothing
1,890✔
54
    str = "::$type"
1,098✔
55
    if !highlighting[:warntype]
1,098✔
56
        print(io, str)
18✔
57
    elseif type isa Union && Base.Compiler.IRShow.is_expected_union(type)
1,080✔
58
        Base.emphasize(io, str, Base.warn_color()) # more mild user notification
45✔
59
    elseif type isa Type && (!Base.isdispatchelem(type) || type == Core.Box)
1,656✔
60
        Base.emphasize(io, str)
21✔
61
    else
62
        Base.printstyled(io, str, color=:cyan) # show the "good" type
1,014✔
63
    end
64
    return nothing
1,098✔
65
end
66

67
function print_warntype_codeinfo(io::IO, src::Core.CodeInfo, @nospecialize(rettype), nargs::Int; lineprinter, label_dynamic_calls)
186✔
68
    if src.slotnames !== nothing
93✔
69
        slotnames = Base.sourceinfo_slotnames(src)
93✔
70
        io = IOContext(io, :SOURCE_SLOTNAMES => slotnames)
93✔
71
        slottypes = src.slottypes
93✔
72
        nargs > 0 && println(io, "Arguments")
93✔
73
        for i = 1:length(slotnames)
93✔
74
            if i == nargs + 1
285✔
75
                println(io, "Locals")
21✔
76
            end
77
            print(io, "  ", slotnames[i])
285✔
78
            if isa(slottypes, Vector{Any})
285✔
79
                warntype_type_printer(io; type=slottypes[i], used=true)
273✔
80
            end
81
            println(io)
285✔
82
        end
477✔
83
    end
84
    print(io, "Body")
93✔
85
    warntype_type_printer(io; type=rettype, used=true)
93✔
86
    println(io)
93✔
87
    irshow_config = Base.IRShow.IRShowConfig(lineprinter(src), warntype_type_printer; label_dynamic_calls)
93✔
88
    Base.IRShow.show_ir(io, src, irshow_config)
93✔
89
    println(io)
93✔
90
end
91

92
function print_warntype_mi(io::IO, mi::Core.MethodInstance)
84✔
93
    println(io, mi)
84✔
94
    print(io, "  from ")
84✔
95
    println(io, mi.def)
84✔
96
    if !isempty(mi.sparam_vals)
84✔
97
        println(io, "Static Parameters")
9✔
98
        sig = mi.def.sig
9✔
99
        warn_color = Base.warn_color() # more mild user notification
9✔
100
        for i = 1:length(mi.sparam_vals)
9✔
101
            sig = sig::UnionAll
21✔
102
            name = sig.var.name
21✔
103
            val = mi.sparam_vals[i]
21✔
104
            print_highlighted(io::IO, v::String, color::Symbol) =
45✔
105
                if highlighting[:warntype]
106
                    Base.printstyled(io, v; color)
24✔
107
                else
108
                    Base.print(io, v)
×
109
                end
110
            if val isa TypeVar
21✔
111
                if val.lb === Union{}
9✔
112
                    print(io, "  ", name, " <: ")
3✔
113
                    print_highlighted(io, "$(val.ub)", warn_color)
3✔
114
                elseif val.ub === Any
6✔
115
                    print(io, "  ", sig.var.name, " >: ")
3✔
116
                    print_highlighted(io, "$(val.lb)", warn_color)
3✔
117
                else
118
                    print(io, "  ")
3✔
119
                    print_highlighted(io, "$(val.lb)", warn_color)
3✔
120
                    print(io, " <: ", sig.var.name, " <: ")
3✔
121
                    print_highlighted(io, "$(val.ub)", warn_color)
3✔
122
                end
123
            elseif val isa typeof(Vararg)
12✔
124
                print(io, "  ", name, "::")
3✔
125
                print_highlighted(io, "Int", warn_color)
3✔
126
            else
127
                print(io, "  ", sig.var.name, " = ")
9✔
128
                print_highlighted(io, "$(val)", :cyan) # show the "good" type
9✔
129
            end
130
            println(io)
21✔
131
            sig = sig.body
21✔
132
        end
21✔
133
    end
134
end
135

136
"""
137
    code_warntype([io::IO], f, types; debuginfo=:default)
138

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

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

150
Keyword argument `debuginfo` may be one of `:source`, `:none` or `:default`, to specify the verbosity of code comments.
151
Unless the user changes `Base.IRShow.default_debuginfo[]`, the value `:default` is equivalent to `:source`.
152

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

155
See also: [`@code_warntype`](@ref), [`code_typed`](@ref), [`code_lowered`](@ref), [`code_llvm`](@ref), [`code_native`](@ref).
156
"""
157
function code_warntype(io::IO, arginfo::ArgInfo;
186✔
158
                       world=Base.get_world_counter(),
159
                       interp::Base.Compiler.AbstractInterpreter=Base.Compiler.NativeInterpreter(world),
160
                       debuginfo::Symbol=:default, optimize::Bool=false, kwargs...)
161
    (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) &&
93✔
162
        error("code reflection cannot be used from generated functions")
163
    debuginfo = Base.IRShow.debuginfo(debuginfo)
93✔
164
    lineprinter = Base.IRShow.__debuginfo[debuginfo]
93✔
165
    nargs::Int = 0
93✔
166
    if arginfo.oc !== nothing
93✔
167
        (; oc, tt) = arginfo
9✔
168
        isa(oc.source, Method) && (nargs = oc.source.nargs)
18✔
169
        print_warntype_codeinfo(io, Base.code_typed_opaque_closure(oc, tt; optimize, interp)[1]..., nargs;
9✔
170
                                lineprinter, label_dynamic_calls = optimize)
171
        return nothing
9✔
172
    end
173
    tt = arginfo.tt
84✔
174
    matches = findall(tt, Base.Compiler.method_table(interp))
84✔
175
    matches === nothing && Base.raise_match_failure(:code_warntype, tt)
84✔
176
    for match in matches.matches
84✔
177
        match = match::Core.MethodMatch
84✔
178
        src = Base.Compiler.typeinf_code(interp, match, optimize)
168✔
179
        mi = Base.Compiler.specialize_method(match)
84✔
180
        mi.def isa Method && (nargs = (mi.def::Method).nargs)
84✔
181
        print_warntype_mi(io, mi)
84✔
182
        if src isa Core.CodeInfo
84✔
183
            print_warntype_codeinfo(io, src, src.rettype, nargs;
84✔
184
                                    lineprinter, label_dynamic_calls = optimize)
185
        else
186
            println(io, "  inference not successful")
×
187
        end
188
    end
84✔
189
    nothing
84✔
190
end
191
code_warntype(io::IO, @nospecialize(f), @nospecialize(tt=Base.default_tt(f)); kwargs...) = code_warntype(io, ArgInfo(f, tt); kwargs...)
180✔
192
code_warntype(io::IO, @nospecialize(argtypes::Union{Tuple,Type{<:Tuple}}); kwargs...) = code_warntype(io, ArgInfo(argtypes); kwargs...)
12✔
193
code_warntype(f; kwargs...) = (@nospecialize; code_warntype(stdout, f; kwargs...))
×
194
code_warntype(f, argtypes; kwargs...) = (@nospecialize; code_warntype(stdout, f, argtypes; kwargs...))
×
195

196
using Base: CodegenParams
197

198
const GENERIC_SIG_WARNING = "; WARNING: This code may not match what actually runs.\n"
199
const OC_MISMATCH_WARNING =
200
"""
201
; WARNING: The pre-inferred opaque closure is not callable with the given arguments
202
;          and will error on dispatch with this signature.
203
"""
204

205
# Printing code representations in IR and assembly
206

207
function _dump_function(arginfo::ArgInfo, native::Bool, wrapper::Bool,
527✔
208
                        raw::Bool, dump_module::Bool, syntax::Symbol,
209
                        optimize::Bool, debuginfo::Symbol, binary::Bool,
210
                        llvm_options::String="",
211
                        params::CodegenParams=CodegenParams(debug_info_kind=Cint(0), debug_info_level=Cint(2), safepoint_on_entry=raw, gcstack_arg=raw))
212
    ccall(:jl_is_in_pure_context, Bool, ()) && error("code reflection cannot be used from generated functions")
530✔
213
    warning = ""
506✔
214
    # get the MethodInstance for the method match
215
    if arginfo.oc === nothing
506✔
216
        world = Base.get_world_counter()
494✔
217
        match = Base._which(arginfo.tt; world)
494✔
218
        mi = Base.specialize_method(match)
491✔
219
        # TODO: use jl_is_cacheable_sig instead of isdispatchtuple
220
        isdispatchtuple(mi.specTypes) || (warning = GENERIC_SIG_WARNING)
491✔
221
    else
222
        (; oc, tt) = arginfo
12✔
223
        world = UInt64(oc.world)
12✔
224
        if !isdefined(oc.source, :source)
12✔
225
            # OC was constructed from inferred source. There's only one
226
            # specialization and we can't infer anything more precise either.
227
            world = oc.source.primary_world
12✔
228
            mi = oc.source.specializations::Core.MethodInstance
12✔
229
            Base.hasintersect(typeof(oc).parameters[1], tt) || (warning = OC_MISMATCH_WARNING)
12✔
230
        else
231
            mi = Base.specialize_method(oc.source, Tuple{typeof(oc.captures), tt.parameters...}, Core.svec())
×
UNCOV
232
            isdispatchtuple(mi.specTypes) || (warning = GENERIC_SIG_WARNING)
×
233
        end
234
    end
235
    # get the code for it
236
    if debuginfo === :default
503✔
237
        debuginfo = :source
362✔
238
    elseif debuginfo !== :source && debuginfo !== :none
141✔
UNCOV
239
        throw(ArgumentError("'debuginfo' must be either :source or :none"))
×
240
    end
241
    if native
503✔
242
        if syntax !== :att && syntax !== :intel
95✔
UNCOV
243
            throw(ArgumentError("'syntax' must be either :intel or :att"))
×
244
        end
245
        str = ""
95✔
246
        if !dump_module
95✔
247
            # if we don't want the module metadata, attempt to disassemble what our JIT has
248
            str = _dump_function_native_disassembly(mi, world, wrapper, syntax, debuginfo, binary)
11✔
249
        end
250
        if isempty(str)
95✔
251
            # if that failed (or we want metadata), use LLVM to generate more accurate assembly output
252
            if arginfo.oc === nothing
84✔
253
                src = Base.Compiler.typeinf_code(Base.Compiler.NativeInterpreter(world), mi, true)
168✔
254
            else
UNCOV
255
                src, rt = Base.get_oc_code_rt(nothing, arginfo.oc, arginfo.tt, true)
×
256
            end
257
            src isa Core.CodeInfo || error("failed to infer source for $mi")
84✔
258
            str = _dump_function_native_assembly(mi, src, wrapper, syntax, debuginfo, binary, raw, params)
84✔
259
        end
260
    else
261
        if arginfo.oc === nothing
408✔
262
            src = Base.Compiler.typeinf_code(Base.Compiler.NativeInterpreter(world), mi, true)
792✔
263
        else
264
            src, rt = Base.get_oc_code_rt(nothing, arginfo.oc, arginfo.tt, true)
12✔
265
        end
266
        src isa Core.CodeInfo || error("failed to infer source for $mi")
408✔
267
        str = _dump_function_llvm(mi, src, wrapper, !raw, dump_module, optimize, debuginfo, llvm_options, params)
408✔
268
    end
269
    str = warning * str
500✔
270
    return str
500✔
271
end
272

273
function _dump_function_native_disassembly(mi::Core.MethodInstance, world::UInt,
274
                                           wrapper::Bool, syntax::Symbol,
275
                                           debuginfo::Symbol, binary::Bool)
276
    str = @ccall jl_dump_method_asm(mi::Any, world::UInt, false::Bool, wrapper::Bool,
11✔
277
                                    syntax::Ptr{UInt8}, debuginfo::Ptr{UInt8},
278
                                    binary::Bool)::Ref{String}
279
    return str
11✔
280
end
281

282
struct LLVMFDump
283
    tsm::Ptr{Cvoid} # opaque
284
    f::Ptr{Cvoid} # opaque
285
    pass_output::Cstring # LLVM pass instrumentation output (lifetime managed by jl_dump_function_ir or jl_dump_function_asm)
286
end
287

288
function _dump_function_native_assembly(mi::Core.MethodInstance, src::Core.CodeInfo,
84✔
289
                                        wrapper::Bool, syntax::Symbol, debuginfo::Symbol,
290
                                        binary::Bool, raw::Bool, params::CodegenParams)
291
    llvmf_dump = Ref{LLVMFDump}()
84✔
292
    @ccall jl_get_llvmf_defn(llvmf_dump::Ptr{LLVMFDump}, mi::Any, src::Any, wrapper::Bool,
84✔
293
                             true::Bool, ""::Cstring, params::CodegenParams)::Cvoid
294
    llvmf_dump[].f == C_NULL && error("could not compile the specified method")
84✔
295
    str = @ccall jl_dump_function_asm(llvmf_dump::Ptr{LLVMFDump}, false::Bool,
81✔
296
                                      syntax::Ptr{UInt8}, debuginfo::Ptr{UInt8},
297
                                      binary::Bool, raw::Bool)::Ref{String}
298
    return str
81✔
299
end
300

301
function _dump_function_llvm(
408✔
302
        mi::Core.MethodInstance, src::Core.CodeInfo, wrapper::Bool,
303
        strip_ir_metadata::Bool, dump_module::Bool,
304
        optimize::Bool, debuginfo::Symbol,
305
        llvm_options::String,
306
        params::CodegenParams)
307
    llvmf_dump = Ref{LLVMFDump}()
408✔
308
    @ccall jl_get_llvmf_defn(llvmf_dump::Ptr{LLVMFDump}, mi::Any, src::Any,
408✔
309
                             wrapper::Bool, optimize::Bool, llvm_options::Cstring,
310
                             params::CodegenParams)::Cvoid
311
    llvmf_dump[].f == C_NULL && error("could not compile the specified method")
408✔
312
    # jl_dump_function_ir handles pass_output internally (prepends it and frees it)
313
    str = @ccall jl_dump_function_ir(llvmf_dump::Ptr{LLVMFDump}, strip_ir_metadata::Bool,
408✔
314
                                     dump_module::Bool, debuginfo::Ptr{UInt8})::Ref{String}
315
    return str
408✔
316
end
317

318
"""
319
    code_llvm([io=stdout,], f, types; raw=false, dump_module=false, optimize=true, debuginfo=:default, llvm_options="")
320

321
Prints the LLVM bitcodes generated for running the method matching the given generic
322
function and type signature to `io`.
323

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

329
The `llvm_options` keyword argument allows passing LLVM options to control the optimization pipeline output.
330
Supported options include:
331
- `-print-after-all`: Print IR after each pass
332
- `-print-before-all`: Print IR before each pass
333
- `-print-after=<passname>`: Print IR after a specific pass (e.g., `-print-after=InstCombinePass`). Comma-separated lists and repeated flags are supported.
334
- `-print-before=<passname>`: Print IR before a specific pass. Comma-separated lists and repeated flags are supported.
335
- `-print-module-scope`: Print entire module instead of just the function
336
- `-filter-print-funcs=<name>`: Only print IR for functions matching the name
337

338
See also: [`@code_llvm`](@ref), [`code_warntype`](@ref), [`code_typed`](@ref), [`code_lowered`](@ref), [`code_native`](@ref).
339
"""
340
function code_llvm(io::IO, arginfo::ArgInfo;
327✔
341
                   raw::Bool=false, dump_module::Bool=false, optimize::Bool=true, debuginfo::Symbol=:default,
342
                   llvm_options::String="",
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(arginfo, false, false, raw, dump_module, :intel, optimize, debuginfo, false, llvm_options, params)
285✔
345
    if highlighting[:llvm] && get(io, :color, false)::Bool
276✔
346
        print_llvm(io, d)
6✔
347
    else
348
        print(io, d)
270✔
349
    end
350
end
351
code_llvm(io::IO, @nospecialize(argtypes::Union{Tuple,Type{<:Tuple}}); kwargs...) = code_llvm(io, ArgInfo(argtypes); kwargs...)
24✔
352
code_llvm(io::IO, @nospecialize(f), @nospecialize(types=Base.default_tt(f)); kwargs...) = code_llvm(io, ArgInfo(f, types); kwargs...)
558✔
353
code_llvm(args...; kwargs...) = (@nospecialize; code_llvm(stdout, args...; kwargs...))
36✔
354

355
"""
356
    code_native([io=stdout,], f, types; syntax=:intel, debuginfo=:default, binary=false, dump_module=true, raw=false)
357

358
Prints the native assembly instructions generated for running the method matching the given
359
generic function and type signature to `io`.
360

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

367
See also: [`@code_native`](@ref), [`code_warntype`](@ref), [`code_typed`](@ref), [`code_lowered`](@ref), [`code_llvm`](@ref).
368
"""
369
function code_native(io::IO, arginfo::ArgInfo;
113✔
370
                     dump_module::Bool=true, syntax::Symbol=:intel, raw::Bool=false,
371
                     debuginfo::Symbol=:default, binary::Bool=false,
372
                     llvm_options::String="",
373
                     params::CodegenParams=CodegenParams(debug_info_kind=Cint(0), debug_info_level=Cint(2), safepoint_on_entry=raw, gcstack_arg=raw))
374
    d = _dump_function(arginfo, true, false, raw, dump_module, syntax, true, debuginfo, binary, llvm_options, params)
101✔
375
    if highlighting[:native] && get(io, :color, false)::Bool
92✔
376
        print_native(io, d)
3✔
377
    else
378
        print(io, d)
89✔
379
    end
380
end
381
code_native(io::IO, @nospecialize(argtypes::Union{Tuple,Type{<:Tuple}}); kwargs...) = code_native(io, ArgInfo(argtypes); kwargs...)
30✔
382
code_native(io::IO, @nospecialize(f), @nospecialize(types=Base.default_tt(f)); kwargs...) = code_native(io, ArgInfo(f, types); kwargs...)
196✔
383
code_native(args...; kwargs...) = (@nospecialize; code_native(stdout, args...; kwargs...))
42✔
384

385
## colorized IR and assembly printing
386

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

389
function print_llvm(io::IO, code::String)
111✔
390
    buf = IOBuffer(code)
111✔
391
    for line in eachline(buf)
222✔
392
        m = match(r"^(\s*)((?:[^;]|;\")*)(.*)$", line)
4,144✔
393
        m === nothing && continue
4,144✔
394
        indent, tokens, comment = m.captures
4,144✔
395
        print(io, indent)
8,288✔
396
        print_llvm_tokens(io, tokens)
8,288✔
397
        printstyled_ll(io, comment, :comment)
8,288✔
398
        println(io)
4,144✔
399
    end
4,144✔
400
end
401

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

406
function print_llvm_tokens(io, tokens)
4,144✔
407
    m = match(r"^((?:[^\"\s:]+:|\"[^\"]*\":)?)(\s*)(.*)", tokens)
4,144✔
408
    if m !== nothing
4,144✔
409
        label, spaces, tokens = m.captures
4,144✔
410
        printstyled_ll(io, label, :label, spaces)
8,288✔
411
    end
412
    m = match(r"^(%[^\s=]+)(\s*)=(\s*)(.*)", tokens)
4,144✔
413
    if m !== nothing
4,144✔
414
        result, spaces, spaces2, tokens = m.captures
1,724✔
415
        printstyled_ll(io, result, :variable, spaces)
3,448✔
416
        printstyled_ll(io, '=', :default, spaces2)
3,448✔
417
    end
418
    m = match(r"^([a-z]\w*)(\s*)(.*)", tokens)
4,144✔
419
    if m !== nothing
4,144✔
420
        inst, spaces, tokens = m.captures
1,919✔
421
        iskeyword = occursin(r"^(?:define|declare|type)$", inst) || occursin("=", tokens)
3,823✔
422
        printstyled_ll(io, inst, iskeyword ? :keyword : :instruction, spaces)
3,838✔
423
    end
424

425
    print_llvm_operands(io, tokens)
4,144✔
426
end
427

UNCOV
428
function print_llvm_operands(io, tokens)
×
429
    while !isempty(tokens)
19,872✔
430
        tokens = print_llvm_operand(io, tokens)
8,466✔
431
    end
8,466✔
432
    return tokens
5,703✔
433
end
434

435
function print_llvm_operand(io, tokens)
8,466✔
436
    islabel = false
8,466✔
437
    while !isempty(tokens)
58,724✔
438
        m = match(r"^,(\s*)(.*)", tokens)
25,972✔
439
        if m !== nothing
25,972✔
440
            spaces, tokens = m.captures
3,517✔
441
            printstyled_ll(io, ',', :default, spaces)
7,034✔
442
            break
3,517✔
443
        end
444
        m = match(r"^(\*+|=)(\s*)(.*)", tokens)
22,455✔
445
        if m !== nothing
22,455✔
446
            sym, spaces, tokens = m.captures
48✔
447
            printstyled_ll(io, sym, :default, spaces)
96✔
448
            continue
48✔
449
        end
450
        m = match(r"^(\"[^\"]*\")(\s*)(.*)", tokens)
22,407✔
451
        if m !== nothing
22,407✔
452
            str, spaces, tokens = m.captures
11✔
453
            printstyled_ll(io, str, :variable, spaces)
22✔
454
            continue
11✔
455
        end
456
        m = match(r"^([({\[<])(\s*)(.*)", tokens)
22,396✔
457
        if m !== nothing
22,396✔
458
            bracket, spaces, tokens = m.captures
1,559✔
459
            printstyled_ll(io, bracket, :bracket, spaces)
3,118✔
460
            tokens = print_llvm_operands(io, tokens) # enter
3,118✔
461
            continue
1,559✔
462
        end
463
        m = match(r"^([)}\]>])(\s*)(.*)", tokens)
20,837✔
464
        if m !== nothing
20,837✔
465
            bracket, spaces, tokens = m.captures
1,559✔
466
            printstyled_ll(io, bracket, :bracket, spaces)
3,118✔
467
            break # leave
1,559✔
468
        end
469

470
        m = match(r"^([^\s,*=(){}\[\]<>]+)(\s*)(.*)", tokens)
19,278✔
471
        m === nothing && break
19,278✔
472
        token, spaces, tokens = m.captures
19,278✔
473
        if occursin(llvm_types, token)
19,278✔
474
            printstyled_ll(io, token, :type)
3,428✔
475
            islabel = token == "label"
3,428✔
476
        elseif occursin(llvm_cond, token) # condition code is instruction-level
15,850✔
477
            printstyled_ll(io, token, :instruction)
51✔
478
        elseif occursin(num_regex, token)
15,799✔
479
            printstyled_ll(io, token, :number)
4,500✔
480
        elseif occursin(r"^@.+$", token)
11,299✔
481
            printstyled_ll(io, token, :funcname)
57✔
482
        elseif occursin(r"^%.+$", token)
11,242✔
483
            islabel |= occursin(r"^%[^\d].*$", token) & occursin(r"^\]", tokens)
803✔
484
            printstyled_ll(io, token, islabel ? :label : :variable)
803✔
485
            islabel = false
803✔
486
        elseif occursin(r"^[a-z]\w+$", token)
10,439✔
487
            printstyled_ll(io, token, :keyword)
10,391✔
488
        else
489
            printstyled_ll(io, token, :default)
48✔
490
        end
491
        print(io, spaces)
19,278✔
492
    end
20,896✔
493
    return tokens
8,466✔
494
end
495

496
function print_native(io::IO, code::String, arch::Symbol=sys_arch_category())
174✔
497
    archv = Val(arch)
177✔
498
    buf = IOBuffer(code)
174✔
499
    for line in eachline(buf)
348✔
500
        m = match(r"^(\s*)((?:[^;#/]|#\S|;\"|/[^/])*)(.*)$", line)
254✔
501
        m === nothing && continue
254✔
502
        indent, tokens, comment = m.captures
254✔
503
        print(io, indent)
508✔
504
        print_native_tokens(io, tokens, archv)
254✔
505
        printstyled_ll(io, comment, :comment)
508✔
506
        println(io)
254✔
507
    end
254✔
508
end
509

510
function sys_arch_category()
511
    if Sys.ARCH === :x86_64 || Sys.ARCH === :i686
3✔
512
        :x86
513
    elseif Sys.ARCH === :aarch64 || startswith(string(Sys.ARCH), "arm")
1✔
514
        :arm
515
    else
516
        :unsupported
517
    end
518
end
519

520
print_native_tokens(io, line, ::Val) = print(io, line)
3✔
521

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

527
function print_native_tokens(io, tokens, arch::Union{Val{:x86}, Val{:arm}})
251✔
528
    x86 = arch isa Val{:x86}
251✔
529
    m = match(r"^((?:[^\s:]+:|\"[^\"]+\":)?)(\s*)(.*)", tokens)
251✔
530
    if m !== nothing
251✔
531
        label, spaces, tokens = m.captures
251✔
532
        printstyled_ll(io, label, :label, spaces)
502✔
533
    end
534
    haslabel = false
251✔
535
    m = match(r"^([a-z][\w.]*)(\s*)(.*)", tokens)
251✔
536
    if m !== nothing
251✔
537
        instruction, spaces, tokens = m.captures
174✔
538
        printstyled_ll(io, instruction, :instruction, spaces)
348✔
539
        haslabel = occursin(r"^(?:bl?|bl?\.\w{2,5}|[ct]bn?z)?$", instruction)
174✔
540
    end
541

542
    isfuncname = false
251✔
543
    while !isempty(tokens)
2,518✔
544
        m = match(r"^([,:*])(\s*)(.*)", tokens)
1,008✔
545
        if m !== nothing
1,008✔
546
            sym, spaces, tokens = m.captures
254✔
547
            printstyled_ll(io, sym, :default, spaces)
508✔
548
            isfuncname = false
254✔
549
            continue
254✔
550
        end
551
        m = match(r"^([(){}\[\]])(\s*)(.*)", tokens)
754✔
552
        if m !== nothing
754✔
553
            bracket, spaces, tokens = m.captures
170✔
554
            printstyled_ll(io, bracket, :bracket, spaces)
340✔
555
            continue
170✔
556
        end
557
        m = match(r"^#([0-9a-fx.-]+)(\s*)(.*)", tokens)
584✔
558
        if !x86 && m !== nothing && occursin(num_regex, m.captures[1])
584✔
559
            num, spaces, tokens = m.captures
26✔
560
            printstyled_ll(io, "#" * num, :number, spaces)
52✔
561
            continue
26✔
562
        end
563

564
        m = match(r"^([^\s,:*(){}\[\]][^\s,:*/(){}\[\]]*)(\s*)(.*)", tokens)
558✔
565
        m === nothing && break
558✔
566
        token, spaces, tokens = m.captures
558✔
567
        if occursin(num_regex, token)
558✔
568
            printstyled_ll(io, token, :number)
46✔
569
        elseif x86 && occursin(x86_ptr, token) || occursin(avx512flags, token)
818✔
570
            printstyled_ll(io, token, :keyword)
61✔
571
            isfuncname = token == "offset"
61✔
572
        elseif !x86 && (occursin(arm_keywords, token) || occursin(arm_cond, token))
596✔
573
            printstyled_ll(io, token, :keyword)
24✔
574
        elseif occursin(r"^L.+$", token)
427✔
575
            printstyled_ll(io, token, :label)
15✔
576
        elseif occursin(r"^\$.+$", token)
412✔
577
            printstyled_ll(io, token, :funcname)
5✔
578
        elseif occursin(r"^%?(?:[a-z][\w.]+|\"[^\"]+\")$", token)
407✔
579
            islabel = haslabel & !occursin(',', tokens)
557✔
580
            printstyled_ll(io, token, islabel ? :label : isfuncname ? :funcname : :variable)
676✔
581
            isfuncname = false
338✔
582
        else
583
            printstyled_ll(io, token, :default)
69✔
584
        end
585
        print(io, spaces)
558✔
586
    end
1,259✔
587
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