Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
36 changes: 7 additions & 29 deletions include/fusilli/support/logging.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#ifndef FUSILLI_SUPPORT_LOGGING_H
#define FUSILLI_SUPPORT_LOGGING_H

#include <fusilli/support/memstream.h>
#include <iree/base/status.h>

#include <cassert>
Expand All @@ -23,7 +24,6 @@
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <stdio.h>
#include <string>
#include <type_traits>
#include <unordered_map>
Expand All @@ -33,7 +33,8 @@
namespace fusilli {

// An RAII based adapter for writing to C++ `std::strings` using C-style
// `fprint`s.
// `fprint`s. This is a cross-platform implementation that works on both
// Linux and Windows.
//
// example usage:
// // C functions from IREE runtime api.
Expand All @@ -49,33 +50,10 @@ namespace fusilli {
// FUSILLI_LOG_LABEL_RED("try_thing failure: " << err);
// }
// }
struct FprintToString {
char *buffer = nullptr;
size_t size = 0;
FILE *stream = nullptr;
std::string &output;

explicit FprintToString(std::string &output)
: stream(open_memstream(&buffer, &size)), output(output) {}

~FprintToString() {
if (stream) {
fclose(stream);
if (buffer) {
output.assign(buffer, size);
free(buffer);
}
}
}

operator FILE *() { return stream; }

// Delete all other constructors.
FprintToString(FprintToString &&other) noexcept = delete;
FprintToString &operator=(FprintToString &&) noexcept = delete;
FprintToString(const FprintToString &) = delete;
FprintToString &operator=(const FprintToString &) = delete;
};
//
// Note: This is now an alias for FprintAdapter from memstream.h, which
// provides the cross-platform implementation.
using FprintToString = FprintAdapter;

enum class [[nodiscard]] ErrorCode : uint8_t {
OK,
Expand Down
219 changes: 219 additions & 0 deletions include/fusilli/support/memstream.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
// Copyright 2025 Advanced Micro Devices, Inc.
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

//===----------------------------------------------------------------------===//
//
// This file contains a cross-platform memory stream utility that provides
// a FILE* interface backed by memory. On Linux, it uses open_memstream.
// On Windows, it uses a pure C++ implementation with temporary files.
//
//===----------------------------------------------------------------------===//

#ifndef FUSILLI_SUPPORT_MEMSTREAM_H
#define FUSILLI_SUPPORT_MEMSTREAM_H

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>

#include "fusilli/support/target_platform.h"

#ifdef FUSILLI_PLATFORM_LINUX
#include <fcntl.h>
#include <vector>
#elif FUSILLI_PLATFORM_WINDOWS
#include <stdio.h>
#include <windows.h>
#else
#error "MemStream is only implemented for Windows and Linux platforms."
#endif

namespace fusilli {

// A cross-platform memory stream that provides a FILE* interface backed by
// dynamically growing memory. This allows C-style fprintf operations to write
// to an in-memory buffer that can later be retrieved as a std::string.
//
// On Linux/POSIX, this uses the native open_memstream function.
// On Windows, this uses a temporary file.
//
// Example usage:
// MemStream ms;
// if (ms.isValid()) {
// fprintf(ms.stream(), "Hello, %s!", "world");
// std::string result = ms.str(); // "Hello, world!"
// }
//
class MemStream {
public:
#ifdef FUSILLI_PLATFORM_WINDOWS
// Windows implementation using a temporary file.
MemStream() : stream_(nullptr), tempFilePath_() {
Comment on lines +53 to +54
Copy link
Member

Choose a reason for hiding this comment

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

The disk I/O can be significant compared to the pure memory operations in the linux case. What about alternatives like std::stringstream based implementation - would that work better here?

// Create a temporary file.
char tempPath[MAX_PATH];
char tempFileName[MAX_PATH];
if (GetTempPathA(MAX_PATH, tempPath) == 0) {
return;
}
if (GetTempFileNameA(tempPath, "mem", 0, tempFileName) == 0) {
return;
}
tempFilePath_ = tempFileName;
stream_ = fopen(tempFilePath_.c_str(), "w+b");
}

~MemStream() {
if (stream_) {
fclose(stream_);
stream_ = nullptr;
}
if (!tempFilePath_.empty()) {
std::remove(tempFilePath_.c_str());
}
}

FILE *stream() { return stream_; }
bool isValid() const { return stream_ != nullptr; }

// Retrieve the contents as a string.
std::string str() {
if (!stream_)
return "";

// Flush pending writes.
fflush(stream_);

// Get the size.
long currentPos = ftell(stream_);
fseek(stream_, 0, SEEK_END);
long size = ftell(stream_);
fseek(stream_, 0, SEEK_SET);

// Read the contents.
std::string result(static_cast<size_t>(size), '\0');
size_t bytesRead =
fread(result.data(), 1, static_cast<size_t>(size), stream_);
result.resize(bytesRead);

// Restore position.
fseek(stream_, currentPos, SEEK_SET);

return result;
}

// Get current size of the stream.
size_t size() {
if (!stream_)
return 0;
fflush(stream_);
long currentPos = ftell(stream_);
fseek(stream_, 0, SEEK_END);
long endPos = ftell(stream_);
fseek(stream_, currentPos, SEEK_SET);
return static_cast<size_t>(endPos);
}

private:
FILE *stream_;
std::string tempFilePath_;

#elif FUSILLI_PLATFORM_LINUX
// Linux/POSIX implementation using open_memstream.
MemStream() : buffer_(nullptr), size_(0), stream_(nullptr) {
stream_ = open_memstream(&buffer_, &size_);
}

~MemStream() {
if (stream_) {
fclose(stream_);
stream_ = nullptr;
}
if (buffer_) {
free(buffer_);
buffer_ = nullptr;
}
}

FILE *stream() { return stream_; }
bool isValid() const { return stream_ != nullptr; }

// Retrieve the contents as a string.
std::string str() {
if (!stream_)
return "";
fflush(stream_);
return std::string(buffer_, size_);
}

// Get current size of the stream.
size_t size() {
if (!stream_)
return 0;
fflush(stream_);
return size_;
}

private:
char *buffer_;
size_t size_;
FILE *stream_;
#else
#error "MemStream is only implemented for Windows and Linux platforms."
#endif

public:
// Implicit conversion to FILE* for convenience.
operator FILE *() { return stream(); }

// Delete copy/move operations to prevent resource management issues.
MemStream(const MemStream &) = delete;
MemStream &operator=(const MemStream &) = delete;
MemStream(MemStream &&) = delete;
MemStream &operator=(MemStream &&) = delete;
};

// An RAII adapter for writing to C++ std::strings using C-style fprints.
// This is useful for interfacing with C APIs that use FILE* for output.
//
// Example usage:
// std::string output;
// {
// FprintAdapter adapter(output);
// fprintf(adapter, "Value: %d", 42);
// } // adapter destructor copies content to output
// // output now contains "Value: 42"
//
class FprintAdapter {
public:
explicit FprintAdapter(std::string &output) : output_(output), ms_() {}

~FprintAdapter() {
if (ms_.isValid()) {
output_ = ms_.str();
}
}

FILE *stream() { return ms_.stream(); }
bool isValid() const { return ms_.isValid(); }

// Implicit conversion to FILE* for convenience.
operator FILE *() { return stream(); }

// Delete copy/move operations.
FprintAdapter(const FprintAdapter &) = delete;
FprintAdapter &operator=(const FprintAdapter &) = delete;
FprintAdapter(FprintAdapter &&) = delete;
FprintAdapter &operator=(FprintAdapter &&) = delete;

private:
std::string &output_;
MemStream ms_;
};

} // namespace fusilli

#endif // FUSILLI_SUPPORT_MEMSTREAM_H
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ add_fusilli_tests(
SRCS
test_cache.cpp
test_dllib.cpp
test_memstream.cpp
test_ssa_validation.cpp
DEPS
libfusilli
Expand Down
Loading
Loading