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) = .{},...@@ -66,6 +66,10 @@ import_table: std.StringArrayHashMapUnmanaged(*Scope.File) = .{},
66/// to the same function.66/// to the same function.
67monomorphed_funcs: MonomorphedFuncsSet = .{},67monomorphed_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
69/// We optimize memory usage for a compilation with no compile errors by storing the73/// We optimize memory usage for a compilation with no compile errors by storing the
70/// error messages and mapping outside of `Decl`.74/// error messages and mapping outside of `Decl`.
71/// The ErrorMsg memory is owned by the decl, using Module's general purpose allocator.75/// The ErrorMsg memory is owned by the decl, using Module's general purpose allocator.
...@@ -157,6 +161,60 @@ const MonomorphedFuncsContext = struct {...@@ -157,6 +161,60 @@ const MonomorphedFuncsContext = struct {
157 }161 }
158};162};
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
160/// A `Module` has zero or one of these depending on whether `-femit-h` is enabled.218/// A `Module` has zero or one of these depending on whether `-femit-h` is enabled.
161pub const GlobalEmitH = struct {219pub const GlobalEmitH = struct {
162 /// Where to put the output.220 /// Where to put the output.
...@@ -2255,15 +2313,26 @@ pub fn deinit(mod: *Module) void {...@@ -2255,15 +2313,26 @@ pub fn deinit(mod: *Module) void {
2255 }2313 }
2256 mod.export_owners.deinit(gpa);2314 mod.export_owners.deinit(gpa);
22572315
2258 var it = mod.global_error_set.keyIterator();2316 {
2259 while (it.next()) |key| {2317 var it = mod.global_error_set.keyIterator();
2260 gpa.free(key.*);2318 while (it.next()) |key| {
2319 gpa.free(key.*);
2320 }
2321 mod.global_error_set.deinit(gpa);
2261 }2322 }
2262 mod.global_error_set.deinit(gpa);
22632323
2264 mod.error_name_list.deinit(gpa);2324 mod.error_name_list.deinit(gpa);
2265 mod.test_functions.deinit(gpa);2325 mod.test_functions.deinit(gpa);
2266 mod.monomorphed_funcs.deinit(gpa);2326 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 }
2267}2336}
22682337
2269fn freeExportList(gpa: *Allocator, export_list: []*Export) void {2338fn freeExportList(gpa: *Allocator, export_list: []*Export) void {
src/Sema.zig+115-40
...@@ -649,6 +649,24 @@ fn resolveValue(...@@ -649,6 +649,24 @@ fn resolveValue(
649 return sema.failWithNeededComptime(block, src);649 return sema.failWithNeededComptime(block, src);
650}650}
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
652/// Will not return Value Tags: `variable`, `undef`. Instead they will emit compile errors.670/// Will not return Value Tags: `variable`, `undef`. Instead they will emit compile errors.
653/// See `resolveValue` for an alternative.671/// See `resolveValue` for an alternative.
654fn resolveConstValue(672fn resolveConstValue(
...@@ -2565,6 +2583,19 @@ fn analyzeCall(...@@ -2565,6 +2583,19 @@ fn analyzeCall(
2565 defer merges.results.deinit(gpa);2583 defer merges.results.deinit(gpa);
2566 defer merges.br_list.deinit(gpa);2584 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
2568 try sema.emitBackwardBranch(&child_block, call_src);2599 try sema.emitBackwardBranch(&child_block, call_src);
25692600
2570 // This will have return instructions analyzed as break instructions to2601 // This will have return instructions analyzed as break instructions to
...@@ -2589,12 +2620,32 @@ fn analyzeCall(...@@ -2589,12 +2620,32 @@ fn analyzeCall(
2589 const arg_src = call_src; // TODO: better source location2620 const arg_src = call_src; // TODO: better source location
2590 const casted_arg = try sema.coerce(&child_block, param_ty, uncasted_args[arg_i], arg_src);2621 const casted_arg = try sema.coerce(&child_block, param_ty, uncasted_args[arg_i], arg_src);
2591 try sema.inst_map.putNoClobber(gpa, inst, casted_arg);2622 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
2592 arg_i += 1;2632 arg_i += 1;
2593 continue;2633 continue;
2594 },2634 },
2595 .param_anytype, .param_anytype_comptime => {2635 .param_anytype, .param_anytype_comptime => {
2596 // No coercion needed.2636 // 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
2598 arg_i += 1;2649 arg_i += 1;
2599 continue;2650 continue;
2600 },2651 },
...@@ -2626,19 +2677,61 @@ fn analyzeCall(...@@ -2626,19 +2677,61 @@ fn analyzeCall(
2626 sema.fn_ret_ty = fn_ret_ty;2677 sema.fn_ret_ty = fn_ret_ty;
2627 defer sema.fn_ret_ty = parent_fn_ret_ty;2678 defer sema.fn_ret_ty = parent_fn_ret_ty;
26282679
2629 _ = try sema.analyzeBody(&child_block, fn_info.body);2680 // This `res2` is here instead of directly breaking from `res` due to a stage1
2630 const result = try sema.analyzeBlockBody(block, call_src, &child_block, merges);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,2698 _ = try sema.analyzeBody(&child_block, fn_info.body);
2633 // we need to resolve the field type expressions right here, right now, while2699 const result = try sema.analyzeBlockBody(block, call_src, &child_block, merges);
2634 // the child `Sema` is still available, with the AIR instruction map intact,2700
2635 // because the field type expressions may reference into it.2701 if (is_comptime_call) {
2636 if (sema.typeOf(result).zigTypeTag() == .Type) {2702 const result_val = try sema.resolveConstMaybeUndefVal(block, call_src, result);
2637 const ty = try sema.analyzeAsType(&child_block, call_src, result);2703
2638 try sema.resolveDeclFields(&child_block, call_src, ty);2704 // TODO: check whether any external comptime memory was mutated by the
2639 }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;
2642 } else if (func_ty_info.is_generic) res: {2735 } else if (func_ty_info.is_generic) res: {
2643 const func_val = try sema.resolveConstValue(block, func_src, func);2736 const func_val = try sema.resolveConstValue(block, func_src, func);
2644 const module_fn = func_val.castTag(.function).?.data;2737 const module_fn = func_val.castTag(.function).?.data;
...@@ -3305,31 +3398,9 @@ fn zirEnumToInt(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileE...@@ -3305,31 +3398,9 @@ fn zirEnumToInt(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileE
3305 }3398 }
33063399
3307 if (try sema.resolveMaybeUndefVal(block, operand_src, enum_tag)) |enum_tag_val| {3400 if (try sema.resolveMaybeUndefVal(block, operand_src, enum_tag)) |enum_tag_val| {
3308 if (enum_tag_val.castTag(.enum_field_index)) |enum_field_payload| {3401 var buffer: Value.Payload.U64 = undefined;
3309 const field_index = enum_field_payload.data;3402 const val = enum_tag_val.enumToInt(enum_tag_ty, &buffer);
3310 switch (enum_tag_ty.tag()) {3403 return sema.addConstant(int_tag_ty, try val.copy(sema.arena));
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 }
3333 }3404 }
33343405
3335 try sema.requireRuntimeBlock(block, src);3406 try sema.requireRuntimeBlock(block, src);
...@@ -3414,7 +3485,10 @@ fn zirOptionalPayloadPtr(...@@ -3414,7 +3485,10 @@ fn zirOptionalPayloadPtr(
3414 return sema.mod.fail(&block.base, src, "unable to unwrap null", .{});3485 return sema.mod.fail(&block.base, src, "unable to unwrap null", .{});
3415 }3486 }
3416 // The same Value represents the pointer to the optional and the payload.3487 // 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 );
3418 }3492 }
3419 }3493 }
34203494
...@@ -3451,7 +3525,8 @@ fn zirOptionalPayload(...@@ -3451,7 +3525,8 @@ fn zirOptionalPayload(
3451 if (val.isNull()) {3525 if (val.isNull()) {
3452 return sema.mod.fail(&block.base, src, "unable to unwrap null", .{});3526 return sema.mod.fail(&block.base, src, "unable to unwrap null", .{});
3453 }3527 }
3454 return sema.addConstant(child_type, val);3528 const sub_val = val.castTag(.opt_payload).?.data;
3529 return sema.addConstant(child_type, sub_val);
3455 }3530 }
34563531
3457 try sema.requireRuntimeBlock(block, src);3532 try sema.requireRuntimeBlock(block, src);
...@@ -9095,7 +9170,7 @@ fn wrapOptional(...@@ -9095,7 +9170,7 @@ fn wrapOptional(
9095 inst_src: LazySrcLoc,9170 inst_src: LazySrcLoc,
9096) !Air.Inst.Ref {9171) !Air.Inst.Ref {
9097 if (try sema.resolveMaybeUndefVal(block, inst_src, inst)) |val| {9172 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));
9099 }9174 }
91009175
9101 try sema.requireRuntimeBlock(block, inst_src);9176 try sema.requireRuntimeBlock(block, inst_src);
src/TypedValue.zig+12-3
...@@ -23,9 +23,18 @@ pub const Managed = struct {...@@ -23,9 +23,18 @@ pub const Managed = struct {
23};23};
2424
25/// Assumes arena allocation. Does a recursive copy.25/// 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 {
27 return TypedValue{27 return TypedValue{
28 .ty = try self.ty.copy(allocator),28 .ty = try self.ty.copy(arena),
29 .val = try self.val.copy(allocator),29 .val = try self.val.copy(arena),
30 };30 };
31}31}
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 {...@@ -319,18 +319,20 @@ pub const DeclGen = struct {
319 .Bool => return writer.print("{}", .{val.toBool()}),319 .Bool => return writer.print("{}", .{val.toBool()}),
320 .Optional => {320 .Optional => {
321 var opt_buf: Type.Payload.ElemType = undefined;321 var opt_buf: Type.Payload.ElemType = undefined;
322 const child_type = t.optionalChild(&opt_buf);322 const payload_type = t.optionalChild(&opt_buf);
323 if (t.isPtrLikeOptional()) {323 if (t.isPtrLikeOptional()) {
324 return dg.renderValue(writer, child_type, val);324 return dg.renderValue(writer, payload_type, val);
325 }325 }
326 try writer.writeByte('(');326 try writer.writeByte('(');
327 try dg.renderType(writer, t);327 try dg.renderType(writer, t);
328 if (val.tag() == .null_value) {328 try writer.writeAll("){");
329 try writer.writeAll("){ .is_null = true }");329 if (val.castTag(.opt_payload)) |pl| {
330 } else {330 const payload_val = pl.data;
331 try writer.writeAll("){ .is_null = false, .payload = ");331 try writer.writeAll(" .is_null = false, .payload = ");
332 try dg.renderValue(writer, child_type, val);332 try dg.renderValue(writer, payload_type, payload_val);
333 try writer.writeAll(" }");333 try writer.writeAll(" }");
334 } else {
335 try writer.writeAll(" .is_null = true }");
334 }336 }
335 },337 },
336 .ErrorSet => {338 .ErrorSet => {
src/codegen/llvm.zig+14-19
...@@ -810,27 +810,22 @@ pub const DeclGen = struct {...@@ -810,27 +810,22 @@ pub const DeclGen = struct {
810 return self.todo("handle more array values", .{});810 return self.todo("handle more array values", .{});
811 },811 },
812 .Optional => {812 .Optional => {
813 if (!tv.ty.isPtrLikeOptional()) {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 {
832 return self.todo("implement const of optional pointer", .{});814 return self.todo("implement const of optional pointer", .{});
833 }815 }
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);
834 },829 },
835 .Fn => {830 .Fn => {
836 const fn_decl = switch (tv.val.tag()) {831 const fn_decl = switch (tv.val.tag()) {
src/codegen/wasm.zig+6-5
...@@ -1198,7 +1198,12 @@ pub const Context = struct {...@@ -1198,7 +1198,12 @@ pub const Context = struct {
11981198
1199 // When constant has value 'null', set is_null local to '1'1199 // When constant has value 'null', set is_null local to '1'
1200 // and payload to '0'1200 // 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 {
1202 try writer.writeByte(wasm.opcode(.i32_const));1207 try writer.writeByte(wasm.opcode(.i32_const));
1203 try leb.writeILEB128(writer, @as(i32, 1));1208 try leb.writeILEB128(writer, @as(i32, 1));
12041209
...@@ -1208,10 +1213,6 @@ pub const Context = struct {...@@ -1208,10 +1213,6 @@ pub const Context = struct {
1208 });1213 });
1209 try writer.writeByte(wasm.opcode(opcode));1214 try writer.writeByte(wasm.opcode(opcode));
1210 try leb.writeULEB128(writer, @as(u32, 0));1215 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);
1215 }1216 }
1216 },1217 },
1217 else => |zig_type| return self.fail("Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}),1218 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 {...@@ -133,12 +133,21 @@ pub const Value = extern union {
133 /// When the type is error union:133 /// When the type is error union:
134 /// * If the tag is `.@"error"`, the error union is an error.134 /// * If the tag is `.@"error"`, the error union is an error.
135 /// * If the tag is `.eu_payload`, the error union is a payload.135 /// * 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 union136 /// * A nested error such as `anyerror!(anyerror!T)` in which the the outer error union
137 /// is non-error, but the inner error union is an error, is represented as137 /// is non-error, but the inner error union is an error, is represented as
138 /// a tag of `.eu_payload`, with a sub-tag of `.@"error"`.138 /// a tag of `.eu_payload`, with a sub-tag of `.@"error"`.
139 eu_payload,139 eu_payload,
140 /// A pointer to the payload of an error union, based on a pointer to an error union.140 /// A pointer to the payload of an error union, based on a pointer to an error union.
141 eu_payload_ptr,141 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,
142 /// An instance of a struct.151 /// An instance of a struct.
143 @"struct",152 @"struct",
144 /// An instance of a union.153 /// An instance of a union.
...@@ -238,6 +247,8 @@ pub const Value = extern union {...@@ -238,6 +247,8 @@ pub const Value = extern union {
238 .repeated,247 .repeated,
239 .eu_payload,248 .eu_payload,
240 .eu_payload_ptr,249 .eu_payload_ptr,
250 .opt_payload,
251 .opt_payload_ptr,
241 => Payload.SubValue,252 => Payload.SubValue,
242253
243 .bytes,254 .bytes,
...@@ -459,7 +470,12 @@ pub const Value = extern union {...@@ -459,7 +470,12 @@ pub const Value = extern union {
459 return Value{ .ptr_otherwise = &new_payload.base };470 return Value{ .ptr_otherwise = &new_payload.base };
460 },471 },
461 .bytes => return self.copyPayloadShallow(allocator, Payload.Bytes),472 .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 => {
463 const payload = self.cast(Payload.SubValue).?;479 const payload = self.cast(Payload.SubValue).?;
464 const new_payload = try allocator.create(Payload.SubValue);480 const new_payload = try allocator.create(Payload.SubValue);
465 new_payload.* = .{481 new_payload.* = .{
...@@ -656,12 +672,20 @@ pub const Value = extern union {...@@ -656,12 +672,20 @@ pub const Value = extern union {
656 try out_stream.writeAll("(eu_payload) ");672 try out_stream.writeAll("(eu_payload) ");
657 val = val.castTag(.eu_payload).?.data;673 val = val.castTag(.eu_payload).?.data;
658 },674 },
675 .opt_payload => {
676 try out_stream.writeAll("(opt_payload) ");
677 val = val.castTag(.opt_payload).?.data;
678 },
659 .inferred_alloc => return out_stream.writeAll("(inferred allocation value)"),679 .inferred_alloc => return out_stream.writeAll("(inferred allocation value)"),
660 .inferred_alloc_comptime => return out_stream.writeAll("(inferred comptime allocation value)"),680 .inferred_alloc_comptime => return out_stream.writeAll("(inferred comptime allocation value)"),
661 .eu_payload_ptr => {681 .eu_payload_ptr => {
662 try out_stream.writeAll("(eu_payload_ptr)");682 try out_stream.writeAll("(eu_payload_ptr)");
663 val = val.castTag(.eu_payload_ptr).?.data;683 val = val.castTag(.eu_payload_ptr).?.data;
664 },684 },
685 .opt_payload_ptr => {
686 try out_stream.writeAll("(opt_payload_ptr)");
687 val = val.castTag(.opt_payload_ptr).?.data;
688 },
665 };689 };
666 }690 }
667691
...@@ -776,6 +800,38 @@ pub const Value = extern union {...@@ -776,6 +800,38 @@ pub const Value = extern union {
776 }800 }
777 }801 }
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
779 /// Asserts the value is an integer.835 /// Asserts the value is an integer.
780 pub fn toBigInt(self: Value, space: *BigIntSpace) BigIntConst {836 pub fn toBigInt(self: Value, space: *BigIntSpace) BigIntConst {
781 switch (self.tag()) {837 switch (self.tag()) {
...@@ -1132,7 +1188,10 @@ pub const Value = extern union {...@@ -1132,7 +1188,10 @@ pub const Value = extern union {
1132 }1188 }
11331189
1134 pub fn hash(val: Value, ty: Type, hasher: *std.hash.Wyhash) void {1190 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) {
1136 .BoundFn => unreachable, // TODO remove this from the language1195 .BoundFn => unreachable, // TODO remove this from the language
11371196
1138 .Void,1197 .Void,
...@@ -1157,7 +1216,10 @@ pub const Value = extern union {...@@ -1157,7 +1216,10 @@ pub const Value = extern union {
1157 }1216 }
1158 },1217 },
1159 .Float, .ComptimeFloat => {1218 .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));
1161 },1223 },
1162 .Pointer => {1224 .Pointer => {
1163 @panic("TODO implement hashing pointer values");1225 @panic("TODO implement hashing pointer values");
...@@ -1169,7 +1231,15 @@ pub const Value = extern union {...@@ -1169,7 +1231,15 @@ pub const Value = extern union {
1169 @panic("TODO implement hashing struct values");1231 @panic("TODO implement hashing struct values");
1170 },1232 },
1171 .Optional => {1233 .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 }
1173 },1243 },
1174 .ErrorUnion => {1244 .ErrorUnion => {
1175 @panic("TODO implement hashing error union values");1245 @panic("TODO implement hashing error union values");
...@@ -1178,7 +1248,16 @@ pub const Value = extern union {...@@ -1178,7 +1248,16 @@ pub const Value = extern union {
1178 @panic("TODO implement hashing error set values");1248 @panic("TODO implement hashing error set values");
1179 },1249 },
1180 .Enum => {1250 .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 }
1182 },1261 },
1183 .Union => {1262 .Union => {
1184 @panic("TODO implement hashing union values");1263 @panic("TODO implement hashing union values");
...@@ -1257,6 +1336,11 @@ pub const Value = extern union {...@@ -1257,6 +1336,11 @@ pub const Value = extern union {
1257 const err_union_val = (try err_union_ptr.pointerDeref(allocator)) orelse return null;1336 const err_union_val = (try err_union_ptr.pointerDeref(allocator)) orelse return null;
1258 break :blk err_union_val.castTag(.eu_payload).?.data;1337 break :blk err_union_val.castTag(.eu_payload).?.data;
1259 },1338 },
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
1261 .zero,1345 .zero,
1262 .one,1346 .one,
...@@ -1354,13 +1438,14 @@ pub const Value = extern union {...@@ -1354,13 +1438,14 @@ pub const Value = extern union {
1354 /// Valid for all types. Asserts the value is not undefined and not unreachable.1438 /// Valid for all types. Asserts the value is not undefined and not unreachable.
1355 pub fn isNull(self: Value) bool {1439 pub fn isNull(self: Value) bool {
1356 return switch (self.tag()) {1440 return switch (self.tag()) {
1441 .null_value => true,
1442 .opt_payload => false,
1443
1357 .undef => unreachable,1444 .undef => unreachable,
1358 .unreachable_value => unreachable,1445 .unreachable_value => unreachable,
1359 .inferred_alloc => unreachable,1446 .inferred_alloc => unreachable,
1360 .inferred_alloc_comptime => unreachable,1447 .inferred_alloc_comptime => unreachable,
1361 .null_value => true,1448 else => unreachable,
1362
1363 else => false,
1364 };1449 };
1365 }1450 }
13661451
...@@ -1390,6 +1475,10 @@ pub const Value = extern union {...@@ -1390,6 +1475,10 @@ pub const Value = extern union {
1390 return switch (val.tag()) {1475 return switch (val.tag()) {
1391 .eu_payload => true,1476 .eu_payload => true,
1392 else => false,1477 else => false,
1478
1479 .undef => unreachable,
1480 .inferred_alloc => unreachable,
1481 .inferred_alloc_comptime => unreachable,
1393 };1482 };
1394 }1483 }
13951484
test/behavior/eval.zig+13
...@@ -148,3 +148,16 @@ const List = blk: {...@@ -148,3 +148,16 @@ const List = blk: {
148 array: T,148 array: T,
149 };149 };
150};150};
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" {...@@ -356,19 +356,6 @@ test "binary math operator in partially inlined function" {
356 try expect(s[3] == 0xd0e0f10);356 try expect(s[3] == 0xd0e0f10);
357}357}
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
372test "comptime function with mutable pointer is not memoized" {359test "comptime function with mutable pointer is not memoized" {
373 comptime {360 comptime {
374 var x: i32 = 1;361 var x: i32 = 1;