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