From 90fa8bc56fd4c3870a9041e39fc156bfacdcd1b1 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sun, 14 Dec 2025 17:00:13 +0200 Subject: [PATCH] builtin: split builtin.v and builtin.c.v into smaller, and more focused .v files, to ease working on unrelated subsystems (#25965) --- vlib/builtin/allocation.c.v | 509 +++++++++++++++++++ vlib/builtin/builtin.c.v | 916 +--------------------------------- vlib/builtin/builtin.v | 123 ----- vlib/builtin/meta_assert.v | 53 ++ vlib/builtin/meta_attribute.v | 17 + vlib/builtin/meta_enum.v | 8 + vlib/builtin/meta_function.v | 18 + vlib/builtin/meta_struct.v | 27 + vlib/builtin/meta_variant.v | 6 + vlib/builtin/panicing.c.v | 166 ++++++ vlib/builtin/printing.c.v | 195 ++++++++ 11 files changed, 1025 insertions(+), 1013 deletions(-) create mode 100644 vlib/builtin/allocation.c.v create mode 100644 vlib/builtin/meta_assert.v create mode 100644 vlib/builtin/meta_attribute.v create mode 100644 vlib/builtin/meta_enum.v create mode 100644 vlib/builtin/meta_function.v create mode 100644 vlib/builtin/meta_struct.v create mode 100644 vlib/builtin/meta_variant.v create mode 100644 vlib/builtin/panicing.c.v create mode 100644 vlib/builtin/printing.c.v diff --git a/vlib/builtin/allocation.c.v b/vlib/builtin/allocation.c.v new file mode 100644 index 000000000..d142cffb0 --- /dev/null +++ b/vlib/builtin/allocation.c.v @@ -0,0 +1,509 @@ +@[has_globals] +module builtin + +// v_memory_panic will be true, *only* when a call to malloc/realloc/vcalloc etc could not succeed. +// In that situation, functions that are registered with at_exit(), should be able to limit their +// activity accordingly, by checking this flag. +// The V compiler itself for example registers a function with at_exit(), for showing timers. +// Without a way to distinguish, that we are in a memory panic, that would just display a second panic, +// which would be less clear to the user. +__global v_memory_panic = false + +@[noreturn] +fn _memory_panic(fname string, size isize) { + v_memory_panic = true + // Note: do not use string interpolation here at all, since string interpolation itself allocates + eprint(fname) + eprint('(') + $if freestanding || vinix { + eprint('size') // TODO: use something more informative here + } $else { + C.fprintf(C.stderr, c'%p', voidptr(size)) + } + if size < 0 { + eprint(' < 0') + } + eprintln(')') + panic('memory allocation failure') +} + +__global total_m = i64(0) +// malloc dynamically allocates a `n` bytes block of memory on the heap. +// malloc returns a `byteptr` pointing to the memory address of the allocated space. +// unlike the `calloc` family of functions - malloc will not zero the memory block. +@[unsafe] +pub fn malloc(n isize) &u8 { + $if trace_malloc ? { + total_m += n + C.fprintf(C.stderr, c'_v_malloc %6d total %10d\n', n, total_m) + // print_backtrace() + } + if n < 0 { + _memory_panic(@FN, n) + } else if n == 0 { + return &u8(unsafe { nil }) + } + mut res := &u8(unsafe { nil }) + $if prealloc { + return unsafe { prealloc_malloc(n) } + } $else $if gcboehm ? { + unsafe { + res = C.GC_MALLOC(n) + } + } $else $if freestanding { + // todo: is this safe to call malloc there? We export __malloc as malloc and it uses dlmalloc behind the scenes + // so theoretically it is safe + res = unsafe { __malloc(usize(n)) } + } $else { + $if windows { + // Warning! On windows, we always use _aligned_malloc to allocate memory. + // This ensures that we can later free the memory with _aligned_free + // without needing to track whether the memory was originally allocated + // by malloc or _aligned_malloc. + res = unsafe { C._aligned_malloc(n, 1) } + } $else { + res = unsafe { C.malloc(n) } + } + } + if res == 0 { + _memory_panic(@FN, n) + } + $if debug_malloc ? { + // Fill in the memory with something != 0 i.e. `M`, so it is easier to spot + // when the calling code wrongly relies on it being zeroed. + unsafe { C.memset(res, 0x4D, n) } + } + return res +} + +@[unsafe] +pub fn malloc_noscan(n isize) &u8 { + $if trace_malloc ? { + total_m += n + C.fprintf(C.stderr, c'malloc_noscan %6d total %10d\n', n, total_m) + // print_backtrace() + } + if n < 0 { + _memory_panic(@FN, n) + } + mut res := &u8(unsafe { nil }) + $if native { + res = unsafe { C.malloc(n) } + } $else $if prealloc { + return unsafe { prealloc_malloc(n) } + } $else $if gcboehm ? { + $if gcboehm_opt ? { + unsafe { + res = C.GC_MALLOC_ATOMIC(n) + } + } $else { + unsafe { + res = C.GC_MALLOC(n) + } + } + } $else $if freestanding { + res = unsafe { __malloc(usize(n)) } + } $else { + $if windows { + // Warning! On windows, we always use _aligned_malloc to allocate memory. + // This ensures that we can later free the memory with _aligned_free + // without needing to track whether the memory was originally allocated + // by malloc or _aligned_malloc. + res = unsafe { C._aligned_malloc(n, 1) } + } $else { + res = unsafe { C.malloc(n) } + } + } + if res == 0 { + _memory_panic(@FN, n) + } + $if debug_malloc ? { + // Fill in the memory with something != 0 i.e. `M`, so it is easier to spot + // when the calling code wrongly relies on it being zeroed. + unsafe { C.memset(res, 0x4D, n) } + } + return res +} + +@[inline] +fn __at_least_one(how_many u64) u64 { + // handle the case for allocating memory for empty structs, which have sizeof(EmptyStruct) == 0 + // in this case, just allocate a single byte, avoiding the panic for malloc(0) + if how_many == 0 { + return 1 + } + return how_many +} + +// malloc_uncollectable dynamically allocates a `n` bytes block of memory +// on the heap, which will NOT be garbage-collected (but its contents will). +@[unsafe] +pub fn malloc_uncollectable(n isize) &u8 { + $if trace_malloc ? { + total_m += n + C.fprintf(C.stderr, c'malloc_uncollectable %6d total %10d\n', n, total_m) + // print_backtrace() + } + if n < 0 { + _memory_panic(@FN, n) + } + + mut res := &u8(unsafe { nil }) + $if prealloc { + return unsafe { prealloc_malloc(n) } + } $else $if gcboehm ? { + unsafe { + res = C.GC_MALLOC_UNCOLLECTABLE(n) + } + } $else $if freestanding { + res = unsafe { __malloc(usize(n)) } + } $else { + $if windows { + // Warning! On windows, we always use _aligned_malloc to allocate memory. + // This ensures that we can later free the memory with _aligned_free + // without needing to track whether the memory was originally allocated + // by malloc or _aligned_malloc. + res = unsafe { C._aligned_malloc(n, 1) } + } $else { + res = unsafe { C.malloc(n) } + } + } + if res == 0 { + _memory_panic(@FN, n) + } + $if debug_malloc ? { + // Fill in the memory with something != 0 i.e. `M`, so it is easier to spot + // when the calling code wrongly relies on it being zeroed. + unsafe { C.memset(res, 0x4D, n) } + } + return res +} + +// v_realloc resizes the memory block `b` with `n` bytes. +// The `b byteptr` must be a pointer to an existing memory block +// previously allocated with `malloc` or `vcalloc`. +// Please, see also realloc_data, and use it instead if possible. +@[unsafe] +pub fn v_realloc(b &u8, n isize) &u8 { + $if trace_realloc ? { + C.fprintf(C.stderr, c'v_realloc %6d\n', n) + } + mut new_ptr := &u8(unsafe { nil }) + $if prealloc { + unsafe { + new_ptr = malloc(n) + C.memcpy(new_ptr, b, n) + } + return new_ptr + } $else $if gcboehm ? { + new_ptr = unsafe { C.GC_REALLOC(b, n) } + } $else { + $if windows { + // Warning! On windows, we always use _aligned_realloc to reallocate memory. + // This ensures that we can later free the memory with _aligned_free + // without needing to track whether the memory was originally allocated + // by malloc or _aligned_malloc/_aligned_realloc. + new_ptr = unsafe { C._aligned_realloc(b, n, 1) } + } $else { + new_ptr = unsafe { C.realloc(b, n) } + } + } + if new_ptr == 0 { + _memory_panic(@FN, n) + } + return new_ptr +} + +// realloc_data resizes the memory block pointed by `old_data` to `new_size` +// bytes. `old_data` must be a pointer to an existing memory block, previously +// allocated with `malloc` or `vcalloc`, of size `old_data`. +// realloc_data returns a pointer to the new location of the block. +// Note: if you know the old data size, it is preferable to call `realloc_data`, +// instead of `v_realloc`, at least during development, because `realloc_data` +// can make debugging easier, when you compile your program with +// `-d debug_realloc`. +@[unsafe] +pub fn realloc_data(old_data &u8, old_size int, new_size int) &u8 { + $if trace_realloc ? { + C.fprintf(C.stderr, c'realloc_data old_size: %6d new_size: %6d\n', old_size, new_size) + } + $if prealloc { + return unsafe { prealloc_realloc(old_data, old_size, new_size) } + } + $if debug_realloc ? { + // Note: this is slower, but helps debugging memory problems. + // The main idea is to always force reallocating: + // 1) allocate a new memory block + // 2) copy the old to the new + // 3) fill the old with 0x57 (`W`) + // 4) free the old block + // => if there is still a pointer to the old block somewhere + // it will point to memory that is now filled with 0x57. + unsafe { + new_ptr := malloc(new_size) + min_size := if old_size < new_size { old_size } else { new_size } + C.memcpy(new_ptr, old_data, min_size) + C.memset(old_data, 0x57, old_size) + free(old_data) + return new_ptr + } + } + mut nptr := &u8(unsafe { nil }) + $if gcboehm ? { + nptr = unsafe { C.GC_REALLOC(old_data, new_size) } + } $else { + $if windows { + // Warning! On windows, we always use _aligned_realloc to reallocate memory. + // This ensures that we can later free the memory with _aligned_free + // without needing to track whether the memory was originally allocated + // by malloc or _aligned_malloc/_aligned_realloc. + nptr = unsafe { C._aligned_realloc(old_data, new_size, 1) } + } $else { + nptr = unsafe { C.realloc(old_data, new_size) } + } + } + if nptr == 0 { + _memory_panic(@FN, isize(new_size)) + } + return nptr +} + +// vcalloc dynamically allocates a zeroed `n` bytes block of memory on the heap. +// vcalloc returns a `byteptr` pointing to the memory address of the allocated space. +// vcalloc checks for negative values given in `n`. +pub fn vcalloc(n isize) &u8 { + $if trace_vcalloc ? { + total_m += n + C.fprintf(C.stderr, c'vcalloc %6d total %10d\n', n, total_m) + } + if n < 0 { + _memory_panic(@FN, n) + } else if n == 0 { + return &u8(unsafe { nil }) + } + $if prealloc { + return unsafe { prealloc_calloc(n) } + } $else $if native { + return unsafe { C.calloc(1, n) } + } $else $if gcboehm ? { + return unsafe { &u8(C.GC_MALLOC(n)) } + } $else { + $if windows { + // Warning! On windows, we always use _aligned_malloc to allocate memory. + // This ensures that we can later free the memory with _aligned_free + // without needing to track whether the memory was originally allocated + // by malloc or _aligned_malloc/_aligned_realloc/_aligned_recalloc. + ptr := unsafe { C._aligned_malloc(n, 1) } + if ptr != &u8(unsafe { nil }) { + unsafe { C.memset(ptr, 0, n) } + } + return ptr + } $else { + return unsafe { C.calloc(1, n) } + } + } + return &u8(unsafe { nil }) // not reached, TODO: remove when V's checker is improved +} + +// special versions of the above that allocate memory which is not scanned +// for pointers (but is collected) when the Boehm garbage collection is used +pub fn vcalloc_noscan(n isize) &u8 { + $if trace_vcalloc ? { + total_m += n + C.fprintf(C.stderr, c'vcalloc_noscan %6d total %10d\n', n, total_m) + } + $if prealloc { + return unsafe { prealloc_calloc(n) } + } $else $if gcboehm ? { + if n < 0 { + _memory_panic(@FN, n) + } + $if gcboehm_opt ? { + res := unsafe { C.GC_MALLOC_ATOMIC(n) } + unsafe { C.memset(res, 0, n) } + return &u8(res) + } $else { + res := unsafe { C.GC_MALLOC(n) } + return &u8(res) + } + } $else { + return unsafe { vcalloc(n) } + } + return &u8(unsafe { nil }) // not reached, TODO: remove when V's checker is improved +} + +// free allows for manually freeing memory allocated at the address `ptr`. +@[unsafe] +pub fn free(ptr voidptr) { + $if trace_free ? { + C.fprintf(C.stderr, c'free ptr: %p\n', ptr) + } + $if builtin_free_nop ? { + return + } + if ptr == unsafe { 0 } { + $if trace_free_nulls ? { + C.fprintf(C.stderr, c'free null ptr\n', ptr) + } + $if trace_free_nulls_break ? { + break_if_debugger_attached() + } + return + } + $if prealloc { + return + } $else $if gcboehm ? { + // It is generally better to leave it to Boehm's gc to free things. + // Calling C.GC_FREE(ptr) was tried initially, but does not work + // well with programs that do manual management themselves. + // + // The exception is doing leak detection for manual memory management: + $if gcboehm_leak ? { + unsafe { C.GC_FREE(ptr) } + } + } $else { + $if windows { + // Warning! On windows, we always use _aligned_free to free memory. + unsafe { C._aligned_free(ptr) } + } $else { + C.free(ptr) + } + } +} + +// memdup dynamically allocates a `sz` bytes block of memory on the heap +// memdup then copies the contents of `src` into the allocated space and +// returns a pointer to the newly allocated space. +@[unsafe] +pub fn memdup(src voidptr, sz isize) voidptr { + $if trace_memdup ? { + C.fprintf(C.stderr, c'memdup size: %10d\n', sz) + } + if sz == 0 { + return vcalloc(1) + } + unsafe { + mem := malloc(sz) + return C.memcpy(mem, src, sz) + } +} + +@[unsafe] +pub fn memdup_noscan(src voidptr, sz isize) voidptr { + $if trace_memdup ? { + C.fprintf(C.stderr, c'memdup_noscan size: %10d\n', sz) + } + if sz == 0 { + return vcalloc_noscan(1) + } + unsafe { + mem := malloc_noscan(sz) + return C.memcpy(mem, src, sz) + } +} + +// memdup_uncollectable dynamically allocates a `sz` bytes block of memory +// on the heap, which will NOT be garbage-collected (but its contents will). +// memdup_uncollectable then copies the contents of `src` into the allocated +// space and returns a pointer to the newly allocated space. +@[unsafe] +pub fn memdup_uncollectable(src voidptr, sz isize) voidptr { + $if trace_memdup ? { + C.fprintf(C.stderr, c'memdup_uncollectable size: %10d\n', sz) + } + if sz == 0 { + return vcalloc(1) + } + unsafe { + mem := malloc_uncollectable(sz) + return C.memcpy(mem, src, sz) + } +} + +// memdup_align dynamically allocates a memory block of `sz` bytes on the heap, +// copies the contents from `src` into the allocated space, and returns a pointer +// to the newly allocated memory. The returned pointer is aligned to the specified `align` boundary. +// - `align` must be a power of two and at least 1 +// - `sz` must be non-negative +// - The memory regions should not overlap +@[unsafe] +pub fn memdup_align(src voidptr, sz isize, align isize) voidptr { + $if trace_memdup ? { + C.fprintf(C.stderr, c'memdup_align size: %10d align: %10d\n', sz, align) + } + if sz == 0 { + return vcalloc(1) + } + n := sz + $if trace_malloc ? { + total_m += n + C.fprintf(C.stderr, c'_v_memdup_align %6d total %10d\n', n, total_m) + // print_backtrace() + } + if n < 0 { + _memory_panic(@FN, n) + } + mut res := &u8(unsafe { nil }) + $if prealloc { + res = prealloc_malloc_align(n, align) + } $else $if gcboehm ? { + unsafe { + res = C.GC_memalign(align, n) + } + } $else $if freestanding { + // todo: is this safe to call malloc there? We export __malloc as malloc and it uses dlmalloc behind the scenes + // so theoretically it is safe + panic('memdup_align is not implemented with -freestanding') + res = unsafe { __malloc(usize(n)) } + } $else { + $if windows { + // Warning! On windows, we always use _aligned_malloc to allocate memory. + // This ensures that we can later free the memory with _aligned_free + // without needing to track whether the memory was originally allocated + // by malloc or _aligned_malloc. + res = unsafe { C._aligned_malloc(n, align) } + } $else { + res = unsafe { C.aligned_alloc(align, n) } + } + } + if res == 0 { + _memory_panic(@FN, n) + } + $if debug_malloc ? { + // Fill in the memory with something != 0 i.e. `M`, so it is easier to spot + // when the calling code wrongly relies on it being zeroed. + unsafe { C.memset(res, 0x4D, n) } + } + return C.memcpy(res, src, sz) +} + +// GCHeapUsage contains stats about the current heap usage of your program. +pub struct GCHeapUsage { +pub: + heap_size usize + free_bytes usize + total_bytes usize + unmapped_bytes usize + bytes_since_gc usize +} + +// gc_heap_usage returns the info about heap usage. +pub fn gc_heap_usage() GCHeapUsage { + $if gcboehm ? { + mut res := GCHeapUsage{} + C.GC_get_heap_usage_safe(&res.heap_size, &res.free_bytes, &res.unmapped_bytes, + &res.bytes_since_gc, &res.total_bytes) + return res + } $else { + return GCHeapUsage{} + } +} + +// gc_memory_use returns the total memory use in bytes by all allocated blocks. +pub fn gc_memory_use() usize { + $if gcboehm ? { + return C.GC_get_memory_use() + } $else { + return 0 + } +} diff --git a/vlib/builtin/builtin.c.v b/vlib/builtin/builtin.c.v index 8b4b9c5ac..730f8c9b4 100644 --- a/vlib/builtin/builtin.c.v +++ b/vlib/builtin/builtin.c.v @@ -1,6 +1,19 @@ @[has_globals] module builtin +// NOTE: g_main_argc and g_main_argv are filled in right after C's main start. +// They are used internally by V's builtin; for user code, it is much +// more convenient to just use `os.args` or call `arguments()` instead. + +@[markused] +__global g_main_argc = int(0) + +@[markused] +__global g_main_argv = unsafe { nil } + +@[markused] +__global g_live_reload_info voidptr + pub type FnExitCb = fn () fn C.atexit(f FnExitCb) int @@ -12,20 +25,6 @@ fn C._vinit(argc int, argv &&char) fn C._vcleanup() -fn v_segmentation_fault_handler(signal_number i32) { - $if freestanding { - eprintln('signal 11: segmentation fault') - } $else { - C.fprintf(C.stderr, c'signal %d: segmentation fault\n', signal_number) - } - $if use_libbacktrace ? { - eprint_libbacktrace(1) - } $else { - print_backtrace() - } - exit(128 + signal_number) -} - // exit terminates execution immediately and returns exit `code` to the shell. @[noreturn] pub fn exit(code int) { @@ -49,874 +48,18 @@ pub fn at_exit(cb FnExitCb) ! { } } -// panic_debug private function that V uses for panics, -cg/-g is passed -// recent versions of tcc print nicer backtraces automatically -// Note: the duplication here is because tcc_backtrace should be called directly -// inside the panic functions. -@[noreturn] -fn panic_debug(line_no int, file string, mod string, fn_name string, s string) { - // Note: the order here is important for a stabler test output - // module is less likely to change than function, etc... - // During edits, the line number will change most frequently, - // so it is last - $if freestanding { - bare_panic(s) - } $else { - // vfmt off - // Note: be carefull to not allocate here, avoid string interpolation - flush_stdout() - eprintln('================ V panic ================') - eprint(' module: '); eprintln(mod) - eprint(' function: '); eprint(fn_name); eprintln('()') - eprint(' message: '); eprintln(s) - eprint(' file: '); eprint(file); eprint(':'); - C.fprintf(C.stderr, c'%d\n', line_no) - eprint(' v hash: '); eprintln(vcurrent_hash()) - $if !vinix && !native { - eprint(' pid: '); C.fprintf(C.stderr, c'%p\n', voidptr(v_getpid())) - eprint(' tid: '); C.fprintf(C.stderr, c'%p\n', voidptr(v_gettid())) - } - eprintln('=========================================') - flush_stdout() - // vfmt on - $if native { - C.exit(1) // TODO: native backtraces - } $else $if exit_after_panic_message ? { - C.exit(1) - } $else $if no_backtrace ? { - C.exit(1) - } $else { - $if tinyc { - $if panics_break_into_debugger ? { - break_if_debugger_attached() - } $else { - C.tcc_backtrace(c'Backtrace') - } - C.exit(1) - } - $if use_libbacktrace ? { - eprint_libbacktrace(1) - } $else { - print_backtrace_skipping_top_frames(1) - } - $if panics_break_into_debugger ? { - break_if_debugger_attached() - } - C.exit(1) - } - } - C.exit(1) -} - -// panic_option_not_set is called by V, when you use option error propagation in your main function. -// It ends the program with a panic. -@[noreturn] -pub fn panic_option_not_set(s string) { - panic('option not set (' + s + ')') -} - -// panic_result_not_set is called by V, when you use result error propagation in your main function -// It ends the program with a panic. -@[noreturn] -pub fn panic_result_not_set(s string) { - panic('result not set (' + s + ')') -} - -pub fn vcurrent_hash() string { - return @VCURRENTHASH -} - -// panic prints a nice error message, then exits the process with exit code of 1. -// It also shows a backtrace on most platforms. -@[noreturn] -pub fn panic(s string) { - // Note: be careful to not use string interpolation here: - $if freestanding { - bare_panic(s) - } $else { - // vfmt off - flush_stdout() - eprint('V panic: ') - eprintln(s) - eprint(' v hash: ') - eprintln(vcurrent_hash()) - $if !vinix && !native { - eprint(' pid: '); C.fprintf(C.stderr, c'%p\n', voidptr(v_getpid())) - eprint(' tid: '); C.fprintf(C.stderr, c'%p\n', voidptr(v_gettid())) - } - flush_stdout() - // vfmt on - $if native { - C.exit(1) // TODO: native backtraces - } $else $if exit_after_panic_message ? { - C.exit(1) - } $else $if no_backtrace ? { - C.exit(1) - } $else { - $if tinyc { - $if panics_break_into_debugger ? { - break_if_debugger_attached() - } $else { - C.tcc_backtrace(c'Backtrace') - } - C.exit(1) - } - $if use_libbacktrace ? { - eprint_libbacktrace(1) - } $else { - print_backtrace_skipping_top_frames(1) - } - $if panics_break_into_debugger ? { - break_if_debugger_attached() - } - C.exit(1) - } - } - C.exit(1) -} - -// return a C-API error message matching to `errnum` -pub fn c_error_number_str(errnum int) string { - mut err_msg := '' - $if freestanding { - err_msg = 'error ' + errnum.str() - } $else { - $if !vinix { - c_msg := C.strerror(errnum) - err_msg = string{ - str: &u8(c_msg) - len: unsafe { C.strlen(c_msg) } - is_lit: 1 - } - } - } - return err_msg -} - -// panic_n prints an error message, followed by the given number, then exits the process with exit code of 1. -@[noreturn] -pub fn panic_n(s string, number1 i64) { - panic(s + impl_i64_to_string(number1)) -} - -// panic_n2 prints an error message, followed by the given numbers, then exits the process with exit code of 1. -@[noreturn] -pub fn panic_n2(s string, number1 i64, number2 i64) { - panic(s + impl_i64_to_string(number1) + ', ' + impl_i64_to_string(number2)) -} - -// panic_n3 prints an error message, followed by the given numbers, then exits the process with exit code of 1. -@[noreturn] -fn panic_n3(s string, number1 i64, number2 i64, number3 i64) { - panic(s + impl_i64_to_string(number1) + ', ' + impl_i64_to_string(number2) + ', ' + - impl_i64_to_string(number3)) -} - -// panic with a C-API error message matching `errnum` -@[noreturn] -pub fn panic_error_number(basestr string, errnum int) { - panic(basestr + c_error_number_str(errnum)) -} - -// eprintln prints a message with a line end, to stderr. Both stderr and stdout are flushed. -pub fn eprintln(s string) { - if s.str == 0 { - eprintln('eprintln(NIL)') - return - } - $if builtin_print_use_fprintf ? { - C.fprintf(C.stderr, c'%.*s\n', s.len, s.str) - return - } - $if freestanding { - // flushing is only a thing with C.FILE from stdio.h, not on the syscall level - bare_eprint(s.str, u64(s.len)) - bare_eprint(c'\n', 1) - } $else $if ios { - C.WrappedNSLog(s.str) - } $else { - flush_stdout() - flush_stderr() - // eprintln is used in panics, so it should not fail at all - $if android && !termux { - C.android_print(C.stderr, c'%.*s\n', s.len, s.str) - } - _writeln_to_fd(2, s) - flush_stderr() - } -} - -// eprint prints a message to stderr. Both stderr and stdout are flushed. -pub fn eprint(s string) { - if s.str == 0 { - eprint('eprint(NIL)') - return - } - $if builtin_print_use_fprintf ? { - C.fprintf(C.stderr, c'%.*s', s.len, s.str) - return - } - $if freestanding { - // flushing is only a thing with C.FILE from stdio.h, not on the syscall level - bare_eprint(s.str, u64(s.len)) - } $else $if ios { - // TODO: Implement a buffer as NSLog doesn't have a "print" - C.WrappedNSLog(s.str) - } $else { - flush_stdout() - flush_stderr() - $if android && !termux { - C.android_print(C.stderr, c'%.*s', s.len, s.str) - } - _write_buf_to_fd(2, s.str, s.len) - flush_stderr() - } -} - -pub fn flush_stdout() { - $if freestanding { - not_implemented := 'flush_stdout is not implemented\n' - bare_eprint(not_implemented.str, u64(not_implemented.len)) - } $else { - C.fflush(C.stdout) - } -} - -pub fn flush_stderr() { - $if freestanding { - not_implemented := 'flush_stderr is not implemented\n' - bare_eprint(not_implemented.str, u64(not_implemented.len)) - } $else { - C.fflush(C.stderr) - } -} - -// unbuffer_stdout will turn off the default buffering done for stdout. -// It will affect all consequent print and println calls, effectively making them behave like -// eprint and eprintln do. It is useful for programs, that want to produce progress bars, without -// cluttering your code with a flush_stdout() call after every print() call. It is also useful for -// programs (sensors), that produce small chunks of output, that you want to be able to process -// immediately. -// Note 1: if used, *it should be called at the start of your program*, before using -// print or println(). -// Note 2: most libc implementations, have logic that use line buffering for stdout, when the output -// stream is connected to an interactive device, like a terminal, and otherwise fully buffer it, -// which is good for the output performance for programs that can produce a lot of output (like -// filters, or cat etc), but bad for latency. Normally, it is usually what you want, so it is the -// default for V programs too. -// See https://www.gnu.org/software/libc/manual/html_node/Buffering-Concepts.html . -// See https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_05 . -pub fn unbuffer_stdout() { +fn v_segmentation_fault_handler(signal_number i32) { $if freestanding { - not_implemented := 'unbuffer_stdout is not implemented\n' - bare_eprint(not_implemented.str, u64(not_implemented.len)) - } $else { - unsafe { C.setbuf(C.stdout, 0) } - } -} - -// print prints a message to stdout. Note that unlike `eprint`, stdout is not automatically flushed. -@[manualfree] -pub fn print(s string) { - $if builtin_print_use_fprintf ? { - C.fprintf(C.stdout, c'%.*s', s.len, s.str) - return - } - $if android && !termux { - C.android_print(C.stdout, c'%.*s\n', s.len, s.str) - } $else $if ios { - // TODO: Implement a buffer as NSLog doesn't have a "print" - C.WrappedNSLog(s.str) - } $else $if freestanding { - bare_print(s.str, u64(s.len)) - } $else { - _write_buf_to_fd(1, s.str, s.len) - } -} - -// println prints a message with a line end, to stdout. Note that unlike `eprintln`, stdout is not automatically flushed. -@[manualfree] -pub fn println(s string) { - if s.str == 0 { - println('println(NIL)') - return - } - $if noprintln ? { - return - } - $if builtin_print_use_fprintf ? { - C.fprintf(C.stdout, c'%.*s\n', s.len, s.str) - return - } - $if android && !termux { - C.android_print(C.stdout, c'%.*s\n', s.len, s.str) - return - } $else $if ios { - C.WrappedNSLog(s.str) - return - } $else $if freestanding { - bare_print(s.str, u64(s.len)) - bare_print(c'\n', 1) - return - } $else { - _writeln_to_fd(1, s) - } -} - -@[manualfree] -fn _writeln_to_fd(fd int, s string) { - $if builtin_writeln_should_write_at_once ? { - unsafe { - buf_len := s.len + 1 // space for \n - mut buf := malloc(buf_len) - C.memcpy(buf, s.str, s.len) - buf[s.len] = `\n` - _write_buf_to_fd(fd, buf, buf_len) - free(buf) - } - } $else { - lf := u8(`\n`) - _write_buf_to_fd(fd, s.str, s.len) - _write_buf_to_fd(fd, &lf, 1) - } -} - -@[manualfree] -fn _write_buf_to_fd(fd int, buf &u8, buf_len int) { - if buf_len <= 0 { - return - } - mut ptr := unsafe { buf } - mut remaining_bytes := isize(buf_len) - mut x := isize(0) - $if freestanding || vinix || builtin_write_buf_to_fd_should_use_c_write ? { - unsafe { - for remaining_bytes > 0 { - x = C.write(fd, ptr, remaining_bytes) - ptr += x - remaining_bytes -= x - } - } - } $else { - mut stream := voidptr(C.stdout) - if fd == 2 { - stream = voidptr(C.stderr) - } - unsafe { - for remaining_bytes > 0 { - x = isize(C.fwrite(ptr, 1, remaining_bytes, stream)) - ptr += x - remaining_bytes -= x - } - } - } -} - -// v_memory_panic will be true, *only* when a call to malloc/realloc/vcalloc etc could not succeed. -// In that situation, functions that are registered with at_exit(), should be able to limit their -// activity accordingly, by checking this flag. -// The V compiler itself for example registers a function with at_exit(), for showing timers. -// Without a way to distinguish, that we are in a memory panic, that would just display a second panic, -// which would be less clear to the user. -__global v_memory_panic = false - -@[noreturn] -fn _memory_panic(fname string, size isize) { - v_memory_panic = true - // Note: do not use string interpolation here at all, since string interpolation itself allocates - eprint(fname) - eprint('(') - $if freestanding || vinix { - eprint('size') // TODO: use something more informative here - } $else { - C.fprintf(C.stderr, c'%p', voidptr(size)) - } - if size < 0 { - eprint(' < 0') - } - eprintln(')') - panic('memory allocation failure') -} - -__global total_m = i64(0) -// malloc dynamically allocates a `n` bytes block of memory on the heap. -// malloc returns a `byteptr` pointing to the memory address of the allocated space. -// unlike the `calloc` family of functions - malloc will not zero the memory block. -@[unsafe] -pub fn malloc(n isize) &u8 { - $if trace_malloc ? { - total_m += n - C.fprintf(C.stderr, c'_v_malloc %6d total %10d\n', n, total_m) - // print_backtrace() - } - if n < 0 { - _memory_panic(@FN, n) - } else if n == 0 { - return &u8(unsafe { nil }) - } - mut res := &u8(unsafe { nil }) - $if prealloc { - return unsafe { prealloc_malloc(n) } - } $else $if gcboehm ? { - unsafe { - res = C.GC_MALLOC(n) - } - } $else $if freestanding { - // todo: is this safe to call malloc there? We export __malloc as malloc and it uses dlmalloc behind the scenes - // so theoretically it is safe - res = unsafe { __malloc(usize(n)) } - } $else { - $if windows { - // Warning! On windows, we always use _aligned_malloc to allocate memory. - // This ensures that we can later free the memory with _aligned_free - // without needing to track whether the memory was originally allocated - // by malloc or _aligned_malloc. - res = unsafe { C._aligned_malloc(n, 1) } - } $else { - res = unsafe { C.malloc(n) } - } - } - if res == 0 { - _memory_panic(@FN, n) - } - $if debug_malloc ? { - // Fill in the memory with something != 0 i.e. `M`, so it is easier to spot - // when the calling code wrongly relies on it being zeroed. - unsafe { C.memset(res, 0x4D, n) } - } - return res -} - -@[unsafe] -pub fn malloc_noscan(n isize) &u8 { - $if trace_malloc ? { - total_m += n - C.fprintf(C.stderr, c'malloc_noscan %6d total %10d\n', n, total_m) - // print_backtrace() - } - if n < 0 { - _memory_panic(@FN, n) - } - mut res := &u8(unsafe { nil }) - $if native { - res = unsafe { C.malloc(n) } - } $else $if prealloc { - return unsafe { prealloc_malloc(n) } - } $else $if gcboehm ? { - $if gcboehm_opt ? { - unsafe { - res = C.GC_MALLOC_ATOMIC(n) - } - } $else { - unsafe { - res = C.GC_MALLOC(n) - } - } - } $else $if freestanding { - res = unsafe { __malloc(usize(n)) } - } $else { - $if windows { - // Warning! On windows, we always use _aligned_malloc to allocate memory. - // This ensures that we can later free the memory with _aligned_free - // without needing to track whether the memory was originally allocated - // by malloc or _aligned_malloc. - res = unsafe { C._aligned_malloc(n, 1) } - } $else { - res = unsafe { C.malloc(n) } - } - } - if res == 0 { - _memory_panic(@FN, n) - } - $if debug_malloc ? { - // Fill in the memory with something != 0 i.e. `M`, so it is easier to spot - // when the calling code wrongly relies on it being zeroed. - unsafe { C.memset(res, 0x4D, n) } - } - return res -} - -@[inline] -fn __at_least_one(how_many u64) u64 { - // handle the case for allocating memory for empty structs, which have sizeof(EmptyStruct) == 0 - // in this case, just allocate a single byte, avoiding the panic for malloc(0) - if how_many == 0 { - return 1 - } - return how_many -} - -// malloc_uncollectable dynamically allocates a `n` bytes block of memory -// on the heap, which will NOT be garbage-collected (but its contents will). -@[unsafe] -pub fn malloc_uncollectable(n isize) &u8 { - $if trace_malloc ? { - total_m += n - C.fprintf(C.stderr, c'malloc_uncollectable %6d total %10d\n', n, total_m) - // print_backtrace() - } - if n < 0 { - _memory_panic(@FN, n) - } - - mut res := &u8(unsafe { nil }) - $if prealloc { - return unsafe { prealloc_malloc(n) } - } $else $if gcboehm ? { - unsafe { - res = C.GC_MALLOC_UNCOLLECTABLE(n) - } - } $else $if freestanding { - res = unsafe { __malloc(usize(n)) } - } $else { - $if windows { - // Warning! On windows, we always use _aligned_malloc to allocate memory. - // This ensures that we can later free the memory with _aligned_free - // without needing to track whether the memory was originally allocated - // by malloc or _aligned_malloc. - res = unsafe { C._aligned_malloc(n, 1) } - } $else { - res = unsafe { C.malloc(n) } - } - } - if res == 0 { - _memory_panic(@FN, n) - } - $if debug_malloc ? { - // Fill in the memory with something != 0 i.e. `M`, so it is easier to spot - // when the calling code wrongly relies on it being zeroed. - unsafe { C.memset(res, 0x4D, n) } - } - return res -} - -// v_realloc resizes the memory block `b` with `n` bytes. -// The `b byteptr` must be a pointer to an existing memory block -// previously allocated with `malloc` or `vcalloc`. -// Please, see also realloc_data, and use it instead if possible. -@[unsafe] -pub fn v_realloc(b &u8, n isize) &u8 { - $if trace_realloc ? { - C.fprintf(C.stderr, c'v_realloc %6d\n', n) - } - mut new_ptr := &u8(unsafe { nil }) - $if prealloc { - unsafe { - new_ptr = malloc(n) - C.memcpy(new_ptr, b, n) - } - return new_ptr - } $else $if gcboehm ? { - new_ptr = unsafe { C.GC_REALLOC(b, n) } - } $else { - $if windows { - // Warning! On windows, we always use _aligned_realloc to reallocate memory. - // This ensures that we can later free the memory with _aligned_free - // without needing to track whether the memory was originally allocated - // by malloc or _aligned_malloc/_aligned_realloc. - new_ptr = unsafe { C._aligned_realloc(b, n, 1) } - } $else { - new_ptr = unsafe { C.realloc(b, n) } - } - } - if new_ptr == 0 { - _memory_panic(@FN, n) - } - return new_ptr -} - -// realloc_data resizes the memory block pointed by `old_data` to `new_size` -// bytes. `old_data` must be a pointer to an existing memory block, previously -// allocated with `malloc` or `vcalloc`, of size `old_data`. -// realloc_data returns a pointer to the new location of the block. -// Note: if you know the old data size, it is preferable to call `realloc_data`, -// instead of `v_realloc`, at least during development, because `realloc_data` -// can make debugging easier, when you compile your program with -// `-d debug_realloc`. -@[unsafe] -pub fn realloc_data(old_data &u8, old_size int, new_size int) &u8 { - $if trace_realloc ? { - C.fprintf(C.stderr, c'realloc_data old_size: %6d new_size: %6d\n', old_size, new_size) - } - $if prealloc { - return unsafe { prealloc_realloc(old_data, old_size, new_size) } - } - $if debug_realloc ? { - // Note: this is slower, but helps debugging memory problems. - // The main idea is to always force reallocating: - // 1) allocate a new memory block - // 2) copy the old to the new - // 3) fill the old with 0x57 (`W`) - // 4) free the old block - // => if there is still a pointer to the old block somewhere - // it will point to memory that is now filled with 0x57. - unsafe { - new_ptr := malloc(new_size) - min_size := if old_size < new_size { old_size } else { new_size } - C.memcpy(new_ptr, old_data, min_size) - C.memset(old_data, 0x57, old_size) - free(old_data) - return new_ptr - } - } - mut nptr := &u8(unsafe { nil }) - $if gcboehm ? { - nptr = unsafe { C.GC_REALLOC(old_data, new_size) } - } $else { - $if windows { - // Warning! On windows, we always use _aligned_realloc to reallocate memory. - // This ensures that we can later free the memory with _aligned_free - // without needing to track whether the memory was originally allocated - // by malloc or _aligned_malloc/_aligned_realloc. - nptr = unsafe { C._aligned_realloc(old_data, new_size, 1) } - } $else { - nptr = unsafe { C.realloc(old_data, new_size) } - } - } - if nptr == 0 { - _memory_panic(@FN, isize(new_size)) - } - return nptr -} - -// vcalloc dynamically allocates a zeroed `n` bytes block of memory on the heap. -// vcalloc returns a `byteptr` pointing to the memory address of the allocated space. -// vcalloc checks for negative values given in `n`. -pub fn vcalloc(n isize) &u8 { - $if trace_vcalloc ? { - total_m += n - C.fprintf(C.stderr, c'vcalloc %6d total %10d\n', n, total_m) - } - if n < 0 { - _memory_panic(@FN, n) - } else if n == 0 { - return &u8(unsafe { nil }) - } - $if prealloc { - return unsafe { prealloc_calloc(n) } - } $else $if native { - return unsafe { C.calloc(1, n) } - } $else $if gcboehm ? { - return unsafe { &u8(C.GC_MALLOC(n)) } - } $else { - $if windows { - // Warning! On windows, we always use _aligned_malloc to allocate memory. - // This ensures that we can later free the memory with _aligned_free - // without needing to track whether the memory was originally allocated - // by malloc or _aligned_malloc/_aligned_realloc/_aligned_recalloc. - ptr := unsafe { C._aligned_malloc(n, 1) } - if ptr != &u8(unsafe { nil }) { - unsafe { C.memset(ptr, 0, n) } - } - return ptr - } $else { - return unsafe { C.calloc(1, n) } - } - } - return &u8(unsafe { nil }) // not reached, TODO: remove when V's checker is improved -} - -// special versions of the above that allocate memory which is not scanned -// for pointers (but is collected) when the Boehm garbage collection is used -pub fn vcalloc_noscan(n isize) &u8 { - $if trace_vcalloc ? { - total_m += n - C.fprintf(C.stderr, c'vcalloc_noscan %6d total %10d\n', n, total_m) - } - $if prealloc { - return unsafe { prealloc_calloc(n) } - } $else $if gcboehm ? { - if n < 0 { - _memory_panic(@FN, n) - } - $if gcboehm_opt ? { - res := unsafe { C.GC_MALLOC_ATOMIC(n) } - unsafe { C.memset(res, 0, n) } - return &u8(res) - } $else { - res := unsafe { C.GC_MALLOC(n) } - return &u8(res) - } - } $else { - return unsafe { vcalloc(n) } - } - return &u8(unsafe { nil }) // not reached, TODO: remove when V's checker is improved -} - -// free allows for manually freeing memory allocated at the address `ptr`. -@[unsafe] -pub fn free(ptr voidptr) { - $if trace_free ? { - C.fprintf(C.stderr, c'free ptr: %p\n', ptr) - } - $if builtin_free_nop ? { - return - } - if ptr == unsafe { 0 } { - $if trace_free_nulls ? { - C.fprintf(C.stderr, c'free null ptr\n', ptr) - } - $if trace_free_nulls_break ? { - break_if_debugger_attached() - } - return - } - $if prealloc { - return - } $else $if gcboehm ? { - // It is generally better to leave it to Boehm's gc to free things. - // Calling C.GC_FREE(ptr) was tried initially, but does not work - // well with programs that do manual management themselves. - // - // The exception is doing leak detection for manual memory management: - $if gcboehm_leak ? { - unsafe { C.GC_FREE(ptr) } - } - } $else { - $if windows { - // Warning! On windows, we always use _aligned_free to free memory. - unsafe { C._aligned_free(ptr) } - } $else { - C.free(ptr) - } - } -} - -// memdup dynamically allocates a `sz` bytes block of memory on the heap -// memdup then copies the contents of `src` into the allocated space and -// returns a pointer to the newly allocated space. -@[unsafe] -pub fn memdup(src voidptr, sz isize) voidptr { - $if trace_memdup ? { - C.fprintf(C.stderr, c'memdup size: %10d\n', sz) - } - if sz == 0 { - return vcalloc(1) - } - unsafe { - mem := malloc(sz) - return C.memcpy(mem, src, sz) - } -} - -@[unsafe] -pub fn memdup_noscan(src voidptr, sz isize) voidptr { - $if trace_memdup ? { - C.fprintf(C.stderr, c'memdup_noscan size: %10d\n', sz) - } - if sz == 0 { - return vcalloc_noscan(1) - } - unsafe { - mem := malloc_noscan(sz) - return C.memcpy(mem, src, sz) - } -} - -// memdup_uncollectable dynamically allocates a `sz` bytes block of memory -// on the heap, which will NOT be garbage-collected (but its contents will). -// memdup_uncollectable then copies the contents of `src` into the allocated -// space and returns a pointer to the newly allocated space. -@[unsafe] -pub fn memdup_uncollectable(src voidptr, sz isize) voidptr { - $if trace_memdup ? { - C.fprintf(C.stderr, c'memdup_uncollectable size: %10d\n', sz) - } - if sz == 0 { - return vcalloc(1) - } - unsafe { - mem := malloc_uncollectable(sz) - return C.memcpy(mem, src, sz) - } -} - -// memdup_align dynamically allocates a memory block of `sz` bytes on the heap, -// copies the contents from `src` into the allocated space, and returns a pointer -// to the newly allocated memory. The returned pointer is aligned to the specified `align` boundary. -// - `align` must be a power of two and at least 1 -// - `sz` must be non-negative -// - The memory regions should not overlap -@[unsafe] -pub fn memdup_align(src voidptr, sz isize, align isize) voidptr { - $if trace_memdup ? { - C.fprintf(C.stderr, c'memdup_align size: %10d align: %10d\n', sz, align) - } - if sz == 0 { - return vcalloc(1) - } - n := sz - $if trace_malloc ? { - total_m += n - C.fprintf(C.stderr, c'_v_memdup_align %6d total %10d\n', n, total_m) - // print_backtrace() - } - if n < 0 { - _memory_panic(@FN, n) - } - mut res := &u8(unsafe { nil }) - $if prealloc { - res = prealloc_malloc_align(n, align) - } $else $if gcboehm ? { - unsafe { - res = C.GC_memalign(align, n) - } - } $else $if freestanding { - // todo: is this safe to call malloc there? We export __malloc as malloc and it uses dlmalloc behind the scenes - // so theoretically it is safe - panic('memdup_align is not implemented with -freestanding') - res = unsafe { __malloc(usize(n)) } - } $else { - $if windows { - // Warning! On windows, we always use _aligned_malloc to allocate memory. - // This ensures that we can later free the memory with _aligned_free - // without needing to track whether the memory was originally allocated - // by malloc or _aligned_malloc. - res = unsafe { C._aligned_malloc(n, align) } - } $else { - res = unsafe { C.aligned_alloc(align, n) } - } - } - if res == 0 { - _memory_panic(@FN, n) - } - $if debug_malloc ? { - // Fill in the memory with something != 0 i.e. `M`, so it is easier to spot - // when the calling code wrongly relies on it being zeroed. - unsafe { C.memset(res, 0x4D, n) } - } - return C.memcpy(res, src, sz) -} - -// GCHeapUsage contains stats about the current heap usage of your program. -pub struct GCHeapUsage { -pub: - heap_size usize - free_bytes usize - total_bytes usize - unmapped_bytes usize - bytes_since_gc usize -} - -// gc_heap_usage returns the info about heap usage. -pub fn gc_heap_usage() GCHeapUsage { - $if gcboehm ? { - mut res := GCHeapUsage{} - C.GC_get_heap_usage_safe(&res.heap_size, &res.free_bytes, &res.unmapped_bytes, - &res.bytes_since_gc, &res.total_bytes) - return res + eprintln('signal 11: segmentation fault') } $else { - return GCHeapUsage{} + C.fprintf(C.stderr, c'signal %d: segmentation fault\n', signal_number) } -} - -// gc_memory_use returns the total memory use in bytes by all allocated blocks. -pub fn gc_memory_use() usize { - $if gcboehm ? { - return C.GC_get_memory_use() + $if use_libbacktrace ? { + eprint_libbacktrace(1) } $else { - return 0 + print_backtrace() } + exit(128 + signal_number) } @[inline] @@ -930,19 +73,6 @@ fn v_fixed_index(i int, len int) int { return i } -// NOTE: g_main_argc and g_main_argv are filled in right after C's main start. -// They are used internally by V's builtin; for user code, it is much -// more convenient to just use `os.args` or call `arguments()` instead. - -@[markused] -__global g_main_argc = int(0) - -@[markused] -__global g_main_argv = unsafe { nil } - -@[markused] -__global g_live_reload_info voidptr - // arguments returns the command line arguments, used for starting the current program as a V array of strings. // The first string in the array (index 0), is the name of the program, used for invoking the program. // The second string in the array (index 1), if it exists, is the first argument to the program, etc. @@ -961,6 +91,12 @@ pub fn arguments() []string { return res } +// vcurrent_hash returns @VCURRENTHASH, which depends on the git version of +// the V repository, from which the V executable had been compiled. +pub fn vcurrent_hash() string { + return @VCURRENTHASH +} + // v_getpid returns a process identifier. It is a number that is guaranteed to // remain the same while the current process is running. It may or may not be // equal to the value of v_gettid(). Note: it is *NOT equal on Windows*. diff --git a/vlib/builtin/builtin.v b/vlib/builtin/builtin.v index f8916a027..f9d5e6472 100644 --- a/vlib/builtin/builtin.v +++ b/vlib/builtin/builtin.v @@ -35,126 +35,3 @@ fn __as_cast(obj voidptr, obj_type int, expected_type int) voidptr { } return obj } - -// VAssertMetaInfo is used during assertions. An instance of it is filled in by compile time generated code, when an assertion fails. -pub struct VAssertMetaInfo { -pub: - fpath string // the source file path of the assertion - line_nr int // the line number of the assertion - fn_name string // the function name in which the assertion is - src string // the actual source line of the assertion - op string // the operation of the assertion, i.e. '==', '<', 'call', etc ... - llabel string // the left side of the infix expressions as source - rlabel string // the right side of the infix expressions as source - lvalue string // the stringified *actual value* of the left side of a failed assertion - rvalue string // the stringified *actual value* of the right side of a failed assertion - message string // the value of the `message` from `assert cond, message` - has_msg bool // false for assertions like `assert cond`, true for `assert cond, 'oh no'` -} - -// free frees the memory occupied by the assertion meta data. It is called automatically by -// the code, that V's test framework generates, after all other callbacks have been called. -@[manualfree; unsafe] -pub fn (ami &VAssertMetaInfo) free() { - unsafe { - ami.fpath.free() - ami.fn_name.free() - ami.src.free() - ami.op.free() - ami.llabel.free() - ami.rlabel.free() - ami.lvalue.free() - ami.rvalue.free() - ami.message.free() - } -} - -fn __print_assert_failure(i &VAssertMetaInfo) { - eprintln('${i.fpath}:${i.line_nr + 1}: FAIL: fn ${i.fn_name}: assert ${i.src}') - if i.op.len > 0 && i.op != 'call' { - if i.llabel == i.lvalue { - eprintln(' left value: ${i.llabel}') - } else { - eprintln(' left value: ${i.llabel} = ${i.lvalue}') - } - if i.rlabel == i.rvalue { - eprintln(' right value: ${i.rlabel}') - } else { - eprintln(' right value: ${i.rlabel} = ${i.rvalue}') - } - } - if i.has_msg { - eprintln(' message: ${i.message}') - } -} - -// FunctionParam holds type information for function and/or method arguments. -pub struct FunctionParam { -pub: - typ int - name string -} - -// FunctionData holds information about a parsed function. -pub struct FunctionData { -pub: - name string - attrs []string - args []FunctionParam - return_type int - typ int -} - -pub struct VariantData { -pub: - typ int -} - -pub struct EnumData { -pub: - name string - value i64 - attrs []string -} - -// FieldData holds information about a field. Fields reside on structs. -pub struct FieldData { -pub: - name string // the name of the field f - typ int // the internal TypeID of the field f, - unaliased_typ int // if f's type was an alias of int, this will be TypeID(int) - - attrs []string // the attributes of the field f - is_pub bool // f is in a `pub:` section - is_mut bool // f is in a `mut:` section - is_embed bool // f is a embedded struct - - is_shared bool // `f shared Abc` - is_atomic bool // `f atomic int` , TODO - is_option bool // `f ?string` , TODO - - is_array bool // `f []string` , TODO - is_map bool // `f map[string]int` , TODO - is_chan bool // `f chan int` , TODO - is_enum bool // `f Enum` where Enum is an enum - is_struct bool // `f Abc` where Abc is a struct , TODO - is_alias bool // `f MyInt` where `type MyInt = int`, TODO - - indirections u8 // 0 for `f int`, 1 for `f &int`, 2 for `f &&int` , TODO -} - -pub enum AttributeKind { - plain // [name] - string // ['name'] - number // [123] - bool // [true] || [false] - comptime_define // [if name] -} - -pub struct VAttribute { -pub: - name string - has_arg bool - arg string - kind AttributeKind -} diff --git a/vlib/builtin/meta_assert.v b/vlib/builtin/meta_assert.v new file mode 100644 index 000000000..5ca58ba51 --- /dev/null +++ b/vlib/builtin/meta_assert.v @@ -0,0 +1,53 @@ +module builtin + +// VAssertMetaInfo is used during assertions. An instance of it is filled in by compile time generated code, when an assertion fails. +pub struct VAssertMetaInfo { +pub: + fpath string // the source file path of the assertion + line_nr int // the line number of the assertion + fn_name string // the function name in which the assertion is + src string // the actual source line of the assertion + op string // the operation of the assertion, i.e. '==', '<', 'call', etc ... + llabel string // the left side of the infix expressions as source + rlabel string // the right side of the infix expressions as source + lvalue string // the stringified *actual value* of the left side of a failed assertion + rvalue string // the stringified *actual value* of the right side of a failed assertion + message string // the value of the `message` from `assert cond, message` + has_msg bool // false for assertions like `assert cond`, true for `assert cond, 'oh no'` +} + +// free frees the memory occupied by the assertion meta data. It is called automatically by +// the code, that V's test framework generates, after all other callbacks have been called. +@[manualfree; unsafe] +pub fn (ami &VAssertMetaInfo) free() { + unsafe { + ami.fpath.free() + ami.fn_name.free() + ami.src.free() + ami.op.free() + ami.llabel.free() + ami.rlabel.free() + ami.lvalue.free() + ami.rvalue.free() + ami.message.free() + } +} + +fn __print_assert_failure(i &VAssertMetaInfo) { + eprintln('${i.fpath}:${i.line_nr + 1}: FAIL: fn ${i.fn_name}: assert ${i.src}') + if i.op.len > 0 && i.op != 'call' { + if i.llabel == i.lvalue { + eprintln(' left value: ${i.llabel}') + } else { + eprintln(' left value: ${i.llabel} = ${i.lvalue}') + } + if i.rlabel == i.rvalue { + eprintln(' right value: ${i.rlabel}') + } else { + eprintln(' right value: ${i.rlabel} = ${i.rvalue}') + } + } + if i.has_msg { + eprintln(' message: ${i.message}') + } +} diff --git a/vlib/builtin/meta_attribute.v b/vlib/builtin/meta_attribute.v new file mode 100644 index 000000000..0730f9cbd --- /dev/null +++ b/vlib/builtin/meta_attribute.v @@ -0,0 +1,17 @@ +module builtin + +pub enum AttributeKind { + plain // [name] + string // ['name'] + number // [123] + bool // [true] || [false] + comptime_define // [if name] +} + +pub struct VAttribute { +pub: + name string + has_arg bool + arg string + kind AttributeKind +} diff --git a/vlib/builtin/meta_enum.v b/vlib/builtin/meta_enum.v new file mode 100644 index 000000000..2478f2d2a --- /dev/null +++ b/vlib/builtin/meta_enum.v @@ -0,0 +1,8 @@ +module builtin + +pub struct EnumData { +pub: + name string + value i64 + attrs []string +} diff --git a/vlib/builtin/meta_function.v b/vlib/builtin/meta_function.v new file mode 100644 index 000000000..d7e9b9c98 --- /dev/null +++ b/vlib/builtin/meta_function.v @@ -0,0 +1,18 @@ +module builtin + +// FunctionParam holds type information for function and/or method arguments. +pub struct FunctionParam { +pub: + typ int + name string +} + +// FunctionData holds information about a parsed function. +pub struct FunctionData { +pub: + name string + attrs []string + args []FunctionParam + return_type int + typ int +} diff --git a/vlib/builtin/meta_struct.v b/vlib/builtin/meta_struct.v new file mode 100644 index 000000000..79a88eedf --- /dev/null +++ b/vlib/builtin/meta_struct.v @@ -0,0 +1,27 @@ +module builtin + +// FieldData holds information about a field. Fields reside on structs. +pub struct FieldData { +pub: + name string // the name of the field f + typ int // the internal TypeID of the field f, + unaliased_typ int // if f's type was an alias of int, this will be TypeID(int) + + attrs []string // the attributes of the field f + is_pub bool // f is in a `pub:` section + is_mut bool // f is in a `mut:` section + is_embed bool // f is a embedded struct + + is_shared bool // `f shared Abc` + is_atomic bool // `f atomic int` , TODO + is_option bool // `f ?string` , TODO + + is_array bool // `f []string` , TODO + is_map bool // `f map[string]int` , TODO + is_chan bool // `f chan int` , TODO + is_enum bool // `f Enum` where Enum is an enum + is_struct bool // `f Abc` where Abc is a struct , TODO + is_alias bool // `f MyInt` where `type MyInt = int`, TODO + + indirections u8 // 0 for `f int`, 1 for `f &int`, 2 for `f &&int` , TODO +} diff --git a/vlib/builtin/meta_variant.v b/vlib/builtin/meta_variant.v new file mode 100644 index 000000000..e1560e8ef --- /dev/null +++ b/vlib/builtin/meta_variant.v @@ -0,0 +1,6 @@ +module builtin + +pub struct VariantData { +pub: + typ int +} diff --git a/vlib/builtin/panicing.c.v b/vlib/builtin/panicing.c.v new file mode 100644 index 000000000..b0b634129 --- /dev/null +++ b/vlib/builtin/panicing.c.v @@ -0,0 +1,166 @@ +module builtin + +// panic_debug private function that V uses for panics, -cg/-g is passed +// recent versions of tcc print nicer backtraces automatically +// Note: the duplication here is because tcc_backtrace should be called directly +// inside the panic functions. +@[noreturn] +fn panic_debug(line_no int, file string, mod string, fn_name string, s string) { + // Note: the order here is important for a stabler test output + // module is less likely to change than function, etc... + // During edits, the line number will change most frequently, + // so it is last + $if freestanding { + bare_panic(s) + } $else { + // vfmt off + // Note: be carefull to not allocate here, avoid string interpolation + flush_stdout() + eprintln('================ V panic ================') + eprint(' module: '); eprintln(mod) + eprint(' function: '); eprint(fn_name); eprintln('()') + eprint(' message: '); eprintln(s) + eprint(' file: '); eprint(file); eprint(':'); + C.fprintf(C.stderr, c'%d\n', line_no) + eprint(' v hash: '); eprintln(vcurrent_hash()) + $if !vinix && !native { + eprint(' pid: '); C.fprintf(C.stderr, c'%p\n', voidptr(v_getpid())) + eprint(' tid: '); C.fprintf(C.stderr, c'%p\n', voidptr(v_gettid())) + } + eprintln('=========================================') + flush_stdout() + // vfmt on + $if native { + C.exit(1) // TODO: native backtraces + } $else $if exit_after_panic_message ? { + C.exit(1) + } $else $if no_backtrace ? { + C.exit(1) + } $else { + $if tinyc { + $if panics_break_into_debugger ? { + break_if_debugger_attached() + } $else { + C.tcc_backtrace(c'Backtrace') + } + C.exit(1) + } + $if use_libbacktrace ? { + eprint_libbacktrace(1) + } $else { + print_backtrace_skipping_top_frames(1) + } + $if panics_break_into_debugger ? { + break_if_debugger_attached() + } + C.exit(1) + } + } + C.exit(1) +} + +// panic_option_not_set is called by V, when you use option error propagation in your main function. +// It ends the program with a panic. +@[noreturn] +pub fn panic_option_not_set(s string) { + panic('option not set (' + s + ')') +} + +// panic_result_not_set is called by V, when you use result error propagation in your main function +// It ends the program with a panic. +@[noreturn] +pub fn panic_result_not_set(s string) { + panic('result not set (' + s + ')') +} + +// panic prints a nice error message, then exits the process with exit code of 1. +// It also shows a backtrace on most platforms. +@[noreturn] +pub fn panic(s string) { + // Note: be careful to not use string interpolation here: + $if freestanding { + bare_panic(s) + } $else { + // vfmt off + flush_stdout() + eprint('V panic: ') + eprintln(s) + eprint(' v hash: ') + eprintln(vcurrent_hash()) + $if !vinix && !native { + eprint(' pid: '); C.fprintf(C.stderr, c'%p\n', voidptr(v_getpid())) + eprint(' tid: '); C.fprintf(C.stderr, c'%p\n', voidptr(v_gettid())) + } + flush_stdout() + // vfmt on + $if native { + C.exit(1) // TODO: native backtraces + } $else $if exit_after_panic_message ? { + C.exit(1) + } $else $if no_backtrace ? { + C.exit(1) + } $else { + $if tinyc { + $if panics_break_into_debugger ? { + break_if_debugger_attached() + } $else { + C.tcc_backtrace(c'Backtrace') + } + C.exit(1) + } + $if use_libbacktrace ? { + eprint_libbacktrace(1) + } $else { + print_backtrace_skipping_top_frames(1) + } + $if panics_break_into_debugger ? { + break_if_debugger_attached() + } + C.exit(1) + } + } + C.exit(1) +} + +// return a C-API error message matching to `errnum` +pub fn c_error_number_str(errnum int) string { + mut err_msg := '' + $if freestanding { + err_msg = 'error ' + errnum.str() + } $else { + $if !vinix { + c_msg := C.strerror(errnum) + err_msg = string{ + str: &u8(c_msg) + len: unsafe { C.strlen(c_msg) } + is_lit: 1 + } + } + } + return err_msg +} + +// panic_n prints an error message, followed by the given number, then exits the process with exit code of 1. +@[noreturn] +pub fn panic_n(s string, number1 i64) { + panic(s + impl_i64_to_string(number1)) +} + +// panic_n2 prints an error message, followed by the given numbers, then exits the process with exit code of 1. +@[noreturn] +pub fn panic_n2(s string, number1 i64, number2 i64) { + panic(s + impl_i64_to_string(number1) + ', ' + impl_i64_to_string(number2)) +} + +// panic_n3 prints an error message, followed by the given numbers, then exits the process with exit code of 1. +@[noreturn] +fn panic_n3(s string, number1 i64, number2 i64, number3 i64) { + panic(s + impl_i64_to_string(number1) + ', ' + impl_i64_to_string(number2) + ', ' + + impl_i64_to_string(number3)) +} + +// panic with a C-API error message matching `errnum` +@[noreturn] +pub fn panic_error_number(basestr string, errnum int) { + panic(basestr + c_error_number_str(errnum)) +} diff --git a/vlib/builtin/printing.c.v b/vlib/builtin/printing.c.v new file mode 100644 index 000000000..c3690633a --- /dev/null +++ b/vlib/builtin/printing.c.v @@ -0,0 +1,195 @@ +module builtin + +// eprintln prints a message with a line end, to stderr. Both stderr and stdout are flushed. +pub fn eprintln(s string) { + if s.str == 0 { + eprintln('eprintln(NIL)') + return + } + $if builtin_print_use_fprintf ? { + C.fprintf(C.stderr, c'%.*s\n', s.len, s.str) + return + } + $if freestanding { + // flushing is only a thing with C.FILE from stdio.h, not on the syscall level + bare_eprint(s.str, u64(s.len)) + bare_eprint(c'\n', 1) + } $else $if ios { + C.WrappedNSLog(s.str) + } $else { + flush_stdout() + flush_stderr() + // eprintln is used in panics, so it should not fail at all + $if android && !termux { + C.android_print(C.stderr, c'%.*s\n', s.len, s.str) + } + _writeln_to_fd(2, s) + flush_stderr() + } +} + +// eprint prints a message to stderr. Both stderr and stdout are flushed. +pub fn eprint(s string) { + if s.str == 0 { + eprint('eprint(NIL)') + return + } + $if builtin_print_use_fprintf ? { + C.fprintf(C.stderr, c'%.*s', s.len, s.str) + return + } + $if freestanding { + // flushing is only a thing with C.FILE from stdio.h, not on the syscall level + bare_eprint(s.str, u64(s.len)) + } $else $if ios { + // TODO: Implement a buffer as NSLog doesn't have a "print" + C.WrappedNSLog(s.str) + } $else { + flush_stdout() + flush_stderr() + $if android && !termux { + C.android_print(C.stderr, c'%.*s', s.len, s.str) + } + _write_buf_to_fd(2, s.str, s.len) + flush_stderr() + } +} + +pub fn flush_stdout() { + $if freestanding { + not_implemented := 'flush_stdout is not implemented\n' + bare_eprint(not_implemented.str, u64(not_implemented.len)) + } $else { + C.fflush(C.stdout) + } +} + +pub fn flush_stderr() { + $if freestanding { + not_implemented := 'flush_stderr is not implemented\n' + bare_eprint(not_implemented.str, u64(not_implemented.len)) + } $else { + C.fflush(C.stderr) + } +} + +// unbuffer_stdout will turn off the default buffering done for stdout. +// It will affect all consequent print and println calls, effectively making them behave like +// eprint and eprintln do. It is useful for programs, that want to produce progress bars, without +// cluttering your code with a flush_stdout() call after every print() call. It is also useful for +// programs (sensors), that produce small chunks of output, that you want to be able to process +// immediately. +// Note 1: if used, *it should be called at the start of your program*, before using +// print or println(). +// Note 2: most libc implementations, have logic that use line buffering for stdout, when the output +// stream is connected to an interactive device, like a terminal, and otherwise fully buffer it, +// which is good for the output performance for programs that can produce a lot of output (like +// filters, or cat etc), but bad for latency. Normally, it is usually what you want, so it is the +// default for V programs too. +// See https://www.gnu.org/software/libc/manual/html_node/Buffering-Concepts.html . +// See https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_05 . +pub fn unbuffer_stdout() { + $if freestanding { + not_implemented := 'unbuffer_stdout is not implemented\n' + bare_eprint(not_implemented.str, u64(not_implemented.len)) + } $else { + unsafe { C.setbuf(C.stdout, 0) } + } +} + +// print prints a message to stdout. Note that unlike `eprint`, stdout is not automatically flushed. +@[manualfree] +pub fn print(s string) { + $if builtin_print_use_fprintf ? { + C.fprintf(C.stdout, c'%.*s', s.len, s.str) + return + } + $if android && !termux { + C.android_print(C.stdout, c'%.*s\n', s.len, s.str) + } $else $if ios { + // TODO: Implement a buffer as NSLog doesn't have a "print" + C.WrappedNSLog(s.str) + } $else $if freestanding { + bare_print(s.str, u64(s.len)) + } $else { + _write_buf_to_fd(1, s.str, s.len) + } +} + +// println prints a message with a line end, to stdout. Note that unlike `eprintln`, stdout is not automatically flushed. +@[manualfree] +pub fn println(s string) { + if s.str == 0 { + println('println(NIL)') + return + } + $if noprintln ? { + return + } + $if builtin_print_use_fprintf ? { + C.fprintf(C.stdout, c'%.*s\n', s.len, s.str) + return + } + $if android && !termux { + C.android_print(C.stdout, c'%.*s\n', s.len, s.str) + return + } $else $if ios { + C.WrappedNSLog(s.str) + return + } $else $if freestanding { + bare_print(s.str, u64(s.len)) + bare_print(c'\n', 1) + return + } $else { + _writeln_to_fd(1, s) + } +} + +@[manualfree] +fn _writeln_to_fd(fd int, s string) { + $if builtin_writeln_should_write_at_once ? { + unsafe { + buf_len := s.len + 1 // space for \n + mut buf := malloc(buf_len) + C.memcpy(buf, s.str, s.len) + buf[s.len] = `\n` + _write_buf_to_fd(fd, buf, buf_len) + free(buf) + } + } $else { + lf := u8(`\n`) + _write_buf_to_fd(fd, s.str, s.len) + _write_buf_to_fd(fd, &lf, 1) + } +} + +@[manualfree] +fn _write_buf_to_fd(fd int, buf &u8, buf_len int) { + if buf_len <= 0 { + return + } + mut ptr := unsafe { buf } + mut remaining_bytes := isize(buf_len) + mut x := isize(0) + $if freestanding || vinix || builtin_write_buf_to_fd_should_use_c_write ? { + unsafe { + for remaining_bytes > 0 { + x = C.write(fd, ptr, remaining_bytes) + ptr += x + remaining_bytes -= x + } + } + } $else { + mut stream := voidptr(C.stdout) + if fd == 2 { + stream = voidptr(C.stderr) + } + unsafe { + for remaining_bytes > 0 { + x = isize(C.fwrite(ptr, 1, remaining_bytes, stream)) + ptr += x + remaining_bytes -= x + } + } + } +} -- 2.39.5