Efficient Fail-Fast Dynamic Subtype Checking Rohan Padhye and Koushik Sen UC Berkeley VMIL 2019
Dynamic Subtype Checking S S obj = ….. T if (obj instance of T) { …. ? } obj 2
Dynamic Subtype Checking S S obj = new X() T if (obj instance of T) { …. ? } X X <: T ? obj 3
General Solution: Linear Search T S P X <: T ? A B X 4
General Solution: Linear Search T S P X <: T ? A B X 5
General Solution: Linear Search T S P X <: T ? A B X 6
General Solution: Linear Search T S P X <: T ? A B X 7
General Solution: Linear Search T S P X <: T A B Subtype test successful X 8
General Solution: Linear Search T S P X <: T ? A B X 9
General Solution: Linear Search T S P X <: T ? A B X 10
General Solution: Linear Search T S P X <: T ? A B X 11
General Solution: Linear Search T S P X <: T A B Subtype test fails X 12
Implementations must consider trade-offs Constant time? Constant space? (per-class) Supports multiple inheritance? Supports open hierarchies? Pick up to 3 13
Existing Schemes 14
Existing Schemes 15
Case Study: HotSpot JVM Metadata for C class A implements I1 { … } Primary Secondary class B extends A implements I2 { … } depth super super interface I5 extends I6, I7, I2 { … } 0 class C extends B implements I3, I4, I5 { … } object I1 1 I2 A object I1 2 I6 B 3 I7 C A I2 I6 I7 4 I5 5 I3 B I3 I4 I5 6 I4 7 C 16
Case Study: HotSpot JVM Metadata for H class A implements I1 { … } Primary Secondary class B extends A implements I2 { … } depth super super interface I5 extends I6, I7, I2 { … } 0 class C extends B implements I3, I4, I5 { … } object I1 1 I2 A class D extends C 2 I6 B class E extends D 3 I7 C class F extends E 4 I5 D class G extends F 5 I3 E class H extends G 6 I4 F 7 H G 17
Case Study: HotSpot JVM Metadata for C Metadata for H Primary Secondary Primary Secondary depth super super depth super super X <: C ? X.primary[3] == C? 0 0 object I1 object I1 1 1 I2 I2 A A X <: D ? 2 2 I6 I6 B B X.primary[4] == D? 3 3 I7 I7 C C 4 4 I5 I5 D X <: H ? X.secondary_check(H) 5 5 I3 I3 E 6 6 I4 I4 F X <: I5 ? 7 7 H G X.secondary_check(I5) 18
Case Study: HotSpot JVM Metadata for H Secondary super I1 X.secondary_check(T) := { I2 if (X.cache == T) return true; I6 if (X == T) return true; foreach S in X.secondaries { I7 if (S == T) { I5 X <: H ? X.cache = S X.secondary_check(H) I3 return true; } I4 X <: I5 ? } H X.secondary_check(I5) return false; } 19
Case Study: HotSpot JVM Observations: 1. Fast path for success 2. Failure == linear search X.secondary_check(T) := { if (X.cache == T) return true; if (X == T) return true; foreach S in X.secondaries { if (S == T) { X.cache = S return true; } } return false; } 20
Is this assumption always true? Are there workloads where dynamic subtype tests often fail? 21
Case Study: Scala’s Pattern Matching obj match { if (obj instanceof A) { A x = (A) obj; case x:A => x.method_on_A() x.method_on_A(); case y:B => y.method_on_B() } else if (obj instanceof B) { case z:C => z.method_on_C() B y = (B) obj; … Compile to JVM y.method_on_B(); } } else if (obj instanceof C) { C z = (C) obj; z.method_on_C(); } … 22
Profiling Scala’s Pattern Matching Small workload: scalac Hello.scala 47,597 instanceof tests 93% failed Large workload: sbt compile # builds scalac 3.1 billion instanceof tests 76% failed 45 million secondary scans 23
Cast Study: LLVM Compiler Infrastructure if (AllocationInst *AI = dyn_cast<AllocationInst>(Val)) { … } else if (CallInst *CI = dyn_cast<CallInst>(Val)) { … } else if … static bool isLoopInvariant(const Value *V, const Loop *L) { if (isa<Constant>(V) || isa<Argument>(V) || isa<GlobalValue>(V)) return true; // Otherwise, it must be an instruction... return !L->contains(cast<Instruction>(V)->getParent()); } 24
Cast Study: LLVM Compiler Infrastructure Inheritance diagram: class CallInst 25
Profiling the LLVM Compiler Infrastructure Small workload: clang++ Hello.cpp 5.5 million dyn_cast<T>/isa<T> operations 74% failed Large workload: clang selfie.c # 10K LoC 93.7 million dyn_cast<T>/isa<T> operations 78% failed 26
Takeaway : In some workloads… Dynamic subtype tests often fail But fast path is optimized for successful tests L 27
Can we fail fast when linear search is likely? (with no overhead for the current fast path) 28
Solution: Bl Bloom oom Filters 29
Fail-Fast using Bloom Filters α = {1, 3} S For each type T: α(T) := k distinct integers, chosen randomly from [1.. m ] β(T) := α(T) ∪ α(S 1 ) ∪ α(S 2 ) ∪ … ∪ α(S n ) A B where S 1 , S 2 , … S n are all the (transitive) super-types of T α = {7, 9} α = {11, 4} X Invariant: α = {5, 8} T <: S ⇒ α(S) ⊆ β(T) X <: T ? No T α = {1, 6} Y β = {1, 3, 4, 6, 7, 9, 11} α = {7, 11} Y <: T ? Maybe 30
Fail-Fast using Bloom Filters α = 0x000000000101 S For each type T: α(T) := compile_time_random(parity= k ) // m-bit integer β(T) := α(T) | α(S 1 ) | α(S 2 ) | …| α(S n ) A B where S 1 , S 2 , … S n are all the (transitive) super-types of T α = 0x010000001000 α = 0x000101000000 X Invariant: α = 0x000010010000 T <: S ⇒ α(S) & β(T) = α(S) X <: T ? No T α = 0x000000100001 Y β = 0x010101101101 α = 0x010001000000 Y <: T ? Maybe 31
Fail-Fast using Bloom Filters Worst-case only when false positive in bloom filters 32
Choosing parameters m = size of machine word k = parity ?? n = num. of transitive supertypes False positive rate: 33
Preliminary Evaluation (JVM HotSpot) 34
Preliminary Evaluation (JVM HotSpot) 35
Preliminary Evaluation (JVM HotSpot) obj match { if (obj instanceof A) { A x = (A) obj; case x:A => x.method_on_A() x.method_on_A(); case y:B => y.method_on_B() } else if (obj instanceof B) { case z:C => z.method_on_C() B y = (B) obj; … Compile to JVM y.method_on_B(); } } else if (obj instanceof C) { C z = (C) obj; z.method_on_C(); } … 36
Preliminary Evaluation (JVM HotSpot) Rewrite if T is a secondary type 37
Preliminary Evaluation (JVM HotSpot) 38
Preliminary Evaluation (JVM HotSpot) obj match { trait Base case x:A => x.method_on_A() trait A extends Base { def method_on_A(): Int } case y:B => y.method_on_B() trait B extends Base { def method_on_B(): Int } object objA extends traitA { … } object objB extends traitB { … } obj = chooseRandom({objA, objB}) } 39
Preliminary Evaluation (JVM HotSpot) obj match { trait Base case x:A => x.method_on_A() trait A extends Base { def method_on_A(): Int } case y:B => y.method_on_B() trait B extends Base { def method_on_B(): Int } case z:C => z.method_on_C() … case u:D => u.method_on_D() object objA extends traitA { … } case v:E => v.method_on_E() object objB extends traitB { … } … obj = chooseRandom({objA, objB, …}) } 40
Preliminary Evaluation (JVM HotSpot) obj match { trait Base case x:A => x.method_on_A() trait A extends Base { def method_on_A(): Int } case y:B => y.method_on_B() trait B extends Base { def method_on_B(): Int } case z:C => z.method_on_C() … case u:D => u.method_on_D() object objA extends traitA { … } case v:E => v.method_on_E() object objB extends traitB { … } … … case q:H => q.method_on_H() obj = chooseRandom({objA, objB, …}) } 41
Preliminary Evaluation (JVM HotSpot) obj match { trait Base extends N1, N2, N3, … N10 case x:A => x.method_on_A() trait A extends Base { def method_on_A(): Int } case y:B => y.method_on_B() trait B extends Base { def method_on_B(): Int } case z:C => z.method_on_C() … case u:D => u.method_on_D() object objA extends traitA { … } case v:E => v.method_on_E() object objB extends traitB { … } … … case q:H => q.method_on_H() obj = chooseRandom({objA, objB, …}) } 42
Summary Dynamic subtype tests often fail (in some workloads) Worst-case linear search occurs (in production VMs) Bloom filters can enable fail-fast refutations (high probability) expected constant time + constant space + multiple inheritance + open hierarchy 43
Recommend
More recommend