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 number Diff line number Diff 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 number Diff line number Diff 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]);
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 Down Expand 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 number Diff line number Diff 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 Down Expand Up @@ -347,6 +349,7 @@ pub const Inst = struct {
.anyframe_type,
.bitnot,
.import,
.setevalbranchquota,
=> UnOp,

.add,
Expand Down Expand 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 number Diff line number Diff 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 number Diff line number Diff 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