From 489e8dc59272cd0db755da53a2c1de362e4175b3 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 13 Nov 2022 23:47:00 +0000 Subject: [PATCH] Add @(require_results) to map procedures where possible --- core/runtime/dynamic_map_internal.odin | 50 ++++++++++++++++++-------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/core/runtime/dynamic_map_internal.odin b/core/runtime/dynamic_map_internal.odin index ac09c3e2b..0ecd0b73b 100644 --- a/core/runtime/dynamic_map_internal.odin +++ b/core/runtime/dynamic_map_internal.odin @@ -107,6 +107,7 @@ Map_Cell_Info :: struct { map_cell_info :: intrinsics.type_map_cell_info // Same as the above procedure but at runtime with the cell Map_Cell_Info value. +@(require_results) map_cell_index_dynamic :: #force_inline proc "contextless" (base: uintptr, #no_alias info: ^Map_Cell_Info, index: uintptr) -> uintptr { // Micro-optimize the common cases to save on integer division. elements_per_cell := uintptr(info.elements_per_cell) @@ -128,6 +129,7 @@ map_cell_index_dynamic :: #force_inline proc "contextless" (base: uintptr, #no_a } // Same as above procedure but with compile-time constant index. +@(require_results) map_cell_index_dynamic_const :: proc "contextless" (base: uintptr, #no_alias info: ^Map_Cell_Info, $INDEX: uintptr) -> uintptr { elements_per_cell := uintptr(info.elements_per_cell) size_of_cell := uintptr(info.size_of_cell) @@ -143,6 +145,7 @@ map_cell_index_dynamic_const :: proc "contextless" (base: uintptr, #no_alias inf // The following compile-time procedure indexes such a [N]Cell(T) structure as // if it were a flat array accounting for the internal padding introduced by the // Cell structure. +@(require_results) map_cell_index_static :: #force_inline proc "contextless" (cells: [^]Map_Cell($T), index: uintptr) -> ^T #no_bounds_check { N :: size_of(Map_Cell(T){}.data) / size_of(T) when size_of(T) > 0 else 1 @@ -179,11 +182,13 @@ map_cell_index_static :: #force_inline proc "contextless" (cells: [^]Map_Cell($T } // len() for map +@(require_results) map_len :: #force_inline proc "contextless" (m: Raw_Map) -> int { return m.len } // cap() for map +@(require_results) map_cap :: #force_inline proc "contextless" (m: Raw_Map) -> int { // The data uintptr stores the capacity in the lower six bits which gives the // a maximum value of 2^6-1, or 63. We store the integer log2 of capacity @@ -196,10 +201,12 @@ map_cap :: #force_inline proc "contextless" (m: Raw_Map) -> int { // some math is needed to compute it. Compute it as a fixed point percentage to // avoid floating point operations. This division can be optimized out by // multiplying by the multiplicative inverse of 100. +@(require_results) map_load_factor :: #force_inline proc "contextless" (log2_capacity: uintptr) -> uintptr { return ((uintptr(1) << log2_capacity) * MAP_LOAD_FACTOR) / 100 } +@(require_results) map_resize_threshold :: #force_inline proc "contextless" (m: Raw_Map) -> int { return int(map_load_factor(map_log2_cap(m))) } @@ -208,12 +215,14 @@ map_resize_threshold :: #force_inline proc "contextless" (m: Raw_Map) -> int { // used in the implementation rather than map_cap since the check for data = 0 // isn't necessary in the implementation. cap() on the otherhand needs to work // when called on an empty map. +@(require_results) map_log2_cap :: #force_inline proc "contextless" (m: Raw_Map) -> uintptr { return m.data & (64 - 1) } // Canonicalize the data by removing the tagged capacity stored in the lower six // bits of the data uintptr. +@(require_results) map_data :: #force_inline proc "contextless" (m: Raw_Map) -> uintptr { return m.data &~ uintptr(64 - 1) } @@ -224,15 +233,18 @@ Map_Hash :: uintptr // Procedure to check if a slot is empty for a given hash. This is represented // by the zero value to make the zero value useful. This is a procedure just // for prose reasons. +@(require_results) map_hash_is_empty :: #force_inline proc "contextless" (hash: Map_Hash) -> bool { return hash == 0 } +@(require_results) map_hash_is_deleted :: #force_no_inline proc "contextless" (hash: Map_Hash) -> bool { // The MSB indicates a tombstone N :: size_of(Map_Hash)*8 - 1 return hash >> N != 0 } +@(require_results) map_hash_is_valid :: #force_inline proc "contextless" (hash: Map_Hash) -> bool { // The MSB indicates a tombstone N :: size_of(Map_Hash)*8 - 1 @@ -242,12 +254,14 @@ map_hash_is_valid :: #force_inline proc "contextless" (hash: Map_Hash) -> bool { // Computes the desired position in the array. This is just index % capacity, // but a procedure as there's some math involved here to recover the capacity. +@(require_results) map_desired_position :: #force_inline proc "contextless" (m: Raw_Map, hash: Map_Hash) -> uintptr { // We do not use map_cap since we know the capacity will not be zero here. capacity := uintptr(1) << map_log2_cap(m) return uintptr(hash & Map_Hash(capacity - 1)) } +@(require_results) map_probe_distance :: #force_inline proc "contextless" (m: Raw_Map, hash: Map_Hash, slot: uintptr) -> uintptr { // We do not use map_cap since we know the capacity will not be zero here. capacity := uintptr(1) << map_log2_cap(m) @@ -275,6 +289,7 @@ Map_Info :: struct { // map_info :: proc "contextless" ($T: typeid/map[$K]$V) -> ^Map_Info {...} map_info :: intrinsics.type_map_info +@(require_results) map_kvh_data_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info) -> (ks: uintptr, vs: uintptr, hs: [^]Map_Hash, sk: uintptr, sv: uintptr) { INFO_HS := intrinsics.type_map_cell_info(Map_Hash) @@ -291,13 +306,14 @@ map_kvh_data_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Inf return } +@(require_results) map_kvh_data_values_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info) -> (vs: uintptr) { capacity := uintptr(1) << map_log2_cap(m) return map_cell_index_dynamic(map_data(m), info.ks, capacity) // Skip past ks to get start of vs } -@(private) +@(private, require_results) map_total_allocation_size :: #force_inline proc "contextless" (capacity: uintptr, info: ^Map_Info) -> uintptr { round :: #force_inline proc "contextless" (value: uintptr) -> uintptr { CACHE_MASK :: MAP_CACHE_LINE_SIZE - 1 @@ -315,6 +331,7 @@ map_total_allocation_size :: #force_inline proc "contextless" (capacity: uintptr } // The only procedure which needs access to the context is the one which allocates the map. +@(require_results) map_alloc_dynamic :: proc "odin" (info: ^Map_Info, log2_capacity: uintptr, allocator := context.allocator, loc := #caller_location) -> (result: Raw_Map, err: Allocator_Error) { result.allocator = allocator // set the allocator always if log2_capacity == 0 { @@ -355,6 +372,7 @@ map_alloc_dynamic :: proc "odin" (info: ^Map_Info, log2_capacity: uintptr, alloc // there is no type information. // // This procedure returns the address of the just inserted value. +@(require_results) map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, h: Map_Hash, ik: uintptr, iv: uintptr) -> (result: uintptr) { h := h pos := map_desired_position(m^, h) @@ -429,6 +447,7 @@ map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^ } } +@(require_results) map_grow_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> Allocator_Error { log2_capacity := map_log2_cap(m^) new_capacity := uintptr(1) << max(log2_capacity + 1, MAP_MIN_LOG2_CAPACITY) @@ -436,7 +455,9 @@ map_grow_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Inf } +@(require_results) map_reserve_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, new_capacity: uintptr, loc := #caller_location) -> Allocator_Error { + @(require_results) ceil_log2 :: #force_inline proc "contextless" (x: uintptr) -> uintptr { z := intrinsics.count_leading_zeros(x) if z > 0 && x & (x-1) != 0 { @@ -482,7 +503,7 @@ map_reserve_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_ } k := map_cell_index_dynamic(ks, info.ks, i) v := map_cell_index_dynamic(vs, info.vs, i) - map_insert_hash_dynamic(&resized, info, hash, k, v) + _ = map_insert_hash_dynamic(&resized, info, hash, k, v) // Only need to do this comparison on each actually added pair, so do not // fold it into the for loop comparator as a micro-optimization. n -= 1 @@ -499,6 +520,7 @@ map_reserve_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_ } +@(require_results) map_shrink_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> Allocator_Error { if m.allocator.procedure == nil { m.allocator = context.allocator @@ -530,9 +552,7 @@ map_shrink_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_I k := map_cell_index_dynamic(ks, info.ks, i) v := map_cell_index_dynamic(vs, info.vs, i) - - map_insert_hash_dynamic(&shrunk, info, hash, k, v) - + _ = map_insert_hash_dynamic(&shrunk, info, hash, k, v) // Only need to do this comparison on each actually added pair, so do not // fold it into the for loop comparator as a micro-optimization. n -= 1 @@ -555,6 +575,7 @@ map_free_dynamic :: proc "odin" (m: Raw_Map, info: ^Map_Info, loc := #caller_loc return mem_free_with_size(ptr, size, m.allocator, loc) } +@(require_results) map_lookup_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (index: uintptr, ok: bool) { if map_len(m) == 0 { return 0, false @@ -577,6 +598,7 @@ map_lookup_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, d += 1 } } +@(require_results) map_exists_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (ok: bool) { if map_len(m) == 0 { return false @@ -626,16 +648,17 @@ map_clear_dynamic :: #force_inline proc "contextless" (#no_alias m: ^Raw_Map, #n } -map_kvh_data_static :: #force_inline proc "contextless" (m: $T/map[$K]$V) -> ([^]Map_Cell(K), [^]Map_Cell(V), [^]Map_Hash) { - H :: Map_Hash +@(require_results) +map_kvh_data_static :: #force_inline proc "contextless" (m: $T/map[$K]$V) -> (ks: [^]Map_Cell(K), vs: [^]Map_Cell(V), hs: [^]Map_Hash) { capacity := uintptr(cap(m)) - ks := ([^]Map_Cell(K))(map_data(transmute(Raw_Map)m)) - vs := ([^]Map_Cell(V))(map_cell_index_static(ks, capacity)) - hs := ([^]Map_Cell(H))(map_cell_index_static(vs, capacity)) - return ks, vs, ([^]Map_Hash)(hs) + ks = ([^]Map_Cell(K))(map_data(transmute(Raw_Map)m)) + vs = ([^]Map_Cell(V))(map_cell_index_static(ks, capacity)) + hs = ([^]Map_Hash)(map_cell_index_static(vs, capacity)) + return } +@(require_results) map_get :: proc "contextless" (m: $T/map[$K]$V, key: K) -> (stored_key: K, stored_value: V, ok: bool) { rm := transmute(Raw_Map)m if rm.len == 0 { @@ -719,15 +742,14 @@ __dynamic_map_set :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_In } result := map_insert_hash_dynamic(m, info, hash, uintptr(key), uintptr(value)) - assert(result != 0) m.len += 1 return rawptr(result) } // IMPORTANT: USED WITHIN THE COMPILER @(private) -__dynamic_map_reserve :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, new_capacity: uint, loc := #caller_location) { - map_reserve_dynamic(m, info, uintptr(new_capacity), loc) +__dynamic_map_reserve :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, new_capacity: uint, loc := #caller_location) -> Allocator_Error { + return map_reserve_dynamic(m, info, uintptr(new_capacity), loc) }