This is an archive of the discontinued LLVM Phabricator instance.

[libc++] Introduce __fits_in_sso()
ClosedPublic

Authored by philnik on Jan 1 2022, 4:52 PM.

Details

Reviewers
Quuxplusone
ldionne
Mordante
Group Reviewers
Restricted Project
Commits
rGfcfc0e7ad34c: [libc++] Introduce __fits_in_sso()
Summary

Introduce __fits_in_sso() to put the constexpr tests into a central place.

Diff Detail

Event Timeline

philnik requested review of this revision.Jan 1 2022, 4:52 PM
philnik created this revision.
Herald added a project: Restricted Project. · View Herald TranscriptJan 1 2022, 4:52 PM
Herald added a reviewer: Restricted Project. · View Herald Transcript
Quuxplusone added inline comments.Jan 1 2022, 5:39 PM
libcxx/include/string
1469

IIUC, I think this should be named __is_less_than_sso_capacity(x), or at least __fits_in_sso(x). __use_sso reads like an imperative instruction, but what this is is a query.

Also, the commit message (PR summary) should explain where we're going with this. Are we going to change the body of this function to

return (!__libcpp_is_constant_evaluated()) && (__sz < __min_cap);

? If so, then you should preemptively put this function's body on its own line; otherwise you're just making the next diff more complicated. We want the next diff to be as simple as

 _LIBCPP_HIDE_FROM_ABI
 static bool __use_sso(size_type __sz) {
-    return __sz < __min_cap;
+    return (!__libcpp_is_constant_evaluated()) && (__sz < __min_cap);
 }
2358–2360

Here and line 2606, I think your change is correct, but it feels really subtle and someone-not-me should look really closely and see if they agree this is right.
IIUC, __assign_short doesn't necessarily mean "assign into the SSO buffer"; it means merely "assign into the currently active buffer," i.e. "I guarantee __n is definitely less than our current capacity() (because our capacity is invariably greater than __min_cap) and so you don't have to codegen any code for growing the buffer." It's a codegen hack. If we're constant-evaluated, then we're not generating code and so we don't need the hack (and also there is no invariant regarding __min_cap). So the hack becomes

if (!__libcpp_is_constant_evaluated() && __builtin_constant_p(__n) && __n < __min_cap)

which is equivalent to what you wrote. But I'd like the next reviewer to also check and see if they agree.

philnik updated this revision to Diff 396919.Jan 2 2022, 4:09 AM
philnik marked an inline comment as done.
  • Renamed to __fits_in_sso
philnik retitled this revision from [libc++][NFC] Introduce __use_sso() to [libc++][NFC] Introduce __fits_in_sso().Jan 2 2022, 4:11 AM
philnik edited the summary of this revision. (Show Details)
Mordante added inline comments.Jan 2 2022, 8:49 AM
libcxx/include/string
1469

How about directly making the function constexpr and implement the final version? That avoids writing a commit message and modifying the code later.

philnik updated this revision to Diff 397030.Jan 3 2022, 3:01 AM
  • Implement final function
philnik marked an inline comment as done.Jan 3 2022, 3:02 AM
ldionne requested changes to this revision.EditedJan 3 2022, 1:03 PM

Are there any tests that need to be modified? I would assume that tests that check for reallocation when a string is assigned-to (or similar stuff) will need to be conditionalized on whether the test is running inside a constexpr context too, right?

Finally, and most importantly -- is this implementation strategy really valid? I had thought about this when I originally wrote the constexpr string paper, and IIRC one of the issues was this:

constexpr std::string s = "short"; // Anything short enough to normally use the SSO. Since this is inside constexpr, though, SSO isn't used.

void use(std::string const& str) {
  // ...
}

int main() { use(s); }

The problem here is that when s is constructed, it is a constexpr context so we don't use the SSO. When we actually *use* s, though, it's not a constexpr context so if we check s's size and ask whether it is using SSO, we'll believe that yes, we are using it (because it would normally fit in the small buffer).

Is this not an issue somehow? It's been a while since I've thought about this.

Edit: Well, in the current state of things, this can't be an issue because __fits_in_sso is only used from code paths that mutate the string, and those code paths can't be called at runtime on a constexpr-created std::string. So this at least does seem correct to me in its current state. However, if we ever queried __fits_in_sso for a read-only operation, it would have the possibility to lie if called on a std::string created with a different constexpr-ness than the context in which __fits_in_sso is called. I think this merits at least a comment.

libcxx/include/string
1470

Can you add a comment saying something like "We never use the SSO in constexpr contexts because it requires some amount of reinterpret-casting, which is not constexpr-friendly"?

Otherwise, one would rightly wonder why a function named __fits_in_sso would return something different based on whether it is called in a constexpr context or not.

This revision now requires changes to proceed.Jan 3 2022, 1:03 PM

Are there any tests that need to be modified? I would assume that tests that check for reallocation when a string is assigned-to (or similar stuff) will need to be conditionalized on whether the test is running inside a constexpr context too, right?

Finally, and most importantly -- is this implementation strategy really valid? I had thought about this when I originally wrote the constexpr string paper, and IIRC one of the issues was this:

constexpr std::string s = "short"; // Anything short enough to normally use the SSO. Since this is inside constexpr, though, SSO isn't used.

void use(std::string const* str) {
  // ...
}

int main() { use(s); }

The problem here is that when s is constructed, it is a constexpr context so we don't use the SSO. When we actually *use* s, though, it's not a constexpr context so if we check s's size and ask whether it is using SSO, we'll believe that yes, we are using it (because it would normally fit in the small buffer).

Is this not an issue somehow? It's been a while since I've thought about this.

You have to deallocate anything that you allocated during constant evaluation. So using a constexpr string in a non-constexpr context is illegal IIUC. https://godbolt.org/z/KKv3YnP4a

You have to deallocate anything that you allocated during constant evaluation. So using a constexpr string in a non-constexpr context is illegal IIUC. https://godbolt.org/z/KKv3YnP4a

Right, it looks like I got confused with promotion to static storage, which was pulled from the paper IIRC.

So I think this works, however if/when we allow something like constexpr std::string s = "hello"; at file scope (which entails that we'll do some sort of promotion of the constexpr-allocated memory to static storage), we'll have to tackle this problem cause the possibility of __fits_in_sso() lying is going to become real. For the time being, no problem though.

philnik updated this revision to Diff 397313.Jan 4 2022, 8:28 AM
philnik marked an inline comment as done.
  • Add comment to explain libcpp_is_constant_evaluated() in fits_in_sso()
ldionne accepted this revision.Jan 11 2022, 9:30 AM

This is not a NFC -- the commit shouldn't say NFC.

libcxx/include/string
2358–2360

That sounds right to me.

This revision is now accepted and ready to land.Jan 11 2022, 9:30 AM
philnik retitled this revision from [libc++][NFC] Introduce __fits_in_sso() to [libc++] Introduce __fits_in_sso().Jan 11 2022, 2:05 PM
philnik edited the summary of this revision. (Show Details)Jan 11 2022, 2:16 PM
This revision was automatically updated to reflect the committed changes.