• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / 1652

20 Apr 2026 08:38PM UTC coverage: 77.962% (+0.3%) from 77.623%
1652

push

buildkite

web-flow
codegen: Propagate `ipo_purity_bits` to LLVM function attributes (#61394)

Translate Julia's inferred effects (consistent, effect_free, nothrow,
terminates, notaskstate) into LLVM function attributes so that
middle-end passes like GVN, LICM, and DSE can exploit them.

The key design insight is that GC interactions don't need to be visible
before GC lowering. Call-site declarations get optimistic memory
attributes (e.g. memory(argmem: read)) that enable pre-GC optimizations,
then LateLowerGCFrame widens them to memory(readwrite) before safepoint
analysis so post-GC passes see correct semantics.

Attributes added:
- nounwind: for nothrow functions (with uwtable(async) on definitions
  to preserve .eh_frame for stack scanning)
- mustprogress: for terminating functions
- willreturn: for nothrow+terminating functions
- memory(argmem: read): for consistent+effect_free functions with no
  user-facing pointer arguments (call-site declarations only)
- readnone on gcstack param: for notaskstate functions, so LICM can
  hoist pure calls past heap stores
- "julia.safepoint" marker: on all call-site declarations, used by
  LateLowerGCFrame to identify and widen optimistic attrs

LateLowerGCFrame strips all optimistic attributes (memory effects,
readnone on gcstack) from both call instructions and function
declarations before safepoint analysis runs.

Previously explored in #47844

Developed with Claude

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Gabriel Baraldi <28694980+gbaraldi@users.noreply.github.com>

65490 of 84002 relevant lines covered (77.96%)

23535049.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.26
/base/permuteddimsarray.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
module PermutedDimsArrays
4

5
import Base: permutedims, permutedims!
6
export PermutedDimsArray
7

8
# Some day we will want storage-order-aware iteration, so put perm in the parameters
9
struct PermutedDimsArray{T,N,perm,iperm,AA<:AbstractArray} <: AbstractArray{T,N}
10
    parent::AA
11

12
    function PermutedDimsArray{T,N,perm,iperm,AA}(data::AA) where {T,N,perm,iperm,AA<:AbstractArray}
617✔
13
        (isa(perm, NTuple{N,Int}) && isa(iperm, NTuple{N,Int})) || error("perm and iperm must both be NTuple{$N,Int}")
1,028✔
14
        isperm(perm) || throw(ArgumentError(string(perm, " is not a valid permutation of dimensions 1:", N)))
1,028✔
15
        all(d->iperm[perm[d]]==d, 1:N) || throw(ArgumentError(string(perm, " and ", iperm, " must be inverses")))
3,786✔
16
        new(data)
1,028✔
17
    end
18
end
19

20
"""
21
    PermutedDimsArray(A, perm) -> B
22

23
Given an AbstractArray `A`, create a view `B` such that the
24
dimensions appear to be permuted. Similar to `permutedims`, except
25
that no copying occurs (`B` shares storage with `A`).
26

27
See also [`permutedims`](@ref), [`invperm`](@ref).
28

29
# Examples
30
```jldoctest
31
julia> A = rand(3,5,4);
32

33
julia> B = PermutedDimsArray(A, (3,1,2));
34

35
julia> size(B)
36
(4, 3, 5)
37

38
julia> B[3,1,2] == A[1,2,3]
39
true
40
```
41
"""
42
Base.@constprop :aggressive function PermutedDimsArray(data::AbstractArray{T,N}, perm) where {T,N}
623✔
43
    length(perm) == N || throw(ArgumentError(string(perm, " is not a valid permutation of dimensions 1:", N)))
977✔
44
    iperm = invperm(perm)
977✔
45
    PermutedDimsArray{T,N,(perm...,),(iperm...,),typeof(data)}(data)
971✔
46
end
47

48
Base.parent(A::PermutedDimsArray) = A.parent
751,789✔
49
Base.size(A::PermutedDimsArray{T,N,perm}) where {T,N,perm} = genperm(size(parent(A)), perm)
161,115✔
50
Base.axes(A::PermutedDimsArray{T,N,perm}) where {T,N,perm} = genperm(axes(parent(A)), perm)
465,307✔
51
Base.has_offset_axes(A::PermutedDimsArray) = Base.has_offset_axes(A.parent)
×
52
Base.similar(A::PermutedDimsArray, T::Type, dims::Base.Dims) = similar(parent(A), T, dims)
69✔
53
Base.dataids(A::PermutedDimsArray) = Base.dataids(parent(A))
57✔
54
Base.unaliascopy(A::PermutedDimsArray) = typeof(A)(Base.unaliascopy(parent(A)))
3✔
55
Base.cconvert(::Type{Ptr{T}}, A::PermutedDimsArray{T}) where {T} = Base.cconvert(Ptr{T}, parent(A))
53,943✔
56

57
# It's OK to return a pointer to the first element, and indeed quite
58
# useful for wrapping C routines that require a different storage
59
# order than used by Julia. But for an array with unconventional
60
# storage order, a linear offset is ambiguous---is it a memory offset
61
# or a linear index?
62
Base.pointer(A::PermutedDimsArray, i::Integer) = throw(ArgumentError("pointer(A, i) is deliberately unsupported for PermutedDimsArray"))
243✔
63

64
function Base.strides(A::PermutedDimsArray{T,N,perm}) where {T,N,perm}
6✔
65
    s = strides(parent(A))
106,806✔
66
    ntuple(d->s[perm[d]], Val(N))
301,581✔
67
end
68
Base.elsize(::Type{<:PermutedDimsArray{<:Any, <:Any, <:Any, <:Any, P}}) where {P} = Base.elsize(P)
106,680✔
69

70
@inline function Base.getindex(A::PermutedDimsArray{T,N,perm,iperm}, I::Vararg{Int,N}) where {T,N,perm,iperm}
71
    @boundscheck checkbounds(A, I...)
222,168✔
72
    @inbounds val = getindex(A.parent, genperm(I, iperm)...)
222,168✔
73
    val
222,168✔
74
end
75
@inline function Base.setindex!(A::PermutedDimsArray{T,N,perm,iperm}, val, I::Vararg{Int,N}) where {T,N,perm,iperm}
76
    @boundscheck checkbounds(A, I...)
11,400✔
77
    @inbounds setindex!(A.parent, val, genperm(I, iperm)...)
11,400✔
78
    val
11,400✔
79
end
80

81
function Base.isassigned(A::PermutedDimsArray{T,N,perm,iperm}, I::Vararg{Int,N}) where {T,N,perm,iperm}
×
82
    @boundscheck checkbounds(Bool, A, I...) || return false
×
83
    @inbounds x = isassigned(A.parent, genperm(I, iperm)...)
×
84
    x
×
85
end
86

87
@inline genperm(I::NTuple{N,Any}, perm::Dims{N}) where {N} = ntuple(d -> I[perm[d]], Val(N))
1,648,652✔
88
@inline genperm(I, perm::AbstractVector{Int}) = genperm(I, (perm...,))
21✔
89

90
"""
91
    permutedims(A::AbstractArray, perm)
92
    permutedims(A::AbstractMatrix)
93

94
Permute the dimensions (axes) of array `A`. `perm` is a tuple or vector of `ndims(A)` integers
95
specifying the permutation.
96

97
If `A` is a 2d array ([`AbstractMatrix`](@ref)), then
98
`perm` defaults to `(2,1)`, swapping the two axes of `A` (the rows and columns
99
of the matrix).   This differs from [`transpose`](@ref) in that the
100
operation is not recursive, which is especially useful for arrays of non-numeric values
101
(where the recursive `transpose` would throw an error) and/or 2d arrays that do not represent
102
linear operators.
103

104
For 1d arrays, see [`permutedims(v::AbstractVector)`](@ref), which returns a 1-row “matrix”.
105

106
See also [`permutedims!`](@ref), [`PermutedDimsArray`](@ref), [`transpose`](@ref), [`invperm`](@ref).
107

108
# Examples
109

110
## 2d arrays:
111
Unlike `transpose`, `permutedims` can be used to swap rows and columns of 2d arrays of
112
arbitrary non-numeric elements, such as strings:
113
```jldoctest
114
julia> A = ["a" "b" "c"
115
            "d" "e" "f"]
116
2×3 Matrix{String}:
117
 "a"  "b"  "c"
118
 "d"  "e"  "f"
119

120
julia> permutedims(A)
121
3×2 Matrix{String}:
122
 "a"  "d"
123
 "b"  "e"
124
 "c"  "f"
125
```
126
And `permutedims` produces results that differ from `transpose`
127
for matrices whose elements are themselves numeric matrices:
128
```jldoctest; setup = :(using LinearAlgebra)
129
julia> a = [1 2; 3 4];
130

131
julia> b = [5 6; 7 8];
132

133
julia> c = [9 10; 11 12];
134

135
julia> d = [13 14; 15 16];
136

137
julia> X = [[a] [b]; [c] [d]]
138
2×2 Matrix{Matrix{Int64}}:
139
 [1 2; 3 4]     [5 6; 7 8]
140
 [9 10; 11 12]  [13 14; 15 16]
141

142
julia> permutedims(X)
143
2×2 Matrix{Matrix{Int64}}:
144
 [1 2; 3 4]  [9 10; 11 12]
145
 [5 6; 7 8]  [13 14; 15 16]
146

147
julia> transpose(X)
148
2×2 transpose(::Matrix{Matrix{Int64}}) with eltype Transpose{Int64, Matrix{Int64}}:
149
 [1 3; 2 4]  [9 11; 10 12]
150
 [5 7; 6 8]  [13 15; 14 16]
151
```
152

153
## Multi-dimensional arrays
154
```jldoctest
155
julia> A = reshape(Vector(1:8), (2,2,2))
156
2×2×2 Array{Int64, 3}:
157
[:, :, 1] =
158
 1  3
159
 2  4
160

161
[:, :, 2] =
162
 5  7
163
 6  8
164

165
julia> perm = (3, 1, 2); # put the last dimension first
166

167
julia> B = permutedims(A, perm)
168
2×2×2 Array{Int64, 3}:
169
[:, :, 1] =
170
 1  2
171
 5  6
172

173
[:, :, 2] =
174
 3  4
175
 7  8
176

177
julia> A == permutedims(B, invperm(perm)) # the inverse permutation
178
true
179
```
180

181
For each dimension `i` of `B = permutedims(A, perm)`, its corresponding dimension of `A`
182
will be `perm[i]`. This means the equality `size(B, i) == size(A, perm[i])` holds.
183

184
```jldoctest
185
julia> A = randn(5, 7, 11, 13);
186

187
julia> perm = [4, 1, 3, 2];
188

189
julia> B = permutedims(A, perm);
190

191
julia> size(B)
192
(13, 5, 11, 7)
193

194
julia> size(A)[perm] == ans
195
true
196
```
197
"""
198
function permutedims(A::AbstractArray, perm)
30✔
199
    dest = similar(A, genperm(axes(A), perm))
36✔
200
    permutedims!(dest, A, perm)
36✔
201
end
202

203
permutedims(A::AbstractMatrix) = permutedims(A, (2,1))
30✔
204

205
"""
206
    permutedims(v::AbstractVector)
207

208
Reshape vector `v` into a `1 × length(v)` row matrix.
209
Differs from [`transpose`](@ref) in that
210
the operation is not recursive, which is especially useful for arrays of non-numeric values
211
(where the recursive `transpose` might throw an error).
212

213
# Examples
214
Unlike `transpose`, `permutedims` can be used on vectors of
215
arbitrary non-numeric elements, such as strings:
216
```jldoctest
217
julia> permutedims(["a", "b", "c"])
218
1×3 Matrix{String}:
219
 "a"  "b"  "c"
220
```
221
For vectors of numbers, `permutedims(v)` works much like `transpose(v)`
222
except that the return type differs (it uses [`reshape`](@ref)
223
rather than a `LinearAlgebra.Transpose` view, though both
224
share memory with the original array `v`):
225
```jldoctest; setup = :(using LinearAlgebra)
226
julia> v = [1, 2, 3, 4]
227
4-element Vector{Int64}:
228
 1
229
 2
230
 3
231
 4
232

233
julia> p = permutedims(v)
234
1×4 Matrix{Int64}:
235
 1  2  3  4
236

237
julia> r = transpose(v)
238
1×4 transpose(::Vector{Int64}) with eltype Int64:
239
 1  2  3  4
240

241
julia> p == r
242
true
243

244
julia> typeof(r)
245
Transpose{Int64, Vector{Int64}}
246

247
julia> p[1] = 5; r[2] = 6; # mutating p or r also changes v
248

249
julia> v # shares memory with both p and r
250
4-element Vector{Int64}:
251
 5
252
 6
253
 3
254
 4
255
```
256
However, `permutedims` produces results that differ from `transpose`
257
for vectors whose elements are themselves numeric matrices:
258
```jldoctest; setup = :(using LinearAlgebra)
259
julia> V = [[[1 2; 3 4]]; [[5 6; 7 8]]]
260
2-element Vector{Matrix{Int64}}:
261
 [1 2; 3 4]
262
 [5 6; 7 8]
263

264
julia> permutedims(V)
265
1×2 Matrix{Matrix{Int64}}:
266
 [1 2; 3 4]  [5 6; 7 8]
267

268
julia> transpose(V)
269
1×2 transpose(::Vector{Matrix{Int64}}) with eltype Transpose{Int64, Matrix{Int64}}:
270
 [1 3; 2 4]  [5 7; 6 8]
271
```
272
"""
273
permutedims(v::AbstractVector) = reshape(v, (1, length(v)))
234✔
274

275
"""
276
    permutedims!(dest, src, perm)
277

278
Permute the dimensions of array `src` and store the result in the array `dest`. `perm` is a
279
vector specifying a permutation of length `ndims(src)`. The preallocated array `dest` should
280
have `size(dest) == size(src)[perm]` and is completely overwritten. No in-place permutation
281
is supported and unexpected results will happen if `src` and `dest` have overlapping memory
282
regions.
283

284
See also [`permutedims`](@ref).
285
"""
286
function permutedims!(dest, src::AbstractArray, perm)
30✔
287
    Base.checkdims_perm(axes(dest), axes(src), perm)
36✔
288
    P = PermutedDimsArray(dest, invperm(perm))
24✔
289
    _copy!(P, src)
24✔
290
    return dest
24✔
291
end
292

293
function Base.copyto!(dest::PermutedDimsArray{T,N}, src::AbstractArray{T,N}) where {T,N}
×
294
    checkbounds(dest, axes(src)...)
×
295
    _copy!(dest, src)
×
296
end
297
Base.copyto!(dest::PermutedDimsArray, src::AbstractArray) = _copy!(dest, src)
×
298

299
function _copy!(P::PermutedDimsArray{T,N,perm}, src) where {T,N,perm}
24✔
300
    # If dest/src are "close to dense," then it pays to be cache-friendly.
301
    # Determine the first permuted dimension
302
    d = 0  # d+1 will hold the first permuted dimension of src
24✔
303
    while d < ndims(src) && perm[d+1] == d+1
36✔
304
        d += 1
12✔
305
    end
12✔
306
    if d == ndims(src)
24✔
307
        copyto!(parent(P), src) # it's not permuted
6✔
308
    else
309
        R1 = CartesianIndices(axes(src)[1:d])
24✔
310
        d1 = findfirst(isequal(d+1), perm)::Int  # first permuted dim of dest
24✔
311
        R2 = CartesianIndices(axes(src)[d+2:d1-1])
21✔
312
        R3 = CartesianIndices(axes(src)[d1+1:end])
21✔
313
        _permutedims!(P, src, R1, R2, R3, d+1, d1)
21✔
314
    end
315
    return P
24✔
316
end
317

318
@noinline function _permutedims!(P::PermutedDimsArray, src, R1::CartesianIndices{0}, R2, R3, ds, dp)
18✔
319
    ip, is = axes(src, dp), axes(src, ds)
18✔
320
    for jo in first(ip):8:last(ip), io in first(is):8:last(is)
36✔
321
        for I3 in R3, I2 in R2
18✔
322
            for j in jo:min(jo+7, last(ip))
36✔
323
                for i in io:min(io+7, last(is))
96✔
324
                    @inbounds P[i, I2, j, I3] = src[i, I2, j, I3]
312✔
325
                end
528✔
326
            end
156✔
327
        end
48✔
328
    end
18✔
329
    P
18✔
330
end
331

332
@noinline function _permutedims!(P::PermutedDimsArray, src, R1, R2, R3, ds, dp)
3✔
333
    ip, is = axes(src, dp), axes(src, ds)
3✔
334
    for jo in first(ip):8:last(ip), io in first(is):8:last(is)
6✔
335
        for I3 in R3, I2 in R2
3✔
336
            for j in jo:min(jo+7, last(ip))
3✔
337
                for i in io:min(io+7, last(is))
6✔
338
                    for I1 in R1
18✔
339
                        @inbounds P[I1, i, I2, j, I3] = src[I1, i, I2, j, I3]
54✔
340
                    end
72✔
341
                end
30✔
342
            end
9✔
343
        end
3✔
344
    end
3✔
345
    P
3✔
346
end
347

348
const CommutativeOps = Union{typeof(+),typeof(Base.add_sum),typeof(min),typeof(max),typeof(Base._extrema_rf),typeof(|),typeof(&)}
349

350
function Base._mapreduce_dim(f, op::CommutativeOps, init::Base._InitialValue, A::PermutedDimsArray, dims::Colon)
351
    Base._mapreduce_dim(f, op, init, parent(A), dims)
57✔
352
end
353
function Base._mapreduce_dim(f::typeof(identity), op::Union{typeof(Base.mul_prod),typeof(*)}, init::Base._InitialValue, A::PermutedDimsArray{<:Union{Real,Complex}}, dims::Colon)
354
    Base._mapreduce_dim(f, op, init, parent(A), dims)
12✔
355
end
356

357
function Base.mapreducedim!(f, op::CommutativeOps, B::AbstractArray{T,N}, A::PermutedDimsArray{S,N,perm,iperm}) where {T,S,N,perm,iperm}
30✔
358
    C = PermutedDimsArray{T,N,iperm,perm,typeof(B)}(B) # make the inverse permutation for the output
30✔
359
    Base.mapreducedim!(f, op, C, parent(A))
30✔
360
    B
30✔
361
end
362
function Base.mapreducedim!(f::typeof(identity), op::Union{typeof(Base.mul_prod),typeof(*)}, B::AbstractArray{T,N}, A::PermutedDimsArray{<:Union{Real,Complex},N,perm,iperm}) where {T,N,perm,iperm}
24✔
363
    C = PermutedDimsArray{T,N,iperm,perm,typeof(B)}(B) # make the inverse permutation for the output
24✔
364
    Base.mapreducedim!(f, op, C, parent(A))
24✔
365
    B
24✔
366
end
367

368
function Base.showarg(io::IO, A::PermutedDimsArray{T,N,perm}, toplevel) where {T,N,perm}
3✔
369
    print(io, "PermutedDimsArray(")
6✔
370
    Base.showarg(io, parent(A), false)
6✔
371
    print(io, ", ", perm, ')')
6✔
372
    toplevel && print(io, " with eltype ", eltype(A))
6✔
373
    return nothing
6✔
374
end
375

376
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc