From 38d019ee1b454bbb5fb71d8ca74961493cf14db7 Mon Sep 17 00:00:00 2001 From: Nycto Date: Sat, 2 Mar 2024 16:03:11 -0800 Subject: [PATCH] Use playdate realloc for memory management Fixes #59 --- src/playdate/api.nim | 24 ++++++---- src/playdate/bindings/graphics.nim | 15 ++++--- src/playdate/bindings/malloc.nim | 72 ++++++++++++++++++++++++++++++ src/playdate/bindings/system.nim | 3 +- src/playdate/bindings/utils.nim | 2 - src/playdate/build/config.nim | 10 ++++- src/playdate/types.nim | 3 +- 7 files changed, 107 insertions(+), 22 deletions(-) create mode 100644 src/playdate/bindings/malloc.nim diff --git a/src/playdate/api.nim b/src/playdate/api.nim index 82cd981..38ed1f2 100644 --- a/src/playdate/api.nim +++ b/src/playdate/api.nim @@ -3,7 +3,6 @@ import macros import std/importutils -import bindings/utils {.all.} as memory import bindings/api export api @@ -16,12 +15,21 @@ macro initSDK*() = proc eventHandler(playdateAPI: ptr PlaydateAPI, event: PDSystemEvent, arg: uint32): cint {.cdecl, exportc.} = privateAccess(PlaydateSys) - if event == kEventInit: - NimMain() - api.playdate = playdateAPI - memory.realloc = playdateAPI.system.realloc - handler(event, arg) - return 0 + + when declared(setupRealloc): + if event == kEventInit: + setupRealloc(playdateAPI.system.realloc) + NimMain() + api.playdate = playdateAPI + handler(event, arg) + return 0 + else: + if event == kEventInit: + playdate.system.error( + "'setupRealloc' proc is undeclared. " & + "This indicates your 'config.nims' is likely misconfigured to support playdate integration" + ) + return 1 when not defined(simulator): proc fini() {.cdecl, exportc: "_fini".} = @@ -50,4 +58,4 @@ when not defined(simulator) and defined(release): return 0 proc write(handle: cint, data: ptr cchar, size: cint): cint {.cdecl, exportc: "_write".} = - return -1 \ No newline at end of file + return -1 diff --git a/src/playdate/bindings/graphics.nim b/src/playdate/bindings/graphics.nim index 60abbe8..adb9262 100644 --- a/src/playdate/bindings/graphics.nim +++ b/src/playdate/bindings/graphics.nim @@ -73,8 +73,9 @@ type LCDBitmapTablePtr {.importc: "LCDBitmapTable*", header: "pd_api.h".} = poin type LCDFontPtr {.importc: "LCDFont*", header: "pd_api.h".} = pointer type LCDFontObj = object resource: LCDFontPtr -proc `=destroy`(this: var LCDFontObj) = - discard utils.realloc(this.resource, 0) + +proc `=destroy`(this: var LCDFontObj) = deallocImpl(this.resource) + type LCDFont* = ref LCDFontObj type LCDFontDataPtr {.importc: "LCDFontData*", header: "pd_api.h".} = object @@ -83,15 +84,17 @@ type LCDFontData* = LCDFontDataPtr type LCDFontPagePtr {.importc: "LCDFontPage*", header: "pd_api.h".} = pointer type LCDFontPageObj = object resource: LCDFontPagePtr -proc `=destroy`(this: var LCDFontPageObj) = - discard utils.realloc(this.resource, 0) + +proc `=destroy`(this: var LCDFontPageObj) = deallocImpl(this.resource) + type LCDFontPage* = ref LCDFontPageObj type LCDFontGlyphPtr {.importc: "LCDFontGlyph*", header: "pd_api.h".} = pointer type LCDFontGlyphObj = object resource: LCDFontGlyphPtr -proc `=destroy`(this: var LCDFontGlyphObj) = - discard utils.realloc(this.resource, 0) + +proc `=destroy`(this: var LCDFontGlyphObj) = deallocImpl(this.resource) + type LCDFontGlyph* = ref LCDFontGlyphObj type LCDVideoPlayerRaw {.importc: "LCDVideoPlayer", header: "pd_api.h".} = object diff --git a/src/playdate/bindings/malloc.nim b/src/playdate/bindings/malloc.nim new file mode 100644 index 0000000..02e786d --- /dev/null +++ b/src/playdate/bindings/malloc.nim @@ -0,0 +1,72 @@ +## +## This file is a re-implementation of malloc.nim in the Nim standard library.It allows Nim itself to use the +## memory allocators provided by the playdate SDK. +## +## It works by by patching it in as a replacement in your configs.nim file, like this: +## +## ```nim +## patchFile("stdlib", "malloc", nimblePlaydatePath / "src/playdate/bindings/malloc") +## ``` +## +## This patching is automatically configured when using `playdate/build/config`, as recommended by the setup +## documentation. +## + +{.push stackTrace: off.} + +when defined(memtrace): + import system/ansi_c + +type PDRealloc = proc (p: pointer; size: csize_t): pointer {.tags: [], raises: [], cdecl, gcsafe.} + +var pdrealloc: PDRealloc + +proc setupRealloc*(allocator: PDRealloc) = + when defined(memtrace): + cfprintf(cstderr, "Setting up playdate allocator") + pdrealloc = allocator + +proc allocImpl(size: Natural): pointer = + when defined(memtrace): + cfprintf(cstderr, "Allocating %d\n", size) + result = pdrealloc(nil, size.csize_t) + when defined(memtrace): + cfprintf(cstderr, " At %p\n", result) + +proc alloc0Impl(size: Natural): pointer = + result = allocImpl(size) + zeroMem(result, size) + +proc reallocImpl(p: pointer, newSize: Natural): pointer = + when defined(memtrace): + cfprintf(cstderr, "Reallocating %p with size %d\n", p, newSize) + return pdrealloc(p, newSize.csize_t) + +proc realloc0Impl(p: pointer, oldsize, newSize: Natural): pointer = + result = realloc(p, newSize.csize_t) + if newSize > oldSize: + zeroMem(cast[pointer](cast[uint](result) + uint(oldSize)), newSize - oldSize) + +proc deallocImpl(p: pointer) = + when defined(memtrace): + cfprintf(cstderr, "Freeing %p\n", p) + discard pdrealloc(p, 0) + +# The shared allocators map on the regular ones + +proc allocSharedImpl(size: Natural): pointer {.used.} = allocImpl(size) + +proc allocShared0Impl(size: Natural): pointer {.used.} = alloc0Impl(size) + +proc reallocSharedImpl(p: pointer, newSize: Natural): pointer {.used.} = reallocImpl(p, newSize) + +proc reallocShared0Impl(p: pointer, oldsize, newSize: Natural): pointer {.used.} = realloc0Impl(p, oldSize, newSize) + +proc deallocSharedImpl(p: pointer) {.used.} = deallocImpl(p) + +proc getOccupiedMem(): int {.used.} = discard +proc getFreeMem(): int {.used.} = discard +proc getTotalMem(): int {.used.} = discard +proc deallocOsPages() {.used.} = discard + +{.pop.} diff --git a/src/playdate/bindings/system.nim b/src/playdate/bindings/system.nim index e38e3fc..1f774d5 100644 --- a/src/playdate/bindings/system.nim +++ b/src/playdate/bindings/system.nim @@ -34,8 +34,7 @@ type PDMenuItemCallbackFunctionRaw {.importc: "PDMenuItemCallbackFunction", head # System sdktype: type PlaydateSys* {.importc: "const struct playdate_sys", header: "pd_api.h".} = object - realloc {.importc: "realloc".}: proc (`ptr`: pointer; size: csize_t): pointer {. - cdecl, raises: [].} + realloc {.importc: "realloc".}: proc (`ptr`: pointer; size: csize_t): pointer {.cdecl, raises: [], tags: [], gcsafe.} formatString {.importc: "formatString".}: proc (ret: cstringArray; fmt: cstring): cint {. cdecl, varargs, raises: [].} logToConsole {.importc: "logToConsole".}: proc (fmt: cstring) {.cdecl, varargs, raises: [].} diff --git a/src/playdate/bindings/utils.nim b/src/playdate/bindings/utils.nim index 43df1b8..f2cb4ce 100644 --- a/src/playdate/bindings/utils.nim +++ b/src/playdate/bindings/utils.nim @@ -1,7 +1,5 @@ import macros -var realloc*: proc(p: pointer, size: csize_t): pointer {.cdecl.} - func toNimSymbol(typeSymbol: string): string = case typeSymbol: of "cint": diff --git a/src/playdate/build/config.nim b/src/playdate/build/config.nim index 865513a..ffbb691 100644 --- a/src/playdate/build/config.nim +++ b/src/playdate/build/config.nim @@ -13,6 +13,9 @@ const headlessTesting = defined(simulator) and declared(test) const nimbleTesting = not defined(simulator) and not defined(devide) and declared(test) const testing = headlessTesting or nimbleTesting +# The path to the nimble playdate package +const nimblePlaydatePath = currentSourcePath / "../../../../" + if not testing: switch("noMain", "on") switch("backend", "c") @@ -154,8 +157,11 @@ if nimbleTesting: switch("passC", "-DTARGET_SIMULATOR=1") switch("passC", "-Wstrict-prototypes") else: - # Add extra files to compile last, so that + # Add extra files to compile last, so that # they get compiled in the correct nimcache folder. # Windows doesn't like having setup.c compiled. if defined(device) or not defined(windows): - switch("compile", sdkPath() / "C_API" / "buildsupport" / "setup.c") \ No newline at end of file + switch("compile", sdkPath() / "C_API" / "buildsupport" / "setup.c") + + # Overrides the nim memory management code to ensure it uses the playdate allocator + patchFile("stdlib", "malloc", nimblePlaydatePath / "src/playdate/bindings/malloc") \ No newline at end of file diff --git a/src/playdate/types.nim b/src/playdate/types.nim index dc36ee3..2d9db5f 100644 --- a/src/playdate/types.nim +++ b/src/playdate/types.nim @@ -1,4 +1,3 @@ -import bindings/utils type SDKArrayObj[T] = object len: int @@ -7,7 +6,7 @@ type SDKArray*[T] = ref SDKArrayObj[T] proc `=destroy`*[T](this: var SDKArrayObj[T]) = if this.data != nil: - discard utils.realloc(this.data, 0) + deallocImpl(this.data) proc `[]`*[T](this: SDKArray[T]; i: Natural): lent T = assert i < this.len