From 9eb6edbf711ceb25346ee40bae68b40a4505cdf5 Mon Sep 17 00:00:00 2001 From: kobalicek Date: Thu, 8 May 2025 07:02:26 +0200 Subject: [PATCH] Allow users to specify a home register hint (compiler) --- src/asmjit/core.h | 100 +++++++++++++++++++++------------ src/asmjit/core/archtraits.h | 2 +- src/asmjit/core/assembler.h | 10 ++-- src/asmjit/core/builder.h | 8 +-- src/asmjit/core/compilerdefs.h | 24 +++++++- src/asmjit/core/cpuinfo.cpp | 2 +- src/asmjit/core/cpuinfo.h | 2 +- src/asmjit/core/radefs_p.h | 3 +- 8 files changed, 99 insertions(+), 52 deletions(-) diff --git a/src/asmjit/core.h b/src/asmjit/core.h index 834d544..8d28815 100644 --- a/src/asmjit/core.h +++ b/src/asmjit/core.h @@ -17,7 +17,7 @@ namespace asmjit { //! functionality is within \ref asmjit namespace and architecture specific functionality is always in its own //! namespace. For example \ref asmjit::x86 provides both 32-bit and 64-bit X86 code generation. //! -//! \section main_groups Documentation Groups +//! \section doc_groups Documentation Groups //! //! AsmJit documentation is structured into groups. Groups can be followed in order to learn AsmJit, but knowledge //! from multiple groups is required to use AsmJit properly: @@ -78,7 +78,7 @@ namespace asmjit { //! output from \ref Logger is always necessary when filling bug reports. In other words, using logging and proper error //! handling can save a lot of time during the development and can also save users from submitting issues. //! -//! \section main_other Other Pages +//! \section other_pages Other Pages //! //! - Class List - List of classes sorted alphabetically //! - AsmJit Namespace - List of symbols provided by `asmjit` namespace @@ -96,6 +96,8 @@ namespace asmjit { //! your project and to define \ref ASMJIT_STATIC. AsmJit can be just updated from time to time without any changes to //! this integration process. Do not embed AsmJit's `test` files in such case as these are used exclusively for testing. //! +//! \section supported_environment Supported Operating Systems, Compilers, and Build Tools +//! //! ### Supported C++ Compilers //! //! - Requirements: @@ -147,7 +149,7 @@ namespace asmjit { //! - **X86** and **X86_64** - Both 32-bit and 64-bit backends tested on CI. //! - **AArch64** - Tested on CI (Native Apple runners and Linux emulated via QEMU). //! -//! ### Static Builds and Embedding +//! \section build_mode Static Builds and Embedding //! //! These definitions can be used to enable static library build. Embed is used when AsmJit's source code is embedded //! directly in another project, implies static build as well. @@ -159,7 +161,31 @@ namespace asmjit { //! otherwise AsmJit would use dynamic library imports in \ref ASMJIT_API decorator. The recommendation is to define //! this macro across the whole project that uses AsmJit this way. //! -//! ### Build Configuration +//! \section cmake_integration CMake Integration +//! +//! AsmJit has a first-class CMake support. When consuming AsmJit as a cmake dependency, just use `asmjit::asmjit` +//! as a link dependency, which would instrument cmake to setup everything else, including include paths, and build +//! flags (either defining `ASMJIT_STATIC` or not, and possibly defining other AsmJit feature macros). For example +//! considering that AsmJit was fetched to `3rdparty/asmjit` directory in your project as an external dependency, +//! you can just use the following CMake snippet that integrates AsmJit with your own CMake project: +//! +//! ```cmake +//! cmake_minimum_required(VERSION 3.30) +//! +//! project(asmjit_consumer C CXX) # Both C and CXX are required. +//! set(CMAKE_CXX_STANDARD 17) # C++11 and never is supported. +//! +//! set(ASMJIT_DIR "3rdparty/asmjit") # Location of AsmJit. +//! set(ASMJIT_STATIC TRUE) # Force static build. +//! +//! add_subdirectory("${ASMJIT_DIR}") # This adds AsmJit as a part of your project. +//! +//! add_executable(asmjit_consumer asmjit_consumer.cpp) +//! target_link_libraries( +//! asmjit_consumer asmjit::asmjit) # This adds AsmJit as a dependency to your target. +//! ``` +//! +//! \section build_type Build Type Configuration //! //! These definitions control whether asserts are active or not. By default AsmJit would autodetect build configuration //! from existing pre-processor definitions, but this behavior can be overridden, for example to enable debug asserts @@ -173,7 +199,7 @@ namespace asmjit { //! were not used. We only recommend using build configuration overrides in special situations, like using AsmJit in //! release configuration with asserts enabled for whatever reason. //! -//! ### AsmJit Backends +//! \section build_backends AsmJit Backends //! //! AsmJit currently supports only X86/X64 backend, but the plan is to add more backends in the future. By default //! AsmJit builds only the host backend, which is auto-detected at compile-time, but this can be overridden. @@ -182,7 +208,7 @@ namespace asmjit { //! - \ref ASMJIT_NO_AARCH64 - Disables AArch64 backend. //! - \ref ASMJIT_NO_FOREIGN - Disables the support for foreign architecture backends, only keeps a native backend. //! -//! ### AsmJit Compilation Options +//! \section build_options Build Options //! //! - \ref ASMJIT_NO_DEPRECATED - Disables deprecated API at compile time so it won't be available and the //! compilation will fail if there is attempt to use such API. This includes deprecated classes, namespaces, @@ -195,9 +221,9 @@ namespace asmjit { //! versions are used at the same time. This option can be debugging a little simpler as there would not be ABI //! tag after `asmjit::` namespace. Otherwise asmjit would look like `asmjit::_abi_1_13::`, for example. //! -//! ### Features Selection +//! \section build_features Build Features //! -//! AsmJit builds by defaults all supported features, which includes all emitters, logging, instruction validation and +//! AsmJit builds by default all supported features, which includes all emitters, logging, instruction validation and //! introspection, and JIT memory allocation. Features can be disabled at compile time by using `ASMJIT_NO_...` //! definitions. //! - \ref ASMJIT_NO_JIT - Disables JIT memory management and \ref JitRuntime. @@ -235,7 +261,7 @@ namespace asmjit { //! removed APIs and should serve as a how-to guide for people that want to port existing code to work with the //! newest AsmJit. //! -//! ### Tips +//! \section tips Tips //! //! Useful tips before you start: //! @@ -247,6 +273,8 @@ namespace asmjit { //! because some compilers would warn about that. If your project compiles fine with `ASMJIT_NO_DEPRECATED` //! it's not using anything, which was deprecated. //! +//! \section api_changes API Changes +//! //! ### Changes committed at 2024-01-01 //! //! Core changes: @@ -513,7 +541,7 @@ namespace asmjit { //! //! \note CodeHolder examples use \ref x86::Assembler as abstract interfaces cannot be used to generate code. //! -//! ### CodeHolder & Emitters +//! \section code_holder_and_emitters CodeHolder & Emitters //! //! The example below shows how the mentioned classes interact to generate X86 code: //! @@ -562,7 +590,7 @@ namespace asmjit { //! - \ref asmjit_builder - Low-level emitter that emits to a \ref BaseNode list. //! - \ref asmjit_compiler - High-level emitter that provides register allocation. //! -//! ### Targets and JitRuntime +//! \section targets_and_jit_runtime Targets and JitRuntime //! //! AsmJit's \ref Target is an interface that provides basic target abstraction. At the moment AsmJit provides only //! one implementation called \ref JitRuntime, which as the name suggests provides JIT code target and execution @@ -631,7 +659,7 @@ namespace asmjit { //! } //! ``` //! -//! ### Explicit Code Relocation +//! \section explicit_code_relocation Explicit Code Relocation //! //! In addition to \ref Environment, \ref CodeHolder can be configured to specify a base-address (or a virtual base //! address in a linker terminology), which could be static (useful when you know the location where the target's @@ -817,7 +845,7 @@ namespace asmjit { //! } //! ``` //! -//! ### Label Offsets and Links +//! \section labels Label Offsets and Links //! //! When a label that is not yet bound is used by the Assembler, it creates a \ref LabelLink, which is then added to //! a \ref LabelEntry. These links are also created if a label is used in a different section than in which it was @@ -871,7 +899,7 @@ namespace asmjit { //! } //! ``` //! -//! ### Sections +//! \section code_sections Code & Data Sections //! //! AsmJit allows to create multiple sections within the same \ref CodeHolder. A test-case //! [asmjit_test_x86_sections.cpp](https://github.com/asmjit/asmjit/blob/master/test/asmjit_test_x86_sections.cpp) @@ -988,7 +1016,7 @@ namespace asmjit { //! //! \note Assembler examples use \ref x86::Assembler as abstract interfaces cannot be used to generate code. //! -//! ### Operand Basics +//! \section operand_basics Operand Basics //! //! Let's start with operands. \ref Operand is a data structure that defines a data layout of any operand. It can be //! inherited, but any class inheriting it cannot add any members to it, only the existing layout can be reused. @@ -1028,7 +1056,7 @@ namespace asmjit { //! - \ref Label - used to reference a location in code or data. Labels must be created by the \ref BaseEmitter or //! by \ref CodeHolder. Each label has its unique id per \ref CodeHolder instance. //! -//! ### Operand Manipulation +//! \section operand_manipulation Operand Manipulation //! //! AsmJit allows to construct operands dynamically, to store them, and to query a complete information about them at //! run-time. Operands are small (always 16 bytes per `Operand`) and should be always copied (by value) if you intend @@ -1099,7 +1127,7 @@ namespace asmjit { //! BaseEmitter::newLabel(), which creates a label entry and returns a \ref Label operand with the id that refers //! to it. Such label then can be used by emitters. //! -//! ### Memory Operands +//! \section memory_operands Memory Operands //! //! Some architectures like X86 provide a complex memory addressing model that allows to encode addresses having a //! BASE register, INDEX register with a possible scale (left shift), and displacement (called offset in AsmJit). @@ -1232,7 +1260,7 @@ namespace asmjit { //! } //! ``` //! -//! ### Assembler Examples +//! \section examples Assembler Examples //! //! - \ref x86::Assembler provides many X86/X64 examples. @@ -1252,7 +1280,7 @@ namespace asmjit { //! compatibility with the existing \ref BaseAssembler emitter so users can move from assembler to builder when needed, //! for example to implement post-processing, which is not possible with Assembler. //! -//! ### Builder Nodes +//! \section builder_nodes Builder Nodes //! //! \ref BaseBuilder doesn't generate machine code directly, it uses an intermediate representation based on nodes, //! however, it allows to serialize to \ref BaseAssembler when the code is ready to be encoded. @@ -1278,7 +1306,7 @@ namespace asmjit { //! //! - Other nodes are provided by \ref asmjit_compiler infrastructure. //! -//! ### Builder Examples +//! \section builder_examples Examples //! //! - \ref x86::Builder - Builder implementation targeting X86 and X86_64 architectures. //! - \ref a64::Builder - Builder implementation targeting AArch64 architecture. @@ -1305,7 +1333,7 @@ namespace asmjit { //! return value(s) are handled by assigning virtual registers to them. Similarly, function calls are handled the same //! way. //! -//! ### Compiler Nodes +//! \section compiler_nodes Compiler Nodes //! //! \ref BaseCompiler adds some nodes that are required for function generation and invocation: //! @@ -1316,12 +1344,12 @@ namespace asmjit { //! \ref BaseCompiler also makes the use of passes (\ref Pass) and automatically adds an architecture-dependent //! register allocator pass to the list of passes when attached to \ref CodeHolder. //! -//! ### Compiler Examples +//! \section compiler_examples Compiler Examples //! //! - \ref x86::Compiler - Compiler implementation targeting X86 and X86_64 architectures. //! - \ref a64::Compiler - Compiler implementation targeting AArch64 architecture. //! -//! ### Compiler Tips +//! \section compiler_tips Compiler Tips //! //! Users of AsmJit have done mistakes in the past, this section should provide some useful tips for beginners: //! @@ -1423,7 +1451,7 @@ namespace asmjit { //! - \ref FormatFlags //! - \ref FormatIndentationGroup //! -//! ### Logging +//! \section logging Logging //! //! A \ref Logger is typically attached to a \ref CodeHolder, which propagates it to all attached emitters //! automatically. The example below illustrates how to use \ref FileLogger that outputs to standard output: @@ -1481,7 +1509,7 @@ namespace asmjit { //! } //! ``` //! -//! ### Formatting +//! \section formatting Formatting //! //! AsmJit uses \ref Formatter to format inputs that are then passed to \ref Logger. Formatting is public and can be //! used by AsmJit users as well. The most important thing to know regarding formatting is that \ref Formatter always @@ -1631,7 +1659,7 @@ namespace asmjit { //! Zone memory and the ownership of memory it allocates always ends with the instance that allocated it. If //! using this approach please never jump outside the life-time of \ref CodeHolder and \ref BaseEmitter. //! -//! ### Using ErrorHandler +//! \section using_error_handler Using ErrorHandler //! //! An example of attaching \ref ErrorHandler to \ref CodeHolder. //! @@ -1695,7 +1723,7 @@ namespace asmjit { //! valid. This is useful for making sure that what user tries to emit is correct and it can be also used by other //! projects that parse user input, like AsmTK project. //! -//! ### Query API +//! \section instruction_query Instruction Queries //! //! The instruction query API is provided by \ref InstAPI namespace. The following queries are possible: //! @@ -1709,7 +1737,7 @@ namespace asmjit { //! - asmjit_test_instinfo.cpp //! can be also used as a reference about accessing instruction information. //! -//! ### Validation API +//! \section instruction_validation Instruction Validation //! //! The instruction validation API is provided by \ref InstAPI namespace in the similar fashion like the Query API, //! however, validation can also be turned on at \ref BaseEmitter level. The following is possible: @@ -1748,7 +1776,7 @@ namespace asmjit { //! example. \ref JitAllocator then tracks used space of each page it maintains. Internally, \ref JitAllocator uses //! two bit arrays to track occupied regions in each allocated block of pages. //! -//! ### Hardened Environments +//! \section hardened_environments Hardened Environments //! //! In the past, allocating virtual memory with Read+Write+Execute (RWX) access permissions was easy. However, modern //! operating systems and runtime environments often use hardening, which typically prohibits mapping pages with both @@ -1796,7 +1824,7 @@ namespace asmjit { //! its data in destructor or in their reset() member function for a future reuse. For this purpose all containers in //! AsmJit are also zone allocated. //! -//! ### Zone Allocation +//! \section zone_allocation Zone Allocation //! //! - \ref Zone - Incremental zone memory allocator with minimum features. It can only allocate memory without the //! possibility to return it back to the allocator. @@ -1808,7 +1836,7 @@ namespace asmjit { //! - \ref ZoneAllocator - A wrapper of \ref Zone that provides the capability of returning memory to the allocator. //! Such memory is stored in a pool for later reuse. //! -//! ### Zone Allocated Containers +//! \section zone_containers Zone Allocated Containers //! //! - \ref ZoneString - Zone allocated string. //! - \ref ZoneHash - Zone allocated hash table. @@ -1818,7 +1846,7 @@ namespace asmjit { //! - \ref ZoneVector - Zone allocated vector. //! - \ref ZoneBitVector - Zone allocated vector of bits. //! -//! ### Using Zone Allocated Containers +//! \section using_zone_containers Using Zone Allocated Containers //! //! The most common data structure exposed by AsmJit is \ref ZoneVector. It's very similar to `std::vector`, but the //! implementation doesn't use exceptions and uses the mentioned \ref ZoneAllocator for performance reasons. You don't @@ -1862,7 +1890,7 @@ namespace asmjit { //! } //! ``` //! -//! ### Design Considerations +//! \section design_considerations Design Considerations //! //! Zone-allocated containers do not store the allocator within the container. This decision was made to reduce the //! footprint of such containers as AsmJit tooling, especially Compiler's register allocation, may use many instances @@ -1923,7 +1951,7 @@ namespace asmjit { //! AsmJit uses and provides utility classes and functions, that can be used with AsmJit. The functionality can be //! divided into the following topics: //! -//! ### String Functionality +//! \section string_utilities String Utilities //! //! - \ref String - AsmJit's string container, which is used internally and which doesn't use exceptions and has //! a stable layout, which is not dependent on C++ standard library. @@ -1933,11 +1961,11 @@ namespace asmjit { //! //! - \ref FixedString - Fixed string container limited up to N characters. //! -//! ### Code Generation Utilities +//! \section codegen_utilities Code Generation Utilities //! //! - \ref ConstPool - Constant pool used by \ref BaseCompiler, but also available to users that may find use of it. //! -//! ### Support Functionality Used by AsmJit +//! \section support_utilities Support Functionality Used by AsmJit //! //! - \ref Support namespace provides many other utility functions and classes that are used by AsmJit, and made //! public. diff --git a/src/asmjit/core/archtraits.h b/src/asmjit/core/archtraits.h index 9f08dea..a10a41e 100644 --- a/src/asmjit/core/archtraits.h +++ b/src/asmjit/core/archtraits.h @@ -196,7 +196,7 @@ struct ArchTraits { //! Maps scalar TypeId values (from TypeId::_kIdBaseStart) to register types, see \ref TypeId. Support::Array _typeIdToRegType; - //! Word name identifiers of 8-bit, 16-bit, 32-biit, and 64-bit quantities that appear in formatted text. + //! Word name identifiers of 8-bit, 16-bit, 32-bit, and 64-bit quantities that appear in formatted text. ArchTypeNameId _typeNameIdTable[4]; //! \} diff --git a/src/asmjit/core/assembler.h b/src/asmjit/core/assembler.h index d53d9e5..68f3d5a 100644 --- a/src/asmjit/core/assembler.h +++ b/src/asmjit/core/assembler.h @@ -17,10 +17,9 @@ ASMJIT_BEGIN_NAMESPACE //! Base assembler. //! -//! This is a base class that provides interface used by architecture specific -//! assembler implementations. Assembler doesn't hold any data, instead it's -//! attached to \ref CodeHolder, which provides all the data that Assembler -//! needs and which can be altered by it. +//! This is a base class that provides interface used by architecture specific assembler implementations. Assembler +//! doesn't hold any data, instead it's attached to \ref CodeHolder, which provides all the data that Assembler needs +//! and which can be altered by it. //! //! Check out architecture specific assemblers for more details and examples: //! @@ -63,8 +62,7 @@ public: //! Sets the current position in the CodeBuffer to `offset`. //! - //! \note The `offset` cannot be greater than buffer size even if it's - //! within the buffer's capacity. + //! \note The `offset` cannot be greater than buffer size even if it's within the buffer's capacity. ASMJIT_API Error setOffset(size_t offset); //! Returns the start of the CodeBuffer in the current section. diff --git a/src/asmjit/core/builder.h b/src/asmjit/core/builder.h index 0de1923..6c10ecb 100644 --- a/src/asmjit/core/builder.h +++ b/src/asmjit/core/builder.h @@ -609,14 +609,12 @@ public: template ASMJIT_INLINE_NODEBUG const T* as() const noexcept { return static_cast(this); } - //! Returns previous node or `nullptr` if this node is either first or not - //! part of Builder/Compiler node-list. + //! Returns previous node or `nullptr` if this node is either first or not part of Builder/Compiler node-list. ASMJIT_INLINE_NODEBUG BaseNode* prev() const noexcept { return _prev; } - //! Returns next node or `nullptr` if this node is either last or not part - //! of Builder/Compiler node-list. + //! Returns next node or `nullptr` if this node is either last or not part of Builder/Compiler node-list. ASMJIT_INLINE_NODEBUG BaseNode* next() const noexcept { return _next; } - //! Returns the type of the node, see `NodeType`. + //! Returns the type of the node, see \ref NodeType. ASMJIT_INLINE_NODEBUG NodeType type() const noexcept { return _any._nodeType; } //! Sets the type of the node, see `NodeType` (internal). diff --git a/src/asmjit/core/compilerdefs.h b/src/asmjit/core/compilerdefs.h index e2e74ce..4b2963c 100644 --- a/src/asmjit/core/compilerdefs.h +++ b/src/asmjit/core/compilerdefs.h @@ -18,7 +18,18 @@ class RAWorkReg; //! \addtogroup asmjit_compiler //! \{ -//! Virtual register data, managed by \ref BaseCompiler. +//! Public virtual register interface, managed by \ref BaseCompiler. +//! +//! When a virtual register is created by \ref BaseCompiler a `VirtReg` is linked with the register operand id it +//! returns. This `VirtReg` can be accessed via \ref BaseCompiler::virtRegByReg() function, which returns a pointer +//! to `VirtReg`. +//! +//! In general, `VirtReg` should be only introspected as it contains important variables that are needed and managed +//! by AsmJit, however, the `VirtReg` API can also be used to influence register allocation. For example there is +//! a \ref VirtReg::setWeight() function, which could be used to increase a weight of a virtual register (thus make +//! it hard to spill, for example). In addition, there is a \ref VirtReg::setHomeIdHint() function, which can be used +//! to do an initial assignment of a physical register of a virtual register. However, AsmJit could still override +//! the physical register assigned in some special cases. class VirtReg { public: ASMJIT_NONCOPYABLE(VirtReg) @@ -45,6 +56,8 @@ public: //! True if this virtual register has assigned stack offset (can be only valid after register allocation pass). uint8_t _hasStackSlot : 1; uint8_t _reservedBits : 5; + //! Home register hint for the register allocator (initially unassigned). + uint8_t _homeIdHint = BaseReg::kIdBad; //! Stack offset assigned by the register allocator relative to stack pointer (can be negative as well). int32_t _stackOffset = 0; @@ -145,6 +158,15 @@ public: _stackOffset = stackOffset; } + //! Tests whether this virtual register has assigned a physical register as a hint to the register allocator. + ASMJIT_INLINE_NODEBUG bool hasHomeIdHint() const noexcept { return _homeIdHint != BaseReg::kIdBad; } + //! Returns a physical register hint, which will be used by the register allocator. + ASMJIT_INLINE_NODEBUG uint32_t homeIdHint() const noexcept { return _homeIdHint; } + //! Assigns a physical register hint, which will be used by the register allocator. + ASMJIT_INLINE_NODEBUG void setHomeIdHint(uint32_t homeId) noexcept { _homeIdHint = uint8_t(homeId); } + //! Resets a physical register hint. + ASMJIT_INLINE_NODEBUG void resetHomeIdHint() noexcept { _homeIdHint = BaseReg::kIdBad; } + //! Returns a stack offset associated with a virtual register or explicit stack allocation. //! //! \note Always verify that the stack offset has been assigned by calling \ref hasStackSlot(). The return diff --git a/src/asmjit/core/cpuinfo.cpp b/src/asmjit/core/cpuinfo.cpp index b27588c..8ada345 100644 --- a/src/asmjit/core/cpuinfo.cpp +++ b/src/asmjit/core/cpuinfo.cpp @@ -488,7 +488,7 @@ static ASMJIT_FAVOR_SIZE void detectX86Cpu(CpuInfo& cpu) noexcept { // The highest EAX that we understand. constexpr uint32_t kHighestProcessedEAX = 0x8000001Fu; - // Several CPUID calls are required to get the whole branc string. It's easier + // Several CPUID calls are required to get the whole brand string. It's easier // to copy one DWORD at a time instead of copying the string a byte by byte. uint32_t* brand = cpu._brand.u32; do { diff --git a/src/asmjit/core/cpuinfo.h b/src/asmjit/core/cpuinfo.h index 2638146..fbb91e9 100644 --- a/src/asmjit/core/cpuinfo.h +++ b/src/asmjit/core/cpuinfo.h @@ -180,7 +180,7 @@ public: #endif // !ASMJIT_NO_DEPRECATED //! \} - }; + }; //! X86 specific features data. struct X86 : public Data { diff --git a/src/asmjit/core/radefs_p.h b/src/asmjit/core/radefs_p.h index 5d6e87b..50e1eef 100644 --- a/src/asmjit/core/radefs_p.h +++ b/src/asmjit/core/radefs_p.h @@ -1120,7 +1120,8 @@ public: : _workId(workId), _virtId(vReg->id()), _virtReg(vReg), - _signature(vReg->signature()) {} + _signature(vReg->signature()), + _hintRegId(uint8_t(vReg->homeIdHint())) {} //! \}