diff --git a/lib/std/core/builtin.c3 b/lib/std/core/builtin.c3 index b5d88304a..8833e690f 100644 --- a/lib/std/core/builtin.c3 +++ b/lib/std/core/builtin.c3 @@ -57,6 +57,10 @@ faultdef TYPE_MISMATCH @builtin; Use `CAPACITY_EXCEEDED` when trying to add to a bounded list or similar. */ faultdef CAPACITY_EXCEEDED @builtin; +/* + Use `NOT_IMPLEMENTED` when something is conditionally available. +*/ +faultdef NOT_IMPLEMENTED @builtin; alias VoidFn = fn void(); diff --git a/lib/std/core/logging.c3 b/lib/std/core/logging.c3 new file mode 100644 index 000000000..bccf09361 --- /dev/null +++ b/lib/std/core/logging.c3 @@ -0,0 +1,217 @@ +module std::core::log; +import std::io, std::thread, std::time, std::math::random; + +const FULL_LOG = env::COMPILER_SAFE_MODE || $feature(FULL_LOG); + +typedef LogCategory = inline char; +typedef LogTag = char[12]; + +const LogCategory CATEGORY_APPLICATION = 0; +const LogCategory CATEGORY_SYSTEM = 1; +const LogCategory CATEGORY_KERNEL = 2; +const LogCategory CATEGORY_AUDIO = 3; +const LogCategory CATEGORY_VIDEO = 4; +const LogCategory CATEGORY_RENDER = 5; +const LogCategory CATEGORY_INPUT = 6; +const LogCategory CATEGORY_NETWORK = 7; +const LogCategory CATEGORY_SOCKET = 8; +const LogCategory CATEGORY_SECURITY = 9; +const LogCategory CATEGORY_TEST = 10; +const LogCategory CATEGORY_ERROR = 11; +const LogCategory CATEGORY_ASSERT = 12; +const LogCategory CATEGORY_CRASH = 13; +const LogCategory CATEGORY_STATS = 14; +const LogCategory CATEGORY_CUSTOM_START = 100; + +tlocal LogCategory default_category = CATEGORY_APPLICATION; +tlocal LogTag current_tag; + + +enum LogPriority : int +{ + VERBOSE, + DEBUG, + INFO, + WARN, + ERROR, + CRITICAL, +} + +interface Logger +{ + fn void log(LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args); +} + +macro void verbose(String fmt, ..., LogCategory category = default_category) => call_log(VERBOSE, category, fmt, $vasplat); +macro void debug(String fmt, ..., LogCategory category = default_category) => call_log(DEBUG, category, fmt, $vasplat); +macro void info(String fmt, ..., LogCategory category = default_category) => call_log(INFO, category, fmt, $vasplat); +macro void warn(String fmt, ..., LogCategory category = default_category) => call_log(WARN, category, fmt, $vasplat); +macro void error(String fmt, ..., LogCategory category = default_category) => call_log(ERROR, category, fmt, $vasplat); +macro void critical(String fmt, ..., LogCategory category = default_category) => call_log(CRITICAL, category, fmt, $vasplat); + +macro void @category_scope(LogCategory new_category; @body) +{ + LogCategory old = default_category; + default_category = new_category; + defer default_category = old; + @body(); +} + +<* + @require tag_prefix.len <= 3 : "The prefix may not exceed 3 bytes" +*> +macro void @tag_scope(String tag_prefix = ""; @body) +{ + LogTag old = current_tag; + push_tag(tag_prefix); + defer current_tag = old; + @body(); +} + +<* + @require tag_prefix.len <= 3 : "The prefix may not exceed 3 bytes" +*> +macro void push_tag(String tag_prefix = "") +{ + current_tag = create_tag(tag_prefix); +} + +<* + @require tag_prefix.len <= 3 : "The prefix may not exceed 3 bytes" +*> +fn LogTag create_tag(String tag_prefix) +{ + LogTag tag @noinit; + int start = 0; + foreach (int i, c : tag_prefix) + { + if (c == 0) break; + tag[start++] = c; + } + if (start > 0) tag[start++] = '_'; + for (int i = start; i < tag.len; i++) + { + tag[i] = (char)rand_in_range('a', 'z'); + } + return tag; +} + +fn void set_priority_for_category(LogCategory category, LogPriority new_priority) +{ + @atomic_store(config_priorities[category], new_priority, UNORDERED); +} + +fn LogPriority get_priority_for_category(LogCategory category) +{ + return @atomic_load(config_priorities[category], UNORDERED); +} + +fn void set_priority_all(LogPriority new_priority) +{ + for (int i = 0; i < config_priorities.len; i++) + { + @atomic_store(config_priorities[i], new_priority, UNORDERED); + } +} +fn void set_logger(Logger logger) +{ + init(); + if (!logger_mutex.is_initialized()) + { + current_logger = logger; + current_logfn = &logger.log; + return; + } + logger_mutex.@in_lock() + { + current_logger = logger; + current_logfn = &logger.log; + }; +} + +macro void init() +{ + log_init.call(fn () => (void)logger_mutex.init()); +} + +fn void call_log(LogPriority prio, LogCategory category, String fmt, args...) +{ + LogPriority priority = mem::@atomic_load(config_priorities[category], UNORDERED); + if (priority < prio) return; + init(); + bool locked = logger_mutex.is_initialized() && @ok(logger_mutex.lock()); + Logger logger = current_logger; + LogFn logfn = current_logfn; + defer if (locked) (void)logger_mutex.unlock(); + $if FULL_LOG: + logfn(logger.ptr, prio, category, current_tag, $$FILE, $$FUNC, $$LINE, fmt, args); + $else + logfn(logger.ptr, prio, category, current_tag, "", "", 0, fmt, args); + $endif +} + +fn String? get_category_name(LogCategory category) +{ + String val = category_names[category]; + return val ?: NOT_FOUND?; +} + +fn void set_category_name(LogCategory category, String name) +{ + category_names[category] = name; +} + +struct NullLogger (Logger) +{ + void* dummy; +} + +fn void NullLogger.log(&self, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args) @dynamic +{} + + +module std::core::log @private; +import std::io, std::thread, std::time; + +struct StderrLogger (Logger) @if(env::LIBC) +{ + void* dummy; +} + +fn void StderrLogger.log(&self, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args) @dynamic @if(env::LIBC) +{ + @stack_mem(256 + 64; Allocator mem) + { + DString str; + str.init(mem, 256); + str.appendf(fmt, ...args); + TzDateTime time = datetime::now().to_local(); + io::eprintfn("[%02d:%02d:%02d:%04d] [%s] %s", time.hour, time.min, time.sec, (time.usec / 1000), priority, str); + }; +} + +alias LogFn = fn void(void*, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args); +LogFn current_logfn = @select(env::LIBC, (LogFn)&StderrLogger.log, (LogFn)&NullLogger.log); +OnceFlag log_init; +Mutex logger_mutex; +Logger current_logger = @select(env::LIBC, &stderr_logger, &null_logger); +StderrLogger stderr_logger @if (env::LIBC); +NullLogger null_logger; +LogPriority[256] config_priorities = { [0..255] = ERROR, [CATEGORY_APPLICATION] = INFO, [CATEGORY_TEST] = VERBOSE, [CATEGORY_ASSERT] = WARN}; +String[256] category_names = { + [CATEGORY_APPLICATION] = "APP", + [CATEGORY_SYSTEM] = "SYSTEM", + [CATEGORY_KERNEL] = "KERNEL", + [CATEGORY_AUDIO] = "AUDIO", + [CATEGORY_VIDEO] = "VIDEO", + [CATEGORY_RENDER] = "RENDER", + [CATEGORY_INPUT] = "INPUT", + [CATEGORY_NETWORK] = "NETWORD", + [CATEGORY_SOCKET] = "SOCKET", + [CATEGORY_SECURITY] = "SECURITY", + [CATEGORY_TEST] = "TEST", + [CATEGORY_ERROR] = "ERROR", + [CATEGORY_ASSERT] = "ASSERT", + [CATEGORY_CRASH] = "CRASH", + [CATEGORY_STATS] = "STATS" +}; diff --git a/lib/std/io/io.c3 b/lib/std/io/io.c3 index b48f4fddd..b0d0daaa4 100644 --- a/lib/std/io/io.c3 +++ b/lib/std/io/io.c3 @@ -254,7 +254,7 @@ macro void eprint(x) @param x : "The value to print" *> -macro void eprintn(x) +macro void eprintn(x = "") { (void)fprintn(io::stderr(), x); } diff --git a/lib/std/threads/os/thread_none.c3 b/lib/std/threads/os/thread_none.c3 index 3509433a2..9148d58e5 100644 --- a/lib/std/threads/os/thread_none.c3 +++ b/lib/std/threads/os/thread_none.c3 @@ -4,4 +4,24 @@ typedef NativeMutex = int; typedef NativeTimedMutex = int; typedef NativeConditionVariable = int; typedef NativeOnceFlag = int; -typedef NativeThread = int; \ No newline at end of file +typedef NativeThread = int; + +fn void NativeOnceFlag.call_once(&flag, OnceFn func) +{ + if (*flag == 0) + { + *flag = 1; + func(); + } +} + +fn void? NativeMutex.init(&mtx, MutexType type) => NOT_IMPLEMENTED?; + +fn bool NativeMutex.is_initialized(&self) +{ + return false; +} + +macro void? NativeMutex.lock(&mutex) => NOT_IMPLEMENTED?; +macro bool NativeMutex.try_lock(&mutex) => NOT_IMPLEMENTED?; +macro void? NativeMutex.unlock(&mutex) => NOT_IMPLEMENTED?; \ No newline at end of file diff --git a/lib/std/threads/os/thread_win32.c3 b/lib/std/threads/os/thread_win32.c3 index a3f0cfc05..45107ee2d 100644 --- a/lib/std/threads/os/thread_win32.c3 +++ b/lib/std/threads/os/thread_win32.c3 @@ -15,6 +15,11 @@ struct NativeMutex } } +fn bool NativeMutex.is_initialized(&self) +{ + return self.initialized; +} + struct NativeTimedMutex { Win32_SRWLOCK srw_lock; diff --git a/lib/std/threads/thread.c3 b/lib/std/threads/thread.c3 index 5103f18ec..693dd8ef6 100644 --- a/lib/std/threads/thread.c3 +++ b/lib/std/threads/thread.c3 @@ -34,6 +34,7 @@ faultdef CHANNEL_CLOSED; macro void? Mutex.init(&mutex) => NativeMutex.init((NativeMutex*)mutex, {}); +macro bool Mutex.is_initialized(mutex) => ((NativeMutex*)&mutex).is_initialized(); macro void? RecursiveMutex.init(&mutex) => NativeMutex.init((NativeMutex*)mutex, {.recursive}); macro void? Mutex.destroy(&mutex) => NativeMutex.destroy((NativeMutex*)mutex); macro void? Mutex.lock(&mutex) => NativeMutex.lock((NativeMutex*)mutex); diff --git a/releasenotes.md b/releasenotes.md index db609263b..db57d140f 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -26,6 +26,7 @@ - Add `vm::mmap_file` to memory map a file. - Updated hash functions in default hash methods. - Added `FixedBlockPool` which is a memory pool for fixed size blocks. +- Added the experimental `std::core::log` for logging. ## 0.7.4 Change list diff --git a/test/test_suite/stdlib/logging.c3t b/test/test_suite/stdlib/logging.c3t new file mode 100644 index 000000000..5e86048ea --- /dev/null +++ b/test/test_suite/stdlib/logging.c3t @@ -0,0 +1,54 @@ +// #target: macos-x64 +module test; +import std; + +fn void main() +{ + log::@category_scope(log::CATEGORY_KERNEL) + { + log::@tag_scope("YO") + { + log::info("Hello"); + }; + }; +} + +/* #expect: test.ll + +define void @test.main() #0 { +entry: + %old = alloca i8, align 1 + %old1 = alloca [12 x i8], align 1 + %result = alloca [12 x i8], align 1 + %tempcoerce = alloca { i64, i32 }, align 8 + %category = alloca i8, align 1 + %0 = call ptr @llvm.threadlocal.address.p0(ptr @std.core.log.default_category) + %1 = load i8, ptr %0, align 1 + store i8 %1, ptr %old, align 1 + %2 = call ptr @llvm.threadlocal.address.p0(ptr @std.core.log.default_category) + store i8 2, ptr %2, align 1 + call void @llvm.assume(i1 true) + %3 = call ptr @llvm.threadlocal.address.p0(ptr @std.core.log.current_tag) + call void @llvm.memcpy.p0.p0.i32(ptr align 1 %old1, ptr align 1 %3, i32 12, i1 false) + call void @llvm.assume(i1 true) + %4 = call ptr @llvm.threadlocal.address.p0(ptr @std.core.log.current_tag) + %5 = call { i64, i32 } @std.core.log.create_tag(ptr @.str, i64 2) + store { i64, i32 } %5, ptr %tempcoerce, align 8 + call void @llvm.memcpy.p0.p0.i32(ptr align 1 %result, ptr align 8 %tempcoerce, i32 12, i1 false) + call void @llvm.memcpy.p0.p0.i32(ptr align 1 %4, ptr align 1 %result, i32 12, i1 false) + %6 = call ptr @llvm.threadlocal.address.p0(ptr @std.core.log.default_category) + %7 = load i8, ptr %6, align 1 + store i8 %7, ptr %category, align 1 + %8 = load i8, ptr %category, align 1 + call void @std.core.log.call_log(i32 2, i8 zeroext %8, ptr @.str.1, i64 5, ptr null, i64 0) + %9 = call ptr @llvm.threadlocal.address.p0(ptr @std.core.log.current_tag) + call void @llvm.memcpy.p0.p0.i32(ptr align 1 %9, ptr align 1 %old1, i32 12, i1 false) + %10 = call ptr @llvm.threadlocal.address.p0(ptr @std.core.log.default_category) + %11 = load i8, ptr %old, align 1 + store i8 %11, ptr %10, align 1 + ret void +} + +declare extern_weak { i64, i32 } @std.core.log.create_tag(ptr, i64) #0 + +declare extern_weak void @std.core.log.call_log(i32, i8 zeroext, ptr, i64, ptr, i64) #0