1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  * vim: set ts=8 sts=2 et sw=2 tw=80:
3  *
4  * Copyright 2021 Mozilla Foundation
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 
19 #ifndef wasm_expr_type_h
20 #define wasm_expr_type_h
21 
22 #include <stdint.h>
23 
24 #include "wasm/WasmTypeDef.h"
25 #include "wasm/WasmValType.h"
26 
27 namespace js {
28 namespace wasm {
29 
30 template <typename PointerType>
31 class TaggedValue {
32  public:
33   enum Kind {
34     ImmediateKind1 = 0,
35     ImmediateKind2 = 1,
36     PointerKind1 = 2,
37     PointerKind2 = 3
38   };
39   using PackedRepr = uintptr_t;
40 
41  private:
42   PackedRepr bits_;
43 
44   static constexpr PackedRepr PayloadShift = 2;
45   static constexpr PackedRepr KindMask = 0x3;
46   static constexpr PackedRepr PointerKindBit = 0x2;
47 
IsPointerKind(Kind kind)48   constexpr static bool IsPointerKind(Kind kind) {
49     return PackedRepr(kind) & PointerKindBit;
50   }
IsImmediateKind(Kind kind)51   constexpr static bool IsImmediateKind(Kind kind) {
52     return !IsPointerKind(kind);
53   }
54 
55   static_assert(IsImmediateKind(ImmediateKind1), "immediate kind 1");
56   static_assert(IsImmediateKind(ImmediateKind2), "immediate kind 2");
57   static_assert(IsPointerKind(PointerKind1), "pointer kind 1");
58   static_assert(IsPointerKind(PointerKind2), "pointer kind 2");
59 
PackImmediate(Kind kind,PackedRepr imm)60   static PackedRepr PackImmediate(Kind kind, PackedRepr imm) {
61     MOZ_ASSERT(IsImmediateKind(kind));
62     MOZ_ASSERT((PackedRepr(kind) & KindMask) == kind);
63     MOZ_ASSERT((imm & (PackedRepr(KindMask)
64                        << ((sizeof(PackedRepr) * 8) - PayloadShift))) == 0);
65     return PackedRepr(kind) | (PackedRepr(imm) << PayloadShift);
66   }
67 
PackPointer(Kind kind,PointerType * ptr)68   static PackedRepr PackPointer(Kind kind, PointerType* ptr) {
69     PackedRepr ptrBits = reinterpret_cast<PackedRepr>(ptr);
70     MOZ_ASSERT(IsPointerKind(kind));
71     MOZ_ASSERT((PackedRepr(kind) & KindMask) == kind);
72     MOZ_ASSERT((ptrBits & KindMask) == 0);
73     return PackedRepr(kind) | ptrBits;
74   }
75 
76  public:
TaggedValue(Kind kind,PackedRepr imm)77   TaggedValue(Kind kind, PackedRepr imm) : bits_(PackImmediate(kind, imm)) {}
TaggedValue(Kind kind,PointerType * ptr)78   TaggedValue(Kind kind, PointerType* ptr) : bits_(PackPointer(kind, ptr)) {}
79 
bits()80   PackedRepr bits() const { return bits_; }
kind()81   Kind kind() const { return Kind(bits() & KindMask); }
immediate()82   PackedRepr immediate() const {
83     MOZ_ASSERT(IsImmediateKind(kind()));
84     return mozilla::AssertedCast<PackedRepr>(bits() >> PayloadShift);
85   }
pointer()86   PointerType* pointer() const {
87     MOZ_ASSERT(IsPointerKind(kind()));
88     return reinterpret_cast<PointerType*>(bits() & ~KindMask);
89   }
90 };
91 
92 static_assert(
93     std::is_same<TaggedValue<void*>::PackedRepr, PackedTypeCode::PackedRepr>(),
94     "can use pointer tagging with PackedTypeCode");
95 
96 // ResultType represents the WebAssembly spec's `resulttype`. Semantically, a
97 // result type is just a vec(valtype).  For effiency, though, the ResultType
98 // value is packed into a word, with separate encodings for these 3 cases:
99 //  []
100 //  [valtype]
101 //  pointer to ValTypeVector
102 //
103 // Additionally there is an encoding indicating uninitialized ResultType
104 // values.
105 //
106 // Generally in the latter case the ValTypeVector is the args() or results() of
107 // a FuncType in the compilation unit, so as long as the lifetime of the
108 // ResultType value is less than the OpIter, we can just borrow the pointer
109 // without ownership or copying.
110 class ResultType {
111   using Tagged = TaggedValue<const ValTypeVector>;
112   Tagged tagged_;
113 
114   enum Kind {
115     EmptyKind = Tagged::ImmediateKind1,
116     SingleKind = Tagged::ImmediateKind2,
117     VectorKind = Tagged::PointerKind1,
118     InvalidKind = Tagged::PointerKind2,
119   };
120 
ResultType(Kind kind,uintptr_t imm)121   ResultType(Kind kind, uintptr_t imm) : tagged_(Tagged::Kind(kind), imm) {}
ResultType(const ValTypeVector * ptr)122   explicit ResultType(const ValTypeVector* ptr)
123       : tagged_(Tagged::Kind(VectorKind), ptr) {}
124 
kind()125   Kind kind() const { return Kind(tagged_.kind()); }
126 
singleValType()127   ValType singleValType() const {
128     MOZ_ASSERT(kind() == SingleKind);
129     return ValType(PackedTypeCode::fromBits(tagged_.immediate()));
130   }
131 
values()132   const ValTypeVector& values() const {
133     MOZ_ASSERT(kind() == VectorKind);
134     return *tagged_.pointer();
135   }
136 
137  public:
ResultType()138   ResultType() : tagged_(Tagged::Kind(InvalidKind), nullptr) {}
139 
Empty()140   static ResultType Empty() { return ResultType(EmptyKind, uintptr_t(0)); }
Single(ValType vt)141   static ResultType Single(ValType vt) {
142     return ResultType(SingleKind, vt.bitsUnsafe());
143   }
Vector(const ValTypeVector & vals)144   static ResultType Vector(const ValTypeVector& vals) {
145     switch (vals.length()) {
146       case 0:
147         return Empty();
148       case 1:
149         return Single(vals[0]);
150       default:
151         return ResultType(&vals);
152     }
153   }
154 
cloneToVector(ValTypeVector * out)155   [[nodiscard]] bool cloneToVector(ValTypeVector* out) {
156     MOZ_ASSERT(out->empty());
157     switch (kind()) {
158       case EmptyKind:
159         return true;
160       case SingleKind:
161         return out->append(singleValType());
162       case VectorKind:
163         return out->appendAll(values());
164       default:
165         MOZ_CRASH("bad resulttype");
166     }
167   }
168 
empty()169   bool empty() const { return kind() == EmptyKind; }
170 
length()171   size_t length() const {
172     switch (kind()) {
173       case EmptyKind:
174         return 0;
175       case SingleKind:
176         return 1;
177       case VectorKind:
178         return values().length();
179       default:
180         MOZ_CRASH("bad resulttype");
181     }
182   }
183 
184   // Polyfill the Span API, which is polyfilling the std library
size()185   size_t size() const { return length(); }
186 
187   ValType operator[](size_t i) const {
188     switch (kind()) {
189       case SingleKind:
190         MOZ_ASSERT(i == 0);
191         return singleValType();
192       case VectorKind:
193         return values()[i];
194       default:
195         MOZ_CRASH("bad resulttype");
196     }
197   }
198 
199   bool operator==(ResultType rhs) const {
200     switch (kind()) {
201       case EmptyKind:
202       case SingleKind:
203       case InvalidKind:
204         return tagged_.bits() == rhs.tagged_.bits();
205       case VectorKind: {
206         if (rhs.kind() != VectorKind) {
207           return false;
208         }
209         return EqualContainers(values(), rhs.values());
210       }
211       default:
212         MOZ_CRASH("bad resulttype");
213     }
214   }
215   bool operator!=(ResultType rhs) const { return !(*this == rhs); }
216 };
217 
218 // BlockType represents the WebAssembly spec's `blocktype`. Semantically, a
219 // block type is just a (vec(valtype) -> vec(valtype)) with four special
220 // encodings which are represented explicitly in BlockType:
221 //  [] -> []
222 //  [] -> [valtype]
223 //  [params] -> [results] via pointer to FuncType
224 //  [] -> [results] via pointer to FuncType (ignoring [params])
225 
226 class BlockType {
227   using Tagged = TaggedValue<const FuncType>;
228   Tagged tagged_;
229 
230   enum Kind {
231     VoidToVoidKind = Tagged::ImmediateKind1,
232     VoidToSingleKind = Tagged::ImmediateKind2,
233     FuncKind = Tagged::PointerKind1,
234     FuncResultsKind = Tagged::PointerKind2
235   };
236 
BlockType(Kind kind,uintptr_t imm)237   BlockType(Kind kind, uintptr_t imm) : tagged_(Tagged::Kind(kind), imm) {}
BlockType(Kind kind,const FuncType & type)238   BlockType(Kind kind, const FuncType& type)
239       : tagged_(Tagged::Kind(kind), &type) {}
240 
kind()241   Kind kind() const { return Kind(tagged_.kind()); }
singleValType()242   ValType singleValType() const {
243     MOZ_ASSERT(kind() == VoidToSingleKind);
244     return ValType(PackedTypeCode::fromBits(tagged_.immediate()));
245   }
246 
funcType()247   const FuncType& funcType() const { return *tagged_.pointer(); }
248 
249  public:
BlockType()250   BlockType()
251       : tagged_(Tagged::Kind(VoidToVoidKind),
252                 PackedTypeCode::invalid().bits()) {}
253 
VoidToVoid()254   static BlockType VoidToVoid() {
255     return BlockType(VoidToVoidKind, uintptr_t(0));
256   }
VoidToSingle(ValType vt)257   static BlockType VoidToSingle(ValType vt) {
258     return BlockType(VoidToSingleKind, vt.bitsUnsafe());
259   }
Func(const FuncType & type)260   static BlockType Func(const FuncType& type) {
261     if (type.args().length() == 0) {
262       return FuncResults(type);
263     }
264     return BlockType(FuncKind, type);
265   }
FuncResults(const FuncType & type)266   static BlockType FuncResults(const FuncType& type) {
267     switch (type.results().length()) {
268       case 0:
269         return VoidToVoid();
270       case 1:
271         return VoidToSingle(type.results()[0]);
272       default:
273         return BlockType(FuncResultsKind, type);
274     }
275   }
276 
params()277   ResultType params() const {
278     switch (kind()) {
279       case VoidToVoidKind:
280       case VoidToSingleKind:
281       case FuncResultsKind:
282         return ResultType::Empty();
283       case FuncKind:
284         return ResultType::Vector(funcType().args());
285       default:
286         MOZ_CRASH("unexpected kind");
287     }
288   }
289 
results()290   ResultType results() const {
291     switch (kind()) {
292       case VoidToVoidKind:
293         return ResultType::Empty();
294       case VoidToSingleKind:
295         return ResultType::Single(singleValType());
296       case FuncKind:
297       case FuncResultsKind:
298         return ResultType::Vector(funcType().results());
299       default:
300         MOZ_CRASH("unexpected kind");
301     }
302   }
303 
304   bool operator==(BlockType rhs) const {
305     if (kind() != rhs.kind()) {
306       return false;
307     }
308     switch (kind()) {
309       case VoidToVoidKind:
310       case VoidToSingleKind:
311         return tagged_.bits() == rhs.tagged_.bits();
312       case FuncKind:
313         return funcType() == rhs.funcType();
314       case FuncResultsKind:
315         return EqualContainers(funcType().results(), rhs.funcType().results());
316       default:
317         MOZ_CRASH("unexpected kind");
318     }
319   }
320 
321   bool operator!=(BlockType rhs) const { return !(*this == rhs); }
322 };
323 
324 }  // namespace wasm
325 }  // namespace js
326 
327 #endif  // wasm_expr_type_h
328