Post on 17-Oct-2020
transcript
Efficient Fail-Fast Dynamic Subtype CheckingRohan Padhye and Koushik Sen
UC Berkeley
VMIL 2019
Dynamic Subtype Checking
2
S obj = …..
if (obj instance of T) {….
}
obj
S
T
?
Dynamic Subtype Checking
3
S obj = new X()
if (obj instance of T) {….
}
S
obj
X
T
?
X <: T ?
General Solution: Linear Search
4
S
X
A B
T P
X <: T ?
General Solution: Linear Search
5
S
X
A B
T P
X <: T ?
General Solution: Linear Search
6
S
X
A B
T P
X <: T ?
General Solution: Linear Search
7
S
X
A B
T P
X <: T ?
General Solution: Linear Search
8
S
X
A B
T P
Subtype test successful
X <: T
General Solution: Linear Search
9
S
X
A B
T P
X <: T ?
General Solution: Linear Search
10
S
X
A B
T P
X <: T ?
General Solution: Linear Search
11
S
X
A B
T P
X <: T ?
General Solution: Linear Search
12
S
X
A B
T P
Subtype test fails
X <: T
Implementations must consider trade-offs
Constant time?
Constant space? (per-class)
Supports multiple inheritance?
Supports open hierarchies?
13
Pick up to 3
Existing Schemes
14
Existing Schemes
15
Case Study: HotSpot JVM
class A implements I1 { … }
class B extends A implements I2 { … }
interface I5 extends I6, I7, I2 { … }class C extends B implements I3, I4, I5 { … }
16
Primary
super
I1
I2
I6
I7
I5
I3
I4
Secondary
Metadata for C
depth super
0 object
1 A
2 B
3 C
4
5
6
7
I3
C
I4 I5
I6 I7
I1
I2A
B
object
Case Study: HotSpot JVM
class A implements I1 { … }
class B extends A implements I2 { … }
interface I5 extends I6, I7, I2 { … }class C extends B implements I3, I4, I5 { … }
class D extends Cclass E extends D
class F extends E
class G extends Fclass H extends G
17
depth super
0 object
1 A
2 B
3 C
4 D
5 E
6 F
7 G
super
I1
I2
I6
I7
I5
I3
I4
H
Primary Secondary
Metadata for H
Case Study: HotSpot JVM
18
depth super
0 object
1 A
2 B
3 C
4 D
5 E
6 F
7 G
super
I1
I2
I6
I7
I5
I3
I4
H
Primary Secondary
Metadata for H
Primary
super
I1
I2
I6
I7
I5
I3
I4
Secondary
Metadata for C
depth super
0 object
1 A
2 B
3 C
4
5
6
7
X <: C ?
X <: D ?
X <: H ?
X <: I5 ?
X.primary[3] == C?
X.primary[4] == D?
X.secondary_check(H)
X.secondary_check(I5)
Case Study: HotSpot JVM
19
X <: H ?
X <: I5 ?
X.secondary_check(H)
X.secondary_check(I5)
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 = Sreturn true;
}}return false;
}
super
I1
I2
I6
I7
I5
I3
I4
H
Secondary
Metadata for H
Case Study: HotSpot JVM
20
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 = Sreturn true;
}}return false;
}
Observations:1. Fast path for success
2. Failure == linear search
Is this assumption always true?Are there workloads where dynamic subtype tests often fail?
21
Case Study: Scala’s Pattern Matching
obj match {case x:A => x.method_on_A()case y:B => y.method_on_B()case z:C => z.method_on_C()…
}
if (obj instanceof A) {A x = (A) obj;x.method_on_A();
} else if (obj instanceof B) {B y = (B) obj;y.method_on_B();
} else if (obj instanceof C) {C z = (C) obj;z.method_on_C();
} …
22
Compile to JVM
Profiling Scala’s Pattern Matching
Small workload: scalac Hello.scala47,597 instanceof tests
93% failed
Large workload: sbt compile # builds scalac3.1 billion instanceof tests
76% failed45 million secondary scans
23
Cast Study: LLVM Compiler Infrastructure
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
if (AllocationInst *AI = dyn_cast<AllocationInst>(Val)) { …
} else if (CallInst *CI = dyn_cast<CallInst>(Val)) { …
} else if …
Cast Study: LLVM Compiler Infrastructure
25
Inheritance diagram: class CallInst
Profiling the LLVM Compiler Infrastructure
Small workload: clang++ Hello.cpp5.5 million dyn_cast<T>/isa<T> operations
74% failed
Large workload: clang selfie.c # 10K LoC93.7 million dyn_cast<T>/isa<T> operations
78% failed
26
Dynamic subtype tests often failBut fast path is optimized for successful tests L
27
Takeaway: In some workloads…
Can we fail fast when linear search is likely?(with no overhead for the current fast path)
28
Solution: Bloom Filters
29
Fail-Fast using Bloom Filters
For each type T:α(T) := k distinct integers, chosen randomly from [1..m]β(T) := α(T) ∪ α(S1) ∪ α(S2) ∪ … ∪ α(Sn)
where S1, S2, … Sn are all the (transitive) super-types of T
Invariant:T <: S ⇒ α(S) ⊆ β(T)
30
S
T
A B
α = {1, 3}
α = {7, 9} α = {11, 4}
X
α = {5, 8}
α = {1, 6}β = {1, 3, 4, 6, 7, 9, 11}Y
α = {7, 11}
X <: T ? No
Y <: T ? Maybe
Fail-Fast using Bloom Filters
For each type T:α(T) := compile_time_random(parity=k) // m-bit integerβ(T) := α(T) | α(S1) | α(S2) | …| α(Sn)
where S1, S2, … Sn are all the (transitive) super-types of T
Invariant:T <: S ⇒ α(S) & β(T) = α(S)
31
S
T
A B
α = 0x000000000101
α = 0x000101000000 α = 0x010000001000
X
α = 0x000010010000
α = 0x000000100001 β = 0x010101101101Y
α = 0x010001000000
X <: T ? No
Y <: T ? Maybe
Fail-Fast using Bloom Filters
32
Worst-case only when false positive in bloom filters
Choosing parameters
m = size of machine wordk = parity ??n = num. of transitive supertypes
33
False positive rate:
Preliminary Evaluation (JVM HotSpot)
34
Preliminary Evaluation (JVM HotSpot)
35
Preliminary Evaluation (JVM HotSpot)
obj match {case x:A => x.method_on_A()case y:B => y.method_on_B()case z:C => z.method_on_C()…
}
if (obj instanceof A) {A x = (A) obj;x.method_on_A();
} else if (obj instanceof B) {B y = (B) obj;y.method_on_B();
} else if (obj instanceof C) {C z = (C) obj;z.method_on_C();
} …
36
Compile to JVM
Preliminary Evaluation (JVM HotSpot)
37
Rewrite if T is a secondary type
Preliminary Evaluation (JVM HotSpot)
38
Preliminary Evaluation (JVM HotSpot)
39
trait Basetrait A extends Base { def method_on_A(): Int }trait B extends Base { def method_on_B(): Int }
object objA extends traitA { … }object objB extends traitB { … }
obj = chooseRandom({objA, objB})
obj match {
case x:A => x.method_on_A()
case y:B => y.method_on_B()
}
Preliminary Evaluation (JVM HotSpot)
40
trait Basetrait A extends Base { def method_on_A(): Int }trait B extends Base { def method_on_B(): Int }…object objA extends traitA { … }object objB extends traitB { … }…obj = chooseRandom({objA, objB, …})
obj match {
case x:A => x.method_on_A()
case y:B => y.method_on_B()
case z:C => z.method_on_C()
case u:D => u.method_on_D()
case v:E => v.method_on_E()
}
Preliminary Evaluation (JVM HotSpot)
41
obj match {
case x:A => x.method_on_A()
case y:B => y.method_on_B()
case z:C => z.method_on_C()
case u:D => u.method_on_D()
case v:E => v.method_on_E()
…
case q:H => q.method_on_H()
}
trait Basetrait A extends Base { def method_on_A(): Int }trait B extends Base { def method_on_B(): Int }…object objA extends traitA { … }object objB extends traitB { … }…obj = chooseRandom({objA, objB, …})
Preliminary Evaluation (JVM HotSpot)
42
obj match {
case x:A => x.method_on_A()
case y:B => y.method_on_B()
case z:C => z.method_on_C()
case u:D => u.method_on_D()
case v:E => v.method_on_E()
…
case q:H => q.method_on_H()
}
trait Base extends N1, N2, N3, … N10trait A extends Base { def method_on_A(): Int }trait B extends Base { def method_on_B(): Int }…object objA extends traitA { … }object objB extends traitB { … }…obj = chooseRandom({objA, objB, …})
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