// Whole-row predicate specialization: a comparison whose sides are// property reads over typed i64 columns (optionally shifted by a constant// integer or months-free duration) -- the dominant fully-bound filter// shape, e.g. a temporal ordering between two pattern variables -- runs as// a monomorphic closure reading both columns directly, skipping the// compiled tree's dispatch and per-row boxing. A slot-vs-slot comparison// flattens to one shared-kernel Compare call. Rows whose slots do not// hold nodes fall back to the full tree evaluation, so results are exactly// the general path's for every input.
package compileimport (chickpeas)// addChecked / mulChecked are checked int64 add / multiply (comma-ok),// mirroring eval's temporal guard so the specialization's constant fold// declines exactly when the interpreter would overflow to Null.func addChecked(, int64) (int64, bool) { := + return , ( > ) == ( > 0) || == 0}func mulChecked(, int64) (int64, bool) {if == 0 || == 0 {return0, true }if ( == math.MinInt64 && == -1) || ( == math.MinInt64 && == -1) {return0, false } := * return , / == }// rowFast is a monomorphic whole-expression evaluator, result-identical// to ceval on the same tree.type rowFast func(ctx *eval.Ctx, row []value.Value, slots map[string]int) value.Value// i64Term reads one comparison side: an i64 node-column property,// optionally shifted by a compile-time constant.type i64Term struct { slot int col chickpeas.I64Col mode uint8 off int64 sub bool}const ( termPlain uint8 = iota// termDur shifts by a months-free duration's tick offset -- Arith's // Int +/- Duration through ApplyDuration, which for months == 0 is // unchecked tick addition (off carries the operator's sign). termDur// termInt shifts by an integer constant with Arith's checked Int // add/subtract semantics (overflow = Null). termInt)// read resolves the term against a row. state readNull propagates a Null// operand (absent property, null slot, checked overflow) into the// comparison's Null result; readFallback sends the whole row to the tree// evaluation (a slot holding anything besides a node or null).const ( readOK uint8 = iota readNull readFallback)func ( *i64Term) ( []value.Value) (int64, uint8) {if .slot < 0 || .slot >= len() {return0, readNull } := [.slot]if .Kind() != value.KindNode {if .IsNull() {return0, readNull }return0, readFallback } , := .AsNode() , := .col.Get(uint32())if ! {return0, readNull }switch .mode {casetermDur:// Months-free tick add, checked like eval's fixed ApplyDuration: // overflow is Null (the interpreter produces the same Null). := + .offif ( > ) == (.off > 0) || .off == 0 {return , readOK }return0, readNullcasetermInt:// Mirror eval's checked int arithmetic exactly: overflow is Null.if .sub { := - .offif ( < ) == (.off > 0) || .off == 0 {return , readOK }return0, readNull } := + .offif ( > ) == (.off > 0) || .off == 0 {return , readOK }return0, readNull }return , readOK}// deriveRowFast specializes a compiled tree whose root is a comparison// over two supported terms; nil keeps the tree evaluation.func deriveRowFast( cnode, *chickpeas.Snapshot) rowFast {// A bare property read or slot reference -- the dominant projection-item // shapes, evaluated once per output column per row -- skips the tree // dispatch entirely: cevalProp/the slot read are self-contained, and the // closure binds the node directly (ceval's cases do nothing more).if , := .(*cProp); {returnfunc( *eval.Ctx, []value.Value, map[string]int) value.Value {returncevalProp(, ) } }if , := .(*cSlot); { := .sreturnfunc( *eval.Ctx, []value.Value, map[string]int) value.Value {returnslotVal(, ) } }// A bare prop-vs-const comparison is already fused to cCmpPropConst; derive // the monomorphic i64 compare from it so the common filter shape skips // value.Compare's type switch too (rev marks the const on the left).if , := .(*cCmpPropConst); {if .prop.reader.node.kind != colI64 {returnnil } , , := constI64Val(.c)if ! {returnnil } := i64Term{slot: .prop.slot, col: .prop.reader.node.i64}returnpropLitFast(, , , opKeep(.op), .rev, , ) } , := .(*cBin)if ! {returnnil }switch .op {caseast.OpEq, ast.OpNeq, ast.OpLt, ast.OpLte, ast.OpGt, ast.OpGte:default:returnnil } := opKeep(.op)if , := .l.(*cSlot); {if , := .r.(*cSlot); { , := .s, .sreturnfunc( *eval.Ctx, []value.Value, map[string]int) value.Value { , := value.Compare(slotVal(, ), slotVal(, ))if ! {returnvalue.Null() }returnvalue.Bool(()) } } } , := i64TermOf(.l) , := i64TermOf(.r)if && {returnfunc( *eval.Ctx, []value.Value, map[string]int) value.Value { , := .read()if == readFallback {returnceval(, , , , ) } , := .read()if == readFallback {returnceval(, , , , ) }if == readNull || == readNull {returnvalue.Null() }// Same-kind ints compare through float64 exactly like // value.Compare's asNum path -- mirroring is the invariant. , := cmpFloat(float64(), float64())if ! {returnvalue.Null() }returnvalue.Bool(()) } }// One typed term against a foldable i64 literal -- WHERE p.age > 30 / // WHERE m.creationDate < datetime(...), the most common filter shape, which // the two-term derivation above silently declined.if {if , , := constI64(.r); {returnpropLitFast(, , , , false, , ) } }if {if , , := constI64(.l); {returnpropLitFast(, , , , true, , ) } }returnnil}// constI64 folds a bare comparison literal to the int64 the monomorphic path// compares against, reporting whether value.Compare orders it EXACTLY (int64)// rather than through the float64 asNum path. Only Int and Temporal literals// fold: an Int compares Int-vs-Int through asNum (exact=false, matching the// two-term path and the always-Int column read); a Temporal compares against// the i64 epoch-millis column through cmpInt (exact=true), so folding it to its// millis is bit-identical. Float/Null/Str/Bool/Duration are declined -- an// integer compare cannot reproduce their semantics (truncating a float would// silently change the result).func constI64( cnode) ( int64, , bool) { , := .(*cLit)if ! {return0, false, false }returnconstI64Val(.v)}// constI64Val is constI64's fold over an already-extracted value -- the form// cCmpPropConst needs, whose folded constant is a value.Value rather than a// cLit node. Same Int/Temporal-only rule; see constI64.func constI64Val( value.Value) ( int64, , bool) {if , := .AsInt(); {return , false, true }if , , := .AsTemporal(); {return , true, true }return0, false, false}// propLitFast is the one-sided fast path: a typed i64 term compared to a folded// constant -- one column read and an integer compare per row. exact selects the// comparison value.Compare uses for the literal's kind (int64 for a Temporal,// float64 asNum for an Int); litOnLeft preserves operand order so// datetime(...) < m.creationDate is not silently inverted.func propLitFast( i64Term, int64, bool, func(int) bool, bool, cnode, *chickpeas.Snapshot) rowFast {returnfunc( *eval.Ctx, []value.Value, map[string]int) value.Value { , := .read()if == readFallback {returnceval(, , , , ) }if == readNull {returnvalue.Null() } , := , if { , = , }varint := trueif { = cmpI64(, ) } else { , = cmpFloat(float64(), float64()) }if ! {returnvalue.Null() }returnvalue.Bool(()) }}// cmpI64 is the exact int64 ordering value.Compare uses for temporal operands.func cmpI64(, int64) int {switch {case < :return -1case > :return1 }return0}// i64TermOf resolves a comparison side: a bare i64-column property read,// or one shifted by a constant integer or months-free duration. Constant// subtraction supports only a right-hand constant (Arith defines no// duration-minus-temporal, and integer const-minus-prop is a different// formula than the term encodes).func i64TermOf( cnode) (i64Term, bool) {switch n := .(type) {case *cProp:if .reader.node.kind != colI64 {returni64Term{}, false }returni64Term{slot: .slot, col: .reader.node.i64}, truecase *cBin:if .op != ast.OpAdd && .op != ast.OpSub {returni64Term{}, false } , , := splitPropLit(.l, .r)if == nil || ( && .op == ast.OpSub) || .reader.node.kind != colI64 {returni64Term{}, false } := i64Term{slot: .slot, col: .reader.node.i64}if , , , := .v.AsDuration(); {if != 0 {returni64Term{}, false }// Fold the months-free duration to a tick offset, checked: if the // constant itself overflows, decline the specialization so the // row falls to the tree, whose fixed ApplyDuration yields Null. , := mulChecked(, eval.MSPerDay)if ! {returni64Term{}, false } , := addChecked(, )if ! {returni64Term{}, false }if .op == ast.OpSub {if == math.MinInt64 {returni64Term{}, false } = - } .mode, .off = termDur, return , true }if , := .v.AsInt(); { .mode, .off, .sub = termInt, , .op == ast.OpSubreturn , true } }returni64Term{}, false}// splitPropLit splits a binary's operands into (property, literal); rev// marks the property on the right.func splitPropLit(, cnode) ( *cProp, *cLit, bool) {if , := .(*cProp); {if , := .(*cLit); {return , , false } }if , := .(*cProp); {if , := .(*cLit); {return , , true } }returnnil, nil, false}// slotVal mirrors cSlot evaluation: out-of-range reads are Null.func slotVal( []value.Value, int) value.Value {if >= 0 && < len() {return [] }returnvalue.Null()}
The pages are generated with Goldsv0.8.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.