From 30bf850fd8648791344a3489d61ef8af221a330d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alf-Andr=C3=A9=20Walla?= Date: Sun, 22 Dec 2024 21:18:14 +0100 Subject: [PATCH] Use Zig musl/libc++ toolchain to build programs --- .gitignore | 1 + engine/build.sh | 3 +- engine/scripts/CMakeLists.txt | 16 +- engine/scripts/src/events.cpp | 2 +- engine/scripts/src/events.hpp | 2 +- engine/scripts/src/gameplay.cpp | 20 +- engine/scripts/src/gui.cpp | 1 + engine/scripts/src/interface.hpp | 2 +- engine/scripts/src/level1.cpp | 6 +- engine/scripts/src/level1_threads.cpp | 4 +- engine/scripts/src/level2.cpp | 4 +- engine/src/main.cpp | 2 +- engine/src/util/function.hpp | 259 ++++++++++++++ ext/libriscv | 2 +- programs/dyncalls/generate.py | 6 +- programs/micro/api/api.h | 2 +- programs/micro/api/api_impl.h | 73 ++-- .../micro/{libc/include => api}/crc32.hpp | 0 programs/micro/api/event_loop.hpp | 83 +++++ programs/micro/api/microthread.hpp | 317 ++++++++++++++++++ programs/micro/api/ringbuffer.hpp | 64 ++++ programs/micro/api/shared_memory.h | 2 +- .../micro/{libc/engine.hpp => api/system.hpp} | 16 +- programs/micro/libc/CMakeLists.txt | 28 -- programs/micro/libc/assert.cpp | 71 ---- programs/micro/libc/engine.cpp | 30 -- programs/micro/libc/include/tuplecall.hpp | 31 -- programs/micro/libc/override/mutex | 8 - programs/micro/libc/override/new | 24 -- programs/micro/libc/write.cpp | 53 --- programs/micro/micro.cmake | 37 +- programs/micro/micro.cpp | 269 +++++++++++++++ programs/micro/microthread.cpp | 60 ++++ programs/micro/zig-ar.cmd | 2 + programs/micro/zig-ranlib.cmd | 2 + programs/micro/zig-toolchain.cmake | 17 + programs/zig.sh | 7 + 37 files changed, 1188 insertions(+), 338 deletions(-) create mode 100644 engine/src/util/function.hpp rename programs/micro/{libc/include => api}/crc32.hpp (100%) create mode 100644 programs/micro/api/event_loop.hpp create mode 100644 programs/micro/api/microthread.hpp create mode 100644 programs/micro/api/ringbuffer.hpp rename programs/micro/{libc/engine.hpp => api/system.hpp} (66%) delete mode 100644 programs/micro/libc/CMakeLists.txt delete mode 100644 programs/micro/libc/assert.cpp delete mode 100644 programs/micro/libc/engine.cpp delete mode 100644 programs/micro/libc/include/tuplecall.hpp delete mode 100644 programs/micro/libc/override/mutex delete mode 100644 programs/micro/libc/override/new delete mode 100644 programs/micro/libc/write.cpp create mode 100644 programs/micro/micro.cpp create mode 100644 programs/micro/microthread.cpp create mode 100644 programs/micro/zig-ar.cmd create mode 100644 programs/micro/zig-ranlib.cmd create mode 100644 programs/micro/zig-toolchain.cmake create mode 100755 programs/zig.sh diff --git a/.gitignore b/.gitignore index 7f5e3c7..da60900 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ **/build/ +**/.zig/ **/build_*/ **/riscv32-unknown-elf/ **/riscv64-unknown-elf/ diff --git a/engine/build.sh b/engine/build.sh index 0eb9327..a28c6f4 100755 --- a/engine/build.sh +++ b/engine/build.sh @@ -5,7 +5,8 @@ export ENGINE_CC="ccache $CC" # Build the script pushd ../programs -source build.sh $@ +#source build.sh $@ +source zig.sh $@ popd echo "RISC-V C-extension is: $CEXT" diff --git a/engine/scripts/CMakeLists.txt b/engine/scripts/CMakeLists.txt index a8c6398..7d7c9e3 100644 --- a/engine/scripts/CMakeLists.txt +++ b/engine/scripts/CMakeLists.txt @@ -16,26 +16,26 @@ add_shared_program(gameplay.elf 0x50000000 *[Gg]ameplay* "src/gameplay.cpp" - "src/gameplay_remote.cpp" + #"src/gameplay_remote.cpp" "src/events.cpp" ) -add_level(gui.elf 0x400000 +add_level(gui.elf "src/gui.cpp" ) -attach_program(gui.elf gameplay.elf) +#attach_program(gui.elf gameplay.elf) -add_level(level1.elf 0x400000 +add_level(level1.elf "src/level1.cpp" "src/level1_local.cpp" - "src/level1_remote.cpp" + #"src/level1_remote.cpp" "src/level1_threads.cpp" "src/events.cpp" ) -attach_program(level1.elf gameplay.elf) +#attach_program(level1.elf gameplay.elf) -add_level(level2.elf 0x400000 +add_level(level2.elf "src/level2.cpp" ) -attach_program(level2.elf gameplay.elf) +#attach_program(level2.elf gameplay.elf) diff --git a/engine/scripts/src/events.cpp b/engine/scripts/src/events.cpp index 5c67d99..e0c20f3 100644 --- a/engine/scripts/src/events.cpp +++ b/engine/scripts/src/events.cpp @@ -1,5 +1,5 @@ #include -#include +#include static std::array, 2> events; diff --git a/engine/scripts/src/events.hpp b/engine/scripts/src/events.hpp index 92743c7..1f2ef00 100644 --- a/engine/scripts/src/events.hpp +++ b/engine/scripts/src/events.hpp @@ -1,5 +1,5 @@ #include -#include +#include PUBLIC(bool add_work(const Events<>::Work*)); diff --git a/engine/scripts/src/gameplay.cpp b/engine/scripts/src/gameplay.cpp index 48c5fca..24d9e2d 100644 --- a/engine/scripts/src/gameplay.cpp +++ b/engine/scripts/src/gameplay.cpp @@ -13,17 +13,17 @@ static void empty_function() {} static void full_thread_function() { - microthread::create([](int, int, int) { /* ... */ }, 1, 2, 3); + //microthread::create([](int, int, int) { /* ... */ }, 1, 2, 3); } static void oneshot_thread_function() { - microthread::oneshot([](int, int, int) { /* ... */ }, 1, 2, 3); + //microthread::oneshot([](int, int, int) { /* ... */ }, 1, 2, 3); } static void direct_thread_function() { - microthread::direct([] { /* ... */ }); + //microthread::direct([] { /* ... */ }); } static void opaque_dyncall_handler() @@ -49,24 +49,10 @@ PUBLIC(void public_donothing()) /* nothing */ } -inline void* sys_memset(void* vdest, const int ch, std::size_t size) -{ - register char* a0 asm("a0") = (char*)vdest; - register int a1 asm("a1") = ch; - register size_t a2 asm("a2") = size; - register long syscall_id asm("a7") = SYSCALL_MEMSET; - - asm volatile ("ecall" - : "=m"(*(char(*)[size]) a0) - : "r"(a0), "r"(a1), "r"(a2), "r"(syscall_id)); - return vdest; -} - static void bench_alloc_free() { auto x = std::make_unique_for_overwrite(1024); __asm__("" :: "m"(x[0]) : "memory"); - //sys_memset(x.get(), 0, 1024); } PUBLIC(void benchmarks()) diff --git a/engine/scripts/src/gui.cpp b/engine/scripts/src/gui.cpp index 6f03777..d425c3f 100644 --- a/engine/scripts/src/gui.cpp +++ b/engine/scripts/src/gui.cpp @@ -14,6 +14,7 @@ measure_exception_throw() { print("Caught exception: ", e.what(), "\n"); } + //print("Exception handling done\n"); const auto cycle1 = rdcycle(); const auto time1 = rdtime(); return {cycle1-cycle0, time1-time0}; diff --git a/engine/scripts/src/interface.hpp b/engine/scripts/src/interface.hpp index c333b06..cf3086d 100644 --- a/engine/scripts/src/interface.hpp +++ b/engine/scripts/src/interface.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include #include #include #include diff --git a/engine/scripts/src/level1.cpp b/engine/scripts/src/level1.cpp index da74b04..ecf23a3 100644 --- a/engine/scripts/src/level1.cpp +++ b/engine/scripts/src/level1.cpp @@ -23,7 +23,7 @@ PUBLIC(void start()) print("** Threads **\n"); /* Test-run some micro-threads. */ - do_threads_stuff(); + //do_threads_stuff(); } static std::unique_ptr test_vector; @@ -75,6 +75,6 @@ void do_benchmarks() }); - if (Game::setting("remote").value_or(false)) - do_remote_stuff(); + //if (Game::setting("remote").value_or(false)) + // do_remote_stuff(); } diff --git a/engine/scripts/src/level1_threads.cpp b/engine/scripts/src/level1_threads.cpp index b9f58c7..85cc493 100644 --- a/engine/scripts/src/level1_threads.cpp +++ b/engine/scripts/src/level1_threads.cpp @@ -21,8 +21,9 @@ void do_threads_stuff() b = 4; c = 6; microthread::yield(); + print("Done, back in the main thread!\n"); - auto thread = microthread::create( + /*auto thread = microthread::create( [](int a, int b, int c) { print( @@ -40,6 +41,7 @@ void do_threads_stuff() print("Joining the thread any time now...\n"); auto retval = microthread::join(thread); print("Full thread exited, return value: ", retval, "\n"); + */ /* GDB can be automatically opened at this point. */ Game::breakpoint(); diff --git a/engine/scripts/src/level2.cpp b/engine/scripts/src/level2.cpp index 82786f5..654eac9 100644 --- a/engine/scripts/src/level2.cpp +++ b/engine/scripts/src/level2.cpp @@ -5,8 +5,8 @@ using namespace api; PUBLIC(void start()) { print("Hello from Level 2!\n"); - int value = gameplay_allowed_function(123); - print("Back in Level2! Got result value = ", value, "\n"); + //int value = gameplay_allowed_function(123); + //print("Back in Level2! Got result value = ", value, "\n"); } int main() {} diff --git a/engine/src/main.cpp b/engine/src/main.cpp index c0df41b..5046009 100644 --- a/engine/src/main.cpp +++ b/engine/src/main.cpp @@ -59,7 +59,7 @@ int main() /* level2 can make remote calls to the gameplay program. */ level2.setup_strict_remote_calls_to(gameplay); /* Allow calling *only* this function remotely, when in strict mode */ - gameplay.add_allowed_remote_function("_Z25gameplay_allowed_functioni"); + //gameplay.add_allowed_remote_function("_Z25gameplay_allowed_functioni"); if (!level2.call("start")) { strf::to(stdout)("Level2 failed to start!\n"); diff --git a/engine/src/util/function.hpp b/engine/src/util/function.hpp new file mode 100644 index 0000000..96a3d8c --- /dev/null +++ b/engine/src/util/function.hpp @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2017 Ambroz Bizjak + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once +#include + +#include +#include +#include +#include + +/** + * @ingroup misc + * @defgroup function Function Wrapper + * @brief Lightweight polymorphic function wrapper and related utilities + * + * The @ref Function "Function" class is a general-purpose lightweight + * polymorphic function wrapper. It has intentionally very limited storage capabilities + * in order to ensure minimal overhead and no possibility of exceptions when copying. + * In this documentation, "function object" refers to an instance of @ref + * Function "Function". + * + * Function objects are most often used for asynchronous callbacks. However this is not + * the only possible mechanism for that purpose, virtual functions being the other major + * one as used in the stack. The choice of which to use is often not simple, but a + * general rule is that if more than one callback function is needed then virtual + * functions in the same class should be considered. + * + * In practical use, valid function objects are created in the following ways: + * - From a combination of non-static member function and pointer to object, using the + * macros @ref AIPSTACK_BIND_MEMBER and @ref AIPSTACK_BIND_MEMBER_TN (which one depends + * on the context). This approach should be preferred when function objects are used + * for asynchronous callbacks. + * - From a lambda object using the Function(Callable) constructor. The lambda object + * must meet the requirements specified for this constructor which means that one + * is very limited in what the lambda can capture, though capturing a single pointer + * or reference specifically should work. + * + * @{ + */ + +/** + * Maximum size of callable objects that @ref Function "Function" can + * store when using the Function(Callable) constructor. + */ +inline constexpr std::size_t FunctionStorageSize = sizeof(void *) * 3; + +template +class Function; + +/** + * A general-purpose lightweight polymorphic function wrapper. + * + * Consult the @ref function module description for an introduction. + * + * A function object is always either empty or stores a callable object. The @ref + * operator bool() "bool operator" can be used to determine which is the case. + * + * @tparam Ret Return type (may be void). + * @tparam Args Argument types. + */ +template +class Function +{ + struct Storage { + alignas(alignof(void*)) char data[FunctionStorageSize]; + }; + + using FunctionPointerType = Ret (*) (Storage, Args...); + +public: + /** + * Default constructor, constructs an empty function object. + */ + inline Function () noexcept : + m_func_ptr(nullptr) + {} + + /** + * Constructor from nullptr, constructs an empty function object. + */ + inline Function (std::nullptr_t) noexcept : + Function() + {} + + /** + * Constructor from a callable, constructs a function object storing the + * callable. + * + * The `Callable` type must satisfy the following requirements: + * - Its size must be less than or equal to @ref FunctionStorageSize. + * - It must be trivially copy-constructible. + * - It must be trivially destructible. + * - It must be possible to "call" a const object of that type with arguments + * of types `Args` and convert the return value to type `Ret` (see below for + * details). + * + * When the function object storing this callable is invoked using @ref + * operator()() "operator()", the callable is invoked using an expression like + * `callable(std::forward(args)...)`, where `callable` is a const + * reference to a copy of the `Callable` object and `args` are the arguments + * declared as `Args ...args`. The result of this expression is returned in + * the context of a function returning type `Ret`, so it must be implicitly + * convertible to `Ret`. + * + * @tparam Callable Type of callable object to be stored (see description for + * requirements). + * @param callable Callable object to be stored in the function object. + */ + template + Function (Callable callable) noexcept + { + static_assert(sizeof(Callable) <= FunctionStorageSize, + "Callable too large (greater than FunctionStorageSize)"); + static_assert(std::is_trivially_copy_constructible_v, + "Callable not trivially copy constructible"); + static_assert(std::is_trivially_destructible_v, + "Callable not trivially destructible"); + + m_func_ptr = &trampoline; + + new(reinterpret_cast(m_storage.data)) Callable(callable); + } + + /** + * Determine whether the function object stores a callable. + * + */ + bool operator==(std::nullptr_t) const noexcept { return m_func_ptr == nullptr; } + bool operator!=(std::nullptr_t) const noexcept { return m_func_ptr != nullptr; } + + /** + * Invoke the stored callable object. + * + * @note The behavior is undefined if the function object is empty. + * + * @param args Arguments forwarded to the stored callable object. + * @return Value returned by the invocation of the callable object. + */ + inline Ret operator() (Args ...args) const + { + return (*m_func_ptr)(m_storage, std::forward(args)...); + } + + inline FunctionPointerType get() const noexcept + { + return m_func_ptr; + } + +private: + template + static Ret trampoline (Storage storage, Args ...args) + { + Callable const *c = reinterpret_cast(storage.data); + return (*c)(std::forward(args)...); + } + +private: + FunctionPointerType m_func_ptr; + Storage m_storage; +}; + +/** + * Wrap a const reference using `std::reference_wrapper`. + * + * This is intended to be used together with the Function(Callable) constructor to + * bypass the restrictions regarding object size and trivial + * copy-construction/destruction. + * + * @warning When this is used to construct a function object, the resulting function + * object references the original callable as passed to this function and must not + * be invoked after the callable has been destructed. + * + * @tparam Callable Type of object to which a reference is to be wrapped. + * @param callable Reference to object to be wrapped. + * @return Wrapped reference: `std::reference_wrapper(callable)`. + */ +template +inline std::reference_wrapper RefFunc (Callable const &callable) noexcept +{ + return std::reference_wrapper(callable); +} + +namespace BindPrivate { + template + struct BindImpl { + template + class Callable { + public: + inline constexpr Callable (Container *container) : + m_container(container) + {} + + inline Ret operator() (Args ...args) const + { + return (m_container->*MemberFunc)(std::forward(args)...); + } + + inline Function toFunction() const + { + return Function(*this); + } + + private: + Container *m_container; + }; + }; + + template + struct BindImplConst { + template + class Callable { + public: + inline constexpr Callable (Container const *container) : + m_container(container) + {} + + inline Ret operator() (Args ...args) const + { + return (m_container->*MemberFunc)(std::forward(args)...); + } + + inline Function toFunction() const + { + return Function(*this); + } + + private: + Container const *m_container; + }; + }; + + template + BindImpl DeduceImpl (Ret (Container::*)(Args...)); + + template + BindImplConst DeduceImpl (Ret (Container::*)(Args...) const); +} diff --git a/ext/libriscv b/ext/libriscv index 444e9ab..e9c1f4f 160000 --- a/ext/libriscv +++ b/ext/libriscv @@ -1 +1 @@ -Subproject commit 444e9ab1bd2469b7a122d1af9a33ef63da14db76 +Subproject commit e9c1f4fb8469df4c00d2f33d62dd25b4d0683666 diff --git a/programs/dyncalls/generate.py b/programs/dyncalls/generate.py index 6698fea..040dfca 100644 --- a/programs/dyncalls/generate.py +++ b/programs/dyncalls/generate.py @@ -122,7 +122,7 @@ def emit_inline_assembly(header, asmdef, index, fargs): asm_in += [] - header += "static inline __attribute__((always_inline, optimize(\"O2\"))) " + retval + " i" + asmdef + " (" + ','.join(fargs) + ') {\n' + header += "static inline __attribute__((always_inline)) " + retval + " i" + asmdef + " (" + ','.join(fargs) + ') {\n' header += asm_regs header += '__asm__ volatile(\".insn i 0b1011011, 0, x0, x0, ' + str(index) + "\"" \ + " : " + ",".join(asm_out) + " : " + ",".join(asm_in) + " : " + ",".join(asm_clob) + ");\n" @@ -221,11 +221,11 @@ def emit_inline_assembly(header, asmdef, index, fargs): # and at run-time this value is lazily resolved source += '__asm__("\\n\\\n' source += '.global ' + asmname + '\\n\\\n' - source += '.func ' + asmname + '\\n\\\n' + #source += '.func ' + asmname + '\\n\\\n' source += asmname + ':\\n\\\n' source += ' .insn i 0b1011011, 0, x0, x0, ' + str(dyncallindex) + '\\n\\\n' source += ' ret\\n\\\n' - source += '.endfunc\\n\\\n' + #source += '.endfunc\\n\\\n' source += '.pushsection .rodata\\n\\\n' source += asmname + '_str:\\n\\\n' source += '.asciz \\\"' + key + '\\\"\\n\\\n' diff --git a/programs/micro/api/api.h b/programs/micro/api/api.h index 590ccdb..e0a2cdb 100644 --- a/programs/micro/api/api.h +++ b/programs/micro/api/api.h @@ -5,7 +5,7 @@ #pragma once #include "api_structs.h" #include -#include +#include #include #include diff --git a/programs/micro/api/api_impl.h b/programs/micro/api/api_impl.h index 3143779..fb8e735 100644 --- a/programs/micro/api/api_impl.h +++ b/programs/micro/api/api_impl.h @@ -33,10 +33,7 @@ inline void expect_check( { if (UNLIKELY(!expr())) { - asm("" ::: "memory"); // prevent dead-store optimization - syscall( - ECALL_ASSERT_FAIL, (long)strexpr, (long)file, (long)line, - (long)func); + sys_assert_fail(strexpr, file, line, func); __builtin_unreachable(); } } @@ -62,20 +59,15 @@ template inline void print(Args&&... args) asm volatile("ecall" : "=r"(a0_out) - : "r"(a0), "m"(*(const char(*)[size])a0), "r"(a1), + : "r"(a0), "m"(*(const char(*)[sizeof(buffer)])a0), "r"(a1), "r"(syscall_id)); } -template inline long measure(const char* testname, T testfunc) -{ - return syscall( - ECALL_MEASURE, (long)testname, - (long)static_cast(testfunc)); -} +#define measure(testname, testfunc) sys_measure(testname, (void (*)())testfunc); inline uint32_t Game::current_machine() { - return syscall1(ECALL_MACHINE_HASH); + return sys_machine_hash(); } #define RUNNING_ON(mach) (api::current_machine() == crc32(mach)) @@ -84,7 +76,7 @@ inline uint32_t Game::current_machine() inline void Game::exit() { - (void)syscall1(ECALL_GAME_EXIT); + (void)sys_game_exit(); } inline std::optional Game::setting(std::string_view setting) @@ -97,7 +89,7 @@ inline std::optional Game::setting(std::string_view setting) asm("ecall" : "=r"(has_value), "=r"(result) - : "m"(*(const char(*)[name_len])name_ptr), + : "m"(*(const char(*)[16384])name_ptr), "r"(name_ptr), "r"(name_len), "r"(sysno)); if (has_value) return int64_t(result); @@ -188,39 +180,74 @@ inline long Timer::sleep(float seconds) inline float sin(float x) { - return fsyscallf(ECALL_SINF, x); + register float a0 asm("fa0") = x; + register long a7 asm("a7") = ECALL_SINF; + + asm volatile("ecall" + : "+f"(a0) : "r"(a7)); + return a0; } inline float cos(float x) { - return fsyscallf(ECALL_SINF, x + PI / 2); + return sin(x + PI / 2); } inline float rand(float a, float b) { - return fsyscallf(ECALL_RANDF, a, b); + register float a0 asm("fa0") = a; + register float a1 asm("fa1") = b; + register long a7 asm("a7") = ECALL_RANDF; + + asm volatile("ecall" + : "+f"(a0) : "f"(a1), "r"(a7)); + return a0; } inline float smoothstep(float a, float b, float x) { - return fsyscallf(ECALL_SMOOTHSTEP, a, b, x); + register float a0 asm("fa0") = a; + register float a1 asm("fa1") = b; + register float a2 asm("fa2") = x; + register long a7 asm("a7") = ECALL_SMOOTHSTEP; + + asm volatile("ecall" + : "+f"(a0) : "f"(a1), "f"(a2), "r"(a7)); + return a0; } inline float length(float dx, float dy) { - return fsyscallf(ECALL_VEC_LENGTH, dx, dy); + register float a0 asm("fa0") = dx; + register float a1 asm("fa1") = dy; + register long a7 asm("a7") = ECALL_VEC_LENGTH; + + asm volatile("ecall" + : "+f"(a0), "+f"(a1) : "r"(a7)); + return a0; } inline vec2 rotate_around(float dx, float dy, float angle) { - const auto [x, y] = fsyscallff(ECALL_VEC_LENGTH, dx, dy, angle); - return {x, y}; + register float a0 asm("fa0") = dx; + register float a1 asm("fa1") = dy; + register float a2 asm("fa2") = angle; + register long a7 asm("a7") = ECALL_VEC_ROTATE; + + asm volatile("ecall" + : "+f"(a0), "+f"(a1) : "f"(a2), "r"(a7)); + return {a0, a1}; } inline vec2 normalize(float dx, float dy) { - const auto [x, y] = fsyscallff(ECALL_VEC_NORMALIZE, dx, dy); - return {x, y}; + register float a0 asm("fa0") = dx; + register float a1 asm("fa1") = dy; + register long a7 asm("a7") = ECALL_VEC_NORMALIZE; + + asm volatile("ecall" + : "+f"(a0), "+f"(a1) : "r"(a7)); + return {a0, a1}; } inline float vec2::length() const diff --git a/programs/micro/libc/include/crc32.hpp b/programs/micro/api/crc32.hpp similarity index 100% rename from programs/micro/libc/include/crc32.hpp rename to programs/micro/api/crc32.hpp diff --git a/programs/micro/api/event_loop.hpp b/programs/micro/api/event_loop.hpp new file mode 100644 index 0000000..601cf71 --- /dev/null +++ b/programs/micro/api/event_loop.hpp @@ -0,0 +1,83 @@ +#include "function.hpp" +#include "ringbuffer.hpp" + +template +struct Events { + using Work = Function; + + FixedRingBuffer ring; + bool in_use = false; + + void consume_work(); + bool add(const Work&); +}; + +template +inline void Events::consume_work() +{ + this->in_use = true; + while (const auto* wrk = ring.read()) { + (*wrk)(); + } + this->in_use = false; +} + +template +inline bool Events::add(const Work& work) { + if (in_use == false) { + return ring.write(work); + } + return false; +} + +/** + * SharedEvents is an events structure designed to be + * shared between the host and the script. +**/ +template +struct SharedEvents { + using Callback = void(*)(Argument); + struct Work { + Callback callback; + Argument argument; + }; + + FixedRingBuffer ring; + bool in_use = false; + + bool has_work() const noexcept { return !ring.empty(); } + void consume_work(); + bool add(Callback cb, Argument arg); + bool host_add(uintptr_t cb, uintptr_t arg); +}; + +template +inline void SharedEvents::consume_work() +{ + this->in_use = true; + try { + while (const auto* wrk = ring.read()) { + wrk->callback(wrk->argument); + } + this->in_use = false; + } catch (...) { + this->in_use = false; + throw; + } +} + +template +inline bool SharedEvents::add(Callback cb, Argument arg) { + if (in_use == false) { + return ring.write({cb, arg}); + } + return false; +} + +template +inline bool SharedEvents::host_add(uintptr_t cb, uintptr_t arg) { + if (in_use == false) { + return ring.write({(Callback&)cb, (Argument)arg}); + } + return false; +} diff --git a/programs/micro/api/microthread.hpp b/programs/micro/api/microthread.hpp new file mode 100644 index 0000000..ce37bdd --- /dev/null +++ b/programs/micro/api/microthread.hpp @@ -0,0 +1,317 @@ +#pragma once +#include +#include +#include +#define THREAD_SYSCALLS_BASE 590 + +/*** + * Example usage: + * + * auto thread = microthread::create( + * [] (int a, int b, int c) -> long { + * printf("Hello from a microthread!\n" + * "a = %d, b = %d, c = %d\n", + * a, b, c); + * return a + b + c; + * }, 111, 222, 333); + * + * long retval = microthread::join(thread); + * printf("microthread exit status: %ld\n", retval); + * + * Note: microthreads require the native threads system calls. + * microthreads do not support thread-local storage. + * The thread function may also return void, in which case the + * return value becomes zero (0). +***/ + +namespace microthread +{ +struct Thread; +extern "C" long sys_microthread_exit(long); +extern "C" long sys_microthread_yield(); +extern "C" long sys_microthread_yield_to(int); +extern "C" long sys_microthread_block(int); +extern "C" long sys_microthread_unblock(int); +extern "C" long sys_microthread_wakeup_one_blocked(int); +extern "C" long sys_microthread_create(void* sp, void(*)(Thread*), void* tls, long flags, void* stack_base, long stack_size); +extern "C" long sys_microthread_direct(void(*)(), void(*)()); + +/* Create a new thread using the given function @func, + and pass all further arguments to the function as is. + Returns the new thread. The new thread starts immediately. */ +template +auto create(const T& func, Args&&... args); + +/* Create a new self-governing thread that deletes itself on completion. + Calling exit() in a sovereign thread is undefined behavior. + Returns thread id on success. */ +template +int oneshot(const T& func, Args&&... args); + +/* Create a new self-governing thread that directly starts on the + threads start function, with a special return address that + self-deletes and exits the thread safely. Arguments cannot be passed, + but a limited amount of capture storage exists. */ +using direct_funcptr = void(*)(); +inline long direct(direct_funcptr); + +/* Waits for a thread to finish and then returns the exit status + of the thread. The thread is then deleted, freeing memory. */ +long join(Thread*); + +/* Exit the current thread with the given exit status. Never returns. */ +void exit(long status); + +/* Yield until condition is true */ +template +void yield_until(Functor&& condition); +/* Return back to another suspended thread. Returns 0 on success. */ +long yield(); +long yield_to(int tid); /* Return to a specific suspended thread. */ +long yield_to(Thread*); + +/* Block a thread with a specific reason. */ +long block(int reason = 0); +template +void block(Functor&& condition, int reason = 0); +long unblock(int tid); +/* Wake thread with @reason that was blocked, returns -1 if nothing happened. */ +long wakeup_one_blocked(int reason); + +Thread* self(); /* Returns the current thread */ +int gettid(); /* Returns the current thread id */ + + +/** implementation details **/ + +struct Thread +{ + static const size_t STACK_SIZE = 256*1024; + + Thread(std::function start) + : startfunc{std::move(start)} {} + + long resume() { return yield_to(this); } + long suspend() { return yield(); } + void exit(long status); + + bool has_exited() const; + + ~Thread() noexcept {} + + int tid = 0; + union { + long return_value; + std::function startfunc; + }; +}; +struct ThreadDeleter { + void operator() (Thread* thread) { + join(thread); + } +}; +static_assert(Thread::STACK_SIZE > sizeof(Thread) + 16384); +using Thread_ptr = std::unique_ptr; + +inline bool Thread::has_exited() const { + return this->tid == 0; +} + +inline Thread* self() { +#ifdef __GNUG__ + register Thread* tp asm("tp"); + asm("" : "=r"(tp)); +#else + Thread* tp; + asm("mv %0, tp" : "=r"(tp)); +#endif + return tp; +} + +inline int gettid() { + return self()->tid; +} + +template +inline auto create(const T& func, Args&&... args) +{ + static_assert( std::is_invocable_v ); + + char* stack_bot = (char*) malloc(Thread::STACK_SIZE); + if (stack_bot == nullptr) return Thread_ptr{}; + constexpr size_t ArgSize = sizeof(std::tuple {std::move(args)...}); + char* stack_top = stack_bot + Thread::STACK_SIZE - sizeof(Thread) - ArgSize; + // store arguments on stack + auto* tuple = new (stack_top) std::tuple{std::move(args)...}; + + // store the thread after arguments (at the very top) + char* thread_addr = stack_top + ArgSize; + Thread* thread = new (thread_addr) Thread( + [func, tuple] () -> void + { + if constexpr (std::is_same_v) + { + std::apply(func, std::move(*tuple)); + self()->exit(0); + } else { + self()->exit( std::apply(func, std::move(*tuple)) ); + } + }); + + extern void trampoline(struct Thread*); + /* stack, func, tls, flags = CHILD_CLEARTID, stack_base, stack_size */ + void* sp = (void*) stack_top; + void* tls = (void*) thread; + (void)sys_microthread_create(sp, &trampoline, tls, 0x200000, + stack_bot, Thread::STACK_SIZE); + //(void) syscall(THREAD_SYSCALLS_BASE+0, sp, (long) &trampoline, tls, 0x200000, + // (long)stack_bot, Thread::STACK_SIZE); + // parent path (reordering doesn't matter) + return Thread_ptr(thread); +} +template +inline int oneshot(const T& func, Args&&... args) +{ + static_assert( std::is_invocable_v ); + static_assert(std::is_same_v, + "Free threads have no return value!"); + char* stack_bot = (char*) malloc(Thread::STACK_SIZE); + if (stack_bot == nullptr) return -12; /* ENOMEM */ + + constexpr size_t ArgSize = sizeof(std::tuple {std::move(args)...}); + char* stack_top = stack_bot + Thread::STACK_SIZE - sizeof(Thread) - ArgSize; + // store arguments on stack + auto* tuple = new (stack_top) std::tuple{std::move(args)...}; + + // store the thread after arguments (at the very top) + char* thread_addr = stack_top + ArgSize; + Thread* thread = new (thread_addr) Thread( + [func, tuple] { + std::apply(func, std::move(*tuple)); + extern void oneshot_exit(); + oneshot_exit(); + }); + extern void trampoline(struct Thread*); + void* sp = (void*) stack_top; + void* tls = (void*) thread; + return sys_microthread_create(sp, &trampoline, tls, 0, stack_bot, Thread::STACK_SIZE); + //return syscall(THREAD_SYSCALLS_BASE+0, sp, (long) &trampoline, tls, 0, + // (long)stack_bot, Thread::STACK_SIZE); +} + +extern "C" long threadcall_create(direct_funcptr, void(*)()); +extern "C" void threadcall_destructor(); + +inline long direct(direct_funcptr starter) +{ + /*register direct_funcptr a0 asm("a0") = starter; + register void(*a1)() asm("a1") = threadcall_destructor; + register long syscall_id asm("a7") = THREAD_SYSCALLS_BASE+8; + register long a0_out asm("a0"); + // Clobbers memory because it's like a function call + asm volatile ("ecall" : "=r"(a0_out) : + "r"(a0), "m"(*a0), "r"(a1), "m"(*a1), "r"(syscall_id) : "memory"); + return a0_out;*/ + return sys_microthread_direct(starter, threadcall_destructor); +} + +inline long join(Thread* thread) +{ + // yield until the tid value is zeroed + while (!thread->has_exited()) { + yield(); + // thread->tid might have changed since yielding + asm ("" : : : "memory"); + } + const long rv = thread->return_value; + free((char *)thread + sizeof(Thread) - Thread::STACK_SIZE); + return rv; +} +inline long join(Thread_ptr& tp) +{ + return join(tp.release()); +} + +template +inline void yield_until(Functor&& condition) +{ + do { + yield(); + asm("" ::: "memory"); + } while (!condition()); +} +inline long yield() +{ + register long a0 asm("a0"); + register long syscall_id asm("a7") = THREAD_SYSCALLS_BASE+2; + + asm volatile ("scall" : "=r"(a0) : "r"(syscall_id) : "memory"); + + return a0; +} +inline long yield_to(int tid) +{ + register long a0 asm("a0") = tid; + register long syscall_id asm("a7") = THREAD_SYSCALLS_BASE+3; + + asm volatile ("scall" : "+r"(a0) : "r"(syscall_id) : "memory"); + + return a0; +} +inline long yield_to(Thread* thread) +{ + return yield_to(thread->tid); +} + +inline long block(int reason) +{ + register long a0 asm("a0") = reason; + register long syscall_id asm("a7") = THREAD_SYSCALLS_BASE+4; + + asm volatile ("scall" : "+r"(a0) : "r"(syscall_id) : "memory"); + + return a0; +} +template +inline void block(Functor&& condition, int reason) +{ + while (!condition()) { + if (block(reason) < 0) break; + } +} +inline long wakeup_one_blocked(int reason) +{ + register long a0 asm("a0") = reason; + register long syscall_id asm("a7") = THREAD_SYSCALLS_BASE+5; + + asm volatile ("scall" : "+r"(a0) : "r"(syscall_id) : "memory"); + + return a0; +} +inline long unblock(int tid) +{ + register long a0 asm("a0") = tid; + register long syscall_id asm("a7") = THREAD_SYSCALLS_BASE+6; + + asm volatile ("scall" : "+r"(a0) : "r"(syscall_id) : "memory"); + + return a0; +} + +__attribute__((noreturn)) +inline void exit(long exitcode) +{ + self()->exit(exitcode); + __builtin_unreachable(); +} + +__attribute__((noreturn)) +inline void Thread::exit(long exitcode) +{ + this->tid = 0; + this->return_value = exitcode; + sys_microthread_exit(exitcode); + __builtin_unreachable(); +} + +} diff --git a/programs/micro/api/ringbuffer.hpp b/programs/micro/api/ringbuffer.hpp new file mode 100644 index 0000000..8b2397d --- /dev/null +++ b/programs/micro/api/ringbuffer.hpp @@ -0,0 +1,64 @@ +#pragma once +#include +#include +#include + +template +class FixedRingBuffer { +public: + bool write(T item) + { + if (!full()) { + buffer_at(this->m_writer ++) = std::move(item); + return true; + } + return false; + } + + const T* read() + { + if (!empty()) { + return &buffer_at(this->m_reader ++); + } + return nullptr; + } + + bool discard() + { + if (!this->empty()) { + this->m_reader ++; + return true; + } + return false; + } + void clear() { + this->m_reader = this->m_writer = 0; + } + + size_t size() const { + return this->m_writer - this->m_reader; + } + constexpr size_t capacity() const { + return N; + } + + bool full() const { + return size() == capacity(); + } + bool empty() const { + return size() == 0; + } + + const T* data() const { + return this->m_buffer; + } + +private: + T& buffer_at(size_t idx) { + return this->m_buffer[idx % capacity()]; + } + + size_t m_reader = 0; + size_t m_writer = 0; + T m_buffer[N]; +}; diff --git a/programs/micro/api/shared_memory.h b/programs/micro/api/shared_memory.h index 67680f3..7a8e0b0 100644 --- a/programs/micro/api/shared_memory.h +++ b/programs/micro/api/shared_memory.h @@ -1,5 +1,5 @@ #pragma once -#include +//#include #include #include #include diff --git a/programs/micro/libc/engine.hpp b/programs/micro/api/system.hpp similarity index 66% rename from programs/micro/libc/engine.hpp rename to programs/micro/api/system.hpp index 16781cf..8c2272a 100644 --- a/programs/micro/libc/engine.hpp +++ b/programs/micro/api/system.hpp @@ -1,15 +1,15 @@ #pragma once -#include "override/new" +//#include "override/new" #include #include #include #include -#include -#include -#include -#include +#include +#include #include #include +#define LIKELY(x) __builtin_expect(!!(x), 1) +#define UNLIKELY(x) __builtin_expect(!!(x), 0) constexpr double PI = 3.14159265358979323846; extern void halt(); @@ -47,3 +47,9 @@ extern "C" long sys_write(const void*, size_t); extern "C" void (*farcall_helper) (); extern "C" void (*direct_farcall_helper) (); extern "C" void sys_interrupt (uint32_t, uint32_t, const void*, size_t); +extern "C" void sys_assert_fail(const char*, const char*, int, const char*); +extern "C" long sys_measure(const char*, void (*)()); +extern "C" uint32_t sys_machine_hash(); +extern "C" void sys_game_exit(); +extern "C" intptr_t sys_game_setting(const char*, size_t); +extern "C" int sys_timer_sleep(float); diff --git a/programs/micro/libc/CMakeLists.txt b/programs/micro/libc/CMakeLists.txt deleted file mode 100644 index ae503a5..0000000 --- a/programs/micro/libc/CMakeLists.txt +++ /dev/null @@ -1,28 +0,0 @@ - -set(LIBC_SOURCES - assert.cpp - engine.cpp - write.cpp - ${BBLIBCPATH}/heap.cpp - ${BBLIBCPATH}/libc.cpp - ${BBLIBCPATH}/libcxx.cpp - ${BBLIBCPATH}/microthread.cpp - ) - -set_source_files_properties(${BBLIBCPATH}/libc.cpp - PROPERTIES COMPILE_FLAGS -fno-builtin) - - -add_library(libc STATIC ${LIBC_SOURCES}) -target_include_directories(libc PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_compile_definitions(libc PRIVATE - NATIVE_MEM_SYSCALLS=1 -) -target_compile_definitions(libc PUBLIC - NATIVE_SYSCALLS_BASE=570 - THREAD_SYSCALLS_BASE=590 -) -target_compile_definitions(libc PUBLIC - USE_NEWLIB=1 -) -target_link_libraries(libc PUBLIC strf-header-only) diff --git a/programs/micro/libc/assert.cpp b/programs/micro/libc/assert.cpp deleted file mode 100644 index 33025a0..0000000 --- a/programs/micro/libc/assert.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include -#include -#include -#include -#include - -template -inline void print(Args&&... args) -{ - char buffer[500]; - auto res = strf::to(buffer) (std::forward (args)...); - psyscall(ECALL_WRITE, buffer, res.ptr - buffer); -} - -static inline void print_backtrace() -{ - syscall1(SYSCALL_BACKTRACE); -} - -extern "C" -__attribute__((noreturn)) -void panic(const char* reason) -{ - print("\n\n!!! PANIC !!!\n", reason, '\n'); - - syscall(ECALL_ASSERT_FAIL, (long)reason, (long)__FILE__, __LINE__, (long)__func__); - - // the end - asm volatile("unimp"); - __builtin_unreachable(); -} - -extern "C" __attribute__((weak)) -void abort() -{ - print_backtrace(); - panic("Abort called"); -} - -extern "C" -void abort_message(const char* text, ...) -{ - print_backtrace(); - panic(text); -} - -extern "C" -void __assert_func( - const char *file, - int line, - const char *func, - const char *failedexpr) -{ - print("assertion ", failedexpr, " failed: \"", - file, "\", line ", line, func ? ", function: " : "", - func ? func : "", '\n'); - print_backtrace(); - panic("Assertion failed"); -} - -extern "C" -{ - __attribute__((used, weak)) - uintptr_t __stack_chk_guard __attribute__((section(".data"))) = 0x0C0A00FF; - __attribute__((used, weak)) - void __stack_chk_fail() - { - print_backtrace(); - panic("Stack protector failed check"); - } -} diff --git a/programs/micro/libc/engine.cpp b/programs/micro/libc/engine.cpp deleted file mode 100644 index cbf7bc5..0000000 --- a/programs/micro/libc/engine.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "engine.hpp" - -void halt() -{ - asm (".insn i SYSTEM, 0, x0, x0, 0x7ff"); -} - -SharedMemoryArea::SharedMemoryArea() - : m_shm {*(SharedMemoryArray *)(void*)SHM_BASE} -{} -void* SharedMemoryArea::push(const void* data, size_t size, size_t align) -{ - auto* dst = this->realign(size, align); - return memcpy(dst, data, size); -} - -#define STRINGIFY_HELPER(x) #x -#define STRINGIFY(x) STRINGIFY_HELPER(x) -asm(".section .text\n.align 8\n"); - -extern "C" void __attribute__((naked, used, retain)) fast_exit() -{ - asm (".insn i SYSTEM, 0, x0, x0, 0x7ff"); -} - -asm(".global sys_write\n" -"sys_write:\n" -" li a7, " STRINGIFY(ECALL_WRITE) "\n" -" ecall\n" -" ret\n"); diff --git a/programs/micro/libc/include/tuplecall.hpp b/programs/micro/libc/include/tuplecall.hpp deleted file mode 100644 index 7e10418..0000000 --- a/programs/micro/libc/include/tuplecall.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once -#include -#include - - -inline std::tuple -fsyscallff(long n, float farg0, float farg1) -{ - register float fa0 asm("fa0") = farg0; - register float fa1 asm("fa1") = farg1; - register long syscall_id asm("a7") = n; - - asm volatile ("scall" - : "+f"(fa0), "+f"(fa1) : "r"(syscall_id) : "a0"); - - return std::make_tuple(float{fa0}, float{fa1}); -} - -inline std::tuple -fsyscallff(long n, float farg0, float farg1, float farg2) -{ - register float fa0 asm("fa0") = farg0; - register float fa1 asm("fa1") = farg1; - register float fa2 asm("fa2") = farg2; - register long syscall_id asm("a7") = n; - - asm volatile ("scall" - : "+f"(fa0), "+f"(fa1) : "f"(fa2), "r"(syscall_id) : "a0"); - - return {float{fa0}, float{fa1}}; -} diff --git a/programs/micro/libc/override/mutex b/programs/micro/libc/override/mutex deleted file mode 100644 index 0422824..0000000 --- a/programs/micro/libc/override/mutex +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -namespace std { - struct mutex { - void lock() {} - void unlock() {} - }; -} diff --git a/programs/micro/libc/override/new b/programs/micro/libc/override/new deleted file mode 100644 index faa41e1..0000000 --- a/programs/micro/libc/override/new +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once -#include_next -#include -#include - -inline void* operator new(std::size_t size) { - return sys_malloc(size); -} -inline void* operator new[](std::size_t size) { - return sys_malloc(size); -} -inline void operator delete(void* ptr) { - sys_free(ptr); -} -inline void operator delete[](void* ptr) { - sys_free(ptr); -} -// C++14 sized deallocation -inline void operator delete(void* ptr, std::size_t) { - sys_free(ptr); -} -inline void operator delete [](void* ptr, std::size_t) { - sys_free(ptr); -} diff --git a/programs/micro/libc/write.cpp b/programs/micro/libc/write.cpp deleted file mode 100644 index 5a69d94..0000000 --- a/programs/micro/libc/write.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include -#include -#include - -extern "C" -int puts(const char* string) -{ - const long len = strlen(string); - return sys_write(string, len); -} - -extern "C" -ssize_t write(int, const void* buffer, size_t len) -{ - return sys_write(buffer, len); -} - -extern "C" -size_t fwrite(const void*__restrict buffer, size_t, size_t count, FILE*) -{ - return sys_write(buffer, count); -} - -extern "C" -int fputs (const char* str, FILE*) -{ - return puts(str); -} - -extern "C" -int fputc (int c, FILE*) -{ - return sys_write(&c, 1); -} -extern "C" -uint8_t fputwc(wchar_t ch, FILE*) -{ - char c = ch; - sys_write(&c, 1); - return ch; -} - -#ifndef USE_NEWLIB - -__attribute__((format (printf, 2, 3))) -int sprintf(char *buffer, const char *format, ...) -{ - const long len = strlen(format); - memcpy(buffer, format, len); - return len; -} - -#endif diff --git a/programs/micro/micro.cmake b/programs/micro/micro.cmake index d9a8c3b..8ac17a0 100644 --- a/programs/micro/micro.cmake +++ b/programs/micro/micro.cmake @@ -1,5 +1,4 @@ option(LTO "Link-time optimizations" ON) -option(GCSECTIONS "Garbage collect empty sections" ON) set(VERSION_FILE "symbols.map" CACHE STRING "Retained symbols file") option(STRIP_SYMBOLS "Remove all symbols except the public API" OFF) @@ -23,23 +22,14 @@ if (LTO) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto -ffat-lto-objects") endif() -if (GCSECTIONS AND NOT DEBUGGING) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffunction-sections -fdata-sections") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-gc-sections") -endif() - set(BUILD_SHARED_LIBS OFF) set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "") # remove -rdynamic -# enforce correct global include order for our tiny libc -include_directories(libc) -set (BBLIBCPATH "${CMAKE_CURRENT_LIST_DIR}/../../ext/libriscv/binaries/barebones/libc") -include_directories(${BBLIBCPATH}) - add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/ext ext) -add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/libc libc) -target_include_directories(libc PUBLIC ${APIPATH}) -target_include_directories(libc PUBLIC ${UTILPATH}) +set(MICRO_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/micro.cpp + ${CMAKE_CURRENT_LIST_DIR}/microthread.cpp +) function (add_verfile NAME VERFILE) set_target_properties(${NAME} PROPERTIES LINK_DEPENDS ${VERFILE}) @@ -53,14 +43,15 @@ function (add_micro_binary NAME ORG) ${CMAKE_BINARY_DIR}/dyncalls/dyncall_api.h) set_source_files_properties(${DYNCALL_API} PROPERTIES GENERATED TRUE) # the micro binary - add_executable(${NAME} ${ARGN} ${DYNCALL_API}) - target_include_directories(${NAME} PUBLIC ${CMAKE_BINARY_DIR}/dyncalls) + add_executable(${NAME} ${ARGN} + ${DYNCALL_API} + ${MICRO_SOURCES} + ) + target_include_directories(${NAME} PUBLIC ${CMAKE_BINARY_DIR}/dyncalls ${APIPATH} ${UTILPATH}) add_dependencies(${NAME} generate_dyncalls) # Add the whole libc directly as source files - target_link_libraries(${NAME} -static -Wl,--whole-archive libc -Wl,--no-whole-archive) - target_link_libraries(${NAME} frozen::frozen) - target_link_libraries(${NAME} "-Wl,-Ttext-segment=${ORG}") - target_link_libraries(${NAME} "-Wl,--wrap=exit") + target_link_libraries(${NAME} frozen::frozen strf-header-only) + target_link_libraries(${NAME} "-Wl,--image-base=${ORG}") # The dynamic call table sometimes gets removed by linker GC target_link_libraries(${NAME} "-Wl,-u,dyncall_table") # place ELF into the sub-projects source folder @@ -86,12 +77,12 @@ function (add_shared_program NAME ORG WILDCARD) add_micro_binary(${NAME} ${ORG} ${ARGN}) add_custom_command( TARGET ${NAME} POST_BUILD - COMMAND ${GCC_TRIPLE}-objcopy -w --extract-symbol --strip-symbol=!${WILDCARD} --strip-symbol=* ${CMAKE_CURRENT_SOURCE_DIR}/${NAME} ${NAME}.syms + COMMAND riscv64-linux-gnu-objcopy -w --extract-symbol --strip-symbol=!${WILDCARD} --strip-symbol=* ${CMAKE_CURRENT_SOURCE_DIR}/${NAME} ${NAME}.syms ) endfunction() -function (add_level NAME ORG) - add_micro_binary(${NAME} ${ORG} ${ARGN}) +function (add_level NAME) + add_micro_binary(${NAME} 0x100000 ${ARGN}) endfunction() function (attach_program NAME PROGRAM) diff --git a/programs/micro/micro.cpp b/programs/micro/micro.cpp new file mode 100644 index 0000000..107879b --- /dev/null +++ b/programs/micro/micro.cpp @@ -0,0 +1,269 @@ +#include "syscalls.h" +#include +#include +#include + +#define NATIVE_MEM_FUNCATTR /* */ +#define NATIVE_SYSCALLS_BASE 570 /* libc starts at 570 */ + +#define SYSCALL_MALLOC (NATIVE_SYSCALLS_BASE + 0) +#define SYSCALL_CALLOC (NATIVE_SYSCALLS_BASE + 1) +#define SYSCALL_REALLOC (NATIVE_SYSCALLS_BASE + 2) +#define SYSCALL_FREE (NATIVE_SYSCALLS_BASE + 3) +#define SYSCALL_MEMINFO (NATIVE_SYSCALLS_BASE + 4) + +#define SYSCALL_MEMCPY (NATIVE_SYSCALLS_BASE + 5) +#define SYSCALL_MEMSET (NATIVE_SYSCALLS_BASE + 6) +#define SYSCALL_MEMMOVE (NATIVE_SYSCALLS_BASE + 7) +#define SYSCALL_MEMCMP (NATIVE_SYSCALLS_BASE + 8) + +#define SYSCALL_STRLEN (NATIVE_SYSCALLS_BASE + 10) +#define SYSCALL_STRCMP (NATIVE_SYSCALLS_BASE + 11) + +#define SYSCALL_BACKTRACE (NATIVE_SYSCALLS_BASE + 19) + +#define STR1(x) #x +#define STR(x) STR1(x) + +// clang-format off +#define CREATE_SYSCALL(name, syscall_id) \ + __asm__(".pushsection .text\n" \ + ".global " #name "\n" \ + ".type " #name ", @function\n" \ + "" #name ":\n" \ + " li a7, " STR(syscall_id) "\n" \ + " ecall\n" \ + " ret\n" \ + ".popsection .text\n") +#define CREATE_SYSCALL_STRCMP(name, syscall_id) \ + __asm__(".pushsection .text\n" \ + ".global " #name "\n" \ + ".type " #name ", @function\n" \ + "" #name ":\n" \ + " li a2, 4096\n" \ + " li a7, " STR(syscall_id) "\n" \ + " ecall\n" \ + " ret\n" \ + ".popsection .text\n") + +// clang-format on + +#ifdef ZIG_COMPILER +#define WRAP_FANCY 0 +#else +#define WRAP_FANCY 1 +#endif + +#if !WRAP_FANCY +CREATE_SYSCALL(malloc, SYSCALL_MALLOC); +CREATE_SYSCALL(calloc, SYSCALL_CALLOC); +CREATE_SYSCALL(realloc, SYSCALL_REALLOC); +CREATE_SYSCALL(free, SYSCALL_FREE); +CREATE_SYSCALL(memset, SYSCALL_MEMSET); +CREATE_SYSCALL(memcpy, SYSCALL_MEMCPY); +CREATE_SYSCALL(memmove, SYSCALL_MEMMOVE); +CREATE_SYSCALL(memcmp, SYSCALL_MEMCMP); +CREATE_SYSCALL(strlen, SYSCALL_STRLEN); +CREATE_SYSCALL_STRCMP(strcmp, SYSCALL_STRCMP); +CREATE_SYSCALL(strncmp, SYSCALL_STRCMP); + +#else // WRAP_FANCY + +CREATE_SYSCALL(__wrap_calloc, SYSCALL_CALLOC); +CREATE_SYSCALL(__wrap_realloc, SYSCALL_REALLOC); + +#endif // WRAP_FANCY + +extern "C" void *__wrap_malloc(size_t size) { + register char *ret __asm__("a0") = nullptr; + register size_t a0 __asm__("a0") = size; + register long syscall_id __asm__("a7") = SYSCALL_MALLOC; + + asm volatile("ecall" + : "=m"(*(char(*)[16384])ret), "=r"(ret) + : "r"(a0), "r"(syscall_id)); + return ret; +} +extern "C" void __wrap_free(void *ptr) { + register void *a0 __asm__("a0") = ptr; + register long syscall_id __asm__("a7") = SYSCALL_FREE; + + asm volatile("ecall" + : + : "r"(a0), "r"(syscall_id)); +} + +extern "C" void *__wrap_memset(void *vdest, const int ch, size_t size) { + register char *a0 __asm__("a0") = (char *)vdest; + register int a1 __asm__("a1") = ch; + register size_t a2 __asm__("a2") = size; + register long syscall_id __asm__("a7") = SYSCALL_MEMSET; + + asm volatile("ecall" + : "=m"(*(char(*)[16384])a0) + : "r"(a0), "r"(a1), "r"(a2), "r"(syscall_id)); + return vdest; +} +extern "C" void *__wrap_memcpy(void *vdest, const void *vsrc, size_t size) { + register char *a0 __asm__("a0") = (char *)vdest; + register const char *a1 __asm__("a1") = (const char *)vsrc; + register size_t a2 __asm__("a2") = size; + register long syscall_id __asm__("a7") = SYSCALL_MEMCPY; + + asm volatile("ecall" + : "=m"(*(char(*)[16384])a0), "+r"(a0) + : "r"(a1), "m"(*(const char(*)[16384])a1), + "r"(a2), "r"(syscall_id)); + return vdest; +} +extern "C" void *__wrap_memmove(void *vdest, const void *vsrc, size_t size) { + // An assumption is being made here that since vsrc might be + // inside vdest, we cannot assume that vsrc is const anymore. + register char *a0 __asm__("a0") = (char *)vdest; + register char *a1 __asm__("a1") = (char *)vsrc; + register size_t a2 __asm__("a2") = size; + register long syscall_id __asm__("a7") = SYSCALL_MEMMOVE; + + asm volatile("ecall" + : "=m"(*(char(*)[16384])a0), "=m"(*(char(*)[16384])a1) + : "r"(a0), "r"(a1), "r"(a2), "r"(syscall_id)); + return vdest; +} +extern "C" int __wrap_memcmp(const void *m1, const void *m2, size_t size) { + register const char *a0 __asm__("a0") = (const char *)m1; + register const char *a1 __asm__("a1") = (const char *)m2; + register size_t a2 __asm__("a2") = size; + register long syscall_id __asm__("a7") = SYSCALL_MEMCMP; + register int a0_out __asm__("a0"); + + asm volatile("ecall" + : "=r"(a0_out) + : "r"(a0), "m"(*(const char(*)[16384])a0), + "r"(a1), "m"(*(const char(*)[16384])a1), + "r"(a2), "r"(syscall_id)); + return a0_out; +} +extern "C" size_t __wrap_strlen(const char *str) { + register const char *a0 __asm__("a0") = str; + register size_t a0_out __asm__("a0"); + register long syscall_id __asm__("a7") = SYSCALL_STRLEN; + + asm volatile("ecall" + : "=r"(a0_out) + : "r"(a0), "m"(*(const char(*)[4096])a0), "r"(syscall_id)); + return a0_out; +} +extern "C" int __wrap_strcmp(const char *str1, const char *str2) { + register const char *a0 __asm__("a0") = str1; + register const char *a1 __asm__("a1") = str2; + register size_t a2 __asm__("a2") = 4096; + register size_t a0_out __asm__("a0"); + register long syscall_id __asm__("a7") = SYSCALL_STRCMP; + + asm volatile("ecall" + : "=r"(a0_out) + : "r"(a0), "m"(*(const char(*)[4096])a0), + "r"(a1), "m"(*(const char(*)[4096])a1), + "r"(a2), "r"(syscall_id)); + return a0_out; +} +extern "C" int __wrap_strncmp(const char *str1, const char *str2, size_t maxlen) { + register const char *a0 __asm__("a0") = str1; + register const char *a1 __asm__("a1") = str2; + register size_t a2 __asm__("a2") = maxlen; + register size_t a0_out __asm__("a0"); + register long syscall_id __asm__("a7") = SYSCALL_STRCMP; + + asm volatile("ecall" + : "=r"(a0_out) + : "r"(a0), "m"(*(const char(*)[16384])a0), + "r"(a1), "m"(*(const char(*)[16384])a1), + "r"(a2), "r"(syscall_id)); + return a0_out; +} + +// Fallback implementation of aligned allocations +extern "C" void *memalign(size_t alignment, size_t size) { + if (alignment <= 16) { + return __wrap_malloc(size); + } + + std::array list; + size_t i = 0; + void *result = nullptr; + for (i = 0; i < list.size(); i++) { + result = __wrap_malloc(size); + list[i] = result; + const bool aligned = ((uintptr_t)result % alignment) == 0; + if (result && aligned) { + break; + } else if (result) { + __wrap_free(result); + list[i] = __wrap_malloc(16); + } else { + result = nullptr; + break; + } + } + for (size_t j = 0; j < i; j++) { + __wrap_free(list[j]); + } + return result; +} +extern "C" +void *aligned_alloc(size_t alignment, size_t size) { + return memalign(alignment, size); +} +extern "C" +int posix_memalign(void **memptr, size_t alignment, size_t size) { + void *result = memalign(alignment, size); + if (result) { + *memptr = result; + return 0; + } + return 1; +} + +void *operator new(size_t size) noexcept(false) { + return __wrap_malloc(size); +} +void *operator new[](size_t size) noexcept(false) { + return __wrap_malloc(size); +} +void operator delete(void *ptr) noexcept(true) { + __wrap_free(ptr); +} +void operator delete[](void *ptr) noexcept(true) { + __wrap_free(ptr); +} +void *operator new (size_t size, size_t alignment) noexcept(false) { + return memalign(alignment, size); +} +void *operator new[](size_t size, size_t alignment) noexcept(false) { + return memalign(alignment, size); +} +void operator delete(void *ptr, size_t alignment) noexcept(true) { + (void)alignment; + __wrap_free(ptr); +} +void operator delete[](void *ptr, size_t alignment) noexcept(true) { + (void)alignment; + __wrap_free(ptr); +} + +CREATE_SYSCALL(sys_assert_fail, ECALL_ASSERT_FAIL); +CREATE_SYSCALL(sys_write, ECALL_WRITE); +CREATE_SYSCALL(sys_measure, ECALL_MEASURE); + +void halt() +{ + asm (".insn i SYSTEM, 0, x0, x0, 0x7ff"); +} + +extern "C" __attribute__((used, retain, noreturn)) +void fast_exit(int code) +{ + register long a0 asm("a0") = code; + asm (".insn i SYSTEM, 0, x0, x0, 0x7ff" : : "r"(a0)); + __builtin_unreachable(); +} diff --git a/programs/micro/microthread.cpp b/programs/micro/microthread.cpp new file mode 100644 index 0000000..dbcd4f2 --- /dev/null +++ b/programs/micro/microthread.cpp @@ -0,0 +1,60 @@ +#include "microthread.hpp" + +extern "C" void microthread_set_tp(void*); + +namespace microthread +{ + static Thread main_thread {nullptr}; + + void trampoline(Thread* thread) + { + thread->startfunc(); + } + void oneshot_exit() + { + auto* thread = self(); + // after this point stack unusable + free((char *)thread + sizeof(Thread) - Thread::STACK_SIZE); + sys_microthread_exit(0); + __builtin_unreachable(); + } + + /* glibc sets up its own main thread, *required* by C++ exceptions */ +#if 0 + __attribute__((constructor, used)) + static void init_threads() + { + microthread_set_tp(&main_thread); + } +#endif +} + +asm(".section .text\n" +".global microthread_set_tp\n" +".type microthread_set_tp, @function\n" +"microthread_set_tp:\n" +" mv tp, a0\n" +" ret\n"); + +#define STRINGIFY_HELPER(x) #x +#define STR(x) STRINGIFY_HELPER(x) + +#define CREATE_SYSCALL(name, syscall_id) \ + __asm__(".pushsection .text\n" \ + ".global " #name "\n" \ + ".type " #name ", @function\n" \ + "" #name ":\n" \ + " li a7, " STR(syscall_id) "\n" \ + " ecall\n" \ + " ret\n" \ + ".popsection .text\n") + +CREATE_SYSCALL(sys_microthread_create, THREAD_SYSCALLS_BASE+0); +CREATE_SYSCALL(sys_microthread_exit, THREAD_SYSCALLS_BASE+1); +CREATE_SYSCALL(sys_microthread_yield, THREAD_SYSCALLS_BASE+2); +CREATE_SYSCALL(sys_microthread_yield_to,THREAD_SYSCALLS_BASE+3); +CREATE_SYSCALL(sys_microthread_block, THREAD_SYSCALLS_BASE+4); +CREATE_SYSCALL(sys_microthread_unblock, THREAD_SYSCALLS_BASE+6); +CREATE_SYSCALL(sys_microthread_wakeup_one_blocked, THREAD_SYSCALLS_BASE+5); +CREATE_SYSCALL(sys_microthread_direct, THREAD_SYSCALLS_BASE+8); +CREATE_SYSCALL(threadcall_destructor, THREAD_SYSCALLS_BASE+9); diff --git a/programs/micro/zig-ar.cmd b/programs/micro/zig-ar.cmd new file mode 100644 index 0000000..2627665 --- /dev/null +++ b/programs/micro/zig-ar.cmd @@ -0,0 +1,2 @@ +@echo off +zig ar %* diff --git a/programs/micro/zig-ranlib.cmd b/programs/micro/zig-ranlib.cmd new file mode 100644 index 0000000..50386ad --- /dev/null +++ b/programs/micro/zig-ranlib.cmd @@ -0,0 +1,2 @@ +@echo off +zig ranlib %* diff --git a/programs/micro/zig-toolchain.cmake b/programs/micro/zig-toolchain.cmake new file mode 100644 index 0000000..107f90d --- /dev/null +++ b/programs/micro/zig-toolchain.cmake @@ -0,0 +1,17 @@ +set(CMAKE_SYSTEM_NAME "Linux") +set(CMAKE_SYSTEM_VERSION 1) +set(CMAKE_SYSTEM_PROCESSOR "riscv64") +set(CMAKE_CROSSCOMPILING TRUE) +set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) +set(CMAKE_C_COMPILER "zig" cc -target riscv64-linux-musl) +set(CMAKE_CXX_COMPILER "zig" c++ -target riscv64-linux-musl) +set(ZIG_COMPILER 1) + +if (CMAKE_HOST_WIN32) + # Windows: Disable .d files + set(CMAKE_C_LINKER_DEPFILE_SUPPORTED FALSE) + set(CMAKE_CXX_LINKER_DEPFILE_SUPPORTED FALSE) + # Windows: Work-around for zig ar and zig ranlib + set(CMAKE_AR "${CMAKE_CURRENT_LIST_DIR}/zig-ar.cmd") + set(CMAKE_RANLIB "${CMAKE_CURRENT_LIST_DIR}/zig-ranlib.cmd") +endif() diff --git a/programs/zig.sh b/programs/zig.sh new file mode 100755 index 0000000..333d81f --- /dev/null +++ b/programs/zig.sh @@ -0,0 +1,7 @@ +TOOLCHAIN=$PWD/micro/zig-toolchain.cmake + +mkdir -p .zig +pushd .zig +cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=$TOOLCHAIN +make -j8 +popd