Skip to content

Commit a08ce50

Browse files
committed
First version of the package
1 parent 96614a9 commit a08ce50

File tree

5 files changed

+201
-0
lines changed

5 files changed

+201
-0
lines changed

.github/workflows/UnitTests.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: Unit Tests
2+
3+
on:
4+
push:
5+
branches: "main"
6+
pull_request:
7+
release:
8+
9+
concurrency:
10+
# Skip intermediate builds: always.
11+
# Cancel intermediate builds: always.
12+
group: ${{ github.workflow }}-${{ github.ref }}
13+
cancel-in-progress: true
14+
15+
jobs:
16+
test:
17+
name: Julia ${{ matrix.julia_version }} - ${{ matrix.os }} - ${{ matrix.julia_arch }}
18+
timeout-minutes: 20
19+
strategy:
20+
fail-fast: false
21+
matrix:
22+
os:
23+
- macos-latest
24+
- ubuntu-latest
25+
- windows-latest
26+
julia_version:
27+
- "nightly"
28+
julia_arch:
29+
- x64
30+
31+
runs-on: ${{ matrix.os }}
32+
33+
steps:
34+
- uses: actions/checkout@v4
35+
- uses: julia-actions/setup-julia@v1
36+
with:
37+
arch: ${{ matrix.julia_arch }}
38+
version: ${{ matrix.julia_version }}
39+
- uses: julia-actions/cache@v1
40+
- uses: julia-actions/julia-runtest@v1
41+
- uses: julia-actions/julia-processcoverage@v1
42+
- uses: codecov/codecov-action@v4
43+
with:
44+
token: ${{ secrets.CODECOV_TOKEN }}
45+
file: lcov.info
46+
continue-on-error: true

Project.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
name = "FixedSizeArrays"
2+
uuid = "3821ddf9-e5b5-40d5-8e25-6813ab96b5e2"
3+
authors = ["Mosè Giordano <[email protected]>"]
4+
version = "0.1.0"
5+
6+
[compat]
7+
Test = "1.11"
8+
julia = "1.11"
9+
10+
[extras]
11+
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
12+
13+
14+
[targets]
15+
test = ["Test"]

README.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
## `FixedSizeArrays.jl`
2+
3+
`FixedSizeArrays.jl` is a proof-of-concept package for the [Julia programming language](https://julialang.org/) which implements mutable fixed-size arrays, which means the lenght of the array is constant and is amenable to be [constant-propagated](https://en.wikipedia.org/wiki/Constant_folding) at compile-time when possible.
4+
This is an alternative implementation to [`MArray`](https://juliaarrays.github.io/StaticArrays.jl/stable/pages/api/#StaticArraysCore.MArray) from [`StaticArrays.jl`](https://github.com/JuliaArrays/StaticArrays.jl).
5+
6+
Main differences between `FixedSizeArray` and `MArray` are:
7+
8+
* `FixedSizeArray` is based on the `Memory` type introduced in Julia v1.11, `MArray` is backed by tuples;
9+
* the size of the array is part of the type parameters of `MArray`, this isn't the case for `FixedSizeArray`, where the size is only a constant field of the data structure.
10+
11+
Note: `FixedSizeArray`s are not guaranteed to be stack-allocated, in fact they will more likely *not* be stack-allocated.
12+
However, in some *extremely* simple cases the compiler may be able to completely elide their allocations:
13+
```julia
14+
julia> using FixedSizeArrays
15+
16+
julia> @noinline f(A::AbstractArray) = length(A)
17+
f (generic function with 1 method)
18+
19+
julia> g() = f(FixedSizeVector{Float64}(undef, 3))
20+
g (generic function with 1 method)
21+
22+
julia> h() = f(Vector{Float64}(undef, 3))
23+
h (generic function with 1 method)
24+
25+
julia> code_llvm(g)
26+
```
27+
```llvm
28+
; Function Signature: g()
29+
; @ REPL[3]:1 within `g`
30+
define i64 @julia_g_511() #0 {
31+
top:
32+
ret i64 3
33+
}
34+
35+
```
36+
```julia
37+
julia> code_llvm(h)
38+
```
39+
```llvm
40+
; Function Signature: h()
41+
; @ REPL[4]:1 within `h`
42+
define i64 @julia_h_693() #0 {
43+
top:
44+
%gcframe1 = alloca [3 x ptr], align 16
45+
call void @llvm.memset.p0.i64(ptr align 16 %gcframe1, i8 0, i64 24, i1 true)
46+
%pgcstack = call ptr inttoptr (i64 7452881148 to ptr)(i64 262) #10
47+
store i64 4, ptr %gcframe1, align 16
48+
%task.gcstack = load ptr, ptr %pgcstack, align 8
49+
%frame.prev = getelementptr inbounds ptr, ptr %gcframe1, i64 1
50+
store ptr %task.gcstack, ptr %frame.prev, align 8
51+
store ptr %gcframe1, ptr %pgcstack, align 8
52+
; ┌ @ boot.jl:576 within `Array`
53+
; │┌ @ boot.jl:514 within `GenericMemory`
54+
%"Memory{Float64}[]" = call ptr @jl_alloc_genericmemory(ptr nonnull @"+Core.GenericMemory#695.jit", i64 3)
55+
; │└
56+
; │ @ boot.jl:577 within `Array`
57+
%.data_ptr = getelementptr inbounds { i64, ptr }, ptr %"Memory{Float64}[]", i64 0, i32 1
58+
%0 = load ptr, ptr %.data_ptr, align 8
59+
%gc_slot_addr_0 = getelementptr inbounds ptr, ptr %gcframe1, i64 2
60+
store ptr %"Memory{Float64}[]", ptr %gc_slot_addr_0, align 16
61+
%ptls_field = getelementptr inbounds ptr, ptr %pgcstack, i64 2
62+
%ptls_load = load ptr, ptr %ptls_field, align 8
63+
%"new::Array" = call noalias nonnull align 8 dereferenceable(32) ptr @ijl_gc_pool_alloc_instrumented(ptr %ptls_load, i32 800, i32 32, i64 4645053728) #8
64+
%"new::Array.tag_addr" = getelementptr inbounds i64, ptr %"new::Array", i64 -1
65+
store atomic i64 4645053728, ptr %"new::Array.tag_addr" unordered, align 8
66+
%1 = getelementptr inbounds ptr, ptr %"new::Array", i64 1
67+
store ptr %0, ptr %"new::Array", align 8
68+
store ptr %"Memory{Float64}[]", ptr %1, align 8
69+
%"new::Array.size_ptr" = getelementptr inbounds i8, ptr %"new::Array", i64 16
70+
store i64 3, ptr %"new::Array.size_ptr", align 8
71+
store ptr %"new::Array", ptr %gc_slot_addr_0, align 16
72+
; └
73+
%2 = call i64 @j_f_699(ptr nonnull %"new::Array")
74+
%frame.prev10 = load ptr, ptr %frame.prev, align 8
75+
store ptr %frame.prev10, ptr %pgcstack, align 8
76+
ret i64 %2
77+
}
78+
```
79+
80+
> [!WARNING]
81+
> This package should currently be used only to experiment with the idea of `Memory`-backed fixed-size arrays, it's highly non-optimised, absolutely don't use it for production.

src/FixedSizeArrays.jl

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
module FixedSizeArrays
2+
3+
export FixedSizeArray, FixedSizeVector, FixedSizeMatrix
4+
5+
mutable struct FixedSizeArray{T,N} <: DenseArray{T,N}
6+
ref::MemoryRef{T}
7+
const size::NTuple{N,Int}
8+
end
9+
10+
const FixedSizeVector{T} = FixedSizeArray{T,1}
11+
const FixedSizeMatrix{T} = FixedSizeArray{T,2}
12+
13+
eval(:(function (self::Type{FixedSizeArray{T,N}})(::UndefInitializer, size::Vararg{Int,N}) where {T,N}
14+
mem = fieldtype(fieldtype(self, :ref), :mem)(undef, prod(size))
15+
return $(Expr(:new, :self, :(Core.memoryref(mem)), :(size)))
16+
end))
17+
18+
function Base.setindex!(A::FixedSizeArray{T}, x, i::Int) where {T}
19+
Base.@_noub_if_noinbounds_meta
20+
@boundscheck (i - 1)%UInt < length(A)%UInt || throw_boundserror(A, (i,))
21+
Core.memoryrefset!(Core.memoryref(A.ref, i, false), x isa T ? x : convert(T,x)::T, :not_atomic, false)
22+
return A
23+
end
24+
function Base.setindex!(A::FixedSizeArray{T}, x, i1::Int, i2::Int, I::Int...) where {T}
25+
@inline
26+
Base.@_noub_if_noinbounds_meta
27+
@boundscheck checkbounds(A, i1, i2, I...) # generally _to_linear_index requires bounds checking
28+
Core.memoryrefset!(Core.memoryref(A.ref, Base._to_linear_index(A, i1, i2, I...), false), x isa T ? x : convert(T,x)::T, :not_atomic, false)
29+
return A
30+
end
31+
32+
function Base.getindex(A::FixedSizeArray, i::Int)
33+
Base.@_noub_if_noinbounds_meta
34+
@boundscheck Base.ult_int(Base.bitcast(UInt, Base.sub_int(i, 1)), Base.bitcast(UInt, length(A))) || throw_boundserror(A, (i,))
35+
Core.memoryrefget(Core.memoryref(getfield(A, :ref), i, false), :not_atomic, false)
36+
end
37+
function Base.getindex(A::FixedSizeArray, i1::Int, i2::Int, I::Int...)
38+
@inline
39+
@boundscheck checkbounds(A, i1, i2, I...) # generally _to_linear_index requires bounds checking
40+
return @inbounds A[Base._to_linear_index(A, i1, i2, I...)]
41+
end
42+
43+
Base.size(a::FixedSizeArray) = getfield(a, :size)
44+
45+
end # module FixedSizeArrays

test/runtests.jl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Test
2+
using FixedSizeArrays
3+
4+
@testset "FixedSizeArrays" begin
5+
v = FixedSizeVector{Float64}(undef, 3)
6+
@test length(v) == 3
7+
v .= 1:3
8+
@test v == 1:3
9+
10+
m = FixedSizeMatrix{Float64}(undef, 3, 3)
11+
@test length(m) == 9
12+
m[:] .= 1:9
13+
@test m[:] == 1:9
14+
end

0 commit comments

Comments
 (0)