Commit b8b9c62

kravantokh <kravantokh@noreply.codeberg.org>
2026-07-17 21:21:35
Implement generic safemath. Revise its test.
1 parent 5a263df
platform/common/compiler.h
@@ -90,13 +90,17 @@
 #if COMPILER == COMPILER_TCC
 	#define WARN(...)
 	#define ERROR(...) (*(void*)0 = 0)
+	#ifndef NOTLIBC_SILENCE
 	#warning Compiling with tcc. Pragmas are not supported, so warnings will just not be printed and errors will give an 'assignment to void' error, but the line number should still point to the right location.
+	#endif
 #endif
 
 #ifndef WARNING
 #define WARNING(...)
+#ifndef NOTLIBC_SILENCE
 #warning No warning directive is implemented for this compiler. No warnings will be emitted from the code.
 #endif
+#endif
 
 #ifndef ERROR
 #define ERROR(...)
@@ -145,7 +149,9 @@ typedef struct _##name { \
 
 #ifndef UNREACHABLE
 #define UNREACHABLE
+#ifndef NOTLIBC_SILENCE
 #warning UNREACHABLE is not defined for the current compiler. Compiler may not perform all optimizations it could based on unreachability in some special cases.
 #endif
+#endif
 
 #endif
platform/platform.h
@@ -49,7 +49,7 @@ ASSERT( sizeof( f64 ) == 8 );
 
 /* Compiler-specific tricks */
 
-GCC( IMPL(
+GCCLIKE( IMPL(
 	/* This is so that GCC can detect stack smashing via canaries and crash the program when it happens. */
 	void __stack_chk_fail(void) {
 		assert(0);
testing/tests/001_safemath.c
@@ -1,30 +1,66 @@
 #include "platform/platform.h"
 #include "util/mathops.h"
 
+#define N 12
+
 void entry(void) {
 
-	u64 v1 = -1;
-	u32 v2 = -1;
-	u16 v3 = -1;
-	u8 v4 = -1;
+	u64 res = 0; /* make sure all writes actually happen */
+
+	assert( (-1^1) == -2 ); /* All safemath code assumes 2's complement. Assert it here, too. */
+
+	/* Validate correct addition and subtraction behaviour */
+	assert( S_OK( u64_add( 3, 2,       &res ))); assert( res == 5 );
+	assert( S_OK( u32_add( 3, 2, (u32*)&res ))); assert( res == 5 );
+	assert( S_OK( u16_add( 3, 2, (u16*)&res ))); assert( res == 5 );
+	assert( S_OK(  u8_add( 3, 2, (u8 *)&res ))); assert( res == 5 );
+
+	assert( S_OK( u64_sub( 3, 2,       &res ))); assert( res == 1 );
+	assert( S_OK( u32_sub( 3, 2, (u32*)&res ))); assert( res == 1 );
+	assert( S_OK( u16_sub( 3, 2, (u16*)&res ))); assert( res == 1 );
+	assert( S_OK(  u8_sub( 3, 2, (u8 *)&res ))); assert( res == 1 );
+
+	assert( S_OK( s64_add( 3, 2, (s64*)&res ))); assert( res == 5 );
+	assert( S_OK( s32_add( 3, 2, (s32*)&res ))); assert( res == 5 );
+	assert( S_OK( s16_add( 3, 2, (s16*)&res ))); assert( res == 5 );
+	assert( S_OK(  s8_add( 3, 2, (s8 *)&res ))); assert( res == 5 );
+
+	assert( S_OK( s64_sub( 3, 2, (s64*)&res ))); assert( res == 1 );
+	assert( S_OK( s32_sub( 3, 2, (s32*)&res ))); assert( res == 1 );
+	assert( S_OK( s16_sub( 3, 2, (s16*)&res ))); assert( res == 1 );
+	assert( S_OK(  s8_sub( 3, 2, (s8 *)&res ))); assert( res == 1 );
 
-	u64 dontcare = 0;
+	/* Unsigned overflow */
+	assert( S_FAILED( u64_add( u64_MAX, N,       &res )));
+	assert( S_FAILED( u32_add( u32_MAX, N, (u32*)&res )));
+	assert( S_FAILED( u16_add( u16_MAX, N, (u16*)&res )));
+	assert( S_FAILED(  u8_add(  u8_MAX, N, (u8 *)&res )));
 
-	assert( S_FAILED(
-		u64_add( v1, 12, &dontcare )
-	));
+	/* Unsigned underflow */
+	assert( S_FAILED( u64_sub( u64_MIN, N,       &res )));
+	assert( S_FAILED( u32_sub( u32_MIN, N, (u32*)&res )));
+	assert( S_FAILED( u16_sub( u16_MIN, N, (u16*)&res )));
+	assert( S_FAILED(  u8_sub(  u8_MIN, N, (u8 *)&res )));
 
-	assert( S_FAILED(
-		u32_add( v2, 12, (u32*)&dontcare )
-	));
+	/* Signed overflow */
+	assert( S_FAILED( s64_add( s64_MAX,  N, (s64*)&res )));
+	assert( S_FAILED( s32_add( s32_MAX,  N, (s32*)&res )));
+	assert( S_FAILED( s16_add( s16_MAX,  N, (s16*)&res )));
+	assert( S_FAILED(  s8_add(  s8_MAX,  N, (s8 *)&res )));
+	assert( S_FAILED( s64_sub( s64_MAX, -N, (s64*)&res )));
+	assert( S_FAILED( s32_sub( s32_MAX, -N, (s32*)&res )));
+	assert( S_FAILED( s16_sub( s16_MAX, -N, (s16*)&res )));
+	assert( S_FAILED(  s8_sub(  s8_MAX, -N,  (s8 *)&res )));
 
-	assert( S_FAILED(
-		u16_add( v3, 12, (u16*)&dontcare )
-	));
+	/* Signed underflow */
+	assert( S_FAILED( s64_sub( s64_MIN,  N, (s64*)&res )));
+	assert( S_FAILED( s32_sub( s32_MIN,  N, (s32*)&res )));
+	assert( S_FAILED( s16_sub( s16_MIN,  N, (s16*)&res )));
+	assert( S_FAILED(  s8_sub(  s8_MIN,  N, (s8 *)&res )));
+	assert( S_FAILED( s64_add( s64_MIN, -N, (s64*)&res )));
+	assert( S_FAILED( s32_add( s32_MIN, -N, (s32*)&res )));
+	assert( S_FAILED( s16_add( s16_MIN, -N, (s16*)&res )));
+	assert( S_FAILED(  s8_add(  s8_MIN, -N, (s8 *)&res )));
 
-	assert( S_FAILED(
-		u8_add( v4, 12, (u8*)&dontcare )
-	));
-	
 	terminate(0);
 }
testing/test.sh
@@ -20,39 +20,41 @@ echo
 echo Compiling tests.
 echo
 
+CFLAGS="-nostdlib -I. -DNOTLIBC_IMPLEMENT -DNOTLIBC_SILENCE -fno-builtin -g"
+
 for SRC in ./testing/tests/*.c; do
 
-	if ! $CC -nostdlib -I. -g -fno-builtin -O0 -DBUILD_TYPE=1 -DNOTLIBC_IMPLEMENT $SRC -o ./build/tests/o0d/$(basename "$SRC" .c); then
+	if ! $CC $CFLAGS -O0 -DBUILD_TYPE=1 $SRC -o ./build/tests/o0d/$(basename "$SRC" .c); then
 		echo
 		echo "Compilation failure for $SRC - O0D."
 		FAILURE=1
 	fi
 
-	if ! $CC -nostdlib -I. -g -fno-builtin -Og -DBUILD_TYPE=1 -DNOTLIBC_IMPLEMENT $SRC -o ./build/tests/ogd/$(basename "$SRC" .c); then
+	if ! $CC $CFLAGS -Og -DBUILD_TYPE=1 $SRC -o ./build/tests/ogd/$(basename "$SRC" .c); then
 		echo
 		echo "Compilation failure for $SRC - OgD."
 		FAILURE=1
 	fi
 
-	if ! $CC -nostdlib -I. -g -fno-builtin -O2 -DBUILD_TYPE=1 -DNOTLIBC_IMPLEMENT $SRC -o ./build/tests/o2d/$(basename "$SRC" .c); then
+	if ! $CC $CFLAGS -O2 -DBUILD_TYPE=1 $SRC -o ./build/tests/o2d/$(basename "$SRC" .c); then
 		echo
 		echo "Compilation failure for $SRC - O2D."
 		FAILURE=1
 	fi
 
-	if ! $CC -nostdlib -I. -g -fno-builtin -O3 -DBUILD_TYPE=1 -DNOTLIBC_IMPLEMENT $SRC -o ./build/tests/o3d/$(basename "$SRC" .c); then
+	if ! $CC $CFLAGS -O3 -DBUILD_TYPE=1 $SRC -o ./build/tests/o3d/$(basename "$SRC" .c); then
 		echo
 		echo "Compilation failure for $SRC - O3D."
 		FAILURE=1
 	fi
 
-	if ! $CC -nostdlib -I. -g -fno-builtin -O2 -DBUILD_TYPE=2 -DNOTLIBC_IMPLEMENT $SRC -o ./build/tests/o2r/$(basename "$SRC" .c); then
+	if ! $CC $CFLAGS -O2 -DBUILD_TYPE=2 $SRC -o ./build/tests/o2r/$(basename "$SRC" .c); then
 		echo
 		echo "Compilation failure for $SRC - O2R."
 		FAILURE=1
 	fi
 
-	if ! $CC -nostdlib -I. -g -fno-builtin -O3 -DBUILD_TYPE=2 -DNOTLIBC_IMPLEMENT $SRC -o ./build/tests/o3r/$(basename "$SRC" .c); then
+	if ! $CC $CFLAGS -O3 -DBUILD_TYPE=2 $SRC -o ./build/tests/o3r/$(basename "$SRC" .c); then
 		echo
 		echo "Compilation failure for $SRC - O3R."
 		FAILURE=1
util/limits.h
@@ -0,0 +1,29 @@
+#ifndef NOTLIBC_HEADER_UTIL_LIMITS_INCLUDED
+#define NOTLIBC_HEADER_UTIL_LIMITS_INCLUDED
+
+#include "platform/platform.h"
+
+#define  u8_MIN 0
+#define u16_MIN 0
+#define u32_MIN 0
+#define u64_MIN 0
+
+#define  u8_MAX ((u8)-1)
+#define u16_MAX ((u16)-1)
+#define u32_MAX ((u32)-1)
+#define u64_MAX ((u64)-1)
+
+/* We assume 2's complement */
+ASSERT( (-1^1) == -2 );
+
+#define  s8_MAX ((s8)( (u8)-1 >> 1))
+#define s16_MAX ((s16)((u16)-1 >> 1))
+#define s32_MAX ((s32)((u32)-1 >> 1))
+#define s64_MAX ((s64)((u64)-1 >> 1))
+
+#define  s8_MIN (- s8_MAX - 1)
+#define s16_MIN (-s16_MAX - 1)
+#define s32_MIN (-s32_MAX - 1)
+#define s64_MIN (-s64_MAX - 1)
+
+#endif
util/safemath.h
@@ -1,13 +1,34 @@
 #ifndef NOTLIBC_HEADER_UTIL_SAFEMATH_INCLUDED
 #define NOTLIBC_HEADER_UTIL_SAFEMATH_INCLUDED
 
-#include "../platform/common/compiler.h"
+#include "../platform/platform.h"
+#include "./limits.h"
 
-#define SAFEMATH_HEADER \
-	GCCLIKE("./safemath_gcclike.h") \
-	TCC("./safemath_generic.h") \
-	MSVC("./safemath_generic.h")
 
-#include SAFEMATH_HEADER
+/* Ensure limit sanity - at least for unsigned */
+ASSERT(u64_MAX == 0xffffffffffffffff );
+ASSERT(u32_MAX == 0xffffffff );
+ASSERT(u16_MAX == 0xffff );
+ASSERT(u8_MAX == 0xff );
+
+static inline Status  u8_add(u8 n1, u8 n2, u8 *out);
+static inline Status u16_add(u16 n1, u16 n2, u16 *out);
+static inline Status u32_add(u32 n1, u32 n2, u32 *out);
+static inline Status u64_add(u64 n1, u64 n2, u64 *out);
+
+static inline Status  s8_add(s8 n1, s8 n2, s8 *out);
+static inline Status s16_add(s16 n1, s16 n2, s16 *out);
+static inline Status s32_add(s32 n1, s32 n2, s32 *out);
+static inline Status s64_add(s64 n1, s64 n2, s64 *out);
+
+
+#ifdef NOTLIBC_IMPLEMENT
+	#define SAFEMATH_IMPL_HEADER \
+		GCCLIKE("./safemath_gcclike.h") \
+		TCC("./safemath_generic.h") \
+		MSVC("./safemath_generic.h")
+
+	#include SAFEMATH_IMPL_HEADER
+#endif
 
 #endif
util/safemath_gcclike.h
@@ -11,6 +11,14 @@
 		return S_SUCCESS; \
 	} \
 
+#define GEN_SUB(T) \
+	static inline Status T##_sub( T n1, T n2, T*out ) { \
+		if( __builtin_sub_overflow(n1, n2, out) ) { \
+			return S_FAILURE | ERR_OVERFLOW;\
+		} \
+		return S_SUCCESS; \
+	} \
+
 
 GEN_ADD(u8)
 GEN_ADD(u16)
@@ -22,4 +30,14 @@ GEN_ADD(s16)
 GEN_ADD(s32)
 GEN_ADD(s64)
 
+GEN_SUB(u8)
+GEN_SUB(u16)
+GEN_SUB(u32)
+GEN_SUB(u64)
+
+GEN_SUB(s8)
+GEN_SUB(s16)
+GEN_SUB(s32)
+GEN_SUB(s64)
+
 #endif
util/safemath_generic.h
@@ -0,0 +1,68 @@
+#ifndef NOTLIBC_HEADER_UTIL_SAFEMATH_GENERIC_INCLUDED
+#define NOTLIBC_HEADER_UTIL_SAFEMATH_GENERIC_INCLUDED
+
+#include "../platform/platform.h"
+#include "util/limits.h"
+
+#define GEN_ADDU(T) \
+	static inline Status T##_add( T n1, T n2, T*out ) { \
+		if( n1 > (T##_MAX) - n2 ) {                    \
+			return S_FAILURE | ERR_OVERFLOW;          \
+		}                                              \
+		*out = n1 + n2;                                \
+		return S_SUCCESS;                              \
+	}
+
+GEN_ADDU(u8)
+GEN_ADDU(u16)
+GEN_ADDU(u32)
+GEN_ADDU(u64)
+
+#define GEN_ADDS(T) \
+	static inline Status T##_add( T n1, T n2, T*out ) { \
+		if( (n1 >= 0 && n2 > T##_MAX - n1)             \
+		 || (n1 < 0 && n2 < T##_MIN - n1) )            \
+		{                                              \
+			return S_FAILURE | ERR_OVERFLOW;          \
+		}                                              \
+		*out = n1 + n2;                                \
+		return S_SUCCESS;                              \
+	}
+
+GEN_ADDS(s8)
+GEN_ADDS(s16)
+GEN_ADDS(s32)
+GEN_ADDS(s64)
+
+
+#define GEN_SUBU(T) \
+	static inline Status T##_sub( T n1, T n2, T*out ) { \
+		if( n2 > n1 ) {                                \
+			return S_FAILURE | ERR_OVERFLOW;          \
+		}                                              \
+		*out = n1 - n2;                                \
+		return S_SUCCESS;                              \
+	}
+
+GEN_SUBU(u8)
+GEN_SUBU(u16)
+GEN_SUBU(u32)
+GEN_SUBU(u64)
+
+#define GEN_SUBS(T) \
+	static inline Status T##_sub( T n1, T n2, T*out ) { \
+		if( (n2 > 0 && n1 < T##_MIN + n2)              \
+		 || (n2 < 0 && n1 > T##_MAX + n2) )            \
+		{                                              \
+			return S_FAILURE | ERR_OVERFLOW;          \
+		}                                              \
+		*out = n1 - n2;                                \
+		return S_SUCCESS;                              \
+	}
+
+GEN_SUBS(s8)
+GEN_SUBS(s16)
+GEN_SUBS(s32)
+GEN_SUBS(s64)
+
+#endif