text: //-------------------------------------------------------------------------------------------------------
  // Copyright (C) Microsoft. All rights reserved.
  // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  //-------------------------------------------------------------------------------------------------------
  #include "RuntimeDebugPch.h"
  
  #if ENABLE_TTD
  
  #include "ByteCode/ByteCodeSerializer.h"
  
  namespace TTD
  {
      namespace JsSupport
      {
          bool IsVarTaggedInline(Js::Var v)
          {
              return Js::TaggedNumber::Is(v);
          }
  
          bool IsVarPtrValued(Js::Var v)
          {
              return !Js::TaggedNumber::Is(v);
          }
  
          bool IsVarPrimitiveKind(Js::Var v)
          {
              if(Js::TaggedNumber::Is(v))
              {
                  return false;
              }
  
              Js::TypeId tid = Js::RecyclableObject::FromVar(v)->GetTypeId();
              return tid <= Js::TypeIds_LastToPrimitiveType;
          }
  
          bool IsVarComplexKind(Js::Var v)
          {
              if(Js::TaggedNumber::Is(v))
              {
                  return false;
              }
  
              Js::TypeId tid = Js::RecyclableObject::FromVar(v)->GetTypeId();
              return tid > Js::TypeIds_LastToPrimitiveType;
          }
  
  #if ENABLE_TTD_INTERNAL_DIAGNOSTICS
          bool AreInlineVarsEquiv(Js::Var v1, Js::Var v2)
          {
              if(v1 == v2)
              {
                  return true; //same bit pattern so no problem
              }
  
              if(v1 == nullptr || v2 == nullptr)
              {
                  return false; //then they should be the same per above
              }
  
              if(Js::TaggedNumber::Is(v1) != Js::TaggedNumber::Is(v2))
              {
                  return false;
              }
  
              double v1val = Js::TaggedInt::Is(v1) ? Js::TaggedInt::ToInt32(v1) : Js::JavascriptNumber::GetValue(v1);
              double v2val = Js::TaggedInt::Is(v2) ? Js::TaggedInt::ToInt32(v2) : Js::JavascriptNumber::GetValue(v2);
  
              if(Js::JavascriptNumber::IsNan(v1val) != Js::JavascriptNumber::IsNan(v2val))
              {
                  return false;
              }
  
              return v1val == v2val;
          }
  #endif
  
          Js::FunctionBody* ForceAndGetFunctionBody(Js::ParseableFunctionInfo* pfi)
          {
              Js::FunctionBody* fb = nullptr;
  
              if(pfi->IsDeferredDeserializeFunction())
              {
                  Js::DeferDeserializeFunctionInfo* deferDeserializeInfo = pfi->GetDeferDeserializeFunctionInfo();
                  fb = deferDeserializeInfo->Deserialize();
              }
              else
              {
                  if(pfi->IsDeferredParseFunction())
                  {
                      fb = pfi->GetParseableFunctionInfo()->Parse();
                  }
                  else
                  {
                      fb = pfi->GetFunctionBody();
                  }
              }
              TTDAssert(fb != nullptr, "I just want a function body!!!");
  
              fb->EnsureDeserialized();
              return fb;
          }
  
          void WriteCodeToFile(ThreadContext* threadContext, bool fromEvent, uint32 bodyId, bool isUtf8Source, byte* sourceBuffer, uint32 length)
          {
              char asciiResourceName[64];
              sprintf_s(asciiResourceName, 64, "src%s_%I32u.js", (fromEvent ? "_ld" : ""), bodyId);
  
              TTDataIOInfo& iofp = threadContext->TTDContext->TTDataIOInfo;
              JsTTDStreamHandle srcStream = iofp.pfOpenResourceStream(iofp.ActiveTTUriLength, iofp.ActiveTTUri, strlen(asciiResourceName), asciiResourceName, false, true);
              TTDAssert(srcStream != nullptr, "Failed to open code resource stream for writing.");
  
              if(isUtf8Source)
              {
                  byte byteOrderArray[3] = { 0xEF, 0xBB, 0xBF };
                  size_t byteOrderCount = 0;
                  bool okBOC = iofp.pfWriteBytesToStream(srcStream, byteOrderArray, _countof(byteOrderArray), &byteOrderCount);
                  TTDAssert(okBOC && byteOrderCount == _countof(byteOrderArray), "Write Failed!!!");
              }
              else
              {
                  byte byteOrderArray[2] = { 0xFF, 0xFE };
                  size_t byteOrderCount = 0;
                  bool okBOC = iofp.pfWriteBytesToStream(srcStream, byteOrderArray, _countof(byteOrderArray), &byteOrderCount);
                  TTDAssert(okBOC && byteOrderCount == _countof(byteOrderArray), "Write Failed!!!");
              }
  
              size_t writtenCount = 0;
              bool ok = iofp.pfWriteBytesToStream(srcStream, sourceBuffer, length, &writtenCount);
              TTDAssert(ok && writtenCount == length, "Write Failed!!!");
  
              iofp.pfFlushAndCloseStream(srcStream, false, true);
          }
  
          void ReadCodeFromFile(ThreadContext* threadContext, bool fromEvent, uint32 bodyId, bool isUtf8Source, byte* sourceBuffer, uint32 length)
          {
              char asciiResourceName[64];
              sprintf_s(asciiResourceName, 64, "src%s_%I32u.js", (fromEvent ? "_ld" : ""), bodyId);
  
              TTDataIOInfo& iofp = threadContext->TTDContext->TTDataIOInfo;
              JsTTDStreamHandle srcStream = iofp.pfOpenResourceStream(iofp.ActiveTTUriLength, iofp.ActiveTTUri, strlen(asciiResourceName), asciiResourceName, true, false);
              TTDAssert(srcStream != nullptr, "Failed to open code resource stream for reading.");
  
              if(isUtf8Source)
              {
                  byte byteOrderArray[3] = { 0x0, 0x0, 0x0 };
                  size_t byteOrderCount = 0;
                  bool okBOC = iofp.pfReadBytesFromStream(srcStream, byteOrderArray, _countof(byteOrderArray), &byteOrderCount);
                  TTDAssert(okBOC && byteOrderCount == _countof(byteOrderArray) && byteOrderArray[0] == 0xEF && byteOrderArray[1] == 0xBB && byteOrderArray[2] == 0xBF, "Read Failed!!!");
              }
              else
              {
                  byte byteOrderArray[2] = { 0x0, 0x0 };
                  size_t byteOrderCount = 0;
                  bool okBOC = iofp.pfReadBytesFromStream(srcStream, byteOrderArray, _countof(byteOrderArray), &byteOrderCount);
                  TTDAssert(okBOC && byteOrderCount == _countof(byteOrderArray) && byteOrderArray[0] == 0xFF && byteOrderArray[1] == 0xFE, "Read Failed!!!");
              }
  
              size_t readCount = 0;
              bool ok = iofp.pfReadBytesFromStream(srcStream, sourceBuffer, length, &readCount);
              TTDAssert(ok && readCount == length, "Read Failed!!!");
  
              iofp.pfFlushAndCloseStream(srcStream, true, false);
          }
      }
  
      namespace NSSnapValues
      {
          void EmitTTDVar(TTDVar var, FileWriter* writer, NSTokens::Separator separator)
          {
              writer->WriteRecordStart(separator);
  
              if(var == nullptr)
              {
                  writer->WriteTag<TTDVarEmitTag>(NSTokens::Key::ttdVarTag, TTDVarEmitTag::TTDVarNull);
                  writer->WriteNull(NSTokens::Key::nullVal, NSTokens::Separator::CommaSeparator);
              }
              else if(Js::TaggedNumber::Is(var))
              {
  #if FLOATVAR
                  if(Js::TaggedInt::Is(var))
                  {
  #endif
                      writer->WriteTag<TTDVarEmitTag>(NSTokens::Key::ttdVarTag, TTDVarEmitTag::TTDVarInt);
                      writer->WriteInt32(NSTokens::Key::i32Val, Js::TaggedInt::ToInt32(var), NSTokens::Separator::CommaSeparator);
  #if FLOATVAR
                  }
                  else
                  {
                      TTDAssert(Js::JavascriptNumber::Is_NoTaggedIntCheck(var), "Only other tagged value we support!!!");
  
                      writer->WriteTag<TTDVarEmitTag>(NSTokens::Key::ttdVarTag, TTDVarEmitTag::TTDVarDouble);
                      writer->WriteDouble(NSTokens::Key::doubleVal, Js::JavascriptNumber::GetValue(var), NSTokens::Separator::CommaSeparator);
                  }
  #endif
              }
              else
              {
                  writer->WriteTag<TTDVarEmitTag>(NSTokens::Key::ttdVarTag, TTDVarEmitTag::TTDVarAddr);
                  writer->WriteAddr(NSTokens::Key::ptrIdVal, TTD_CONVERT_VAR_TO_PTR_ID(var), NSTokens::Separator::CommaSeparator);
              }
  
              writer->WriteRecordEnd();
          }
  
          TTDVar ParseTTDVar(bool readSeparator, FileReader* reader)
          {
              reader->ReadRecordStart(readSeparator);
  
              TTDVar res = nullptr;
              TTDVarEmitTag tag = reader->ReadTag<TTDVarEmitTag>(NSTokens::Key::ttdVarTag);
              if(tag == TTDVarEmitTag::TTDVarNull)
              {
                  reader->ReadNull(NSTokens::Key::nullVal, true);
                  res = nullptr;
              }
              else if(tag == TTDVarEmitTag::TTDVarInt)
              {
                  int32 intVal = reader->ReadInt32(NSTokens::Key::i32Val, true);
                  res = Js::TaggedInt::ToVarUnchecked(intVal);
              }
  #if FLOATVAR
              else if(tag == TTDVarEmitTag::TTDVarDouble)
              {
                  double doubleVal = reader->ReadDouble(NSTokens::Key::doubleVal, true);
                  res = Js::JavascriptNumber::NewInlined(doubleVal, nullptr);
              }
  #endif
              else
              {
                  TTDAssert(tag == TTDVarEmitTag::TTDVarAddr, "Is there something else?");
  
                  TTD_PTR_ID addrVal = reader->ReadAddr(NSTokens::Key::ptrIdVal, true);
                  res = TTD_COERCE_PTR_ID_TO_VAR(addrVal);
              }
  
              reader->ReadRecordEnd();
  
              return res;
          }
  
  #if ENABLE_SNAPSHOT_COMPARE
          bool CheckSnapEquivTTDDouble(double d1, double d2)
          {
              if(Js::JavascriptNumber::IsNan(d1) || Js::JavascriptNumber::IsNan(d2))
              {
                  return (Js::JavascriptNumber::IsNan(d1) && Js::JavascriptNumber::IsNan(d2));
              }
              else
              {
                  return (d1 == d2);
              }
          }
  
          void AssertSnapEquivTTDVar_Helper(const TTDVar v1, const TTDVar v2, TTDCompareMap& compareMap, TTDComparePath::StepKind stepKind, const TTDComparePath::PathEntry& next)
          {
              if(v1 == nullptr || v2 == nullptr)
              {
                  compareMap.DiagnosticAssert(v1 == v2);
              }
              else if(Js::TaggedNumber::Is(v1) || Js::TaggedNumber::Is(v2))
              {
                  compareMap.DiagnosticAssert(JsSupport::AreInlineVarsEquiv(v1, v2));
              }
              else
              {
                  compareMap.CheckConsistentAndAddPtrIdMapping_Helper(TTD_CONVERT_VAR_TO_PTR_ID(v1), TTD_CONVERT_VAR_TO_PTR_ID(v2), stepKind, next);
              }
          }
  
          void AssertSnapEquivTTDVar_Property(const TTDVar v1, const TTDVar v2, TTDCompareMap& compareMap, Js::PropertyId pid)
          {
              TTDComparePath::PathEntry next{ (uint32)pid, nullptr };
              AssertSnapEquivTTDVar_Helper(v1, v2, compareMap, TTDComparePath::StepKind::PropertyData, next);
          }
  
          void AssertSnapEquivTTDVar_PropertyGetter(const TTDVar v1, const TTDVar v2, TTDCompareMap& compareMap, Js::PropertyId pid)
          {
              TTDComparePath::PathEntry next{ (uint32)pid, nullptr };
              AssertSnapEquivTTDVar_Helper(v1, v2, compareMap, TTDComparePath::StepKind::PropertyGetter, next);
          }
  
          void AssertSnapEquivTTDVar_PropertySetter(const TTDVar v1, const TTDVar v2, TTDCompareMap& compareMap, Js::PropertyId pid)
          {
              TTDComparePath::PathEntry next{ (uint32)pid, nullptr };
              AssertSnapEquivTTDVar_Helper(v1, v2, compareMap, TTDComparePath::StepKind::PropertySetter, next);
          }
  
          void AssertSnapEquivTTDVar_Array(const TTDVar v1, const TTDVar v2, TTDCompareMap& compareMap, uint32 index)
          {
              TTDComparePath::PathEntry next{ index, nullptr };
              AssertSnapEquivTTDVar_Helper(v1, v2, compareMap, TTDComparePath::StepKind::Array, next);
          }
  
          void AssertSnapEquivTTDVar_SlotArray(const TTDVar v1, const TTDVar v2, TTDCompareMap& compareMap, uint32 index)
          {
              TTDComparePath::PathEntry next{ index, nullptr };
              AssertSnapEquivTTDVar_Helper(v1, v2, compareMap, TTDComparePath::StepKind::SlotArray, next);
          }
  
          void AssertSnapEquivTTDVar_Special(const TTDVar v1, const TTDVar v2, TTDCompareMap& compareMap, const char16* specialField)
          {
              TTDComparePath::PathEntry next{ -1, specialField };
              AssertSnapEquivTTDVar_Helper(v1, v2, compareMap, TTDComparePath::StepKind::Special, next);
          }
  
          void AssertSnapEquivTTDVar_SpecialArray(const TTDVar v1, const TTDVar v2, TTDCompareMap& compareMap, const char16* specialField, uint32 index)
          {
              TTDComparePath::PathEntry next{ index, specialField };
              AssertSnapEquivTTDVar_Helper(v1, v2, compareMap, TTDComparePath::StepKind::SpecialArray, next);
          }
  #endif
  
          //////////////////
  
          void ExtractSnapPrimitiveValue(SnapPrimitiveValue* snapValue, Js::RecyclableObject* jsValue, bool isWellKnown, const TTDIdentifierDictionary<TTD_PTR_ID, NSSnapType::SnapType*>& idToTypeMap, SlabAllocator& alloc)
          {
              snapValue->PrimitiveValueId = TTD_CONVERT_VAR_TO_PTR_ID(jsValue);
  
              Js::Type* objType = jsValue->GetType();
              snapValue->SnapType = idToTypeMap.LookupKnownItem(TTD_CONVERT_TYPEINFO_TO_PTR_ID(objType));
  
              TTD_WELLKNOWN_TOKEN lookupToken = isWellKnown ? jsValue->GetScriptContext()->TTDWellKnownInfo->ResolvePathForKnownObject(jsValue) : TTD_INVALID_WELLKNOWN_TOKEN;
              snapValue->OptWellKnownToken = alloc.CopyRawNullTerminatedStringInto(lookupToken);
  
              if(snapValue->OptWellKnownToken == TTD_INVALID_WELLKNOWN_TOKEN)
              {
                  Js::JavascriptLibrary* jslib = jsValue->GetLibrary();
  
                  switch(snapValue->SnapType->JsTypeId)
                  {
                  case Js::TypeIds_Undefined:
                  case Js::TypeIds_Null:
                      break;
                  case Js::TypeIds_Boolean:
                      snapValue->u_boolValue = Js::JavascriptBoolean::FromVar(jsValue)->GetValue();
                      break;
                  case Js::TypeIds_Number:
                      snapValue->u_doubleValue = Js::JavascriptNumber::GetValue(jsValue);
                      break;
                  case Js::TypeIds_Int64Number:
                      snapValue->u_int64Value = Js::JavascriptInt64Number::FromVar(jsValue)->GetValue();
                      break;
                  case Js::TypeIds_UInt64Number:
                      snapValue->u_uint64Value = Js::JavascriptUInt64Number::FromVar(jsValue)->GetValue();
                      break;
                  case Js::TypeIds_String:
                      snapValue->u_stringValue = alloc.SlabAllocateStruct<TTString>();
                      alloc.CopyStringIntoWLength(Js::JavascriptString::FromVar(jsValue)->GetSz(), Js::JavascriptString::FromVar(jsValue)->GetLength(), *(snapValue->u_stringValue));
                      break;
                  case Js::TypeIds_Symbol:
                      snapValue->u_propertyIdValue = jslib->ExtractPrimitveSymbolId_TTD(jsValue);
                      break;
                  default:
                      TTDAssert(false, "These are supposed to be primitive values on the heap e.g., no pointers or properties.");
                      break;
                  }
              }
          }
  
          void InflateSnapPrimitiveValue(const SnapPrimitiveValue* snapValue, InflateMap* inflator)
          {
              Js::ScriptContext* ctx = inflator->LookupScriptContext(snapValue->SnapType->ScriptContextLogId);
              Js::JavascriptLibrary* jslib = ctx->GetLibrary();
  
              Js::Var res = nullptr;
              if(snapValue->OptWellKnownToken != TTD_INVALID_WELLKNOWN_TOKEN)
              {
                  res = ctx->TTDWellKnownInfo->LookupKnownObjectFromPath(snapValue->OptWellKnownToken);
              }
              else
              {
                  Js::RecyclableObject* rObj = inflator->FindReusableObjectIfExists(snapValue->PrimitiveValueId);
                  if(rObj != nullptr)
                  {
                      res = rObj;
                  }
                  else
                  {
                      switch(snapValue->SnapType->JsTypeId)
                      {
                      case Js::TypeIds_Undefined:
                          res = jslib->GetUndefined();
                          break;
                      case Js::TypeIds_Null:
                          res = jslib->GetNull();
                          break;
                      case Js::TypeIds_Boolean:
                          res = (snapValue->u_boolValue ? jslib->GetTrue() : jslib->GetFalse());
                          break;
                      case Js::TypeIds_Number:
                          res = Js::JavascriptNumber::ToVarWithCheck(snapValue->u_doubleValue, ctx);
                          break;
                      case Js::TypeIds_Int64Number:
                          res = Js::JavascriptInt64Number::ToVar(snapValue->u_int64Value, ctx);
                          break;
                      case Js::TypeIds_UInt64Number:
                          res = Js::JavascriptUInt64Number::ToVar(snapValue->u_uint64Value, ctx);
                          break;
                      case Js::TypeIds_String:
                          res = Js::JavascriptString::NewCopyBuffer(snapValue->u_stringValue->Contents, snapValue->u_stringValue->Length, ctx);
                          break;
                      case Js::TypeIds_Symbol:
                          res = jslib->CreatePrimitveSymbol_TTD(snapValue->u_propertyIdValue);
                          break;
                      default:
                          TTDAssert(false, "These are supposed to be primitive values e.g., no pointers or properties.");
                          res = nullptr;
                      }
                  }
              }
  
              inflator->AddObject(snapValue->PrimitiveValueId, Js::RecyclableObject::FromVar(res));
          }
  
          void EmitSnapPrimitiveValue(const SnapPrimitiveValue* snapValue, FileWriter* writer, NSTokens::Separator separator)
          {
              writer->WriteRecordStart(separator);
              writer->WriteAddr(NSTokens::Key::primitiveId, snapValue->PrimitiveValueId);
              writer->WriteAddr(NSTokens::Key::typeId, snapValue->SnapType->TypePtrId, NSTokens::Separator::CommaSeparator);
  
              writer->WriteBool(NSTokens::Key::isWellKnownToken, snapValue->OptWellKnownToken != TTD_INVALID_WELLKNOWN_TOKEN, NSTokens::Separator::CommaSeparator);
              if(snapValue->OptWellKnownToken != TTD_INVALID_WELLKNOWN_TOKEN)
              {
                  writer->WriteWellKnownToken(NSTokens::Key::wellKnownToken, snapValue->OptWellKnownToken, NSTokens::Separator::CommaSeparator);
              }
              else
              {
                  switch(snapValue->SnapType->JsTypeId)
                  {
                  case Js::TypeIds_Undefined:
                  case Js::TypeIds_Null:
                      break;
                  case Js::TypeIds_Boolean:
                      writer->WriteBool(NSTokens::Key::boolVal, !!snapValue->u_boolValue, NSTokens::Separator::CommaSeparator);
                      break;
                  case Js::TypeIds_Number:
                      writer->WriteDouble(NSTokens::Key::doubleVal, snapValue->u_doubleValue, NSTokens::Separator::CommaSeparator);
                      break;
                  case Js::TypeIds_Int64Number:
                      writer->WriteInt64(NSTokens::Key::i64Val, snapValue->u_int64Value, NSTokens::Separator::CommaSeparator);
                      break;
                  case Js::TypeIds_UInt64Number:
                      writer->WriteUInt64(NSTokens::Key::u64Val, snapValue->u_uint64Value, NSTokens::Separator::CommaSeparator);
                      break;
                  case Js::TypeIds_String:
                      writer->WriteString(NSTokens::Key::stringVal, *(snapValue->u_stringValue), NSTokens::Separator::CommaSeparator);
                      break;
                  case Js::TypeIds_Symbol:
                      writer->WriteInt32(NSTokens::Key::propertyId, snapValue->u_propertyIdValue, NSTokens::Separator::CommaSeparator);
                      break;
                  default:
                      TTDAssert(false, "These are supposed to be primitive values e.g., no pointers or properties.");
                      break;
                  }
              }
              writer->WriteRecordEnd();
          }
  
          void ParseSnapPrimitiveValue(SnapPrimitiveValue* snapValue, bool readSeparator, FileReader* reader, SlabAllocator& alloc, const TTDIdentifierDictionary<TTD_PTR_ID, NSSnapType::SnapType*>& ptrIdToTypeMap)
          {
              reader->ReadRecordStart(readSeparator);
              snapValue->PrimitiveValueId = reader->ReadAddr(NSTokens::Key::primitiveId);
  
              TTD_PTR_ID snapTypeId = reader->ReadAddr(NSTokens::Key::typeId, true);
              snapValue->SnapType = ptrIdToTypeMap.LookupKnownItem(snapTypeId);
  
              bool isWellKnown = reader->ReadBool(NSTokens::Key::isWellKnownToken, true);
              if(isWellKnown)
              {
                  snapValue->OptWellKnownToken = reader->ReadWellKnownToken(NSTokens::Key::wellKnownToken, alloc, true);
  
                  //just clear it to help avoid any confusion later
                  snapValue->u_uint64Value = 0;
              }
              else
              {
                  snapValue->OptWellKnownToken = nullptr;
  
                  switch(snapValue->SnapType->JsTypeId)
                  {
                  case Js::TypeIds_Undefined:
                  case Js::TypeIds_Null:
                      break;
                  case Js::TypeIds_Boolean:
                      snapValue->u_boolValue = reader->ReadBool(NSTokens::Key::boolVal, true);
                      break;
                  case Js::TypeIds_Number:
                      snapValue->u_doubleValue = reader->ReadDouble(NSTokens::Key::doubleVal, true);
                      break;
                  case Js::TypeIds_Int64Number:
                      snapValue->u_int64Value = reader->ReadInt64(NSTokens::Key::i64Val, true);
                      break;
                  case Js::TypeIds_UInt64Number:
                      snapValue->u_uint64Value = reader->ReadUInt64(NSTokens::Key::u64Val, true);
                      break;
                  case Js::TypeIds_String:
                      snapValue->u_stringValue = alloc.SlabAllocateStruct<TTString>();
                      reader->ReadString(NSTokens::Key::stringVal, alloc, *(snapValue->u_stringValue), true);
                      break;
                  case Js::TypeIds_Symbol:
                      snapValue->u_propertyIdValue = (Js::PropertyId)reader->ReadInt32(NSTokens::Key::propertyId, true);
                      break;
                  default:
                      TTDAssert(false, "These are supposed to be primitive values e.g., no pointers or properties.");
                      break;
                  }
              }
  
              reader->ReadRecordEnd();
          }
  
  #if ENABLE_SNAPSHOT_COMPARE
          void AssertSnapEquiv(const SnapPrimitiveValue* v1, const SnapPrimitiveValue* v2, TTDCompareMap& compareMap)
          {
              compareMap.DiagnosticAssert(v1->SnapType->JsTypeId == v2->SnapType->JsTypeId);
  
              NSSnapType::AssertSnapEquiv(v1->SnapType, v2->SnapType, compareMap);
  
              if(v1->OptWellKnownToken != TTD_INVALID_WELLKNOWN_TOKEN || v2->OptWellKnownToken != TTD_INVALID_WELLKNOWN_TOKEN)
              {
                  compareMap.DiagnosticAssert(v1->OptWellKnownToken != TTD_INVALID_WELLKNOWN_TOKEN && v2->OptWellKnownToken != TTD_INVALID_WELLKNOWN_TOKEN);
                  compareMap.DiagnosticAssert(TTD_DIAGNOSTIC_COMPARE_WELLKNOWN_TOKENS(v1->OptWellKnownToken, v2->OptWellKnownToken));
              }
              else
              {
                  switch(v1->SnapType->JsTypeId)
                  {
                  case Js::TypeIds_Undefined:
                  case Js::TypeIds_Null:
                      break;
                  case Js::TypeIds_Boolean:
                      compareMap.DiagnosticAssert((!!v1->u_boolValue) == (!!v2->u_boolValue));
                      break;
                  case Js::TypeIds_Number:
                      compareMap.DiagnosticAssert(CheckSnapEquivTTDDouble(v1->u_doubleValue, v2->u_doubleValue));
                      break;
                  case Js::TypeIds_Int64Number:
                      compareMap.DiagnosticAssert(v1->u_int64Value == v2->u_int64Value);
                      break;
                  case Js::TypeIds_UInt64Number:
                      compareMap.DiagnosticAssert(v1->u_uint64Value == v2->u_uint64Value);
                      break;
                  case Js::TypeIds_String:
                      compareMap.DiagnosticAssert(TTStringEQForDiagnostics(*(v1->u_stringValue), *(v2->u_stringValue)));
                      break;
                  case Js::TypeIds_Symbol:
                      compareMap.DiagnosticAssert(v1->u_propertyIdValue == v2->u_propertyIdValue);
                      break;
                  default:
                      TTDAssert(false, "These are supposed to be primitive values e.g., no pointers or properties.");
                      break;
                  }
              }
          }
  #endif
  
          //////////////////
  
          Js::Var* InflateSlotArrayInfo(const SlotArrayInfo* slotInfo, InflateMap* inflator)
          {
              Js::ScriptContext* ctx = inflator->LookupScriptContext(slotInfo->ScriptContextLogId);
              Field(Js::Var)* slotArray = RecyclerNewArray(ctx->GetRecycler(), Field(Js::Var), slotInfo->SlotCount + Js::ScopeSlots::FirstSlotIndex);
  
              Js::ScopeSlots scopeSlots((Js::Var*)slotArray);
              scopeSlots.SetCount(slotInfo->SlotCount);
  
              Js::Var undef = ctx->GetLibrary()->GetUndefined();
              for(uint32 j = 0; j < slotInfo->SlotCount; j++)
              {
                  scopeSlots.Set(j, undef);
              }
  
              if(slotInfo->isFunctionBodyMetaData)
              {
                  Js::FunctionBody* fbody = inflator->LookupFunctionBody(slotInfo->OptFunctionBodyId);
                  scopeSlots.SetScopeMetadata(fbody->GetFunctionInfo());
  
                  //This is a doubly nested lookup so if the scope slot array is large this could be expensive
                  Js::PropertyId* propertyIds = fbody->GetPropertyIdsForScopeSlotArray();
                  for(uint32 j = 0; j < slotInfo->SlotCount; j++)
                  {
                      Js::PropertyId trgtPid = slotInfo->PIDArray[j];
                      for(uint32 i = 0; i < fbody->GetScopeSlotArraySize(); i++)
                      {
                          if(trgtPid == propertyIds[i])
                          {
                              Js::Var sval = inflator->InflateTTDVar(slotInfo->Slots[j]);
                              scopeSlots.Set(i, sval);
                              break;
                          }
                      }
                  }
              }
              else
              {
                  Js::DebuggerScope* dbgScope = nullptr;
                  if(slotInfo->OptWellKnownDbgScope != TTD_INVALID_WELLKNOWN_TOKEN)
                  {
                      dbgScope = ctx->TTDWellKnownInfo->LookupKnownDebuggerScopeFromPath(slotInfo->OptWellKnownDbgScope);
                  }
                  else
                  {
                      Js::FunctionBody* scopeBody = nullptr;
                      int32 scopeIndex = -1;
                      inflator->LookupInfoForDebugScope(slotInfo->OptDebugScopeId, &scopeBody, &scopeIndex);
  
                      dbgScope = scopeBody->GetScopeObjectChain()->pScopeChain->Item(scopeIndex);
                  }
  
                  scopeSlots.SetScopeMetadata(dbgScope);
  
                  //This is a doubly nested lookup so if the scope slot array is large this could be expensive
                  for(uint32 j = 0; j < slotInfo->SlotCount; j++)
                  {
                      Js::PropertyId trgtPid = slotInfo->PIDArray[j];
                      for(uint32 i = 0; i < slotInfo->SlotCount; i++)
                      {
                          if(trgtPid == dbgScope->GetPropertyIdForSlotIndex_TTD(i))
                          {
                              Js::Var sval = inflator->InflateTTDVar(slotInfo->Slots[j]);
                              scopeSlots.Set(i, sval);
                              break;
                          }
                      }
                  }
              }
  
              return (Js::Var*)slotArray;
          }
  
          void EmitSlotArrayInfo(const SlotArrayInfo* slotInfo, FileWriter* writer, NSTokens::Separator separator)
          {
              writer->WriteRecordStart(separator);
              writer->AdjustIndent(1);
  
              writer->WriteAddr(NSTokens::Key::slotId, slotInfo->SlotId, NSTokens::Separator::BigSpaceSeparator);
              writer->WriteLogTag(NSTokens::Key::ctxTag, slotInfo->ScriptContextLogId, NSTokens::Separator::CommaSeparator);
  
              writer->WriteBool(NSTokens::Key::isFunctionMetaData, slotInfo->isFunctionBodyMetaData, NSTokens::Separator::CommaSeparator);
              if(slotInfo->isFunctionBodyMetaData)
              {
                  writer->WriteAddr(NSTokens::Key::functionBodyId, slotInfo->OptFunctionBodyId, NSTokens::Separator::CommaSeparator);
              }
              else
              {
                  writer->WriteBool(NSTokens::Key::isWellKnownToken, slotInfo->OptWellKnownDbgScope != TTD_INVALID_WELLKNOWN_TOKEN, NSTokens::Separator::CommaSeparator);
                  if(slotInfo->OptWellKnownDbgScope != TTD_INVALID_WELLKNOWN_TOKEN)
                  {
                      writer->WriteWellKnownToken(NSTokens::Key::wellKnownToken, slotInfo->OptWellKnownDbgScope, NSTokens::Separator::CommaSeparator);
                  }
                  else
                  {
                      writer->WriteAddr(NSTokens::Key::debuggerScopeId, slotInfo->OptDebugScopeId, NSTokens::Separator::CommaSeparator);
                  }
              }
  
              writer->WriteLengthValue(slotInfo->SlotCount, NSTokens::Separator::CommaAndBigSpaceSeparator);
              writer->WriteSequenceStart_DefaultKey(NSTokens::Separator::CommaAndBigSpaceSeparator);
              writer->AdjustIndent(1);
              for(uint32 i = 0; i < slotInfo->SlotCount; ++i)
              {
                  writer->WriteRecordStart(i != 0 ? NSTokens::Separator::CommaAndBigSpaceSeparator : NSTokens::Separator::BigSpaceSeparator);
                  writer->WriteUInt32(NSTokens::Key::pid, slotInfo->PIDArray[i], NSTokens::Separator::NoSeparator);
                  writer->WriteKey(NSTokens::Key::entry, NSTokens::Separator::CommaSeparator);
  
                  EmitTTDVar(slotInfo->Slots[i], writer, NSTokens::Separator::NoSeparator);
  
                  writer->WriteRecordEnd();
              }
              writer->AdjustIndent(-1);
              writer->WriteSequenceEnd(NSTokens::Separator::BigSpaceSeparator);
  
              writer->AdjustIndent(-1);
              writer->WriteRecordEnd(NSTokens::Separator::BigSpaceSeparator);
          }
  
          void ParseSlotArrayInfo(SlotArrayInfo* slotInfo, bool readSeparator, FileReader* reader, SlabAllocator& alloc)
          {
              reader->ReadRecordStart(readSeparator);
  
              slotInfo->SlotId = reader->ReadAddr(NSTokens::Key::slotId);
              slotInfo->ScriptContextLogId = reader->ReadLogTag(NSTokens::Key::ctxTag, true);
  
              slotInfo->isFunctionBodyMetaData = reader->ReadBool(NSTokens::Key::isFunctionMetaData, true);
              slotInfo->OptFunctionBodyId = TTD_INVALID_PTR_ID;
              slotInfo->OptDebugScopeId = TTD_INVALID_PTR_ID;
              slotInfo->OptWellKnownDbgScope = TTD_INVALID_WELLKNOWN_TOKEN;
  
              if(slotInfo->isFunctionBodyMetaData)
              {
                  slotInfo->OptFunctionBodyId = reader->ReadAddr(NSTokens::Key::functionBodyId, true);
              }
              else
              {
                  bool isWellKnown = reader->ReadBool(NSTokens::Key::isWellKnownToken, true);
                  if(isWellKnown)
                  {
                      slotInfo->OptWellKnownDbgScope = reader->ReadWellKnownToken(NSTokens::Key::wellKnownToken, alloc, true);
                  }
                  else
                  {
                      slotInfo->OptDebugScopeId = reader->ReadAddr(NSTokens::Key::debuggerScopeId, true);
                  }
              }
  
              slotInfo->SlotCount = reader->ReadLengthValue(true);
              reader->ReadSequenceStart_WDefaultKey(true);
  
              slotInfo->Slots = alloc.SlabAllocateArray<TTDVar>(slotInfo->SlotCount);
  
              slotInfo->PIDArray = alloc.SlabAllocateArray<Js::PropertyId>(slotInfo->SlotCount);
  
              for(uint32 i = 0; i < slotInfo->SlotCount; ++i)
              {
                  reader->ReadRecordStart(i != 0);
                  slotInfo->PIDArray[i] = (Js::PropertyId)reader->ReadUInt32(NSTokens::Key::pid);
                  reader->ReadKey(NSTokens::Key::entry, true);
  
                  slotInfo->Slots[i] = ParseTTDVar(false, reader);
  
                  reader->ReadRecordEnd();
              }
              reader->ReadSequenceEnd();
  
              reader->ReadRecordEnd();
          }
  
  #if ENABLE_SNAPSHOT_COMPARE
          void AssertSnapEquiv(const SlotArrayInfo* sai1, const SlotArrayInfo* sai2, TTDCompareMap& compareMap)
          {
              compareMap.DiagnosticAssert(sai1->ScriptContextLogId == sai2->ScriptContextLogId);
  
              compareMap.DiagnosticAssert(sai1->isFunctionBodyMetaData == sai2->isFunctionBodyMetaData);
              if(sai1->isFunctionBodyMetaData)
              {
                  compareMap.CheckConsistentAndAddPtrIdMapping_FunctionBody(sai1->OptFunctionBodyId, sai2->OptFunctionBodyId);
              }
              else
              {
                  //TODO: emit debugger scope metadata
              }
  
              compareMap.DiagnosticAssert(sai1->SlotCount == sai2->SlotCount);
              for(uint32 i = 0; i < sai1->SlotCount; ++i)
              {
                  Js::PropertyId id1 = sai1->PIDArray[i];
                  bool found = false;
                  for(uint32 j = 0; j < sai1->SlotCount; ++j)
                  {
                      if(id1 == sai2->PIDArray[j])
                      {
                          AssertSnapEquivTTDVar_SlotArray(sai1->Slots[i], sai2->Slots[j], compareMap, i);
                          found = true;
                          break;
                      }
                  }
                  //TODO: We see this hit in a case where record has all values in a slot array when replaying --replay-debug (but not --replay).
                  //      In the debug version the propertyId is in a Js::DebuggerScopeProperty instead. So this needs to be investigated in both extract and inflate.
                  compareMap.DiagnosticAssert(found);
              }
          }
  #endif
  
          Js::FrameDisplay* InflateScriptFunctionScopeInfo(const ScriptFunctionScopeInfo* funcScopeInfo, InflateMap* inflator)
          {
              Js::ScriptContext* ctx = inflator->LookupScriptContext(funcScopeInfo->ScriptContextLogId);
  
              Js::FrameDisplay* environment = RecyclerNewPlus(ctx->GetRecycler(), funcScopeInfo->ScopeCount * sizeof(Js::Var), Js::FrameDisplay, funcScopeInfo->ScopeCount);
  
              for(uint16 i = 0; i < funcScopeInfo->ScopeCount; ++i)
              {
                  const ScopeInfoEntry& scp = funcScopeInfo->ScopeArray[i];
  
                  switch(scp.Tag)
                  {
                  case Js::ScopeType::ScopeType_ActivationObject:
                  case Js::ScopeType::ScopeType_WithScope:
                  {
                      Js::Var sval = inflator->LookupObject(scp.IDValue);
                      environment->SetItem(i, sval);
                      break;
                  }
                  case Js::ScopeType::ScopeType_SlotArray:
                  {
                      Js::Var* saval = inflator->LookupSlotArray(scp.IDValue);
                      environment->SetItem(i, saval);
                      break;
                  }
                  default:
                      TTDAssert(false, "Unknown scope kind");
                      break;
                  }
              }
  
              return environment;
          }
  
          void EmitScriptFunctionScopeInfo(const ScriptFunctionScopeInfo* funcScopeInfo, FileWriter* writer, NSTokens::Separator separator)
          {
              writer->WriteRecordStart(separator);
  
              writer->WriteAddr(NSTokens::Key::scopeId, funcScopeInfo->ScopeId);
              writer->WriteLogTag(NSTokens::Key::ctxTag, funcScopeInfo->ScriptContextLogId, NSTokens::Separator::CommaSeparator);
              writer->WriteUInt32(NSTokens::Key::count, funcScopeInfo->ScopeCount, NSTokens::Separator::CommaSeparator);
  
              writer->WriteSequenceStart_DefaultKey(NSTokens::Separator::CommaSeparator);
  
              for(uint32 i = 0; i < funcScopeInfo->ScopeCount; ++i)
              {
                  writer->WriteRecordStart(i != 0 ? NSTokens::Separator::CommaSeparator : NSTokens::Separator::NoSeparator);
  
                  writer->WriteTag<Js::ScopeType>(NSTokens::Key::scopeType, funcScopeInfo->ScopeArray[i].Tag);
                  writer->WriteAddr(NSTokens::Key::subscopeId, funcScopeInfo->ScopeArray[i].IDValue, NSTokens::Separator::CommaSeparator);
  
                  writer->WriteRecordEnd();
              }
  
              writer->WriteSequenceEnd();
  
              writer->WriteRecordEnd();
          }
  
          void ParseScriptFunctionScopeInfo(ScriptFunctionScopeInfo* funcScopeInfo, bool readSeparator, FileReader* reader, SlabAllocator& alloc)
          {
              reader->ReadRecordStart(readSeparator);
  
              funcScopeInfo->ScopeId = reader->ReadAddr(NSTokens::Key::scopeId);
              funcScopeInfo->ScriptContextLogId = reader->ReadLogTag(NSTokens::Key::ctxTag, true);
              funcScopeInfo->ScopeCount = (uint16)reader->ReadUInt32(NSTokens::Key::count, true);
  
              reader->ReadSequenceStart_WDefaultKey(true);
  
              funcScopeInfo->ScopeArray = alloc.SlabAllocateArray<ScopeInfoEntry>(funcScopeInfo->ScopeCount);
              for(uint32 i = 0; i < funcScopeInfo->ScopeCount; ++i)
              {
                  reader->ReadRecordStart(i != 0);
  
                  funcScopeInfo->ScopeArray[i].Tag = reader->ReadTag<Js::ScopeType>(NSTokens::Key::scopeType);
                  funcScopeInfo->ScopeArray[i].IDValue = reader->ReadAddr(NSTokens::Key::subscopeId, true);
  
                  reader->ReadRecordEnd();
              }
  
              reader->ReadSequenceEnd();
  
              reader->ReadRecordEnd();
          }
  
  #if ENABLE_SNAPSHOT_COMPARE
          void AssertSnapEquiv(const ScriptFunctionScopeInfo* funcScopeInfo1, const ScriptFunctionScopeInfo* funcScopeInfo2, TTDCompareMap& compareMap)
          {
              compareMap.DiagnosticAssert(funcScopeInfo1->ScriptContextLogId == funcScopeInfo2->ScriptContextLogId);
  
              compareMap.DiagnosticAssert(funcScopeInfo1->ScopeCount == funcScopeInfo2->ScopeCount);
              for(uint32 i = 0; i < funcScopeInfo1->ScopeCount; ++i)
              {
                  compareMap.DiagnosticAssert(funcScopeInfo1->ScopeArray[i].Tag == funcScopeInfo2->ScopeArray[i].Tag);
                  compareMap.CheckConsistentAndAddPtrIdMapping_Scope(funcScopeInfo1->ScopeArray[i].IDValue, funcScopeInfo2->ScopeArray[i].IDValue, i);
              }
          }
  #endif
  
          //////////////////
  
          Js::JavascriptPromiseCapability* InflatePromiseCapabilityInfo(const SnapPromiseCapabilityInfo* capabilityInfo, Js::ScriptContext* ctx, InflateMap* inflator)
          {
              if(!inflator->IsPromiseInfoDefined<Js::JavascriptPromiseCapability>(capabilityInfo->CapabilityId))
              {
                  Js::Var promise = inflator->InflateTTDVar(capabilityInfo->PromiseVar);
                  Js::Var resolve = inflator->InflateTTDVar(capabilityInfo->ResolveVar);
                  Js::Var reject = inflator->InflateTTDVar(capabilityInfo->RejectVar);
  
                  Js::JavascriptPromiseCapability* res = ctx->GetLibrary()->CreatePromiseCapability_TTD(promise, resolve, reject);
                  inflator->AddInflatedPromiseInfo<Js::JavascriptPromiseCapability>(capabilityInfo->CapabilityId, res);
              }
  
              return inflator->LookupInflatedPromiseInfo<Js::JavascriptPromiseCapability>(capabilityInfo->CapabilityId);
          }
  
          void EmitPromiseCapabilityInfo(const SnapPromiseCapabilityInfo* capabilityInfo, FileWriter* writer, NSTokens::Separator separator)
          {
              writer->WriteRecordStart(separator);
  
              writer->WriteAddr(NSTokens::Key::ptrIdVal, capabilityInfo->CapabilityId);
  
              writer->WriteKey(NSTokens::Key::entry, NSTokens::Separator::CommaSeparator);
              EmitTTDVar(capabilityInfo->PromiseVar, writer, NSTokens::Separator::NoSeparator);
  
              writer->WriteKey(NSTokens::Key::entry, NSTokens::Separator::CommaSeparator);
              EmitTTDVar(capabilityInfo->ResolveVar, writer, NSTokens::Separator::NoSeparator);
  
              writer->WriteKey(NSTokens::Key::entry, NSTokens::Separator::CommaSeparator);
              EmitTTDVar(capabilityInfo->RejectVar, writer, NSTokens::Separator::NoSeparator);
  
              writer->WriteRecordEnd();
          }
  
          void ParsePromiseCapabilityInfo(SnapPromiseCapabilityInfo* capabilityInfo, bool readSeparator, FileReader* reader, SlabAllocator& alloc)
          {
              reader->ReadRecordStart(readSeparator);
  
              capabilityInfo->CapabilityId = reader->ReadAddr(NSTokens::Key::ptrIdVal);
  
              reader->ReadKey(NSTokens::Key::entry, true);
              capabilityInfo->PromiseVar = ParseTTDVar(false, reader);
  
              reader->ReadKey(NSTokens::Key::entry, true);
              capabilityInfo->ResolveVar = ParseTTDVar(false, reader);
  
              reader->ReadKey(NSTokens::Key::entry, true);
              capabilityInfo->RejectVar = ParseTTDVar(false, reader);
  
              reader->ReadRecordEnd();
          }
  
  
  #if ENABLE_SNAPSHOT_COMPARE
          void AssertSnapEquiv(const SnapPromiseCapabilityInfo* capabilityInfo1, const SnapPromiseCapabilityInfo* capabilityInfo2, TTDCompareMap& compareMap)
          {
              compareMap.CheckConsistentAndAddPtrIdMapping_NoEnqueue(capabilityInfo1->CapabilityId, capabilityInfo2->CapabilityId);
  
              AssertSnapEquivTTDVar_Special(capabilityInfo1->PromiseVar, capabilityInfo2->PromiseVar, compareMap, _u("promiseVar"));
              AssertSnapEquivTTDVar_Special(capabilityInfo1->ResolveVar, capabilityInfo2->ResolveVar, compareMap, _u("resolveObjId"));
              AssertSnapEquivTTDVar_Special(capabilityInfo1->RejectVar, capabilityInfo2->RejectVar, compareMap, _u("rejectObjId"));
          }
  #endif
  
          Js::JavascriptPromiseReaction* InflatePromiseReactionInfo(const SnapPromiseReactionInfo* reactionInfo, Js::ScriptContext* ctx, InflateMap* inflator)
          {
              if(!inflator->IsPromiseInfoDefined<Js::JavascriptPromiseReaction>(reactionInfo->PromiseReactionId))
              {
                  Js::RecyclableObject* handler = inflator->LookupObject(reactionInfo->HandlerObjId);
                  Js::JavascriptPromiseCapability* capabilities = InflatePromiseCapabilityInfo(&reactionInfo->Capabilities, ctx, inflator);
  
                  Js::JavascriptPromiseReaction* res = ctx->GetLibrary()->CreatePromiseReaction_TTD(handler, capabilities);
                  inflator->AddInflatedPromiseInfo<Js::JavascriptPromiseReaction>(reactionInfo->PromiseReactionId, res);
              }
  
              return inflator->LookupInflatedPromiseInfo<Js::JavascriptPromiseReaction>(reactionInfo->PromiseReactionId);
          }
  
          void EmitPromiseReactionInfo(const SnapPromiseReactionInfo* reactionInfo, FileWriter* writer, NSTokens::Separator separator)
          {
              writer->WriteRecordStart(separator);
  
              writer->WriteAddr(NSTokens::Key::ptrIdVal, reactionInfo->PromiseReactionId);
  
              writer->WriteAddr(NSTokens::Key::ptrIdVal, reactionInfo->HandlerObjId, NSTokens::Separator::CommaSeparator);
              writer->WriteKey(NSTokens::Key::entry, NSTokens::Separator::CommaSeparator);
              EmitPromiseCapabilityInfo(&reactionInfo->Capabilities, writer, NSTokens::Separator::NoSeparator);
  
              writer->WriteRecordEnd();
          }
  
          void ParsePromiseReactionInfo(SnapPromiseReactionInfo* reactionInfo, bool readSeparator, FileReader* reader, SlabAllocator& alloc)
          {
              reader->ReadRecordStart(readSeparator);
  
              reactionInfo->PromiseReactionId = reader->ReadAddr(NSTokens::Key::ptrIdVal);
  
              reactionInfo->HandlerObjId = reader->ReadAddr(NSTokens::Key::ptrIdVal, true);
              reader->ReadKey(NSTokens::Key::entry, true);
              ParsePromiseCapabilityInfo(&reactionInfo->Capabilities, false, reader, alloc);
  
              reader->ReadRecordEnd();
          }
  
  #if ENABLE_SNAPSHOT_COMPARE
          void AssertSnapEquiv(const SnapPromiseReactionInfo* reactionInfo1, const SnapPromiseReactionInfo* reactionInfo2, TTDCompareMap& compareMap)
          {
              compareMap.CheckConsistentAndAddPtrIdMapping_NoEnqueue(reactionInfo1->PromiseReactionId, reactionInfo2->PromiseReactionId);
  
              compareMap.CheckConsistentAndAddPtrIdMapping_Special(reactionInfo1->HandlerObjId, reactionInfo2->HandlerObjId, _u("handlerObjId"));
              AssertSnapEquiv(&(reactionInfo1->Capabilities), &(reactionInfo2->Capabilities), compareMap);
          }
  #endif
  
          //////////////////
  
          void ExtractSnapFunctionBodyScopeChain(bool isWellKnownFunction, SnapFunctionBodyScopeChain& scopeChain, Js::FunctionBody* fb, SlabAllocator& alloc)
          {
              scopeChain.ScopeCount = 0;
              scopeChain.ScopeArray = nullptr;
  
              if(!isWellKnownFunction && fb->GetScopeObjectChain() != nullptr)
              {
                  Js::ScopeObjectChain* scChain = fb->GetScopeObjectChain();
                  scopeChain.ScopeCount = (uint32)scChain->pScopeChain->Count();
  
                  if(scopeChain.ScopeCount == 0)
                  {
                      scopeChain.ScopeArray = nullptr;
                  }
                  else
                  {
                      scopeChain.ScopeArray = alloc.SlabAllocateArray<TTD_PTR_ID>(scopeChain.ScopeCount);
  
                      for(int32 i = 0; i < scChain->pScopeChain->Count(); ++i)
                      {
                          Js::DebuggerScope* dbgScope = scChain->pScopeChain->Item(i);
                          scopeChain.ScopeArray[i] = TTD_CONVERT_DEBUGSCOPE_TO_PTR_ID(dbgScope);
                      }
                  }
              }
          }
  
          void EmitSnapFunctionBodyScopeChain(const SnapFunctionBodyScopeChain& scopeChain, FileWriter* writer)
          {
              writer->WriteRecordStart();
  
              writer->WriteLengthValue(scopeChain.ScopeCount);
              writer->WriteSequenceStart_DefaultKey(NSTokens::Separator::CommaSeparator);
              for(uint32 i = 0; i < scopeChain.ScopeCount; ++i)
              {
                  writer->WriteNakedAddr(scopeChain.ScopeArray[i], (i != 0) ? NSTokens::Separator::CommaSeparator : NSTokens::Separator::NoSeparator);
              }
              writer->WriteSequenceEnd();
  
              writer->WriteRecordEnd();
          }
  
          void ParseSnapFunctionBodyScopeChain(SnapFunctionBodyScopeChain& scopeChain, FileReader* reader, SlabAllocator& alloc)
          {
              reader->ReadRecordStart();
  
              scopeChain.ScopeCount = reader->ReadLengthValue();
              scopeChain.ScopeArray = (scopeChain.ScopeCount != 0) ? alloc.SlabAllocateArray<TTD_PTR_ID>(scopeChain.ScopeCount) : nullptr;
  
              reader->ReadSequenceStart_WDefaultKey(true);
              for(uint32 i = 0; i < scopeChain.ScopeCount; ++i)
              {
                  scopeChain.ScopeArray[i] = reader->ReadNakedAddr(i != 0);
              }
              reader->ReadSequenceEnd();
  
              reader->ReadRecordEnd();
          }
  
  #if ENABLE_SNAPSHOT_COMPARE
          void AssertSnapEquiv(const SnapFunctionBodyScopeChain& chain1, const SnapFunctionBodyScopeChain& chain2, TTDCompareMap& compareMap)
          {
              compareMap.DiagnosticAssert(chain1.ScopeCount == chain2.ScopeCount);
  
              //Not sure if there is a way to compare the pointer ids in the two scopes
          }
  #endif
  
          //////////////////
  
          void ExtractTopLevelCommonBodyResolveInfo(TopLevelCommonBodyResolveInfo* fbInfo, Js::FunctionBody* fb, uint32 topLevelCtr, Js::ModuleID moduleId, uint64 sourceContextId, bool isUtf8source, const byte* source, uint32 sourceLen, SlabAllocator& alloc)
          {
              fbInfo->ScriptContextLogId = fb->GetScriptContext()->ScriptContextLogTag;
              fbInfo->TopLevelBodyCtr = topLevelCtr;
  
              alloc.CopyNullTermStringInto(fb->GetDisplayName(), fbInfo->FunctionName);
  
              fbInfo->ModuleId = moduleId;
              fbInfo->SourceContextId = sourceContextId;
              alloc.CopyNullTermStringInto(fb->GetSourceContextInfo()->url, fbInfo->SourceUri);
  
              fbInfo->IsUtf8 = isUtf8source;
              fbInfo->ByteLength = sourceLen;
              fbInfo->SourceBuffer = alloc.SlabAllocateArray<byte>(fbInfo->ByteLength);
              js_memcpy_s(fbInfo->SourceBuffer, fbInfo->ByteLength, source, sourceLen);
  
              fbInfo->DbgSerializedBytecodeSize = 0;
              fbInfo->DbgSerializedBytecodeBuffer = nullptr;
  
              ExtractSnapFunctionBodyScopeChain(false, fbInfo->ScopeChainInfo, fb, alloc);
          }
  
          void EmitTopLevelCommonBodyResolveInfo(const TopLevelCommonBodyResolveInfo* fbInfo, bool emitInline, ThreadContext* threadContext, FileWriter* writer, NSTokens::Separator separator)
          {
              writer->WriteRecordStart(separator);
              writer->WriteUInt32(NSTokens::Key::functionBodyId, fbInfo->TopLevelBodyCtr);
              writer->WriteLogTag(NSTokens::Key::ctxTag, fbInfo->ScriptContextLogId, NSTokens::Separator::CommaSeparator);
  
              writer->WriteString(NSTokens::Key::name, fbInfo->FunctionName, NSTokens::Separator::CommaSeparator);
  
              writer->WriteUInt64(NSTokens::Key::moduleId, fbInfo->ModuleId, NSTokens::Separator::CommaSeparator);
              writer->WriteUInt64(NSTokens::Key::sourceContextId, fbInfo->SourceContextId, NSTokens::Separator::CommaSeparator);
              writer->WriteString(NSTokens::Key::uri, fbInfo->SourceUri, NSTokens::Separator::CommaSeparator);
  
              writer->WriteBool(NSTokens::Key::boolVal, fbInfo->IsUtf8, NSTokens::Separator::CommaSeparator);
              writer->WriteLengthValue(fbInfo->ByteLength, NSTokens::Separator::CommaSeparator);
  
              writer->WriteKey(NSTokens::Key::scopeChain, NSTokens::Separator::CommaSeparator);
              EmitSnapFunctionBodyScopeChain(fbInfo->ScopeChainInfo, writer);
  
              if(emitInline || IsNullPtrTTString(fbInfo->SourceUri))
              {
                  TTDAssert(!fbInfo->IsUtf8, "Should only emit char16 encoded data in inline mode.");
  
                  writer->WriteInlineCode((char16*)fbInfo->SourceBuffer, fbInfo->ByteLength / sizeof(char16), NSTokens::Separator::CommaSeparator);
              }
              else
              {
                  JsSupport::WriteCodeToFile(threadContext, false, fbInfo->TopLevelBodyCtr, fbInfo->IsUtf8, fbInfo->SourceBuffer, fbInfo->ByteLength);
              }
          }
  
          void ParseTopLevelCommonBodyResolveInfo(TopLevelCommonBodyResolveInfo* fbInfo, bool readSeparator, bool parseInline, ThreadContext* threadContext, FileReader* reader, SlabAllocator& alloc)
          {
              reader->ReadRecordStart(readSeparator);
              fbInfo->TopLevelBodyCtr = reader->ReadUInt32(NSTokens::Key::functionBodyId);
              fbInfo->ScriptContextLogId = reader->ReadLogTag(NSTokens::Key::ctxTag, true);
  
              reader->ReadString(NSTokens::Key::name, alloc, fbInfo->FunctionName, true);
  
              fbInfo->ModuleId = (Js::ModuleID)reader->ReadUInt64(NSTokens::Key::moduleId, true);
              fbInfo->SourceContextId = reader->ReadUInt64(NSTokens::Key::sourceContextId, true);
              reader->ReadString(NSTokens::Key::uri, alloc, fbInfo->SourceUri, true);
  
              fbInfo->IsUtf8 = reader->ReadBool(NSTokens::Key::boolVal, true);
              fbInfo->ByteLength = reader->ReadLengthValue(true);
              fbInfo->SourceBuffer = alloc.SlabAllocateArray<byte>(fbInfo->ByteLength);
  
              reader->ReadKey(NSTokens::Key::scopeChain, true);
              ParseSnapFunctionBodyScopeChain(fbInfo->ScopeChainInfo, reader, alloc);
  
              if(parseInline || IsNullPtrTTString(fbInfo->SourceUri))
              {
                  TTDAssert(!fbInfo->IsUtf8, "Should only emit char16 encoded data in inline mode.");
  
                  reader->ReadInlineCode((char16*)fbInfo->SourceBuffer, fbInfo->ByteLength / sizeof(char16), true);
              }
              else
              {
                  JsSupport::ReadCodeFromFile(threadContext, false, fbInfo->TopLevelBodyCtr, fbInfo->IsUtf8, fbInfo->SourceBuffer, fbInfo->ByteLength);
              }
  
              fbInfo->DbgSerializedBytecodeSize = 0;
              fbInfo->DbgSerializedBytecodeBuffer = nullptr;
          }
  
  #if ENABLE_SNAPSHOT_COMPARE
          void AssertSnapEquiv(const TopLevelCommonBodyResolveInfo* fbInfo1, const TopLevelCommonBodyResolveInfo* fbInfo2, TTDCompareMap& compareMap)
          {
              compareMap.DiagnosticAssert(fbInfo1->ScriptContextLogId == fbInfo2->ScriptContextLogId);
              compareMap.DiagnosticAssert(fbInfo1->TopLevelBodyCtr == fbInfo2->TopLevelBodyCtr);
              compareMap.DiagnosticAssert(TTStringEQForDiagnostics(fbInfo1->FunctionName, fbInfo2->FunctionName));
  
              compareMap.DiagnosticAssert(fbInfo1->ModuleId == fbInfo2->ModuleId);
              compareMap.DiagnosticAssert(fbInfo1->SourceContextId == fbInfo2->SourceContextId);
  
              compareMap.DiagnosticAssert(TTStringEQForDiagnostics(fbInfo1->SourceUri, fbInfo2->SourceUri));
  
              compareMap.DiagnosticAssert(fbInfo1->IsUtf8 == fbInfo2->IsUtf8);
              compareMap.DiagnosticAssert(fbInfo1->ByteLength == fbInfo2->ByteLength);
  
              for(uint32 i = 0; i < fbInfo1->ByteLength; ++i)
              {
                  compareMap.DiagnosticAssert(fbInfo1->SourceBuffer[i] == fbInfo2->SourceBuffer[i]);
              }
  
              AssertSnapEquiv(fbInfo1->ScopeChainInfo, fbInfo2->ScopeChainInfo, compareMap);
          }
  #endif
  
          ////
          //Regular script-load functions
  
          void ExtractTopLevelLoadedFunctionBodyInfo(TopLevelScriptLoadFunctionBodyResolveInfo* fbInfo, Js::FunctionBody* fb, uint32 topLevelCtr, Js::ModuleID moduleId, uint64 sourceContextId, bool isUtf8, const byte* source, uint32 sourceLen, LoadScriptFlag loadFlag, SlabAllocator& alloc)
          {
              NSSnapValues::ExtractTopLevelCommonBodyResolveInfo(&fbInfo->TopLevelBase, fb, topLevelCtr, moduleId, sourceContextId, isUtf8, source, sourceLen, alloc);
  
              fbInfo->LoadFlag = loadFlag;
          }
  
          Js::FunctionBody* InflateTopLevelLoadedFunctionBodyInfo(const TopLevelScriptLoadFunctionBodyResolveInfo* fbInfo, Js::ScriptContext* ctx)
          {
              byte* script = fbInfo->TopLevelBase.SourceBuffer;
              uint32 scriptLength = fbInfo->TopLevelBase.ByteLength;
              uint64 sourceContext = fbInfo->TopLevelBase.SourceContextId;
  
              TTDAssert(ctx->GetSourceContextInfo((DWORD_PTR)sourceContext, nullptr) == nullptr, "On inflate we should either have clean ctxts or we want to optimize the inflate process by skipping redoing this work!!!");
              TTDAssert(fbInfo->TopLevelBase.IsUtf8 == ((fbInfo->LoadFlag & LoadScriptFlag_Utf8Source) == LoadScriptFlag_Utf8Source), "Utf8 status is inconsistent!!!");
  
              const char16* srcUri = fbInfo->TopLevelBase.SourceUri.Contents;
              uint32 srcUriLength = fbInfo->TopLevelBase.SourceUri.Length;
  
              SourceContextInfo * sourceContextInfo = ctx->CreateSourceContextInfo((DWORD_PTR)sourceContext, srcUri, srcUriLength, nullptr);
  
              TTDAssert(fbInfo->TopLevelBase.IsUtf8 || sizeof(wchar) == sizeof(char16), "Non-utf8 code only allowed on windows!!!");
              const int chsize = (fbInfo->LoadFlag & LoadScriptFlag_Utf8Source) ? sizeof(char) : sizeof(char16);
              SRCINFO si = {
                  /* sourceContextInfo   */ sourceContextInfo,
                  /* dlnHost             */ 0,
                  /* ulColumnHost        */ 0,
                  /* lnMinHost           */ 0,
                  /* ichMinHost          */ 0,
                  /* ichLimHost          */ static_cast<ULONG>(scriptLength / chsize), // OK to truncate since this is used to limit sourceText in debugDocument/compilation errors.
                  /* ulCharOffset        */ 0,
                  /* mod                 */ fbInfo->TopLevelBase.ModuleId,
                  /* grfsi               */ 0
              };
  
              Js::Utf8SourceInfo* utf8SourceInfo = nullptr;
              CompileScriptException se;
              Js::JavascriptFunction* scriptFunction = nullptr;
              Js::FunctionBody* globalBody = nullptr;
  
              if(fbInfo->TopLevelBase.DbgSerializedBytecodeSize != 0)
              {
                  //
                  //TODO: Bytecode serializer does not support debug bytecode (StatementMaps vs Positions) so add this to serializer code.
                  //      Then we can add code do optimized bytecode reload here.
                  //
                  TTDAssert(false, "Not implemented yet -- this branch should never be taken.");
              }
              else
              {
                  scriptFunction = ctx->LoadScript(script, scriptLength, &si, &se, &utf8SourceInfo, Js::Constants::GlobalCode, fbInfo->LoadFlag, nullptr);
                  TTDAssert(scriptFunction != nullptr, "Something went wrong");
  
                  globalBody = TTD::JsSupport::ForceAndGetFunctionBody(scriptFunction->GetParseableFunctionInfo());
  
                  //
                  //TODO: Bytecode serializer does not suppper debug bytecode (StatementMaps vs Positions) so add this to serializer code.
                  //      Then we can add code to do optimized bytecode reload here.
                  //
              }
  
              ////
              //We don't do this automatically in the load script helper so do it here
              BEGIN_JS_RUNTIME_CALL(ctx);
              {
                  ctx->TTDContextInfo->ProcessFunctionBodyOnLoad(globalBody, nullptr);
                  ctx->TTDContextInfo->RegisterLoadedScript(globalBody, fbInfo->TopLevelBase.TopLevelBodyCtr);
  
                  globalBody->GetUtf8SourceInfo()->SetSourceInfoForDebugReplay_TTD(fbInfo->TopLevelBase.TopLevelBodyCtr);
              }
              END_JS_RUNTIME_CALL(ctx);
  
              bool isLibraryCode = ((fbInfo->LoadFlag & LoadScriptFlag_LibraryCode) == LoadScriptFlag_LibraryCode);
              if(ctx->GetThreadContext()->TTDExecutionInfo != nullptr && !isLibraryCode)
              {
                  ctx->GetThreadContext()->TTDExecutionInfo->ProcessScriptLoad(ctx, fbInfo->TopLevelBase.TopLevelBodyCtr, globalBody, utf8SourceInfo, &se);
              }
              ////
  
              return globalBody;
          }
  
          void EmitTopLevelLoadedFunctionBodyInfo(const TopLevelScriptLoadFunctionBodyResolveInfo* fbInfo, ThreadContext* threadContext, FileWriter* writer, NSTokens::Separator separator)
          {
              NSSnapValues::EmitTopLevelCommonBodyResolveInfo(&fbInfo->TopLevelBase, false, threadContext, writer, separator);
  
              writer->WriteTag<LoadScriptFlag>(NSTokens::Key::loadFlag, fbInfo->LoadFlag, NSTokens::Separator::CommaSeparator);
  
              writer->WriteRecordEnd();
          }
  
          void ParseTopLevelLoadedFunctionBodyInfo(TopLevelScriptLoadFunctionBodyResolveInfo* fbInfo, bool readSeparator, ThreadContext* threadContext, FileReader* reader, SlabAllocator& alloc)
          {
              NSSnapValues::ParseTopLevelCommonBodyResolveInfo(&fbInfo->TopLevelBase, readSeparator, false, threadContext, reader, alloc);
  
              fbInfo->LoadFlag = reader->ReadTag<LoadScriptFlag>(NSTokens::Key::loadFlag, true);
  
              reader->ReadRecordEnd();
          }
  
  #if ENABLE_SNAPSHOT_COMPARE
          void AssertSnapEquiv(const TopLevelScriptLoadFunctionBodyResolveInfo* fbInfo1, const TopLevelScriptLoadFunctionBodyResolveInfo* fbInfo2, TTDCompareMap& compareMap)
          {
              compareMap.DiagnosticAssert(fbInfo1->LoadFlag == fbInfo2->LoadFlag);
  
              AssertSnapEquiv(&(fbInfo1->TopLevelBase), &(fbInfo2->TopLevelBase), compareMap);
          }
  #endif
  
          ////
          //'new Function(...)' functions
  
          void ExtractTopLevelNewFunctionBodyInfo(TopLevelNewFunctionBodyResolveInfo* fbInfo, Js::FunctionBody* fb, uint32 topLevelCtr, Js::ModuleID moduleId, const char16* source, uint32 sourceLen, SlabAllocator& alloc)
          {
              NSSnapValues::ExtractTopLevelCommonBodyResolveInfo(&fbInfo->TopLevelBase, fb, topLevelCtr, moduleId, 0, false, (const byte*)source, sourceLen * sizeof(char16), alloc);
          }
  
          Js::FunctionBody* InflateTopLevelNewFunctionBodyInfo(const TopLevelNewFunctionBodyResolveInfo* fbInfo, Js::ScriptContext* ctx)
          {
              // Bug 1105479. Get the module id from the caller
              Js::ModuleID moduleID = kmodGlobal;
              BOOL strictMode = FALSE;
  
              char16* source = (char16*)fbInfo->TopLevelBase.SourceBuffer;
              int32 length = (int32)(fbInfo->TopLevelBase.ByteLength / sizeof(char16));
  
              Js::JavascriptFunction* pfuncScript = ctx->GetGlobalObject()->EvalHelper(ctx, source, length, moduleID, fscrNil, Js::Constants::FunctionCode, TRUE, TRUE, strictMode);
              TTDAssert(pfuncScript != nullptr, "Something went wrong!!!");
  
              // Indicate that this is a top-level function. We don't pass the fscrGlobalCode flag to the eval helper,
              // or it will return the global function that wraps the declared function body, as though it were an eval.
              // But we want, for instance, to be able to verify that we did the right amount of deferred parsing.
              Js::ParseableFunctionInfo* functionInfo = pfuncScript->GetParseableFunctionInfo();
              functionInfo->SetGrfscr(functionInfo->GetGrfscr() | fscrGlobalCode);
  
              Js::FunctionBody* fb = JsSupport::ForceAndGetFunctionBody(pfuncScript->GetParseableFunctionInfo());
  
              //We don't do this automatically in the eval helper so do it here
              ctx->TTDContextInfo->ProcessFunctionBodyOnLoad(fb, nullptr);
              ctx->TTDContextInfo->RegisterNewScript(fb, fbInfo->TopLevelBase.TopLevelBodyCtr);
  
              return fb;
          }
  
          void EmitTopLevelNewFunctionBodyInfo(const TopLevelNewFunctionBodyResolveInfo* fbInfo, ThreadContext* threadContext, FileWriter* writer, NSTokens::Separator separator)
          {
              NSSnapValues::EmitTopLevelCommonBodyResolveInfo(&fbInfo->TopLevelBase, true, threadContext, writer, separator);
  
              writer->WriteRecordEnd();
          }
  
          void ParseTopLevelNewFunctionBodyInfo(TopLevelNewFunctionBodyResolveInfo* fbInfo, bool readSeparator, ThreadContext* threadContext, FileReader* reader, SlabAllocator& alloc)
          {
              NSSnapValues::ParseTopLevelCommonBodyResolveInfo(&fbInfo->TopLevelBase, readSeparator, false, threadContext, reader, alloc);
  
              reader->ReadRecordEnd();
          }
  
  #if ENABLE_SNAPSHOT_COMPARE
          void AssertSnapEquiv(const TopLevelNewFunctionBodyResolveInfo* fbInfo1, const TopLevelNewFunctionBodyResolveInfo* fbInfo2, TTDCompareMap& compareMap)
          {
              AssertSnapEquiv(&(fbInfo1->TopLevelBase), &(fbInfo2->TopLevelBase), compareMap);
          }
  #endif
  
          ////
          //'eval(...)' functions
  
          void ExtractTopLevelEvalFunctionBodyInfo(TopLevelEvalFunctionBodyResolveInfo* fbInfo, Js::FunctionBody* fb, uint32 topLevelCtr, Js::ModuleID moduleId, const char16* source, uint32 sourceLen, uint32 grfscr, bool registerDocument, BOOL isIndirect, BOOL strictMode, SlabAllocator& alloc)
          {
              NSSnapValues::ExtractTopLevelCommonBodyResolveInfo(&fbInfo->TopLevelBase, fb, topLevelCtr, moduleId, 0, false, (const byte*)source, sourceLen * sizeof(char16), alloc);
  
              fbInfo->EvalFlags = grfscr;
              fbInfo->RegisterDocument = registerDocument;
              fbInfo->IsIndirect = !!isIndirect;
              fbInfo->IsStrictMode = !!strictMode;
          }
  
          Js::FunctionBody* InflateTopLevelEvalFunctionBodyInfo(const TopLevelEvalFunctionBodyResolveInfo* fbInfo, Js::ScriptContext* ctx)
          {
              uint32 grfscr = ((uint32)fbInfo->EvalFlags) | fscrReturnExpression | fscrEval | fscrEvalCode | fscrGlobalCode;
  
              char16* source = (char16*)fbInfo->TopLevelBase.SourceBuffer;
              int32 sourceLen = (int32)(fbInfo->TopLevelBase.ByteLength / sizeof(char16));
              Js::ScriptFunction* pfuncScript = ctx->GetLibrary()->GetGlobalObject()->EvalHelper(ctx, source, sourceLen, fbInfo->TopLevelBase.ModuleId, grfscr, Js::Constants::EvalCode, fbInfo->RegisterDocument, fbInfo->IsIndirect, fbInfo->IsStrictMode);
              Assert(!pfuncScript->GetFunctionInfo()->IsGenerator());
  
              Js::FastEvalMapString key(source, sourceLen, fbInfo->TopLevelBase.ModuleId, fbInfo->IsStrictMode, false);
              ctx->AddToEvalMap(key, fbInfo->IsIndirect, pfuncScript);
  
              Js::FunctionBody* fb = JsSupport::ForceAndGetFunctionBody(pfuncScript->GetParseableFunctionInfo());
  
              //We don't do this automatically ing the eval helper so do it here
              ctx->TTDContextInfo->ProcessFunctionBodyOnLoad(fb, nullptr);
              ctx->TTDContextInfo->RegisterEvalScript(fb, fbInfo->TopLevelBase.TopLevelBodyCtr);
  
              return fb;
          }
  
          void EmitTopLevelEvalFunctionBodyInfo(const TopLevelEvalFunctionBodyResolveInfo* fbInfo, ThreadContext* threadContext, FileWriter* writer, NSTokens::Separator separator)
          {
              NSSnapValues::EmitTopLevelCommonBodyResolveInfo(&fbInfo->TopLevelBase, true, threadContext, writer, separator);
  
              writer->WriteUInt64(NSTokens::Key::u64Val, fbInfo->EvalFlags, NSTokens::Separator::CommaSeparator);
              writer->WriteBool(NSTokens::Key::boolVal, fbInfo->RegisterDocument, NSTokens::Separator::CommaSeparator);
              writer->WriteBool(NSTokens::Key::boolVal, fbInfo->IsIndirect, NSTokens::Separator::CommaSeparator);
              writer->WriteBool(NSTokens::Key::boolVal, fbInfo->IsStrictMode, NSTokens::Separator::CommaSeparator);
              writer->WriteRecordEnd();
          }
  
          void ParseTopLevelEvalFunctionBodyInfo(TopLevelEvalFunctionBodyResolveInfo* fbInfo, bool readSeparator, ThreadContext* threadContext, FileReader* reader, SlabAllocator& alloc)
          {
              NSSnapValues::ParseTopLevelCommonBodyResolveInfo(&fbInfo->TopLevelBase, readSeparator, false, threadContext, reader, alloc);
  
              fbInfo->EvalFlags = reader->ReadUInt64(NSTokens::Key::u64Val, true);
              fbInfo->RegisterDocument = reader->ReadBool(NSTokens::Key::boolVal, true);
              fbInfo->IsIndirect = reader->ReadBool(NSTokens::Key::boolVal, true);
              fbInfo->IsStrictMode = reader->ReadBool(NSTokens::Key::boolVal, true);
              reader->ReadRecordEnd();
          }
  
  #if ENABLE_SNAPSHOT_COMPARE
          void AssertSnapEquiv(const TopLevelEvalFunctionBodyResolveInfo* fbInfo1, const TopLevelEvalFunctionBodyResolveInfo* fbInfo2, TTDCompareMap& compareMap)
          {
              compareMap.DiagnosticAssert(fbInfo1->EvalFlags == fbInfo2->EvalFlags);
              compareMap.DiagnosticAssert(fbInfo1->RegisterDocument == fbInfo2->RegisterDocument);
              compareMap.DiagnosticAssert(fbInfo1->IsIndirect == fbInfo2->IsIndirect);
              compareMap.DiagnosticAssert(fbInfo1->IsStrictMode == fbInfo2->IsStrictMode);
  
              AssertSnapEquiv(&(fbInfo1->TopLevelBase), &(fbInfo2->TopLevelBase), compareMap);
          }
  #endif
  
          void ExtractFunctionBodyInfo(FunctionBodyResolveInfo* fbInfo, Js::FunctionBody* fb, bool isWellKnown, SlabAllocator& alloc)
          {
              fbInfo->FunctionBodyId = TTD_CONVERT_FUNCTIONBODY_TO_PTR_ID(fb);
              fbInfo->ScriptContextLogId = fb->GetScriptContext()->ScriptContextLogTag;
  
              alloc.CopyStringIntoWLength(fb->GetDisplayName(), fb->GetDisplayNameLength(), fbInfo->FunctionName);
              TTDAssert(wcscmp(fbInfo->FunctionName.Contents, Js::Constants::GlobalCode) != 0, "Why are we snapshotting global code??");
  
              if(isWellKnown)
              {
                  fbInfo->OptKnownPath = alloc.CopyRawNullTerminatedStringInto(fb->GetScriptContext()->TTDWellKnownInfo->ResolvePathForKnownFunctionBody(fb));
  
                  fbInfo->OptParentBodyId = TTD_INVALID_PTR_ID;
                  fbInfo->OptLine = -1;
                  fbInfo->OptColumn = -1;
              }
              else
              {
                  fbInfo->OptKnownPath = TTD_INVALID_WELLKNOWN_TOKEN;
  
                  Js::FunctionBody* parentBody = fb->GetScriptContext()->TTDContextInfo->ResolveParentBody(fb);
                  TTDAssert(parentBody != nullptr, "We missed something!!!");
  
                  fbInfo->OptParentBodyId = TTD_CONVERT_FUNCTIONBODY_TO_PTR_ID(parentBody);
                  fbInfo->OptLine = fb->GetLineNumber();
                  fbInfo->OptColumn = fb->GetColumnNumber();
              }
  
              ExtractSnapFunctionBodyScopeChain(fbInfo->OptKnownPath != TTD_INVALID_WELLKNOWN_TOKEN, fbInfo->ScopeChainInfo, fb, alloc);
          }
  
          void InflateFunctionBody(const FunctionBodyResolveInfo* fbInfo, InflateMap* inflator, const TTDIdentifierDictionary<TTD_PTR_ID, FunctionBodyResolveInfo*>& idToFbResolveMap)
          {
              if(inflator->IsFunctionBodyAlreadyInflated(fbInfo->FunctionBodyId))
              {
                  return;
              }
  
              Js::ScriptContext* ctx = inflator->LookupScriptContext(fbInfo->ScriptContextLogId);
  
              Js::FunctionBody* resfb = nullptr;
              if(fbInfo->OptKnownPath != TTD_INVALID_WELLKNOWN_TOKEN)
              {
                  resfb = ctx->TTDWellKnownInfo->LookupKnownFunctionBodyFromPath(fbInfo->OptKnownPath);
              }
              else
              {
                  resfb = inflator->FindReusableFunctionBodyIfExists(fbInfo->FunctionBodyId);
                  if(resfb == nullptr)
                  {
                      //Find and inflate the parent body
                      if(!inflator->IsFunctionBodyAlreadyInflated(fbInfo->OptParentBodyId))
                      {
                          const FunctionBodyResolveInfo* pBodyInfo = idToFbResolveMap.LookupKnownItem(fbInfo->OptParentBodyId);
                          InflateFunctionBody(pBodyInfo, inflator, idToFbResolveMap);
                      }
  
                      Js::FunctionBody* parentBody = inflator->LookupFunctionBody(fbInfo->OptParentBodyId);
  
                      //
                      //TODO: this is a potentially expensive linear search (but needed since classes dump implicit functions out-of-text order).
                      //      May want to add sort and save in inflator or our shaddow info in script context if this is looking expensive.
                      //
                      uint32 blength = parentBody->GetNestedCount();
                      for(uint32 i = 0; i < blength; ++i)
                      {
                          Js::ParseableFunctionInfo* pfi = parentBody->GetNestedFunctionForExecution(i);
                          Js::FunctionBody* currfb = JsSupport::ForceAndGetFunctionBody(pfi);
  
                          if(fbInfo->OptLine == currfb->GetLineNumber() && fbInfo->OptColumn == currfb->GetColumnNumber())
                          {
                              resfb = currfb;
                              break;
                          }
                      }
  
                      TTDAssert(resfb != nullptr && fbInfo->OptLine == resfb->GetLineNumber() && fbInfo->OptColumn == resfb->GetColumnNumber(), "We are missing something");
                      TTDAssert(resfb != nullptr && (wcscmp(fbInfo->FunctionName.Contents, resfb->GetDisplayName()) == 0 || wcscmp(_u("get"), resfb->GetDisplayName()) == 0 || wcscmp(_u("set"), resfb->GetDisplayName()) == 0), "We are missing something");
                  }
  
                  //Make sure to register any scopes the found function body has (but *not* for well known functions)
                  inflator->UpdateFBScopes(fbInfo->ScopeChainInfo, resfb);
              }
  
              bool updateName = false;
              if(fbInfo->FunctionName.Length != resfb->GetDisplayNameLength())
              {
                  updateName = true;
              }
              else
              {
                  const char16* fname = resfb->GetDisplayName();
                  for(uint32 i = 0; i < fbInfo->FunctionName.Length; ++i)
                  {
                      updateName |= (fbInfo->FunctionName.Contents[i] != fname[i]);
                  }
              }
  
              if(updateName)
              {
                  uint32 suffixWDotPos = (fbInfo->FunctionName.Length - 4);
                  uint32 suffixPos = (fbInfo->FunctionName.Length - 3);
  
                  TTDAssert(wcsstr(fbInfo->FunctionName.Contents, _u(".get")) == (fbInfo->FunctionName.Contents + suffixWDotPos) || wcsstr(fbInfo->FunctionName.Contents, _u(".set")) == (fbInfo->FunctionName.Contents + suffixWDotPos), "Does not start with get or set");
  
                  resfb->SetDisplayName(fbInfo->FunctionName.Contents, fbInfo->FunctionName.Length, suffixPos, Js::FunctionProxy::SetDisplayNameFlagsRecyclerAllocated);
              }
  
              inflator->AddInflationFunctionBody(fbInfo->FunctionBodyId, resfb);
          }
  
          void EmitFunctionBodyInfo(const FunctionBodyResolveInfo* fbInfo, FileWriter* writer, NSTokens::Separator separator)
          {
              writer->WriteRecordStart(separator);
              writer->WriteAddr(NSTokens::Key::functionBodyId, fbInfo->FunctionBodyId);
              writer->WriteLogTag(NSTokens::Key::ctxTag, fbInfo->ScriptContextLogId, NSTokens::Separator::CommaSeparator);
              writer->WriteString(NSTokens::Key::name, fbInfo->FunctionName, NSTokens::Separator::CommaSeparator);
  
              writer->WriteBool(NSTokens::Key::isWellKnownToken, fbInfo->OptKnownPath != nullptr, NSTokens::Separator::CommaSeparator);
              if(fbInfo->OptKnownPath != TTD_INVALID_WELLKNOWN_TOKEN)
              {
                  writer->WriteWellKnownToken(NSTokens::Key::wellKnownToken, fbInfo->OptKnownPath, NSTokens::Separator::CommaSeparator);
              }
              else
              {
                  writer->WriteAddr(NSTokens::Key::parentBodyId, fbInfo->OptParentBodyId, NSTokens::Separator::CommaSeparator);
                  writer->WriteInt64(NSTokens::Key::line, fbInfo->OptLine, NSTokens::Separator::CommaSeparator);
                  writer->WriteInt64(NSTokens::Key::column, fbInfo->OptColumn, NSTokens::Separator::CommaSeparator);
              }
  
              writer->WriteKey(NSTokens::Key::scopeChain, NSTokens::Separator::CommaSeparator);
              EmitSnapFunctionBodyScopeChain(fbInfo->ScopeChainInfo, writer);
  
              writer->WriteRecordEnd();
          }
  
          void ParseFunctionBodyInfo(FunctionBodyResolveInfo* fbInfo, bool readSeparator, FileReader* reader, SlabAllocator& alloc)
          {
              reader->ReadRecordStart(readSeparator);
  
              fbInfo->FunctionBodyId = reader->ReadAddr(NSTokens::Key::functionBodyId);
              fbInfo->ScriptContextLogId = reader->ReadLogTag(NSTokens::Key::ctxTag, true);
              reader->ReadString(NSTokens::Key::name, alloc, fbInfo->FunctionName, true);
  
              bool isWellKnown = reader->ReadBool(NSTokens::Key::isWellKnownToken, true);
              if(isWellKnown)
              {
                  fbInfo->OptKnownPath = reader->ReadWellKnownToken(NSTokens::Key::wellKnownToken, alloc, true);
  
                  fbInfo->OptParentBodyId = TTD_INVALID_PTR_ID;
                  fbInfo->OptLine = -1;
                  fbInfo->OptColumn = -1;
              }
              else
              {
                  fbInfo->OptKnownPath = TTD_INVALID_WELLKNOWN_TOKEN;
  
                  fbInfo->OptParentBodyId = reader->ReadAddr(NSTokens::Key::parentBodyId, true);
                  fbInfo->OptLine = reader->ReadInt64(NSTokens::Key::line, true);
                  fbInfo->OptColumn = reader->ReadInt64(NSTokens::Key::column, true);
              }
  
              reader->ReadKey(NSTokens::Key::scopeChain, true);
              ParseSnapFunctionBodyScopeChain(fbInfo->ScopeChainInfo, reader, alloc);
  
              reader->ReadRecordEnd();
          }
  
  #if ENABLE_SNAPSHOT_COMPARE
          void AssertSnapEquiv(const FunctionBodyResolveInfo* fbInfo1, const FunctionBodyResolveInfo* fbInfo2, TTDCompareMap& compareMap)
          {
              compareMap.DiagnosticAssert(fbInfo1->ScriptContextLogId == fbInfo2->ScriptContextLogId);
              compareMap.DiagnosticAssert(TTStringEQForDiagnostics(fbInfo1->FunctionName, fbInfo2->FunctionName));
  
              compareMap.DiagnosticAssert(TTD_DIAGNOSTIC_COMPARE_WELLKNOWN_TOKENS(fbInfo1->OptKnownPath, fbInfo2->OptKnownPath));
              if(fbInfo1->OptKnownPath == TTD_INVALID_WELLKNOWN_TOKEN)
              {
                  compareMap.CheckConsistentAndAddPtrIdMapping_FunctionBody(fbInfo1->OptParentBodyId, fbInfo2->OptParentBodyId);
  
                  compareMap.DiagnosticAssert(fbInfo1->OptLine == fbInfo2->OptLine);
                  compareMap.DiagnosticAssert(fbInfo1->OptColumn == fbInfo2->OptColumn);
              }
  
              AssertSnapEquiv(fbInfo1->ScopeChainInfo, fbInfo1->ScopeChainInfo, compareMap);
          }
  #endif
  
          //////////////////
  
          void ExtractScriptContext(SnapContext* snapCtx, Js::ScriptContext* ctx, const JsUtil::BaseDictionary<Js::RecyclableObject*, TTD_LOG_PTR_ID, HeapAllocator>& objToLogIdMap, const JsUtil::BaseHashSet<Js::FunctionBody*, HeapAllocator>& liveTopLevelBodies, SlabAllocator& alloc)
          {
              snapCtx->ScriptContextLogId = ctx->ScriptContextLogTag;
  
              snapCtx->IsPNRGSeeded = ctx->GetLibrary()->IsPRNGSeeded();
              snapCtx->RandomSeed0 = ctx->GetLibrary()->GetRandSeed0();
              snapCtx->RandomSeed1 = ctx->GetLibrary()->GetRandSeed1();
              alloc.CopyNullTermStringInto(ctx->GetUrl(), snapCtx->ContextSRC);
  
              JsUtil::List<TopLevelFunctionInContextRelation, HeapAllocator> topLevelScriptLoad(&HeapAllocator::Instance);
              JsUtil::List<TopLevelFunctionInContextRelation, HeapAllocator> topLevelNewFunction(&HeapAllocator::Instance);
              JsUtil::List<TopLevelFunctionInContextRelation, HeapAllocator> topLevelEval(&HeapAllocator::Instance);
  
              ctx->TTDContextInfo->GetLoadedSources(&liveTopLevelBodies, topLevelScriptLoad, topLevelNewFunction, topLevelEval);
  
              snapCtx->LoadedTopLevelScriptCount = topLevelScriptLoad.Count();
              if(snapCtx->LoadedTopLevelScriptCount == 0)
              {
                  snapCtx->LoadedTopLevelScriptArray = nullptr;
              }
              else
              {
                  snapCtx->LoadedTopLevelScriptArray = alloc.SlabAllocateArray<TopLevelFunctionInContextRelation>(snapCtx->LoadedTopLevelScriptCount);
                  for(int32 i = 0; i < topLevelScriptLoad.Count(); ++i)
                  {
                      snapCtx->LoadedTopLevelScriptArray[i] = topLevelScriptLoad.Item(i);
                  }
              }
  
              snapCtx->NewFunctionTopLevelScriptCount = topLevelNewFunction.Count();
              if(snapCtx->NewFunctionTopLevelScriptCount == 0)
              {
                  snapCtx->NewFunctionTopLevelScriptArray = nullptr;
              }
              else
              {
                  snapCtx->NewFunctionTopLevelScriptArray = alloc.SlabAllocateArray<TopLevelFunctionInContextRelation>(snapCtx->NewFunctionTopLevelScriptCount);
                  for(int32 i = 0; i < topLevelNewFunction.Count(); ++i)
                  {
                      snapCtx->NewFunctionTopLevelScriptArray[i] = topLevelNewFunction.Item(i);
                  }
              }
  
              snapCtx->EvalTopLevelScriptCount = topLevelEval.Count();
              if(snapCtx->EvalTopLevelScriptCount == 0)
              {
                  snapCtx->EvalTopLevelScriptArray = nullptr;
              }
              else
              {
                  snapCtx->EvalTopLevelScriptArray = alloc.SlabAllocateArray<TopLevelFunctionInContextRelation>(snapCtx->EvalTopLevelScriptCount);
                  for(int32 i = 0; i < topLevelEval.Count(); ++i)
                  {
                      snapCtx->EvalTopLevelScriptArray[i] = topLevelEval.Item(i);
                  }
              }
  
              //Extract pending async modification info
              const JsUtil::List<TTDPendingAsyncBufferModification, HeapAllocator>& pendingAsyncList = ctx->TTDContextInfo->GetPendingAsyncModListForSnapshot();
              snapCtx->PendingAsyncModCount = pendingAsyncList.Count();
              if(snapCtx->PendingAsyncModCount == 0)
              {
                  snapCtx->PendingAsyncModArray = nullptr;
              }
              else
              {
                  snapCtx->PendingAsyncModArray = alloc.SlabAllocateArray<SnapPendingAsyncBufferModification>(snapCtx->PendingAsyncModCount);
  
                  for(int32 k = 0; k < pendingAsyncList.Count(); ++k)
                  {
                      const TTDPendingAsyncBufferModification& pk = pendingAsyncList.Item(k);
                      snapCtx->PendingAsyncModArray[k].LogId = objToLogIdMap.Item(Js::RecyclableObject::FromVar(pk.ArrayBufferVar));
                      snapCtx->PendingAsyncModArray[k].Index = pk.Index;
                  }
              }
          }
  
          void InflateScriptContext(const SnapContext* snpCtx, Js::ScriptContext* intoCtx, InflateMap* inflator,
              const TTDIdentifierDictionary<uint64, TopLevelScriptLoadFunctionBodyResolveInfo*>& topLevelLoadScriptMap,
              const TTDIdentifierDictionary<uint64, TopLevelNewFunctionBodyResolveInfo*>& topLevelNewScriptMap,
              const TTDIdentifierDictionary<uint64, TopLevelEvalFunctionBodyResolveInfo*>& topLevelEvalScriptMap)
          {
              TTDAssert(wcscmp(snpCtx->ContextSRC.Contents, intoCtx->GetUrl()) == 0, "Make sure the src uri values are the same.");
  
              intoCtx->GetLibrary()->SetIsPRNGSeeded(snpCtx->IsPNRGSeeded);
              intoCtx->GetLibrary()->SetRandSeed0(snpCtx->RandomSeed0);
              intoCtx->GetLibrary()->SetRandSeed1(snpCtx->RandomSeed1);
              inflator->AddScriptContext(snpCtx->ScriptContextLogId, intoCtx);
  
              intoCtx->TTDContextInfo->ClearLoadedSourcesForSnapshotRestore();
  
              if(intoCtx->HasRecordedException())
              {
                  intoCtx->GetAndClearRecordedException(nullptr);
              }
  
              for(uint32 i = 0; i < snpCtx->LoadedTopLevelScriptCount; ++i)
              {
                  const TopLevelFunctionInContextRelation& cri = snpCtx->LoadedTopLevelScriptArray[i];
  
                  Js::FunctionBody* fb = inflator->FindReusableFunctionBodyIfExists(cri.ContextSpecificBodyPtrId);
                  const TopLevelScriptLoadFunctionBodyResolveInfo* fbInfo = topLevelLoadScriptMap.LookupKnownItem(cri.TopLevelBodyCtr);
  
                  if(fb == nullptr)
                  {
                      fb = NSSnapValues::InflateTopLevelLoadedFunctionBodyInfo(fbInfo, intoCtx);
                  }
                  else
                  {
                      intoCtx->TTDContextInfo->ProcessFunctionBodyOnLoad(fb, nullptr);
                      intoCtx->TTDContextInfo->RegisterLoadedScript(fb, cri.TopLevelBodyCtr);
  
                      intoCtx->GetThreadContext()->TTDExecutionInfo->ProcessScriptLoad_InflateReuseBody(cri.TopLevelBodyCtr, fb);
                  }
  
                  inflator->UpdateFBScopes(fbInfo->TopLevelBase.ScopeChainInfo, fb);
                  inflator->AddInflationFunctionBody(cri.ContextSpecificBodyPtrId, fb);
              }
  
              //The inflation code for NewFunction and Eval uses the paths in the runtime (which assume they are in script) -- so enter here to make them happy
              BEGIN_ENTER_SCRIPT(intoCtx, true, true, true)
              {
                  for(uint32 i = 0; i < snpCtx->NewFunctionTopLevelScriptCount; ++i)
                  {
                      const TopLevelFunctionInContextRelation& cri = snpCtx->NewFunctionTopLevelScriptArray[i];
  
                      Js::FunctionBody* fb = inflator->FindReusableFunctionBodyIfExists(cri.ContextSpecificBodyPtrId);
                      const TopLevelNewFunctionBodyResolveInfo* fbInfo = topLevelNewScriptMap.LookupKnownItem(cri.TopLevelBodyCtr);
  
                      if(fb == nullptr)
                      {
                          fb = NSSnapValues::InflateTopLevelNewFunctionBodyInfo(fbInfo, intoCtx);
                      }
                      else
                      {
                          intoCtx->TTDContextInfo->ProcessFunctionBodyOnLoad(fb, nullptr);
                          intoCtx->TTDContextInfo->RegisterNewScript(fb, cri.TopLevelBodyCtr);
  
                          intoCtx->GetThreadContext()->TTDExecutionInfo->ProcessScriptLoad_InflateReuseBody(cri.TopLevelBodyCtr, fb);
                      }
  
                      inflator->UpdateFBScopes(fbInfo->TopLevelBase.ScopeChainInfo, fb);
                      inflator->AddInflationFunctionBody(cri.ContextSpecificBodyPtrId, fb);
                  }
  
                  for(uint32 i = 0; i < snpCtx->EvalTopLevelScriptCount; ++i)
                  {
                      const TopLevelFunctionInContextRelation& cri = snpCtx->EvalTopLevelScriptArray[i];
  
                      Js::FunctionBody* fb = inflator->FindReusableFunctionBodyIfExists(cri.ContextSpecificBodyPtrId);
                      const TopLevelEvalFunctionBodyResolveInfo* fbInfo = topLevelEvalScriptMap.LookupKnownItem(cri.TopLevelBodyCtr);
  
                      if(fb == nullptr)
                      {
                          fb = NSSnapValues::InflateTopLevelEvalFunctionBodyInfo(fbInfo, intoCtx);
                      }
                      else
                      {
                          intoCtx->TTDContextInfo->ProcessFunctionBodyOnLoad(fb, nullptr);
                          intoCtx->TTDContextInfo->RegisterEvalScript(fb, cri.TopLevelBodyCtr);
  
                          intoCtx->GetThreadContext()->TTDExecutionInfo->ProcessScriptLoad_InflateReuseBody(cri.TopLevelBodyCtr, fb);
                      }
  
                      inflator->UpdateFBScopes(fbInfo->TopLevelBase.ScopeChainInfo, fb);
                      inflator->AddInflationFunctionBody(cri.ContextSpecificBodyPtrId, fb);
                  }
              }
              END_ENTER_SCRIPT
          }
  
          void ResetPendingAsyncBufferModInfo(const SnapContext* snpCtx, Js::ScriptContext* intoCtx, InflateMap* inflator)
          {
              intoCtx->TTDContextInfo->ClearPendingAsyncModListForSnapRestore();
  
              for(uint32 i = 0; i < snpCtx->PendingAsyncModCount; ++i)
              {
                  Js::RecyclableObject* buff = intoCtx->GetThreadContext()->TTDContext->LookupObjectForLogID(snpCtx->PendingAsyncModArray[i].LogId);
                  uint32 index = snpCtx->PendingAsyncModArray[i].Index;
  
                  TTDAssert(Js::ArrayBuffer::Is(buff), "Not an ArrayBuffer!!!");
                  intoCtx->TTDContextInfo->AddToAsyncPendingList(Js::ArrayBuffer::FromVar(buff), index);
              }
          }
  
          void EmitSnapContext(const SnapContext* snapCtx, FileWriter* writer, NSTokens::Separator separator)
          {
              writer->WriteRecordStart(separator);
  
              writer->WriteLogTag(NSTokens::Key::ctxTag, snapCtx->ScriptContextLogId);
              writer->WriteBool(NSTokens::Key::boolVal, snapCtx->IsPNRGSeeded, NSTokens::Separator::CommaSeparator);
              writer->WriteUInt64(NSTokens::Key::u64Val, snapCtx->RandomSeed0, NSTokens::Separator::CommaSeparator);
              writer->WriteUInt64(NSTokens::Key::u64Val, snapCtx->RandomSeed1, NSTokens::Separator::CommaSeparator);
              writer->WriteString(NSTokens::Key::ctxUri, snapCtx->ContextSRC, NSTokens::Separator::CommaSeparator);
  
              writer->WriteLengthValue(snapCtx->LoadedTopLevelScriptCount, NSTokens::Separator::CommaSeparator);
              writer->WriteSequenceStart_DefaultKey(NSTokens::Separator::CommaSeparator);
              for(uint32 i = 0; i < snapCtx->LoadedTopLevelScriptCount; ++i)
              {
                  const TopLevelFunctionInContextRelation* cri = snapCtx->LoadedTopLevelScriptArray + i;
                  NSTokens::Separator sep = (i != 0) ? NSTokens::Separator::CommaSeparator : NSTokens::Separator::NoSeparator;
  
                  writer->WriteRecordStart(sep);
                  writer->WriteUInt32(NSTokens::Key::bodyCounterId, cri->TopLevelBodyCtr);
                  writer->WriteAddr(NSTokens::Key::functionBodyId, cri->ContextSpecificBodyPtrId, NSTokens::Separator::CommaSeparator);
                  writer->WriteRecordEnd();
              }
              writer->WriteSequenceEnd();
  
              writer->WriteLengthValue(snapCtx->NewFunctionTopLevelScriptCount, NSTokens::Separator::CommaSeparator);
              writer->WriteSequenceStart_DefaultKey(NSTokens::Separator::CommaSeparator);
              for(uint32 i = 0; i < snapCtx->NewFunctionTopLevelScriptCount; ++i)
              {
                  const TopLevelFunctionInContextRelation* cri = snapCtx->NewFunctionTopLevelScriptArray + i;
                  NSTokens::Separator sep = (i != 0) ? NSTokens::Separator::CommaSeparator : NSTokens::Separator::NoSeparator;
  
                  writer->WriteRecordStart(sep);
                  writer->WriteUInt32(NSTokens::Key::bodyCounterId, cri->TopLevelBodyCtr);
                  writer->WriteAddr(NSTokens::Key::functionBodyId, cri->ContextSpecificBodyPtrId, NSTokens::Separator::CommaSeparator);
                  writer->WriteRecordEnd();
              }
              writer->WriteSequenceEnd();
  
              writer->WriteLengthValue(snapCtx->EvalTopLevelScriptCount, NSTokens::Separator::CommaSeparator);
              writer->WriteSequenceStart_DefaultKey(NSTokens::Separator::CommaSeparator);
              for(uint32 i = 0; i < snapCtx->EvalTopLevelScriptCount; ++i)
              {
                  const TopLevelFunctionInContextRelation* cri = snapCtx->EvalTopLevelScriptArray + i;
                  NSTokens::Separator sep = (i != 0) ? NSTokens::Separator::CommaSeparator : NSTokens::Separator::NoSeparator;
  
                  writer->WriteRecordStart(sep);
                  writer->WriteUInt32(NSTokens::Key::bodyCounterId, cri->TopLevelBodyCtr);
                  writer->WriteAddr(NSTokens::Key::functionBodyId, cri->ContextSpecificBodyPtrId, NSTokens::Separator::CommaSeparator);
                  writer->WriteRecordEnd();
              }
              writer->WriteSequenceEnd();
  
              writer->WriteLengthValue(snapCtx->PendingAsyncModCount, NSTokens::Separator::CommaSeparator);
              writer->WriteSequenceStart_DefaultKey(NSTokens::Separator::CommaSeparator);
              for(uint32 i = 0; i < snapCtx->PendingAsyncModCount; ++i)
              {
                  NSTokens::Separator sep = (i != 0) ? NSTokens::Separator::CommaSeparator : NSTokens::Separator::NoSeparator;
                  writer->WriteRecordStart(sep);
                  writer->WriteLogTag(NSTokens::Key::logTag, snapCtx->PendingAsyncModArray[i].LogId);
                  writer->WriteUInt32(NSTokens::Key::u32Val, snapCtx->PendingAsyncModArray[i].Index, NSTokens::Separator::CommaSeparator);
                  writer->WriteRecordEnd();
              }
              writer->WriteSequenceEnd();
  
              writer->WriteRecordEnd();
          }
  
          void ParseSnapContext(SnapContext* intoCtx, bool readSeparator, FileReader* reader, SlabAllocator& alloc)
          {
              reader->ReadRecordStart(readSeparator);
  
              intoCtx->ScriptContextLogId = reader->ReadLogTag(NSTokens::Key::ctxTag);
              intoCtx->IsPNRGSeeded = reader->ReadBool(NSTokens::Key::boolVal, true);
              intoCtx->RandomSeed0 = reader->ReadUInt64(NSTokens::Key::u64Val, true);
              intoCtx->RandomSeed1 = reader->ReadUInt64(NSTokens::Key::u64Val, true);
              reader->ReadString(NSTokens::Key::ctxUri, alloc, intoCtx->ContextSRC, true);
  
              intoCtx->LoadedTopLevelScriptCount = reader->ReadLengthValue(true);
              intoCtx->LoadedTopLevelScriptArray = (intoCtx->LoadedTopLevelScriptCount != 0) ? alloc.SlabAllocateArray<TopLevelFunctionInContextRelation>(intoCtx->LoadedTopLevelScriptCount) : nullptr;
              reader->ReadSequenceStart_WDefaultKey(true);
              for(uint32 i = 0; i < intoCtx->LoadedTopLevelScriptCount; ++i)
              {
                  TopLevelFunctionInContextRelation* cri = intoCtx->LoadedTopLevelScriptArray + i;
  
                  reader->ReadRecordStart(i != 0);
                  cri->TopLevelBodyCtr = reader->ReadUInt32(NSTokens::Key::bodyCounterId);
                  cri->ContextSpecificBodyPtrId = reader->ReadAddr(NSTokens::Key::functionBodyId, true);
                  reader->ReadRecordEnd();
              }
              reader->ReadSequenceEnd();
  
              intoCtx->NewFunctionTopLevelScriptCount = reader->ReadLengthValue(true);
              intoCtx->NewFunctionTopLevelScriptArray = (intoCtx->NewFunctionTopLevelScriptCount != 0) ? alloc.SlabAllocateArray<TopLevelFunctionInContextRelation>(intoCtx->NewFunctionTopLevelScriptCount) : nullptr;
              reader->ReadSequenceStart_WDefaultKey(true);
              for(uint32 i = 0; i < intoCtx->NewFunctionTopLevelScriptCount; ++i)
              {
                  TopLevelFunctionInContextRelation* cri = intoCtx->NewFunctionTopLevelScriptArray + i;
  
                  reader->ReadRecordStart(i != 0);
                  cri->TopLevelBodyCtr = reader->ReadUInt32(NSTokens::Key::bodyCounterId);
                  cri->ContextSpecificBodyPtrId = reader->ReadAddr(NSTokens::Key::functionBodyId, true);
                  reader->ReadRecordEnd();
              }
              reader->ReadSequenceEnd();
  
              intoCtx->EvalTopLevelScriptCount = reader->ReadLengthValue(true);
              intoCtx->EvalTopLevelScriptArray = (intoCtx->EvalTopLevelScriptCount != 0) ? alloc.SlabAllocateArray<TopLevelFunctionInContextRelation>(intoCtx->EvalTopLevelScriptCount) : nullptr;
              reader->ReadSequenceStart_WDefaultKey(true);
              for(uint32 i = 0; i < intoCtx->EvalTopLevelScriptCount; ++i)
              {
                  TopLevelFunctionInContextRelation* cri = intoCtx->EvalTopLevelScriptArray + i;
  
                  reader->ReadRecordStart(i != 0);
                  cri->TopLevelBodyCtr = reader->ReadUInt32(NSTokens::Key::bodyCounterId);
                  cri->ContextSpecificBodyPtrId = reader->ReadAddr(NSTokens::Key::functionBodyId, true);
                  reader->ReadRecordEnd();
              }
              reader->ReadSequenceEnd();
  
              intoCtx->PendingAsyncModCount = reader->ReadLengthValue(true);
              intoCtx->PendingAsyncModArray = (intoCtx->PendingAsyncModCount != 0) ? alloc.SlabAllocateArray<SnapPendingAsyncBufferModification>(intoCtx->PendingAsyncModCount) : nullptr;
  
              reader->ReadSequenceStart_WDefaultKey(true);
              for(uint32 i = 0; i < intoCtx->PendingAsyncModCount; ++i)
              {
                  reader->ReadRecordStart(i != 0);
                  intoCtx->PendingAsyncModArray[i].LogId = reader->ReadLogTag(NSTokens::Key::logTag);
                  intoCtx->PendingAsyncModArray[i].Index = reader->ReadUInt32(NSTokens::Key::u32Val, true);
                  reader->ReadRecordEnd();
              }
              reader->ReadSequenceEnd();
  
              reader->ReadRecordEnd();
          }
  
  #if ENABLE_SNAPSHOT_COMPARE 
          void AssertSnapEquiv(const SnapContext* snapCtx1, const SnapContext* snapCtx2, const JsUtil::BaseDictionary<TTD_LOG_PTR_ID, NSSnapValues::SnapRootInfoEntry*, HeapAllocator>& allRootMap1, const JsUtil::BaseDictionary<TTD_LOG_PTR_ID, NSSnapValues::SnapRootInfoEntry*, HeapAllocator>& allRootMap2, TTDCompareMap& compareMap)
          {
              compareMap.DiagnosticAssert(snapCtx1->ScriptContextLogId == snapCtx2->ScriptContextLogId);
  
              compareMap.DiagnosticAssert(snapCtx1->IsPNRGSeeded == snapCtx2->IsPNRGSeeded);
              compareMap.DiagnosticAssert(snapCtx1->RandomSeed0 == snapCtx2->RandomSeed0);
              compareMap.DiagnosticAssert(snapCtx1->RandomSeed1 == snapCtx2->RandomSeed1);
  
              compareMap.DiagnosticAssert(TTStringEQForDiagnostics(snapCtx1->ContextSRC, snapCtx2->ContextSRC));
  
              //
              //TODO: For now just sanity check the number of top-level functions and let the FunctionBody matching drive any matching.
              //
  
              compareMap.DiagnosticAssert(snapCtx1->LoadedTopLevelScriptCount == snapCtx2->LoadedTopLevelScriptCount);
              //TopLevelScriptLoadFunctionBodyResolveInfo* m_loadedScriptArray;
  
              compareMap.DiagnosticAssert(snapCtx1->NewFunctionTopLevelScriptCount == snapCtx2->NewFunctionTopLevelScriptCount);
              //TopLevelNewFunctionBodyResolveInfo* m_newScriptArray;
  
              compareMap.DiagnosticAssert(snapCtx1->EvalTopLevelScriptCount == snapCtx2->EvalTopLevelScriptCount);
              //TopLevelEvalFunctionBodyResolveInfo* m_evalScriptArray;
  
              compareMap.DiagnosticAssert(snapCtx1->PendingAsyncModCount == snapCtx2->PendingAsyncModCount);
  
              for(uint32 i = 0; i < snapCtx1->PendingAsyncModCount; ++i)
              {
                  const SnapPendingAsyncBufferModification& pendEntry1 = snapCtx1->PendingAsyncModArray[i];
                  const SnapPendingAsyncBufferModification& pendEntry2 = snapCtx2->PendingAsyncModArray[i];
  
                  compareMap.DiagnosticAssert(pendEntry1.LogId == pendEntry2.LogId && pendEntry1.Index == pendEntry2.Index);
  
                  compareMap.H1PendingAsyncModBufferSet.AddNew(allRootMap1.Item(pendEntry1.LogId)->LogObject);
                  compareMap.H2PendingAsyncModBufferSet.AddNew(allRootMap2.Item(pendEntry2.LogId)->LogObject);
              }
          }
  #endif
      }
  }
  
  #endif
  