From 3915c4a963a9d97ef0aa02182e715689e753466b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 22 Jul 2024 11:28:19 +0100 Subject: [PATCH] Add experimental ability to build LLVM libc. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is extremely new and barely tested: I've compiled and run a hello-world program, and that is literally all. But that's a start – if you can do that, you can also investigate what else does and doesn't compile or run successfully. So this is enough to allow users to experiment. As documented in the new `docs/llvmlibc.md`, this currently builds C libraries only (not C++), for AArch32 only. I don't know how much effort it will take to fix each of those: I only know that in each case _at least one_ cause of build failure exists, but I don't know how many more are hiding behind that one. You can use the new `-DLLVM_TOOLCHAIN_C_LIBRARY` with or without the option to build an overlay package. If you have both a newlib and an llvm-libc overlay package, then it should be possible to install both alongside each other on top of the same main toolchain. Unlike picolibc and newlib, llvm-libc doesn't come with a crt0.o startup file. Compiled for bare metal (as we're doing here), it also leaves some support functions undefined (e.g. for writing to stdout or stderr), expecting the user to provide them. So I've also added a small source directory containing the missing pieces. That builds two additional libraries, -lcrt0 and -lsemihost. At present, I haven't set up a llvm-libc specific ld script, and there's no code in my new crt0 that copies RW data out of ROM (as there is in picolibc). The only definition required by the crt0 is `__stack`, which you can define via an ld script of your own if you want, or directly on the ld.lld command ilne if you prefer. --- CMakeLists.txt | 241 ++++++++++++++++++++++++++----- README.md | 2 +- cmake/generate_version_txt.cmake | 14 +- docs/llvmlibc.md | 66 +++++++++ llvmlibc-support/CMakeLists.txt | 46 ++++++ llvmlibc-support/crt0.c | 34 +++++ llvmlibc-support/exit.c | 32 ++++ llvmlibc-support/init.c | 39 +++++ llvmlibc-support/platform.h | 34 +++++ llvmlibc-support/semihost.h | 117 +++++++++++++++ llvmlibc-support/stdio_read.c | 33 +++++ llvmlibc-support/stdio_write.c | 32 ++++ llvmlibc.cfg | 1 + 13 files changed, 650 insertions(+), 41 deletions(-) create mode 100644 docs/llvmlibc.md create mode 100644 llvmlibc-support/CMakeLists.txt create mode 100644 llvmlibc-support/crt0.c create mode 100644 llvmlibc-support/exit.c create mode 100644 llvmlibc-support/init.c create mode 100644 llvmlibc-support/platform.h create mode 100644 llvmlibc-support/semihost.h create mode 100644 llvmlibc-support/stdio_read.c create mode 100644 llvmlibc-support/stdio_write.c create mode 100644 llvmlibc.cfg diff --git a/CMakeLists.txt b/CMakeLists.txt index 73cd4246..dd5c96c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -140,7 +140,7 @@ set(LLVM_TOOLCHAIN_C_LIBRARY "Which C library to use." ) set_property(CACHE LLVM_TOOLCHAIN_C_LIBRARY - PROPERTY STRINGS picolibc newlib) + PROPERTY STRINGS picolibc newlib llvmlibc) # Previously, the LLVM_TOOLCHAIN_LIBRARY_OVERLAY_INSTALL option was # called LLVM_TOOLCHAIN_NEWLIB_OVERLAY_INSTALL. Detect a setting of @@ -153,7 +153,7 @@ else() set(overlay_install_default OFF) endif() option(LLVM_TOOLCHAIN_LIBRARY_OVERLAY_INSTALL - "Make cpack build an overlay package that can be unpacked over the main toolchain to install a secondary set of libraries based on newlib." + "Make cpack build an overlay package that can be unpacked over the main toolchain to install a secondary set of libraries based on newlib or llvm-libc." ${overlay_install_default}) if(LLVM_TOOLCHAIN_LIBRARY_OVERLAY_INSTALL) if(LLVM_TOOLCHAIN_C_LIBRARY STREQUAL "picolibc") @@ -268,7 +268,9 @@ function(read_repo_version output_variable_prefix repo) set(${output_variable_prefix}_SHALLOW "${shallow}" PARENT_SCOPE) endfunction() read_repo_version(llvmproject llvm-project) -read_repo_version(${LLVM_TOOLCHAIN_C_LIBRARY} ${LLVM_TOOLCHAIN_C_LIBRARY}) +if(NOT (LLVM_TOOLCHAIN_C_LIBRARY STREQUAL llvmlibc)) # libc in a separate repo? + read_repo_version(${LLVM_TOOLCHAIN_C_LIBRARY} ${LLVM_TOOLCHAIN_C_LIBRARY}) +endif() # The patches are generated from custom branch, with followin command: # git format-patch -k origin/main @@ -319,7 +321,9 @@ FetchContent_Declare(newlib ) FetchContent_MakeAvailable(llvmproject) -FetchContent_MakeAvailable(${LLVM_TOOLCHAIN_C_LIBRARY}) +if(NOT (LLVM_TOOLCHAIN_C_LIBRARY STREQUAL llvmlibc)) # libc in a separate repo? + FetchContent_MakeAvailable(${LLVM_TOOLCHAIN_C_LIBRARY}) +endif() # We generally want to install to a local directory to see what the # output will look like rather than install into the system, so change @@ -334,6 +338,10 @@ if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) ) endif() +# Whether to try to build C++ libraries. (We can't currently do this +# for all choices of C library.) +set(CXX_LIBS ON) + if(LLVM_TOOLCHAIN_C_LIBRARY STREQUAL newlib) install( FILES @@ -350,6 +358,42 @@ install( COMPONENT llvm-toolchain-omax-cfg ) +if(LLVM_TOOLCHAIN_C_LIBRARY STREQUAL llvmlibc) + install( + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/llvmlibc.cfg + DESTINATION bin + COMPONENT llvm-toolchain-llvmlibc-configs + ) + + # We aren't yet able to build C++ libraries to go with llvm-libc + set(CXX_LIBS OFF) + + # We need to build libc-hdrgen + ExternalProject_Add( + libc_hdrgen + SOURCE_DIR ${llvmproject_SOURCE_DIR}/llvm + DEPENDS ${lib_tool_dependencies} + CMAKE_ARGS + -DLLVM_ENABLE_RUNTIMES=libc + -DLLVM_LIBC_FULL_BUILD=ON + -DCMAKE_BUILD_TYPE=Debug + STEP_TARGETS build install + BUILD_COMMAND ${CMAKE_COMMAND} --build . --target libc-hdrgen + INSTALL_COMMAND ${CMAKE_COMMAND} -E true + # Always run the build command so that incremental builds are correct. + BUILD_ALWAYS TRUE + CONFIGURE_HANDLED_BY_BUILD TRUE + ) + ExternalProject_Get_property(libc_hdrgen BINARY_DIR) + set(LIBC_HDRGEN ${BINARY_DIR}/bin/libc-hdrgen${CMAKE_EXECUTABLE_SUFFIX}) + + # Add an empty check target, to simplify the logic below that expects to + # find one for every libc type. We have no current setup to run the LLVM + # libc test suite. + add_custom_target(check-llvmlibc) +endif() + add_subdirectory( ${llvmproject_SOURCE_DIR}/llvm llvm ) @@ -396,7 +440,7 @@ set(CPACK_ARCHIVE_COMPONENT_INSTALL TRUE) # Don't create a separate archive for each component. set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE) # When extracting the files put them in an ArmCompiler-.../ directory. -# Exception: the newlib overlay package does not do this, because it has +# Exception: the overlay packages do not do this, because they have # to be able to unpack over the top of an existing installation on all # platforms, and each platform has a different top-level directory name. if(LLVM_TOOLCHAIN_LIBRARY_OVERLAY_INSTALL) @@ -479,7 +523,7 @@ add_custom_target( -DLLVMEmbeddedToolchainForArm_VERSION=${LLVMEmbeddedToolchainForArm_VERSION} -DLLVMEmbeddedToolchainForArm_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR} -Dllvmproject_SOURCE_DIR=${llvmproject_SOURCE_DIR} - # only one of picolibc and newlib source dirs is needed, but easiest to + # at most one of picolibc and newlib source dirs is needed, but easiest to # specify both definitions -Dpicolibc_SOURCE_DIR=${picolibc_SOURCE_DIR} -Dnewlib_SOURCE_DIR=${newlib_SOURCE_DIR} @@ -817,6 +861,112 @@ function( ) endfunction() +function( + add_llvmlibc + directory + variant + target_triple + flags + test_executor_params + default_boot_flash_addr + default_boot_flash_size + default_flash_addr + default_flash_size + default_ram_addr + default_ram_size + default_stack_size +) + get_runtimes_flags("${directory}" "${flags}") + + set(runtimes_flags "${runtimes_flags} -Wno-error=atomic-alignment") + + set(common_cmake_args + -DCMAKE_AR=${LLVM_BINARY_DIR}/bin/llvm-ar${CMAKE_EXECUTABLE_SUFFIX} + -DCMAKE_ASM_COMPILER=${LLVM_BINARY_DIR}/bin/clang${CMAKE_EXECUTABLE_SUFFIX} + -DCMAKE_ASM_COMPILER_TARGET=${target_triple} + -DCMAKE_ASM_FLAGS=${runtimes_flags} + -DCMAKE_BUILD_TYPE=Release + -DCMAKE_CXX_COMPILER=${LLVM_BINARY_DIR}/bin/clang++${CMAKE_EXECUTABLE_SUFFIX} + -DCMAKE_CXX_COMPILER_TARGET=${target_triple} + -DCMAKE_CXX_FLAGS=${runtimes_flags} + -DCMAKE_C_COMPILER=${LLVM_BINARY_DIR}/bin/clang${CMAKE_EXECUTABLE_SUFFIX} + -DCMAKE_C_COMPILER_TARGET=${target_triple} + -DCMAKE_C_FLAGS=${runtimes_flags} + -DCMAKE_INSTALL_MESSAGE=${CMAKE_INSTALL_MESSAGE} + -DCMAKE_INSTALL_PREFIX= + -DCMAKE_NM=${LLVM_BINARY_DIR}/bin/llvm-nm${CMAKE_EXECUTABLE_SUFFIX} + -DCMAKE_RANLIB=${LLVM_BINARY_DIR}/bin/llvm-ranlib${CMAKE_EXECUTABLE_SUFFIX} + # Let CMake know we're cross-compiling + -DCMAKE_SYSTEM_NAME=Generic + -DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY + ) + + ExternalProject_Add( + llvmlibc_${variant} + SOURCE_DIR ${llvmproject_SOURCE_DIR}/runtimes + PREFIX llvmlibc/${variant} + INSTALL_DIR llvmlibc/${variant}/install + DEPENDS ${lib_tool_dependencies} ${libc_target} libc_hdrgen + CMAKE_ARGS + ${common_cmake_args} + -DLIBC_TARGET_TRIPLE=${target_triple} + -DLIBC_HDRGEN_EXE=${LIBC_HDRGEN} + -DLIBC_TARGET_OS=baremetal + -DLLVM_CMAKE_DIR=${LLVM_BINARY_DIR}/lib/cmake/llvm + -DLLVM_ENABLE_PER_TARGET_RUNTIME_DIR=ON + -DLLVM_ENABLE_RUNTIMES=libc + -DLLVM_INCLUDE_TESTS=OFF # I haven't yet got the tests to build + -DLLVM_LIBC_FULL_BUILD=ON + STEP_TARGETS build install + USES_TERMINAL_CONFIGURE FALSE + USES_TERMINAL_BUILD TRUE + USES_TERMINAL_INSTALL TRUE + USES_TERMINAL_TEST TRUE + LIST_SEPARATOR , + # Always run the build command so that incremental builds are correct. + BUILD_ALWAYS TRUE + CONFIGURE_HANDLED_BY_BUILD TRUE + INSTALL_COMMAND ${CMAKE_COMMAND} --install . + # Copy llvm-libc lib directory, moving libraries out of their + # target-specific subdirectory. + COMMAND + ${CMAKE_COMMAND} + -E copy_directory + /lib/${target_triple} + "${LLVM_BINARY_DIR}/${directory}/lib" + # And copy the include directory, which is already arranged right. + COMMAND + ${CMAKE_COMMAND} + -E copy_directory + /include + "${LLVM_BINARY_DIR}/${directory}/include" + ) + + ExternalProject_Add( + llvmlibc-support_${variant} + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/llvmlibc-support + PREFIX llvmlibc-support/${variant} + INSTALL_DIR "${LLVM_BINARY_DIR}/${directory}" + DEPENDS ${lib_tool_dependencies} llvmlibc_${variant}-install + CMAKE_ARGS ${common_cmake_args} + STEP_TARGETS build install + USES_TERMINAL_CONFIGURE FALSE + USES_TERMINAL_BUILD TRUE + USES_TERMINAL_INSTALL TRUE + USES_TERMINAL_TEST TRUE + LIST_SEPARATOR , + # Always run the build command so that incremental builds are correct. + BUILD_ALWAYS TRUE + CONFIGURE_HANDLED_BY_BUILD TRUE + ) + + add_dependencies( + llvm-toolchain-runtimes + llvmlibc_${variant} + llvmlibc-support_${variant} + ) +endfunction() + macro( add_libc directory @@ -869,6 +1019,21 @@ macro( "${default_ram_size}" "${default_stack_size}" ) + elseif(LLVM_TOOLCHAIN_C_LIBRARY STREQUAL llvmlibc) + add_llvmlibc( + "${directory}" + "${variant}" + "${target_triple}" + "${flags}" + "${test_executor_params}" + "${default_boot_flash_addr}" + "${default_boot_flash_size}" + "${default_flash_addr}" + "${default_flash_size}" + "${default_ram_addr}" + "${default_ram_size}" + "${default_stack_size}" + ) endif() endmacro() @@ -1231,25 +1396,29 @@ function(add_library_variant target_arch) "${VARIANT_COMPILE_FLAGS}" "${lit_test_executor}" "${LLVM_TOOLCHAIN_C_LIBRARY}_${variant}-install" - ) - add_libcxx_libcxxabi_libunwind( - "${directory}" - "${variant}" - "${target_triple}" - "${VARIANT_COMPILE_FLAGS}" - "${lit_test_executor}" - "${LLVM_TOOLCHAIN_C_LIBRARY}_${variant}-install" - "${${LLVM_TOOLCHAIN_C_LIBRARY}_specific_runtimes_options}" - ${VARIANT_ENABLE_EXCEPTIONS} - ${VARIANT_ENABLE_RTTI} - ) + ) + if(CXX_LIBS) + add_libcxx_libcxxabi_libunwind( + "${directory}" + "${variant}" + "${target_triple}" + "${VARIANT_COMPILE_FLAGS}" + "${lit_test_executor}" + "${LLVM_TOOLCHAIN_C_LIBRARY}_${variant}-install" + "${${LLVM_TOOLCHAIN_C_LIBRARY}_specific_runtimes_options}" + ${VARIANT_ENABLE_EXCEPTIONS} + ${VARIANT_ENABLE_RTTI} + ) + endif() if(VARIANT_COMPILE_FLAGS MATCHES "-march=armv8") message("C++ runtime libraries tests disabled for ${variant}") else() add_custom_target(check-llvm-toolchain-runtimes-${variant}) add_dependencies(check-llvm-toolchain-runtimes check-llvm-toolchain-runtimes-${variant}) add_compiler_rt_tests("${variant}") - add_libcxx_libcxxabi_libunwind_tests("${variant}") + if(CXX_LIBS) + add_libcxx_libcxxabi_libunwind_tests("${variant}") + endif() endif() endif() @@ -1333,21 +1502,25 @@ set(multilib_yaml_content "") # For most variants, the "flash" memory is placed in address range, where # simulated boards have RAM. This is because code for some tests does not fit # the real flash. -add_library_variants_for_cpu( - aarch64 - COMPILE_FLAGS "-march=armv8-a" - MULTILIB_FLAGS "--target=aarch64-unknown-none-elf" - PICOLIBC_BUILD_TYPE "release" - QEMU_MACHINE "virt" - QEMU_CPU "cortex-a57" - BOOT_FLASH_ADDRESS 0x40000000 - BOOT_FLASH_SIZE 0x1000 - FLASH_ADDRESS 0x40001000 - FLASH_SIZE 0xfff000 - RAM_ADDRESS 0x41000000 - RAM_SIZE 0x1000000 - STACK_SIZE 8K -) +if(NOT (LLVM_TOOLCHAIN_C_LIBRARY STREQUAL llvmlibc)) + # llvm-libc doesn't have a bare-metal configuration for AArch64, so we + # leave out the AArch64 library if we're building that libc. + add_library_variants_for_cpu( + aarch64 + COMPILE_FLAGS "-march=armv8-a" + MULTILIB_FLAGS "--target=aarch64-unknown-none-elf" + PICOLIBC_BUILD_TYPE "release" + QEMU_MACHINE "virt" + QEMU_CPU "cortex-a57" + BOOT_FLASH_ADDRESS 0x40000000 + BOOT_FLASH_SIZE 0x1000 + FLASH_ADDRESS 0x40001000 + FLASH_SIZE 0xfff000 + RAM_ADDRESS 0x41000000 + RAM_SIZE 0x1000000 + STACK_SIZE 8K + ) +endif() # For AArch32, clang uses different defaults for FPU selection than GCC, both # when "+fp" or "+fp.dp" are used and when no FPU specifier is provided in # "-march=". Using "-mfpu=" explicitly. diff --git a/README.md b/README.md index 802085fc..0bdb97b8 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ bare-metal LLVM based toolchain targeting Arm based on: * libc++abi * libc++ * compiler-rt -* picolibc, or optionally newlib +* picolibc, or optionally newlib or LLVM's libc ## Goal diff --git a/cmake/generate_version_txt.cmake b/cmake/generate_version_txt.cmake index b36460cb..a3f70327 100644 --- a/cmake/generate_version_txt.cmake +++ b/cmake/generate_version_txt.cmake @@ -23,12 +23,14 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY ) -execute_process( - COMMAND git -C ${${LLVM_TOOLCHAIN_C_LIBRARY}_SOURCE_DIR} rev-parse HEAD - OUTPUT_VARIABLE ${LLVM_TOOLCHAIN_C_LIBRARY}_COMMIT - OUTPUT_STRIP_TRAILING_WHITESPACE - COMMAND_ERROR_IS_FATAL ANY -) +if(NOT (LLVM_TOOLCHAIN_C_LIBRARY STREQUAL llvmlibc)) # libc in a separate repo? + execute_process( + COMMAND git -C ${${LLVM_TOOLCHAIN_C_LIBRARY}_SOURCE_DIR} rev-parse HEAD + OUTPUT_VARIABLE ${LLVM_TOOLCHAIN_C_LIBRARY}_COMMIT + OUTPUT_STRIP_TRAILING_WHITESPACE + COMMAND_ERROR_IS_FATAL ANY + ) +endif() configure_file( ${CMAKE_CURRENT_LIST_DIR}/VERSION.txt.in diff --git a/docs/llvmlibc.md b/docs/llvmlibc.md new file mode 100644 index 00000000..b9cc6fc8 --- /dev/null +++ b/docs/llvmlibc.md @@ -0,0 +1,66 @@ +# Experimental LLVM libc support + +LLVM Embedded Toolchain for Arm uses +[`picolibc`](https://github.com/picolibc/picolibc) as the standard C +library. For experimental and evaluation purposes, you can instead +choose to use the LLVM project's own C library. + +> **NOTE:** `llvmlibc` support in LLVM Embedded Toolchain for Arm is +> an experimental technology preview, with significant limitations. + +## Building the toolchain with LLVM libc + +> **NOTE:** Building the LLVM libc package is only supported on Linux +> and macOS. + +Configure the toolchain with the CMake setting +`-DLLVM_TOOLCHAIN_C_LIBRARY=llvmlibc` to build a version of the +toolchain based on LLVM libc. + +If you also add `-DLLVM_TOOLCHAIN_LIBRARY_OVERLAY_INSTALL=on` then the +`package-llvm-toolchain` CMake target will generate an overlay package +similar to the [newlib overlay +package](https://github.com/ARM-software/LLVM-embedded-toolchain-for-Arm/blob/main/docs/newlib.md). +If you unpack this over an existing installation of the toolchain, +then you can switch to LLVM libc by adding `--config=llvmlibc.cfg` on +the command line. + +## Using LLVM libc + +To compile a program with this LLVM libc, you must provide the +following command line options, in addition to `--target`, `-march` or +`-mcpu`, and the input and output files: + +* `--config=llvmlibc.cfg` if you are using LLVM libc as an overlay + package (but you do not need this if you have built the whole + toolchain with only LLVM libc) + +* `-lcrt0` to include a library defining the `_start` symbol (or else + provide that symbol yourself) + +* `-lsemihost` to include a library that implements porting functions + in LLVM's libc in terms of the Arm semihosting API (or else provide + an alternative implementation of those functions yourself) + +* `-Wl,--defsym=__stack=0x`_nnnnnn_ to define the starting value of + your stack pointer. Alternatively, use a linker script that defines + the symbol `__stack` in addition to whatever other memory layout you + want. + +For example: + +``` +clang --config=llvmlibc.cfg --target=arm-none-eabi -march=armv7m -o hello hello.c -lsemihost -lcrt0 -Wl,--defsym=__stack=0x200000 +``` + +## Limitations of LLVM libc in LLVM Embedded Toolchain for Arm + +At present, this toolchain only builds LLVM libc for AArch32, not for +AArch64. + +At present, this toolchain does not build any C++ libraries to go with +LLVM libc. + +At the time of writing this (2024-07), LLVM libc is a work in +progress. It is incomplete: not all standard C library functionality +is provided. diff --git a/llvmlibc-support/CMakeLists.txt b/llvmlibc-support/CMakeLists.txt new file mode 100644 index 00000000..0e61b37f --- /dev/null +++ b/llvmlibc-support/CMakeLists.txt @@ -0,0 +1,46 @@ +# +# Copyright (c) 2022, Arm Limited and affiliates. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# This directory builds two additional library files to go with llvm-libc: +# +# libcrt0.a contains an implementation of the _start() default entry point, +# which sets up the stack and does any necessary initialization before +# calling main(). +# +# libsemihost.a implements llvm-libc's porting function API such as +# __llvm_libc_stdio_write, in terms of the Arm semihosting system. +# +# To use LLVM libc in a semihosting context, include both of these +# libraries. To use it in a different context, you will need to +# reimplement the same functions that libsemihost.a provides, but +# libcrt0.a might still be useful. + +cmake_minimum_required(VERSION 3.20.0) +project(llvmlibc-support LANGUAGES C ASM) + +add_library(semihost STATIC + init.c + exit.c + stdio_read.c + stdio_write.c +) + +add_library(crt0 STATIC + crt0.c +) + +install(TARGETS semihost crt0) diff --git a/llvmlibc-support/crt0.c b/llvmlibc-support/crt0.c new file mode 100644 index 00000000..b2f11693 --- /dev/null +++ b/llvmlibc-support/crt0.c @@ -0,0 +1,34 @@ +// +// Copyright (c) 2022, Arm Limited and affiliates. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include +#include + +#include "platform.h" + +int main(int, char **); + +__attribute__((used)) static void c_startup(void) { + _platform_init(); + _Exit(main(0, NULL)); +} + +extern long __stack[]; +__attribute__((naked)) void _start(void) { + __asm__("mov sp, %0" : : "r"(__stack)); + __asm__("b c_startup"); +} diff --git a/llvmlibc-support/exit.c b/llvmlibc-support/exit.c new file mode 100644 index 00000000..d784a597 --- /dev/null +++ b/llvmlibc-support/exit.c @@ -0,0 +1,32 @@ +// +// Copyright (c) 2022, Arm Limited and affiliates. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "semihost.h" + +void __llvm_libc_exit(int status) { + +#if defined(__ARM_64BIT_STATE) && __ARM_64BIT_STATE + size_t block[2]; + block[0] = ADP_Stopped_ApplicationExit; + block[1] = code; + semihosting_call(SYS_EXIT, block); +#else + semihosting_call(SYS_EXIT, (const void *)ADP_Stopped_ApplicationExit); +#endif + + __builtin_unreachable(); /* semihosting call doesn't return */ +} diff --git a/llvmlibc-support/init.c b/llvmlibc-support/init.c new file mode 100644 index 00000000..bbe8d3c8 --- /dev/null +++ b/llvmlibc-support/init.c @@ -0,0 +1,39 @@ +// +// Copyright (c) 2022, Arm Limited and affiliates. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include + +#include "platform.h" +#include "semihost.h" + +struct __llvm_libc_stdio_cookie __llvm_libc_stdin_cookie; +struct __llvm_libc_stdio_cookie __llvm_libc_stdout_cookie; +struct __llvm_libc_stdio_cookie __llvm_libc_stderr_cookie; + +static void stdio_open(struct __llvm_libc_stdio_cookie *cookie, int mode) { + size_t args[3]; + args[0] = (size_t) ":tt"; + args[1] = (size_t)mode; + args[2] = (size_t)3; /* name length */ + cookie->handle = semihosting_call(SYS_OPEN, args); +} + +void _platform_init(void) { + stdio_open(&__llvm_libc_stdin_cookie, OPENMODE_R); + stdio_open(&__llvm_libc_stdout_cookie, OPENMODE_W); + stdio_open(&__llvm_libc_stderr_cookie, OPENMODE_W); +} diff --git a/llvmlibc-support/platform.h b/llvmlibc-support/platform.h new file mode 100644 index 00000000..668c3304 --- /dev/null +++ b/llvmlibc-support/platform.h @@ -0,0 +1,34 @@ +// +// Copyright (c) 2022, Arm Limited and affiliates. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// This header file defines the interface between libcrt0.a, which defines +// the program entry point, and libsemihost.a, which implements the +// LLVM-libc porting functions in terms of semihosting. If you replace +// libsemihost.a with something else, this header file shows how to make +// that work with libcrt0.a. + +#ifndef LLVMET_LLVMLIBC_SUPPORT_PLATFORM_H +#define LLVMET_LLVMLIBC_SUPPORT_PLATFORM_H + +// libcrt0.a will call this function after the stack pointer is +// initialized. If any setup specific to the libc porting layer is +// needed, this is where to do it. For example, in semihosting, the +// standard I/O handles must be opened via the SYS_OPEN operation, and +// this function is where libsemihost.a does it. +void _platform_init(void); + +#endif // LLVMET_LLVMLIBC_SUPPORT_PLATFORM_H diff --git a/llvmlibc-support/semihost.h b/llvmlibc-support/semihost.h new file mode 100644 index 00000000..71c80949 --- /dev/null +++ b/llvmlibc-support/semihost.h @@ -0,0 +1,117 @@ +// +// Copyright (c) 2022, Arm Limited and affiliates. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// This header file provides internal definitions for libsemihost.a, +// including an inline function to make a semihosting call, and a lot +// of constant definitions. + +#ifndef LLVMET_LLVMLIBC_SUPPORT_SEMIHOST_H +#define LLVMET_LLVMLIBC_SUPPORT_SEMIHOST_H + +#include + +#if __ARM_64BIT_STATE +# define ARG_REG_0 "x0" +# define ARG_REG_1 "x1" +#else +# define ARG_REG_0 "r0" +# define ARG_REG_1 "r1" +#endif + +#if __ARM_64BIT_STATE // A64 +# define SEMIHOST_INSTRUCTION "hlt #0xf000" +#elif defined(__thumb__) // T32 +# if defined(__ARM_ARCH_PROFILE) && __ARM_ARCH_PROFILE == 'M' +# define SEMIHOST_INSTRUCTION "bkpt #0xAB" +# elif defined(HLT_SEMIHOSTING) +# define SEMIHOST_INSTRUCTION ".inst.n 0xbabc" // hlt #60 +# else +# define SEMIHOST_INSTRUCTION "svc 0xab" +# endif +#else // A32 +# if defined(HLT_SEMIHOSTING) +# define SEMIHOST_INSTRUCTION ".inst 0xe10f0070" // hlt #0xf000 +# else +# define SEMIHOST_INSTRUCTION "svc 0x123456" +# endif +#endif + +__attribute__((always_inline)) +static long semihosting_call(long val, const void *ptr) { + register long v __asm__(ARG_REG_0) = val; + register const void *p __asm__(ARG_REG_1) = ptr; + __asm__ __volatile__(SEMIHOST_INSTRUCTION + : "+r"(v), "+r"(p) + : + : "memory", "cc"); + return v; +} + +#define SYS_CLOCK 0x10 +#define SYS_CLOSE 0x02 +#define SYS_ELAPSED 0x30 +#define SYS_ERRNO 0x13 +#define SYS_EXIT 0x18 +#define SYS_EXIT_EXTENDED 0x20 +#define SYS_FLEN 0x0c +#define SYS_GET_CMDLINE 0x15 +#define SYS_HEAPINFO 0x16 +#define SYS_ISERROR 0x08 +#define SYS_ISTTY 0x09 +#define SYS_OPEN 0x01 +#define SYS_READ 0x06 +#define SYS_READC 0x07 +#define SYS_REMOVE 0x0e +#define SYS_RENAME 0x0f +#define SYS_SEEK 0x0a +#define SYS_SYSTEM 0x12 +#define SYS_TICKFREQ 0x31 +#define SYS_TIME 0x11 +#define SYS_TMPNAM 0x0d +#define SYS_WRITE0 0x04 +#define SYS_WRITE 0x05 +#define SYS_WRITEC 0x03 + +#define ADP_Stopped_BranchThroughZero 0x20000 +#define ADP_Stopped_UndefinedInstr 0x20001 +#define ADP_Stopped_SoftwareInterrupt 0x20002 +#define ADP_Stopped_PrefetchAbort 0x20003 +#define ADP_Stopped_DataAbort 0x20004 +#define ADP_Stopped_AddressException 0x20005 +#define ADP_Stopped_IRQ 0x20006 +#define ADP_Stopped_FIQ 0x20007 +#define ADP_Stopped_BreakPoint 0x20020 +#define ADP_Stopped_WatchPoint 0x20021 +#define ADP_Stopped_StepComplete 0x20022 +#define ADP_Stopped_RunTimeErrorUnknown 0x20023 +#define ADP_Stopped_InternalError 0x20024 +#define ADP_Stopped_UserInterruption 0x20025 +#define ADP_Stopped_ApplicationExit 0x20026 +#define ADP_Stopped_StackOverflow 0x20027 +#define ADP_Stopped_DivisionByZero 0x20028 +#define ADP_Stopped_OSSpecific 0x20029 + +/* SYS_OPEN modes must be one of R,W,A, plus an optional B and optional PLUS */ +#define OPENMODE_R 0 +#define OPENMODE_W 4 +#define OPENMODE_A 8 +#define OPENMODE_B 1 +#define OPENMODE_PLUS 2 + +struct __llvm_libc_stdio_cookie { int handle; }; + +#endif // LLVMET_LLVMLIBC_SUPPORT_SEMIHOST_H diff --git a/llvmlibc-support/stdio_read.c b/llvmlibc-support/stdio_read.c new file mode 100644 index 00000000..aa2c00fe --- /dev/null +++ b/llvmlibc-support/stdio_read.c @@ -0,0 +1,33 @@ +// +// Copyright (c) 2022, Arm Limited and affiliates. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include + +#include "semihost.h" + +ssize_t __llvm_libc_stdio_read(struct __llvm_libc_stdio_cookie *cookie, + const char *buf, size_t size) { + size_t args[4]; + args[0] = (size_t)cookie->handle; + args[1] = (size_t)buf; + args[2] = (size_t)size; + args[3] = 0; + ssize_t retval = semihosting_call(SYS_READ, args); + if (retval >= 0) + retval = size - retval; + return retval; +} diff --git a/llvmlibc-support/stdio_write.c b/llvmlibc-support/stdio_write.c new file mode 100644 index 00000000..9b7741fd --- /dev/null +++ b/llvmlibc-support/stdio_write.c @@ -0,0 +1,32 @@ +// +// Copyright (c) 2022, Arm Limited and affiliates. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include + +#include "semihost.h" + +ssize_t __llvm_libc_stdio_write(struct __llvm_libc_stdio_cookie *cookie, + const char *buf, size_t size) { + size_t args[4]; + args[0] = (size_t)cookie->handle; + args[1] = (size_t)buf; + args[2] = (size_t)size; + ssize_t retval = semihosting_call(SYS_WRITE, args); + if (retval >= 0) + retval = size - retval; + return retval; +} diff --git a/llvmlibc.cfg b/llvmlibc.cfg new file mode 100644 index 00000000..14aba3bf --- /dev/null +++ b/llvmlibc.cfg @@ -0,0 +1 @@ +--sysroot /../lib/clang-runtimes/llvmlibc