Skip to content

Commit 348f085

Browse files
authored
Add appropriate cconvert/unsafe_convert methods to avoid String alloc… (#56)
* Add appropriate cconvert/unsafe_convert methods to avoid String allocation in Base * Address review comments
1 parent 6cb0088 commit 348f085

File tree

2 files changed

+29
-3
lines changed

2 files changed

+29
-3
lines changed

src/InlineStrings.jl

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ for sz in (1, 4, 8, 16, 32, 64, 128, 256)
7373
end
7474
end
7575

76+
# used to zero out n lower bytes of an inline string
77+
clear_n_bytes(s, n) = Base.shl_int(Base.lshr_int(s, 8 * n), 8 * n)
7678
_bswap(x::T) where {T <: InlineString} = T === InlineString1 ? x : Base.bswap_int(x)
7779

7880
const InlineStringTypes = Union{InlineString1,
@@ -147,6 +149,26 @@ function Base.Symbol(x::T) where {T <: InlineString}
147149
(Ref{T}, Int), ref, sizeof(x))
148150
end
149151

152+
Base.cconvert(::Type{Ptr{UInt8}}, x::InlineString1) = Base.cconvert(Ptr{UInt8}, InlineString3(x))
153+
Base.cconvert(::Type{Ptr{Int8}}, x::InlineString1) = Base.cconvert(Ptr{Int8}, InlineString3(x))
154+
Base.cconvert(::Type{Cstring}, x::InlineString1) = Base.cconvert(Cstring, InlineString3(x))
155+
Base.cconvert(::Type{Ptr{UInt8}}, x::T) where {T <: InlineString} =
156+
Ref{T}(_bswap(clear_n_bytes(x, 1)))
157+
Base.cconvert(::Type{Ptr{Int8}}, x::T) where {T <: InlineString} =
158+
Ref{T}(_bswap(clear_n_bytes(x, 1)))
159+
function Base.cconvert(::Type{Cstring}, x::T) where {T <: InlineString}
160+
ref = Ref{T}(_bswap(clear_n_bytes(x, 1)))
161+
Base.containsnul(Ptr{Int8}(pointer_from_objref(ref)), sizeof(x)) &&
162+
throw(ArgumentError("embedded NULs are not allowed in C strings: $x"))
163+
return ref
164+
end
165+
Base.unsafe_convert(::Type{Ptr{UInt8}}, x::Ref{T}) where {T <: InlineString} =
166+
Ptr{UInt8}(pointer_from_objref(x))
167+
Base.unsafe_convert(::Type{Ptr{Int8}}, x::Ref{T}) where {T <: InlineString} =
168+
Ptr{Int8}(pointer_from_objref(x))
169+
Base.unsafe_convert(::Type{Cstring}, s::Ref{T}) where {T <: InlineString} =
170+
Cstring(Base.unsafe_convert(Ptr{Cchar}, s))
171+
150172
Base.Vector{UInt8}(s::InlineString) = Vector{UInt8}(codeunits(s))
151173
Base.Array{UInt8}(s::InlineString) = Vector{UInt8}(codeunits(s))
152174

@@ -506,9 +528,6 @@ function Base.rstrip(f, s::InlineString)
506528
return nc == 0 ? s : _chopsuffix(s, nc)
507529
end
508530

509-
# used to zero out n lower bytes of an inline string
510-
clear_n_bytes(s, n) = Base.shl_int(Base.lshr_int(s, 8 * n), 8 * n)
511-
512531
Base.chomp(s::InlineString1) = chomp(String3(s))
513532
function Base.chomp(s::InlineString)
514533
i = lastindex(s)

test/runtests.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,9 @@ for S in SUBTYPES
254254
end
255255
end
256256

257+
# can't contain NUL when converting to Cstring
258+
@test_throws ArgumentError Base.cconvert(Cstring, InlineString("a\0c"))
259+
257260
end # @testset
258261

259262
const STRINGS = ["", "🍕", "a", "a"^3, "a"^7, "a"^15, "a"^31, "a"^63, "a"^127, "a"^255]
@@ -339,6 +342,10 @@ const INLINES = map(InlineString, STRINGS)
339342
@test last(x, sizeof(x)) == last(y, sizeof(y))
340343
y != "" && @test last(x, sizeof(x) - 1) == last(y, sizeof(y) - 1)
341344
@test last(x, sizeof(x) + 1) == last(y, sizeof(y) + 1)
345+
# https://github.com/JuliaDatabases/SQLite.jl/issues/306
346+
@test unsafe_string(Base.unsafe_convert(Ptr{UInt8}, Base.cconvert(Ptr{UInt8}, x))) == y
347+
@test unsafe_string(Base.unsafe_convert(Ptr{Int8}, Base.cconvert(Ptr{Int8}, x))) == y
348+
@test unsafe_string(Base.unsafe_convert(Cstring, Base.cconvert(Cstring, x))) == y
342349
end
343350
end
344351

0 commit comments

Comments
 (0)