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:
parent
bdd6839fa2
commit
8eb4b6ef41
|
@ -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
|
||||||
|
|
51
ideas.md
51
ideas.md
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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"; }
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
setters[key](LUA);
|
||||||
// Push the value to be written onto the top of the stack.
|
|
||||||
// This is mostly for ergonomic reasons.
|
|
||||||
LUA->Push(3);
|
|
||||||
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;
|
||||||
|
|
|
@ -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 ||
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue