From 08965c4c11f5966d23ca00294bf89d81eb94e4ac Mon Sep 17 00:00:00 2001 From: RSDuck Date: Wed, 11 Aug 2021 08:54:49 +0200 Subject: [PATCH] Switch maintanance (#18668) * Fix and improve Nintendo Switch support * Document the necessity for nimAllocPagesViaMalloc * update changelog * Use --gc:orc in examples --- changelog.md | 3 + doc/nimc.rst | 21 ++--- lib/nintendoswitch/switch_memory.nim | 36 --------- lib/pure/os.nim | 9 +-- lib/system/osalloc.nim | 111 --------------------------- 5 files changed, 15 insertions(+), 165 deletions(-) delete mode 100644 lib/nintendoswitch/switch_memory.nim diff --git a/changelog.md b/changelog.md index a19697c8cb23a..0e3c54d77bacd 100644 --- a/changelog.md +++ b/changelog.md @@ -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 @@ -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`. diff --git a/doc/nimc.rst b/doc/nimc.rst index ed90779121b8a..ad5ecaa76bc76 100644 --- a/doc/nimc.rst +++ b/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: .. 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 `_ or `here for Windows `_. @@ -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 `_. -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 ============== diff --git a/lib/nintendoswitch/switch_memory.nim b/lib/nintendoswitch/switch_memory.nim deleted file mode 100644 index f34bd363a1bcc..0000000000000 --- a/lib/nintendoswitch/switch_memory.nim +++ /dev/null @@ -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 = "" -const svcHeader = "" -const mallocHeader = "" - -## 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 () -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.} diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 73e3fcb31d5f3..fd618e93cf9d5 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -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 @@ -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() diff --git a/lib/system/osalloc.nim b/lib/system/osalloc.nim index 2830adb485b3a..39bf65d6c9485 100644 --- a/lib/system/osalloc.nim +++ b/lib/system/osalloc.nim @@ -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 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