Skip to content

Commit efeba05

Browse files
mbaumankleinhenz
authored andcommitted
RFC: Deprecate partial linear indexing (JuliaLang#20079)
* Allow multiple lookup sites in depwarn * Deprecate partial linear indexing * Add NEWS.md * Add comments on the disabled PLI tests * Ensure linearindices completely inlines (through _length)
1 parent 2e409bb commit efeba05

12 files changed

+211
-74
lines changed

NEWS.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,13 @@ Compiler/Runtime improvements
224224
Deprecated or removed
225225
---------------------
226226

227+
* Linear indexing is now only supported when there is exactly one
228+
non-cartesian index provided. Allowing a trailing index at dimension `d` to
229+
linearly access the higher dimensions from array `A` (beyond `size(A, d)`)
230+
has been deprecated as a stricter constraint during bounds checking.
231+
Instead, `reshape` the array such that its dimensionality matches the
232+
number of indices ([#20079]).
233+
227234
* `isdefined(a::Array, i::Int)` has been deprecated in favor of `isassigned` ([#18346]).
228235

229236
* `is` has been deprecated in favor of `===` (which used to be an alias for `is`) ([#17758]).

base/abstractarray.jl

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,10 @@ julia> length(A)
120120
60
121121
```
122122
"""
123-
length(t::AbstractArray) = prod(size(t))
124-
_length(A::AbstractArray) = prod(map(unsafe_length, indices(A))) # circumvent missing size
125-
_length(A) = length(A)
126-
endof(a::AbstractArray) = length(a)
123+
length(t::AbstractArray) = (@_inline_meta; prod(size(t)))
124+
_length(A::AbstractArray) = (@_inline_meta; prod(map(unsafe_length, indices(A)))) # circumvent missing size
125+
_length(A) = (@_inline_meta; length(A))
126+
endof(a::AbstractArray) = (@_inline_meta; length(a))
127127
first(a::AbstractArray) = a[first(eachindex(a))]
128128

129129
"""
@@ -306,6 +306,11 @@ function checkbounds(::Type{Bool}, A::AbstractArray, I...)
306306
@_inline_meta
307307
checkbounds_indices(Bool, indices(A), I)
308308
end
309+
# Linear indexing is explicitly allowed when there is only one (non-cartesian) index
310+
function checkbounds(::Type{Bool}, A::AbstractArray, i)
311+
@_inline_meta
312+
checkindex(Bool, linearindices(A), i)
313+
end
309314
# As a special extension, allow using logical arrays that match the source array exactly
310315
function checkbounds{_,N}(::Type{Bool}, A::AbstractArray{_,N}, I::AbstractArray{Bool,N})
311316
@_inline_meta
@@ -358,7 +363,21 @@ function checkbounds_indices(::Type{Bool}, IA::Tuple{Any}, I::Tuple{Any})
358363
end
359364
function checkbounds_indices(::Type{Bool}, IA::Tuple, I::Tuple{Any})
360365
@_inline_meta
361-
checkindex(Bool, OneTo(trailingsize(IA)), I[1]) # linear indexing
366+
checkbounds_linear_indices(Bool, IA, I[1])
367+
end
368+
function checkbounds_linear_indices(::Type{Bool}, IA::Tuple, i)
369+
@_inline_meta
370+
if checkindex(Bool, IA[1], i)
371+
return true
372+
elseif checkindex(Bool, OneTo(trailingsize(IA)), i) # partial linear indexing
373+
partial_linear_indexing_warning_lookup(length(IA))
374+
return true # TODO: Return false after the above function is removed in deprecated.jl
375+
end
376+
return false
377+
end
378+
function checkbounds_linear_indices(::Type{Bool}, IA::Tuple, i::Union{Slice,Colon})
379+
partial_linear_indexing_warning_lookup(length(IA))
380+
true
362381
end
363382
checkbounds_indices(::Type{Bool}, ::Tuple, ::Tuple{}) = true
364383

base/deprecated.jl

Lines changed: 61 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -59,40 +59,40 @@ end
5959
function depwarn(msg, funcsym)
6060
opts = JLOptions()
6161
if opts.depwarn > 0
62-
ln = Int(unsafe_load(cglobal(:jl_lineno, Cint)))
63-
fn = unsafe_string(unsafe_load(cglobal(:jl_filename, Ptr{Cchar})))
6462
bt = backtrace()
65-
caller = firstcaller(bt, funcsym)
66-
if opts.depwarn == 1 # raise a warning
67-
warn(msg, once=(caller != C_NULL), key=caller, bt=bt,
68-
filename=fn, lineno=ln)
69-
elseif opts.depwarn == 2 # raise an error
70-
throw(ErrorException(msg))
71-
end
63+
_depwarn(msg, opts, bt, firstcaller(bt, funcsym))
7264
end
7365
nothing
7466
end
67+
function _depwarn(msg, opts, bt, caller)
68+
ln = Int(unsafe_load(cglobal(:jl_lineno, Cint)))
69+
fn = unsafe_string(unsafe_load(cglobal(:jl_filename, Ptr{Cchar})))
70+
if opts.depwarn == 1 # raise a warning
71+
warn(msg, once=(caller != StackTraces.UNKNOWN), key=(caller,fn,ln), bt=bt,
72+
filename=fn, lineno=ln)
73+
elseif opts.depwarn == 2 # raise an error
74+
throw(ErrorException(msg))
75+
end
76+
end
7577

76-
function firstcaller(bt::Array{Ptr{Void},1}, funcsym::Symbol)
78+
firstcaller(bt::Array{Ptr{Void},1}, funcsym::Symbol) = firstcaller(bt, (funcsym,))
79+
function firstcaller(bt::Array{Ptr{Void},1}, funcsyms)
7780
# Identify the calling line
78-
i = 1
79-
while i <= length(bt)
80-
lkups = StackTraces.lookup(bt[i])
81-
i += 1
81+
found = false
82+
lkup = StackTraces.UNKNOWN
83+
for frame in bt
84+
lkups = StackTraces.lookup(frame)
8285
for lkup in lkups
8386
if lkup === StackTraces.UNKNOWN
8487
continue
8588
end
86-
if lkup.func == funcsym
87-
@goto found
88-
end
89+
found && @goto found
90+
found = lkup.func in funcsyms
8991
end
9092
end
93+
return StackTraces.UNKNOWN
9194
@label found
92-
if i <= length(bt)
93-
return bt[i]
94-
end
95-
return C_NULL
95+
return lkup
9696
end
9797

9898
deprecate(s::Symbol) = deprecate(current_module(), s)
@@ -1739,6 +1739,46 @@ eval(Base.Test, quote
17391739
export @test_approx_eq
17401740
end)
17411741

1742+
# Deprecate partial linear indexing
1743+
function partial_linear_indexing_warning_lookup(nidxs_remaining)
1744+
# We need to figure out how many indices were passed for a sensible deprecation warning
1745+
opts = JLOptions()
1746+
if opts.depwarn > 0
1747+
# Find the caller -- this is very expensive so we don't want to do it twice
1748+
bt = backtrace()
1749+
found = false
1750+
call = StackTraces.UNKNOWN
1751+
caller = StackTraces.UNKNOWN
1752+
for frame in bt
1753+
lkups = StackTraces.lookup(frame)
1754+
for caller in lkups
1755+
if caller === StackTraces.UNKNOWN
1756+
continue
1757+
end
1758+
found && @goto found
1759+
if caller.func in (:getindex, :setindex!, :view)
1760+
found = true
1761+
call = caller
1762+
end
1763+
end
1764+
end
1765+
@label found
1766+
fn = "`reshape`"
1767+
if call != StackTraces.UNKNOWN && !isnull(call.linfo)
1768+
# Try to grab the number of dimensions in the parent array
1769+
mi = get(call.linfo)
1770+
args = mi.specTypes.parameters
1771+
if length(args) >= 2 && args[2] <: AbstractArray
1772+
fn = "`reshape(A, Val{$(ndims(args[2]) - nidxs_remaining + 1)})`"
1773+
end
1774+
end
1775+
_depwarn("Partial linear indexing is deprecated. Use $fn to make the dimensionality of the array match the number of indices.", opts, bt, caller)
1776+
end
1777+
end
1778+
function partial_linear_indexing_warning(n)
1779+
depwarn("Partial linear indexing is deprecated. Use `reshape(A, Val{$n})` to make the dimensionality of the array match the number of indices.", (:getindex, :setindex!, :view))
1780+
end
1781+
17421782
# Deprecate Array(T, dims...) in favor of proper type constructors
17431783
@deprecate Array{T,N}(::Type{T}, d::NTuple{N,Int}) Array{T,N}(d)
17441784
@deprecate Array{T}(::Type{T}, d::Int...) Array{T,length(d)}(d...)

base/multidimensional.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,12 @@ end # IteratorsMD
181181
using .IteratorsMD
182182

183183
## Bounds-checking with CartesianIndex
184+
# Disallow linear indexing with CartesianIndex
185+
function checkbounds(::Type{Bool}, A::AbstractArray, i::Union{CartesianIndex, AbstractArray{C} where C <: CartesianIndex})
186+
@_inline_meta
187+
checkbounds_indices(Bool, indices(A), (i,))
188+
end
189+
184190
@inline checkbounds_indices(::Type{Bool}, ::Tuple{}, I::Tuple{CartesianIndex,Vararg{Any}}) =
185191
checkbounds_indices(Bool, (), (I[1].I..., tail(I)...))
186192
@inline checkbounds_indices(::Type{Bool}, IA::Tuple{Any}, I::Tuple{CartesianIndex,Vararg{Any}}) =

src/array.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,7 @@ int jl_array_isdefined(jl_value_t **args0, int nargs)
530530
{
531531
assert(jl_is_array(args0[0]));
532532
jl_depwarn("`isdefined(a::Array, i::Int)` is deprecated, "
533-
"use `isassigned(a, i)` instead", jl_symbol("isdefined"));
533+
"use `isassigned(a, i)` instead", (jl_value_t*)jl_symbol("isdefined"));
534534

535535
jl_array_t *a = (jl_array_t*)args0[0];
536536
jl_value_t **args = &args0[1];

src/builtins.c

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,7 +1045,7 @@ JL_CALLABLE(jl_f_invoke)
10451045
if (jl_is_tuple(args[1])) {
10461046
jl_depwarn("`invoke(f, (types...), ...)` is deprecated, "
10471047
"use `invoke(f, Tuple{types...}, ...)` instead",
1048-
jl_symbol("invoke"));
1048+
(jl_value_t*)jl_symbol("invoke"));
10491049
argtypes = (jl_value_t*)jl_apply_tuple_type_v((jl_value_t**)jl_data_ptr(argtypes),
10501050
jl_nfields(argtypes));
10511051
}
@@ -1735,7 +1735,7 @@ JL_DLLEXPORT void jl_breakpoint(jl_value_t *v)
17351735
// put a breakpoint in your debugger here
17361736
}
17371737

1738-
void jl_depwarn(const char *msg, jl_sym_t *sym)
1738+
void jl_depwarn(const char *msg, jl_value_t *sym)
17391739
{
17401740
static jl_value_t *depwarn_func = NULL;
17411741
if (!depwarn_func && jl_base_module) {
@@ -1749,11 +1749,31 @@ void jl_depwarn(const char *msg, jl_sym_t *sym)
17491749
JL_GC_PUSHARGS(depwarn_args, 3);
17501750
depwarn_args[0] = depwarn_func;
17511751
depwarn_args[1] = jl_cstr_to_string(msg);
1752-
depwarn_args[2] = (jl_value_t*)sym;
1752+
depwarn_args[2] = sym;
17531753
jl_apply(depwarn_args, 3);
17541754
JL_GC_POP();
17551755
}
17561756

1757+
void jl_depwarn_partial_indexing(size_t n)
1758+
{
1759+
static jl_value_t *depwarn_func = NULL;
1760+
if (!depwarn_func && jl_base_module) {
1761+
depwarn_func = jl_get_global(jl_base_module, jl_symbol("partial_linear_indexing_warning"));
1762+
}
1763+
if (!depwarn_func) {
1764+
jl_safe_printf("WARNING: Partial linear indexing is deprecated. Use "
1765+
"`reshape(A, Val{%zd})` to make the dimensionality of the array match "
1766+
"the number of indices\n", n);
1767+
return;
1768+
}
1769+
jl_value_t **depwarn_args;
1770+
JL_GC_PUSHARGS(depwarn_args, 2);
1771+
depwarn_args[0] = depwarn_func;
1772+
depwarn_args[1] = jl_box_long(n);
1773+
jl_apply(depwarn_args, 2);
1774+
JL_GC_POP();
1775+
}
1776+
17571777
#ifdef __cplusplus
17581778
}
17591779
#endif

src/cgutils.cpp

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,8 +1396,30 @@ static Value *emit_array_nd_index(const jl_cgval_t &ainfo, jl_value_t *ex, ssize
13961396
if (linear_indexing) {
13971397
// Compare the linearized index `i` against the linearized size of
13981398
// the accessed array, i.e. `if !(i < alen) goto error`.
1399-
Value *alen = emit_arraylen(ainfo, ex, ctx);
1400-
builder.CreateCondBr(builder.CreateICmpULT(i, alen), endBB, failBB);
1399+
if (nidxs > 1) {
1400+
// TODO: REMOVE DEPWARN AND RETURN FALSE AFTER 0.6.
1401+
// We need to check if this is inside the non-linearized size
1402+
BasicBlock *partidx = BasicBlock::Create(jl_LLVMContext, "partlinidx");
1403+
BasicBlock *partidxwarn = BasicBlock::Create(jl_LLVMContext, "partlinidxwarn");
1404+
Value *d = emit_arraysize_for_unsafe_dim(ainfo, ex, nidxs, nd, ctx);
1405+
builder.CreateCondBr(builder.CreateICmpULT(ii, d), endBB, partidx);
1406+
1407+
// We failed the normal bounds check; check to see if we're
1408+
// inside the linearized size (partial linear indexing):
1409+
ctx->f->getBasicBlockList().push_back(partidx);
1410+
builder.SetInsertPoint(partidx);
1411+
Value *alen = emit_arraylen(ainfo, ex, ctx);
1412+
builder.CreateCondBr(builder.CreateICmpULT(i, alen), partidxwarn, failBB);
1413+
1414+
// We passed the linearized bounds check; now throw the depwarn:
1415+
ctx->f->getBasicBlockList().push_back(partidxwarn);
1416+
builder.SetInsertPoint(partidxwarn);
1417+
builder.CreateCall(prepare_call(jldepwarnpi_func), ConstantInt::get(T_size, nidxs));
1418+
builder.CreateBr(endBB);
1419+
} else {
1420+
Value *alen = emit_arraylen(ainfo, ex, ctx);
1421+
builder.CreateCondBr(builder.CreateICmpULT(i, alen), endBB, failBB);
1422+
}
14011423
} else {
14021424
// Compare the last index of the access against the last dimension of
14031425
// the accessed array, i.e. `if !(last_index < last_dimension) goto error`.

src/codegen.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ static Function *expect_func;
394394
static Function *jldlsym_func;
395395
static Function *jlnewbits_func;
396396
static Function *jltypeassert_func;
397+
static Function *jldepwarnpi_func;
397398
#if JL_LLVM_VERSION < 30600
398399
static Function *jlpow_func;
399400
static Function *jlpowf_func;
@@ -5777,6 +5778,13 @@ static void init_julia_llvm_env(Module *m)
57775778
"jl_typeassert", m);
57785779
add_named_global(jltypeassert_func, &jl_typeassert);
57795780

5781+
std::vector<Type*> argsdepwarnpi(0);
5782+
argsdepwarnpi.push_back(T_size);
5783+
jldepwarnpi_func = Function::Create(FunctionType::get(T_void, argsdepwarnpi, false),
5784+
Function::ExternalLinkage,
5785+
"jl_depwarn_partial_indexing", m);
5786+
add_named_global(jldepwarnpi_func, &jl_depwarn_partial_indexing);
5787+
57805788
queuerootfun = Function::Create(FunctionType::get(T_void, args_1ptr, false),
57815789
Function::ExternalLinkage,
57825790
"jl_gc_queue_root", m);

src/julia_internal.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -817,7 +817,8 @@ STATIC_INLINE void *jl_get_frame_addr(void)
817817
}
818818

819819
JL_DLLEXPORT jl_array_t *jl_array_cconvert_cstring(jl_array_t *a);
820-
void jl_depwarn(const char *msg, jl_sym_t *sym);
820+
void jl_depwarn(const char *msg, jl_value_t *sym);
821+
void jl_depwarn_partial_indexing(size_t n);
821822

822823
int isabspath(const char *in);
823824

0 commit comments

Comments
 (0)