WebAssembly JS API: check and test in-call / out-call values
[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     for (unsigned argNum = 0; argNum < argCount; ++argNum) {
79         Type argType = signature->argument(argNum);
80         switch (argType) {
81         case Void:
82         case Func:
83         case Anyfunc:
84         case I64: {
85             // FIXME: Figure out the correct behavior here. I suspect we want such a stub to throw an exception immediately.
86             // if called. https://bugs.webkit.org/show_bug.cgi?id=165991
87             jit.breakpoint();
88             LinkBuffer patchBuffer(*vm, jit, GLOBAL_THUNK_ID);
89             return FINALIZE_CODE(patchBuffer, ("WebAssembly import[%i] stub for signature %i", importIndex, signatureIndex));
90         }
91         case I32:
92         case F32:
93         case F64:
94             continue;
95         }
96     }
97
98     // FIXME make these loops which switch 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
99     
100     // First go through the integer parameters, freeing up their register for use afterwards.
101     {
102         unsigned marshalledGPRs = 0;
103         unsigned marshalledFPRs = 0;
104         unsigned calleeFrameOffset = CallFrameSlot::firstArgument * static_cast<int>(sizeof(Register));
105         unsigned frOffset = CallFrameSlot::firstArgument * static_cast<int>(sizeof(Register));
106         for (unsigned argNum = 0; argNum < argCount; ++argNum) {
107             Type argType = signature->argument(argNum);
108             switch (argType) {
109             case Void:
110             case Func:
111             case Anyfunc:
112             case I64:
113                 RELEASE_ASSERT_NOT_REACHED(); // Handled above.
114             case I32: {
115                 GPRReg gprReg;
116                 if (marshalledGPRs < wasmCC.m_gprArgs.size())
117                     gprReg = wasmCC.m_gprArgs[marshalledGPRs].gpr();
118                 else {
119                     // We've already spilled all arguments, these registers are available as scratch.
120                     gprReg = GPRInfo::argumentGPR0;
121                     jit.load64(JIT::Address(GPRInfo::callFrameRegister, frOffset), gprReg);
122                     frOffset += sizeof(Register);
123                 }
124                 ++marshalledGPRs;
125                 jit.boxInt32(gprReg, JSValueRegs(gprReg), DoNotHaveTagRegisters);
126                 jit.store64(gprReg, calleeFrame.withOffset(calleeFrameOffset));
127                 calleeFrameOffset += sizeof(Register);
128                 break;
129             }
130             case F32:
131             case F64:
132                 // Skipped: handled below.
133                 if (marshalledFPRs >= wasmCC.m_fprArgs.size())
134                     frOffset += sizeof(Register);
135                 ++marshalledFPRs;
136                 calleeFrameOffset += sizeof(Register);
137                 break;
138             }
139         }
140     }
141     
142     {
143         // Integer registers have already been spilled, these are now available.
144         GPRReg doubleEncodeOffsetGPRReg = GPRInfo::argumentGPR0;
145         GPRReg scratch = GPRInfo::argumentGPR1;
146         bool hasMaterializedDoubleEncodeOffset = false;
147         auto materializeDoubleEncodeOffset = [&hasMaterializedDoubleEncodeOffset, &jit] (GPRReg dest) {
148             if (!hasMaterializedDoubleEncodeOffset) {
149                 static_assert(DoubleEncodeOffset == 1ll << 48, "codegen assumes this below");
150                 jit.move(JIT::TrustedImm32(1), dest);
151                 jit.lshift64(JIT::TrustedImm32(48), dest);
152                 hasMaterializedDoubleEncodeOffset = true;
153             }
154         };
155
156         unsigned marshalledGPRs = 0;
157         unsigned marshalledFPRs = 0;
158         unsigned calleeFrameOffset = CallFrameSlot::firstArgument * static_cast<int>(sizeof(Register));
159         unsigned frOffset = CallFrameSlot::firstArgument * static_cast<int>(sizeof(Register));
160         for (unsigned argNum = 0; argNum < argCount; ++argNum) {
161             Type argType = signature->argument(argNum);
162             switch (argType) {
163             case Void:
164             case Func:
165             case Anyfunc:
166             case I64:
167                 RELEASE_ASSERT_NOT_REACHED(); // Handled above.
168             case I32:
169                 // Skipped: handled above.
170                 if (marshalledGPRs < wasmCC.m_gprArgs.size())
171                     frOffset += sizeof(Register);
172                 ++marshalledGPRs;
173                 calleeFrameOffset += sizeof(Register);
174                 break;
175             case F32: {
176                 FPRReg fprReg;
177                 if (marshalledFPRs < wasmCC.m_fprArgs.size())
178                     fprReg = wasmCC.m_fprArgs[marshalledFPRs].fpr();
179                 else {
180                     // We've already spilled all arguments, these registers are available as scratch.
181                     fprReg = FPRInfo::argumentFPR0;
182                     jit.loadFloat(JIT::Address(GPRInfo::callFrameRegister, frOffset), fprReg);
183                     frOffset += sizeof(Register);
184                 }
185                 jit.convertFloatToDouble(fprReg, fprReg);
186                 jit.purifyNaN(fprReg);
187                 jit.moveDoubleTo64(fprReg, scratch);
188                 materializeDoubleEncodeOffset(doubleEncodeOffsetGPRReg);
189                 jit.add64(doubleEncodeOffsetGPRReg, scratch);
190                 jit.store64(scratch, calleeFrame.withOffset(calleeFrameOffset));
191                 calleeFrameOffset += sizeof(Register);
192                 ++marshalledFPRs;
193                 break;
194             }
195             case F64: {
196                 FPRReg fprReg;
197                 if (marshalledFPRs < wasmCC.m_fprArgs.size())
198                     fprReg = wasmCC.m_fprArgs[marshalledFPRs].fpr();
199                 else {
200                     // We've already spilled all arguments, these registers are available as scratch.
201                     fprReg = FPRInfo::argumentFPR0;
202                     jit.loadDouble(JIT::Address(GPRInfo::callFrameRegister, frOffset), fprReg);
203                     frOffset += sizeof(Register);
204                 }
205                 jit.purifyNaN(fprReg);
206                 jit.moveDoubleTo64(fprReg, scratch);
207                 materializeDoubleEncodeOffset(doubleEncodeOffsetGPRReg);
208                 jit.add64(doubleEncodeOffsetGPRReg, scratch);
209                 jit.store64(scratch, calleeFrame.withOffset(calleeFrameOffset));
210                 calleeFrameOffset += sizeof(Register);
211                 ++marshalledFPRs;
212                 break;
213             }
214             }
215         }
216     }
217
218     GPRReg importJSCellGPRReg = GPRInfo::regT0; // Callee needs to be in regT0 for slow path below.
219     ASSERT(!wasmCC.m_calleeSaveRegisters.get(importJSCellGPRReg));
220
221     materializeImportJSCell(vm, jit, importIndex, importJSCellGPRReg);
222
223     uint64_t thisArgument = ValueUndefined; // FIXME what does the WebAssembly spec say this should be? https://bugs.webkit.org/show_bug.cgi?id=165471
224     jit.store64(importJSCellGPRReg, calleeFrame.withOffset(CallFrameSlot::callee * static_cast<int>(sizeof(Register))));
225     jit.store32(JIT::TrustedImm32(numberOfParameters), calleeFrame.withOffset(CallFrameSlot::argumentCount * static_cast<int>(sizeof(Register)) + PayloadOffset));
226     jit.store64(JIT::TrustedImm64(thisArgument), calleeFrame.withOffset(CallFrameSlot::thisArgument * static_cast<int>(sizeof(Register))));
227
228     // FIXME Tail call if the wasm return type is void and no registers were spilled. https://bugs.webkit.org/show_bug.cgi?id=165488
229
230     CallLinkInfo* callLinkInfo = callLinkInfos.add();
231     callLinkInfo->setUpCall(CallLinkInfo::Call, CodeOrigin(), importJSCellGPRReg);
232     JIT::DataLabelPtr targetToCheck;
233     JIT::TrustedImmPtr initialRightValue(0);
234     JIT::Jump slowPath = jit.branchPtrWithPatch(MacroAssembler::NotEqual, importJSCellGPRReg, targetToCheck, initialRightValue);
235     JIT::Call fastCall = jit.nearCall();
236     JIT::Jump done = jit.jump();
237     slowPath.link(&jit);
238     // Callee needs to be in regT0 here.
239     jit.move(MacroAssembler::TrustedImmPtr(callLinkInfo), GPRInfo::regT2); // Link info needs to be in regT2.
240     JIT::Call slowCall = jit.nearCall();
241     done.link(&jit);
242
243     switch (signature->returnType()) {
244     case Void:
245         // Discard.
246         break;
247     case Func:
248     case Anyfunc:
249         // For the JavaScript embedding, imports with these types in their signature return are a WebAssembly.Module validation error.
250         RELEASE_ASSERT_NOT_REACHED();
251         break;
252     case I32: {
253         jit.move(JIT::TrustedImm64(TagTypeNumber), GPRInfo::returnValueGPR2);
254         JIT::Jump checkJSInt32 = jit.branch64(JIT::AboveOrEqual, GPRInfo::returnValueGPR, GPRInfo::returnValueGPR2);
255         jit.move64ToDouble(GPRInfo::returnValueGPR, FPRInfo::returnValueFPR);
256         jit.truncateDoubleToInt32(FPRInfo::returnValueFPR, GPRInfo::returnValueGPR);
257         JIT::Jump checkJSNumber = jit.branchTest64(JIT::NonZero, GPRInfo::returnValueGPR, GPRInfo::returnValueGPR2);
258         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
259         checkJSInt32.link(&jit);
260         jit.zeroExtend32ToPtr(GPRInfo::returnValueGPR, GPRInfo::returnValueGPR);
261         checkJSNumber.link(&jit);
262         break;
263     }
264     case I64: {
265         jit.move(JIT::TrustedImm64(TagTypeNumber), GPRInfo::returnValueGPR2);
266         JIT::Jump checkJSInt32 = jit.branch64(JIT::AboveOrEqual, GPRInfo::returnValueGPR, GPRInfo::returnValueGPR2);
267         jit.move64ToDouble(GPRInfo::returnValueGPR, FPRInfo::returnValueFPR);
268         jit.truncateDoubleToInt64(FPRInfo::returnValueFPR, GPRInfo::returnValueGPR);
269         JIT::Jump checkJSNumber = jit.branchTest64(JIT::NonZero, GPRInfo::returnValueGPR, GPRInfo::returnValueGPR2);
270         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
271         checkJSInt32.link(&jit);
272         jit.zeroExtend32ToPtr(GPRInfo::returnValueGPR, GPRInfo::returnValueGPR);
273         checkJSNumber.link(&jit);
274         break;
275     }
276     case F32: {
277         jit.move(JIT::TrustedImm64(TagTypeNumber), GPRInfo::returnValueGPR2);
278         jit.move64ToDouble(GPRInfo::returnValueGPR, FPRInfo::returnValueFPR);
279         jit.convertDoubleToFloat(FPRInfo::returnValueFPR, FPRInfo::returnValueFPR);
280         JIT::Jump checkJSInt32 = jit.branch64(JIT::AboveOrEqual, GPRInfo::returnValueGPR, GPRInfo::returnValueGPR2);
281         JIT::Jump checkJSNumber = jit.branchTest64(JIT::NonZero, GPRInfo::returnValueGPR, GPRInfo::returnValueGPR2);
282         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
283         checkJSInt32.link(&jit);
284         jit.zeroExtend32ToPtr(GPRInfo::returnValueGPR, GPRInfo::returnValueGPR);
285         jit.convertInt64ToFloat(GPRInfo::returnValueGPR, FPRInfo::returnValueFPR);
286         checkJSNumber.link(&jit);
287         break;
288     }
289     case F64: {
290         jit.move(JIT::TrustedImm64(TagTypeNumber), GPRInfo::returnValueGPR2);
291         jit.move64ToDouble(GPRInfo::returnValueGPR, FPRInfo::returnValueFPR);
292         JIT::Jump checkJSInt32 = jit.branch64(JIT::AboveOrEqual, GPRInfo::returnValueGPR, GPRInfo::returnValueGPR2);
293         JIT::Jump checkJSNumber = jit.branchTest64(JIT::NonZero, GPRInfo::returnValueGPR, GPRInfo::returnValueGPR2);
294         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
295         checkJSInt32.link(&jit);
296         jit.zeroExtend32ToPtr(GPRInfo::returnValueGPR, GPRInfo::returnValueGPR);
297         jit.convertInt64ToDouble(GPRInfo::returnValueGPR, FPRInfo::returnValueFPR);
298         checkJSNumber.link(&jit);
299         break;
300     }
301     }
302
303     jit.emitFunctionEpilogue();
304     jit.ret();
305
306     LinkBuffer patchBuffer(*vm, jit, GLOBAL_THUNK_ID);
307     patchBuffer.link(slowCall, FunctionPtr(vm->getCTIStub(linkCallThunkGenerator).code().executableAddress()));
308     CodeLocationLabel callReturnLocation(patchBuffer.locationOfNearCall(slowCall));
309     CodeLocationLabel hotPathBegin(patchBuffer.locationOf(targetToCheck));
310     CodeLocationNearCall hotPathOther = patchBuffer.locationOfNearCall(fastCall);
311     callLinkInfo->setCallLocations(callReturnLocation, hotPathBegin, hotPathOther);
312     String signatureDescription = SignatureInformation::get(vm, signatureIndex)->toString();
313     return FINALIZE_CODE(patchBuffer, ("WebAssembly->JavaScript import[%i] %s", importIndex, signatureDescription.ascii().data()));
314 }
315
316 static MacroAssemblerCodeRef wasmToWasm(VM* vm, unsigned importIndex)
317 {
318     const PinnedRegisterInfo& pinnedRegs = PinnedRegisterInfo::get();
319     JIT jit(vm, nullptr);
320
321     GPRReg scratch = GPRInfo::nonPreservedNonArgumentGPR;
322
323     // B3's call codegen ensures that the JSCell is a WebAssemblyFunction.
324     materializeImportJSCell(vm, jit, importIndex, scratch);
325
326     // Get the callee's WebAssembly.Instance and set it as vm.topJSWebAssemblyInstance. The caller will take care of restoring its own Instance.
327     GPRReg baseMemory = pinnedRegs.baseMemoryPointer;
328     ASSERT(baseMemory != scratch);
329     jit.loadPtr(JIT::Address(scratch, WebAssemblyFunction::offsetOfInstance()), baseMemory); // Instance*.
330     jit.storePtr(baseMemory, &vm->topJSWebAssemblyInstance);
331
332     // FIXME the following code assumes that all WebAssembly.Instance have the same pinned registers. https://bugs.webkit.org/show_bug.cgi?id=162952
333     // Set up the callee's baseMemory register as well as the memory size registers.
334     jit.loadPtr(JIT::Address(baseMemory, JSWebAssemblyInstance::offsetOfMemory()), baseMemory); // JSWebAssemblyMemory*.
335     const auto& sizeRegs = pinnedRegs.sizeRegisters;
336     ASSERT(sizeRegs.size() >= 1);
337     ASSERT(sizeRegs[0].sizeRegister != baseMemory);
338     ASSERT(sizeRegs[0].sizeRegister != scratch);
339     ASSERT(!sizeRegs[0].sizeOffset); // The following code assumes we start at 0, and calculates subsequent size registers relative to 0.
340     jit.loadPtr(JIT::Address(baseMemory, JSWebAssemblyMemory::offsetOfSize()), sizeRegs[0].sizeRegister); // Memory size.
341     jit.loadPtr(JIT::Address(baseMemory, JSWebAssemblyMemory::offsetOfMemory()), baseMemory); // WasmMemory::void*.
342     for (unsigned i = 1; i < sizeRegs.size(); ++i) {
343         ASSERT(sizeRegs[i].sizeRegister != baseMemory);
344         ASSERT(sizeRegs[i].sizeRegister != scratch);
345         jit.add64(JIT::TrustedImm32(-sizeRegs[i].sizeOffset), sizeRegs[0].sizeRegister, sizeRegs[i].sizeRegister);
346     }
347
348     // Tail call into the callee WebAssembly function.
349     jit.loadPtr(JIT::Address(scratch, WebAssemblyFunction::offsetOfWasmEntryPointCode()), scratch);
350     jit.jump(scratch);
351
352     LinkBuffer patchBuffer(*vm, jit, GLOBAL_THUNK_ID);
353     return FINALIZE_CODE(patchBuffer, ("WebAssembly->WebAssembly import[%i]", importIndex));
354 }
355
356 WasmExitStubs exitStubGenerator(VM* vm, Bag<CallLinkInfo>& callLinkInfos, SignatureIndex signatureIndex, unsigned importIndex)
357 {
358     WasmExitStubs stubs;
359     stubs.wasmToJs = wasmToJs(vm, callLinkInfos, signatureIndex, importIndex);
360     stubs.wasmToWasm = wasmToWasm(vm, importIndex);
361     return stubs;
362 }
363
364 } } // namespace JSC::Wasm
365
366 #endif // ENABLE(WEBASSEMBLY)