1/*
2 * Copyright (C) 2018-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#pragma once
27
28#include "GetPutInfo.h"
29#include "Interpreter.h"
30#include "Label.h"
31#include "OpcodeSize.h"
32#include "ProfileTypeBytecodeFlag.h"
33#include "PutByIdFlags.h"
34#include "ResultType.h"
35#include "SymbolTableOrScopeDepth.h"
36#include "VirtualRegister.h"
37#include <type_traits>
38
39namespace JSC {
40
41enum FitsAssertion {
42 Assert,
43 NoAssert
44};
45
46// Fits template
47template<typename, OpcodeSize, typename = std::true_type>
48struct Fits;
49
50// Implicit conversion for types of the same size
51template<typename T, OpcodeSize size>
52struct Fits<T, size, std::enable_if_t<sizeof(T) == size, std::true_type>> {
53 using TargetType = typename TypeBySize<size>::unsignedType;
54
55 static bool check(T) { return true; }
56
57 static TargetType convert(T t) { return bitwise_cast<TargetType>(t); }
58
59 template<class T1 = T, OpcodeSize size1 = size, typename = std::enable_if_t<!std::is_same<T1, TargetType>::value, std::true_type>>
60 static T1 convert(TargetType t) { return bitwise_cast<T1>(t); }
61};
62
63template<typename T, OpcodeSize size>
64struct Fits<T, size, std::enable_if_t<std::is_integral<T>::value && sizeof(T) != size && !std::is_same<bool, T>::value, std::true_type>> {
65 using TargetType = std::conditional_t<std::is_unsigned<T>::value, typename TypeBySize<size>::unsignedType, typename TypeBySize<size>::signedType>;
66
67 static bool check(T t)
68 {
69 return t >= std::numeric_limits<TargetType>::min() && t <= std::numeric_limits<TargetType>::max();
70 }
71
72 static TargetType convert(T t)
73 {
74 ASSERT(check(t));
75 return static_cast<TargetType>(t);
76 }
77
78 template<class T1 = T, OpcodeSize size1 = size, typename TargetType1 = TargetType, typename = std::enable_if_t<!std::is_same<T1, TargetType1>::value, std::true_type>>
79 static T1 convert(TargetType1 t) { return static_cast<T1>(t); }
80};
81
82template<OpcodeSize size>
83struct Fits<bool, size, std::enable_if_t<size != sizeof(bool), std::true_type>> : public Fits<uint8_t, size> {
84 using Base = Fits<uint8_t, size>;
85
86 static bool check(bool e) { return Base::check(static_cast<uint8_t>(e)); }
87
88 static typename Base::TargetType convert(bool e)
89 {
90 return Base::convert(static_cast<uint8_t>(e));
91 }
92
93 static bool convert(typename Base::TargetType e)
94 {
95 return Base::convert(e);
96 }
97};
98
99template<OpcodeSize size>
100struct FirstConstant;
101
102template<>
103struct FirstConstant<OpcodeSize::Narrow> {
104 static constexpr int index = FirstConstantRegisterIndex8;
105};
106
107template<>
108struct FirstConstant<OpcodeSize::Wide16> {
109 static constexpr int index = FirstConstantRegisterIndex16;
110};
111
112template<OpcodeSize size>
113struct Fits<VirtualRegister, size, std::enable_if_t<size != OpcodeSize::Wide32, std::true_type>> {
114 // Narrow:
115 // -128..-1 local variables
116 // 0..15 arguments
117 // 16..127 constants
118 //
119 // Wide16:
120 // -2**15..-1 local variables
121 // 0..64 arguments
122 // 64..2**15-1 constants
123
124 using TargetType = typename TypeBySize<size>::signedType;
125
126 static constexpr int s_firstConstantIndex = FirstConstant<size>::index;
127 static bool check(VirtualRegister r)
128 {
129 if (r.isConstant())
130 return (s_firstConstantIndex + r.toConstantIndex()) <= std::numeric_limits<TargetType>::max();
131 return r.offset() >= std::numeric_limits<TargetType>::min() && r.offset() < s_firstConstantIndex;
132 }
133
134 static TargetType convert(VirtualRegister r)
135 {
136 ASSERT(check(r));
137 if (r.isConstant())
138 return static_cast<TargetType>(s_firstConstantIndex + r.toConstantIndex());
139 return static_cast<TargetType>(r.offset());
140 }
141
142 static VirtualRegister convert(TargetType u)
143 {
144 int i = static_cast<int>(static_cast<TargetType>(u));
145 if (i >= s_firstConstantIndex)
146 return VirtualRegister { (i - s_firstConstantIndex) + FirstConstantRegisterIndex };
147 return VirtualRegister { i };
148 }
149};
150
151template<OpcodeSize size>
152struct Fits<SymbolTableOrScopeDepth, size, std::enable_if_t<size != OpcodeSize::Wide32, std::true_type>> : public Fits<unsigned, size> {
153 static_assert(sizeof(SymbolTableOrScopeDepth) == sizeof(unsigned));
154 using TargetType = typename TypeBySize<size>::unsignedType;
155 using Base = Fits<unsigned, size>;
156
157 static bool check(SymbolTableOrScopeDepth u) { return Base::check(u.raw()); }
158
159 static TargetType convert(SymbolTableOrScopeDepth u)
160 {
161 return Base::convert(u.raw());
162 }
163
164 static SymbolTableOrScopeDepth convert(TargetType u)
165 {
166 return SymbolTableOrScopeDepth::raw(Base::convert(u));
167 }
168};
169
170template<OpcodeSize size>
171struct Fits<GetPutInfo, size, std::enable_if_t<size != OpcodeSize::Wide32, std::true_type>> {
172 using TargetType = typename TypeBySize<size>::unsignedType;
173
174 // 13 Resolve Types
175 // 3 Initialization Modes
176 // 2 Resolve Modes
177 //
178 // Try to encode encode as
179 //
180 // initialization mode
181 // v
182 // free bit-> 0|0000|00|0
183 // ^ ^
184 // resolve type resolve mode
185 static constexpr int s_resolveTypeMax = 1 << 4;
186 static constexpr int s_initializationModeMax = 1 << 2;
187 static constexpr int s_resolveModeMax = 1 << 1;
188
189 static constexpr int s_resolveTypeBits = (s_resolveTypeMax - 1) << 3;
190 static constexpr int s_initializationModeBits = (s_initializationModeMax - 1) << 1;
191 static constexpr int s_resolveModeBits = (s_resolveModeMax - 1);
192
193 static_assert(!(s_resolveTypeBits & s_initializationModeBits & s_resolveModeBits), "There should be no intersection between ResolveMode, ResolveType and InitializationMode");
194
195 static bool check(GetPutInfo gpi)
196 {
197 auto resolveType = static_cast<int>(gpi.resolveType());
198 auto initializationMode = static_cast<int>(gpi.initializationMode());
199 auto resolveMode = static_cast<int>(gpi.resolveMode());
200 return resolveType < s_resolveTypeMax && initializationMode < s_initializationModeMax && resolveMode < s_resolveModeMax;
201 }
202
203 static TargetType convert(GetPutInfo gpi)
204 {
205 ASSERT(check(gpi));
206 auto resolveType = static_cast<uint8_t>(gpi.resolveType());
207 auto initializationMode = static_cast<uint8_t>(gpi.initializationMode());
208 auto resolveMode = static_cast<uint8_t>(gpi.resolveMode());
209 return (resolveType << 3) | (initializationMode << 1) | resolveMode;
210 }
211
212 static GetPutInfo convert(TargetType gpi)
213 {
214 auto resolveType = static_cast<ResolveType>((gpi & s_resolveTypeBits) >> 3);
215 auto initializationMode = static_cast<InitializationMode>((gpi & s_initializationModeBits) >> 1);
216 auto resolveMode = static_cast<ResolveMode>(gpi & s_resolveModeBits);
217 return GetPutInfo(resolveMode, resolveType, initializationMode);
218 }
219};
220
221template<typename E, OpcodeSize size>
222struct Fits<E, size, std::enable_if_t<sizeof(E) != size && std::is_enum<E>::value, std::true_type>> : public Fits<std::underlying_type_t<E>, size> {
223 using Base = Fits<std::underlying_type_t<E>, size>;
224
225 static bool check(E e) { return Base::check(static_cast<std::underlying_type_t<E>>(e)); }
226
227 static typename Base::TargetType convert(E e)
228 {
229 return Base::convert(static_cast<std::underlying_type_t<E>>(e));
230 }
231
232 static E convert(typename Base::TargetType e)
233 {
234 return static_cast<E>(Base::convert(e));
235 }
236};
237
238template<OpcodeSize size>
239struct Fits<ResultType, size, std::enable_if_t<sizeof(ResultType) != size, std::true_type>> : public Fits<uint8_t, size> {
240 static_assert(sizeof(ResultType) == sizeof(uint8_t));
241 using Base = Fits<uint8_t, size>;
242
243 static bool check(ResultType type) { return Base::check(type.bits()); }
244
245 static typename Base::TargetType convert(ResultType type) { return Base::convert(type.bits()); }
246
247 static ResultType convert(typename Base::TargetType type) { return ResultType(Base::convert(type)); }
248};
249
250template<OpcodeSize size>
251struct Fits<OperandTypes, size, std::enable_if_t<sizeof(OperandTypes) != size, std::true_type>> {
252 static_assert(sizeof(OperandTypes) == sizeof(uint16_t));
253 using TargetType = typename TypeBySize<size>::unsignedType;
254
255 // a pair of (ResultType::Type, ResultType::Type) - try to fit each type into 4 bits
256 // additionally, encode unknown types as 0 rather than the | of all types
257 static constexpr unsigned typeWidth = 4;
258 static constexpr unsigned maxType = (1 << typeWidth) - 1;
259
260 static bool check(OperandTypes types)
261 {
262 if (size == OpcodeSize::Narrow) {
263 auto first = types.first().bits();
264 auto second = types.second().bits();
265 if (first == ResultType::unknownType().bits())
266 first = 0;
267 if (second == ResultType::unknownType().bits())
268 second = 0;
269 return first <= maxType && second <= maxType;
270 }
271 return true;
272 }
273
274 static TargetType convert(OperandTypes types)
275 {
276 if (size == OpcodeSize::Narrow) {
277 ASSERT(check(types));
278 auto first = types.first().bits();
279 auto second = types.second().bits();
280 if (first == ResultType::unknownType().bits())
281 first = 0;
282 if (second == ResultType::unknownType().bits())
283 second = 0;
284 return (first << typeWidth) | second;
285 }
286 return static_cast<TargetType>(types.bits());
287 }
288
289 static OperandTypes convert(TargetType types)
290 {
291 if (size == OpcodeSize::Narrow) {
292 auto first = types >> typeWidth;
293 auto second = types & maxType;
294 if (!first)
295 first = ResultType::unknownType().bits();
296 if (!second)
297 second = ResultType::unknownType().bits();
298 return OperandTypes(ResultType(first), ResultType(second));
299 }
300 return OperandTypes::fromBits(static_cast<uint16_t>(types));
301 }
302};
303
304template<OpcodeSize size, typename GeneratorTraits>
305struct Fits<GenericBoundLabel<GeneratorTraits>, size> : public Fits<int, size> {
306 // This is a bit hacky: we need to delay computing jump targets, since we
307 // might have to emit `nop`s to align the instructions stream. Additionally,
308 // we have to compute the target before we start writing to the instruction
309 // stream, since the offset is computed from the start of the bytecode. We
310 // achieve this by computing the target when we `check` and saving it, then
311 // later we use the saved target when we call convert.
312
313 using Base = Fits<int, size>;
314 static bool check(GenericBoundLabel<GeneratorTraits>& label)
315 {
316 return Base::check(label.saveTarget());
317 }
318
319 static typename Base::TargetType convert(GenericBoundLabel<GeneratorTraits>& label)
320 {
321 return Base::convert(label.commitTarget());
322 }
323
324 static GenericBoundLabel<GeneratorTraits> convert(typename Base::TargetType target)
325 {
326 return GenericBoundLabel<GeneratorTraits>(Base::convert(target));
327 }
328};
329
330} // namespace JSC
331