WasmValType.cpp (12220B)
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 #include "wasm/WasmValType.h" 20 21 #include "js/Conversions.h" 22 #include "js/ErrorReport.h" 23 #include "js/friend/ErrorMessages.h" // JSMSG_* 24 #include "js/Printf.h" 25 #include "js/Value.h" 26 27 #include "vm/JSAtomUtils.h" // Atomize 28 #include "vm/JSObject.h" 29 #include "vm/StringType.h" 30 #include "wasm/WasmFeatures.h" 31 #include "wasm/WasmJS.h" 32 33 #include "vm/JSAtomUtils-inl.h" // AtomToId 34 #include "vm/JSObject-inl.h" 35 36 using namespace js; 37 using namespace js::wasm; 38 39 RefType RefType::topType() const { 40 switch (hierarchy()) { 41 case wasm::RefTypeHierarchy::Any: 42 return wasm::RefType::any(); 43 case wasm::RefTypeHierarchy::Func: 44 return wasm::RefType::func(); 45 case wasm::RefTypeHierarchy::Extern: 46 return wasm::RefType::extern_(); 47 case wasm::RefTypeHierarchy::Exn: 48 return wasm::RefType::exn(); 49 default: 50 MOZ_CRASH("switch is exhaustive"); 51 } 52 } 53 54 RefType RefType::bottomType() const { 55 switch (hierarchy()) { 56 case wasm::RefTypeHierarchy::Any: 57 return wasm::RefType::none(); 58 case wasm::RefTypeHierarchy::Func: 59 return wasm::RefType::nofunc(); 60 case wasm::RefTypeHierarchy::Extern: 61 return wasm::RefType::noextern(); 62 case wasm::RefTypeHierarchy::Exn: 63 return wasm::RefType::noexn(); 64 default: 65 MOZ_CRASH("switch is exhaustive"); 66 } 67 } 68 69 static RefType FirstCommonSuperType(RefType a, RefType b, 70 std::initializer_list<RefType> supers) { 71 for (RefType super : supers) { 72 if (RefType::isSubTypeOf(a, super) && RefType::isSubTypeOf(b, super)) { 73 return super; 74 } 75 } 76 MOZ_CRASH("failed to find common super type"); 77 } 78 79 RefType RefType::leastUpperBound(RefType a, RefType b) { 80 // Types in different hierarchies have no common bound. Validation should 81 // always prevent two such types from being compared. 82 MOZ_RELEASE_ASSERT(a.hierarchy() == b.hierarchy()); 83 84 // Whether the LUB is nullable can be determined by the nullability of a and 85 // b, regardless of their actual types. 86 bool nullable = a.isNullable() || b.isNullable(); 87 88 // If one type is a subtype of the other, the higher type is the LUB - and we 89 // can capture nulls here too, as we know the nullability of the LUB. 90 if (RefType::isSubTypeOf(a, b.withIsNullable(nullable))) { 91 return b.withIsNullable(nullable); 92 } 93 if (RefType::isSubTypeOf(b, a.withIsNullable(nullable))) { 94 return a.withIsNullable(nullable); 95 } 96 97 // Concrete types may share a concrete parent type. We can test b against all 98 // of a's parent types to see if this is true. 99 if (a.isTypeRef() && b.isTypeRef()) { 100 const TypeDef* aSuper = a.typeDef()->superTypeDef(); 101 while (aSuper) { 102 if (TypeDef::isSubTypeOf(b.typeDef(), aSuper)) { 103 return RefType(aSuper, nullable); 104 } 105 aSuper = aSuper->superTypeDef(); 106 } 107 } 108 109 // Because wasm type hierarchies are pretty small and simple, we can 110 // essentially brute-force the LUB by simply iterating over all the abstract 111 // types bottom-to-top. The first one that is a super type of both a and b is 112 // the LUB. We are guaranteed to find a common bound because we have verified 113 // that the types have the same hierarchy and we will therefore at least find 114 // the hierarchy's top type. 115 // 116 // We test against the nullable versions of these types, and then apply the 117 // true nullability afterward. This is ok -- this finds the *kind* of the LUB 118 // (which we now know to be abstract), and applying the correct nullability 119 // will not affect this. For example, for the non-nullable types 120 // (ref $myStruct) and (ref $myArray), we will find (ref null eq), and then 121 // modify it to (ref eq), which is the correct LUB. 122 RefType common; 123 switch (a.hierarchy()) { 124 case RefTypeHierarchy::Any: 125 common = FirstCommonSuperType( 126 a, b, 127 {RefType::none(), RefType::i31(), RefType::struct_(), 128 RefType::array(), RefType::eq(), RefType::any()}); 129 break; 130 case RefTypeHierarchy::Func: 131 common = FirstCommonSuperType(a, b, {RefType::nofunc(), RefType::func()}); 132 break; 133 case RefTypeHierarchy::Extern: 134 common = 135 FirstCommonSuperType(a, b, {RefType::noextern(), RefType::extern_()}); 136 break; 137 case RefTypeHierarchy::Exn: 138 common = FirstCommonSuperType(a, b, {RefType::noexn(), RefType::exn()}); 139 break; 140 default: 141 MOZ_CRASH("unknown type hierarchy"); 142 } 143 return common.withIsNullable(nullable); 144 } 145 146 TypeDefKind RefType::typeDefKind() const { 147 switch (kind()) { 148 case RefType::Struct: 149 return TypeDefKind::Struct; 150 case RefType::Array: 151 return TypeDefKind::Array; 152 case RefType::Func: 153 return TypeDefKind::Func; 154 default: 155 return TypeDefKind::None; 156 } 157 MOZ_CRASH("switch is exhaustive"); 158 } 159 160 static bool ToRefType(JSContext* cx, const JSLinearString* typeLinearStr, 161 RefType* out) { 162 if (StringEqualsLiteral(typeLinearStr, "anyfunc") || 163 StringEqualsLiteral(typeLinearStr, "funcref")) { 164 // The JS API uses "anyfunc" uniformly as the external name of funcref. We 165 // also allow "funcref" for compatibility with code we've already shipped. 166 *out = RefType::func(); 167 return true; 168 } 169 if (StringEqualsLiteral(typeLinearStr, "externref")) { 170 *out = RefType::extern_(); 171 return true; 172 } 173 if (StringEqualsLiteral(typeLinearStr, "exnref")) { 174 *out = RefType::exn(); 175 return true; 176 } 177 if (StringEqualsLiteral(typeLinearStr, "anyref")) { 178 *out = RefType::any(); 179 return true; 180 } 181 if (StringEqualsLiteral(typeLinearStr, "eqref")) { 182 *out = RefType::eq(); 183 return true; 184 } 185 if (StringEqualsLiteral(typeLinearStr, "i31ref")) { 186 *out = RefType::i31(); 187 return true; 188 } 189 if (StringEqualsLiteral(typeLinearStr, "structref")) { 190 *out = RefType::struct_(); 191 return true; 192 } 193 if (StringEqualsLiteral(typeLinearStr, "arrayref")) { 194 *out = RefType::array(); 195 return true; 196 } 197 if (StringEqualsLiteral(typeLinearStr, "nullfuncref")) { 198 *out = RefType::nofunc(); 199 return true; 200 } 201 if (StringEqualsLiteral(typeLinearStr, "nullexternref")) { 202 *out = RefType::noextern(); 203 return true; 204 } 205 if (StringEqualsLiteral(typeLinearStr, "nullexnref")) { 206 *out = RefType::noexn(); 207 return true; 208 } 209 if (StringEqualsLiteral(typeLinearStr, "nullref")) { 210 *out = RefType::none(); 211 return true; 212 } 213 214 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, 215 JSMSG_WASM_BAD_STRING_VAL_TYPE); 216 return false; 217 } 218 219 enum class RefTypeResult { 220 Failure, 221 Parsed, 222 Unparsed, 223 }; 224 225 bool wasm::ToValType(JSContext* cx, HandleValue v, ValType* out) { 226 RootedString typeStr(cx, ToString(cx, v)); 227 if (!typeStr) { 228 return false; 229 } 230 231 Rooted<JSLinearString*> typeLinearStr(cx, typeStr->ensureLinear(cx)); 232 if (!typeLinearStr) { 233 return false; 234 } 235 236 if (StringEqualsLiteral(typeLinearStr, "i32")) { 237 *out = ValType::I32; 238 } else if (StringEqualsLiteral(typeLinearStr, "i64")) { 239 *out = ValType::I64; 240 } else if (StringEqualsLiteral(typeLinearStr, "f32")) { 241 *out = ValType::F32; 242 } else if (StringEqualsLiteral(typeLinearStr, "f64")) { 243 *out = ValType::F64; 244 #ifdef ENABLE_WASM_SIMD 245 } else if (SimdAvailable(cx) && StringEqualsLiteral(typeLinearStr, "v128")) { 246 *out = ValType::V128; 247 #endif 248 } else { 249 RefType rt; 250 if (ToRefType(cx, typeLinearStr, &rt)) { 251 *out = ValType(rt); 252 } else { 253 // ToRefType will report an error when it fails, just return false 254 return false; 255 } 256 } 257 258 return true; 259 } 260 261 bool wasm::ToRefType(JSContext* cx, HandleValue v, RefType* out) { 262 RootedString typeStr(cx, ToString(cx, v)); 263 if (!typeStr) { 264 return false; 265 } 266 267 Rooted<JSLinearString*> typeLinearStr(cx, typeStr->ensureLinear(cx)); 268 if (!typeLinearStr) { 269 return false; 270 } 271 272 return ToRefType(cx, typeLinearStr, out); 273 } 274 275 UniqueChars wasm::ToString(RefType type, const TypeContext* types) { 276 // Try to emit a shorthand version first 277 if (type.isNullable() && !type.isTypeRef()) { 278 const char* literal = nullptr; 279 switch (type.kind()) { 280 case RefType::Func: 281 literal = "funcref"; 282 break; 283 case RefType::Extern: 284 literal = "externref"; 285 break; 286 case RefType::Exn: 287 literal = "exnref"; 288 break; 289 case RefType::Any: 290 literal = "anyref"; 291 break; 292 case RefType::NoFunc: 293 literal = "nullfuncref"; 294 break; 295 case RefType::NoExn: 296 literal = "nullexnref"; 297 break; 298 case RefType::NoExtern: 299 literal = "nullexternref"; 300 break; 301 case RefType::None: 302 literal = "nullref"; 303 break; 304 case RefType::Eq: 305 literal = "eqref"; 306 break; 307 case RefType::I31: 308 literal = "i31ref"; 309 break; 310 case RefType::Struct: 311 literal = "structref"; 312 break; 313 case RefType::Array: 314 literal = "arrayref"; 315 break; 316 case RefType::TypeRef: { 317 MOZ_CRASH("type ref should not be possible here"); 318 } 319 } 320 return DuplicateString(literal); 321 } 322 323 // Emit the full reference type with heap type 324 const char* heapType = nullptr; 325 switch (type.kind()) { 326 case RefType::Func: 327 heapType = "func"; 328 break; 329 case RefType::Extern: 330 heapType = "extern"; 331 break; 332 case RefType::Exn: 333 heapType = "exn"; 334 break; 335 case RefType::Any: 336 heapType = "any"; 337 break; 338 case RefType::NoFunc: 339 heapType = "nofunc"; 340 break; 341 case RefType::NoExn: 342 heapType = "noexn"; 343 break; 344 case RefType::NoExtern: 345 heapType = "noextern"; 346 break; 347 case RefType::None: 348 heapType = "none"; 349 break; 350 case RefType::Eq: 351 heapType = "eq"; 352 break; 353 case RefType::I31: 354 heapType = "i31"; 355 break; 356 case RefType::Struct: 357 heapType = "struct"; 358 break; 359 case RefType::Array: 360 heapType = "array"; 361 break; 362 case RefType::TypeRef: { 363 if (types) { 364 uint32_t typeIndex = types->indexOf(*type.typeDef()); 365 return JS_smprintf("(ref %s%d)", type.isNullable() ? "null " : "", 366 typeIndex); 367 } 368 return JS_smprintf("(ref %s?)", type.isNullable() ? "null " : ""); 369 } 370 } 371 return JS_smprintf("(ref %s%s)", type.isNullable() ? "null " : "", heapType); 372 } 373 374 UniqueChars wasm::ToString(ValType type, const TypeContext* types) { 375 return ToString(type.storageType(), types); 376 } 377 378 UniqueChars wasm::ToString(StorageType type, const TypeContext* types) { 379 const char* literal = nullptr; 380 switch (type.kind()) { 381 case StorageType::I8: 382 literal = "i8"; 383 break; 384 case StorageType::I16: 385 literal = "i16"; 386 break; 387 case StorageType::I32: 388 literal = "i32"; 389 break; 390 case StorageType::I64: 391 literal = "i64"; 392 break; 393 case StorageType::V128: 394 literal = "v128"; 395 break; 396 case StorageType::F32: 397 literal = "f32"; 398 break; 399 case StorageType::F64: 400 literal = "f64"; 401 break; 402 case StorageType::Ref: 403 return ToString(type.refType(), types); 404 } 405 return DuplicateString(literal); 406 } 407 408 UniqueChars wasm::ToString(const mozilla::Maybe<ValType>& type, 409 const TypeContext* types) { 410 return type ? ToString(type.ref(), types) : JS_smprintf("%s", "void"); 411 } 412 413 UniqueChars wasm::ToString(const MaybeRefType& type, const TypeContext* types) { 414 return type ? ToString(type.value(), types) : JS_smprintf("%s", "void"); 415 }