Skip to content
  • Jeremy Evans's avatar
    67d1dd2e
    Avoid array allocation for *nil, by not calling nil.to_a · 67d1dd2e
    Jeremy Evans authored
    The following method call:
    
    ```ruby
    a(*nil)
    ```
    
    A method call such as `a(*nil)` previously allocated an array, because
    it calls `nil.to_a`, but I have determined this array allocation is
    unnecessary.  The instructions in this case are:
    
    ```
    0000 putself                                                          (   1)[Li]
    0001 putnil
    0002 splatarray                             false
    0004 opt_send_without_block                 <calldata!mid:a, argc:1, ARGS_SPLAT|FCALL>
    0006 leave
    ```
    
    The method call uses `ARGS_SPLAT` without `ARGS_SPLAT_MUT`, so the
    returned array doesn't need to be mutable.  I believe all cases where
    `splatarray false` are used allow the returned object to be frozen,
    since the `false` means to not duplicate the array.  The optimization
    in this case is to have `splatarray false` push a shared empty frozen
    array, instead of calling `nil.to_a` to return a newly allocated array.
    
    There is a slightly backwards incompatibility with this optimization,
    in that `nil.to_a` is not called.  However, I believe the new behavior
    of `*nil` not calling `nil.to_a` is more consistent with how `**nil`
    does not call `nil.to_hash`.  Also, so much Ruby code would break if
    `nil.to_a` returned something different from the empty hash, that it's
    difficult to imagine anyone actually doing that in real code, though
    we have a few tests/specs for that.
    
    I think it would be bad for consistency if `*nil` called `nil.to_a`
    in some cases and not others, so this changes other cases to not
    call `nil.to_a`:
    
    For `[*nil]`, this uses `splatarray true`, which now allocates a
    new array for a `nil` argument without calling `nil.to_a`.
    
    For `[1, *nil]`, this uses `concattoarray`, which now returns
    the first array if the second array is `nil`.
    
    This updates the allocation tests to check that the array allocations
    are avoided where possible.
    
    Implements [Feature #21047]
    67d1dd2e
    Avoid array allocation for *nil, by not calling nil.to_a
    Jeremy Evans authored
    The following method call:
    
    ```ruby
    a(*nil)
    ```
    
    A method call such as `a(*nil)` previously allocated an array, because
    it calls `nil.to_a`, but I have determined this array allocation is
    unnecessary.  The instructions in this case are:
    
    ```
    0000 putself                                                          (   1)[Li]
    0001 putnil
    0002 splatarray                             false
    0004 opt_send_without_block                 <calldata!mid:a, argc:1, ARGS_SPLAT|FCALL>
    0006 leave
    ```
    
    The method call uses `ARGS_SPLAT` without `ARGS_SPLAT_MUT`, so the
    returned array doesn't need to be mutable.  I believe all cases where
    `splatarray false` are used allow the returned object to be frozen,
    since the `false` means to not duplicate the array.  The optimization
    in this case is to have `splatarray false` push a shared empty frozen
    array, instead of calling `nil.to_a` to return a newly allocated array.
    
    There is a slightly backwards incompatibility with this optimization,
    in that `nil.to_a` is not called.  However, I believe the new behavior
    of `*nil` not calling `nil.to_a` is more consistent with how `**nil`
    does not call `nil.to_hash`.  Also, so much Ruby code would break if
    `nil.to_a` returned something different from the empty hash, that it's
    difficult to imagine anyone actually doing that in real code, though
    we have a few tests/specs for that.
    
    I think it would be bad for consistency if `*nil` called `nil.to_a`
    in some cases and not others, so this changes other cases to not
    call `nil.to_a`:
    
    For `[*nil]`, this uses `splatarray true`, which now allocates a
    new array for a `nil` argument without calling `nil.to_a`.
    
    For `[1, *nil]`, this uses `concattoarray`, which now returns
    the first array if the second array is `nil`.
    
    This updates the allocation tests to check that the array allocations
    are avoided where possible.
    
    Implements [Feature #21047]
Loading