diff --git a/source/tanya/memory/arch/x86_64.d b/source/tanya/memory/arch/x86_64.d index 26d948e..834c3da 100644 --- a/source/tanya/memory/arch/x86_64.d +++ b/source/tanya/memory/arch/x86_64.d @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* - * Implementions of functions found in $(D_PSYMBOL tanya.memory.op) for X86-64. + * Implementions of functions found in $(D_PSYMBOL tanya.memory.op) for x64. * * Copyright: Eugene Wissner 2017. * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, @@ -51,7 +51,7 @@ pure nothrow @system @nogc asm pure nothrow @nogc { cmp RDX, 0x08; - jc aligned_8; + jc aligned_1; test EDI, 0x07; jz aligned_8; @@ -69,6 +69,7 @@ pure nothrow @system @nogc and EDX, 0x07; jz end; + aligned_1: // Write the remaining bytes. mov RCX, RDX; rep; @@ -340,3 +341,99 @@ pure nothrow @system @nogc ret; } } + +pragma(inline, true) +package (tanya.memory) int cmp(const void[] r1, const void[] r2) +pure nothrow @system @nogc +{ + asm pure nothrow @nogc + { + naked; + + // RDI and RSI should be preserved. + mov R9, RDI; + mov R8, RSI; + } + // Set the registers for cmpsb/cmpsq. + version (Windows) asm pure nothrow @nogc + { + // RDX - r1. + // RCX - r2. + + mov RDI, [ RCX + 8 ]; + mov RSI, [ RDX + 8 ]; + mov RDX, [ RDX ]; + mov RCX, [ RCX ]; + } + else asm pure nothrow @nogc + { + // RDX - r1 length. + // RCX - r1 data. + // RDI - r2 length + // RSI - r2 data. + + mov RSI, RCX; + mov RCX, RDI; + mov RDI, R8; + } + asm pure nothrow @nogc + { + // Compare the lengths. + cmp RDX, RCX; + jl less; + jg greater; + + // Check if we're aligned. + cmp RDX, 0x08; + jc aligned_1; + test EDI, 0x07; + jz aligned_8; + + naligned: + cmpsb; + jl less; + jg greater; + + dec RDX; + test EDI, 0x07; + jnz naligned; + + aligned_8: + mov RCX, RDX; + shr RCX, 0x03; + + repe; + cmpsq; + jl less; + jg greater; + + and EDX, 0x07; + jz equal; + + aligned_1: // Compare the remaining bytes. + mov RCX, RDX; + + repe; + cmpsb; + jl less; + jg greater; + + equal: + xor RAX, RAX; // Return 0. + jmp end; + + greater: + mov RAX, 1; + jmp end; + + less: + mov RAX, -1; + jmp end; + + end: // Restore registers. + mov RSI, R8; + mov RDI, R9; + + ret; + } +} diff --git a/source/tanya/memory/op.d b/source/tanya/memory/op.d index 9b15586..2134355 100644 --- a/source/tanya/memory/op.d +++ b/source/tanya/memory/op.d @@ -92,7 +92,7 @@ pure nothrow @safe @nogc unittest ubyte[9] source = [1, 2, 3, 4, 5, 6, 7, 8, 9]; ubyte[9] target; source.copy(target); - assert(source == target); + assert(cmp(source, target) == 0); } private pure nothrow @safe @nogc unittest @@ -111,7 +111,7 @@ private pure nothrow @safe @nogc unittest ubyte[8] source = [1, 2, 3, 4, 5, 6, 7, 8]; ubyte[8] target; source.copy(target); - assert(source == target); + assert(cmp(source, target) == 0); } } @@ -283,5 +283,120 @@ pure nothrow @safe @nogc unittest ubyte[6] expected = [ 'a', 'a', 'a', 'a', 'b', 'b' ]; copyBackward(mem[0 .. 4], mem[2 .. $]); - assert(expected == mem); + assert(cmp(expected, mem) == 0); +} + +private nothrow @safe @nogc unittest +{ + ubyte[9] r1 = [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i' ]; + ubyte[9] r2; + + copyBackward(r1, r2); + assert(cmp(r1, r2) == 0); +} + +/** + * Compares two memory areas $(D_PARAM r1) and $(D_PARAM r2). + * + * $(D_PSYMBOL cmp) returns a positive integer if + * $(D_INLINECODE r1.length > r2.length) or the first `n` compared bytes of + * $(D_PARAM r1) found to be greater than the first `n` bytes of $(D_PARAM r2), + * + * $(D_PSYMBOL cmp) returns a negative integer if + * $(D_INLINECODE r2.length > r1.length) or the first `n` compared bytes of + * $(D_PARAM r1) found to be less than the first `n` bytes of $(D_PARAM r2), + * + * `0` is returned otherwise. + * + * Returns: Positive integer if $(D_INLINECODE r1 > r2), + * negative integer if $(D_INLINECODE r2 > r1), + * `0` if $(D_INLINECODE r1 == r2). + */ +int cmp(const void[] r1, const void[] r2) pure nothrow @trusted @nogc +{ + version (D_InlineAsm_X86_64) + { + return tanya.memory.arch.x86_64.cmp(r1, r2); + } + else // Naive implementation. + { + if (r1.length > r2.length) + { + return 1; + } + else if (r1.length < r2.length) + { + return -1; + } + auto p1 = cast(const(ubyte)*) r1; + auto p2 = cast(const(ubyte)*) r2; + auto count = r1.length; + + // Check if the pointers are aligned or at least can be aligned + // properly. + if (((cast(size_t) p1) & alignMask) == ((cast(size_t) p2) & alignMask)) + { + // Align the pointers if possible. + for (; ((cast(size_t) p1) & alignMask) != 0; ++p1, ++p2, --count) + { + if (*p1 != *p2) + { + return *p1 - *p2; + } + } + // Compare size_t.sizeof bytes at once. + for (; count >= size_t.sizeof; count -= size_t.sizeof) + { + if (*(cast(const(size_t)*) p1) > *(cast(const(size_t)*) p2)) + { + return 1; + } + else if (*(cast(const(size_t)*) p1) < *(cast(const(size_t)*) p2)) + { + return -1; + } + p1 += size_t.sizeof; + p2 += size_t.sizeof; + } + } + // Compare the remaining bytes by one. + for (; count--; ++p1, ++p2) + { + if (*p1 != *p2) + { + return *p1 - *p2; + } + } + return 0; + } +} + +/// +pure nothrow @safe @nogc unittest +{ + ubyte[4] r1 = [ 'a', 'b', 'c', 'd' ]; + ubyte[3] r2 = [ 'c', 'a', 'b' ]; + + assert(cmp(r1[0 .. 3], r2[]) < 0); + assert(cmp(r2[], r1[0 .. 3]) > 0); + + assert(cmp(r1, r2) > 0); + assert(cmp(r2, r1) < 0); +} + +private pure nothrow @safe @nogc unittest +{ + ubyte[16] r1 = [ + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + ]; + ubyte[16] r2 = [ + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + ]; + + assert(cmp(r1, r2) == 0); + assert(cmp(r1[1 .. $], r2[1 .. $]) == 0); + assert(cmp(r1[0 .. $ - 1], r2[0 .. $ - 1]) == 0); + assert(cmp(r1[0 .. 8], r2[0 .. 8]) == 0); }