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 "DisallowMacroScratchRegisterUsage.h"
33#include "FrameTracers.h"
34#include "JSCInlines.h"
35#include "JSWebAssemblyHelpers.h"
36#include "JSWebAssemblyInstance.h"
37#include "JSWebAssemblyRuntimeError.h"
38#include "MaxFrameExtentForSlowPathCall.h"
39#include "WasmCallingConvention.h"
40#include "WasmContextInlines.h"
41#include "WasmOperations.h"
42#include "WasmSignatureInlines.h"
43#include "WasmToJS.h"
44
45namespace JSC { namespace Wasm {
46
47inline void boxWasmResult(CCallHelpers& jit, Wasm::Type type, Reg src, JSValueRegs dst)
48{
49 switch (type) {
50 case Wasm::Void:
51 jit.moveTrustedValue(jsUndefined(), dst);
52 break;
53 case Wasm::Anyref:
54 case Wasm::Funcref:
55 jit.move(src.gpr(), dst.payloadGPR());
56 break;
57 case Wasm::I32:
58 jit.zeroExtend32ToPtr(src.gpr(), dst.payloadGPR());
59 jit.boxInt32(dst.payloadGPR(), dst, DoNotHaveTagRegisters);
60 break;
61 case Wasm::F32:
62 jit.convertFloatToDouble(src.fpr(), src.fpr());
63 FALLTHROUGH;
64 case Wasm::F64: {
65 jit.moveTrustedValue(jsNumber(pureNaN()), dst);
66 auto isNaN = jit.branchIfNaN(src.fpr());
67 jit.boxDouble(src.fpr(), dst, DoNotHaveTagRegisters);
68 isNaN.link(&jit);
69 break;
70 }
71 default:
72 jit.breakpoint();
73 break;
74 }
75}
76
77void marshallJSResult(CCallHelpers& jit, const Signature& signature, const CallInformation& wasmFrameConvention, const RegisterAtOffsetList& savedResultRegisters)
78{
79 if (signature.returnsVoid())
80 jit.moveTrustedValue(jsUndefined(), JSValueRegs { GPRInfo::returnValueGPR });
81 else if (signature.returnCount() == 1)
82 boxWasmResult(jit, signature.returnType(0), wasmFrameConvention.results[0].reg(), JSValueRegs { GPRInfo::returnValueGPR });
83 else {
84 IndexingType indexingType = ArrayWithUndecided;
85 JSValueRegs scratch = JSValueRegs { wasmCallingConvention().prologueScratchGPRs[1] };
86 // We can use the first floating point register as a scratch since it will always be moved onto the stack before other values.
87 FPRReg fprScratch = wasmCallingConvention().fprArgs[0].fpr();
88 for (unsigned i = 0; i < signature.returnCount(); ++i) {
89 B3::ValueRep rep = wasmFrameConvention.results[i];
90 Type type = signature.returnType(i);
91
92 if (rep.isReg()) {
93 boxWasmResult(jit, signature.returnType(i), rep.reg(), scratch);
94 jit.storeValue(scratch, CCallHelpers::Address(CCallHelpers::stackPointerRegister, savedResultRegisters.find(rep.reg())->offset() + wasmFrameConvention.headerAndArgumentStackSizeInBytes));
95 } else {
96 auto location = CCallHelpers::Address(CCallHelpers::stackPointerRegister, rep.offsetFromSP());
97 Reg tmp = type == F32 || type == F64 ? Reg(fprScratch) : Reg(scratch.gpr());
98 jit.load64ToReg(location, tmp);
99 boxWasmResult(jit, signature.returnType(i), tmp, scratch);
100 jit.storeValue(scratch, location);
101 }
102
103 switch (type) {
104 case Wasm::I32:
105 indexingType = leastUpperBoundOfIndexingTypes(indexingType, ArrayWithInt32);
106 break;
107 case Wasm::F32:
108 case Wasm::F64:
109 indexingType = leastUpperBoundOfIndexingTypes(indexingType, ArrayWithDouble);
110 break;
111 default:
112 indexingType = leastUpperBoundOfIndexingTypes(indexingType, ArrayWithContiguous);
113 break;
114 }
115 }
116
117 GPRReg wasmContextInstanceGPR = PinnedRegisterInfo::get().wasmContextInstancePointer;
118 if (Context::useFastTLS()) {
119 wasmContextInstanceGPR = GPRInfo::argumentGPR1;
120 static_assert(std::is_same_v<Wasm::Instance*, typename FunctionTraits<decltype(operationAllocateResultsArray)>::ArgumentType<1>>);
121 jit.loadWasmContextInstance(wasmContextInstanceGPR);
122 }
123
124 jit.setupArguments<decltype(operationAllocateResultsArray)>(wasmContextInstanceGPR, CCallHelpers::TrustedImmPtr(&signature), indexingType, CCallHelpers::stackPointerRegister);
125 jit.callOperation(FunctionPtr<OperationPtrTag>(operationAllocateResultsArray));
126 }
127}
128
129std::unique_ptr<InternalFunction> createJSToWasmWrapper(CCallHelpers& jit, const Signature& signature, Vector<UnlinkedWasmToWasmCall>* unlinkedWasmToWasmCalls, const ModuleInformation& info, MemoryMode mode, unsigned functionIndex)
130{
131 auto result = makeUnique<InternalFunction>();
132 jit.emitFunctionPrologue();
133
134 // FIXME Stop using 0 as codeBlocks. https://bugs.webkit.org/show_bug.cgi?id=165321
135 jit.store64(CCallHelpers::TrustedImm64(0), CCallHelpers::Address(GPRInfo::callFrameRegister, CallFrameSlot::codeBlock * static_cast<int>(sizeof(Register))));
136 MacroAssembler::DataLabelPtr calleeMoveLocation = jit.moveWithPatch(MacroAssembler::TrustedImmPtr(nullptr), GPRInfo::nonPreservedNonReturnGPR);
137 jit.storePtr(GPRInfo::nonPreservedNonReturnGPR, CCallHelpers::Address(GPRInfo::callFrameRegister, CallFrameSlot::callee * static_cast<int>(sizeof(Register))));
138 CodeLocationDataLabelPtr<WasmEntryPtrTag>* linkedCalleeMove = &result->calleeMoveLocation;
139 jit.addLinkTask([=] (LinkBuffer& linkBuffer) {
140 *linkedCalleeMove = linkBuffer.locationOf<WasmEntryPtrTag>(calleeMoveLocation);
141 });
142
143 const PinnedRegisterInfo& pinnedRegs = PinnedRegisterInfo::get();
144 RegisterSet toSave = pinnedRegs.toSave(mode);
145
146#if !ASSERT_DISABLED
147 unsigned toSaveSize = toSave.numberOfSetGPRs();
148 // They should all be callee saves.
149 toSave.filter(RegisterSet::calleeSaveRegisters());
150 ASSERT(toSave.numberOfSetGPRs() == toSaveSize);
151#endif
152
153 RegisterAtOffsetList registersToSpill(toSave, RegisterAtOffsetList::OffsetBaseType::FramePointerBased);
154 result->entrypoint.calleeSaveRegisters = registersToSpill;
155
156 size_t totalFrameSize = registersToSpill.size() * sizeof(CPURegister);
157 CallInformation wasmFrameConvention = wasmCallingConvention().callInformationFor(signature);
158 RegisterAtOffsetList savedResultRegisters = wasmFrameConvention.computeResultsOffsetList();
159 totalFrameSize += wasmFrameConvention.headerAndArgumentStackSizeInBytes;
160 totalFrameSize += savedResultRegisters.size() * sizeof(CPURegister);
161
162 totalFrameSize = WTF::roundUpToMultipleOf(stackAlignmentBytes(), totalFrameSize);
163 jit.subPtr(MacroAssembler::TrustedImm32(totalFrameSize), MacroAssembler::stackPointerRegister);
164
165 // We save all these registers regardless of having a memory or not.
166 // The reason is that we use one of these as a scratch. That said,
167 // almost all real wasm programs use memory, so it's not really
168 // worth optimizing for the case that they don't.
169 for (const RegisterAtOffset& regAtOffset : registersToSpill) {
170 GPRReg reg = regAtOffset.reg().gpr();
171 ptrdiff_t offset = regAtOffset.offset();
172 jit.storePtr(reg, CCallHelpers::Address(GPRInfo::callFrameRegister, offset));
173 }
174
175 if (wasmFrameConvention.argumentsIncludeI64 || wasmFrameConvention.resultsIncludeI64) {
176 if (Context::useFastTLS())
177 jit.loadWasmContextInstance(GPRInfo::argumentGPR2);
178 else {
179 // vmEntryToWasm passes the JSWebAssemblyInstance corresponding to Wasm::Context*'s
180 // instance as the first JS argument when we're not using fast TLS to hold the
181 // Wasm::Context*'s instance.
182 jit.loadPtr(CCallHelpers::Address(GPRInfo::callFrameRegister, CallFrameSlot::thisArgument * sizeof(EncodedJSValue)), GPRInfo::argumentGPR2);
183 jit.loadPtr(CCallHelpers::Address(GPRInfo::argumentGPR2, JSWebAssemblyInstance::offsetOfInstance()), GPRInfo::argumentGPR2);
184 }
185
186 emitThrowWasmToJSException(jit, GPRInfo::argumentGPR2, wasmFrameConvention.argumentsIncludeI64 ? ExceptionType::I64ArgumentType : ExceptionType::I64ReturnType);
187 return result;
188 }
189
190 GPRReg wasmContextInstanceGPR = pinnedRegs.wasmContextInstancePointer;
191
192 {
193 CallInformation jsFrameConvention = jsCallingConvention().callInformationFor(signature, CallRole::Callee);
194
195 CCallHelpers::Address calleeFrame = CCallHelpers::Address(MacroAssembler::stackPointerRegister, 0);
196
197 // We're going to set the pinned registers after this. So
198 // we can use this as a scratch for now since we saved it above.
199 GPRReg scratchReg = pinnedRegs.baseMemoryPointer;
200
201 if (!Context::useFastTLS()) {
202 jit.loadPtr(CCallHelpers::Address(GPRInfo::callFrameRegister, JSCallingConvention::instanceStackOffset), wasmContextInstanceGPR);
203 jit.loadPtr(CCallHelpers::Address(wasmContextInstanceGPR, JSWebAssemblyInstance::offsetOfInstance()), wasmContextInstanceGPR);
204 }
205
206 for (unsigned i = 0; i < signature.argumentCount(); i++) {
207 RELEASE_ASSERT(jsFrameConvention.params[i].isStack());
208
209 Type type = signature.argument(i);
210 CCallHelpers::Address jsParam(GPRInfo::callFrameRegister, jsFrameConvention.params[i].offsetFromFP());
211 if (wasmFrameConvention.params[i].isStackArgument()) {
212 if (type == Wasm::I32 || type == Wasm::F32) {
213 jit.load32(jsParam, scratchReg);
214 jit.store32(scratchReg, calleeFrame.withOffset(wasmFrameConvention.params[i].offsetFromSP()));
215 } else {
216 jit.load64(jsParam, scratchReg);
217 jit.store64(scratchReg, calleeFrame.withOffset(wasmFrameConvention.params[i].offsetFromSP()));
218 }
219 } else {
220 if (type == Wasm::I32 || type == Wasm::F32)
221 jit.load32ToReg(jsParam, wasmFrameConvention.params[i].reg());
222 else
223 jit.load64ToReg(jsParam, wasmFrameConvention.params[i].reg());
224 }
225 }
226 }
227
228 if (!!info.memory) {
229 GPRReg baseMemory = pinnedRegs.baseMemoryPointer;
230 GPRReg scratchOrSize = wasmCallingConvention().prologueScratchGPRs[0];
231
232 if (Context::useFastTLS())
233 jit.loadWasmContextInstance(baseMemory);
234
235 GPRReg currentInstanceGPR = Context::useFastTLS() ? baseMemory : wasmContextInstanceGPR;
236 if (isARM64E()) {
237 if (mode != Wasm::MemoryMode::Signaling)
238 scratchOrSize = pinnedRegs.sizeRegister;
239 jit.loadPtr(CCallHelpers::Address(currentInstanceGPR, Wasm::Instance::offsetOfCachedMemorySize()), scratchOrSize);
240 } else {
241 if (mode != Wasm::MemoryMode::Signaling)
242 jit.loadPtr(CCallHelpers::Address(currentInstanceGPR, Wasm::Instance::offsetOfCachedMemorySize()), pinnedRegs.sizeRegister);
243 }
244
245 jit.loadPtr(CCallHelpers::Address(currentInstanceGPR, Wasm::Instance::offsetOfCachedMemory()), baseMemory);
246 jit.cageConditionally(Gigacage::Primitive, baseMemory, scratchOrSize, scratchOrSize);
247 }
248
249 CCallHelpers::Call call = jit.threadSafePatchableNearCall();
250 unsigned functionIndexSpace = functionIndex + info.importFunctionCount();
251 ASSERT(functionIndexSpace < info.functionIndexSpaceSize());
252 jit.addLinkTask([unlinkedWasmToWasmCalls, call, functionIndexSpace] (LinkBuffer& linkBuffer) {
253 unlinkedWasmToWasmCalls->append({ linkBuffer.locationOfNearCall<WasmEntryPtrTag>(call), functionIndexSpace });
254 });
255
256 marshallJSResult(jit, signature, wasmFrameConvention, savedResultRegisters);
257
258 for (const RegisterAtOffset& regAtOffset : registersToSpill) {
259 GPRReg reg = regAtOffset.reg().gpr();
260 ASSERT(reg != GPRInfo::returnValueGPR);
261 ptrdiff_t offset = regAtOffset.offset();
262 jit.loadPtr(CCallHelpers::Address(GPRInfo::callFrameRegister, offset), reg);
263 }
264
265 jit.emitFunctionEpilogue();
266 jit.ret();
267
268 return result;
269}
270
271} } // namespace JSC::Wasm
272
273#endif // ENABLE(WEBASSEMBLY)
274