Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
michael@0 | 2 | * vim: set ts=8 sts=4 et sw=4 tw=99: |
michael@0 | 3 | * This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | #include "jsinferinlines.h" |
michael@0 | 8 | |
michael@0 | 9 | #include "mozilla/DebugOnly.h" |
michael@0 | 10 | #include "mozilla/MemoryReporting.h" |
michael@0 | 11 | #include "mozilla/PodOperations.h" |
michael@0 | 12 | |
michael@0 | 13 | #include "jsapi.h" |
michael@0 | 14 | #include "jscntxt.h" |
michael@0 | 15 | #include "jsgc.h" |
michael@0 | 16 | #include "jshashutil.h" |
michael@0 | 17 | #include "jsobj.h" |
michael@0 | 18 | #include "jsprf.h" |
michael@0 | 19 | #include "jsscript.h" |
michael@0 | 20 | #include "jsstr.h" |
michael@0 | 21 | #include "jsworkers.h" |
michael@0 | 22 | #include "prmjtime.h" |
michael@0 | 23 | |
michael@0 | 24 | #include "gc/Marking.h" |
michael@0 | 25 | #ifdef JS_ION |
michael@0 | 26 | #include "jit/BaselineJIT.h" |
michael@0 | 27 | #include "jit/Ion.h" |
michael@0 | 28 | #include "jit/IonAnalysis.h" |
michael@0 | 29 | #include "jit/JitCompartment.h" |
michael@0 | 30 | #endif |
michael@0 | 31 | #include "js/MemoryMetrics.h" |
michael@0 | 32 | #include "vm/Opcodes.h" |
michael@0 | 33 | #include "vm/Shape.h" |
michael@0 | 34 | |
michael@0 | 35 | #include "jsatominlines.h" |
michael@0 | 36 | #include "jsgcinlines.h" |
michael@0 | 37 | #include "jsobjinlines.h" |
michael@0 | 38 | #include "jsscriptinlines.h" |
michael@0 | 39 | |
michael@0 | 40 | #include "jit/ExecutionMode-inl.h" |
michael@0 | 41 | |
michael@0 | 42 | using namespace js; |
michael@0 | 43 | using namespace js::gc; |
michael@0 | 44 | using namespace js::types; |
michael@0 | 45 | using namespace js::analyze; |
michael@0 | 46 | |
michael@0 | 47 | using mozilla::DebugOnly; |
michael@0 | 48 | using mozilla::Maybe; |
michael@0 | 49 | using mozilla::PodArrayZero; |
michael@0 | 50 | using mozilla::PodCopy; |
michael@0 | 51 | using mozilla::PodZero; |
michael@0 | 52 | |
michael@0 | 53 | static inline jsid |
michael@0 | 54 | id_prototype(JSContext *cx) { |
michael@0 | 55 | return NameToId(cx->names().prototype); |
michael@0 | 56 | } |
michael@0 | 57 | |
michael@0 | 58 | static inline jsid |
michael@0 | 59 | id___proto__(JSContext *cx) { |
michael@0 | 60 | return NameToId(cx->names().proto); |
michael@0 | 61 | } |
michael@0 | 62 | |
michael@0 | 63 | static inline jsid |
michael@0 | 64 | id_constructor(JSContext *cx) { |
michael@0 | 65 | return NameToId(cx->names().constructor); |
michael@0 | 66 | } |
michael@0 | 67 | |
michael@0 | 68 | static inline jsid |
michael@0 | 69 | id_caller(JSContext *cx) { |
michael@0 | 70 | return NameToId(cx->names().caller); |
michael@0 | 71 | } |
michael@0 | 72 | |
michael@0 | 73 | #ifdef DEBUG |
michael@0 | 74 | const char * |
michael@0 | 75 | types::TypeIdStringImpl(jsid id) |
michael@0 | 76 | { |
michael@0 | 77 | if (JSID_IS_VOID(id)) |
michael@0 | 78 | return "(index)"; |
michael@0 | 79 | if (JSID_IS_EMPTY(id)) |
michael@0 | 80 | return "(new)"; |
michael@0 | 81 | static char bufs[4][100]; |
michael@0 | 82 | static unsigned which = 0; |
michael@0 | 83 | which = (which + 1) & 3; |
michael@0 | 84 | PutEscapedString(bufs[which], 100, JSID_TO_FLAT_STRING(id), 0); |
michael@0 | 85 | return bufs[which]; |
michael@0 | 86 | } |
michael@0 | 87 | #endif |
michael@0 | 88 | |
michael@0 | 89 | ///////////////////////////////////////////////////////////////////// |
michael@0 | 90 | // Logging |
michael@0 | 91 | ///////////////////////////////////////////////////////////////////// |
michael@0 | 92 | |
michael@0 | 93 | #ifdef DEBUG |
michael@0 | 94 | |
michael@0 | 95 | static bool InferSpewActive(SpewChannel channel) |
michael@0 | 96 | { |
michael@0 | 97 | static bool active[SPEW_COUNT]; |
michael@0 | 98 | static bool checked = false; |
michael@0 | 99 | if (!checked) { |
michael@0 | 100 | checked = true; |
michael@0 | 101 | PodArrayZero(active); |
michael@0 | 102 | const char *env = getenv("INFERFLAGS"); |
michael@0 | 103 | if (!env) |
michael@0 | 104 | return false; |
michael@0 | 105 | if (strstr(env, "ops")) |
michael@0 | 106 | active[ISpewOps] = true; |
michael@0 | 107 | if (strstr(env, "result")) |
michael@0 | 108 | active[ISpewResult] = true; |
michael@0 | 109 | if (strstr(env, "full")) { |
michael@0 | 110 | for (unsigned i = 0; i < SPEW_COUNT; i++) |
michael@0 | 111 | active[i] = true; |
michael@0 | 112 | } |
michael@0 | 113 | } |
michael@0 | 114 | return active[channel]; |
michael@0 | 115 | } |
michael@0 | 116 | |
michael@0 | 117 | static bool InferSpewColorable() |
michael@0 | 118 | { |
michael@0 | 119 | /* Only spew colors on xterm-color to not screw up emacs. */ |
michael@0 | 120 | static bool colorable = false; |
michael@0 | 121 | static bool checked = false; |
michael@0 | 122 | if (!checked) { |
michael@0 | 123 | checked = true; |
michael@0 | 124 | const char *env = getenv("TERM"); |
michael@0 | 125 | if (!env) |
michael@0 | 126 | return false; |
michael@0 | 127 | if (strcmp(env, "xterm-color") == 0 || strcmp(env, "xterm-256color") == 0) |
michael@0 | 128 | colorable = true; |
michael@0 | 129 | } |
michael@0 | 130 | return colorable; |
michael@0 | 131 | } |
michael@0 | 132 | |
michael@0 | 133 | const char * |
michael@0 | 134 | types::InferSpewColorReset() |
michael@0 | 135 | { |
michael@0 | 136 | if (!InferSpewColorable()) |
michael@0 | 137 | return ""; |
michael@0 | 138 | return "\x1b[0m"; |
michael@0 | 139 | } |
michael@0 | 140 | |
michael@0 | 141 | const char * |
michael@0 | 142 | types::InferSpewColor(TypeConstraint *constraint) |
michael@0 | 143 | { |
michael@0 | 144 | /* Type constraints are printed out using foreground colors. */ |
michael@0 | 145 | static const char * const colors[] = { "\x1b[31m", "\x1b[32m", "\x1b[33m", |
michael@0 | 146 | "\x1b[34m", "\x1b[35m", "\x1b[36m", |
michael@0 | 147 | "\x1b[37m" }; |
michael@0 | 148 | if (!InferSpewColorable()) |
michael@0 | 149 | return ""; |
michael@0 | 150 | return colors[DefaultHasher<TypeConstraint *>::hash(constraint) % 7]; |
michael@0 | 151 | } |
michael@0 | 152 | |
michael@0 | 153 | const char * |
michael@0 | 154 | types::InferSpewColor(TypeSet *types) |
michael@0 | 155 | { |
michael@0 | 156 | /* Type sets are printed out using bold colors. */ |
michael@0 | 157 | static const char * const colors[] = { "\x1b[1;31m", "\x1b[1;32m", "\x1b[1;33m", |
michael@0 | 158 | "\x1b[1;34m", "\x1b[1;35m", "\x1b[1;36m", |
michael@0 | 159 | "\x1b[1;37m" }; |
michael@0 | 160 | if (!InferSpewColorable()) |
michael@0 | 161 | return ""; |
michael@0 | 162 | return colors[DefaultHasher<TypeSet *>::hash(types) % 7]; |
michael@0 | 163 | } |
michael@0 | 164 | |
michael@0 | 165 | const char * |
michael@0 | 166 | types::TypeString(Type type) |
michael@0 | 167 | { |
michael@0 | 168 | if (type.isPrimitive()) { |
michael@0 | 169 | switch (type.primitive()) { |
michael@0 | 170 | case JSVAL_TYPE_UNDEFINED: |
michael@0 | 171 | return "void"; |
michael@0 | 172 | case JSVAL_TYPE_NULL: |
michael@0 | 173 | return "null"; |
michael@0 | 174 | case JSVAL_TYPE_BOOLEAN: |
michael@0 | 175 | return "bool"; |
michael@0 | 176 | case JSVAL_TYPE_INT32: |
michael@0 | 177 | return "int"; |
michael@0 | 178 | case JSVAL_TYPE_DOUBLE: |
michael@0 | 179 | return "float"; |
michael@0 | 180 | case JSVAL_TYPE_STRING: |
michael@0 | 181 | return "string"; |
michael@0 | 182 | case JSVAL_TYPE_MAGIC: |
michael@0 | 183 | return "lazyargs"; |
michael@0 | 184 | default: |
michael@0 | 185 | MOZ_ASSUME_UNREACHABLE("Bad type"); |
michael@0 | 186 | } |
michael@0 | 187 | } |
michael@0 | 188 | if (type.isUnknown()) |
michael@0 | 189 | return "unknown"; |
michael@0 | 190 | if (type.isAnyObject()) |
michael@0 | 191 | return " object"; |
michael@0 | 192 | |
michael@0 | 193 | static char bufs[4][40]; |
michael@0 | 194 | static unsigned which = 0; |
michael@0 | 195 | which = (which + 1) & 3; |
michael@0 | 196 | |
michael@0 | 197 | if (type.isSingleObject()) |
michael@0 | 198 | JS_snprintf(bufs[which], 40, "<0x%p>", (void *) type.singleObject()); |
michael@0 | 199 | else |
michael@0 | 200 | JS_snprintf(bufs[which], 40, "[0x%p]", (void *) type.typeObject()); |
michael@0 | 201 | |
michael@0 | 202 | return bufs[which]; |
michael@0 | 203 | } |
michael@0 | 204 | |
michael@0 | 205 | const char * |
michael@0 | 206 | types::TypeObjectString(TypeObject *type) |
michael@0 | 207 | { |
michael@0 | 208 | return TypeString(Type::ObjectType(type)); |
michael@0 | 209 | } |
michael@0 | 210 | |
michael@0 | 211 | unsigned JSScript::id() { |
michael@0 | 212 | if (!id_) { |
michael@0 | 213 | id_ = ++compartment()->types.scriptCount; |
michael@0 | 214 | InferSpew(ISpewOps, "script #%u: %p %s:%d", |
michael@0 | 215 | id_, this, filename() ? filename() : "<null>", lineno()); |
michael@0 | 216 | } |
michael@0 | 217 | return id_; |
michael@0 | 218 | } |
michael@0 | 219 | |
michael@0 | 220 | void |
michael@0 | 221 | types::InferSpew(SpewChannel channel, const char *fmt, ...) |
michael@0 | 222 | { |
michael@0 | 223 | if (!InferSpewActive(channel)) |
michael@0 | 224 | return; |
michael@0 | 225 | |
michael@0 | 226 | va_list ap; |
michael@0 | 227 | va_start(ap, fmt); |
michael@0 | 228 | fprintf(stderr, "[infer] "); |
michael@0 | 229 | vfprintf(stderr, fmt, ap); |
michael@0 | 230 | fprintf(stderr, "\n"); |
michael@0 | 231 | va_end(ap); |
michael@0 | 232 | } |
michael@0 | 233 | |
michael@0 | 234 | bool |
michael@0 | 235 | types::TypeHasProperty(JSContext *cx, TypeObject *obj, jsid id, const Value &value) |
michael@0 | 236 | { |
michael@0 | 237 | /* |
michael@0 | 238 | * Check the correctness of the type information in the object's property |
michael@0 | 239 | * against an actual value. |
michael@0 | 240 | */ |
michael@0 | 241 | if (!obj->unknownProperties() && !value.isUndefined()) { |
michael@0 | 242 | id = IdToTypeId(id); |
michael@0 | 243 | |
michael@0 | 244 | /* Watch for properties which inference does not monitor. */ |
michael@0 | 245 | if (id == id___proto__(cx) || id == id_constructor(cx) || id == id_caller(cx)) |
michael@0 | 246 | return true; |
michael@0 | 247 | |
michael@0 | 248 | Type type = GetValueType(value); |
michael@0 | 249 | |
michael@0 | 250 | AutoEnterAnalysis enter(cx); |
michael@0 | 251 | |
michael@0 | 252 | /* |
michael@0 | 253 | * We don't track types for properties inherited from prototypes which |
michael@0 | 254 | * haven't yet been accessed during analysis of the inheriting object. |
michael@0 | 255 | * Don't do the property instantiation now. |
michael@0 | 256 | */ |
michael@0 | 257 | TypeSet *types = obj->maybeGetProperty(id); |
michael@0 | 258 | if (!types) |
michael@0 | 259 | return true; |
michael@0 | 260 | |
michael@0 | 261 | if (!types->hasType(type)) { |
michael@0 | 262 | TypeFailure(cx, "Missing type in object %s %s: %s", |
michael@0 | 263 | TypeObjectString(obj), TypeIdString(id), TypeString(type)); |
michael@0 | 264 | } |
michael@0 | 265 | } |
michael@0 | 266 | return true; |
michael@0 | 267 | } |
michael@0 | 268 | |
michael@0 | 269 | #endif |
michael@0 | 270 | |
michael@0 | 271 | void |
michael@0 | 272 | types::TypeFailure(JSContext *cx, const char *fmt, ...) |
michael@0 | 273 | { |
michael@0 | 274 | char msgbuf[1024]; /* Larger error messages will be truncated */ |
michael@0 | 275 | char errbuf[1024]; |
michael@0 | 276 | |
michael@0 | 277 | va_list ap; |
michael@0 | 278 | va_start(ap, fmt); |
michael@0 | 279 | JS_vsnprintf(errbuf, sizeof(errbuf), fmt, ap); |
michael@0 | 280 | va_end(ap); |
michael@0 | 281 | |
michael@0 | 282 | JS_snprintf(msgbuf, sizeof(msgbuf), "[infer failure] %s", errbuf); |
michael@0 | 283 | |
michael@0 | 284 | /* Dump type state, even if INFERFLAGS is unset. */ |
michael@0 | 285 | cx->compartment()->types.print(cx, true); |
michael@0 | 286 | |
michael@0 | 287 | MOZ_ReportAssertionFailure(msgbuf, __FILE__, __LINE__); |
michael@0 | 288 | MOZ_CRASH(); |
michael@0 | 289 | } |
michael@0 | 290 | |
michael@0 | 291 | ///////////////////////////////////////////////////////////////////// |
michael@0 | 292 | // TypeSet |
michael@0 | 293 | ///////////////////////////////////////////////////////////////////// |
michael@0 | 294 | |
michael@0 | 295 | TemporaryTypeSet::TemporaryTypeSet(Type type) |
michael@0 | 296 | { |
michael@0 | 297 | if (type.isUnknown()) { |
michael@0 | 298 | flags |= TYPE_FLAG_BASE_MASK; |
michael@0 | 299 | } else if (type.isPrimitive()) { |
michael@0 | 300 | flags = PrimitiveTypeFlag(type.primitive()); |
michael@0 | 301 | if (flags == TYPE_FLAG_DOUBLE) |
michael@0 | 302 | flags |= TYPE_FLAG_INT32; |
michael@0 | 303 | } else if (type.isAnyObject()) { |
michael@0 | 304 | flags |= TYPE_FLAG_ANYOBJECT; |
michael@0 | 305 | } else if (type.isTypeObject() && type.typeObject()->unknownProperties()) { |
michael@0 | 306 | flags |= TYPE_FLAG_ANYOBJECT; |
michael@0 | 307 | } else { |
michael@0 | 308 | setBaseObjectCount(1); |
michael@0 | 309 | objectSet = reinterpret_cast<TypeObjectKey**>(type.objectKey()); |
michael@0 | 310 | } |
michael@0 | 311 | } |
michael@0 | 312 | |
michael@0 | 313 | bool |
michael@0 | 314 | TypeSet::mightBeMIRType(jit::MIRType type) |
michael@0 | 315 | { |
michael@0 | 316 | if (unknown()) |
michael@0 | 317 | return true; |
michael@0 | 318 | |
michael@0 | 319 | if (type == jit::MIRType_Object) |
michael@0 | 320 | return unknownObject() || baseObjectCount() != 0; |
michael@0 | 321 | |
michael@0 | 322 | switch (type) { |
michael@0 | 323 | case jit::MIRType_Undefined: |
michael@0 | 324 | return baseFlags() & TYPE_FLAG_UNDEFINED; |
michael@0 | 325 | case jit::MIRType_Null: |
michael@0 | 326 | return baseFlags() & TYPE_FLAG_NULL; |
michael@0 | 327 | case jit::MIRType_Boolean: |
michael@0 | 328 | return baseFlags() & TYPE_FLAG_BOOLEAN; |
michael@0 | 329 | case jit::MIRType_Int32: |
michael@0 | 330 | return baseFlags() & TYPE_FLAG_INT32; |
michael@0 | 331 | case jit::MIRType_Float32: // Fall through, there's no JSVAL for Float32. |
michael@0 | 332 | case jit::MIRType_Double: |
michael@0 | 333 | return baseFlags() & TYPE_FLAG_DOUBLE; |
michael@0 | 334 | case jit::MIRType_String: |
michael@0 | 335 | return baseFlags() & TYPE_FLAG_STRING; |
michael@0 | 336 | case jit::MIRType_MagicOptimizedArguments: |
michael@0 | 337 | return baseFlags() & TYPE_FLAG_LAZYARGS; |
michael@0 | 338 | case jit::MIRType_MagicHole: |
michael@0 | 339 | case jit::MIRType_MagicIsConstructing: |
michael@0 | 340 | // These magic constants do not escape to script and are not observed |
michael@0 | 341 | // in the type sets. |
michael@0 | 342 | // |
michael@0 | 343 | // The reason we can return false here is subtle: if Ion is asking the |
michael@0 | 344 | // type set if it has seen such a magic constant, then the MIR in |
michael@0 | 345 | // question is the most generic type, MIRType_Value. A magic constant |
michael@0 | 346 | // could only be emitted by a MIR of MIRType_Value if that MIR is a |
michael@0 | 347 | // phi, and we check that different magic constants do not flow to the |
michael@0 | 348 | // same join point in GuessPhiType. |
michael@0 | 349 | return false; |
michael@0 | 350 | default: |
michael@0 | 351 | MOZ_ASSUME_UNREACHABLE("Bad MIR type"); |
michael@0 | 352 | } |
michael@0 | 353 | } |
michael@0 | 354 | |
michael@0 | 355 | bool |
michael@0 | 356 | TypeSet::isSubset(TypeSet *other) |
michael@0 | 357 | { |
michael@0 | 358 | if ((baseFlags() & other->baseFlags()) != baseFlags()) |
michael@0 | 359 | return false; |
michael@0 | 360 | |
michael@0 | 361 | if (unknownObject()) { |
michael@0 | 362 | JS_ASSERT(other->unknownObject()); |
michael@0 | 363 | } else { |
michael@0 | 364 | for (unsigned i = 0; i < getObjectCount(); i++) { |
michael@0 | 365 | TypeObjectKey *obj = getObject(i); |
michael@0 | 366 | if (!obj) |
michael@0 | 367 | continue; |
michael@0 | 368 | if (!other->hasType(Type::ObjectType(obj))) |
michael@0 | 369 | return false; |
michael@0 | 370 | } |
michael@0 | 371 | } |
michael@0 | 372 | |
michael@0 | 373 | return true; |
michael@0 | 374 | } |
michael@0 | 375 | |
michael@0 | 376 | bool |
michael@0 | 377 | TypeSet::enumerateTypes(TypeList *list) |
michael@0 | 378 | { |
michael@0 | 379 | /* If any type is possible, there's no need to worry about specifics. */ |
michael@0 | 380 | if (flags & TYPE_FLAG_UNKNOWN) |
michael@0 | 381 | return list->append(Type::UnknownType()); |
michael@0 | 382 | |
michael@0 | 383 | /* Enqueue type set members stored as bits. */ |
michael@0 | 384 | for (TypeFlags flag = 1; flag < TYPE_FLAG_ANYOBJECT; flag <<= 1) { |
michael@0 | 385 | if (flags & flag) { |
michael@0 | 386 | Type type = Type::PrimitiveType(TypeFlagPrimitive(flag)); |
michael@0 | 387 | if (!list->append(type)) |
michael@0 | 388 | return false; |
michael@0 | 389 | } |
michael@0 | 390 | } |
michael@0 | 391 | |
michael@0 | 392 | /* If any object is possible, skip specifics. */ |
michael@0 | 393 | if (flags & TYPE_FLAG_ANYOBJECT) |
michael@0 | 394 | return list->append(Type::AnyObjectType()); |
michael@0 | 395 | |
michael@0 | 396 | /* Enqueue specific object types. */ |
michael@0 | 397 | unsigned count = getObjectCount(); |
michael@0 | 398 | for (unsigned i = 0; i < count; i++) { |
michael@0 | 399 | TypeObjectKey *object = getObject(i); |
michael@0 | 400 | if (object) { |
michael@0 | 401 | if (!list->append(Type::ObjectType(object))) |
michael@0 | 402 | return false; |
michael@0 | 403 | } |
michael@0 | 404 | } |
michael@0 | 405 | |
michael@0 | 406 | return true; |
michael@0 | 407 | } |
michael@0 | 408 | |
michael@0 | 409 | inline bool |
michael@0 | 410 | TypeSet::addTypesToConstraint(JSContext *cx, TypeConstraint *constraint) |
michael@0 | 411 | { |
michael@0 | 412 | /* |
michael@0 | 413 | * Build all types in the set into a vector before triggering the |
michael@0 | 414 | * constraint, as doing so may modify this type set. |
michael@0 | 415 | */ |
michael@0 | 416 | TypeList types; |
michael@0 | 417 | if (!enumerateTypes(&types)) |
michael@0 | 418 | return false; |
michael@0 | 419 | |
michael@0 | 420 | for (unsigned i = 0; i < types.length(); i++) |
michael@0 | 421 | constraint->newType(cx, this, types[i]); |
michael@0 | 422 | |
michael@0 | 423 | return true; |
michael@0 | 424 | } |
michael@0 | 425 | |
michael@0 | 426 | bool |
michael@0 | 427 | ConstraintTypeSet::addConstraint(JSContext *cx, TypeConstraint *constraint, bool callExisting) |
michael@0 | 428 | { |
michael@0 | 429 | if (!constraint) { |
michael@0 | 430 | /* OOM failure while constructing the constraint. */ |
michael@0 | 431 | return false; |
michael@0 | 432 | } |
michael@0 | 433 | |
michael@0 | 434 | JS_ASSERT(cx->compartment()->activeAnalysis); |
michael@0 | 435 | |
michael@0 | 436 | InferSpew(ISpewOps, "addConstraint: %sT%p%s %sC%p%s %s", |
michael@0 | 437 | InferSpewColor(this), this, InferSpewColorReset(), |
michael@0 | 438 | InferSpewColor(constraint), constraint, InferSpewColorReset(), |
michael@0 | 439 | constraint->kind()); |
michael@0 | 440 | |
michael@0 | 441 | JS_ASSERT(constraint->next == nullptr); |
michael@0 | 442 | constraint->next = constraintList; |
michael@0 | 443 | constraintList = constraint; |
michael@0 | 444 | |
michael@0 | 445 | if (callExisting) |
michael@0 | 446 | return addTypesToConstraint(cx, constraint); |
michael@0 | 447 | return true; |
michael@0 | 448 | } |
michael@0 | 449 | |
michael@0 | 450 | void |
michael@0 | 451 | TypeSet::clearObjects() |
michael@0 | 452 | { |
michael@0 | 453 | setBaseObjectCount(0); |
michael@0 | 454 | objectSet = nullptr; |
michael@0 | 455 | } |
michael@0 | 456 | |
michael@0 | 457 | void |
michael@0 | 458 | TypeSet::addType(Type type, LifoAlloc *alloc) |
michael@0 | 459 | { |
michael@0 | 460 | if (unknown()) |
michael@0 | 461 | return; |
michael@0 | 462 | |
michael@0 | 463 | if (type.isUnknown()) { |
michael@0 | 464 | flags |= TYPE_FLAG_BASE_MASK; |
michael@0 | 465 | clearObjects(); |
michael@0 | 466 | JS_ASSERT(unknown()); |
michael@0 | 467 | return; |
michael@0 | 468 | } |
michael@0 | 469 | |
michael@0 | 470 | if (type.isPrimitive()) { |
michael@0 | 471 | TypeFlags flag = PrimitiveTypeFlag(type.primitive()); |
michael@0 | 472 | if (flags & flag) |
michael@0 | 473 | return; |
michael@0 | 474 | |
michael@0 | 475 | /* If we add float to a type set it is also considered to contain int. */ |
michael@0 | 476 | if (flag == TYPE_FLAG_DOUBLE) |
michael@0 | 477 | flag |= TYPE_FLAG_INT32; |
michael@0 | 478 | |
michael@0 | 479 | flags |= flag; |
michael@0 | 480 | return; |
michael@0 | 481 | } |
michael@0 | 482 | |
michael@0 | 483 | if (flags & TYPE_FLAG_ANYOBJECT) |
michael@0 | 484 | return; |
michael@0 | 485 | if (type.isAnyObject()) |
michael@0 | 486 | goto unknownObject; |
michael@0 | 487 | |
michael@0 | 488 | { |
michael@0 | 489 | uint32_t objectCount = baseObjectCount(); |
michael@0 | 490 | TypeObjectKey *object = type.objectKey(); |
michael@0 | 491 | TypeObjectKey **pentry = HashSetInsert<TypeObjectKey *,TypeObjectKey,TypeObjectKey> |
michael@0 | 492 | (*alloc, objectSet, objectCount, object); |
michael@0 | 493 | if (!pentry) |
michael@0 | 494 | goto unknownObject; |
michael@0 | 495 | if (*pentry) |
michael@0 | 496 | return; |
michael@0 | 497 | *pentry = object; |
michael@0 | 498 | |
michael@0 | 499 | setBaseObjectCount(objectCount); |
michael@0 | 500 | |
michael@0 | 501 | if (objectCount == TYPE_FLAG_OBJECT_COUNT_LIMIT) |
michael@0 | 502 | goto unknownObject; |
michael@0 | 503 | } |
michael@0 | 504 | |
michael@0 | 505 | if (type.isTypeObject()) { |
michael@0 | 506 | TypeObject *nobject = type.typeObject(); |
michael@0 | 507 | JS_ASSERT(!nobject->singleton()); |
michael@0 | 508 | if (nobject->unknownProperties()) |
michael@0 | 509 | goto unknownObject; |
michael@0 | 510 | } |
michael@0 | 511 | |
michael@0 | 512 | if (false) { |
michael@0 | 513 | unknownObject: |
michael@0 | 514 | flags |= TYPE_FLAG_ANYOBJECT; |
michael@0 | 515 | clearObjects(); |
michael@0 | 516 | } |
michael@0 | 517 | } |
michael@0 | 518 | |
michael@0 | 519 | void |
michael@0 | 520 | ConstraintTypeSet::addType(ExclusiveContext *cxArg, Type type) |
michael@0 | 521 | { |
michael@0 | 522 | JS_ASSERT(cxArg->compartment()->activeAnalysis); |
michael@0 | 523 | |
michael@0 | 524 | if (hasType(type)) |
michael@0 | 525 | return; |
michael@0 | 526 | |
michael@0 | 527 | TypeSet::addType(type, &cxArg->typeLifoAlloc()); |
michael@0 | 528 | |
michael@0 | 529 | if (type.isObjectUnchecked() && unknownObject()) |
michael@0 | 530 | type = Type::AnyObjectType(); |
michael@0 | 531 | |
michael@0 | 532 | InferSpew(ISpewOps, "addType: %sT%p%s %s", |
michael@0 | 533 | InferSpewColor(this), this, InferSpewColorReset(), |
michael@0 | 534 | TypeString(type)); |
michael@0 | 535 | |
michael@0 | 536 | /* Propagate the type to all constraints. */ |
michael@0 | 537 | if (JSContext *cx = cxArg->maybeJSContext()) { |
michael@0 | 538 | TypeConstraint *constraint = constraintList; |
michael@0 | 539 | while (constraint) { |
michael@0 | 540 | constraint->newType(cx, this, type); |
michael@0 | 541 | constraint = constraint->next; |
michael@0 | 542 | } |
michael@0 | 543 | } else { |
michael@0 | 544 | JS_ASSERT(!constraintList); |
michael@0 | 545 | } |
michael@0 | 546 | } |
michael@0 | 547 | |
michael@0 | 548 | void |
michael@0 | 549 | TypeSet::print() |
michael@0 | 550 | { |
michael@0 | 551 | if (flags & TYPE_FLAG_NON_DATA_PROPERTY) |
michael@0 | 552 | fprintf(stderr, " [non-data]"); |
michael@0 | 553 | |
michael@0 | 554 | if (flags & TYPE_FLAG_NON_WRITABLE_PROPERTY) |
michael@0 | 555 | fprintf(stderr, " [non-writable]"); |
michael@0 | 556 | |
michael@0 | 557 | if (definiteProperty()) |
michael@0 | 558 | fprintf(stderr, " [definite:%d]", definiteSlot()); |
michael@0 | 559 | |
michael@0 | 560 | if (baseFlags() == 0 && !baseObjectCount()) { |
michael@0 | 561 | fprintf(stderr, " missing"); |
michael@0 | 562 | return; |
michael@0 | 563 | } |
michael@0 | 564 | |
michael@0 | 565 | if (flags & TYPE_FLAG_UNKNOWN) |
michael@0 | 566 | fprintf(stderr, " unknown"); |
michael@0 | 567 | if (flags & TYPE_FLAG_ANYOBJECT) |
michael@0 | 568 | fprintf(stderr, " object"); |
michael@0 | 569 | |
michael@0 | 570 | if (flags & TYPE_FLAG_UNDEFINED) |
michael@0 | 571 | fprintf(stderr, " void"); |
michael@0 | 572 | if (flags & TYPE_FLAG_NULL) |
michael@0 | 573 | fprintf(stderr, " null"); |
michael@0 | 574 | if (flags & TYPE_FLAG_BOOLEAN) |
michael@0 | 575 | fprintf(stderr, " bool"); |
michael@0 | 576 | if (flags & TYPE_FLAG_INT32) |
michael@0 | 577 | fprintf(stderr, " int"); |
michael@0 | 578 | if (flags & TYPE_FLAG_DOUBLE) |
michael@0 | 579 | fprintf(stderr, " float"); |
michael@0 | 580 | if (flags & TYPE_FLAG_STRING) |
michael@0 | 581 | fprintf(stderr, " string"); |
michael@0 | 582 | if (flags & TYPE_FLAG_LAZYARGS) |
michael@0 | 583 | fprintf(stderr, " lazyargs"); |
michael@0 | 584 | |
michael@0 | 585 | uint32_t objectCount = baseObjectCount(); |
michael@0 | 586 | if (objectCount) { |
michael@0 | 587 | fprintf(stderr, " object[%u]", objectCount); |
michael@0 | 588 | |
michael@0 | 589 | unsigned count = getObjectCount(); |
michael@0 | 590 | for (unsigned i = 0; i < count; i++) { |
michael@0 | 591 | TypeObjectKey *object = getObject(i); |
michael@0 | 592 | if (object) |
michael@0 | 593 | fprintf(stderr, " %s", TypeString(Type::ObjectType(object))); |
michael@0 | 594 | } |
michael@0 | 595 | } |
michael@0 | 596 | } |
michael@0 | 597 | |
michael@0 | 598 | bool |
michael@0 | 599 | TypeSet::clone(LifoAlloc *alloc, TemporaryTypeSet *result) const |
michael@0 | 600 | { |
michael@0 | 601 | JS_ASSERT(result->empty()); |
michael@0 | 602 | |
michael@0 | 603 | unsigned objectCount = baseObjectCount(); |
michael@0 | 604 | unsigned capacity = (objectCount >= 2) ? HashSetCapacity(objectCount) : 0; |
michael@0 | 605 | |
michael@0 | 606 | TypeObjectKey **newSet; |
michael@0 | 607 | if (capacity) { |
michael@0 | 608 | newSet = alloc->newArray<TypeObjectKey*>(capacity); |
michael@0 | 609 | if (!newSet) |
michael@0 | 610 | return false; |
michael@0 | 611 | PodCopy(newSet, objectSet, capacity); |
michael@0 | 612 | } |
michael@0 | 613 | |
michael@0 | 614 | new(result) TemporaryTypeSet(flags, capacity ? newSet : objectSet); |
michael@0 | 615 | return true; |
michael@0 | 616 | } |
michael@0 | 617 | |
michael@0 | 618 | TemporaryTypeSet * |
michael@0 | 619 | TypeSet::clone(LifoAlloc *alloc) const |
michael@0 | 620 | { |
michael@0 | 621 | TemporaryTypeSet *res = alloc->new_<TemporaryTypeSet>(); |
michael@0 | 622 | if (!res || !clone(alloc, res)) |
michael@0 | 623 | return nullptr; |
michael@0 | 624 | return res; |
michael@0 | 625 | } |
michael@0 | 626 | |
michael@0 | 627 | TemporaryTypeSet * |
michael@0 | 628 | TypeSet::filter(LifoAlloc *alloc, bool filterUndefined, bool filterNull) const |
michael@0 | 629 | { |
michael@0 | 630 | TemporaryTypeSet *res = clone(alloc); |
michael@0 | 631 | if (!res) |
michael@0 | 632 | return nullptr; |
michael@0 | 633 | |
michael@0 | 634 | if (filterUndefined) |
michael@0 | 635 | res->flags = res->flags & ~TYPE_FLAG_UNDEFINED; |
michael@0 | 636 | |
michael@0 | 637 | if (filterNull) |
michael@0 | 638 | res->flags = res->flags & ~TYPE_FLAG_NULL; |
michael@0 | 639 | |
michael@0 | 640 | return res; |
michael@0 | 641 | } |
michael@0 | 642 | |
michael@0 | 643 | /* static */ TemporaryTypeSet * |
michael@0 | 644 | TypeSet::unionSets(TypeSet *a, TypeSet *b, LifoAlloc *alloc) |
michael@0 | 645 | { |
michael@0 | 646 | TemporaryTypeSet *res = alloc->new_<TemporaryTypeSet>(a->baseFlags() | b->baseFlags(), |
michael@0 | 647 | static_cast<TypeObjectKey**>(nullptr)); |
michael@0 | 648 | if (!res) |
michael@0 | 649 | return nullptr; |
michael@0 | 650 | |
michael@0 | 651 | if (!res->unknownObject()) { |
michael@0 | 652 | for (size_t i = 0; i < a->getObjectCount() && !res->unknownObject(); i++) { |
michael@0 | 653 | if (TypeObjectKey *key = a->getObject(i)) |
michael@0 | 654 | res->addType(Type::ObjectType(key), alloc); |
michael@0 | 655 | } |
michael@0 | 656 | for (size_t i = 0; i < b->getObjectCount() && !res->unknownObject(); i++) { |
michael@0 | 657 | if (TypeObjectKey *key = b->getObject(i)) |
michael@0 | 658 | res->addType(Type::ObjectType(key), alloc); |
michael@0 | 659 | } |
michael@0 | 660 | } |
michael@0 | 661 | |
michael@0 | 662 | return res; |
michael@0 | 663 | } |
michael@0 | 664 | |
michael@0 | 665 | ///////////////////////////////////////////////////////////////////// |
michael@0 | 666 | // Compiler constraints |
michael@0 | 667 | ///////////////////////////////////////////////////////////////////// |
michael@0 | 668 | |
michael@0 | 669 | // Compiler constraints overview |
michael@0 | 670 | // |
michael@0 | 671 | // Constraints generated during Ion compilation capture assumptions made about |
michael@0 | 672 | // heap properties that will trigger invalidation of the resulting Ion code if |
michael@0 | 673 | // the constraint is violated. Constraints can only be attached to type sets on |
michael@0 | 674 | // the main thread, so to allow compilation to occur almost entirely off thread |
michael@0 | 675 | // the generation is split into two phases. |
michael@0 | 676 | // |
michael@0 | 677 | // During compilation, CompilerConstraint values are constructed in a list, |
michael@0 | 678 | // recording the heap property type set which was read from and its expected |
michael@0 | 679 | // contents, along with the assumption made about those contents. |
michael@0 | 680 | // |
michael@0 | 681 | // At the end of compilation, when linking the result on the main thread, the |
michael@0 | 682 | // list of compiler constraints are read and converted to type constraints and |
michael@0 | 683 | // attached to the type sets. If the property type sets have changed so that the |
michael@0 | 684 | // assumptions no longer hold then the compilation is aborted and its result |
michael@0 | 685 | // discarded. |
michael@0 | 686 | |
michael@0 | 687 | // Superclass of all constraints generated during Ion compilation. These may |
michael@0 | 688 | // be allocated off the main thread, using the current Ion context's allocator. |
michael@0 | 689 | class CompilerConstraint |
michael@0 | 690 | { |
michael@0 | 691 | public: |
michael@0 | 692 | // Property being queried by the compiler. |
michael@0 | 693 | HeapTypeSetKey property; |
michael@0 | 694 | |
michael@0 | 695 | // Contents of the property at the point when the query was performed. This |
michael@0 | 696 | // may differ from the actual property types later in compilation as the |
michael@0 | 697 | // main thread performs side effects. |
michael@0 | 698 | TemporaryTypeSet *expected; |
michael@0 | 699 | |
michael@0 | 700 | CompilerConstraint(LifoAlloc *alloc, const HeapTypeSetKey &property) |
michael@0 | 701 | : property(property), |
michael@0 | 702 | expected(property.maybeTypes() ? property.maybeTypes()->clone(alloc) : nullptr) |
michael@0 | 703 | {} |
michael@0 | 704 | |
michael@0 | 705 | // Generate the type constraint recording the assumption made by this |
michael@0 | 706 | // compilation. Returns true if the assumption originally made still holds. |
michael@0 | 707 | virtual bool generateTypeConstraint(JSContext *cx, RecompileInfo recompileInfo) = 0; |
michael@0 | 708 | }; |
michael@0 | 709 | |
michael@0 | 710 | class types::CompilerConstraintList |
michael@0 | 711 | { |
michael@0 | 712 | public: |
michael@0 | 713 | struct FrozenScript |
michael@0 | 714 | { |
michael@0 | 715 | JSScript *script; |
michael@0 | 716 | TemporaryTypeSet *thisTypes; |
michael@0 | 717 | TemporaryTypeSet *argTypes; |
michael@0 | 718 | TemporaryTypeSet *bytecodeTypes; |
michael@0 | 719 | }; |
michael@0 | 720 | |
michael@0 | 721 | private: |
michael@0 | 722 | |
michael@0 | 723 | // OOM during generation of some constraint. |
michael@0 | 724 | bool failed_; |
michael@0 | 725 | |
michael@0 | 726 | #ifdef JS_ION |
michael@0 | 727 | // Allocator used for constraints. |
michael@0 | 728 | LifoAlloc *alloc_; |
michael@0 | 729 | |
michael@0 | 730 | // Constraints generated on heap properties. |
michael@0 | 731 | Vector<CompilerConstraint *, 0, jit::IonAllocPolicy> constraints; |
michael@0 | 732 | |
michael@0 | 733 | // Scripts whose stack type sets were frozen for the compilation. |
michael@0 | 734 | Vector<FrozenScript, 1, jit::IonAllocPolicy> frozenScripts; |
michael@0 | 735 | #endif |
michael@0 | 736 | |
michael@0 | 737 | public: |
michael@0 | 738 | CompilerConstraintList(jit::TempAllocator &alloc) |
michael@0 | 739 | : failed_(false) |
michael@0 | 740 | #ifdef JS_ION |
michael@0 | 741 | , alloc_(alloc.lifoAlloc()) |
michael@0 | 742 | , constraints(alloc) |
michael@0 | 743 | , frozenScripts(alloc) |
michael@0 | 744 | #endif |
michael@0 | 745 | {} |
michael@0 | 746 | |
michael@0 | 747 | void add(CompilerConstraint *constraint) { |
michael@0 | 748 | #ifdef JS_ION |
michael@0 | 749 | if (!constraint || !constraints.append(constraint)) |
michael@0 | 750 | setFailed(); |
michael@0 | 751 | #else |
michael@0 | 752 | MOZ_CRASH(); |
michael@0 | 753 | #endif |
michael@0 | 754 | } |
michael@0 | 755 | |
michael@0 | 756 | void freezeScript(JSScript *script, |
michael@0 | 757 | TemporaryTypeSet *thisTypes, |
michael@0 | 758 | TemporaryTypeSet *argTypes, |
michael@0 | 759 | TemporaryTypeSet *bytecodeTypes) |
michael@0 | 760 | { |
michael@0 | 761 | #ifdef JS_ION |
michael@0 | 762 | FrozenScript entry; |
michael@0 | 763 | entry.script = script; |
michael@0 | 764 | entry.thisTypes = thisTypes; |
michael@0 | 765 | entry.argTypes = argTypes; |
michael@0 | 766 | entry.bytecodeTypes = bytecodeTypes; |
michael@0 | 767 | if (!frozenScripts.append(entry)) |
michael@0 | 768 | setFailed(); |
michael@0 | 769 | #else |
michael@0 | 770 | MOZ_CRASH(); |
michael@0 | 771 | #endif |
michael@0 | 772 | } |
michael@0 | 773 | |
michael@0 | 774 | size_t length() { |
michael@0 | 775 | #ifdef JS_ION |
michael@0 | 776 | return constraints.length(); |
michael@0 | 777 | #else |
michael@0 | 778 | MOZ_CRASH(); |
michael@0 | 779 | #endif |
michael@0 | 780 | } |
michael@0 | 781 | |
michael@0 | 782 | CompilerConstraint *get(size_t i) { |
michael@0 | 783 | #ifdef JS_ION |
michael@0 | 784 | return constraints[i]; |
michael@0 | 785 | #else |
michael@0 | 786 | MOZ_CRASH(); |
michael@0 | 787 | #endif |
michael@0 | 788 | } |
michael@0 | 789 | |
michael@0 | 790 | size_t numFrozenScripts() { |
michael@0 | 791 | #ifdef JS_ION |
michael@0 | 792 | return frozenScripts.length(); |
michael@0 | 793 | #else |
michael@0 | 794 | MOZ_CRASH(); |
michael@0 | 795 | #endif |
michael@0 | 796 | } |
michael@0 | 797 | |
michael@0 | 798 | const FrozenScript &frozenScript(size_t i) { |
michael@0 | 799 | #ifdef JS_ION |
michael@0 | 800 | return frozenScripts[i]; |
michael@0 | 801 | #else |
michael@0 | 802 | MOZ_CRASH(); |
michael@0 | 803 | #endif |
michael@0 | 804 | } |
michael@0 | 805 | |
michael@0 | 806 | bool failed() { |
michael@0 | 807 | return failed_; |
michael@0 | 808 | } |
michael@0 | 809 | void setFailed() { |
michael@0 | 810 | failed_ = true; |
michael@0 | 811 | } |
michael@0 | 812 | LifoAlloc *alloc() const { |
michael@0 | 813 | #ifdef JS_ION |
michael@0 | 814 | return alloc_; |
michael@0 | 815 | #else |
michael@0 | 816 | MOZ_CRASH(); |
michael@0 | 817 | #endif |
michael@0 | 818 | } |
michael@0 | 819 | }; |
michael@0 | 820 | |
michael@0 | 821 | CompilerConstraintList * |
michael@0 | 822 | types::NewCompilerConstraintList(jit::TempAllocator &alloc) |
michael@0 | 823 | { |
michael@0 | 824 | #ifdef JS_ION |
michael@0 | 825 | return alloc.lifoAlloc()->new_<CompilerConstraintList>(alloc); |
michael@0 | 826 | #else |
michael@0 | 827 | MOZ_CRASH(); |
michael@0 | 828 | #endif |
michael@0 | 829 | } |
michael@0 | 830 | |
michael@0 | 831 | /* static */ bool |
michael@0 | 832 | TypeScript::FreezeTypeSets(CompilerConstraintList *constraints, JSScript *script, |
michael@0 | 833 | TemporaryTypeSet **pThisTypes, |
michael@0 | 834 | TemporaryTypeSet **pArgTypes, |
michael@0 | 835 | TemporaryTypeSet **pBytecodeTypes) |
michael@0 | 836 | { |
michael@0 | 837 | LifoAlloc *alloc = constraints->alloc(); |
michael@0 | 838 | StackTypeSet *existing = script->types->typeArray(); |
michael@0 | 839 | |
michael@0 | 840 | size_t count = NumTypeSets(script); |
michael@0 | 841 | TemporaryTypeSet *types = alloc->newArrayUninitialized<TemporaryTypeSet>(count); |
michael@0 | 842 | if (!types) |
michael@0 | 843 | return false; |
michael@0 | 844 | PodZero(types, count); |
michael@0 | 845 | |
michael@0 | 846 | for (size_t i = 0; i < count; i++) { |
michael@0 | 847 | if (!existing[i].clone(alloc, &types[i])) |
michael@0 | 848 | return false; |
michael@0 | 849 | } |
michael@0 | 850 | |
michael@0 | 851 | *pThisTypes = types + (ThisTypes(script) - existing); |
michael@0 | 852 | *pArgTypes = (script->functionNonDelazifying() && script->functionNonDelazifying()->nargs()) |
michael@0 | 853 | ? (types + (ArgTypes(script, 0) - existing)) |
michael@0 | 854 | : nullptr; |
michael@0 | 855 | *pBytecodeTypes = types; |
michael@0 | 856 | |
michael@0 | 857 | constraints->freezeScript(script, *pThisTypes, *pArgTypes, *pBytecodeTypes); |
michael@0 | 858 | return true; |
michael@0 | 859 | } |
michael@0 | 860 | |
michael@0 | 861 | namespace { |
michael@0 | 862 | |
michael@0 | 863 | template <typename T> |
michael@0 | 864 | class CompilerConstraintInstance : public CompilerConstraint |
michael@0 | 865 | { |
michael@0 | 866 | T data; |
michael@0 | 867 | |
michael@0 | 868 | public: |
michael@0 | 869 | CompilerConstraintInstance<T>(LifoAlloc *alloc, const HeapTypeSetKey &property, const T &data) |
michael@0 | 870 | : CompilerConstraint(alloc, property), data(data) |
michael@0 | 871 | {} |
michael@0 | 872 | |
michael@0 | 873 | bool generateTypeConstraint(JSContext *cx, RecompileInfo recompileInfo); |
michael@0 | 874 | }; |
michael@0 | 875 | |
michael@0 | 876 | // Constraint generated from a CompilerConstraint when linking the compilation. |
michael@0 | 877 | template <typename T> |
michael@0 | 878 | class TypeCompilerConstraint : public TypeConstraint |
michael@0 | 879 | { |
michael@0 | 880 | // Compilation which this constraint may invalidate. |
michael@0 | 881 | RecompileInfo compilation; |
michael@0 | 882 | |
michael@0 | 883 | T data; |
michael@0 | 884 | |
michael@0 | 885 | public: |
michael@0 | 886 | TypeCompilerConstraint<T>(RecompileInfo compilation, const T &data) |
michael@0 | 887 | : compilation(compilation), data(data) |
michael@0 | 888 | {} |
michael@0 | 889 | |
michael@0 | 890 | const char *kind() { return data.kind(); } |
michael@0 | 891 | |
michael@0 | 892 | void newType(JSContext *cx, TypeSet *source, Type type) { |
michael@0 | 893 | if (data.invalidateOnNewType(type)) |
michael@0 | 894 | cx->zone()->types.addPendingRecompile(cx, compilation); |
michael@0 | 895 | } |
michael@0 | 896 | |
michael@0 | 897 | void newPropertyState(JSContext *cx, TypeSet *source) { |
michael@0 | 898 | if (data.invalidateOnNewPropertyState(source)) |
michael@0 | 899 | cx->zone()->types.addPendingRecompile(cx, compilation); |
michael@0 | 900 | } |
michael@0 | 901 | |
michael@0 | 902 | void newObjectState(JSContext *cx, TypeObject *object) { |
michael@0 | 903 | // Note: Once the object has unknown properties, no more notifications |
michael@0 | 904 | // will be sent on changes to its state, so always invalidate any |
michael@0 | 905 | // associated compilations. |
michael@0 | 906 | if (object->unknownProperties() || data.invalidateOnNewObjectState(object)) |
michael@0 | 907 | cx->zone()->types.addPendingRecompile(cx, compilation); |
michael@0 | 908 | } |
michael@0 | 909 | |
michael@0 | 910 | bool sweep(TypeZone &zone, TypeConstraint **res) { |
michael@0 | 911 | if (data.shouldSweep() || compilation.shouldSweep(zone)) |
michael@0 | 912 | return false; |
michael@0 | 913 | *res = zone.typeLifoAlloc.new_<TypeCompilerConstraint<T> >(compilation, data); |
michael@0 | 914 | return true; |
michael@0 | 915 | } |
michael@0 | 916 | }; |
michael@0 | 917 | |
michael@0 | 918 | template <typename T> |
michael@0 | 919 | bool |
michael@0 | 920 | CompilerConstraintInstance<T>::generateTypeConstraint(JSContext *cx, RecompileInfo recompileInfo) |
michael@0 | 921 | { |
michael@0 | 922 | if (property.object()->unknownProperties()) |
michael@0 | 923 | return false; |
michael@0 | 924 | |
michael@0 | 925 | if (!property.instantiate(cx)) |
michael@0 | 926 | return false; |
michael@0 | 927 | |
michael@0 | 928 | if (!data.constraintHolds(cx, property, expected)) |
michael@0 | 929 | return false; |
michael@0 | 930 | |
michael@0 | 931 | return property.maybeTypes()->addConstraint(cx, cx->typeLifoAlloc().new_<TypeCompilerConstraint<T> >(recompileInfo, data), |
michael@0 | 932 | /* callExisting = */ false); |
michael@0 | 933 | } |
michael@0 | 934 | |
michael@0 | 935 | } /* anonymous namespace */ |
michael@0 | 936 | |
michael@0 | 937 | const Class * |
michael@0 | 938 | TypeObjectKey::clasp() |
michael@0 | 939 | { |
michael@0 | 940 | return isTypeObject() ? asTypeObject()->clasp() : asSingleObject()->getClass(); |
michael@0 | 941 | } |
michael@0 | 942 | |
michael@0 | 943 | TaggedProto |
michael@0 | 944 | TypeObjectKey::proto() |
michael@0 | 945 | { |
michael@0 | 946 | JS_ASSERT(hasTenuredProto()); |
michael@0 | 947 | return isTypeObject() ? asTypeObject()->proto() : asSingleObject()->getTaggedProto(); |
michael@0 | 948 | } |
michael@0 | 949 | |
michael@0 | 950 | bool |
michael@0 | 951 | ObjectImpl::hasTenuredProto() const |
michael@0 | 952 | { |
michael@0 | 953 | return type_->hasTenuredProto(); |
michael@0 | 954 | } |
michael@0 | 955 | |
michael@0 | 956 | bool |
michael@0 | 957 | TypeObjectKey::hasTenuredProto() |
michael@0 | 958 | { |
michael@0 | 959 | return isTypeObject() ? asTypeObject()->hasTenuredProto() : asSingleObject()->hasTenuredProto(); |
michael@0 | 960 | } |
michael@0 | 961 | |
michael@0 | 962 | JSObject * |
michael@0 | 963 | TypeObjectKey::singleton() |
michael@0 | 964 | { |
michael@0 | 965 | return isTypeObject() ? asTypeObject()->singleton() : asSingleObject(); |
michael@0 | 966 | } |
michael@0 | 967 | |
michael@0 | 968 | TypeNewScript * |
michael@0 | 969 | TypeObjectKey::newScript() |
michael@0 | 970 | { |
michael@0 | 971 | if (isTypeObject() && asTypeObject()->hasNewScript()) |
michael@0 | 972 | return asTypeObject()->newScript(); |
michael@0 | 973 | return nullptr; |
michael@0 | 974 | } |
michael@0 | 975 | |
michael@0 | 976 | TypeObject * |
michael@0 | 977 | TypeObjectKey::maybeType() |
michael@0 | 978 | { |
michael@0 | 979 | if (isTypeObject()) |
michael@0 | 980 | return asTypeObject(); |
michael@0 | 981 | if (asSingleObject()->hasLazyType()) |
michael@0 | 982 | return nullptr; |
michael@0 | 983 | return asSingleObject()->type(); |
michael@0 | 984 | } |
michael@0 | 985 | |
michael@0 | 986 | bool |
michael@0 | 987 | TypeObjectKey::unknownProperties() |
michael@0 | 988 | { |
michael@0 | 989 | if (TypeObject *type = maybeType()) |
michael@0 | 990 | return type->unknownProperties(); |
michael@0 | 991 | return false; |
michael@0 | 992 | } |
michael@0 | 993 | |
michael@0 | 994 | HeapTypeSetKey |
michael@0 | 995 | TypeObjectKey::property(jsid id) |
michael@0 | 996 | { |
michael@0 | 997 | JS_ASSERT(!unknownProperties()); |
michael@0 | 998 | |
michael@0 | 999 | HeapTypeSetKey property; |
michael@0 | 1000 | property.object_ = this; |
michael@0 | 1001 | property.id_ = id; |
michael@0 | 1002 | if (TypeObject *type = maybeType()) |
michael@0 | 1003 | property.maybeTypes_ = type->maybeGetProperty(id); |
michael@0 | 1004 | |
michael@0 | 1005 | return property; |
michael@0 | 1006 | } |
michael@0 | 1007 | |
michael@0 | 1008 | void |
michael@0 | 1009 | TypeObjectKey::ensureTrackedProperty(JSContext *cx, jsid id) |
michael@0 | 1010 | { |
michael@0 | 1011 | #ifdef JS_ION |
michael@0 | 1012 | // If we are accessing a lazily defined property which actually exists in |
michael@0 | 1013 | // the VM and has not been instantiated yet, instantiate it now if we are |
michael@0 | 1014 | // on the main thread and able to do so. |
michael@0 | 1015 | if (!JSID_IS_VOID(id) && !JSID_IS_EMPTY(id)) { |
michael@0 | 1016 | JS_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime())); |
michael@0 | 1017 | if (JSObject *obj = singleton()) { |
michael@0 | 1018 | if (obj->isNative() && obj->nativeLookupPure(id)) |
michael@0 | 1019 | EnsureTrackPropertyTypes(cx, obj, id); |
michael@0 | 1020 | } |
michael@0 | 1021 | } |
michael@0 | 1022 | #endif // JS_ION |
michael@0 | 1023 | } |
michael@0 | 1024 | |
michael@0 | 1025 | bool |
michael@0 | 1026 | HeapTypeSetKey::instantiate(JSContext *cx) |
michael@0 | 1027 | { |
michael@0 | 1028 | if (maybeTypes()) |
michael@0 | 1029 | return true; |
michael@0 | 1030 | if (object()->isSingleObject() && !object()->asSingleObject()->getType(cx)) { |
michael@0 | 1031 | cx->clearPendingException(); |
michael@0 | 1032 | return false; |
michael@0 | 1033 | } |
michael@0 | 1034 | maybeTypes_ = object()->maybeType()->getProperty(cx, id()); |
michael@0 | 1035 | return maybeTypes_ != nullptr; |
michael@0 | 1036 | } |
michael@0 | 1037 | |
michael@0 | 1038 | static bool |
michael@0 | 1039 | CheckFrozenTypeSet(JSContext *cx, TemporaryTypeSet *frozen, StackTypeSet *actual) |
michael@0 | 1040 | { |
michael@0 | 1041 | // Return whether the types frozen for a script during compilation are |
michael@0 | 1042 | // still valid. Also check for any new types added to the frozen set during |
michael@0 | 1043 | // compilation, and add them to the actual stack type sets. These new types |
michael@0 | 1044 | // indicate places where the compiler relaxed its possible inputs to be |
michael@0 | 1045 | // more tolerant of potential new types. |
michael@0 | 1046 | |
michael@0 | 1047 | if (!actual->isSubset(frozen)) |
michael@0 | 1048 | return false; |
michael@0 | 1049 | |
michael@0 | 1050 | if (!frozen->isSubset(actual)) { |
michael@0 | 1051 | TypeSet::TypeList list; |
michael@0 | 1052 | frozen->enumerateTypes(&list); |
michael@0 | 1053 | |
michael@0 | 1054 | for (size_t i = 0; i < list.length(); i++) |
michael@0 | 1055 | actual->addType(cx, list[i]); |
michael@0 | 1056 | } |
michael@0 | 1057 | |
michael@0 | 1058 | return true; |
michael@0 | 1059 | } |
michael@0 | 1060 | |
michael@0 | 1061 | namespace { |
michael@0 | 1062 | |
michael@0 | 1063 | /* |
michael@0 | 1064 | * As for TypeConstraintFreeze, but describes an implicit freeze constraint |
michael@0 | 1065 | * added for stack types within a script. Applies to all compilations of the |
michael@0 | 1066 | * script, not just a single one. |
michael@0 | 1067 | */ |
michael@0 | 1068 | class TypeConstraintFreezeStack : public TypeConstraint |
michael@0 | 1069 | { |
michael@0 | 1070 | JSScript *script_; |
michael@0 | 1071 | |
michael@0 | 1072 | public: |
michael@0 | 1073 | TypeConstraintFreezeStack(JSScript *script) |
michael@0 | 1074 | : script_(script) |
michael@0 | 1075 | {} |
michael@0 | 1076 | |
michael@0 | 1077 | const char *kind() { return "freezeStack"; } |
michael@0 | 1078 | |
michael@0 | 1079 | void newType(JSContext *cx, TypeSet *source, Type type) { |
michael@0 | 1080 | /* |
michael@0 | 1081 | * Unlike TypeConstraintFreeze, triggering this constraint once does |
michael@0 | 1082 | * not disable it on future changes to the type set. |
michael@0 | 1083 | */ |
michael@0 | 1084 | cx->zone()->types.addPendingRecompile(cx, script_); |
michael@0 | 1085 | } |
michael@0 | 1086 | |
michael@0 | 1087 | bool sweep(TypeZone &zone, TypeConstraint **res) { |
michael@0 | 1088 | if (IsScriptAboutToBeFinalized(&script_)) |
michael@0 | 1089 | return false; |
michael@0 | 1090 | *res = zone.typeLifoAlloc.new_<TypeConstraintFreezeStack>(script_); |
michael@0 | 1091 | return true; |
michael@0 | 1092 | } |
michael@0 | 1093 | }; |
michael@0 | 1094 | |
michael@0 | 1095 | } /* anonymous namespace */ |
michael@0 | 1096 | |
michael@0 | 1097 | bool |
michael@0 | 1098 | types::FinishCompilation(JSContext *cx, HandleScript script, ExecutionMode executionMode, |
michael@0 | 1099 | CompilerConstraintList *constraints, RecompileInfo *precompileInfo) |
michael@0 | 1100 | { |
michael@0 | 1101 | if (constraints->failed()) |
michael@0 | 1102 | return false; |
michael@0 | 1103 | |
michael@0 | 1104 | CompilerOutput co(script, executionMode); |
michael@0 | 1105 | |
michael@0 | 1106 | TypeZone &types = cx->zone()->types; |
michael@0 | 1107 | if (!types.compilerOutputs) { |
michael@0 | 1108 | types.compilerOutputs = cx->new_< Vector<CompilerOutput> >(cx); |
michael@0 | 1109 | if (!types.compilerOutputs) |
michael@0 | 1110 | return false; |
michael@0 | 1111 | } |
michael@0 | 1112 | |
michael@0 | 1113 | #ifdef DEBUG |
michael@0 | 1114 | for (size_t i = 0; i < types.compilerOutputs->length(); i++) { |
michael@0 | 1115 | const CompilerOutput &co = (*types.compilerOutputs)[i]; |
michael@0 | 1116 | JS_ASSERT_IF(co.isValid(), co.script() != script || co.mode() != executionMode); |
michael@0 | 1117 | } |
michael@0 | 1118 | #endif |
michael@0 | 1119 | |
michael@0 | 1120 | uint32_t index = types.compilerOutputs->length(); |
michael@0 | 1121 | if (!types.compilerOutputs->append(co)) |
michael@0 | 1122 | return false; |
michael@0 | 1123 | |
michael@0 | 1124 | *precompileInfo = RecompileInfo(index); |
michael@0 | 1125 | |
michael@0 | 1126 | bool succeeded = true; |
michael@0 | 1127 | |
michael@0 | 1128 | for (size_t i = 0; i < constraints->length(); i++) { |
michael@0 | 1129 | CompilerConstraint *constraint = constraints->get(i); |
michael@0 | 1130 | if (!constraint->generateTypeConstraint(cx, *precompileInfo)) |
michael@0 | 1131 | succeeded = false; |
michael@0 | 1132 | } |
michael@0 | 1133 | |
michael@0 | 1134 | for (size_t i = 0; i < constraints->numFrozenScripts(); i++) { |
michael@0 | 1135 | const CompilerConstraintList::FrozenScript &entry = constraints->frozenScript(i); |
michael@0 | 1136 | JS_ASSERT(entry.script->types); |
michael@0 | 1137 | |
michael@0 | 1138 | if (!CheckFrozenTypeSet(cx, entry.thisTypes, types::TypeScript::ThisTypes(entry.script))) |
michael@0 | 1139 | succeeded = false; |
michael@0 | 1140 | unsigned nargs = entry.script->functionNonDelazifying() |
michael@0 | 1141 | ? entry.script->functionNonDelazifying()->nargs() |
michael@0 | 1142 | : 0; |
michael@0 | 1143 | for (size_t i = 0; i < nargs; i++) { |
michael@0 | 1144 | if (!CheckFrozenTypeSet(cx, &entry.argTypes[i], types::TypeScript::ArgTypes(entry.script, i))) |
michael@0 | 1145 | succeeded = false; |
michael@0 | 1146 | } |
michael@0 | 1147 | for (size_t i = 0; i < entry.script->nTypeSets(); i++) { |
michael@0 | 1148 | if (!CheckFrozenTypeSet(cx, &entry.bytecodeTypes[i], &entry.script->types->typeArray()[i])) |
michael@0 | 1149 | succeeded = false; |
michael@0 | 1150 | } |
michael@0 | 1151 | |
michael@0 | 1152 | // If necessary, add constraints to trigger invalidation on the script |
michael@0 | 1153 | // after any future changes to the stack type sets. |
michael@0 | 1154 | if (entry.script->hasFreezeConstraints()) |
michael@0 | 1155 | continue; |
michael@0 | 1156 | entry.script->setHasFreezeConstraints(); |
michael@0 | 1157 | |
michael@0 | 1158 | size_t count = TypeScript::NumTypeSets(entry.script); |
michael@0 | 1159 | |
michael@0 | 1160 | StackTypeSet *array = entry.script->types->typeArray(); |
michael@0 | 1161 | for (size_t i = 0; i < count; i++) { |
michael@0 | 1162 | if (!array[i].addConstraint(cx, cx->typeLifoAlloc().new_<TypeConstraintFreezeStack>(entry.script), false)) |
michael@0 | 1163 | succeeded = false; |
michael@0 | 1164 | } |
michael@0 | 1165 | } |
michael@0 | 1166 | |
michael@0 | 1167 | if (!succeeded || types.compilerOutputs->back().pendingInvalidation()) { |
michael@0 | 1168 | types.compilerOutputs->back().invalidate(); |
michael@0 | 1169 | script->resetUseCount(); |
michael@0 | 1170 | return false; |
michael@0 | 1171 | } |
michael@0 | 1172 | |
michael@0 | 1173 | return true; |
michael@0 | 1174 | } |
michael@0 | 1175 | |
michael@0 | 1176 | static void |
michael@0 | 1177 | CheckDefinitePropertiesTypeSet(JSContext *cx, TemporaryTypeSet *frozen, StackTypeSet *actual) |
michael@0 | 1178 | { |
michael@0 | 1179 | // The definite properties analysis happens on the main thread, so no new |
michael@0 | 1180 | // types can have been added to actual. The analysis may have updated the |
michael@0 | 1181 | // contents of |frozen| though with new speculative types, and these need |
michael@0 | 1182 | // to be reflected in |actual| for AddClearDefiniteFunctionUsesInScript |
michael@0 | 1183 | // to work. |
michael@0 | 1184 | if (!frozen->isSubset(actual)) { |
michael@0 | 1185 | TypeSet::TypeList list; |
michael@0 | 1186 | frozen->enumerateTypes(&list); |
michael@0 | 1187 | |
michael@0 | 1188 | for (size_t i = 0; i < list.length(); i++) |
michael@0 | 1189 | actual->addType(cx, list[i]); |
michael@0 | 1190 | } |
michael@0 | 1191 | } |
michael@0 | 1192 | |
michael@0 | 1193 | void |
michael@0 | 1194 | types::FinishDefinitePropertiesAnalysis(JSContext *cx, CompilerConstraintList *constraints) |
michael@0 | 1195 | { |
michael@0 | 1196 | #ifdef DEBUG |
michael@0 | 1197 | // Assert no new types have been added to the StackTypeSets. Do this before |
michael@0 | 1198 | // calling CheckDefinitePropertiesTypeSet, as it may add new types to the |
michael@0 | 1199 | // StackTypeSets and break these invariants if a script is inlined more |
michael@0 | 1200 | // than once. See also CheckDefinitePropertiesTypeSet. |
michael@0 | 1201 | for (size_t i = 0; i < constraints->numFrozenScripts(); i++) { |
michael@0 | 1202 | const CompilerConstraintList::FrozenScript &entry = constraints->frozenScript(i); |
michael@0 | 1203 | JSScript *script = entry.script; |
michael@0 | 1204 | JS_ASSERT(script->types); |
michael@0 | 1205 | |
michael@0 | 1206 | JS_ASSERT(TypeScript::ThisTypes(script)->isSubset(entry.thisTypes)); |
michael@0 | 1207 | |
michael@0 | 1208 | unsigned nargs = entry.script->functionNonDelazifying() |
michael@0 | 1209 | ? entry.script->functionNonDelazifying()->nargs() |
michael@0 | 1210 | : 0; |
michael@0 | 1211 | for (size_t j = 0; j < nargs; j++) |
michael@0 | 1212 | JS_ASSERT(TypeScript::ArgTypes(script, j)->isSubset(&entry.argTypes[j])); |
michael@0 | 1213 | |
michael@0 | 1214 | for (size_t j = 0; j < script->nTypeSets(); j++) |
michael@0 | 1215 | JS_ASSERT(script->types->typeArray()[j].isSubset(&entry.bytecodeTypes[j])); |
michael@0 | 1216 | } |
michael@0 | 1217 | #endif |
michael@0 | 1218 | |
michael@0 | 1219 | for (size_t i = 0; i < constraints->numFrozenScripts(); i++) { |
michael@0 | 1220 | const CompilerConstraintList::FrozenScript &entry = constraints->frozenScript(i); |
michael@0 | 1221 | JSScript *script = entry.script; |
michael@0 | 1222 | JS_ASSERT(script->types); |
michael@0 | 1223 | |
michael@0 | 1224 | if (!script->types) |
michael@0 | 1225 | MOZ_CRASH(); |
michael@0 | 1226 | |
michael@0 | 1227 | CheckDefinitePropertiesTypeSet(cx, entry.thisTypes, TypeScript::ThisTypes(script)); |
michael@0 | 1228 | |
michael@0 | 1229 | unsigned nargs = script->functionNonDelazifying() |
michael@0 | 1230 | ? script->functionNonDelazifying()->nargs() |
michael@0 | 1231 | : 0; |
michael@0 | 1232 | for (size_t j = 0; j < nargs; j++) |
michael@0 | 1233 | CheckDefinitePropertiesTypeSet(cx, &entry.argTypes[j], TypeScript::ArgTypes(script, j)); |
michael@0 | 1234 | |
michael@0 | 1235 | for (size_t j = 0; j < script->nTypeSets(); j++) |
michael@0 | 1236 | CheckDefinitePropertiesTypeSet(cx, &entry.bytecodeTypes[j], &script->types->typeArray()[j]); |
michael@0 | 1237 | } |
michael@0 | 1238 | } |
michael@0 | 1239 | |
michael@0 | 1240 | namespace { |
michael@0 | 1241 | |
michael@0 | 1242 | // Constraint which triggers recompilation of a script if any type is added to a type set. */ |
michael@0 | 1243 | class ConstraintDataFreeze |
michael@0 | 1244 | { |
michael@0 | 1245 | public: |
michael@0 | 1246 | ConstraintDataFreeze() {} |
michael@0 | 1247 | |
michael@0 | 1248 | const char *kind() { return "freeze"; } |
michael@0 | 1249 | |
michael@0 | 1250 | bool invalidateOnNewType(Type type) { return true; } |
michael@0 | 1251 | bool invalidateOnNewPropertyState(TypeSet *property) { return true; } |
michael@0 | 1252 | bool invalidateOnNewObjectState(TypeObject *object) { return false; } |
michael@0 | 1253 | |
michael@0 | 1254 | bool constraintHolds(JSContext *cx, |
michael@0 | 1255 | const HeapTypeSetKey &property, TemporaryTypeSet *expected) |
michael@0 | 1256 | { |
michael@0 | 1257 | return expected |
michael@0 | 1258 | ? property.maybeTypes()->isSubset(expected) |
michael@0 | 1259 | : property.maybeTypes()->empty(); |
michael@0 | 1260 | } |
michael@0 | 1261 | |
michael@0 | 1262 | bool shouldSweep() { return false; } |
michael@0 | 1263 | }; |
michael@0 | 1264 | |
michael@0 | 1265 | } /* anonymous namespace */ |
michael@0 | 1266 | |
michael@0 | 1267 | void |
michael@0 | 1268 | HeapTypeSetKey::freeze(CompilerConstraintList *constraints) |
michael@0 | 1269 | { |
michael@0 | 1270 | LifoAlloc *alloc = constraints->alloc(); |
michael@0 | 1271 | |
michael@0 | 1272 | typedef CompilerConstraintInstance<ConstraintDataFreeze> T; |
michael@0 | 1273 | constraints->add(alloc->new_<T>(alloc, *this, ConstraintDataFreeze())); |
michael@0 | 1274 | } |
michael@0 | 1275 | |
michael@0 | 1276 | static inline jit::MIRType |
michael@0 | 1277 | GetMIRTypeFromTypeFlags(TypeFlags flags) |
michael@0 | 1278 | { |
michael@0 | 1279 | switch (flags) { |
michael@0 | 1280 | case TYPE_FLAG_UNDEFINED: |
michael@0 | 1281 | return jit::MIRType_Undefined; |
michael@0 | 1282 | case TYPE_FLAG_NULL: |
michael@0 | 1283 | return jit::MIRType_Null; |
michael@0 | 1284 | case TYPE_FLAG_BOOLEAN: |
michael@0 | 1285 | return jit::MIRType_Boolean; |
michael@0 | 1286 | case TYPE_FLAG_INT32: |
michael@0 | 1287 | return jit::MIRType_Int32; |
michael@0 | 1288 | case (TYPE_FLAG_INT32 | TYPE_FLAG_DOUBLE): |
michael@0 | 1289 | return jit::MIRType_Double; |
michael@0 | 1290 | case TYPE_FLAG_STRING: |
michael@0 | 1291 | return jit::MIRType_String; |
michael@0 | 1292 | case TYPE_FLAG_LAZYARGS: |
michael@0 | 1293 | return jit::MIRType_MagicOptimizedArguments; |
michael@0 | 1294 | case TYPE_FLAG_ANYOBJECT: |
michael@0 | 1295 | return jit::MIRType_Object; |
michael@0 | 1296 | default: |
michael@0 | 1297 | return jit::MIRType_Value; |
michael@0 | 1298 | } |
michael@0 | 1299 | } |
michael@0 | 1300 | |
michael@0 | 1301 | jit::MIRType |
michael@0 | 1302 | TemporaryTypeSet::getKnownMIRType() |
michael@0 | 1303 | { |
michael@0 | 1304 | TypeFlags flags = baseFlags(); |
michael@0 | 1305 | jit::MIRType type; |
michael@0 | 1306 | |
michael@0 | 1307 | if (baseObjectCount()) |
michael@0 | 1308 | type = flags ? jit::MIRType_Value : jit::MIRType_Object; |
michael@0 | 1309 | else |
michael@0 | 1310 | type = GetMIRTypeFromTypeFlags(flags); |
michael@0 | 1311 | |
michael@0 | 1312 | /* |
michael@0 | 1313 | * If the type set is totally empty then it will be treated as unknown, |
michael@0 | 1314 | * but we still need to record the dependency as adding a new type can give |
michael@0 | 1315 | * it a definite type tag. This is not needed if there are enough types |
michael@0 | 1316 | * that the exact tag is unknown, as it will stay unknown as more types are |
michael@0 | 1317 | * added to the set. |
michael@0 | 1318 | */ |
michael@0 | 1319 | DebugOnly<bool> empty = flags == 0 && baseObjectCount() == 0; |
michael@0 | 1320 | JS_ASSERT_IF(empty, type == jit::MIRType_Value); |
michael@0 | 1321 | |
michael@0 | 1322 | return type; |
michael@0 | 1323 | } |
michael@0 | 1324 | |
michael@0 | 1325 | jit::MIRType |
michael@0 | 1326 | HeapTypeSetKey::knownMIRType(CompilerConstraintList *constraints) |
michael@0 | 1327 | { |
michael@0 | 1328 | TypeSet *types = maybeTypes(); |
michael@0 | 1329 | |
michael@0 | 1330 | if (!types || types->unknown()) |
michael@0 | 1331 | return jit::MIRType_Value; |
michael@0 | 1332 | |
michael@0 | 1333 | TypeFlags flags = types->baseFlags() & ~TYPE_FLAG_ANYOBJECT; |
michael@0 | 1334 | jit::MIRType type; |
michael@0 | 1335 | |
michael@0 | 1336 | if (types->unknownObject() || types->getObjectCount()) |
michael@0 | 1337 | type = flags ? jit::MIRType_Value : jit::MIRType_Object; |
michael@0 | 1338 | else |
michael@0 | 1339 | type = GetMIRTypeFromTypeFlags(flags); |
michael@0 | 1340 | |
michael@0 | 1341 | if (type != jit::MIRType_Value) |
michael@0 | 1342 | freeze(constraints); |
michael@0 | 1343 | |
michael@0 | 1344 | /* |
michael@0 | 1345 | * If the type set is totally empty then it will be treated as unknown, |
michael@0 | 1346 | * but we still need to record the dependency as adding a new type can give |
michael@0 | 1347 | * it a definite type tag. This is not needed if there are enough types |
michael@0 | 1348 | * that the exact tag is unknown, as it will stay unknown as more types are |
michael@0 | 1349 | * added to the set. |
michael@0 | 1350 | */ |
michael@0 | 1351 | JS_ASSERT_IF(types->empty(), type == jit::MIRType_Value); |
michael@0 | 1352 | |
michael@0 | 1353 | return type; |
michael@0 | 1354 | } |
michael@0 | 1355 | |
michael@0 | 1356 | bool |
michael@0 | 1357 | HeapTypeSetKey::isOwnProperty(CompilerConstraintList *constraints) |
michael@0 | 1358 | { |
michael@0 | 1359 | if (maybeTypes() && (!maybeTypes()->empty() || maybeTypes()->nonDataProperty())) |
michael@0 | 1360 | return true; |
michael@0 | 1361 | if (JSObject *obj = object()->singleton()) { |
michael@0 | 1362 | if (CanHaveEmptyPropertyTypesForOwnProperty(obj)) |
michael@0 | 1363 | return true; |
michael@0 | 1364 | } |
michael@0 | 1365 | freeze(constraints); |
michael@0 | 1366 | return false; |
michael@0 | 1367 | } |
michael@0 | 1368 | |
michael@0 | 1369 | bool |
michael@0 | 1370 | HeapTypeSetKey::knownSubset(CompilerConstraintList *constraints, const HeapTypeSetKey &other) |
michael@0 | 1371 | { |
michael@0 | 1372 | if (!maybeTypes() || maybeTypes()->empty()) { |
michael@0 | 1373 | freeze(constraints); |
michael@0 | 1374 | return true; |
michael@0 | 1375 | } |
michael@0 | 1376 | if (!other.maybeTypes() || !maybeTypes()->isSubset(other.maybeTypes())) |
michael@0 | 1377 | return false; |
michael@0 | 1378 | freeze(constraints); |
michael@0 | 1379 | return true; |
michael@0 | 1380 | } |
michael@0 | 1381 | |
michael@0 | 1382 | JSObject * |
michael@0 | 1383 | TemporaryTypeSet::getSingleton() |
michael@0 | 1384 | { |
michael@0 | 1385 | if (baseFlags() != 0 || baseObjectCount() != 1) |
michael@0 | 1386 | return nullptr; |
michael@0 | 1387 | |
michael@0 | 1388 | return getSingleObject(0); |
michael@0 | 1389 | } |
michael@0 | 1390 | |
michael@0 | 1391 | JSObject * |
michael@0 | 1392 | HeapTypeSetKey::singleton(CompilerConstraintList *constraints) |
michael@0 | 1393 | { |
michael@0 | 1394 | HeapTypeSet *types = maybeTypes(); |
michael@0 | 1395 | |
michael@0 | 1396 | if (!types || types->nonDataProperty() || types->baseFlags() != 0 || types->getObjectCount() != 1) |
michael@0 | 1397 | return nullptr; |
michael@0 | 1398 | |
michael@0 | 1399 | JSObject *obj = types->getSingleObject(0); |
michael@0 | 1400 | |
michael@0 | 1401 | if (obj) |
michael@0 | 1402 | freeze(constraints); |
michael@0 | 1403 | |
michael@0 | 1404 | return obj; |
michael@0 | 1405 | } |
michael@0 | 1406 | |
michael@0 | 1407 | bool |
michael@0 | 1408 | HeapTypeSetKey::needsBarrier(CompilerConstraintList *constraints) |
michael@0 | 1409 | { |
michael@0 | 1410 | TypeSet *types = maybeTypes(); |
michael@0 | 1411 | if (!types) |
michael@0 | 1412 | return false; |
michael@0 | 1413 | bool result = types->unknownObject() |
michael@0 | 1414 | || types->getObjectCount() > 0 |
michael@0 | 1415 | || types->hasAnyFlag(TYPE_FLAG_STRING); |
michael@0 | 1416 | if (!result) |
michael@0 | 1417 | freeze(constraints); |
michael@0 | 1418 | return result; |
michael@0 | 1419 | } |
michael@0 | 1420 | |
michael@0 | 1421 | namespace { |
michael@0 | 1422 | |
michael@0 | 1423 | // Constraint which triggers recompilation if an object acquires particular flags. |
michael@0 | 1424 | class ConstraintDataFreezeObjectFlags |
michael@0 | 1425 | { |
michael@0 | 1426 | public: |
michael@0 | 1427 | // Flags we are watching for on this object. |
michael@0 | 1428 | TypeObjectFlags flags; |
michael@0 | 1429 | |
michael@0 | 1430 | ConstraintDataFreezeObjectFlags(TypeObjectFlags flags) |
michael@0 | 1431 | : flags(flags) |
michael@0 | 1432 | { |
michael@0 | 1433 | JS_ASSERT(flags); |
michael@0 | 1434 | } |
michael@0 | 1435 | |
michael@0 | 1436 | const char *kind() { return "freezeObjectFlags"; } |
michael@0 | 1437 | |
michael@0 | 1438 | bool invalidateOnNewType(Type type) { return false; } |
michael@0 | 1439 | bool invalidateOnNewPropertyState(TypeSet *property) { return false; } |
michael@0 | 1440 | bool invalidateOnNewObjectState(TypeObject *object) { |
michael@0 | 1441 | return object->hasAnyFlags(flags); |
michael@0 | 1442 | } |
michael@0 | 1443 | |
michael@0 | 1444 | bool constraintHolds(JSContext *cx, |
michael@0 | 1445 | const HeapTypeSetKey &property, TemporaryTypeSet *expected) |
michael@0 | 1446 | { |
michael@0 | 1447 | return !invalidateOnNewObjectState(property.object()->maybeType()); |
michael@0 | 1448 | } |
michael@0 | 1449 | |
michael@0 | 1450 | bool shouldSweep() { return false; } |
michael@0 | 1451 | }; |
michael@0 | 1452 | |
michael@0 | 1453 | } /* anonymous namespace */ |
michael@0 | 1454 | |
michael@0 | 1455 | bool |
michael@0 | 1456 | TypeObjectKey::hasFlags(CompilerConstraintList *constraints, TypeObjectFlags flags) |
michael@0 | 1457 | { |
michael@0 | 1458 | JS_ASSERT(flags); |
michael@0 | 1459 | |
michael@0 | 1460 | if (TypeObject *type = maybeType()) { |
michael@0 | 1461 | if (type->hasAnyFlags(flags)) |
michael@0 | 1462 | return true; |
michael@0 | 1463 | } |
michael@0 | 1464 | |
michael@0 | 1465 | HeapTypeSetKey objectProperty = property(JSID_EMPTY); |
michael@0 | 1466 | LifoAlloc *alloc = constraints->alloc(); |
michael@0 | 1467 | |
michael@0 | 1468 | typedef CompilerConstraintInstance<ConstraintDataFreezeObjectFlags> T; |
michael@0 | 1469 | constraints->add(alloc->new_<T>(alloc, objectProperty, ConstraintDataFreezeObjectFlags(flags))); |
michael@0 | 1470 | return false; |
michael@0 | 1471 | } |
michael@0 | 1472 | |
michael@0 | 1473 | bool |
michael@0 | 1474 | TemporaryTypeSet::hasObjectFlags(CompilerConstraintList *constraints, TypeObjectFlags flags) |
michael@0 | 1475 | { |
michael@0 | 1476 | if (unknownObject()) |
michael@0 | 1477 | return true; |
michael@0 | 1478 | |
michael@0 | 1479 | /* |
michael@0 | 1480 | * Treat type sets containing no objects as having all object flags, |
michael@0 | 1481 | * to spare callers from having to check this. |
michael@0 | 1482 | */ |
michael@0 | 1483 | if (baseObjectCount() == 0) |
michael@0 | 1484 | return true; |
michael@0 | 1485 | |
michael@0 | 1486 | unsigned count = getObjectCount(); |
michael@0 | 1487 | for (unsigned i = 0; i < count; i++) { |
michael@0 | 1488 | TypeObjectKey *object = getObject(i); |
michael@0 | 1489 | if (object && object->hasFlags(constraints, flags)) |
michael@0 | 1490 | return true; |
michael@0 | 1491 | } |
michael@0 | 1492 | |
michael@0 | 1493 | return false; |
michael@0 | 1494 | } |
michael@0 | 1495 | |
michael@0 | 1496 | gc::InitialHeap |
michael@0 | 1497 | TypeObject::initialHeap(CompilerConstraintList *constraints) |
michael@0 | 1498 | { |
michael@0 | 1499 | // If this object is not required to be pretenured but could be in the |
michael@0 | 1500 | // future, add a constraint to trigger recompilation if the requirement |
michael@0 | 1501 | // changes. |
michael@0 | 1502 | |
michael@0 | 1503 | if (shouldPreTenure()) |
michael@0 | 1504 | return gc::TenuredHeap; |
michael@0 | 1505 | |
michael@0 | 1506 | if (!canPreTenure()) |
michael@0 | 1507 | return gc::DefaultHeap; |
michael@0 | 1508 | |
michael@0 | 1509 | HeapTypeSetKey objectProperty = TypeObjectKey::get(this)->property(JSID_EMPTY); |
michael@0 | 1510 | LifoAlloc *alloc = constraints->alloc(); |
michael@0 | 1511 | |
michael@0 | 1512 | typedef CompilerConstraintInstance<ConstraintDataFreezeObjectFlags> T; |
michael@0 | 1513 | constraints->add(alloc->new_<T>(alloc, objectProperty, ConstraintDataFreezeObjectFlags(OBJECT_FLAG_PRE_TENURE))); |
michael@0 | 1514 | |
michael@0 | 1515 | return gc::DefaultHeap; |
michael@0 | 1516 | } |
michael@0 | 1517 | |
michael@0 | 1518 | namespace { |
michael@0 | 1519 | |
michael@0 | 1520 | // Constraint which triggers recompilation on any type change in an inlined |
michael@0 | 1521 | // script. The freeze constraints added to stack type sets will only directly |
michael@0 | 1522 | // invalidate the script containing those stack type sets. To invalidate code |
michael@0 | 1523 | // for scripts into which the base script was inlined, ObjectStateChange is used. |
michael@0 | 1524 | class ConstraintDataFreezeObjectForInlinedCall |
michael@0 | 1525 | { |
michael@0 | 1526 | public: |
michael@0 | 1527 | ConstraintDataFreezeObjectForInlinedCall() |
michael@0 | 1528 | {} |
michael@0 | 1529 | |
michael@0 | 1530 | const char *kind() { return "freezeObjectForInlinedCall"; } |
michael@0 | 1531 | |
michael@0 | 1532 | bool invalidateOnNewType(Type type) { return false; } |
michael@0 | 1533 | bool invalidateOnNewPropertyState(TypeSet *property) { return false; } |
michael@0 | 1534 | bool invalidateOnNewObjectState(TypeObject *object) { |
michael@0 | 1535 | // We don't keep track of the exact dependencies the caller has on its |
michael@0 | 1536 | // inlined scripts' type sets, so always invalidate the caller. |
michael@0 | 1537 | return true; |
michael@0 | 1538 | } |
michael@0 | 1539 | |
michael@0 | 1540 | bool constraintHolds(JSContext *cx, |
michael@0 | 1541 | const HeapTypeSetKey &property, TemporaryTypeSet *expected) |
michael@0 | 1542 | { |
michael@0 | 1543 | return true; |
michael@0 | 1544 | } |
michael@0 | 1545 | |
michael@0 | 1546 | bool shouldSweep() { return false; } |
michael@0 | 1547 | }; |
michael@0 | 1548 | |
michael@0 | 1549 | // Constraint which triggers recompilation when the template object for a |
michael@0 | 1550 | // type's new script changes. |
michael@0 | 1551 | class ConstraintDataFreezeObjectForNewScriptTemplate |
michael@0 | 1552 | { |
michael@0 | 1553 | JSObject *templateObject; |
michael@0 | 1554 | |
michael@0 | 1555 | public: |
michael@0 | 1556 | ConstraintDataFreezeObjectForNewScriptTemplate(JSObject *templateObject) |
michael@0 | 1557 | : templateObject(templateObject) |
michael@0 | 1558 | {} |
michael@0 | 1559 | |
michael@0 | 1560 | const char *kind() { return "freezeObjectForNewScriptTemplate"; } |
michael@0 | 1561 | |
michael@0 | 1562 | bool invalidateOnNewType(Type type) { return false; } |
michael@0 | 1563 | bool invalidateOnNewPropertyState(TypeSet *property) { return false; } |
michael@0 | 1564 | bool invalidateOnNewObjectState(TypeObject *object) { |
michael@0 | 1565 | return !object->hasNewScript() || object->newScript()->templateObject != templateObject; |
michael@0 | 1566 | } |
michael@0 | 1567 | |
michael@0 | 1568 | bool constraintHolds(JSContext *cx, |
michael@0 | 1569 | const HeapTypeSetKey &property, TemporaryTypeSet *expected) |
michael@0 | 1570 | { |
michael@0 | 1571 | return !invalidateOnNewObjectState(property.object()->maybeType()); |
michael@0 | 1572 | } |
michael@0 | 1573 | |
michael@0 | 1574 | bool shouldSweep() { |
michael@0 | 1575 | // Note: |templateObject| is only used for equality testing. |
michael@0 | 1576 | return false; |
michael@0 | 1577 | } |
michael@0 | 1578 | }; |
michael@0 | 1579 | |
michael@0 | 1580 | // Constraint which triggers recompilation when a typed array's data becomes |
michael@0 | 1581 | // invalid. |
michael@0 | 1582 | class ConstraintDataFreezeObjectForTypedArrayData |
michael@0 | 1583 | { |
michael@0 | 1584 | void *viewData; |
michael@0 | 1585 | uint32_t length; |
michael@0 | 1586 | |
michael@0 | 1587 | public: |
michael@0 | 1588 | ConstraintDataFreezeObjectForTypedArrayData(TypedArrayObject &tarray) |
michael@0 | 1589 | : viewData(tarray.viewData()), |
michael@0 | 1590 | length(tarray.length()) |
michael@0 | 1591 | {} |
michael@0 | 1592 | |
michael@0 | 1593 | const char *kind() { return "freezeObjectForTypedArrayData"; } |
michael@0 | 1594 | |
michael@0 | 1595 | bool invalidateOnNewType(Type type) { return false; } |
michael@0 | 1596 | bool invalidateOnNewPropertyState(TypeSet *property) { return false; } |
michael@0 | 1597 | bool invalidateOnNewObjectState(TypeObject *object) { |
michael@0 | 1598 | TypedArrayObject &tarray = object->singleton()->as<TypedArrayObject>(); |
michael@0 | 1599 | return tarray.viewData() != viewData || tarray.length() != length; |
michael@0 | 1600 | } |
michael@0 | 1601 | |
michael@0 | 1602 | bool constraintHolds(JSContext *cx, |
michael@0 | 1603 | const HeapTypeSetKey &property, TemporaryTypeSet *expected) |
michael@0 | 1604 | { |
michael@0 | 1605 | return !invalidateOnNewObjectState(property.object()->maybeType()); |
michael@0 | 1606 | } |
michael@0 | 1607 | |
michael@0 | 1608 | bool shouldSweep() { |
michael@0 | 1609 | // Note: |viewData| is only used for equality testing. |
michael@0 | 1610 | return false; |
michael@0 | 1611 | } |
michael@0 | 1612 | }; |
michael@0 | 1613 | |
michael@0 | 1614 | } /* anonymous namespace */ |
michael@0 | 1615 | |
michael@0 | 1616 | void |
michael@0 | 1617 | TypeObjectKey::watchStateChangeForInlinedCall(CompilerConstraintList *constraints) |
michael@0 | 1618 | { |
michael@0 | 1619 | HeapTypeSetKey objectProperty = property(JSID_EMPTY); |
michael@0 | 1620 | LifoAlloc *alloc = constraints->alloc(); |
michael@0 | 1621 | |
michael@0 | 1622 | typedef CompilerConstraintInstance<ConstraintDataFreezeObjectForInlinedCall> T; |
michael@0 | 1623 | constraints->add(alloc->new_<T>(alloc, objectProperty, ConstraintDataFreezeObjectForInlinedCall())); |
michael@0 | 1624 | } |
michael@0 | 1625 | |
michael@0 | 1626 | void |
michael@0 | 1627 | TypeObjectKey::watchStateChangeForNewScriptTemplate(CompilerConstraintList *constraints) |
michael@0 | 1628 | { |
michael@0 | 1629 | JSObject *templateObject = asTypeObject()->newScript()->templateObject; |
michael@0 | 1630 | HeapTypeSetKey objectProperty = property(JSID_EMPTY); |
michael@0 | 1631 | LifoAlloc *alloc = constraints->alloc(); |
michael@0 | 1632 | |
michael@0 | 1633 | typedef CompilerConstraintInstance<ConstraintDataFreezeObjectForNewScriptTemplate> T; |
michael@0 | 1634 | constraints->add(alloc->new_<T>(alloc, objectProperty, |
michael@0 | 1635 | ConstraintDataFreezeObjectForNewScriptTemplate(templateObject))); |
michael@0 | 1636 | } |
michael@0 | 1637 | |
michael@0 | 1638 | void |
michael@0 | 1639 | TypeObjectKey::watchStateChangeForTypedArrayData(CompilerConstraintList *constraints) |
michael@0 | 1640 | { |
michael@0 | 1641 | TypedArrayObject &tarray = asSingleObject()->as<TypedArrayObject>(); |
michael@0 | 1642 | HeapTypeSetKey objectProperty = property(JSID_EMPTY); |
michael@0 | 1643 | LifoAlloc *alloc = constraints->alloc(); |
michael@0 | 1644 | |
michael@0 | 1645 | typedef CompilerConstraintInstance<ConstraintDataFreezeObjectForTypedArrayData> T; |
michael@0 | 1646 | constraints->add(alloc->new_<T>(alloc, objectProperty, |
michael@0 | 1647 | ConstraintDataFreezeObjectForTypedArrayData(tarray))); |
michael@0 | 1648 | } |
michael@0 | 1649 | |
michael@0 | 1650 | static void |
michael@0 | 1651 | ObjectStateChange(ExclusiveContext *cxArg, TypeObject *object, bool markingUnknown) |
michael@0 | 1652 | { |
michael@0 | 1653 | if (object->unknownProperties()) |
michael@0 | 1654 | return; |
michael@0 | 1655 | |
michael@0 | 1656 | /* All constraints listening to state changes are on the empty id. */ |
michael@0 | 1657 | HeapTypeSet *types = object->maybeGetProperty(JSID_EMPTY); |
michael@0 | 1658 | |
michael@0 | 1659 | /* Mark as unknown after getting the types, to avoid assertion. */ |
michael@0 | 1660 | if (markingUnknown) |
michael@0 | 1661 | object->addFlags(OBJECT_FLAG_DYNAMIC_MASK | OBJECT_FLAG_UNKNOWN_PROPERTIES); |
michael@0 | 1662 | |
michael@0 | 1663 | if (types) { |
michael@0 | 1664 | if (JSContext *cx = cxArg->maybeJSContext()) { |
michael@0 | 1665 | TypeConstraint *constraint = types->constraintList; |
michael@0 | 1666 | while (constraint) { |
michael@0 | 1667 | constraint->newObjectState(cx, object); |
michael@0 | 1668 | constraint = constraint->next; |
michael@0 | 1669 | } |
michael@0 | 1670 | } else { |
michael@0 | 1671 | JS_ASSERT(!types->constraintList); |
michael@0 | 1672 | } |
michael@0 | 1673 | } |
michael@0 | 1674 | } |
michael@0 | 1675 | |
michael@0 | 1676 | static void |
michael@0 | 1677 | CheckNewScriptProperties(JSContext *cx, TypeObject *type, JSFunction *fun); |
michael@0 | 1678 | |
michael@0 | 1679 | namespace { |
michael@0 | 1680 | |
michael@0 | 1681 | class ConstraintDataFreezePropertyState |
michael@0 | 1682 | { |
michael@0 | 1683 | public: |
michael@0 | 1684 | enum Which { |
michael@0 | 1685 | NON_DATA, |
michael@0 | 1686 | NON_WRITABLE |
michael@0 | 1687 | } which; |
michael@0 | 1688 | |
michael@0 | 1689 | ConstraintDataFreezePropertyState(Which which) |
michael@0 | 1690 | : which(which) |
michael@0 | 1691 | {} |
michael@0 | 1692 | |
michael@0 | 1693 | const char *kind() { return (which == NON_DATA) ? "freezeNonDataProperty" : "freezeNonWritableProperty"; } |
michael@0 | 1694 | |
michael@0 | 1695 | bool invalidateOnNewType(Type type) { return false; } |
michael@0 | 1696 | bool invalidateOnNewPropertyState(TypeSet *property) { |
michael@0 | 1697 | return (which == NON_DATA) |
michael@0 | 1698 | ? property->nonDataProperty() |
michael@0 | 1699 | : property->nonWritableProperty(); |
michael@0 | 1700 | } |
michael@0 | 1701 | bool invalidateOnNewObjectState(TypeObject *object) { return false; } |
michael@0 | 1702 | |
michael@0 | 1703 | bool constraintHolds(JSContext *cx, |
michael@0 | 1704 | const HeapTypeSetKey &property, TemporaryTypeSet *expected) |
michael@0 | 1705 | { |
michael@0 | 1706 | return !invalidateOnNewPropertyState(property.maybeTypes()); |
michael@0 | 1707 | } |
michael@0 | 1708 | |
michael@0 | 1709 | bool shouldSweep() { return false; } |
michael@0 | 1710 | }; |
michael@0 | 1711 | |
michael@0 | 1712 | } /* anonymous namespace */ |
michael@0 | 1713 | |
michael@0 | 1714 | bool |
michael@0 | 1715 | HeapTypeSetKey::nonData(CompilerConstraintList *constraints) |
michael@0 | 1716 | { |
michael@0 | 1717 | if (maybeTypes() && maybeTypes()->nonDataProperty()) |
michael@0 | 1718 | return true; |
michael@0 | 1719 | |
michael@0 | 1720 | LifoAlloc *alloc = constraints->alloc(); |
michael@0 | 1721 | |
michael@0 | 1722 | typedef CompilerConstraintInstance<ConstraintDataFreezePropertyState> T; |
michael@0 | 1723 | constraints->add(alloc->new_<T>(alloc, *this, |
michael@0 | 1724 | ConstraintDataFreezePropertyState(ConstraintDataFreezePropertyState::NON_DATA))); |
michael@0 | 1725 | return false; |
michael@0 | 1726 | } |
michael@0 | 1727 | |
michael@0 | 1728 | bool |
michael@0 | 1729 | HeapTypeSetKey::nonWritable(CompilerConstraintList *constraints) |
michael@0 | 1730 | { |
michael@0 | 1731 | if (maybeTypes() && maybeTypes()->nonWritableProperty()) |
michael@0 | 1732 | return true; |
michael@0 | 1733 | |
michael@0 | 1734 | LifoAlloc *alloc = constraints->alloc(); |
michael@0 | 1735 | |
michael@0 | 1736 | typedef CompilerConstraintInstance<ConstraintDataFreezePropertyState> T; |
michael@0 | 1737 | constraints->add(alloc->new_<T>(alloc, *this, |
michael@0 | 1738 | ConstraintDataFreezePropertyState(ConstraintDataFreezePropertyState::NON_WRITABLE))); |
michael@0 | 1739 | return false; |
michael@0 | 1740 | } |
michael@0 | 1741 | |
michael@0 | 1742 | bool |
michael@0 | 1743 | TemporaryTypeSet::filtersType(const TemporaryTypeSet *other, Type filteredType) const |
michael@0 | 1744 | { |
michael@0 | 1745 | if (other->unknown()) |
michael@0 | 1746 | return unknown(); |
michael@0 | 1747 | |
michael@0 | 1748 | for (TypeFlags flag = 1; flag < TYPE_FLAG_ANYOBJECT; flag <<= 1) { |
michael@0 | 1749 | Type type = Type::PrimitiveType(TypeFlagPrimitive(flag)); |
michael@0 | 1750 | if (type != filteredType && other->hasType(type) && !hasType(type)) |
michael@0 | 1751 | return false; |
michael@0 | 1752 | } |
michael@0 | 1753 | |
michael@0 | 1754 | if (other->unknownObject()) |
michael@0 | 1755 | return unknownObject(); |
michael@0 | 1756 | |
michael@0 | 1757 | for (size_t i = 0; i < other->getObjectCount(); i++) { |
michael@0 | 1758 | TypeObjectKey *key = other->getObject(i); |
michael@0 | 1759 | if (key) { |
michael@0 | 1760 | Type type = Type::ObjectType(key); |
michael@0 | 1761 | if (type != filteredType && !hasType(type)) |
michael@0 | 1762 | return false; |
michael@0 | 1763 | } |
michael@0 | 1764 | } |
michael@0 | 1765 | |
michael@0 | 1766 | return true; |
michael@0 | 1767 | } |
michael@0 | 1768 | |
michael@0 | 1769 | TemporaryTypeSet::DoubleConversion |
michael@0 | 1770 | TemporaryTypeSet::convertDoubleElements(CompilerConstraintList *constraints) |
michael@0 | 1771 | { |
michael@0 | 1772 | if (unknownObject() || !getObjectCount()) |
michael@0 | 1773 | return AmbiguousDoubleConversion; |
michael@0 | 1774 | |
michael@0 | 1775 | bool alwaysConvert = true; |
michael@0 | 1776 | bool maybeConvert = false; |
michael@0 | 1777 | bool dontConvert = false; |
michael@0 | 1778 | |
michael@0 | 1779 | for (unsigned i = 0; i < getObjectCount(); i++) { |
michael@0 | 1780 | TypeObjectKey *type = getObject(i); |
michael@0 | 1781 | if (!type) |
michael@0 | 1782 | continue; |
michael@0 | 1783 | |
michael@0 | 1784 | if (type->unknownProperties()) { |
michael@0 | 1785 | alwaysConvert = false; |
michael@0 | 1786 | continue; |
michael@0 | 1787 | } |
michael@0 | 1788 | |
michael@0 | 1789 | HeapTypeSetKey property = type->property(JSID_VOID); |
michael@0 | 1790 | property.freeze(constraints); |
michael@0 | 1791 | |
michael@0 | 1792 | // We can't convert to double elements for objects which do not have |
michael@0 | 1793 | // double in their element types (as the conversion may render the type |
michael@0 | 1794 | // information incorrect), nor for non-array objects (as their elements |
michael@0 | 1795 | // may point to emptyObjectElements, which cannot be converted). |
michael@0 | 1796 | if (!property.maybeTypes() || |
michael@0 | 1797 | !property.maybeTypes()->hasType(Type::DoubleType()) || |
michael@0 | 1798 | type->clasp() != &ArrayObject::class_) |
michael@0 | 1799 | { |
michael@0 | 1800 | dontConvert = true; |
michael@0 | 1801 | alwaysConvert = false; |
michael@0 | 1802 | continue; |
michael@0 | 1803 | } |
michael@0 | 1804 | |
michael@0 | 1805 | // Only bother with converting known packed arrays whose possible |
michael@0 | 1806 | // element types are int or double. Other arrays require type tests |
michael@0 | 1807 | // when elements are accessed regardless of the conversion. |
michael@0 | 1808 | if (property.knownMIRType(constraints) == jit::MIRType_Double && |
michael@0 | 1809 | !type->hasFlags(constraints, OBJECT_FLAG_NON_PACKED)) |
michael@0 | 1810 | { |
michael@0 | 1811 | maybeConvert = true; |
michael@0 | 1812 | } else { |
michael@0 | 1813 | alwaysConvert = false; |
michael@0 | 1814 | } |
michael@0 | 1815 | } |
michael@0 | 1816 | |
michael@0 | 1817 | JS_ASSERT_IF(alwaysConvert, maybeConvert); |
michael@0 | 1818 | |
michael@0 | 1819 | if (maybeConvert && dontConvert) |
michael@0 | 1820 | return AmbiguousDoubleConversion; |
michael@0 | 1821 | if (alwaysConvert) |
michael@0 | 1822 | return AlwaysConvertToDoubles; |
michael@0 | 1823 | if (maybeConvert) |
michael@0 | 1824 | return MaybeConvertToDoubles; |
michael@0 | 1825 | return DontConvertToDoubles; |
michael@0 | 1826 | } |
michael@0 | 1827 | |
michael@0 | 1828 | const Class * |
michael@0 | 1829 | TemporaryTypeSet::getKnownClass() |
michael@0 | 1830 | { |
michael@0 | 1831 | if (unknownObject()) |
michael@0 | 1832 | return nullptr; |
michael@0 | 1833 | |
michael@0 | 1834 | const Class *clasp = nullptr; |
michael@0 | 1835 | unsigned count = getObjectCount(); |
michael@0 | 1836 | |
michael@0 | 1837 | for (unsigned i = 0; i < count; i++) { |
michael@0 | 1838 | const Class *nclasp = getObjectClass(i); |
michael@0 | 1839 | if (!nclasp) |
michael@0 | 1840 | continue; |
michael@0 | 1841 | |
michael@0 | 1842 | if (clasp && clasp != nclasp) |
michael@0 | 1843 | return nullptr; |
michael@0 | 1844 | clasp = nclasp; |
michael@0 | 1845 | } |
michael@0 | 1846 | |
michael@0 | 1847 | return clasp; |
michael@0 | 1848 | } |
michael@0 | 1849 | |
michael@0 | 1850 | TemporaryTypeSet::ForAllResult |
michael@0 | 1851 | TemporaryTypeSet::forAllClasses(bool (*func)(const Class* clasp)) |
michael@0 | 1852 | { |
michael@0 | 1853 | if (unknownObject()) |
michael@0 | 1854 | return ForAllResult::MIXED; |
michael@0 | 1855 | |
michael@0 | 1856 | unsigned count = getObjectCount(); |
michael@0 | 1857 | if (count == 0) |
michael@0 | 1858 | return ForAllResult::EMPTY; |
michael@0 | 1859 | |
michael@0 | 1860 | bool true_results = false; |
michael@0 | 1861 | bool false_results = false; |
michael@0 | 1862 | for (unsigned i = 0; i < count; i++) { |
michael@0 | 1863 | const Class *clasp = getObjectClass(i); |
michael@0 | 1864 | if (!clasp) |
michael@0 | 1865 | return ForAllResult::MIXED; |
michael@0 | 1866 | if (func(clasp)) { |
michael@0 | 1867 | true_results = true; |
michael@0 | 1868 | if (false_results) return ForAllResult::MIXED; |
michael@0 | 1869 | } |
michael@0 | 1870 | else { |
michael@0 | 1871 | false_results = true; |
michael@0 | 1872 | if (true_results) return ForAllResult::MIXED; |
michael@0 | 1873 | } |
michael@0 | 1874 | } |
michael@0 | 1875 | |
michael@0 | 1876 | JS_ASSERT(true_results != false_results); |
michael@0 | 1877 | |
michael@0 | 1878 | return true_results ? ForAllResult::ALL_TRUE : ForAllResult::ALL_FALSE; |
michael@0 | 1879 | } |
michael@0 | 1880 | |
michael@0 | 1881 | int |
michael@0 | 1882 | TemporaryTypeSet::getTypedArrayType() |
michael@0 | 1883 | { |
michael@0 | 1884 | const Class *clasp = getKnownClass(); |
michael@0 | 1885 | |
michael@0 | 1886 | if (clasp && IsTypedArrayClass(clasp)) |
michael@0 | 1887 | return clasp - &TypedArrayObject::classes[0]; |
michael@0 | 1888 | return ScalarTypeDescr::TYPE_MAX; |
michael@0 | 1889 | } |
michael@0 | 1890 | |
michael@0 | 1891 | bool |
michael@0 | 1892 | TemporaryTypeSet::isDOMClass() |
michael@0 | 1893 | { |
michael@0 | 1894 | if (unknownObject()) |
michael@0 | 1895 | return false; |
michael@0 | 1896 | |
michael@0 | 1897 | unsigned count = getObjectCount(); |
michael@0 | 1898 | for (unsigned i = 0; i < count; i++) { |
michael@0 | 1899 | const Class *clasp = getObjectClass(i); |
michael@0 | 1900 | if (clasp && !clasp->isDOMClass()) |
michael@0 | 1901 | return false; |
michael@0 | 1902 | } |
michael@0 | 1903 | |
michael@0 | 1904 | return count > 0; |
michael@0 | 1905 | } |
michael@0 | 1906 | |
michael@0 | 1907 | bool |
michael@0 | 1908 | TemporaryTypeSet::maybeCallable() |
michael@0 | 1909 | { |
michael@0 | 1910 | if (!maybeObject()) |
michael@0 | 1911 | return false; |
michael@0 | 1912 | |
michael@0 | 1913 | if (unknownObject()) |
michael@0 | 1914 | return true; |
michael@0 | 1915 | |
michael@0 | 1916 | unsigned count = getObjectCount(); |
michael@0 | 1917 | for (unsigned i = 0; i < count; i++) { |
michael@0 | 1918 | const Class *clasp = getObjectClass(i); |
michael@0 | 1919 | if (clasp && clasp->isCallable()) |
michael@0 | 1920 | return true; |
michael@0 | 1921 | } |
michael@0 | 1922 | |
michael@0 | 1923 | return false; |
michael@0 | 1924 | } |
michael@0 | 1925 | |
michael@0 | 1926 | bool |
michael@0 | 1927 | TemporaryTypeSet::maybeEmulatesUndefined() |
michael@0 | 1928 | { |
michael@0 | 1929 | if (!maybeObject()) |
michael@0 | 1930 | return false; |
michael@0 | 1931 | |
michael@0 | 1932 | if (unknownObject()) |
michael@0 | 1933 | return true; |
michael@0 | 1934 | |
michael@0 | 1935 | unsigned count = getObjectCount(); |
michael@0 | 1936 | for (unsigned i = 0; i < count; i++) { |
michael@0 | 1937 | // The object emulates undefined if clasp->emulatesUndefined() or if |
michael@0 | 1938 | // it's a WrapperObject, see EmulatesUndefined. Since all wrappers are |
michael@0 | 1939 | // proxies, we can just check for that. |
michael@0 | 1940 | const Class *clasp = getObjectClass(i); |
michael@0 | 1941 | if (clasp && (clasp->emulatesUndefined() || clasp->isProxy())) |
michael@0 | 1942 | return true; |
michael@0 | 1943 | } |
michael@0 | 1944 | |
michael@0 | 1945 | return false; |
michael@0 | 1946 | } |
michael@0 | 1947 | |
michael@0 | 1948 | JSObject * |
michael@0 | 1949 | TemporaryTypeSet::getCommonPrototype() |
michael@0 | 1950 | { |
michael@0 | 1951 | if (unknownObject()) |
michael@0 | 1952 | return nullptr; |
michael@0 | 1953 | |
michael@0 | 1954 | JSObject *proto = nullptr; |
michael@0 | 1955 | unsigned count = getObjectCount(); |
michael@0 | 1956 | |
michael@0 | 1957 | for (unsigned i = 0; i < count; i++) { |
michael@0 | 1958 | TypeObjectKey *object = getObject(i); |
michael@0 | 1959 | if (!object) |
michael@0 | 1960 | continue; |
michael@0 | 1961 | |
michael@0 | 1962 | if (!object->hasTenuredProto()) |
michael@0 | 1963 | return nullptr; |
michael@0 | 1964 | |
michael@0 | 1965 | TaggedProto nproto = object->proto(); |
michael@0 | 1966 | if (proto) { |
michael@0 | 1967 | if (nproto != proto) |
michael@0 | 1968 | return nullptr; |
michael@0 | 1969 | } else { |
michael@0 | 1970 | if (!nproto.isObject()) |
michael@0 | 1971 | return nullptr; |
michael@0 | 1972 | proto = nproto.toObject(); |
michael@0 | 1973 | } |
michael@0 | 1974 | } |
michael@0 | 1975 | |
michael@0 | 1976 | return proto; |
michael@0 | 1977 | } |
michael@0 | 1978 | |
michael@0 | 1979 | bool |
michael@0 | 1980 | TemporaryTypeSet::propertyNeedsBarrier(CompilerConstraintList *constraints, jsid id) |
michael@0 | 1981 | { |
michael@0 | 1982 | if (unknownObject()) |
michael@0 | 1983 | return true; |
michael@0 | 1984 | |
michael@0 | 1985 | for (unsigned i = 0; i < getObjectCount(); i++) { |
michael@0 | 1986 | TypeObjectKey *type = getObject(i); |
michael@0 | 1987 | if (!type) |
michael@0 | 1988 | continue; |
michael@0 | 1989 | |
michael@0 | 1990 | if (type->unknownProperties()) |
michael@0 | 1991 | return true; |
michael@0 | 1992 | |
michael@0 | 1993 | HeapTypeSetKey property = type->property(id); |
michael@0 | 1994 | if (property.needsBarrier(constraints)) |
michael@0 | 1995 | return true; |
michael@0 | 1996 | } |
michael@0 | 1997 | |
michael@0 | 1998 | return false; |
michael@0 | 1999 | } |
michael@0 | 2000 | |
michael@0 | 2001 | ///////////////////////////////////////////////////////////////////// |
michael@0 | 2002 | // TypeCompartment |
michael@0 | 2003 | ///////////////////////////////////////////////////////////////////// |
michael@0 | 2004 | |
michael@0 | 2005 | TypeCompartment::TypeCompartment() |
michael@0 | 2006 | { |
michael@0 | 2007 | PodZero(this); |
michael@0 | 2008 | } |
michael@0 | 2009 | |
michael@0 | 2010 | TypeObject * |
michael@0 | 2011 | TypeCompartment::newTypeObject(ExclusiveContext *cx, const Class *clasp, Handle<TaggedProto> proto, |
michael@0 | 2012 | TypeObjectFlags initialFlags) |
michael@0 | 2013 | { |
michael@0 | 2014 | JS_ASSERT_IF(proto.isObject(), cx->isInsideCurrentCompartment(proto.toObject())); |
michael@0 | 2015 | |
michael@0 | 2016 | if (cx->isJSContext()) { |
michael@0 | 2017 | if (proto.isObject() && IsInsideNursery(cx->asJSContext()->runtime(), proto.toObject())) |
michael@0 | 2018 | initialFlags |= OBJECT_FLAG_NURSERY_PROTO; |
michael@0 | 2019 | } |
michael@0 | 2020 | |
michael@0 | 2021 | TypeObject *object = js::NewTypeObject(cx); |
michael@0 | 2022 | if (!object) |
michael@0 | 2023 | return nullptr; |
michael@0 | 2024 | new(object) TypeObject(clasp, proto, initialFlags); |
michael@0 | 2025 | |
michael@0 | 2026 | return object; |
michael@0 | 2027 | } |
michael@0 | 2028 | |
michael@0 | 2029 | TypeObject * |
michael@0 | 2030 | TypeCompartment::addAllocationSiteTypeObject(JSContext *cx, AllocationSiteKey key) |
michael@0 | 2031 | { |
michael@0 | 2032 | AutoEnterAnalysis enter(cx); |
michael@0 | 2033 | |
michael@0 | 2034 | if (!allocationSiteTable) { |
michael@0 | 2035 | allocationSiteTable = cx->new_<AllocationSiteTable>(); |
michael@0 | 2036 | if (!allocationSiteTable || !allocationSiteTable->init()) { |
michael@0 | 2037 | js_delete(allocationSiteTable); |
michael@0 | 2038 | return nullptr; |
michael@0 | 2039 | } |
michael@0 | 2040 | } |
michael@0 | 2041 | |
michael@0 | 2042 | AllocationSiteTable::AddPtr p = allocationSiteTable->lookupForAdd(key); |
michael@0 | 2043 | JS_ASSERT(!p); |
michael@0 | 2044 | |
michael@0 | 2045 | TypeObject *res = nullptr; |
michael@0 | 2046 | |
michael@0 | 2047 | jsbytecode *pc = key.script->offsetToPC(key.offset); |
michael@0 | 2048 | RootedScript keyScript(cx, key.script); |
michael@0 | 2049 | |
michael@0 | 2050 | if (!res) { |
michael@0 | 2051 | RootedObject proto(cx); |
michael@0 | 2052 | if (!GetBuiltinPrototype(cx, key.kind, &proto)) |
michael@0 | 2053 | return nullptr; |
michael@0 | 2054 | |
michael@0 | 2055 | Rooted<TaggedProto> tagged(cx, TaggedProto(proto)); |
michael@0 | 2056 | res = newTypeObject(cx, GetClassForProtoKey(key.kind), tagged, OBJECT_FLAG_FROM_ALLOCATION_SITE); |
michael@0 | 2057 | if (!res) |
michael@0 | 2058 | return nullptr; |
michael@0 | 2059 | key.script = keyScript; |
michael@0 | 2060 | } |
michael@0 | 2061 | |
michael@0 | 2062 | if (JSOp(*pc) == JSOP_NEWOBJECT) { |
michael@0 | 2063 | /* |
michael@0 | 2064 | * This object is always constructed the same way and will not be |
michael@0 | 2065 | * observed by other code before all properties have been added. Mark |
michael@0 | 2066 | * all the properties as definite properties of the object. |
michael@0 | 2067 | */ |
michael@0 | 2068 | RootedObject baseobj(cx, key.script->getObject(GET_UINT32_INDEX(pc))); |
michael@0 | 2069 | |
michael@0 | 2070 | if (!res->addDefiniteProperties(cx, baseobj)) |
michael@0 | 2071 | return nullptr; |
michael@0 | 2072 | } |
michael@0 | 2073 | |
michael@0 | 2074 | if (!allocationSiteTable->add(p, key, res)) |
michael@0 | 2075 | return nullptr; |
michael@0 | 2076 | |
michael@0 | 2077 | return res; |
michael@0 | 2078 | } |
michael@0 | 2079 | |
michael@0 | 2080 | static inline jsid |
michael@0 | 2081 | GetAtomId(JSContext *cx, JSScript *script, const jsbytecode *pc, unsigned offset) |
michael@0 | 2082 | { |
michael@0 | 2083 | PropertyName *name = script->getName(GET_UINT32_INDEX(pc + offset)); |
michael@0 | 2084 | return IdToTypeId(NameToId(name)); |
michael@0 | 2085 | } |
michael@0 | 2086 | |
michael@0 | 2087 | bool |
michael@0 | 2088 | types::UseNewType(JSContext *cx, JSScript *script, jsbytecode *pc) |
michael@0 | 2089 | { |
michael@0 | 2090 | /* |
michael@0 | 2091 | * Make a heuristic guess at a use of JSOP_NEW that the constructed object |
michael@0 | 2092 | * should have a fresh type object. We do this when the NEW is immediately |
michael@0 | 2093 | * followed by a simple assignment to an object's .prototype field. |
michael@0 | 2094 | * This is designed to catch common patterns for subclassing in JS: |
michael@0 | 2095 | * |
michael@0 | 2096 | * function Super() { ... } |
michael@0 | 2097 | * function Sub1() { ... } |
michael@0 | 2098 | * function Sub2() { ... } |
michael@0 | 2099 | * |
michael@0 | 2100 | * Sub1.prototype = new Super(); |
michael@0 | 2101 | * Sub2.prototype = new Super(); |
michael@0 | 2102 | * |
michael@0 | 2103 | * Using distinct type objects for the particular prototypes of Sub1 and |
michael@0 | 2104 | * Sub2 lets us continue to distinguish the two subclasses and any extra |
michael@0 | 2105 | * properties added to those prototype objects. |
michael@0 | 2106 | */ |
michael@0 | 2107 | if (JSOp(*pc) == JSOP_NEW) |
michael@0 | 2108 | pc += JSOP_NEW_LENGTH; |
michael@0 | 2109 | else if (JSOp(*pc) == JSOP_SPREADNEW) |
michael@0 | 2110 | pc += JSOP_SPREADNEW_LENGTH; |
michael@0 | 2111 | else |
michael@0 | 2112 | return false; |
michael@0 | 2113 | if (JSOp(*pc) == JSOP_SETPROP) { |
michael@0 | 2114 | jsid id = GetAtomId(cx, script, pc, 0); |
michael@0 | 2115 | if (id == id_prototype(cx)) |
michael@0 | 2116 | return true; |
michael@0 | 2117 | } |
michael@0 | 2118 | |
michael@0 | 2119 | return false; |
michael@0 | 2120 | } |
michael@0 | 2121 | |
michael@0 | 2122 | NewObjectKind |
michael@0 | 2123 | types::UseNewTypeForInitializer(JSScript *script, jsbytecode *pc, JSProtoKey key) |
michael@0 | 2124 | { |
michael@0 | 2125 | /* |
michael@0 | 2126 | * Objects created outside loops in global and eval scripts should have |
michael@0 | 2127 | * singleton types. For now this is only done for plain objects and typed |
michael@0 | 2128 | * arrays, but not normal arrays. |
michael@0 | 2129 | */ |
michael@0 | 2130 | |
michael@0 | 2131 | if (script->functionNonDelazifying() && !script->treatAsRunOnce()) |
michael@0 | 2132 | return GenericObject; |
michael@0 | 2133 | |
michael@0 | 2134 | if (key != JSProto_Object && !(key >= JSProto_Int8Array && key <= JSProto_Uint8ClampedArray)) |
michael@0 | 2135 | return GenericObject; |
michael@0 | 2136 | |
michael@0 | 2137 | /* |
michael@0 | 2138 | * All loops in the script will have a JSTRY_ITER or JSTRY_LOOP try note |
michael@0 | 2139 | * indicating their boundary. |
michael@0 | 2140 | */ |
michael@0 | 2141 | |
michael@0 | 2142 | if (!script->hasTrynotes()) |
michael@0 | 2143 | return SingletonObject; |
michael@0 | 2144 | |
michael@0 | 2145 | unsigned offset = script->pcToOffset(pc); |
michael@0 | 2146 | |
michael@0 | 2147 | JSTryNote *tn = script->trynotes()->vector; |
michael@0 | 2148 | JSTryNote *tnlimit = tn + script->trynotes()->length; |
michael@0 | 2149 | for (; tn < tnlimit; tn++) { |
michael@0 | 2150 | if (tn->kind != JSTRY_ITER && tn->kind != JSTRY_LOOP) |
michael@0 | 2151 | continue; |
michael@0 | 2152 | |
michael@0 | 2153 | unsigned startOffset = script->mainOffset() + tn->start; |
michael@0 | 2154 | unsigned endOffset = startOffset + tn->length; |
michael@0 | 2155 | |
michael@0 | 2156 | if (offset >= startOffset && offset < endOffset) |
michael@0 | 2157 | return GenericObject; |
michael@0 | 2158 | } |
michael@0 | 2159 | |
michael@0 | 2160 | return SingletonObject; |
michael@0 | 2161 | } |
michael@0 | 2162 | |
michael@0 | 2163 | NewObjectKind |
michael@0 | 2164 | types::UseNewTypeForInitializer(JSScript *script, jsbytecode *pc, const Class *clasp) |
michael@0 | 2165 | { |
michael@0 | 2166 | return UseNewTypeForInitializer(script, pc, JSCLASS_CACHED_PROTO_KEY(clasp)); |
michael@0 | 2167 | } |
michael@0 | 2168 | |
michael@0 | 2169 | static inline bool |
michael@0 | 2170 | ClassCanHaveExtraProperties(const Class *clasp) |
michael@0 | 2171 | { |
michael@0 | 2172 | JS_ASSERT(clasp->resolve); |
michael@0 | 2173 | return clasp->resolve != JS_ResolveStub |
michael@0 | 2174 | || clasp->ops.lookupGeneric |
michael@0 | 2175 | || clasp->ops.getGeneric |
michael@0 | 2176 | || IsTypedArrayClass(clasp); |
michael@0 | 2177 | } |
michael@0 | 2178 | |
michael@0 | 2179 | static inline bool |
michael@0 | 2180 | PrototypeHasIndexedProperty(CompilerConstraintList *constraints, JSObject *obj) |
michael@0 | 2181 | { |
michael@0 | 2182 | do { |
michael@0 | 2183 | TypeObjectKey *type = TypeObjectKey::get(obj); |
michael@0 | 2184 | if (ClassCanHaveExtraProperties(type->clasp())) |
michael@0 | 2185 | return true; |
michael@0 | 2186 | if (type->unknownProperties()) |
michael@0 | 2187 | return true; |
michael@0 | 2188 | HeapTypeSetKey index = type->property(JSID_VOID); |
michael@0 | 2189 | if (index.nonData(constraints) || index.isOwnProperty(constraints)) |
michael@0 | 2190 | return true; |
michael@0 | 2191 | if (!obj->hasTenuredProto()) |
michael@0 | 2192 | return true; |
michael@0 | 2193 | obj = obj->getProto(); |
michael@0 | 2194 | } while (obj); |
michael@0 | 2195 | |
michael@0 | 2196 | return false; |
michael@0 | 2197 | } |
michael@0 | 2198 | |
michael@0 | 2199 | bool |
michael@0 | 2200 | types::ArrayPrototypeHasIndexedProperty(CompilerConstraintList *constraints, JSScript *script) |
michael@0 | 2201 | { |
michael@0 | 2202 | if (JSObject *proto = script->global().maybeGetArrayPrototype()) |
michael@0 | 2203 | return PrototypeHasIndexedProperty(constraints, proto); |
michael@0 | 2204 | return true; |
michael@0 | 2205 | } |
michael@0 | 2206 | |
michael@0 | 2207 | bool |
michael@0 | 2208 | types::TypeCanHaveExtraIndexedProperties(CompilerConstraintList *constraints, |
michael@0 | 2209 | TemporaryTypeSet *types) |
michael@0 | 2210 | { |
michael@0 | 2211 | const Class *clasp = types->getKnownClass(); |
michael@0 | 2212 | |
michael@0 | 2213 | // Note: typed arrays have indexed properties not accounted for by type |
michael@0 | 2214 | // information, though these are all in bounds and will be accounted for |
michael@0 | 2215 | // by JIT paths. |
michael@0 | 2216 | if (!clasp || (ClassCanHaveExtraProperties(clasp) && !IsTypedArrayClass(clasp))) |
michael@0 | 2217 | return true; |
michael@0 | 2218 | |
michael@0 | 2219 | if (types->hasObjectFlags(constraints, types::OBJECT_FLAG_SPARSE_INDEXES)) |
michael@0 | 2220 | return true; |
michael@0 | 2221 | |
michael@0 | 2222 | JSObject *proto = types->getCommonPrototype(); |
michael@0 | 2223 | if (!proto) |
michael@0 | 2224 | return true; |
michael@0 | 2225 | |
michael@0 | 2226 | return PrototypeHasIndexedProperty(constraints, proto); |
michael@0 | 2227 | } |
michael@0 | 2228 | |
michael@0 | 2229 | void |
michael@0 | 2230 | TypeZone::processPendingRecompiles(FreeOp *fop) |
michael@0 | 2231 | { |
michael@0 | 2232 | if (!pendingRecompiles) |
michael@0 | 2233 | return; |
michael@0 | 2234 | |
michael@0 | 2235 | /* Steal the list of scripts to recompile, else we will try to recursively recompile them. */ |
michael@0 | 2236 | Vector<RecompileInfo> *pending = pendingRecompiles; |
michael@0 | 2237 | pendingRecompiles = nullptr; |
michael@0 | 2238 | |
michael@0 | 2239 | JS_ASSERT(!pending->empty()); |
michael@0 | 2240 | |
michael@0 | 2241 | #ifdef JS_ION |
michael@0 | 2242 | jit::Invalidate(*this, fop, *pending); |
michael@0 | 2243 | #endif |
michael@0 | 2244 | |
michael@0 | 2245 | fop->delete_(pending); |
michael@0 | 2246 | } |
michael@0 | 2247 | |
michael@0 | 2248 | void |
michael@0 | 2249 | TypeZone::addPendingRecompile(JSContext *cx, const RecompileInfo &info) |
michael@0 | 2250 | { |
michael@0 | 2251 | CompilerOutput *co = info.compilerOutput(cx); |
michael@0 | 2252 | if (!co || !co->isValid() || co->pendingInvalidation()) |
michael@0 | 2253 | return; |
michael@0 | 2254 | |
michael@0 | 2255 | InferSpew(ISpewOps, "addPendingRecompile: %p:%s:%d", |
michael@0 | 2256 | co->script(), co->script()->filename(), co->script()->lineno()); |
michael@0 | 2257 | |
michael@0 | 2258 | co->setPendingInvalidation(); |
michael@0 | 2259 | |
michael@0 | 2260 | if (!pendingRecompiles) { |
michael@0 | 2261 | pendingRecompiles = cx->new_< Vector<RecompileInfo> >(cx); |
michael@0 | 2262 | if (!pendingRecompiles) |
michael@0 | 2263 | CrashAtUnhandlableOOM("Could not update pendingRecompiles"); |
michael@0 | 2264 | } |
michael@0 | 2265 | |
michael@0 | 2266 | if (!pendingRecompiles->append(info)) |
michael@0 | 2267 | CrashAtUnhandlableOOM("Could not update pendingRecompiles"); |
michael@0 | 2268 | } |
michael@0 | 2269 | |
michael@0 | 2270 | void |
michael@0 | 2271 | TypeZone::addPendingRecompile(JSContext *cx, JSScript *script) |
michael@0 | 2272 | { |
michael@0 | 2273 | JS_ASSERT(script); |
michael@0 | 2274 | |
michael@0 | 2275 | #ifdef JS_ION |
michael@0 | 2276 | CancelOffThreadIonCompile(cx->compartment(), script); |
michael@0 | 2277 | |
michael@0 | 2278 | // Let the script warm up again before attempting another compile. |
michael@0 | 2279 | if (jit::IsBaselineEnabled(cx)) |
michael@0 | 2280 | script->resetUseCount(); |
michael@0 | 2281 | |
michael@0 | 2282 | if (script->hasIonScript()) |
michael@0 | 2283 | addPendingRecompile(cx, script->ionScript()->recompileInfo()); |
michael@0 | 2284 | |
michael@0 | 2285 | if (script->hasParallelIonScript()) |
michael@0 | 2286 | addPendingRecompile(cx, script->parallelIonScript()->recompileInfo()); |
michael@0 | 2287 | #endif |
michael@0 | 2288 | |
michael@0 | 2289 | // When one script is inlined into another the caller listens to state |
michael@0 | 2290 | // changes on the callee's script, so trigger these to force recompilation |
michael@0 | 2291 | // of any such callers. |
michael@0 | 2292 | if (script->functionNonDelazifying() && !script->functionNonDelazifying()->hasLazyType()) |
michael@0 | 2293 | ObjectStateChange(cx, script->functionNonDelazifying()->type(), false); |
michael@0 | 2294 | } |
michael@0 | 2295 | |
michael@0 | 2296 | void |
michael@0 | 2297 | TypeCompartment::markSetsUnknown(JSContext *cx, TypeObject *target) |
michael@0 | 2298 | { |
michael@0 | 2299 | JS_ASSERT(this == &cx->compartment()->types); |
michael@0 | 2300 | JS_ASSERT(!(target->flags() & OBJECT_FLAG_SETS_MARKED_UNKNOWN)); |
michael@0 | 2301 | JS_ASSERT(!target->singleton()); |
michael@0 | 2302 | JS_ASSERT(target->unknownProperties()); |
michael@0 | 2303 | |
michael@0 | 2304 | AutoEnterAnalysis enter(cx); |
michael@0 | 2305 | |
michael@0 | 2306 | /* Mark type sets which contain obj as having a generic object types. */ |
michael@0 | 2307 | |
michael@0 | 2308 | for (gc::CellIter i(cx->zone(), gc::FINALIZE_TYPE_OBJECT); !i.done(); i.next()) { |
michael@0 | 2309 | TypeObject *object = i.get<TypeObject>(); |
michael@0 | 2310 | unsigned count = object->getPropertyCount(); |
michael@0 | 2311 | for (unsigned i = 0; i < count; i++) { |
michael@0 | 2312 | Property *prop = object->getProperty(i); |
michael@0 | 2313 | if (prop && prop->types.hasType(Type::ObjectType(target))) |
michael@0 | 2314 | prop->types.addType(cx, Type::AnyObjectType()); |
michael@0 | 2315 | } |
michael@0 | 2316 | } |
michael@0 | 2317 | |
michael@0 | 2318 | for (gc::CellIter i(cx->zone(), gc::FINALIZE_SCRIPT); !i.done(); i.next()) { |
michael@0 | 2319 | RootedScript script(cx, i.get<JSScript>()); |
michael@0 | 2320 | if (script->types) { |
michael@0 | 2321 | unsigned count = TypeScript::NumTypeSets(script); |
michael@0 | 2322 | StackTypeSet *typeArray = script->types->typeArray(); |
michael@0 | 2323 | for (unsigned i = 0; i < count; i++) { |
michael@0 | 2324 | if (typeArray[i].hasType(Type::ObjectType(target))) |
michael@0 | 2325 | typeArray[i].addType(cx, Type::AnyObjectType()); |
michael@0 | 2326 | } |
michael@0 | 2327 | } |
michael@0 | 2328 | } |
michael@0 | 2329 | |
michael@0 | 2330 | target->addFlags(OBJECT_FLAG_SETS_MARKED_UNKNOWN); |
michael@0 | 2331 | } |
michael@0 | 2332 | |
michael@0 | 2333 | void |
michael@0 | 2334 | TypeCompartment::print(JSContext *cx, bool force) |
michael@0 | 2335 | { |
michael@0 | 2336 | #ifdef DEBUG |
michael@0 | 2337 | gc::AutoSuppressGC suppressGC(cx); |
michael@0 | 2338 | |
michael@0 | 2339 | JSCompartment *compartment = this->compartment(); |
michael@0 | 2340 | AutoEnterAnalysis enter(nullptr, compartment); |
michael@0 | 2341 | |
michael@0 | 2342 | if (!force && !InferSpewActive(ISpewResult)) |
michael@0 | 2343 | return; |
michael@0 | 2344 | |
michael@0 | 2345 | for (gc::CellIter i(compartment->zone(), gc::FINALIZE_SCRIPT); !i.done(); i.next()) { |
michael@0 | 2346 | // Note: use cx->runtime() instead of cx to work around IsInRequest(cx) |
michael@0 | 2347 | // assertion failures when we're called from DestroyContext. |
michael@0 | 2348 | RootedScript script(cx->runtime(), i.get<JSScript>()); |
michael@0 | 2349 | if (script->types) |
michael@0 | 2350 | script->types->printTypes(cx, script); |
michael@0 | 2351 | } |
michael@0 | 2352 | |
michael@0 | 2353 | for (gc::CellIter i(compartment->zone(), gc::FINALIZE_TYPE_OBJECT); !i.done(); i.next()) { |
michael@0 | 2354 | TypeObject *object = i.get<TypeObject>(); |
michael@0 | 2355 | object->print(); |
michael@0 | 2356 | } |
michael@0 | 2357 | #endif |
michael@0 | 2358 | } |
michael@0 | 2359 | |
michael@0 | 2360 | ///////////////////////////////////////////////////////////////////// |
michael@0 | 2361 | // TypeCompartment tables |
michael@0 | 2362 | ///////////////////////////////////////////////////////////////////// |
michael@0 | 2363 | |
michael@0 | 2364 | /* |
michael@0 | 2365 | * The arrayTypeTable and objectTypeTable are per-compartment tables for making |
michael@0 | 2366 | * common type objects to model the contents of large script singletons and |
michael@0 | 2367 | * JSON objects. These are vanilla Arrays and native Objects, so we distinguish |
michael@0 | 2368 | * the types of different ones by looking at the types of their properties. |
michael@0 | 2369 | * |
michael@0 | 2370 | * All singleton/JSON arrays which have the same prototype, are homogenous and |
michael@0 | 2371 | * of the same element type will share a type object. All singleton/JSON |
michael@0 | 2372 | * objects which have the same shape and property types will also share a type |
michael@0 | 2373 | * object. We don't try to collate arrays or objects that have type mismatches. |
michael@0 | 2374 | */ |
michael@0 | 2375 | |
michael@0 | 2376 | static inline bool |
michael@0 | 2377 | NumberTypes(Type a, Type b) |
michael@0 | 2378 | { |
michael@0 | 2379 | return (a.isPrimitive(JSVAL_TYPE_INT32) || a.isPrimitive(JSVAL_TYPE_DOUBLE)) |
michael@0 | 2380 | && (b.isPrimitive(JSVAL_TYPE_INT32) || b.isPrimitive(JSVAL_TYPE_DOUBLE)); |
michael@0 | 2381 | } |
michael@0 | 2382 | |
michael@0 | 2383 | /* |
michael@0 | 2384 | * As for GetValueType, but requires object types to be non-singletons with |
michael@0 | 2385 | * their default prototype. These are the only values that should appear in |
michael@0 | 2386 | * arrays and objects whose type can be fixed. |
michael@0 | 2387 | */ |
michael@0 | 2388 | static inline Type |
michael@0 | 2389 | GetValueTypeForTable(const Value &v) |
michael@0 | 2390 | { |
michael@0 | 2391 | Type type = GetValueType(v); |
michael@0 | 2392 | JS_ASSERT(!type.isSingleObject()); |
michael@0 | 2393 | return type; |
michael@0 | 2394 | } |
michael@0 | 2395 | |
michael@0 | 2396 | struct types::ArrayTableKey : public DefaultHasher<types::ArrayTableKey> |
michael@0 | 2397 | { |
michael@0 | 2398 | Type type; |
michael@0 | 2399 | JSObject *proto; |
michael@0 | 2400 | |
michael@0 | 2401 | ArrayTableKey() |
michael@0 | 2402 | : type(Type::UndefinedType()), proto(nullptr) |
michael@0 | 2403 | {} |
michael@0 | 2404 | |
michael@0 | 2405 | ArrayTableKey(Type type, JSObject *proto) |
michael@0 | 2406 | : type(type), proto(proto) |
michael@0 | 2407 | {} |
michael@0 | 2408 | |
michael@0 | 2409 | static inline uint32_t hash(const ArrayTableKey &v) { |
michael@0 | 2410 | return (uint32_t) (v.type.raw() ^ ((uint32_t)(size_t)v.proto >> 2)); |
michael@0 | 2411 | } |
michael@0 | 2412 | |
michael@0 | 2413 | static inline bool match(const ArrayTableKey &v1, const ArrayTableKey &v2) { |
michael@0 | 2414 | return v1.type == v2.type && v1.proto == v2.proto; |
michael@0 | 2415 | } |
michael@0 | 2416 | }; |
michael@0 | 2417 | |
michael@0 | 2418 | void |
michael@0 | 2419 | TypeCompartment::setTypeToHomogenousArray(ExclusiveContext *cx, |
michael@0 | 2420 | JSObject *obj, Type elementType) |
michael@0 | 2421 | { |
michael@0 | 2422 | JS_ASSERT(cx->compartment()->activeAnalysis); |
michael@0 | 2423 | |
michael@0 | 2424 | if (!arrayTypeTable) { |
michael@0 | 2425 | arrayTypeTable = cx->new_<ArrayTypeTable>(); |
michael@0 | 2426 | if (!arrayTypeTable || !arrayTypeTable->init()) { |
michael@0 | 2427 | arrayTypeTable = nullptr; |
michael@0 | 2428 | return; |
michael@0 | 2429 | } |
michael@0 | 2430 | } |
michael@0 | 2431 | |
michael@0 | 2432 | ArrayTableKey key(elementType, obj->getProto()); |
michael@0 | 2433 | DependentAddPtr<ArrayTypeTable> p(cx, *arrayTypeTable, key); |
michael@0 | 2434 | if (p) { |
michael@0 | 2435 | obj->setType(p->value()); |
michael@0 | 2436 | } else { |
michael@0 | 2437 | /* Make a new type to use for future arrays with the same elements. */ |
michael@0 | 2438 | RootedObject objProto(cx, obj->getProto()); |
michael@0 | 2439 | TypeObject *objType = newTypeObject(cx, &ArrayObject::class_, objProto); |
michael@0 | 2440 | if (!objType) |
michael@0 | 2441 | return; |
michael@0 | 2442 | obj->setType(objType); |
michael@0 | 2443 | |
michael@0 | 2444 | if (!objType->unknownProperties()) |
michael@0 | 2445 | objType->addPropertyType(cx, JSID_VOID, elementType); |
michael@0 | 2446 | |
michael@0 | 2447 | key.proto = objProto; |
michael@0 | 2448 | (void) p.add(cx, *arrayTypeTable, key, objType); |
michael@0 | 2449 | } |
michael@0 | 2450 | } |
michael@0 | 2451 | |
michael@0 | 2452 | void |
michael@0 | 2453 | TypeCompartment::fixArrayType(ExclusiveContext *cx, JSObject *obj) |
michael@0 | 2454 | { |
michael@0 | 2455 | AutoEnterAnalysis enter(cx); |
michael@0 | 2456 | |
michael@0 | 2457 | /* |
michael@0 | 2458 | * If the array is of homogenous type, pick a type object which will be |
michael@0 | 2459 | * shared with all other singleton/JSON arrays of the same type. |
michael@0 | 2460 | * If the array is heterogenous, keep the existing type object, which has |
michael@0 | 2461 | * unknown properties. |
michael@0 | 2462 | */ |
michael@0 | 2463 | JS_ASSERT(obj->is<ArrayObject>()); |
michael@0 | 2464 | |
michael@0 | 2465 | unsigned len = obj->getDenseInitializedLength(); |
michael@0 | 2466 | if (len == 0) |
michael@0 | 2467 | return; |
michael@0 | 2468 | |
michael@0 | 2469 | Type type = GetValueTypeForTable(obj->getDenseElement(0)); |
michael@0 | 2470 | |
michael@0 | 2471 | for (unsigned i = 1; i < len; i++) { |
michael@0 | 2472 | Type ntype = GetValueTypeForTable(obj->getDenseElement(i)); |
michael@0 | 2473 | if (ntype != type) { |
michael@0 | 2474 | if (NumberTypes(type, ntype)) |
michael@0 | 2475 | type = Type::DoubleType(); |
michael@0 | 2476 | else |
michael@0 | 2477 | return; |
michael@0 | 2478 | } |
michael@0 | 2479 | } |
michael@0 | 2480 | |
michael@0 | 2481 | setTypeToHomogenousArray(cx, obj, type); |
michael@0 | 2482 | } |
michael@0 | 2483 | |
michael@0 | 2484 | void |
michael@0 | 2485 | types::FixRestArgumentsType(ExclusiveContext *cx, JSObject *obj) |
michael@0 | 2486 | { |
michael@0 | 2487 | cx->compartment()->types.fixRestArgumentsType(cx, obj); |
michael@0 | 2488 | } |
michael@0 | 2489 | |
michael@0 | 2490 | void |
michael@0 | 2491 | TypeCompartment::fixRestArgumentsType(ExclusiveContext *cx, JSObject *obj) |
michael@0 | 2492 | { |
michael@0 | 2493 | AutoEnterAnalysis enter(cx); |
michael@0 | 2494 | |
michael@0 | 2495 | /* |
michael@0 | 2496 | * Tracking element types for rest argument arrays is not worth it, but we |
michael@0 | 2497 | * still want it to be known that it's a dense array. |
michael@0 | 2498 | */ |
michael@0 | 2499 | JS_ASSERT(obj->is<ArrayObject>()); |
michael@0 | 2500 | |
michael@0 | 2501 | setTypeToHomogenousArray(cx, obj, Type::UnknownType()); |
michael@0 | 2502 | } |
michael@0 | 2503 | |
michael@0 | 2504 | /* |
michael@0 | 2505 | * N.B. We could also use the initial shape of the object (before its type is |
michael@0 | 2506 | * fixed) as the key in the object table, but since all references in the table |
michael@0 | 2507 | * are weak the hash entries would usually be collected on GC even if objects |
michael@0 | 2508 | * with the new type/shape are still live. |
michael@0 | 2509 | */ |
michael@0 | 2510 | struct types::ObjectTableKey |
michael@0 | 2511 | { |
michael@0 | 2512 | jsid *properties; |
michael@0 | 2513 | uint32_t nproperties; |
michael@0 | 2514 | uint32_t nfixed; |
michael@0 | 2515 | |
michael@0 | 2516 | struct Lookup { |
michael@0 | 2517 | IdValuePair *properties; |
michael@0 | 2518 | uint32_t nproperties; |
michael@0 | 2519 | uint32_t nfixed; |
michael@0 | 2520 | |
michael@0 | 2521 | Lookup(IdValuePair *properties, uint32_t nproperties, uint32_t nfixed) |
michael@0 | 2522 | : properties(properties), nproperties(nproperties), nfixed(nfixed) |
michael@0 | 2523 | {} |
michael@0 | 2524 | }; |
michael@0 | 2525 | |
michael@0 | 2526 | static inline HashNumber hash(const Lookup &lookup) { |
michael@0 | 2527 | return (HashNumber) (JSID_BITS(lookup.properties[lookup.nproperties - 1].id) ^ |
michael@0 | 2528 | lookup.nproperties ^ |
michael@0 | 2529 | lookup.nfixed); |
michael@0 | 2530 | } |
michael@0 | 2531 | |
michael@0 | 2532 | static inline bool match(const ObjectTableKey &v, const Lookup &lookup) { |
michael@0 | 2533 | if (lookup.nproperties != v.nproperties || lookup.nfixed != v.nfixed) |
michael@0 | 2534 | return false; |
michael@0 | 2535 | for (size_t i = 0; i < lookup.nproperties; i++) { |
michael@0 | 2536 | if (lookup.properties[i].id != v.properties[i]) |
michael@0 | 2537 | return false; |
michael@0 | 2538 | } |
michael@0 | 2539 | return true; |
michael@0 | 2540 | } |
michael@0 | 2541 | }; |
michael@0 | 2542 | |
michael@0 | 2543 | struct types::ObjectTableEntry |
michael@0 | 2544 | { |
michael@0 | 2545 | ReadBarriered<TypeObject> object; |
michael@0 | 2546 | ReadBarriered<Shape> shape; |
michael@0 | 2547 | Type *types; |
michael@0 | 2548 | }; |
michael@0 | 2549 | |
michael@0 | 2550 | static inline void |
michael@0 | 2551 | UpdateObjectTableEntryTypes(ExclusiveContext *cx, ObjectTableEntry &entry, |
michael@0 | 2552 | IdValuePair *properties, size_t nproperties) |
michael@0 | 2553 | { |
michael@0 | 2554 | if (entry.object->unknownProperties()) |
michael@0 | 2555 | return; |
michael@0 | 2556 | for (size_t i = 0; i < nproperties; i++) { |
michael@0 | 2557 | Type type = entry.types[i]; |
michael@0 | 2558 | Type ntype = GetValueTypeForTable(properties[i].value); |
michael@0 | 2559 | if (ntype == type) |
michael@0 | 2560 | continue; |
michael@0 | 2561 | if (ntype.isPrimitive(JSVAL_TYPE_INT32) && |
michael@0 | 2562 | type.isPrimitive(JSVAL_TYPE_DOUBLE)) |
michael@0 | 2563 | { |
michael@0 | 2564 | /* The property types already reflect 'int32'. */ |
michael@0 | 2565 | } else { |
michael@0 | 2566 | if (ntype.isPrimitive(JSVAL_TYPE_DOUBLE) && |
michael@0 | 2567 | type.isPrimitive(JSVAL_TYPE_INT32)) |
michael@0 | 2568 | { |
michael@0 | 2569 | /* Include 'double' in the property types to avoid the update below later. */ |
michael@0 | 2570 | entry.types[i] = Type::DoubleType(); |
michael@0 | 2571 | } |
michael@0 | 2572 | entry.object->addPropertyType(cx, IdToTypeId(properties[i].id), ntype); |
michael@0 | 2573 | } |
michael@0 | 2574 | } |
michael@0 | 2575 | } |
michael@0 | 2576 | |
michael@0 | 2577 | void |
michael@0 | 2578 | TypeCompartment::fixObjectType(ExclusiveContext *cx, JSObject *obj) |
michael@0 | 2579 | { |
michael@0 | 2580 | AutoEnterAnalysis enter(cx); |
michael@0 | 2581 | |
michael@0 | 2582 | if (!objectTypeTable) { |
michael@0 | 2583 | objectTypeTable = cx->new_<ObjectTypeTable>(); |
michael@0 | 2584 | if (!objectTypeTable || !objectTypeTable->init()) { |
michael@0 | 2585 | js_delete(objectTypeTable); |
michael@0 | 2586 | objectTypeTable = nullptr; |
michael@0 | 2587 | return; |
michael@0 | 2588 | } |
michael@0 | 2589 | } |
michael@0 | 2590 | |
michael@0 | 2591 | /* |
michael@0 | 2592 | * Use the same type object for all singleton/JSON objects with the same |
michael@0 | 2593 | * base shape, i.e. the same fields written in the same order. |
michael@0 | 2594 | */ |
michael@0 | 2595 | JS_ASSERT(obj->is<JSObject>()); |
michael@0 | 2596 | |
michael@0 | 2597 | /* |
michael@0 | 2598 | * Exclude some objects we can't readily associate common types for based on their |
michael@0 | 2599 | * shape. Objects with metadata are excluded so that the metadata does not need to |
michael@0 | 2600 | * be included in the table lookup (the metadata object might be in the nursery). |
michael@0 | 2601 | */ |
michael@0 | 2602 | if (obj->slotSpan() == 0 || obj->inDictionaryMode() || !obj->hasEmptyElements() || obj->getMetadata()) |
michael@0 | 2603 | return; |
michael@0 | 2604 | |
michael@0 | 2605 | Vector<IdValuePair> properties(cx); |
michael@0 | 2606 | if (!properties.resize(obj->slotSpan())) |
michael@0 | 2607 | return; |
michael@0 | 2608 | |
michael@0 | 2609 | Shape *shape = obj->lastProperty(); |
michael@0 | 2610 | while (!shape->isEmptyShape()) { |
michael@0 | 2611 | IdValuePair &entry = properties[shape->slot()]; |
michael@0 | 2612 | entry.id = shape->propid(); |
michael@0 | 2613 | entry.value = obj->getSlot(shape->slot()); |
michael@0 | 2614 | shape = shape->previous(); |
michael@0 | 2615 | } |
michael@0 | 2616 | |
michael@0 | 2617 | ObjectTableKey::Lookup lookup(properties.begin(), properties.length(), obj->numFixedSlots()); |
michael@0 | 2618 | ObjectTypeTable::AddPtr p = objectTypeTable->lookupForAdd(lookup); |
michael@0 | 2619 | |
michael@0 | 2620 | if (p) { |
michael@0 | 2621 | JS_ASSERT(obj->getProto() == p->value().object->proto().toObject()); |
michael@0 | 2622 | JS_ASSERT(obj->lastProperty() == p->value().shape); |
michael@0 | 2623 | |
michael@0 | 2624 | UpdateObjectTableEntryTypes(cx, p->value(), properties.begin(), properties.length()); |
michael@0 | 2625 | obj->setType(p->value().object); |
michael@0 | 2626 | return; |
michael@0 | 2627 | } |
michael@0 | 2628 | |
michael@0 | 2629 | /* Make a new type to use for the object and similar future ones. */ |
michael@0 | 2630 | Rooted<TaggedProto> objProto(cx, obj->getTaggedProto()); |
michael@0 | 2631 | TypeObject *objType = newTypeObject(cx, &JSObject::class_, objProto); |
michael@0 | 2632 | if (!objType || !objType->addDefiniteProperties(cx, obj)) |
michael@0 | 2633 | return; |
michael@0 | 2634 | |
michael@0 | 2635 | if (obj->isIndexed()) |
michael@0 | 2636 | objType->setFlags(cx, OBJECT_FLAG_SPARSE_INDEXES); |
michael@0 | 2637 | |
michael@0 | 2638 | ScopedJSFreePtr<jsid> ids(cx->pod_calloc<jsid>(properties.length())); |
michael@0 | 2639 | if (!ids) |
michael@0 | 2640 | return; |
michael@0 | 2641 | |
michael@0 | 2642 | ScopedJSFreePtr<Type> types(cx->pod_calloc<Type>(properties.length())); |
michael@0 | 2643 | if (!types) |
michael@0 | 2644 | return; |
michael@0 | 2645 | |
michael@0 | 2646 | for (size_t i = 0; i < properties.length(); i++) { |
michael@0 | 2647 | ids[i] = properties[i].id; |
michael@0 | 2648 | types[i] = GetValueTypeForTable(obj->getSlot(i)); |
michael@0 | 2649 | if (!objType->unknownProperties()) |
michael@0 | 2650 | objType->addPropertyType(cx, IdToTypeId(ids[i]), types[i]); |
michael@0 | 2651 | } |
michael@0 | 2652 | |
michael@0 | 2653 | ObjectTableKey key; |
michael@0 | 2654 | key.properties = ids; |
michael@0 | 2655 | key.nproperties = properties.length(); |
michael@0 | 2656 | key.nfixed = obj->numFixedSlots(); |
michael@0 | 2657 | JS_ASSERT(ObjectTableKey::match(key, lookup)); |
michael@0 | 2658 | |
michael@0 | 2659 | ObjectTableEntry entry; |
michael@0 | 2660 | entry.object = objType; |
michael@0 | 2661 | entry.shape = obj->lastProperty(); |
michael@0 | 2662 | entry.types = types; |
michael@0 | 2663 | |
michael@0 | 2664 | obj->setType(objType); |
michael@0 | 2665 | |
michael@0 | 2666 | p = objectTypeTable->lookupForAdd(lookup); |
michael@0 | 2667 | if (objectTypeTable->add(p, key, entry)) { |
michael@0 | 2668 | ids.forget(); |
michael@0 | 2669 | types.forget(); |
michael@0 | 2670 | } |
michael@0 | 2671 | } |
michael@0 | 2672 | |
michael@0 | 2673 | JSObject * |
michael@0 | 2674 | TypeCompartment::newTypedObject(JSContext *cx, IdValuePair *properties, size_t nproperties) |
michael@0 | 2675 | { |
michael@0 | 2676 | AutoEnterAnalysis enter(cx); |
michael@0 | 2677 | |
michael@0 | 2678 | if (!objectTypeTable) { |
michael@0 | 2679 | objectTypeTable = cx->new_<ObjectTypeTable>(); |
michael@0 | 2680 | if (!objectTypeTable || !objectTypeTable->init()) { |
michael@0 | 2681 | js_delete(objectTypeTable); |
michael@0 | 2682 | objectTypeTable = nullptr; |
michael@0 | 2683 | return nullptr; |
michael@0 | 2684 | } |
michael@0 | 2685 | } |
michael@0 | 2686 | |
michael@0 | 2687 | /* |
michael@0 | 2688 | * Use the object type table to allocate an object with the specified |
michael@0 | 2689 | * properties, filling in its final type and shape and failing if no cache |
michael@0 | 2690 | * entry could be found for the properties. |
michael@0 | 2691 | */ |
michael@0 | 2692 | |
michael@0 | 2693 | /* |
michael@0 | 2694 | * Filter out a few cases where we don't want to use the object type table. |
michael@0 | 2695 | * Note that if the properties contain any duplicates or dense indexes, |
michael@0 | 2696 | * the lookup below will fail as such arrays of properties cannot be stored |
michael@0 | 2697 | * in the object type table --- fixObjectType populates the table with |
michael@0 | 2698 | * properties read off its input object, which cannot be duplicates, and |
michael@0 | 2699 | * ignores objects with dense indexes. |
michael@0 | 2700 | */ |
michael@0 | 2701 | if (!nproperties || nproperties >= PropertyTree::MAX_HEIGHT) |
michael@0 | 2702 | return nullptr; |
michael@0 | 2703 | |
michael@0 | 2704 | gc::AllocKind allocKind = gc::GetGCObjectKind(nproperties); |
michael@0 | 2705 | size_t nfixed = gc::GetGCKindSlots(allocKind, &JSObject::class_); |
michael@0 | 2706 | |
michael@0 | 2707 | ObjectTableKey::Lookup lookup(properties, nproperties, nfixed); |
michael@0 | 2708 | ObjectTypeTable::AddPtr p = objectTypeTable->lookupForAdd(lookup); |
michael@0 | 2709 | |
michael@0 | 2710 | if (!p) |
michael@0 | 2711 | return nullptr; |
michael@0 | 2712 | |
michael@0 | 2713 | RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_, allocKind)); |
michael@0 | 2714 | if (!obj) { |
michael@0 | 2715 | cx->clearPendingException(); |
michael@0 | 2716 | return nullptr; |
michael@0 | 2717 | } |
michael@0 | 2718 | JS_ASSERT(obj->getProto() == p->value().object->proto().toObject()); |
michael@0 | 2719 | |
michael@0 | 2720 | RootedShape shape(cx, p->value().shape); |
michael@0 | 2721 | if (!JSObject::setLastProperty(cx, obj, shape)) { |
michael@0 | 2722 | cx->clearPendingException(); |
michael@0 | 2723 | return nullptr; |
michael@0 | 2724 | } |
michael@0 | 2725 | |
michael@0 | 2726 | UpdateObjectTableEntryTypes(cx, p->value(), properties, nproperties); |
michael@0 | 2727 | |
michael@0 | 2728 | for (size_t i = 0; i < nproperties; i++) |
michael@0 | 2729 | obj->setSlot(i, properties[i].value); |
michael@0 | 2730 | |
michael@0 | 2731 | obj->setType(p->value().object); |
michael@0 | 2732 | return obj; |
michael@0 | 2733 | } |
michael@0 | 2734 | |
michael@0 | 2735 | ///////////////////////////////////////////////////////////////////// |
michael@0 | 2736 | // TypeObject |
michael@0 | 2737 | ///////////////////////////////////////////////////////////////////// |
michael@0 | 2738 | |
michael@0 | 2739 | void |
michael@0 | 2740 | TypeObject::setProto(JSContext *cx, TaggedProto proto) |
michael@0 | 2741 | { |
michael@0 | 2742 | JS_ASSERT(singleton()); |
michael@0 | 2743 | |
michael@0 | 2744 | if (proto.isObject() && IsInsideNursery(cx->runtime(), proto.toObject())) |
michael@0 | 2745 | addFlags(OBJECT_FLAG_NURSERY_PROTO); |
michael@0 | 2746 | |
michael@0 | 2747 | setProtoUnchecked(proto); |
michael@0 | 2748 | } |
michael@0 | 2749 | |
michael@0 | 2750 | static inline void |
michael@0 | 2751 | UpdatePropertyType(ExclusiveContext *cx, HeapTypeSet *types, JSObject *obj, Shape *shape, |
michael@0 | 2752 | bool indexed) |
michael@0 | 2753 | { |
michael@0 | 2754 | if (!shape->writable()) |
michael@0 | 2755 | types->setNonWritableProperty(cx); |
michael@0 | 2756 | |
michael@0 | 2757 | if (shape->hasGetterValue() || shape->hasSetterValue()) { |
michael@0 | 2758 | types->setNonDataProperty(cx); |
michael@0 | 2759 | types->TypeSet::addType(Type::UnknownType(), &cx->typeLifoAlloc()); |
michael@0 | 2760 | } else if (shape->hasDefaultGetter() && shape->hasSlot()) { |
michael@0 | 2761 | if (!indexed && types->canSetDefinite(shape->slot())) |
michael@0 | 2762 | types->setDefinite(shape->slot()); |
michael@0 | 2763 | |
michael@0 | 2764 | const Value &value = obj->nativeGetSlot(shape->slot()); |
michael@0 | 2765 | |
michael@0 | 2766 | /* |
michael@0 | 2767 | * Don't add initial undefined types for properties of global objects |
michael@0 | 2768 | * that are not collated into the JSID_VOID property (see propertySet |
michael@0 | 2769 | * comment). |
michael@0 | 2770 | */ |
michael@0 | 2771 | if (indexed || !value.isUndefined() || !CanHaveEmptyPropertyTypesForOwnProperty(obj)) { |
michael@0 | 2772 | Type type = GetValueType(value); |
michael@0 | 2773 | types->TypeSet::addType(type, &cx->typeLifoAlloc()); |
michael@0 | 2774 | } |
michael@0 | 2775 | } |
michael@0 | 2776 | } |
michael@0 | 2777 | |
michael@0 | 2778 | void |
michael@0 | 2779 | TypeObject::updateNewPropertyTypes(ExclusiveContext *cx, jsid id, HeapTypeSet *types) |
michael@0 | 2780 | { |
michael@0 | 2781 | InferSpew(ISpewOps, "typeSet: %sT%p%s property %s %s", |
michael@0 | 2782 | InferSpewColor(types), types, InferSpewColorReset(), |
michael@0 | 2783 | TypeObjectString(this), TypeIdString(id)); |
michael@0 | 2784 | |
michael@0 | 2785 | if (!singleton() || !singleton()->isNative()) |
michael@0 | 2786 | return; |
michael@0 | 2787 | |
michael@0 | 2788 | /* |
michael@0 | 2789 | * Fill the property in with any type the object already has in an own |
michael@0 | 2790 | * property. We are only interested in plain native properties and |
michael@0 | 2791 | * dense elements which don't go through a barrier when read by the VM |
michael@0 | 2792 | * or jitcode. |
michael@0 | 2793 | */ |
michael@0 | 2794 | |
michael@0 | 2795 | if (JSID_IS_VOID(id)) { |
michael@0 | 2796 | /* Go through all shapes on the object to get integer-valued properties. */ |
michael@0 | 2797 | RootedShape shape(cx, singleton()->lastProperty()); |
michael@0 | 2798 | while (!shape->isEmptyShape()) { |
michael@0 | 2799 | if (JSID_IS_VOID(IdToTypeId(shape->propid()))) |
michael@0 | 2800 | UpdatePropertyType(cx, types, singleton(), shape, true); |
michael@0 | 2801 | shape = shape->previous(); |
michael@0 | 2802 | } |
michael@0 | 2803 | |
michael@0 | 2804 | /* Also get values of any dense elements in the object. */ |
michael@0 | 2805 | for (size_t i = 0; i < singleton()->getDenseInitializedLength(); i++) { |
michael@0 | 2806 | const Value &value = singleton()->getDenseElement(i); |
michael@0 | 2807 | if (!value.isMagic(JS_ELEMENTS_HOLE)) { |
michael@0 | 2808 | Type type = GetValueType(value); |
michael@0 | 2809 | types->TypeSet::addType(type, &cx->typeLifoAlloc()); |
michael@0 | 2810 | } |
michael@0 | 2811 | } |
michael@0 | 2812 | } else if (!JSID_IS_EMPTY(id)) { |
michael@0 | 2813 | RootedId rootedId(cx, id); |
michael@0 | 2814 | Shape *shape = singleton()->nativeLookup(cx, rootedId); |
michael@0 | 2815 | if (shape) |
michael@0 | 2816 | UpdatePropertyType(cx, types, singleton(), shape, false); |
michael@0 | 2817 | } |
michael@0 | 2818 | |
michael@0 | 2819 | if (singleton()->watched()) { |
michael@0 | 2820 | /* |
michael@0 | 2821 | * Mark the property as non-data, to inhibit optimizations on it |
michael@0 | 2822 | * and avoid bypassing the watchpoint handler. |
michael@0 | 2823 | */ |
michael@0 | 2824 | types->setNonDataProperty(cx); |
michael@0 | 2825 | } |
michael@0 | 2826 | } |
michael@0 | 2827 | |
michael@0 | 2828 | bool |
michael@0 | 2829 | TypeObject::addDefiniteProperties(ExclusiveContext *cx, JSObject *obj) |
michael@0 | 2830 | { |
michael@0 | 2831 | if (unknownProperties()) |
michael@0 | 2832 | return true; |
michael@0 | 2833 | |
michael@0 | 2834 | /* Mark all properties of obj as definite properties of this type. */ |
michael@0 | 2835 | AutoEnterAnalysis enter(cx); |
michael@0 | 2836 | |
michael@0 | 2837 | RootedShape shape(cx, obj->lastProperty()); |
michael@0 | 2838 | while (!shape->isEmptyShape()) { |
michael@0 | 2839 | jsid id = IdToTypeId(shape->propid()); |
michael@0 | 2840 | if (!JSID_IS_VOID(id) && obj->isFixedSlot(shape->slot())) { |
michael@0 | 2841 | TypeSet *types = getProperty(cx, id); |
michael@0 | 2842 | if (!types) |
michael@0 | 2843 | return false; |
michael@0 | 2844 | types->setDefinite(shape->slot()); |
michael@0 | 2845 | } |
michael@0 | 2846 | shape = shape->previous(); |
michael@0 | 2847 | } |
michael@0 | 2848 | |
michael@0 | 2849 | return true; |
michael@0 | 2850 | } |
michael@0 | 2851 | |
michael@0 | 2852 | bool |
michael@0 | 2853 | TypeObject::matchDefiniteProperties(HandleObject obj) |
michael@0 | 2854 | { |
michael@0 | 2855 | unsigned count = getPropertyCount(); |
michael@0 | 2856 | for (unsigned i = 0; i < count; i++) { |
michael@0 | 2857 | Property *prop = getProperty(i); |
michael@0 | 2858 | if (!prop) |
michael@0 | 2859 | continue; |
michael@0 | 2860 | if (prop->types.definiteProperty()) { |
michael@0 | 2861 | unsigned slot = prop->types.definiteSlot(); |
michael@0 | 2862 | |
michael@0 | 2863 | bool found = false; |
michael@0 | 2864 | Shape *shape = obj->lastProperty(); |
michael@0 | 2865 | while (!shape->isEmptyShape()) { |
michael@0 | 2866 | if (shape->slot() == slot && shape->propid() == prop->id) { |
michael@0 | 2867 | found = true; |
michael@0 | 2868 | break; |
michael@0 | 2869 | } |
michael@0 | 2870 | shape = shape->previous(); |
michael@0 | 2871 | } |
michael@0 | 2872 | if (!found) |
michael@0 | 2873 | return false; |
michael@0 | 2874 | } |
michael@0 | 2875 | } |
michael@0 | 2876 | |
michael@0 | 2877 | return true; |
michael@0 | 2878 | } |
michael@0 | 2879 | |
michael@0 | 2880 | static inline void |
michael@0 | 2881 | InlineAddTypeProperty(ExclusiveContext *cx, TypeObject *obj, jsid id, Type type) |
michael@0 | 2882 | { |
michael@0 | 2883 | JS_ASSERT(id == IdToTypeId(id)); |
michael@0 | 2884 | |
michael@0 | 2885 | AutoEnterAnalysis enter(cx); |
michael@0 | 2886 | |
michael@0 | 2887 | HeapTypeSet *types = obj->getProperty(cx, id); |
michael@0 | 2888 | if (!types || types->hasType(type)) |
michael@0 | 2889 | return; |
michael@0 | 2890 | |
michael@0 | 2891 | InferSpew(ISpewOps, "externalType: property %s %s: %s", |
michael@0 | 2892 | TypeObjectString(obj), TypeIdString(id), TypeString(type)); |
michael@0 | 2893 | types->addType(cx, type); |
michael@0 | 2894 | } |
michael@0 | 2895 | |
michael@0 | 2896 | void |
michael@0 | 2897 | TypeObject::addPropertyType(ExclusiveContext *cx, jsid id, Type type) |
michael@0 | 2898 | { |
michael@0 | 2899 | InlineAddTypeProperty(cx, this, id, type); |
michael@0 | 2900 | } |
michael@0 | 2901 | |
michael@0 | 2902 | void |
michael@0 | 2903 | TypeObject::addPropertyType(ExclusiveContext *cx, jsid id, const Value &value) |
michael@0 | 2904 | { |
michael@0 | 2905 | InlineAddTypeProperty(cx, this, id, GetValueType(value)); |
michael@0 | 2906 | } |
michael@0 | 2907 | |
michael@0 | 2908 | void |
michael@0 | 2909 | TypeObject::markPropertyNonData(ExclusiveContext *cx, jsid id) |
michael@0 | 2910 | { |
michael@0 | 2911 | AutoEnterAnalysis enter(cx); |
michael@0 | 2912 | |
michael@0 | 2913 | id = IdToTypeId(id); |
michael@0 | 2914 | |
michael@0 | 2915 | HeapTypeSet *types = getProperty(cx, id); |
michael@0 | 2916 | if (types) |
michael@0 | 2917 | types->setNonDataProperty(cx); |
michael@0 | 2918 | } |
michael@0 | 2919 | |
michael@0 | 2920 | void |
michael@0 | 2921 | TypeObject::markPropertyNonWritable(ExclusiveContext *cx, jsid id) |
michael@0 | 2922 | { |
michael@0 | 2923 | AutoEnterAnalysis enter(cx); |
michael@0 | 2924 | |
michael@0 | 2925 | id = IdToTypeId(id); |
michael@0 | 2926 | |
michael@0 | 2927 | HeapTypeSet *types = getProperty(cx, id); |
michael@0 | 2928 | if (types) |
michael@0 | 2929 | types->setNonWritableProperty(cx); |
michael@0 | 2930 | } |
michael@0 | 2931 | |
michael@0 | 2932 | bool |
michael@0 | 2933 | TypeObject::isPropertyNonData(jsid id) |
michael@0 | 2934 | { |
michael@0 | 2935 | TypeSet *types = maybeGetProperty(id); |
michael@0 | 2936 | if (types) |
michael@0 | 2937 | return types->nonDataProperty(); |
michael@0 | 2938 | return false; |
michael@0 | 2939 | } |
michael@0 | 2940 | |
michael@0 | 2941 | bool |
michael@0 | 2942 | TypeObject::isPropertyNonWritable(jsid id) |
michael@0 | 2943 | { |
michael@0 | 2944 | TypeSet *types = maybeGetProperty(id); |
michael@0 | 2945 | if (types) |
michael@0 | 2946 | return types->nonWritableProperty(); |
michael@0 | 2947 | return false; |
michael@0 | 2948 | } |
michael@0 | 2949 | |
michael@0 | 2950 | void |
michael@0 | 2951 | TypeObject::markStateChange(ExclusiveContext *cxArg) |
michael@0 | 2952 | { |
michael@0 | 2953 | if (unknownProperties()) |
michael@0 | 2954 | return; |
michael@0 | 2955 | |
michael@0 | 2956 | AutoEnterAnalysis enter(cxArg); |
michael@0 | 2957 | HeapTypeSet *types = maybeGetProperty(JSID_EMPTY); |
michael@0 | 2958 | if (types) { |
michael@0 | 2959 | if (JSContext *cx = cxArg->maybeJSContext()) { |
michael@0 | 2960 | TypeConstraint *constraint = types->constraintList; |
michael@0 | 2961 | while (constraint) { |
michael@0 | 2962 | constraint->newObjectState(cx, this); |
michael@0 | 2963 | constraint = constraint->next; |
michael@0 | 2964 | } |
michael@0 | 2965 | } else { |
michael@0 | 2966 | JS_ASSERT(!types->constraintList); |
michael@0 | 2967 | } |
michael@0 | 2968 | } |
michael@0 | 2969 | } |
michael@0 | 2970 | |
michael@0 | 2971 | void |
michael@0 | 2972 | TypeObject::setFlags(ExclusiveContext *cx, TypeObjectFlags flags) |
michael@0 | 2973 | { |
michael@0 | 2974 | if (hasAllFlags(flags)) |
michael@0 | 2975 | return; |
michael@0 | 2976 | |
michael@0 | 2977 | AutoEnterAnalysis enter(cx); |
michael@0 | 2978 | |
michael@0 | 2979 | if (singleton()) { |
michael@0 | 2980 | /* Make sure flags are consistent with persistent object state. */ |
michael@0 | 2981 | JS_ASSERT_IF(flags & OBJECT_FLAG_ITERATED, |
michael@0 | 2982 | singleton()->lastProperty()->hasObjectFlag(BaseShape::ITERATED_SINGLETON)); |
michael@0 | 2983 | } |
michael@0 | 2984 | |
michael@0 | 2985 | addFlags(flags); |
michael@0 | 2986 | |
michael@0 | 2987 | InferSpew(ISpewOps, "%s: setFlags 0x%x", TypeObjectString(this), flags); |
michael@0 | 2988 | |
michael@0 | 2989 | ObjectStateChange(cx, this, false); |
michael@0 | 2990 | } |
michael@0 | 2991 | |
michael@0 | 2992 | void |
michael@0 | 2993 | TypeObject::markUnknown(ExclusiveContext *cx) |
michael@0 | 2994 | { |
michael@0 | 2995 | AutoEnterAnalysis enter(cx); |
michael@0 | 2996 | |
michael@0 | 2997 | JS_ASSERT(cx->compartment()->activeAnalysis); |
michael@0 | 2998 | JS_ASSERT(!unknownProperties()); |
michael@0 | 2999 | |
michael@0 | 3000 | if (!(flags() & OBJECT_FLAG_ADDENDUM_CLEARED)) |
michael@0 | 3001 | clearAddendum(cx); |
michael@0 | 3002 | |
michael@0 | 3003 | InferSpew(ISpewOps, "UnknownProperties: %s", TypeObjectString(this)); |
michael@0 | 3004 | |
michael@0 | 3005 | ObjectStateChange(cx, this, true); |
michael@0 | 3006 | |
michael@0 | 3007 | /* |
michael@0 | 3008 | * Existing constraints may have already been added to this object, which we need |
michael@0 | 3009 | * to do the right thing for. We can't ensure that we will mark all unknown |
michael@0 | 3010 | * objects before they have been accessed, as the __proto__ of a known object |
michael@0 | 3011 | * could be dynamically set to an unknown object, and we can decide to ignore |
michael@0 | 3012 | * properties of an object during analysis (i.e. hashmaps). Adding unknown for |
michael@0 | 3013 | * any properties accessed already accounts for possible values read from them. |
michael@0 | 3014 | */ |
michael@0 | 3015 | |
michael@0 | 3016 | unsigned count = getPropertyCount(); |
michael@0 | 3017 | for (unsigned i = 0; i < count; i++) { |
michael@0 | 3018 | Property *prop = getProperty(i); |
michael@0 | 3019 | if (prop) { |
michael@0 | 3020 | prop->types.addType(cx, Type::UnknownType()); |
michael@0 | 3021 | prop->types.setNonDataProperty(cx); |
michael@0 | 3022 | } |
michael@0 | 3023 | } |
michael@0 | 3024 | } |
michael@0 | 3025 | |
michael@0 | 3026 | void |
michael@0 | 3027 | TypeObject::clearAddendum(ExclusiveContext *cx) |
michael@0 | 3028 | { |
michael@0 | 3029 | JS_ASSERT(!(flags() & OBJECT_FLAG_ADDENDUM_CLEARED)); |
michael@0 | 3030 | |
michael@0 | 3031 | addFlags(OBJECT_FLAG_ADDENDUM_CLEARED); |
michael@0 | 3032 | |
michael@0 | 3033 | /* |
michael@0 | 3034 | * It is possible for the object to not have a new script or other |
michael@0 | 3035 | * addendum yet, but to have one added in the future. When |
michael@0 | 3036 | * analyzing properties of new scripts we mix in adding |
michael@0 | 3037 | * constraints to trigger clearNewScript with changes to the type |
michael@0 | 3038 | * sets themselves (from breakTypeBarriers). It is possible that |
michael@0 | 3039 | * we could trigger one of these constraints before |
michael@0 | 3040 | * AnalyzeNewScriptProperties has finished, in which case we want |
michael@0 | 3041 | * to make sure that call fails. |
michael@0 | 3042 | */ |
michael@0 | 3043 | if (!addendum) |
michael@0 | 3044 | return; |
michael@0 | 3045 | |
michael@0 | 3046 | switch (addendum->kind) { |
michael@0 | 3047 | case TypeObjectAddendum::NewScript: |
michael@0 | 3048 | clearNewScriptAddendum(cx); |
michael@0 | 3049 | break; |
michael@0 | 3050 | |
michael@0 | 3051 | case TypeObjectAddendum::TypedObject: |
michael@0 | 3052 | clearTypedObjectAddendum(cx); |
michael@0 | 3053 | break; |
michael@0 | 3054 | } |
michael@0 | 3055 | |
michael@0 | 3056 | /* We nullptr out addendum *before* freeing it so the write barrier works. */ |
michael@0 | 3057 | TypeObjectAddendum *savedAddendum = addendum; |
michael@0 | 3058 | addendum = nullptr; |
michael@0 | 3059 | js_free(savedAddendum); |
michael@0 | 3060 | |
michael@0 | 3061 | markStateChange(cx); |
michael@0 | 3062 | } |
michael@0 | 3063 | |
michael@0 | 3064 | void |
michael@0 | 3065 | TypeObject::clearNewScriptAddendum(ExclusiveContext *cx) |
michael@0 | 3066 | { |
michael@0 | 3067 | AutoEnterAnalysis enter(cx); |
michael@0 | 3068 | |
michael@0 | 3069 | /* |
michael@0 | 3070 | * Any definite properties we added due to analysis of the new script when |
michael@0 | 3071 | * the type object was created are now invalid: objects with the same type |
michael@0 | 3072 | * can be created by using 'new' on a different script or through some |
michael@0 | 3073 | * other mechanism (e.g. Object.create). Rather than clear out the definite |
michael@0 | 3074 | * bits on the object's properties, just mark such properties as having |
michael@0 | 3075 | * been deleted/reconfigured, which will have the same effect on JITs |
michael@0 | 3076 | * wanting to use the definite bits to optimize property accesses. |
michael@0 | 3077 | */ |
michael@0 | 3078 | for (unsigned i = 0; i < getPropertyCount(); i++) { |
michael@0 | 3079 | Property *prop = getProperty(i); |
michael@0 | 3080 | if (!prop) |
michael@0 | 3081 | continue; |
michael@0 | 3082 | if (prop->types.definiteProperty()) |
michael@0 | 3083 | prop->types.setNonDataProperty(cx); |
michael@0 | 3084 | } |
michael@0 | 3085 | |
michael@0 | 3086 | /* |
michael@0 | 3087 | * If we cleared the new script while in the middle of initializing an |
michael@0 | 3088 | * object, it will still have the new script's shape and reflect the no |
michael@0 | 3089 | * longer correct state of the object once its initialization is completed. |
michael@0 | 3090 | * We can't really detect the possibility of this statically, but the new |
michael@0 | 3091 | * script keeps track of where each property is initialized so we can walk |
michael@0 | 3092 | * the stack and fix up any such objects. |
michael@0 | 3093 | */ |
michael@0 | 3094 | if (cx->isJSContext()) { |
michael@0 | 3095 | Vector<uint32_t, 32> pcOffsets(cx); |
michael@0 | 3096 | for (ScriptFrameIter iter(cx->asJSContext()); !iter.done(); ++iter) { |
michael@0 | 3097 | pcOffsets.append(iter.script()->pcToOffset(iter.pc())); |
michael@0 | 3098 | if (!iter.isConstructing() || |
michael@0 | 3099 | iter.callee() != newScript()->fun || |
michael@0 | 3100 | !iter.thisv().isObject() || |
michael@0 | 3101 | iter.thisv().toObject().hasLazyType() || |
michael@0 | 3102 | iter.thisv().toObject().type() != this) |
michael@0 | 3103 | { |
michael@0 | 3104 | continue; |
michael@0 | 3105 | } |
michael@0 | 3106 | |
michael@0 | 3107 | // Found a matching frame. |
michael@0 | 3108 | RootedObject obj(cx, &iter.thisv().toObject()); |
michael@0 | 3109 | |
michael@0 | 3110 | // Whether all identified 'new' properties have been initialized. |
michael@0 | 3111 | bool finished = false; |
michael@0 | 3112 | |
michael@0 | 3113 | // If not finished, number of properties that have been added. |
michael@0 | 3114 | uint32_t numProperties = 0; |
michael@0 | 3115 | |
michael@0 | 3116 | // Whether the current SETPROP is within an inner frame which has |
michael@0 | 3117 | // finished entirely. |
michael@0 | 3118 | bool pastProperty = false; |
michael@0 | 3119 | |
michael@0 | 3120 | // Index in pcOffsets of the outermost frame. |
michael@0 | 3121 | int callDepth = pcOffsets.length() - 1; |
michael@0 | 3122 | |
michael@0 | 3123 | // Index in pcOffsets of the frame currently being checked for a SETPROP. |
michael@0 | 3124 | int setpropDepth = callDepth; |
michael@0 | 3125 | |
michael@0 | 3126 | for (TypeNewScript::Initializer *init = newScript()->initializerList;; init++) { |
michael@0 | 3127 | if (init->kind == TypeNewScript::Initializer::SETPROP) { |
michael@0 | 3128 | if (!pastProperty && pcOffsets[setpropDepth] < init->offset) { |
michael@0 | 3129 | // Have not yet reached this setprop. |
michael@0 | 3130 | break; |
michael@0 | 3131 | } |
michael@0 | 3132 | // This setprop has executed, reset state for the next one. |
michael@0 | 3133 | numProperties++; |
michael@0 | 3134 | pastProperty = false; |
michael@0 | 3135 | setpropDepth = callDepth; |
michael@0 | 3136 | } else if (init->kind == TypeNewScript::Initializer::SETPROP_FRAME) { |
michael@0 | 3137 | if (!pastProperty) { |
michael@0 | 3138 | if (pcOffsets[setpropDepth] < init->offset) { |
michael@0 | 3139 | // Have not yet reached this inner call. |
michael@0 | 3140 | break; |
michael@0 | 3141 | } else if (pcOffsets[setpropDepth] > init->offset) { |
michael@0 | 3142 | // Have advanced past this inner call. |
michael@0 | 3143 | pastProperty = true; |
michael@0 | 3144 | } else if (setpropDepth == 0) { |
michael@0 | 3145 | // Have reached this call but not yet in it. |
michael@0 | 3146 | break; |
michael@0 | 3147 | } else { |
michael@0 | 3148 | // Somewhere inside this inner call. |
michael@0 | 3149 | setpropDepth--; |
michael@0 | 3150 | } |
michael@0 | 3151 | } |
michael@0 | 3152 | } else { |
michael@0 | 3153 | JS_ASSERT(init->kind == TypeNewScript::Initializer::DONE); |
michael@0 | 3154 | finished = true; |
michael@0 | 3155 | break; |
michael@0 | 3156 | } |
michael@0 | 3157 | } |
michael@0 | 3158 | |
michael@0 | 3159 | if (!finished) |
michael@0 | 3160 | (void) JSObject::rollbackProperties(cx, obj, numProperties); |
michael@0 | 3161 | } |
michael@0 | 3162 | } else { |
michael@0 | 3163 | // Threads with an ExclusiveContext are not allowed to run scripts. |
michael@0 | 3164 | JS_ASSERT(!cx->perThreadData->activation()); |
michael@0 | 3165 | } |
michael@0 | 3166 | } |
michael@0 | 3167 | |
michael@0 | 3168 | void |
michael@0 | 3169 | TypeObject::maybeClearNewScriptAddendumOnOOM() |
michael@0 | 3170 | { |
michael@0 | 3171 | if (!isMarked()) |
michael@0 | 3172 | return; |
michael@0 | 3173 | |
michael@0 | 3174 | if (!addendum || addendum->kind != TypeObjectAddendum::NewScript) |
michael@0 | 3175 | return; |
michael@0 | 3176 | |
michael@0 | 3177 | for (unsigned i = 0; i < getPropertyCount(); i++) { |
michael@0 | 3178 | Property *prop = getProperty(i); |
michael@0 | 3179 | if (!prop) |
michael@0 | 3180 | continue; |
michael@0 | 3181 | if (prop->types.definiteProperty()) |
michael@0 | 3182 | prop->types.setNonDataPropertyIgnoringConstraints(); |
michael@0 | 3183 | } |
michael@0 | 3184 | |
michael@0 | 3185 | // This method is called during GC sweeping, so there is no write barrier |
michael@0 | 3186 | // that needs to be triggered. |
michael@0 | 3187 | js_free(addendum); |
michael@0 | 3188 | addendum.unsafeSet(nullptr); |
michael@0 | 3189 | } |
michael@0 | 3190 | |
michael@0 | 3191 | void |
michael@0 | 3192 | TypeObject::clearTypedObjectAddendum(ExclusiveContext *cx) |
michael@0 | 3193 | { |
michael@0 | 3194 | } |
michael@0 | 3195 | |
michael@0 | 3196 | void |
michael@0 | 3197 | TypeObject::print() |
michael@0 | 3198 | { |
michael@0 | 3199 | TaggedProto tagged(proto()); |
michael@0 | 3200 | fprintf(stderr, "%s : %s", |
michael@0 | 3201 | TypeObjectString(this), |
michael@0 | 3202 | tagged.isObject() ? TypeString(Type::ObjectType(tagged.toObject())) |
michael@0 | 3203 | : (tagged.isLazy() ? "(lazy)" : "(null)")); |
michael@0 | 3204 | |
michael@0 | 3205 | if (unknownProperties()) { |
michael@0 | 3206 | fprintf(stderr, " unknown"); |
michael@0 | 3207 | } else { |
michael@0 | 3208 | if (!hasAnyFlags(OBJECT_FLAG_SPARSE_INDEXES)) |
michael@0 | 3209 | fprintf(stderr, " dense"); |
michael@0 | 3210 | if (!hasAnyFlags(OBJECT_FLAG_NON_PACKED)) |
michael@0 | 3211 | fprintf(stderr, " packed"); |
michael@0 | 3212 | if (!hasAnyFlags(OBJECT_FLAG_LENGTH_OVERFLOW)) |
michael@0 | 3213 | fprintf(stderr, " noLengthOverflow"); |
michael@0 | 3214 | if (hasAnyFlags(OBJECT_FLAG_ITERATED)) |
michael@0 | 3215 | fprintf(stderr, " iterated"); |
michael@0 | 3216 | if (interpretedFunction) |
michael@0 | 3217 | fprintf(stderr, " ifun"); |
michael@0 | 3218 | } |
michael@0 | 3219 | |
michael@0 | 3220 | unsigned count = getPropertyCount(); |
michael@0 | 3221 | |
michael@0 | 3222 | if (count == 0) { |
michael@0 | 3223 | fprintf(stderr, " {}\n"); |
michael@0 | 3224 | return; |
michael@0 | 3225 | } |
michael@0 | 3226 | |
michael@0 | 3227 | fprintf(stderr, " {"); |
michael@0 | 3228 | |
michael@0 | 3229 | for (unsigned i = 0; i < count; i++) { |
michael@0 | 3230 | Property *prop = getProperty(i); |
michael@0 | 3231 | if (prop) { |
michael@0 | 3232 | fprintf(stderr, "\n %s:", TypeIdString(prop->id)); |
michael@0 | 3233 | prop->types.print(); |
michael@0 | 3234 | } |
michael@0 | 3235 | } |
michael@0 | 3236 | |
michael@0 | 3237 | fprintf(stderr, "\n}\n"); |
michael@0 | 3238 | } |
michael@0 | 3239 | |
michael@0 | 3240 | ///////////////////////////////////////////////////////////////////// |
michael@0 | 3241 | // Type Analysis |
michael@0 | 3242 | ///////////////////////////////////////////////////////////////////// |
michael@0 | 3243 | |
michael@0 | 3244 | /* |
michael@0 | 3245 | * Persistent constraint clearing out newScript and definite properties from |
michael@0 | 3246 | * an object should a property on another object get a getter or setter. |
michael@0 | 3247 | */ |
michael@0 | 3248 | class TypeConstraintClearDefiniteGetterSetter : public TypeConstraint |
michael@0 | 3249 | { |
michael@0 | 3250 | public: |
michael@0 | 3251 | TypeObject *object; |
michael@0 | 3252 | |
michael@0 | 3253 | TypeConstraintClearDefiniteGetterSetter(TypeObject *object) |
michael@0 | 3254 | : object(object) |
michael@0 | 3255 | {} |
michael@0 | 3256 | |
michael@0 | 3257 | const char *kind() { return "clearDefiniteGetterSetter"; } |
michael@0 | 3258 | |
michael@0 | 3259 | void newPropertyState(JSContext *cx, TypeSet *source) |
michael@0 | 3260 | { |
michael@0 | 3261 | if (!object->hasNewScript()) |
michael@0 | 3262 | return; |
michael@0 | 3263 | /* |
michael@0 | 3264 | * Clear out the newScript shape and definite property information from |
michael@0 | 3265 | * an object if the source type set could be a setter or could be |
michael@0 | 3266 | * non-writable. |
michael@0 | 3267 | */ |
michael@0 | 3268 | if (!(object->flags() & OBJECT_FLAG_ADDENDUM_CLEARED) && |
michael@0 | 3269 | (source->nonDataProperty() || source->nonWritableProperty())) |
michael@0 | 3270 | { |
michael@0 | 3271 | object->clearAddendum(cx); |
michael@0 | 3272 | } |
michael@0 | 3273 | } |
michael@0 | 3274 | |
michael@0 | 3275 | void newType(JSContext *cx, TypeSet *source, Type type) {} |
michael@0 | 3276 | |
michael@0 | 3277 | bool sweep(TypeZone &zone, TypeConstraint **res) { |
michael@0 | 3278 | if (IsTypeObjectAboutToBeFinalized(&object)) |
michael@0 | 3279 | return false; |
michael@0 | 3280 | *res = zone.typeLifoAlloc.new_<TypeConstraintClearDefiniteGetterSetter>(object); |
michael@0 | 3281 | return true; |
michael@0 | 3282 | } |
michael@0 | 3283 | }; |
michael@0 | 3284 | |
michael@0 | 3285 | bool |
michael@0 | 3286 | types::AddClearDefiniteGetterSetterForPrototypeChain(JSContext *cx, TypeObject *type, HandleId id) |
michael@0 | 3287 | { |
michael@0 | 3288 | /* |
michael@0 | 3289 | * Ensure that if the properties named here could have a getter, setter or |
michael@0 | 3290 | * a permanent property in any transitive prototype, the definite |
michael@0 | 3291 | * properties get cleared from the type. |
michael@0 | 3292 | */ |
michael@0 | 3293 | RootedObject parent(cx, type->proto().toObjectOrNull()); |
michael@0 | 3294 | while (parent) { |
michael@0 | 3295 | TypeObject *parentObject = parent->getType(cx); |
michael@0 | 3296 | if (!parentObject || parentObject->unknownProperties()) |
michael@0 | 3297 | return false; |
michael@0 | 3298 | HeapTypeSet *parentTypes = parentObject->getProperty(cx, id); |
michael@0 | 3299 | if (!parentTypes || parentTypes->nonDataProperty() || parentTypes->nonWritableProperty()) |
michael@0 | 3300 | return false; |
michael@0 | 3301 | if (!parentTypes->addConstraint(cx, cx->typeLifoAlloc().new_<TypeConstraintClearDefiniteGetterSetter>(type))) |
michael@0 | 3302 | return false; |
michael@0 | 3303 | parent = parent->getProto(); |
michael@0 | 3304 | } |
michael@0 | 3305 | return true; |
michael@0 | 3306 | } |
michael@0 | 3307 | |
michael@0 | 3308 | /* |
michael@0 | 3309 | * Constraint which clears definite properties on an object should a type set |
michael@0 | 3310 | * contain any types other than a single object. |
michael@0 | 3311 | */ |
michael@0 | 3312 | class TypeConstraintClearDefiniteSingle : public TypeConstraint |
michael@0 | 3313 | { |
michael@0 | 3314 | public: |
michael@0 | 3315 | TypeObject *object; |
michael@0 | 3316 | |
michael@0 | 3317 | TypeConstraintClearDefiniteSingle(TypeObject *object) |
michael@0 | 3318 | : object(object) |
michael@0 | 3319 | {} |
michael@0 | 3320 | |
michael@0 | 3321 | const char *kind() { return "clearDefiniteSingle"; } |
michael@0 | 3322 | |
michael@0 | 3323 | void newType(JSContext *cx, TypeSet *source, Type type) { |
michael@0 | 3324 | if (object->flags() & OBJECT_FLAG_ADDENDUM_CLEARED) |
michael@0 | 3325 | return; |
michael@0 | 3326 | |
michael@0 | 3327 | if (source->baseFlags() || source->getObjectCount() > 1) |
michael@0 | 3328 | object->clearAddendum(cx); |
michael@0 | 3329 | } |
michael@0 | 3330 | |
michael@0 | 3331 | bool sweep(TypeZone &zone, TypeConstraint **res) { |
michael@0 | 3332 | if (IsTypeObjectAboutToBeFinalized(&object)) |
michael@0 | 3333 | return false; |
michael@0 | 3334 | *res = zone.typeLifoAlloc.new_<TypeConstraintClearDefiniteSingle>(object); |
michael@0 | 3335 | return true; |
michael@0 | 3336 | } |
michael@0 | 3337 | }; |
michael@0 | 3338 | |
michael@0 | 3339 | bool |
michael@0 | 3340 | types::AddClearDefiniteFunctionUsesInScript(JSContext *cx, TypeObject *type, |
michael@0 | 3341 | JSScript *script, JSScript *calleeScript) |
michael@0 | 3342 | { |
michael@0 | 3343 | // Look for any uses of the specified calleeScript in type sets for |
michael@0 | 3344 | // |script|, and add constraints to ensure that if the type sets' contents |
michael@0 | 3345 | // change then the definite properties are cleared from the type. |
michael@0 | 3346 | // This ensures that the inlining performed when the definite properties |
michael@0 | 3347 | // analysis was done is stable. |
michael@0 | 3348 | |
michael@0 | 3349 | TypeObjectKey *calleeKey = Type::ObjectType(calleeScript->functionNonDelazifying()).objectKey(); |
michael@0 | 3350 | |
michael@0 | 3351 | unsigned count = TypeScript::NumTypeSets(script); |
michael@0 | 3352 | StackTypeSet *typeArray = script->types->typeArray(); |
michael@0 | 3353 | |
michael@0 | 3354 | for (unsigned i = 0; i < count; i++) { |
michael@0 | 3355 | StackTypeSet *types = &typeArray[i]; |
michael@0 | 3356 | if (!types->unknownObject() && types->getObjectCount() == 1) { |
michael@0 | 3357 | if (calleeKey != types->getObject(0)) { |
michael@0 | 3358 | // Also check if the object is the Function.call or |
michael@0 | 3359 | // Function.apply native. IonBuilder uses the presence of these |
michael@0 | 3360 | // functions during inlining. |
michael@0 | 3361 | JSObject *singleton = types->getSingleObject(0); |
michael@0 | 3362 | if (!singleton || !singleton->is<JSFunction>()) |
michael@0 | 3363 | continue; |
michael@0 | 3364 | JSFunction *fun = &singleton->as<JSFunction>(); |
michael@0 | 3365 | if (!fun->isNative()) |
michael@0 | 3366 | continue; |
michael@0 | 3367 | if (fun->native() != js_fun_call && fun->native() != js_fun_apply) |
michael@0 | 3368 | continue; |
michael@0 | 3369 | } |
michael@0 | 3370 | // This is a type set that might have been used when inlining |
michael@0 | 3371 | // |calleeScript| into |script|. |
michael@0 | 3372 | if (!types->addConstraint(cx, cx->typeLifoAlloc().new_<TypeConstraintClearDefiniteSingle>(type))) |
michael@0 | 3373 | return false; |
michael@0 | 3374 | } |
michael@0 | 3375 | } |
michael@0 | 3376 | |
michael@0 | 3377 | return true; |
michael@0 | 3378 | } |
michael@0 | 3379 | |
michael@0 | 3380 | /* |
michael@0 | 3381 | * Either make the newScript information for type when it is constructed |
michael@0 | 3382 | * by the specified script, or regenerate the constraints for an existing |
michael@0 | 3383 | * newScript on the type after they were cleared by a GC. |
michael@0 | 3384 | */ |
michael@0 | 3385 | static void |
michael@0 | 3386 | CheckNewScriptProperties(JSContext *cx, TypeObject *type, JSFunction *fun) |
michael@0 | 3387 | { |
michael@0 | 3388 | JS_ASSERT(cx->compartment()->activeAnalysis); |
michael@0 | 3389 | |
michael@0 | 3390 | #ifdef JS_ION |
michael@0 | 3391 | if (type->unknownProperties()) |
michael@0 | 3392 | return; |
michael@0 | 3393 | |
michael@0 | 3394 | /* Strawman object to add properties to and watch for duplicates. */ |
michael@0 | 3395 | RootedObject baseobj(cx, NewBuiltinClassInstance(cx, &JSObject::class_, gc::FINALIZE_OBJECT16)); |
michael@0 | 3396 | if (!baseobj) |
michael@0 | 3397 | return; |
michael@0 | 3398 | |
michael@0 | 3399 | Vector<TypeNewScript::Initializer> initializerList(cx); |
michael@0 | 3400 | |
michael@0 | 3401 | if (!jit::AnalyzeNewScriptProperties(cx, fun, type, baseobj, &initializerList) || |
michael@0 | 3402 | baseobj->slotSpan() == 0 || |
michael@0 | 3403 | !!(type->flags() & OBJECT_FLAG_ADDENDUM_CLEARED)) |
michael@0 | 3404 | { |
michael@0 | 3405 | if (type->hasNewScript()) |
michael@0 | 3406 | type->clearAddendum(cx); |
michael@0 | 3407 | return; |
michael@0 | 3408 | } |
michael@0 | 3409 | |
michael@0 | 3410 | /* |
michael@0 | 3411 | * If the type already has a new script, we are just regenerating the type |
michael@0 | 3412 | * constraints and don't need to make another TypeNewScript. Make sure that |
michael@0 | 3413 | * the properties added to baseobj match the type's definite properties. |
michael@0 | 3414 | */ |
michael@0 | 3415 | if (type->hasNewScript()) { |
michael@0 | 3416 | if (!type->matchDefiniteProperties(baseobj)) |
michael@0 | 3417 | type->clearAddendum(cx); |
michael@0 | 3418 | return; |
michael@0 | 3419 | } |
michael@0 | 3420 | JS_ASSERT(!type->hasNewScript()); |
michael@0 | 3421 | JS_ASSERT(!(type->flags() & OBJECT_FLAG_ADDENDUM_CLEARED)); |
michael@0 | 3422 | |
michael@0 | 3423 | gc::AllocKind kind = gc::GetGCObjectKind(baseobj->slotSpan()); |
michael@0 | 3424 | |
michael@0 | 3425 | /* We should not have overflowed the maximum number of fixed slots for an object. */ |
michael@0 | 3426 | JS_ASSERT(gc::GetGCKindSlots(kind) >= baseobj->slotSpan()); |
michael@0 | 3427 | |
michael@0 | 3428 | TypeNewScript::Initializer done(TypeNewScript::Initializer::DONE, 0); |
michael@0 | 3429 | |
michael@0 | 3430 | /* |
michael@0 | 3431 | * The base object may have been created with a different finalize kind |
michael@0 | 3432 | * than we will use for subsequent new objects. Generate an object with the |
michael@0 | 3433 | * appropriate final shape. |
michael@0 | 3434 | */ |
michael@0 | 3435 | Rooted<TypeObject *> rootedType(cx, type); |
michael@0 | 3436 | RootedShape shape(cx, baseobj->lastProperty()); |
michael@0 | 3437 | baseobj = NewReshapedObject(cx, rootedType, baseobj->getParent(), kind, shape, MaybeSingletonObject); |
michael@0 | 3438 | if (!baseobj || |
michael@0 | 3439 | !type->addDefiniteProperties(cx, baseobj) || |
michael@0 | 3440 | !initializerList.append(done)) |
michael@0 | 3441 | { |
michael@0 | 3442 | return; |
michael@0 | 3443 | } |
michael@0 | 3444 | |
michael@0 | 3445 | size_t numBytes = sizeof(TypeNewScript) |
michael@0 | 3446 | + (initializerList.length() * sizeof(TypeNewScript::Initializer)); |
michael@0 | 3447 | TypeNewScript *newScript = (TypeNewScript *) cx->calloc_(numBytes); |
michael@0 | 3448 | if (!newScript) |
michael@0 | 3449 | return; |
michael@0 | 3450 | |
michael@0 | 3451 | new (newScript) TypeNewScript(); |
michael@0 | 3452 | |
michael@0 | 3453 | type->setAddendum(newScript); |
michael@0 | 3454 | |
michael@0 | 3455 | newScript->fun = fun; |
michael@0 | 3456 | newScript->templateObject = baseobj; |
michael@0 | 3457 | |
michael@0 | 3458 | newScript->initializerList = (TypeNewScript::Initializer *) |
michael@0 | 3459 | ((char *) newScript + sizeof(TypeNewScript)); |
michael@0 | 3460 | PodCopy(newScript->initializerList, |
michael@0 | 3461 | initializerList.begin(), |
michael@0 | 3462 | initializerList.length()); |
michael@0 | 3463 | #endif // JS_ION |
michael@0 | 3464 | } |
michael@0 | 3465 | |
michael@0 | 3466 | ///////////////////////////////////////////////////////////////////// |
michael@0 | 3467 | // Interface functions |
michael@0 | 3468 | ///////////////////////////////////////////////////////////////////// |
michael@0 | 3469 | |
michael@0 | 3470 | void |
michael@0 | 3471 | types::TypeMonitorCallSlow(JSContext *cx, JSObject *callee, const CallArgs &args, |
michael@0 | 3472 | bool constructing) |
michael@0 | 3473 | { |
michael@0 | 3474 | unsigned nargs = callee->as<JSFunction>().nargs(); |
michael@0 | 3475 | JSScript *script = callee->as<JSFunction>().nonLazyScript(); |
michael@0 | 3476 | |
michael@0 | 3477 | if (!constructing) |
michael@0 | 3478 | TypeScript::SetThis(cx, script, args.thisv()); |
michael@0 | 3479 | |
michael@0 | 3480 | /* |
michael@0 | 3481 | * Add constraints going up to the minimum of the actual and formal count. |
michael@0 | 3482 | * If there are more actuals than formals the later values can only be |
michael@0 | 3483 | * accessed through the arguments object, which is monitored. |
michael@0 | 3484 | */ |
michael@0 | 3485 | unsigned arg = 0; |
michael@0 | 3486 | for (; arg < args.length() && arg < nargs; arg++) |
michael@0 | 3487 | TypeScript::SetArgument(cx, script, arg, args[arg]); |
michael@0 | 3488 | |
michael@0 | 3489 | /* Watch for fewer actuals than formals to the call. */ |
michael@0 | 3490 | for (; arg < nargs; arg++) |
michael@0 | 3491 | TypeScript::SetArgument(cx, script, arg, UndefinedValue()); |
michael@0 | 3492 | } |
michael@0 | 3493 | |
michael@0 | 3494 | static inline bool |
michael@0 | 3495 | IsAboutToBeFinalized(TypeObjectKey *key) |
michael@0 | 3496 | { |
michael@0 | 3497 | /* Mask out the low bit indicating whether this is a type or JS object. */ |
michael@0 | 3498 | gc::Cell *tmp = reinterpret_cast<gc::Cell *>(uintptr_t(key) & ~1); |
michael@0 | 3499 | bool isAboutToBeFinalized = IsCellAboutToBeFinalized(&tmp); |
michael@0 | 3500 | JS_ASSERT(tmp == reinterpret_cast<gc::Cell *>(uintptr_t(key) & ~1)); |
michael@0 | 3501 | return isAboutToBeFinalized; |
michael@0 | 3502 | } |
michael@0 | 3503 | |
michael@0 | 3504 | void |
michael@0 | 3505 | types::FillBytecodeTypeMap(JSScript *script, uint32_t *bytecodeMap) |
michael@0 | 3506 | { |
michael@0 | 3507 | uint32_t added = 0; |
michael@0 | 3508 | for (jsbytecode *pc = script->code(); pc < script->codeEnd(); pc += GetBytecodeLength(pc)) { |
michael@0 | 3509 | JSOp op = JSOp(*pc); |
michael@0 | 3510 | if (js_CodeSpec[op].format & JOF_TYPESET) { |
michael@0 | 3511 | bytecodeMap[added++] = script->pcToOffset(pc); |
michael@0 | 3512 | if (added == script->nTypeSets()) |
michael@0 | 3513 | break; |
michael@0 | 3514 | } |
michael@0 | 3515 | } |
michael@0 | 3516 | JS_ASSERT(added == script->nTypeSets()); |
michael@0 | 3517 | } |
michael@0 | 3518 | |
michael@0 | 3519 | void |
michael@0 | 3520 | types::TypeMonitorResult(JSContext *cx, JSScript *script, jsbytecode *pc, const js::Value &rval) |
michael@0 | 3521 | { |
michael@0 | 3522 | /* Allow the non-TYPESET scenario to simplify stubs used in compound opcodes. */ |
michael@0 | 3523 | if (!(js_CodeSpec[*pc].format & JOF_TYPESET)) |
michael@0 | 3524 | return; |
michael@0 | 3525 | |
michael@0 | 3526 | if (!script->hasBaselineScript()) |
michael@0 | 3527 | return; |
michael@0 | 3528 | |
michael@0 | 3529 | AutoEnterAnalysis enter(cx); |
michael@0 | 3530 | |
michael@0 | 3531 | Type type = GetValueType(rval); |
michael@0 | 3532 | StackTypeSet *types = TypeScript::BytecodeTypes(script, pc); |
michael@0 | 3533 | if (types->hasType(type)) |
michael@0 | 3534 | return; |
michael@0 | 3535 | |
michael@0 | 3536 | InferSpew(ISpewOps, "bytecodeType: #%u:%05u: %s", |
michael@0 | 3537 | script->id(), script->pcToOffset(pc), TypeString(type)); |
michael@0 | 3538 | types->addType(cx, type); |
michael@0 | 3539 | } |
michael@0 | 3540 | |
michael@0 | 3541 | bool |
michael@0 | 3542 | types::UseNewTypeForClone(JSFunction *fun) |
michael@0 | 3543 | { |
michael@0 | 3544 | if (!fun->isInterpreted()) |
michael@0 | 3545 | return false; |
michael@0 | 3546 | |
michael@0 | 3547 | if (fun->hasScript() && fun->nonLazyScript()->shouldCloneAtCallsite()) |
michael@0 | 3548 | return true; |
michael@0 | 3549 | |
michael@0 | 3550 | if (fun->isArrow()) |
michael@0 | 3551 | return false; |
michael@0 | 3552 | |
michael@0 | 3553 | if (fun->hasSingletonType()) |
michael@0 | 3554 | return false; |
michael@0 | 3555 | |
michael@0 | 3556 | /* |
michael@0 | 3557 | * When a function is being used as a wrapper for another function, it |
michael@0 | 3558 | * improves precision greatly to distinguish between different instances of |
michael@0 | 3559 | * the wrapper; otherwise we will conflate much of the information about |
michael@0 | 3560 | * the wrapped functions. |
michael@0 | 3561 | * |
michael@0 | 3562 | * An important example is the Class.create function at the core of the |
michael@0 | 3563 | * Prototype.js library, which looks like: |
michael@0 | 3564 | * |
michael@0 | 3565 | * var Class = { |
michael@0 | 3566 | * create: function() { |
michael@0 | 3567 | * return function() { |
michael@0 | 3568 | * this.initialize.apply(this, arguments); |
michael@0 | 3569 | * } |
michael@0 | 3570 | * } |
michael@0 | 3571 | * }; |
michael@0 | 3572 | * |
michael@0 | 3573 | * Each instance of the innermost function will have a different wrapped |
michael@0 | 3574 | * initialize method. We capture this, along with similar cases, by looking |
michael@0 | 3575 | * for short scripts which use both .apply and arguments. For such scripts, |
michael@0 | 3576 | * whenever creating a new instance of the function we both give that |
michael@0 | 3577 | * instance a singleton type and clone the underlying script. |
michael@0 | 3578 | */ |
michael@0 | 3579 | |
michael@0 | 3580 | uint32_t begin, end; |
michael@0 | 3581 | if (fun->hasScript()) { |
michael@0 | 3582 | if (!fun->nonLazyScript()->usesArgumentsAndApply()) |
michael@0 | 3583 | return false; |
michael@0 | 3584 | begin = fun->nonLazyScript()->sourceStart(); |
michael@0 | 3585 | end = fun->nonLazyScript()->sourceEnd(); |
michael@0 | 3586 | } else { |
michael@0 | 3587 | if (!fun->lazyScript()->usesArgumentsAndApply()) |
michael@0 | 3588 | return false; |
michael@0 | 3589 | begin = fun->lazyScript()->begin(); |
michael@0 | 3590 | end = fun->lazyScript()->end(); |
michael@0 | 3591 | } |
michael@0 | 3592 | |
michael@0 | 3593 | return end - begin <= 100; |
michael@0 | 3594 | } |
michael@0 | 3595 | ///////////////////////////////////////////////////////////////////// |
michael@0 | 3596 | // TypeScript |
michael@0 | 3597 | ///////////////////////////////////////////////////////////////////// |
michael@0 | 3598 | |
michael@0 | 3599 | bool |
michael@0 | 3600 | JSScript::makeTypes(JSContext *cx) |
michael@0 | 3601 | { |
michael@0 | 3602 | JS_ASSERT(!types); |
michael@0 | 3603 | |
michael@0 | 3604 | AutoEnterAnalysis enter(cx); |
michael@0 | 3605 | |
michael@0 | 3606 | unsigned count = TypeScript::NumTypeSets(this); |
michael@0 | 3607 | |
michael@0 | 3608 | TypeScript *typeScript = (TypeScript *) |
michael@0 | 3609 | cx->calloc_(TypeScript::SizeIncludingTypeArray(count)); |
michael@0 | 3610 | if (!typeScript) |
michael@0 | 3611 | return false; |
michael@0 | 3612 | |
michael@0 | 3613 | new(typeScript) TypeScript(); |
michael@0 | 3614 | |
michael@0 | 3615 | TypeSet *typeArray = typeScript->typeArray(); |
michael@0 | 3616 | |
michael@0 | 3617 | for (unsigned i = 0; i < count; i++) |
michael@0 | 3618 | new (&typeArray[i]) StackTypeSet(); |
michael@0 | 3619 | |
michael@0 | 3620 | types = typeScript; |
michael@0 | 3621 | |
michael@0 | 3622 | #ifdef DEBUG |
michael@0 | 3623 | for (unsigned i = 0; i < nTypeSets(); i++) { |
michael@0 | 3624 | InferSpew(ISpewOps, "typeSet: %sT%p%s bytecode%u #%u", |
michael@0 | 3625 | InferSpewColor(&typeArray[i]), &typeArray[i], InferSpewColorReset(), |
michael@0 | 3626 | i, id()); |
michael@0 | 3627 | } |
michael@0 | 3628 | TypeSet *thisTypes = TypeScript::ThisTypes(this); |
michael@0 | 3629 | InferSpew(ISpewOps, "typeSet: %sT%p%s this #%u", |
michael@0 | 3630 | InferSpewColor(thisTypes), thisTypes, InferSpewColorReset(), |
michael@0 | 3631 | id()); |
michael@0 | 3632 | unsigned nargs = functionNonDelazifying() ? functionNonDelazifying()->nargs() : 0; |
michael@0 | 3633 | for (unsigned i = 0; i < nargs; i++) { |
michael@0 | 3634 | TypeSet *types = TypeScript::ArgTypes(this, i); |
michael@0 | 3635 | InferSpew(ISpewOps, "typeSet: %sT%p%s arg%u #%u", |
michael@0 | 3636 | InferSpewColor(types), types, InferSpewColorReset(), |
michael@0 | 3637 | i, id()); |
michael@0 | 3638 | } |
michael@0 | 3639 | #endif |
michael@0 | 3640 | |
michael@0 | 3641 | return true; |
michael@0 | 3642 | } |
michael@0 | 3643 | |
michael@0 | 3644 | /* static */ bool |
michael@0 | 3645 | JSFunction::setTypeForScriptedFunction(ExclusiveContext *cx, HandleFunction fun, |
michael@0 | 3646 | bool singleton /* = false */) |
michael@0 | 3647 | { |
michael@0 | 3648 | if (singleton) { |
michael@0 | 3649 | if (!setSingletonType(cx, fun)) |
michael@0 | 3650 | return false; |
michael@0 | 3651 | } else { |
michael@0 | 3652 | RootedObject funProto(cx, fun->getProto()); |
michael@0 | 3653 | TypeObject *type = |
michael@0 | 3654 | cx->compartment()->types.newTypeObject(cx, &JSFunction::class_, funProto); |
michael@0 | 3655 | if (!type) |
michael@0 | 3656 | return false; |
michael@0 | 3657 | |
michael@0 | 3658 | fun->setType(type); |
michael@0 | 3659 | type->interpretedFunction = fun; |
michael@0 | 3660 | } |
michael@0 | 3661 | |
michael@0 | 3662 | return true; |
michael@0 | 3663 | } |
michael@0 | 3664 | |
michael@0 | 3665 | ///////////////////////////////////////////////////////////////////// |
michael@0 | 3666 | // JSObject |
michael@0 | 3667 | ///////////////////////////////////////////////////////////////////// |
michael@0 | 3668 | |
michael@0 | 3669 | bool |
michael@0 | 3670 | JSObject::shouldSplicePrototype(JSContext *cx) |
michael@0 | 3671 | { |
michael@0 | 3672 | /* |
michael@0 | 3673 | * During bootstrapping, if inference is enabled we need to make sure not |
michael@0 | 3674 | * to splice a new prototype in for Function.prototype or the global |
michael@0 | 3675 | * object if their __proto__ had previously been set to null, as this |
michael@0 | 3676 | * will change the prototype for all other objects with the same type. |
michael@0 | 3677 | */ |
michael@0 | 3678 | if (getProto() != nullptr) |
michael@0 | 3679 | return false; |
michael@0 | 3680 | return hasSingletonType(); |
michael@0 | 3681 | } |
michael@0 | 3682 | |
michael@0 | 3683 | bool |
michael@0 | 3684 | JSObject::splicePrototype(JSContext *cx, const Class *clasp, Handle<TaggedProto> proto) |
michael@0 | 3685 | { |
michael@0 | 3686 | JS_ASSERT(cx->compartment() == compartment()); |
michael@0 | 3687 | |
michael@0 | 3688 | RootedObject self(cx, this); |
michael@0 | 3689 | |
michael@0 | 3690 | /* |
michael@0 | 3691 | * For singleton types representing only a single JSObject, the proto |
michael@0 | 3692 | * can be rearranged as needed without destroying type information for |
michael@0 | 3693 | * the old or new types. |
michael@0 | 3694 | */ |
michael@0 | 3695 | JS_ASSERT(self->hasSingletonType()); |
michael@0 | 3696 | |
michael@0 | 3697 | /* Inner objects may not appear on prototype chains. */ |
michael@0 | 3698 | JS_ASSERT_IF(proto.isObject(), !proto.toObject()->getClass()->ext.outerObject); |
michael@0 | 3699 | |
michael@0 | 3700 | /* |
michael@0 | 3701 | * Force type instantiation when splicing lazy types. This may fail, |
michael@0 | 3702 | * in which case inference will be disabled for the compartment. |
michael@0 | 3703 | */ |
michael@0 | 3704 | Rooted<TypeObject*> type(cx, self->getType(cx)); |
michael@0 | 3705 | if (!type) |
michael@0 | 3706 | return false; |
michael@0 | 3707 | Rooted<TypeObject*> protoType(cx, nullptr); |
michael@0 | 3708 | if (proto.isObject()) { |
michael@0 | 3709 | protoType = proto.toObject()->getType(cx); |
michael@0 | 3710 | if (!protoType) |
michael@0 | 3711 | return false; |
michael@0 | 3712 | } |
michael@0 | 3713 | |
michael@0 | 3714 | type->setClasp(clasp); |
michael@0 | 3715 | type->setProto(cx, proto); |
michael@0 | 3716 | return true; |
michael@0 | 3717 | } |
michael@0 | 3718 | |
michael@0 | 3719 | /* static */ TypeObject * |
michael@0 | 3720 | JSObject::makeLazyType(JSContext *cx, HandleObject obj) |
michael@0 | 3721 | { |
michael@0 | 3722 | JS_ASSERT(obj->hasLazyType()); |
michael@0 | 3723 | JS_ASSERT(cx->compartment() == obj->compartment()); |
michael@0 | 3724 | |
michael@0 | 3725 | /* De-lazification of functions can GC, so we need to do it up here. */ |
michael@0 | 3726 | if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpretedLazy()) { |
michael@0 | 3727 | RootedFunction fun(cx, &obj->as<JSFunction>()); |
michael@0 | 3728 | if (!fun->getOrCreateScript(cx)) |
michael@0 | 3729 | return nullptr; |
michael@0 | 3730 | } |
michael@0 | 3731 | |
michael@0 | 3732 | // Find flags which need to be specified immediately on the object. |
michael@0 | 3733 | // Don't track whether singletons are packed. |
michael@0 | 3734 | TypeObjectFlags initialFlags = OBJECT_FLAG_NON_PACKED; |
michael@0 | 3735 | |
michael@0 | 3736 | if (obj->lastProperty()->hasObjectFlag(BaseShape::ITERATED_SINGLETON)) |
michael@0 | 3737 | initialFlags |= OBJECT_FLAG_ITERATED; |
michael@0 | 3738 | |
michael@0 | 3739 | if (obj->isIndexed()) |
michael@0 | 3740 | initialFlags |= OBJECT_FLAG_SPARSE_INDEXES; |
michael@0 | 3741 | |
michael@0 | 3742 | if (obj->is<ArrayObject>() && obj->as<ArrayObject>().length() > INT32_MAX) |
michael@0 | 3743 | initialFlags |= OBJECT_FLAG_LENGTH_OVERFLOW; |
michael@0 | 3744 | |
michael@0 | 3745 | Rooted<TaggedProto> proto(cx, obj->getTaggedProto()); |
michael@0 | 3746 | TypeObject *type = cx->compartment()->types.newTypeObject(cx, obj->getClass(), proto, initialFlags); |
michael@0 | 3747 | if (!type) |
michael@0 | 3748 | return nullptr; |
michael@0 | 3749 | |
michael@0 | 3750 | AutoEnterAnalysis enter(cx); |
michael@0 | 3751 | |
michael@0 | 3752 | /* Fill in the type according to the state of this object. */ |
michael@0 | 3753 | |
michael@0 | 3754 | type->initSingleton(obj); |
michael@0 | 3755 | |
michael@0 | 3756 | if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) |
michael@0 | 3757 | type->interpretedFunction = &obj->as<JSFunction>(); |
michael@0 | 3758 | |
michael@0 | 3759 | obj->type_ = type; |
michael@0 | 3760 | |
michael@0 | 3761 | return type; |
michael@0 | 3762 | } |
michael@0 | 3763 | |
michael@0 | 3764 | /* static */ inline HashNumber |
michael@0 | 3765 | TypeObjectWithNewScriptEntry::hash(const Lookup &lookup) |
michael@0 | 3766 | { |
michael@0 | 3767 | return PointerHasher<JSObject *, 3>::hash(lookup.hashProto.raw()) ^ |
michael@0 | 3768 | PointerHasher<const Class *, 3>::hash(lookup.clasp) ^ |
michael@0 | 3769 | PointerHasher<JSFunction *, 3>::hash(lookup.newFunction); |
michael@0 | 3770 | } |
michael@0 | 3771 | |
michael@0 | 3772 | /* static */ inline bool |
michael@0 | 3773 | TypeObjectWithNewScriptEntry::match(const TypeObjectWithNewScriptEntry &key, const Lookup &lookup) |
michael@0 | 3774 | { |
michael@0 | 3775 | return key.object->proto() == lookup.matchProto && |
michael@0 | 3776 | key.object->clasp() == lookup.clasp && |
michael@0 | 3777 | key.newFunction == lookup.newFunction; |
michael@0 | 3778 | } |
michael@0 | 3779 | |
michael@0 | 3780 | #ifdef DEBUG |
michael@0 | 3781 | bool |
michael@0 | 3782 | JSObject::hasNewType(const Class *clasp, TypeObject *type) |
michael@0 | 3783 | { |
michael@0 | 3784 | TypeObjectWithNewScriptSet &table = compartment()->newTypeObjects; |
michael@0 | 3785 | |
michael@0 | 3786 | if (!table.initialized()) |
michael@0 | 3787 | return false; |
michael@0 | 3788 | |
michael@0 | 3789 | TypeObjectWithNewScriptSet::Ptr p = table.lookup(TypeObjectWithNewScriptSet::Lookup(clasp, this, nullptr)); |
michael@0 | 3790 | return p && p->object == type; |
michael@0 | 3791 | } |
michael@0 | 3792 | #endif /* DEBUG */ |
michael@0 | 3793 | |
michael@0 | 3794 | /* static */ bool |
michael@0 | 3795 | JSObject::setNewTypeUnknown(JSContext *cx, const Class *clasp, HandleObject obj) |
michael@0 | 3796 | { |
michael@0 | 3797 | if (!obj->setFlag(cx, js::BaseShape::NEW_TYPE_UNKNOWN)) |
michael@0 | 3798 | return false; |
michael@0 | 3799 | |
michael@0 | 3800 | /* |
michael@0 | 3801 | * If the object already has a new type, mark that type as unknown. It will |
michael@0 | 3802 | * not have the SETS_MARKED_UNKNOWN bit set, so may require a type set |
michael@0 | 3803 | * crawl if prototypes of the object change dynamically in the future. |
michael@0 | 3804 | */ |
michael@0 | 3805 | TypeObjectWithNewScriptSet &table = cx->compartment()->newTypeObjects; |
michael@0 | 3806 | if (table.initialized()) { |
michael@0 | 3807 | if (TypeObjectWithNewScriptSet::Ptr p = table.lookup(TypeObjectWithNewScriptSet::Lookup(clasp, obj.get(), nullptr))) |
michael@0 | 3808 | MarkTypeObjectUnknownProperties(cx, p->object); |
michael@0 | 3809 | } |
michael@0 | 3810 | |
michael@0 | 3811 | return true; |
michael@0 | 3812 | } |
michael@0 | 3813 | |
michael@0 | 3814 | #ifdef JSGC_GENERATIONAL |
michael@0 | 3815 | /* |
michael@0 | 3816 | * This class is used to add a post barrier on the newTypeObjects set, as the |
michael@0 | 3817 | * key is calculated from a prototype object which may be moved by generational |
michael@0 | 3818 | * GC. |
michael@0 | 3819 | */ |
michael@0 | 3820 | class NewTypeObjectsSetRef : public BufferableRef |
michael@0 | 3821 | { |
michael@0 | 3822 | TypeObjectWithNewScriptSet *set; |
michael@0 | 3823 | const Class *clasp; |
michael@0 | 3824 | JSObject *proto; |
michael@0 | 3825 | JSFunction *newFunction; |
michael@0 | 3826 | |
michael@0 | 3827 | public: |
michael@0 | 3828 | NewTypeObjectsSetRef(TypeObjectWithNewScriptSet *s, const Class *clasp, JSObject *proto, JSFunction *newFunction) |
michael@0 | 3829 | : set(s), clasp(clasp), proto(proto), newFunction(newFunction) |
michael@0 | 3830 | {} |
michael@0 | 3831 | |
michael@0 | 3832 | void mark(JSTracer *trc) { |
michael@0 | 3833 | JSObject *prior = proto; |
michael@0 | 3834 | trc->setTracingLocation(&*prior); |
michael@0 | 3835 | Mark(trc, &proto, "newTypeObjects set prototype"); |
michael@0 | 3836 | if (prior == proto) |
michael@0 | 3837 | return; |
michael@0 | 3838 | |
michael@0 | 3839 | TypeObjectWithNewScriptSet::Ptr p = set->lookup(TypeObjectWithNewScriptSet::Lookup(clasp, prior, proto, newFunction)); |
michael@0 | 3840 | JS_ASSERT(p); // newTypeObjects set must still contain original entry. |
michael@0 | 3841 | |
michael@0 | 3842 | set->rekeyAs(TypeObjectWithNewScriptSet::Lookup(clasp, prior, proto, newFunction), |
michael@0 | 3843 | TypeObjectWithNewScriptSet::Lookup(clasp, proto, newFunction), *p); |
michael@0 | 3844 | } |
michael@0 | 3845 | }; |
michael@0 | 3846 | #endif |
michael@0 | 3847 | |
michael@0 | 3848 | TypeObject * |
michael@0 | 3849 | ExclusiveContext::getNewType(const Class *clasp, TaggedProto proto, JSFunction *fun) |
michael@0 | 3850 | { |
michael@0 | 3851 | JS_ASSERT_IF(fun, proto.isObject()); |
michael@0 | 3852 | JS_ASSERT_IF(proto.isObject(), isInsideCurrentCompartment(proto.toObject())); |
michael@0 | 3853 | |
michael@0 | 3854 | TypeObjectWithNewScriptSet &newTypeObjects = compartment()->newTypeObjects; |
michael@0 | 3855 | |
michael@0 | 3856 | if (!newTypeObjects.initialized() && !newTypeObjects.init()) |
michael@0 | 3857 | return nullptr; |
michael@0 | 3858 | |
michael@0 | 3859 | // Canonicalize new functions to use the original one associated with its script. |
michael@0 | 3860 | if (fun) { |
michael@0 | 3861 | if (fun->hasScript()) |
michael@0 | 3862 | fun = fun->nonLazyScript()->functionNonDelazifying(); |
michael@0 | 3863 | else if (fun->isInterpretedLazy() && !fun->isSelfHostedBuiltin()) |
michael@0 | 3864 | fun = fun->lazyScript()->functionNonDelazifying(); |
michael@0 | 3865 | else |
michael@0 | 3866 | fun = nullptr; |
michael@0 | 3867 | } |
michael@0 | 3868 | |
michael@0 | 3869 | TypeObjectWithNewScriptSet::AddPtr p = |
michael@0 | 3870 | newTypeObjects.lookupForAdd(TypeObjectWithNewScriptSet::Lookup(clasp, proto, fun)); |
michael@0 | 3871 | if (p) { |
michael@0 | 3872 | TypeObject *type = p->object; |
michael@0 | 3873 | JS_ASSERT(type->clasp() == clasp); |
michael@0 | 3874 | JS_ASSERT(type->proto() == proto); |
michael@0 | 3875 | JS_ASSERT_IF(type->hasNewScript(), type->newScript()->fun == fun); |
michael@0 | 3876 | return type; |
michael@0 | 3877 | } |
michael@0 | 3878 | |
michael@0 | 3879 | AutoEnterAnalysis enter(this); |
michael@0 | 3880 | |
michael@0 | 3881 | if (proto.isObject() && !proto.toObject()->setDelegate(this)) |
michael@0 | 3882 | return nullptr; |
michael@0 | 3883 | |
michael@0 | 3884 | TypeObjectFlags initialFlags = 0; |
michael@0 | 3885 | if (!proto.isObject() || proto.toObject()->lastProperty()->hasObjectFlag(BaseShape::NEW_TYPE_UNKNOWN)) { |
michael@0 | 3886 | // The new type is not present in any type sets, so mark the object as |
michael@0 | 3887 | // unknown in all type sets it appears in. This allows the prototype of |
michael@0 | 3888 | // such objects to mutate freely without triggering an expensive walk of |
michael@0 | 3889 | // the compartment's type sets. (While scripts normally don't mutate |
michael@0 | 3890 | // __proto__, the browser will for proxies and such, and we need to |
michael@0 | 3891 | // accommodate this behavior). |
michael@0 | 3892 | initialFlags = OBJECT_FLAG_UNKNOWN_MASK | OBJECT_FLAG_SETS_MARKED_UNKNOWN; |
michael@0 | 3893 | } |
michael@0 | 3894 | |
michael@0 | 3895 | Rooted<TaggedProto> protoRoot(this, proto); |
michael@0 | 3896 | TypeObject *type = compartment()->types.newTypeObject(this, clasp, protoRoot, initialFlags); |
michael@0 | 3897 | if (!type) |
michael@0 | 3898 | return nullptr; |
michael@0 | 3899 | |
michael@0 | 3900 | if (!newTypeObjects.add(p, TypeObjectWithNewScriptEntry(type, fun))) |
michael@0 | 3901 | return nullptr; |
michael@0 | 3902 | |
michael@0 | 3903 | #ifdef JSGC_GENERATIONAL |
michael@0 | 3904 | if (proto.isObject() && hasNursery() && nursery().isInside(proto.toObject())) { |
michael@0 | 3905 | asJSContext()->runtime()->gcStoreBuffer.putGeneric( |
michael@0 | 3906 | NewTypeObjectsSetRef(&newTypeObjects, clasp, proto.toObject(), fun)); |
michael@0 | 3907 | } |
michael@0 | 3908 | #endif |
michael@0 | 3909 | |
michael@0 | 3910 | if (proto.isObject()) { |
michael@0 | 3911 | RootedObject obj(this, proto.toObject()); |
michael@0 | 3912 | |
michael@0 | 3913 | if (fun) |
michael@0 | 3914 | CheckNewScriptProperties(asJSContext(), type, fun); |
michael@0 | 3915 | |
michael@0 | 3916 | /* |
michael@0 | 3917 | * Some builtin objects have slotful native properties baked in at |
michael@0 | 3918 | * creation via the Shape::{insert,get}initialShape mechanism. Since |
michael@0 | 3919 | * these properties are never explicitly defined on new objects, update |
michael@0 | 3920 | * the type information for them here. |
michael@0 | 3921 | */ |
michael@0 | 3922 | |
michael@0 | 3923 | if (obj->is<RegExpObject>()) { |
michael@0 | 3924 | AddTypePropertyId(this, type, NameToId(names().source), Type::StringType()); |
michael@0 | 3925 | AddTypePropertyId(this, type, NameToId(names().global), Type::BooleanType()); |
michael@0 | 3926 | AddTypePropertyId(this, type, NameToId(names().ignoreCase), Type::BooleanType()); |
michael@0 | 3927 | AddTypePropertyId(this, type, NameToId(names().multiline), Type::BooleanType()); |
michael@0 | 3928 | AddTypePropertyId(this, type, NameToId(names().sticky), Type::BooleanType()); |
michael@0 | 3929 | AddTypePropertyId(this, type, NameToId(names().lastIndex), Type::Int32Type()); |
michael@0 | 3930 | } |
michael@0 | 3931 | |
michael@0 | 3932 | if (obj->is<StringObject>()) |
michael@0 | 3933 | AddTypePropertyId(this, type, NameToId(names().length), Type::Int32Type()); |
michael@0 | 3934 | |
michael@0 | 3935 | if (obj->is<ErrorObject>()) { |
michael@0 | 3936 | AddTypePropertyId(this, type, NameToId(names().fileName), Type::StringType()); |
michael@0 | 3937 | AddTypePropertyId(this, type, NameToId(names().lineNumber), Type::Int32Type()); |
michael@0 | 3938 | AddTypePropertyId(this, type, NameToId(names().columnNumber), Type::Int32Type()); |
michael@0 | 3939 | AddTypePropertyId(this, type, NameToId(names().stack), Type::StringType()); |
michael@0 | 3940 | } |
michael@0 | 3941 | } |
michael@0 | 3942 | |
michael@0 | 3943 | return type; |
michael@0 | 3944 | } |
michael@0 | 3945 | |
michael@0 | 3946 | #if defined(JSGC_GENERATIONAL) && defined(JS_GC_ZEAL) |
michael@0 | 3947 | void |
michael@0 | 3948 | JSCompartment::checkNewTypeObjectTableAfterMovingGC() |
michael@0 | 3949 | { |
michael@0 | 3950 | /* |
michael@0 | 3951 | * Assert that the postbarriers have worked and that nothing is left in |
michael@0 | 3952 | * newTypeObjects that points into the nursery, and that the hash table |
michael@0 | 3953 | * entries are discoverable. |
michael@0 | 3954 | */ |
michael@0 | 3955 | JS::shadow::Runtime *rt = JS::shadow::Runtime::asShadowRuntime(runtimeFromMainThread()); |
michael@0 | 3956 | for (TypeObjectWithNewScriptSet::Enum e(newTypeObjects); !e.empty(); e.popFront()) { |
michael@0 | 3957 | TypeObjectWithNewScriptEntry entry = e.front(); |
michael@0 | 3958 | JS_ASSERT(!IsInsideNursery(rt, entry.newFunction)); |
michael@0 | 3959 | TaggedProto proto = entry.object->proto(); |
michael@0 | 3960 | JS_ASSERT_IF(proto.isObject(), !IsInsideNursery(rt, proto.toObject())); |
michael@0 | 3961 | TypeObjectWithNewScriptEntry::Lookup |
michael@0 | 3962 | lookup(entry.object->clasp(), proto, entry.newFunction); |
michael@0 | 3963 | TypeObjectWithNewScriptSet::Ptr ptr = newTypeObjects.lookup(lookup); |
michael@0 | 3964 | JS_ASSERT(ptr.found() && &*ptr == &e.front()); |
michael@0 | 3965 | } |
michael@0 | 3966 | } |
michael@0 | 3967 | #endif |
michael@0 | 3968 | |
michael@0 | 3969 | TypeObject * |
michael@0 | 3970 | ExclusiveContext::getSingletonType(const Class *clasp, TaggedProto proto) |
michael@0 | 3971 | { |
michael@0 | 3972 | JS_ASSERT_IF(proto.isObject(), compartment() == proto.toObject()->compartment()); |
michael@0 | 3973 | |
michael@0 | 3974 | AutoEnterAnalysis enter(this); |
michael@0 | 3975 | |
michael@0 | 3976 | TypeObjectWithNewScriptSet &table = compartment()->lazyTypeObjects; |
michael@0 | 3977 | |
michael@0 | 3978 | if (!table.initialized() && !table.init()) |
michael@0 | 3979 | return nullptr; |
michael@0 | 3980 | |
michael@0 | 3981 | TypeObjectWithNewScriptSet::AddPtr p = table.lookupForAdd(TypeObjectWithNewScriptSet::Lookup(clasp, proto, nullptr)); |
michael@0 | 3982 | if (p) { |
michael@0 | 3983 | TypeObject *type = p->object; |
michael@0 | 3984 | JS_ASSERT(type->lazy()); |
michael@0 | 3985 | |
michael@0 | 3986 | return type; |
michael@0 | 3987 | } |
michael@0 | 3988 | |
michael@0 | 3989 | Rooted<TaggedProto> protoRoot(this, proto); |
michael@0 | 3990 | TypeObject *type = compartment()->types.newTypeObject(this, clasp, protoRoot); |
michael@0 | 3991 | if (!type) |
michael@0 | 3992 | return nullptr; |
michael@0 | 3993 | |
michael@0 | 3994 | if (!table.add(p, TypeObjectWithNewScriptEntry(type, nullptr))) |
michael@0 | 3995 | return nullptr; |
michael@0 | 3996 | |
michael@0 | 3997 | type->initSingleton((JSObject *) TypeObject::LAZY_SINGLETON); |
michael@0 | 3998 | MOZ_ASSERT(type->singleton(), "created type must be a proper singleton"); |
michael@0 | 3999 | |
michael@0 | 4000 | return type; |
michael@0 | 4001 | } |
michael@0 | 4002 | |
michael@0 | 4003 | ///////////////////////////////////////////////////////////////////// |
michael@0 | 4004 | // Tracing |
michael@0 | 4005 | ///////////////////////////////////////////////////////////////////// |
michael@0 | 4006 | |
michael@0 | 4007 | void |
michael@0 | 4008 | ConstraintTypeSet::sweep(Zone *zone, bool *oom) |
michael@0 | 4009 | { |
michael@0 | 4010 | /* |
michael@0 | 4011 | * Purge references to type objects that are no longer live. Type sets hold |
michael@0 | 4012 | * only weak references. For type sets containing more than one object, |
michael@0 | 4013 | * live entries in the object hash need to be copied to the zone's |
michael@0 | 4014 | * new arena. |
michael@0 | 4015 | */ |
michael@0 | 4016 | unsigned objectCount = baseObjectCount(); |
michael@0 | 4017 | if (objectCount >= 2) { |
michael@0 | 4018 | unsigned oldCapacity = HashSetCapacity(objectCount); |
michael@0 | 4019 | TypeObjectKey **oldArray = objectSet; |
michael@0 | 4020 | |
michael@0 | 4021 | clearObjects(); |
michael@0 | 4022 | objectCount = 0; |
michael@0 | 4023 | for (unsigned i = 0; i < oldCapacity; i++) { |
michael@0 | 4024 | TypeObjectKey *object = oldArray[i]; |
michael@0 | 4025 | if (object && !IsAboutToBeFinalized(object)) { |
michael@0 | 4026 | TypeObjectKey **pentry = |
michael@0 | 4027 | HashSetInsert<TypeObjectKey *,TypeObjectKey,TypeObjectKey> |
michael@0 | 4028 | (zone->types.typeLifoAlloc, objectSet, objectCount, object); |
michael@0 | 4029 | if (pentry) { |
michael@0 | 4030 | *pentry = object; |
michael@0 | 4031 | } else { |
michael@0 | 4032 | *oom = true; |
michael@0 | 4033 | flags |= TYPE_FLAG_ANYOBJECT; |
michael@0 | 4034 | clearObjects(); |
michael@0 | 4035 | objectCount = 0; |
michael@0 | 4036 | break; |
michael@0 | 4037 | } |
michael@0 | 4038 | } |
michael@0 | 4039 | } |
michael@0 | 4040 | setBaseObjectCount(objectCount); |
michael@0 | 4041 | } else if (objectCount == 1) { |
michael@0 | 4042 | TypeObjectKey *object = (TypeObjectKey *) objectSet; |
michael@0 | 4043 | if (IsAboutToBeFinalized(object)) { |
michael@0 | 4044 | objectSet = nullptr; |
michael@0 | 4045 | setBaseObjectCount(0); |
michael@0 | 4046 | } |
michael@0 | 4047 | } |
michael@0 | 4048 | |
michael@0 | 4049 | /* |
michael@0 | 4050 | * Type constraints only hold weak references. Copy constraints referring |
michael@0 | 4051 | * to data that is still live into the zone's new arena. |
michael@0 | 4052 | */ |
michael@0 | 4053 | TypeConstraint *constraint = constraintList; |
michael@0 | 4054 | constraintList = nullptr; |
michael@0 | 4055 | while (constraint) { |
michael@0 | 4056 | TypeConstraint *copy; |
michael@0 | 4057 | if (constraint->sweep(zone->types, ©)) { |
michael@0 | 4058 | if (copy) { |
michael@0 | 4059 | copy->next = constraintList; |
michael@0 | 4060 | constraintList = copy; |
michael@0 | 4061 | } else { |
michael@0 | 4062 | *oom = true; |
michael@0 | 4063 | } |
michael@0 | 4064 | } |
michael@0 | 4065 | constraint = constraint->next; |
michael@0 | 4066 | } |
michael@0 | 4067 | } |
michael@0 | 4068 | |
michael@0 | 4069 | inline void |
michael@0 | 4070 | TypeObject::clearProperties() |
michael@0 | 4071 | { |
michael@0 | 4072 | setBasePropertyCount(0); |
michael@0 | 4073 | propertySet = nullptr; |
michael@0 | 4074 | } |
michael@0 | 4075 | |
michael@0 | 4076 | /* |
michael@0 | 4077 | * Before sweeping the arenas themselves, scan all type objects in a |
michael@0 | 4078 | * compartment to fixup weak references: property type sets referencing dead |
michael@0 | 4079 | * JS and type objects, and singleton JS objects whose type is not referenced |
michael@0 | 4080 | * elsewhere. This also releases memory associated with dead type objects, |
michael@0 | 4081 | * so that type objects do not need later finalization. |
michael@0 | 4082 | */ |
michael@0 | 4083 | inline void |
michael@0 | 4084 | TypeObject::sweep(FreeOp *fop, bool *oom) |
michael@0 | 4085 | { |
michael@0 | 4086 | if (!isMarked()) { |
michael@0 | 4087 | if (addendum) |
michael@0 | 4088 | fop->free_(addendum); |
michael@0 | 4089 | return; |
michael@0 | 4090 | } |
michael@0 | 4091 | |
michael@0 | 4092 | LifoAlloc &typeLifoAlloc = zone()->types.typeLifoAlloc; |
michael@0 | 4093 | |
michael@0 | 4094 | /* |
michael@0 | 4095 | * Properties were allocated from the old arena, and need to be copied over |
michael@0 | 4096 | * to the new one. |
michael@0 | 4097 | */ |
michael@0 | 4098 | unsigned propertyCount = basePropertyCount(); |
michael@0 | 4099 | if (propertyCount >= 2) { |
michael@0 | 4100 | unsigned oldCapacity = HashSetCapacity(propertyCount); |
michael@0 | 4101 | Property **oldArray = propertySet; |
michael@0 | 4102 | |
michael@0 | 4103 | clearProperties(); |
michael@0 | 4104 | propertyCount = 0; |
michael@0 | 4105 | for (unsigned i = 0; i < oldCapacity; i++) { |
michael@0 | 4106 | Property *prop = oldArray[i]; |
michael@0 | 4107 | if (prop) { |
michael@0 | 4108 | if (singleton() && !prop->types.constraintList && !zone()->isPreservingCode()) { |
michael@0 | 4109 | /* |
michael@0 | 4110 | * Don't copy over properties of singleton objects when their |
michael@0 | 4111 | * presence will not be required by jitcode or type constraints |
michael@0 | 4112 | * (i.e. for the definite properties analysis). The contents of |
michael@0 | 4113 | * these type sets will be regenerated as necessary. |
michael@0 | 4114 | */ |
michael@0 | 4115 | continue; |
michael@0 | 4116 | } |
michael@0 | 4117 | |
michael@0 | 4118 | Property *newProp = typeLifoAlloc.new_<Property>(*prop); |
michael@0 | 4119 | if (newProp) { |
michael@0 | 4120 | Property **pentry = |
michael@0 | 4121 | HashSetInsert<jsid,Property,Property> |
michael@0 | 4122 | (typeLifoAlloc, propertySet, propertyCount, prop->id); |
michael@0 | 4123 | if (pentry) { |
michael@0 | 4124 | *pentry = newProp; |
michael@0 | 4125 | newProp->types.sweep(zone(), oom); |
michael@0 | 4126 | continue; |
michael@0 | 4127 | } |
michael@0 | 4128 | } |
michael@0 | 4129 | |
michael@0 | 4130 | *oom = true; |
michael@0 | 4131 | addFlags(OBJECT_FLAG_DYNAMIC_MASK | OBJECT_FLAG_UNKNOWN_PROPERTIES); |
michael@0 | 4132 | clearProperties(); |
michael@0 | 4133 | return; |
michael@0 | 4134 | } |
michael@0 | 4135 | } |
michael@0 | 4136 | setBasePropertyCount(propertyCount); |
michael@0 | 4137 | } else if (propertyCount == 1) { |
michael@0 | 4138 | Property *prop = (Property *) propertySet; |
michael@0 | 4139 | if (singleton() && !prop->types.constraintList && !zone()->isPreservingCode()) { |
michael@0 | 4140 | // Skip, as above. |
michael@0 | 4141 | clearProperties(); |
michael@0 | 4142 | } else { |
michael@0 | 4143 | Property *newProp = typeLifoAlloc.new_<Property>(*prop); |
michael@0 | 4144 | if (newProp) { |
michael@0 | 4145 | propertySet = (Property **) newProp; |
michael@0 | 4146 | newProp->types.sweep(zone(), oom); |
michael@0 | 4147 | } else { |
michael@0 | 4148 | *oom = true; |
michael@0 | 4149 | addFlags(OBJECT_FLAG_DYNAMIC_MASK | OBJECT_FLAG_UNKNOWN_PROPERTIES); |
michael@0 | 4150 | clearProperties(); |
michael@0 | 4151 | return; |
michael@0 | 4152 | } |
michael@0 | 4153 | } |
michael@0 | 4154 | } |
michael@0 | 4155 | } |
michael@0 | 4156 | |
michael@0 | 4157 | void |
michael@0 | 4158 | TypeCompartment::clearTables() |
michael@0 | 4159 | { |
michael@0 | 4160 | if (allocationSiteTable && allocationSiteTable->initialized()) |
michael@0 | 4161 | allocationSiteTable->clear(); |
michael@0 | 4162 | if (arrayTypeTable && arrayTypeTable->initialized()) |
michael@0 | 4163 | arrayTypeTable->clear(); |
michael@0 | 4164 | if (objectTypeTable && objectTypeTable->initialized()) |
michael@0 | 4165 | objectTypeTable->clear(); |
michael@0 | 4166 | } |
michael@0 | 4167 | |
michael@0 | 4168 | void |
michael@0 | 4169 | TypeCompartment::sweep(FreeOp *fop) |
michael@0 | 4170 | { |
michael@0 | 4171 | /* |
michael@0 | 4172 | * Iterate through the array/object type tables and remove all entries |
michael@0 | 4173 | * referencing collected data. These tables only hold weak references. |
michael@0 | 4174 | */ |
michael@0 | 4175 | |
michael@0 | 4176 | if (arrayTypeTable) { |
michael@0 | 4177 | for (ArrayTypeTable::Enum e(*arrayTypeTable); !e.empty(); e.popFront()) { |
michael@0 | 4178 | const ArrayTableKey &key = e.front().key(); |
michael@0 | 4179 | JS_ASSERT(key.type.isUnknown() || !key.type.isSingleObject()); |
michael@0 | 4180 | |
michael@0 | 4181 | bool remove = false; |
michael@0 | 4182 | TypeObject *typeObject = nullptr; |
michael@0 | 4183 | if (!key.type.isUnknown() && key.type.isTypeObject()) { |
michael@0 | 4184 | typeObject = key.type.typeObject(); |
michael@0 | 4185 | if (IsTypeObjectAboutToBeFinalized(&typeObject)) |
michael@0 | 4186 | remove = true; |
michael@0 | 4187 | } |
michael@0 | 4188 | if (IsTypeObjectAboutToBeFinalized(e.front().value().unsafeGet())) |
michael@0 | 4189 | remove = true; |
michael@0 | 4190 | |
michael@0 | 4191 | if (remove) { |
michael@0 | 4192 | e.removeFront(); |
michael@0 | 4193 | } else if (typeObject && typeObject != key.type.typeObject()) { |
michael@0 | 4194 | ArrayTableKey newKey; |
michael@0 | 4195 | newKey.type = Type::ObjectType(typeObject); |
michael@0 | 4196 | newKey.proto = key.proto; |
michael@0 | 4197 | e.rekeyFront(newKey); |
michael@0 | 4198 | } |
michael@0 | 4199 | } |
michael@0 | 4200 | } |
michael@0 | 4201 | |
michael@0 | 4202 | if (objectTypeTable) { |
michael@0 | 4203 | for (ObjectTypeTable::Enum e(*objectTypeTable); !e.empty(); e.popFront()) { |
michael@0 | 4204 | const ObjectTableKey &key = e.front().key(); |
michael@0 | 4205 | ObjectTableEntry &entry = e.front().value(); |
michael@0 | 4206 | |
michael@0 | 4207 | bool remove = false; |
michael@0 | 4208 | if (IsTypeObjectAboutToBeFinalized(entry.object.unsafeGet())) |
michael@0 | 4209 | remove = true; |
michael@0 | 4210 | if (IsShapeAboutToBeFinalized(entry.shape.unsafeGet())) |
michael@0 | 4211 | remove = true; |
michael@0 | 4212 | for (unsigned i = 0; !remove && i < key.nproperties; i++) { |
michael@0 | 4213 | if (JSID_IS_STRING(key.properties[i])) { |
michael@0 | 4214 | JSString *str = JSID_TO_STRING(key.properties[i]); |
michael@0 | 4215 | if (IsStringAboutToBeFinalized(&str)) |
michael@0 | 4216 | remove = true; |
michael@0 | 4217 | JS_ASSERT(AtomToId((JSAtom *)str) == key.properties[i]); |
michael@0 | 4218 | } |
michael@0 | 4219 | JS_ASSERT(!entry.types[i].isSingleObject()); |
michael@0 | 4220 | TypeObject *typeObject = nullptr; |
michael@0 | 4221 | if (entry.types[i].isTypeObject()) { |
michael@0 | 4222 | typeObject = entry.types[i].typeObject(); |
michael@0 | 4223 | if (IsTypeObjectAboutToBeFinalized(&typeObject)) |
michael@0 | 4224 | remove = true; |
michael@0 | 4225 | else if (typeObject != entry.types[i].typeObject()) |
michael@0 | 4226 | entry.types[i] = Type::ObjectType(typeObject); |
michael@0 | 4227 | } |
michael@0 | 4228 | } |
michael@0 | 4229 | |
michael@0 | 4230 | if (remove) { |
michael@0 | 4231 | js_free(key.properties); |
michael@0 | 4232 | js_free(entry.types); |
michael@0 | 4233 | e.removeFront(); |
michael@0 | 4234 | } |
michael@0 | 4235 | } |
michael@0 | 4236 | } |
michael@0 | 4237 | |
michael@0 | 4238 | if (allocationSiteTable) { |
michael@0 | 4239 | for (AllocationSiteTable::Enum e(*allocationSiteTable); !e.empty(); e.popFront()) { |
michael@0 | 4240 | AllocationSiteKey key = e.front().key(); |
michael@0 | 4241 | bool keyDying = IsScriptAboutToBeFinalized(&key.script); |
michael@0 | 4242 | bool valDying = IsTypeObjectAboutToBeFinalized(e.front().value().unsafeGet()); |
michael@0 | 4243 | if (keyDying || valDying) |
michael@0 | 4244 | e.removeFront(); |
michael@0 | 4245 | else if (key.script != e.front().key().script) |
michael@0 | 4246 | e.rekeyFront(key); |
michael@0 | 4247 | } |
michael@0 | 4248 | } |
michael@0 | 4249 | } |
michael@0 | 4250 | |
michael@0 | 4251 | void |
michael@0 | 4252 | JSCompartment::sweepNewTypeObjectTable(TypeObjectWithNewScriptSet &table) |
michael@0 | 4253 | { |
michael@0 | 4254 | gcstats::AutoPhase ap(runtimeFromMainThread()->gcStats, |
michael@0 | 4255 | gcstats::PHASE_SWEEP_TABLES_TYPE_OBJECT); |
michael@0 | 4256 | |
michael@0 | 4257 | JS_ASSERT(zone()->isGCSweeping()); |
michael@0 | 4258 | if (table.initialized()) { |
michael@0 | 4259 | for (TypeObjectWithNewScriptSet::Enum e(table); !e.empty(); e.popFront()) { |
michael@0 | 4260 | TypeObjectWithNewScriptEntry entry = e.front(); |
michael@0 | 4261 | if (IsTypeObjectAboutToBeFinalized(entry.object.unsafeGet())) { |
michael@0 | 4262 | e.removeFront(); |
michael@0 | 4263 | } else if (entry.newFunction && IsObjectAboutToBeFinalized(&entry.newFunction)) { |
michael@0 | 4264 | e.removeFront(); |
michael@0 | 4265 | } else if (entry.object != e.front().object) { |
michael@0 | 4266 | TypeObjectWithNewScriptSet::Lookup lookup(entry.object->clasp(), |
michael@0 | 4267 | entry.object->proto(), |
michael@0 | 4268 | entry.newFunction); |
michael@0 | 4269 | e.rekeyFront(lookup, entry); |
michael@0 | 4270 | } |
michael@0 | 4271 | } |
michael@0 | 4272 | } |
michael@0 | 4273 | } |
michael@0 | 4274 | |
michael@0 | 4275 | TypeCompartment::~TypeCompartment() |
michael@0 | 4276 | { |
michael@0 | 4277 | js_delete(arrayTypeTable); |
michael@0 | 4278 | js_delete(objectTypeTable); |
michael@0 | 4279 | js_delete(allocationSiteTable); |
michael@0 | 4280 | } |
michael@0 | 4281 | |
michael@0 | 4282 | /* static */ void |
michael@0 | 4283 | TypeScript::Sweep(FreeOp *fop, JSScript *script, bool *oom) |
michael@0 | 4284 | { |
michael@0 | 4285 | JSCompartment *compartment = script->compartment(); |
michael@0 | 4286 | JS_ASSERT(compartment->zone()->isGCSweeping()); |
michael@0 | 4287 | |
michael@0 | 4288 | unsigned num = NumTypeSets(script); |
michael@0 | 4289 | StackTypeSet *typeArray = script->types->typeArray(); |
michael@0 | 4290 | |
michael@0 | 4291 | /* Remove constraints and references to dead objects from the persistent type sets. */ |
michael@0 | 4292 | for (unsigned i = 0; i < num; i++) |
michael@0 | 4293 | typeArray[i].sweep(compartment->zone(), oom); |
michael@0 | 4294 | } |
michael@0 | 4295 | |
michael@0 | 4296 | void |
michael@0 | 4297 | TypeScript::destroy() |
michael@0 | 4298 | { |
michael@0 | 4299 | js_free(this); |
michael@0 | 4300 | } |
michael@0 | 4301 | |
michael@0 | 4302 | void |
michael@0 | 4303 | Zone::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, |
michael@0 | 4304 | size_t *typePool, |
michael@0 | 4305 | size_t *baselineStubsOptimized) |
michael@0 | 4306 | { |
michael@0 | 4307 | *typePool += types.typeLifoAlloc.sizeOfExcludingThis(mallocSizeOf); |
michael@0 | 4308 | #ifdef JS_ION |
michael@0 | 4309 | if (jitZone()) { |
michael@0 | 4310 | *baselineStubsOptimized += |
michael@0 | 4311 | jitZone()->optimizedStubSpace()->sizeOfExcludingThis(mallocSizeOf); |
michael@0 | 4312 | } |
michael@0 | 4313 | #endif |
michael@0 | 4314 | } |
michael@0 | 4315 | |
michael@0 | 4316 | void |
michael@0 | 4317 | TypeCompartment::addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, |
michael@0 | 4318 | size_t *allocationSiteTables, |
michael@0 | 4319 | size_t *arrayTypeTables, |
michael@0 | 4320 | size_t *objectTypeTables) |
michael@0 | 4321 | { |
michael@0 | 4322 | if (allocationSiteTable) |
michael@0 | 4323 | *allocationSiteTables += allocationSiteTable->sizeOfIncludingThis(mallocSizeOf); |
michael@0 | 4324 | |
michael@0 | 4325 | if (arrayTypeTable) |
michael@0 | 4326 | *arrayTypeTables += arrayTypeTable->sizeOfIncludingThis(mallocSizeOf); |
michael@0 | 4327 | |
michael@0 | 4328 | if (objectTypeTable) { |
michael@0 | 4329 | *objectTypeTables += objectTypeTable->sizeOfIncludingThis(mallocSizeOf); |
michael@0 | 4330 | |
michael@0 | 4331 | for (ObjectTypeTable::Enum e(*objectTypeTable); |
michael@0 | 4332 | !e.empty(); |
michael@0 | 4333 | e.popFront()) |
michael@0 | 4334 | { |
michael@0 | 4335 | const ObjectTableKey &key = e.front().key(); |
michael@0 | 4336 | const ObjectTableEntry &value = e.front().value(); |
michael@0 | 4337 | |
michael@0 | 4338 | /* key.ids and values.types have the same length. */ |
michael@0 | 4339 | *objectTypeTables += mallocSizeOf(key.properties) + mallocSizeOf(value.types); |
michael@0 | 4340 | } |
michael@0 | 4341 | } |
michael@0 | 4342 | } |
michael@0 | 4343 | |
michael@0 | 4344 | size_t |
michael@0 | 4345 | TypeObject::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const |
michael@0 | 4346 | { |
michael@0 | 4347 | return mallocSizeOf(addendum); |
michael@0 | 4348 | } |
michael@0 | 4349 | |
michael@0 | 4350 | TypeZone::TypeZone(Zone *zone) |
michael@0 | 4351 | : zone_(zone), |
michael@0 | 4352 | typeLifoAlloc(TYPE_LIFO_ALLOC_PRIMARY_CHUNK_SIZE), |
michael@0 | 4353 | compilerOutputs(nullptr), |
michael@0 | 4354 | pendingRecompiles(nullptr) |
michael@0 | 4355 | { |
michael@0 | 4356 | } |
michael@0 | 4357 | |
michael@0 | 4358 | TypeZone::~TypeZone() |
michael@0 | 4359 | { |
michael@0 | 4360 | js_delete(compilerOutputs); |
michael@0 | 4361 | js_delete(pendingRecompiles); |
michael@0 | 4362 | } |
michael@0 | 4363 | |
michael@0 | 4364 | void |
michael@0 | 4365 | TypeZone::sweep(FreeOp *fop, bool releaseTypes, bool *oom) |
michael@0 | 4366 | { |
michael@0 | 4367 | JS_ASSERT(zone()->isGCSweeping()); |
michael@0 | 4368 | |
michael@0 | 4369 | JSRuntime *rt = fop->runtime(); |
michael@0 | 4370 | |
michael@0 | 4371 | /* |
michael@0 | 4372 | * Clear the analysis pool, but don't release its data yet. While |
michael@0 | 4373 | * sweeping types any live data will be allocated into the pool. |
michael@0 | 4374 | */ |
michael@0 | 4375 | LifoAlloc oldAlloc(typeLifoAlloc.defaultChunkSize()); |
michael@0 | 4376 | oldAlloc.steal(&typeLifoAlloc); |
michael@0 | 4377 | |
michael@0 | 4378 | /* Sweep and find compressed indexes for each compiler output. */ |
michael@0 | 4379 | size_t newCompilerOutputCount = 0; |
michael@0 | 4380 | |
michael@0 | 4381 | #ifdef JS_ION |
michael@0 | 4382 | if (compilerOutputs) { |
michael@0 | 4383 | for (size_t i = 0; i < compilerOutputs->length(); i++) { |
michael@0 | 4384 | CompilerOutput &output = (*compilerOutputs)[i]; |
michael@0 | 4385 | if (output.isValid()) { |
michael@0 | 4386 | JSScript *script = output.script(); |
michael@0 | 4387 | if (IsScriptAboutToBeFinalized(&script)) { |
michael@0 | 4388 | jit::GetIonScript(script, output.mode())->recompileInfoRef() = uint32_t(-1); |
michael@0 | 4389 | output.invalidate(); |
michael@0 | 4390 | } else { |
michael@0 | 4391 | output.setSweepIndex(newCompilerOutputCount++); |
michael@0 | 4392 | } |
michael@0 | 4393 | } |
michael@0 | 4394 | } |
michael@0 | 4395 | } |
michael@0 | 4396 | #endif |
michael@0 | 4397 | |
michael@0 | 4398 | { |
michael@0 | 4399 | gcstats::AutoPhase ap2(rt->gcStats, gcstats::PHASE_DISCARD_TI); |
michael@0 | 4400 | |
michael@0 | 4401 | for (CellIterUnderGC i(zone(), FINALIZE_SCRIPT); !i.done(); i.next()) { |
michael@0 | 4402 | JSScript *script = i.get<JSScript>(); |
michael@0 | 4403 | if (script->types) { |
michael@0 | 4404 | types::TypeScript::Sweep(fop, script, oom); |
michael@0 | 4405 | |
michael@0 | 4406 | if (releaseTypes) { |
michael@0 | 4407 | if (script->hasParallelIonScript()) { |
michael@0 | 4408 | #ifdef JS_ION |
michael@0 | 4409 | // It's possible that we preserved the parallel |
michael@0 | 4410 | // IonScript. The heuristic for their preservation is |
michael@0 | 4411 | // independent of general JIT code preservation. |
michael@0 | 4412 | MOZ_ASSERT(jit::ShouldPreserveParallelJITCode(rt, script)); |
michael@0 | 4413 | script->parallelIonScript()->recompileInfoRef().shouldSweep(*this); |
michael@0 | 4414 | #else |
michael@0 | 4415 | MOZ_CRASH(); |
michael@0 | 4416 | #endif |
michael@0 | 4417 | } else { |
michael@0 | 4418 | script->types->destroy(); |
michael@0 | 4419 | script->types = nullptr; |
michael@0 | 4420 | |
michael@0 | 4421 | /* |
michael@0 | 4422 | * Freeze constraints on stack type sets need to be |
michael@0 | 4423 | * regenerated the next time the script is analyzed. |
michael@0 | 4424 | */ |
michael@0 | 4425 | script->clearHasFreezeConstraints(); |
michael@0 | 4426 | } |
michael@0 | 4427 | |
michael@0 | 4428 | JS_ASSERT(!script->hasIonScript()); |
michael@0 | 4429 | } else { |
michael@0 | 4430 | /* Update the recompile indexes in any IonScripts still on the script. */ |
michael@0 | 4431 | if (script->hasIonScript()) |
michael@0 | 4432 | script->ionScript()->recompileInfoRef().shouldSweep(*this); |
michael@0 | 4433 | if (script->hasParallelIonScript()) |
michael@0 | 4434 | script->parallelIonScript()->recompileInfoRef().shouldSweep(*this); |
michael@0 | 4435 | } |
michael@0 | 4436 | } |
michael@0 | 4437 | } |
michael@0 | 4438 | } |
michael@0 | 4439 | |
michael@0 | 4440 | { |
michael@0 | 4441 | gcstats::AutoPhase ap2(rt->gcStats, gcstats::PHASE_SWEEP_TYPES); |
michael@0 | 4442 | |
michael@0 | 4443 | for (gc::CellIterUnderGC iter(zone(), gc::FINALIZE_TYPE_OBJECT); |
michael@0 | 4444 | !iter.done(); iter.next()) |
michael@0 | 4445 | { |
michael@0 | 4446 | TypeObject *object = iter.get<TypeObject>(); |
michael@0 | 4447 | object->sweep(fop, oom); |
michael@0 | 4448 | } |
michael@0 | 4449 | |
michael@0 | 4450 | for (CompartmentsInZoneIter comp(zone()); !comp.done(); comp.next()) |
michael@0 | 4451 | comp->types.sweep(fop); |
michael@0 | 4452 | } |
michael@0 | 4453 | |
michael@0 | 4454 | if (compilerOutputs) { |
michael@0 | 4455 | size_t sweepIndex = 0; |
michael@0 | 4456 | for (size_t i = 0; i < compilerOutputs->length(); i++) { |
michael@0 | 4457 | CompilerOutput output = (*compilerOutputs)[i]; |
michael@0 | 4458 | if (output.isValid()) { |
michael@0 | 4459 | JS_ASSERT(sweepIndex == output.sweepIndex()); |
michael@0 | 4460 | output.invalidateSweepIndex(); |
michael@0 | 4461 | (*compilerOutputs)[sweepIndex++] = output; |
michael@0 | 4462 | } |
michael@0 | 4463 | } |
michael@0 | 4464 | JS_ASSERT(sweepIndex == newCompilerOutputCount); |
michael@0 | 4465 | JS_ALWAYS_TRUE(compilerOutputs->resize(newCompilerOutputCount)); |
michael@0 | 4466 | } |
michael@0 | 4467 | |
michael@0 | 4468 | { |
michael@0 | 4469 | gcstats::AutoPhase ap2(rt->gcStats, gcstats::PHASE_FREE_TI_ARENA); |
michael@0 | 4470 | rt->freeLifoAlloc.transferFrom(&oldAlloc); |
michael@0 | 4471 | } |
michael@0 | 4472 | } |
michael@0 | 4473 | |
michael@0 | 4474 | void |
michael@0 | 4475 | TypeZone::clearAllNewScriptAddendumsOnOOM() |
michael@0 | 4476 | { |
michael@0 | 4477 | for (gc::CellIterUnderGC iter(zone(), gc::FINALIZE_TYPE_OBJECT); |
michael@0 | 4478 | !iter.done(); iter.next()) |
michael@0 | 4479 | { |
michael@0 | 4480 | TypeObject *object = iter.get<TypeObject>(); |
michael@0 | 4481 | object->maybeClearNewScriptAddendumOnOOM(); |
michael@0 | 4482 | } |
michael@0 | 4483 | } |
michael@0 | 4484 | |
michael@0 | 4485 | #ifdef DEBUG |
michael@0 | 4486 | void |
michael@0 | 4487 | TypeScript::printTypes(JSContext *cx, HandleScript script) const |
michael@0 | 4488 | { |
michael@0 | 4489 | JS_ASSERT(script->types == this); |
michael@0 | 4490 | |
michael@0 | 4491 | if (!script->hasBaselineScript()) |
michael@0 | 4492 | return; |
michael@0 | 4493 | |
michael@0 | 4494 | AutoEnterAnalysis enter(nullptr, script->compartment()); |
michael@0 | 4495 | |
michael@0 | 4496 | if (script->functionNonDelazifying()) |
michael@0 | 4497 | fprintf(stderr, "Function"); |
michael@0 | 4498 | else if (script->isForEval()) |
michael@0 | 4499 | fprintf(stderr, "Eval"); |
michael@0 | 4500 | else |
michael@0 | 4501 | fprintf(stderr, "Main"); |
michael@0 | 4502 | fprintf(stderr, " #%u %s:%d ", script->id(), script->filename(), (int) script->lineno()); |
michael@0 | 4503 | |
michael@0 | 4504 | if (script->functionNonDelazifying()) { |
michael@0 | 4505 | if (js::PropertyName *name = script->functionNonDelazifying()->name()) { |
michael@0 | 4506 | const jschar *chars = name->getChars(nullptr); |
michael@0 | 4507 | JSString::dumpChars(chars, name->length()); |
michael@0 | 4508 | } |
michael@0 | 4509 | } |
michael@0 | 4510 | |
michael@0 | 4511 | fprintf(stderr, "\n this:"); |
michael@0 | 4512 | TypeScript::ThisTypes(script)->print(); |
michael@0 | 4513 | |
michael@0 | 4514 | for (unsigned i = 0; |
michael@0 | 4515 | script->functionNonDelazifying() && i < script->functionNonDelazifying()->nargs(); |
michael@0 | 4516 | i++) |
michael@0 | 4517 | { |
michael@0 | 4518 | fprintf(stderr, "\n arg%u:", i); |
michael@0 | 4519 | TypeScript::ArgTypes(script, i)->print(); |
michael@0 | 4520 | } |
michael@0 | 4521 | fprintf(stderr, "\n"); |
michael@0 | 4522 | |
michael@0 | 4523 | for (jsbytecode *pc = script->code(); pc < script->codeEnd(); pc += GetBytecodeLength(pc)) { |
michael@0 | 4524 | PrintBytecode(cx, script, pc); |
michael@0 | 4525 | |
michael@0 | 4526 | if (js_CodeSpec[*pc].format & JOF_TYPESET) { |
michael@0 | 4527 | StackTypeSet *types = TypeScript::BytecodeTypes(script, pc); |
michael@0 | 4528 | fprintf(stderr, " typeset %u:", unsigned(types - typeArray())); |
michael@0 | 4529 | types->print(); |
michael@0 | 4530 | fprintf(stderr, "\n"); |
michael@0 | 4531 | } |
michael@0 | 4532 | } |
michael@0 | 4533 | |
michael@0 | 4534 | fprintf(stderr, "\n"); |
michael@0 | 4535 | } |
michael@0 | 4536 | #endif /* DEBUG */ |
michael@0 | 4537 | |
michael@0 | 4538 | ///////////////////////////////////////////////////////////////////// |
michael@0 | 4539 | // Binary data |
michael@0 | 4540 | ///////////////////////////////////////////////////////////////////// |
michael@0 | 4541 | |
michael@0 | 4542 | void |
michael@0 | 4543 | TypeObject::setAddendum(TypeObjectAddendum *addendum) |
michael@0 | 4544 | { |
michael@0 | 4545 | this->addendum = addendum; |
michael@0 | 4546 | } |
michael@0 | 4547 | |
michael@0 | 4548 | bool |
michael@0 | 4549 | TypeObject::addTypedObjectAddendum(JSContext *cx, Handle<TypeDescr*> descr) |
michael@0 | 4550 | { |
michael@0 | 4551 | // Type descriptors are always pre-tenured. This is both because |
michael@0 | 4552 | // we expect them to live a long time and so that they can be |
michael@0 | 4553 | // safely accessed during ion compilation. |
michael@0 | 4554 | JS_ASSERT(!IsInsideNursery(cx->runtime(), descr)); |
michael@0 | 4555 | JS_ASSERT(descr); |
michael@0 | 4556 | |
michael@0 | 4557 | if (flags() & OBJECT_FLAG_ADDENDUM_CLEARED) |
michael@0 | 4558 | return true; |
michael@0 | 4559 | |
michael@0 | 4560 | JS_ASSERT(!unknownProperties()); |
michael@0 | 4561 | |
michael@0 | 4562 | if (addendum) { |
michael@0 | 4563 | JS_ASSERT(hasTypedObject()); |
michael@0 | 4564 | JS_ASSERT(&typedObject()->descr() == descr); |
michael@0 | 4565 | return true; |
michael@0 | 4566 | } |
michael@0 | 4567 | |
michael@0 | 4568 | TypeTypedObject *typedObject = js_new<TypeTypedObject>(descr); |
michael@0 | 4569 | if (!typedObject) |
michael@0 | 4570 | return false; |
michael@0 | 4571 | addendum = typedObject; |
michael@0 | 4572 | return true; |
michael@0 | 4573 | } |
michael@0 | 4574 | |
michael@0 | 4575 | ///////////////////////////////////////////////////////////////////// |
michael@0 | 4576 | // Type object addenda constructor |
michael@0 | 4577 | ///////////////////////////////////////////////////////////////////// |
michael@0 | 4578 | |
michael@0 | 4579 | TypeObjectAddendum::TypeObjectAddendum(Kind kind) |
michael@0 | 4580 | : kind(kind) |
michael@0 | 4581 | {} |
michael@0 | 4582 | |
michael@0 | 4583 | TypeNewScript::TypeNewScript() |
michael@0 | 4584 | : TypeObjectAddendum(NewScript) |
michael@0 | 4585 | {} |
michael@0 | 4586 | |
michael@0 | 4587 | TypeTypedObject::TypeTypedObject(Handle<TypeDescr*> descr) |
michael@0 | 4588 | : TypeObjectAddendum(TypedObject), |
michael@0 | 4589 | descr_(descr) |
michael@0 | 4590 | { |
michael@0 | 4591 | } |
michael@0 | 4592 | |
michael@0 | 4593 | TypeDescr & |
michael@0 | 4594 | js::types::TypeTypedObject::descr() { |
michael@0 | 4595 | return descr_->as<TypeDescr>(); |
michael@0 | 4596 | } |