diff --git a/README.md b/README.md index cec3d39..a6262ab 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,21 @@ 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 @@ -75,7 +90,7 @@ Which functions belong into which header can be figured out by taking a look at | Header File | Header Status | Implementation Status | Description | | --------------- | ------------- | --------------------- | ------------------------------------------------------------------------------------------------------- | -| `assert.h` | ❌ | | Conditionally compiled macro that compares its argument to zero | +| `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 | diff --git a/build.zig b/build.zig index b1df2a9..f73f285 100644 --- a/build.zig +++ b/build.zig @@ -112,6 +112,32 @@ pub fn build(b: *std.Build) void { 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 = .{}, // just build for the host + .optimize = .Debug, + }); + assert_validator.addCSourceFile(.{ + .file = .{ .path = "tests/assert-validator.c" }, + .flags = &common_c_flags, + }); + assert_validator.addIncludePath(include_path); + _ = assert_validator.getEmittedBin(); + + assert_validator.defineCMacro("FOUNDATION_LIBC_ASSERT", assert_mode); + + // Just compile, do not install: + validation_step.dependOn(&assert_validator.step); + } } } diff --git a/include/assert.h b/include/assert.h index 81cbd9e..16c4979 100644 --- a/include/assert.h +++ b/include/assert.h @@ -1,16 +1,43 @@ #ifndef _FOUNDATION_LIBC_ASSERT_H_ #define _FOUNDATION_LIBC_ASSERT_H_ -#ifndef NDEBUG -#define assert(expr) -#else -extern void __assert(char const * assertion, char const * file, unsigned line) __attribute__((__noreturn__)); +#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) \ - ((expr) \ - ? void(0) \ - : __assert(#expr, __FILE__, __LINE__)) +#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 #endif diff --git a/include/foundation/builtins.h b/include/foundation/builtins.h new file mode 100644 index 0000000..e95803e --- /dev/null +++ b/include/foundation/builtins.h @@ -0,0 +1,37 @@ +#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 + +#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/src/modules/assert.zig b/src/modules/assert.zig index fc181de..436760a 100644 --- a/src/modules/assert.zig +++ b/src/modules/assert.zig @@ -1,14 +1,20 @@ const std = @import("std"); +const builtin = @import("builtin"); -export fn __assert( +export fn foundation_libc_assert( assertion: ?[*:0]const u8, file: ?[*:0]const u8, line: c_uint, ) noreturn { - 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); + 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/tests/assert-validator.c b/tests/assert-validator.c new file mode 100644 index 0000000..b75ca55 --- /dev/null +++ b/tests/assert-validator.c @@ -0,0 +1,25 @@ +#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); +} diff --git a/tests/syntactic-validation.c b/tests/syntactic-validation.c index c31050e..375c489 100644 --- a/tests/syntactic-validation.c +++ b/tests/syntactic-validation.c @@ -1,5 +1,6 @@ // 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"