Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[features/run] Parse primitive types #2733

Open
wants to merge 9 commits into
base: features/run
Choose a base branch
from
156 changes: 152 additions & 4 deletions runtime/common/RecordLogDecoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,82 @@

#pragma once

#include "cudaq/utils/cudaq_utils.h"
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>

namespace cudaq {

/// QIR output schema
enum struct SchemaType { LABELED, ORDERED };
enum struct RecordType { HEADER, METADATA, OUTPUT, START, END };
enum struct OutputType { RESULT, BOOL, INT, DOUBLE };
enum struct ContainerType { ARRAY, TUPLE };

/// Simple decoder for translating QIR recorded results to a C++ binary data
/// structure.
class RecordLogDecoder {

private:
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: class means these are already private. I prefer private data members and member functions to come last since they are not part of the interface of the class, but implementation details. Putting them up front means the reader has to wade through them to find the interface, which is where one should start reading to figure out what the class is for and does.

std::vector<char> buffer;
SchemaType schema = SchemaType::ORDERED;
RecordType currentRecord;
OutputType currentOutput;

OutputType extractPrimitiveType(const std::string &label) {
if ('i' == label[0]) {
auto digits = std::stoi(label.substr(1));
if (1 == digits)
return OutputType::BOOL;
return OutputType::INT;
} else if ('f' == label[0]) {
return OutputType::DOUBLE;
}
throw std::runtime_error("Unknown datatype in label");
}

template <typename T>
void addPrimitiveRecord(T value) {
/// ASKME: Is this efficient?
std::size_t position = buffer.size();
buffer.resize(position + sizeof(T));
Copy link
Collaborator

Choose a reason for hiding this comment

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

For efficiency it might be better to call resize with an overestimate. That way the vector would only be resized every $i$ calls instead of potentially every time.

std::memcpy(buffer.data() + position, &value, sizeof(T));
}

void prcoessSingleRecord(const std::string &recValue,
const std::string &recLabel) {
if ((!recLabel.empty()) &&
(extractPrimitiveType(recLabel) != currentOutput))
throw std::runtime_error("Type mismatch in label");

switch (currentOutput) {
case OutputType::BOOL: {
bool value;
if ("true" == recValue)
value = true;
else if ("false" == recValue)
value = false;
else
throw std::runtime_error("Invalid boolean value");
addPrimitiveRecord<bool>(value);
break;
}
case OutputType::INT: {
addPrimitiveRecord<int>(std::stoi(recValue));
break;
}
case OutputType::DOUBLE: {
addPrimitiveRecord<double>(std::stod(recValue));
break;
}
default:
throw std::runtime_error("Unsupported output type");
}
}

public:
RecordLogDecoder() = default;

Expand All @@ -23,7 +92,89 @@ class RecordLogDecoder {
/// structure is created in a generic memory buffer. The buffer's address and
/// length may be queried and returned as a result.
void decode(const std::string &outputLog) {
// NYI
std::vector<std::string> lines = cudaq::split(outputLog, '\n');
if (lines.empty())
return;

for (auto line : lines) {
std::vector<std::string> entries = cudaq::split(line, '\t');
if (entries.empty())
continue;

if ("HEADER" == entries[0])
currentRecord = RecordType::HEADER;
else if ("METADATA" == entries[0])
currentRecord = RecordType::METADATA;
else if ("OUTPUT" == entries[0])
currentRecord = RecordType::OUTPUT;
else if ("START" == entries[0])
currentRecord = RecordType::START;
else if ("END" == entries[0])
currentRecord = RecordType::END;
else
throw std::runtime_error("Invalid data");

switch (currentRecord) {
case RecordType::HEADER: {
if ("schema_name" == entries[1]) {
if ("labeled" == entries[2])
schema = SchemaType::LABELED;
else if ("ordered" == entries[2])
schema = SchemaType::ORDERED;
else
throw std::runtime_error("Unknown schema type");
}
/// TODO: Check schema version
break;
}
case RecordType::METADATA:
// ignore metadata for now
break;
case RecordType::START:
// indicates start of a shot
break;
case RecordType::END: {
// indicates end of a shot
if (entries.size() < 2)
throw std::runtime_error("Missing shot status");
if ("0" != entries[1])
throw std::runtime_error("Cannot handle unsuccessful shot");
break;
}
case RecordType::OUTPUT: {
if (entries.size() < 3)
throw std::runtime_error("Insufficent data in a record");
if ((schema == SchemaType::LABELED) && (entries.size() != 4))
throw std::runtime_error(
"Unexpected record size for a labeled record");

std::string recType = entries[1];
std::string recValue = entries[2];
std::string recLabel = (entries.size() == 4) ? entries[3] : "";

if ("RESULT" == recType)
throw std::runtime_error("This type is not yet supported");
if ("TUPLE" == recType)
throw std::runtime_error("This type is not yet supported");
if ("ARRAY" == recType)
throw std::runtime_error("This type is not yet supported");

if ("BOOL" == recType)
currentOutput = OutputType::BOOL;
else if ("INT" == recType)
currentOutput = OutputType::INT;
else if ("DOUBLE" == recType)
currentOutput = OutputType::DOUBLE;
else
throw std::runtime_error("Invalid data");

prcoessSingleRecord(recValue, recLabel);
break;
}
default:
throw std::runtime_error("Unknown record type");
}
} // for line
}

/// Get a pointer to the data buffer. Note that the data buffer will be
Expand All @@ -34,8 +185,5 @@ class RecordLogDecoder {

/// Get the size of the data buffer (in bytes).
std::size_t getBufferSize() const { return buffer.size(); }

private:
std::vector<char> buffer;
};
} // namespace cudaq
18 changes: 9 additions & 9 deletions test/AST-Quake/cudaq_run.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ struct CliffHanger {
};

// clang-format off
// CHECK-LABEL: func.func @__nvqpp__mlirgen__K9() attributes {"cudaq-entrypoint", "cudaq-kernel"} {
// CHECK-LABEL: func.func @__nvqpp__mlirgen__K9() attributes {"cudaq-entrypoint", "cudaq-kernel", "qir-api"} {
// CHECK-DAG: %[[VAL_0:.*]] = arith.constant 4 : i64
// CHECK-DAG: %[[VAL_1:.*]] = arith.constant 3 : i64
// CHECK-DAG: %[[VAL_2:.*]] = arith.constant 2 : i64
Expand Down Expand Up @@ -138,7 +138,7 @@ struct CliffHanger {
// CHECK: }

// CHECK-LABEL: func.func @__nvqpp__mlirgen__function_kernel_of_truth.
// CHECK-SAME: () attributes {"cudaq-entrypoint", "cudaq-kernel", no_this} {
// CHECK-SAME: () attributes {"cudaq-entrypoint", "cudaq-kernel", no_this, "qir-api"} {
// CHECK: %[[VAL_0:.*]] = arith.constant true
// CHECK: %[[VAL_1:.*]] = cc.string_literal "i1" : !cc.ptr<!cc.array<i8 x 3>>
// CHECK: %[[VAL_2:.*]] = cc.cast %[[VAL_1]] : (!cc.ptr<!cc.array<i8 x 3>>) -> !cc.ptr<i8>
Expand All @@ -147,15 +147,15 @@ struct CliffHanger {
// CHECK: }

// CHECK-LABEL: func.func @__nvqpp__mlirgen__function_kernel_of_corn.
// CHECK-SAME: () attributes {"cudaq-entrypoint", "cudaq-kernel", no_this} {
// CHECK-SAME: () attributes {"cudaq-entrypoint", "cudaq-kernel", no_this, "qir-api"} {
// CHECK: %[[VAL_0:.*]] = arith.constant -559038737 : i64
// CHECK: %[[VAL_1:.*]] = cc.string_literal "i32" : !cc.ptr<!cc.array<i8 x 4>>
// CHECK: %[[VAL_2:.*]] = cc.cast %[[VAL_1]] : (!cc.ptr<!cc.array<i8 x 4>>) -> !cc.ptr<i8>
// CHECK: call @__quantum__rt__integer_record_output(%[[VAL_0]], %[[VAL_2]]) : (i64, !cc.ptr<i8>) -> ()
// CHECK: return
// CHECK: }

// CHECK-LABEL: func.func @__nvqpp__mlirgen__CliffDiver() attributes {"cudaq-entrypoint", "cudaq-kernel"} {
// CHECK-LABEL: func.func @__nvqpp__mlirgen__CliffDiver() attributes {"cudaq-entrypoint", "cudaq-kernel", "qir-api"} {
// CHECK: %[[VAL_0:.*]] = arith.constant 4.200000e+01 : f64
// CHECK: %[[VAL_1:.*]] = cc.string_literal "f64" : !cc.ptr<!cc.array<i8 x 4>>
// CHECK: %[[VAL_2:.*]] = cc.cast %[[VAL_1]] : (!cc.ptr<!cc.array<i8 x 4>>) -> !cc.ptr<i8>
Expand All @@ -164,15 +164,15 @@ struct CliffHanger {
// CHECK: }

// CHECK-LABEL: func.func @__nvqpp__mlirgen__function_kernel_of_wheat.
// CHECK-SAME: () attributes {"cudaq-entrypoint", "cudaq-kernel", no_this} {
// CHECK-SAME: () attributes {"cudaq-entrypoint", "cudaq-kernel", no_this, "qir-api"} {
// CHECK: %[[VAL_0:.*]] = arith.constant 13.100000381469727 : f64
// CHECK: %[[VAL_1:.*]] = cc.string_literal "f32" : !cc.ptr<!cc.array<i8 x 4>>
// CHECK: %[[VAL_2:.*]] = cc.cast %[[VAL_1]] : (!cc.ptr<!cc.array<i8 x 4>>) -> !cc.ptr<i8>
// CHECK: call @__quantum__rt__double_record_output(%[[VAL_0]], %[[VAL_2]]) : (f64, !cc.ptr<i8>) -> ()
// CHECK: return
// CHECK: }

// CHECK-LABEL: func.func @__nvqpp__mlirgen__CliffClimber() attributes {"cudaq-entrypoint", "cudaq-kernel"} {
// CHECK-LABEL: func.func @__nvqpp__mlirgen__CliffClimber() attributes {"cudaq-entrypoint", "cudaq-kernel", "qir-api"} {
// CHECK: %[[VAL_0:.*]] = arith.constant 99 : i64
// CHECK: %[[VAL_1:.*]] = cc.string_literal "i8" : !cc.ptr<!cc.array<i8 x 3>>
// CHECK: %[[VAL_2:.*]] = cc.cast %[[VAL_1]] : (!cc.ptr<!cc.array<i8 x 3>>) -> !cc.ptr<i8>
Expand All @@ -181,7 +181,7 @@ struct CliffHanger {
// CHECK: }

// CHECK-LABEL: func.func @__nvqpp__mlirgen__function_this_is_not_a_drill.
// CHECK-SAME: () attributes {"cudaq-entrypoint", "cudaq-kernel", no_this} {
// CHECK-SAME: () attributes {"cudaq-entrypoint", "cudaq-kernel", no_this, "qir-api"} {
// CHECK: %[[VAL_0:.*]] = arith.constant 123400000 : i64
// CHECK: %[[VAL_1:.*]] = cc.string_literal "i64" : !cc.ptr<!cc.array<i8 x 4>>
// CHECK: %[[VAL_2:.*]] = cc.cast %[[VAL_1]] : (!cc.ptr<!cc.array<i8 x 4>>) -> !cc.ptr<i8>
Expand All @@ -190,15 +190,15 @@ struct CliffHanger {
// CHECK: }

// CHECK-LABEL: func.func @__nvqpp__mlirgen__function_this_is_a_hammer.
// CHECK-SAME: () attributes {"cudaq-entrypoint", "cudaq-kernel", no_this} {
// CHECK-SAME: () attributes {"cudaq-entrypoint", "cudaq-kernel", no_this, "qir-api"} {
// CHECK: %[[VAL_0:.*]] = arith.constant 2387 : i64
// CHECK: %[[VAL_1:.*]] = cc.string_literal "i16" : !cc.ptr<!cc.array<i8 x 4>>
// CHECK: %[[VAL_2:.*]] = cc.cast %[[VAL_1]] : (!cc.ptr<!cc.array<i8 x 4>>) -> !cc.ptr<i8>
// CHECK: call @__quantum__rt__integer_record_output(%[[VAL_0]], %[[VAL_2]]) : (i64, !cc.ptr<i8>) -> ()
// CHECK: return
// CHECK: }

// CHECK-LABEL: func.func @__nvqpp__mlirgen__CliffHanger() attributes {"cudaq-entrypoint", "cudaq-kernel"} {
// CHECK-LABEL: func.func @__nvqpp__mlirgen__CliffHanger() attributes {"cudaq-entrypoint", "cudaq-kernel", "qir-api"} {
// CHECK-DAG: %[[VAL_0:.*]] = arith.constant 2 : i64
// CHECK-DAG: %[[VAL_1:.*]] = arith.constant true
// CHECK-DAG: %[[VAL_2:.*]] = arith.constant 747 : i32
Expand Down
1 change: 1 addition & 0 deletions unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ endif()

add_subdirectory(backends)
add_subdirectory(Optimizer)
add_subdirectory(output_record)

if (CUDAQ_ENABLE_PYTHON)
if (NOT Python_FOUND)
Expand Down
24 changes: 24 additions & 0 deletions unittests/output_record/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# ============================================================================ #
# Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. #
# All rights reserved. #
# #
# This source code and the accompanying materials are made available under #
# the terms of the Apache License 2.0 which accompanies this distribution. #
# ============================================================================ #

add_executable(test_record RecordParserTester.cpp)

if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT APPLE)
target_link_options(test_record PRIVATE -Wl,--no-as-needed)
endif()
target_include_directories(test_record PRIVATE ..)
target_link_libraries(test_record
PRIVATE
fmt::fmt-header-only
cudaq
fmt::fmt-header-only
cudaq-common
gtest_main
)

gtest_discover_tests(test_record)
52 changes: 52 additions & 0 deletions unittests/output_record/RecordParserTester.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*******************************************************************************
* Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. *
* All rights reserved. *
* *
* This source code and the accompanying materials are made available under *
* the terms of the Apache License 2.0 which accompanies this distribution. *
******************************************************************************/

#include "CUDAQTestUtils.h"
#include "common/RecordLogDecoder.h"
#include <cudaq.h>

CUDAQ_TEST(ParserTester, checkSingleBoolean) {
const std::string log = "OUTPUT\tBOOL\ttrue";
cudaq::RecordLogDecoder parser;
parser.decode(log);
auto *origBuffer = parser.getBufferPtr();
bool value;
std::memcpy(&value, origBuffer, sizeof(bool));
EXPECT_EQ(true, value);
}

CUDAQ_TEST(ParserTester, checkIntegers) {
const std::string log = "OUTPUT\tINT\t0\n"
"OUTPUT\tINT\t1\n"
"OUTPUT\tINT\t2\n";
cudaq::RecordLogDecoder parser;
parser.decode(log);
auto *origBuffer = parser.getBufferPtr();
std::size_t bufferSize = parser.getBufferSize();
EXPECT_EQ(3, bufferSize / sizeof(int));
int *buffer = static_cast<int *>(malloc(bufferSize));
std::memcpy(buffer, origBuffer, bufferSize);
for (int i = 0; i < 3; ++i)
EXPECT_EQ(i, buffer[i]);
}

CUDAQ_TEST(ParserTester, checkDoubles) {
const std::string log = "START\n"
"OUTPUT\tDOUBLE\t3.14\n"
"OUTPUT\tDOUBLE\t2.717\n"
"END\t0";
cudaq::RecordLogDecoder parser;
parser.decode(log);
auto *origBuffer = parser.getBufferPtr();
std::size_t bufferSize = parser.getBufferSize();
EXPECT_EQ(2, bufferSize / sizeof(double));
double *buffer = static_cast<double *>(malloc(bufferSize));
std::memcpy(buffer, origBuffer, bufferSize);
EXPECT_EQ(3.14, buffer[0]);
EXPECT_EQ(2.717, buffer[1]);
}