Skip to content

Commit

Permalink
implement @expect builtin (ziglang#19658)
Browse files Browse the repository at this point in the history
* implement `@expect`

* add docs

* add a second arg for expected bool

* fix typo

* move `expect` to use BinOp

* update to newer langref format
  • Loading branch information
Rexicon226 authored May 22, 2024
1 parent ed75f62 commit a7de02e
Show file tree
Hide file tree
Showing 25 changed files with 202 additions and 1 deletion.
8 changes: 8 additions & 0 deletions doc/langref.html.in
Original file line number Diff line number Diff line change
Expand Up @@ -4799,6 +4799,14 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
{#see_also|@export#}
{#header_close#}

{#header_open|@expect#}
<pre>{#syntax#}@expect(operand: bool, expected: bool) bool{#endsyntax#}</pre>
<p>
Informs the optimizer that {#syntax#}operand{#endsyntax#} will likely be {#syntax#}expected{#endsyntax#}, which influences branch compilation to prefer generating the true branch first.
</p>
{#code|expect_if.zig#}
{#header_close#}

{#header_open|@fence#}
<pre>{#syntax#}@fence(order: AtomicOrder) void{#endsyntax#}</pre>
<p>
Expand Down
15 changes: 15 additions & 0 deletions doc/langref/expect_if.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
pub fn a(x: u32) void {
if (@expect(x == 0, false)) {
// condition check falls through at code generation
return;
} else {
// condition is branched to at code generation
return;
}
}

test "expect" {
a(10);
}

// test
10 changes: 9 additions & 1 deletion lib/std/zig/AstGen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2823,6 +2823,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
.set_float_mode,
.set_align_stack,
.set_cold,
.expect,
=> break :b true,
else => break :b false,
},
Expand Down Expand Up @@ -9292,7 +9293,14 @@ fn builtinCall(
});
return rvalue(gz, ri, .void_value, node);
},

.expect => {
const val = try gz.addExtendedPayload(.expect, Zir.Inst.BinNode{
.node = gz.nodeIndexToRelative(node),
.lhs = try expr(gz, scope, .{ .rl = .{ .ty = .bool_type } }, params[0]),
.rhs = try expr(gz, scope, .{ .rl = .{ .ty = .bool_type } }, params[1]),
});
return rvalue(gz, ri, val, node);
},
.src => {
const token_starts = tree.tokens.items(.start);
const node_start = token_starts[tree.firstToken(node)];
Expand Down
5 changes: 5 additions & 0 deletions lib/std/zig/AstRlAnnotate.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1100,5 +1100,10 @@ fn builtinCall(astrl: *AstRlAnnotate, block: ?*Block, ri: ResultInfo, node: Ast.
_ = try astrl.expr(args[4], block, ResultInfo.type_only);
return false;
},
.expect => {
_ = try astrl.expr(args[0], block, ResultInfo.none);
_ = try astrl.expr(args[1], block, ResultInfo.none);
return false;
},
}
}
8 changes: 8 additions & 0 deletions lib/std/zig/BuiltinFn.zig
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ pub const Tag = enum {
select,
set_align_stack,
set_cold,
expect,
set_eval_branch_quota,
set_float_mode,
set_runtime_safety,
Expand Down Expand Up @@ -743,6 +744,13 @@ pub const list = list: {
.illegal_outside_function = true,
},
},
.{
"@expect",
.{
.tag = .expect,
.param_count = 2,
},
},
.{
"@setEvalBranchQuota",
.{
Expand Down
3 changes: 3 additions & 0 deletions lib/std/zig/Zir.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2060,6 +2060,9 @@ pub const Inst = struct {
/// Guaranteed to not have the `ptr_cast` flag.
/// Uses the `pl_node` union field with payload `FieldParentPtr`.
field_parent_ptr,
/// Implements the `@expect` builtin.
/// `operand` is BinOp
expect,

pub const InstData = struct {
opcode: Extended,
Expand Down
6 changes: 6 additions & 0 deletions lib/zig.h
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,12 @@ typedef char bool;
#define zig_noreturn
#endif

#if defined(__GNUC__) || defined(__clang__)
#define zig_expect(op, exp) __builtin_expect(op, exp)
#else
#define zig_expect(op, exp) (op)
#endif

#define zig_bitSizeOf(T) (CHAR_BIT * sizeof(T))

#define zig_compiler_rt_abbrev_uint32_t si
Expand Down
7 changes: 7 additions & 0 deletions src/Air.zig
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,10 @@ pub const Inst = struct {
/// Operand is unused and set to Ref.none
work_group_id,

/// Implements @expect builtin.
/// Uses the `bin_op` field.
expect,

pub fn fromCmpOp(op: std.math.CompareOperator, optimized: bool) Tag {
switch (op) {
.lt => return if (optimized) .cmp_lt_optimized else .cmp_lt,
Expand Down Expand Up @@ -1517,6 +1521,8 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool)
.work_group_id,
=> return Type.u32,

.expect => return Type.bool,

.inferred_alloc => unreachable,
.inferred_alloc_comptime => unreachable,
}
Expand Down Expand Up @@ -1634,6 +1640,7 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {
.add_safe,
.sub_safe,
.mul_safe,
.expect,
=> true,

.add,
Expand Down
2 changes: 2 additions & 0 deletions src/Liveness.zig
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ pub fn categorizeOperand(
.cmp_gte_optimized,
.cmp_gt_optimized,
.cmp_neq_optimized,
.expect,
=> {
const o = air_datas[@intFromEnum(inst)].bin_op;
if (o.lhs == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none);
Expand Down Expand Up @@ -955,6 +956,7 @@ fn analyzeInst(
.memset,
.memset_safe,
.memcpy,
.expect,
=> {
const o = inst_datas[@intFromEnum(inst)].bin_op;
return analyzeOperands(a, pass, data, inst, .{ o.lhs, o.rhs, .none });
Expand Down
1 change: 1 addition & 0 deletions src/Liveness/Verify.zig
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
.memset,
.memset_safe,
.memcpy,
.expect,
=> {
const bin_op = data[@intFromEnum(inst)].bin_op;
try self.verifyInstOperands(inst, .{ bin_op.lhs, bin_op.rhs, .none });
Expand Down
1 change: 1 addition & 0 deletions src/Module.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5546,6 +5546,7 @@ pub const Feature = enum {
/// to generate better machine code in the backends. All backends should migrate to
/// enabling this feature.
safety_checked_instructions,
can_expect,
};

pub fn backendSupportsFeature(zcu: Module, feature: Feature) bool {
Expand Down
29 changes: 29 additions & 0 deletions src/Sema.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1258,6 +1258,7 @@ fn analyzeBodyInner(
.work_group_size => try sema.zirWorkItem( block, extended, extended.opcode),
.work_group_id => try sema.zirWorkItem( block, extended, extended.opcode),
.in_comptime => try sema.zirInComptime( block),
.expect => try sema.zirExpect( block, extended),
.closure_get => try sema.zirClosureGet( block, extended),
// zig fmt: on

Expand Down Expand Up @@ -17553,6 +17554,34 @@ fn zirThis(
return sema.analyzeDeclVal(block, src, this_decl_index);
}

fn zirExpect(sema: *Sema, block: *Block, inst: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
const bin_op = sema.code.extraData(Zir.Inst.BinNode, inst.operand).data;
const operand = try sema.resolveInst(bin_op.lhs);
const expected = try sema.resolveInst(bin_op.rhs);

const expected_src = LazySrcLoc{ .node_offset_builtin_call_arg1 = bin_op.node };

if (!try sema.isComptimeKnown(expected)) {
return sema.fail(block, expected_src, "@expect 'expected' must be comptime-known", .{});
}

if (try sema.resolveValue(operand)) |op| {
return Air.internedToRef(op.toIntern());
}

if (sema.mod.backendSupportsFeature(.can_expect) and sema.mod.optimizeMode() != .Debug) {
return try block.addInst(.{
.tag = .expect,
.data = .{ .bin_op = .{
.lhs = operand,
.rhs = expected,
} },
});
} else {
return operand;
}
}

fn zirClosureGet(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
const mod = sema.mod;
const ip = &mod.intern_pool;
Expand Down
2 changes: 2 additions & 0 deletions src/arch/aarch64/CodeGen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.@"try" => try self.airTry(inst),
.try_ptr => try self.airTryPtr(inst),

.expect => unreachable,

.dbg_stmt => try self.airDbgStmt(inst),
.dbg_inline_block => try self.airDbgInlineBlock(inst),
.dbg_var_ptr,
Expand Down
2 changes: 2 additions & 0 deletions src/arch/arm/CodeGen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
.wrap_errunion_err => try self.airWrapErrUnionErr(inst),

.expect => unreachable,

.add_optimized,
.sub_optimized,
.mul_optimized,
Expand Down
2 changes: 2 additions & 0 deletions src/arch/riscv64/CodeGen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1200,6 +1200,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.@"try" => try self.airTry(inst),
.try_ptr => return self.fail("TODO: try_ptr", .{}),

.expect => unreachable,

.dbg_var_ptr,
.dbg_var_val,
=> try self.airDbgVar(inst),
Expand Down
2 changes: 2 additions & 0 deletions src/arch/sparc64/CodeGen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.@"try" => try self.airTry(inst),
.try_ptr => @panic("TODO try self.airTryPtr(inst)"),

.expect => unreachable,

.dbg_stmt => try self.airDbgStmt(inst),
.dbg_inline_block => try self.airDbgInlineBlock(inst),
.dbg_var_ptr,
Expand Down
2 changes: 2 additions & 0 deletions src/arch/wasm/CodeGen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2016,6 +2016,8 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
.c_va_start,
=> |tag| return func.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}),

.expect => unreachable,

.atomic_load => func.airAtomicLoad(inst),
.atomic_store_unordered,
.atomic_store_monotonic,
Expand Down
2 changes: 2 additions & 0 deletions src/arch/x86_64/CodeGen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2014,6 +2014,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {

.abs => try self.airAbs(inst),

.expect => unreachable,

.add_with_overflow => try self.airAddSubWithOverflow(inst),
.sub_with_overflow => try self.airAddSubWithOverflow(inst),
.mul_with_overflow => try self.airMulWithOverflow(inst),
Expand Down
23 changes: 23 additions & 0 deletions src/codegen/c.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3343,6 +3343,8 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,

.@"try" => try airTry(f, inst),
.try_ptr => try airTryPtr(f, inst),

.expect => try airExpect(f, inst),

.dbg_stmt => try airDbgStmt(f, inst),
.dbg_inline_block => try airDbgInlineBlock(f, inst),
Expand Down Expand Up @@ -4704,6 +4706,27 @@ fn airTryPtr(f: *Function, inst: Air.Inst.Index) !CValue {
return lowerTry(f, inst, extra.data.ptr, body, err_union_ty, true);
}

fn airExpect(f: *Function, inst: Air.Inst.Index) !CValue {
const bin_op = f.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
const operand = try f.resolveInst(bin_op.lhs);
const expected = try f.resolveInst(bin_op.rhs);

const writer = f.object.writer();
const local = try f.allocLocal(inst, Type.bool);
const a = try Assignment.start(f, writer, CType.bool);
try f.writeCValue(writer, local, .Other);
try a.assign(f, writer);

try writer.writeAll("zig_expect(");
try f.writeCValue(writer, operand, .FunctionArgument);
try writer.writeAll(", ");
try f.writeCValue(writer, expected, .FunctionArgument);
try writer.writeAll(")");

try a.end(f, writer);
return local;
}

fn lowerTry(
f: *Function,
inst: Air.Inst.Index,
Expand Down
22 changes: 22 additions & 0 deletions src/codegen/llvm.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5038,6 +5038,8 @@ pub const FuncGen = struct {
.slice_ptr => try self.airSliceField(inst, 0),
.slice_len => try self.airSliceField(inst, 1),

.expect => try self.airExpect(inst),

.call => try self.airCall(inst, .auto),
.call_always_tail => try self.airCall(inst, .always_tail),
.call_never_tail => try self.airCall(inst, .never_tail),
Expand Down Expand Up @@ -6365,6 +6367,26 @@ pub const FuncGen = struct {
return result;
}

// Note that the LowerExpectPass only runs in Release modes
fn airExpect(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {
const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;

const operand = try self.resolveInst(bin_op.lhs);
const expected = try self.resolveInst(bin_op.rhs);

return try self.wip.callIntrinsic(
.normal,
.none,
.expect,
&.{operand.typeOfWip(&self.wip)},
&.{
operand,
expected,
},
"",
);
}

fn sliceOrArrayPtr(fg: *FuncGen, ptr: Builder.Value, ty: Type) Allocator.Error!Builder.Value {
const o = fg.dg.object;
const mod = o.module;
Expand Down
1 change: 1 addition & 0 deletions src/print_air.zig
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ const Writer = struct {
.memcpy,
.memset,
.memset_safe,
.expect,
=> try w.writeBinOp(s, inst),

.is_null,
Expand Down
1 change: 1 addition & 0 deletions src/print_zir.zig
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,7 @@ const Writer = struct {
.wasm_memory_grow,
.prefetch,
.c_va_arg,
.expect,
=> {
const inst_data = self.code.extraData(Zir.Inst.BinNode, extended.operand).data;
const src = LazySrcLoc.nodeOffset(inst_data.node);
Expand Down
1 change: 1 addition & 0 deletions src/target.zig
Original file line number Diff line number Diff line change
Expand Up @@ -535,5 +535,6 @@ pub fn backendSupportsFeature(
.error_set_has_value => use_llvm or cpu_arch.isWasm(),
.field_reordering => ofmt == .c or use_llvm,
.safety_checked_instructions => use_llvm,
.can_expect => use_llvm or ofmt == .c,
};
}
Loading

0 comments on commit a7de02e

Please sign in to comment.