Skip to content

Commit

Permalink
#17754: Lower Indestructible to Metal, add guidance on using static v…
Browse files Browse the repository at this point in the history
…ars with non-trivial destructors (#17899)

### Ticket
#17754, #17607

### Problem description
Variables with static storage duration should have trivial destructors.
Add guidance on why this so, and lower `Indestructible` utility to
Metal, as the suggested alternative.

### What's changed
* Lower `Indestructible` from tt-train to Metal.
* Add guidance to best practices doc.
* Add comments and a test for `Indestructible`.

### Checklist
- [X] [All post
commit](https://github.com/tenstorrent/tt-metal/actions/runs/13350667483)
- [X] New/Existing tests provide coverage for changes
- [X] Checked that standalone tt-train compiles.
  • Loading branch information
omilyutin-tt authored Feb 20, 2025
1 parent bdb0bfc commit 42cf08b
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 46 deletions.
2 changes: 1 addition & 1 deletion CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ tt_metal/hw/firmware/src/*erisc* @aliuTT @ubcheema
tt_metal/hw/inc/ethernet/ @aliuTT @ubcheema
tt_metal/hw/inc/wormhole/eth_l1_address_map.h @aliuTT @ubcheema
tt_metal/third_party/tt_llk_* @rtawfik01 @ttmtrajkovic @rdjogoTT
tt_metal/tt_stl/ @patrickroberts @ayerofieiev-tt @dmakoviichuk-tt @sminakov-tt
tt_metal/tt_stl/ @patrickroberts @ayerofieiev-tt @dmakoviichuk-tt @sminakov-tt @omilyutin-tt

sfpi/ @pgkeller

Expand Down
55 changes: 53 additions & 2 deletions contributing/BestPractices.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Best Practices for C++20 Repository
# Best Practices for Contributing to TT Metal

## 1. Pass Complex Types by Const References

Expand Down Expand Up @@ -319,7 +319,7 @@ struct PadDimension {
```
Motivation
- **Bug Prevention:** Reduces the risk of bugs due to uninitialized variables.
- **Code Safety:** Ensures that all variables have a known value, leading to safer and more predictable code.
- **Safety:** Ensures that all variables have a known value, leading to safer and more predictable code.
- **Ease of Review:** Simplifies code reviews by making initialization explicit.

## 16. Use Early Exit for Contract Checks
Expand Down Expand Up @@ -354,3 +354,54 @@ void doSomething(...) {
- **Code Clarity:** Improves code clarity by reducing unnecessary nesting.
- **Maintainability:** Makes the code easier to maintain by focusing on the main logic once preconditions are validated.
- **Efficiency:** Potentially improves performance by avoiding unnecessary processing when contract conditions aren't met.

## 17. Avoid `static` variables with non-trivial destructors
### Practice
Avoid using `static` variables with non-trivial destructors. When applicable, use `tt::stl::Indestructible<T>` to create static objects with disabled destructor.

### Explanation
Objects with static storage duration (globals, static class members, or function-local statics) live from initialization until program termination.

A non-trivial destructor (i.e., one that is user-defined or virtual) may depend on the state of other objects, which might have already been destroyed by the time it is invoked. This can lead to undefined behavior or subtle bugs, especially in the multi-threaded environments.

An object is considered trivially destructible if it has no custom or virtual destructor and all its bases and non-static members are also trivially destructible. Examples include: fundamental types (pointers, int, float, etc.), arrays of trivially destructible types, variables marked with `constexpr`.

To ensure safe and predictable program termination, static objects should meet these criteria. If dynamic initialization is required, consider using function-local statics with `tt::stl::Indestructible<T>` that disables destruction.

### Motivation
- **Safety:** Prevents accessing objects after they have been destroyed.
- **Maintainability:** Simplifies tracking the lifetime of objects and helps avoid errors related to destruction ordering.

### Example
**Avoid:**
```cpp
// Bad: Using a static object with a non-trivial destructor.
static const std::map<int, std::string> kDeviceConfigFiles = {
{1, "n150.yaml"},
{2, "n300.yaml"},
{8, "t3000.yaml"}
};
```

**Prefer:**
```cpp
// Option 1: Use a trivial type for static data when possible.
constexpr std::string_view kData = "Trivial destructor! Good!";

constexpr uint32_t kMaxNumberOfCommandQueues = 2;

// Using array of trivially destructible types is OK.
constexpr std::array<int, 3> kDeviceIds = {1, 2, 8};

// Option 2: If dynamic initialization is required, use function-local statics with `Indestructible`.
const auto& get_device_configs() {
static tt::stl::Indestructible<std::map<int, std::string_view>> configs{
std::map<int, std::string_view>{
{1, "n150.yaml"},
{2, "n300.yaml"},
{8, "t3000.yaml"}
}
};
return configs.get();
}
```
1 change: 1 addition & 0 deletions tests/tt_metal/tt_metal/stl/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
set(UNIT_TESTS_STL_SRC
${CMAKE_CURRENT_SOURCE_DIR}/test_any_range.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_indestructible.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_slotmap.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_strong_type.cpp
)
Expand Down
25 changes: 25 additions & 0 deletions tests/tt_metal/tt_metal/stl/test_indestructible.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc.
//
// SPDX-License-Identifier: Apache-2.0

#include <gtest/gtest.h>
#include <gmock/gmock.h>

#include "tt_metal/tt_stl/indestructible.hpp"

namespace tt::stl {
namespace {

TEST(IndestructibleTest, Basic) {
struct DangerouslyDestructible {
~DangerouslyDestructible() {
// Wrapping in a lambda, as `FAIL()` returns `void`.
[]() { FAIL(); }();
}
};

Indestructible<DangerouslyDestructible> obj;
}

} // namespace
} // namespace tt::stl
2 changes: 1 addition & 1 deletion tt-train/sources/ttml/autograd/auto_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ uint32_t AutoContext::get_seed() const {
}

AutoContext& AutoContext::get_instance() {
static core::Indestructible<AutoContext> instance{};
static tt::stl::Indestructible<AutoContext> instance{};
return instance.get();
}
std::optional<NodeId> AutoContext::add_backward_node(GradFunction&& grad_function, std::span<NodeId> links) {
Expand Down
4 changes: 2 additions & 2 deletions tt-train/sources/ttml/autograd/auto_context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

#pragma once

#include <indestructible.hpp>
#include <memory>
#include <random>

#include "core/indestructible.hpp"
#include "core/mesh_device.hpp"
#include "graph.hpp"

Expand Down Expand Up @@ -62,7 +62,7 @@ class AutoContext {
tt::tt_metal::distributed::MeshShape m_mesh_shape = {1, 1};
std::unique_ptr<core::MeshDevice> m_device;

friend class core::Indestructible<AutoContext>;
friend class tt::stl::Indestructible<AutoContext>;
};

inline auto& ctx() {
Expand Down
40 changes: 0 additions & 40 deletions tt-train/sources/ttml/core/indestructible.hpp

This file was deleted.

51 changes: 51 additions & 0 deletions tt_metal/tt_stl/indestructible.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// SPDX-FileCopyrightText: (c) 2024 Tenstorrent AI ULC
//
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include <cstddef>
#include <new>
#include <utility>

namespace tt::stl {

// `Indestructible` is a wrapper around `T` that behaves like `T` but does not call the destructor of `T`.
// This is useful for creating objects with static storage duration: `Indestructible` avoids heap allocation, provides
// thread-safe construction, and ensures the destructor is no-op, so does not depend on any other objects.
//
//
// Example usage:
//
// const auto& get_object() {
// static Indestructible<MyObject> object;
// return object.get();
// }
//
template <typename T>
class Indestructible {
public:
template <typename... Args>
explicit Indestructible(Args&&... args) {
// Construct T in our aligned storage
new (&storage_) T(std::forward<Args>(args)...);
}

T& get() { return *std::launder(reinterpret_cast<T*>(&storage_)); }

const T& get() const { return *std::launder(reinterpret_cast<const T*>(&storage_)); }

// Disable copy and assignment
Indestructible(const Indestructible&) = delete;
Indestructible& operator=(const Indestructible&) = delete;

// Destructor does NOT call T's destructor.
// This leaves the object "indestructible."
~Indestructible() = default;

private:
// A buffer of std::byte with alignment of T and size of T
alignas(T) std::byte storage_[sizeof(T)];
};

} // namespace tt::stl

0 comments on commit 42cf08b

Please sign in to comment.