liblinux++

A hosted C++ runtime without any libc.
git clone git://henryandlizzy.uk/liblinux++
Log | Files | Refs

commit 5108dd9f1b9b187376b3744e72cc729a18c59936
parent 83d715acb180f4fd7189cc8de00a8889a36abe6d
Author: Henry Wilson <henry@henryandlizzy.uk>
Date:   Thu, 12 Mar 2026 15:55:32 +0000

Squash cpponline branch (only code, no slides/docs)

from: 3df49732f2bb3a4a32211ad9d7d6af585f682ed8

Diffstat:
MTupfile | 2--
Mcat.cpp | 2+-
Merrno.sh | 4++++
Mexec.cpp | 1-
Mgen.sh | 1+
Ahello.cpp | 24++++++++++++++++++++++++
Mlinux.hpp | 64+++++++++++++++++++++++++++++++++-------------------------------
Mpaste.cpp | 2+-
Msyscalls.h | 32++++++++++++++++----------------
Mtac.cpp | 4++--
Mtcp-listen.cpp | 4++--
Mtee.cpp | 2+-
Munix-listen.cpp | 4++--
Mvector.hpp | 27++++++++++++++++++++++++++-
Mwriter.cpp | 9+++++----
Mx86_64.S | 2--
16 files changed, 118 insertions(+), 66 deletions(-)

diff --git a/Tupfile b/Tupfile @@ -21,6 +21,4 @@ LDFLAGS = --gc-sections run ./gen.sh -: bin/writer.aarch64 |> %f |> - .gitignore diff --git a/cat.cpp b/cat.cpp @@ -1,6 +1,6 @@ #include "linux.hpp" -static void cat(file fd) +static void cat(fd fd) { static char buf[0x1000]; diff --git a/errno.sh b/errno.sh @@ -4,3 +4,7 @@ set -eu echo -e '#pragma once\n\nenum class errno_t : int16_t {' sed -En 's/#[[:space:]]*define[[:space:]]+E([[:alnum:]]+)[[:space:]]+([[:digit:]]+)/E\1 = -\2,/p' /usr/include/asm-generic/errno{,-base}.h echo '};' + +echo -e 'inline span<char const> errname(errno_t e) { switch (e) {' +sed -En 's/#[[:space:]]*define[[:space:]]+E([[:alnum:]]+)[[:space:]]+([[:digit:]]+)/case errno_t::E\1: return "E\1"_sp;/p' /usr/include/asm-generic/errno{,-base}.h +echo '} return "E?unknown?"_sp; }' diff --git a/exec.cpp b/exec.cpp @@ -1,5 +1,4 @@ #include "linux.hpp" -#include "errno.hpp" int main(int argc, char* argv[], char* envp[]) { diff --git a/gen.sh b/gen.sh @@ -3,3 +3,4 @@ ARCH=`uname -m` echo ": foreach {bin-$ARCH} |> ln -s %f %o |> %B" +echo ": bin/writer.$ARCH |> %f |>" diff --git a/hello.cpp b/hello.cpp @@ -0,0 +1,24 @@ +#include "linux.hpp" + +int main() +{ + auto msg = "Hello, World!\n"_sp; + auto res = write(stdout, msg); + + if (not res) + { + span<char const> const iov[] = { + "write() failed: "_sp, + errname(res.err()), + "\n"_sp + }; + (void)write(stderr, span{iov}); + return 1; + } + + if (*res != msg.size()) + { + (void)write(stderr, "Wrote less than expected\n"_sp); + return 1; + } +} diff --git a/linux.hpp b/linux.hpp @@ -23,6 +23,7 @@ enum { O_WRONLY = 1, O_RDWR = 2, O_CREAT = 0100, + O_PATH = 010000000, PROT_READ = 0x1, PROT_WRITE = 0x2, @@ -48,7 +49,7 @@ enum { template <typename T> struct span { - span() = default; + constexpr span() = default; constexpr span(T* p, size_t n) noexcept : ptr{p} , len{n} @@ -103,20 +104,21 @@ constexpr span<char const> operator ""_sp(char const* begin, size_t size) noexce return {begin, size}; } -struct file +#include "errno.hpp" + +struct fd { - constexpr file() = default; - constexpr explicit file(int n) noexcept : num{n} {} + constexpr fd() = default; + constexpr explicit fd(int n) noexcept : num{n} {} constexpr operator bool() const noexcept { return num >= 0; } +private: int num = -1; }; -constexpr file const AT_FDCWD{-100}; -constexpr file const stdin{0}; -constexpr file const stdout{1}; -constexpr file const stderr{2}; - -enum class errno_t : int16_t; +constexpr fd const AT_FDCWD{-100}; +constexpr fd const stdin{0}; +constexpr fd const stdout{1}; +constexpr fd const stderr{2}; template <typename T> struct [[nodiscard]] syscall_result @@ -173,24 +175,24 @@ private: [[noreturn]] extern void exit(int error_code) noexcept; -extern syscall_result<size_t> read(file fd, char data[], size_t count) noexcept; -extern syscall_result<size_t> read(file fd, span<char> data) noexcept; -inline syscall_result<size_t> read(file fd, char* begin, char* end) noexcept +extern syscall_result<size_t> read(fd src, char data[], size_t count) noexcept; +extern syscall_result<size_t> read(fd src, span<char> data) noexcept; +inline syscall_result<size_t> read(fd src, char* begin, char* end) noexcept { - return read(fd, begin, end - begin); + return read(src, begin, end - begin); } -extern syscall_result<size_t> write(file fd, char const data[], size_t count) noexcept; -extern syscall_result<size_t> write(file fd, span<char const> data) noexcept; -extern syscall_result<size_t> write(file fd, span<span<char const> const> data) noexcept; -inline syscall_result<size_t> write(file fd, char const* begin, char const* end) noexcept +extern syscall_result<size_t> write(fd dst, char const data[], size_t count) noexcept; +extern syscall_result<size_t> write(fd dst, span<char const> data) noexcept; +extern syscall_result<size_t> write(fd dst, span<span<char const> const> data) noexcept; +inline syscall_result<size_t> write(fd dst, char const* begin, char const* end) noexcept { - return write(fd, begin, end - begin); + return write(dst, begin, end - begin); } -extern syscall_result<file> openat(file fd, c_str name, int flags, int mode) noexcept; +extern syscall_result<fd> openat(fd dir, c_str name, int flags, int mode) noexcept; -extern syscall_result<void> close(file fd) noexcept; +extern syscall_result<void> close(fd) noexcept; struct [[gnu::packed]] linux_dirent64 { ino64_t d_ino; /* 64-bit inode number */ @@ -201,9 +203,9 @@ struct [[gnu::packed]] linux_dirent64 { linux_dirent64* next() noexcept { return reinterpret_cast<linux_dirent64*>(reinterpret_cast<char*>(this) + d_reclen); } char* name() noexcept { return reinterpret_cast<char*>(this) + sizeof(*this); } }; -extern syscall_result<size_t> getdents64(file dir, span<char> buffer) noexcept; +extern syscall_result<size_t> getdents64(fd dir, span<char> buffer) noexcept; -extern syscall_result<void*> mmap(void* addr, size_t len, unsigned long prot, unsigned long flags, file fd, size_t off) noexcept; +extern syscall_result<void*> mmap(void* addr, size_t len, unsigned long prot, unsigned long flags, fd file, size_t off) noexcept; extern syscall_result<void> munmap(void* addr, size_t len) noexcept; struct timespec { @@ -220,18 +222,18 @@ enum class rename_flags : unsigned int WHITEOUT = 0b100, }; -extern syscall_result<void> renameat2(file olddir, c_str oldpath, file newdir, c_str newpath, rename_flags flags) noexcept; +extern syscall_result<void> renameat2(fd olddir, c_str oldpath, fd newdir, c_str newpath, rename_flags flags) noexcept; enum class unlink_flags : int { REMOVEDIR = 0x200, }; -extern syscall_result<void> unlinkat(file dir, c_str path, unlink_flags flags) noexcept; +extern syscall_result<void> unlinkat(fd dir, c_str path, unlink_flags flags) noexcept; extern syscall_result<void> execve(c_str path, char *const /*_Nullable*/ argv[], char *const /*_Nullable*/ envp[]) noexcept; -extern syscall_result<file> socket(int domain, int type, int protocol) noexcept; +extern syscall_result<fd> socket(int domain, int type, int protocol) noexcept; struct addr_ref { @@ -276,8 +278,8 @@ private: [[maybe_unused]] uint8_t data[14]; }; -extern syscall_result<void> bind(file socket, addr_ref addr) noexcept; -extern syscall_result<void> listen(file socket, int backlog) noexcept; -extern syscall_result<file> accept(file socket, void* addr_buf, socklen_t* addr_len) noexcept; -syscall_result<void> setsockopt(file socket, int level, int option_name, span<char const> value); -extern syscall_result<file> dup(file oldfd, file newfd, int flags = 0) noexcept; +extern syscall_result<void> bind(fd socket, addr_ref addr) noexcept; +extern syscall_result<void> listen(fd socket, int backlog) noexcept; +extern syscall_result<fd> accept(fd socket, void* addr_buf, socklen_t* addr_len) noexcept; +syscall_result<void> setsockopt(fd socket, int level, int option_name, span<char const> value); +extern syscall_result<fd> dup(fd oldfd, fd newfd, int flags = 0) noexcept; diff --git a/paste.cpp b/paste.cpp @@ -2,7 +2,7 @@ struct input { - file fd; + fd fd; unsigned short start, end; char buf[0x1000 - 8]; }; diff --git a/syscalls.h b/syscalls.h @@ -1,31 +1,31 @@ extern unlinkat -extern_alias _Z8unlinkat4file5c_str12unlink_flags +extern_alias _Z8unlinkat2fd5c_str12unlink_flags SYS(unlinkat) extern getdents64 -extern_alias _Z10getdents644file4spanIcE +extern_alias _Z10getdents642fd4spanIcE SYS(getdents64) extern read -extern_alias _Z4read4filePcm -extern_alias _Z4read4file4spanIcE +extern_alias _Z4read2fdPcm +extern_alias _Z4read2fd4spanIcE SYS(read) extern write -extern_alias _Z5write4filePKcm -extern_alias _Z5write4file4spanIKcE +extern_alias _Z5write2fdPKcm +extern_alias _Z5write2fd4spanIKcE SYS(write) extern openat -extern_alias _Z6openat4file5c_strii +extern_alias _Z6openat2fd5c_strii SYS(openat) extern writev -extern_alias _Z5write4file4spanIKS0_IKcEE +extern_alias _Z5write2fd4spanIKS0_IKcEE SYS(writev) extern close -extern_alias _Z5close4file +extern_alias _Z5close2fd SYS(close) extern execve @@ -45,11 +45,11 @@ extern_alias _Z6munmapPvm SYS(munmap) extern mmap -extern_alias _Z4mmapPvmmm4filem +extern_alias _Z4mmapPvmmm2fdm SYS(mmap) extern renameat2 -extern_alias _Z9renameat24file5c_strS_S0_12rename_flags +extern_alias _Z9renameat22fd5c_strS_S0_12rename_flags SYS(renameat2) extern socket @@ -57,21 +57,21 @@ extern_alias _Z6socketiii SYS(socket) extern bind -extern_alias _Z4bind4file8addr_ref +extern_alias _Z4bind2fd8addr_ref SYS(bind) extern listen -extern_alias _Z6listen4filei +extern_alias _Z6listen2fdi SYS(listen) extern accept -extern_alias _Z6accept4filePvPj +extern_alias _Z6accept2fdPvPj SYS(accept) extern dup3 -extern_alias _Z3dup4fileS_i +extern_alias _Z3dup2fdS_i SYS(dup3) extern setsockopt -extern_alias _Z10setsockopt4fileii4spanIKcE +extern_alias _Z10setsockopt2fdii4spanIKcE SYS(setsockopt) diff --git a/tac.cpp b/tac.cpp @@ -2,7 +2,7 @@ extern char mappable[]; -static int rd(file const fd, char* const start, char* const end) +static int rd(fd const fd, char* const start, char* const end) { char* pos = start; while (pos != end) @@ -18,7 +18,7 @@ static int rd(file const fd, char* const start, char* const end) return pos - start; } -static void wr(file const fd, char const* start, char const* const end) +static void wr(fd const fd, char const* start, char const* const end) { while (start != end) { diff --git a/tcp-listen.cpp b/tcp-listen.cpp @@ -82,12 +82,12 @@ int main(int argc, char* argv[], char* envp[]) if (port > 65535) return 3; - file sock = *socket(2/*AF_INET*/, 1/*SOCK_STREAM*/, 0/*IPPROTO_IP*/); + fd sock = *socket(2/*AF_INET*/, 1/*SOCK_STREAM*/, 0/*IPPROTO_IP*/); in_addr addr{static_cast<uint16_t>(port), 0/*IN_ADDR_ANY*/}; *bind(sock, addr); *listen(sock, 1); socklen_t addr_len = sizeof(addr); - file conn = *accept(sock, &addr, &addr_len); + fd conn = *accept(sock, &addr, &addr_len); *dup(conn, stdin); *dup(conn, stdout); *close(conn); diff --git a/tee.cpp b/tee.cpp @@ -3,7 +3,7 @@ int main(int argc, char* argv[]) { - vector<file> files; + vector<fd> files; for (int i = 1; i < argc; ++i) files.push_back(*openat(AT_FDCWD, argv[i], O_WRONLY | O_CREAT, 0666)); diff --git a/unix-listen.cpp b/unix-listen.cpp @@ -7,12 +7,12 @@ int main(int argc, char* argv[], char* envp[]) (void)unlinkat(AT_FDCWD, argv[1], {}); - file sock = *socket(1/*AF_UNIX*/, 1/*SOCK_STREAM*/, 0); + fd sock = *socket(1/*AF_UNIX*/, 1/*SOCK_STREAM*/, 0); un_addr addr{span<char const>{argv[1], c_str{argv[1]}.length()}}; *bind(sock, addr); *listen(sock, 1); socklen_t addr_len = sizeof(addr); - file conn = *accept(sock, &addr, &addr_len); + fd conn = *accept(sock, &addr, &addr_len); *dup(conn, stdin); *dup(conn, stdout); *close(conn); diff --git a/vector.hpp b/vector.hpp @@ -6,12 +6,37 @@ inline void* operator new(size_t, void* p) noexcept return p; } +struct false_type { static constexpr bool value = false; }; +struct true_type { static constexpr bool value = true; }; + +template<typename T, typename U> struct is_same : false_type {}; +template<typename T> struct is_same<T, T> : true_type {}; +template<typename T, typename U> constexpr bool is_same_v = is_same<T, U>::value; + +template <typename T> +consteval unsigned bits() +{ + if constexpr (is_same_v<T, uint64_t>) + return 64; + if constexpr (is_same_v<T, int64_t>) + return 64; + if constexpr (is_same_v<T, uint32_t>) + return 32; + if constexpr (is_same_v<T, int32_t>) + return 32; +} + +static_assert(bits<int64_t>() == 64); +static_assert(bits<uint64_t>() == 64); +static_assert(bits<int32_t>() == 32); +static_assert(bits<uint32_t>() == 32); + constexpr size_t next_power_of_two(size_t n) { if (not n) return n; --n; - for (unsigned i = 1; i <= 32; i <<= 1) + for (unsigned i = 1; i <= bits<size_t>(); i <<= 1) n |= n >> i; return ++n; } diff --git a/writer.cpp b/writer.cpp @@ -13,14 +13,14 @@ protected: struct fd_writer : writer { - fd_writer(file f) : out{f} {} + fd_writer(fd f) : out{f} {} void write(span<char const> data) override { assert(*::write(out, data) == data.size()); } private: - file out; + fd out; }; struct buf_writer : writer @@ -42,11 +42,12 @@ private: void decimal(writer& w, uint64_t n) { char buf[20]; - char* const end = buf + 16; + char* const end = buf + sizeof(buf); char* p = end; do { - *--p = n % 10 + '0'; + assert(--p >= buf); + *p = n % 10 + '0'; n /= 10; } while (n); w.write({p, end}); diff --git a/x86_64.S b/x86_64.S @@ -1,5 +1,3 @@ -# Syscall numbers https://www.chromium.org/chromium-os/developer-library/reference/linux-constants/syscalls/#x86_64-64-bit - #include "NR_x86_64.h" .intel_syntax noprefix