From 6ef07d2d3a5f73b05749539be9adb257dbe5d01a Mon Sep 17 00:00:00 2001 From: Marc Auberer Date: Sat, 15 Feb 2025 22:50:39 +0100 Subject: [PATCH] Avoid copy of key for hash table bucket lookup (#783) --- src/typechecker/TypeCheckerExpressions.cpp | 7 +- std/data/hash-table.spice | 3 +- std/math/hash.spice | 71 +++++++++++++++---- .../cout.out | 0 .../unordered-map-complex-key/source.spice | 38 ++++++++++ .../data/unordered-map-complex-value/cout.out | 1 + .../source.spice | 0 test/test-files/std/math/hash-basic/cout.out | 12 ++-- .../std/math/hash-basic/source.spice | 29 +++++--- 9 files changed, 128 insertions(+), 33 deletions(-) rename test/test-files/std/data/{unordered-map-normal-usecase => unordered-map-complex-key}/cout.out (100%) create mode 100644 test/test-files/std/data/unordered-map-complex-key/source.spice create mode 100644 test/test-files/std/data/unordered-map-complex-value/cout.out rename test/test-files/std/data/{unordered-map-normal-usecase => unordered-map-complex-value}/source.spice (100%) diff --git a/src/typechecker/TypeCheckerExpressions.cpp b/src/typechecker/TypeCheckerExpressions.cpp index 8ab20d6b1..700c18b01 100644 --- a/src/typechecker/TypeCheckerExpressions.cpp +++ b/src/typechecker/TypeCheckerExpressions.cpp @@ -490,9 +490,6 @@ std::any TypeChecker::visitPostfixUnaryExpr(PostfixUnaryExprNode *node) { AssignExprNode *indexAssignExpr = node->subscriptIndexExpr; const auto index = std::any_cast(visit(indexAssignExpr)); HANDLE_UNRESOLVED_TYPE_ER(index.type) - // Check if the index is of the right type - if (!index.type.isOneOf({TY_INT, TY_LONG})) - SOFT_ERROR_ER(node, ARRAY_INDEX_NOT_INT_OR_LONG, "Array index must be of type int or long") // Check is there is an overloaded operator function available, if yes accept it const auto [type, _] = opRuleManager.isOperatorOverloadingFctAvailable<2>(node, OP_FCT_SUBSCRIPT, {operand, index}, 0); @@ -503,6 +500,10 @@ std::any TypeChecker::visitPostfixUnaryExpr(PostfixUnaryExprNode *node) { operandType = operandType.removeReferenceWrapper(); + // Check if the index is of the right type + if (!index.type.isOneOf({TY_INT, TY_LONG})) + SOFT_ERROR_ER(node, ARRAY_INDEX_NOT_INT_OR_LONG, "Array index must be of type int or long") + // Check if we can apply the subscript operator on the lhs type if (!operandType.isOneOf({TY_ARRAY, TY_PTR, TY_STRING})) SOFT_ERROR_ER(node, OPERATOR_WRONG_DATA_TYPE, diff --git a/std/data/hash-table.spice b/std/data/hash-table.spice index 1301de0df..cc2725c56 100644 --- a/std/data/hash-table.spice +++ b/std/data/hash-table.spice @@ -146,8 +146,7 @@ inline f>&> HashTable.getBucket(const K& key) { } inline f HashTable.hash(const K& key) { - const K keyCopy = key; // ToDo: Avoid copy of key - return hash(keyCopy) % this.buckets.getSize(); + return hash(key) % this.buckets.getSize(); } /** diff --git a/std/math/hash.spice b/std/math/hash.spice index 0518347e8..257560896 100644 --- a/std/math/hash.spice +++ b/std/math/hash.spice @@ -3,34 +3,77 @@ type TT int|long|short|bool; // Trivially hashable types type TC byte|char; // Trivially hashable types with one additional cast type T dyn; // Any type +// Aliases +public type Hash alias unsigned long; + public type IHashable interface { - public f hash(const T&); + public f hash(); } -public f hash(TT input) { - return cast(input); +/** + * Hash primitive numeric type + * + * @param input Primitieve numeric to hash + * @return Hash of primitive numeric + */ +public f hash(TT input) { + return cast(input); } -public f hash(TC input) { - return cast(input); +/** + * Hash primitive numeric type + * + * @param input Primitieve numeric to hash + * @return Hash of primitive numeric + */ +public f hash(TC input) { + return cast(cast(input)); } -public f hash(double input) { +/** + * Hash a double value + * + * @param input Double to hash + * @return Hash of double + */ +public f hash(double input) { unsafe { - return *(cast(&input)); + return *(cast(&input)); } } -// djb2 hash -public f hash(string input) { - unsigned long hash = 5381l; +/** + * Hash contents of an immutable string + * + * @param input String to hash + * @return Hash of string + */ +public f hash(string input) { + // Hash using djb2 algorithm + Hash hash = 5381l; for unsigned long i = 0l; i < len(input); i++ { - unsigned long c = cast(cast(input[i])); + Hash c = cast(cast(input[i])); hash = ((hash << 5l) + hash) + c; // hash * 33 + c } return hash; } -/*public f hash(const T& input) { - return input.hash(); -}*/ \ No newline at end of file +/** + * Hash contents of a mutable String object + * + * @param input String to hash + * @return Hash of string + */ +public f hash(const String& input) { + return hash(input.getRaw()); +} + +/** + * Dispatch function for structs, that implement the IHashable interface + * + * @param hashable Hashable object + * @return Hash of the struct + */ +public f hash(const IHashable& hashable) { + return hashable.hash(); +} diff --git a/test/test-files/std/data/unordered-map-normal-usecase/cout.out b/test/test-files/std/data/unordered-map-complex-key/cout.out similarity index 100% rename from test/test-files/std/data/unordered-map-normal-usecase/cout.out rename to test/test-files/std/data/unordered-map-complex-key/cout.out diff --git a/test/test-files/std/data/unordered-map-complex-key/source.spice b/test/test-files/std/data/unordered-map-complex-key/source.spice new file mode 100644 index 000000000..efaea27b5 --- /dev/null +++ b/test/test-files/std/data/unordered-map-complex-key/source.spice @@ -0,0 +1,38 @@ +import "std/data/unordered-map"; + +f main() { + UnorderedMap map = UnorderedMap(); + assert map.getSize() == 0; + assert map.isEmpty(); + map.upsert(String("one"), 1); + map.upsert(String("two"), 2); + map.upsert(String("three"), 3); + map.upsert(String("four"), 4); + assert map.getSize() == 4; + assert !map.isEmpty(); + assert map.contains(String("one")); + assert map.contains(String("two")); + assert map.contains(String("three")); + assert map.contains(String("four")); + assert !map.contains(String("five")); + assert map.get(String("one")) == 1; + assert map[String("two")] == 2; + assert map.get(String("three")) == 3; + assert map.get(String("four")) == 4; + const Result item5 = map.getSafe(String("five")); + assert item5.isErr(); + map.remove(String("two")); + assert !map.contains(String("two")); + assert map.getSize() == 3; + map.clear(); + assert map.getSize() == 0; + assert map.isEmpty(); + map.upsert(String("one"), 1); + map.upsert(String("one"), 11); + assert map.getSize() == 1; + assert map[String("one")] == 11; + map.remove(String("one")); + assert map.isEmpty(); + + printf("All assertions passed!\n"); +} \ No newline at end of file diff --git a/test/test-files/std/data/unordered-map-complex-value/cout.out b/test/test-files/std/data/unordered-map-complex-value/cout.out new file mode 100644 index 000000000..d8347e3cc --- /dev/null +++ b/test/test-files/std/data/unordered-map-complex-value/cout.out @@ -0,0 +1 @@ +All assertions passed! diff --git a/test/test-files/std/data/unordered-map-normal-usecase/source.spice b/test/test-files/std/data/unordered-map-complex-value/source.spice similarity index 100% rename from test/test-files/std/data/unordered-map-normal-usecase/source.spice rename to test/test-files/std/data/unordered-map-complex-value/source.spice diff --git a/test/test-files/std/math/hash-basic/cout.out b/test/test-files/std/math/hash-basic/cout.out index 516121888..b70751f46 100644 --- a/test/test-files/std/math/hash-basic/cout.out +++ b/test/test-files/std/math/hash-basic/cout.out @@ -1,7 +1,9 @@ +Hash (double): 4638355772470722560 Hash (int): 123 -Hash (long): 123 Hash (short): 123 -Hash (char): 0 -Hash (byte): 0 -Hash (string): -1763540338 -Hash (double): 0 +Hash (long): 123 +Hash (char): 123 +Hash (byte): 123 +Hash (string): 5904905660241445518 +Hash (String): 5904905660241445518 +Hash (HashableType): 124 diff --git a/test/test-files/std/math/hash-basic/source.spice b/test/test-files/std/math/hash-basic/source.spice index 006c07998..0bd9e4085 100644 --- a/test/test-files/std/math/hash-basic/source.spice +++ b/test/test-files/std/math/hash-basic/source.spice @@ -1,13 +1,24 @@ import "std/math/hash"; +type HashableType struct : IHashable { + unsigned long content = 124l +} + +f HashableType.hash() { + return this.content; +} + f main() { - // Trivial hashes - printf("Hash (int): %d\n", hash(123)); - printf("Hash (long): %d\n", hash(123l)); - printf("Hash (short): %d\n", hash(123s)); - printf("Hash (char): %d\n", hash(123.0)); - printf("Hash (byte): %d\n", hash(123.0)); - printf("Hash (string): %d\n", hash("Hello, World!")); - // Complex hashes - printf("Hash (double): %d\n", hash(123.0)); + // Commonly used hash functions + printf("Hash (double): %lu\n", hash(123.0)); + printf("Hash (int): %lu\n", hash(123)); + printf("Hash (short): %lu\n", hash(123s)); + printf("Hash (long): %lu\n", hash(123l)); + printf("Hash (char): %lu\n", hash(cast(123))); + printf("Hash (byte): %lu\n", hash(cast(123))); + printf("Hash (string): %lu\n", hash("Hello, World!")); + printf("Hash (String): %lu\n", hash(String("Hello, World!"))); + // Custom hash implementation + const HashableType ht; + printf("Hash (HashableType): %lu\n", hash(ht)); } \ No newline at end of file