Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions src/Module.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -765,6 +765,8 @@ pub const Scope = struct {
label: ?Label = null,
inlining: ?*Inlining,
is_comptime: bool,
/// Shared to sub-blocks.
branch_quota: *u32,

pub const InstTable = std.AutoHashMap(*zir.Inst, *Inst);

Expand DownExpand Up@@ -792,8 +794,7 @@ pub const Scope = struct {

pub const Shared = struct {
caller: ?*Fn,
branch_count: u64,
branch_quota: u64,
branch_count: u32,
};
};

Expand DownExpand Up@@ -1104,6 +1105,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
var inst_table = Scope.Block.InstTable.init(self.gpa);
defer inst_table.deinit();

var branch_quota: u32 = 1000;

var block_scope: Scope.Block = .{
.parent = null,
.inst_table = &inst_table,
Expand All@@ -1113,6 +1116,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
.arena = &decl_arena.allocator,
.inlining = null,
.is_comptime = false,
.branch_quota = &branch_quota,
};
defer block_scope.instructions.deinit(self.gpa);

Expand DownExpand Up@@ -1297,6 +1301,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
var decl_inst_table = Scope.Block.InstTable.init(self.gpa);
defer decl_inst_table.deinit();

var branch_quota: u32 = 1000;

var block_scope: Scope.Block = .{
.parent = null,
.inst_table = &decl_inst_table,
Expand All@@ -1306,6 +1312,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
.arena = &decl_arena.allocator,
.inlining = null,
.is_comptime = true,
.branch_quota = &branch_quota,
};
defer block_scope.instructions.deinit(self.gpa);

Expand DownExpand Up@@ -1367,6 +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 branch_quota_vi: u32 = 1000;
var inner_block: Scope.Block = .{
.parent = null,
.inst_table = &var_inst_table,
Expand All@@ -1376,6 +1384,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
.arena = &gen_scope_arena.allocator,
.inlining = null,
.is_comptime = true,
.branch_quota = &branch_quota_vi,
};
defer inner_block.instructions.deinit(self.gpa);
try zir_sema.analyzeBody(self, &inner_block, .{
Expand DownExpand Up@@ -1494,6 +1503,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
var inst_table = Scope.Block.InstTable.init(self.gpa);
defer inst_table.deinit();

var branch_quota: u32 = 1000;

var block_scope: Scope.Block = .{
.parent = null,
.inst_table = &inst_table,
Expand All@@ -1503,6 +1514,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
.arena = &analysis_arena.allocator,
.inlining = null,
.is_comptime = true,
.branch_quota = &branch_quota,
};
defer block_scope.instructions.deinit(self.gpa);

Expand DownExpand Up@@ -1875,6 +1887,8 @@ 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 branch_quota: u32 = 1000;

var inner_block: Scope.Block = .{
.parent = null,
.inst_table = &inst_table,
Expand All@@ -1884,6 +1898,7 @@ pub fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void {
.arena = &arena.allocator,
.inlining = null,
.is_comptime = false,
.branch_quota = &branch_quota,
};
defer inner_block.instructions.deinit(self.gpa);

Expand DownExpand Up@@ -3466,7 +3481,9 @@ 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_quota = parent_block.branch_quota,
};

defer fail_block.instructions.deinit(mod.gpa);

_ = try mod.safetyPanic(&fail_block, ok.src, panic_id);
Expand DownExpand Up@@ -3532,10 +3549,10 @@ pub fn identifierTokenString(mod: *Module, scope: *Scope, token: ast.TokenIndex)
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) {
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", .{
shared.branch_quota,
block.branch_quota.*,
});
}
}
15 changes: 15 additions & 0 deletions src/astgen.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -2317,6 +2317,19 @@ 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]);
Comment thread
g-w1 marked this conversation as resolved.
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 {
const tree = scope.tree();
const arena = scope.arena();
Expand DownExpand Up@@ -2362,6 +2375,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});
}
Expand Down
4 changes: 4 additions & 0 deletions src/zir.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -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.
Expand DownExpand Up@@ -347,6 +349,7 @@ pub const Inst = struct {
.anyframe_type,
.bitnot,
.import,
.setevalbranchquota,
=> UnOp,

.add,
Expand DownExpand Up@@ -535,6 +538,7 @@ pub const Inst = struct {
.switch_range,
.typeof_peer,
.resolve_inferred_alloc,
.setevalbranchquota,
=> false,

.@"break",
Expand Down
22 changes: 21 additions & 1 deletion src/zir_sema.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand DownExpand Up@@ -486,6 +487,18 @@ 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 = @truncate(u32, try resolveInt(mod, scope, inst.positionals.operand, Type.initTag(.u32)));
if (b.branch_quota.* < quota)
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);
Expand DownExpand Up@@ -594,6 +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,
.branch_quota = parent_block.branch_quota,
};
defer child_block.instructions.deinit(mod.gpa);

Expand All@@ -619,6 +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,
.branch_quota = parent_block.branch_quota,
};
defer child_block.instructions.deinit(mod.gpa);

Expand DownExpand Up@@ -666,6 +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,
.branch_quota = parent_block.branch_quota,
};
const merges = &child_block.label.?.merges;

Expand DownExpand Up@@ -867,7 +883,6 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError
// 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
Expand DownExpand Up@@ -896,7 +911,9 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError
.label = null,
.inlining = &inlining,
.is_comptime = is_comptime_call,
.branch_quota = b.branch_quota,
};

const merges = &child_block.inlining.?.merges;

defer child_block.instructions.deinit(mod.gpa);
Expand DownExpand Up@@ -1417,6 +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,
.branch_quota = parent_block.branch_quota,
};
defer case_block.instructions.deinit(mod.gpa);

Expand DownExpand Up@@ -1960,6 +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,
.branch_quota = parent_block.branch_quota,
};
defer true_block.instructions.deinit(mod.gpa);
try analyzeBody(mod, &true_block, inst.positionals.then_body);
Expand All@@ -1973,6 +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,
.branch_quota = parent_block.branch_quota,
};
defer false_block.instructions.deinit(mod.gpa);
try analyzeBody(mod, &false_block, inst.positionals.else_body);
Expand Down
15 changes: 15 additions & 0 deletions test/stage2/cbe.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand Down