rpiet 0.1 → 0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/Gemfile +12 -0
- data/bin/color_wheel +84 -0
- data/bin/image_gen +39 -0
- data/bin/rpiet +68 -11
- data/images/counter.txt +7 -0
- data/lib/rpiet/asg/graph_interpreter.rb +39 -0
- data/lib/rpiet/asg/parser.rb +156 -0
- data/lib/rpiet/asg/visitor.rb +66 -0
- data/lib/rpiet/asg.rb +336 -0
- data/lib/rpiet/codel_chooser.rb +32 -4
- data/lib/rpiet/color.rb +70 -25
- data/lib/rpiet/cycle.rb +18 -7
- data/lib/rpiet/debugger/debugger.rb +298 -0
- data/lib/rpiet/debugger/stylesheet.css +88 -0
- data/lib/rpiet/direction_pointer.rb +49 -18
- data/lib/rpiet/event_handler.rb +62 -7
- data/lib/rpiet/group.rb +25 -8
- data/lib/rpiet/image/ascii_image.rb +8 -20
- data/lib/rpiet/image/image.rb +8 -3
- data/lib/rpiet/image/url_image.rb +28 -14
- data/lib/rpiet/interpreter.rb +72 -72
- data/lib/rpiet/ir/assembler.rb +87 -0
- data/lib/rpiet/ir/builder.rb +255 -0
- data/lib/rpiet/ir/cfg.rb +494 -0
- data/lib/rpiet/ir/instructions.rb +536 -0
- data/lib/rpiet/ir/ir_cfg_interpreter.rb +23 -0
- data/lib/rpiet/ir/ir_interpreter.rb +101 -0
- data/lib/rpiet/ir/ir_native_interpreter.rb +77 -0
- data/lib/rpiet/ir/jruby_backend.rb +279 -0
- data/lib/rpiet/ir/operands.rb +28 -0
- data/lib/rpiet/ir/passes/data_flow_problem.rb +32 -0
- data/lib/rpiet/ir/passes/flow_graph_node.rb +76 -0
- data/lib/rpiet/ir/passes/peephole.rb +214 -0
- data/lib/rpiet/ir/passes/push_pop_elimination_pass.rb +112 -0
- data/lib/rpiet/live_machine_state.rb +15 -0
- data/lib/rpiet/machine.rb +62 -32
- data/lib/rpiet/source.rb +83 -0
- data/lib/rpiet/version.rb +1 -1
- data/lib/rpiet.rb +2 -2
- data/rpiet.gemspec +19 -0
- data/spec/asg/visitor_spec.rb +41 -0
- data/spec/cycle_spec.rb +34 -34
- data/spec/direction_pointer_spec.rb +33 -6
- data/spec/group_spec.rb +73 -48
- data/spec/interpreter_spec.rb +161 -12
- data/spec/ir/assembler_spec.rb +122 -0
- data/spec/ir/builder_spec.rb +20 -0
- data/spec/ir/cfg_spec.rb +151 -0
- data/spec/ir/ir_interpreter_spec.rb +102 -0
- data/spec/ir/passes/push_pop_elimination_pass_spec.rb +34 -0
- data/spec/machine_spec.rb +5 -3
- data/spec/source_spec.rb +69 -0
- data/spec/spec_helper.rb +78 -0
- metadata +54 -16
- data/images/nfib.png +0 -0
    
        data/spec/group_spec.rb
    CHANGED
    
    | @@ -1,57 +1,82 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
             | 
| 1 | 
            +
            require_relative 'spec_helper'
         | 
| 2 | 
            +
            require_relative '../lib/rpiet/group'
         | 
| 3 | 
            +
            require_relative '../lib/rpiet/color'
         | 
| 3 4 |  | 
| 4 5 | 
             
            describe "Group" do
         | 
| 5 | 
            -
               | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 6 | 
            +
              SQUARE = <<-EOS
         | 
| 7 | 
            +
            .###.
         | 
| 8 | 
            +
            .###.
         | 
| 9 | 
            +
            .###.
         | 
| 10 | 
            +
              EOS
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              WACKY = <<-EOS
         | 
| 13 | 
            +
            .#.#.
         | 
| 14 | 
            +
            #####
         | 
| 15 | 
            +
            .#.#.
         | 
| 16 | 
            +
              EOS
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              HOOKY = <<-EOS
         | 
| 19 | 
            +
            .#.#.#
         | 
| 20 | 
            +
            ######
         | 
| 21 | 
            +
            .#.#..
         | 
| 22 | 
            +
              EOS
         | 
| 23 | 
            +
             | 
| 24 | 
            +
             | 
| 25 | 
            +
              let(:light_cyan) { RPiet::Color.color_for('0x0000c0') }
         | 
| 26 | 
            +
              let(:black) { RPiet::Color.color_for('0x000000') }
         | 
| 27 | 
            +
              let(:blue) { RPiet::Color.color_for('0x0000ff') }
         | 
| 28 | 
            +
              let(:square_group) { create_group(light_cyan, SQUARE) }
         | 
| 29 | 
            +
              let(:wacky_group) { create_group(black, WACKY) }
         | 
| 30 | 
            +
              let(:hooky_group) { create_group(blue, HOOKY) }
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              it "knows its color" do
         | 
| 33 | 
            +
                expect(square_group.color).to eq light_cyan
         | 
| 34 | 
            +
                expect(wacky_group.color).to eq black
         | 
| 35 | 
            +
                expect(hooky_group.color).to eq blue
         | 
| 24 36 | 
             
              end
         | 
| 25 37 |  | 
| 26 38 | 
             
              it "knows its size" do
         | 
| 27 | 
            -
                 | 
| 39 | 
            +
                expect(square_group.size).to eq(9)
         | 
| 40 | 
            +
                expect(wacky_group.size).to eq(9)
         | 
| 41 | 
            +
                expect(hooky_group.size).to eq(11)
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
              it 'picks the right points (wacky)' do
         | 
| 45 | 
            +
                expect(wacky_group.rr).to eq([4,1])
         | 
| 46 | 
            +
                expect(wacky_group.rl).to eq([4,1])
         | 
| 47 | 
            +
                expect(wacky_group.lr).to eq([0,1])
         | 
| 48 | 
            +
                expect(wacky_group.ll).to eq([0,1])
         | 
| 49 | 
            +
                expect(wacky_group.dr).to eq([1,2])
         | 
| 50 | 
            +
                expect(wacky_group.dl).to eq([3,2])
         | 
| 51 | 
            +
                expect(wacky_group.ur).to eq([3,0])
         | 
| 52 | 
            +
                expect(wacky_group.ul).to eq([1,0])
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
              it 'picks the right points (simple)' do
         | 
| 56 | 
            +
                expect(square_group.rr).to eq([3,2])
         | 
| 57 | 
            +
                expect(square_group.rl).to eq([3,0])
         | 
| 58 | 
            +
                expect(square_group.lr).to eq([1,0])
         | 
| 59 | 
            +
                expect(square_group.ll).to eq([1,2])
         | 
| 60 | 
            +
                expect(square_group.dr).to eq([1,2])
         | 
| 61 | 
            +
                expect(square_group.dl).to eq([3,2])
         | 
| 62 | 
            +
                expect(square_group.ur).to eq([3,0])
         | 
| 63 | 
            +
                expect(square_group.ul).to eq([1,0])
         | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
              it 'picks the right points (hooky)' do
         | 
| 67 | 
            +
                expect(hooky_group.rr).to eq([5,1])
         | 
| 68 | 
            +
                expect(hooky_group.rl).to eq([5,0])
         | 
| 69 | 
            +
                expect(hooky_group.lr).to eq([0,1])
         | 
| 70 | 
            +
                expect(hooky_group.ll).to eq([0,1])
         | 
| 71 | 
            +
                expect(hooky_group.dr).to eq([1,2])
         | 
| 72 | 
            +
                expect(hooky_group.dl).to eq([3,2])
         | 
| 73 | 
            +
                expect(hooky_group.ur).to eq([5,0])
         | 
| 74 | 
            +
                expect(hooky_group.ul).to eq([1,0])
         | 
| 28 75 | 
             
              end
         | 
| 29 76 |  | 
| 30 | 
            -
              it  | 
| 31 | 
            -
                 | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
                @pvm.dp.rotate! # dp: RIGHT -> DOWN
         | 
| 35 | 
            -
                @pvm.cc.switch! # cc: RIGHT -> LEFT
         | 
| 36 | 
            -
                @group.point_for(@pvm).should == [0, 7] # dp: DOWN cc: LEFT -> LR
         | 
| 37 | 
            -
                @pvm.cc.switch! # cc: LEFT -> RIGHT
         | 
| 38 | 
            -
                @group.point_for(@pvm).should == [0, 7] # dp: DOWN cc: RIGHT -> LL
         | 
| 39 | 
            -
                @pvm.dp.rotate! # dp: DOWN -> LEFT
         | 
| 40 | 
            -
                @pvm.cc.switch! # cc: RIGHT -> LEFT
         | 
| 41 | 
            -
                @group.point_for(@pvm).should == [0, 7] # dp: LEFT cc: LEFT -> LL
         | 
| 42 | 
            -
                @pvm.cc.switch! # cc: LEFT -> RIGHT
         | 
| 43 | 
            -
                @group.point_for(@pvm).should == [0, 3] # dp: LEFT cc: RIGHT -> UL
         | 
| 44 | 
            -
                @pvm.dp.rotate! # dp: LEFT -> UP
         | 
| 45 | 
            -
                @pvm.cc.switch! 
         | 
| 46 | 
            -
                @group.point_for(@pvm).should == [0, 3] # dp: UP cc: LEFT -> UL
         | 
| 47 | 
            -
                @pvm.cc.switch! # cc: LEFT -> RIGHT
         | 
| 48 | 
            -
                @group.point_for(@pvm).should == [4, 3] # dp: UP cc: RIGHT -> UR
         | 
| 49 | 
            -
             | 
| 50 | 
            -
                # Since last group only has single wide bottom let's try another
         | 
| 51 | 
            -
                @pvm.cc.switch! # cc: RIGHT -> LEFT
         | 
| 52 | 
            -
                @pvm.dp.rotate! 2 # dp: UP -> DOWN
         | 
| 53 | 
            -
                @group2.point_for(@pvm).should == [7, 3] # dp: DOWN cc: LEFT -> LR
         | 
| 54 | 
            -
                @pvm.cc.switch! # cc: RIGHT -> LEFT
         | 
| 55 | 
            -
                @group2.point_for(@pvm).should == [6, 3] # dp: DOWN cc: RIGHT -> LL
         | 
| 77 | 
            +
              it 'has edges' do
         | 
| 78 | 
            +
                expect(square_group.edges.to_a).to match_array([[1,0,:left], [1,0,:up], [2,0,:up], [3,0,:up], [3,0,:right],
         | 
| 79 | 
            +
                                                                [1,1,:left], [3,1,:right],
         | 
| 80 | 
            +
                                                                [1,2,:left], [1,2,:down], [2,2,:down], [3,2,:down],[3,2,:right]])
         | 
| 56 81 | 
             
              end
         | 
| 57 82 | 
             
            end
         | 
    
        data/spec/interpreter_spec.rb
    CHANGED
    
    | @@ -1,14 +1,163 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
             | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
                 | 
| 1 | 
            +
            require_relative 'spec_helper'
         | 
| 2 | 
            +
            require_relative '../lib/rpiet/asg/graph_interpreter'
         | 
| 3 | 
            +
            require_relative '../lib/rpiet/interpreter'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            describe "RPiet Runtimes" do
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              let(:push_pop) do # [push 2, pop]*
         | 
| 8 | 
            +
                create_image <<-EOS
         | 
| 9 | 
            +
            nb db ++
         | 
| 10 | 
            +
            nb ++ ++
         | 
| 11 | 
            +
            ++ ++ ++
         | 
| 12 | 
            +
                EOS
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              let(:push_add) do # [push 2, push 1, add, ...]
         | 
| 16 | 
            +
                create_image <<-EOS
         | 
| 17 | 
            +
            nb db lb lm ++
         | 
| 18 | 
            +
            nb ++ ++ lm ++
         | 
| 19 | 
            +
            ++ ++ ++ ++ ++
         | 
| 20 | 
            +
                EOS
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              let(:push_subtract) do # [push 2, push 1, subtract, ...]
         | 
| 24 | 
            +
                create_image <<-EOS
         | 
| 25 | 
            +
            nb db lb nm ++
         | 
| 26 | 
            +
            nb ++ ++ nm ++
         | 
| 27 | 
            +
            ++ ++ ++ ++ ++
         | 
| 28 | 
            +
                EOS
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
              let(:push_multiply) do # [push 2, push 2, multiply, ...]
         | 
| 32 | 
            +
                create_image <<-EOS
         | 
| 33 | 
            +
            nb db lb dm ++
         | 
| 34 | 
            +
            nb db ++ dm ++
         | 
| 35 | 
            +
            ++ ++ ++ ++ ++
         | 
| 36 | 
            +
                EOS
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              let(:push_divide) do # [push 2, push 2, divide, ...]
         | 
| 40 | 
            +
                create_image <<-EOS
         | 
| 41 | 
            +
            nb db lb lr ++
         | 
| 42 | 
            +
            nb db ++ lr ++
         | 
| 43 | 
            +
            ++ ++ ++ ++ ++
         | 
| 44 | 
            +
                EOS
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
              let(:divide_by_zero) do # [push 2, push 1, push 1, subtract, divide]
         | 
| 48 | 
            +
                create_image <<-EOS
         | 
| 49 | 
            +
            nb db lb nb dm dy ++
         | 
| 50 | 
            +
            nb ++ ++ ++ ++ dy ++
         | 
| 51 | 
            +
            ++ ++ ++ ++ ++ ++ ++
         | 
| 52 | 
            +
                EOS
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
              let(:push_mod) do # [push 2, push 2, mod, ...]
         | 
| 56 | 
            +
                create_image <<-EOS
         | 
| 57 | 
            +
            nb db lb nr ++
         | 
| 58 | 
            +
            nb db ++ nr ++
         | 
| 59 | 
            +
            ++ ++ ++ ++ ++
         | 
| 60 | 
            +
                EOS
         | 
| 61 | 
            +
              end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
              let(:push_not) do # [push 2, push 2, mod, not, ...]
         | 
| 64 | 
            +
                create_image <<-EOS
         | 
| 65 | 
            +
            nb db lb nr lg ++
         | 
| 66 | 
            +
            nb db ++ nr lg ++
         | 
| 67 | 
            +
            ++ ++ ++ ++ ++ ++
         | 
| 68 | 
            +
                EOS
         | 
| 69 | 
            +
              end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
              let(:skip_white) do
         | 
| 72 | 
            +
                create_image <<-EOS
         | 
| 73 | 
            +
            nb .. .. db ++
         | 
| 74 | 
            +
            nb .. .. ++ ++
         | 
| 75 | 
            +
            ++ .. .. ++ ++
         | 
| 76 | 
            +
                EOS
         | 
| 77 | 
            +
              end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
              [RPiet::Interpreter, RPiet::ASG::GraphInterpreter].each do |runtime|
         | 
| 80 | 
            +
                describe runtime do
         | 
| 81 | 
            +
                  it "Can push and pop" do
         | 
| 82 | 
            +
                    interpreter = runtime.new push_pop
         | 
| 83 | 
            +
                    interpreter.reset
         | 
| 84 | 
            +
                    interpreter.next_step
         | 
| 85 | 
            +
                    expect(interpreter.stack).to eq [2]
         | 
| 86 | 
            +
                    interpreter.next_step
         | 
| 87 | 
            +
                    expect(interpreter.stack).to eq []
         | 
| 88 | 
            +
                  end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                  it "Can push and add" do
         | 
| 91 | 
            +
                    interpreter = runtime.new push_add
         | 
| 92 | 
            +
                    interpreter.reset
         | 
| 93 | 
            +
                    2.times { interpreter.next_step }
         | 
| 94 | 
            +
                    expect(interpreter.stack).to eq [2, 1]
         | 
| 95 | 
            +
                    interpreter.next_step
         | 
| 96 | 
            +
                    expect(interpreter.stack).to eq [3]
         | 
| 97 | 
            +
                  end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                  it "Can push and subtract" do
         | 
| 100 | 
            +
                    interpreter = runtime.new push_subtract
         | 
| 101 | 
            +
                    interpreter.reset
         | 
| 102 | 
            +
                    2.times { interpreter.next_step }
         | 
| 103 | 
            +
                    expect(interpreter.stack).to eq [2, 1]
         | 
| 104 | 
            +
                    interpreter.next_step
         | 
| 105 | 
            +
                    expect(interpreter.stack).to eq [1]
         | 
| 106 | 
            +
                  end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                  it "Can push and multiply" do
         | 
| 109 | 
            +
                    interpreter = runtime.new push_multiply
         | 
| 110 | 
            +
                    interpreter.reset
         | 
| 111 | 
            +
                    2.times { interpreter.next_step }
         | 
| 112 | 
            +
                    expect(interpreter.stack).to eq [2, 2]
         | 
| 113 | 
            +
                    interpreter.next_step
         | 
| 114 | 
            +
                    expect(interpreter.stack).to eq [4]
         | 
| 115 | 
            +
                  end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                  it "Can push and divide" do
         | 
| 118 | 
            +
                    interpreter = runtime.new push_divide
         | 
| 119 | 
            +
                    interpreter.reset
         | 
| 120 | 
            +
                    2.times { interpreter.next_step }
         | 
| 121 | 
            +
                    expect(interpreter.stack).to eq [2, 2]
         | 
| 122 | 
            +
                    interpreter.next_step
         | 
| 123 | 
            +
                    expect(interpreter.stack).to eq [1]
         | 
| 124 | 
            +
                  end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                  it "Can divide by zero" do
         | 
| 127 | 
            +
                    interpreter = runtime.new divide_by_zero
         | 
| 128 | 
            +
                    interpreter.reset
         | 
| 129 | 
            +
                    4.times { interpreter.next_step }
         | 
| 130 | 
            +
                    expect(interpreter.stack).to eq [2, 0]
         | 
| 131 | 
            +
                    interpreter.next_step
         | 
| 132 | 
            +
                    expect(interpreter.stack).to eq [99999999]
         | 
| 133 | 
            +
                  end
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                  it "Can push and mod" do
         | 
| 136 | 
            +
                    interpreter = runtime.new push_mod
         | 
| 137 | 
            +
                    interpreter.reset
         | 
| 138 | 
            +
                    2.times { interpreter.next_step }
         | 
| 139 | 
            +
                    expect(interpreter.stack).to eq [2, 2]
         | 
| 140 | 
            +
                    interpreter.next_step
         | 
| 141 | 
            +
                    expect(interpreter.stack).to eq [0]
         | 
| 142 | 
            +
                  end
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                  it "Can push and not" do
         | 
| 145 | 
            +
                    interpreter = runtime.new push_not
         | 
| 146 | 
            +
                    interpreter.reset
         | 
| 147 | 
            +
                    2.times { interpreter.next_step }
         | 
| 148 | 
            +
                    expect(interpreter.stack).to eq [2, 2]
         | 
| 149 | 
            +
                    2.times { interpreter.next_step }
         | 
| 150 | 
            +
                    expect(interpreter.stack).to eq [1]
         | 
| 151 | 
            +
                  end
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                  it "Can skip white and push and pop" do
         | 
| 154 | 
            +
                    interpreter = runtime.new push_pop
         | 
| 155 | 
            +
                    interpreter.reset
         | 
| 156 | 
            +
                    interpreter.next_step
         | 
| 157 | 
            +
                    expect(interpreter.stack).to eq [2]
         | 
| 158 | 
            +
                    interpreter.next_step
         | 
| 159 | 
            +
                    expect(interpreter.stack).to eq []
         | 
| 160 | 
            +
                  end
         | 
| 161 | 
            +
                end
         | 
| 13 162 | 
             
              end
         | 
| 14 163 | 
             
            end
         | 
| @@ -0,0 +1,122 @@ | |
| 1 | 
            +
            require_relative '../spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            include RPiet::IR::Instructions
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            describe "RPiet::IR::Assembler" do
         | 
| 6 | 
            +
              context "individual instructions" do
         | 
| 7 | 
            +
                it "can load copy" do
         | 
| 8 | 
            +
                  instr = assemble("v1 = copy 1\n").first
         | 
| 9 | 
            +
                  expect(instr.operation).to eq(:copy)
         | 
| 10 | 
            +
                  expect(instr.operand).to be_numeric_operand(1)
         | 
| 11 | 
            +
                  expect(instr.result).to be_variable_operand("v1")
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                it "can load push" do
         | 
| 15 | 
            +
                  instr = assemble("push 10\n").first
         | 
| 16 | 
            +
                  expect(instr.operation).to eq(:push)
         | 
| 17 | 
            +
                  expect(instr.operand).to be_numeric_operand(10)
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                it "can load pop" do
         | 
| 21 | 
            +
                  instr = assemble("v1 = pop\n").first
         | 
| 22 | 
            +
                  expect(instr.operation).to eq(:pop)
         | 
| 23 | 
            +
                  expect(instr.result).to be_variable_operand("v1")
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                it "can load nout" do
         | 
| 27 | 
            +
                  instr = assemble("nout 12\n").first
         | 
| 28 | 
            +
                  expect(instr.operation).to eq(:nout)
         | 
| 29 | 
            +
                  expect(instr.operand).to be_numeric_operand(12)
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                it "can load cout" do
         | 
| 33 | 
            +
                  instr = assemble("cout 'a'\n").first
         | 
| 34 | 
            +
                  expect(instr.operation).to eq(:cout)
         | 
| 35 | 
            +
                  expect(instr.operand).to be_string_operand("a")
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                it "can load nin" do
         | 
| 39 | 
            +
                  instr = assemble("v1 = nin\n").first
         | 
| 40 | 
            +
                  expect(instr.operation).to eq(:nin)
         | 
| 41 | 
            +
                  expect(instr.result).to be_variable_operand("v1")
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                it "can load roll" do
         | 
| 45 | 
            +
                  instr = assemble("roll 1 10\n").first
         | 
| 46 | 
            +
                  expect(instr.operation).to eq(:roll)
         | 
| 47 | 
            +
                  expect(instr.depth).to be_numeric_operand(1)
         | 
| 48 | 
            +
                  expect(instr.num).to be_numeric_operand(10)
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                it "can load label" do
         | 
| 52 | 
            +
                  instr = assemble("label foo\n").first
         | 
| 53 | 
            +
                  expect(instr.operation).to eq(:label)
         | 
| 54 | 
            +
                  expect(instr.operand).to be_label_operand(:jump_reduction)
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                it "cannot load label with non-label operand" do
         | 
| 58 | 
            +
                  expect { assemble("label 1\n") }.to raise_error(ArgumentError)
         | 
| 59 | 
            +
                  expect { assemble("label 'a'\n") }.to raise_error(ArgumentError)
         | 
| 60 | 
            +
                  expect { assemble("v1 = copy 1\nlabel v1\n") }.to raise_error(ArgumentError)
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                context "can load infix math" do
         | 
| 64 | 
            +
                  %w[+ - * / % **].zip(%i[add sub mult div mod pow]).each do |oper, type|
         | 
| 65 | 
            +
                    it "can load #{oper}" do
         | 
| 66 | 
            +
                      instr = assemble("v1 = copy 2\nv2 = 1 #{oper} v1\n")[1]
         | 
| 67 | 
            +
                      expect(instr.operation).to eq(type)
         | 
| 68 | 
            +
                      expect(instr.operand1).to be_numeric_operand(1)
         | 
| 69 | 
            +
                      expect(instr.operand2).to be_variable_operand("v1")
         | 
| 70 | 
            +
                      expect(instr.result).to be_variable_operand("v2")
         | 
| 71 | 
            +
                    end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                    it "cannot load non-numeric/variable operands" do
         | 
| 74 | 
            +
                      expect { assemble("v1 = 10 #{oper} label\n")}.to raise_error(ArgumentError)
         | 
| 75 | 
            +
                      expect { assemble("v1 = 10 #{oper} 'a'\n")}.to raise_error(ArgumentError)
         | 
| 76 | 
            +
                    end
         | 
| 77 | 
            +
                  end
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                it "can process gt(>)" do
         | 
| 81 | 
            +
                  instr = assemble("v1 = 1 > 2\n").first
         | 
| 82 | 
            +
                  expect(instr.operation).to eq(:gt)
         | 
| 83 | 
            +
                  expect(instr.result).to be_variable_operand("v1")
         | 
| 84 | 
            +
                  expect(instr.operand1).to be_numeric_operand(1)
         | 
| 85 | 
            +
                  expect(instr.operand2).to be_numeric_operand(2)
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                context "can process branches" do
         | 
| 89 | 
            +
                  %w[!= ==].zip(%i[bne beq]).each do |oper, type|
         | 
| 90 | 
            +
                    it "can load #{oper}" do
         | 
| 91 | 
            +
                      instr = assemble("1 #{oper} 2 label\n").first
         | 
| 92 | 
            +
                      expect(instr.operation).to eq(type)
         | 
| 93 | 
            +
                      expect(instr.operand1).to be_numeric_operand(1)
         | 
| 94 | 
            +
                      expect(instr.operand2).to be_numeric_operand(2)
         | 
| 95 | 
            +
                      expect(instr.label).to be_label_operand(:label)
         | 
| 96 | 
            +
                    end
         | 
| 97 | 
            +
                  end
         | 
| 98 | 
            +
                end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                it "can load jump" do
         | 
| 101 | 
            +
                  instr = assemble("jump label\n").first
         | 
| 102 | 
            +
                  expect(instr.operation).to eq(:jump)
         | 
| 103 | 
            +
                  expect(instr.label).to be_label_operand(:label)
         | 
| 104 | 
            +
                end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                it "can load multiple instructions" do
         | 
| 107 | 
            +
                  instrs = assemble("push 10\nv1 = pop\n")
         | 
| 108 | 
            +
                  expect(instrs.size).to eq(2)
         | 
| 109 | 
            +
                  expect(instrs[0].operation).to eq(:push)
         | 
| 110 | 
            +
                  expect(instrs[1].operation).to eq(:pop)
         | 
| 111 | 
            +
                end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                it "is in single-static-assignment form (SSA)" do
         | 
| 114 | 
            +
                  expect { assemble("v1 = pop\nv2 = pop\nv1 = pop\n") }.to raise_error(ArgumentError)
         | 
| 115 | 
            +
                  expect { assemble("push v1\n") }.to raise_error(ArgumentError)
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                it "cannot use a non-variable as a result" do
         | 
| 119 | 
            +
                  expect { assemble("10 = pop\n")}.to raise_error(ArgumentError)
         | 
| 120 | 
            +
                end
         | 
| 121 | 
            +
              end
         | 
| 122 | 
            +
            end
         | 
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            require_relative '../spec_helper'
         | 
| 2 | 
            +
            require_relative '../../lib/rpiet/asg/parser'
         | 
| 3 | 
            +
            require_relative '../../lib/rpiet/ir/builder'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            describe "RPiet::Builder" do
         | 
| 6 | 
            +
              let(:cycle) do
         | 
| 7 | 
            +
                create_image <<-EOS
         | 
| 8 | 
            +
            nb db nb
         | 
| 9 | 
            +
            db ++ nb
         | 
| 10 | 
            +
            db db db
         | 
| 11 | 
            +
                EOS
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              it "can visit all nodes once plus one extra visit for a cycle" do
         | 
| 15 | 
            +
                graph = RPiet::ASG::Parser.new(cycle).run
         | 
| 16 | 
            +
                builder = RPiet::Builder.new
         | 
| 17 | 
            +
                builder.run graph
         | 
| 18 | 
            +
                #p builder.instructions
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
            end
         | 
    
        data/spec/ir/cfg_spec.rb
    ADDED
    
    | @@ -0,0 +1,151 @@ | |
| 1 | 
            +
            require_relative '../spec_helper'
         | 
| 2 | 
            +
            require_relative '../../lib/rpiet/asg/parser'
         | 
| 3 | 
            +
            require_relative '../../lib/rpiet/ir/builder'
         | 
| 4 | 
            +
            require_relative '../../lib/rpiet/ir/cfg'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
             | 
| 7 | 
            +
            describe "RPiet::IR::CFG" do
         | 
| 8 | 
            +
              let(:push_divide) {
         | 
| 9 | 
            +
                assemble("push 10\npush 2\nv1 = pop\nv2 = pop\nv3 = v2 / v1; push v3")
         | 
| 10 | 
            +
              }
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              let(:gtr) {
         | 
| 13 | 
            +
                assemble <<~EOS
         | 
| 14 | 
            +
                  10 != 2 true
         | 
| 15 | 
            +
                  push 0
         | 
| 16 | 
            +
                  jump end
         | 
| 17 | 
            +
                  label true
         | 
| 18 | 
            +
                  push 1
         | 
| 19 | 
            +
                  label end
         | 
| 20 | 
            +
                  exit
         | 
| 21 | 
            +
                EOS
         | 
| 22 | 
            +
              }
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              let(:pntr) {
         | 
| 25 | 
            +
                assemble <<~EOS
         | 
| 26 | 
            +
                  dpset 0
         | 
| 27 | 
            +
                  push 2
         | 
| 28 | 
            +
                  v25 = pop
         | 
| 29 | 
            +
                  v26 = dpget
         | 
| 30 | 
            +
                  v27 = v26 + v25
         | 
| 31 | 
            +
                  v28 = v27 % 4
         | 
| 32 | 
            +
                  dpset v28
         | 
| 33 | 
            +
                  v28 != 0 pntr[0]43
         | 
| 34 | 
            +
                  push 0
         | 
| 35 | 
            +
                  jump re.4008
         | 
| 36 | 
            +
                  label pntr[0]43
         | 
| 37 | 
            +
                  v28 != 1 pntr[1]43
         | 
| 38 | 
            +
                  push 1
         | 
| 39 | 
            +
                  jump re.4008
         | 
| 40 | 
            +
                  label pntr[1]43
         | 
| 41 | 
            +
                  v28 != 2 pntr[2]43
         | 
| 42 | 
            +
                  push 2
         | 
| 43 | 
            +
                  jump re.4008
         | 
| 44 | 
            +
                  label pntr[2]43
         | 
| 45 | 
            +
                  v28 != 3 re.4008
         | 
| 46 | 
            +
                  push 3
         | 
| 47 | 
            +
                  label re.4008
         | 
| 48 | 
            +
                  v29 = pop
         | 
| 49 | 
            +
                  exit
         | 
| 50 | 
            +
                EOS
         | 
| 51 | 
            +
              }
         | 
| 52 | 
            +
             | 
| 53 | 
            +
              let(:diamond) {
         | 
| 54 | 
            +
                assemble <<~EOS
         | 
| 55 | 
            +
                  push 10
         | 
| 56 | 
            +
                  push 5
         | 
| 57 | 
            +
                  10 != 2 true
         | 
| 58 | 
            +
                  push 0
         | 
| 59 | 
            +
                  jump end
         | 
| 60 | 
            +
                  label true
         | 
| 61 | 
            +
                  push 1
         | 
| 62 | 
            +
                  label end
         | 
| 63 | 
            +
                  v1 = pop
         | 
| 64 | 
            +
                  v2 = pop
         | 
| 65 | 
            +
                  v3 = pop
         | 
| 66 | 
            +
                  v4 = v3 / v2
         | 
| 67 | 
            +
                  push v4
         | 
| 68 | 
            +
                  exit
         | 
| 69 | 
            +
                EOS
         | 
| 70 | 
            +
              }
         | 
| 71 | 
            +
             | 
| 72 | 
            +
              let(:graph1) {
         | 
| 73 | 
            +
                assemble <<~EOS
         | 
| 74 | 
            +
                  push 10
         | 
| 75 | 
            +
                  push 5
         | 
| 76 | 
            +
                  10 != 2 true
         | 
| 77 | 
            +
                  label foo
         | 
| 78 | 
            +
                  push 0
         | 
| 79 | 
            +
                  jump end
         | 
| 80 | 
            +
                  label true
         | 
| 81 | 
            +
                  push 1
         | 
| 82 | 
            +
                  label end
         | 
| 83 | 
            +
                  v1 = pop
         | 
| 84 | 
            +
                  v2 = pop
         | 
| 85 | 
            +
                  v3 = pop
         | 
| 86 | 
            +
                  v4 = v3 / v2
         | 
| 87 | 
            +
                  push v4
         | 
| 88 | 
            +
                  1 != 2 foo
         | 
| 89 | 
            +
                  push 1
         | 
| 90 | 
            +
                  exit
         | 
| 91 | 
            +
                EOS
         | 
| 92 | 
            +
              }
         | 
| 93 | 
            +
             | 
| 94 | 
            +
              context "outgoing_edges" do
         | 
| 95 | 
            +
                it "can see all edges in a simple graph" do
         | 
| 96 | 
            +
                  cfg = RPiet::IR::CFG.new(gtr)
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                  entry_bb = cfg.entry_bb
         | 
| 99 | 
            +
                  fall_through = cfg.outgoing_target(entry_bb, :fall_through)
         | 
| 100 | 
            +
                  expect(fall_through.label).to start_with("fall_thru_")
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                  jump = cfg.outgoing_target(entry_bb, :jump)
         | 
| 103 | 
            +
                  expect(jump.label).to eq(:true)
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                  end_bb = cfg.outgoing_target(jump, :fall_through)
         | 
| 106 | 
            +
                  expect(end_bb.label).to eq(:end)
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                  end_bb2 = cfg.outgoing_target(fall_through, :jump)
         | 
| 109 | 
            +
                  expect(end_bb.label).to eq(:end)
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                  expect(end_bb).to eq(end_bb2)
         | 
| 112 | 
            +
                end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                it "can remove an edge" do
         | 
| 115 | 
            +
                  cfg = RPiet::IR::CFG.new(gtr)
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                  entry_bb = cfg.entry_bb
         | 
| 118 | 
            +
                  jump = cfg.outgoing_target(entry_bb, :jump)
         | 
| 119 | 
            +
                  expect(cfg.outgoing_targets(entry_bb).size).to eq(2)
         | 
| 120 | 
            +
                  cfg.remove_edge(entry_bb, jump)
         | 
| 121 | 
            +
                  expect(cfg.outgoing_targets(entry_bb).size).to eq(1)
         | 
| 122 | 
            +
                end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                it "can see see outgoing edges with a block" do
         | 
| 125 | 
            +
                  cfg = RPiet::IR::CFG.new(gtr)
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                  entry_bb = cfg.entry_bb
         | 
| 128 | 
            +
                  results = []
         | 
| 129 | 
            +
                  cfg.outgoing_edges(entry_bb) do |edge|
         | 
| 130 | 
            +
                    results << edge.target.label.to_s
         | 
| 131 | 
            +
                  end
         | 
| 132 | 
            +
                  results.sort!
         | 
| 133 | 
            +
                  expect(results[0]).to start_with("fall_thru_")
         | 
| 134 | 
            +
                  expect(results[1]).to eq("true")
         | 
| 135 | 
            +
                end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                it "can generate postorder traversal" do
         | 
| 138 | 
            +
                  cfg = RPiet::IR::CFG.new(diamond)
         | 
| 139 | 
            +
                  puts cfg.postorder_bbs.map { |bb| bb.label }.join(", ")
         | 
| 140 | 
            +
                  puts cfg.preorder_bbs.map { |bb| bb.label }.join(", ")
         | 
| 141 | 
            +
                end
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                it "can generate postorder traversal" do
         | 
| 144 | 
            +
                  cfg = RPiet::IR::CFG.new(graph1)
         | 
| 145 | 
            +
                  cfg.write_to_dot_file
         | 
| 146 | 
            +
                  puts cfg.postorder_bbs.map { |bb| bb.label }.join(", ")
         | 
| 147 | 
            +
                  puts cfg.preorder_bbs.map { |bb| bb.label }.join(", ")
         | 
| 148 | 
            +
                end
         | 
| 149 | 
            +
             | 
| 150 | 
            +
              end
         | 
| 151 | 
            +
            end
         | 
| @@ -0,0 +1,102 @@ | |
| 1 | 
            +
            require_relative '../spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe "RPiet::IR::IRInterpreter" do
         | 
| 4 | 
            +
              it "can exec push" do
         | 
| 5 | 
            +
                interp = ir_interp(assemble("push 10\n"))
         | 
| 6 | 
            +
                expect(interp.stack).to eq([])
         | 
| 7 | 
            +
                interp.next_step
         | 
| 8 | 
            +
                expect(interp.stack).to eq([10])
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              it "can exec pop" do
         | 
| 12 | 
            +
                interp = ir_interp(assemble("push 10\nv1 = pop\n"))
         | 
| 13 | 
            +
                interp.next_step
         | 
| 14 | 
            +
                expect(interp.stack).to eq([10])
         | 
| 15 | 
            +
                interp.next_step
         | 
| 16 | 
            +
                expect(interp.stack).to eq([])
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              it "can exec add" do
         | 
| 20 | 
            +
                interp = ir_interp(assemble("v1 = 10 + 2\npush v1\n"))
         | 
| 21 | 
            +
                2.times { interp.next_step }
         | 
| 22 | 
            +
                expect(interp.stack).to eq([12])
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              it "can exec add with variable" do
         | 
| 26 | 
            +
                interp = ir_interp(assemble("v1 = copy 10\nv2 = v1 + 2\npush v2\n"))
         | 
| 27 | 
            +
                3.times { interp.next_step }
         | 
| 28 | 
            +
                expect(interp.stack).to eq([12])
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
              it "can exec sub" do
         | 
| 32 | 
            +
                interp = ir_interp(assemble("v1 = 10 - 2\npush v1\n"))
         | 
| 33 | 
            +
                2.times { interp.next_step }
         | 
| 34 | 
            +
                expect(interp.stack).to eq([8])
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              it "can exec mult" do
         | 
| 38 | 
            +
                interp = ir_interp(assemble("v1 = 10 * 2\npush v1\n"))
         | 
| 39 | 
            +
                2.times { interp.next_step }
         | 
| 40 | 
            +
                expect(interp.stack).to eq([20])
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
              it "can exec div" do
         | 
| 44 | 
            +
                interp = ir_interp(assemble("v1 = 10 / 2\npush v1\n"))
         | 
| 45 | 
            +
                2.times { interp.next_step }
         | 
| 46 | 
            +
                expect(interp.stack).to eq([5])
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
              it "can exec mod" do
         | 
| 50 | 
            +
                interp = ir_interp(assemble("v1 = 10 % 2\npush v1\n"))
         | 
| 51 | 
            +
                2.times { interp.next_step }
         | 
| 52 | 
            +
                expect(interp.stack).to eq([0])
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
              it "can exec pow" do
         | 
| 56 | 
            +
                interp = ir_interp(assemble("v1 = 10 ** 2\npush v1\n"))
         | 
| 57 | 
            +
                2.times { interp.next_step }
         | 
| 58 | 
            +
                expect(interp.stack).to eq([100])
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
              it "can exec jump" do
         | 
| 62 | 
            +
                interp = ir_interp(assemble("jump label\npush 1\nlabel label\npush 0\n"))
         | 
| 63 | 
            +
                2.times { interp.next_step }
         | 
| 64 | 
            +
                expect(interp.stack).to eq([0])
         | 
| 65 | 
            +
              end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
              it "can exec bne" do
         | 
| 68 | 
            +
                interp = ir_interp(assemble("1 != 2 label\npush 1\nlabel label\npush 2\n"))
         | 
| 69 | 
            +
                2.times { interp.next_step }
         | 
| 70 | 
            +
                expect(interp.stack).to eq([2])
         | 
| 71 | 
            +
              end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
              it "can exec beq" do
         | 
| 74 | 
            +
                interp = ir_interp(assemble("1 == 1 label\npush 1\nlabel label\npush 2\n"))
         | 
| 75 | 
            +
                2.times { interp.next_step }
         | 
| 76 | 
            +
                expect(interp.stack).to eq([2])
         | 
| 77 | 
            +
              end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
              it "can exec gt" do
         | 
| 80 | 
            +
                interp = ir_interp(assemble("v1 = 2 > 1\npush v1\n"))
         | 
| 81 | 
            +
                2.times { interp.next_step }
         | 
| 82 | 
            +
                expect(interp.stack).to eq([1])
         | 
| 83 | 
            +
              end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
              it "can exec roll" do
         | 
| 86 | 
            +
                interp = ir_interp(assemble("push 1\npush 2\npush 3\npush 4\npush 3\npush 1\nv1 = pop\nv2 = pop\nroll v2 v1\n"))
         | 
| 87 | 
            +
                9.times { interp.next_step }
         | 
| 88 | 
            +
                expect(interp.stack).to eq([1, 4, 2, 3]) # <- [1,2,3,4]  roll 3, 1
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                interp = ir_interp(assemble("push 1\npush 2\npush 3\npush 4\npush 3\npush 2\nv1 = pop\nv2 = pop\nroll v2 v1\n"))
         | 
| 91 | 
            +
                9.times { interp.next_step }
         | 
| 92 | 
            +
                expect(interp.stack).to eq([1, 3, 4, 2])  # <- [1,2,3,4] roll 3, 2
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                interp = ir_interp(assemble("push 1\npush 2\npush 3\npush 4\npush 4\npush -1\nv1 = pop\nv2 = pop\nroll v2 v1\n"))
         | 
| 95 | 
            +
                9.times { interp.next_step; p interp.stack }
         | 
| 96 | 
            +
                expect(interp.stack).to eq([2, 3, 4, 1])  # <- [1,2,3,4] roll 4, -1
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                interp = ir_interp(assemble("push 1\npush 2\npush 3\npush 4\npush 4\npush -2\nv1 = pop\nv2 = pop\nroll v2 v1\n"))
         | 
| 99 | 
            +
                9.times { interp.next_step; p interp.stack }
         | 
| 100 | 
            +
                expect(interp.stack).to eq([3, 4, 1, 2])  # <- [1,2,3,4] roll 4, -2
         | 
| 101 | 
            +
              end
         | 
| 102 | 
            +
            end
         |