From 2368351e4196260a7c12647ea7dfb6ead4f13729 Mon Sep 17 00:00:00 2001 From: Adrian Siekierka Date: Thu, 2 May 2024 16:58:02 +0200 Subject: [PATCH] asset, kernel, toolchain: initial picolibc support --- src/asset.c | 12 +++ src/kernel/kernel.c | 10 +-- src/kernel/kernel_internal.h | 4 +- src/system.c | 11 +++ src/system_newlib_locks.c | 7 +- tools/build-toolchain.sh | 153 +++++++++++++++++++++++++++-------- tools/meson-cross.txt | 19 +++++ 7 files changed, 176 insertions(+), 40 deletions(-) create mode 100644 tools/meson-cross.txt diff --git a/src/asset.c b/src/asset.c index 07227caaa1..50324a0384 100644 --- a/src/asset.c +++ b/src/asset.c @@ -275,7 +275,13 @@ static fpos_t seekfn_none(void *c, fpos_t pos, int whence) return -1; } +#if defined(__PICOLIBC__) && defined(TINY_STDIO) +static int readfn_none(void *c, void *buf, size_t sz) +#elif defined(__PICOLIBC__) +static int readfn_none(void *c, char *buf, size_t sz) +#else static int readfn_none(void *c, char *buf, int sz) +#endif { cookie_none_t *cookie = c; assertf(!cookie->seeked, "Cannot seek in file opened via asset_fopen (it might be compressed)"); @@ -299,7 +305,13 @@ typedef struct { uint8_t alignas(8) state[]; } cookie_cmp_t; +#if defined(__PICOLIBC__) && defined(TINY_STDIO) +static int readfn_cmp(void *c, void *buf, size_t sz) +#elif defined(__PICOLIBC__) +static int readfn_cmp(void *c, char *buf, size_t sz) +#else static int readfn_cmp(void *c, char *buf, int sz) +#endif { cookie_cmp_t *cookie = (cookie_cmp_t*)c; assertf(!cookie->seeked, "Cannot seek in file opened via asset_fopen (it might be compressed)"); diff --git a/src/kernel/kernel.c b/src/kernel/kernel.c index 89d068f02a..90fdb49fc7 100644 --- a/src/kernel/kernel.c +++ b/src/kernel/kernel.c @@ -326,7 +326,7 @@ reg_block_t* __kthread_syscall_schedule(reg_block_t *stack_state) th_cur_tp = th_cur->tp_value; - #ifdef __NEWLIB__ + #if defined(__NEWLIB__) && !defined(__PICOLIBC__) _REENT = th_cur->tls.reent_ptr; #endif @@ -356,7 +356,7 @@ kthread_t* kernel_init(void) { assertf(__tdata_align <= 8, "Unsupported TLS alignment of %d (Maximum 8)", __tdata_align); assert(!__kernel); - #ifdef __NEWLIB__ + #if defined(__NEWLIB__) && !defined(__PICOLIBC__) // Check if __malloc_lock is a nop. This happens with old toolchains where // newlib was compiled with --disable-threads. extern void __malloc_lock(void); @@ -383,7 +383,7 @@ kthread_t* kernel_init(void) th_main.flags = TH_FLAG_DETACHED; // main thread cannot be joined th_main.tp_value = __tls_base+TP_OFFSET; - #ifdef __NEWLIB__ + #if defined(__NEWLIB__) && !defined(__PICOLIBC__) th_main.tls.reent_ptr = _REENT; #endif @@ -450,7 +450,7 @@ kthread_t* __kthread_new_internal(const char *name, int stack_size, int8_t pri, // make debugging more difficult and might even cause the kernel to crash. int extra_size = TLS_SIZE; - #ifdef __NEWLIB__ + #if defined(__NEWLIB__) && !defined(__PICOLIBC__) extra_size += sizeof(struct _reent); #endif void *thmem = malloc(STACK_GUARD + stack_size + sizeof(kthread_t) + extra_size); @@ -516,7 +516,7 @@ kthread_t* __kthread_new_internal(const char *name, int stack_size, int8_t pri, th->tp_value = extra+TP_OFFSET; extra += TLS_SIZE; // TLS initial configuration - #ifdef __NEWLIB__ + #if defined(__NEWLIB__) && !defined(__PICOLIBC__) th->tls.reent_ptr = extra; _REENT_INIT_PTR(th->tls.reent_ptr); #endif diff --git a/src/kernel/kernel_internal.h b/src/kernel/kernel_internal.h index cac7e1afc5..f2b86c172d 100644 --- a/src/kernel/kernel_internal.h +++ b/src/kernel/kernel_internal.h @@ -7,7 +7,7 @@ #define __LIBDRAGON_KERNEL_INTERNAL_H #include "kernel.h" -#ifdef __NEWLIB__ +#if defined(__NEWLIB__) && !defined(__PICOLIBC__) #include #endif @@ -57,7 +57,7 @@ typedef struct __attribute__((aligned (8))) kthread_s int interrupt_depth; /** Mirror of __interrupt_sr */ int interrupt_sr; - #ifdef __NEWLIB__ + #if defined(__NEWLIB__) && !defined(__PICOLIBC__) /** Newlib reentrancy */ struct _reent *reent_ptr; #endif diff --git a/src/system.c b/src/system.c index e019fdc1f1..7622b440fe 100644 --- a/src/system.c +++ b/src/system.c @@ -3,13 +3,17 @@ * @brief newlib Interface Hooks * @ingroup system */ +#include +#if !defined(__PICOLIBC__) #include <_ansi.h> +#endif #include <_syslist.h> #include #include #include #include #include +#include #include #include #include @@ -35,6 +39,13 @@ #define STDERR_FILENO 2 /** @} */ +#if defined(__PICOLIBC__) && defined(TINY_STDIO) +/* Force weakly referenced default stdin/stdout/stderr implementations to be linked. */ +__asm__(".equ stdin_reference, stdin"); +__asm__(".equ stdout_reference, stdout"); +__asm__(".equ stderr_reference, stderr"); +#endif + /** * @brief Stack size * diff --git a/src/system_newlib_locks.c b/src/system_newlib_locks.c index 7ce4242187..570d9fdae7 100644 --- a/src/system_newlib_locks.c +++ b/src/system_newlib_locks.c @@ -12,6 +12,7 @@ */ #include "kernel.h" #include +#include // Toolchains since Sept 2024 build with threads and retargetable locks enabled, // both of which are required for libc to be thread safe and thus the kernel @@ -27,6 +28,9 @@ struct __lock { }; ///@cond +#ifdef __PICOLIBC__ +struct __lock __lock___libc_recursive_mutex; +#else struct __lock __lock___sfp_recursive_mutex; struct __lock __lock___atexit_recursive_mutex; struct __lock __lock___at_quick_exit_mutex; @@ -35,6 +39,7 @@ struct __lock __lock___env_recursive_mutex; struct __lock __lock___tz_mutex; struct __lock __lock___dd_hash_mutex; struct __lock __lock___arc4random_mutex; +#endif ///@endcond /** Pool of dynamically allocated lists */ @@ -118,4 +123,4 @@ void __retarget_lock_release_recursive (_LOCK_T lock) { ///@endcond -#endif \ No newline at end of file +#endif diff --git a/tools/build-toolchain.sh b/tools/build-toolchain.sh index 130373f9ae..dd491b801d 100755 --- a/tools/build-toolchain.sh +++ b/tools/build-toolchain.sh @@ -22,6 +22,14 @@ N64_BUILD=${N64_BUILD:-""} N64_HOST=${N64_HOST:-""} N64_TARGET=${N64_TARGET:-mips64-elf} +# Toolchain configuration options. +N64_USE_PICOLIBC=${N64_USE_PICOLIBC:-"false"} +N64_USE_PICOLIBC_TINYSTDIO=${N64_USE_PICOLIBC_TINYSTDIO:-"false"} +N64_USE_PICOLIBC_LEGACY_STDIO=true +if [ "$N64_USE_PICOLIBC_TINYSTDIO" == "true" ]; then + N64_USE_PICOLIBC_LEGACY_STDIO=false +fi + # Set N64_INST before calling the script to change the default installation directory path INSTALL_PATH="${N64_INST}" # Set PATH for newlib to compile using GCC for MIPS N64 (pass 1) @@ -38,8 +46,9 @@ GCC_CONFIGURE_ARGS=() BINUTILS_V=2.43.1 GCC_V=14.2.0 NEWLIB_V=4.4.0.20231231 -GMP_V=6.3.0 -MPC_V=1.3.1 +PICOLIBC_V=e0a04fec075f5cb1ddb80ea8c359748ef72b9122 +GMP_V=6.3.0 +MPC_V=1.3.1 MPFR_V=4.2.1 MAKE_V=${MAKE_V:-""} @@ -104,8 +113,13 @@ test -d "binutils-$BINUTILS_V" || tar -xzf "binutils-$BINUTILS_V.tar.gz" test -f "gcc-$GCC_V.tar.gz" || download "https://ftp.gnu.org/gnu/gcc/gcc-$GCC_V/gcc-$GCC_V.tar.gz" test -d "gcc-$GCC_V" || tar -xzf "gcc-$GCC_V.tar.gz" -test -f "newlib-$NEWLIB_V.tar.gz" || download "https://sourceware.org/pub/newlib/newlib-$NEWLIB_V.tar.gz" -test -d "newlib-$NEWLIB_V" || tar -xzf "newlib-$NEWLIB_V.tar.gz" +if [ "$N64_USE_PICOLIBC" == "true" ]; then + test -f "picolibc-$PICOLIBC_V.zip" || ( download "https://github.com/picolibc/picolibc/archive/$PICOLIBC_V.zip" && mv "$PICOLIBC_V.zip" "picolibc-$PICOLIBC_V.zip" ) + test -d "picolibc-$PICOLIBC_V" || unzip "picolibc-$PICOLIBC_V.zip" +else + test -f "newlib-$NEWLIB_V.tar.gz" || download "https://sourceware.org/pub/newlib/newlib-$NEWLIB_V.tar.gz" + test -d "newlib-$NEWLIB_V" || tar -xzf "newlib-$NEWLIB_V.tar.gz" +fi if [ "$GMP_V" != "" ]; then test -f "gmp-$GMP_V.tar.bz2" || download "https://ftp.gnu.org/gnu/gmp/gmp-$GMP_V.tar.bz2" @@ -220,20 +234,58 @@ make all-target-libgcc -j "$JOBS" make install-target-libgcc || sudo make install-target-libgcc || su -c "make install-target-libgcc" popd -# Compile newlib for target. -mkdir -p newlib_compile_target -pushd newlib_compile_target -CFLAGS_FOR_TARGET="-DHAVE_ASSERT_FUNC -O2 -fpermissive" ../"newlib-$NEWLIB_V"/configure \ - --prefix="$CROSS_PREFIX" \ - --target="$N64_TARGET" \ - --with-cpu=mips64vr4300 \ - --disable-libssp \ - --disable-werror \ - --enable-newlib-multithread \ - --enable-newlib-retargetable-locking -make -j "$JOBS" -make install || sudo env PATH="$PATH" make install || su -c "env PATH=\"$PATH\" make install" -popd +if [ "$N64_USE_PICOLIBC" == "true" ]; then + # Compile picolibc for target. + mkdir -p picolibc_compile_target + pushd picolibc_compile_target + meson setup \ + --cross-file=../../meson-cross.txt \ + -Dmultilib=false \ + -Dpicocrt=false \ + -Dpicolib=false \ + -Dsemihost=false \ + -Dspecsdir=none \ + -Dtests=false \ + -Dtinystdio="$N64_USE_PICOLIBC_TINYSTDIO" \ + -Dfast-bufio=true \ + -Dio-long-long=true \ + -Dio-pos-args="$N64_USE_PICOLIBC_TINYSTDIO" \ + -Dio-percent-b=true \ + -Dposix-console=true \ + -Dformat-default=double \ + -Dnewlib-fseek-optimization="$N64_USE_PICOLIBC_LEGACY_STDIO" \ + -Dnewlib-fvwrite-in-streamio="$N64_USE_PICOLIBC_LEGACY_STDIO" \ + -Dnewlib-io-float="$N64_USE_PICOLIBC_LEGACY_STDIO" \ + -Dnewlib-stdio64=false \ + -Dnewlib-unbuf-stream-opt="$N64_USE_PICOLIBC_LEGACY_STDIO" \ + -Dnewlib-nano-malloc=false \ + -Dnewlib-multithread=true \ + -Dnewlib-retargetable-locking=true \ + -Dthread-local-storage=true \ + -Dpicoexit=false \ + -Dprefix="$CROSS_PREFIX" \ + -Dlibdir=mips64-elf/lib \ + -Dincludedir=mips64-elf/include \ + ../"picolibc-$PICOLIBC_V" + ninja -j "$JOBS" + ninja install || sudo env PATH="$PATH" ninja install || su -c "env PATH=\"$PATH\" ninja install" + popd +else + # Compile newlib for target. + mkdir -p newlib_compile_target + pushd newlib_compile_target + CFLAGS_FOR_TARGET="-DHAVE_ASSERT_FUNC -O2 -fpermissive" ../"newlib-$NEWLIB_V"/configure \ + --prefix="$CROSS_PREFIX" \ + --target="$N64_TARGET" \ + --with-cpu=mips64vr4300 \ + --disable-libssp \ + --disable-werror \ + --enable-newlib-multithread \ + --enable-newlib-retargetable-locking + make -j "$JOBS" + make install || sudo env PATH="$PATH" make install || su -c "env PATH=\"$PATH\" make install" + popd +fi # For a standard cross-compiler, the only thing left is to finish compiling the target libraries # like libstd++. We can continue on the previous GCC build target. @@ -286,20 +338,57 @@ else make install-target-libgcc || sudo make install-target-libgcc || su -c "make install-target-libgcc" popd - # Compile newlib for target. - mkdir -p newlib_compile - pushd newlib_compile - CFLAGS_FOR_TARGET="-DHAVE_ASSERT_FUNC -O2 -fpermissive" ../"newlib-$NEWLIB_V"/configure \ - --prefix="$INSTALL_PATH" \ - --target="$N64_TARGET" \ - --with-cpu=mips64vr4300 \ - --disable-libssp \ - --disable-werror \ - --enable-newlib-multithread \ - --enable-newlib-retargetable-locking - make -j "$JOBS" - make install || sudo env PATH="$PATH" make install || su -c "env PATH=\"$PATH\" make install" - popd + if [ "$N64_USE_PICOLIBC" == "true" ]; then + # Compile picolibc for target. + mkdir -p picolibc_compile_target + pushd picolibc_compile_target + meson setup \ + --cross-file=../../meson-cross.txt \ + -Dmultilib=false \ + -Dpicocrt=false \ + -Dpicolib=false \ + -Dsemihost=false \ + -Dspecsdir=none \ + -Dtests=false \ + -Dtinystdio="$N64_USE_PICOLIBC_TINYSTDIO" \ + -Dfast-bufio=true \ + -Dio-long-long=true \ + -Dio-pos-args="$N64_USE_PICOLIBC_TINYSTDIO" \ + -Dio-percent-b=true \ + -Dposix-console=true \ + -Dformat-default=double \ + -Dnewlib-fseek-optimization="$N64_USE_PICOLIBC_LEGACY_STDIO" \ + -Dnewlib-fvwrite-in-streamio="$N64_USE_PICOLIBC_LEGACY_STDIO" \ + -Dnewlib-io-float="$N64_USE_PICOLIBC_LEGACY_STDIO" \ + -Dnewlib-stdio64=false \ + -Dnewlib-unbuf-stream-opt="$N64_USE_PICOLIBC_LEGACY_STDIO" \ + -Dnewlib-nano-malloc=false \ + -Dnewlib-multithread=true \ + -Dnewlib-retargetable-locking=true \ + -Dthread-local-storage=false \ + -Dprefix="$INSTALL_PATH" \ + -Dlibdir=mips64-elf/lib \ + -Dincludedir=mips64-elf/include \ + ../"picolibc-$PICOLIBC_V" + ninja -j "$JOBS" + ninja install || sudo env PATH="$PATH" ninja install || su -c "env PATH=\"$PATH\" ninja install" + popd + else + # Compile newlib for target. + mkdir -p newlib_compile_target + pushd newlib_compile_target + CFLAGS_FOR_TARGET="-DHAVE_ASSERT_FUNC -O2 -fpermissive" ../"newlib-$NEWLIB_V"/configure \ + --prefix="$INSTALL_PATH" \ + --target="$N64_TARGET" \ + --with-cpu=mips64vr4300 \ + --disable-libssp \ + --disable-werror \ + --enable-newlib-multithread \ + --enable-newlib-retargetable-locking + make -j "$JOBS" + make install || sudo env PATH="$PATH" make install || su -c "env PATH=\"$PATH\" make install" + popd + fi # Finish compiling GCC mkdir -p gcc_compile diff --git a/tools/meson-cross.txt b/tools/meson-cross.txt new file mode 100644 index 0000000000..26fee89b1b --- /dev/null +++ b/tools/meson-cross.txt @@ -0,0 +1,19 @@ +[binaries] +# Meson 0.53.2 doesn't use any cflags when doing basic compiler tests, +# so we have to add -nostdlib to the compiler configuration itself or +# early compiler tests will fail. This can be removed when picolibc +# requires at least version 0.54.2 of meson. +c = ['mips64-elf-gcc', '-nostdlib'] +ar = 'mips64-elf-ar' +as = 'mips64-elf-as' +nm = 'mips64-elf-nm' +strip = 'mips64-elf-strip' + +[host_machine] +system = 'none' +cpu_family = 'mips64' +cpu = 'mips64vr4300' +endian = 'big' + +[properties] +skip_sanity_check = true