authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2016-05-09 15:07:38-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2016-05-09 15:07:38-07:00
logd92ae20f459c3ac491a0f5ef8aa42e8ac37634c8
tree178cba215ccb255b88bc7b1b487f1836f5f2f196
parentf1e5be96860406d7a4239b174c896799d8fd6545

add hashmap to standard library

closes #22

7 files changed, 115 insertions(+), 55 deletions(-)

CMakeLists.txt+1
...@@ -216,6 +216,7 @@ install(FILES "${CMAKE_SOURCE_DIR}/std/linux_x86_64.zig" DESTINATION "${ZIG_STD_...@@ -216,6 +216,7 @@ install(FILES "${CMAKE_SOURCE_DIR}/std/linux_x86_64.zig" DESTINATION "${ZIG_STD_
216install(FILES "${CMAKE_SOURCE_DIR}/std/linux_i386.zig" DESTINATION "${ZIG_STD_DEST}")216install(FILES "${CMAKE_SOURCE_DIR}/std/linux_i386.zig" DESTINATION "${ZIG_STD_DEST}")
217install(FILES "${CMAKE_SOURCE_DIR}/std/mem.zig" DESTINATION "${ZIG_STD_DEST}")217install(FILES "${CMAKE_SOURCE_DIR}/std/mem.zig" DESTINATION "${ZIG_STD_DEST}")
218install(FILES "${CMAKE_SOURCE_DIR}/std/list.zig" DESTINATION "${ZIG_STD_DEST}")218install(FILES "${CMAKE_SOURCE_DIR}/std/list.zig" DESTINATION "${ZIG_STD_DEST}")
219install(FILES "${CMAKE_SOURCE_DIR}/std/hash_map.zig" DESTINATION "${ZIG_STD_DEST}")
219220
220add_executable(run_tests ${TEST_SOURCES})221add_executable(run_tests ${TEST_SOURCES})
221target_link_libraries(run_tests)222target_link_libraries(run_tests)
src/analyze.cpp+18-5
...@@ -4014,15 +4014,28 @@ static TypeTableEntry *analyze_if(CodeGen *g, ImportTableEntry *import, BlockCon...@@ -4014,15 +4014,28 @@ static TypeTableEntry *analyze_if(CodeGen *g, ImportTableEntry *import, BlockCon
4014 else_context = parent_context;4014 else_context = parent_context;
4015 }4015 }
40164016
4017 TypeTableEntry *then_type = analyze_expression(g, import, then_context, expected_type, *then_node);4017 TypeTableEntry *then_type = nullptr;
4018 TypeTableEntry *else_type = analyze_expression(g, import, else_context, expected_type, *else_node);4018 TypeTableEntry *else_type = nullptr;
40194019
4020 if (then_type->id == TypeTableEntryIdInvalid || else_type->id == TypeTableEntryIdInvalid) {4020 if (!then_context->codegen_excluded) {
4021 return g->builtin_types.entry_invalid;4021 then_type = analyze_expression(g, import, then_context, expected_type, *then_node);
4022 if (then_type->id == TypeTableEntryIdInvalid) {
4023 return g->builtin_types.entry_invalid;
4024 }
4025 }
4026 if (!else_context->codegen_excluded) {
4027 else_type = analyze_expression(g, import, else_context, expected_type, *else_node);
4028 if (else_type->id == TypeTableEntryIdInvalid) {
4029 return g->builtin_types.entry_invalid;
4030 }
4022 }4031 }
40234032
4024 TypeTableEntry *result_type;4033 TypeTableEntry *result_type;
4025 if (expected_type) {4034 if (then_context->codegen_excluded) {
4035 result_type = else_type;
4036 } else if (else_context->codegen_excluded) {
4037 result_type = then_type;
4038 } else if (expected_type) {
4026 result_type = (then_type->id == TypeTableEntryIdUnreachable) ? else_type : then_type;4039 result_type = (then_type->id == TypeTableEntryIdUnreachable) ? else_type : then_type;
4027 } else {4040 } else {
4028 AstNode *op_nodes[] = {*then_node, *else_node};4041 AstNode *op_nodes[] = {*then_node, *else_node};
src/codegen.cpp+42-25
...@@ -2501,6 +2501,33 @@ static void gen_var_debug_decl(CodeGen *g, VariableTableEntry *var) {...@@ -2501,6 +2501,33 @@ static void gen_var_debug_decl(CodeGen *g, VariableTableEntry *var) {
2501 LLVMGetInsertBlock(g->builder));2501 LLVMGetInsertBlock(g->builder));
2502}2502}
25032503
2504static LLVMValueRef gen_if_var_then_block(CodeGen *g, AstNode *node, VariableTableEntry *variable, bool maybe_is_ptr,
2505 LLVMValueRef init_val, TypeTableEntry *child_type, AstNode *then_node)
2506{
2507 if (node->data.if_var_expr.var_is_ptr) {
2508 LLVMValueRef payload_ptr;
2509 if (maybe_is_ptr) {
2510 zig_panic("TODO");
2511 } else {
2512 payload_ptr = LLVMBuildStructGEP(g->builder, init_val, 0, "");
2513 }
2514 LLVMBuildStore(g->builder, payload_ptr, variable->value_ref);
2515 } else {
2516 LLVMValueRef payload_val;
2517 if (maybe_is_ptr) {
2518 payload_val = init_val;
2519 } else {
2520 LLVMValueRef payload_ptr = LLVMBuildStructGEP(g->builder, init_val, 0, "");
2521 payload_val = get_handle_value(g, node, payload_ptr, child_type);
2522 }
2523 gen_assign_raw(g, node, BinOpTypeAssign, variable->value_ref, payload_val,
2524 variable->type, child_type);
2525 }
2526 gen_var_debug_decl(g, variable);
2527
2528 return gen_expr(g, then_node);
2529}
2530
2504static LLVMValueRef gen_if_var_expr(CodeGen *g, AstNode *node) {2531static LLVMValueRef gen_if_var_expr(CodeGen *g, AstNode *node) {
2505 assert(node->type == NodeTypeIfVarExpr);2532 assert(node->type == NodeTypeIfVarExpr);
2506 assert(node->data.if_var_expr.var_decl.expr);2533 assert(node->data.if_var_expr.var_decl.expr);
...@@ -2514,8 +2541,21 @@ static LLVMValueRef gen_if_var_expr(CodeGen *g, AstNode *node) {...@@ -2514,8 +2541,21 @@ static LLVMValueRef gen_if_var_expr(CodeGen *g, AstNode *node) {
25142541
2515 LLVMValueRef init_val = gen_expr(g, var_decl->expr);2542 LLVMValueRef init_val = gen_expr(g, var_decl->expr);
25162543
2517 LLVMValueRef cond_value;2544
2545 AstNode *then_node = node->data.if_var_expr.then_block;
2546 AstNode *else_node = node->data.if_var_expr.else_node;
2518 bool maybe_is_ptr = child_type->id == TypeTableEntryIdPointer || child_type->id == TypeTableEntryIdFn;2547 bool maybe_is_ptr = child_type->id == TypeTableEntryIdPointer || child_type->id == TypeTableEntryIdFn;
2548
2549 ConstExprValue *const_val = &get_resolved_expr(var_decl->expr)->const_val;
2550 if (const_val->ok) {
2551 if (const_val->data.x_maybe) {
2552 return gen_if_var_then_block(g, node, variable, maybe_is_ptr, init_val, child_type, then_node);
2553 } else {
2554 return gen_expr(g, else_node);
2555 }
2556 }
2557
2558 LLVMValueRef cond_value;
2519 if (maybe_is_ptr) {2559 if (maybe_is_ptr) {
2520 set_debug_source_node(g, node);2560 set_debug_source_node(g, node);
2521 cond_value = LLVMBuildICmp(g->builder, LLVMIntNE, init_val, LLVMConstNull(child_type->type_ref), "");2561 cond_value = LLVMBuildICmp(g->builder, LLVMIntNE, init_val, LLVMConstNull(child_type->type_ref), "");
...@@ -2525,9 +2565,6 @@ static LLVMValueRef gen_if_var_expr(CodeGen *g, AstNode *node) {...@@ -2525,9 +2565,6 @@ static LLVMValueRef gen_if_var_expr(CodeGen *g, AstNode *node) {
2525 cond_value = LLVMBuildLoad(g->builder, maybe_field_ptr, "");2565 cond_value = LLVMBuildLoad(g->builder, maybe_field_ptr, "");
2526 }2566 }
25272567
2528 AstNode *then_node = node->data.if_var_expr.then_block;
2529 AstNode *else_node = node->data.if_var_expr.else_node;
2530
2531 TypeTableEntry *then_type = get_expr_type(then_node);2568 TypeTableEntry *then_type = get_expr_type(then_node);
2532 TypeTableEntry *else_type = get_expr_type(else_node);2569 TypeTableEntry *else_type = get_expr_type(else_node);
25332570
...@@ -2548,28 +2585,8 @@ static LLVMValueRef gen_if_var_expr(CodeGen *g, AstNode *node) {...@@ -2548,28 +2585,8 @@ static LLVMValueRef gen_if_var_expr(CodeGen *g, AstNode *node) {
2548 LLVMBuildCondBr(g->builder, cond_value, then_block, else_block);2585 LLVMBuildCondBr(g->builder, cond_value, then_block, else_block);
25492586
2550 LLVMPositionBuilderAtEnd(g->builder, then_block);2587 LLVMPositionBuilderAtEnd(g->builder, then_block);
2551 if (node->data.if_var_expr.var_is_ptr) {2588 LLVMValueRef then_expr_result = gen_if_var_then_block(g, node, variable, maybe_is_ptr, init_val, child_type, then_node);
2552 LLVMValueRef payload_ptr;
2553 if (maybe_is_ptr) {
2554 zig_panic("TODO");
2555 } else {
2556 payload_ptr = LLVMBuildStructGEP(g->builder, init_val, 0, "");
2557 }
2558 LLVMBuildStore(g->builder, payload_ptr, variable->value_ref);
2559 } else {
2560 LLVMValueRef payload_val;
2561 if (maybe_is_ptr) {
2562 payload_val = init_val;
2563 } else {
2564 LLVMValueRef payload_ptr = LLVMBuildStructGEP(g->builder, init_val, 0, "");
2565 payload_val = get_handle_value(g, node, payload_ptr, child_type);
2566 }
2567 gen_assign_raw(g, node, BinOpTypeAssign, variable->value_ref, payload_val,
2568 variable->type, child_type);
2569 }
2570 gen_var_debug_decl(g, variable);
25712589
2572 LLVMValueRef then_expr_result = gen_expr(g, then_node);
2573 if (then_endif_reachable) {2590 if (then_endif_reachable) {
2574 LLVMBuildBr(g->builder, endif_block);2591 LLVMBuildBr(g->builder, endif_block);
2575 }2592 }
std/hash_map.zig+49-21
...@@ -4,17 +4,20 @@ const mem = @import("mem.zig");...@@ -4,17 +4,20 @@ const mem = @import("mem.zig");
4const Allocator = mem.Allocator;4const Allocator = mem.Allocator;
55
6const want_modification_safety = !@compile_var("is_release");6const want_modification_safety = !@compile_var("is_release");
7const debug_u32 = if (want_modification_safety) void else u32;7const debug_u32 = if (want_modification_safety) u32 else void;
88
9pub struct HashMap(K: type, V: type, hash: fn(key: K)->u32, eql: fn(a: K, b: K)->bool) {9pub struct SmallHashMap(K: type, V: type, hash: fn(key: K)->u32, eql: fn(a: K, b: K)->bool, STATIC_SIZE: isize) {
10 entries: []Entry,10 entries: []Entry,
11 size: isize,11 size: isize,
12 max_distance_from_start_index: isize,12 max_distance_from_start_index: isize,
13 allocator: &Allocator,13 allocator: &Allocator,
14 // if the hash map is small enough, we use linear search through these
15 // entries instead of allocating memory
16 prealloc_entries: [STATIC_SIZE]Entry,
14 // this is used to detect bugs where a hashtable is edited while an iterator is running.17 // this is used to detect bugs where a hashtable is edited while an iterator is running.
15 modification_count: debug_u32,18 modification_count: debug_u32,
1619
17 const Self = HashMap(K, V, hash, eql);20 const Self = SmallHashMap(K, V, hash, eql, STATIC_SIZE);
1821
19 pub struct Entry {22 pub struct Entry {
20 used: bool,23 used: bool,
...@@ -49,14 +52,20 @@ pub struct HashMap(K: type, V: type, hash: fn(key: K)->u32, eql: fn(a: K, b: K)-...@@ -49,14 +52,20 @@ pub struct HashMap(K: type, V: type, hash: fn(key: K)->u32, eql: fn(a: K, b: K)-
49 }52 }
50 }53 }
51 54
52 pub fn init(hm: &Self, allocator: &Allocator, capacity: isize) {55 pub fn init(hm: &Self, allocator: &Allocator) {
53 assert(capacity > 0);56 hm.entries = hm.prealloc_entries[0...];
54 hm.allocator = allocator;57 hm.allocator = allocator;
55 hm.init_capacity(capacity);58 hm.size = 0;
59 hm.max_distance_from_start_index = 0;
60 for (hm.entries) |*entry| {
61 entry.used = false;
62 }
56 }63 }
5764
58 pub fn deinit(hm: &Self) {65 pub fn deinit(hm: &Self) {
59 hm.allocator.free(hm.allocator, ([]u8)(hm.entries));66 if (hm.entries.ptr != &hm.prealloc_entries[0]) {
67 hm.allocator.free(hm.allocator, ([]u8)(hm.entries));
68 }
60 }69 }
6170
62 pub fn clear(hm: &Self) {71 pub fn clear(hm: &Self) {
...@@ -68,26 +77,35 @@ pub struct HashMap(K: type, V: type, hash: fn(key: K)->u32, eql: fn(a: K, b: K)-...@@ -68,26 +77,35 @@ pub struct HashMap(K: type, V: type, hash: fn(key: K)->u32, eql: fn(a: K, b: K)-
68 hm.increment_modification_count();77 hm.increment_modification_count();
69 }78 }
7079
71 pub fn put(hm: &Self, key: K, value: V) {80 pub fn put(hm: &Self, key: K, value: V) -> %void {
72 hm.increment_modification_count();81 hm.increment_modification_count();
73 hm.internal_put(key, value);
7482
75 // if we get too full (60%), double the capacity83 const resize = if (hm.entries.ptr == &hm.prealloc_entries[0]) {
76 if (hm.size * 5 >= hm.entries.len * 3) {84 // preallocated entries table is full
85 hm.size == hm.entries.len
86 } else {
87 // if we get too full (60%), double the capacity
88 hm.size * 5 >= hm.entries.len * 3
89 };
90 if (resize) {
77 const old_entries = hm.entries;91 const old_entries = hm.entries;
78 hm.init_capacity(hm.entries.len * 2);92 %return hm.init_capacity(hm.entries.len * 2);
79 // dump all of the old elements into the new table93 // dump all of the old elements into the new table
80 for (old_entries) |*old_entry| {94 for (old_entries) |*old_entry| {
81 if (old_entry.used) {95 if (old_entry.used) {
82 hm.internal_put(old_entry.key, old_entry.value);96 hm.internal_put(old_entry.key, old_entry.value);
83 }97 }
84 }98 }
85 hm.allocator.free(hm.allocator, ([]u8)(old_entries));99 if (old_entries.ptr != &hm.prealloc_entries[0]) {
100 hm.allocator.free(hm.allocator, ([]u8)(old_entries));
101 }
86 }102 }
103
104 hm.internal_put(key, value);
87 }105 }
88106
89 pub fn get(hm: &Self, key: K) {107 pub fn get(hm: &Self, key: K) -> ?&Entry {
90 return internal_get(key);108 return hm.internal_get(key);
91 }109 }
92110
93 pub fn remove(hm: &Self, key: K) {111 pub fn remove(hm: &Self, key: K) {
...@@ -95,7 +113,7 @@ pub struct HashMap(K: type, V: type, hash: fn(key: K)->u32, eql: fn(a: K, b: K)-...@@ -95,7 +113,7 @@ pub struct HashMap(K: type, V: type, hash: fn(key: K)->u32, eql: fn(a: K, b: K)-
95 const start_index = hm.key_to_index(key);113 const start_index = hm.key_to_index(key);
96 {var roll_over: isize = 0; while (roll_over <= hm.max_distance_from_start_index; roll_over += 1) {114 {var roll_over: isize = 0; while (roll_over <= hm.max_distance_from_start_index; roll_over += 1) {
97 const index = (start_index + roll_over) % hm.entries.len;115 const index = (start_index + roll_over) % hm.entries.len;
98 const entry = &hm.entries[index];116 var entry = &hm.entries[index];
99117
100 assert(entry.used); // key not found118 assert(entry.used); // key not found
101119
...@@ -127,7 +145,7 @@ pub struct HashMap(K: type, V: type, hash: fn(key: K)->u32, eql: fn(a: K, b: K)-...@@ -127,7 +145,7 @@ pub struct HashMap(K: type, V: type, hash: fn(key: K)->u32, eql: fn(a: K, b: K)-
127 };145 };
128 }146 }
129147
130 fn init_capacity(hm: &Self, capacity: isize) {148 fn init_capacity(hm: &Self, capacity: isize) -> %void {
131 hm.entries = ([]Entry)(%return hm.allocator.alloc(hm.allocator, capacity * @sizeof(Entry)));149 hm.entries = ([]Entry)(%return hm.allocator.alloc(hm.allocator, capacity * @sizeof(Entry)));
132 hm.size = 0;150 hm.size = 0;
133 hm.max_distance_from_start_index = 0;151 hm.max_distance_from_start_index = 0;
...@@ -145,7 +163,7 @@ pub struct HashMap(K: type, V: type, hash: fn(key: K)->u32, eql: fn(a: K, b: K)-...@@ -145,7 +163,7 @@ pub struct HashMap(K: type, V: type, hash: fn(key: K)->u32, eql: fn(a: K, b: K)-
145 fn internal_put(hm: &Self, orig_key: K, orig_value: V) {163 fn internal_put(hm: &Self, orig_key: K, orig_value: V) {
146 var key = orig_key;164 var key = orig_key;
147 var value = orig_value;165 var value = orig_value;
148 const start_index = key_to_index(key);166 const start_index = hm.key_to_index(key);
149 var roll_over: isize = 0;167 var roll_over: isize = 0;
150 var distance_from_start_index: isize = 0;168 var distance_from_start_index: isize = 0;
151 while (roll_over < hm.entries.len; {roll_over += 1; distance_from_start_index += 1}) {169 while (roll_over < hm.entries.len; {roll_over += 1; distance_from_start_index += 1}) {
...@@ -190,7 +208,7 @@ pub struct HashMap(K: type, V: type, hash: fn(key: K)->u32, eql: fn(a: K, b: K)-...@@ -190,7 +208,7 @@ pub struct HashMap(K: type, V: type, hash: fn(key: K)->u32, eql: fn(a: K, b: K)-
190 }208 }
191209
192 fn internal_get(hm: &Self, key: K) -> ?&Entry {210 fn internal_get(hm: &Self, key: K) -> ?&Entry {
193 const start_index = key_to_index(key);211 const start_index = hm.key_to_index(key);
194 {var roll_over: isize = 0; while (roll_over <= hm.max_distance_from_start_index; roll_over += 1) {212 {var roll_over: isize = 0; while (roll_over <= hm.max_distance_from_start_index; roll_over += 1) {
195 const index = (start_index + roll_over) % hm.entries.len;213 const index = (start_index + roll_over) % hm.entries.len;
196 const entry = &hm.entries[index];214 const entry = &hm.entries[index];
...@@ -233,9 +251,19 @@ fn global_free(self: &Allocator, old_mem: []u8) {...@@ -233,9 +251,19 @@ fn global_free(self: &Allocator, old_mem: []u8) {
233251
234#attribute("test")252#attribute("test")
235fn basic_hash_map_test() {253fn basic_hash_map_test() {
236 var map: HashMap(i32, i32, hash_i32, eql_i32) = undefined;254 var map: SmallHashMap(i32, i32, hash_i32, eql_i32, 4) = undefined;
237 map.init(&global_allocator, 4);255 map.init(&global_allocator);
238 defer map.deinit();256 defer map.deinit();
257
258 %%map.put(1, 11);
259 %%map.put(2, 22);
260 %%map.put(3, 33);
261 %%map.put(4, 44);
262 %%map.put(5, 55);
263
264 assert((??map.get(2)).value == 22);
265 map.remove(2);
266 assert(if (const entry ?= map.get(2)) false else true);
239}267}
240268
241fn hash_i32(x: i32) -> u32 {269fn hash_i32(x: i32) -> u32 {
std/index.zig+1
...@@ -6,6 +6,7 @@ pub const str = @import("str.zig");...@@ -6,6 +6,7 @@ pub const str = @import("str.zig");
6pub const cstr = @import("cstr.zig");6pub const cstr = @import("cstr.zig");
7pub const net = @import("net.zig");7pub const net = @import("net.zig");
8pub const list = @import("list.zig");8pub const list = @import("list.zig");
9pub const hash_map = @import("hash_map.zig");
9pub const mem = @import("mem.zig");10pub const mem = @import("mem.zig");
1011
11pub fn assert(b: bool) {12pub fn assert(b: bool) {
std/list.zig+1-1
...@@ -21,7 +21,7 @@ pub struct SmallList(T: type, STATIC_SIZE: isize) {...@@ -21,7 +21,7 @@ pub struct SmallList(T: type, STATIC_SIZE: isize) {
21 }21 }
2222
23 pub fn deinit(l: &SmallList(T, STATIC_SIZE)) {23 pub fn deinit(l: &SmallList(T, STATIC_SIZE)) {
24 if (l.items.ptr == &l.prealloc_items[0]) {24 if (l.items.ptr != &l.prealloc_items[0]) {
25 l.allocator.free(l.allocator, ([]u8)(l.items));25 l.allocator.free(l.allocator, ([]u8)(l.items));
26 }26 }
27 }27 }
test/run_tests.cpp+3-3
...@@ -831,9 +831,9 @@ fn f() {...@@ -831,9 +831,9 @@ fn f() {
831831
832832
833 add_compile_fail_case("missing else clause", R"SOURCE(833 add_compile_fail_case("missing else clause", R"SOURCE(
834fn f() {834fn f(b: bool) {
835 const x : i32 = if (true) { 1 };835 const x : i32 = if (b) { 1 };
836 const y = if (true) { i32(1) };836 const y = if (b) { i32(1) };
837}837}
838 )SOURCE", 2, ".tmp_source.zig:3:21: error: expected type 'i32', got 'void'",838 )SOURCE", 2, ".tmp_source.zig:3:21: error: expected type 'i32', got 'void'",
839 ".tmp_source.zig:4:15: error: incompatible types: 'i32' and 'void'");839 ".tmp_source.zig:4:15: error: incompatible types: 'i32' and 'void'");