implement a basic logger as a part of lucore
I could have implemented a spdlog sink, however spdlog isn't setup for standard format header being a thing yet. This also introduces a temporary executable target for my own testing of lucore utilities.
This commit is contained in:
parent
daf29d4fd7
commit
22796b69bf
|
@ -4,6 +4,7 @@ project(lcpu-native
|
|||
)
|
||||
|
||||
add_subdirectory(projects/lucore)
|
||||
add_subdirectory(projects/lucore_test)
|
||||
|
||||
# RISC-V emulation library
|
||||
add_subdirectory(projects/riscv)
|
||||
|
|
|
@ -7,6 +7,7 @@ project(lucore
|
|||
|
||||
add_library(lucore
|
||||
src/Assert.cpp
|
||||
src/Logger.cpp
|
||||
)
|
||||
|
||||
target_compile_features(lucore PUBLIC cxx_std_20)
|
||||
|
|
|
@ -2,34 +2,30 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <cstdio>
|
||||
#include <format>
|
||||
|
||||
namespace lucore {
|
||||
|
||||
// TODO: wrapper which uses source_location (we don't need no macros anymore!)
|
||||
[[noreturn]] void ExitMsg(const char* fileName, int fileLine, const char* message);
|
||||
|
||||
}
|
||||
} // namespace lucore
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define LUCORE_ASSERT(expr, format, ...) \
|
||||
#define LUCORE_ASSERT(expr, fmt, ...) \
|
||||
if(!(expr)) [[unlikely]] { \
|
||||
char buffer[256]; \
|
||||
std::snprintf(&buffer[0], sizeof(buffer), \
|
||||
"[Lucore] Assertion \"%s\" failed with message: " format "\n", #expr, \
|
||||
##__VA_ARGS__); \
|
||||
::lucore::ExitMsg(__FILE__, __LINE__, &buffer[0]); \
|
||||
auto msg = std::format("Assertion \"{}\" failed with message: {}", #expr, \
|
||||
std::format(fmt, ##__VA_ARGS__)); \
|
||||
::lucore::ExitMsg(__FILE__, __LINE__, msg.c_str()); \
|
||||
}
|
||||
#else
|
||||
#define LUCORE_ASSERT(expr, format, ...)
|
||||
#endif
|
||||
|
||||
// CHECK() is always active, even in release builds
|
||||
#define LUCORE_CHECK(expr, format, ...) \
|
||||
#define LUCORE_CHECK(expr, fmt, ...) \
|
||||
if(!(expr)) [[unlikely]] { \
|
||||
char buffer[256]; \
|
||||
std::snprintf(&buffer[0], sizeof(buffer), \
|
||||
"[Lucore] Check \"%s\" failed with message: " format "\n", #expr, \
|
||||
##__VA_ARGS__); \
|
||||
::lucore::ExitMsg(__FILE__, __LINE__, &buffer[0]); \
|
||||
auto msg = std::format("Check \"{}\" failed with message: {}", #expr, \
|
||||
std::format(fmt, ##__VA_ARGS__)); \
|
||||
::lucore::ExitMsg(__FILE__, __LINE__, msg.c_str()); \
|
||||
}
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
//! Logging utilities for Lucore
|
||||
//! Using Standard C++ <format>
|
||||
|
||||
#include <format>
|
||||
#include <cstdint>
|
||||
|
||||
namespace lucore {
|
||||
|
||||
/// The Lucore logger.
|
||||
struct Logger {
|
||||
enum class MessageSeverity {
|
||||
Debug,
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
Fatal
|
||||
};
|
||||
|
||||
static constexpr std::string_view SeverityToString(MessageSeverity sev) {
|
||||
const char* table[] = {
|
||||
"Deb",
|
||||
"Inf",
|
||||
"Wrn",
|
||||
"Err",
|
||||
"Ftl"
|
||||
};
|
||||
return table[static_cast<std::size_t>(sev)];
|
||||
}
|
||||
|
||||
/// A sink.
|
||||
struct Sink {
|
||||
virtual void OutputMessage(MessageSeverity severity, std::string_view format, std::format_args args) = 0;
|
||||
};
|
||||
|
||||
|
||||
static Logger& The();
|
||||
|
||||
Logger() = default;
|
||||
Logger(const Logger&) = delete;
|
||||
Logger(Logger&&) = delete;
|
||||
|
||||
void AttachSink(Sink& sink);
|
||||
|
||||
MessageSeverity GetLogLevel() const {
|
||||
return logLevel;
|
||||
}
|
||||
|
||||
void SetLogLevel(MessageSeverity newLogLevel) {
|
||||
logLevel = newLogLevel;
|
||||
}
|
||||
|
||||
template<class... Args>
|
||||
inline void Debug(std::string_view fmt, Args... args) {
|
||||
VOut(MessageSeverity::Debug, fmt, std::make_format_args(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
template<class... Args>
|
||||
inline void Info(std::string_view fmt, Args... args) {
|
||||
VOut(MessageSeverity::Info, fmt, std::make_format_args(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
template<class... Args>
|
||||
inline void Warning(std::string_view fmt, Args... args) {
|
||||
VOut(MessageSeverity::Warning, fmt, std::make_format_args(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
template<class... Args>
|
||||
inline void Error(std::string_view fmt, Args... args) {
|
||||
VOut(MessageSeverity::Error, fmt, std::make_format_args(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
template<class... Args>
|
||||
inline void Fatal(std::string_view fmt, Args... args) {
|
||||
VOut(MessageSeverity::Fatal, fmt, std::make_format_args(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
private:
|
||||
void VOut(MessageSeverity severity, std::string_view format, std::format_args args);
|
||||
|
||||
MessageSeverity logLevel{MessageSeverity::Info};
|
||||
|
||||
Sink* sinks[4];
|
||||
std::uint8_t sinkCount;
|
||||
};
|
||||
|
||||
/// A logger sink implementation that prints to standard output.
|
||||
struct StdoutSink : public Logger::Sink {
|
||||
static StdoutSink& The();
|
||||
|
||||
virtual void OutputMessage(Logger::MessageSeverity severity, std::string_view format, std::format_args args) override;
|
||||
};
|
||||
|
||||
/// Attach stdout to the logger.
|
||||
void LoggerAttachStdout();
|
||||
|
||||
}
|
|
@ -9,9 +9,6 @@ namespace lucore::detail {
|
|||
/// Sentinel value to explicitly not populate an OptionalRef.
|
||||
inline static Nullref_t NullRef {};
|
||||
|
||||
template <class T>
|
||||
struct OptionalRef; // sfinae on non-reference types
|
||||
|
||||
/// Like std::optional<T>, but optimized specifically for references to objects.
|
||||
///
|
||||
/// Additionally, as a bonus, since the repressentation is so simple, this is
|
||||
|
@ -21,7 +18,7 @@ namespace lucore::detail {
|
|||
/// Treat this class like [std::reference_wrapper].
|
||||
/// Do *not* give this class invalid references.
|
||||
template <class T>
|
||||
struct OptionalRef<T&> {
|
||||
struct OptionalRef {
|
||||
constexpr OptionalRef() : ptr(nullptr) {
|
||||
}
|
||||
|
||||
|
@ -34,7 +31,7 @@ namespace lucore::detail {
|
|||
|
||||
// polymorphic downconstruction from another OptionalRef<U>
|
||||
template <class U>
|
||||
constexpr OptionalRef(const OptionalRef<U&>& other) : ptr(&other.ptr) {
|
||||
constexpr OptionalRef(const OptionalRef<U>& other) : ptr(&other.ptr) {
|
||||
}
|
||||
|
||||
constexpr T& Value() const {
|
||||
|
|
|
@ -2,13 +2,12 @@
|
|||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <lucore/Logger.hpp>
|
||||
|
||||
namespace lucore {
|
||||
|
||||
[[noreturn]] void ExitMsg(const char* fileName, int fileLine, const char* message) {
|
||||
// TODO: move this to logger functionality of lucore (the native module will end up
|
||||
// containing a Sink implementation that funnels to either gmod or tier libs..)
|
||||
std::puts(message);
|
||||
Logger::The().Fatal("{}", message);
|
||||
std::quick_exit(0xAF);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
#include <format>
|
||||
#include <lucore/Logger.hpp>
|
||||
|
||||
|
||||
namespace lucore {
|
||||
|
||||
Logger& Logger::The() {
|
||||
static Logger logger;
|
||||
return logger;
|
||||
}
|
||||
|
||||
void Logger::AttachSink(Sink& sink) {
|
||||
sinks[sinkCount++] = &sink;
|
||||
}
|
||||
|
||||
void Logger::VOut(MessageSeverity severity, std::string_view format, std::format_args args) {
|
||||
if(severity < logLevel)
|
||||
return;
|
||||
|
||||
for(int i = 0; i < sinkCount; ++i)
|
||||
sinks[i]->OutputMessage(severity, format, args);
|
||||
}
|
||||
|
||||
StdoutSink& StdoutSink::The() {
|
||||
static StdoutSink sink;
|
||||
return sink;
|
||||
}
|
||||
|
||||
void StdoutSink::OutputMessage(Logger::MessageSeverity severity, std::string_view format, std::format_args args) {
|
||||
auto message = std::vformat(format, args);
|
||||
std::printf("[Lucore Stdout/%s] %s\n", Logger::SeverityToString(severity).data(), message.c_str());
|
||||
}
|
||||
|
||||
void LoggerAttachStdout() {
|
||||
Logger::The().AttachSink(StdoutSink::The());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
# this is a temporary project to test lucore functionality while it's in development
|
||||
|
||||
add_executable(lucore_test
|
||||
main.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(lucore_test PUBLIC
|
||||
lucore::lucore)
|
|
@ -0,0 +1,28 @@
|
|||
#include <format>
|
||||
#include <lucore/Assert.hpp>
|
||||
#include <lucore/Logger.hpp>
|
||||
|
||||
/// Sample implementation of lucore logger sink.
|
||||
struct MySink : public lucore::Logger::Sink {
|
||||
void OutputMessage(lucore::Logger::MessageSeverity sev, std::string_view fmt, std::format_args args) override {
|
||||
std::printf("[My Sink] [%s] %s\n", lucore::Logger::SeverityToString(sev).data(), std::vformat(fmt, args).c_str());
|
||||
}
|
||||
|
||||
static MySink& The() {
|
||||
static MySink sink;
|
||||
return sink;
|
||||
}
|
||||
};
|
||||
|
||||
int main() {
|
||||
lucore::LoggerAttachStdout();
|
||||
auto& logger = lucore::Logger::The();
|
||||
|
||||
logger.AttachSink(MySink::The()); // attach our sink
|
||||
|
||||
logger.Info("Hello World {}", 123.456);
|
||||
logger.Warning("sample warning");
|
||||
logger.Error("Smaple Error");
|
||||
|
||||
LUCORE_CHECK(false, "should appear");
|
||||
}
|
|
@ -21,8 +21,6 @@ namespace riscv {
|
|||
// Used to allow bus devices to know when they are attached to a memory bus,
|
||||
// and ultimately, an instance of a System
|
||||
virtual void Attached(Bus* memoryBus, AddressT baseAddress) = 0;
|
||||
virtual AddressT BaseAddress() const = 0; // TODO(cleanup): Make this non-virtual?
|
||||
|
||||
|
||||
/// Is this device clocked?
|
||||
virtual bool Clocked() const { return false; }
|
||||
|
@ -35,6 +33,9 @@ namespace riscv {
|
|||
// from devices. This needs to be implemented to facilitate the
|
||||
// implementation of the timer device as an actual Device implmentation
|
||||
// instead of poorly hard-coding it into the CPU core logic.
|
||||
//
|
||||
// Also, default implementations of Peek* and Poke* should trap.
|
||||
|
||||
|
||||
// Peek() -> reads a value from this device.
|
||||
virtual u8 PeekByte(AddressT offset) = 0;
|
||||
|
@ -75,11 +76,11 @@ namespace riscv {
|
|||
void PokeWord(AddressT address, u32 value);
|
||||
private:
|
||||
|
||||
lucore::OptionalRef<Device&> FindDeviceForAddress(AddressT address) const;
|
||||
lucore::OptionalRef<Device> FindDeviceForAddress(AddressT address) const;
|
||||
|
||||
CPU* attachedCpu{};
|
||||
|
||||
// TODO: if this ends up being a hotpath replace with robinhood unordered map
|
||||
// TODO: if this ends up being a hotpath replace with ankerl::unordered_dense
|
||||
std::unordered_map<AddressT, Device*> mapped_devices;
|
||||
};
|
||||
|
||||
|
|
|
@ -33,41 +33,41 @@ namespace riscv {
|
|||
|
||||
u8 Bus::PeekByte(AddressT address) {
|
||||
if(auto opt = FindDeviceForAddress(address); opt)
|
||||
return opt->PeekByte(address - opt->BaseAddress());
|
||||
return opt->PeekByte(address);
|
||||
return -1;
|
||||
}
|
||||
|
||||
u16 Bus::PeekShort(AddressT address) {
|
||||
if(auto opt = FindDeviceForAddress(address); opt)
|
||||
return opt->PeekShort(address - opt->BaseAddress());
|
||||
return opt->PeekShort(address);
|
||||
return -1;
|
||||
}
|
||||
|
||||
u32 Bus::PeekWord(AddressT address) {
|
||||
if(auto opt = FindDeviceForAddress(address); opt)
|
||||
return opt->PeekWord(address - opt->BaseAddress());
|
||||
return opt->PeekWord(address);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void Bus::PokeByte(AddressT address, u8 value) {
|
||||
if(auto opt = FindDeviceForAddress(address); opt)
|
||||
return opt->PokeByte(address - opt->BaseAddress(), value);
|
||||
return opt->PokeByte(address, value);
|
||||
}
|
||||
|
||||
void Bus::PokeShort(AddressT address, u16 value) {
|
||||
if(auto opt = FindDeviceForAddress(address); opt)
|
||||
return opt->PokeShort(address - opt->BaseAddress(), value);
|
||||
return opt->PokeShort(address, value);
|
||||
}
|
||||
|
||||
void Bus::PokeWord(AddressT address, u32 value) {
|
||||
if(auto opt = FindDeviceForAddress(address); opt)
|
||||
return opt->PokeWord(address - opt->BaseAddress(), value);
|
||||
return opt->PokeWord(address, value);
|
||||
}
|
||||
|
||||
lucore::OptionalRef<Bus::Device&> Bus::FindDeviceForAddress(AddressT address) const {
|
||||
lucore::OptionalRef<Bus::Device> Bus::FindDeviceForAddress(AddressT address) const {
|
||||
auto it = std::find_if(mapped_devices.begin(), mapped_devices.end(), [&](const auto& pair) {
|
||||
return
|
||||
// We can shorcut the region checking if the requested addess matches base address.
|
||||
// We can shorcut region checking if the requested addess matches base address.
|
||||
pair.first == address ||
|
||||
// If it doesn't we really can't, though.
|
||||
(address >= pair.first && address < pair.first + pair.second->Size());
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
#include <riscv/Bus.hpp>
|
||||
#include <span>
|
||||
|
||||
#include "riscv/Types.hpp"
|
||||
|
||||
namespace riscv {
|
||||
|
||||
|
@ -9,7 +6,7 @@ namespace riscv {
|
|||
|
||||
template <bool Rom>
|
||||
struct BasicMemoryDevice : public Bus::Device {
|
||||
BasicMemoryDevice(usize size) : memorySize(size) {
|
||||
BasicMemoryDevice(usize size) : Bus::Device(), memorySize(size) {
|
||||
memory = new u8[size];
|
||||
LUCORE_CHECK(memory, "Could not allocate buffer for memory device.");
|
||||
}
|
||||
|
@ -19,6 +16,10 @@ namespace riscv {
|
|||
delete[] memory;
|
||||
}
|
||||
|
||||
AddressT Size() const override {
|
||||
return memorySize;
|
||||
}
|
||||
|
||||
// Implementation of Device interface
|
||||
|
||||
void Attached(Bus* bus, AddressT base) override {
|
||||
|
@ -26,51 +27,41 @@ namespace riscv {
|
|||
baseAddress = base;
|
||||
}
|
||||
|
||||
AddressT BaseAddress() const override {
|
||||
return baseAddress;
|
||||
u8 PeekByte(AddressT address) override {
|
||||
return memory[AddressToIndex<u8>(address)];
|
||||
}
|
||||
|
||||
u8 PeekByte(AddressT offset) override {
|
||||
return memory[offset % memorySize];
|
||||
u16 PeekShort(AddressT address) override {
|
||||
return std::bit_cast<u16*>(memory)[AddressToIndex<u16>(address)];
|
||||
}
|
||||
|
||||
u16 PeekShort(AddressT offset) override {
|
||||
return std::bit_cast<u16*>(memory)[OffsetToIndex<u16>(offset)];
|
||||
u32 PeekWord(AddressT address) override {
|
||||
return std::bit_cast<u32*>(memory)[AddressToIndex<u32>(address)];
|
||||
}
|
||||
|
||||
u32 PeekWord(AddressT offset) override {
|
||||
return std::bit_cast<u32*>(memory)[OffsetToIndex<u32>(offset)];
|
||||
}
|
||||
|
||||
void PokeByte(AddressT offset, u8 value) override {
|
||||
void PokeByte(AddressT address, u8 value) override {
|
||||
if constexpr(!Rom) {
|
||||
memory[offset % memorySize] = value;
|
||||
} else {
|
||||
// TODO: trap here
|
||||
memory[AddressToIndex<u8>(address)] = value;
|
||||
}
|
||||
}
|
||||
|
||||
void PokeShort(AddressT offset, u16 value) override {
|
||||
void PokeShort(AddressT address, u16 value) override {
|
||||
if constexpr(!Rom) {
|
||||
std::bit_cast<u16*>(memory)[OffsetToIndex<u16>(offset)] = value;
|
||||
} else {
|
||||
// TODO: trap here
|
||||
std::bit_cast<u16*>(memory)[AddressToIndex<u16>(address)] = value;
|
||||
}
|
||||
}
|
||||
|
||||
void PokeWord(AddressT offset, u32 value) override {
|
||||
void PokeWord(AddressT address, u32 value) override {
|
||||
if constexpr(!Rom) {
|
||||
std::bit_cast<u32*>(memory)[OffsetToIndex<u32>(offset)] = value;
|
||||
} else {
|
||||
// TODO: trap here
|
||||
std::bit_cast<u32*>(memory)[AddressToIndex<u32>(address)] = value;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/// helper used for implementing stuff
|
||||
template <class T>
|
||||
constexpr usize OffsetToIndex(AddressT offset) {
|
||||
return (offset % memorySize) / sizeof(T);
|
||||
constexpr usize AddressToIndex(AddressT address) {
|
||||
return ((address - baseAddress) % memorySize) / sizeof(T);
|
||||
}
|
||||
|
||||
// remember what we were attached to via "signal"
|
||||
|
@ -86,6 +77,8 @@ namespace riscv {
|
|||
|
||||
} // namespace
|
||||
|
||||
// Bus::Device* NewRam()
|
||||
Bus::Device* NewRam(usize size) {
|
||||
return new RamDevice(size);
|
||||
}
|
||||
|
||||
} // namespace riscv
|
||||
|
|
Loading…
Reference in New Issue