[RFC] Use `sysret` wrapper on linux

According to nolibc, __sysret is used to handle most cases when returning from kernel and setting errno. __sysret identifier - Linux source code (v6.8-rc1) - Bootlin.

__sysret is defined as

/* Syscall return helper: takes the syscall value in argument and checks for an
 * error in it. This may only be used with signed returns (int or long), but
 * not with pointers. An error is any value < 0. When an error is encountered,
 * -ret is set into errno and -1 is returned. Otherwise the returned value is
 * passed as-is with its type preserved.
 */

#define __sysret(arg)							\
({									\
	__typeof__(arg) __sysret_arg = (arg);				\
	(__sysret_arg < 0)                              /* error ? */	\
		? (({ SET_ERRNO(-__sysret_arg); }), -1) /* ret -1 with errno = -arg */ \
		: __sysret_arg;                         /* return original value */ \
})

I suppose we can add a similar implementation:

//===--- Linux Syscall Errno Wrapper ----------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_SYSRET_H
#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_SYSRET_H
#include "src/__support/CPP/optional.h"
#include "src/__support/OSUtil/linux/syscall.h"
#include "src/errno/libc_errno.h"
namespace LIBC_NAMESPACE {
template <typename R, typename... Ts>
LIBC_INLINE cpp::optional<R> sysret(long number, Ts... ts) {
  long ret = syscall_impl<long>(number, ts...);
  if (ret < 0) {
    libc_errno = static_cast<int>(-ret);
    return cpp::nullopt;
  }
  return {cpp::bit_or_static_cast<R>(ret)};
}
template <typename R = int, R DEFAULT = R(-1), typename... Ts>
LIBC_INLINE R sysret_default(long number, Ts... ts) {
  auto ret = sysret<R>(number, ts...);
  return ret ? *ret : DEFAULT;
}
} // namespace LIBC_NAMESPACE
#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_SYSRET_H

In this way, we can rewrite many syscall wrappers. For example,

namespace LIBC_NAMESPACE {

LLVM_LIBC_FUNCTION(int, creat, (const char *path, int mode_flags)) {
#ifdef SYS_open
  int fd = LIBC_NAMESPACE::syscall_impl<int>(
      SYS_open, path, O_CREAT | O_WRONLY | O_TRUNC, mode_flags);
#else
  int fd = LIBC_NAMESPACE::syscall_impl<int>(
      SYS_openat, AT_FDCWD, path, O_CREAT | O_WRONLY | O_TRUNC, mode_flags);
#endif

  if (fd > 0)
    return fd;

  libc_errno = -fd;
  return -1;
}

} // namespace LIBC_NAMESPACE

can be shortened into

namespace LIBC_NAMESPACE {

LLVM_LIBC_FUNCTION(int, creat, (const char *path, int mode_flags)) {
#ifdef SYS_open
  return sysret_default(SYS_open, path, O_CREAT | O_WRONLY | O_TRUNC,
                        mode_flags);
#else
  return sysret_default(SYS_openat, AT_FDCWD, path,
                        O_CREAT | O_WRONLY | O_TRUNC, mode_flags);
#endif
}

} // namespace LIBC_NAMESPACE

This does seem like a good idea overall, but there are a couple changes I would make to better fit our overall design.

In internal functions, we ideally avoid setting errno so that function calls don’t have unexpected side effects, so instead of returning an optional I’d recommend something similar to StrToNumResult: A return struct that contains a T value and an int error.

Making that change may seem to make this change less important, but it actually would still be very useful since in future we will need to add support for pthread_cancel to many of these functions and having a base to make sysret_cancellable from will make that much easier.

Another minor thing that is probably helpful is that we can annotate the successful path with [[likely]], which may be useful once we have LTO support.