Skip to content
  • Aaron Patterson's avatar
    62cc3464
    Remove leading `nop` from block when we don't need it · 62cc3464
    Aaron Patterson authored
    Blocks insert a leading `nop` instruction in order to execute a "block
    call" tracepoint. Block compilation unconditionally inserts a leading
    `nop` plus a label after the instruction:
    
      https://github.com/ruby/ruby/blob/641f15b1c6bd8921407a1f045573d3b0605f00d3/prism_compile.c#L6867-L6869
    
    This `nop` instruction is used entirely for firing the block entry
    tracepoint.  The label exists so that the block can contain a loop but
    the block entry tracepoint is executed only once.
    
    For example, the following code is an infinite loop, but should only
    execute the b_call tracepoint once:
    
    ```ruby
    -> { redo }.call
    ```
    
    Previous to this commit, we would eliminate the `nop` instruction, but
    only if there were no other jump instructions inside the block.  This
    means that the following code would still contain a leading `nop` even
    though the label following the `nop` is unused:
    
    ```ruby
    -> { nil if bar }
    ```
    
    ```
    == disasm: #<ISeq:block in <main>@test.rb:1 (1,2)-(1,17)> (catch: FALSE)
    0000 nop                                                              (   1)[Bc]
    0001 putself                                [Li]
    0002 opt_send_without_block                 <calldata!mid:bar, argc:0, FCALL|VCALL|ARGS_SIMPLE>
    0004 branchunless                           8
    0006 putnil
    0007 leave                                  [Br]
    0008 putnil
    0009 leave                                  [Br]
    ```
    
    This commit checks to see if the label inserted after the `nop` is
    actually a jump target.  If it's not a jump target, then we should be
    safe to eliminate the leading `nop`:
    
    ```
    > build-master/miniruby --dump=insns test.rb
    == disasm: #<ISeq:<main>@test.rb:1 (1,0)-(1,17)>
    0000 putspecialobject                       1                         (   1)[Li]
    0002 send                                   <calldata!mid:lambda, argc:0, FCALL>, block in <main>
    0005 leave
    
    == disasm: #<ISeq:block in <main>@test.rb:1 (1,2)-(1,17)>
    0000 putself                                                          (   1)[LiBc]
    0001 opt_send_without_block                 <calldata!mid:bar, argc:0, FCALL|VCALL|ARGS_SIMPLE>
    0003 branchunless                           7
    0005 putnil
    0006 leave                                  [Br]
    0007 putnil
    0008 leave                                  [Br]
    ```
    
    We have a test for b_call tracepoints that use `redo` here:
    
      https://github.com/ruby/ruby/blob/aebf96f371c8d874398e0041b798892e545fa206/test/ruby/test_settracefunc.rb#L1728-L1736
    62cc3464
    Remove leading `nop` from block when we don't need it
    Aaron Patterson authored
    Blocks insert a leading `nop` instruction in order to execute a "block
    call" tracepoint. Block compilation unconditionally inserts a leading
    `nop` plus a label after the instruction:
    
      https://github.com/ruby/ruby/blob/641f15b1c6bd8921407a1f045573d3b0605f00d3/prism_compile.c#L6867-L6869
    
    This `nop` instruction is used entirely for firing the block entry
    tracepoint.  The label exists so that the block can contain a loop but
    the block entry tracepoint is executed only once.
    
    For example, the following code is an infinite loop, but should only
    execute the b_call tracepoint once:
    
    ```ruby
    -> { redo }.call
    ```
    
    Previous to this commit, we would eliminate the `nop` instruction, but
    only if there were no other jump instructions inside the block.  This
    means that the following code would still contain a leading `nop` even
    though the label following the `nop` is unused:
    
    ```ruby
    -> { nil if bar }
    ```
    
    ```
    == disasm: #<ISeq:block in <main>@test.rb:1 (1,2)-(1,17)> (catch: FALSE)
    0000 nop                                                              (   1)[Bc]
    0001 putself                                [Li]
    0002 opt_send_without_block                 <calldata!mid:bar, argc:0, FCALL|VCALL|ARGS_SIMPLE>
    0004 branchunless                           8
    0006 putnil
    0007 leave                                  [Br]
    0008 putnil
    0009 leave                                  [Br]
    ```
    
    This commit checks to see if the label inserted after the `nop` is
    actually a jump target.  If it's not a jump target, then we should be
    safe to eliminate the leading `nop`:
    
    ```
    > build-master/miniruby --dump=insns test.rb
    == disasm: #<ISeq:<main>@test.rb:1 (1,0)-(1,17)>
    0000 putspecialobject                       1                         (   1)[Li]
    0002 send                                   <calldata!mid:lambda, argc:0, FCALL>, block in <main>
    0005 leave
    
    == disasm: #<ISeq:block in <main>@test.rb:1 (1,2)-(1,17)>
    0000 putself                                                          (   1)[LiBc]
    0001 opt_send_without_block                 <calldata!mid:bar, argc:0, FCALL|VCALL|ARGS_SIMPLE>
    0003 branchunless                           7
    0005 putnil
    0006 leave                                  [Br]
    0007 putnil
    0008 leave                                  [Br]
    ```
    
    We have a test for b_call tracepoints that use `redo` here:
    
      https://github.com/ruby/ruby/blob/aebf96f371c8d874398e0041b798892e545fa206/test/ruby/test_settracefunc.rb#L1728-L1736
Loading