Skip to content

Commit

Permalink
Switch maintanance (nim-lang#18668)
Browse files Browse the repository at this point in the history
* Fix and improve Nintendo Switch support

* Document the necessity for nimAllocPagesViaMalloc

* update changelog

* Use --gc:orc in examples
  • Loading branch information
RSDuck authored and PMunch committed Mar 28, 2022
1 parent 0953ce8 commit 08965c4
Show file tree
Hide file tree
Showing 5 changed files with 15 additions and 165 deletions.
3 changes: 3 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@
- In `std/dom`, `Interval` is now a `ref object`, same as `Timeout`. Definitions of `setTimeout`,
`clearTimeout`, `setInterval`, `clearInterval` were updated.

- The allocator for Nintendo Switch, which was nonfunctional because
of breaking changes in libnx, was removed, in favour of the new `-d:nimAllocPagesViaMalloc` option.

## Standard library additions and changes

Expand Down Expand Up @@ -363,6 +365,7 @@

- Added `dom.setInterval`, `dom.clearInterval` overloads.

- Allow reading parameters when compiling for Nintendo Switch.

- Deprecated `sequtils.delete` and added an overload taking a `Slice` that raises a defect
if the slice is out of bounds, likewise with `strutils.delete`.
Expand Down
21 changes: 11 additions & 10 deletions doc/nimc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -406,16 +406,18 @@ to your usual `nim c`:cmd: or `nim cpp`:cmd: command and set the `passC`:option:
and `passL`:option: command line switches to something like:

.. code-block:: cmd
nim c ... --passC="-I$DEVKITPRO/libnx/include" ...
nim c ... --d:nimAllocPagesViaMalloc --gc:orc --passC="-I$DEVKITPRO/libnx/include" ...
--passL="-specs=$DEVKITPRO/libnx/switch.specs -L$DEVKITPRO/libnx/lib -lnx"
or setup a ``nim.cfg`` file like so::

#nim.cfg
--gc:orc
--d:nimAllocPagesViaMalloc
--passC="-I$DEVKITPRO/libnx/include"
--passL="-specs=$DEVKITPRO/libnx/switch.specs -L$DEVKITPRO/libnx/lib -lnx"

The DevkitPro setup must be the same as the default with their new installer
The devkitPro setup must be the same as the default with their new installer
`here for Mac/Linux <https://github.com/devkitPro/pacman/releases>`_ or
`here for Windows <https://github.com/devkitPro/installer/releases>`_.

Expand All @@ -426,20 +428,19 @@ For example, with the above-mentioned config:
nim c --os:nintendoswitch switchhomebrew.nim
This will generate a file called ``switchhomebrew.elf`` which can then be turned into
an nro file with the `elf2nro`:cmd: tool in the DevkitPro release. Examples can be found at
an nro file with the `elf2nro`:cmd: tool in the devkitPro release. Examples can be found at
`the nim-libnx github repo <https://github.com/jyapayne/nim-libnx.git>`_.

There are a few things that don't work because the DevkitPro libraries don't support them.
There are a few things that don't work because the devkitPro libraries don't support them.
They are:

1. Waiting for a subprocess to finish. A subprocess can be started, but right
now it can't be waited on, which sort of makes subprocesses a bit hard to use
2. Dynamic calls. DevkitPro libraries have no dlopen/dlclose functions.
3. Command line parameters. It doesn't make sense to have these for a console
anyways, so no big deal here.
4. mqueue. Sadly there are no mqueue headers.
5. ucontext. No headers for these either. No coroutines for now :(
6. nl_types. No headers for this.
2. Dynamic calls. Switch OS (Horizon) doesn't support dynamic libraries, so dlopen/dlclose are not available.
3. mqueue. Sadly there are no mqueue headers.
4. ucontext. No headers for these either. No coroutines for now :(
5. nl_types. No headers for this.
6. As mmap is not supported, the nimAllocPagesViaMalloc option has to be used.

DLL generation
==============
Expand Down
36 changes: 0 additions & 36 deletions lib/nintendoswitch/switch_memory.nim

This file was deleted.

9 changes: 1 addition & 8 deletions lib/pure/os.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2910,13 +2910,6 @@ elif defined(nodejs):
result = $argv[i]
else:
raise newException(IndexDefect, formatErrorIndexBound(i - 1, argv.len - 2))
elif defined(nintendoswitch):
proc paramStr*(i: int): string {.tags: [ReadIOEffect].} =
raise newException(OSError, "paramStr is not implemented on Nintendo Switch")

proc paramCount*(): int {.tags: [ReadIOEffect].} =
raise newException(OSError, "paramCount is not implemented on Nintendo Switch")

elif defined(windows):
# Since we support GUI applications with Nim, we sometimes generate
# a WinMain entry proc. But a WinMain proc has no access to the parsed
Expand Down Expand Up @@ -3190,7 +3183,7 @@ proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noW
result = getApplAux("/proc/self/exe")
elif defined(solaris):
result = getApplAux("/proc/" & $getpid() & "/path/a.out")
elif defined(genode) or defined(nintendoswitch):
elif defined(genode):
raiseOSError(OSErrorCode(-1), "POSIX command line not supported")
elif defined(freebsd) or defined(dragonfly) or defined(netbsd):
result = getApplFreebsd()
Expand Down
111 changes: 0 additions & 111 deletions lib/system/osalloc.nim
Original file line number Diff line number Diff line change
Expand Up @@ -103,117 +103,6 @@ elif defined(emscripten) and not defined(StandaloneHeapSize):
elif defined(genode) and not defined(StandaloneHeapSize):
include genode/alloc # osAllocPages, osTryAllocPages, osDeallocPages

elif defined(nintendoswitch) and not defined(StandaloneHeapSize):

import nintendoswitch/switch_memory

type
PSwitchBlock = ptr NSwitchBlock
## This will hold the heap pointer data in a separate
## block of memory that is PageSize bytes above
## the requested memory. It's the only good way
## to pass around data with heap allocations
NSwitchBlock {.pure, inheritable.} = object
realSize: int
heap: pointer # pointer to main heap alloc
heapMirror: pointer # pointer to virtmem mapped heap

proc alignSize(size: int): int {.inline.} =
## Align a size integer to be in multiples of PageSize
## The nintendo switch will not allocate memory that is not
## aligned to 0x1000 bytes and will just crash.
(size + (PageSize - 1)) and not (PageSize - 1)

proc deallocate(heapMirror: pointer, heap: pointer, size: int) =
# Unmap the allocated memory
discard svcUnmapMemory(heapMirror, heap, size.uint64)
# These should be called (theoretically), but referencing them crashes the switch.
# The above call seems to free all heap memory, so these are not needed.
# virtmemFreeMap(nswitchBlock.heapMirror, nswitchBlock.realSize.csize)
# free(nswitchBlock.heap)

proc freeMem(p: pointer) =
# Retrieve the switch block data from the pointer we set before
# The data is located just sizeof(NSwitchBlock) bytes below
# the top of the pointer to the heap
let
nswitchDescrPos = cast[ByteAddress](p) -% sizeof(NSwitchBlock)
nswitchBlock = cast[PSwitchBlock](nswitchDescrPos)

deallocate(
nswitchBlock.heapMirror, nswitchBlock.heap, nswitchBlock.realSize
)

proc storeHeapData(address, heapMirror, heap: pointer, size: int) {.inline.} =
## Store data in the heap for deallocation purposes later

# the position of our heap pointer data. Since we allocated PageSize extra
# bytes, we should have a buffer on top of the requested size of at least
# PageSize bytes, which is much larger than sizeof(NSwitchBlock). So we
# decrement the address by sizeof(NSwitchBlock) and use that address
# to store our pointer data
let nswitchBlockPos = cast[ByteAddress](address) -% sizeof(NSwitchBlock)

# We need to store this in a pointer obj (PSwitchBlock) so that the data sticks
# at the address we've chosen. If NSwitchBlock is used here, the data will
# be all 0 when we try to retrieve it later.
var nswitchBlock = cast[PSwitchBlock](nswitchBlockPos)
nswitchBlock.realSize = size
nswitchBlock.heap = heap
nswitchBlock.heapMirror = heapMirror

proc getOriginalHeapPosition(address: pointer, difference: int): pointer {.inline.} =
## This function sets the heap back to the originally requested
## size
let
pos = cast[int](address)
newPos = cast[ByteAddress](pos) +% difference

return cast[pointer](newPos)

template allocPages(size: int, outOfMemoryStmt: untyped): untyped =
# This is to ensure we get a block of memory the requested
# size, as well as space to store our structure
let realSize = alignSize(size + sizeof(NSwitchBlock))

let heap = memalign(PageSize, realSize)

if heap.isNil:
outOfMemoryStmt

let heapMirror = virtmemReserveMap(realSize.csize)
result = heapMirror

let rc = svcMapMemory(heapMirror, heap, realSize.uint64)
# Any return code not equal 0 means an error in libnx
if rc.uint32 != 0:
deallocate(heapMirror, heap, realSize)
outOfMemoryStmt

# set result to be the original size requirement
result = getOriginalHeapPosition(result, realSize - size)

storeHeapData(result, heapMirror, heap, realSize)

proc osAllocPages(size: int): pointer {.inline.} =
allocPages(size):
raiseOutOfMem()

proc osTryAllocPages(size: int): pointer =
allocPages(size):
return nil

proc osDeallocPages(p: pointer, size: int) =
# Note that in order for the Switch not to crash, a call to
# deallocHeap(runFinalizers = true, allowGcAfterwards = false)
# must be run before gfxExit(). The Switch requires all memory
# to be deallocated before the graphics application has exited.
#
# gfxExit() can be found in <switch/gfx/gfx.h> in the github
# repo https://github.com/switchbrew/libnx
when reallyOsDealloc:
freeMem(p)

elif defined(posix) and not defined(StandaloneHeapSize):
const
PROT_READ = 1 # page can be read
Expand Down

0 comments on commit 08965c4

Please sign in to comment.