authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-08-19 07:50:57+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-08-21 01:26:55+01:00
log9cf8a7661f34e764f785583a3028429e2a700d20
tree44d19306bf88259cdb82f7b23eee976a2a6711a0
parent16d74809d44d6bb8db1a32923ef8db43d956e24d
signaturelock-open Commit is signed but in an unrecognized format.

compiler: handle eval branch quota in memoized calls

In a `memoized_call`, store how many backwards braches the call performs. Add this to `sema.branch_count` when using a memoized call. If this exceeds the quota, perform a non-memoized call to get a correct "exceeded X backwards branches" error. Also, do not memoize calls which do `@setEvalBranchQuota` or similar, as this affects global state which must apply to the caller. Change some eval branch quotas so that the compiler itself still builds correctly. This commit manually changes a file in Aro which is automatically generated. The sources which generate the file are not in this repo. Upstream Aro should make the suitable changes on their end before the next sync of Aro sources into the Zig repo.

6 files changed, 50 insertions(+), 11 deletions(-)

lib/compiler/aro/aro/Builtins/Builtin.zig+1-1
...@@ -5165,7 +5165,7 @@ const dafsa = [_]Node{...@@ -5165,7 +5165,7 @@ const dafsa = [_]Node{
5165 .{ .char = 'e', .end_of_word = false, .end_of_list = true, .number = 1, .child_index = 4913 },5165 .{ .char = 'e', .end_of_word = false, .end_of_list = true, .number = 1, .child_index = 4913 },
5166};5166};
5167pub const data = blk: {5167pub const data = blk: {
5168 @setEvalBranchQuota(3986);5168 @setEvalBranchQuota(30_000);
5169 break :blk [_]@This(){5169 break :blk [_]@This(){
5170 // _Block_object_assign5170 // _Block_object_assign
5171 .{ .tag = @enumFromInt(0), .properties = .{ .param_str = "vv*vC*iC", .header = .blocks, .attributes = .{ .lib_function_without_prefix = true } } },5171 .{ .tag = @enumFromInt(0), .properties = .{ .param_str = "vv*vC*iC", .header = .blocks, .attributes = .{ .lib_function_without_prefix = true } } },
lib/compiler/aro/aro/Parser.zig+2
...@@ -4802,6 +4802,7 @@ const CallExpr = union(enum) {...@@ -4802,6 +4802,7 @@ const CallExpr = union(enum) {
4802 }4802 }
48034803
4804 fn shouldPromoteVarArg(self: CallExpr, arg_idx: u32) bool {4804 fn shouldPromoteVarArg(self: CallExpr, arg_idx: u32) bool {
4805 @setEvalBranchQuota(2000);
4805 return switch (self) {4806 return switch (self) {
4806 .standard => true,4807 .standard => true,
4807 .builtin => |builtin| switch (builtin.tag) {4808 .builtin => |builtin| switch (builtin.tag) {
...@@ -4902,6 +4903,7 @@ const CallExpr = union(enum) {...@@ -4902,6 +4903,7 @@ const CallExpr = union(enum) {
4902 }4903 }
49034904
4904 fn returnType(self: CallExpr, p: *Parser, callable_ty: Type) Type {4905 fn returnType(self: CallExpr, p: *Parser, callable_ty: Type) Type {
4906 @setEvalBranchQuota(6000);
4905 return switch (self) {4907 return switch (self) {
4906 .standard => callable_ty.returnType(),4908 .standard => callable_ty.returnType(),
4907 .builtin => |builtin| switch (builtin.tag) {4909 .builtin => |builtin| switch (builtin.tag) {
lib/std/crypto/pcurves/p384.zig+1-1
...@@ -393,7 +393,7 @@ pub const P384 = struct {...@@ -393,7 +393,7 @@ pub const P384 = struct {
393 }393 }
394394
395 const basePointPc = pc: {395 const basePointPc = pc: {
396 @setEvalBranchQuota(50000);396 @setEvalBranchQuota(70000);
397 break :pc precompute(P384.basePoint, 15);397 break :pc precompute(P384.basePoint, 15);
398 };398 };
399399
src/InternPool.zig+4
...@@ -2391,6 +2391,7 @@ pub const Key = union(enum) {...@@ -2391,6 +2391,7 @@ pub const Key = union(enum) {
2391 func: Index,2391 func: Index,
2392 arg_values: []const Index,2392 arg_values: []const Index,
2393 result: Index,2393 result: Index,
2394 branch_count: u32,
2394 };2395 };
23952396
2396 pub fn hash32(key: Key, ip: *const InternPool) u32 {2397 pub fn hash32(key: Key, ip: *const InternPool) u32 {
...@@ -6157,6 +6158,7 @@ pub const MemoizedCall = struct {...@@ -6157,6 +6158,7 @@ pub const MemoizedCall = struct {
6157 func: Index,6158 func: Index,
6158 args_len: u32,6159 args_len: u32,
6159 result: Index,6160 result: Index,
6161 branch_count: u32,
6160};6162};
61616163
6162pub fn init(ip: *InternPool, gpa: Allocator, available_threads: usize) !void {6164pub fn init(ip: *InternPool, gpa: Allocator, available_threads: usize) !void {
...@@ -6785,6 +6787,7 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key {...@@ -6785,6 +6787,7 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key {
6785 .func = extra.data.func,6787 .func = extra.data.func,
6786 .arg_values = @ptrCast(extra_list.view().items(.@"0")[extra.end..][0..extra.data.args_len]),6788 .arg_values = @ptrCast(extra_list.view().items(.@"0")[extra.end..][0..extra.data.args_len]),
6787 .result = extra.data.result,6789 .result = extra.data.result,
6790 .branch_count = extra.data.branch_count,
6788 } };6791 } };
6789 },6792 },
6790 };6793 };
...@@ -7955,6 +7958,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All...@@ -7955,6 +7958,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All
7955 .func = memoized_call.func,7958 .func = memoized_call.func,
7956 .args_len = @intCast(memoized_call.arg_values.len),7959 .args_len = @intCast(memoized_call.arg_values.len),
7957 .result = memoized_call.result,7960 .result = memoized_call.result,
7961 .branch_count = memoized_call.branch_count,
7958 }),7962 }),
7959 });7963 });
7960 extra.appendSliceAssumeCapacity(.{@ptrCast(memoized_call.arg_values)});7964 extra.appendSliceAssumeCapacity(.{@ptrCast(memoized_call.arg_values)});
src/Sema.zig+40-9
...@@ -113,6 +113,11 @@ type_references: std.AutoArrayHashMapUnmanaged(InternPool.Index, void) = .{},...@@ -113,6 +113,11 @@ type_references: std.AutoArrayHashMapUnmanaged(InternPool.Index, void) = .{},
113/// `AnalUnit` multiple times.113/// `AnalUnit` multiple times.
114dependencies: std.AutoArrayHashMapUnmanaged(InternPool.Dependee, void) = .{},114dependencies: std.AutoArrayHashMapUnmanaged(InternPool.Dependee, void) = .{},
115115
116/// Whether memoization of this call is permitted. Operations with side effects global
117/// to the `Sema`, such as `@setEvalBranchQuota`, set this to `false`. It is observed
118/// by `analyzeCall`.
119allow_memoize: bool = true,
120
116const MaybeComptimeAlloc = struct {121const MaybeComptimeAlloc = struct {
117 /// The runtime index of the `alloc` instruction.122 /// The runtime index of the `alloc` instruction.
118 runtime_index: Value.RuntimeIndex,123 runtime_index: Value.RuntimeIndex,
...@@ -5524,6 +5529,7 @@ fn zirSetEvalBranchQuota(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Compi...@@ -5524,6 +5529,7 @@ fn zirSetEvalBranchQuota(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Compi
5524 .needed_comptime_reason = "eval branch quota must be comptime-known",5529 .needed_comptime_reason = "eval branch quota must be comptime-known",
5525 }));5530 }));
5526 sema.branch_quota = @max(sema.branch_quota, quota);5531 sema.branch_quota = @max(sema.branch_quota, quota);
5532 sema.allow_memoize = false;
5527}5533}
55285534
5529fn zirStoreNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {5535fn zirStoreNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
...@@ -6416,6 +6422,7 @@ fn zirSetAlignStack(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.Inst...@@ -6416,6 +6422,7 @@ fn zirSetAlignStack(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.Inst
6416 }6422 }
64176423
6418 zcu.intern_pool.funcMaxStackAlignment(sema.func_index, alignment);6424 zcu.intern_pool.funcMaxStackAlignment(sema.func_index, alignment);
6425 sema.allow_memoize = false;
6419}6426}
64206427
6421fn zirSetCold(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!void {6428fn zirSetCold(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!void {
...@@ -6434,6 +6441,7 @@ fn zirSetCold(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData)...@@ -6434,6 +6441,7 @@ fn zirSetCold(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData)
6434 .cau => return, // does nothing outside a function6441 .cau => return, // does nothing outside a function
6435 };6442 };
6436 ip.funcSetCold(func, is_cold);6443 ip.funcSetCold(func, is_cold);
6444 sema.allow_memoize = false;
6437}6445}
64386446
6439fn zirDisableInstrumentation(sema: *Sema) CompileError!void {6447fn zirDisableInstrumentation(sema: *Sema) CompileError!void {
...@@ -6445,6 +6453,7 @@ fn zirDisableInstrumentation(sema: *Sema) CompileError!void {...@@ -6445,6 +6453,7 @@ fn zirDisableInstrumentation(sema: *Sema) CompileError!void {
6445 .cau => return, // does nothing outside a function6453 .cau => return, // does nothing outside a function
6446 };6454 };
6447 ip.funcSetDisableInstrumentation(func);6455 ip.funcSetDisableInstrumentation(func);
6456 sema.allow_memoize = false;
6448}6457}
64496458
6450fn zirSetFloatMode(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!void {6459fn zirSetFloatMode(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!void {
...@@ -7728,15 +7737,25 @@ fn analyzeCall(...@@ -7728,15 +7737,25 @@ fn analyzeCall(
7728 // This `res2` is here instead of directly breaking from `res` due to a stage17737 // This `res2` is here instead of directly breaking from `res` due to a stage1
7729 // bug generating invalid LLVM IR.7738 // bug generating invalid LLVM IR.
7730 const res2: Air.Inst.Ref = res2: {7739 const res2: Air.Inst.Ref = res2: {
7731 if (should_memoize and is_comptime_call) {7740 memoize: {
7732 if (zcu.intern_pool.getIfExists(.{ .memoized_call = .{7741 if (!should_memoize) break :memoize;
7733 .func = module_fn_index,7742 if (!is_comptime_call) break :memoize;
7734 .arg_values = memoized_arg_values,7743 const memoized_call_index = ip.getIfExists(.{
7735 .result = .none,7744 .memoized_call = .{
7736 } })) |memoized_call_index| {7745 .func = module_fn_index,
7737 const memoized_call = zcu.intern_pool.indexToKey(memoized_call_index).memoized_call;7746 .arg_values = memoized_arg_values,
7738 break :res2 Air.internedToRef(memoized_call.result);7747 .result = undefined, // ignored by hash+eql
7748 .branch_count = undefined, // ignored by hash+eql
7749 },
7750 }) orelse break :memoize;
7751 const memoized_call = ip.indexToKey(memoized_call_index).memoized_call;
7752 if (sema.branch_count + memoized_call.branch_count > sema.branch_quota) {
7753 // Let the call play out se we get the correct source location for the
7754 // "evaluation exceeded X backwards branches" error.
7755 break :memoize;
7739 }7756 }
7757 sema.branch_count += memoized_call.branch_count;
7758 break :res2 Air.internedToRef(memoized_call.result);
7740 }7759 }
77417760
7742 new_fn_info.return_type = sema.fn_ret_ty.toIntern();7761 new_fn_info.return_type = sema.fn_ret_ty.toIntern();
...@@ -7774,6 +7793,17 @@ fn analyzeCall(...@@ -7774,6 +7793,17 @@ fn analyzeCall(
7774 child_block.error_return_trace_index = error_return_trace_index;7793 child_block.error_return_trace_index = error_return_trace_index;
7775 }7794 }
77767795
7796 // We temporarily set `allow_memoize` to `true` to track this comptime call.
7797 // It is restored after this call finishes analysis, so that a caller may
7798 // know whether an in-progress call (containing this call) may be memoized.
7799 const old_allow_memoize = sema.allow_memoize;
7800 defer sema.allow_memoize = old_allow_memoize and sema.allow_memoize;
7801 sema.allow_memoize = true;
7802
7803 // Store the current eval branch count so we can find out how many eval branches
7804 // the comptime call caused.
7805 const old_branch_count = sema.branch_count;
7806
7777 const result = result: {7807 const result = result: {
7778 sema.analyzeFnBody(&child_block, fn_info.body) catch |err| switch (err) {7808 sema.analyzeFnBody(&child_block, fn_info.body) catch |err| switch (err) {
7779 error.ComptimeReturn => break :result inlining.comptime_result,7809 error.ComptimeReturn => break :result inlining.comptime_result,
...@@ -7793,11 +7823,12 @@ fn analyzeCall(...@@ -7793,11 +7823,12 @@ fn analyzeCall(
7793 // a reference to `comptime_allocs` so is not stable across instances of `Sema`.7823 // a reference to `comptime_allocs` so is not stable across instances of `Sema`.
7794 // TODO: check whether any external comptime memory was mutated by the7824 // TODO: check whether any external comptime memory was mutated by the
7795 // comptime function call. If so, then do not memoize the call here.7825 // comptime function call. If so, then do not memoize the call here.
7796 if (should_memoize and !Value.fromInterned(result_interned).canMutateComptimeVarState(zcu)) {7826 if (should_memoize and sema.allow_memoize and !Value.fromInterned(result_interned).canMutateComptimeVarState(zcu)) {
7797 _ = try pt.intern(.{ .memoized_call = .{7827 _ = try pt.intern(.{ .memoized_call = .{
7798 .func = module_fn_index,7828 .func = module_fn_index,
7799 .arg_values = memoized_arg_values,7829 .arg_values = memoized_arg_values,
7800 .result = result_transformed,7830 .result = result_transformed,
7831 .branch_count = sema.branch_count - old_branch_count,
7801 } });7832 } });
7802 }7833 }
78037834
src/register_manager.zig+2
...@@ -93,6 +93,8 @@ pub fn RegisterManager(...@@ -93,6 +93,8 @@ pub fn RegisterManager(
93 comptime set: []const Register,93 comptime set: []const Register,
94 reg: Register,94 reg: Register,
95 ) ?std.math.IntFittingRange(0, set.len - 1) {95 ) ?std.math.IntFittingRange(0, set.len - 1) {
96 @setEvalBranchQuota(3000);
97
96 const Id = @TypeOf(reg.id());98 const Id = @TypeOf(reg.id());
97 comptime var min_id: Id = std.math.maxInt(Id);99 comptime var min_id: Id = std.math.maxInt(Id);
98 comptime var max_id: Id = std.math.minInt(Id);100 comptime var max_id: Id = std.math.minInt(Id);