diff --git a/modules/foundation-libc/.clang-format b/modules/foundation-libc/.clang-format new file mode 100644 index 00000000..8951bebb --- /dev/null +++ b/modules/foundation-libc/.clang-format @@ -0,0 +1,26 @@ +--- +Language: Cpp +BasedOnStyle: LLVM + +# basic indentation is 4 spaces, indented by spaces: +IndentWidth: 4 +TabWidth: 4 +UseTab: Never + +# use east const for better readability: +QualifierAlignment: Right + +# do not tack pointer to type nor value: +PointerAlignment: Middle + +# align function names and keep them apart at least one line +AlignConsecutiveDeclarations: AcrossEmptyLines + +# we sort includes to prevent merge conflicts: +SortIncludes: CaseSensitive + +# disable column limit: +ColumnLimit: 0 + +--- + diff --git a/modules/foundation-libc/.gitattributes b/modules/foundation-libc/.gitattributes new file mode 100644 index 00000000..0cb064ae --- /dev/null +++ b/modules/foundation-libc/.gitattributes @@ -0,0 +1 @@ +*.zig text=auto eol=lf diff --git a/modules/foundation-libc/.github/workflows/build.yml b/modules/foundation-libc/.github/workflows/build.yml new file mode 100644 index 00000000..2395a232 --- /dev/null +++ b/modules/foundation-libc/.github/workflows/build.yml @@ -0,0 +1,28 @@ +name: Continuous Integration + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Zig + uses: mlugg/setup-zig@v1 + with: + version: 0.13.0 + + - name: Generate and validate packages + working-directory: test + run: | + zig build validate diff --git a/modules/foundation-libc/.gitignore b/modules/foundation-libc/.gitignore new file mode 100644 index 00000000..3389c86c --- /dev/null +++ b/modules/foundation-libc/.gitignore @@ -0,0 +1,2 @@ +.zig-cache/ +zig-out/ diff --git a/modules/foundation-libc/LICENCE b/modules/foundation-libc/LICENCE new file mode 100644 index 00000000..95d3d596 --- /dev/null +++ b/modules/foundation-libc/LICENCE @@ -0,0 +1,19 @@ +Copyright (c) 2024 Felix "xq" QueiรŸner + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/modules/foundation-libc/README.md b/modules/foundation-libc/README.md new file mode 100644 index 00000000..a6262abc --- /dev/null +++ b/modules/foundation-libc/README.md @@ -0,0 +1,125 @@ +# Foundation libc + +[![Continuous Integration](https://github.com/ZigEmbeddedGroup/foundation-libc/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/ZigEmbeddedGroup/foundation-libc/actions/workflows/build.yml) + +A C standard library that only implements a subset of functions that can be safely used without an operating system. +This is called a [freestanding environment](https://en.cppreference.com/w/cpp/freestanding). + +This libc is primarily meant to be used with microcontrollers, hobbyist operating systems and so on. + +## Support + +The first goal is to reach full C11 *freestanding* support. + +- No support for locales +- No allocator (ship your own!) +- No support for functions that require an operating system of sorts in the background. +- No support for `wchar_t` and `wchar.h` as it isn't portable between compilers. +- Multi-byte character strings are implemented as UTF-8. + +## Customization + +Foundation libc doesn't really support much customization/configuration except for the hard required options. + +There is [`foundation/libc.h`](include/foundation/libc.h) which documents the behaviour of all required configurations. + +Right now, the following configurations exist: + +- `foundation_libc_panic_handler`, which allows users to catch detectable undefined behaviour. + +You can also configure the libc by chosing the build mode: + +- `Debug`: Implements additional safety checks and adds breakpoints in panics. +- `ReleaseSafe`: Keeps the safety checks, but removes breakpoints. +- `ReleaseSmall`: Still keeps a certain amount of safety, but drops long internal strings to reduce code and ram size. +- `ReleaseFast`: Gotta go fast. Drops all safety and assumes all code behaves well. + +There are also certain "usage" configurations that can be chosen to affect behaviour when *using* the headers. Those are implemented as C macros/defines: + +- `FOUNDATION_LIBC_ASSERT` is a global macro that defines how `assert()` should behave: + - `FOUNDATION_LIBC_ASSERT_DEFAULT=0`: Behaves like a regular assert that can print file name, assertion message and line. + - `FOUNDATION_LIBC_ASSERT_NOFILE=1`: Drops the filename from the assertion to reduce code size. + - `FOUNDATION_LIBC_ASSERT_NOMSG=2`: Additionally drops the assertion message from the assertion to reduce code size. + - `FOUNDATION_LIBC_ASSERT_EXPECTED=3`: Replaces `assert(โ€ฆ)` with a construct that tells the compiler the assertion is always met. Makes code very fast. Assertions aren't checked. + +## Development + +Zig Version: 0.11 + +Run +```sh-session +user@microzig ~/foundation-libc $ zig build +user@microzig ~/foundation-libc $ +``` + +to compile the libc and generate a lib file in `zig-out/lib` as well as the headers in `zig-out/include`. + +## Contribution + +Start by grabbing a header marked with โณ or ๐Ÿ›  and implement the functions from that header. See if others already have a PR open for those functions so you don't do work twice! + +Leverage functions from Zig `std` if possible as they are already well tested and should work. + +Which functions belong into which header can be figured out by taking a look at the *C11 Standard Draft* document or the *IBM libc functions* list. [cppreference.com](https://en.cppreference.com/w/c) usually has the better docs though, so best check out both. + +### Style Guides + +- The header files are ment to be as minimal as possible + - Do not use comments documenting the functions, they are well documented everywhere else. + - Only insert empty lines between functions if necessarity for clarity + - Keep function names sorted alphabetically +- Try not to use macros at all +- Use `clang-format` with the provided style file. + + +## Links + +- [C11 Standard](https://www.iso.org/standard/57853.html) +- [C11 Standard Draft](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1548.pdf) +- [ziglibc](https://github.com/marler8997/ziglibc) +- [libc-test](https://wiki.musl-libc.org/libc-test.html) by musl +- [cppreference on freestanding](https://en.cppreference.com/w/cpp/freestanding) +- [GCC on freestanding](https://gcc.gnu.org/onlinedocs/gcc/Standards.html#C-Language) +- [IBM libc functions](https://www.ibm.com/docs/en/i/7.5?topic=extensions-standard-c-library-functions-table-by-name) (function to header map) +- [cppreference](https://en.cppreference.com/w/c) +- [clang-format style options](https://releases.llvm.org/16.0.0/tools/clang/docs/ClangFormatStyleOptions.html) + +## Status + +โณ (not started), ๐Ÿ›  (work in progress), โš ๏ธ (partial support), โœ… (full support), โŒ (no support), ๐Ÿ”ฎ (potential future support), ๐Ÿ”€ (implemented by compiler) + +| Header File | Header Status | Implementation Status | Description | +| --------------- | ------------- | --------------------- | ------------------------------------------------------------------------------------------------------- | +| `assert.h` | โœ… | โœ… | Conditionally compiled macro that compares its argument to zero | +| `complex.h` | โŒ | | (since C99) Complex number arithmetic | +| `ctype.h` | โœ… | โœ… | Functions to determine the type contained in character data | +| `errno.h` | โœ… | โœ… | Macros reporting error conditions | +| `fenv.h` | ๐Ÿ”ฎ | | (since C99) Floating-point environment | +| `float.h` | ๐Ÿ”€ | | Limits of floating-point types | +| `inttypes.h` | โณ | โณ | (since C99) Format conversion of integer types | +| `iso646.h` | ๐Ÿ”€ | | (since C95) Alternative operator spellings | +| `limits.h` | ๐Ÿ”€ | | Ranges of integer types | +| `locale.h` | โŒ | | Localization utilities | +| `math.h` | ๐Ÿ›  | โณ | Common mathematics functions | +| `setjmp.h` | ๐Ÿ›  | โณ | Nonlocal jumps | +| `signal.h` | โŒ | | Signal handling | +| `stdalign.h` | ๐Ÿ”€ | | (since C11) alignas and alignof convenience macros | +| `stdarg.h` | ๐Ÿ”€ | | Variable arguments | +| `stdatomic.h` | ๐Ÿ”ฎ | | (since C11) Atomic operations | +| `stdbit.h` | ๐Ÿ”ฎ | | (since C23) Macros to work with the byte and bit representations of types | +| `stdbool.h` | ๐Ÿ”€ | | (since C99) Macros for boolean type | +| `stdckdint.h` | ๐Ÿ”ฎ | | (since C23) macros for performing checked integer arithmetic | +| `stddef.h` | ๐Ÿ”€ | | Common macro definitions | +| `stdint.h` | ๐Ÿ”€ | | (since C99) Fixed-width integer types | +| `stdio.h` | โŒ | | Input/output | +| `stdlib.h` | ๐Ÿ›  | ๐Ÿ›  | General utilities: memory management, program utilities, string conversions, random numbers, algorithms | +| `stdnoreturn.h` | ๐Ÿ”€ | | (since C11) noreturn convenience macro | +| `string.h` | โœ… | ๐Ÿ›  | String handling | +| `tgmath.h` | โณ | โณ | (since C99) Type-generic math (macros wrapping math.h and complex.h) | +| `threads.h` | โŒ | | (since C11) Thread library | +| `time.h` | โŒ | | Time/date utilities | +| `uchar.h` | ๐Ÿ›  | โณ | (since C11) UTF-16 and UTF-32 character utilities | +| `wchar.h` | โŒ | | (since C95) Extended multibyte and wide character utilities | +| `wctype.h` | โŒ | | (since C95) Functions to determine the type contained in wide character data | + + diff --git a/modules/foundation-libc/build.zig b/modules/foundation-libc/build.zig new file mode 100644 index 00000000..aabe36ff --- /dev/null +++ b/modules/foundation-libc/build.zig @@ -0,0 +1,48 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const validation_step = b.step("validate", "Runs the test suite and validates everything. Automatically triggered in Debug builds."); + + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const single_threaded = b.option(bool, "single_threaded", "Create a single-threaded libc implementation (default: false)") orelse false; + + // Run validation in debug builds for convenience: + if (optimize == .Debug) { + b.getInstallStep().dependOn(validation_step); + } + + const libc = b.addStaticLibrary(.{ + .name = "foundation", + .target = target, + .optimize = optimize, + .root_source_file = b.path("src/libc.zig"), + .single_threaded = single_threaded, + }); + + libc.addIncludePath(b.path("include")); + for (header_files) |header_name| + libc.installHeader( + b.path(b.fmt("include/{s}", .{header_name})), + header_name, + ); + + libc.installHeadersDirectory(b.path("include/foundation"), "foundation", .{}); + + b.installArtifact(libc); +} + +const header_files = [_][]const u8{ + "assert.h", + "ctype.h", + "errno.h", + "inttypes.h", + "math.h", + "setjmp.h", + "stdlib.h", + "string.h", + "tgmath.h", + "uchar.h", + "foundation/libc.h", +}; diff --git a/modules/foundation-libc/build.zig.zon b/modules/foundation-libc/build.zig.zon new file mode 100644 index 00000000..3ad4c39a --- /dev/null +++ b/modules/foundation-libc/build.zig.zon @@ -0,0 +1,12 @@ +.{ + .name = "foundation-libc", + .version = "0.0.0", + .paths = .{ + "LICENSE", + "README.md", + "build.zig", + "build.zig.zon", + "include", + "src", + }, +} diff --git a/modules/foundation-libc/flake.lock b/modules/foundation-libc/flake.lock new file mode 100644 index 00000000..1e42e19b --- /dev/null +++ b/modules/foundation-libc/flake.lock @@ -0,0 +1,164 @@ +{ + "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-compat_2": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1705309234, + "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1718478900, + "narHash": "sha256-v43N1gZLcGkhg3PdcrKUNIZ1L0FBzB2JqhIYEyKAHEs=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "c884223af91820615a6146af1ae1fea25c107005", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "release-23.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1708161998, + "narHash": "sha256-6KnemmUorCvlcAvGziFosAVkrlWZGIc6UNT9GUYr0jQ=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "84d981bae8b5e783b3b548de505b22880559515f", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-23.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "zig": "zig" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "zig": { + "inputs": { + "flake-compat": "flake-compat_2", + "flake-utils": "flake-utils_2", + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1718324667, + "narHash": "sha256-AZGskEGjvUmeb+fgBv4lxtCUtXmYBI+ABOlV+og9X14=", + "owner": "mitchellh", + "repo": "zig-overlay", + "rev": "b2c14e5f842af6b2bf03e634f73fd84f6956d4ba", + "type": "github" + }, + "original": { + "owner": "mitchellh", + "repo": "zig-overlay", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/modules/foundation-libc/flake.nix b/modules/foundation-libc/flake.nix new file mode 100644 index 00000000..4759faac --- /dev/null +++ b/modules/foundation-libc/flake.nix @@ -0,0 +1,65 @@ +{ + description = "foundation libc development environment"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/release-23.11"; + flake-utils.url = "github:numtide/flake-utils"; + + # required for latest zig + zig.url = "github:mitchellh/zig-overlay"; + + # Used for shell.nix + flake-compat = { + url = github:edolstra/flake-compat; + flake = false; + }; + }; + + outputs = { + self, + nixpkgs, + flake-utils, + ... + } @ inputs: let + overlays = [ + # Other overlays + (final: prev: { + zigpkgs = inputs.zig.packages.${prev.system}; + }) + ]; + + # Our supported systems are the same supported systems as the Zig binaries + systems = builtins.attrNames inputs.zig.packages; + in + flake-utils.lib.eachSystem systems ( + system: let + pkgs = import nixpkgs {inherit overlays system;}; + in rec { + devShells.default = pkgs.mkShell { + nativeBuildInputs = [ + pkgs.zigpkgs."0.13.0" + pkgs.llvmPackages.clangUseLLVM + ]; + + buildInputs = [ + # we need a version of bash capable of being interactive + # as opposed to a bash just used for building this flake + # in non-interactive mode + pkgs.bashInteractive + ]; + + # see https://github.com/NixOS/nixpkgs/issues/18995 + hardeningDisable = ["all"]; + + shellHook = '' + # once we set SHELL to point to the interactive bash, neovim will + # launch the correct $SHELL in its :terminal + export SHELL=${pkgs.bashInteractive}/bin/bash + ''; + }; + + # For compatibility with older versions of the `nix` binary + devShell = self.devShells.${system}.default; + } + ); +} diff --git a/modules/foundation-libc/include/assert.h b/modules/foundation-libc/include/assert.h new file mode 100644 index 00000000..74ab2cde --- /dev/null +++ b/modules/foundation-libc/include/assert.h @@ -0,0 +1,48 @@ +#ifndef _FOUNDATION_LIBC_ASSERT_H_ +#define _FOUNDATION_LIBC_ASSERT_H_ + +#include "foundation/builtins.h" + +extern FOUNDATION_LIBC_NORETURN void foundation_libc_assert(char const * assertion, char const * file, unsigned line); + +#if FOUNDATION_LIBC_ASSERT == FOUNDATION_LIBC_ASSERT_DEFAULT + +#define assert(expr) \ + do { \ + if ((expr) == 0) { \ + foundation_libc_assert(#expr, __FILE__, __LINE__); \ + } \ + } while (0) + +#elif FOUNDATION_LIBC_ASSERT == FOUNDATION_LIBC_ASSERT_NOFILE + +#define assert(expr) \ + do { \ + if ((expr) == 0) { \ + foundation_libc_assert(#expr, "", __LINE__); \ + } \ + } while (0) + +#elif FOUNDATION_LIBC_ASSERT == FOUNDATION_LIBC_ASSERT_NOMSG + +#define assert(expr) \ + do { \ + if ((expr) == 0) { \ + foundation_libc_assert("", "", __LINE__); \ + } \ + } while (0) + +#elif FOUNDATION_LIBC_ASSERT == FOUNDATION_LIBC_ASSERT_EXPECTED + +#define assert(expr) FOUNDATION_LIBC_EXPECT(expr) + +#else +#error "bad definition of FOUNDATION_LIBC_ASSERT_DEFAULT!" +#endif + +#if !defined(__cplusplus) +#undef static_assert +#define static_assert FOUNDATION_LIBC_STATIC_ASSERT +#endif + +#endif diff --git a/modules/foundation-libc/include/ctype.h b/modules/foundation-libc/include/ctype.h new file mode 100644 index 00000000..c4e7caac --- /dev/null +++ b/modules/foundation-libc/include/ctype.h @@ -0,0 +1,21 @@ +#ifndef _FOUNDATION_LIBC_CTYPE_H_ +#define _FOUNDATION_LIBC_CTYPE_H_ + +#define EOF (-1) + +int isalnum(int c); +int isalpha(int c); +int isblank(int c); +int iscntrl(int c); +int isdigit(int c); +int isgraph(int c); +int islower(int c); +int isprint(int c); +int ispunct(int c); +int isspace(int c); +int isupper(int c); +int isxdigit(int c); +int tolower(int c); +int toupper(int c); + +#endif diff --git a/modules/foundation-libc/include/errno.h b/modules/foundation-libc/include/errno.h new file mode 100644 index 00000000..f00e1f1f --- /dev/null +++ b/modules/foundation-libc/include/errno.h @@ -0,0 +1,12 @@ +#ifndef _FOUNDATION_LIBC_ERRNO_H_ +#define _FOUNDATION_LIBC_ERRNO_H_ + +#define EDOM 1 +#define EILSEQ 2 +#define ERANGE 3 + +#define errno (*__get_errno()) + +int * __get_errno(void); + +#endif diff --git a/modules/foundation-libc/include/foundation/builtins.h b/modules/foundation-libc/include/foundation/builtins.h new file mode 100644 index 00000000..a5b57b96 --- /dev/null +++ b/modules/foundation-libc/include/foundation/builtins.h @@ -0,0 +1,39 @@ +#ifndef _FOUNDATION_LIBC_BUILTINS_H_ +#define _FOUNDATION_LIBC_BUILTINS_H_ + +#include + +#define FOUNDATION_LIBC_ASSERT_DEFAULT 0 +#define FOUNDATION_LIBC_ASSERT_NOFILE 1 +#define FOUNDATION_LIBC_ASSERT_NOMSG 2 +#define FOUNDATION_LIBC_ASSERT_EXPECTED 3 + +#ifndef FOUNDATION_LIBC_ASSERT +#define FOUNDATION_LIBC_ASSERT FOUNDATION_LIBC_ASSERT_DEFAULT +#endif + +#define FOUNDATION_LIBC_STATIC_ASSERT _Static_assert + +#if defined(__clang__) + +#define FOUNDATION_LIBC_NORETURN __attribute__((__noreturn__)) +#define FOUNDATION_LIBC_EXPECT(expr) (__builtin_expect(!(expr), 0)) + +#elif defined(__GNUC__) || defined(__GNUG__) + +#define FOUNDATION_LIBC_NORETURN __attribute__((__noreturn__)) +#define FOUNDATION_LIBC_EXPECT(expr) (__builtin_expect(!(expr), 0)) + +#elif defined(_MSC_VER) + +#define FOUNDATION_LIBC_NORETURN __declspec(noreturn) +#define FOUNDATION_LIBC_EXPECT(expr) (__assume((expr))) + +#else + +#define FOUNDATION_LIBC_NORETURN +#define FOUNDATION_LIBC_EXPECT(expr) + +#endif + +#endif diff --git a/modules/foundation-libc/include/foundation/libc.h b/modules/foundation-libc/include/foundation/libc.h new file mode 100644 index 00000000..7b245c80 --- /dev/null +++ b/modules/foundation-libc/include/foundation/libc.h @@ -0,0 +1,25 @@ +#ifndef _FOUNDATION_LIBC_INTERNALS_H_ +#define _FOUNDATION_LIBC_INTERNALS_H_ + +#include + +/// +/// Panic handler for undefined, but catchable behaviour in safe modes. +/// +/// This will be invoked when Zig detects undefined behaviour at runtime, +/// or when foundation libc can recognize illegal arguments. +/// +/// The function receives a non-terminated pointer to the panic message +/// with `msg_len` bytes of UTF-8 encoded payload. +/// +/// It has a weak default implementation shipped, so just implement this +/// function to plug in your own custom behaviour. +/// The default implementation is done by invoking a `trap` instruction to +/// emit an illegal instruction or otherwise crash the program execution. +/// +/// NOTE: This function must never return, because otherwise, the undefined +/// behaviour will be actually undefined! +/// +void foundation_libc_panic_handler(char const * msg_ptr, size_t msg_len); + +#endif diff --git a/modules/foundation-libc/include/inttypes.h b/modules/foundation-libc/include/inttypes.h new file mode 100644 index 00000000..2c0697a6 --- /dev/null +++ b/modules/foundation-libc/include/inttypes.h @@ -0,0 +1,4 @@ +#ifndef _FOUNDATION_LIBC_INTTYPES_H_ +#define _FOUNDATION_LIBC_INTTYPES_H_ + +#endif diff --git a/modules/foundation-libc/include/math.h b/modules/foundation-libc/include/math.h new file mode 100644 index 00000000..833cbf69 --- /dev/null +++ b/modules/foundation-libc/include/math.h @@ -0,0 +1,221 @@ +#ifndef _FOUNDATION_LIBC_MATH_H_ +#define _FOUNDATION_LIBC_MATH_H_ + +typedef float float_t; +typedef double double_t; + +#define FLT_EVAL_METHOD 0 + +#define HUGE_VAL +#define HUGE_VALF +#define HUGE_VALL + +#define INFINITY +#define NAN + +#define FP_INFINITE +#define FP_NAN +#define FP_NORMAL +#define FP_SUBNORMAL +#define FP_ZERO + +#define FP_FAST_FMA + +#define FP_FAST_FMAF +#define FP_FAST_FMAL + +#define FP_ILOGB0 +#define FP_ILOGBNAN + +#define MATH_ERRNO 1 +#define MATH_ERREXCEPT 2 + +#define math_errhandling + +#define fpclassify(x) +#define isfinite(x) +#define isinf(x) +#define isnan(x) +#define isnormal(x) +#define signbit(x) + +double acos(double x); +float acosf(float x); +long double acosl(long double x); +double asin(double x); +float asinf(float x); +long double asinl(long double x); +double atan(double x); +float atanf(float x); +long double atanl(long double x); +double atan2(double y, double x); +float atan2f(float y, float x); +long double atan2l(long double y, long double x); +double cos(double x); +float cosf(float x); +long double cosl(long double x); +double sin(double x); +float sinf(float x); +long double sinl(long double x); +double tan(double x); +float tanf(float x); +long double tanl(long double x); +double acosh(double x); +float acoshf(float x); +long double acoshl(long double x); +double asinh(double x); +float asinhf(float x); +long double asinhl(long double x); +double atanh(double x); +float atanhf(float x); +long double atanhl(long double x); +double cosh(double x); +float coshf(float x); +long double coshl(long double x); +double sinh(double x); +float sinhf(float x); +long double sinhl(long double x); +double tanh(double x); +float tanhf(float x); +long double tanhl(long double x); +double exp(double x); +float expf(float x); +long double expl(long double x); +double exp2(double x); +float exp2f(float x); +long double exp2l(long double x); +double expm1(double x); +float expm1f(float x); +long double expm1l(long double x); +double frexp(double value, int * exp); +float frexpf(float value, int * exp); +long double frexpl(long double value, int * exp); +int ilogb(double x); +int ilogbf(float x); +int ilogbl(long double x); +double ldexp(double x, int exp); +float ldexpf(float x, int exp); +long double ldexpl(long double x, int exp); +double log(double x); +float logf(float x); +long double logl(long double x); +double log10(double x); +float log10f(float x); +long double log10l(long double x); +double log1p(double x); +float log1pf(float x); +long double log1pl(long double x); +double log2(double x); +float log2f(float x); +long double log2l(long double x); +double logb(double x); +float logbf(float x); +long double logbl(long double x); +double modf(double value, double * iptr); +float modff(float value, float * iptr); +long double modfl(long double value, long double * iptr); +double scalbn(double x, int n); +float scalbnf(float x, int n); +long double scalbnl(long double x, int n); +double scalbln(double x, long int n); +float scalblnf(float x, long int n); +long double scalblnl(long double x, long int n); +double cbrt(double x); +float cbrtf(float x); +long double cbrtl(long double x); +double fabs(double x); +float fabsf(float x); +long double fabsl(long double x); +double hypot(double x, double y); +float hypotf(float x, float y); +long double hypotl(long double x, long double y); +double pow(double x, double y); +float powf(float x, float y); +long double powl(long double x, long double y); +double sqrt(double x); +float sqrtf(float x); +long double sqrtl(long double x); +double erf(double x); +float erff(float x); +long double erfl(long double x); +double erfc(double x); +float erfcf(float x); +long double erfcl(long double x); +double lgamma(double x); +float lgammaf(float x); +long double lgammal(long double x); +double tgamma(double x); +float tgammaf(float x); +long double tgammal(long double x); +double ceil(double x); +float ceilf(float x); +long double ceill(long double x); +double floor(double x); +float floorf(float x); +long double floorl(long double x); +double nearbyint(double x); +float nearbyintf(float x); +long double nearbyintl(long double x); +double rint(double x); +float rintf(float x); +long double rintl(long double x); +long int lrint(double x); +long int lrintf(float x); +long int lrintl(long double x); +long long int llrint(double x); +long long int llrintf(float x); +long long int llrintl(long double x); +double round(double x); +float roundf(float x); +long double roundl(long double x); +long int lround(double x); +long int lroundf(float x); +long int lroundl(long double x); +long long int llround(double x); +long long int llroundf(float x); +long long int llroundl(long double x); +double trunc(double x); +float truncf(float x); +long double truncl(long double x); +double fmod(double x, double y); +float fmodf(float x, float y); +long double fmodl(long double x, long double y); +double remainder(double x, double y); +float remainderf(float x, float y); +long double remainderl(long double x, long double y); +double remquo(double x, double y, int * quo); +float remquof(float x, float y, int * quo); +long double remquol(long double x, long double y, int * quo); +double copysign(double x, double y); +float copysignf(float x, float y); +long double copysignl(long double x, long double y); +double nan(char const * tagp); +float nanf(char const * tagp); +long double nanl(char const * tagp); +double nextafter(double x, double y); +float nextafterf(float x, float y); +long double nextafterl(long double x, long double y); +double nexttoward(double x, long double y); +float nexttowardf(float x, long double y); +long double nexttowardl(long double x, long double y); +double fdim(double x, double y); +float fdimf(float x, float y); +long double fdiml(long double x, long double y); +double fmax(double x, double y); +float fmaxf(float x, float y); +long double fmaxl(long double x, long double y); +double fmin(double x, double y); +float fminf(float x, float y); +long double fminl(long double x, long double y); +double fma(double x, double y, double z); +float fmaf(float x, float y, float z); +long double fmal(long double x, long double y, long double z); + +#define isgreater(x, y) +#define isgreaterequal(x, y) +#define isless(x, y) +#define islessequal(x, y) +#define islessgreater(x, y) +#define isunordered(x, y) + +#endif diff --git a/modules/foundation-libc/include/setjmp.h b/modules/foundation-libc/include/setjmp.h new file mode 100644 index 00000000..7481adc7 --- /dev/null +++ b/modules/foundation-libc/include/setjmp.h @@ -0,0 +1,10 @@ +#ifndef _FOUNDATION_LIBC_SETJMP_H_ +#define _FOUNDATION_LIBC_SETJMP_H_ + +typedef unsigned int jmp_buf[1]; + +#define setjmp(env) + +_Noreturn void longjmp(jmp_buf env, int val); + +#endif diff --git a/modules/foundation-libc/include/stdlib.h b/modules/foundation-libc/include/stdlib.h new file mode 100644 index 00000000..a3fb8ee3 --- /dev/null +++ b/modules/foundation-libc/include/stdlib.h @@ -0,0 +1,6 @@ +#ifndef _FOUNDATION_LIBC_STDLIB_H_ +#define _FOUNDATION_LIBC_STDLIB_H_ + +int atoi(char const * str); + +#endif diff --git a/modules/foundation-libc/include/string.h b/modules/foundation-libc/include/string.h new file mode 100644 index 00000000..0392b02d --- /dev/null +++ b/modules/foundation-libc/include/string.h @@ -0,0 +1,30 @@ +#ifndef _FOUNDATION_LIBC_STRING_H_ +#define _FOUNDATION_LIBC_STRING_H_ + +#include + +void * memchr(void const * s, int c, size_t n); +int memcmp(void const * s1, void const * s2, size_t n); +void * memcpy(void * restrict s1, void const * restrict s2, size_t n); +void * memmove(void * s1, void const * s2, size_t n); +void * memset(void * s, int c, size_t n); +char * strcat(char * restrict s1, char const * restrict s2); +char * strchr(char const * str, int ch); +int strcmp(char const * s1, char const * s2); +char * strcpy(char * restrict s1, char const * restrict s2); +size_t strcspn(char const * s1, char const * s2); +char * strerror(int errnum); +size_t strlen(char const * str); +char * strncat(char * restrict s1, char const * restrict s2, size_t n); +int strncmp(char const * lhs, char const * rhs, size_t count); +char * strncpy(char * restrict s1, char const * restrict s2, size_t n); +char * strpbrk(char const * s1, char const * s2); +char * strrchr(char const * s, int c); +size_t strspn(char const * s1, char const * s2); +char * strstr(char const * s1, char const * s2); +char * strtok(char * restrict s1, char const * restrict s2); + +// locale dependent: size_t strxfrm(char * restrict s1, const char * restrict +// s2, size_t n); locale dependent: int strcoll(const char *s1, const char *s2); + +#endif diff --git a/modules/foundation-libc/include/tgmath.h b/modules/foundation-libc/include/tgmath.h new file mode 100644 index 00000000..a6a0c603 --- /dev/null +++ b/modules/foundation-libc/include/tgmath.h @@ -0,0 +1,4 @@ +#ifndef _FOUNDATION_LIBC_TGMATH_H_ +#define _FOUNDATION_LIBC_TGMATH_H_ + +#endif diff --git a/modules/foundation-libc/include/uchar.h b/modules/foundation-libc/include/uchar.h new file mode 100644 index 00000000..634470ca --- /dev/null +++ b/modules/foundation-libc/include/uchar.h @@ -0,0 +1,18 @@ +#ifndef _FOUNDATION_LIBC_UCHAR_H_ +#define _FOUNDATION_LIBC_UCHAR_H_ + +#include + +typedef struct { + int dummy; // TODO: fill in internal multi-byte conversion state +} mbstate_t; + +typedef uint_least16_t char16_t; +typedef uint_least32_t char32_t; + +size_t mbrtoc16(char16_t * restrict pc16, char const * restrict s, size_t n, mbstate_t * restrict ps); +size_t c16rtomb(char * restrict s, char16_t c16, mbstate_t * restrict ps); +size_t mbrtoc32(char32_t * restrict pc32, char const * restrict s, size_t n, mbstate_t * restrict ps); +size_t c32rtomb(char * restrict s, char32_t c32, mbstate_t * restrict ps); + +#endif diff --git a/modules/foundation-libc/src/libc.zig b/modules/foundation-libc/src/libc.zig new file mode 100644 index 00000000..62e9fe76 --- /dev/null +++ b/modules/foundation-libc/src/libc.zig @@ -0,0 +1,89 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +pub const h = @cImport({ + @cInclude("assert.h"); + @cInclude("ctype.h"); + @cInclude("errno.h"); + @cInclude("inttypes.h"); + @cInclude("math.h"); + @cInclude("setjmp.h"); + @cInclude("stdlib.h"); + @cInclude("string.h"); + @cInclude("tgmath.h"); + @cInclude("uchar.h"); + + @cInclude("foundation/libc.h"); +}); + +comptime { + // Some assertions over the target platform: + std.debug.assert(@bitSizeOf(c_char) == 8); + + // Ensure hierarchy: + std.debug.assert(@bitSizeOf(c_short) >= @bitSizeOf(c_char)); + std.debug.assert(@bitSizeOf(c_int) >= @bitSizeOf(c_short)); + std.debug.assert(@bitSizeOf(c_long) >= @bitSizeOf(c_int)); + std.debug.assert(@bitSizeOf(c_longlong) >= @bitSizeOf(c_long)); + + // Ensure same-sized signed and unsigned + std.debug.assert(@bitSizeOf(c_ushort) == @bitSizeOf(c_short)); + std.debug.assert(@bitSizeOf(c_uint) == @bitSizeOf(c_int)); + std.debug.assert(@bitSizeOf(c_ulong) == @bitSizeOf(c_long)); + std.debug.assert(@bitSizeOf(c_ulonglong) == @bitSizeOf(c_longlong)); +} + +comptime { + // Drag in all implementations, so they are compiled: + _ = @import("modules/assert.zig"); + _ = @import("modules/ctype.zig"); + _ = @import("modules/errno.zig"); + _ = @import("modules/math.zig"); + _ = @import("modules/setjmp.zig"); + _ = @import("modules/stdlib.zig"); + _ = @import("modules/string.zig"); + _ = @import("modules/uchar.zig"); +} + +/// Invokes safety-checked undefined behaviour, use this to implement +/// UB checks in the libc itself. +pub fn undefined_behaviour(comptime string: []const u8) noreturn { + switch (builtin.mode) { + // In debug mode, trigger a breakpoint so it's easier to detect the situation + // of the undefined behaviour: + .Debug => { + @breakpoint(); + @panic("UNDEFINED BEHAVIOUR: " ++ string); + }, + + // Safe modes have nice messages with + .ReleaseSafe => @panic("UNDEFINED BEHAVIOUR DETECTED: " ++ string), + + .ReleaseSmall => @panic("UB"), + + .ReleaseFast => unreachable, + } +} + +/// Zig panic handler, forwards panics to `foundation_libc_panic_handler`. +pub fn panic(msg: []const u8, maybe_error_return_trace: ?*std.builtin.StackTrace, maybe_return_address: ?usize) noreturn { + _ = maybe_error_return_trace; + _ = maybe_return_address; + h.foundation_libc_panic_handler(msg.ptr, msg.len); + unreachable; +} + +/// default implementation for `foundation_libc_panic_handler`. +fn fallback_panic_handler(msg_ptr: [*]const u8, msg_len: usize) callconv(.C) noreturn { + _ = msg_ptr; + _ = msg_len; + @trap(); +} +comptime { + const ptr = &fallback_panic_handler; + @export(ptr, std.builtin.ExportOptions{ + .name = "foundation_libc_panic_handler", + .linkage = .weak, + .visibility = .default, + }); +} diff --git a/modules/foundation-libc/src/modules/assert.zig b/modules/foundation-libc/src/modules/assert.zig new file mode 100644 index 00000000..436760ae --- /dev/null +++ b/modules/foundation-libc/src/modules/assert.zig @@ -0,0 +1,20 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +export fn foundation_libc_assert( + assertion: ?[*:0]const u8, + file: ?[*:0]const u8, + line: c_uint, +) noreturn { + switch (builtin.mode) { + .Debug, .ReleaseSafe => { + var buf: [256]u8 = undefined; + const str = std.fmt.bufPrint(&buf, "assertion failed: '{?s}' in file {?s} line {}", .{ assertion, file, line }) catch { + @panic("assertion failed"); + }; + @panic(str); + }, + .ReleaseSmall => @panic("assertion failed"), + .ReleaseFast => unreachable, + } +} diff --git a/modules/foundation-libc/src/modules/ctype.zig b/modules/foundation-libc/src/modules/ctype.zig new file mode 100644 index 00000000..1f613311 --- /dev/null +++ b/modules/foundation-libc/src/modules/ctype.zig @@ -0,0 +1,127 @@ +//! implementation of `ctype.h` +//! +//! The header declares several functions useful for classifying and mapping +//! characters. In all cases the argument is an int, the value of which shall be +//! representable as an unsigned char or shall equal the value of the macro EOF. If the +//! argument has any other value, the behavior is undefined. +//! + +const std = @import("std"); +const libc = @import("../libc.zig"); + +// Use an alias to std.ascii to allow potential future replacement +// of the locale implementation: +const locale = std.ascii; + +const EOF = libc.h.EOF; + +/// Convert input to u8, undefined behaviour +fn conv(c: c_int) ?u8 { + if (c == EOF) + return null; + return std.math.cast(u8, c) orelse libc.undefined_behaviour("passed a value that is not unsigned char nor EOF to a ctype function"); +} + +/// The isalnum function tests for any character for which isalpha or isdigit is true. +export fn isalnum(c: c_int) c_int { + const u = conv(c) orelse return EOF; + return @intFromBool(locale.isAlphanumeric(u)); +} + +/// The isalpha function tests for any character for which isupper or islower is true, +/// or any character that is one of a locale-specific set of alphabetic characters for which +/// none of iscntrl, isdigit, ispunct, or isspace is true.200) In the "C" locale, +/// isalpha returns true only for the characters for which isupper or islower is true. +export fn isalpha(c: c_int) c_int { + const u = conv(c) orelse return EOF; + return @intFromBool(locale.isAlphabetic(u)); +} + +/// The isblank function tests for any character that is a standard blank character or is one +/// of a locale-specific set of characters for which isspace is true and that is used to +/// separate words within a line of text. The standard blank characters are the following: +/// space (' '), and horizontal tab ('\t'). In the "C" locale, isblank returns true only +/// for the standard blank characters. +export fn isblank(c: c_int) c_int { + const u = conv(c) orelse return EOF; + return @intFromBool(u == ' ' or u == '\t'); +} + +/// The iscntrl function tests for any control character. +export fn iscntrl(c: c_int) c_int { + const u = conv(c) orelse return EOF; + return @intFromBool(locale.isControl(u)); +} + +/// The isdigit function tests for any decimal-digit character (as defined in 5.2.1). +export fn isdigit(c: c_int) c_int { + const u = conv(c) orelse return EOF; + return @intFromBool(locale.isDigit(u)); +} + +/// The isgraph function tests for any printing character except space (' '). +export fn isgraph(c: c_int) c_int { + const u = conv(c) orelse return EOF; + return @intFromBool(locale.isPrint(u) and (c != ' ')); +} + +/// The islower function tests for any character that is a lowercase letter or is one of a +/// locale-specific set of characters for which none of iscntrl, isdigit, ispunct, or +/// isspace is true. In the "C" locale, islower returns true only for the lowercase +/// letters (as defined in 5.2.1). +export fn islower(c: c_int) c_int { + const u = conv(c) orelse return EOF; + return @intFromBool(locale.isLower(u)); +} + +/// The isprint function tests for any printing character including space (' '). +export fn isprint(c: c_int) c_int { + const u = conv(c) orelse return EOF; + return @intFromBool(locale.isPrint(u)); +} + +/// The ispunct function tests for any printing character that is one of a locale-specific set +/// of punctuation characters for which neither isspace nor isalnum is true. In the "C" +/// locale, ispunct returns true for every printing character for which neither isspace +/// nor isalnum is true. +export fn ispunct(c: c_int) c_int { + const u = conv(c) orelse return EOF; + return @intFromBool(!locale.isWhitespace(u) and !locale.isAlphanumeric(u)); +} + +/// The isspace function tests for any character that is a standard white-space character or +/// is one of a locale-specific set of characters for which isalnum is false. The standard +/// white-space characters are the following: space (' '), form feed ('\f'), new-line +/// ('\n'), carriage return ('\r'), horizontal tab ('\t'), and vertical tab ('\v'). In the +/// "C" locale, isspace returns true only for the standard white-space characters. +export fn isspace(c: c_int) c_int { + const u = conv(c) orelse return EOF; + return @intFromBool(locale.isWhitespace(u)); +} + +/// The isupper function tests for any character that is an uppercase letter or is one of a +/// locale-specific set of characters for which none of iscntrl, isdigit, ispunct, or +/// isspace is true. In the "C" locale, isupper returns true only for the uppercase +/// letters (as defined in 5.2.1). +export fn isupper(c: c_int) c_int { + const u = conv(c) orelse return EOF; + return @intFromBool(locale.isUpper(u)); +} + +/// The isxdigit function tests for any hexadecimal-digit character (as defined in 6.4.4.1). +export fn isxdigit(c: c_int) c_int { + const u = conv(c) orelse return EOF; + return @intFromBool(locale.isHex(u)); +} + +/// The tolower function converts an uppercase letter to a corresponding lowercase letter. +export fn tolower(c: c_int) c_int { + const u = conv(c) orelse return EOF; + return locale.toLower(u); +} + +/// The toupper function converts a lowercase letter to a corresponding uppercase letter. +export fn toupper(c: c_int) c_int { + const u = conv(c) orelse return EOF; + return locale.toUpper(u); +} diff --git a/modules/foundation-libc/src/modules/errno.zig b/modules/foundation-libc/src/modules/errno.zig new file mode 100644 index 00000000..a1f4c1ac --- /dev/null +++ b/modules/foundation-libc/src/modules/errno.zig @@ -0,0 +1,17 @@ +//! implementation of `errno.h` + +const std = @import("std"); +const builtin = @import("builtin"); + +const storage = if (builtin.single_threaded) + struct { + var errno: c_int = 0; // regular variable on single-threaded systems + } +else + struct { + threadlocal var errno: c_int = 0; // thread-local variable on multi-threaded systems + }; + +export fn __get_errno() *c_int { + return &storage.errno; +} diff --git a/modules/foundation-libc/src/modules/math.zig b/modules/foundation-libc/src/modules/math.zig new file mode 100644 index 00000000..1a3da769 --- /dev/null +++ b/modules/foundation-libc/src/modules/math.zig @@ -0,0 +1,3 @@ +//! implementation of `math.h` + +const std = @import("std"); diff --git a/modules/foundation-libc/src/modules/setjmp.zig b/modules/foundation-libc/src/modules/setjmp.zig new file mode 100644 index 00000000..6e0b0ea3 --- /dev/null +++ b/modules/foundation-libc/src/modules/setjmp.zig @@ -0,0 +1,3 @@ +//! implementation of `setjmp.h` + +const std = @import("std"); diff --git a/modules/foundation-libc/src/modules/stdlib.zig b/modules/foundation-libc/src/modules/stdlib.zig new file mode 100644 index 00000000..b3375800 --- /dev/null +++ b/modules/foundation-libc/src/modules/stdlib.zig @@ -0,0 +1,17 @@ +//! implementation of `stdlib.h` + +const std = @import("std"); + +/// https://en.cppreference.com/w/c/string/byte/atoi +export fn atoi(str: ?[*:0]const c_char) c_int { + const s = str orelse return 0; + + var i: usize = 0; + while (std.ascii.isWhitespace(@bitCast(s[i]))) { + i += 1; + } + + const slice = std.mem.sliceTo(s + i, 0); + + return std.fmt.parseInt(c_int, @ptrCast(slice), 10) catch return 0; +} diff --git a/modules/foundation-libc/src/modules/string.zig b/modules/foundation-libc/src/modules/string.zig new file mode 100644 index 00000000..d17ce7f0 --- /dev/null +++ b/modules/foundation-libc/src/modules/string.zig @@ -0,0 +1,69 @@ +//! implementation of `string.h` + +const std = @import("std"); + +// Implemented in compiler_rt: +// * memcpy +// * memset + +// TODO: memchr +// TODO: memcmp +// TODO: memmove +// TODO: strcat +// TODO: strcmp +// TODO: strcpy +// TODO: strcspn +// TODO: strerror +// TODO: strncat +// TODO: strncpy +// TODO: strpbrk +// TODO: strrchr +// TODO: strspn +// TODO: strstr + +/// https://en.cppreference.com/w/c/string/byte/strchr +export fn strchr(str: ?[*:0]const c_char, ch: c_int) ?[*:0]c_char { + const s = str orelse return null; + + const searched: c_char = @bitCast(@as(u8, @truncate(@as(c_uint, @bitCast(ch))))); + + var i: usize = 0; + while (true) { + const actual = s[i]; + if (actual == searched) + return @constCast(s + i); + if (actual == 0) + return null; + i += 1; + } +} + +/// https://en.cppreference.com/w/c/string/byte/strlen +export fn strlen(str: ?[*:0]const c_char) usize { + const s = str orelse return 0; + return std.mem.len(s); +} + +/// https://en.cppreference.com/w/c/string/byte/strncmp +export fn strncmp(lhs: ?[*:0]const c_char, rhs: ?[*:0]const c_char, count: usize) c_int { + const lhs_s: [*:0]const u8 = @ptrCast(lhs orelse return if (rhs != null) 1 else 0); + const rhs_s: [*:0]const u8 = @ptrCast(rhs orelse return if (lhs != null) -1 else 0); + + var i: usize = 0; + while (i < count) { + const l = lhs_s[i]; + const r = rhs_s[i]; + + const d = @as(c_int, l) - @as(c_int, r); + if (d != 0) + return d; + + if (l == 0 and r == 0) + return 0; + std.debug.assert(l != 0); + std.debug.assert(r != 0); + + i += 1; + } + return 0; +} diff --git a/modules/foundation-libc/src/modules/uchar.zig b/modules/foundation-libc/src/modules/uchar.zig new file mode 100644 index 00000000..7b4706d6 --- /dev/null +++ b/modules/foundation-libc/src/modules/uchar.zig @@ -0,0 +1,3 @@ +//! implementation of `uchar.h` + +const std = @import("std"); diff --git a/modules/foundation-libc/test/build.zig b/modules/foundation-libc/test/build.zig new file mode 100644 index 00000000..d6c282c9 --- /dev/null +++ b/modules/foundation-libc/test/build.zig @@ -0,0 +1,222 @@ +const std = @import("std"); +const Build = std.Build; + +pub fn build(b: *Build) void { + const validation_step = b.step("validate", "Runs the test suite and validates everything. Automatically triggered in Debug builds."); + + //const maybe_gcc = b.findProgram(&.{"gcc"}, &.{}) catch null; + const maybe_clang = b.findProgram(&.{"clang"}, &.{}) catch null; + + // Compile for huge amount of targets to detect breakage early on: + for ([_]bool{ false, true }) |validation_single_threaded| { + for (std.enums.values(std.builtin.OptimizeMode)) |validation_optimize| { + for (validation_target_list) |validation_target_query| { + const validation_target = b.resolveTargetQuery(validation_target_query); + + // skip everything that cannot support multithreading on freestanding: + if (!validation_single_threaded and !target_can_multithread(validation_target)) + continue; + + const dep = b.dependency("foundation_libc", .{ + .target = validation_target, + .optimize = validation_optimize, + .single_threaded = validation_single_threaded, + }); + + const foundation = dep.artifact("foundation"); + const syntax_validator_source = b.path("src/syntactic-validation.c"); + + { + // Check if the syntax of all of our header files is valid: + const syntax_validator = b.addStaticLibrary(.{ + .name = "syntax-validator", + .target = b.host, + .optimize = .Debug, + }); + syntax_validator.addCSourceFile(.{ + .file = syntax_validator_source, + .flags = &common_c_flags, + }); + syntax_validator.linkLibrary(foundation); + _ = syntax_validator.getEmittedBin(); + + // Just compile, do not install: + validation_step.dependOn(&syntax_validator.step); + } + + // use the host C compilers to validate our code: + for ([_]?[]const u8{maybe_clang}) |maybe_cc| { + const cc = maybe_cc orelse continue; + + const ext_compiler = b.addSystemCommand(&.{cc}); + + // just compile every time, we don't have dir caching + // so changes on the headers wouldn't re-trigger this + ext_compiler.has_side_effects = true; + + ext_compiler.addPrefixedDirectoryArg("-I", b.path("../include")); + + ext_compiler.addArg("-c"); // compile only + ext_compiler.addArg("-O0"); // no optimization for fast compiles + ext_compiler.addArg("-ffreestanding"); // we require freestanding environment + ext_compiler.addArg("-nostdlib"); // do not try to link anything useful + + // turn on warnings + ext_compiler.addArg("-Werror"); + ext_compiler.addArg("-Wall"); + ext_compiler.addArg("-Wextra"); + + ext_compiler.addFileArg(syntax_validator_source); + + ext_compiler.addArg("-o"); + ext_compiler.addArg(b.pathJoin(&.{ b.makeTempPath(), "dummy" })); // we don't really care where this ends up + + validation_step.dependOn(&ext_compiler.step); + } + + // Validate all modes of assertion: + for ([_][]const u8{ + "FOUNDATION_LIBC_ASSERT_DEFAULT", + "FOUNDATION_LIBC_ASSERT_NOFILE", + "FOUNDATION_LIBC_ASSERT_NOMSG", + "FOUNDATION_LIBC_ASSERT_EXPECTED", + }) |assert_mode| { + // Check if the syntax of all of our header files is valid: + const assert_validator = b.addStaticLibrary(.{ + .name = "assert-validator", + .target = b.host, + .optimize = .Debug, + }); + assert_validator.addCSourceFile(.{ + .file = b.path("src/assert-validator.c"), + .flags = &common_c_flags, + }); + assert_validator.linkLibrary(foundation); + _ = assert_validator.getEmittedBin(); + + assert_validator.defineCMacro("FOUNDATION_LIBC_ASSERT", assert_mode); + + // Just compile, do not install: + validation_step.dependOn(&assert_validator.step); + } + } + } + } +} + +const common_c_flags = [_][]const u8{ + "-std=c11", // target C standard + // + "-Werror", // we do not allow warnings + "-Wall", + "-Wextra", + "-Wmost", + "-Weverything", // this might be dropped later as we just want to find all potential warnings and enable/disable them as required. + // + "-Wno-reserved-macro-identifier", // we actually want to implement those! + "-Wno-reserved-identifier", // we actually want to implement those! +}; + +fn target_can_multithread(target: Build.ResolvedTarget) bool { + return switch (target.result.cpu.arch) { + .wasm32, + .wasm64, + .msp430, + => false, + + else => true, + }; +} + +const validation_target_list = [_]std.zig.CrossTarget{ + .{}, // regular host platform + .{ .os_tag = .freestanding }, // host platform, but no OS + + // Check several common cpu targets: + + // arm: + .{ .cpu_arch = .arm, .os_tag = .freestanding }, + .{ .cpu_arch = .armeb, .os_tag = .freestanding }, + .{ .cpu_arch = .thumb, .os_tag = .freestanding }, + .{ .cpu_arch = .thumbeb, .os_tag = .freestanding }, + .{ .cpu_arch = .aarch64, .os_tag = .freestanding }, + // .{ .cpu_arch = .aarch64_32, .os_tag = .freestanding }, // error: unknown target triple 'aarch64_32-unknown-unknown-eabi', please use -triple or -arch + .{ .cpu_arch = .aarch64_be, .os_tag = .freestanding }, + + // risc-v: + .{ .cpu_arch = .riscv32, .os_tag = .freestanding }, + .{ .cpu_arch = .riscv64, .os_tag = .freestanding }, + + // intel: + .{ .cpu_arch = .x86_64, .os_tag = .freestanding }, + .{ .cpu_arch = .x86, .os_tag = .freestanding }, + + // mips: + .{ .cpu_arch = .mips, .os_tag = .freestanding }, + .{ .cpu_arch = .mips64, .os_tag = .freestanding }, + .{ .cpu_arch = .mips64el, .os_tag = .freestanding }, + .{ .cpu_arch = .mipsel, .os_tag = .freestanding }, + + // sparc: + .{ .cpu_arch = .sparc, .os_tag = .freestanding }, + .{ .cpu_arch = .sparc64, .os_tag = .freestanding }, + .{ .cpu_arch = .sparcel, .os_tag = .freestanding }, + + // power: + .{ .cpu_arch = .powerpc, .os_tag = .freestanding }, + .{ .cpu_arch = .powerpc64, .os_tag = .freestanding }, + .{ .cpu_arch = .powerpc64le, .os_tag = .freestanding }, + .{ .cpu_arch = .powerpcle, .os_tag = .freestanding }, + + // web assembly: + .{ .cpu_arch = .wasm32, .os_tag = .freestanding }, + .{ .cpu_arch = .wasm64, .os_tag = .freestanding }, + + // nice to have, but broken: + //.{ .cpu_arch = .avr, .os_tag = .freestanding }, + // .{ .cpu_arch = .msp430, .os_tag = .freestanding }, // error: unknown target CPU 'generic' + // .{ .cpu_arch = .m68k, .os_tag = .freestanding }, + // .{ .cpu_arch = .xtensa, .os_tag = .freestanding }, + + // Not evaluated if reasonable to check: + // arc + // csky + // hexagon + // hsail + // hsail64 + // kalimba + // lanai + // le32 + // le64 + // loongarch32 + // loongarch64 + // r600 + // s390x + // shave + // spu_2 + // tce + // tcele + // ve + // xcore + + // will never be supported due to their properties: + // spir + // spir64 + // spirv32 + // spirv64 + + // bpfeb + // bpfel + + // renderscript32 + // renderscript64 + + // amdgcn + // amdil + // amdil64 + + // nvptx + // nvptx64 + + // dxil +}; diff --git a/modules/foundation-libc/test/build.zig.zon b/modules/foundation-libc/test/build.zig.zon new file mode 100644 index 00000000..267e6d73 --- /dev/null +++ b/modules/foundation-libc/test/build.zig.zon @@ -0,0 +1,12 @@ +.{ + .name = "foundation-libc/test", + .version = "0.0.0", + .dependencies = .{ + .foundation_libc = .{ .path = ".." }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/modules/foundation-libc/test/src/assert-validator.c b/modules/foundation-libc/test/src/assert-validator.c new file mode 100644 index 00000000..db1f4925 --- /dev/null +++ b/modules/foundation-libc/test/src/assert-validator.c @@ -0,0 +1,29 @@ +#include + +#ifndef FOUNDATION_LIBC_ASSERT +#error "FOUNDATION_LIBC_ASSERT wasn't implicitly or explicitly defined by assert.h" +#endif + +#pragma GCC diagnostic ignored "-Wunused-function" +#pragma GCC diagnostic ignored "-Wmissing-prototypes" +// + +void assert_dynamic(int ok) { + assert(ok); +} + +void assert_ok(void) { + assert(1); +} + +// suppress noreturn diagnostic for missing "noreturn" +#pragma GCC diagnostic ignored "-Wmissing-noreturn" +// + +void assert_bad(void) { + assert(0); +} + +void assert_static(void) { + static_assert(sizeof(int) == sizeof(int), "int size check"); +} diff --git a/modules/foundation-libc/test/src/syntactic-validation.c b/modules/foundation-libc/test/src/syntactic-validation.c new file mode 100644 index 00000000..375c4891 --- /dev/null +++ b/modules/foundation-libc/test/src/syntactic-validation.c @@ -0,0 +1,24 @@ + +// our own files must be included as non-system includes to trigger warnings: +#include "assert.h" +#include "ctype.h" +#include "errno.h" +#include "foundation/libc.h" +#include "inttypes.h" +#include "math.h" +#include "setjmp.h" +#include "stdlib.h" +#include "string.h" +#include "tgmath.h" +#include "uchar.h" + +// those files should be present still, but are compiler provided: +#include +#include +#include +#include +#include +#include +#include +#include +#include