Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use playdate realloc for memory management #60

Merged
merged 1 commit into from
Mar 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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")
Nycto marked this conversation as resolved.
Show resolved Hide resolved
## ```
##
## 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