Skip to content

Commit

Permalink
Merge pull request #46 from tshort/static_type
Browse files Browse the repository at this point in the history
Add utilities to convert object models
  • Loading branch information
brenhinkeller authored May 14, 2023
2 parents ef7a5c5 + be8ec9d commit 04026f6
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "StaticTools"
uuid = "86c06d3c-3f03-46de-9781-57580aa96d0a"
authors = ["C. Brenhin Keller and contributors"]
version = "0.8.7"
version = "0.8.8"

[deps]
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ Fortunately, going to all this trouble does have some side benefits besides comp
* No GC means no GC pauses
* Since we're only including what we need, binaries can be quite small (e.g. 8.4K for Hello World)

### Utilities

The utilities `static_type` and `static_type_contents` are utilities to help convert an object to something similar with fields and type parameters that are amenable to static compilation.

`static_type` is mainly useful for converting objects that are heavily paramaterized. The SciML infrastructure has a lot of this. The main objects like a `DiffEq.Integrator` has many type parameters, and by default, some are not amenable to static compilation. `static_type` can be used to convert them to forms that can help numerical code to be statically compiled.

For the default rules, `Array`s are converted to `MallocArray`s, and `String`s are converted to `MallocString`s. The default rules can be extended or redefined by using multiple dispatch and a context variable. Note however that these `MallocArray`s and `MallocString`s must be `free`d when you are done with them.

## Examples

### Compiled command-line executables
Expand Down
5 changes: 5 additions & 0 deletions src/StaticTools.jl
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,16 @@ module StaticTools
include("staticrng.jl")
include("ziggurat.jl")

# Utilities
include("static_type.jl") # Convert types and objects to more static-friendly versions

# Types
export StaticString, MallocString, StringView, AbstractStaticString # String types
export MallocArray, MallocMatrix, MallocVector # Heap-allocated array types
export StackArray, StackMatrix, StackVector # Stack-allocated array types
export ArrayView
export SplitMix64, Xoshiro256✴︎✴︎, BoxMuller, MarsagliaPolar, Ziggurat # RNG types
export StaticContext, DefaultStaticContext # Context for `static_type`

# Macros
export @c_str, @m_str, @mm_str
Expand All @@ -62,4 +66,5 @@ module StaticTools
export unsafe_mallocstring, strlen # String management
export printf, printdlm, parsedlm, argparse # File parsing and formatting
export static_rng, splitmix64, xoshiro256✴︎✴︎, rand!, randn! # RNG functions
export static_type, static_type_contents # Utilities
end
95 changes: 95 additions & 0 deletions src/static_type.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
abstract type StaticContext end
abstract type DefaultStaticContext <: StaticContext end

struct DefaultCtx <: DefaultStaticContext end

"""
```julia
static_type(ctx::StaticContext, x)
static_type(x)
```
Returns an object similar to `x` with contents converted based on rules
specified by `ctx`. `static_type` can be used for types or for objects.
For the default case, this converts `Array`s to `MallocArray`s and
`String`s to `MallocString`s.
To define your own rules, create a new `StaticContext` and then define
two versions of `static_type` for each type you would like to convert.
One converts the value, and one converts the type. Here is the builtin
example for converting Arrays:
```
struct MyCtx <: StaticContext end
static_type(ctx::MyCtx, x::Array) = MallocArray(x)
static_type(ctx::MyCtx, ::Type{Array{T,N}}) where {T,N} = MallocArray{T,N}
```
For this context struct, inherit from `StaticTools.DefaultStaticContext`
to build on the defaults, or inherit from `StaticTools.StaticContext`
to define rules from scratch.
`static_type` is mainly useful for converting objects that are heavily
paramaterized. The SciML infrastructure has a lot of this. The main
objects like a `DiffEq.Integrator` has many type parameters, and by
default, some are not amenable to static compilation. `static_type`
can be used to convert them to forms that can help numerical code to
be statically compiled.
`static_type` cannot convert all objects automatically. It transforms
all type parameters and the contents of each field in an object
(recursively). But, some objects do not define a "fully specified"
constructor. In some cases, another method, `static_type_contents`
can help by returning the components to help for a manual invocation
of the constructor.
Note that any `Malloc`-objects created through this function must still be
`free`d manually if you do not wish to leak memory.
"""
static_type(x) = static_type(DefaultCtx(), x)
static_type(ctx::DefaultStaticContext, x::Array) = MallocArray(x)
static_type(ctx::DefaultStaticContext, ::Type{Array{T,N}}) where {T,N} = MallocArray{T,N}
static_type(ctx::DefaultStaticContext, x::Vector{Vector{T}}) where {T} = MallocArray(MallocArray.(x))
static_type(ctx::DefaultStaticContext, ::Type{Vector{Vector{T}}}) where {T} = MallocVector{MallocVector{T}}
static_type(ctx::DefaultStaticContext, x::Tuple) = tuple((static_type(ctx, y) for y in x)...)
static_type(ctx::DefaultStaticContext, x::String) = MallocString(x)
static_type(ctx::DefaultStaticContext, ::Type{String}) = MallocString

# version for types including parameters
function static_type(ctx::StaticContext, ::Type{T}) where {T}
(!isconcretetype(T) || length(T.parameters) == 0) && return T
return T.name.wrapper{(static_type(ctx, p) for p in T.parameters)...}
end

function static_type(ctx::StaticContext, x::T) where T
length(fieldnames(T)) == 0 && return x
newtypes, newfields = static_type_contents(ctx, x)
if length(newtypes) > 0
return T.name.wrapper{newtypes...}(newfields...)
else
return T.name.wrapper(newfields...)
end
end

"""
```julia
static_type_contents(ctx::StaticContext, x)
static_type_contents(x)
```
Returns a tuple with:
* a vector of type parameters for `x` transformed by `static_type`
* a vector of the contents of the fields in `x` transformed by
`static_type`
Results can be useful for defining objects that do not define a
fully specified constructor.
"""
static_type_contents(x) = static_type_contents(DefaultCtx(), x)
function static_type_contents(ctx::StaticContext, x::T) where T
newtypes = [static_type(ctx, p) for p in T.parameters]
newfields = [static_type(ctx, getfield(x, i)) for i in 1:fieldcount(T)]
return newtypes, newfields
end

5 changes: 5 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,8 @@ using LoopVectorization
@static if GROUP == "Integration" || GROUP == "All"
@testset "StaticCompiler" begin include("teststaticcompiler.jl") end
end

@static if GROUP == "Utilities" || GROUP == "All"
@testset "static_type" begin include("teststatic_type.jl") end
end

29 changes: 29 additions & 0 deletions test/teststatic_type.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@

struct X{A,B,C}
a::A
b::B
c::C
end

x = X([1,2,3], 3, "hello")
xt = static_type(x)
xtt = static_type(typeof(x))

@test xt.a isa MallocArray
@test xt.b isa Int
@test xt.c isa MallocString
@test xtt.parameters[1] == MallocVector{Int}

types, fields = static_type_contents(x)

@test types[1] == MallocVector{Int}
@test fields[1] isa MallocVector
@test fields[1][1] == 1

x = X(1, X([1,2], 1, "hello"), "hello")
xt = static_type(x)

@test xt.b.a isa MallocArray
@test xt.b.c isa MallocString


2 comments on commit 04026f6

@brenhinkeller
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/83562

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.8.8 -m "<description of version>" 04026f6dc6e441530bd58f8281ac2c97a5af7fbb
git push origin v0.8.8

Please sign in to comment.