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