From fd29473deffe583bc6c90f8704dbd463d487c4f5 Mon Sep 17 00:00:00 2001 From: Khalil Estell Date: Tue, 9 Dec 2025 08:58:46 -0800 Subject: [PATCH] :rocket: Add LLVM support --- CMakeLists.txt | 20 ++- conanfile.py | 67 ++++---- demos/main.cpp | 16 +- demos/platforms/lpc4078.cpp | 1 - demos/platforms/stm32f103c8.cpp | 1 - include/libhal-exceptions/control.hpp | 53 ------ include/libhal-exceptions/module.cppm | 8 + include/libhal-exceptions/new.hpp | 19 +++ src/arm_cortex/estell/exception.cpp | 155 +++++++---------- src/arm_cortex/estell/internal.hpp | 28 ++- src/arm_cortex/estell/libunwind_wrappers.cpp | 33 ++++ src/builtin/gcc/impl.cpp | 171 ------------------- src/builtin/gcc/override.cpp | 38 +++++ src/builtin/llvm/override.cpp | 21 +++ test_package/CMakeLists.txt | 21 --- test_package/conanfile.py | 6 +- test_package/main.cpp | 27 ++- tests/control.test.cpp | 22 +-- 18 files changed, 277 insertions(+), 430 deletions(-) delete mode 100644 include/libhal-exceptions/control.hpp create mode 100644 include/libhal-exceptions/module.cppm create mode 100644 include/libhal-exceptions/new.hpp delete mode 100644 src/builtin/gcc/impl.cpp create mode 100644 src/builtin/gcc/override.cpp create mode 100644 src/builtin/llvm/override.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c6b9fe..bd7e11b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,11 +14,16 @@ cmake_minimum_required(VERSION 3.15) -project(libhal-exceptions LANGUAGES CXX) +set(CMAKE_COLOR_DIAGNOSTICS ON) +project(hal-exceptions LANGUAGES CXX) + +message("Runtime: '${RUNTIME}', selected") if("${RUNTIME}" STREQUAL "ARM_CORTEX_GCC") -set(SOURCE_LIST src/builtin/gcc/impl.cpp) +set(SOURCE_LIST src/builtin/gcc/override.cpp) +elseif("${RUNTIME}" STREQUAL "ARM_CORTEX_LLVM") +set(SOURCE_LIST src/builtin/llvm/override.cpp) elseif("${RUNTIME}" STREQUAL "ARM_CORTEX_ESTELL") set(SOURCE_LIST src/arm_cortex/estell/exception.cpp @@ -29,7 +34,7 @@ message(FATAL "Invalid Exception RUNTIME: '${RUNTIME}' provided!") endif() libhal_make_library( - LIBRARY_NAME libhal-exceptions + LIBRARY_NAME hal-exceptions SOURCES ${SOURCE_LIST} @@ -39,13 +44,16 @@ libhal_make_library( LINK_LIBRARIES libhal::util + + COMPILE_FLAGS + -Wno-error,-Wgnu-label-as-value ) -target_compile_options(libhal-exceptions PRIVATE -save-temps=obj) +target_compile_options(hal-exceptions PRIVATE -save-temps=obj) if(NOT ${CMAKE_CROSSCOMPILING}) libhal_unit_test( - LIBRARY_NAME libhal-exceptions + LIBRARY_NAME hal-exceptions SOURCES tests/main.test.cpp @@ -58,4 +66,4 @@ libhal_unit_test( LINK_LIBRARIES libhal::util ) -endif() \ No newline at end of file +endif() diff --git a/conanfile.py b/conanfile.py index 037d373..acfd628 100644 --- a/conanfile.py +++ b/conanfile.py @@ -16,7 +16,8 @@ from conan.tools.cmake import CMake, cmake_layout from conan.tools.files import copy from conan.tools.build import check_min_cppstd -import os +from conan.errors import ConanException +from pathlib import Path required_conan_version = ">=2.0.14" @@ -62,12 +63,15 @@ def _is_arm_cortex(self): @property def _runtime_select(self): - if self._is_arm_cortex and self.options.runtime == "builtin": - return "ARM_CORTEX_GCC" - elif self._is_arm_cortex and self.options.runtime == "estell": + if self.options.runtime == "builtin": + if self.settings.compiler == "gcc": + return "ARM_CORTEX_GCC" + if self.settings.compiler == "clang": + return "ARM_CORTEX_LLVM" + elif self.options.runtime == "estell": return "ARM_CORTEX_ESTELL" - else: - return "ARM_CORTEX_GCC" + raise ConanException( + f"Invalid runtime ({self.options.runtime}) & compiler ({self.settings.compiler}) selected") def validate(self): if self.settings.get_safe("compiler.cppstd"): @@ -77,7 +81,7 @@ def layout(self): cmake_layout(self) def build_requirements(self): - self.tool_requires("cmake/3.27.1") + self.tool_requires("cmake/[>=3.38.0 <5.0.0]") self.tool_requires("libhal-cmake-util/[^4.0.3]") self.test_requires("boost-ext-ut/2.1.0") @@ -93,57 +97,58 @@ def build(self): def package(self): copy(self, "LICENSE", - dst=os.path.join(self.package_folder, "licenses"), + dst=Path(self.package_folder) / "licenses", src=self.source_folder) copy(self, "*.h", - dst=os.path.join(self.package_folder, "include"), - src=os.path.join(self.source_folder, "include")) + dst=Path(self.package_folder) / "include", + src=Path(self.source_folder) / "include") copy(self, "*.hpp", - dst=os.path.join(self.package_folder, "include"), - src=os.path.join(self.source_folder, "include")) + dst=Path(self.package_folder) / "include", + src=Path(self.source_folder) / "include") copy(self, "*.ld", - dst=os.path.join(self.package_folder, "linker_scripts"), - src=os.path.join(self.source_folder, "linker_scripts")) + dst=Path(self.package_folder) / "linker_scripts", + src=Path(self.source_folder) / "linker_scripts") cmake = CMake(self) cmake.install() def package_info(self): - self.cpp_info.libs = ["libhal-exceptions"] + self.cpp_info.libs = ["hal-exceptions"] self.cpp_info.set_property("cmake_target_name", "libhal::exceptions") - lib_path = os.path.join(self.package_folder, - 'lib', 'liblibhal-exceptions.a') using_wrap = False + self.cpp_info.defines = [] + if self.options.runtime == "estell": using_wrap = True # If the platform matches the linker script, just use that linker # script self.cpp_info.exelinkflags.extend([ "-fexceptions", - "-L" + os.path.join(self.package_folder, "linker_scripts"), - '-Wl,-Tarm-none-eabi-gcc-14.2_discard.ld', + "-L" + str(Path(self.package_folder) / "linker_scripts"), + # '-Wl,-Tarm-none-eabi-gcc-14.2_discard.ld', "-Wl,--wrap=__cxa_throw", "-Wl,--wrap=__cxa_rethrow", "-Wl,--wrap=__cxa_end_catch", "-Wl,--wrap=__cxa_begin_catch", "-Wl,--wrap=__cxa_end_cleanup", "-Wl,--wrap=_Unwind_Resume", - # "-Wl,--wrap=__gnu_unwind_pr_common", - # "-Wl,--wrap=__aeabi_unwind_cpp_pr0", - # "-Wl,--wrap=__aeabi_unwind_cpp_pr1", - # "-Wl,--wrap=__aeabi_unwind_cpp_pr2", - # "-Wl,--wrap=_sig_func", - # "-Wl,--wrap=__gxx_personality_v0", - # "-Wl,--wrap=__gcc_personality_v0", - # "-Wl,--wrap=deregister_tm_clones", - # "-Wl,--wrap=register_tm_clones", + ]) + self.cpp_info.defines.append("LIBHAL_ESTELL_EXCEPTIONS") + + if self.settings.compiler == "clang": + using_wrap = True + # If the platform matches the linker script, just use that linker + # script + self.cpp_info.exelinkflags.extend([ + "-fexceptions", + "-Wl,--wrap=__cxa_terminate_handler", ]) # Keep this for now, will update this for the runtime select - if self._is_arm_cortex: + if self._is_arm_cortex and self.settings.compiler == "gcc": using_wrap = True self.cpp_info.exelinkflags.extend([ "-Wl,--wrap=__cxa_allocate_exception", @@ -152,9 +157,11 @@ def package_info(self): ]) if using_wrap: + package_folder = Path(self.package_folder) + lib_path = package_folder / 'lib' / 'libhal-exceptions.a' self.cpp_info.exelinkflags.extend([ # Ensure that all symbols are added to the linker's symbol table "-Wl,--whole-archive", - lib_path, + str(lib_path), "-Wl,--no-whole-archive", ]) diff --git a/demos/main.cpp b/demos/main.cpp index ac73d91..9c750af 100644 --- a/demos/main.cpp +++ b/demos/main.cpp @@ -1,6 +1,4 @@ -#include - -#include +#include #include @@ -13,17 +11,7 @@ void terminate_handler() int main() { - hal::set_terminate(terminate_handler); + std::set_terminate(terminate_handler); initialize_platform(); application(); } - -// NOLINTBEGIN(bugprone-reserved-identifier) -// NOLINTBEGIN(readability-identifier-naming) -namespace ke::__except_abi { -std::span near_point_descriptor{}; -std::span normal_table{}; -std::span small_table{}; -} // namespace ke::__except_abi -// NOLINTEND(readability-identifier-naming) -// NOLINTEND(bugprone-reserved-identifier) diff --git a/demos/platforms/lpc4078.cpp b/demos/platforms/lpc4078.cpp index c93f970..f06bb89 100644 --- a/demos/platforms/lpc4078.cpp +++ b/demos/platforms/lpc4078.cpp @@ -14,7 +14,6 @@ #include #include -#include #include diff --git a/demos/platforms/stm32f103c8.cpp b/demos/platforms/stm32f103c8.cpp index 7cf195d..766daee 100644 --- a/demos/platforms/stm32f103c8.cpp +++ b/demos/platforms/stm32f103c8.cpp @@ -14,7 +14,6 @@ #include #include -#include #include diff --git a/include/libhal-exceptions/control.hpp b/include/libhal-exceptions/control.hpp deleted file mode 100644 index 98eb84e..0000000 --- a/include/libhal-exceptions/control.hpp +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2024 - 2025 Khalil Estell and the libhal contributors -// -// 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. - -#pragma once - -#include -#include - -namespace hal { -/** - * @brief Set the global exception allocator function - * - * More details on how you should use this API to come in the future. - * - * @param p_allocator - exception memory allocator implementation - */ -void set_exception_allocator(std::pmr::memory_resource& p_allocator) noexcept; - -/** - * @brief Get the global exception allocator function - * - * More details on how you should use this API to come in the future. - * - * @returns the global exception memory allocator implementation - */ -std::pmr::memory_resource& get_exception_allocator() noexcept; - -/** - * @brief Set the terminate handler - * - * @param p_terminate_handler - new global terminate handler - */ -std::terminate_handler set_terminate( - std::terminate_handler p_terminate_handler) noexcept; - -/** - * @brief Get the terminate handler - * - * @return std::terminate_handler - the currently set terminate handler - */ -std::terminate_handler get_terminate() noexcept; -} // namespace hal diff --git a/include/libhal-exceptions/module.cppm b/include/libhal-exceptions/module.cppm new file mode 100644 index 0000000..591a803 --- /dev/null +++ b/include/libhal-exceptions/module.cppm @@ -0,0 +1,8 @@ + +module; + +#include "new.hpp" + +export module libhal_exceptions; + +export ke::exception_allocation_tag; diff --git a/include/libhal-exceptions/new.hpp b/include/libhal-exceptions/new.hpp new file mode 100644 index 0000000..c3fc87b --- /dev/null +++ b/include/libhal-exceptions/new.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include + +namespace ke { +struct exception_allocation_tag +{}; +} // namespace ke + +void* operator new(std::size_t p_size, + std::align_val_t p_align, + ke::exception_allocation_tag); + +void operator delete(void* p_ptr, + std::size_t p_size, + std::align_val_t p_align, + ke::exception_allocation_tag); diff --git a/src/arm_cortex/estell/exception.cpp b/src/arm_cortex/estell/exception.cpp index 0b8a7f4..a86372e 100644 --- a/src/arm_cortex/estell/exception.cpp +++ b/src/arm_cortex/estell/exception.cpp @@ -12,18 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include +#include + #include -#include #include #include #include #include #include -#include #include #include -#include +#include #include "internal.hpp" @@ -820,8 +821,8 @@ enum class pop_lr : std::uint8_t template [[nodiscard("You MUST set the unwind function's stack pointer to this " "value after executing it!")]] -inline std::uint32_t const* pop_register_range(std::uint32_t const* sp_ptr, - cortex_m_cpu& p_cpu) +inline std::uintptr_t const* pop_register_range(std::uintptr_t const* sp_ptr, + cortex_m_cpu& p_cpu) { // We pull these pointers out in order to access them incrementally, which // will give the hint to the compiler to convert them into a sequence of @@ -849,6 +850,8 @@ inline std::uint32_t const* pop_register_range(std::uint32_t const* sp_ptr, void unwind_frame(instructions_t const& p_instructions, cortex_m_cpu& p_cpu) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgnu-label-as-value" static constexpr std::array jump_table{ &&vsp_add_0, // [0] &&vsp_add_1, // [1] @@ -1175,6 +1178,7 @@ void unwind_frame(instructions_t const& p_instructions, cortex_m_cpu& p_cpu) &&reserved_or_spare_thus_terminate, // 11011'110 [] &&reserved_or_spare_thus_terminate, // 11111'111 [255] }; +#pragma clang diagnostic pop bool move_lr_to_pc = true; std::uint32_t u32_storage = 0; @@ -1184,7 +1188,10 @@ void unwind_frame(instructions_t const& p_instructions, cortex_m_cpu& p_cpu) while (true) { auto const* instruction_handler = jump_table[*instruction_ptr]; instruction_ptr++; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgnu-label-as-value" goto* instruction_handler; +#pragma clang diagnostic pop // +=========================================================================+ // | Finish! | @@ -1985,8 +1992,8 @@ void push_vmi_info(exception_ptr p_thrown_exception, auto const* word_pointer = reinterpret_cast(p_info.type_info); - auto const length = word_pointer[vla_length]; - for (std::size_t i = vla_start; i < vla_start + (length * 2); i += 2) { + auto const length = static_cast(word_pointer[vla_length]); + for (std::size_t i = vla_start; i < (vla_start + (length * 2)); i += 2) { base_class_type_info parent_info{}; auto const parent_address = word_pointer[i]; // Shift by 8 to remove the first byte which is just flags @@ -2070,13 +2077,39 @@ void flatten_rtti(exception_ptr p_thrown_exception, } } } - } // namespace ke namespace { bool const volatile libhal_convince_compiler_to_emit_metadata = false; } +#if defined(__clang__) +#define LIBHAL_WEAK __attribute__((weak)) +#elif defined(__GNUC__) +#define LIBHAL_WEAK [[gnu::weak]] +#else +#define LIBHAL_WEAK +#endif + +LIBHAL_WEAK +void* operator new(std::size_t p_size, + std::align_val_t p_align, + ke::exception_allocation_tag) +{ + return ::operator new(p_size, p_align); +} + +LIBHAL_WEAK +void operator delete(void* p_ptr, + std::size_t p_size, + std::align_val_t p_align, + ke::exception_allocation_tag) +{ + ::operator delete(p_ptr, p_size, p_align); +} + +#undef LIBHAL_WEAK + // NOLINTBEGIN(bugprone-reserved-identifier) extern "C" { @@ -2096,15 +2129,19 @@ extern "C" // more importantly, in order to detect if the allocator throws bad alloc // and we re-enter this function, we can detect that and terminate. ke::control_block.cache.state(ke::runtime_state::get_next_frame); - auto const allocation_amount = - sizeof(ke::exception_allocation<>) + p_thrown_size; - auto exception_memory_resource = &hal::get_exception_allocator(); + constexpr auto exception_wrapper_size = sizeof(ke::exception_allocation<>); + auto const allocation_amount = exception_wrapper_size + p_thrown_size; + // using alloc_tag = ke::exception_allocation_tag; - auto* object = static_cast*>( - exception_memory_resource->allocate(allocation_amount)); + // Currently: always max_align_t since we don't know better + [[maybe_unused]] constexpr auto align = alignof(std::max_align_t); + + auto* exception_address = ::operator new(allocation_amount, + std::align_val_t{ align }, + ke::exception_allocation_tag{}); + auto* object = static_cast*>(exception_address); - object->allocator = exception_memory_resource; object->size = allocation_amount; return &object->data; @@ -2114,7 +2151,13 @@ extern "C" void __wrap___cxa_free_exception(void* p_thrown_exception) { auto object = ke::get_allocation_from_exception(p_thrown_exception); - object->allocator->deallocate(object, object->size); + + // Match the allocation + constexpr auto align = alignof(std::max_align_t); + ::operator delete(object, + object->size, + std::align_val_t{ align }, + ke::exception_allocation_tag{}); } // NOLINTBEGIN(readability-identifier-naming) @@ -2230,85 +2273,3 @@ extern "C" } } // extern "C" // NOLINTEND(bugprone-reserved-identifier) - -namespace __cxxabiv1 { // NOLINT -std::terminate_handler __terminate_handler = +[]() { // NOLINT - while (true) { - continue; - } -}; -} // namespace __cxxabiv1 - -namespace hal { -std::terminate_handler set_terminate( - std::terminate_handler p_terminate_handler) noexcept -{ - auto copy = __cxxabiv1::__terminate_handler; - __cxxabiv1::__terminate_handler = p_terminate_handler; - return copy; -} - -std::terminate_handler get_terminate() noexcept -{ - return __cxxabiv1::__terminate_handler; -} - -/** - * @brief Simple exception allocator - * - * This allocator can only allocates space for a single exception object at a - * time. - */ -class single_exception_allocator : public std::pmr::memory_resource -{ -public: - single_exception_allocator() = default; - ~single_exception_allocator() override = default; - -private: - void* do_allocate(std::size_t p_size, // NOLINT - [[maybe_unused]] std::size_t p_alignment) override - { - if (m_allocated || p_size > m_buffer.size()) { - return nullptr; - } - m_allocated = true; - return m_buffer.data(); - } - - void do_deallocate(void* p_address, - [[maybe_unused]] std::size_t p_size, // NOLINT - [[maybe_unused]] std::size_t p_alignment) override - { - if (p_address != m_buffer.data()) { - std::terminate(); - } - m_allocated = false; - } - - [[nodiscard]] bool do_is_equal( - std::pmr::memory_resource const& other) const noexcept override - { - return this == &other; - } - - alignas(std::max_align_t) std::array m_buffer{}; - bool m_allocated = false; -}; - -// TODO(#11): Add macro to IFDEF this out if the user want to save 64 bytes. -// NOLINTNEXTLINE -single_exception_allocator _default_allocator{}; -// NOLINTNEXTLINE -std::pmr::memory_resource* _exception_allocator = &_default_allocator; - -void set_exception_allocator(std::pmr::memory_resource& p_allocator) noexcept -{ - _exception_allocator = &p_allocator; -} - -std::pmr::memory_resource& get_exception_allocator() noexcept -{ - return *_exception_allocator; -} -} // namespace hal diff --git a/src/arm_cortex/estell/internal.hpp b/src/arm_cortex/estell/internal.hpp index 813d0d8..cd7189f 100644 --- a/src/arm_cortex/estell/internal.hpp +++ b/src/arm_cortex/estell/internal.hpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include @@ -30,20 +29,20 @@ using destructor_t = void(void*); struct register_t { - std::uint32_t data; + std::uintptr_t data; register_t() : data(0) { } - register_t(std::uint32_t p_data) + register_t(std::uintptr_t p_data) : data(p_data) { } register_t(void const* p_data) - : data(reinterpret_cast(p_data)) + : data(reinterpret_cast(p_data)) { } @@ -52,10 +51,10 @@ struct register_t return data; } - std::uint32_t* operator*() + auto* operator*() { // NOLINTNEXTLINE(performance-no-int-to-ptr) - return reinterpret_cast(data); + return reinterpret_cast(data); } }; @@ -65,9 +64,9 @@ constexpr std::uint32_t end_descriptor = 0x0000'0000; constexpr std::uint32_t su16_mask = 0b1111'1111'1111'1110; } // namespace arm_ehabi -constexpr std::uint32_t to_absolute_address(void const* p_object) +constexpr std::uintptr_t to_absolute_address(void const* p_object) { - auto const object_address = std::bit_cast(p_object); + auto const object_address = std::bit_cast(p_object); auto offset = *std::bit_cast(p_object); // Shift bits to the end @@ -105,16 +104,16 @@ struct function_t { using callable_t = void(); - std::uint32_t address; + std::uintptr_t address; - function_t(std::uint32_t p_address) + function_t(std::uintptr_t p_address) : address(p_address) { } - operator std::uint32_t() + operator std::uintptr_t() { - return reinterpret_cast(address); + return reinterpret_cast(address); } operator void*() @@ -234,7 +233,7 @@ struct cortex_m_cpu { using register_file = std::array; static_assert(sizeof(cortex_m_cpu) == sizeof(register_file)); - static_assert(sizeof(std::uint32_t) == sizeof(register_t)); + static_assert(sizeof(std::uintptr_t) == sizeof(register_t)); auto* file = reinterpret_cast(this); return (*file)[p_size]; @@ -304,7 +303,7 @@ struct exception_cache struct base_class_type_info { void const* type_info = nullptr; - std::int32_t offset = 0; + std::ptrdiff_t offset = 0; }; template @@ -379,7 +378,6 @@ struct flattened_hierarchy template struct exception_allocation { - std::pmr::memory_resource* allocator = nullptr; std::size_t size = 0uz; alignas(std::max_align_t) T data; }; diff --git a/src/arm_cortex/estell/libunwind_wrappers.cpp b/src/arm_cortex/estell/libunwind_wrappers.cpp index be9943d..427d27c 100644 --- a/src/arm_cortex/estell/libunwind_wrappers.cpp +++ b/src/arm_cortex/estell/libunwind_wrappers.cpp @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include + extern "C" { void __wrap___gnu_unwind_pr_common() @@ -42,3 +44,34 @@ extern "C" { } } + +// Symbol for GCC +namespace __cxxabiv1 { // NOLINT +/** + * @brief Implementation of the terminate handler that simply halts + * + * This will override the weak symbol used in GCC's exception runtime. The + * original implementation prints a message to stdout for the user. The code + * that performs the message rendering can result in 40kb to 80kB added to ROM. + * By replacing that symbol with one that does nothing besides halt, all of + * those dependencies on those functions are unnecessary and can be garbage + * collected. + * + * NOTE: If for some reason in the future, this stops working or the linker + * complains of duplicate symbols, then we should wrap the symbol using. + * `-Wl,--wrap=__terminate_handler` + * + */ +std::terminate_handler __terminate_handler = +[]() { // NOLINT + while (true) { + continue; + } +}; +} // namespace __cxxabiv1 + +// Symbol for LLVM +std::terminate_handler __wrap___cxa_terminate_handler = []() { // NOLINT + while (true) { + continue; + } +}; diff --git a/src/builtin/gcc/impl.cpp b/src/builtin/gcc/impl.cpp deleted file mode 100644 index e90c29b..0000000 --- a/src/builtin/gcc/impl.cpp +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright 2024 - 2025 Khalil Estell and the libhal contributors -// -// 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 -#include -#include -#include - -#include - -namespace { - -// Size of the GCC exception object header is 128 bytes. Will have to update -// this if the size of the EO increases. 😅 -// Might need to add some GCC macro flags here to keep track of all of the -// EO sizes over the versions. -constexpr size_t gcc_header_size = 128; - -template -struct exception_allocation -{ - std::size_t size; - std::array header; - alignas(std::max_align_t) T data; -}; - -template -exception_allocation* get_allocation_from_exception(void* p_exception_ptr) -{ - auto const member_offset = offsetof(exception_allocation, data); - auto const exception_bytes = static_cast(p_exception_ptr); - return reinterpret_cast*>(exception_bytes - - member_offset); -} - -} // namespace - -extern "C" -{ - void _exit([[maybe_unused]] int rc) // NOLINT - { - std::terminate(); - } - - void* __wrap___cxa_allocate_exception(std::size_t p_size) noexcept // NOLINT - { - - auto const allocation_amount = sizeof(exception_allocation<>) + p_size; - auto exception_memory_resource = &hal::get_exception_allocator(); - - auto* object = static_cast*>( - exception_memory_resource->allocate(allocation_amount)); - - if (object == nullptr) { - std::terminate(); - } - - // Required for GCC's impl to work correctly as it assumes that all bytes - // default to 0. - object->header.fill(std::byte{ 0 }); - object->size = allocation_amount; - return &object->data; - } - - void __wrap___cxa_call_unexpected(void*) // NOLINT - { - std::terminate(); - } - - void __wrap___cxa_free_exception(void* p_exception) noexcept // NOLINT - { - auto* object = get_allocation_from_exception(p_exception); - hal::get_exception_allocator().deallocate(object, object->size); - } -} // extern "C" - -namespace __cxxabiv1 { // NOLINT -std::terminate_handler __terminate_handler = +[]() { // NOLINT - while (true) { - continue; - } -}; -} // namespace __cxxabiv1 - -namespace hal { -std::terminate_handler set_terminate( - std::terminate_handler p_terminate_handler) noexcept -{ - auto copy = __cxxabiv1::__terminate_handler; - __cxxabiv1::__terminate_handler = p_terminate_handler; - return copy; -} - -std::terminate_handler get_terminate() noexcept -{ - return __cxxabiv1::__terminate_handler; -} - -/** - * @brief Simple exception allocator - * - * This allocator can only allocates space for a single exception object at a - * time. - */ -class single_exception_allocator : public std::pmr::memory_resource -{ -public: - single_exception_allocator() = default; - ~single_exception_allocator() override = default; - -private: - void* do_allocate(std::size_t p_size, // NOLINT - [[maybe_unused]] std::size_t p_alignment) override - { - if (m_allocated || p_size > m_buffer.size()) { - return nullptr; - } - m_allocated = true; - return m_buffer.data(); - } - - void do_deallocate(void* p_address, - [[maybe_unused]] std::size_t p_size, // NOLINT - [[maybe_unused]] std::size_t p_alignment) override - { - if (p_address != m_buffer.data()) { - std::terminate(); - } - m_allocated = false; - } - - [[nodiscard]] bool do_is_equal( - std::pmr::memory_resource const& other) const noexcept override - { - return this == &other; - } - - alignas(std::max_align_t) std::array m_buffer{}; - bool m_allocated = false; -}; - -// TODO(#11): Add macro to IFDEF this out if the user want to save 64 bytes. -// NOLINTNEXTLINE -single_exception_allocator _default_allocator{}; -// NOLINTNEXTLINE -std::pmr::memory_resource* _exception_allocator = &_default_allocator; - -void set_exception_allocator(std::pmr::memory_resource& p_allocator) noexcept -{ - _exception_allocator = &p_allocator; -} - -std::pmr::memory_resource& get_exception_allocator() noexcept -{ - return *_exception_allocator; -} -} // namespace hal diff --git a/src/builtin/gcc/override.cpp b/src/builtin/gcc/override.cpp new file mode 100644 index 0000000..788b151 --- /dev/null +++ b/src/builtin/gcc/override.cpp @@ -0,0 +1,38 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// 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 + +namespace __cxxabiv1 { // NOLINT +/** + * @brief Implementation of the terminate handler that simply halts + * + * This will override the weak symbol used in GCC's exception runtime. The + * original implementation prints a message to stdout for the user. The code + * that performs the message rendering can result in 40kb to 80kB added to ROM. + * By replacing that symbol with one that does nothing besides halt, all of + * those dependencies on those functions are unnecessary and can be garbage + * collected. + * + * NOTE: If for some reason in the future, this stops working or the linker + * complains of duplicate symbols, then we should wrap the symbol using. + * `-Wl,--wrap=__terminate_handler` + * + */ +std::terminate_handler __terminate_handler = +[]() { // NOLINT + while (true) { + continue; + } +}; +} // namespace __cxxabiv1 diff --git a/src/builtin/llvm/override.cpp b/src/builtin/llvm/override.cpp new file mode 100644 index 0000000..b4005e6 --- /dev/null +++ b/src/builtin/llvm/override.cpp @@ -0,0 +1,21 @@ +// Copyright 2024 - 2025 Khalil Estell and the libhal contributors +// +// 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 + +std::terminate_handler __wrap___cxa_terminate_handler = []() { + while (true) { + continue; + } +}; diff --git a/test_package/CMakeLists.txt b/test_package/CMakeLists.txt index 8c42da4..6d10d3a 100644 --- a/test_package/CMakeLists.txt +++ b/test_package/CMakeLists.txt @@ -19,26 +19,5 @@ find_package(libhal-exceptions REQUIRED CONFIG) add_executable(${PROJECT_NAME} main.cpp) target_include_directories(${PROJECT_NAME} PUBLIC .) -target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) -set_target_properties(${PROJECT_NAME} PROPERTIES CXX_EXTENSIONS OFF) target_link_libraries(${PROJECT_NAME} PRIVATE libhal::exceptions) target_compile_options(${PROJECT_NAME} PRIVATE -fexceptions -fno-rtti) - -# Add nano newlib and nosys specs for ARM embedded systems -if(CMAKE_SYSTEM_PROCESSOR MATCHES "^arm" OR - CMAKE_SYSTEM_PROCESSOR MATCHES "^ARM") - if(CMAKE_SYSTEM_NAME STREQUAL "Generic" OR - CMAKE_CROSSCOMPILING OR - CMAKE_C_COMPILER MATCHES "arm.*gcc" OR - CMAKE_CXX_COMPILER MATCHES "arm.*g\\+\\+") - # NOTE: We disable the ODR violation because we need to have a way to - # spell and address the types for the RTTI parent classes, but seeing - # identical symbols with different representation triggers this warning. - target_link_options(${PROJECT_NAME} PRIVATE - --specs=nosys.specs - -Wno-odr - ) - - message(STATUS "Added nosys.specs for ARM embedded target") - endif() -endif() diff --git a/test_package/conanfile.py b/test_package/conanfile.py index 87a2161..0362b06 100644 --- a/test_package/conanfile.py +++ b/test_package/conanfile.py @@ -19,11 +19,11 @@ class TestPackageConan(ConanFile): settings = "os", "arch", "compiler", "build_type" - generators = "CMakeToolchain", "CMakeDeps", "VirtualRunEnv" + generators = "VirtualRunEnv", "CMakeDeps", "CMakeToolchain" def build_requirements(self): - self.tool_requires("make/4.4.1") - self.tool_requires("cmake/3.27.1") + self.tool_requires("cmake/[^4.0.0]") + self.tool_requires("ninja/[^1.3.0]") def requirements(self): self.requires(self.tested_reference_str) diff --git a/test_package/main.cpp b/test_package/main.cpp index 016f1f1..a592ccc 100644 --- a/test_package/main.cpp +++ b/test_package/main.cpp @@ -12,9 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include -#include +#include +#include + +#include // NOLINTBEGIN(readability-identifier-naming) struct V // NOLINT(readability-identifier-naming) @@ -85,10 +89,29 @@ void foo() throw error{}; } +#if defined(LIBHAL_ESTELL_EXCEPTIONS) || 1 + +std::array m_buffer{}; + +void* operator new(std::size_t, std::align_val_t, ke::exception_allocation_tag) +{ + m_buffer.fill(std::byte{ 0 }); + return m_buffer.data(); +} + +void operator delete(void*, + std::size_t, + std::align_val_t, + ke::exception_allocation_tag) +{ + m_buffer.fill(std::byte{ 0 }); +} +#endif // defined(LIBHAL_ESTELL_EXCEPTIONS) + int main() { [[maybe_unused]] static constexpr auto error_size = sizeof(error); - hal::set_terminate(+[]() { puts("terminating application!"); }); + std::set_terminate(+[]() { puts("terminating application!"); }); try { foo(); diff --git a/tests/control.test.cpp b/tests/control.test.cpp index dd3b842..e6a8014 100644 --- a/tests/control.test.cpp +++ b/tests/control.test.cpp @@ -1,23 +1,13 @@ -#include -#include - -#include - #include namespace hal { -boost::ut::suite<"control_test_suite"> control_test_suite = []() { +boost::ut::suite<"override_new_test_suite"> override_new_test_suite = []() { using namespace boost::ut; - "hal::set_exception_allocator()"_test = []() { - // setup - std::array buffer; - std::pmr::monotonic_buffer_resource resource(buffer.data(), buffer.size()); - - // exercise - hal::set_exception_allocator(resource); - - // verify - expect(that % &resource == &hal::get_exception_allocator()); + "operator new(...)"_test = []() { + // TODO(#109): Add testing for operator new APIs + }; + "operator delete(...)"_test = []() { + // TODO(#109): Add testing for operator delete APIs }; }; } // namespace hal