Skip to content

Commit

Permalink
Add Libc methods for memmove/memcpy/memset/memcmp (#49550)
Browse files Browse the repository at this point in the history
These are used in many places (and are actually LLVM compiler
intrinsics), so it probably makes more sense to define them one and
export them to users.

The Libc module contains some code that we might not care to have as
part of bootstrapping. However, the C-memory methods are directly called
throughout bootstrapping so these are now defined in a seperate
"cmem.jl" file that is defined in Base then imported into `Libc` for the
public interface.

Co-authored-by: Jameson Nash <[email protected]>
  • Loading branch information
Tokazama and vtjnash authored May 26, 2023
1 parent ba1391a commit f8dd16e
Show file tree
Hide file tree
Showing 24 changed files with 158 additions and 57 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ New library functions
* `tanpi` is now defined. It computes tan(πx) more accurately than `tan(pi*x)` ([#48575]).
* `fourthroot(x)` is now defined in `Base.Math` and can be used to compute the fourth root of `x`.
It can also be accessed using the unicode character ``, which can be typed by `\fourthroot<tab>` ([#48899]).
* `Libc.memmove`, `Libc.memset`, and `Libc.memcpy` are now defined, whose functionality matches that of their respective C calls.

New library features
--------------------
Expand Down
3 changes: 2 additions & 1 deletion base/Base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ include("int.jl")
include("operators.jl")
include("pointer.jl")
include("refvalue.jl")
include("cmem.jl")
include("refpointer.jl")

# now replace the Pair constructor (relevant for NamedTuples) with one that calls our Base.convert
Expand Down Expand Up @@ -316,7 +317,7 @@ include("version.jl")
# system & environment
include("sysinfo.jl")
include("libc.jl")
using .Libc: getpid, gethostname, time
using .Libc: getpid, gethostname, time, memcpy, memset, memmove, memcmp

# These used to be in build_h.jl and are retained for backwards compatibility.
# NOTE: keep in sync with `libblastrampoline_jll.libblastrampoline`.
Expand Down
57 changes: 42 additions & 15 deletions base/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,7 @@ segfault your program, in the same manner as C.
function unsafe_copyto!(dest::Ptr{T}, src::Ptr{T}, n) where T
# Do not use this to copy data between pointer arrays.
# It can't be made safe no matter how carefully you checked.
ccall(:memmove, Ptr{Cvoid}, (Ptr{Cvoid}, Ptr{Cvoid}, Csize_t),
dest, src, n * aligned_sizeof(T))
memmove(dest, src, n * aligned_sizeof(T))
return dest
end

Expand Down Expand Up @@ -328,13 +327,11 @@ function unsafe_copyto!(dest::Array{T}, doffs, src::Array{T}, soffs, n) where T
ccall(:jl_array_ptr_copy, Cvoid, (Any, Ptr{Cvoid}, Any, Ptr{Cvoid}, Int),
dest, destp, src, srcp, n)
elseif isbitstype(T)
ccall(:memmove, Ptr{Cvoid}, (Ptr{Cvoid}, Ptr{Cvoid}, Csize_t),
destp, srcp, n * aligned_sizeof(T))
memmove(destp, srcp, n * aligned_sizeof(T))
elseif isbitsunion(T)
ccall(:memmove, Ptr{Cvoid}, (Ptr{Cvoid}, Ptr{Cvoid}, Csize_t),
destp, srcp, n * aligned_sizeof(T))
memmove(destp, srcp, n * aligned_sizeof(T))
# copy selector bytes
ccall(:memmove, Ptr{Cvoid}, (Ptr{Cvoid}, Ptr{Cvoid}, Csize_t),
memmove(
ccall(:jl_array_typetagdata, Ptr{UInt8}, (Any,), dest) + doffs - 1,
ccall(:jl_array_typetagdata, Ptr{UInt8}, (Any,), src) + soffs - 1,
n)
Expand Down Expand Up @@ -467,7 +464,10 @@ end
getindex(::Type{Any}) = Vector{Any}()

function fill!(a::Union{Array{UInt8}, Array{Int8}}, x::Integer)
ccall(:memset, Ptr{Cvoid}, (Ptr{Cvoid}, Cint, Csize_t), a, x isa eltype(a) ? x : convert(eltype(a), x), length(a))
t = @_gc_preserve_begin a
p = unsafe_convert(Ptr{Cvoid}, a)
memset(p, x isa eltype(a) ? x : convert(eltype(a), x), length(a))
@_gc_preserve_end t
return a
end

Expand Down Expand Up @@ -1834,23 +1834,50 @@ function empty!(a::Vector)
return a
end

_memcmp(a, b, len) = ccall(:memcmp, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Csize_t), a, b, len % Csize_t) % Int

# use memcmp for cmp on byte arrays
function cmp(a::Array{UInt8,1}, b::Array{UInt8,1})
c = _memcmp(a, b, min(length(a),length(b)))
ta = @_gc_preserve_begin a
tb = @_gc_preserve_begin b
pa = unsafe_convert(Ptr{Cvoid}, a)
pb = unsafe_convert(Ptr{Cvoid}, b)
c = memcmp(pa, pb, min(length(a),length(b)))
@_gc_preserve_end ta
@_gc_preserve_end tb
return c < 0 ? -1 : c > 0 ? +1 : cmp(length(a),length(b))
end

const BitIntegerArray{N} = Union{map(T->Array{T,N}, BitInteger_types)...} where N
# use memcmp for == on bit integer types
==(a::Arr, b::Arr) where {Arr <: BitIntegerArray} =
size(a) == size(b) && 0 == _memcmp(a, b, sizeof(eltype(Arr)) * length(a))
function ==(a::Arr, b::Arr) where {Arr <: BitIntegerArray}
if size(a) == size(b)
ta = @_gc_preserve_begin a
tb = @_gc_preserve_begin b
pa = unsafe_convert(Ptr{Cvoid}, a)
pb = unsafe_convert(Ptr{Cvoid}, b)
c = memcmp(pa, pb, sizeof(eltype(Arr)) * length(a))
@_gc_preserve_end ta
@_gc_preserve_end tb
return c == 0
else
return false
end
end

# this is ~20% faster than the generic implementation above for very small arrays
function ==(a::Arr, b::Arr) where Arr <: BitIntegerArray{1}
len = length(a)
len == length(b) && 0 == _memcmp(a, b, sizeof(eltype(Arr)) * len)
if len == length(b)
ta = @_gc_preserve_begin a
tb = @_gc_preserve_begin b
T = eltype(Arr)
pa = unsafe_convert(Ptr{T}, a)
pb = unsafe_convert(Ptr{T}, b)
c = memcmp(pa, pb, sizeof(T) * len)
@_gc_preserve_end ta
@_gc_preserve_end tb
return c == 0
else
return false
end
end

"""
Expand Down
2 changes: 1 addition & 1 deletion base/bitset.jl
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ function ==(s1::BitSet, s2::BitSet)
if overlap > 0
t1 = @_gc_preserve_begin a1
t2 = @_gc_preserve_begin a2
_memcmp(pointer(a1, b2-b1+1), pointer(a2), overlap<<3) == 0 || return false
memcmp(pointer(a1, b2-b1+1), pointer(a2), overlap<<3) == 0 || return false
@_gc_preserve_end t2
@_gc_preserve_end t1
end
Expand Down
53 changes: 53 additions & 0 deletions base/cmem.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

"""
memcpy(dst::Ptr, src::Ptr, n::Integer) -> Ptr{Cvoid}
Call `memcpy` from the C standard library.
!!! compat "Julia 1.10"
Support for `memcpy` requires at least Julia 1.10.
"""
function memcpy(dst::Ptr, src::Ptr, n::Integer)
ccall(:memcpy, Ptr{Cvoid}, (Ptr{Cvoid}, Ptr{Cvoid}, Csize_t), dst, src, n)
end

"""
memmove(dst::Ptr, src::Ptr, n::Integer) -> Ptr{Cvoid}
Call `memmove` from the C standard library.
!!! compat "Julia 1.10"
Support for `memmove` requires at least Julia 1.10.
"""
function memmove(dst::Ptr, src::Ptr, n::Integer)
ccall(:memmove, Ptr{Cvoid}, (Ptr{Cvoid}, Ptr{Cvoid}, Csize_t), dst, src, n)
end

"""
memset(dst::Ptr, val, n::Integer) -> Ptr{Cvoid}
Call `memset` from the C standard library.
!!! compat "Julia 1.10"
Support for `memset` requires at least Julia 1.10.
"""
function memset(p::Ptr, val, n::Integer)
ccall(:memset, Ptr{Cvoid}, (Ptr{Cvoid}, Cint, Csize_t), p, val, n)
end

"""
memcmp(a::Ptr, b::Ptr, n::Integer) -> Int
Call `memcmp` from the C standard library.
!!! compat "Julia 1.10"
Support for `memcmp` requires at least Julia 1.9.
"""
function memcmp(a::Ptr, b::Ptr, n::Integer)
ccall(:memcmp, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Csize_t), a, b, n % Csize_t) % Int
end
1 change: 1 addition & 0 deletions base/compiler/compiler.jl
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ add_with_overflow(x::T, y::T) where {T<:SignedInt} = checked_sadd_int(x, y)
add_with_overflow(x::T, y::T) where {T<:UnsignedInt} = checked_uadd_int(x, y)
add_with_overflow(x::Bool, y::Bool) = (x+y, false)

include("cmem.jl")
include("strings/lazy.jl")

# core array operations
Expand Down
5 changes: 4 additions & 1 deletion base/iddict.jl
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,10 @@ end

function empty!(d::IdDict)
resize!(d.ht, 32)
ccall(:memset, Ptr{Cvoid}, (Ptr{Cvoid}, Cint, Csize_t), d.ht, 0, sizeof(d.ht))
ht = d.ht
t = @_gc_preserve_begin ht
memset(unsafe_convert(Ptr{Cvoid}, ht), 0, sizeof(ht))
@_gc_preserve_end t
d.ndel = 0
d.count = 0
return d
Expand Down
13 changes: 8 additions & 5 deletions base/libc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ Interface to libc, the C standard library.
""" Libc

import Base: transcode, windowserror, show
# these need to be defined seperately for bootstrapping but belong to Libc
import Base: memcpy, memmove, memset, memcmp
import Core.Intrinsics: bitcast

export FILE, TmStruct, strftime, strptime, getpid, gethostname, free, malloc, calloc, realloc,
errno, strerror, flush_cstdio, systemsleep, time, transcode
export FILE, TmStruct, strftime, strptime, getpid, gethostname, free, malloc, memcpy,
memmove, memset, calloc, realloc, errno, strerror, flush_cstdio, systemsleep, time,
transcode
if Sys.iswindows()
export GetLastError, FormatMessage
end
Expand Down Expand Up @@ -336,7 +339,6 @@ if Sys.iswindows()
end

## Memory related ##

"""
free(addr::Ptr)
Expand All @@ -346,6 +348,8 @@ be freed by the free functions defined in that library, to avoid assertion failu
multiple `libc` libraries exist on the system.
"""
free(p::Ptr) = ccall(:free, Cvoid, (Ptr{Cvoid},), p)
free(p::Cstring) = free(convert(Ptr{UInt8}, p))
free(p::Cwstring) = free(convert(Ptr{Cwchar_t}, p))

"""
malloc(size::Integer) -> Ptr{Cvoid}
Expand All @@ -371,8 +375,7 @@ Call `calloc` from the C standard library.
"""
calloc(num::Integer, size::Integer) = ccall(:calloc, Ptr{Cvoid}, (Csize_t, Csize_t), num, size)

free(p::Cstring) = free(convert(Ptr{UInt8}, p))
free(p::Cwstring) = free(convert(Ptr{Cwchar_t}, p))


## Random numbers ##

Expand Down
4 changes: 3 additions & 1 deletion base/mpfr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import
isone, big, _string_n, decompose, minmax,
sinpi, cospi, sincospi, tanpi, sind, cosd, tand, asind, acosd, atand


using .Base.Libc
import ..Rounding: rounding_raw, setrounding_raw

import ..GMP: ClongMax, CulongMax, CdoubleMax, Limb, libgmp
Expand Down Expand Up @@ -1140,7 +1142,7 @@ function decompose(x::BigFloat)::Tuple{BigInt, Int, Int}
s.size = cld(x.prec, 8*sizeof(Limb)) # limbs
b = s.size * sizeof(Limb) # bytes
ccall((:__gmpz_realloc2, libgmp), Cvoid, (Ref{BigInt}, Culong), s, 8b) # bits
ccall(:memcpy, Ptr{Cvoid}, (Ptr{Cvoid}, Ptr{Cvoid}, Csize_t), s.d, x.d, b) # bytes
memcpy(s.d, x.d, b)
s, x.exp - 8b, x.sign
end

Expand Down
8 changes: 5 additions & 3 deletions base/parse.jl
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,11 @@ function tryparse_internal(::Type{Bool}, sbuff::AbstractString,
len = endpos - startpos + 1
if sbuff isa Union{String, SubString{String}}
p = pointer(sbuff) + startpos - 1
GC.@preserve sbuff begin
(len == 4) && (0 == _memcmp(p, "true", 4)) && (return true)
(len == 5) && (0 == _memcmp(p, "false", 5)) && (return false)
truestr = "true"
falsestr = "false"
GC.@preserve sbuff truestr falsestr begin
(len == 4) && (0 == memcmp(p, unsafe_convert(Ptr{UInt8}, truestr), 4)) && (return true)
(len == 5) && (0 == memcmp(p, unsafe_convert(Ptr{UInt8}, falsestr), 5)) && (return false)
end
else
(len == 4) && (SubString(sbuff, startpos:startpos+3) == "true") && (return true)
Expand Down
1 change: 1 addition & 0 deletions base/refpointer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ else
primitive type Cwstring 32 end
end


### General Methods for Ref{T} type

eltype(x::Type{<:Ref{T}}) where {T} = @isdefined(T) ? T : Any
Expand Down
10 changes: 4 additions & 6 deletions base/reinterpretarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -387,8 +387,6 @@ end
end
end

@inline _memcpy!(dst, src, n) = ccall(:memcpy, Cvoid, (Ptr{UInt8}, Ptr{UInt8}, Csize_t), dst, src, n)

@inline @propagate_inbounds function _getindex_ra(a::NonReshapedReinterpretArray{T,N,S}, i1::Int, tailinds::TT) where {T,N,S,TT}
# Make sure to match the scalar reinterpret if that is applicable
if sizeof(T) == sizeof(S) && (fieldcount(T) + fieldcount(S)) == 0
Expand Down Expand Up @@ -434,7 +432,7 @@ end
while nbytes_copied < sizeof(T)
s[] = a.parent[ind_start + i, tailinds...]
nb = min(sizeof(S) - sidx, sizeof(T)-nbytes_copied)
_memcpy!(tptr + nbytes_copied, sptr + sidx, nb)
memcpy(tptr + nbytes_copied, sptr + sidx, nb)
nbytes_copied += nb
sidx = 0
i += 1
Expand Down Expand Up @@ -574,7 +572,7 @@ end
if sidx != 0
s[] = a.parent[ind_start + i, tailinds...]
nb = min((sizeof(S) - sidx) % UInt, sizeof(T) % UInt)
_memcpy!(sptr + sidx, tptr, nb)
memcpy(sptr + sidx, tptr, nb)
nbytes_copied += nb
a.parent[ind_start + i, tailinds...] = s[]
i += 1
Expand All @@ -583,7 +581,7 @@ end
# Deal with the main body of elements
while nbytes_copied < sizeof(T) && (sizeof(T) - nbytes_copied) > sizeof(S)
nb = min(sizeof(S), sizeof(T) - nbytes_copied)
_memcpy!(sptr, tptr + nbytes_copied, nb)
memcpy(sptr, tptr + nbytes_copied, nb)
nbytes_copied += nb
a.parent[ind_start + i, tailinds...] = s[]
i += 1
Expand All @@ -592,7 +590,7 @@ end
if nbytes_copied < sizeof(T)
s[] = a.parent[ind_start + i, tailinds...]
nb = min(sizeof(S), sizeof(T) - nbytes_copied)
_memcpy!(sptr, tptr + nbytes_copied, nb)
memcpy(sptr, tptr + nbytes_copied, nb)
a.parent[ind_start + i, tailinds...] = s[]
end
end
Expand Down
1 change: 1 addition & 0 deletions base/ryu/Ryu.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module Ryu

using .Base.Libc
import .Base: significand_bits, significand_mask, exponent_bits, exponent_mask, exponent_bias, exponent_max, uinttype

include("utils.jl")
Expand Down
20 changes: 10 additions & 10 deletions base/ryu/shortest.jl
Original file line number Diff line number Diff line change
Expand Up @@ -363,10 +363,10 @@ function writeshortest(buf::Vector{UInt8}, pos, x::T,
c1 = (c ÷ 100) << 1
d0 = (d % 100) << 1
d1 = (d ÷ 100) << 1
memcpy(ptr, pos + olength - 2, ptr2, c0 + 1, 2)
memcpy(ptr, pos + olength - 4, ptr2, c1 + 1, 2)
memcpy(ptr, pos + olength - 6, ptr2, d0 + 1, 2)
memcpy(ptr, pos + olength - 8, ptr2, d1 + 1, 2)
memcpy(ptr + pos + olength - 3, ptr2 + c0, 2)
memcpy(ptr + pos + olength - 5, ptr2 + c1, 2)
memcpy(ptr + pos + olength - 7, ptr2 + d0, 2)
memcpy(ptr + pos + olength - 9, ptr2 + d1, 2)
i += 8
end
output2 = output % UInt32
Expand All @@ -375,14 +375,14 @@ function writeshortest(buf::Vector{UInt8}, pos, x::T,
output2 = div(output2, UInt32(10000))
c0 = (c % 100) << 1
c1 = (c ÷ 100) << 1
memcpy(ptr, pos + olength - i - 2, ptr2, c0 + 1, 2)
memcpy(ptr, pos + olength - i - 4, ptr2, c1 + 1, 2)
memcpy(ptr + pos + olength - i - 3, ptr2 + c0, 2)
memcpy(ptr + pos + olength - i - 5, ptr2 + c1, 2)
i += 4
end
if output2 >= 100
c = (output2 % UInt32(100)) << 1
output2 = div(output2, UInt32(100))
memcpy(ptr, pos + olength - i - 2, ptr2, c + 1, 2)
memcpy(ptr + pos + olength - i - 3, ptr2 + c, 2)
i += 2
end
if output2 >= 10
Expand Down Expand Up @@ -425,7 +425,7 @@ function writeshortest(buf::Vector{UInt8}, pos, x::T,
end
else
pointoff = olength - abs(nexp)
memmove(ptr, pos + pointoff + 1, ptr, pos + pointoff, olength - pointoff + 1)
memmove(ptr + pos + pointoff, ptr + pos + pointoff - 1, olength - pointoff + 1)
buf[pos + pointoff] = decchar
pos += olength + 1
precision -= olength
Expand Down Expand Up @@ -470,11 +470,11 @@ function writeshortest(buf::Vector{UInt8}, pos, x::T,

if exp2 >= 100
c = exp2 % 10
memcpy(ptr, pos, ptr2, 2 * div(exp2, 10) + 1, 2)
memcpy(ptr + pos - 1, ptr2 + 2 * div(exp2, 10), 2)
buf[pos + 2] = UInt8('0') + (c % UInt8)
pos += 3
elseif exp2 >= 10
memcpy(ptr, pos, ptr2, 2 * exp2 + 1, 2)
memcpy(ptr + pos - 1, ptr2 + 2 * exp2, 2)
pos += 2
else
if padexp
Expand Down
Loading

0 comments on commit f8dd16e

Please sign in to comment.