1""" 2Implement slices and various slice computations. 3""" 4 5from itertools import zip_longest 6 7from llvmlite import ir 8 9from numba.core import types, typing, utils, cgutils 10from numba.core.imputils import (lower_builtin, lower_getattr, 11 iternext_impl, impl_ret_borrowed, 12 impl_ret_new_ref, impl_ret_untracked) 13 14 15def fix_index(builder, idx, size): 16 """ 17 Fix negative index by adding *size* to it. Positive 18 indices are left untouched. 19 """ 20 is_negative = builder.icmp_signed('<', idx, ir.Constant(size.type, 0)) 21 wrapped_index = builder.add(idx, size) 22 return builder.select(is_negative, wrapped_index, idx) 23 24 25def fix_slice(builder, slice, size): 26 """ 27 Fix *slice* start and stop to be valid (inclusive and exclusive, resp) 28 indexing bounds for a sequence of the given *size*. 29 """ 30 # See PySlice_GetIndicesEx() 31 zero = ir.Constant(size.type, 0) 32 minus_one = ir.Constant(size.type, -1) 33 34 def fix_bound(bound_name, lower_repl, upper_repl): 35 bound = getattr(slice, bound_name) 36 bound = fix_index(builder, bound, size) 37 # Store value 38 setattr(slice, bound_name, bound) 39 # Still negative? => clamp to lower_repl 40 underflow = builder.icmp_signed('<', bound, zero) 41 with builder.if_then(underflow, likely=False): 42 setattr(slice, bound_name, lower_repl) 43 # Greater than size? => clamp to upper_repl 44 overflow = builder.icmp_signed('>=', bound, size) 45 with builder.if_then(overflow, likely=False): 46 setattr(slice, bound_name, upper_repl) 47 48 with builder.if_else(cgutils.is_neg_int(builder, slice.step)) as (if_neg_step, if_pos_step): 49 with if_pos_step: 50 # < 0 => 0; >= size => size 51 fix_bound('start', zero, size) 52 fix_bound('stop', zero, size) 53 with if_neg_step: 54 # < 0 => -1; >= size => size - 1 55 lower = minus_one 56 upper = builder.add(size, minus_one) 57 fix_bound('start', lower, upper) 58 fix_bound('stop', lower, upper) 59 60 61def get_slice_length(builder, slicestruct): 62 """ 63 Given a slice, compute the number of indices it spans, i.e. the 64 number of iterations that for_range_slice() will execute. 65 66 Pseudo-code: 67 assert step != 0 68 if step > 0: 69 if stop <= start: 70 return 0 71 else: 72 return (stop - start - 1) // step + 1 73 else: 74 if stop >= start: 75 return 0 76 else: 77 return (stop - start + 1) // step + 1 78 79 (see PySlice_GetIndicesEx() in CPython) 80 """ 81 start = slicestruct.start 82 stop = slicestruct.stop 83 step = slicestruct.step 84 one = ir.Constant(start.type, 1) 85 zero = ir.Constant(start.type, 0) 86 87 is_step_negative = cgutils.is_neg_int(builder, step) 88 delta = builder.sub(stop, start) 89 90 # Nominal case 91 pos_dividend = builder.sub(delta, one) 92 neg_dividend = builder.add(delta, one) 93 dividend = builder.select(is_step_negative, neg_dividend, pos_dividend) 94 nominal_length = builder.add(one, builder.sdiv(dividend, step)) 95 96 # Catch zero length 97 is_zero_length = builder.select(is_step_negative, 98 builder.icmp_signed('>=', delta, zero), 99 builder.icmp_signed('<=', delta, zero)) 100 101 # Clamp to 0 if is_zero_length 102 return builder.select(is_zero_length, zero, nominal_length) 103 104 105def get_slice_bounds(builder, slicestruct): 106 """ 107 Return the [lower, upper) indexing bounds of a slice. 108 """ 109 start = slicestruct.start 110 stop = slicestruct.stop 111 zero = start.type(0) 112 one = start.type(1) 113 # This is a bit pessimal, e.g. it will return [1, 5) instead 114 # of [1, 4) for `1:5:2` 115 is_step_negative = builder.icmp_signed('<', slicestruct.step, zero) 116 lower = builder.select(is_step_negative, 117 builder.add(stop, one), start) 118 upper = builder.select(is_step_negative, 119 builder.add(start, one), stop) 120 return lower, upper 121 122 123def fix_stride(builder, slice, stride): 124 """ 125 Fix the given stride for the slice's step. 126 """ 127 return builder.mul(slice.step, stride) 128 129def guard_invalid_slice(context, builder, typ, slicestruct): 130 """ 131 Guard against *slicestruct* having a zero step (and raise ValueError). 132 """ 133 if typ.has_step: 134 cgutils.guard_null(context, builder, slicestruct.step, 135 (ValueError, "slice step cannot be zero")) 136 137 138def get_defaults(context): 139 """ 140 Get the default values for a slice's members: 141 (start for positive step, start for negative step, 142 stop for positive step, stop for negative step, step) 143 """ 144 maxint = (1 << (context.address_size - 1)) - 1 145 return (0, maxint, maxint, - maxint - 1, 1) 146 147 148#--------------------------------------------------------------------------- 149# The slice structure 150 151@lower_builtin(slice, types.VarArg(types.Any)) 152def slice_constructor_impl(context, builder, sig, args): 153 default_start_pos, default_start_neg, default_stop_pos, default_stop_neg, default_step = \ 154 [context.get_constant(types.intp, x) for x in get_defaults(context)] 155 156 slice_args = [None] * 3 157 158 # Fetch non-None arguments 159 if len(args) == 1 and sig.args[0] is not types.none: 160 slice_args[1] = args[0] 161 else: 162 for i, (ty, val) in enumerate(zip(sig.args, args)): 163 if ty is not types.none: 164 slice_args[i] = val 165 166 # Fill omitted arguments 167 def get_arg_value(i, default): 168 val = slice_args[i] 169 if val is None: 170 return default 171 else: 172 return val 173 174 step = get_arg_value(2, default_step) 175 is_step_negative = builder.icmp_signed('<', step, 176 context.get_constant(types.intp, 0)) 177 default_stop = builder.select(is_step_negative, 178 default_stop_neg, default_stop_pos) 179 default_start = builder.select(is_step_negative, 180 default_start_neg, default_start_pos) 181 stop = get_arg_value(1, default_stop) 182 start = get_arg_value(0, default_start) 183 184 ty = sig.return_type 185 sli = context.make_helper(builder, sig.return_type) 186 sli.start = start 187 sli.stop = stop 188 sli.step = step 189 190 res = sli._getvalue() 191 return impl_ret_untracked(context, builder, sig.return_type, res) 192 193 194@lower_getattr(types.SliceType, "start") 195def slice_start_impl(context, builder, typ, value): 196 sli = context.make_helper(builder, typ, value) 197 return sli.start 198 199@lower_getattr(types.SliceType, "stop") 200def slice_stop_impl(context, builder, typ, value): 201 sli = context.make_helper(builder, typ, value) 202 return sli.stop 203 204@lower_getattr(types.SliceType, "step") 205def slice_step_impl(context, builder, typ, value): 206 if typ.has_step: 207 sli = context.make_helper(builder, typ, value) 208 return sli.step 209 else: 210 return context.get_constant(types.intp, 1) 211 212 213@lower_builtin("slice.indices", types.SliceType, types.Integer) 214def slice_indices(context, builder, sig, args): 215 length = args[1] 216 sli = context.make_helper(builder, sig.args[0], args[0]) 217 218 with builder.if_then(cgutils.is_neg_int(builder, length), likely=False): 219 context.call_conv.return_user_exc( 220 builder, ValueError, 221 ("length should not be negative",) 222 ) 223 with builder.if_then(cgutils.is_scalar_zero(builder, sli.step), likely=False): 224 context.call_conv.return_user_exc( 225 builder, ValueError, 226 ("slice step cannot be zero",) 227 ) 228 229 fix_slice(builder, sli, length) 230 231 return context.make_tuple( 232 builder, 233 sig.return_type, 234 (sli.start, sli.stop, sli.step) 235 ) 236