Commit 6f9ca10

kravantokh <kravantokh@noreply.codeberg.org>
2026-07-17 17:58:05
Add entrypoint and tests. Fix some mistakes.
1 parent 005854e
platform/common/compiler.h
@@ -139,7 +139,7 @@ typedef struct _##name { \
 
 #if COMPILER == COMPILER_GCC || COMPILER == COMPILER_CLANG
 	#define UNREACHABLE __builtin_unreachable()
-#elif COMPILER == MSVC
+#elif COMPILER == COPMILER_MSVC
 	#define UNREACHABLE __assume(0)
 #endif
 
platform/common/corefunc.h
@@ -0,0 +1,15 @@
+#ifndef NOTLIBC_HEADER_PLATFORM_COREFUNC_INCLUDED
+#define NOTLIBC_HEADER_PLATFORM_COREFUNC_INCLUDED
+
+static inline void terminate(u32 code); /* aka exit */
+static inline void terminate_forced(void); /* force exit, fault, anything that terminates forcefully */
+
+static inline void breakpoint(void); /* raise a debug breakpoint, if possible on the target */
+
+static inline void assert( usize condition ) {
+	if( !condition ) {
+		breakpoint();
+	}
+}
+
+#endif
platform/common/error.h
@@ -0,0 +1,63 @@
+#ifndef NOTLIBC_HEADER_PLATFORM_ERROR_INCLUDED
+#define NOTLIBC_HEADER_PLATFORM_ERROR_INCLUDED
+
+typedef u32 Status;
+
+#define S_MASK (3 << 30)
+
+#define S_SUCCESS (1 << 30) /* Requested action performed. */
+#define S_COMPLETION (0 << 30) /* Action didn't need to be performed - desired state is already present/active. */
+#define S_FAILURE (2 << 30) /* Action failed. */
+
+#define S_SOURCE_MASK (0x3ff << 20)
+enum _ErrorSources {
+
+	ERR_ARG_1 =  (0x1 << 20),
+	ERR_ARG_2 =  (0x2 << 20),
+	ERR_ARG_3 =  (0x3 << 20),
+	ERR_ARG_4 =  (0x4 << 20),
+	ERR_ARG_5 =  (0x5 << 20),
+	ERR_ARG_6 =  (0x6 << 20),
+	ERR_ARG_7 =  (0x7 << 20),
+	ERR_ARG_8 =  (0x8 << 20),
+	ERR_ARG_9 =  (0x9 << 20),
+	ERR_ARG_10 = (0xa << 20),
+	ERR_ARG_11 = (0xb << 20),
+	ERR_ARG_12 = (0xc << 20),
+
+	ERR_PLATFORM = (0x10 << 20), /* The error resulted inside platform code. */
+};
+
+#define S_REASON_MASK (0xfffff)
+enum _ErrorReasons {
+	ERR_GENERIC = 0, /* Even numbers are for the implementations, odd are for user-defined errors */
+
+	ERR_INVALID_ARGUMENT = 2,
+
+	ERR_OVERFLOW = 4,
+
+	ERR_OUT_OF_BOUND = 6, /* a value is out of bounds or the operation would violate certain bounds with the current arguments */
+
+	ERR_MEM_MAP_FAILURE = 0x100,
+	ERR_MEM_UNMAP_FAILURE = 0x102,
+	ERR_MEM_SETPERM_FAILURE = 0x104,
+	ERR_MEM_SETHINT_FAILURE = 0x106,
+
+};
+
+
+#define S_OK(...) ( ((__VA_ARGS__) & S_MASK) != (Status)S_FAILURE ) /* != FAILURE, cause otherwise the expression will appear twice, which is a great footgun when it's stateful... we are having none of that here */
+#define S_NOK(...) ( ((__VA_ARGS__) & S_MASK) == (Status)S_FAILURE )
+
+#define S_SUCCESSFUL(...) ( ((__VA_ARGS__) & S_MASK) == (Status)S_SUCCESS )
+#define S_COMPLETED(...) ( ((__VA_ARGS__) & S_MASK) == (Status)S_COMPLETION )
+#define S_FAILED(...) ( ((__VA_ARGS__) & S_MASK) == (Status)S_FAILURE )
+
+#define TRY(...) do {             \
+	Status _res = (__VA_ARGS__); \
+	if( S_NOK(_res) ) {          \
+		return _res;             \
+	}                            \
+} while(0)
+
+#endif
platform/linux/entry.h
@@ -0,0 +1,28 @@
+#ifndef NOTLIBC_HEADER_PLATFORM_LINUX_ENTRY_INCLUDED
+#define NOTLIBC_HEADER_PLATFORM_LINUX_ENTRY_INCLUDED
+
+#include "../common/build.h"
+#include "./coretypes.h"
+
+u8* linux_startup_sp = 0;
+
+void entry(void); /* aka main */
+
+X86_64(
+	__asm__ (
+		".section .text\n"
+		".global _start\n"
+		".type _start, @function\n"
+		"_start:\n"
+			"leaq linux_startup_sp(%rip), %rax\n"
+			"movq %rsp, (%rax)\n" /* save original stack pointer */
+			"xor %rax, %rax\n"
+			"call entry\n"
+	);
+)
+
+I386(
+	ERROR("i386 entrypoint not implemented")
+)
+
+#endif
platform/linux/linux_types.h
@@ -1,3 +0,0 @@
-#include "./coretypes.h"
-#include "../common/compiler.h"
-
platform/linux/syscall_wrappers_generic.h
@@ -3,88 +3,86 @@
 
 #include "./coretypes.h"
 
-
-X86_64(
-
+#if ARCH == ARCH_X86_64
 	static inline isize linux_syscall1(usize num, usize arg1) {
 		__asm__ volatile (
-			"mov %[a1], %%rdi\n"
+			"movq %[a1], %%rdi\n"
 			"syscall\n"
 			: [num]"+a"(num)
-			: [a1]"r"(arg1)
-			: "rcx", "r11", "memory"
+			: [a1]"m"(arg1) /* memory operands are forced, because otherwise tcc does not generate correct inline asm - it uses mm registers, which have bogus values... */
+			: "rcx", "r11", "memory", "rdi"
 		);
 		return num;
 	}
 
 	static inline isize linux_syscall2(usize num, usize arg1, usize arg2) {
 		__asm__ volatile (
-			"mov %[a1], %%rdi\n"
-			"mov %[a2], %%rsi\n"
+			"movq %[a1], %%rdi\n"
+			"movq %[a2], %%rsi\n"
 			"syscall\n"
 			: [num]"+a"(num)
-			: [a1]"r"(arg1), [a2]"r"(arg2)
-			: "rcx", "r11", "memory"
+			: [a1]"m"(arg1), [a2]"m"(arg2)
+			: "rcx", "r11", "memory", "rdi", "rsi"
 		);
 		return num;
 	}
 	static inline isize linux_syscall3(usize num, usize arg1, usize arg2, usize arg3) {
 		__asm__ volatile (
-			"mov %[a1], %%rdi\n"
-			"mov %[a2], %%rsi\n"
-			"mov %[a3], %%rdx\n"
+			"movq %[a1], %%rdi\n"
+			"movq %[a2], %%rsi\n"
+			"movq %[a3], %%rdx\n"
 			"syscall\n"
 			: [num]"+a"(num)
-			: [a1]"r"(arg1), [a2]"r"(arg2), [a3]"r"(arg3)
-			: "rcx", "r11", "memory"
+			: [a1]"m"(arg1), [a2]"m"(arg2), [a3]"m"(arg3)
+			: "rcx", "r11", "memory", "rdi", "rsi", "rdx"
 		);
 		return num;
 	}
 	static inline isize linux_syscall4(usize num, usize arg1, usize arg2, usize arg3, usize arg4) {
 		__asm__ volatile (
-			"mov %[a1], %%rdi\n"
-			"mov %[a2], %%rsi\n"
-			"mov %[a3], %%rdx\n"
-			"mov %[a4], %%r10\n"
+			"movq %[a1], %%rdi\n"
+			"movq %[a2], %%rsi\n"
+			"movq %[a3], %%rdx\n"
+			"movq %[a4], %%r10\n"
 			"syscall\n"
 			: [num]"+a"(num)
-			: [a1]"r"(arg1), [a2]"r"(arg2), [a3]"r"(arg3), [a4]"r"(arg4)
-			: "rcx", "r11", "memory"
+			: [a1]"m"(arg1), [a2]"m"(arg2), [a3]"m"(arg3), [a4]"m"(arg4)
+			: "rcx", "r11", "memory", "rdi", "rsi", "rdx", "r10"
 		);
 		return num;
 	}
 	static inline isize linux_syscall5(usize num, usize arg1, usize arg2, usize arg3, usize arg4, usize arg5) {
 		__asm__ volatile (
-			"mov %[a1], %%rdi\n"
-			"mov %[a2], %%rsi\n"
-			"mov %[a3], %%rdx\n"
-			"mov %[a4], %%r10\n"
-			"mov %[a5], %%r8\n"
+			"movq %[a1], %%rdi\n"
+			"movq %[a2], %%rsi\n"
+			"movq %[a3], %%rdx\n"
+			"movq %[a4], %%r10\n"
+			"movq %[a5], %%r8\n"
 			"syscall\n"
 			: [num]"+a"(num)
-			: [a1]"r"(arg1), [a2]"r"(arg2), [a3]"r"(arg3), [a4]"r"(arg4), [a5]"r"(arg5)
-			: "rcx", "r11", "memory"
+			: [a1]"m"(arg1), [a2]"m"(arg2), [a3]"m"(arg3), [a4]"m"(arg4), [a5]"m"(arg5)
+			: "rcx", "r11", "memory", "rdi", "rsi", "rdx", "r10", "r8"
 		);
 		return num;
 	}
 
 	static inline isize linux_syscall6(usize num, usize arg1, usize arg2, usize arg3, usize arg4, usize arg5, usize arg6) {
 		__asm__ volatile (
-			"mov %[a1], %%rdi\n"
-			"mov %[a2], %%rsi\n"
-			"mov %[a3], %%rdx\n"
-			"mov %[a4], %%r10\n"
-			"mov %[a5], %%r8\n"
-			"mov %[a6], %%r9\n"
+			"movq %[a1], %%rdi\n"
+			"movq %[a2], %%rsi\n"
+			"movq %[a3], %%rdx\n"
+			"movq %[a4], %%r10\n"
+			"movq %[a5], %%r8\n"
+			"movq %[a6], %%r9\n"
 			"syscall\n"
 			: [num]"+a"(num)
-			: [a1]"r"(arg1), [a2]"r"(arg2), [a3]"r"(arg3), [a4]"r"(arg4), [a5]"r"(arg5), [a6]"r"(arg6)
-			: "rcx", "r11", "memory"
+			: [a1]"m"(arg1), [a2]"m"(arg2), [a3]"m"(arg3), [a4]"m"(arg4), [a5]"m"(arg5), [a6]"m"(arg6)
+			: "rcx", "r11", "memory", "rdi", "rsi", "rdx", "r10", "r8", "r9"
 		);
 		return num;
 	}
+#endif
 
-)
 
 I386(
 	ERROR("Linux syscalls not implemented for I386.");
platform/linux/syscalls.h
@@ -64,7 +64,7 @@ static inline isize linux_munmap( usize start, usize len ) {
 }
 
 static inline isize linux_mprotect( usize start, usize len, usize prot ) {
-	return linux_syscall3( 11, start, len, prot );
+	return linux_syscall3( 10, start, len, prot );
 }
 
 
platform/platform.h
@@ -10,9 +10,10 @@
 	LINUX("linux/coretypes.h") \
 	WINDOWS("windows/coretypes.h")
 
-/* Basic types - with sanity checks */
+/* Basic types */
 #include HEADER_PLATFORM_CORETYPES
 
+/* Type sanity checks */
 ASSERT( sizeof( u8 ) == 1 );
 ASSERT( sizeof( u16 ) == 2 );
 ASSERT( sizeof( u32 ) == 4 );
@@ -26,84 +27,23 @@ ASSERT( sizeof( s64 ) == 8 );
 ASSERT( sizeof( f32 ) == 4 );
 ASSERT( sizeof( f64 ) == 8 );
 
-/* Error handling types and macros. */
-
-typedef u32 Status;
-
-#define S_MASK (3 << 30)
-
-#define S_SUCCESS (1 << 30) /* Requested action performed. */
-#define S_COMPLETION (0 << 30) /* Action didn't need to be performed - desired state is already present/active. */
-#define S_FAILURE (2 << 30) /* Action failed. */
-
-#define S_SOURCE_MASK (0x3ff << 20)
-enum _ErrorSources {
-
-	ERR_ARG_1 =  (0x1 << 20),
-	ERR_ARG_2 =  (0x2 << 20),
-	ERR_ARG_3 =  (0x3 << 20),
-	ERR_ARG_4 =  (0x4 << 20),
-	ERR_ARG_5 =  (0x5 << 20),
-	ERR_ARG_6 =  (0x6 << 20),
-	ERR_ARG_7 =  (0x7 << 20),
-	ERR_ARG_8 =  (0x8 << 20),
-	ERR_ARG_9 =  (0x9 << 20),
-	ERR_ARG_10 = (0xa << 20),
-	ERR_ARG_11 = (0xb << 20),
-	ERR_ARG_12 = (0xc << 20),
-
-	ERR_PLATFORM = (0x10 << 20), /* The error resulted inside platform code. */
-};
-
-#define S_REASON_MASK (0xfffff)
-enum _ErrorReasons {
-	ERR_GENERIC = 0, /* Even numbers are for the implementations, odd are for user-defined errors */
-
-	ERR_INVALID_ARGUMENT = 2,
-
-	ERR_OVERFLOW = 4,
-
-	ERR_OUT_OF_BOUND = 6, /* a value is out of bounds or the operation would violate certain bounds with the current arguments */
-
-	ERR_MEM_MAP_FAILURE = 0x100,
-	ERR_MEM_UNMAP_FAILURE = 0x102,
-	ERR_MEM_SETPERM_FAILURE = 0x104,
-	ERR_MEM_SETHINT_FAILURE = 0x106,
-
-};
-
-
-#define S_OK(...) ( ((__VA_ARGS__) & S_MASK) != (Status)S_FAILURE ) /* != FAILURE, cause otherwise the expression will appear twice, which is a great footgun when it's stateful... we are having none of that here */
-#define S_NOK(...) ( ((__VA_ARGS__) & S_MASK) == (Status)S_FAILURE )
-
-#define S_SUCCESSFUL(...) ( ((__VA_ARGS__) & S_MASK) == (Status)S_SUCCESS )
-#define S_COMPLETED(...) ( ((__VA_ARGS__) & S_MASK) == (Status)S_COMPLETION )
-#define S_FAILED(...) ( ((__VA_ARGS__) & S_MASK) == (Status)S_FAILURE )
-
-#define TRY(...) do {             \
-	Status _res = (__VA_ARGS__); \
-	if( S_NOK(_res) ) {          \
-		return _res;             \
-	}                            \
-} while(0)
-
 /* Core platform functions */
+#include "./common/corefunc.h"
 
-static inline void terminate(u32 code); /* aka exit */
-static inline void terminate_forced(void); /* force exit, fault, anything that terminates forcefully */
+#define HEADER_PLATFORM_COREFUNC   \
+	LINUX("./linux/corefunc.h")     \
+	WINDOWS("./windows/corefunc.h")
 
-static inline void breakpoint(void); /* raise a debug breakpoint, if possible on the target */
+#include HEADER_PLATFORM_COREFUNC
 
-static inline void assert( usize condition ) {
-	if( !condition ) {
-		breakpoint();
-	}
-}
+/* Platform-dependent entrypoint function */
+#define HEADER_PLATFORM_ENTRY \
+	LINUX("./linux/entry.h") \
+	WINDOWS("./windows/entry.h")
 
-#define HEADER_PLATFORM_COREFUNC   \
-	LINUX("linux/corefunc.h")     \
-	WINDOWS("windows/corefunc.h")
+#include HEADER_PLATFORM_ENTRY
 
-#include HEADER_PLATFORM_COREFUNC
+/* Error handling types and macros. */
+#include "./common/error.h"
 
 #endif
testing/tests/000_entry_exit.c
@@ -0,0 +1,5 @@
+#include "platform/platform.h"
+
+void entry(void) {
+	terminate(0);
+}
testing/tests/001_mem_basic.c
@@ -0,0 +1,35 @@
+#include "memory/memory.h"
+#include "platform/platform.h"
+
+#define PAGECOUNT 2
+
+void entry(void) {
+	Page *mem = 0;
+
+	assert(S_OK(
+		mem_claim( PAGECOUNT, &mem )
+	));
+
+	assert( mem != 0 );
+
+	assert(S_OK(
+		mem_occupy( mem, PAGECOUNT )
+	));
+
+	mem_set( mem, PAGECOUNT*sizeof(Page), 0xde );
+
+	for( usize i = 0; i < PAGECOUNT*sizeof(Page); ++i ) {
+		assert( *(u8*)mem == 0xde );
+	}
+
+	assert(S_OK(
+		mem_vacate( mem, PAGECOUNT )
+	));
+
+	assert(S_OK(
+		mem_relinquish( mem, PAGECOUNT )
+	));
+
+
+	terminate(0);
+}
testing/test.sh
@@ -0,0 +1,80 @@
+#!/bin/bash
+
+FAILED=0
+
+rm -rf ./build/tests/*
+mkdir -p ./build/tests
+
+if [[ ! -v CC ]]; then
+	CC=cc
+fi
+
+# Debug test builds on Og, O2, O3 and release test builds on O2 and O3
+TESTDIRS="o0d ogd o2d o3d o2r o3r"
+
+for DIR in $TESTDIRS; do
+	mkdir -p "./build/tests/$DIR"
+done
+
+echo
+echo Compiling tests.
+echo
+
+for SRC in ./testing/tests/*.c; do
+
+	if ! $CC -nostdlib -I. -g -fno-builtin -O0 -DBUILD_TYPE=1 -DNOTLIBC_IMPLEMENT -fno-stack-protector -static $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
+		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
+		echo
+		echo "Compilation failure for $SRC - O2D."
+		FAILURE=1
+	fi
+
+	if ! $CC -nostdlib -I. -g -fno-builtin -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 $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 $SRC -o ./build/tests/o3r/$(basename "$SRC" .c); then
+		echo
+		echo "Compilation failure for $SRC - O3R."
+		FAILURE=1
+	fi
+done
+
+echo
+echo Running tests.
+echo
+
+for DIR in $TESTDIRS; do
+	for TEST in "./build/tests/$DIR/"*; do
+		"$TEST"
+		RET=$?
+
+		if [ "$RET" -ne 0 ]; then
+			echo "FAILURE: $(basename $TEST)@$(basename $DIR):$RET (./testing/tests/$(basename $TEST).c)"
+			FAILURE=1
+		fi
+	done
+done
+
+exit $FAILURE
+
+
.gitignore
@@ -2,3 +2,4 @@
 Session.vim
 main.c
 compile_flags.txt
+build