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

JuliaLang / julia / #37918

28 Sep 2024 11:02PM UTC coverage: 86.484% (-0.8%) from 87.243%
#37918

push

local

web-flow
add --trim option for generating smaller binaries (#55047)

This adds a command line option `--trim` that builds images where code
is only included if it is statically reachable from methods marked using
the new function `entrypoint`. Compile-time errors are given for call
sites that are too dynamic to allow trimming the call graph (however
there is an `unsafe` option if you want to try building anyway to see
what happens).

The PR has two other components. One is changes to Base that generally
allow more code to be compiled in this mode. These changes will either
be merged in separate PRs or moved to a separate part of the workflow
(where we will build a custom system image for this purpose). The branch
is set up this way to make it easy to check out and try the
functionality.

The other component is everything in the `juliac/` directory, which
implements a compiler driver script based on this new option, along with
some examples and tests. This will eventually become a package "app"
that depends on PackageCompiler and provides a CLI for all of this
stuff, so it will not be merged here. To try an example:

```
julia contrib/juliac.jl --output-exe hello --trim test/trimming/hello.jl
```

When stripped the resulting executable is currently about 900kb on my
machine.

Also includes a lot of work by @topolarity

---------

Co-authored-by: Gabriel Baraldi <baraldigabriel@gmail.com>
Co-authored-by: Tim Holy <tim.holy@gmail.com>
Co-authored-by: Cody Tapscott <topolarity@tapscott.me>

2 of 10 new or added lines in 4 files covered. (20.0%)

1269 existing lines in 41 files now uncovered.

77375 of 89467 relevant lines covered (86.48%)

15490193.44 hits per line

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

33.86
/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 = Core.Compiler.typeinf_code(interp, match, optimize)
4✔
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
        if src isa Core.CodeInfo
2✔
170
            print_warntype_codeinfo(io, src, src.rettype, nargs; lineprinter)
2✔
171
        else
172
            println(io, "  inference not successful")
×
173
        end
174
    end
2✔
175
    nothing
2✔
176
end
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,
96✔
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")
96✔
195
    if isa(f, Core.Builtin)
89✔
UNCOV
196
        throw(ArgumentError("argument is not a generic function"))
×
197
    end
198
    warning = ""
89✔
199
    # get the MethodInstance for the method match
200
    if !isa(f, Core.OpaqueClosure)
89✔
201
        world = Base.get_world_counter()
87✔
202
        match = Base._which(signature_type(f, t); world)
87✔
203
        mi = Core.Compiler.specialize_method(match)
87✔
204
        # TODO: use jl_is_cacheable_sig instead of isdispatchtuple
205
        isdispatchtuple(mi.specTypes) || (warning = GENERIC_SIG_WARNING)
87✔
206
    else
207
        world = UInt64(f.world)
2✔
208
        tt = Base.to_tuple_type(t)
2✔
209
        if !isdefined(f.source, :source)
2✔
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
2✔
213
            mi = f.source.specializations::Core.MethodInstance
2✔
214
            Core.Compiler.hasintersect(typeof(f).parameters[1], tt) || (warning = OC_MISMATCH_WARNING)
2✔
215
        else
216
            mi = Core.Compiler.specialize_method(f.source, Tuple{typeof(f.captures), tt.parameters...}, Core.svec())
×
217
            isdispatchtuple(mi.specTypes) || (warning = GENERIC_SIG_WARNING)
×
218
        end
219
    end
220
    # get the code for it
221
    if debuginfo === :default
89✔
222
        debuginfo = :source
45✔
223
    elseif debuginfo !== :source && debuginfo !== :none
44✔
224
        throw(ArgumentError("'debuginfo' must be either :source or :none"))
×
225
    end
226
    if native
89✔
227
        if syntax !== :att && syntax !== :intel
1✔
228
            throw(ArgumentError("'syntax' must be either :intel or :att"))
×
229
        end
230
        str = ""
1✔
231
        if !dump_module
1✔
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)
1✔
234
        end
235
        if isempty(str)
1✔
236
            # if that failed (or we want metadata), use LLVM to generate more accurate assembly output
UNCOV
237
            if !isa(f, Core.OpaqueClosure)
×
UNCOV
238
                src = Core.Compiler.typeinf_code(Core.Compiler.NativeInterpreter(world), mi, true)
×
239
            else
240
                src, rt = Base.get_oc_code_rt(f, tt, true)
×
241
            end
UNCOV
242
            src isa Core.CodeInfo || error("failed to infer source for $mi")
×
UNCOV
243
            str = _dump_function_native_assembly(mi, src, wrapper, syntax, debuginfo, binary, raw, params)
×
244
        end
245
    else
246
        if !isa(f, Core.OpaqueClosure)
88✔
247
            src = Core.Compiler.typeinf_code(Core.Compiler.NativeInterpreter(world), mi, true)
172✔
248
        else
249
            src, rt = Base.get_oc_code_rt(f, tt, true)
2✔
250
        end
251
        src isa Core.CodeInfo || error("failed to infer source for $mi")
88✔
252
        str = _dump_function_llvm(mi, src, wrapper, !raw, dump_module, optimize, debuginfo, params)
88✔
253
    end
254
    str = warning * str
89✔
255
    return str
89✔
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,
1✔
262
                                    syntax::Ptr{UInt8}, debuginfo::Ptr{UInt8},
263
                                    binary::Bool)::Ref{String}
264
    return str
1✔
265
end
266

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

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

285
function _dump_function_llvm(
88✔
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}()
88✔
291
    @ccall jl_get_llvmf_defn(llvmf_dump::Ptr{LLVMFDump}, mi::Any, src::Any,
88✔
292
                             wrapper::Bool, optimize::Bool, params::CodegenParams)::Cvoid
293
    llvmf_dump[].f == C_NULL && error("could not compile the specified method")
88✔
294
    str = @ccall jl_dump_function_ir(llvmf_dump::Ptr{LLVMFDump}, strip_ir_metadata::Bool,
88✔
295
                                     dump_module::Bool, debuginfo::Ptr{UInt8})::Ref{String}
296
    return str
88✔
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));
47✔
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)
47✔
316
    if highlighting[:llvm] && get(io, :color, false)::Bool
45✔
UNCOV
317
        print_llvm(io, d)
×
318
    else
319
        print(io, d)
45✔
320
    end
321
end
322
code_llvm(args...; kwargs...) = (@nospecialize; code_llvm(stdout, args...; kwargs...))
6✔
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));
3✔
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)
3✔
343
    if highlighting[:native] && get(io, :color, false)::Bool
1✔
UNCOV
344
        print_native(io, d)
×
345
    else
346
        print(io, d)
1✔
347
    end
348
end
349
code_native(args...; kwargs...) = (@nospecialize; code_native(stdout, args...; kwargs...))
4✔
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

UNCOV
355
function print_llvm(io::IO, code::String)
×
UNCOV
356
    buf = IOBuffer(code)
×
UNCOV
357
    for line in eachline(buf)
×
UNCOV
358
        m = match(r"^(\s*)((?:[^;]|;\")*)(.*)$", line)
×
UNCOV
359
        m === nothing && continue
×
UNCOV
360
        indent, tokens, comment = m.captures
×
UNCOV
361
        print(io, indent)
×
UNCOV
362
        print_llvm_tokens(io, tokens)
×
UNCOV
363
        printstyled_ll(io, comment, :comment)
×
UNCOV
364
        println(io)
×
UNCOV
365
    end
×
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

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

UNCOV
391
    print_llvm_operands(io, tokens)
×
392
end
393

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

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

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

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

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

UNCOV
486
print_native_tokens(io, line, ::Val) = print(io, line)
×
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

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

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

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

© 2025 Coveralls, Inc