diff --git a/native/CMakeLists.txt b/native/CMakeLists.txt index cc7de41..03c39f3 100644 --- a/native/CMakeLists.txt +++ b/native/CMakeLists.txt @@ -4,6 +4,7 @@ project(lcpu-native ) add_subdirectory(projects/lucore) +add_subdirectory(projects/lucore_test) # RISC-V emulation library add_subdirectory(projects/riscv) diff --git a/native/projects/lucore/CMakeLists.txt b/native/projects/lucore/CMakeLists.txt index d7f30d3..7a824f9 100644 --- a/native/projects/lucore/CMakeLists.txt +++ b/native/projects/lucore/CMakeLists.txt @@ -7,6 +7,7 @@ project(lucore add_library(lucore src/Assert.cpp + src/Logger.cpp ) target_compile_features(lucore PUBLIC cxx_std_20) diff --git a/native/projects/lucore/include/lucore/Assert.hpp b/native/projects/lucore/include/lucore/Assert.hpp index 72b3ddc..dd39748 100644 --- a/native/projects/lucore/include/lucore/Assert.hpp +++ b/native/projects/lucore/include/lucore/Assert.hpp @@ -2,34 +2,30 @@ #pragma once -#include +#include namespace lucore { - // TODO: wrapper which uses source_location (we don't need no macros anymore!) + // 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, ...) \ - 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]); \ + #define LUCORE_ASSERT(expr, fmt, ...) \ + if(!(expr)) [[unlikely]] { \ + 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, ...) \ - 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]); \ +#define LUCORE_CHECK(expr, fmt, ...) \ + if(!(expr)) [[unlikely]] { \ + auto msg = std::format("Check \"{}\" failed with message: {}", #expr, \ + std::format(fmt, ##__VA_ARGS__)); \ + ::lucore::ExitMsg(__FILE__, __LINE__, msg.c_str()); \ } diff --git a/native/projects/lucore/include/lucore/Logger.hpp b/native/projects/lucore/include/lucore/Logger.hpp new file mode 100644 index 0000000..00b9ab5 --- /dev/null +++ b/native/projects/lucore/include/lucore/Logger.hpp @@ -0,0 +1,96 @@ +//! Logging utilities for Lucore +//! Using Standard C++ + +#include +#include + +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(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 + inline void Debug(std::string_view fmt, Args... args) { + VOut(MessageSeverity::Debug, fmt, std::make_format_args(std::forward(args)...)); + } + + template + inline void Info(std::string_view fmt, Args... args) { + VOut(MessageSeverity::Info, fmt, std::make_format_args(std::forward(args)...)); + } + + template + inline void Warning(std::string_view fmt, Args... args) { + VOut(MessageSeverity::Warning, fmt, std::make_format_args(std::forward(args)...)); + } + + template + inline void Error(std::string_view fmt, Args... args) { + VOut(MessageSeverity::Error, fmt, std::make_format_args(std::forward(args)...)); + } + + template + inline void Fatal(std::string_view fmt, Args... args) { + VOut(MessageSeverity::Fatal, fmt, std::make_format_args(std::forward(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(); + +} diff --git a/native/projects/lucore/include/lucore/OptionalRef.hpp b/native/projects/lucore/include/lucore/OptionalRef.hpp index 82ad1d7..d8fa4d9 100644 --- a/native/projects/lucore/include/lucore/OptionalRef.hpp +++ b/native/projects/lucore/include/lucore/OptionalRef.hpp @@ -9,9 +9,6 @@ namespace lucore::detail { /// Sentinel value to explicitly not populate an OptionalRef. inline static Nullref_t NullRef {}; - template - struct OptionalRef; // sfinae on non-reference types - /// Like std::optional, 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 - struct OptionalRef { + struct OptionalRef { constexpr OptionalRef() : ptr(nullptr) { } @@ -34,7 +31,7 @@ namespace lucore::detail { // polymorphic downconstruction from another OptionalRef template - constexpr OptionalRef(const OptionalRef& other) : ptr(&other.ptr) { + constexpr OptionalRef(const OptionalRef& other) : ptr(&other.ptr) { } constexpr T& Value() const { diff --git a/native/projects/lucore/src/Assert.cpp b/native/projects/lucore/src/Assert.cpp index 1a56e95..f4ebb5f 100644 --- a/native/projects/lucore/src/Assert.cpp +++ b/native/projects/lucore/src/Assert.cpp @@ -2,13 +2,12 @@ #include #include +#include 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); } diff --git a/native/projects/lucore/src/Logger.cpp b/native/projects/lucore/src/Logger.cpp new file mode 100644 index 0000000..73a68a1 --- /dev/null +++ b/native/projects/lucore/src/Logger.cpp @@ -0,0 +1,38 @@ +#include +#include + + +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()); + } + +} diff --git a/native/projects/lucore_test/CMakeLists.txt b/native/projects/lucore_test/CMakeLists.txt new file mode 100644 index 0000000..d58b036 --- /dev/null +++ b/native/projects/lucore_test/CMakeLists.txt @@ -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) diff --git a/native/projects/lucore_test/main.cpp b/native/projects/lucore_test/main.cpp new file mode 100644 index 0000000..72c6de3 --- /dev/null +++ b/native/projects/lucore_test/main.cpp @@ -0,0 +1,28 @@ +#include +#include +#include + +/// 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"); +} diff --git a/native/projects/riscv/include/riscv/Bus.hpp b/native/projects/riscv/include/riscv/Bus.hpp index 6320822..d9a9cb5 100644 --- a/native/projects/riscv/include/riscv/Bus.hpp +++ b/native/projects/riscv/include/riscv/Bus.hpp @@ -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 FindDeviceForAddress(AddressT address) const; + lucore::OptionalRef 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 mapped_devices; }; diff --git a/native/projects/riscv/src/Bus.cpp b/native/projects/riscv/src/Bus.cpp index 5691faa..121276b 100644 --- a/native/projects/riscv/src/Bus.cpp +++ b/native/projects/riscv/src/Bus.cpp @@ -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::FindDeviceForAddress(AddressT address) const { + lucore::OptionalRef 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()); diff --git a/native/projects/riscv/src/MemoryDevice.cpp b/native/projects/riscv/src/MemoryDevice.cpp index 520caa6..8fefcf5 100644 --- a/native/projects/riscv/src/MemoryDevice.cpp +++ b/native/projects/riscv/src/MemoryDevice.cpp @@ -1,7 +1,4 @@ #include -#include - -#include "riscv/Types.hpp" namespace riscv { @@ -9,7 +6,7 @@ namespace riscv { template 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(address)]; } - u8 PeekByte(AddressT offset) override { - return memory[offset % memorySize]; + u16 PeekShort(AddressT address) override { + return std::bit_cast(memory)[AddressToIndex(address)]; } - u16 PeekShort(AddressT offset) override { - return std::bit_cast(memory)[OffsetToIndex(offset)]; + u32 PeekWord(AddressT address) override { + return std::bit_cast(memory)[AddressToIndex(address)]; } - u32 PeekWord(AddressT offset) override { - return std::bit_cast(memory)[OffsetToIndex(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(address)] = value; } } - void PokeShort(AddressT offset, u16 value) override { + void PokeShort(AddressT address, u16 value) override { if constexpr(!Rom) { - std::bit_cast(memory)[OffsetToIndex(offset)] = value; - } else { - // TODO: trap here + std::bit_cast(memory)[AddressToIndex(address)] = value; } } - void PokeWord(AddressT offset, u32 value) override { + void PokeWord(AddressT address, u32 value) override { if constexpr(!Rom) { - std::bit_cast(memory)[OffsetToIndex(offset)] = value; - } else { - // TODO: trap here + std::bit_cast(memory)[AddressToIndex(address)] = value; } } private: /// helper used for implementing stuff template - 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