Repository: cloudflare/loom
Branch: trunk
Commit: f8634373af36
Files: 6
Total size: 38.2 KB
Directory structure:
gitextract_k1j9jgsu/
├── .github/
│ └── workflows/
│ └── semgrep.yml
├── COPYRIGHT
├── README.md
├── jit/
│ └── loom.lua
├── loom.html
└── sample.lua
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/semgrep.yml
================================================
on:
pull_request: {}
workflow_dispatch: {}
push:
branches:
- main
- master
schedule:
- cron: '0 0 * * *'
name: Semgrep config
jobs:
semgrep:
name: semgrep/ci
runs-on: ubuntu-20.04
env:
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
SEMGREP_URL: https://cloudflare.semgrep.dev
SEMGREP_APP_URL: https://cloudflare.semgrep.dev
SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version
container:
image: returntocorp/semgrep
steps:
- uses: actions/checkout@v3
- run: semgrep ci
================================================
FILE: COPYRIGHT
================================================
The MIT License (MIT)
Copyright (c) 2016-2017 CloudFlare
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
LOOM
====
It's a replacement / enhancement of the `-jdump` option included in LuaJIT.
As a command line argument
===
Just put it in a `jit/` directory within `package.path` or `$LUA_PATH`, typically `'/usr/local/share/luajit-2.1..../jit/'`; but it also works in `'/usr/local/share/lua/5.1/jit/'` or even `'./jit/'`. Then it can be used as an argument to LuaJIT in the form:
**`-jloom[=<tmpl>[,<out>]]`**
`<tmpl>` is a template file (default `'loom.html'`) and `<out>` is an output file name (default `io.stdout`).
Lua API
===
If you want to report traces on just part of your code, it's better to use it explicitly.
**`local loom = require 'jit.loom'`**
As any module, you have to `require()` it first.
**`loom.on()`**
Starts recording all JIT events and traces.
**`traces, funcs = loom.off()`**
**`report = loom.off([f [, ...]])`**
Stops recording and performs any processing and cross references needed to actually generate a report.
Called without any arguments, returns two Lua tables, one with the processed trace information and a second one with all the functions involved in those traces execution.
The second form is equivalent to
do
local traces, funcs = loom.off()
report = f(traces, funcs, ...)
end
That is, both return values (the `traces` and `funcs` arrays) are passed to the given function `f`, together with any extra argument, and returns any return value(s) of `f`.
**`loom.start(tmpl, out)`**
Implements the `-jloom[=tmpl[,out]]` option. The `tmpl` argument is passed to `loom.template()` to create a reporting function. If omitted, defaults to `'loom.html'`. The `out` parameter is either a writeable open file or a file name where the report is written into (after formatting by the template), defaults to `io.stdout`. When the Lua VM is terminated normally, `loom.off()` is called with the reporting function created by the given template.,
### Utility Functions
There are some functions included in the `loom` package to help formatting a report.
**`f = loom.template(tmpl)`**
The string `tmpl` is a report template using the template syntax described below. If it doesn't contain any line break, is interpreted as a pathname to read the template from a text file.
The template is compiled into a Lua function that takes some arguments (named with `{@ name ...}` tags) and outputs the result as a string.
**`loom.annotated(funcs, traces)`**
Returns an annotated listing of the source code of the given `funcs` and `traces` arrays.
**`loom.allipairs(t)`**
Like `ipairs(t)`, but stops at `table.maxn(t)` instead of the first `nil` value.
**`loom.sortedpairs(t)`**
Returns an iterator that visits the same pairs as `pairs(t)`, but sorted by keys.
Template syntax
===
The included template implementation is based on Danila Poyarkov's [lua-template](https://github.com/dannote/lua-template), with a syntax more like Django's or Handlebar's, to make it more friendly to editors that help with HTML content.
**`{% lua code %}`**
Embeds any Lua code
**`{{ expression }}`**
Outputs the result of the Lua expression, with the `&`, `"`, `<` and `>` characters escaped.
**`{{= expression }}`**
Outputs the result of the Lua expression verbatim, without any character escaping.
**`{{: 'fmt', args, ... }}`**
Outputs the result of `string.format(fmt, args, ...)` without any escaping.
**`{@ name ... }`**
Defines template argument names. Each `name` must be a valid Lua variable name (that is, a sequence of letters, numbers or underscores not beginning with a number), separated by commas or spaces (or any non-alfanumeric-underscore character).
Included Template
===
The included `loom.html` template renders the trace report as an HTML document. It's divided in two sections: a Sourcecode -> Bytecode -> Traces one, and a list of traces, with the Bytecode -> IR -> mcode progression for each one.
1.- Source list
---
For each function that appears in the traces, the source code is shown on the left column (if the source was in a file) and the bytecode at right of it with a random background color (a different one for each function).
The bytecode is shown in the order it appears in memory, so some source lines are in a different order as in the original source or repeated, for example the opening of a `for` loop appears again (in a lighter gray) in the bottom of the loop.
At the right of the bytecode, there are links of the form `<tr>/<seq>`, showing some trace executed it at some sequential number. For example `3/4` means it's the fourth bytecode executed by trace #3. A single bytecode can appear several times in the same trace (for example, when unrolling a loop). Each trace have assigned a random background color.
Trace start and abort events are also marked to the right of the relevant bytecode, with `[n=xx]` notes telling how many times they happened until the trace gets compiled or blacklisted.
2.- Trace list
---
Each trace gets a three-column table framed on the same color as the respective links in the previous section.
The first column lists the bytecode in the order it was executed. On the left there are links that go back to the same bytecode on the previous section. The bytecodes keep the same background colour of the function. Clicking on the column title toggles source code annotations.
The second column lists the IR code generated by the trace. While the total semantics is supposed to be maintained, there's no direct correspondence between IR and bytecode instructions. For example, as much code as possible is moved before the start of an inner loop, and compilable library functions are just checked (to assure they're the right functions) and IR code is emitted instead of a call to the function.
Snapshot points are inserted in the IR code, but the snapshot content isn't shown by default. To see them, either hover the mouse over the snapshot or click in the column title to reveal all at the same time.
The third column is the generated mcode that is natively executed by the processor. Exit points are labelled by the snapshot number or a trace number if a later trace patched itself in.
Examples
===
The 'sample.lua' file includes some small code snippets to play with. For example, the comments about `-jv` option show:
luajit -jv -e "for i=1,1000 do for j=1,1000 do end end"
To output just two lines (one per trace). Changing to `-jdump` results in:
---- TRACE 1 start (command line):1
0009 FORL 4 => 0009
---- TRACE 1 IR
0001 int SLOAD #5 CI
0002 + int ADD 0001 +1
0003 > int LE 0002 +1000
0004 ------ LOOP ------------
0005 + int ADD 0002 +1
0006 > int LE 0005 +1000
0007 int PHI 0002 0005
---- TRACE 1 mcode 47
0bcbffd1 mov dword [0x41a94410], 0x1
0bcbffdc cvttsd2si ebp, [rdx+0x20]
0bcbffe1 add ebp, +0x01
0bcbffe4 cmp ebp, 0x3e8
0bcbffea jg 0x0bcb0014 ->1
->LOOP:
0bcbfff0 add ebp, +0x01
0bcbfff3 cmp ebp, 0x3e8
0bcbfff9 jle 0x0bcbfff0 ->LOOP
0bcbfffb jmp 0x0bcb001c ->3
---- TRACE 1 stop -> loop
---- TRACE 2 start 1/3 (command line):1
0010 FORL 0 => 0005
0005 KSHORT 4 1
0006 KSHORT 5 1000
0007 KSHORT 6 1
0008 JFORI 4 => 0010
---- TRACE 2 IR
0001 num SLOAD #1 I
0002 num ADD 0001 +1
0003 > num LE 0002 +1000
---- TRACE 2 mcode 81
0bcbff79 mov dword [0x41a94410], 0x2
0bcbff84 movsd xmm6, [0x403382b8]
0bcbff8d movsd xmm5, [0x403382c8]
0bcbff96 movsd xmm7, [rdx]
0bcbff9a addsd xmm7, xmm6
0bcbff9e ucomisd xmm5, xmm7
0bcbffa2 jb 0x0bcb0014 ->1
0bcbffa8 movsd [rdx+0x38], xmm6
0bcbffad movsd [rdx+0x30], xmm6
0bcbffb2 movsd [rdx+0x28], xmm5
0bcbffb7 movsd [rdx+0x20], xmm6
0bcbffbc movsd [rdx+0x18], xmm7
0bcbffc1 movsd [rdx], xmm7
0bcbffc5 jmp 0x0bcbffd1
---- TRACE 2 stop -> 1
To recreate under loom, try:
luajit -jloom -e "require('sample').lulu()" > out.html
And open the resulting `out.html` with a browser to see the same thing with nice colours and links to help following how the traces flow together.

================================================
FILE: jit/loom.lua
================================================
local bit = require 'bit'
local jutil = require 'jit.util'
local vmdef = require 'jit.vmdef'
local bc = require 'jit.bc'
local disass = require('jit.dis_'..jit.arch)
local band, shr = bit.band, bit.rshift
local inf = tonumber('inf')
local function pushf(t, f, ...)
if select('#', ...) > 0 then
f = f:format(...)
end
t[#t+1] = f
return f
end
local function allipairs(t, start)
start = start or 1
local maxn = table.maxn(t)
return function (t, k) -- luacheck: ignore t
repeat
k = k + 1
until t[k] ~= nil or k > maxn
return k <= maxn and k or nil, t[k]
end, t, start-1
end
local function sortedpairs(t, emptyelem)
if emptyelem ~= nil and next(t) == nil then
local done = false
return function ()
if not done then
done = true
return emptyelem
end
end
end
local t2, map = {}, {}
for k in pairs(t) do
local sk = type(k) == 'number' and ('%20.6f'):format(k) or tostring(k)
t2[#t2+1] = sk
map[sk] = k
end
table.sort(t2)
local i = 1
return function ()
local k = map[t2[i]]
i = i+1
return k, t[k]
end
end
-- copied from jit.dump
local function fmtfunc(func, pc)
local fi = jutil.funcinfo(func, pc)
if fi.loc then
return fi.loc
elseif fi.ffid then
return vmdef.ffnames[fi.ffid]
elseif fi.addr then
return ("C:%x"):format(fi.addr)
else
return "(?)"
end
end
-----------
local function bcline(func, pc, prefix)
local l
if pc >= 0 then
l = bc.line(func, pc, prefix)
if not l then return l end
else
l = "0000 "..prefix.." FUNCC \n"
end
l = l:gsub('%s+$', '')
return l
end
local function func_bc(func, o)
o = o or {}
o[func] = jutil.funcinfo(func)
if o[func].children then
for n = -1, -inf, -1 do
local k = jutil.funck(func, n)
if not k then break end
if type(k) == 'proto' then func_bc(k, o) end
end
end
o[func].func = func
o[func].bytecode = {}
if not o[func].addr then
local target = bc.targets(func)
for pc = 1, inf do
local s = bcline (func, pc, target[pc] and "=>")
if not s then break end
local fi_sub = jutil.funcinfo(func, pc)
o[func].bytecode[pc] = {fi_sub.currentline, s}
end
end
return o
end
--------------------------------------
-- tracing
-- copied from jit/dump.lua
local symtabmt = { __index = false }
local symtab = {}
local nexitsym = 0
-- Fill nested symbol table with per-trace exit stub addresses.
local function fillsymtab_tr(tr, nexit)
local t = {}
symtabmt.__index = t
if jit.arch == "mips" or jit.arch == "mipsel" then
t[jutil.traceexitstub(tr, 0)] = "exit"
return
end
for i=0,nexit-1 do
local addr = jutil.traceexitstub(tr, i)
if addr < 0 then addr = addr + 2^32 end
t[addr] = tostring(i)
end
local addr = jutil.traceexitstub(tr, nexit)
if addr then t[addr] = "stack_check" end
end
-- Fill symbol table with trace exit stub addresses.
local function fillsymtab(tr, nexit)
local t = symtab
if nexitsym == 0 then
local ircall = vmdef.ircall
for i=0,#ircall do
local addr = jutil.ircalladdr(i)
if addr and addr ~= 0 then
if addr < 0 then addr = addr + 2^32 end
t[addr] = ircall[i]
end
end
end
if nexitsym == 1000000 then -- Per-trace exit stubs.
fillsymtab_tr(tr, nexit)
elseif nexit > nexitsym then -- Shared exit stubs.
for i=nexitsym,nexit-1 do
local addr = jutil.traceexitstub(i)
if addr == nil then -- Fall back to per-trace exit stubs.
fillsymtab_tr(tr, nexit)
setmetatable(symtab, symtabmt)
nexit = 1000000
break
end
if addr < 0 then addr = addr + 2^32 end
t[addr] = tostring(i)
end
nexitsym = nexit
end
return t
end
-- Disassemble machine code.
local function dump_mcode(tr)
local o = {}
local info = jutil.traceinfo(tr)
if not info then return end
local mcode, addr, loop = jutil.tracemc(tr)
if not mcode then return end
if addr < 0 then addr = addr + 2^32 end
local ctx = disass.create(mcode, addr, function (s) pushf(o, s) end)
ctx.hexdump = 0
ctx.symtab = fillsymtab(tr, info.nexit)
if loop ~= 0 then
symtab[addr+loop] = "LOOP"
ctx:disass(0, loop)
pushf (o, "->LOOP:\n")
ctx:disass(loop, #mcode-loop)
symtab[addr+loop] = nil
else
ctx:disass(0, #mcode)
end
return table.concat(o)
end
local irtype = {
[0] = "nil",
"fal",
"tru",
"lud",
"str",
"p32",
"thr",
"pro",
"fun",
"p64",
"cdt",
"tab",
"udt",
"flt",
"num",
"i8 ",
"u8 ",
"i16",
"u16",
"int",
"u32",
"i64",
"u64",
"sfp",
}
-- Lookup tables to convert some literals into names.
local litname = {
["SLOAD "] = setmetatable({}, { __index = function(t, mode)
local s = ""
if band(mode, 1) ~= 0 then s = s.."P" end
if band(mode, 2) ~= 0 then s = s.."F" end
if band(mode, 4) ~= 0 then s = s.."T" end
if band(mode, 8) ~= 0 then s = s.."C" end
if band(mode, 16) ~= 0 then s = s.."R" end
if band(mode, 32) ~= 0 then s = s.."I" end
t[mode] = s
return s
end}),
["XLOAD "] = { [0] = "", "R", "V", "RV", "U", "RU", "VU", "RVU", },
["CONV "] = setmetatable({}, { __index = function(t, mode)
local s = irtype[band(mode, 31)]
s = irtype[band(shr(mode, 5), 31)].."."..s
if band(mode, 0x800) ~= 0 then s = s.." sext" end
local c = shr(mode, 14)
if c == 2 then s = s.." index" elseif c == 3 then s = s.." check" end
t[mode] = s
return s
end}),
["FLOAD "] = vmdef.irfield,
["FREF "] = vmdef.irfield,
["FPMATH"] = vmdef.irfpm,
["BUFHDR"] = { [0] = "RESET", "APPEND" },
["TOSTR "] = { [0] = "INT", "NUM", "CHAR" },
}
local function ctlsub(c)
if c == "\n" then return "\\n"
elseif c == "\r" then return "\\r"
elseif c == "\t" then return "\\t"
else return ("\\%03d"):format(c:byte())
end
end
local function formatk(tr, idx)
local k, t, slot = jutil.tracek(tr, idx)
local tn = type(k)
local s
if tn == "number" then
if k == 2^52+2^51 then
s = "bias"
else
s = ("%+.14g"):format(k)
end
elseif tn == "string" then
s = (#k > 20 and '"%.20s"~' or '"%s"'):format(k:gsub("%c", ctlsub))
elseif tn == "function" then
s = fmtfunc(k)
elseif tn == "table" then
s = ("{%p}"):format(k)
elseif tn == "userdata" then
if t == 12 then
s = ("userdata:%p"):format(k)
else
s = ("[%p]"):format(k)
if s == "[0x00000000]" then s = "NULL" end
end
elseif t == 21 then -- int64_t
s = tostring(k):sub(1, -3)
if (s):sub(1, 1) ~= "-" then s = "+"..s end
else
s = tostring(k) -- For primitives.
end
s = ("%-4s"):format(s)
if slot then
s = ("%s @%d"):format(s, slot)
end
return s
end
local function printsnap(tr, snap)
local o = {}
local n = 2
for s=0,snap[1]-1 do
local sn = snap[n]
if shr(sn, 24) == s then
n = n + 1
local ref = band(sn, 0xffff) - 0x8000 -- REF_BIAS
if ref < 0 then
pushf(o, formatk(tr, ref))
elseif band(sn, 0x80000) ~= 0 then -- SNAP_SOFTFPNUM
pushf(o, "%04d/%04d", ref, ref+1)
else
pushf(o, "%04d", ref)
end
pushf(o, band(sn, 0x10000) == 0 and " " or "|") -- SNAP_FRAME
else
pushf(o, "---- ")
end
end
pushf(o, "]\n")
return table.concat(o)
end
-- Dump snapshots (not interleaved with IR).
local function dump_snap(tr)
local o = {"---- TRACE "..tr.." snapshots\n"}
for i=0,1000000000 do
local snap = jutil.tracesnap(tr, i)
if not snap then break end
pushf(o, "#%-3d %04d [ ", i, snap[0])
pushf(o, printsnap(tr, snap))
end
return table.concat(o)
end
-- Return a register name or stack slot for a rid/sp location.
local function ridsp_name(ridsp, ins)
local rid, slot = band(ridsp, 0xff), shr(ridsp, 8)
if rid == 253 or rid == 254 then
return (slot == 0 or slot == 255) and " {sink" or (" {%04d"):format(ins-slot)
end
if ridsp > 255 then return ("[%x]"):format(slot*4) end
if rid < 128 then return disass.regname(rid) end
return ""
end
-- Dump CALL* function ref and return optional ctype.
local function dumpcallfunc(o, tr, ins)
local ctype
if ins > 0 then
local m, ot, op1, op2 = jutil.traceir(tr, ins) -- luacheck: ignore m
if band(ot, 31) == 0 then -- nil type means CARG(func, ctype).
ins = op1
ctype = formatk(tr, op2)
end
end
if ins < 0 then
pushf(o, "[0x%x](", tonumber((jutil.tracek(tr, ins))))
else
pushf(o, "%04d (", ins)
end
return ctype
end
-- Recursively gather CALL* args and dump them.
local function dumpcallargs(o, tr, ins)
if ins < 0 then
pushf(o, formatk(tr, ins))
else
local m, ot, op1, op2 = jutil.traceir(tr, ins) -- luacheck: ignore m
local oidx = 6*shr(ot, 8)
local op = vmdef.irnames:sub(oidx+1, oidx+6)
if op == "CARG " then
dumpcallargs(o, tr, op1)
if op2 < 0 then
pushf(o, " "..formatk(tr, op2))
else
pushf(o, " %04d", op2)
end
else
pushf(o, "%04d", ins)
end
end
end
-- Dump IR and interleaved snapshots.
local function dump_ir(tr)
local dumpsnap, dumpreg = true, true
local info = jutil.traceinfo(tr)
if not info then return end
local nins = info.nins
local o = {}
local irnames = vmdef.irnames
local snapref = 65536
local snap, snapno
if dumpsnap then
snap = jutil.tracesnap(tr, 0)
snapref = snap[0]
snapno = 0
end
for ins=1,nins do
if ins >= snapref then
if dumpreg then
pushf (o, ".... SNAP #%-3d [ ", snapno)
else
pushf (o, ".... SNAP #%-3d [ ", snapno)
end
pushf (o, printsnap(tr, snap))
snapno = snapno + 1
snap = jutil.tracesnap(tr, snapno)
snapref = snap and snap[0] or 65536
end
local m, ot, op1, op2, ridsp = jutil.traceir(tr, ins)
local oidx, t = 6*shr(ot, 8), band(ot, 31)
local op = irnames:sub(oidx+1, oidx+6)
if op == "LOOP " then
if dumpreg then
pushf (o, "%04d ------------ LOOP ------------\n", ins)
else
pushf (o, "%04d ------ LOOP ------------\n", ins)
end
elseif op ~= "NOP " and op ~= "CARG " and
(dumpreg or op ~= "RENAME")
then
local rid = band(ridsp, 255)
if dumpreg then
pushf (o, "%04d %-6s", ins, ridsp_name(ridsp, ins))
else
pushf (o, "%04d ", ins)
end
pushf (o, "%s%s %s %s ",
(rid == 254 or rid == 253) and "}" or
(band(ot, 128) == 0 and " " or ">"),
band(ot, 64) == 0 and " " or "+",
irtype[t], op)
local m1, m2 = band(m, 3), band(m, 3*4)
if op:sub(1, 4) == "CALL" then
local ctype
if m2 == 1*4 then -- op2 == IRMlit
pushf (o, "%-10s (", vmdef.ircall[op2])
else
ctype = dumpcallfunc(o, tr, op2)
end
if op1 ~= -1 then dumpcallargs(o, tr, op1) end
pushf(o, ")")
if ctype then pushf(o, " ctype "..ctype) end
elseif op == "CNEW " and op2 == -1 then
pushf(o, formatk(tr, op1))
elseif m1 ~= 3 then -- op1 != IRMnone
if op1 < 0 then
pushf(o, formatk(tr, op1))
else
pushf(o, m1 == 0 and "%04d" or "#%-3d", op1)
end
if m2 ~= 3*4 then -- op2 != IRMnone
if m2 == 1*4 then -- op2 == IRMlit
local litn = litname[op]
if litn and litn[op2] then
pushf(o, " "..litn[op2])
elseif op == "UREFO " or op == "UREFC " then
pushf (o, " #%-3d", shr(op2, 8))
else
pushf (o, " #%-3d", op2)
end
elseif op2 < 0 then
pushf (o, " "..formatk(tr, op2))
else
pushf (o, " %04d", op2)
end
end
end
pushf (o, "\n")
end
end
if snap then
if dumpreg then
pushf (o, ".... SNAP #%-3d [ ", snapno)
else
pushf (o, ".... SNAP #%-3d [ ", snapno)
end
pushf (o, printsnap(tr, snap))
end
return table.concat(o)
end
local function get_bytecode(bc)
return vmdef.bcnames:sub(bc*6+1, bc*6+6):gsub(' ', '')
end
-- Format trace error message.
local function fmterr(err, info)
if type(err) == "number" then
if type(info) == "function" then info = fmtfunc(info) end
err = vmdef.traceerr[err]:format(info)
if type(info) == 'number' and err:find('bytecode') then
err = ("%s (%s)"):format(err, get_bytecode(info))
end
end
return err
end
local function tracelabel(tr, func, pc, otr, oex)
local startex = otr and "("..otr.."/"..oex..") " or ""
local info = jutil.traceinfo(tr)
if not info then return '-- no trace info --' end
local link, ltype = info.link, info.linktype
if ltype == "interpreter" then
return ("%s -- fallback to interpreter\n")
:format(startex)
elseif ltype == "stitch" then
return ("%s %s [%s]\n")
:format(startex, ltype, fmtfunc(func, pc))
elseif link == tr or link == 0 then
return ("%s %s\n")
:format(startex, ltype)
elseif ltype == "root" then
return ("%s -> %d\n")
:format(startex, link)
else
return ("%s -> %d %s\n")
:format(startex, link, ltype)
end
end
----
local loomstart, loomstop
do
local collecting = {[0]=0}
local function append(v)
local c = collecting
c[0] = c[0] + 1
c[c[0]] = v
return c[0]
end
local function collect_trace(what, tr, func, pc, otr, oex)
append({'trace', what, tr, func, pc, otr, oex, ''})
end
local function collect_record(tr, func, pc, depth)
append({'record', tr, func, pc, depth, ''})
end
local function collect_texit(tr, ex, ngpr, nfpr, ...)
append({'texit', tr, ex, ngpr, nfpr, ...})
end
local function do_attachs()
jit.attach(collect_trace, 'trace')
jit.attach(collect_record, 'record')
jit.attach(collect_texit, 'texit')
end
local function do_detachs()
jit.attach(collect_texit)
jit.attach(collect_record)
jit.attach(collect_trace)
end
local traces_data, seen_funcs = {}, {}
local prevtraces = {}
local prevexp_t = {
trace = function (what, tr, func, pc, otr, oex) -- luacheck: ignore func pc
if what == 'start' then
local mcode, addr, loop = jutil.tracemc(tr) -- luacheck: ignore mcode loop
if addr ~= nil then
if otr and oex then
symtab[addr] = ("Trace #%d (exit %d/%d)"):format(tr, otr, oex)
else
symtab[addr] = ("Trace #%d"):format(tr)
end
end
end
end,
record = function() end,
texit = function () end,
}
local function gettrace(tn)
local tr = traces_data[tn]
if tr then return tr end
tr = prevtraces[tn] or {
info = jutil.traceinfo(tn) or {},
ir = dump_ir(tn),
snap = dump_snap(tn),
evt = {},
rec = {},
n = {
trace = 0,
start = 0,
stop = 0,
abort = 0,
flush = 0,
record = 0,
texit = 0,
},
exits = {},
}
tr.mcode = dump_mcode(tn)
traces_data[tn] = tr
return tr
end
local exp_trace_t = {
start = function (tr, func, pc, otr, oex) -- luacheck: ignore func pc
local t = gettrace(tr)
t.parent = t.parent or otr
t.p_exit = t.p_exit or oex
end,
stop = function (tr, func, pc, otr, oex) -- luacheck: ignore tr func pc otr oex
end,
abort = function (tr, func, pc, otr, oex) -- luacheck: ignore func pc
local t = gettrace(tr)
t.err = t.err or fmterr(otr, oex)
end,
flush = function (tr, func, pc, otr, oex) -- luacheck: ignore tr func pc otr oex
symtab, nexitsym = {}, 0
end,
}
local expand_t = {
trace = function (what, tr, func, pc, otr, oex)
seen_funcs[func] = true
local t = gettrace(tr)
t.n.trace = t.n.trace + 1
t.n[what] = (t.n[what] or 0) + 1
t.tracelabel = t.tracelabel or tracelabel(tr, func, pc, otr, oex)
t.otr, t.oex = otr, oex
do
msg = what=='abort' and fmterr(otr, oex) or nil
t.evt[#t.evt +1] = {
what, func, pc,
msg,
}
if msg then
t.rec[#t.rec+1] = {func, pc, ("%s: %q"):format(what, msg)}
end
end
local expf = exp_trace_t[what]
return expf and expf(tr, func, pc, otr, oex)
end,
record = function (tr, func, pc, depth)
local t = gettrace(tr)
t.n.record = t.n.record + 1
seen_funcs[func] = true
t.rec[#t.rec+1] = {func, pc, bcline(func, pc, (' .'):rep(depth))}
if pc >= 0 and band(jutil.funcbc(func, pc), 0xff) < 16 then
t.rec[#t.rec+1] = {func, pc+1, bcline(func, pc+1, (' .'):rep(depth))}
end
end,
texit = function (tr, ex, ngpr, nfpr, ...)
local t = gettrace(tr)
t.n.texit = t.n.texit + 1
t.exits[ex] = (t.exits[ex] or 0) + 1
t.evt[#t.evt+1] = {'exit', ex, ngpr, nfpr, ...}
end,
}
function loomstart(clear)
if clear then
for k, v in pairs(traces_data) do
prevtraces[k] = v
end
traces_data, seen_funcs = {}, {}
collecting = {[0]=0}
end
do_attachs()
end
function loomstop(f, ...)
do_detachs()
for _, v in ipairs(collecting) do
prevexp_t[v[1]](unpack(v, 2, table.maxn(v)))
end
for _, v in ipairs(collecting) do
expand_t[v[1]](unpack(v, 2, table.maxn(v)))
end
for tn, tr in pairs(traces_data) do
gettrace(tr.otr or tn)
gettrace(tr.info.link or tn)
if tr.mcode then
for tns in tr.mcode:gmatch('Trace #(%d+)') do
gettrace(tonumber(tns))
end
end
end
for _, tr in pairs(traces_data) do
for _, rec in ipairs(tr.rec) do
seen_funcs[rec[1]] = true
end
for _, evt in ipairs(tr.evt) do
if type(evt[2]) == 'function' then
seen_funcs[evt[2]] = true
end
end
end
local funcslist = {}
for fun in pairs(seen_funcs) do
for subf, fi in pairs(func_bc(fun)) do
funcslist[subf] = fi
end
end
if f then
return f(traces_data, funcslist, ...)
end
return traces_data, funcslist
end
end
--------------------------------------
local function srclines(fn)
local t, f = {}, io.open(fn)
if f then
for l in f:lines() do
t[#t+1] = l
end
f:close()
end
return t
end
local function defget(t, k, d)
local v = t[k] or d
if v == nil then v = {} end
if t[k] == nil then t[k] = v end
return v
end
local function annotated(funcs, traces)
local ranges = {}
for f, fi in pairs(funcs) do
if type(f)=='function' and fi.source then
local srcranges = defget(ranges, fi.source:gsub('^@', ''), nil)
local lineranges = defget(srcranges, fi.linedefined, nil)
lineranges[f] = fi
end
end
local presources = {}
do
local cmdlinesrc = ''
for k, v in pairs(arg) do
if type(k) == 'number' and v == '-e' then
cmdlinesrc = cmdlinesrc .. arg[k+1]
end
end
local cmdlines = {}
for l in (cmdlinesrc..'\r'):gmatch('(.-)[\r\n]') do
cmdlines[#cmdlines+1] = l
end
presources['=(command line)'] = cmdlines
end
local o = {}
for srcname, srcranges in sortedpairs(ranges) do
o[srcname] = {}
local of, src = o[srcname], presources[srcname] or srclines(srcname)
local lastline = nil
local function newline(i, func, pc, bcl)
local nl = {
i = i,
src = src[i],
func = func,
pc = pc,
bc = bcl or '',
back = type(i)=='number' and i <= lastline,
tr = {},
evt = {},
}
of[#of+1] = nl
return nl
end
for startline, lst in allipairs(srcranges, 0) do
for _, fi in pairs(lst) do
lastline = math.max(0, startline-2)
for pc, l in sortedpairs(fi.bytecode or {}) do
local lnum, bcl = unpack(l)
if lnum > lastline + 5 then
for i = lastline+1, lastline+3 do
newline (i)
end
newline ('...')
for i = lnum-2, lnum-1 do
newline(i, fi.func)
end
else
for i = lastline+1, lnum-1 do
newline(i, fi.func)
end
end
newline(lnum, fi.func, pc, bcl)
lastline = math.max(lastline, lnum)
end
end
end
end
for i, tr in allipairs(traces) do
for j, rec in ipairs(tr.rec) do
local f, pc, bcl = unpack (rec) -- luacheck: ignore bcl
for srcname, osrc in pairs(o) do -- luacheck: ignore srcname
for _, ol in ipairs(osrc) do
if ol.func == f and ol.pc == pc and #ol.bc>0 then
ol.tr[#ol.tr+1] = {i, j}
rec[#rec+1] = {name=srcname, i=ol.i, l=ol.src}
break
end
end
end
end
for _, evt in ipairs(tr.evt) do
local what, func, pc, msg = unpack(evt)
for srcname, osrc in pairs(o) do -- luacheck: ignore srcname
for _, ol in ipairs(osrc) do
if ol.func == func and ol.pc == pc and #ol.bc>0 then
local k = what .. (msg and ': '..msg or '')
ol.evt[k] = (ol.evt[k] or 0) + 1
end
end
end
end
end
return o
end
--------------------------------------
--[[
template compiling function, loosely derived from:
https://github.com/dannote/lua-template/blob/master/template.lua
by Danila Poyarkov
--]]
local template
do
local _esc = {
['&'] = '&',
['<'] = '<',
['>'] = '>',
['"'] = '"',
}
local function escape(s)
return tostring(s or ''):gsub('[><&"]', _esc)
end
function template (tpl)
local tplname = 'tmpl'
if not tpl:find('\n', 1, true) then
tplname = tpl
local f = assert(io.open(tpl))
tpl = assert(f:read('*a'))
f:close()
end
local args = {'_e'}
tpl = tpl:gsub('{@(.-)}', function (argl)
argl:gsub('([_%a][_%w]*)', function (a) args[#args+1] = a return '' end)
return ''
end)
local src = (
'local %s = ... ' ..
'local _o = {} ' ..
'local function _p(x) _o[#_o+1] = tostring(x or "") end ' ..
'local function _fp(f, ...) _p(f:format(...)) end '..
'local function _ep(x) _p(_e(x)) end ' ..
'_p[=[%s]=] ' ..
'return table.concat(_o)')
:format(
table.concat(args, ', '),
tpl
:gsub('[][]=[][]', ']=] _p"%1" _p[=[')
:gsub('{{=', ']=] _p(')
:gsub('{{:', ']=] _fp(')
:gsub('{{', ']=] _ep(')
:gsub('}}', ') _p[=[')
:gsub('{%%', ']=] ')
:gsub('%%}', ' _p[=[')
)
local f = assert(loadstring(src, tplname))
return function (...)
return f(escape, ...)
end
end
end
-------------------------------------
local defer
return {
on = loomstart,
off = loomstop,
start = function (opt, out)
local tmpl = template(opt or 'loom.html')
defer = newproxy(true)
getmetatable(defer).__gc = function() xpcall(function ()
local o = loomstop(tmpl)
out = type(out)=='string' and assert(io.open(out, 'w'))
or out or io.stdout
out:write(o)
end, function(err) print(debug.traceback(err)) end) end
loomstart()
end,
template = template,
annotated = annotated,
allipairs = allipairs,
sortedpairs = sortedpairs,
}
================================================
FILE: loom.html
================================================
<!DOCTYPE html>
<html lang="en">
{@ traces, funcs}
{%
local loom = require 'jit.loom'
local function class(t)
if not t then return '' end
o = {}
for k, v in pairs(t) do
if v then o[#o+1] = k end
end
if #o == 0 then return '' end
return 'class="'..table.concat(o, ' ')..'"'
end
local _ft_, _fndx_ = {}, 0
local function funclabel(f)
if not f then return '' end
if _ft_[f] == nil then
_fndx_ = _fndx_+1
_ft_[f] = ('fn%03d'):format(_fndx_)
end
return _ft_[f]
end
local function lines(s)
s = s or ''
local o = {}
for l in s:gmatch('[^\r\n]+') do
o[#o+1] = l
end
return o
end
local function cols(s, cwl)
local o, start = {}, 1
for i, w in ipairs(cwl) do
o[i] = s:sub(start, start+w-1):gsub('%s+$', '')
start = start+w
end
return o
end
local function is_irref(f)
if f:match('^%d%d%d%d$') then
return 'ref_'..f
end
end
local function all_refs(s)
c = {}
for ref in s:gmatch('%d+') do
c[#c+1] = is_irref(ref)
end
return table.concat(c, ' ')
end
local function table_ir(txt)
local o = lines(txt)
local cwl = {5, 6, 3, 4, 7, 6, 1000}
for i, l in ipairs(o) do
l = cols(l, cwl)
local class = {is_irref(l[1])}
if l[5] == 'SNAP' then
class[#class+1] = 'snap_'..l[6]:sub(2)
l.title = l[7]
l[7] = ('<span class="opt">%s</span>'):format(l[7])
end
l.class = next(class) and table.concat(class, ' ')
o[i] = l
end
return o
end
local function annot_mcode(txt)
if type(txt) ~= 'string' then return '' end
txt = txt:gsub('%(exit (%d+)/(%d+)%)', function (a, b)
a, b = tonumber(a), tonumber(b)
return ('(exit %d/%d [n=%d])'):format(a, b, traces[a].exits[b] or 0)
end)
txt = _e(txt)
txt = txt:gsub('Trace #(%d+)', function (tr)
return ('<span class="tr%03d">Trace #%d</span>'):format(
tr, tr)
end)
return txt
end
local cmdline = ''
do
local minarg, maxarg = 1000,-1000
for k in pairs(arg) do
if type(k) == 'number' then
minarg = math.min(k, minarg)
maxarg = math.max(k, maxarg)
end
end
local newarg = {}
for i = minarg, maxarg do
local v = tostring(arg[i])
if v:find('[^%w.,/=_-]') then
v = ('%q'):format(v)
end
newarg[i] = v
end
cmdline = table.concat(newarg, ' ', minarg, maxarg)
end
local annotated = loom.annotated(funcs, traces)
%}
<head>
<meta charset="utf-8" />
<style media="screen" type="text/css">
.code {
font-family: monospace;
white-space: pre;
tab-size: 4;
}
.opt { display: none; }
.codespan {
width: 100%;
}
.bordertop td {
border-top: thin lightgray solid;
}
.phantom {
color: #ccc;
}
.white {
background-color: white;
}
.hilight {
background-color: lightsteelblue;
}
{% for f, fi in pairs(funcs) do %}.{{funclabel(f)}} {
background-color: hsla({{math.random(360)}}, 100%, 90%, 1);
}
{% end %}
{% for i = 1, table.maxn(traces) do %}{{:'.tr%03d', i}} {
background-color: hsla({{math.random(360)}}, 80%, 75%, 1);
}
{% end %}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script>
var sameref = function (elm, parent) {
var refmatch = elm.className.match(/ref_\d+/);
return refmatch ? $(elm).closest(parent).find('.'+refmatch[0])
: $();
return $(elm).closest(parent).find('.'+refclass);
}
$(function() {
$('th.bc').click(function(e) {
$('.bc .opt').toggle();
});
$('th.ir').click(function(e) {
$('.ir .opt').toggle();
});
$('th.titlebar').click(function(e) {
$(e.target).closest('tr').siblings().toggle();
});
$('[class^="ref_"]').mouseenter(function(e) {
sameref(e.target, '.ir').addClass('hilight');
}).mouseleave(function(e){
sameref(e.target, '.ir').removeClass('hilight');
});
});
</script>
<title>{{cmdline}}</title>
</head>
<body>
<h2>{{=cmdline:gsub('\\[\r\n]+',"<br/>")}}</h2>
<table class="code" cellpadding="0" cellspacing="0">
{% for filename, filedata in pairs(annotated) do
local lastline
%}
<tr>
<th colspan="2">{{ filename }}</th>
<th colspan="3">Bytecode</th>
</th>
{% for i, l in loom.sortedpairs(filedata) do
local notsame = l.i ~= lastline
lastline = l.i
%}
<tr {{= class{bordertop=notsame and l.bc ~= ''} }}>
<td {{= class{phantom=l.back} }}> {{ notsame and l.i or '' }} </td>
<td {{= class{phantom=l.back} }}> {{ notsame and l.src or '' }} </td>
<td {{= class{[funclabel(l.func)] = l.bc ~= ''} }}> {{ l.bc }} </td>
<td>{% for i, tr in ipairs(l.tr or {}) do
local trref = ('tr%03d'):format(tr[1])
local lnref = ('tr%03d_%03d'):format(tr[1], tr[2])
%} <a href="#{{trref}}" name="{{lnref}}"><span
id="{{lnref}}"
class="{{trref}}"
>{{tr[1]}}/{{tr[2]}}</span></a> {%
end %}</td>
<td>{% for msg, n in pairs(l.evt or {}) do
%} <span>"{{msg}}" [n={{n}}]</span> {%
end %}</td>
</tr>
{% end %}
{% end %}
</table>
{% for i, tr in loom.allipairs(traces) do local prevsrc%}
<br/>
<a name="#{{:'tr%0dd', i}}"><table class="popup trace {{:'tr%03d', i}}" id="{{:'tr%03d', i}}" cellpadding="4">
<tr>
<th colspan="3" class="titlebar">Trace #{{i}}: {{tr.tracelabel}}</th>
</tr>
<tr class="white">
<th class="bc" >bytecode</th>
<th class="ir" >IR</th>
<th class="mcode" >mcode</th>
</tr>
<tr class="white" valign="top">
<td class="code bc"><table cellpadding="0" cellspacing="0">
{% for j, rec in ipairs(tr.rec) do
local f, pc, l, src = unpack(rec)
local srcline = src and ('%s:%d %s'):format(src.name, src.i, src.l or '')
local lnref = ('tr%03d_%03d'):format(i, j)
%}
<tr class="code">
<td class="{{:'tr%03d', i}}"><a href="#{{lnref}}">{{i}}/{{j}}</a> </td>
<td class="{{funclabel(f)}}"> {{l}} </td>
<td class="src opt">{{srcline ~= prevsrc and srcline or ''}}</td>
</tr>
{% prevsrc = srcline
end %}
</table></td>
<td class="code ir"><table>
{%for _, l in ipairs(table_ir(tr.ir)) do %}
<tr class="{{=l.class or ''}}" title="{{=l.title}}">{% for _, f in ipairs(l) do %}
<td class="{{=all_refs(f)}}">{{=f}}</td>
{% end %}</tr>
{% end %}
</table></td>
<td class="code mcode">{{=annot_mcode(tr.mcode)}}</td>
</tr>
</table></a>
{% end %}
</body>
</html>
================================================
FILE: sample.lua
================================================
local Sm = {}
function Sm.lulu()
for i = 1, 1000 do
for j = 1, 1000 do
end
end
end
function Sm.motivating_example_1()
local x, z = 0, nil
for i=1,100 do
local t = {i}
if i == 90 then
z = t
end
x = x + t[1]
end
print(x, z[1])
end
function Sm.motivating_example_2()
local x, z = 0, nil
for i=1,100 do
if i == 90 then
local t = {i}
z = t
end
x = x + i
end
print(x, z[1])
end
function Sm.resinking()
local z = nil
for i=1,200 do
local t = {i}
if i > 100 then
if i == 190 then z = t end
end
end
print(z[1])
end
function Sm.pointadds()
local point
point = {
new = function(self, x, y)
return setmetatable({x=x, y=y}, self)
end,
__add = function(a, b)
return point:new(a.x + b.x, a.y + b.y)
end,
}
point.__index = point
local a, b = point:new(1.5, 2.5), point:new(3.25, 4.75)
for i=1,100000000 do
a = (a + b) + b
end
print(a.x, a.y)
end
function Sm.tdup(x)
return { foo=1, bar=2, 1,2,x,4 }
end
function Sm.miltdup(x)
for i=1,1000 do Sm.tdup(i) end
end
function Sm.call_some()
print ("one")
Sm.motivating_example_1()
print ("end")
end
return Sm
gitextract_k1j9jgsu/ ├── .github/ │ └── workflows/ │ └── semgrep.yml ├── COPYRIGHT ├── README.md ├── jit/ │ └── loom.lua ├── loom.html └── sample.lua
Condensed preview — 6 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (44K chars).
[
{
"path": ".github/workflows/semgrep.yml",
"chars": 591,
"preview": "\non:\n pull_request: {}\n workflow_dispatch: {}\n push: \n branches:\n - main\n - master\n schedule:\n - cro"
},
{
"path": "COPYRIGHT",
"chars": 1082,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2016-2017 CloudFlare\n\nPermission is hereby granted, free of charge, to any person o"
},
{
"path": "README.md",
"chars": 8228,
"preview": "LOOM\n====\n\nIt's a replacement / enhancement of the `-jdump` option included in LuaJIT.\n\nAs a command line argument\n===\n\n"
},
{
"path": "jit/loom.lua",
"chars": 21629,
"preview": "\nlocal bit = require 'bit'\nlocal jutil = require 'jit.util'\nlocal vmdef = require 'jit.vmdef'\nlocal bc = require 'jit.bc"
},
{
"path": "loom.html",
"chars": 6490,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\t{@ traces, funcs}\n\t{%\n\t\tlocal loom = require 'jit.loom'\n\t\tlocal function class(t)\n\t\t\ti"
},
{
"path": "sample.lua",
"chars": 1126,
"preview": "local Sm = {}\n\nfunction Sm.lulu()\n\tfor i = 1, 1000 do\n\t\tfor j = 1, 1000 do\n\t\tend\n\tend\nend\n\nfunction Sm.motivating_exampl"
}
]
About this extraction
This page contains the full source code of the cloudflare/loom GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 6 files (38.2 KB), approximately 13.4k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.