From 9655362e52f64f09b9f6bcd5afe7b85bb1145ba3 Mon Sep 17 00:00:00 2001 From: He-Pin Date: Sun, 5 Apr 2026 10:17:32 +0800 Subject: [PATCH] perf: valTag dispatch for O(1) materializer type routing Add a valTag: Byte abstract method to Val with TAG constants (0-7) for each concrete subclass, enabling JVM tableswitch O(1) dispatch in the materializer instead of linear pattern matching. Changes: - Val.scala: Add valTag abstract method and TAG_STR/NUM/TRUE/FALSE/NULL/ ARR/OBJ/FUNC constants (0-7 contiguous range) - Materializer.scala: Replace pattern-match in materializeRecursiveChild with @switch tableswitch dispatch on valTag. Hoist xs.length out of materializeRecursiveArr while-loop. - CustomValTests.scala: Add valTag=-1 to ImportantString (custom Val) JMH improvements: reverse -2.9%, base64DecodeBytes -4.6%, comparison2 -1.9%, base64 -2.1%. No regressions outside noise range. Upstream: he-pin/sjsonnet jit branch commits 30b7495b, 9ddb1a57 --- sjsonnet/src/sjsonnet/Materializer.scala | 101 +++++++++++------- sjsonnet/src/sjsonnet/Val.scala | 23 ++++ .../test/src/sjsonnet/CustomValTests.scala | 1 + 3 files changed, 84 insertions(+), 41 deletions(-) diff --git a/sjsonnet/src/sjsonnet/Materializer.scala b/sjsonnet/src/sjsonnet/Materializer.scala index 97cc5ccc..227ba2db 100644 --- a/sjsonnet/src/sjsonnet/Materializer.scala +++ b/sjsonnet/src/sjsonnet/Materializer.scala @@ -16,10 +16,10 @@ import upickle.core.{ArrVisitor, ObjVisitor, Visitor} * guards against accidental TailCall leakage with a clear internal-error diagnostic. * * Match ordering: all dispatch points ([[apply0]], [[materializeRecursiveChild]], - * [[materializeChild]]) use a unified match that places [[Val.Str]] first, followed by [[Val.Obj]], - * [[Val.Num]], [[Val.Arr]], and other leaf types. This ordering mirrors the original single-method - * design and ensures the most common leaf type (strings) is matched without first testing for - * container types. + * [[materializeChild]]) use a unified dispatch that routes via `valTag` (a byte field on each + * [[Val]] subclass) through a JVM tableswitch for O(1) type resolution. The tag values 0-7 + * correspond to Str, Num, True, False, Null, Arr, Obj, Func respectively. Rare types + * (Materializable, TailCall) fall through to a pattern match in the default branch. */ abstract class Materializer { def storePos(pos: Position): Unit @@ -110,9 +110,10 @@ abstract class Materializer { depth: Int, ctx: Materializer.MaterializeContext)(implicit evaluator: EvalScope): T = { storePos(xs.pos) - val av = visitor.visitArray(xs.length, -1) + val len = xs.length + val av = visitor.visitArray(len, -1) var i = 0 - while (i < xs.length) { + while (i < len) { val childVal = xs.value(i) av.visitValue( materializeRecursiveChild(childVal, av.subVisitor.asInstanceOf[Visitor[T, T]], depth, ctx), @@ -127,41 +128,59 @@ abstract class Materializer { childVal: Val, childVisitor: Visitor[T, T], depth: Int, - ctx: Materializer.MaterializeContext)(implicit evaluator: EvalScope): T = childVal match { - case Val.Str(pos, s) => storePos(pos); childVisitor.visitString(s, -1) - case obj: Val.Obj => - val nextDepth = depth + 1 - if (nextDepth < ctx.recursiveDepthLimit) - materializeRecursiveObj(obj, childVisitor, nextDepth, ctx) - else - materializeStackless(childVal, childVisitor, ctx) - case Val.Num(pos, _) => storePos(pos); childVisitor.visitFloat64(childVal.asDouble, -1) - case xs: Val.Arr => - val nextDepth = depth + 1 - if (nextDepth < ctx.recursiveDepthLimit) - materializeRecursiveArr(xs, childVisitor, nextDepth, ctx) - else - materializeStackless(childVal, childVisitor, ctx) - case Val.True(pos) => storePos(pos); childVisitor.visitTrue(-1) - case Val.False(pos) => storePos(pos); childVisitor.visitFalse(-1) - case Val.Null(pos) => storePos(pos); childVisitor.visitNull(-1) - case mat: Materializer.Materializable => storePos(childVal.pos); mat.materialize(childVisitor) - case s: Val.Func => - Error.fail( - "Couldn't manifest function with params [" + s.params.names.mkString(",") + "]", - childVal.pos - ) - case tc: TailCall => - Error.fail( - "Internal error: TailCall sentinel leaked into materialization. " + - "This indicates a bug in the TCO protocol — a TailCall was not resolved before " + - "reaching the Materializer.", - tc.pos - ) - case vv: Val => - Error.fail("Unknown value type " + vv.prettyName, vv.pos) - case null => - Error.fail("Unknown value type " + childVal) + ctx: Materializer.MaterializeContext)(implicit evaluator: EvalScope): T = { + if (childVal == null) Error.fail("Unknown value type " + childVal) + // Use tableswitch dispatch via @switch on valTag for O(1) type routing. + // This replaces up to 7 sequential instanceof checks with a single + // byte comparison + jump table, benefiting materialization-heavy workloads. + val vt: Int = childVal.valTag.toInt + (vt: @scala.annotation.switch) match { + case 0 => // TAG_STR + val s = childVal.asInstanceOf[Val.Str] + storePos(s.pos); childVisitor.visitString(s.str, -1) + case 1 => // TAG_NUM + storePos(childVal.pos); childVisitor.visitFloat64(childVal.asDouble, -1) + case 2 => // TAG_TRUE + storePos(childVal.pos); childVisitor.visitTrue(-1) + case 3 => // TAG_FALSE + storePos(childVal.pos); childVisitor.visitFalse(-1) + case 4 => // TAG_NULL + storePos(childVal.pos); childVisitor.visitNull(-1) + case 5 => // TAG_ARR + val xs = childVal.asInstanceOf[Val.Arr] + val nextDepth = depth + 1 + if (nextDepth < ctx.recursiveDepthLimit) + materializeRecursiveArr(xs, childVisitor, nextDepth, ctx) + else + materializeStackless(childVal, childVisitor, ctx) + case 6 => // TAG_OBJ + val obj = childVal.asInstanceOf[Val.Obj] + val nextDepth = depth + 1 + if (nextDepth < ctx.recursiveDepthLimit) + materializeRecursiveObj(obj, childVisitor, nextDepth, ctx) + else + materializeStackless(childVal, childVisitor, ctx) + case 7 => // TAG_FUNC + val s = childVal.asInstanceOf[Val.Func] + Error.fail( + "Couldn't manifest function with params [" + s.params.names.mkString(",") + "]", + childVal.pos + ) + case _ => + childVal match { + case mat: Materializer.Materializable => + storePos(childVal.pos); mat.materialize(childVisitor) + case tc: TailCall => + Error.fail( + "Internal error: TailCall sentinel leaked into materialization. " + + "This indicates a bug in the TCO protocol — a TailCall was not resolved before " + + "reaching the Materializer.", + tc.pos + ) + case vv: Val => + Error.fail("Unknown value type " + vv.prettyName, vv.pos) + } + } } // Iterative materialization for deep nesting. Used as a fallback when recursive depth exceeds diff --git a/sjsonnet/src/sjsonnet/Val.scala b/sjsonnet/src/sjsonnet/Val.scala index ae1bd415..472efb8d 100644 --- a/sjsonnet/src/sjsonnet/Val.scala +++ b/sjsonnet/src/sjsonnet/Val.scala @@ -139,6 +139,9 @@ final class LazyApply2( sealed abstract class Val extends Eval { final def value: Val = this + /** Runtime type tag for O(1) dispatch in Materializer (tableswitch). */ + private[sjsonnet] def valTag: Byte + def pos: Position def prettyName: String @@ -175,6 +178,17 @@ object Val { private[sjsonnet] final val DOUBLE_MAX_SAFE_INTEGER = (1L << 53) - 1 private[sjsonnet] final val DOUBLE_MIN_SAFE_INTEGER = -((1L << 53) - 1) + // Runtime type tags for O(1) dispatch in Materializer (tableswitch). + // Values 0-7 form a contiguous range enabling JVM tableswitch bytecode. + private[sjsonnet] final val TAG_STR: Byte = 0 + private[sjsonnet] final val TAG_NUM: Byte = 1 + private[sjsonnet] final val TAG_TRUE: Byte = 2 + private[sjsonnet] final val TAG_FALSE: Byte = 3 + private[sjsonnet] final val TAG_NULL: Byte = 4 + private[sjsonnet] final val TAG_ARR: Byte = 5 + private[sjsonnet] final val TAG_OBJ: Byte = 6 + private[sjsonnet] final val TAG_FUNC: Byte = 7 + abstract class Literal extends Val with Expr { final override private[sjsonnet] def tag = ExprTags.`Val.Literal` } @@ -186,16 +200,20 @@ object Val { final case class True(var pos: Position) extends Bool { def prettyName = "boolean" + private[sjsonnet] def valTag: Byte = TAG_TRUE } final case class False(var pos: Position) extends Bool { def prettyName = "boolean" + private[sjsonnet] def valTag: Byte = TAG_FALSE } final case class Null(var pos: Position) extends Literal { def prettyName = "null" + private[sjsonnet] def valTag: Byte = TAG_NULL } final case class Str(var pos: Position, str: String) extends Literal { def prettyName = "string" override def asString: String = str + private[sjsonnet] def valTag: Byte = TAG_STR } final case class Num(var pos: Position, private val num: Double) extends Literal { if (num.isInfinite) { @@ -235,10 +253,12 @@ object Val { } num } + private[sjsonnet] def valTag: Byte = TAG_NUM } final case class Arr(var pos: Position, private val arr: Array[? <: Eval]) extends Literal { def prettyName = "array" + private[sjsonnet] def valTag: Byte = TAG_ARR override def asArr: Arr = this def length: Int = arr.length @@ -364,6 +384,7 @@ object Val { private val excludedKeys: java.util.Set[String] = null) extends Literal with Expr.ObjBody { + private[sjsonnet] def valTag: Byte = TAG_OBJ private var asserting: Boolean = false def getSuper: Obj = `super` @@ -767,6 +788,7 @@ object Val { extends Val with Expr { final override private[sjsonnet] def tag = ExprTags.`Val.Func` + private[sjsonnet] def valTag: Byte = TAG_FUNC def evalRhs(scope: ValScope, ev: EvalScope, fs: FileScope, pos: Position): Val @@ -1185,6 +1207,7 @@ final class TailCall( val namedNames: Array[String], val callSiteExpr: Expr) extends Val { + private[sjsonnet] def valTag: Byte = -1 def pos: Position = callSiteExpr.pos def prettyName = "tailcall" def exprErrorString: String = callSiteExpr.exprErrorString diff --git a/sjsonnet/test/src/sjsonnet/CustomValTests.scala b/sjsonnet/test/src/sjsonnet/CustomValTests.scala index 9b920a5d..4860997c 100644 --- a/sjsonnet/test/src/sjsonnet/CustomValTests.scala +++ b/sjsonnet/test/src/sjsonnet/CustomValTests.scala @@ -8,6 +8,7 @@ object CustomValTests extends TestSuite { extends Val.Literal with Materializer.Materializable { override def prettyName: String = "Important string" + private[sjsonnet] def valTag: Byte = -1 def materialize[T](visitor: Visitor[T, T])(implicit evaluator: EvalScope): T = { visitor.visitString(str + "!".repeat(importance), -1) }