Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
Unreleased
----------
- Task 06: Encapsulated MoveGen diagnostics via `SolverContext`; removed stray direct calls; all tests green and merged.
- Task 07: Migrated heuristic sorting helpers to use `HeuristicContext` snapshots; static analysis addressed; merged with tests green.
- Task 08 (in progress):
- Utilities: Introduced header-only `Utilities` with per-instance RNG (`std::mt19937`), an append-only log buffer, and minimal `Stats` counters.
- Deterministic seeding exposed via `SolverConfig.rngSeed` and `SolverContext::utilities()` accessors.
- Optional logging under `DDS_UTILITIES_LOG`: compact entries for TransTable lifecycle (create/dispose/clear/resize) and context ops (reset-for-solve, reset-best-moves-lite).
- Optional stats under `DDS_UTILITIES_STATS`: counters `tt_creates` and `tt_disposes` incremented at lifecycle points.
- Feature detection helpers `Utilities::log_enabled()` and `Utilities::stats_enabled()` added, with tests for enabled/disabled variants.
- Bazel wiring: dedicated testable targets enable flags only for targeted tests; default builds remain unaffected.
- Tests added: RNG determinism; logging enabled/disabled; stats enabled/disabled; context operation logs; feature-flag detection.

Release Notes DDS 2.9.1
-----------------------
test/print.cpp: Fixed bug in equals_to_string() noticed by tucsu.
Expand Down
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,28 @@ Version 2.9.0 has no known bugs.

Please report bugs to bo.haglund@bahnhof.se and soren.hein@gmail.com.


Utilities (RNG, logging, stats)
================================

Task 08 introduced an instance-scoped Utilities bundle and opt-in diagnostics. These changes are behavior-neutral by default.

- Per-instance RNG
- `dds::Utilities` (header-only) lives under `library/src/system/util/Utilities.h`.
- `SolverContext` exposes `utilities()` with access to a `std::mt19937` RNG.
- Deterministic seeding is available via `SolverConfig.rngSeed` on `SolverContext` construction.

- Optional logging (compile-time)
- When compiled with `DDS_UTILITIES_LOG`, `SolverContext` appends compact entries for internal lifecycle (e.g., TransTable create/dispose/clear/resize, context resets/best-move reset).
- Log lines are available via `ctx.utilities().logBuffer()` and can be cleared with `logClear()`.

- Optional stats (compile-time)
- When compiled with `DDS_UTILITIES_STATS`, `Utilities::Stats` exposes small counters (e.g., `tt_creates`, `tt_disposes`) incremented from `SolverContext` lifecycle.
- Stats can be reset with `stats_reset()`.

Testing and build variants
- Default builds do not enable these flags and thus incur no overhead.
- Tests enable flags via dedicated Bazel targets so the main library remains unaffected:
- Logging: `//library/src/system:system_util_log`, `//library/src:testable_dds_util_log`.
- Stats: `//library/src/system:system_util_stats`, `//library/src:testable_dds_util_stats`.

48 changes: 48 additions & 0 deletions library/src/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ cc_library(
"//library/tests:__pkg__",
"//library/tests/heuristic_sorting:__pkg__",
"//library/tests/system:__pkg__",
"//library/tests/utility:__pkg__",
"//library/tests/regression/heuristic_sorting:__pkg__",
],
deps = [
Expand All @@ -47,3 +48,50 @@ cc_library(
],
)

# Variant of testable_dds with Utilities logging compiled in.
cc_library(
name = "testable_dds_util_log",
srcs = glob(["*.cpp", "*.h"]),
hdrs = glob(["*.h"]),
copts = DDS_CPPOPTS,
linkopts = DDS_LINKOPTS,
local_defines = DDS_LOCAL_DEFINES,
include_prefix = "dds",
visibility = [
"//:__pkg__",
"//library/tests/system:__pkg__",
],
deps = [
"//library/src/data_types:dds_types",
"//library/src/utility:constants",
"//library/src/utility:lookup_tables",
"//library/src/heuristic_sorting",
"//library/src/trans_table",
"//library/src/moves:moves",
"//library/src/system:system_util_log",
],
)

cc_library(
name = "testable_dds_util_stats",
srcs = glob(["*.cpp", "*.h"]),
hdrs = glob(["*.h"]),
copts = DDS_CPPOPTS,
linkopts = DDS_LINKOPTS,
local_defines = DDS_LOCAL_DEFINES,
include_prefix = "dds",
visibility = [
"//:__pkg__",
"//library/tests/system:__pkg__",
],
deps = [
"//library/src/data_types:dds_types",
"//library/src/utility:constants",
"//library/src/utility:lookup_tables",
"//library/src/heuristic_sorting",
"//library/src/trans_table",
"//library/src/moves:moves",
"//library/src/system:system_util_stats",
],
)

42 changes: 41 additions & 1 deletion library/src/system/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ load("@rules_cc//cc:defs.bzl", "cc_library")
cc_library(
name = "system",
srcs = glob(["*.cpp"]),
hdrs = glob(["*.h"]),
hdrs = glob(["*.h", "util/*.h"]),
visibility = ["//visibility:public"],
includes = [".."],
deps = [
Expand All @@ -13,9 +13,49 @@ cc_library(
"//library/src/data_types:dds_types",
"//library/src/trans_table",
"//library/src/moves:moves",
# Utilities lives under system/util (header-only for now)
],
include_prefix = "system",
copts = DDS_CPPOPTS,
linkopts = DDS_LINKOPTS,
local_defines = DDS_LOCAL_DEFINES + DDS_SCHEDULER_DEFINE,
)

# Variant with Utilities logging enabled for tests that assert log behavior
cc_library(
name = "system_util_log",
srcs = glob(["*.cpp"]),
hdrs = glob(["*.h", "util/*.h"]),
visibility = ["//visibility:public"],
includes = [".."],
deps = [
"//library/src/utility:constants",
"//library/src/utility:lookup_tables",
"//library/src/data_types:dds_types",
"//library/src/trans_table",
"//library/src/moves:moves",
],
include_prefix = "system",
copts = DDS_CPPOPTS,
linkopts = DDS_LINKOPTS,
local_defines = DDS_LOCAL_DEFINES + DDS_SCHEDULER_DEFINE + ["DDS_UTILITIES_LOG"],
)

cc_library(
name = "system_util_stats",
srcs = glob(["*.cpp"]),
hdrs = glob(["*.h", "util/*.h"]),
visibility = ["//visibility:public"],
includes = [".."],
deps = [
"//library/src/utility:constants",
"//library/src/utility:lookup_tables",
"//library/src/data_types:dds_types",
"//library/src/trans_table",
"//library/src/moves:moves",
],
include_prefix = "system",
copts = DDS_CPPOPTS,
linkopts = DDS_LINKOPTS,
local_defines = DDS_LOCAL_DEFINES + DDS_SCHEDULER_DEFINE + ["DDS_UTILITIES_STATS"],
)
38 changes: 38 additions & 0 deletions library/src/system/SolverContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "data_types/dds.h" // THREADMEM_* defaults
#include <cstdlib>
#include <iostream>
#include <cstdio>
#include <unordered_map>

namespace {
Expand Down Expand Up @@ -85,6 +86,20 @@ TransTable* SolverContext::transTable() const
created->SetMemoryMaximum(maxMB);
created->MakeTT();

#ifdef DDS_UTILITIES_LOG
// Append a tiny debug entry indicating TT creation and chosen kind/sizes.
{
char buf[96];
Copy link

Copilot AI Oct 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Magic number 96 for buffer size should be defined as a named constant to improve maintainability and make the buffer size choice explicit.

Copilot uses AI. Check for mistakes.
const char kch = (kind == TTKind::Small ? 'S' : 'L');
std::snprintf(buf, sizeof(buf), "tt:create|%c|%d|%d", kch, defMB, maxMB);
Comment on lines +92 to +94
Copy link

Copilot AI Oct 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Magic number 96 for buffer size should be documented or replaced with a named constant to explain why this size was chosen.

Copilot uses AI. Check for mistakes.
utilities().logAppend(std::string(buf));
}
#endif

#ifdef DDS_UTILITIES_STATS
utilities().util().stats().tt_creates++;
#endif

// Attach to registry
registry()[thr_] = created;
}
Expand Down Expand Up @@ -148,6 +163,13 @@ void SolverContext::DisposeTransTable() const
auto it = registry().find(thr_);
if (it != registry().end())
{
#ifdef DDS_UTILITIES_LOG
// Append a tiny debug entry indicating TT disposal.
utilities().logAppend("tt:dispose");
#endif
#ifdef DDS_UTILITIES_STATS
utilities().util().stats().tt_disposes++;
#endif
delete it->second;
it->second = nullptr;
registry().erase(it);
Expand All @@ -158,6 +180,9 @@ SolverContext::~SolverContext() = default;

void SolverContext::ResetForSolve() const
{
#ifdef DDS_UTILITIES_LOG
utilities().logAppend("ctx:reset_for_solve");
#endif
if (auto* tt = maybeTransTable())
tt->ResetMemory(TT_RESET_FREE_MEMORY);
if (!thr_) return;
Expand Down Expand Up @@ -185,12 +210,22 @@ void SolverContext::ResetForSolve() const

void SolverContext::ClearTT() const
{
#ifdef DDS_UTILITIES_LOG
utilities().logAppend("tt:clear");
#endif
if (auto* tt = maybeTransTable())
tt->ReturnAllMemory();
}

void SolverContext::ResizeTT(int defMB, int maxMB) const
{
#ifdef DDS_UTILITIES_LOG
{
char buf[64];
Copy link

Copilot AI Oct 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Magic number 64 should be defined as a named constant to clarify the buffer size requirement and improve maintainability.

Copilot uses AI. Check for mistakes.
std::snprintf(buf, sizeof(buf), "tt:resize|%d|%d", defMB, maxMB);
Comment on lines +224 to +225
Copy link

Copilot AI Oct 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Magic number 64 for buffer size should be documented or replaced with a named constant. Consider using the same buffer size as the tt:create logging for consistency.

Copilot uses AI. Check for mistakes.
utilities().logAppend(std::string(buf));
}
#endif
if (auto* tt = maybeTransTable())
{
if (maxMB < defMB) maxMB = defMB;
Expand All @@ -202,6 +237,9 @@ void SolverContext::ResizeTT(int defMB, int maxMB) const
// Lightweight reset matching legacy ResetBestMoves semantics.
void SolverContext::ResetBestMovesLite() const
{
#ifdef DDS_UTILITIES_LOG
utilities().logAppend("ctx:reset_best_moves_lite");
#endif
if (!thr_) return;
for (int d = 0; d <= 49; ++d)
{
Expand Down
36 changes: 34 additions & 2 deletions library/src/system/SolverContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ struct pos; // from dds/dds.h
struct relRanksType; // from dds/dds.h
struct trickDataType; // from data_types/dds.h
#include "trans_table/TransTable.h" // ensure complete type and enums
#include "system/util/Utilities.h" // instance-scoped RNG and logging
#include <string>
#include <vector>
#include <random>

// Minimal configuration scaffold for future expansion.
// TT configuration without depending on Memory headers.
Expand All @@ -28,23 +32,50 @@ struct SolverConfig
TTKind ttKind = TTKind::Small;
int ttMemDefaultMB = 0;
int ttMemMaximumMB = 0;
// Optional deterministic RNG seed (0 means "no explicit seed").
unsigned long long rngSeed = 0ULL;
};

class SolverContext
{
public:
explicit SolverContext(ThreadData* thread, SolverConfig cfg = {})
: thr_(thread), cfg_(cfg) {}
: thr_(thread), cfg_(cfg)
{
if (cfg_.rngSeed != 0ULL) utils_.seed(cfg_.rngSeed);
}

// Allow construction from const ThreadData* for read-only contexts
explicit SolverContext(const ThreadData* thread, SolverConfig cfg = {})
: thr_(const_cast<ThreadData*>(thread)), cfg_(cfg) {}
: thr_(const_cast<ThreadData*>(thread)), cfg_(cfg)
{
if (cfg_.rngSeed != 0ULL) utils_.seed(cfg_.rngSeed);
}

~SolverContext();

ThreadData* thread() const { return thr_; }
const SolverConfig& config() const { return cfg_; }

// --- Utilities facade ---
class UtilitiesContext {
public:
explicit UtilitiesContext(::dds::Utilities* util) : util_(util) {}
::dds::Utilities& util() { return *util_; }
const ::dds::Utilities& util() const { return *util_; }
std::mt19937& rng() { return util_->rng(); }
const std::mt19937& rng() const { return util_->rng(); }
void seedRng(unsigned long long seed) { util_->seed(seed); }
void logAppend(const std::string& s) { util_->log_append(s); }
const std::vector<std::string>& logBuffer() const { return util_->log_buffer(); }
void logClear() { util_->log_clear(); }
private:
::dds::Utilities* util_ = nullptr;
};

inline UtilitiesContext utilities() { return UtilitiesContext(&utils_); }
inline UtilitiesContext utilities() const { return UtilitiesContext(&utils_); }

TransTable* transTable() const;
TransTable* maybeTransTable() const;

Expand Down Expand Up @@ -179,6 +210,7 @@ class SolverContext
private:
ThreadData* thr_ = nullptr;
SolverConfig cfg_{};
mutable ::dds::Utilities utils_{};
};

double ThreadMemoryUsed();
Expand Down
72 changes: 72 additions & 0 deletions library/src/system/util/Utilities.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#ifndef DDS_SYSTEM_UTIL_UTILITIES_H
#define DDS_SYSTEM_UTIL_UTILITIES_H

#include <cstdint>
#include <random>
#include <string>
#include <vector>

namespace dds {

// A tiny, instance-scoped utility bundle holding RNG and a simple log buffer.
class Utilities {
public:
Utilities() = default;

// Compile-time feature detection helpers for tests and guarded code paths.
static constexpr bool log_enabled() {
#ifdef DDS_UTILITIES_LOG
return true;
#else
return false;
#endif
}

static constexpr bool stats_enabled() {
#ifdef DDS_UTILITIES_STATS
return true;
#else
return false;
#endif
}

// RNG: mt19937 seeded with a 64-bit seed for determinism across platforms.
void seed(uint64_t seed_value) {
rng_.seed(static_cast<std::mt19937::result_type>(seed_value));
}

std::mt19937& rng() { return rng_; }
const std::mt19937& rng() const { return rng_; }

// Logging: a very simple append-only buffer; callers can flush and clear.
void log_append(const std::string& s) { log_.push_back(s); }
const std::vector<std::string>& log_buffer() const { return log_; }
size_t log_size() const { return log_.size(); }
bool log_contains(const std::string& prefix) const {
for (const auto& line : log_) {
if (line.rfind(prefix, 0) == 0) return true; // prefix match
}
return false;
}
void log_clear() { log_.clear(); }

// Minimal stats: opt-in counters for smoke validation in tests.
struct Stats {
unsigned tt_creates = 0;
unsigned tt_disposes = 0;
};

const Stats& stats() const { return stats_; }
Stats& stats() { return stats_; }
Stats stats_snapshot() const { return stats_; }
void stats_reset() { stats_ = Stats{}; }

private:
std::mt19937 rng_{}; // default-constructed; seed via seed()
std::vector<std::string> log_{}; // minimal structured log lines
Stats stats_{}; // optional counters
};

} // namespace dds

#endif // DDS_SYSTEM_UTIL_UTILITIES_H
Loading
Loading