This is an archive of the discontinued LLVM Phabricator instance.

[libc] move errno out of file internals
ClosedPublic

Authored by michaelrj on Dec 7 2022, 1:28 PM.

Details

Summary

Now errno is only set by the terminal entrypoints, and not the internal
implementations. This patch is part of the larger effort to not set
errno in libc internal code: https://github.com/llvm/llvm-project/issues/59278

Diff Detail

Event Timeline

michaelrj created this revision.Dec 7 2022, 1:28 PM
Herald added projects: Restricted Project, Restricted Project. · View Herald TranscriptDec 7 2022, 1:28 PM
michaelrj requested review of this revision.Dec 7 2022, 1:28 PM
lntue added inline comments.Dec 7 2022, 4:54 PM
libc/src/__support/File/file.h
33

Is this used in the test somewhere? So far all the tests I've seen are directly tested with get_value()?

sivachandra added inline comments.Dec 7 2022, 11:54 PM
libc/src/__support/File/file.h
51

For read and write operations, I think is fine with a slight modification. Instead of calling the result type FileResult and making it a template, just define it this way:

struct FileIOResult {
  size_t size;
  int error;

  constexpr FileResult(size_t s) : size(s), error(0) {}
  constexpr FileResult(size_t s, int err) : size(s), error(err) {}

  constexpr operator size_t() const { return size; }
  constexpr operator bool() const { return error != 0; }
};

The call sites will be like:

const auto result = file.write(...)
if (!result)
  errno = result.error;
return result; // OK because of operator size_t
54

This is an example of an ErrorOr or StatusOr return value.

56

The close and flush operation functions can return the error value directly. Call sites will detect an error if they see a non-zero return value.

sivachandra added inline comments.Dec 8 2022, 11:44 PM
libc/src/__support/File/file.h
54

Actually, we should be use an equivalent of std::expected: https://en.cppreference.com/w/cpp/utility/expected

You can find an implementation effort here:
https://reviews.llvm.org/D124516

michaelrj updated this revision to Diff 481745.Dec 9 2022, 2:20 PM
michaelrj marked an inline comment as done.

move to ErrorOr for the functions that effectively return a boolean. Also build a generic "expected" class.

michaelrj marked 4 inline comments as done.Dec 9 2022, 2:45 PM
michaelrj added inline comments.
libc/src/__support/File/file.h
56

close and flush call write, meaning that they can theoretically have any error that write can, not just EBADF.

sivachandra added inline comments.Dec 9 2022, 3:51 PM
libc/src/__support/CPP/expected.h
32 ↗(On Diff #481745)

Does std::expected have constructors of this kind? If no, then we should not add incompatible constructors.

35 ↗(On Diff #481745)

Likewise, if has_error is not present, do not include it here.

libc/src/__support/File/file.cpp
43–44

Do you need a var like buf_result ? Could we just do:

auto bytes_written = platform_write(...);
if (bytes_written < write_size) { // This should OK because of operator size_t ?
  ... 
}
54–55

Same here?

libc/src/__support/File/file.h
19

Is it used with T != size_t anywhere? If not, it need not be a template.

56

EBADF or any error that write returns is still of an integer type. So, call sites can detect error if return value is non-zero. That non-zero value is itself the error. Otherwise, what meaning would the int value in ErrorOr have for flush and close?

michaelrj updated this revision to Diff 481776.Dec 9 2022, 4:12 PM
michaelrj marked 5 inline comments as done.

address comments

libc/src/__support/CPP/expected.h
32 ↗(On Diff #481745)

Yes, this is how the std::expected constructors are defined.

libc/src/__support/File/file.cpp
43–44

yes. If you look below, we extract the error from buf_result if we're returning an error.

54–55

Same as above, by returning result we preserve the error information. written is only used to determine if the error flag should be set.

libc/src/__support/File/file.h
56

while theoretically we could say that the int result for flush and close actually represents the error instead of the usual return value (which is effectively a boolean for success) I feel like that would be confusing for someone trying to read the codebase.

If you're used to a file function named flush that returns 0 on success and -1 on failure (as laid out in the standard), then the internal version of the function actually returning 0 on success and any positive integer on failure could be unexpected. This way it's unambiguous what the error state is.

sivachandra added inline comments.Dec 9 2022, 4:44 PM
libc/src/__support/CPP/expected.h
32 ↗(On Diff #481745)

Can you point me to a link or section in the standard where its listed? I am looking at cppreference.com and I don't see an unexpected type. I see one which takes std::unexpect_t followed by args for in-place construction of the error value.

libc/src/__support/File/file.cpp
43–44

You can still do return {0, bytes_written.error}, no?

54–55

Over here, you shouldn't need the written var because you can still compare result < len? I am not advocating for keeping the names as is though. For example, here and above, you can name the var capturing the return value from platform_write as write_result.

libc/src/__support/File/file.h
56

I would actually say using ErrorOr is confusing because one will then start looking for the meaning of the non-error int value.

I am not sure the "used to a file function named flush that return 0 ..." is an argument to use - when you return an error ErrorOr value there is no 0 or -1. But, if you actually return int value, then you can still do:

if (flush_result != 0) {
  // Error handling
}
michaelrj marked 5 inline comments as done.

address comments:

Moved flush and close to returning an int.
Avoided using additional variables where it was unnecessary.

libc/src/__support/CPP/expected.h
32 ↗(On Diff #481745)

here is std::unexpected: https://en.cppreference.com/w/cpp/error/unexpected
It's used in constructors 7 and 8 in the list on this page: https://en.cppreference.com/w/cpp/utility/expected/expected

sivachandra accepted this revision.Dec 12 2022, 12:06 PM

Minor comments inline, but LGTM.

libc/src/__support/CPP/expected.h
32 ↗(On Diff #481745)

here is std::unexpected: https://en.cppreference.com/w/cpp/error/unexpected

Ah! That stale cppreference page threw me off! That page is actually talking about a removed function std::unexpected and not a type named std::unexpected. But, your interpretation is correct and according to the C++23 draft: https://eel.is/c++draft/expected.unexpected

It's used in constructors 7 and 8 in the list on this page: https://en.cppreference.com/w/cpp/utility/expected/expected

Thanks!

17 ↗(On Diff #482184)

I think, per https://eel.is/c++draft/expected.unexpected, this should be private?

19 ↗(On Diff #482184)

explicit ? Without explicit, the constructor on line 30 below can become ambiguous if T and E are same.

libc/src/__support/File/file.h
19

I have read this patch on three different days and all those three times the name FileResult has made me a bit uncomfortable. Can we give it a less general name, say FileIOResult or something which makes it clear that this only relates to read/write operations?

This revision is now accepted and ready to land.Dec 12 2022, 12:06 PM
michaelrj marked 4 inline comments as done.

address comments:

Rename FileResult to FileIOResult
Make cpp::unexpected constructor explicit to avoid implicit conversions
Make cpp::unexpected member private and accessed through a getter

sivachandra accepted this revision.Dec 12 2022, 1:05 PM
sivachandra added inline comments.
libc/src/__support/CPP/expected.h
21 ↗(On Diff #482238)

The getter should be named error according to this: https://en.cppreference.com/w/cpp/utility/expected/unexpected

michaelrj updated this revision to Diff 482241.Dec 12 2022, 1:09 PM
michaelrj marked an inline comment as done.

fix final comment before landing

This revision was landed with ongoing or failed builds.Dec 12 2022, 1:11 PM
This revision was automatically updated to reflect the committed changes.