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
322 lines
12 KiB
C++
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
|