Assume StatusBuilder conversion to Status returns non-OK when non-empty. When an rvalue `StatusBuilder` is converted to `absl::Status`, the conversion delegates to the out-of-line `CreateStatusAndConditionallyLog` helper. Because that helper is out-of-line, the compiler could not previously prove that a non-empty `StatusBuilder` always produces a non-OK `absl::Status`. Consequently, whenever `RETURN_IF_ERROR` was used inside a function returning `absl::StatusOr` (or when constructing `absl::StatusOr` from a `StatusBuilder`), the `absl::StatusOr` constructor emitted a redundant check and crash-handler call on the error path to guard against an OK status. Maintain the invariant that `StatusBuilder` holds an allocated internal representation if and only if its status is non-OK by clearing the internal representation when `SetCode` or `SetErrorCode` is called with an OK status code (using out-of-line destruction in `SetErrorCode` so `Rep` destruction is not inlined at call sites). With that invariant established, add an optimizer assumption in the rvalue `absl::Status` conversion operator that a non-empty `StatusBuilder` always produces a non-OK `absl::Status`. This allows the compiler to eliminate the redundant status check when constructing `absl::StatusOr` from `StatusBuilder`, reducing code size by 18 bytes per function (and up to 44 bytes per `SetErrorCode` call site) and improving error-path latency by 3 to 8 ns in microbenchmarks. PiperOrigin-RevId: 1007675020
diff --git a/absl/status/status_builder.cc b/absl/status/status_builder.cc index 8b30f61..7e9cd4e 100644 --- a/absl/status/status_builder.cc +++ b/absl/status/status_builder.cc
@@ -90,6 +90,11 @@ ABSL_ATTRIBUTE_WEAK StatusBuilder& StatusBuilder::SetCode( absl::StatusCode code) & { + if (code == absl::StatusCode::kOk) { + // Maintain the invariant that `rep_ == nullptr` iff the status is OK. + rep_ = nullptr; + return *this; + } if (rep_ == nullptr) { rep_ = std::make_unique<StatusBuilder::Rep>( absl::Status(code, absl::string_view(), absl::SourceLocation()));
diff --git a/absl/status/status_builder.h b/absl/status/status_builder.h index 55ab7a8..4d6752a 100644 --- a/absl/status/status_builder.h +++ b/absl/status/status_builder.h
@@ -357,6 +357,16 @@ -> std::enable_if_t< std::is_void_v<decltype(AbslInternalSetErrorCode(*this, code))>, StatusBuilder&> { + if (static_cast<int>(code) == 0) { + // Maintain the invariant that `rep_ == nullptr` iff the status is OK, + // while moving `Rep` destruction out of line to avoid inlining `~Rep()` + // at call sites (matching `~StatusBuilder()`). + if (!IsKnownToBeEmpty()) { + Destroy(std::move(rep_)); + AssumeEmpty(); + } + return *this; + } AbslInternalSetErrorCode(*this, code); return *this; } @@ -936,7 +946,7 @@ } inline bool StatusBuilder::ok() const { - return rep_ == nullptr ? true : rep_->status.ok(); + return rep_ == nullptr; } inline absl::StatusCode StatusBuilder::code() const { @@ -950,7 +960,15 @@ if (IsKnownToBeEmpty()) { return absl::OkStatus(); } + const bool rep_not_null = rep_ != nullptr; absl::Status result = CreateStatusAndConditionallyLog(loc_, std::move(rep_)); + // `rep_` is non-null if and only if the builder holds a non-OK status, and + // `CreateStatusAndConditionallyLog` preserves non-OK status. Hint this to + // the optimizer so `StatusOr` constructors can elide their `!status.ok()` + // check when converting from a known non-OK `StatusBuilder` (such as on the + // `RETURN_IF_ERROR` error path). + [[maybe_unused]] const bool result_ok = result.ok(); + ABSL_ASSUME(!rep_not_null || !result_ok); // Tell the compiler that `rep_` was not filled again even if `this` escaped. AssumeEmpty(); return result;
diff --git a/absl/status/status_builder_test.cc b/absl/status/status_builder_test.cc index 28fd6b9..3ff40bf 100644 --- a/absl/status/status_builder_test.cc +++ b/absl/status/status_builder_test.cc
@@ -361,12 +361,44 @@ } } -TEST_F(StatusBuilderTest, SetErrorCode) { +TEST_F(StatusBuilderTest, SetCode) { StatusBuilder builder; - builder.SetCode(absl::StatusCode::kResourceExhausted); + builder.SetCode(absl::StatusCode::kResourceExhausted) << "extra"; LOG(INFO) << "Builder code: " << builder; EXPECT_FALSE(builder.ok()); EXPECT_EQ(builder.code(), absl::StatusCode::kResourceExhausted); + builder.SetCode(absl::StatusCode::kInternal); + EXPECT_FALSE(builder.ok()); + EXPECT_EQ(builder.code(), absl::StatusCode::kInternal); + builder.SetCode(absl::StatusCode::kOk); + EXPECT_TRUE(builder.ok()); + EXPECT_EQ(builder.code(), absl::StatusCode::kOk); + EXPECT_EQ(ToStatus(builder), absl::OkStatus()); + EXPECT_EQ(static_cast<absl::Status>(std::move(builder)), absl::OkStatus()); + + EXPECT_EQ(static_cast<absl::Status>(StatusBuilder(absl::CancelledError("msg")) + .SetCode(absl::StatusCode::kOk)), + absl::OkStatus()); +} + +TEST_F(StatusBuilderTest, SetErrorCode) { + StatusBuilder builder; + builder.SetErrorCode(absl::StatusCode::kResourceExhausted) << "extra"; + LOG(INFO) << "Builder code: " << builder; + EXPECT_FALSE(builder.ok()); + EXPECT_EQ(builder.code(), absl::StatusCode::kResourceExhausted); + builder.SetErrorCode(absl::StatusCode::kInternal); + EXPECT_FALSE(builder.ok()); + EXPECT_EQ(builder.code(), absl::StatusCode::kInternal); + builder.SetErrorCode(absl::StatusCode::kOk); + EXPECT_TRUE(builder.ok()); + EXPECT_EQ(builder.code(), absl::StatusCode::kOk); + EXPECT_EQ(ToStatus(builder), absl::OkStatus()); + EXPECT_EQ(static_cast<absl::Status>(std::move(builder)), absl::OkStatus()); + + EXPECT_EQ(static_cast<absl::Status>(StatusBuilder(absl::CancelledError("msg")) + .SetErrorCode(absl::StatusCode::kOk)), + absl::OkStatus()); } TEST_F(StatusBuilderTest, BuilderToStatusOrStatusShouldGiveErrorStatusOr) {