Class ConstantMethodResultSpecializer

java.lang.Object
org.elasticsearch.compute.operator.ConstantMethodResultSpecializer

public final class ConstantMethodResultSpecializer extends Object
Specializes a class at runtime that extends a given evaluator base class and supplies specified constants in a way HotSpot's C2 compiler treats as JIT-time constants. For primitives the constant is baked as a static final field on the generated subclass. For reference types the constant is passed via class data and loaded through a condy bootstrap into the accessor method. Either way, once the subclass is loaded, C2 inlines the accessor through the monomorphic call site and folds the constant — unlocking optimizations such as Granlund-Montgomery strength reduction for integer divide, devirtualization of subsequent method calls on a reference receiver, branch elimination, and so on.

The factory used by ESQL's @Fixed(jitConstant = true) codegen calls one of the *ConstantSubclass methods. Authors do not invoke this class directly.

Cache design — weak refs + admission filter

Two structures handle the workload-shape tradeoffs:
  1. Class cache: ConcurrentHashMap<Key, WeakReference<Class>>. Unbounded; JVM manages size via reachability. A constant-specialized class is kept alive by any live evaluator instance referencing it. Once nothing references the class, GC reclaims it and the weak ref clears — the next access starts fresh.
  2. Admission tracker: bounded LRU counters keyed by specializer key (default 4096 entries, ~128 KB). Specialization triggers only when a key's count reaches admissionThreshold (default 2). Single-occurrence keys never trigger a spin — the caller falls back to the non-folded path. This eliminates the spin tax for one-off cold keys.

Properties this gives:

  • High-cardinality one-off workload (e.g. 50 K distinct constants none repeating): zero spins. Per-access overhead = one CHM lookup + one counter increment (~50 ns).
  • Bursty workload: hot keys spin once on second access; stay alive while bursts run (live instances pin them); GC after a quiet period.
  • Steady-state hot: spin once per key, then constant hit rate forever.
  • Stampede: concurrent threads requesting the same missing key all funnel through a single computeIfAbsent — only one spin happens.

Cost calibration

Measured on Apple aarch64 JDK 21 after JIT warmup:
  • Specialization cost: mean 13 μs, p99 52 μs, max 1.5 ms (cold-JIT outlier)
  • Per-class metaspace: ~1.4-2 KB
  • Cache hit lookup: ~50 ns
  • Admission-rejected path: ~50 ns (one CHM lookup + counter inc)

Measurement is mandatory for adoption

Adoption decisions are not predictable from code reading alone — both wins and regressions surface that the static rules don't catch. Every flag adoption must come with a JMH bench case (const-folded + variable baseline) measured on at least three microarchitectures. See Fixed.jitConstant() for the decision framework and the calibration table from PR #148678 covering 11 attempted adoptions, of which 4 shipped, 1 was kept noise-band-neutral, and 6 were reverted with measurement evidence.
  • Field Details

    • SHARED

      public static final ConstantMethodResultSpecializer SHARED
      JVM-wide default specializer instance. Generated evaluator factories call ConstantMethodResultSpecializer.SHARED.specializeLong(...); tests construct their own instance via new ConstantMethodResultSpecializer() for isolation (no shared state).

      The cache + admission tracker are intentionally process-global by default: two instances would each spin a hidden class for the same (base, accessor, value) triple, duplicating JIT compilations and defeating the cache. Use new only when you want isolated state.

  • Method Details

    • specializeLong

      public <T> Optional<Class<? extends T>> specializeLong(Class<T> baseClass, String methodName, long value)
      Materialize (or fetch from cache) a hidden subclass of baseClass that overrides methodName() (which the base must declare as protected abstract long methodName()) to return value as a JIT-time constant. Returns Optional.empty() only when the cache is at capacity and adding this entry would require eviction — the caller should fall back to the non-folded path in that case.
    • specializeInt

      public <T> Optional<Class<? extends T>> specializeInt(Class<T> baseClass, String methodName, int value)
    • specializeDouble

      public <T> Optional<Class<? extends T>> specializeDouble(Class<T> baseClass, String methodName, double value)
    • specializeReference

      public <T, V> Optional<Class<? extends T>> specializeReference(Class<T> baseClass, String methodName, Class<V> valueType, V value)
      Same as specializeLong(java.lang.Class<T>, java.lang.String, long) but for an arbitrary reference type. The constant is passed via class data and loaded through a condy bootstrap referencing MethodHandles.classData(MethodHandles.Lookup, String, Class).
    • setAdmissionThreshold

      public void setAdmissionThreshold(int newThreshold)
      Set admission threshold. A key must be seen this many times before a class is specialized for it. Default = 2 (skip the first-time access — usually a one-off). Set to 1 to disable admission filtering (every miss spins immediately, like the prior behavior).

      Public so cross-package tests in esql can force the Standard / constant-specialized paths by setting the threshold extreme. No production caller; if a tuning API becomes useful, route it through SHARED explicitly.

    • resetForTest

      public void resetForTest()
      Stress-test / tooling support: clear all caches and counters and reset config. Not for production use. Marked public so external benchmark/stress harnesses can reset state between scenarios; production code should never call this.