From 2ba7eb155f71c94522d3e2a70a8dd3f0874c8420 Mon Sep 17 00:00:00 2001 From: g-w1 Date: Sun, 3 Jan 2021 15:45:22 -0500 Subject: [PATCH 1/3] stage2: implimentation of @setEvalBranchQuota: @setEvalBranchQuota can be run out of comptime function contexts so it must be in any block that can be run at comptime. For example: ``` @setEvalBranchQuota(100); comptime { while (true) {} } ``` requires it, but it can be in a non-comptime function call. A block will inherit it's parent's branch count/quota. --- src/Module.zig | 26 +++++++++++++------ src/astgen.zig | 11 ++++++++ src/zir.zig | 4 +++ src/zir_sema.zig | 35 +++++++++++++++++++++++--- test/stage2/test.zig | 60 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 125 insertions(+), 11 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index 24ea48043b8d..5691d6c168a7 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -766,6 +766,8 @@ pub const Scope = struct { inlining: ?*Inlining, is_comptime: bool, + branch_count: u64, + branch_quota: u64, pub const InstTable = std.AutoHashMap(*zir.Inst, *Inst); /// This `Block` maps a block ZIR instruction to the corresponding @@ -792,8 +794,6 @@ pub const Scope = struct { pub const Shared = struct { caller: ?*Fn, - branch_count: u64, - branch_quota: u64, }; }; @@ -1113,6 +1113,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .arena = &decl_arena.allocator, .inlining = null, .is_comptime = false, + .branch_count = 0, + .branch_quota = 1000, }; defer block_scope.instructions.deinit(self.gpa); @@ -1306,6 +1308,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .arena = &decl_arena.allocator, .inlining = null, .is_comptime = true, + .branch_count = 0, + .branch_quota = 1000, }; defer block_scope.instructions.deinit(self.gpa); @@ -1376,6 +1380,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .arena = &gen_scope_arena.allocator, .inlining = null, .is_comptime = true, + .branch_count = 0, + .branch_quota = 1000, }; defer inner_block.instructions.deinit(self.gpa); try zir_sema.analyzeBody(self, &inner_block, .{ @@ -1503,6 +1509,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .arena = &analysis_arena.allocator, .inlining = null, .is_comptime = true, + .branch_count = 0, + .branch_quota = 1000, }; defer block_scope.instructions.deinit(self.gpa); @@ -1884,6 +1892,8 @@ pub fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { .arena = &arena.allocator, .inlining = null, .is_comptime = false, + .branch_count = 0, + .branch_quota = 1000, }; defer inner_block.instructions.deinit(self.gpa); @@ -3466,7 +3476,10 @@ pub fn addSafetyCheck(mod: *Module, parent_block: *Scope.Block, ok: *Inst, panic .arena = parent_block.arena, .inlining = parent_block.inlining, .is_comptime = parent_block.is_comptime, + .branch_count = parent_block.branch_count, + .branch_quota = parent_block.branch_quota, }; + defer fail_block.instructions.deinit(mod.gpa); _ = try mod.safetyPanic(&fail_block, ok.src, panic_id); @@ -3529,13 +3542,12 @@ pub fn identifierTokenString(mod: *Module, scope: *Scope, token: ast.TokenIndex) return ident_name; } -pub fn emitBackwardBranch(mod: *Module, block: *Scope.Block, src: usize) !void { - const shared = block.inlining.?.shared; - shared.branch_count += 1; - if (shared.branch_count > shared.branch_quota) { +pub fn emitBackwardBranch(mod: *Module, block: *Scope.Block, number: u64, src: usize) !void { + block.branch_count += number; + if (block.branch_count > block.branch_quota) { // TODO show the "called from here" stack return mod.fail(&block.base, src, "evaluation exceeded {d} backwards branches", .{ - shared.branch_quota, + block.branch_quota, }); } } diff --git a/src/astgen.zig b/src/astgen.zig index 7e4e9e227146..ba61ee6c2267 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -2317,6 +2317,15 @@ fn compileError(mod: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) InnerE return addZIRUnOp(mod, scope, src, .compileerror, target); } +fn setEvalBranchQuota(mod: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst { + try ensureBuiltinParamCount(mod, scope, call, 1); + const tree = scope.tree(); + const src = tree.token_locs[call.builtin_token].start; + const params = call.params(); + const target = try expr(mod, scope, .none, params[0]); + return addZIRUnOp(mod, scope, src, .setevalbranchquota, target); +} + fn typeOf(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst { const tree = scope.tree(); const arena = scope.arena(); @@ -2362,6 +2371,8 @@ fn builtinCall(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.Built return rlWrap(mod, scope, rl, try import(mod, scope, call)); } else if (mem.eql(u8, builtin_name, "@compileError")) { return compileError(mod, scope, call); + } else if (mem.eql(u8, builtin_name, "@setEvalBranchQuota")) { + return setEvalBranchQuota(mod, scope, call); } else { return mod.failTok(scope, call.builtin_token, "invalid builtin function: '{s}'", .{builtin_name}); } diff --git a/src/zir.zig b/src/zir.zig index 3fd2ac7c801b..25984b665f81 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -127,6 +127,8 @@ pub const Inst = struct { coerce_to_ptr_elem, /// Emit an error message and fail compilation. compileerror, + /// Changes the maximum number of backwards branches that compile-time code execution can use before giving up and making a compile error. + setevalbranchquota, /// Conditional branch. Splits control flow based on a boolean condition value. condbr, /// Special case, has no textual representation. @@ -347,6 +349,7 @@ pub const Inst = struct { .anyframe_type, .bitnot, .import, + .setevalbranchquota, => UnOp, .add, @@ -535,6 +538,7 @@ pub const Inst = struct { .switch_range, .typeof_peer, .resolve_inferred_alloc, + .setevalbranchquota, => false, .@"break", diff --git a/src/zir_sema.zig b/src/zir_sema.zig index a5627933e1b7..b903a4d7338e 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -81,6 +81,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .mut_slice_type => return analyzeInstSimplePtrType(mod, scope, old_inst.castTag(.mut_slice_type).?, true, .Slice), .ptr_type => return analyzeInstPtrType(mod, scope, old_inst.castTag(.ptr_type).?), .store => return analyzeInstStore(mod, scope, old_inst.castTag(.store).?), + .setevalbranchquota => return analyzeInstSetEvalBranchQuota(mod, scope, old_inst.castTag(.setevalbranchquota).?), .str => return analyzeInstStr(mod, scope, old_inst.castTag(.str).?), .int => { const big_int = old_inst.castTag(.int).?.positionals.int; @@ -486,6 +487,17 @@ fn analyzeInstStoreToInferredPtr( return mod.storePtr(scope, inst.base.src, bitcasted_ptr, value); } +fn analyzeInstSetEvalBranchQuota( + mod: *Module, + scope: *Scope, + inst: *zir.Inst.UnOp, +) InnerError!*Inst { + const b = try mod.requireFunctionBlock(scope, inst.base.src); + const quota = try resolveInt(mod, scope, inst.positionals.operand, Type.initTag(.u32)); + b.branch_quota = quota; + return mod.constVoid(scope, inst.base.src); +} + fn analyzeInstStore(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { const ptr = try resolveInst(mod, scope, inst.positionals.lhs); const value = try resolveInst(mod, scope, inst.positionals.rhs); @@ -594,6 +606,8 @@ fn analyzeInstLoop(mod: *Module, scope: *Scope, inst: *zir.Inst.Loop) InnerError .arena = parent_block.arena, .inlining = parent_block.inlining, .is_comptime = parent_block.is_comptime, + .branch_count = parent_block.branch_count, + .branch_quota = parent_block.branch_quota, }; defer child_block.instructions.deinit(mod.gpa); @@ -619,6 +633,8 @@ fn analyzeInstBlockFlat(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_c .label = null, .inlining = parent_block.inlining, .is_comptime = parent_block.is_comptime or is_comptime, + .branch_count = parent_block.branch_count, + .branch_quota = parent_block.branch_quota, }; defer child_block.instructions.deinit(mod.gpa); @@ -666,6 +682,8 @@ fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_compt }), .inlining = parent_block.inlining, .is_comptime = is_comptime or parent_block.is_comptime, + .branch_count = parent_block.branch_count, + .branch_quota = parent_block.branch_quota, }; const merges = &child_block.label.?.merges; @@ -866,8 +884,6 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError // If this is the top of the inline/comptime call stack, we use this data. // Otherwise we pass on the shared data from the parent scope. var shared_inlining = Scope.Block.Inlining.Shared{ - .branch_count = 0, - .branch_quota = 1000, .caller = b.func, }; // This one is shared among sub-blocks within the same callee, but not @@ -896,18 +912,23 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError .label = null, .inlining = &inlining, .is_comptime = is_comptime_call, + .branch_count = b.branch_count, + .branch_quota = b.branch_quota, }; + const merges = &child_block.inlining.?.merges; defer child_block.instructions.deinit(mod.gpa); defer merges.results.deinit(mod.gpa); - try mod.emitBackwardBranch(&child_block, inst.base.src); - // This will have return instructions analyzed as break instructions to // the block_inst above. try analyzeBody(mod, &child_block, module_fn.zir); + // bubble up how many backwards branches the child used + // + 1 because calling the child is 1 + try mod.emitBackwardBranch(b, child_block.branch_count - b.branch_count + 1, inst.base.src); + const result = try analyzeBlockBody(mod, scope, &child_block, merges); if (result.castTag(.constant)) |constant| { log.debug("inline call resulted in {}", .{constant.val}); @@ -1417,6 +1438,8 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In .arena = parent_block.arena, .inlining = parent_block.inlining, .is_comptime = parent_block.is_comptime, + .branch_count = parent_block.branch_count, + .branch_quota = parent_block.branch_quota, }; defer case_block.instructions.deinit(mod.gpa); @@ -1960,6 +1983,8 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE .arena = parent_block.arena, .inlining = parent_block.inlining, .is_comptime = parent_block.is_comptime, + .branch_count = parent_block.branch_count, + .branch_quota = parent_block.branch_quota, }; defer true_block.instructions.deinit(mod.gpa); try analyzeBody(mod, &true_block, inst.positionals.then_body); @@ -1973,6 +1998,8 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE .arena = parent_block.arena, .inlining = parent_block.inlining, .is_comptime = parent_block.is_comptime, + .branch_count = parent_block.branch_count, + .branch_quota = parent_block.branch_quota, }; defer false_block.instructions.deinit(mod.gpa); try analyzeBody(mod, &false_block, inst.positionals.else_body); diff --git a/test/stage2/test.zig b/test/stage2/test.zig index 6e25dc283b59..0c3f275728ab 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -1477,4 +1477,64 @@ pub fn addCases(ctx: *TestContext) !void { \\} , &[_][]const u8{":8:10: error: evaluation exceeded 1000 backwards branches"}); } + { + var case = ctx.exe("@setEvalBranchQuota", linux_x64); + case.addCompareOutput( + \\export fn _start() noreturn { + \\ @setEvalBranchQuota(100000); + \\ const y = fibonacci(18); + \\ assert(y == 4181); + \\ exit(); + \\} + \\ + \\inline fn fibonacci(n: usize) usize { + \\ if (n <= 2) return n; + \\ return fibonacci(n - 2) + fibonacci(n - 1); + \\} + \\ + \\fn assert(b: bool) void { + \\ if (!b) unreachable; + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (0) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , + "", + ); + + case.addError( + \\export fn _start() noreturn { + \\ @setEvalBranchQuota(10); + \\ const y = fibonacci(18); + \\ assert(y == 4181); + \\ exit(); + \\} + \\ + \\inline fn fibonacci(n: usize) usize { + \\ if (n <= 2) return n; + \\ return fibonacci(n - 2) + fibonacci(n - 1); + \\} + \\ + \\fn assert(b: bool) void { + \\ if (!b) unreachable; + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (0) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , &[_][]const u8{":10:35: error: evaluation exceeded 10 backwards branches"}); + } } From d72225549f6cfb9b20adebb9a48ce7eff7dad83c Mon Sep 17 00:00:00 2001 From: g-w1 Date: Sun, 3 Jan 2021 21:00:07 -0500 Subject: [PATCH 2/3] add shared so that you can do this ``` fn foo() { foo(); } ``` --- src/Module.zig | 58 +++++++++++++++++++++++++++++--------------- src/zir_sema.zig | 33 +++++++++---------------- test/stage2/test.zig | 14 +++-------- 3 files changed, 54 insertions(+), 51 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index 5691d6c168a7..dfdb92abe68a 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -766,8 +766,13 @@ pub const Scope = struct { inlining: ?*Inlining, is_comptime: bool, - branch_count: u64, - branch_quota: u64, + shared: *Shared, + + const Shared = struct { + branch_count: u32 = 0, + branch_quota: u32 = 1000, + }; + pub const InstTable = std.AutoHashMap(*zir.Inst, *Inst); /// This `Block` maps a block ZIR instruction to the corresponding @@ -784,7 +789,7 @@ pub const Scope = struct { /// function. pub const Inlining = struct { /// Shared state among the entire inline/comptime call stack. - shared: *Shared, + shared: *IShared, /// We use this to count from 0 so that arg instructions know /// which parameter index they are, without having to store /// a parameter index with each arg instruction. @@ -792,7 +797,7 @@ pub const Scope = struct { casted_args: []*Inst, merges: Merges, - pub const Shared = struct { + pub const IShared = struct { caller: ?*Fn, }; }; @@ -1104,6 +1109,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { var inst_table = Scope.Block.InstTable.init(self.gpa); defer inst_table.deinit(); + var shared = try self.gpa.create(Scope.Block.Shared); + defer self.gpa.destroy(shared); + shared.* = .{}; + var block_scope: Scope.Block = .{ .parent = null, .inst_table = &inst_table, @@ -1113,8 +1122,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .arena = &decl_arena.allocator, .inlining = null, .is_comptime = false, - .branch_count = 0, - .branch_quota = 1000, + .shared = shared, }; defer block_scope.instructions.deinit(self.gpa); @@ -1299,6 +1307,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { var decl_inst_table = Scope.Block.InstTable.init(self.gpa); defer decl_inst_table.deinit(); + var shared = try self.gpa.create(Scope.Block.Shared); + defer self.gpa.destroy(shared); + shared.* = .{}; + var block_scope: Scope.Block = .{ .parent = null, .inst_table = &decl_inst_table, @@ -1308,8 +1320,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .arena = &decl_arena.allocator, .inlining = null, .is_comptime = true, - .branch_count = 0, - .branch_quota = 1000, + .shared = shared, }; defer block_scope.instructions.deinit(self.gpa); @@ -1371,6 +1382,9 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { var var_inst_table = Scope.Block.InstTable.init(self.gpa); defer var_inst_table.deinit(); + var shared_vi = try self.gpa.create(Scope.Block.Shared); + defer self.gpa.destroy(shared_vi); + shared_vi.* = .{}; var inner_block: Scope.Block = .{ .parent = null, .inst_table = &var_inst_table, @@ -1380,8 +1394,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .arena = &gen_scope_arena.allocator, .inlining = null, .is_comptime = true, - .branch_count = 0, - .branch_quota = 1000, + .shared = shared_vi, }; defer inner_block.instructions.deinit(self.gpa); try zir_sema.analyzeBody(self, &inner_block, .{ @@ -1500,6 +1513,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { var inst_table = Scope.Block.InstTable.init(self.gpa); defer inst_table.deinit(); + var shared = try self.gpa.create(Scope.Block.Shared); + defer self.gpa.destroy(shared); + shared.* = .{}; + var block_scope: Scope.Block = .{ .parent = null, .inst_table = &inst_table, @@ -1509,8 +1526,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .arena = &analysis_arena.allocator, .inlining = null, .is_comptime = true, - .branch_count = 0, - .branch_quota = 1000, + .shared = shared, }; defer block_scope.instructions.deinit(self.gpa); @@ -1883,6 +1899,10 @@ pub fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { defer decl.typed_value.most_recent.arena.?.* = arena.state; var inst_table = Scope.Block.InstTable.init(self.gpa); defer inst_table.deinit(); + var shared = try self.gpa.create(Scope.Block.Shared); + defer self.gpa.destroy(shared); + shared.* = .{}; + var inner_block: Scope.Block = .{ .parent = null, .inst_table = &inst_table, @@ -1892,8 +1912,7 @@ pub fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { .arena = &arena.allocator, .inlining = null, .is_comptime = false, - .branch_count = 0, - .branch_quota = 1000, + .shared = shared, }; defer inner_block.instructions.deinit(self.gpa); @@ -3476,8 +3495,7 @@ pub fn addSafetyCheck(mod: *Module, parent_block: *Scope.Block, ok: *Inst, panic .arena = parent_block.arena, .inlining = parent_block.inlining, .is_comptime = parent_block.is_comptime, - .branch_count = parent_block.branch_count, - .branch_quota = parent_block.branch_quota, + .shared = parent_block.shared, }; defer fail_block.instructions.deinit(mod.gpa); @@ -3542,12 +3560,12 @@ pub fn identifierTokenString(mod: *Module, scope: *Scope, token: ast.TokenIndex) return ident_name; } -pub fn emitBackwardBranch(mod: *Module, block: *Scope.Block, number: u64, src: usize) !void { - block.branch_count += number; - if (block.branch_count > block.branch_quota) { +pub fn emitBackwardBranch(mod: *Module, block: *Scope.Block, src: usize) !void { + block.shared.branch_count += 1; + if (block.shared.branch_count > block.shared.branch_quota) { // TODO show the "called from here" stack return mod.fail(&block.base, src, "evaluation exceeded {d} backwards branches", .{ - block.branch_quota, + block.shared.branch_quota, }); } } diff --git a/src/zir_sema.zig b/src/zir_sema.zig index b903a4d7338e..9c51da0f686a 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -493,8 +493,8 @@ fn analyzeInstSetEvalBranchQuota( inst: *zir.Inst.UnOp, ) InnerError!*Inst { const b = try mod.requireFunctionBlock(scope, inst.base.src); - const quota = try resolveInt(mod, scope, inst.positionals.operand, Type.initTag(.u32)); - b.branch_quota = quota; + const quota = @truncate(u32, try resolveInt(mod, scope, inst.positionals.operand, Type.initTag(.u32))); + b.shared.branch_quota = quota; return mod.constVoid(scope, inst.base.src); } @@ -606,8 +606,7 @@ fn analyzeInstLoop(mod: *Module, scope: *Scope, inst: *zir.Inst.Loop) InnerError .arena = parent_block.arena, .inlining = parent_block.inlining, .is_comptime = parent_block.is_comptime, - .branch_count = parent_block.branch_count, - .branch_quota = parent_block.branch_quota, + .shared = parent_block.shared, }; defer child_block.instructions.deinit(mod.gpa); @@ -633,8 +632,7 @@ fn analyzeInstBlockFlat(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_c .label = null, .inlining = parent_block.inlining, .is_comptime = parent_block.is_comptime or is_comptime, - .branch_count = parent_block.branch_count, - .branch_quota = parent_block.branch_quota, + .shared = parent_block.shared, }; defer child_block.instructions.deinit(mod.gpa); @@ -682,8 +680,7 @@ fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_compt }), .inlining = parent_block.inlining, .is_comptime = is_comptime or parent_block.is_comptime, - .branch_count = parent_block.branch_count, - .branch_quota = parent_block.branch_quota, + .shared = parent_block.shared, }; const merges = &child_block.label.?.merges; @@ -883,7 +880,7 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError }; // If this is the top of the inline/comptime call stack, we use this data. // Otherwise we pass on the shared data from the parent scope. - var shared_inlining = Scope.Block.Inlining.Shared{ + var shared_inlining = Scope.Block.Inlining.IShared{ .caller = b.func, }; // This one is shared among sub-blocks within the same callee, but not @@ -912,8 +909,7 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError .label = null, .inlining = &inlining, .is_comptime = is_comptime_call, - .branch_count = b.branch_count, - .branch_quota = b.branch_quota, + .shared = b.shared, }; const merges = &child_block.inlining.?.merges; @@ -921,14 +917,12 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError defer child_block.instructions.deinit(mod.gpa); defer merges.results.deinit(mod.gpa); + try mod.emitBackwardBranch(&child_block, inst.base.src); + // This will have return instructions analyzed as break instructions to // the block_inst above. try analyzeBody(mod, &child_block, module_fn.zir); - // bubble up how many backwards branches the child used - // + 1 because calling the child is 1 - try mod.emitBackwardBranch(b, child_block.branch_count - b.branch_count + 1, inst.base.src); - const result = try analyzeBlockBody(mod, scope, &child_block, merges); if (result.castTag(.constant)) |constant| { log.debug("inline call resulted in {}", .{constant.val}); @@ -1438,8 +1432,7 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In .arena = parent_block.arena, .inlining = parent_block.inlining, .is_comptime = parent_block.is_comptime, - .branch_count = parent_block.branch_count, - .branch_quota = parent_block.branch_quota, + .shared = parent_block.shared, }; defer case_block.instructions.deinit(mod.gpa); @@ -1983,8 +1976,7 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE .arena = parent_block.arena, .inlining = parent_block.inlining, .is_comptime = parent_block.is_comptime, - .branch_count = parent_block.branch_count, - .branch_quota = parent_block.branch_quota, + .shared = parent_block.shared, }; defer true_block.instructions.deinit(mod.gpa); try analyzeBody(mod, &true_block, inst.positionals.then_body); @@ -1998,8 +1990,7 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE .arena = parent_block.arena, .inlining = parent_block.inlining, .is_comptime = parent_block.is_comptime, - .branch_count = parent_block.branch_count, - .branch_quota = parent_block.branch_quota, + .shared = parent_block.shared, }; defer false_block.instructions.deinit(mod.gpa); try analyzeBody(mod, &false_block, inst.positionals.else_body); diff --git a/test/stage2/test.zig b/test/stage2/test.zig index 0c3f275728ab..7249cf025312 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -1512,18 +1512,12 @@ pub fn addCases(ctx: *TestContext) !void { case.addError( \\export fn _start() noreturn { \\ @setEvalBranchQuota(10); - \\ const y = fibonacci(18); - \\ assert(y == 4181); + \\ const y = rec(18); \\ exit(); \\} \\ - \\inline fn fibonacci(n: usize) usize { - \\ if (n <= 2) return n; - \\ return fibonacci(n - 2) + fibonacci(n - 1); - \\} - \\ - \\fn assert(b: bool) void { - \\ if (!b) unreachable; + \\inline fn rec(n: usize) usize { + \\ return rec(n); \\} \\ \\fn exit() noreturn { @@ -1535,6 +1529,6 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); \\ unreachable; \\} - , &[_][]const u8{":10:35: error: evaluation exceeded 10 backwards branches"}); + , &[_][]const u8{":8:12: error: evaluation exceeded 10 backwards branches"}); } } From 87b789c8fd1a11f6d1fe62516bd6af9e5395362f Mon Sep 17 00:00:00 2001 From: g-w1 Date: Sun, 3 Jan 2021 23:29:07 -0500 Subject: [PATCH 3/3] update changes from @andrewrk's review --- src/Module.zig | 53 ++++++++++++++++--------------------------- src/astgen.zig | 6 ++++- src/zir_sema.zig | 20 ++++++++-------- test/stage2/cbe.zig | 15 ++++++++++++ test/stage2/test.zig | 54 -------------------------------------------- 5 files changed, 51 insertions(+), 97 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index dfdb92abe68a..ce4fd51bb9c4 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -765,13 +765,8 @@ pub const Scope = struct { label: ?Label = null, inlining: ?*Inlining, is_comptime: bool, - - shared: *Shared, - - const Shared = struct { - branch_count: u32 = 0, - branch_quota: u32 = 1000, - }; + /// Shared to sub-blocks. + branch_quota: *u32, pub const InstTable = std.AutoHashMap(*zir.Inst, *Inst); @@ -789,7 +784,7 @@ pub const Scope = struct { /// function. pub const Inlining = struct { /// Shared state among the entire inline/comptime call stack. - shared: *IShared, + shared: *Shared, /// We use this to count from 0 so that arg instructions know /// which parameter index they are, without having to store /// a parameter index with each arg instruction. @@ -797,8 +792,9 @@ pub const Scope = struct { casted_args: []*Inst, merges: Merges, - pub const IShared = struct { + pub const Shared = struct { caller: ?*Fn, + branch_count: u32, }; }; @@ -1109,9 +1105,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { var inst_table = Scope.Block.InstTable.init(self.gpa); defer inst_table.deinit(); - var shared = try self.gpa.create(Scope.Block.Shared); - defer self.gpa.destroy(shared); - shared.* = .{}; + var branch_quota: u32 = 1000; var block_scope: Scope.Block = .{ .parent = null, @@ -1122,7 +1116,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .arena = &decl_arena.allocator, .inlining = null, .is_comptime = false, - .shared = shared, + .branch_quota = &branch_quota, }; defer block_scope.instructions.deinit(self.gpa); @@ -1307,9 +1301,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { var decl_inst_table = Scope.Block.InstTable.init(self.gpa); defer decl_inst_table.deinit(); - var shared = try self.gpa.create(Scope.Block.Shared); - defer self.gpa.destroy(shared); - shared.* = .{}; + var branch_quota: u32 = 1000; var block_scope: Scope.Block = .{ .parent = null, @@ -1320,7 +1312,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .arena = &decl_arena.allocator, .inlining = null, .is_comptime = true, - .shared = shared, + .branch_quota = &branch_quota, }; defer block_scope.instructions.deinit(self.gpa); @@ -1382,9 +1374,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { var var_inst_table = Scope.Block.InstTable.init(self.gpa); defer var_inst_table.deinit(); - var shared_vi = try self.gpa.create(Scope.Block.Shared); - defer self.gpa.destroy(shared_vi); - shared_vi.* = .{}; + var branch_quota_vi: u32 = 1000; var inner_block: Scope.Block = .{ .parent = null, .inst_table = &var_inst_table, @@ -1394,7 +1384,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .arena = &gen_scope_arena.allocator, .inlining = null, .is_comptime = true, - .shared = shared_vi, + .branch_quota = &branch_quota_vi, }; defer inner_block.instructions.deinit(self.gpa); try zir_sema.analyzeBody(self, &inner_block, .{ @@ -1513,9 +1503,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { var inst_table = Scope.Block.InstTable.init(self.gpa); defer inst_table.deinit(); - var shared = try self.gpa.create(Scope.Block.Shared); - defer self.gpa.destroy(shared); - shared.* = .{}; + var branch_quota: u32 = 1000; var block_scope: Scope.Block = .{ .parent = null, @@ -1526,7 +1514,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .arena = &analysis_arena.allocator, .inlining = null, .is_comptime = true, - .shared = shared, + .branch_quota = &branch_quota, }; defer block_scope.instructions.deinit(self.gpa); @@ -1899,9 +1887,7 @@ pub fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { defer decl.typed_value.most_recent.arena.?.* = arena.state; var inst_table = Scope.Block.InstTable.init(self.gpa); defer inst_table.deinit(); - var shared = try self.gpa.create(Scope.Block.Shared); - defer self.gpa.destroy(shared); - shared.* = .{}; + var branch_quota: u32 = 1000; var inner_block: Scope.Block = .{ .parent = null, @@ -1912,7 +1898,7 @@ pub fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { .arena = &arena.allocator, .inlining = null, .is_comptime = false, - .shared = shared, + .branch_quota = &branch_quota, }; defer inner_block.instructions.deinit(self.gpa); @@ -3495,7 +3481,7 @@ pub fn addSafetyCheck(mod: *Module, parent_block: *Scope.Block, ok: *Inst, panic .arena = parent_block.arena, .inlining = parent_block.inlining, .is_comptime = parent_block.is_comptime, - .shared = parent_block.shared, + .branch_quota = parent_block.branch_quota, }; defer fail_block.instructions.deinit(mod.gpa); @@ -3561,11 +3547,12 @@ pub fn identifierTokenString(mod: *Module, scope: *Scope, token: ast.TokenIndex) } pub fn emitBackwardBranch(mod: *Module, block: *Scope.Block, src: usize) !void { - block.shared.branch_count += 1; - if (block.shared.branch_count > block.shared.branch_quota) { + const shared = block.inlining.?.shared; + shared.branch_count += 1; + if (shared.branch_count > block.branch_quota.*) { // TODO show the "called from here" stack return mod.fail(&block.base, src, "evaluation exceeded {d} backwards branches", .{ - block.shared.branch_quota, + block.branch_quota.*, }); } } diff --git a/src/astgen.zig b/src/astgen.zig index ba61ee6c2267..a24470c304b8 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -2323,7 +2323,11 @@ fn setEvalBranchQuota(mod: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) const src = tree.token_locs[call.builtin_token].start; const params = call.params(); const target = try expr(mod, scope, .none, params[0]); - return addZIRUnOp(mod, scope, src, .setevalbranchquota, target); + const u32_type = try addZIRInstConst(mod, scope, src, .{ + .ty = Type.initTag(.type), + .val = Value.initTag(.u32_type), + }); + return addZIRUnOp(mod, scope, src, .setevalbranchquota, try rlWrap(mod, scope, .{ .ty = u32_type }, target)); } fn typeOf(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst { diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 9c51da0f686a..605dc7dcf4df 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -494,7 +494,8 @@ fn analyzeInstSetEvalBranchQuota( ) InnerError!*Inst { const b = try mod.requireFunctionBlock(scope, inst.base.src); const quota = @truncate(u32, try resolveInt(mod, scope, inst.positionals.operand, Type.initTag(.u32))); - b.shared.branch_quota = quota; + if (b.branch_quota.* < quota) + b.branch_quota.* = quota; return mod.constVoid(scope, inst.base.src); } @@ -606,7 +607,7 @@ fn analyzeInstLoop(mod: *Module, scope: *Scope, inst: *zir.Inst.Loop) InnerError .arena = parent_block.arena, .inlining = parent_block.inlining, .is_comptime = parent_block.is_comptime, - .shared = parent_block.shared, + .branch_quota = parent_block.branch_quota, }; defer child_block.instructions.deinit(mod.gpa); @@ -632,7 +633,7 @@ fn analyzeInstBlockFlat(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_c .label = null, .inlining = parent_block.inlining, .is_comptime = parent_block.is_comptime or is_comptime, - .shared = parent_block.shared, + .branch_quota = parent_block.branch_quota, }; defer child_block.instructions.deinit(mod.gpa); @@ -680,7 +681,7 @@ fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_compt }), .inlining = parent_block.inlining, .is_comptime = is_comptime or parent_block.is_comptime, - .shared = parent_block.shared, + .branch_quota = parent_block.branch_quota, }; const merges = &child_block.label.?.merges; @@ -880,7 +881,8 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError }; // If this is the top of the inline/comptime call stack, we use this data. // Otherwise we pass on the shared data from the parent scope. - var shared_inlining = Scope.Block.Inlining.IShared{ + var shared_inlining = Scope.Block.Inlining.Shared{ + .branch_count = 0, .caller = b.func, }; // This one is shared among sub-blocks within the same callee, but not @@ -909,7 +911,7 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError .label = null, .inlining = &inlining, .is_comptime = is_comptime_call, - .shared = b.shared, + .branch_quota = b.branch_quota, }; const merges = &child_block.inlining.?.merges; @@ -1432,7 +1434,7 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In .arena = parent_block.arena, .inlining = parent_block.inlining, .is_comptime = parent_block.is_comptime, - .shared = parent_block.shared, + .branch_quota = parent_block.branch_quota, }; defer case_block.instructions.deinit(mod.gpa); @@ -1976,7 +1978,7 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE .arena = parent_block.arena, .inlining = parent_block.inlining, .is_comptime = parent_block.is_comptime, - .shared = parent_block.shared, + .branch_quota = parent_block.branch_quota, }; defer true_block.instructions.deinit(mod.gpa); try analyzeBody(mod, &true_block, inst.positionals.then_body); @@ -1990,7 +1992,7 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE .arena = parent_block.arena, .inlining = parent_block.inlining, .is_comptime = parent_block.is_comptime, - .shared = parent_block.shared, + .branch_quota = parent_block.branch_quota, }; defer false_block.instructions.deinit(mod.gpa); try analyzeBody(mod, &false_block, inst.positionals.else_body); diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index a0a4587983fd..b227d6a7839b 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -67,7 +67,22 @@ pub fn addCases(ctx: *TestContext) !void { \\} , ""); } + { + var case = ctx.exeFromCompiledC("@setEvalBranchQuota", .{}); + case.addCompareOutput( + \\export fn main() i32 { + \\ @setEvalBranchQuota(1001); + \\ const y = rec(1001); + \\ return y - 1; + \\} + \\ + \\inline fn rec(n: usize) usize { + \\ if (n <= 1) return n; + \\ return rec(n - 1); + \\} + , ""); + } ctx.c("empty start function", linux_x64, \\export fn _start() noreturn { \\ unreachable; diff --git a/test/stage2/test.zig b/test/stage2/test.zig index 7249cf025312..6e25dc283b59 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -1477,58 +1477,4 @@ pub fn addCases(ctx: *TestContext) !void { \\} , &[_][]const u8{":8:10: error: evaluation exceeded 1000 backwards branches"}); } - { - var case = ctx.exe("@setEvalBranchQuota", linux_x64); - case.addCompareOutput( - \\export fn _start() noreturn { - \\ @setEvalBranchQuota(100000); - \\ const y = fibonacci(18); - \\ assert(y == 4181); - \\ exit(); - \\} - \\ - \\inline fn fibonacci(n: usize) usize { - \\ if (n <= 2) return n; - \\ return fibonacci(n - 2) + fibonacci(n - 1); - \\} - \\ - \\fn assert(b: bool) void { - \\ if (!b) unreachable; - \\} - \\ - \\fn exit() noreturn { - \\ asm volatile ("syscall" - \\ : - \\ : [number] "{rax}" (231), - \\ [arg1] "{rdi}" (0) - \\ : "rcx", "r11", "memory" - \\ ); - \\ unreachable; - \\} - , - "", - ); - - case.addError( - \\export fn _start() noreturn { - \\ @setEvalBranchQuota(10); - \\ const y = rec(18); - \\ exit(); - \\} - \\ - \\inline fn rec(n: usize) usize { - \\ return rec(n); - \\} - \\ - \\fn exit() noreturn { - \\ asm volatile ("syscall" - \\ : - \\ : [number] "{rax}" (231), - \\ [arg1] "{rdi}" (0) - \\ : "rcx", "r11", "memory" - \\ ); - \\ unreachable; - \\} - , &[_][]const u8{":8:12: error: evaluation exceeded 10 backwards branches"}); - } }