| author | |
| committer | |
| log | d92ae20f459c3ac491a0f5ef8aa42e8ac37634c8 |
| tree | 178cba215ccb255b88bc7b1b487f1836f5f2f196 |
| parent | f1e5be96860406d7a4239b174c896799d8fd6545 |
closes #227 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 | 216 | install(FILES "${CMAKE_SOURCE_DIR}/std/linux_i386.zig" DESTINATION "${ZIG_STD_DEST}") |
| 217 | 217 | install(FILES "${CMAKE_SOURCE_DIR}/std/mem.zig" DESTINATION "${ZIG_STD_DEST}") |
| 218 | 218 | install(FILES "${CMAKE_SOURCE_DIR}/std/list.zig" DESTINATION "${ZIG_STD_DEST}") |
| 219 | install(FILES "${CMAKE_SOURCE_DIR}/std/hash_map.zig" DESTINATION "${ZIG_STD_DEST}") | |
| 219 | 220 | |
| 220 | 221 | add_executable(run_tests ${TEST_SOURCES}) |
| 221 | 222 | target_link_libraries(run_tests) |
src/analyze.cpp+18-5| ... | ... | @@ -4014,15 +4014,28 @@ static TypeTableEntry *analyze_if(CodeGen *g, ImportTableEntry *import, BlockCon |
| 4014 | 4014 | else_context = parent_context; |
| 4015 | 4015 | } |
| 4016 | 4016 | |
| 4017 | TypeTableEntry *then_type = analyze_expression(g, import, then_context, expected_type, *then_node); | |
| 4018 | TypeTableEntry *else_type = analyze_expression(g, import, else_context, expected_type, *else_node); | |
| 4017 | TypeTableEntry *then_type = nullptr; | |
| 4018 | TypeTableEntry *else_type = nullptr; | |
| 4019 | 4019 | |
| 4020 | if (then_type->id == TypeTableEntryIdInvalid || else_type->id == TypeTableEntryIdInvalid) { | |
| 4021 | return g->builtin_types.entry_invalid; | |
| 4020 | if (!then_context->codegen_excluded) { | |
| 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 | } |
| 4023 | 4032 | |
| 4024 | 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 | 4039 | result_type = (then_type->id == TypeTableEntryIdUnreachable) ? else_type : then_type; |
| 4027 | 4040 | } else { |
| 4028 | 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 | 2501 | LLVMGetInsertBlock(g->builder)); |
| 2502 | 2502 | } |
| 2503 | 2503 | |
| 2504 | static 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 | ||
| 2504 | 2531 | static LLVMValueRef gen_if_var_expr(CodeGen *g, AstNode *node) { |
| 2505 | 2532 | assert(node->type == NodeTypeIfVarExpr); |
| 2506 | 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 | 2541 | |
| 2515 | 2542 | LLVMValueRef init_val = gen_expr(g, var_decl->expr); |
| 2516 | 2543 | |
| 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 | 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 | 2559 | if (maybe_is_ptr) { |
| 2520 | 2560 | set_debug_source_node(g, node); |
| 2521 | 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 | 2565 | cond_value = LLVMBuildLoad(g->builder, maybe_field_ptr, ""); |
| 2526 | 2566 | } |
| 2527 | 2567 | |
| 2528 | AstNode *then_node = node->data.if_var_expr.then_block; | |
| 2529 | AstNode *else_node = node->data.if_var_expr.else_node; | |
| 2530 | ||
| 2531 | 2568 | TypeTableEntry *then_type = get_expr_type(then_node); |
| 2532 | 2569 | TypeTableEntry *else_type = get_expr_type(else_node); |
| 2533 | 2570 | |
| ... | ... | @@ -2548,28 +2585,8 @@ static LLVMValueRef gen_if_var_expr(CodeGen *g, AstNode *node) { |
| 2548 | 2585 | LLVMBuildCondBr(g->builder, cond_value, then_block, else_block); |
| 2549 | 2586 | |
| 2550 | 2587 | LLVMPositionBuilderAtEnd(g->builder, then_block); |
| 2551 | if (node->data.if_var_expr.var_is_ptr) { | |
| 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); | |
| 2588 | LLVMValueRef then_expr_result = gen_if_var_then_block(g, node, variable, maybe_is_ptr, init_val, child_type, then_node); | |
| 2571 | 2589 | |
| 2572 | LLVMValueRef then_expr_result = gen_expr(g, then_node); | |
| 2573 | 2590 | if (then_endif_reachable) { |
| 2574 | 2591 | LLVMBuildBr(g->builder, endif_block); |
| 2575 | 2592 | } |
std/hash_map.zig+49-21| ... | ... | @@ -4,17 +4,20 @@ const mem = @import("mem.zig"); |
| 4 | 4 | const Allocator = mem.Allocator; |
| 5 | 5 | |
| 6 | 6 | const want_modification_safety = !@compile_var("is_release"); |
| 7 | const debug_u32 = if (want_modification_safety) void else u32; | |
| 7 | const debug_u32 = if (want_modification_safety) u32 else void; | |
| 8 | 8 | |
| 9 | pub struct HashMap(K: type, V: type, hash: fn(key: K)->u32, eql: fn(a: K, b: K)->bool) { | |
| 9 | pub struct SmallHashMap(K: type, V: type, hash: fn(key: K)->u32, eql: fn(a: K, b: K)->bool, STATIC_SIZE: isize) { | |
| 10 | 10 | entries: []Entry, |
| 11 | 11 | size: isize, |
| 12 | 12 | max_distance_from_start_index: isize, |
| 13 | 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 | 17 | // this is used to detect bugs where a hashtable is edited while an iterator is running. |
| 15 | 18 | modification_count: debug_u32, |
| 16 | 19 | |
| 17 | const Self = HashMap(K, V, hash, eql); | |
| 20 | const Self = SmallHashMap(K, V, hash, eql, STATIC_SIZE); | |
| 18 | 21 | |
| 19 | 22 | pub struct Entry { |
| 20 | 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 | 52 | } |
| 50 | 53 | } |
| 51 | 54 | |
| 52 | pub fn init(hm: &Self, allocator: &Allocator, capacity: isize) { | |
| 53 | assert(capacity > 0); | |
| 55 | pub fn init(hm: &Self, allocator: &Allocator) { | |
| 56 | hm.entries = hm.prealloc_entries[0...]; | |
| 54 | 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 | } |
| 57 | 64 | |
| 58 | 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 | } |
| 61 | 70 | |
| 62 | 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 | 77 | hm.increment_modification_count(); |
| 69 | 78 | } |
| 70 | 79 | |
| 71 | pub fn put(hm: &Self, key: K, value: V) { | |
| 80 | pub fn put(hm: &Self, key: K, value: V) -> %void { | |
| 72 | 81 | hm.increment_modification_count(); |
| 73 | hm.internal_put(key, value); | |
| 74 | 82 | |
| 75 | // if we get too full (60%), double the capacity | |
| 76 | if (hm.size * 5 >= hm.entries.len * 3) { | |
| 83 | const resize = if (hm.entries.ptr == &hm.prealloc_entries[0]) { | |
| 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 | 91 | const old_entries = hm.entries; |
| 78 | hm.init_capacity(hm.entries.len * 2); | |
| 92 | %return hm.init_capacity(hm.entries.len * 2); | |
| 79 | 93 | // dump all of the old elements into the new table |
| 80 | 94 | for (old_entries) |*old_entry| { |
| 81 | 95 | if (old_entry.used) { |
| 82 | 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 | } |
| 88 | 106 | |
| 89 | pub fn get(hm: &Self, key: K) { | |
| 90 | return internal_get(key); | |
| 107 | pub fn get(hm: &Self, key: K) -> ?&Entry { | |
| 108 | return hm.internal_get(key); | |
| 91 | 109 | } |
| 92 | 110 | |
| 93 | 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 | 113 | const start_index = hm.key_to_index(key); |
| 96 | 114 | {var roll_over: isize = 0; while (roll_over <= hm.max_distance_from_start_index; roll_over += 1) { |
| 97 | 115 | const index = (start_index + roll_over) % hm.entries.len; |
| 98 | const entry = &hm.entries[index]; | |
| 116 | var entry = &hm.entries[index]; | |
| 99 | 117 | |
| 100 | 118 | assert(entry.used); // key not found |
| 101 | 119 | |
| ... | ... | @@ -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 | } |
| 129 | 147 | |
| 130 | fn init_capacity(hm: &Self, capacity: isize) { | |
| 148 | fn init_capacity(hm: &Self, capacity: isize) -> %void { | |
| 131 | 149 | hm.entries = ([]Entry)(%return hm.allocator.alloc(hm.allocator, capacity * @sizeof(Entry))); |
| 132 | 150 | hm.size = 0; |
| 133 | 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 | 163 | fn internal_put(hm: &Self, orig_key: K, orig_value: V) { |
| 146 | 164 | var key = orig_key; |
| 147 | 165 | var value = orig_value; |
| 148 | const start_index = key_to_index(key); | |
| 166 | const start_index = hm.key_to_index(key); | |
| 149 | 167 | var roll_over: isize = 0; |
| 150 | 168 | var distance_from_start_index: isize = 0; |
| 151 | 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 | 208 | } |
| 191 | 209 | |
| 192 | 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 | 212 | {var roll_over: isize = 0; while (roll_over <= hm.max_distance_from_start_index; roll_over += 1) { |
| 195 | 213 | const index = (start_index + roll_over) % hm.entries.len; |
| 196 | 214 | const entry = &hm.entries[index]; |
| ... | ... | @@ -233,9 +251,19 @@ fn global_free(self: &Allocator, old_mem: []u8) { |
| 233 | 251 | |
| 234 | 252 | #attribute("test") |
| 235 | 253 | fn basic_hash_map_test() { |
| 236 | var map: HashMap(i32, i32, hash_i32, eql_i32) = undefined; | |
| 237 | map.init(&global_allocator, 4); | |
| 254 | var map: SmallHashMap(i32, i32, hash_i32, eql_i32, 4) = undefined; | |
| 255 | map.init(&global_allocator); | |
| 238 | 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 | } |
| 240 | 268 | |
| 241 | 269 | fn hash_i32(x: i32) -> u32 { |
std/index.zig+1| ... | ... | @@ -6,6 +6,7 @@ pub const str = @import("str.zig"); |
| 6 | 6 | pub const cstr = @import("cstr.zig"); |
| 7 | 7 | pub const net = @import("net.zig"); |
| 8 | 8 | pub const list = @import("list.zig"); |
| 9 | pub const hash_map = @import("hash_map.zig"); | |
| 9 | 10 | pub const mem = @import("mem.zig"); |
| 10 | 11 | |
| 11 | 12 | pub fn assert(b: bool) { |
std/list.zig+1-1| ... | ... | @@ -21,7 +21,7 @@ pub struct SmallList(T: type, STATIC_SIZE: isize) { |
| 21 | 21 | } |
| 22 | 22 | |
| 23 | 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 | 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 | 831 | |
| 832 | 832 | |
| 833 | 833 | add_compile_fail_case("missing else clause", R"SOURCE( |
| 834 | fn f() { | |
| 835 | const x : i32 = if (true) { 1 }; | |
| 836 | const y = if (true) { i32(1) }; | |
| 834 | fn f(b: bool) { | |
| 835 | const x : i32 = if (b) { 1 }; | |
| 836 | const y = if (b) { i32(1) }; | |
| 837 | 837 | } |
| 838 | 838 | )SOURCE", 2, ".tmp_source.zig:3:21: error: expected type 'i32', got 'void'", |
| 839 | 839 | ".tmp_source.zig:4:15: error: incompatible types: 'i32' and 'void'"); |