Add type bounds to StructLayout
Summary:
In typical Hack code, many fields of shape types will have a type bound that's more specific than "mixed". Given that we back many shapes with StructDict, that presents us with an opportunity: we can introduce a per-field type bound for this layout, check the bound on writes, and assume the bound on reads. Since reads are more common than writes - especially in the case of APC entries, but also in general - and since we often have type information at write time anyway, these restrictions should yield a perf win.
We do not support the full range of Hack types as bounds, as it's slow to test a general Hack type. Instead, we support a subset of bounds that can be checked *entirely* by a simple test-jnz on the 1-byte DataType, so all checks are done with uniform, efficient code. We support these bounds:
1. Any specific DataType. The mask is simply ~type, because of our encoding in
D26020606 (https://github.com/facebook/hhvm/commit/
03023afa4e886ba7928355f2d21dc751e2e1251f).
2. "Uncounted" - a union of scalars and uncounted values. The mask is kRefCountedBit.
3. "Cell" - the trivial bound, allowing any value. The mask is 0.
We have to split operations on StructDict into operations that check these bounds and operations that assume them. In general, "virtual" methods that are part of the ArrayData interface must check type bounds, while any specialized method that's specific to StructDict can assume them:
- SetStrMove and MakeFromVanilla check type bounds.
- SetStrInSlot, SetStrInSlotInPlace, and NewStructDict assume them.
When we make use of specialized operations from the JIT, we have to check the bounds beforehand using CheckType and related IR instructions. In particular, we modify irgen-bespoke's handling of set ops to generically emit a set for the profiled layout, and as a fallback, when we simplify a BespokeSet op to StructDictSet, we also check and branch at irlower time if the type bound is not already known to be satisfied. Constructors are simpler, because there's no possibility of picking up more layout information during optimization passes, so we emit bounds for a NewStructDict bytecode at irgen (with a side exit if any fail).
Finally, we also have to patch layout selection, the APC integration, and the RuntimeStruct integration. Our layout selection algorithm just takes the union of all types stored to the field during profiling, and relaxes it to the tightest supported bound from the list above. For APC, we introduce a new pair of ops profiled during APC stores, then use these ops to decide on type bounds. Finally, for RuntimeStruct, we pull in the 1-byte type-mask and do the test in the struct setter, so that we can still make use of the optimized SetStrInSlotInPlace helper.
Reviewed By: colavitam
Differential Revision:
D32076269
fbshipit-source-id:
c620f6a87b3d0bb81d315d8003e0b9f5b9aa969a