1/*
2 * Copyright (C) 2016-2018 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(WEBASSEMBLY)
29
30#include "AirCode.h"
31#include "AllowMacroScratchRegisterUsage.h"
32#include "B3ArgumentRegValue.h"
33#include "B3BasicBlock.h"
34#include "B3Const64Value.h"
35#include "B3ConstrainedValue.h"
36#include "B3MemoryValue.h"
37#include "B3PatchpointValue.h"
38#include "B3Procedure.h"
39#include "B3StackmapGenerationParams.h"
40#include "CallFrame.h"
41#include "LinkBuffer.h"
42#include "RegisterSet.h"
43#include "WasmFormat.h"
44#include "WasmSignature.h"
45
46namespace JSC { namespace Wasm {
47
48typedef unsigned (*NextOffset)(unsigned currentOffset);
49
50template<unsigned headerSize, NextOffset updateOffset>
51class CallingConvention {
52public:
53 CallingConvention(Vector<Reg>&& gprArgs, Vector<Reg>&& fprArgs, RegisterSet&& calleeSaveRegisters)
54 : m_gprArgs(WTFMove(gprArgs))
55 , m_fprArgs(WTFMove(fprArgs))
56 , m_calleeSaveRegisters(WTFMove(calleeSaveRegisters))
57 {
58 }
59
60private:
61 B3::ValueRep marshallArgumentImpl(const Vector<Reg>& regArgs, size_t& count, size_t& stackOffset) const
62 {
63 if (count < regArgs.size())
64 return B3::ValueRep::reg(regArgs[count++]);
65
66 count++;
67 B3::ValueRep result = B3::ValueRep::stackArgument(stackOffset);
68 stackOffset = updateOffset(stackOffset);
69 return result;
70 }
71
72 B3::ValueRep marshallArgument(B3::Type type, size_t& gpArgumentCount, size_t& fpArgumentCount, size_t& stackOffset) const
73 {
74 switch (type) {
75 case B3::Int32:
76 case B3::Int64:
77 return marshallArgumentImpl(m_gprArgs, gpArgumentCount, stackOffset);
78 case B3::Float:
79 case B3::Double:
80 return marshallArgumentImpl(m_fprArgs, fpArgumentCount, stackOffset);
81 case B3::Void:
82 break;
83 }
84 RELEASE_ASSERT_NOT_REACHED();
85 }
86
87public:
88 static unsigned headerSizeInBytes() { return headerSize; }
89 void setupFrameInPrologue(CodeLocationDataLabelPtr<WasmEntryPtrTag>* calleeMoveLocation, B3::Procedure& proc, B3::Origin origin, B3::BasicBlock* block) const
90 {
91 static_assert(CallFrameSlot::callee * sizeof(Register) < headerSize, "We rely on this here for now.");
92 static_assert(CallFrameSlot::codeBlock * sizeof(Register) < headerSize, "We rely on this here for now.");
93
94 B3::PatchpointValue* getCalleePatchpoint = block->appendNew<B3::PatchpointValue>(proc, B3::Int64, origin);
95 getCalleePatchpoint->resultConstraint = B3::ValueRep::SomeRegister;
96 getCalleePatchpoint->effects = B3::Effects::none();
97 getCalleePatchpoint->setGenerator(
98 [=] (CCallHelpers& jit, const B3::StackmapGenerationParams& params) {
99 GPRReg result = params[0].gpr();
100 MacroAssembler::DataLabelPtr moveLocation = jit.moveWithPatch(MacroAssembler::TrustedImmPtr(nullptr), result);
101 jit.addLinkTask([calleeMoveLocation, moveLocation] (LinkBuffer& linkBuffer) {
102 *calleeMoveLocation = linkBuffer.locationOf<WasmEntryPtrTag>(moveLocation);
103 });
104 });
105
106 B3::Value* framePointer = block->appendNew<B3::Value>(proc, B3::FramePointer, origin);
107 B3::Value* offsetOfCallee = block->appendNew<B3::Const64Value>(proc, origin, CallFrameSlot::callee * sizeof(Register));
108 block->appendNew<B3::MemoryValue>(proc, B3::Store, origin,
109 getCalleePatchpoint,
110 block->appendNew<B3::Value>(proc, B3::Add, origin, framePointer, offsetOfCallee));
111
112 // FIXME: We shouldn't have to store zero into the CodeBlock* spot in the call frame,
113 // but there are places that interpret non-null CodeBlock slot to mean a valid CodeBlock.
114 // When doing unwinding, we'll need to verify that the entire runtime is OK with a non-null
115 // CodeBlock not implying that the CodeBlock is valid.
116 // https://bugs.webkit.org/show_bug.cgi?id=165321
117 B3::Value* offsetOfCodeBlock = block->appendNew<B3::Const64Value>(proc, origin, CallFrameSlot::codeBlock * sizeof(Register));
118 block->appendNew<B3::MemoryValue>(proc, B3::Store, origin,
119 block->appendNew<B3::Const64Value>(proc, origin, 0),
120 block->appendNew<B3::Value>(proc, B3::Add, origin, framePointer, offsetOfCodeBlock));
121 }
122
123 template<typename Functor>
124 void loadArguments(const Signature& signature, B3::Procedure& proc, B3::BasicBlock* block, B3::Origin origin, const Functor& functor) const
125 {
126 B3::Value* framePointer = block->appendNew<B3::Value>(proc, B3::FramePointer, origin);
127
128 size_t gpArgumentCount = 0;
129 size_t fpArgumentCount = 0;
130 size_t stackOffset = headerSize;
131
132 for (size_t i = 0; i < signature.argumentCount(); ++i) {
133 B3::Type type = toB3Type(signature.argument(i));
134 B3::Value* argument;
135 B3::ValueRep rep = marshallArgument(type, gpArgumentCount, fpArgumentCount, stackOffset);
136 if (rep.isReg()) {
137 argument = block->appendNew<B3::ArgumentRegValue>(proc, origin, rep.reg());
138 if (type == B3::Int32 || type == B3::Float)
139 argument = block->appendNew<B3::Value>(proc, B3::Trunc, origin, argument);
140 } else {
141 ASSERT(rep.isStackArgument());
142 B3::Value* address = block->appendNew<B3::Value>(proc, B3::Add, origin, framePointer,
143 block->appendNew<B3::Const64Value>(proc, origin, rep.offsetFromSP()));
144 argument = block->appendNew<B3::MemoryValue>(proc, B3::Load, type, origin, address);
145 }
146 functor(argument, i);
147 }
148 }
149
150 // It's expected that the pachpointFunctor sets the generator for the call operation.
151 template<typename Functor>
152 B3::Value* setupCall(B3::Procedure& proc, B3::BasicBlock* block, B3::Origin origin, const Vector<B3::Value*>& arguments, B3::Type returnType, const Functor& patchpointFunctor) const
153 {
154 size_t gpArgumentCount = 0;
155 size_t fpArgumentCount = 0;
156 size_t stackOffset = headerSize - sizeof(CallerFrameAndPC);
157
158 Vector<B3::ConstrainedValue> constrainedArguments;
159 for (B3::Value* argument : arguments) {
160 B3::ValueRep rep = marshallArgument(argument->type(), gpArgumentCount, fpArgumentCount, stackOffset);
161 constrainedArguments.append(B3::ConstrainedValue(argument, rep));
162 }
163
164 proc.requestCallArgAreaSizeInBytes(WTF::roundUpToMultipleOf(stackAlignmentBytes(), stackOffset));
165
166 B3::PatchpointValue* patchpoint = block->appendNew<B3::PatchpointValue>(proc, returnType, origin);
167 patchpoint->clobberEarly(RegisterSet::macroScratchRegisters());
168 patchpoint->clobberLate(RegisterSet::volatileRegistersForJSCall());
169 patchpointFunctor(patchpoint);
170 patchpoint->appendVector(constrainedArguments);
171
172 switch (returnType) {
173 case B3::Void:
174 return nullptr;
175 case B3::Float:
176 case B3::Double:
177 patchpoint->resultConstraint = B3::ValueRep::reg(FPRInfo::returnValueFPR);
178 break;
179 case B3::Int32:
180 case B3::Int64:
181 patchpoint->resultConstraint = B3::ValueRep::reg(GPRInfo::returnValueGPR);
182 break;
183 }
184 return patchpoint;
185 }
186
187 const Vector<Reg> m_gprArgs;
188 const Vector<Reg> m_fprArgs;
189 const RegisterSet m_calleeSaveRegisters;
190 const RegisterSet m_callerSaveRegisters;
191};
192
193// FIXME: Share more code with CallingConvention above:
194// https://bugs.webkit.org/show_bug.cgi?id=194065
195template<unsigned headerSize, NextOffset updateOffset>
196class CallingConventionAir {
197public:
198 CallingConventionAir(Vector<Reg>&& gprArgs, Vector<Reg>&& fprArgs, RegisterSet&& calleeSaveRegisters)
199 : m_gprArgs(WTFMove(gprArgs))
200 , m_fprArgs(WTFMove(fprArgs))
201 , m_calleeSaveRegisters(WTFMove(calleeSaveRegisters))
202 {
203 RegisterSet scratch = RegisterSet::allGPRs();
204 scratch.exclude(RegisterSet::macroScratchRegisters());
205 scratch.exclude(RegisterSet::reservedHardwareRegisters());
206 scratch.exclude(RegisterSet::stackRegisters());
207 for (Reg reg : m_gprArgs)
208 scratch.clear(reg);
209 for (Reg reg : m_calleeSaveRegisters)
210 scratch.clear(reg);
211 for (Reg reg : scratch)
212 m_scratchGPRs.append(reg);
213 RELEASE_ASSERT(m_scratchGPRs.size() >= 2);
214 }
215
216 GPRReg prologueScratch(size_t i) const { return m_scratchGPRs[i].gpr(); }
217
218private:
219 template <typename RegFunc, typename StackFunc>
220 void marshallArgumentImpl(const Vector<Reg>& regArgs, size_t& count, size_t& stackOffset, const RegFunc& regFunc, const StackFunc& stackFunc) const
221 {
222 if (count < regArgs.size()) {
223 regFunc(regArgs[count++]);
224 return;
225 }
226
227 count++;
228 stackFunc(stackOffset);
229 stackOffset = updateOffset(stackOffset);
230 }
231
232 template <typename RegFunc, typename StackFunc>
233 void marshallArgument(Type type, size_t& gpArgumentCount, size_t& fpArgumentCount, size_t& stackOffset, const RegFunc& regFunc, const StackFunc& stackFunc) const
234 {
235 switch (type) {
236 case Type::I32:
237 case Type::I64:
238 case Type::Anyref:
239 marshallArgumentImpl(m_gprArgs, gpArgumentCount, stackOffset, regFunc, stackFunc);
240 break;
241 case Type::F32:
242 case Type::F64:
243 marshallArgumentImpl(m_fprArgs, fpArgumentCount, stackOffset, regFunc, stackFunc);
244 break;
245 default:
246 RELEASE_ASSERT_NOT_REACHED();
247 }
248 }
249
250public:
251 static unsigned headerSizeInBytes() { return headerSize; }
252
253 template<typename Functor>
254 void loadArguments(const Signature& signature, const Functor& functor) const
255 {
256 size_t gpArgumentCount = 0;
257 size_t fpArgumentCount = 0;
258 size_t stackOffset = headerSize;
259
260 for (size_t i = 0; i < signature.argumentCount(); ++i) {
261 marshallArgument(signature.argument(i), gpArgumentCount, fpArgumentCount, stackOffset,
262 [&] (Reg reg) {
263 functor(B3::Air::Tmp(reg), i);
264 },
265 [&] (size_t stackOffset) {
266 functor(B3::Air::Arg::addr(B3::Air::Tmp(GPRInfo::callFrameRegister), stackOffset), i);
267 });
268 }
269 }
270
271 // It's expected that the pachpointFunctor sets the generator for the call operation.
272 template<typename Functor>
273 void setupCall(B3::Air::Code& code, Type returnType, B3::PatchpointValue* patchpoint, const Vector<B3::Air::Tmp>& args, const Functor& functor) const
274 {
275 size_t gpArgumentCount = 0;
276 size_t fpArgumentCount = 0;
277 size_t stackOffset = headerSize - sizeof(CallerFrameAndPC);
278
279 for (auto tmp : args) {
280 marshallArgument(tmp.isGP() ? Type::I64 : Type::F64, gpArgumentCount, fpArgumentCount, stackOffset,
281 [&] (Reg reg) {
282 functor(tmp, B3::ValueRep::reg(reg));
283 },
284 [&] (size_t stackOffset) {
285 functor(tmp, B3::ValueRep::stackArgument(stackOffset));
286 });
287 }
288
289 code.requestCallArgAreaSizeInBytes(WTF::roundUpToMultipleOf(stackAlignmentBytes(), stackOffset));
290
291 patchpoint->clobberEarly(RegisterSet::macroScratchRegisters());
292 patchpoint->clobberLate(RegisterSet::volatileRegistersForJSCall());
293
294 switch (returnType) {
295 case Type::Void:
296 break;
297 case Type::F32:
298 case Type::F64:
299 patchpoint->resultConstraint = B3::ValueRep::reg(FPRInfo::returnValueFPR);
300 break;
301 case Type::I32:
302 case Type::I64:
303 case Type::Anyref:
304 patchpoint->resultConstraint = B3::ValueRep::reg(GPRInfo::returnValueGPR);
305 break;
306 default:
307 RELEASE_ASSERT_NOT_REACHED();
308 }
309 }
310
311 const Vector<Reg> m_gprArgs;
312 const Vector<Reg> m_fprArgs;
313 Vector<Reg> m_scratchGPRs;
314 const RegisterSet m_calleeSaveRegisters;
315 const RegisterSet m_callerSaveRegisters;
316};
317
318inline unsigned nextJSCOffset(unsigned currentOffset)
319{
320 return currentOffset + sizeof(Register);
321}
322
323constexpr unsigned jscHeaderSize = ExecState::headerSizeInRegisters * sizeof(Register);
324
325using JSCCallingConvention = CallingConvention<jscHeaderSize, nextJSCOffset>;
326using WasmCallingConvention = JSCCallingConvention;
327const JSCCallingConvention& jscCallingConvention();
328const WasmCallingConvention& wasmCallingConvention();
329
330using JSCCallingConventionAir = CallingConventionAir<jscHeaderSize, nextJSCOffset>;
331using WasmCallingConventionAir = JSCCallingConventionAir;
332const JSCCallingConventionAir& jscCallingConventionAir();
333const WasmCallingConventionAir& wasmCallingConventionAir();
334
335} } // namespace JSC::Wasm
336
337#endif // ENABLE(WEBASSEMBLY)
338