Skip to content

Commit db8a594

Browse files
ehighamclaude
andauthored
[query] Dispatch TypeCheck on a per-node tag
TypeCheck.checkSingleNode was one match of about 20.8 KB of bytecode. HotSpot never compiles a method over 8000 bytes, since DontCompileHugeMethods is on by default. So it always ran in the interpreter. Every IR node class now carries a tag, and TypeCheck switches on it in two levels. checkSingleNode switches on the node's family and calls one method per family, which switches on the tag. Both levels compile to jump tables. Each case casts the node and reads its fields. The largest method, checkStream, is 4.2 KB. ir-gen puts each node in one family, named where the node is defined: node(Const, "I32", ...). There are 13 families: Const, Core, Struct, Stream, Collection, Agg, IO, NDArray, Random, Relational, Table, Matrix and BlockMatrix. Relational, Table and Matrix hold the nodes a lowering pass removes, and a node goes in the family of the first invariant that forbids it. BlockMatrix is removed at the same point as Table, and takes the nodes that read or make a block matrix but no Table. The other families are buckets that keep each method small, and a node goes in the family of what it produces. A tag holds the family in the bits above Tag.FamilyShift and the node's position in its family below. Within a family, nodes are ordered so the cases TypeCheck and ChildEnv handle sit on consecutive tags, which keeps each switch dense enough to compile to a jump table. ir-gen writes Tag_gen.scala, with the Family and Tag value classes and an Int constant for each family and node. The constants are untyped final vals, so a @switch on them compiles to a jump table. The Table, Matrix and BlockMatrix nodes are written by hand. ir-gen lists them among the generated nodes as handWritten(Table, "TableRead") and generates their tags too. BaseIR takes the tag as a final constructor field, so reading it is a field load. An overridden def tag was a virtual call HotSpot could not inline, and made TypeCheck 11% slower on a 1000-field MakeStruct. Tag stores a Short, which fits in padding most node classes already have. 203 of 217 classes keep their size. Weighted by node visits in the Python test suite, the average node grows from 42.4 to 42.5 bytes. The NDArrayIR trait goes. ir-gen marks those nodes TypedIR[TNDArray] instead, and TypeCheck reads x.typ.elementType. In IRPassBenchmark, against the previous commit, TypeCheck drops from 4.9 ms to 1.0 ms on a 3000-binding Block, from 5.3 ms to 0.58 ms on 2000 nested StreamMaps, from 6.6 ms to 0.92 ms on a balanced tree and from 1.5 ms to 0.26 ms on a 1000-field MakeStruct. The gain comes from getting each method under 8000 bytes. A version that only split the instanceof chain into four methods ran within noise of the tag version over 3 forks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 6955c2d commit db8a594

10 files changed

Lines changed: 1566 additions & 997 deletions

File tree

‎hail/hail/ir-gen/src/Main.scala‎

Lines changed: 643 additions & 291 deletions
Large diffs are not rendered by default.

‎hail/hail/src/is/hail/expr/ir/BaseIR.scala‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ case class Name(str: String) {
1111
override def toString: String = str
1212
}
1313

14-
abstract class BaseIR {
14+
/** @param tag
15+
* identifies this node's class, for dispatch with `@switch`. See [[Tag]]. It is a field, not a
16+
* method, so reading it is a load rather than a virtual call.
17+
*/
18+
abstract class BaseIR(final val tag: Tag) {
1519
def typ: VType
1620

1721
protected def childrenSeq: IndexedSeq[BaseIR]

‎hail/hail/src/is/hail/expr/ir/BlockMatrixIR.scala‎

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ object BlockMatrixIR {
4343
)
4444
}
4545

46-
sealed abstract class BlockMatrixIR extends BaseIR {
46+
sealed abstract class BlockMatrixIR(tag: Tag) extends BaseIR(tag) {
4747
override def typ: BlockMatrixType
4848
override def deepCopy: BlockMatrixIR = super.deepCopy.asInstanceOf[BlockMatrixIR]
4949

@@ -55,7 +55,8 @@ sealed abstract class BlockMatrixIR extends BaseIR {
5555
def blockCostIsLinear: Boolean
5656
}
5757

58-
case class BlockMatrixRead(reader: BlockMatrixReader) extends BlockMatrixIR {
58+
case class BlockMatrixRead(reader: BlockMatrixReader)
59+
extends BlockMatrixIR(new Tag(Tag.BlockMatrixRead)) {
5960
override lazy val typ: BlockMatrixType = reader.fullType
6061

6162
lazy val childrenSeq: IndexedSeq[BaseIR] = ArraySeq.empty
@@ -261,7 +262,7 @@ case class BlockMatrixPersistReader(id: String, typ: BlockMatrixType) extends Bl
261262
}
262263

263264
case class BlockMatrixMap(child: BlockMatrixIR, eltName: Name, f: IR, needsDense: Boolean)
264-
extends BlockMatrixIR {
265+
extends BlockMatrixIR(new Tag(Tag.BlockMatrixMap)) {
265266
override def typ: BlockMatrixType = child.typ
266267

267268
lazy val childrenSeq: IndexedSeq[BaseIR] = ArraySeq(child, f)
@@ -398,7 +399,7 @@ case class BlockMatrixMap2(
398399
rightName: Name,
399400
f: IR,
400401
sparsityStrategy: SparsityStrategy,
401-
) extends BlockMatrixIR with Logging {
402+
) extends BlockMatrixIR(new Tag(Tag.BlockMatrixMap2)) with Logging {
402403
override lazy val typ: BlockMatrixType =
403404
left.typ.copy(sparsity = sparsityStrategy.mergeSparsity(left.typ.sparsity, right.typ.sparsity))
404405

@@ -529,7 +530,7 @@ case class BlockMatrixMap2(
529530
}
530531

531532
case class BlockMatrixDot(left: BlockMatrixIR, right: BlockMatrixIR)
532-
extends BlockMatrixIR with Logging {
533+
extends BlockMatrixIR(new Tag(Tag.BlockMatrixDot)) with Logging {
533534
override lazy val typ: BlockMatrixType = {
534535
val blockSize = left.typ.blockSize
535536
assert(left.typ.nCols == right.typ.nRows)
@@ -588,7 +589,7 @@ case class BlockMatrixBroadcast(
588589
inIndexExpr: IndexedSeq[Int],
589590
shape: IndexedSeq[Long],
590591
blockSize: Int,
591-
) extends BlockMatrixIR {
592+
) extends BlockMatrixIR(new Tag(Tag.BlockMatrixBroadcast)) {
592593

593594
val blockCostIsLinear: Boolean = child.blockCostIsLinear
594595

@@ -679,7 +680,7 @@ case class BlockMatrixBroadcast(
679680
case class BlockMatrixAgg(
680681
child: BlockMatrixIR,
681682
axesToSumOut: IndexedSeq[Int],
682-
) extends BlockMatrixIR {
683+
) extends BlockMatrixIR(new Tag(Tag.BlockMatrixAgg)) {
683684

684685
val blockCostIsLinear: Boolean = child.blockCostIsLinear
685686

@@ -729,7 +730,7 @@ case class BlockMatrixAgg(
729730
case class BlockMatrixFilter(
730731
child: BlockMatrixIR,
731732
indices: IndexedSeq[IndexedSeq[Long]],
732-
) extends BlockMatrixIR {
733+
) extends BlockMatrixIR(new Tag(Tag.BlockMatrixFilter)) {
733734

734735
assert(indices.length == 2)
735736

@@ -772,7 +773,8 @@ case class BlockMatrixFilter(
772773
}
773774
}
774775

775-
case class BlockMatrixDensify(child: BlockMatrixIR) extends BlockMatrixIR {
776+
case class BlockMatrixDensify(child: BlockMatrixIR)
777+
extends BlockMatrixIR(new Tag(Tag.BlockMatrixDensify)) {
776778
override lazy val typ: BlockMatrixType = child.typ.densify
777779

778780
override def blockCostIsLinear: Boolean = child.blockCostIsLinear
@@ -897,7 +899,7 @@ case class PerBlockSparsifier(blocks: IndexedSeq[Int]) extends BlockMatrixSparsi
897899
case class BlockMatrixSparsify(
898900
child: BlockMatrixIR,
899901
sparsifier: BlockMatrixSparsifier,
900-
) extends BlockMatrixIR {
902+
) extends BlockMatrixIR(new Tag(Tag.BlockMatrixSparsify)) {
901903
override lazy val typ: BlockMatrixType =
902904
child.typ.copy(sparsity = sparsifier.definedBlocks(child.typ))
903905

@@ -915,7 +917,7 @@ case class BlockMatrixSparsify(
915917
}
916918

917919
case class BlockMatrixSlice(child: BlockMatrixIR, slices: IndexedSeq[IndexedSeq[Long]])
918-
extends BlockMatrixIR {
920+
extends BlockMatrixIR(new Tag(Tag.BlockMatrixSlice)) {
919921
assert(slices.length == 2)
920922
assert(slices.forall(_.length == 3))
921923

@@ -986,7 +988,7 @@ case class ValueToBlockMatrix(
986988
child: IR,
987989
shape: IndexedSeq[Long],
988990
blockSize: Int,
989-
) extends BlockMatrixIR {
991+
) extends BlockMatrixIR(new Tag(Tag.ValueToBlockMatrix)) {
990992
assert(shape.length == 2)
991993

992994
val blockCostIsLinear: Boolean = true
@@ -1041,7 +1043,7 @@ case class BlockMatrixRandom(
10411043
gaussian: Boolean,
10421044
shape: IndexedSeq[Long],
10431045
blockSize: Int,
1044-
) extends BlockMatrixIR {
1046+
) extends BlockMatrixIR(new Tag(Tag.BlockMatrixRandom)) {
10451047

10461048
assert(shape.length == 2)
10471049

‎hail/hail/src/is/hail/expr/ir/ChildEnv.scala‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,8 @@ object ChildEnv {
133133
}
134134

135135
/** Walks Block `b`'s children (each binding's value, then the body), threading `b`'s env so the
136-
* walk is linear in its bindings. `f` takes child `i`, `i` and child `i`'s env, and returns child
137-
* `i`'s final form, whose type binding `i` binds.
136+
* walk is linear in its bindings. `f` takes child `i`, `i` and child `i`'s env, and returns
137+
* child `i`'s final form, whose type binding `i` binds.
138138
*/
139139
def walkBlock[E[_]: Scopes, A](
140140
b: Block,

‎hail/hail/src/is/hail/expr/ir/IR.scala‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import java.io.OutputStream
3030
import org.json4s.{DefaultFormats, Extraction, Formats, JValue, ShortTypeHints}
3131
import org.json4s.JsonAST.{JNothing, JString}
3232

33-
abstract class IR extends BaseIR {
33+
abstract class IR(tag: Tag) extends BaseIR(tag) {
3434
private var _typ: Type = null
3535

3636
override def typ: Type =
@@ -348,10 +348,6 @@ package defs {
348348
}
349349
}
350350

351-
trait NDArrayIR extends TypedIR[TNDArray] {
352-
def elementTyp: Type = typ.elementType
353-
}
354-
355351
object GetFieldByIdx {
356352
def apply(s: IR, field: Int): IR =
357353
(s.typ: @unchecked) match {

0 commit comments

Comments
 (0)