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

zeroflag / equinox / 13767010558

10 Mar 2025 02:22PM UTC coverage: 89.277% (-0.6%) from 89.879%
13767010558

push

github

zeroflag
optimization

24 of 24 new or added lines in 1 file covered. (100.0%)

15 existing lines in 1 file now uncovered.

1865 of 2089 relevant lines covered (89.28%)

7756.7 hits per line

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

93.96
/src/ast_matchers.lua
1
local asts = require("ast")
73✔
2

3
local AstMatcher = {}
73✔
4

5
local function OR(...)
6
  local fs = {...}
803✔
7
  return function(ast)
8
    local result = false
141,758✔
9
    for i, f in ipairs(fs) do
589,699✔
10
      result = result or f(ast)
447,941✔
11
    end
12
    return result
141,758✔
13
  end
14
end
15

16
local function AND(...)
17
  local fs = {...}
2,920✔
18
  return function(ast)
19
    local result = nil
20
    for i, f in ipairs(fs) do
1,646,859✔
21
      if result == nil then
1,103,745✔
22
        result = f(ast)
543,114✔
23
      else
24
        result = result and f(ast)
560,631✔
25
      end
26
    end
27
    return result
543,114✔
28
  end
29
end
30

31
local function NOT(f)
32
  return function(ast)
33
    return not f(ast)
41,298✔
34
  end
35
end
36

37
local function is(ast, name) return ast.name == name end
73✔
38
local function any(ast) return ast ~= nil end
29,669✔
39

40
local function has(name, matcher)
41
  return function (ast)
42
    return matcher(ast[name])
821,738✔
43
  end
44
end
45

46
local function eq(expected)
47
  return function (val)
48
    return val == expected
671,631✔
49
  end
50
end
51

52
local function has_name(name) return has("name", eq(name)) end
1,971✔
53
local function has_op(name) return has("op", eq(name)) end
803✔
54
local function has_exp(matcher) return has("exp", matcher) end
1,825✔
55
local function has_tbl(matcher) return has("tbl", matcher) end
365✔
56
local function has_key(matcher) return has("key", matcher) end
365✔
57
local function has_p1(matcher) return has("p1", matcher) end
657✔
58
local function has_p2(matcher) return has("p2", matcher) end
584✔
59
local function has_value(val) return has("value", eq(val)) end
219✔
60

61
local is_identifier = has_name("identifier")
73✔
62
local is_literal = has_name("literal")
73✔
63
local is_stack_consume = has_name("stack_consume")
73✔
64
local is_assignment = AND(has_name("assignment"), has_exp(is_stack_consume))
73✔
65
local is_if = AND(has_name("if"), has_exp(is_stack_consume))
73✔
66
local is_init_local = AND(has_name("init_local"), has_exp(is_stack_consume))
73✔
67
local is_push_binop = AND(has_name("push"), has_exp(has_name("bin_op")))
73✔
68
local is_push_unop  = AND(has_name("push"), has_exp(has_name("unary_op")))
73✔
69
local is_pop = AND(is_stack_consume, has_op("pop"))
73✔
70

71
local is_literal_tbl_at = AND(
146✔
72
  has_name("table_at"),
73✔
73
  AND(
146✔
74
    OR(has_tbl(is_identifier), has_tbl(is_literal)),
73✔
75
    OR(has_key(is_identifier), has_key(is_literal))))
73✔
76

77
local is_const = OR(is_identifier, is_literal, is_literal_tbl_at)
73✔
78
local is_push_const = AND(has_name("push"), has_exp(is_const))
73✔
79
local is_push_unop_pop = AND(is_push_unop, has_exp(has_exp(is_stack_consume)))
73✔
80
local is_stack_op = has_name("stack_op")
73✔
81
local is_stack_peek = has_name("stack_peek")
73✔
82
local is_dup  = AND(is_stack_op, has_op("dup"))
73✔
83
local is_2dup = AND(is_stack_op, has_op("dup2"))
73✔
84
local is_over = AND(is_stack_op, has_op("over"))
73✔
85
local is_tos  = AND(is_stack_peek, has_op("tos"))
73✔
86
local has_p1_pop = has_p1(has_op("pop"))
73✔
87
local has_p2_pop = has_p2(has_op("pop"))
73✔
88
local has_lit_value = AND(is_literal, has("value", eq(1)))
73✔
89

90
local is_push_binop_pop = AND(
146✔
91
  has_name("push"),
73✔
92
  has_exp(AND(
146✔
93
             has_name("bin_op"),
73✔
94
             has_p1(is_stack_consume),
73✔
95
             has_p2(is_stack_consume))))
73✔
96

97
local is_exp_binop_pop = AND(
146✔
98
  has("exp", any),
73✔
99
  has_exp(AND(
146✔
100
             has_name("bin_op"),
73✔
101
             has_p1(is_stack_consume),
73✔
102
             has_p2(is_stack_consume))))
73✔
103

104
local is_wrapped_binop_tos = AND(
146✔
105
  has("exp", any),
73✔
106
  has_exp(AND(
146✔
107
             has_name("bin_op"),
73✔
108
             OR(
146✔
109
               AND(has_p1(is_tos)),
73✔
110
               AND(has_p2(is_tos), NOT(has_p1(is_stack_consume)))))))
73✔
111

112
local is_inc = AND(
146✔
113
  has_name("push"),
73✔
114
  has_exp(AND(
146✔
115
            has_name("bin_op"),
73✔
116
            has_op("+"),
73✔
117
            OR(
146✔
118
              AND(has_p1_pop, has_p2(has_value(1))),
73✔
119
              AND(has_p2_pop, has_p1(has_value(1)))))))
73✔
120

121
local is_neg = AND(
146✔
122
  has_name("push"),
73✔
123
  has_exp(AND(
146✔
124
            has_name("unary_op"),
73✔
125
            has_exp(is_pop),
73✔
126
            has_op("not"))))
73✔
127

128
local is_wrapped_binop_free_operand = AND(
146✔
129
  has("exp", any),
73✔
130
  has_exp(AND(
146✔
131
             has_name("bin_op"),
73✔
132
             OR(has_p1_pop,
146✔
133
                has_p2_pop))))
73✔
134

135
local inlined_push_unop = AND(
146✔
136
  is_push_unop,
73✔
137
  NOT(has_exp(has_exp(is_stack_consume))))
73✔
138

139
local inlined_push_binop = AND(
146✔
140
  is_push_binop,
73✔
141
  OR(
146✔
142
    -- fully inlined
143
    AND(NOT(has_exp(has_p1(is_stack_consume)), NOT(has_exp(has_p2(is_stack_consume))))),
73✔
144
    -- partially inlined
145
    AND(has_exp(has_p1_pop), NOT(has_exp(has_p2(is_stack_consume)))),
73✔
146
    AND(has_exp(has_p2_pop), NOT(has_exp(has_p1(is_stack_consume))))))
73✔
147

148
local is_tbl_at = AND(
146✔
149
  has_name("push"),
73✔
150
  has_exp(
146✔
151
    AND(
146✔
152
      has_name("table_at"),
73✔
153
      has_tbl(is_stack_consume),
73✔
154
      has_key(is_stack_consume))))
73✔
155

156
local is_tbl_put = AND(
146✔
157
  has_name("table_put"),
73✔
158
  has_tbl(is_stack_consume),
73✔
159
  has_key(is_stack_consume))
73✔
160

161
function AstMatcher:new(name, parts)
73✔
162
  local obj = {name = name, parts = parts, logging = false}
1,752✔
163
  setmetatable(obj, self)
1,752✔
164
  self.__index = self
1,752✔
165
  return obj
1,752✔
166
end
167

168
function AstMatcher:matches(ast, start)
73✔
169
  for i, matcher in ipairs(self.parts) do
319,587✔
170
    if start + i -1 > #ast then return false end
318,731✔
171
    local node = ast[start + i -1]
317,902✔
172
    if not matcher(node) then
317,902✔
173
      return false
230,035✔
174
    end
175
  end
176
  return true
856✔
177
end
178

179
function AstMatcher:optimize(ast, i, result)
73✔
180
  error("not implemented")
×
181
end
182

183
function AstMatcher:log(message)
73✔
184
  if self.logging then
885✔
185
    print("[OPTI] " .. self.name .. ": " .. message)
86✔
186
  end
187
end
188

189
function AstMatcher:size()
73✔
190
  return #self.parts
856✔
191
end
192

193
AtParamsInline = AstMatcher:new()
73✔
194
AtParamsInlineP2 = AstMatcher:new()
73✔
195
PutParamsInline = AstMatcher:new()
73✔
196
StackOpBinaryInline = AstMatcher:new()
73✔
197
BinaryInline = AstMatcher:new()
73✔
198
BinaryInlineP2 = AstMatcher:new()
73✔
199
BinaryConstBinaryInline = AstMatcher:new()
73✔
200
InlineGeneralUnary = AstMatcher:new()
73✔
201
TosBinaryInline = AstMatcher:new()
73✔
202
IncInline = AstMatcher:new()
73✔
203
NegInline = AstMatcher:new()
73✔
204
NegBinInline = AstMatcher:new()
73✔
205

206
--[[
207
 Inline table at parameters
208
  1.)  t  4 at   =>   PUSH(t[4])
209
]]--
210
function AtParamsInline:optimize(ast, i, result)
146✔
211
  self:log("inlining tbl at params")
43✔
212
  local tbl, idx, op = ast[i], ast[i + 1], ast[i + 2]
43✔
213
  op.exp.tbl = tbl.exp
43✔
214
  op.exp.key = idx.exp
43✔
215
  table.insert(result, op)
43✔
216
end
217

218
--[[
219
 Inline table at index parameter
220
  1.)  ... 4 at   =>   PUSH(POP[4])
221
]]--
222
function AtParamsInlineP2:optimize(ast, i, result)
146✔
223
  self:log("inlining tbl at 2nd param")
10✔
224
  local tbl, idx, op = ast[i], ast[i + 1], ast[i + 2]
10✔
225
  if op.exp.tbl.name == "stack_consume" and
10✔
226
     op.exp.tbl.op == "pop2nd"
10✔
227
  then
228
    op.exp.tbl.op = "pop"
10✔
229
  end
230
  op.exp.key = idx.exp
10✔
231
  table.insert(result, tbl)
10✔
232
  table.insert(result, op)
10✔
233
end
234

235
--[[
236
 Inline table put parameters
237
  1.)  t 4 "abc" put   =>   t[4]="abc"
238
]]--
239
function PutParamsInline:optimize(ast, i, result)
146✔
240
  self:log("inlining tbl put params")
7✔
241
  local tbl, key, val, op = ast[i], ast[i + 1], ast[i + 2], ast[i + 3]
7✔
242
  op.tbl = tbl.exp
7✔
243
  op.key = key.exp
7✔
244
  op.value = val.exp
7✔
245
  table.insert(result, op)
7✔
246
end
247

248
--[[
249
 Inline assignment/init-local(used internal) operator's value operand
250

251
  1.) 123 -> v   =>   v = 123
252
  2.) false not   =>   PUSH(not false)
253
  3.) false not IF ... THEN   =>   IF not false THEN ... END
254
  4.) 10 v  <   IF ... THEN   =>   IF 10 < v    THEN ... END
255
  5.)    v      IF ... THEN   =>   IF v         THEN ... END
256
  6.) DUP IF .. THEN   =>   TOS IF .. THEN
257
  7.) OVER IF .. THEN   =>   TOS2 IF .. THEN
258
  8.) [ 1 2 3 ] DUP size   =>   PUSH(#TOS)
259
  9.) true false over not => PUSH(NOT TOS2)
260
]]--
261
function InlineGeneralUnary:optimize(ast, i, result)
146✔
UNCOV
262
  local p1, operator = ast[i], ast[i + 1]
×
263
  local target
UNCOV
264
  if is_push_unop_pop(operator) then
×
265
    -- unary is embedded into a push
UNCOV
266
    target = operator.exp
×
267
  else
UNCOV
268
    target = operator
×
269
  end
270

UNCOV
271
  if is_dup(p1) then
×
UNCOV
272
    self:log(operator.name .. " (dup)")
×
UNCOV
273
    target.exp.op = "tos"
×
UNCOV
274
    target.exp.name ="stack_peek"
×
UNCOV
275
  elseif is_over(p1) then
×
UNCOV
276
    self:log(operator.name .. " (over)")
×
UNCOV
277
    target.exp.op = "tos2"
×
UNCOV
278
    target.exp.name ="stack_peek"
×
279
  else
UNCOV
280
    self:log(operator.name)
×
UNCOV
281
    target.exp = p1.exp
×
282
  end
283

UNCOV
284
  table.insert(result, operator)
×
285
end
286

287
function InlineGeneralUnary:optimize(ast, i, result)
146✔
288
  local p1, operator = ast[i], ast[i + 1]
239✔
289
  local target
290
  if is_push_unop_pop(operator) then
239✔
291
    -- unary is embedded into a push
292
    target = operator.exp
24✔
293
  else
294
    target = operator
215✔
295
  end
296

297
  if is_dup(p1) then
239✔
298
    self:log(operator.name .. " (dup)")
8✔
299
    target.exp.op = "tos"
8✔
300
    target.exp.name ="stack_peek"
8✔
301
  elseif is_over(p1) then
231✔
302
    self:log(operator.name .. " (over)")
4✔
303
    target.exp.op = "tos2"
4✔
304
    target.exp.name ="stack_peek"
4✔
305
  else
306
    self:log(operator.name)
227✔
307
    target.exp = p1.exp
227✔
308
  end
309

310
  table.insert(result, operator)
239✔
311
end
312

313
--[[
314
 Inline DUP followed by binary operator
315

316
  1.) 3 DUP *      =>   PUSH(TOS * POP)
317
  2.) DUP DUP *    =>   PUSH(TOS * TOS)
318
  3.) 3 7 OVER +   =>   PUSH(TOS2 + POP)
319
  4.) 3 7 OVER -   =>   PUSH(POP  - TOS)
320
  5.) 1 2 2DUP +   =>   PUSH(TOS + TOS)
321
]]--
322
function StackOpBinaryInline:optimize(ast, i, result)
146✔
323
  local p1, p2, op = ast[i], ast[i + 1], ast[i + 2]
29✔
324
  self:log("inlining stack op in binary")
29✔
325
  if is_dup(p1) and is_dup(p2) then
29✔
326
    -- double dup
327
    self:log("dup dup")
7✔
328
    op.exp.p1.op = "tos"
7✔
329
    op.exp.p2.op = "tos"
7✔
330
    op.exp.p1.name = "stack_peek"
7✔
331
    op.exp.p2.name = "stack_peek"
7✔
332
    table.insert(result, op)
7✔
333
  elseif is_2dup(p2) then
22✔
334
    self:log("2dup")
3✔
335
    op.exp.p1.op = "tos2"
3✔
336
    op.exp.p2.op = "tos"
3✔
337
    op.exp.p1.name = "stack_peek"
3✔
338
    op.exp.p2.name = "stack_peek"
3✔
339
    table.insert(result, p1)
3✔
340
    table.insert(result, op)
3✔
341
  elseif is_dup(p2) then
19✔
342
    -- single dup
343
    self:log("single dup")
14✔
344
    op.exp.p1.op = "tos"
14✔
345
    op.exp.p1.name = "stack_peek"
14✔
346
    table.insert(result, p1)
14✔
347
    table.insert(result, op)
14✔
348
  elseif is_over(p2) then
5✔
349
    -- single over
350
    self:log("over")
5✔
351
    op.exp.p1.op = "pop"
5✔
352
    op.exp.p1.name = "stack_consume"
5✔
353
    op.exp.p2.op = "tos"
5✔
354
    op.exp.p2.name = "stack_peek"
5✔
355
    table.insert(result, p1)
5✔
356
    table.insert(result, op)
5✔
357
  else
358
    error("Unexpected p2: " .. tostring(p2.name))
×
359
  end
360
end
361

362
--[[
363
 Inline binary operator's ALL constant operands
364

365
  1.) 12      45 +   =>   PUSH(12   + 45)
366
  2.) 12      v1 +   =>   PUSH(12   + v1)
367
  3.) t 1 at  45 +   =>   PUSH(t[1] + 45)
368
]]--
369
function BinaryInline:optimize(ast, i, result)
146✔
370
  self:log("inlining binary operator params")
148✔
371
  local p1, p2, op = ast[i], ast[i + 1], ast[i + 2]
148✔
372
  op.exp.p1 = p1.exp
148✔
373
  op.exp.p2 = p2.exp
148✔
374
  table.insert(result, op)
148✔
375
end
376

377
--[[
378
 Inline binary operator's 2nd, constant operand
379
 Additonally replace DUP with DROP if applicable
380

381
  1.)      123 +   =>   PUSH(POP + 123)
382
  2.)  DUP 123 +   =>   PUSH(TOS + 123)
383
]]--
384
function BinaryInlineP2:optimize(ast, i, result)
146✔
385
  self:log("inlining binary operator's 2nd param")
238✔
386
  local p1, p2, op = ast[i], ast[i + 1], ast[i + 2]
238✔
387
  if op.exp.p1.name == "stack_consume" then
238✔
388
    if op.exp.p1.op == "pop2nd" then
238✔
389
      op.exp.p1.op = "pop"
167✔
390
    end
391
    if is_dup(p1) then
238✔
392
      op.exp.p1.op = "tos" -- inline if dup
47✔
393
      op.exp.p1.name = "stack_peek"
47✔
394
    end
395
  end
396
  op.exp.p2 = p2.exp -- inline const param
238✔
397
  if not is_dup(p1) then -- dup was inlined skip it
238✔
398
    table.insert(result, p1)
191✔
399
  end
400
  table.insert(result, op)
238✔
401
end
402

403
function BinaryConstBinaryInline:optimize(ast, i, result)
146✔
404
  self:log("inlining binary to binary operator")
58✔
405
  local bin, op = ast[i], ast[i + 1]
58✔
406
  local target = op.exp
58✔
407
  if target.p1.op == "pop" then
58✔
408
    target.p1 = bin.exp
53✔
409
    if target.p2.op == "tos" then
53✔
410
      target.p2 = bin.exp
×
411
    elseif target.p2.op == "pop2nd" then
53✔
412
      target.p2.op = "pop"
×
413
    end
414
  elseif target.p2.op == "pop" then
5✔
415
    target.p2 = bin.exp
5✔
416
    if target.p1.op == "tos" then
5✔
417
      target.p1 = bin.exp
3✔
418
    elseif target.p1.op == "pop2nd" then
2✔
419
      target.p1.op = "pop"
2✔
420
    end
421
  else -- shouldn't happen
422
    error("one of binary operator's param was expected to be stack_consume")
×
423
  end
424
  table.insert(result, op)
58✔
425
end
426

427
function TosBinaryInline:optimize(ast, i, result)
146✔
428
  self:log("inlining binary tos operand")
17✔
429
  local cons, op = ast[i], ast[i + 1]
17✔
430
  if op.exp.p1.op == "tos" then
17✔
431
    op.exp.p1 = cons.exp
15✔
432
  end
433
  if op.exp.p2.op == "tos" then
17✔
434
    op.exp.p2 = cons.exp
8✔
435
  end
436
  table.insert(result, cons)
17✔
437
  table.insert(result, op)
17✔
438
end
439

440
function IncInline:optimize(ast, i, result)
146✔
441
  self:log("inlining inc")
8✔
442
  table.insert(result, asts.stack_op("_inc"))
8✔
443
end
444

445
function NegInline:optimize(ast, i, result)
146✔
446
  self:log("inlining neg")
51✔
447
  table.insert(result, asts.stack_op("_neg"))
51✔
448
end
449

450
function NegBinInline:optimize(ast, i, result)
146✔
451
  self:log("inlining neg binary")
8✔
452
  local op, neg = ast[i], ast[i + 1]
8✔
453
  table.insert(result, asts.push(asts.unary_op("not", op.exp)))
8✔
454
end
455

456
return {
73✔
457

458
  PutParamsInline:new(
146✔
459
    "inline put params ",
73✔
460
    {is_push_const, is_push_const, is_push_const, is_tbl_put}),
73✔
461

462
  AtParamsInline:new(
146✔
463
    "inline at params",
73✔
464
    {is_push_const, is_push_const, is_tbl_at}),
73✔
465

466
  AtParamsInlineP2:new(
146✔
467
    "inline at p2",
73✔
468
    {NOT(is_push_const), is_push_const, is_tbl_at}),
73✔
469

470
  BinaryInline:new(
146✔
471
    "binary inline",
73✔
472
    {is_push_const, is_push_const, is_exp_binop_pop}),
73✔
473

474
  BinaryInlineP2:new(
146✔
475
    "binary p2 inline",
73✔
476
    {NOT(is_push_const), is_push_const, is_exp_binop_pop}),
73✔
477

478
  BinaryConstBinaryInline:new(
146✔
479
    "binary const binary inline",
73✔
480
     {OR(inlined_push_binop,
146✔
481
         inlined_push_unop),
73✔
482
      is_wrapped_binop_free_operand}),
73✔
483

484
  StackOpBinaryInline:new(
146✔
485
    "stackop binary inline",
73✔
486
    {any, OR(is_dup,
146✔
487
             is_2dup,
73✔
488
             is_over), is_push_binop_pop}),
146✔
489

490
  TosBinaryInline:new(
146✔
491
    "tos binary inline",
73✔
492
    {is_push_const, is_wrapped_binop_tos}),
73✔
493

494
  InlineGeneralUnary:new(
146✔
495
    "inline general unary",
73✔
496
    {OR(is_dup,
146✔
497
        is_over,
73✔
498
        is_push_const,
73✔
499
        is_push_unop,
73✔
500
        is_push_binop),
73✔
501
     OR(is_init_local,  -- init-local only optimizes one parameter
146✔
502
        is_assignment,
73✔
503
        is_if,
73✔
504
        is_push_unop_pop)}),
146✔
505

506
  IncInline:new("inline inc", {is_inc}),
73✔
507
  NegInline:new("inline neg", {is_neg}),
73✔
508

509
  NegBinInline:new(
146✔
510
    "inline negate binary",
73✔
511
    {is_push_binop, AND(is_stack_op, has_op("_neg"))}),
73✔
512
}
73✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc