Files
TheRock/rocm-libraries/projects/rocthrust/test/bitwise_repro/bwr_db.hpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

322 lines
12 KiB
C++

// Copyright (C) 2024-2025 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#ifndef BWR_DB_HPP
#define BWR_DB_HPP
#include <string>
#include "bwr_utils.hpp"
#include <sqlite3.h>
/*! \brief Database that can be used to store function call information between runs.
* This allows us to test whether the input has changed between runs.
*/
class BitwiseReproDB
{
public:
/*! \brief Enum class used to control the mode the database operates in.
* There are two reasons that a row might not be found in the database:
* 1. There is a run-to-run reproducibility error.
* 2. The database entries for this architecture/rocm version/rocThrust version
* haven't been generated yet.
*
* This enum class allows us to distinguish between these two cases.
* *
* In test mode, if an entry is not found, it is not inserted. This allows
* errors to be detected (case 1 above).
*
* In generate_mode, if an entry is not found, it is inserted (case 2 above).
* No run-to-run errors will be detected while in generate mode (the match functions
* will always report that a match was found).
*/
enum class Mode
{
test_mode,
generate_mode
};
/*! \brief Database constructor. Will create the SQLite database file if it doesn't already exist (and db_path is not
* null). \param db_path Path to the database file (eg. "./repro.db"). If null, database won't be created. \param mode
* The database mode (generate or test). See the enum class above for details.
*/
BitwiseReproDB(const char* db_path, const BitwiseReproDB::Mode mode)
: m_db_conn(nullptr)
, m_insert_stmt(nullptr)
, m_match_stmt(nullptr)
, m_mode(mode)
{
if (!db_path)
{
throw std::runtime_error("No database path given (ROCTHRUST_REPRO_DB_PATH environment variable is not set).");
}
int ret = sqlite3_open(db_path, &m_db_conn);
if (ret != SQLITE_OK)
{
throw std::runtime_error("Cannot open run-to-run bitwise reproducibility database: " + std::string(db_path));
}
// Access to a database file may occur in parallel.
// Increase default sqlite timeout, so diferent process
// can wait for one another.
sqlite3_busy_timeout(m_db_conn, 30000);
// Set sqlite3 engine to WAL mode to avoid potential deadlocks with multiple
// concurrent processes (if a deadlock occurs, the busy timeout is not honored).
ret = sqlite3_exec(m_db_conn, "PRAGMA journal_mode = WAL", nullptr, nullptr, nullptr);
if (ret != SQLITE_OK)
{
throw std::runtime_error("Error setting WAL mode: " + std::string(sqlite3_errmsg(m_db_conn)));
}
// Create the rocthrust_test_run table if it doesn't already exist.
ret = sqlite3_exec(m_db_conn, BitwiseReproDB::get_create_table_sql().c_str(), nullptr, nullptr, nullptr);
if (ret != SQLITE_OK)
{
throw std::runtime_error("Error creating table: " + std::string(sqlite3_errmsg(m_db_conn)));
}
// Initialize prepared statements.
prepare_match_stmt();
prepare_insert_stmt();
}
/*! \brief Destructor - cleans up and closes the database connection.
*/
~BitwiseReproDB()
{
sqlite3_finalize(m_insert_stmt);
sqlite3_finalize(m_match_stmt);
sqlite3_close(m_db_conn);
}
/*! \brief Given a pair of input and output "tokens" (which uniquely identify a function call),
* looks for a match in the database. If the DB is in generate mode and a match is not found,
* the (input, output) token pair will be inserted into the database. In test mode, no insertion
* is performed.
*
* \param input_token String that uniquely identifies a function call's inputs. See bwr_utils.hpp for details.
* \param output_token String that uniquely identifies a function call's outputs. See bwr_utils.hpp for details.
* \param match_found [out] In test mode, set to true if the (input, output) token pair is already in the database.
* In generate mode, rows are inserted if they don't already exist, and this is always set to true.
* \param inserted [out] Set to true if a row was inserted.
*/
void match(const std::string& input_token, const std::string& output_token, bool& match_found, bool& inserted)
{
match_found = false;
inserted = false;
// A test_run is a convenience struct that encapsulates all the information in a single row of the database table.
// Create one using the given input/output pair.
const rocthrust_test_run test_run(input_token, output_token);
// Do a select to check for a matching existing row.
const int match_count = select(test_run);
// Note: Because of our database constraints (the unique index),
// we know that match_count will either be 0 or 1 here.
match_found = (match_count == 1);
// Only insert if we are in generate mode and an entry does
// not already exist.
if (m_mode == Mode::generate_mode && !match_found)
{
try
{
inserted = insert(test_run);
// If the insertion was successful, set match to true, since a matching row now exists.
match_found = inserted;
}
catch (const std::exception& e)
{
std::cerr << e.what() << '\n';
std::cerr << "input_token: " << input_token << std::endl;
std::cerr << "output_token: " << output_token << std::endl;
std::cerr << "match_found: " << int(match_found) << std::endl;
std::cerr << "inserted: " << int(inserted) << std::endl;
}
}
}
/*! \brief See above. This overload exists for convenience - you can call it when you don't need to check if anything
* was inserted. \return A bool indicating whether or not a match was found.
*/
bool match(const std::string& input_token, const std::string& output_token)
{
bool match_found;
bool inserted;
match(input_token, output_token, match_found, inserted);
return match_found;
}
private:
struct rocthrust_test_run
{
rocthrust_test_run(const std::string& input_token,
const std::string& output_token,
const std::string& rocm_version,
const std::string& rocthrust_version,
const std::string& gpu_arch)
: input_token(input_token)
, output_token(output_token)
, rocm_version(rocm_version)
, rocthrust_version(rocthrust_version)
, gpu_arch(gpu_arch)
{}
rocthrust_test_run(const std::string& input_token, const std::string& output_token)
: input_token(input_token)
, output_token(output_token)
, rocm_version(bwr_utils::get_rocm_version())
, rocthrust_version(bwr_utils::get_rocthrust_version())
, gpu_arch(bwr_utils::get_gpu_arch())
{}
std::string input_token;
std::string output_token;
std::string rocm_version;
std::string rocthrust_version;
std::string gpu_arch;
};
static const std::string get_create_table_sql()
{
return "CREATE TABLE IF NOT EXISTS rocthrust_test_run("
"input_token TEXT NOT NULL, "
"output_token TEXT NOT NULL, "
"rocm_version TEXT NOT NULL, "
"rocthrust_version TEXT NOT NULL, "
"gpu_arch TEXT NOT NULL);"
"CREATE UNIQUE INDEX IF NOT EXISTS id_index_unique_run ON rocthrust_test_run("
"input_token, rocm_version, rocthrust_version, gpu_arch);";
}
static const std::string get_insert_sql()
{
return "INSERT INTO rocthrust_test_run("
"input_token, output_token, rocm_version, rocthrust_version, gpu_arch) "
"VALUES (?, ?, ?, ?, ?);";
}
static const std::string get_match_sql()
{
return "SELECT COUNT(*) FROM rocthrust_test_run WHERE "
"input_token = ? AND output_token = ? AND rocm_version = ? AND rocthrust_version = ? AND gpu_arch = ?;";
}
void prepare_match_stmt()
{
static const std::string match_sql = get_match_sql();
const int ret = sqlite3_prepare_v2(m_db_conn, match_sql.c_str(), -1, &m_match_stmt, nullptr);
if (ret != SQLITE_OK)
{
throw std::runtime_error("Cannot prepare match statement: " + std::string(sqlite3_errmsg(m_db_conn)));
}
}
void prepare_insert_stmt()
{
static const std::string insert_sql = get_insert_sql();
const int ret = sqlite3_prepare_v2(m_db_conn, insert_sql.c_str(), -1, &m_insert_stmt, nullptr);
if (ret != SQLITE_OK)
{
throw std::runtime_error("Cannot prepare insert statement: " + std::string(sqlite3_errmsg(m_db_conn)));
}
}
int select(const rocthrust_test_run& test_run)
{
int count = 0;
bind_match_stmt(test_run);
const int ret = sqlite3_step(m_match_stmt);
if (ret != SQLITE_ROW)
{
throw std::runtime_error(
std::string("Error executing select statement: ") + std::string(sqlite3_errmsg(m_db_conn)));
}
// Note: select indices start at 0
count = sqlite3_column_int(m_match_stmt, 0);
return count;
}
bool insert(const rocthrust_test_run& test_run)
{
bind_insert_stmt(test_run);
const int ret = sqlite3_step(m_insert_stmt);
const bool inserted = (ret == SQLITE_DONE);
if (!inserted)
{
throw std::runtime_error(
std::string("Error executing insert statement: ") + std::string(sqlite3_errmsg(m_db_conn)));
}
return inserted;
}
void bind_insert_stmt(const rocthrust_test_run& test_run)
{
// Note: bind indices start at 1
sqlite3_reset(m_insert_stmt);
bind_text(m_insert_stmt, test_run.input_token, 1);
bind_text(m_insert_stmt, test_run.output_token, 2);
bind_text(m_insert_stmt, test_run.rocm_version, 3);
bind_text(m_insert_stmt, test_run.rocthrust_version, 4);
bind_text(m_insert_stmt, test_run.gpu_arch, 5);
}
void bind_match_stmt(const rocthrust_test_run& test_run)
{
// Note: bind indices start at 1
sqlite3_reset(m_match_stmt);
bind_text(m_match_stmt, test_run.input_token, 1);
bind_text(m_match_stmt, test_run.output_token, 2);
bind_text(m_match_stmt, test_run.rocm_version, 3);
bind_text(m_match_stmt, test_run.rocthrust_version, 4);
bind_text(m_match_stmt, test_run.gpu_arch, 5);
}
void bind_text(sqlite3_stmt* stmt, const std::string& text, const int index)
{
const int ret = sqlite3_bind_text(stmt, index, text.c_str(), -1, SQLITE_TRANSIENT);
if (ret != SQLITE_OK)
{
throw std::runtime_error(std::string(
"Error binding text field in insert statement:\n"
"index: "
+ std::to_string(index)
+ "\n"
"value: "
+ text));
}
}
sqlite3* m_db_conn;
sqlite3_stmt* m_insert_stmt;
sqlite3_stmt* m_match_stmt;
Mode m_mode;
};
#endif // BWR_DB_HPP