MMgr v1.0.0
Zero-heap memory manager in C11. One borrowed buffer, carved by asserted offset.
Loading...
Searching...
No Matches
praet_platform_detection.h
Go to the documentation of this file.
1/* MMgr - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2 * SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Commercial OR LicenseRef-Educational
3 *
4 * Every use falls under AGPL-3.0-or-later unless you hold explicit permission, which is either a
5 * negotiated commercial licensing contract or an educator's license issued to you personally.
6 */
7/**
8 * @file praet_platform_detection.h
9 * @brief Which architecture this is being built for, and what that architecture gives a clock.
10 * @author dstroy0 (Douglas Quigg) <dquigg123@gmail.com>
11 * @date 2026-09-01
12 *
13 * @note Nothing below names a part, a vendor or a board, and nothing should. A list of parts covers
14 * the ones somebody thought of on the day they wrote it and silently misses every part released
15 * afterwards. What is tested here is the architecture level, which every member of a family
16 * defines - an ATSAMD21 answers the ARMv6-M question the same way every other Cortex-M0+ does,
17 * and a part nobody here has heard of answers it too.
18 * @note The three families are the ones this library targets: ARM, RISC-V and Xtensa, all through
19 * GCC or clang. A build that is none of them is a host build, which is where the suites run.
20 * @note The architecture tests below exist for the clock. embedded_types answers the compiler and
21 * width questions, and does not say whether a cycle counter exists.
22 *
23 * Sources for the macros used below:
24 * Arm C Language Extensions https://arm-software.github.io/acle/main/acle.html
25 * RISC-V C API specification https://github.com/riscv-non-isa/riscv-c-api-doc
26 * GCC gcc/config/xtensa/xtensa.h TARGET_CPU_CPP_BUILTINS
27 */
28#ifndef MMGR_PRAET_PLATFORM_DETECTION_H
29#define MMGR_PRAET_PLATFORM_DETECTION_H
30
31#include "mmgr.h"
32
33/**
34 * @brief Set where this is an ARM build, in either execution state.
35 *
36 * @note __arm__ covers every 32-bit ARM back to ARMv4, and __aarch64__ covers the 64-bit state. ACLE
37 * leaves both in place, and so testing the pair catches every ARM subfamily without naming one.
38 */
39#if defined(__arm__) || defined(__aarch64__) || defined(__thumb__)
40#define PRAET_PLATFORM_ARM 1
41#else
42#define PRAET_PLATFORM_ARM 0
43#endif
44
45/**
46 * @brief Set where this is a RISC-V build, at any register width.
47 *
48 * @note The RISC-V C API defines __riscv as 1 on every RISC-V target. There is no second name and
49 * no per-vendor variant of it.
50 */
51#if defined(__riscv)
52#define PRAET_PLATFORM_RISCV 1
53#else
54#define PRAET_PLATFORM_RISCV 0
55#endif
56
57/**
58 * @brief Set where this is an Xtensa build.
59 *
60 * @note GCC predefines both names unconditionally for Xtensa. Both are tested because a clang
61 * build for Xtensa is a separate front end and there is no reason to depend on it agreeing
62 * about which one to emit.
63 */
64#if defined(__XTENSA__) || defined(__xtensa__)
65#define PRAET_PLATFORM_XTENSA 1
66#else
67#define PRAET_PLATFORM_XTENSA 0
68#endif
69
70/**
71 * @brief Set where this is none of the three, which is a host build.
72 */
73#if !PRAET_PLATFORM_ARM && !PRAET_PLATFORM_RISCV && !PRAET_PLATFORM_XTENSA
74#define PRAET_PLATFORM_HOST 1
75#else
76#define PRAET_PLATFORM_HOST 0
77#endif
78
80 "the platform detection selected more than one architecture family, or none");
81
82#if PRAET_PLATFORM_ARM
83
84/*
85 * ARM subfamilies, by architecture level and profile. No part is named.
86 *
87 * __ARM_ARCH is the architecture level as an integer and __ARM_ARCH_PROFILE is the profile as a
88 * character constant, 'A', 'R' or 'M'. Between them they name every Cortex subfamily, and a
89 * pre-Cortex core defines neither profile nor a level above 6. A character constant is a legal
90 * operand in a preprocessor expression, and an undefined __ARM_ARCH_PROFILE evaluates to 0, which
91 * matches none of the three.
92 */
93#if defined(__ARM_ARCH_PROFILE) && (__ARM_ARCH_PROFILE == 'M')
94#if __ARM_ARCH >= 8
95
96/**
97 * @brief What to call this architecture in a diagnostic.
98 *
99 * @note ARMv8-M and ARMv8.1-M, which is Cortex-M23 through M85. The baseline and mainline split is
100 * below in the cycle counter question, where it is the part that matters.
101 */
102#define PRAET_PLATFORM_NAME "ARMv8-M"
103#elif __ARM_ARCH == 7
104#define PRAET_PLATFORM_NAME "ARMv7-M"
105#else
106#define PRAET_PLATFORM_NAME "ARMv6-M"
107#endif
108#elif defined(__ARM_ARCH_PROFILE) && (__ARM_ARCH_PROFILE == 'R')
109#define PRAET_PLATFORM_NAME "ARM real-time profile"
110#elif defined(__ARM_ARCH_PROFILE) && (__ARM_ARCH_PROFILE == 'A')
111#if defined(__ARM_64BIT_STATE)
112#define PRAET_PLATFORM_NAME "ARM application profile, 64 bit"
113#else
114#define PRAET_PLATFORM_NAME "ARM application profile, 32 bit"
115#endif
116#else
117#define PRAET_PLATFORM_NAME "ARM, pre-Cortex"
118#endif
119
120/**
121 * @brief Set where the architecture defines a counter the port can read without the caller running
122 * one.
123 *
124 * @note ARMv7-M and up carry the DWT, whose cycle counter is what a port reads. ARMv6-M does not have
125 * one at all. A Cortex-M0 or M0+ has nothing here and the caller has to supply the clock.
126 * @note The application and real-time profiles have the generic timer and the performance monitors,
127 * either of which a port can read.
128 * @warning Says the architecture defines one, and never that this part implemented it. DWT_CYCCNT is
129 * optional even where the DWT is present, and reading it can be disabled. Whether it is
130 * really there is the port's to find out, and this only decides which default to take.
131 */
132#if defined(__ARM_ARCH_PROFILE) && (__ARM_ARCH_PROFILE == 'M')
133#if __ARM_ARCH >= 7
134#define PRAET_PLATFORM_HAS_CYCLE_COUNTER 1
135#else
136#define PRAET_PLATFORM_HAS_CYCLE_COUNTER 0
137#endif
138#else
139#define PRAET_PLATFORM_HAS_CYCLE_COUNTER 1
140#endif
141
142/**
143 * @brief Bits in this architecture's general purpose register.
144 *
145 * @note ACLE reports the execution state, and says nothing of the register width. On ARM those are the
146 * same question: the 64-bit state gives 64-bit registers.
147 */
148#if defined(__ARM_64BIT_STATE)
149#define PRAET_PLATFORM_XLEN 64u
150#else
151#define PRAET_PLATFORM_XLEN 32u
152#endif
153
154/**
155 * @brief Set where the architecture loads and stores at an address that is not aligned to the size.
156 *
157 * @note ACLE defines __ARM_FEATURE_UNALIGNED where the hardware does it. A Cortex-M0 and an M7 answer
158 * this differently, and both are ARM.
159 * @note Only affects what the word boundary in a recovery means. A part that traps on an unaligned
160 * word makes the boundary the hardware's granularity as well as this module's.
161 */
162#if defined(__ARM_FEATURE_UNALIGNED)
163#define PRAET_PLATFORM_UNALIGNED_LOADS 1
164#else
165#define PRAET_PLATFORM_UNALIGNED_LOADS 0
166#endif
167
168#elif PRAET_PLATFORM_RISCV
169
170/*
171 * RISC-V subfamilies, by register width and register file.
172 *
173 * __riscv_xlen is 32, 64 or 128. The embedded profiles cut the register file to sixteen and are
174 * reported by __riscv_32e, __riscv_64e, or __riscv_abi_rve where the ABI is the embedded one.
175 */
176#if defined(__riscv_32e) || defined(__riscv_64e) || defined(__riscv_abi_rve)
177
178/**
179 * @brief What to call this architecture in a diagnostic.
180 */
181#if __riscv_xlen == 32
182#define PRAET_PLATFORM_NAME "RV32E"
183#else
184#define PRAET_PLATFORM_NAME "RV64E"
185#endif
186#elif __riscv_xlen == 32
187#define PRAET_PLATFORM_NAME "RV32"
188#elif __riscv_xlen == 64
189#define PRAET_PLATFORM_NAME "RV64"
190#else
191#define PRAET_PLATFORM_NAME "RV128"
192#endif
193
194/**
195 * @brief Set where the architecture defines a counter the port can read without the caller running
196 * one.
197 *
198 * @note The cycle counter is the Zicntr CSR, which every profile carries. A port reads it with rdcycle
199 * or from mcycle where it runs in machine mode.
200 * @warning Says the architecture defines one, and never that this build can reach it. Reading cycle
201 * from a lower privilege level is gated by mcounteren, and a platform may leave it closed.
202 */
203#define PRAET_PLATFORM_HAS_CYCLE_COUNTER 1
204
205/**
206 * @brief Bits in this architecture's general purpose register.
207 *
208 * @note Straight off __riscv_xlen, which the C API defines as 32, 64 or 128 on every RISC-V target.
209 */
210#define PRAET_PLATFORM_XLEN __riscv_xlen
211
212/**
213 * @brief Set where the architecture loads and stores at an address that is not aligned to the size.
214 *
215 * @note The three __riscv_misaligned_ macros arrived in GCC 14. An older toolchain defines none of
216 * them and this reads as strict. That is the fail-closed answer: a build described as strict on
217 * a part that is not loses nothing, and the reverse is a fault at run time.
218 */
219#if defined(__riscv_misaligned_fast)
220#define PRAET_PLATFORM_UNALIGNED_LOADS 1
221#else
222#define PRAET_PLATFORM_UNALIGNED_LOADS 0
223#endif
224
225#elif PRAET_PLATFORM_XTENSA
226
227/*
228 * Xtensa subfamilies, by calling convention.
229 *
230 * GCC predefines exactly one of __XTENSA_WINDOWED_ABI__ and __XTENSA_CALL0_ABI__ for every Xtensa
231 * configuration. The register windows are the difference that shows up in anything reaching the
232 * stack, and so it is the split worth naming.
233 */
234#if defined(__XTENSA_WINDOWED_ABI__)
235
236/**
237 * @brief What to call this architecture in a diagnostic.
238 */
239#define PRAET_PLATFORM_NAME "Xtensa, windowed ABI"
240#elif defined(__XTENSA_CALL0_ABI__)
241#define PRAET_PLATFORM_NAME "Xtensa, call0 ABI"
242#else
243#define PRAET_PLATFORM_NAME "Xtensa"
244#endif
245
246/**
247 * @brief Set where the architecture defines a counter the port can read without the caller running
248 * one.
249 *
250 * @note CCOUNT, the cycle count special register. A port reads it with rsr.
251 * @warning Says the architecture defines one, and never that this core was configured with it. An
252 * Xtensa core is configurable and the timer option can be left out, the port's to
253 * find out.
254 */
255#define PRAET_PLATFORM_HAS_CYCLE_COUNTER 1
256
257/**
258 * @brief Bits in this architecture's general purpose register.
259 *
260 * @note Thirty-two on every Xtensa configuration. The register file is configurable in depth and in
261 * which options are present, and never in width.
262 */
263#define PRAET_PLATFORM_XLEN 32u
264
265/**
266 * @brief Set where the architecture loads and stores at an address that is not aligned to the size.
267 *
268 * @note Zero, the fail-closed answer and was not measured. The base ISA raises a load
269 * or store alignment exception, the unaligned option is a core configuration choice, and GCC
270 * predefines nothing that reports it. A core that does support it is described conservatively
271 * here and loses nothing by it.
272 */
273#define PRAET_PLATFORM_UNALIGNED_LOADS 0
274
275#else
276
277/**
278 * @brief What to call this architecture in a diagnostic.
279 *
280 * @note A host build. The suites run here, and a host has a clock the caller reaches through the
281 * standard library. This library has nothing to pin there.
282 */
283#define PRAET_PLATFORM_NAME "this host"
284
285/**
286 * @brief Set where the architecture defines a counter the port can read without the caller running
287 * one.
288 *
289 * @note Zero, and not because a host has no counter. A host build has no core to pin a timer to and
290 * no architectural register to read, and so the clock is the caller's - and that leaves a host
291 * build take the same arm a Cortex-M0 does, for the same reason.
292 */
293#define PRAET_PLATFORM_HAS_CYCLE_COUNTER 0
294
295/**
296 * @brief Bits in this architecture's general purpose register.
297 *
298 * @note Off the pointer's width, read from UINTPTR_MAX, which <stdint.h> gives every C11 compiler as a
299 * constant the preprocessor compares. A host is whatever the machine running the suites is, and so
300 * there is nothing else to read it from.
301 */
302#if UINTPTR_MAX > 0xFFFFFFFFu
303#define PRAET_PLATFORM_XLEN 64u
304#elif UINTPTR_MAX > 0xFFFFu
305#define PRAET_PLATFORM_XLEN 32u
306#else
307#define PRAET_PLATFORM_XLEN 16u
308#endif
309
310/**
311 * @brief Set where the architecture loads and stores at an address that is not aligned to the size.
312 */
313#define PRAET_PLATFORM_UNALIGNED_LOADS 1
314
315#endif
316
317// A word wider than the register it is carried in. The environments build this at 16 and 32 bits on a
318// 64-bit host deliberately, and so narrower is expected and only the other direction is wrong
319EMBED_STATIC_ASSERT(EMBED_WORD_BITS <= PRAET_PLATFORM_XLEN,
320 "EMBED_WORD_BITS is wider than this architecture's register");
321
322/**
323 * @brief The core a platform default pins a timer to.
324 *
325 * @note Core zero, on every family. A part with one core has it and a part with several starts at it,
326 * and so it is the number that exists everywhere. It is the right one on no part in
327 * particular. Which core is right is the caller's to say, and PRAET_CLOCK_CORE is where they
328 * say it.
329 */
330#define PRAET_PLATFORM_CLOCK_CORE 0u
331
332#endif
The embedded_types headers every module builds on.
#define PRAET_PLATFORM_XTENSA
Set where this is an Xtensa build.
#define PRAET_PLATFORM_ARM
Set where this is an ARM build, in either execution state.
#define PRAET_PLATFORM_HOST
Set where this is none of the three, which is a host build.
#define PRAET_PLATFORM_RISCV
Set where this is a RISC-V build, at any register width.
#define PRAET_PLATFORM_XLEN
Bits in this architecture's general purpose register.