1 ----------------------------------------------------------------------------
2 -- LuaJIT compiler dump module.
4 -- Copyright (C) 2005-2014 Mike Pall. All rights reserved.
5 -- Released under the MIT license. See Copyright Notice in luajit.h
6 ----------------------------------------------------------------------------
8 -- This module can be used to debug the JIT compiler itself. It dumps the
9 -- code representations and structures used in various compiler stages.
13 -- luajit -jdump -e "local x=0; for i=1,1e6 do x=x+i end; print(x)"
14 -- luajit -jdump=im -e "for i=1,1000 do for j=1,1000 do end end" | less -R
15 -- luajit -jdump=is myapp.lua | less -R
16 -- luajit -jdump=-b myapp.lua
17 -- luajit -jdump=+aH,myapp.html myapp.lua
18 -- luajit -jdump=ixT,myapp.dump myapp.lua
20 -- The first argument specifies the dump mode. The second argument gives
21 -- the output file name. Default output is to stdout, unless the environment
22 -- variable LUAJIT_DUMPFILE is set. The file is overwritten every time the
25 -- Different features can be turned on or off with the dump mode. If the
26 -- mode starts with a '+', the following features are added to the default
27 -- set of features; a '-' removes them. Otherwise the features are replaced.
29 -- The following dump features are available (* marks the default):
31 -- * t Print a line for each started, ended or aborted trace (see also -jv).
32 -- * b Dump the traced bytecode.
33 -- * i Dump the IR (intermediate representation).
34 -- r Augment the IR with register/stack slots.
35 -- s Dump the snapshot map.
36 -- * m Dump the generated machine code.
37 -- x Print each taken trace exit.
38 -- X Print each taken trace exit and the contents of all registers.
39 -- a Print the IR of aborted traces, too.
41 -- The output format can be set with the following characters:
43 -- T Plain text output.
44 -- A ANSI-colored text output
45 -- H Colorized HTML + CSS output.
47 -- The default output format is plain text. It's set to ANSI-colored text
48 -- if the COLORTERM variable is set. Note: this is independent of any output
49 -- redirection, which is actually considered a feature.
51 -- You probably want to use less -R to enjoy viewing ANSI-colored text from
52 -- a pipe or a file. Add this to your ~/.bashrc: export LESS="-R"
54 ------------------------------------------------------------------------------
56 -- Cache some library functions and objects.
57 local jit
= require("jit")
58 assert(jit
.version_num
== 20100, "LuaJIT core/library version mismatch")
59 local jutil
= require("jit.util")
60 local vmdef
= require("jit.vmdef")
61 local funcinfo
, funcbc
= jutil
.funcinfo
, jutil
.funcbc
62 local traceinfo
, traceir
, tracek
= jutil
.traceinfo
, jutil
.traceir
, jutil
.tracek
63 local tracemc
, tracesnap
= jutil
.tracemc
, jutil
.tracesnap
64 local traceexitstub
, ircalladdr
= jutil
.traceexitstub
, jutil
.ircalladdr
65 local bit
= require("bit")
66 local band
, shl
, shr
, tohex
= bit
.band
, bit
.lshift
, bit
.rshift
, bit
.tohex
67 local sub
, gsub, format = string.sub
, string.gsub, string.format
68 local byte
, char
, rep
= string.byte
, string.char
, string.rep
69 local type, tostring = type, tostring
70 local stdout
, stderr
= io
.stdout
, io
.stderr
72 -- Load other modules on-demand.
75 -- Active flag, output file handle and dump mode.
76 local active
, out
, dumpmode
78 ------------------------------------------------------------------------------
80 local symtabmt
= { __index
= false }
84 -- Fill nested symbol table with per-trace exit stub addresses.
85 local function fillsymtab_tr(tr
, nexit
)
88 if jit
.arch
== "mips" or jit
.arch
== "mipsel" then
89 t
[traceexitstub(tr
, 0)] = "exit"
93 local addr
= traceexitstub(tr
, i
)
94 if addr
< 0 then addr
= addr
+ 2^
32 end
97 local addr
= traceexitstub(tr
, nexit
)
98 if addr
then t
[addr
] = "stack_check" end
101 -- Fill symbol table with trace exit stub addresses.
102 local function fillsymtab(tr
, nexit
)
104 if nexitsym
== 0 then
105 local ircall
= vmdef
.ircall
107 local addr
= ircalladdr(i
)
109 if addr
< 0 then addr
= addr
+ 2^
32 end
114 if nexitsym
== 1000000 then -- Per-trace exit stubs.
115 fillsymtab_tr(tr
, nexit
)
116 elseif nexit
> nexitsym
then -- Shared exit stubs.
117 for i
=nexitsym
,nexit
-1 do
118 local addr
= traceexitstub(i
)
119 if addr
== nil then -- Fall back to per-trace exit stubs.
120 fillsymtab_tr(tr
, nexit
)
121 setmetatable(symtab
, symtabmt
)
125 if addr
< 0 then addr
= addr
+ 2^
32 end
126 t
[addr
] = tostring(i
)
133 local function dumpwrite(s
)
137 -- Disassemble machine code.
138 local function dump_mcode(tr
)
139 local info
= traceinfo(tr
)
140 if not info
then return end
141 local mcode
, addr
, loop
= tracemc(tr
)
142 if not mcode
then return end
143 if not disass
then disass
= require("jit.dis_"..jit
.arch
) end
144 if addr
< 0 then addr
= addr
+ 2^
32 end
145 out
:write("---- TRACE ", tr
, " mcode ", #mcode
, "\n")
146 local ctx
= disass
.create(mcode
, addr
, dumpwrite
)
148 ctx
.symtab
= fillsymtab(tr
, info
.nexit
)
150 symtab
[addr
+loop
] = "LOOP"
152 out
:write("->LOOP:\n")
153 ctx
:disass(loop
, #mcode
-loop
)
154 symtab
[addr
+loop
] = nil
156 ctx
:disass(0, #mcode
)
160 ------------------------------------------------------------------------------
162 local irtype_text
= {
189 local colortype_ansi
= {
216 local function colorize_text(s
, t
)
220 local function colorize_ansi(s
, t
)
221 return format(colortype_ansi
[t
], s
)
224 local irtype_ansi
= setmetatable({},
225 { __index
= function(tab
, t
)
226 local s
= colorize_ansi(irtype_text
[t
], t
); tab
[t
] = s
; return s
; end })
228 local html_escape
= { ["<"] = "<", [">"] = ">", ["&"] = "&", }
230 local function colorize_html(s
, t
)
231 s
= gsub(s
, "[<>&]", html_escape
)
232 return format('<span class="irt_%s">%s</span>', irtype_text
[t
], s
)
235 local irtype_html
= setmetatable({},
236 { __index
= function(tab
, t
)
237 local s
= colorize_html(irtype_text
[t
], t
); tab
[t
] = s
; return s
; end })
239 local header_html
= [[
240 <style type="text/css">
241 background { background: #ffffff; color: #000000; }
246 border: 1px solid #bfcfff;
251 span.irt_str { color: #00a000; }
252 span.irt_thr, span.irt_fun { color: #404040; font-weight: bold; }
253 span.irt_tab { color: #c00000; }
254 span.irt_udt, span.irt_lud { color: #00c0c0; }
255 span.irt_num { color: #4040c0; }
256 span.irt_int, span.irt_i8, span.irt_u8, span.irt_i16, span.irt_u16 { color: #b040b0; }
260 local colorize
, irtype
262 -- Lookup tables to convert some literals into names.
264 ["SLOAD "] = setmetatable({}, { __index
= function(t
, mode
)
266 if band(mode
, 1) ~= 0 then s
= s
.."P" end
267 if band(mode
, 2) ~= 0 then s
= s
.."F" end
268 if band(mode
, 4) ~= 0 then s
= s
.."T" end
269 if band(mode
, 8) ~= 0 then s
= s
.."C" end
270 if band(mode
, 16) ~= 0 then s
= s
.."R" end
271 if band(mode
, 32) ~= 0 then s
= s
.."I" end
275 ["XLOAD "] = { [0] = "", "R", "V", "RV", "U", "RU", "VU", "RVU", },
276 ["CONV "] = setmetatable({}, { __index
= function(t
, mode
)
277 local s
= irtype
[band(mode
, 31)]
278 s
= irtype
[band(shr(mode
, 5), 31)].."."..s
279 if band(mode
, 0x800) ~= 0 then s
= s
.." sext" end
280 local c
= shr(mode
, 14)
281 if c
== 2 then s
= s
.." index" elseif c
== 3 then s
= s
.." check" end
285 ["FLOAD "] = vmdef
.irfield
,
286 ["FREF "] = vmdef
.irfield
,
287 ["FPMATH"] = vmdef
.irfpm
,
288 ["BUFHDR"] = { [0] = "RESET", "APPEND" },
289 ["TOSTR "] = { [0] = "INT", "NUM", "CHAR" },
292 local function ctlsub(c
)
293 if c
== "\n" then return "\\n"
294 elseif c
== "\r" then return "\\r"
295 elseif c
== "\t" then return "\\t"
296 else return format("\\%03d", byte(c
))
300 local function fmtfunc(func
, pc
)
301 local fi
= funcinfo(func
, pc
)
305 return vmdef
.ffnames
[fi
.ffid
]
307 return format("C:%x", fi
.addr
)
313 local function formatk(tr
, idx
)
314 local k
, t
, slot
= tracek(tr
, idx
)
317 if tn
== "number" then
318 if k
== 2^
52+2^
51 then
321 s
= format("%+.14g", k
)
323 elseif tn
== "string" then
324 s
= format(#k
> 20 and '"%.20s"~' or '"%s"', gsub(k
, "%c", ctlsub
))
325 elseif tn
== "function" then
327 elseif tn
== "table" then
328 s
= format("{%p}", k
)
329 elseif tn
== "userdata" then
331 s
= format("userdata:%p", k
)
333 s
= format("[%p]", k
)
334 if s
== "[0x00000000]" then s
= "NULL" end
336 elseif t
== 21 then -- int64_t
337 s
= sub(tostring(k
), 1, -3)
338 if sub(s
, 1, 1) ~= "-" then s
= "+"..s
end
340 s
= tostring(k
) -- For primitives.
342 s
= colorize(format("%-4s", s
), t
)
344 s
= format("%s @%d", s
, slot
)
349 local function printsnap(tr
, snap
)
353 if shr(sn
, 24) == s
then
355 local ref
= band(sn
, 0xffff) - 0x8000 -- REF_BIAS
357 out
:write(formatk(tr
, ref
))
358 elseif band(sn
, 0x80000) ~= 0 then -- SNAP_SOFTFPNUM
359 out
:write(colorize(format("%04d/%04d", ref
, ref
+1), 14))
361 local m
, ot
, op1
, op2
= traceir(tr
, ref
)
362 out
:write(colorize(format("%04d", ref
), band(ot
, 31)))
364 out
:write(band(sn
, 0x10000) == 0 and " " or "|") -- SNAP_FRAME
372 -- Dump snapshots (not interleaved with IR).
373 local function dump_snap(tr
)
374 out
:write("---- TRACE ", tr
, " snapshots\n")
375 for i
=0,1000000000 do
376 local snap
= tracesnap(tr
, i
)
377 if not snap
then break end
378 out
:write(format("#%-3d %04d [ ", i
, snap
[0]))
383 -- Return a register name or stack slot for a rid/sp location.
384 local function ridsp_name(ridsp
, ins
)
385 if not disass
then disass
= require("jit.dis_"..jit
.arch
) end
386 local rid
, slot
= band(ridsp
, 0xff), shr(ridsp
, 8)
387 if rid
== 253 or rid
== 254 then
388 return (slot
== 0 or slot
== 255) and " {sink" or format(" {%04d", ins
-slot
)
390 if ridsp
> 255 then return format("[%x]", slot
*4) end
391 if rid
< 128 then return disass
.regname(rid
) end
395 -- Dump CALL* function ref and return optional ctype.
396 local function dumpcallfunc(tr
, ins
)
399 local m
, ot
, op1
, op2
= traceir(tr
, ins
)
400 if band(ot
, 31) == 0 then -- nil type means CARG(func, ctype).
402 ctype
= formatk(tr
, op2
)
406 out
:write(format("[0x%x](", tonumber((tracek(tr
, ins
)))))
408 out
:write(format("%04d (", ins
))
413 -- Recursively gather CALL* args and dump them.
414 local function dumpcallargs(tr
, ins
)
416 out
:write(formatk(tr
, ins
))
418 local m
, ot
, op1
, op2
= traceir(tr
, ins
)
419 local oidx
= 6*shr(ot
, 8)
420 local op
= sub(vmdef
.irnames
, oidx
+1, oidx
+6)
421 if op
== "CARG " then
422 dumpcallargs(tr
, op1
)
424 out
:write(" ", formatk(tr
, op2
))
426 out
:write(" ", format("%04d", op2
))
429 out
:write(format("%04d", ins
))
434 -- Dump IR and interleaved snapshots.
435 local function dump_ir(tr
, dumpsnap
, dumpreg
)
436 local info
= traceinfo(tr
)
437 if not info
then return end
438 local nins
= info
.nins
439 out
:write("---- TRACE ", tr
, " IR\n")
440 local irnames
= vmdef
.irnames
441 local snapref
= 65536
444 snap
= tracesnap(tr
, 0)
449 if ins
>= snapref
then
451 out
:write(format(".... SNAP #%-3d [ ", snapno
))
453 out
:write(format(".... SNAP #%-3d [ ", snapno
))
457 snap
= tracesnap(tr
, snapno
)
458 snapref
= snap
and snap
[0] or 65536
460 local m
, ot
, op1
, op2
, ridsp
= traceir(tr
, ins
)
461 local oidx
, t
= 6*shr(ot
, 8), band(ot
, 31)
462 local op
= sub(irnames
, oidx
+1, oidx
+6)
463 if op
== "LOOP " then
465 out
:write(format("%04d ------------ LOOP ------------\n", ins
))
467 out
:write(format("%04d ------ LOOP ------------\n", ins
))
469 elseif op
~= "NOP " and op
~= "CARG " and
470 (dumpreg
or op
~= "RENAME") then
471 local rid
= band(ridsp
, 255)
473 out
:write(format("%04d %-6s", ins
, ridsp_name(ridsp
, ins
)))
475 out
:write(format("%04d ", ins
))
477 out
:write(format("%s%s %s %s ",
478 (rid
== 254 or rid
== 253) and "}" or
479 (band(ot
, 128) == 0 and " " or ">"),
480 band(ot
, 64) == 0 and " " or "+",
482 local m1
, m2
= band(m
, 3), band(m
, 3*4)
483 if sub(op
, 1, 4) == "CALL" then
485 if m2
== 1*4 then -- op2 == IRMlit
486 out
:write(format("%-10s (", vmdef
.ircall
[op2
]))
488 ctype
= dumpcallfunc(tr
, op2
)
490 if op1
~= -1 then dumpcallargs(tr
, op1
) end
492 if ctype
then out
:write(" ctype ", ctype
) end
493 elseif op
== "CNEW " and op2
== -1 then
494 out
:write(formatk(tr
, op1
))
495 elseif m1
~= 3 then -- op1 != IRMnone
497 out
:write(formatk(tr
, op1
))
499 out
:write(format(m1
== 0 and "%04d" or "#%-3d", op1
))
501 if m2
~= 3*4 then -- op2 != IRMnone
502 if m2
== 1*4 then -- op2 == IRMlit
503 local litn
= litname
[op
]
504 if litn
and litn
[op2
] then
505 out
:write(" ", litn
[op2
])
506 elseif op
== "UREFO " or op
== "UREFC " then
507 out
:write(format(" #%-3d", shr(op2
, 8)))
509 out
:write(format(" #%-3d", op2
))
512 out
:write(" ", formatk(tr
, op2
))
514 out
:write(format(" %04d", op2
))
523 out
:write(format(".... SNAP #%-3d [ ", snapno
))
525 out
:write(format(".... SNAP #%-3d [ ", snapno
))
531 ------------------------------------------------------------------------------
536 -- Format trace error message.
537 local function fmterr(err
, info
)
538 if type(err
) == "number" then
539 if type(info
) == "function" then info
= fmtfunc(info
) end
540 err
= format(vmdef
.traceerr
[err
], info
)
545 -- Dump trace states.
546 local function dump_trace(what
, tr
, func
, pc
, otr
, oex
)
547 if what
== "stop" or (what
== "abort" and dumpmode
.a
) then
548 if dumpmode
.i
then dump_ir(tr
, dumpmode
.s
, dumpmode
.r
and what
== "stop")
549 elseif dumpmode
.s
then dump_snap(tr
) end
550 if dumpmode
.m
then dump_mcode(tr
) end
552 if what
== "start" then
553 if dumpmode
.H
then out
:write('<pre class="ljdump">\n') end
554 out
:write("---- TRACE ", tr
, " ", what
)
555 if otr
then out
:write(" ", otr
, "/", oex
) end
556 out
:write(" ", fmtfunc(func
, pc
), "\n")
557 elseif what
== "stop" or what
== "abort" then
558 out
:write("---- TRACE ", tr
, " ", what
)
559 if what
== "abort" then
560 out
:write(" ", fmtfunc(func
, pc
), " -- ", fmterr(otr
, oex
), "\n")
562 local info
= traceinfo(tr
)
563 local link
, ltype
= info
.link
, info
.linktype
564 if link
== tr
or link
== 0 then
565 out
:write(" -> ", ltype
, "\n")
566 elseif ltype
== "root" then
567 out
:write(" -> ", link
, "\n")
569 out
:write(" -> ", link
, " ", ltype
, "\n")
572 if dumpmode
.H
then out
:write("</pre>\n\n") else out
:write("\n") end
574 out
:write("---- TRACE ", what
, "\n\n")
579 -- Dump recorded bytecode.
580 local function dump_record(tr
, func
, pc
, depth
, callee
)
581 if depth
~= recdepth
then
583 recprefix
= rep(" .", depth
)
587 line
= bcline(func
, pc
, recprefix
)
588 if dumpmode
.H
then line
= gsub(line
, "[<>&]", html_escape
) end
590 line
= "0000 "..recprefix
.." FUNCC \n"
594 out
:write(sub(line
, 1, -2), " ; ", fmtfunc(func
), "\n")
598 if pc
>= 0 and band(funcbc(func
, pc
), 0xff) < 16 then -- ORDER BC
599 out
:write(bcline(func
, pc
+1, recprefix
)) -- Write JMP for cond.
603 ------------------------------------------------------------------------------
605 -- Dump taken trace exits.
606 local function dump_texit(tr
, ex
, ngpr
, nfpr
, ...)
607 out
:write("---- TRACE ", tr
, " exit ", ex
, "\n")
610 if jit
.arch
== "x64" then
612 out
:write(format(" %016x", regs
[i
]))
613 if i
% 4 == 0 then out
:write("\n") end
617 out
:write(" ", tohex(regs
[i
]))
618 if i
% 8 == 0 then out
:write("\n") end
621 if jit
.arch
== "mips" or jit
.arch
== "mipsel" then
623 out
:write(format(" %+17.14g", regs
[ngpr
+i
]))
624 if i
% 8 == 7 then out
:write("\n") end
628 out
:write(format(" %+17.14g", regs
[ngpr
+i
]))
629 if i
% 4 == 0 then out
:write("\n") end
635 ------------------------------------------------------------------------------
637 -- Detach dump handlers.
638 local function dumpoff()
641 jit
.attach(dump_texit
)
642 jit
.attach(dump_record
)
643 jit
.attach(dump_trace
)
644 if out
and out
~= stdout
and out
~= stderr
then out
:close() end
649 -- Open the output file and attach dump handlers.
650 local function dumpon(opt
, outfile
)
651 if active
then dumpoff() end
653 local colormode
= os
.getenv("COLORTERM") and "A" or "T"
655 opt
= gsub(opt
, "[TAH]", function(mode
) colormode
= mode
; return ""; end)
658 local m
= { t
=true, b
=true, i
=true, m
=true, }
659 if opt
and opt
~= "" then
660 local o
= sub(opt
, 1, 1)
661 if o
~= "+" and o
~= "-" then m
= {} end
662 for i
=1,#opt
do m
[sub(opt
, i
, i
)] = (o
~= "-") end
666 if m
.t
or m
.b
or m
.i
or m
.s
or m
.m
then
667 jit
.attach(dump_trace
, "trace")
670 jit
.attach(dump_record
, "record")
671 if not bcline
then bcline
= require("jit.bc").line
end
674 jit
.attach(dump_texit
, "texit")
677 if not outfile
then outfile
= os
.getenv("LUAJIT_DUMPFILE") end
679 out
= outfile
== "-" and stdout
or assert(io
.open(outfile
, "w"))
685 if colormode
== "A" then
686 colorize
= colorize_ansi
688 elseif colormode
== "H" then
689 colorize
= colorize_html
691 out
:write(header_html
)
693 colorize
= colorize_text
700 -- Public module functions.
704 start
= dumpon
-- For -j command line option.