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