Skip to content

Commit 630a551

Browse files
vtjnashcarnaval
authored andcommitted
layout: support pointers inlining into parents [NFCI] (#33886)
Includes codegen support for immutable objects that contain pointers appearing on stack (well, in registers, since LLVM support of non-integral addrspace pointers inside aggregates in memory is poor), and includes layout support, so that most (non-self-referential) immutable objects can be stored inline inside their parent allocation. Currently fully disabled, aside from some optimizations and improvements to object_id / isa tests. Co-Authored-By: Oscar Blumberg <[email protected]>
1 parent fdcaa06 commit 630a551

25 files changed

+1072
-412
lines changed

base/array.jl

+44-35
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ size(a::Array{<:Any,N}) where {N} = (@_inline_meta; ntuple(M -> size(a, M), Val(
158158

159159
asize_from(a::Array, n) = n > ndims(a) ? () : (arraysize(a,n), asize_from(a, n+1)...)
160160

161-
allocatedinline(::Type{T}) where {T} = (@_pure_meta; ccall(:jl_array_store_unboxed, Cint, (Any,), T) != Cint(0))
161+
allocatedinline(::Type{T}) where {T} = (@_pure_meta; ccall(:jl_stored_inline, Cint, (Any,), T) != Cint(0))
162162

163163
"""
164164
Base.isbitsunion(::Type{T})
@@ -177,14 +177,20 @@ false
177177
isbitsunion(u::Union) = allocatedinline(u)
178178
isbitsunion(x) = false
179179

180-
function _unsetindex!(A::Array{T}, i::Int) where {T}
180+
@inbounds function _unsetindex!(A::Array{T}, i::Int) where {T}
181181
@boundscheck checkbounds(A, i)
182+
t = @_gc_preserve_begin A
183+
p = Ptr{Ptr{Cvoid}}(pointer(A, i))
182184
if !allocatedinline(T)
183-
t = @_gc_preserve_begin A
184-
p = Ptr{Ptr{Cvoid}}(pointer(A))
185-
unsafe_store!(p, C_NULL, i)
186-
@_gc_preserve_end t
185+
unsafe_store!(p, C_NULL)
186+
elseif T isa DataType
187+
if !datatype_pointerfree(T)
188+
for j = 1:(Core.sizeof(T) ÷ Core.sizeof(Ptr{Cvoid}))
189+
unsafe_store!(p, C_NULL, j)
190+
end
191+
end
187192
end
193+
@_gc_preserve_end t
188194
return A
189195
end
190196

@@ -255,19 +261,41 @@ the same manner as C.
255261
function unsafe_copyto!(dest::Array{T}, doffs, src::Array{T}, soffs, n) where T
256262
t1 = @_gc_preserve_begin dest
257263
t2 = @_gc_preserve_begin src
258-
if isbitsunion(T)
264+
destp = pointer(dest, doffs)
265+
srcp = pointer(src, soffs)
266+
if !allocatedinline(T)
267+
ccall(:jl_array_ptr_copy, Cvoid, (Any, Ptr{Cvoid}, Any, Ptr{Cvoid}, Int),
268+
dest, destp, src, srcp, n)
269+
elseif isbitstype(T)
259270
ccall(:memmove, Ptr{Cvoid}, (Ptr{Cvoid}, Ptr{Cvoid}, UInt),
260-
pointer(dest, doffs), pointer(src, soffs), n * aligned_sizeof(T))
271+
destp, srcp, n * aligned_sizeof(T))
272+
elseif isbitsunion(T)
273+
ccall(:memmove, Ptr{Cvoid}, (Ptr{Cvoid}, Ptr{Cvoid}, UInt),
274+
destp, srcp, n * aligned_sizeof(T))
261275
# copy selector bytes
262276
ccall(:memmove, Ptr{Cvoid}, (Ptr{Cvoid}, Ptr{Cvoid}, UInt),
263277
ccall(:jl_array_typetagdata, Ptr{UInt8}, (Any,), dest) + doffs - 1,
264278
ccall(:jl_array_typetagdata, Ptr{UInt8}, (Any,), src) + soffs - 1,
265279
n)
266-
elseif allocatedinline(T)
267-
unsafe_copyto!(pointer(dest, doffs), pointer(src, soffs), n)
268280
else
269-
ccall(:jl_array_ptr_copy, Cvoid, (Any, Ptr{Cvoid}, Any, Ptr{Cvoid}, Int),
270-
dest, pointer(dest, doffs), src, pointer(src, soffs), n)
281+
# handle base-case: everything else above was just optimizations
282+
@inbounds if destp < srcp || destp > srcp + n
283+
for i = 1:n
284+
if isassigned(src, soffs + i - 1)
285+
dest[doffs + i - 1] = src[soffs + i - 1]
286+
else
287+
_unsetindex!(dest, doffs + i - 1)
288+
end
289+
end
290+
else
291+
for i = n:-1:1
292+
if isassigned(src, soffs + i - 1)
293+
dest[doffs + i - 1] = src[soffs + i - 1]
294+
else
295+
_unsetindex!(dest, doffs + i - 1)
296+
end
297+
end
298+
end
271299
end
272300
@_gc_preserve_end t2
273301
@_gc_preserve_end t1
@@ -1566,32 +1594,13 @@ function vcat(arrays::Vector{T}...) where T
15661594
n += length(a)
15671595
end
15681596
arr = Vector{T}(undef, n)
1569-
ptr = pointer(arr)
1570-
if isbitsunion(T)
1571-
selptr = ccall(:jl_array_typetagdata, Ptr{UInt8}, (Any,), arr)
1572-
end
1573-
elsz = aligned_sizeof(T)
1574-
t = @_gc_preserve_begin arr
1597+
nd = 1
15751598
for a in arrays
15761599
na = length(a)
1577-
nba = na * elsz
1578-
if isbitsunion(T)
1579-
ccall(:memcpy, Ptr{Cvoid}, (Ptr{Cvoid}, Ptr{Cvoid}, UInt),
1580-
ptr, a, nba)
1581-
# copy selector bytes
1582-
ccall(:memcpy, Ptr{Cvoid}, (Ptr{Cvoid}, Ptr{Cvoid}, UInt),
1583-
selptr, ccall(:jl_array_typetagdata, Ptr{UInt8}, (Any,), a), na)
1584-
selptr += na
1585-
elseif allocatedinline(T)
1586-
ccall(:memcpy, Ptr{Cvoid}, (Ptr{Cvoid}, Ptr{Cvoid}, UInt),
1587-
ptr, a, nba)
1588-
else
1589-
ccall(:jl_array_ptr_copy, Cvoid, (Any, Ptr{Cvoid}, Any, Ptr{Cvoid}, Int),
1590-
arr, ptr, a, pointer(a), na)
1591-
end
1592-
ptr += nba
1600+
@assert nd + na <= 1 + length(arr) # Concurrent modification of arrays?
1601+
unsafe_copyto!(arr, nd, a, 1, na)
1602+
nd += na
15931603
end
1594-
@_gc_preserve_end t
15951604
return arr
15961605
end
15971606

base/reflection.jl

+1
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ datatype_fieldtypes(x::DataType) = ccall(:jl_get_fieldtypes, Any, (Any,), x)
318318
struct DataTypeLayout
319319
nfields::UInt32
320320
npointers::UInt32
321+
firstptr::Int32
321322
alignment::UInt32
322323
# alignment : 9;
323324
# haspadding : 1;

base/refpointer.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ RefArray(x::AbstractArray{T}, i::Int=1, roots::Nothing=nothing) where {T} = RefA
7474
convert(::Type{Ref{T}}, x::AbstractArray{T}) where {T} = RefArray(x, 1)
7575

7676
function unsafe_convert(P::Type{Ptr{T}}, b::RefArray{T}) where T
77-
if datatype_pointerfree(RefValue{T})
77+
if allocatedinline(T)
7878
p = pointer(b.x, b.i)
7979
elseif isconcretetype(T) && T.mutable
8080
p = pointer_from_objref(b.x[b.i])

base/refvalue.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ RefValue(x::T) where {T} = RefValue{T}(x)
1111
isassigned(x::RefValue) = isdefined(x, :x)
1212

1313
function unsafe_convert(P::Type{Ptr{T}}, b::RefValue{T}) where T
14-
if datatype_pointerfree(RefValue{T})
14+
if allocatedinline(T)
1515
p = pointer_from_objref(b)
1616
elseif isconcretetype(T) && T.mutable
1717
p = pointer_from_objref(b.x)

src/array.c

+47-23
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,6 @@ char *jl_array_typetagdata(jl_array_t *a) JL_NOTSAFEPOINT
2727
return ((char*)jl_array_data(a)) + ((jl_array_ndims(a) == 1 ? (a->maxsize - a->offset) : jl_array_len(a)) * a->elsize) + a->offset;
2828
}
2929

30-
JL_DLLEXPORT int jl_array_store_unboxed(jl_value_t *eltype) JL_NOTSAFEPOINT
31-
{
32-
size_t fsz = 0, al = 0;
33-
return jl_islayout_inline(eltype, &fsz, &al);
34-
}
35-
3630
STATIC_INLINE jl_value_t *jl_array_owner(jl_array_t *a JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT
3731
{
3832
if (a->flags.how == 3) {
@@ -53,7 +47,7 @@ size_t jl_arr_xtralloc_limit = 0;
5347
#define MAXINTVAL (((size_t)-1)>>1)
5448

5549
static jl_array_t *_new_array_(jl_value_t *atype, uint32_t ndims, size_t *dims,
56-
int isunboxed, int isunion, int elsz)
50+
int isunboxed, int hasptr, int isunion, int elsz)
5751
{
5852
jl_ptls_t ptls = jl_get_ptls_states();
5953
size_t i, tot, nel=1;
@@ -101,7 +95,7 @@ static jl_array_t *_new_array_(jl_value_t *atype, uint32_t ndims, size_t *dims,
10195
// No allocation or safepoint allowed after this
10296
a->flags.how = 0;
10397
data = (char*)a + doffs;
104-
if ((tot > 0 && !isunboxed) || isunion)
98+
if (tot > 0 && (!isunboxed || hasptr || isunion)) // TODO: check for zeroinit
10599
memset(data, 0, tot);
106100
}
107101
else {
@@ -113,7 +107,7 @@ static jl_array_t *_new_array_(jl_value_t *atype, uint32_t ndims, size_t *dims,
113107
// No allocation or safepoint allowed after this
114108
a->flags.how = 2;
115109
jl_gc_track_malloced_array(ptls, a);
116-
if (!isunboxed || isunion)
110+
if (tot > 0 && (!isunboxed || hasptr || isunion)) // TODO: check for zeroinit
117111
// need to zero out isbits union array selector bytes to ensure a valid type index
118112
memset(data, 0, tot);
119113
}
@@ -127,6 +121,7 @@ static jl_array_t *_new_array_(jl_value_t *atype, uint32_t ndims, size_t *dims,
127121
#endif
128122
a->flags.ndims = ndims;
129123
a->flags.ptrarray = !isunboxed;
124+
a->flags.hasptr = hasptr;
130125
a->elsize = elsz;
131126
a->flags.isshared = 0;
132127
a->flags.isaligned = 1;
@@ -135,9 +130,12 @@ static jl_array_t *_new_array_(jl_value_t *atype, uint32_t ndims, size_t *dims,
135130
a->nrows = nel;
136131
a->maxsize = nel;
137132
}
133+
else if (a->flags.ndims != ndims) {
134+
jl_exceptionf(jl_argumenterror_type, "invalid Array dimensions");
135+
}
138136
else {
139137
size_t *adims = &a->nrows;
140-
for(i=0; i < ndims; i++)
138+
for (i = 0; i < ndims; i++)
141139
adims[i] = dims[i];
142140
}
143141

@@ -152,6 +150,7 @@ static inline jl_array_t *_new_array(jl_value_t *atype, uint32_t ndims, size_t *
152150
jl_type_error_rt("Array", "element type", (jl_value_t*)jl_type_type, eltype);
153151
int isunboxed = jl_islayout_inline(eltype, &elsz, &al);
154152
int isunion = jl_is_uniontype(eltype);
153+
int hasptr = isunboxed && (jl_is_datatype(eltype) && ((jl_datatype_t*)eltype)->layout->npointers > 0);
155154
if (!isunboxed) {
156155
elsz = sizeof(void*);
157156
al = elsz;
@@ -160,13 +159,13 @@ static inline jl_array_t *_new_array(jl_value_t *atype, uint32_t ndims, size_t *
160159
elsz = LLT_ALIGN(elsz, al);
161160
}
162161

163-
return _new_array_(atype, ndims, dims, isunboxed, isunion, elsz);
162+
return _new_array_(atype, ndims, dims, isunboxed, hasptr, isunion, elsz);
164163
}
165164

166165
jl_array_t *jl_new_array_for_deserialization(jl_value_t *atype, uint32_t ndims, size_t *dims,
167-
int isunboxed, int isunion, int elsz)
166+
int isunboxed, int hasptr, int isunion, int elsz)
168167
{
169-
return _new_array_(atype, ndims, dims, isunboxed, isunion, elsz);
168+
return _new_array_(atype, ndims, dims, isunboxed, hasptr, isunion, elsz);
170169
}
171170

172171
#ifndef JL_NDEBUG
@@ -224,10 +223,12 @@ JL_DLLEXPORT jl_array_t *jl_reshape_array(jl_value_t *atype, jl_array_t *data,
224223
"reinterpret from alignment %d bytes to alignment %d bytes not allowed",
225224
(int) oldalign, (int) align);
226225
a->flags.ptrarray = 0;
226+
a->flags.hasptr = data->flags.hasptr;
227227
}
228228
else {
229229
a->elsize = sizeof(void*);
230230
a->flags.ptrarray = 1;
231+
a->flags.hasptr = 0;
231232
}
232233

233234
// if data is itself a shared wrapper,
@@ -247,6 +248,9 @@ JL_DLLEXPORT jl_array_t *jl_reshape_array(jl_value_t *atype, jl_array_t *data,
247248
a->nrows = l;
248249
a->maxsize = l;
249250
}
251+
else if (a->flags.ndims != ndims) {
252+
jl_exceptionf(jl_argumenterror_type, "invalid Array dimensions");
253+
}
250254
else {
251255
size_t *adims = &a->nrows;
252256
size_t l = 1;
@@ -281,6 +285,7 @@ JL_DLLEXPORT jl_array_t *jl_string_to_array(jl_value_t *str)
281285
a->flags.isaligned = 0;
282286
a->elsize = 1;
283287
a->flags.ptrarray = 0;
288+
a->flags.hasptr = 0;
284289
jl_array_data_owner(a) = str;
285290
a->flags.how = 3;
286291
a->flags.isshared = 1;
@@ -300,12 +305,12 @@ JL_DLLEXPORT jl_array_t *jl_ptr_to_array_1d(jl_value_t *atype, void *data,
300305
jl_array_t *a;
301306
jl_value_t *eltype = jl_tparam0(atype);
302307

303-
int isunboxed = jl_array_store_unboxed(eltype);
304-
size_t elsz;
305-
unsigned align;
308+
int isunboxed = jl_stored_inline(eltype);
306309
if (isunboxed && jl_is_uniontype(eltype))
307310
jl_exceptionf(jl_argumenterror_type,
308311
"unsafe_wrap: unspecified layout for union element type");
312+
size_t elsz;
313+
unsigned align;
309314
if (isunboxed) {
310315
elsz = jl_datatype_size(eltype);
311316
align = jl_datatype_align(eltype);
@@ -328,6 +333,7 @@ JL_DLLEXPORT jl_array_t *jl_ptr_to_array_1d(jl_value_t *atype, void *data,
328333
#endif
329334
a->elsize = LLT_ALIGN(elsz, align);
330335
a->flags.ptrarray = !isunboxed;
336+
a->flags.hasptr = isunboxed && (jl_is_datatype(eltype) && ((jl_datatype_t*)eltype)->layout->npointers > 0);
331337
a->flags.ndims = 1;
332338
a->flags.isshared = 1;
333339
a->flags.isaligned = 0; // TODO: allow passing memalign'd buffers
@@ -366,12 +372,12 @@ JL_DLLEXPORT jl_array_t *jl_ptr_to_array(jl_value_t *atype, void *data,
366372
return jl_ptr_to_array_1d(atype, data, nel, own_buffer);
367373
jl_value_t *eltype = jl_tparam0(atype);
368374

369-
int isunboxed = jl_array_store_unboxed(eltype);
370-
size_t elsz;
371-
unsigned align;
375+
int isunboxed = jl_stored_inline(eltype);
372376
if (isunboxed && jl_is_uniontype(eltype))
373377
jl_exceptionf(jl_argumenterror_type,
374378
"unsafe_wrap: unspecified layout for union element type");
379+
size_t elsz;
380+
unsigned align;
375381
if (isunboxed) {
376382
elsz = jl_datatype_size(eltype);
377383
align = jl_datatype_align(eltype);
@@ -394,6 +400,7 @@ JL_DLLEXPORT jl_array_t *jl_ptr_to_array(jl_value_t *atype, void *data,
394400
#endif
395401
a->elsize = LLT_ALIGN(elsz, align);
396402
a->flags.ptrarray = !isunboxed;
403+
a->flags.hasptr = isunboxed && (jl_is_datatype(eltype) && ((jl_datatype_t*)eltype)->layout->npointers > 0);
397404
a->flags.ndims = ndims;
398405
a->offset = 0;
399406
a->flags.isshared = 1;
@@ -408,6 +415,8 @@ JL_DLLEXPORT jl_array_t *jl_ptr_to_array(jl_value_t *atype, void *data,
408415
}
409416

410417
assert(ndims != 1); // handled above
418+
if (a->flags.ndims != ndims)
419+
jl_exceptionf(jl_argumenterror_type, "invalid Array dimensions");
411420
memcpy(&a->nrows, dims, ndims * sizeof(size_t));
412421
return a;
413422
}
@@ -559,8 +568,16 @@ JL_DLLEXPORT jl_value_t *jl_arrayref(jl_array_t *a, size_t i)
559568

560569
JL_DLLEXPORT int jl_array_isassigned(jl_array_t *a, size_t i)
561570
{
562-
if (a->flags.ptrarray)
571+
if (a->flags.ptrarray) {
563572
return ((jl_value_t**)jl_array_data(a))[i] != NULL;
573+
}
574+
else if (a->flags.hasptr) {
575+
jl_datatype_t *eltype = (jl_datatype_t*)jl_tparam0(jl_typeof(a));
576+
assert(eltype->layout->first_ptr >= 0);
577+
jl_value_t **slot =
578+
(jl_value_t**)(&((char*)a->data)[i*a->elsize] + eltype->layout->first_ptr);
579+
return *slot != NULL;
580+
}
564581
return 1;
565582
}
566583

@@ -585,6 +602,8 @@ JL_DLLEXPORT void jl_arrayset(jl_array_t *a JL_ROOTING_ARGUMENT, jl_value_t *rhs
585602
return;
586603
}
587604
jl_assign_bits(&((char*)a->data)[i * a->elsize], rhs);
605+
if (a->flags.hasptr)
606+
jl_gc_multi_wb(jl_array_owner(a), rhs);
588607
}
589608
else {
590609
((jl_value_t**)a->data)[i] = rhs;
@@ -598,6 +617,11 @@ JL_DLLEXPORT void jl_arrayunset(jl_array_t *a, size_t i)
598617
jl_bounds_error_int((jl_value_t*)a, i + 1);
599618
if (a->flags.ptrarray)
600619
((jl_value_t**)a->data)[i] = NULL;
620+
else if (a->flags.hasptr) {
621+
size_t elsize = a->elsize;
622+
jl_assume(elsize >= sizeof(void*) && elsize % sizeof(void*) == 0);
623+
memset(&((jl_value_t**)a->data)[i], 0, elsize);
624+
}
601625
}
602626

603627
// at this size and bigger, allocate resized array data with malloc directly
@@ -809,7 +833,7 @@ STATIC_INLINE void jl_array_grow_at_beg(jl_array_t *a, size_t idx, size_t inc,
809833
#endif
810834
a->nrows = newnrows;
811835
a->data = newdata;
812-
if (a->flags.ptrarray) {
836+
if (a->flags.ptrarray || a->flags.hasptr) { // TODO: check for zeroinit
813837
memset(newdata + idx * elsz, 0, nbinc);
814838
}
815839
else if (isbitsunion) {
@@ -890,7 +914,7 @@ STATIC_INLINE void jl_array_grow_at_end(jl_array_t *a, size_t idx,
890914
a->length = newnrows;
891915
#endif
892916
a->nrows = newnrows;
893-
if (a->flags.ptrarray) {
917+
if (a->flags.ptrarray || a->flags.hasptr) { // TODO: check for zeroinit
894918
memset(data + idx * elsz, 0, inc * elsz);
895919
}
896920
}
@@ -1129,7 +1153,7 @@ JL_DLLEXPORT jl_array_t *jl_array_copy(jl_array_t *ary)
11291153
int isunion = jl_is_uniontype(jl_tparam0(jl_typeof(ary)));
11301154
jl_array_t *new_ary = _new_array_(jl_typeof(ary), jl_array_ndims(ary),
11311155
&ary->nrows, !ary->flags.ptrarray,
1132-
isunion, elsz);
1156+
ary->flags.hasptr, isunion, elsz);
11331157
memcpy(new_ary->data, ary->data, len * elsz);
11341158
// ensure isbits union arrays copy their selector bytes correctly
11351159
if (jl_array_isbitsunion(ary))

0 commit comments

Comments
 (0)