Bug 1651037 part 8 - Support nursery-allocated objects in the transpiler. r=iain

Depends on D82673

Differential Revision: https://phabricator.services.mozilla.com/D82674
This commit is contained in:
Jan de Mooij 2020-07-08 18:38:10 +00:00
Родитель 506eea20ec
Коммит c18a778773
9 изменённых файлов: 219 добавлений и 97 удалений

Просмотреть файл

@ -721,68 +721,6 @@ void ICStub::trace(JSTracer* trc) {
}
}
bool ICStub::stubDataHasNurseryPointers(const CacheIRStubInfo* stubInfo) {
MOZ_ASSERT(IsCacheIRKind(kind()));
uint32_t field = 0;
size_t offset = 0;
while (true) {
StubField::Type fieldType = stubInfo->fieldType(field);
switch (fieldType) {
case StubField::Type::RawWord:
case StubField::Type::RawInt64:
case StubField::Type::DOMExpandoGeneration:
break;
case StubField::Type::Shape:
static_assert(std::is_convertible_v<Shape*, gc::TenuredCell*>,
"Code assumes shapes are tenured");
break;
case StubField::Type::ObjectGroup:
static_assert(std::is_convertible_v<ObjectGroup*, gc::TenuredCell*>,
"Code assumes groups are tenured");
break;
case StubField::Type::Symbol:
static_assert(std::is_convertible_v<JS::Symbol*, gc::TenuredCell*>,
"Code assumes symbols are tenured");
break;
case StubField::Type::JSObject: {
JSObject* obj = stubInfo->getStubField<ICStub, JSObject*>(this, offset);
if (IsInsideNursery(obj)) {
return true;
}
break;
}
case StubField::Type::String: {
JSString* str = stubInfo->getStubField<ICStub, JSString*>(this, offset);
if (IsInsideNursery(str)) {
return true;
}
break;
}
case StubField::Type::Id: {
#ifdef DEBUG
// jsid never contains nursery-allocated things.
jsid id = stubInfo->getStubField<ICStub, jsid>(this, offset);
MOZ_ASSERT_IF(id.isGCThing(),
!IsInsideNursery(id.toGCCellPtr().asCell()));
#endif
break;
}
case StubField::Type::Value: {
Value v = stubInfo->getStubField<ICStub, JS::Value>(this, offset);
if (v.isGCThing() && IsInsideNursery(v.toGCThing())) {
return true;
}
break;
}
case StubField::Type::Limit:
return false; // Done. Didn't find any nursery pointers.
}
field++;
offset += StubField::sizeInBytes(fieldType);
}
}
// This helper handles ICState updates/transitions while attaching CacheIR
// stubs.
template <typename IRGenerator, typename... Args>

Просмотреть файл

@ -420,8 +420,6 @@ class ICStub {
void updateCode(JitCode* stubCode);
void trace(JSTracer* trc);
bool stubDataHasNurseryPointers(const CacheIRStubInfo* stubInfo);
static const uint16_t EXPECTED_TRACE_MAGIC = 0b1100011;
template <typename T, typename... Args>

Просмотреть файл

@ -1000,6 +1000,15 @@ int64_t CacheIRStubInfo::getStubRawInt64(ICStub* stub, uint32_t offset) const {
return getStubRawInt64(stubData, offset);
}
void CacheIRStubInfo::replaceStubRawWord(uint8_t* stubData, uint32_t offset,
uintptr_t oldWord,
uintptr_t newWord) const {
MOZ_ASSERT(uintptr_t(stubData) % sizeof(uintptr_t) == 0);
uintptr_t* addr = reinterpret_cast<uintptr_t*>(stubData + offset);
MOZ_ASSERT(*addr == oldWord);
*addr = newWord;
}
template <class Stub, class T>
GCPtr<T>& CacheIRStubInfo::getStubField(Stub* stub, uint32_t offset) const {
uint8_t* stubData = (uint8_t*)stub + stubDataOffset_;

Просмотреть файл

@ -1208,6 +1208,9 @@ class CacheIRStubInfo {
int64_t getStubRawInt64(const uint8_t* stubData, uint32_t offset) const;
int64_t getStubRawInt64(ICStub* stub, uint32_t offset) const;
void replaceStubRawWord(uint8_t* stubData, uint32_t offset, uintptr_t oldWord,
uintptr_t newWord) const;
};
template <typename T>

Просмотреть файл

@ -93,9 +93,6 @@ class MOZ_RAII WarpCacheIRTranspiler : public WarpBuilderShared {
JS::Symbol* symbolStubField(uint32_t offset) {
return reinterpret_cast<JS::Symbol*>(readStubWord(offset));
}
JSObject* objectStubField(uint32_t offset) {
return reinterpret_cast<JSObject*>(readStubWord(offset));
}
const void* rawPointerField(uint32_t offset) {
return reinterpret_cast<const void*>(readStubWord(offset));
}
@ -106,6 +103,16 @@ class MOZ_RAII WarpCacheIRTranspiler : public WarpBuilderShared {
return static_cast<uint32_t>(readStubWord(offset));
}
// This must only be called when the caller knows the object is tenured and
// not a nursery index.
JSObject* tenuredObjectStubField(uint32_t offset) {
WarpObjectField field = WarpObjectField::fromData(readStubWord(offset));
return field.toObject();
}
// Returns either MConstant or MNurseryIndex. See WarpObjectField.
MInstruction* objectStubField(uint32_t offset);
MOZ_MUST_USE bool emitGuardTo(ValOperandId inputId, MIRType type);
MOZ_MUST_USE bool emitToString(OperandId inputId, StringOperandId resultId);
@ -178,6 +185,20 @@ bool WarpCacheIRTranspiler::transpile(const MDefinitionStackVector& inputs) {
return true;
}
MInstruction* WarpCacheIRTranspiler::objectStubField(uint32_t offset) {
WarpObjectField field = WarpObjectField::fromData(readStubWord(offset));
if (field.isNurseryIndex()) {
auto* ins = MNurseryObject::New(alloc(), field.toNurseryIndex());
add(ins);
return ins;
}
auto* ins = MConstant::NewConstraintlessObject(alloc(), field.toObject());
add(ins);
return ins;
}
bool WarpCacheIRTranspiler::emitGuardClass(ObjOperandId objId,
GuardClassKind kind) {
MDefinition* def = getOperand(objId);
@ -235,11 +256,9 @@ bool WarpCacheIRTranspiler::emitGuardNullProto(ObjOperandId objId) {
bool WarpCacheIRTranspiler::emitGuardProto(ObjOperandId objId,
uint32_t protoOffset) {
MDefinition* def = getOperand(objId);
JSObject* proto = objectStubField(protoOffset);
MDefinition* proto = objectStubField(protoOffset);
MConstant* protoConst = constant(ObjectValue(*proto));
auto* ins = MGuardProto::New(alloc(), def, protoConst);
auto* ins = MGuardProto::New(alloc(), def, proto);
add(ins);
setOperand(objId, ins);
@ -295,12 +314,9 @@ bool WarpCacheIRTranspiler::emitGuardSpecificSymbol(SymbolOperandId symId,
bool WarpCacheIRTranspiler::emitGuardSpecificObject(ObjOperandId objId,
uint32_t expectedOffset) {
MDefinition* obj = getOperand(objId);
JSObject* expected = objectStubField(expectedOffset);
MDefinition* expected = objectStubField(expectedOffset);
auto* constObj = MConstant::NewConstraintlessObject(alloc(), expected);
add(constObj);
auto* ins = MGuardObjectIdentity::New(alloc(), obj, constObj,
auto* ins = MGuardObjectIdentity::New(alloc(), obj, expected,
/* bailOnEquality = */ false);
add(ins);
@ -311,18 +327,13 @@ bool WarpCacheIRTranspiler::emitGuardSpecificObject(ObjOperandId objId,
bool WarpCacheIRTranspiler::emitGuardSpecificFunction(
ObjOperandId objId, uint32_t expectedOffset, uint32_t nargsAndFlagsOffset) {
MDefinition* obj = getOperand(objId);
JSObject* expected = objectStubField(expectedOffset);
MDefinition* expected = objectStubField(expectedOffset);
uint32_t nargsAndFlags = uint32StubField(nargsAndFlagsOffset);
MOZ_ASSERT(expected->is<JSFunction>());
auto* constObj = MConstant::NewConstraintlessObject(alloc(), expected);
add(constObj);
uint16_t nargs = nargsAndFlags >> 16;
FunctionFlags flags = FunctionFlags(uint16_t(nargsAndFlags));
auto* ins = MGuardSpecificFunction::New(alloc(), obj, constObj, nargs, flags);
auto* ins = MGuardSpecificFunction::New(alloc(), obj, expected, nargs, flags);
add(ins);
setOperand(objId, ins);
@ -617,10 +628,7 @@ bool WarpCacheIRTranspiler::emitLoadEnclosingEnvironment(
bool WarpCacheIRTranspiler::emitLoadObject(ObjOperandId resultId,
uint32_t objOffset) {
JSObject* obj = objectStubField(objOffset);
auto* ins = MConstant::NewConstraintlessObject(alloc(), obj);
add(ins);
MInstruction* ins = objectStubField(objOffset);
return defineOperand(resultId, ins);
}
@ -1758,7 +1766,7 @@ bool WarpCacheIRTranspiler::emitMetaTwoByte(MetaTwoByteKind kind,
return true;
}
JSObject* templateObj = objectStubField(templateObjectOffset);
JSObject* templateObj = tenuredObjectStubField(templateObjectOffset);
MConstant* templateConst = constant(ObjectValue(*templateObj));
// TODO: support pre-tenuring.

Просмотреть файл

@ -54,6 +54,9 @@ class MOZ_STACK_CLASS WarpScriptOracle {
AbortReasonOr<WarpEnvironment> createEnvironment();
AbortReasonOr<Ok> maybeInlineIC(WarpOpSnapshotList& snapshots,
BytecodeLocation loc);
MOZ_MUST_USE bool replaceNurseryPointers(ICStub* stub,
const CacheIRStubInfo* stubInfo,
uint8_t* stubDataCopy);
public:
WarpScriptOracle(JSContext* cx, WarpOracle* oracle, HandleScript script)
@ -115,6 +118,10 @@ AbortReasonOr<WarpSnapshot*> WarpOracle::createSnapshot() {
return abort(outerScript_, AbortReason::Alloc);
}
if (!snapshot->nurseryObjects().appendAll(nurseryObjects_)) {
return abort(outerScript_, AbortReason::Alloc);
}
#ifdef JS_JITSPEW
if (JitSpewEnabled(JitSpew_WarpSnapshots)) {
Fprinter& out = JitSpewPrinter();
@ -783,12 +790,6 @@ AbortReasonOr<Ok> WarpScriptOracle::maybeInlineIC(WarpOpSnapshotList& snapshots,
const CacheIRStubInfo* stubInfo = stub->cacheIRStubInfo();
const uint8_t* stubData = stub->cacheIRStubData();
// TODO: we don't support stubs with nursery pointers for now. Handling this
// well requires special machinery. See bug 1631267.
if (stub->stubDataHasNurseryPointers(stubInfo)) {
return Ok();
}
// Only create a snapshots if all opcodes are supported by the transpiler.
CacheIRReader reader(stubInfo);
while (reader.more()) {
@ -849,10 +850,14 @@ AbortReasonOr<Ok> WarpScriptOracle::maybeInlineIC(WarpOpSnapshotList& snapshots,
return abort(AbortReason::Alloc);
}
// We don't need any GC barriers because the stub data does not contain
// nursery pointers (checked above) so we can do a bitwise copy.
// Note: nursery pointers are handled below so we don't need to trigger any GC
// barriers and can do a bitwise copy.
std::copy_n(stubData, bytesNeeded, stubDataCopy);
if (!replaceNurseryPointers(stub, stubInfo, stubDataCopy)) {
return abort(AbortReason::Alloc);
}
JitCode* jitCode = stub->jitCode();
if (!AddOpSnapshot<WarpCacheIR>(alloc_, snapshots, offset, jitCode, stubInfo,
@ -864,3 +869,93 @@ AbortReasonOr<Ok> WarpScriptOracle::maybeInlineIC(WarpOpSnapshotList& snapshots,
return Ok();
}
bool WarpScriptOracle::replaceNurseryPointers(ICStub* stub,
const CacheIRStubInfo* stubInfo,
uint8_t* stubDataCopy) {
// If the stub data contains nursery object pointers, replace them with the
// corresponding nursery index. See WarpObjectField.
//
// Also asserts non-object fields don't contain nursery pointers.
uint32_t field = 0;
size_t offset = 0;
while (true) {
StubField::Type fieldType = stubInfo->fieldType(field);
switch (fieldType) {
case StubField::Type::RawWord:
case StubField::Type::RawInt64:
case StubField::Type::DOMExpandoGeneration:
break;
case StubField::Type::Shape:
static_assert(std::is_convertible_v<Shape*, gc::TenuredCell*>,
"Code assumes shapes are tenured");
break;
case StubField::Type::ObjectGroup:
static_assert(std::is_convertible_v<ObjectGroup*, gc::TenuredCell*>,
"Code assumes groups are tenured");
break;
case StubField::Type::Symbol:
static_assert(std::is_convertible_v<JS::Symbol*, gc::TenuredCell*>,
"Code assumes symbols are tenured");
break;
case StubField::Type::JSObject: {
JSObject* obj = stubInfo->getStubField<ICStub, JSObject*>(stub, offset);
if (IsInsideNursery(obj)) {
uint32_t nurseryIndex;
if (!oracle_->registerNurseryObject(obj, &nurseryIndex)) {
return false;
}
uintptr_t oldWord = WarpObjectField::fromObject(obj).rawData();
uintptr_t newWord =
WarpObjectField::fromNurseryIndex(nurseryIndex).rawData();
stubInfo->replaceStubRawWord(stubDataCopy, offset, oldWord, newWord);
}
break;
}
case StubField::Type::String: {
#ifdef DEBUG
JSString* str = stubInfo->getStubField<ICStub, JSString*>(stub, offset);
MOZ_ASSERT(!IsInsideNursery(str));
#endif
break;
}
case StubField::Type::Id: {
#ifdef DEBUG
// jsid never contains nursery-allocated things.
jsid id = stubInfo->getStubField<ICStub, jsid>(stub, offset);
MOZ_ASSERT_IF(id.isGCThing(),
!IsInsideNursery(id.toGCCellPtr().asCell()));
#endif
break;
}
case StubField::Type::Value: {
#ifdef DEBUG
Value v = stubInfo->getStubField<ICStub, JS::Value>(stub, offset);
MOZ_ASSERT_IF(v.isGCThing(), !IsInsideNursery(v.toGCThing()));
#endif
break;
}
case StubField::Type::Limit:
return true; // Done.
}
field++;
offset += StubField::sizeInBytes(fieldType);
}
}
bool WarpOracle::registerNurseryObject(JSObject* obj, uint32_t* nurseryIndex) {
MOZ_ASSERT(IsInsideNursery(obj));
auto p = nurseryObjectsMap_.lookupForAdd(obj);
if (p) {
*nurseryIndex = p->value();
return true;
}
if (!nurseryObjects_.append(obj)) {
return false;
}
*nurseryIndex = nurseryObjects_.length() - 1;
return nurseryObjectsMap_.add(p, obj, *nurseryIndex);
}

Просмотреть файл

@ -25,12 +25,24 @@ class MOZ_STACK_CLASS WarpOracle {
HandleScript outerScript_;
WarpBailoutInfo bailoutInfo_;
// List of nursery objects to copy to the snapshot. See WarpObjectField.
// The HashMap is used to de-duplicate the Vector. It maps each object to the
// corresponding nursery index (index into the Vector).
// Note: this stores raw object pointers because WarpOracle can't GC.
Vector<JSObject*, 8, SystemAllocPolicy> nurseryObjects_;
using NurseryObjectsMap =
HashMap<JSObject*, uint32_t, DefaultHasher<JSObject*>, SystemAllocPolicy>;
NurseryObjectsMap nurseryObjectsMap_;
public:
WarpOracle(JSContext* cx, MIRGenerator& mirGen, HandleScript outerScript);
MIRGenerator& mirGen() { return mirGen_; }
WarpBailoutInfo& bailoutInfo() { return bailoutInfo_; }
MOZ_MUST_USE bool registerNurseryObject(JSObject* obj,
uint32_t* nurseryIndex);
AbortReasonOr<WarpSnapshot*> createSnapshot();
mozilla::GenericErrorResult<AbortReason> abort(HandleScript script,

Просмотреть файл

@ -285,5 +285,7 @@ void WarpBailout::traceData(JSTracer* trc) {
void WarpCacheIR::traceData(JSTracer* trc) {
TraceWarpGCPtr(trc, stubCode_, "warp-stub-code");
// TODO: trace pointers in stub data.
// TODO: trace pointers in stub data. Beware of nursery indexes in the stub
// data. See WarpObjectField.
}

Просмотреть файл

@ -280,6 +280,63 @@ class WarpCacheIR : public WarpOpSnapshot {
#endif
};
// [SMDOC] Warp Nursery Object support
//
// CacheIR stub data can contain nursery allocated objects. This can happen for
// example for GuardSpecificObject/GuardSpecificFunction or GuardProto.
//
// To support nursery GCs in parallel with off-thread compilation, we use the
// following mechanism:
//
// * When WarpOracle copies stub data, it builds a Vector of nursery objects.
// The nursery object pointers in the stub data are replaced with the
// corresponding index into this Vector.
// See WarpScriptOracle::replaceNurseryPointers.
//
// * The Vector is copied to the snapshot and, at the end of compilation, to
// the IonScript. The Vector is only accessed on the main thread.
//
// * The MIR backend never accesses the raw JSObject*. Instead, it uses
// MNurseryObject which will load the object at runtime from the IonScript.
//
// WarpObjectField is a helper class to encode/decode a stub data field that
// either stores an object or a nursery index.
class WarpObjectField {
// This is a nursery index if the low bit is set. Else it's a JSObject*.
static constexpr uintptr_t NurseryIndexTag = 0x1;
static constexpr uintptr_t NurseryIndexShift = 1;
uintptr_t data_;
explicit WarpObjectField(uintptr_t data) : data_(data) {}
public:
static WarpObjectField fromData(uintptr_t data) {
return WarpObjectField(data);
}
static WarpObjectField fromObject(JSObject* obj) {
return WarpObjectField(uintptr_t(obj));
}
static WarpObjectField fromNurseryIndex(uint32_t index) {
uintptr_t data = (uintptr_t(index) << NurseryIndexShift) | NurseryIndexTag;
return WarpObjectField(data);
}
uintptr_t rawData() const { return data_; }
bool isNurseryIndex() const { return (data_ & NurseryIndexTag) != 0; }
uint32_t toNurseryIndex() const {
MOZ_ASSERT(isNurseryIndex());
return data_ >> NurseryIndexShift;
}
JSObject* toObject() const {
MOZ_ASSERT(!isNurseryIndex());
return reinterpret_cast<JSObject*>(data_);
}
};
// Template object for JSOp::Rest.
class WarpRest : public WarpOpSnapshot {
WarpGCPtr<ArrayObject*> templateObject_;