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 case Wasm::Funcref:
240 marshallArgumentImpl(m_gprArgs, gpArgumentCount, stackOffset, regFunc, stackFunc);
241 break;
242 case Type::F32:
243 case Type::F64:
244 marshallArgumentImpl(m_fprArgs, fpArgumentCount, stackOffset, regFunc, stackFunc);
245 break;
246 default:
247 RELEASE_ASSERT_NOT_REACHED();
248 }
249 }
250
251public:
252 static unsigned headerSizeInBytes() { return headerSize; }
253
254 template<typename Functor>
255 void loadArguments(const Signature& signature, const Functor& functor) const
256 {
257 size_t gpArgumentCount = 0;
258 size_t fpArgumentCount = 0;
259 size_t stackOffset = headerSize;
260
261 for (size_t i = 0; i < signature.argumentCount(); ++i) {
262 marshallArgument(signature.argument(i), gpArgumentCount, fpArgumentCount, stackOffset,
263 [&] (Reg reg) {
264 functor(B3::Air::Tmp(reg), i);
265 },
266 [&] (size_t stackOffset) {
267 functor(B3::Air::Arg::addr(B3::Air::Tmp(GPRInfo::callFrameRegister), stackOffset), i);
268 });
269 }
270 }
271
272 // It's expected that the pachpointFunctor sets the generator for the call operation.
273 template<typename Functor>
274 void setupCall(B3::Air::Code& code, Type returnType, B3::PatchpointValue* patchpoint, const Vector<B3::Air::Tmp>& args, const Functor& functor) const
275 {
276 size_t gpArgumentCount = 0;
277 size_t fpArgumentCount = 0;
278 size_t stackOffset = headerSize - sizeof(CallerFrameAndPC);
279
280 for (auto tmp : args) {
281 marshallArgument(tmp.isGP() ? Type::I64 : Type::F64, gpArgumentCount, fpArgumentCount, stackOffset,
282 [&] (Reg reg) {
283 functor(tmp, B3::ValueRep::reg(reg));
284 },
285 [&] (size_t stackOffset) {
286 functor(tmp, B3::ValueRep::stackArgument(stackOffset));
287 });
288 }
289
290 code.requestCallArgAreaSizeInBytes(WTF::roundUpToMultipleOf(stackAlignmentBytes(), stackOffset));
291
292 patchpoint->clobberEarly(RegisterSet::macroScratchRegisters());
293 patchpoint->clobberLate(RegisterSet::volatileRegistersForJSCall());
294
295 switch (returnType) {
296 case Type::Void:
297 break;
298 case Type::F32:
299 case Type::F64:
300 patchpoint->resultConstraint = B3::ValueRep::reg(FPRInfo::returnValueFPR);
301 break;
302 case Type::I32:
303 case Type::I64:
304 case Type::Anyref:
305 case Wasm::Funcref:
306 patchpoint->resultConstraint = B3::ValueRep::reg(GPRInfo::returnValueGPR);
307 break;
308 default:
309 RELEASE_ASSERT_NOT_REACHED();
310 }
311 }
312
313 const Vector<Reg> m_gprArgs;
314 const Vector<Reg> m_fprArgs;
315 Vector<Reg> m_scratchGPRs;
316 const RegisterSet m_calleeSaveRegisters;
317 const RegisterSet m_callerSaveRegisters;
318};
319
320inline unsigned nextJSCOffset(unsigned currentOffset)
321{
322 return currentOffset + sizeof(Register);
323}
324
325constexpr unsigned jscHeaderSize = ExecState::headerSizeInRegisters * sizeof(Register);
326
327using JSCCallingConvention = CallingConvention<jscHeaderSize, nextJSCOffset>;
328using WasmCallingConvention = JSCCallingConvention;
329const JSCCallingConvention& jscCallingConvention();
330const WasmCallingConvention& wasmCallingConvention();
331
332using JSCCallingConventionAir = CallingConventionAir<jscHeaderSize, nextJSCOffset>;
333using WasmCallingConventionAir = JSCCallingConventionAir;
334const JSCCallingConventionAir& jscCallingConventionAir();
335const WasmCallingConventionAir& wasmCallingConventionAir();
336
337} } // namespace JSC::Wasm
338
339#endif // ENABLE(WEBASSEMBLY)
340