Add type bounds to StructLayout
commitf0994e5355608cdcfff1125c83cec4a14b0e38de
authorShaunak Kishore <kshaunak@fb.com>
Sun, 7 Nov 2021 03:32:30 +0000 (6 20:32 -0700)
committerFacebook GitHub Bot <facebook-github-bot@users.noreply.github.com>
Sun, 7 Nov 2021 03:33:41 +0000 (6 20:33 -0700)
tree99ff521a9c801be859be769e125f76a06fd0f89e
parent2b9d867ed59373ee5bab1450e1628f6916190aab
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
13 files changed:
hphp/doc/ir.specification
hphp/runtime/base/apc-bespoke.cpp
hphp/runtime/base/bespoke-runtime.cpp
hphp/runtime/base/bespoke-runtime.h
hphp/runtime/base/bespoke/layout-selection.cpp
hphp/runtime/base/bespoke/layout.cpp
hphp/runtime/base/bespoke/logging-profile.cpp
hphp/runtime/base/bespoke/logging-profile.h
hphp/runtime/base/bespoke/struct-dict.cpp
hphp/runtime/base/bespoke/struct-dict.h
hphp/runtime/vm/jit/array-layout.cpp
hphp/runtime/vm/jit/irgen-bespoke.cpp
hphp/runtime/vm/jit/irlower-bespoke.cpp