Skip to content

Commit 10164cb

Browse files
authored
support armhf / other arches (#267)
1 parent 72e2c18 commit 10164cb

3 files changed

Lines changed: 211 additions & 92 deletions

File tree

include/tmc/channel.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "tmc/detail/compat.hpp"
2929
#include "tmc/detail/concepts_awaitable.hpp"
3030
#include "tmc/detail/qu_storage.hpp"
31+
#include "tmc/detail/timer_compat.hpp"
3132
#include "tmc/detail/tiny_lock.hpp"
3233
#include "tmc/ex_any.hpp"
3334
#include "tmc/task.hpp"

include/tmc/detail/compat.hpp

Lines changed: 22 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242

4343
#ifdef __has_cpp_attribute
4444

45-
#if __has_cpp_attribute(clang::coro_await_elidable_argument) && \
45+
#if __has_cpp_attribute(clang::coro_await_elidable) && \
4646
__has_cpp_attribute(clang::coro_await_elidable_argument)
4747
#define TMC_CORO_AWAIT_ELIDABLE [[clang::coro_await_elidable]]
4848
#define TMC_CORO_AWAIT_ELIDABLE_ARGUMENT [[clang::coro_await_elidable_argument]]
@@ -62,81 +62,43 @@
6262
#define TMC_SIZED_DEALLOCATION 0
6363
#endif
6464

65-
#if defined(__x86_64__) || defined(_M_AMD64) || defined(i386) || \
66-
defined(__i386__) || defined(__i386) || defined(_M_IX86)
65+
#if defined(__x86_64__) || defined(_M_AMD64) || defined(i386) || defined(__i386__) || \
66+
defined(__i386) || defined(_M_IX86)
6767
#ifdef _MSC_VER
6868
#include <intrin.h>
6969
#else
7070
#include <immintrin.h>
7171
#endif
7272
#define TMC_CPU_X86
7373
#define TMC_CPU_PAUSE _mm_pause
74-
static inline size_t TMC_CPU_TIMESTAMP() noexcept {
75-
return static_cast<size_t>(__rdtsc());
76-
}
77-
// Assume a 3.5GHz CPU if we can't get the value (on x86).
78-
// Yes, this is hacky. Getting the real RDTSC freq requires
79-
// waiting for another time source (system timer) and then dividing by
80-
// that duration. This takes real time and would have to be done on
81-
// startup. Using a 3.5GHz default means that slower processors will appear to
82-
// be running faster, and vice versa. For the current usage of this (the
83-
// clustering threshold in tmc::channel) this seems like reasonable behavior
84-
// anyway.
85-
static inline const size_t TMC_CPU_FREQ = 3500000000;
86-
#elif defined(__arm__) || defined(_M_ARM) || defined(_M_ARM64) || \
87-
defined(__aarch64__) || defined(__ARM_ACLE)
74+
#elif defined(__arm__) || defined(_M_ARM) || defined(_M_ARM64) || defined(__aarch64__)
75+
#if defined(__aarch64__) || defined(_M_ARM64) || \
76+
(defined(__ARM_ARCH_PROFILE) && __ARM_ARCH_PROFILE == 'A') || \
77+
(defined(__ARM_ARCH_PROFILE) && __ARM_ARCH_PROFILE == 'R')
8878
#define TMC_CPU_ARM
79+
#endif
80+
#ifdef _MSC_VER
81+
#include <intrin.h>
82+
#endif
8983
static inline void TMC_CPU_PAUSE() noexcept {
90-
// Clang defines __yield intrinsic, but GCC doesn't, so we use asm
84+
// Clang defines __yield intrinsic, but GCC doesn't, so we use asm where we
85+
// know the ARM yield instruction is available.
86+
#if defined(__aarch64__) || defined(_M_ARM64) || (defined(__ARM_ARCH) && __ARM_ARCH >= 7)
87+
#if defined(_MSC_VER)
88+
__yield();
89+
#else
9190
asm volatile("yield");
91+
#endif
92+
#endif
9293
}
93-
// Read the ARM "Virtual Counter" register.
94-
// This ticks at a frequency independent of the processor frequency.
95-
// https://developer.arm.com/documentation/ddi0406/cb/System-Level-Architecture/The-Generic-Timer/About-the-Generic-Timer/The-virtual-counter?lang=en
96-
static inline size_t TMC_CPU_TIMESTAMP() noexcept {
97-
size_t count;
98-
asm volatile("mrs %0, cntvct_el0; " : "=r"(count)::"memory");
99-
return count;
100-
}
101-
// Read the ARM "Virtual Counter" frequency.
102-
static inline size_t TMC_ARM_CPU_FREQ() noexcept {
103-
size_t freq;
104-
asm volatile("mrs %0, cntfrq_el0; isb; " : "=r"(freq)::"memory");
105-
return freq;
106-
}
107-
static inline const size_t TMC_CPU_FREQ = TMC_ARM_CPU_FREQ();
10894
#elif defined(__loongarch__) && defined(__LP64__)
10995
// Use some barrier instructions to generate a delay, as there's
11096
// no instruction dedicated for the delay in spinlock :(.
11197
static inline void TMC_CPU_PAUSE() noexcept {
11298
for (int i = 0; i < 32; i++)
11399
asm volatile("ibar 0");
114100
}
115-
// Read the LoongArch stable counter
116-
static inline size_t TMC_CPU_TIMESTAMP() noexcept {
117-
size_t count;
118-
asm volatile("rdtime.d %0, $zero" : "=r"(count));
119-
return count;
120-
}
121-
// Calculate the LoongArch stable counter frequency
122-
static inline size_t TMC_LOONGARCH_CPU_FREQ() noexcept {
123-
size_t cc_freq;
124-
asm ("cpucfg %0, %1" : "=r"(cc_freq) : "r"(0x4));
125-
126-
size_t cc_mul_div;
127-
asm ("cpucfg %0, %1" : "=r"(cc_mul_div) : "r"(0x5));
128-
129-
size_t cc_mul = cc_mul_div & 0xffff;
130-
size_t cc_div = (cc_mul_div >> 16) & 0xffff;
131-
132-
return cc_freq * cc_mul / cc_div;
133-
}
134-
135-
static inline const size_t TMC_CPU_FREQ = TMC_LOONGARCH_CPU_FREQ();
136101
#elif defined(__riscv)
137-
#if defined(__linux__)
138-
#include <cstdio>
139-
#endif
140102
#define TMC_CPU_RISCV
141103
static inline void TMC_CPU_PAUSE() noexcept {
142104
#if defined(__riscv_zihintpause)
@@ -145,41 +107,9 @@ static inline void TMC_CPU_PAUSE() noexcept {
145107
asm volatile("nop" ::: "memory");
146108
#endif
147109
}
148-
static inline size_t TMC_RISCV_READ_TIMEBASE_FREQ() noexcept {
149-
#if defined(__linux__)
150-
const char* paths[] = {
151-
"/proc/device-tree/cpus/timebase-frequency",
152-
"/sys/firmware/devicetree/base/cpus/timebase-frequency",
153-
};
154-
for (const char* path : paths) {
155-
auto* file = std::fopen(path, "rb");
156-
if (file == nullptr) {
157-
continue;
158-
}
159-
unsigned char bytes[8] = {};
160-
const auto size = std::fread(bytes, 1, sizeof(bytes), file);
161-
std::fclose(file);
162-
if (size == 4 || size == 8) {
163-
size_t result = 0;
164-
for (size_t i = 0; i != size; ++i) {
165-
result = (result << 8) | bytes[i];
166-
}
167-
if (result != 0) {
168-
return result;
169-
}
170-
}
171-
}
172-
#endif
173-
// This is only used for heuristics if the real RISC-V timebase is unavailable.
174-
// Use the 2GHz clock frequency of SG2042-class server chips as a default.
175-
return 2000000000;
176-
}
177-
static inline size_t TMC_CPU_TIMESTAMP() noexcept {
178-
size_t count;
179-
asm volatile("rdtime %0" : "=r"(count));
180-
return count;
181-
}
182-
static inline const size_t TMC_CPU_FREQ = TMC_RISCV_READ_TIMEBASE_FREQ();
110+
#else
111+
// Fallback for unknown architectures
112+
static inline void TMC_CPU_PAUSE() noexcept {}
183113
#endif
184114

185115
// clang-format tries to collapse the pragmas into one line...
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
// Copyright (c) 2023-2026 Logan McDougall
2+
//
3+
// Distributed under the Boost Software License, Version 1.0. (See accompanying
4+
// file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
5+
6+
#pragma once
7+
8+
// CPU timestamp-counter facilities (TMC_CPU_TIMESTAMP / TMC_CPU_FREQ) used by
9+
// tmc::channel for load-based clustering. Split out of compat.hpp because
10+
// channel.hpp is the only consumer. The architecture #if/#elif chain below
11+
// mirrors the one in compat.hpp; when adding support for a new architecture,
12+
// update both.
13+
14+
#include <cstddef>
15+
#include <cstdint> // IWYU pragma: keep
16+
17+
#if defined(__x86_64__) || defined(_M_AMD64) || defined(i386) || defined(__i386__) || \
18+
defined(__i386) || defined(_M_IX86)
19+
#ifdef _MSC_VER
20+
#include <intrin.h>
21+
#else
22+
#include <immintrin.h>
23+
#endif
24+
static inline size_t TMC_CPU_TIMESTAMP() noexcept {
25+
return static_cast<size_t>(__rdtsc());
26+
}
27+
// Assume a 3.5GHz CPU if we can't get the value (on x86).
28+
// Yes, this is hacky. Getting the real RDTSC freq requires
29+
// waiting for another time source (system timer) and then dividing by
30+
// that duration. This takes real time and would have to be done on
31+
// startup. Using a 3.5GHz default means that slower processors will appear to
32+
// be running faster, and vice versa. For the current usage of this (the
33+
// clustering threshold in tmc::channel) this seems like reasonable behavior
34+
// anyway.
35+
static inline const size_t TMC_CPU_FREQ = 3500000000;
36+
#elif defined(__arm__) || defined(_M_ARM) || defined(_M_ARM64) || defined(__aarch64__)
37+
#ifdef _MSC_VER
38+
#include <intrin.h>
39+
#endif
40+
#if defined(_MSC_VER) && defined(_M_ARM64)
41+
#define TMC_ARM64_SYSREG(op0, op1, crn, crm, op2) \
42+
(((op0 & 1) << 14) | ((op1 & 7) << 11) | ((crn & 15) << 7) | ((crm & 15) << 3) | \
43+
((op2 & 7) << 0))
44+
static inline size_t TMC_CPU_TIMESTAMP() noexcept {
45+
return static_cast<size_t>(_ReadStatusReg(TMC_ARM64_SYSREG(3, 3, 14, 0, 2)));
46+
}
47+
static inline size_t TMC_ARM_CPU_FREQ() noexcept {
48+
return static_cast<size_t>(_ReadStatusReg(TMC_ARM64_SYSREG(3, 3, 14, 0, 0)));
49+
}
50+
#undef TMC_ARM64_SYSREG
51+
#elif defined(_MSC_VER) && defined(_M_ARM)
52+
static inline size_t TMC_CPU_TIMESTAMP() noexcept {
53+
return static_cast<size_t>(_MoveFromCoprocessor64(15, 1, 14));
54+
}
55+
static inline size_t TMC_ARM_CPU_FREQ() noexcept {
56+
return static_cast<size_t>(_MoveFromCoprocessor(15, 0, 14, 0, 0));
57+
}
58+
#elif defined(__aarch64__) || defined(_M_ARM64)
59+
// AArch64: the generic timer is read through dedicated system registers.
60+
// Read the ARM "Virtual Counter" register.
61+
// This ticks at a frequency independent of the processor frequency.
62+
// https://developer.arm.com/documentation/ddi0406/cb/System-Level-Architecture/The-Generic-Timer/About-the-Generic-Timer/The-virtual-counter?lang=en
63+
static inline size_t TMC_CPU_TIMESTAMP() noexcept {
64+
size_t count;
65+
asm volatile("mrs %0, cntvct_el0; " : "=r"(count)::"memory");
66+
return count;
67+
}
68+
// Read the ARM "Virtual Counter" frequency.
69+
static inline size_t TMC_ARM_CPU_FREQ() noexcept {
70+
size_t freq;
71+
asm volatile("mrs %0, cntfrq_el0; isb; " : "=r"(freq)::"memory");
72+
return freq;
73+
}
74+
#elif defined(__ARM_ARCH) && __ARM_ARCH >= 7 && defined(__ARM_ARCH_PROFILE) && \
75+
(__ARM_ARCH_PROFILE == 'A' || __ARM_ARCH_PROFILE == 'R')
76+
#if defined(__linux__)
77+
#include <asm/hwcap.h>
78+
#include <sys/auxv.h>
79+
#endif
80+
// AArch32 (e.g. armhf): the AArch64 `cntvct_el0` / `cntfrq_el0` system
81+
// registers don't exist here. The ARMv7-A Generic Timer exposes the same
82+
// counters through CP15 coprocessor accesses instead.
83+
// https://developer.arm.com/documentation/ddi0406/cb/System-Level-Architecture/The-Generic-Timer/Register-descriptions?lang=en
84+
// The Generic Timer is optional on ARMv7-A, and user-mode access is controlled
85+
// by the OS. On Linux, HWCAP_EVTSTRM indicates that the kernel configured the
86+
// architected timer event stream; if that is absent, fall back to inert timing
87+
// rather than trapping on an undefined/privileged CP15 access.
88+
static inline bool TMC_ARM_HAS_GENERIC_TIMER_IMPL() noexcept {
89+
#if defined(__linux__) && defined(HWCAP_EVTSTRM)
90+
return (getauxval(AT_HWCAP) & HWCAP_EVTSTRM) != 0;
91+
#else
92+
return false;
93+
#endif
94+
}
95+
static inline const bool TMC_ARM_HAS_GENERIC_TIMER = TMC_ARM_HAS_GENERIC_TIMER_IMPL();
96+
// Read the 64-bit "Virtual Counter" (CNTVCT) via MRRC into a register pair.
97+
// size_t is 32-bit here, so we return only the low word - matching the 32-bit
98+
// x86 path, which likewise truncates __rdtsc(). The counter ticks at a
99+
// frequency independent of the processor frequency.
100+
static inline size_t TMC_CPU_TIMESTAMP() noexcept {
101+
if (!TMC_ARM_HAS_GENERIC_TIMER) {
102+
return 0;
103+
}
104+
uint32_t lo, hi;
105+
asm volatile("mrrc p15, 1, %0, %1, c14" : "=r"(lo), "=r"(hi)::"memory");
106+
(void)hi;
107+
return lo;
108+
}
109+
// Read the "Virtual Counter" frequency (CNTFRQ, a 32-bit register) via MRC.
110+
static inline size_t TMC_ARM_CPU_FREQ() noexcept {
111+
if (!TMC_ARM_HAS_GENERIC_TIMER) {
112+
return 1000000000;
113+
}
114+
uint32_t freq;
115+
asm volatile("mrc p15, 0, %0, c14, c0, 0" : "=r"(freq)::"memory");
116+
return freq;
117+
}
118+
#else
119+
static inline size_t TMC_CPU_TIMESTAMP() noexcept { return 0; }
120+
static inline size_t TMC_ARM_CPU_FREQ() noexcept { return 1000000000; }
121+
#endif
122+
static inline const size_t TMC_CPU_FREQ = TMC_ARM_CPU_FREQ();
123+
#elif defined(__loongarch__) && defined(__LP64__)
124+
// Read the LoongArch stable counter
125+
static inline size_t TMC_CPU_TIMESTAMP() noexcept {
126+
size_t count;
127+
asm volatile("rdtime.d %0, $zero" : "=r"(count));
128+
return count;
129+
}
130+
// Calculate the LoongArch stable counter frequency
131+
static inline size_t TMC_LOONGARCH_CPU_FREQ() noexcept {
132+
size_t cc_freq;
133+
asm("cpucfg %0, %1" : "=r"(cc_freq) : "r"(0x4));
134+
135+
size_t cc_mul_div;
136+
asm("cpucfg %0, %1" : "=r"(cc_mul_div) : "r"(0x5));
137+
138+
size_t cc_mul = cc_mul_div & 0xffff;
139+
size_t cc_div = (cc_mul_div >> 16) & 0xffff;
140+
141+
return cc_freq * cc_mul / cc_div;
142+
}
143+
144+
static inline const size_t TMC_CPU_FREQ = TMC_LOONGARCH_CPU_FREQ();
145+
#elif defined(__riscv)
146+
#if defined(__linux__)
147+
#include <cstdio>
148+
#endif
149+
static inline size_t TMC_RISCV_READ_TIMEBASE_FREQ() noexcept {
150+
#if defined(__linux__)
151+
const char* paths[] = {
152+
"/proc/device-tree/cpus/timebase-frequency",
153+
"/sys/firmware/devicetree/base/cpus/timebase-frequency",
154+
};
155+
for (const char* path : paths) {
156+
auto* file = std::fopen(path, "rb");
157+
if (file == nullptr) {
158+
continue;
159+
}
160+
unsigned char bytes[8] = {};
161+
const auto size = std::fread(bytes, 1, sizeof(bytes), file);
162+
std::fclose(file);
163+
if (size == 4 || size == 8) {
164+
size_t result = 0;
165+
for (size_t i = 0; i != size; ++i) {
166+
result = (result << 8) | bytes[i];
167+
}
168+
if (result != 0) {
169+
return result;
170+
}
171+
}
172+
}
173+
#endif
174+
// This is only used for heuristics if the real RISC-V timebase is unavailable.
175+
// Use the 2GHz clock frequency of SG2042-class server chips as a default.
176+
return 2000000000;
177+
}
178+
static inline size_t TMC_CPU_TIMESTAMP() noexcept {
179+
size_t count;
180+
asm volatile("rdtime %0" : "=r"(count));
181+
return count;
182+
}
183+
static inline const size_t TMC_CPU_FREQ = TMC_RISCV_READ_TIMEBASE_FREQ();
184+
#else
185+
// Fallback for unknown architectures
186+
static inline size_t TMC_CPU_TIMESTAMP() noexcept { return 0; }
187+
static inline const size_t TMC_CPU_FREQ = 1000000000;
188+
#endif

0 commit comments

Comments
 (0)