]>
Commit | Line | Data |
---|---|---|
6fe7ccc8 | 1 | /* |
81345200 | 2 | * Copyright (C) 2012, 2013, 2014 Apple Inc. All rights reserved. |
6fe7ccc8 A |
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 | #include "config.h" | |
27 | #include "PutByIdStatus.h" | |
28 | ||
ed1e77d3 | 29 | #include "AccessorCallJITStubRoutine.h" |
6fe7ccc8 | 30 | #include "CodeBlock.h" |
ed1e77d3 | 31 | #include "ComplexGetStatus.h" |
93a37866 | 32 | #include "LLIntData.h" |
6fe7ccc8 | 33 | #include "LowLevelInterpreter.h" |
81345200 A |
34 | #include "JSCInlines.h" |
35 | #include "PolymorphicPutByIdList.h" | |
6fe7ccc8 A |
36 | #include "Structure.h" |
37 | #include "StructureChain.h" | |
81345200 | 38 | #include <wtf/ListDump.h> |
6fe7ccc8 A |
39 | |
40 | namespace JSC { | |
41 | ||
81345200 A |
42 | bool PutByIdStatus::appendVariant(const PutByIdVariant& variant) |
43 | { | |
44 | for (unsigned i = 0; i < m_variants.size(); ++i) { | |
ed1e77d3 A |
45 | if (m_variants[i].attemptToMerge(variant)) |
46 | return true; | |
47 | } | |
48 | for (unsigned i = 0; i < m_variants.size(); ++i) { | |
49 | if (m_variants[i].oldStructure().overlaps(variant.oldStructure())) | |
81345200 A |
50 | return false; |
51 | } | |
52 | m_variants.append(variant); | |
53 | return true; | |
54 | } | |
55 | ||
56 | #if ENABLE(DFG_JIT) | |
ed1e77d3 | 57 | bool PutByIdStatus::hasExitSite(const ConcurrentJITLocker& locker, CodeBlock* profiledBlock, unsigned bytecodeIndex) |
81345200 | 58 | { |
ed1e77d3 A |
59 | return profiledBlock->hasExitSite(locker, DFG::FrequentExitSite(bytecodeIndex, BadCache)) |
60 | || profiledBlock->hasExitSite(locker, DFG::FrequentExitSite(bytecodeIndex, BadConstantCache)); | |
81345200 A |
61 | |
62 | } | |
63 | #endif | |
64 | ||
ed1e77d3 | 65 | PutByIdStatus PutByIdStatus::computeFromLLInt(CodeBlock* profiledBlock, unsigned bytecodeIndex, UniquedStringImpl* uid) |
6fe7ccc8 A |
66 | { |
67 | UNUSED_PARAM(profiledBlock); | |
68 | UNUSED_PARAM(bytecodeIndex); | |
81345200 | 69 | UNUSED_PARAM(uid); |
6fe7ccc8 A |
70 | Instruction* instruction = profiledBlock->instructions().begin() + bytecodeIndex; |
71 | ||
72 | Structure* structure = instruction[4].u.structure.get(); | |
73 | if (!structure) | |
81345200 | 74 | return PutByIdStatus(NoInformation); |
6fe7ccc8 | 75 | |
81345200 A |
76 | if (instruction[0].u.opcode == LLInt::getOpcode(op_put_by_id) |
77 | || instruction[0].u.opcode == LLInt::getOpcode(op_put_by_id_out_of_line)) { | |
ed1e77d3 | 78 | PropertyOffset offset = structure->getConcurrently(uid); |
93a37866 | 79 | if (!isValidOffset(offset)) |
81345200 | 80 | return PutByIdStatus(NoInformation); |
6fe7ccc8 | 81 | |
81345200 | 82 | return PutByIdVariant::replace(structure, offset); |
6fe7ccc8 A |
83 | } |
84 | ||
93a37866 A |
85 | ASSERT(structure->transitionWatchpointSetHasBeenInvalidated()); |
86 | ||
81345200 A |
87 | ASSERT(instruction[0].u.opcode == LLInt::getOpcode(op_put_by_id_transition_direct) |
88 | || instruction[0].u.opcode == LLInt::getOpcode(op_put_by_id_transition_normal) | |
89 | || instruction[0].u.opcode == LLInt::getOpcode(op_put_by_id_transition_direct_out_of_line) | |
90 | || instruction[0].u.opcode == LLInt::getOpcode(op_put_by_id_transition_normal_out_of_line)); | |
6fe7ccc8 A |
91 | |
92 | Structure* newStructure = instruction[6].u.structure.get(); | |
93 | StructureChain* chain = instruction[7].u.structureChain.get(); | |
94 | ASSERT(newStructure); | |
95 | ASSERT(chain); | |
96 | ||
ed1e77d3 | 97 | PropertyOffset offset = newStructure->getConcurrently(uid); |
93a37866 | 98 | if (!isValidOffset(offset)) |
81345200 | 99 | return PutByIdStatus(NoInformation); |
6fe7ccc8 | 100 | |
ed1e77d3 A |
101 | RefPtr<IntendedStructureChain> intendedChain; |
102 | if (chain) | |
103 | intendedChain = adoptRef(new IntendedStructureChain(profiledBlock, structure, chain)); | |
104 | ||
105 | return PutByIdVariant::transition(structure, newStructure, intendedChain.get(), offset); | |
6fe7ccc8 A |
106 | } |
107 | ||
ed1e77d3 | 108 | PutByIdStatus PutByIdStatus::computeFor(CodeBlock* profiledBlock, StubInfoMap& map, unsigned bytecodeIndex, UniquedStringImpl* uid) |
6fe7ccc8 | 109 | { |
81345200 A |
110 | ConcurrentJITLocker locker(profiledBlock->m_lock); |
111 | ||
6fe7ccc8 A |
112 | UNUSED_PARAM(profiledBlock); |
113 | UNUSED_PARAM(bytecodeIndex); | |
81345200 A |
114 | UNUSED_PARAM(uid); |
115 | #if ENABLE(DFG_JIT) | |
ed1e77d3 | 116 | if (hasExitSite(locker, profiledBlock, bytecodeIndex)) |
81345200 | 117 | return PutByIdStatus(TakesSlowPath); |
6fe7ccc8 | 118 | |
81345200 | 119 | StructureStubInfo* stubInfo = map.get(CodeOrigin(bytecodeIndex)); |
ed1e77d3 A |
120 | PutByIdStatus result = computeForStubInfo( |
121 | locker, profiledBlock, stubInfo, uid, | |
122 | CallLinkStatus::computeExitSiteData(locker, profiledBlock, bytecodeIndex)); | |
81345200 A |
123 | if (!result) |
124 | return computeFromLLInt(profiledBlock, bytecodeIndex, uid); | |
6fe7ccc8 | 125 | |
81345200 A |
126 | return result; |
127 | #else // ENABLE(JIT) | |
128 | UNUSED_PARAM(map); | |
129 | return PutByIdStatus(NoInformation); | |
130 | #endif // ENABLE(JIT) | |
131 | } | |
132 | ||
133 | #if ENABLE(JIT) | |
ed1e77d3 A |
134 | PutByIdStatus PutByIdStatus::computeForStubInfo( |
135 | const ConcurrentJITLocker& locker, CodeBlock* profiledBlock, StructureStubInfo* stubInfo, | |
136 | UniquedStringImpl* uid, CallLinkStatus::ExitSiteData callExitSiteData) | |
81345200 | 137 | { |
ed1e77d3 | 138 | if (!stubInfo) |
81345200 | 139 | return PutByIdStatus(); |
6fe7ccc8 | 140 | |
ed1e77d3 | 141 | if (stubInfo->tookSlowPath) |
81345200 | 142 | return PutByIdStatus(TakesSlowPath); |
ed1e77d3 A |
143 | |
144 | if (!stubInfo->seen) | |
145 | return PutByIdStatus(); | |
146 | ||
81345200 | 147 | switch (stubInfo->accessType) { |
6fe7ccc8 | 148 | case access_unset: |
93a37866 | 149 | // If the JIT saw it but didn't optimize it, then assume that this takes slow path. |
81345200 | 150 | return PutByIdStatus(TakesSlowPath); |
6fe7ccc8 A |
151 | |
152 | case access_put_by_id_replace: { | |
81345200 | 153 | PropertyOffset offset = |
ed1e77d3 | 154 | stubInfo->u.putByIdReplace.baseObjectStructure->getConcurrently(uid); |
93a37866 | 155 | if (isValidOffset(offset)) { |
81345200 A |
156 | return PutByIdVariant::replace( |
157 | stubInfo->u.putByIdReplace.baseObjectStructure.get(), offset); | |
6fe7ccc8 | 158 | } |
81345200 | 159 | return PutByIdStatus(TakesSlowPath); |
6fe7ccc8 A |
160 | } |
161 | ||
162 | case access_put_by_id_transition_normal: | |
163 | case access_put_by_id_transition_direct: { | |
81345200 A |
164 | ASSERT(stubInfo->u.putByIdTransition.previousStructure->transitionWatchpointSetHasBeenInvalidated()); |
165 | PropertyOffset offset = | |
ed1e77d3 | 166 | stubInfo->u.putByIdTransition.structure->getConcurrently(uid); |
93a37866 | 167 | if (isValidOffset(offset)) { |
ed1e77d3 A |
168 | RefPtr<IntendedStructureChain> chain; |
169 | if (stubInfo->u.putByIdTransition.chain) { | |
170 | chain = adoptRef(new IntendedStructureChain( | |
171 | profiledBlock, stubInfo->u.putByIdTransition.previousStructure.get(), | |
172 | stubInfo->u.putByIdTransition.chain.get())); | |
173 | } | |
81345200 A |
174 | return PutByIdVariant::transition( |
175 | stubInfo->u.putByIdTransition.previousStructure.get(), | |
176 | stubInfo->u.putByIdTransition.structure.get(), | |
ed1e77d3 | 177 | chain.get(), offset); |
6fe7ccc8 | 178 | } |
81345200 A |
179 | return PutByIdStatus(TakesSlowPath); |
180 | } | |
181 | ||
182 | case access_put_by_id_list: { | |
183 | PolymorphicPutByIdList* list = stubInfo->u.putByIdList.list; | |
184 | ||
185 | PutByIdStatus result; | |
186 | result.m_state = Simple; | |
187 | ||
ed1e77d3 A |
188 | State slowPathState = TakesSlowPath; |
189 | for (unsigned i = 0; i < list->size(); ++i) { | |
190 | const PutByIdAccess& access = list->at(i); | |
191 | ||
192 | switch (access.type()) { | |
193 | case PutByIdAccess::Setter: | |
194 | case PutByIdAccess::CustomSetter: | |
195 | slowPathState = MakesCalls; | |
196 | break; | |
197 | default: | |
198 | break; | |
199 | } | |
200 | } | |
201 | ||
81345200 A |
202 | for (unsigned i = 0; i < list->size(); ++i) { |
203 | const PutByIdAccess& access = list->at(i); | |
204 | ||
ed1e77d3 A |
205 | PutByIdVariant variant; |
206 | ||
81345200 A |
207 | switch (access.type()) { |
208 | case PutByIdAccess::Replace: { | |
209 | Structure* structure = access.structure(); | |
ed1e77d3 | 210 | PropertyOffset offset = structure->getConcurrently(uid); |
81345200 | 211 | if (!isValidOffset(offset)) |
ed1e77d3 A |
212 | return PutByIdStatus(slowPathState); |
213 | variant = PutByIdVariant::replace(structure, offset); | |
81345200 A |
214 | break; |
215 | } | |
216 | ||
217 | case PutByIdAccess::Transition: { | |
218 | PropertyOffset offset = | |
ed1e77d3 | 219 | access.newStructure()->getConcurrently(uid); |
81345200 | 220 | if (!isValidOffset(offset)) |
ed1e77d3 A |
221 | return PutByIdStatus(slowPathState); |
222 | RefPtr<IntendedStructureChain> chain; | |
223 | if (access.chain()) { | |
224 | chain = adoptRef(new IntendedStructureChain( | |
225 | profiledBlock, access.oldStructure(), access.chain())); | |
226 | if (!chain->isStillValid()) | |
227 | continue; | |
228 | } | |
229 | variant = PutByIdVariant::transition( | |
230 | access.oldStructure(), access.newStructure(), chain.get(), offset); | |
81345200 A |
231 | break; |
232 | } | |
ed1e77d3 A |
233 | |
234 | case PutByIdAccess::Setter: { | |
235 | Structure* structure = access.structure(); | |
236 | ||
237 | ComplexGetStatus complexGetStatus = ComplexGetStatus::computeFor( | |
238 | profiledBlock, structure, access.chain(), access.chainCount(), uid); | |
239 | ||
240 | switch (complexGetStatus.kind()) { | |
241 | case ComplexGetStatus::ShouldSkip: | |
242 | continue; | |
243 | ||
244 | case ComplexGetStatus::TakesSlowPath: | |
245 | return PutByIdStatus(slowPathState); | |
246 | ||
247 | case ComplexGetStatus::Inlineable: { | |
248 | AccessorCallJITStubRoutine* stub = static_cast<AccessorCallJITStubRoutine*>( | |
249 | access.stubRoutine()); | |
250 | std::unique_ptr<CallLinkStatus> callLinkStatus = | |
251 | std::make_unique<CallLinkStatus>( | |
252 | CallLinkStatus::computeFor( | |
253 | locker, profiledBlock, *stub->m_callLinkInfo, callExitSiteData)); | |
254 | ||
255 | variant = PutByIdVariant::setter( | |
256 | structure, complexGetStatus.offset(), complexGetStatus.chain(), | |
257 | WTF::move(callLinkStatus)); | |
258 | } } | |
259 | break; | |
260 | } | |
261 | ||
81345200 A |
262 | case PutByIdAccess::CustomSetter: |
263 | return PutByIdStatus(MakesCalls); | |
264 | ||
265 | default: | |
ed1e77d3 | 266 | return PutByIdStatus(slowPathState); |
81345200 | 267 | } |
ed1e77d3 A |
268 | |
269 | if (!result.appendVariant(variant)) | |
270 | return PutByIdStatus(slowPathState); | |
81345200 A |
271 | } |
272 | ||
273 | return result; | |
6fe7ccc8 A |
274 | } |
275 | ||
276 | default: | |
81345200 | 277 | return PutByIdStatus(TakesSlowPath); |
6fe7ccc8 | 278 | } |
81345200 A |
279 | } |
280 | #endif | |
281 | ||
ed1e77d3 | 282 | PutByIdStatus PutByIdStatus::computeFor(CodeBlock* baselineBlock, CodeBlock* dfgBlock, StubInfoMap& baselineMap, StubInfoMap& dfgMap, CodeOrigin codeOrigin, UniquedStringImpl* uid) |
81345200 A |
283 | { |
284 | #if ENABLE(DFG_JIT) | |
285 | if (dfgBlock) { | |
ed1e77d3 | 286 | CallLinkStatus::ExitSiteData exitSiteData; |
81345200 A |
287 | { |
288 | ConcurrentJITLocker locker(baselineBlock->m_lock); | |
ed1e77d3 | 289 | if (hasExitSite(locker, baselineBlock, codeOrigin.bytecodeIndex)) |
81345200 | 290 | return PutByIdStatus(TakesSlowPath); |
ed1e77d3 A |
291 | exitSiteData = CallLinkStatus::computeExitSiteData( |
292 | locker, baselineBlock, codeOrigin.bytecodeIndex); | |
81345200 A |
293 | } |
294 | ||
295 | PutByIdStatus result; | |
296 | { | |
297 | ConcurrentJITLocker locker(dfgBlock->m_lock); | |
ed1e77d3 A |
298 | result = computeForStubInfo( |
299 | locker, dfgBlock, dfgMap.get(codeOrigin), uid, exitSiteData); | |
81345200 A |
300 | } |
301 | ||
302 | // We use TakesSlowPath in some cases where the stub was unset. That's weird and | |
303 | // it would be better not to do that. But it means that we have to defend | |
304 | // ourselves here. | |
305 | if (result.isSimple()) | |
306 | return result; | |
307 | } | |
308 | #else | |
309 | UNUSED_PARAM(dfgBlock); | |
310 | UNUSED_PARAM(dfgMap); | |
311 | #endif | |
312 | ||
313 | return computeFor(baselineBlock, baselineMap, codeOrigin.bytecodeIndex, uid); | |
6fe7ccc8 A |
314 | } |
315 | ||
ed1e77d3 | 316 | PutByIdStatus PutByIdStatus::computeFor(JSGlobalObject* globalObject, const StructureSet& set, UniquedStringImpl* uid, bool isDirect) |
93a37866 | 317 | { |
ed1e77d3 | 318 | if (parseIndex(*uid)) |
81345200 A |
319 | return PutByIdStatus(TakesSlowPath); |
320 | ||
ed1e77d3 A |
321 | if (set.isEmpty()) |
322 | return PutByIdStatus(); | |
93a37866 | 323 | |
ed1e77d3 A |
324 | PutByIdStatus result; |
325 | result.m_state = Simple; | |
326 | for (unsigned i = 0; i < set.size(); ++i) { | |
327 | Structure* structure = set[i]; | |
328 | ||
329 | if (structure->typeInfo().overridesGetOwnPropertySlot() && structure->typeInfo().type() != GlobalObjectType) | |
330 | return PutByIdStatus(TakesSlowPath); | |
93a37866 | 331 | |
ed1e77d3 A |
332 | if (!structure->propertyAccessesAreCacheable()) |
333 | return PutByIdStatus(TakesSlowPath); | |
93a37866 | 334 | |
ed1e77d3 A |
335 | unsigned attributes; |
336 | PropertyOffset offset = structure->getConcurrently(uid, attributes); | |
337 | if (isValidOffset(offset)) { | |
338 | if (attributes & CustomAccessor) | |
339 | return PutByIdStatus(MakesCalls); | |
81345200 | 340 | |
ed1e77d3 A |
341 | if (attributes & (Accessor | ReadOnly)) |
342 | return PutByIdStatus(TakesSlowPath); | |
343 | ||
344 | WatchpointSet* replaceSet = structure->propertyReplacementWatchpointSet(offset); | |
345 | if (!replaceSet || replaceSet->isStillValid()) { | |
346 | // When this executes, it'll create, and fire, this replacement watchpoint set. | |
347 | // That means that this has probably never executed or that something fishy is | |
348 | // going on. Also, we cannot create or fire the watchpoint set from the concurrent | |
349 | // JIT thread, so even if we wanted to do this, we'd need to have a lazy thingy. | |
350 | // So, better leave this alone and take slow path. | |
351 | return PutByIdStatus(TakesSlowPath); | |
352 | } | |
353 | ||
354 | if (!result.appendVariant(PutByIdVariant::replace(structure, offset))) | |
355 | return PutByIdStatus(TakesSlowPath); | |
356 | continue; | |
93a37866 | 357 | } |
93a37866 | 358 | |
ed1e77d3 A |
359 | // Our hypothesis is that we're doing a transition. Before we prove that this is really |
360 | // true, we want to do some sanity checks. | |
93a37866 | 361 | |
ed1e77d3 A |
362 | // Don't cache put transitions on dictionaries. |
363 | if (structure->isDictionary()) | |
364 | return PutByIdStatus(TakesSlowPath); | |
93a37866 | 365 | |
ed1e77d3 A |
366 | // If the structure corresponds to something that isn't an object, then give up, since |
367 | // we don't want to be adding properties to strings. | |
368 | if (!structure->typeInfo().isObject()) | |
369 | return PutByIdStatus(TakesSlowPath); | |
93a37866 | 370 | |
ed1e77d3 A |
371 | RefPtr<IntendedStructureChain> chain; |
372 | if (!isDirect) { | |
373 | chain = adoptRef(new IntendedStructureChain(globalObject, structure)); | |
81345200 | 374 | |
ed1e77d3 A |
375 | // If the prototype chain has setters or read-only properties, then give up. |
376 | if (chain->mayInterceptStoreTo(uid)) | |
377 | return PutByIdStatus(TakesSlowPath); | |
93a37866 | 378 | |
ed1e77d3 A |
379 | // If the prototype chain hasn't been normalized (i.e. there are proxies or dictionaries) |
380 | // then give up. The dictionary case would only happen if this structure has not been | |
381 | // used in an optimized put_by_id transition. And really the only reason why we would | |
382 | // bail here is that I don't really feel like having the optimizing JIT go and flatten | |
383 | // dictionaries if we have evidence to suggest that those objects were never used as | |
384 | // prototypes in a cacheable prototype access - i.e. there's a good chance that some of | |
385 | // the other checks below will fail. | |
386 | if (structure->isProxy() || !chain->isNormalized()) | |
387 | return PutByIdStatus(TakesSlowPath); | |
388 | } | |
389 | ||
390 | // We only optimize if there is already a structure that the transition is cached to. | |
391 | Structure* transition = Structure::addPropertyTransitionToExistingStructureConcurrently(structure, uid, 0, offset); | |
392 | if (!transition) | |
393 | return PutByIdStatus(TakesSlowPath); | |
394 | ASSERT(isValidOffset(offset)); | |
395 | ||
396 | bool didAppend = result.appendVariant( | |
397 | PutByIdVariant::transition(structure, transition, chain.get(), offset)); | |
398 | if (!didAppend) | |
93a37866 A |
399 | return PutByIdStatus(TakesSlowPath); |
400 | } | |
401 | ||
ed1e77d3 A |
402 | return result; |
403 | } | |
404 | ||
405 | bool PutByIdStatus::makesCalls() const | |
406 | { | |
407 | if (m_state == MakesCalls) | |
408 | return true; | |
409 | ||
410 | if (m_state != Simple) | |
411 | return false; | |
412 | ||
413 | for (unsigned i = m_variants.size(); i--;) { | |
414 | if (m_variants[i].makesCalls()) | |
415 | return true; | |
416 | } | |
93a37866 | 417 | |
ed1e77d3 | 418 | return false; |
81345200 A |
419 | } |
420 | ||
421 | void PutByIdStatus::dump(PrintStream& out) const | |
422 | { | |
423 | switch (m_state) { | |
424 | case NoInformation: | |
425 | out.print("(NoInformation)"); | |
426 | return; | |
427 | ||
428 | case Simple: | |
429 | out.print("(", listDump(m_variants), ")"); | |
430 | return; | |
431 | ||
432 | case TakesSlowPath: | |
433 | out.print("(TakesSlowPath)"); | |
434 | return; | |
435 | case MakesCalls: | |
436 | out.print("(MakesCalls)"); | |
437 | return; | |
438 | } | |
439 | ||
440 | RELEASE_ASSERT_NOT_REACHED(); | |
93a37866 A |
441 | } |
442 | ||
6fe7ccc8 A |
443 | } // namespace JSC |
444 |