| 1 | /* |
| 2 | * Copyright (C) 2017 Apple Inc. All rights reserved. |
| 3 | * |
| 4 | * Redistribution and use in source and binary forms, with or without |
| 5 | * modification, are permitted provided that the following conditions |
| 6 | * are met: |
| 7 | * 1. Redistributions of source code must retain the above copyright |
| 8 | * notice, this list of conditions and the following disclaimer. |
| 9 | * 2. Redistributions in binary form must reproduce the above copyright |
| 10 | * notice, this list of conditions and the following disclaimer in the |
| 11 | * documentation and/or other materials provided with the distribution. |
| 12 | * |
| 13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| 14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| 17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| 20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| 21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 24 | */ |
| 25 | |
| 26 | #pragma once |
| 27 | |
| 28 | #if ENABLE(JIT) |
| 29 | |
| 30 | #include "JSFunctionInlines.h" |
| 31 | #include "ObjectPropertyConditionSet.h" |
| 32 | #include "PolyProtoAccessChain.h" |
| 33 | #include <wtf/Box.h> |
| 34 | #include <wtf/CommaPrinter.h> |
| 35 | |
| 36 | namespace JSC { |
| 37 | |
| 38 | struct AccessGenerationState; |
| 39 | |
| 40 | // An AccessCase describes one of the cases of a PolymorphicAccess. A PolymorphicAccess represents a |
| 41 | // planned (to generate in future) or generated stub for some inline cache. That stub contains fast |
| 42 | // path code for some finite number of fast cases, each described by an AccessCase object. |
| 43 | // |
| 44 | // An AccessCase object has a lifecycle that proceeds through several states. Note that the states |
| 45 | // of AccessCase have a lot to do with the global effect epoch (we'll say epoch for short). This is |
| 46 | // a simple way of reasoning about the state of the system outside this AccessCase. Any observable |
| 47 | // effect - like storing to a property, changing an object's structure, etc. - increments the epoch. |
| 48 | // The states are: |
| 49 | // |
| 50 | // Primordial: This is an AccessCase that was just allocated. It does not correspond to any actual |
| 51 | // code and it is not owned by any PolymorphicAccess. In this state, the AccessCase |
| 52 | // assumes that it is in the same epoch as when it was created. This is important |
| 53 | // because it may make claims about itself ("I represent a valid case so long as you |
| 54 | // register a watchpoint on this set") that could be contradicted by some outside |
| 55 | // effects (like firing and deleting the watchpoint set in question). This is also the |
| 56 | // state that an AccessCase is in when it is cloned (AccessCase::clone()). |
| 57 | // |
| 58 | // Committed: This happens as soon as some PolymorphicAccess takes ownership of this AccessCase. |
| 59 | // In this state, the AccessCase no longer assumes anything about the epoch. To |
| 60 | // accomplish this, PolymorphicAccess calls AccessCase::commit(). This must be done |
| 61 | // during the same epoch when the AccessCase was created, either by the client or by |
| 62 | // clone(). When created by the client, committing during the same epoch works because |
| 63 | // we can be sure that whatever watchpoint sets they spoke of are still valid. When |
| 64 | // created by clone(), we can be sure that the set is still valid because the original |
| 65 | // of the clone still has watchpoints on it. |
| 66 | // |
| 67 | // Generated: This is the state when the PolymorphicAccess generates code for this case by |
| 68 | // calling AccessCase::generate() or AccessCase::generateWithGuard(). At this point |
| 69 | // the case object will have some extra stuff in it, like possibly the CallLinkInfo |
| 70 | // object associated with the inline cache. |
| 71 | // FIXME: Moving into the Generated state should not mutate the AccessCase object or |
| 72 | // put more stuff into it. If we fix this, then we can get rid of AccessCase::clone(). |
| 73 | // https://bugs.webkit.org/show_bug.cgi?id=156456 |
| 74 | // |
| 75 | // An AccessCase may be destroyed while in any of these states. |
| 76 | // |
| 77 | // We will sometimes buffer committed AccessCases in the PolymorphicAccess object before generating |
| 78 | // code. This allows us to only regenerate once we've accumulated (hopefully) more than one new |
| 79 | // AccessCase. |
| 80 | class AccessCase { |
| 81 | WTF_MAKE_FAST_ALLOCATED; |
| 82 | public: |
| 83 | enum AccessType : uint8_t { |
| 84 | Load, |
| 85 | Transition, |
| 86 | Replace, |
| 87 | Miss, |
| 88 | GetGetter, |
| 89 | Getter, |
| 90 | Setter, |
| 91 | CustomValueGetter, |
| 92 | CustomAccessorGetter, |
| 93 | CustomValueSetter, |
| 94 | CustomAccessorSetter, |
| 95 | IntrinsicGetter, |
| 96 | InHit, |
| 97 | InMiss, |
| 98 | ArrayLength, |
| 99 | StringLength, |
| 100 | DirectArgumentsLength, |
| 101 | ScopedArgumentsLength, |
| 102 | ModuleNamespaceLoad, |
| 103 | InstanceOfHit, |
| 104 | InstanceOfMiss, |
| 105 | InstanceOfGeneric, |
| 106 | IndexedInt32Load, |
| 107 | IndexedDoubleLoad, |
| 108 | IndexedContiguousLoad, |
| 109 | IndexedArrayStorageLoad, |
| 110 | IndexedScopedArgumentsLoad, |
| 111 | IndexedDirectArgumentsLoad, |
| 112 | IndexedTypedArrayInt8Load, |
| 113 | IndexedTypedArrayUint8Load, |
| 114 | IndexedTypedArrayUint8ClampedLoad, |
| 115 | IndexedTypedArrayInt16Load, |
| 116 | IndexedTypedArrayUint16Load, |
| 117 | IndexedTypedArrayInt32Load, |
| 118 | IndexedTypedArrayUint32Load, |
| 119 | IndexedTypedArrayFloat32Load, |
| 120 | IndexedTypedArrayFloat64Load, |
| 121 | IndexedStringLoad |
| 122 | }; |
| 123 | |
| 124 | enum State : uint8_t { |
| 125 | Primordial, |
| 126 | Committed, |
| 127 | Generated |
| 128 | }; |
| 129 | |
| 130 | template<typename T> |
| 131 | T& as() { return *static_cast<T*>(this); } |
| 132 | |
| 133 | template<typename T> |
| 134 | const T& as() const { return *static_cast<const T*>(this); } |
| 135 | |
| 136 | |
| 137 | template<typename AccessCaseType, typename... Arguments> |
| 138 | static std::unique_ptr<AccessCaseType> create(Arguments... arguments) |
| 139 | { |
| 140 | return std::unique_ptr<AccessCaseType>(new AccessCaseType(arguments...)); |
| 141 | } |
| 142 | |
| 143 | static std::unique_ptr<AccessCase> create(VM&, JSCell* owner, AccessType, const Identifier&, PropertyOffset = invalidOffset, |
| 144 | Structure* = nullptr, const ObjectPropertyConditionSet& = ObjectPropertyConditionSet(), std::unique_ptr<PolyProtoAccessChain> = nullptr); |
| 145 | |
| 146 | // This create method should be used for transitions. |
| 147 | static std::unique_ptr<AccessCase> create(VM&, JSCell* owner, const Identifier&, PropertyOffset, Structure* oldStructure, |
| 148 | Structure* newStructure, const ObjectPropertyConditionSet&, std::unique_ptr<PolyProtoAccessChain>); |
| 149 | |
| 150 | static std::unique_ptr<AccessCase> fromStructureStubInfo(VM&, JSCell* owner, const Identifier&, StructureStubInfo&); |
| 151 | |
| 152 | AccessType type() const { return m_type; } |
| 153 | State state() const { return m_state; } |
| 154 | PropertyOffset offset() const { return m_offset; } |
| 155 | |
| 156 | Structure* structure() const |
| 157 | { |
| 158 | if (m_type == Transition) |
| 159 | return m_structure->previousID(); |
| 160 | return m_structure.get(); |
| 161 | } |
| 162 | bool guardedByStructureCheck(const StructureStubInfo&) const; |
| 163 | |
| 164 | Structure* newStructure() const |
| 165 | { |
| 166 | ASSERT(m_type == Transition); |
| 167 | return m_structure.get(); |
| 168 | } |
| 169 | |
| 170 | ObjectPropertyConditionSet conditionSet() const { return m_conditionSet; } |
| 171 | |
| 172 | virtual bool hasAlternateBase() const; |
| 173 | virtual JSObject* alternateBase() const; |
| 174 | |
| 175 | virtual WatchpointSet* additionalSet() const { return nullptr; } |
| 176 | bool viaProxy() const { return m_viaProxy; } |
| 177 | |
| 178 | // If you supply the optional vector, this will append the set of cells that this will need to keep alive |
| 179 | // past the call. |
| 180 | bool doesCalls(Vector<JSCell*>* cellsToMark = nullptr) const; |
| 181 | |
| 182 | bool isGetter() const |
| 183 | { |
| 184 | switch (type()) { |
| 185 | case Getter: |
| 186 | case CustomValueGetter: |
| 187 | case CustomAccessorGetter: |
| 188 | return true; |
| 189 | default: |
| 190 | return false; |
| 191 | } |
| 192 | } |
| 193 | |
| 194 | bool isAccessor() const { return isGetter() || type() == Setter; } |
| 195 | |
| 196 | // Is it still possible for this case to ever be taken? Must call this as a prerequisite for |
| 197 | // calling generate() and friends. If this returns true, then you can call generate(). If |
| 198 | // this returns false, then generate() will crash. You must call generate() in the same epoch |
| 199 | // as when you called couldStillSucceed(). |
| 200 | bool couldStillSucceed() const; |
| 201 | |
| 202 | // If this method returns true, then it's a good idea to remove 'other' from the access once 'this' |
| 203 | // is added. This method assumes that in case of contradictions, 'this' represents a newer, and so |
| 204 | // more useful, truth. This method can be conservative; it will return false when it doubt. |
| 205 | bool canReplace(const AccessCase& other) const; |
| 206 | |
| 207 | void dump(PrintStream& out) const; |
| 208 | virtual void dumpImpl(PrintStream&, CommaPrinter&) const { } |
| 209 | |
| 210 | virtual ~AccessCase(); |
| 211 | |
| 212 | bool usesPolyProto() const |
| 213 | { |
| 214 | return !!m_polyProtoAccessChain; |
| 215 | } |
| 216 | |
| 217 | bool requiresIdentifierNameMatch() const; |
| 218 | bool requiresInt32PropertyCheck() const; |
| 219 | bool needsScratchFPR() const; |
| 220 | |
| 221 | static TypedArrayType toTypedArrayType(AccessType); |
| 222 | |
| 223 | UniquedStringImpl* uid() const { return m_identifier->impl(); } |
| 224 | Box<Identifier> identifier() const { return m_identifier; } |
| 225 | |
| 226 | |
| 227 | #if !ASSERT_DISABLED |
| 228 | void checkConsistency(StructureStubInfo&); |
| 229 | #else |
| 230 | ALWAYS_INLINE void checkConsistency(StructureStubInfo&) { } |
| 231 | #endif |
| 232 | |
| 233 | protected: |
| 234 | AccessCase(VM&, JSCell* owner, AccessType, const Identifier&, PropertyOffset, Structure*, const ObjectPropertyConditionSet&, std::unique_ptr<PolyProtoAccessChain>); |
| 235 | AccessCase(AccessCase&&) = default; |
| 236 | AccessCase(const AccessCase& other) |
| 237 | : m_type(other.m_type) |
| 238 | , m_state(other.m_state) |
| 239 | , m_viaProxy(other.m_viaProxy) |
| 240 | , m_offset(other.m_offset) |
| 241 | , m_structure(other.m_structure) |
| 242 | , m_conditionSet(other.m_conditionSet) |
| 243 | , m_identifier(other.m_identifier) |
| 244 | { |
| 245 | if (other.m_polyProtoAccessChain) |
| 246 | m_polyProtoAccessChain = other.m_polyProtoAccessChain->clone(); |
| 247 | } |
| 248 | |
| 249 | AccessCase& operator=(const AccessCase&) = delete; |
| 250 | void resetState() { m_state = Primordial; } |
| 251 | |
| 252 | private: |
| 253 | friend class CodeBlock; |
| 254 | friend class PolymorphicAccess; |
| 255 | |
| 256 | template<typename Functor> |
| 257 | void forEachDependentCell(const Functor&) const; |
| 258 | |
| 259 | bool visitWeak(VM&) const; |
| 260 | bool propagateTransitions(SlotVisitor&) const; |
| 261 | |
| 262 | // FIXME: This only exists because of how AccessCase puts post-generation things into itself. |
| 263 | // https://bugs.webkit.org/show_bug.cgi?id=156456 |
| 264 | virtual std::unique_ptr<AccessCase> clone() const; |
| 265 | |
| 266 | // Perform any action that must be performed before the end of the epoch in which the case |
| 267 | // was created. Returns a set of watchpoint sets that will need to be watched. |
| 268 | Vector<WatchpointSet*, 2> commit(VM&); |
| 269 | |
| 270 | // Fall through on success. Two kinds of failures are supported: fall-through, which means that we |
| 271 | // should try a different case; and failure, which means that this was the right case but it needs |
| 272 | // help from the slow path. |
| 273 | void generateWithGuard(AccessGenerationState&, MacroAssembler::JumpList& fallThrough); |
| 274 | |
| 275 | // Fall through on success, add a jump to the failure list on failure. |
| 276 | void generate(AccessGenerationState&); |
| 277 | |
| 278 | void generateImpl(AccessGenerationState&); |
| 279 | |
| 280 | bool guardedByStructureCheckSkippingConstantIdentifierCheck() const; |
| 281 | |
| 282 | AccessType m_type; |
| 283 | State m_state { Primordial }; |
| 284 | protected: |
| 285 | // m_viaProxy is true only if the instance inherits (or it is) ProxyableAccessCase. |
| 286 | // We put this value here instead of ProxyableAccessCase to reduce the size of ProxyableAccessCase and its |
| 287 | // derived classes, which are super frequently allocated. |
| 288 | bool m_viaProxy { false }; |
| 289 | private: |
| 290 | PropertyOffset m_offset; |
| 291 | |
| 292 | // Usually this is the structure that we expect the base object to have. But, this is the *new* |
| 293 | // structure for a transition and we rely on the fact that it has a strong reference to the old |
| 294 | // structure. For proxies, this is the structure of the object behind the proxy. |
| 295 | WriteBarrier<Structure> m_structure; |
| 296 | |
| 297 | ObjectPropertyConditionSet m_conditionSet; |
| 298 | |
| 299 | std::unique_ptr<PolyProtoAccessChain> m_polyProtoAccessChain; |
| 300 | |
| 301 | Box<Identifier> m_identifier; // We use this indirection so the concurrent compiler can concurrently ref this Box. |
| 302 | }; |
| 303 | |
| 304 | } // namespace JSC |
| 305 | |
| 306 | #endif |
| 307 | |