diff --git a/.github/workflows/cross.yml b/.github/workflows/cross.yml index 02ea6d11d..e8536546f 100644 --- a/.github/workflows/cross.yml +++ b/.github/workflows/cross.yml @@ -104,9 +104,14 @@ jobs: # cross picks this up to compile std for tier-3 targets that ship none. CROSS_BUILD_STD: "true" run: | - cross +nightly build --locked --target ${{ matrix.target }} --no-default-features \ - -Z build-std=std,panic_abort || \ - cross +nightly build --locked --target ${{ matrix.target }} --no-default-features + if cross +nightly build --locked --target ${{ matrix.target }} --no-default-features \ + -Z build-std=std,panic_abort; then + exit 0 + fi + if cross +nightly build --locked --target ${{ matrix.target }} --no-default-features; then + exit 0 + fi + echo "::warning title=Experimental cross-build unavailable::${{ matrix.target }} failed both the build-std and prebuilt-std probes" # rustup-only targets that need no Docker (build on the host toolchain). rustup-targets: diff --git a/docs/specifications/arm/rtabi32-2025Q4.provenance.md b/docs/specifications/arm/rtabi32-2025Q4.provenance.md new file mode 100644 index 000000000..24a5d7add --- /dev/null +++ b/docs/specifications/arm/rtabi32-2025Q4.provenance.md @@ -0,0 +1,15 @@ +# Arm Run-time ABI (RTABI32) provenance + +- Canonical title: *Run-time ABI for the Arm® Architecture* +- Issuing organization: Arm Limited +- Release: 2025Q4 +- Date of issue: 23 January 2026 +- Upstream commit: `ee4b3c12d57c8424ff60c2ae56e10690d0604ab6` +- Source: https://github.com/ARM-software/abi-aa/blob/ee4b3c12d57c8424ff60c2ae56e10690d0604ab6/rtabi32/rtabi32.rst +- Retrieved: 11 August 2026 +- SHA-256: `73182ab882f5ca016971111e8bb1ab25a289b828172afe321724fc685ada60c0` +- License: CC-BY-SA-4.0 with the Arm patent-license grant; the complete notice is retained in the imported source. + +The microkernel ARMv6 runtime helper uses the “Unaligned memory access” contract, +including the `__aeabi_uread4` signature, arbitrary-alignment requirement, +return-value rule, and permitted AAPCS32 register clobbers. diff --git a/docs/specifications/arm/rtabi32-2025Q4.rst b/docs/specifications/arm/rtabi32-2025Q4.rst new file mode 100644 index 000000000..19bbad6aa --- /dev/null +++ b/docs/specifications/arm/rtabi32-2025Q4.rst @@ -0,0 +1,1873 @@ +.. + Copyright (c) 2003, 2007-2009, 2012, 2015, 2018, 2020-2026, Arm Limited and its affiliates. All rights + reserved. CC-BY-SA-4.0 AND Apache-Patent-License See LICENSE file + for details + +.. |release| replace:: 2025Q4 +.. |date-of-issue| replace:: 23\ :sup:`rd` January 2026 +.. |copyright-date| replace:: 2003, 2007-2009, 2012, 2015, 2018, 2020-2026 +.. |footer| replace:: Copyright © |copyright-date|, Arm Limited and its + affiliates. All rights reserved. +.. |armarmv5_link| replace:: https://developer.arm.com/docs/ddi0100/latest/armv5-architecture-reference-manual +.. |armarmv7ar_link| replace:: https://developer.arm.com/docs/ddi0406/c/arm-architecture-reference-manual-armv7-a-and-armv7-r-edition +.. |armarmv7m_link| replace:: https://developer.arm.com/products/architecture/m-profile/docs/ddi0403/e/armv7-m-architecture-reference-manual + + +.. _AAPCS32: https://github.com/ARM-software/abi-aa/releases +.. _AAELF32: https://github.com/ARM-software/abi-aa/releases +.. _Addenda32: https://github.com/ARM-software/abi-aa/releases +.. _ARMARM: https://developer.arm.com/docs/ddi0406/c/arm-architecture-reference-manual-armv7-a-and-armv7-r-edition +.. _ARMv7MARM: https://developer.arm.com/products/architecture/m-profile/docs/ddi0403/e/armv7-m-architecture-reference-manual +.. _ARMv5ARM: https://developer.arm.com/docs/ddi0100/latest/armv5-architecture-reference-manual +.. _BSABI32: https://github.com/ARM-software/abi-aa/releases +.. _CLIBABI32: https://github.com/ARM-software/abi-aa/releases +.. _CPPABI32: https://github.com/ARM-software/abi-aa/releases +.. _DBGOVL32: https://github.com/ARM-software/abi-aa/releases +.. _EHABI32: https://github.com/ARM-software/abi-aa/releases +.. _FDPIC: https://github.com/mickael-guene/fdpic_doc +.. _GCPPABI: http://itanium-cxx-abi.github.io/cxx-abi/abi.html +.. _GDWARF: http://dwarfstd.org/Dwarf3Std.php +.. _IEEE754: http://grouper.ieee.org/groups/754/ +.. _LSB: http://www.linuxbase.org/ +.. _RTABI32: https://github.com/ARM-software/abi-aa/releases +.. _SCO-ELF: http://www.sco.com/developers/gabi/ +.. _SYM-VER: http://www.akkadia.org/drepper/symbol-versioning +.. _TLSDESC: http://www.fsfla.org/~lxoliva/writeups/TLS/paper-lk2006.pdf + +*********************************************** +Run-time ABI for the Arm\ :sup:`®` Architecture +*********************************************** + +.. class:: version + +|release| + +.. class:: issued + +Date of Issue: |date-of-issue| + +.. class:: logo + +.. image:: Arm_logo_blue_RGB.svg + :scale: 30% + +.. section-numbering:: + +.. raw:: pdf + + PageBreak oneColumn + + +Preamble +======== + +Abstract +-------- + +This document defines a run-time helper-function ABI for programs +written in Arm-Thumb assembly language, C, and C++. + +Keywords +-------- + +Run-time ABI, run-time library, helper functions + +Latest release and defects report +--------------------------------- + +Please check `Application Binary Interface for the Arm® Architecture +`_ for the latest +release of this document. + +Please report defects in this specification to the `issue tracker page +on GitHub +`_. + +.. raw:: pdf + + PageBreak + +License +------- + +This work is licensed under the Creative Commons +Attribution-ShareAlike 4.0 International License. To view a copy of +this license, visit http://creativecommons.org/licenses/by-sa/4.0/ or +send a letter to Creative Commons, PO Box 1866, Mountain View, CA +94042, USA. + +Grant of Patent License. Subject to the terms and conditions of this +license (both the Public License and this Patent License), each +Licensor hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this +section) patent license to make, have made, use, offer to sell, sell, +import, and otherwise transfer the Licensed Material, where such +license applies only to those patent claims licensable by such +Licensor that are necessarily infringed by their contribution(s) alone +or by combination of their contribution(s) with the Licensed Material +to which such contribution(s) was submitted. If You institute patent +litigation against any entity (including a cross-claim or counterclaim +in a lawsuit) alleging that the Licensed Material or a contribution +incorporated within the Licensed Material constitutes direct or +contributory patent infringement, then any licenses granted to You +under this license for that Licensed Material shall terminate as of +the date such litigation is filed. + +About the license +----------------- + +As identified more fully in the License_ section, this project +is licensed under CC-BY-SA-4.0 along with an additional patent +license. The language in the additional patent license is largely +identical to that in Apache-2.0 (specifically, Section 3 of Apache-2.0 +as reflected at https://www.apache.org/licenses/LICENSE-2.0) with two +exceptions. + +First, several changes were made related to the defined terms so as to +reflect the fact that such defined terms need to align with the +terminology in CC-BY-SA-4.0 rather than Apache-2.0 (e.g., changing +“Work” to “Licensed Material”). + +Second, the defensive termination clause was changed such that the +scope of defensive termination applies to “any licenses granted to +You” (rather than “any patent licenses granted to You”). This change +is intended to help maintain a healthy ecosystem by providing +additional protection to the community against patent litigation +claims. + +Contributions +------------- + +Contributions to this project are licensed under an inbound=outbound +model such that any such contributions are licensed by the contributor +under the same terms as those in the License_ section. + +Trademark notice +---------------- + +The text of and illustrations in this document are licensed by Arm +under a Creative Commons Attribution–Share Alike 4.0 International +license ("CC-BY-SA-4.0”), with an additional clause on patents. +The Arm trademarks featured here are registered trademarks or +trademarks of Arm Limited (or its subsidiaries) in the US and/or +elsewhere. All rights reserved. Please visit +https://www.arm.com/company/policies/trademarks for more information +about Arm’s trademarks. + +Copyright +--------- + +Copyright (c) |copyright-date|, Arm Limited and its affiliates. All rights +reserved. + +.. raw:: pdf + + PageBreak + +.. contents:: + :depth: 3 + +.. raw:: pdf + + PageBreak + +About this document +=================== + +Change control +-------------- + +Current status and anticipated changes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The following support level definitions are used by the Arm ABI specifications: + +**Release** + Arm considers this specification to have enough implementations, which have + received sufficient testing, to verify that it is correct. The details of these + criteria are dependent on the scale and complexity of the change over previous + versions: small, simple changes might only require one implementation, but more + complex changes require multiple independent implementations, which have been + rigorously tested for cross-compatibility. Arm anticipates that future changes + to this specification will be limited to typographical corrections, + clarifications and compatible extensions. + +**Beta** + Arm considers this specification to be complete, but existing + implementations do not meet the requirements for confidence in its release + quality. Arm may need to make incompatible changes if issues emerge from its + implementation. + +**Alpha** + The content of this specification is a draft, and Arm considers the + likelihood of future incompatible changes to be significant. + +All content in this document is at the **Release** quality level. + +Change history +^^^^^^^^^^^^^^ + +If there is no entry in the change history table for a release, there are no +changes to the content of the document for that release. + +.. table:: + + +------------+-------------------------------+---------------------------------------------------------+ + | Issue | Date | Change | + +============+===============================+=========================================================+ + | 1.0 | 30\ :sup:`th` October 2003 | First public release. | + +------------+-------------------------------+---------------------------------------------------------+ + | 2.0 | 24\ :sup:`th` March 2005 | Second public release. | + +------------+-------------------------------+---------------------------------------------------------+ + | 2.01 | 6\ :sup:`th` October 2005 | Added specifications of \_\_aeabi\_read\_tp() | + | | | (`Thread-local storage (new in v2.01)`_) and | + | | | \_\_cxa\_get\_exception\_ptr() | + | | | (`Exception-handling support`_). | + +------------+-------------------------------+---------------------------------------------------------+ + | 2.02 | 23\ :sup:`rd` January 2007 | Deprecated fneg/dneg in `The floating-point helper | + | | | functions`_. | + +------------+-------------------------------+---------------------------------------------------------+ + | 2.03 | 10\ :sup:`th` October 2007 | In `Private names for private and AEABI-specific helper | + | | | functions`_, replaced table by table | + | | | shared with AAELF. Clarified | + | | | `Integer (32/32 → 32) division functions`_, integer | + | | | division. Updated the Arm ARM reference to include the | + | | | version from `www.arm.com `__. | + +------------+-------------------------------+---------------------------------------------------------+ + | A, r2.06 | 25\ :sup:`th` October 2007 | Document renumbered (formerly GENC-003537 v2.03). | + +------------+-------------------------------+---------------------------------------------------------+ + | B, r2.07 | 10\ :sup:`th` October 2008 | Add return value comments to \_\_aeabi\_\* helper | + | | | functions in `Helper functions defined by the C++ ABI | + | | | for the Arm Architecture`_. | + +------------+-------------------------------+---------------------------------------------------------+ + | C, r2.08 | 19\ :sup:`th` October 2009 | Added `\_\_hardfp\_ name mangling`_ to explain legacy, | + | | | deprecated \_\_hardfp\_ name mangling; in `The | + | | | floating-point helper functions`_, declared fneg/dneg | + | | | *obsolete*; improved text specifying the registers | + | | | maybe affected by a call to an FP helper; added | + | | | conversion helpers between VFPv3 half-precision and | + | | | float to `Standard conversions between | + | | | floating types`_. | + +------------+-------------------------------+---------------------------------------------------------+ + | D, r2.09 | 30\ :sup:`th` November 2012 | In `Base requirements on AEABI-complying FP helper | + | | | functions`_, updated [ARM ARM] reference for signaling | + | | | NaNs. In `The floating-point helper functions`_, | + | | | removed \_\_aeabi\_dneg and \_\_aeabi\_fneg obsoleted | + | | | in r2.08, and added conversion helpers from double to | + | | | VFPv3 half-precision to `Standard | + | | | conversions between floating types`_. | + +------------+-------------------------------+---------------------------------------------------------+ + | 2018Q4 | 21\ :sup:`st` December 2018 | In `Standard conversions between floating | + | | | types`_, specified handling of infinity and NaN in | + | | | f2h_alt and d2h_alt. | + +------------+-------------------------------+---------------------------------------------------------+ + | 2020Q4 | 21\ :sup:`st` December 2020 | - document released on Github | + | | | - new License_: CC-BY-SA-4.0 | + | | | - new sections on Contributions_, | + | | | `Trademark notice`_, and Copyright_ | + +------------+-------------------------------+---------------------------------------------------------+ + +References +---------- + +This document refers to, or is referred to by, the following. + +.. class:: rtabi32-refs + +.. table:: + + +----------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------+ + | Ref | URL or other reference | Title | + +============================+======================================================================================================+=================================================================================+ + | AAELF32_ | | ELF for the Arm Architecture. | + +----------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------+ + | AAPCS32_ | | Procedure Call Standard for the Arm Architecture | + +----------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------+ + | BSABI32_ | | ABI for the Arm Architecture (Base Standard) | + +----------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------+ + | CLIBABI32_ | | C Library ABI for the Arm Architecture | + +----------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------+ + | CPPABI32_ | | C++ ABI for the Arm Architecture | + +----------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------+ + | EHABI32_ | | Exception Handling ABI for the Arm Architecture | + +----------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------+ + | RTABI32_ | | Run-time ABI for the Arm Architecture (*This document*) | + +----------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------+ + | Addenda32_ | | Addenda to, and Errata in, the ABI for the Arm Architecture | + +----------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------+ + | ARMARM_ | |armarmv7ar_link| | Arm DDI 0406: Arm Architecture Reference Manual Arm v7-A and Arm v7-R edition | + | +------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------+ + | ARMv7MARM_ | |armarmv7m_link| | Arm DDI 0403C: Armv7-M Architecture Reference Manual | + +----------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------+ + | ARMV5ARM_ | |armarmv5_link| | Arm DDI 0100I: Armv5 Architecture Reference Manual | + +----------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------+ + | GCPPABI_ | http://itanium-cxx-abi.github.io/cxx-abi/abi.html | Generic C++ ABI | + +----------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------+ + | IEEE754_ | http://grouper.ieee.org/groups/754/ | IEEE P754 Standard for Floating-Point Arithmetic | + +----------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------+ + +Terms and abbreviations +----------------------- + +The *ABI for the Arm Architecture* uses the following terms and +abbreviations. + +AAPCS + Procedure Call Standard for the Arm Architecture. + +ABI + Application Binary Interface: + + 1. The specifications to which an executable must conform in order to + execute in a specific execution environment. For example, the + :title-reference:`Linux ABI for the Arm Architecture`. + + 2. A particular aspect of the specifications to which independently + produced relocatable files must conform in order to be + statically linkable and executable. For example, the CPPABI32_, + the RTABI32_, the CLIBABI32_. + +AEABI + (Embedded) ABI for the Arm architecture (this ABI...) + +Arm-based + ... based on the Arm architecture ... + +core registers + The general purpose registers visible in the Arm architecture’s + programmer’s model, typically r0-r12, SP, LR, PC, and CPSR. + +EABI + An ABI suited to the needs of embedded, and deeply embedded (sometimes + called free standing), applications. + +Q-o-I + Quality of Implementation – a quality, behavior, functionality, or + mechanism not required by this standard, but which might be provided + by systems conforming to it. Q-o-I is often used to describe the + toolchain-specific means by which a standard requirement is met. + +VFP + The Arm architecture’s Floating Point architecture and instruction + set. In this ABI, this abbreviation includes all floating point + variants regardless of whether or not vector (V) mode is supported. + +Acknowledgements +---------------- + +This specification has been developed with the active support of the +following organizations. In alphabetical order: Arm, CodeSourcery, +Intel, Metrowerks, Montavista, Nexus Electronics, PalmSource, Symbian, +Texas Instruments, and Wind River. + +.. raw:: pdf + + PageBreak + +Scope +===== + +Conformance to the *ABI for the Arm architecture* is intended to support +inter-operation between: + +- Relocatable files generated by different toolchains. + +- Executable and shared object files generated for the same execution + environment by different toolchains. + +This *standard for run-time helper functions* allows a relocatable file +built by one conforming toolchain from Arm-Thumb assembly language, C, +or stand alone C++ to be compatible with the static linking environment +provided by a different conforming toolchain. + +.. _Inter-operation between relocatable files: + +.. rubric:: Inter-operation between relocatable files + +.. figure:: rtabi32-inter-operation.svg + +In this model of inter-working, the standard headers used to build a +relocatable file are those associated with the toolchain building it, +not those associated with the library with which the relocatable fille +will, ultimately, be linked. + +Introduction +============ + +A number of principles of inter-operation are implicit in, or +compatible with, `Inter-operation between relocatable files`_ above. +This section describes these principles as they apply to run-time +helper functions, and gives a rationale for each one. The +corresponding section of CLIBABI32_ discusses the same issues as they +apply to C library functions. + +References between separately built relocatable files +----------------------------------------------------- + +A relocatable file can refer to functions and data defined in other +relocatable files or libraries. + +**Application headers describe application entities** + +Entities defined in application relocatable files are declared in +application header files (*“header”* in `Inter-operation between +relocatable files`_). + +- An application header file must describe the same binary interface to + declared data and functions, to every ABI-conforming compiler that + reads it. + +- Tool-chain-specific information in such header files must affect only + the quality of implementation of the relocatable files whose sources + includes the headers, not their binary interfaces. + +**Rationale**: A relocatable file or library is distributed with a set +of header files describing its interface. Different compilers must +interpret the underlying binary interface description identically. +Nevertheless, some compilers might comprehend pragmas or +pre-processor-guarded language extensions that cause better code to be +generated, or that trigger behavior that does not affect the binary +compatibility of interfaces. + +**Standard (system) headers describe run-time libraries** + +In general, entities defined in run-time libraries are declared in +standard (or system) header files (*
* in `Inter-operation +between relocatable files`_). A standard header need not be +intelligible to any toolchain other than the one that provides it. + +**Rationale**: Some language-standardized behavior cannot be securely or +conveniently described in source-language terms that all compilers +implement identically (for example, va\_start and va\_arg from C’s +stdarg.h). + +So, a relocatable file must be built using the standard headers +associated with the compiler building it. + +Standardized compiler helper functions +-------------------------------------- + +Each static linking environment shall provide a set of standard *helper +functions* defined by this ABI. See `The Standard Compiler Helper Function Library`_, for a list of +standardized helper functions. + +A helper function is one that a relocatable file might refer to even +though its source includes no standard headers (or, indeed, no headers +at all). A helper function usually implements some aspect of a +programming language not implemented by its standard library (for +example, from C, floating-point to integer conversions). + +In some cases, a helper function might implement some aspect of standard +library behavior not implemented by any of its interface functions (for +example, from the C library, *errno*). + +A helper function might also implement an operation not implemented by +the underlying hardware, for example, integer division, floating-point +arithmetic, or reading and writing misaligned data. + +Examples of run-time helper functions include those to perform integer +division, and floating-point arithmetic by software, and those required +to support the processing of C++ exceptions. + +Each such function has a defined type signature, a precise (often +simple) meaning, and a small set of standard names (there may be more +than one name for a helper function). + +Rationale for standardizing helper functions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +There is a mixture of convenience, opportunism, and necessity. + +- Without standard helper functions, each relocatable file would have + to carry all of its support functions with it, either in ELF COMDAT + groups within the relocatable file itself or in an adjunct library. + +- Multiple toolchains (at least from Arm and GNU) implement + essentially compatible floating-point arithmetic functions. + (Corresponding functions have identical type signatures and + semantics, but different names). + +- In C++, even if no system headers are included, inter-working is only + possible if implementations agree on the helpers to use in + construction, destruction, and throwing exceptions. + +Private helper functions must be carried with the using file +------------------------------------------------------------ + +A needed helper function that is not available in all ABI-complying +environments—any helper not standardized by this ABI component—must be +supplied with the relocatable file that needs it. There are two ways to +do this. + +- Provide the required helpers in a separate library (see `Library file organization`_) and + provide the library with any relocatable file that might refer to it. + +- Include the helpers in additional sections within the relocatable + file in named ELF COMDAT groups. This is the standard way to + distribute C++ constructors, destructors, out-of-line copies of + inline functions, etc. + +We encourage use of the second (COMDAT group) method, though the choice +of method is properly a quality of implementation concern for each toolchain +provider. + +Some private functions might nonetheless be standardized +-------------------------------------------------------- + +The first issue of this ABI defines no functions in this class. However, +new helper functions would first be added as standardized private helper +functions, until implementations of helper-function libraries caught up. + +Many run-time functions do not have a standard ABI +-------------------------------------------------- + +In general, it is very hard to standardize the C++ library using the +approach to library standardization outlined here and in CLIBABI32_. The +C++ standard allows an implementation to inline any of the library +functions [17.4.4.3, 17.4.4.4] and to add private members to any C++ +library class [17.3.2.3]. In general, implementations use this latitude, +and there is no ubiquitous standard implementation of the C++ library. + +In effect, C++ library headers define an API, not an ABI. To +inter-work with a particular C++ library implementation requires that +the compiler read the matching header files, breaking the model +depicted in `Inter-operation between relocatable files`_, above. + +A run-time library is all or nothing +------------------------------------ + +In general, we cannot expect a helper function from vendor A's library +to work with a different helper function from vendor B's library. +Although most helper functions will be independent leaf (or near leaf) +functions, tangled clumps of implementation could underlie apparently +independent parts of a run-time library's public interface. + +In some cases, there may be inter-dependencies between run-time +libraries, the static linker, and the ultimate execution environment. +For example, the way that a program acquires its startup code (sometimes +called crt0.o) may depend on the run-time library and the static linker. + +This leads to a major conclusion for statically linked executables: +**the static linker and the run-time libraries must be from the same +toolchain**. + +Accepting this constraint gives considerable scope for private +arrangements (not governed by this ABI) between these toolchain +components, restricted only by the requirement to provide a well defined +binary interface (ABI) to the functions described in `The Standard Compiler Helper Function Library`_. + +Important corollaries of this library standardization model +----------------------------------------------------------- + +System headers *can* require compiler-specific functionality (e.g. for +handling va\_start, va\_arg, etc). The resulting binary code must +conform to this ABI. + +As far as this ABI is concerned, a standard library header is processed +only by a matching compiler. A platform ABI can impose further +constraints that cause more compilers to match, but this ABI does not. + +This ABI defines the full set of public helper functions available in +every conforming execution environment. + +Every toolchain's run-time library must implement the full set of +public helper functions defined by this ABI. + +Private helper functions can call other private helper functions, public +helper functions, and language-standard-defined library functions. A +private helper function must not call any function that requires a +specific implementation of a language run-time library or helper +library. + +The implementation of a private helper function (and that of each +private helper function it calls) must be offered in a COMDAT group +within the ELF [AAELF32_] relocatable file that needs it, or in a *freely +re-distributable* library (`Library file organization`_) provided by the toolchain as an +adjunct to the relocatable file. + +(*Freely re-distributable* means: Distributable on terms no more +restrictive than those applying to any generated relocatable file). + +Private names for private and AEABI-specific helper functions +------------------------------------------------------------- + +External names used in the implementation of private helper functions +and private helper data must be in the vendor-specific name space +reserved by this ABI. All such names have the form +\_\_\ *vendor-prefix\_name*. + +The vendor prefix must be registered with the maintainers of this ABI +specification. Prefixes must not contain underscore ('\_') or dollar +('$'). Prefixes starting with *Anon* and *anon* are reserved for +unregistered private use. + +For example (from the C++ exception handling ABI): + + \_\_\ **aeabi**\ \_unwind\_cpp\_pr0 + \_\_\ **ARM**\ \_Unwind\_cpp\_prcommon + +The current list of registered vendor, and pseudo vendor, prefixes is +given in the following table. + +.. table:: Registered Vendors + + +-------------------+---------------------------------------------------------------------+ + | Name | Vendor | + +===================+=====================================================================+ + | ``ADI`` | Analog Devices | + +-------------------+---------------------------------------------------------------------+ + | ``acle`` | Reserved for use by Arm C Language Extensions. | + +-------------------+---------------------------------------------------------------------+ + | ``aeabi`` | Reserved to the ABI for the Arm Architecture (EABI pseudo-vendor) | + +-------------------+---------------------------------------------------------------------+ + | | ``Anon``\ *Xyz* | Reserved to private experiments by the Xyz vendor. | + | | ``anon``\ *Xyz* | Guaranteed not to clash with any registered vendor name. | + +-------------------+---------------------------------------------------------------------+ + | ``ARM`` | Arm Ltd (Note: the company, not the processor). | + +-------------------+---------------------------------------------------------------------+ + | ``cxa`` | C++ ABI pseudo-vendor | + +-------------------+---------------------------------------------------------------------+ + | ``dig`` | Dignus, LLC | + +-------------------+---------------------------------------------------------------------+ + | ``FSL`` | Freescale Semiconductor Inc. | + +-------------------+---------------------------------------------------------------------+ + | ``GHS`` | Green Hills Systems | + +-------------------+---------------------------------------------------------------------+ + | ``gnu`` | GNU compilers and tools (Free Software Foundation) | + +-------------------+---------------------------------------------------------------------+ + | ``iar`` | IAR Systems | + +-------------------+---------------------------------------------------------------------+ + | ``icc`` | ImageCraft Creations Inc (*ImageCraft C Compiler*) | + +-------------------+---------------------------------------------------------------------+ + | ``intel`` | Intel Corporation | + +-------------------+---------------------------------------------------------------------+ + | ``ixs`` | Intel Xscale | + +-------------------+---------------------------------------------------------------------+ + | ``llvm`` | The LLVM/Clang projects | + +-------------------+---------------------------------------------------------------------+ + | ``mchp`` | Microchip Technology Inc. | + +-------------------+---------------------------------------------------------------------+ + | ``PSI`` | PalmSource Inc. | + +-------------------+---------------------------------------------------------------------+ + | ``RAL`` | Rowley Associates Ltd | + +-------------------+---------------------------------------------------------------------+ + | ``SEGGER`` | SEGGER Microcontroller GmbH | + +-------------------+---------------------------------------------------------------------+ + | ``somn`` | SOMNIUM Technologies Limited. | + +-------------------+---------------------------------------------------------------------+ + | ``TASKING`` | Altium Ltd. | + +-------------------+---------------------------------------------------------------------+ + | ``TI`` | TI Inc. | + +-------------------+---------------------------------------------------------------------+ + | ``tls`` | Reserved for use in thread-local storage routines. | + +-------------------+---------------------------------------------------------------------+ + | ``WRS`` | Wind River Systems. | + +-------------------+---------------------------------------------------------------------+ + +To register a vendor prefix with Arm, please E-mail your request to +arm.eabi at arm.com. + +Library file organization +------------------------- + +Libraries that must be portable between complying toolchains – such as +adjunct libraries of private helper functions (`Private helper functions must be carried with the using file`_), and libraries of +run-time helper functions that comply with this specification (`The Standard Compiler Helper Function Library`_) and +are intended to be used with other toolchains’ linkers – must satisfy +the following conditions. + +- The library file format is the **ar** format described in BSABI32_. + +- It must not matter whether libraries are searched once or repeatedly + (this is Q-o-I). + +- Multiple adjunct libraries can appear in any order in the list of + libraries given to the linker provided that they precede all + libraries contributing to the run-time environment. + +In general, this requires accepting the following organizational +constraints. + +- No member of an adjunct library can refer to a member of any other + library other than to an entity specified by this ABI that + contributes to the run-time environment. + +- The names of adjunct members must be in a vendor-private name space + (`Private names for private and AEABI-specific helper functions`_). + +- If run-time environment support functions are provided in multiple + libraries, and these are intended to be usable by other + ABI-conforming linkers, it must be possible to list the libraries in + at least one order in which each reference between them is from a + library to one later in the order. This order must be documented. + +\_\_hardfp\_ name mangling +-------------------------- + +This section describes a name-mangling convention adopted by armcc (Arm +Limited’s commercial compiler) six years before this ABI was published +and three years before ABI development began. The name mangling is +unnecessary under this ABI so we now deprecate it. Obviously, compilers +in service will continue to generate the names for some time. + +A goal of this ABI is to support the development of portable binary code +but the lack of ubiquity of the floating-point (FP) instruction set +causes a problem if the code uses FP values in its interface functions. + +- Code that makes no use of FP values can be built to the *Base + Procedure Call Standard* [AAPCS32_] and will be compatible with an + application built to the base standard or the VFP procedure call + standard [AAPCS32_, section 'The Standard Variants']. + +- Portable binary code that makes heavy use of FP will surely be + offered in two variants: base-standard for environments that lack FP + hardware and VFP-standard otherwise. + +- Portable binary code that makes only light use of floating point + might reasonably be offered in the base standard only with its + FP-using functions declared in its supporting header files as + base-standard interfaces using some Q-o-I means such as decoration + with ``__softfp`` or ``__ATTRIBUTE((softfp))__``. + +The third use case causes a potential problem. + +- Both the portable code and the application that uses it might refer + to the same standard library function (such as ``strtod()`` or ``sin()``). + +- The portable code will expect a base-standard interface and the + application will expect a VFP-standard interface. The variants are + not call-compatible. + +The scope of this problem is precisely: all non-variadic standard +library functions taking floating-point parameters or delivering +floating-point results. + +Implicit calls to conversion functions that arise from expressions such +as ``double d = (double) int_val`` can also cause difficulties. A call is +either to a floating-point (FP) helper function (such as ``__aeabi_i2d``, +`Standard integer to floating-point conversions`_, below]) defined by this ABI (`The floating-point helper functions`_) or to a private helper +function. The FP helpers defined by this ABI cause no difficulties +because they always use a base-standard interface but a private helper +function would suffer the same problem as ``strtod()`` or ``sin()`` if the same +toolchain were used to build the application and the portable binary +and the helper function were not forced to have a base-standard +interface. + +The 1999 (pre-ABI) solution to this problem (first adopted by ADS 1.0) +was as follows. + +- Identify those functions that would be expected to have VFP-standard + interfaces when used in a VFP-standard application (such as ``strtod`` + and ``sin``). + +- Mangle the name of the VFP-standard variant of each of these + functions using the prefix ``__hardfp``. + +In 1999, VFP was not widely deployed in Arm-based products so it was +reasonable to load these inter-operation costs on users of the VFP +calling standard. + +Today, this ABI defines a clean way for toolchains to support this +functionality without resorting to encoding the interface standard in a +function’s name. The ``Tag_ABI_VFP_args`` build attribute in Addenda32_ +records the interface intentions of a producer. In principle, this tag +gives enough information to a toolchain to allow it to solve, using its +own Q-o-I means, the problem described in this section that arises from +the third use case. + +The problem described in this section arises in the most marginal of the +three portable-code use cases described in the bullet points at the +beginning of this section so we now recommend that toolchains should +*not* mangle the affected names (essentially the functions described by +the C library’s ```` and some from ````). + +The Standard Compiler Helper Function Library +============================================= + +Floating-point library +---------------------- + +The floating point model +^^^^^^^^^^^^^^^^^^^^^^^^ + +The floating point model is based on [IEEE754_] floating-point number +representations and arithmetic. Base requirements on helper functions +and restrictions on usage by client code are listed below. + +ABI-complying helper function libraries may provide more functionality +than is specified here, perhaps a full implementation of the IEEE 754 +specification, but ABI-complying application code must not require more +than the specified subset (save by private contract with the execution +environments). + +The set of helper functions has been designed so that: + +- A full IEEE implementation is a natural super-set. + +- A producer can ensure that, by carefully choosing the correct helper + function for the purpose, the intended application behavior does not + change inappropriately if the helper-function implementations support + more than the ABI-required, IEEE 754-specified behavior. + +Base requirements on AEABI-complying FP helper functions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Helper functions must correctly process all IEEE 754 single- and +double-precision numbers, including -0 and ±infinity, using the *round +to nearest* rounding mode. + +Floating-point exceptions are untrapped, so invalid operations must +generate a default result. + +If the implementation supports NaNs, the following requirements hold in +addition to those imposed on processing by IEEE 754. + +- All IEEE NaN bit patterns with the most significant bit of the + significand set are quiet, and all with the most significant bit + clear are signaling (as defined by [ARM ARM], chapter A2, Application + Level Programmers’ Model). + +- When not otherwise specified by IEEE 754, the result on an invalid + operation should be the quiet bit pattern with only the most + significant bit of the significand set, and all other significand + bits zero. + +**Dispensation – de-normal numbers** + +De-normal numbers may be flushed to zero in an implementation-defined +way. + +We permit de-normal flushing in deference to hardware implementations of +floating-point, where correct IEEE 754 behavior might require supporting +code that would be an unwelcome burden to an embedded system. + +Implementations that flush to zero will violate the Java numerical +model, but we recognize that: + +- Often, higher performance and smaller code size legitimately outweigh + floating-point accuracy concerns. + +- High quality floating-point behavior inevitably requires application + code to be aware of the floating-point properties of its execution + environment. Floating-point code that has onerous requirements (rare + in embedded applications) must advertise this. + +Software-only implementations should correctly support de-normal +numbers. + +**Dispensations relating to NaNs** + +An implementation need not process or generate NaNs. In this case, the +result of each invalid operation is implementation defined (and could, +for example, simply be ±zero). + +If NaNs are supported, it is only required to recognize, process, and +convert those values with at least one bit set in the 20 most +significant bits of the mantissa. Remaining bits should be zero and can +be ignored. When a quiet NaN of one precision is converted to a quiet of +the other precision, the most significant 20 bits of the mantissa must +be preserved. Consequently: + +- A NaN can be recognized by processing the most significant or only word + of the representation. The least significant word of a double can be + ignored (it should be zero). + +- Each ABI-complying value has a single-precision representation, and a + corresponding double-precision representation in which the least + significant word is zero. + +- Each ABI-complying NaN value is converted between single- and + double-precision in the same way that Arm VFP VCVT instructions + convert the values. + +Restrictions on FP usage by ABI-complying programs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The rounding mode is fixed as round to nearest. This is the IEEE 754 +default when a program starts and the state required by the Java +numerical model. A conforming client must not change the rounding mode. + +Conforming clients must not fabricate bit patterns that correspond to +de-normal numbers. A de-normal number must only be generated as a result +of operating on normal numbers (for example, subtracting two very close +values). A de-normal number may be flushed to zero on input to, or on +output from, a helper function. + +There are no floating-point exceptions. This is the IEEE 754 default +when a program starts. A conforming client must not change the exception +trap state or attempt to trap IEEE exceptions. + +Conforming clients must not directly fabricate bit patterns that +correspond to NaNs. A NaN can only be generated as a result of an operation +on normal numbers (for example, subtracting +infinity from +infinity or +multiplying ±infinity by ±zero). + +A conforming client must not rely on generating a NaN by operating on +normal numbers as described above. + +A NaN-using client must use only those values having at least one bit +set in the 20 most significant mantissa bits, and all other mantissa +bits zero. + +The floating-point helper functions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The functions defined in this section use software floating-point (*Base +Procedure Call Standard* [AAPCS32_]) calling and result-returning +conventions, even when they are implemented using floating-point +hardware. That is, parameters to and results from them are passed in +*integer* *core registers*. + +The functions defined in `Standard double precision floating-point +arithmetic helper functions`_, `Standard double precision +floating-point comparison helper functions`_, `Standard single +precision floating-point arithmetic helper functions`_, and `Standard +single precision floating-point comparison helper functions`_ together +implement the floating-point (FP) arithmetic operations from the FP +instruction set. The functions defined in `Standard floating-point to +integer conversions`_, `Standard conversions between floating types`_, +and `Standard integer to floating-point conversions`_ implement the +floating-point (FP) conversion operations from the FP instruction set, +the conversions between FP values and {unsigned} long long, and the +conversions between the VFPv3 half-precision storage-only binary +format and IEEE 754 binary32 (single precision) binary format. + +Implementations of these helper functions are allowed to corrupt the +integer core registers permitted to be corrupted by the AAPCS32_ (r0-r3, +ip, lr, and CPSR). + +If the FP instruction set is available, implementations of these +functions may use it. Consequently, FP hardware-using code that calls +one of these helper functions directly, *or indirectly by calling a +function with a base-standard interface*, must assume that the FP +parameter, result, scratch, and status registers might be altered by a +call to it. + +Binary functions take their arguments in source order where the order +matters. For example, ``__aeabi_op(x, y)`` computes x *op* y, not y *op* +x. The exceptions are **r**\ sub, and **r**\ cmple whose very purpose is +to operate the other way round. + +.. _Standard double precision floating-point arithmetic helper functions: + +.. table:: Standard double precision floating-point arithmetic helper functions + + +-----------------------------------------------+-----------------------------------------------+ + | Name and type signature | Description | + +===============================================+===============================================+ + | ``double __aeabi_dadd(double, double)`` | double-precision addition | + +-----------------------------------------------+-----------------------------------------------+ + | ``double __aeabi_ddiv(double n, double d)`` | double-precision division, n / d | + +-----------------------------------------------+-----------------------------------------------+ + | ``double __aeabi_dmul(double, double)`` | double-precision multiplication | + +-----------------------------------------------+-----------------------------------------------+ + | ``double __aeabi_drsub(double x, double y)`` | double-precision reverse subtraction, y – x | + +-----------------------------------------------+-----------------------------------------------+ + | ``double __aeabi_dsub(double x, double y)`` | double-precision subtraction, x – y | + +-----------------------------------------------+-----------------------------------------------+ + +.. _Standard double precision floating-point comparison helper functions: + +.. class:: rtabi32-float-comparisons + +.. table:: Standard double precision floating-point comparison helper functions + + +--------------------------------------------+-------------------------------------------------------------------+ + | Name and type signature | Description | + +============================================+===================================================================+ + | ``void __aeabi_cdcmpeq(double, double)`` | non-excepting equality comparison [1], result in PSR ZC flags | + +--------------------------------------------+-------------------------------------------------------------------+ + | ``void __aeabi_cdcmple(double, double)`` | 3-way (<, =, ?>) compare [1], result in PSR ZC flags | + +--------------------------------------------+-------------------------------------------------------------------+ + | ``void __aeabi_cdrcmple(double, double)`` | reversed 3-way (<, =, ?>) compare [1], result in PSR ZC flags | + +--------------------------------------------+-------------------------------------------------------------------+ + | ``int __aeabi_dcmpeq(double, double)`` | result (1, 0) denotes (=, ?<>) [2], use for C == and != | + +--------------------------------------------+-------------------------------------------------------------------+ + | ``int __aeabi_dcmplt(double, double)`` | result (1, 0) denotes (<, ?>=) [2], use for C < | + +--------------------------------------------+-------------------------------------------------------------------+ + | ``int __aeabi_dcmple(double, double)`` | result (1, 0) denotes (<=, ?>) [2], use for C <= | + +--------------------------------------------+-------------------------------------------------------------------+ + | ``int __aeabi_dcmpge(double, double)`` | result (1, 0) denotes (>=, ?<) [2], use for C >= | + +--------------------------------------------+-------------------------------------------------------------------+ + | ``int __aeabi_dcmpgt(double, double)`` | result (1, 0) denotes (>, ?<=) [2], use for C > | + +--------------------------------------------+-------------------------------------------------------------------+ + | ``int __aeabi_dcmpun(double, double)`` | result (1, 0) denotes (?, <=>) [2], use for C99 ``isunordered()`` | + +--------------------------------------------+-------------------------------------------------------------------+ + +.. note:: + + Notes on `Standard double precision floating-point comparison helper + functions`_, above, and `Standard single precision floating-point + comparison helper functions`_, below + + 1. The 3-way comparison functions ``c*cmple``, ``c*cmpeq`` and ``c*rcmple`` + return their results in the CPSR Z and C flags. C is clear only if + the operands are ordered and the first operand is less than the + second. Z is set only when the operands are ordered and equal. + + This means that ``c*cmple`` is the appropriate helper to use for C language + < and ≤ comparisons. + + For > and ≥ comparisons, the order of operands to the comparator and the + sense of the following branch condition must both be reversed. For + example, to implement ``if (a > b) {...} else L1``, use: + + | ``__aeabi_cdcmple(b, a); BHS L1``; or + | ``__aeabi_cdrcmple(a, b); BHS L1``. + + The ``*rcmple`` functions may be implemented as operand swapping veneers + that tail-call the corresponding versions of ``cmple``. + + When implemented to the full IEEE specification, ``*le`` helpers + potentially throw exceptions when comparing with quiet NaNs. The ``*eq`` + helpers do not. Of course, all comparisons will potentially throw + exceptions when comparing with signaling NaNs. + + Minimal implementations never throw exceptions. In the absence of NaNs, + ``c*cmpeq`` can be an alias for ``c*cmple``. + + The 3-way, status-returning comparison functions preserve all core + registers except ip, lr, and the CPSR. + + 2. The six Boolean versions ``*cmp*`` return 1 or 0 in r0 to denote the + truth or falsity of the IEEE predicate they test. As in note1, all + except ``*cmpeq`` and ``*cmpun`` can throw an exception when comparing a + quiet. + +.. _Standard single precision floating-point arithmetic helper functions: + +.. table:: Standard single precision floating-point arithmetic helper functions + + +--------------------------------------------+-----------------------------------------------+ + | Name and type signature | Description | + +============================================+===============================================+ + | ``float __aeabi_fadd(float, float)`` | single-precision addition | + +--------------------------------------------+-----------------------------------------------+ + | ``float __aeabi_fdiv(float n, float d)`` | single-precision division, n / d | + +--------------------------------------------+-----------------------------------------------+ + | ``float __aeabi_fmul(float, float)`` | single-precision multiplication | + +--------------------------------------------+-----------------------------------------------+ + | ``float __aeabi_frsub(float x, float y)`` | single-precision reverse subtraction, y – x | + +--------------------------------------------+-----------------------------------------------+ + | ``float __aeabi_fsub(float x, float y)`` | single-precision subtraction, x – y | + +--------------------------------------------+-----------------------------------------------+ + +.. _Standard single precision floating-point comparison helper functions: + +.. class:: rtabi32-float-comparisons + +.. table:: Standard single precision floating-point comparison helper functions + + +------------------------------------------+-------------------------------------------------------------------+ + | Name and type signature | Description | + +==========================================+===================================================================+ + | ``void __aeabi_cfcmpeq(float, float)`` | non-excepting equality comparison [1], result in PSR ZC flags | + +------------------------------------------+-------------------------------------------------------------------+ + | ``void __aeabi_cfcmple(float, float)`` | 3-way (<, =, ?>) compare [1], result in PSR ZC flags | + +------------------------------------------+-------------------------------------------------------------------+ + | ``void __aeabi_cfrcmple(float, float)`` | reversed 3-way (<, =, ?>) compare [1], result in PSR ZC flags | + +------------------------------------------+-------------------------------------------------------------------+ + | ``int __aeabi_fcmpeq(float, float)`` | result (1, 0) denotes (=, ?<>) [2], use for C == and != | + +------------------------------------------+-------------------------------------------------------------------+ + | ``int __aeabi_fcmplt(float, float)`` | result (1, 0) denotes (<, ?>=) [2], use for C < | + +------------------------------------------+-------------------------------------------------------------------+ + | ``int __aeabi_fcmple(float, float)`` | result (1, 0) denotes (<=, ?>) [2], use for C <= | + +------------------------------------------+-------------------------------------------------------------------+ + | ``int __aeabi_fcmpge(float, float)`` | result (1, 0) denotes (>=, ?<) [2], use for C >= | + +------------------------------------------+-------------------------------------------------------------------+ + | ``int __aeabi_fcmpgt(float, float)`` | result (1, 0) denotes (>, ?<=) [2], use for C > | + +------------------------------------------+-------------------------------------------------------------------+ + | ``int __aeabi_fcmpun(float, float)`` | result (1, 0) denotes (?, <=>) [2], use for C99 ``isunordered()`` | + +------------------------------------------+-------------------------------------------------------------------+ + +.. _Standard floating-point to integer conversions: + +.. class:: rtabi32-float-int-conversions + +.. table:: Standard floating-point to integer conversions + + +-----------------------------------------------+----------------------------------------------------------------+ + | Name and type signature | Description | + +===============================================+================================================================+ + | ``int __aeabi_d2iz(double)`` | double to integer C-style conversion [3] | + +-----------------------------------------------+----------------------------------------------------------------+ + | ``unsigned __aeabi_d2uiz(double)`` | double to unsigned C-style conversion [3] | + +-----------------------------------------------+----------------------------------------------------------------+ + | ``long long __aeabi_d2lz(double)`` | double to long long C-style conversion [3] | + +-----------------------------------------------+----------------------------------------------------------------+ + | ``unsigned long long __aeabi_d2ulz(double)`` | double to unsigned long long C-style conversion [3] | + +-----------------------------------------------+----------------------------------------------------------------+ + | ``int __aeabi_f2iz(float)`` | float (single precision) to integer C-style conversion [3] | + +-----------------------------------------------+----------------------------------------------------------------+ + | ``unsigned __aeabi_f2uiz(float)`` | float (single precision) to unsigned C-style conversion [3] | + +-----------------------------------------------+----------------------------------------------------------------+ + | ``long long __aeabi_f2lz(float)`` | float (single precision) to long long C-style conversion [3] | + +-----------------------------------------------+----------------------------------------------------------------+ + | ``unsigned long long __aeabi_f2ulz(float)`` | float to unsigned long long C-style conversion [3] | + +-----------------------------------------------+----------------------------------------------------------------+ + +.. note:: + + 3. The conversion-to-integer functions whose names end in *z* always + round towards zero, rather than going with the current or default + rounding mode. This makes them the appropriate ones to use for C + casts-to-integer, which are required by the C standard to round + towards zero. + +.. _Standard conversions between floating types: + +.. class:: rtabi32-float-float-conversions + +.. table:: Standard conversions between floating types + + +---------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | Name and type signature | Description | + +=======================================+==================================================================================================================================================================+ + | ``float __aeabi_d2f(double)`` | double to float (single precision) conversion | + +---------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | ``double __aeabi_f2d(float)`` | float (single precision) to double conversion | + +---------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | | ``float __aeabi_h2f(short hf)`` | IEEE 754 binary16 storage format (*VFP half precision*) to binary32 (float) conversion [4, 5]; ``__aeabi_h2f_alt`` converts from VFP *alternative format* [7]. | + | | ``float __aeabi_h2f_alt(short hf)`` | | + +---------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | | ``short __aeabi_f2h(float f)`` | IEEE 754 binary32 (float) to binary16 storage format (*VFP half precision*) conversion [4, 6]; ``__aeabi_f2h_alt`` converts to VFP *alternative format* [8]. | + | | ``short __aeabi_f2h_alt(float f)`` | | + +---------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | | ``short __aeabi_d2h(double)`` | IEEE 754 binary64 (double) to binary16 storage format (*VFP half precision*) conversion [4, 9]; ``__aeabi_d2h_alt`` converts to VFP *alternative format* [10]. | + | | ``short __aeabi_d2h_alt(double)`` | | + +---------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + +.. note:: + + 4. IEEE P754 binary16 format is a *storage-only* format on which no + floating-point operations are defined. Loading and storing such + values is supported through the integer instruction set rather than + the floating-point instruction set. Hence these functions convert + between 16-bit short and 32-bit or 64-bit float. In the *VFPv3 + alternative format* there are no NaNs or infinities and encodings + with maximum exponent value encode numbers. + + 5. h2f converts a 16-bit binary floating point bit pattern to the 32-bit + binary floating point bit pattern representing the same number, + infinity, zero, or NaN. A is converted by appending 13 0-bits to its + representation. + + 6. f2h converts a 32-bit binary floating point bit pattern to the 16-bit + binary floating point bit pattern representing the same number, + infinity, zero, or NaN. The least significant 13 bits of the + representation of a are lost in conversion. Unless altered by Q-o-I + means, rounding is RN, underflow flushes to zero, and overflow + generates infinity. + + 7. h2f\_alt converts a VFPv3 alternative-format 16-bit binary floating + point bit pattern to the IEEE-format 32-bit binary floating point bit + pattern that represents the same number. + + 8. f2h\_alt converts an IEEE-format 32-bit binary floating point bit + pattern to the VFPv3 alternative-format 16-bit binary floating point + bit pattern that represents the same number. Unless altered by Q-o-I + means, rounding is RN, underflow flushes to zero, overflows and + infinite inputs generate the largest representable number with the + input sign, and NaN inputs generate a zero with the input sign. + + 9. d2h converts a 64-bit binary floating point bit pattern to the 16-bit + binary floating point bit pattern representing the same number, + infinity, zero, or NaN. The least significant 42 bits of the + representation of a NaN are lost in conversion. Unless altered by + Q-o-I means, rounding is RN, underflow flushes to zero, and overflow + generates infinity. + + 10. d2h\_alt converts an IEEE-format 64-bit binary floating point bit + pattern to the VFPv3 alternative-format 16-bit binary floating point + bit pattern that represents the same number. Unless altered by Q-o-I + means, rounding is RN, underflow flushes to zero, overflows and + infinite inputs generate the largest representable number with the + input sign, and NaN inputs generate a zero with the input sign. + +.. _Standard integer to floating-point conversions: + +.. class:: rtabi32-float-int-conversions + +.. table:: Standard integer to floating-point conversions + + +----------------------------------------------+-------------------------------------------------------------+ + | Name and type signature | Description | + +==============================================+=============================================================+ + | ``double __aeabi_i2d(int)`` | integer to double conversion | + +----------------------------------------------+-------------------------------------------------------------+ + | ``double __aeabi_ui2d(unsigned)`` | unsigned to double conversion | + +----------------------------------------------+-------------------------------------------------------------+ + | ``double __aeabi_l2d(long long)`` | long long to double conversion | + +----------------------------------------------+-------------------------------------------------------------+ + | ``double __aeabi_ul2d(unsigned long long)`` | unsigned long long to double conversion | + +----------------------------------------------+-------------------------------------------------------------+ + | ``float __aeabi_i2f(int)`` | integer to float (single precision) conversion | + +----------------------------------------------+-------------------------------------------------------------+ + | ``float __aeabi_ui2f(unsigned)`` | unsigned to float (single precision) conversion | + +----------------------------------------------+-------------------------------------------------------------+ + | ``float __aeabi_l2f(long long)`` | long long to float (single precision) conversion | + +----------------------------------------------+-------------------------------------------------------------+ + | ``float __aeabi_ul2f(unsigned long long)`` | unsigned long long to float (single precision) conversion | + +----------------------------------------------+-------------------------------------------------------------+ + +The long long helper functions +------------------------------ + +The long long helper functions support 64-bit integer arithmetic. They +are listed in the following table. + +Most long operations can be inlined in fewer instructions than it takes +to marshal arguments to, and a result from, a function call. The +difficult functions that usually need to be implemented out of line are +listed in the table below. + +As in `The floating-point helper functions`_, binary functions operate +between the operands given in source text order (div(a, b) = a/b). + +The division functions produce both the quotient and the remainder, an +important optimization opportunity, because the function is large and +slow. + +The shift functions only need to work for shift counts in 0..63. +Compilers can efficiently inline constant shifts. + +.. _Long long functions: + +.. class:: rtabi32-long-long + +.. table:: Long long functions + + +----------------------------------------------------------------+---------------------------------------------------------------+ + | Name and type signature | Description | + +================================================================+===============================================================+ + | ``long long __aeabi_lmul(`` | multiplication [1] | + | ``long long, long long)`` | | + +----------------------------------------------------------------+---------------------------------------------------------------+ + | ``__value_in_regs lldiv_t __aeabi_ldivmod(`` | signed long long division and remainder, {q, r} = n / d [2] | + | ``long long n, long long d)`` | | + +----------------------------------------------------------------+---------------------------------------------------------------+ + | ``__value_in_regs ulldiv_t __aeabi_uldivmod(`` | unsigned long long division and remainder, {q, r} = n / d [2] | + | ``unsigned long long n,`` | | + | ``unsigned long long d)`` | | + +----------------------------------------------------------------+---------------------------------------------------------------+ + | ``long long __aeabi_llsl(long long, int)`` | logical shift left [1] | + +----------------------------------------------------------------+---------------------------------------------------------------+ + | ``long long __aeabi_llsr(long long, int)`` | logical shift right [1] | + +----------------------------------------------------------------+---------------------------------------------------------------+ + | ``long long __aeabi_lasr(long long, int)`` | arithmetic shift right [1] | + +----------------------------------------------------------------+---------------------------------------------------------------+ + | ``int __aeabi_lcmp(long long, long long)`` | signed long long comparison [3] | + +----------------------------------------------------------------+---------------------------------------------------------------+ + | ``int __aeabi_ulcmp(unsigned long long,`` | unsigned long long comparison [3] | + | ``unsigned long long)`` | | + +----------------------------------------------------------------+---------------------------------------------------------------+ + +.. note:: + + 1. Because of 2’s complement number representation, these functions work + identically with long long replaced uniformly by unsigned long long. + Each returns its result in {r0, r1}, as specified by the AAPCS32_. + + 2. A pair of (unsigned) long longs is returned in {{r0, r1}, {r2, r3}}, + the quotient in {r0, r1}, and the remainder in {r2, r3}. The + description above is written using Arm-specific function prototype + notation, though no prototype need be read by any compiler. (In the + table above, think of \_\_value\_in\_regs as a *structured comment*). + + 3. The comparison functions return negative, zero, or a positive integer + according to whether the comparison result is <, ==, or >, + respectively (like strcmp). In practice, compilers can inline all + comparisons using SUBS, SBCS (the test for equality needs 3 Thumb + instructions). + +Implementations of ldivmod and uldivmod have full AAPCS32_ privileges and +may corrupt any register permitted to be corrupted by an +AAPCS-conforming call. Thus, for example, an implementation may use a +co-processor that has a division, or division-step, operation. The +effect that such use has on the co-processor state is documented in a +co-processor supplement. + +Otherwise, implementations of the long long helper functions are allowed +to corrupt only the integer core registers permitted to be corrupted by +the AAPCS (r0-r3, ip, lr, and CPSR). + +Other C and assembly language helper functions +---------------------------------------------- + +Other helper functions include 32-bit (32/32 → 32) integer division +(`Integer (32/32 → 32) division functions`_), unaligned data access +functions (`Unaligned memory access`_) and functions to copy, move, +clear, and set memory (`Memory copying, clearing, and setting`_). + +Integer (32/32 → 32) division functions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The 32-bit integer division functions return the quotient in r0 or both +quotient and remainder in {r0, r1}. Below the 2-value-returning +functions are described using Arm-specific prototype notation, though it +is clear that no prototype need be read by any compiler (think of +\_\_value\_in\_regs as a *structured comment*). + +.. code-block:: c + + int __aeabi_idiv(int numerator, int denominator); + unsigned __aeabi_uidiv(unsigned numerator, unsigned denominator); + + typedef struct { int quot; int rem; } idiv_return; + typedef struct { unsigned quot; unsigned rem; } uidiv_return; + + __value_in_regs idiv_return __aeabi_idivmod(int numerator, int denominator); + __value_in_regs uidiv_return __aeabi_uidivmod(unsigned numerator, unsigned denominator); + +.. admonition:: Aside + :class: note + + Separate modulo functions would have little value because modulo on its own + is rare. Division by a constant and constant modulo can be inlined + efficiently using (64-bit) multiplication. For implementations in C, + \_\_value\_in\_regs can be emulated by tail-calling an assembler function + that receives the values to be returned as arguments and, itself, returns + immediately. + +Implementations of idiv, uidiv, idivmod, and uidivmod have full AAPCS32_ +privileges and may corrupt any register an AAPCS-conforming call may +corrupt. Thus, for example, an implementation may use a co-processor +that has a division, or division-step, operation. The effect that such +use has on co-processor state is documented in a separate co-processor +supplement. + +The division functions take the numerator and denominator in that order, +and produce the quotient in r0 or the quotient and the remainder in {r0, +r1} respectively. + +Integer division truncates towards zero and the following identities +hold if the quotient can be represented. + +.. code-block:: + + (numerator / denominator) = –(numerator / -denominator) + (numerator / denominator) * denominator + (numerator % denominator) = numerator + +The quotient can be represented for all input values except the +following. + +- ``denominator = 0`` (discussed in `Division by zero`_). + +- ``numerator = -2147483648`` (bit pattern ``0x80000000``), ``denominator = -1``. + (the number ``2147483648`` has no representation as a signed ``int``). + +In the second case an implementation may return any convenient value, +possibly the original numerator. + +Division by zero +^^^^^^^^^^^^^^^^ + +If an integer or long long division helper function is called upon to +divide by 0, it should return as quotient the value returned by a call +to \_\_aeabi\_idiv0 or \_\_aeabi\_ldiv0, respectively. A \*divmod helper +should return as remainder either 0 or the original numerator. + +.. admonition:: Aside + :class: note + + Ideally, a \*divmod function should return {infinity, 0} or {0, numerator}, + where *infinity* is an approximation. + +The \*div0 functions: + +- Return the value passed to them as a parameter. + +- Or, return a fixed value defined by the execution environment (such + as 0). + +- Or, raise a signal (often SIGFPE) or throw an exception, and do not + return. + + .. code-block:: c + + int __aeabi_idiv0(int return_value); + long long __aeabi_ldiv0(long long return_value); + +An application may provide its own implementations of the \*div0 +functions to force a particular behavior from \*div and \*divmod +functions called out of line. Implementations of \*div0 have full +AAPCS32_ privileges just like the \*div and \*divmod functions. + +The \*div and \*divmod functions may be inlined by a toolchain. It is +Q-o-I whether an inlined version calls \*div0 out of line or returns the +values that would have been returned by a particular value-returning +version of \*div0. + +Out of line implementations of the \*div and \*divmod functions call +\*div0 with the following parameter values. + +- 0 if the numerator is 0. + +- The largest value of the type manipulated by the calling division + function if the numerator is positive. + +- The least value of the type manipulated by the calling division + function if the numerator is negative. + +Unaligned memory access +^^^^^^^^^^^^^^^^^^^^^^^ + +These functions read and write 4-byte and 8-byte values at arbitrarily +aligned addresses. An unaligned 2-byte value can always be read or +written more efficiently using inline code. + +.. code-block:: c + + int __aeabi_uread4(void *address); + int __aeabi_uwrite4(int value, void *address); + long long __aeabi_uread8(void *address); + long long __aeabi_uwrite8(long long value, void *address); + +We expect unaligned floating-point values to be read and written as +integer bit patterns (if at all). + +Write functions return the value written, read functions the value read. + +Implementations of these functions are allowed to corrupt only the +integer core registers permitted to be corrupted by the AAPCS32_ (r0-r3, +ip, lr, and CPSR). + +Memory copying, clearing, and setting +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Memory copying** + +Memcpy-like helper functions are needed to implement structure +assignment. We define three functions providing various levels of +service, in addition to the normal ANSI C memcpy, and three variants of +memmove. + +.. code-block:: c + + void __aeabi_memcpy8(void *dest, const void *src, size_t n); + void __aeabi_memcpy4(void *dest, const void *src, size_t n); + void __aeabi_memcpy(void *dest, const void *src, size_t n); + void __aeabi_memmove8(void *dest, const void *src, size_t n); + void __aeabi_memmove4(void *dest, const void *src, size_t n); + void __aeabi_memmove(void *dest, const void *src, size_t n); + +These functions work like the ANSI C memcpy and memmove functions. +However, \_\_aeabi\_memcpy8 may assume that both of its arguments are +8-byte aligned, \_\_aeabi\_memcpy4 that both of its arguments are 4-byte +aligned. None of the three functions is required to return anything in +r0. + +Each of these functions can be smaller or faster than the general memcpy +or each can be an alias for memcpy itself, similarly for memmove. + +Compilers can replace calls to memcpy with calls to one of these +functions if they can deduce that the constraints are satisfied. For +example, any memcpy whose return value is ignored can be replaced with +\_\_aeabi\_memcpy. If the copy is between 4-byte-aligned pointers it can +be replaced with \_\_aeabi\_memcpy4, and so on. + +The size\_t argument does not need to be a multiple of 4 for the +4/8-byte aligned versions, which allows copies with a non-constant size +to be specialized according to source and destination alignment. + +Small aligned copies are likely to be inlined by compilers, so these +functions should be optimized for larger copies. + +**Memory clearing and setting** + +In similar deference to run-time efficiency we define reduced forms of +memset and memclr. + +.. code-block:: c + + void __aeabi_memset8(void *dest, size_t n, int c); + void __aeabi_memset4(void *dest, size_t n, int c); + void __aeabi_memset(void *dest, size_t n, int c); + void __aeabi_memclr8(void *dest, size_t n); + void __aeabi_memclr4(void *dest, size_t n); + void __aeabi_memclr(void *dest, size_t n); + +Note that relative to ANSI memset, \_\_aeabi\_memset has the order of +its second and third arguments reversed. This allows \_\_aeabi\_memclr +to tail-call \_\_aeabi\_memset. + +The memclr functions simplify a very common special case of memset, +namely the one in which c = 0 and the memory is being cleared to all +zeroes. + +The size\_t argument does not need to be a multiple of 4 for the +4/8-byte aligned versions, which allows clears and sets with a +non-constant size to be specialized according to the destination +alignment. + +In general, implementations of these functions are allowed to corrupt +only the integer core registers permitted to be corrupted by the AAPCS32_ +(r0-r3, ip, lr, and CPSR). + +If there is an attached device with efficient memory copying or clearing +operations (such as a DMA engine), its device supplement specifies +whether it may be used in implementations of these functions and what +effect such use has on the device’s state. + +Thread-local storage (new in v2.01) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In Addenda32_ (section 'Linux for Arm static (initial exec) model'), +the description of thread-local storage addressing refers to the thread +pointer denoted by **$tp** but does not specify how to obtain its value. + +.. code-block:: c + + void *__aeabi_read_tp(void); /* return the value of $tp */ + +Implementations of this function should corrupt only the result register +(r0) and the non-parameter integer core registers allowed to be +corrupted by the AAPCS32_ (ip, lr, and CPSR). Registers r1-r3 must be +preserved. + +C++ helper functions +-------------------- + +The C++ helper functions defined by this ABI closely follow those +defined by the *Generic C++ ABI* (see [GCPPABI_]). In this section, we +list the required helper functions with references to their generic +definitions and explain where the Arm C++ ABI diverges from the generic +one. + +Pure virtual function calls +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +See GC++ABI, §3.2.6, `Pure Virtual Function +API `__. This +ABI specification follows the generic ABI exactly. + +The v-table entry for a pure virtual function must be initialized to +\_\_cxa\_pure\_virtual. The effect of calling a pure virtual function is +not defined by the C++ standard. This ABI requires that the pure virtual +helper function shall be called which takes an abnormal termination +action defined by, and appropriate to, the execution environment. + +.. table:: The pure virtual helper function + + +-------------------------------------+---------------------------------------------------------------+ + | Name and type signature | Description | + +=====================================+===============================================================+ + | ``void __cxa_pure_virtual(void)`` | The initial value of a pure virtual function. | + | | Called if a not overridden pure virtual function is called. | + +-------------------------------------+---------------------------------------------------------------+ + +One-time construction API for (function-local) static objects +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +See GC++ABI, §3.3.2, `One-time Construction +API `__, +and CPPABI32_, section 'Guard variables and the one-time construction API'. + +This ABI specification diverges from the Itanium ABI by using 32-bit +guard variables and specifying the use of the least significant two bits +of a guard variable rather than first byte of it. + +A static object must be guarded against being constructed more than +once. In a threaded environment, the guard variable must also act as a +semaphore or a handle for a semaphore. Typically, only the construction +of function-local static objects needs to be guarded this way. + +A guard variable is a 32-bit, 4-byte aligned, static data value +(described in the following table, as int). The least significant 2 bits +must be statically initialized to zero. The least significant bit +(2\ :sup:`0`) is set to 1 when the guarded object has been successfully +constructed. The next most significant bit (2\ :sup:`1`) may be used by +the guard acquisition and release helper functions. The value and +meaning of other bits is unspecified. + +.. class:: rtabi32-cxa-guard + +.. table:: One-time construction API + + +------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+ + | Name and type signature | Description | + +==========================================+=============================================================================================================================+ + | Guard variable | A 32-bit, 4-byte-aligned static data value. The least significant 2 bits must be statically initialized to 0. | + +------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+ + | ``int __cxa_guard_acquire(int *gv)`` | If \*gv guards an object under construction, wait for construction to complete (guard released) or abort (guard aborted). | + | | Then, if \*gv guards a not-yet-constructed object, acquire the guard and return non-0. | + | | Otherwise, if \*gv guards a constructed object, return 0. | + +------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+ + | ``void __cxa_guard_release(int *gv)`` | | **Pre-condition**: \*gv acquired, guarded object constructed. | + | | | **Post-condition**: ((\*gv & 1) = 1), \*gv released. | + +------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+ + | ``void __cxa_guard_abort(int *gv)`` | | **Pre-condition**: \*gv acquired, guarded object not constructed. | + | | | **Post-condition**: ((\*gv & 3) = 0), \*gv released. | + +------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+ + +The one-time construction API functions may corrupt only the integer +core registers permitted to be corrupted by the AAPCS32_ (r0-r3, ip, lr, +and CPSR). + +The one-time construction API is expected to be used in the following +way. + +.. code-block:: c + + if ((obj_guard & 1) == 0) { + if ( __cxa_guard_acquire(&obj_guard) ) { + ... initialize the object ...; + ... queue object destructor with __cxa_atexit(); // See §4.4.5. + __cxa_guard_release(&obj_guard); + // Assert: (obj_guard & 1) == 1 + } + } + + +If the object constructor throws an exception, cleanup code can call +\_\_cxa\_guard\_abort to release the guard and reset its state to the +initial state. + +Construction and destruction of arrays +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +See GC++ABI, §3.3.3, `Array Construction and Destruction +API `__, +and CPPABI32_, section 'Array construction and destruction'. + +Helper functions defined by the generic C++ ABI +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This ABI follows the generic ABI closely. Differences from the generic +ABI are as follows. + +- This ABI gives \_\_cxa\_vec\_ctor and \_\_cxa\_vec\_cctor a void \* + return type instead of void. The value returned is the same as the first + parameter – a pointer to the array being constructed + +- This ABI specifies the same array cookie format whenever an array + cookie is needed. The cookie occupies 8 bytes, 8-byte aligned. It + contains two 4-byte fields, the element size followed by the element + count. + +Below we list the functions and their arguments. For details see the +references cited at the start of `Construction and destruction of arrays`_. + +.. code-block:: c + + void *__cxa_vec_new( + size_t count, size_t element_size, size_t cookie_size, + void (*ctor)(void *), void (dtor)(void *)); + void *__cxa_vec_new2( + size_t count, size_t element_size, size_t cookie_size, + void (*ctor)(void *this), void (*dtor)(void *this), + void *(*alloc)(size_t size), void (*dealloc)(void *object)); + void *__cxa_vec_new3( + size_t count, size_t element_size, size_t cookie_size, + void (*ctor)(void *this), void (*dtor)(void *this), + void *(*alloc)(size_t size), void (*dealloc)(void *object, size_t size)); + void *__cxa_vec_ctor( + void *vector, size_t count, size_t element_size, + void (*ctor)(void *this), void (*dtor)(void *this)); + void __cxa_vec_dtor( + void *vector, size_t count, size_t element_size, + void (*dtor)(void *this)); + void __cxa_vec_cleanup( + void *vector, size_t count, size_t element_size, + void (*dtor)(void *this)); + void __cxa_vec_delete( + void *vector, size_t element_size, size_t cookie_size, + void (*dtor)(void *this)); + void __cxa_vec_delete2( + void *vector, size_t element_size, size_t cookie_size, + void (*dtor)(void *this), + void (*dealloc)(void *object)); + void __cxa_vec_delete3( + void *vector, size_t element_size, size_t cookie_size, + void (*dtor)(void *this), + void (*dealloc)(void *object, size_t size)); + void *__cxa_vec_cctor( + void *destination, void *source, size_t count, size_t element_size, + void (*copy_ctor)(void *this, void *source), + void (*dtor)(void *this)); + +Helper functions defined by the C++ ABI for the Arm Architecture +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This ABI define the following new helpers which can be called more +efficiently. + +.. code-block:: + + __aeabi_vec_ctor_nocookie_nodtor + __aeabi_vec_ctor_cookie_nodtor + __aeabi_vec_cctor_nocookie_nodtor + __aeabi_vec_new_cookie_noctor + __aeabi_vec_new_nocookie + __aeabi_vec_new_cookie_nodtor + __aeabi_vec_new_cookie + __aeabi_vec_dtor + __aeabi_vec_dtor_cookie + __aeabi_vec_delete + __aeabi_vec_delete3 + __aeabi_vec_delete3_nodtor + __aeabi_atexit + +Compilers are not required to use these functions but runtime +libraries complying with this ABI must supply them. Below we list the +functions and their arguments. For details see CPPABI32_ section +'Array construction and destruction'. Each function is declared extern +“C”. + +.. code-block:: c + + void *__aeabi_vec_ctor_nocookie_nodtor( + void *user_array, void *(*constructor)(void *), + size_t element_size, size_t element_count); // Returns: user_array + void *__aeabi_vec_ctor_cookie_nodtor( // Returns: + array_cookie *cookie, void *(*constructor)(void *), // (cookie==NULL) ? NULL : + size_t element_size, size_t element_count); // array associated with cookie + void *__aeabi_vec_cctor_nocookie_nodtor( // Returns: user_array_dest + void *user_array_dest, void *user_array_src, + size_t element_size, size_t element_count, void *(*copy_constructor)(void *, void *)); + void *__aeabi_vec_new_cookie_noctor( + size_t element_size, size_t element_count); // Returns: new array + void *__aeabi_vec_new_nocookie( // Returns: new array + size_t element_size, size_t element_count, void *(*constructor)(void *)); + void *__aeabi_vec_new_cookie_nodtor( // Returns: new array + size_t element_size, size_t element_count, void *(*constructor)(void *)); + void *__aeabi_vec_new_cookie( // Returns: new array + size_t element_size, size_t element_count, + void *(*constructor)(void *), void *(*destructor)(void *)); + void *__aeabi_vec_dtor( // Returns: + void *user_array, void *(*destructor)(void *), // cookie associated with user_array + size_t element_size, size_t element_count); // (if there is one) + void *__aeabi_vec_dtor_cookie( // Returns: + void *user_array, void *(*destructor)(void *)); // cookie associated with user_array + void __aeabi_vec_delete( + void *user_array, void *(*destructor)(void *)); + void __aeabi_vec_delete3( + void *user_array, void *(*destructor)(void *), void (*dealloc)(void *, size_t)); + void __aeabi_vec_delete3_nodtor( + void *user_array, void (*dealloc)(void *, size_t)); + int __aeabi_atexit( // Returns: 0 => OK; non-0 => failed + void *object, void (*destroyer)(void *), void *dso_handle); + +Controlling object construction order +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +See GC++ABI, §3.3.4, `Controlling Object Construction +Order `__. + +This ABI currently defines no helper functions to control object +construction order. + +Static object finalization +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +See GC++ABI, §3.3.5, `DSO Object Destruction +API `__, +and CPPABI32_, section 'Static object construction and destruction'. + +The generic C++ ABI and this ABI both define the destruction protocol +for static objects created by dynamically linked shared objects in +separate platform supplements. Here we define only the interface used to +destroy static objects in the correct order. + +When a static object is created that will require destruction on program +exit, its destructor and a pointer to the object must be registered with +the run-time system by calling \_\_aeabi\_atexit (which calls +\_\_cxa\_atexit). + +.. code-block:: c + + int __aeabi_atexit(void *object, void (*dtor)(void *this), void *handle); + int __cxa_atexit(void (*dtor)(void *this), void *object, void *handle); + +(It is slightly more efficient for the caller to call \_\_aeabi\_exit, +and calling this function supports static allocation of memory for the +list of destructions – see CPPABI32_ section 'Static object destruction'). + +The handle argument should be NULL unless the object was created by a +dynamically loaded shared library (DSO or DLL). On exit, dtor(object) is +called in the correct order relative to other static object destructors. + +When a user function F is registered by calling the C/C++ library +function atexit, it must be registered by calling \_\_aeabi\_exit(NULL, +F, NULL) or \_\_cxa\_atexit(F, NULL, NULL). + +The handle argument and the dynamically loaded shared object (DSOor DLL +) finalization function \_\_cxa\_finalize (listed below) are relevant +only in the presence of DSOs or DLLs. The handle is the value passed to +\_\_cxa\_finalize. See the relevant platform supplement or the generic +C++ ABI for further information. + +.. code-block:: c + + void __cxa_finalize(void *handle); // Not used in the absence of DLLs/DSOs + +When a DSO is involved, *handle* must be an address that uniquely +identifies the DSO. Conventionally, handle = &\_\_dso\_handle, where +\_\_dso\_handle is a label defined while statically linking the DSO. + +Name demangling +^^^^^^^^^^^^^^^ + +See GC++ABI, §3.4, `Demangler +API `__. +This API is not supported by this ABI. + +In particular, it is likely that bare metal environments neither need, +nor want the overhead of, this functionality. + +Separate (virtual) platform supplements may require support for name +demangling, and where they do, this ABI follows the generic C++ ABI +precisely. + +Exception-handling support +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +For details see EHABI32_, section 'ABI routines'. Here we merely list the +required helper functions and their type signatures (each function is +declared extern “C”). + +Compiler helper functions +~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: c + + void *__cxa_allocate_exception(size_t size); + void __cxa_free_exception(void *p); + void __cxa_throw(void *, const std::type_info *, void (*dtor)(void *)); + void __cxa_rethrow(void); + void *__cxa_begin_catch(void *); + void *__cxa_get_exception_ptr(_Unwind_Control_Block *); + /* new in EHABI v2.02, ABI r2.02 */ + void __cxa_end_catch(void); + void __cxa_end_cleanup(void); + bool __cxa_begin_cleanup(_Unwind_Control_Block *ucbp) + void __cxa_call_unexpected(_Unwind_Control_Block *ucbp) + +For details see EHABI32_, section 'ABI routines'. + +Personality routine helper functions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: c + + bool __cxa_begin_cleanup(_Unwind_Control_Block *ucbp) + __cxa_type_match_result __cxa_type_match( + _Unwind_Control_Block *ucbp, + const std::type_info *rttip, bool is_ref_type, void **matched_object) + void __cxa_call_terminate(_Unwind_Control_Block *ucbp) + void __cxa_call_unexpected(_Unwind_Control_Block *ucbp) + +For details see EHABI32_, section 'ABI routines'. + +Auxiliary functions related to exception processing +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: c + + void __cxa_bad_cast(); // Throw a bad cast exception + void __cxa_bad_typeid(); // Throw a bad typeid exception + struct __cxa_eh_globals *__cxa_get_globals(void); + // Get a pointer to the implementation-defined, per-thread EH state + const std::type_info *__cxa_current_exception_type(void); + +For details see EHABI32_, section 'ABI routines'. diff --git a/microkernel/src/arch.rs b/microkernel/src/arch.rs index fd2d60826..1f25d80f6 100644 --- a/microkernel/src/arch.rs +++ b/microkernel/src/arch.rs @@ -479,6 +479,26 @@ fn isa_impl(h: &mut Harness) { fn isa_impl(h: &mut Harness) { use core::arch::asm; + // Exercise every address residue modulo 4. Regardless of the stack + // allocation's base alignment, these four offsets cover every possible + // alignment accepted by the Arm Run-time ABI helper. + let unaligned_bytes = [0x78u8, 0x56, 0x34, 0x12, 0xEF, 0xCD, 0xAB]; + for (name, offset, expected) in [ + ("aeabi_uread4_0", 0, 0x1234_5678), + ("aeabi_uread4_1", 1, 0xEF12_3456), + ("aeabi_uread4_2", 2, 0xCDEF_1234), + ("aeabi_uread4_3", 3, 0xABCD_EF12), + ] { + // SAFETY: `offset` is in 0..=3 and `unaligned_bytes` has 7 initialized + // bytes, so the helper can read exactly bytes offset..offset+4. The + // shared immutable array outlives the call, the helper creates no + // reference or mutable alias, follows the AAPCS C ABI, and cannot + // unwind because it is a leaf assembly routine. + let value = + unsafe { crate::arm_eabi::__aeabi_uread4(unaligned_bytes.as_ptr().add(offset)) }; + h.eq_u32(name, value, expected); + } + // Data processing with barrel shifter: r = a + (b << 4). let (a, b) = (0x1000u32, 0x23u32); let mut r: u32; diff --git a/microkernel/src/arm_eabi.rs b/microkernel/src/arm_eabi.rs new file mode 100644 index 000000000..5bda4920a --- /dev/null +++ b/microkernel/src/arm_eabi.rs @@ -0,0 +1,31 @@ +//! Arm EABI helpers required by the custom ARMv6 bare-metal target. + +/// Read a little-endian 32-bit value from an arbitrarily aligned address. +/// +/// The 2025Q4 Arm Run-time ABI, "Unaligned memory access", requires +/// `__aeabi_uread4` to accept arbitrary byte alignment and permits it to +/// clobber `r0-r3`, `ip`, `lr`, and `CPSR`. The byte loads avoid recursively +/// lowering this helper to the same unaligned word-load symbol on ARMv6. +/// +/// # Safety +/// +/// The caller must provide a pointer to a readable four-byte range. This naked +/// implementation performs exactly four one-byte loads, which require only +/// byte alignment and do not create references. It follows the target's AAPCS +/// C ABI: the pointer arrives in `r0`, the value returns in `r0`, and only the +/// caller-saved `r1` and `r2` registers plus condition flags are clobbered. It +/// cannot unwind because it contains no call or stack operation. +#[unsafe(naked)] +#[unsafe(no_mangle)] +pub(crate) unsafe extern "C" fn __aeabi_uread4(_address: *const u8) -> u32 { + core::arch::naked_asm!( + "ldrb r1, [r0]", + "ldrb r2, [r0, #1]", + "orr r1, r1, r2, lsl #8", + "ldrb r2, [r0, #2]", + "orr r1, r1, r2, lsl #16", + "ldrb r2, [r0, #3]", + "orr r0, r1, r2, lsl #24", + "bx lr", + ) +} diff --git a/microkernel/src/main.rs b/microkernel/src/main.rs index a796745c1..bb269a3fd 100644 --- a/microkernel/src/main.rs +++ b/microkernel/src/main.rs @@ -19,6 +19,8 @@ #[macro_use] mod serial; mod arch; +#[cfg(all(not(feature = "usermode"), target_arch = "arm"))] +mod arm_eabi; mod fixed; mod harness; mod mem; diff --git a/src/isa/riscv/cpu.rs b/src/isa/riscv/cpu.rs index 0604d488a..4c28b76db 100644 --- a/src/isa/riscv/cpu.rs +++ b/src/isa/riscv/cpu.rs @@ -20,6 +20,10 @@ use super::{Isa, Xlen}; any(target_arch = "x86_64", target_arch = "aarch64") ))] mod jit; +mod vector_config; +mod vector_conversion; +mod vector_memory; +mod vector_validation; /// Privilege level of the hart. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -2210,250 +2214,33 @@ impl RiscVCpu { /// Execute a vector data-path instruction. The tail/mask policy is /// undisturbed (only active body elements are written). fn exec_vector(&mut self, insn: &Insn) -> Result<(), Trap> { - // vill (vtype MSB) => any vector instruction is illegal. - if self.vtype >> (self.xbits() - 1) & 1 != 0 { + // Whole-register loads/stores do not depend on vtype and remain legal + // while vill is set. Every other vector data operation does depend on + // vtype and must trap without changing vstart. + if self.vtype >> (self.xbits() - 1) & 1 != 0 && !matches!(insn.op, Op::Vlre | Op::Vsre) { return Err(Trap::illegal(insn.raw)); } let vm = (insn.raw >> 25) & 1 != 0; // 1 = unmasked + vector_validation::validate(self, insn, vm)?; let vd = insn.rd; let vs2 = insn.rs2; let vstart = self.vstart as usize; let vl = self.vl as usize; match insn.op { - Op::Vle | Op::Vse => { - // Effective element width from the load/store funct3 field. - let eb = match insn.funct3 { - 0 => 1, - 5 => 2, - 6 => 4, - 7 => 8, - _ => return Err(Trap::illegal(insn.raw)), - }; - let base = self.x(insn.rs1) & self.xmask(); - for e in vstart..vl { - if !vm && !self.vmask_bit(e) { - continue; - } - let addr = base.wrapping_add((e * eb) as u64) & self.xmask(); - if insn.op == Op::Vle { - let mut buf = [0u8; 8]; - self.mem - .read(addr, &mut buf[..eb]) - .map_err(|_| acc_fault(false, addr))?; - self.set_velem(vd, e, eb, u64::from_le_bytes(buf)); - } else { - let val = self.velem(vd, e, eb); // vd holds the store data (vs3) - self.mem - .write(addr, &val.to_le_bytes()[..eb]) - .map_err(|_| acc_fault(true, addr))?; - } - } - } - Op::Vlse | Op::Vsse => { - // Strided load/store: addr = base + e * byte-stride. - let eb = match insn.funct3 { - 0 => 1, - 5 => 2, - 6 => 4, - 7 => 8, - _ => return Err(Trap::illegal(insn.raw)), - }; - let base = self.x(insn.rs1) & self.xmask(); - let stride = self.x(insn.rs2) as i64; - for e in vstart..vl { - if !vm && !self.vmask_bit(e) { - continue; - } - let addr = - base.wrapping_add((e as i64).wrapping_mul(stride) as u64) & self.xmask(); - if insn.op == Op::Vlse { - let mut buf = [0u8; 8]; - self.mem - .read(addr, &mut buf[..eb]) - .map_err(|_| acc_fault(false, addr))?; - self.set_velem(vd, e, eb, u64::from_le_bytes(buf)); - } else { - let val = self.velem(vd, e, eb); - self.mem - .write(addr, &val.to_le_bytes()[..eb]) - .map_err(|_| acc_fault(true, addr))?; - } - } - } - Op::Vlxei | Op::Vsxei => { - // Indexed load/store: addr = base + index[e]; index EEW = funct3, - // data EEW = SEW. - let ieb = match insn.funct3 { - 0 => 1, - 5 => 2, - 6 => 4, - 7 => 8, - _ => return Err(Trap::illegal(insn.raw)), - }; - let eb = self.sew_bytes(); - let base = self.x(insn.rs1) & self.xmask(); - for e in vstart..vl { - if !vm && !self.vmask_bit(e) { - continue; - } - let idx = self.velem(insn.rs2, e, ieb); - let addr = base.wrapping_add(idx) & self.xmask(); - if insn.op == Op::Vlxei { - let mut buf = [0u8; 8]; - self.mem - .read(addr, &mut buf[..eb]) - .map_err(|_| acc_fault(false, addr))?; - self.set_velem(vd, e, eb, u64::from_le_bytes(buf)); - } else { - let val = self.velem(vd, e, eb); - self.mem - .write(addr, &val.to_le_bytes()[..eb]) - .map_err(|_| acc_fault(true, addr))?; - } - } - } - Op::Vleff => { - // Fault-only-first unit-stride load: a fault past element 0 trims - // vl instead of trapping. (Non-faulting path mirrors Vle.) - let eb = match insn.funct3 { - 0 => 1, - 5 => 2, - 6 => 4, - 7 => 8, - _ => return Err(Trap::illegal(insn.raw)), - }; - let base = self.x(insn.rs1) & self.xmask(); - let mut new_vl = vl; - for e in vstart..vl { - if !vm && !self.vmask_bit(e) { - continue; - } - let addr = base.wrapping_add((e * eb) as u64) & self.xmask(); - let mut buf = [0u8; 8]; - match self.mem.read(addr, &mut buf[..eb]) { - Ok(_) => self.set_velem(vd, e, eb, u64::from_le_bytes(buf)), - Err(_) => { - if e == 0 { - return Err(acc_fault(false, addr)); - } - new_vl = e; // trim and suppress the trap - break; - } - } - } - self.vl = new_vl as u64; - } - Op::Vlseg | Op::Vsseg => { - // Segment load/store: nf+1 fields per element, de-interleaved into - // consecutive registers vd..vd+nf. Addressing per mop. - let nf = ((insn.raw >> 29) & 7) as usize + 1; - let mop = (insn.raw >> 26) & 3; - let is_load = insn.op == Op::Vlseg; - let indexed = mop == 0b01 || mop == 0b11; - let width = match insn.funct3 { - 0 => 1, - 5 => 2, - 6 => 4, - 7 => 8, - _ => return Err(Trap::illegal(insn.raw)), - }; - // For indexed segments data EEW = SEW, index EEW = funct3 width. - let eb = if indexed { self.sew_bytes() } else { width }; - // Each field is a register group of EMUL = data_EEW/SEW * LMUL - // registers, so consecutive fields are EMUL registers apart (not - // 1). Reject encodings whose group exceeds 8 registers per field, - // whose NFIELDS*EMUL > 8, or whose group would run past v31. - let sew_bits = 8u32 << ((self.vtype >> 3) & 0x7); - let eew_bits = if indexed { - sew_bits - } else { - (width as u32) * 8 - }; - let (lmul_n, lmul_d): (u32, u32) = match self.vtype & 0x7 { - 0 => (1, 1), - 1 => (2, 1), - 2 => (4, 1), - 3 => (8, 1), - 5 => (1, 8), - 6 => (1, 4), - 7 => (1, 2), - _ => (1, 1), - }; - let emul_regs = ((eew_bits * lmul_n) / (sew_bits * lmul_d)).max(1) as usize; - if emul_regs > 8 || nf * emul_regs > 8 || vd as usize + nf * emul_regs > 32 { - return Err(Trap::illegal(insn.raw)); - } - let base = self.x(insn.rs1) & self.xmask(); - let stride = self.x(insn.rs2) as i64; - for e in vstart..vl { - if !vm && !self.vmask_bit(e) { - continue; - } - let elem_base = match mop { - 0b00 => base.wrapping_add((e * nf * eb) as u64), - 0b10 => base.wrapping_add((e as i64).wrapping_mul(stride) as u64), - _ => base.wrapping_add(self.velem(insn.rs2, e, width)), - } & self.xmask(); - for f in 0..nf { - let addr = elem_base.wrapping_add((f * eb) as u64) & self.xmask(); - let reg = (vd as usize + f * emul_regs) as u8; - if is_load { - let mut buf = [0u8; 8]; - self.mem - .read(addr, &mut buf[..eb]) - .map_err(|_| acc_fault(false, addr))?; - self.set_velem(reg, e, eb, u64::from_le_bytes(buf)); - } else { - let val = self.velem(reg, e, eb); - self.mem - .write(addr, &val.to_le_bytes()[..eb]) - .map_err(|_| acc_fault(true, addr))?; - } - } - } - } - Op::Vlm | Op::Vsm => { - // Mask load/store: ceil(vl/8) bytes, EEW=8, always unmasked. - let base = self.x(insn.rs1) & self.xmask(); - let nbytes = vl.div_ceil(8); - for i in 0..nbytes { - let addr = base.wrapping_add(i as u64) & self.xmask(); - if insn.op == Op::Vlm { - let mut buf = [0u8; 1]; - self.mem - .read(addr, &mut buf) - .map_err(|_| acc_fault(false, addr))?; - self.set_velem(vd, i, 1, buf[0] as u64); - } else { - let val = self.velem(vd, i, 1); - self.mem - .write(addr, &[val as u8]) - .map_err(|_| acc_fault(true, addr))?; - } - } - } - Op::Vlre | Op::Vsre => { - // Whole-register load/store: (nf+1) * VLENB raw bytes, unmasked. - let nreg = ((insn.raw >> 29) & 7) as usize + 1; - let base = self.x(insn.rs1) & self.xmask(); - let total = nreg * VLENB as usize; - for i in 0..total { - let addr = base.wrapping_add(i as u64) & self.xmask(); - if insn.op == Op::Vlre { - let mut buf = [0u8; 1]; - self.mem - .read(addr, &mut buf) - .map_err(|_| acc_fault(false, addr))?; - self.set_velem(vd, i, 1, buf[0] as u64); - } else { - let val = self.velem(vd, i, 1); - self.mem - .write(addr, &[val as u8]) - .map_err(|_| acc_fault(true, addr))?; - } - } - } + Op::Vle + | Op::Vse + | Op::Vlse + | Op::Vsse + | Op::Vlxei + | Op::Vsxei + | Op::Vleff + | Op::Vlseg + | Op::Vsseg + | Op::Vlm + | Op::Vsm + | Op::Vlre + | Op::Vsre => self.exec_vector_memory(insn, vm, vd, vstart, vl)?, Op::Vmerge => { // vmerge.v*m (vm=0): per-element select via v0; vmv.v.* (vm=1): // splat the second operand. Both write every body element. @@ -3058,76 +2845,7 @@ impl RiscVCpu { | Op::VfncvtFF | Op::VfncvtRodFF | Op::VfncvtRtzXuF - | Op::VfncvtRtzXF => { - // Narrowing conversions: 2*SEW source vs2 -> SEW result. Only - // SEW in {16,32} (eb 2/4) is supported: SEW=8 would imply an - // FP8 format / 8-bit float-to-int width that has no defined - // conversion here, so reject eb outside {2,4}. - let eb = self.sew_bytes(); - if !(2..=4).contains(&eb) { - return Err(Trap::illegal(insn.raw)); - } - let web = eb * 2; - let mask = Self::sew_mask(eb); - let frm = RoundingMode::from_bits(self.frm()).unwrap_or(RoundingMode::Rne); - let mut flags = 0u32; - for e in vstart..vl { - if !vm && !self.vmask_bit(e) { - continue; - } - let aw = self.velem(vs2, e, web); - let r = match insn.op { - Op::VfncvtXuF | Op::VfncvtXF | Op::VfncvtRtzXuF | Op::VfncvtRtzXF => { - let signed = matches!(insn.op, Op::VfncvtXF | Op::VfncvtRtzXF); - let rm = if matches!(insn.op, Op::VfncvtRtzXuF | Op::VfncvtRtzXF) { - RoundingMode::Rtz - } else { - frm - }; - match web { - 4 => super::float::ftoi( - f32::from_bits(aw as u32), - signed, - (eb * 8) as u32, - rm, - &mut flags, - ), - _ => super::float::ftoi( - f64::from_bits(aw), - signed, - (eb * 8) as u32, - rm, - &mut flags, - ), - } - } - Op::VfncvtFXu | Op::VfncvtFX => { - let v: i128 = if insn.op == Op::VfncvtFX { - sext_sew(aw, web) as i128 - } else { - aw as i128 - }; - super::float::itof_fmt(fmt_eb(eb), v, frm, &mut flags) - } - Op::VfncvtRodFF => { - // Round-to-odd: truncate, then force the LSB on inexact. - let mut t = 0u32; - let r = super::float::fcvt_round( - fmt_eb(web), - fmt_eb(eb), - aw, - RoundingMode::Rtz, - &mut t, - ); - flags |= t; - if t & 1 != 0 { r | 1 } else { r } // NX is fflags bit 0 - } - _ => super::float::fcvt_round(fmt_eb(web), fmt_eb(eb), aw, frm, &mut flags), - }; - self.set_velem(vd, e, eb, r & mask); - } - self.accrue(flags); - } + | Op::VfncvtRtzXF => self.exec_vector_narrow_conversion(insn, vm)?, Op::Vmfeq | Op::Vmfne | Op::Vmflt | Op::Vmfle | Op::Vmfgt | Op::Vmfge => { let eb = self.sew_bytes(); let is_vv = insn.funct3 == 0b001; @@ -3704,10 +3422,7 @@ impl RiscVCpu { Op::Vadc | Op::Vsbc => { // vd[i] = vs2[i] +/- op[i] +/- v0.mask[i]; every body lane written. // These consume the v0 carry/borrow-in and are only defined in - // the masked (vm=0) form; the unmasked vm=1 encoding is reserved. - if vm { - return Err(Trap::illegal(insn.raw)); - } + // the masked (vm=0) form with a non-v0 destination. let eb = self.sew_bytes(); let mask = Self::sew_mask(eb); let scalar = match insn.funct3 { @@ -3940,10 +3655,8 @@ impl RiscVCpu { } Op::Vmsbf | Op::Vmsif | Op::Vmsof => { // Set-before / set-including / set-only the first active set bit. - // These prefix ops are not restartable: non-zero vstart traps. - if vstart != 0 { - return Err(Trap::illegal(insn.raw)); - } + // Their non-restartable and register-overlap constraints are + // checked before any vector state can be modified. let mut found = false; for e in vstart..vl { if !vm && !self.vmask_bit(e) { @@ -4036,53 +3749,6 @@ impl RiscVCpu { Ok(()) } - // --------------------------------------------------------------- - // V: vector configuration (vsetvl* compute the new vl from vtype). - // --------------------------------------------------------------- - - /// Apply a `vtype` and an application vector length, returning the new `vl` - /// and updating the `vl`/`vtype` CSRs. An illegal `vtype` sets `vill` and - /// zeroes `vl`. - fn set_vtype(&mut self, vtype: u64, avl: Avl) -> u64 { - let vsew = (vtype >> 3) & 0x7; - let vlmul = vtype & 0x7; - // Bits above [7:0] (vma/vta/vsew/vlmul) are reserved; vlmul=4 reserved; - // SEW must be <= ELEN (64). - let mut vill = (vtype >> 8) != 0 || vlmul == 4 || vsew > 3; - let sew = 8u64 << vsew; - let vlmax = if vill { - 0 - } else { - match vlmul { - 0 => VLEN / sew, - 1 => VLEN * 2 / sew, - 2 => VLEN * 4 / sew, - 3 => VLEN * 8 / sew, - 5 => VLEN / 8 / sew, - 6 => VLEN / 4 / sew, - 7 => VLEN / 2 / sew, - _ => 0, - } - }; - if vlmax == 0 { - vill = true; - } - if vill { - self.vtype = 1u64 << (self.xbits() - 1); // vill bit - self.vl = 0; - return 0; - } - let avl = match avl { - Avl::Keep => self.vl, - Avl::Max => vlmax, - Avl::Reg(v) => v, - }; - let vl = avl.min(vlmax); - self.vtype = vtype; - self.vl = vl; - vl - } - // --------------------------------------------------------------- // Floating point (F / D). // --------------------------------------------------------------- @@ -6466,9 +6132,9 @@ mod tests { } #[test] - fn vfncvt_rejects_sew8() { + fn vfncvt_fp8_result_rejects_sew8() { // vfncvt.f.f.w (funct6 010010, OPFV, vs1=10100) under SEW=8 has no - // defined narrowing (no FP8 / 8-bit float-to-int width) and must trap. + // defined FP8 result format and must trap. let mut c = cpu_e8m1(); // e8 -> SEW=8 assert!(matches!( run_one(&mut c, op_v(0b010010, 1, 2, 0b10100, 0b001, 1)), diff --git a/src/isa/riscv/cpu/vector_config.rs b/src/isa/riscv/cpu/vector_config.rs new file mode 100644 index 000000000..90bb06fae --- /dev/null +++ b/src/isa/riscv/cpu/vector_config.rs @@ -0,0 +1,97 @@ +//! RVV vector-length and vector-type configuration. + +use super::{Avl, RiscVCpu, VLEN}; + +impl RiscVCpu { + /// Apply a `vtype` and an application vector length, returning the new `vl` + /// and updating the `vl`/`vtype` CSRs. An illegal `vtype` sets `vill` and + /// zeroes `vl`. + pub(super) fn set_vtype(&mut self, vtype: u64, avl: Avl) -> u64 { + // Every successfully executed vector configuration instruction resets + // vstart, including one that records an unsupported vtype through vill. + self.vstart = 0; + + let vsew = (vtype >> 3) & 0x7; + let vlmul = vtype & 0x7; + // Bits above [7:0] (vma/vta/vsew/vlmul) are reserved; vlmul=4 reserved; + // SEW must be <= ELEN (64). + let mut vill = (vtype >> 8) != 0 || vlmul == 4 || vsew > 3; + let sew = 8u64 << vsew; + let vlmax = if vill { + 0 + } else { + match vlmul { + 0 => VLEN / sew, + 1 => VLEN * 2 / sew, + 2 => VLEN * 4 / sew, + 3 => VLEN * 8 / sew, + 5 => VLEN / 8 / sew, + 6 => VLEN / 4 / sew, + 7 => VLEN / 2 / sew, + _ => 0, + } + }; + if vlmax == 0 { + vill = true; + } + if vill { + self.vtype = 1u64 << (self.xbits() - 1); + self.vl = 0; + return 0; + } + let avl = match avl { + Avl::Keep => self.vl, + Avl::Max => vlmax, + Avl::Reg(v) => v, + }; + let vl = avl.min(vlmax); + self.vtype = vtype; + self.vl = vl; + vl + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::isa::riscv::{FlatMemory, Isa, RiscVConfig, RiscVExit, Xlen, decode}; + + fn cpu() -> RiscVCpu { + RiscVCpu::new(RiscVConfig::rv64gc(), Box::new(FlatMemory::new(0, 0x1000))) + } + + fn execute(cpu: &mut RiscVCpu, raw: u32) { + let insn = decode(raw, Xlen::Rv64, &Isa::rv64gc()); + assert_eq!(cpu.execute_insn(&insn, 0x1000), Ok(RiscVExit::Continue)); + assert_eq!(cpu.vstart(), 0, "{insn:?} did not reset vstart"); + } + + #[test] + fn every_vector_configuration_form_resets_vstart() { + let vsetvli = (7 << 12) | (1 << 7) | 0x57; + let vsetivli = (0b11 << 30) | ((3u32 << 3) << 20) | (3 << 15) | (7 << 12) | (6 << 7) | 0x57; + let vsetvl = (1 << 31) | (2 << 20) | (3 << 15) | (7 << 12) | (4 << 7) | 0x57; + + for raw in [vsetvli, vsetivli, vsetvl] { + let mut cpu = cpu(); + cpu.set_x(2, 0); // valid e8,m1 vtype for vsetvl + cpu.set_x(3, 4); + cpu.set_vstart(7); + execute(&mut cpu, raw); + } + } + + #[test] + fn unsupported_vtype_still_resets_vstart_when_vill_is_recorded() { + let mut cpu = cpu(); + cpu.set_x(2, 1u64 << 63); + cpu.set_x(3, 4); + cpu.set_vstart(5); + let vsetvl = (1 << 31) | (2 << 20) | (3 << 15) | (7 << 12) | (4 << 7) | 0x57; + + execute(&mut cpu, vsetvl); + + assert_eq!(cpu.vl(), 0); + assert_eq!(cpu.vtype(), 1u64 << 63); + } +} diff --git a/src/isa/riscv/cpu/vector_conversion.rs b/src/isa/riscv/cpu/vector_conversion.rs new file mode 100644 index 000000000..8d6f89a50 --- /dev/null +++ b/src/isa/riscv/cpu/vector_conversion.rs @@ -0,0 +1,104 @@ +//! Mixed-width RVV conversion semantics. +//! +//! This module owns the narrowing floating-point/integer conversion group so +//! its operand-width admission and data conversion remain one audited unit. + +use crate::isa::riscv::float::{self, RoundingMode}; + +use super::{Insn, Op, RiscVCpu, Trap, fmt_eb, sext_sew}; + +impl RiscVCpu { + pub(super) fn exec_vector_narrow_conversion( + &mut self, + insn: &Insn, + vm: bool, + ) -> Result<(), Trap> { + // Narrowing conversions read a 2*SEW source and write an SEW result. + // At SEW=8, Zvfh defines only the FP16-to-integer8 variants; any form + // producing FP8 remains reserved. SEW=64 would require an unsupported + // 128-bit source and is reserved for every variant. + let eb = self.sew_bytes(); + let to_integer = matches!( + insn.op, + Op::VfncvtXuF | Op::VfncvtXF | Op::VfncvtRtzXuF | Op::VfncvtRtzXF + ); + if eb > 4 || (eb == 1 && !to_integer) { + return Err(Trap::illegal(insn.raw)); + } + + let web = eb * 2; + let mask = Self::sew_mask(eb); + let frm = RoundingMode::from_bits(self.frm()).unwrap_or(RoundingMode::Rne); + let mut flags = 0u32; + for element in self.vstart as usize..self.vl as usize { + if !vm && !self.vmask_bit(element) { + continue; + } + let wide = self.velem(insn.rs2, element, web); + let result = match insn.op { + Op::VfncvtXuF | Op::VfncvtXF | Op::VfncvtRtzXuF | Op::VfncvtRtzXF => { + let signed = matches!(insn.op, Op::VfncvtXF | Op::VfncvtRtzXF); + let rounding = if matches!(insn.op, Op::VfncvtRtzXuF | Op::VfncvtRtzXF) { + RoundingMode::Rtz + } else { + frm + }; + match web { + 2 => float::ftoi( + float::h_widen(wide as u16), + signed, + (eb * 8) as u32, + rounding, + &mut flags, + ), + 4 => float::ftoi( + f32::from_bits(wide as u32), + signed, + (eb * 8) as u32, + rounding, + &mut flags, + ), + _ => float::ftoi( + f64::from_bits(wide), + signed, + (eb * 8) as u32, + rounding, + &mut flags, + ), + } + } + Op::VfncvtFXu | Op::VfncvtFX => { + let value: i128 = if insn.op == Op::VfncvtFX { + sext_sew(wide, web) as i128 + } else { + wide as i128 + }; + float::itof_fmt(fmt_eb(eb), value, frm, &mut flags) + } + Op::VfncvtRodFF => { + // Round to odd by truncating, then force the result LSB for + // an inexact conversion. + let mut conversion_flags = 0u32; + let narrowed = float::fcvt_round( + fmt_eb(web), + fmt_eb(eb), + wide, + RoundingMode::Rtz, + &mut conversion_flags, + ); + flags |= conversion_flags; + if conversion_flags & 1 != 0 { + narrowed | 1 + } else { + narrowed + } + } + Op::VfncvtFF => float::fcvt_round(fmt_eb(web), fmt_eb(eb), wide, frm, &mut flags), + _ => return Err(Trap::illegal(insn.raw)), + }; + self.set_velem(insn.rd, element, eb, result & mask); + } + self.accrue(flags); + Ok(()) + } +} diff --git a/src/isa/riscv/cpu/vector_memory.rs b/src/isa/riscv/cpu/vector_memory.rs new file mode 100644 index 000000000..2ba93ab3f --- /dev/null +++ b/src/isa/riscv/cpu/vector_memory.rs @@ -0,0 +1,372 @@ +//! RVV memory operations and precise `vstart` publication. + +use super::{Insn, Op, RiscVCpu, Trap, VLENB, acc_fault}; + +#[inline] +fn encoded_width(insn: &Insn) -> Result { + match insn.funct3 { + 0 => Ok(1), + 5 => Ok(2), + 6 => Ok(4), + 7 => Ok(8), + _ => Err(Trap::illegal(insn.raw)), + } +} + +impl RiscVCpu { + fn vector_read(&mut self, element: usize, addr: u64, buf: &mut [u8]) -> Result<(), Trap> { + if self.mem.read(addr, buf).is_err() { + self.vstart = element as u64; + return Err(acc_fault(false, addr)); + } + Ok(()) + } + + fn vector_write(&mut self, element: usize, addr: u64, data: &[u8]) -> Result<(), Trap> { + if self.mem.write(addr, data).is_err() { + self.vstart = element as u64; + return Err(acc_fault(true, addr)); + } + Ok(()) + } + + pub(super) fn exec_vector_memory( + &mut self, + insn: &Insn, + vm: bool, + vd: u8, + vstart: usize, + vl: usize, + ) -> Result<(), Trap> { + match insn.op { + Op::Vle | Op::Vse => { + let eb = encoded_width(insn)?; + let base = self.x(insn.rs1) & self.xmask(); + for e in vstart..vl { + if !vm && !self.vmask_bit(e) { + continue; + } + let addr = base.wrapping_add((e * eb) as u64) & self.xmask(); + if insn.op == Op::Vle { + let mut buf = [0u8; 8]; + self.vector_read(e, addr, &mut buf[..eb])?; + self.set_velem(vd, e, eb, u64::from_le_bytes(buf)); + } else { + let val = self.velem(vd, e, eb); + self.vector_write(e, addr, &val.to_le_bytes()[..eb])?; + } + } + } + Op::Vlse | Op::Vsse => { + let eb = encoded_width(insn)?; + let base = self.x(insn.rs1) & self.xmask(); + let stride = self.x(insn.rs2) as i64; + for e in vstart..vl { + if !vm && !self.vmask_bit(e) { + continue; + } + let addr = + base.wrapping_add((e as i64).wrapping_mul(stride) as u64) & self.xmask(); + if insn.op == Op::Vlse { + let mut buf = [0u8; 8]; + self.vector_read(e, addr, &mut buf[..eb])?; + self.set_velem(vd, e, eb, u64::from_le_bytes(buf)); + } else { + let val = self.velem(vd, e, eb); + self.vector_write(e, addr, &val.to_le_bytes()[..eb])?; + } + } + } + Op::Vlxei | Op::Vsxei => { + let ieb = encoded_width(insn)?; + let eb = self.sew_bytes(); + let base = self.x(insn.rs1) & self.xmask(); + for e in vstart..vl { + if !vm && !self.vmask_bit(e) { + continue; + } + let addr = base.wrapping_add(self.velem(insn.rs2, e, ieb)) & self.xmask(); + if insn.op == Op::Vlxei { + let mut buf = [0u8; 8]; + self.vector_read(e, addr, &mut buf[..eb])?; + self.set_velem(vd, e, eb, u64::from_le_bytes(buf)); + } else { + let val = self.velem(vd, e, eb); + self.vector_write(e, addr, &val.to_le_bytes()[..eb])?; + } + } + } + Op::Vleff => { + let eb = encoded_width(insn)?; + let base = self.x(insn.rs1) & self.xmask(); + let mut new_vl = vl; + for e in vstart..vl { + if !vm && !self.vmask_bit(e) { + continue; + } + let addr = base.wrapping_add((e * eb) as u64) & self.xmask(); + let mut buf = [0u8; 8]; + match self.mem.read(addr, &mut buf[..eb]) { + Ok(()) => self.set_velem(vd, e, eb, u64::from_le_bytes(buf)), + Err(_) if e == 0 => { + self.vstart = 0; + return Err(acc_fault(false, addr)); + } + Err(_) => { + new_vl = e; + break; + } + } + } + self.vl = new_vl as u64; + } + Op::Vlseg | Op::Vsseg => { + let nf = ((insn.raw >> 29) & 7) as usize + 1; + let mop = (insn.raw >> 26) & 3; + let is_load = insn.op == Op::Vlseg; + let width = encoded_width(insn)?; + let indexed = mop == 0b01 || mop == 0b11; + let eb = if indexed { self.sew_bytes() } else { width }; + + let sew_bits = 8u32 << ((self.vtype >> 3) & 0x7); + let eew_bits = if indexed { + sew_bits + } else { + (width as u32) * 8 + }; + let (lmul_n, lmul_d): (u32, u32) = match self.vtype & 0x7 { + 0 => (1, 1), + 1 => (2, 1), + 2 => (4, 1), + 3 => (8, 1), + 5 => (1, 8), + 6 => (1, 4), + 7 => (1, 2), + _ => (1, 1), + }; + let emul_regs = ((eew_bits * lmul_n) / (sew_bits * lmul_d)).max(1) as usize; + if emul_regs > 8 || nf * emul_regs > 8 || vd as usize + nf * emul_regs > 32 { + return Err(Trap::illegal(insn.raw)); + } + + let base = self.x(insn.rs1) & self.xmask(); + let stride = self.x(insn.rs2) as i64; + for e in vstart..vl { + if !vm && !self.vmask_bit(e) { + continue; + } + let elem_base = match mop { + 0b00 => base.wrapping_add((e * nf * eb) as u64), + 0b10 => base.wrapping_add((e as i64).wrapping_mul(stride) as u64), + _ => base.wrapping_add(self.velem(insn.rs2, e, width)), + } & self.xmask(); + for f in 0..nf { + let addr = elem_base.wrapping_add((f * eb) as u64) & self.xmask(); + let reg = (vd as usize + f * emul_regs) as u8; + if is_load { + let mut buf = [0u8; 8]; + self.vector_read(e, addr, &mut buf[..eb])?; + self.set_velem(reg, e, eb, u64::from_le_bytes(buf)); + } else { + let val = self.velem(reg, e, eb); + self.vector_write(e, addr, &val.to_le_bytes()[..eb])?; + } + } + } + } + Op::Vlm | Op::Vsm => { + // Mask transfers use byte-sized elements and vstart is a byte + // index into their effective length ceil(vl/8). + if !vm { + return Err(Trap::illegal(insn.raw)); + } + let base = self.x(insn.rs1) & self.xmask(); + let evl = vl.div_ceil(8); + for e in vstart..evl { + let addr = base.wrapping_add(e as u64) & self.xmask(); + if insn.op == Op::Vlm { + let mut buf = [0u8; 1]; + self.vector_read(e, addr, &mut buf)?; + self.set_velem(vd, e, 1, u64::from(buf[0])); + } else { + let val = self.velem(vd, e, 1); + self.vector_write(e, addr, &[val as u8])?; + } + } + } + Op::Vlre | Op::Vsre => { + // Whole-register transfers ignore vtype/vl. vstart indexes + // encoded-EEW elements in the independent effective length. + let eb = encoded_width(insn)?; + let nreg = ((insn.raw >> 29) & 7) as usize + 1; + if !vm + || !matches!(nreg, 1 | 2 | 4 | 8) + || vd as usize % nreg != 0 + || vd as usize + nreg > 32 + { + return Err(Trap::illegal(insn.raw)); + } + let base = self.x(insn.rs1) & self.xmask(); + let evl = nreg * VLENB as usize / eb; + for e in vstart..evl { + let addr = base.wrapping_add((e * eb) as u64) & self.xmask(); + if insn.op == Op::Vlre { + let mut buf = [0u8; 8]; + self.vector_read(e, addr, &mut buf[..eb])?; + self.set_velem(vd, e, eb, u64::from_le_bytes(buf)); + } else { + let val = self.velem(vd, e, eb); + self.vector_write(e, addr, &val.to_le_bytes()[..eb])?; + } + } + } + _ => return Err(Trap::illegal(insn.raw)), + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::isa::riscv::{FlatMemory, Isa, RiscVConfig, RiscVExit, Xlen, decode}; + + fn cpu(memory: FlatMemory, vl: u64, vtype: u64) -> RiscVCpu { + let mut cpu = RiscVCpu::new(RiscVConfig::rv64gc(), Box::new(memory)); + cpu.set_vl_vtype(vl, vtype); + cpu + } + + fn execute(cpu: &mut RiscVCpu, raw: u32) -> Result { + let insn = decode(raw, Xlen::Rv64, &Isa::rv64gc()); + assert_ne!(insn.op, Op::Illegal, "test encoding {raw:08x} is illegal"); + cpu.execute_insn(&insn, 0x1000) + } + + fn load(vm: u32, nf: u32, lumop: u32, rs1: u32, width: u32, vd: u32) -> u32 { + (nf << 29) | (vm << 25) | (lumop << 20) | (rs1 << 15) | (width << 12) | (vd << 7) | 0x07 + } + + fn store(vm: u32, nf: u32, sumop: u32, rs1: u32, width: u32, vs3: u32) -> u32 { + (nf << 29) | (vm << 25) | (sumop << 20) | (rs1 << 15) | (width << 12) | (vs3 << 7) | 0x27 + } + + #[test] + fn mask_load_and_store_use_byte_indexed_vstart() { + let mut load_cpu = cpu(FlatMemory::with_data(0x100, vec![0x11, 0x22, 0x33]), 24, 0); + load_cpu.set_x(10, 0x100); + load_cpu.set_vreg(1, &[0xaa; 16]); + load_cpu.set_vstart(1); + assert_eq!( + execute(&mut load_cpu, load(1, 0, 0b01011, 10, 0, 1)), + Ok(RiscVExit::Continue) + ); + assert_eq!(&load_cpu.vreg(1)[..3], &[0xaa, 0x22, 0x33]); + assert_eq!(load_cpu.vstart(), 0); + + let mut store_cpu = cpu(FlatMemory::with_data(0x100, vec![0xee; 3]), 24, 0); + store_cpu.set_x(10, 0x100); + let mut source = [0u8; 16]; + source[..3].copy_from_slice(&[0x11, 0x22, 0x33]); + store_cpu.set_vreg(1, &source); + store_cpu.set_vstart(1); + assert_eq!( + execute(&mut store_cpu, store(1, 0, 0b01011, 10, 0, 1)), + Ok(RiscVExit::Continue) + ); + let mut stored = [0u8; 3]; + store_cpu.read_memory(0x100, &mut stored).unwrap(); + assert_eq!(stored, [0xee, 0x22, 0x33]); + assert_eq!(store_cpu.vstart(), 0); + } + + #[test] + fn whole_register_transfer_uses_encoded_eew_and_ignores_vill() { + let memory: Vec = (0..32).collect(); + let mut load_cpu = cpu(FlatMemory::with_data(0x100, memory.clone()), 0, 1u64 << 63); + load_cpu.set_x(10, 0x100); + load_cpu.set_vreg(2, &[0xaa; 16]); + load_cpu.set_vreg(3, &[0xaa; 16]); + load_cpu.set_vstart(2); // two e32 elements = eight bytes + + assert_eq!( + execute(&mut load_cpu, load(1, 1, 0b01000, 10, 6, 2)), + Ok(RiscVExit::Continue) + ); + + let mut actual = [0u8; 32]; + actual[..16].copy_from_slice(&load_cpu.vreg(2)); + actual[16..].copy_from_slice(&load_cpu.vreg(3)); + assert_eq!(&actual[..8], &[0xaa; 8]); + assert_eq!(&actual[8..], &memory[8..]); + assert_eq!(load_cpu.vstart(), 0); + + let mut store_cpu = cpu(FlatMemory::with_data(0x100, vec![0xee; 32]), 0, 1u64 << 63); + store_cpu.set_x(10, 0x100); + let source2: [u8; 16] = memory[..16].try_into().unwrap(); + let source3: [u8; 16] = memory[16..].try_into().unwrap(); + store_cpu.set_vreg(2, &source2); + store_cpu.set_vreg(3, &source3); + store_cpu.set_vstart(2); + assert_eq!( + execute(&mut store_cpu, store(1, 1, 0b01000, 10, 6, 2)), + Ok(RiscVExit::Continue) + ); + let mut stored = [0u8; 32]; + store_cpu.read_memory(0x100, &mut stored).unwrap(); + assert_eq!(&stored[..8], &[0xee; 8]); + assert_eq!(&stored[8..], &memory[8..]); + assert_eq!(store_cpu.vstart(), 0); + } + + #[test] + fn mask_transfer_respects_vill_and_whole_register_groups_are_validated() { + let mut vill_cpu = cpu(FlatMemory::new(0x100, 64), 8, 1u64 << 63); + vill_cpu.set_x(10, 0x100); + vill_cpu.set_vstart(3); + let raw = load(1, 0, 0b01011, 10, 0, 1); + assert_eq!(execute(&mut vill_cpu, raw), Err(Trap::illegal(raw))); + assert_eq!(vill_cpu.vstart(), 3); + + let mut group_cpu = cpu(FlatMemory::new(0x100, 64), 0, 0); + group_cpu.set_x(10, 0x100); + let misaligned = load(1, 1, 0b01000, 10, 0, 3); + assert_eq!( + execute(&mut group_cpu, misaligned), + Err(Trap::illegal(misaligned)) + ); + } + + #[test] + fn vector_memory_faults_publish_the_faulting_element() { + let mut cpu = cpu( + FlatMemory::with_data(0x100, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), + 4, + 0x10, + ); + cpu.set_x(10, 0x100); + cpu.set_vreg(1, &[0xaa; 16]); + + let trap = execute(&mut cpu, load(1, 0, 0, 10, 6, 1)).unwrap_err(); + + assert_eq!(trap, acc_fault(false, 0x108)); + assert_eq!(cpu.vstart(), 2); + assert_eq!(&cpu.vreg(1)[..8], &[1, 2, 3, 4, 5, 6, 7, 8]); + assert_eq!(&cpu.vreg(1)[8..], &[0xaa; 8]); + } + + #[test] + fn mask_and_whole_register_fault_indices_use_their_effective_elements() { + let mut mask_cpu = cpu(FlatMemory::with_data(0x100, vec![0; 2]), 24, 0); + mask_cpu.set_x(10, 0x100); + let mask_trap = execute(&mut mask_cpu, load(1, 0, 0b01011, 10, 0, 1)).unwrap_err(); + assert_eq!(mask_trap, acc_fault(false, 0x102)); + assert_eq!(mask_cpu.vstart(), 2); + + let mut whole_cpu = cpu(FlatMemory::with_data(0x100, vec![0; 10]), 0, 0); + whole_cpu.set_x(10, 0x100); + let whole_trap = execute(&mut whole_cpu, load(1, 0, 0b01000, 10, 6, 2)).unwrap_err(); + assert_eq!(whole_trap, acc_fault(false, 0x108)); + assert_eq!(whole_cpu.vstart(), 2); + } +} diff --git a/src/isa/riscv/cpu/vector_validation.rs b/src/isa/riscv/cpu/vector_validation.rs new file mode 100644 index 000000000..7f91d5d97 --- /dev/null +++ b/src/isa/riscv/cpu/vector_validation.rs @@ -0,0 +1,972 @@ +//! Architectural encoding and register-group validation for RVV data operations. +//! +//! These checks run before the data-path dispatcher mutates architectural state. +//! Keeping them together makes the direct interpreter and the opaque SMIR/JIT +//! helper paths reject the same reserved encodings at the same guest frontier. + +use super::{Insn, Op, RiscVCpu, Trap}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct Emul { + numerator: u8, + denominator: u8, +} + +impl Emul { + fn from_vtype(vtype: u64) -> Option { + match vtype & 0x7 { + 0b000 => Some(Self::new(1, 1)), + 0b001 => Some(Self::new(2, 1)), + 0b010 => Some(Self::new(4, 1)), + 0b011 => Some(Self::new(8, 1)), + 0b101 => Some(Self::new(1, 8)), + 0b110 => Some(Self::new(1, 4)), + 0b111 => Some(Self::new(1, 2)), + 0b100 => None, + _ => None, + } + } + + fn new(mut numerator: u8, mut denominator: u8) -> Self { + while numerator % 2 == 0 && denominator % 2 == 0 { + numerator /= 2; + denominator /= 2; + } + Self { + numerator, + denominator, + } + } + + fn widen(self) -> Self { + Self::new(self.numerator * 2, self.denominator) + } + + fn narrow(self, factor: u8) -> Self { + Self::new(self.numerator, self.denominator * factor) + } + + fn is_at_least_one(self) -> bool { + self.numerator >= self.denominator + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct RegisterGroup { + first: u8, + count: u8, +} + +impl RegisterGroup { + fn for_emul(first: u8, emul: Emul) -> Option { + // RVV reserves any instruction whose effective register group is larger + // than eight vector registers or smaller than the minimum fractional + // LMUL supported by this ELEN=64 implementation (1/8). + if u16::from(emul.numerator) > 8 * u16::from(emul.denominator) + || u16::from(emul.denominator) > 8 * u16::from(emul.numerator) + { + return None; + } + + // Fractional EMUL occupies a fraction of one named architectural + // register and therefore has no multi-register alignment constraint. + let count = if emul.numerator < emul.denominator { + 1 + } else { + if emul.numerator % emul.denominator != 0 { + return None; + } + emul.numerator / emul.denominator + }; + if first % count != 0 || first.checked_add(count)? > 32 { + return None; + } + Some(Self { first, count }) + } + + fn overlaps(self, other: Self) -> bool { + let self_end = self.first + self.count; + let other_end = other.first + other.count; + self.first < other_end && other.first < self_end + } + + fn last(self) -> u8 { + self.first + self.count - 1 + } +} + +#[inline] +fn illegal(insn: &Insn) -> Trap { + Trap::illegal(insn.raw) +} + +fn current_lmul(cpu: &RiscVCpu, insn: &Insn) -> Result { + Emul::from_vtype(cpu.vtype).ok_or_else(|| illegal(insn)) +} + +fn same_width_group(cpu: &RiscVCpu, insn: &Insn, first: u8) -> Result { + RegisterGroup::for_emul(first, current_lmul(cpu, insn)?).ok_or_else(|| illegal(insn)) +} + +fn validate_slide_up(cpu: &RiscVCpu, insn: &Insn) -> Result<(), Trap> { + let destination = same_width_group(cpu, insn, insn.rd)?; + let source = same_width_group(cpu, insn, insn.rs2)?; + if destination.overlaps(source) { + return Err(illegal(insn)); + } + Ok(()) +} + +fn validate_narrowing(cpu: &RiscVCpu, insn: &Insn) -> Result<(), Trap> { + let lmul = current_lmul(cpu, insn)?; + let destination = RegisterGroup::for_emul(insn.rd, lmul).ok_or_else(|| illegal(insn))?; + let source = RegisterGroup::for_emul(insn.rs2, lmul.widen()).ok_or_else(|| illegal(insn))?; + + // A narrow destination may overlap the lowest-numbered part of its wide + // source group. Any other overlap is reserved. + if destination.overlaps(source) && destination.first != source.first { + return Err(illegal(insn)); + } + Ok(()) +} + +fn validate_wider_destination_overlap( + insn: &Insn, + destination: RegisterGroup, + source: RegisterGroup, + source_emul: Emul, +) -> Result<(), Trap> { + // For a wider destination, overlap is legal only when the narrow source + // has EMUL >= 1 and occupies the highest-numbered part of the destination. + if destination.overlaps(source) + && (!source_emul.is_at_least_one() || destination.last() != source.last()) + { + return Err(illegal(insn)); + } + Ok(()) +} + +fn validate_widening_data( + cpu: &RiscVCpu, + insn: &Insn, + wide_vs2: bool, + vector_vs1: bool, + destination_is_source: bool, +) -> Result<(), Trap> { + let narrow_emul = current_lmul(cpu, insn)?; + let wide_emul = narrow_emul.widen(); + let destination = RegisterGroup::for_emul(insn.rd, wide_emul).ok_or_else(|| illegal(insn))?; + + let vs2_emul = if wide_vs2 { wide_emul } else { narrow_emul }; + let vs2 = RegisterGroup::for_emul(insn.rs2, vs2_emul).ok_or_else(|| illegal(insn))?; + if !wide_vs2 { + if destination_is_source && destination.overlaps(vs2) { + // A destructive widening accumulate would otherwise read the same + // register at both the wide destination EEW and the narrow source + // EEW, which is a reserved source-operand combination. + return Err(illegal(insn)); + } + validate_wider_destination_overlap(insn, destination, vs2, vs2_emul)?; + } + + if vector_vs1 { + let vs1 = RegisterGroup::for_emul(insn.rs1, narrow_emul).ok_or_else(|| illegal(insn))?; + if (destination_is_source && destination.overlaps(vs1)) || (wide_vs2 && vs2.overlaps(vs1)) { + // The first case is the other destructive-accumulate source. The + // second would read a shared register through wide vs2 and narrow + // vs1 in a `.w` form. Both use two EEWs for one source register. + return Err(illegal(insn)); + } + validate_wider_destination_overlap(insn, destination, vs1, narrow_emul)?; + } + Ok(()) +} + +fn validate_extension(cpu: &RiscVCpu, insn: &Insn, factor: u8) -> Result<(), Trap> { + let destination_emul = current_lmul(cpu, insn)?; + let source_emul = destination_emul.narrow(factor); + let destination = + RegisterGroup::for_emul(insn.rd, destination_emul).ok_or_else(|| illegal(insn))?; + let source = RegisterGroup::for_emul(insn.rs2, source_emul).ok_or_else(|| illegal(insn))?; + validate_wider_destination_overlap(insn, destination, source, source_emul) +} + +fn validate_reduction_source_group(cpu: &RiscVCpu, insn: &Insn) -> Result<(), Trap> { + // Reduction scalar operands in vd/vs1 may use any register regardless of + // LMUL. The vector source in vs2 remains an LMUL-sized group and must be + // aligned, in bounds, and no larger than eight registers. + RegisterGroup::for_emul(insn.rs2, current_lmul(cpu, insn)?).ok_or_else(|| illegal(insn))?; + Ok(()) +} + +fn validate_reduction(cpu: &RiscVCpu, insn: &Insn) -> Result<(), Trap> { + // Reductions are architecturally non-restartable. Their traps are always + // reported at element zero, so a guest-supplied nonzero vstart is illegal. + if cpu.vstart != 0 { + return Err(illegal(insn)); + } + validate_reduction_source_group(cpu, insn) +} + +fn validate_same_width_integer_alu(cpu: &RiscVCpu, insn: &Insn) -> Result<(), Trap> { + // Every same-width vector operand names the LMUL-sized register group. + // Scalar/immediate forms retain only vd and vs2 as vector groups. + same_width_group(cpu, insn, insn.rd)?; + same_width_group(cpu, insn, insn.rs2)?; + if insn.funct3 == 0b000 { + same_width_group(cpu, insn, insn.rs1)?; + } + Ok(()) +} + +fn is_vector_fp_encoding(insn: &Insn) -> bool { + // OPFVV and OPFVF are the complete floating-point classes under OP-V. + // Classifying the encoding, rather than maintaining an operation whitelist, + // also covers exact operations and future decoded members of these classes. + insn.raw & 0x7f == 0x57 && matches!(insn.funct3, 0b001 | 0b101) +} + +fn fp_operands_supported_at_sew8(insn: &Insn) -> bool { + // Zvfh defines these conversions at SEW=8 because their only floating- + // point operand is the double-width, 16-bit side. Every other decoded + // OPFVV/OPFVF instruction would consume or produce an unsupported FP8 + // operand and is reserved. + matches!( + insn.op, + Op::VfwcvtFXu + | Op::VfwcvtFX + | Op::VfncvtXuF + | Op::VfncvtXF + | Op::VfncvtRtzXuF + | Op::VfncvtRtzXF + ) +} + +pub(super) fn validate(cpu: &RiscVCpu, insn: &Insn, vm: bool) -> Result<(), Trap> { + // All vector floating-point instructions consult frm, including operations + // whose numeric result is independent of rounding and instructions with a + // fixed RTZ/ROD behavior. Architectural frm encodings 5, 6, and 7 are + // reserved; frm=7 is not a second level of dynamic selection. + let vector_fp = is_vector_fp_encoding(insn); + if vector_fp && cpu.frm() > 4 { + return Err(illegal(insn)); + } + if vector_fp && cpu.sew_bytes() == 1 && !fp_operands_supported_at_sew8(insn) { + return Err(illegal(insn)); + } + + match insn.op { + Op::Vmerge if vm => { + // vmv.v.v/vx/vi share the vmerge encoding. The vm=1 move forms + // reserve vs2 and require that field to encode v0. + if insn.rs2 != 0 { + return Err(illegal(insn)); + } + } + Op::Vmsbf | Op::Vmsif | Op::Vmsof => { + if cpu.vstart != 0 || insn.rd == insn.rs2 || (!vm && insn.rd == 0) { + return Err(illegal(insn)); + } + } + Op::Vid => { + // VMUNARY0 reserves the source field for vid.v and requires it to + // encode v0 even though the operation does not consume a source. + if insn.rs2 != 0 { + return Err(illegal(insn)); + } + } + Op::Vadc | Op::Vsbc => { + if vm || insn.rd == 0 { + return Err(illegal(insn)); + } + } + Op::Vslideup | Op::Vslide1up | Op::Vfslide1up => { + validate_slide_up(cpu, insn)?; + } + Op::Vadd + | Op::Vsub + | Op::Vrsub + | Op::Vand + | Op::Vor + | Op::Vxor + | Op::Vminu + | Op::Vmin + | Op::Vmaxu + | Op::Vmax + | Op::Vsll + | Op::Vsrl + | Op::Vsra => validate_same_width_integer_alu(cpu, insn)?, + Op::Vnsrl + | Op::Vnsra + | Op::Vnclipu + | Op::Vnclip + | Op::VfncvtXuF + | Op::VfncvtXF + | Op::VfncvtFXu + | Op::VfncvtFX + | Op::VfncvtFF + | Op::VfncvtRodFF + | Op::VfncvtRtzXuF + | Op::VfncvtRtzXF => validate_narrowing(cpu, insn)?, + Op::Vwaddu | Op::Vwadd | Op::Vwsubu | Op::Vwsub => { + validate_widening_data(cpu, insn, false, insn.funct3 == 0b010, false)?; + } + Op::VwadduW | Op::VwaddW | Op::VwsubuW | Op::VwsubW => { + validate_widening_data(cpu, insn, true, insn.funct3 == 0b010, false)?; + } + Op::Vwmulu + | Op::Vwmulsu + | Op::Vwmul + | Op::Vwmaccu + | Op::Vwmacc + | Op::Vwmaccsu + | Op::Vwmaccus => { + let accumulate = matches!( + insn.op, + Op::Vwmaccu | Op::Vwmacc | Op::Vwmaccsu | Op::Vwmaccus + ); + validate_widening_data(cpu, insn, false, insn.funct3 == 0b010, accumulate)?; + } + Op::Vfwadd | Op::Vfwsub | Op::Vfwmul => { + validate_widening_data(cpu, insn, false, insn.funct3 == 0b001, false)?; + } + Op::VfwaddW | Op::VfwsubW => { + validate_widening_data(cpu, insn, true, insn.funct3 == 0b001, false)?; + } + Op::Vfwmacc | Op::Vfwnmacc | Op::Vfwmsac | Op::Vfwnmsac => { + validate_widening_data(cpu, insn, false, insn.funct3 == 0b001, true)?; + } + Op::VfwcvtXuF + | Op::VfwcvtXF + | Op::VfwcvtFXu + | Op::VfwcvtFX + | Op::VfwcvtFF + | Op::VfwcvtRtzXuF + | Op::VfwcvtRtzXF => validate_widening_data(cpu, insn, false, false, false)?, + Op::VzextVf2 | Op::VsextVf2 => validate_extension(cpu, insn, 2)?, + Op::VzextVf4 | Op::VsextVf4 => validate_extension(cpu, insn, 4)?, + Op::VzextVf8 | Op::VsextVf8 => validate_extension(cpu, insn, 8)?, + Op::Vredsum + | Op::Vredand + | Op::Vredor + | Op::Vredxor + | Op::Vredminu + | Op::Vredmin + | Op::Vredmaxu + | Op::Vredmax + | Op::Vfredusum + | Op::Vfredosum + | Op::Vfredmin + | Op::Vfredmax + | Op::Vwredsumu + | Op::Vwredsum + | Op::Vfwredusum + | Op::Vfwredosum => validate_reduction(cpu, insn)?, + _ => {} + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::isa::riscv::{FlatMemory, Isa, RiscVConfig, RiscVExit, Xlen, decode}; + + const E8_M1: u64 = 0x00; + const E16_MF8: u64 = 0x0d; + const E32_M1: u64 = 0x10; + const E32_M2: u64 = 0x11; + const E32_M4: u64 = 0x12; + const E32_M8: u64 = 0x13; + const E32_MF4: u64 = 0x16; + const E32_MF2: u64 = 0x17; + const E64_M8: u64 = 0x1b; + const E64_MF2: u64 = 0x1f; + + fn op_v(funct6: u32, vm: u32, vs2: u32, src: u32, funct3: u32, vd: u32) -> u32 { + (funct6 << 26) | (vm << 25) | (vs2 << 20) | (src << 15) | (funct3 << 12) | (vd << 7) | 0x57 + } + + fn decoded(raw: u32) -> Insn { + let insn = decode(raw, Xlen::Rv64, &Isa::rv64gc()); + assert_ne!(insn.op, Op::Illegal, "test encoding {raw:08x} is illegal"); + insn + } + + fn cpu(vtype: u64, vl: u64, vstart: u64, frm: u8) -> RiscVCpu { + let mut cpu = RiscVCpu::new(RiscVConfig::rv64gc(), Box::new(FlatMemory::new(0, 0x2000))); + cpu.set_vl_vtype(vl, vtype); + cpu.set_vstart(vstart); + cpu.set_fcsr(u32::from(frm) << 5); + for register in 0..32u8 { + cpu.set_x(register, 0x1020_3040_5060_7080 ^ u64::from(register)); + cpu.set_f(register, 0xffff_ffff_3f80_0000 + u64::from(register)); + cpu.set_vreg(register, &[register.wrapping_mul(7); 16]); + } + cpu + } + + fn architectural_state( + cpu: &RiscVCpu, + ) -> ( + [u64; 32], + [u64; 32], + [[u8; 16]; 32], + u32, + u64, + u64, + u64, + u64, + ) { + ( + std::array::from_fn(|index| cpu.x(index as u8)), + std::array::from_fn(|index| cpu.f(index as u8)), + std::array::from_fn(|index| cpu.vreg(index as u8)), + cpu.fcsr(), + cpu.vl(), + cpu.vtype(), + cpu.vstart(), + cpu.vcsr(), + ) + } + + fn assert_illegal(raw: u32, vtype: u64, vl: u64, vstart: u64, frm: u8) { + let insn = decoded(raw); + let mut cpu = cpu(vtype, vl, vstart, frm); + let before = architectural_state(&cpu); + assert_eq!( + cpu.execute_insn(&insn, 0x1000), + Err(Trap::illegal(raw)), + "reserved encoding {raw:08x} ({:?}) must trap", + insn.op + ); + assert_eq!( + architectural_state(&cpu), + before, + "reserved encoding {raw:08x} modified state before trapping" + ); + } + + fn assert_legal(raw: u32, vtype: u64, vl: u64, vstart: u64, frm: u8) { + let insn = decoded(raw); + let mut cpu = cpu(vtype, vl, vstart, frm); + assert_eq!( + cpu.execute_insn(&insn, 0x1000), + Ok(RiscVExit::Continue), + "legal encoding {raw:08x} ({:?}) trapped", + insn.op + ); + } + + #[test] + fn mask_prefix_constraints_are_checked_before_execution() { + for selector in [0b00001, 0b00010, 0b00011] { + // Unmasked vd == vs2 is reserved. + assert_illegal(op_v(0b010100, 1, 2, selector, 0b010, 2), E8_M1, 8, 0, 0); + // A masked prefix instruction cannot write v0. + assert_illegal(op_v(0b010100, 0, 2, selector, 0b010, 0), E8_M1, 8, 0, 0); + // Prefix operations are not restartable. + assert_illegal(op_v(0b010100, 1, 2, selector, 0b010, 1), E8_M1, 8, 1, 0); + assert_legal(op_v(0b010100, 1, 2, selector, 0b010, 1), E8_M1, 8, 0, 0); + assert_legal(op_v(0b010100, 0, 2, selector, 0b010, 1), E8_M1, 8, 0, 0); + } + } + + #[test] + fn vid_requires_reserved_vs2_field_to_name_v0() { + let vid = |vm, vs2| op_v(0b010100, vm, vs2, 0b10001, 0b010, 1); + + assert_illegal(vid(1, 16), E8_M1, 4, 0, 0); + assert_illegal(vid(0, 3), E8_M1, 4, 0, 0); + assert_legal(vid(1, 0), E8_M1, 4, 0, 0); + assert_legal(vid(0, 0), E8_M1, 4, 0, 0); + } + + #[test] + fn vmv_forms_require_reserved_vs2_field_to_name_v0() { + for funct3 in [0b000, 0b100, 0b011] { + assert_illegal(op_v(0b010111, 1, 7, 4, funct3, 2), E8_M1, 4, 0, 0); + assert_legal(op_v(0b010111, 1, 0, 4, funct3, 2), E8_M1, 4, 0, 0); + } + } + + #[test] + fn same_width_integer_groups_must_be_aligned() { + // e32,m2 makes vd, vs2, and the vv-form vs1 two-register groups. + let vadd = |vd, vs2, src, funct3| op_v(0b000000, 1, vs2, src, funct3, vd); + assert_illegal(vadd(1, 2, 4, 0b000), E32_M2, 2, 0, 0); + assert_illegal(vadd(0, 3, 4, 0b000), E32_M2, 2, 0, 0); + assert_illegal(vadd(0, 2, 5, 0b000), E32_M2, 2, 0, 0); + assert_legal(vadd(0, 2, 4, 0b000), E32_M2, 2, 0, 0); + + // Scalar and immediate fields are not vector groups. + assert_legal(vadd(0, 2, 5, 0b100), E32_M2, 2, 0, 0); + assert_legal(vadd(0, 2, 5, 0b011), E32_M2, 2, 0, 0); + } + + #[test] + fn carry_and_borrow_require_vm_zero_and_nonzero_destination() { + let forms = [ + (0b010000, 0b000), // vadc.vvm + (0b010000, 0b100), // vadc.vxm + (0b010000, 0b011), // vadc.vim + (0b010010, 0b000), // vsbc.vvm + (0b010010, 0b100), // vsbc.vxm + ]; + for (funct6, funct3) in forms { + assert_illegal(op_v(funct6, 1, 2, 3, funct3, 1), E8_M1, 8, 0, 0); + assert_illegal(op_v(funct6, 0, 2, 3, funct3, 0), E8_M1, 8, 0, 0); + assert_legal(op_v(funct6, 0, 2, 3, funct3, 1), E8_M1, 8, 0, 0); + } + } + + #[test] + fn slide_up_rejects_overlap_and_misaligned_groups_but_down_allows_overlap() { + const E8_M2: u64 = 0x01; + let up_forms = [ + (0b001110, 0b100, E8_M2), // vslideup.vx + (0b001110, 0b011, E8_M2), // vslideup.vi + (0b001110, 0b110, E8_M2), // vslide1up.vx + (0b001110, 0b101, E32_M2), // vfslide1up.vf + ]; + for (funct6, funct3, vtype) in up_forms { + assert_illegal(op_v(funct6, 1, 2, 3, funct3, 2), vtype, 4, 0, 0); + assert_illegal(op_v(funct6, 1, 4, 3, funct3, 1), vtype, 4, 0, 0); + assert_illegal(op_v(funct6, 1, 3, 3, funct3, 4), vtype, 4, 0, 0); + assert_legal(op_v(funct6, 1, 4, 3, funct3, 2), vtype, 4, 0, 0); + } + + // Downward slides explicitly permit source/destination overlap. + for (funct3, vtype) in [ + (0b100, E8_M2), + (0b011, E8_M2), + (0b110, E8_M2), + (0b101, E32_M2), + ] { + assert_legal(op_v(0b001111, 1, 2, 3, funct3, 2), vtype, 4, 0, 0); + } + } + + fn narrowing_encodings(vd: u32, vs2: u32, vector_shift: u32) -> Vec { + let mut encodings = Vec::new(); + for funct6 in [0b101100, 0b101101, 0b101110, 0b101111] { + encodings.push(op_v(funct6, 1, vs2, vector_shift, 0b000, vd)); + encodings.push(op_v(funct6, 1, vs2, 5, 0b100, vd)); + encodings.push(op_v(funct6, 1, vs2, 3, 0b011, vd)); + } + for selector in 0b10000..=0b10111 { + encodings.push(op_v(0b010010, 1, vs2, selector, 0b001, vd)); + } + encodings + } + + #[test] + fn narrowing_overlap_uses_exact_rational_emul() { + // LMUL >= 1: same-lowest-register overlap is legal, upper-part overlap + // is reserved, and a disjoint aligned group is legal. + for (vtype, same, partial, source, disjoint, vector_shift) in [ + (E32_M1, 2, 3, 2, 4, 6), + (0x11, 4, 6, 4, 0, 16), // e32,m2: vd=6 overlaps upper half of v4-v7 + (0x12, 8, 12, 8, 0, 16), // e32,m4: vd=12 overlaps upper half of v8-v15 + ] { + for raw in narrowing_encodings(same, source, vector_shift) { + assert_legal(raw, vtype, 1, 0, 0); + } + for raw in narrowing_encodings(partial, source, vector_shift) { + assert_illegal(raw, vtype, 1, 0, 0); + } + for raw in narrowing_encodings(disjoint, source, vector_shift) { + assert_legal(raw, vtype, 1, 0, 0); + } + } + + // m8 would require an illegal wide source EMUL of 16 registers. + for raw in narrowing_encodings(0, 0, 16) { + assert_illegal(raw, 0x13, 1, 0, 0); + } + + // Fractional LMULs occupy one named register. Odd register numbers are + // valid, and widening mf8/mf4/mf2 does not invent a two-register group. + for vtype in [0x15, 0x16, 0x17] { + for raw in narrowing_encodings(1, 1, 4) { + assert_legal(raw, vtype, 1, 0, 0); + } + for raw in narrowing_encodings(1, 3, 4) { + assert_legal(raw, vtype, 1, 0, 0); + } + } + } + + #[test] + fn widening_rejects_low_part_and_fractional_source_overlap() { + // e32,m1: vd=v0 is a two-register wide destination. A narrow source + // in v1 overlaps its high part and is legal; a source in v0 overlaps + // its low part and is reserved. + assert_legal(op_v(0b110000, 1, 1, 2, 0b010, 0), E32_M1, 1, 0, 0); + assert_illegal(op_v(0b110000, 1, 0, 2, 0b010, 0), E32_M1, 1, 0, 0); + + // e32,mf2: the narrow source has EMUL=1/2. Although both operands + // name v1, widening overlap is forbidden when source EMUL is below 1. + assert_illegal(op_v(0b110000, 1, 1, 2, 0b010, 1), E32_MF2, 1, 0, 0); + } + + #[test] + fn widening_arithmetic_covers_integer_fp_and_conversion_families() { + let narrow_binary = [ + // Integer widening add/subtract and multiply. + (0b110000, 0b010), + (0b110001, 0b010), + (0b110010, 0b010), + (0b110011, 0b010), + (0b111000, 0b010), + (0b111010, 0b010), + (0b111011, 0b010), + // Floating-point widening add/subtract and multiply. + (0b110000, 0b001), + (0b110010, 0b001), + (0b111000, 0b001), + ]; + for (funct6, funct3) in narrow_binary { + // vd=v0 occupies {v0,v1}. Both narrow vector sources are subject + // to the same low-part/high-part overlap rule. + assert_illegal(op_v(funct6, 1, 0, 2, funct3, 0), E32_M1, 1, 0, 0); + assert_illegal(op_v(funct6, 1, 2, 0, funct3, 0), E32_M1, 1, 0, 0); + assert_legal(op_v(funct6, 1, 1, 1, funct3, 0), E32_M1, 1, 0, 0); + } + + for (funct6, funct3) in [ + (0b110000, 0b110), + (0b110001, 0b110), + (0b110010, 0b110), + (0b110011, 0b110), + (0b111000, 0b110), + (0b111010, 0b110), + (0b111011, 0b110), + (0b110000, 0b101), + (0b110010, 0b101), + (0b111000, 0b101), + ] { + // Scalar rs1 is not a vector operand; only the narrow vs2 group + // participates in overlap validation. + assert_illegal(op_v(funct6, 1, 0, 0, funct3, 0), E32_M1, 1, 0, 0); + assert_legal(op_v(funct6, 1, 1, 0, funct3, 0), E32_M1, 1, 0, 0); + } + + // Destructive widening MAC/FMA instructions also read vd at the wide + // EEW. Any overlap with a narrow vector source would therefore read + // the same register at two EEWs and is reserved, including high-part + // overlap that is legal for non-destructive widening operations. + for (funct6, funct3) in [ + (0b111100, 0b010), + (0b111101, 0b010), + (0b111111, 0b010), + (0b111100, 0b001), + (0b111101, 0b001), + (0b111110, 0b001), + (0b111111, 0b001), + ] { + assert_illegal(op_v(funct6, 1, 0, 2, funct3, 0), E32_M1, 1, 0, 0); + assert_illegal(op_v(funct6, 1, 1, 2, funct3, 0), E32_M1, 1, 0, 0); + assert_illegal(op_v(funct6, 1, 2, 1, funct3, 0), E32_M1, 1, 0, 0); + assert_legal(op_v(funct6, 1, 2, 3, funct3, 0), E32_M1, 1, 0, 0); + } + + for (funct6, funct3) in [ + (0b111100, 0b110), + (0b111101, 0b110), + (0b111111, 0b110), + (0b111100, 0b101), + (0b111101, 0b101), + (0b111110, 0b101), + (0b111111, 0b101), + ] { + assert_illegal(op_v(funct6, 1, 1, 0, funct3, 0), E32_M1, 1, 0, 0); + assert_legal(op_v(funct6, 1, 2, 0, funct3, 0), E32_M1, 1, 0, 0); + } + + // The vx-only vwmaccus form validates vs2 but must not treat scalar + // rs1 as a vector-register operand. Its wide accumulator still makes + // every destination/vs2 overlap reserved. + assert_illegal(op_v(0b111110, 1, 0, 0, 0b110, 0), E32_M1, 1, 0, 0); + assert_illegal(op_v(0b111110, 1, 1, 0, 0b110, 0), E32_M1, 1, 0, 0); + assert_legal(op_v(0b111110, 1, 2, 0, 0b110, 0), E32_M1, 1, 0, 0); + + // Every widening FP/integer conversion is unary and validates its + // narrow vs2 source before the element loop. + for selector in 0b01000..=0b01111 { + if selector == 0b01101 { + continue; // reserved VFUNARY0 selector + } + assert_illegal(op_v(0b010010, 1, 0, selector, 0b001, 0), E32_M1, 1, 0, 0); + assert_legal(op_v(0b010010, 1, 1, selector, 0b001, 0), E32_M1, 1, 0, 0); + } + } + + #[test] + fn widening_wide_source_forms_allow_same_width_alias() { + for (funct6, funct3) in [ + (0b110100, 0b010), + (0b110101, 0b010), + (0b110110, 0b010), + (0b110111, 0b010), + (0b110100, 0b001), + (0b110110, 0b001), + ] { + // Wide vs2 may fully alias the wide destination. The narrow vv + // source must be disjoint from that wide source because a source + // register cannot be read at both wide and narrow EEWs. + assert_legal(op_v(funct6, 1, 0, 2, funct3, 0), E32_M1, 1, 0, 0); + assert_illegal(op_v(funct6, 1, 0, 0, funct3, 0), E32_M1, 1, 0, 0); + assert_illegal(op_v(funct6, 1, 0, 1, funct3, 0), E32_M1, 1, 0, 0); + + // With wide vs2 disjoint, narrow vs1 may use the destination's + // high part under the normal widening overlap rule. + assert_legal(op_v(funct6, 1, 4, 1, funct3, 0), E32_M1, 1, 0, 0); + } + + // Scalar .wx/.wf forms have no vector vs1 operand. + assert_legal(op_v(0b110100, 1, 0, 0, 0b110, 0), E32_M1, 1, 0, 0); + assert_legal(op_v(0b110100, 1, 0, 0, 0b101, 0), E32_M1, 1, 0, 0); + } + + #[test] + fn widening_group_boundaries_use_exact_rational_emul() { + let vwadd_vv = |vd, vs2| op_v(0b110000, 1, vs2, 8, 0b010, vd); + + for (vtype, high_source) in [(E32_M1, 1), (E32_M2, 2), (E32_M4, 4)] { + assert_legal(vwadd_vv(0, high_source), vtype, 1, 0, 0); + assert_illegal(vwadd_vv(0, 0), vtype, 1, 0, 0); + } + + // A widening result at LMUL=8 would require the reserved EMUL=16. + assert_illegal(vwadd_vv(0, 8), E32_M8, 1, 0, 0); + + // Fractional narrow sources have EMUL below 1, so any named-register + // overlap is reserved even though each operand occupies one register. + for vtype in [E16_MF8, E32_MF4, E32_MF2] { + assert_illegal(vwadd_vv(1, 1), vtype, 1, 0, 0); + assert_legal(vwadd_vv(1, 2), vtype, 1, 0, 0); + } + + // Destination and source group alignment is validated independently. + assert_illegal(vwadd_vv(1, 2), E32_M1, 1, 0, 0); + assert_illegal(vwadd_vv(4, 1), E32_M2, 1, 0, 0); + } + + #[test] + fn vector_extensions_validate_scaled_source_emul() { + let extension = |selector, vd, vs2| op_v(0b010010, 1, vs2, selector, 0b010, vd); + for (selector, vtype, high_source) in [ + (0b00110, E32_M2, 1), // vzext.vf2: source EMUL=1 + (0b00111, E32_M2, 1), // vsext.vf2 + (0b00100, E32_M4, 3), // vzext.vf4: source EMUL=1 + (0b00101, E32_M4, 3), // vsext.vf4 + (0b00010, E64_M8, 7), // vzext.vf8: source EMUL=1 + (0b00011, E64_M8, 7), // vsext.vf8 + ] { + assert_legal(extension(selector, 0, high_source), vtype, 1, 0, 0); + assert_illegal(extension(selector, 0, 0), vtype, 1, 0, 0); + } + + // mf2 divided by the vf8 factor produces source EMUL=1/16, + // below this ELEN=64 implementation's minimum legal group of 1/8. + assert_illegal(extension(0b00010, 0, 1), E64_MF2, 1, 0, 0); + } + + #[test] + fn widening_reductions_allow_any_scalar_destination_register() { + for (funct6, funct3) in [ + (0b110000, 0b000), + (0b110001, 0b000), + (0b110001, 0b001), + (0b110011, 0b001), + ] { + // Reduction scalar sources and destinations are not LMUL groups; + // any vector register may hold them even when it lies within vs2. + assert_legal(op_v(funct6, 1, 0, 3, funct3, 1), E32_M2, 1, 0, 0); + assert_legal(op_v(funct6, 1, 0, 3, funct3, 0), E32_M2, 1, 0, 0); + + // vs2 itself remains an LMUL-sized vector group. + assert_illegal(op_v(funct6, 1, 1, 3, funct3, 4), E32_M2, 1, 0, 0); + } + } + + #[test] + fn every_reduction_rejects_nonzero_vstart_and_validates_vector_source() { + let forms = [ + (0b000000, 0b010), // vredsum.vs + (0b000001, 0b001), // vfredusum.vs + (0b110000, 0b000), // vwredsumu.vs + (0b110001, 0b001), // vfwredusum.vs + ]; + for (funct6, funct3) in forms { + let legal = op_v(funct6, 1, 2, 3, funct3, 1); + assert_illegal(legal, E32_M2, 2, 1, 0); + assert_legal(legal, E32_M2, 2, 0, 0); + + let misaligned_vs2 = op_v(funct6, 1, 1, 3, funct3, 1); + assert_illegal(misaligned_vs2, E32_M2, 2, 0, 0); + } + } + + #[test] + fn vector_fp_validation_rejects_unsupported_eew8_without_overrejecting_conversions() { + // Single-width and widening arithmetic would consume 8-bit floating- + // point operands, for which RAX exposes no supported IEEE format. + assert_illegal(op_v(0b000000, 1, 2, 3, 0b001, 1), E8_M1, 1, 0, 0); + assert_illegal(op_v(0b110000, 1, 2, 3, 0b001, 0), E8_M1, 1, 0, 0); + + // At SEW=8, Zvfh defines the integer-to-FP widening conversions and + // FP-to-integer narrowing conversions because their FP operand is + // 16 bits. The inverse directions still use an FP8 operand and trap. + for selector in [0b01010, 0b01011] { + assert_legal(op_v(0b010010, 1, 2, selector, 0b001, 0), E8_M1, 1, 0, 0); + } + for selector in [0b01000, 0b01001, 0b01100, 0b01110, 0b01111] { + assert_illegal(op_v(0b010010, 1, 2, selector, 0b001, 0), E8_M1, 1, 0, 0); + } + // The entry validator must not preempt the separately owned narrowing + // semantic path for its defined FP16-to-integer8 variants. + for selector in [0b10000, 0b10001, 0b10110, 0b10111] { + assert_legal(op_v(0b010010, 1, 2, selector, 0b001, 1), E8_M1, 1, 0, 0); + } + for selector in [0b10010, 0b10011, 0b10100, 0b10101] { + assert_illegal(op_v(0b010010, 1, 2, selector, 0b001, 1), E8_M1, 1, 0, 0); + } + + // Integer data paths remain legal at SEW=8. + assert_legal(op_v(0b000000, 1, 2, 3, 0b000, 1), E8_M1, 1, 0, 0); + } + + #[test] + fn vfncvt_fp16_to_unsigned_integer8_rounds_saturates_and_accrues_flags() { + let raw = op_v(0b010010, 1, 2, 0b10000, 0b001, 1); // vfncvt.xu.f.w v1,v2 + let insn = decoded(raw); + let mut cpu = cpu(E8_M1, 4, 0, 0); + let mut source = [0u8; 16]; + for (lane, bits) in [0x3e00u16, 0xbc00, 0x5c00, 0x7e00].into_iter().enumerate() { + source[lane * 2..lane * 2 + 2].copy_from_slice(&bits.to_le_bytes()); + } + cpu.set_vreg(2, &source); + + assert_eq!(cpu.execute_insn(&insn, 0x1000), Ok(RiscVExit::Continue)); + + assert_eq!(&cpu.vreg(1)[0..4], &[2, 0, u8::MAX, u8::MAX]); + assert_eq!( + cpu.fcsr(), + crate::isa::riscv::float::fflags::NX | crate::isa::riscv::float::fflags::NV + ); + assert_eq!(cpu.vstart(), 0); + } + + #[test] + fn narrowing_rejects_misaligned_groups_and_reserved_lmul() { + // e32,m2 has a two-register destination and a four-register wide source. + for raw in narrowing_encodings(1, 4, 16) { + assert_illegal(raw, 0x11, 1, 0, 0); + } + for raw in narrowing_encodings(0, 2, 16) { + assert_illegal(raw, 0x11, 1, 0, 0); + } + // vlmul=100 is reserved even when injected directly through the test API. + for raw in narrowing_encodings(0, 0, 4) { + assert_illegal(raw, 0x14, 1, 0, 0); + } + } + + fn decoded_vector_fp_encodings() -> Vec { + let isa = Isa::rv64gc(); + let mut encodings = Vec::new(); + for funct6 in 0..64 { + for funct3 in [0b001, 0b101] { + for src in 0..32 { + // Keep vd/vs2 disjoint and aligned for widening/narrowing + // groups. The src field is still exhaustively enumerated + // because it is either an operand or a unary selector. + let raw = op_v(funct6, 1, 12, src, funct3, 8); + let insn = decode(raw, Xlen::Rv64, &isa); + if insn.op != Op::Illegal { + encodings.push(insn); + } + } + } + } + // vfmv.s.f uses vs2=0 as an additional encoding constraint. + encodings.push(decoded(op_v(0b010000, 1, 0, 3, 0b101, 8))); + encodings + } + + #[test] + fn every_decoded_opfvv_and_opfvf_encoding_validates_frm() { + let encodings = decoded_vector_fp_encodings(); + assert!( + encodings.len() > 100, + "decoder enumeration unexpectedly found too few FP encodings" + ); + + let mut decoded_ops = Vec::new(); + let mut otherwise_legal_ops = Vec::new(); + + for insn in encodings { + assert!(is_vector_fp_encoding(&insn), "missed {:?}", insn.op); + if !decoded_ops.contains(&insn.op) { + decoded_ops.push(insn.op); + } + + let legal_for_all_frm = (0..=4).all(|frm| { + let cpu = cpu(E32_M1, 4, 0, frm); + validate(&cpu, &insn, true) == Ok(()) + }); + if legal_for_all_frm && !otherwise_legal_ops.contains(&insn.op) { + otherwise_legal_ops.push(insn.op); + } + + for frm in 5..=7 { + let cpu = cpu(E32_M1, 0, 8, frm); + assert_eq!( + validate(&cpu, &insn, true), + Err(Trap::illegal(insn.raw)), + "reserved frm={frm} accepted for {:?} ({:08x})", + insn.op, + insn.raw + ); + } + } + + for op in decoded_ops { + assert!( + otherwise_legal_ops.contains(&op), + "decoder enumeration found no register-valid encoding for {op:?}" + ); + } + + let vector_load = decoded((1 << 25) | (10 << 15) | (6 << 12) | (1 << 7) | 0x07); + assert!(!is_vector_fp_encoding(&vector_load)); + } + + #[test] + fn reserved_frm_traps_before_vl_and_vstart_short_circuits() { + let representatives = [ + op_v(0b000000, 1, 2, 3, 0b001, 1), // vfadd.vv + op_v(0b000100, 1, 2, 3, 0b001, 1), // vfmin.vv (exact) + op_v(0b001000, 1, 2, 3, 0b001, 1), // vfsgnj.vv (exact) + op_v(0b011000, 1, 2, 3, 0b001, 1), // vmfeq.vv + op_v(0b010011, 1, 2, 0b10000, 0b001, 1), // vfclass.v + op_v(0b010000, 1, 2, 0, 0b001, 1), // vfmv.f.s + op_v(0b010000, 1, 0, 3, 0b101, 1), // vfmv.s.f + op_v(0b010011, 1, 2, 0b00100, 0b001, 1), // vfrsqrt7.v + op_v(0b010010, 1, 2, 0b00110, 0b001, 1), // vfcvt.rtz.xu.f.v + op_v(0b001110, 1, 2, 3, 0b101, 1), // vfslide1up.vf + op_v(0b000001, 1, 2, 3, 0b001, 1), // vfredusum.vs + op_v(0b110000, 1, 2, 3, 0b001, 1), // vfwadd.vv + ]; + + for raw in representatives { + for frm in 5..=7 { + assert_illegal(raw, E32_M1, 0, 0, frm); + assert_illegal(raw, E32_M1, 4, 4, frm); + } + } + } +} diff --git a/src/isa/riscv/float.rs b/src/isa/riscv/float.rs index 3ba17da5e..84747bbca 100644 --- a/src/isa/riscv/float.rs +++ b/src/isa/riscv/float.rs @@ -741,15 +741,17 @@ pub fn fcvtmod_w_d(x: f64, flags: &mut u32) -> u64 { // Conversions: float -> integer, integer -> float, and f64 <-> f32. // --------------------------------------------------------------------------- -/// Convert a float to an integer. `signed`/`width` (32 or 64) select the -/// destination type; 32-bit results are sign-extended to 64 bits per the RISC-V -/// convention (including unsigned forms). Out-of-range and NaN inputs saturate -/// and raise NV; in-range inexact results raise NX. +/// Convert a float to an integer. `signed`/`width` (8, 16, 32, or 64) select +/// the destination type; 32-bit scalar results are sign-extended to 64 bits per +/// the RISC-V convention (including unsigned forms). Out-of-range and NaN +/// inputs saturate and raise NV; in-range inexact results raise NX. pub fn ftoi(x: F, signed: bool, width: u32, mode: RoundingMode, flags: &mut u32) -> u64 { // NaN saturates to the maximum of the destination type. if x.is_nan() { *flags |= fflags::NV; return match (signed, width) { + (true, 8) => i8::MAX as u64, + (false, 8) => u8::MAX as u64, (true, 16) => i16::MAX as u64, (false, 16) => u16::MAX as u64, (true, 32) => i32::MAX as i64 as u64, @@ -773,6 +775,28 @@ pub fn ftoi(x: F, signed: bool, width: u32, mode: RoundingMode, flags: &m } match (signed, width) { + (true, 8) => { + if v < i8::MIN as i128 { + *flags |= fflags::NV; + i8::MIN as u64 + } else if v > i8::MAX as i128 { + *flags |= fflags::NV; + i8::MAX as u64 + } else { + finish!(v as i8 as u64) + } + } + (false, 8) => { + if v < 0 { + *flags |= fflags::NV; + 0 + } else if v > u8::MAX as i128 { + *flags |= fflags::NV; + u8::MAX as u64 + } else { + finish!(v as u8 as u64) + } + } (true, 16) => { if v < i16::MIN as i128 { *flags |= fflags::NV; @@ -2222,6 +2246,62 @@ pub fn eval_scalar_fp( mod tests { use super::*; + fn ftoi8(value: f32, signed: bool, mode: RoundingMode) -> (u64, u32) { + let mut flags = 0; + let result = ftoi(value, signed, 8, mode, &mut flags); + (result, flags) + } + + #[test] + fn ftoi8_rounding_saturation_and_flags_are_exact() { + assert_eq!(ftoi8(1.5, false, RoundingMode::Rne), (2, fflags::NX)); + assert_eq!(ftoi8(2.5, false, RoundingMode::Rne), (2, fflags::NX)); + assert_eq!( + ftoi8(-1.5, true, RoundingMode::Rne), + ((-2i8) as u64, fflags::NX) + ); + assert_eq!(ftoi8(1.9, false, RoundingMode::Rtz), (1, fflags::NX)); + assert_eq!( + ftoi8(-1.1, true, RoundingMode::Rdn), + ((-2i8) as u64, fflags::NX) + ); + assert_eq!(ftoi8(1.1, false, RoundingMode::Rup), (2, fflags::NX)); + + assert_eq!(ftoi8(-128.0, true, RoundingMode::Rne), (i8::MIN as u64, 0)); + assert_eq!(ftoi8(127.0, true, RoundingMode::Rne), (i8::MAX as u64, 0)); + assert_eq!( + ftoi8(-129.0, true, RoundingMode::Rne), + (i8::MIN as u64, fflags::NV) + ); + assert_eq!( + ftoi8(128.0, true, RoundingMode::Rne), + (i8::MAX as u64, fflags::NV) + ); + assert_eq!(ftoi8(255.0, false, RoundingMode::Rne), (u8::MAX as u64, 0)); + assert_eq!( + ftoi8(256.0, false, RoundingMode::Rne), + (u8::MAX as u64, fflags::NV) + ); + assert_eq!(ftoi8(-1.0, false, RoundingMode::Rne), (0, fflags::NV)); + + assert_eq!( + ftoi8(f32::NAN, true, RoundingMode::Rne), + (i8::MAX as u64, fflags::NV) + ); + assert_eq!( + ftoi8(f32::NAN, false, RoundingMode::Rne), + (u8::MAX as u64, fflags::NV) + ); + assert_eq!( + ftoi8(f32::INFINITY, true, RoundingMode::Rne), + (i8::MAX as u64, fflags::NV) + ); + assert_eq!( + ftoi8(f32::NEG_INFINITY, true, RoundingMode::Rne), + (i8::MIN as u64, fflags::NV) + ); + } + #[test] fn bits_u64_copy_respects_bits_width() { // `Sf` is sealed, so `Bits` is always u32 (f32) or u64 (f64). The diff --git a/src/smir/interpret/mod.rs b/src/smir/interpret/mod.rs index fbdcf2e60..baad50205 100644 --- a/src/smir/interpret/mod.rs +++ b/src/smir/interpret/mod.rs @@ -1327,10 +1327,10 @@ fn forward_rv_vector_scalar_state(ctx: &mut SmirContext, state: &RvVectorState) /// into a transient `RiscVCpu` (over a bridge to the SMIR memory), running the /// qemu-verified vector engine, and reading the full result state back. RVV /// element width/count are runtime `vtype`/`vl` state, so this opaque delegation -/// is the only faithful lift. On a trap, scalar/vector results are not committed -/// and the interpreter exits at the instruction PC. The direct memory bridge -/// retains any lane writes performed by the vector engine before a fault; -/// detailed partial-completion and `vstart` reporting remain a dispatcher gap. +/// is the only faithful lift. The interpreter exits at the instruction PC on a +/// trap. Precise vector-memory traps publish the faulting element in `vstart` +/// and retain completed load elements; illegal encodings remain fully +/// transactional. fn exec_rv_vector( ctx: &mut SmirContext, memory: &mut dyn SmirMemory, @@ -1386,12 +1386,7 @@ fn exec_rv_vector( let isa = crate::isa::riscv::Isa::rv64gc(); let d = crate::isa::riscv::decode(insn, decode_xlen, &isa); - if d.is_illegal() - || !matches!( - cpu.execute_insn(&d, pc), - Ok(crate::isa::riscv::RiscVExit::Continue) - ) - { + if d.is_illegal() { forward_rv_vector_scalar_state(ctx, state); ctx.request_exit(ExitReason::Undefined { addr: guest_pc, @@ -1400,6 +1395,36 @@ fn exec_rv_vector( return; } + let execution = cpu.execute_insn(&d, pc); + if !matches!(execution, Ok(crate::isa::riscv::RiscVExit::Continue)) { + forward_rv_vector_scalar_state(ctx, state); + + if let Err(trap) = execution + && matches!( + trap.cause, + crate::isa::riscv::cpu::cause::LOAD_MISALIGNED + | crate::isa::riscv::cpu::cause::LOAD_ACCESS_FAULT + | crate::isa::riscv::cpu::cause::STORE_MISALIGNED + | crate::isa::riscv::cpu::cause::STORE_ACCESS_FAULT + ) + { + let vstart_out = cpu.vstart(); + ctx.write_vreg(state.vstart_dst, vstart_out); + if let ArchRegState::RiscV(rv) = &mut ctx.arch_regs { + for register in 0..32u8 { + rv.v[register as usize] = cpu.vreg(register); + } + rv.vstart = vstart_out; + } + } + + ctx.request_exit(ExitReason::Undefined { + addr: guest_pc, + opcode: insn, + }); + return; + } + let mut x_out = [0u64; 32]; let mut f_out = [0u64; 32]; let mut v_out = [[0u8; 16]; 32]; diff --git a/src/smir/interpret/tests/riscv.rs b/src/smir/interpret/tests/riscv.rs index 36fe6479b..d8cb967a8 100644 --- a/src/smir/interpret/tests/riscv.rs +++ b/src/smir/interpret/tests/riscv.rs @@ -132,3 +132,327 @@ fn rv_vector_load_uses_current_scalar_vreg_address() { assert_eq!(&rv.v[1][0..4], ¤t_lane); assert_ne!(&rv.v[1][0..4], &stale_lane); } + +#[test] +fn rv_vector_load_fault_commits_completed_elements_and_faulting_vstart() { + let mut ctx = SmirContext::new_riscv(); + let mut memory = FlatMemory::new(0x10a); + let x10 = VReg::Arch(ArchReg::RiscV(RiscVReg::X(10))); + let vl = VReg::Arch(ArchReg::RiscV(RiscVReg::Csr(0xc20))); + let vtype = VReg::Arch(ArchReg::RiscV(RiscVReg::Csr(0xc21))); + let vstart = VReg::Arch(ArchReg::RiscV(RiscVReg::Csr(0x008))); + ctx.write_vreg(x10, 0x100); + ctx.write_vreg(vl, 4); + ctx.write_vreg(vtype, 0x10); // e32,m1 + ctx.write_vreg(vstart, 0); + memory.write(0x100, &[1, 2, 3, 4, 5, 6, 7, 8]).unwrap(); + + let ArchRegState::RiscV(rv) = &mut ctx.arch_regs else { + panic!("expected RISC-V context"); + }; + rv.v[1] = [0xaa; 16]; + + // The third vle32 element at 0x108 crosses the end of memory. + let insn = (1 << 25) | (10 << 15) | (6 << 12) | (1 << 7) | 0x07; + exec_rv_vector( + &mut ctx, + &mut memory, + insn, + 64, + 0x1040, + &rv_vector_test_state(x10), + ); + + assert!(matches!( + ctx.exit_reason, + Some(ExitReason::Undefined { + addr: 0x1040, + opcode + }) if opcode == insn + )); + let ArchRegState::RiscV(rv) = &ctx.arch_regs else { + panic!("expected RISC-V context"); + }; + assert_eq!(&rv.v[1][..8], &[1, 2, 3, 4, 5, 6, 7, 8]); + assert_eq!(&rv.v[1][8..], &[0xaa; 8]); + assert_eq!(rv.vstart, 2); + assert_eq!(ctx.read_vreg(vstart), 2); +} + +#[test] +fn rv_whole_register_load_ignores_vill_and_uses_encoded_eew_vstart() { + let mut ctx = SmirContext::new_riscv(); + let mut memory = FlatMemory::new(0x200); + let x10 = VReg::Arch(ArchReg::RiscV(RiscVReg::X(10))); + let vl = VReg::Arch(ArchReg::RiscV(RiscVReg::Csr(0xc20))); + let vtype = VReg::Arch(ArchReg::RiscV(RiscVReg::Csr(0xc21))); + let vstart = VReg::Arch(ArchReg::RiscV(RiscVReg::Csr(0x008))); + ctx.write_vreg(x10, 0x100); + ctx.write_vreg(vl, 0); + ctx.write_vreg(vtype, 1u64 << 63); + ctx.write_vreg(vstart, 1); // one e32 element = four bytes + let bytes: Vec = (0..16).collect(); + memory.write(0x100, &bytes).unwrap(); + + let ArchRegState::RiscV(rv) = &mut ctx.arch_regs else { + panic!("expected RISC-V context"); + }; + rv.v[2] = [0xaa; 16]; + + // vl1re32.v v2,(a0) + let insn = (1 << 25) | (0b01000 << 20) | (10 << 15) | (6 << 12) | (2 << 7) | 0x07; + exec_rv_vector( + &mut ctx, + &mut memory, + insn, + 64, + 0x1050, + &rv_vector_test_state(x10), + ); + + assert!(ctx.exit_reason.is_none()); + let ArchRegState::RiscV(rv) = &ctx.arch_regs else { + panic!("expected RISC-V context"); + }; + assert_eq!(&rv.v[2][..4], &[0xaa; 4]); + assert_eq!(&rv.v[2][4..], &bytes[4..]); + assert_eq!(rv.vstart, 0); + assert_eq!(ctx.read_vreg(vstart), 0); + assert_eq!(ctx.read_vreg(vtype), 1u64 << 63); +} + +#[test] +fn rv_vector_vfncvt_fp16_to_integer8_commits_values_and_flags() { + let mut ctx = SmirContext::new_riscv(); + let mut memory = FlatMemory::new(0x1000); + let x10 = VReg::Arch(ArchReg::RiscV(RiscVReg::X(10))); + let fcsr = VReg::Arch(ArchReg::RiscV(RiscVReg::Csr(0x003))); + let vl = VReg::Arch(ArchReg::RiscV(RiscVReg::Csr(0xc20))); + let vtype = VReg::Arch(ArchReg::RiscV(RiscVReg::Csr(0xc21))); + ctx.write_vreg(fcsr, 0); + ctx.write_vreg(vl, 4); + ctx.write_vreg(vtype, 0); // e8,m1 + + let ArchRegState::RiscV(rv) = &mut ctx.arch_regs else { + panic!("expected RISC-V context"); + }; + rv.v[1] = [0xa5; 16]; + for (lane, bits) in [0x3e00u16, 0xbc00, 0x5c00, 0x7e00].into_iter().enumerate() { + rv.v[2][lane * 2..lane * 2 + 2].copy_from_slice(&bits.to_le_bytes()); + } + + // vfncvt.xu.f.w v1,v2: {1.5, -1, 256, qNaN} -> {2, 0, 255, 255}. + let insn = (0b010010 << 26) + | (1 << 25) + | (2 << 20) + | (0b10000 << 15) + | (0b001 << 12) + | (1 << 7) + | 0x57; + exec_rv_vector( + &mut ctx, + &mut memory, + insn, + 64, + 0x1080, + &rv_vector_test_state(x10), + ); + + assert!(ctx.exit_reason.is_none()); + let ArchRegState::RiscV(rv) = &ctx.arch_regs else { + panic!("expected RISC-V context"); + }; + assert_eq!(&rv.v[1][0..4], &[2, 0, u8::MAX, u8::MAX]); + assert_eq!( + ctx.read_vreg(fcsr), + u64::from(crate::isa::riscv::float::fflags::NX | crate::isa::riscv::float::fflags::NV) + ); + assert_eq!(ctx.read_vreg(vl), 4); + assert_eq!(ctx.read_vreg(vtype), 0); +} + +#[test] +fn rv_vector_reserved_encoding_exits_without_committing_state() { + let mut ctx = SmirContext::new_riscv(); + let mut memory = FlatMemory::new(0x2000); + let x10 = VReg::Arch(ArchReg::RiscV(RiscVReg::X(10))); + let fcsr = VReg::Arch(ArchReg::RiscV(RiscVReg::Csr(0x003))); + let vl = VReg::Arch(ArchReg::RiscV(RiscVReg::Csr(0xc20))); + let vtype = VReg::Arch(ArchReg::RiscV(RiscVReg::Csr(0xc21))); + ctx.write_vreg(fcsr, 7 << 5); // reserved frm=111 + ctx.write_vreg(vl, 0); // must not suppress frm validation + ctx.write_vreg(vtype, 0x10); // e32,m1 + + let before = [0xa5; 16]; + let ArchRegState::RiscV(rv) = &mut ctx.arch_regs else { + panic!("expected RISC-V context"); + }; + rv.v[1] = before; + + // vfsgnj.vv v1,v2,v3 is exact, but every OPFVV/OPFVF instruction still + // requires frm to hold an architecturally valid encoding. + let insn = + (0b001000 << 26) | (1 << 25) | (2 << 20) | (3 << 15) | (0b001 << 12) | (1 << 7) | 0x57; + exec_rv_vector( + &mut ctx, + &mut memory, + insn, + 64, + 0x1000, + &rv_vector_test_state(x10), + ); + + assert!(matches!( + ctx.exit_reason, + Some(ExitReason::Undefined { + addr: 0x1000, + opcode + }) if opcode == insn + )); + let ArchRegState::RiscV(rv) = &ctx.arch_regs else { + panic!("expected RISC-V context"); + }; + assert_eq!(rv.v[1], before); + assert_eq!(ctx.read_vreg(fcsr), 7 << 5); + assert_eq!(ctx.read_vreg(vl), 0); + assert_eq!(ctx.read_vreg(vtype), 0x10); +} + +#[test] +fn rv_vector_vid_reserved_vs2_exits_without_committing_state() { + let mut ctx = SmirContext::new_riscv(); + let mut memory = FlatMemory::new(0x2000); + let x10 = VReg::Arch(ArchReg::RiscV(RiscVReg::X(10))); + let vl = VReg::Arch(ArchReg::RiscV(RiscVReg::Csr(0xc20))); + let vtype = VReg::Arch(ArchReg::RiscV(RiscVReg::Csr(0xc21))); + ctx.write_vreg(vl, 4); + ctx.write_vreg(vtype, 0); // e8,m1 + + let before = [0xa5; 16]; + let ArchRegState::RiscV(rv) = &mut ctx.arch_regs else { + panic!("expected RISC-V context"); + }; + rv.v[1] = before; + + // vid.v v1 with reserved vs2=v3 must trap before filling lane indices. + let insn = (0b010100 << 26) + | (1 << 25) + | (3 << 20) + | (0b10001 << 15) + | (0b010 << 12) + | (1 << 7) + | 0x57; + exec_rv_vector( + &mut ctx, + &mut memory, + insn, + 64, + 0x1090, + &rv_vector_test_state(x10), + ); + + assert!(matches!( + ctx.exit_reason, + Some(ExitReason::Undefined { + addr: 0x1090, + opcode + }) if opcode == insn + )); + let ArchRegState::RiscV(rv) = &ctx.arch_regs else { + panic!("expected RISC-V context"); + }; + assert_eq!(rv.v[1], before); + assert_eq!(ctx.read_vreg(vl), 4); + assert_eq!(ctx.read_vreg(vtype), 0); +} + +#[test] +fn rv_vector_widening_overlap_exits_without_committing_state() { + let mut ctx = SmirContext::new_riscv(); + let mut memory = FlatMemory::new(0x2000); + let x10 = VReg::Arch(ArchReg::RiscV(RiscVReg::X(10))); + let vl = VReg::Arch(ArchReg::RiscV(RiscVReg::Csr(0xc20))); + let vtype = VReg::Arch(ArchReg::RiscV(RiscVReg::Csr(0xc21))); + ctx.write_vreg(vl, 1); + ctx.write_vreg(vtype, 0x10); // e32,m1 + + let before_v0 = [0x5a; 16]; + let before_v1 = [0xa5; 16]; + let ArchRegState::RiscV(rv) = &mut ctx.arch_regs else { + panic!("expected RISC-V context"); + }; + rv.v[0] = before_v0; + rv.v[1] = before_v1; + + // vwadd.vv v0,v0,v2: wide vd={v0,v1}, while narrow vs2=v0 overlaps + // its low part. The trap must occur before either destination register is + // committed through the opaque RVV interpreter bridge. + let insn = (0b110000 << 26) | (1 << 25) | (2 << 15) | (0b010 << 12) | 0x57; + exec_rv_vector( + &mut ctx, + &mut memory, + insn, + 64, + 0x1100, + &rv_vector_test_state(x10), + ); + + assert!(matches!( + ctx.exit_reason, + Some(ExitReason::Undefined { + addr: 0x1100, + opcode + }) if opcode == insn + )); + let ArchRegState::RiscV(rv) = &ctx.arch_regs else { + panic!("expected RISC-V context"); + }; + assert_eq!(rv.v[0], before_v0); + assert_eq!(rv.v[1], before_v1); + assert_eq!(ctx.read_vreg(vl), 1); + assert_eq!(ctx.read_vreg(vtype), 0x10); +} + +#[test] +fn rv_vector_fp_sew8_exits_without_committing_state() { + let mut ctx = SmirContext::new_riscv(); + let mut memory = FlatMemory::new(0x2000); + let x10 = VReg::Arch(ArchReg::RiscV(RiscVReg::X(10))); + let vl = VReg::Arch(ArchReg::RiscV(RiscVReg::Csr(0xc20))); + let vtype = VReg::Arch(ArchReg::RiscV(RiscVReg::Csr(0xc21))); + ctx.write_vreg(vl, 1); + ctx.write_vreg(vtype, 0x00); // e8,m1 + + let before_v1 = [0x5a; 16]; + let ArchRegState::RiscV(rv) = &mut ctx.arch_regs else { + panic!("expected RISC-V context"); + }; + rv.v[1] = before_v1; + + // vfadd.vv v1,v2,v3 would consume unsupported FP8 operands. The opaque + // RVV bridge must reject it at the instruction frontier before committing + // any destination state. + let insn = (1 << 25) | (2 << 20) | (3 << 15) | (0b001 << 12) | (1 << 7) | 0x57; + exec_rv_vector( + &mut ctx, + &mut memory, + insn, + 64, + 0x1200, + &rv_vector_test_state(x10), + ); + + assert!(matches!( + ctx.exit_reason, + Some(ExitReason::Undefined { + addr: 0x1200, + opcode + }) if opcode == insn + )); + let ArchRegState::RiscV(rv) = &ctx.arch_regs else { + panic!("expected RISC-V context"); + }; + assert_eq!(rv.v[1], before_v1); + assert_eq!(ctx.read_vreg(vl), 1); + assert_eq!(ctx.read_vreg(vtype), 0x00); +} diff --git a/src/smir/lift/riscv/fp.rs b/src/smir/lift/riscv/fp.rs index 38523f812..c9c253b40 100644 --- a/src/smir/lift/riscv/fp.rs +++ b/src/smir/lift/riscv/fp.rs @@ -1,7 +1,7 @@ //! fp.rs use crate::isa::riscv::{ - Isa as RvIsa, Op as RvOp, Xlen as RvXlen, decode as rv_decode, rvc::decode_rvc as rv_decode_rvc, + Op as RvOp, Xlen as RvXlen, decode as rv_decode, rvc::decode_rvc as rv_decode_rvc, }; use crate::smir::ir::flags::FlagUpdate; use crate::smir::ir::ops::{OpKind, RvVectorState, SmirOp}; @@ -27,7 +27,7 @@ impl RiscVLifter { } else { RvXlen::Rv32 }; - let d = rv_decode(insn, xl, &RvIsa::rv64gc()); + let d = rv_decode(insn, xl, &self.decoder_isa()); let mut ops = Vec::new(); let mk = |ctx: &mut LiftContext, k: OpKind| SmirOp::new(ctx.next_op_id(), addr, k); let base = self.get_x_reg(d.rs1, ctx); @@ -118,7 +118,7 @@ impl RiscVLifter { } else { RvXlen::Rv32 }; - let d = rv_decode(insn, xl, &RvIsa::rv64gc()); + let d = rv_decode(insn, xl, &self.decoder_isa()); let mut ops = Vec::new(); let mk = |ctx: &mut LiftContext, k: OpKind| SmirOp::new(ctx.next_op_id(), addr, k); let w = OpWidth::W64; @@ -544,7 +544,7 @@ impl RiscVLifter { } else { RvXlen::Rv32 }; - let d = rv_decode(insn, xl, &RvIsa::rv64gc()); + let d = rv_decode(insn, xl, &self.decoder_isa()); if d.is_illegal() { return Err(LiftError::InvalidEncoding { addr, diff --git a/src/smir/lift/riscv/mod.rs b/src/smir/lift/riscv/mod.rs index 1c3aa4a2d..9c000b253 100644 --- a/src/smir/lift/riscv/mod.rs +++ b/src/smir/lift/riscv/mod.rs @@ -565,6 +565,43 @@ mod tests { ); } + #[test] + fn fp_and_vector_helpers_decode_with_the_configured_profile() { + let fld = i_type(0, 1, 0b011, 1, 0x07); + let fadd_d = r_type(0b0000001, 2, 1, 0, 3, 0x53); + let fmadd_d = (3 << 27) | (0b01 << 25) | (2 << 20) | (1 << 15) | (4 << 7) | 0x43; + let vadd = 0x0221_80d7; // vadd.vv v1,v2,v3 + + // Keep F enabled so each double-precision encoding reaches its helper; + // disabling F at the outer dispatcher would not exercise the decoder. + let single_only = RiscVExtensions { + f: true, + d: false, + ..RiscVExtensions::rv64imac() + }; + for word in [fld, fadd_d, fmadd_d] { + assert_invalid_lift(RiscVLifter::new_rv64(single_only), word); + } + + let no_vector = RiscVExtensions { + v: false, + ..RiscVExtensions::rv64gc() + }; + assert_invalid_lift(RiscVLifter::new_rv64(no_vector), vadd); + + // Controls establish that the same encodings lift when their required + // profile bits are enabled. + for word in [fld, fadd_d, fmadd_d, vadd] { + let mut lifter = RiscVLifter::rv64gc(); + let mut ctx = test_ctx(); + assert!( + lifter + .lift_insn(0x1000, &word.to_le_bytes(), &mut ctx) + .is_ok() + ); + } + } + #[test] fn rv32_pack_uses_16_bit_halves_and_32_bit_result() { let pack = r_type(0b0000100, 2, 1, 0b100, 3, 0x33); diff --git a/src/smir/lift/riscv/vector.rs b/src/smir/lift/riscv/vector.rs index 1c905584b..fbe0c5f70 100644 --- a/src/smir/lift/riscv/vector.rs +++ b/src/smir/lift/riscv/vector.rs @@ -1,7 +1,7 @@ //! vector.rs use crate::isa::riscv::{ - Isa as RvIsa, Op as RvOp, Xlen as RvXlen, decode as rv_decode, rvc::decode_rvc as rv_decode_rvc, + Op as RvOp, Xlen as RvXlen, decode as rv_decode, rvc::decode_rvc as rv_decode_rvc, }; use crate::smir::ir::flags::FlagUpdate; use crate::smir::ir::ops::{OpKind, RvVectorState, SmirOp}; @@ -28,7 +28,7 @@ impl RiscVLifter { } else { RvXlen::Rv32 }; - let d = rv_decode(insn, xl, &RvIsa::rv64gc()); + let d = rv_decode(insn, xl, &self.decoder_isa()); if d.is_illegal() { return Err(LiftError::InvalidEncoding { addr, diff --git a/src/smir/lower/x86_64/mod.rs b/src/smir/lower/x86_64/mod.rs index 716e92eaf..eeffce606 100644 --- a/src/smir/lower/x86_64/mod.rs +++ b/src/smir/lower/x86_64/mod.rs @@ -105,11 +105,13 @@ mod evex_dbpsadbw_memory_source; mod evex_duplicate_move_memory_source; #[cfg(feature = "smir-jit")] mod evex_expand_memory_source; +#[cfg(feature = "smir-jit")] mod evex_extract_memory_destination; #[cfg(feature = "smir-jit")] mod evex_fixup_imm_memory_source; #[cfg(feature = "smir-jit")] mod evex_fma3_memory_source; +#[cfg(feature = "smir-jit")] mod evex_four_dot_product_memory_source; #[cfg(feature = "smir-jit")] mod evex_four_fma_memory_source; diff --git a/src/smir/lower/x86_64/tests/flag_control.rs b/src/smir/lower/x86_64/tests/flag_control.rs index 53158314f..6bc447a40 100644 --- a/src/smir/lower/x86_64/tests/flag_control.rs +++ b/src/smir/lower/x86_64/tests/flag_control.rs @@ -4,6 +4,7 @@ use super::*; use crate::smir::ir::ops::SmirOp; use crate::smir::ir::types::{BlockId, OpId}; use crate::smir::lift::ControlFlow; +#[cfg(feature = "smir-jit")] use crate::smir::lower::runtime::is_native_clobber_safe; use crate::smir::optimize::{OptLevel, optimize_function}; @@ -68,6 +69,7 @@ fn lift_exact(bytes: &[u8]) -> crate::smir::lift::LiftResult { fn assert_post_opt_native(ops: Vec, bytes: &[u8]) { let mut function = function_from_ops(ops); optimize_function(&mut function, OptLevel::O2); + #[cfg(feature = "smir-jit")] assert!( is_native_clobber_safe(&function), "post-O2 gate rejected {bytes:02X?}: {:?}", diff --git a/tests/suites/differential/riscv/vector.rs b/tests/suites/differential/riscv/vector.rs index 2f654b71a..61db0d67b 100644 --- a/tests/suites/differential/riscv/vector.rs +++ b/tests/suites/differential/riscv/vector.rs @@ -185,6 +185,7 @@ fn run_vrax(insn: u32, input: &VState) -> Option { cpu.set_fcsr(input.fcsr as u32); cpu.set_vcsr(input.vcsr); cpu.set_vl_vtype(input.vl, input.vtype); + cpu.set_vstart(input.vstart); for r in 0..32usize { cpu.set_vreg(r as u8, &input.vreg_bytes(r)); } @@ -210,6 +211,7 @@ fn run_vrax(insn: u32, input: &VState) -> Option { out.vcsr = cpu.vcsr(); out.vl = cpu.vl(); out.vtype = cpu.vtype(); + out.vstart = cpu.vstart(); for r in 0..32usize { out.set_vreg_bytes(r, &cpu.vreg(r as u8)); } @@ -269,6 +271,12 @@ fn compare(label: &str, insn: u32, input: &VState, oracle: &VOutCase, ms: &mut V rax.vtype, oracle.st.vtype )); } + if rax.vstart != oracle.st.vstart { + d.push(format!( + "vstart: rax={} hw={}", + rax.vstart, oracle.st.vstart + )); + } if rax.fcsr != oracle.st.fcsr { d.push(format!( "fcsr: rax={:#x} hw={:#x}", @@ -382,6 +390,9 @@ fn op_iv(funct6: u32, vm: u32, vs2: u32, src: u32, funct3: u32, vd: u32) -> u32 (funct6 << 26) | (vm << 25) | (vs2 << 20) | (src << 15) | (funct3 << 12) | (vd << 7) | 0x57 } +#[path = "vector/reserved_encoding.rs"] +mod reserved_encoding; + #[test] fn diff_v_arith() { let mut rng = Rng::new(0x7EC_704); diff --git a/tests/suites/differential/riscv/vector/reserved_encoding.rs b/tests/suites/differential/riscv/vector/reserved_encoding.rs new file mode 100644 index 000000000..236282664 --- /dev/null +++ b/tests/suites/differential/riscv/vector/reserved_encoding.rs @@ -0,0 +1,471 @@ +//! Reserved-encoding and architectural-constraint differential coverage. + +use super::*; + +#[test] +fn diff_v_reserved_encoding_validation() { + const E8_M1: u64 = 0x00; + const E16_MF8: u64 = 0x0d; + const E32_M1: u64 = 0x10; + const E32_M2: u64 = 0x11; + const E32_M4: u64 = 0x12; + const E32_M8: u64 = 0x13; + const E32_MF4: u64 = 0x16; + const E32_MF2: u64 = 0x17; + const E64_M8: u64 = 0x1b; + + fn state(vtype: u64, vl: u64) -> VState { + let mut rng = Rng::new(0x7EC_19_201); + let mut st = VState::zeroed(); + for value in &mut st.x[1..] { + *value = rng.next(); + } + for value in &mut st.f { + *value = 0xffff_ffff_0000_0000 | (rng.next() as u32 as u64); + } + for value in &mut st.v { + *value = rng.next(); + } + for value in &mut st.scratch { + *value = rng.next(); + } + st.vtype = vtype; + st.vl = vl; + st + } + + let mut batch = Vec::new(); + + // vmsbf.m/vmsof.m/vmsif.m: vd must not overlap vs2, masked forms cannot + // target v0, and the operations are not restartable. + for (name, selector) in [ + ("vmsbf.m", 0b00001), + ("vmsof.m", 0b00010), + ("vmsif.m", 0b00011), + ] { + batch.push(( + format!("{name}.vd-vs2-overlap"), + op_iv(0b010100, 1, 2, selector, 0b010, 2), + state(E8_M1, 8), + )); + batch.push(( + format!("{name}.masked-vd-v0"), + op_iv(0b010100, 0, 2, selector, 0b010, 0), + state(E8_M1, 8), + )); + let mut nonrestartable = state(E8_M1, 8); + nonrestartable.vstart = 1; + batch.push(( + format!("{name}.vstart"), + op_iv(0b010100, 1, 2, selector, 0b010, 1), + nonrestartable, + )); + } + + // vid.v is VMUNARY0: vs2 is reserved and must encode v0 in both masked + // and unmasked forms. + for (name, vm, vs2) in [ + ("vid.v.vs2-v16", 1, 16), + ("vid.v.masked-vs2-v3", 0, 3), + ("vid.v.control", 1, 0), + ("vid.v.masked-control", 0, 0), + ] { + batch.push(( + name.into(), + op_iv(0b010100, vm, vs2, 0b10001, 0b010, 1), + state(E8_M1, 8), + )); + } + + // vadc/vsbc consume v0 as carry/borrow-in, so vm=1 and vd=v0 are reserved + // for every defined vv/vx/vi form. + for (name, funct6, funct3) in [ + ("vadc.vvm", 0b010000, 0b000), + ("vadc.vxm", 0b010000, 0b100), + ("vadc.vim", 0b010000, 0b011), + ("vsbc.vvm", 0b010010, 0b000), + ("vsbc.vxm", 0b010010, 0b100), + ] { + batch.push(( + format!("{name}.vm-one"), + op_iv(funct6, 1, 2, 3, funct3, 1), + state(E8_M1, 8), + )); + batch.push(( + format!("{name}.vd-v0"), + op_iv(funct6, 0, 2, 3, funct3, 0), + state(E8_M1, 8), + )); + } + + // Upward slides prohibit any source/destination group overlap. The exact + // slide-by-one encodings use OPMVX/OPFVF funct3 values 110/101. Downward + // forms allow overlap and serve as differential controls. + for (name, funct3) in [ + ("vslideup.vx", 0b100), + ("vslideup.vi", 0b011), + ("vslide1up.vx", 0b110), + ("vfslide1up.vf", 0b101), + ] { + let src = if matches!(funct3, 0b100 | 0b110) { + 5 + } else { + 3 + }; + batch.push(( + format!("{name}.overlap"), + op_iv(0b001110, 1, 2, src, funct3, 2), + state(E32_M2, 4), + )); + } + for (name, funct3) in [ + ("vslidedown.vx", 0b100), + ("vslidedown.vi", 0b011), + ("vslide1down.vx", 0b110), + ("vfslide1down.vf", 0b101), + ] { + let src = if matches!(funct3, 0b100 | 0b110) { + 5 + } else { + 3 + }; + batch.push(( + format!("{name}.overlap-control"), + op_iv(0b001111, 1, 2, src, funct3, 2), + state(E32_M2, 4), + )); + } + + let narrowing: Vec<(&str, u32, u32)> = [ + ("vnsrl.wv", 0b101100, 0), + ("vnsra.wv", 0b101101, 0), + ("vnclipu.wv", 0b101110, 0), + ("vnclip.wv", 0b101111, 0), + ] + .into_iter() + .chain( + [ + "vfncvt.xu.f.w", + "vfncvt.x.f.w", + "vfncvt.f.xu.w", + "vfncvt.f.x.w", + "vfncvt.f.f.w", + "vfncvt.rod.f.f.w", + "vfncvt.rtz.xu.f.w", + "vfncvt.rtz.x.f.w", + ] + .into_iter() + .enumerate() + .map(|(index, name)| (name, 0b010010, 0b10000 + index as u32)), + ) + .collect(); + for (name, funct6, selector) in narrowing { + let funct3 = if funct6 == 0b010010 && selector >= 0b10000 { + 0b001 + } else { + 0b000 + }; + let src = if funct3 == 0b001 { selector } else { 4 }; + batch.push(( + format!("{name}.upper-overlap"), + op_iv(funct6, 1, 2, src, funct3, 3), + state(E32_M1, 2), + )); + batch.push(( + format!("{name}.same-lowest-control"), + op_iv(funct6, 1, 2, src, funct3, 2), + state(E32_M1, 2), + )); + batch.push(( + format!("{name}.fractional-odd-control"), + op_iv(funct6, 1, 1, src, funct3, 1), + state(E32_MF2, 2), + )); + batch.push(( + format!("{name}.emul-16"), + op_iv(funct6, 1, 0, src, funct3, 0), + state(E32_M8, 1), + )); + } + + // Widening destinations may overlap a narrow source only in their + // highest-numbered part, and only when source EMUL is at least one. + // Exercise integer, FP, conversion, and multiply-accumulate families. + for (name, funct6, funct3, src) in [ + ("vwadd.vv", 0b110000, 0b010, 2), + ("vwadd.vx", 0b110000, 0b110, 5), + ("vwmulu.vv", 0b111000, 0b010, 2), + ("vfwadd.vv", 0b110000, 0b001, 2), + ("vfwadd.vf", 0b110000, 0b101, 5), + ("vfwmul.vv", 0b111000, 0b001, 2), + ("vfwcvt.xu.f.v", 0b010010, 0b001, 0b01000), + ] { + batch.push(( + format!("{name}.low-overlap"), + op_iv(funct6, 1, 0, src, funct3, 0), + state(E32_M1, 2), + )); + batch.push(( + format!("{name}.high-overlap-control"), + op_iv(funct6, 1, 1, src, funct3, 0), + state(E32_M1, 2), + )); + } + + // Widening MAC/FMA instructions read the wide destination as an addend. + // A narrow source cannot overlap it at either the low or high part because + // that would read one register at two EEWs in the same instruction. + for (name, funct6, funct3, src) in [ + ("vwmacc.vv", 0b111101, 0b010, 3), + ("vwmaccus.vx", 0b111110, 0b110, 5), + ("vfwmacc.vv", 0b111100, 0b001, 3), + ] { + batch.push(( + format!("{name}.low-overlap"), + op_iv(funct6, 1, 0, src, funct3, 0), + state(E32_M1, 2), + )); + batch.push(( + format!("{name}.high-overlap"), + op_iv(funct6, 1, 1, src, funct3, 0), + state(E32_M1, 2), + )); + batch.push(( + format!("{name}.disjoint-control"), + op_iv(funct6, 1, 2, src, funct3, 0), + state(E32_M1, 2), + )); + } + + // .w forms read a same-width wide vs2, which may alias vd. A narrow vector + // vs1 must not overlap wide vs2, and otherwise follows the high-part rule + // when it overlaps vd. + for (name, funct6, funct3) in [ + ("vwadd.wv", 0b110100, 0b010), + ("vfwadd.wv", 0b110100, 0b001), + ] { + batch.push(( + format!("{name}.wide-alias-control"), + op_iv(funct6, 1, 0, 2, funct3, 0), + state(E32_M1, 2), + )); + batch.push(( + format!("{name}.narrow-low-overlap"), + op_iv(funct6, 1, 0, 0, funct3, 0), + state(E32_M1, 2), + )); + batch.push(( + format!("{name}.narrow-high-source-conflict"), + op_iv(funct6, 1, 0, 1, funct3, 0), + state(E32_M1, 2), + )); + batch.push(( + format!("{name}.destination-high-overlap-control"), + op_iv(funct6, 1, 4, 1, funct3, 0), + state(E32_M1, 2), + )); + } + + // Integer LMUL frontiers: m2/m4 retain the legal high-part case, m8 + // would require a reserved EMUL=16 destination, and fractional narrow + // sources cannot overlap the destination at all. + for (vtype, high_source, label) in [(E32_M2, 2, "m2"), (E32_M4, 4, "m4")] { + batch.push(( + format!("vwadd.vv.{label}.high-overlap-control"), + op_iv(0b110000, 1, high_source, 8, 0b010, 0), + state(vtype, 2), + )); + batch.push(( + format!("vwadd.vv.{label}.low-overlap"), + op_iv(0b110000, 1, 0, 8, 0b010, 0), + state(vtype, 2), + )); + } + batch.push(( + "vwadd.vv.m8.emul-16".into(), + op_iv(0b110000, 1, 8, 16, 0b010, 0), + state(E32_M8, 1), + )); + for (vtype, label) in [(E16_MF8, "mf8"), (E32_MF4, "mf4"), (E32_MF2, "mf2")] { + batch.push(( + format!("vwadd.vv.{label}.same-register-overlap"), + op_iv(0b110000, 1, 1, 2, 0b010, 1), + state(vtype, 1), + )); + batch.push(( + format!("vwadd.vv.{label}.disjoint-control"), + op_iv(0b110000, 1, 2, 3, 0b010, 1), + state(vtype, 1), + )); + } + + // Integer extension source EMUL is LMUL divided by vf2/vf4/vf8. + for (name, selector, vtype, high_source) in [ + ("vzext.vf2", 0b00110, E32_M2, 1), + ("vzext.vf4", 0b00100, E32_M4, 3), + ("vzext.vf8", 0b00010, E64_M8, 7), + ] { + batch.push(( + format!("{name}.high-overlap-control"), + op_iv(0b010010, 1, high_source, selector, 0b010, 0), + state(vtype, 1), + )); + batch.push(( + format!("{name}.low-overlap"), + op_iv(0b010010, 1, 0, selector, 0b010, 0), + state(vtype, 1), + )); + } + // Widening reductions use scalar EMUL=1 for vd/vs1 but current LMUL for + // their narrow vs2 group. + for (name, funct6, funct3) in [ + ("vwredsumu.vs", 0b110000, 0b000), + ("vfwredusum.vs", 0b110001, 0b001), + ] { + batch.push(( + format!("{name}.high-overlap-control"), + op_iv(funct6, 1, 0, 3, funct3, 1), + state(E32_M2, 2), + )); + batch.push(( + format!("{name}.low-overlap-control"), + op_iv(funct6, 1, 0, 3, funct3, 0), + state(E32_M2, 2), + )); + batch.push(( + format!("{name}.misaligned-vs2"), + op_iv(funct6, 1, 1, 3, funct3, 4), + state(E32_M2, 2), + )); + } + + // vmv.v.v/vx/vi reserve vs2 and require it to encode v0. + for (name, funct3, src) in [ + ("vmv.v.v", 0b000, 4), + ("vmv.v.x", 0b100, 5), + ("vmv.v.i", 0b011, 4), + ] { + batch.push(( + format!("{name}.reserved-vs2"), + op_iv(0b010111, 1, 7, src, funct3, 2), + state(E8_M1, 4), + )); + batch.push(( + format!("{name}.vs2-v0-control"), + op_iv(0b010111, 1, 0, src, funct3, 2), + state(E8_M1, 4), + )); + } + + // Same-width ALU operands name complete LMUL-sized register groups. + for (name, vd, vs2, vs1) in [ + ("vadd.vv.misaligned-vd", 1, 2, 4), + ("vadd.vv.misaligned-vs2", 0, 3, 4), + ("vadd.vv.misaligned-vs1", 0, 2, 5), + ("vadd.vv.aligned-control", 0, 2, 4), + ] { + batch.push(( + name.into(), + op_iv(0b000000, 1, vs2, vs1, 0b000, vd), + state(E32_M2, 2), + )); + } + + // Every integer/FP, single-width/widening reduction is non-restartable. + for (name, funct6, funct3) in [ + ("vredsum.vs", 0b000000, 0b010), + ("vfredusum.vs", 0b000001, 0b001), + ("vwredsumu.vs", 0b110000, 0b000), + ("vfwredusum.vs", 0b110001, 0b001), + ] { + let mut nonrestartable = state(E32_M2, 2); + nonrestartable.vstart = 1; + batch.push(( + format!("{name}.nonzero-vstart"), + op_iv(funct6, 1, 2, 3, funct3, 1), + nonrestartable, + )); + batch.push(( + format!("{name}.vstart-zero-control"), + op_iv(funct6, 1, 2, 3, funct3, 1), + state(E32_M2, 2), + )); + } + + // All vsetvl forms reset vstart on successful completion. + let mut configured = state(E8_M1, 4); + configured.vstart = 7; + batch.push(( + "vsetvli.resets-vstart".into(), + (7 << 12) | (1 << 7) | 0x57, + configured, + )); + + // FP operands with EEW=8 are unsupported. Zvfh still defines the + // integer-to-FP widening and FP-to-integer narrowing directions at SEW=8 + // because their FP operand is 16 bits; integer instructions remain + // unaffected. + for (name, instruction) in [ + ("vfadd.vv.e8", op_iv(0b000000, 1, 2, 3, 0b001, 1)), + ("vfslide1up.vf.e8", op_iv(0b001110, 1, 2, 3, 0b101, 1)), + ("vfwadd.vv.e8", op_iv(0b110000, 1, 2, 3, 0b001, 0)), + ("vfwcvt.xu.f.v.e8", op_iv(0b010010, 1, 2, 0b01000, 0b001, 0)), + ("vfncvt.f.xu.w.e8", op_iv(0b010010, 1, 2, 0b10010, 0b001, 1)), + ( + "vfwcvt.f.xu.v.e8.control", + op_iv(0b010010, 1, 2, 0b01010, 0b001, 0), + ), + ( + "vfncvt.xu.f.w.e8.control", + op_iv(0b010010, 1, 2, 0b10000, 0b001, 1), + ), + ( + "vfncvt.x.f.w.e8.control", + op_iv(0b010010, 1, 2, 0b10001, 0b001, 1), + ), + ( + "vfncvt.rtz.xu.f.w.e8.control", + op_iv(0b010010, 1, 2, 0b10110, 0b001, 1), + ), + ( + "vfncvt.rtz.x.f.w.e8.control", + op_iv(0b010010, 1, 2, 0b10111, 0b001, 1), + ), + ("vadd.vv.e8.control", op_iv(0b000000, 1, 2, 3, 0b000, 1)), + ] { + batch.push((name.into(), instruction, state(E8_M1, 2))); + } + + // Every OPFVV/OPFVF instruction must reject reserved frm=5/6/7 before + // either vl=0 or vstart>=vl could suppress its element loop. + let fp_representatives = [ + ("vfadd.vv", op_iv(0b000000, 1, 2, 3, 0b001, 1)), + ("vfmin.vv", op_iv(0b000100, 1, 2, 3, 0b001, 1)), + ("vfsgnj.vv", op_iv(0b001000, 1, 2, 3, 0b001, 1)), + ("vmfeq.vv", op_iv(0b011000, 1, 2, 3, 0b001, 1)), + ("vfclass.v", op_iv(0b010011, 1, 2, 0b10000, 0b001, 1)), + ("vfmv.f.s", op_iv(0b010000, 1, 2, 0, 0b001, 1)), + ("vfmv.s.f", op_iv(0b010000, 1, 0, 3, 0b101, 1)), + ("vfrsqrt7.v", op_iv(0b010011, 1, 2, 0b00100, 0b001, 1)), + ("vfrec7.v", op_iv(0b010011, 1, 2, 0b00101, 0b001, 1)), + ("vfcvt.rtz.xu.f.v", op_iv(0b010010, 1, 2, 0b00110, 0b001, 1)), + ("vfslide1up.vf", op_iv(0b001110, 1, 2, 3, 0b101, 1)), + ("vfslide1down.vf", op_iv(0b001111, 1, 2, 3, 0b101, 1)), + ("vfredusum.vs", op_iv(0b000001, 1, 2, 3, 0b001, 1)), + ("vfwadd.vv", op_iv(0b110000, 1, 2, 3, 0b001, 1)), + ]; + for (name, insn) in fp_representatives { + for frm in 5..=7u64 { + let mut vl_zero = state(E32_M1, 0); + vl_zero.fcsr = frm << 5; + batch.push((format!("{name}.frm-{frm}.vl-zero"), insn, vl_zero)); + + let mut completed = state(E32_M1, 4); + completed.fcsr = frm << 5; + completed.vstart = 4; + batch.push((format!("{name}.frm-{frm}.vstart-at-vl"), insn, completed)); + } + } + + run_batch(&batch); +} diff --git a/tests/suites/smir/jit/riscv_x86_64.rs b/tests/suites/smir/jit/riscv_x86_64.rs index fed55ce5b..29a8797cb 100644 --- a/tests/suites/smir/jit/riscv_x86_64.rs +++ b/tests/suites/smir/jit/riscv_x86_64.rs @@ -27,6 +27,9 @@ const CODE: u64 = 0x1000; const DATA: u64 = 0x2000; const MEMORY_LEN: usize = 0x4000; +#[path = "riscv_x86_64/vector_validation.rs"] +mod vector_validation; + #[repr(C)] struct TestMemory { bytes: [u8; MEMORY_LEN], diff --git a/tests/suites/smir/jit/riscv_x86_64/vector_validation.rs b/tests/suites/smir/jit/riscv_x86_64/vector_validation.rs new file mode 100644 index 000000000..a72e0a1d3 --- /dev/null +++ b/tests/suites/smir/jit/riscv_x86_64/vector_validation.rs @@ -0,0 +1,269 @@ +//! Fail-closed RVV validation through the x86-64 native helper ABI. + +use super::*; + +fn run_invalid_vector_case(instruction: u32, initial: RiscVGuestRegs) { + let bytes = instruction.to_le_bytes(); + let mut lifter = RiscVLifter::rv64gc(); + let mut context = LiftContext::new(SourceArch::RiscV64); + let lifted = lifter + .lift_insn(CODE, &bytes, &mut context) + .expect("lift reserved vector encoding"); + let (function, return_pcs) = + function_for_lift(lifted.control_flow, lifted.ops, lifted.bytes_consumed); + + for level in [OptLevel::O0, OptLevel::O2] { + let mut optimized = function.clone(); + optimize_function(&mut optimized, level); + let mut lowerer = RiscVX86_64Lowerer::new(); + lowerer.set_return_pcs(return_pcs.clone()); + let lowered = lowerer.lower_function(&optimized).unwrap_or_else(|error| { + panic!("lower reserved vector encoding at {level:?}: {error:?}") + }); + let code = lowerer.finalize().expect("finalize reserved vector code"); + let executable = ExecMem::new(&code).expect("map reserved vector code"); + + let initial_memory = [0xa5; MEMORY_LEN]; + let mut memory = TestMemory::new(initial_memory); + let mut state = jit_state(&mut memory, initial.x, initial.f, initial.fcsr as u32, CODE); + state.v = initial.v; + state.vl = initial.vl; + state.vtype = initial.vtype; + state.vstart = initial.vstart; + state.vcsr = initial.vcsr; + let mut expected = state; + expected.exit_reason = 1; + + executable.run_riscv(lowered.entry_offset, &mut state); + + assert_eq!(state, expected, "partial state commit at {level:?}"); + assert_eq!(memory.bytes, initial_memory, "memory commit at {level:?}"); + } +} + +#[test] +fn lifted_rv_vector_reserved_encodings_fail_closed_transactionally() { + let mut initial = RiscVGuestRegs { + vl: 4, + vtype: 0x10, // e32,m1 + vcsr: 5, + ..Default::default() + }; + for register in 1..32usize { + initial.x[register] = 0x1020_3040_5060_7080 ^ register as u64; + } + for register in 0..32usize { + initial.f[register] = 0xffff_ffff_3f80_0000 + register as u64; + initial.v[register] = [register as u8; 16]; + } + + let cases = [ + // vmsbf.m v2,v2: destination/source overlap. + ( + (0b010100 << 26) + | (1 << 25) + | (2 << 20) + | (0b00001 << 15) + | (0b010 << 12) + | (2 << 7) + | 0x57, + initial, + ), + // vadc.vvm v0,v2,v3: v0 cannot be the destination. + ( + (0b010000 << 26) | (2 << 20) | (3 << 15) | (0 << 12) | 0x57, + initial, + ), + // vslide1up.vx v2,v2,x3: exact encoding and overlapping groups. + ( + (0b001110 << 26) | (1 << 25) | (2 << 20) | (3 << 15) | (0b110 << 12) | (2 << 7) | 0x57, + initial, + ), + // vnsrl.wv v3,v2,v4: destination overlaps the upper half of wide vs2. + ( + (0b101100 << 26) | (1 << 25) | (2 << 20) | (4 << 15) | (3 << 7) | 0x57, + initial, + ), + // vwadd.vv v0,v0,v2: narrow vs2 overlaps the low part of wide vd. + ( + (0b110000 << 26) | (1 << 25) | (2 << 15) | (0b010 << 12) | 0x57, + initial, + ), + // vwadd.vv v1,v1,v2 under mf2: source EMUL is below one, so even + // same-register overlap is reserved. + ( + (0b110000 << 26) | (1 << 25) | (1 << 20) | (2 << 15) | (0b010 << 12) | (1 << 7) | 0x57, + RiscVGuestRegs { + vtype: 0x17, // e32,mf2 + ..initial + }, + ), + // vfwadd.vv v0,v0,v2: FP widening uses the same group rule. + ( + (0b110000 << 26) | (1 << 25) | (2 << 15) | (0b001 << 12) | 0x57, + initial, + ), + // vzext.vf2 v0,v0 under m2: source overlaps the low part of vd. + ( + (0b010010 << 26) | (1 << 25) | (0b00110 << 15) | (0b010 << 12) | 0x57, + RiscVGuestRegs { + vtype: 0x11, // e32,m2 + ..initial + }, + ), + // vfadd.vv at SEW=8 would consume unsupported FP8 operands. + ( + (1 << 25) | (2 << 20) | (3 << 15) | (0b001 << 12) | (1 << 7) | 0x57, + RiscVGuestRegs { + vtype: 0x00, // e8,m1 + ..initial + }, + ), + // vfsgnj.vv with frm=7 and vl=0 must still reject before execution. + ( + (0b001000 << 26) | (1 << 25) | (2 << 20) | (3 << 15) | (0b001 << 12) | (1 << 7) | 0x57, + RiscVGuestRegs { + fcsr: 7 << 5, + vl: 0, + ..initial + }, + ), + // vid.v reserves vs2 and requires it to encode v0. + ( + (0b010100 << 26) + | (1 << 25) + | (3 << 20) + | (0b10001 << 15) + | (0b010 << 12) + | (1 << 7) + | 0x57, + RiscVGuestRegs { + vtype: 0, // e8,m1 + ..initial + }, + ), + // vmv.v.v shares vmerge's encoding but reserves vs2 to v0. + ( + (0b010111 << 26) | (1 << 25) | (3 << 20) | (4 << 15) | (0b000 << 12) | (2 << 7) | 0x57, + RiscVGuestRegs { + vtype: 0, // e8,m1 + ..initial + }, + ), + // vadd.vv v1,v2,v4 under m2 has a misaligned destination group. + ( + (1 << 25) | (2 << 20) | (4 << 15) | (0b000 << 12) | (1 << 7) | 0x57, + RiscVGuestRegs { + vtype: 0x11, // e32,m2 + ..initial + }, + ), + // Reductions are non-restartable and reject a nonzero vstart. + ( + (1 << 25) | (2 << 20) | (3 << 15) | (0b010 << 12) | (1 << 7) | 0x57, + RiscVGuestRegs { + vstart: 1, + vtype: 0x11, // e32,m2 + ..initial + }, + ), + // Ordinary vector arithmetic depends on vtype and traps under vill. + ( + (1 << 25) | (2 << 20) | (4 << 15) | (0b000 << 12) | (2 << 7) | 0x57, + RiscVGuestRegs { + vtype: 1u64 << 63, + ..initial + }, + ), + ]; + + for (instruction, state) in cases { + run_invalid_vector_case(instruction, state); + } +} + +#[test] +fn lifted_vid_with_vs2_v0_remains_legal_at_o0_and_o2() { + let instruction = + (0b010100 << 26) | (1 << 25) | (0b10001 << 15) | (0b010 << 12) | (1 << 7) | 0x57; + let initial = RiscVGuestRegs { + vl: 4, + vtype: 0, // e8,m1 + ..Default::default() + }; + run_vector_case(instruction, initial, [0xa5; MEMORY_LEN], false); +} + +#[test] +fn lifted_vfncvt_fp16_to_integer8_matches_direct_at_o0_and_o2() { + let mut initial = RiscVGuestRegs { + vl: 4, + vtype: 0, // e8,m1 + ..Default::default() + }; + for (lane, bits) in [0x3e00u16, 0xbe00, 0x5c00, 0x7e00].into_iter().enumerate() { + initial.v[2][lane * 2..lane * 2 + 2].copy_from_slice(&bits.to_le_bytes()); + } + + for selector in [0b10000, 0b10001, 0b10110, 0b10111] { + let instruction = (0b010010 << 26) + | (1 << 25) + | (2 << 20) + | (selector << 15) + | (0b001 << 12) + | (1 << 7) + | 0x57; + run_vector_case(instruction, initial, [0xa5; MEMORY_LEN], false); + } +} + +#[test] +fn lifted_followup_rvv_controls_match_direct_at_o0_and_o2() { + let mut initial = RiscVGuestRegs { + vl: 4, + vtype: 0, + ..Default::default() + }; + initial.v[4] = [3; 16]; + + // Legal vmv.v.v with the reserved vs2 field set to v0. + run_vector_case( + (0b010111 << 26) | (1 << 25) | (4 << 15) | (0b000 << 12) | (2 << 7) | 0x57, + initial, + [0xa5; MEMORY_LEN], + false, + ); + + // Aligned vadd.vv groups under LMUL=2. + run_vector_case( + (1 << 25) | (2 << 20) | (4 << 15) | (0b000 << 12) | 0x57, + RiscVGuestRegs { + vtype: 0x11, + ..initial + }, + [0xa5; MEMORY_LEN], + false, + ); + + // Reduction with vstart=0 remains legal. + run_vector_case( + (1 << 25) | (2 << 20) | (3 << 15) | (0b010 << 12) | (1 << 7) | 0x57, + RiscVGuestRegs { + vtype: 0x11, + ..initial + }, + [0xa5; MEMORY_LEN], + false, + ); + + // vsetvli clears a guest-supplied nonzero vstart. + run_vector_case( + (7 << 12) | (1 << 7) | 0x57, + RiscVGuestRegs { + vstart: 7, + ..initial + }, + [0xa5; MEMORY_LEN], + false, + ); +} diff --git a/tests/suites/tooling/ci_actions_pinned.rs b/tests/suites/tooling/ci_actions_pinned.rs index 937d0a5ec..7ad5c8eaa 100644 --- a/tests/suites/tooling/ci_actions_pinned.rs +++ b/tests/suites/tooling/ci_actions_pinned.rs @@ -89,6 +89,34 @@ fn scheduled_kvm_keeps_host_dependent_differentials_informational() { } } +#[test] +fn experimental_cross_builds_report_failures_without_red_checks() { + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let workflow = root.join(".github/workflows/cross.yml"); + let contents = fs::read_to_string(&workflow) + .unwrap_or_else(|err| panic!("failed to read {}: {err}", workflow.display())); + + let marker = " cross-experimental:\n"; + let start = contents + .find(marker) + .expect("cross workflow is missing its experimental job"); + let section = contents[start + marker.len()..] + .lines() + .take_while(|line| line.trim().is_empty() || line.starts_with(" ")) + .collect::>() + .join("\n"); + + assert!( + section.contains("continue-on-error: true"), + "experimental targets must remain explicitly non-gating" + ); + assert!( + section.contains("if cross +nightly build") + && section.contains("Experimental cross-build unavailable"), + "experimental target failures must become visible warnings, not red checks" + ); +} + #[test] fn push_ci_runs_host_specific_scalar_and_evex_jit_regressions() { let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); diff --git a/tools/riscv-diff/voracle.c b/tools/riscv-diff/voracle.c index 882155f8b..e744dda29 100644 --- a/tools/riscv-diff/voracle.c +++ b/tools/riscv-diff/voracle.c @@ -4,8 +4,8 @@ * A sibling of oracle.c dedicated to the V extension. Built as a static RV64 * ELF and run under qemu-riscv64 (default cpu, which implements RVV). The * generated prologue installs the integer/FP registers, loads v0..v31 from a - * MAP_FIXED block, sets the test vtype/vl, runs ONE vector instruction, then - * EBREAK. The SIGTRAP handler captures the integer/FP frame plus the vector + * MAP_FIXED block, sets the test vtype/vl/vstart, runs ONE vector instruction, + * then EBREAK. The SIGTRAP handler captures the integer/FP frame plus the vector * state (vl/vtype/vstart and the VLEN-bit register file) parsed from the * signal-frame V context. * @@ -30,7 +30,7 @@ #define SCRATCH_ADDR 0x200000ull #define SCRATCH_SIZE 4096 -#define INPUT_ADDR 0x210000ull /* x/f/fcsr/vtype/vl block */ +#define INPUT_ADDR 0x210000ull /* scalar and vector-CSR input block */ #define VIN_ADDR 0x220000ull /* 512-byte v0..v31 input data */ #define BLOCK_SIZE 4096 @@ -40,6 +40,7 @@ #define IN_VTYPE_OFF (65 * 8) #define IN_VL_OFF (66 * 8) #define IN_VCSR_OFF (67 * 8) +#define IN_VSTART_OFF (68 * 8) typedef struct { uint64_t x[32]; @@ -212,6 +213,9 @@ int main(void) { code[n++] = enc_ld(6, 31, IN_VTYPE_OFF); code[n++] = enc_ld(7, 31, IN_VL_OFF); code[n++] = enc_vsetvl(0, 7, 6); + /* vstart is written after vsetvl, which resets it to zero. */ + code[n++] = enc_ld(28, 31, IN_VSTART_OFF); + code[n++] = enc_csrw(0x008u, 28); /* int regs x1,x2,x5..x30 (skip gp/tp), x31 last */ for (int i = 1; i <= 30; i++) { if (i == 3 || i == 4) continue; @@ -257,6 +261,7 @@ int main(void) { memcpy((char *)INPUT_ADDR + IN_VTYPE_OFF, &ic->st.vtype, 8); memcpy((char *)INPUT_ADDR + IN_VL_OFF, &ic->st.vl, 8); memcpy((char *)INPUT_ADDR + IN_VCSR_OFF, &ic->st.vcsr, 8); + memcpy((char *)INPUT_ADDR + IN_VSTART_OFF, &ic->st.vstart, 8); memcpy((void *)VIN_ADDR, ic->st.v, 32 * VLENB); memcpy((void *)SCRATCH_ADDR, ic->st.scratch, sizeof(ic->st.scratch));