commit b4bfeaba9214d299c450544f77517aa6a19a59f3 parent 5e69b646150a00a9b2db85dc89f5fdc5eacd1f3e Author: Ryan Sepassi <rsepassi@gmail.com> Date: Sun, 14 Jun 2026 12:43:19 -0700 feat(c): add __builtin_readcyclecounter Lower the GCC/Clang builtin to the native cycle/timestamp counter on every arch via a new KIT_CG_INTRIN_READCYCLECOUNTER intrinsic (returns u64): aarch64 MRS Xd, CNTVCT_EL0 (EL0-readable; not PMCCNTR which traps) x86-64 RDTSC, recombined from edx:eax (rax/rdx modeled as clobbers) riscv64 inline RDCYCLE (csrrs rd, cycle, x0) riscv32 __kit_readcyclecounter rt helper: reads the cycle/cycleh CSR pair with a re-read loop (the 64-bit result is a register pair the single-register intrinsic path can't carry, so it routes to libkit_rt like wide-64 clz/ctz) wasm reported unsupported (no architectural counter) Wired end to end: C frontend recognition (parse + cg_adapter) -> CG intrinsic dispatch -> per-arch native emit + c_target forward. Also adds an x64 RDTSC ISA-table row so `cc -S` and `cc -S | as` round-trip, the rv32 cycle/cycleh/timeh/instreth CSR names, and an RT_ARCH_SRCS build slot for the rv32 helper. Tests: parse case builtin_31_readcyclecounter (passes D/R/E/J on all arches incl. rv32 exec under qemu-system at -O0/-O1) and x64 rdtsc encode/decode asm corpus. Diffstat:
32 files changed, 173 insertions(+), 5 deletions(-)
diff --git a/include/kit/cg.h b/include/kit/cg.h @@ -1075,6 +1075,12 @@ typedef enum KitCgIntrinsic { * pointer (wasm) report unsupported. */ KIT_CG_INTRIN_FRAME_ADDRESS, /* pop level(u32 const); push void* */ KIT_CG_INTRIN_RETURN_ADDRESS, /* pop level(u32 const); push void* */ + /* Read the target cycle/timestamp counter (GCC/Clang + * __builtin_readcyclecounter). No operands; pushes an unsigned 64-bit value. + * Lowered to the native counter read (x86 RDTSC, aarch64 MRS CNTVCT_EL0, + * riscv64 RDCYCLE). Targets without a single-register 64-bit counter read + * report unsupported. */ + KIT_CG_INTRIN_READCYCLECOUNTER, /* push u64 */ } KitCgIntrinsic; typedef enum KitCgBarrierScope { diff --git a/lang/c/parse/cg_adapter.c b/lang/c/parse/cg_adapter.c @@ -1290,6 +1290,15 @@ void pcg_syscall(Parser* p, u32 nargs, const Type* long_ty) { pcg_push_type(p, long_ty); } +/* __builtin_readcyclecounter(): read the target cycle/timestamp counter. No + * operands; pushes an unsigned long long result. */ +void pcg_readcyclecounter(Parser* p) { + const Type* ull = type_prim(p->pool, TY_ULLONG); + if (pcg_emit_enabled(p)) + kit_cg_intrinsic(p->cg, KIT_CG_INTRIN_READCYCLECOUNTER, 0, pcg_tid(p, ull)); + pcg_push_type(p, ull); +} + /* __builtin_return_address(level) / __builtin_frame_address(level): emit the * frame-pointer-chain intrinsic. The constant level rides as a single immediate * operand (kept as OPK_IMM by kit_cg_intrinsic); the result is void*. */ diff --git a/lang/c/parse/cg_adapter.h b/lang/c/parse/cg_adapter.h @@ -373,6 +373,7 @@ void pcg_intrinsic_unary_to_int(Parser*, IntrinKind); void pcg_intrinsic_void(Parser*, IntrinKind); void pcg_syscall(Parser*, u32 nargs, const Type* long_ty); void pcg_frame_or_return_address(Parser*, int is_return, u32 level); +void pcg_readcyclecounter(Parser*); void pcg_inline_asm(Parser*, const char*, const AsmConstraint*, u32, const AsmConstraint*, u32, const Sym*, u32); diff --git a/lang/c/parse/parse.c b/lang/c/parse/parse.c @@ -1675,6 +1675,8 @@ void parse_c(Compiler* c, Pool* pool, Pp* pp, DeclTable* decls, CG* cg, kit_sym_intern(p.pool->c, KIT_SLICE_LIT("__builtin_return_address")); p.sym_b_frame_address = kit_sym_intern(p.pool->c, KIT_SLICE_LIT("__builtin_frame_address")); + p.sym_b_readcyclecounter = + kit_sym_intern(p.pool->c, KIT_SLICE_LIT("__builtin_readcyclecounter")); for (syscall_i = 0; syscall_i < 7u; ++syscall_i) { char name[16]; memcpy(name, "__kit_syscall", 13u); diff --git a/lang/c/parse/parse_expr.c b/lang/c/parse/parse_expr.c @@ -1797,7 +1797,8 @@ static int try_parse_builtin_call(Parser* p) { name != p->sym_b_clzl && name != p->sym_b_clzll && name != p->sym_b_trap && name != p->sym_b_unreachable && name != p->sym_b_return_address && name != p->sym_b_frame_address && - name != p->sym_b_expect && name != p->sym_b_offsetof && + name != p->sym_b_readcyclecounter && name != p->sym_b_expect && + name != p->sym_b_offsetof && name != p->sym_b_va_start && name != p->sym_b_va_arg && name != p->sym_b_va_end && name != p->sym_b_va_copy && name != p->sym_a_load_n && name != p->sym_a_store_n && @@ -1890,6 +1891,13 @@ static int try_parse_builtin_call(Parser* p) { return 1; } + if (name == p->sym_b_readcyclecounter) { + expect_punct(p, ')', "')' after __builtin_readcyclecounter"); + pcg_set_loc(p, loc); + pcg_readcyclecounter(p); + return 1; + } + if (name == p->sym_b_va_start) { parse_assign_expr(p); pcg_addr(p); diff --git a/lang/c/parse/parse_priv.h b/lang/c/parse/parse_priv.h @@ -323,8 +323,9 @@ typedef struct Parser { Sym sym_b_va_arg; Sym sym_b_va_end; Sym sym_b_va_copy; - Sym sym_b_return_address; /* __builtin_return_address */ - Sym sym_b_frame_address; /* __builtin_frame_address */ + Sym sym_b_return_address; /* __builtin_return_address */ + Sym sym_b_frame_address; /* __builtin_frame_address */ + Sym sym_b_readcyclecounter; /* __builtin_readcyclecounter */ Sym sym_kit_syscall[7]; /* __kit_syscall0 .. __kit_syscall6 */ Sym sym_attribute; Sym sym_volatile_alias; diff --git a/mk/rt.mk b/mk/rt.mk @@ -261,6 +261,11 @@ RT_CORO_SRCS_arm32_thumb1 = rt/lib/coro/arm32_thumb1.c rt/lib/coro/coro.c RT_CORO_SRCS_riscv32 = rt/lib/coro/riscv32.c rt/lib/coro/coro.c RT_CORO_SRCS_riscv64 = rt/lib/coro/riscv64.c rt/lib/coro/coro.c +# Extra arch-specific runtime helpers, keyed on the same micro-arch token as +# RT_CORO_SRCS. rv32 needs __kit_readcyclecounter to read the 64-bit cycle +# counter across the cycle/cycleh CSR pair (rv64 reads it inline, no helper). +RT_ARCH_SRCS_riscv32 = rt/lib/riscv/readcyclecounter.S + RT_LDBL128_SRCS = rt/lib/fp_tf/fp_tf.c rt/lib/fp_ti/fp_ti.c RT_LDBL128_FLAGS = -Irt/lib/include/lp64_le_ldbl128 -DKITRT_LDBL128=1 @@ -282,6 +287,7 @@ RT_SRCS_$(1) := \ $$(if $$(RT_$(1)_HOSTED),$$(RT_COMPILER_SRCS),$$(RT_BASE_SRCS)) \ $$(RT_ABI_SRCS_$$(RT_$(1)_ABI)) \ $$(RT_CORO_SRCS_$$(RT_$(1)_CORO)) \ + $$(RT_ARCH_SRCS_$$(RT_$(1)_CORO)) \ $$(if $$(RT_$(1)_LDBL128),$$(RT_LDBL128_SRCS)) \ $$(if $$(RT_$(1)_SAVE_RESTORE),$$(RT_SAVE_RESTORE_SRCS_$$(RT_$(1)_ABI))) \ $$(RT_AEABI_SRCS_$$(RT_$(1)_AEABI)) \ diff --git a/rt/lib/riscv/readcyclecounter.S b/rt/lib/riscv/readcyclecounter.S @@ -0,0 +1,23 @@ +// RISC-V rv32 cycle-counter read for kit's libkit_rt.a. +// License: Apache-2.0 WITH LLVM-exception (see lib/LICENSE-compiler-rt.txt). +// +// unsigned long long __kit_readcyclecounter(void); +// +// The 64-bit cycle counter is split across two CSRs on rv32: `cycle` (low 32) +// and `cycleh` (high 32). A naive hi/lo read races when the low half wraps +// between the two reads, so re-read the high half and retry if it changed. +// Returns the 64-bit value in the ilp32 pair a0 (low) : a1 (high). Backs the +// frontend's __builtin_readcyclecounter() on rv32 (rv64 reads it inline with a +// single RDCYCLE). See src/cg/arith.c (kit_cg_intrinsic). + + .text + .globl __kit_readcyclecounter + .type __kit_readcyclecounter,@function +__kit_readcyclecounter: +.Lretry: + csrrs a1, cycleh, zero // a1 = high + csrrs a0, cycle, zero // a0 = low + csrrs a2, cycleh, zero // a2 = high (re-read) + bne a1, a2, .Lretry // retry if the high half advanced + ret + .size __kit_readcyclecounter, .-__kit_readcyclecounter diff --git a/src/arch/aa64/arch.c b/src/arch/aa64/arch.c @@ -197,6 +197,7 @@ static int aa64_supports_intrinsic(const Compiler* c, KitCgIntrinsic intrin) { case KIT_CG_INTRIN_IRQ_DISABLE: case KIT_CG_INTRIN_FRAME_ADDRESS: case KIT_CG_INTRIN_RETURN_ADDRESS: + case KIT_CG_INTRIN_READCYCLECOUNTER: /* MRS CNTVCT_EL0 */ return 1; case KIT_CG_INTRIN_SYSCALL: return c->target.os == KIT_OS_LINUX || diff --git a/src/arch/aa64/native.c b/src/arch/aa64/native.c @@ -3969,6 +3969,13 @@ static void aa_intrinsic(NativeTarget* t, IntrinKind kind, aa_move(t, dsts[0], native_loc_reg(dsts[0].type, NATIVE_REG_INT, 0)); } return; + case INTRIN_READCYCLECOUNTER: + /* MRS Xd, CNTVCT_EL0 (op0=3 op1=3 CRn=14 CRm=0 op2=2): the EL0-readable + * virtual counter. Prefer it over PMCCNTR_EL0, which traps at EL0 unless + * the PMU is explicitly enabled for userspace. */ + if (ndst == 1u) + aa_emit32(t->mc, aa64_sysreg_move(1, 3, 3, 14, 0, 2, loc_reg(dsts[0]))); + return; case INTRIN_CPU_NOP: aa_emit32(t->mc, aa64_hint(AA64_HINT_OP_NOP)); return; diff --git a/src/arch/c_target/c_emit.c b/src/arch/c_target/c_emit.c @@ -2653,6 +2653,19 @@ void c_emit_intrinsic(CTarget* t, IntrinKind k, Operand* dsts, u32 ndst, c_emit_local_assign_close(t); return; } + case INTRIN_READCYCLECOUNTER: { + /* Forward to the host compiler's builtin. dsts[0] is the u64 result. */ + if (ndst != 1) { + compiler_panic(t->c, loc, + "C target: readcyclecounter: expected 1 dst, got %u", + (unsigned)ndst); + } + c_ensure_local(t, dsts[0].v.local, dsts[0].type); + c_emit_local_assign_open(t, dsts[0].v.local, (KitCgTypeId)0); + cbuf_puts(&t->body, "__builtin_readcyclecounter()"); + c_emit_local_assign_close(t); + return; + } case INTRIN_SYSCALL: compiler_panic(t->c, loc, "C target: syscall intrinsic not supported"); return; diff --git a/src/arch/riscv/arch.c b/src/arch/riscv/arch.c @@ -371,6 +371,12 @@ static int rv64_supports_intrinsic(const Compiler* c, KitCgIntrinsic intrin) { case KIT_CG_INTRIN_FRAME_ADDRESS: case KIT_CG_INTRIN_RETURN_ADDRESS: return 1; + case KIT_CG_INTRIN_READCYCLECOUNTER: + /* rv64 reads the 64-bit `cycle` CSR inline with a single RDCYCLE. rv32's + * result spans the cycle/cycleh CSR pair (a register pair the single- + * register intrinsic path can't carry), so it routes to the + * __kit_readcyclecounter libkit_rt helper instead (src/cg/arith.c). */ + return c->target.arch == KIT_ARCH_RV64 || c->target.arch == KIT_ARCH_RV32; case KIT_CG_INTRIN_SYSCALL: return c->target.os == KIT_OS_LINUX || c->target.os == KIT_OS_FREESTANDING; diff --git a/src/arch/riscv/isa.c b/src/arch/riscv/isa.c @@ -1371,6 +1371,7 @@ const u32 rv64_insn_table_n = const Rv64CsrName rv64_csr_names[] = { {"fflags", 0x001}, {"frm", 0x002}, {"fcsr", 0x003}, {"cycle", 0xC00}, {"time", 0xC01}, {"instret", 0xC02}, + {"cycleh", 0xC80}, {"timeh", 0xC81}, {"instreth", 0xC82}, {"mstatus", 0x300}, {"misa", 0x301}, {"mie", 0x304}, {"mtvec", 0x305}, {"mscratch", 0x340}, {"mepc", 0x341}, {"mcause", 0x342}, {"mtval", 0x343}, {"mip", 0x344}, diff --git a/src/arch/riscv/isa.h b/src/arch/riscv/isa.h @@ -562,6 +562,12 @@ static inline u32 rv_sc_d(u32 rd, u32 rs1, u32 rs2, u32 aq, u32 rl) { #define RV_AMO_MINU 0x18u #define RV_AMO_MAXU 0x1Cu +/* User-mode read-only performance CSRs (Zicntr). RDCYCLE rd is the canonical + * `csrrs rd, cycle, x0`. The high halves (0xC80+) are rv32-only. */ +#define RV_CSR_CYCLE 0xC00u +#define RV_CSR_TIME 0xC01u +#define RV_CSR_INSTRET 0xC02u + /* Zicsr — CSR instructions. csr in imm[11:0]; funct3 selects op. * csrrw=1, csrrs=2, csrrc=3, csrrwi=5, csrrsi=6, csrrci=7 */ static inline u32 rv_csrrw(u32 rd, u32 csr, u32 rs1) { diff --git a/src/arch/riscv/native.c b/src/arch/riscv/native.c @@ -3219,6 +3219,11 @@ static void rv_intrinsic(NativeTarget* t, IntrinKind kind, case INTRIN_TRAP: rv64_emit32(mc, rv_ebreak()); return; + case INTRIN_READCYCLECOUNTER: + /* RDCYCLE rd = csrrs rd, cycle, x0 — reads the 64-bit cycle CSR on rv64 + * (rv32 is gated out in rv64_supports_intrinsic). */ + rv64_emit32(mc, rv_csrrs(loc_reg(dsts[0]), RV_CSR_CYCLE, RV_ZERO)); + return; case INTRIN_SYSCALL: if (ndst == 1u && narg >= 1u && narg <= 7u) { static const u32 syscall_regs[7] = {RV_A7, RV_A0, RV_A1, RV_A2, diff --git a/src/arch/wasm/arch.c b/src/arch/wasm/arch.c @@ -134,6 +134,8 @@ static int wasm_supports_intrinsic(const Compiler* c, KitCgIntrinsic intrin) { /* wasm has no frame-pointer chain to walk. */ case KIT_CG_INTRIN_FRAME_ADDRESS: case KIT_CG_INTRIN_RETURN_ADDRESS: + /* wasm has no architectural cycle-counter instruction. */ + case KIT_CG_INTRIN_READCYCLECOUNTER: return 0; } return 0; diff --git a/src/arch/wasm/emit.c b/src/arch/wasm/emit.c @@ -1627,6 +1627,8 @@ static const char* intrin_name(IntrinKind k) { return "frame_address"; case INTRIN_RETURN_ADDRESS: return "return_address"; + case INTRIN_READCYCLECOUNTER: + return "readcyclecounter"; } return "<unknown>"; } @@ -1775,6 +1777,7 @@ void wasm_intrinsic(CGTarget* tg, IntrinKind k, Operand* dst, u32 ndst, case INTRIN_IRQ_ENABLE: case INTRIN_IRQ_DISABLE: case INTRIN_SYSCALL: + case INTRIN_READCYCLECOUNTER: /* No frame-pointer chain in wasm; reported unsupported up front. */ case INTRIN_FRAME_ADDRESS: case INTRIN_RETURN_ADDRESS: diff --git a/src/arch/x64/arch.c b/src/arch/x64/arch.c @@ -161,6 +161,7 @@ static int x64_supports_intrinsic(const Compiler* c, KitCgIntrinsic intrin) { case KIT_CG_INTRIN_IRQ_DISABLE: case KIT_CG_INTRIN_FRAME_ADDRESS: case KIT_CG_INTRIN_RETURN_ADDRESS: + case KIT_CG_INTRIN_READCYCLECOUNTER: /* RDTSC */ return 1; case KIT_CG_INTRIN_SYSCALL: return c->target.os == KIT_OS_LINUX || diff --git a/src/arch/x64/isa.c b/src/arch/x64/isa.c @@ -50,6 +50,9 @@ const X64InsnDesc x64_insn_table[] = { /* ---- SYSCALL (0F 05): fast system call ---- */ ROW("syscall", X64_PFX_NONE, 2, 0x0F, 0x05, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, X64_FMT_NULLARY, 0), + /* ---- RDTSC (0F 31): read timestamp counter into edx:eax ---- */ + ROW("rdtsc", X64_PFX_NONE, 2, 0x0F, 0x31, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, + X64_FMT_NULLARY, 0), ROW("mfence", X64_PFX_NONE, 3, 0x0F, 0xAE, 0xF0, 0xFF, NO_MODRM, X64_W_REQ_ANY, X64_FMT_NULLARY, 0), diff --git a/src/arch/x64/native.c b/src/arch/x64/native.c @@ -3534,6 +3534,11 @@ static void emit_syscall(MCEmitter* mc) { mc_emit_bytes(mc, b, 2); } +static void emit_rdtsc(MCEmitter* mc) { + u8 b[2] = {0x0F, 0x31}; + mc_emit_bytes(mc, b, 2); +} + static void x64_intrinsic(NativeTarget* t, IntrinKind kind, const NativeLoc* dsts, u32 ndst, const NativeLoc* args, u32 narg) { @@ -3556,6 +3561,18 @@ static void x64_intrinsic(NativeTarget* t, IntrinKind kind, case INTRIN_TRAP: emit_ud2(mc); return; + case INTRIN_READCYCLECOUNTER: { + /* RDTSC returns the 64-bit timestamp counter split across edx:eax + * (writing eax/edx zero-extends the upper halves of rax/rdx). Recombine + * into a single 64-bit value: dst = (rdx << 32) | rax. The rax/rdx + * clobber is modeled in x64_machine_op_clobbers. */ + u32 rd = loc_reg(dsts[0]); + emit_rdtsc(mc); + emit_shift_imm(mc, 1, X64_SHIFT_SUB_SHL, X64_RDX, 32); + emit_alu_rr(mc, 1, X64_OPC_ALU_OR, X64_RAX, X64_RDX); + if (rd != X64_RAX) emit_mov_rr(mc, 1, rd, X64_RAX); + return; + } case INTRIN_SYSCALL: if (ndst == 1u && narg >= 1u && narg <= 7u) { static const u32 syscall_regs[7] = {X64_RAX, X64_RDI, X64_RSI, X64_RDX, @@ -4292,7 +4309,10 @@ static int x64_machine_op_clobbers(NativeTarget* t, const NativeMachineOp* op, * two-operand IMUL (no fixed-register clobber). Linux syscall writes rax * and the CPU instruction itself clobbers rcx/r11; the kernel ABI treats * the integer caller-saved syscall registers as volatile. */ - if ((IntrinKind)op->intrin == INTRIN_UMUL_OVERFLOW) { + if ((IntrinKind)op->intrin == INTRIN_UMUL_OVERFLOW || + (IntrinKind)op->intrin == INTRIN_READCYCLECOUNTER) { + /* MUL's rdx:rax product / RDTSC's edx:eax counter both write both + * registers; keep live values out of them across the op. */ mask[NATIVE_REG_INT] = (1u << X64_RAX) | (1u << X64_RDX); return 1; } diff --git a/src/cg/arith.c b/src/cg/arith.c @@ -1874,10 +1874,12 @@ static const IntrinDesc kIntrinTable[] = { false, false}, [KIT_CG_INTRIN_RETURN_ADDRESS] = {INTRIN_RETURN_ADDRESS, "return_address", false, false}, + [KIT_CG_INTRIN_READCYCLECOUNTER] = {INTRIN_READCYCLECOUNTER, + "readcyclecounter", false, false}, }; _Static_assert(sizeof(kIntrinTable) / sizeof(kIntrinTable[0]) == - KIT_CG_INTRIN_RETURN_ADDRESS + 1, + KIT_CG_INTRIN_READCYCLECOUNTER + 1, "kIntrinTable must have exactly one row per KitCgIntrinsic"); /* Bounds-guarded row lookup: an out-of-range intrinsic falls back to the NONE @@ -1924,6 +1926,16 @@ void kit_cg_intrinsic(KitCg* g, KitCgIntrinsic intrin, uint32_t nargs, u32 ndst = 0; Heap* h; if (!g) return; + /* readcyclecounter returns a 64-bit value. On rv32 that is a register pair + * the single-register native intrinsic path can't carry, and the read spans + * the cycle/cycleh CSRs with a re-read loop, so route to the libkit_rt helper + * (mirrors the wide-64 clz/ctz routing below). rv64 reads it inline. */ + if (intrin == KIT_CG_INTRIN_READCYCLECOUNTER && + g->c->target.arch == KIT_ARCH_RV32) { + api_runtime_call_values(g, "__kit_readcyclecounter", + builtin_id(KIT_CG_BUILTIN_I64), NULL, 0, NULL); + return; + } /* clz/ctz/popcount/bswap on a split 64-bit value cannot use the backend's * single-register software sequence. Route them to the compiler-rt __*di2 * helpers, which decompose into 32-bit operations. (32-bit forms still lower diff --git a/src/cg/cgir.h b/src/cg/cgir.h @@ -193,6 +193,11 @@ typedef enum IntrinKind { * side-effecting in opt, so it is never hoisted, CSE'd, or eliminated). */ INTRIN_FRAME_ADDRESS, INTRIN_RETURN_ADDRESS, + + /* Read the target cycle/timestamp counter. No operands; dsts[0] receives the + * unsigned 64-bit result. Modeled like a frame-dependent read (IR_INTRINSIC + * is conservatively side-effecting, so it is never hoisted/CSE'd/removed). */ + INTRIN_READCYCLECOUNTER, } IntrinKind; typedef enum OpKind { diff --git a/test/asm/decode/x64_rdtsc.expected.txt b/test/asm/decode/x64_rdtsc.expected.txt @@ -0,0 +1 @@ +0: rdtsc diff --git a/test/asm/decode/x64_rdtsc.hex b/test/asm/decode/x64_rdtsc.hex @@ -0,0 +1 @@ +0f31 diff --git a/test/asm/decode/x64_rdtsc.targets b/test/asm/decode/x64_rdtsc.targets @@ -0,0 +1 @@ +x64 diff --git a/test/asm/encode/x64_rdtsc.expected.hex b/test/asm/encode/x64_rdtsc.expected.hex @@ -0,0 +1 @@ +0f31c3 diff --git a/test/asm/encode/x64_rdtsc.s b/test/asm/encode/x64_rdtsc.s @@ -0,0 +1,6 @@ +// x64: RDTSC (0F 31) reads the timestamp counter into edx:eax. +.text +.globl rdtsc_only +rdtsc_only: + rdtsc + ret diff --git a/test/asm/encode/x64_rdtsc.targets b/test/asm/encode/x64_rdtsc.targets @@ -0,0 +1 @@ +x64 diff --git a/test/parse/cases/builtin_31_readcyclecounter.c b/test/parse/cases/builtin_31_readcyclecounter.c @@ -0,0 +1,13 @@ +/* __builtin_readcyclecounter(): reads the target cycle/timestamp counter as an + * unsigned long long (x86 RDTSC, aarch64 CNTVCT_EL0, riscv64 RDCYCLE). A free- + * running counter is monotonic, so re-reading it eventually yields a larger + * value. Spin-read until it advances, bounded so a broken/constant read fails + * the oracle instead of looping forever. */ +typedef unsigned long long u64; + +int test_main(void) { + u64 a = __builtin_readcyclecounter(); + u64 b = a; + for (int i = 0; i < 100000000 && b == a; i++) b = __builtin_readcyclecounter(); + return b > a ? 42 : 1; +} diff --git a/test/parse/cases/builtin_31_readcyclecounter.cbackend.skip b/test/parse/cases/builtin_31_readcyclecounter.cbackend.skip @@ -0,0 +1 @@ +emitted C forwards to __builtin_readcyclecounter, a clang-only builtin not portable to a gcc host cc diff --git a/test/parse/cases/builtin_31_readcyclecounter.expected b/test/parse/cases/builtin_31_readcyclecounter.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases/builtin_31_readcyclecounter.wasm.skip b/test/parse/cases/builtin_31_readcyclecounter.wasm.skip @@ -0,0 +1 @@ +wasm has no architectural cycle counter (__builtin_readcyclecounter unsupported)