v16-0002-Allow-retrieving-x86-TSC-frequency-flags-from-CP.patch

application/octet-stream

Filename: v16-0002-Allow-retrieving-x86-TSC-frequency-flags-from-CP.patch
Type: application/octet-stream
Part: 0
Message: Re: Reduce timing overhead of EXPLAIN ANALYZE using rdtsc?

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch v16-0002
Subject: Allow retrieving x86 TSC frequency/flags from CPUID
File+
src/include/port/pg_cpu.h 8 0
src/port/pg_cpu_x86.c 121 3
From a9d6b939681445c855de73e6aea50ba9a2acbbc3 Mon Sep 17 00:00:00 2001
From: Lukas Fittl <lukas@fittl.com>
Date: Wed, 11 Mar 2026 00:55:03 -0700
Subject: [PATCH v16 2/5] Allow retrieving x86 TSC frequency/flags from CPUID

This adds additional x86 specific CPUID checks for flags needed for
determining whether the Time-Stamp Counter (TSC) is usable on a given
system, as well as a helper function to retrieve the TSC frequency from
CPUID.

This is intended for a future patch that will utilize the TSC to lower
the overhead of timing instrumentation.

Author: Lukas Fittl <lukas@fittl.com>
Author: David Geier <geidav.pg@gmail.com>
Author: Andres Freund <andres@anarazel.de>
Reviewed-by: Andres Freund <andres@anarazel.de>
Reviewed-by: David Geier <geidav.pg@gmail.com>
Reviewed-by: John Naylor <john.naylor@postgresql.org>
Reviewed-by: Jakub Wartak <jakub.wartak@enterprisedb.com> (in an earlier version)
Discussion: https://www.postgresql.org/message-id/flat/20200612232810.f46nbqkdhbutzqdg%40alap3.anarazel.de
---
 src/include/port/pg_cpu.h |   8 +++
 src/port/pg_cpu_x86.c     | 124 +++++++++++++++++++++++++++++++++++++-
 2 files changed, 129 insertions(+), 3 deletions(-)

diff --git a/src/include/port/pg_cpu.h b/src/include/port/pg_cpu.h
index b93b828d3ac..0e1fea7fa92 100644
--- a/src/include/port/pg_cpu.h
+++ b/src/include/port/pg_cpu.h
@@ -23,6 +23,12 @@ typedef enum X86FeatureId
 	/* scalar registers and 128-bit XMM registers */
 	PG_SSE4_2,
 	PG_POPCNT,
+	PG_HYPERVISOR,
+
+	/* TSC flags */
+	PG_RDTSCP,
+	PG_TSC_INVARIANT,
+	PG_TSC_ADJUST,
 
 	/* 512-bit ZMM registers */
 	PG_AVX512_BW,
@@ -45,6 +51,8 @@ x86_feature_available(X86FeatureId feature)
 	return X86Features[feature];
 }
 
+extern uint32 x86_tsc_frequency_khz(void);
+
 #endif							/* defined(USE_SSE2) || defined(__i386__) */
 
 #endif							/* PG_CPU_H */
diff --git a/src/port/pg_cpu_x86.c b/src/port/pg_cpu_x86.c
index 1331f3f4eb8..71a07b55bf3 100644
--- a/src/port/pg_cpu_x86.c
+++ b/src/port/pg_cpu_x86.c
@@ -101,19 +101,24 @@ void
 set_x86_features(void)
 {
 	unsigned int reg[4] = {0};
+	bool		have_osxsave;
 
 	pg_cpuid(0x01, reg);
 
 	X86Features[PG_SSE4_2] = reg[ECX] >> 20 & 1;
 	X86Features[PG_POPCNT] = reg[ECX] >> 23 & 1;
+	X86Features[PG_HYPERVISOR] = reg[ECX] >> 31 & 1;
+	have_osxsave = reg[ECX] & (1 << 27);
+
+	pg_cpuid_subleaf(0x07, 0, reg);
+
+	X86Features[PG_TSC_ADJUST] = (reg[EBX] & (1 << 1)) != 0;
 
 	/* leaf 7 features that depend on OSXSAVE */
-	if (reg[ECX] & (1 << 27))
+	if (have_osxsave)
 	{
 		uint32		xcr0_val = 0;
 
-		pg_cpuid_subleaf(0x07, 0, reg);
-
 #ifdef HAVE_XSAVE_INTRINSICS
 		/* get value of Extended Control Register */
 		xcr0_val = _xgetbv(0);
@@ -131,7 +136,120 @@ set_x86_features(void)
 		}
 	}
 
+	/* Check for other TSC related flags */
+	pg_cpuid(0x80000001, reg);
+	X86Features[PG_RDTSCP] = reg[EDX] >> 27 & 1;
+
+	pg_cpuid(0x80000007, reg);
+	X86Features[PG_TSC_INVARIANT] = reg[EDX] >> 8 & 1;
+
 	X86Features[INIT_PG_X86] = true;
 }
 
+/* TSC (Time-stamp Counter) handling code */
+
+static uint32 x86_hypervisor_tsc_frequency_khz(void);
+
+/*
+ * Determine the TSC frequency of the CPU through CPUID, where supported.
+ *
+ * Needed to interpret the tick value returned by RDTSC/RDTSCP. Return value of
+ * 0 indicates the frequency information was not accessible via CPUID.
+ */
+uint32
+x86_tsc_frequency_khz(void)
+{
+	unsigned int reg[4] = {0};
+
+	if (x86_feature_available(PG_HYPERVISOR))
+		return x86_hypervisor_tsc_frequency_khz();
+
+	/*
+	 * On modern Intel CPUs, the TSC is implemented by invariant timekeeping
+	 * hardware, also called "Always Running Timer", or ART. The ART stays
+	 * consistent even if the CPU changes frequency due to changing power
+	 * levels.
+	 *
+	 * As documented in "Determining the Processor Base Frequency" in the
+	 * "Intel® 64 and IA-32 Architectures Software Developer's Manual",
+	 * February 2026 Edition, we can get the TSC frequency as follows:
+	 *
+	 * Nominal TSC frequency = ( CPUID.15H:ECX[31:0] * CPUID.15H:EBX[31:0] ) /
+	 * CPUID.15H:EAX[31:0]
+	 *
+	 * With CPUID.15H:ECX representing the nominal core crystal clock
+	 * frequency, and EAX/EBX representing values used to translate the TSC
+	 * value to that frequency, see "Chapter 20.17 "Time-Stamp Counter" of
+	 * that manual.
+	 *
+	 * Older Intel CPUs, and other vendors do not set CPUID.15H:ECX, and as
+	 * such we fall back to alternate approaches.
+	 */
+	pg_cpuid(0x15, reg);
+	if (reg[ECX] > 0)
+	{
+		/*
+		 * EBX not being set indicates invariant TSC is not available. Require
+		 * EAX being non-zero too, to avoid a theoretical divide by zero.
+		 */
+		if (reg[EAX] == 0 || reg[EBX] == 0)
+			return 0;
+
+		return reg[ECX] / 1000 * reg[EBX] / reg[EAX];
+	}
+
+	/*
+	 * When CPUID.15H is not available/incomplete, we can instead try to get
+	 * the processor base frequency in MHz from CPUID.16H:EAX, the "Processor
+	 * Frequency Information Leaf".
+	 */
+	pg_cpuid(0x16, reg);
+	if (reg[EAX] > 0)
+		return reg[EAX] * 1000;
+
+	return 0;
+}
+
+/*
+ * Support for reading TSC frequency for hypervisors passing it to a guest VM.
+ *
+ * Two Hypervisors (VMware and KVM) are known to make TSC frequency in KHz
+ * available at the vendor-specific 0x40000010 leaf in the EAX register.
+ *
+ * For some other Hypervisors that have an invariant TSC, e.g. HyperV, we would
+ * need to access an MSR to get the frequency (which is typically not available
+ * for unprivileged processes), so we instead rely on the TSC calibration logic.
+ */
+#define CPUID_HYPERVISOR_VMWARE(r) (r[EBX] == 0x61774d56 && r[ECX] == 0x4d566572 && r[EDX] == 0x65726177)	/* VMwareVMware */
+#define CPUID_HYPERVISOR_KVM(r) (r[EBX] == 0x4b4d564b && r[ECX] == 0x564b4d56 && r[EDX] == 0x0000004d)	/* KVMKVMKVM */
+static uint32
+x86_hypervisor_tsc_frequency_khz(void)
+{
+	unsigned int reg[4] = {0};
+
+#if defined(HAVE__CPUIDEX)
+
+	/*
+	 * The hypervisor is determined using the 0x40000000 Hypervisor
+	 * information leaf, which requires use of __cpuidex to set ECX to 0 to
+	 * access it.
+	 *
+	 * The similar __get_cpuid_count function does not work as expected since
+	 * it contains a check for __get_cpuid_max, which has been observed to be
+	 * lower than the special Hypervisor leaf, despite it being available.
+	 */
+	__cpuidex((int *) reg, 0x40000000, 0);
+
+	if (reg[EAX] >= 0x40000010 && (CPUID_HYPERVISOR_VMWARE(reg) || CPUID_HYPERVISOR_KVM(reg)))
+	{
+		__cpuidex((int *) reg, 0x40000010, 0);
+		if (reg[EAX] > 0)
+			return reg[EAX];
+	}
+#endif							/* HAVE__CPUIDEX */
+
+	return 0;
+}
+
+
 #endif							/* defined(USE_SSE2) || defined(__i386__) */
-- 
2.47.1