Browse Source

spall instrumentation

Laytan Laats 1 year ago
parent
commit
ca6300c860

+ 13 - 0
core/prof/spall/doc.odin

@@ -22,5 +22,18 @@ main :: proc() {
 		foo()
 		foo()
     }
     }
 }
 }
+
+// Automatic profiling of every procedure:
+
+@(instrumentation_enter)
+spall_enter :: proc "contextless" (proc_address, call_site_return_address: rawptr, loc: runtime.Source_Code_Location) {
+	spall._buffer_begin(&spall_ctx, &spall_buffer, "", "", loc)
+}
+
+@(instrumentation_exit)
+spall_exit :: proc "contextless" (proc_address, call_site_return_address: rawptr, loc: runtime.Source_Code_Location) {
+	spall._buffer_end(&spall_ctx, &spall_buffer)
+}
+
 */
 */
 package spall
 package spall

+ 25 - 10
core/prof/spall/spall.odin

@@ -3,7 +3,6 @@ package spall
 import "core:os"
 import "core:os"
 import "core:time"
 import "core:time"
 import "base:intrinsics"
 import "base:intrinsics"
-import "core:mem"
 
 
 // File Format
 // File Format
 
 
@@ -111,9 +110,10 @@ buffer_create :: proc(data: []byte, tid: u32 = 0, pid: u32 = 0) -> (buffer: Buff
 	return
 	return
 }
 }
 
 
-buffer_flush :: proc(ctx: ^Context, buffer: ^Buffer) {
+@(no_instrumentation)
+buffer_flush :: proc "contextless" (ctx: ^Context, buffer: ^Buffer) #no_bounds_check /* bounds check would segfault instrumentation */ {
 	start := _trace_now(ctx)
 	start := _trace_now(ctx)
-	os.write(ctx.fd, buffer.data[:buffer.head])
+	write(ctx.fd, buffer.data[:buffer.head])
 	buffer.head = 0
 	buffer.head = 0
 	end := _trace_now(ctx)
 	end := _trace_now(ctx)
 
 
@@ -140,15 +140,16 @@ _scoped_buffer_end :: proc(ctx: ^Context, buffer: ^Buffer, _, _: string, _ := #c
 	_buffer_end(ctx, buffer)
 	_buffer_end(ctx, buffer)
 }
 }
 
 
-
+@(no_instrumentation)
 _trace_now :: proc "contextless" (ctx: ^Context) -> f64 {
 _trace_now :: proc "contextless" (ctx: ^Context) -> f64 {
 	if !ctx.precise_time {
 	if !ctx.precise_time {
-		return f64(time.tick_now()._nsec) / 1_000
+		return f64(tick_now()) / 1_000
 	}
 	}
 
 
 	return f64(intrinsics.read_cycle_counter())
 	return f64(intrinsics.read_cycle_counter())
 }
 }
 
 
+@(no_instrumentation)
 _build_header :: proc "contextless" (buffer: []u8, timestamp_scale: f64) -> (header_size: int, ok: bool) #optional_ok {
 _build_header :: proc "contextless" (buffer: []u8, timestamp_scale: f64) -> (header_size: int, ok: bool) #optional_ok {
 	header_size = size_of(Manual_Header)
 	header_size = size_of(Manual_Header)
 	if header_size > len(buffer) {
 	if header_size > len(buffer) {
@@ -164,7 +165,8 @@ _build_header :: proc "contextless" (buffer: []u8, timestamp_scale: f64) -> (hea
 	return
 	return
 }
 }
 
 
-_build_begin :: proc "contextless" (buffer: []u8, name: string, args: string, ts: f64, tid: u32, pid: u32) -> (event_size: int, ok: bool) #optional_ok {
+@(no_instrumentation)
+_build_begin :: #force_inline proc "contextless" (buffer: []u8, name: string, args: string, ts: f64, tid: u32, pid: u32) -> (event_size: int, ok: bool) #optional_ok #no_bounds_check /* bounds check would segfault instrumentation */ {
 	ev := (^Begin_Event)(raw_data(buffer))
 	ev := (^Begin_Event)(raw_data(buffer))
 	name_len := min(len(name), 255)
 	name_len := min(len(name), 255)
 	args_len := min(len(args), 255)
 	args_len := min(len(args), 255)
@@ -180,13 +182,14 @@ _build_begin :: proc "contextless" (buffer: []u8, name: string, args: string, ts
 	ev.ts   = f64le(ts)
 	ev.ts   = f64le(ts)
 	ev.name_len = u8(name_len)
 	ev.name_len = u8(name_len)
 	ev.args_len = u8(args_len)
 	ev.args_len = u8(args_len)
-	mem.copy(raw_data(buffer[size_of(Begin_Event):]), raw_data(name), name_len)
-	mem.copy(raw_data(buffer[size_of(Begin_Event)+name_len:]), raw_data(args), args_len)
+	intrinsics.mem_copy_non_overlapping(raw_data(buffer[size_of(Begin_Event):]), raw_data(name), name_len)
+	intrinsics.mem_copy_non_overlapping(raw_data(buffer[size_of(Begin_Event)+name_len:]), raw_data(args), args_len)
 	ok = true
 	ok = true
 
 
 	return
 	return
 }
 }
 
 
+@(no_instrumentation)
 _build_end :: proc "contextless" (buffer: []u8, ts: f64, tid: u32, pid: u32) -> (event_size: int, ok: bool) #optional_ok {
 _build_end :: proc "contextless" (buffer: []u8, ts: f64, tid: u32, pid: u32) -> (event_size: int, ok: bool) #optional_ok {
 	ev := (^End_Event)(raw_data(buffer))
 	ev := (^End_Event)(raw_data(buffer))
 	event_size = size_of(End_Event)
 	event_size = size_of(End_Event)
@@ -203,7 +206,8 @@ _build_end :: proc "contextless" (buffer: []u8, ts: f64, tid: u32, pid: u32) ->
 	return
 	return
 }
 }
 
 
-_buffer_begin :: proc(ctx: ^Context, buffer: ^Buffer, name: string, args: string = "", location := #caller_location) {
+@(no_instrumentation)
+_buffer_begin :: proc "contextless" (ctx: ^Context, buffer: ^Buffer, name: string, args: string = "", location := #caller_location) #no_bounds_check /* bounds check would segfault instrumentation */ {
 	if buffer.head + BEGIN_EVENT_MAX > len(buffer.data) {
 	if buffer.head + BEGIN_EVENT_MAX > len(buffer.data) {
 		buffer_flush(ctx, buffer)
 		buffer_flush(ctx, buffer)
 	}
 	}
@@ -211,7 +215,8 @@ _buffer_begin :: proc(ctx: ^Context, buffer: ^Buffer, name: string, args: string
 	buffer.head += _build_begin(buffer.data[buffer.head:], name, args, _trace_now(ctx), buffer.tid, buffer.pid)
 	buffer.head += _build_begin(buffer.data[buffer.head:], name, args, _trace_now(ctx), buffer.tid, buffer.pid)
 }
 }
 
 
-_buffer_end :: proc(ctx: ^Context, buffer: ^Buffer) {
+@(no_instrumentation)
+_buffer_end :: proc "contextless" (ctx: ^Context, buffer: ^Buffer) #no_bounds_check /* bounds check would segfault instrumentation */ {
 	ts := _trace_now(ctx)
 	ts := _trace_now(ctx)
 
 
 	if buffer.head + size_of(End_Event) > len(buffer.data) {
 	if buffer.head + size_of(End_Event) > len(buffer.data) {
@@ -220,3 +225,13 @@ _buffer_end :: proc(ctx: ^Context, buffer: ^Buffer) {
 
 
 	buffer.head += _build_end(buffer.data[buffer.head:], ts, buffer.tid, buffer.pid)
 	buffer.head += _build_end(buffer.data[buffer.head:], ts, buffer.tid, buffer.pid)
 }
 }
+
+@(no_instrumentation)
+write :: proc "contextless" (fd: os.Handle, buf: []byte) -> (n: int, err: os.Errno) {
+	return _write(fd, buf)
+}
+
+@(no_instrumentation)
+tick_now :: proc "contextless" () -> (ns: i64) {
+	return _tick_now()
+}

+ 52 - 0
core/prof/spall/spall_darwin.odin

@@ -0,0 +1,52 @@
+//+private
+package spall
+
+// Only for types.
+import "core:os"
+
+foreign import system "system:System.framework"
+
+timespec :: struct {
+	tv_sec:  i64, // seconds
+	tv_nsec: i64, // nanoseconds
+}
+
+foreign system {
+	@(link_name="__error") __error :: proc() -> ^i32 ---
+	@(link_name="write") _unix_write :: proc(handle: os.Handle, buffer: rawptr, count: uint) -> int ---
+	@(link_name="clock_gettime") _unix_clock_gettime :: proc(clock_id: u64, timespec: ^timespec) -> i32 ---
+}
+
+@(no_instrumentation)
+get_last_error :: proc "contextless" () -> int {
+	return int(__error()^)
+}
+
+MAX_RW :: 0x7fffffff
+
+@(no_instrumentation)
+_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (n: int, err: os.Errno) #no_bounds_check /* bounds check would segfault instrumentation */ {
+	if len(data) == 0 {
+		return 0, os.ERROR_NONE
+	}
+
+	for n < len(data) {
+		chunk := data[:min(len(data), MAX_RW)]
+		written := _unix_write(fd, raw_data(chunk), len(chunk))
+		if written < 0 {
+			return n, os.Errno(get_last_error())
+		}
+		n += written
+	}
+
+	return n, os.ERROR_NONE
+}
+
+CLOCK_MONOTONIC_RAW :: 4 // NOTE(tetra): "RAW" means: Not adjusted by NTP.
+
+@(no_instrumentation)
+_tick_now :: proc "contextless" () -> (ns: i64) {
+	t: timespec
+	_unix_clock_gettime(CLOCK_MONOTONIC_RAW, &t)
+	return t.tv_sec*1e9 + t.tv_nsec
+}

+ 36 - 0
core/prof/spall/spall_linux.odin

@@ -0,0 +1,36 @@
+//+private
+package spall
+
+// Only for types and constants.
+import "core:os"
+
+// Package is `//+no-instrumentation`, safe to use.
+import "core:sys/linux"
+
+MAX_RW :: 0x7fffffff
+
+@(no_instrumentation)
+_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (n: int, err: os.Errno) #no_bounds_check /* bounds check would segfault instrumentation */ {
+	if len(data) == 0 {
+		return 0, os.ERROR_NONE
+	}
+	
+	for n < len(data) {
+		chunk := data[:min(len(data), MAX_RW)]
+		written, errno := linux.write(linux.Fd(fd), chunk)
+		if errno != .NONE {
+			return n, os.Errno(errno)
+		}
+		n += written
+	}
+
+	return n, os.ERROR_NONE
+}
+
+CLOCK_MONOTONIC_RAW :: 4 // NOTE(tetra): "RAW" means: Not adjusted by NTP.
+
+@(no_instrumentation)
+_tick_now :: proc "contextless" () -> (ns: i64) {
+	t, _ := linux.clock_gettime(.MONOTONIC_RAW)
+	return i64(t.time_sec)*1e9 + i64(t.time_nsec)
+}

+ 53 - 0
core/prof/spall/spall_windows.odin

@@ -0,0 +1,53 @@
+//+private
+package spall
+
+// Only for types.
+import "core:os"
+
+// Package is `//+no-instrumentation`, safe to use.
+import win32 "core:sys/windows"
+
+MAX_RW :: 1<<30
+
+@(no_instrumentation)
+_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (int, os.Errno) #no_bounds_check /* bounds check would segfault instrumentation */ {
+	if len(data) == 0 {
+		return 0, os.ERROR_NONE
+	}
+
+	single_write_length: win32.DWORD
+	total_write: i64
+	length := i64(len(data))
+
+	for total_write < length {
+		remaining := length - total_write
+		to_write := win32.DWORD(min(i32(remaining), MAX_RW))
+
+		e := win32.WriteFile(win32.HANDLE(fd), &data[total_write], to_write, &single_write_length, nil)
+		if single_write_length <= 0 || !e {
+			err := os.Errno(win32.GetLastError())
+			return int(total_write), err
+		}
+		total_write += i64(single_write_length)
+	}
+	return int(total_write), os.ERROR_NONE
+}
+
+@(no_instrumentation)
+_tick_now :: proc "contextless" () -> (ns: i64) {
+	mul_div_u64 :: #force_inline proc "contextless" (val, num, den: i64) -> i64 {
+		q := val / den
+		r := val % den
+		return q * num + r * num / den
+	}
+
+	@thread_local qpc_frequency: win32.LARGE_INTEGER
+
+	if qpc_frequency == 0 {
+		win32.QueryPerformanceFrequency(&qpc_frequency)
+	}
+	now: win32.LARGE_INTEGER
+	win32.QueryPerformanceCounter(&now)
+
+	return mul_div_u64(i64(now), 1e9, i64(qpc_frequency))
+}

+ 1 - 0
core/sys/linux/helpers.odin

@@ -1,4 +1,5 @@
 //+build linux
 //+build linux
+//+no-instrumentation
 package linux
 package linux
 
 
 import "base:intrinsics"
 import "base:intrinsics"

+ 6 - 1
core/sys/linux/sys.odin

@@ -1,3 +1,4 @@
+//+no-instrumentation
 package linux
 package linux
 
 
 import "base:intrinsics"
 import "base:intrinsics"
@@ -2383,7 +2384,11 @@ timer_delete :: proc "contextless" (timer: Timer) -> (Errno) {
 
 
 // TODO(flysand): clock_settime
 // TODO(flysand): clock_settime
 
 
-// TODO(flysand): clock_gettime
+clock_gettime :: proc "contextless" (clock: Clock_Id) -> (ts: Time_Spec, err: Errno) {
+	ret := syscall(SYS_clock_gettime, clock, &ts)
+	err = Errno(-ret)
+	return
+}
 
 
 // TODO(flysand): clock_getres
 // TODO(flysand): clock_getres
 
 

+ 1 - 0
core/sys/windows/kernel32.odin

@@ -1,4 +1,5 @@
 // +build windows
 // +build windows
+//+no-instrumentation
 package sys_windows
 package sys_windows
 
 
 foreign import kernel32 "system:Kernel32.lib"
 foreign import kernel32 "system:Kernel32.lib"