99702ec6bff90f60a6e364281ac157cf676ac4e4
[WebKit-https.git] / Source / JavaScriptCore / wasm / WasmBinding.cpp
1 /*
2  * Copyright (C) 2016 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 "WasmBinding.h"
28
29 #if ENABLE(WEBASSEMBLY)
30
31 #include "AssemblyHelpers.h"
32 #include "JSCJSValueInlines.h"
33 #include "JSWebAssemblyInstance.h"
34 #include "LinkBuffer.h"
35 #include "WasmCallingConvention.h"
36
37 namespace JSC { namespace Wasm {
38
39 typedef AssemblyHelpers JIT;
40
41 static void materializeImportJSCell(VM* vm, JIT& jit, unsigned importIndex, GPRReg result)
42 {
43     // We're calling out of the current WebAssembly.Instance, which is identified on VM. That Instance has a list of all its import functions.
44     jit.loadPtr(&vm->topJSWebAssemblyInstance, result);
45     jit.loadPtr(JIT::Address(result, JSWebAssemblyInstance::offsetOfImportFunction(importIndex)), result);
46 }
47
48 static MacroAssemblerCodeRef wasmToJs(VM* vm, Bag<CallLinkInfo>& callLinkInfos, SignatureIndex signatureIndex, unsigned importIndex)
49 {
50     const WasmCallingConvention& wasmCC = wasmCallingConvention();
51     const JSCCallingConvention& jsCC = jscCallingConvention();
52     const Signature* signature = SignatureInformation::get(vm, signatureIndex);
53     unsigned argCount = signature->argumentCount();
54     JIT jit(vm, nullptr);
55
56     // Below, we assume that the JS calling convention is always on the stack.
57     ASSERT(!jsCC.m_gprArgs.size());
58     ASSERT(!jsCC.m_fprArgs.size());
59
60     jit.emitFunctionPrologue();
61     jit.store64(JIT::TrustedImm32(0), JIT::Address(GPRInfo::callFrameRegister, CallFrameSlot::codeBlock * static_cast<int>(sizeof(Register)))); // FIXME Stop using 0 as codeBlocks. https://bugs.webkit.org/show_bug.cgi?id=165321
62     jit.storePtr(JIT::TrustedImmPtr(vm->webAssemblyToJSCallee.get()), JIT::Address(GPRInfo::callFrameRegister, CallFrameSlot::callee * static_cast<int>(sizeof(Register))));
63
64     // Here we assume that the JS calling convention saves at least all the wasm callee saved. We therefore don't need to save and restore more registers since the wasm callee already took care of this.
65     RegisterSet missingCalleeSaves = wasmCC.m_calleeSaveRegisters;
66     missingCalleeSaves.exclude(jsCC.m_calleeSaveRegisters);
67     ASSERT(missingCalleeSaves.isEmpty());
68
69     // FIXME perform a stack check before updating SP. https://bugs.webkit.org/show_bug.cgi?id=165546
70
71     unsigned numberOfParameters = argCount + 1; // There is a "this" argument.
72     unsigned numberOfRegsForCall = CallFrame::headerSizeInRegisters + numberOfParameters;
73     unsigned numberOfBytesForCall = numberOfRegsForCall * sizeof(Register) - sizeof(CallerFrameAndPC);
74     const unsigned stackOffset = WTF::roundUpToMultipleOf(stackAlignmentBytes(), numberOfBytesForCall);
75     jit.subPtr(MacroAssembler::TrustedImm32(stackOffset), MacroAssembler::stackPointerRegister);
76     JIT::Address calleeFrame = CCallHelpers::Address(MacroAssembler::stackPointerRegister, -static_cast<ptrdiff_t>(sizeof(CallerFrameAndPC)));
77
78     // FIXME make this a loop which switches on Signature if there are many arguments on the stack. It'll otherwise be huge for huge signatures. https://bugs.webkit.org/show_bug.cgi?id=165547
79     unsigned marshalledGPRs = 0;
80     unsigned marshalledFPRs = 0;
81     unsigned calleeFrameOffset = CallFrameSlot::firstArgument * static_cast<int>(sizeof(Register));
82     unsigned frOffset = CallFrameSlot::firstArgument * static_cast<int>(sizeof(Register));
83     for (unsigned argNum = 0; argNum < argCount; ++argNum) {
84         Type argType = signature->argument(argNum);
85         switch (argType) {
86         case Void:
87         case Func:
88         case Anyfunc:
89         case I64:
90             // FIXME: Figure out the correct behavior here. I suspect we want such a stub to throw an exception immediately
91             // if called. https://bugs.webkit.org/show_bug.cgi?id=165991
92             jit.breakpoint();
93             break;
94         case I32: {
95             GPRReg gprReg;
96             if (marshalledGPRs < wasmCC.m_gprArgs.size())
97                 gprReg = wasmCC.m_gprArgs[marshalledGPRs].gpr();
98             else {
99                 // We've already spilled all arguments, these registers are available as scratch.
100                 gprReg = GPRInfo::argumentGPR0;
101                 jit.load64(JIT::Address(GPRInfo::callFrameRegister, frOffset), gprReg);
102                 frOffset += sizeof(Register);
103             }
104             ++marshalledGPRs;
105             jit.boxInt32(gprReg, JSValueRegs(gprReg), DoNotHaveTagRegisters);
106             jit.store64(gprReg, calleeFrame.withOffset(calleeFrameOffset));
107             calleeFrameOffset += sizeof(Register);
108             break;
109         }
110         case F32: {
111             FPRReg fprReg;
112             if (marshalledFPRs < wasmCC.m_fprArgs.size())
113                 fprReg = wasmCC.m_fprArgs[marshalledFPRs].fpr();
114             else {
115                 // We've already spilled all arguments, these registers are available as scratch.
116                 fprReg = FPRInfo::argumentFPR0;
117                 jit.loadFloat(JIT::Address(GPRInfo::callFrameRegister, frOffset), fprReg);
118                 frOffset += sizeof(Register);
119             }
120             jit.convertFloatToDouble(fprReg, fprReg);
121             jit.purifyNaN(fprReg);
122             jit.storeDouble(fprReg, calleeFrame.withOffset(calleeFrameOffset));
123             calleeFrameOffset += sizeof(Register);
124             ++marshalledFPRs;
125             break;
126         }
127         case F64: {
128             FPRReg fprReg;
129             if (marshalledFPRs < wasmCC.m_fprArgs.size())
130                 fprReg = wasmCC.m_fprArgs[marshalledFPRs].fpr();
131             else {
132                 // We've already spilled all arguments, these registers are available as scratch.
133                 fprReg = FPRInfo::argumentFPR0;
134                 jit.loadDouble(JIT::Address(GPRInfo::callFrameRegister, frOffset), fprReg);
135                 frOffset += sizeof(Register);
136             }
137             jit.purifyNaN(fprReg);
138             jit.storeDouble(fprReg, calleeFrame.withOffset(calleeFrameOffset));
139             calleeFrameOffset += sizeof(Register);
140             ++marshalledFPRs;
141             break;
142         }
143         }
144     }
145
146     GPRReg importJSCellGPRReg = GPRInfo::regT0; // Callee needs to be in regT0 for slow path below.
147     ASSERT(!wasmCC.m_calleeSaveRegisters.get(importJSCellGPRReg));
148
149     materializeImportJSCell(vm, jit, importIndex, importJSCellGPRReg);
150
151     uint64_t thisArgument = ValueUndefined; // FIXME what does the WebAssembly spec say this should be? https://bugs.webkit.org/show_bug.cgi?id=165471
152     jit.store64(importJSCellGPRReg, calleeFrame.withOffset(CallFrameSlot::callee * static_cast<int>(sizeof(Register))));
153     jit.store32(JIT::TrustedImm32(numberOfParameters), calleeFrame.withOffset(CallFrameSlot::argumentCount * static_cast<int>(sizeof(Register)) + PayloadOffset));
154     jit.store64(JIT::TrustedImm64(thisArgument), calleeFrame.withOffset(CallFrameSlot::thisArgument * static_cast<int>(sizeof(Register))));
155
156     // FIXME Tail call if the wasm return type is void and no registers were spilled. https://bugs.webkit.org/show_bug.cgi?id=165488
157
158     CallLinkInfo* callLinkInfo = callLinkInfos.add();
159     callLinkInfo->setUpCall(CallLinkInfo::Call, CodeOrigin(), importJSCellGPRReg);
160     JIT::DataLabelPtr targetToCheck;
161     JIT::TrustedImmPtr initialRightValue(0);
162     JIT::Jump slowPath = jit.branchPtrWithPatch(MacroAssembler::NotEqual, importJSCellGPRReg, targetToCheck, initialRightValue);
163     JIT::Call fastCall = jit.nearCall();
164     JIT::Jump done = jit.jump();
165     slowPath.link(&jit);
166     // Callee needs to be in regT0 here.
167     jit.move(MacroAssembler::TrustedImmPtr(callLinkInfo), GPRInfo::regT2); // Link info needs to be in regT2.
168     JIT::Call slowCall = jit.nearCall();
169     done.link(&jit);
170
171     switch (signature->returnType()) {
172     case Void:
173         // Discard.
174         break;
175     case Func:
176     case Anyfunc:
177         // For the JavaScript embedding, imports with these types in their signature return are a WebAssembly.Module validation error.
178         RELEASE_ASSERT_NOT_REACHED();
179         break;
180     case I32: {
181         jit.move(JIT::TrustedImm64(TagTypeNumber), GPRInfo::returnValueGPR2);
182         JIT::Jump checkJSInt32 = jit.branch64(JIT::AboveOrEqual, GPRInfo::returnValueGPR, GPRInfo::returnValueGPR2);
183         jit.move64ToDouble(GPRInfo::returnValueGPR, FPRInfo::returnValueFPR);
184         jit.truncateDoubleToInt32(FPRInfo::returnValueFPR, GPRInfo::returnValueGPR);
185         JIT::Jump checkJSNumber = jit.branchTest64(JIT::NonZero, GPRInfo::returnValueGPR, GPRInfo::returnValueGPR2);
186         jit.abortWithReason(AHIsNotJSNumber); // FIXME Coerce when the values aren't what we expect, instead of aborting. https://bugs.webkit.org/show_bug.cgi?id=165480
187         checkJSInt32.link(&jit);
188         jit.zeroExtend32ToPtr(GPRInfo::returnValueGPR, GPRInfo::returnValueGPR);
189         checkJSNumber.link(&jit);
190         break;
191     }
192     case I64: {
193         jit.move(JIT::TrustedImm64(TagTypeNumber), GPRInfo::returnValueGPR2);
194         JIT::Jump checkJSInt32 = jit.branch64(JIT::AboveOrEqual, GPRInfo::returnValueGPR, GPRInfo::returnValueGPR2);
195         jit.move64ToDouble(GPRInfo::returnValueGPR, FPRInfo::returnValueFPR);
196         jit.truncateDoubleToInt64(FPRInfo::returnValueFPR, GPRInfo::returnValueGPR);
197         JIT::Jump checkJSNumber = jit.branchTest64(JIT::NonZero, GPRInfo::returnValueGPR, GPRInfo::returnValueGPR2);
198         jit.abortWithReason(AHIsNotJSNumber); // FIXME Coerce when the values aren't what we expect, instead of aborting. https://bugs.webkit.org/show_bug.cgi?id=165480
199         checkJSInt32.link(&jit);
200         jit.zeroExtend32ToPtr(GPRInfo::returnValueGPR, GPRInfo::returnValueGPR);
201         checkJSNumber.link(&jit);
202         break;
203     }
204     case F32: {
205         jit.move(JIT::TrustedImm64(TagTypeNumber), GPRInfo::returnValueGPR2);
206         jit.move64ToDouble(GPRInfo::returnValueGPR, FPRInfo::returnValueFPR);
207         jit.convertDoubleToFloat(FPRInfo::returnValueFPR, FPRInfo::returnValueFPR);
208         JIT::Jump checkJSInt32 = jit.branch64(JIT::AboveOrEqual, GPRInfo::returnValueGPR, GPRInfo::returnValueGPR2);
209         JIT::Jump checkJSNumber = jit.branchTest64(JIT::NonZero, GPRInfo::returnValueGPR, GPRInfo::returnValueGPR2);
210         jit.abortWithReason(AHIsNotJSNumber); // FIXME Coerce when the values aren't what we expect, instead of aborting. https://bugs.webkit.org/show_bug.cgi?id=165480
211         checkJSInt32.link(&jit);
212         jit.zeroExtend32ToPtr(GPRInfo::returnValueGPR, GPRInfo::returnValueGPR);
213         jit.convertInt64ToFloat(GPRInfo::returnValueGPR, FPRInfo::returnValueFPR);
214         checkJSNumber.link(&jit);
215         break;
216     }
217     case F64: {
218         jit.move(JIT::TrustedImm64(TagTypeNumber), GPRInfo::returnValueGPR2);
219         jit.move64ToDouble(GPRInfo::returnValueGPR, FPRInfo::returnValueFPR);
220         JIT::Jump checkJSInt32 = jit.branch64(JIT::AboveOrEqual, GPRInfo::returnValueGPR, GPRInfo::returnValueGPR2);
221         JIT::Jump checkJSNumber = jit.branchTest64(JIT::NonZero, GPRInfo::returnValueGPR, GPRInfo::returnValueGPR2);
222         jit.abortWithReason(AHIsNotJSNumber); // FIXME Coerce when the values aren't what we expect, instead of aborting. https://bugs.webkit.org/show_bug.cgi?id=165480
223         checkJSInt32.link(&jit);
224         jit.zeroExtend32ToPtr(GPRInfo::returnValueGPR, GPRInfo::returnValueGPR);
225         jit.convertInt64ToDouble(GPRInfo::returnValueGPR, FPRInfo::returnValueFPR);
226         checkJSNumber.link(&jit);
227         break;
228     }
229     }
230
231     jit.emitFunctionEpilogue();
232     jit.ret();
233
234     LinkBuffer patchBuffer(*vm, jit, GLOBAL_THUNK_ID);
235     patchBuffer.link(slowCall, FunctionPtr(vm->getCTIStub(linkCallThunkGenerator).code().executableAddress()));
236     CodeLocationLabel callReturnLocation(patchBuffer.locationOfNearCall(slowCall));
237     CodeLocationLabel hotPathBegin(patchBuffer.locationOf(targetToCheck));
238     CodeLocationNearCall hotPathOther = patchBuffer.locationOfNearCall(fastCall);
239     callLinkInfo->setCallLocations(callReturnLocation, hotPathBegin, hotPathOther);
240     String signatureDescription = SignatureInformation::get(vm, signatureIndex)->toString();
241     return FINALIZE_CODE(patchBuffer, ("WebAssembly->JavaScript import[%i] %s", importIndex, signatureDescription.ascii().data()));
242 }
243
244 static MacroAssemblerCodeRef wasmToWasm(VM* vm, unsigned importIndex)
245 {
246     const PinnedRegisterInfo& pinnedRegs = PinnedRegisterInfo::get();
247     JIT jit(vm, nullptr);
248
249     GPRReg scratch = GPRInfo::nonPreservedNonArgumentGPR;
250
251     // B3's call codegen ensures that the JSCell is a WebAssemblyFunction.
252     materializeImportJSCell(vm, jit, importIndex, scratch);
253
254     // Get the callee's WebAssembly.Instance and set it as vm.topJSWebAssemblyInstance. The caller will take care of restoring its own Instance.
255     GPRReg baseMemory = pinnedRegs.baseMemoryPointer;
256     ASSERT(baseMemory != scratch);
257     jit.loadPtr(JIT::Address(scratch, WebAssemblyFunction::offsetOfInstance()), baseMemory); // Instance*.
258     jit.storePtr(baseMemory, &vm->topJSWebAssemblyInstance);
259
260     // FIXME the following code assumes that all WebAssembly.Instance have the same pinned registers. https://bugs.webkit.org/show_bug.cgi?id=162952
261     // Set up the callee's baseMemory register as well as the memory size registers.
262     jit.loadPtr(JIT::Address(baseMemory, JSWebAssemblyInstance::offsetOfMemory()), baseMemory); // JSWebAssemblyMemory*.
263     const auto& sizeRegs = pinnedRegs.sizeRegisters;
264     ASSERT(sizeRegs.size() >= 1);
265     ASSERT(sizeRegs[0].sizeRegister != baseMemory);
266     ASSERT(sizeRegs[0].sizeRegister != scratch);
267     ASSERT(!sizeRegs[0].sizeOffset); // The following code assumes we start at 0, and calculates subsequent size registers relative to 0.
268     jit.loadPtr(JIT::Address(baseMemory, JSWebAssemblyMemory::offsetOfSize()), sizeRegs[0].sizeRegister); // Memory size.
269     jit.loadPtr(JIT::Address(baseMemory, JSWebAssemblyMemory::offsetOfMemory()), baseMemory); // WasmMemory::void*.
270     for (unsigned i = 1; i < sizeRegs.size(); ++i) {
271         ASSERT(sizeRegs[i].sizeRegister != baseMemory);
272         ASSERT(sizeRegs[i].sizeRegister != scratch);
273         jit.add64(JIT::TrustedImm32(-sizeRegs[i].sizeOffset), sizeRegs[0].sizeRegister, sizeRegs[i].sizeRegister);
274     }
275
276     // Tail call into the callee WebAssembly function.
277     jit.loadPtr(JIT::Address(scratch, WebAssemblyFunction::offsetOfWasmEntryPointCode()), scratch);
278     jit.jump(scratch);
279
280     LinkBuffer patchBuffer(*vm, jit, GLOBAL_THUNK_ID);
281     return FINALIZE_CODE(patchBuffer, ("WebAssembly->WebAssembly import[%i]", importIndex));
282 }
283
284 WasmExitStubs exitStubGenerator(VM* vm, Bag<CallLinkInfo>& callLinkInfos, SignatureIndex signatureIndex, unsigned importIndex)
285 {
286     WasmExitStubs stubs;
287     stubs.wasmToJs = wasmToJs(vm, callLinkInfos, signatureIndex, importIndex);
288     stubs.wasmToWasm = wasmToWasm(vm, importIndex);
289     return stubs;
290 }
291
292 } } // namespace JSC::Wasm
293
294 #endif // ENABLE(WEBASSEMBLY)