lcpu: Misc cleanup

LuaDevice now works fully, without any weird bugs. Yay!
This took quite a bit of bugstomping to arrive to; some of which ended up discovering I'm not particularly proud of doing. Oops!

The LuaCpu implementation no longer has a UART implementation in C++ (which actually leaked everytime a LuaCpu was garbage collected... Yeesh) - instead the Lua entity code actually implements the UART! Pretty cool to see the fruits of my labor actually working!

The LuaHelpers macros are renamed slightly to make them less of a pain (and also because I think having `_IMPLEMENT` in a function implementation is a bit stupid.)

I have updated the ideas document to better reflect my plans for the project system.
This commit is contained in:
Lily Tsuru 2023-07-28 06:05:58 -04:00
parent bdd6839fa2
commit 8eb4b6ef41
14 changed files with 209 additions and 125 deletions

View File

@ -16,9 +16,11 @@ See [this link](https://git.crustywindo.ws/modeco80/gmod-lcpu) for the actual de
# Installation # Installation
You will need Wiremod installed, either from the Workshop or cloned as a filesystem addon.
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. 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: Preliminary installation steps (by hand):
``` ```
garrysmod/addons$ git clone --recursive https://git.crustywindo.ws/modeco80/gmod-lcpu.git lcpu garrysmod/addons$ git clone --recursive https://git.crustywindo.ws/modeco80/gmod-lcpu.git lcpu
@ -40,7 +42,7 @@ garrysmod/addons/lcpu$ [[ ! -d '../../lua/bin']] && mkdir -p ../../lua/bin && cp
On Linux you can alternatively use the `./build_module.sh` script that will do all the build and installation steps automatically, once cloning the repository in the garrysmod/addons folder. On Linux you can alternatively use the `./build_module.sh` script that will do all the build and installation steps automatically, once cloning the repository in the garrysmod/addons folder.
Windows building is currently untested; I see no reason why it wouldn't work but it is not a platform I will focus on specifically. Windows building is currently untested; I see no reason why it wouldn't work.
# Special Thanks # Special Thanks

View File

@ -13,49 +13,68 @@ This is basically the working ideas for the LCPU project.
- Upload a raw binary to execute, generated from any user tooling (goes into server data folder) - Upload a raw binary to execute, generated from any user tooling (goes into server data folder)
- Yes, this means you can run Linux in GMod. No, I'm not sorry. - Yes, this means you can run Linux in GMod. No, I'm not sorry.
- Or even an ELF? Would require less linker hell?
## Integrated simple project workflow (WIP) ## Integrated simple project workflow
- Uses official RISC-V GCC toolchain - Uses official RISC-V GCC toolchain
- Server operator can control root path of where they have installed it. - In a podman container for jailing reasons?
- Write assembly/C/C++ code using a tiny project system (source for them would go in server data folder ?) - Write assembly/C/C++ code using a tiny project system (source for them would go in server data folder ?)
- At the root of a project, a `project.json` file exists, with something like: - At the root of a project, a `project.json` file is expected to exist, with contents like:
```json ```json
{ {
"project": { "project": {
"cCompileFlags": "-O2", // All configurations for a project.
"cppCompileFlags": "-O2 -fno-exceptions -fno-rtti", "configurations": {
"debug": {
"CCompileFlags": "-O0 -g ${BaseCCompileFlags}",
"CppCompileFlags": "-O0 -g ${BaseCppCompileFlags}",
"LinkerScript": "binary.ld",
},
"release": {
"CCompileFlags": "-O2 ${BaseCCompileFlags}",
"CppCompileFlags": "-O2 ${BaseCppCompileFlags}",
"LinkerScript": "binary.ld",
"LinkerFlags": "-Wl,--gc-sections"
},
},
"sources": [ // Obviously you can use separate subdirectories;
// this is just a very very simple baremetal program.
"Sources": [
"startup.S", "startup.S",
"main.cpp" "main.cpp"
] ]
} }
} }
``` ```
- No conditional compilation - `BaseCCompileFlags` and `BaseCppCompileFlags` are defaulted to sane values for each language.
- All files in a project are built by that project
- This will be transpiled into a `Makefile` by the addon.
- A standalone tool will be provided and used for transpiling `project.json` to a `Makefile` (and maybe even passed into the container and transpiled there, to reduce the actions on the host to just the podman run?)
- which is then run with `make` in a temporary podman container which only has access to the source code for the project (and nothing else, besides riscv tools).
- Command line is probably something like `make CONFIG=${config}`
- the output binary will be stored alongside the source code on the server side, with a name like `${name}-${config}.bin`
- This file can then be selected for loading (without uploading from the client).
- There is no conditional compilation in the `project.json` system
- All files in a project are always built by that project.
- Text editor used to edit project source files - Text editor used to edit project source files
- Use the Wire editor? (we need wiremod anyways, and the text editor is.. OK I suppose.)
- Some example projects? - Some example projects?
- I joke about it, but an RTOS would be really nice and a good stress test of the project system (for usage in "real" projects.) - I joke about it, but an RTOS would be really nice and a good stress test of the project system (for usage in "real" projects.)
## Moderation/administration tools ## Moderation/administration tools
- Admin controlled per-user max RAM size (default 64mb) - Admin controlled per-user max RAM size (default 64mb)
- possibly override for "respectful" users and admins (admins probably wouldn't even count)?
- Admin controlled per-user max LCPU entity count (default 8) - Admin controlled per-user max LCPU entity count (default 8)
- Admins don't count to limits
- Admin controled global (affects all placed LCPUs) scheduler cycle rate. - Admin controled global (affects all placed LCPUs) scheduler cycle rate/instruction count.
- Couldn't be faster than tickrate though or we might block source (and.. well, i dont think i have to explain) - default is 0.1/1024 instructions
- I decided not to go with the cpu thread stuff just because its annoying, and would require more state tracking. just ticking in lua using `ENT:Think` should be more than good enough (even if theres a risk of hitching source, but I don't think it's that big of a problem...)
- Project compilations however will definitely end up in a different thread though. Running them in the engine thread would undoubtably cause issues.
## Addon interopability ## Addon interopability

View File

@ -6,9 +6,10 @@ if SERVER then
if LCPUNative.ModuleVersion ~= 1 then if LCPUNative.ModuleVersion ~= 1 then
print("Your LCPU native module is somehow lagging behind the Lua code. Please rebuild it.") print("Your LCPU native module is somehow lagging behind the Lua code. Please rebuild it.")
LCPUNative = nil LCPUNative = nil
return return
end end
--LCPUNative.EnableDebug()
AddCSLuaFile("entities/gmod_lcpu_cpu.lua") AddCSLuaFile("entities/gmod_lcpu_cpu.lua")
end end

View File

@ -1,28 +1,57 @@
AddCSLuaFile() AddCSLuaFile()
DEFINE_BASECLASS("base_wire_entity") -- for now? DEFINE_BASECLASS("base_wire_entity")
ENT.PrintName = "LCPU" ENT.PrintName = "LCPU"
ENT.Author = "Lily <3" ENT.Author = "Lily <3"
-- no more, this deeply uses native APIs -- no more, this deeply uses native APIs
if CLIENT then return end if CLIENT then return end
-- TODO: serverside convars to control execution rate & cycle count
function ENT:Initialize() function ENT:Initialize()
self:PhysicsInit(SOLID_VPHYSICS) self:PhysicsInit(SOLID_VPHYSICS)
self:SetMoveType(MOVETYPE_VPHYSICS) self:SetMoveType(MOVETYPE_VPHYSICS)
self:SetSolid(SOLID_VPHYSICS) self:SetSolid(SOLID_VPHYSICS)
-- 64 kb of ram for now.
self.cpu = LCPUNative.CreateCPU(128 * 1024) self.cpu = LCPUNative.CreateCPU(128 * 1024)
-- UART
self.uart = LCPUNative.CreateDevice(0x10000000, 0xc)
self.uart.buffer = ""
function self.uart:Peek(address)
if address == self.Base then return 0 end
if address == self.Base + 5 then return 0x60 end --
return 0xffffffff
end
function self.uart:Poke(address, value)
if address == self.Base then
local c = bit.band(value, 0x000000ff)
if c == 0 then return end
-- Newline, reset the buffer
if c == 0xa then
print(self.buffer)
self:Reset()
else
-- Not a newline so we can keep going with it
self.buffer = self.buffer .. string.char(c)
end
end
end
function self.uart:Reset()
self.buffer = ""
end
-- todo: cpu callbacks? once they become a thing -- todo: cpu callbacks? once they become a thing
-- test device framework -- test device framework
-- (for once something works out how I wanted it to..) -- (for once something works out how I wanted it to..)
self.test_device = LCPUNative.CreateDevice(0x11300000, 0x8) self.test_device = LCPUNative.CreateDevice(0x11300000, 0x8)
self.test_device.register = 0x0 self.test_device.register = 0x0
--function self.test_device:Clock()
--print("TestDevice Clock()")
--end
function self.test_device:Peek(address) function self.test_device:Peek(address)
--print("peek @ " .. address) --print(string.format("TestDevice:Peek @ 0x%08x", address))
if address == self.Base then return CurTime() end -- it a test! if address == self.Base then return CurTime() end -- it a test!
if address == self.Base + 4 then return self.register end if address == self.Base + 4 then return self.register end
@ -30,26 +59,35 @@ function ENT:Initialize()
end end
function self.test_device:Poke(address, value) function self.test_device:Poke(address, value)
print("poke of address " .. address .. " -> " .. value) --print(string.format("TestDevice:Poke @ 0x%08x -> 0x%08x", address, value))
if address == self.Base + 4 then if address == self.Base + 4 then
print("LUAREG write") --print("LUAREG write")
self.register = value self.register = value
end end
end end
function self.test_device:Reset() function self.test_device:Reset()
print("device was reset") --print("TestDevice:Reset")
-- clear the register -- clear the register
self.register = 0 self.register = 0
end end
self.cpu:AttachDevice(self.test_device)
self.cpu:AttachDevice(self.uart);
self.cpu:AttachDevice(self.test_device);
self:SetOverlayText("hi :)")
end end
function ENT:Think() function ENT:Think()
-- -- Avoid running if the cpu is not powered on
if not self.cpu:PoweredOn() then return end if not self.cpu:PoweredOn() then return end
self.cpu:Cycle()
-- Even though this is gated by tickrate I'm just trying to be nice here -- Even though this is gated by tickrate I'm just trying to be nice here
self:NextThink(CurTime() + 0.1) self:NextThink(CurTime() + 0.1)
self.cpu:Cycle()
end
function ENT:PowerOn()
self.cpu:PowerOn()
self:NextThink(CurTime() + 0.1)
end end

View File

@ -12,13 +12,11 @@ if CLIENT then
text = "Create/Update LCPU" text = "Create/Update LCPU"
}, },
} }
--{ name = "right", text = "Open editor" },
--{ name = "reload", text = "Attach debugger" },
--{ name = "reload_shift", text = "Shift+Reload: Clear" },
end end
WireToolSetup.BaseLang() WireToolSetup.BaseLang()
WireToolSetup.SetupMax(7) WireToolSetup.SetupMax(8)
TOOL.ClientConVar = { TOOL.ClientConVar = {
model = "models/cheeze/wires/cpu.mdl", model = "models/cheeze/wires/cpu.mdl",
} }
@ -77,11 +75,22 @@ end
if CLIENT then if CLIENT then
function TOOL.BuildCPanel(panel) function TOOL.BuildCPanel(panel)
local modelPanel = WireDermaExts.ModelSelect(panel, "lcpu_cpu_model", list.Get("Wire_gate_Models"), 2) local modelPanel = WireDermaExts.ModelSelect(panel, "wire_lcpu_model", list.Get("Wire_gate_Models"), 2)
panel:AddControl( panel:AddControl(
"Label", "Label",
{ {
Text = "" Text = "LCPU Options:"
}
)
panel:AddControl("CheckBox", {
Label = "Start powered on"
})
panel:AddControl(
"Label",
{
Text = "Wire Interface Device options:"
} }
) )
end end

View File

@ -3,6 +3,7 @@
#include <GarrysMod/Lua/Interface.h> #include <GarrysMod/Lua/Interface.h>
#include "LuaCpu.hpp" #include "LuaCpu.hpp"
#include "LuaDevice.hpp" #include "LuaDevice.hpp"
#include "lucore/Logger.hpp"
namespace lcpu { namespace lcpu {
LUA_FUNCTION(LCPUNative_CreateCPU) { LUA_FUNCTION(LCPUNative_CreateCPU) {
@ -13,6 +14,7 @@ namespace lcpu {
if(memorySize > (64 * 1024 * 1024)) if(memorySize > (64 * 1024 * 1024))
LUA->ThrowError("Over RAM size limit."); LUA->ThrowError("Over RAM size limit.");
lucore::LogDebug("Creating Lua CPU object with 0x{:08x} memory size", memorySize);
LuaCpu::Create(LUA, memorySize); LuaCpu::Create(LUA, memorySize);
return 1; return 1;
} }
@ -20,12 +22,24 @@ namespace lcpu {
LUA_FUNCTION(LCPUNative_CreateDevice) { LUA_FUNCTION(LCPUNative_CreateDevice) {
auto base = LUA->CheckNumber(1); auto base = LUA->CheckNumber(1);
auto size = LUA->CheckNumber(2); auto size = LUA->CheckNumber(2);
lucore::LogInfo("Creating Lua device object mapped @ 0x{:08x} with size 0x{:08x}", static_cast<riscv::Address>(base), lucore::LogDebug("Creating Lua device object mapped @ 0x{:08x} with size 0x{:08x}", static_cast<riscv::Address>(base),
static_cast<riscv::Address>(size)); static_cast<riscv::Address>(size));
LuaDevice::Create(LUA, static_cast<riscv::Address>(base), static_cast<riscv::Address>(size)); LuaDevice::Create(LUA, static_cast<riscv::Address>(base), static_cast<riscv::Address>(size));
return 1; return 1;
} }
LUA_FUNCTION(LCPUNative_EnableDebug) {
lucore::LogInfo("Enabling debug logging");
lucore::Logger::The().SetLogLevel(lucore::Logger::MessageSeverity::Debug);
return 0;
}
LUA_FUNCTION(LCPUNative_DisableDebug) {
lucore::LogInfo("Disabling debug logging");
lucore::Logger::The().SetLogLevel(lucore::Logger::MessageSeverity::Info);
return 0;
}
void GlobalsBind(GarrysMod::Lua::ILuaBase* LUA) { void GlobalsBind(GarrysMod::Lua::ILuaBase* LUA) {
LuaCpu::RegisterClass(LUA); LuaCpu::RegisterClass(LUA);
LuaDevice::RegisterClass(LUA); LuaDevice::RegisterClass(LUA);
@ -36,8 +50,10 @@ namespace lcpu {
LUA->PushNumber(LCPU_MODULE_VERSION); LUA->PushNumber(LCPU_MODULE_VERSION);
LUA->SetField(-2, "ModuleVersion"); LUA->SetField(-2, "ModuleVersion");
LUA_SET_C_FUNCTION_NAME(LCPUNative_CreateCPU, "CreateCPU"); LUA_SET_C_FUNCTION_NAME(LCPUNative_CreateCPU, "CreateCPU"); /// Create a CPU
LUA_SET_C_FUNCTION_NAME(LCPUNative_CreateDevice, "CreateDevice"); LUA_SET_C_FUNCTION_NAME(LCPUNative_CreateDevice, "CreateDevice"); /// Create a device implemented in Lua
LUA_SET_C_FUNCTION_NAME(LCPUNative_EnableDebug, "EnableDebug"); /// Enable native module debug logging
LUA_SET_C_FUNCTION_NAME(LCPUNative_DisableDebug, "DisableDebug"); /// Disable native module debug logging
LUA->SetField(-2, "LCPUNative"); LUA->SetField(-2, "LCPUNative");
LUA->Pop(); LUA->Pop();
// clang-format on // clang-format on

View File

@ -4,40 +4,14 @@
#include "LuaDevice.hpp" #include "LuaDevice.hpp"
// this is temporary from the test harness, and will be replaced
// at some point.
/// simple 16550 UART implementation
struct SimpleUartDevice : public riscv::Bus::MmioDevice {
constexpr static riscv::Address BASE_ADDRESS = 0x10000000;
riscv::Address Base() const override { return BASE_ADDRESS; }
riscv::Address Size() const override { return 12; } // for now
u32 Peek(riscv::Address address) override {
switch(address) {
case BASE_ADDRESS: return '\0'; // just return 0 for the input register
case BASE_ADDRESS + 5: return 0x60; // active, but no keyboard input
}
return 0;
}
void Poke(riscv::Address address, u32 value) override {
if(address == BASE_ADDRESS) {
char c = value & 0x000000ff;
std::fputc(c, stderr);
}
}
};
namespace lcpu { namespace lcpu {
LUA_MEMBER_FUNCTION_IMPLEMENT(LuaCpu, PoweredOn) { LUA_CLASS_FUNCTION(LuaCpu, PoweredOn) {
auto self = LuaCpu::FromLua(LUA, 1); auto self = LuaCpu::FromLua(LUA, 1);
LUA->PushBool(self->poweredOn); LUA->PushBool(self->poweredOn);
return 1; return 1;
} }
LUA_MEMBER_FUNCTION_IMPLEMENT(LuaCpu, Cycle) { LUA_CLASS_FUNCTION(LuaCpu, Cycle) {
auto self = LuaCpu::FromLua(LUA, 1); auto self = LuaCpu::FromLua(LUA, 1);
if(!self->poweredOn) if(!self->poweredOn)
return 0; return 0;
@ -45,7 +19,7 @@ namespace lcpu {
return 0; return 0;
} }
LUA_MEMBER_FUNCTION_IMPLEMENT(LuaCpu, PowerOff) { LUA_CLASS_FUNCTION(LuaCpu, PowerOff) {
auto self = LuaCpu::FromLua(LUA, 1); auto self = LuaCpu::FromLua(LUA, 1);
if(!self->poweredOn) if(!self->poweredOn)
return 0; return 0;
@ -55,7 +29,7 @@ namespace lcpu {
return 0; return 0;
} }
LUA_MEMBER_FUNCTION_IMPLEMENT(LuaCpu, PowerOn) { LUA_CLASS_FUNCTION(LuaCpu, PowerOn) {
auto self = LuaCpu::FromLua(LUA, 1); auto self = LuaCpu::FromLua(LUA, 1);
if(self->poweredOn) if(self->poweredOn)
return 0; return 0;
@ -65,13 +39,13 @@ namespace lcpu {
return 0; return 0;
} }
LUA_MEMBER_FUNCTION_IMPLEMENT(LuaCpu, Reset) { LUA_CLASS_FUNCTION(LuaCpu, Reset) {
auto self = LuaCpu::FromLua(LUA, 1); auto self = LuaCpu::FromLua(LUA, 1);
self->system->bus->Reset(); self->system->bus->Reset();
return 0; return 0;
} }
LUA_MEMBER_FUNCTION_IMPLEMENT(LuaCpu, AttachDevice) { LUA_CLASS_FUNCTION(LuaCpu, AttachDevice) {
auto self = LuaCpu::FromLua(LUA, 1); auto self = LuaCpu::FromLua(LUA, 1);
auto device = LuaDevice::FromLua(LUA, 2); auto device = LuaDevice::FromLua(LUA, 2);
@ -100,7 +74,6 @@ namespace lcpu {
}; };
// lame test code. this WILL be removed, I just want this for a quick test // lame test code. this WILL be removed, I just want this for a quick test
system->bus->AttachDevice(new SimpleUartDevice);
auto fp = std::fopen("/home/lily/test-gmod.bin", "rb"); auto fp = std::fopen("/home/lily/test-gmod.bin", "rb");
if(fp) { if(fp) {
std::fseek(fp, 0, SEEK_END); std::fseek(fp, 0, SEEK_END);

View File

@ -5,7 +5,7 @@
#include "LuaObject.hpp" #include "LuaObject.hpp"
namespace lcpu { namespace lcpu {
/// Bindings of [riscv::System] to Lua. /// Binding of [riscv::System] to Lua.
struct LuaCpu : public lua::LuaObject<LuaCpu> { struct LuaCpu : public lua::LuaObject<LuaCpu> {
/// Lua binding stuff /// Lua binding stuff
constexpr static const char* Name() { return "LuaCpu"; } constexpr static const char* Name() { return "LuaCpu"; }
@ -17,12 +17,12 @@ namespace lcpu {
~LuaCpu(); ~LuaCpu();
private: private:
LUA_MEMBER_FUNCTION(PoweredOn); // Check if the CPU is powered on LUA_CLASS_FUNCTION_DECL(PoweredOn); // Check if the CPU is powered on
LUA_MEMBER_FUNCTION(Cycle); // do a single cycle (called internally by LCPU entity) LUA_CLASS_FUNCTION_DECL(Cycle); // do a single cycle (called internally by LCPU entity)
LUA_MEMBER_FUNCTION(PowerOff); // power off and reset the LCPU LUA_CLASS_FUNCTION_DECL(PowerOff); // power off and reset the LCPU
LUA_MEMBER_FUNCTION(PowerOn); // power on the LCPU LUA_CLASS_FUNCTION_DECL(PowerOn); // power on the LCPU
LUA_MEMBER_FUNCTION(Reset); // reset the LCPU LUA_CLASS_FUNCTION_DECL(Reset); // reset the LCPU
LUA_MEMBER_FUNCTION(AttachDevice); // attach a LuaDevice to this cpu LUA_CLASS_FUNCTION_DECL(AttachDevice); // attach a LuaDevice to this cpu
// member variables // member variables
riscv::System* system; riscv::System* system;

View File

@ -40,7 +40,7 @@ namespace lcpu {
} }
riscv::Address LuaDevice::Size() const { riscv::Address LuaDevice::Size() const {
return base; return size;
} }
u32 LuaDevice::Peek(riscv::Address address) { u32 LuaDevice::Peek(riscv::Address address) {
@ -51,7 +51,6 @@ namespace lcpu {
lua->Push(-2); // 'self' argument lua->Push(-2); // 'self' argument
lua->PushNumber(static_cast<double>(address)); lua->PushNumber(static_cast<double>(address));
lua->Call(2, 1); lua->Call(2, 1);
auto result = static_cast<u32>(lua->GetNumber(-1)); auto result = static_cast<u32>(lua->GetNumber(-1));
lua->Pop(2); // pop result and the table off lua->Pop(2); // pop result and the table off
return result; return result;

View File

@ -6,7 +6,7 @@
#include "LuaObject.hpp" #include "LuaObject.hpp"
namespace lcpu { namespace lcpu {
/// A work-in-progress binding of [riscv::Bus::MmioDevice] to lua /// Binding of [riscv::Bus::MmioDevice] to Lua
struct LuaDevice : public riscv::Bus::MmioDevice, lcpu::lua::LuaObject<LuaDevice> { struct LuaDevice : public riscv::Bus::MmioDevice, lcpu::lua::LuaObject<LuaDevice> {
/// Lua binding stuff /// Lua binding stuff
constexpr static const char* Name() { return "LuaDevice"; } constexpr static const char* Name() { return "LuaDevice"; }

View File

@ -1,16 +1,21 @@
//! Helpers for binding Lua and C++. //! Helper macros and inlines for binding Lua and C++.
//!
//! If you want to bind a C++ class to Lua, see the //! If you want to bind a C++ class to Lua, see the
//! [lcpu::lua::LuaObject<TImpl>] type in LuaObject.hpp //! [lcpu::lua::LuaObject<TImpl>] type in LuaObject.hpp.
#pragma once #pragma once
#include <GarrysMod/Lua/Interface.h> #include <GarrysMod/Lua/Interface.h>
#include <lucore/Logger.hpp> #include <lucore/Logger.hpp>
// These are like the official GMOD header LUA_FUNCTION but allow forward declaration /// Declare a Lua function inside of a class.
// and implementation inside of classes, making writing class bindings that much less /// This is like the official GMOD header's LUA_FUNCTION but allows:
// of a PITA. Nifty! /// - forward declaration
#define LUA_MEMBER_FUNCTION(FUNC) \ /// - being put inside of a class
/// - an out-of-line implementation (in .cpp file)
///
/// This makes writing class bindings a lot less annoying.
#define LUA_CLASS_FUNCTION_DECL(FUNC) \
static int FUNC##__ImpStatic(GarrysMod::Lua::ILuaBase* LUA); \ static int FUNC##__ImpStatic(GarrysMod::Lua::ILuaBase* LUA); \
static int FUNC(lua_State* L) { \ static int FUNC(lua_State* L) { \
GarrysMod::Lua::ILuaBase* LUA = L->luabase; \ GarrysMod::Lua::ILuaBase* LUA = L->luabase; \
@ -18,20 +23,28 @@
return FUNC##__ImpStatic(LUA); \ return FUNC##__ImpStatic(LUA); \
} }
#define LUA_MEMBER_FUNCTION_IMPLEMENT(CLASS, FUNC) int CLASS::FUNC##__ImpStatic(GarrysMod::Lua::ILuaBase* LUA) /// Implement a previously declared (with [LUA_CLASS_FUNCTION_DECL]) Lua function
/// from a class.
#define LUA_CLASS_FUNCTION(CLASS, FUNC) int CLASS::FUNC##__ImpStatic(GarrysMod::Lua::ILuaBase* LUA)
// Set a C function as a field. /// Set a C function as a field of a table.
/// The stack layout prior to this function should be:
/// -1 (top) -> table
#define LUA_SET_C_FUNCTION(name) \ #define LUA_SET_C_FUNCTION(name) \
LUA->PushCFunction(name); \ LUA->PushCFunction(name); \
LUA->SetField(-2, #name); LUA->SetField(-2, #name);
// Set a C function as a field with an alternative field name.
/// Set a C function as a field of a table, with an alternative name.
/// The stack layout prior to this function should be:
/// -1 (top) -> table
#define LUA_SET_C_FUNCTION_NAME(name, altName) \ #define LUA_SET_C_FUNCTION_NAME(name, altName) \
LUA->PushCFunction(name); \ LUA->PushCFunction(name); \
LUA->SetField(-2, altName); LUA->SetField(-2, altName);
namespace lcpu::lua { namespace lcpu::lua {
/// Get a string from Lua as a STL string.
inline std::string GetLuaString(GarrysMod::Lua::ILuaBase* LUA, int stackPos) { inline std::string GetLuaString(GarrysMod::Lua::ILuaBase* LUA, int stackPos) {
unsigned len {}; unsigned len {};
auto ptr = LUA->GetString(stackPos, &len); auto ptr = LUA->GetString(stackPos, &len);

View File

@ -4,6 +4,7 @@
#include "GarrysMod/Lua/Interface.h" #include "GarrysMod/Lua/Interface.h"
#include "GarrysMod/Lua/LuaBase.h" #include "GarrysMod/Lua/LuaBase.h"
#include "GarrysMod/Lua/Types.h"
#include "LuaHelpers.hpp" #include "LuaHelpers.hpp"
namespace lcpu::lua { namespace lcpu::lua {
@ -27,6 +28,8 @@ namespace lcpu::lua {
/// Register a setter. This can be used to make a /// Register a setter. This can be used to make a
/// C++ registered value read-write. /// C++ registered value read-write.
/// Notes:
/// - Stack index 3 will always be the value to be set.
static void RegisterSetter(const std::string& name, ILuaVoidFunc func) { setters()[name] = func; } static void RegisterSetter(const std::string& name, ILuaVoidFunc func) { setters()[name] = func; }
virtual void AfterLuaInit() {}; virtual void AfterLuaInit() {};
@ -42,7 +45,6 @@ namespace lcpu::lua {
} }
static TImpl* FromLua(GarrysMod::Lua::ILuaBase* LUA, int stackPos) { static TImpl* FromLua(GarrysMod::Lua::ILuaBase* LUA, int stackPos) {
LUA->CheckType(stackPos, __lua_typeid);
return LUA->GetUserType<TImpl>(stackPos, __lua_typeid); return LUA->GetUserType<TImpl>(stackPos, __lua_typeid);
} }
@ -95,11 +97,14 @@ namespace lcpu::lua {
RegisterGetter("Name", [](GarrysMod::Lua::ILuaBase* LUA) { LUA->PushString(TImpl::Name()); }); RegisterGetter("Name", [](GarrysMod::Lua::ILuaBase* LUA) { LUA->PushString(TImpl::Name()); });
} }
/// Get the user property table reference. This can be used
/// to fetch user properties from native C++ code.
int GetTableReference() { return tableReference; } int GetTableReference() { return tableReference; }
LuaObject() = default; LuaObject() = default;
virtual ~LuaObject() { virtual ~LuaObject() {
// free the table reference // free the table reference so it gets garbage collected too
if(tableReference != -1) if(tableReference != -1)
lua->ReferenceFree(tableReference); lua->ReferenceFree(tableReference);
} }
@ -108,12 +113,11 @@ namespace lcpu::lua {
GarrysMod::Lua::ILuaBase* lua; GarrysMod::Lua::ILuaBase* lua;
private: private:
// base metamethods LUA_CLASS_FUNCTION_DECL(__gc)
LUA_MEMBER_FUNCTION(__gc) LUA_CLASS_FUNCTION_DECL(__index)
LUA_MEMBER_FUNCTION(__index) LUA_CLASS_FUNCTION_DECL(__newindex)
LUA_MEMBER_FUNCTION(__newindex)
// static stuff /// Lua type ID for this wrapped class
static int __lua_typeid; static int __lua_typeid;
static auto& methods() { static auto& methods() {
@ -139,19 +143,20 @@ namespace lcpu::lua {
int LuaObject<TImpl>::__lua_typeid = 0; int LuaObject<TImpl>::__lua_typeid = 0;
template <class TImpl> template <class TImpl>
LUA_MEMBER_FUNCTION_IMPLEMENT(LuaObject<TImpl>, __gc) { LUA_CLASS_FUNCTION(LuaObject<TImpl>, __gc) {
auto self = FromLua(LUA, 1); auto self = FromLua(LUA, 1);
if(self != nullptr) { if(self != nullptr) {
lucore::LogInfo("GCing LuaObject {} @ {:p}", TImpl::Name(), static_cast<void*>(self)); lucore::LogDebug("GCing LuaObject {} @ {:p}", TImpl::Name(), static_cast<void*>(self));
delete self; delete self;
} }
return 0; return 0;
} }
template <class TImpl> template <class TImpl>
LUA_MEMBER_FUNCTION_IMPLEMENT(LuaObject<TImpl>, __index) { LUA_CLASS_FUNCTION(LuaObject<TImpl>, __index) {
auto self = FromLua(LUA, 1); auto self = FromLua(LUA, 1);
// If the key is something we support,
if(LUA->GetType(2) == GarrysMod::Lua::Type::String) { if(LUA->GetType(2) == GarrysMod::Lua::Type::String) {
auto& methods = LuaObject::methods(); auto& methods = LuaObject::methods();
auto& getters = LuaObject::getters(); auto& getters = LuaObject::getters();
@ -167,19 +172,22 @@ namespace lcpu::lua {
getters[key](LUA); getters[key](LUA);
return 1; return 1;
} }
lucore::LogDebug("LuaObject::__index({}) going to table", key);
} }
// look up from the table // Failing to look up an item is not fatal;
// we simply then look up the key in the backing table.
// clang-format off // clang-format off
LUA->ReferencePush(self->tableReference); LUA->ReferencePush(self->tableReference); // push table reference
LUA->Push(2); LUA->Push(2); // push key onto the stack
LUA->GetTable(-2); LUA->GetTable(-2); // push table[key] onto the top of stack
// clang-format on // clang-format on
return 1; return 1;
} }
template <class TImpl> template <class TImpl>
LUA_MEMBER_FUNCTION_IMPLEMENT(LuaObject<TImpl>, __newindex) { LUA_CLASS_FUNCTION(LuaObject<TImpl>, __newindex) {
auto self = FromLua(LUA, 1); auto self = FromLua(LUA, 1);
if(LUA->GetType(2) == GarrysMod::Lua::Type::String) { if(LUA->GetType(2) == GarrysMod::Lua::Type::String) {
@ -197,22 +205,21 @@ namespace lcpu::lua {
return 0; return 0;
if(setters.find(key) != setters.end()) { if(setters.find(key) != setters.end()) {
// clang-format off
// Push the value to be written onto the top of the stack.
// This is mostly for ergonomic reasons.
LUA->Push(3);
setters[key](LUA); setters[key](LUA);
LUA->Pop();
return 0; return 0;
} }
lucore::LogDebug("LuaObject::__newindex({}) going to table", key);
} }
// push onto the table
// set the provided value onto the table
// clang-format off // clang-format off
LUA->ReferencePush(self->tableReference); LUA->ReferencePush(self->tableReference); // table
LUA->Push(2); LUA->Push(2); // key
LUA->Push(3); LUA->Push(3); // value
LUA->SetTable(-3); LUA->SetTable(-3); // table[key] = value
LUA->Pop(); LUA->Pop();
// clang-format on // clang-format on
return 0; return 0;

View File

@ -1,6 +1,7 @@
#include <algorithm> #include <algorithm>
#include <riscv/Bus.hpp> #include <riscv/Bus.hpp>
#include <riscv/CPU.hpp> #include <riscv/CPU.hpp>
#include <lucore/Logger.hpp>
namespace riscv { namespace riscv {
@ -34,10 +35,10 @@ namespace riscv {
// Refuse to overlap a device at its base address.. // Refuse to overlap a device at its base address..
if(FindDeviceForAddress(upcasted->Base())) if(FindDeviceForAddress(upcasted->Base()))
return false; return false;
// ... or have the end overlap the start of another device. // ... or have the end overlap the start of another device.
else if(FindDeviceForAddress(upcasted->Base() + upcasted->Size())) else if(FindDeviceForAddress(upcasted->Base() + upcasted->Size()))
return false; return false;
mmio_devices[upcasted->Base()] = upcasted; mmio_devices[upcasted->Base()] = upcasted;
} }
@ -123,6 +124,8 @@ namespace riscv {
Bus::Device* Bus::FindDeviceForAddress(Address address) const { Bus::Device* Bus::FindDeviceForAddress(Address address) const {
auto try_find_device = [&](const auto& container, Address address) { auto try_find_device = [&](const auto& container, Address address) {
return std::find_if(container.begin(), container.end(), [&](const auto& pair) { return std::find_if(container.begin(), container.end(), [&](const auto& pair) {
//lucore::LogInfo("0x{:08x} base, 0x{:08x} size -> {}", pair.first, pair.second->Size(), static_cast<void*>(pair.second));
return return
// We can shorcut region checking if the requested addess matches base address. // We can shorcut region checking if the requested addess matches base address.
pair.first == address || pair.first == address ||

View File

@ -1,5 +1,5 @@
// a simple test program - this version would talk to a device // a simple test program - this version talks to a device
// written in GLua // that is implemented in GLua thanks to the LCPU native addon
#include <stdarg.h> #include <stdarg.h>
#include <stdint.h> #include <stdint.h>
@ -14,7 +14,7 @@ uint32_t strlen(const char* str) {
#define GLUA_DEVICE_BASE 0x11300000 // base address of the lua test device #define GLUA_DEVICE_BASE 0x11300000 // base address of the lua test device
#define GLUA_DEVICE_WORLDTIME *(volatile uint32_t*)GLUA_DEVICE_BASE // world time register (read only) #define GLUA_DEVICE_WORLDTIME *(volatile uint32_t*)GLUA_DEVICE_BASE // world time register (read only)
#define GLUA_DEVICE_LUAREG *(volatile uint32_t*)(GLUA_DEVICE_BASE + 4) // lua register (read/write) #define GLUA_DEVICE_LUAREG *(volatile uint32_t*)(GLUA_DEVICE_BASE + 4) // lua number register (read/write)
#define SYSCON *(volatile uint32_t*)0x11100000 #define SYSCON *(volatile uint32_t*)0x11100000
@ -74,6 +74,8 @@ void vprintf(const char* format, va_list val) {
case '%': case '%':
if(format[i + 1] == '%') if(format[i + 1] == '%')
putc('%'); putc('%');
if(format[i+1] == '\0')
return;
switch(format[i + 1]) { switch(format[i + 1]) {
case 'i': case 'i':
case 'd': { case 'd': {
@ -98,6 +100,8 @@ void vprintf(const char* format, va_list val) {
default: putc(' '); break; default: putc(' '); break;
} }
break; break;
case '\0': // band-aid fix.
return;
default: putc(format[i]); break; default: putc(format[i]); break;
} }
} }
@ -111,7 +115,7 @@ void printf(const char* format, ...) {
} }
void main() { void main() {
puts("fuck you garry I win"); puts("fuck you garry I win\n");
for(int i = 0; i < 8; ++i) for(int i = 0; i < 8; ++i)
printf("GLUA_DEVICE_WORLDTIME reading says -> %d\n", GLUA_DEVICE_WORLDTIME); printf("GLUA_DEVICE_WORLDTIME reading says -> %d\n", GLUA_DEVICE_WORLDTIME);