├── .github └── workflows │ └── semgrep.yml ├── COPYRIGHT ├── README.md ├── jit └── loom.lua ├── loom.html ├── sample.lua └── shot.png /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | 2 | on: 3 | pull_request: {} 4 | workflow_dispatch: {} 5 | push: 6 | branches: 7 | - main 8 | - master 9 | schedule: 10 | - cron: '0 0 * * *' 11 | name: Semgrep config 12 | jobs: 13 | semgrep: 14 | name: semgrep/ci 15 | runs-on: ubuntu-20.04 16 | env: 17 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 18 | SEMGREP_URL: https://cloudflare.semgrep.dev 19 | SEMGREP_APP_URL: https://cloudflare.semgrep.dev 20 | SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version 21 | container: 22 | image: returntocorp/semgrep 23 | steps: 24 | - uses: actions/checkout@v3 25 | - run: semgrep ci 26 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2017 CloudFlare 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | LOOM 2 | ==== 3 | 4 | It's a replacement / enhancement of the `-jdump` option included in LuaJIT. 5 | 6 | As a command line argument 7 | === 8 | 9 | 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: 10 | 11 | **`-jloom[=[,]]`** 12 | 13 | `` is a template file (default `'loom.html'`) and `` is an output file name (default `io.stdout`). 14 | 15 | Lua API 16 | === 17 | 18 | If you want to report traces on just part of your code, it's better to use it explicitly. 19 | 20 | **`local loom = require 'jit.loom'`** 21 | 22 | As any module, you have to `require()` it first. 23 | 24 | **`loom.on()`** 25 | 26 | Starts recording all JIT events and traces. 27 | 28 | **`traces, funcs = loom.off()`** 29 | 30 | **`report = loom.off([f [, ...]])`** 31 | 32 | Stops recording and performs any processing and cross references needed to actually generate a report. 33 | 34 | 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. 35 | 36 | The second form is equivalent to 37 | 38 | do 39 | local traces, funcs = loom.off() 40 | report = f(traces, funcs, ...) 41 | end 42 | 43 | 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`. 44 | 45 | **`loom.start(tmpl, out)`** 46 | 47 | 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., 48 | 49 | ### Utility Functions 50 | 51 | There are some functions included in the `loom` package to help formatting a report. 52 | 53 | **`f = loom.template(tmpl)`** 54 | 55 | 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. 56 | 57 | The template is compiled into a Lua function that takes some arguments (named with `{@ name ...}` tags) and outputs the result as a string. 58 | 59 | 60 | **`loom.annotated(funcs, traces)`** 61 | 62 | Returns an annotated listing of the source code of the given `funcs` and `traces` arrays. 63 | 64 | **`loom.allipairs(t)`** 65 | 66 | Like `ipairs(t)`, but stops at `table.maxn(t)` instead of the first `nil` value. 67 | 68 | **`loom.sortedpairs(t)`** 69 | 70 | Returns an iterator that visits the same pairs as `pairs(t)`, but sorted by keys. 71 | 72 | 73 | Template syntax 74 | === 75 | 76 | 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. 77 | 78 | 79 | **`{% lua code %}`** 80 | 81 | Embeds any Lua code 82 | 83 | **`{{ expression }}`** 84 | 85 | Outputs the result of the Lua expression, with the `&`, `"`, `<` and `>` characters escaped. 86 | 87 | **`{{= expression }}`** 88 | 89 | Outputs the result of the Lua expression verbatim, without any character escaping. 90 | 91 | **`{{: 'fmt', args, ... }}`** 92 | 93 | Outputs the result of `string.format(fmt, args, ...)` without any escaping. 94 | 95 | **`{@ name ... }`** 96 | 97 | 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). 98 | 99 | 100 | Included Template 101 | === 102 | 103 | 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. 104 | 105 | 1.- Source list 106 | --- 107 | 108 | 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). 109 | 110 | 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. 111 | 112 | At the right of the bytecode, there are links of the form `/`, 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. 113 | 114 | 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. 115 | 116 | 2.- Trace list 117 | --- 118 | 119 | Each trace gets a three-column table framed on the same color as the respective links in the previous section. 120 | 121 | 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. 122 | 123 | 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. 124 | 125 | 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. 126 | 127 | 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. 128 | 129 | Examples 130 | === 131 | 132 | The 'sample.lua' file includes some small code snippets to play with. For example, the comments about `-jv` option show: 133 | 134 | luajit -jv -e "for i=1,1000 do for j=1,1000 do end end" 135 | 136 | To output just two lines (one per trace). Changing to `-jdump` results in: 137 | 138 | ---- TRACE 1 start (command line):1 139 | 0009 FORL 4 => 0009 140 | ---- TRACE 1 IR 141 | 0001 int SLOAD #5 CI 142 | 0002 + int ADD 0001 +1 143 | 0003 > int LE 0002 +1000 144 | 0004 ------ LOOP ------------ 145 | 0005 + int ADD 0002 +1 146 | 0006 > int LE 0005 +1000 147 | 0007 int PHI 0002 0005 148 | ---- TRACE 1 mcode 47 149 | 0bcbffd1 mov dword [0x41a94410], 0x1 150 | 0bcbffdc cvttsd2si ebp, [rdx+0x20] 151 | 0bcbffe1 add ebp, +0x01 152 | 0bcbffe4 cmp ebp, 0x3e8 153 | 0bcbffea jg 0x0bcb0014 ->1 154 | ->LOOP: 155 | 0bcbfff0 add ebp, +0x01 156 | 0bcbfff3 cmp ebp, 0x3e8 157 | 0bcbfff9 jle 0x0bcbfff0 ->LOOP 158 | 0bcbfffb jmp 0x0bcb001c ->3 159 | ---- TRACE 1 stop -> loop 160 | 161 | ---- TRACE 2 start 1/3 (command line):1 162 | 0010 FORL 0 => 0005 163 | 0005 KSHORT 4 1 164 | 0006 KSHORT 5 1000 165 | 0007 KSHORT 6 1 166 | 0008 JFORI 4 => 0010 167 | ---- TRACE 2 IR 168 | 0001 num SLOAD #1 I 169 | 0002 num ADD 0001 +1 170 | 0003 > num LE 0002 +1000 171 | ---- TRACE 2 mcode 81 172 | 0bcbff79 mov dword [0x41a94410], 0x2 173 | 0bcbff84 movsd xmm6, [0x403382b8] 174 | 0bcbff8d movsd xmm5, [0x403382c8] 175 | 0bcbff96 movsd xmm7, [rdx] 176 | 0bcbff9a addsd xmm7, xmm6 177 | 0bcbff9e ucomisd xmm5, xmm7 178 | 0bcbffa2 jb 0x0bcb0014 ->1 179 | 0bcbffa8 movsd [rdx+0x38], xmm6 180 | 0bcbffad movsd [rdx+0x30], xmm6 181 | 0bcbffb2 movsd [rdx+0x28], xmm5 182 | 0bcbffb7 movsd [rdx+0x20], xmm6 183 | 0bcbffbc movsd [rdx+0x18], xmm7 184 | 0bcbffc1 movsd [rdx], xmm7 185 | 0bcbffc5 jmp 0x0bcbffd1 186 | ---- TRACE 2 stop -> 1 187 | 188 | To recreate under loom, try: 189 | 190 | luajit -jloom -e "require('sample').lulu()" > out.html 191 | 192 | 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. 193 | 194 | ![screenshot](shot.png) 195 | 196 | -------------------------------------------------------------------------------- /jit/loom.lua: -------------------------------------------------------------------------------- 1 | 2 | local bit = require 'bit' 3 | local jutil = require 'jit.util' 4 | local vmdef = require 'jit.vmdef' 5 | local bc = require 'jit.bc' 6 | local disass = require('jit.dis_'..jit.arch) 7 | 8 | local band, shr = bit.band, bit.rshift 9 | local inf = tonumber('inf') 10 | 11 | 12 | local function pushf(t, f, ...) 13 | if select('#', ...) > 0 then 14 | f = f:format(...) 15 | end 16 | t[#t+1] = f 17 | return f 18 | end 19 | 20 | local function allipairs(t, start) 21 | start = start or 1 22 | local maxn = table.maxn(t) 23 | return function (t, k) -- luacheck: ignore t 24 | repeat 25 | k = k + 1 26 | until t[k] ~= nil or k > maxn 27 | return k <= maxn and k or nil, t[k] 28 | end, t, start-1 29 | end 30 | 31 | local function sortedpairs(t, emptyelem) 32 | if emptyelem ~= nil and next(t) == nil then 33 | local done = false 34 | return function () 35 | if not done then 36 | done = true 37 | return emptyelem 38 | end 39 | end 40 | end 41 | 42 | local t2, map = {}, {} 43 | for k in pairs(t) do 44 | local sk = type(k) == 'number' and ('%20.6f'):format(k) or tostring(k) 45 | t2[#t2+1] = sk 46 | map[sk] = k 47 | end 48 | table.sort(t2) 49 | local i = 1 50 | return function () 51 | local k = map[t2[i]] 52 | i = i+1 53 | return k, t[k] 54 | end 55 | end 56 | 57 | -- copied from jit.dump 58 | 59 | local function fmtfunc(func, pc) 60 | local fi = jutil.funcinfo(func, pc) 61 | if fi.loc then 62 | return fi.loc 63 | elseif fi.ffid then 64 | return vmdef.ffnames[fi.ffid] 65 | elseif fi.addr then 66 | return ("C:%x"):format(fi.addr) 67 | else 68 | return "(?)" 69 | end 70 | end 71 | 72 | ----------- 73 | 74 | local function bcline(func, pc, prefix) 75 | local l 76 | if pc >= 0 then 77 | l = bc.line(func, pc, prefix) 78 | if not l then return l end 79 | else 80 | l = "0000 "..prefix.." FUNCC \n" 81 | end 82 | 83 | l = l:gsub('%s+$', '') 84 | return l 85 | end 86 | 87 | 88 | local function func_bc(func, o) 89 | o = o or {} 90 | o[func] = jutil.funcinfo(func) 91 | if o[func].children then 92 | for n = -1, -inf, -1 do 93 | local k = jutil.funck(func, n) 94 | if not k then break end 95 | if type(k) == 'proto' then func_bc(k, o) end 96 | end 97 | end 98 | o[func].func = func 99 | o[func].bytecode = {} 100 | if not o[func].addr then 101 | local target = bc.targets(func) 102 | for pc = 1, inf do 103 | local s = bcline (func, pc, target[pc] and "=>") 104 | if not s then break end 105 | local fi_sub = jutil.funcinfo(func, pc) 106 | o[func].bytecode[pc] = {fi_sub.currentline, s} 107 | end 108 | end 109 | return o 110 | end 111 | 112 | -------------------------------------- 113 | -- tracing 114 | 115 | -- copied from jit/dump.lua 116 | 117 | local symtabmt = { __index = false } 118 | local symtab = {} 119 | local nexitsym = 0 120 | 121 | -- Fill nested symbol table with per-trace exit stub addresses. 122 | local function fillsymtab_tr(tr, nexit) 123 | local t = {} 124 | symtabmt.__index = t 125 | if jit.arch == "mips" or jit.arch == "mipsel" then 126 | t[jutil.traceexitstub(tr, 0)] = "exit" 127 | return 128 | end 129 | for i=0,nexit-1 do 130 | local addr = jutil.traceexitstub(tr, i) 131 | if addr < 0 then addr = addr + 2^32 end 132 | t[addr] = tostring(i) 133 | end 134 | local addr = jutil.traceexitstub(tr, nexit) 135 | if addr then t[addr] = "stack_check" end 136 | end 137 | 138 | -- Fill symbol table with trace exit stub addresses. 139 | local function fillsymtab(tr, nexit) 140 | local t = symtab 141 | if nexitsym == 0 then 142 | local ircall = vmdef.ircall 143 | for i=0,#ircall do 144 | local addr = jutil.ircalladdr(i) 145 | if addr and addr ~= 0 then 146 | if addr < 0 then addr = addr + 2^32 end 147 | t[addr] = ircall[i] 148 | end 149 | end 150 | end 151 | if nexitsym == 1000000 then -- Per-trace exit stubs. 152 | fillsymtab_tr(tr, nexit) 153 | elseif nexit > nexitsym then -- Shared exit stubs. 154 | for i=nexitsym,nexit-1 do 155 | local addr = jutil.traceexitstub(i) 156 | if addr == nil then -- Fall back to per-trace exit stubs. 157 | fillsymtab_tr(tr, nexit) 158 | setmetatable(symtab, symtabmt) 159 | nexit = 1000000 160 | break 161 | end 162 | if addr < 0 then addr = addr + 2^32 end 163 | t[addr] = tostring(i) 164 | end 165 | nexitsym = nexit 166 | end 167 | return t 168 | end 169 | 170 | -- Disassemble machine code. 171 | local function dump_mcode(tr) 172 | local o = {} 173 | local info = jutil.traceinfo(tr) 174 | if not info then return end 175 | local mcode, addr, loop = jutil.tracemc(tr) 176 | if not mcode then return end 177 | if addr < 0 then addr = addr + 2^32 end 178 | local ctx = disass.create(mcode, addr, function (s) pushf(o, s) end) 179 | ctx.hexdump = 0 180 | ctx.symtab = fillsymtab(tr, info.nexit) 181 | if loop ~= 0 then 182 | symtab[addr+loop] = "LOOP" 183 | ctx:disass(0, loop) 184 | pushf (o, "->LOOP:\n") 185 | ctx:disass(loop, #mcode-loop) 186 | symtab[addr+loop] = nil 187 | else 188 | ctx:disass(0, #mcode) 189 | end 190 | return table.concat(o) 191 | end 192 | 193 | 194 | 195 | 196 | local irtype = { 197 | [0] = "nil", 198 | "fal", 199 | "tru", 200 | "lud", 201 | "str", 202 | "p32", 203 | "thr", 204 | "pro", 205 | "fun", 206 | "p64", 207 | "cdt", 208 | "tab", 209 | "udt", 210 | "flt", 211 | "num", 212 | "i8 ", 213 | "u8 ", 214 | "i16", 215 | "u16", 216 | "int", 217 | "u32", 218 | "i64", 219 | "u64", 220 | "sfp", 221 | } 222 | 223 | -- Lookup tables to convert some literals into names. 224 | local litname = { 225 | ["SLOAD "] = setmetatable({}, { __index = function(t, mode) 226 | local s = "" 227 | if band(mode, 1) ~= 0 then s = s.."P" end 228 | if band(mode, 2) ~= 0 then s = s.."F" end 229 | if band(mode, 4) ~= 0 then s = s.."T" end 230 | if band(mode, 8) ~= 0 then s = s.."C" end 231 | if band(mode, 16) ~= 0 then s = s.."R" end 232 | if band(mode, 32) ~= 0 then s = s.."I" end 233 | t[mode] = s 234 | return s 235 | end}), 236 | ["XLOAD "] = { [0] = "", "R", "V", "RV", "U", "RU", "VU", "RVU", }, 237 | ["CONV "] = setmetatable({}, { __index = function(t, mode) 238 | local s = irtype[band(mode, 31)] 239 | s = irtype[band(shr(mode, 5), 31)].."."..s 240 | if band(mode, 0x800) ~= 0 then s = s.." sext" end 241 | local c = shr(mode, 14) 242 | if c == 2 then s = s.." index" elseif c == 3 then s = s.." check" end 243 | t[mode] = s 244 | return s 245 | end}), 246 | ["FLOAD "] = vmdef.irfield, 247 | ["FREF "] = vmdef.irfield, 248 | ["FPMATH"] = vmdef.irfpm, 249 | ["BUFHDR"] = { [0] = "RESET", "APPEND" }, 250 | ["TOSTR "] = { [0] = "INT", "NUM", "CHAR" }, 251 | } 252 | 253 | 254 | local function ctlsub(c) 255 | if c == "\n" then return "\\n" 256 | elseif c == "\r" then return "\\r" 257 | elseif c == "\t" then return "\\t" 258 | else return ("\\%03d"):format(c:byte()) 259 | end 260 | end 261 | 262 | local function formatk(tr, idx) 263 | local k, t, slot = jutil.tracek(tr, idx) 264 | local tn = type(k) 265 | local s 266 | if tn == "number" then 267 | if k == 2^52+2^51 then 268 | s = "bias" 269 | else 270 | s = ("%+.14g"):format(k) 271 | end 272 | elseif tn == "string" then 273 | s = (#k > 20 and '"%.20s"~' or '"%s"'):format(k:gsub("%c", ctlsub)) 274 | elseif tn == "function" then 275 | s = fmtfunc(k) 276 | elseif tn == "table" then 277 | s = ("{%p}"):format(k) 278 | elseif tn == "userdata" then 279 | if t == 12 then 280 | s = ("userdata:%p"):format(k) 281 | else 282 | s = ("[%p]"):format(k) 283 | if s == "[0x00000000]" then s = "NULL" end 284 | end 285 | elseif t == 21 then -- int64_t 286 | s = tostring(k):sub(1, -3) 287 | if (s):sub(1, 1) ~= "-" then s = "+"..s end 288 | else 289 | s = tostring(k) -- For primitives. 290 | end 291 | s = ("%-4s"):format(s) 292 | if slot then 293 | s = ("%s @%d"):format(s, slot) 294 | end 295 | return s 296 | end 297 | 298 | local function printsnap(tr, snap) 299 | local o = {} 300 | local n = 2 301 | for s=0,snap[1]-1 do 302 | local sn = snap[n] 303 | if shr(sn, 24) == s then 304 | n = n + 1 305 | local ref = band(sn, 0xffff) - 0x8000 -- REF_BIAS 306 | if ref < 0 then 307 | pushf(o, formatk(tr, ref)) 308 | elseif band(sn, 0x80000) ~= 0 then -- SNAP_SOFTFPNUM 309 | pushf(o, "%04d/%04d", ref, ref+1) 310 | else 311 | pushf(o, "%04d", ref) 312 | end 313 | pushf(o, band(sn, 0x10000) == 0 and " " or "|") -- SNAP_FRAME 314 | else 315 | pushf(o, "---- ") 316 | end 317 | end 318 | pushf(o, "]\n") 319 | return table.concat(o) 320 | end 321 | 322 | -- Dump snapshots (not interleaved with IR). 323 | local function dump_snap(tr) 324 | local o = {"---- TRACE "..tr.." snapshots\n"} 325 | for i=0,1000000000 do 326 | local snap = jutil.tracesnap(tr, i) 327 | if not snap then break end 328 | pushf(o, "#%-3d %04d [ ", i, snap[0]) 329 | pushf(o, printsnap(tr, snap)) 330 | end 331 | return table.concat(o) 332 | end 333 | 334 | -- Return a register name or stack slot for a rid/sp location. 335 | local function ridsp_name(ridsp, ins) 336 | local rid, slot = band(ridsp, 0xff), shr(ridsp, 8) 337 | if rid == 253 or rid == 254 then 338 | return (slot == 0 or slot == 255) and " {sink" or (" {%04d"):format(ins-slot) 339 | end 340 | if ridsp > 255 then return ("[%x]"):format(slot*4) end 341 | if rid < 128 then return disass.regname(rid) end 342 | return "" 343 | end 344 | 345 | -- Dump CALL* function ref and return optional ctype. 346 | local function dumpcallfunc(o, tr, ins) 347 | local ctype 348 | if ins > 0 then 349 | local m, ot, op1, op2 = jutil.traceir(tr, ins) -- luacheck: ignore m 350 | if band(ot, 31) == 0 then -- nil type means CARG(func, ctype). 351 | ins = op1 352 | ctype = formatk(tr, op2) 353 | end 354 | end 355 | if ins < 0 then 356 | pushf(o, "[0x%x](", tonumber((jutil.tracek(tr, ins)))) 357 | else 358 | pushf(o, "%04d (", ins) 359 | end 360 | return ctype 361 | end 362 | 363 | -- Recursively gather CALL* args and dump them. 364 | local function dumpcallargs(o, tr, ins) 365 | if ins < 0 then 366 | pushf(o, formatk(tr, ins)) 367 | else 368 | local m, ot, op1, op2 = jutil.traceir(tr, ins) -- luacheck: ignore m 369 | local oidx = 6*shr(ot, 8) 370 | local op = vmdef.irnames:sub(oidx+1, oidx+6) 371 | if op == "CARG " then 372 | dumpcallargs(o, tr, op1) 373 | if op2 < 0 then 374 | pushf(o, " "..formatk(tr, op2)) 375 | else 376 | pushf(o, " %04d", op2) 377 | end 378 | else 379 | pushf(o, "%04d", ins) 380 | end 381 | end 382 | end 383 | 384 | -- Dump IR and interleaved snapshots. 385 | local function dump_ir(tr) 386 | local dumpsnap, dumpreg = true, true 387 | local info = jutil.traceinfo(tr) 388 | if not info then return end 389 | local nins = info.nins 390 | local o = {} 391 | local irnames = vmdef.irnames 392 | local snapref = 65536 393 | local snap, snapno 394 | if dumpsnap then 395 | snap = jutil.tracesnap(tr, 0) 396 | snapref = snap[0] 397 | snapno = 0 398 | end 399 | for ins=1,nins do 400 | if ins >= snapref then 401 | if dumpreg then 402 | pushf (o, ".... SNAP #%-3d [ ", snapno) 403 | else 404 | pushf (o, ".... SNAP #%-3d [ ", snapno) 405 | end 406 | pushf (o, printsnap(tr, snap)) 407 | snapno = snapno + 1 408 | snap = jutil.tracesnap(tr, snapno) 409 | snapref = snap and snap[0] or 65536 410 | end 411 | local m, ot, op1, op2, ridsp = jutil.traceir(tr, ins) 412 | local oidx, t = 6*shr(ot, 8), band(ot, 31) 413 | local op = irnames:sub(oidx+1, oidx+6) 414 | if op == "LOOP " then 415 | if dumpreg then 416 | pushf (o, "%04d ------------ LOOP ------------\n", ins) 417 | else 418 | pushf (o, "%04d ------ LOOP ------------\n", ins) 419 | end 420 | elseif op ~= "NOP " and op ~= "CARG " and 421 | (dumpreg or op ~= "RENAME") 422 | then 423 | local rid = band(ridsp, 255) 424 | if dumpreg then 425 | pushf (o, "%04d %-6s", ins, ridsp_name(ridsp, ins)) 426 | else 427 | pushf (o, "%04d ", ins) 428 | end 429 | pushf (o, "%s%s %s %s ", 430 | (rid == 254 or rid == 253) and "}" or 431 | (band(ot, 128) == 0 and " " or ">"), 432 | band(ot, 64) == 0 and " " or "+", 433 | irtype[t], op) 434 | local m1, m2 = band(m, 3), band(m, 3*4) 435 | if op:sub(1, 4) == "CALL" then 436 | local ctype 437 | if m2 == 1*4 then -- op2 == IRMlit 438 | pushf (o, "%-10s (", vmdef.ircall[op2]) 439 | else 440 | ctype = dumpcallfunc(o, tr, op2) 441 | end 442 | if op1 ~= -1 then dumpcallargs(o, tr, op1) end 443 | pushf(o, ")") 444 | if ctype then pushf(o, " ctype "..ctype) end 445 | elseif op == "CNEW " and op2 == -1 then 446 | pushf(o, formatk(tr, op1)) 447 | elseif m1 ~= 3 then -- op1 != IRMnone 448 | if op1 < 0 then 449 | pushf(o, formatk(tr, op1)) 450 | else 451 | pushf(o, m1 == 0 and "%04d" or "#%-3d", op1) 452 | end 453 | if m2 ~= 3*4 then -- op2 != IRMnone 454 | if m2 == 1*4 then -- op2 == IRMlit 455 | local litn = litname[op] 456 | if litn and litn[op2] then 457 | pushf(o, " "..litn[op2]) 458 | elseif op == "UREFO " or op == "UREFC " then 459 | pushf (o, " #%-3d", shr(op2, 8)) 460 | else 461 | pushf (o, " #%-3d", op2) 462 | end 463 | elseif op2 < 0 then 464 | pushf (o, " "..formatk(tr, op2)) 465 | else 466 | pushf (o, " %04d", op2) 467 | end 468 | end 469 | end 470 | pushf (o, "\n") 471 | end 472 | end 473 | if snap then 474 | if dumpreg then 475 | pushf (o, ".... SNAP #%-3d [ ", snapno) 476 | else 477 | pushf (o, ".... SNAP #%-3d [ ", snapno) 478 | end 479 | pushf (o, printsnap(tr, snap)) 480 | end 481 | return table.concat(o) 482 | end 483 | 484 | 485 | local function get_bytecode(bc) 486 | return vmdef.bcnames:sub(bc*6+1, bc*6+6):gsub(' ', '') 487 | end 488 | 489 | -- Format trace error message. 490 | local function fmterr(err, info) 491 | if type(err) == "number" then 492 | if type(info) == "function" then info = fmtfunc(info) end 493 | err = vmdef.traceerr[err]:format(info) 494 | if type(info) == 'number' and err:find('bytecode') then 495 | err = ("%s (%s)"):format(err, get_bytecode(info)) 496 | end 497 | end 498 | return err 499 | end 500 | 501 | 502 | local function tracelabel(tr, func, pc, otr, oex) 503 | local startex = otr and "("..otr.."/"..oex..") " or "" 504 | local info = jutil.traceinfo(tr) 505 | if not info then return '-- no trace info --' end 506 | 507 | local link, ltype = info.link, info.linktype 508 | if ltype == "interpreter" then 509 | return ("%s -- fallback to interpreter\n") 510 | :format(startex) 511 | elseif ltype == "stitch" then 512 | return ("%s %s [%s]\n") 513 | :format(startex, ltype, fmtfunc(func, pc)) 514 | elseif link == tr or link == 0 then 515 | return ("%s %s\n") 516 | :format(startex, ltype) 517 | elseif ltype == "root" then 518 | return ("%s -> %d\n") 519 | :format(startex, link) 520 | else 521 | return ("%s -> %d %s\n") 522 | :format(startex, link, ltype) 523 | end 524 | end 525 | 526 | ---- 527 | 528 | local loomstart, loomstop 529 | do 530 | local collecting = {[0]=0} 531 | local function append(v) 532 | local c = collecting 533 | c[0] = c[0] + 1 534 | c[c[0]] = v 535 | return c[0] 536 | end 537 | 538 | local function collect_trace(what, tr, func, pc, otr, oex) 539 | append({'trace', what, tr, func, pc, otr, oex, ''}) 540 | end 541 | 542 | local function collect_record(tr, func, pc, depth) 543 | append({'record', tr, func, pc, depth, ''}) 544 | end 545 | 546 | local function collect_texit(tr, ex, ngpr, nfpr, ...) 547 | append({'texit', tr, ex, ngpr, nfpr, ...}) 548 | end 549 | 550 | local function do_attachs() 551 | jit.attach(collect_trace, 'trace') 552 | jit.attach(collect_record, 'record') 553 | jit.attach(collect_texit, 'texit') 554 | end 555 | 556 | local function do_detachs() 557 | jit.attach(collect_texit) 558 | jit.attach(collect_record) 559 | jit.attach(collect_trace) 560 | end 561 | 562 | local traces_data, seen_funcs = {}, {} 563 | local prevtraces = {} 564 | local prevexp_t = { 565 | trace = function (what, tr, func, pc, otr, oex) -- luacheck: ignore func pc 566 | if what == 'start' then 567 | local mcode, addr, loop = jutil.tracemc(tr) -- luacheck: ignore mcode loop 568 | if addr ~= nil then 569 | if otr and oex then 570 | symtab[addr] = ("Trace #%d (exit %d/%d)"):format(tr, otr, oex) 571 | else 572 | symtab[addr] = ("Trace #%d"):format(tr) 573 | end 574 | end 575 | end 576 | end, 577 | record = function() end, 578 | texit = function () end, 579 | } 580 | local function gettrace(tn) 581 | local tr = traces_data[tn] 582 | if tr then return tr end 583 | 584 | tr = prevtraces[tn] or { 585 | info = jutil.traceinfo(tn) or {}, 586 | ir = dump_ir(tn), 587 | snap = dump_snap(tn), 588 | evt = {}, 589 | rec = {}, 590 | n = { 591 | trace = 0, 592 | start = 0, 593 | stop = 0, 594 | abort = 0, 595 | flush = 0, 596 | record = 0, 597 | texit = 0, 598 | }, 599 | exits = {}, 600 | } 601 | tr.mcode = dump_mcode(tn) 602 | traces_data[tn] = tr 603 | return tr 604 | end 605 | 606 | local exp_trace_t = { 607 | start = function (tr, func, pc, otr, oex) -- luacheck: ignore func pc 608 | local t = gettrace(tr) 609 | t.parent = t.parent or otr 610 | t.p_exit = t.p_exit or oex 611 | end, 612 | 613 | stop = function (tr, func, pc, otr, oex) -- luacheck: ignore tr func pc otr oex 614 | end, 615 | 616 | abort = function (tr, func, pc, otr, oex) -- luacheck: ignore func pc 617 | local t = gettrace(tr) 618 | t.err = t.err or fmterr(otr, oex) 619 | end, 620 | 621 | flush = function (tr, func, pc, otr, oex) -- luacheck: ignore tr func pc otr oex 622 | symtab, nexitsym = {}, 0 623 | end, 624 | } 625 | local expand_t = { 626 | trace = function (what, tr, func, pc, otr, oex) 627 | seen_funcs[func] = true 628 | local t = gettrace(tr) 629 | t.n.trace = t.n.trace + 1 630 | t.n[what] = (t.n[what] or 0) + 1 631 | t.tracelabel = t.tracelabel or tracelabel(tr, func, pc, otr, oex) 632 | t.otr, t.oex = otr, oex 633 | 634 | do 635 | msg = what=='abort' and fmterr(otr, oex) or nil 636 | t.evt[#t.evt +1] = { 637 | what, func, pc, 638 | msg, 639 | } 640 | if msg then 641 | t.rec[#t.rec+1] = {func, pc, ("%s: %q"):format(what, msg)} 642 | end 643 | end 644 | 645 | local expf = exp_trace_t[what] 646 | return expf and expf(tr, func, pc, otr, oex) 647 | end, 648 | 649 | record = function (tr, func, pc, depth) 650 | local t = gettrace(tr) 651 | t.n.record = t.n.record + 1 652 | seen_funcs[func] = true 653 | t.rec[#t.rec+1] = {func, pc, bcline(func, pc, (' .'):rep(depth))} 654 | if pc >= 0 and band(jutil.funcbc(func, pc), 0xff) < 16 then 655 | t.rec[#t.rec+1] = {func, pc+1, bcline(func, pc+1, (' .'):rep(depth))} 656 | end 657 | end, 658 | 659 | texit = function (tr, ex, ngpr, nfpr, ...) 660 | local t = gettrace(tr) 661 | t.n.texit = t.n.texit + 1 662 | t.exits[ex] = (t.exits[ex] or 0) + 1 663 | t.evt[#t.evt+1] = {'exit', ex, ngpr, nfpr, ...} 664 | end, 665 | } 666 | 667 | function loomstart(clear) 668 | if clear then 669 | for k, v in pairs(traces_data) do 670 | prevtraces[k] = v 671 | end 672 | traces_data, seen_funcs = {}, {} 673 | collecting = {[0]=0} 674 | end 675 | do_attachs() 676 | end 677 | 678 | function loomstop(f, ...) 679 | do_detachs() 680 | for _, v in ipairs(collecting) do 681 | prevexp_t[v[1]](unpack(v, 2, table.maxn(v))) 682 | end 683 | for _, v in ipairs(collecting) do 684 | expand_t[v[1]](unpack(v, 2, table.maxn(v))) 685 | end 686 | 687 | for tn, tr in pairs(traces_data) do 688 | gettrace(tr.otr or tn) 689 | gettrace(tr.info.link or tn) 690 | if tr.mcode then 691 | for tns in tr.mcode:gmatch('Trace #(%d+)') do 692 | gettrace(tonumber(tns)) 693 | end 694 | end 695 | end 696 | for _, tr in pairs(traces_data) do 697 | for _, rec in ipairs(tr.rec) do 698 | seen_funcs[rec[1]] = true 699 | end 700 | for _, evt in ipairs(tr.evt) do 701 | if type(evt[2]) == 'function' then 702 | seen_funcs[evt[2]] = true 703 | end 704 | end 705 | end 706 | 707 | local funcslist = {} 708 | for fun in pairs(seen_funcs) do 709 | for subf, fi in pairs(func_bc(fun)) do 710 | funcslist[subf] = fi 711 | end 712 | end 713 | 714 | if f then 715 | return f(traces_data, funcslist, ...) 716 | end 717 | return traces_data, funcslist 718 | end 719 | end 720 | -------------------------------------- 721 | local function srclines(fn) 722 | local t, f = {}, io.open(fn) 723 | if f then 724 | for l in f:lines() do 725 | t[#t+1] = l 726 | end 727 | f:close() 728 | end 729 | return t 730 | end 731 | 732 | local function defget(t, k, d) 733 | local v = t[k] or d 734 | if v == nil then v = {} end 735 | if t[k] == nil then t[k] = v end 736 | return v 737 | end 738 | 739 | 740 | local function annotated(funcs, traces) 741 | local ranges = {} 742 | for f, fi in pairs(funcs) do 743 | if type(f)=='function' and fi.source then 744 | local srcranges = defget(ranges, fi.source:gsub('^@', ''), nil) 745 | local lineranges = defget(srcranges, fi.linedefined, nil) 746 | lineranges[f] = fi 747 | end 748 | end 749 | 750 | local presources = {} 751 | do 752 | local cmdlinesrc = '' 753 | for k, v in pairs(arg) do 754 | if type(k) == 'number' and v == '-e' then 755 | cmdlinesrc = cmdlinesrc .. arg[k+1] 756 | end 757 | end 758 | local cmdlines = {} 759 | for l in (cmdlinesrc..'\r'):gmatch('(.-)[\r\n]') do 760 | cmdlines[#cmdlines+1] = l 761 | end 762 | presources['=(command line)'] = cmdlines 763 | end 764 | 765 | local o = {} 766 | for srcname, srcranges in sortedpairs(ranges) do 767 | o[srcname] = {} 768 | local of, src = o[srcname], presources[srcname] or srclines(srcname) 769 | local lastline = nil 770 | local function newline(i, func, pc, bcl) 771 | local nl = { 772 | i = i, 773 | src = src[i], 774 | func = func, 775 | pc = pc, 776 | bc = bcl or '', 777 | back = type(i)=='number' and i <= lastline, 778 | tr = {}, 779 | evt = {}, 780 | } 781 | of[#of+1] = nl 782 | return nl 783 | end 784 | for startline, lst in allipairs(srcranges, 0) do 785 | for _, fi in pairs(lst) do 786 | lastline = math.max(0, startline-2) 787 | for pc, l in sortedpairs(fi.bytecode or {}) do 788 | local lnum, bcl = unpack(l) 789 | if lnum > lastline + 5 then 790 | for i = lastline+1, lastline+3 do 791 | newline (i) 792 | end 793 | newline ('...') 794 | for i = lnum-2, lnum-1 do 795 | newline(i, fi.func) 796 | end 797 | else 798 | for i = lastline+1, lnum-1 do 799 | newline(i, fi.func) 800 | end 801 | end 802 | newline(lnum, fi.func, pc, bcl) 803 | lastline = math.max(lastline, lnum) 804 | end 805 | end 806 | end 807 | end 808 | 809 | for i, tr in allipairs(traces) do 810 | for j, rec in ipairs(tr.rec) do 811 | local f, pc, bcl = unpack (rec) -- luacheck: ignore bcl 812 | for srcname, osrc in pairs(o) do -- luacheck: ignore srcname 813 | for _, ol in ipairs(osrc) do 814 | if ol.func == f and ol.pc == pc and #ol.bc>0 then 815 | ol.tr[#ol.tr+1] = {i, j} 816 | rec[#rec+1] = {name=srcname, i=ol.i, l=ol.src} 817 | break 818 | end 819 | end 820 | end 821 | end 822 | for _, evt in ipairs(tr.evt) do 823 | local what, func, pc, msg = unpack(evt) 824 | for srcname, osrc in pairs(o) do -- luacheck: ignore srcname 825 | for _, ol in ipairs(osrc) do 826 | if ol.func == func and ol.pc == pc and #ol.bc>0 then 827 | local k = what .. (msg and ': '..msg or '') 828 | ol.evt[k] = (ol.evt[k] or 0) + 1 829 | end 830 | end 831 | end 832 | end 833 | end 834 | return o 835 | end 836 | 837 | -------------------------------------- 838 | 839 | --[[ 840 | template compiling function, loosely derived from: 841 | https://github.com/dannote/lua-template/blob/master/template.lua 842 | by Danila Poyarkov 843 | --]] 844 | local template 845 | do 846 | local _esc = { 847 | ['&'] = '&', 848 | ['<'] = '<', 849 | ['>'] = '>', 850 | ['"'] = '"', 851 | } 852 | local function escape(s) 853 | return tostring(s or ''):gsub('[><&"]', _esc) 854 | end 855 | 856 | function template (tpl) 857 | local tplname = 'tmpl' 858 | if not tpl:find('\n', 1, true) then 859 | tplname = tpl 860 | local f = assert(io.open(tpl)) 861 | tpl = assert(f:read('*a')) 862 | f:close() 863 | end 864 | 865 | local args = {'_e'} 866 | tpl = tpl:gsub('{@(.-)}', function (argl) 867 | argl:gsub('([_%a][_%w]*)', function (a) args[#args+1] = a return '' end) 868 | return '' 869 | end) 870 | 871 | local src = ( 872 | 'local %s = ... ' .. 873 | 'local _o = {} ' .. 874 | 'local function _p(x) _o[#_o+1] = tostring(x or "") end ' .. 875 | 'local function _fp(f, ...) _p(f:format(...)) end '.. 876 | 'local function _ep(x) _p(_e(x)) end ' .. 877 | '_p[=[%s]=] ' .. 878 | 'return table.concat(_o)') 879 | :format( 880 | table.concat(args, ', '), 881 | tpl 882 | :gsub('[][]=[][]', ']=] _p"%1" _p[=[') 883 | :gsub('{{=', ']=] _p(') 884 | :gsub('{{:', ']=] _fp(') 885 | :gsub('{{', ']=] _ep(') 886 | :gsub('}}', ') _p[=[') 887 | :gsub('{%%', ']=] ') 888 | :gsub('%%}', ' _p[=[') 889 | ) 890 | local f = assert(loadstring(src, tplname)) 891 | return function (...) 892 | return f(escape, ...) 893 | end 894 | end 895 | end 896 | 897 | 898 | ------------------------------------- 899 | local defer 900 | 901 | return { 902 | on = loomstart, 903 | off = loomstop, 904 | 905 | start = function (opt, out) 906 | local tmpl = template(opt or 'loom.html') 907 | defer = newproxy(true) 908 | getmetatable(defer).__gc = function() xpcall(function () 909 | local o = loomstop(tmpl) 910 | out = type(out)=='string' and assert(io.open(out, 'w')) 911 | or out or io.stdout 912 | out:write(o) 913 | end, function(err) print(debug.traceback(err)) end) end 914 | 915 | loomstart() 916 | end, 917 | 918 | template = template, 919 | annotated = annotated, 920 | allipairs = allipairs, 921 | sortedpairs = sortedpairs, 922 | } 923 | -------------------------------------------------------------------------------- /loom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {@ traces, funcs} 4 | {% 5 | local loom = require 'jit.loom' 6 | local function class(t) 7 | if not t then return '' end 8 | o = {} 9 | for k, v in pairs(t) do 10 | if v then o[#o+1] = k end 11 | end 12 | if #o == 0 then return '' end 13 | return 'class="'..table.concat(o, ' ')..'"' 14 | end 15 | 16 | local _ft_, _fndx_ = {}, 0 17 | local function funclabel(f) 18 | if not f then return '' end 19 | if _ft_[f] == nil then 20 | _fndx_ = _fndx_+1 21 | _ft_[f] = ('fn%03d'):format(_fndx_) 22 | end 23 | return _ft_[f] 24 | end 25 | 26 | local function lines(s) 27 | s = s or '' 28 | local o = {} 29 | for l in s:gmatch('[^\r\n]+') do 30 | o[#o+1] = l 31 | end 32 | return o 33 | end 34 | 35 | local function cols(s, cwl) 36 | local o, start = {}, 1 37 | for i, w in ipairs(cwl) do 38 | o[i] = s:sub(start, start+w-1):gsub('%s+$', '') 39 | start = start+w 40 | end 41 | return o 42 | end 43 | 44 | local function is_irref(f) 45 | if f:match('^%d%d%d%d$') then 46 | return 'ref_'..f 47 | end 48 | end 49 | 50 | local function all_refs(s) 51 | c = {} 52 | for ref in s:gmatch('%d+') do 53 | c[#c+1] = is_irref(ref) 54 | end 55 | return table.concat(c, ' ') 56 | end 57 | 58 | local function table_ir(txt) 59 | local o = lines(txt) 60 | local cwl = {5, 6, 3, 4, 7, 6, 1000} 61 | for i, l in ipairs(o) do 62 | l = cols(l, cwl) 63 | local class = {is_irref(l[1])} 64 | if l[5] == 'SNAP' then 65 | class[#class+1] = 'snap_'..l[6]:sub(2) 66 | l.title = l[7] 67 | l[7] = ('%s'):format(l[7]) 68 | end 69 | l.class = next(class) and table.concat(class, ' ') 70 | o[i] = l 71 | end 72 | return o 73 | end 74 | 75 | local function annot_mcode(txt) 76 | if type(txt) ~= 'string' then return '' end 77 | txt = txt:gsub('%(exit (%d+)/(%d+)%)', function (a, b) 78 | a, b = tonumber(a), tonumber(b) 79 | return ('(exit %d/%d [n=%d])'):format(a, b, traces[a].exits[b] or 0) 80 | end) 81 | txt = _e(txt) 82 | txt = txt:gsub('Trace #(%d+)', function (tr) 83 | return ('Trace #%d'):format( 84 | tr, tr) 85 | end) 86 | return txt 87 | end 88 | 89 | local cmdline = '' 90 | do 91 | local minarg, maxarg = 1000,-1000 92 | for k in pairs(arg) do 93 | if type(k) == 'number' then 94 | minarg = math.min(k, minarg) 95 | maxarg = math.max(k, maxarg) 96 | end 97 | end 98 | local newarg = {} 99 | for i = minarg, maxarg do 100 | local v = tostring(arg[i]) 101 | if v:find('[^%w.,/=_-]') then 102 | v = ('%q'):format(v) 103 | end 104 | newarg[i] = v 105 | end 106 | cmdline = table.concat(newarg, ' ', minarg, maxarg) 107 | end 108 | 109 | local annotated = loom.annotated(funcs, traces) 110 | %} 111 | 112 | 113 | 145 | 146 | 170 | {{cmdline}} 171 | 172 | 173 |

{{=cmdline:gsub('\\[\r\n]+',"
")}}

174 | 175 | {% for filename, filedata in pairs(annotated) do 176 | local lastline 177 | %} 178 | 179 | 180 | 181 | 182 | {% for i, l in loom.sortedpairs(filedata) do 183 | local notsame = l.i ~= lastline 184 | lastline = l.i 185 | %} 186 | 187 | 188 | 189 | 190 | 198 | 201 | 202 | {% end %} 203 | {% end %} 204 |
{{ filename }}Bytecode
{{ notsame and l.i or '' }} {{ notsame and l.src or '' }} {{ l.bc }} {% for i, tr in ipairs(l.tr or {}) do 191 | local trref = ('tr%03d'):format(tr[1]) 192 | local lnref = ('tr%03d_%03d'):format(tr[1], tr[2]) 193 | %} {{tr[1]}}/{{tr[2]}} {% 197 | end %}{% for msg, n in pairs(l.evt or {}) do 199 | %} "{{msg}}" [n={{n}}] {% 200 | end %}
205 | 206 | {% for i, tr in loom.allipairs(traces) do local prevsrc%} 207 |
208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 232 | 239 | 240 | 241 | 242 | {% end %} 243 | 244 | 245 | -------------------------------------------------------------------------------- /sample.lua: -------------------------------------------------------------------------------- 1 | local Sm = {} 2 | 3 | function Sm.lulu() 4 | for i = 1, 1000 do 5 | for j = 1, 1000 do 6 | end 7 | end 8 | end 9 | 10 | function Sm.motivating_example_1() 11 | local x, z = 0, nil 12 | for i=1,100 do 13 | local t = {i} 14 | if i == 90 then 15 | z = t 16 | end 17 | x = x + t[1] 18 | end 19 | print(x, z[1]) 20 | end 21 | 22 | function Sm.motivating_example_2() 23 | local x, z = 0, nil 24 | for i=1,100 do 25 | if i == 90 then 26 | local t = {i} 27 | z = t 28 | end 29 | x = x + i 30 | end 31 | print(x, z[1]) 32 | end 33 | 34 | function Sm.resinking() 35 | local z = nil 36 | for i=1,200 do 37 | local t = {i} 38 | if i > 100 then 39 | if i == 190 then z = t end 40 | end 41 | end 42 | print(z[1]) 43 | end 44 | 45 | function Sm.pointadds() 46 | local point 47 | point = { 48 | new = function(self, x, y) 49 | return setmetatable({x=x, y=y}, self) 50 | end, 51 | __add = function(a, b) 52 | return point:new(a.x + b.x, a.y + b.y) 53 | end, 54 | } 55 | point.__index = point 56 | local a, b = point:new(1.5, 2.5), point:new(3.25, 4.75) 57 | for i=1,100000000 do 58 | a = (a + b) + b 59 | end 60 | print(a.x, a.y) 61 | end 62 | 63 | function Sm.tdup(x) 64 | return { foo=1, bar=2, 1,2,x,4 } 65 | end 66 | 67 | function Sm.miltdup(x) 68 | for i=1,1000 do Sm.tdup(i) end 69 | end 70 | 71 | function Sm.call_some() 72 | print ("one") 73 | Sm.motivating_example_1() 74 | print ("end") 75 | end 76 | 77 | 78 | return Sm 79 | -------------------------------------------------------------------------------- /shot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/loom/f8634373af363a85eeff69c0fe2459fceb8dd9f4/shot.png --------------------------------------------------------------------------------