Skip to content

Conversation

@mlugg
Copy link
Member

@mlugg mlugg commented Aug 26, 2024

This PR is just #21206 and #21191 rebased and rolled into one, to avoid doing two separate updates to zig1.wasm.

mlugg and others added 8 commits August 27, 2024 00:41
mlugg: this is cherry-picked from Andrew's nosanitize branch (with
Jacob's fixes squashed in) since I needed this for `unpredictable` and
`prof` metadata. The nosanitize-specific changes are reverted in the
next commit.

Co-authored-by: Jacob Young <[email protected]>
Implements the accepted proposal to introduce `@branchHint`. This
builtin is permitted as the first statement of a block if that block is
the direct body of any of the following:

* a function (*not* a `test`)
* either branch of an `if`
* the RHS of a `catch` or `orelse`
* a `switch` prong
* an `or` or `and` expression

It lowers to the ZIR instruction `extended(branch_hint(...))`. When Sema
encounters this instruction, it sets `sema.branch_hint` appropriately,
and `zirCondBr` etc are expected to reset this value as necessary. The
state is on `Sema` rather than `Block` to make it automatically
propagate up non-conditional blocks without special handling. If
`@panic` is reached, the branch hint is set to `.cold` if none was
already set; similarly, error branches get a hint of `.unlikely` if no
hint is explicitly provided. If a condition is comptime-known, `cold`
hints from the taken branch are allowed to propagate up, but other hints
are discarded. This is because a `likely`/`unlikely` hint just indicates
the direction this branch is likely to go, which is redundant
information when the branch is known at comptime; but `cold` hints
indicate that control flow is unlikely to ever reach this branch,
meaning if the branch is always taken from its parent, then the parent
is also unlikely to ever be reached.

This branch information is stored in AIR `cond_br` and `switch_br`. In
addition, `try` and `try_ptr` instructions have variants `try_cold` and
`try_ptr_cold` which indicate that the error case is cold (rather than
just unlikely); this is reachable through e.g. `errdefer unreachable` or
`errdefer @Panic("")`.

A new API `unwrapSwitch` is introduced to `Air` to make it more
convenient to access `switch_br` instructions. In time, I plan to update
all AIR instructions to be accessed via an `unwrap` method which returns
a convenient tagged union a la `InternPool.indexToKey`.

The LLVM backend lowers branch hints for conditional branches and
switches as follows:

* If any branch is marked `unpredictable`, the instruction is marked
  `!unpredictable`.
* Any branch which is marked as `cold` gets a
  `llvm.assume(i1 true) [ "cold"() ]` call to mark the code path cold.
* If any branch is marked `likely` or `unlikely`, branch weight metadata
  is attached with `!prof`. Likely branches get a weight of 2000, and
  unlikely branches a weight of 1. In `switch` statements, un-annotated
  branches get a weight of 1000 as a "middle ground" hint, since there
  could be likely *and* unlikely *and* un-annotated branches.

For functions, a `cold` hint corresponds to the `cold` function
attribute, and other hints are currently ignored -- as far as I can tell
LLVM doesn't really have a way to lower them. (Ideally, we would want
the branch hint given in the function to propagate to call sites.)

The compiler and standard library do not yet use this new builtin.

Resolves: ziglang#21148
This update implements both `@branchHint` and the new usage of `@export`
into zig1.

Signed-off-by: mlugg <[email protected]>
And remove the now-invalid test for the return value of `@branchHint`.
@mlugg mlugg added breaking Implementing this issue could cause existing code to no longer compile or have different behavior. release notes This PR should be mentioned in the release notes. labels Aug 26, 2024
@mlugg mlugg enabled auto-merge August 26, 2024 23:45
@mlugg mlugg merged commit d3c6f71 into ziglang:master Aug 27, 2024
@andrewrk
Copy link
Member

Don't forget to type up those release notes 🙏

@mlugg
Copy link
Member Author

mlugg commented Nov 11, 2024

Release Notes

@export Operand is Now a Pointer

This release of Zig simplifies the @export builtin. In previous versions of Zig, this builtin's first operand syntactically appeared to be the value which was to be exported, which was restricted to an identifier or field access of a local variable or container-level declaration. This system was unnecessarily restrictive, and moreover, syntactically confusing and inconsistent; it is reasonable to export constant comptime-known values, and this usage implied that the value was somehow being exported, whereas in reality its address was the relevant piece of information. To resolve this, @export has a new usage which closely mirrors that of @extern; its first operand is a pointer, which points to the data being exported. In most cases, solving this will just consist of adding a & operator:

const foo: u32 = 123;
test "@export" {
    @export(foo, .{ .name = "bar" });
}

-->

const foo: u32 = 123;
test "@export" {
    @export(&foo, .{ .name = "bar" });
}

New @branchHint Builtin, Replacing @setCold

In high-performance code, it is sometimes desirable to hint to the optimizer which branch of a condition is more likely; this can allow more efficient machine code to be generated. Some languages offer this through a "likely" annotation on a boolean condition; for instance, GCC and Clang implement the __builtin_expect function. Zig 0.14.0 introduces a mechanism to communicate this information: the new @branchHint(comptime hint: std.builtin.BranchHint) builtin. This builtin, rather than modifying a condition, appears as the first statement in a block to communicate whether control flow is likely to reach the block in question:

fn warnIf(cond: bool, message: []const u8) void {
    if (cond) {
        @branchHint(.unlikely); // we expect warnings to *not* happen most of the time!
        std.log.warn("{s}", message);
    }
}
const std = @import("std");

The BranchHint type is as follows:

pub const BranchHint = enum(u3) {
    /// Equivalent to no hint given.
    none,
    /// This branch of control flow is more likely to be reached than its peers.
    /// The optimizer should optimize for reaching it.
    likely,
    /// This branch of control flow is less likely to be reached than its peers.
    /// The optimizer should optimize for not reaching it.
    unlikely,
    /// This branch of control flow is unlikely to *ever* be reached.
    /// The optimizer may place it in a different page of memory to optimize other branches.
    cold,
    /// It is difficult to predict whether this branch of control flow will be reached.
    /// The optimizer should avoid branching behavior with expensive mispredictions.
    unpredictable,
};

As well as being the first statement of a block behind a condition, @branchHint is also permitted as the first statement of any function. The expectation is that the optimizer may propagate likelihood information to branches containing these calls; for instance, if a given branch of control flow always calls a function which is marked @branchHint(.unlikely), then the optimizer may assume that the branch in question is unlikely to be reached.

This feature combined with the existence of the .cold variant of BranchHint means that the old @setCold builtin, which could be used to communicate that a function is unlikely to ever be called, becomes redundant. Therefore, @setCold has been removed in favor of @branchHint. In most cases, the migration will be very simple; just replace @setCold(true) with @branchHint(.cold):

fn foo() void {
    @setCold(true);
    // ...
}

-->

fn foo() void {
    @branchHint(.cold);
    // ...
}

However, remember that @branchHint must be the first statement in the enclosing block, which in this case is the function. This restriction did not exist for @setCold, so non-trivial usages may require small refactors:

fn foo(comptime x: u8) void {
    if (x == 0) {
        @setCold(true);
    }
    // ...
}

-->

fn foo(comptime x: u8) void {
    @branchHint(if (x == 0) .cold else .none);
    // ...
}

philip-peterson pushed a commit to quine-global/ncdu that referenced this pull request Mar 5, 2025
New `@branchHint` builtin is more expressive than `@setCold`, therefore
latter was removed.
See ziglang/zig#21214 .

Signed-off-by: Eric Joldasov <[email protected]>
@mlugg mlugg deleted the branch-hint-and-export branch May 18, 2025 20:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking Implementing this issue could cause existing code to no longer compile or have different behavior. release notes This PR should be mentioned in the release notes.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants