diff --git a/Compiler/src/tfuncs.jl b/Compiler/src/tfuncs.jl index e314430e0bedf..f12a75555bfd8 100644 --- a/Compiler/src/tfuncs.jl +++ b/Compiler/src/tfuncs.jl @@ -524,11 +524,12 @@ function sizeof_nothrow(@nospecialize(x)) return true end -function _const_sizeof(@nospecialize(x)) +# f shall be Core.sizeof or Core.bitsizeof +function _const_sizeof(@nospecialize(f), @nospecialize(x)) # Constant GenericMemory does not have constant size isa(x, GenericMemory) && return Int size = try - Core.sizeof(x) + f(x) catch ex # Might return # "argument is an abstract type; size is indeterminate" or @@ -538,18 +539,18 @@ function _const_sizeof(@nospecialize(x)) end return Const(size) end -@nospecs function sizeof_tfunc(๐•ƒ::AbstractLattice, x) +@nospecs function size_tfunc(๐•ƒ::AbstractLattice, x, f) x = widenmustalias(x) - isa(x, Const) && return _const_sizeof(x.val) - isa(x, Conditional) && return _const_sizeof(Bool) - isconstType(x) && return _const_sizeof(type_parameter(x)) + isa(x, Const) && return _const_sizeof(f, x.val) + isa(x, Conditional) && return _const_sizeof(f, Bool) + isconstType(x) && return _const_sizeof(f, type_parameter(x)) xu = unwrap_unionall(x) if isa(xu, Union) - return tmerge(sizeof_tfunc(๐•ƒ, rewrap_unionall(xu.a, x)), - sizeof_tfunc(๐•ƒ, rewrap_unionall(xu.b, x))) + return tmerge(size_tfunc(๐•ƒ, rewrap_unionall(xu.a, x), f), + size_tfunc(๐•ƒ, rewrap_unionall(xu.b, x), f)) end - # Core.sizeof operates on either a type or a value. First check which - # case we're in. + # Core.sizeof or Core.bitsizeof operate on either a type or a value. + # First check which case we're in. t, exact = instanceof_tfunc(x, false) if t !== Bottom # The value corresponding to `x` at runtime could be a type. @@ -557,18 +558,21 @@ end x = unwrap_unionall(t) if exact && isa(x, Union) isinline = uniontype_layout(x)[1] - return isinline ? Const(Int(Core.sizeof(x))) : Bottom + return isinline ? Const(Int(f(x))) : Bottom end isa(x, DataType) || return Int - (isconcretetype(x) || isprimitivetype(x)) && return _const_sizeof(x) + (isconcretetype(x) || isprimitivetype(x)) && return _const_sizeof(f, x) else x = widenconst(x) - x !== DataType && isconcretetype(x) && return _const_sizeof(x) - isprimitivetype(x) && return _const_sizeof(x) + x !== DataType && isconcretetype(x) && return _const_sizeof(f, x) + isprimitivetype(x) && return _const_sizeof(f, x) end return Int end +@nospecs sizeof_tfunc(๐•ƒ::AbstractLattice, x) = size_tfunc(๐•ƒ, x, Core.sizeof) +@nospecs bitsizeof_tfunc(๐•ƒ::AbstractLattice, x) = size_tfunc(๐•ƒ, x, Core.bitsizeof) add_tfunc(Core.sizeof, 1, 1, sizeof_tfunc, 1) +add_tfunc(Core.bitsizeof, 1, 1, bitsizeof_tfunc, 1) @nospecs function nfields_tfunc(๐•ƒ::AbstractLattice, x) isa(x, Const) && return Const(nfields(x.val)) isa(x, Conditional) && return Const(0) @@ -2580,7 +2584,7 @@ function _builtin_nothrow(๐•ƒ::AbstractLattice, @nospecialize(f::Builtin), argt return subtype_nothrow(๐•ƒ, argtypes[1], argtypes[2]) elseif f === isdefined return isdefined_nothrow(๐•ƒ, argtypes) - elseif f === Core.sizeof + elseif f === Core.sizeof || f === Core.bitsizeof na == 1 || return false return sizeof_nothrow(argtypes[1]) elseif f === Core.ifelse @@ -2632,6 +2636,7 @@ const _CONSISTENT_BUILTINS = Any[ apply_type, isa, UnionAll, + Core.bitsizeof, Core.sizeof, Core.ifelse, (<:), @@ -2659,6 +2664,7 @@ const _EFFECT_FREE_BUILTINS = [ memoryrefget, memoryref_isassigned, isdefined, + Core.bitsizeof, Core.sizeof, Core.ifelse, Core._typevar, @@ -2676,6 +2682,7 @@ const _INACCESSIBLEMEM_BUILTINS = Any[ (<:), (===), apply_type, + Core.bitsizeof, Core.ifelse, Core.sizeof, svec, @@ -2890,6 +2897,7 @@ const _EFFECTS_KNOWN_BUILTINS = Any[ # Core.memoryrefsetonce!, # Core.memoryrefswap!, memoryrefunset!, + Core.bitsizeof, Core.sizeof, svec, Core.throw_methoderror, @@ -3252,7 +3260,7 @@ function intrinsic_exct(๐•ƒ::AbstractLattice, f::IntrinsicFunction, argtypes::V if !isconcrete return Union{ErrorException, TypeError} end - if !(isprimitivetype(ty) && isprimitivetype(xty) && Core.sizeof(ty) === Core.sizeof(xty)) + if !(isprimitivetype(ty) && isprimitivetype(xty) && Core.bitsizeof(ty) === Core.bitsizeof(xty)) return ErrorException end return Union{} @@ -3278,14 +3286,14 @@ function intrinsic_exct(๐•ƒ::AbstractLattice, f::IntrinsicFunction, argtypes::V !(ty <: CORE_FLOAT_TYPES && xty <: CORE_FLOAT_TYPES && Core.sizeof(ty) > Core.sizeof(xty)) return ErrorException end - if (f === Intrinsics.sext_int || f === Intrinsics.zext_int) && !(Core.sizeof(ty) > Core.sizeof(xty)) + if (f === Intrinsics.sext_int || f === Intrinsics.zext_int) && !(Core.bitsizeof(ty) > Core.bitsizeof(xty)) return ErrorException end if f === Intrinsics.fptrunc && !(ty <: CORE_FLOAT_TYPES && xty <: CORE_FLOAT_TYPES && Core.sizeof(ty) < Core.sizeof(xty)) return ErrorException end - if f === Intrinsics.trunc_int && !(Core.sizeof(ty) < Core.sizeof(xty)) + if f === Intrinsics.trunc_int && !(Core.bitsizeof(ty) < Core.bitsizeof(xty)) return ErrorException end if (f === Intrinsics.fptoui || f === Intrinsics.fptosi) && !(xty <: CORE_FLOAT_TYPES) diff --git a/Compiler/test/effects.jl b/Compiler/test/effects.jl index 805c514d18e5d..c5d7deb6ab490 100644 --- a/Compiler/test/effects.jl +++ b/Compiler/test/effects.jl @@ -1507,6 +1507,12 @@ end @test Base.infer_effects(invokelatest, Tuple{Vararg{Any}}) == Compiler.Effects() @test Base.infer_effects(invoke, Tuple{Vararg{Any}}) == Compiler.Effects() +bitsizeof_int() = Core.bitsizeof(Int) +let effects = Base.infer_effects(bitsizeof_int) + @test Compiler.is_foldable_nothrow(effects) + @test Compiler.is_inaccessiblememonly(effects) +end + # Core._svec_ref effects modeling (required for external abstract interpreter that doesn't run optimization) let effects = Base.infer_effects((Core.SimpleVector,Int); optimize=false) do svec, i Core._svec_ref(svec, i) @@ -1553,6 +1559,27 @@ let f = (x) -> Core.Intrinsics.trunc_int(Int16, x) @test catch_error_61435(f, Int16(0)) === :caught end +# Intrinsic width checks use logical primitive widths rather than storage sizes. +primitive type EffectsUInt17 17 end +primitive type EffectsUInt23 23 end +let f = (x) -> Core.Intrinsics.bitcast(EffectsUInt17, x) + @test !Compiler.is_nothrow(Base.infer_effects(f, (EffectsUInt23,))) + @test Base.infer_exception_type(f, (EffectsUInt23,)) === ErrorException + @test !fully_eliminated((EffectsUInt23,)) do x + f(x) + return nothing + end +end +let f = (x) -> Core.Intrinsics.zext_int(EffectsUInt23, x) + @test Compiler.is_nothrow(Base.infer_effects(f, (EffectsUInt17,))) +end +let f = (x) -> Core.Intrinsics.sext_int(EffectsUInt23, x) + @test Compiler.is_nothrow(Base.infer_effects(f, (EffectsUInt17,))) +end +let f = (x) -> Core.Intrinsics.trunc_int(EffectsUInt17, x) + @test Compiler.is_nothrow(Base.infer_effects(f, (EffectsUInt23,))) +end + # issue #57324 module Issue57324 struct T <: AbstractVector{Float64} diff --git a/Compiler/test/inference.jl b/Compiler/test/inference.jl index d2c5030f15957..88cafb880c4dd 100644 --- a/Compiler/test/inference.jl +++ b/Compiler/test/inference.jl @@ -1449,6 +1449,7 @@ test_const_return(()->sizeof(1 < 2), Tuple{}, 1) test_const_return(()->fieldtype(Dict{Int64,Nothing}, :age), Tuple{}, UInt) test_const_return(@eval(()->Core.sizeof($(Array{Int,0}(undef)))), Tuple{}, 2 * sizeof(Int)) test_const_return(@eval(()->Core.sizeof($(Matrix{Float32}(undef, 2, 2)))), Tuple{}, 4 * sizeof(Int)) +primitive type BitsizeofUInt17 17 end # TODO: do we want to implement these? # test_const_return(@eval(()->sizeof($(Array{Int,0}(undef)))), Tuple{}, sizeof(Int)) # test_const_return(@eval(()->sizeof($(Matrix{Float32}(undef, 2, 2)))), Tuple{}, 4 * 2 * 2) @@ -1460,6 +1461,15 @@ function sizeof_typeref(typeref) end @test @inferred(sizeof_typeref(Ref{DataType}(Int))) == sizeof(Int) @test find_call(only(code_typed(sizeof_typeref, (Ref{DataType},)))[1], Core.sizeof, 2) +# Make sure Core.bitsizeof with a ::DataType as inferred input type is inferred but not constant. +function bitsizeof_typeref(typeref) + return Core.bitsizeof(typeref[]) +end +@test bitsizeof_typeref(Ref{DataType}(BitsizeofUInt17)) == 17 +let (src, rt) = only(code_typed(bitsizeof_typeref, (Ref{DataType},))) + @test rt === Int + @test find_call(src, Core.bitsizeof, 2) +end # Constant `Vector` can be resized and shouldn't be optimized to a constant. const constvec = [1, 2, 3] @eval function sizeof_constvec() @@ -1642,10 +1652,14 @@ let nfields_tfunc(@nospecialize xs...) = Compiler.nfields_tfunc(Compiler.fallback_lattice, xs...) sizeof_tfunc(@nospecialize xs...) = Compiler.sizeof_tfunc(Compiler.fallback_lattice, xs...) + bitsizeof_tfunc(@nospecialize xs...) = + Compiler.bitsizeof_tfunc(Compiler.fallback_lattice, xs...) sizeof_nothrow(@nospecialize xs...) = Compiler.sizeof_nothrow(xs...) @test sizeof_tfunc(Const(Ptr)) === sizeof_tfunc(Union{Ptr, Int, Type{Ptr{Int8}}, Type{Int}}) === Const(Sys.WORD_SIZE รท 8) @test sizeof_tfunc(Type{Ptr}) === Const(sizeof(Ptr)) + @test bitsizeof_tfunc(Type{BitsizeofUInt17}) === Const(17) + @test bitsizeof_tfunc(DataType) === Int @test !sizeof_nothrow(Union{Ptr, Int, Type{Ptr{Int8}}, Type{Int}}) @test sizeof_nothrow(Union{Ptr, Int, Core.TypeEgal{Ptr{Int8}}, Core.TypeEgal{Int}}) @test sizeof_nothrow(Const(Ptr)) diff --git a/base/essentials.jl b/base/essentials.jl index d8f887320003d..a8eae32cee5f4 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -834,6 +834,32 @@ Stacktrace: """ sizeof(x) = Core.sizeof(x) +""" + Core.bitsizeof(T::DataType) + Core.bitsizeof(obj) + +Logical size, in bits, of the canonical binary representation of the given `DataType` `T`, if any. +Or the logical size, in bits, of object `obj` if it is not a `DataType`. + +For primitive types, this may differ from `8*sizeof(T)` when the type uses byte-rounded storage +with unused bits in the last byte. + +# Examples +```jldoctest +julia> Core.bitsizeof(Float32) +32 + +julia> Core.bitsizeof(1.0) +64 + +julia> primitive type MyUInt63 <: Unsigned 63 end + +julia> Core.bitsizeof(MyUInt63) +63 +``` +""" +Core.bitsizeof + """ ifelse(condition::Bool, x, y) diff --git a/base/intfuncs.jl b/base/intfuncs.jl index 4a89e321d1343..4313d4959f19f 100644 --- a/base/intfuncs.jl +++ b/base/intfuncs.jl @@ -1139,13 +1139,15 @@ julia> bitstring(2.2) """ function bitstring(x::T) where {T} isprimitivetype(T) || throw(ArgumentError(LazyString(T, " not a primitive type"))) - sz = sizeof(T) * 8 + sz = Core.bitsizeof(T) + onebyte = sz == 8 + subbyte = sz < 8 str = _string_n(sz) GC.@preserve str begin p = pointer(str) i = sz while i >= 4 - b = UInt32(sizeof(T) == 1 ? bitcast(UInt8, x) : trunc_int(UInt8, x)) + b = UInt32(onebyte ? bitcast(UInt8, x) : subbyte ? zext_int(UInt8, x) : trunc_int(UInt8, x)) d = 0x30303030 +% ((b *% 0x08040201) >> 0x3) & 0x01010101 unsafe_store!(p, (d >> 0x00) % UInt8, i-3) unsafe_store!(p, (d >> 0x08) % UInt8, i-2) @@ -1154,6 +1156,12 @@ function bitstring(x::T) where {T} x = lshr_int(x, 4) i -= 4 end + while i > 0 + b = UInt8(onebyte ? bitcast(UInt8, x) : subbyte ? zext_int(UInt8, x) : trunc_int(UInt8, x)) + unsafe_store!(p, 0x30 + (b & 0x01), i) + x = lshr_int(x, 1) + i -= 1 + end end return str end diff --git a/base/reinterpretarray.jl b/base/reinterpretarray.jl index 18a95099d87e0..1696a3bbf8b5a 100644 --- a/base/reinterpretarray.jl +++ b/base/reinterpretarray.jl @@ -768,6 +768,11 @@ end """ @assume_effects :foldable function padding(T::DataType, baseoffset::Int = 0) pads = Padding[] + if isprimitivetype(T) + Core.bitsizeof(T) % 8 == 0 || throw(ArgumentError(LazyString( + "padding cannot be computed for non-byte-aligned primitive type ", T))) + return Core.svec() + end last_end::Int = baseoffset for i = 1:fieldcount(T) offset = baseoffset + Int(fieldoffset(T, i)) @@ -827,11 +832,30 @@ end end @assume_effects :foldable function packedsize(::Type{T}) where T + if isprimitivetype(T) + Core.bitsizeof(T) % 8 == 0 || throw(ArgumentError(LazyString( + "packed size cannot be computed for non-byte-aligned primitive type ", T))) + return Core.bitsizeof(T) >> 3 + end pads = padding(T) return sizeof(T) - sum((p.size for p โˆˆ pads), init = 0) end -@assume_effects :foldable ispacked(::Type{T}) where T = isempty(padding(T)) +@assume_effects :foldable function ispacked(::Type{T}) where T + isprimitivetype(T) && return Core.bitsizeof(T) == sizeof(T) * 8 + return isempty(padding(T)) +end + +@assume_effects :foldable function has_bit_padding(::Type{T}) where T + if isprimitivetype(T) + return Core.bitsizeof(T) % 8 != 0 + end + T isa Union && return any(has_bit_padding, uniontypes(T)) + for i in 1:fieldcount(T) + has_bit_padding(fieldtype(T, i)) && return true + end + return false +end function _copytopacked!(ptr_out::Ptr{Out}, ptr_in::Ptr{In}) where {Out, In} writeoffset = 0 @@ -869,6 +893,10 @@ end # handle non-primitive types isbitstype(Out) || throw(ArgumentError("Target type for `reinterpret` must be isbits")) isbitstype(In) || throw(ArgumentError("Source type for `reinterpret` must be isbits")) + has_bit_padding(Out) && throw(ArgumentError(LazyString( + "cannot `reinterpret` type ", Out, " containing non-byte-aligned primitive fields"))) + has_bit_padding(In) && throw(ArgumentError(LazyString( + "cannot `reinterpret` type ", In, " containing non-byte-aligned primitive fields"))) inpackedsize = packedsize(In) outpackedsize = packedsize(Out) inpackedsize == outpackedsize || diff --git a/base/runtime_internals.jl b/base/runtime_internals.jl index 5c3ce50a373d0..5326c67f9aae7 100644 --- a/base/runtime_internals.jl +++ b/base/runtime_internals.jl @@ -617,7 +617,8 @@ struct DataTypeLayout # arrayelem_isatomic : 1; # arrayelem_islocked : 1; # isbitsegal : 1; - # padding : 8; + # unused_bits : 3; + # padding : 5; end function DataTypeLayout(dt::DataType) diff --git a/base/show.jl b/base/show.jl index e5d172d70a3e7..6d4adc1154c2c 100644 --- a/base/show.jl +++ b/base/show.jl @@ -525,10 +525,18 @@ function _show_default(io::IO, @nospecialize(x)) else print(io, "0x") r = Ref{Any}(x) + nbits = Core.bitsizeof(t) + nbytes = cld(nbits, 8) GC.@preserve r begin p = unsafe_convert(Ptr{Cvoid}, r) - for i in (nb - 1):-1:0 - print(io, string(unsafe_load(convert(Ptr{UInt8}, p + i)), base = 16, pad = 2)) + for i in (nbytes - 1):-1:0 + byte = unsafe_load(convert(Ptr{UInt8}, p + i)) + if i == nbytes - 1 && nbits % 8 != 0 + byte &= (UInt8(1) << (nbits % 8)) - UInt8(1) + print(io, string(byte, base = 16, pad = cld(nbits % 8, 4))) + else + print(io, string(byte, base = 16, pad = 2)) + end end end end @@ -1374,7 +1382,7 @@ show(io::IO, ::Nothing) = print(io, "nothing") show(io::IO, n::Signed) = (write(io, string(n)); nothing) function show(io::IO, n::Unsigned) if get(io, :hexunsigned, true)::Bool - print(io, "0x", string(n, pad = sizeof(n)<<1, base = 16)) + print(io, "0x", string(n, pad = cld(Core.bitsizeof(n), 4), base = 16)) else if get(io, :typeinfo, Nothing)::Type == typeof(n) print(io, n) diff --git a/doc/src/manual/types.md b/doc/src/manual/types.md index 90bcee2ce60e2..76e5aa480370b 100644 --- a/doc/src/manual/types.md +++ b/doc/src/manual/types.md @@ -296,14 +296,20 @@ primitive type ยซnameยป ยซbitsยป end primitive type ยซnameยป <: ยซsupertypeยป ยซbitsยป end ``` -The number of bits indicates how much storage the type requires and the name gives the new type -a name. A primitive type can optionally be declared to be a subtype of some supertype. If a supertype -is omitted, then the type defaults to having `Any` as its immediate supertype. The declaration -of [`Bool`](@ref) above therefore means that a boolean value takes eight bits to store, and has -[`Integer`](@ref) as its immediate supertype. Currently, only sizes that are multiples of -8 bits are supported and you are more likely to experience bugs with sizes other than those used above. -Therefore, boolean values, although they really need just a single bit, cannot be declared to be any -smaller than eight bits. +The number of bits gives the declared logical width of the primitive type, and the name gives the +new type a name. A primitive type can optionally be declared to be a subtype of some supertype. If a +supertype is omitted, then the type defaults to having `Any` as its immediate supertype. The +declaration of [`Bool`](@ref) above therefore means that a boolean value has a logical width of eight +bits, and has [`Integer`](@ref) as its immediate supertype. Primitive types continue to use +byte-rounded storage, so `sizeof(T)` rounds their storage size up to a whole number of bytes even +when their logical width is not a multiple of 8 bits. Use `Core.bitsizeof(T)` to query the declared +logical width. + +Non-byte primitive widths are accepted, but remain an expert-only feature. They are more likely to +expose compiler, runtime, or ABI bugs than the standard built-in primitive widths, and arrays still +use byte-rounded element storage rather than packed bit layouts. Therefore, boolean values, although +they really need just a single bit, should still use the built-in eight-bit [`Bool`](@ref) unless +you are deliberately working with these expert-only semantics. The types [`Bool`](@ref), [`Int8`](@ref) and [`UInt8`](@ref) all have identical representations: they are eight-bit chunks of memory. Since Julia's type system is nominative, however, they diff --git a/src/APInt.c b/src/APInt.c index 1dfcc72b7541d..21cb3a1beca70 100644 --- a/src/APInt.c +++ b/src/APInt.c @@ -705,7 +705,7 @@ static int FPtoInt(jl_datatype_t *ty, void *pa, jl_datatype_t *oty, "implemented for bit sizes other than 16, 32 and 64"); unsigned onumbytes = jl_datatype_size(oty); - unsigned onumbits = onumbytes * 8; + unsigned onumbits = jl_datatype_nbits(oty); unsigned nw = APINT_NWORDS(onumbits); uint64_t *result = (uint64_t *)alloca(nw * sizeof(uint64_t)); memset(result, 0, nw * sizeof(uint64_t)); @@ -749,8 +749,7 @@ JL_DLLEXPORT int APInt_fptoui_exact(jl_datatype_t *ty, integerPart *pa, jl_datat static void APInt_inttofp(jl_datatype_t *ty, integerPart *pa, jl_datatype_t *oty, integerPart *pr, int is_signed) JL_NOTSAFEPOINT { - unsigned numbytes = jl_datatype_size(ty); - unsigned numbits = numbytes * 8; + unsigned numbits = jl_datatype_nbits(ty); unsigned nw = APINT_NWORDS(numbits); uint64_t *a = (uint64_t *)alloca(nw * 8); load(a, pa, numbits); @@ -801,18 +800,21 @@ JL_DLLEXPORT void APInt_uitofp(jl_datatype_t *ty, integerPart *pa, jl_datatype_t JL_DLLEXPORT void APInt_sext(jl_datatype_t *ty, integerPart *pa, jl_datatype_t *otys, integerPart *pr) { - unsigned inumbytes = jl_datatype_size(ty); + unsigned inumbits = jl_datatype_nbits(ty); + unsigned onumbits = jl_datatype_nbits(otys); + unsigned inumbytes = APINT_NBYTES(inumbits); unsigned onumbytes = jl_datatype_size(otys); - if (!(onumbytes > inumbytes)) + if (!(onumbits > inumbits)) jl_error("SExt: output bitsize must be > input bitsize"); - unsigned inumbits = inumbytes * 8; - int bits = (0 - inumbits) % 8; + unsigned bits = (8 - (inumbits % 8)) % 8; int signbit = (inumbits - 1) % 8; int sign = ((unsigned char *)pa)[inumbytes - 1] & (1 << signbit) ? -1 : 0; memcpy(pr, pa, inumbytes); if (bits) { - ((signed char *)pr)[inumbytes - 1] = - ((signed char *)pa)[inumbytes - 1] << bits >> bits; + unsigned char byte = ((unsigned char *)pa)[inumbytes - 1]; + ((unsigned char *)pr)[inumbytes - 1] = sign + ? (byte | (unsigned char)(0xFF << (8 - bits))) + : (byte & (unsigned char)(0xFF >> bits)); } memset((char *)pr + inumbytes, sign, onumbytes - inumbytes); } @@ -820,16 +822,17 @@ JL_DLLEXPORT void APInt_sext(jl_datatype_t *ty, integerPart *pa, JL_DLLEXPORT void APInt_zext(jl_datatype_t *ty, integerPart *pa, jl_datatype_t *otys, integerPart *pr) { - unsigned inumbytes = jl_datatype_size(ty); + unsigned inumbits = jl_datatype_nbits(ty); + unsigned onumbits = jl_datatype_nbits(otys); + unsigned inumbytes = APINT_NBYTES(inumbits); unsigned onumbytes = jl_datatype_size(otys); - if (!(onumbytes > inumbytes)) + if (!(onumbits > inumbits)) jl_error("ZExt: output bitsize must be > input bitsize"); - unsigned inumbits = inumbytes * 8; - int bits = (0 - inumbits) % 8; + unsigned bits = (8 - (inumbits % 8)) % 8; memcpy(pr, pa, inumbytes); if (bits) { ((unsigned char *)pr)[inumbytes - 1] = - ((unsigned char *)pa)[inumbytes - 1] << bits >> bits; + ((unsigned char *)pa)[inumbytes - 1] & (unsigned char)(0xFF >> bits); } memset((char *)pr + inumbytes, 0, onumbytes - inumbytes); } @@ -837,11 +840,17 @@ JL_DLLEXPORT void APInt_zext(jl_datatype_t *ty, integerPart *pa, JL_DLLEXPORT void APInt_trunc(jl_datatype_t *ty, integerPart *pa, jl_datatype_t *otys, integerPart *pr) { - unsigned inumbytes = jl_datatype_size(ty); - unsigned onumbytes = jl_datatype_size(otys); - if (!(onumbytes < inumbytes)) + unsigned inumbits = jl_datatype_nbits(ty); + unsigned onumbits = jl_datatype_nbits(otys); + unsigned onumbytes_storage = jl_datatype_size(otys); + unsigned onumbytes_value = APINT_NBYTES(onumbits); + if (!(onumbits < inumbits)) jl_error("Trunc: output bitsize must be < input bitsize"); - memcpy(pr, pa, onumbytes); + memcpy(pr, pa, onumbytes_value); + if (onumbits % 8) + ((unsigned char *)pr)[onumbytes_value - 1] &= (1 << (onumbits % 8)) - 1; + if (onumbytes_value < onumbytes_storage) + memset((char *)pr + onumbytes_value, 0, onumbytes_storage - onumbytes_value); } // ---- Misc ---- diff --git a/src/abi_win32.cpp b/src/abi_win32.cpp index 4e0d34a0a1ff9..15d26c89fbab0 100644 --- a/src/abi_win32.cpp +++ b/src/abi_win32.cpp @@ -76,7 +76,7 @@ Type *preferred_llvm_type(jl_datatype_t *dt, bool isret, LLVMContext &ctx) const // rewrite integer sized (non-sret) struct to the corresponding integer if (!dt->layout->nfields && !dt->layout->npointers) return NULL; - return Type::getIntNTy(ctx, jl_datatype_nbits(dt)); + return Type::getIntNTy(ctx, jl_datatype_size(dt) * 8); } }; diff --git a/src/abi_win64.cpp b/src/abi_win64.cpp index 3cc5abca927ce..720ece35334b7 100644 --- a/src/abi_win64.cpp +++ b/src/abi_win64.cpp @@ -72,7 +72,7 @@ Type *preferred_llvm_type(jl_datatype_t *dt, bool isret, LLVMContext &ctx) const { size_t size = jl_datatype_size(dt); if (size > 0 && win64_reg_size(size) && !jl_is_primitivetype(dt)) - return Type::getIntNTy(ctx, jl_datatype_nbits(dt)); + return Type::getIntNTy(ctx, jl_datatype_size(dt) * 8); return NULL; } diff --git a/src/abi_x86_64.cpp b/src/abi_x86_64.cpp index cb03ce7ef04cc..f7d73b404381c 100644 --- a/src/abi_x86_64.cpp +++ b/src/abi_x86_64.cpp @@ -223,7 +223,7 @@ Type *preferred_llvm_type(jl_datatype_t *dt, bool isret, LLVMContext &ctx) const return NULL; size_t size = jl_datatype_size(dt); - size_t nbits = jl_datatype_nbits(dt); + size_t nbits = size * 8; if (size > 16 || size == 0) return NULL; diff --git a/src/builtin_proto.h b/src/builtin_proto.h index dba229cdfdea5..5faf705536439 100644 --- a/src/builtin_proto.h +++ b/src/builtin_proto.h @@ -67,6 +67,7 @@ extern "C" { XX(setfieldonce,"setfieldonce!") \ XX(setglobal,"setglobal!") \ XX(setglobalonce,"setglobalonce!") \ + XX(bitsizeof,"bitsizeof") \ XX(sizeof,"sizeof") \ XX(svec,"svec") \ XX(swapfield,"swapfield!") \ diff --git a/src/builtins.c b/src/builtins.c index 6af2dc5ebeae5..24df4ffad5205 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -98,6 +98,21 @@ static int NOINLINE compare_svec(jl_svec_t *a, jl_svec_t *b) JL_NOTSAFEPOINT return 1; } +static inline uint8_t last_byte_mask(jl_datatype_t *dt) JL_NOTSAFEPOINT +{ + uint32_t unused = jl_datatype_unusedbits(dt); + return (uint8_t)(0xff >> unused); +} + +static inline int primitive_bits_equal(const void *a, const void *b, jl_datatype_t *dt) JL_NOTSAFEPOINT +{ + size_t sz = jl_datatype_size(dt); + if (sz == 0) + return 1; + return (sz <= 1 || bits_equal(a, b, sz - 1)) && + ((((const uint8_t*)a)[sz - 1] ^ ((const uint8_t*)b)[sz - 1]) & last_byte_mask(dt)) == 0; +} + // See comment above for an explanation of NOINLINE. static int NOINLINE compare_fields(const jl_value_t *a, const jl_value_t *b, jl_datatype_t *dt) JL_NOTSAFEPOINT { @@ -143,8 +158,13 @@ static int NOINLINE compare_fields(const jl_value_t *a, const jl_value_t *b, jl_ return 0; } else { - assert(jl_datatype_nfields(ft) > 0); - if (!compare_fields((jl_value_t*)ao, (jl_value_t*)bo, ft)) + if (jl_datatype_nfields(ft) == 0) { + // Odd-bit primitives have trailing unused bits, which are + // represented as padding even though they have no fields. + if (!primitive_bits_equal(ao, bo, ft)) + return 0; + } + else if (!compare_fields((jl_value_t*)ao, (jl_value_t*)bo, ft)) return 0; } } @@ -346,7 +366,9 @@ inline int jl_egal__bits(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t if (sz == 0) return 1; size_t nf = jl_datatype_nfields(dt); - if (nf == 0 || (!dt->layout->flags.haspadding && dt->layout->flags.isbitsegal)) + if (nf == 0) + return dt->layout->flags.haspadding ? primitive_bits_equal(a, b, dt) : bits_equal(a, b, sz); + if (!dt->layout->flags.haspadding && dt->layout->flags.isbitsegal) return bits_equal(a, b, sz); return compare_fields(a, b, dt); } @@ -466,6 +488,12 @@ static uintptr_t immut_id_(jl_datatype_t *dt, jl_value_t *v, uintptr_t h) JL_NOT // a few select pointers (notably symbol) also have special hash values // which may affect the stability of the objectid hash, even though // they don't affect egal comparison + if (nf == 0 && dt->layout->flags.haspadding) { + void *buf = alloca(sz); + memcpy(buf, v, sz); + ((uint8_t*)buf)[sz - 1] &= last_byte_mask(dt); + return bits_hash(buf, sz) ^ h; + } return bits_hash(v, sz) ^ h; } if (dt == jl_unionall_type) @@ -622,6 +650,26 @@ JL_CALLABLE(jl_f_sizeof) return jl_box_long(sz); } +JL_CALLABLE(jl_f_bitsizeof) +{ + JL_NARGS(bitsizeof, 1, 1); + jl_value_t *x = args[0]; + if (jl_is_unionall(x) || jl_is_uniontype(x)) + return jl_box_long(jl_unbox_long(jl_f_sizeof(F, args, 1)) * 8); + if (jl_is_datatype(x)) { + jl_datatype_t *dx = (jl_datatype_t*)x; + if (jl_is_primitivetype(dx)) + return jl_box_long(jl_datatype_nbits(dx)); + return jl_box_long(jl_unbox_long(jl_f_sizeof(F, args, 1)) * 8); + } + if (x == jl_bottom_type) + jl_error("The empty type does not have a definite size since it does not have instances."); + jl_datatype_t *dt = (jl_datatype_t*)jl_typeof(x); + if (jl_is_primitivetype(dt)) + return jl_box_long(jl_datatype_nbits(dt)); + return jl_box_long(jl_unbox_long(jl_f_sizeof(F, args, 1)) * 8); +} + JL_CALLABLE(jl_f_issubtype) { JL_NARGS(<:, 2, 2); @@ -2214,7 +2262,7 @@ JL_CALLABLE(jl_f__primitivetype) jl_errorf("invalid declaration of primitive type %s", jl_symbol_name((jl_sym_t*)name)); ssize_t nb = jl_unbox_long(vnb); - if (nb < 1 || nb >= (1 << 23) || (nb & 7) != 0) + if (nb < 1 || nb >= (1 << 23)) jl_errorf("invalid number of bits in primitive type %s", jl_symbol_name((jl_sym_t*)name)); jl_datatype_t *dt = jl_new_primitivetype(args[1], (jl_module_t*)args[0], NULL, (jl_svec_t*)args[2], nb); diff --git a/src/cgutils.cpp b/src/cgutils.cpp index 5c414ac60f559..5aadcd71668c9 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -824,7 +824,7 @@ static Type *_julia_type_to_llvm(jl_codegen_output_t *ctx, LLVMContext &ctxt, jl if (is_typeofbottom_typealias(jt)) return getVoidTy(ctxt); if (jl_is_concrete_immutable(jt) || no_boxing) { - if (jl_datatype_nbits(jt) == 0) + if (jl_datatype_nbits((jl_datatype_t*)jt) == 0) return getVoidTy(ctxt); Type *t = _julia_struct_to_llvm(ctx, ctxt, jt, isboxed); assert(t != NULL); @@ -884,8 +884,8 @@ static Type *bitstype_to_llvm(jl_value_t *bt, LLVMContext &ctxt, bool llvmcall = jl_error("invalid pointer address space"); return PointerType::get(ctxt, as); } - int nb = jl_datatype_size(bt); - return Type::getIntNTy(ctxt, nb * 8); + int nb = jl_datatype_nbits((jl_datatype_t*)bt); + return Type::getIntNTy(ctxt, nb); } static bool jl_type_hasptr(jl_value_t* typ) @@ -2475,7 +2475,8 @@ static jl_cgval_t typed_load(jl_codectx_t &ctx, Value *ptr, Value *idx_0based, j } if (isa(elty)) { unsigned nb2 = PowerOf2Ceil(nb); - if (nb != nb2) + unsigned bitwidth = cast(elty)->getBitWidth(); + if (nb != nb2 || bitwidth != 8 * nb) elty = Type::getIntNTy(ctx.builder.getContext(), 8 * nb2); } } @@ -2654,7 +2655,8 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, realelty = elty; if (Order != AtomicOrdering::NotAtomic && isa(elty)) { unsigned nb2 = PowerOf2Ceil(nb); - if (nb != nb2) + unsigned bitwidth = cast(elty)->getBitWidth(); + if (nb != nb2 || bitwidth != 8 * nb) elty = Type::getIntNTy(ctx.builder.getContext(), 8 * nb2); } if (op != StoreKind::Modify) { diff --git a/src/common_symbols2.inc b/src/common_symbols2.inc index e9c070ee8da6a..f10f955e69004 100644 --- a/src/common_symbols2.inc +++ b/src/common_symbols2.inc @@ -9,6 +9,7 @@ jl_symbol("unchecked_oneto"), jl_symbol("structdiff"), jl_symbol("UnitRange"), jl_symbol("unitrange_last"), +jl_symbol("bitsizeof"), jl_symbol("sizeof"), jl_symbol("check_sign_bit"), jl_symbol("is_top_bit_set"), diff --git a/src/datatype.c b/src/datatype.c index 3513502da5594..7d4257b52a1f1 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -193,6 +193,7 @@ static jl_datatype_layout_t *jl_get_layout(uint32_t sz, int haspadding, int isbitsegal, int arrayelem, + uint8_t unused_bits, jl_fielddesc32_t desc[], uint32_t pointers[]) JL_NOTSAFEPOINT { @@ -244,6 +245,7 @@ static jl_datatype_layout_t *jl_get_layout(uint32_t sz, flddesc->flags.arrayelem_isunion = (arrayelem & 2) != 0; flddesc->flags.arrayelem_isatomic = (arrayelem & 4) != 0; flddesc->flags.arrayelem_islocked = (arrayelem & 8) != 0; + flddesc->flags.unused_bits = unused_bits; flddesc->flags.padding = 0; flddesc->npointers = npointers; flddesc->first_ptr = first_ptr; @@ -614,7 +616,7 @@ static void jl_get_genericmemory_layout(jl_datatype_t *st) JL_CANSAFEPOINT arrayelem |= 8; // arrayelem_islocked } assert(!st->layout); - st->layout = jl_get_layout(elsz, nfields, npointers, al, haspadding, isbitsegal, arrayelem, NULL, pointers); + st->layout = jl_get_layout(elsz, nfields, npointers, al, haspadding, isbitsegal, arrayelem, 0, NULL, pointers); st->zeroinit = zi; //st->has_concrete_subtype = 1; //st->isbitstype = 0; @@ -852,7 +854,7 @@ void jl_compute_field_offsets(jl_datatype_t *st) } } assert(ptr_i == npointers); - st->layout = jl_get_layout(sz, nfields, npointers, alignm, haspadding, isbitsegal, 0, desc, pointers); + st->layout = jl_get_layout(sz, nfields, npointers, alignm, haspadding, isbitsegal, 0, 0, desc, pointers); if (should_malloc) { free(desc); if (npointers) @@ -1003,6 +1005,7 @@ JL_DLLEXPORT jl_datatype_t *jl_new_primitivetype(jl_value_t *name, jl_module_t * jl_datatype_t *bt = jl_new_datatype((jl_sym_t*)name, module, super, parameters, jl_emptysvec, jl_emptysvec, jl_emptysvec, 0, 0, 0); uint32_t nbytes = (nbits + 7) / 8; + uint8_t unused_bits = (uint8_t)(nbytes * 8 - nbits); uint32_t alignm = next_power_of_two(nbytes); # if defined(_CPU_X86_) && !defined(_OS_WINDOWS_) // datalayout strings are often weird: on 64-bit they usually follow fairly simple rules, @@ -1025,7 +1028,7 @@ JL_DLLEXPORT jl_datatype_t *jl_new_primitivetype(jl_value_t *name, jl_module_t * bt->ismutationfree = 1; bt->isidentityfree = 1; bt->isbitstype = (parameters == jl_emptysvec); - bt->layout = jl_get_layout(nbytes, 0, 0, alignm, 0, 1, 0, NULL, NULL); + bt->layout = jl_get_layout(nbytes, 0, 0, alignm, unused_bits != 0, 1, 0, unused_bits, NULL, NULL); bt->instance = NULL; return bt; } @@ -1050,6 +1053,7 @@ JL_DLLEXPORT jl_datatype_t * jl_new_foreign_type(jl_sym_t *name, layout->flags.haspadding = 1; layout->flags.isbitsegal = 0; layout->flags.fielddesc_type = JL_FIELDDESC_FOREIGN; + layout->flags.unused_bits = 0; layout->flags.padding = 0; layout->flags.arrayelem_isboxed = 0; layout->flags.arrayelem_isunion = 0; diff --git a/src/intrinsics.cpp b/src/intrinsics.cpp index 62a815a39d42a..5f2ead54dfd13 100644 --- a/src/intrinsics.cpp +++ b/src/intrinsics.cpp @@ -213,21 +213,23 @@ static Constant *julia_const_to_llvm(jl_codectx_t &ctx, const void *ptr, jl_data APFloat(lt->getFltSemantics(), APInt(64, data64))); } if (lt->isFloatingPointTy() || lt->isIntegerTy() || lt->isPointerTy()) { - int nb = jl_datatype_size(bt); - APInt val(8 * nb, 0); + int nbytes = jl_datatype_size(bt); + APInt val(jl_datatype_nbits(bt), 0); void *bits = const_cast(val.getRawData()); assert(sys::IsLittleEndianHost); - memcpy(bits, ptr, nb); + memcpy(bits, ptr, nbytes); + if (nbytes > 0) + ((uint8_t*)bits)[nbytes - 1] &= (uint8_t)(0xff >> jl_datatype_unusedbits(bt)); if (lt->isFloatingPointTy()) { return ConstantFP::get(ctx.builder.getContext(), APFloat(lt->getFltSemantics(), val)); } if (lt->isPointerTy()) { - Type *Ty = IntegerType::get(ctx.builder.getContext(), 8 * nb); + Type *Ty = IntegerType::get(ctx.builder.getContext(), nbytes * 8); Constant *addr = ConstantInt::get(Ty, val); return ConstantExpr::getIntToPtr(addr, lt); } - assert(cast(lt)->getBitWidth() == 8u * nb); + assert(cast(lt)->getBitWidth() == val.getBitWidth()); return ConstantInt::get(lt, val); } @@ -570,6 +572,7 @@ static jl_cgval_t generic_bitcast(jl_codectx_t &ctx, ArrayRef argv) Type *llvmt = bitstype_to_llvm((jl_value_t*)bt, ctx.builder.getContext(), true); uint32_t nb = jl_datatype_size(bt); + uint32_t nbits = jl_datatype_nbits(bt); Value *bt_value_rt = NULL; if (!jl_is_concrete_type((jl_value_t*)bt)) { @@ -580,30 +583,19 @@ static jl_cgval_t generic_bitcast(jl_codectx_t &ctx, ArrayRef argv) // Examine the second argument // bool isboxed; Type *vxt = julia_type_to_llvm(ctx, v.typ, &isboxed); - if (!jl_is_primitivetype(v.typ) || jl_datatype_size(v.typ) != nb) { + if (!jl_is_primitivetype(v.typ)) { Value *typ = emit_typeof(ctx, v, false, false); - if (!jl_is_primitivetype(v.typ)) { - if (jl_is_datatype(v.typ) && !jl_is_abstracttype(v.typ)) { - emit_error(ctx, "bitcast: value not a primitive type"); - return jl_cgval_t(); - } - else { - Value *isprimitive = emit_datatype_isprimitivetype(ctx, typ); - error_unless(ctx, isprimitive, "bitcast: value not a primitive type"); - } - } if (jl_is_datatype(v.typ) && !jl_is_abstracttype(v.typ)) { - emit_error(ctx, "bitcast: argument size does not match size of target type"); + emit_error(ctx, "bitcast: value not a primitive type"); return jl_cgval_t(); } - else { - Value *size = emit_datatype_size(ctx, typ); - auto sizecheck = ctx.builder.CreateICmpEQ(size, ConstantInt::get(size->getType(), nb)); - setName(ctx.emission_context, sizecheck, "sizecheck"); - error_unless(ctx, - sizecheck, - "bitcast: argument size does not match size of target type"); - } + Value *isprimitive = emit_datatype_isprimitivetype(ctx, typ); + error_unless(ctx, isprimitive, "bitcast: value not a primitive type"); + return emit_runtime_call(ctx, bitcast, argv, 2); + } + if (jl_datatype_nbits((jl_datatype_t*)v.typ) != nbits) { + emit_error(ctx, "bitcast: argument bitsize does not match bitsize of target type"); + return jl_cgval_t(); } assert(!v.isghost); diff --git a/src/julia.h b/src/julia.h index 529b2b25163ad..69133ca4238e7 100644 --- a/src/julia.h +++ b/src/julia.h @@ -714,7 +714,8 @@ typedef struct { // If set, this type's egality can be determined entirely by comparing // the non-padding bits of this datatype. uint16_t isbitsegal : 1; - uint16_t padding : 8; + uint16_t unused_bits : 3; + uint16_t padding : 5; } flags; // union { // jl_fielddesc8_t field8[nfields]; @@ -1573,8 +1574,16 @@ STATIC_INLINE const jl_datatype_layout_t *jl_datatype_layout(jl_datatype_t *t) J } #define jl_datatype_size(t) (jl_datatype_layout((jl_datatype_t*)(t))->size) #define jl_datatype_align(t) (jl_datatype_layout((jl_datatype_t*)(t))->alignment) -#define jl_datatype_nbits(t) ((jl_datatype_layout((jl_datatype_t*)(t))->size)*8) #define jl_datatype_nfields(t) (jl_datatype_layout((jl_datatype_t*)(t))->nfields) +STATIC_INLINE uint32_t jl_datatype_unusedbits(jl_datatype_t *t) JL_NOTSAFEPOINT +{ + return jl_datatype_layout(t)->flags.unused_bits; +} +STATIC_INLINE uint32_t jl_datatype_nbits(jl_datatype_t *t) JL_NOTSAFEPOINT +{ + const jl_datatype_layout_t *layout = jl_datatype_layout(t); + return layout->size * 8 - layout->flags.unused_bits; +} JL_DLLEXPORT void *jl_symbol_name(jl_sym_t *s); // inline version with strong type check to detect typos in a `->name` chain diff --git a/src/runtime_intrinsics.c b/src/runtime_intrinsics.c index 11b2512e665d2..4a1f30b888c30 100644 --- a/src/runtime_intrinsics.c +++ b/src/runtime_intrinsics.c @@ -410,8 +410,8 @@ JL_DLLEXPORT jl_value_t *jl_bitcast(jl_value_t *ty, jl_value_t *v) jl_error("bitcast: target type not a leaf primitive type"); if (!jl_is_primitivetype(jl_typeof(v))) jl_error("bitcast: value not a primitive type"); - if (jl_datatype_size(jl_typeof(v)) != jl_datatype_size(ty)) - jl_error("bitcast: argument size does not match size of target type"); + if (jl_datatype_nbits((jl_datatype_t*)jl_typeof(v)) != jl_datatype_nbits((jl_datatype_t*)ty)) + jl_error("bitcast: argument bitsize does not match bitsize of target type"); if (ty == jl_typeof(v)) return v; if (ty == (jl_value_t*)jl_bool_type) @@ -700,14 +700,17 @@ JL_DLLEXPORT jl_value_t *jl_cglobal_auto(jl_value_t *v) { return jl_cglobal(v, (jl_value_t*)jl_nothing_type); } -static inline char signbitbyte(void *a, unsigned bytes) JL_NOTSAFEPOINT +static inline char signbitbyte(void *a, unsigned bytes, unsigned nbits) JL_NOTSAFEPOINT { - // sign bit of an signed number of n bytes, as a byte - return (((signed char*)a)[bytes - 1] < 0) ? ~0 : 0; + unsigned signbit = (nbits - 1) % host_char_bit; + return (((unsigned char*)a)[bytes - 1] & (1 << signbit)) ? ~0 : 0; } -static inline char usignbitbyte(void *a, unsigned bytes) JL_NOTSAFEPOINT +static inline char usignbitbyte(void *a, unsigned bytes, unsigned nbits) JL_NOTSAFEPOINT { + (void)a; + (void)bytes; + (void)nbits; // sign bit of an unsigned number return 0; } @@ -727,9 +730,9 @@ static inline unsigned select_by_size(unsigned sz) JL_NOTSAFEPOINT #define SELECTOR_FUNC(intrinsic) \ typedef intrinsic##_t select_##intrinsic##_t[6]; \ - static inline intrinsic##_t select_##intrinsic(unsigned sz, const select_##intrinsic##_t list) JL_NOTSAFEPOINT \ + static inline intrinsic##_t select_##intrinsic(unsigned sz, unsigned runtime_nbits, const select_##intrinsic##_t list) JL_NOTSAFEPOINT \ { \ - intrinsic##_t thunk = list[select_by_size(sz)]; \ + intrinsic##_t thunk = runtime_nbits == sz * host_char_bit ? list[select_by_size(sz)] : NULL; \ if (!thunk) thunk = list[0]; \ return thunk; \ } @@ -983,7 +986,7 @@ uu_iintrinsic(name, u) static inline jl_value_t *jl_iintrinsic_1(jl_value_t *a, const char *name, - char (*getsign)(void*, unsigned), + char (*getsign)(void*, unsigned, unsigned), jl_value_t *(*lambda1)(jl_value_t*, void*, unsigned, unsigned, const void*) JL_CANSAFEPOINT, const void *list) JL_CANSAFEPOINT { @@ -991,7 +994,8 @@ jl_value_t *jl_iintrinsic_1(jl_value_t *a, const char *name, if (!jl_is_primitivetype(ty)) jl_errorf("%s: value is not a primitive type", name); void *pa = jl_data_ptr(a); - unsigned isize = jl_datatype_size(jl_typeof(a)); + unsigned runtime_nbits = jl_datatype_nbits((jl_datatype_t*)ty); + unsigned isize = jl_datatype_size((jl_datatype_t*)ty); unsigned isize2 = next_power_of_two(isize); unsigned osize = jl_datatype_size(ty); unsigned osize2 = next_power_of_two(osize); @@ -1004,7 +1008,7 @@ jl_value_t *jl_iintrinsic_1(jl_value_t *a, const char *name, /* TODO: this memcpy assumes little-endian, * for big-endian, need to align the copy to the other end */ \ memcpy(pa2, pa, isize); - memset((char*)pa2 + isize, getsign(pa, isize), osize2 - isize); + memset((char*)pa2 + isize, getsign(pa, isize, runtime_nbits), osize2 - isize); pa = pa2; } jl_value_t *newv = lambda1(ty, pa, osize, osize2, list); @@ -1015,17 +1019,19 @@ jl_value_t *jl_iintrinsic_1(jl_value_t *a, const char *name, static inline jl_value_t *jl_intrinsiclambda_ty1(jl_value_t *ty, void *pa, unsigned osize, unsigned osize2, const void *voidlist) JL_CANSAFEPOINT { - intrinsic_1_t op = select_intrinsic_1(osize2, (const intrinsic_1_t*)voidlist); + unsigned runtime_nbits = jl_datatype_nbits((jl_datatype_t*)ty); + intrinsic_1_t op = select_intrinsic_1(osize2, runtime_nbits, (const intrinsic_1_t*)voidlist); void *pr = alloca(osize2); - op(osize * host_char_bit, pa, pr); + op(runtime_nbits, pa, pr); return jl_new_bits(ty, pr); } static inline jl_value_t *jl_intrinsiclambda_u1(jl_value_t *ty, void *pa, unsigned osize, unsigned osize2, const void *voidlist) JL_CANSAFEPOINT { jl_task_t *ct = jl_current_task; - intrinsic_u1_t op = select_intrinsic_u1(osize2, (const intrinsic_u1_t*)voidlist); - uint64_t cnt = op(osize * host_char_bit, pa); + unsigned runtime_nbits = jl_datatype_nbits((jl_datatype_t*)ty); + intrinsic_u1_t op = select_intrinsic_u1(osize2, runtime_nbits, (const intrinsic_u1_t*)voidlist); + uint64_t cnt = op(runtime_nbits, pa); // TODO: the following assume little-endian // for big-endian, need to copy from the other end of cnt if (osize <= sizeof(cnt)) { @@ -1191,7 +1197,7 @@ checked_iintrinsic(name, u, jl_intrinsiclambda_checkeddiv) static inline jl_value_t *jl_iintrinsic_2(jl_value_t *a, jl_value_t *b, const char *name, - char (*getsign)(void*, unsigned), + char (*getsign)(void*, unsigned, unsigned), jl_value_t *(*lambda2)(jl_value_t*, void*, void*, unsigned, unsigned, const void*) JL_CANSAFEPOINT, const void *list, int cvtb) JL_CANSAFEPOINT @@ -1207,6 +1213,7 @@ jl_value_t *jl_iintrinsic_2(jl_value_t *a, jl_value_t *b, const char *name, if (!jl_is_primitivetype(ty)) jl_errorf("%s: a is not a primitive type", name); void *pa = jl_data_ptr(a), *pb = jl_data_ptr(b); + unsigned runtime_nbits = jl_datatype_nbits((jl_datatype_t*)ty); unsigned sz = jl_datatype_size(ty); unsigned sz2 = next_power_of_two(sz); unsigned szb = cvtb ? jl_datatype_size(tyb) : sz; @@ -1214,7 +1221,7 @@ jl_value_t *jl_iintrinsic_2(jl_value_t *a, jl_value_t *b, const char *name, /* round type up to the appropriate c-type and set/clear the unused bits */ void *pa2 = alloca(sz2); memcpy(pa2, pa, sz); - memset((char*)pa2 + sz, getsign(pa, sz), sz2 - sz); + memset((char*)pa2 + sz, getsign(pa, sz, runtime_nbits), sz2 - sz); pa = pa2; } if (sz2 > szb) { @@ -1223,7 +1230,7 @@ jl_value_t *jl_iintrinsic_2(jl_value_t *a, jl_value_t *b, const char *name, */ void *pb2 = alloca(sz2); memcpy(pb2, pb, szb); - memset((char*)pb2 + szb, cvtb ? 0 : getsign(pb, szb), sz2 - szb); + memset((char*)pb2 + szb, cvtb ? 0 : getsign(pb, szb, runtime_nbits), sz2 - szb); pb = pb2; } jl_value_t *newv = lambda2(ty, pa, pb, sz, sz2, list); @@ -1233,15 +1240,17 @@ jl_value_t *jl_iintrinsic_2(jl_value_t *a, jl_value_t *b, const char *name, static inline jl_value_t *jl_intrinsiclambda_2(jl_value_t *ty, void *pa, void *pb, unsigned sz, unsigned sz2, const void *voidlist) JL_CANSAFEPOINT { void *pr = alloca(sz2); - intrinsic_2_t op = select_intrinsic_2(sz2, (const intrinsic_2_t*)voidlist); - op(sz * host_char_bit, pa, pb, pr); + unsigned runtime_nbits = jl_datatype_nbits((jl_datatype_t*)ty); + intrinsic_2_t op = select_intrinsic_2(sz2, runtime_nbits, (const intrinsic_2_t*)voidlist); + op(runtime_nbits, pa, pb, pr); return jl_new_bits(ty, pr); } static inline jl_value_t *jl_intrinsiclambda_cmp(jl_value_t *ty, void *pa, void *pb, unsigned sz, unsigned sz2, const void *voidlist) { - intrinsic_cmp_t op = select_intrinsic_cmp(sz2, (const intrinsic_cmp_t*)voidlist); - int cmp = op(sz * host_char_bit, pa, pb); + unsigned runtime_nbits = jl_datatype_nbits((jl_datatype_t*)ty); + intrinsic_cmp_t op = select_intrinsic_cmp(sz2, runtime_nbits, (const intrinsic_cmp_t*)voidlist); + int cmp = op(runtime_nbits, pa, pb); return cmp ? jl_true : jl_false; } @@ -1255,8 +1264,9 @@ static inline jl_value_t *jl_intrinsiclambda_checked(jl_value_t *ty, void *pa, v jl_task_t *ct = jl_current_task; jl_value_t *newv = jl_gc_alloc(ct->ptls, jl_datatype_size(tuptyp), tuptyp); - intrinsic_checked_t op = select_intrinsic_checked(sz2, (const intrinsic_checked_t*)voidlist); - int ovflw = op(sz * host_char_bit, pa, pb, jl_data_ptr(newv)); + unsigned runtime_nbits = jl_datatype_nbits((jl_datatype_t*)ty); + intrinsic_checked_t op = select_intrinsic_checked(sz2, runtime_nbits, (const intrinsic_checked_t*)voidlist); + int ovflw = op(runtime_nbits, pa, pb, jl_data_ptr(newv)); char *ao = (char*)jl_data_ptr(newv) + jl_field_offset(tuptyp, 1); *ao = (char)ovflw; @@ -1265,8 +1275,9 @@ static inline jl_value_t *jl_intrinsiclambda_checked(jl_value_t *ty, void *pa, v static inline jl_value_t *jl_intrinsiclambda_checkeddiv(jl_value_t *ty, void *pa, void *pb, unsigned sz, unsigned sz2, const void *voidlist) JL_CANSAFEPOINT { void *pr = alloca(sz2); - intrinsic_checked_t op = select_intrinsic_checked(sz2, (const intrinsic_checked_t*)voidlist); - int ovflw = op(sz * host_char_bit, pa, pb, pr); + unsigned runtime_nbits = jl_datatype_nbits((jl_datatype_t*)ty); + intrinsic_checked_t op = select_intrinsic_checked(sz2, runtime_nbits, (const intrinsic_checked_t*)voidlist); + int ovflw = op(runtime_nbits, pa, pb, pr); if (ovflw) jl_throw(jl_diverror_exception); return jl_new_bits(ty, pr); diff --git a/test/intfuncs.jl b/test/intfuncs.jl index 771502218f8a2..d1b73006ad631 100644 --- a/test/intfuncs.jl +++ b/test/intfuncs.jl @@ -516,6 +516,13 @@ primitive type BitString128 128 end "0000000000000000000000000000000000000000000000000000010000001011") @test bitstring(Int128(3)) == "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011" @test bitstring(reinterpret(BitString128, Int128(3))) == "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011" + primitive type BitString6 <: Unsigned 6 end + bit6(x::UInt64) = Core.Intrinsics.trunc_int(BitString6, x) + @test bitstring(bit6(UInt64(0x11))) == "010001" + primitive type BitString63 <: Unsigned 63 end + bit63(x::UInt64) = Core.Intrinsics.trunc_int(BitString63, x) + @test bitstring(bit63(0x7fff_ffff_ffff_ffff)) == repeat("1", 63) + @test bitstring(bit63(0x4000_0000_0000_0001)) == "1" * repeat("0", 61) * "1" end @testset "digits/base" begin diff --git a/test/intrinsics.jl b/test/intrinsics.jl index 459c39c621607..1f2f27eaf82fa 100644 --- a/test/intrinsics.jl +++ b/test/intrinsics.jl @@ -4,6 +4,7 @@ # For curmod_* include("testenv.jl") +using InteractiveUtils: code_llvm # bits types @test isa((() -> Core.Intrinsics.bitcast(Ptr{Int8}, 0))(), Ptr{Int8}) @@ -79,6 +80,157 @@ let x, y, f @test string(y) == "$(curmod_prefix)Int24(0x468ace)" end +@testset "non-standard integer widths" begin + primitive type TestUInt24 24 end + primitive type TestUInt40 40 end + primitive type TestUInt48 48 end + primitive type TestUInt5 5 end + primitive type TestUInt17 17 end + primitive type TestUInt63 63 end + primitive type TestInt17 <: Signed 17 end + primitive type TestInt63 <: Signed 63 end + + @test Core.bitsizeof(TestUInt24) == 24 + @test Core.bitsizeof(TestUInt40) == 40 + @test Core.bitsizeof(TestUInt48) == 48 + @test Core.bitsizeof(TestUInt5) == 5 + @test Core.bitsizeof(TestUInt17) == 17 + @test Core.bitsizeof(TestUInt63) == 63 + + x24 = Core.Intrinsics.trunc_int(TestUInt24, UInt64(0xffee_ddcc_bbaa_9988)) + @test Core.Intrinsics.zext_int(UInt64, x24) === 0x0000_0000_00aa_9988 + + x40 = Core.Intrinsics.trunc_int(TestUInt40, UInt64(0xffee_ddcc_bbaa_9988)) + @test Core.Intrinsics.zext_int(UInt64, x40) === 0x0000_00cc_bbaa_9988 + + x48 = Core.Intrinsics.trunc_int(TestUInt48, UInt64(0xffee_ddcc_bbaa_9988)) + @test Core.Intrinsics.zext_int(UInt64, x48) === 0x0000_ddcc_bbaa_9988 + + x5 = Core.Intrinsics.trunc_int(TestUInt5, UInt16(0xffff)) + @test Core.Intrinsics.zext_int(UInt16, x5) === 0x001f + + x17 = Core.Intrinsics.trunc_int(TestUInt17, UInt32(0xffff_ffff)) + @test Core.Intrinsics.zext_int(UInt32, x17) === 0x0001_ffff + + x63 = Core.Intrinsics.trunc_int(TestUInt63, UInt64(0xffff_ffff_ffff_ffff)) + @test Core.Intrinsics.zext_int(UInt64, x63) === 0x7fff_ffff_ffff_ffff + + i17 = Core.Intrinsics.trunc_int(TestInt17, Int32(-1)) + @test Core.Intrinsics.sext_int(Int32, i17) === Int32(-1) + i17min = Core.Intrinsics.trunc_int(TestInt17, Int32(-65536)) + @test Core.Intrinsics.sext_int(Int32, i17min) === Int32(-65536) + + i63 = Core.Intrinsics.trunc_int(TestInt63, Int64(-1)) + @test Core.Intrinsics.sext_int(Int64, i63) === Int64(-1) + + @test Core.Intrinsics.bitcast(TestUInt24, x24) === x24 + @test Core.Intrinsics.bitcast(TestUInt63, i63) === Core.Intrinsics.trunc_int(TestUInt63, typemax(UInt64)) + @test_throws ErrorException Core.Intrinsics.bitcast(TestUInt24, x40) + @test_throws ErrorException Core.Intrinsics.bitcast(TestUInt63, x24) + + function compiled_sitofp(::Type{T}, x) where T + Core.Intrinsics.sitofp(Float64, x)::Float64 + end + function compiled_uitofp(::Type{T}, x) where T + Core.Intrinsics.uitofp(Float64, x)::Float64 + end + function compiled_fptosi(::Type{T}, x) where T + Core.Intrinsics.fptosi(T, x) + end + function compiled_fptoui(::Type{T}, x) where T + Core.Intrinsics.fptoui(T, x) + end + + si17 = Core.Intrinsics.trunc_int(TestInt17, Int32(-12345)) + @test compiled_sitofp(TestInt17, si17) === -12345.0 + @test compiled_fptosi(TestInt17, -12345.0) === si17 + + ui17 = Core.Intrinsics.trunc_int(TestUInt17, UInt32(54321)) + @test compiled_uitofp(TestUInt17, ui17) === 54321.0 + @test compiled_fptoui(TestUInt17, 54321.0) === ui17 + + si63 = Core.Intrinsics.trunc_int(TestInt63, Int64(-(Int64(1) << 52) + 3)) + @test compiled_sitofp(TestInt63, si63) === -4.503599627370493e15 + @test compiled_fptosi(TestInt63, -4.503599627370493e15) === si63 + + ui63 = Core.Intrinsics.trunc_int(TestUInt63, UInt64((UInt64(1) << 52) + 3)) + @test compiled_uitofp(TestUInt63, ui63) === 4.503599627370499e15 + @test compiled_fptoui(TestUInt63, 4.503599627370499e15) === ui63 + + chain40 = Core.Intrinsics.trunc_int(TestUInt40, UInt64(0x0000_00aa_bbcc_ddee)) + chain24 = Core.Intrinsics.trunc_int(TestUInt24, chain40) + chain17 = Core.Intrinsics.trunc_int(TestUInt17, chain24) + @test Core.Intrinsics.zext_int(UInt64, chain24) === 0x0000_0000_00cc_ddee + @test Core.Intrinsics.zext_int(UInt32, chain17) === 0x0000_ddee + @test invokelatest(Core.Intrinsics.zext_int, UInt64, x63) === 0x7fff_ffff_ffff_ffff + + ref63 = Ref(ui63) + @test ref63[] === ui63 + ref63[] = x63 + @test ref63[] === x63 + + struct FieldWrap63 + x::TestUInt63 + y::UInt8 + end + field_wrap = FieldWrap63(ui63, 0x12) + @test field_wrap.x === ui63 + @test field_wrap.y === 0x12 + + struct HashWrap63 + x::TestUInt63 + end + hw1 = Ref(HashWrap63(Core.Intrinsics.trunc_int(TestUInt63, UInt64(1)))) + hw2 = Ref(HashWrap63(Core.Intrinsics.trunc_int(TestUInt63, UInt64(1)))) + GC.@preserve hw1 hw2 begin + p2 = Ptr{UInt8}(Base.unsafe_convert(Ptr{HashWrap63}, hw2)) + unsafe_store!(p2 + 7, unsafe_load(p2 + 7) | 0x80) + end + @test hw1[] === hw2[] + @test hash(hw1[]) == hash(hw2[]) + @test objectid(hw1[]) == objectid(hw2[]) + + primitive type TestBits63 63 end + bits63 = Core.Intrinsics.trunc_int(TestBits63, UInt64(0xffff_ffff_ffff_ffff)) + @test repr(bits63) == "$(curmod_prefix)TestBits63(0x7fffffffffffffff)" + + primitive type TestBits5 5 end + primitive type TestBits6 6 end + primitive type TestBits7 7 end + @test repr(Core.Intrinsics.trunc_int(TestBits5, UInt8(1))) == + "$(curmod_prefix)TestBits5(0x01)" + @test repr(Core.Intrinsics.trunc_int(TestBits6, UInt8(1))) == + "$(curmod_prefix)TestBits6(0x01)" + @test repr(Core.Intrinsics.trunc_int(TestBits7, UInt8(1))) == + "$(curmod_prefix)TestBits7(0x01)" +end + +# odd-bit primitive integers keep byte-rounded storage but logical bit widths +primitive type Int63 <: Signed 63 end +primitive type UInt63 <: Unsigned 63 end +Int63(x::Int64) = Core.Intrinsics.trunc_int(Int63, x) +Int63(x::Signed) = Core.Intrinsics.trunc_int(Int63, Int64(x)) +UInt63(x::UInt64) = Core.Intrinsics.trunc_int(UInt63, x) +Base.Int64(x::Int63) = Core.Intrinsics.sext_int(Int64, x) +Base.UInt64(x::UInt63) = Core.Intrinsics.zext_int(UInt64, x) +let x = UInt63(0xc000_ba98_8765_4321), y = Int63(-1) + @test sizeof(UInt63) == 8 + @test Core.bitsizeof(UInt63) == 63 + @test Core.bitsizeof(x) == 63 + @test Core.bitsizeof(Int64) == 64 + @test Core.bitsizeof(1.0) == 64 + @test UInt64(x) === 0x4000_ba98_8765_4321 + @test Int64(y) === Int64(-1) + @test Int64(Int63(Int32(-1))) === Int64(-1) + # Under Revise`, this `code_llvm` query can fail in InteractiveUtils' + # reflective inference path before it reaches the actual odd-bit lowering. + if !isdefined(Main, :Revise) + id_u63(x::UInt63) = x + ir = sprint(io -> code_llvm(io, id_u63, Tuple{UInt63}; debuginfo=:none)) + @test occursin(r"\bi63\b", ir) + end +end + # test nonsensical valid conversions and errors compiled_addi(x, y) = Core.Intrinsics.add_int(x, y) @@ -110,6 +262,10 @@ end (0x80000000, Int64(0x80000000), -Int64(0x80000000)) @test compiled_conv(UInt32, UInt64(0xC000_BA98_8765_4321)) == (0x87654321, 0x0000000087654321, 0xffffffff87654321) +@test compiled_conv(UInt63, UInt64(0xC000_BA98_8765_4321)) == + (UInt63(0x4000_BA98_8765_4321), 0x4000_BA98_8765_4321, 0xC000_BA98_8765_4321) +@test compiled_conv(Int63, Int64(-1)) == + (Int63(-1), typemax(Int64), -Int64(1)) @test_throws ErrorException compiled_conv(Bool, im) function compiled_fptrunc(::Type{T}, x) where T diff --git a/test/reinterpretarray.jl b/test/reinterpretarray.jl index 4ebb212eb2851..078cecb7f243c 100644 --- a/test/reinterpretarray.jl +++ b/test/reinterpretarray.jl @@ -669,3 +669,62 @@ end @test reinterpret(reshape, UInt8, fill(x)) == [0x67, 0x45, 0x23, 0x01, 0xef, 0xcd, 0xab, 0x00] @test reinterpret(reshape, UInt8, [x]) == [0x67; 0x45; 0x23; 0x01; 0xef; 0xcd; 0xab; 0x00;;] end + +@testset "primitive reinterpret alignment" begin + primitive type RUInt24 24 end + primitive type RUInt40 40 end + primitive type RUInt48 48 end + primitive type RUInt17 17 end + primitive type RUInt23 23 end + primitive type RUInt63 63 end + + @test Base.ispacked(RUInt24) + @test Base.ispacked(RUInt40) + @test Base.ispacked(RUInt48) + @test !Base.datatype_haspadding(RUInt24) + @test !Base.datatype_haspadding(RUInt40) + @test !Base.datatype_haspadding(RUInt48) + @test Base.packedsize(RUInt24) == 3 + @test Base.packedsize(RUInt40) == 5 + @test Base.packedsize(RUInt48) == 6 + @test !Base.ispacked(RUInt17) + @test !Base.ispacked(RUInt23) + @test !Base.ispacked(RUInt63) + @test Base.datatype_haspadding(RUInt17) + @test Base.datatype_haspadding(RUInt23) + @test Base.datatype_haspadding(RUInt63) + + r24 = reinterpret(RUInt24, (0xaa, 0xbb, 0xcc)) + @test reinterpret(NTuple{3, UInt8}, r24) === (0xaa, 0xbb, 0xcc) + + r40 = reinterpret(RUInt40, (0x01, 0x02, 0x03, 0x04, 0x05)) + @test reinterpret(NTuple{5, UInt8}, r40) === (0x01, 0x02, 0x03, 0x04, 0x05) + + r48 = reinterpret(RUInt48, (0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f)) + @test reinterpret(NTuple{6, UInt8}, r48) === (0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f) + + @test_throws ArgumentError Base.padding(RUInt17) + @test_throws ArgumentError Base.packedsize(RUInt17) + @test_throws ArgumentError Base.padding(RUInt23) + @test_throws ArgumentError Base.packedsize(RUInt23) + @test_throws ArgumentError Base.padding(RUInt63) + @test_throws ArgumentError Base.packedsize(RUInt63) + + @test_throws ArgumentError reinterpret(RUInt17, (0x01, 0x02, 0x03)) + @test_throws ArgumentError reinterpret(RUInt23, (0x01, 0x02, 0x03)) + @test_throws ArgumentError reinterpret(RUInt63, ntuple(i -> UInt8(i), 8)) + + struct RHasUInt17 + x::RUInt17 + y::UInt8 + end + struct RHasUnionUInt17 + x::Union{UInt8, RUInt17} + end + @test_throws ArgumentError reinterpret(RHasUInt17, (0x01, 0x02, 0x03, 0x04)) + @test_throws ArgumentError reinterpret(RHasUnionUInt17, (0x01, 0x02, 0x03)) + @test_throws ArgumentError reinterpret( + NTuple{4, UInt8}, + RHasUInt17(Core.Intrinsics.trunc_int(RUInt17, UInt32(1)), 0x02), + ) +end