authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-08-21 20:42:45-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-08-21 20:47:42-07:00
logf378b0adce80aa6f85d9bf6bf97172426de2c719
tree68a460620cb480de283afe9e64d4ea8b9a0fbd7d
parent2b40815a220bbbd657bfa441e304090f11f1eb4c

stage2: comptime function with the same args is memoized

* Introduce `memoized_calls` to `Module` which stores all the comptime function calls that are cached. It is keyed on the `*Fn` and the comptime arguments, but it does not yet properly detect comptime function pointers and avoid memoizing in this case. So it will have false positives for when a comptime function call mutates data through a pointer parameter. * Sema: Add a new helper function: `resolveConstMaybeUndefVal` * Value: add `enumToInt` method and use it in `zirEnumToInt`. It is also used by the hashing function. * Value: fix representation of optionals to match error unions. Previously it would not handle nested optionals correctly. Now it matches the memory layout of error unions and supports nested optionals properly. This required changes in all the backends for generating optional constants. * TypedValue gains `eql` and `hash` methods. * Value: Implement hashing for floats, optionals, and enums. Additionally, the zig type tag is added to the hash, where it was not previously, so that values of differing types will get different hashes.

9 files changed, 340 insertions(+), 100 deletions(-)

src/Module.zig+73-4
......@@ -66,6 +66,10 @@ import_table: std.StringArrayHashMapUnmanaged(*Scope.File) = .{},
6666/// to the same function.
6767monomorphed_funcs: MonomorphedFuncsSet = .{},
6868
69/// The set of all comptime function calls that have been cached so that future calls
70/// with the same parameters will get the same return value.
71memoized_calls: MemoizedCallSet = .{},
72
6973/// We optimize memory usage for a compilation with no compile errors by storing the
7074/// error messages and mapping outside of `Decl`.
7175/// The ErrorMsg memory is owned by the decl, using Module's general purpose allocator.
......@@ -157,6 +161,60 @@ const MonomorphedFuncsContext = struct {
157161 }
158162};
159163
164pub const MemoizedCallSet = std.HashMapUnmanaged(
165 MemoizedCall.Key,
166 MemoizedCall.Result,
167 MemoizedCall,
168 std.hash_map.default_max_load_percentage,
169);
170
171pub const MemoizedCall = struct {
172 pub const Key = struct {
173 func: *Fn,
174 args: []TypedValue,
175 };
176
177 pub const Result = struct {
178 val: Value,
179 arena: std.heap.ArenaAllocator.State,
180 };
181
182 pub fn eql(ctx: @This(), a: Key, b: Key) bool {
183 _ = ctx;
184
185 if (a.func != b.func) return false;
186
187 assert(a.args.len == b.args.len);
188 for (a.args) |a_arg, arg_i| {
189 const b_arg = b.args[arg_i];
190 if (!a_arg.eql(b_arg)) {
191 return false;
192 }
193 }
194
195 return true;
196 }
197
198 /// Must match `Sema.GenericCallAdapter.hash`.
199 pub fn hash(ctx: @This(), key: Key) u64 {
200 _ = ctx;
201
202 var hasher = std.hash.Wyhash.init(0);
203
204 // The generic function Decl is guaranteed to be the first dependency
205 // of each of its instantiations.
206 std.hash.autoHash(&hasher, @ptrToInt(key.func));
207
208 // This logic must be kept in sync with the logic in `analyzeCall` that
209 // computes the hash.
210 for (key.args) |arg| {
211 arg.hash(&hasher);
212 }
213
214 return hasher.final();
215 }
216};
217
160218/// A `Module` has zero or one of these depending on whether `-femit-h` is enabled.
161219pub const GlobalEmitH = struct {
162220 /// Where to put the output.
......@@ -2255,15 +2313,26 @@ pub fn deinit(mod: *Module) void {
22552313 }
22562314 mod.export_owners.deinit(gpa);
22572315
2258 var it = mod.global_error_set.keyIterator();
2259 while (it.next()) |key| {
2260 gpa.free(key.*);
2316 {
2317 var it = mod.global_error_set.keyIterator();
2318 while (it.next()) |key| {
2319 gpa.free(key.*);
2320 }
2321 mod.global_error_set.deinit(gpa);
22612322 }
2262 mod.global_error_set.deinit(gpa);
22632323
22642324 mod.error_name_list.deinit(gpa);
22652325 mod.test_functions.deinit(gpa);
22662326 mod.monomorphed_funcs.deinit(gpa);
2327
2328 {
2329 var it = mod.memoized_calls.iterator();
2330 while (it.next()) |entry| {
2331 gpa.free(entry.key_ptr.args);
2332 entry.value_ptr.arena.promote(gpa).deinit();
2333 }
2334 mod.memoized_calls.deinit(gpa);
2335 }
22672336}
22682337
22692338fn freeExportList(gpa: *Allocator, export_list: []*Export) void {
src/Sema.zig+115-40
......@@ -649,6 +649,24 @@ fn resolveValue(
649649 return sema.failWithNeededComptime(block, src);
650650}
651651
652/// Value Tag `variable` will cause a compile error.
653/// Value Tag `undef` may be returned.
654fn resolveConstMaybeUndefVal(
655 sema: *Sema,
656 block: *Scope.Block,
657 src: LazySrcLoc,
658 inst: Air.Inst.Ref,
659) CompileError!Value {
660 if (try sema.resolveMaybeUndefValAllowVariables(block, src, inst)) |val| {
661 switch (val.tag()) {
662 .variable => return sema.failWithNeededComptime(block, src),
663 .generic_poison => return error.GenericPoison,
664 else => return val,
665 }
666 }
667 return sema.failWithNeededComptime(block, src);
668}
669
652670/// Will not return Value Tags: `variable`, `undef`. Instead they will emit compile errors.
653671/// See `resolveValue` for an alternative.
654672fn resolveConstValue(
......@@ -2565,6 +2583,19 @@ fn analyzeCall(
25652583 defer merges.results.deinit(gpa);
25662584 defer merges.br_list.deinit(gpa);
25672585
2586 // If it's a comptime function call, we need to memoize it as long as no external
2587 // comptime memory is mutated.
2588 var memoized_call_key: Module.MemoizedCall.Key = undefined;
2589 var delete_memoized_call_key = false;
2590 defer if (delete_memoized_call_key) gpa.free(memoized_call_key.args);
2591 if (is_comptime_call) {
2592 memoized_call_key = .{
2593 .func = module_fn,
2594 .args = try gpa.alloc(TypedValue, func_ty_info.param_types.len),
2595 };
2596 delete_memoized_call_key = true;
2597 }
2598
25682599 try sema.emitBackwardBranch(&child_block, call_src);
25692600
25702601 // This will have return instructions analyzed as break instructions to
......@@ -2589,12 +2620,32 @@ fn analyzeCall(
25892620 const arg_src = call_src; // TODO: better source location
25902621 const casted_arg = try sema.coerce(&child_block, param_ty, uncasted_args[arg_i], arg_src);
25912622 try sema.inst_map.putNoClobber(gpa, inst, casted_arg);
2623
2624 if (is_comptime_call) {
2625 const arg_val = try sema.resolveConstMaybeUndefVal(&child_block, arg_src, casted_arg);
2626 memoized_call_key.args[arg_i] = .{
2627 .ty = param_ty,
2628 .val = arg_val,
2629 };
2630 }
2631
25922632 arg_i += 1;
25932633 continue;
25942634 },
25952635 .param_anytype, .param_anytype_comptime => {
25962636 // No coercion needed.
2597 try sema.inst_map.putNoClobber(gpa, inst, uncasted_args[arg_i]);
2637 const uncasted_arg = uncasted_args[arg_i];
2638 try sema.inst_map.putNoClobber(gpa, inst, uncasted_arg);
2639
2640 if (is_comptime_call) {
2641 const arg_src = call_src; // TODO: better source location
2642 const arg_val = try sema.resolveConstMaybeUndefVal(&child_block, arg_src, uncasted_arg);
2643 memoized_call_key.args[arg_i] = .{
2644 .ty = sema.typeOf(uncasted_arg),
2645 .val = arg_val,
2646 };
2647 }
2648
25982649 arg_i += 1;
25992650 continue;
26002651 },
......@@ -2626,19 +2677,61 @@ fn analyzeCall(
26262677 sema.fn_ret_ty = fn_ret_ty;
26272678 defer sema.fn_ret_ty = parent_fn_ret_ty;
26282679
2629 _ = try sema.analyzeBody(&child_block, fn_info.body);
2630 const result = try sema.analyzeBlockBody(block, call_src, &child_block, merges);
2680 // This `res2` is here instead of directly breaking from `res` due to a stage1
2681 // bug generating invalid LLVM IR.
2682 const res2: Air.Inst.Ref = res2: {
2683 if (is_comptime_call) {
2684 if (mod.memoized_calls.get(memoized_call_key)) |result| {
2685 const ty_inst = try sema.addType(fn_ret_ty);
2686 try sema.air_values.append(gpa, result.val);
2687 sema.air_instructions.set(block_inst, .{
2688 .tag = .constant,
2689 .data = .{ .ty_pl = .{
2690 .ty = ty_inst,
2691 .payload = @intCast(u32, sema.air_values.items.len - 1),
2692 } },
2693 });
2694 break :res2 Air.indexToRef(block_inst);
2695 }
2696 }
26312697
2632 // Much like in `Module.semaDecl`, if the result is a struct or union type,
2633 // we need to resolve the field type expressions right here, right now, while
2634 // the child `Sema` is still available, with the AIR instruction map intact,
2635 // because the field type expressions may reference into it.
2636 if (sema.typeOf(result).zigTypeTag() == .Type) {
2637 const ty = try sema.analyzeAsType(&child_block, call_src, result);
2638 try sema.resolveDeclFields(&child_block, call_src, ty);
2639 }
2698 _ = try sema.analyzeBody(&child_block, fn_info.body);
2699 const result = try sema.analyzeBlockBody(block, call_src, &child_block, merges);
2700
2701 if (is_comptime_call) {
2702 const result_val = try sema.resolveConstMaybeUndefVal(block, call_src, result);
2703
2704 // TODO: check whether any external comptime memory was mutated by the
2705 // comptime function call. If so, then do not memoize the call here.
2706 {
2707 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
2708 errdefer arena_allocator.deinit();
2709 const arena = &arena_allocator.allocator;
2710
2711 for (memoized_call_key.args) |*arg| {
2712 arg.* = try arg.*.copy(arena);
2713 }
2714
2715 try mod.memoized_calls.put(gpa, memoized_call_key, .{
2716 .val = result_val,
2717 .arena = arena_allocator.state,
2718 });
2719 delete_memoized_call_key = false;
2720 }
2721
2722 // Much like in `Module.semaDecl`, if the result is a struct or union type,
2723 // we need to resolve the field type expressions right here, right now, while
2724 // the child `Sema` is still available, with the AIR instruction map intact,
2725 // because the field type expressions may reference into it.
2726 if (sema.typeOf(result).zigTypeTag() == .Type) {
2727 const ty = try sema.analyzeAsType(&child_block, call_src, result);
2728 try sema.resolveDeclFields(&child_block, call_src, ty);
2729 }
2730 }
26402731
2641 break :res result;
2732 break :res2 result;
2733 };
2734 break :res res2;
26422735 } else if (func_ty_info.is_generic) res: {
26432736 const func_val = try sema.resolveConstValue(block, func_src, func);
26442737 const module_fn = func_val.castTag(.function).?.data;
......@@ -3305,31 +3398,9 @@ fn zirEnumToInt(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileE
33053398 }
33063399
33073400 if (try sema.resolveMaybeUndefVal(block, operand_src, enum_tag)) |enum_tag_val| {
3308 if (enum_tag_val.castTag(.enum_field_index)) |enum_field_payload| {
3309 const field_index = enum_field_payload.data;
3310 switch (enum_tag_ty.tag()) {
3311 .enum_full => {
3312 const enum_full = enum_tag_ty.castTag(.enum_full).?.data;
3313 if (enum_full.values.count() != 0) {
3314 const val = enum_full.values.keys()[field_index];
3315 return sema.addConstant(int_tag_ty, val);
3316 } else {
3317 // Field index and integer values are the same.
3318 const val = try Value.Tag.int_u64.create(arena, field_index);
3319 return sema.addConstant(int_tag_ty, val);
3320 }
3321 },
3322 .enum_simple => {
3323 // Field index and integer values are the same.
3324 const val = try Value.Tag.int_u64.create(arena, field_index);
3325 return sema.addConstant(int_tag_ty, val);
3326 },
3327 else => unreachable,
3328 }
3329 } else {
3330 // Assume it is already an integer and return it directly.
3331 return sema.addConstant(int_tag_ty, enum_tag_val);
3332 }
3401 var buffer: Value.Payload.U64 = undefined;
3402 const val = enum_tag_val.enumToInt(enum_tag_ty, &buffer);
3403 return sema.addConstant(int_tag_ty, try val.copy(sema.arena));
33333404 }
33343405
33353406 try sema.requireRuntimeBlock(block, src);
......@@ -3414,7 +3485,10 @@ fn zirOptionalPayloadPtr(
34143485 return sema.mod.fail(&block.base, src, "unable to unwrap null", .{});
34153486 }
34163487 // The same Value represents the pointer to the optional and the payload.
3417 return sema.addConstant(child_pointer, pointer_val);
3488 return sema.addConstant(
3489 child_pointer,
3490 try Value.Tag.opt_payload_ptr.create(sema.arena, pointer_val),
3491 );
34183492 }
34193493 }
34203494
......@@ -3451,7 +3525,8 @@ fn zirOptionalPayload(
34513525 if (val.isNull()) {
34523526 return sema.mod.fail(&block.base, src, "unable to unwrap null", .{});
34533527 }
3454 return sema.addConstant(child_type, val);
3528 const sub_val = val.castTag(.opt_payload).?.data;
3529 return sema.addConstant(child_type, sub_val);
34553530 }
34563531
34573532 try sema.requireRuntimeBlock(block, src);
......@@ -9095,7 +9170,7 @@ fn wrapOptional(
90959170 inst_src: LazySrcLoc,
90969171) !Air.Inst.Ref {
90979172 if (try sema.resolveMaybeUndefVal(block, inst_src, inst)) |val| {
9098 return sema.addConstant(dest_type, val);
9173 return sema.addConstant(dest_type, try Value.Tag.opt_payload.create(sema.arena, val));
90999174 }
91009175
91019176 try sema.requireRuntimeBlock(block, inst_src);
src/TypedValue.zig+12-3
......@@ -23,9 +23,18 @@ pub const Managed = struct {
2323};
2424
2525/// Assumes arena allocation. Does a recursive copy.
26pub fn copy(self: TypedValue, allocator: *Allocator) error{OutOfMemory}!TypedValue {
26pub fn copy(self: TypedValue, arena: *Allocator) error{OutOfMemory}!TypedValue {
2727 return TypedValue{
28 .ty = try self.ty.copy(allocator),
29 .val = try self.val.copy(allocator),
28 .ty = try self.ty.copy(arena),
29 .val = try self.val.copy(arena),
3030 };
3131}
32
33pub fn eql(a: TypedValue, b: TypedValue) bool {
34 if (!a.ty.eql(b.ty)) return false;
35 return a.val.eql(b.val, a.ty);
36}
37
38pub fn hash(tv: TypedValue, hasher: *std.hash.Wyhash) void {
39 return tv.val.hash(tv.ty, hasher);
40}
src/codegen/c.zig+9-7
......@@ -319,18 +319,20 @@ pub const DeclGen = struct {
319319 .Bool => return writer.print("{}", .{val.toBool()}),
320320 .Optional => {
321321 var opt_buf: Type.Payload.ElemType = undefined;
322 const child_type = t.optionalChild(&opt_buf);
322 const payload_type = t.optionalChild(&opt_buf);
323323 if (t.isPtrLikeOptional()) {
324 return dg.renderValue(writer, child_type, val);
324 return dg.renderValue(writer, payload_type, val);
325325 }
326326 try writer.writeByte('(');
327327 try dg.renderType(writer, t);
328 if (val.tag() == .null_value) {
329 try writer.writeAll("){ .is_null = true }");
330 } else {
331 try writer.writeAll("){ .is_null = false, .payload = ");
332 try dg.renderValue(writer, child_type, val);
328 try writer.writeAll("){");
329 if (val.castTag(.opt_payload)) |pl| {
330 const payload_val = pl.data;
331 try writer.writeAll(" .is_null = false, .payload = ");
332 try dg.renderValue(writer, payload_type, payload_val);
333333 try writer.writeAll(" }");
334 } else {
335 try writer.writeAll(" .is_null = true }");
334336 }
335337 },
336338 .ErrorSet => {
src/codegen/llvm.zig+14-19
......@@ -810,27 +810,22 @@ pub const DeclGen = struct {
810810 return self.todo("handle more array values", .{});
811811 },
812812 .Optional => {
813 if (!tv.ty.isPtrLikeOptional()) {
814 var buf: Type.Payload.ElemType = undefined;
815 const child_type = tv.ty.optionalChild(&buf);
816 const llvm_child_type = try self.llvmType(child_type);
817
818 if (tv.val.tag() == .null_value) {
819 var optional_values: [2]*const llvm.Value = .{
820 llvm_child_type.constNull(),
821 self.context.intType(1).constNull(),
822 };
823 return self.context.constStruct(&optional_values, optional_values.len, .False);
824 } else {
825 var optional_values: [2]*const llvm.Value = .{
826 try self.genTypedValue(.{ .ty = child_type, .val = tv.val }),
827 self.context.intType(1).constAllOnes(),
828 };
829 return self.context.constStruct(&optional_values, optional_values.len, .False);
830 }
831 } else {
813 if (tv.ty.isPtrLikeOptional()) {
832814 return self.todo("implement const of optional pointer", .{});
833815 }
816 var buf: Type.Payload.ElemType = undefined;
817 const payload_type = tv.ty.optionalChild(&buf);
818 const is_pl = !tv.val.isNull();
819 const llvm_i1 = self.context.intType(1);
820
821 const fields: [2]*const llvm.Value = .{
822 try self.genTypedValue(.{
823 .ty = payload_type,
824 .val = if (tv.val.castTag(.opt_payload)) |pl| pl.data else Value.initTag(.undef),
825 }),
826 if (is_pl) llvm_i1.constAllOnes() else llvm_i1.constNull(),
827 };
828 return self.context.constStruct(&fields, fields.len, .False);
834829 },
835830 .Fn => {
836831 const fn_decl = switch (tv.val.tag()) {
src/codegen/wasm.zig+6-5
......@@ -1198,7 +1198,12 @@ pub const Context = struct {
11981198
11991199 // When constant has value 'null', set is_null local to '1'
12001200 // and payload to '0'
1201 if (val.tag() == .null_value) {
1201 if (val.castTag(.opt_payload)) |pl| {
1202 const payload_val = pl.data;
1203 try writer.writeByte(wasm.opcode(.i32_const));
1204 try leb.writeILEB128(writer, @as(i32, 0));
1205 try self.emitConstant(payload_val, payload_type);
1206 } else {
12021207 try writer.writeByte(wasm.opcode(.i32_const));
12031208 try leb.writeILEB128(writer, @as(i32, 1));
12041209
......@@ -1208,10 +1213,6 @@ pub const Context = struct {
12081213 });
12091214 try writer.writeByte(wasm.opcode(opcode));
12101215 try leb.writeULEB128(writer, @as(u32, 0));
1211 } else {
1212 try writer.writeByte(wasm.opcode(.i32_const));
1213 try leb.writeILEB128(writer, @as(i32, 0));
1214 try self.emitConstant(val, payload_type);
12151216 }
12161217 },
12171218 else => |zig_type| return self.fail("Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}),
src/value.zig+98-9
......@@ -133,12 +133,21 @@ pub const Value = extern union {
133133 /// When the type is error union:
134134 /// * If the tag is `.@"error"`, the error union is an error.
135135 /// * If the tag is `.eu_payload`, the error union is a payload.
136 /// * A nested error such as `((anyerror!T1)!T2)` in which the the outer error union
136 /// * A nested error such as `anyerror!(anyerror!T)` in which the the outer error union
137137 /// is non-error, but the inner error union is an error, is represented as
138138 /// a tag of `.eu_payload`, with a sub-tag of `.@"error"`.
139139 eu_payload,
140140 /// A pointer to the payload of an error union, based on a pointer to an error union.
141141 eu_payload_ptr,
142 /// When the type is optional:
143 /// * If the tag is `.null_value`, the optional is null.
144 /// * If the tag is `.opt_payload`, the optional is a payload.
145 /// * A nested optional such as `??T` in which the the outer optional
146 /// is non-null, but the inner optional is null, is represented as
147 /// a tag of `.opt_payload`, with a sub-tag of `.null_value`.
148 opt_payload,
149 /// A pointer to the payload of an optional, based on a pointer to an optional.
150 opt_payload_ptr,
142151 /// An instance of a struct.
143152 @"struct",
144153 /// An instance of a union.
......@@ -238,6 +247,8 @@ pub const Value = extern union {
238247 .repeated,
239248 .eu_payload,
240249 .eu_payload_ptr,
250 .opt_payload,
251 .opt_payload_ptr,
241252 => Payload.SubValue,
242253
243254 .bytes,
......@@ -459,7 +470,12 @@ pub const Value = extern union {
459470 return Value{ .ptr_otherwise = &new_payload.base };
460471 },
461472 .bytes => return self.copyPayloadShallow(allocator, Payload.Bytes),
462 .repeated, .eu_payload, .eu_payload_ptr => {
473 .repeated,
474 .eu_payload,
475 .eu_payload_ptr,
476 .opt_payload,
477 .opt_payload_ptr,
478 => {
463479 const payload = self.cast(Payload.SubValue).?;
464480 const new_payload = try allocator.create(Payload.SubValue);
465481 new_payload.* = .{
......@@ -656,12 +672,20 @@ pub const Value = extern union {
656672 try out_stream.writeAll("(eu_payload) ");
657673 val = val.castTag(.eu_payload).?.data;
658674 },
675 .opt_payload => {
676 try out_stream.writeAll("(opt_payload) ");
677 val = val.castTag(.opt_payload).?.data;
678 },
659679 .inferred_alloc => return out_stream.writeAll("(inferred allocation value)"),
660680 .inferred_alloc_comptime => return out_stream.writeAll("(inferred comptime allocation value)"),
661681 .eu_payload_ptr => {
662682 try out_stream.writeAll("(eu_payload_ptr)");
663683 val = val.castTag(.eu_payload_ptr).?.data;
664684 },
685 .opt_payload_ptr => {
686 try out_stream.writeAll("(opt_payload_ptr)");
687 val = val.castTag(.opt_payload_ptr).?.data;
688 },
665689 };
666690 }
667691
......@@ -776,6 +800,38 @@ pub const Value = extern union {
776800 }
777801 }
778802
803 pub fn enumToInt(val: Value, ty: Type, buffer: *Payload.U64) Value {
804 if (val.castTag(.enum_field_index)) |enum_field_payload| {
805 const field_index = enum_field_payload.data;
806 switch (ty.tag()) {
807 .enum_full, .enum_nonexhaustive => {
808 const enum_full = ty.cast(Type.Payload.EnumFull).?.data;
809 if (enum_full.values.count() != 0) {
810 return enum_full.values.keys()[field_index];
811 } else {
812 // Field index and integer values are the same.
813 buffer.* = .{
814 .base = .{ .tag = .int_u64 },
815 .data = field_index,
816 };
817 return Value.initPayload(&buffer.base);
818 }
819 },
820 .enum_simple => {
821 // Field index and integer values are the same.
822 buffer.* = .{
823 .base = .{ .tag = .int_u64 },
824 .data = field_index,
825 };
826 return Value.initPayload(&buffer.base);
827 },
828 else => unreachable,
829 }
830 }
831 // Assume it is already an integer and return it directly.
832 return val;
833 }
834
779835 /// Asserts the value is an integer.
780836 pub fn toBigInt(self: Value, space: *BigIntSpace) BigIntConst {
781837 switch (self.tag()) {
......@@ -1132,7 +1188,10 @@ pub const Value = extern union {
11321188 }
11331189
11341190 pub fn hash(val: Value, ty: Type, hasher: *std.hash.Wyhash) void {
1135 switch (ty.zigTypeTag()) {
1191 const zig_ty_tag = ty.zigTypeTag();
1192 std.hash.autoHash(hasher, zig_ty_tag);
1193
1194 switch (zig_ty_tag) {
11361195 .BoundFn => unreachable, // TODO remove this from the language
11371196
11381197 .Void,
......@@ -1157,7 +1216,10 @@ pub const Value = extern union {
11571216 }
11581217 },
11591218 .Float, .ComptimeFloat => {
1160 @panic("TODO implement hashing float values");
1219 // TODO double check the lang spec. should we to bitwise hashing here,
1220 // or a hash that normalizes the float value?
1221 const float = val.toFloat(f128);
1222 std.hash.autoHash(hasher, @bitCast(u128, float));
11611223 },
11621224 .Pointer => {
11631225 @panic("TODO implement hashing pointer values");
......@@ -1169,7 +1231,15 @@ pub const Value = extern union {
11691231 @panic("TODO implement hashing struct values");
11701232 },
11711233 .Optional => {
1172 @panic("TODO implement hashing optional values");
1234 if (val.castTag(.opt_payload)) |payload| {
1235 std.hash.autoHash(hasher, true); // non-null
1236 const sub_val = payload.data;
1237 var buffer: Type.Payload.ElemType = undefined;
1238 const sub_ty = ty.optionalChild(&buffer);
1239 sub_val.hash(sub_ty, hasher);
1240 } else {
1241 std.hash.autoHash(hasher, false); // non-null
1242 }
11731243 },
11741244 .ErrorUnion => {
11751245 @panic("TODO implement hashing error union values");
......@@ -1178,7 +1248,16 @@ pub const Value = extern union {
11781248 @panic("TODO implement hashing error set values");
11791249 },
11801250 .Enum => {
1181 @panic("TODO implement hashing enum values");
1251 var enum_space: Payload.U64 = undefined;
1252 const int_val = val.enumToInt(ty, &enum_space);
1253
1254 var space: BigIntSpace = undefined;
1255 const big = int_val.toBigInt(&space);
1256
1257 std.hash.autoHash(hasher, big.positive);
1258 for (big.limbs) |limb| {
1259 std.hash.autoHash(hasher, limb);
1260 }
11821261 },
11831262 .Union => {
11841263 @panic("TODO implement hashing union values");
......@@ -1257,6 +1336,11 @@ pub const Value = extern union {
12571336 const err_union_val = (try err_union_ptr.pointerDeref(allocator)) orelse return null;
12581337 break :blk err_union_val.castTag(.eu_payload).?.data;
12591338 },
1339 .opt_payload_ptr => blk: {
1340 const opt_ptr = self.castTag(.opt_payload_ptr).?.data;
1341 const opt_val = (try opt_ptr.pointerDeref(allocator)) orelse return null;
1342 break :blk opt_val.castTag(.opt_payload).?.data;
1343 },
12601344
12611345 .zero,
12621346 .one,
......@@ -1354,13 +1438,14 @@ pub const Value = extern union {
13541438 /// Valid for all types. Asserts the value is not undefined and not unreachable.
13551439 pub fn isNull(self: Value) bool {
13561440 return switch (self.tag()) {
1441 .null_value => true,
1442 .opt_payload => false,
1443
13571444 .undef => unreachable,
13581445 .unreachable_value => unreachable,
13591446 .inferred_alloc => unreachable,
13601447 .inferred_alloc_comptime => unreachable,
1361 .null_value => true,
1362
1363 else => false,
1448 else => unreachable,
13641449 };
13651450 }
13661451
......@@ -1390,6 +1475,10 @@ pub const Value = extern union {
13901475 return switch (val.tag()) {
13911476 .eu_payload => true,
13921477 else => false,
1478
1479 .undef => unreachable,
1480 .inferred_alloc => unreachable,
1481 .inferred_alloc_comptime => unreachable,
13931482 };
13941483 }
13951484
test/behavior/eval.zig+13
......@@ -148,3 +148,16 @@ const List = blk: {
148148 array: T,
149149 };
150150};
151
152test "comptime function with the same args is memoized" {
153 comptime {
154 try expect(MakeType(i32) == MakeType(i32));
155 try expect(MakeType(i32) != MakeType(f64));
156 }
157}
158
159fn MakeType(comptime T: type) type {
160 return struct {
161 field: T,
162 };
163}
test/behavior/eval_stage1.zig-13
......@@ -356,19 +356,6 @@ test "binary math operator in partially inlined function" {
356356 try expect(s[3] == 0xd0e0f10);
357357}
358358
359test "comptime function with the same args is memoized" {
360 comptime {
361 try expect(MakeType(i32) == MakeType(i32));
362 try expect(MakeType(i32) != MakeType(f64));
363 }
364}
365
366fn MakeType(comptime T: type) type {
367 return struct {
368 field: T,
369 };
370}
371
372359test "comptime function with mutable pointer is not memoized" {
373360 comptime {
374361 var x: i32 = 1;