revert loggingsystem usage until I feel safe requiring x64 gmod

This commit is contained in:
Lily Tsuru 2023-07-18 23:33:30 -04:00
parent 0d6646f8cb
commit e3ffcb02c2
15 changed files with 122 additions and 151 deletions

8
LICENSE Normal file
View File

@ -0,0 +1,8 @@
Copyright 2023 Lily Tsuru <lily.modeco80@protonmail.ch>
Portions copyright 2022 Charles Lohr (CNLohr)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -5,13 +5,36 @@ LCPU is an alternative CPU core addon for GMod/Wiremod.
It provides:
- A standard RISC-V architechure (rv32ima) CPU core, implemented in native code
- No wiremod native code embargos here!
- No wiremod native code embargos here, so we get actually good performance without half our SENTs becoming lag machine fodder!
- Interoperation with the Wiremod addon (and addons which implement Wiremod integration)
# Building Native Module
TODO: Steps to build the LCPU native module on Windows and Linux
This addon (for now) works with both the non-beta branch and the x86-64 beta branches of GMod.
# Installation
This repository is set up in the exact directory structure to be a Legacy Addon; therefore once you have built the native module (TODO), adding it to a server should work fine.
This repository is set up to be a Filesystem Addon; therefore, workflows which clone repositories from Git and put them in addons/ should be able to work with the LCPU addon just fine.
Preliminary installation steps:
```
garrysmod/addons$ git clone --recursive https://github.com/modeco80/gmod-lcpu.git lcpu
garrysmod/addons$ cd lcpu
# Build the LCPU native module. These steps build the linux64 version of the module
# on linux; you'll need to alter it if you want to build for 32-bit.
garrysmod/addons/lcpu$ cmake -Wno-dev -GNinja -S native -B build \
-DCMAKE_BUILD_TYPE=Release
garrysmod/addons/lcpu$ ninja -C build
# Install the native module (Linux)
# For Windows you can do the same thing, just replace this
# with how you'd do it in batch (or use Explorer, I'm not your dad)
garrysmod/addons/lcpu$ [[ ! -d '../../lua/bin']] && mkdir -p ../../lua/bin && cp build/projects/lcpu/*.dll ../../lua/bin
# Tada!
```
# Special Thanks
[CNLohr](https://github.com/CNLohr) - I may not like the mini-rv32ima code that much, but it did still help a lot (and my emulation core library is based off it, with tons of refactoring and a C++ rewrite to boot.)

View File

@ -8,7 +8,6 @@ This is basically the working ideas for the LCPU project.
- RISC-V rv32ima core
- Would supporting more than one CPU core type be worthwhile? If so, the project is relatively setup for such workflow...
- Controllable paramaters (RAM size, ...)
- Our own framebuffer screen SENT (since wiremod decided to go stupid mode and saw off the gpu)
## Code upload
@ -63,3 +62,4 @@ This is basically the working ideas for the LCPU project.
- Wiremod interopability
- Wiremod GPIO peripheral (to interface with wire world)
- special Console Screen peripheral (interfacing specifically with it)
- frambuffer peripheral which uses the Digital Screen

View File

@ -3,126 +3,62 @@
#include <lucore/Library.hpp>
#include <lucore/Types.hpp>
// The old non-beta branch of GMod on Linux has multiple tier0 libraries for client and server.
// This compatibility define allows to support that case (for now).
#define LCPU_SUPPORT_OLD_GMOD
namespace tier0 {
// TODO: For now this'll leak..
lucore::Library* library = nullptr;
/// Minimally Source compatiable Color type
/// (Even more POD than Source)
struct Color {
constexpr explicit Color(u8 r, u8 g, u8 b, u8 a) {
colors[0] = r;
colors[1] = g;
colors[2] = b;
colors[3] = a;
}
u8 colors[4];
};
using LoggingChannelID_t = s32;
enum LoggingSeverity_t {
LS_MESSAGE,
LS_WARNING,
LS_ASSERT,
LS_ERROR,
LS_HIGHEST_SEVERITY
};
enum LoggingChannelFlags_t { LCF_CONSOLE_ONLY = 1, LCF_DO_NOT_ECHO = 2 };
using RegisterTagsFunc = void (*)();
// LoggingSystem_ APIs
using RegisterLoggingChannel_t = LoggingChannelID_t (*)(const char*, RegisterTagsFunc,
int flags, LoggingSeverity_t severity,
Color color);
using Log_t = s32 (*)(LoggingChannelID_t, LoggingSeverity_t, const char*, ...);
using GetChannelColor_t = Color (*)(LoggingChannelID_t);
RegisterLoggingChannel_t LoggingSystem_RegisterLoggingChannel {};
Log_t LoggingSystem_Log {};
GetChannelColor_t LoggingSystem_GetChannelColor {};
using Msg_t = void (*)(const char*, ...);
Msg_t Msg {};
} // namespace tier0
namespace lcpu {
tier0::LoggingChannelID_t gSourceSinkLoggerChannel { -1 };
SourceSink& SourceSink::The() {
static SourceSink sink;
return sink;
}
void SourceSink::Init() {
// Modern 64-bit GMod has one tier0 library.
// I am not supporting non-x86-64 branch servers, so this is OK and I don't need to
// do any library scanning nonsense to pick the correct one.
//
// (note that the x86-64 branch also includes 32-bit binaries, rather
// weirdly. You can build the module for this too, but not the non-x86-64 branch
// 32-bit GMod, and it also shares having only one tier0 library.)
SourceSink::SourceSink() {
#ifdef LCPU_SUPPORT_OLD_GMOD
constexpr static std::string_view tier0_libraries[] {
#ifdef __linux__
"tier0_srv"
#endif
"tier0"
};
for(auto lib : tier0_libraries) {
if(lucore::Library::Loaded(lib)) {
// Found the correct tier0 library to open; use that.
tier0::library = lucore::Library::Open(lib);
break;
}
}
#else
// The x86-64 branch of GMod, including the 32-bit binaries in the branch,
// have a single tier0 library, which makes the codepath much simpler.
tier0::library = lucore::Library::Open("tier0");
#endif
#define GRAB_SYMBOL(name, T) tier0::name = tier0::library->Symbol<T>(#name);
GRAB_SYMBOL(Msg, tier0::Msg_t);
}
GRAB_SYMBOL(LoggingSystem_RegisterLoggingChannel, tier0::RegisterLoggingChannel_t);
GRAB_SYMBOL(LoggingSystem_Log, tier0::Log_t);
GRAB_SYMBOL(LoggingSystem_GetChannelColor, tier0::GetChannelColor_t);
gSourceSinkLoggerChannel = tier0::LoggingSystem_RegisterLoggingChannel(
"LCPU Native", nullptr, 0, tier0::LS_MESSAGE, tier0::Color(0xff, 0x99, 0x00, 0xff));
SourceSink::~SourceSink() {
delete tier0::library;
}
void SourceSink::OutputMessage(const lucore::Logger::MessageData& data) {
auto formatted =
std::format("[LCPU Native/{}] [{}] {}\n", lucore::Logger::SeverityToString(data.severity),
std::format("[LCPU Native/{}] [{}] {}", lucore::Logger::SeverityToString(data.severity),
data.time, std::vformat(data.format, data.args));
auto lucore_to_source = [severity = data.severity]() -> tier0::LoggingSeverity_t {
using enum lucore::Logger::MessageSeverity;
switch(severity) {
case Info:
return tier0::LS_MESSAGE;
case Warning:
return tier0::LS_WARNING;
case Error:
return tier0::LS_WARNING;
case Fatal:
return tier0::LS_ASSERT;
default:
return tier0::LS_MESSAGE;
}
}();
#if 0
// This is a pretty stupid hack for console colors on Linux.
// I don't really like it that much tbh, wish tier0 supported this.
auto color = tier0::LoggingSystem_GetChannelColor(gSourceSinkLoggerChannel);
{
using enum lucore::Logger::MessageSeverity;
switch(data.severity) {
case Warning:
color = tier0::Color(0xff, 0xff, 0x0, 0xff);
break;
case Error:
case Fatal:
color = tier0::Color(0xff, 0x0, 0x0, 0xff);
break;
default:
break;
}
}
tier0::LoggingSystem_Log(gSourceSinkLoggerChannel, lucore_to_source,
"\033[38;2;%d;%d;%dm%s\033[0m", color.colors[0], color.colors[1],
color.colors[2], formatted.c_str());
#else
tier0::LoggingSystem_Log(gSourceSinkLoggerChannel, lucore_to_source, "%s",
formatted.c_str());
#endif
tier0::Msg("%s\n", formatted.c_str());
}
} // namespace lcpu

View File

@ -7,9 +7,10 @@ namespace lcpu {
struct SourceSink : public lucore::Logger::Sink {
static SourceSink& The();
static void Init();
SourceSink();
~SourceSink();
void OutputMessage(const lucore::Logger::MessageData& data) override;
};
}
} // namespace lcpu

View File

@ -5,13 +5,9 @@
#include <lucore/Assert.hpp>
GMOD_MODULE_OPEN() {
// Initalize the source logging sink and attach it to the lucore logger.
lcpu::SourceSink::Init();
lucore::Logger::The().AttachSink(lcpu::SourceSink::The());
lucore::LogInfo("Hello Source World :) {} {}", 123.456, "This should work");
lucore::LogWarning("test");
LUCORE_CHECK(false, "this should bring the process down");
lucore::LogInfo("LCPU Native Module loading");
return 0;
}

View File

@ -6,15 +6,28 @@ project(lucore
)
add_library(lucore
# Assertion/Check failure handling
src/Assert.cpp
# Logging functionality
src/Logger.cpp
src/StdoutSink.cpp
src/OsLibrary.cpp
# Dynamic library wrappers
src/Library.cpp
)
# Target-specific Lucore sources.
if(WIN32)
target_sources(lucore PRIVATE
src/OsLibrary.win32.cpp
)
elseif(UNIX)
target_sources(lucore PRIVATE
src/OsLibrary.linux.cpp
)
endif()
target_compile_features(lucore PUBLIC cxx_std_20)
target_include_directories(lucore PUBLIC ${PROJECT_SOURCE_DIR}/include)

View File

@ -35,7 +35,6 @@ namespace lucore {
/// Get the single instance of the logger.
static Logger& The();
Logger() = default;
Logger(const Logger&) = delete;
Logger(Logger&&) = delete;
@ -76,6 +75,7 @@ namespace lucore {
}
private:
Logger() = default;
void VOut(MessageSeverity severity, std::string_view format, std::format_args args);
MessageSeverity logLevel { MessageSeverity::Info };

View File

@ -3,15 +3,13 @@
namespace lucore {
/// A logger sink implementation that prints to standard output.
/// Not used by the lcpu native module, but could be used by applications
/// using lucore later on.
struct StdoutSink : public Logger::Sink {
static StdoutSink& The();
virtual void OutputMessage(const Logger::MessageData& data) override;
};
/// Attach the stdout logger sink to the logger.
/// Attach the stdout logger sink to the global Lucore logger.
void LoggerAttachStdout();
}

View File

@ -1,9 +0,0 @@
#include "OsLibrary.hpp"
#if defined(_WIN32)
#include "OsLibrary.win32.cpp"
#elif defined(__linux__)
#include "OsLibrary.linux.cpp"
#else
#error No OsLibrary implementation for this platform
#endif

View File

@ -1,3 +1,5 @@
#include "OsLibrary.hpp"
#include <dlfcn.h>
namespace lucore::detail {

View File

@ -1,3 +1,5 @@
#include "OsLibrary.hpp"
#define _WIN32_LEAN_AND_MEAN
#include <windows.h>
@ -20,4 +22,4 @@ namespace lucore::detail {
// therefore, we have nothing to do here on Windows.
}
}
} // namespace lucore::detail

View File

@ -1,28 +1,34 @@
#include <riscv/Types.hpp>
#include <lucore/OptionalRef.hpp>
#include <vector>
#include <riscv/Types.hpp>
#include <unordered_map>
#include <vector>
namespace riscv {
struct CPU;
/// An address bus.
/// An address/memory bus. No virtual address translation is implemented;
/// all addresses/devices are placed in physical addresses.
struct Bus {
/// Interface all memory bus devices use.
struct Device {
Device() = default;
// Devices have no need to be copied or moved.
Device(const Device&) = delete;
Device(Device&&) = delete;
virtual ~Device() = default;
// How many bytes does this device occupy of address space?
/// How many bytes does this device occupy of address space? This should
/// effectively be a constant, and should probably not change during CPU execution.
virtual AddressT Size() const = 0;
// Used to allow bus devices to know when they are attached to a memory bus,
// and ultimately, an instance of a System
/// 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;
/// Is this device clocked?
/// Does this device require a clock "signal"?
virtual bool Clocked() const { return false; }
/// This function is called to give clocked devices
@ -32,8 +38,7 @@ namespace riscv {
// TODO(feat): default implementations of Peek* and Poke* should exist
// and trap the CPU (similarly to what happens if a unmapped bus read occurs).
// Peek() -> reads a value from this device.
/// Peek() -> reads a value from this device.
virtual u8 PeekByte(AddressT offset) = 0;
virtual u16 PeekShort(AddressT offset) = 0;
virtual u32 PeekWord(AddressT offset) = 0;
@ -42,7 +47,6 @@ namespace riscv {
virtual void PokeByte(AddressT offset, u8 value) = 0;
virtual void PokeShort(AddressT offset, u16 value) = 0;
virtual void PokeWord(AddressT offset, u32 value) = 0;
};
Bus(CPU* cpu);
@ -57,10 +61,10 @@ namespace riscv {
/// This function returns true if the device was able to be put on the bus.
/// This function returns false in the following error cases:
/// - [device] is a null pointer
/// - The provided base address overlaps a already attached device in some way
/// - The provided base address overlaps a already attached device in some way
bool AttachDevice(AddressT baseAddress, Device* device);
/// Clock all clocked devices.
/// Clock all clocked devices mapped onto the bus..
void Clock();
u8 PeekByte(AddressT address);
@ -70,16 +74,14 @@ namespace riscv {
void PokeByte(AddressT address, u8 value);
void PokeShort(AddressT address, u16 value);
void PokeWord(AddressT address, u32 value);
private:
private:
lucore::OptionalRef<Device> FindDeviceForAddress(AddressT address) const;
CPU* attachedCpu{};
CPU* attachedCpu {};
// TODO: if this ends up being a hotpath replace with ankerl::unordered_dense
std::unordered_map<AddressT, Device*> mapped_devices;
};
}
} // namespace riscv

View File

@ -3,8 +3,9 @@
namespace riscv {
/** The CPU core. */
/// The CPU core.
struct CPU {
/// CPU core state.
struct State {
u32 gpr[32];
u32 pc;

View File

@ -1,18 +1,16 @@
#include <riscv/Bus.hpp>
#include "riscv/Types.hpp"
namespace riscv::devices {
/// A block of RAM which can be used by the CPU.
struct RamDevice : public Bus::Device {
RamDevice(AddressT size);
virtual ~RamDevice();
AddressT Size() const override;
// Implementation of Device interface
AddressT Size() const override;
void Attached(Bus* bus, AddressT base) override;
u8 PeekByte(AddressT address) override;
@ -24,7 +22,7 @@ namespace riscv::devices {
void PokeWord(AddressT address, u32 value) override;
private:
/// helper used for implementing stuff
/// helper used for implementing Peek/Poke API
template <class T>
constexpr usize AddressToIndex(AddressT address) {
return ((address - baseAddress) % memorySize) / sizeof(T);