Skip to content

Commit

Permalink
Use playdate realloc for memory management
Browse files Browse the repository at this point in the history
Fixes #59
  • Loading branch information
Nycto authored Mar 17, 2024
1 parent 056df90 commit b71b164
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 24 deletions.
6 changes: 3 additions & 3 deletions src/playdate/api.nim
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import macros
import std/importutils

import bindings/utils {.all.} as memory
import bindings/api
export api

Expand All @@ -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

Expand Down Expand Up @@ -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
return -1
15 changes: 9 additions & 6 deletions src/playdate/bindings/graphics.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
72 changes: 72 additions & 0 deletions src/playdate/bindings/malloc.nim
Original file line number Diff line number Diff line change
@@ -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.}
3 changes: 1 addition & 2 deletions src/playdate/bindings/system.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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: [].}
Expand Down
2 changes: 0 additions & 2 deletions src/playdate/bindings/utils.nim
Original file line number Diff line number Diff line change
@@ -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":
Expand Down
31 changes: 22 additions & 9 deletions src/playdate/build/config.nim
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import os, strutils

when defined(device):
import strformat

Expand All @@ -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")
Expand All @@ -34,20 +45,21 @@ 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")
switch("cc", "gcc")
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
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
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")
3 changes: 1 addition & 2 deletions src/playdate/types.nim
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import bindings/utils

type SDKArrayObj[T] = object
len: int
Expand All @@ -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
Expand Down

0 comments on commit b71b164

Please sign in to comment.