Switch maintanance (#18668)
* Fix and improve Nintendo Switch support * Document the necessity for nimAllocPagesViaMalloc * update changelog * Use --gc:orc in examples
This commit is contained in:
parent
31fc0f9718
commit
d0dd923acf
5 changed files with 15 additions and 165 deletions
|
|
@ -104,6 +104,8 @@
|
||||||
- In `std/dom`, `Interval` is now a `ref object`, same as `Timeout`. Definitions of `setTimeout`,
|
- In `std/dom`, `Interval` is now a `ref object`, same as `Timeout`. Definitions of `setTimeout`,
|
||||||
`clearTimeout`, `setInterval`, `clearInterval` were updated.
|
`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
|
## Standard library additions and changes
|
||||||
|
|
||||||
|
|
@ -363,6 +365,7 @@
|
||||||
|
|
||||||
- Added `dom.setInterval`, `dom.clearInterval` overloads.
|
- 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
|
- 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`.
|
if the slice is out of bounds, likewise with `strutils.delete`.
|
||||||
|
|
|
||||||
21
doc/nimc.rst
21
doc/nimc.rst
|
|
@ -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:
|
and `passL`:option: command line switches to something like:
|
||||||
|
|
||||||
.. code-block:: cmd
|
.. 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"
|
--passL="-specs=$DEVKITPRO/libnx/switch.specs -L$DEVKITPRO/libnx/lib -lnx"
|
||||||
|
|
||||||
or setup a ``nim.cfg`` file like so::
|
or setup a ``nim.cfg`` file like so::
|
||||||
|
|
||||||
#nim.cfg
|
#nim.cfg
|
||||||
|
--gc:orc
|
||||||
|
--d:nimAllocPagesViaMalloc
|
||||||
--passC="-I$DEVKITPRO/libnx/include"
|
--passC="-I$DEVKITPRO/libnx/include"
|
||||||
--passL="-specs=$DEVKITPRO/libnx/switch.specs -L$DEVKITPRO/libnx/lib -lnx"
|
--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 Mac/Linux <https://github.com/devkitPro/pacman/releases>`_ or
|
||||||
`here for Windows <https://github.com/devkitPro/installer/releases>`_.
|
`here for Windows <https://github.com/devkitPro/installer/releases>`_.
|
||||||
|
|
||||||
|
|
@ -426,20 +428,19 @@ For example, with the above-mentioned config:
|
||||||
nim c --os:nintendoswitch switchhomebrew.nim
|
nim c --os:nintendoswitch switchhomebrew.nim
|
||||||
|
|
||||||
This will generate a file called ``switchhomebrew.elf`` which can then be turned into
|
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>`_.
|
`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:
|
They are:
|
||||||
|
|
||||||
1. Waiting for a subprocess to finish. A subprocess can be started, but right
|
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
|
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.
|
2. Dynamic calls. Switch OS (Horizon) doesn't support dynamic libraries, so dlopen/dlclose are not available.
|
||||||
3. Command line parameters. It doesn't make sense to have these for a console
|
3. mqueue. Sadly there are no mqueue headers.
|
||||||
anyways, so no big deal here.
|
4. ucontext. No headers for these either. No coroutines for now :(
|
||||||
4. mqueue. Sadly there are no mqueue headers.
|
5. nl_types. No headers for this.
|
||||||
5. ucontext. No headers for these either. No coroutines for now :(
|
6. As mmap is not supported, the nimAllocPagesViaMalloc option has to be used.
|
||||||
6. nl_types. No headers for this.
|
|
||||||
|
|
||||||
DLL generation
|
DLL generation
|
||||||
==============
|
==============
|
||||||
|
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
## All of these library headers and source can be found in the github repo
|
|
||||||
## https://github.com/switchbrew/libnx.
|
|
||||||
|
|
||||||
const virtMemHeader = "<switch/kernel/virtmem.h>"
|
|
||||||
const svcHeader = "<switch/kernel/svc.h>"
|
|
||||||
const mallocHeader = "<malloc.h>"
|
|
||||||
|
|
||||||
## Aligns a block of memory with request `size` to `bytes` size. For
|
|
||||||
## example, a request of memalign(0x1000, 0x1001) == 0x2000 bytes allocated
|
|
||||||
proc memalign*(bytes: csize, size: csize): pointer {.importc: "memalign",
|
|
||||||
header: mallocHeader.}
|
|
||||||
|
|
||||||
# Should be required, but not needed now because of how
|
|
||||||
# svcUnmapMemory frees all memory
|
|
||||||
#proc free*(address: pointer) {.importc: "free",
|
|
||||||
# header: mallocHeader.}
|
|
||||||
|
|
||||||
## Maps a memaligned block of memory from `src_addr` to `dst_addr`. The
|
|
||||||
## Nintendo Switch requires this call in order to make use of memory, otherwise
|
|
||||||
## an invalid memory access occurs.
|
|
||||||
proc svcMapMemory*(dst_addr: pointer; src_addr: pointer; size: uint64): uint32 {.
|
|
||||||
importc: "svcMapMemory", header: svcHeader.}
|
|
||||||
|
|
||||||
## Unmaps (frees) all memory from both `dst_addr` and `src_addr`. **Must** be called
|
|
||||||
## whenever svcMapMemory is used. The Switch will expect all memory to be allocated
|
|
||||||
## before gfxExit() calls (<switch/gfx/gfx.h>)
|
|
||||||
proc svcUnmapMemory*(dst_addr: pointer; src_addr: pointer; size: uint64): uint32 {.
|
|
||||||
importc: "svcUnmapMemory", header: svcHeader.}
|
|
||||||
|
|
||||||
proc virtmemReserveMap*(size: csize): pointer {.importc: "virtmemReserveMap",
|
|
||||||
header: virtMemHeader.}
|
|
||||||
|
|
||||||
# Should be required, but not needed now because of how
|
|
||||||
# svcUnmapMemory frees all memory
|
|
||||||
#proc virtmemFreeMap*(address: pointer; size: csize) {.importc: "virtmemFreeMap",
|
|
||||||
# header: virtMemHeader.}
|
|
||||||
|
|
@ -2910,13 +2910,6 @@ elif defined(nodejs):
|
||||||
result = $argv[i]
|
result = $argv[i]
|
||||||
else:
|
else:
|
||||||
raise newException(IndexDefect, formatErrorIndexBound(i - 1, argv.len - 2))
|
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):
|
elif defined(windows):
|
||||||
# Since we support GUI applications with Nim, we sometimes generate
|
# Since we support GUI applications with Nim, we sometimes generate
|
||||||
# a WinMain entry proc. But a WinMain proc has no access to the parsed
|
# a WinMain entry proc. But a WinMain proc has no access to the parsed
|
||||||
|
|
@ -3190,7 +3183,7 @@ proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noW
|
||||||
result = getApplAux("/proc/self/exe")
|
result = getApplAux("/proc/self/exe")
|
||||||
elif defined(solaris):
|
elif defined(solaris):
|
||||||
result = getApplAux("/proc/" & $getpid() & "/path/a.out")
|
result = getApplAux("/proc/" & $getpid() & "/path/a.out")
|
||||||
elif defined(genode) or defined(nintendoswitch):
|
elif defined(genode):
|
||||||
raiseOSError(OSErrorCode(-1), "POSIX command line not supported")
|
raiseOSError(OSErrorCode(-1), "POSIX command line not supported")
|
||||||
elif defined(freebsd) or defined(dragonfly) or defined(netbsd):
|
elif defined(freebsd) or defined(dragonfly) or defined(netbsd):
|
||||||
result = getApplFreebsd()
|
result = getApplFreebsd()
|
||||||
|
|
|
||||||
|
|
@ -103,117 +103,6 @@ elif defined(emscripten) and not defined(StandaloneHeapSize):
|
||||||
elif defined(genode) and not defined(StandaloneHeapSize):
|
elif defined(genode) and not defined(StandaloneHeapSize):
|
||||||
include genode/alloc # osAllocPages, osTryAllocPages, osDeallocPages
|
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):
|
elif defined(posix) and not defined(StandaloneHeapSize):
|
||||||
const
|
const
|
||||||
PROT_READ = 1 # page can be read
|
PROT_READ = 1 # page can be read
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue