Skip to content

Commit 89478f3

Browse files
authored
Optimize jump opcodes (#545)
1 parent 1c8c9e6 commit 89478f3

File tree

2 files changed

+188
-0
lines changed

2 files changed

+188
-0
lines changed

compiler/compiler.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ func Compile(tree *parser.Tree, config *conf.Config) (program *Program, err erro
4545
case reflect.Float64:
4646
c.emit(OpCast, 2)
4747
}
48+
if c.config.Optimize {
49+
c.optimize()
50+
}
4851
}
4952

5053
program = NewProgram(
@@ -1050,6 +1053,19 @@ func (c *compiler) derefInNeeded(node ast.Node) {
10501053
}
10511054
}
10521055

1056+
func (c *compiler) optimize() {
1057+
for i, op := range c.bytecode {
1058+
switch op {
1059+
case OpJumpIfTrue, OpJumpIfFalse, OpJumpIfNil, OpJumpIfNotNil:
1060+
target := i + c.arguments[i] + 1
1061+
for target < len(c.bytecode) && c.bytecode[target] == op {
1062+
target += c.arguments[target] + 1
1063+
}
1064+
c.arguments[i] = target - i - 1
1065+
}
1066+
}
1067+
}
1068+
10531069
func kind(node ast.Node) reflect.Kind {
10541070
t := node.Type()
10551071
if t == nil {

compiler/compiler_test.go

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,3 +384,175 @@ func TestCompile_OpCallFast(t *testing.T) {
384384
require.Equal(t, vm.OpCallFast, program.Bytecode[4])
385385
require.Equal(t, 3, program.Arguments[4])
386386
}
387+
388+
func TestCompile_optimizes_jumps(t *testing.T) {
389+
env := map[string]any{
390+
"a": true,
391+
"b": true,
392+
"c": true,
393+
"d": true,
394+
}
395+
type op struct {
396+
Bytecode vm.Opcode
397+
Arg int
398+
}
399+
tests := []struct {
400+
code string
401+
want []op
402+
}{
403+
{
404+
`let foo = true; let bar = false; let baz = true; foo || bar || baz`,
405+
[]op{
406+
{vm.OpTrue, 0},
407+
{vm.OpStore, 0},
408+
{vm.OpFalse, 0},
409+
{vm.OpStore, 1},
410+
{vm.OpTrue, 0},
411+
{vm.OpStore, 2},
412+
{vm.OpLoadVar, 0},
413+
{vm.OpJumpIfTrue, 5},
414+
{vm.OpPop, 0},
415+
{vm.OpLoadVar, 1},
416+
{vm.OpJumpIfTrue, 2},
417+
{vm.OpPop, 0},
418+
{vm.OpLoadVar, 2},
419+
},
420+
},
421+
{
422+
`a && b && c`,
423+
[]op{
424+
{vm.OpLoadFast, 0},
425+
{vm.OpJumpIfFalse, 5},
426+
{vm.OpPop, 0},
427+
{vm.OpLoadFast, 1},
428+
{vm.OpJumpIfFalse, 2},
429+
{vm.OpPop, 0},
430+
{vm.OpLoadFast, 2},
431+
},
432+
},
433+
{
434+
`a && b || c && d`,
435+
[]op{
436+
{vm.OpLoadFast, 0},
437+
{vm.OpJumpIfFalse, 2},
438+
{vm.OpPop, 0},
439+
{vm.OpLoadFast, 1},
440+
{vm.OpJumpIfTrue, 5},
441+
{vm.OpPop, 0},
442+
{vm.OpLoadFast, 2},
443+
{vm.OpJumpIfFalse, 2},
444+
{vm.OpPop, 0},
445+
{vm.OpLoadFast, 3},
446+
},
447+
},
448+
{
449+
`filter([1, 2, 3, 4, 5], # > 3 && # != 4 && # != 5)`,
450+
[]op{
451+
{vm.OpPush, 0},
452+
{vm.OpBegin, 0},
453+
{vm.OpJumpIfEnd, 26},
454+
{vm.OpPointer, 0},
455+
{vm.OpDeref, 0},
456+
{vm.OpPush, 1},
457+
{vm.OpMore, 0},
458+
{vm.OpJumpIfFalse, 18},
459+
{vm.OpPop, 0},
460+
{vm.OpPointer, 0},
461+
{vm.OpDeref, 0},
462+
{vm.OpPush, 2},
463+
{vm.OpEqual, 0},
464+
{vm.OpNot, 0},
465+
{vm.OpJumpIfFalse, 11},
466+
{vm.OpPop, 0},
467+
{vm.OpPointer, 0},
468+
{vm.OpDeref, 0},
469+
{vm.OpPush, 3},
470+
{vm.OpEqual, 0},
471+
{vm.OpNot, 0},
472+
{vm.OpJumpIfFalse, 4},
473+
{vm.OpPop, 0},
474+
{vm.OpIncrementCount, 0},
475+
{vm.OpPointer, 0},
476+
{vm.OpJump, 1},
477+
{vm.OpPop, 0},
478+
{vm.OpIncrementIndex, 0},
479+
{vm.OpJumpBackward, 27},
480+
{vm.OpGetCount, 0},
481+
{vm.OpEnd, 0},
482+
{vm.OpArray, 0},
483+
},
484+
},
485+
{
486+
`let foo = true; let bar = false; let baz = true; foo && bar || baz`,
487+
[]op{
488+
{vm.OpTrue, 0},
489+
{vm.OpStore, 0},
490+
{vm.OpFalse, 0},
491+
{vm.OpStore, 1},
492+
{vm.OpTrue, 0},
493+
{vm.OpStore, 2},
494+
{vm.OpLoadVar, 0},
495+
{vm.OpJumpIfFalse, 2},
496+
{vm.OpPop, 0},
497+
{vm.OpLoadVar, 1},
498+
{vm.OpJumpIfTrue, 2},
499+
{vm.OpPop, 0},
500+
{vm.OpLoadVar, 2},
501+
},
502+
},
503+
{
504+
`true ?? nil ?? nil ?? nil`,
505+
[]op{
506+
{vm.OpTrue, 0},
507+
{vm.OpJumpIfNotNil, 8},
508+
{vm.OpPop, 0},
509+
{vm.OpNil, 0},
510+
{vm.OpJumpIfNotNil, 5},
511+
{vm.OpPop, 0},
512+
{vm.OpNil, 0},
513+
{vm.OpJumpIfNotNil, 2},
514+
{vm.OpPop, 0},
515+
{vm.OpNil, 0},
516+
},
517+
},
518+
{
519+
`let m = {"a": {"b": {"c": 1}}}; m?.a?.b?.c`,
520+
[]op{
521+
{vm.OpPush, 0},
522+
{vm.OpPush, 1},
523+
{vm.OpPush, 2},
524+
{vm.OpPush, 3},
525+
{vm.OpPush, 3},
526+
{vm.OpMap, 0},
527+
{vm.OpPush, 3},
528+
{vm.OpMap, 0},
529+
{vm.OpPush, 3},
530+
{vm.OpMap, 0},
531+
{vm.OpStore, 0},
532+
{vm.OpLoadVar, 0},
533+
{vm.OpJumpIfNil, 8},
534+
{vm.OpPush, 0},
535+
{vm.OpFetch, 0},
536+
{vm.OpJumpIfNil, 5},
537+
{vm.OpPush, 1},
538+
{vm.OpFetch, 0},
539+
{vm.OpJumpIfNil, 2},
540+
{vm.OpPush, 2},
541+
{vm.OpFetch, 0},
542+
},
543+
},
544+
}
545+
546+
for _, test := range tests {
547+
t.Run(test.code, func(t *testing.T) {
548+
program, err := expr.Compile(test.code, expr.Env(env))
549+
require.NoError(t, err)
550+
551+
require.Equal(t, len(test.want), len(program.Bytecode))
552+
for i, op := range test.want {
553+
require.Equal(t, op.Bytecode, program.Bytecode[i])
554+
require.Equalf(t, op.Arg, program.Arguments[i], "at %d", i)
555+
}
556+
})
557+
}
558+
}

0 commit comments

Comments
 (0)