diff --git a/lua/autorun/lcpu_load.lua b/lua/autorun/lcpu_load.lua index e104041..657bb98 100644 --- a/lua/autorun/lcpu_load.lua +++ b/lua/autorun/lcpu_load.lua @@ -15,6 +15,8 @@ if SERVER then -- Uncomment this to enable debug logging (useful for troubleshooting bugs) --LCPUNative.EnableDebug() + include("lcpu/cvars.lua") + AddCSLuaFile("entities/gmod_lcpu_cpu.lua") -- Serverside devices (that don't depend on wiremod being loaded) diff --git a/lua/entities/gmod_lcpu_cpu.lua b/lua/entities/gmod_lcpu_cpu.lua index 1d4b453..5a7b767 100644 --- a/lua/entities/gmod_lcpu_cpu.lua +++ b/lua/entities/gmod_lcpu_cpu.lua @@ -2,7 +2,6 @@ AddCSLuaFile() DEFINE_BASECLASS("base_wire_entity") ENT.PrintName = "LCPU" ENT.Author = "Lily <3" - -- no more, this deeply uses native APIs if CLIENT then return end @@ -11,27 +10,32 @@ if CLIENT then return end include("lcpu/devices/wire_interface.lua") -- TODO: serverside convars to control execution rate & cycle count - function ENT:Initialize() self:PhysicsInit(SOLID_VPHYSICS) self:SetMoveType(MOVETYPE_VPHYSICS) self:SetSolid(SOLID_VPHYSICS) - -- CPU callbacks? self.cpu = LCPUNative.CreateCPU(128 * 1024) self.uart = LCPU.Devices.UART(0x10000000) self.wireInterface = LCPU.Devices.WireInterface(0x11310000, self, 8, 8) - self.cpu:AttachDevice(self.uart) self.cpu:AttachDevice(self.wireInterface) - self:SetOverlayText("hi :)") end function ENT:Think() -- Avoid running if the cpu is not powered on if not self.cpu:PoweredOn() then return end - self.cpu:Cycle() + + if LCPU.cycleCount ~= self.cpu.CycleCount then + --print(string.format("bumping up cycle count to %d", LCPU.cycleCount)); + self.cpu.CycleCount = LCPU.cycleCount + end + + for i = 1, LCPU.tickCount do + self.cpu:Cycle() + end + -- Even though this is gated by tickrate I'm just trying to be nice here self:NextThink(CurTime() + 0.1) end diff --git a/lua/lcpu/cvars.lua b/lua/lcpu/cvars.lua new file mode 100644 index 0000000..61b3c1c --- /dev/null +++ b/lua/lcpu/cvars.lua @@ -0,0 +1,44 @@ +-- these are where all the cvars the addon uses go + + +-- honestly I wonder if I could get away with models/classes instead of a global limitation +--[[ + c class: basic + c1: 128k ram, 1 tick, 1024 cycles + c2: 512k ram, 4 ticks, 2048 cycles + + b class: more suited to crazier programs + b1: 1mb ram, 8 ticks, 4096 cycles + b2: 2mb ram, 12 ticks, 6144 cycles + + a class: higher performance + a1: 4mb ram, 16 ticks, 8192 cycles + a2: 8mb ram, 16 ticks, 12240 cycles + + (this one will definitly be limited in some way if I ever do this since it is a walking lag machine in the making) + s class: you don't like your gmod server very much + s1: 64mb ram, 16 ticks, 16384 cycles +]] + +LCPU.tickCount = 4 +LCPU.cycleCount = 1024 + +-- cvars for limiting/reducing lcpu usage +local lcpu_cpu_tick_count = CreateConVar("lcpu_cpu_tick_count", LCPU.tickCount, FCVAR_REPLICATED, "How many cycle steps a LCPU will do") +local lcpu_cpu_cycle_count = CreateConVar("lcpu_cpu_cycle_count", LCPU.cycleCount, FCVAR_REPLICATED, "How many CPU cycles run in each CPU tick") + +cvars.AddChangeCallback( + "lcpu_cpu_tick_count", + function() + -- this seems reasonable enough right? + LCPU.tickCount = math.Clamp(math.floor(lcpu_cpu_tick_count:GetInt()), 1, 16) + end +) + +cvars.AddChangeCallback( + "lcpu_cpu_cycle_count", + function() + -- these are NOT final values + LCPU.cycleCount = math.Clamp(math.floor(lcpu_cpu_cycle_count:GetInt()), 1, 16384) + end +) diff --git a/native/projects/lcpu/src/LuaCpu.cpp b/native/projects/lcpu/src/LuaCpu.cpp index d5ed356..3c5a8c1 100644 --- a/native/projects/lcpu/src/LuaCpu.cpp +++ b/native/projects/lcpu/src/LuaCpu.cpp @@ -2,6 +2,7 @@ #include +#include "GarrysMod/Lua/LuaBase.h" #include "LuaDevice.hpp" namespace lcpu { @@ -56,6 +57,21 @@ namespace lcpu { void LuaCpu::RegisterClass(GarrysMod::Lua::ILuaBase* LUA) { RegisterClassStart(LUA); + RegisterGetter("CycleCount", [](GarrysMod::Lua::ILuaBase* LUA) { + auto self = FromLua(LUA, 1); + LUA->PushNumber(self->system->cpu->GetCycleCount()); + }); + + RegisterSetter("CycleCount", [](GarrysMod::Lua::ILuaBase* LUA) { + auto self = FromLua(LUA, 1); + + auto newValue = static_cast(LUA->GetNumber(-1)); + if(newValue == 0) { + LUA->ThrowError("Invalid value to set LuaCpu:CycleCount"); + } + self->system->cpu->SetCycleCount(newValue); + }); + RegisterMethod("PoweredOn", PoweredOn); RegisterMethod("Cycle", Cycle); RegisterMethod("PowerOff", PowerOff); @@ -67,9 +83,7 @@ namespace lcpu { LuaCpu::LuaCpu(u32 memorySize) { poweredOn = true; system = riscv::System::Create(memorySize); - system->OnPowerOff = [&]() { - poweredOn = false; - }; + system->OnPowerOff = [&]() { poweredOn = false; }; // lame test code. this WILL be removed, I just want this for a quick test auto fp = std::fopen("/home/lily/test-gmod.bin", "rb"); diff --git a/native/projects/lcpu/src/LuaObject.hpp b/native/projects/lcpu/src/LuaObject.hpp index 28ad1d1..cdbacd4 100644 --- a/native/projects/lcpu/src/LuaObject.hpp +++ b/native/projects/lcpu/src/LuaObject.hpp @@ -6,6 +6,7 @@ #include "GarrysMod/Lua/LuaBase.h" #include "GarrysMod/Lua/Types.h" #include "LuaHelpers.hpp" +#include "lucore/Assert.hpp" namespace lcpu::lua { @@ -45,7 +46,9 @@ namespace lcpu::lua { } static TImpl* FromLua(GarrysMod::Lua::ILuaBase* LUA, int stackPos) { - return LUA->GetUserType(stackPos, __lua_typeid); + if(auto ptr = LUA->GetUserType(stackPos, __lua_typeid); ptr) + return ptr; + return nullptr; } protected: @@ -76,6 +79,10 @@ namespace lcpu::lua { /// - __newindex /// static void RegisterMetaFunction(GarrysMod::Lua::ILuaBase* LUA, const std::string& name, CFunc func) { + LUCORE_CHECK(name != "__gc", "Attempting to overwrite __gc metamethod"); + LUCORE_CHECK(name != "__index", "Attempting to overwrite __index metamethod"); + LUCORE_CHECK(name != "__newindex", "Attempting to overwrite __newindex metamethod"); + // clang-format off LUA->PushMetaTable(__lua_typeid); LUA->PushCFunction(func); @@ -155,8 +162,7 @@ namespace lcpu::lua { template LUA_CLASS_FUNCTION(LuaObject, __index) { auto self = FromLua(LUA, 1); - - // If the key is something we support, + if(LUA->GetType(2) == GarrysMod::Lua::Type::String) { auto& methods = LuaObject::methods(); auto& getters = LuaObject::getters(); @@ -172,8 +178,6 @@ namespace lcpu::lua { getters[key](LUA); return 1; } - - lucore::LogDebug("LuaObject::__index({}) going to table", key); } // Failing to look up an item is not fatal; @@ -208,12 +212,8 @@ namespace lcpu::lua { setters[key](LUA); return 0; } - - - lucore::LogDebug("LuaObject::__newindex({}) going to table", key); } - // set the provided value onto the table // clang-format off LUA->ReferencePush(self->tableReference); // table diff --git a/native/projects/lucore/include/lucore/Assert.hpp b/native/projects/lucore/include/lucore/Assert.hpp index 402583a..52d685c 100644 --- a/native/projects/lucore/include/lucore/Assert.hpp +++ b/native/projects/lucore/include/lucore/Assert.hpp @@ -1,4 +1,16 @@ //! Lucore Assert Wrappers +//! +//! Lucore uses its own assertion system which is more flexible than the +//! standard C library's assertion macros. +//! +//! They are not intended to be directly compatible with some of the quirks +//! the Standard C library allows (like using assert() as an expression). +//! +//! They are: +//! - LUCORE_ASSERT() +//! - active in debug builds and removed on release +//! - LUCORE_CHECK() +//! - always active, even in release builds #pragma once @@ -18,7 +30,6 @@ namespace lucore { #define LUCORE_ASSERT(expr, format, ...) #endif -// CHECK() is always active, even in release builds #define LUCORE_CHECK(expr, fmt, ...) \ if(!(expr)) [[unlikely]] { \ auto msg = std::format("Check \"{}\" @ {}:{} failed with message: {}", #expr, __FILE__, __LINE__, std::format(fmt, ##__VA_ARGS__)); \ diff --git a/native/projects/lucore/include/lucore/Library.hpp b/native/projects/lucore/include/lucore/Library.hpp index d06384f..5e27a32 100644 --- a/native/projects/lucore/include/lucore/Library.hpp +++ b/native/projects/lucore/include/lucore/Library.hpp @@ -5,9 +5,14 @@ namespace lucore { + /// A platform-independent class for working with a shared library + /// as a resource. struct Library { using Handle = void*; + /// Open a library regardless of if it's been loaded or not. + static Unique Open(std::string_view name); + /// Open an already loaded library static Unique OpenExisting(std::string_view dllname); @@ -22,10 +27,17 @@ namespace lucore { } private: + struct ExistingTag{}; + constexpr static inline ExistingTag Existing{}; + constexpr explicit Library(Handle handle) : handle(handle) {} + constexpr explicit Library(Handle handle, ExistingTag) : handle(handle), existing(true) {} void* SymbolImpl(const char* symbol); Handle handle {}; + + /// Tracks if this Library instance was created using [Library::OpenExisting]. + bool existing{false}; }; } // namespace lucore diff --git a/native/projects/lucore/include/lucore/Logger.hpp b/native/projects/lucore/include/lucore/Logger.hpp index a096172..dbb3f65 100644 --- a/native/projects/lucore/include/lucore/Logger.hpp +++ b/native/projects/lucore/include/lucore/Logger.hpp @@ -15,10 +15,16 @@ namespace lucore { static constexpr std::string_view SeverityToString(MessageSeverity sev) { // This must match order of Logger::MessageSeverity. - const char* MessageSeverityStringTable[] = { "Deb", "Inf", "Wrn", "Err", "Ftl" }; + const char* MessageSeverityStringTable[] = { "Debug", "Info", "Warn", "Error", "Fatal" }; return MessageSeverityStringTable[static_cast(sev)]; } + struct MessageDataUnformatted { + std::chrono::system_clock::time_point time; + MessageSeverity severity; + std::string_view message; + }; + /// Message data. This is only used by logger sinks. struct MessageData { std::chrono::system_clock::time_point time; @@ -33,7 +39,8 @@ namespace lucore { virtual void OutputMessage(const MessageData& data) = 0; }; - /// Get the single instance of the logger. + /// Get the common instance of the logger. + /// LogInfo() etc operates on this function only. static Logger& The(); Logger(const Logger&) = delete; @@ -50,6 +57,15 @@ namespace lucore { /// Set the current log level. void SetLogLevel(MessageSeverity newLogLevel) { logLevel = newLogLevel; } + // TODO: sinks should get a "unformatted output" OutputMessage overload +#if 0 + constexpr void Debug(std::string_view message) { VOut(MessageSeverity::Debug, message); } + constexpr void Info(std::string_view message) { VOut(MessageSeverity::Info, message); } + constexpr void Warning(std::string_view message) { VOut(MessageSeverity::Warning, message); } + constexpr void Error(std::string_view message) { VOut(MessageSeverity::Error, message); } + constexpr void Fatal(std::string_view message) { VOut(MessageSeverity::Fatal, message); } +#endif + template inline void Debug(std::string_view fmt, Args... args) { VOut(MessageSeverity::Debug, fmt, std::make_format_args(std::forward(args)...)); diff --git a/native/projects/lucore/src/Library.cpp b/native/projects/lucore/src/Library.cpp index 6183d3c..1041059 100644 --- a/native/projects/lucore/src/Library.cpp +++ b/native/projects/lucore/src/Library.cpp @@ -16,6 +16,25 @@ namespace lucore { } } // namespace + Unique Library::Open(std::string_view dllname) { + auto name = FormatLibraryName(dllname); + auto handle = detail::OsOpenLibrary(name.c_str()); + + if(!handle) { +#ifndef _WIN32 + // Try without a `lib` prefix next. If this fails, give up. + name = std::format("{}.so", dllname); + handle = detail::OsOpenLibrary(name.c_str()); + if(!handle) + return nullptr; +#else + return nullptr; +#endif + } + + return Unique(new Library(handle)); + } + Unique Library::OpenExisting(std::string_view dllname) { auto name = FormatLibraryName(dllname); if(!detail::OsLibraryLoaded(name.c_str())) { @@ -29,7 +48,7 @@ namespace lucore { #endif } - return Unique(new Library(detail::OsOpenLibrary(name.c_str()))); + return Unique(new Library(detail::OsOpenExistingLibrary(name.c_str()), Existing)); } bool Library::Loaded(std::string_view dllname) { @@ -38,7 +57,11 @@ namespace lucore { Library::~Library() { if(handle) { - detail::OsFreeLibrary(handle); + // See OsLibrary.win32.cpp for reasoning of this weird ifdef thing +#ifdef _WIN32 + if(!existing) +#endif + detail::OsFreeLibrary(handle); } } diff --git a/native/projects/lucore/src/Logger.cpp b/native/projects/lucore/src/Logger.cpp index 66a04bf..30714d8 100644 --- a/native/projects/lucore/src/Logger.cpp +++ b/native/projects/lucore/src/Logger.cpp @@ -1,5 +1,3 @@ -#include - #include #include #include diff --git a/native/projects/lucore/src/OsLibrary.hpp b/native/projects/lucore/src/OsLibrary.hpp index 60c9a77..d9680c3 100644 --- a/native/projects/lucore/src/OsLibrary.hpp +++ b/native/projects/lucore/src/OsLibrary.hpp @@ -1,4 +1,4 @@ -//! Operating-system independent utilities for opening +//! Operating-system independent API for opening //! shared libraries. This is currently a detail-only //! Lucore API, and its stability is NOT guaranteed. @@ -6,9 +6,11 @@ namespace lucore::detail { /// Opaque handle type for libraries. using OsLibraryHandle = void*; - /// Open a library. OsLibraryHandle OsOpenLibrary(const char* filename); + /// Open a library. + OsLibraryHandle OsOpenExistingLibrary(const char* filename); + /// Query if the library with the given [filename] is loaded. bool OsLibraryLoaded(const char* filename); diff --git a/native/projects/lucore/src/OsLibrary.linux.cpp b/native/projects/lucore/src/OsLibrary.linux.cpp index 4983b1c..d3333b1 100644 --- a/native/projects/lucore/src/OsLibrary.linux.cpp +++ b/native/projects/lucore/src/OsLibrary.linux.cpp @@ -3,11 +3,19 @@ #include namespace lucore::detail { + OsLibraryHandle OsOpenLibrary(const char* filename) { return dlopen(filename, RTLD_LAZY); } + OsLibraryHandle OsOpenExistingLibrary(const char* filename) { + return dlopen(filename, RTLD_LAZY); + } + bool OsLibraryLoaded(const char* filename) { + // RTLD_NOLOAD tells the dynamic linker *not* to load + // the module if it's not loaded in this process, which + // allows us to test for if a module is loaded or not return dlopen(filename, RTLD_NOLOAD | RTLD_LAZY) != nullptr; } @@ -16,9 +24,8 @@ namespace lucore::detail { } void OsFreeLibrary(OsLibraryHandle handle) { - // The reference count on *Nix will be incremented by the launcher - // process itself, therefore we do not risk accidentally pulling the - // library out of the rug of the engine in either case. + // The reference count on *Nix *will* be incremented by dlopen(), + // therefore we do have to always free libraries. dlclose(handle); } } // namespace lucore::detail diff --git a/native/projects/lucore/src/OsLibrary.win32.cpp b/native/projects/lucore/src/OsLibrary.win32.cpp index 35d0c26..8211784 100644 --- a/native/projects/lucore/src/OsLibrary.win32.cpp +++ b/native/projects/lucore/src/OsLibrary.win32.cpp @@ -6,7 +6,14 @@ namespace lucore::detail { OsLibraryHandle OsOpenLibrary(const char* filename) { - return reinterpret_cast(GetModuleHandleA(filename); + return reinterpret_cast(LoadLibraryA(filename)); + } + + OsLibraryHandle OsOpenExistingLibrary(const char* filename) { + if(!OsLibraryLoaded(filename)) + return nullptr; + + return reinterpret_cast(GetModuleHandleA(filename)); } bool OsLibraryLoaded(const char* filename) { @@ -18,8 +25,11 @@ namespace lucore::detail { } void OsFreeLibrary(OsLibraryHandle handle) { - // GetModuleHandle*() does not increment the reference count; - // therefore, we have nothing to do here on Windows. + // Note that this function should never be called on a handle retrieved + // from OsOpenExistingLibrary(); GetModuleHandle() does **not** increment + // the module's reference count and therefore there is a real risk of accidentally + // freeing the module and causing crashes + FreeLibraryA(reinterpret_cast(handle)); } } // namespace lucore::detail diff --git a/native/projects/lucore/src/StdoutSink.cpp b/native/projects/lucore/src/StdoutSink.cpp index 4da7da1..300a4ba 100644 --- a/native/projects/lucore/src/StdoutSink.cpp +++ b/native/projects/lucore/src/StdoutSink.cpp @@ -8,7 +8,7 @@ namespace lucore { void StdoutSink::OutputMessage(const Logger::MessageData& data) { // This is kinda iffy, but required until more standard libraries support the C++23 - // header. + // header (or C++23 in general, but that's a different can of worms.) struct FputcIterator { using iterator_category = std::output_iterator_tag; using value_type = void; diff --git a/native/projects/projgen/src/FsUtils.hpp b/native/projects/projgen/src/FsUtils.hpp index 4759ce7..2353c40 100644 --- a/native/projects/projgen/src/FsUtils.hpp +++ b/native/projects/projgen/src/FsUtils.hpp @@ -10,11 +10,17 @@ namespace fs = std::filesystem; namespace projgen::util { + struct FCloseDeleter { + void operator()(std::FILE* file) { + if(file) + std::fclose(file); + } + }; - using UniqueFilePtr = std::unique_ptr; + using UniqueFilePtr = std::unique_ptr; inline UniqueFilePtr UniqueFopen(std::string_view path, std::string_view mode) { - return UniqueFilePtr(std::fopen(path.data(), mode.data()), &std::fclose); + return UniqueFilePtr(std::fopen(path.data(), mode.data())); } inline std::string ReadFileAsString(const fs::path& path) { diff --git a/native/projects/projgen/src/Makefile.hpp b/native/projects/projgen/src/Makefile.hpp index 1172d3f..d07e8ec 100644 --- a/native/projects/projgen/src/Makefile.hpp +++ b/native/projects/projgen/src/Makefile.hpp @@ -163,6 +163,9 @@ namespace projgen::make { bool Write(const std::vector>& g) { for(auto& p : g) { + WriteLine("# Generated by LCPU project generator\n"); + WriteLine("# Do not modify this file.\n"); + auto generated_data = p->Generate(); if(std::fwrite(generated_data.data(), 1, generated_data.length(), file.get()) != generated_data.length()) return false; @@ -175,7 +178,12 @@ namespace projgen::make { } private: - projgen::util::UniqueFilePtr file { nullptr, std::fclose }; + + void WriteLine(std::string_view line) { + std::fwrite(line.data(), sizeof(char), line.length(), file.get()); + } + + projgen::util::UniqueFilePtr file; }; } // namespace projgen::make diff --git a/native/projects/riscv/include/riscv/CPU.hpp b/native/projects/riscv/include/riscv/CPU.hpp index 08dab6e..a9824a4 100644 --- a/native/projects/riscv/include/riscv/CPU.hpp +++ b/native/projects/riscv/include/riscv/CPU.hpp @@ -24,30 +24,38 @@ namespace riscv { extraflags |= 3; // Start in Machine mode } + constexpr u32 GetCycleCount() { return cycleCount; } + constexpr void SetCycleCount(u32 value) { + LUCORE_ASSERT(value != 0, "no <3"); + cycleCount = value; + } + // TODO: Handlers for CSR read/write (if we need it?) /// CPU state - GeneralPurposeRegisters gpr{}; - u32 pc{}; - u32 mstatus{}; - u32 cyclel{}; - u32 cycleh{}; - u32 mscratch{}; - u32 mtvec{}; - u32 mie{}; - u32 mip{}; + GeneralPurposeRegisters gpr {}; + u32 pc {}; + u32 mstatus {}; + u32 cyclel {}; + u32 cycleh {}; + u32 mscratch {}; + u32 mtvec {}; + u32 mie {}; + u32 mip {}; - u32 mepc{}; - u32 mtval{}; - u32 mcause{}; + u32 mepc {}; + u32 mtval {}; + u32 mcause {}; // Note: only a few bits are used. (Machine = 3, User = 0) // Bits 0..1 = privilege. // Bit 2 = WFI (Wait for interrupt) // Bit 3+ = Load/Store reservation LSBs. - u32 extraflags{}; + u32 extraflags {}; private: + u32 cycleCount { 1024 }; + /// Set by [CPU::Trap] to tell the CPU it was trapped. bool trapped { false }; diff --git a/native/projects/riscv/src/CPU.cpp b/native/projects/riscv/src/CPU.cpp index b38e556..1a55e78 100644 --- a/native/projects/riscv/src/CPU.cpp +++ b/native/projects/riscv/src/CPU.cpp @@ -7,7 +7,7 @@ namespace riscv { void CPU::Clock() { - Step(1024); + Step(cycleCount); } void CPU::Trap(u32 trapCode) {