Skip to content

[features/run] Parse primitive types #2733

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

Merged
merged 10 commits into from
Mar 20, 2025
33 changes: 17 additions & 16 deletions runtime/common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@ set(LIBRARY_NAME cudaq-common)

set(COMMON_EXTRA_DEPS "")
set(COMMON_RUNTIME_SRC
CustomOp.cpp
CustomOp.cpp
Environment.cpp
Executor.cpp
Future.cpp
Logger.cpp
MeasureCounts.cpp
NoiseModel.cpp
Logger.cpp
MeasureCounts.cpp
NoiseModel.cpp
RecordLogDecoder.cpp
Resources.cpp
ServerHelper.cpp
ServerHelper.cpp
Trace.cpp
)

Expand All @@ -30,19 +31,19 @@ add_library(${LIBRARY_NAME} SHARED ${COMMON_RUNTIME_SRC})
set_property(GLOBAL APPEND PROPERTY CUDAQ_RUNTIME_LIBS ${LIBRARY_NAME})

# Setup the includes
target_include_directories(${LIBRARY_NAME}
PRIVATE .
target_include_directories(${LIBRARY_NAME}
PRIVATE .
$<BUILD_INTERFACE:${LLVM_INCLUDE_DIRS}>
${CMAKE_SOURCE_DIR}/tpls/eigen
${CMAKE_SOURCE_DIR}/tpls/eigen
${CMAKE_SOURCE_DIR}/runtime)

# Link privately to all dependencies
target_link_libraries(${LIBRARY_NAME} PUBLIC cudaq-spin PRIVATE spdlog::spdlog)

# Bug in GCC 12 leads to spurious warnings (-Wrestrict)
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105329
if (CMAKE_COMPILER_IS_GNUCXX
AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 12.0.0
if (CMAKE_COMPILER_IS_GNUCXX
AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 12.0.0
AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 13.0.0)
target_compile_options(${LIBRARY_NAME} PUBLIC --param=evrp-mode=legacy)
endif()
Expand Down Expand Up @@ -76,11 +77,11 @@ if(EXISTS "$ENV{CURL_INSTALL_PREFIX}/cacert.pem")
install(FILES "$ENV{CURL_INSTALL_PREFIX}/cacert.pem" DESTINATION .)
endif()

## ----- Runtime MLIR Library -----
## We need support in the runtime for compiler-level things,
## but we have to collate this functionality in one place to avoid
## LLVM starting up static Options more than once. Create a library
## here called cudaq-mlir-runtime that cudaq-builder and cudaq-rest-qpu
## ----- Runtime MLIR Library -----
## We need support in the runtime for compiler-level things,
## but we have to collate this functionality in one place to avoid
## LLVM starting up static Options more than once. Create a library
## here called cudaq-mlir-runtime that cudaq-builder and cudaq-rest-qpu
## can link to and get MLIR/LLVM things uniformly

add_library(cudaq-mlir-runtime
Expand Down Expand Up @@ -108,7 +109,7 @@ target_include_directories(cudaq-mlir-runtime
${CMAKE_SOURCE_DIR}/tpls/fmt/include
${CMAKE_SOURCE_DIR}/runtime)

target_link_libraries(cudaq-mlir-runtime
target_link_libraries(cudaq-mlir-runtime
PUBLIC
nvqir
CCDialect
Expand Down
116 changes: 116 additions & 0 deletions runtime/common/RecordLogDecoder.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*******************************************************************************
* 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 "RecordLogDecoder.h"

void cudaq::RecordLogDecoder::decode(const std::string &outputLog) {
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");

processSingleRecord(recValue, recLabel);
} break;
}
} // for line
}

void cudaq::RecordLogDecoder::processSingleRecord(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");
}
}
43 changes: 40 additions & 3 deletions runtime/common/RecordLogDecoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,22 @@

#pragma once

#include "cudaq/utils/cudaq_utils.h"
#include <algorithm>
#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 {
Expand All @@ -22,9 +34,7 @@ class RecordLogDecoder {
/// binary data structure that is compatible with the C++ host code. The data
/// 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
}
void decode(const std::string &outputLog);

/// Get a pointer to the data buffer. Note that the data buffer will be
/// deallocated as soon as the RecordLogDecoder object is deconstructed.
Expand All @@ -36,6 +46,33 @@ class RecordLogDecoder {
std::size_t getBufferSize() const { return buffer.size(); }

private:
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 +
std::max(static_cast<std::size_t>(128), sizeof(T)));
std::memcpy(buffer.data() + position, &value, sizeof(T));
}

void processSingleRecord(const std::string &recValue,
const std::string &recLabel);

std::vector<char> buffer;
SchemaType schema = SchemaType::ORDERED;
RecordType currentRecord;
OutputType currentOutput;
};
} // 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)
Loading
Loading