Compare commits
10 Commits
55eb398b1c
...
a85a8ddc97
Author | SHA1 | Date |
---|---|---|
Lily Tsuru | a85a8ddc97 | |
Lily Tsuru | fb29336ca7 | |
Lily Tsuru | 70d90d4879 | |
Lily Tsuru | 16ac661643 | |
Lily Tsuru | e3ffcb02c2 | |
Lily Tsuru | 0d6646f8cb | |
Lily Tsuru | 3d9f9139ff | |
Lily Tsuru | 9260acce31 | |
Lily Tsuru | cd1a80e4e4 | |
Lily Tsuru | c59dcfc614 |
|
@ -0,0 +1,8 @@
|
||||||
|
Copyright 2023 Lily Tsuru <lily.modeco80@protonmail.ch>
|
||||||
|
Portions copyright 2022 Charles Lohr (CNLohr)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
35
README.md
35
README.md
|
@ -5,13 +5,38 @@ LCPU is an alternative CPU core addon for GMod/Wiremod.
|
||||||
It provides:
|
It provides:
|
||||||
|
|
||||||
- A standard RISC-V architechure (rv32ima) CPU core, implemented in native code
|
- A standard RISC-V architechure (rv32ima) CPU core, implemented in native code
|
||||||
- No wiremod native code embargos here!
|
- No wiremod native code embargos here, so we get actually good performance without half our SENTs becoming lag machine fodder!
|
||||||
- Interoperation with the Wiremod addon (and addons which implement Wiremod integration)
|
- Interoperation with the Wiremod addon (and addons which implement Wiremod integration)
|
||||||
|
|
||||||
# Building Native Module
|
This addon (for now) works with both the non-beta branch and the x86-64 beta branches of GMod.
|
||||||
|
|
||||||
TODO: Steps to build the LCPU native module on Windows and Linux
|
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
This repository is set up in the exact directory structure to be a Legacy Addon; therefore once you have built the native module (TODO), adding it to a server should work 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:
|
||||||
|
|
||||||
|
```
|
||||||
|
garrysmod/addons$ git clone --recursive https://github.com/modeco80/gmod-lcpu.git lcpu
|
||||||
|
garrysmod/addons$ cd lcpu
|
||||||
|
|
||||||
|
# Build the LCPU native module. These steps build the linux64 version of the module
|
||||||
|
# on linux; you'll need to alter it if you want to build for 32-bit.
|
||||||
|
garrysmod/addons/lcpu$ cmake -Wno-dev -GNinja -S native -B build \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release
|
||||||
|
garrysmod/addons/lcpu$ ninja -C build
|
||||||
|
|
||||||
|
# Install the native module (Linux)
|
||||||
|
# For Windows you can do the same thing, just replace this
|
||||||
|
# with how you'd do it in batch (or use Explorer, I'm not your dad)
|
||||||
|
garrysmod/addons/lcpu$ [[ ! -d '../../lua/bin']] && mkdir -p ../../lua/bin && cp build/projects/lcpu/*.dll ../../lua/bin
|
||||||
|
|
||||||
|
# Tada!
|
||||||
|
```
|
||||||
|
|
||||||
|
On Linux you can alternatively use the `./build_module.sh` script that will do all the build and installation steps automatically.
|
||||||
|
|
||||||
|
|
||||||
|
# Special Thanks
|
||||||
|
|
||||||
|
[CNLohr](https://github.com/CNLohr) - I may not like the mini-rv32ima code that much, but it did still help a lot (and my emulation core library is based off it, with tons of refactoring and a C++ rewrite to boot.)
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Build the LCPU addon for the reccomended environment
|
||||||
|
# and install it into the proper directory gmod wants native modules to be.
|
||||||
|
# This expects to be ran in [gmod]/addons/[addon folder].
|
||||||
|
|
||||||
|
set -x
|
||||||
|
|
||||||
|
cmake -Wno-dev -GNinja -S native -B build -DCMAKE_BUILD_TYPE=Release
|
||||||
|
ninja -C build
|
||||||
|
|
||||||
|
[[ ! -d '../../lua/bin' ]] && {
|
||||||
|
mkdir -p ../../lua/bin
|
||||||
|
}
|
||||||
|
|
||||||
|
cp -v build/projects/lcpu/*.dll ../../lua/bin
|
2
ideas.md
2
ideas.md
|
@ -8,7 +8,6 @@ This is basically the working ideas for the LCPU project.
|
||||||
- RISC-V rv32ima core
|
- RISC-V rv32ima core
|
||||||
- Would supporting more than one CPU core type be worthwhile? If so, the project is relatively setup for such workflow...
|
- Would supporting more than one CPU core type be worthwhile? If so, the project is relatively setup for such workflow...
|
||||||
- Controllable paramaters (RAM size, ...)
|
- Controllable paramaters (RAM size, ...)
|
||||||
- Our own framebuffer screen SENT (since wiremod decided to go stupid mode and saw off the gpu)
|
|
||||||
|
|
||||||
## Code upload
|
## Code upload
|
||||||
|
|
||||||
|
@ -63,3 +62,4 @@ This is basically the working ideas for the LCPU project.
|
||||||
- Wiremod interopability
|
- Wiremod interopability
|
||||||
- Wiremod GPIO peripheral (to interface with wire world)
|
- Wiremod GPIO peripheral (to interface with wire world)
|
||||||
- special Console Screen peripheral (interfacing specifically with it)
|
- special Console Screen peripheral (interfacing specifically with it)
|
||||||
|
- frambuffer peripheral which uses the Digital Screen
|
||||||
|
|
|
@ -1,10 +1,3 @@
|
||||||
-- skeleton load file to get gmod to recognize this as an addon
|
-- skeleton load file to get gmod to recognize this as an addon
|
||||||
-- this will contain files later on in life.
|
-- this will contain files later on in life.
|
||||||
|
AddCSLuaFile()
|
||||||
print("hello world?")
|
|
||||||
|
|
||||||
-- detect if wiremod is installed
|
|
||||||
if not istable(WireLib) then
|
|
||||||
print("[LCPU] we need wiremod..")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
|
@ -1,39 +1,18 @@
|
||||||
|
include(./gmod_headers.cmake)
|
||||||
|
|
||||||
add_library(gmod_headers INTERFACE)
|
|
||||||
target_include_directories(gmod_headers INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gmod_headers/include)
|
|
||||||
|
|
||||||
# Originally from facepunch cmake build system, modified to be slightly less painful
|
|
||||||
function(set_gmod_suffix_prefix library)
|
|
||||||
set_target_properties(${library} PROPERTIES PREFIX "gmsv_")
|
|
||||||
if(APPLE)
|
|
||||||
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4")
|
|
||||||
set_target_properties(${library} PROPERTIES SUFFIX "_osx.dll")
|
|
||||||
else()
|
|
||||||
set_target_properties(${library} PROPERTIES SUFFIX "_osx64.dll")
|
|
||||||
endif()
|
|
||||||
elseif(UNIX)
|
|
||||||
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4")
|
|
||||||
set_target_properties(${library} PROPERTIES SUFFIX "_linux.dll")
|
|
||||||
else()
|
|
||||||
set_target_properties(${library} PROPERTIES SUFFIX "_linux64.dll")
|
|
||||||
endif()
|
|
||||||
elseif(WIN32)
|
|
||||||
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4")
|
|
||||||
set_target_properties(${library} PROPERTIES SUFFIX "_win32.dll")
|
|
||||||
else()
|
|
||||||
set_target_properties(${library} PROPERTIES SUFFIX "_win64.dll")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
|
|
||||||
add_library(lcpu_native SHARED
|
add_library(lcpu_native SHARED
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
|
src/SourceSink.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||||
target_link_options(lcpu_native PRIVATE "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/gmod_abi.ver")
|
target_link_options(lcpu_native PRIVATE
|
||||||
|
"-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/gmod_abi.ver"
|
||||||
|
# screw it, dude
|
||||||
|
"-static-libstdc++"
|
||||||
|
"-static-libgcc"
|
||||||
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_link_libraries(lcpu_native
|
target_link_libraries(lcpu_native
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
add_library(gmod_headers INTERFACE)
|
||||||
|
target_include_directories(gmod_headers INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gmod_headers/include)
|
||||||
|
|
||||||
|
# Originally from facepunch cmake build system, modified to be slightly less painful
|
||||||
|
function(set_gmod_suffix_prefix library)
|
||||||
|
set_target_properties(${library} PROPERTIES PREFIX "gmsv_")
|
||||||
|
if(APPLE)
|
||||||
|
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4")
|
||||||
|
set_target_properties(${library} PROPERTIES SUFFIX "_osx.dll")
|
||||||
|
else()
|
||||||
|
set_target_properties(${library} PROPERTIES SUFFIX "_osx64.dll")
|
||||||
|
endif()
|
||||||
|
elseif(UNIX)
|
||||||
|
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4")
|
||||||
|
set_target_properties(${library} PROPERTIES SUFFIX "_linux.dll")
|
||||||
|
else()
|
||||||
|
set_target_properties(${library} PROPERTIES SUFFIX "_linux64.dll")
|
||||||
|
endif()
|
||||||
|
elseif(WIN32)
|
||||||
|
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4")
|
||||||
|
set_target_properties(${library} PROPERTIES SUFFIX "_win32.dll")
|
||||||
|
else()
|
||||||
|
set_target_properties(${library} PROPERTIES SUFFIX "_win64.dll")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endfunction()
|
|
@ -0,0 +1,93 @@
|
||||||
|
#include "SourceSink.hpp"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <lucore/Assert.hpp>
|
||||||
|
#include <lucore/Library.hpp>
|
||||||
|
#include <lucore/Types.hpp>
|
||||||
|
|
||||||
|
// The old non-beta branch of GMod on Linux has multiple tier0 libraries for client and server.
|
||||||
|
// This compatibility define allows to support that case (for now). Once this define is removed,
|
||||||
|
// the old codepath can be totally removed.
|
||||||
|
#define LCPU_SUPPORT_OLD_GMOD
|
||||||
|
|
||||||
|
namespace tier0 {
|
||||||
|
// TODO: For now this'll leak..
|
||||||
|
lucore::Library* library = nullptr;
|
||||||
|
|
||||||
|
using Msg_t = void (*)(const char*, ...);
|
||||||
|
Msg_t Msg {};
|
||||||
|
|
||||||
|
bool OpenLibrary() {
|
||||||
|
#ifdef LCPU_SUPPORT_OLD_GMOD
|
||||||
|
constexpr static std::string_view tier0_libraries[] {
|
||||||
|
#ifdef __linux__
|
||||||
|
"tier0_srv",
|
||||||
|
#endif
|
||||||
|
"tier0"
|
||||||
|
};
|
||||||
|
|
||||||
|
for(auto lib : tier0_libraries) {
|
||||||
|
if(lucore::Library::Loaded(lib)) {
|
||||||
|
// Found the correct tier0 library to open; use that.
|
||||||
|
tier0::library = lucore::Library::OpenExisting(lib);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// The x86-64 branch of GMod, including the 32-bit binaries in the branch,
|
||||||
|
// have a single tier0 library, which makes the codepath much simpler.
|
||||||
|
// Hopefully I can switch to this path at some point.
|
||||||
|
tier0::library = lucore::Library::OpenExisting("tier0");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if(tier0::library == nullptr)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GrabSymbols() {
|
||||||
|
#define GRAB_SYMBOL(name, T) \
|
||||||
|
name = library->Symbol<T>(#name); \
|
||||||
|
if(name == nullptr) \
|
||||||
|
return false;
|
||||||
|
GRAB_SYMBOL(Msg, Msg_t);
|
||||||
|
#undef GRAB_SYMBOL
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace tier0
|
||||||
|
|
||||||
|
namespace lcpu {
|
||||||
|
|
||||||
|
SourceSink& SourceSink::The() {
|
||||||
|
static SourceSink sink;
|
||||||
|
return sink;
|
||||||
|
}
|
||||||
|
|
||||||
|
SourceSink::SourceSink() {
|
||||||
|
if(!tier0::OpenLibrary()) {
|
||||||
|
std::printf("Tier0 could not be opened\n");
|
||||||
|
std::quick_exit(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: A bit nicer of an error message?
|
||||||
|
// Explain *what* to do if you see this message.
|
||||||
|
if(!tier0::GrabSymbols()) {
|
||||||
|
std::printf("Tier0 symbols could not be grabbed\n");
|
||||||
|
std::quick_exit(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SourceSink::~SourceSink() {
|
||||||
|
delete tier0::library;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SourceSink::OutputMessage(const lucore::Logger::MessageData& data) {
|
||||||
|
auto formatted =
|
||||||
|
std::format("[LCPU Native/{}] [{}] {}", lucore::Logger::SeverityToString(data.severity),
|
||||||
|
data.time, std::vformat(data.format, data.args));
|
||||||
|
|
||||||
|
tier0::Msg("%s\n", formatted.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace lcpu
|
|
@ -0,0 +1,16 @@
|
||||||
|
#include <lucore/Logger.hpp>
|
||||||
|
|
||||||
|
namespace lcpu {
|
||||||
|
|
||||||
|
/// A lucore logger sink which funnels to the Source engine's
|
||||||
|
/// Tier0 library's logging functionality.
|
||||||
|
struct SourceSink : public lucore::Logger::Sink {
|
||||||
|
static SourceSink& The();
|
||||||
|
|
||||||
|
SourceSink();
|
||||||
|
~SourceSink();
|
||||||
|
|
||||||
|
void OutputMessage(const lucore::Logger::MessageData& data) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace lcpu
|
|
@ -1,6 +1,19 @@
|
||||||
#include <GarrysMod/Lua/Interface.h>
|
#include <GarrysMod/Lua/Interface.h>
|
||||||
|
|
||||||
|
#include "SourceSink.hpp"
|
||||||
|
|
||||||
|
#include <lucore/Assert.hpp>
|
||||||
|
|
||||||
|
LUA_FUNCTION(lcpu_native_test) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
GMOD_MODULE_OPEN() {
|
GMOD_MODULE_OPEN() {
|
||||||
|
lucore::Logger::The().AttachSink(lcpu::SourceSink::The());
|
||||||
|
|
||||||
|
lucore::LogInfo("LCPU Native Module loading");
|
||||||
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,28 @@ project(lucore
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(lucore
|
add_library(lucore
|
||||||
|
# Assertion/Check failure handling
|
||||||
src/Assert.cpp
|
src/Assert.cpp
|
||||||
|
|
||||||
|
# Logging functionality
|
||||||
src/Logger.cpp
|
src/Logger.cpp
|
||||||
|
src/StdoutSink.cpp
|
||||||
|
|
||||||
|
# Dynamic library wrappers
|
||||||
|
src/Library.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Target-specific Lucore sources.
|
||||||
|
if(WIN32)
|
||||||
|
target_sources(lucore PRIVATE
|
||||||
|
src/OsLibrary.win32.cpp
|
||||||
|
)
|
||||||
|
elseif(UNIX)
|
||||||
|
target_sources(lucore PRIVATE
|
||||||
|
src/OsLibrary.linux.cpp
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
target_compile_features(lucore PUBLIC cxx_std_20)
|
target_compile_features(lucore PUBLIC cxx_std_20)
|
||||||
target_include_directories(lucore PUBLIC ${PROJECT_SOURCE_DIR}/include)
|
target_include_directories(lucore PUBLIC ${PROJECT_SOURCE_DIR}/include)
|
||||||
|
|
||||||
|
|
|
@ -5,18 +5,15 @@
|
||||||
#include <format>
|
#include <format>
|
||||||
|
|
||||||
namespace lucore {
|
namespace lucore {
|
||||||
|
[[noreturn]] void ExitMsg(const char* message);
|
||||||
// TODO: wrapper which uses source_location (we don't need no macros anymore!)
|
|
||||||
[[noreturn]] void ExitMsg(const char* fileName, int fileLine, const char* message);
|
|
||||||
|
|
||||||
} // namespace lucore
|
} // namespace lucore
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
#define LUCORE_ASSERT(expr, fmt, ...) \
|
#define LUCORE_ASSERT(expr, fmt, ...) \
|
||||||
if(!(expr)) [[unlikely]] { \
|
if(!(expr)) [[unlikely]] { \
|
||||||
auto msg = std::format("Assertion \"{}\" failed with message: {}", #expr, \
|
auto msg = std::format("Assertion \"{}\" @ {}:{} failed with message: {}", #expr, \
|
||||||
std::format(fmt, ##__VA_ARGS__)); \
|
__FILE__, __LINE__, std::format(fmt, ##__VA_ARGS__)); \
|
||||||
::lucore::ExitMsg(__FILE__, __LINE__, msg.c_str()); \
|
::lucore::ExitMsg(msg.c_str()); \
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
#define LUCORE_ASSERT(expr, format, ...)
|
#define LUCORE_ASSERT(expr, format, ...)
|
||||||
|
@ -25,7 +22,7 @@ namespace lucore {
|
||||||
// CHECK() is always active, even in release builds
|
// CHECK() is always active, even in release builds
|
||||||
#define LUCORE_CHECK(expr, fmt, ...) \
|
#define LUCORE_CHECK(expr, fmt, ...) \
|
||||||
if(!(expr)) [[unlikely]] { \
|
if(!(expr)) [[unlikely]] { \
|
||||||
auto msg = std::format("Check \"{}\" failed with message: {}", #expr, \
|
auto msg = std::format("Check \"{}\" @ {}:{} failed with message: {}", #expr, __FILE__, \
|
||||||
std::format(fmt, ##__VA_ARGS__)); \
|
__LINE__, std::format(fmt, ##__VA_ARGS__)); \
|
||||||
::lucore::ExitMsg(__FILE__, __LINE__, msg.c_str()); \
|
::lucore::ExitMsg(msg.c_str()); \
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace lucore {
|
||||||
|
|
||||||
|
struct Library {
|
||||||
|
using Handle = void*;
|
||||||
|
|
||||||
|
/// Open an already loaded library
|
||||||
|
static Library* OpenExisting(std::string_view dllname);
|
||||||
|
|
||||||
|
/// Query if [dllname] is loaded in the process.
|
||||||
|
static bool Loaded(std::string_view dllname);
|
||||||
|
|
||||||
|
~Library();
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T Symbol(std::string_view symbolName) {
|
||||||
|
return reinterpret_cast<T>(SymbolImpl(symbolName.data()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void* SymbolImpl(const char* symbol);
|
||||||
|
constexpr explicit Library(Handle handle) : handle(handle) {}
|
||||||
|
Handle handle {};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace lucore
|
|
@ -1,5 +1,6 @@
|
||||||
//! Logging utilities for Lucore
|
//! Logging utilities for Lucore
|
||||||
//! Using Standard C++ <format>
|
//! Using Standard C++ <format>
|
||||||
|
#pragma once
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
@ -35,7 +36,6 @@ namespace lucore {
|
||||||
/// Get the single instance of the logger.
|
/// Get the single instance of the logger.
|
||||||
static Logger& The();
|
static Logger& The();
|
||||||
|
|
||||||
Logger() = default;
|
|
||||||
Logger(const Logger&) = delete;
|
Logger(const Logger&) = delete;
|
||||||
Logger(Logger&&) = delete;
|
Logger(Logger&&) = delete;
|
||||||
|
|
||||||
|
@ -76,20 +76,31 @@ namespace lucore {
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Logger() = default;
|
||||||
void VOut(MessageSeverity severity, std::string_view format, std::format_args args);
|
void VOut(MessageSeverity severity, std::string_view format, std::format_args args);
|
||||||
|
|
||||||
MessageSeverity logLevel { MessageSeverity::Info };
|
MessageSeverity logLevel { MessageSeverity::Info };
|
||||||
std::vector<Sink*> sinks;
|
std::vector<Sink*> sinks;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A logger sink implementation that prints to standard output.
|
template <class... Args>
|
||||||
struct StdoutSink : public Logger::Sink {
|
void LogInfo(std::string_view format, Args... args) {
|
||||||
static StdoutSink& The();
|
Logger::The().Info(format, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
virtual void OutputMessage(const Logger::MessageData& data) override;
|
template <class... Args>
|
||||||
};
|
void LogWarning(std::string_view format, Args... args) {
|
||||||
|
Logger::The().Warning(format, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
/// Attach the stdout logger sink to the logger.
|
template <class... Args>
|
||||||
void LoggerAttachStdout();
|
void LogError(std::string_view format, Args... args) {
|
||||||
|
Logger::The().Error(format, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class... Args>
|
||||||
|
void LogFatal(std::string_view format, Args... args) {
|
||||||
|
Logger::The().Fatal(format, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace lucore
|
} // namespace lucore
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <lucore/Logger.hpp>
|
||||||
|
|
||||||
|
namespace lucore {
|
||||||
|
|
||||||
|
/// A logger sink implementation that prints to standard output.
|
||||||
|
struct StdoutSink : public Logger::Sink {
|
||||||
|
static StdoutSink& The();
|
||||||
|
|
||||||
|
virtual void OutputMessage(const Logger::MessageData& data) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Attach the stdout logger sink to the global Lucore logger.
|
||||||
|
void LoggerAttachStdout();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
//namespace lucore {
|
||||||
|
using u8 = std::uint8_t;
|
||||||
|
using s8 = std::int8_t;
|
||||||
|
using u16 = std::uint16_t;
|
||||||
|
using s16 = std::int16_t;
|
||||||
|
using u32 = std::uint32_t;
|
||||||
|
using s32 = std::int32_t;
|
||||||
|
using u64 = std::uint64_t;
|
||||||
|
using s64 = std::int64_t;
|
||||||
|
using usize = std::size_t;
|
||||||
|
using ssize = std::intptr_t;
|
||||||
|
|
||||||
|
//} // namespace lucore
|
|
@ -6,8 +6,8 @@
|
||||||
|
|
||||||
namespace lucore {
|
namespace lucore {
|
||||||
|
|
||||||
[[noreturn]] void ExitMsg(const char* fileName, int fileLine, const char* message) {
|
[[noreturn]] void ExitMsg(const char* message) {
|
||||||
Logger::The().Fatal(message);
|
LogFatal("{}", message);
|
||||||
std::quick_exit(0xAF);
|
std::quick_exit(0xAF);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
#include <format>
|
||||||
|
#include <lucore/Library.hpp>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "OsLibrary.hpp"
|
||||||
|
|
||||||
|
namespace lucore {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::string FormatLibraryName(std::string_view dllName) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
return std::format("{}.dll", dllName);
|
||||||
|
#else
|
||||||
|
return std::format("lib{}.so", dllName);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Library* Library::OpenExisting(std::string_view dllname) {
|
||||||
|
auto name = FormatLibraryName(dllname);
|
||||||
|
if(!detail::OsLibraryLoaded(name.c_str()))
|
||||||
|
return nullptr;
|
||||||
|
return new Library(detail::OsOpenLibrary(name.c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Library::Loaded(std::string_view dllname) {
|
||||||
|
return detail::OsLibraryLoaded(FormatLibraryName(dllname).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
Library::~Library() {
|
||||||
|
if(handle) {
|
||||||
|
detail::OsFreeLibrary(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void* Library::SymbolImpl(const char* symbolName) {
|
||||||
|
return detail::OsLibrarySymbol(handle, symbolName);
|
||||||
|
}
|
||||||
|
} // namespace lucore
|
|
@ -32,42 +32,5 @@ namespace lucore {
|
||||||
sink->OutputMessage(data);
|
sink->OutputMessage(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
StdoutSink& StdoutSink::The() {
|
|
||||||
static StdoutSink sink;
|
|
||||||
return sink;
|
|
||||||
}
|
|
||||||
|
|
||||||
void StdoutSink::OutputMessage(const Logger::MessageData& data) {
|
|
||||||
// This is kinda iffy, but required until more standard libraries support the C++23 <print>
|
|
||||||
// header.
|
|
||||||
struct FileOutIterator {
|
|
||||||
using iterator_category = std::output_iterator_tag;
|
|
||||||
using value_type = void;
|
|
||||||
using difference_type = std::ptrdiff_t;
|
|
||||||
using pointer = void;
|
|
||||||
using reference = void;
|
|
||||||
|
|
||||||
FileOutIterator(std::FILE* file) : file(file) {}
|
|
||||||
FileOutIterator& operator*() { return *this; }
|
|
||||||
FileOutIterator& operator++() { return *this; }
|
|
||||||
FileOutIterator& operator++(int) { return *this; }
|
|
||||||
|
|
||||||
FileOutIterator& operator=(const char& val) {
|
|
||||||
fputc(val, file);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::FILE* file;
|
|
||||||
};
|
|
||||||
std::format_to(
|
|
||||||
FileOutIterator(data.severity < Logger::MessageSeverity::Error ? stdout : stderr),
|
|
||||||
"[Lucore/{}] [{}] {}\n", Logger::SeverityToString(data.severity), data.time,
|
|
||||||
std::vformat(data.format, data.args));
|
|
||||||
}
|
|
||||||
|
|
||||||
void LoggerAttachStdout() {
|
|
||||||
Logger::The().AttachSink(StdoutSink::The());
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace lucore
|
} // namespace lucore
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
//! Operating-system independent utilities for opening
|
||||||
|
//! shared libraries. This is currently a detail-only
|
||||||
|
//! Lucore API, and its stability is NOT guaranteed.
|
||||||
|
|
||||||
|
namespace lucore::detail {
|
||||||
|
/// Opaque handle type for libraries.
|
||||||
|
using OsLibraryHandle = void*;
|
||||||
|
|
||||||
|
/// Open a library.
|
||||||
|
OsLibraryHandle OsOpenLibrary(const char* filename);
|
||||||
|
|
||||||
|
/// Query if the library with the given [filename] is loaded.
|
||||||
|
bool OsLibraryLoaded(const char* filename);
|
||||||
|
|
||||||
|
/// Get a pointer to the symbol of a library.
|
||||||
|
void* OsLibrarySymbol(OsLibraryHandle dll, const char* symbolName);
|
||||||
|
|
||||||
|
/// Free the library.
|
||||||
|
void OsFreeLibrary(OsLibraryHandle handle);
|
||||||
|
|
||||||
|
} // namespace lucore::detail
|
|
@ -0,0 +1,24 @@
|
||||||
|
#include "OsLibrary.hpp"
|
||||||
|
|
||||||
|
#include <dlfcn.h>
|
||||||
|
|
||||||
|
namespace lucore::detail {
|
||||||
|
OsLibraryHandle OsOpenLibrary(const char* filename) {
|
||||||
|
return dlopen(filename, RTLD_LAZY);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OsLibraryLoaded(const char* filename) {
|
||||||
|
return dlopen(filename, RTLD_NOLOAD | RTLD_LAZY) != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* OsLibrarySymbol(OsLibraryHandle dll, const char* symbolName) {
|
||||||
|
return dlsym(dll, symbolName);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
dlclose(handle);
|
||||||
|
}
|
||||||
|
} // namespace lucore::detail
|
|
@ -0,0 +1,25 @@
|
||||||
|
#include "OsLibrary.hpp"
|
||||||
|
|
||||||
|
#define _WIN32_LEAN_AND_MEAN
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
namespace lucore::detail {
|
||||||
|
|
||||||
|
OsLibraryHandle OsOpenLibrary(const char* filename) {
|
||||||
|
return reinterpret_cast<OsLibraryHandle>(GetModuleHandleA(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OsLibraryLoaded(const char* filename) {
|
||||||
|
return GetModuleHandleA(filename) != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* OsLibrarySymbol(OsLibraryHandle dll, const char* symbolName) {
|
||||||
|
return GetProcAddressA(reinterpret_cast<HMODULE*>(dll), symbolName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OsFreeLibrary(OsLibraryHandle handle) {
|
||||||
|
// GetModuleHandle*() does not increment the reference count;
|
||||||
|
// therefore, we have nothing to do here on Windows.
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace lucore::detail
|
|
@ -0,0 +1,41 @@
|
||||||
|
#include <lucore/StdoutSink.hpp>
|
||||||
|
|
||||||
|
namespace lucore {
|
||||||
|
StdoutSink& StdoutSink::The() {
|
||||||
|
static StdoutSink sink;
|
||||||
|
return sink;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StdoutSink::OutputMessage(const Logger::MessageData& data) {
|
||||||
|
// This is kinda iffy, but required until more standard libraries support the C++23 <print>
|
||||||
|
// header.
|
||||||
|
struct FputcIterator {
|
||||||
|
using iterator_category = std::output_iterator_tag;
|
||||||
|
using value_type = void;
|
||||||
|
using difference_type = std::ptrdiff_t;
|
||||||
|
using pointer = void;
|
||||||
|
using reference = void;
|
||||||
|
|
||||||
|
FputcIterator(std::FILE* file) : file(file) {}
|
||||||
|
FputcIterator& operator*() { return *this; }
|
||||||
|
FputcIterator& operator++() { return *this; }
|
||||||
|
FputcIterator& operator++(int) { return *this; }
|
||||||
|
|
||||||
|
FputcIterator& operator=(const char& val) {
|
||||||
|
fputc(val, file);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::FILE* file;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto it = FputcIterator(data.severity < Logger::MessageSeverity::Error ? stdout : stderr);
|
||||||
|
std::format_to(it, "[Lucore/{}] [{}] {}\n", Logger::SeverityToString(data.severity),
|
||||||
|
data.time, std::vformat(data.format, data.args));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoggerAttachStdout() {
|
||||||
|
Logger::The().AttachSink(StdoutSink::The());
|
||||||
|
}
|
||||||
|
} // namespace lucore
|
|
@ -1,28 +1,34 @@
|
||||||
#include <riscv/Types.hpp>
|
|
||||||
#include <lucore/OptionalRef.hpp>
|
#include <lucore/OptionalRef.hpp>
|
||||||
|
#include <riscv/Types.hpp>
|
||||||
#include <vector>
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace riscv {
|
namespace riscv {
|
||||||
|
|
||||||
struct CPU;
|
struct CPU;
|
||||||
|
|
||||||
/// An address bus.
|
/// An address/memory bus. No virtual address translation is implemented;
|
||||||
|
/// all addresses/devices are placed in physical addresses.
|
||||||
struct Bus {
|
struct Bus {
|
||||||
|
/// Interface all memory bus devices use.
|
||||||
struct Device {
|
struct Device {
|
||||||
|
Device() = default;
|
||||||
|
|
||||||
|
// Devices have no need to be copied or moved.
|
||||||
|
Device(const Device&) = delete;
|
||||||
|
Device(Device&&) = delete;
|
||||||
|
|
||||||
virtual ~Device() = default;
|
virtual ~Device() = default;
|
||||||
|
|
||||||
// How many bytes does this device occupy of address space?
|
/// How many bytes does this device occupy of address space? This should
|
||||||
|
/// effectively be a constant, and should probably not change during CPU execution.
|
||||||
virtual AddressT Size() const = 0;
|
virtual AddressT Size() const = 0;
|
||||||
|
|
||||||
// Used to allow bus devices to know when they are attached to a memory bus,
|
/// Used to allow bus devices to know when they are attached to a memory bus,
|
||||||
// and ultimately, an instance of a System
|
/// and ultimately, an instance of a System
|
||||||
virtual void Attached(Bus* memoryBus, AddressT baseAddress) = 0;
|
virtual void Attached(Bus* memoryBus, AddressT baseAddress) = 0;
|
||||||
|
|
||||||
/// Is this device clocked?
|
/// Does this device require a clock "signal"?
|
||||||
virtual bool Clocked() const { return false; }
|
virtual bool Clocked() const { return false; }
|
||||||
|
|
||||||
/// This function is called to give clocked devices
|
/// This function is called to give clocked devices
|
||||||
|
@ -32,8 +38,7 @@ namespace riscv {
|
||||||
// TODO(feat): default implementations of Peek* and Poke* should exist
|
// TODO(feat): default implementations of Peek* and Poke* should exist
|
||||||
// and trap the CPU (similarly to what happens if a unmapped bus read occurs).
|
// and trap the CPU (similarly to what happens if a unmapped bus read occurs).
|
||||||
|
|
||||||
|
/// Peek() -> reads a value from this device.
|
||||||
// Peek() -> reads a value from this device.
|
|
||||||
virtual u8 PeekByte(AddressT offset) = 0;
|
virtual u8 PeekByte(AddressT offset) = 0;
|
||||||
virtual u16 PeekShort(AddressT offset) = 0;
|
virtual u16 PeekShort(AddressT offset) = 0;
|
||||||
virtual u32 PeekWord(AddressT offset) = 0;
|
virtual u32 PeekWord(AddressT offset) = 0;
|
||||||
|
@ -42,7 +47,6 @@ namespace riscv {
|
||||||
virtual void PokeByte(AddressT offset, u8 value) = 0;
|
virtual void PokeByte(AddressT offset, u8 value) = 0;
|
||||||
virtual void PokeShort(AddressT offset, u16 value) = 0;
|
virtual void PokeShort(AddressT offset, u16 value) = 0;
|
||||||
virtual void PokeWord(AddressT offset, u32 value) = 0;
|
virtual void PokeWord(AddressT offset, u32 value) = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Bus(CPU* cpu);
|
Bus(CPU* cpu);
|
||||||
|
@ -60,7 +64,7 @@ namespace riscv {
|
||||||
/// - The provided base address overlaps a already attached device in some way
|
/// - The provided base address overlaps a already attached device in some way
|
||||||
bool AttachDevice(AddressT baseAddress, Device* device);
|
bool AttachDevice(AddressT baseAddress, Device* device);
|
||||||
|
|
||||||
/// Clock all clocked devices.
|
/// Clock all clocked devices mapped onto the bus..
|
||||||
void Clock();
|
void Clock();
|
||||||
|
|
||||||
u8 PeekByte(AddressT address);
|
u8 PeekByte(AddressT address);
|
||||||
|
@ -70,8 +74,8 @@ namespace riscv {
|
||||||
void PokeByte(AddressT address, u8 value);
|
void PokeByte(AddressT address, u8 value);
|
||||||
void PokeShort(AddressT address, u16 value);
|
void PokeShort(AddressT address, u16 value);
|
||||||
void PokeWord(AddressT address, u32 value);
|
void PokeWord(AddressT address, u32 value);
|
||||||
private:
|
|
||||||
|
|
||||||
|
private:
|
||||||
lucore::OptionalRef<Device> FindDeviceForAddress(AddressT address) const;
|
lucore::OptionalRef<Device> FindDeviceForAddress(AddressT address) const;
|
||||||
|
|
||||||
CPU* attachedCpu {};
|
CPU* attachedCpu {};
|
||||||
|
@ -80,6 +84,4 @@ namespace riscv {
|
||||||
std::unordered_map<AddressT, Device*> mapped_devices;
|
std::unordered_map<AddressT, Device*> mapped_devices;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
} // namespace riscv
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
|
|
||||||
namespace riscv {
|
namespace riscv {
|
||||||
|
|
||||||
/** The CPU core. */
|
/// The CPU core.
|
||||||
struct CPU {
|
struct CPU {
|
||||||
|
/// CPU core state.
|
||||||
struct State {
|
struct State {
|
||||||
u32 gpr[32];
|
u32 gpr[32];
|
||||||
u32 pc;
|
u32 pc;
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
|
|
||||||
#include <riscv/Bus.hpp>
|
#include <riscv/Bus.hpp>
|
||||||
|
|
||||||
#include "riscv/Types.hpp"
|
|
||||||
|
|
||||||
namespace riscv::devices {
|
namespace riscv::devices {
|
||||||
|
|
||||||
|
/// A block of RAM which can be used by the CPU.
|
||||||
struct RamDevice : public Bus::Device {
|
struct RamDevice : public Bus::Device {
|
||||||
RamDevice(AddressT size);
|
RamDevice(AddressT size);
|
||||||
virtual ~RamDevice();
|
virtual ~RamDevice();
|
||||||
|
|
||||||
AddressT Size() const override;
|
|
||||||
|
|
||||||
// Implementation of Device interface
|
// Implementation of Device interface
|
||||||
|
|
||||||
|
AddressT Size() const override;
|
||||||
|
|
||||||
void Attached(Bus* bus, AddressT base) override;
|
void Attached(Bus* bus, AddressT base) override;
|
||||||
|
|
||||||
u8 PeekByte(AddressT address) override;
|
u8 PeekByte(AddressT address) override;
|
||||||
|
@ -24,7 +22,7 @@ namespace riscv::devices {
|
||||||
void PokeWord(AddressT address, u32 value) override;
|
void PokeWord(AddressT address, u32 value) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// helper used for implementing stuff
|
/// helper used for implementing Peek/Poke API
|
||||||
template <class T>
|
template <class T>
|
||||||
constexpr usize AddressToIndex(AddressT address) {
|
constexpr usize AddressToIndex(AddressT address) {
|
||||||
return ((address - baseAddress) % memorySize) / sizeof(T);
|
return ((address - baseAddress) % memorySize) / sizeof(T);
|
||||||
|
|
|
@ -1,26 +1,11 @@
|
||||||
//! Common types
|
//! Common types
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cassert>
|
#include <lucore/Types.hpp>
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
namespace riscv {
|
namespace riscv {
|
||||||
|
|
||||||
using u8 = std::uint8_t;
|
|
||||||
using s8 = std::int8_t;
|
|
||||||
using u16 = std::uint16_t;
|
|
||||||
using s16 = std::int16_t;
|
|
||||||
using u32 = std::uint32_t;
|
|
||||||
using s32 = std::int32_t;
|
|
||||||
using u64 = std::uint64_t;
|
|
||||||
using s64 = std::int64_t;
|
|
||||||
using usize = std::size_t;
|
|
||||||
using ssize = std::intptr_t;
|
|
||||||
|
|
||||||
/// A type that can repressent address space or address space offsets.
|
/// A type that can repressent address space or address space offsets.
|
||||||
using AddressT = u32;
|
using AddressT = u32;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace riscv
|
} // namespace riscv
|
||||||
|
|
Loading…
Reference in New Issue