From b71b164dcc4d8fc6e033da31c3444c12cf9b6f0e Mon Sep 17 00:00:00 2001 From: James Date: Sun, 17 Mar 2024 10:38:47 -0700 Subject: [PATCH] Use playdate realloc for memory management Fixes #59 --- src/playdate/api.nim | 6 +-- 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 | 31 +++++++++---- src/playdate/types.nim | 3 +- 7 files changed, 108 insertions(+), 24 deletions(-) create mode 100644 src/playdate/bindings/malloc.nim diff --git a/src/playdate/api.nim b/src/playdate/api.nim index 82cd981..80ad787 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 @@ -17,9 +16,10 @@ macro initSDK*() = proc eventHandler(playdateAPI: ptr PlaydateAPI, event: PDSystemEvent, arg: uint32): cint {.cdecl, exportc.} = privateAccess(PlaydateSys) if event == kEventInit: + when declared(setupRealloc): + setupRealloc(playdateAPI.system.realloc) NimMain() api.playdate = playdateAPI - memory.realloc = playdateAPI.system.realloc handler(event, arg) return 0 @@ -50,4 +50,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..4940af2 100644 --- a/src/playdate/build/config.nim +++ b/src/playdate/build/config.nim @@ -1,4 +1,5 @@ -import os +import os, strutils + when defined(device): import strformat @@ -13,6 +14,16 @@ const headlessTesting = defined(simulator) and declared(test) const nimbleTesting = not defined(simulator) and not defined(devide) and declared(test) const testing = headlessTesting or nimbleTesting +# Path to the playdate src directory when checked out locally +const localPlaydatePath = currentSourcePath / "../../../../src" + +# The path to the nimble playdate package +let nimblePlaydatePath = + if dirExists(localPlaydatePath / "playdate"): + localPlaydatePath + else: + gorgeEx("nimble path playdate").output.split("\n")[0] + if not testing: switch("noMain", "on") switch("backend", "c") @@ -34,8 +45,12 @@ switch("passC", "-Wno-unknown-pragmas") switch("passC", "-Wdouble-promotion") switch("passC", "-I" & sdkPath() / "C_API") +switch("os", "any") +switch("define", "useMalloc") +switch("define", "standalone") +switch("threads", "off") + when defined(device): - switch("os", "any") switch("gcc.options.always", "") switch("nimcache", nimcacheDir() / "device") @@ -43,11 +58,8 @@ when defined(device): switch("app", "console") switch("cpu", "arm") switch("checks", "off") - switch("threads", "off") switch("assertions", "off") switch("hotCodeReloading", "off") - switch("define", "useMalloc") - switch("define", "standalone") let heapSize = 8388208 let stackSize = 61800 @@ -114,8 +126,6 @@ when defined(simulator): switch("opt", "none") switch("define", "debug") - switch("define", "nimAllocPagesViaMalloc") - switch("define", "nimPage256") switch("passC", "-DTARGET_SIMULATOR=1") switch("passC", "-Wstrict-prototypes") @@ -154,8 +164,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 / "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