1 | /* |
2 | * Copyright (C) 2017-2019 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 | #include "config.h" |
27 | #include "GetterSetterAccessCase.h" |
28 | |
29 | #if ENABLE(JIT) |
30 | |
31 | #include "AccessCaseSnippetParams.h" |
32 | #include "DOMJITCallDOMGetterSnippet.h" |
33 | #include "DOMJITGetterSetter.h" |
34 | #include "HeapInlines.h" |
35 | #include "JSCJSValueInlines.h" |
36 | #include "PolymorphicAccess.h" |
37 | #include "StructureStubInfo.h" |
38 | |
39 | namespace JSC { |
40 | |
41 | namespace GetterSetterAccessCaseInternal { |
42 | static constexpr bool verbose = false; |
43 | } |
44 | |
45 | GetterSetterAccessCase::GetterSetterAccessCase(VM& vm, JSCell* owner, AccessType accessType, const Identifier& identifier, PropertyOffset offset, Structure* structure, const ObjectPropertyConditionSet& conditionSet, bool viaProxy, WatchpointSet* additionalSet, JSObject* customSlotBase, std::unique_ptr<PolyProtoAccessChain> prototypeAccessChain) |
46 | : Base(vm, owner, accessType, identifier, offset, structure, conditionSet, viaProxy, additionalSet, WTFMove(prototypeAccessChain)) |
47 | { |
48 | m_customSlotBase.setMayBeNull(vm, owner, customSlotBase); |
49 | } |
50 | |
51 | |
52 | std::unique_ptr<AccessCase> GetterSetterAccessCase::create( |
53 | VM& vm, JSCell* owner, AccessType type, const Identifier& identifier, PropertyOffset offset, Structure* structure, const ObjectPropertyConditionSet& conditionSet, |
54 | bool viaProxy, WatchpointSet* additionalSet, FunctionPtr<OperationPtrTag> customGetter, JSObject* customSlotBase, |
55 | Optional<DOMAttributeAnnotation> domAttribute, std::unique_ptr<PolyProtoAccessChain> prototypeAccessChain) |
56 | { |
57 | switch (type) { |
58 | case Getter: |
59 | case CustomAccessorGetter: |
60 | case CustomValueGetter: |
61 | break; |
62 | default: |
63 | ASSERT_NOT_REACHED(); |
64 | }; |
65 | |
66 | std::unique_ptr<GetterSetterAccessCase> result(new GetterSetterAccessCase(vm, owner, type, identifier, offset, structure, conditionSet, viaProxy, additionalSet, customSlotBase, WTFMove(prototypeAccessChain))); |
67 | result->m_domAttribute = domAttribute; |
68 | result->m_customAccessor = customGetter ? FunctionPtr<OperationPtrTag>(customGetter) : nullptr; |
69 | return result; |
70 | } |
71 | |
72 | std::unique_ptr<AccessCase> GetterSetterAccessCase::create(VM& vm, JSCell* owner, AccessType type, Structure* structure, const Identifier& identifier, PropertyOffset offset, |
73 | const ObjectPropertyConditionSet& conditionSet, std::unique_ptr<PolyProtoAccessChain> prototypeAccessChain, FunctionPtr<OperationPtrTag> customSetter, |
74 | JSObject* customSlotBase) |
75 | { |
76 | ASSERT(type == Setter || type == CustomValueSetter || type == CustomAccessorSetter); |
77 | std::unique_ptr<GetterSetterAccessCase> result(new GetterSetterAccessCase(vm, owner, type, identifier, offset, structure, conditionSet, false, nullptr, customSlotBase, WTFMove(prototypeAccessChain))); |
78 | result->m_customAccessor = customSetter ? FunctionPtr<OperationPtrTag>(customSetter) : nullptr; |
79 | return result; |
80 | } |
81 | |
82 | |
83 | GetterSetterAccessCase::~GetterSetterAccessCase() |
84 | { |
85 | } |
86 | |
87 | |
88 | GetterSetterAccessCase::GetterSetterAccessCase(const GetterSetterAccessCase& other) |
89 | : Base(other) |
90 | , m_customSlotBase(other.m_customSlotBase) |
91 | { |
92 | m_customAccessor = other.m_customAccessor; |
93 | m_domAttribute = other.m_domAttribute; |
94 | } |
95 | |
96 | std::unique_ptr<AccessCase> GetterSetterAccessCase::clone() const |
97 | { |
98 | std::unique_ptr<GetterSetterAccessCase> result(new GetterSetterAccessCase(*this)); |
99 | result->resetState(); |
100 | return result; |
101 | } |
102 | |
103 | bool GetterSetterAccessCase::hasAlternateBase() const |
104 | { |
105 | if (customSlotBase()) |
106 | return true; |
107 | return Base::hasAlternateBase(); |
108 | } |
109 | |
110 | JSObject* GetterSetterAccessCase::alternateBase() const |
111 | { |
112 | if (customSlotBase()) |
113 | return customSlotBase(); |
114 | return Base::alternateBase(); |
115 | } |
116 | |
117 | void GetterSetterAccessCase::dumpImpl(PrintStream& out, CommaPrinter& comma) const |
118 | { |
119 | Base::dumpImpl(out, comma); |
120 | out.print(comma, "customSlotBase = " , RawPointer(customSlotBase())); |
121 | if (callLinkInfo()) |
122 | out.print(comma, "callLinkInfo = " , RawPointer(callLinkInfo())); |
123 | out.print(comma, "customAccessor = " , RawPointer(m_customAccessor.executableAddress())); |
124 | } |
125 | |
126 | void GetterSetterAccessCase::emitDOMJITGetter(AccessGenerationState& state, const DOMJIT::GetterSetter* domJIT, GPRReg baseForGetGPR) |
127 | { |
128 | CCallHelpers& jit = *state.jit; |
129 | StructureStubInfo& stubInfo = *state.stubInfo; |
130 | JSValueRegs valueRegs = state.valueRegs; |
131 | GPRReg baseGPR = state.baseGPR; |
132 | GPRReg scratchGPR = state.scratchGPR; |
133 | |
134 | // We construct the environment that can execute the DOMJIT::Snippet here. |
135 | Ref<DOMJIT::CallDOMGetterSnippet> snippet = domJIT->compiler()(); |
136 | |
137 | Vector<GPRReg> gpScratch; |
138 | Vector<FPRReg> fpScratch; |
139 | Vector<SnippetParams::Value> regs; |
140 | |
141 | ScratchRegisterAllocator allocator(stubInfo.patch.usedRegisters); |
142 | allocator.lock(baseGPR); |
143 | #if USE(JSVALUE32_64) |
144 | allocator.lock(stubInfo.patch.baseTagGPR); |
145 | #endif |
146 | allocator.lock(valueRegs); |
147 | allocator.lock(scratchGPR); |
148 | |
149 | GPRReg paramBaseGPR = InvalidGPRReg; |
150 | GPRReg paramGlobalObjectGPR = InvalidGPRReg; |
151 | JSValueRegs paramValueRegs = valueRegs; |
152 | GPRReg remainingScratchGPR = InvalidGPRReg; |
153 | |
154 | // valueRegs and baseForGetGPR may be the same. For example, in Baseline JIT, we pass the same regT0 for baseGPR and valueRegs. |
155 | // In FTL, there is no constraint that the baseForGetGPR interferes with the result. To make implementation simple in |
156 | // Snippet, Snippet assumes that result registers always early interfere with input registers, in this case, |
157 | // baseForGetGPR. So we move baseForGetGPR to the other register if baseForGetGPR == valueRegs. |
158 | if (baseForGetGPR != valueRegs.payloadGPR()) { |
159 | paramBaseGPR = baseForGetGPR; |
160 | if (!snippet->requireGlobalObject) |
161 | remainingScratchGPR = scratchGPR; |
162 | else |
163 | paramGlobalObjectGPR = scratchGPR; |
164 | } else { |
165 | jit.move(valueRegs.payloadGPR(), scratchGPR); |
166 | paramBaseGPR = scratchGPR; |
167 | if (snippet->requireGlobalObject) |
168 | paramGlobalObjectGPR = allocator.allocateScratchGPR(); |
169 | } |
170 | |
171 | JSGlobalObject* globalObjectForDOMJIT = structure()->globalObject(); |
172 | |
173 | regs.append(paramValueRegs); |
174 | regs.append(paramBaseGPR); |
175 | if (snippet->requireGlobalObject) { |
176 | ASSERT(paramGlobalObjectGPR != InvalidGPRReg); |
177 | regs.append(SnippetParams::Value(paramGlobalObjectGPR, globalObjectForDOMJIT)); |
178 | } |
179 | |
180 | if (snippet->numGPScratchRegisters) { |
181 | unsigned i = 0; |
182 | if (remainingScratchGPR != InvalidGPRReg) { |
183 | gpScratch.append(remainingScratchGPR); |
184 | ++i; |
185 | } |
186 | for (; i < snippet->numGPScratchRegisters; ++i) |
187 | gpScratch.append(allocator.allocateScratchGPR()); |
188 | } |
189 | |
190 | for (unsigned i = 0; i < snippet->numFPScratchRegisters; ++i) |
191 | fpScratch.append(allocator.allocateScratchFPR()); |
192 | |
193 | // Let's store the reused registers to the stack. After that, we can use allocated scratch registers. |
194 | ScratchRegisterAllocator::PreservedState preservedState = |
195 | allocator.preserveReusedRegistersByPushing(jit, ScratchRegisterAllocator::ExtraStackSpace::SpaceForCCall); |
196 | |
197 | if (GetterSetterAccessCaseInternal::verbose) { |
198 | dataLog("baseGPR = " , baseGPR, "\n" ); |
199 | dataLog("valueRegs = " , valueRegs, "\n" ); |
200 | dataLog("scratchGPR = " , scratchGPR, "\n" ); |
201 | dataLog("paramBaseGPR = " , paramBaseGPR, "\n" ); |
202 | if (paramGlobalObjectGPR != InvalidGPRReg) |
203 | dataLog("paramGlobalObjectGPR = " , paramGlobalObjectGPR, "\n" ); |
204 | dataLog("paramValueRegs = " , paramValueRegs, "\n" ); |
205 | for (unsigned i = 0; i < snippet->numGPScratchRegisters; ++i) |
206 | dataLog("gpScratch[" , i, "] = " , gpScratch[i], "\n" ); |
207 | } |
208 | |
209 | if (snippet->requireGlobalObject) |
210 | jit.move(CCallHelpers::TrustedImmPtr(globalObjectForDOMJIT), paramGlobalObjectGPR); |
211 | |
212 | // We just spill the registers used in Snippet here. For not spilled registers here explicitly, |
213 | // they must be in the used register set passed by the callers (Baseline, DFG, and FTL) if they need to be kept. |
214 | // Some registers can be locked, but not in the used register set. For example, the caller could make baseGPR |
215 | // same to valueRegs, and not include it in the used registers since it will be changed. |
216 | RegisterSet registersToSpillForCCall; |
217 | for (auto& value : regs) { |
218 | SnippetReg reg = value.reg(); |
219 | if (reg.isJSValueRegs()) |
220 | registersToSpillForCCall.set(reg.jsValueRegs()); |
221 | else if (reg.isGPR()) |
222 | registersToSpillForCCall.set(reg.gpr()); |
223 | else |
224 | registersToSpillForCCall.set(reg.fpr()); |
225 | } |
226 | for (GPRReg reg : gpScratch) |
227 | registersToSpillForCCall.set(reg); |
228 | for (FPRReg reg : fpScratch) |
229 | registersToSpillForCCall.set(reg); |
230 | registersToSpillForCCall.exclude(RegisterSet::registersToNotSaveForCCall()); |
231 | |
232 | AccessCaseSnippetParams params(state.m_vm, WTFMove(regs), WTFMove(gpScratch), WTFMove(fpScratch)); |
233 | snippet->generator()->run(jit, params); |
234 | allocator.restoreReusedRegistersByPopping(jit, preservedState); |
235 | state.succeed(); |
236 | |
237 | CCallHelpers::JumpList exceptions = params.emitSlowPathCalls(state, registersToSpillForCCall, jit); |
238 | if (!exceptions.empty()) { |
239 | exceptions.link(&jit); |
240 | allocator.restoreReusedRegistersByPopping(jit, preservedState); |
241 | state.emitExplicitExceptionHandler(); |
242 | } |
243 | } |
244 | |
245 | } // namespace JSC |
246 | |
247 | #endif // ENABLE(JIT) |
248 | |