more lua binding fun

This commit is contained in:
Lily Tsuru 2023-07-25 06:46:52 -04:00
parent c69dc8d753
commit 9c5415c061
14 changed files with 449 additions and 70 deletions

View File

@ -11,6 +11,33 @@ function ENT:Initialize()
self:SetSolid(SOLID_VPHYSICS)
-- 64 kb of ram for now.
self.cpu = LCPUNative.CreateCPU(128 * 1024)
-- test device framework
-- this is how I ideally want to do things,
-- dunno if it's possible
self.test_device = LCPUNative.CreateDevice()
print(self.test_device)
self.test_device:SetBase(0x12000000)
self.test_device:SetSize(0x10)
function self.test_device:Clock()
print("TestDevice Clock()")
end
function self.test_device:Peek(address)
if address == 0x12000000 then
-- it a test!
return CurTime()
end
return 0x10000
end
function self.test_device:Poke(address, value)
if address == 0x12000000 then
print("What you doing?")
end
end
self.cpu:AttachDevice(self.test_device)
end
function ENT:Think()

2
lua/lcpu/upload_lib.lua Normal file
View File

@ -0,0 +1,2 @@
-- upload library
-- (TODO)

View File

@ -4,8 +4,7 @@ add_library(lcpu_native SHARED
src/main.cpp
src/LcpuGlobals.cpp
src/LuaCpu.cpp
#src/LuaDevice.cpp
src/LuaDevice.cpp
src/SourceSink.cpp
)

View File

@ -1,35 +1,30 @@
#include "LcpuGlobals.hpp"
#include <cmath>
#include "LuaCpu.hpp"
#include "LuaDevice.hpp"
/// This should be bumped on any incompatible change to the native bindings
/// that would break older Lua code, or requires newer Lua code to run.
#define LCPU_MODULE_VERSION 1
LUA_FUNCTION(LCPUNative_CreateCPU) {
LUA->CheckType(1, GarrysMod::Lua::Type::Number);
auto memorySize = (u32)std::round(LUA->GetNumber(1));
auto memorySize = static_cast<u32>(LUA->GetNumber(1));
// TODO: There's probably a way to like, ensure a per-player max.
if(memorySize > (64 * 1024 * 1024)) {
if(memorySize > (64 * 1024 * 1024))
LUA->ThrowError("Over RAM size limit.");
}
LuaCpu::Create(LUA, memorySize);
return 1;
}
LUA_FUNCTION(LCPUNative_CreateDevice) {
//LuaDevice::Create(LUA);
return 0;//1;
LuaDevice::Create(LUA);
return 1;
}
void GlobalsBind(GarrysMod::Lua::ILuaBase* LUA) {
LuaCpu::Bind(LUA);
//LuaDevice::Bind(LUA);
// LuaDevice::Bind(LUA);
// clang-format off
LUA->PushSpecial(GarrysMod::Lua::SPECIAL_GLOB);

View File

@ -1,3 +1,7 @@
#include "LuaHelpers.hpp"
/// This should be bumped on any incompatible change to the native bindings
/// that would break older Lua code, or requires newer Lua code to run.
#define LCPU_MODULE_VERSION 1
void GlobalsBind(GarrysMod::Lua::ILuaBase* LUA);

View File

@ -34,67 +34,56 @@ struct SimpleUartDevice : public riscv::Bus::MmioDevice {
LUA_CLASS_BIND_VARIABLES_IMPLEMENT(LuaCpu);
LUA_MEMBER_FUNCTION_IMPLEMENT(LuaCpu, PoweredOn) {
LUA_CLASS_GET(LuaCpu, self, 1);
auto self = LUA_CLASS_GET(LuaCpu)(1);
LUA->PushBool(self->poweredOn);
return 1;
}
LUA_MEMBER_FUNCTION_IMPLEMENT(LuaCpu, Cycle) {
LUA_CLASS_GET(LuaCpu, self, 1);
[&self]() {
if(!self->poweredOn)
return;
self->system->Step();
}();
auto self = LUA_CLASS_GET(LuaCpu)(1);
if(!self->poweredOn)
return 0;
self->system->Step();
return 0;
}
LUA_MEMBER_FUNCTION_IMPLEMENT(LuaCpu, PowerOff) {
LUA_CLASS_GET(LuaCpu, self, 1);
[&self]() {
if(!self->poweredOn)
return;
self->poweredOn = false;
self->system->bus->Reset();
}();
auto self = LUA_CLASS_GET(LuaCpu)(1);
if(!self->poweredOn)
return 0;
self->poweredOn = false;
self->system->bus->Reset();
return 0;
}
LUA_MEMBER_FUNCTION_IMPLEMENT(LuaCpu, PowerOn) {
LUA_CLASS_GET(LuaCpu, self, 1);
[&self]() {
if(self->poweredOn)
return;
auto self = LUA_CLASS_GET(LuaCpu)(1);
if(self->poweredOn)
return 0;
self->poweredOn = true;
self->system->bus->Reset();
}();
self->poweredOn = true;
self->system->bus->Reset();
return 0;
}
LUA_MEMBER_FUNCTION_IMPLEMENT(LuaCpu, Reset) {
LUA_CLASS_GET(LuaCpu, self, 1);
[&self]() { self->system->bus->Reset(); }();
auto self = LUA_CLASS_GET(LuaCpu)(1);
self->system->bus->Reset();
return 0;
}
LUA_MEMBER_FUNCTION_IMPLEMENT(LuaCpu, AttachDevice) {
LUA_CLASS_GET(LuaCpu, self, 1);
bool result = false;
#if 0
[&]() {
LUA_CLASS_GET(LuaDevice, device, 2);
if(!device)
return; // the bus is safe against this possibility, but
// I'd rather be doubly-safe tbh
auto self = LUA_CLASS_GET(LuaCpu)(1);
auto device = LUA_CLASS_GET(LuaDevice)(1);
// the bus is safe against this possibility, but
// I'd rather be doubly-safe tbh
if(!device)
LUA->ThrowError("Null device pointer");
// Attach it
result = self->system->bus->AttachDevice(static_cast<riscv::Bus::Device*>(device));
}();
#endif
LUA->PushBool(result);
// Attach it
LUA->PushBool(self->system->bus->AttachDevice(static_cast<riscv::Bus::Device*>(device)));
return 1;
}
@ -116,7 +105,7 @@ void LuaCpu::Create(GarrysMod::Lua::ILuaBase* LUA, u32 memorySize) {
// lame test code. this WILL be removed, I just want this for a quick test
cpuWrapper->system->bus->AttachDevice(new SimpleUartDevice);
auto fp = std::fopen("/home/lily/test.bin", "rb");
auto fp = std::fopen("/home/lily/test-gmod.bin", "rb");
if(fp) {
std::fseek(fp, 0, SEEK_END);
auto len = std::ftell(fp);
@ -130,8 +119,6 @@ void LuaCpu::Create(GarrysMod::Lua::ILuaBase* LUA, u32 memorySize) {
}
LuaCpu::LuaCpu(u32 memorySize) {
lucore::LogInfo("in LuaCpu::LuaCpu(0x{:08x})\n", memorySize);
poweredOn = true;
system = riscv::System::Create(memorySize);
system->OnPowerOff = [&]() {

View File

@ -1,15 +1,107 @@
#include "LuaDevice.hpp"
#include "LuaHelpers.hpp"
LUA_CLASS_BIND_VARIABLES_IMPLEMENT(LuaDevice);
LUA_MEMBER_FUNCTION_IMPLEMENT(LuaDevice, SetBase) {
auto self = LUA_CLASS_GET(LuaDevice)(1);
if(self->bus)
LUA->ThrowError("Do not call this on an attached device");
self->base = static_cast<u32>(LUA->CheckNumber(2));
return 0;
}
LUA_MEMBER_FUNCTION_IMPLEMENT(LuaDevice, SetSize) {
auto self = LUA_CLASS_GET(LuaDevice)(1);
if(self->bus)
LUA->ThrowError("Do not call this on an attached device");
self->size = static_cast<u32>(LUA->CheckNumber(2));
return 0;
}
bool LuaDevice::Clocked() const {
// Even if Lua devices may not have a clock handler
// installed there's no real non-awful way to check,
// so just lie and say yes always.
return true;
}
void LuaDevice::Clock() {
LuaState->PushUserType(this, __lua_typeid);
LuaState->GetField(-2, "Clock");
if(!LuaState->IsType(-1, GarrysMod::Lua::Type::Function)) {
LuaState->Pop(2);
return;
}
LuaState->Push(-2); // "self"
LuaState->Call(1, 0);
LuaState->Pop();
}
riscv::Address LuaDevice::Base() const {
return base;
}
riscv::Address LuaDevice::Size() const {
return base;
}
u32 LuaDevice::Peek(riscv::Address address) {
LuaState->PushUserType(this, __lua_typeid);
LuaState->GetField(-2, "Peek");
if(!LuaState->IsType(-1, GarrysMod::Lua::Type::Function)) {
LuaState->Pop(2);
return -1;
}
LuaState->Push(-2); // "self"
LuaState->PushNumber(address);
LuaState->Call(2, 1);
auto result = LuaState->GetNumber(-1);
LuaState->Pop();
return static_cast<u32>(result);
}
void LuaDevice::Poke(riscv::Address address, u32 value) {
LuaState->PushUserType(this, __lua_typeid);
LuaState->GetField(-2, "Poke");
if(!LuaState->IsType(-1, GarrysMod::Lua::Type::Function)) {
LuaState->Pop(2);
return;
}
LuaState->Push(-2); // "self"
LuaState->PushNumber(address);
LuaState->PushNumber(value);
LuaState->Call(3, 0);
LuaState->Pop();
}
void LuaDevice::Reset() {
LuaState->PushUserType(this, __lua_typeid);
LuaState->GetField(-2, "Reset");
if(!LuaState->IsType(-1, GarrysMod::Lua::Type::Function))
return;
LuaState->Push(-2); // "self"
LuaState->Call(1, 0);
LuaState->Pop();
}
LuaDevice::~LuaDevice() = default;
void LuaDevice::Bind(GarrysMod::Lua::ILuaBase* LUA) {
// clang-format off
// TODO: I need to figure out how to like, set up metamethod stuff
// so it all properly works.
LUA_CLASS_BIND_BEGIN(LuaDevice);
// todo handlers
LUA_SET_C_FUNCTION(SetBase)
LUA_SET_C_FUNCTION(SetSize)
LUA_CLASS_BIND_END();
// clang-format on
}
void LuaDevice::Create(GarrysMod::Lua::ILuaBase* LUA) {
LUA->PushUserType(new LuaDevice(), __lua_typeid);
auto device = new LuaDevice();
device->LuaState = LUA;
LUA->PushUserType(device, __lua_typeid);
}

View File

@ -10,8 +10,14 @@ struct LuaDevice : public riscv::Bus::MmioDevice {
static void Bind(GarrysMod::Lua::ILuaBase* LUA);
static void Create(GarrysMod::Lua::ILuaBase* LUA);
riscv::Address Base() const override { return base; }
riscv::Address Size() const override { return size; } // I think this is right?
~LuaDevice();
bool Clocked() const override;
void Clock() override;
void Reset() override;
riscv::Address Base() const override;
riscv::Address Size() const override;
u32 Peek(riscv::Address address) override;
void Poke(riscv::Address address, u32 value) override;
@ -24,14 +30,10 @@ struct LuaDevice : public riscv::Bus::MmioDevice {
LUA_MEMBER_FUNCTION(SetBase);
LUA_MEMBER_FUNCTION(SetSize);
LUA_MEMBER_FUNCTION(SetClockHandler);
LUA_MEMBER_FUNCTION(SetResetHandler);
LUA_MEMBER_FUNCTION(SetPeekHandler);
LUA_MEMBER_FUNCTION(SetPokeHandler);
// GetBase/GetSize?
riscv::Address base {};
riscv::Address size {};
// ...?
GarrysMod::Lua::ILuaBase* LuaState;
};

View File

@ -2,6 +2,7 @@
#pragma once
#include <GarrysMod/Lua/Interface.h>
#include <lucore/Logger.hpp>
// These are like the official GMOD header LUA_FUNCTION but allow forward declaration
@ -17,20 +18,25 @@
#define LUA_MEMBER_FUNCTION_IMPLEMENT(CLASS, FUNC) int CLASS::FUNC##__ImpStatic(GarrysMod::Lua::ILuaBase* LUA)
// will make a "self" variable with the class
#define LUA_CLASS_GET(T, name, stackPos) \
LUA->CheckType(stackPos, T::__lua_typeid); \
auto name = LUA->GetUserType<T>(stackPos, T::__lua_typeid); \
if(!name) { \
LUA->ThrowError("nullptr " #T " passed to function which requires a valid instance"); \
// this synthesizes a lambda which takes the stack argument to get. this can actually also be
// stored as a variable for later usage (... if you so desire?)
#define LUA_CLASS_GET(T) \
[LUA](int stackPos) { \
LUA->CheckType(stackPos, T::__lua_typeid); \
return LUA->GetUserType<T>(stackPos, T::__lua_typeid); \
}
// This class binding package always implements the __gc metamethod
// to free any C++ object bound to Lua.
// Declare required binding variables.
#define LUA_CLASS_BIND_VARIABLES(ACCESS_LEVEL) \
public: \
static int __lua_typeid; \
ACCESS_LEVEL: \
LUA_MEMBER_FUNCTION(__gc);
// Implement required binding variables (typically in a .cpp file).
#define LUA_CLASS_BIND_VARIABLES_IMPLEMENT(T) \
int T::__lua_typeid = 0; \
LUA_MEMBER_FUNCTION_IMPLEMENT(T, __gc) { \
@ -43,6 +49,8 @@
return 0; \
}
// Begin the Bind() method of a class. This just sets up boilerplate
// and required things to setup a class.
#define LUA_CLASS_BIND_BEGIN(T) \
T::__lua_typeid = LUA->CreateMetaTable(#T); \
LUA->PushSpecial(GarrysMod::Lua::SPECIAL_REG); \
@ -53,13 +61,15 @@
LUA->SetField(-2, "__index"); \
LUA_SET_C_FUNCTION(__gc)
// End the Bind() method.
#define LUA_CLASS_BIND_END() LUA->Pop();
// Helpers for lua functions
// Set a C function as a field.
#define LUA_SET_C_FUNCTION(name) \
LUA->PushCFunction(name); \
LUA->SetField(-2, #name);
// Set a C function as a field with an alternative field name.
#define LUA_SET_C_FUNCTION_NAME(name, altName) \
LUA->PushCFunction(name); \
LUA->SetField(-2, altName);

View File

@ -8,7 +8,7 @@
GMOD_MODULE_OPEN() {
lucore::Logger::The().AttachSink(lcpu::SourceSink::The());
lucore::LogInfo("LCPU Native Module!");
lucore::LogInfo("LCPU Native Module! (ModuleVersion {})", LCPU_MODULE_VERSION);
GlobalsBind(LUA);
return 0;
}

53
test-gmod/Makefile Normal file
View File

@ -0,0 +1,53 @@
PROJECT = test
# where your rv32 toolchain is
TCPATH = /home/lily/bin/riscv/bin
PREFIX = $(TCPATH)/riscv32-unknown-elf
CC = $(PREFIX)-gcc
CXX = $(PREFIX)-g++
ARCHFLAGS = -ffreestanding -fno-stack-protector -fdata-sections -ffunction-sections -march=rv32ima -mabi=ilp32
CCFLAGS = -g -Os $(ARCHFLAGS) -std=c18
CXXFLAGS = $(ARCHFLAGS) -g -Os -std=c++20 -fno-exceptions -fno-rtti
LDFLAGS = -T binary.ld -nostdlib -Wl,--gc-sections
OBJS = start.o \
main.o
.PHONY: all test clean
all: $(PROJECT).bin $(PROJECT).debug.txt
# this assumes the lcpu project build dir you're using is
# [lcpu repo root]/build
test: $(PROJECT).bin $(PROJECT).debug.txt
../../../../build/projects/riscv_test_harness/rvtest $<
clean:
rm $(PROJECT).elf $(PROJECT).bin $(PROJECT).debug.txt $(OBJS)
# Link rules
$(PROJECT).elf: $(OBJS)
$(CC) $(CCFLAGS) $(LDFLAGS) -o $@ $(OBJS)
$(PROJECT).bin : $(PROJECT).elf
$(PREFIX)-objcopy $^ -O binary $@
$(PROJECT).debug.txt : $(PROJECT).elf
$(PREFIX)-objdump -t $^ > $@
$(PREFIX)-objdump -S $^ >> $@
# Compile rules
%.o: %.cpp
$(CXX) -c $(CXXFLAGS) $< -o $@
%.o: %.c
$(CC) -c $(CCFLAGS) $< -o $@
%.o: %.S
$(CC) -x assembler-with-cpp -march=rv32ima -mabi=ilp32 -c $< -o $@

73
test-gmod/binary.ld Normal file
View File

@ -0,0 +1,73 @@
__heap_size = 0x1000;
__stack_size = 0x1000;
ENTRY(_start)
SECTIONS
{
. = 0x80000000;
.text : ALIGN(16) {
__TEXT_BEGIN__ = .;
*(.initial_jump)
*(.entry.text)
*(.init.literal)
*(.init)
*(.text)
*(.literal .text .literal.* .text.* .stub)
*(.out_jump.literal.*)
*(.out_jump.*)
__TEXT_END__ = .;
}
/* If we're on a newer compiler */
/DISCARD/ :
{
*(.interp)
*(.dynsym)
*(.dynstr)
*(.header)
} : phdr
.data : ALIGN(16) {
__DATA_BEGIN__ = .;
*(.rodata)
*(.rodata.*)
*(.gnu.linkonce.r.*)
*(.rodata1)
*(.dynsbss)
*(.gnu.linkonce.sb.*)
*(.scommon)
*(.gnu.linkonce.sb2.*)
*(.sbss)
*(.sbss.*)
*(.sbss2)
*(.sbss2.*)
*(.dynbss)
*(.data)
*(.data.*)
*(.got)
*(.got.*)
__DATA_END__ = .;
}
.bss : ALIGN( 16 ) {
__BSS_BEGIN__ = .;
*(.bss) /* Tricky: BSS needs to be allocated but not sent. GCC Will not populate these for calculating data size */
*(.bss.*)
__BSS_END__ = .;
}
.heap : ALIGN( 16 ) {
_sheap = .;
. = . + __heap_size;
_eheap = .;
}
.stack : ALIGN( 16 ) {
_estack = .;
. = . + __stack_size;
_sstack = .;
}
}

122
test-gmod/main.c Normal file
View File

@ -0,0 +1,122 @@
// a simple test program - this version would talk to a device
// written in GLua
#include <stdint.h>
#include <stdarg.h>
uint32_t strlen(const char* str) {
if(!str)
return 0;
const char* c = str;
while(*c++)
;
return c - str;
}
#define GLUA_DEVICE *(volatile uint32_t*)0x12000000
#define SYSCON *(volatile uint32_t*)0x11100000
#define UART_BASE 0x10000000
#define UART_DATA *(volatile uint32_t*)UART_BASE
#define UART_STATUS UART_DATA
void putc(char c) {
UART_DATA = (uint32_t)c;
}
__attribute__((noinline)) void puts(const char* str) {
const uint32_t length = strlen(str);
for(uint32_t i = 0; i < length; ++i)
putc(str[i]);
}
int itoa(int value, char* sp, int radix) {
char tmp[16];
char* tp = tmp;
int i;
unsigned v;
int sign = (radix == 10 && value < 0);
if(sign)
v = -value;
else
v = (unsigned)value;
while(v || tp == tmp) {
i = v % radix;
v /= radix;
if(i < 10)
*tp++ = i + '0';
else
*tp++ = i + 'a' - 10;
}
int len = tp - tmp;
if(sign) {
*sp++ = '-';
len++;
}
while(tp > tmp)
*sp++ = *--tp;
*sp = '\0';
return len;
}
void vprintf(const char* format, va_list val) {
const int fl = strlen(format);
for(int i = 0; i < fl; ++i) {
switch(format[i]) {
case '%':
if(format[i+1] == '%')
putc('%');
switch(format[i+1]) {
case 'i':
case 'd': {
char a[32];
itoa(va_arg(val, uint32_t), &a[0], 10);
const int al = strlen(a);
for(int j = 0; j < al; ++j)
putc(a[j]);
} break;
case 's': {
char* p = va_arg(val, char*);
if(!p)
puts("(null)");
else
puts(p);
};
default:
putc(' ');
break;
}
break;
default:
putc(format[i]);
break;
}
}
}
void printf(const char* format, ...) {
va_list val;
va_start(val, format);
vprintf(format, val);
va_end(val);
}
void main() {
puts("fuck you garry I win");
for(int i = 0; i < 256; ++i)
printf("uhh %d\n", GLUA_DEVICE);
SYSCON = 0x5555;
}

13
test-gmod/start.S Normal file
View File

@ -0,0 +1,13 @@
# Simple bare-metal RISCV startup code.
.section .initial_jump
.global _start
.extern main
.align 4
_start:
la sp, _sstack # set up C stack
addi sp,sp,-16 # ...
sw ra,12(sp) # ...
jal ra, main # jump to C code!