diff --git a/src/methods.jl b/src/methods.jl index 0f281db..13868d9 100644 --- a/src/methods.jl +++ b/src/methods.jl @@ -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) diff --git a/test/comparisons.jl b/test/comparisons.jl new file mode 100644 index 0000000..199e73d --- /dev/null +++ b/test/comparisons.jl @@ -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 \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 1327e5b..8a838ab 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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