authorgravatar for jhc@dismail.deJimmi Holst Christensen <jhc@dismail.de> 2022-11-25 20:12:25+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-11-27 02:10:00-05:00
log609716524169c538b7f12666c3f9bb46a0aeeb3c
tree3c7634b5391a1bc8568b07717e988b888334f79e
parent0196010b0cdc0854286fa2442dc28b45f1278425

Use a slice for InstMap instead of std.HashMap

The `sema.inst_map` datastructure is very often accessed. All instructions that reference the result of other instructions does a lookup into this field. Because of this, a significant amount of time, is spent in `std.HashMap.get`. This commit replaces the `HashMap` with a simpler data structure that uses the zir indexes to index into a slice for the result. See the data structure doc comment for more info.

2 files changed, 125 insertions(+), 23 deletions(-)

src/Module.zig+1-1
...@@ -5582,7 +5582,7 @@ pub fn analyzeFnBody(mod: *Module, func: *Fn, arena: Allocator) SemaError!Air {...@@ -5582,7 +5582,7 @@ pub fn analyzeFnBody(mod: *Module, func: *Fn, arena: Allocator) SemaError!Air {
5582 const runtime_params_len = @intCast(u32, fn_ty_info.param_types.len);5582 const runtime_params_len = @intCast(u32, fn_ty_info.param_types.len);
5583 try inner_block.instructions.ensureTotalCapacityPrecise(gpa, runtime_params_len);5583 try inner_block.instructions.ensureTotalCapacityPrecise(gpa, runtime_params_len);
5584 try sema.air_instructions.ensureUnusedCapacity(gpa, fn_info.total_params_len * 2); // * 2 for the `addType`5584 try sema.air_instructions.ensureUnusedCapacity(gpa, fn_info.total_params_len * 2); // * 2 for the `addType`
5585 try sema.inst_map.ensureUnusedCapacity(gpa, fn_info.total_params_len);5585 try sema.inst_map.ensureSpaceForInstructions(gpa, fn_info.param_body);
55865586
5587 var runtime_param_index: usize = 0;5587 var runtime_param_index: usize = 0;
5588 var total_param_index: usize = 0;5588 var total_param_index: usize = 0;
src/Sema.zig+124-22
...@@ -117,7 +117,103 @@ const build_options = @import("build_options");...@@ -117,7 +117,103 @@ const build_options = @import("build_options");
117pub const default_branch_quota = 1000;117pub const default_branch_quota = 1000;
118pub const default_reference_trace_len = 2;118pub const default_reference_trace_len = 2;
119119
120pub const InstMap = std.AutoHashMapUnmanaged(Zir.Inst.Index, Air.Inst.Ref);120/// Stores the mapping from `Zir.Inst.Index -> Air.Inst.Ref`, which is used by sema to resolve
121/// instructions during analysis.
122/// Instead of a hash table approach, InstMap is simply a slice that is indexed into using the
123/// zir instruction index and a start offset. An index is not pressent in the map if the value
124/// at the index is `Air.Inst.Ref.none`.
125/// `ensureSpaceForInstructions` can be called to force InstMap to have a mapped range that
126/// includes all instructions in a slice. After calling this function, `putAssumeCapacity*` can
127/// be called safely for any of the instructions passed in.
128pub const InstMap = struct {
129 items: []Air.Inst.Ref = &[_]Air.Inst.Ref{},
130 start: Zir.Inst.Index = 0,
131
132 pub fn deinit(map: InstMap, allocator: mem.Allocator) void {
133 allocator.free(map.items);
134 }
135
136 pub fn get(map: InstMap, key: Zir.Inst.Index) ?Air.Inst.Ref {
137 if (!map.contains(key)) return null;
138 return map.items[key - map.start];
139 }
140
141 pub fn putAssumeCapacity(
142 map: *InstMap,
143 key: Zir.Inst.Index,
144 ref: Air.Inst.Ref,
145 ) void {
146 map.items[key - map.start] = ref;
147 }
148
149 pub fn putAssumeCapacityNoClobber(
150 map: *InstMap,
151 key: Zir.Inst.Index,
152 ref: Air.Inst.Ref,
153 ) void {
154 assert(!map.contains(key));
155 map.putAssumeCapacity(key, ref);
156 }
157
158 pub const GetOrPutResult = struct {
159 value_ptr: *Air.Inst.Ref,
160 found_existing: bool,
161 };
162
163 pub fn getOrPutAssumeCapacity(
164 map: *InstMap,
165 key: Zir.Inst.Index,
166 ) GetOrPutResult {
167 const index = key - map.start;
168 return GetOrPutResult{
169 .value_ptr = &map.items[index],
170 .found_existing = map.items[index] != .none,
171 };
172 }
173
174 pub fn remove(map: InstMap, key: Zir.Inst.Index) bool {
175 if (!map.contains(key)) return false;
176 map.items[key - map.start] = .none;
177 return true;
178 }
179
180 pub fn contains(map: InstMap, key: Zir.Inst.Index) bool {
181 return map.items[key - map.start] != .none;
182 }
183
184 pub fn ensureSpaceForInstructions(
185 map: *InstMap,
186 allocator: mem.Allocator,
187 insts: []const Zir.Inst.Index,
188 ) !void {
189 const min_max = mem.minMax(Zir.Inst.Index, insts);
190 const start = min_max.min;
191 const end = min_max.max;
192 if (map.start <= start and end < map.items.len + map.start)
193 return;
194
195 const old_start = if (map.items.len == 0) start else map.start;
196 var better_capacity = map.items.len;
197 var better_start = old_start;
198 while (true) {
199 const extra_capacity = better_capacity / 2 + 16;
200 better_capacity += extra_capacity;
201 better_start -|= @intCast(Zir.Inst.Index, extra_capacity / 2);
202 if (better_start <= start and end < better_capacity + better_start)
203 break;
204 }
205
206 const start_diff = old_start - better_start;
207 const new_items = try allocator.alloc(Air.Inst.Ref, better_capacity);
208 mem.set(Air.Inst.Ref, new_items[0..start_diff], .none);
209 mem.copy(Air.Inst.Ref, new_items[start_diff..], map.items);
210 mem.set(Air.Inst.Ref, new_items[start_diff + map.items.len ..], .none);
211
212 allocator.free(map.items);
213 map.items = new_items;
214 map.start = @intCast(Zir.Inst.Index, better_start);
215 }
216};
121217
122/// This is the context needed to semantically analyze ZIR instructions and218/// This is the context needed to semantically analyze ZIR instructions and
123/// produce AIR instructions.219/// produce AIR instructions.
...@@ -753,6 +849,8 @@ fn analyzeBodyInner(...@@ -753,6 +849,8 @@ fn analyzeBodyInner(
753) CompileError!Zir.Inst.Index {849) CompileError!Zir.Inst.Index {
754 // No tracy calls here, to avoid interfering with the tail call mechanism.850 // No tracy calls here, to avoid interfering with the tail call mechanism.
755851
852 try sema.inst_map.ensureSpaceForInstructions(sema.gpa, body);
853
756 const parent_capture_scope = block.wip_capture_scope;854 const parent_capture_scope = block.wip_capture_scope;
757855
758 var wip_captures = WipCaptureScope{856 var wip_captures = WipCaptureScope{
...@@ -1443,7 +1541,7 @@ fn analyzeBodyInner(...@@ -1443,7 +1541,7 @@ fn analyzeBodyInner(
1443 labeled_block.destroy(gpa);1541 labeled_block.destroy(gpa);
1444 assert(sema.post_hoc_blocks.remove(new_block_inst));1542 assert(sema.post_hoc_blocks.remove(new_block_inst));
1445 }1543 }
1446 try map.put(gpa, inst, block_result);1544 map.putAssumeCapacity(inst, block_result);
1447 i += 1;1545 i += 1;
1448 continue;1546 continue;
1449 }1547 }
...@@ -1622,7 +1720,7 @@ fn analyzeBodyInner(...@@ -1622,7 +1720,7 @@ fn analyzeBodyInner(
1622 const extra = sema.code.extraData(Zir.Inst.DeferErrCode, inst_data.payload_index).data;1720 const extra = sema.code.extraData(Zir.Inst.DeferErrCode, inst_data.payload_index).data;
1623 const defer_body = sema.code.extra[extra.index..][0..extra.len];1721 const defer_body = sema.code.extra[extra.index..][0..extra.len];
1624 const err_code = try sema.resolveInst(inst_data.err_code);1722 const err_code = try sema.resolveInst(inst_data.err_code);
1625 try sema.inst_map.put(sema.gpa, extra.remapped_err_code, err_code);1723 sema.inst_map.putAssumeCapacity(extra.remapped_err_code, err_code);
1626 const break_inst = sema.analyzeBodyInner(block, defer_body) catch |err| switch (err) {1724 const break_inst = sema.analyzeBodyInner(block, defer_body) catch |err| switch (err) {
1627 error.ComptimeBreak => sema.comptime_break_inst,1725 error.ComptimeBreak => sema.comptime_break_inst,
1628 else => |e| return e,1726 else => |e| return e,
...@@ -1633,7 +1731,7 @@ fn analyzeBodyInner(...@@ -1633,7 +1731,7 @@ fn analyzeBodyInner(
1633 };1731 };
1634 if (sema.typeOf(air_inst).isNoReturn())1732 if (sema.typeOf(air_inst).isNoReturn())
1635 break always_noreturn;1733 break always_noreturn;
1636 try map.put(sema.gpa, inst, air_inst);1734 map.putAssumeCapacity(inst, air_inst);
1637 i += 1;1735 i += 1;
1638 } else unreachable;1736 } else unreachable;
16391737
...@@ -5980,7 +6078,7 @@ fn zirCall(...@@ -5980,7 +6078,7 @@ fn zirCall(
5980 }6078 }
59816079
5982 const param_ty_inst = try sema.addType(param_ty);6080 const param_ty_inst = try sema.addType(param_ty);
5983 try sema.inst_map.put(sema.gpa, inst, param_ty_inst);6081 sema.inst_map.putAssumeCapacity(inst, param_ty_inst);
59846082
5985 const resolved = try sema.resolveBody(block, args_body[arg_start..arg_end], inst);6083 const resolved = try sema.resolveBody(block, args_body[arg_start..arg_end], inst);
5986 const resolved_ty = sema.typeOf(resolved);6084 const resolved_ty = sema.typeOf(resolved);
...@@ -6368,6 +6466,8 @@ fn analyzeCall(...@@ -6368,6 +6466,8 @@ fn analyzeCall(
6368 // which means its parameter type expressions must be resolved in order and used6466 // which means its parameter type expressions must be resolved in order and used
6369 // to successively coerce the arguments.6467 // to successively coerce the arguments.
6370 const fn_info = sema.code.getFnInfo(module_fn.zir_body_inst);6468 const fn_info = sema.code.getFnInfo(module_fn.zir_body_inst);
6469 try sema.inst_map.ensureSpaceForInstructions(sema.gpa, fn_info.param_body);
6470
6371 var arg_i: usize = 0;6471 var arg_i: usize = 0;
6372 for (fn_info.param_body) |inst| {6472 for (fn_info.param_body) |inst| {
6373 sema.analyzeInlineCallArg(6473 sema.analyzeInlineCallArg(
...@@ -6662,7 +6762,7 @@ fn analyzeInlineCallArg(...@@ -6662,7 +6762,7 @@ fn analyzeInlineCallArg(
6662 const casted_arg = try sema.coerce(arg_block, param_ty, uncasted_arg, arg_src);6762 const casted_arg = try sema.coerce(arg_block, param_ty, uncasted_arg, arg_src);
66636763
6664 if (is_comptime_call) {6764 if (is_comptime_call) {
6665 try sema.inst_map.putNoClobber(sema.gpa, inst, casted_arg);6765 sema.inst_map.putAssumeCapacityNoClobber(inst, casted_arg);
6666 const arg_val = sema.resolveConstMaybeUndefVal(arg_block, arg_src, casted_arg, "argument to function being called at comptime must be comptime-known") catch |err| {6766 const arg_val = sema.resolveConstMaybeUndefVal(arg_block, arg_src, casted_arg, "argument to function being called at comptime must be comptime-known") catch |err| {
6667 if (err == error.AnalysisFail and param_block.comptime_reason != null) try param_block.comptime_reason.?.explain(sema, sema.err);6767 if (err == error.AnalysisFail and param_block.comptime_reason != null) try param_block.comptime_reason.?.explain(sema, sema.err);
6668 return err;6768 return err;
...@@ -6686,13 +6786,13 @@ fn analyzeInlineCallArg(...@@ -6686,13 +6786,13 @@ fn analyzeInlineCallArg(
6686 .val = arg_val,6786 .val = arg_val,
6687 };6787 };
6688 } else if (zir_tags[inst] == .param_comptime or try sema.typeRequiresComptime(param_ty)) {6788 } else if (zir_tags[inst] == .param_comptime or try sema.typeRequiresComptime(param_ty)) {
6689 try sema.inst_map.putNoClobber(sema.gpa, inst, casted_arg);6789 sema.inst_map.putAssumeCapacityNoClobber(inst, casted_arg);
6690 } else if (try sema.resolveMaybeUndefVal(casted_arg)) |val| {6790 } else if (try sema.resolveMaybeUndefVal(casted_arg)) |val| {
6691 // We have a comptime value but we need a runtime value to preserve inlining semantics,6791 // We have a comptime value but we need a runtime value to preserve inlining semantics,
6692 const wrapped = try sema.addConstant(param_ty, try Value.Tag.runtime_value.create(sema.arena, val));6792 const wrapped = try sema.addConstant(param_ty, try Value.Tag.runtime_value.create(sema.arena, val));
6693 try sema.inst_map.putNoClobber(sema.gpa, inst, wrapped);6793 sema.inst_map.putAssumeCapacityNoClobber(inst, wrapped);
6694 } else {6794 } else {
6695 try sema.inst_map.putNoClobber(sema.gpa, inst, casted_arg);6795 sema.inst_map.putAssumeCapacityNoClobber(inst, casted_arg);
6696 }6796 }
66976797
6698 arg_i.* += 1;6798 arg_i.* += 1;
...@@ -6704,7 +6804,7 @@ fn analyzeInlineCallArg(...@@ -6704,7 +6804,7 @@ fn analyzeInlineCallArg(
6704 const param_ty = sema.typeOf(uncasted_arg);6804 const param_ty = sema.typeOf(uncasted_arg);
67056805
6706 if (is_comptime_call) {6806 if (is_comptime_call) {
6707 try sema.inst_map.putNoClobber(sema.gpa, inst, uncasted_arg);6807 sema.inst_map.putAssumeCapacityNoClobber(inst, uncasted_arg);
6708 const arg_val = sema.resolveConstMaybeUndefVal(arg_block, arg_src, uncasted_arg, "argument to function being called at comptime must be comptime-known") catch |err| {6808 const arg_val = sema.resolveConstMaybeUndefVal(arg_block, arg_src, uncasted_arg, "argument to function being called at comptime must be comptime-known") catch |err| {
6709 if (err == error.AnalysisFail and param_block.comptime_reason != null) try param_block.comptime_reason.?.explain(sema, sema.err);6809 if (err == error.AnalysisFail and param_block.comptime_reason != null) try param_block.comptime_reason.?.explain(sema, sema.err);
6710 return err;6810 return err;
...@@ -6728,13 +6828,13 @@ fn analyzeInlineCallArg(...@@ -6728,13 +6828,13 @@ fn analyzeInlineCallArg(
6728 .val = arg_val,6828 .val = arg_val,
6729 };6829 };
6730 } else if (zir_tags[inst] == .param_anytype_comptime or try sema.typeRequiresComptime(param_ty)) {6830 } else if (zir_tags[inst] == .param_anytype_comptime or try sema.typeRequiresComptime(param_ty)) {
6731 try sema.inst_map.putNoClobber(sema.gpa, inst, uncasted_arg);6831 sema.inst_map.putAssumeCapacityNoClobber(inst, uncasted_arg);
6732 } else if (try sema.resolveMaybeUndefVal(uncasted_arg)) |val| {6832 } else if (try sema.resolveMaybeUndefVal(uncasted_arg)) |val| {
6733 // We have a comptime value but we need a runtime value to preserve inlining semantics,6833 // We have a comptime value but we need a runtime value to preserve inlining semantics,
6734 const wrapped = try sema.addConstant(param_ty, try Value.Tag.runtime_value.create(sema.arena, val));6834 const wrapped = try sema.addConstant(param_ty, try Value.Tag.runtime_value.create(sema.arena, val));
6735 try sema.inst_map.putNoClobber(sema.gpa, inst, wrapped);6835 sema.inst_map.putAssumeCapacityNoClobber(inst, wrapped);
6736 } else {6836 } else {
6737 try sema.inst_map.putNoClobber(sema.gpa, inst, uncasted_arg);6837 sema.inst_map.putAssumeCapacityNoClobber(inst, uncasted_arg);
6738 }6838 }
67396839
6740 arg_i.* += 1;6840 arg_i.* += 1;
...@@ -7008,7 +7108,8 @@ fn instantiateGenericCall(...@@ -7008,7 +7108,8 @@ fn instantiateGenericCall(
7008 child_block.params.deinit(gpa);7108 child_block.params.deinit(gpa);
7009 }7109 }
70107110
7011 try child_sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, uncasted_args.len));7111 try child_sema.inst_map.ensureSpaceForInstructions(gpa, fn_info.param_body);
7112
7012 var arg_i: usize = 0;7113 var arg_i: usize = 0;
7013 for (fn_info.param_body) |inst| {7114 for (fn_info.param_body) |inst| {
7014 var is_comptime = false;7115 var is_comptime = false;
...@@ -8111,6 +8212,7 @@ fn resolveGenericBody(...@@ -8111,6 +8212,7 @@ fn resolveGenericBody(
8111 block.params.deinit(sema.gpa);8212 block.params.deinit(sema.gpa);
8112 block.params = prev_params;8213 block.params = prev_params;
8113 }8214 }
8215
8114 const uncasted = sema.resolveBody(block, body, func_inst) catch |err| break :err err;8216 const uncasted = sema.resolveBody(block, body, func_inst) catch |err| break :err err;
8115 const result = sema.coerce(block, dest_ty, uncasted, src) catch |err| break :err err;8217 const result = sema.coerce(block, dest_ty, uncasted, src) catch |err| break :err err;
8116 const val = sema.resolveConstValue(block, src, result, reason) catch |err| break :err err;8218 const val = sema.resolveConstValue(block, src, result, reason) catch |err| break :err err;
...@@ -8660,7 +8762,7 @@ fn zirParam(...@@ -8660,7 +8762,7 @@ fn zirParam(
8660 .is_comptime = comptime_syntax,8762 .is_comptime = comptime_syntax,
8661 .name = param_name,8763 .name = param_name,
8662 });8764 });
8663 try sema.inst_map.putNoClobber(sema.gpa, inst, .generic_poison);8765 sema.inst_map.putAssumeCapacityNoClobber(inst, .generic_poison);
8664 return;8766 return;
8665 },8767 },
8666 else => |e| return e,8768 else => |e| return e,
...@@ -8676,7 +8778,7 @@ fn zirParam(...@@ -8676,7 +8778,7 @@ fn zirParam(
8676 .is_comptime = comptime_syntax,8778 .is_comptime = comptime_syntax,
8677 .name = param_name,8779 .name = param_name,
8678 });8780 });
8679 try sema.inst_map.putNoClobber(sema.gpa, inst, .generic_poison);8781 sema.inst_map.putAssumeCapacityNoClobber(inst, .generic_poison);
8680 return;8782 return;
8681 },8783 },
8682 else => |e| return e,8784 else => |e| return e,
...@@ -8700,7 +8802,7 @@ fn zirParam(...@@ -8700,7 +8802,7 @@ fn zirParam(
8700 // non-anytype parameter that ended up being a one-possible-type.8802 // non-anytype parameter that ended up being a one-possible-type.
8701 // We don't want the parameter to be part of the instantiated function type.8803 // We don't want the parameter to be part of the instantiated function type.
8702 const result = try sema.addConstant(param_ty, opv);8804 const result = try sema.addConstant(param_ty, opv);
8703 try sema.inst_map.put(sema.gpa, inst, result);8805 sema.inst_map.putAssumeCapacity(inst, result);
8704 return;8806 return;
8705 }8807 }
8706 }8808 }
...@@ -8715,7 +8817,7 @@ fn zirParam(...@@ -8715,7 +8817,7 @@ fn zirParam(
8715 // If this is a comptime parameter we can add a constant generic_poison8817 // If this is a comptime parameter we can add a constant generic_poison
8716 // since this is also a generic parameter.8818 // since this is also a generic parameter.
8717 const result = try sema.addConstant(param_ty, Value.initTag(.generic_poison));8819 const result = try sema.addConstant(param_ty, Value.initTag(.generic_poison));
8718 try sema.inst_map.putNoClobber(sema.gpa, inst, result);8820 sema.inst_map.putAssumeCapacityNoClobber(inst, result);
8719 } else {8821 } else {
8720 // Otherwise we need a dummy runtime instruction.8822 // Otherwise we need a dummy runtime instruction.
8721 const result_index = @intCast(Air.Inst.Index, sema.air_instructions.len);8823 const result_index = @intCast(Air.Inst.Index, sema.air_instructions.len);
...@@ -8724,7 +8826,7 @@ fn zirParam(...@@ -8724,7 +8826,7 @@ fn zirParam(
8724 .data = .{ .ty = param_ty },8826 .data = .{ .ty = param_ty },
8725 });8827 });
8726 const result = Air.indexToRef(result_index);8828 const result = Air.indexToRef(result_index);
8727 try sema.inst_map.putNoClobber(sema.gpa, inst, result);8829 sema.inst_map.putAssumeCapacityNoClobber(inst, result);
8728 }8830 }
8729}8831}
87308832
...@@ -8763,7 +8865,7 @@ fn zirParamAnytype(...@@ -8763,7 +8865,7 @@ fn zirParamAnytype(
8763 .is_comptime = comptime_syntax,8865 .is_comptime = comptime_syntax,
8764 .name = param_name,8866 .name = param_name,
8765 });8867 });
8766 try sema.inst_map.put(sema.gpa, inst, .generic_poison);8868 sema.inst_map.putAssumeCapacity(inst, .generic_poison);
8767}8869}
87688870
8769fn zirAs(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {8871fn zirAs(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
...@@ -11220,7 +11322,7 @@ fn maybeErrorUnwrap(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, op...@@ -11220,7 +11322,7 @@ fn maybeErrorUnwrap(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, op
11220 };11322 };
11221 if (sema.typeOf(air_inst).isNoReturn())11323 if (sema.typeOf(air_inst).isNoReturn())
11222 return true;11324 return true;
11223 try sema.inst_map.put(sema.gpa, inst, air_inst);11325 sema.inst_map.putAssumeCapacity(inst, air_inst);
11224 }11326 }
11225 unreachable;11327 unreachable;
11226}11328}
...@@ -16348,7 +16450,7 @@ fn zirTryPtr(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileErr...@@ -16348,7 +16450,7 @@ fn zirTryPtr(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileErr
16348// break from an inline loop. In such case we must convert it to16450// break from an inline loop. In such case we must convert it to
16349// a runtime break.16451// a runtime break.
16350fn addRuntimeBreak(sema: *Sema, child_block: *Block, break_data: BreakData) !void {16452fn addRuntimeBreak(sema: *Sema, child_block: *Block, break_data: BreakData) !void {
16351 const gop = try sema.inst_map.getOrPut(sema.gpa, break_data.block_inst);16453 const gop = sema.inst_map.getOrPutAssumeCapacity(break_data.block_inst);
16352 const labeled_block = if (!gop.found_existing) blk: {16454 const labeled_block = if (!gop.found_existing) blk: {
16353 try sema.post_hoc_blocks.ensureUnusedCapacity(sema.gpa, 1);16455 try sema.post_hoc_blocks.ensureUnusedCapacity(sema.gpa, 1);
1635416456