1/*
2 * Copyright (C) 2016-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 "JSToWasm.h"
28
29#if ENABLE(WEBASSEMBLY)
30
31#include "CCallHelpers.h"
32#include "DisallowMacroScratchRegisterUsage.h"
33#include "JSCInlines.h"
34#include "JSWebAssemblyHelpers.h"
35#include "JSWebAssemblyInstance.h"
36#include "JSWebAssemblyRuntimeError.h"
37#include "MaxFrameExtentForSlowPathCall.h"
38#include "WasmCallingConvention.h"
39#include "WasmContextInlines.h"
40#include "WasmSignatureInlines.h"
41#include "WasmToJS.h"
42
43namespace JSC { namespace Wasm {
44
45std::unique_ptr<InternalFunction> createJSToWasmWrapper(CompilationContext& compilationContext, const Signature& signature, Vector<UnlinkedWasmToWasmCall>* unlinkedWasmToWasmCalls, const ModuleInformation& info, MemoryMode mode, unsigned functionIndex)
46{
47 CCallHelpers& jit = *compilationContext.embedderEntrypointJIT;
48
49 auto result = std::make_unique<InternalFunction>();
50 jit.emitFunctionPrologue();
51
52 // FIXME Stop using 0 as codeBlocks. https://bugs.webkit.org/show_bug.cgi?id=165321
53 jit.store64(CCallHelpers::TrustedImm64(0), CCallHelpers::Address(GPRInfo::callFrameRegister, CallFrameSlot::codeBlock * static_cast<int>(sizeof(Register))));
54 MacroAssembler::DataLabelPtr calleeMoveLocation = jit.moveWithPatch(MacroAssembler::TrustedImmPtr(nullptr), GPRInfo::nonPreservedNonReturnGPR);
55 jit.storePtr(GPRInfo::nonPreservedNonReturnGPR, CCallHelpers::Address(GPRInfo::callFrameRegister, CallFrameSlot::callee * static_cast<int>(sizeof(Register))));
56 CodeLocationDataLabelPtr<WasmEntryPtrTag>* linkedCalleeMove = &result->calleeMoveLocation;
57 jit.addLinkTask([=] (LinkBuffer& linkBuffer) {
58 *linkedCalleeMove = linkBuffer.locationOf<WasmEntryPtrTag>(calleeMoveLocation);
59 });
60
61 const PinnedRegisterInfo& pinnedRegs = PinnedRegisterInfo::get();
62 RegisterSet toSave = pinnedRegs.toSave(mode);
63
64#if !ASSERT_DISABLED
65 unsigned toSaveSize = toSave.numberOfSetGPRs();
66 // They should all be callee saves.
67 toSave.filter(RegisterSet::calleeSaveRegisters());
68 ASSERT(toSave.numberOfSetGPRs() == toSaveSize);
69#endif
70
71 RegisterAtOffsetList registersToSpill(toSave, RegisterAtOffsetList::OffsetBaseType::FramePointerBased);
72 result->entrypoint.calleeSaveRegisters = registersToSpill;
73
74 unsigned totalFrameSize = registersToSpill.size() * sizeof(void*);
75 totalFrameSize += WasmCallingConvention::headerSizeInBytes();
76 totalFrameSize -= sizeof(CallerFrameAndPC);
77 unsigned numGPRs = 0;
78 unsigned numFPRs = 0;
79 bool argumentsIncludeI64 = false;
80 for (unsigned i = 0; i < signature.argumentCount(); i++) {
81 switch (signature.argument(i)) {
82 case Wasm::I64:
83 argumentsIncludeI64 = true;
84 FALLTHROUGH;
85 case Wasm::I32:
86 case Wasm::Anyref:
87 case Wasm::Funcref:
88 if (numGPRs >= wasmCallingConvention().m_gprArgs.size())
89 totalFrameSize += sizeof(void*);
90 ++numGPRs;
91 break;
92 case Wasm::F32:
93 case Wasm::F64:
94 if (numFPRs >= wasmCallingConvention().m_fprArgs.size())
95 totalFrameSize += sizeof(void*);
96 ++numFPRs;
97 break;
98 default:
99 RELEASE_ASSERT_NOT_REACHED();
100 }
101 }
102
103 totalFrameSize = WTF::roundUpToMultipleOf(stackAlignmentBytes(), totalFrameSize);
104 jit.subPtr(MacroAssembler::TrustedImm32(totalFrameSize), MacroAssembler::stackPointerRegister);
105
106 // We save all these registers regardless of having a memory or not.
107 // The reason is that we use one of these as a scratch. That said,
108 // almost all real wasm programs use memory, so it's not really
109 // worth optimizing for the case that they don't.
110 for (const RegisterAtOffset& regAtOffset : registersToSpill) {
111 GPRReg reg = regAtOffset.reg().gpr();
112 ptrdiff_t offset = regAtOffset.offset();
113 jit.storePtr(reg, CCallHelpers::Address(GPRInfo::callFrameRegister, offset));
114 }
115
116 if (argumentsIncludeI64 || signature.returnType() == Wasm::I64) {
117 if (Context::useFastTLS())
118 jit.loadWasmContextInstance(GPRInfo::argumentGPR2);
119 else {
120 // vmEntryToWasm passes the JSWebAssemblyInstance corresponding to Wasm::Context*'s
121 // instance as the first JS argument when we're not using fast TLS to hold the
122 // Wasm::Context*'s instance.
123 jit.loadPtr(CCallHelpers::Address(GPRInfo::callFrameRegister, CallFrameSlot::thisArgument * sizeof(EncodedJSValue)), GPRInfo::argumentGPR2);
124 jit.loadPtr(CCallHelpers::Address(GPRInfo::argumentGPR2, JSWebAssemblyInstance::offsetOfInstance()), GPRInfo::argumentGPR2);
125 }
126
127 emitThrowWasmToJSException(jit, GPRInfo::argumentGPR2, argumentsIncludeI64 ? ExceptionType::I64ArgumentType : ExceptionType::I64ReturnType);
128 return result;
129 }
130
131 GPRReg wasmContextInstanceGPR = pinnedRegs.wasmContextInstancePointer;
132
133 {
134 CCallHelpers::Address calleeFrame = CCallHelpers::Address(MacroAssembler::stackPointerRegister, -static_cast<ptrdiff_t>(sizeof(CallerFrameAndPC)));
135 numGPRs = 0;
136 numFPRs = 0;
137 // We're going to set the pinned registers after this. So
138 // we can use this as a scratch for now since we saved it above.
139 GPRReg scratchReg = pinnedRegs.baseMemoryPointer;
140
141 ptrdiff_t jsOffset = CallFrameSlot::thisArgument * sizeof(EncodedJSValue);
142
143 // vmEntryToWasm passes the JSWebAssemblyInstance corresponding to Wasm::Context*'s
144 // instance as the first JS argument when we're not using fast TLS to hold the
145 // Wasm::Context*'s instance.
146 if (!Context::useFastTLS()) {
147 jit.loadPtr(CCallHelpers::Address(GPRInfo::callFrameRegister, jsOffset), wasmContextInstanceGPR);
148 jit.loadPtr(CCallHelpers::Address(wasmContextInstanceGPR, JSWebAssemblyInstance::offsetOfInstance()), wasmContextInstanceGPR);
149 jsOffset += sizeof(EncodedJSValue);
150 }
151
152 ptrdiff_t wasmOffset = CallFrame::headerSizeInRegisters * sizeof(void*);
153 for (unsigned i = 0; i < signature.argumentCount(); i++) {
154 switch (signature.argument(i)) {
155 case Wasm::I32:
156 case Wasm::I64:
157 case Wasm::Funcref:
158 case Wasm::Anyref:
159 if (numGPRs >= wasmCallingConvention().m_gprArgs.size()) {
160 if (signature.argument(i) == Wasm::I32) {
161 jit.load32(CCallHelpers::Address(GPRInfo::callFrameRegister, jsOffset), scratchReg);
162 jit.store32(scratchReg, calleeFrame.withOffset(wasmOffset));
163 } else {
164 jit.load64(CCallHelpers::Address(GPRInfo::callFrameRegister, jsOffset), scratchReg);
165 jit.store64(scratchReg, calleeFrame.withOffset(wasmOffset));
166 }
167 wasmOffset += sizeof(void*);
168 } else {
169 if (signature.argument(i) == Wasm::I32)
170 jit.load32(CCallHelpers::Address(GPRInfo::callFrameRegister, jsOffset), wasmCallingConvention().m_gprArgs[numGPRs].gpr());
171 else
172 jit.load64(CCallHelpers::Address(GPRInfo::callFrameRegister, jsOffset), wasmCallingConvention().m_gprArgs[numGPRs].gpr());
173 }
174 ++numGPRs;
175 break;
176 case Wasm::F32:
177 case Wasm::F64:
178 if (numFPRs >= wasmCallingConvention().m_fprArgs.size()) {
179 if (signature.argument(i) == Wasm::F32) {
180 jit.load32(CCallHelpers::Address(GPRInfo::callFrameRegister, jsOffset), scratchReg);
181 jit.store32(scratchReg, calleeFrame.withOffset(wasmOffset));
182 } else {
183 jit.load64(CCallHelpers::Address(GPRInfo::callFrameRegister, jsOffset), scratchReg);
184 jit.store64(scratchReg, calleeFrame.withOffset(wasmOffset));
185 }
186 wasmOffset += sizeof(void*);
187 } else {
188 if (signature.argument(i) == Wasm::F32)
189 jit.loadFloat(CCallHelpers::Address(GPRInfo::callFrameRegister, jsOffset), wasmCallingConvention().m_fprArgs[numFPRs].fpr());
190 else
191 jit.loadDouble(CCallHelpers::Address(GPRInfo::callFrameRegister, jsOffset), wasmCallingConvention().m_fprArgs[numFPRs].fpr());
192 }
193 ++numFPRs;
194 break;
195 default:
196 RELEASE_ASSERT_NOT_REACHED();
197 }
198
199 jsOffset += sizeof(EncodedJSValue);
200 }
201 }
202
203 if (!!info.memory) {
204 GPRReg baseMemory = pinnedRegs.baseMemoryPointer;
205 GPRReg scratchOrSize = wasmCallingConventionAir().prologueScratch(0);
206
207 if (Context::useFastTLS())
208 jit.loadWasmContextInstance(baseMemory);
209
210 GPRReg currentInstanceGPR = Context::useFastTLS() ? baseMemory : wasmContextInstanceGPR;
211 if (isARM64E()) {
212 if (mode != Wasm::MemoryMode::Signaling)
213 scratchOrSize = pinnedRegs.sizeRegister;
214 jit.loadPtr(CCallHelpers::Address(currentInstanceGPR, Wasm::Instance::offsetOfCachedMemorySize()), scratchOrSize);
215 } else {
216 if (mode != Wasm::MemoryMode::Signaling)
217 jit.loadPtr(CCallHelpers::Address(currentInstanceGPR, Wasm::Instance::offsetOfCachedMemorySize()), pinnedRegs.sizeRegister);
218 }
219
220 jit.loadPtr(CCallHelpers::Address(currentInstanceGPR, Wasm::Instance::offsetOfCachedMemory()), baseMemory);
221 jit.cageConditionally(Gigacage::Primitive, baseMemory, scratchOrSize, scratchOrSize);
222 }
223
224 CCallHelpers::Call call = jit.threadSafePatchableNearCall();
225 unsigned functionIndexSpace = functionIndex + info.importFunctionCount();
226 ASSERT(functionIndexSpace < info.functionIndexSpaceSize());
227 jit.addLinkTask([unlinkedWasmToWasmCalls, call, functionIndexSpace] (LinkBuffer& linkBuffer) {
228 unlinkedWasmToWasmCalls->append({ linkBuffer.locationOfNearCall<WasmEntryPtrTag>(call), functionIndexSpace });
229 });
230
231 for (const RegisterAtOffset& regAtOffset : registersToSpill) {
232 GPRReg reg = regAtOffset.reg().gpr();
233 ASSERT(reg != GPRInfo::returnValueGPR);
234 ptrdiff_t offset = regAtOffset.offset();
235 jit.loadPtr(CCallHelpers::Address(GPRInfo::callFrameRegister, offset), reg);
236 }
237
238 switch (signature.returnType()) {
239 case Wasm::Void:
240 jit.moveTrustedValue(jsUndefined(), JSValueRegs { GPRInfo::returnValueGPR });
241 break;
242 case Wasm::Anyref:
243 case Wasm::Funcref:
244 break;
245 case Wasm::I32:
246 jit.zeroExtend32ToPtr(GPRInfo::returnValueGPR, GPRInfo::returnValueGPR);
247 jit.boxInt32(GPRInfo::returnValueGPR, JSValueRegs { GPRInfo::returnValueGPR }, DoNotHaveTagRegisters);
248 break;
249 case Wasm::F32:
250 jit.convertFloatToDouble(FPRInfo::returnValueFPR, FPRInfo::returnValueFPR);
251 FALLTHROUGH;
252 case Wasm::F64: {
253 jit.moveTrustedValue(jsNumber(pureNaN()), JSValueRegs { GPRInfo::returnValueGPR });
254 auto isNaN = jit.branchIfNaN(FPRInfo::returnValueFPR);
255 jit.boxDouble(FPRInfo::returnValueFPR, JSValueRegs { GPRInfo::returnValueGPR }, DoNotHaveTagRegisters);
256 isNaN.link(&jit);
257 break;
258 }
259 case Wasm::I64:
260 case Wasm::Func:
261 jit.breakpoint();
262 break;
263 default:
264 break;
265 }
266
267 jit.emitFunctionEpilogue();
268 jit.ret();
269
270 return result;
271}
272
273} } // namespace JSC::Wasm
274
275#endif // ENABLE(WEBASSEMBLY)
276