From 9730f65f616225c0a42d3d013bc2dd9e0ab750dd Mon Sep 17 00:00:00 2001 From: David Rubin Date: Sun, 14 Apr 2024 18:47:57 -0700 Subject: [PATCH 1/6] implement `@expect` --- lib/std/zig/AstGen.zig | 9 +++++- lib/std/zig/AstRlAnnotate.zig | 4 +++ lib/std/zig/BuiltinFn.zig | 8 +++++ lib/std/zig/Zir.zig | 3 ++ lib/zig.h | 6 ++++ src/Air.zig | 6 ++++ src/Liveness.zig | 2 ++ src/Liveness/Verify.zig | 1 + src/Module.zig | 1 + src/Sema.zig | 15 +++++++++ src/arch/aarch64/CodeGen.zig | 2 ++ src/arch/arm/CodeGen.zig | 2 ++ src/arch/riscv64/CodeGen.zig | 2 ++ src/arch/sparc64/CodeGen.zig | 2 ++ src/arch/wasm/CodeGen.zig | 2 ++ src/arch/x86_64/CodeGen.zig | 2 ++ src/codegen/c.zig | 20 ++++++++++++ src/codegen/llvm.zig | 21 +++++++++++++ src/print_air.zig | 1 + src/print_zir.zig | 1 + src/target.zig | 1 + test/behavior/expect.zig | 31 +++++++++++++++++++ .../cases/compile_errors/@expect_non_bool.zig | 11 +++++++ 23 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 test/behavior/expect.zig create mode 100644 test/cases/compile_errors/@expect_non_bool.zig diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index a52007eabf24..4d27b598cb35 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -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, }, @@ -9290,7 +9291,13 @@ fn builtinCall( }); return rvalue(gz, ri, .void_value, node); }, - + .expect => { + const val = try gz.addExtendedPayload(.expect, Zir.Inst.UnNode{ + .node = gz.nodeIndexToRelative(node), + .operand = try expr(gz, scope, .{ .rl = .{ .ty = .bool_type } }, params[0]), + }); + return rvalue(gz, ri, val, node); + }, .src => { const token_starts = tree.tokens.items(.start); const node_start = token_starts[tree.firstToken(node)]; diff --git a/lib/std/zig/AstRlAnnotate.zig b/lib/std/zig/AstRlAnnotate.zig index 4a1203ca09fc..5ae6d3560d44 100644 --- a/lib/std/zig/AstRlAnnotate.zig +++ b/lib/std/zig/AstRlAnnotate.zig @@ -1100,5 +1100,9 @@ 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); + return false; + }, } } diff --git a/lib/std/zig/BuiltinFn.zig b/lib/std/zig/BuiltinFn.zig index 11d6a17303c8..670a24237578 100644 --- a/lib/std/zig/BuiltinFn.zig +++ b/lib/std/zig/BuiltinFn.zig @@ -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, @@ -743,6 +744,13 @@ pub const list = list: { .illegal_outside_function = true, }, }, + .{ + "@expect", + .{ + .tag = .expect, + .param_count = 1, + }, + }, .{ "@setEvalBranchQuota", .{ diff --git a/lib/std/zig/Zir.zig b/lib/std/zig/Zir.zig index 64e8a1c8050f..384495323cb2 100644 --- a/lib/std/zig/Zir.zig +++ b/lib/std/zig/Zir.zig @@ -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 UnOp + expect, pub const InstData = struct { opcode: Extended, diff --git a/lib/zig.h b/lib/zig.h index ec7508670d36..a824e96d215b 100644 --- a/lib/zig.h +++ b/lib/zig.h @@ -318,6 +318,12 @@ typedef char bool; #define zig_noreturn #endif +#if defined(__GNUC__) || defined(__clang__) +#define zig_expect(op) __builtin_expect(op, true) +#else +#define zig_expect(op) (op) +#endif + #define zig_bitSizeOf(T) (CHAR_BIT * sizeof(T)) #define zig_compiler_rt_abbrev_uint32_t si diff --git a/src/Air.zig b/src/Air.zig index 9554c55561a5..67af16fd4121 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -848,6 +848,10 @@ pub const Inst = struct { /// Operand is unused and set to Ref.none work_group_id, + /// Implements @expect builtin. + /// Uses the `expect` field. + expect, + pub fn fromCmpOp(op: std.math.CompareOperator, optimized: bool) Tag { switch (op) { .lt => return if (optimized) .cmp_lt_optimized else .cmp_lt, @@ -1335,6 +1339,7 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool) .trunc_float, .neg, .neg_optimized, + .expect, => return air.typeOf(datas[@intFromEnum(inst)].un_op, ip), .cmp_lt, @@ -1640,6 +1645,7 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool { .add_safe, .sub_safe, .mul_safe, + .expect, => true, .add, diff --git a/src/Liveness.zig b/src/Liveness.zig index 4ca28758e222..df549ce588e4 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -424,6 +424,7 @@ pub fn categorizeOperand( .neg, .cmp_lt_errors_len, .c_va_end, + .expect, => { const o = air_datas[@intFromEnum(inst)].un_op; if (o == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none); @@ -1067,6 +1068,7 @@ fn analyzeInst( .cmp_lt_errors_len, .set_err_return_trace, .c_va_end, + .expect, => { const operand = inst_datas[@intFromEnum(inst)].un_op; return analyzeOperands(a, pass, data, inst, .{ operand, .none, .none }); diff --git a/src/Liveness/Verify.zig b/src/Liveness/Verify.zig index 4392f25e101d..4279add65dd3 100644 --- a/src/Liveness/Verify.zig +++ b/src/Liveness/Verify.zig @@ -142,6 +142,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void { .cmp_lt_errors_len, .set_err_return_trace, .c_va_end, + .expect, => { const un_op = data[@intFromEnum(inst)].un_op; try self.verifyInstOperands(inst, .{ un_op, .none, .none }); diff --git a/src/Module.zig b/src/Module.zig index c4d7f43fe4b9..ba6eb06e6569 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -5532,6 +5532,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 { diff --git a/src/Sema.zig b/src/Sema.zig index d3989f630cb5..ce361ea6b92c 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1259,6 +1259,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 @@ -17481,6 +17482,20 @@ 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 un_op = sema.code.extraData(Zir.Inst.UnNode, inst.operand).data; + const operand = try sema.resolveInst(un_op.operand); + + if (sema.mod.backendSupportsFeature(.can_expect) and sema.mod.optimizeMode() != .Debug) { + return try block.addInst(.{ + .tag = .expect, + .data = .{ .un_op = operand }, + }); + } 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; diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index ddde72345efe..dc7e8c4dd580 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -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, diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 86d4e8f7fdd6..7f001e5fa9c7 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -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, diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index 5abe3afcfd2a..b6d429b5605b 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -622,6 +622,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .@"try" => @panic("TODO"), .try_ptr => @panic("TODO"), + .expect => unreachable, + .dbg_stmt => try self.airDbgStmt(inst), .dbg_inline_block => try self.airDbgInlineBlock(inst), .dbg_var_ptr, diff --git a/src/arch/sparc64/CodeGen.zig b/src/arch/sparc64/CodeGen.zig index 19c18ec4a6b0..b78935d5b205 100644 --- a/src/arch/sparc64/CodeGen.zig +++ b/src/arch/sparc64/CodeGen.zig @@ -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, diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 83159ec80e7d..38d0ef9f2da1 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -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, diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index c165baf7e885..05d2f84b8d70 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -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), diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 818267a8b819..f2989a30833a 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -3370,6 +3370,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), @@ -4733,6 +4735,24 @@ 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 un_op = f.air.instructions.items(.data)[@intFromEnum(inst)].un_op; + const operand = try f.resolveInst(un_op); + + 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 a.end(f, writer); + return local; +} + fn lowerTry( f: *Function, inst: Air.Inst.Index, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index db0eaa3ce5e6..0a22484cbae9 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -5000,6 +5000,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), @@ -6327,6 +6329,25 @@ 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 un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op; + + const operand = try self.resolveInst(un_op); + + return try self.wip.callIntrinsic( + .normal, + .none, + .expect, + &.{operand.typeOfWip(&self.wip)}, + &.{ + operand, + .true, + }, + "", + ); + } + fn sliceOrArrayPtr(fg: *FuncGen, ptr: Builder.Value, ty: Type) Allocator.Error!Builder.Value { const o = fg.dg.object; const mod = o.module; diff --git a/src/print_air.zig b/src/print_air.zig index 12e2825d4ef0..d7eb166b1dbb 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -198,6 +198,7 @@ const Writer = struct { .cmp_lt_errors_len, .set_err_return_trace, .c_va_end, + .expect, => try w.writeUnOp(s, inst), .trap, diff --git a/src/print_zir.zig b/src/print_zir.zig index dfe94d397097..68fe810497ca 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -577,6 +577,7 @@ const Writer = struct { .work_item_id, .work_group_size, .work_group_id, + .expect, => { const inst_data = self.code.extraData(Zir.Inst.UnNode, extended.operand).data; const src = LazySrcLoc.nodeOffset(inst_data.node); diff --git a/src/target.zig b/src/target.zig index 3ad36deab2c7..d0e4b4294bfa 100644 --- a/src/target.zig +++ b/src/target.zig @@ -532,5 +532,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, }; } diff --git a/test/behavior/expect.zig b/test/behavior/expect.zig new file mode 100644 index 000000000000..83059065dd6c --- /dev/null +++ b/test/behavior/expect.zig @@ -0,0 +1,31 @@ +const std = @import("std"); +const expect = std.testing.expect; + +test "@expect if-statement" { + const x: u32 = 10; + _ = &x; + if (@expect(x == 20)) {} +} + +test "@expect runtime if-statement" { + var x: u32 = 10; + var y: u32 = 20; + _ = &x; + _ = &y; + if (@expect(x != y)) {} +} + +test "@expect bool input/output" { + const b: bool = true; + try expect(@TypeOf(@expect(b)) == bool); +} + +test "@expect bool is transitive" { + const a: bool = true; + const b = @expect(a); + + const c = @intFromBool(~b); + std.mem.doNotOptimizeAway(c); + + try expect(c == true); +} diff --git a/test/cases/compile_errors/@expect_non_bool.zig b/test/cases/compile_errors/@expect_non_bool.zig new file mode 100644 index 000000000000..2b8b226f98d5 --- /dev/null +++ b/test/cases/compile_errors/@expect_non_bool.zig @@ -0,0 +1,11 @@ +export fn a() void { + var x: u32 = 10; + _ = &x; + _ = @expect(x); +} + +// error +// backend=stage2 +// target=native +// +// :4:17: error: expected type 'bool', found 'u32' From d784d18306d3761db28d00fb31b35ea04e35de43 Mon Sep 17 00:00:00 2001 From: David Rubin Date: Sun, 14 Apr 2024 18:48:03 -0700 Subject: [PATCH 2/6] add docs --- doc/langref.html.in | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/doc/langref.html.in b/doc/langref.html.in index 6f3e9961c389..00898229c79d 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -8726,6 +8726,24 @@ export fn @"A function name that is a complete sentence."() void {} {#see_also|@export#} {#header_close#} + {#header_open|@expect#} +
{#syntax#}@expect(operand: bool) bool{#endsyntax#}
+

+ Informs the optimizer that operand will likely be true, which influences branch compilation to prefer generating the true branch first. +

+ {#code_begin|obj|expect_if#} +export fn a(x: u32) void { + if (@expect(x == 0)) { + // condition check falls through at code generation + return; + } else { + // condition is branched to at code generation + return; + } +} + {#code_end#} + {#header_close#} + {#header_open|@fence#}
{#syntax#}@fence(order: AtomicOrder) void{#endsyntax#}

From 7aeff39cd5d1503076565a0ab319ac73e86c428f Mon Sep 17 00:00:00 2001 From: David Rubin Date: Tue, 16 Apr 2024 11:54:24 -0700 Subject: [PATCH 3/6] add a second arg for expected bool --- doc/langref.html.in | 6 +++--- lib/std/zig/AstGen.zig | 5 +++-- lib/std/zig/AstRlAnnotate.zig | 1 + lib/std/zig/BuiltinFn.zig | 2 +- lib/std/zig/Zir.zig | 2 +- lib/zig.h | 4 ++-- src/Air.zig | 5 +++-- src/Liveness.zig | 4 ++-- src/Liveness/Verify.zig | 2 +- src/Sema.zig | 20 ++++++++++++++++--- src/codegen/c.zig | 7 +++++-- src/codegen/llvm.zig | 7 ++++--- test/behavior/expect.zig | 18 +++++++++++------ .../cases/compile_errors/@expect_non_bool.zig | 2 +- 14 files changed, 56 insertions(+), 29 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index 00898229c79d..9cc249d52130 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -8727,13 +8727,13 @@ export fn @"A function name that is a complete sentence."() void {} {#header_close#} {#header_open|@expect#} -

{#syntax#}@expect(operand: bool) bool{#endsyntax#}
+
{#syntax#}@expect(operand: bool, expected: bool) bool{#endsyntax#}

- Informs the optimizer that operand will likely be true, which influences branch compilation to prefer generating the true branch first. + Informs the optimizer that {#syntax#}operand{#endsyntax#} will likely be {#syntax#}expected{#endsyntax#}, which influences branch compilation to prefer generating the true branch first.

{#code_begin|obj|expect_if#} export fn a(x: u32) void { - if (@expect(x == 0)) { + if (@expect(x == 0, false)) { // condition check falls through at code generation return; } else { diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index 4d27b598cb35..d4cb4f8b3f94 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -9292,9 +9292,10 @@ fn builtinCall( return rvalue(gz, ri, .void_value, node); }, .expect => { - const val = try gz.addExtendedPayload(.expect, Zir.Inst.UnNode{ + const val = try gz.addExtendedPayload(.expect, Zir.Inst.BinNode{ .node = gz.nodeIndexToRelative(node), - .operand = try expr(gz, scope, .{ .rl = .{ .ty = .bool_type } }, params[0]), + .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); }, diff --git a/lib/std/zig/AstRlAnnotate.zig b/lib/std/zig/AstRlAnnotate.zig index 5ae6d3560d44..0e80d0687249 100644 --- a/lib/std/zig/AstRlAnnotate.zig +++ b/lib/std/zig/AstRlAnnotate.zig @@ -1102,6 +1102,7 @@ fn builtinCall(astrl: *AstRlAnnotate, block: ?*Block, ri: ResultInfo, node: Ast. }, .expect => { _ = try astrl.expr(args[0], block, ResultInfo.none); + _ = try astrl.expr(args[1], block, ResultInfo.none); return false; }, } diff --git a/lib/std/zig/BuiltinFn.zig b/lib/std/zig/BuiltinFn.zig index 670a24237578..ba70b32cecfc 100644 --- a/lib/std/zig/BuiltinFn.zig +++ b/lib/std/zig/BuiltinFn.zig @@ -748,7 +748,7 @@ pub const list = list: { "@expect", .{ .tag = .expect, - .param_count = 1, + .param_count = 2, }, }, .{ diff --git a/lib/std/zig/Zir.zig b/lib/std/zig/Zir.zig index 384495323cb2..6da5c1397d70 100644 --- a/lib/std/zig/Zir.zig +++ b/lib/std/zig/Zir.zig @@ -2061,7 +2061,7 @@ pub const Inst = struct { /// Uses the `pl_node` union field with payload `FieldParentPtr`. field_parent_ptr, /// Implements the `@expect` builtin. - /// `operand` is UnOp + /// `operand` is BinOp expect, pub const InstData = struct { diff --git a/lib/zig.h b/lib/zig.h index a824e96d215b..25e1d93a9e15 100644 --- a/lib/zig.h +++ b/lib/zig.h @@ -319,9 +319,9 @@ typedef char bool; #endif #if defined(__GNUC__) || defined(__clang__) -#define zig_expect(op) __builtin_expect(op, true) +#define zig_expect(op, exp) __builtin_expect(op, exp) #else -#define zig_expect(op) (op) +#define zig_expect(op, exp) (op) #endif #define zig_bitSizeOf(T) (CHAR_BIT * sizeof(T)) diff --git a/src/Air.zig b/src/Air.zig index 67af16fd4121..cd17944ff005 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -849,7 +849,7 @@ pub const Inst = struct { work_group_id, /// Implements @expect builtin. - /// Uses the `expect` field. + /// Uses the `bin_op` field. expect, pub fn fromCmpOp(op: std.math.CompareOperator, optimized: bool) Tag { @@ -1339,7 +1339,6 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool) .trunc_float, .neg, .neg_optimized, - .expect, => return air.typeOf(datas[@intFromEnum(inst)].un_op, ip), .cmp_lt, @@ -1527,6 +1526,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, } diff --git a/src/Liveness.zig b/src/Liveness.zig index df549ce588e4..e66859ff4f5a 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -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); @@ -424,7 +425,6 @@ pub fn categorizeOperand( .neg, .cmp_lt_errors_len, .c_va_end, - .expect, => { const o = air_datas[@intFromEnum(inst)].un_op; if (o == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none); @@ -956,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 }); @@ -1068,7 +1069,6 @@ fn analyzeInst( .cmp_lt_errors_len, .set_err_return_trace, .c_va_end, - .expect, => { const operand = inst_datas[@intFromEnum(inst)].un_op; return analyzeOperands(a, pass, data, inst, .{ operand, .none, .none }); diff --git a/src/Liveness/Verify.zig b/src/Liveness/Verify.zig index 4279add65dd3..31442f25b232 100644 --- a/src/Liveness/Verify.zig +++ b/src/Liveness/Verify.zig @@ -142,7 +142,6 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void { .cmp_lt_errors_len, .set_err_return_trace, .c_va_end, - .expect, => { const un_op = data[@intFromEnum(inst)].un_op; try self.verifyInstOperands(inst, .{ un_op, .none, .none }); @@ -258,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 }); diff --git a/src/Sema.zig b/src/Sema.zig index ce361ea6b92c..9a25138d1d18 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -17483,13 +17483,27 @@ fn zirThis( } fn zirExpect(sema: *Sema, block: *Block, inst: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref { - const un_op = sema.code.extraData(Zir.Inst.UnNode, inst.operand).data; - const operand = try sema.resolveInst(un_op.operand); + 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 = .{ .un_op = operand }, + .data = .{ .bin_op = .{ + .lhs = operand, + .rhs = expected, + } }, }); } else { return operand; diff --git a/src/codegen/c.zig b/src/codegen/c.zig index f2989a30833a..b4b4beba0970 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -4736,8 +4736,9 @@ fn airTryPtr(f: *Function, inst: Air.Inst.Index) !CValue { } fn airExpect(f: *Function, inst: Air.Inst.Index) !CValue { - const un_op = f.air.instructions.items(.data)[@intFromEnum(inst)].un_op; - const operand = try f.resolveInst(un_op); + 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); @@ -4747,6 +4748,8 @@ fn airExpect(f: *Function, inst: Air.Inst.Index) !CValue { 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); diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 0a22484cbae9..467044fdcdbb 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -6331,9 +6331,10 @@ pub const FuncGen = struct { // Note that the LowerExpectPass only runs in Release modes fn airExpect(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value { - const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op; + const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; - const operand = try self.resolveInst(un_op); + const operand = try self.resolveInst(bin_op.lhs); + const epxected = try self.resolveInst(bin_op.rhs); return try self.wip.callIntrinsic( .normal, @@ -6342,7 +6343,7 @@ pub const FuncGen = struct { &.{operand.typeOfWip(&self.wip)}, &.{ operand, - .true, + epxected, }, "", ); diff --git a/test/behavior/expect.zig b/test/behavior/expect.zig index 83059065dd6c..7d759a4d0447 100644 --- a/test/behavior/expect.zig +++ b/test/behavior/expect.zig @@ -4,7 +4,7 @@ const expect = std.testing.expect; test "@expect if-statement" { const x: u32 = 10; _ = &x; - if (@expect(x == 20)) {} + if (@expect(x == 20, true)) {} } test "@expect runtime if-statement" { @@ -12,20 +12,26 @@ test "@expect runtime if-statement" { var y: u32 = 20; _ = &x; _ = &y; - if (@expect(x != y)) {} + if (@expect(x != y, false)) {} } test "@expect bool input/output" { const b: bool = true; - try expect(@TypeOf(@expect(b)) == bool); + try expect(@TypeOf(@expect(b, false)) == bool); } test "@expect bool is transitive" { const a: bool = true; - const b = @expect(a); + const b = @expect(a, false); - const c = @intFromBool(~b); + const c = @intFromBool(!b); std.mem.doNotOptimizeAway(c); - try expect(c == true); + try expect(c == 0); + try expect(@expect(c != 0, false) == false); +} + +test "@expect at comptime" { + const a: bool = true; + comptime try expect(@expect(a, true) == true); } diff --git a/test/cases/compile_errors/@expect_non_bool.zig b/test/cases/compile_errors/@expect_non_bool.zig index 2b8b226f98d5..da49b30d126a 100644 --- a/test/cases/compile_errors/@expect_non_bool.zig +++ b/test/cases/compile_errors/@expect_non_bool.zig @@ -1,7 +1,7 @@ export fn a() void { var x: u32 = 10; _ = &x; - _ = @expect(x); + _ = @expect(x, true); } // error From 4c393cb2ebb912d837e8f29af27368b978852df3 Mon Sep 17 00:00:00 2001 From: David Rubin Date: Wed, 17 Apr 2024 17:08:36 -0700 Subject: [PATCH 4/6] fix typo --- src/codegen/llvm.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 467044fdcdbb..17acd6e5b705 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -6334,7 +6334,7 @@ pub const FuncGen = struct { const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; const operand = try self.resolveInst(bin_op.lhs); - const epxected = try self.resolveInst(bin_op.rhs); + const expected = try self.resolveInst(bin_op.rhs); return try self.wip.callIntrinsic( .normal, @@ -6343,7 +6343,7 @@ pub const FuncGen = struct { &.{operand.typeOfWip(&self.wip)}, &.{ operand, - epxected, + expected, }, "", ); From ad24fb7bb48661f65727ad43dc47e18c1a06e1c2 Mon Sep 17 00:00:00 2001 From: David Rubin Date: Sun, 21 Apr 2024 00:04:06 -0700 Subject: [PATCH 5/6] move `expect` to use BinOp --- src/print_air.zig | 2 +- src/print_zir.zig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/print_air.zig b/src/print_air.zig index d7eb166b1dbb..79b4a24ec058 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -162,6 +162,7 @@ const Writer = struct { .memcpy, .memset, .memset_safe, + .expect, => try w.writeBinOp(s, inst), .is_null, @@ -198,7 +199,6 @@ const Writer = struct { .cmp_lt_errors_len, .set_err_return_trace, .c_va_end, - .expect, => try w.writeUnOp(s, inst), .trap, diff --git a/src/print_zir.zig b/src/print_zir.zig index 68fe810497ca..bc95d41b28df 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -577,7 +577,6 @@ const Writer = struct { .work_item_id, .work_group_size, .work_group_id, - .expect, => { const inst_data = self.code.extraData(Zir.Inst.UnNode, extended.operand).data; const src = LazySrcLoc.nodeOffset(inst_data.node); @@ -592,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); From 5dac3ac2580dbb82e4d98a93872e2f40a6aeb135 Mon Sep 17 00:00:00 2001 From: David Rubin Date: Tue, 7 May 2024 00:37:23 -0700 Subject: [PATCH 6/6] update to newer langref format --- doc/langref.html.in | 12 +----------- doc/langref/expect_if.zig | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 11 deletions(-) create mode 100644 doc/langref/expect_if.zig diff --git a/doc/langref.html.in b/doc/langref.html.in index 73a76c9a4307..7443d81a2fd7 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -4804,17 +4804,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val

Informs the optimizer that {#syntax#}operand{#endsyntax#} will likely be {#syntax#}expected{#endsyntax#}, which influences branch compilation to prefer generating the true branch first.

- {#code_begin|obj|expect_if#} -export 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; - } -} - {#code_end#} + {#code|expect_if.zig#} {#header_close#} {#header_open|@fence#} diff --git a/doc/langref/expect_if.zig b/doc/langref/expect_if.zig new file mode 100644 index 000000000000..e971db4e5a56 --- /dev/null +++ b/doc/langref/expect_if.zig @@ -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