-
Notifications
You must be signed in to change notification settings - Fork 8.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use 32-bit coordinates throughout the project #13025
Conversation
oh my god I love this image |
680f4e0
to
b70ef3d
Compare
2e5808f
to
97c4048
Compare
ba60e7b
to
34fc317
Compare
ca6498f
to
6d2f72c
Compare
Codeflow link:
|
@@ -13,7 +13,6 @@ | |||
// - ulSize - The height of the cursor within this buffer | |||
Cursor::Cursor(const ULONG ulSize, TextBuffer& parentBuffer) noexcept : | |||
_parentBuffer{ parentBuffer }, | |||
_cPosition{ 0 }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we sure this gets 0 initialized always? I know we used to have some weird OS-side bugs where memory wouldn't get 0 initialized. (apologies for needing a refresher on basic language concepts)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, with _cPosition
a til::point
now, it doesn't accept a single constructor parameter anymore - it needs either 0 (_cPosition{}
) or 2 (_cPosition{ 0, 0 }
). But if you write the former, you can drop it from the constructor list entirely, since it's already constructed like that by default anyways.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To expand on that:
The old COORD _cPosition
needed a manual initialization unlike til::point
, because that type has no custom constructor which initializes X
/Y
to 0, nor does it use C++11's default member initializers (short X = 0;
) like til::point
does, so the default value for its members is "uninitialized" (which is UB).
The reason the existing code said _cPosition{ 0 }
and not _cPosition{}
is probably a carry-over from the C code, because C structs don't have default constructors - _cPosition{}
is not valid C.
In C, if you pass less arguments than the struct has members, then the remaining fields will be set to 0. RECT{ 0 }
is equivalent to RECT{ 0, 0, 0, 0 }
and RECT{ 1 }
is equivalent to RECT{ 1, 0, 0, 0 }
. In C++, the compiler-generated constructor acts equivalent to C's with the addition of the default constructor {}
which acts equivalent to { 0 }
.
Since til::point
has a custom constructor it gets no compiler-generated one and thus it can't be used with C-style constructor arguments anymore ({ 0 }
).
@@ -33,17 +33,17 @@ namespace std | |||
// - coord - the coord to hash | |||
// Return Value: | |||
// - the hashed coord | |||
constexpr size_t operator()(const COORD& coord) const noexcept | |||
constexpr size_t operator()(const til::point coord) const noexcept |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. Technically it's beneficial to pass primitive structs like that by value at all times, because x64 calling conventions / ABIs pass them via registers. Any x64 ABI will pass the 8 byte large til::point
within a single register (RCX on Windows, RDI everywhere else) which means that it's not just super fast, but also that no stack needs to be allocated, etc. This might not matter for functions that are only called from time to time, but some functions we got are called thousands of times per second. This is why I pass til::point
and til::size
by value throughout the project - it's just consistently very very performant.
Now the problem is that the Windows x64 ABI has a very unfortunate, very important flaw as compared to the SYSV ABI used by every single other x64 OS in the world and it makes me very very sad every single time I'm reminded of this. 🥲
...It can't pass structs larger than 8 byte via registers. With the SYSV ABI a 16 byte struct simply gets passed in the first two 8 byte large registers (RDI/RSI), but on Windows such arguments are passed as "hidden references/pointers" - the argument is still passed via a register (RCX), but the register now contains a pointer to the struct on the caller's stack. The callee will then copy it on their stack to create a by-value copy (via SSE's movups
/movaps
). Sometimes the optimizer can optimize the copy away, but usually it'll fail at doing so. The difference is a 4-5 fold increase in runtime cost for function calls, just by merely having a single by-value 16-byte struct as an argument.
This is why I pass til::rect
and til::inclusive_rect
consistently by-reference, because it prevents the VS 2022 (v143) compiler from creating such redundant, very costly stack copies on the callee side.
(I've been cooperating with DevDiv/Intel on optimizing these redundant stack copies away in the most common cases and it should hopefully land in a future VS 2022 update soonish. It reduces the overhead from 4-5x down to around 2x which is then about as good as passing it by-reference.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow that's incredibly thorough. We should have this saved somewhere (niksa.md maybe) as a reference that til::point
&size
should always be by value and rect
always by ref.
Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, please do write this up as an addendum to Niksa.md
Log::Comment(NoThrowString().Format(L"Original buffer size: %dx%d", oldSize.width, oldSize.height)); | ||
Log::Comment(NoThrowString().Format(L"Original viewport: %dx%d", oldView.width, oldView.height)); | ||
Log::Comment(NoThrowString().Format(L"Original buffer size: %dx%d", oldSize.X, oldSize.Y)); | ||
Log::Comment(NoThrowString().Format(L"Original viewport: %dx%d", oldView.X, oldView.Y)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this one seems wrong, yea?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If no other issues turn up I might just leave it as it is, because I'm going to fix all those members in a follow-up anyways.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
96/234
@@ -983,7 +983,7 @@ CATCH_RETURN(); | |||
// - the text that the UiaTextRange encompasses | |||
#pragma warning(push) | |||
#pragma warning(disable : 26447) // compiler isn't filtering throws inside the try/catch | |||
std::wstring UiaTextRangeBase::_getTextValue(std::optional<unsigned int> maxLength) const | |||
std::wstring UiaTextRangeBase::_getTextValue(til::CoordType maxLength) const |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if I could admit: i think optional
captures the essence of this API better than "magic -1
" does
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand where you're coming from, but this method exists only to implement ITextRangeProvider::GetText
which says:
The maximum length of the string to return. Use -1 if no limit is required.
I feel like this is a direct translation of this API. Additionally I personally think that negative lengths are very common in C code (especially if cast to unsigned integers).
I chose this so that it generates the same |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
139/237! less than a hundo to go
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
154
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ALRIGHT! Get them concerns addressed and we are a GO!
@@ -311,7 +311,7 @@ class CommandListPopupTests | |||
|
|||
VERIFY_ARE_EQUAL(popup.Process(cookedReadData), static_cast<NTSTATUS>(CONSOLE_STATUS_WAIT_NO_BLOCK)); | |||
// selection should have moved up a page | |||
VERIFY_ARE_EQUAL(static_cast<short>(m_pHistory->GetNumberOfCommands()) - popup.Height() - 1, popup._currentCommand); | |||
VERIFY_ARE_EQUAL(static_cast<til::CoordType>(m_pHistory->GetNumberOfCommands()) - popup.Height() - 1, popup._currentCommand); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this isn't exactly a coordinate either...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The popup.Height()
is in character cells and so I made it return a til::CoordType
.
This change makes it so that the compiler doesn't complain that we should promote integers first before we add/subtract/multiply/divide them.
@@ -1293,7 +1293,7 @@ void ScreenBufferTests::VtResizeDECCOLM() | |||
newViewWidth = si.GetViewport().Width(); | |||
|
|||
VERIFY_IS_FALSE(areMarginsSet()); | |||
VERIFY_ARE_EQUAL(COORD({ 0, 0 }), getRelativeCursorPosition()); | |||
VERIFY_ARE_EQUAL(til::point(0, 0), getRelativeCursorPosition()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious, using the constructor via function call syntax. You replaced similar thing with point{}
; should we here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The VERIFY_ARE_EQUAL
macro blows up if you use curly brackets inside it. Not kidding. IIRC the ({ 0, 0 })
doesn't work because the constructor is explicit and doesn't take a single parameter list argument (unlike structs).
|
||
return s; | ||
} | ||
|
||
void FillBothCoordsSameRandom(COORD* pcoordA, COORD* pcoordB) | ||
void FillBothCoordsSameRandom(til::point* pcoordA, til::point* pcoordB) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like a very silly way to do a = rand(); b = a;
@@ -1262,14 +1237,14 @@ void Renderer::_PaintSelection(_In_ IRenderEngine* const pEngine) | |||
// - Helper to determine the selected region of the buffer. | |||
// Return Value: | |||
// - A vector of rectangles representing the regions to select, line by line. | |||
std::vector<SMALL_RECT> Renderer::_GetSelectionRects() const | |||
std::vector<til::rect> Renderer::_GetSelectionRects() const |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Double check this code. I saw that INSIDE the renderer they're exclusive now, but via RenderData and OUTSIDE the renderer they're inclusive. That's fine, but look at line R1284 -- we bump up the inclusive SR to be exclusive, and then we construct a til::rect
out of it making it exclusive again...
@@ -329,7 +329,7 @@ std::wstring_view Terminal::GetWorkingDirectory() | |||
oldRows.mutableViewportTop = oldViewportTop; | |||
oldRows.visibleViewportTop = newVisibleTop; | |||
|
|||
const std::optional<short> oldViewStart{ oldViewportTop }; | |||
const std::optional oldViewStart{ oldViewportTop }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I, too, am surprised!
Oh no!
|
Hello @DHowett! Because this pull request has the p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (
|
This is a follow-up of #13025 to make the members of `til::point/size/rect` uniform and consistent without the use of `unions`. The only file that has any changes is `src/host/getset.cpp` where an if condition was simplified. ## Validation Steps Performed * Host unit tests ✅ * Host feature tests ✅ * ControlCore feature tests ✅
Previously this project used a great variety of types to present text buffer
coordinates:
short
,unsigned short
,int
,unsigned int
,size_t
,ptrdiff_t
,COORD
/SMALL_RECT
(akashort
), and more.This massive commit migrates almost all use of those types over to the
centralized types
til::point
/size
/rect
/inclusive_rect
and theirunderlying type
til::CoordType
(akaint32_t
).Due to the size of the changeset and statistics I expect it to contain bugs.
The biggest risk I see is that some code potentially, maybe implicitly, expected
arithmetic to be mod 2^16 and that this code now allows it to be mod 2^32.
Any narrowing into
short
later on would then throw exceptions.PR Checklist
Validation Steps Performed
Casual usage of OpenConsole and Windows Terminal. ✅