diff --git a/README.md b/README.md index 0599d93..98a135b 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,10 @@ Xcode Command Line Tools. The Windows Win32/WGL backend cross-compiles for `x86_64-windows-gnu`; runtime verification on Windows is pending. It requires Windows 10 version 1703 or later and an OpenGL 3.3-capable driver. +Private profiling hooks compile away in this standalone build. Tracy support +belongs to the parent repository's static demos; it adds no dependencies or +public ABI to this package. + ## Build ```sh @@ -77,3 +81,28 @@ watch completion. Input uses IMM32 composition; TSF reconversion and pen/touch are not implemented. Native UTF-16 path bytes are kept separately from the WTF-8 paths passed to Zig I/O. If a native path cannot be exported losslessly as a shell URI, URI export returns `UNSUPPORTED`; native-path export still preserves it. + + +## Optional profiling + +`uhdk.h` includes `uhdk_profile.h`: scope, frame, thread-name and named allocation/ +free macros. They compile away without evaluating arguments unless the consumer +defines `UHDK_PROFILE_BACKEND` to a quoted adapter header before including UHDK. +An adapter implements the macros documented in that header. Names require static +lifetime; pair zones on the same thread in nesting order and report frees before +releasing memory. Do not put allocation expressions inside profiling macros. + +Internal CPU and memory instrumentation is disabled by default. A static consumer +can pass `.tracy = true` to `b.dependency("uhdk", ...)` (or `-Dtracy=true` when +building the archive) and link its own Tracy 0.14.1 client configured with +`TRACY_ENABLE` and `TRACY_ON_DEMAND`. UHDK never fetches or links Tracy itself. +Shared instrumentation is unsupported. Run standalone unit tests with the default +configuration; enabled instrumentation requires the consumer's linked client. +The full repository provides an example adapter under `examples/profiling`. + +The `UHDK` memory pool measures requested Zig backing allocations, including +objects and arena blocks. Win32 explicit calloc/free blocks use a separate +`UHDK native` pool. OS/framework allocations and GPU memory are excluded. On-demand capture omits allocations made before connection, so it is not +a full live-heap snapshot. Failed allocations emit nothing; in-place resizes emit +free + alloc. Moving reallocations use alloc/copy/free to preserve event ordering. +Disabled builds use the original allocator directly. diff --git a/build.zig b/build.zig index 2e3e280..e1d12d2 100644 --- a/build.zig +++ b/build.zig @@ -5,42 +5,10 @@ pub fn build(b: *std.Build) void { if (target.result.os.tag != .macos and target.result.os.tag != .windows) @panic("Uhdk supports macOS and Windows."); const optimize = b.standardOptimizeOption(.{}); const shared = b.option(bool, "shared", "Build libuhdk as a shared library instead of static") orelse false; - const module = b.createModule(.{ - .root_source_file = b.path("src/root.zig"), - .target = target, - .optimize = optimize, - .link_libc = true, - }); - const native = b.addTranslateC(.{ - .root_source_file = b.path("src/native.h"), - .target = target, - .optimize = optimize, - }); - native.addIncludePath(b.path("include")); - module.addImport("native", native.createModule()); - module.addIncludePath(b.path("include")); - module.addIncludePath(b.path("src")); - if (target.result.os.tag == .macos) { - module.addCSourceFile(.{ - .file = b.path("src/macos/native.m"), - .flags = &.{ - "-fobjc-arc", "-fblocks", "-Wno-deprecated-declarations", - "-Wall", "-Wextra", "-Werror", - }, - }); - module.linkFramework("Cocoa", .{}); - module.linkFramework("OpenGL", .{}); - module.linkFramework("CoreServices", .{}); - } else { - module.addCSourceFile(.{ .file = b.path("src/windows/native.c"), .flags = &.{ "-std=c11", "-Wall", "-Wextra", "-Werror", "-DUNICODE", "-D_UNICODE" } }); - for ([_][]const u8{ "user32", "gdi32", "opengl32", "shell32", "ole32", "uuid", "imm32", "comctl32", "shlwapi", "advapi32" }) |name| module.linkSystemLibrary(name, .{}); - } - module.addCSourceFile(.{ .file = b.path("src/glad.c"), .flags = if (shared) &.{ "-std=c99", "-DGLAD_API_CALL_EXPORT", "-DGLAD_API_CALL_EXPORT_BUILD" } else &.{"-std=c99"} }); - const lib = b.addLibrary(.{ - .name = "uhdk", - .linkage = if (shared) .dynamic else .static, - .root_module = module, - }); + const tracy = b.option(bool, "tracy", "Emit Tracy 0.14.1 on-demand hooks; consumer must link the client") orelse false; + if (tracy and shared) @panic("Tracy instrumentation requires static linking"); + const lib = createLibraryNamed(b, target, optimize, shared, tracy, "uhdk"); + const module = lib.root_module; const install_lib = b.addInstallArtifact(lib, .{}); const install_headers = b.addInstallDirectory(.{ .source_dir = b.path("include"), @@ -72,3 +40,64 @@ pub fn build(b: *std.Build) void { }); public_module.linkLibrary(lib); } + +// The parent development build uses this to create a separate, uninstalled +// instrumented archive. Standalone consumers may opt in and supply their client. +pub fn createLibrary(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, shared: bool, instrumented: bool) *std.Build.Step.Compile { + return createLibraryNamed(b, target, optimize, shared, instrumented, if (instrumented) "uhdk-instrumented" else "uhdk"); +} + +fn createLibraryNamed(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, shared: bool, instrumented: bool, artifact_name: []const u8) *std.Build.Step.Compile { + std.debug.assert(!shared or !instrumented); + const module = b.createModule(.{ + .root_source_file = b.path("src/root.zig"), + .target = target, + .optimize = optimize, + .link_libc = true, + }); + const native = b.addTranslateC(.{ + .root_source_file = b.path("src/native.h"), + .target = target, + .optimize = optimize, + }); + native.addIncludePath(b.path("include")); + module.addImport("native", native.createModule()); + module.addImport("profile", createProfileModule(b, target, optimize, instrumented)); + module.addIncludePath(b.path("include")); + module.addIncludePath(b.path("src")); + if (instrumented) module.addCMacro("UHDK_INTERNAL_TRACY", "1"); + if (target.result.os.tag == .macos) { + module.addCSourceFile(.{ + .file = b.path("src/macos/native.m"), + .flags = &.{ + "-fobjc-arc", "-fblocks", "-Wno-deprecated-declarations", + "-Wall", "-Wextra", "-Werror", + }, + }); + module.linkFramework("Cocoa", .{}); + module.linkFramework("OpenGL", .{}); + module.linkFramework("CoreServices", .{}); + } else { + module.addCSourceFile(.{ .file = b.path("src/windows/native.c"), .flags = &.{ "-std=c11", "-Wall", "-Wextra", "-Werror", "-DUNICODE", "-D_UNICODE" } }); + for ([_][]const u8{ "user32", "gdi32", "opengl32", "shell32", "ole32", "uuid", "imm32", "comctl32", "shlwapi", "advapi32" }) |name| module.linkSystemLibrary(name, .{}); + } + module.addCSourceFile(.{ .file = b.path("src/glad.c"), .flags = if (shared) &.{ "-std=c99", "-DGLAD_API_CALL_EXPORT", "-DGLAD_API_CALL_EXPORT_BUILD" } else &.{"-std=c99"} }); + const lib = b.addLibrary(.{ + .name = artifact_name, + .linkage = if (shared) .dynamic else .static, + .root_module = module, + }); + return lib; +} + +pub fn createProfileModule(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, enabled: bool) *std.Build.Module { + const options = b.addOptions(); + options.addOption(bool, "enabled", enabled); + const profile = b.createModule(.{ + .root_source_file = b.path("src/Profile.zig"), + .target = target, + .optimize = optimize, + }); + profile.addOptions("profile_options", options); + return profile; +} diff --git a/include/uhdk.h b/include/uhdk.h index 82466d0..a2c1b09 100644 --- a/include/uhdk.h +++ b/include/uhdk.h @@ -48,6 +48,7 @@ */ #include +#include "uhdk_profile.h" #ifndef UHDK_API #if defined(_WIN32) && defined(UHDK_SHARED) diff --git a/include/uhdk_profile.h b/include/uhdk_profile.h new file mode 100644 index 0000000..bcd9f4b --- /dev/null +++ b/include/uhdk_profile.h @@ -0,0 +1,28 @@ +#ifndef UHDK_PROFILE_H_INCLUDED +#define UHDK_PROFILE_H_INCLUDED + +/* Optional, consumer-owned instrumentation; no exported UHDK ABI or dependency. + * Define UHDK_PROFILE_BACKEND to a quoted adapter header before including uhdk.h. + * That header implements the macros below. Without it, arguments are not evaluated. + * Names (zone, thread, pool) must be string literals or have static lifetime. + * C zones: UHDK_PROFILE_BEGIN(token, "name"); ... UHDK_PROFILE_END(token); + * End on the same thread, in reverse nesting order, including early exits. + * C++ may use UHDK_PROFILE_SCOPE("name") for automatic scope exit. + * Memory: report successful non-null allocations, then free BEFORE releasing + * storage. A successful resize is free + alloc; failed allocations emit nothing. + * A pointer must use the same pool name for allocation and free. These hooks do + * not allocate/free memory themselves, intercept malloc, or transfer ownership. + */ +#ifdef UHDK_PROFILE_BACKEND +#include UHDK_PROFILE_BACKEND +#else +#define UHDK_PROFILE_BEGIN(token, name) ((void)0) +#define UHDK_PROFILE_END(token) ((void)0) +#define UHDK_PROFILE_SCOPE(name) ((void)0) +#define UHDK_PROFILE_FRAME() ((void)0) +#define UHDK_PROFILE_THREAD(name) ((void)0) +#define UHDK_PROFILE_ALLOC(ptr, size, pool) ((void)0) +#define UHDK_PROFILE_FREE(ptr, pool) ((void)0) +#endif + +#endif diff --git a/src/Profile.zig b/src/Profile.zig new file mode 100644 index 0000000..4c6f185 --- /dev/null +++ b/src/Profile.zig @@ -0,0 +1,137 @@ +//! Private, compile-time instrumentation. Standalone UHDK builds disable it. +const std = @import("std"); +const builtin = @import("builtin"); +pub const enabled = @import("profile_options").enabled; +const verify_stack_order = builtin.mode == .Debug or builtin.mode == .ReleaseSafe; + +// Tracy 0.14.1 C ABI with TRACY_ON_DEMAND, linked only by the full repo's demos. +// No C header translation or references to Tracy symbols in disabled builds. +const SourceLocation = extern struct { + name: [*:0]const u8, + function: [*:0]const u8, + file: [*:0]const u8, + line: u32, + color: u32 = 0, +}; +const Token = extern struct { + id: u32 = 0, + active: i32 = 0, + connection: u64 = 0, +}; +extern fn ___tracy_emit_zone_begin(source: *const SourceLocation, active: i32) Token; +extern fn ___tracy_emit_zone_end(zone: Token) void; +extern fn ___tracy_emit_frame_mark(name: ?[*:0]const u8) void; +extern fn ___tracy_set_thread_name(name: [*:0]const u8) void; + +threadlocal var stack_depth: usize = 0; + +pub const Zone = if (enabled) struct { + token: Token, + depth: if (verify_stack_order) usize else void, + owner: if (verify_stack_order) std.Thread.Id else void, + + pub inline fn end(self: @This()) void { + if (verify_stack_order) { + std.debug.assert(self.owner == std.Thread.getCurrentId()); + std.debug.assert(self.depth == stack_depth); + stack_depth -= 1; + } + if (self.token.active != 0) ___tracy_emit_zone_end(self.token); + } +} else struct { + pub inline fn end(_: @This()) void {} +}; + +// The complete @src() (including column) specializes this type. Mutable static +// storage guarantees a unique address even when two records have equal fields. +// It is initialized at comptime and never rewritten, so threads can share it. +fn StaticSource(comptime source: std.builtin.SourceLocation, comptime name: [:0]const u8) type { + return struct { + var location: SourceLocation = .{ + .name = name, + .function = source.fn_name, + .file = source.file, + .line = source.line, + }; + }; +} + +pub inline fn begin(comptime source: std.builtin.SourceLocation, comptime name: [:0]const u8) Zone { + if (!enabled) return .{}; + const Statics = StaticSource(source, name); + const token = ___tracy_emit_zone_begin(&Statics.location, 1); + if (verify_stack_order) stack_depth += 1; + return .{ + // Tracy leaves id/connection unspecified for inactive zones. + .token = if (token.active != 0) token else .{}, + .depth = if (verify_stack_order) stack_depth else {}, + .owner = if (verify_stack_order) std.Thread.getCurrentId() else {}, + }; +} + +pub inline fn frame() void { + if (enabled) ___tracy_emit_frame_mark(null); +} + +pub inline fn thread(comptime name: [:0]const u8) void { + if (enabled) ___tracy_set_thread_name(name); +} + +extern fn ___tracy_emit_memory_alloc_named(ptr: *const anyopaque, size: usize, name: [*:0]const u8) void; +extern fn ___tracy_emit_memory_free_named(ptr: *const anyopaque, name: [*:0]const u8) void; + +pub inline fn alloc(ptr: *const anyopaque, size: usize, comptime pool: [:0]const u8) void { + if (enabled) ___tracy_emit_memory_alloc_named(ptr, size, pool); +} + +pub inline fn free(ptr: *const anyopaque, comptime pool: [:0]const u8) void { + if (enabled) ___tracy_emit_memory_free_named(ptr, pool); +} + +/// Keep this wrapper alive and at a stable address while its allocator is used. +/// No wrapper or instrumentation overhead when disabled. Tracks backing blocks, +/// not arena suballocations. Thread safety is inherited from the child allocator. +pub fn TrackingAllocator(comptime pool: [:0]const u8) type { + return struct { + child: std.mem.Allocator, + const Self = @This(); + pub fn allocator(self: *Self) std.mem.Allocator { + if (!enabled) return self.child; + return .{ + .ptr = self, + .vtable = &.{ + .alloc = allocate, + .resize = resize, + // Let Allocator.realloc use alloc/copy/free. A moving rawRemap + // releases the old address before we can report its free, so + // another thread could reuse it and emit an out-of-order alloc. + .remap = std.mem.Allocator.noRemap, + .free = release, + }, + }; + } + fn allocate(ctx: *anyopaque, len: usize, alignment: std.mem.Alignment, ret: usize) ?[*]u8 { + const self: *Self = @ptrCast(@alignCast(ctx)); + const ptr = self.child.rawAlloc(len, alignment, ret) orelse return null; + alloc(ptr, len, pool); + return ptr; + } + fn resize(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, len: usize, ret: usize) bool { + const self: *Self = @ptrCast(@alignCast(ctx)); + if (!self.child.rawResize(memory, alignment, len, ret)) return false; + free(memory.ptr, pool); + alloc(memory.ptr, len, pool); + return true; + } + fn release(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, ret: usize) void { + const self: *Self = @ptrCast(@alignCast(ctx)); + free(memory.ptr, pool); + self.child.rawFree(memory, alignment, ret); + } + }; +} + +var uhdk_allocator: TrackingAllocator("UHDK") = .{ .child = std.heap.smp_allocator }; +pub fn allocator() std.mem.Allocator { + return uhdk_allocator.allocator(); +} diff --git a/src/api/app.zig b/src/api/app.zig index e13416d..03490d4 100644 --- a/src/api/app.zig +++ b/src/api/app.zig @@ -1,5 +1,6 @@ //! C ABI entry points for app. const std = @import("std"); +const profile = @import("profile"); const backend_kind = if (@import("builtin").os.tag == .windows) c.UHDK_BACKEND_WINDOWS else c.UHDK_BACKEND_MACOS; const c = @import("native"); const runtime = @import("../runtime.zig"); @@ -22,7 +23,7 @@ pub export fn uhdk_app_create(desc: [*c]const c.uhdk_app_desc, out: [*c]?*c.uhdk !abi.validText(desc.*.application_id, true)) return abi.INVALID; if (desc.*.preferred_backend != c.UHDK_BACKEND_AUTO and desc.*.preferred_backend != backend_kind) return c.UHDK_STATUS_UNSUPPORTED; if (singleton) return c.UHDK_STATUS_BUSY; - const gpa = std.heap.smp_allocator; + const gpa = profile.allocator(); const backend = gpa.create(std.Io.Threaded) catch return abi.OOM; backend.* = .init(gpa, .{ .async_limit = .nothing, .concurrent_limit = .nothing }); const application = Application.create(gpa, backend.io(), abi.textBytes(desc.*.display_name)) catch { @@ -45,6 +46,7 @@ pub export fn uhdk_app_run( if (c.z_native_is_main_thread() == 0) return c.UHDK_STATUS_WRONG_THREAD; const application = runtime.fromAppHandle(handle); if (application.has_run) return abi.UNAVAILABLE; + profile.thread("UHDK platform"); runtime.simpleEvent(application, c.UHDK_EVENT_READY); const status = c.z_native_start(handle, abi.textSpan(application.name)); if (status != abi.OK) return status; @@ -94,6 +96,8 @@ pub export fn uhdk_app_poll_events( if (handle == null or out == null) return abi.INVALID; const application = runtime.fromAppHandle(handle); if (runtime.checkThread(application) != abi.OK) return c.UHDK_STATUS_WRONG_THREAD; + const poll_zone = profile.begin(@src(), "UHDK poll events (includes wait)"); + defer poll_zone.end(); runtime.destroyEvents(application.published); application.published = null; application.events.clearRetainingCapacity(); @@ -123,6 +127,8 @@ pub export fn uhdk_app_poll_events( const ns = wake_time -| current; application.available.reset(); application.unlock(); + const wait_zone = profile.begin(@src(), "UHDK wait for events"); + defer wait_zone.end(); application.available.waitTimeout(application.io, .{ .duration = .{ .raw = .fromNanoseconds(ns), .clock = .awake, diff --git a/src/api/graphics.zig b/src/api/graphics.zig index ddb390b..e0c870f 100644 --- a/src/api/graphics.zig +++ b/src/api/graphics.zig @@ -1,5 +1,6 @@ //! C ABI entry points for graphics. const std = @import("std"); +const profile = @import("profile"); const c = @import("native"); const runtime = @import("../runtime.zig"); const abi = @import("../abi.zig"); @@ -30,6 +31,8 @@ pub export fn uhdk_gl_context_destroy(handle: ?*c.uhdk_gl_context) void { } pub export fn uhdk_gl_context_begin_frame(handle: ?*c.uhdk_gl_context) abi.Status { + const zone = profile.begin(@src(), "UHDK acquire GL context"); + defer zone.end(); if (handle == null) return abi.INVALID; const object = runtime.fromObjectHandle(handle); if (object.payload.gl.bracket) return c.UHDK_STATUS_BUSY; @@ -91,6 +94,8 @@ pub export fn uhdk_gl_context_get_version( } pub export fn uhdk_gl_context_swap_buffers(handle: ?*c.uhdk_gl_context) abi.Status { + const zone = profile.begin(@src(), "UHDK present (includes vsync)"); + defer zone.end(); if (handle == null) return abi.INVALID; const object = runtime.fromObjectHandle(handle); const valid = runtime.checkObject(object, c.Z_KIND_GL); diff --git a/src/runtime.zig b/src/runtime.zig index 7af46cb..cfa8fc0 100644 --- a/src/runtime.zig +++ b/src/runtime.zig @@ -1,5 +1,6 @@ //! Queue handoff and platform scheduling. const std = @import("std"); +const profile = @import("profile"); const c = @import("native"); const abi = @import("abi.zig"); const Event = @import("Event.zig"); @@ -288,6 +289,7 @@ pub fn finish( } pub fn applicationMain(application: *Application) void { + profile.thread("UHDK application/render"); const pool = c.z_native_pool_push(); defer c.z_native_pool_pop(pool); application.lock(); @@ -451,6 +453,8 @@ pub fn fileError(err: anyerror) abi.Status { } pub fn fileStep(application: *Application, command: *Request) bool { + const zone = profile.begin(@src(), "UHDK file operation batch"); + defer zone.end(); if (isCancelled(application, command)) { finish(application, command, abi.CANCELLED, null); return true; @@ -611,6 +615,8 @@ pub fn complete( } pub fn tick(handle: ?*c.uhdk_app) void { + const zone = profile.begin(@src(), "UHDK platform dispatch"); + defer zone.end(); const application = fromAppHandle(handle); // Native tracking loops can reenter the run loop; never reenter application command. if (application.in_tick) return; diff --git a/src/windows/native.c b/src/windows/native.c index 6a2611a..a089a38 100644 --- a/src/windows/native.c +++ b/src/windows/native.c @@ -16,6 +16,27 @@ #include "native.h" #include "glad/gl.h" +/* Explicit native-owned heap blocks. Framework/COM/OS allocations retain their + * own ownership APIs and are not included in this pool. */ +#if defined(UHDK_INTERNAL_TRACY) +extern void ___tracy_emit_memory_alloc_named(const void *ptr, size_t size, const char *name); +extern void ___tracy_emit_memory_free_named(const void *ptr, const char *name); +static void *profileCalloc(size_t count, size_t size) { + void *ptr = calloc(count, size); + if (ptr) + ___tracy_emit_memory_alloc_named(ptr, count * size, "UHDK native"); + return ptr; +} +static void profileFree(void *ptr) { + if (ptr) + ___tracy_emit_memory_free_named(ptr, "UHDK native"); + free(ptr); +} +#else +#define profileCalloc calloc +#define profileFree free +#endif + #define Z_WAKE (WM_APP + 1) #define Z_DIALOG_MESSAGE (WM_APP + 2) #define Z_POPUP_MESSAGE (WM_APP + 3) @@ -103,14 +124,14 @@ static WCHAR *wide(uhdk_text text) { : 0; if (text.len && !n) return NULL; - WCHAR *s = calloc((size_t)n + 1, sizeof(WCHAR)); + WCHAR *s = profileCalloc((size_t)n + 1, sizeof(WCHAR)); if (s && n) MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text.ptr, (int)text.len, s, n); return s; } static char *utf8(const WCHAR *text, int len, uint64_t *out_len) { int n = len ? WideCharToMultiByte(CP_UTF8, 0, text, len, NULL, 0, NULL, NULL) : 0; - char *s = calloc((size_t)n + 1, 1); + char *s = profileCalloc((size_t)n + 1, 1); if (s && n) WideCharToMultiByte(CP_UTF8, 0, text, len, s, n, NULL, NULL); *out_len = (uint64_t)n; @@ -118,7 +139,7 @@ static char *utf8(const WCHAR *text, int len, uint64_t *out_len) { } static WCHAR *filePath(uhdk_file *file) { uhdk_bytes bytes = z_file_path(file); - WCHAR *s = calloc((size_t)bytes.len / 2 + 1, sizeof(WCHAR)); + WCHAR *s = profileCalloc((size_t)bytes.len / 2 + 1, sizeof(WCHAR)); if (s && bytes.len) memcpy(s, bytes.ptr, (size_t)bytes.len); return s; @@ -200,11 +221,11 @@ static void textEvent(Window *w, uint32_t type, const WCHAR *text, int length, i char *prefix = utf8(text, cursor, &position); if (prefix) { e.data.text.selection_start = position; - free(prefix); + profileFree(prefix); } } z_emit(host.app, &e); - free(s); + profileFree(s); } static void editEvent(Window *w, uint32_t command) { uhdk_event e = {0}; @@ -394,24 +415,24 @@ static void composition(Window *w, LPARAM flags) { LONG n = ImmGetCompositionStringW(ime, kinds[i], NULL, 0); if (n < 0) continue; - WCHAR *s = calloc((size_t)n + 2, 1); + WCHAR *s = profileCalloc((size_t)n + 2, 1); if (!s) continue; ImmGetCompositionStringW(ime, kinds[i], s, (DWORD)n); int cursor = i ? ImmGetCompositionStringW(ime, GCS_CURSORPOS, NULL, 0) : -1; textEvent(w, i ? UHDK_EVENT_TEXT_PREEDIT : UHDK_EVENT_TEXT_COMMIT, s, n / 2, cursor); - free(s); + profileFree(s); } ImmReleaseContext(w->hwnd, ime); } static void dropFiles(Window *w, HDROP drop) { UINT count = DragQueryFileW(drop, UINT_MAX, NULL, 0); - uhdk_file **files = calloc(count ? count : 1, sizeof(*files)); + uhdk_file **files = profileCalloc(count ? count : 1, sizeof(*files)); uint64_t used = 0; if (files) for (UINT i = 0; i < count; ++i) { UINT n = DragQueryFileW(drop, i, NULL, 0); - WCHAR *p = calloc((size_t)n + 1, sizeof(WCHAR)); + WCHAR *p = profileCalloc((size_t)n + 1, sizeof(WCHAR)); if (!p) break; DragQueryFileW(drop, i, p, n + 1); @@ -419,7 +440,7 @@ static void dropFiles(Window *w, HDROP drop) { z_file_from_url(host.app, (uhdk_bytes){(uint8_t *)p, (uint64_t)n * 2}, NULL); if (file) files[used++] = file; - free(p); + profileFree(p); } POINT point; DragQueryPoint(drop, &point); @@ -433,7 +454,7 @@ static void dropFiles(Window *w, HDROP drop) { } for (uint64_t i = 0; i < used; ++i) uhdk_file_release(files[i]); - free(files); + profileFree(files); DragFinish(drop); } static LRESULT CALLBACK windowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { @@ -741,13 +762,13 @@ uhdk_status z_native_file_init(uhdk_file *file, uhdk_bytes path, uhdk_text uri, if (path.len) { if (path.len % 2 || path.len > 65534) return UHDK_STATUS_INVALID_ARGUMENT; - name = calloc((size_t)path.len / 2 + 1, sizeof(WCHAR)); + name = profileCalloc((size_t)path.len / 2 + 1, sizeof(WCHAR)); if (!name) return UHDK_STATUS_OUT_OF_MEMORY; memcpy(name, path.ptr, (size_t)path.len); for (size_t i = 0; i < path.len / 2; i++) if (!name[i]) { - free(name); + profileFree(name); return UHDK_STATUS_INVALID_ARGUMENT; } } else { @@ -755,19 +776,19 @@ uhdk_status z_native_file_init(uhdk_file *file, uhdk_bytes path, uhdk_text uri, if (!url) return UHDK_STATUS_INVALID_ARGUMENT; if (_wcsnicmp(url, L"file:", 5)) { - free(url); + profileFree(url); return UHDK_STATUS_INVALID_ARGUMENT; } DWORD capacity = 32768; - name = calloc(capacity, sizeof(WCHAR)); + name = profileCalloc(capacity, sizeof(WCHAR)); if (!name) { - free(url); + profileFree(url); return UHDK_STATUS_OUT_OF_MEMORY; } HRESULT hr = PathCreateFromUrlW(url, name, &capacity, 0); - free(url); + profileFree(url); if (FAILED(hr)) { - free(name); + profileFree(name); return UHDK_STATUS_INVALID_ARGUMENT; } } @@ -777,13 +798,13 @@ uhdk_status z_native_file_init(uhdk_file *file, uhdk_bytes path, uhdk_text uri, name[1] == L':' && (name[2] == L'\\' || name[2] == L'/'); BOOL unc = n >= 3 && name[0] == L'\\' && name[1] == L'\\'; if (!drive && !unc) { - free(name); + profileFree(name); return UHDK_STATUS_INVALID_ARGUMENT; } DWORD uri_capacity = 3 * 32768; - WCHAR *url = calloc(uri_capacity, sizeof(WCHAR)); + WCHAR *url = profileCalloc(uri_capacity, sizeof(WCHAR)); if (!url) { - free(name); + profileFree(name); return UHDK_STATUS_OUT_OF_MEMORY; } HRESULT hr = UrlCreateFromPathW(name, url, &uri_capacity, 0); @@ -802,10 +823,10 @@ uhdk_status z_native_file_init(uhdk_file *file, uhdk_bytes path, uhdk_text uri, display ? z_file_init(file, (uhdk_bytes){(uint8_t *)name, n * 2}, (uhdk_text){url8, uri_len}, (uhdk_text){display, display_len}, NULL) : UHDK_STATUS_OS_ERROR; - free(display); - free(url8); - free(url); - free(name); + profileFree(display); + profileFree(url8); + profileFree(url); + profileFree(name); return status; } @@ -819,13 +840,13 @@ static PROC glProc(const char *name) { static uhdk_status createGraphics(Window *w, void *handle) { if (!(w->flags & UHDK_WINDOW_OPENGL)) return UHDK_STATUS_INVALID_ARGUMENT; - Graphics *g = calloc(1, sizeof(*g)); + Graphics *g = profileCalloc(1, sizeof(*g)); if (!g) return UHDK_STATUS_OUT_OF_MEMORY; g->window = w->hwnd; g->dc = GetDC(w->hwnd); if (!g->dc) { - free(g); + profileFree(g); return UHDK_STATUS_OS_ERROR; } PIXELFORMATDESCRIPTOR pfd = {0}; @@ -888,7 +909,7 @@ failed: if (g->rc) wglDeleteContext(g->rc); ReleaseDC(g->window, g->dc); - free(g); + profileFree(g); return UHDK_STATUS_UNSUPPORTED; } uhdk_status z_native_gl(void *native, uint32_t op, void *out) { @@ -982,11 +1003,11 @@ static HMENU buildMenu(uhdk_menu *source, BOOL popup) { !InsertMenuItemW(menu, (UINT)i, TRUE, &native)) { if (native.hSubMenu) DestroyMenu(native.hSubMenu); - free(label); + profileFree(label); DestroyMenu(menu); return NULL; } - free(label); + profileFree(label); } return menu; } @@ -1087,10 +1108,10 @@ static void showDialog(void) { IFileDialog_SetFolder(dialog, folder); IShellItem_Release(folder); } - free(path); + profileFree(path); } COMDLG_FILTERSPEC *filters = - calloc(d->filters_len ? (size_t)d->filters_len : 1, sizeof(*filters)); + profileCalloc(d->filters_len ? (size_t)d->filters_len : 1, sizeof(*filters)); BOOL filter_ok = filters != NULL; if (filters) for (uint64_t i = 0; i < d->filters_len; i++) { @@ -1099,7 +1120,7 @@ static void showDialog(void) { size_t capacity = 2; for (uint64_t j = 0; j < extensions.len; j++) capacity += (size_t)extensions.ptr[j].len + 3; - WCHAR *pattern = calloc(capacity, sizeof(WCHAR)); + WCHAR *pattern = profileCalloc(capacity, sizeof(WCHAR)); filters[i].pszSpec = pattern; if (!filters[i].pszName || !pattern) { filter_ok = FALSE; @@ -1117,7 +1138,7 @@ static void showDialog(void) { wcscat(pattern, L";"); wcscat(pattern, L"*."); wcscat(pattern, ext); - free(ext); + profileFree(ext); } } if (filter_ok && d->filters_len) @@ -1137,7 +1158,7 @@ static void showDialog(void) { if (SUCCEEDED(hr)) { DWORD n = 0; IShellItemArray_GetCount(items, &n); - files = calloc(n ? n : 1, sizeof(*files)); + files = profileCalloc(n ? n : 1, sizeof(*files)); if (files) for (DWORD i = 0; i < n; i++) { IShellItem *item = NULL; @@ -1162,7 +1183,7 @@ static void showDialog(void) { status = UHDK_STATUS_OS_ERROR; } else if (status == UHDK_STATUS_OK) { IShellItem *item = NULL; - files = calloc(1, sizeof(*files)); + files = profileCalloc(1, sizeof(*files)); if (files && SUCCEEDED(IFileDialog_GetResult(dialog, &item))) { files[0] = shellFile(item); IShellItem_Release(item); @@ -1178,20 +1199,20 @@ static void showDialog(void) { host.dialog = NULL; host.dialog_id = 0; IFileDialog_Release(dialog); - free(title); - free(label); - free(suggestion); + profileFree(title); + profileFree(label); + profileFree(suggestion); if (filters) { for (uint64_t i = 0; i < d->filters_len; i++) { - free((void *)filters[i].pszName); - free((void *)filters[i].pszSpec); + profileFree((void *)filters[i].pszName); + profileFree((void *)filters[i].pszSpec); } - free(filters); + profileFree(filters); } z_complete(host.app, id, status, &result); for (uint64_t i = 0; i < count; i++) uhdk_file_release(files[i]); - free(files); + profileFree(files); } static BOOL armWatch(Watch *watch) { @@ -1207,15 +1228,15 @@ static uhdk_status createWatch(uhdk_file *root, uhdk_watch *handle) { WCHAR *path = filePath(root); if (!path) return UHDK_STATUS_OUT_OF_MEMORY; - Watch *watch = calloc(1, sizeof(*watch)); + Watch *watch = profileCalloc(1, sizeof(*watch)); if (!watch) { - free(path); + profileFree(path); return UHDK_STATUS_OUT_OF_MEMORY; } DWORD attributes = GetFileAttributesW(path); if (attributes == INVALID_FILE_ATTRIBUTES) { - free(path); - free(watch); + profileFree(path); + profileFree(watch); return osError(); } if (!(attributes & FILE_ATTRIBUTE_DIRECTORY)) { @@ -1224,14 +1245,14 @@ static uhdk_status createWatch(uhdk_file *root, uhdk_watch *handle) { if (other && (!slash || other > slash)) slash = other; if (!slash) { - free(path); - free(watch); + profileFree(path); + profileFree(watch); return UHDK_STATUS_INVALID_ARGUMENT; } watch->leaf = _wcsdup(slash + 1); if (!watch->leaf) { - free(path); - free(watch); + profileFree(path); + profileFree(watch); return UHDK_STATUS_OUT_OF_MEMORY; } slash[1] = 0; @@ -1239,7 +1260,7 @@ static uhdk_status createWatch(uhdk_file *root, uhdk_watch *handle) { watch->directory = CreateFileW( path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); - free(path); + profileFree(path); watch->overlapped.hEvent = CreateEventW(NULL, TRUE, FALSE, NULL); watch->handle = handle; if (watch->directory == INVALID_HANDLE_VALUE || !watch->overlapped.hEvent || !armWatch(watch)) { @@ -1248,8 +1269,8 @@ static uhdk_status createWatch(uhdk_file *root, uhdk_watch *handle) { CloseHandle(watch->directory); if (watch->overlapped.hEvent) CloseHandle(watch->overlapped.hEvent); - free(watch->leaf); - free(watch); + profileFree(watch->leaf); + profileFree(watch); return status; } watch->next = host.watches; @@ -1462,7 +1483,7 @@ void z_native_destroy(uint32_t kind, void *native) { ReleaseDC(g->window, g->dc); LeaveCriticalSection(&g->lock); DeleteCriticalSection(&g->lock); - free(g); + profileFree(g); } else if (kind == Z_KIND_WINDOW) { Window *w = native; Window **link = &host.windows; @@ -1481,7 +1502,7 @@ void z_native_destroy(uint32_t kind, void *native) { DestroyWindow(w->hwnd); if (w->menu_source) uhdk_menu_release(w->menu_source); - free(w); + profileFree(w); } else if (kind == Z_KIND_WATCH) { Watch *w = native; Watch **link = &host.watches; @@ -1496,8 +1517,8 @@ void z_native_destroy(uint32_t kind, void *native) { } CloseHandle(w->directory); CloseHandle(w->overlapped.hEvent); - free(w->leaf); - free(w); + profileFree(w->leaf); + profileFree(w); } } static uhdk_status clipboardWrite(uhdk_text text) { @@ -1509,18 +1530,18 @@ static uhdk_status clipboardWrite(uhdk_text text) { size_t length = (wcslen(value) + 1) * sizeof(WCHAR); HGLOBAL memory = GlobalAlloc(GMEM_MOVEABLE, length); if (!memory) { - free(value); + profileFree(value); return UHDK_STATUS_OUT_OF_MEMORY; } void *bytes = GlobalLock(memory); if (!bytes) { GlobalFree(memory); - free(value); + profileFree(value); return UHDK_STATUS_OS_ERROR; } memcpy(bytes, value, length); GlobalUnlock(memory); - free(value); + profileFree(value); if (!OpenClipboard(host.dispatch)) { GlobalFree(memory); return UHDK_STATUS_BUSY; @@ -1554,13 +1575,13 @@ static uhdk_status clipboardRead(uint64_t id, uint64_t limit) { if (!text) return UHDK_STATUS_OS_ERROR; if (len > limit) { - free(text); + profileFree(text); return UHDK_STATUS_BUFFER_TOO_SMALL; } uhdk_request_result result = {0}; result.value.text = (uhdk_text){text, len}; z_complete(host.app, id, UHDK_STATUS_OK, &result); - free(text); + profileFree(text); return Z_DEFERRED; } static uhdk_status setIcon(const uhdk_image *image) { @@ -1617,7 +1638,7 @@ uhdk_status z_native_execute(uhdk_app *app, const z_command *cmd, uint64_t reque cmd->target && z_object_kind(cmd->target) == Z_KIND_WINDOW ? windowOf(cmd->target) : NULL; switch (cmd->op) { case Z_WINDOW: { - w = calloc(1, sizeof(*w)); + w = profileCalloc(1, sizeof(*w)); if (!w) return UHDK_STATUS_OUT_OF_MEMORY; w->handle = cmd->target; @@ -1638,16 +1659,16 @@ uhdk_status z_native_execute(uhdk_app *app, const z_command *cmd, uint64_t reque AdjustWindowRectExForDpi(&r, style, FALSE, 0, dpi); WCHAR *title = wide(cmd->window.title); if (!title) { - free(w); + profileFree(w); return UHDK_STATUS_OUT_OF_MEMORY; } HWND parent = cmd->window.parent ? windowOf(cmd->window.parent)->hwnd : NULL; HWND hwnd = CreateWindowExW(0, window_class, title, style, CW_USEDEFAULT, CW_USEDEFAULT, r.right - r.left, r.bottom - r.top, parent, NULL, GetModuleHandleW(NULL), w); - free(title); + profileFree(title); if (!hwnd) { - free(w); + profileFree(w); return osError(); } ImmAssociateContextEx(hwnd, NULL, 0); @@ -1668,7 +1689,7 @@ uhdk_status z_native_execute(uhdk_app *app, const z_command *cmd, uint64_t reque if (!title) return UHDK_STATUS_OUT_OF_MEMORY; BOOL ok = SetWindowTextW(w->hwnd, title); - free(title); + profileFree(title); return ok ? UHDK_STATUS_OK : osError(); } case Z_SIZE: { @@ -1831,11 +1852,11 @@ uhdk_status z_native_execute(uhdk_app *app, const z_command *cmd, uint64_t reque if (!url) return UHDK_STATUS_OUT_OF_MEMORY; if (!UrlIsW(url, URLIS_URL)) { - free(url); + profileFree(url); return UHDK_STATUS_INVALID_ARGUMENT; } INT_PTR status = (INT_PTR)ShellExecuteW(NULL, L"open", url, NULL, NULL, SW_SHOWNORMAL); - free(url); + profileFree(url); return status > 32 ? UHDK_STATUS_OK : UHDK_STATUS_OS_ERROR; } case Z_WATCH: {