Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions src/methods.jl
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,27 @@ Base.signed(::Type{T}) where T<:EmulatedUnsigned = _signedness_counterpart_und
Base.Unsigned(x::T) where T<:EmulatedSigned = _signedness_counterpart_undefined(T, :unsigned)
Base.Signed(x::T) where T<:EmulatedUnsigned = _signedness_counterpart_undefined(T, :signed)

# Only `<` and `<=` are defined; Base derives `>`/`>=` from them (`>(x, y) = y < x`). Defining `>`/`>=` here would supersede their universal `Any` fallbacks and invalidate a large amount of precompiled Base code for no behavioral gain.
for f in (:<, :<=)
@eval Base.$f(x::EmulatedInteger, y::EmulatedInteger) = $f(x[], y[])
# Select value-preserving storage representations for efficient comparisons, allowing promotion within the delegated storage comparison. Mixed-sign primitive operands use the larger storage width's signed type when it holds both logical ranges, avoiding a separate sign test. Otherwise, retain the storage values and their comparison methods. Base derives the remaining integer comparisons from `==`, `<`, and `<=`. The emulated/emulated and BigInt methods resolve ambiguities with the mixed methods and Base's BigInt comparisons.
@inline prepromote(x::Integer, y::Integer) = x[], y[]
@inline prepromote(x::Unsigned, y::Signed) = reverse(prepromote(y, x))

@inline function prepromote(x::Signed, y::Unsigned)
left, right = x[], y[]
isprimitivetype(typeof(left)) && isprimitivetype(typeof(right)) || return left, right
storage = sizeof(left) >= sizeof(right) ? typeof(left) : typeof(right)
bits(y) < 8sizeof(storage) || return left, right
target = signed(storage)
return left % target, right % target
end

for OP in (:(==), :<, :<=)
@eval begin
Base.$OP(x::EmulatedInteger, y::Integer) = $OP(prepromote(x, y)...)
Base.$OP(x::Integer, y::EmulatedInteger) = $OP(prepromote(x, y)...)
Base.$OP(x::EmulatedInteger, y::EmulatedInteger) = $OP(prepromote(x, y)...)
Base.$OP(x::EmulatedInteger, y::BigInt) = $OP(prepromote(x, y)...)
Base.$OP(x::BigInt, y::EmulatedInteger) = $OP(prepromote(x, y)...)
end
end

Base.hash(x::EmulatedInteger, h::UInt) = hash(x[], h)
Expand Down
53 changes: 53 additions & 0 deletions test/comparisons.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
@testset "integer comparisons" begin
@emulate UInt1 Int1 UInt9 Int9 UInt9_32 Int9_32 UInt65 Int65 UInt129 Int129

@test Int8(-1) < UInt9(0)
@test Int8(-1) <= UInt9(0)
@test !(Int8(-1) == UInt9(0))
@test !(UInt9(0) < Int8(-1))
@test !(UInt9(0) <= Int8(-1))
@test !(UInt9(0) == Int8(-1))

@testset "comparison storage" begin
for (Left, Right, Storage) in ((Int8, UInt9, Int16), (Int9, UInt8, Int16),
(Int9, UInt9, Int16), (Int8, UInt9_32, Int32),
(Int9_32, UInt16, Int32), (Int65, UInt65, Int128))
for (First, Second) in ((Left, Right), (Right, Left))
operands = @inferred EmulatedBitIntegers.prepromote(typemin(First), typemax(Second))
@test operands isa Tuple{Storage, Storage}
@test operands == (typemin(First)[], typemax(Second)[])
end
end
@test EmulatedBitIntegers.prepromote(Int9(-1), typemax(UInt16)) === (Int16(-1), typemax(UInt16))
@test EmulatedBitIntegers.prepromote(typemax(UInt16), Int9(-1)) === (typemax(UInt16), Int16(-1))
end

@testset "single comparison codegen" begin
for (Left, Right) in ((Int8, UInt9), (Int9, UInt8), (Int9, UInt9),
(Int8, UInt9_32), (Int9_32, UInt16))
for types in ((Left, Right), (Right, Left)), op in (==, <, <=)
ir = sprint(io -> code_llvm(io, op, types; debuginfo=:none))
@test count("icmp", ir) == 1
end
end
end

emulated_types = (UInt1, Int1, UInt9, Int9, UInt9_32, Int9_32, UInt65, Int65, UInt129, Int129)
integer_types = (Bool, Base.BitInteger_types..., BitIntegers.Int256, BitIntegers.UInt256)
operators = (==, !=, <, <=, >, >=, isequal, isless)

Threads.@threads for Emulated in emulated_types
for value in (typemin(Emulated), zero(Emulated), typemax(Emulated))
for Other in (integer_types..., emulated_types...)
for other in (typemin(Other), zero(Other), typemax(Other)), op in operators
@test op(value, other) === op(value[], other[])
@test op(other, value) === op(other[], value[])
end
end
for other in (-big(2)^256, big(-1), big(0), big(1), big(2)^256, BigInt(value[])), op in operators
@test op(value, other) === op(value[], other[])
@test op(other, value) === op(other[], value[])
end
end
end
end
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ values(x) = x |> fieldvalues |> collect
@emulate(UInt1, Int1, UInt3, Int3, Int4, Int20)
@emulate(UInt1_64, UInt3_64, Int4_8, Int4_16, Int7_16, Int20_32)

include("comparisons.jl")

# ============================================================================
# Static analysis
Expand Down