added: `memdebug` allocator
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Tue, 31 Oct 2023 00:52:54 +0000 (19:52 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Tue, 31 Oct 2023 00:52:54 +0000 (19:52 -0500)
core/alloc/alloc.onyx
core/alloc/memdebug.onyx [new file with mode: 0644]
core/encoding/osad.onyx

index de3eb988f239548f48ad92eed05ec1a5e15c3bfd..7b98a6600533d0411472db6af97475f85ef10190 100644 (file)
@@ -8,6 +8,7 @@ package core.alloc
 #load "./pool"
 #load "./logging"
 #load "./gc"
+#load "./memdebug"
 
 use core.memory
 use core.intrinsics.types {type_is_function}
diff --git a/core/alloc/memdebug.onyx b/core/alloc/memdebug.onyx
new file mode 100644 (file)
index 0000000..c700d5f
--- /dev/null
@@ -0,0 +1,127 @@
+package core.alloc.memdebug
+#allow_stale_code
+
+use core {Result}
+use core.alloc
+use core.net
+use core.io
+use core.iter
+use core.encoding.osad
+use runtime
+
+VERSION :: 1
+DEFAULT_PORT :: 4004
+
+MemDebugState :: struct {
+    wrapped_allocator: Allocator;
+    listen_addr: net.SocketAddress;
+
+    socket: ? net.Socket;
+    writer: ? io.Writer;
+}
+
+MemDebugMsg :: union {
+    Start: struct {
+        version: u32;
+    };
+
+    Action: struct {
+        action: AllocationAction;
+        oldptr: u32;
+        newptr: u32;
+        size: u32;
+        align: u32;
+        trace: [] MemDebugStackNode;
+    };
+}
+
+MemDebugStackNode :: struct {
+    file: str;
+    line: u32;
+    current_line: u32;
+    func_name: str;
+}
+
+
+make :: (a: Allocator, listen_addr: &net.SocketAddress) -> MemDebugState {
+    return .{
+        a,
+        *listen_addr,
+        .{ None = .{} },
+        .{ None = .{} }
+    };
+}
+
+free :: (m: &MemDebugState) {
+    io.writer_free(m.writer->unwrap_ptr());
+    m.socket->unwrap_ptr()->close();
+}
+
+wait_for_connection :: (m: &MemDebugState) -> Result(void, io.Error) {
+    listen_socket := net.socket_create(.Inet, .Stream, .ANY)?;
+    listen_socket->option(.ReuseAddress, true);
+    listen_socket->bind(&m.listen_addr);
+    listen_socket->listen(1);
+    result := listen_socket->accept()?;
+
+    m.socket = .{ Some = result.socket };
+    m.writer = .{ Some = io.writer_make(m.socket->unwrap_ptr(), 0) };
+
+    memdebug_send_message(m, .{ Start = .{ version = VERSION } });
+}
+
+enable_in_scope :: macro (a: Allocator, port := DEFAULT_PORT) {
+    use core.alloc.memdebug
+
+    addr: net.SocketAddress;
+    net.make_ipv4_address(&addr, 0, ~~port);
+
+    old_allocator := a;
+    dbg := memdebug.make(old_allocator, &addr);
+    a = alloc.as_allocator(&dbg);
+
+    memdebug.wait_for_connection(&dbg);
+
+    defer memdebug.free(&dbg);
+}
+
+
+#overload
+alloc.as_allocator :: (memdebug: &MemDebugState) => Allocator.{ memdebug, memdebug_proc }
+
+#local
+memdebug_proc :: (m: &MemDebugState, action: AllocationAction, size: u32, align: u32, oldptr: rawptr) -> rawptr {
+    newptr := m.wrapped_allocator.func(m.wrapped_allocator.data, action, size, align, oldptr);
+
+    trace := iter.as_iter(runtime.info.get_stack_trace())
+        |> iter.map(frame => {
+            info := frame.info;
+
+            return MemDebugStackNode.{
+                info.file, info.line, frame.current_line, info.func_name
+            };
+        })
+        |> iter.collect(context.temp_allocator);
+
+    memdebug_send_message(m, .{
+        Action = .{
+            action,
+            ~~oldptr,
+            ~~newptr,
+            size,
+            align,
+            trace
+        }
+    });
+
+    return newptr;
+}
+
+
+#local
+memdebug_send_message :: (m: &MemDebugState, msg: MemDebugMsg) {
+    success := osad.serialize(msg, m.writer->unwrap_ptr());
+    if !success {
+        logf(.Warning, "MemDebug logging failed when sending.");
+    }
+}
index e311571453b21511335f5aa055997c3c76dd7032..aaa63d5e9d95e517eeeb798ad61e3f6d5e5b7c82 100644 (file)
@@ -106,13 +106,39 @@ serialize :: (v: any, w: &io.Writer) -> bool {
                 try(serialize(any.{ base + member.offset, member.type }, w));
             }
         }
+
+        case .Union {
+            u_info := cast(&type_info.Type_Info_Union, info);
+
+            base: [&] u8 = ~~v.data;
+
+            tag_value: u64;
+            switch e := u_info.tag_enum->info()->as_enum(); e.backing_type {
+                case i8,  u8  do tag_value = cast(u64) *(cast(&u8) v.data);
+                case i16, u16 do tag_value = cast(u64) *(cast(&u16) v.data);
+                case i32, u32 do tag_value = cast(u64) *(cast(&u32) v.data);
+                case i64, u64 do tag_value = cast(u64) *(cast(&u64) v.data);
+                case #default do assert(false, "Bad union backing type");
+            }
+
+            variant := array.first(u_info.variants, [x](x.tag_value == ~~tag_value));
+
+            if !variant {
+                output_u32(w, 0xffffffff);
+                break;
+            }
+
+            output_u32(w, ~~tag_value);
+
+            try(serialize(any.{ base + u_info.alignment, variant.type }, w));
+        }
     }
 
     return true;
 
 
     output_u32 :: macro (w: &io.Writer, i: u32) {
-        v := i;
+        v: u32 = i;
         io.write_str(w, str.{ ~~&v, 4 });
     }
 }
@@ -155,7 +181,8 @@ deserialize :: (target: rawptr, type: type_expr, r: &io.Reader, allocator := con
 
         case .Basic {
             err := io.read_fill_buffer(r, .{ target, info.size });
-            assert(err == .None, "Deserialize expected to be able to read all the bytes.");
+            // if err != .None do logf(.Warning, "Deserialize hit end of file.");
+            return err == .None;
         }
 
         case .Enum {
@@ -211,6 +238,28 @@ deserialize :: (target: rawptr, type: type_expr, r: &io.Reader, allocator := con
                 try(deserialize(base + member.offset, member.type, r, allocator));
             }
         }
+
+        case .Union {
+            u_info := cast(&type_info.Type_Info_Union, info);
+
+            base: [&] u8 = target;
+            variant_value := read_u32(r);
+            variant := array.first(u_info.variants, [x](x.tag_value == ~~variant_value));
+            if !variant {
+                memory.set(target, 0, u_info.size);
+                break;
+            }
+
+            switch e := u_info.tag_enum->info()->as_enum(); e.backing_type {
+                case i8,  u8  do *(cast(&u8)  target) = ~~variant_value;
+                case i16, u16 do *(cast(&u16) target) = ~~variant_value;
+                case i32, u32 do *(cast(&u32) target) = ~~variant_value;
+                case i64, u64 do *(cast(&u64) target) = ~~variant_value;
+                case #default do assert(false, "Bad union backing type");
+            }
+
+            try(deserialize(base + u_info.alignment, variant.type, r));
+        }
     }
 
     return true;