misc fun changes

This commit is contained in:
Lily Tsuru 2023-08-05 06:46:56 -04:00
parent a5f7735d9a
commit 4f6ec38f22
18 changed files with 216 additions and 51 deletions

View File

@ -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)

View File

@ -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

44
lua/lcpu/cvars.lua Normal file
View File

@ -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
)

View File

@ -2,6 +2,7 @@
#include <lucore/Logger.hpp>
#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<u32>(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");

View File

@ -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<TImpl>(stackPos, __lua_typeid);
if(auto ptr = LUA->GetUserType<TImpl>(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 <class TImpl>
LUA_CLASS_FUNCTION(LuaObject<TImpl>, __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

View File

@ -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__)); \

View File

@ -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<Library> Open(std::string_view name);
/// Open an already loaded library
static Unique<Library> 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

View File

@ -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<std::size_t>(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 <class... Args>
inline void Debug(std::string_view fmt, Args... args) {
VOut(MessageSeverity::Debug, fmt, std::make_format_args(std::forward<Args>(args)...));

View File

@ -16,6 +16,25 @@ namespace lucore {
}
} // namespace
Unique<Library> 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<Library>(new Library(handle));
}
Unique<Library> 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<Library>(new Library(detail::OsOpenLibrary(name.c_str())));
return Unique<Library>(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);
}
}

View File

@ -1,5 +1,3 @@
#include <bits/iterator_concepts.h>
#include <cstddef>
#include <iostream>
#include <lucore/Logger.hpp>

View File

@ -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);

View File

@ -3,11 +3,19 @@
#include <dlfcn.h>
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

View File

@ -6,7 +6,14 @@
namespace lucore::detail {
OsLibraryHandle OsOpenLibrary(const char* filename) {
return reinterpret_cast<OsLibraryHandle>(GetModuleHandleA(filename);
return reinterpret_cast<OsLibraryHandle>(LoadLibraryA(filename));
}
OsLibraryHandle OsOpenExistingLibrary(const char* filename) {
if(!OsLibraryLoaded(filename))
return nullptr;
return reinterpret_cast<OsLibraryHandle>(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<HMODULE>(handle));
}
} // namespace lucore::detail

View File

@ -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 <print>
// 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;

View File

@ -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<std::FILE, decltype(&std::fclose)>;
using UniqueFilePtr = std::unique_ptr<std::FILE, FCloseDeleter>;
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) {

View File

@ -163,6 +163,9 @@ namespace projgen::make {
bool Write(const std::vector<std::unique_ptr<MakefileGeneratable>>& 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

View File

@ -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 };

View File

@ -7,7 +7,7 @@
namespace riscv {
void CPU::Clock() {
Step(1024);
Step(cycleCount);
}
void CPU::Trap(u32 trapCode) {