diff --git a/BeefRT/dbg/gc.cpp b/BeefRT/dbg/gc.cpp index 2edaacd3..63e4310e 100644 --- a/BeefRT/dbg/gc.cpp +++ b/BeefRT/dbg/gc.cpp @@ -663,6 +663,15 @@ BFGC::BFGC() for (int i = 0; i < kNumClasses; i++) gGCDbgData.mSizeClasses[i] = Static::sizemap()->ByteSizeForClass(i); + mStats = NULL; + Beefy::String memName = StrFormat("BFGC_stats_%d", GetCurrentProcessId()); + auto* fileMapping = ::CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(Stats), memName.c_str()); + if (fileMapping != NULL) + { + mStats = (Stats*)MapViewOfFile(fileMapping, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(Stats)); + mStats->mHeapSize = 0; + } + RawInit(); } @@ -1816,6 +1825,13 @@ void BFGC::FinishCollect() mStage = 4; } +void BFGC::UpdateStats() +{ + if (mStats == NULL) + return; + mStats->mHeapSize = TCMalloc_SystemTaken; +} + void BFGC::Run() { BfpThread_SetName(BfpThread_GetCurrent(), "BFGC", NULL); @@ -1824,6 +1840,8 @@ void BFGC::Run() while (!mExiting) { + UpdateStats(); + float fullGCPeriod = mFullGCPeriod; if ((fullGCPeriod != -1) && (mMaxPausePercentage > 0) && (!mCollectReports.IsEmpty())) { @@ -1835,7 +1853,7 @@ void BFGC::Run() fullGCPeriod = BF_MIN(fullGCPeriod, maxExpandPeriod); } - int waitPeriod = fullGCPeriod; + int waitPeriod = BF_MIN(fullGCPeriod, 100); if (waitPeriod == 0) waitPeriod = -1; mCollectEvent.WaitFor(waitPeriod); diff --git a/BeefRT/dbg/gc.h b/BeefRT/dbg/gc.h index 3b30d037..fe1eae19 100644 --- a/BeefRT/dbg/gc.h +++ b/BeefRT/dbg/gc.h @@ -164,6 +164,11 @@ namespace tcmalloc_raw class BFGC { public: + struct Stats + { + intptr mHeapSize; + }; + struct ThreadInfo { static BF_TLS_DECLSPEC ThreadInfo* sCurThreadInfo; @@ -276,6 +281,7 @@ public: BfpThreadId mThreadId; + Stats* mStats; volatile bool mExiting; volatile bool mRunning; bool mGracelessShutdown; @@ -368,6 +374,7 @@ public: void DoCollect(bool doingFullGC); void FinishCollect(); + void UpdateStats(); void Run(); static void BFP_CALLTYPE RunStub(void* gc); diff --git a/IDE/src/IDEApp.bf b/IDE/src/IDEApp.bf index 4f8403d0..5c8f0c0b 100644 --- a/IDE/src/IDEApp.bf +++ b/IDE/src/IDEApp.bf @@ -12873,6 +12873,7 @@ namespace IDE for (var project in mWorkspace.mProjects) project.Update(); UpdateCompilersAndDebugger(); + gApp.mDiagnosticsPanel.UpdateStats(); if (mScriptManager != null) mScriptManager.Update(); diff --git a/IDE/src/ui/DiagnosticsPanel.bf b/IDE/src/ui/DiagnosticsPanel.bf index 1b0235e9..664223d0 100644 --- a/IDE/src/ui/DiagnosticsPanel.bf +++ b/IDE/src/ui/DiagnosticsPanel.bf @@ -1,25 +1,695 @@ using System; using Beefy.utils; using System.Diagnostics; +using System.Collections; +using Beefy.gfx; +using Beefy.geom; +using Beefy.theme.dark; +using Beefy.widgets; namespace IDE.ui { class DiagnosticsPanel : Panel { + class DbgAllocListViewItem : IDEListViewItem + { + public int mTypeId; + public int mAllocCount; + public int mAllocSize; + } + + class DbgAllocListView : DarkListView + { + public this() + { + //mShowLineGrid = true; + } + + protected override ListViewItem CreateListViewItem() + { + return new DbgAllocListViewItem(); + } + + public override void DrawAll(Graphics g) + { + base.DrawAll(g); + + /*using (g.PushColor(0x40FF0000)) + g.FillRect(8, 8, mWidth - 16, mHeight - 16);*/ + } + } + + struct Sample + { + public double mValue; + public double mTime; + + public this(double value, double time) + { + mValue = value; + mTime = time; + } + + public static Sample operator+(Sample lhs, Sample rhs) + { + return .(lhs.mValue + rhs.mValue, lhs.mTime + rhs.mTime); + } + + public static Sample operator-(Sample lhs, Sample rhs) + { + return .(lhs.mValue - rhs.mValue, lhs.mTime - rhs.mTime); + } + + public static Sample operator*(Sample lhs, float rhs) + { + return .(lhs.mValue * rhs, lhs.mTime * rhs); + } + } + + class Data + { + public const int cMaxSamples = 1000; + public static float[cMaxSamples + 1] sSampleMixTable; + + static this() + { + for (int i < cMaxSamples + 1) + { + float pct = (i + 0.1f) / (float)(cMaxSamples - 3); + pct = Math.Pow(pct, 1.5f); + sSampleMixTable[i] = pct; + } + } + + public List mSamples = new .() ~ delete _; + double mPrevSample; + double mNextSample; + + public double mMaxValue; + List mPrevValues = new List() ~ delete _; + + double mSampleAcc; + int32 mSampleCount; + int32 mSampleCombine; + + public this(int sampleCombine) + { + mSampleCombine = (.)sampleCombine; + } + + public void AddSample(double sample) + { + double insertSample = sample; + + if (mSampleCombine > 1) + { + if (mSamples.IsEmpty) + { + mPrevSample = sample; + mNextSample = sample; + } + + mSampleAcc += sample; + mSampleCount++; + + insertSample = Math.Lerp(mPrevSample, mNextSample, mSampleCount / (float)mSampleCombine); + + if (mSampleCount == mSampleCombine) + { + mPrevSample = mNextSample; + mNextSample = mSampleAcc / mSampleCombine; + mSampleCount = 0; + mSampleAcc = 0; + } + } + + mSamples.Add(insertSample); + + mPrevValues.Add(insertSample); + + mMaxValue = Math.Max(mMaxValue, insertSample); + + while (mSamples.Count > cMaxSamples) + { + for (int sampleIdx < mSamples.Count - 1) + { + let lhs = mSamples[sampleIdx]; + let rhs = mSamples[sampleIdx + 1]; + float pct = sSampleMixTable[sampleIdx]; + mSamples[sampleIdx] = Math.Lerp(lhs, rhs, pct); + } + mSamples.PopBack(); + } + } + + public void Clear() + { + mMaxValue = 0; + mSamples.Clear(); + } + + public float Get(float sampleIdx) + { + let lhs = mSamples[(int)sampleIdx]; + let rhs = mSamples[(int)Math.Min(sampleIdx + 1, mSamples.Count - 1)]; + return (.)Math.Lerp(lhs, rhs, sampleIdx - (int)sampleIdx); + } + } + + class DataView : Widget + { + public DiagnosticsPanel mPanel; + public Data mTimes; + public Data mData; + public double? mOverDataTime; + public double? mOverDataValue; + public String mLabel = new .() ~ delete _; + + public this(StringView label) + { + mLabel.Set(label); + } + + [LinkName(.C)] + public static extern double log10(double val); + + [LinkName(.C)] + public static extern double log2(double val); + + void ValToString(double val, String str) + { + let kSize = 1024; + + let maxValue = mData.mMaxValue; + if (maxValue < 1000) + { + str.AppendF("{:0.00}", val); + } + else if (maxValue < 10000) + { + str.AppendF("{:0.0}", val); + } + else if (maxValue < 1'000'000) + { + str.AppendF("{}", (int)val); + } + else if (maxValue < 1000 * kSize) + { + str.AppendF("{:0.000}K", val / kSize); + } + else if (maxValue < 1000 * (kSize * kSize)) + { + str.AppendF("{:0.000}M", val / (kSize * kSize)); + } + else + { + str.AppendF("{:0.000}G", val / (kSize * kSize * kSize)); + } + } + + public void DrawData(Graphics g, Data data, Data timeData, float xOfs, float yOfs, float width, float height) + { + + + /*for (double maxValue = 0.001; maxValue < 2; maxValue += 0.001) + { + double segLog10 = log10(maxValue); + if (segLog10 > 0) + segLog10 = (int)(segLog10); + double segSize = Math.Pow(10, segLog10); + double numSegs = maxValue / segSize; + if (numSegs < 2) + segSize /= 4; + else if (numSegs < 4) + segSize /= 2; + numSegs = maxValue / segSize; + + Debug.WriteLine("{} {:.00} {:.00}", maxValue, segSize, numSegs); + }*/ + + /*while (true) + { + double numSegs = maxValue / segSize; + if (numSegs >= 3) + break; + }*/ + + + + using (g.PushTranslate(xOfs, yOfs)) + { + using (g.PushColor(0xA0000000)) + { + g.FillRect(0, 0, width, height); + } + + using (g.PushColor(0x40FFFFFF)) + { + g.OutlineRect(-1, -1, width + 2, height + 2); + } + + if (data.mSamples.IsEmpty) + return; + + if (data.mMaxValue > 0) + { + using (g.PushClip(0, 0, width, height)) + { + using (g.PushColor(0xFF00FF00)) + { + float prevY = 0; + + for (int x < (int)width) + { + float sampleIdx = x * (data.mSamples.Count - 1) / width; + float value = data.Get(sampleIdx); + + float y = (.)(height * (1.0 - (value / data.mMaxValue)) + 0.5); + + if (x == 0) + g.FillRect(x, y, 1, 1); + else if (y > prevY) + g.FillRect(x, prevY, 1, y - prevY + 1); + else + g.FillRect(x, y, 1, prevY - y + 1); + + prevY = y; + } + } + } + + float labelX = 0; + float labelWidth = 0; + + g.SetFont(DarkTheme.sDarkTheme.mSmallFont); + int[?] times = .(5, 15, 60, 5*60, 15*60); + int findIdx = 0; + int findTime = times[0]; + double prevTime = mPanel.mCurTimeSecs; + + bool wantOverTime = mOverDataTime != null; + + bool drawLabels = width > GS!(240); + for (int idx = data.mSamples.Count - 1; idx >= 0; idx--) + { + double checkTime = timeData.mSamples[idx]; + + if ((wantOverTime) && (checkTime <= mOverDataTime)) + { + wantOverTime = false; + + //float x = idx + (float)((findTime - checkTime) / (prevTime - checkTime)); + + float overIdx = idx; // + (float)((mOverDataTime - checkTime) / (prevTime - checkTime)); + + float x = (int)((overIdx / (float)(data.mSamples.Count - 1)) * width); + + double value = mOverDataValue.Value; + g.SetFont(DarkTheme.sDarkTheme.mSmallBoldFont); + + int deltaSecs = (int)(mPanel.mCurTimeSecs - mOverDataTime.Value); + + String drawStr = scope .(); + ValToString(value, drawStr); + drawStr.AppendF(" at {}:{:00}", deltaSecs / 60, deltaSecs % 60); + + labelWidth = g.mFont.GetWidth(drawStr); + + labelX = Math.Clamp(x, labelWidth / 2, width - labelWidth / 2); + + g.DrawString(drawStr, labelX, -GS!(19), .Centered); + + using (g.PushColor(0x70FFFFFF)) + g.FillRect(x, 0, 1, height); + } + + double timeDif = mPanel.mCurTimeSecs - checkTime; + if (timeDif >= findTime) + { + double prevTimeDif = mPanel.mCurTimeSecs - prevTime; + float addIdx = idx + (float)((timeDif - findTime) / (timeDif - prevTimeDif)); + + float x = (int)((addIdx / (data.mSamples.Count - 1)) * width); + + using (g.PushColor(0xA0FFFFFF)) + g.FillRect(x, 0, 1, height); + + if (drawLabels) + { + uint32 color = 0xFFFFFFFF; + if (Math.Abs(x - labelX) < labelWidth / 2 + GS!(20)) + color = 0x60FFFFFF; + using (g.PushColor(color)) + g.DrawString(scope String()..AppendF("{}:{:00}", findTime / 60, findTime % 60), x + GS!(2), -GS!(20), .Centered); + } + + if (++findIdx >= times.Count) + break; + findTime = times[findIdx]; + } + + prevTime = checkTime; + } + } + + // Draw segments + + double maxValue = mData.mMaxValue; + double segLog10 = log10(maxValue); + if (segLog10 > 0) + segLog10 = (int)(segLog10); + double segSize = Math.Pow(10, segLog10); + + if (maxValue >= 1'000'000) + { + double segLog2 = log2(maxValue); + segLog2 = (int)(segLog2); + segSize = Math.Pow(2, segLog2); + } + + double numSegs = maxValue / segSize; + + + if (numSegs < 2) + segSize /= 4; + else if (numSegs < 4) + segSize /= 2; + + /*int kSize = 1024; + if (segSize >= kSize * kSize * kSize) + segSize = (int)(segSize / (kSize * kSize * kSize)) * (kSize * kSize * kSize); + else if (segSize >= kSize * kSize) + segSize = (int)(segSize / (kSize * kSize)) * (kSize * kSize); + else if (segSize >= kSize) + segSize = (int)(segSize / (kSize)) * (kSize);*/ + + numSegs = (int)(maxValue / segSize + 0.95f); + + g.SetFont(DarkTheme.sDarkTheme.mSmallFont); + for (int segIdx = 1; segIdx < (int)numSegs; segIdx++) + { + bool drawLabels = width > GS!(120); + double segValue = segIdx * segSize; + + float drawY = (int)((1 - segValue / maxValue) * (height - 1)); + using (g.PushColor(0x40FFFFFF)) + g.FillRect(0, drawY, width, 1); + if (drawLabels) + { + var labelStr = scope String(); + ValToString(segValue, labelStr); + g.DrawString(labelStr, -xOfs, drawY - GS!(10), .Right, xOfs - GS!(4)); + } + } + } + } + + Rect GetDataRect() + { + return .(GS!(80), GS!(20), mWidth - GS!(80) - 2, mHeight - GS!(20) - 2); + } + + public override void MouseLeave() + { + base.MouseLeave(); + mOverDataTime = null; + } + + public override void MouseMove(float x, float y) + { + base.MouseMove(x, y); + + let dataRect = GetDataRect(); + if ((x >= dataRect.mX) && (x < dataRect.Right) && (!mData.mSamples.IsEmpty)) + { + float overDataIdx = (x - dataRect.mX) / (float)dataRect.mWidth * mData.mSamples.Count; + mOverDataTime = mTimes.Get(overDataIdx); + mOverDataValue = mData.Get(overDataIdx); + } + else + mOverDataTime = null; + } + + public override void Draw(Graphics g) + { + base.Draw(g); + + let dataRect = GetDataRect(); + + using (g.PushColor(0x20FFFFFF)) + g.FillRect(0, 0, mWidth, mHeight); + + DrawData(g, mData, mTimes, dataRect.mX, dataRect.mY, dataRect.mWidth, dataRect.mHeight); + + g.SetFont(DarkTheme.sDarkTheme.mSmallBoldFont); + g.DrawString(mLabel, -GS!(0), -GS!(0), .Centered, dataRect.mX); + } + } + +#region fields + ScrollableWidget mScrollableWidget; + Widget mContentWidget; + Stopwatch mSampleStopwatch = new .() ~ delete _; - int mSampleKernelTime; - int mSampleUserTime; + int mSampleCPUTime; + double mCurTimeSecs; + + const int cMaxSamples = 1024; + Data mRunningTime = new .(0) ~ delete _; + Data mUserTime = new .(6) ~ delete _; + Data mWorkingMem = new .(0) ~ delete _; + Data mVirtualMem = new .(0) ~ delete _; + Data mDbgAllocMem = new .(0) ~ delete _; + + List mDataViews = new List() ~ delete _; + DbgAllocListView mDbgAllocListView; + DarkButton mRefreshButton; public this() { mSampleStopwatch.Start(); + /*for (int i < 10) + AddSample(mUserTime, .(i, i));*/ + + mScrollableWidget = new ScrollableWidget(); + mScrollableWidget.InitScrollbars(false, true); + AddWidget(mScrollableWidget); + + mContentWidget = new Widget(); + mScrollableWidget.mScrollContentContainer.AddWidget(mContentWidget); + mScrollableWidget.mScrollContent = mContentWidget; + + DataView dataView = new DataView("CPU"); + dataView.mPanel = this; + dataView.mTimes = mRunningTime; + dataView.mData = mUserTime; + mContentWidget.AddWidget(dataView); + mDataViews.Add(dataView); + + dataView = new DataView("VMem"); + dataView.mPanel = this; + dataView.mTimes = mRunningTime; + dataView.mData = mVirtualMem; + mContentWidget.AddWidget(dataView); + mDataViews.Add(dataView); + + dataView = new DataView("WkMem"); + dataView.mPanel = this; + dataView.mTimes = mRunningTime; + dataView.mData = mWorkingMem; + mContentWidget.AddWidget(dataView); + mDataViews.Add(dataView); + + dataView = new DataView("DbgAlloc"); + dataView.mPanel = this; + dataView.mTimes = mRunningTime; + dataView.mData = mDbgAllocMem; + mContentWidget.AddWidget(dataView); + mDataViews.Add(dataView); + + mRefreshButton = new DarkButton(); + mRefreshButton.Label = "Get Debug Alloc Information"; + mRefreshButton.mOnMouseClick.Add(new (mouseArgs) => + { + if (gApp.mDebugger.mIsRunning) + { + var info = scope String(); + //let sampler = Profiler.StartSampling().GetValueOrDefault(); + gApp.mDebugger.GetDbgAllocInfo(info); + /*if (sampler != default) + sampler.Dispose();*/ + + PopulateDbgAllocInfo(info); + } + }); + mContentWidget.AddWidget(mRefreshButton); + + mDbgAllocListView = new DbgAllocListView(); + + ListViewColumn column = mDbgAllocListView.AddColumn(300, "Type"); + column.mMinWidth = 100; + + column = mDbgAllocListView.AddColumn(80, "Count"); + column.mMinWidth = 60; + column = mDbgAllocListView.AddColumn(80, "Size"); + column.mMinWidth = 60; + + mDbgAllocListView.mOnItemMouseDown.Add(new => ListViewItemMouseDown); + mDbgAllocListView.mOnItemMouseClicked.Add(new => ListViewItemMouseClicked); + mDbgAllocListView.mOnKeyDown.Add(new => ListViewKeyDown_ShowMenu); + + mContentWidget.AddWidget(mDbgAllocListView); + + ResizeComponents(); } - public override void Update() + void Clear() { + mSampleCPUTime = 0; + mCurTimeSecs = 0; + mUserTime.Clear(); + mRunningTime.Clear(); + mVirtualMem.Clear(); + mWorkingMem.Clear(); + mDbgAllocMem.Clear(); + mDbgAllocListView.GetRoot().Clear(); + } + + void ResizeComponents() + { + mScrollableWidget.Resize(0, 0, mWidth, mHeight); + + float width = Math.Max(mScrollableWidget.mWidth - GS!(20), 0); + + float dataViewWidth = Math.Max((width - GS!(24)) / 2, 100); + float dataViewHeight = (int)(dataViewWidth * 0.5f); + + for (let dataView in mDataViews) + { + int col = @dataView.Index % 2; + int row = @dataView.Index / 2; + + + dataView.Resize(col * (dataViewWidth + GS!(8)) + GS!(8), row * (dataViewHeight + GS!(8)) + GS!(8), dataViewWidth, dataViewHeight); + } + + float curY = dataViewHeight * 2 + GS!(24); + + mRefreshButton.Resize(GS!(8), curY, GS!(240), GS!(20)); + mDbgAllocListView.Resize(GS!(0), curY + GS!(20), Math.Max(width - GS!(0), 120), mDbgAllocListView.mScrollContent.mHeight + GS!(28)); + + mContentWidget.Resize(0, 0, width, curY + mDbgAllocListView.mScrollContent.mHeight + GS!(48)); + mScrollableWidget.UpdateScrollbars(); + mScrollableWidget.UpdateContentPosition(); + } + + void PopulateDbgAllocInfo(String info) + { + //Debug.WriteLine(info); + + mDbgAllocListView.GetRoot().Clear(); + + String totalLabel = "Total Debug Allocations"; + + int totalSize = 0; + int totalCount = 0; + + DbgAllocListViewItem AddItem(StringView label, int allocCount, int allocSize) + { + var newItem = (DbgAllocListViewItem)mDbgAllocListView.GetRoot().CreateChildItem(); + newItem.mAllocCount = allocCount; + newItem.mAllocSize = allocSize; + newItem.Label = label; + + var subItem = newItem.GetOrCreateSubItem(1); + subItem.Label = scope String()..AppendF("{}", allocCount); + + subItem = newItem.GetOrCreateSubItem(2); + subItem.Label = scope String()..AppendF("{}k", (allocSize + 1023)/1024); + return newItem; + } + + for (let line in info.Split('\n')) + { + var elemItr = line.Split('\t'); + switch (elemItr.GetNext().Value) + { + case "type": + int typeId = int.Parse(elemItr.GetNext().Value).Value; + StringView name = elemItr.GetNext().Value; + int allocCount = int.Parse(elemItr.GetNext().Value).Value; + int allocSize = int.Parse(elemItr.GetNext().Value).Value; + + totalCount += allocCount; + totalSize += allocSize; + + var newItem = AddItem(name, allocCount, allocSize); + newItem.mTypeId = typeId; + //newItem.MakeParent(); + } + } + + AddItem(totalLabel, totalCount, totalSize); + + mDbgAllocListView.GetRoot().mChildItems.Sort(scope (lhs, rhs) => + { + return ((DbgAllocListViewItem)rhs).mAllocSize <=> ((DbgAllocListViewItem)lhs).mAllocSize; + }); + mDbgAllocListView.GetRoot().ResizeComponents(0); + mDbgAllocListView.mAutoFocus = true; + mDbgAllocListView.mOnLostFocus.Add(new (evt) => + { + if (!mShowingRightClickMenu) + mDbgAllocListView.GetRoot().SelectItemExclusively(null); + }); + + mDbgAllocListView.UpdateListSize(); + ResizeComponents(); + } + + public override void Draw(Graphics g) + { + base.Draw(g); + + } + + public void UpdateStats() + { + /*int addIdx = mUpdateCnt; + AddSample(mUserTime, .(addIdx, addIdx));*/ + + scope AutoBeefPerf("DiagnosticsPanel.UpdateStats"); + String procInfo = scope .(); gApp.mDebugger.GetProcessInfo(procInfo); + if (gApp.mDebugger.IsPaused()) + { + mSampleStopwatch.Restart(); + return; + } + + if (procInfo.IsEmpty) + { + Clear(); + return; + } + + if (gApp.mUpdateCnt % 5 != 0) + { + return; + } + + MarkDirty(); + if (mSampleStopwatch.ElapsedMilliseconds < 10) + return; + + // 5 Hz sample rate + /*if (elapsedTime < 100) + return;*/ + mSampleStopwatch.Restart(); + + int runningTime = 0; int virtualMem = 0; int workingMem = 0; int kernelTime = 0; @@ -39,12 +709,40 @@ namespace IDE.ui { case "WorkingMemory": workingMem = value; case "VirtualMemory": virtualMem = value; + case "RunningTime": runningTime = value; case "KernelTime": kernelTime = value; case "UserTime": userTime = value; } } + int cpuTime = kernelTime + userTime; + double newTimeSecs = runningTime / (double)(10*1000*1000); + double elapsedTime = newTimeSecs - mCurTimeSecs; + + mCurTimeSecs = newTimeSecs; + + mRunningTime.AddSample(mCurTimeSecs); + mUserTime.AddSample((cpuTime - mSampleCPUTime) * 100 / elapsedTime / 10000000.0f); + mWorkingMem.AddSample(workingMem); + mVirtualMem.AddSample(virtualMem); + mDbgAllocMem.AddSample(gApp.mDebugger.GetDbgAllocHeapSize()); + + mSampleCPUTime = cpuTime; + + /*int addIdx = mUpdateCnt; + AddSample(mUserTime, .(addIdx, addIdx));*/ + } + + public override void Update() + { + base.Update(); + } + + public override void Resize(float x, float y, float width, float height) + { + base.Resize(x, y, width, height); + ResizeComponents(); } public override void Serialize(StructuredData data) diff --git a/IDEHelper/Compiler/BfModule.cpp b/IDEHelper/Compiler/BfModule.cpp index 05193afc..5a278b03 100644 --- a/IDEHelper/Compiler/BfModule.cpp +++ b/IDEHelper/Compiler/BfModule.cpp @@ -9874,15 +9874,10 @@ void BfModule::CurrentAddToConstHolder(BfIRValue& irVal) newVals.push_back(newVal); } - irVal = mCurTypeInstance->mConstHolder->CreateConstArray(constArray->mType, newVals); + irVal = mCurTypeInstance->GetOrCreateConstHolder()->CreateConstArray(constArray->mType, newVals); return; } - if (constant->mConstType == BfConstType_GlobalVar) - { - NOP; - } - auto origConst = irVal; if ((constant->mConstType == BfConstType_BitCast) || (constant->mConstType == BfConstType_BitCastNull)) { diff --git a/IDEHelper/DbgExprEvaluator.cpp b/IDEHelper/DbgExprEvaluator.cpp index 814698c3..59cbf79b 100644 --- a/IDEHelper/DbgExprEvaluator.cpp +++ b/IDEHelper/DbgExprEvaluator.cpp @@ -732,10 +732,19 @@ DbgExprEvaluator::DbgExprEvaluator(WinDebugger* winDebugger, DbgModule* dbgModul mCapturingChildRef = true; mPassInstance = passInstance; mDebugger = winDebugger; - mLanguage = DbgLanguage_NotSet; - mDebugTarget = dbgModule->mDebugTarget; + mLanguage = DbgLanguage_NotSet; mOrigDbgModule = dbgModule; - mDbgModule = dbgModule->GetLinkedModule(); + if (dbgModule != NULL) + { + mDebugTarget = dbgModule->mDebugTarget; + mDbgModule = dbgModule->GetLinkedModule(); + } + else + { + mDebugTarget = NULL; + mDbgModule = NULL; + } + mDbgCompileUnit = NULL; mExplicitThisExpr = NULL; mExpectingType = NULL; @@ -1125,12 +1134,14 @@ DbgTypedValue DbgExprEvaluator::GetBeefTypeById(int typeId) if (mDebugTarget->mTargetBinary == NULL) return DbgTypedValue(); + mDebugTarget->mTargetBinary->ParseTypeData(); auto typeTypeEntry = mDebugTarget->mTargetBinary->FindType("System.Type", DbgLanguage_Beef); if ((typeTypeEntry == NULL) || (typeTypeEntry->mValue == NULL)) return DbgTypedValue(); auto typeType = typeTypeEntry->mValue; - mDbgModule->PopulateTypeGlobals(typeType); + if (typeType->mNeedsGlobalsPopulated) + typeType->mCompileUnit->mDbgModule->PopulateTypeGlobals(typeType); for (auto member : typeType->mMemberList) { if ((member->mIsStatic) && (member->mName != NULL) && (strcmp(member->mName, "sTypes") == 0) && (member->mLocationData != NULL)) @@ -1232,6 +1243,12 @@ void DbgExprEvaluator::BeefTypeToString(const DbgTypedValue& val, String& outStr _TypeCode mElementType; }; + struct _SizedArrayType : _Type + { + _TypeId mElementType; + int32 mElementCount; + }; + struct _String { @@ -1267,16 +1284,27 @@ void DbgExprEvaluator::BeefTypeToString(const DbgTypedValue& val, String& outStr uint8 mInterfaceCount; int16 mMethodDataCount; int16 mPropertyDataCount; - int16 mFieldDataCount; - int16 mConstructorDataCount; + int16 mFieldDataCount; void* mInterfaceDataPtr; _MethodData* mMethodDataPtr; void* mPropertyDataPtr; - _FieldData* mFieldDataPtr; - void* mConstructorDataPtr; + _FieldData* mFieldDataPtr; void** mCustomAttrDataPtr; }; + + struct _SpecializedGenericType : _TypeInstance + { + _TypeId mUnspecializedType; + _TypeId* mResolvedTypeRefs; + }; + + struct _ArrayType : _SpecializedGenericType + { + int32 mElementSize; + uint8 mRank; + uint8 mElemensDataOffset; + }; int typeIdSize = sizeof(_TypeId); int ptrSize = (int)sizeof(addr_target); @@ -1284,12 +1312,54 @@ void DbgExprEvaluator::BeefTypeToString(const DbgTypedValue& val, String& outStr int typeSize = sizeof(_Type); int typeInstanceSize = objectSize + sizeof(_TypeInstance); - + auto addr = useVal.mSrcAddress; auto typeAddr = addr + objectSize; _TypeFlags typeFlags = mDebugger->ReadMemory<_TypeFlags>(typeAddr + offsetof(_Type, mTypeFlags)); - if (((typeFlags & BfTypeFlags_Struct) != 0) || ((typeFlags & BfTypeFlags_TypedPrimitive) != 0) || ((typeFlags & BfTypeFlags_Object) != 0)) + + if ((typeFlags & BfTypeFlags_Array) != 0) + { + _TypeId unspecializedTypeId = mDebugger->ReadMemory<_TypeId>(typeAddr + offsetof(_SpecializedGenericType, mUnspecializedType)); + addr_target elementsArrayAddr = mDebugger->ReadMemory(typeAddr + offsetof(_SpecializedGenericType, mResolvedTypeRefs)); + _TypeId elementTypeId = mDebugger->ReadMemory<_TypeId>(elementsArrayAddr); + + auto elementType = GetBeefTypeById(elementTypeId); + BeefTypeToString(elementType, outStr); + + outStr += "["; + int rank = mDebugger->ReadMemory(typeAddr + offsetof(_ArrayType, mRank)); + for (int commaIdx = 0; commaIdx < rank - 1; commaIdx++) + outStr += ","; + outStr += "]"; + } + else if ((typeFlags & BfTypeFlags_Pointer) != 0) + { + _TypeId elementTypeId = mDebugger->ReadMemory<_TypeId>(typeAddr + offsetof(_PointerType, mElementType)); + auto elementType = GetBeefTypeById(elementTypeId); + BeefTypeToString(elementType, outStr); + } + else if ((typeFlags & BfTypeFlags_Delegate) != 0) + { + outStr += "delegate"; + } + else if ((typeFlags & BfTypeFlags_Function) != 0) + { + outStr += "function"; + } +// else if ((typeFlags & BfTypeFlags_Tuple) != 0) +// { +// outStr += "function"; +// } + else if ((typeFlags & BfTypeFlags_SizedArray) != 0) + { + _TypeId elementTypeId = mDebugger->ReadMemory<_TypeId>(typeAddr + objectSize + offsetof(_SizedArrayType, mElementType)); + auto elementType = GetBeefTypeById(elementTypeId); + BeefTypeToString(elementType, outStr); + int elementCount = mDebugger->ReadMemory(typeAddr + objectSize + offsetof(_SizedArrayType, mElementCount)); + outStr += StrFormat("[%d]", elementCount); + } + else if (((typeFlags & BfTypeFlags_Struct) != 0) || ((typeFlags & BfTypeFlags_TypedPrimitive) != 0) || ((typeFlags & BfTypeFlags_Object) != 0)) { addr_target namePtr = mDebugger->ReadMemory(typeAddr + offsetof(_TypeInstance, mName)); addr_target namespacePtr = mDebugger->ReadMemory(typeAddr + offsetof(_TypeInstance, mNamespace)); diff --git a/IDEHelper/DebugManager.cpp b/IDEHelper/DebugManager.cpp index 030d7279..d4c2bd36 100644 --- a/IDEHelper/DebugManager.cpp +++ b/IDEHelper/DebugManager.cpp @@ -1535,6 +1535,22 @@ BF_EXPORT void BF_CALLTYPE Debugger_InitiateHotResolve(int flags) gDebugger->InitiateHotResolve((DbgHotResolveFlags)flags); } + +BF_EXPORT intptr BF_CALLTYPE Debugger_GetDbgAllocHeapSize() +{ + AutoCrit autoCrit(gDebugManager->mCritSect); + return gDebugger->GetDbgAllocHeapSize(); +} + +BF_EXPORT const char* BF_CALLTYPE Debugger_GetDbgAllocInfo() +{ + AutoCrit autoCrit(gDebugManager->mCritSect); + + String& outString = *gTLStrReturn.Get(); + outString = gDebugger->GetDbgAllocInfo(); + return outString.c_str(); +} + BF_EXPORT const char* BF_CALLTYPE Debugger_GetHotResolveData(uint8* outTypeData, int* outTypeDataSize) { AutoCrit autoCrit(gDebugManager->mCritSect); @@ -1554,7 +1570,10 @@ BF_EXPORT const char* BF_CALLTYPE Debugger_GetHotResolveData(uint8* outTypeData, *outTypeDataSize = dataSize; if (dataSize > 0) - memcpy(outTypeData, &gDebugger->mHotResolveData->mTypeData[0], dataSize); + { + for (int i = 0; i < dataSize; i++) + outTypeData[i] = (gDebugger->mHotResolveData->mTypeData[i].mCount > 0) ? 1 : 0; + } String& outString = *gTLStrReturn.Get(); outString.Clear(); diff --git a/IDEHelper/Debugger.h b/IDEHelper/Debugger.h index 3424ef6b..1b0a740e 100644 --- a/IDEHelper/Debugger.h +++ b/IDEHelper/Debugger.h @@ -215,7 +215,20 @@ public: class DbgHotResolveData { public: - Array mTypeData; + struct TypeData + { + intptr mCount; + intptr mSize; + + TypeData() + { + mCount = 0; + mSize = 0; + } + }; + +public: + Array mTypeData; Beefy::HashSet mBeefCallStackEntries; }; @@ -248,6 +261,8 @@ public: virtual void Run() = 0; virtual void HotLoad(const Array& objectFiles, int hotIdx) = 0; virtual void InitiateHotResolve(DbgHotResolveFlags flags) = 0; + virtual intptr GetDbgAllocHeapSize() = 0; + virtual String GetDbgAllocInfo() = 0; virtual void Update() = 0; virtual void ContinueDebugEvent() = 0; virtual void ForegroundTarget() = 0; diff --git a/IDEHelper/HotScanner.cpp b/IDEHelper/HotScanner.cpp index c9c850e0..579cbde5 100644 --- a/IDEHelper/HotScanner.cpp +++ b/IDEHelper/HotScanner.cpp @@ -169,11 +169,15 @@ void DbgHotScanner::ScanSpan(TCFake::Span* span, int expectedStartPage, int memK elementSize = spanSize; //BF_LOGASSERT(elementSize >= sizeof(bf::System::Object)); - auto _MarkTypeUsed = [&](int typeId) + auto _MarkTypeUsed = [&](int typeId, intptr size) { + if (typeId < 0) + return; while (mDebugger->mHotResolveData->mTypeData.size() <= typeId) - mDebugger->mHotResolveData->mTypeData.Add(0); - mDebugger->mHotResolveData->mTypeData[typeId] = 1; + mDebugger->mHotResolveData->mTypeData.Add(DbgHotResolveData::TypeData()); + auto& typeData = mDebugger->mHotResolveData->mTypeData[typeId]; + typeData.mSize += size; + typeData.mCount++; }; int objectSize = ((mDbgGCData.mDbgFlags & BfRtFlags_ObjectHasDebugFlags) != 0) ? sizeof(addr_target)*2 : sizeof(addr_target); @@ -206,36 +210,68 @@ void DbgHotScanner::ScanSpan(TCFake::Span* span, int expectedStartPage, int memK if ((mDbgGCData.mDbgFlags & BfRtFlags_ObjectHasDebugFlags) != 0) classVDataAddr = classVDataAddr & ~0xFF; } - else if (mFoundRawAllocDataAddrs.Add(rawAllocDataAddr)) + else { - Fake_DbgRawAllocData rawAllocData = mDebugger->ReadMemory(rawAllocDataAddr); - if ((rawAllocData.mType != NULL) && (mFoundTypeAddrs.Add(rawAllocData.mType))) + + int* rawTypeIdPtr = NULL; + if (mFoundRawAllocDataAddrs.TryAdd(rawAllocDataAddr, NULL, &rawTypeIdPtr)) { - Fake_Type_Data typeData; - if (mDebugger->ReadMemory(rawAllocData.mType + objectSize, sizeof(typeData), &typeData)) - _MarkTypeUsed(typeData.mTypeId); + *rawTypeIdPtr = -1; + Fake_DbgRawAllocData rawAllocData = mDebugger->ReadMemory(rawAllocDataAddr); + if (rawAllocData.mType != NULL) + { + int* typeAddrIdPtr = NULL; + if (mFoundTypeAddrs.TryAdd(rawAllocData.mType, NULL, &typeAddrIdPtr)) + { + *typeAddrIdPtr = -1; + Fake_Type_Data typeData; + if (mDebugger->ReadMemory(rawAllocData.mType + objectSize, sizeof(typeData), &typeData)) + { + *typeAddrIdPtr = typeData.mTypeId; + *rawTypeIdPtr = typeData.mTypeId; + _MarkTypeUsed(typeData.mTypeId, elementSize); + } + } + else + { + _MarkTypeUsed(*typeAddrIdPtr, elementSize); + } + } + } + else + { + _MarkTypeUsed(*rawTypeIdPtr, elementSize); } } } } - if ((classVDataAddr != 0) && (mFoundClassVDataAddrs.Add(classVDataAddr))) + if (classVDataAddr != 0) { - addr_target typeAddr = mDebugger->ReadMemory(classVDataAddr); - Fake_Type_Data typeData; - mDebugger->ReadMemory(typeAddr + objectSize, sizeof(typeData), &typeData); - - _MarkTypeUsed(typeData.mTypeId); - if ((typeData.mTypeFlags & BfTypeFlags_Delegate) != 0) + int* typeIdPtr = NULL; + if (mFoundClassVDataAddrs.TryAdd(classVDataAddr, NULL, &typeIdPtr)) { - Fake_Delegate_Data* dlg = (Fake_Delegate_Data*)((uint8*)spanPtr + objectSize); - if (mFoundFuncPtrs.Add(dlg->mFuncPtr)) + addr_target typeAddr = mDebugger->ReadMemory(classVDataAddr); + Fake_Type_Data typeData; + mDebugger->ReadMemory(typeAddr + objectSize, sizeof(typeData), &typeData); + + *typeIdPtr = typeData.mTypeId; + _MarkTypeUsed(typeData.mTypeId, elementSize); + if ((typeData.mTypeFlags & BfTypeFlags_Delegate) != 0) { - auto subProgram = mDebugger->mDebugTarget->FindSubProgram(dlg->mFuncPtr, DbgOnDemandKind_None); - if ((subProgram != NULL) && (subProgram->GetLanguage() == DbgLanguage_Beef)) - AddSubProgram(subProgram, true, "D "); + Fake_Delegate_Data* dlg = (Fake_Delegate_Data*)((uint8*)spanPtr + objectSize); + if (mFoundFuncPtrs.Add(dlg->mFuncPtr)) + { + auto subProgram = mDebugger->mDebugTarget->FindSubProgram(dlg->mFuncPtr, DbgOnDemandKind_None); + if ((subProgram != NULL) && (subProgram->GetLanguage() == DbgLanguage_Beef)) + AddSubProgram(subProgram, true, "D "); + } } } + else + { + _MarkTypeUsed(*typeIdPtr, elementSize); + } } spanPtr = (void*)((intptr)spanPtr + elementSize); diff --git a/IDEHelper/HotScanner.h b/IDEHelper/HotScanner.h index cf1fe66b..f6ce6bb9 100644 --- a/IDEHelper/HotScanner.h +++ b/IDEHelper/HotScanner.h @@ -84,9 +84,9 @@ class DbgHotScanner public: WinDebugger* mDebugger; DbgGCData mDbgGCData; - Beefy::HashSet mFoundClassVDataAddrs; - Beefy::HashSet mFoundRawAllocDataAddrs; - Beefy::HashSet mFoundTypeAddrs; + Beefy::Dictionary mFoundClassVDataAddrs; + Beefy::Dictionary mFoundRawAllocDataAddrs; + Beefy::Dictionary mFoundTypeAddrs; Beefy::HashSet mFoundFuncPtrs; #ifdef BF_DBG_32 diff --git a/IDEHelper/WinDebugger.cpp b/IDEHelper/WinDebugger.cpp index 92cf7c24..eb042f67 100644 --- a/IDEHelper/WinDebugger.cpp +++ b/IDEHelper/WinDebugger.cpp @@ -523,6 +523,7 @@ WinDebugger::WinDebugger(DebugManager* debugManager) : mDbgSymSrv(this) mDbgProcessHandle = 0; mDbgThreadHandle = 0; mDbgProcessId = 0; + mDbgHeapData = NULL; mIsPartialCallStack = true; for (int i = 0; i < 4; i++) @@ -1174,6 +1175,76 @@ void WinDebugger::InitiateHotResolve(DbgHotResolveFlags flags) delete hotScanner; } +intptr WinDebugger::GetDbgAllocHeapSize() +{ + if (mDbgHeapData == NULL) + { + Beefy::String memName = StrFormat("BFGC_stats_%d", mProcessInfo.dwProcessId); + + mDbgHeapData = new WinDbgHeapData(); + mDbgHeapData->mFileMapping = ::OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, memName.c_str()); + if (mDbgHeapData->mFileMapping == 0) + { + delete mDbgHeapData; + mDbgHeapData = NULL; + return 0; + } + + mDbgHeapData->mStats = (WinDbgHeapData::Stats*)MapViewOfFile(mDbgHeapData->mFileMapping, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(WinDbgHeapData::Stats)); + } + + if (mDbgHeapData->mStats == NULL) + return 0; + + return mDbgHeapData->mStats->mHeapSize; +} + +String WinDebugger::GetDbgAllocInfo() +{ + AutoCrit autoCrit(mDebugManager->mCritSect); + + for (auto threadInfo : mThreadList) + ::SuspendThread(threadInfo->mHThread); + + delete mHotResolveData; + mHotResolveData = NULL; + + mHotResolveData = new DbgHotResolveData(); + DbgHotScanner* hotScanner = new DbgHotScanner(this); + hotScanner->Scan(DbgHotResolveFlag_Allocations); + delete hotScanner; + + String result; + + if (mHotResolveData != NULL) + { + DbgExprEvaluator exprEvaluator(this, NULL, NULL, -1, -1); + exprEvaluator.mDebugTarget = mDebugTarget; + + String typeName; + + result += ":types\n"; + + for (int typeId = 0; typeId < mHotResolveData->mTypeData.size(); typeId++) + { + auto& typeData = mHotResolveData->mTypeData[typeId]; + if (typeData.mCount > 0) + { + auto type = exprEvaluator.GetBeefTypeById(typeId); + typeName.Clear(); + exprEvaluator.BeefTypeToString(type, typeName); + + result += StrFormat("type\t%d\t%s\t%lld\t%lld\n", typeId, typeName.c_str(), typeData.mCount, typeData.mSize); + } + } + } + + for (auto threadInfo : mThreadList) + ::ResumeThread(threadInfo->mHThread); + + return result; +} + bool WinDebugger::DoOpenFile(const StringImpl& fileName, const StringImpl& args, const StringImpl& workingDir, const Array& envBlock) { BP_ZONE("WinDebugger::DoOpenFile"); @@ -1398,6 +1469,8 @@ void WinDebugger::Detach() mDbgAttachFlags = BfDbgAttachFlag_None; mDbgProcessId = 0; + delete mDbgHeapData; + mDbgHeapData = NULL; mDbgProcessHandle = 0; ClearCallStack(); mWantsDebugContinue = false; @@ -10208,21 +10281,27 @@ String WinDebugger::GetProcessInfo() if ((mActiveThread == NULL) && (!mIsRunning)) return ""; + SYSTEM_INFO sysinfo = { 0 }; + GetSystemInfo(&sysinfo); + FILETIME creationTime = { 0 }; FILETIME exitTime = { 0 }; FILETIME kernelTime = { 0 }; FILETIME userTime = { 0 }; - GetProcessTimes(mProcessInfo.hProcess, &creationTime, &exitTime, &kernelTime, &userTime); + ::GetProcessTimes(mProcessInfo.hProcess, &creationTime, &exitTime, &kernelTime, &userTime); String retStr; PROCESS_MEMORY_COUNTERS memInfo = { 0 }; - GetProcessMemoryInfo(mProcessInfo.hProcess, &memInfo, sizeof(PROCESS_MEMORY_COUNTERS)); + ::GetProcessMemoryInfo(mProcessInfo.hProcess, &memInfo, sizeof(PROCESS_MEMORY_COUNTERS)); + FILETIME currentTime = { 0 }; + ::GetSystemTimeAsFileTime(¤tTime); + retStr += StrFormat("VirtualMemory\t%d\n", memInfo.PagefileUsage); retStr += StrFormat("WorkingMemory\t%d\n", memInfo.WorkingSetSize); - retStr += StrFormat("RunningTime\t%lld\n", *(int64*)&creationTime); - retStr += StrFormat("KernelTime\t%lld\n", *(int64*)&kernelTime); - retStr += StrFormat("UserTime\t%lld\n", *(int64*)&userTime); + retStr += StrFormat("RunningTime\t%lld\n", *(int64*)¤tTime - *(int64*)&creationTime); + retStr += StrFormat("KernelTime\t%lld\n", *(int64*)&kernelTime / sysinfo.dwNumberOfProcessors); + retStr += StrFormat("UserTime\t%lld\n", *(int64*)&userTime / sysinfo.dwNumberOfProcessors); return retStr; } diff --git a/IDEHelper/WinDebugger.h b/IDEHelper/WinDebugger.h index 77a3f364..4d287e9f 100644 --- a/IDEHelper/WinDebugger.h +++ b/IDEHelper/WinDebugger.h @@ -384,6 +384,31 @@ struct WinHotThreadState int mThreadId; }; +class WinDbgHeapData +{ +public: + struct Stats + { + intptr mHeapSize; + }; + +public: + HANDLE mFileMapping; + Stats* mStats; + + WinDbgHeapData() + { + mFileMapping = 0; + mStats = NULL; + } + + ~WinDbgHeapData() + { + if (mFileMapping != 0) + ::CloseHandle(mFileMapping); + } +}; + class WinDebugger : public Debugger { public: @@ -407,6 +432,7 @@ public: CPU* mCPU; PROCESS_INFORMATION mProcessInfo; BfDbgAttachFlags mDbgAttachFlags; + WinDbgHeapData* mDbgHeapData; DWORD mDbgProcessId; HANDLE mDbgProcessHandle; HANDLE mDbgThreadHandle; @@ -593,6 +619,8 @@ public: virtual void Run() override; virtual void HotLoad(const Array& objectFiles, int hotIdx) override; virtual void InitiateHotResolve(DbgHotResolveFlags flags) override; + virtual intptr GetDbgAllocHeapSize() override; + virtual String GetDbgAllocInfo() override; virtual void Update() override; virtual void ContinueDebugEvent() override; virtual void ForegroundTarget() override;