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