WebAssembly: JSC::link* shouldn't need a CodeBlock
[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 WasmToJSStub importStubGenerator(VM* vm, Bag<CallLinkInfo>& callLinkInfos, Signature* signature, unsigned importIndex)
40 {
41     const WasmCallingConvention& wasmCC = wasmCallingConvention();
42     const JSCCallingConvention& jsCC = jscCallingConvention();
43     unsigned argCount = signature->arguments.size();
44     typedef AssemblyHelpers JIT;
45     JIT jit(vm, nullptr);
46
47     // Below, we assume that the JS calling convention is always on the stack.
48     ASSERT(!jsCC.m_gprArgs.size());
49     ASSERT(!jsCC.m_fprArgs.size());
50
51     jit.emitFunctionPrologue();
52     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
53     jit.storePtr(JIT::TrustedImmPtr(vm->webAssemblyToJSCallee.get()), JIT::Address(GPRInfo::callFrameRegister, CallFrameSlot::callee * static_cast<int>(sizeof(Register))));
54
55     // 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.
56     RegisterSet missingCalleeSaves = wasmCC.m_calleeSaveRegisters;
57     missingCalleeSaves.exclude(jsCC.m_calleeSaveRegisters);
58     ASSERT(missingCalleeSaves.isEmpty());
59
60     // FIXME perform a stack check before updating SP. https://bugs.webkit.org/show_bug.cgi?id=165546
61
62     unsigned numberOfParameters = argCount + 1; // There is a "this" argument.
63     unsigned numberOfRegsForCall = CallFrame::headerSizeInRegisters + numberOfParameters;
64     unsigned numberOfBytesForCall = numberOfRegsForCall * sizeof(Register) - sizeof(CallerFrameAndPC);
65     const unsigned stackOffset = WTF::roundUpToMultipleOf(stackAlignmentBytes(), numberOfBytesForCall);
66     jit.subPtr(MacroAssembler::TrustedImm32(stackOffset), MacroAssembler::stackPointerRegister);
67     JIT::Address calleeFrame = CCallHelpers::Address(MacroAssembler::stackPointerRegister, -static_cast<ptrdiff_t>(sizeof(CallerFrameAndPC)));
68
69     // 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
70     unsigned marshalledGPRs = 0;
71     unsigned marshalledFPRs = 0;
72     unsigned calleeFrameOffset = CallFrameSlot::firstArgument * static_cast<int>(sizeof(Register));
73     unsigned frOffset = CallFrameSlot::firstArgument * static_cast<int>(sizeof(Register));
74     for (unsigned argNum = 0; argNum < argCount; ++argNum) {
75         Type argType = signature->arguments[argNum];
76         switch (argType) {
77         case Void:
78         case Func:
79         case Anyfunc:
80         case I64:
81             // For the JavaScript embedding, imports with these types in their signature arguments are a WebAssembly.Module validation error.
82             RELEASE_ASSERT_NOT_REACHED();
83             break;
84         case I32: {
85             GPRReg gprReg;
86             if (marshalledGPRs < wasmCC.m_gprArgs.size())
87                 gprReg = wasmCC.m_gprArgs[marshalledGPRs].gpr();
88             else {
89                 // We've already spilled all arguments, these registers are available as scratch.
90                 gprReg = GPRInfo::argumentGPR0;
91                 jit.load64(JIT::Address(GPRInfo::callFrameRegister, frOffset), gprReg);
92                 frOffset += sizeof(Register);
93             }
94             ++marshalledGPRs;
95             jit.boxInt32(gprReg, JSValueRegs(gprReg), DoNotHaveTagRegisters);
96             jit.store64(gprReg, calleeFrame.withOffset(calleeFrameOffset));
97             calleeFrameOffset += sizeof(Register);
98             break;
99         }
100         case F32: {
101             FPRReg fprReg;
102             if (marshalledFPRs < wasmCC.m_fprArgs.size())
103                 fprReg = wasmCC.m_fprArgs[marshalledFPRs].fpr();
104             else {
105                 // We've already spilled all arguments, these registers are available as scratch.
106                 fprReg = FPRInfo::argumentFPR0;
107                 jit.loadFloat(JIT::Address(GPRInfo::callFrameRegister, frOffset), fprReg);
108                 frOffset += sizeof(Register);
109             }
110             jit.convertFloatToDouble(fprReg, fprReg);
111             jit.purifyNaN(fprReg);
112             jit.storeDouble(fprReg, calleeFrame.withOffset(calleeFrameOffset));
113             calleeFrameOffset += sizeof(Register);
114             ++marshalledFPRs;
115             break;
116         }
117         case F64: {
118             FPRReg fprReg;
119             if (marshalledFPRs < wasmCC.m_fprArgs.size())
120                 fprReg = wasmCC.m_fprArgs[marshalledFPRs].fpr();
121             else {
122                 // We've already spilled all arguments, these registers are available as scratch.
123                 fprReg = FPRInfo::argumentFPR0;
124                 jit.loadDouble(JIT::Address(GPRInfo::callFrameRegister, frOffset), fprReg);
125                 frOffset += sizeof(Register);
126             }
127             jit.purifyNaN(fprReg);
128             jit.storeDouble(fprReg, calleeFrame.withOffset(calleeFrameOffset));
129             calleeFrameOffset += sizeof(Register);
130             ++marshalledFPRs;
131             break;
132         }
133         }
134     }
135
136     GPRReg importJSCellGPRReg = GPRInfo::regT0; // Callee needs to be in regT0 for slow path below.
137     ASSERT(!wasmCC.m_calleeSaveRegisters.get(importJSCellGPRReg));
138
139     // Each JS -> wasm entry sets the WebAssembly.Instance whose export is being called. We're calling out of this Instance, and can therefore figure out the import being called.
140     jit.loadPtr(&vm->topJSWebAssemblyInstance, importJSCellGPRReg);
141     jit.loadPtr(JIT::Address(importJSCellGPRReg, JSWebAssemblyInstance::offsetOfImportFunction(importIndex)), importJSCellGPRReg);
142
143     uint64_t thisArgument = ValueUndefined; // FIXME what does the WebAssembly spec say this should be? https://bugs.webkit.org/show_bug.cgi?id=165471
144     jit.store64(importJSCellGPRReg, calleeFrame.withOffset(CallFrameSlot::callee * static_cast<int>(sizeof(Register))));
145     jit.store32(JIT::TrustedImm32(numberOfParameters), calleeFrame.withOffset(CallFrameSlot::argumentCount * static_cast<int>(sizeof(Register)) + PayloadOffset));
146     jit.store64(JIT::TrustedImm64(thisArgument), calleeFrame.withOffset(CallFrameSlot::thisArgument * static_cast<int>(sizeof(Register))));
147
148     // FIXME Tail call if the wasm return type is void and no registers were spilled. https://bugs.webkit.org/show_bug.cgi?id=165488
149
150     CallLinkInfo* callLinkInfo = callLinkInfos.add();
151     callLinkInfo->setUpCall(CallLinkInfo::Call, CodeOrigin(), importJSCellGPRReg);
152     JIT::DataLabelPtr targetToCheck;
153     JIT::TrustedImmPtr initialRightValue(0);
154     JIT::Jump slowPath = jit.branchPtrWithPatch(MacroAssembler::NotEqual, importJSCellGPRReg, targetToCheck, initialRightValue);
155     JIT::Call fastCall = jit.nearCall();
156     JIT::Jump done = jit.jump();
157     slowPath.link(&jit);
158     // Callee needs to be in regT0 here.
159     jit.move(MacroAssembler::TrustedImmPtr(callLinkInfo), GPRInfo::regT2); // Link info needs to be in regT2.
160     JIT::Call slowCall = jit.nearCall();
161     done.link(&jit);
162
163     switch (signature->returnType) {
164     case Void:
165         // Discard.
166         break;
167     case Func:
168     case Anyfunc:
169         // For the JavaScript embedding, imports with these types in their signature return are a WebAssembly.Module validation error.
170         RELEASE_ASSERT_NOT_REACHED();
171         break;
172     case I32: {
173         jit.move(JIT::TrustedImm64(TagTypeNumber), GPRInfo::returnValueGPR2);
174         JIT::Jump checkJSInt32 = jit.branch64(JIT::AboveOrEqual, GPRInfo::returnValueGPR, GPRInfo::returnValueGPR2);
175         jit.move64ToDouble(GPRInfo::returnValueGPR, FPRInfo::returnValueFPR);
176         jit.truncateDoubleToInt32(FPRInfo::returnValueFPR, GPRInfo::returnValueGPR);
177         JIT::Jump checkJSNumber = jit.branchTest64(JIT::NonZero, GPRInfo::returnValueGPR, GPRInfo::returnValueGPR2);
178         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
179         checkJSInt32.link(&jit);
180         jit.zeroExtend32ToPtr(GPRInfo::returnValueGPR, GPRInfo::returnValueGPR);
181         checkJSNumber.link(&jit);
182         break;
183     }
184     case I64: {
185         jit.move(JIT::TrustedImm64(TagTypeNumber), GPRInfo::returnValueGPR2);
186         JIT::Jump checkJSInt32 = jit.branch64(JIT::AboveOrEqual, GPRInfo::returnValueGPR, GPRInfo::returnValueGPR2);
187         jit.move64ToDouble(GPRInfo::returnValueGPR, FPRInfo::returnValueFPR);
188         jit.truncateDoubleToInt64(FPRInfo::returnValueFPR, GPRInfo::returnValueGPR);
189         JIT::Jump checkJSNumber = jit.branchTest64(JIT::NonZero, GPRInfo::returnValueGPR, GPRInfo::returnValueGPR2);
190         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
191         checkJSInt32.link(&jit);
192         jit.zeroExtend32ToPtr(GPRInfo::returnValueGPR, GPRInfo::returnValueGPR);
193         checkJSNumber.link(&jit);
194         break;
195     }
196     case F32: {
197         jit.move(JIT::TrustedImm64(TagTypeNumber), GPRInfo::returnValueGPR2);
198         jit.move64ToDouble(GPRInfo::returnValueGPR, FPRInfo::returnValueFPR);
199         jit.convertDoubleToFloat(FPRInfo::returnValueFPR, FPRInfo::returnValueFPR);
200         JIT::Jump checkJSInt32 = jit.branch64(JIT::AboveOrEqual, GPRInfo::returnValueGPR, GPRInfo::returnValueGPR2);
201         JIT::Jump checkJSNumber = jit.branchTest64(JIT::NonZero, GPRInfo::returnValueGPR, GPRInfo::returnValueGPR2);
202         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
203         checkJSInt32.link(&jit);
204         jit.zeroExtend32ToPtr(GPRInfo::returnValueGPR, GPRInfo::returnValueGPR);
205         jit.convertInt64ToFloat(GPRInfo::returnValueGPR, FPRInfo::returnValueFPR);
206         checkJSNumber.link(&jit);
207         break;
208     }
209     case F64: {
210         jit.move(JIT::TrustedImm64(TagTypeNumber), GPRInfo::returnValueGPR2);
211         jit.move64ToDouble(GPRInfo::returnValueGPR, FPRInfo::returnValueFPR);
212         JIT::Jump checkJSInt32 = jit.branch64(JIT::AboveOrEqual, GPRInfo::returnValueGPR, GPRInfo::returnValueGPR2);
213         JIT::Jump checkJSNumber = jit.branchTest64(JIT::NonZero, GPRInfo::returnValueGPR, GPRInfo::returnValueGPR2);
214         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
215         checkJSInt32.link(&jit);
216         jit.zeroExtend32ToPtr(GPRInfo::returnValueGPR, GPRInfo::returnValueGPR);
217         jit.convertInt64ToDouble(GPRInfo::returnValueGPR, FPRInfo::returnValueFPR);
218         checkJSNumber.link(&jit);
219         break;
220     }
221     }
222
223     jit.emitFunctionEpilogue();
224     jit.ret();
225
226     LinkBuffer patchBuffer(*vm, jit, GLOBAL_THUNK_ID);
227     patchBuffer.link(slowCall, FunctionPtr(vm->getCTIStub(linkCallThunkGenerator).code().executableAddress()));
228     CodeLocationLabel callReturnLocation(patchBuffer.locationOfNearCall(slowCall));
229     CodeLocationLabel hotPathBegin(patchBuffer.locationOf(targetToCheck));
230     CodeLocationNearCall hotPathOther = patchBuffer.locationOfNearCall(fastCall);
231     callLinkInfo->setCallLocations(callReturnLocation, hotPathBegin, hotPathOther);
232     return FINALIZE_CODE(patchBuffer, ("WebAssembly import[%i] stub for signature %p", importIndex, signature));
233 }
234
235 } } // namespace JSC::Wasm
236
237 #endif // ENABLE(WEBASSEMBLY)