diff --git a/.github/actions/linux-setup-env/action.yml b/.github/actions/linux-setup-env/action.yml index 1d11cdcfd5..71cdd6d38c 100644 --- a/.github/actions/linux-setup-env/action.yml +++ b/.github/actions/linux-setup-env/action.yml @@ -7,6 +7,8 @@ inputs: java-version: description: "Java version to use in tests" default: "8" + llvm-version: + description: "LLVM toolchain version" runs: using: "composite" steps: @@ -33,6 +35,14 @@ runs: sudo apt-get update sudo apt-get install libgc-dev + - name: Install explicit LLVM toolchain + shell: bash + if: ${{ inputs.llvm-version != '' }} + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + yes "" | sudo ./llvm.sh ${{ inputs.llvm-version }} + # Loads cache with dependencies created in test-tools job - name: Cache dependencies uses: actions/cache@v3 diff --git a/.github/actions/macos-setup-env/action.yml b/.github/actions/macos-setup-env/action.yml index 6ca2bc0688..262546c2e7 100644 --- a/.github/actions/macos-setup-env/action.yml +++ b/.github/actions/macos-setup-env/action.yml @@ -7,6 +7,8 @@ inputs: java-version: description: "Java version to use in tests" default: "8" + gc: + description: "Garbage collector used, might require installing dependencies" runs: using: "composite" steps: @@ -26,6 +28,11 @@ runs: echo "binary-version=3" >> $GITHUB_ENV echo "project-version=3" >> $GITHUB_ENV fi + + - name: Install dependencies + shell: bash + if: ${{ startsWith(inputs.gc, 'boehm') }} + run: brew install bdw-gc # Loads cache with dependencies created in test-tools job - name: Cache dependencies diff --git a/.github/actions/windows-setup-env/action.yml b/.github/actions/windows-setup-env/action.yml index ece8e78806..515b71190d 100644 --- a/.github/actions/windows-setup-env/action.yml +++ b/.github/actions/windows-setup-env/action.yml @@ -7,6 +7,9 @@ inputs: java-version: description: "Java version to use in tests" default: "8" + llvm-version: + description: "LLVM version to use" + default: "11.0.0" outputs: vcpkg-dir: description: "Directory containing installed libraries" @@ -60,9 +63,10 @@ runs: key: ${{ runner.os }}-${{ inputs.scala-version }}-deps # Install LLVM in case if cache is missing + # Choco-Install is GH Actions wrappers around choco, which does retries - name: Install LLVM shell: pwsh - run: choco install llvm --version=11.0.0 --allow-downgrade + run: Choco-Install -PackageName llvm -ArgumentList "--version=${{ inputs.llvm-version }}", "--allow-downgrade", "--force" - name: Add LLVM on Path shell: pwsh diff --git a/.github/workflows/run-tests-macos.yml b/.github/workflows/run-tests-macos.yml index 35db8d626b..5b2c4993c7 100644 --- a/.github/workflows/run-tests-macos.yml +++ b/.github/workflows/run-tests-macos.yml @@ -7,11 +7,11 @@ on: - main - 0.4.x concurrency: - group: macOS-${{ github.head_ref }} + group: macOS-${{ github.head_ref }}-${{ github.event_name }} cancel-in-progress: true jobs: - run-tests: + test-runtime: name: Test runtime runs-on: macos-11 strategy: @@ -35,6 +35,7 @@ jobs: id: setup with: scala-version: ${{matrix.scala}} + gc: ${{ matrix.gc }} - name: Test runtime run: > diff --git a/.github/workflows/run-tests-windows.yml b/.github/workflows/run-tests-windows.yml index eb3feeaa57..179b3ea894 100644 --- a/.github/workflows/run-tests-windows.yml +++ b/.github/workflows/run-tests-windows.yml @@ -7,7 +7,7 @@ on: - main - 0.4.x concurrency: - group: windows-${{ github.head_ref }} + group: windows-${{ github.head_ref }}-${{ github.event_name }} cancel-in-progress: true jobs: @@ -132,3 +132,30 @@ jobs: tests${{env.project-version}}/test testsExt${{env.project-version}}/test shell: cmd + + test-llvm-versions: + runs-on: windows-2019 + strategy: + fail-fast: false + matrix: + scala: [3.2.2] + llvm: ["13.0.1", "14.0.6", "15.0.7"] # Last 3 stable versions + steps: + - name: Setup git config + run: git config --global core.autocrlf false + - uses: actions/checkout@v3 + - uses: ./.github/actions/windows-setup-env + id: setup + with: + scala-version: ${{matrix.scala}} + llvm-version: ${{ matrix.llvm }} + java-version: 8 + + - name: Run tests + shell: cmd + run: > + set SCALANATIVE_INCLUDE_DIRS=${{steps.setup.outputs.vcpkg-dir}}\include& + set SCALANATIVE_LIB_DIRS=${{steps.setup.outputs.vcpkg-dir}}\lib& + set SCALANATIVE_CI_NO_DEBUG_SYMBOLS=true& + set SCALANATIVE & + sbt ++${{matrix.scala}} "show tests3/nativeConfig" "test-runtime ${{matrix.scala}}" diff --git a/clib/src/main/resources/scala-native/math.c b/clib/src/main/resources/scala-native/math.c index f36640db91..223b9743ab 100644 --- a/clib/src/main/resources/scala-native/math.c +++ b/clib/src/main/resources/scala-native/math.c @@ -8,8 +8,14 @@ float scalanative_infinity() { return INFINITY; } float scalanative_nan() { return NAN; } +#if defined(math_errhandling) int scalanative_math_errhandling() { return math_errhandling; } +#endif +#if defined(MATH_ERRNO) int scalanative_math_errno() { return MATH_ERRNO; } +#endif +#if defined(MATH_ERREXCEPT) int scalanative_math_errexcept() { return MATH_ERREXCEPT; } +#endif diff --git a/clib/src/main/scala/scala/scalanative/libc/stdio.scala b/clib/src/main/scala/scala/scalanative/libc/stdio.scala index dbf94dd2af..658a49509a 100644 --- a/clib/src/main/scala/scala/scalanative/libc/stdio.scala +++ b/clib/src/main/scala/scala/scalanative/libc/stdio.scala @@ -2,6 +2,7 @@ package scala.scalanative package libc import scalanative.unsafe._ +import stddef.size_t @extern object stdio { @@ -116,6 +117,7 @@ object stdio { def fwide(stream: Ptr[FILE], mode: CInt): CInt = extern // Direct input/output + /** Reads up to count objects into the array buffer from the given input * stream stream as if by calling fgetc size times for each object, and * storing the results, in the order obtained, into the successive positions @@ -153,8 +155,7 @@ object stdio { size: CSize, count: CSize, stream: Ptr[FILE] - ): CSize = - extern + ): CSize = extern /** Writes count of objects from the given array buffer to the output stream * stream. The objects are written as if by reinterpreting each object as an @@ -412,6 +413,77 @@ object stdio { def ungetc(ch: CInt, stream: Ptr[FILE]): CInt = extern // Formatted input/output + + /** Reads data from stdin and stores them according to the parameter format + * into the locations pointed by the additional arguments. + * @param format + * C string that contains a sequence of characters that control how + * characters extracted from the stream are treated + * + * @param vargs + * Depending on the format string, the function may expect a sequence of + * additional arguments, each containing a pointer to allocated storage + * where the interpretation of the extracted characters is stored with the + * appropriate type. There should be at least as many of these arguments as + * the number of values stored by the format specifiers. Additional + * arguments are ignored by the function. + * @return + * the number of items of the argument listsuccessfully filled on success. + * If a reading error happens or the end-of-file is reached while reading, + * the proper indicator is set (feof or ferror). And, if either happens + * before any data could be successfully read, EOF is returned. + */ + def scanf(format: CString, vargs: Any*): CInt = extern + + /** Reads data from the stream and stores them according to the parameter + * format into the locations pointed by the additional arguments. + * @param stream + * Pointer to a FILE object that identifies the input stream to read data + * from. + * @param format + * C string that contains a sequence of characters that control how + * characters extracted from the stream are treated + * + * @param vargs + * Depending on the format string, the function may expect a sequence of + * additional arguments, each containing a pointer to allocated storage + * where the interpretation of the extracted characters is stored with the + * appropriate type. There should be at least as many of these arguments as + * the number of values stored by the format specifiers. Additional + * arguments are ignored by the function. + * @return + * the number of items of the argument listsuccessfully filled on success. + * If a reading error happens or the end-of-file is reached while reading, + * the proper indicator is set (feof or ferror). And, if either happens + * before any data could be successfully read, EOF is returned. + */ + def fscanf(stream: Ptr[FILE], format: CString, vargs: Any*): CInt = + extern + + /** Reads data from s and stores them according to parameter format into the + * locations given by the additional arguments, as if scanf was used, but + * reading from s instead of the standard input + * @param s + * C string that the function processes as its source to retrieve the data. + * @param format + * C string that contains a sequence of characters that control how + * characters extracted from the stream are treated + * + * @param vargs + * Depending on the format string, the function may expect a sequence of + * additional arguments, each containing a pointer to allocated storage + * where the interpretation of the extracted characters is stored with the + * appropriate type. There should be at least as many of these arguments as + * the number of values stored by the format specifiers. Additional + * arguments are ignored by the function. + * @return + * the number of items of the argument listsuccessfully filled on success. + * If a reading error happens or the end-of-file is reached while reading, + * the proper indicator is set (feof or ferror). And, if either happens + * before any data could be successfully read, EOF is returned. + */ + def sscanf(s: CString, format: CString, vargs: Any*): CInt = extern + /** Read formatted data into variable argument list Reads data from the * standard input (stdin) and stores them according to parameter format into * the locations pointed by the elements in the variable argument list @@ -472,6 +544,175 @@ object stdio { def vsscanf(buffer: CString, format: CString, valist: CVarArgList): CInt = extern + /** Writes the results to stdout. + * @param format + * pointer to a null-terminated character string specifying how to + * interpret the data + * @param vargs + * variable argument list containing the data to print. + * + * @return + * The number of characters written if successful or negative value if an + * error occurred. + * @see + * [[https://en.cppreference.com/w/c/io/printf]] + */ + def printf(format: CString, vargs: Any*): CInt = extern + + /** Writes the results to selected stream. + * @param stream + * output file stream to write to + * @param format + * pointer to a null-terminated character string specifying how to + * interpret the data + * @param vargs + * variable argument list containing the data to print. + * + * @return + * The number of characters written if successful or negative value if an + * error occurred. + * @see + * [[https://en.cppreference.com/w/c/io/fprintf]] + */ + def fprintf(stream: Ptr[FILE], format: CString, vargs: Any*): CInt = + extern + + /** Writes the results to a character string buffer. + * @param buffer + * pointer to a character string to write to + * @param format + * pointer to a null-terminated character string specifying how to + * interpret the data + * @param vargs + * variable argument list containing the data to print. + * + * @return + * The number of characters written if successful or negative value if an + * error occurred. + * @see + * [[https://en.cppreference.com/w/c/io/sprintf]] + */ + def sprintf( + buffer: Ptr[CChar], + format: CString, + vargs: Any* + ): CInt = extern + + /** Writes the results to a character string buffer. At most bufsz - 1 + * characters are written. The resulting character string will be terminated + * with a null character, unless bufsz is zero. If bufsz is zero, nothing is + * written and buffer may be a null pointer, however the return value (number + * of bytes that would be written not including the null terminator) is still + * calculated and returned. + * @param buffer + * pointer to a character string to write to + * @param busz + * number of character to write + * @param format + * pointer to a null-terminated character string specifying how to + * interpret the data + * @param vargs + * variable argument list containing the data to print. + * + * @return + * The number of characters written if successful or negative value if an + * error occurred. + * @see + * [[https://en.cppreference.com/w/c/io/snprintf]] + */ + def snprintf( + buffer: Ptr[CChar], + bufsz: size_t, + format: CString, + vargs: Any* + ): CInt = extern + + /** Writes the results to stdout. + * @param format + * pointer to a null-terminated character string specifying how to + * interpret the data + * @param vargs + * variable argument list containing the data to print. + * + * @return + * The number of characters written if successful or negative value if an + * error occurred. + * @see + * [[https://en.cppreference.com/w/c/io/printf_s]] + */ + def printf_s(format: CString, vargs: Any*): CInt = extern + + /** Writes the results to selected stream. + * @param stream + * output file stream to write to + * @param format + * pointer to a null-terminated character string specifying how to + * interpret the data + * @param vargs + * variable argument list containing the data to print. + * + * @return + * The number of characters written if successful or negative value if an + * error occurred. + * @see + * [[https://en.cppreference.com/w/c/io/fprintf_s]] + */ + def fprintf_s( + stream: Ptr[FILE], + format: CString, + vargs: Any* + ): CInt = extern + + /** Writes the results to a character string buffer. + * @param buffer + * pointer to a character string to write to + * @param format + * pointer to a null-terminated character string specifying how to + * interpret the data + * @param vargs + * variable argument list containing the data to print. + * + * @return + * The number of characters written if successful or negative value if an + * error occurred. + * @see + * [[https://en.cppreference.com/w/c/io/sprintf_s]] + */ + def sprintf_s( + buffer: Ptr[CChar], + format: CString, + vargs: Any* + ): CInt = extern + + /** Writes the results to a character string buffer. At most bufsz - 1 + * characters are written. The resulting character string will be terminated + * with a null character, unless bufsz is zero. If bufsz is zero, nothing is + * written and buffer may be a null pointer, however the return value (number + * of bytes that would be written not including the null terminator) is still + * calculated and returned. + * @param buffer + * pointer to a character string to write to + * @param busz + * number of character to write + * @param format + * pointer to a null-terminated character string specifying how to + * interpret the data + * @param vargs + * variable argument list containing the data to print. + * + * @return + * The number of characters written if successful or negative value if an + * error occurred. + * @see + * [[https://en.cppreference.com/w/c/io/snprintf_s]] + */ + def snprintf_s( + buffer: Ptr[CChar], + bufsz: size_t, + format: CString, + vargs: Any* + ): CInt = extern + /** Writes the results to stdout. * @param format * pointer to a null-terminated character string specifying how to diff --git a/docs/changelog/0.4.11.md b/docs/changelog/0.4.11.md new file mode 100644 index 0000000000..fbdd56b0d2 --- /dev/null +++ b/docs/changelog/0.4.11.md @@ -0,0 +1,178 @@ + +# 0.4.11 (2023-03-15) + +We're happy to announce the release of Scala Native. It's the next maintenance release for Scala Native 0.4.x. As always it brings bug fixes and minor improvements. + +## Notable changes + +### Extern methods with a variadic number of arguments +For a long time, Scala Native supported C `va_list` using `scalanative.unsafe.CVarArgList`. This allowed for interop with some of the C functions taking the variadic number of arguments. This release makes usage and definition of them easier, by restoring support for idiomatic ways of passing them using Scala variadic arguments lists. +```c +void printf(char* format, ...); +``` + +```scala +@extern def printf(format: CString, args: Any*): Unit = extern + +@main def test() = + val msg = c"MyMessage" + printf("String '%s' is allocated at %p and has %d characters\n", msg, msg, strlen(msg)) +``` + +### Support for LLVM 15 +The latest versions of LLVM added a new internal representation of pointers - their opaque variant replaces typed pointers. This change should not affect most of the users, but in some specific builds it could have lead to linking issues. +Now Scala Native will try to detect version of LLVM toolchain. When using LLVM 15 or newer Scala Native toolchain would always generate opaque pointers in the compiled LLVM IR. + +The Scala standard library used by this release is based on the following versions: + + + + + + + + + + + + + + + + + + + +
Scala binary versionScala release
2.122.12.17
2.132.13.10
33.2.2
+ + + + + + + + + + + + + + + + +
Commits since last release43
Merged PRs40
Contributors6
+ +## Contributors + +Big thanks to everybody who contributed to this release or reported an issue! + +``` +$ git shortlog -sn --no-merges v0.4.10..v0.4.11 + 24 Wojciech Mazur + 14 LeeTibbert + 2 Arman Bilge + 1 João Costa + 1 Ondra Pelech + 1 philwalk +``` + +## Merged PRs + +## [](https://github.com/scala-native/scala-native/tree/) (2023-03-16) + +[Full Changelog](https://github.com/scala-native/scala-native/compare/v0.4.10..v0.4.11) + +**Merged pull requests:** + +## Java Standard Library +- Partial Fix #3090: j.nio.MappedByteBuffer no longer causes segmentation fault on FreeBSD64 + [\#3113](https://github.com/scala-native/scala-native/pull/3113) + ([LeeTibbert](https://github.com/LeeTibbert)) +- Port all missing `java.util.function` types + [\#3127](https://github.com/scala-native/scala-native/pull/3127) + ([WojciechMazur](https://github.com/WojciechMazur)) +- Fix #3131: javalib ServerSocket should now be more accepting + [\#3140](https://github.com/scala-native/scala-native/pull/3140) + ([LeeTibbert](https://github.com/LeeTibbert)) +- Fix #3153: j.nio.fs.FileHelpers uses only java.io.tmp property for temporary files/dirs + [\#3155](https://github.com/scala-native/scala-native/pull/3155) + ([LeeTibbert](https://github.com/LeeTibbert)) +- Fix #3071, #3135: Implement Java 11 writeString & readString methods and Java 10 transferTo + [\#3159](https://github.com/scala-native/scala-native/pull/3159) + ([LeeTibbert](https://github.com/LeeTibbert)) +- Fix #2937, 3163: improved j.nio.f.Files default directory idiom handling + [\#3166](https://github.com/scala-native/scala-native/pull/3166) + ([LeeTibbert](https://github.com/LeeTibbert)) +- Partial fix #3165: Port two JSR-166 concurrent interfaces/traits: BlockingDeque, TransferQueue + [\#3188](https://github.com/scala-native/scala-native/pull/3188) + ([LeeTibbert](https://github.com/LeeTibbert)) +- Fix #3192. #3194: Implement limited java spliterator support + [\#3202](https://github.com/scala-native/scala-native/pull/3202) + ([LeeTibbert](https://github.com/LeeTibbert)) +- javalib Spliterators trySplit() methods now split + [\#3218](https://github.com/scala-native/scala-native/pull/3218) + ([LeeTibbert](https://github.com/LeeTibbert)) + +## POSIX bindings +- posixlib socket.c now compiles on FreeBSD arm64 + [\#3112](https://github.com/scala-native/scala-native/pull/3112) + ([LeeTibbert](https://github.com/LeeTibbert)) +- Fix #1642: posixlib stdio.scala is now mostly Open Group 2018 compliant + [\#3160](https://github.com/scala-native/scala-native/pull/3160) + ([LeeTibbert](https://github.com/LeeTibbert)) +- Fix #3206: posixlib unistd and monetary use new CVarArgs support + [\#3209](https://github.com/scala-native/scala-native/pull/3209) + ([LeeTibbert](https://github.com/LeeTibbert)) + +## Compiler plugin +- Fix generation of CFuncPtr extern forwarders using opaque types + [\#3182](https://github.com/scala-native/scala-native/pull/3182) + ([WojciechMazur](https://github.com/WojciechMazur)) +- Don't emit `Inst.Jump`/`Inst.Label` in NIR taking single `Unit` argument + [\#3201](https://github.com/scala-native/scala-native/pull/3201) + ([WojciechMazur](https://github.com/WojciechMazur)) +- Restore support for C VarArgs alongside current CVarArgLists + [\#3204](https://github.com/scala-native/scala-native/pull/3204) + ([WojciechMazur](https://github.com/WojciechMazur)) +- Allow for materialization of `Tag[Ptr[_]]` or taking abstract type + [\#3207](https://github.com/scala-native/scala-native/pull/3207) + ([WojciechMazur](https://github.com/WojciechMazur)) + +## Toolchain +- Define null guards for methods using `this` + [\#3123](https://github.com/scala-native/scala-native/pull/3123) + ([WojciechMazur](https://github.com/WojciechMazur)) +- Fix #3173: Linux executable file .comment section shows build info + [\#3183](https://github.com/scala-native/scala-native/pull/3183) + ([LeeTibbert](https://github.com/LeeTibbert)) +- Fix cygwin and msys build problems + [\#3180](https://github.com/scala-native/scala-native/pull/3180) + ([philwalk](https://github.com/philwalk)) +- Use opaque pointers in generated LLVM IR when possible + [\#3190](https://github.com/scala-native/scala-native/pull/3190) + ([WojciechMazur](https://github.com/WojciechMazur)) +- Don't emit debug logs when skipping embeding source files + [\#3191](https://github.com/scala-native/scala-native/pull/3191) + ([WojciechMazur](https://github.com/WojciechMazur)) +- Emit `Val.Unit`/`Type.Unit` as `void` in LLVM IR instead of ref to `BoxedUnit` + [\#3200](https://github.com/scala-native/scala-native/pull/3200) + ([WojciechMazur](https://github.com/WojciechMazur)) +- Restore stack state after executing inlined function to prevent stack overflows + [\#3199](https://github.com/scala-native/scala-native/pull/3199) + ([WojciechMazur](https://github.com/WojciechMazur)) +- Fix poisonous new lines escapes in `nir.Show` leading to linker failures + [\#3208](https://github.com/scala-native/scala-native/pull/3208) + ([WojciechMazur](https://github.com/WojciechMazur)) + +## Runtime +- Update LLVM libunwind to 15.0.7 (was 12.0.1) + [\#3184](https://github.com/scala-native/scala-native/pull/3184) + ([WojciechMazur](https://github.com/WojciechMazur)) +- Commix GC - fix deadlocks due to missaligned pointers when marking range + [\#3185](https://github.com/scala-native/scala-native/pull/3185) + ([WojciechMazur](https://github.com/WojciechMazur)) + +## sbt plugin +- Port `definedTestNames` override from Scala.js + [\#3203](https://github.com/scala-native/scala-native/pull/3203) + ([armanbilge](https://github.com/armanbilge)) diff --git a/docs/changelog/0.4.12.md b/docs/changelog/0.4.12.md new file mode 100644 index 0000000000..879cc024e4 --- /dev/null +++ b/docs/changelog/0.4.12.md @@ -0,0 +1,109 @@ +We're happy to announce the release of Scala Native. It's the next maintenance release for Scala Native 0.4.x. +This release fixes regressions introduced in the previous version and adds some requested features. + + +The Scala standard library used by this release is based on the following versions: + + + + + + + + + + + + + + + + + + + +
Scala binary versionScala release
2.122.12.17
2.132.13.10
33.2.2
+ + + + + + + + + + + + + + + + +
Commits since last release10
Merged PRs8
Contributors3
+ +## Notable changes + +### Composable extern definitions using `@extern trait` +Extern definitions can now be composed using traits annotated as `@extern`. Extern objects can now be composed using multiple extern traits allowing for better modeling of foreign APIs. +A good candidate for modeling C bindings with this approach can be `errno.h` from C standard library and its POSIX extension. +It can now be modeled as following + +```scala +import scala.scalanative.unsafe.* + +@extern trait errnoC { + var errno: CInt = extern + + def EILSEQ: CInt = extern +} + +@extern trait errnoPosix extends errnoC { + def EWOULDBLOCK: CInt = extern + def EINPROGRESS: CInt = extern + def EINTR: CInt = extern +} + +@extern object errno extends errnoC with errnoPosix +``` +The current bindings of POSIX and C standard library are not affected by this change, however, new model would be used in Scala Native 0.5.x + +## Contributors + +Big thanks to everybody who contributed to this release or reported an issue! + +``` +$ git shortlog -sn --no-merges v0.4.11..v0.4.12 + 8 Wojciech Mazur + 1 Eric K Richardson + 1 LeeTibbert +``` + +## Merged PRs + +## [](https://github.com/scala-native/scala-native/tree/) (2023-03-22) + +[Full Changelog](https://github.com/scala-native/scala-native/compare/v0.4.11..v0.4.12) + +**Merged pull requests:** + + +## POSIX bindings +- Implement posixlib dlfcn + [\#3234](https://github.com/scala-native/scala-native/pull/3234) + ([LeeTibbert](https://github.com/LeeTibbert)) + +## Compiler plugin +- Improve resolving repeated parameters in Scala3 extern methods + [\#3230](https://github.com/scala-native/scala-native/pull/3230) + ([WojciechMazur](https://github.com/WojciechMazur)) +- Fix exports of extern methods using variadic arguments in Scala3 + [\#3232](https://github.com/scala-native/scala-native/pull/3232) + ([WojciechMazur](https://github.com/WojciechMazur)) +- Allow to compose extern definitions using `@extern trait` + [\#2988](https://github.com/scala-native/scala-native/pull/2988) + ([WojciechMazur](https://github.com/WojciechMazur)) + +## Toolchain +- Fix regression in handling opaque pointers on Windows + [\#3226](https://github.com/scala-native/scala-native/pull/3226) + ([WojciechMazur](https://github.com/WojciechMazur)) diff --git a/docs/changelog/index.rst b/docs/changelog/index.rst index d2d67953a3..7f8d59ab1a 100644 --- a/docs/changelog/index.rst +++ b/docs/changelog/index.rst @@ -5,7 +5,9 @@ Changelog .. toctree:: :maxdepth: 1 - + + 0.4.12 + 0.4.11 0.4.10 0.4.9 0.4.8 diff --git a/docs/conf.py b/docs/conf.py index 31ac2ffa8b..6a18c37867 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -69,9 +69,9 @@ def generateScalaNativeCurrentYear(): # built documents. # # The short X.Y version. -version = u'0.4.10' +version = u'0.4.12' # The full version, including alpha/beta/rc tags. -release = u'0.4.10' +release = u'0.4.12' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/lib/javalib.rst b/docs/lib/javalib.rst index f804049147..dbb197b2f4 100644 --- a/docs/lib/javalib.rst +++ b/docs/lib/javalib.rst @@ -423,6 +423,7 @@ java.util * ``NoSuchElementException`` * ``Objects`` * ``Optional`` +* ``PrimitiveIterator`` * ``PriorityQueue`` * ``Properties`` * ``Queue`` @@ -434,6 +435,8 @@ java.util * ``SizeChangeEvent`` * ``SortedMap`` * ``SortedSet`` +* ``Spliterator`` +* ``Spliterators`` * ``StringTokenizer`` * ``TooManyListenersException`` * ``TreeSet`` diff --git a/docs/lib/posixlib.rst b/docs/lib/posixlib.rst index b1861b5374..198f4e9311 100644 --- a/docs/lib/posixlib.rst +++ b/docs/lib/posixlib.rst @@ -16,7 +16,7 @@ C Header Scala Native Module `cpio.h`_ scala.scalanative.posix.cpio_ `ctype.h`_ scala.scalanative.libc.ctype_ `dirent.h`_ scala.scalanative.posix.dirent_ -`dlfcn.h`_ N/A +`dlfcn.h`_ scala.scalanative.posix.dlfcn_ `errno.h`_ scala.scalanative.posix.errno_ `fcntl.h`_ scala.scalanative.posix.fcntl_ `fenv.h`_ N/A @@ -183,6 +183,7 @@ C Header Scala Native Module .. _scala.scalanative.libc.ctype: https://github.com/scala-native/scala-native/blob/main/clib/src/main/scala/scala/scalanative/libc/ctype.scala .. _scala.scalanative.posix.cpio: https://github.com/scala-native/scala-native/blob/main/posixlib/src/main/scala/scala/scalanative/posix/cpio.scala .. _scala.scalanative.posix.dirent: https://github.com/scala-native/scala-native/blob/main/posixlib/src/main/scala/scala/scalanative/posix/dirent.scala +.. _scala.scalanative.posix.dlfcn: https://github.com/scala-native/scala-native/blob/main/posixlib/src/main/scala/scala/scalanative/posix/dlfcn.scala .. _scala.scalanative.posix.errno: https://github.com/scala-native/scala-native/blob/main/posixlib/src/main/scala/scala/scalanative/posix/errno.scala .. _scala.scalanative.posix.fcntl: https://github.com/scala-native/scala-native/blob/main/posixlib/src/main/scala/scala/scalanative/posix/fcntl.scala .. _scala.scalanative.libc.float: https://github.com/scala-native/scala-native/blob/main/clib/src/main/scala/scala/scalanative/libc/float.scala diff --git a/docs/user/setup.rst b/docs/user/setup.rst index be8a42a182..f61addc016 100644 --- a/docs/user/setup.rst +++ b/docs/user/setup.rst @@ -6,7 +6,7 @@ Environment setup Scala Native has the following build dependencies: * Java 8 or newer -* sbt 1.1.6 or newer +* sbt 1.5.8 or newer * LLVM/Clang 6.0 or newer And following completely optional runtime library dependencies: @@ -72,7 +72,7 @@ installation of macOS. .. code-block:: shell - $ sudo apt install clang + $ sudo apt install clang libstdc++-12-dev $ sudo apt install libgc-dev # optional **Arch Linux** diff --git a/javalib/src/main/scala/java/io/Reader.scala b/javalib/src/main/scala/java/io/Reader.scala index 5b477fd520..23c5817adc 100644 --- a/javalib/src/main/scala/java/io/Reader.scala +++ b/javalib/src/main/scala/java/io/Reader.scala @@ -1,6 +1,7 @@ package java.io // Ported from Scala.js, commit: 7d7a621, dated 2022-03-07 +// 2023-02-01 implemented Java 10 transferTo() method import java.nio.CharBuffer @@ -82,4 +83,21 @@ abstract class Reader() extends Readable with Closeable { def close(): Unit + // Since: Java 10 + def transferTo(out: Writer): Long = { + val buffer = new Array[Char](4096) + + @tailrec + def loop(nRead: Long): Long = { + val n = this.read(buffer) + if (n == -1) { + nRead + } else { + out.write(buffer, 0, n) + loop(nRead + n) + } + } + + loop(0) + } } diff --git a/javalib/src/main/scala/java/io/StringWriter.scala b/javalib/src/main/scala/java/io/StringWriter.scala index 3f8344c0ce..11d8e0feb4 100644 --- a/javalib/src/main/scala/java/io/StringWriter.scala +++ b/javalib/src/main/scala/java/io/StringWriter.scala @@ -1,10 +1,13 @@ package java.io -class StringWriter extends Writer { +class StringWriter(initialSize: Int) extends Writer { - private[this] val buf = new StringBuffer + def this() = this(128) - def this(initialSize: Int) = this() + if (initialSize < 0) + throw new IllegalArgumentException("Initial size < 0") + + private val buf = new StringBuffer(initialSize) override def write(c: Int): Unit = buf.append(c.toChar) diff --git a/javalib/src/main/scala/java/lang/Iterable.scala b/javalib/src/main/scala/java/lang/Iterable.scala index f0276e25b6..404f94d220 100644 --- a/javalib/src/main/scala/java/lang/Iterable.scala +++ b/javalib/src/main/scala/java/lang/Iterable.scala @@ -1,9 +1,11 @@ // Ported from Scala.js commit: f9fc1a dated: 2020-03-06 +// default spliterator method added for Scala Native. package java.lang import java.util.Iterator import java.util.function.Consumer +import java.util.{Spliterator, Spliterators} trait Iterable[T] { def iterator(): Iterator[T] @@ -13,4 +15,14 @@ trait Iterable[T] { while (iter.hasNext()) action.accept(iter.next()) } + + /** From the Java 8 documentation: The default implementation should usually + * be overridden. The spliterator returned by the default implementation has + * poor splitting capabilities, is unsized, and does not report any + * spliterator characteristics. Implementing classes can nearly always + * provide a better implementation. + */ + def spliterator(): Spliterator[T] = { + Spliterators.spliteratorUnknownSize[T](this.iterator(), 0) + } } diff --git a/javalib/src/main/scala/java/lang/Runtime.scala b/javalib/src/main/scala/java/lang/Runtime.scala index db21451ce7..cccffc39a1 100644 --- a/javalib/src/main/scala/java/lang/Runtime.scala +++ b/javalib/src/main/scala/java/lang/Runtime.scala @@ -8,7 +8,7 @@ class Runtime private () { import Runtime.ProcessBuilderOps def availableProcessors(): Int = 1 def exit(status: Int): Unit = stdlib.exit(status) - def gc(): Unit = () + def gc(): Unit = System.gc() @stub def addShutdownHook(thread: java.lang.Thread): Unit = ??? diff --git a/javalib/src/main/scala/java/lang/Throwables.scala b/javalib/src/main/scala/java/lang/Throwables.scala index e90d6a430b..74ac7eb7e6 100644 --- a/javalib/src/main/scala/java/lang/Throwables.scala +++ b/javalib/src/main/scala/java/lang/Throwables.scala @@ -1,6 +1,7 @@ package java.lang import scala.collection.mutable +import scala.scalanative.meta.LinktimeInfo import scalanative.unsafe._ import scalanative.unsigned._ import scalanative.runtime.unwind @@ -11,10 +12,10 @@ private[lang] object StackTrace { private def makeStackTraceElement( cursor: Ptr[scala.Byte] - ): StackTraceElement = { + )(implicit zone: Zone): StackTraceElement = { val nameMax = 1024 - val name: Ptr[CChar] = stackalloc[CChar](nameMax.toUInt) - val offset: Ptr[scala.Byte] = stackalloc[scala.Byte](8.toUInt) + val name = alloc[CChar](nameMax.toUInt) + val offset = alloc[scala.Long]() unwind.get_proc_name(cursor, name, nameMax.toUInt, offset) @@ -33,7 +34,7 @@ private[lang] object StackTrace { private def cachedStackTraceElement( cursor: Ptr[scala.Byte], ip: CUnsignedLong - ): StackTraceElement = + )(implicit zone: Zone): StackTraceElement = cache.getOrElseUpdate(ip, makeStackTraceElement(cursor)) @noinline private[lang] def currentStackTrace(): Array[StackTraceElement] = { @@ -41,15 +42,19 @@ private[lang] object StackTrace { val context: Ptr[scala.Byte] = stackalloc[scala.Byte](2048.toUInt) val offset: Ptr[scala.Byte] = stackalloc[scala.Byte](8.toUInt) val ip = stackalloc[CUnsignedLongLong]() - var buffer = mutable.ArrayBuffer.empty[StackTraceElement] - - unwind.get_context(context) - unwind.init_local(cursor, context) - while (unwind.step(cursor) > 0) { - unwind.get_reg(cursor, unwind.UNW_REG_IP, ip) - buffer += cachedStackTraceElement(cursor, !ip) + val buffer = mutable.ArrayBuffer.empty[StackTraceElement] + Zone { implicit z => + val cursor = alloc[scala.Byte](unwind.sizeOfCursor) + val context = alloc[scala.Byte](unwind.sizeOfContext) + val ip = stackalloc[CSize]() + + unwind.get_context(context) + unwind.init_local(cursor, context) + while (unwind.step(cursor) > 0) { + unwind.get_reg(cursor, unwind.UNW_REG_IP, ip) + buffer += cachedStackTraceElement(cursor, !ip) + } } - buffer.toArray } } diff --git a/javalib/src/main/scala/java/net/AbstractPlainSocketImpl.scala b/javalib/src/main/scala/java/net/AbstractPlainSocketImpl.scala index 6f9213e5bf..9d31312c9d 100644 --- a/javalib/src/main/scala/java/net/AbstractPlainSocketImpl.scala +++ b/javalib/src/main/scala/java/net/AbstractPlainSocketImpl.scala @@ -5,23 +5,23 @@ import scala.scalanative.unsafe._ import scala.scalanative.runtime.ByteArray import scalanative.libc.string.memcpy -import scalanative.libc.errno.errno +import scala.scalanative.posix.arpa.inet // Import posix name errno as variable, not class or type. +import scala.scalanative.libc.errno.errno import scala.scalanative.posix.{errno => posixErrno}, posixErrno._ -import scala.scalanative.posix.unistd -import scala.scalanative.posix.sys.socket -import scala.scalanative.posix.sys.socketOps._ -import scala.scalanative.posix.sys.ioctl._ import scala.scalanative.posix.netinet.in import scala.scalanative.posix.netinet.inOps._ import scala.scalanative.posix.netinet.tcp -import scala.scalanative.posix.arpa.inet import scala.scalanative.posix.netdb._ import scala.scalanative.posix.netdbOps._ +import scala.scalanative.posix.string.strerror +import scala.scalanative.posix.sys.ioctl._ +import scala.scalanative.posix.sys.socket +import scala.scalanative.posix.sys.socketOps._ import scala.scalanative.posix.sys.time._ import scala.scalanative.posix.sys.timeOps._ -import scala.scalanative.posix.arpa.inet._ +import scala.scalanative.posix.unistd import scala.scalanative.meta.LinktimeInfo.isWindows import java.io.{FileDescriptor, IOException, OutputStream, InputStream} @@ -120,7 +120,7 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl { */ sa6.sin6_family = socket.AF_INET6.toUShort - sa6.sin6_port = htons(port.toUShort) + sa6.sin6_port = inet.htons(port.toUShort) val src = inetAddress.getAddress() @@ -217,7 +217,7 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl { if (timeout > 0) tryPollOnAccept() - val storage: Ptr[Byte] = stackalloc[Byte](sizeof[in.sockaddr_in6]) + val storage = stackalloc[Byte](sizeof[in.sockaddr_in6]) val len = stackalloc[socket.socklen_t]() !len = sizeof[in.sockaddr_in6].toUInt @@ -230,30 +230,33 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl { storage.asInstanceOf[Ptr[socket.sockaddr_storage]].ss_family.toInt val ipstr: Ptr[CChar] = stackalloc[CChar](in.INET6_ADDRSTRLEN.toUInt) - if (family == socket.AF_INET) { + val (srcPtr, port) = if (family == socket.AF_INET) { val sa = storage.asInstanceOf[Ptr[in.sockaddr_in]] - inet.inet_ntop( - socket.AF_INET, - sa.sin_addr.asInstanceOf[Ptr[Byte]], - ipstr, - in.INET6_ADDRSTRLEN.toUInt - ) - s.port = inet.ntohs(sa.sin_port).toInt + val port = inet.ntohs(sa.sin_port).toInt + (sa.sin_addr.at1.asInstanceOf[Ptr[Byte]], port) } else { val sa = storage.asInstanceOf[Ptr[in.sockaddr_in6]] - inet.inet_ntop( - socket.AF_INET6, - sa.sin6_addr.asInstanceOf[Ptr[Byte]], - ipstr, - in.INET6_ADDRSTRLEN.toUInt + val port = inet.ntohs(sa.sin6_port).toInt + (sa.sin6_addr.at1.at(0).asInstanceOf[Ptr[Byte]], port) + } + + val ret = inet.inet_ntop( + family, + srcPtr, + ipstr, + in.INET6_ADDRSTRLEN.toUInt + ) + + if (ret == null) { + throw new IOException( + s"inet_ntop failed: ${fromCString(strerror(errno))}" ) - s.port = inet.ntohs(sa.sin6_port).toInt } s.address = InetAddress.getByName(fromCString(ipstr)) - - s.fd = new FileDescriptor(newFd) + s.port = port s.localport = this.localport + s.fd = new FileDescriptor(newFd) } override def connect(host: String, port: Int): Unit = { @@ -433,7 +436,7 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl { } else if (isClosed) { 0 } else { - val cArr = buffer.asInstanceOf[ByteArray].at(offset) + val cArr = buffer.at(offset) var sent = 0 while (sent < count) { val ret = socket @@ -452,7 +455,7 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl { if (shutInput) -1 else { val bytesNum = socket - .recv(fd.fd, buffer.asInstanceOf[ByteArray].at(offset), count.toUInt, 0) + .recv(fd.fd, buffer.at(offset), count.toUInt, 0) .toInt def timeoutDetected = mapLastError( diff --git a/javalib/src/main/scala/java/net/InetAddress.scala b/javalib/src/main/scala/java/net/InetAddress.scala index 55839cda2a..edeaef3420 100644 --- a/javalib/src/main/scala/java/net/InetAddress.scala +++ b/javalib/src/main/scala/java/net/InetAddress.scala @@ -225,7 +225,7 @@ object InetAddress { new Inet4Address(addrinfoToByteArray(addrinfoP), effectiveHost) } else if (addrinfoP.ai_family == AF_INET6) { val addr = addrinfoP.ai_addr.asInstanceOf[Ptr[sockaddr_in6]] - val addrBytes = addr.sin6_addr.at1.asInstanceOf[Ptr[Byte]] + val addrBytes = addr.sin6_addr.at1.at(0).asInstanceOf[Ptr[Byte]] // Scala JVM down-converts even when preferIPv6Addresses is "true" if (isIPv4MappedAddress(addrBytes)) { @@ -305,16 +305,11 @@ object InetAddress { } private def formatIn4Addr(pb: Ptr[Byte]): String = { - val addr = pb.asInstanceOf[Ptr[in_addr]] + // By contract, pb isInstanceOf[Ptr[in_addr]] val dstSize = INET_ADDRSTRLEN val dst = stackalloc[Byte](dstSize.toUInt) - val result = inet_ntop( - AF_INET, - addr.at1.asInstanceOf[Ptr[Byte]], - dst, - dstSize.toUInt - ) + val result = inet_ntop(AF_INET, pb, dst, dstSize.toUInt) if (result == null) throw new IOException(s"inet_ntop IPv4 failed, errno: ${errno}") diff --git a/javalib/src/main/scala/java/net/InterfaceAddress.scala b/javalib/src/main/scala/java/net/InterfaceAddress.scala new file mode 100644 index 0000000000..d09045c4c1 --- /dev/null +++ b/javalib/src/main/scala/java/net/InterfaceAddress.scala @@ -0,0 +1,43 @@ +package java.net + +class InterfaceAddress private[net] ( + inetAddr: InetAddress, + broadcastAddr: Option[Array[Byte]], + prefixLength: Short +) { + + override def equals(that: Any): Boolean = that match { + case that: InterfaceAddress => this.hashCode() == that.hashCode() + case _ => false + } + + def getAddress(): InetAddress = inetAddr + + lazy val bcAddress = { + if (broadcastAddr.isEmpty) null + else InetAddress.getByAddress(broadcastAddr.get) + } + + def getBroadcast(): InetAddress = bcAddress + + def getNetworkPrefixLength(): Short = prefixLength + + /** This hashCode is not intended or guaranteed to match Java. + */ + override def hashCode(): Int = + inetAddr.hashCode() + broadcastAddr.hashCode() + prefixLength + + override def toString(): String = { + val iaPart = inetAddr.getHostAddress() + + val broadcastPart = + if (broadcastAddr.isEmpty) "null" + else { + // Not the most runtime efficient algorithm, but easy to implement. + InetAddress.getByAddress(broadcastAddr.get).toString() + } + + s"/${iaPart}/${prefixLength} [${broadcastPart}]" + } + +} diff --git a/javalib/src/main/scala/java/net/NetworkInterface.scala b/javalib/src/main/scala/java/net/NetworkInterface.scala new file mode 100644 index 0000000000..437c66c33d --- /dev/null +++ b/javalib/src/main/scala/java/net/NetworkInterface.scala @@ -0,0 +1,984 @@ +package java.net + +import scala.scalanative.unsafe._ +import scala.scalanative.unsigned._ + +import scala.annotation.tailrec + +import java.net.SocketHelpers.sockaddrToByteArray + +import java.{util => ju} +import ju.Objects +import ju.stream.Stream + +import scala.scalanative.libc.errno.errno +import scala.scalanative.libc.string.strncpy +import scala.scalanative.posix.errno.ENXIO +import scala.scalanative.posix.net.`if`._ +import scala.scalanative.posix.net.ifOps._ +import scala.scalanative.posix.netinet.in._ +import scala.scalanative.posix.netinet.inOps._ +import scala.scalanative.posix.sys.ioctl.ioctl +import scala.scalanative.posix.sys.socket._ +import scala.scalanative.posix.sys.socketOps._ +import scala.scalanative.posix.string._ +import scala.scalanative.posix.unistd + +import scala.scalanative.meta.LinktimeInfo +import scala.scalanative.runtime.Platform + +import macOsIf._ +import macOsIfDl._ + +/* Design Notes: + * 1) This code is Unix only. On Windows, "empty" values are returned. + * A Windows implementation is left as an exercise for the reader. + * + * 2) The Unix implementation often splits into a Linux path and a + * macOS/BSD path. The former uses ioctl() calls and lets the + * operating system search for the named interface. Such a kernel + * search should be marginally faster and less error prone than + * the user land search of getifaddrs() results done on the + * macOS/BSD path. + * + * 3) Virtual and/or sub-interface methods rely on the convention that such + * interfaces have a colon (:) in the name. Improvements are welcome. + * + * 4) For future reference: + * GetAdaptersAddresses() function exist on Windows Vista and later. + * Function returns a linked list of detailed adapter information + * (much more than just addresses). + * C examples are provided in the documentation on MSDN. + */ + +class NetworkInterface private (ifName: String) { + + override def equals(that: Any): Boolean = that match { + case that: NetworkInterface => this.hashCode() == that.hashCode() + case _ => false + } + + def getDisplayName(): String = getName() + + def getHardwareAddress(): Array[Byte] = { + if (Platform.isWindows()) new Array[Byte](0) // No Windows support + else { + NetworkInterface.unixImplGetHardwareAddress(ifName) + } + } + + def getIndex(): Int = { + if (Platform.isWindows()) 0 // No Windows support + else { + NetworkInterface.unixImplGetIndex(ifName) + } + } + + def getInetAddresses(): ju.Enumeration[InetAddress] = { + if (Platform.isWindows()) { // No Windows support + ju.Collections.enumeration[InetAddress](new ju.ArrayList[InetAddress]) + } else { + NetworkInterface.unixImplGetInetAddresses(ifName) + } + } + + def getInterfaceAddresses(): ju.List[InterfaceAddress] = { + if (Platform.isWindows()) { // No Windows support + ju.Collections.emptyList[InterfaceAddress]() + } else { + NetworkInterface.unixImplGetInterfaceAddresses(ifName) + } + } + + def getMTU(): Int = { + if (Platform.isWindows()) 0 // No Windows support + else { + NetworkInterface.unixImplGetIfMTU(ifName) + } + } + + def getName(): String = ifName + + def getParent(): NetworkInterface = { + if (Platform.isWindows()) null // No Windows support + else if (!this.isVirtual()) null + else { + val parentName = ifName.split(":")(0) + NetworkInterface.getByName(parentName) + } + } + + def getSubInterfaces(): ju.Enumeration[NetworkInterface] = { + val ifList = new ju.ArrayList[NetworkInterface]() + + // No Windows support, so empty Enumeration will be returned. + if (!Platform.isWindows()) { + val allIfs = NetworkInterface.getNetworkInterfaces() + val matchMe = s"${ifName}:" + while (allIfs.hasMoreElements()) { + val elem = allIfs.nextElement() + val elemName = elem.getName() + if (elemName.startsWith(matchMe)) + ifList.add(elem) + } + } + ju.Collections.enumeration[NetworkInterface](ifList) + } + + def inetAddresses(): Stream[InetAddress] = { + if (Platform.isWindows()) Stream.empty[InetAddress]() // No Windows support + else { + NetworkInterface.unixImplInetAddresses(ifName) + } + } + + def isLoopback(): Boolean = { + if (Platform.isWindows()) false // No Windows support + else { + val ifFlags = NetworkInterface.unixImplGetIfFlags(ifName) + (ifFlags & unixIf.IFF_LOOPBACK) == unixIf.IFF_LOOPBACK + } + } + + def isPointToPoint(): Boolean = { + if (Platform.isWindows()) false // No Windows support + else { + val ifFlags = NetworkInterface.unixImplGetIfFlags(ifName) + (ifFlags & unixIf.IFF_POINTOPOINT) == unixIf.IFF_POINTOPOINT + } + } + + def isUp(): Boolean = { + if (Platform.isWindows()) false // No Windows support + else { + val ifFlags = NetworkInterface.unixImplGetIfFlags(ifName) + (ifFlags & unixIf.IFF_UP) == unixIf.IFF_UP + } + } + + // relies upon convention that Virtual or sub-interfaces have colon in name. + def isVirtual(): Boolean = ifName.indexOf(':') >= 0 // a best guess + + override def hashCode(): Int = ifName.hashCode() + + def subInterfaces(): Stream[NetworkInterface] = { + val allIfs = NetworkInterface.networkInterfaces() + val matchMe = s"${ifName}:" + allIfs.filter(_.getName().startsWith(matchMe)) + } + + def supportsMulticast(): Boolean = { + if (Platform.isWindows()) false // No Windows support + else { + val ifFlags = NetworkInterface.unixImplGetIfFlags(ifName) + (ifFlags & unixIf.IFF_MULTICAST) == unixIf.IFF_MULTICAST + } + } + + override def toString(): String = s"name:${ifName} (${ifName})" + +} + +object NetworkInterface { + import unixIfaddrs._ + import unixIfaddrsOps._ + + def getByIndex(index: Int): NetworkInterface = { + if (index < 0) + throw new IllegalArgumentException("Interface index can't be negative") + + if (Platform.isWindows()) { + null + } else { + unixGetByIndex(index) + } + } + + def getByInetAddress(addr: InetAddress): NetworkInterface = { + Objects.requireNonNull(addr) + if (Platform.isWindows()) { + null + } else { + unixGetByInetAddress(addr) + } + } + + def getByName(name: String): NetworkInterface = { + Objects.requireNonNull(name) + if (Platform.isWindows()) { + null + } else { + unixGetByName(name) + } + } + + def getNetworkInterfaces(): ju.Enumeration[NetworkInterface] = { + if (Platform.isWindows()) { + null + } else { + unixGetNetworkInterfaces() + } + } + + /** networkInterfaces() method is Java 9. It is provided because Streams are + * less clumsy than Enumerations. + */ + def networkInterfaces(): Stream[NetworkInterface] = { + if (Platform.isWindows()) { + null + } else { + unixNetworkInterfaces() + } + } + + private def createInetAddress( + ifa: Ptr[ifaddrs], + ifName: String + ): Option[InetAddress] = { + val sa = ifa.ifa_addr + val af = sa.sa_family.toInt + + if (!((af == AF_INET) || (af == AF_INET6))) None + else { + val bytes = sockaddrToByteArray(sa) + if (af == AF_INET) { + Some(InetAddress.getByAddress(bytes)) + } else { + val scopeId = sa.asInstanceOf[Ptr[sockaddr_in6]].sin6_scope_id.toInt + Some(Inet6Address(bytes, "", scopeId, ifName)) + } + } + } + + private def createInterfaceAddress( + ifa: Ptr[ifaddrs], + interfaceName: String + ): Option[InterfaceAddress] = { + + def decodePrefixLength(sa: Ptr[sockaddr]): Short = { + val result = + if (sa.sa_family.toInt == AF_INET) { + val sin4 = sa.asInstanceOf[Ptr[sockaddr_in]] + val mask = sin4.sin_addr.s_addr.toInt + Integer.bitCount(mask) + } else if (sa.sa_family.toInt == AF_INET6) { + val sin6 = sa.asInstanceOf[Ptr[sockaddr_in6]] + val longs = + sin6.sin6_addr.at1.at(0).asInstanceOf[Ptr[scala.Long]] + java.lang.Long.bitCount(longs(0)) + java.lang.Long.bitCount(longs(1)) + } else { + 0 // Blivet! Unknown address family, assume zero length prefix. + } + result.toShort + } + + val sa = ifa.ifa_addr + val af = sa.sa_family.toInt + if (!((af == AF_INET) || (af == AF_INET6))) { + None // Silently skip AF_PACKET (17) and such. + } else { + val bytes = sockaddrToByteArray(sa) + val inetAddress = if (af == AF_INET) { + InetAddress.getByAddress(bytes) + } else { + val scopeId = sa.asInstanceOf[Ptr[sockaddr_in6]].sin6_scope_id + Inet6Address(bytes, "", scopeId.toInt, interfaceName) + } + + val broadcastAddress: Option[Array[Byte]] = + if (sa.sa_family.toInt == AF_INET6) None + else if ((ifa.ifa_flags & unixIf.IFF_LOOPBACK.toUInt) != 0.toUInt) None + else Some(sockaddrToByteArray(ifa.ifa_broadaddr)) + + val prefixLen = decodePrefixLength(ifa.ifa_netmask) + + val ifAddress = + new InterfaceAddress(inetAddress, broadcastAddress, prefixLen) + + Some(ifAddress) + } + } + + private def createNetworkInterface(ifa: Ptr[ifaddrs]): NetworkInterface = { + val ifName = fromCString(ifa.ifa_name) + new NetworkInterface(ifName) + } + + private def unixGetByIndex(index: Int): NetworkInterface = { + val buf = stackalloc[Byte](IF_NAMESIZE.toUInt) + + val ret = if_indextoname(index.toUInt, buf) + + if (ret != null) unixGetByName(fromCString(ret)) + else if (errno == ENXIO) null // no interface has that index + else + throw new SocketException(fromCString(strerror(errno))) + } + + private def unixGetByInetAddress(addr: InetAddress): NetworkInterface = { + + def found(addr: Array[Byte], addrLen: Int, sa: Ptr[sockaddr]): Boolean = { + val sa_family = sa.sa_family.toInt + if (sa_family == AF_INET6) { + if (addrLen != 16) false + else { + val sa6 = sa.asInstanceOf[Ptr[sockaddr_in6]] + val sin6Addr = sa6.sin6_addr.at1.at(0).asInstanceOf[Ptr[Byte]] + memcmp(addr.at(0), sin6Addr, addrLen.toUInt) == 0 + } + } else if (sa_family == AF_INET) { + val sa4 = sa.asInstanceOf[Ptr[sockaddr_in]] + val sin4Addr = sa4.sin_addr.at1.asInstanceOf[Ptr[Byte]] + memcmp(addr.at(0), sin4Addr, addrLen.toUInt) == 0 + } else false + } + + @tailrec + def findIfInetAddress( + ipAddress: Array[Byte], + addrLen: Int, + ifa: Ptr[ifaddrs] + ): NetworkInterface = { + if (ifa == null) null + else if (found(ipAddress, addrLen, ifa.ifa_addr)) + createNetworkInterface(ifa) + else + findIfInetAddress( + ipAddress, + addrLen, + ifa.ifa_next.asInstanceOf[Ptr[ifaddrs]] + ) + } + + val addrBytes = addr.getAddress() + val len = addrBytes.length // check this once, not N times + + if (!((len == 4) || (len == 16))) + throw new SocketException( + s"unixGetByInetAddress: wrong Array[Byte] length: ${len}" + ) + else { + val ifap = stackalloc[Ptr[ifaddrs]]() + + val gifStatus = getifaddrs(ifap) + if (gifStatus == -1) + throw new SocketException( + s"getifaddrs failed: ${fromCString(strerror(errno))}" + ) + + val result = + try { + findIfInetAddress(addrBytes, len, !ifap) + } finally { + freeifaddrs(!ifap) + } + + result + } + } + + private def unixGetByName(name: String): NetworkInterface = Zone { + implicit z => + @tailrec + def findIfName( + cName: CString, + ifa: Ptr[ifaddrs] + ): NetworkInterface = { + if (ifa == null) null + else if (strcmp(ifa.ifa_name, cName) == 0) + createNetworkInterface(ifa) + else findIfName(cName, ifa.ifa_next.asInstanceOf[Ptr[ifaddrs]]) + } + + val cName = toCString(name) + val ifap = stackalloc[Ptr[ifaddrs]]() + + val gifStatus = getifaddrs(ifap) + if (gifStatus == -1) + throw new SocketException( + s"getifaddrs failed: ${fromCString(strerror(errno))}" + ) + + val result = + try { + findIfName(cName, !ifap) + } finally { + freeifaddrs(!ifap) + } + + result + } + + private def unixAccumulateNetworkInterfaces( + accumulator: (NetworkInterface) => Unit + ): Unit = { + + @tailrec + def accumulateNetIfs( + ni: Ptr[if_nameindex], + addOne: (NetworkInterface) => Unit + ): Unit = { + if ((ni.if_index.toInt != 0) || (ni.if_name != null)) { + val ifName = + if (ni.if_name == null) "" + else fromCString(ni.if_name) + + addOne(new NetworkInterface(ifName)) + + accumulateNetIfs( + ni + 1, // + 1 should skip entire structure + accumulator + ) + } + } + + val nameIndex = if_nameindex() + + if (nameIndex == null) + throw new SocketException( + s"if_nameindex() failed: ${fromCString(strerror(errno))}" + ) + + try { + accumulateNetIfs(nameIndex, accumulator) + } finally { + if_freenameindex(nameIndex) + } + } + + private def unixGetNetworkInterfaces(): ju.Enumeration[NetworkInterface] = { + val ifList = new ju.ArrayList[NetworkInterface]() + unixAccumulateNetworkInterfaces((netIf: NetworkInterface) => { + ifList.add(netIf); () + }) + ju.Collections.enumeration[NetworkInterface](ifList) + } + + private def unixNetworkInterfaces(): Stream[NetworkInterface] = { + val builder = Stream.builder[NetworkInterface]() + unixAccumulateNetworkInterfaces((netIf: NetworkInterface) => { + builder.add(netIf); () + }) + builder.build() + } + + /* Implement OS specific class & helper methods + */ + + private def linuxImplGetIoctlFd(): Int = { + val fd = socket(AF_INET, SOCK_DGRAM, 0) + + if (fd == -1) { + val msg = fromCString(strerror(errno)) + throw new SocketException(s"socket(AF_INET, SOCK_DGRAM) failed: ${msg}\n") + } + + fd + } + + private def macOsImplExecCallback( + ifName: String, + callback: Ptr[ifaddrs] => Tuple2[Int, Array[Byte]] + ): Tuple2[Int, Array[Byte]] = { + @tailrec + def findAfLinkIfName( + ifNameC: CString, + ifa: Ptr[ifaddrs] + ): Ptr[ifaddrs] = { + if (ifa == null) null + else if ((strcmp(ifNameC, ifa.ifa_name) == 0) + && (ifa.ifa_addr.sa_family.toInt == 18 /* AF_LINK */ )) + ifa + else + findAfLinkIfName(ifNameC, ifa.ifa_next) + } + + val ifap = stackalloc[Ptr[ifaddrs]]() + + val gifStatus = getifaddrs(ifap) + if (gifStatus == -1) + throw new SocketException( + s"getifaddrs failed: ${fromCString(strerror(errno))}" + ) + + try + Zone { implicit z => + val foundIfa = findAfLinkIfName(toCString(ifName), !ifap) + callback(foundIfa) + } + finally { + freeifaddrs(!ifap) + } + } + + private def unixImplGetIndex(ifName: String): Int = Zone { implicit z => + // toInt truncation OK, since index will never be larger than MAX_INT + if_nametoindex(toCString(ifName)).toInt + // Return 0 on error. Do not give errno error message. + } + + private def unixImplGetHardwareAddress(ifName: String): Array[Byte] = { + if (LinktimeInfo.isLinux) + linuxImplGetHardwareAddress(ifName) + else + macOsImplGetHardwareAddress(ifName) + } + + private def macOsImplGetHardwareAddress(ifName: String): Array[Byte] = { + def decodeSocketDl(sockaddrDl: Ptr[macOsIfDl.sockaddr_dl]): Array[Byte] = { + + val nBytes = if (sockaddrDl == null) 0 else sockaddrDl.sdl_alen.toInt + val bytes = new Array[Byte](nBytes) + + if (nBytes > 0) { // skip name + val src = sockaddrDl.sdl_data.at(sockaddrDl.sdl_nlen.toInt) + val dst = bytes.at(0) + memcpy(dst, src, nBytes.toUInt) + } + bytes + } + + def cb(ifa: Ptr[ifaddrs]): Tuple2[Int, Array[Byte]] = { + val arr = + if (ifa == null) new Array[Byte](0) + else + decodeSocketDl(ifa.ifa_addr.asInstanceOf[Ptr[sockaddr_dl]]) + + (0, arr) + } + + macOsImplExecCallback(ifName, cb)._2 + } + + private def linuxImplGetHardwareAddress(ifName: String): Array[Byte] = Zone { + implicit z => + // acknowledge: + // https://www.geekpage.jp/en/programming/linux-network/get-macaddr.php + + val request = stackalloc[unixIf.ifreq_hwaddress]() + + strncpy( + request.at1.asInstanceOf[CString], + toCString(ifName), + (unixIf.IFNAMSIZ - 1).toUInt + ) + + val saP = request.at2.asInstanceOf[Ptr[sockaddr]] + saP.sa_family = AF_INET.toUShort + + val fd = linuxImplGetIoctlFd() + + try { + val status = + ioctl(fd, unixIf.SIOCGIFHWADDR, request.asInstanceOf[Ptr[Byte]]); + if (status != 0) { + val msg = fromCString(strerror(errno)) + throw new SocketException(s"ioctl SIOCGIFHWADDR failed: ${msg}\n") + } + } finally { + unistd.close(fd) + } + + val hwAddress = new Array[Byte](6) + val hwAddrBytes = request.at2.sa_data + + for (j <- 0 until 6) + hwAddress(j) = hwAddrBytes(j) + + hwAddress + } + + private def unixImplGetIfMTU(ifName: String): Int = { + if (LinktimeInfo.isLinux) + linuxImplGetIfMTU(ifName) + else + macOsImplGetIfMTU(ifName) + } + + private def macOsImplGetIfMTU(ifName: String): Int = { + def cb(ifa: Ptr[ifaddrs]): Tuple2[Int, Array[Byte]] = { + val result = + if (ifa == null) 0 + else + ifa.ifa_data.asInstanceOf[Ptr[macOsIf.if_data]].ifi_mtu.toInt + + (result, null) + } + + macOsImplExecCallback(ifName, cb)._1 + } + + private def linuxImplGetIfMTU(ifName: String): Int = Zone { implicit z => + val request = stackalloc[unixIf.ifreq_mtu]() + + strncpy( + request.at1.asInstanceOf[CString], + toCString(ifName), + (unixIf.IFNAMSIZ - 1).toUInt + ) + + val saP = request.at2.asInstanceOf[Ptr[sockaddr]] + saP.sa_family = AF_INET.toUShort + + val fd = linuxImplGetIoctlFd() + + try { + val status = + ioctl(fd, unixIf.SIOCGIFMTU, request.asInstanceOf[Ptr[Byte]]); + if (status != 0) + throw new SocketException( + s"ioctl SIOCGIFMTU failed: ${fromCString(strerror(errno))}" + ) + + } finally { + unistd.close(fd) + } + + request._2 // ifr_mtu + } + + private def unixImplGetIfFlags(ifName: String): Short = { + if (LinktimeInfo.isLinux) + linuxImplGetIfFlags(ifName) + else + macOsImplGetIfFlags(ifName) + } + + private def macOsImplGetIfFlags(ifName: String): Short = { + def cb(ifa: Ptr[ifaddrs]): Tuple2[Int, Array[Byte]] = { + val result = + if (ifa == null) 0 + else ifa.ifa_flags.toInt + + (result, null) + } + + macOsImplExecCallback(ifName, cb)._1.toShort + } + + private def linuxImplGetIfFlags(ifName: String): Short = Zone { implicit z => + val request = stackalloc[unixIf.ifreq_flags]() + + strncpy( + request.at1.asInstanceOf[CString], + toCString(ifName), + (unixIf.IFNAMSIZ - 1).toUInt + ) + + val saP = request.at2.asInstanceOf[Ptr[sockaddr]] + saP.sa_family = AF_INET.toUShort + + val fd = linuxImplGetIoctlFd() + + try { + val status = + ioctl(fd, unixIf.SIOCGIFFLAGS, request.asInstanceOf[Ptr[Byte]]); + + if (status != 0) { + val msg = fromCString(strerror(errno)) + throw new SocketException(s"ioctl SIOCGIFFLAGS failed: ${msg}\n") + } + } finally { + unistd.close(fd) + } + + request._2 // ifr_flags + } + + private def unixAccumulateInetAddresses( + ifNameJ: String, + accumulator: (InetAddress) => Unit + ): Unit = Zone { implicit z => + @tailrec + def accumulateInetAddresses( + ifNameC: CString, + addOne: (InetAddress) => Unit, + ifa: Ptr[ifaddrs] + ): Unit = { + if (ifa != null) { + if (strcmp(ifNameC, ifa.ifa_name) == 0) { + createInetAddress(ifa, ifNameJ).map(ia => addOne(ia)) + } + accumulateInetAddresses( + ifNameC, + addOne, + ifa.ifa_next.asInstanceOf[Ptr[ifaddrs]] + ) + } + } + + val ifap = stackalloc[Ptr[ifaddrs]]() + + val gifStatus = getifaddrs(ifap) + + if (gifStatus == -1) + throw new SocketException( + s"getifaddrs failed: ${fromCString(strerror(errno))}" + ) + + try { + accumulateInetAddresses(toCString(ifNameJ), accumulator, !ifap) + } finally { + freeifaddrs(!ifap) + } + } + + private def unixAccumulateInterfaceAddresses( + ifName: String, + accumulator: (InterfaceAddress) => Unit + ): Unit = Zone { implicit z => + @tailrec + def accumulateInterfaceAddresses( + ifNameJ: String, + ifNameC: CString, + addOne: (InterfaceAddress) => Unit, + ifa: Ptr[ifaddrs] + ): Unit = { + if (ifa != null) { + if (strcmp(ifNameC, ifa.ifa_name) == 0) { + createInterfaceAddress(ifa, ifNameJ).map(ia => addOne(ia)) + } + accumulateInterfaceAddresses( + ifNameJ, + ifNameC, + addOne, + ifa.ifa_next.asInstanceOf[Ptr[ifaddrs]] + ) + } + } + + val ifap = stackalloc[Ptr[ifaddrs]]() + + val gifStatus = getifaddrs(ifap) + + if (gifStatus == -1) + throw new SocketException( + s"getifaddrs failed: ${fromCString(strerror(errno))}" + ) + + try { + accumulateInterfaceAddresses( + ifName, + toCString(ifName), + accumulator, + !ifap + ) + } finally { + freeifaddrs(!ifap) + } + } + + private def unixImplGetInterfaceAddresses( + ifName: String + ): ju.List[InterfaceAddress] = { + val ifaList = new ju.ArrayList[InterfaceAddress]() + unixAccumulateInterfaceAddresses( + ifName, + (ifa: InterfaceAddress) => { ifaList.add(ifa); () } + ) + ifaList + } + + private def unixImplGetInetAddresses( + ifName: String + ): ju.Enumeration[InetAddress] = { + val ifList = new ju.ArrayList[InetAddress]() + unixAccumulateInetAddresses( + ifName, + (ia: InetAddress) => { ifList.add(ia); () } + ) + ju.Collections.enumeration[InetAddress](ifList) + } + + private def unixImplInetAddresses(ifName: String): Stream[InetAddress] = { + val builder = Stream.builder[InetAddress]() + unixAccumulateInetAddresses( + ifName, + (ia: InetAddress) => { builder.add(ia); () } + ) + builder.build() + } + +} + +@extern +private object unixIfaddrs { + /* Reference: man getifaddrs + * #include + */ + + // format: off + type ifaddrs = CStruct7[ + Ptr[Byte], /* Ptr[ifaddrs] */ // ifa_next: Next item in list + CString, // ifa_name: Name of interface + CUnsignedInt, // ifa_flags: Flags from SIOCGIFFLAGS + Ptr[sockaddr], // ifa_addr: Address of interface + Ptr[sockaddr], // ifa_netmask: Netmask of interface + // ifu_broadaddr: Broadcast address of interface + // ifu_dstaddr: Point-to-point destination address + Ptr[sockaddr], // union: ifu_broadaddr, ifu_dstaddr + Ptr[Byte] // ifa_data: Address-specific data + ] + // format: on + + def getifaddrs(ifap: Ptr[Ptr[ifaddrs]]): CInt = extern + + def freeifaddrs(ifa: Ptr[ifaddrs]): Unit = extern +} + +private object unixIfaddrsOps { + import unixIfaddrs._ + + implicit class unixIfaddrOps(val ptr: Ptr[ifaddrs]) extends AnyVal { + def ifa_next: Ptr[ifaddrs] = ptr._1.asInstanceOf[Ptr[ifaddrs]] + def ifa_name: CString = ptr._2 + def ifa_flags: CUnsignedInt = ptr._3 + def ifa_addr: Ptr[sockaddr] = ptr._4 + def ifa_netmask: Ptr[sockaddr] = ptr._5 + def ifa_broadaddr: Ptr[sockaddr] = ptr._6 + def ifa_dstaddr: Ptr[sockaddr] = ptr._6 + def ifa_data: Ptr[Byte] = ptr._7 + + // ifa fields are read-only in use, so no Ops here to set them. + } +} + +@extern +private object unixIf { + /* Reference: man 7 netdevice + * #include + */ + + // Three SN-only types used to facilitate retrieving specific types of data. + type ifreq_hwaddress = CStruct2[ + CArray[CChar, Nat.Digit2[Nat._1, Nat._6]], + sockaddr + ] + + type ifreq_mtu = CStruct2[ + CArray[CChar, Nat.Digit2[Nat._1, Nat._6]], + CInt + ] + + type ifreq_flags = CStruct2[ + CArray[CChar, Nat.Digit2[Nat._1, Nat._6]], + CShort + ] + + @name("scalanative_ifnamesiz") + def IFNAMSIZ: CInt = extern + + @name("scalanative_iff_broadcast") + def IFF_BROADCAST: CInt = extern + + @name("scalanative_iff_loopback") + def IFF_LOOPBACK: CInt = extern + + @name("scalanative_iff_multicast") + def IFF_MULTICAST: CInt = extern + + @name("scalanative_iff_pointopoint") + def IFF_POINTOPOINT: CInt = extern + + @name("scalanative_iff_running") + def IFF_RUNNING: CInt = extern + + @name("scalanative_siocgifflags") + def SIOCGIFFLAGS: CInt = extern + + @name("scalanative_siocgifhwaddr") + def SIOCGIFHWADDR: CInt = extern + + @name("scalanative_siocgifmtu") + def SIOCGIFMTU: CInt = extern + + @name("scalanative_iff_up") + def IFF_UP: CInt = extern +} + +private object macOsIf { + + /* Scala if_data & corresponding ifDataOps definitions are not complete. + * Only items used in NetworkInterface are declared. + */ + + /* Reference: macOS + * /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include + * /net/if_var.h + * + * struct if_data { + * // generic interface information + * u_char ifi_type; // ethernet, tokenring, etc + * u_char ifi_typelen; // Length of frame type id + * u_char ifi_physical; // e.g., AUI, Thinnet, 10base-T, etc + * u_char ifi_addrlen; // media address length + * u_char ifi_hdrlen; // media header length + * u_char ifi_recvquota; // polling quota for receive intrs + * u_char ifi_xmitquota; // polling quota for xmit intrs + * u_char ifi_unused1; // for future use + * u_int32_t ifi_mtu; // maximum transmission unit + */ + + // Incomplete + type if_data = CStruct2[ + CLongLong, // Placeholder, consolidate & skip fields of no interest. + CUnsignedInt // ifi_mtu + ] + + // Incomplete, corresponding to incomplete if_data just above. + implicit class ifDataOps(val ptr: Ptr[if_data]) extends AnyVal { + def ifi_mtu: CUnsignedInt = ptr._2 + } + // ifi fields read-only fields in use, so no Ops here to set them. +} + +private object macOsIfDl { + /* Scala sockaddr_dl & corresponding sockaddrDlOps definitions are not + * complete. They are only what NetworkInterface uses. + */ + + /* Reference: FreeBSD man sockaddr_dl + * #include + * + * For sdl_data field, use the larger of macOS defined 12 and + * FreeBSD defined 46. + * + * struct sockaddr_dl + * The sockaddr_dl structure is used to describe a layer 2 link-level + * address. The structure has the following members: + * + * ushort_t sdl_family; // address family + * ushort_t sdl_index; // if != 0, system interface index + * uchar_t sdl_type; // interface type + * uchar_t sdl_nlen; // interface name length + * uchar_t sdl_alen; // link level address length + * uchar_t sdl_slen; // link layer selector length + * char sdl_data[46]; // contains both if name and ll address + */ + + // sdl_data, max(macOs == 12, FreeBsd == 46) + type _46 = Nat.Digit2[Nat._4, Nat._6] + type sdl_data_t = CArray[CChar, _46] + + type sockaddr_dl = CStruct8[ + Byte, // sdl_len; // Total length of sockaddr + Byte, // sdl_family; // address family + CShort, // sdl_index + Byte, // sdl_type + Byte, // sdl_nlen + Byte, // sdl_alen + Byte, // sdl_slen + sdl_data_t + ] + + implicit class sockaddrDlOps(val ptr: Ptr[sockaddr_dl]) extends AnyVal { + def sdl_len: UByte = ptr._1.toUByte + def sdl_family: UByte = ptr._2.toUByte + def sdl_index: UShort = ptr._3.toUShort + def sdl_type: UByte = ptr._4.toUByte + def sdl_nlen: UByte = ptr._5.toUByte + def sdl_alen: UByte = ptr._6.toUByte + def sdl_slen: UByte = ptr._7.toUByte + def sdl_data: sdl_data_t = ptr._8 + } +} diff --git a/javalib/src/main/scala/java/net/SocketHelpers.scala b/javalib/src/main/scala/java/net/SocketHelpers.scala index 9caa1a7c8c..30ebab2216 100644 --- a/javalib/src/main/scala/java/net/SocketHelpers.scala +++ b/javalib/src/main/scala/java/net/SocketHelpers.scala @@ -5,8 +5,10 @@ import scala.scalanative.unsafe._ import scala.scalanative.posix.{netdb, netdbOps}, netdb._, netdbOps._ import scala.scalanative.posix.netinet.in +import scala.scalanative.posix.netinet.inOps._ import scala.scalanative.posix.sys.socket._ import scala.scalanative.posix.sys.socketOps._ +import scala.scalanative.posix.string.memcpy import scala.scalanative.meta.LinktimeInfo.isWindows @@ -130,6 +132,31 @@ object SocketHelpers { } } + private[net] def sockaddrToByteArray(sockAddr: Ptr[sockaddr]): Array[Byte] = { + + val (src, byteArraySize) = { + val af = sockAddr.sa_family.toInt + if (af == AF_INET6) { + val v6addr = sockAddr.asInstanceOf[Ptr[in.sockaddr_in6]] + val sin6Addr = v6addr.sin6_addr.at1.asInstanceOf[Ptr[Byte]] + val arraySize = 16 + (sin6Addr, arraySize) + } else if (af == AF_INET) { + val v4addr = sockAddr.asInstanceOf[Ptr[in.sockaddr_in]] + val sin4Addr = v4addr.sin_addr.at1.asInstanceOf[Ptr[Byte]] + val arraySize = 4 + (sin4Addr, arraySize) + } else { + throw new SocketException(s"Unsupported address family: ${af}") + } + } + + val byteArray = new Array[Byte](byteArraySize) + memcpy(byteArray.at(0), src, byteArraySize.toUInt) + + byteArray + } + // Create copies of loopback & wildcard, so that originals never get changed // ScalaJVM shows loopbacks with null host, wildcards with numeric host. diff --git a/javalib/src/main/scala/java/nio/MappedByteBufferImpl.scala b/javalib/src/main/scala/java/nio/MappedByteBufferImpl.scala index fce1dd5839..08f326fed7 100644 --- a/javalib/src/main/scala/java/nio/MappedByteBufferImpl.scala +++ b/javalib/src/main/scala/java/nio/MappedByteBufferImpl.scala @@ -298,12 +298,19 @@ private[nio] object MappedByteBufferImpl { fd: FileDescriptor, mode: MapMode ): MappedByteBufferData = { + + /* FreeBSD requires that PROT_READ be explicit with MAP_SHARED. + * Linux, macOS, & FreeBSD MAP_PRIVATE allow PROT_WRITE to imply + * PROT_READ. Make PROT_READ explicit in all these cases to document + * the intention. + */ val (prot: Int, isPrivate: Int) = mode match { - case MapMode.PRIVATE => (PROT_WRITE, MAP_PRIVATE) + case MapMode.PRIVATE => (PROT_READ | PROT_WRITE, MAP_PRIVATE) case MapMode.READ_ONLY => (PROT_READ, MAP_SHARED) - case MapMode.READ_WRITE => (PROT_WRITE, MAP_SHARED) + case MapMode.READ_WRITE => (PROT_READ | PROT_WRITE, MAP_SHARED) case _ => throw new IllegalStateException("Unknown MapMode") } + val ptr = mmap( null, size.toUInt, diff --git a/javalib/src/main/scala/java/nio/file/Files.scala b/javalib/src/main/scala/java/nio/file/Files.scala index 4baae0e71a..0cd8101c4b 100644 --- a/javalib/src/main/scala/java/nio/file/Files.scala +++ b/javalib/src/main/scala/java/nio/file/Files.scala @@ -11,8 +11,10 @@ import java.io.{ InputStreamReader, OutputStream, OutputStreamWriter, + StringWriter, UncheckedIOException } + import java.nio.file.attribute._ import java.nio.charset.{Charset, StandardCharsets} import java.nio.channels.{FileChannel, SeekableByteChannel} @@ -28,6 +30,9 @@ import java.util.{ Set } import java.util.stream.{Stream, WrappedScalaStream} + +import scala.annotation.tailrec + import scalanative.unsigned._ import scalanative.unsafe._ import scalanative.libc._ @@ -54,7 +59,9 @@ import java.io.FileNotFoundException object Files { - private val `1U` = 1.toUInt + private final val `1U` = 1.toUInt + + private final val emptyPath = Paths.get("", Array.empty) // def getFileStore(path: Path): FileStore // def probeContentType(path: Path): String @@ -192,15 +199,14 @@ object Files { throw new IOException() } - def createFile(path: Path, attrs: Array[FileAttribute[_]]): Path = + def createFile(path: Path, attrs: Array[FileAttribute[_]]): Path = { if (exists(path, Array.empty)) throw new FileAlreadyExistsException(path.toString) - else if (FileHelpers.createNewFile(path.toString)) { + else if (FileHelpers.createNewFile(path.toString, throwOnError = true)) { setAttributes(path, attrs) - path - } else { - throw new IOException() } + path + } def createLink(link: Path, existing: Path): Path = Zone { implicit z => if (isWindows) { @@ -528,11 +534,25 @@ object Files { def lines(path: Path, cs: Charset): Stream[String] = newBufferedReader(path, cs).lines(true) - def list(dir: Path): Stream[Path] = + def list(dir: Path): Stream[Path] = { + /* Fix Issue 3165 - From Java "Path" documentation URL: + * https://docs.oracle.com/javase/8/docs/api/java/nio/file/Path.html + * + * "Accessing a file using an empty path is equivalent to accessing the + * default directory of the file system." + * + * Operating Systems can not opendir() an empty string, so expand "" to + * "./". + */ + val dirString = + if (dir.equals(emptyPath)) "./" + else dir.toString() + new WrappedScalaStream( - FileHelpers.list(dir.toString, (n, _) => dir.resolve(n)).toScalaStream, + FileHelpers.list(dirString, (n, _) => dir.resolve(n)).toScalaStream, None ) + } def move(source: Path, target: Path, options: Array[CopyOption]): Path = { lazy val replaceExisting = options.contains(REPLACE_EXISTING) @@ -647,7 +667,13 @@ object Files { val filter = new DirectoryStream.Filter[Path] { private val matcher = FileSystems.getDefault().getPathMatcher("glob:" + glob) - override def accept(p: Path): Boolean = matcher.matches(p) + + /* Fix Issue 2937 - Java considers "" & "./" to be the same: current + * default directory. To ease comparison here and follow JDK practice, + * change "./" to "" on candidate path. See related "" to "./ " + * comment in "def list()" above. + */ + override def accept(p: Path): Boolean = matcher.matches(p.normalize()) } newDirectoryStream(dir, filter) } @@ -773,6 +799,24 @@ object Files { } } + // Since: Java 11 + def readString(path: Path): String = { + readString(path, StandardCharsets.UTF_8) + } + + // Since: Java 11 + def readString(path: Path, cs: Charset): String = { + val reader = newBufferedReader(path, cs) + try { + // Guess an cost-effective amortized size. + val writer = new StringWriter(2 * 1024) + reader.transferTo(writer) + writer.toString() + // No need to close() StringWriter, so no inner try/finally. + } finally + reader.close() + } + def readSymbolicLink(link: Path): Path = if (!isSymbolicLink(link)) { throw new NotLinkException(link.toString) @@ -1096,4 +1140,61 @@ object Files { "posix" -> classOf[PosixFileAttributeView] ) + // Since: Java 11 + def writeString( + path: Path, + csq: java.lang.CharSequence, + cs: Charset, + options: Array[OpenOption] + ): Path = { + import java.io.Reader + + // Java API has no CharSequenceReader, but the concept is useful here. + class CharSequenceReader(csq: CharSequence) extends Reader { + private var closed = false + private var pos = 0 + + override def close(): Unit = closed = true + + override def read(cbuf: Array[Char], off: Int, len: Int): Int = { + if (closed) + throw new IOException("Operation on closed stream") + + if (off < 0 || len < 0 || len > cbuf.length - off) + throw new IndexOutOfBoundsException + + if (len == 0) 0 + else { + val count = Math.min(len, csq.length() - pos) + var i = 0 + while (i < count) { + cbuf(off + i) = csq.charAt(pos + i) + i += 1 + } + pos += count + if (count == 0) -1 else count + } + } + } + + val reader = new CharSequenceReader(csq) + val writer = newBufferedWriter(path, cs, options) + try { + reader.transferTo(writer) + // No need to close() CharSequenceReader, so no inner try/finally. + } finally + writer.close() + + path + } + + // Since: Java 11 + def writeString( + path: Path, + csq: java.lang.CharSequence, + options: Array[OpenOption] + ): Path = { + writeString(path, csq, StandardCharsets.UTF_8, options) + } + } diff --git a/javalib/src/main/scala/java/util/ArrayDeque.scala b/javalib/src/main/scala/java/util/ArrayDeque.scala index 00d5990856..74cf95f7b2 100644 --- a/javalib/src/main/scala/java/util/ArrayDeque.scala +++ b/javalib/src/main/scala/java/util/ArrayDeque.scala @@ -851,7 +851,7 @@ class ArrayDeque[E]( * a {@code Spliterator} over the elements in this deque * @since 1.8 */ - def spliterator(): Spliterator[E] = { + override def spliterator(): Spliterator[E] = { return new DeqSpliterator() } diff --git a/javalib/src/main/scala/java/util/ArrayList.scala b/javalib/src/main/scala/java/util/ArrayList.scala index fbf42e2b40..eebecd57b4 100644 --- a/javalib/src/main/scala/java/util/ArrayList.scala +++ b/javalib/src/main/scala/java/util/ArrayList.scala @@ -178,11 +178,4 @@ class ArrayList[E] private ( } _size = 0 } - - // TODO: JDK 1.8 - // def forEach(action: Consumer[_ >: E]): Unit = - // def spliterator(): Spliterator[E] = - // def removeIf(filter: Predicate[_ >: E]): Boolean = - // def replaceAll(operator: UnaryOperator[E]): Unit = - // def sort(c: Comparator[_ >: E]): Unit = } diff --git a/javalib/src/main/scala/java/util/Arrays.scala b/javalib/src/main/scala/java/util/Arrays.scala index c7f7e03654..2ed8c6f6c9 100644 --- a/javalib/src/main/scala/java/util/Arrays.scala +++ b/javalib/src/main/scala/java/util/Arrays.scala @@ -1,4 +1,5 @@ // Ported from Scala.js commit: ba618ed dated: 2020-10-05 +// Arrays.spliterator() methods added for Scala Native. package java.util @@ -997,4 +998,109 @@ object Arrays { } } } + +// Scala Native additions -------------------------------------------------- + import java.util.{Spliterator, Spliterators} + + private final val standardArraySpliteratorCharacteristics = + Spliterator.SIZED | + Spliterator.SUBSIZED | + Spliterator.ORDERED | + Spliterator.IMMUTABLE + + def spliterator(array: Array[Double]): Spliterator.OfDouble = { + Objects.requireNonNull(array) + Spliterators.spliterator( + array, + 0, + array.size, + standardArraySpliteratorCharacteristics + ) + } + + def spliterator( + array: Array[Double], + startInclusive: Int, + endExclusive: Int + ): Spliterator.OfDouble = { + Objects.requireNonNull(array) + Spliterators.spliterator( + array, + startInclusive, + endExclusive, + standardArraySpliteratorCharacteristics + ) + } + + def spliterator(array: Array[Int]): Spliterator.OfInt = { + Objects.requireNonNull(array) + Spliterators.spliterator( + array, + 0, + array.size, + standardArraySpliteratorCharacteristics + ) + } + + def spliterator( + array: Array[Int], + startInclusive: Int, + endExclusive: Int + ): Spliterator.OfInt = { + Objects.requireNonNull(array) + Spliterators.spliterator( + array, + startInclusive, + endExclusive, + standardArraySpliteratorCharacteristics + ) + } + + def spliterator(array: Array[Long]): Spliterator.OfLong = { + Objects.requireNonNull(array) + Spliterators.spliterator( + array, + 0, + array.size, + standardArraySpliteratorCharacteristics + ) + } + + def spliterator( + array: Array[Long], + startInclusive: Int, + endExclusive: Int + ): Spliterator.OfLong = { + Objects.requireNonNull(array) + Spliterators.spliterator( + array, + startInclusive, + endExclusive, + standardArraySpliteratorCharacteristics + ) + } + + def spliterator[T](array: Array[Object]): Spliterator[T] = { + Objects.requireNonNull(array) + Spliterators.spliterator( + array, + 0, + array.size, + standardArraySpliteratorCharacteristics + ) + } + + def spliterator[T]( + array: Array[Object], + startInclusive: Int, + endExclusive: Int + ): Spliterator[T] = { + Objects.requireNonNull(array) + Spliterators.spliterator( + array, + startInclusive, + endExclusive, + standardArraySpliteratorCharacteristics + ) + } } diff --git a/javalib/src/main/scala/java/util/Collection.scala b/javalib/src/main/scala/java/util/Collection.scala index c9bfff6382..25d9748698 100644 --- a/javalib/src/main/scala/java/util/Collection.scala +++ b/javalib/src/main/scala/java/util/Collection.scala @@ -1,7 +1,8 @@ // Ported from Scala.js commit: f122aa5 dated: 2019-07-03 - +// Additional Spliterator code implemented for Scala Native package java.util +import java.util.function.Consumer import java.util.function.Predicate trait Collection[E] extends java.lang.Iterable[E] { @@ -33,4 +34,12 @@ trait Collection[E] extends java.lang.Iterable[E] { def clear(): Unit def equals(o: Any): Boolean def hashCode(): Int + + /* From the Java documentation: + * "The default implementation should be overridden by subclasses that + * can return a more efficient spliterator." + */ + override def spliterator(): Spliterator[E] = { + Spliterators.spliterator[E](this, Spliterator.SIZED | Spliterator.SUBSIZED) + } } diff --git a/javalib/src/main/scala/java/util/PrimitiveIterator.scala b/javalib/src/main/scala/java/util/PrimitiveIterator.scala new file mode 100644 index 0000000000..b1aff34597 --- /dev/null +++ b/javalib/src/main/scala/java/util/PrimitiveIterator.scala @@ -0,0 +1,95 @@ +package java.util + +import java.util.function._ + +import Spliterator._ + +object PrimitiveIterator { + trait OfDouble extends PrimitiveIterator[Double, DoubleConsumer] { + override def forEachRemaining(action: Consumer[_ >: Double]): Unit = { + Objects.requireNonNull(action) + + if (action.isInstanceOf[DoubleConsumer]) { + forEachRemaining(action.asInstanceOf[DoubleConsumer]) + } else { + + while (hasNext()) + action.accept(next()) + } + } + + def forEachRemaining(action: DoubleConsumer): Unit = { + Objects.requireNonNull(action) + while (hasNext()) + action.accept(nextDouble()) + } + + /* BEWARE: The Java Doc says that the result from next() should + * be boxed, i.e. new java.lang.Double(nextDouble). + * The Scala Native implementation of Iterator demands + * that this be an unboxed, primitive. The boxed result + * conflicts with Iterator.next() declaration. + * + * Similar consideration exists for OfInt and OfLong. + */ + def next() = nextDouble() // return should be boxed primitive but is not. + + // Throws NoSuchElementException if iterator has no more elements + def nextDouble(): scala.Double // Abstract + } + + trait OfInt extends PrimitiveIterator[Int, IntConsumer] { + override def forEachRemaining(action: Consumer[_ >: Int]): Unit = { + Objects.requireNonNull(action) + + if (action.isInstanceOf[IntConsumer]) { + forEachRemaining(action.asInstanceOf[IntConsumer]) + } else { + + while (hasNext()) + action.accept(next()) + } + } + + def forEachRemaining(action: IntConsumer): Unit = { + Objects.requireNonNull(action) + while (hasNext()) + action.accept(nextInt()) + } + + // See BEWARE above for OfDouble.next() + def next() = nextInt() // return should be boxed primitive but is not. + + // Throws NoSuchElementException if iterator has no more elements + def nextInt(): Int // Abstract + } + + trait OfLong extends PrimitiveIterator[Long, LongConsumer] { + override def forEachRemaining(action: Consumer[_ >: Long]): Unit = { + Objects.requireNonNull(action) + if (action.isInstanceOf[LongConsumer]) { + forEachRemaining(action.asInstanceOf[LongConsumer]) + } else { + + while (hasNext()) + action.accept(next()) + } + } + + def forEachRemaining(action: LongConsumer): Unit = { + Objects.requireNonNull(action) + while (hasNext()) + action.accept(nextLong()) + } + + // See BEWARE above for OfDouble.next() + def next() = nextLong() // return should be boxed primitive but is not. + + // Throws NoSuchElementException if iterator has no more elements + def nextLong(): Long // Abstract + } +} + +trait PrimitiveIterator[T, T_CONS] extends Iterator[T] { + def forEachRemaining(action: T_CONS): Unit +} diff --git a/javalib/src/main/scala/java/util/Spliterator.scala b/javalib/src/main/scala/java/util/Spliterator.scala index 6e90a3dfd1..91bc6d9496 100644 --- a/javalib/src/main/scala/java/util/Spliterator.scala +++ b/javalib/src/main/scala/java/util/Spliterator.scala @@ -1,6 +1,6 @@ package java.util -import java.util.function.Consumer +import java.util.function._ import Spliterator._ @@ -13,6 +13,78 @@ object Spliterator { final val IMMUTABLE = 0x00000400 final val CONCURRENT = 0x00001000 final val SUBSIZED = 0x00004000 + + trait OfPrimitive[ + T, + T_CONS, + T_SPLITR <: Spliterator.OfPrimitive[T, T_CONS, T_SPLITR] + ] extends Spliterator[T] { + override def trySplit(): T_SPLITR + def tryAdvance(action: T_CONS): Boolean + def forEachRemaining(action: T_CONS): Unit = { + while (tryAdvance(action)) () + } + } + + trait OfInt + extends OfPrimitive[java.lang.Integer, IntConsumer, Spliterator.OfInt] { + override def trySplit(): OfInt + override def tryAdvance(action: IntConsumer): Boolean + override def forEachRemaining(action: IntConsumer): Unit = + while (tryAdvance(action)) () + override def tryAdvance(action: Consumer[_ >: Integer]): Boolean = + action match { + case action: IntConsumer => tryAdvance(action: IntConsumer) + case _ => tryAdvance((action.accept(_)): IntConsumer) + } + override def forEachRemaining(action: Consumer[_ >: Integer]): Unit = + action match { + case action: IntConsumer => forEachRemaining(action: IntConsumer) + case _ => forEachRemaining((action.accept(_)): IntConsumer) + } + + } + trait OfLong + extends OfPrimitive[java.lang.Long, LongConsumer, Spliterator.OfLong] { + override def trySplit(): OfLong + override def tryAdvance(action: LongConsumer): Boolean + override def forEachRemaining(action: LongConsumer): Unit = + while (tryAdvance(action)) () + override def tryAdvance(action: Consumer[_ >: java.lang.Long]): Boolean = + action match { + case action: LongConsumer => tryAdvance(action: LongConsumer) + case _ => tryAdvance((action.accept(_)): LongConsumer) + } + override def forEachRemaining(action: Consumer[_ >: java.lang.Long]): Unit = + action match { + case action: LongConsumer => forEachRemaining(action: LongConsumer) + case _ => forEachRemaining((action.accept(_)): LongConsumer) + } + } + trait OfDouble + extends OfPrimitive[ + java.lang.Double, + DoubleConsumer, + Spliterator.OfDouble + ] { + override def trySplit(): OfDouble + override def tryAdvance(action: DoubleConsumer): Boolean + override def forEachRemaining(action: DoubleConsumer): Unit = + while (tryAdvance(action)) () + override def tryAdvance(action: Consumer[_ >: java.lang.Double]): Boolean = + action match { + case action: DoubleConsumer => tryAdvance(action: DoubleConsumer) + case _ => tryAdvance((action.accept(_)): DoubleConsumer) + } + override def forEachRemaining( + action: Consumer[_ >: java.lang.Double] + ): Unit = + action match { + case action: DoubleConsumer => forEachRemaining(action: DoubleConsumer) + case _ => forEachRemaining((action.accept(_)): DoubleConsumer) + } + } + } trait Spliterator[T] { diff --git a/javalib/src/main/scala/java/util/Spliterators.scala b/javalib/src/main/scala/java/util/Spliterators.scala new file mode 100644 index 0000000000..cc5967e4b8 --- /dev/null +++ b/javalib/src/main/scala/java/util/Spliterators.scala @@ -0,0 +1,939 @@ +package java.util + +import java.util.function._ + +import Spliterator._ + +/** This is a basic, limit implementation of Spliterators. It is a basis for + * further Scala Native development, especially in the java.util.concurrent + * package. + * + * It is most empathically __NOT__ intended for production use. + * + * The limitations of the this implementation may not be as strong as they + * appear at first blush. Many/most classes which extend Spliterator (no s) + * supply more competent and efficient implementations. + * + * The implementation of methods on Spliterators are, to current knowledge, + * robust. Many of these methods return spliterators. Those spliterators have + * some known limitations and may have others. + * + * Future evolutions should, over time, remove these limitations: + * + * - spliterators specified by Java as late-binding may not be late-binding. + * + * - spliterators never check for concurrent modification. + * + * - A number of spliterator methods have JVM descriptions of what happens + * after iteration starts and one of certain methods, say, trySplit() is + * called. This implementation may not follow the JVM description. Even in + * Java, it is better to never trySplit() after having begun using a + * spliterator to iterate. + * + * Also noted: + * + * - Java documents that spliterators need not be thread-safe. This + * implementation follows that guidance. + */ + +/* Developer Notes on evolving Spliterators + * + * 1) The limitations listed above should be corrected, or at least relaxed. + * + * 2) Performance, especially with spliterators which have a large, + * say million or US billion elements, should be measured. That + * will probably show that both execution time and memory usage + * need to be reduced. + * + * For example, an individual development-only Test + * in SpliteratorsTrySplitTest showed an an un-optimized Scala Native + * executable having results matching the same Test on JVM but taking + * approximately 50% longer (a minute or so), possibly due to swapping + * caused by higher memory usage. + */ + +object Spliterators { + + private final val sizedCharacteristicsMask = + Spliterator.SIZED | Spliterator.SUBSIZED + + private def isMaskSet(characteristics: Int, mask: Int): Boolean = + (characteristics & mask) == mask + + private def maskOff(characteristics: Int, mask: Int): Int = + characteristics & ~mask + + private def maskOn(characteristics: Int, mask: Int): Int = + characteristics | mask + + private def maybeSetSizedCharacteristics(characteristics: Int): Int = { + if (isMaskSet(characteristics, Spliterator.CONCURRENT)) characteristics + else maskOn(characteristics, sizedCharacteristicsMask) + } + + /* This implementation of trySplit() is reverse engineered from the + * default JVM algorithm for Iterable and Collection, without having + * looked at the JVM code. + * + * It allows unit-tests to run in either JVM or Scala Native with the + * matching results. + * + * The JVM algorithm switches from a first "count-them-out" iteration + * algorithm to a reasonably efficient array based "bisection" algorithm. + * + * As advised by the Java documentation authors, sub-classes may benefit + * from overriding this implementation with a more efficient one. + * + * Case in Point, JSR-166 implementations, which can be examined, tend to + * use a different algorithm for batch sizing. + */ + + private final val ABSTRACT_TRYSPLIT_BATCH_SIZE = 1024 + + private def getTrySplitBatchSize(multiplier: Long): Int = { + /* To be discovered: + * JVM may have a lower maximum batch size. + * + * JSR-166 LinkedBlockingQueue.scala specifies a MAX_BATCH of + * 1 << 25 (33_554_432), well less than Integer.MAX_VALUE. + */ + val computedSize = multiplier * ABSTRACT_TRYSPLIT_BATCH_SIZE + Math.min(computedSize, Integer.MAX_VALUE).toInt + } + + private def trySplitUsageRatioOK(used: Long, total: Long): Boolean = { + /* This method concentrates the decision of whether trySplit() should take + * the faster and easier route of passing its work buffer directly to + * Spliterators or if it should reduce it to an exact size by copying. + * + * The issue is that the size of the allocated buffer grows after + * repeated splits on the same spliterator. If a buffer is filled, + * there is no need to copy. The opposite is also clear, if there is + * one byte in a megabyte buffer, it makes sense to pay the Array allocation + * and copy in order to free up the unused memory. + * + * Somewhere between the two scenarios is a sweet spot, which probably + * varies by workload and available resources. Configuration is the + * classical solution but it brings complexity. Auto-tuning of buffer size + * or a different, perhaps capped, scale-up buffer size algorithm + * is the other classical solution. Here that would mean no longer + * matching the JVM size progression. + * + * This is a place to make it easier to tune heuristics. The current + * ones are best guesses, without the benefit of configuration. + * + * Life is choices! + */ + if (total < ABSTRACT_TRYSPLIT_BATCH_SIZE) true // avoid copy on first split + else if (used == total) true + else { + val usageRatio = used / total + usageRatio > 0.8 // Allow 20% wastage. + } + } + + abstract class AbstractDoubleSpliterator( + est: Long, + additionalCharacteristics: Int + ) extends Spliterator.OfDouble { + private var remaining = est + + // JVM uses an arithmetic progression, incrementing factor with each split. + private var trySplitsMultiplier = 1L // a Long to ease overflow checking + + def characteristics(): Int = additionalCharacteristics + + def estimateSize(): Long = remaining + + def trySplit(): Spliterator.OfDouble = { + // Guard ArrayList(size) constructor by avoiding int overflow (to minus). + val batchSize = getTrySplitBatchSize(trySplitsMultiplier) + val buf = new Array[Double](batchSize) + + var count = 0 + + val action: DoubleConsumer = + (e: Double) => { buf(count) = e; count += 1 } + + while ((count < batchSize) && tryAdvance(action)) { /* side-effect */ } + + if (count == 0) null.asInstanceOf[Spliterator.OfDouble] + else { + remaining -= count + trySplitsMultiplier += 1 + + /* Passing an Array down allows the created spliterator to + * traverse and split more efficiently. + * + * Pass accumulating buffer if small or if unused, wasted space is + * tolerable. Otherwise, pay the cost of an allocation and + * potentially large copy. + */ + + val batch = + if (trySplitUsageRatioOK(count, batchSize)) buf + else Arrays.copyOf(buf, count) + + Spliterators.spliterator( + batch, // of AnyVal primitives + 0, + count, + additionalCharacteristics + ) + } + } + } + + abstract class AbstractIntSpliterator( + est: Long, + additionalCharacteristics: Int + ) extends Spliterator.OfInt { + private var remaining = est + + // JVM uses an arithmetic progression, incrementing factor with each split. + private var trySplitsMultiplier = 1L // a Long to ease overflow checking + + def characteristics(): Int = additionalCharacteristics + + def estimateSize(): Long = remaining + + def trySplit(): Spliterator.OfInt = { + // Guard ArrayList(size) constructor by avoiding int overflow (to minus). + val batchSize = getTrySplitBatchSize(trySplitsMultiplier) + val buf = new Array[Int](batchSize) + + var count = 0 + + val action: IntConsumer = + (e: Int) => { buf(count) = e; count += 1 } + + while ((count < batchSize) && tryAdvance(action)) { /* side-effect */ } + + if (count == 0) null.asInstanceOf[Spliterator.OfInt] + else { + remaining -= count + trySplitsMultiplier += 1 + + // See comment in corresponding place in AbstractDoubleSpliterator + val batch = + if (trySplitUsageRatioOK(count, batchSize)) buf + else Arrays.copyOf(buf, count) + + Spliterators.spliterator( + batch, // of AnyVal primitives + 0, + count, + additionalCharacteristics + ) + } + } + } + + abstract class AbstractLongSpliterator( + est: Long, + additionalCharacteristics: Int + ) extends Spliterator.OfLong { + private var remaining = est + + // JVM uses an arithmetic progression, incrementing factor with each split. + private var trySplitsMultiplier = 1L // a Long to ease overflow checking + + def characteristics(): Int = additionalCharacteristics + + def estimateSize(): Long = remaining + + def trySplit(): Spliterator.OfLong = { + // Guard ArrayList(size) constructor by avoiding int overflow (to minus). + val batchSize = getTrySplitBatchSize(trySplitsMultiplier) + val buf = new Array[Long](batchSize) + + var count = 0 + + val action: LongConsumer = + (e: Long) => { buf(count) = e; count += 1 } + + while ((count < batchSize) && tryAdvance(action)) { /* side-effect */ } + + if (count == 0) null.asInstanceOf[Spliterator.OfLong] + else { + remaining -= count + trySplitsMultiplier += 1 + + // See comment in corresponding place in AbstractDoubleSpliterator + val batch = + if (trySplitUsageRatioOK(count, batchSize)) buf + else Arrays.copyOf(buf, count) + + Spliterators.spliterator( + batch, // of AnyVal primitives + 0, + count, + additionalCharacteristics + ) + } + } + } + + abstract class AbstractSpliterator[T]( + est: Long, + additionalCharacteristics: Int + ) extends Spliterator[T] { + private var remaining = est + + // JVM uses an arithmetic progression, incrementing factor with each split. + private var trySplitsMultiplier = 1L // a Long to ease overflow checking + + def characteristics(): Int = additionalCharacteristics + + def estimateSize(): Long = remaining + + def trySplit(): Spliterator[T] = { + // Guard ArrayList(size) constructor by avoiding int overflow (to minus). + val batchSize = getTrySplitBatchSize(trySplitsMultiplier) + val buf = new Array[Object](batchSize) + + var count = 0 + + /* Someday it would be nice to get rid of the cost of the runtime cast. + * The current issue is that type T has no upper bound, such as + * Object or AnyRef. With current declarations, an uninformed, + * unwary, unfortunate, or malicious user could specify an AnyVal + * for T, such as "new AbstractSplitertor[scala.Double]". + * + * The Scala Native compiler checks the signature of "action" + * against the JDK, so that signature can not be modified. + */ + val action: Consumer[_ >: T] = + (e: T) => { buf(count) = e.asInstanceOf[Object]; count += 1 } + + while ((count < batchSize) && tryAdvance(action)) { /* side-effect */ } + + if (count == 0) null.asInstanceOf[Spliterator[T]] + else { + remaining -= count + trySplitsMultiplier += 1 + + // See comment in corresponding place in AbstractDoubleSpliterator + val batch = + if (trySplitUsageRatioOK(count, batchSize)) buf + else Arrays.copyOf(buf, count) + + Spliterators.spliterator( + batch, // of AnyRef Objects + 0, + count, + additionalCharacteristics + ) + } + } + } + + def emptyDoubleSpliterator(): Spliterator.OfDouble = { + new AbstractDoubleSpliterator(0L, sizedCharacteristicsMask) { + def tryAdvance(action: DoubleConsumer): Boolean = false + } + } + + def emptyIntSpliterator(): Spliterator.OfInt = { + new AbstractIntSpliterator(0L, sizedCharacteristicsMask) { + def tryAdvance(action: IntConsumer): Boolean = false + } + } + + def emptyLongSpliterator(): Spliterator.OfLong = { + new AbstractLongSpliterator(0L, sizedCharacteristicsMask) { + def tryAdvance(action: LongConsumer): Boolean = false + } + } + + def emptySpliterator[T](): Spliterator[T] = { + new AbstractSpliterator[T](0, sizedCharacteristicsMask) { + def tryAdvance(action: Consumer[_ >: T]): Boolean = false + } + } + + def iterator( + spliterator: Spliterator.OfDouble + ): PrimitiveIterator.OfDouble = { + Objects.requireNonNull(spliterator) + + new PrimitiveIterator.OfDouble { + // One element lookahead + var cached: Option[scala.Double] = None + + def hasNext(): Boolean = { + if (cached.nonEmpty) true + else { + spliterator.tryAdvance((e: Double) => (cached = Some(e))) + cached.nonEmpty + } + } + + def nextDouble(): scala.Double = { + if (!hasNext()) { + throw new NoSuchElementException() + } else { + val nxt = cached.get + cached = None + nxt + } + } + } + } + + def iterator(spliterator: Spliterator.OfInt): PrimitiveIterator.OfInt = { + Objects.requireNonNull(spliterator) + + new PrimitiveIterator.OfInt { + // One element lookahead + var cached: Option[scala.Int] = None + + def hasNext(): Boolean = { + if (cached.nonEmpty) true + else { + spliterator.tryAdvance((e: Int) => (cached = Some(e))) + cached.nonEmpty + } + } + + def nextInt(): scala.Int = { + if (!hasNext()) { + throw new NoSuchElementException() + } else { + val nxt = cached.get + cached = None + nxt + } + } + } + } + + def iterator(spliterator: Spliterator.OfLong): PrimitiveIterator.OfLong = { + Objects.requireNonNull(spliterator) + + new PrimitiveIterator.OfLong { + // One element lookahead + var cached: Option[scala.Long] = None + + def hasNext(): Boolean = { + if (cached.nonEmpty) true + else { + spliterator.tryAdvance((e: Long) => (cached = Some(e))) + cached.nonEmpty + } + } + + def nextLong(): scala.Long = { + if (!hasNext()) { + throw new NoSuchElementException() + } else { + val nxt = cached.get + cached = None + nxt + } + } + } + } + + def iterator[T](spliterator: Spliterator[_ <: T]): Iterator[T] = { + Objects.requireNonNull(spliterator) + + new Iterator[T] { + // One element lookahead + var cached: Option[T] = None + + def hasNext(): Boolean = { + if (cached.nonEmpty) true + else { + spliterator.tryAdvance((e: T) => (cached = Some(e))) + cached.nonEmpty + } + } + + def next(): T = { + if (!hasNext()) { + throw new NoSuchElementException() + } else { + val nxt = cached.get + cached = None + nxt + } + } + } + } + + def spliterator[T]( + c: Collection[_ <: T], + characteristics: Int + ): Spliterator[T] = { + Objects.requireNonNull(c) + + val harmonized = maybeSetSizedCharacteristics(characteristics) + new AbstractSpliterator[T](c.size(), harmonized) { + lazy val it = c.iterator() + + def tryAdvance(action: Consumer[_ >: T]): Boolean = { + Objects.requireNonNull(action) + if (!it.hasNext()) false + else { + action.accept(it.next()) + true + } + } + } + } + + private final class SpliteratorFromArrayDouble( + array: Array[Double], + fromIndex: Int, + toIndex: Int, + additionalCharacteristics: Int + ) extends Spliterator.OfDouble { + // By contract, array == null & bounds have been checked by caller. + + // current index, modified on traverse/split + private var cursor: Int = fromIndex + + def trySplit(): Spliterator.OfDouble = { + val hi = toIndex + val lo = cursor + val mid = (lo + hi) >>> 1 + if (lo >= mid) null + else { + cursor = mid + new SpliteratorFromArrayDouble( + array, + lo, + mid, + additionalCharacteristics + ) + } + } + + def tryAdvance(action: DoubleConsumer): Boolean = { + Objects.requireNonNull(action) + if (cursor == toIndex) false + else { + action.accept(array(cursor)) + cursor += 1 + true + } + } + + def estimateSize(): Long = { toIndex - cursor } + + def characteristics(): Int = + maskOn(additionalCharacteristics, sizedCharacteristicsMask) + } + + def spliterator( + array: Array[Double], + additionalCharacteristics: Int + ): Spliterator.OfDouble = { + Objects.requireNonNull(array) + spliterator(array, 0, array.size, additionalCharacteristics) + } + + private def checkArrayBounds( + arraySize: Int, + fromIndex: Int, + toIndex: Int + ): Unit = { + if (fromIndex < 0) + throw new ArrayIndexOutOfBoundsException(fromIndex) + + if (toIndex < fromIndex) { + throw new ArrayIndexOutOfBoundsException( + s"origin(${toIndex}) > fence(${fromIndex})" + ) + } + + if (toIndex > arraySize) { + throw new ArrayIndexOutOfBoundsException( + s"Array index out of range: ${toIndex}" + ) + } + } + + def spliterator( + array: Array[Double], + fromIndex: Int, + toIndex: Int, + additionalCharacteristics: Int + ): Spliterator.OfDouble = { + Objects.requireNonNull(array) + checkArrayBounds(array.length, fromIndex, toIndex) + + new SpliteratorFromArrayDouble( + array, + fromIndex, + toIndex, + additionalCharacteristics + ) + } + + private final class SpliteratorFromArrayInt( + array: Array[Int], + fromIndex: Int, + toIndex: Int, + additionalCharacteristics: Int + ) extends Spliterator.OfInt { + // By contract, array == null & bounds have been checked by caller. + + // current index, modified on traverse/split + private var cursor: Int = fromIndex + + def trySplit(): Spliterator.OfInt = { + val hi = toIndex + val lo = cursor + val mid = (lo + hi) >>> 1 + if (lo >= mid) null + else { + cursor = mid + new SpliteratorFromArrayInt(array, lo, mid, additionalCharacteristics) + } + } + + def tryAdvance(action: IntConsumer): Boolean = { + Objects.requireNonNull(action) + if (cursor == toIndex) false + else { + action.accept(array(cursor)) + cursor += 1 + true + } + } + + def estimateSize(): Long = { toIndex - cursor } + + def characteristics(): Int = + maskOn(additionalCharacteristics, sizedCharacteristicsMask) + } + + def spliterator( + array: Array[Int], + additionalCharacteristics: Int + ): Spliterator.OfInt = { + Objects.requireNonNull(array) + spliterator(array, 0, array.size, additionalCharacteristics) + } + + def spliterator( + array: Array[Int], + fromIndex: Int, + toIndex: Int, + additionalCharacteristics: Int + ): Spliterator.OfInt = { + Objects.requireNonNull(array) + checkArrayBounds(array.length, fromIndex, toIndex) + + new SpliteratorFromArrayInt( + array, + fromIndex, + toIndex, + additionalCharacteristics + ) + } + + def spliterator[T]( + iterator: Iterator[_ <: T], + size: Long, + characteristics: Int + ): Spliterator[T] = { + Objects.requireNonNull(iterator) + val harmonized = maybeSetSizedCharacteristics(characteristics) + new AbstractSpliterator[T](size, harmonized) { + def tryAdvance(action: Consumer[_ >: T]): Boolean = { + Objects.requireNonNull(action) + if (!iterator.hasNext()) false + else { + action.accept(iterator.next()) + true + } + } + } + } + + private final class SpliteratorFromArrayLong( + array: Array[Long], + fromIndex: Int, + toIndex: Int, + additionalCharacteristics: Int + ) extends Spliterator.OfLong { + // By contract, array == null & bounds have been checked by caller. + + // current index, modified on traverse/split + private var cursor: Int = fromIndex + + def trySplit(): Spliterator.OfLong = { + val hi = toIndex + val lo = cursor + val mid = (lo + hi) >>> 1 + if (lo >= mid) null + else { + cursor = mid + new SpliteratorFromArrayLong(array, lo, mid, additionalCharacteristics) + } + } + + def tryAdvance(action: LongConsumer): Boolean = { + Objects.requireNonNull(action) + if (cursor == toIndex) false + else { + action.accept(array(cursor)) + cursor += 1 + true + } + } + + def estimateSize(): Long = { toIndex - cursor } + + def characteristics(): Int = + maskOn(additionalCharacteristics, sizedCharacteristicsMask) + } + + def spliterator( + array: Array[Long], + additionalCharacteristics: Int + ): Spliterator.OfLong = { + Objects.requireNonNull(array) + spliterator(array, 0, array.size, additionalCharacteristics) + } + + def spliterator( + array: Array[Long], + fromIndex: Int, + toIndex: Int, + additionalCharacteristics: Int + ): Spliterator.OfLong = { + Objects.requireNonNull(array) + checkArrayBounds(array.length, fromIndex, toIndex) + + new SpliteratorFromArrayLong( + array, + fromIndex, + toIndex, + additionalCharacteristics + ) + } + + private final class SpliteratorFromArrayObject[T]( + array: Array[Object], + fromIndex: Int, + toIndex: Int, + additionalCharacteristics: Int + ) extends Spliterator[T] { + // By contract, array == null & bounds have been checked by caller. + + // current index, modified on traverse/split + private var cursor: Int = fromIndex + + def trySplit(): Spliterator[T] = { + val hi = toIndex + val lo = cursor + val mid = (lo + hi) >>> 1 + if (lo >= mid) null + else { + cursor = mid + new SpliteratorFromArrayObject[T]( + array, + lo, + mid, + additionalCharacteristics + ) + } + } + + def tryAdvance(action: Consumer[_ >: T]): Boolean = { + Objects.requireNonNull(action) + if (cursor == toIndex) false + else { + action.accept(array(cursor).asInstanceOf[T]) + cursor += 1 + true + } + } + + def estimateSize(): Long = { toIndex - cursor } + + def characteristics(): Int = { + additionalCharacteristics | + sizedCharacteristicsMask + } + } + + def spliterator[T]( + array: Array[Object], + additionalCharacteristics: Int + ): Spliterator[T] = { + Objects.requireNonNull(array) + + new SpliteratorFromArrayObject[T]( + array, + 0, + array.size, + additionalCharacteristics + ) + } + + def spliterator[T]( + array: Array[Object], + fromIndex: Int, + toIndex: Int, + additionalCharacteristics: Int + ): Spliterator[T] = { + Objects.requireNonNull(array) + checkArrayBounds(array.length, fromIndex, toIndex) + + new SpliteratorFromArrayObject[T]( + array, + fromIndex, + toIndex, + additionalCharacteristics + ) + } + + def spliterator( + iterator: PrimitiveIterator.OfDouble, + size: Long, + characteristics: Int + ): Spliterator.OfDouble = { + Objects.requireNonNull(iterator) + + val harmonized = maybeSetSizedCharacteristics(characteristics) + new AbstractDoubleSpliterator(size, harmonized) { + def tryAdvance(action: DoubleConsumer): Boolean = { + Objects.requireNonNull(action) + if (!iterator.hasNext()) false + else { + action.accept(iterator.nextDouble()) + true + } + } + } + } + + def spliterator( + iterator: PrimitiveIterator.OfInt, + size: Long, + characteristics: Int + ): Spliterator.OfInt = { + Objects.requireNonNull(iterator) + + val harmonized = maybeSetSizedCharacteristics(characteristics) + new AbstractIntSpliterator(size, harmonized) { + def tryAdvance(action: IntConsumer): Boolean = { + Objects.requireNonNull(action) + if (!iterator.hasNext()) false + else { + action.accept(iterator.nextInt()) + true + } + } + } + } + + def spliterator( + iterator: PrimitiveIterator.OfLong, + size: Long, + characteristics: Int + ): Spliterator.OfLong = { + Objects.requireNonNull(iterator) + + val harmonized = maybeSetSizedCharacteristics(characteristics) + new AbstractLongSpliterator(size, harmonized) { + def tryAdvance(action: LongConsumer): Boolean = { + Objects.requireNonNull(action) + if (!iterator.hasNext()) false + else { + action.accept(iterator.nextLong()) + true + } + } + } + } + + def spliteratorUnknownSize[T]( + iterator: Iterator[_ <: T], + characteristics: Int + ): Spliterator[T] = { + Objects.requireNonNull(iterator) + + new AbstractSpliterator[T]( + Long.MaxValue, + maskOff(characteristics, sizedCharacteristicsMask) + ) { + def tryAdvance(action: Consumer[_ >: T]): Boolean = { + Objects.requireNonNull(action) + if (!iterator.hasNext()) false + else { + action.accept(iterator.next()) + true + } + } + } + } + + def spliteratorUnknownSize( + iterator: PrimitiveIterator.OfDouble, + characteristics: Int + ): Spliterator.OfDouble = { + Objects.requireNonNull(iterator) + + new AbstractDoubleSpliterator( + Long.MaxValue, + maskOff(characteristics, sizedCharacteristicsMask) + ) { + def tryAdvance(action: DoubleConsumer): Boolean = { + Objects.requireNonNull(action) + if (!iterator.hasNext()) false + else { + action.accept(iterator.nextDouble()) + true + } + } + } + } + + def spliteratorUnknownSize( + iterator: PrimitiveIterator.OfInt, + characteristics: Int + ): Spliterator.OfInt = { + Objects.requireNonNull(iterator) + + new AbstractIntSpliterator( + Long.MaxValue, + maskOff(characteristics, sizedCharacteristicsMask) + ) { + def tryAdvance(action: IntConsumer): Boolean = { + Objects.requireNonNull(action) + if (!iterator.hasNext()) false + else { + action.accept(iterator.nextInt()) + true + } + } + } + } + + def spliteratorUnknownSize( + iterator: PrimitiveIterator.OfLong, + characteristics: Int + ): Spliterator.OfLong = { + Objects.requireNonNull(iterator) + + new AbstractLongSpliterator( + Long.MaxValue, + maskOff(characteristics, sizedCharacteristicsMask) + ) { + def tryAdvance(action: LongConsumer): Boolean = { + Objects.requireNonNull(action) + if (!iterator.hasNext()) false + else { + action.accept(iterator.nextLong()) + true + } + } + } + } +} diff --git a/javalib/src/main/scala/java/util/concurrent/BlockingDeque.scala b/javalib/src/main/scala/java/util/concurrent/BlockingDeque.scala new file mode 100644 index 0000000000..dda0e77c49 --- /dev/null +++ b/javalib/src/main/scala/java/util/concurrent/BlockingDeque.scala @@ -0,0 +1,31 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package java.util +package concurrent + +trait BlockingDeque[E] extends BlockingQueue[E] with Deque[E] { + + def addFirst(e: E): Unit + + def addLast(e: E): Unit + + def putFirst(e: E): Unit + + def putLast(e: E): Unit + + def offerFirst(e: E, timeout: Long, unit: TimeUnit): Boolean + + def offerLast(e: E, timeout: Long, unit: TimeUnit): Boolean + + def takeFirst(): E + + def takeLast(): E + + def pollFirst(timeout: Long, unit: TimeUnit): E + + def pollLast(timeout: Long, unit: TimeUnit): E +} diff --git a/javalib/src/main/scala/java/util/concurrent/TransferQueue.scala b/javalib/src/main/scala/java/util/concurrent/TransferQueue.scala new file mode 100644 index 0000000000..b4d6dfabaf --- /dev/null +++ b/javalib/src/main/scala/java/util/concurrent/TransferQueue.scala @@ -0,0 +1,21 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package java.util +package concurrent + +trait TransferQueue[E] extends BlockingQueue[E] { + + def tryTransfer(e: E): Boolean + + def transfer(e: E): Unit + + def tryTransfer(e: E, timeout: Long, unit: TimeUnit): Boolean + + def hasWaitingConsumer(): Boolean + + def getWaitingConsumerCount(): Int +} diff --git a/javalib/src/main/scala/java/util/concurrent/atomic/AtomicInteger.scala b/javalib/src/main/scala/java/util/concurrent/atomic/AtomicInteger.scala index 23c59d6bc4..9a17a48257 100644 --- a/javalib/src/main/scala/java/util/concurrent/atomic/AtomicInteger.scala +++ b/javalib/src/main/scala/java/util/concurrent/atomic/AtomicInteger.scala @@ -1,5 +1,7 @@ package java.util.concurrent.atomic +import java.util.function.IntUnaryOperator + class AtomicInteger(private[this] var value: Int) extends Number with Serializable { @@ -55,6 +57,17 @@ class AtomicInteger(private[this] var value: Int) newValue } + final def getAndUpdate(updateFunction: IntUnaryOperator): Int = { + val old = value + value = updateFunction.applyAsInt(old) + old + } + + final def updateAndGet(updateFunction: IntUnaryOperator): Int = { + value = updateFunction.applyAsInt(value) + value + } + override def toString(): String = value.toString() diff --git a/javalib/src/main/scala/java/util/concurrent/atomic/AtomicLong.scala b/javalib/src/main/scala/java/util/concurrent/atomic/AtomicLong.scala index 14264aed46..5f43cd984b 100644 --- a/javalib/src/main/scala/java/util/concurrent/atomic/AtomicLong.scala +++ b/javalib/src/main/scala/java/util/concurrent/atomic/AtomicLong.scala @@ -1,5 +1,7 @@ package java.util.concurrent.atomic +import java.util.function.LongUnaryOperator + class AtomicLong(private[this] var value: Long) extends Number with Serializable { @@ -54,6 +56,17 @@ class AtomicLong(private[this] var value: Long) newValue } + final def getAndUpdate(updateFunction: LongUnaryOperator): Long = { + val old = value + value = updateFunction.applyAsLong(old) + old + } + + final def updateAndGet(updateFunction: LongUnaryOperator): Long = { + value = updateFunction.applyAsLong(value) + value + } + override def toString(): String = value.toString() diff --git a/javalib/src/main/scala/java/util/function/BiConsumer.scala b/javalib/src/main/scala/java/util/function/BiConsumer.scala index 04d0e1a5ea..2ad5df5cab 100644 --- a/javalib/src/main/scala/java/util/function/BiConsumer.scala +++ b/javalib/src/main/scala/java/util/function/BiConsumer.scala @@ -1,17 +1,11 @@ -// Ported from Scala.js commit: f86ed6 c2f5a43 dated: 2020-09-06 - +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 package java.util.function trait BiConsumer[T, U] { - self => - def accept(t: T, u: U): Unit - def andThen(after: BiConsumer[T, U]): BiConsumer[T, U] = - new BiConsumer[T, U]() { - override def accept(t: T, u: U): Unit = { - self.accept(t, u) - after.accept(t, u) - } - } + def andThen(after: BiConsumer[T, U]): BiConsumer[T, U] = { (t: T, u: U) => + accept(t, u) + after.accept(t, u) + } } diff --git a/javalib/src/main/scala/java/util/function/BiFunction.scala b/javalib/src/main/scala/java/util/function/BiFunction.scala index 7eabfeb804..3ba125083a 100644 --- a/javalib/src/main/scala/java/util/function/BiFunction.scala +++ b/javalib/src/main/scala/java/util/function/BiFunction.scala @@ -1,13 +1,11 @@ -// Ported from Scala.js commit: d3a9711 dated: 2020-09-06 - +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 package java.util.function -trait BiFunction[T, U, R] { self => +trait BiFunction[T, U, R] { def apply(t: T, u: U): R def andThen[V](after: Function[_ >: R, _ <: V]): BiFunction[T, U, V] = { - new BiFunction[T, U, V] { - def apply(t: T, u: U): V = after.apply(self.apply(t, u)) - } + (t: T, u: U) => + after.apply(this.apply(t, u)) } } diff --git a/javalib/src/main/scala/java/util/function/BiPredicate.scala b/javalib/src/main/scala/java/util/function/BiPredicate.scala index bc160335d6..e28893003f 100644 --- a/javalib/src/main/scala/java/util/function/BiPredicate.scala +++ b/javalib/src/main/scala/java/util/function/BiPredicate.scala @@ -1,25 +1,18 @@ -// Ported from Scala.js commit: 0c27b64 dated: 2020-09-06 - +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 package java.util.function -trait BiPredicate[T, U] { self => +trait BiPredicate[T, U] { def test(t: T, u: U): Boolean - def and(other: BiPredicate[_ >: T, _ >: U]): BiPredicate[T, U] = - new BiPredicate[T, U] { - override def test(t: T, u: U): Boolean = - self.test(t, u) && other.test(t, u) - } + def and(other: BiPredicate[_ >: T, _ >: U]): BiPredicate[T, U] = { + (t: T, u: U) => + test(t, u) && other.test(t, u) + } - def negate(): BiPredicate[T, U] = - new BiPredicate[T, U] { - override def test(t: T, u: U): Boolean = - !self.test(t, u) - } + def negate(): BiPredicate[T, U] = (t: T, u: U) => !test(t, u) - def or(other: BiPredicate[_ >: T, _ >: U]): BiPredicate[T, U] = - new BiPredicate[T, U] { - override def test(t: T, u: U): Boolean = - self.test(t, u) || other.test(t, u) - } + def or(other: BiPredicate[_ >: T, _ >: U]): BiPredicate[T, U] = { + (t: T, u: U) => + test(t, u) || other.test(t, u) + } } diff --git a/javalib/src/main/scala/java/util/function/BinaryOperator.scala b/javalib/src/main/scala/java/util/function/BinaryOperator.scala index 503c24057f..43dd5e35bc 100644 --- a/javalib/src/main/scala/java/util/function/BinaryOperator.scala +++ b/javalib/src/main/scala/java/util/function/BinaryOperator.scala @@ -1,24 +1,20 @@ +// Ported from Scala.js, commit SHA: 1ef4c4e0f dated: 2020-09-06 package java.util.function -import java.util.{Comparator, Objects} +import java.util.Comparator -trait BinaryOperator[T] extends BiFunction[T, T, T] { self => } +trait BinaryOperator[T] extends BiFunction[T, T, T] object BinaryOperator { - def minBy[T](comparator: Comparator[_ >: T]): BinaryOperator[T] = { - Objects.requireNonNull(comparator) - new BinaryOperator[T] { - override def apply(a: T, b: T): T = - if (comparator.compare(a, b) <= 0) a else b - } + (a: T, b: T) => + if (comparator.compare(a, b) <= 0) a + else b } def maxBy[T](comparator: Comparator[_ >: T]): BinaryOperator[T] = { - Objects.requireNonNull(comparator) - new BinaryOperator[T] { - override def apply(a: T, b: T): T = - if (comparator.compare(a, b) >= 0) a else b - } + (a: T, b: T) => + if (comparator.compare(a, b) >= 0) a + else b } } diff --git a/javalib/src/main/scala/java/util/function/BooleanSupplier.scala b/javalib/src/main/scala/java/util/function/BooleanSupplier.scala new file mode 100644 index 0000000000..cb60c1f860 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/BooleanSupplier.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: db63dabed dated: 2020-10-06 +package java.util.function + +@FunctionalInterface +trait BooleanSupplier { + def getAsBoolean(): Boolean +} diff --git a/javalib/src/main/scala/java/util/function/Consumer.scala b/javalib/src/main/scala/java/util/function/Consumer.scala index 5a093ad3e7..a4bebd84fb 100644 --- a/javalib/src/main/scala/java/util/function/Consumer.scala +++ b/javalib/src/main/scala/java/util/function/Consumer.scala @@ -1,12 +1,16 @@ +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 package java.util.function +@FunctionalInterface trait Consumer[T] { self => def accept(t: T): Unit - def andThen(after: Consumer[T]): Consumer[T] = new Consumer[T]() { - def accept(t: T): Unit = { - self.accept(t) - after.accept(t) + def andThen(after: Consumer[_ >: T]): Consumer[T] = { + new Consumer[T] { + def accept(t: T): Unit = { + self.accept(t) + after.accept(t) + } } } } diff --git a/javalib/src/main/scala/java/util/function/DoubleBinaryOperator.scala b/javalib/src/main/scala/java/util/function/DoubleBinaryOperator.scala new file mode 100644 index 0000000000..2a531b87d4 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/DoubleBinaryOperator.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait DoubleBinaryOperator { + def applyAsDouble(left: Double, right: Double): Double +} diff --git a/javalib/src/main/scala/java/util/function/DoubleConsumer.scala b/javalib/src/main/scala/java/util/function/DoubleConsumer.scala new file mode 100644 index 0000000000..01181527d1 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/DoubleConsumer.scala @@ -0,0 +1,12 @@ +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 +package java.util.function + +@FunctionalInterface +trait DoubleConsumer { + def accept(value: Double): Unit + + def andThen(after: DoubleConsumer): DoubleConsumer = { (value: Double) => + this.accept(value) + after.accept(value) + } +} diff --git a/javalib/src/main/scala/java/util/function/DoubleFunction.scala b/javalib/src/main/scala/java/util/function/DoubleFunction.scala new file mode 100644 index 0000000000..7c08b76c4a --- /dev/null +++ b/javalib/src/main/scala/java/util/function/DoubleFunction.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait DoubleFunction[R] { + def apply(value: Double): R +} diff --git a/javalib/src/main/scala/java/util/function/DoublePredicate.scala b/javalib/src/main/scala/java/util/function/DoublePredicate.scala new file mode 100644 index 0000000000..2e8119789e --- /dev/null +++ b/javalib/src/main/scala/java/util/function/DoublePredicate.scala @@ -0,0 +1,30 @@ +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 +package java.util.function + +@FunctionalInterface +trait DoublePredicate { self => + def test(t: Double): Boolean + + def and(other: DoublePredicate): DoublePredicate = { + new DoublePredicate { + def test(value: Double): Boolean = + // the order and short-circuit are by-spec + self.test(value) && other.test(value) + } + } + + def negate(): DoublePredicate = { + new DoublePredicate { + def test(value: Double): Boolean = + !self.test(value) + } + } + + def or(other: DoublePredicate): DoublePredicate = { + new DoublePredicate { + def test(value: Double): Boolean = + // the order and short-circuit are by-spec + self.test(value) || other.test(value) + } + } +} diff --git a/javalib/src/main/scala/java/util/function/DoubleSupplier.scala b/javalib/src/main/scala/java/util/function/DoubleSupplier.scala new file mode 100644 index 0000000000..feed9e76ce --- /dev/null +++ b/javalib/src/main/scala/java/util/function/DoubleSupplier.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: db63dabed dated: 2020-10-06 +package java.util.function + +@FunctionalInterface +trait DoubleSupplier { + def getAsDouble(): Double +} diff --git a/javalib/src/main/scala/java/util/function/DoubleToIntFunction.scala b/javalib/src/main/scala/java/util/function/DoubleToIntFunction.scala new file mode 100644 index 0000000000..fa1123c230 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/DoubleToIntFunction.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait DoubleToIntFunction { + def applyAsInt(value: Double): Int +} diff --git a/javalib/src/main/scala/java/util/function/DoubleToLongFunction.scala b/javalib/src/main/scala/java/util/function/DoubleToLongFunction.scala new file mode 100644 index 0000000000..ca6c2b53ba --- /dev/null +++ b/javalib/src/main/scala/java/util/function/DoubleToLongFunction.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait DoubleToLongFunction { + def applyAsLong(value: Double): Long +} diff --git a/javalib/src/main/scala/java/util/function/DoubleUnaryOperator.scala b/javalib/src/main/scala/java/util/function/DoubleUnaryOperator.scala new file mode 100644 index 0000000000..a52dadc64b --- /dev/null +++ b/javalib/src/main/scala/java/util/function/DoubleUnaryOperator.scala @@ -0,0 +1,21 @@ +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 +package java.util.function + +@FunctionalInterface +trait DoubleUnaryOperator { + def applyAsDouble(operand: Double): Double + + def andThen(after: DoubleUnaryOperator): DoubleUnaryOperator = { + (d: Double) => + after.applyAsDouble(applyAsDouble(d)) + } + + def compose(before: DoubleUnaryOperator): DoubleUnaryOperator = { + (d: Double) => + applyAsDouble(before.applyAsDouble(d)) + } +} + +object DoubleUnaryOperator { + def identity(): DoubleUnaryOperator = (d: Double) => d +} diff --git a/javalib/src/main/scala/java/util/function/Function.scala b/javalib/src/main/scala/java/util/function/Function.scala index 9abd8c367e..32c37d03e0 100644 --- a/javalib/src/main/scala/java/util/function/Function.scala +++ b/javalib/src/main/scala/java/util/function/Function.scala @@ -1,24 +1,18 @@ -// Ported from Scala.js commit: eb637e3 dated: 2020-09-06 - +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 package java.util.function -trait Function[T, R] { self => +trait Function[T, R] { def apply(t: T): R - def andThen[V](after: Function[_ >: R, _ <: V]): Function[T, V] = - new Function[T, V] { - override def apply(t: T): V = after.apply(self.apply(t)) - } + def andThen[V](after: Function[_ >: R, _ <: V]): Function[T, V] = { (t: T) => + after.apply(apply(t)) + } - def compose[V](before: Function[_ >: V, _ <: T]): Function[V, R] = - new Function[V, R] { - override def apply(v: V): R = self.apply(before.apply(v)) - } + def compose[V](before: Function[_ >: V, _ <: T]): Function[V, R] = { (v: V) => + apply(before.apply(v)) + } } object Function { - def identity[T](): Function[T, T] = - new Function[T, T] { - override def apply(t: T): T = t - } + def identity[T](): Function[T, T] = (t: T) => t } diff --git a/javalib/src/main/scala/java/util/function/IntBinaryOperator.scala b/javalib/src/main/scala/java/util/function/IntBinaryOperator.scala new file mode 100644 index 0000000000..d5399d9c2b --- /dev/null +++ b/javalib/src/main/scala/java/util/function/IntBinaryOperator.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait IntBinaryOperator { + def applyAsInt(left: Int, right: Int): Int +} diff --git a/javalib/src/main/scala/java/util/function/IntConsumer.scala b/javalib/src/main/scala/java/util/function/IntConsumer.scala new file mode 100644 index 0000000000..b39ee7d292 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/IntConsumer.scala @@ -0,0 +1,12 @@ +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 +package java.util.function + +@FunctionalInterface +trait IntConsumer { + def accept(value: Int): Unit + + def andThen(after: IntConsumer): IntConsumer = { (value: Int) => + this.accept(value) + after.accept(value) + } +} diff --git a/javalib/src/main/scala/java/util/function/IntFunction.scala b/javalib/src/main/scala/java/util/function/IntFunction.scala new file mode 100644 index 0000000000..ba442d8e0b --- /dev/null +++ b/javalib/src/main/scala/java/util/function/IntFunction.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait IntFunction[R] { + def apply(value: Int): R +} diff --git a/javalib/src/main/scala/java/util/function/IntPredicate.scala b/javalib/src/main/scala/java/util/function/IntPredicate.scala new file mode 100644 index 0000000000..0a3491814f --- /dev/null +++ b/javalib/src/main/scala/java/util/function/IntPredicate.scala @@ -0,0 +1,30 @@ +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 +package java.util.function + +@FunctionalInterface +trait IntPredicate { self => + def test(t: Int): Boolean + + def and(other: IntPredicate): IntPredicate = { + new IntPredicate { + def test(value: Int): Boolean = + // the order and short-circuit are by-spec + self.test(value) && other.test(value) + } + } + + def negate(): IntPredicate = { + new IntPredicate { + def test(value: Int): Boolean = + !self.test(value) + } + } + + def or(other: IntPredicate): IntPredicate = { + new IntPredicate { + def test(value: Int): Boolean = + // the order and short-circuit are by-spec + self.test(value) || other.test(value) + } + } +} diff --git a/javalib/src/main/scala/java/util/function/IntSupplier.scala b/javalib/src/main/scala/java/util/function/IntSupplier.scala new file mode 100644 index 0000000000..26e62ad22f --- /dev/null +++ b/javalib/src/main/scala/java/util/function/IntSupplier.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: db63dabed dated: 2020-10-06 +package java.util.function + +@FunctionalInterface +trait IntSupplier { + def getAsInt(): Int +} diff --git a/javalib/src/main/scala/java/util/function/IntToDoubleFunction.scala b/javalib/src/main/scala/java/util/function/IntToDoubleFunction.scala new file mode 100644 index 0000000000..6d8ac63fb2 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/IntToDoubleFunction.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait IntToDoubleFunction { + def applyAsDouble(value: Int): Double +} diff --git a/javalib/src/main/scala/java/util/function/IntToLongFunction.scala b/javalib/src/main/scala/java/util/function/IntToLongFunction.scala new file mode 100644 index 0000000000..eb98ea0c49 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/IntToLongFunction.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait IntToLongFunction { + def applyAsLong(value: Int): Long +} diff --git a/javalib/src/main/scala/java/util/function/IntUnaryOperator.scala b/javalib/src/main/scala/java/util/function/IntUnaryOperator.scala index 821195415a..32d136da72 100644 --- a/javalib/src/main/scala/java/util/function/IntUnaryOperator.scala +++ b/javalib/src/main/scala/java/util/function/IntUnaryOperator.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit: d028054 dated: 2022-05-16 - +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 package java.util.function @FunctionalInterface diff --git a/javalib/src/main/scala/java/util/function/LongBinaryOperator.scala b/javalib/src/main/scala/java/util/function/LongBinaryOperator.scala new file mode 100644 index 0000000000..8d9bb34729 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/LongBinaryOperator.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait LongBinaryOperator { + def applyAsLong(left: Long, right: Long): Long +} diff --git a/javalib/src/main/scala/java/util/function/LongConsumer.scala b/javalib/src/main/scala/java/util/function/LongConsumer.scala new file mode 100644 index 0000000000..28b6c0190b --- /dev/null +++ b/javalib/src/main/scala/java/util/function/LongConsumer.scala @@ -0,0 +1,12 @@ +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 +package java.util.function + +@FunctionalInterface +trait LongConsumer { + def accept(value: Long): Unit + + def andThen(after: LongConsumer): LongConsumer = { (value: Long) => + this.accept(value) + after.accept(value) + } +} diff --git a/javalib/src/main/scala/java/util/function/LongFunction.scala b/javalib/src/main/scala/java/util/function/LongFunction.scala new file mode 100644 index 0000000000..62f53bea59 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/LongFunction.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait LongFunction[R] { + def apply(value: Long): R +} diff --git a/javalib/src/main/scala/java/util/function/LongPredicate.scala b/javalib/src/main/scala/java/util/function/LongPredicate.scala new file mode 100644 index 0000000000..c9e4179726 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/LongPredicate.scala @@ -0,0 +1,30 @@ +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 +package java.util.function + +@FunctionalInterface +trait LongPredicate { self => + def test(t: Long): Boolean + + def and(other: LongPredicate): LongPredicate = { + new LongPredicate { + def test(value: Long): Boolean = + // the order and short-circuit are by-spec + self.test(value) && other.test(value) + } + } + + def negate(): LongPredicate = { + new LongPredicate { + def test(value: Long): Boolean = + !self.test(value) + } + } + + def or(other: LongPredicate): LongPredicate = { + new LongPredicate { + def test(value: Long): Boolean = + // the order and short-circuit are by-spec + self.test(value) || other.test(value) + } + } +} diff --git a/javalib/src/main/scala/java/util/function/LongSupplier.scala b/javalib/src/main/scala/java/util/function/LongSupplier.scala new file mode 100644 index 0000000000..0bf66821ec --- /dev/null +++ b/javalib/src/main/scala/java/util/function/LongSupplier.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: db63dabed dated: 2020-10-06 +package java.util.function + +@FunctionalInterface +trait LongSupplier { + def getAsLong(): Long +} diff --git a/javalib/src/main/scala/java/util/function/LongToDoubleFunction.scala b/javalib/src/main/scala/java/util/function/LongToDoubleFunction.scala new file mode 100644 index 0000000000..d3cc1c3764 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/LongToDoubleFunction.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait LongToDoubleFunction { + def applyAsDouble(value: Long): Double +} diff --git a/javalib/src/main/scala/java/util/function/LongToIntFunction.scala b/javalib/src/main/scala/java/util/function/LongToIntFunction.scala new file mode 100644 index 0000000000..18b27e5528 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/LongToIntFunction.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait LongToIntFunction { + def applyAsInt(value: Long): Int +} diff --git a/javalib/src/main/scala/java/util/function/LongUnaryOperator.scala b/javalib/src/main/scala/java/util/function/LongUnaryOperator.scala new file mode 100644 index 0000000000..9b0c894cf3 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/LongUnaryOperator.scala @@ -0,0 +1,19 @@ +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 +package java.util.function + +@FunctionalInterface +trait LongUnaryOperator { + def applyAsLong(operand: Long): Long + + def andThen(after: LongUnaryOperator): LongUnaryOperator = { (l: Long) => + after.applyAsLong(applyAsLong(l)) + } + + def compose(before: LongUnaryOperator): LongUnaryOperator = { (l: Long) => + applyAsLong(before.applyAsLong(l)) + } +} + +object LongUnaryOperator { + def identity(): LongUnaryOperator = (l: Long) => l +} diff --git a/javalib/src/main/scala/java/util/function/ObjDoubleConsumer.scala b/javalib/src/main/scala/java/util/function/ObjDoubleConsumer.scala new file mode 100644 index 0000000000..247539f074 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/ObjDoubleConsumer.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait ObjDoubleConsumer[T] { + def accept(t: T, value: Double): Unit +} diff --git a/javalib/src/main/scala/java/util/function/ObjIntConsumer.scala b/javalib/src/main/scala/java/util/function/ObjIntConsumer.scala new file mode 100644 index 0000000000..accd3df517 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/ObjIntConsumer.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait ObjIntConsumer[T] { + def accept(t: T, value: Int): Unit +} diff --git a/javalib/src/main/scala/java/util/function/ObjLongConsumer.scala b/javalib/src/main/scala/java/util/function/ObjLongConsumer.scala new file mode 100644 index 0000000000..84b638bb55 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/ObjLongConsumer.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait ObjLongConsumer[T] { + def accept(t: T, value: Long): Unit +} diff --git a/javalib/src/main/scala/java/util/function/Predicate.scala b/javalib/src/main/scala/java/util/function/Predicate.scala index 8524858a9d..17e122d81f 100644 --- a/javalib/src/main/scala/java/util/function/Predicate.scala +++ b/javalib/src/main/scala/java/util/function/Predicate.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit: 137c11d dated: 2019-07-03 - +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 package java.util.function import java.{util => ju} diff --git a/javalib/src/main/scala/java/util/function/Supplier.scala b/javalib/src/main/scala/java/util/function/Supplier.scala index ee7bfa7a3b..726f7edd4c 100644 --- a/javalib/src/main/scala/java/util/function/Supplier.scala +++ b/javalib/src/main/scala/java/util/function/Supplier.scala @@ -1,3 +1,4 @@ +// Ported from Scala.js, commit SHA: 5df5a4142 dated: 2020-09-06 package java.util.function trait Supplier[T] { diff --git a/javalib/src/main/scala/java/util/function/ToDoubleBiFunction.scala b/javalib/src/main/scala/java/util/function/ToDoubleBiFunction.scala new file mode 100644 index 0000000000..c2e90896b4 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/ToDoubleBiFunction.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait ToDoubleBiFunction[T, U] { + def applyAsDouble(t: T, u: U): Double +} diff --git a/javalib/src/main/scala/java/util/function/ToDoubleFunction.scala b/javalib/src/main/scala/java/util/function/ToDoubleFunction.scala index 5dbc90aed0..e6275c77d9 100644 --- a/javalib/src/main/scala/java/util/function/ToDoubleFunction.scala +++ b/javalib/src/main/scala/java/util/function/ToDoubleFunction.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit 00e462d dated: 2023-01-22 - +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 package java.util.function @FunctionalInterface diff --git a/javalib/src/main/scala/java/util/function/ToIntBiFunction.scala b/javalib/src/main/scala/java/util/function/ToIntBiFunction.scala new file mode 100644 index 0000000000..f143f27afd --- /dev/null +++ b/javalib/src/main/scala/java/util/function/ToIntBiFunction.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait ToIntBiFunction[T, U] { + def applyAsInt(t: T, u: U): Int +} diff --git a/javalib/src/main/scala/java/util/function/ToIntFunction.scala b/javalib/src/main/scala/java/util/function/ToIntFunction.scala index b55440d307..fdd276fcdc 100644 --- a/javalib/src/main/scala/java/util/function/ToIntFunction.scala +++ b/javalib/src/main/scala/java/util/function/ToIntFunction.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit 00e462d dated: 2023-01-22 - +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 package java.util.function @FunctionalInterface diff --git a/javalib/src/main/scala/java/util/function/ToLongBiFunction.scala b/javalib/src/main/scala/java/util/function/ToLongBiFunction.scala new file mode 100644 index 0000000000..d61254c7ad --- /dev/null +++ b/javalib/src/main/scala/java/util/function/ToLongBiFunction.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait ToLongBiFunction[T, U] { + def applyAsLong(t: T, u: U): Long +} diff --git a/javalib/src/main/scala/java/util/function/ToLongFunction.scala b/javalib/src/main/scala/java/util/function/ToLongFunction.scala index b1847833ac..4c7c00f253 100644 --- a/javalib/src/main/scala/java/util/function/ToLongFunction.scala +++ b/javalib/src/main/scala/java/util/function/ToLongFunction.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit 00e462d dated: 2023-01-22 - +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 package java.util.function @FunctionalInterface diff --git a/javalib/src/main/scala/java/util/function/UnaryOperator.scala b/javalib/src/main/scala/java/util/function/UnaryOperator.scala index ee62e672ab..5b734f6ca6 100644 --- a/javalib/src/main/scala/java/util/function/UnaryOperator.scala +++ b/javalib/src/main/scala/java/util/function/UnaryOperator.scala @@ -1,10 +1,8 @@ +// Ported from Scala.js, commit SHA: 4a394815e dated: 2020-09-06 package java.util.function -trait UnaryOperator[T] extends Function[T, T] { self => } +trait UnaryOperator[T] extends Function[T, T] object UnaryOperator { - def identity[T](): UnaryOperator[T] = - new UnaryOperator[T] { - override def apply(t: T): T = t - } + def identity[T](): UnaryOperator[T] = (t: T) => t } diff --git a/javalib/src/main/scala/java/util/zip/InflaterInputStream.scala b/javalib/src/main/scala/java/util/zip/InflaterInputStream.scala index 464a5d7d16..ebb4481b69 100644 --- a/javalib/src/main/scala/java/util/zip/InflaterInputStream.scala +++ b/javalib/src/main/scala/java/util/zip/InflaterInputStream.scala @@ -9,8 +9,18 @@ class InflaterInputStream private ( protected var inf: Inflater, protected var buf: Array[Byte] ) extends FilterInputStream(in) { - def this(in: InputStream, inf: Inflater, len: Int) = - this(in, inf, new Array[Byte](len)) + def this(in: InputStream, inf: Inflater, len: Int) = { + this( + in, + inf, { + if (len <= 0) { + throw new IllegalArgumentException() + } + new Array[Byte](len) + } + ) + } + def this(in: InputStream, inf: Inflater) = this(in, inf, InflaterInputStream.BUF_SIZE) def this(in: InputStream) = this(in, new Inflater()) diff --git a/javalib/src/main/scala/java/util/zip/InflaterOutputStream.scala b/javalib/src/main/scala/java/util/zip/InflaterOutputStream.scala index 104f29e865..c83118ec91 100644 --- a/javalib/src/main/scala/java/util/zip/InflaterOutputStream.scala +++ b/javalib/src/main/scala/java/util/zip/InflaterOutputStream.scala @@ -16,7 +16,13 @@ class InflaterOutputStream private ( private var closed = false def this(out: OutputStream, inf: Inflater, bufferSize: Int) = { - this(out, inf, new Array[Byte](bufferSize)) + this( + out, + inf, + if (bufferSize <= 0) + throw new IllegalArgumentException("bufferSize <= 0: " + bufferSize) + else new Array[Byte](bufferSize) + ) if (out == null) { throw new NullPointerException("out == null") } else if (inf == null) { diff --git a/javalib/src/main/scala/scala/scalanative/nio/fs/FileHelpers.scala b/javalib/src/main/scala/scala/scalanative/nio/fs/FileHelpers.scala index 4ea41ffebf..d7c8e2deb4 100644 --- a/javalib/src/main/scala/scala/scalanative/nio/fs/FileHelpers.scala +++ b/javalib/src/main/scala/scala/scalanative/nio/fs/FileHelpers.scala @@ -9,8 +9,11 @@ import scalanative.unsafe._, stdlib._, stdio._, string._ import scalanative.meta.LinktimeInfo.isWindows import scala.collection.mutable.UnrolledBuffer import scala.reflect.ClassTag + import java.io.{File, IOException} import java.nio.charset.StandardCharsets +import java.{util => ju} + import scala.scalanative.windows._ import scala.scalanative.windows.HandleApiExt.INVALID_HANDLE_VALUE import scala.scalanative.windows.FileApi._ @@ -203,21 +206,14 @@ object FileHelpers { } lazy val tempDir: String = { - if (isWindows) { - val buffer: Ptr[WChar] = stackalloc[WChar](MAX_PATH) - GetTempPathW(MAX_PATH, buffer) - fromCWideString(buffer, StandardCharsets.UTF_16LE) - } else { - val dir = getenv(c"TMPDIR") - if (dir == null) { - System.getProperty("java.io.tmpdir") match { - case null => "/tmp" - case d => d - } - } else { - fromCString(dir) - } - } + val propertyName = "java.io.tmpdir" + // set at first lookup after program start. + val dir = System.getProperty(propertyName) + ju.Objects.requireNonNull( + dir, + s"Required Java System property ${propertyName} is not defined." + ) + dir } private def genTempFile( diff --git a/nativelib/src/main/resources/scala-native/gc/commix/Marker.c b/nativelib/src/main/resources/scala-native/gc/commix/Marker.c index feb2200fdc..209e7f626c 100644 --- a/nativelib/src/main/resources/scala-native/gc/commix/Marker.c +++ b/nativelib/src/main/resources/scala-native/gc/commix/Marker.c @@ -143,7 +143,10 @@ int Marker_markRange(Heap *heap, Stats *stats, GreyPacket **outHolder, word_t **limit = fields + length; for (word_t **current = fields; current < limit; current++) { word_t *field = *current; - if (Heap_IsWordInHeap(heap, field)) { + // Memory allocated by GC is alligned, ignore unaligned pointers e.g. + // interim pointers, otherwise we risk undefined behaviour when assuming + // memory layout of underlying object. + if (Heap_IsWordInHeap(heap, field) && Bytemap_isPtrAligned(field)) { ObjectMeta *fieldMeta = Bytemap_Get(bytemap, field); if (ObjectMeta_IsAllocated(fieldMeta)) { Marker_markObject(heap, stats, outHolder, outWeakRefHolder, @@ -386,11 +389,10 @@ void Marker_markProgramStack(Heap *heap, Stats *stats, GreyPacket **outHolder, word_t **stackBottom = __stack_bottom; while (current <= stackBottom) { - - word_t *stackObject = *current; - if (Heap_IsWordInHeap(heap, stackObject)) { + word_t *obj = *current; + if (Heap_IsWordInHeap(heap, obj) && Bytemap_isPtrAligned(obj)) { Marker_markConservative(heap, stats, outHolder, outWeakRefHolder, - stackObject); + obj); } current += 1; } diff --git a/nativelib/src/main/resources/scala-native/gc/commix/datastructures/Bytemap.h b/nativelib/src/main/resources/scala-native/gc/commix/datastructures/Bytemap.h index 88369ead0a..e67c0cd543 100644 --- a/nativelib/src/main/resources/scala-native/gc/commix/datastructures/Bytemap.h +++ b/nativelib/src/main/resources/scala-native/gc/commix/datastructures/Bytemap.h @@ -18,23 +18,22 @@ typedef struct { void Bytemap_Init(Bytemap *bytemap, word_t *firstAddress, size_t size); +static inline bool Bytemap_isPtrAligned(word_t *address) { + word_t aligned = ((word_t)address & ALLOCATION_ALIGNMENT_INVERSE_MASK); + return (word_t *)aligned == address; +} + static inline size_t Bytemap_index(Bytemap *bytemap, word_t *address) { size_t index = (address - bytemap->firstAddress) / ALLOCATION_ALIGNMENT_WORDS; assert(address >= bytemap->firstAddress); assert(index < bytemap->size); - assert(((word_t)address & ALLOCATION_ALIGNMENT_INVERSE_MASK) == - (word_t)address); + assert(Bytemap_isPtrAligned(address)); return index; } static inline ObjectMeta *Bytemap_Get(Bytemap *bytemap, word_t *address) { - size_t index = - (address - bytemap->firstAddress) / ALLOCATION_ALIGNMENT_WORDS; - assert(address >= bytemap->firstAddress); - assert(index < bytemap->size); - assert(((word_t)address & ALLOCATION_ALIGNMENT_INVERSE_MASK) == - (word_t)address); + size_t index = Bytemap_index(bytemap, address); return &bytemap->data[index]; } diff --git a/nativelib/src/main/resources/scala-native/gc/immix/Marker.c b/nativelib/src/main/resources/scala-native/gc/immix/Marker.c index af331abd45..ca0fcf9616 100644 --- a/nativelib/src/main/resources/scala-native/gc/immix/Marker.c +++ b/nativelib/src/main/resources/scala-native/gc/immix/Marker.c @@ -18,6 +18,7 @@ extern word_t **__stack_bottom; void Marker_markObject(Heap *heap, Stack *stack, Bytemap *bytemap, Object *object, ObjectMeta *objectMeta) { assert(ObjectMeta_IsAllocated(objectMeta)); + assert(object->rtti != NULL); if (Object_IsWeakReference(object)) { // Added to the WeakReference stack for additional later visit @@ -29,6 +30,16 @@ void Marker_markObject(Heap *heap, Stack *stack, Bytemap *bytemap, Stack_Push(stack, object); } +static inline void Marker_markField(Heap *heap, Stack *stack, Field_t field) { + if (Heap_IsWordInHeap(heap, field)) { + ObjectMeta *fieldMeta = Bytemap_Get(heap->bytemap, field); + if (ObjectMeta_IsAllocated(fieldMeta)) { + Object *object = (Object *)field; + Marker_markObject(heap, stack, heap->bytemap, object, fieldMeta); + } + } +} + void Marker_markConservative(Heap *heap, Stack *stack, word_t *address) { assert(Heap_IsWordInHeap(heap, address)); Object *object = Object_GetUnmarkedObject(heap, address); @@ -46,21 +57,13 @@ void Marker_Mark(Heap *heap, Stack *stack) { Bytemap *bytemap = heap->bytemap; while (!Stack_IsEmpty(stack)) { Object *object = Stack_Pop(stack); - if (Object_IsArray(object)) { if (object->rtti->rt.id == __object_array_id) { ArrayHeader *arrayHeader = (ArrayHeader *)object; size_t length = arrayHeader->length; word_t **fields = (word_t **)(arrayHeader + 1); for (int i = 0; i < length; i++) { - word_t *field = fields[i]; - if (Heap_IsWordInHeap(heap, field)) { - ObjectMeta *fieldMeta = Bytemap_Get(bytemap, field); - if (ObjectMeta_IsAllocated(fieldMeta)) { - Marker_markObject(heap, stack, bytemap, - (Object *)field, fieldMeta); - } - } + Marker_markField(heap, stack, fields[i]); } } // non-object arrays do not contain pointers @@ -69,37 +72,34 @@ void Marker_Mark(Heap *heap, Stack *stack) { for (int i = 0; ptr_map[i] != LAST_FIELD_OFFSET; i++) { if (Object_IsReferantOfWeakReference(object, ptr_map[i])) continue; - - word_t *field = object->fields[ptr_map[i]]; - if (Heap_IsWordInHeap(heap, field)) { - ObjectMeta *fieldMeta = Bytemap_Get(bytemap, field); - if (ObjectMeta_IsAllocated(fieldMeta)) { - Marker_markObject(heap, stack, bytemap, (Object *)field, - fieldMeta); - } - } + Marker_markField(heap, stack, object->fields[ptr_map[i]]); } } } } +NO_SANITIZE void Marker_markRange(Heap *heap, Stack *stack, word_t **from, + word_t **to) { + assert(from != NULL); + assert(to != NULL); + for (word_t **current = from; current <= to; current += 1) { + word_t *addr = *current; + if (Heap_IsWordInHeap(heap, addr) && Bytemap_isPtrAligned(addr)) { + Marker_markConservative(heap, stack, addr); + } + } +} + void Marker_markProgramStack(Heap *heap, Stack *stack) { // Dumps registers into 'regs' which is on stack jmp_buf regs; setjmp(regs); word_t *dummy; - word_t **current = &dummy; + word_t **stackTop = &dummy; word_t **stackBottom = __stack_bottom; - while (current <= stackBottom) { - - word_t *stackObject = *current; - if (Heap_IsWordInHeap(heap, stackObject)) { - Marker_markConservative(heap, stack, stackObject); - } - current += 1; - } + Marker_markRange(heap, stack, stackTop, stackBottom); } void Marker_markModules(Heap *heap, Stack *stack) { @@ -108,13 +108,7 @@ void Marker_markModules(Heap *heap, Stack *stack) { Bytemap *bytemap = heap->bytemap; for (int i = 0; i < nb_modules; i++) { Object *object = (Object *)modules[i]; - if (Heap_IsWordInHeap(heap, (word_t *)object)) { - // is within heap - ObjectMeta *objectMeta = Bytemap_Get(bytemap, (word_t *)object); - if (ObjectMeta_IsAllocated(objectMeta)) { - Marker_markObject(heap, stack, bytemap, object, objectMeta); - } - } + Marker_markField(heap, stack, (Field_t)object); } } diff --git a/nativelib/src/main/resources/scala-native/gc/immix/datastructures/Bytemap.h b/nativelib/src/main/resources/scala-native/gc/immix/datastructures/Bytemap.h index 79859ae48c..e67c0cd543 100644 --- a/nativelib/src/main/resources/scala-native/gc/immix/datastructures/Bytemap.h +++ b/nativelib/src/main/resources/scala-native/gc/immix/datastructures/Bytemap.h @@ -5,8 +5,8 @@ #include #include #include "GCTypes.h" -#include "Log.h" #include "../Constants.h" +#include "Log.h" #include "../metadata/ObjectMeta.h" typedef struct { @@ -18,23 +18,22 @@ typedef struct { void Bytemap_Init(Bytemap *bytemap, word_t *firstAddress, size_t size); +static inline bool Bytemap_isPtrAligned(word_t *address) { + word_t aligned = ((word_t)address & ALLOCATION_ALIGNMENT_INVERSE_MASK); + return (word_t *)aligned == address; +} + static inline size_t Bytemap_index(Bytemap *bytemap, word_t *address) { size_t index = (address - bytemap->firstAddress) / ALLOCATION_ALIGNMENT_WORDS; assert(address >= bytemap->firstAddress); assert(index < bytemap->size); - assert(((word_t)address & ALLOCATION_ALIGNMENT_INVERSE_MASK) == - (word_t)address); + assert(Bytemap_isPtrAligned(address)); return index; } static inline ObjectMeta *Bytemap_Get(Bytemap *bytemap, word_t *address) { - size_t index = - (address - bytemap->firstAddress) / ALLOCATION_ALIGNMENT_WORDS; - assert(address >= bytemap->firstAddress); - assert(index < bytemap->size); - assert(((word_t)address & ALLOCATION_ALIGNMENT_INVERSE_MASK) == - (word_t)address); + size_t index = Bytemap_index(bytemap, address); return &bytemap->data[index]; } diff --git a/nativelib/src/main/resources/scala-native/gc/immix_commix/StackTrace.c b/nativelib/src/main/resources/scala-native/gc/immix_commix/StackTrace.c index 86be060c91..2618f0bd56 100644 --- a/nativelib/src/main/resources/scala-native/gc/immix_commix/StackTrace.c +++ b/nativelib/src/main/resources/scala-native/gc/immix_commix/StackTrace.c @@ -1,26 +1,27 @@ #include +#include +#include #include "StackTrace.h" void StackTrace_PrintStackTrace() { -#if defined(_WIN32) - printf("Stacktrace not implemented in Windows\n"); -#else - unw_cursor_t cursor; - unw_context_t context; - unw_getcontext(&context); - unw_init_local(&cursor, &context); + void *cursor = malloc(scalanative_unwind_sizeof_cursor()); + void *context = malloc(scalanative_unwind_sizeof_context()); + scalanative_unwind_get_context(context); + scalanative_unwind_init_local(cursor, context); - while (unw_step(&cursor) > 0) { - unw_word_t offset, pc; - unw_get_reg(&cursor, UNW_REG_IP, &pc); + while (scalanative_unwind_step(cursor) > 0) { + uintptr_t offset, pc; + scalanative_unwind_get_reg(cursor, scalanative_unw_reg_ip(), &pc); if (pc == 0) { break; } char sym[256]; - if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) { + if (scalanative_unwind_get_proc_name(cursor, sym, sizeof(sym), + &offset) == 0) { printf("\tat %s\n", sym); } } -#endif + free(cursor); + free(context); } \ No newline at end of file diff --git a/nativelib/src/main/resources/scala-native/gc/immix_commix/StackTrace.h b/nativelib/src/main/resources/scala-native/gc/immix_commix/StackTrace.h index ed529875ae..ce5e33f4e0 100644 --- a/nativelib/src/main/resources/scala-native/gc/immix_commix/StackTrace.h +++ b/nativelib/src/main/resources/scala-native/gc/immix_commix/StackTrace.h @@ -1,9 +1,7 @@ #ifndef IMMIX_STACKTRACE_H #define IMMIX_STACKTRACE_H -#ifndef _WIN32 -#include "../../platform/posix/libunwind/libunwind.h" -#endif +#include "../../platform/unwind.h" void StackTrace_PrintStackTrace(); diff --git a/nativelib/src/main/resources/scala-native/gc/shared/GCTypes.h b/nativelib/src/main/resources/scala-native/gc/shared/GCTypes.h index bd4ca2998f..396d69b62a 100644 --- a/nativelib/src/main/resources/scala-native/gc/shared/GCTypes.h +++ b/nativelib/src/main/resources/scala-native/gc/shared/GCTypes.h @@ -6,6 +6,16 @@ #define NOINLINE __attribute__((noinline)) #define INLINE __attribute__((always_inline)) +#if defined(__has_feature) +#if __has_feature(address_sanitizer) +#define NO_SANITIZE __attribute__((no_sanitize("address"))) +#endif +#endif + +#ifndef NO_SANITIZE +#define NO_SANITIZE +#endif + #define UNLIKELY(b) __builtin_expect((b), 0) #define LIKELY(b) __builtin_expect((b), 1) diff --git a/nativelib/src/main/resources/scala-native/platform/platform.c b/nativelib/src/main/resources/scala-native/platform/platform.c index 6d58eb9f3b..fa9f09a7c9 100644 --- a/nativelib/src/main/resources/scala-native/platform/platform.c +++ b/nativelib/src/main/resources/scala-native/platform/platform.c @@ -103,6 +103,14 @@ char *scalanative_windows_get_user_country() { return ""; } +int scalanative_platform_is_msys() { +#ifdef __MSYS__ + return 1; +#else + return 0; +#endif +} + // See http://stackoverflow.com/a/4181991 int scalanative_little_endian() { int n = 1; diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/AddressSpace.hpp b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/AddressSpace.hpp index ecea81e9bc..841434074f 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/AddressSpace.hpp +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/AddressSpace.hpp @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===------------------------- AddressSpace.hpp ---------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -27,7 +27,7 @@ #include "Registers.hpp" #ifndef _LIBUNWIND_USE_DLADDR - #if !defined(_LIBUNWIND_IS_BAREMETAL) && !defined(_WIN32) + #if !(defined(_LIBUNWIND_IS_BAREMETAL) || defined(_WIN32) || defined(_AIX)) #define _LIBUNWIND_USE_DLADDR 1 #else #define _LIBUNWIND_USE_DLADDR 0 @@ -48,6 +48,13 @@ struct EHABIIndexEntry { }; #endif +#if defined(_AIX) +namespace libunwind { +char *getFuncNameFromTBTable(uintptr_t pc, uint16_t &NameLen, + unw_word_t *offset); +} +#endif + #ifdef __APPLE__ struct dyld_unwind_sections @@ -124,23 +131,23 @@ struct UnwindInfoSections { uintptr_t dso_base; #endif #if defined(_LIBUNWIND_USE_DL_ITERATE_PHDR) - uintptr_t text_segment_length; + size_t text_segment_length; #endif #if defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) uintptr_t dwarf_section; - uintptr_t dwarf_section_length; + size_t dwarf_section_length; #endif #if defined(_LIBUNWIND_SUPPORT_DWARF_INDEX) uintptr_t dwarf_index_section; - uintptr_t dwarf_index_section_length; + size_t dwarf_index_section_length; #endif #if defined(_LIBUNWIND_SUPPORT_COMPACT_UNWIND) uintptr_t compact_unwind_section; - uintptr_t compact_unwind_section_length; + size_t compact_unwind_section_length; #endif #if defined(_LIBUNWIND_ARM_EHABI) uintptr_t arm_section; - uintptr_t arm_section_length; + size_t arm_section_length; #endif }; @@ -433,7 +440,7 @@ static bool checkForUnwindInfoSegment(const Elf_Phdr *phdr, size_t image_base, // .eh_frame_hdr records the start of .eh_frame, but not its size. // Rely on a zero terminator to find the end of the section. cbdata->sects->dwarf_section = hdrInfo.eh_frame_ptr; - cbdata->sects->dwarf_section_length = UINTPTR_MAX; + cbdata->sects->dwarf_section_length = SIZE_MAX; return true; } } @@ -509,22 +516,22 @@ inline bool LocalAddressSpace::findUnwindSections(pint_t targetAddr, info.dso_base = (uintptr_t)dyldInfo.mh; #if defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) info.dwarf_section = (uintptr_t)dyldInfo.dwarf_section; - info.dwarf_section_length = dyldInfo.dwarf_section_length; + info.dwarf_section_length = (size_t)dyldInfo.dwarf_section_length; #endif info.compact_unwind_section = (uintptr_t)dyldInfo.compact_unwind_section; - info.compact_unwind_section_length = dyldInfo.compact_unwind_section_length; + info.compact_unwind_section_length = (size_t)dyldInfo.compact_unwind_section_length; return true; } #elif defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) && defined(_LIBUNWIND_IS_BAREMETAL) info.dso_base = 0; // Bare metal is statically linked, so no need to ask the dynamic loader - info.dwarf_section_length = (uintptr_t)(&__eh_frame_end - &__eh_frame_start); + info.dwarf_section_length = (size_t)(&__eh_frame_end - &__eh_frame_start); info.dwarf_section = (uintptr_t)(&__eh_frame_start); _LIBUNWIND_TRACE_UNWINDING("findUnwindSections: section %p length %p", (void *)info.dwarf_section, (void *)info.dwarf_section_length); #if defined(_LIBUNWIND_SUPPORT_DWARF_INDEX) info.dwarf_index_section = (uintptr_t)(&__eh_frame_hdr_start); - info.dwarf_index_section_length = (uintptr_t)(&__eh_frame_hdr_end - &__eh_frame_hdr_start); + info.dwarf_index_section_length = (size_t)(&__eh_frame_hdr_end - &__eh_frame_hdr_start); _LIBUNWIND_TRACE_UNWINDING("findUnwindSections: index section %p length %p", (void *)info.dwarf_index_section, (void *)info.dwarf_index_section_length); #endif @@ -533,7 +540,7 @@ inline bool LocalAddressSpace::findUnwindSections(pint_t targetAddr, #elif defined(_LIBUNWIND_ARM_EHABI) && defined(_LIBUNWIND_IS_BAREMETAL) // Bare metal is statically linked, so no need to ask the dynamic loader info.arm_section = (uintptr_t)(&__exidx_start); - info.arm_section_length = (uintptr_t)(&__exidx_end - &__exidx_start); + info.arm_section_length = (size_t)(&__exidx_end - &__exidx_start); _LIBUNWIND_TRACE_UNWINDING("findUnwindSections: section %p length %p", (void *)info.arm_section, (void *)info.arm_section_length); if (info.arm_section && info.arm_section_length) @@ -547,6 +554,7 @@ inline bool LocalAddressSpace::findUnwindSections(pint_t targetAddr, DWORD err = GetLastError(); _LIBUNWIND_TRACE_UNWINDING("findUnwindSections: EnumProcessModules failed, " "returned error %d", (int)err); + (void)err; return false; } @@ -583,11 +591,16 @@ inline bool LocalAddressSpace::findUnwindSections(pint_t targetAddr, (void)targetAddr; (void)info; return true; +#elif defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND) + // The traceback table is used for unwinding. + (void)targetAddr; + (void)info; + return true; #elif defined(_LIBUNWIND_USE_DL_UNWIND_FIND_EXIDX) int length = 0; info.arm_section = (uintptr_t)dl_unwind_find_exidx((_Unwind_Ptr)targetAddr, &length); - info.arm_section_length = (uintptr_t)length * sizeof(EHABIIndexEntry); + info.arm_section_length = (size_t)length * sizeof(EHABIIndexEntry); if (info.arm_section && info.arm_section_length) return true; #elif defined(_LIBUNWIND_USE_DL_ITERATE_PHDR) @@ -599,7 +612,6 @@ inline bool LocalAddressSpace::findUnwindSections(pint_t targetAddr, return false; } - inline bool LocalAddressSpace::findOtherFDE(pint_t targetAddr, pint_t &fde) { // TO DO: if OS has way to dynamically register FDEs, check that. (void)targetAddr; @@ -619,6 +631,13 @@ inline bool LocalAddressSpace::findFunctionName(pint_t addr, char *buf, return true; } } +#elif defined(_AIX) + uint16_t nameLen; + char *funcName = getFuncNameFromTBTable(addr, nameLen, offset); + if (funcName != NULL) { + snprintf(buf, bufLen, "%.*s", nameLen, funcName); + return true; + } #else (void)addr; (void)buf; diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/CompactUnwinder.hpp b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/CompactUnwinder.hpp index b882bd0eef..fec1004b81 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/CompactUnwinder.hpp +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/CompactUnwinder.hpp @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===-------------------------- CompactUnwinder.hpp -----------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -540,65 +540,65 @@ int CompactUnwinder_arm64::stepWithCompactEncodingFrameless( uint64_t savedRegisterLoc = registers.getSP() + stackSize; if (encoding & UNWIND_ARM64_FRAME_X19_X20_PAIR) { - registers.setRegister(UNW_ARM64_X19, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X19, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setRegister(UNW_ARM64_X20, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X20, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_X21_X22_PAIR) { - registers.setRegister(UNW_ARM64_X21, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X21, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setRegister(UNW_ARM64_X22, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X22, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_X23_X24_PAIR) { - registers.setRegister(UNW_ARM64_X23, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X23, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setRegister(UNW_ARM64_X24, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X24, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_X25_X26_PAIR) { - registers.setRegister(UNW_ARM64_X25, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X25, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setRegister(UNW_ARM64_X26, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X26, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_X27_X28_PAIR) { - registers.setRegister(UNW_ARM64_X27, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X27, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setRegister(UNW_ARM64_X28, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X28, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_D8_D9_PAIR) { - registers.setFloatRegister(UNW_ARM64_D8, + registers.setFloatRegister(UNW_AARCH64_V8, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setFloatRegister(UNW_ARM64_D9, + registers.setFloatRegister(UNW_AARCH64_V9, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_D10_D11_PAIR) { - registers.setFloatRegister(UNW_ARM64_D10, + registers.setFloatRegister(UNW_AARCH64_V10, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setFloatRegister(UNW_ARM64_D11, + registers.setFloatRegister(UNW_AARCH64_V11, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_D12_D13_PAIR) { - registers.setFloatRegister(UNW_ARM64_D12, + registers.setFloatRegister(UNW_AARCH64_V12, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setFloatRegister(UNW_ARM64_D13, + registers.setFloatRegister(UNW_AARCH64_V13, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_D14_D15_PAIR) { - registers.setFloatRegister(UNW_ARM64_D14, + registers.setFloatRegister(UNW_AARCH64_V14, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setFloatRegister(UNW_ARM64_D15, + registers.setFloatRegister(UNW_AARCH64_V15, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; } @@ -607,7 +607,7 @@ int CompactUnwinder_arm64::stepWithCompactEncodingFrameless( registers.setSP(savedRegisterLoc); // set pc to be value in lr - registers.setIP(registers.getRegister(UNW_ARM64_LR)); + registers.setIP(registers.getRegister(UNW_AARCH64_LR)); return UNW_STEP_SUCCESS; } @@ -619,65 +619,65 @@ int CompactUnwinder_arm64::stepWithCompactEncodingFrame( uint64_t savedRegisterLoc = registers.getFP() - 8; if (encoding & UNWIND_ARM64_FRAME_X19_X20_PAIR) { - registers.setRegister(UNW_ARM64_X19, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X19, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setRegister(UNW_ARM64_X20, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X20, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_X21_X22_PAIR) { - registers.setRegister(UNW_ARM64_X21, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X21, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setRegister(UNW_ARM64_X22, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X22, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_X23_X24_PAIR) { - registers.setRegister(UNW_ARM64_X23, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X23, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setRegister(UNW_ARM64_X24, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X24, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_X25_X26_PAIR) { - registers.setRegister(UNW_ARM64_X25, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X25, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setRegister(UNW_ARM64_X26, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X26, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_X27_X28_PAIR) { - registers.setRegister(UNW_ARM64_X27, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X27, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setRegister(UNW_ARM64_X28, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X28, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_D8_D9_PAIR) { - registers.setFloatRegister(UNW_ARM64_D8, + registers.setFloatRegister(UNW_AARCH64_V8, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setFloatRegister(UNW_ARM64_D9, + registers.setFloatRegister(UNW_AARCH64_V9, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_D10_D11_PAIR) { - registers.setFloatRegister(UNW_ARM64_D10, + registers.setFloatRegister(UNW_AARCH64_V10, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setFloatRegister(UNW_ARM64_D11, + registers.setFloatRegister(UNW_AARCH64_V11, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_D12_D13_PAIR) { - registers.setFloatRegister(UNW_ARM64_D12, + registers.setFloatRegister(UNW_AARCH64_V12, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setFloatRegister(UNW_ARM64_D13, + registers.setFloatRegister(UNW_AARCH64_V13, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_D14_D15_PAIR) { - registers.setFloatRegister(UNW_ARM64_D14, + registers.setFloatRegister(UNW_AARCH64_V14, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setFloatRegister(UNW_ARM64_D15, + registers.setFloatRegister(UNW_AARCH64_V15, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; } diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/DwarfInstructions.hpp b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/DwarfInstructions.hpp index 9c9c9d1d99..20b6e9bb14 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/DwarfInstructions.hpp +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/DwarfInstructions.hpp @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===-------------------------- DwarfInstructions.hpp ---------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -75,8 +75,19 @@ class DwarfInstructions { assert(0 && "getCFA(): unknown location"); __builtin_unreachable(); } +#if defined(_LIBUNWIND_TARGET_AARCH64) + static bool getRA_SIGN_STATE(A &addressSpace, R registers, pint_t cfa, + PrologInfo &prolog); +#endif }; +template +auto getSparcWCookie(const R &r, int) -> decltype(r.getWCookie()) { + return r.getWCookie(); +} +template uint64_t getSparcWCookie(const R &, long) { + return 0; +} template typename A::pint_t DwarfInstructions::getSavedRegister( @@ -86,6 +97,10 @@ typename A::pint_t DwarfInstructions::getSavedRegister( case CFI_Parser::kRegisterInCFA: return (pint_t)addressSpace.getRegister(cfa + (pint_t)savedReg.value); + case CFI_Parser::kRegisterInCFADecrypt: // sparc64 specific + return (pint_t)(addressSpace.getP(cfa + (pint_t)savedReg.value) ^ + getSparcWCookie(registers, 0)); + case CFI_Parser::kRegisterAtExpression: return (pint_t)addressSpace.getRegister(evaluateExpression( (pint_t)savedReg.value, addressSpace, registers, cfa)); @@ -118,12 +133,16 @@ double DwarfInstructions::getSavedFloatRegister( return addressSpace.getDouble( evaluateExpression((pint_t)savedReg.value, addressSpace, registers, cfa)); - + case CFI_Parser::kRegisterUndefined: + return 0.0; + case CFI_Parser::kRegisterInRegister: +#ifndef _LIBUNWIND_TARGET_ARM + return registers.getFloatRegister((int)savedReg.value); +#endif case CFI_Parser::kRegisterIsExpression: case CFI_Parser::kRegisterUnused: - case CFI_Parser::kRegisterUndefined: case CFI_Parser::kRegisterOffsetFromCFA: - case CFI_Parser::kRegisterInRegister: + case CFI_Parser::kRegisterInCFADecrypt: // FIX ME break; } @@ -148,11 +167,27 @@ v128 DwarfInstructions::getSavedVectorRegister( case CFI_Parser::kRegisterUndefined: case CFI_Parser::kRegisterOffsetFromCFA: case CFI_Parser::kRegisterInRegister: + case CFI_Parser::kRegisterInCFADecrypt: // FIX ME break; } _LIBUNWIND_ABORT("unsupported restore location for vector register"); } +#if defined(_LIBUNWIND_TARGET_AARCH64) +template +bool DwarfInstructions::getRA_SIGN_STATE(A &addressSpace, R registers, + pint_t cfa, PrologInfo &prolog) { + pint_t raSignState; + auto regloc = prolog.savedRegisters[UNW_AARCH64_RA_SIGN_STATE]; + if (regloc.location == CFI_Parser::kRegisterUnused) + raSignState = static_cast(regloc.value); + else + raSignState = getSavedRegister(addressSpace, registers, cfa, regloc); + + // Only bit[0] is meaningful. + return raSignState & 0x01; +} +#endif template int DwarfInstructions::stepWithDwarf(A &addressSpace, pint_t pc, @@ -170,10 +205,21 @@ int DwarfInstructions::stepWithDwarf(A &addressSpace, pint_t pc, // restore registers that DWARF says were saved R newRegisters = registers; + + // Typically, the CFA is the stack pointer at the call site in + // the previous frame. However, there are scenarios in which this is not + // true. For example, if we switched to a new stack. In that case, the + // value of the previous SP might be indicated by a CFI directive. + // + // We set the SP here to the CFA, allowing for it to be overridden + // by a CFI directive later on. + newRegisters.setSP(cfa); + pint_t returnAddress = 0; - const int lastReg = R::lastDwarfRegNum(); - assert(static_cast(CFI_Parser::kMaxRegisterNumber) >= lastReg && - "register range too large"); + constexpr int lastReg = R::lastDwarfRegNum(); + static_assert(static_cast(CFI_Parser::kMaxRegisterNumber) >= + lastReg, + "register range too large"); assert(lastReg >= (int)cieInfo.returnAddressRegister && "register range does not contain return address register"); for (int i = 0; i <= lastReg; ++i) { @@ -203,10 +249,6 @@ int DwarfInstructions::stepWithDwarf(A &addressSpace, pint_t pc, } } - // By definition, the CFA is the stack pointer at the call site, so - // restoring SP means setting it to CFA. - newRegisters.setSP(cfa); - isSignalFrame = cieInfo.isSignalFrame; #if defined(_LIBUNWIND_TARGET_AARCH64) @@ -216,7 +258,8 @@ int DwarfInstructions::stepWithDwarf(A &addressSpace, pint_t pc, // restored. autia1716 is used instead of autia as autia1716 assembles // to a NOP on pre-v8.3a architectures. if ((R::getArch() == REGISTERS_ARM64) && - prolog.savedRegisters[UNW_ARM64_RA_SIGN_STATE].value) { + getRA_SIGN_STATE(addressSpace, registers, cfa, prolog) && + returnAddress != 0) { #if !defined(_LIBUNWIND_IS_NATIVE_ONLY) return UNW_ECROSSRASIGNING; #else @@ -235,6 +278,20 @@ int DwarfInstructions::stepWithDwarf(A &addressSpace, pint_t pc, } #endif +#if defined(_LIBUNWIND_IS_NATIVE_ONLY) && defined(_LIBUNWIND_TARGET_ARM) && \ + defined(__ARM_FEATURE_PAUTH) + if ((R::getArch() == REGISTERS_ARM) && + prolog.savedRegisters[UNW_ARM_RA_AUTH_CODE].value) { + pint_t pac = + getSavedRegister(addressSpace, registers, cfa, + prolog.savedRegisters[UNW_ARM_RA_AUTH_CODE]); + __asm__ __volatile__("autg %0, %1, %2" + : + : "r"(pac), "r"(returnAddress), "r"(cfa) + :); + } +#endif + #if defined(_LIBUNWIND_TARGET_SPARC) if (R::getArch() == REGISTERS_SPARC) { // Skip call site instruction and delay slot @@ -245,6 +302,12 @@ int DwarfInstructions::stepWithDwarf(A &addressSpace, pint_t pc, } #endif +#if defined(_LIBUNWIND_TARGET_SPARC64) + // Skip call site instruction and delay slot. + if (R::getArch() == REGISTERS_SPARC64) + returnAddress += 8; +#endif + #if defined(_LIBUNWIND_TARGET_PPC64) #define PPC64_ELFV1_R2_LOAD_INST_ENCODING 0xe8410028u // ld r2,40(r1) #define PPC64_ELFV1_R2_OFFSET 40 diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/DwarfParser.hpp b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/DwarfParser.hpp index a69e00aa37..439172197a 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/DwarfParser.hpp +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/DwarfParser.hpp @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===--------------------------- DwarfParser.hpp --------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -74,6 +74,7 @@ class CFI_Parser { kRegisterUnused, kRegisterUndefined, kRegisterInCFA, + kRegisterInCFADecrypt, // sparc64 specific kRegisterOffsetFromCFA, kRegisterInRegister, kRegisterAtExpression, @@ -154,10 +155,11 @@ class CFI_Parser { }; static bool findFDE(A &addressSpace, pint_t pc, pint_t ehSectionStart, - uintptr_t sectionLength, pint_t fdeHint, FDE_Info *fdeInfo, + size_t sectionLength, pint_t fdeHint, FDE_Info *fdeInfo, CIE_Info *cieInfo); static const char *decodeFDE(A &addressSpace, pint_t fdeStart, - FDE_Info *fdeInfo, CIE_Info *cieInfo); + FDE_Info *fdeInfo, CIE_Info *cieInfo, + bool useCIEInfo = false); static bool parseFDEInstructions(A &addressSpace, const FDE_Info &fdeInfo, const CIE_Info &cieInfo, pint_t upToPC, int arch, PrologInfo *results); @@ -165,10 +167,14 @@ class CFI_Parser { static const char *parseCIE(A &addressSpace, pint_t cie, CIE_Info *cieInfo); }; -/// Parse a FDE into a CIE_Info and an FDE_Info +/// Parse a FDE into a CIE_Info and an FDE_Info. If useCIEInfo is +/// true, treat cieInfo as already-parsed CIE_Info (whose start offset +/// must match the one specified by the FDE) rather than parsing the +/// one indicated within the FDE. template const char *CFI_Parser::decodeFDE(A &addressSpace, pint_t fdeStart, - FDE_Info *fdeInfo, CIE_Info *cieInfo) { + FDE_Info *fdeInfo, CIE_Info *cieInfo, + bool useCIEInfo) { pint_t p = fdeStart; pint_t cfiLength = (pint_t)addressSpace.get32(p); p += 4; @@ -184,9 +190,14 @@ const char *CFI_Parser::decodeFDE(A &addressSpace, pint_t fdeStart, return "FDE is really a CIE"; // this is a CIE not an FDE pint_t nextCFI = p + cfiLength; pint_t cieStart = p - ciePointer; - const char *err = parseCIE(addressSpace, cieStart, cieInfo); - if (err != NULL) - return err; + if (useCIEInfo) { + if (cieInfo->cieStart != cieStart) + return "CIE start does not match"; + } else { + const char *err = parseCIE(addressSpace, cieStart, cieInfo); + if (err != NULL) + return err; + } p += 4; // Parse pc begin and range. pint_t pcStart = @@ -223,11 +234,11 @@ const char *CFI_Parser::decodeFDE(A &addressSpace, pint_t fdeStart, /// Scan an eh_frame section to find an FDE for a pc template bool CFI_Parser::findFDE(A &addressSpace, pint_t pc, pint_t ehSectionStart, - uintptr_t sectionLength, pint_t fdeHint, + size_t sectionLength, pint_t fdeHint, FDE_Info *fdeInfo, CIE_Info *cieInfo) { //fprintf(stderr, "findFDE(0x%llX)\n", (long long)pc); pint_t p = (fdeHint != 0) ? fdeHint : ehSectionStart; - const pint_t ehSectionEnd = (sectionLength == UINTPTR_MAX) + const pint_t ehSectionEnd = (sectionLength == SIZE_MAX) ? static_cast(-1) : (ehSectionStart + sectionLength); while (p < ehSectionEnd) { @@ -726,7 +737,8 @@ bool CFI_Parser::parseFDEInstructions(A &addressSpace, "DW_CFA_GNU_negative_offset_extended(%" PRId64 ")\n", offset); break; -#if defined(_LIBUNWIND_TARGET_AARCH64) || defined(_LIBUNWIND_TARGET_SPARC) +#if defined(_LIBUNWIND_TARGET_AARCH64) || defined(_LIBUNWIND_TARGET_SPARC) || \ + defined(_LIBUNWIND_TARGET_SPARC64) // The same constant is used to represent different instructions on // AArch64 (negate_ra_state) and SPARC (window_save). static_assert(DW_CFA_AARCH64_negate_ra_state == DW_CFA_GNU_window_save, @@ -736,8 +748,8 @@ bool CFI_Parser::parseFDEInstructions(A &addressSpace, #if defined(_LIBUNWIND_TARGET_AARCH64) case REGISTERS_ARM64: { int64_t value = - results->savedRegisters[UNW_ARM64_RA_SIGN_STATE].value ^ 0x1; - results->setRegisterValue(UNW_ARM64_RA_SIGN_STATE, value, + results->savedRegisters[UNW_AARCH64_RA_SIGN_STATE].value ^ 0x1; + results->setRegisterValue(UNW_AARCH64_RA_SIGN_STATE, value, initialState); _LIBUNWIND_TRACE_DWARF("DW_CFA_AARCH64_negate_ra_state\n"); } break; @@ -760,8 +772,31 @@ bool CFI_Parser::parseFDEInstructions(A &addressSpace, } break; #endif + +#if defined(_LIBUNWIND_TARGET_SPARC64) + // case DW_CFA_GNU_window_save: + case REGISTERS_SPARC64: + // Don't save %o0-%o7 on sparc64. + // https://reviews.llvm.org/D32450#736405 + + for (reg = UNW_SPARC_L0; reg <= UNW_SPARC_I7; reg++) { + if (reg == UNW_SPARC_I7) + results->setRegister( + reg, kRegisterInCFADecrypt, + static_cast((reg - UNW_SPARC_L0) * sizeof(pint_t)), + initialState); + else + results->setRegister( + reg, kRegisterInCFA, + static_cast((reg - UNW_SPARC_L0) * sizeof(pint_t)), + initialState); + } + _LIBUNWIND_TRACE_DWARF("DW_CFA_GNU_window_save\n"); + break; +#endif } break; + #else (void)arch; #endif diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/EHHeaderParser.hpp b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/EHHeaderParser.hpp index 392e60e20e..dc251df983 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/EHHeaderParser.hpp +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/EHHeaderParser.hpp @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===------------------------- EHHeaderParser.hpp -------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -60,7 +60,8 @@ bool EHHeaderParser::decodeEHHdr(A &addressSpace, pint_t ehHdrStart, pint_t p = ehHdrStart; uint8_t version = addressSpace.get8(p++); if (version != 1) { - _LIBUNWIND_LOG0("Unsupported .eh_frame_hdr version"); + _LIBUNWIND_LOG("unsupported .eh_frame_hdr version: %" PRIu8 " at %" PRIx64, + version, static_cast(ehHdrStart)); return false; } diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/RWMutex.hpp b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/RWMutex.hpp index 884e935ee6..102bd16333 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/RWMutex.hpp +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/RWMutex.hpp @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===----------------------------- Registers.hpp --------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Registers.hpp b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Registers.hpp index bd2cbf212d..597e1ac9d4 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Registers.hpp +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Registers.hpp @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===----------------------------- Registers.hpp --------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -18,8 +18,9 @@ #include #include -#include "libunwind.h" +#include "cet_unwind.h" #include "config.h" +#include "libunwind.h" namespace libunwind { @@ -37,14 +38,23 @@ enum { REGISTERS_MIPS_O32, REGISTERS_MIPS_NEWABI, REGISTERS_SPARC, + REGISTERS_SPARC64, REGISTERS_HEXAGON, REGISTERS_RISCV, REGISTERS_VE, + REGISTERS_S390X, }; #if defined(_LIBUNWIND_TARGET_I386) class _LIBUNWIND_HIDDEN Registers_x86; extern "C" void __libunwind_Registers_x86_jumpto(Registers_x86 *); + +#if defined(_LIBUNWIND_USE_CET) +extern "C" void *__libunwind_cet_get_jump_target() { + return reinterpret_cast(&__libunwind_Registers_x86_jumpto); +} +#endif + /// Registers_x86 holds the register state of a thread in a 32-bit intel /// process. class _LIBUNWIND_HIDDEN Registers_x86 { @@ -63,7 +73,9 @@ class _LIBUNWIND_HIDDEN Registers_x86 { void setVectorRegister(int num, v128 value); static const char *getRegisterName(int num); void jumpto() { __libunwind_Registers_x86_jumpto(this); } - static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_X86; } + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_X86; + } static int getArch() { return REGISTERS_X86; } uint32_t getSP() const { return _registers.__esp; } @@ -256,6 +268,13 @@ inline void Registers_x86::setVectorRegister(int, v128) { /// process. class _LIBUNWIND_HIDDEN Registers_x86_64; extern "C" void __libunwind_Registers_x86_64_jumpto(Registers_x86_64 *); + +#if defined(_LIBUNWIND_USE_CET) +extern "C" void *__libunwind_cet_get_jump_target() { + return reinterpret_cast(&__libunwind_Registers_x86_64_jumpto); +} +#endif + class _LIBUNWIND_HIDDEN Registers_x86_64 { public: Registers_x86_64(); @@ -272,7 +291,9 @@ class _LIBUNWIND_HIDDEN Registers_x86_64 { void setVectorRegister(int num, v128 value); static const char *getRegisterName(int num); void jumpto() { __libunwind_Registers_x86_64_jumpto(this); } - static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_X86_64; } + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_X86_64; + } static int getArch() { return REGISTERS_X86_64; } uint64_t getSP() const { return _registers.__rsp; } @@ -342,7 +363,7 @@ inline bool Registers_x86_64::validRegister(int regNum) const { return true; if (regNum < 0) return false; - if (regNum > 15) + if (regNum > 16) return false; return true; } @@ -350,6 +371,7 @@ inline bool Registers_x86_64::validRegister(int regNum) const { inline uint64_t Registers_x86_64::getRegister(int regNum) const { switch (regNum) { case UNW_REG_IP: + case UNW_X86_64_RIP: return _registers.__rip; case UNW_REG_SP: return _registers.__rsp; @@ -392,6 +414,7 @@ inline uint64_t Registers_x86_64::getRegister(int regNum) const { inline void Registers_x86_64::setRegister(int regNum, uint64_t value) { switch (regNum) { case UNW_REG_IP: + case UNW_X86_64_RIP: _registers.__rip = value; return; case UNW_REG_SP: @@ -452,6 +475,7 @@ inline void Registers_x86_64::setRegister(int regNum, uint64_t value) { inline const char *Registers_x86_64::getRegisterName(int regNum) { switch (regNum) { case UNW_REG_IP: + case UNW_X86_64_RIP: return "rip"; case UNW_REG_SP: return "rsp"; @@ -586,13 +610,17 @@ class _LIBUNWIND_HIDDEN Registers_ppc { void setVectorRegister(int num, v128 value); static const char *getRegisterName(int num); void jumpto(); - static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_PPC; } + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_PPC; + } static int getArch() { return REGISTERS_PPC; } uint64_t getSP() const { return _registers.__r1; } void setSP(uint32_t value) { _registers.__r1 = value; } uint64_t getIP() const { return _registers.__srr0; } void setIP(uint32_t value) { _registers.__srr0 = value; } + uint64_t getCR() const { return _registers.__cr; } + void setCR(uint32_t value) { _registers.__cr = value; } private: struct ppc_thread_state_t { @@ -1152,13 +1180,17 @@ class _LIBUNWIND_HIDDEN Registers_ppc64 { void setVectorRegister(int num, v128 value); static const char *getRegisterName(int num); void jumpto(); - static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_PPC64; } + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_PPC64; + } static int getArch() { return REGISTERS_PPC64; } uint64_t getSP() const { return _registers.__r1; } void setSP(uint64_t value) { _registers.__r1 = value; } uint64_t getIP() const { return _registers.__srr0; } void setIP(uint64_t value) { _registers.__srr0 = value; } + uint64_t getCR() const { return _registers.__cr; } + void setCR(uint64_t value) { _registers.__cr = value; } private: struct ppc64_thread_state_t { @@ -1797,7 +1829,9 @@ class _LIBUNWIND_HIDDEN Registers_arm64 { void setVectorRegister(int num, v128 value); static const char *getRegisterName(int num); void jumpto() { __libunwind_Registers_arm64_jumpto(this); } - static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_ARM64; } + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_ARM64; + } static int getArch() { return REGISTERS_ARM64; } uint64_t getSP() const { return _registers.__sp; } @@ -1850,33 +1884,41 @@ inline bool Registers_arm64::validRegister(int regNum) const { return false; if (regNum > 95) return false; - if (regNum == UNW_ARM64_RA_SIGN_STATE) + if (regNum == UNW_AARCH64_RA_SIGN_STATE) return true; - if ((regNum > 31) && (regNum < 64)) + if ((regNum > 32) && (regNum < 64)) return false; return true; } inline uint64_t Registers_arm64::getRegister(int regNum) const { - if (regNum == UNW_REG_IP) + if (regNum == UNW_REG_IP || regNum == UNW_AARCH64_PC) return _registers.__pc; - if (regNum == UNW_REG_SP) + if (regNum == UNW_REG_SP || regNum == UNW_AARCH64_SP) return _registers.__sp; - if (regNum == UNW_ARM64_RA_SIGN_STATE) + if (regNum == UNW_AARCH64_RA_SIGN_STATE) return _registers.__ra_sign_state; - if ((regNum >= 0) && (regNum < 32)) + if (regNum == UNW_AARCH64_FP) + return _registers.__fp; + if (regNum == UNW_AARCH64_LR) + return _registers.__lr; + if ((regNum >= 0) && (regNum < 29)) return _registers.__x[regNum]; _LIBUNWIND_ABORT("unsupported arm64 register"); } inline void Registers_arm64::setRegister(int regNum, uint64_t value) { - if (regNum == UNW_REG_IP) + if (regNum == UNW_REG_IP || regNum == UNW_AARCH64_PC) _registers.__pc = value; - else if (regNum == UNW_REG_SP) + else if (regNum == UNW_REG_SP || regNum == UNW_AARCH64_SP) _registers.__sp = value; - else if (regNum == UNW_ARM64_RA_SIGN_STATE) + else if (regNum == UNW_AARCH64_RA_SIGN_STATE) _registers.__ra_sign_state = value; - else if ((regNum >= 0) && (regNum < 32)) + else if (regNum == UNW_AARCH64_FP) + _registers.__fp = value; + else if (regNum == UNW_AARCH64_LR) + _registers.__lr = value; + else if ((regNum >= 0) && (regNum < 29)) _registers.__x[regNum] = value; else _LIBUNWIND_ABORT("unsupported arm64 register"); @@ -1888,133 +1930,135 @@ inline const char *Registers_arm64::getRegisterName(int regNum) { return "pc"; case UNW_REG_SP: return "sp"; - case UNW_ARM64_X0: + case UNW_AARCH64_X0: return "x0"; - case UNW_ARM64_X1: + case UNW_AARCH64_X1: return "x1"; - case UNW_ARM64_X2: + case UNW_AARCH64_X2: return "x2"; - case UNW_ARM64_X3: + case UNW_AARCH64_X3: return "x3"; - case UNW_ARM64_X4: + case UNW_AARCH64_X4: return "x4"; - case UNW_ARM64_X5: + case UNW_AARCH64_X5: return "x5"; - case UNW_ARM64_X6: + case UNW_AARCH64_X6: return "x6"; - case UNW_ARM64_X7: + case UNW_AARCH64_X7: return "x7"; - case UNW_ARM64_X8: + case UNW_AARCH64_X8: return "x8"; - case UNW_ARM64_X9: + case UNW_AARCH64_X9: return "x9"; - case UNW_ARM64_X10: + case UNW_AARCH64_X10: return "x10"; - case UNW_ARM64_X11: + case UNW_AARCH64_X11: return "x11"; - case UNW_ARM64_X12: + case UNW_AARCH64_X12: return "x12"; - case UNW_ARM64_X13: + case UNW_AARCH64_X13: return "x13"; - case UNW_ARM64_X14: + case UNW_AARCH64_X14: return "x14"; - case UNW_ARM64_X15: + case UNW_AARCH64_X15: return "x15"; - case UNW_ARM64_X16: + case UNW_AARCH64_X16: return "x16"; - case UNW_ARM64_X17: + case UNW_AARCH64_X17: return "x17"; - case UNW_ARM64_X18: + case UNW_AARCH64_X18: return "x18"; - case UNW_ARM64_X19: + case UNW_AARCH64_X19: return "x19"; - case UNW_ARM64_X20: + case UNW_AARCH64_X20: return "x20"; - case UNW_ARM64_X21: + case UNW_AARCH64_X21: return "x21"; - case UNW_ARM64_X22: + case UNW_AARCH64_X22: return "x22"; - case UNW_ARM64_X23: + case UNW_AARCH64_X23: return "x23"; - case UNW_ARM64_X24: + case UNW_AARCH64_X24: return "x24"; - case UNW_ARM64_X25: + case UNW_AARCH64_X25: return "x25"; - case UNW_ARM64_X26: + case UNW_AARCH64_X26: return "x26"; - case UNW_ARM64_X27: + case UNW_AARCH64_X27: return "x27"; - case UNW_ARM64_X28: + case UNW_AARCH64_X28: return "x28"; - case UNW_ARM64_X29: + case UNW_AARCH64_FP: return "fp"; - case UNW_ARM64_X30: + case UNW_AARCH64_LR: return "lr"; - case UNW_ARM64_X31: + case UNW_AARCH64_SP: return "sp"; - case UNW_ARM64_D0: + case UNW_AARCH64_PC: + return "pc"; + case UNW_AARCH64_V0: return "d0"; - case UNW_ARM64_D1: + case UNW_AARCH64_V1: return "d1"; - case UNW_ARM64_D2: + case UNW_AARCH64_V2: return "d2"; - case UNW_ARM64_D3: + case UNW_AARCH64_V3: return "d3"; - case UNW_ARM64_D4: + case UNW_AARCH64_V4: return "d4"; - case UNW_ARM64_D5: + case UNW_AARCH64_V5: return "d5"; - case UNW_ARM64_D6: + case UNW_AARCH64_V6: return "d6"; - case UNW_ARM64_D7: + case UNW_AARCH64_V7: return "d7"; - case UNW_ARM64_D8: + case UNW_AARCH64_V8: return "d8"; - case UNW_ARM64_D9: + case UNW_AARCH64_V9: return "d9"; - case UNW_ARM64_D10: + case UNW_AARCH64_V10: return "d10"; - case UNW_ARM64_D11: + case UNW_AARCH64_V11: return "d11"; - case UNW_ARM64_D12: + case UNW_AARCH64_V12: return "d12"; - case UNW_ARM64_D13: + case UNW_AARCH64_V13: return "d13"; - case UNW_ARM64_D14: + case UNW_AARCH64_V14: return "d14"; - case UNW_ARM64_D15: + case UNW_AARCH64_V15: return "d15"; - case UNW_ARM64_D16: + case UNW_AARCH64_V16: return "d16"; - case UNW_ARM64_D17: + case UNW_AARCH64_V17: return "d17"; - case UNW_ARM64_D18: + case UNW_AARCH64_V18: return "d18"; - case UNW_ARM64_D19: + case UNW_AARCH64_V19: return "d19"; - case UNW_ARM64_D20: + case UNW_AARCH64_V20: return "d20"; - case UNW_ARM64_D21: + case UNW_AARCH64_V21: return "d21"; - case UNW_ARM64_D22: + case UNW_AARCH64_V22: return "d22"; - case UNW_ARM64_D23: + case UNW_AARCH64_V23: return "d23"; - case UNW_ARM64_D24: + case UNW_AARCH64_V24: return "d24"; - case UNW_ARM64_D25: + case UNW_AARCH64_V25: return "d25"; - case UNW_ARM64_D26: + case UNW_AARCH64_V26: return "d26"; - case UNW_ARM64_D27: + case UNW_AARCH64_V27: return "d27"; - case UNW_ARM64_D28: + case UNW_AARCH64_V28: return "d28"; - case UNW_ARM64_D29: + case UNW_AARCH64_V29: return "d29"; - case UNW_ARM64_D30: + case UNW_AARCH64_V30: return "d30"; - case UNW_ARM64_D31: + case UNW_AARCH64_V31: return "d31"; default: return "unknown register"; @@ -2022,21 +2066,21 @@ inline const char *Registers_arm64::getRegisterName(int regNum) { } inline bool Registers_arm64::validFloatRegister(int regNum) const { - if (regNum < UNW_ARM64_D0) + if (regNum < UNW_AARCH64_V0) return false; - if (regNum > UNW_ARM64_D31) + if (regNum > UNW_AARCH64_V31) return false; return true; } inline double Registers_arm64::getFloatRegister(int regNum) const { assert(validFloatRegister(regNum)); - return _vectorHalfRegisters[regNum - UNW_ARM64_D0]; + return _vectorHalfRegisters[regNum - UNW_AARCH64_V0]; } inline void Registers_arm64::setFloatRegister(int regNum, double value) { assert(validFloatRegister(regNum)); - _vectorHalfRegisters[regNum - UNW_ARM64_D0] = value; + _vectorHalfRegisters[regNum - UNW_AARCH64_V0] = value; } inline bool Registers_arm64::validVectorRegister(int) const { @@ -2077,7 +2121,9 @@ class _LIBUNWIND_HIDDEN Registers_arm { restoreSavedFloatRegisters(); restoreCoreAndJumpTo(); } - static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_ARM; } + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_ARM; + } static int getArch() { return REGISTERS_ARM; } uint32_t getSP() const { return _registers.__sp; } @@ -2115,6 +2161,10 @@ class _LIBUNWIND_HIDDEN Registers_arm { uint32_t __pc; // Program counter r15 }; + struct PseudoRegisters { + uint32_t __pac; // Return Authentication Code (PAC) + }; + static void saveVFPWithFSTMD(void*); static void saveVFPWithFSTMX(void*); static void saveVFPv3(void*); @@ -2131,6 +2181,7 @@ class _LIBUNWIND_HIDDEN Registers_arm { // ARM registers GPRs _registers; + PseudoRegisters _pseudo_registers; // We save floating point registers lazily because we can't know ahead of // time which ones are used. See EHABI #4.7. @@ -2168,6 +2219,7 @@ inline Registers_arm::Registers_arm(const void *registers) "arm registers do not fit into unw_context_t"); // See __unw_getcontext() note about data. memcpy(&_registers, registers, sizeof(_registers)); + memset(&_pseudo_registers, 0, sizeof(_pseudo_registers)); memset(&_vfp_d0_d15_pad, 0, sizeof(_vfp_d0_d15_pad)); memset(&_vfp_d16_d31, 0, sizeof(_vfp_d16_d31)); #if defined(__ARM_WMMX) @@ -2183,6 +2235,7 @@ inline Registers_arm::Registers_arm() _saved_vfp_d0_d15(false), _saved_vfp_d16_d31(false) { memset(&_registers, 0, sizeof(_registers)); + memset(&_pseudo_registers, 0, sizeof(_pseudo_registers)); memset(&_vfp_d0_d15_pad, 0, sizeof(_vfp_d0_d15_pad)); memset(&_vfp_d16_d31, 0, sizeof(_vfp_d16_d31)); #if defined(__ARM_WMMX) @@ -2210,6 +2263,11 @@ inline bool Registers_arm::validRegister(int regNum) const { return true; #endif +#ifdef __ARM_FEATURE_PAUTH + if (regNum == UNW_ARM_RA_AUTH_CODE) + return true; +#endif + return false; } @@ -2236,6 +2294,11 @@ inline uint32_t Registers_arm::getRegister(int regNum) const { } #endif +#ifdef __ARM_FEATURE_PAUTH + if (regNum == UNW_ARM_RA_AUTH_CODE) + return _pseudo_registers.__pac; +#endif + _LIBUNWIND_ABORT("unsupported arm register"); } @@ -2271,6 +2334,11 @@ inline void Registers_arm::setRegister(int regNum, uint32_t value) { } #endif + if (regNum == UNW_ARM_RA_AUTH_CODE) { + _pseudo_registers.__pac = value; + return; + } + _LIBUNWIND_ABORT("unsupported arm register"); } @@ -2555,7 +2623,9 @@ class _LIBUNWIND_HIDDEN Registers_or1k { void setVectorRegister(int num, v128 value); static const char *getRegisterName(int num); void jumpto(); - static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_OR1K; } + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_OR1K; + } static int getArch() { return REGISTERS_OR1K; } uint64_t getSP() const { return _registers.__r[1]; } @@ -2752,7 +2822,9 @@ class _LIBUNWIND_HIDDEN Registers_mips_o32 { void setVectorRegister(int num, v128 value); static const char *getRegisterName(int num); void jumpto(); - static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_MIPS; } + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_MIPS; + } static int getArch() { return REGISTERS_MIPS_O32; } uint32_t getSP() const { return _registers.__r[29]; } @@ -3079,7 +3151,9 @@ class _LIBUNWIND_HIDDEN Registers_mips_newabi { void setVectorRegister(int num, v128 value); static const char *getRegisterName(int num); void jumpto(); - static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_MIPS; } + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_MIPS; + } static int getArch() { return REGISTERS_MIPS_NEWABI; } uint64_t getSP() const { return _registers.__r[29]; } @@ -3374,7 +3448,9 @@ class _LIBUNWIND_HIDDEN Registers_sparc { void setVectorRegister(int num, v128 value); static const char *getRegisterName(int num); void jumpto(); - static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_SPARC; } + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_SPARC; + } static int getArch() { return REGISTERS_SPARC; } uint64_t getSP() const { return _registers.__regs[UNW_SPARC_O6]; } @@ -3539,6 +3615,191 @@ inline const char *Registers_sparc::getRegisterName(int regNum) { } #endif // _LIBUNWIND_TARGET_SPARC +#if defined(_LIBUNWIND_TARGET_SPARC64) +/// Registers_sparc64 holds the register state of a thread in a 64-bit +/// sparc process. +class _LIBUNWIND_HIDDEN Registers_sparc64 { +public: + Registers_sparc64() = default; + Registers_sparc64(const void *registers); + + bool validRegister(int num) const; + uint64_t getRegister(int num) const; + void setRegister(int num, uint64_t value); + bool validFloatRegister(int num) const; + double getFloatRegister(int num) const; + void setFloatRegister(int num, double value); + bool validVectorRegister(int num) const; + v128 getVectorRegister(int num) const; + void setVectorRegister(int num, v128 value); + const char *getRegisterName(int num); + void jumpto(); + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_SPARC64; + } + static int getArch() { return REGISTERS_SPARC64; } + + uint64_t getSP() const { return _registers.__regs[UNW_SPARC_O6] + 2047; } + void setSP(uint64_t value) { _registers.__regs[UNW_SPARC_O6] = value - 2047; } + uint64_t getIP() const { return _registers.__regs[UNW_SPARC_O7]; } + void setIP(uint64_t value) { _registers.__regs[UNW_SPARC_O7] = value; } + uint64_t getWCookie() const { return _wcookie; } + +private: + struct sparc64_thread_state_t { + uint64_t __regs[32]; + }; + + sparc64_thread_state_t _registers{}; + uint64_t _wcookie = 0; +}; + +inline Registers_sparc64::Registers_sparc64(const void *registers) { + static_assert((check_fit::does_fit), + "sparc64 registers do not fit into unw_context_t"); + memcpy(&_registers, registers, sizeof(_registers)); + memcpy(&_wcookie, + static_cast(registers) + sizeof(_registers), + sizeof(_wcookie)); +} + +inline bool Registers_sparc64::validRegister(int regNum) const { + if (regNum == UNW_REG_IP) + return true; + if (regNum == UNW_REG_SP) + return true; + if (regNum < 0) + return false; + if (regNum <= UNW_SPARC_I7) + return true; + return false; +} + +inline uint64_t Registers_sparc64::getRegister(int regNum) const { + if (regNum >= UNW_SPARC_G0 && regNum <= UNW_SPARC_I7) + return _registers.__regs[regNum]; + + switch (regNum) { + case UNW_REG_IP: + return _registers.__regs[UNW_SPARC_O7]; + case UNW_REG_SP: + return _registers.__regs[UNW_SPARC_O6] + 2047; + } + _LIBUNWIND_ABORT("unsupported sparc64 register"); +} + +inline void Registers_sparc64::setRegister(int regNum, uint64_t value) { + if (regNum >= UNW_SPARC_G0 && regNum <= UNW_SPARC_I7) { + _registers.__regs[regNum] = value; + return; + } + + switch (regNum) { + case UNW_REG_IP: + _registers.__regs[UNW_SPARC_O7] = value; + return; + case UNW_REG_SP: + _registers.__regs[UNW_SPARC_O6] = value - 2047; + return; + } + _LIBUNWIND_ABORT("unsupported sparc64 register"); +} + +inline bool Registers_sparc64::validFloatRegister(int) const { return false; } + +inline double Registers_sparc64::getFloatRegister(int) const { + _LIBUNWIND_ABORT("no sparc64 float registers"); +} + +inline void Registers_sparc64::setFloatRegister(int, double) { + _LIBUNWIND_ABORT("no sparc64 float registers"); +} + +inline bool Registers_sparc64::validVectorRegister(int) const { return false; } + +inline v128 Registers_sparc64::getVectorRegister(int) const { + _LIBUNWIND_ABORT("no sparc64 vector registers"); +} + +inline void Registers_sparc64::setVectorRegister(int, v128) { + _LIBUNWIND_ABORT("no sparc64 vector registers"); +} + +inline const char *Registers_sparc64::getRegisterName(int regNum) { + switch (regNum) { + case UNW_REG_IP: + return "pc"; + case UNW_SPARC_G0: + return "g0"; + case UNW_SPARC_G1: + return "g1"; + case UNW_SPARC_G2: + return "g2"; + case UNW_SPARC_G3: + return "g3"; + case UNW_SPARC_G4: + return "g4"; + case UNW_SPARC_G5: + return "g5"; + case UNW_SPARC_G6: + return "g6"; + case UNW_SPARC_G7: + return "g7"; + case UNW_SPARC_O0: + return "o0"; + case UNW_SPARC_O1: + return "o1"; + case UNW_SPARC_O2: + return "o2"; + case UNW_SPARC_O3: + return "o3"; + case UNW_SPARC_O4: + return "o4"; + case UNW_SPARC_O5: + return "o5"; + case UNW_REG_SP: + case UNW_SPARC_O6: + return "o6"; + case UNW_SPARC_O7: + return "o7"; + case UNW_SPARC_L0: + return "l0"; + case UNW_SPARC_L1: + return "l1"; + case UNW_SPARC_L2: + return "l2"; + case UNW_SPARC_L3: + return "l3"; + case UNW_SPARC_L4: + return "l4"; + case UNW_SPARC_L5: + return "l5"; + case UNW_SPARC_L6: + return "l6"; + case UNW_SPARC_L7: + return "l7"; + case UNW_SPARC_I0: + return "i0"; + case UNW_SPARC_I1: + return "i1"; + case UNW_SPARC_I2: + return "i2"; + case UNW_SPARC_I3: + return "i3"; + case UNW_SPARC_I4: + return "i4"; + case UNW_SPARC_I5: + return "i5"; + case UNW_SPARC_I6: + return "i6"; + case UNW_SPARC_I7: + return "i7"; + default: + return "unknown register"; + } +} +#endif // _LIBUNWIND_TARGET_SPARC64 + #if defined(_LIBUNWIND_TARGET_HEXAGON) /// Registers_hexagon holds the register state of a thread in a Hexagon QDSP6 /// process. @@ -3558,7 +3819,9 @@ class _LIBUNWIND_HIDDEN Registers_hexagon { void setVectorRegister(int num, v128 value); const char *getRegisterName(int num); void jumpto(); - static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_HEXAGON; } + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_HEXAGON; + } static int getArch() { return REGISTERS_HEXAGON; } uint32_t getSP() const { return _registers.__r[UNW_HEXAGON_R29]; } @@ -3721,52 +3984,100 @@ inline const char *Registers_hexagon::getRegisterName(int regNum) { #if defined(_LIBUNWIND_TARGET_RISCV) -/// Registers_riscv holds the register state of a thread in a 64-bit RISC-V +/// Registers_riscv holds the register state of a thread in a RISC-V /// process. + +// This check makes it safe when LIBUNWIND_ENABLE_CROSS_UNWINDING enabled. +# ifdef __riscv +# if __riscv_xlen == 32 +typedef uint32_t reg_t; +# elif __riscv_xlen == 64 +typedef uint64_t reg_t; +# else +# error "Unsupported __riscv_xlen" +# endif + +# if defined(__riscv_flen) +# if __riscv_flen == 64 +typedef double fp_t; +# elif __riscv_flen == 32 +typedef float fp_t; +# else +# error "Unsupported __riscv_flen" +# endif +# else +// This is just for supressing undeclared error of fp_t. +typedef double fp_t; +# endif +# else +// Use Max possible width when cross unwinding +typedef uint64_t reg_t; +typedef double fp_t; +# define __riscv_xlen 64 +# define __riscv_flen 64 +#endif + +/// Registers_riscv holds the register state of a thread. class _LIBUNWIND_HIDDEN Registers_riscv { public: Registers_riscv(); Registers_riscv(const void *registers); bool validRegister(int num) const; - uint64_t getRegister(int num) const; - void setRegister(int num, uint64_t value); + reg_t getRegister(int num) const; + void setRegister(int num, reg_t value); bool validFloatRegister(int num) const; - double getFloatRegister(int num) const; - void setFloatRegister(int num, double value); + fp_t getFloatRegister(int num) const; + void setFloatRegister(int num, fp_t value); bool validVectorRegister(int num) const; v128 getVectorRegister(int num) const; void setVectorRegister(int num, v128 value); static const char *getRegisterName(int num); void jumpto(); - static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_RISCV; } + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_RISCV; + } static int getArch() { return REGISTERS_RISCV; } - uint64_t getSP() const { return _registers[2]; } - void setSP(uint64_t value) { _registers[2] = value; } - uint64_t getIP() const { return _registers[0]; } - void setIP(uint64_t value) { _registers[0] = value; } + reg_t getSP() const { return _registers[2]; } + void setSP(reg_t value) { _registers[2] = value; } + reg_t getIP() const { return _registers[0]; } + void setIP(reg_t value) { _registers[0] = value; } private: // _registers[0] holds the pc - uint64_t _registers[32]; - double _floats[32]; + reg_t _registers[32]; +# if defined(__riscv_flen) + fp_t _floats[32]; +# endif }; inline Registers_riscv::Registers_riscv(const void *registers) { static_assert((check_fit::does_fit), "riscv registers do not fit into unw_context_t"); memcpy(&_registers, registers, sizeof(_registers)); +# if __riscv_xlen == 32 + static_assert(sizeof(_registers) == 0x80, + "expected float registers to be at offset 128"); +# elif __riscv_xlen == 64 static_assert(sizeof(_registers) == 0x100, "expected float registers to be at offset 256"); +# else +# error "Unexpected float registers." +# endif + +# if defined(__riscv_flen) memcpy(_floats, static_cast(registers) + sizeof(_registers), sizeof(_floats)); +# endif } inline Registers_riscv::Registers_riscv() { memset(&_registers, 0, sizeof(_registers)); +# if defined(__riscv_flen) memset(&_floats, 0, sizeof(_floats)); +# endif } inline bool Registers_riscv::validRegister(int regNum) const { @@ -3781,7 +4092,7 @@ inline bool Registers_riscv::validRegister(int regNum) const { return true; } -inline uint64_t Registers_riscv::getRegister(int regNum) const { +inline reg_t Registers_riscv::getRegister(int regNum) const { if (regNum == UNW_REG_IP) return _registers[0]; if (regNum == UNW_REG_SP) @@ -3793,7 +4104,7 @@ inline uint64_t Registers_riscv::getRegister(int regNum) const { _LIBUNWIND_ABORT("unsupported riscv register"); } -inline void Registers_riscv::setRegister(int regNum, uint64_t value) { +inline void Registers_riscv::setRegister(int regNum, reg_t value) { if (regNum == UNW_REG_IP) _registers[0] = value; else if (regNum == UNW_REG_SP) @@ -3947,32 +4258,37 @@ inline const char *Registers_riscv::getRegisterName(int regNum) { } inline bool Registers_riscv::validFloatRegister(int regNum) const { +# if defined(__riscv_flen) if (regNum < UNW_RISCV_F0) return false; if (regNum > UNW_RISCV_F31) return false; return true; +# else + (void)regNum; + return false; +# endif } -inline double Registers_riscv::getFloatRegister(int regNum) const { -#if defined(__riscv_flen) && __riscv_flen == 64 +inline fp_t Registers_riscv::getFloatRegister(int regNum) const { +# if defined(__riscv_flen) assert(validFloatRegister(regNum)); return _floats[regNum - UNW_RISCV_F0]; -#else +# else (void)regNum; _LIBUNWIND_ABORT("libunwind not built with float support"); -#endif +# endif } -inline void Registers_riscv::setFloatRegister(int regNum, double value) { -#if defined(__riscv_flen) && __riscv_flen == 64 +inline void Registers_riscv::setFloatRegister(int regNum, fp_t value) { +# if defined(__riscv_flen) assert(validFloatRegister(regNum)); _floats[regNum - UNW_RISCV_F0] = value; -#else +# else (void)regNum; (void)value; _LIBUNWIND_ABORT("libunwind not built with float support"); -#endif +# endif } inline bool Registers_riscv::validVectorRegister(int) const { @@ -4006,7 +4322,9 @@ class _LIBUNWIND_HIDDEN Registers_ve { void setVectorRegister(int num, v128 value); static const char *getRegisterName(int num); void jumpto(); - static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_VE; } + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_VE; + } static int getArch() { return REGISTERS_VE; } uint64_t getSP() const { return _registers.__s[11]; } @@ -4428,6 +4746,295 @@ inline const char *Registers_ve::getRegisterName(int regNum) { } #endif // _LIBUNWIND_TARGET_VE +#if defined(_LIBUNWIND_TARGET_S390X) +/// Registers_s390x holds the register state of a thread in a +/// 64-bit Linux on IBM zSystems process. +class _LIBUNWIND_HIDDEN Registers_s390x { +public: + Registers_s390x(); + Registers_s390x(const void *registers); + + bool validRegister(int num) const; + uint64_t getRegister(int num) const; + void setRegister(int num, uint64_t value); + bool validFloatRegister(int num) const; + double getFloatRegister(int num) const; + void setFloatRegister(int num, double value); + bool validVectorRegister(int num) const; + v128 getVectorRegister(int num) const; + void setVectorRegister(int num, v128 value); + static const char *getRegisterName(int num); + void jumpto(); + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_S390X; + } + static int getArch() { return REGISTERS_S390X; } + + uint64_t getSP() const { return _registers.__gpr[15]; } + void setSP(uint64_t value) { _registers.__gpr[15] = value; } + uint64_t getIP() const { return _registers.__pswa; } + void setIP(uint64_t value) { _registers.__pswa = value; } + +private: + struct s390x_thread_state_t { + uint64_t __pswm; // Problem Status Word: Mask + uint64_t __pswa; // Problem Status Word: Address (PC) + uint64_t __gpr[16]; // General Purpose Registers + double __fpr[16]; // Floating-Point Registers + }; + + s390x_thread_state_t _registers; +}; + +inline Registers_s390x::Registers_s390x(const void *registers) { + static_assert((check_fit::does_fit), + "s390x registers do not fit into unw_context_t"); + memcpy(&_registers, static_cast(registers), + sizeof(_registers)); +} + +inline Registers_s390x::Registers_s390x() { + memset(&_registers, 0, sizeof(_registers)); +} + +inline bool Registers_s390x::validRegister(int regNum) const { + switch (regNum) { + case UNW_S390X_PSWM: + case UNW_S390X_PSWA: + case UNW_REG_IP: + case UNW_REG_SP: + return true; + } + + if (regNum >= UNW_S390X_R0 && regNum <= UNW_S390X_R15) + return true; + + return false; +} + +inline uint64_t Registers_s390x::getRegister(int regNum) const { + if (regNum >= UNW_S390X_R0 && regNum <= UNW_S390X_R15) + return _registers.__gpr[regNum - UNW_S390X_R0]; + + switch (regNum) { + case UNW_S390X_PSWM: + return _registers.__pswm; + case UNW_S390X_PSWA: + case UNW_REG_IP: + return _registers.__pswa; + case UNW_REG_SP: + return _registers.__gpr[15]; + } + _LIBUNWIND_ABORT("unsupported s390x register"); +} + +inline void Registers_s390x::setRegister(int regNum, uint64_t value) { + if (regNum >= UNW_S390X_R0 && regNum <= UNW_S390X_R15) { + _registers.__gpr[regNum - UNW_S390X_R0] = value; + return; + } + + switch (regNum) { + case UNW_S390X_PSWM: + _registers.__pswm = value; + return; + case UNW_S390X_PSWA: + case UNW_REG_IP: + _registers.__pswa = value; + return; + case UNW_REG_SP: + _registers.__gpr[15] = value; + return; + } + _LIBUNWIND_ABORT("unsupported s390x register"); +} + +inline bool Registers_s390x::validFloatRegister(int regNum) const { + return regNum >= UNW_S390X_F0 && regNum <= UNW_S390X_F15; +} + +inline double Registers_s390x::getFloatRegister(int regNum) const { + // NOTE: FPR DWARF register numbers are not consecutive. + switch (regNum) { + case UNW_S390X_F0: + return _registers.__fpr[0]; + case UNW_S390X_F1: + return _registers.__fpr[1]; + case UNW_S390X_F2: + return _registers.__fpr[2]; + case UNW_S390X_F3: + return _registers.__fpr[3]; + case UNW_S390X_F4: + return _registers.__fpr[4]; + case UNW_S390X_F5: + return _registers.__fpr[5]; + case UNW_S390X_F6: + return _registers.__fpr[6]; + case UNW_S390X_F7: + return _registers.__fpr[7]; + case UNW_S390X_F8: + return _registers.__fpr[8]; + case UNW_S390X_F9: + return _registers.__fpr[9]; + case UNW_S390X_F10: + return _registers.__fpr[10]; + case UNW_S390X_F11: + return _registers.__fpr[11]; + case UNW_S390X_F12: + return _registers.__fpr[12]; + case UNW_S390X_F13: + return _registers.__fpr[13]; + case UNW_S390X_F14: + return _registers.__fpr[14]; + case UNW_S390X_F15: + return _registers.__fpr[15]; + } + _LIBUNWIND_ABORT("unsupported s390x register"); +} + +inline void Registers_s390x::setFloatRegister(int regNum, double value) { + // NOTE: FPR DWARF register numbers are not consecutive. + switch (regNum) { + case UNW_S390X_F0: + _registers.__fpr[0] = value; + return; + case UNW_S390X_F1: + _registers.__fpr[1] = value; + return; + case UNW_S390X_F2: + _registers.__fpr[2] = value; + return; + case UNW_S390X_F3: + _registers.__fpr[3] = value; + return; + case UNW_S390X_F4: + _registers.__fpr[4] = value; + return; + case UNW_S390X_F5: + _registers.__fpr[5] = value; + return; + case UNW_S390X_F6: + _registers.__fpr[6] = value; + return; + case UNW_S390X_F7: + _registers.__fpr[7] = value; + return; + case UNW_S390X_F8: + _registers.__fpr[8] = value; + return; + case UNW_S390X_F9: + _registers.__fpr[9] = value; + return; + case UNW_S390X_F10: + _registers.__fpr[10] = value; + return; + case UNW_S390X_F11: + _registers.__fpr[11] = value; + return; + case UNW_S390X_F12: + _registers.__fpr[12] = value; + return; + case UNW_S390X_F13: + _registers.__fpr[13] = value; + return; + case UNW_S390X_F14: + _registers.__fpr[14] = value; + return; + case UNW_S390X_F15: + _registers.__fpr[15] = value; + return; + } + _LIBUNWIND_ABORT("unsupported s390x register"); +} + +inline bool Registers_s390x::validVectorRegister(int /*regNum*/) const { + return false; +} + +inline v128 Registers_s390x::getVectorRegister(int /*regNum*/) const { + _LIBUNWIND_ABORT("s390x vector support not implemented"); +} + +inline void Registers_s390x::setVectorRegister(int /*regNum*/, v128 /*value*/) { + _LIBUNWIND_ABORT("s390x vector support not implemented"); +} + +inline const char *Registers_s390x::getRegisterName(int regNum) { + switch (regNum) { + case UNW_REG_IP: + return "ip"; + case UNW_REG_SP: + return "sp"; + case UNW_S390X_R0: + return "r0"; + case UNW_S390X_R1: + return "r1"; + case UNW_S390X_R2: + return "r2"; + case UNW_S390X_R3: + return "r3"; + case UNW_S390X_R4: + return "r4"; + case UNW_S390X_R5: + return "r5"; + case UNW_S390X_R6: + return "r6"; + case UNW_S390X_R7: + return "r7"; + case UNW_S390X_R8: + return "r8"; + case UNW_S390X_R9: + return "r9"; + case UNW_S390X_R10: + return "r10"; + case UNW_S390X_R11: + return "r11"; + case UNW_S390X_R12: + return "r12"; + case UNW_S390X_R13: + return "r13"; + case UNW_S390X_R14: + return "r14"; + case UNW_S390X_R15: + return "r15"; + case UNW_S390X_F0: + return "f0"; + case UNW_S390X_F1: + return "f1"; + case UNW_S390X_F2: + return "f2"; + case UNW_S390X_F3: + return "f3"; + case UNW_S390X_F4: + return "f4"; + case UNW_S390X_F5: + return "f5"; + case UNW_S390X_F6: + return "f6"; + case UNW_S390X_F7: + return "f7"; + case UNW_S390X_F8: + return "f8"; + case UNW_S390X_F9: + return "f9"; + case UNW_S390X_F10: + return "f10"; + case UNW_S390X_F11: + return "f11"; + case UNW_S390X_F12: + return "f12"; + case UNW_S390X_F13: + return "f13"; + case UNW_S390X_F14: + return "f14"; + case UNW_S390X_F15: + return "f15"; + } + return "unknown register"; +} +#endif // _LIBUNWIND_TARGET_S390X + + } // namespace libunwind #endif // __REGISTERS_HPP__ diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-EHABI.cpp b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-EHABI.cpp index 0361f04cd5..164ca14b1b 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-EHABI.cpp +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-EHABI.cpp @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===--------------------------- Unwind-EHABI.cpp -------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -100,9 +100,11 @@ _Unwind_Reason_Code ProcessDescriptors( case Descriptor::LU32: descriptor = getNextWord(descriptor, &length); descriptor = getNextWord(descriptor, &offset); + break; case Descriptor::LU16: descriptor = getNextNibble(descriptor, &length); descriptor = getNextNibble(descriptor, &offset); + break; default: assert(false); return _URC_FAILURE; @@ -188,9 +190,14 @@ static _Unwind_Reason_Code unwindOneFrame(_Unwind_State state, if (result != _URC_CONTINUE_UNWIND) return result; - if (__unw_step(reinterpret_cast(context)) != UNW_STEP_SUCCESS) + switch (__unw_step(reinterpret_cast(context))) { + case UNW_STEP_SUCCESS: + return _URC_CONTINUE_UNWIND; + case UNW_STEP_END: + return _URC_END_OF_STACK; + default: return _URC_FAILURE; - return _URC_CONTINUE_UNWIND; + } } // Generates mask discriminator for _Unwind_VRS_Pop, e.g. for _UVRSC_CORE / @@ -257,6 +264,7 @@ _Unwind_VRS_Interpret(_Unwind_Context *context, const uint32_t *data, size_t offset, size_t len) { bool wrotePC = false; bool finish = false; + bool hasReturnAddrAuthCode = false; while (offset < len && !finish) { uint8_t byte = getByte(data, offset++); if ((byte & 0x80) == 0) { @@ -343,6 +351,10 @@ _Unwind_VRS_Interpret(_Unwind_Context *context, const uint32_t *data, break; } case 0xb4: + hasReturnAddrAuthCode = true; + _Unwind_VRS_Pop(context, _UVRSC_PSEUDO, + 0 /* Return Address Auth Code */, _UVRSD_UINT32); + break; case 0xb5: case 0xb6: case 0xb7: @@ -418,6 +430,17 @@ _Unwind_VRS_Interpret(_Unwind_Context *context, const uint32_t *data, if (!wrotePC) { uint32_t lr; _Unwind_VRS_Get(context, _UVRSC_CORE, UNW_ARM_LR, _UVRSD_UINT32, &lr); +#ifdef __ARM_FEATURE_PAUTH + if (hasReturnAddrAuthCode) { + uint32_t sp; + uint32_t pac; + _Unwind_VRS_Get(context, _UVRSC_CORE, UNW_ARM_SP, _UVRSD_UINT32, &sp); + _Unwind_VRS_Get(context, _UVRSC_PSEUDO, 0, _UVRSD_UINT32, &pac); + __asm__ __volatile__("autg %0, %1, %2" : : "r"(pac), "r"(lr), "r"(sp) :); + } +#else + (void)hasReturnAddrAuthCode; +#endif _Unwind_VRS_Set(context, _UVRSC_CORE, UNW_ARM_IP, _UVRSD_UINT32, &lr); } return _URC_CONTINUE_UNWIND; @@ -464,6 +487,7 @@ unwind_phase1(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Exception *except return _URC_FATAL_PHASE1_ERROR; } +#ifndef NDEBUG // When tracing, print state information. if (_LIBUNWIND_TRACING_UNWINDING) { char functionBuf[512]; @@ -482,6 +506,7 @@ unwind_phase1(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Exception *except frameInfo.start_ip, functionName, frameInfo.lsda, frameInfo.handler); } +#endif // If there is a personality routine, ask it if it will want to stop at // this frame. @@ -583,6 +608,7 @@ static _Unwind_Reason_Code unwind_phase2(unw_context_t *uc, unw_cursor_t *cursor return _URC_FATAL_PHASE2_ERROR; } +#ifndef NDEBUG // When tracing, print state information. if (_LIBUNWIND_TRACING_UNWINDING) { char functionBuf[512]; @@ -599,11 +625,12 @@ static _Unwind_Reason_Code unwind_phase2(unw_context_t *uc, unw_cursor_t *cursor functionName, sp, frameInfo.lsda, frameInfo.handler); } +#endif // If there is a personality routine, tell it we are unwinding. if (frameInfo.handler != 0) { _Unwind_Personality_Fn p = - (_Unwind_Personality_Fn)(long)(frameInfo.handler); + (_Unwind_Personality_Fn)(intptr_t)(frameInfo.handler); struct _Unwind_Context *context = (struct _Unwind_Context *)(cursor); // EHABI #7.2 exception_object->pr_cache.fnstart = frameInfo.start_ip; @@ -671,6 +698,123 @@ static _Unwind_Reason_Code unwind_phase2(unw_context_t *uc, unw_cursor_t *cursor return _URC_FATAL_PHASE2_ERROR; } +static _Unwind_Reason_Code +unwind_phase2_forced(unw_context_t *uc, unw_cursor_t *cursor, + _Unwind_Exception *exception_object, _Unwind_Stop_Fn stop, + void *stop_parameter) { + bool endOfStack = false; + // See comment at the start of unwind_phase1 regarding VRS integrity. + __unw_init_local(cursor, uc); + _LIBUNWIND_TRACE_UNWINDING("unwind_phase2_force(ex_ojb=%p)", + static_cast(exception_object)); + // Walk each frame until we reach where search phase said to stop + while (!endOfStack) { + // Update info about this frame. + unw_proc_info_t frameInfo; + if (__unw_get_proc_info(cursor, &frameInfo) != UNW_ESUCCESS) { + _LIBUNWIND_TRACE_UNWINDING("unwind_phase2_forced(ex_ojb=%p): __unw_step " + "failed => _URC_END_OF_STACK", + (void *)exception_object); + return _URC_FATAL_PHASE2_ERROR; + } + +#ifndef NDEBUG + // When tracing, print state information. + if (_LIBUNWIND_TRACING_UNWINDING) { + char functionBuf[512]; + const char *functionName = functionBuf; + unw_word_t offset; + if ((__unw_get_proc_name(cursor, functionBuf, sizeof(functionBuf), + &offset) != UNW_ESUCCESS) || + (frameInfo.start_ip + offset > frameInfo.end_ip)) + functionName = ".anonymous."; + _LIBUNWIND_TRACE_UNWINDING( + "unwind_phase2_forced(ex_ojb=%p): start_ip=0x%" PRIxPTR + ", func=%s, lsda=0x%" PRIxPTR ", personality=0x%" PRIxPTR, + (void *)exception_object, frameInfo.start_ip, functionName, + frameInfo.lsda, frameInfo.handler); + } +#endif + + // Call stop function at each frame. + _Unwind_Action action = + (_Unwind_Action)(_UA_FORCE_UNWIND | _UA_CLEANUP_PHASE); + _Unwind_Reason_Code stopResult = + (*stop)(1, action, exception_object->exception_class, exception_object, + (_Unwind_Context *)(cursor), stop_parameter); + _LIBUNWIND_TRACE_UNWINDING( + "unwind_phase2_forced(ex_ojb=%p): stop function returned %d", + (void *)exception_object, stopResult); + if (stopResult != _URC_NO_REASON) { + _LIBUNWIND_TRACE_UNWINDING( + "unwind_phase2_forced(ex_ojb=%p): stopped by stop function", + (void *)exception_object); + return _URC_FATAL_PHASE2_ERROR; + } + + // If there is a personality routine, tell it we are unwinding. + if (frameInfo.handler != 0) { + _Unwind_Personality_Fn p = + (_Unwind_Personality_Fn)(uintptr_t)(frameInfo.handler); + struct _Unwind_Context *context = (struct _Unwind_Context *)(cursor); + // EHABI #7.2 + exception_object->pr_cache.fnstart = frameInfo.start_ip; + exception_object->pr_cache.ehtp = + (_Unwind_EHT_Header *)frameInfo.unwind_info; + exception_object->pr_cache.additional = frameInfo.flags; + _Unwind_Reason_Code personalityResult = + (*p)(_US_FORCE_UNWIND | _US_UNWIND_FRAME_STARTING, exception_object, + context); + switch (personalityResult) { + case _URC_CONTINUE_UNWIND: + _LIBUNWIND_TRACE_UNWINDING("unwind_phase2_forced(ex_ojb=%p): " + "personality returned " + "_URC_CONTINUE_UNWIND", + (void *)exception_object); + // Destructors called, continue unwinding + break; + case _URC_INSTALL_CONTEXT: + _LIBUNWIND_TRACE_UNWINDING("unwind_phase2_forced(ex_ojb=%p): " + "personality returned " + "_URC_INSTALL_CONTEXT", + (void *)exception_object); + // We may get control back if landing pad calls _Unwind_Resume(). + __unw_resume(cursor); + break; + case _URC_END_OF_STACK: + _LIBUNWIND_TRACE_UNWINDING("unwind_phase2_forced(ex_ojb=%p): " + "personality returned " + "_URC_END_OF_STACK", + (void *)exception_object); + // Personalty routine did the step and it can't step forward. + endOfStack = true; + break; + default: + // Personality routine returned an unknown result code. + _LIBUNWIND_TRACE_UNWINDING("unwind_phase2_forced(ex_ojb=%p): " + "personality returned %d, " + "_URC_FATAL_PHASE2_ERROR", + (void *)exception_object, personalityResult); + return _URC_FATAL_PHASE2_ERROR; + } + } + } + + // Call stop function one last time and tell it we've reached the end + // of the stack. + _LIBUNWIND_TRACE_UNWINDING("unwind_phase2_forced(ex_ojb=%p): calling stop " + "function with _UA_END_OF_STACK", + (void *)exception_object); + _Unwind_Action lastAction = + (_Unwind_Action)(_UA_FORCE_UNWIND | _UA_CLEANUP_PHASE | _UA_END_OF_STACK); + (*stop)(1, lastAction, exception_object->exception_class, exception_object, + (struct _Unwind_Context *)(cursor), stop_parameter); + + // Clean up phase did not resume at the frame that the search phase said it + // would. + return _URC_FATAL_PHASE2_ERROR; +} + /// Called by __cxa_throw. Only returns if there is a fatal error. _LIBUNWIND_EXPORT _Unwind_Reason_Code _Unwind_RaiseException(_Unwind_Exception *exception_object) { @@ -718,10 +862,13 @@ _Unwind_Resume(_Unwind_Exception *exception_object) { unw_cursor_t cursor; __unw_getcontext(&uc); - // _Unwind_RaiseException on EHABI will always set the reserved1 field to 0, - // which is in the same position as private_1 below. - // TODO(ajwong): Who wronte the above? Why is it true? - unwind_phase2(&uc, &cursor, exception_object, true); + if (exception_object->unwinder_cache.reserved1) + unwind_phase2_forced( + &uc, &cursor, exception_object, + (_Unwind_Stop_Fn)exception_object->unwinder_cache.reserved1, + (void *)exception_object->unwinder_cache.reserved3); + else + unwind_phase2(&uc, &cursor, exception_object, true); // Clients assume _Unwind_Resume() does not return, so all we can do is abort. _LIBUNWIND_ABORT("_Unwind_Resume() can't return"); @@ -813,6 +960,15 @@ _Unwind_VRS_Set(_Unwind_Context *context, _Unwind_VRS_RegClass regclass, case _UVRSC_WMMXD: break; #endif + case _UVRSC_PSEUDO: + // There's only one pseudo-register, PAC, with regno == 0. + if (representation != _UVRSD_UINT32 || regno != 0) + return _UVRSR_FAILED; + return __unw_set_reg(cursor, (unw_regnum_t)(UNW_ARM_RA_AUTH_CODE), + *(unw_word_t *)valuep) == UNW_ESUCCESS + ? _UVRSR_OK + : _UVRSR_FAILED; + break; } _LIBUNWIND_ABORT("unsupported register class"); } @@ -867,6 +1023,15 @@ _Unwind_VRS_Get_Internal(_Unwind_Context *context, case _UVRSC_WMMXD: break; #endif + case _UVRSC_PSEUDO: + // There's only one pseudo-register, PAC, with regno == 0. + if (representation != _UVRSD_UINT32 || regno != 0) + return _UVRSR_FAILED; + return __unw_get_reg(cursor, (unw_regnum_t)(UNW_ARM_RA_AUTH_CODE), + (unw_word_t *)valuep) == UNW_ESUCCESS + ? _UVRSR_OK + : _UVRSR_FAILED; + break; } _LIBUNWIND_ABORT("unsupported register class"); } @@ -964,10 +1129,44 @@ _Unwind_VRS_Pop(_Unwind_Context *context, _Unwind_VRS_RegClass regclass, return _Unwind_VRS_Set(context, _UVRSC_CORE, UNW_ARM_SP, _UVRSD_UINT32, &sp); } + case _UVRSC_PSEUDO: { + if (representation != _UVRSD_UINT32 || discriminator != 0) + return _UVRSR_FAILED; + // Return Address Authentication code (PAC) - discriminator 0 + uint32_t *sp; + if (_Unwind_VRS_Get(context, _UVRSC_CORE, UNW_ARM_SP, _UVRSD_UINT32, + &sp) != _UVRSR_OK) { + return _UVRSR_FAILED; + } + uint32_t pac = *sp++; + _Unwind_VRS_Set(context, _UVRSC_CORE, UNW_ARM_SP, _UVRSD_UINT32, &sp); + return _Unwind_VRS_Set(context, _UVRSC_PSEUDO, 0, _UVRSD_UINT32, &pac); + } } _LIBUNWIND_ABORT("unsupported register class"); } +/// Not used by C++. +/// Unwinds stack, calling "stop" function at each frame. +/// Could be used to implement longjmp(). +_LIBUNWIND_EXPORT _Unwind_Reason_Code +_Unwind_ForcedUnwind(_Unwind_Exception *exception_object, _Unwind_Stop_Fn stop, + void *stop_parameter) { + _LIBUNWIND_TRACE_API("_Unwind_ForcedUnwind(ex_obj=%p, stop=%p)", + (void *)exception_object, (void *)(uintptr_t)stop); + unw_context_t uc; + unw_cursor_t cursor; + __unw_getcontext(&uc); + + // Mark that this is a forced unwind, so _Unwind_Resume() can do + // the right thing. + exception_object->unwinder_cache.reserved1 = (uintptr_t)stop; + exception_object->unwinder_cache.reserved3 = (uintptr_t)stop_parameter; + + return unwind_phase2_forced(&uc, &cursor, exception_object, stop, + stop_parameter); +} + /// Called by personality handler during phase 2 to find the start of the /// function. _LIBUNWIND_EXPORT uintptr_t @@ -997,10 +1196,16 @@ _Unwind_DeleteException(_Unwind_Exception *exception_object) { extern "C" _LIBUNWIND_EXPORT _Unwind_Reason_Code __gnu_unwind_frame(_Unwind_Exception *exception_object, struct _Unwind_Context *context) { + (void)exception_object; unw_cursor_t *cursor = (unw_cursor_t *)context; - if (__unw_step(cursor) != UNW_STEP_SUCCESS) + switch (__unw_step(cursor)) { + case UNW_STEP_SUCCESS: + return _URC_OK; + case UNW_STEP_END: + return _URC_END_OF_STACK; + default: return _URC_FAILURE; - return _URC_OK; + } } #endif // defined(_LIBUNWIND_ARM_EHABI) diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-EHABI.h b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-EHABI.h index 4ca3f90a81..46c7e3098b 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-EHABI.h +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-EHABI.h @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===------------------------- Unwind-EHABI.hpp ---------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-seh.cpp b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-seh.cpp index d892c2f4df..0ff679f209 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-seh.cpp +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-seh.cpp @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===--------------------------- Unwind-seh.cpp ---------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -107,7 +107,7 @@ _GCC_specific_handler(PEXCEPTION_RECORD ms_exc, PVOID frame, PCONTEXT ms_ctx, if (!ctx) { __unw_init_seh(&cursor, disp->ContextRecord); __unw_seh_set_disp_ctx(&cursor, disp); - __unw_set_reg(&cursor, UNW_REG_IP, disp->ControlPc - 1); + __unw_set_reg(&cursor, UNW_REG_IP, disp->ControlPc); ctx = (struct _Unwind_Context *)&cursor; if (!IS_UNWINDING(ms_exc->ExceptionFlags)) { @@ -172,8 +172,8 @@ _GCC_specific_handler(PEXCEPTION_RECORD ms_exc, PVOID frame, PCONTEXT ms_ctx, __unw_get_reg(&cursor, UNW_ARM_R1, &exc->private_[3]); #elif defined(__aarch64__) exc->private_[2] = disp->TargetPc; - __unw_get_reg(&cursor, UNW_ARM64_X0, &retval); - __unw_get_reg(&cursor, UNW_ARM64_X1, &exc->private_[3]); + __unw_get_reg(&cursor, UNW_AARCH64_X0, &retval); + __unw_get_reg(&cursor, UNW_AARCH64_X1, &exc->private_[3]); #endif __unw_get_reg(&cursor, UNW_REG_IP, &target); ms_exc->ExceptionCode = STATUS_GCC_UNWIND; @@ -247,6 +247,7 @@ unwind_phase2_forced(unw_context_t *uc, return _URC_FATAL_PHASE2_ERROR; } +#ifndef NDEBUG // When tracing, print state information. if (_LIBUNWIND_TRACING_UNWINDING) { char functionBuf[512]; @@ -257,11 +258,12 @@ unwind_phase2_forced(unw_context_t *uc, (frameInfo.start_ip + offset > frameInfo.end_ip)) functionName = ".anonymous."; _LIBUNWIND_TRACE_UNWINDING( - "unwind_phase2_forced(ex_ojb=%p): start_ip=0x%" PRIx64 - ", func=%s, lsda=0x%" PRIx64 ", personality=0x%" PRIx64, + "unwind_phase2_forced(ex_ojb=%p): start_ip=0x%" PRIxPTR + ", func=%s, lsda=0x%" PRIxPTR ", personality=0x%" PRIxPTR, (void *)exception_object, frameInfo.start_ip, functionName, frameInfo.lsda, frameInfo.handler); } +#endif // Call stop function at each frame. _Unwind_Action action = diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-sjlj.c b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-sjlj.c index ba9b2dafe6..02c3828229 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-sjlj.c +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-sjlj.c @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===--------------------------- Unwind-sjlj.c ----------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindCursor.hpp b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindCursor.hpp index e2082f0a6e..a575f3702b 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindCursor.hpp +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindCursor.hpp @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===------------------------- UnwindCursor.hpp ---------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -14,6 +14,7 @@ #ifndef __UNWINDCURSOR_HPP__ #define __UNWINDCURSOR_HPP__ +#include "cet_unwind.h" #include #include #include @@ -26,6 +27,19 @@ #ifdef __APPLE__ #include "mach-o/dyld.h" #endif +#ifdef _AIX +#include +#include +#include +#endif + +#if defined(_LIBUNWIND_TARGET_LINUX) && \ + (defined(_LIBUNWIND_TARGET_AARCH64) || defined(_LIBUNWIND_TARGET_S390X)) +#include +#include +#include +#define _LIBUNWIND_CHECK_LINUX_SIGRETURN 1 +#endif #if defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) // Provide a definition for the DISPATCHER_CONTEXT struct for old (Win7 and @@ -452,6 +466,18 @@ class _LIBUNWIND_HIDDEN AbstractUnwindCursor { #ifdef __arm__ virtual void saveVFPAsX() { _LIBUNWIND_ABORT("saveVFPAsX not implemented"); } #endif + +#ifdef _AIX + virtual uintptr_t getDataRelBase() { + _LIBUNWIND_ABORT("getDataRelBase not implemented"); + } +#endif + +#if defined(_LIBUNWIND_USE_CET) + virtual void *get_registers() { + _LIBUNWIND_ABORT("get_registers not implemented"); + } +#endif }; #if defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) && defined(_WIN32) @@ -495,6 +521,14 @@ class UnwindCursor : public AbstractUnwindCursor { pint_t getLastPC() const { return _dispContext.ControlPc; } void setLastPC(pint_t pc) { _dispContext.ControlPc = pc; } RUNTIME_FUNCTION *lookUpSEHUnwindInfo(pint_t pc, pint_t *base) { +#ifdef __arm__ + // Remove the thumb bit; FunctionEntry ranges don't include the thumb bit. + pc &= ~1U; +#endif + // If pc points exactly at the end of the range, we might resolve the + // next function instead. Decrement pc by 1 to fit inside the current + // function. + pc -= 1; _dispContext.FunctionEntry = RtlLookupFunctionEntry(pc, &_dispContext.ImageBase, _dispContext.HistoryTable); @@ -623,12 +657,12 @@ UnwindCursor::UnwindCursor(unw_context_t *context, A &as) _msContext.D[i - UNW_ARM_D0] = d.w; } #elif defined(_LIBUNWIND_TARGET_AARCH64) - for (int i = UNW_ARM64_X0; i <= UNW_ARM64_X30; ++i) - _msContext.X[i - UNW_ARM64_X0] = r.getRegister(i); + for (int i = UNW_AARCH64_X0; i <= UNW_ARM64_X30; ++i) + _msContext.X[i - UNW_AARCH64_X0] = r.getRegister(i); _msContext.Sp = r.getRegister(UNW_REG_SP); _msContext.Pc = r.getRegister(UNW_REG_IP); - for (int i = UNW_ARM64_D0; i <= UNW_ARM64_D31; ++i) - _msContext.V[i - UNW_ARM64_D0].D[0] = r.getFloatRegister(i); + for (int i = UNW_AARCH64_V0; i <= UNW_ARM64_D31; ++i) + _msContext.V[i - UNW_AARCH64_V0].D[0] = r.getFloatRegister(i); #endif } @@ -651,9 +685,11 @@ bool UnwindCursor::validReg(int regNum) { #if defined(_LIBUNWIND_TARGET_X86_64) if (regNum >= UNW_X86_64_RAX && regNum <= UNW_X86_64_R15) return true; #elif defined(_LIBUNWIND_TARGET_ARM) - if (regNum >= UNW_ARM_R0 && regNum <= UNW_ARM_R15) return true; + if ((regNum >= UNW_ARM_R0 && regNum <= UNW_ARM_R15) || + regNum == UNW_ARM_RA_AUTH_CODE) + return true; #elif defined(_LIBUNWIND_TARGET_AARCH64) - if (regNum >= UNW_ARM64_X0 && regNum <= UNW_ARM64_X30) return true; + if (regNum >= UNW_AARCH64_X0 && regNum <= UNW_ARM64_X30) return true; #endif return false; } @@ -702,7 +738,7 @@ unw_word_t UnwindCursor::getReg(int regNum) { #elif defined(_LIBUNWIND_TARGET_AARCH64) case UNW_REG_SP: return _msContext.Sp; case UNW_REG_IP: return _msContext.Pc; - default: return _msContext.X[regNum - UNW_ARM64_X0]; + default: return _msContext.X[regNum - UNW_AARCH64_X0]; #endif } _LIBUNWIND_ABORT("unsupported register"); @@ -752,37 +788,37 @@ void UnwindCursor::setReg(int regNum, unw_word_t value) { #elif defined(_LIBUNWIND_TARGET_AARCH64) case UNW_REG_SP: _msContext.Sp = value; break; case UNW_REG_IP: _msContext.Pc = value; break; - case UNW_ARM64_X0: - case UNW_ARM64_X1: - case UNW_ARM64_X2: - case UNW_ARM64_X3: - case UNW_ARM64_X4: - case UNW_ARM64_X5: - case UNW_ARM64_X6: - case UNW_ARM64_X7: - case UNW_ARM64_X8: - case UNW_ARM64_X9: - case UNW_ARM64_X10: - case UNW_ARM64_X11: - case UNW_ARM64_X12: - case UNW_ARM64_X13: - case UNW_ARM64_X14: - case UNW_ARM64_X15: - case UNW_ARM64_X16: - case UNW_ARM64_X17: - case UNW_ARM64_X18: - case UNW_ARM64_X19: - case UNW_ARM64_X20: - case UNW_ARM64_X21: - case UNW_ARM64_X22: - case UNW_ARM64_X23: - case UNW_ARM64_X24: - case UNW_ARM64_X25: - case UNW_ARM64_X26: - case UNW_ARM64_X27: - case UNW_ARM64_X28: - case UNW_ARM64_FP: - case UNW_ARM64_LR: _msContext.X[regNum - UNW_ARM64_X0] = value; break; + case UNW_AARCH64_X0: + case UNW_AARCH64_X1: + case UNW_AARCH64_X2: + case UNW_AARCH64_X3: + case UNW_AARCH64_X4: + case UNW_AARCH64_X5: + case UNW_AARCH64_X6: + case UNW_AARCH64_X7: + case UNW_AARCH64_X8: + case UNW_AARCH64_X9: + case UNW_AARCH64_X10: + case UNW_AARCH64_X11: + case UNW_AARCH64_X12: + case UNW_AARCH64_X13: + case UNW_AARCH64_X14: + case UNW_AARCH64_X15: + case UNW_AARCH64_X16: + case UNW_AARCH64_X17: + case UNW_AARCH64_X18: + case UNW_AARCH64_X19: + case UNW_AARCH64_X20: + case UNW_AARCH64_X21: + case UNW_AARCH64_X22: + case UNW_AARCH64_X23: + case UNW_AARCH64_X24: + case UNW_AARCH64_X25: + case UNW_AARCH64_X26: + case UNW_AARCH64_X27: + case UNW_AARCH64_X28: + case UNW_AARCH64_FP: + case UNW_AARCH64_LR: _msContext.X[regNum - UNW_ARM64_X0] = value; break; #endif default: _LIBUNWIND_ABORT("unsupported register"); @@ -795,7 +831,7 @@ bool UnwindCursor::validFloatReg(int regNum) { if (regNum >= UNW_ARM_S0 && regNum <= UNW_ARM_S31) return true; if (regNum >= UNW_ARM_D0 && regNum <= UNW_ARM_D31) return true; #elif defined(_LIBUNWIND_TARGET_AARCH64) - if (regNum >= UNW_ARM64_D0 && regNum <= UNW_ARM64_D31) return true; + if (regNum >= UNW_AARCH64_V0 && regNum <= UNW_ARM64_D31) return true; #else (void)regNum; #endif @@ -823,7 +859,7 @@ unw_fpreg_t UnwindCursor::getFloatReg(int regNum) { } _LIBUNWIND_ABORT("unsupported float register"); #elif defined(_LIBUNWIND_TARGET_AARCH64) - return _msContext.V[regNum - UNW_ARM64_D0].D[0]; + return _msContext.V[regNum - UNW_AARCH64_V0].D[0]; #else (void)regNum; _LIBUNWIND_ABORT("float registers unimplemented"); @@ -838,7 +874,7 @@ void UnwindCursor::setFloatReg(int regNum, unw_fpreg_t value) { uint32_t w; float f; } d; - d.f = value; + d.f = (float)value; _msContext.S[regNum - UNW_ARM_S0] = d.w; } if (regNum >= UNW_ARM_D0 && regNum <= UNW_ARM_D31) { @@ -851,7 +887,7 @@ void UnwindCursor::setFloatReg(int regNum, unw_fpreg_t value) { } _LIBUNWIND_ABORT("unsupported float register"); #elif defined(_LIBUNWIND_TARGET_AARCH64) - _msContext.V[regNum - UNW_ARM64_D0].D[0] = value; + _msContext.V[regNum - UNW_AARCH64_V0].D[0] = value; #else (void)regNum; (void)value; @@ -904,6 +940,14 @@ class UnwindCursor : public AbstractUnwindCursor{ virtual void saveVFPAsX(); #endif +#ifdef _AIX + virtual uintptr_t getDataRelBase(); +#endif + +#if defined(_LIBUNWIND_USE_CET) + virtual void *get_registers() { return &_registers; } +#endif + // libunwind does not and should not depend on C++ library which means that we // need our own defition of inline placement new. static void *operator new(size_t, UnwindCursor *p) { return p; } @@ -928,7 +972,7 @@ class UnwindCursor : public AbstractUnwindCursor{ } #endif -#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64) +#if defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN) bool setInfoForSigReturn() { R dummy; return setInfoForSigReturn(dummy); @@ -937,8 +981,14 @@ class UnwindCursor : public AbstractUnwindCursor{ R dummy; return stepThroughSigReturn(dummy); } +#if defined(_LIBUNWIND_TARGET_AARCH64) bool setInfoForSigReturn(Registers_arm64 &); int stepThroughSigReturn(Registers_arm64 &); +#endif +#if defined(_LIBUNWIND_TARGET_S390X) + bool setInfoForSigReturn(Registers_s390x &); + int stepThroughSigReturn(Registers_s390x &); +#endif template bool setInfoForSigReturn(Registers &) { return false; } @@ -1023,6 +1073,10 @@ class UnwindCursor : public AbstractUnwindCursor{ int stepWithCompactEncoding(Registers_sparc &) { return UNW_EINVAL; } #endif +#if defined(_LIBUNWIND_TARGET_SPARC64) + int stepWithCompactEncoding(Registers_sparc64 &) { return UNW_EINVAL; } +#endif + #if defined (_LIBUNWIND_TARGET_RISCV) int stepWithCompactEncoding(Registers_riscv &) { return UNW_EINVAL; @@ -1095,6 +1149,12 @@ class UnwindCursor : public AbstractUnwindCursor{ bool compactSaysUseDwarf(Registers_sparc &, uint32_t *) const { return true; } #endif +#if defined(_LIBUNWIND_TARGET_SPARC64) + bool compactSaysUseDwarf(Registers_sparc64 &, uint32_t *) const { + return true; + } +#endif + #if defined (_LIBUNWIND_TARGET_RISCV) bool compactSaysUseDwarf(Registers_riscv &, uint32_t *) const { return true; @@ -1173,12 +1233,24 @@ class UnwindCursor : public AbstractUnwindCursor{ compact_unwind_encoding_t dwarfEncoding(Registers_sparc &) const { return 0; } #endif +#if defined(_LIBUNWIND_TARGET_SPARC64) + compact_unwind_encoding_t dwarfEncoding(Registers_sparc64 &) const { + return 0; + } +#endif + #if defined (_LIBUNWIND_TARGET_RISCV) compact_unwind_encoding_t dwarfEncoding(Registers_riscv &) const { return 0; } #endif +#if defined (_LIBUNWIND_TARGET_S390X) + compact_unwind_encoding_t dwarfEncoding(Registers_s390x &) const { + return 0; + } +#endif + #endif // defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) #if defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) @@ -1195,13 +1267,23 @@ class UnwindCursor : public AbstractUnwindCursor{ int stepWithSEHData() { /* FIXME: Implement */ return 0; } #endif // defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) +#if defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND) + bool getInfoFromTBTable(pint_t pc, R ®isters); + int stepWithTBTable(pint_t pc, tbtable *TBTable, R ®isters, + bool &isSignalFrame); + int stepWithTBTableData() { + return stepWithTBTable(reinterpret_cast(this->getReg(UNW_REG_IP)), + reinterpret_cast(_info.unwind_info), + _registers, _isSignalFrame); + } +#endif // defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND) A &_addressSpace; R _registers; unw_proc_info_t _info; bool _unwindInfoMissing; bool _isSignalFrame; -#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64) +#if defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN) bool _isSigReturn = false; #endif }; @@ -1267,6 +1349,13 @@ template void UnwindCursor::saveVFPAsX() { } #endif +#ifdef _AIX +template +uintptr_t UnwindCursor::getDataRelBase() { + return reinterpret_cast(_info.extra); +} +#endif + template const char *UnwindCursor::getRegisterName(int regNum) { return _registers.getRegisterName(regNum); @@ -1740,14 +1829,16 @@ bool UnwindCursor::getInfoFromCompactEncodingSection(pint_t pc, else funcEnd = firstLevelNextPageFunctionOffset + sects.dso_base; if (pc < funcStart) { - _LIBUNWIND_DEBUG_LOG("malformed __unwind_info, pc=0x%llX not in second " - "level compressed unwind table. funcStart=0x%llX", + _LIBUNWIND_DEBUG_LOG("malformed __unwind_info, pc=0x%llX " + "not in second level compressed unwind table. " + "funcStart=0x%llX", (uint64_t) pc, (uint64_t) funcStart); return false; } if (pc > funcEnd) { - _LIBUNWIND_DEBUG_LOG("malformed __unwind_info, pc=0x%llX not in second " - "level compressed unwind table. funcEnd=0x%llX", + _LIBUNWIND_DEBUG_LOG("malformed __unwind_info, pc=0x%llX " + "not in second level compressed unwind table. " + "funcEnd=0x%llX", (uint64_t) pc, (uint64_t) funcEnd); return false; } @@ -1767,9 +1858,9 @@ bool UnwindCursor::getInfoFromCompactEncodingSection(pint_t pc, pageEncodingIndex * sizeof(uint32_t)); } } else { - _LIBUNWIND_DEBUG_LOG("malformed __unwind_info at 0x%0llX bad second " - "level page", - (uint64_t) sects.compact_unwind_section); + _LIBUNWIND_DEBUG_LOG( + "malformed __unwind_info at 0x%0llX bad second level page", + (uint64_t)sects.compact_unwind_section); return false; } @@ -1885,20 +1976,517 @@ bool UnwindCursor::getInfoFromSEH(pint_t pc) { _info.handler = 0; } } -#elif defined(_LIBUNWIND_TARGET_ARM) - _info.end_ip = _info.start_ip + unwindEntry->FunctionLength; - _info.lsda = 0; // FIXME - _info.handler = 0; // FIXME #endif setLastPC(pc); return true; } #endif +#if defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND) +// Masks for traceback table field xtbtable. +enum xTBTableMask : uint8_t { + reservedBit = 0x02, // The traceback table was incorrectly generated if set + // (see comments in function getInfoFromTBTable(). + ehInfoBit = 0x08 // Exception handling info is present if set +}; + +enum frameType : unw_word_t { + frameWithXLEHStateTable = 0, + frameWithEHInfo = 1 +}; + +extern "C" { +typedef _Unwind_Reason_Code __xlcxx_personality_v0_t(int, _Unwind_Action, + uint64_t, + _Unwind_Exception *, + struct _Unwind_Context *); +__attribute__((__weak__)) __xlcxx_personality_v0_t __xlcxx_personality_v0; +} + +static __xlcxx_personality_v0_t *xlcPersonalityV0; +static RWMutex xlcPersonalityV0InitLock; + +template +bool UnwindCursor::getInfoFromTBTable(pint_t pc, R ®isters) { + uint32_t *p = reinterpret_cast(pc); + + // Keep looking forward until a word of 0 is found. The traceback + // table starts at the following word. + while (*p) + ++p; + tbtable *TBTable = reinterpret_cast(p + 1); + + if (_LIBUNWIND_TRACING_UNWINDING) { + char functionBuf[512]; + const char *functionName = functionBuf; + unw_word_t offset; + if (!getFunctionName(functionBuf, sizeof(functionBuf), &offset)) { + functionName = ".anonymous."; + } + _LIBUNWIND_TRACE_UNWINDING("%s: Look up traceback table of func=%s at %p", + __func__, functionName, + reinterpret_cast(TBTable)); + } + + // If the traceback table does not contain necessary info, bypass this frame. + if (!TBTable->tb.has_tboff) + return false; + + // Structure tbtable_ext contains important data we are looking for. + p = reinterpret_cast(&TBTable->tb_ext); + + // Skip field parminfo if it exists. + if (TBTable->tb.fixedparms || TBTable->tb.floatparms) + ++p; + + // p now points to tb_offset, the offset from start of function to TB table. + unw_word_t start_ip = + reinterpret_cast(TBTable) - *p - sizeof(uint32_t); + unw_word_t end_ip = reinterpret_cast(TBTable); + ++p; + + _LIBUNWIND_TRACE_UNWINDING("start_ip=%p, end_ip=%p\n", + reinterpret_cast(start_ip), + reinterpret_cast(end_ip)); + + // Skip field hand_mask if it exists. + if (TBTable->tb.int_hndl) + ++p; + + unw_word_t lsda = 0; + unw_word_t handler = 0; + unw_word_t flags = frameType::frameWithXLEHStateTable; + + if (TBTable->tb.lang == TB_CPLUSPLUS && TBTable->tb.has_ctl) { + // State table info is available. The ctl_info field indicates the + // number of CTL anchors. There should be only one entry for the C++ + // state table. + assert(*p == 1 && "libunwind: there must be only one ctl_info entry"); + ++p; + // p points to the offset of the state table into the stack. + pint_t stateTableOffset = *p++; + + int framePointerReg; + + // Skip fields name_len and name if exist. + if (TBTable->tb.name_present) { + const uint16_t name_len = *(reinterpret_cast(p)); + p = reinterpret_cast(reinterpret_cast(p) + name_len + + sizeof(uint16_t)); + } + + if (TBTable->tb.uses_alloca) + framePointerReg = *(reinterpret_cast(p)); + else + framePointerReg = 1; // default frame pointer == SP + + _LIBUNWIND_TRACE_UNWINDING( + "framePointerReg=%d, framePointer=%p, " + "stateTableOffset=%#lx\n", + framePointerReg, + reinterpret_cast(_registers.getRegister(framePointerReg)), + stateTableOffset); + lsda = _registers.getRegister(framePointerReg) + stateTableOffset; + + // Since the traceback table generated by the legacy XLC++ does not + // provide the location of the personality for the state table, + // function __xlcxx_personality_v0(), which is the personality for the state + // table and is exported from libc++abi, is directly assigned as the + // handler here. When a legacy XLC++ frame is encountered, the symbol + // is resolved dynamically using dlopen() to avoid hard dependency from + // libunwind on libc++abi. + + // Resolve the function pointer to the state table personality if it has + // not already. + if (xlcPersonalityV0 == NULL) { + xlcPersonalityV0InitLock.lock(); + if (xlcPersonalityV0 == NULL) { + // If libc++abi is statically linked in, symbol __xlcxx_personality_v0 + // has been resolved at the link time. + xlcPersonalityV0 = &__xlcxx_personality_v0; + if (xlcPersonalityV0 == NULL) { + // libc++abi is dynamically linked. Resolve __xlcxx_personality_v0 + // using dlopen(). + const char libcxxabi[] = "libc++abi.a(libc++abi.so.1)"; + void *libHandle; + libHandle = dlopen(libcxxabi, RTLD_MEMBER | RTLD_NOW); + if (libHandle == NULL) { + _LIBUNWIND_TRACE_UNWINDING("dlopen() failed with errno=%d\n", + errno); + assert(0 && "dlopen() failed"); + } + xlcPersonalityV0 = reinterpret_cast<__xlcxx_personality_v0_t *>( + dlsym(libHandle, "__xlcxx_personality_v0")); + if (xlcPersonalityV0 == NULL) { + _LIBUNWIND_TRACE_UNWINDING("dlsym() failed with errno=%d\n", errno); + assert(0 && "dlsym() failed"); + } + dlclose(libHandle); + } + } + xlcPersonalityV0InitLock.unlock(); + } + handler = reinterpret_cast(xlcPersonalityV0); + _LIBUNWIND_TRACE_UNWINDING("State table: LSDA=%p, Personality=%p\n", + reinterpret_cast(lsda), + reinterpret_cast(handler)); + } else if (TBTable->tb.longtbtable) { + // This frame has the traceback table extension. Possible cases are + // 1) a C++ frame that has the 'eh_info' structure; 2) a C++ frame that + // is not EH aware; or, 3) a frame of other languages. We need to figure out + // if the traceback table extension contains the 'eh_info' structure. + // + // We also need to deal with the complexity arising from some XL compiler + // versions use the wrong ordering of 'longtbtable' and 'has_vec' bits + // where the 'longtbtable' bit is meant to be the 'has_vec' bit and vice + // versa. For frames of code generated by those compilers, the 'longtbtable' + // bit may be set but there isn't really a traceback table extension. + // + // In , there is the following definition of + // 'struct tbtable_ext'. It is not really a structure but a dummy to + // collect the description of optional parts of the traceback table. + // + // struct tbtable_ext { + // ... + // char alloca_reg; /* Register for alloca automatic storage */ + // struct vec_ext vec_ext; /* Vector extension (if has_vec is set) */ + // unsigned char xtbtable; /* More tbtable fields, if longtbtable is set*/ + // }; + // + // Depending on how the 'has_vec'/'longtbtable' bit is interpreted, the data + // following 'alloca_reg' can be treated either as 'struct vec_ext' or + // 'unsigned char xtbtable'. 'xtbtable' bits are defined in + // as flags. The 7th bit '0x02' is currently + // unused and should not be set. 'struct vec_ext' is defined in + // as follows: + // + // struct vec_ext { + // unsigned vr_saved:6; /* Number of non-volatile vector regs saved + // */ + // /* first register saved is assumed to be */ + // /* 32 - vr_saved */ + // unsigned saves_vrsave:1; /* Set if vrsave is saved on the stack */ + // unsigned has_varargs:1; + // ... + // }; + // + // Here, the 7th bit is used as 'saves_vrsave'. To determine whether it + // is 'struct vec_ext' or 'xtbtable' that follows 'alloca_reg', + // we checks if the 7th bit is set or not because 'xtbtable' should + // never have the 7th bit set. The 7th bit of 'xtbtable' will be reserved + // in the future to make sure the mitigation works. This mitigation + // is not 100% bullet proof because 'struct vec_ext' may not always have + // 'saves_vrsave' bit set. + // + // 'reservedBit' is defined in enum 'xTBTableMask' above as the mask for + // checking the 7th bit. + + // p points to field name len. + uint8_t *charPtr = reinterpret_cast(p); + + // Skip fields name_len and name if they exist. + if (TBTable->tb.name_present) { + const uint16_t name_len = *(reinterpret_cast(charPtr)); + charPtr = charPtr + name_len + sizeof(uint16_t); + } + + // Skip field alloc_reg if it exists. + if (TBTable->tb.uses_alloca) + ++charPtr; + + // Check traceback table bit has_vec. Skip struct vec_ext if it exists. + if (TBTable->tb.has_vec) + // Note struct vec_ext does exist at this point because whether the + // ordering of longtbtable and has_vec bits is correct or not, both + // are set. + charPtr += sizeof(struct vec_ext); + + // charPtr points to field 'xtbtable'. Check if the EH info is available. + // Also check if the reserved bit of the extended traceback table field + // 'xtbtable' is set. If it is, the traceback table was incorrectly + // generated by an XL compiler that uses the wrong ordering of 'longtbtable' + // and 'has_vec' bits and this is in fact 'struct vec_ext'. So skip the + // frame. + if ((*charPtr & xTBTableMask::ehInfoBit) && + !(*charPtr & xTBTableMask::reservedBit)) { + // Mark this frame has the new EH info. + flags = frameType::frameWithEHInfo; + + // eh_info is available. + charPtr++; + // The pointer is 4-byte aligned. + if (reinterpret_cast(charPtr) % 4) + charPtr += 4 - reinterpret_cast(charPtr) % 4; + uintptr_t *ehInfo = + reinterpret_cast(*(reinterpret_cast( + registers.getRegister(2) + + *(reinterpret_cast(charPtr))))); + + // ehInfo points to structure en_info. The first member is version. + // Only version 0 is currently supported. + assert(*(reinterpret_cast(ehInfo)) == 0 && + "libunwind: ehInfo version other than 0 is not supported"); + + // Increment ehInfo to point to member lsda. + ++ehInfo; + lsda = *ehInfo++; + + // enInfo now points to member personality. + handler = *ehInfo; + + _LIBUNWIND_TRACE_UNWINDING("Range table: LSDA=%#lx, Personality=%#lx\n", + lsda, handler); + } + } + + _info.start_ip = start_ip; + _info.end_ip = end_ip; + _info.lsda = lsda; + _info.handler = handler; + _info.gp = 0; + _info.flags = flags; + _info.format = 0; + _info.unwind_info = reinterpret_cast(TBTable); + _info.unwind_info_size = 0; + _info.extra = registers.getRegister(2); + + return true; +} + +// Step back up the stack following the frame back link. +template +int UnwindCursor::stepWithTBTable(pint_t pc, tbtable *TBTable, + R ®isters, bool &isSignalFrame) { + if (_LIBUNWIND_TRACING_UNWINDING) { + char functionBuf[512]; + const char *functionName = functionBuf; + unw_word_t offset; + if (!getFunctionName(functionBuf, sizeof(functionBuf), &offset)) { + functionName = ".anonymous."; + } + _LIBUNWIND_TRACE_UNWINDING("%s: Look up traceback table of func=%s at %p", + __func__, functionName, + reinterpret_cast(TBTable)); + } + +#if defined(__powerpc64__) + // Instruction to reload TOC register "l r2,40(r1)" + const uint32_t loadTOCRegInst = 0xe8410028; + const int32_t unwPPCF0Index = UNW_PPC64_F0; + const int32_t unwPPCV0Index = UNW_PPC64_V0; +#else + // Instruction to reload TOC register "l r2,20(r1)" + const uint32_t loadTOCRegInst = 0x80410014; + const int32_t unwPPCF0Index = UNW_PPC_F0; + const int32_t unwPPCV0Index = UNW_PPC_V0; +#endif + + R newRegisters = registers; + + // lastStack points to the stack frame of the next routine up. + pint_t lastStack = *(reinterpret_cast(registers.getSP())); + + // Return address is the address after call site instruction. + pint_t returnAddress; + + if (isSignalFrame) { + _LIBUNWIND_TRACE_UNWINDING("Possible signal handler frame: lastStack=%p", + reinterpret_cast(lastStack)); + + sigcontext *sigContext = reinterpret_cast( + reinterpret_cast(lastStack) + STKMIN); + returnAddress = sigContext->sc_jmpbuf.jmp_context.iar; + + _LIBUNWIND_TRACE_UNWINDING("From sigContext=%p, returnAddress=%p\n", + reinterpret_cast(sigContext), + reinterpret_cast(returnAddress)); + + if (returnAddress < 0x10000000) { + // Try again using STKMINALIGN + sigContext = reinterpret_cast( + reinterpret_cast(lastStack) + STKMINALIGN); + returnAddress = sigContext->sc_jmpbuf.jmp_context.iar; + if (returnAddress < 0x10000000) { + _LIBUNWIND_TRACE_UNWINDING("Bad returnAddress=%p\n", + reinterpret_cast(returnAddress)); + return UNW_EBADFRAME; + } else { + _LIBUNWIND_TRACE_UNWINDING("Tried again using STKMINALIGN: " + "sigContext=%p, returnAddress=%p. " + "Seems to be a valid address\n", + reinterpret_cast(sigContext), + reinterpret_cast(returnAddress)); + } + } + // Restore the condition register from sigcontext. + newRegisters.setCR(sigContext->sc_jmpbuf.jmp_context.cr); + + // Restore GPRs from sigcontext. + for (int i = 0; i < 32; ++i) + newRegisters.setRegister(i, sigContext->sc_jmpbuf.jmp_context.gpr[i]); + + // Restore FPRs from sigcontext. + for (int i = 0; i < 32; ++i) + newRegisters.setFloatRegister(i + unwPPCF0Index, + sigContext->sc_jmpbuf.jmp_context.fpr[i]); + + // Restore vector registers if there is an associated extended context + // structure. + if (sigContext->sc_jmpbuf.jmp_context.msr & __EXTCTX) { + ucontext_t *uContext = reinterpret_cast(sigContext); + if (uContext->__extctx->__extctx_magic == __EXTCTX_MAGIC) { + for (int i = 0; i < 32; ++i) + newRegisters.setVectorRegister( + i + unwPPCV0Index, *(reinterpret_cast( + &(uContext->__extctx->__vmx.__vr[i])))); + } + } + } else { + // Step up a normal frame. + returnAddress = reinterpret_cast(lastStack)[2]; + + _LIBUNWIND_TRACE_UNWINDING("Extract info from lastStack=%p, " + "returnAddress=%p\n", + reinterpret_cast(lastStack), + reinterpret_cast(returnAddress)); + _LIBUNWIND_TRACE_UNWINDING("fpr_regs=%d, gpr_regs=%d, saves_cr=%d\n", + TBTable->tb.fpr_saved, TBTable->tb.gpr_saved, + TBTable->tb.saves_cr); + + // Restore FP registers. + char *ptrToRegs = reinterpret_cast(lastStack); + double *FPRegs = reinterpret_cast( + ptrToRegs - (TBTable->tb.fpr_saved * sizeof(double))); + for (int i = 0; i < TBTable->tb.fpr_saved; ++i) + newRegisters.setFloatRegister( + 32 - TBTable->tb.fpr_saved + i + unwPPCF0Index, FPRegs[i]); + + // Restore GP registers. + ptrToRegs = reinterpret_cast(FPRegs); + uintptr_t *GPRegs = reinterpret_cast( + ptrToRegs - (TBTable->tb.gpr_saved * sizeof(uintptr_t))); + for (int i = 0; i < TBTable->tb.gpr_saved; ++i) + newRegisters.setRegister(32 - TBTable->tb.gpr_saved + i, GPRegs[i]); + + // Restore Vector registers. + ptrToRegs = reinterpret_cast(GPRegs); + + // Restore vector registers only if this is a Clang frame. Also + // check if traceback table bit has_vec is set. If it is, structure + // vec_ext is available. + if (_info.flags == frameType::frameWithEHInfo && TBTable->tb.has_vec) { + + // Get to the vec_ext structure to check if vector registers are saved. + uint32_t *p = reinterpret_cast(&TBTable->tb_ext); + + // Skip field parminfo if exists. + if (TBTable->tb.fixedparms || TBTable->tb.floatparms) + ++p; + + // Skip field tb_offset if exists. + if (TBTable->tb.has_tboff) + ++p; + + // Skip field hand_mask if exists. + if (TBTable->tb.int_hndl) + ++p; + + // Skip fields ctl_info and ctl_info_disp if exist. + if (TBTable->tb.has_ctl) { + // Skip field ctl_info. + ++p; + // Skip field ctl_info_disp. + ++p; + } + + // Skip fields name_len and name if exist. + // p is supposed to point to field name_len now. + uint8_t *charPtr = reinterpret_cast(p); + if (TBTable->tb.name_present) { + const uint16_t name_len = *(reinterpret_cast(charPtr)); + charPtr = charPtr + name_len + sizeof(uint16_t); + } + + // Skip field alloc_reg if it exists. + if (TBTable->tb.uses_alloca) + ++charPtr; + + struct vec_ext *vec_ext = reinterpret_cast(charPtr); + + _LIBUNWIND_TRACE_UNWINDING("vr_saved=%d\n", vec_ext->vr_saved); + + // Restore vector register(s) if saved on the stack. + if (vec_ext->vr_saved) { + // Saved vector registers are 16-byte aligned. + if (reinterpret_cast(ptrToRegs) % 16) + ptrToRegs -= reinterpret_cast(ptrToRegs) % 16; + v128 *VecRegs = reinterpret_cast(ptrToRegs - vec_ext->vr_saved * + sizeof(v128)); + for (int i = 0; i < vec_ext->vr_saved; ++i) { + newRegisters.setVectorRegister( + 32 - vec_ext->vr_saved + i + unwPPCV0Index, VecRegs[i]); + } + } + } + if (TBTable->tb.saves_cr) { + // Get the saved condition register. The condition register is only + // a single word. + newRegisters.setCR( + *(reinterpret_cast(lastStack + sizeof(uintptr_t)))); + } + + // Restore the SP. + newRegisters.setSP(lastStack); + + // The first instruction after return. + uint32_t firstInstruction = *(reinterpret_cast(returnAddress)); + + // Do we need to set the TOC register? + _LIBUNWIND_TRACE_UNWINDING( + "Current gpr2=%p\n", + reinterpret_cast(newRegisters.getRegister(2))); + if (firstInstruction == loadTOCRegInst) { + _LIBUNWIND_TRACE_UNWINDING( + "Set gpr2=%p from frame\n", + reinterpret_cast(reinterpret_cast(lastStack)[5])); + newRegisters.setRegister(2, reinterpret_cast(lastStack)[5]); + } + } + _LIBUNWIND_TRACE_UNWINDING("lastStack=%p, returnAddress=%p, pc=%p\n", + reinterpret_cast(lastStack), + reinterpret_cast(returnAddress), + reinterpret_cast(pc)); + + // The return address is the address after call site instruction, so + // setting IP to that simualates a return. + newRegisters.setIP(reinterpret_cast(returnAddress)); + + // Simulate the step by replacing the register set with the new ones. + registers = newRegisters; + + // Check if the next frame is a signal frame. + pint_t nextStack = *(reinterpret_cast(registers.getSP())); + + // Return address is the address after call site instruction. + pint_t nextReturnAddress = reinterpret_cast(nextStack)[2]; + + if (nextReturnAddress > 0x01 && nextReturnAddress < 0x10000) { + _LIBUNWIND_TRACE_UNWINDING("The next is a signal handler frame: " + "nextStack=%p, next return address=%p\n", + reinterpret_cast(nextStack), + reinterpret_cast(nextReturnAddress)); + isSignalFrame = true; + } else { + isSignalFrame = false; + } + + return UNW_STEP_SUCCESS; +} +#endif // defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND) template void UnwindCursor::setInfoBasedOnIPRegister(bool isReturnAddress) { -#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64) +#if defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN) _isSigReturn = false; #endif @@ -1921,7 +2509,14 @@ void UnwindCursor::setInfoBasedOnIPRegister(bool isReturnAddress) { // To disambiguate this, back up the pc when we know it is a return // address. if (isReturnAddress) +#if defined(_AIX) + // PC needs to be a 4-byte aligned address to be able to look for a + // word of 0 that indicates the start of the traceback table at the end + // of a function on AIX. + pc -= 4; +#else --pc; +#endif // Ask address space object to find unwind sections for this pc. UnwindInfoSections sects; @@ -1955,6 +2550,12 @@ void UnwindCursor::setInfoBasedOnIPRegister(bool isReturnAddress) { return; #endif +#if defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND) + // If there is unwind info in the traceback table, look there next. + if (this->getInfoFromTBTable(pc, _registers)) + return; +#endif + #if defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) // If there is dwarf unwind info, look there next. if (sects.dwarf_section != 0) { @@ -2000,7 +2601,7 @@ void UnwindCursor::setInfoBasedOnIPRegister(bool isReturnAddress) { } #endif // #if defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) -#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64) +#if defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN) if (setInfoForSigReturn()) return; #endif @@ -2009,7 +2610,8 @@ void UnwindCursor::setInfoBasedOnIPRegister(bool isReturnAddress) { _unwindInfoMissing = true; } -#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64) +#if defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN) && \ + defined(_LIBUNWIND_TARGET_AARCH64) template bool UnwindCursor::setInfoForSigReturn(Registers_arm64 &) { // Look for the sigreturn trampoline. The trampoline's body is two @@ -2028,14 +2630,28 @@ bool UnwindCursor::setInfoForSigReturn(Registers_arm64 &) { // // [1] https://github.com/torvalds/linux/blob/master/arch/arm64/kernel/vdso/sigreturn.S const pint_t pc = static_cast(this->getReg(UNW_REG_IP)); + // The PC might contain an invalid address if the unwind info is bad, so + // directly accessing it could cause a segfault. Use process_vm_readv to read + // the memory safely instead. process_vm_readv was added in Linux 3.2, and + // AArch64 supported was added in Linux 3.7, so the syscall is guaranteed to + // be present. Unfortunately, there are Linux AArch64 environments where the + // libc wrapper for the syscall might not be present (e.g. Android 5), so call + // the syscall directly instead. + uint32_t instructions[2]; + struct iovec local_iov = {&instructions, sizeof instructions}; + struct iovec remote_iov = {reinterpret_cast(pc), sizeof instructions}; + long bytesRead = + syscall(SYS_process_vm_readv, getpid(), &local_iov, 1, &remote_iov, 1, 0); // Look for instructions: mov x8, #0x8b; svc #0x0 - if (_addressSpace.get32(pc) == 0xd2801168 && - _addressSpace.get32(pc + 4) == 0xd4000001) { - _info = {}; - _isSigReturn = true; - return true; - } - return false; + if (bytesRead != sizeof instructions || instructions[0] != 0xd2801168 || + instructions[1] != 0xd4000001) + return false; + + _info = {}; + _info.start_ip = pc; + _info.end_ip = pc + 4; + _isSigReturn = true; + return true; } template @@ -2062,14 +2678,121 @@ int UnwindCursor::stepThroughSigReturn(Registers_arm64 &) { for (int i = 0; i <= 30; ++i) { uint64_t value = _addressSpace.get64(sigctx + kOffsetGprs + static_cast(i * 8)); - _registers.setRegister(UNW_ARM64_X0 + i, value); + _registers.setRegister(UNW_AARCH64_X0 + i, value); } _registers.setSP(_addressSpace.get64(sigctx + kOffsetSp)); _registers.setIP(_addressSpace.get64(sigctx + kOffsetPc)); _isSignalFrame = true; return UNW_STEP_SUCCESS; } -#endif // defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64) +#endif // defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN) && + // defined(_LIBUNWIND_TARGET_AARCH64) + +#if defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN) && \ + defined(_LIBUNWIND_TARGET_S390X) +template +bool UnwindCursor::setInfoForSigReturn(Registers_s390x &) { + // Look for the sigreturn trampoline. The trampoline's body is a + // specific instruction (see below). Typically the trampoline comes from the + // vDSO (i.e. the __kernel_[rt_]sigreturn function). A libc might provide its + // own restorer function, though, or user-mode QEMU might write a trampoline + // onto the stack. + const pint_t pc = static_cast(this->getReg(UNW_REG_IP)); + // The PC might contain an invalid address if the unwind info is bad, so + // directly accessing it could cause a segfault. Use process_vm_readv to + // read the memory safely instead. + uint16_t inst; + struct iovec local_iov = {&inst, sizeof inst}; + struct iovec remote_iov = {reinterpret_cast(pc), sizeof inst}; + long bytesRead = process_vm_readv(getpid(), &local_iov, 1, &remote_iov, 1, 0); + if (bytesRead == sizeof inst && (inst == 0x0a77 || inst == 0x0aad)) { + _info = {}; + _info.start_ip = pc; + _info.end_ip = pc + 2; + _isSigReturn = true; + return true; + } + return false; +} + +template +int UnwindCursor::stepThroughSigReturn(Registers_s390x &) { + // Determine current SP. + const pint_t sp = static_cast(this->getReg(UNW_REG_SP)); + // According to the s390x ABI, the CFA is at (incoming) SP + 160. + const pint_t cfa = sp + 160; + + // Determine current PC and instruction there (this must be either + // a "svc __NR_sigreturn" or "svc __NR_rt_sigreturn"). + const pint_t pc = static_cast(this->getReg(UNW_REG_IP)); + const uint16_t inst = _addressSpace.get16(pc); + + // Find the addresses of the signo and sigcontext in the frame. + pint_t pSigctx = 0; + pint_t pSigno = 0; + + // "svc __NR_sigreturn" uses a non-RT signal trampoline frame. + if (inst == 0x0a77) { + // Layout of a non-RT signal trampoline frame, starting at the CFA: + // - 8-byte signal mask + // - 8-byte pointer to sigcontext, followed by signo + // - 4-byte signo + pSigctx = _addressSpace.get64(cfa + 8); + pSigno = pSigctx + 344; + } + + // "svc __NR_rt_sigreturn" uses a RT signal trampoline frame. + if (inst == 0x0aad) { + // Layout of a RT signal trampoline frame, starting at the CFA: + // - 8-byte retcode (+ alignment) + // - 128-byte siginfo struct (starts with signo) + // - ucontext struct: + // - 8-byte long (uc_flags) + // - 8-byte pointer (uc_link) + // - 24-byte stack_t + // - 8 bytes of padding because sigcontext has 16-byte alignment + // - sigcontext/mcontext_t + pSigctx = cfa + 8 + 128 + 8 + 8 + 24 + 8; + pSigno = cfa + 8; + } + + assert(pSigctx != 0); + assert(pSigno != 0); + + // Offsets from sigcontext to each register. + const pint_t kOffsetPc = 8; + const pint_t kOffsetGprs = 16; + const pint_t kOffsetFprs = 216; + + // Restore all registers. + for (int i = 0; i < 16; ++i) { + uint64_t value = _addressSpace.get64(pSigctx + kOffsetGprs + + static_cast(i * 8)); + _registers.setRegister(UNW_S390X_R0 + i, value); + } + for (int i = 0; i < 16; ++i) { + static const int fpr[16] = { + UNW_S390X_F0, UNW_S390X_F1, UNW_S390X_F2, UNW_S390X_F3, + UNW_S390X_F4, UNW_S390X_F5, UNW_S390X_F6, UNW_S390X_F7, + UNW_S390X_F8, UNW_S390X_F9, UNW_S390X_F10, UNW_S390X_F11, + UNW_S390X_F12, UNW_S390X_F13, UNW_S390X_F14, UNW_S390X_F15 + }; + double value = _addressSpace.getDouble(pSigctx + kOffsetFprs + + static_cast(i * 8)); + _registers.setFloatRegister(fpr[i], value); + } + _registers.setIP(_addressSpace.get64(pSigctx + kOffsetPc)); + + // SIGILL, SIGFPE and SIGTRAP are delivered with psw_addr + // after the faulting instruction rather than before it. + // Do not set _isSignalFrame in that case. + uint32_t signo = _addressSpace.get32(pSigno); + _isSignalFrame = (signo != 4 && signo != 5 && signo != 8); + + return UNW_STEP_SUCCESS; +} +#endif // defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN) && + // defined(_LIBUNWIND_TARGET_S390X) template int UnwindCursor::step() { @@ -2079,7 +2802,7 @@ int UnwindCursor::step() { // Use unwinding info to modify register set as if function returned. int result; -#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64) +#if defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN) if (_isSigReturn) { result = this->stepThroughSigReturn(); } else @@ -2089,6 +2812,8 @@ int UnwindCursor::step() { result = this->stepWithCompactEncoding(); #elif defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) result = this->stepWithSEHData(); +#elif defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND) + result = this->stepWithTBTableData(); #elif defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) result = this->stepWithDwarfFDE(); #elif defined(_LIBUNWIND_ARM_EHABI) @@ -2126,6 +2851,12 @@ bool UnwindCursor::getFunctionName(char *buf, size_t bufLen, buf, bufLen, offset); } +#if defined(_LIBUNWIND_USE_CET) +extern "C" void *__libunwind_cet_get_registers(unw_cursor_t *cursor) { + AbstractUnwindCursor *co = (AbstractUnwindCursor *)cursor; + return co->get_registers(); +} +#endif } // namespace libunwind #endif // __UNWINDCURSOR_HPP__ diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindLevel1-gcc-ext.c b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindLevel1-gcc-ext.c index b88de202a8..371b75fbb9 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindLevel1-gcc-ext.c +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindLevel1-gcc-ext.c @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===--------------------- UnwindLevel1-gcc-ext.c -------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -28,31 +28,24 @@ #if defined(_LIBUNWIND_BUILD_ZERO_COST_APIS) #if defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) -#define private_1 private_[0] +#define PRIVATE_1 private_[0] +#elif defined(_LIBUNWIND_ARM_EHABI) +#define PRIVATE_1 unwinder_cache.reserved1 +#else +#define PRIVATE_1 private_1 #endif /// Called by __cxa_rethrow(). _LIBUNWIND_EXPORT _Unwind_Reason_Code _Unwind_Resume_or_Rethrow(_Unwind_Exception *exception_object) { -#if defined(_LIBUNWIND_ARM_EHABI) - _LIBUNWIND_TRACE_API("_Unwind_Resume_or_Rethrow(ex_obj=%p), private_1=%ld", - (void *)exception_object, - (long)exception_object->unwinder_cache.reserved1); -#else - _LIBUNWIND_TRACE_API("_Unwind_Resume_or_Rethrow(ex_obj=%p), private_1=%" PRIdPTR, - (void *)exception_object, - (intptr_t)exception_object->private_1); -#endif + _LIBUNWIND_TRACE_API( + "_Unwind_Resume_or_Rethrow(ex_obj=%p), private_1=%" PRIdPTR, + (void *)exception_object, (intptr_t)exception_object->PRIVATE_1); -#if defined(_LIBUNWIND_ARM_EHABI) - // _Unwind_RaiseException on EHABI will always set the reserved1 field to 0, - // which is in the same position as private_1 below. - return _Unwind_RaiseException(exception_object); -#else // If this is non-forced and a stopping place was found, then this is a // re-throw. // Call _Unwind_RaiseException() as if this was a new exception - if (exception_object->private_1 == 0) { + if (exception_object->PRIVATE_1 == 0) { return _Unwind_RaiseException(exception_object); // Will return if there is no catch clause, so that __cxa_rethrow can call // std::terminate(). @@ -63,20 +56,21 @@ _Unwind_Resume_or_Rethrow(_Unwind_Exception *exception_object) { _Unwind_Resume(exception_object); _LIBUNWIND_ABORT("_Unwind_Resume_or_Rethrow() called _Unwind_RaiseException()" " which unexpectedly returned"); -#endif } - /// Called by personality handler during phase 2 to get base address for data /// relative encodings. _LIBUNWIND_EXPORT uintptr_t _Unwind_GetDataRelBase(struct _Unwind_Context *context) { - (void)context; _LIBUNWIND_TRACE_API("_Unwind_GetDataRelBase(context=%p)", (void *)context); +#if defined(_AIX) + return unw_get_data_rel_base((unw_cursor_t *)context); +#else + (void)context; _LIBUNWIND_ABORT("_Unwind_GetDataRelBase() not implemented"); +#endif } - /// Called by personality handler during phase 2 to get base address for text /// relative encodings. _LIBUNWIND_EXPORT uintptr_t @@ -121,7 +115,7 @@ _Unwind_Backtrace(_Unwind_Trace_Fn callback, void *ref) { // Create a mock exception object for force unwinding. _Unwind_Exception ex; memset(&ex, '\0', sizeof(ex)); - ex.exception_class = 0x434C4E47554E5700; // CLNGUNW\0 + strcpy((char *)&ex.exception_class, "CLNGUNW"); #endif // walk each frame diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindLevel1.c b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindLevel1.c index e9d9aeae83..1f20260d64 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindLevel1.c +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindLevel1.c @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===------------------------- UnwindLevel1.c -----------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -28,6 +28,7 @@ #include #include +#include "cet_unwind.h" #include "config.h" #include "libunwind.h" #include "libunwind_ext.h" @@ -37,6 +38,42 @@ #ifndef _LIBUNWIND_SUPPORT_SEH_UNWIND +// When CET is enabled, each "call" instruction will push return address to +// CET shadow stack, each "ret" instruction will pop current CET shadow stack +// top and compare it with target address which program will return. +// In exception handing, some stack frames will be skipped before jumping to +// landing pad and we must adjust CET shadow stack accordingly. +// _LIBUNWIND_POP_CET_SSP is used to adjust CET shadow stack pointer and we +// directly jump to __libunwind_Registerts_x86/x86_64_jumpto instead of using +// a regular function call to avoid pushing to CET shadow stack again. +#if !defined(_LIBUNWIND_USE_CET) +#define __unw_phase2_resume(cursor, fn) \ + do { \ + (void)fn; \ + __unw_resume((cursor)); \ + } while (0) +#elif defined(_LIBUNWIND_TARGET_I386) +#define __unw_phase2_resume(cursor, fn) \ + do { \ + _LIBUNWIND_POP_CET_SSP((fn)); \ + void *cetRegContext = __libunwind_cet_get_registers((cursor)); \ + void *cetJumpAddress = __libunwind_cet_get_jump_target(); \ + __asm__ volatile("push %%edi\n\t" \ + "sub $4, %%esp\n\t" \ + "jmp *%%edx\n\t" :: "D"(cetRegContext), \ + "d"(cetJumpAddress)); \ + } while (0) +#elif defined(_LIBUNWIND_TARGET_X86_64) +#define __unw_phase2_resume(cursor, fn) \ + do { \ + _LIBUNWIND_POP_CET_SSP((fn)); \ + void *cetRegContext = __libunwind_cet_get_registers((cursor)); \ + void *cetJumpAddress = __libunwind_cet_get_jump_target(); \ + __asm__ volatile("jmpq *%%rdx\n\t" :: "D"(cetRegContext), \ + "d"(cetJumpAddress)); \ + } while (0) +#endif + static _Unwind_Reason_Code unwind_phase1(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Exception *exception_object) { __unw_init_local(cursor, uc); @@ -71,6 +108,7 @@ unwind_phase1(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Exception *except return _URC_FATAL_PHASE1_ERROR; } +#ifndef NDEBUG // When tracing, print state information. if (_LIBUNWIND_TRACING_UNWINDING) { char functionBuf[512]; @@ -88,6 +126,7 @@ unwind_phase1(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Exception *except (void *)exception_object, pc, frameInfo.start_ip, functionName, frameInfo.lsda, frameInfo.handler); } +#endif // If there is a personality routine, ask it if it will want to stop at // this frame. @@ -138,6 +177,9 @@ unwind_phase2(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Exception *except _LIBUNWIND_TRACE_UNWINDING("unwind_phase2(ex_ojb=%p)", (void *)exception_object); + // uc is initialized by __unw_getcontext in the parent frame. The first stack + // frame walked is unwind_phase2. + unsigned framesWalked = 1; // Walk each frame until we reach where search phase said to stop. while (true) { @@ -170,6 +212,7 @@ unwind_phase2(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Exception *except return _URC_FATAL_PHASE2_ERROR; } +#ifndef NDEBUG // When tracing, print state information. if (_LIBUNWIND_TRACING_UNWINDING) { char functionBuf[512]; @@ -186,7 +229,9 @@ unwind_phase2(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Exception *except functionName, sp, frameInfo.lsda, frameInfo.handler); } +#endif + ++framesWalked; // If there is a personality routine, tell it we are unwinding. if (frameInfo.handler != 0) { _Unwind_Personality_Fn p = @@ -226,8 +271,9 @@ unwind_phase2(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Exception *except ", sp=0x%" PRIxPTR, (void *)exception_object, pc, sp); } - __unw_resume(cursor); - // __unw_resume() only returns if there was an error. + + __unw_phase2_resume(cursor, framesWalked); + // __unw_phase2_resume() only returns if there was an error. return _URC_FATAL_PHASE2_ERROR; default: // Personality routine returned an unknown result code. @@ -249,6 +295,9 @@ unwind_phase2_forced(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Stop_Fn stop, void *stop_parameter) { __unw_init_local(cursor, uc); + // uc is initialized by __unw_getcontext in the parent frame. The first stack + // frame walked is unwind_phase2_forced. + unsigned framesWalked = 1; // Walk each frame until we reach where search phase said to stop while (__unw_step(cursor) > 0) { @@ -261,6 +310,7 @@ unwind_phase2_forced(unw_context_t *uc, unw_cursor_t *cursor, return _URC_FATAL_PHASE2_ERROR; } +#ifndef NDEBUG // When tracing, print state information. if (_LIBUNWIND_TRACING_UNWINDING) { char functionBuf[512]; @@ -276,6 +326,7 @@ unwind_phase2_forced(unw_context_t *uc, unw_cursor_t *cursor, (void *)exception_object, frameInfo.start_ip, functionName, frameInfo.lsda, frameInfo.handler); } +#endif // Call stop function at each frame. _Unwind_Action action = @@ -293,6 +344,7 @@ unwind_phase2_forced(unw_context_t *uc, unw_cursor_t *cursor, return _URC_FATAL_PHASE2_ERROR; } + ++framesWalked; // If there is a personality routine, tell it we are unwinding. if (frameInfo.handler != 0) { _Unwind_Personality_Fn p = @@ -317,7 +369,7 @@ unwind_phase2_forced(unw_context_t *uc, unw_cursor_t *cursor, "_URC_INSTALL_CONTEXT", (void *)exception_object); // We may get control back if landing pad calls _Unwind_Resume(). - __unw_resume(cursor); + __unw_phase2_resume(cursor, framesWalked); break; default: // Personality routine returned an unknown result code. @@ -435,11 +487,13 @@ _Unwind_GetLanguageSpecificData(struct _Unwind_Context *context) { _LIBUNWIND_TRACE_API( "_Unwind_GetLanguageSpecificData(context=%p) => 0x%" PRIxPTR, (void *)context, result); +#if !defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND) if (result != 0) { if (*((uint8_t *)result) != 0xFF) _LIBUNWIND_DEBUG_LOG("lsda at 0x%" PRIxPTR " does not start with 0xFF", result); } +#endif return result; } diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindRegistersRestore.S b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindRegistersRestore.S index 239d4105c4..4aaf6d3964 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindRegistersRestore.S +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindRegistersRestore.S @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===-------------------- UnwindRegistersRestore.S ------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -11,7 +11,11 @@ #include "assembly.h" +#if defined(_AIX) + .toc +#else .text +#endif #if !defined(__USING_SJLJ_EXCEPTIONS__) @@ -28,6 +32,8 @@ DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_x86_jumpto) # + return address + # +-----------------------+ <-- SP # + + + + _LIBUNWIND_CET_ENDBR movl 4(%esp), %eax # set up eax and ret on new stack location movl 28(%eax), %edx # edx holds new stack pointer @@ -49,7 +55,8 @@ DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_x86_jumpto) # skip ss # skip eflags pop %eax # eax was already pushed on new stack - ret # eip was already pushed on new stack + pop %ecx + jmp *%ecx # skip cs # skip ds # skip es @@ -73,6 +80,7 @@ DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_x86_64_jumpto) # On entry, thread_state pointer is in rdi #endif + _LIBUNWIND_CET_ENDBR movq 56(%rdi), %rax # rax holds new stack pointer subq $16, %rax movq %rax, 56(%rdi) @@ -122,7 +130,8 @@ DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_x86_64_jumpto) #endif movq 56(%rdi), %rsp # cut back rsp to new location pop %rdi # rdi was saved here earlier - ret # rip was saved here + pop %rcx + jmpq *%rcx #elif defined(__powerpc64__) @@ -137,7 +146,7 @@ DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind15Registers_ppc646jumptoEv) // load register (GPR) #define PPC64_LR(n) \ - ld %r##n, (8 * (n + 2))(%r3) + ld n, (8 * (n + 2))(3) // restore integral registers // skip r0 for now @@ -179,12 +188,12 @@ DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind15Registers_ppc646jumptoEv) // (note that this also restores floating point registers and V registers, // because part of VS is mapped to these registers) - addi %r4, %r3, PPC64_OFFS_FP + addi 4, 3, PPC64_OFFS_FP // load VS register #define PPC64_LVS(n) \ - lxvd2x %vs##n, 0, %r4 ;\ - addi %r4, %r4, 16 + lxvd2x n, 0, 4 ;\ + addi 4, 4, 16 // restore the first 32 VS regs (and also all floating point regs) PPC64_LVS(0) @@ -220,27 +229,36 @@ DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind15Registers_ppc646jumptoEv) PPC64_LVS(30) PPC64_LVS(31) - // use VRSAVE to conditionally restore the remaining VS regs, - // that are where the V regs are mapped +#define PPC64_CLVS_RESTORE(n) \ + addi 4, 3, PPC64_OFFS_FP + n * 16 ;\ + lxvd2x n, 0, 4 - ld %r5, PPC64_OFFS_VRSAVE(%r3) // test VRsave - cmpwi %r5, 0 +#if !defined(_AIX) + // use VRSAVE to conditionally restore the remaining VS regs, that are + // where the V regs are mapped. In the AIX ABI, VRSAVE is not used. + ld 5, PPC64_OFFS_VRSAVE(3) // test VRsave + cmpwi 5, 0 beq Lnovec // conditionally load VS -#define PPC64_CLVS_BOTTOM(n) \ - beq Ldone##n ;\ - addi %r4, %r3, PPC64_OFFS_FP + n * 16 ;\ - lxvd2x %vs##n, 0, %r4 ;\ +#define PPC64_CLVSl(n) \ + andis. 0, 5, (1 PPC_LEFT_SHIFT(47-n)) ;\ + beq Ldone##n ;\ + PPC64_CLVS_RESTORE(n) ;\ Ldone##n: -#define PPC64_CLVSl(n) \ - andis. %r0, %r5, (1<<(47-n)) ;\ -PPC64_CLVS_BOTTOM(n) +#define PPC64_CLVSh(n) \ + andi. 0, 5, (1 PPC_LEFT_SHIFT(63-n)) ;\ + beq Ldone##n ;\ + PPC64_CLVS_RESTORE(n) ;\ +Ldone##n: -#define PPC64_CLVSh(n) \ - andi. %r0, %r5, (1<<(63-n)) ;\ -PPC64_CLVS_BOTTOM(n) +#else + +#define PPC64_CLVSl(n) PPC64_CLVS_RESTORE(n) +#define PPC64_CLVSh(n) PPC64_CLVS_RESTORE(n) + +#endif // !defined(_AIX) PPC64_CLVSl(32) PPC64_CLVSl(33) @@ -279,7 +297,7 @@ PPC64_CLVS_BOTTOM(n) // load FP register #define PPC64_LF(n) \ - lfd %f##n, (PPC64_OFFS_FP + n * 16)(%r3) + lfd n, (PPC64_OFFS_FP + n * 16)(3) // restore float registers PPC64_LF(0) @@ -316,32 +334,44 @@ PPC64_CLVS_BOTTOM(n) PPC64_LF(31) #if defined(__ALTIVEC__) - // restore vector registers if any are in use - ld %r5, PPC64_OFFS_VRSAVE(%r3) // test VRsave - cmpwi %r5, 0 + +#define PPC64_CLV_UNALIGNED_RESTORE(n) \ + ld 0, (PPC64_OFFS_V + n * 16)(3) ;\ + std 0, 0(4) ;\ + ld 0, (PPC64_OFFS_V + n * 16 + 8)(3) ;\ + std 0, 8(4) ;\ + lvx n, 0, 4 + +#if !defined(_AIX) + // restore vector registers if any are in use. In the AIX ABI, VRSAVE is + // not used. + ld 5, PPC64_OFFS_VRSAVE(3) // test VRsave + cmpwi 5, 0 beq Lnovec - subi %r4, %r1, 16 - // r4 is now a 16-byte aligned pointer into the red zone - // the _vectorScalarRegisters may not be 16-byte aligned - // so copy via red zone temp buffer +#define PPC64_CLV_UNALIGNEDl(n) \ + andis. 0, 5, (1 PPC_LEFT_SHIFT(15-n)) ;\ + beq Ldone##n ;\ + PPC64_CLV_UNALIGNED_RESTORE(n) ;\ +Ldone ## n: -#define PPC64_CLV_UNALIGNED_BOTTOM(n) \ - beq Ldone##n ;\ - ld %r0, (PPC64_OFFS_V + n * 16)(%r3) ;\ - std %r0, 0(%r4) ;\ - ld %r0, (PPC64_OFFS_V + n * 16 + 8)(%r3) ;\ - std %r0, 8(%r4) ;\ - lvx %v##n, 0, %r4 ;\ +#define PPC64_CLV_UNALIGNEDh(n) \ + andi. 0, 5, (1 PPC_LEFT_SHIFT(31-n)) ;\ + beq Ldone##n ;\ + PPC64_CLV_UNALIGNED_RESTORE(n) ;\ Ldone ## n: -#define PPC64_CLV_UNALIGNEDl(n) \ - andis. %r0, %r5, (1<<(15-n)) ;\ -PPC64_CLV_UNALIGNED_BOTTOM(n) +#else + +#define PPC64_CLV_UNALIGNEDl(n) PPC64_CLV_UNALIGNED_RESTORE(n) +#define PPC64_CLV_UNALIGNEDh(n) PPC64_CLV_UNALIGNED_RESTORE(n) + +#endif // !defined(_AIX) -#define PPC64_CLV_UNALIGNEDh(n) \ - andi. %r0, %r5, (1<<(31-n)) ;\ -PPC64_CLV_UNALIGNED_BOTTOM(n) + subi 4, 1, 16 + // r4 is now a 16-byte aligned pointer into the red zone + // the _vectorScalarRegisters may not be 16-byte aligned + // so copy via red zone temp buffer PPC64_CLV_UNALIGNEDl(0) PPC64_CLV_UNALIGNEDl(1) @@ -380,19 +410,31 @@ PPC64_CLV_UNALIGNED_BOTTOM(n) #endif Lnovec: - ld %r0, PPC64_OFFS_CR(%r3) - mtcr %r0 - ld %r0, PPC64_OFFS_SRR0(%r3) - mtctr %r0 - + ld 0, PPC64_OFFS_CR(3) + mtcr 0 + ld 0, PPC64_OFFS_SRR0(3) + mtctr 0 + +#if defined(_AIX) + // After setting GPR1 to a higher address, AIX wipes out the original + // stack space below that address invalidated by the new GPR1 value. Use + // GPR0 to save the value of GPR3 in the context before it is wiped out. + // This compromises the content of GPR0 which is a volatile register. + ld 0, (8 * (3 + 2))(3) +#else PPC64_LR(0) +#endif PPC64_LR(5) PPC64_LR(4) PPC64_LR(1) +#if defined(_AIX) + mr 3, 0 +#else PPC64_LR(3) +#endif bctr -#elif defined(__ppc__) +#elif defined(__powerpc__) DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind13Registers_ppc6jumptoEv) // @@ -405,113 +447,116 @@ DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind13Registers_ppc6jumptoEv) // restore integral registerrs // skip r0 for now // skip r1 for now - lwz %r2, 16(%r3) + lwz 2, 16(3) // skip r3 for now // skip r4 for now // skip r5 for now - lwz %r6, 32(%r3) - lwz %r7, 36(%r3) - lwz %r8, 40(%r3) - lwz %r9, 44(%r3) - lwz %r10, 48(%r3) - lwz %r11, 52(%r3) - lwz %r12, 56(%r3) - lwz %r13, 60(%r3) - lwz %r14, 64(%r3) - lwz %r15, 68(%r3) - lwz %r16, 72(%r3) - lwz %r17, 76(%r3) - lwz %r18, 80(%r3) - lwz %r19, 84(%r3) - lwz %r20, 88(%r3) - lwz %r21, 92(%r3) - lwz %r22, 96(%r3) - lwz %r23,100(%r3) - lwz %r24,104(%r3) - lwz %r25,108(%r3) - lwz %r26,112(%r3) - lwz %r27,116(%r3) - lwz %r28,120(%r3) - lwz %r29,124(%r3) - lwz %r30,128(%r3) - lwz %r31,132(%r3) + lwz 6, 32(3) + lwz 7, 36(3) + lwz 8, 40(3) + lwz 9, 44(3) + lwz 10, 48(3) + lwz 11, 52(3) + lwz 12, 56(3) + lwz 13, 60(3) + lwz 14, 64(3) + lwz 15, 68(3) + lwz 16, 72(3) + lwz 17, 76(3) + lwz 18, 80(3) + lwz 19, 84(3) + lwz 20, 88(3) + lwz 21, 92(3) + lwz 22, 96(3) + lwz 23,100(3) + lwz 24,104(3) + lwz 25,108(3) + lwz 26,112(3) + lwz 27,116(3) + lwz 28,120(3) + lwz 29,124(3) + lwz 30,128(3) + lwz 31,132(3) #ifndef __NO_FPRS__ // restore float registers - lfd %f0, 160(%r3) - lfd %f1, 168(%r3) - lfd %f2, 176(%r3) - lfd %f3, 184(%r3) - lfd %f4, 192(%r3) - lfd %f5, 200(%r3) - lfd %f6, 208(%r3) - lfd %f7, 216(%r3) - lfd %f8, 224(%r3) - lfd %f9, 232(%r3) - lfd %f10,240(%r3) - lfd %f11,248(%r3) - lfd %f12,256(%r3) - lfd %f13,264(%r3) - lfd %f14,272(%r3) - lfd %f15,280(%r3) - lfd %f16,288(%r3) - lfd %f17,296(%r3) - lfd %f18,304(%r3) - lfd %f19,312(%r3) - lfd %f20,320(%r3) - lfd %f21,328(%r3) - lfd %f22,336(%r3) - lfd %f23,344(%r3) - lfd %f24,352(%r3) - lfd %f25,360(%r3) - lfd %f26,368(%r3) - lfd %f27,376(%r3) - lfd %f28,384(%r3) - lfd %f29,392(%r3) - lfd %f30,400(%r3) - lfd %f31,408(%r3) + lfd 0, 160(3) + lfd 1, 168(3) + lfd 2, 176(3) + lfd 3, 184(3) + lfd 4, 192(3) + lfd 5, 200(3) + lfd 6, 208(3) + lfd 7, 216(3) + lfd 8, 224(3) + lfd 9, 232(3) + lfd 10,240(3) + lfd 11,248(3) + lfd 12,256(3) + lfd 13,264(3) + lfd 14,272(3) + lfd 15,280(3) + lfd 16,288(3) + lfd 17,296(3) + lfd 18,304(3) + lfd 19,312(3) + lfd 20,320(3) + lfd 21,328(3) + lfd 22,336(3) + lfd 23,344(3) + lfd 24,352(3) + lfd 25,360(3) + lfd 26,368(3) + lfd 27,376(3) + lfd 28,384(3) + lfd 29,392(3) + lfd 30,400(3) + lfd 31,408(3) #endif #if defined(__ALTIVEC__) - // restore vector registers if any are in use - lwz %r5, 156(%r3) // test VRsave - cmpwi %r5, 0 + +#define LOAD_VECTOR_RESTORE(_index) \ + lwz 0, 424+_index*16(3) SEPARATOR \ + stw 0, 0(4) SEPARATOR \ + lwz 0, 424+_index*16+4(3) SEPARATOR \ + stw 0, 4(4) SEPARATOR \ + lwz 0, 424+_index*16+8(3) SEPARATOR \ + stw 0, 8(4) SEPARATOR \ + lwz 0, 424+_index*16+12(3) SEPARATOR \ + stw 0, 12(4) SEPARATOR \ + lvx _index, 0, 4 + +#if !defined(_AIX) + // restore vector registers if any are in use. In the AIX ABI, VRSAVE + // is not used. + lwz 5, 156(3) // test VRsave + cmpwi 5, 0 beq Lnovec - subi %r4, %r1, 16 - rlwinm %r4, %r4, 0, 0, 27 // mask low 4-bits - // r4 is now a 16-byte aligned pointer into the red zone - // the _vectorRegisters may not be 16-byte aligned so copy via red zone temp buffer - - -#define LOAD_VECTOR_UNALIGNEDl(_index) \ - andis. %r0, %r5, (1<<(15-_index)) SEPARATOR \ - beq Ldone ## _index SEPARATOR \ - lwz %r0, 424+_index*16(%r3) SEPARATOR \ - stw %r0, 0(%r4) SEPARATOR \ - lwz %r0, 424+_index*16+4(%r3) SEPARATOR \ - stw %r0, 4(%r4) SEPARATOR \ - lwz %r0, 424+_index*16+8(%r3) SEPARATOR \ - stw %r0, 8(%r4) SEPARATOR \ - lwz %r0, 424+_index*16+12(%r3) SEPARATOR \ - stw %r0, 12(%r4) SEPARATOR \ - lvx %v ## _index, 0, %r4 SEPARATOR \ +#define LOAD_VECTOR_UNALIGNEDl(_index) \ + andis. 0, 5, (1 PPC_LEFT_SHIFT(15-_index)) SEPARATOR \ + beq Ldone ## _index SEPARATOR \ + LOAD_VECTOR_RESTORE(_index) SEPARATOR \ Ldone ## _index: -#define LOAD_VECTOR_UNALIGNEDh(_index) \ - andi. %r0, %r5, (1<<(31-_index)) SEPARATOR \ - beq Ldone ## _index SEPARATOR \ - lwz %r0, 424+_index*16(%r3) SEPARATOR \ - stw %r0, 0(%r4) SEPARATOR \ - lwz %r0, 424+_index*16+4(%r3) SEPARATOR \ - stw %r0, 4(%r4) SEPARATOR \ - lwz %r0, 424+_index*16+8(%r3) SEPARATOR \ - stw %r0, 8(%r4) SEPARATOR \ - lwz %r0, 424+_index*16+12(%r3) SEPARATOR \ - stw %r0, 12(%r4) SEPARATOR \ - lvx %v ## _index, 0, %r4 SEPARATOR \ +#define LOAD_VECTOR_UNALIGNEDh(_index) \ + andi. 0, 5, (1 PPC_LEFT_SHIFT(31-_index)) SEPARATOR \ + beq Ldone ## _index SEPARATOR \ + LOAD_VECTOR_RESTORE(_index) SEPARATOR \ Ldone ## _index: +#else + +#define LOAD_VECTOR_UNALIGNEDl(_index) LOAD_VECTOR_RESTORE(_index) +#define LOAD_VECTOR_UNALIGNEDh(_index) LOAD_VECTOR_RESTORE(_index) + +#endif // !defined(_AIX) + + subi 4, 1, 16 + rlwinm 4, 4, 0, 0, 27 // mask low 4-bits + // r4 is now a 16-byte aligned pointer into the red zone + // the _vectorRegisters may not be 16-byte aligned so copy via red zone temp buffer LOAD_VECTOR_UNALIGNEDl(0) LOAD_VECTOR_UNALIGNEDl(1) @@ -548,17 +593,17 @@ DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind13Registers_ppc6jumptoEv) #endif Lnovec: - lwz %r0, 136(%r3) // __cr - mtcr %r0 - lwz %r0, 148(%r3) // __ctr - mtctr %r0 - lwz %r0, 0(%r3) // __ssr0 - mtctr %r0 - lwz %r0, 8(%r3) // do r0 now - lwz %r5, 28(%r3) // do r5 now - lwz %r4, 24(%r3) // do r4 now - lwz %r1, 12(%r3) // do sp now - lwz %r3, 20(%r3) // do r3 last + lwz 0, 136(3) // __cr + mtcr 0 + lwz 0, 148(3) // __ctr + mtctr 0 + lwz 0, 0(3) // __ssr0 + mtctr 0 + lwz 0, 8(3) // do r0 now + lwz 5, 28(3) // do r5 now + lwz 4, 24(3) // do r4 now + lwz 1, 12(3) // do sp now + lwz 3, 20(3) // do r3 last bctr #elif defined(__aarch64__) @@ -658,7 +703,13 @@ DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind13Registers_arm20restoreCoreAndJumpToEv) ldr sp, [lr, #52] ldr lr, [lr, #60] @ restore pc into lr #endif +#if defined(__ARM_FEATURE_BTI_DEFAULT) && !defined(__ARM_ARCH_ISA_ARM) + // 'bx' is not BTI setting when used with lr, therefore r12 is used instead + mov r12, lr + JMP(r12) +#else JMP(lr) +#endif @ @ static void libunwind::Registers_arm::restoreVFPWithFLDMD(unw_fpreg_t* values) @@ -803,11 +854,12 @@ DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind14Registers_or1k6jumptoEv) l.lwz r30,120(r3) l.lwz r31,124(r3) + # load new pc into ra + l.lwz r9, 128(r3) + # at last, restore r3 l.lwz r3, 12(r3) - # load new pc into ra - l.lwz r9, 128(r3) # jump to pc l.jr r9 l.nop @@ -1053,6 +1105,53 @@ DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind21Registers_mips_newabi6jumptoEv) ld $4, (8 * 4)($4) .set pop +#elif defined(__sparc__) && defined(__arch64__) + +DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind17Registers_sparc646jumptoEv) +// +// void libunwind::Registers_sparc64::jumpto() +// +// On entry: +// thread_state pointer is in %o0 +// + .register %g2, #scratch + .register %g3, #scratch + .register %g6, #scratch + .register %g7, #scratch + flushw + ldx [%o0 + 0x08], %g1 + ldx [%o0 + 0x10], %g2 + ldx [%o0 + 0x18], %g3 + ldx [%o0 + 0x20], %g4 + ldx [%o0 + 0x28], %g5 + ldx [%o0 + 0x30], %g6 + ldx [%o0 + 0x38], %g7 + ldx [%o0 + 0x48], %o1 + ldx [%o0 + 0x50], %o2 + ldx [%o0 + 0x58], %o3 + ldx [%o0 + 0x60], %o4 + ldx [%o0 + 0x68], %o5 + ldx [%o0 + 0x70], %o6 + ldx [%o0 + 0x78], %o7 + ldx [%o0 + 0x80], %l0 + ldx [%o0 + 0x88], %l1 + ldx [%o0 + 0x90], %l2 + ldx [%o0 + 0x98], %l3 + ldx [%o0 + 0xa0], %l4 + ldx [%o0 + 0xa8], %l5 + ldx [%o0 + 0xb0], %l6 + ldx [%o0 + 0xb8], %l7 + ldx [%o0 + 0xc0], %i0 + ldx [%o0 + 0xc8], %i1 + ldx [%o0 + 0xd0], %i2 + ldx [%o0 + 0xd8], %i3 + ldx [%o0 + 0xe0], %i4 + ldx [%o0 + 0xe8], %i5 + ldx [%o0 + 0xf0], %i6 + ldx [%o0 + 0xf8], %i7 + jmp %o7 + ldx [%o0 + 0x40], %o0 + #elif defined(__sparc__) // @@ -1075,7 +1174,7 @@ DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind15Registers_sparc6jumptoEv) jmp %o7 nop -#elif defined(__riscv) && __riscv_xlen == 64 +#elif defined(__riscv) // // void libunwind::Registers_riscv::jumpto() @@ -1085,77 +1184,114 @@ DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind15Registers_sparc6jumptoEv) // .p2align 2 DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind15Registers_riscv6jumptoEv) -#if defined(__riscv_flen) && __riscv_flen == 64 - fld f0, (8 * 32 + 8 * 0)(a0) - fld f1, (8 * 32 + 8 * 1)(a0) - fld f2, (8 * 32 + 8 * 2)(a0) - fld f3, (8 * 32 + 8 * 3)(a0) - fld f4, (8 * 32 + 8 * 4)(a0) - fld f5, (8 * 32 + 8 * 5)(a0) - fld f6, (8 * 32 + 8 * 6)(a0) - fld f7, (8 * 32 + 8 * 7)(a0) - fld f8, (8 * 32 + 8 * 8)(a0) - fld f9, (8 * 32 + 8 * 9)(a0) - fld f10, (8 * 32 + 8 * 10)(a0) - fld f11, (8 * 32 + 8 * 11)(a0) - fld f12, (8 * 32 + 8 * 12)(a0) - fld f13, (8 * 32 + 8 * 13)(a0) - fld f14, (8 * 32 + 8 * 14)(a0) - fld f15, (8 * 32 + 8 * 15)(a0) - fld f16, (8 * 32 + 8 * 16)(a0) - fld f17, (8 * 32 + 8 * 17)(a0) - fld f18, (8 * 32 + 8 * 18)(a0) - fld f19, (8 * 32 + 8 * 19)(a0) - fld f20, (8 * 32 + 8 * 20)(a0) - fld f21, (8 * 32 + 8 * 21)(a0) - fld f22, (8 * 32 + 8 * 22)(a0) - fld f23, (8 * 32 + 8 * 23)(a0) - fld f24, (8 * 32 + 8 * 24)(a0) - fld f25, (8 * 32 + 8 * 25)(a0) - fld f26, (8 * 32 + 8 * 26)(a0) - fld f27, (8 * 32 + 8 * 27)(a0) - fld f28, (8 * 32 + 8 * 28)(a0) - fld f29, (8 * 32 + 8 * 29)(a0) - fld f30, (8 * 32 + 8 * 30)(a0) - fld f31, (8 * 32 + 8 * 31)(a0) -#endif +# if defined(__riscv_flen) + FLOAD f0, (RISCV_FOFFSET + RISCV_FSIZE * 0)(a0) + FLOAD f1, (RISCV_FOFFSET + RISCV_FSIZE * 1)(a0) + FLOAD f2, (RISCV_FOFFSET + RISCV_FSIZE * 2)(a0) + FLOAD f3, (RISCV_FOFFSET + RISCV_FSIZE * 3)(a0) + FLOAD f4, (RISCV_FOFFSET + RISCV_FSIZE * 4)(a0) + FLOAD f5, (RISCV_FOFFSET + RISCV_FSIZE * 5)(a0) + FLOAD f6, (RISCV_FOFFSET + RISCV_FSIZE * 6)(a0) + FLOAD f7, (RISCV_FOFFSET + RISCV_FSIZE * 7)(a0) + FLOAD f8, (RISCV_FOFFSET + RISCV_FSIZE * 8)(a0) + FLOAD f9, (RISCV_FOFFSET + RISCV_FSIZE * 9)(a0) + FLOAD f10, (RISCV_FOFFSET + RISCV_FSIZE * 10)(a0) + FLOAD f11, (RISCV_FOFFSET + RISCV_FSIZE * 11)(a0) + FLOAD f12, (RISCV_FOFFSET + RISCV_FSIZE * 12)(a0) + FLOAD f13, (RISCV_FOFFSET + RISCV_FSIZE * 13)(a0) + FLOAD f14, (RISCV_FOFFSET + RISCV_FSIZE * 14)(a0) + FLOAD f15, (RISCV_FOFFSET + RISCV_FSIZE * 15)(a0) + FLOAD f16, (RISCV_FOFFSET + RISCV_FSIZE * 16)(a0) + FLOAD f17, (RISCV_FOFFSET + RISCV_FSIZE * 17)(a0) + FLOAD f18, (RISCV_FOFFSET + RISCV_FSIZE * 18)(a0) + FLOAD f19, (RISCV_FOFFSET + RISCV_FSIZE * 19)(a0) + FLOAD f20, (RISCV_FOFFSET + RISCV_FSIZE * 20)(a0) + FLOAD f21, (RISCV_FOFFSET + RISCV_FSIZE * 21)(a0) + FLOAD f22, (RISCV_FOFFSET + RISCV_FSIZE * 22)(a0) + FLOAD f23, (RISCV_FOFFSET + RISCV_FSIZE * 23)(a0) + FLOAD f24, (RISCV_FOFFSET + RISCV_FSIZE * 24)(a0) + FLOAD f25, (RISCV_FOFFSET + RISCV_FSIZE * 25)(a0) + FLOAD f26, (RISCV_FOFFSET + RISCV_FSIZE * 26)(a0) + FLOAD f27, (RISCV_FOFFSET + RISCV_FSIZE * 27)(a0) + FLOAD f28, (RISCV_FOFFSET + RISCV_FSIZE * 28)(a0) + FLOAD f29, (RISCV_FOFFSET + RISCV_FSIZE * 29)(a0) + FLOAD f30, (RISCV_FOFFSET + RISCV_FSIZE * 30)(a0) + FLOAD f31, (RISCV_FOFFSET + RISCV_FSIZE * 31)(a0) +# endif // x0 is zero - ld x1, (8 * 0)(a0) // restore pc into ra - ld x2, (8 * 2)(a0) - ld x3, (8 * 3)(a0) - ld x4, (8 * 4)(a0) - ld x5, (8 * 5)(a0) - ld x6, (8 * 6)(a0) - ld x7, (8 * 7)(a0) - ld x8, (8 * 8)(a0) - ld x9, (8 * 9)(a0) + ILOAD x1, (RISCV_ISIZE * 0)(a0) // restore pc into ra + ILOAD x2, (RISCV_ISIZE * 2)(a0) + ILOAD x3, (RISCV_ISIZE * 3)(a0) + ILOAD x4, (RISCV_ISIZE * 4)(a0) + ILOAD x5, (RISCV_ISIZE * 5)(a0) + ILOAD x6, (RISCV_ISIZE * 6)(a0) + ILOAD x7, (RISCV_ISIZE * 7)(a0) + ILOAD x8, (RISCV_ISIZE * 8)(a0) + ILOAD x9, (RISCV_ISIZE * 9)(a0) // skip a0 for now - ld x11, (8 * 11)(a0) - ld x12, (8 * 12)(a0) - ld x13, (8 * 13)(a0) - ld x14, (8 * 14)(a0) - ld x15, (8 * 15)(a0) - ld x16, (8 * 16)(a0) - ld x17, (8 * 17)(a0) - ld x18, (8 * 18)(a0) - ld x19, (8 * 19)(a0) - ld x20, (8 * 20)(a0) - ld x21, (8 * 21)(a0) - ld x22, (8 * 22)(a0) - ld x23, (8 * 23)(a0) - ld x24, (8 * 24)(a0) - ld x25, (8 * 25)(a0) - ld x26, (8 * 26)(a0) - ld x27, (8 * 27)(a0) - ld x28, (8 * 28)(a0) - ld x29, (8 * 29)(a0) - ld x30, (8 * 30)(a0) - ld x31, (8 * 31)(a0) - ld x10, (8 * 10)(a0) // restore a0 + ILOAD x11, (RISCV_ISIZE * 11)(a0) + ILOAD x12, (RISCV_ISIZE * 12)(a0) + ILOAD x13, (RISCV_ISIZE * 13)(a0) + ILOAD x14, (RISCV_ISIZE * 14)(a0) + ILOAD x15, (RISCV_ISIZE * 15)(a0) + ILOAD x16, (RISCV_ISIZE * 16)(a0) + ILOAD x17, (RISCV_ISIZE * 17)(a0) + ILOAD x18, (RISCV_ISIZE * 18)(a0) + ILOAD x19, (RISCV_ISIZE * 19)(a0) + ILOAD x20, (RISCV_ISIZE * 20)(a0) + ILOAD x21, (RISCV_ISIZE * 21)(a0) + ILOAD x22, (RISCV_ISIZE * 22)(a0) + ILOAD x23, (RISCV_ISIZE * 23)(a0) + ILOAD x24, (RISCV_ISIZE * 24)(a0) + ILOAD x25, (RISCV_ISIZE * 25)(a0) + ILOAD x26, (RISCV_ISIZE * 26)(a0) + ILOAD x27, (RISCV_ISIZE * 27)(a0) + ILOAD x28, (RISCV_ISIZE * 28)(a0) + ILOAD x29, (RISCV_ISIZE * 29)(a0) + ILOAD x30, (RISCV_ISIZE * 30)(a0) + ILOAD x31, (RISCV_ISIZE * 31)(a0) + ILOAD x10, (RISCV_ISIZE * 10)(a0) // restore a0 ret // jump to ra +#elif defined(__s390x__) + +DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind15Registers_s390x6jumptoEv) +// +// void libunwind::Registers_s390x::jumpto() +// +// On entry: +// thread_state pointer is in r2 +// + + // Skip PSWM, but load PSWA into r1 + lg %r1, 8(%r2) + + // Restore FPRs + ld %f0, 144(%r2) + ld %f1, 152(%r2) + ld %f2, 160(%r2) + ld %f3, 168(%r2) + ld %f4, 176(%r2) + ld %f5, 184(%r2) + ld %f6, 192(%r2) + ld %f7, 200(%r2) + ld %f8, 208(%r2) + ld %f9, 216(%r2) + ld %f10, 224(%r2) + ld %f11, 232(%r2) + ld %f12, 240(%r2) + ld %f13, 248(%r2) + ld %f14, 256(%r2) + ld %f15, 264(%r2) + + // Restore GPRs - skipping %r0 and %r1 + lmg %r2, %r15, 32(%r2) + + // Return to PSWA (was loaded into %r1 above) + br %r1 + #endif #endif /* !defined(__USING_SJLJ_EXCEPTIONS__) */ diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindRegistersSave.S b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindRegistersSave.S index 071efe585b..b4f72f5c18 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindRegistersSave.S +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindRegistersSave.S @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===------------------------ UnwindRegistersSave.S -----------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -11,7 +11,11 @@ #include "assembly.h" +#if defined(_AIX) + .toc +#else .text +#endif #if !defined(__USING_SJLJ_EXCEPTIONS__) @@ -30,6 +34,8 @@ # + + # DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) + + _LIBUNWIND_CET_ENDBR push %eax movl 8(%esp), %eax movl %ebx, 4(%eax) @@ -73,6 +79,7 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) #define TMP %rsi #endif + _LIBUNWIND_CET_ENDBR movq %rax, (PTR) movq %rbx, 8(PTR) movq %rcx, 16(PTR) @@ -334,16 +341,19 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) // On entry: // thread_state pointer is in r3 // +#if defined(_AIX) +DEFINE_LIBUNWIND_FUNCTION_AND_WEAK_ALIAS(__unw_getcontext, unw_getcontext) +#else DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) - +#endif // store register (GPR) #define PPC64_STR(n) \ - std %r##n, (8 * (n + 2))(%r3) + std n, (8 * (n + 2))(3) // save GPRs PPC64_STR(0) - mflr %r0 - std %r0, PPC64_OFFS_SRR0(%r3) // store lr as ssr0 + mflr 0 + std 0, PPC64_OFFS_SRR0(3) // store lr as ssr0 PPC64_STR(1) PPC64_STR(2) PPC64_STR(3) @@ -376,28 +386,28 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) PPC64_STR(30) PPC64_STR(31) - mfcr %r0 - std %r0, PPC64_OFFS_CR(%r3) - mfxer %r0 - std %r0, PPC64_OFFS_XER(%r3) - mflr %r0 - std %r0, PPC64_OFFS_LR(%r3) - mfctr %r0 - std %r0, PPC64_OFFS_CTR(%r3) - mfvrsave %r0 - std %r0, PPC64_OFFS_VRSAVE(%r3) + mfcr 0 + std 0, PPC64_OFFS_CR(3) + mfxer 0 + std 0, PPC64_OFFS_XER(3) + mflr 0 + std 0, PPC64_OFFS_LR(3) + mfctr 0 + std 0, PPC64_OFFS_CTR(3) + mfvrsave 0 + std 0, PPC64_OFFS_VRSAVE(3) #if defined(__VSX__) // save VS registers // (note that this also saves floating point registers and V registers, // because part of VS is mapped to these registers) - addi %r4, %r3, PPC64_OFFS_FP + addi 4, 3, PPC64_OFFS_FP // store VS register #define PPC64_STVS(n) \ - stxvd2x %vs##n, 0, %r4 ;\ - addi %r4, %r4, 16 + stxvd2x n, 0, 4 ;\ + addi 4, 4, 16 PPC64_STVS(0) PPC64_STVS(1) @@ -468,7 +478,7 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) // store FP register #define PPC64_STF(n) \ - stfd %f##n, (PPC64_OFFS_FP + n * 16)(%r3) + stfd n, (PPC64_OFFS_FP + n * 16)(3) // save float registers PPC64_STF(0) @@ -510,14 +520,14 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) // Use 16-bytes below the stack pointer as an // aligned buffer to save each vector register. // Note that the stack pointer is always 16-byte aligned. - subi %r4, %r1, 16 + subi 4, 1, 16 -#define PPC64_STV_UNALIGNED(n) \ - stvx %v##n, 0, %r4 ;\ - ld %r5, 0(%r4) ;\ - std %r5, (PPC64_OFFS_V + n * 16)(%r3) ;\ - ld %r5, 8(%r4) ;\ - std %r5, (PPC64_OFFS_V + n * 16 + 8)(%r3) +#define PPC64_STV_UNALIGNED(n) \ + stvx n, 0, 4 ;\ + ld 5, 0(4) ;\ + std 5, (PPC64_OFFS_V + n * 16)(3) ;\ + ld 5, 8(4) ;\ + std 5, (PPC64_OFFS_V + n * 16 + 8)(3) PPC64_STV_UNALIGNED(0) PPC64_STV_UNALIGNED(1) @@ -555,11 +565,11 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) #endif #endif - li %r3, 0 // return UNW_ESUCCESS + li 3, 0 // return UNW_ESUCCESS blr -#elif defined(__ppc__) +#elif defined(__powerpc__) // // extern int unw_getcontext(unw_context_t* thread_state) @@ -567,141 +577,147 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) // On entry: // thread_state pointer is in r3 // +#if defined(_AIX) +DEFINE_LIBUNWIND_FUNCTION_AND_WEAK_ALIAS(__unw_getcontext, unw_getcontext) +#else DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) - stw %r0, 8(%r3) - mflr %r0 - stw %r0, 0(%r3) // store lr as ssr0 - stw %r1, 12(%r3) - stw %r2, 16(%r3) - stw %r3, 20(%r3) - stw %r4, 24(%r3) - stw %r5, 28(%r3) - stw %r6, 32(%r3) - stw %r7, 36(%r3) - stw %r8, 40(%r3) - stw %r9, 44(%r3) - stw %r10, 48(%r3) - stw %r11, 52(%r3) - stw %r12, 56(%r3) - stw %r13, 60(%r3) - stw %r14, 64(%r3) - stw %r15, 68(%r3) - stw %r16, 72(%r3) - stw %r17, 76(%r3) - stw %r18, 80(%r3) - stw %r19, 84(%r3) - stw %r20, 88(%r3) - stw %r21, 92(%r3) - stw %r22, 96(%r3) - stw %r23,100(%r3) - stw %r24,104(%r3) - stw %r25,108(%r3) - stw %r26,112(%r3) - stw %r27,116(%r3) - stw %r28,120(%r3) - stw %r29,124(%r3) - stw %r30,128(%r3) - stw %r31,132(%r3) +#endif + stw 0, 8(3) + mflr 0 + stw 0, 0(3) // store lr as ssr0 + stw 1, 12(3) + stw 2, 16(3) + stw 3, 20(3) + stw 4, 24(3) + stw 5, 28(3) + stw 6, 32(3) + stw 7, 36(3) + stw 8, 40(3) + stw 9, 44(3) + stw 10, 48(3) + stw 11, 52(3) + stw 12, 56(3) + stw 13, 60(3) + stw 14, 64(3) + stw 15, 68(3) + stw 16, 72(3) + stw 17, 76(3) + stw 18, 80(3) + stw 19, 84(3) + stw 20, 88(3) + stw 21, 92(3) + stw 22, 96(3) + stw 23,100(3) + stw 24,104(3) + stw 25,108(3) + stw 26,112(3) + stw 27,116(3) + stw 28,120(3) + stw 29,124(3) + stw 30,128(3) + stw 31,132(3) +#if defined(__ALTIVEC__) // save VRSave register - mfspr %r0, 256 - stw %r0, 156(%r3) + mfspr 0, 256 + stw 0, 156(3) +#endif // save CR registers - mfcr %r0 - stw %r0, 136(%r3) + mfcr 0 + stw 0, 136(3) // save CTR register - mfctr %r0 - stw %r0, 148(%r3) + mfctr 0 + stw 0, 148(3) #if !defined(__NO_FPRS__) // save float registers - stfd %f0, 160(%r3) - stfd %f1, 168(%r3) - stfd %f2, 176(%r3) - stfd %f3, 184(%r3) - stfd %f4, 192(%r3) - stfd %f5, 200(%r3) - stfd %f6, 208(%r3) - stfd %f7, 216(%r3) - stfd %f8, 224(%r3) - stfd %f9, 232(%r3) - stfd %f10,240(%r3) - stfd %f11,248(%r3) - stfd %f12,256(%r3) - stfd %f13,264(%r3) - stfd %f14,272(%r3) - stfd %f15,280(%r3) - stfd %f16,288(%r3) - stfd %f17,296(%r3) - stfd %f18,304(%r3) - stfd %f19,312(%r3) - stfd %f20,320(%r3) - stfd %f21,328(%r3) - stfd %f22,336(%r3) - stfd %f23,344(%r3) - stfd %f24,352(%r3) - stfd %f25,360(%r3) - stfd %f26,368(%r3) - stfd %f27,376(%r3) - stfd %f28,384(%r3) - stfd %f29,392(%r3) - stfd %f30,400(%r3) - stfd %f31,408(%r3) + stfd 0, 160(3) + stfd 1, 168(3) + stfd 2, 176(3) + stfd 3, 184(3) + stfd 4, 192(3) + stfd 5, 200(3) + stfd 6, 208(3) + stfd 7, 216(3) + stfd 8, 224(3) + stfd 9, 232(3) + stfd 10,240(3) + stfd 11,248(3) + stfd 12,256(3) + stfd 13,264(3) + stfd 14,272(3) + stfd 15,280(3) + stfd 16,288(3) + stfd 17,296(3) + stfd 18,304(3) + stfd 19,312(3) + stfd 20,320(3) + stfd 21,328(3) + stfd 22,336(3) + stfd 23,344(3) + stfd 24,352(3) + stfd 25,360(3) + stfd 26,368(3) + stfd 27,376(3) + stfd 28,384(3) + stfd 29,392(3) + stfd 30,400(3) + stfd 31,408(3) #endif #if defined(__ALTIVEC__) // save vector registers - subi %r4, %r1, 16 - rlwinm %r4, %r4, 0, 0, 27 // mask low 4-bits + subi 4, 1, 16 + rlwinm 4, 4, 0, 0, 27 // mask low 4-bits // r4 is now a 16-byte aligned pointer into the red zone #define SAVE_VECTOR_UNALIGNED(_vec, _offset) \ - stvx _vec, 0, %r4 SEPARATOR \ - lwz %r5, 0(%r4) SEPARATOR \ - stw %r5, _offset(%r3) SEPARATOR \ - lwz %r5, 4(%r4) SEPARATOR \ - stw %r5, _offset+4(%r3) SEPARATOR \ - lwz %r5, 8(%r4) SEPARATOR \ - stw %r5, _offset+8(%r3) SEPARATOR \ - lwz %r5, 12(%r4) SEPARATOR \ - stw %r5, _offset+12(%r3) - - SAVE_VECTOR_UNALIGNED( %v0, 424+0x000) - SAVE_VECTOR_UNALIGNED( %v1, 424+0x010) - SAVE_VECTOR_UNALIGNED( %v2, 424+0x020) - SAVE_VECTOR_UNALIGNED( %v3, 424+0x030) - SAVE_VECTOR_UNALIGNED( %v4, 424+0x040) - SAVE_VECTOR_UNALIGNED( %v5, 424+0x050) - SAVE_VECTOR_UNALIGNED( %v6, 424+0x060) - SAVE_VECTOR_UNALIGNED( %v7, 424+0x070) - SAVE_VECTOR_UNALIGNED( %v8, 424+0x080) - SAVE_VECTOR_UNALIGNED( %v9, 424+0x090) - SAVE_VECTOR_UNALIGNED(%v10, 424+0x0A0) - SAVE_VECTOR_UNALIGNED(%v11, 424+0x0B0) - SAVE_VECTOR_UNALIGNED(%v12, 424+0x0C0) - SAVE_VECTOR_UNALIGNED(%v13, 424+0x0D0) - SAVE_VECTOR_UNALIGNED(%v14, 424+0x0E0) - SAVE_VECTOR_UNALIGNED(%v15, 424+0x0F0) - SAVE_VECTOR_UNALIGNED(%v16, 424+0x100) - SAVE_VECTOR_UNALIGNED(%v17, 424+0x110) - SAVE_VECTOR_UNALIGNED(%v18, 424+0x120) - SAVE_VECTOR_UNALIGNED(%v19, 424+0x130) - SAVE_VECTOR_UNALIGNED(%v20, 424+0x140) - SAVE_VECTOR_UNALIGNED(%v21, 424+0x150) - SAVE_VECTOR_UNALIGNED(%v22, 424+0x160) - SAVE_VECTOR_UNALIGNED(%v23, 424+0x170) - SAVE_VECTOR_UNALIGNED(%v24, 424+0x180) - SAVE_VECTOR_UNALIGNED(%v25, 424+0x190) - SAVE_VECTOR_UNALIGNED(%v26, 424+0x1A0) - SAVE_VECTOR_UNALIGNED(%v27, 424+0x1B0) - SAVE_VECTOR_UNALIGNED(%v28, 424+0x1C0) - SAVE_VECTOR_UNALIGNED(%v29, 424+0x1D0) - SAVE_VECTOR_UNALIGNED(%v30, 424+0x1E0) - SAVE_VECTOR_UNALIGNED(%v31, 424+0x1F0) + stvx _vec, 0, 4 SEPARATOR \ + lwz 5, 0(4) SEPARATOR \ + stw 5, _offset(3) SEPARATOR \ + lwz 5, 4(4) SEPARATOR \ + stw 5, _offset+4(3) SEPARATOR \ + lwz 5, 8(4) SEPARATOR \ + stw 5, _offset+8(3) SEPARATOR \ + lwz 5, 12(4) SEPARATOR \ + stw 5, _offset+12(3) + + SAVE_VECTOR_UNALIGNED( 0, 424+0x000) + SAVE_VECTOR_UNALIGNED( 1, 424+0x010) + SAVE_VECTOR_UNALIGNED( 2, 424+0x020) + SAVE_VECTOR_UNALIGNED( 3, 424+0x030) + SAVE_VECTOR_UNALIGNED( 4, 424+0x040) + SAVE_VECTOR_UNALIGNED( 5, 424+0x050) + SAVE_VECTOR_UNALIGNED( 6, 424+0x060) + SAVE_VECTOR_UNALIGNED( 7, 424+0x070) + SAVE_VECTOR_UNALIGNED( 8, 424+0x080) + SAVE_VECTOR_UNALIGNED( 9, 424+0x090) + SAVE_VECTOR_UNALIGNED(10, 424+0x0A0) + SAVE_VECTOR_UNALIGNED(11, 424+0x0B0) + SAVE_VECTOR_UNALIGNED(12, 424+0x0C0) + SAVE_VECTOR_UNALIGNED(13, 424+0x0D0) + SAVE_VECTOR_UNALIGNED(14, 424+0x0E0) + SAVE_VECTOR_UNALIGNED(15, 424+0x0F0) + SAVE_VECTOR_UNALIGNED(16, 424+0x100) + SAVE_VECTOR_UNALIGNED(17, 424+0x110) + SAVE_VECTOR_UNALIGNED(18, 424+0x120) + SAVE_VECTOR_UNALIGNED(19, 424+0x130) + SAVE_VECTOR_UNALIGNED(20, 424+0x140) + SAVE_VECTOR_UNALIGNED(21, 424+0x150) + SAVE_VECTOR_UNALIGNED(22, 424+0x160) + SAVE_VECTOR_UNALIGNED(23, 424+0x170) + SAVE_VECTOR_UNALIGNED(24, 424+0x180) + SAVE_VECTOR_UNALIGNED(25, 424+0x190) + SAVE_VECTOR_UNALIGNED(26, 424+0x1A0) + SAVE_VECTOR_UNALIGNED(27, 424+0x1B0) + SAVE_VECTOR_UNALIGNED(28, 424+0x1C0) + SAVE_VECTOR_UNALIGNED(29, 424+0x1D0) + SAVE_VECTOR_UNALIGNED(30, 424+0x1E0) + SAVE_VECTOR_UNALIGNED(31, 424+0x1F0) #endif - li %r3, 0 // return UNW_ESUCCESS + li 3, 0 // return UNW_ESUCCESS blr @@ -999,6 +1015,64 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) jumpr r31 +#elif defined(__sparc__) && defined(__arch64__) + +# +# extern int __unw_getcontext(unw_context_t* thread_state) +# +# On entry: +# thread_state pointer is in %o0 +# +DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) + .register %g2, #scratch + .register %g3, #scratch + .register %g6, #scratch + .register %g7, #scratch + stx %g1, [%o0 + 0x08] + stx %g2, [%o0 + 0x10] + stx %g3, [%o0 + 0x18] + stx %g4, [%o0 + 0x20] + stx %g5, [%o0 + 0x28] + stx %g6, [%o0 + 0x30] + stx %g7, [%o0 + 0x38] + stx %o0, [%o0 + 0x40] + stx %o1, [%o0 + 0x48] + stx %o2, [%o0 + 0x50] + stx %o3, [%o0 + 0x58] + stx %o4, [%o0 + 0x60] + stx %o5, [%o0 + 0x68] + stx %o6, [%o0 + 0x70] + stx %o7, [%o0 + 0x78] + stx %l0, [%o0 + 0x80] + stx %l1, [%o0 + 0x88] + stx %l2, [%o0 + 0x90] + stx %l3, [%o0 + 0x98] + stx %l4, [%o0 + 0xa0] + stx %l5, [%o0 + 0xa8] + stx %l6, [%o0 + 0xb0] + stx %l7, [%o0 + 0xb8] + stx %i0, [%o0 + 0xc0] + stx %i1, [%o0 + 0xc8] + stx %i2, [%o0 + 0xd0] + stx %i3, [%o0 + 0xd8] + stx %i4, [%o0 + 0xe0] + stx %i5, [%o0 + 0xe8] + stx %i6, [%o0 + 0xf0] + stx %i7, [%o0 + 0xf8] + + # save StackGhost cookie + mov %i7, %g4 + save %sp, -176, %sp + # register window flush necessary even without StackGhost + flushw + restore + ldx [%sp + 2047 + 0x78], %g5 + xor %g4, %g5, %g4 + stx %g4, [%o0 + 0x100] + retl + # return UNW_ESUCCESS + clr %o0 + #elif defined(__sparc__) # @@ -1029,7 +1103,7 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) jmp %o7 clr %o0 // return UNW_ESUCCESS -#elif defined(__riscv) && __riscv_xlen == 64 +#elif defined(__riscv) # # extern int __unw_getcontext(unw_context_t* thread_state) @@ -1038,76 +1112,119 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) # thread_state pointer is in a0 # DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) - sd x1, (8 * 0)(a0) // store ra as pc - sd x1, (8 * 1)(a0) - sd x2, (8 * 2)(a0) - sd x3, (8 * 3)(a0) - sd x4, (8 * 4)(a0) - sd x5, (8 * 5)(a0) - sd x6, (8 * 6)(a0) - sd x7, (8 * 7)(a0) - sd x8, (8 * 8)(a0) - sd x9, (8 * 9)(a0) - sd x10, (8 * 10)(a0) - sd x11, (8 * 11)(a0) - sd x12, (8 * 12)(a0) - sd x13, (8 * 13)(a0) - sd x14, (8 * 14)(a0) - sd x15, (8 * 15)(a0) - sd x16, (8 * 16)(a0) - sd x17, (8 * 17)(a0) - sd x18, (8 * 18)(a0) - sd x19, (8 * 19)(a0) - sd x20, (8 * 20)(a0) - sd x21, (8 * 21)(a0) - sd x22, (8 * 22)(a0) - sd x23, (8 * 23)(a0) - sd x24, (8 * 24)(a0) - sd x25, (8 * 25)(a0) - sd x26, (8 * 26)(a0) - sd x27, (8 * 27)(a0) - sd x28, (8 * 28)(a0) - sd x29, (8 * 29)(a0) - sd x30, (8 * 30)(a0) - sd x31, (8 * 31)(a0) - -#if defined(__riscv_flen) && __riscv_flen == 64 - fsd f0, (8 * 32 + 8 * 0)(a0) - fsd f1, (8 * 32 + 8 * 1)(a0) - fsd f2, (8 * 32 + 8 * 2)(a0) - fsd f3, (8 * 32 + 8 * 3)(a0) - fsd f4, (8 * 32 + 8 * 4)(a0) - fsd f5, (8 * 32 + 8 * 5)(a0) - fsd f6, (8 * 32 + 8 * 6)(a0) - fsd f7, (8 * 32 + 8 * 7)(a0) - fsd f8, (8 * 32 + 8 * 8)(a0) - fsd f9, (8 * 32 + 8 * 9)(a0) - fsd f10, (8 * 32 + 8 * 10)(a0) - fsd f11, (8 * 32 + 8 * 11)(a0) - fsd f12, (8 * 32 + 8 * 12)(a0) - fsd f13, (8 * 32 + 8 * 13)(a0) - fsd f14, (8 * 32 + 8 * 14)(a0) - fsd f15, (8 * 32 + 8 * 15)(a0) - fsd f16, (8 * 32 + 8 * 16)(a0) - fsd f17, (8 * 32 + 8 * 17)(a0) - fsd f18, (8 * 32 + 8 * 18)(a0) - fsd f19, (8 * 32 + 8 * 19)(a0) - fsd f20, (8 * 32 + 8 * 20)(a0) - fsd f21, (8 * 32 + 8 * 21)(a0) - fsd f22, (8 * 32 + 8 * 22)(a0) - fsd f23, (8 * 32 + 8 * 23)(a0) - fsd f24, (8 * 32 + 8 * 24)(a0) - fsd f25, (8 * 32 + 8 * 25)(a0) - fsd f26, (8 * 32 + 8 * 26)(a0) - fsd f27, (8 * 32 + 8 * 27)(a0) - fsd f28, (8 * 32 + 8 * 28)(a0) - fsd f29, (8 * 32 + 8 * 29)(a0) - fsd f30, (8 * 32 + 8 * 30)(a0) - fsd f31, (8 * 32 + 8 * 31)(a0) -#endif + ISTORE x1, (RISCV_ISIZE * 0)(a0) // store ra as pc + ISTORE x1, (RISCV_ISIZE * 1)(a0) + ISTORE x2, (RISCV_ISIZE * 2)(a0) + ISTORE x3, (RISCV_ISIZE * 3)(a0) + ISTORE x4, (RISCV_ISIZE * 4)(a0) + ISTORE x5, (RISCV_ISIZE * 5)(a0) + ISTORE x6, (RISCV_ISIZE * 6)(a0) + ISTORE x7, (RISCV_ISIZE * 7)(a0) + ISTORE x8, (RISCV_ISIZE * 8)(a0) + ISTORE x9, (RISCV_ISIZE * 9)(a0) + ISTORE x10, (RISCV_ISIZE * 10)(a0) + ISTORE x11, (RISCV_ISIZE * 11)(a0) + ISTORE x12, (RISCV_ISIZE * 12)(a0) + ISTORE x13, (RISCV_ISIZE * 13)(a0) + ISTORE x14, (RISCV_ISIZE * 14)(a0) + ISTORE x15, (RISCV_ISIZE * 15)(a0) + ISTORE x16, (RISCV_ISIZE * 16)(a0) + ISTORE x17, (RISCV_ISIZE * 17)(a0) + ISTORE x18, (RISCV_ISIZE * 18)(a0) + ISTORE x19, (RISCV_ISIZE * 19)(a0) + ISTORE x20, (RISCV_ISIZE * 20)(a0) + ISTORE x21, (RISCV_ISIZE * 21)(a0) + ISTORE x22, (RISCV_ISIZE * 22)(a0) + ISTORE x23, (RISCV_ISIZE * 23)(a0) + ISTORE x24, (RISCV_ISIZE * 24)(a0) + ISTORE x25, (RISCV_ISIZE * 25)(a0) + ISTORE x26, (RISCV_ISIZE * 26)(a0) + ISTORE x27, (RISCV_ISIZE * 27)(a0) + ISTORE x28, (RISCV_ISIZE * 28)(a0) + ISTORE x29, (RISCV_ISIZE * 29)(a0) + ISTORE x30, (RISCV_ISIZE * 30)(a0) + ISTORE x31, (RISCV_ISIZE * 31)(a0) + +# if defined(__riscv_flen) + FSTORE f0, (RISCV_FOFFSET + RISCV_FSIZE * 0)(a0) + FSTORE f1, (RISCV_FOFFSET + RISCV_FSIZE * 1)(a0) + FSTORE f2, (RISCV_FOFFSET + RISCV_FSIZE * 2)(a0) + FSTORE f3, (RISCV_FOFFSET + RISCV_FSIZE * 3)(a0) + FSTORE f4, (RISCV_FOFFSET + RISCV_FSIZE * 4)(a0) + FSTORE f5, (RISCV_FOFFSET + RISCV_FSIZE * 5)(a0) + FSTORE f6, (RISCV_FOFFSET + RISCV_FSIZE * 6)(a0) + FSTORE f7, (RISCV_FOFFSET + RISCV_FSIZE * 7)(a0) + FSTORE f8, (RISCV_FOFFSET + RISCV_FSIZE * 8)(a0) + FSTORE f9, (RISCV_FOFFSET + RISCV_FSIZE * 9)(a0) + FSTORE f10, (RISCV_FOFFSET + RISCV_FSIZE * 10)(a0) + FSTORE f11, (RISCV_FOFFSET + RISCV_FSIZE * 11)(a0) + FSTORE f12, (RISCV_FOFFSET + RISCV_FSIZE * 12)(a0) + FSTORE f13, (RISCV_FOFFSET + RISCV_FSIZE * 13)(a0) + FSTORE f14, (RISCV_FOFFSET + RISCV_FSIZE * 14)(a0) + FSTORE f15, (RISCV_FOFFSET + RISCV_FSIZE * 15)(a0) + FSTORE f16, (RISCV_FOFFSET + RISCV_FSIZE * 16)(a0) + FSTORE f17, (RISCV_FOFFSET + RISCV_FSIZE * 17)(a0) + FSTORE f18, (RISCV_FOFFSET + RISCV_FSIZE * 18)(a0) + FSTORE f19, (RISCV_FOFFSET + RISCV_FSIZE * 19)(a0) + FSTORE f20, (RISCV_FOFFSET + RISCV_FSIZE * 20)(a0) + FSTORE f21, (RISCV_FOFFSET + RISCV_FSIZE * 21)(a0) + FSTORE f22, (RISCV_FOFFSET + RISCV_FSIZE * 22)(a0) + FSTORE f23, (RISCV_FOFFSET + RISCV_FSIZE * 23)(a0) + FSTORE f24, (RISCV_FOFFSET + RISCV_FSIZE * 24)(a0) + FSTORE f25, (RISCV_FOFFSET + RISCV_FSIZE * 25)(a0) + FSTORE f26, (RISCV_FOFFSET + RISCV_FSIZE * 26)(a0) + FSTORE f27, (RISCV_FOFFSET + RISCV_FSIZE * 27)(a0) + FSTORE f28, (RISCV_FOFFSET + RISCV_FSIZE * 28)(a0) + FSTORE f29, (RISCV_FOFFSET + RISCV_FSIZE * 29)(a0) + FSTORE f30, (RISCV_FOFFSET + RISCV_FSIZE * 30)(a0) + FSTORE f31, (RISCV_FOFFSET + RISCV_FSIZE * 31)(a0) +# endif li a0, 0 // return UNW_ESUCCESS ret // jump to ra + +#elif defined(__s390x__) + +// +// extern int __unw_getcontext(unw_context_t* thread_state) +// +// On entry: +// thread_state pointer is in r2 +// +DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) + + // Save GPRs + stmg %r0, %r15, 16(%r2) + + // Save PSWM + epsw %r0, %r1 + stm %r0, %r1, 0(%r2) + + // Store return address as PSWA + stg %r14, 8(%r2) + + // Save FPRs + std %f0, 144(%r2) + std %f1, 152(%r2) + std %f2, 160(%r2) + std %f3, 168(%r2) + std %f4, 176(%r2) + std %f5, 184(%r2) + std %f6, 192(%r2) + std %f7, 200(%r2) + std %f8, 208(%r2) + std %f9, 216(%r2) + std %f10, 224(%r2) + std %f11, 232(%r2) + std %f12, 240(%r2) + std %f13, 248(%r2) + std %f14, 256(%r2) + std %f15, 264(%r2) + + // Return UNW_ESUCCESS + lghi %r2, 0 + br %r14 + #endif WEAK_ALIAS(__unw_getcontext, unw_getcontext) diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind_AIXExtras.cpp b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind_AIXExtras.cpp new file mode 100644 index 0000000000..548f21a3cb --- /dev/null +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind_AIXExtras.cpp @@ -0,0 +1,67 @@ +// clang-format off +#if defined(__unix__) || defined(__unix) || defined(unix) || \ + (defined(__APPLE__) && defined(__MACH__)) +//===--------------------- Unwind_AIXExtras.cpp -------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// +//===----------------------------------------------------------------------===// + +// This file is only used for AIX. +#if defined(_AIX) + +#include "config.h" +#include "libunwind_ext.h" +#include + +namespace libunwind { +// getFuncNameFromTBTable +// Get the function name from its traceback table. +char *getFuncNameFromTBTable(uintptr_t Pc, uint16_t &NameLen, + unw_word_t *Offset) { + uint32_t *p = reinterpret_cast(Pc); + *Offset = 0; + + // Keep looking forward until a word of 0 is found. The traceback + // table starts at the following word. + while (*p) + p++; + tbtable *TBTable = reinterpret_cast(p + 1); + + if (!TBTable->tb.name_present) + return NULL; + + // Get to the name of the function. + p = reinterpret_cast(&TBTable->tb_ext); + + // Skip field parminfo if it exists. + if (TBTable->tb.fixedparms || TBTable->tb.floatparms) + p++; + + // If the tb_offset field exisits, get the offset from the start of + // the function to pc. Skip the field. + if (TBTable->tb.has_tboff) { + unw_word_t StartIp = + reinterpret_cast(TBTable) - *p - sizeof(uint32_t); + *Offset = Pc - StartIp; + p++; + } + + // Skip field hand_mask if it exists. + if (TBTable->tb.int_hndl) + p++; + + // Skip fields ctl_info and ctl_info_disp if they exist. + if (TBTable->tb.has_ctl) { + p += 1 + *p; + } + + NameLen = *(reinterpret_cast(p)); + return reinterpret_cast(p) + sizeof(uint16_t); +} +} // namespace libunwind +#endif // defined(_AIX) +#endif diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind_AppleExtras.cpp b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind_AppleExtras.cpp index ad080b4e31..7abdaaed72 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind_AppleExtras.cpp +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind_AppleExtras.cpp @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===--------------------- Unwind_AppleExtras.cpp -------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/__libunwind_config.h b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/__libunwind_config.h index adf86ee4e8..900d5a2a27 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/__libunwind_config.h +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/__libunwind_config.h @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===------------------------- __libunwind_config.h -----------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -12,8 +12,10 @@ #ifndef ____LIBUNWIND_CONFIG_H__ #define ____LIBUNWIND_CONFIG_H__ +#define _LIBUNWIND_VERSION 15000 + #if defined(__arm__) && !defined(__USING_SJLJ_EXCEPTIONS__) && \ - !defined(__ARM_DWARF_EH__) + !defined(__ARM_DWARF_EH__) && !defined(__SEH__) #define _LIBUNWIND_ARM_EHABI #endif @@ -26,9 +28,11 @@ #define _LIBUNWIND_HIGHEST_DWARF_REGISTER_OR1K 32 #define _LIBUNWIND_HIGHEST_DWARF_REGISTER_MIPS 65 #define _LIBUNWIND_HIGHEST_DWARF_REGISTER_SPARC 31 +#define _LIBUNWIND_HIGHEST_DWARF_REGISTER_SPARC64 31 #define _LIBUNWIND_HIGHEST_DWARF_REGISTER_HEXAGON 34 #define _LIBUNWIND_HIGHEST_DWARF_REGISTER_RISCV 64 #define _LIBUNWIND_HIGHEST_DWARF_REGISTER_VE 143 +#define _LIBUNWIND_HIGHEST_DWARF_REGISTER_S390X 83 #if defined(_LIBUNWIND_IS_NATIVE_ONLY) # if defined(__linux__) @@ -58,7 +62,7 @@ # define _LIBUNWIND_CONTEXT_SIZE 167 # define _LIBUNWIND_CURSOR_SIZE 179 # define _LIBUNWIND_HIGHEST_DWARF_REGISTER _LIBUNWIND_HIGHEST_DWARF_REGISTER_PPC64 -# elif defined(__ppc__) +# elif defined(__powerpc__) # define _LIBUNWIND_TARGET_PPC 1 # define _LIBUNWIND_CONTEXT_SIZE 117 # define _LIBUNWIND_CURSOR_SIZE 124 @@ -128,18 +132,31 @@ # error "Unsupported MIPS ABI and/or environment" # endif # define _LIBUNWIND_HIGHEST_DWARF_REGISTER _LIBUNWIND_HIGHEST_DWARF_REGISTER_MIPS +#elif defined(__sparc__) && defined(__arch64__) +#define _LIBUNWIND_TARGET_SPARC64 1 +#define _LIBUNWIND_HIGHEST_DWARF_REGISTER \ + _LIBUNWIND_HIGHEST_DWARF_REGISTER_SPARC64 +#define _LIBUNWIND_CONTEXT_SIZE 33 +#define _LIBUNWIND_CURSOR_SIZE 45 # elif defined(__sparc__) #define _LIBUNWIND_TARGET_SPARC 1 #define _LIBUNWIND_HIGHEST_DWARF_REGISTER _LIBUNWIND_HIGHEST_DWARF_REGISTER_SPARC #define _LIBUNWIND_CONTEXT_SIZE 16 #define _LIBUNWIND_CURSOR_SIZE 23 # elif defined(__riscv) -# if __riscv_xlen == 64 -# define _LIBUNWIND_TARGET_RISCV 1 -# define _LIBUNWIND_CONTEXT_SIZE 64 -# define _LIBUNWIND_CURSOR_SIZE 76 +# define _LIBUNWIND_TARGET_RISCV 1 +# if defined(__riscv_flen) +# define RISCV_FLEN __riscv_flen +# else +# define RISCV_FLEN 0 +# endif +# define _LIBUNWIND_CONTEXT_SIZE (32 * (__riscv_xlen + RISCV_FLEN) / 64) +# if __riscv_xlen == 32 +# define _LIBUNWIND_CURSOR_SIZE (_LIBUNWIND_CONTEXT_SIZE + 7) +# elif __riscv_xlen == 64 +# define _LIBUNWIND_CURSOR_SIZE (_LIBUNWIND_CONTEXT_SIZE + 12) # else -# error "Unsupported RISC-V ABI" +# error "Unsupported RISC-V ABI" # endif # define _LIBUNWIND_HIGHEST_DWARF_REGISTER _LIBUNWIND_HIGHEST_DWARF_REGISTER_RISCV # elif defined(__ve__) @@ -147,6 +164,11 @@ # define _LIBUNWIND_CONTEXT_SIZE 67 # define _LIBUNWIND_CURSOR_SIZE 79 # define _LIBUNWIND_HIGHEST_DWARF_REGISTER _LIBUNWIND_HIGHEST_DWARF_REGISTER_VE +# elif defined(__s390x__) +# define _LIBUNWIND_TARGET_S390X 1 +# define _LIBUNWIND_CONTEXT_SIZE 34 +# define _LIBUNWIND_CURSOR_SIZE 46 +# define _LIBUNWIND_HIGHEST_DWARF_REGISTER _LIBUNWIND_HIGHEST_DWARF_REGISTER_S390X # else # error "Unsupported architecture." # endif @@ -161,9 +183,11 @@ # define _LIBUNWIND_TARGET_MIPS_O32 1 # define _LIBUNWIND_TARGET_MIPS_NEWABI 1 # define _LIBUNWIND_TARGET_SPARC 1 +# define _LIBUNWIND_TARGET_SPARC64 1 # define _LIBUNWIND_TARGET_HEXAGON 1 # define _LIBUNWIND_TARGET_RISCV 1 # define _LIBUNWIND_TARGET_VE 1 +# define _LIBUNWIND_TARGET_S390X 1 # define _LIBUNWIND_CONTEXT_SIZE 167 # define _LIBUNWIND_CURSOR_SIZE 179 # define _LIBUNWIND_HIGHEST_DWARF_REGISTER 287 diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/assembly.h b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/assembly.h index 266e52e63b..1ea1759c8d 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/assembly.h +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/assembly.h @@ -18,6 +18,13 @@ #ifndef UNWIND_ASSEMBLY_H #define UNWIND_ASSEMBLY_H +#if defined(__linux__) && defined(__CET__) +#include +#define _LIBUNWIND_CET_ENDBR _CET_ENDBR +#else +#define _LIBUNWIND_CET_ENDBR +#endif + #if defined(__powerpc64__) #define SEPARATOR ; #define PPC64_OFFS_SRR0 0 @@ -30,11 +37,41 @@ #define PPC64_OFFS_V 824 #elif defined(__APPLE__) && defined(__aarch64__) #define SEPARATOR %% +#elif defined(__riscv) +# define RISCV_ISIZE (__riscv_xlen / 8) +# define RISCV_FOFFSET (RISCV_ISIZE * 32) +# if defined(__riscv_flen) +# define RISCV_FSIZE (__riscv_flen / 8) +# endif + +# if __riscv_xlen == 64 +# define ILOAD ld +# define ISTORE sd +# elif __riscv_xlen == 32 +# define ILOAD lw +# define ISTORE sw +# else +# error "Unsupported __riscv_xlen" +# endif + +# if defined(__riscv_flen) +# if __riscv_flen == 64 +# define FLOAD fld +# define FSTORE fsd +# elif __riscv_flen == 32 +# define FLOAD flw +# define FSTORE fsw +# else +# error "Unsupported __riscv_flen" +# endif +# endif +# define SEPARATOR ; #else #define SEPARATOR ; #endif -#if defined(__powerpc64__) && (!defined(_CALL_ELF) || _CALL_ELF == 1) +#if defined(__powerpc64__) && (!defined(_CALL_ELF) || _CALL_ELF == 1) && \ + !defined(_AIX) #define PPC64_OPD1 .section .opd,"aw",@progbits SEPARATOR #define PPC64_OPD2 SEPARATOR \ .p2align 3 SEPARATOR \ @@ -48,7 +85,7 @@ #define PPC64_OPD2 #endif -#if defined(__ARM_FEATURE_BTI_DEFAULT) +#if defined(__aarch64__) && defined(__ARM_FEATURE_BTI_DEFAULT) .pushsection ".note.gnu.property", "a" SEPARATOR \ .balign 8 SEPARATOR \ .long 4 SEPARATOR \ @@ -66,6 +103,17 @@ #define AARCH64_BTI #endif +#if !defined(__aarch64__) +#ifdef __ARM_FEATURE_PAC_DEFAULT + .eabi_attribute Tag_PAC_extension, 2 + .eabi_attribute Tag_PACRET_use, 1 +#endif +#ifdef __ARM_FEATURE_BTI_DEFAULT + .eabi_attribute Tag_BTI_extension, 1 + .eabi_attribute Tag_BTI_use, 1 +#endif +#endif + #define GLUE2(a, b) a ## b #define GLUE(a, b) GLUE2(a, b) #define SYMBOL_NAME(name) GLUE(__USER_LABEL_PREFIX__, name) @@ -73,12 +121,15 @@ #if defined(__APPLE__) #define SYMBOL_IS_FUNC(name) -#define EXPORT_SYMBOL(name) #define HIDDEN_SYMBOL(name) .private_extern name -#define WEAK_SYMBOL(name) .weak_reference name +#if defined(_LIBUNWIND_HIDE_SYMBOLS) +#define EXPORT_SYMBOL(name) HIDDEN_SYMBOL(name) +#else +#define EXPORT_SYMBOL(name) +#endif #define WEAK_ALIAS(name, aliasname) \ .globl SYMBOL_NAME(aliasname) SEPARATOR \ - WEAK_SYMBOL(aliasname) SEPARATOR \ + EXPORT_SYMBOL(SYMBOL_NAME(aliasname)) SEPARATOR \ SYMBOL_NAME(aliasname) = SYMBOL_NAME(name) #define NO_EXEC_STACK_DIRECTIVE @@ -90,17 +141,23 @@ #else #define SYMBOL_IS_FUNC(name) .type name,@function #endif -#define EXPORT_SYMBOL(name) #define HIDDEN_SYMBOL(name) .hidden name +#if defined(_LIBUNWIND_HIDE_SYMBOLS) +#define EXPORT_SYMBOL(name) HIDDEN_SYMBOL(name) +#else +#define EXPORT_SYMBOL(name) +#endif #define WEAK_SYMBOL(name) .weak name #if defined(__hexagon__) -#define WEAK_ALIAS(name, aliasname) \ - WEAK_SYMBOL(aliasname) SEPARATOR \ +#define WEAK_ALIAS(name, aliasname) \ + EXPORT_SYMBOL(SYMBOL_NAME(aliasname)) SEPARATOR \ + WEAK_SYMBOL(SYMBOL_NAME(aliasname)) SEPARATOR \ .equiv SYMBOL_NAME(aliasname), SYMBOL_NAME(name) #else #define WEAK_ALIAS(name, aliasname) \ - WEAK_SYMBOL(aliasname) SEPARATOR \ + EXPORT_SYMBOL(SYMBOL_NAME(aliasname)) SEPARATOR \ + WEAK_SYMBOL(SYMBOL_NAME(aliasname)) SEPARATOR \ SYMBOL_NAME(aliasname) = SYMBOL_NAME(name) #endif @@ -122,7 +179,7 @@ .section .drectve,"yn" SEPARATOR \ .ascii "-export:", #name, "\0" SEPARATOR \ .text -#if defined(_LIBUNWIND_DISABLE_VISIBILITY_ANNOTATIONS) +#if defined(_LIBUNWIND_HIDE_SYMBOLS) #define EXPORT_SYMBOL(name) #else #define EXPORT_SYMBOL(name) EXPORT_SYMBOL2(name) @@ -150,12 +207,57 @@ #elif defined(__sparc__) +#elif defined(_AIX) + +#if defined(__powerpc64__) +#define VBYTE_LEN 8 +#define CSECT_ALIGN 3 +#else +#define VBYTE_LEN 4 +#define CSECT_ALIGN 2 +#endif + +// clang-format off +#define DEFINE_LIBUNWIND_FUNCTION_AND_WEAK_ALIAS(name, aliasname) \ + .csect .text[PR], 2 SEPARATOR \ + .csect .name[PR], 2 SEPARATOR \ + .globl name[DS] SEPARATOR \ + .globl .name[PR] SEPARATOR \ + .align 4 SEPARATOR \ + .csect name[DS], CSECT_ALIGN SEPARATOR \ +aliasname: \ + .vbyte VBYTE_LEN, .name[PR] SEPARATOR \ + .vbyte VBYTE_LEN, TOC[TC0] SEPARATOR \ + .vbyte VBYTE_LEN, 0 SEPARATOR \ + .weak aliasname SEPARATOR \ + .weak .aliasname SEPARATOR \ + .csect .name[PR], 2 SEPARATOR \ +.aliasname: \ + +#define WEAK_ALIAS(name, aliasname) +#define NO_EXEC_STACK_DIRECTIVE + +// clang-format on #else #error Unsupported target #endif +#if defined(_AIX) +// clang-format off +#define DEFINE_LIBUNWIND_FUNCTION(name) \ + .globl name[DS] SEPARATOR \ + .globl .name SEPARATOR \ + .align 4 SEPARATOR \ + .csect name[DS], CSECT_ALIGN SEPARATOR \ + .vbyte VBYTE_LEN, .name SEPARATOR \ + .vbyte VBYTE_LEN, TOC[TC0] SEPARATOR \ + .vbyte VBYTE_LEN, 0 SEPARATOR \ + .csect .text[PR], 2 SEPARATOR \ +.name: +// // clang-format on +#else #define DEFINE_LIBUNWIND_FUNCTION(name) \ .globl SYMBOL_NAME(name) SEPARATOR \ HIDDEN_SYMBOL(SYMBOL_NAME(name)) SEPARATOR \ @@ -164,6 +266,7 @@ SYMBOL_NAME(name): \ PPC64_OPD2 \ AARCH64_BTI +#endif #if defined(__arm__) #if !defined(__ARM_ARCH) @@ -181,5 +284,9 @@ #endif #endif /* __arm__ */ +#if defined(__powerpc__) +#define PPC_LEFT_SHIFT(index) << (index) +#endif + #endif /* UNWIND_ASSEMBLY_H */ #endif diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/cet_unwind.h b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/cet_unwind.h new file mode 100644 index 0000000000..5a9f3281fd --- /dev/null +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/cet_unwind.h @@ -0,0 +1,45 @@ +// clang-format off +#if defined(__unix__) || defined(__unix) || defined(unix) || \ + (defined(__APPLE__) && defined(__MACH__)) +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// +//===----------------------------------------------------------------------===// + +#ifndef LIBUNWIND_CET_UNWIND_H +#define LIBUNWIND_CET_UNWIND_H + +#include "libunwind.h" + +// Currently, CET is implemented on Linux x86 platforms. +#if defined(_LIBUNWIND_TARGET_LINUX) && defined(__CET__) && defined(__SHSTK__) +#define _LIBUNWIND_USE_CET 1 +#endif + +#if defined(_LIBUNWIND_USE_CET) +#include +#include + +#define _LIBUNWIND_POP_CET_SSP(x) \ + do { \ + unsigned long ssp = _get_ssp(); \ + if (ssp != 0) { \ + unsigned int tmp = (x); \ + while (tmp > 255) { \ + _inc_ssp(255); \ + tmp -= 255; \ + } \ + _inc_ssp(tmp); \ + } \ + } while (0) +#endif + +extern void *__libunwind_cet_get_registers(unw_cursor_t *); +extern void *__libunwind_cet_get_jump_target(void); + +#endif +#endif diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/config.h b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/config.h index de7b82af56..e6eee472ee 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/config.h +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/config.h @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===----------------------------- config.h -------------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -46,6 +46,9 @@ // For ARM EHABI, Bionic didn't implement dl_iterate_phdr until API 21. After // API 21, dl_iterate_phdr exists, but dl_unwind_find_exidx is much faster. #define _LIBUNWIND_USE_DL_UNWIND_FIND_EXIDX 1 +#elif defined(_AIX) +// The traceback table at the end of each function is used for unwinding. +#define _LIBUNWIND_SUPPORT_TBTAB_UNWIND 1 #else // Assume an ELF system with a dl_iterate_phdr function. #define _LIBUNWIND_USE_DL_ITERATE_PHDR 1 @@ -55,11 +58,12 @@ #endif #endif -#if defined(_LIBUNWIND_DISABLE_VISIBILITY_ANNOTATIONS) +#if defined(_LIBUNWIND_HIDE_SYMBOLS) + // The CMake file passes -fvisibility=hidden to control ELF/Mach-O visibility. #define _LIBUNWIND_EXPORT #define _LIBUNWIND_HIDDEN #else - #if !defined(__ELF__) && !defined(__MACH__) + #if !defined(__ELF__) && !defined(__MACH__) && !defined(_AIX) #define _LIBUNWIND_EXPORT __declspec(dllexport) #define _LIBUNWIND_HIDDEN #else @@ -73,12 +77,16 @@ #define SYMBOL_NAME(name) XSTR(__USER_LABEL_PREFIX__) #name #if defined(__APPLE__) +#if defined(_LIBUNWIND_HIDE_SYMBOLS) +#define _LIBUNWIND_ALIAS_VISIBILITY(name) __asm__(".private_extern " name); +#else +#define _LIBUNWIND_ALIAS_VISIBILITY(name) +#endif #define _LIBUNWIND_WEAK_ALIAS(name, aliasname) \ __asm__(".globl " SYMBOL_NAME(aliasname)); \ __asm__(SYMBOL_NAME(aliasname) " = " SYMBOL_NAME(name)); \ - extern "C" _LIBUNWIND_EXPORT __typeof(name) aliasname \ - __attribute__((weak_import)); -#elif defined(__ELF__) + _LIBUNWIND_ALIAS_VISIBILITY(SYMBOL_NAME(aliasname)) +#elif defined(__ELF__) || defined(_AIX) #define _LIBUNWIND_WEAK_ALIAS(name, aliasname) \ extern "C" _LIBUNWIND_EXPORT __typeof(name) aliasname \ __attribute__((weak, alias(#name))); @@ -103,17 +111,14 @@ #define _LIBUNWIND_BUILD_SJLJ_APIS #endif -#if defined(__i386__) || defined(__x86_64__) || defined(__ppc__) || defined(__ppc64__) || defined(__powerpc64__) +#if defined(__i386__) || defined(__x86_64__) || defined(__powerpc__) #define _LIBUNWIND_SUPPORT_FRAME_APIS #endif -#if defined(__i386__) || defined(__x86_64__) || \ - defined(__ppc__) || defined(__ppc64__) || defined(__powerpc64__) || \ - (!defined(__APPLE__) && defined(__arm__)) || \ - defined(__aarch64__) || \ - defined(__mips__) || \ - defined(__riscv) || \ - defined(__hexagon__) +#if defined(__i386__) || defined(__x86_64__) || defined(__powerpc__) || \ + (!defined(__APPLE__) && defined(__arm__)) || defined(__aarch64__) || \ + defined(__mips__) || defined(__riscv) || defined(__hexagon__) || \ + defined(__sparc__) || defined(__s390x__) #if !defined(_LIBUNWIND_BUILD_SJLJ_APIS) #define _LIBUNWIND_BUILD_ZERO_COST_APIS #endif @@ -189,9 +194,9 @@ #ifdef __cplusplus extern "C" { #endif - extern bool logAPIs(); - extern bool logUnwinding(); - extern bool logDWARF(); + extern bool logAPIs(void); + extern bool logUnwinding(void); + extern bool logDWARF(void); #ifdef __cplusplus } #endif diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/dwarf2.h b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/dwarf2.h index 5fea08bafe..0e96ee5f7c 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/dwarf2.h +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/dwarf2.h @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===------------------------------- dwarf2.h -----------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/libunwind.cpp b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/libunwind.cpp index 5feca1c4e5..a6c2c92586 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/libunwind.cpp +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/libunwind.cpp @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===--------------------------- libunwind.cpp ----------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -14,11 +14,20 @@ #include "libunwind.h" -#include "libunwind_ext.h" #include "config.h" +#include "libunwind_ext.h" #include +// Define the __has_feature extension for compilers that do not support it so +// that we can later check for the presence of ASan in a compiler-neutral way. +#if !defined(__has_feature) +#define __has_feature(feature) 0 +#endif + +#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) +#include +#endif #if !defined(__USING_SJLJ_EXCEPTIONS__) #include "AddressSpace.hpp" @@ -45,7 +54,7 @@ _LIBUNWIND_HIDDEN int __unw_init_local(unw_cursor_t *cursor, # define REGISTER_KIND Registers_x86_64 #elif defined(__powerpc64__) # define REGISTER_KIND Registers_ppc64 -#elif defined(__ppc__) +#elif defined(__powerpc__) # define REGISTER_KIND Registers_ppc #elif defined(__aarch64__) # define REGISTER_KIND Registers_arm64 @@ -61,12 +70,16 @@ _LIBUNWIND_HIDDEN int __unw_init_local(unw_cursor_t *cursor, # define REGISTER_KIND Registers_mips_newabi #elif defined(__mips__) # warning The MIPS architecture is not supported with this ABI and environment! +#elif defined(__sparc__) && defined(__arch64__) +#define REGISTER_KIND Registers_sparc64 #elif defined(__sparc__) # define REGISTER_KIND Registers_sparc -#elif defined(__riscv) && __riscv_xlen == 64 +#elif defined(__riscv) # define REGISTER_KIND Registers_riscv #elif defined(__ve__) # define REGISTER_KIND Registers_ve +#elif defined(__s390x__) +# define REGISTER_KIND Registers_s390x #else # error Architecture not supported #endif @@ -187,6 +200,10 @@ _LIBUNWIND_WEAK_ALIAS(__unw_get_proc_info, unw_get_proc_info) /// Resume execution at cursor position (aka longjump). _LIBUNWIND_HIDDEN int __unw_resume(unw_cursor_t *cursor) { _LIBUNWIND_TRACE_API("__unw_resume(cursor=%p)", static_cast(cursor)); +#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) + // Inform the ASan runtime that now might be a good time to clean stuff up. + __asan_handle_no_return(); +#endif AbstractUnwindCursor *co = (AbstractUnwindCursor *)cursor; co->jumpto(); return UNW_EUNSPEC; @@ -235,6 +252,16 @@ _LIBUNWIND_HIDDEN int __unw_is_signal_frame(unw_cursor_t *cursor) { } _LIBUNWIND_WEAK_ALIAS(__unw_is_signal_frame, unw_is_signal_frame) +#ifdef _AIX +_LIBUNWIND_EXPORT uintptr_t __unw_get_data_rel_base(unw_cursor_t *cursor) { + _LIBUNWIND_TRACE_API("unw_get_data_rel_base(cursor=%p)", + static_cast(cursor)); + AbstractUnwindCursor *co = reinterpret_cast(cursor); + return co->getDataRelBase(); +} +_LIBUNWIND_WEAK_ALIAS(__unw_get_data_rel_base, unw_get_data_rel_base) +#endif + #ifdef __arm__ // Save VFP registers d0-d15 using FSTMIADX instead of FSTMIADD _LIBUNWIND_HIDDEN void __unw_save_vfp_as_X(unw_cursor_t *cursor) { @@ -282,6 +309,35 @@ void __unw_remove_dynamic_fde(unw_word_t fde) { // fde is own mh_group DwarfFDECache::removeAllIn((LocalAddressSpace::pint_t)fde); } + +void __unw_add_dynamic_eh_frame_section(unw_word_t eh_frame_start) { + // The eh_frame section start serves as the mh_group + unw_word_t mh_group = eh_frame_start; + CFI_Parser::CIE_Info cieInfo; + CFI_Parser::FDE_Info fdeInfo; + auto p = (LocalAddressSpace::pint_t)eh_frame_start; + while (true) { + if (CFI_Parser::decodeFDE( + LocalAddressSpace::sThisAddressSpace, p, &fdeInfo, &cieInfo, + true) == NULL) { + DwarfFDECache::add((LocalAddressSpace::pint_t)mh_group, + fdeInfo.pcStart, fdeInfo.pcEnd, + fdeInfo.fdeStart); + p += fdeInfo.fdeLength; + } else if (CFI_Parser::parseCIE( + LocalAddressSpace::sThisAddressSpace, p, &cieInfo) == NULL) { + p += cieInfo.cieLength; + } else + return; + } +} + +void __unw_remove_dynamic_eh_frame_section(unw_word_t eh_frame_start) { + // The eh_frame section start serves as the mh_group + DwarfFDECache::removeAllIn( + (LocalAddressSpace::pint_t)eh_frame_start); +} + #endif // defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) #endif // !defined(__USING_SJLJ_EXCEPTIONS__) diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/libunwind.h b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/libunwind.h index 786ecaad93..bf0edbf511 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/libunwind.h +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/libunwind.h @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===---------------------------- libunwind.h -----------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -84,7 +84,7 @@ typedef struct unw_addr_space *unw_addr_space_t; typedef int unw_regnum_t; typedef uintptr_t unw_word_t; -#if defined(__arm__) && !defined(__ARM_DWARF_EH__) +#if defined(__arm__) && !defined(__ARM_DWARF_EH__) && !defined(__SEH__) typedef uint64_t unw_fpreg_t; #else typedef double unw_fpreg_t; @@ -123,6 +123,9 @@ extern int unw_resume(unw_cursor_t *) LIBUNWIND_AVAIL; extern void unw_save_vfp_as_X(unw_cursor_t *) LIBUNWIND_AVAIL; #endif +#ifdef _AIX +extern uintptr_t unw_get_data_rel_base(unw_cursor_t *) LIBUNWIND_AVAIL; +#endif extern const char *unw_regname(unw_cursor_t *, unw_regnum_t) LIBUNWIND_AVAIL; extern int unw_get_proc_info(unw_cursor_t *, unw_proc_info_t *) LIBUNWIND_AVAIL; @@ -496,76 +499,150 @@ enum { // 64-bit ARM64 registers enum { - UNW_ARM64_X0 = 0, - UNW_ARM64_X1 = 1, - UNW_ARM64_X2 = 2, - UNW_ARM64_X3 = 3, - UNW_ARM64_X4 = 4, - UNW_ARM64_X5 = 5, - UNW_ARM64_X6 = 6, - UNW_ARM64_X7 = 7, - UNW_ARM64_X8 = 8, - UNW_ARM64_X9 = 9, - UNW_ARM64_X10 = 10, - UNW_ARM64_X11 = 11, - UNW_ARM64_X12 = 12, - UNW_ARM64_X13 = 13, - UNW_ARM64_X14 = 14, - UNW_ARM64_X15 = 15, - UNW_ARM64_X16 = 16, - UNW_ARM64_X17 = 17, - UNW_ARM64_X18 = 18, - UNW_ARM64_X19 = 19, - UNW_ARM64_X20 = 20, - UNW_ARM64_X21 = 21, - UNW_ARM64_X22 = 22, - UNW_ARM64_X23 = 23, - UNW_ARM64_X24 = 24, - UNW_ARM64_X25 = 25, - UNW_ARM64_X26 = 26, - UNW_ARM64_X27 = 27, - UNW_ARM64_X28 = 28, - UNW_ARM64_X29 = 29, - UNW_ARM64_FP = 29, - UNW_ARM64_X30 = 30, - UNW_ARM64_LR = 30, - UNW_ARM64_X31 = 31, - UNW_ARM64_SP = 31, - // reserved block - UNW_ARM64_RA_SIGN_STATE = 34, + UNW_AARCH64_X0 = 0, + UNW_AARCH64_X1 = 1, + UNW_AARCH64_X2 = 2, + UNW_AARCH64_X3 = 3, + UNW_AARCH64_X4 = 4, + UNW_AARCH64_X5 = 5, + UNW_AARCH64_X6 = 6, + UNW_AARCH64_X7 = 7, + UNW_AARCH64_X8 = 8, + UNW_AARCH64_X9 = 9, + UNW_AARCH64_X10 = 10, + UNW_AARCH64_X11 = 11, + UNW_AARCH64_X12 = 12, + UNW_AARCH64_X13 = 13, + UNW_AARCH64_X14 = 14, + UNW_AARCH64_X15 = 15, + UNW_AARCH64_X16 = 16, + UNW_AARCH64_X17 = 17, + UNW_AARCH64_X18 = 18, + UNW_AARCH64_X19 = 19, + UNW_AARCH64_X20 = 20, + UNW_AARCH64_X21 = 21, + UNW_AARCH64_X22 = 22, + UNW_AARCH64_X23 = 23, + UNW_AARCH64_X24 = 24, + UNW_AARCH64_X25 = 25, + UNW_AARCH64_X26 = 26, + UNW_AARCH64_X27 = 27, + UNW_AARCH64_X28 = 28, + UNW_AARCH64_X29 = 29, + UNW_AARCH64_FP = 29, + UNW_AARCH64_X30 = 30, + UNW_AARCH64_LR = 30, + UNW_AARCH64_X31 = 31, + UNW_AARCH64_SP = 31, + UNW_AARCH64_PC = 32, + // reserved block - UNW_ARM64_D0 = 64, - UNW_ARM64_D1 = 65, - UNW_ARM64_D2 = 66, - UNW_ARM64_D3 = 67, - UNW_ARM64_D4 = 68, - UNW_ARM64_D5 = 69, - UNW_ARM64_D6 = 70, - UNW_ARM64_D7 = 71, - UNW_ARM64_D8 = 72, - UNW_ARM64_D9 = 73, - UNW_ARM64_D10 = 74, - UNW_ARM64_D11 = 75, - UNW_ARM64_D12 = 76, - UNW_ARM64_D13 = 77, - UNW_ARM64_D14 = 78, - UNW_ARM64_D15 = 79, - UNW_ARM64_D16 = 80, - UNW_ARM64_D17 = 81, - UNW_ARM64_D18 = 82, - UNW_ARM64_D19 = 83, - UNW_ARM64_D20 = 84, - UNW_ARM64_D21 = 85, - UNW_ARM64_D22 = 86, - UNW_ARM64_D23 = 87, - UNW_ARM64_D24 = 88, - UNW_ARM64_D25 = 89, - UNW_ARM64_D26 = 90, - UNW_ARM64_D27 = 91, - UNW_ARM64_D28 = 92, - UNW_ARM64_D29 = 93, - UNW_ARM64_D30 = 94, - UNW_ARM64_D31 = 95, + UNW_AARCH64_RA_SIGN_STATE = 34, + + // FP/vector registers + UNW_AARCH64_V0 = 64, + UNW_AARCH64_V1 = 65, + UNW_AARCH64_V2 = 66, + UNW_AARCH64_V3 = 67, + UNW_AARCH64_V4 = 68, + UNW_AARCH64_V5 = 69, + UNW_AARCH64_V6 = 70, + UNW_AARCH64_V7 = 71, + UNW_AARCH64_V8 = 72, + UNW_AARCH64_V9 = 73, + UNW_AARCH64_V10 = 74, + UNW_AARCH64_V11 = 75, + UNW_AARCH64_V12 = 76, + UNW_AARCH64_V13 = 77, + UNW_AARCH64_V14 = 78, + UNW_AARCH64_V15 = 79, + UNW_AARCH64_V16 = 80, + UNW_AARCH64_V17 = 81, + UNW_AARCH64_V18 = 82, + UNW_AARCH64_V19 = 83, + UNW_AARCH64_V20 = 84, + UNW_AARCH64_V21 = 85, + UNW_AARCH64_V22 = 86, + UNW_AARCH64_V23 = 87, + UNW_AARCH64_V24 = 88, + UNW_AARCH64_V25 = 89, + UNW_AARCH64_V26 = 90, + UNW_AARCH64_V27 = 91, + UNW_AARCH64_V28 = 92, + UNW_AARCH64_V29 = 93, + UNW_AARCH64_V30 = 94, + UNW_AARCH64_V31 = 95, + + // Compatibility aliases + UNW_ARM64_X0 = UNW_AARCH64_X0, + UNW_ARM64_X1 = UNW_AARCH64_X1, + UNW_ARM64_X2 = UNW_AARCH64_X2, + UNW_ARM64_X3 = UNW_AARCH64_X3, + UNW_ARM64_X4 = UNW_AARCH64_X4, + UNW_ARM64_X5 = UNW_AARCH64_X5, + UNW_ARM64_X6 = UNW_AARCH64_X6, + UNW_ARM64_X7 = UNW_AARCH64_X7, + UNW_ARM64_X8 = UNW_AARCH64_X8, + UNW_ARM64_X9 = UNW_AARCH64_X9, + UNW_ARM64_X10 = UNW_AARCH64_X10, + UNW_ARM64_X11 = UNW_AARCH64_X11, + UNW_ARM64_X12 = UNW_AARCH64_X12, + UNW_ARM64_X13 = UNW_AARCH64_X13, + UNW_ARM64_X14 = UNW_AARCH64_X14, + UNW_ARM64_X15 = UNW_AARCH64_X15, + UNW_ARM64_X16 = UNW_AARCH64_X16, + UNW_ARM64_X17 = UNW_AARCH64_X17, + UNW_ARM64_X18 = UNW_AARCH64_X18, + UNW_ARM64_X19 = UNW_AARCH64_X19, + UNW_ARM64_X20 = UNW_AARCH64_X20, + UNW_ARM64_X21 = UNW_AARCH64_X21, + UNW_ARM64_X22 = UNW_AARCH64_X22, + UNW_ARM64_X23 = UNW_AARCH64_X23, + UNW_ARM64_X24 = UNW_AARCH64_X24, + UNW_ARM64_X25 = UNW_AARCH64_X25, + UNW_ARM64_X26 = UNW_AARCH64_X26, + UNW_ARM64_X27 = UNW_AARCH64_X27, + UNW_ARM64_X28 = UNW_AARCH64_X28, + UNW_ARM64_X29 = UNW_AARCH64_X29, + UNW_ARM64_FP = UNW_AARCH64_FP, + UNW_ARM64_X30 = UNW_AARCH64_X30, + UNW_ARM64_LR = UNW_AARCH64_LR, + UNW_ARM64_X31 = UNW_AARCH64_X31, + UNW_ARM64_SP = UNW_AARCH64_SP, + UNW_ARM64_PC = UNW_AARCH64_PC, + UNW_ARM64_RA_SIGN_STATE = UNW_AARCH64_RA_SIGN_STATE, + UNW_ARM64_D0 = UNW_AARCH64_V0, + UNW_ARM64_D1 = UNW_AARCH64_V1, + UNW_ARM64_D2 = UNW_AARCH64_V2, + UNW_ARM64_D3 = UNW_AARCH64_V3, + UNW_ARM64_D4 = UNW_AARCH64_V4, + UNW_ARM64_D5 = UNW_AARCH64_V5, + UNW_ARM64_D6 = UNW_AARCH64_V6, + UNW_ARM64_D7 = UNW_AARCH64_V7, + UNW_ARM64_D8 = UNW_AARCH64_V8, + UNW_ARM64_D9 = UNW_AARCH64_V9, + UNW_ARM64_D10 = UNW_AARCH64_V10, + UNW_ARM64_D11 = UNW_AARCH64_V11, + UNW_ARM64_D12 = UNW_AARCH64_V12, + UNW_ARM64_D13 = UNW_AARCH64_V13, + UNW_ARM64_D14 = UNW_AARCH64_V14, + UNW_ARM64_D15 = UNW_AARCH64_V15, + UNW_ARM64_D16 = UNW_AARCH64_V16, + UNW_ARM64_D17 = UNW_AARCH64_V17, + UNW_ARM64_D18 = UNW_AARCH64_V18, + UNW_ARM64_D19 = UNW_AARCH64_V19, + UNW_ARM64_D20 = UNW_AARCH64_V20, + UNW_ARM64_D21 = UNW_AARCH64_V21, + UNW_ARM64_D22 = UNW_AARCH64_V22, + UNW_ARM64_D23 = UNW_AARCH64_V23, + UNW_ARM64_D24 = UNW_AARCH64_V24, + UNW_ARM64_D25 = UNW_AARCH64_V25, + UNW_ARM64_D26 = UNW_AARCH64_V26, + UNW_ARM64_D27 = UNW_AARCH64_V27, + UNW_ARM64_D28 = UNW_AARCH64_V28, + UNW_ARM64_D29 = UNW_AARCH64_V29, + UNW_ARM64_D30 = UNW_AARCH64_V30, + UNW_ARM64_D31 = UNW_AARCH64_V31, }; // 32-bit ARM registers. Numbers match DWARF for ARM spec #3.1 Table 1. @@ -647,7 +724,8 @@ enum { UNW_ARM_WR14 = 126, UNW_ARM_WR15 = 127, // 128-133 -- SPSR, SPSR_{FIQ|IRQ|ABT|UND|SVC} - // 134-143 -- Reserved + // 134-142 -- Reserved + UNW_ARM_RA_AUTH_CODE = 143, // 144-150 -- R8_USR-R14_USR // 151-157 -- R8_FIQ-R14_FIQ // 158-159 -- R13_IRQ-R14_IRQ @@ -1102,5 +1180,47 @@ enum { UNW_VE_VL = 145, }; +// s390x register numbers +enum { + UNW_S390X_R0 = 0, + UNW_S390X_R1 = 1, + UNW_S390X_R2 = 2, + UNW_S390X_R3 = 3, + UNW_S390X_R4 = 4, + UNW_S390X_R5 = 5, + UNW_S390X_R6 = 6, + UNW_S390X_R7 = 7, + UNW_S390X_R8 = 8, + UNW_S390X_R9 = 9, + UNW_S390X_R10 = 10, + UNW_S390X_R11 = 11, + UNW_S390X_R12 = 12, + UNW_S390X_R13 = 13, + UNW_S390X_R14 = 14, + UNW_S390X_R15 = 15, + UNW_S390X_F0 = 16, + UNW_S390X_F2 = 17, + UNW_S390X_F4 = 18, + UNW_S390X_F6 = 19, + UNW_S390X_F1 = 20, + UNW_S390X_F3 = 21, + UNW_S390X_F5 = 22, + UNW_S390X_F7 = 23, + UNW_S390X_F8 = 24, + UNW_S390X_F10 = 25, + UNW_S390X_F12 = 26, + UNW_S390X_F14 = 27, + UNW_S390X_F9 = 28, + UNW_S390X_F11 = 29, + UNW_S390X_F13 = 30, + UNW_S390X_F15 = 31, + // 32-47 Control Registers + // 48-63 Access Registers + UNW_S390X_PSWM = 64, + UNW_S390X_PSWA = 65, + // 66-67 Reserved + // 68-83 Vector Registers %v16-%v31 +}; + #endif #endif diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/libunwind_ext.h b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/libunwind_ext.h index 3807f99b3d..19c38b7570 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/libunwind_ext.h +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/libunwind_ext.h @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===------------------------ libunwind_ext.h -----------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -46,6 +46,10 @@ extern int __unw_is_fpreg(unw_cursor_t *, unw_regnum_t); extern int __unw_is_signal_frame(unw_cursor_t *); extern int __unw_get_proc_name(unw_cursor_t *, char *, size_t, unw_word_t *); +#if defined(_AIX) +extern uintptr_t __unw_get_data_rel_base(unw_cursor_t *); +#endif + // SPI extern void __unw_iterate_dwarf_unwind_cache(void (*func)( unw_word_t ip_start, unw_word_t ip_end, unw_word_t fde, unw_word_t mh)); @@ -54,6 +58,9 @@ extern void __unw_iterate_dwarf_unwind_cache(void (*func)( extern void __unw_add_dynamic_fde(unw_word_t fde); extern void __unw_remove_dynamic_fde(unw_word_t fde); +extern void __unw_add_dynamic_eh_frame_section(unw_word_t eh_frame_start); +extern void __unw_remove_dynamic_eh_frame_section(unw_word_t eh_frame_start); + #if defined(_LIBUNWIND_ARM_EHABI) extern const uint32_t* decode_eht_entry(const uint32_t*, size_t*, size_t*); extern _Unwind_Reason_Code _Unwind_VRS_Interpret(_Unwind_Context *context, diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/mach-o/compact_unwind_encoding.h b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/mach-o/compact_unwind_encoding.h index ae35364eb9..f49f859e00 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/mach-o/compact_unwind_encoding.h +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/mach-o/compact_unwind_encoding.h @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===------------------ mach-o/compact_unwind_encoding.h ------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/rev.txt b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/rev.txt index d6b5d35133..563d51c5ed 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/rev.txt +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/rev.txt @@ -1 +1 @@ -llvmorg-12.0.1 +llvmorg-15.0.7 diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/unwind.h b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/unwind.h index e1425adf75..454f2cef30 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/unwind.h +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/unwind.h @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===------------------------------- unwind.h -----------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -59,211 +59,23 @@ typedef enum { typedef struct _Unwind_Context _Unwind_Context; // opaque #if defined(_LIBUNWIND_ARM_EHABI) -typedef uint32_t _Unwind_State; - -static const _Unwind_State _US_VIRTUAL_UNWIND_FRAME = 0; -static const _Unwind_State _US_UNWIND_FRAME_STARTING = 1; -static const _Unwind_State _US_UNWIND_FRAME_RESUME = 2; -static const _Unwind_State _US_ACTION_MASK = 3; -/* Undocumented flag for force unwinding. */ -static const _Unwind_State _US_FORCE_UNWIND = 8; - -typedef uint32_t _Unwind_EHT_Header; - -struct _Unwind_Control_Block; -typedef struct _Unwind_Control_Block _Unwind_Control_Block; -typedef struct _Unwind_Control_Block _Unwind_Exception; /* Alias */ - -struct _Unwind_Control_Block { - uint64_t exception_class; - void (*exception_cleanup)(_Unwind_Reason_Code, _Unwind_Control_Block*); - - /* Unwinder cache, private fields for the unwinder's use */ - struct { - uint32_t reserved1; /* init reserved1 to 0, then don't touch */ - uint32_t reserved2; - uint32_t reserved3; - uint32_t reserved4; - uint32_t reserved5; - } unwinder_cache; - - /* Propagation barrier cache (valid after phase 1): */ - struct { - uint32_t sp; - uint32_t bitpattern[5]; - } barrier_cache; - - /* Cleanup cache (preserved over cleanup): */ - struct { - uint32_t bitpattern[4]; - } cleanup_cache; - - /* Pr cache (for pr's benefit): */ - struct { - uint32_t fnstart; /* function start address */ - _Unwind_EHT_Header* ehtp; /* pointer to EHT entry header word */ - uint32_t additional; - uint32_t reserved1; - } pr_cache; - - long long int :0; /* Enforce the 8-byte alignment */ -} __attribute__((__aligned__(8))); - -typedef _Unwind_Reason_Code (*_Unwind_Stop_Fn) - (_Unwind_State state, - _Unwind_Exception* exceptionObject, - struct _Unwind_Context* context); - -typedef _Unwind_Reason_Code (*_Unwind_Personality_Fn)( - _Unwind_State state, _Unwind_Exception *exceptionObject, - struct _Unwind_Context *context); +#include "unwind_arm_ehabi.h" #else -struct _Unwind_Context; // opaque -struct _Unwind_Exception; // forward declaration -typedef struct _Unwind_Exception _Unwind_Exception; - -struct _Unwind_Exception { - uint64_t exception_class; - void (*exception_cleanup)(_Unwind_Reason_Code reason, - _Unwind_Exception *exc); -#if defined(__SEH__) && !defined(__USING_SJLJ_EXCEPTIONS__) - uintptr_t private_[6]; -#else - uintptr_t private_1; // non-zero means forced unwind - uintptr_t private_2; // holds sp that phase1 found for phase2 to use +#include "unwind_itanium.h" #endif -#if __SIZEOF_POINTER__ == 4 - // The implementation of _Unwind_Exception uses an attribute mode on the - // above fields which has the side effect of causing this whole struct to - // round up to 32 bytes in size (48 with SEH). To be more explicit, we add - // pad fields added for binary compatibility. - uint32_t reserved[3]; -#endif - // The Itanium ABI requires that _Unwind_Exception objects are "double-word - // aligned". GCC has interpreted this to mean "use the maximum useful - // alignment for the target"; so do we. -} __attribute__((__aligned__)); typedef _Unwind_Reason_Code (*_Unwind_Stop_Fn) (int version, _Unwind_Action actions, - uint64_t exceptionClass, + _Unwind_Exception_Class exceptionClass, _Unwind_Exception* exceptionObject, struct _Unwind_Context* context, - void* stop_parameter ); - -typedef _Unwind_Reason_Code (*_Unwind_Personality_Fn)( - int version, _Unwind_Action actions, uint64_t exceptionClass, - _Unwind_Exception *exceptionObject, struct _Unwind_Context *context); -#endif + void* stop_parameter); #ifdef __cplusplus extern "C" { #endif -// -// The following are the base functions documented by the C++ ABI -// -#ifdef __USING_SJLJ_EXCEPTIONS__ -extern _Unwind_Reason_Code - _Unwind_SjLj_RaiseException(_Unwind_Exception *exception_object); -extern void _Unwind_SjLj_Resume(_Unwind_Exception *exception_object); -#else -extern _Unwind_Reason_Code - _Unwind_RaiseException(_Unwind_Exception *exception_object); -extern void _Unwind_Resume(_Unwind_Exception *exception_object); -#endif -extern void _Unwind_DeleteException(_Unwind_Exception *exception_object); - -#if defined(_LIBUNWIND_ARM_EHABI) -typedef enum { - _UVRSC_CORE = 0, /* integer register */ - _UVRSC_VFP = 1, /* vfp */ - _UVRSC_WMMXD = 3, /* Intel WMMX data register */ - _UVRSC_WMMXC = 4 /* Intel WMMX control register */ -} _Unwind_VRS_RegClass; - -typedef enum { - _UVRSD_UINT32 = 0, - _UVRSD_VFPX = 1, - _UVRSD_UINT64 = 3, - _UVRSD_FLOAT = 4, - _UVRSD_DOUBLE = 5 -} _Unwind_VRS_DataRepresentation; - -typedef enum { - _UVRSR_OK = 0, - _UVRSR_NOT_IMPLEMENTED = 1, - _UVRSR_FAILED = 2 -} _Unwind_VRS_Result; - -extern void _Unwind_Complete(_Unwind_Exception* exception_object); - -extern _Unwind_VRS_Result -_Unwind_VRS_Get(_Unwind_Context *context, _Unwind_VRS_RegClass regclass, - uint32_t regno, _Unwind_VRS_DataRepresentation representation, - void *valuep); - -extern _Unwind_VRS_Result -_Unwind_VRS_Set(_Unwind_Context *context, _Unwind_VRS_RegClass regclass, - uint32_t regno, _Unwind_VRS_DataRepresentation representation, - void *valuep); - -extern _Unwind_VRS_Result -_Unwind_VRS_Pop(_Unwind_Context *context, _Unwind_VRS_RegClass regclass, - uint32_t discriminator, - _Unwind_VRS_DataRepresentation representation); -#endif - -#if !defined(_LIBUNWIND_ARM_EHABI) - -extern uintptr_t _Unwind_GetGR(struct _Unwind_Context *context, int index); -extern void _Unwind_SetGR(struct _Unwind_Context *context, int index, - uintptr_t new_value); -extern uintptr_t _Unwind_GetIP(struct _Unwind_Context *context); -extern void _Unwind_SetIP(struct _Unwind_Context *, uintptr_t new_value); - -#else // defined(_LIBUNWIND_ARM_EHABI) - -#if defined(_LIBUNWIND_UNWIND_LEVEL1_EXTERNAL_LINKAGE) -#define _LIBUNWIND_EXPORT_UNWIND_LEVEL1 extern -#else -#define _LIBUNWIND_EXPORT_UNWIND_LEVEL1 static __inline__ -#endif - -// These are de facto helper functions for ARM, which delegate the function -// calls to _Unwind_VRS_Get/Set(). These are not a part of ARM EHABI -// specification, thus these function MUST be inlined. Please don't replace -// these with the "extern" function declaration; otherwise, the program -// including this "unwind.h" header won't be ABI compatible and will result in -// link error when we are linking the program with libgcc. - -_LIBUNWIND_EXPORT_UNWIND_LEVEL1 -uintptr_t _Unwind_GetGR(struct _Unwind_Context *context, int index) { - uintptr_t value = 0; - _Unwind_VRS_Get(context, _UVRSC_CORE, (uint32_t)index, _UVRSD_UINT32, &value); - return value; -} - -_LIBUNWIND_EXPORT_UNWIND_LEVEL1 -void _Unwind_SetGR(struct _Unwind_Context *context, int index, - uintptr_t value) { - _Unwind_VRS_Set(context, _UVRSC_CORE, (uint32_t)index, _UVRSD_UINT32, &value); -} - -_LIBUNWIND_EXPORT_UNWIND_LEVEL1 -uintptr_t _Unwind_GetIP(struct _Unwind_Context *context) { - // remove the thumb-bit before returning - return _Unwind_GetGR(context, 15) & (~(uintptr_t)0x1); -} - -_LIBUNWIND_EXPORT_UNWIND_LEVEL1 -void _Unwind_SetIP(struct _Unwind_Context *context, uintptr_t value) { - uintptr_t thumb_bit = _Unwind_GetGR(context, 15) & ((uintptr_t)0x1); - _Unwind_SetGR(context, 15, value | thumb_bit); -} -#endif // defined(_LIBUNWIND_ARM_EHABI) - extern uintptr_t _Unwind_GetRegionStart(struct _Unwind_Context *context); extern uintptr_t _Unwind_GetLanguageSpecificData(struct _Unwind_Context *context); @@ -351,7 +163,7 @@ extern const void *_Unwind_Find_FDE(const void *pc, struct dwarf_eh_bases *); extern void *_Unwind_FindEnclosingFunction(void *pc); // Mac OS X does not support text-rel and data-rel addressing so these functions -// are unimplemented +// are unimplemented. extern uintptr_t _Unwind_GetDataRelBase(struct _Unwind_Context *context) LIBUNWIND_UNAVAIL; extern uintptr_t _Unwind_GetTextRelBase(struct _Unwind_Context *context) diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/unwind_arm_ehabi.h b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/unwind_arm_ehabi.h new file mode 100644 index 0000000000..af3289c18d --- /dev/null +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/unwind_arm_ehabi.h @@ -0,0 +1,174 @@ +// clang-format off +#if defined(__unix__) || defined(__unix) || defined(unix) || \ + (defined(__APPLE__) && defined(__MACH__)) +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// +// C++ ABI Level 1 ABI documented at: +// https://github.com/ARM-software/abi-aa/blob/main/ehabi32/ehabi32.rst +// +//===----------------------------------------------------------------------===// + +#ifndef __ARM_EHABI_UNWIND_H__ +#define __ARM_EHABI_UNWIND_H__ + +typedef uint32_t _Unwind_State; + +static const _Unwind_State _US_VIRTUAL_UNWIND_FRAME = 0; +static const _Unwind_State _US_UNWIND_FRAME_STARTING = 1; +static const _Unwind_State _US_UNWIND_FRAME_RESUME = 2; +static const _Unwind_State _US_ACTION_MASK = 3; +/* Undocumented flag for force unwinding. */ +static const _Unwind_State _US_FORCE_UNWIND = 8; + +typedef uint32_t _Unwind_EHT_Header; + +struct _Unwind_Control_Block; +typedef struct _Unwind_Control_Block _Unwind_Control_Block; +#define _Unwind_Exception _Unwind_Control_Block /* Alias */ +typedef uint8_t _Unwind_Exception_Class[8]; + +struct _Unwind_Control_Block { + _Unwind_Exception_Class exception_class; + void (*exception_cleanup)(_Unwind_Reason_Code, _Unwind_Control_Block*); + + /* Unwinder cache, private fields for the unwinder's use */ + struct { + uint32_t reserved1; /* init reserved1 to 0, then don't touch */ + uint32_t reserved2; + uint32_t reserved3; + uint32_t reserved4; + uint32_t reserved5; + } unwinder_cache; + + /* Propagation barrier cache (valid after phase 1): */ + struct { + uint32_t sp; + uint32_t bitpattern[5]; + } barrier_cache; + + /* Cleanup cache (preserved over cleanup): */ + struct { + uint32_t bitpattern[4]; + } cleanup_cache; + + /* Pr cache (for pr's benefit): */ + struct { + uint32_t fnstart; /* function start address */ + _Unwind_EHT_Header* ehtp; /* pointer to EHT entry header word */ + uint32_t additional; + uint32_t reserved1; + } pr_cache; + + long long int :0; /* Enforce the 8-byte alignment */ +} __attribute__((__aligned__(8))); + +typedef _Unwind_Reason_Code (*_Unwind_Personality_Fn)( + _Unwind_State state, _Unwind_Exception *exceptionObject, + struct _Unwind_Context *context); + +#ifdef __cplusplus +extern "C" { +#endif + +// +// The following are the base functions documented by the C++ ABI +// +#ifdef __USING_SJLJ_EXCEPTIONS__ +extern _Unwind_Reason_Code + _Unwind_SjLj_RaiseException(_Unwind_Exception *exception_object); +extern void _Unwind_SjLj_Resume(_Unwind_Exception *exception_object); +#else +extern _Unwind_Reason_Code + _Unwind_RaiseException(_Unwind_Exception *exception_object); +extern void _Unwind_Resume(_Unwind_Exception *exception_object); +#endif +extern void _Unwind_DeleteException(_Unwind_Exception *exception_object); + +typedef enum { + _UVRSC_CORE = 0, /* integer register */ + _UVRSC_VFP = 1, /* vfp */ + _UVRSC_WMMXD = 3, /* Intel WMMX data register */ + _UVRSC_WMMXC = 4, /* Intel WMMX control register */ + _UVRSC_PSEUDO = 5 /* Special purpose pseudo register */ +} _Unwind_VRS_RegClass; + +typedef enum { + _UVRSD_UINT32 = 0, + _UVRSD_VFPX = 1, + _UVRSD_UINT64 = 3, + _UVRSD_FLOAT = 4, + _UVRSD_DOUBLE = 5 +} _Unwind_VRS_DataRepresentation; + +typedef enum { + _UVRSR_OK = 0, + _UVRSR_NOT_IMPLEMENTED = 1, + _UVRSR_FAILED = 2 +} _Unwind_VRS_Result; + +extern void _Unwind_Complete(_Unwind_Exception* exception_object); + +extern _Unwind_VRS_Result +_Unwind_VRS_Get(_Unwind_Context *context, _Unwind_VRS_RegClass regclass, + uint32_t regno, _Unwind_VRS_DataRepresentation representation, + void *valuep); + +extern _Unwind_VRS_Result +_Unwind_VRS_Set(_Unwind_Context *context, _Unwind_VRS_RegClass regclass, + uint32_t regno, _Unwind_VRS_DataRepresentation representation, + void *valuep); + +extern _Unwind_VRS_Result +_Unwind_VRS_Pop(_Unwind_Context *context, _Unwind_VRS_RegClass regclass, + uint32_t discriminator, + _Unwind_VRS_DataRepresentation representation); + +#if defined(_LIBUNWIND_UNWIND_LEVEL1_EXTERNAL_LINKAGE) +#define _LIBUNWIND_EXPORT_UNWIND_LEVEL1 extern +#else +#define _LIBUNWIND_EXPORT_UNWIND_LEVEL1 static __inline__ +#endif + +// These are de facto helper functions for ARM, which delegate the function +// calls to _Unwind_VRS_Get/Set(). These are not a part of ARM EHABI +// specification, thus these function MUST be inlined. Please don't replace +// these with the "extern" function declaration; otherwise, the program +// including this "unwind.h" header won't be ABI compatible and will result in +// link error when we are linking the program with libgcc. + +_LIBUNWIND_EXPORT_UNWIND_LEVEL1 +uintptr_t _Unwind_GetGR(struct _Unwind_Context *context, int index) { + uintptr_t value = 0; + _Unwind_VRS_Get(context, _UVRSC_CORE, (uint32_t)index, _UVRSD_UINT32, &value); + return value; +} + +_LIBUNWIND_EXPORT_UNWIND_LEVEL1 +void _Unwind_SetGR(struct _Unwind_Context *context, int index, + uintptr_t value) { + _Unwind_VRS_Set(context, _UVRSC_CORE, (uint32_t)index, _UVRSD_UINT32, &value); +} + +_LIBUNWIND_EXPORT_UNWIND_LEVEL1 +uintptr_t _Unwind_GetIP(struct _Unwind_Context *context) { + // remove the thumb-bit before returning + return _Unwind_GetGR(context, 15) & (~(uintptr_t)0x1); +} + +_LIBUNWIND_EXPORT_UNWIND_LEVEL1 +void _Unwind_SetIP(struct _Unwind_Context *context, uintptr_t value) { + uintptr_t thumb_bit = _Unwind_GetGR(context, 15) & ((uintptr_t)0x1); + _Unwind_SetGR(context, 15, value | thumb_bit); +} + +#ifdef __cplusplus +} +#endif + +#endif // __ARM_EHABI_UNWIND_H__ +#endif diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/unwind_itanium.h b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/unwind_itanium.h new file mode 100644 index 0000000000..e0ff9a2bd9 --- /dev/null +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/unwind_itanium.h @@ -0,0 +1,80 @@ +// clang-format off +#if defined(__unix__) || defined(__unix) || defined(unix) || \ + (defined(__APPLE__) && defined(__MACH__)) +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// +// C++ ABI Level 1 ABI documented at: +// https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html +// +//===----------------------------------------------------------------------===// + +#ifndef __ITANIUM_UNWIND_H__ +#define __ITANIUM_UNWIND_H__ + +struct _Unwind_Context; // opaque +struct _Unwind_Exception; // forward declaration +typedef struct _Unwind_Exception _Unwind_Exception; +typedef uint64_t _Unwind_Exception_Class; + +struct _Unwind_Exception { + _Unwind_Exception_Class exception_class; + void (*exception_cleanup)(_Unwind_Reason_Code reason, + _Unwind_Exception *exc); +#if defined(__SEH__) && !defined(__USING_SJLJ_EXCEPTIONS__) + uintptr_t private_[6]; +#else + uintptr_t private_1; // non-zero means forced unwind + uintptr_t private_2; // holds sp that phase1 found for phase2 to use +#endif +#if __SIZEOF_POINTER__ == 4 + // The implementation of _Unwind_Exception uses an attribute mode on the + // above fields which has the side effect of causing this whole struct to + // round up to 32 bytes in size (48 with SEH). To be more explicit, we add + // pad fields added for binary compatibility. + uint32_t reserved[3]; +#endif + // The Itanium ABI requires that _Unwind_Exception objects are "double-word + // aligned". GCC has interpreted this to mean "use the maximum useful + // alignment for the target"; so do we. +} __attribute__((__aligned__)); + +typedef _Unwind_Reason_Code (*_Unwind_Personality_Fn)( + int version, _Unwind_Action actions, uint64_t exceptionClass, + _Unwind_Exception *exceptionObject, struct _Unwind_Context *context); + +#ifdef __cplusplus +extern "C" { +#endif + +// +// The following are the base functions documented by the C++ ABI +// +#ifdef __USING_SJLJ_EXCEPTIONS__ +extern _Unwind_Reason_Code + _Unwind_SjLj_RaiseException(_Unwind_Exception *exception_object); +extern void _Unwind_SjLj_Resume(_Unwind_Exception *exception_object); +#else +extern _Unwind_Reason_Code + _Unwind_RaiseException(_Unwind_Exception *exception_object); +extern void _Unwind_Resume(_Unwind_Exception *exception_object); +#endif +extern void _Unwind_DeleteException(_Unwind_Exception *exception_object); + + +extern uintptr_t _Unwind_GetGR(struct _Unwind_Context *context, int index); +extern void _Unwind_SetGR(struct _Unwind_Context *context, int index, + uintptr_t new_value); +extern uintptr_t _Unwind_GetIP(struct _Unwind_Context *context); +extern void _Unwind_SetIP(struct _Unwind_Context *, uintptr_t new_value); + +#ifdef __cplusplus +} +#endif + +#endif // __ITANIUM_UNWIND_H__ +#endif diff --git a/nativelib/src/main/resources/scala-native/platform/posix/unwind.c b/nativelib/src/main/resources/scala-native/platform/posix/unwind.c index 256462191e..10fc90ea48 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/unwind.c +++ b/nativelib/src/main/resources/scala-native/platform/posix/unwind.c @@ -22,11 +22,13 @@ int scalanative_unwind_get_proc_name(void *cursor, char *buffer, size_t length, (unw_word_t *)offset); } -int scalanative_unwind_get_reg(void *cursor, int regnum, - unsigned long long *valp) { +int scalanative_unwind_get_reg(void *cursor, int regnum, uintptr_t *valp) { return unw_get_reg((unw_cursor_t *)cursor, regnum, (unw_word_t *)valp); } int scalanative_unw_reg_ip() { return UNW_REG_IP; } +size_t scalanative_unwind_sizeof_context() { return sizeof(unw_context_t); } +size_t scalanative_unwind_sizeof_cursor() { return sizeof(unw_cursor_t); } + #endif // Unix or Mac OS diff --git a/nativelib/src/main/resources/scala-native/platform/unwind.h b/nativelib/src/main/resources/scala-native/platform/unwind.h index 0a5b02fb81..13f40cddd3 100644 --- a/nativelib/src/main/resources/scala-native/platform/unwind.h +++ b/nativelib/src/main/resources/scala-native/platform/unwind.h @@ -2,14 +2,16 @@ #define UNWIND_H #include +#include int scalanative_unwind_get_context(void *context); int scalanative_unwind_init_local(void *cursor, void *context); int scalanative_unwind_step(void *cursor); int scalanative_unwind_get_proc_name(void *cursor, char *buffer, size_t length, void *offset); -int scalanative_unwind_get_reg(void *cursor, int regnum, - unsigned long long *valp); +int scalanative_unwind_get_reg(void *cursor, int regnum, uintptr_t *valp); int scalanative_unw_reg_ip(); +size_t scalanative_unwind_sizeof_context(); +size_t scalanative_unwind_sizeof_cursor(); #endif diff --git a/nativelib/src/main/resources/scala-native/platform/windows/unwind.c b/nativelib/src/main/resources/scala-native/platform/windows/unwind.c index aac99266c8..1cc8fe7ed2 100644 --- a/nativelib/src/main/resources/scala-native/platform/windows/unwind.c +++ b/nativelib/src/main/resources/scala-native/platform/windows/unwind.c @@ -8,22 +8,27 @@ #include "../unwind.h" #define MAX_LENGTH_OF_CALLSTACK 255 +#define MAX_LENGHT_OF_NAME 255 typedef struct _UnwindContext { - void **stack; + void *stack[MAX_LENGTH_OF_CALLSTACK]; unsigned short frames; HANDLE process; DWORD64 cursor; - SYMBOL_INFOW symbol; + struct { + SYMBOL_INFOW info; + wchar_t nameBuffer[MAX_LENGHT_OF_NAME + 1]; + } symbol; } UnwindContext; int scalanative_unwind_get_context(void *context) { return 0; } int scalanative_unwind_init_local(void *cursor, void *context) { static int symInitialized = 0; - UnwindContext *ucontext = (UnwindContext *)cursor; + UnwindContext *ucontext = (UnwindContext *)context; + UnwindContext **ucontextRef = (UnwindContext **)cursor; + *ucontextRef = ucontext; memset(ucontext, 0, sizeof(UnwindContext)); - ucontext->stack = (void **)context; ucontext->process = GetCurrentProcess(); if (!symInitialized) { if (SymInitialize(ucontext->process, NULL, TRUE) == FALSE) { @@ -34,13 +39,13 @@ int scalanative_unwind_init_local(void *cursor, void *context) { ucontext->frames = CaptureStackBackTrace(0, MAX_LENGTH_OF_CALLSTACK, ucontext->stack, NULL); ucontext->cursor = 0; - ucontext->symbol.MaxNameLen = 255; - ucontext->symbol.SizeOfStruct = sizeof(SYMBOL_INFOW); + ucontext->symbol.info.MaxNameLen = MAX_LENGHT_OF_NAME; + ucontext->symbol.info.SizeOfStruct = sizeof(ucontext->symbol.info); return 0; } int scalanative_unwind_step(void *cursor) { - UnwindContext *ucontext = (UnwindContext *)cursor; + UnwindContext *ucontext = *(UnwindContext **)cursor; return ucontext->frames - (++ucontext->cursor); } @@ -49,10 +54,10 @@ int scalanative_unwind_get_proc_name(void *cursor, char *buffer, size_t length, DWORD displacement = 0; IMAGEHLP_LINE line; int fileNameLen = 0; - UnwindContext *ucontext = (UnwindContext *)cursor; + UnwindContext *ucontext = *(UnwindContext **)cursor; if (ucontext->cursor < ucontext->frames) { void *address = ucontext->stack[ucontext->cursor]; - PSYMBOL_INFOW symbol = &ucontext->symbol; + PSYMBOL_INFOW symbol = &ucontext->symbol.info; SymFromAddrW(ucontext->process, (DWORD64)address, 0, symbol); snprintf(buffer, length, "%ws", symbol->Name); memcpy(offset, &(symbol->Address), sizeof(void *)); @@ -68,10 +73,9 @@ int scalanative_unwind_get_proc_name(void *cursor, char *buffer, size_t length, return 0; } -int scalanative_unwind_get_reg(void *cursor, int regnum, - unsigned long long *valp) { - UnwindContext *ucontext = (UnwindContext *)cursor; - *valp = (unsigned long long)(ucontext->stack[ucontext->cursor]); +int scalanative_unwind_get_reg(void *cursor, int regnum, uintptr_t *valp) { + UnwindContext *ucontext = *(UnwindContext **)cursor; + *valp = (uintptr_t)(ucontext->stack[ucontext->cursor]); return 0; } @@ -79,4 +83,7 @@ int scalanative_unwind_get_reg(void *cursor, int regnum, // used int scalanative_unw_reg_ip() { return -1; } +size_t scalanative_unwind_sizeof_context() { return sizeof(UnwindContext); } +size_t scalanative_unwind_sizeof_cursor() { return sizeof(UnwindContext *); } + #endif // _WIN32 diff --git a/nativelib/src/main/scala/scala/scalanative/runtime/Arrays.scala b/nativelib/src/main/scala/scala/scalanative/runtime/Arrays.scala index e893befc89..49db30321f 100644 --- a/nativelib/src/main/scala/scala/scalanative/runtime/Arrays.scala +++ b/nativelib/src/main/scala/scala/scalanative/runtime/Arrays.scala @@ -38,7 +38,7 @@ sealed abstract class Array[T] /** Number of elements of the array. */ @inline def length: Int = { val rawptr = castObjectToRawPtr(this) - val lenptr = elemRawPtr(rawptr, 8) + val lenptr = elemRawPtr(rawptr, MemoryLayout.Array.LengthOffset) loadInt(lenptr) } @@ -163,30 +163,16 @@ final class BooleanArray private () extends Array[Boolean] { throwOutOfBounds(i) } else { val rawptr = castObjectToRawPtr(this) - elemRawPtr(rawptr, 16 + 1 * i) + elemRawPtr(rawptr, MemoryLayout.Array.ValuesOffset + 1 * i) } - @inline def apply(i: Int): Boolean = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 1 * i) - loadBoolean(ith) - } + @inline def apply(i: Int): Boolean = loadBoolean(atRaw(i)) - @inline def update(i: Int, value: Boolean): Unit = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 1 * i) - storeBoolean(ith, value) - } + @inline def update(i: Int, value: Boolean): Unit = storeBoolean(atRaw(i), value) @inline override def clone(): BooleanArray = { val arrcls = classOf[BooleanArray] - val arrsize = (16 + 1 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 1 * length) val arr = GC.alloc_atomic(arrcls, arrsize) val src = castObjectToRawPtr(this) libc.memcpy(arr, src, arrsize) @@ -197,11 +183,15 @@ final class BooleanArray private () extends Array[Boolean] { object BooleanArray { @inline def alloc(length: Int): BooleanArray = { + if (length < 0) { + throw new NegativeArraySizeException + } + val arrcls = classOf[BooleanArray] - val arrsize = (16 + 1 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 1 * length) val arr = GC.alloc_atomic(arrcls, arrsize) - storeInt(elemRawPtr(arr, 8), length) - storeInt(elemRawPtr(arr, 12), 1.toInt) + storeInt(elemRawPtr(arr, MemoryLayout.Array.LengthOffset), length) + storeInt(elemRawPtr(arr, MemoryLayout.Array.StrideOffset), 1) castRawPtrToObject(arr).asInstanceOf[BooleanArray] } @@ -209,7 +199,7 @@ object BooleanArray { val arr = alloc(length) val dst = arr.atRaw(0) val src = data - val size = (1 * length).toULong + val size = new ULong(1 * length) libc.memcpy(dst, src, size) arr } @@ -225,30 +215,16 @@ final class CharArray private () extends Array[Char] { throwOutOfBounds(i) } else { val rawptr = castObjectToRawPtr(this) - elemRawPtr(rawptr, 16 + 2 * i) + elemRawPtr(rawptr, MemoryLayout.Array.ValuesOffset + 2 * i) } - @inline def apply(i: Int): Char = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 2 * i) - loadChar(ith) - } + @inline def apply(i: Int): Char = loadChar(atRaw(i)) - @inline def update(i: Int, value: Char): Unit = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 2 * i) - storeChar(ith, value) - } + @inline def update(i: Int, value: Char): Unit = storeChar(atRaw(i), value) @inline override def clone(): CharArray = { val arrcls = classOf[CharArray] - val arrsize = (16 + 2 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 2 * length) val arr = GC.alloc_atomic(arrcls, arrsize) val src = castObjectToRawPtr(this) libc.memcpy(arr, src, arrsize) @@ -259,11 +235,15 @@ final class CharArray private () extends Array[Char] { object CharArray { @inline def alloc(length: Int): CharArray = { + if (length < 0) { + throw new NegativeArraySizeException + } + val arrcls = classOf[CharArray] - val arrsize = (16 + 2 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 2 * length) val arr = GC.alloc_atomic(arrcls, arrsize) - storeInt(elemRawPtr(arr, 8), length) - storeInt(elemRawPtr(arr, 12), 2.toInt) + storeInt(elemRawPtr(arr, MemoryLayout.Array.LengthOffset), length) + storeInt(elemRawPtr(arr, MemoryLayout.Array.StrideOffset), 2) castRawPtrToObject(arr).asInstanceOf[CharArray] } @@ -271,7 +251,7 @@ object CharArray { val arr = alloc(length) val dst = arr.atRaw(0) val src = data - val size = (2 * length).toULong + val size = new ULong(2 * length) libc.memcpy(dst, src, size) arr } @@ -287,30 +267,16 @@ final class ByteArray private () extends Array[Byte] { throwOutOfBounds(i) } else { val rawptr = castObjectToRawPtr(this) - elemRawPtr(rawptr, 16 + 1 * i) + elemRawPtr(rawptr, MemoryLayout.Array.ValuesOffset + 1 * i) } - @inline def apply(i: Int): Byte = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 1 * i) - loadByte(ith) - } + @inline def apply(i: Int): Byte = loadByte(atRaw(i)) - @inline def update(i: Int, value: Byte): Unit = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 1 * i) - storeByte(ith, value) - } + @inline def update(i: Int, value: Byte): Unit = storeByte(atRaw(i), value) @inline override def clone(): ByteArray = { val arrcls = classOf[ByteArray] - val arrsize = (16 + 1 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 1 * length) val arr = GC.alloc_atomic(arrcls, arrsize) val src = castObjectToRawPtr(this) libc.memcpy(arr, src, arrsize) @@ -321,11 +287,15 @@ final class ByteArray private () extends Array[Byte] { object ByteArray { @inline def alloc(length: Int): ByteArray = { + if (length < 0) { + throw new NegativeArraySizeException + } + val arrcls = classOf[ByteArray] - val arrsize = (16 + 1 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 1 * length) val arr = GC.alloc_atomic(arrcls, arrsize) - storeInt(elemRawPtr(arr, 8), length) - storeInt(elemRawPtr(arr, 12), 1.toInt) + storeInt(elemRawPtr(arr, MemoryLayout.Array.LengthOffset), length) + storeInt(elemRawPtr(arr, MemoryLayout.Array.StrideOffset), 1) castRawPtrToObject(arr).asInstanceOf[ByteArray] } @@ -333,7 +303,7 @@ object ByteArray { val arr = alloc(length) val dst = arr.atRaw(0) val src = data - val size = (1 * length).toULong + val size = new ULong(1 * length) libc.memcpy(dst, src, size) arr } @@ -349,30 +319,16 @@ final class ShortArray private () extends Array[Short] { throwOutOfBounds(i) } else { val rawptr = castObjectToRawPtr(this) - elemRawPtr(rawptr, 16 + 2 * i) + elemRawPtr(rawptr, MemoryLayout.Array.ValuesOffset + 2 * i) } - @inline def apply(i: Int): Short = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 2 * i) - loadShort(ith) - } + @inline def apply(i: Int): Short = loadShort(atRaw(i)) - @inline def update(i: Int, value: Short): Unit = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 2 * i) - storeShort(ith, value) - } + @inline def update(i: Int, value: Short): Unit = storeShort(atRaw(i), value) @inline override def clone(): ShortArray = { val arrcls = classOf[ShortArray] - val arrsize = (16 + 2 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 2 * length) val arr = GC.alloc_atomic(arrcls, arrsize) val src = castObjectToRawPtr(this) libc.memcpy(arr, src, arrsize) @@ -383,11 +339,15 @@ final class ShortArray private () extends Array[Short] { object ShortArray { @inline def alloc(length: Int): ShortArray = { + if (length < 0) { + throw new NegativeArraySizeException + } + val arrcls = classOf[ShortArray] - val arrsize = (16 + 2 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 2 * length) val arr = GC.alloc_atomic(arrcls, arrsize) - storeInt(elemRawPtr(arr, 8), length) - storeInt(elemRawPtr(arr, 12), 2.toInt) + storeInt(elemRawPtr(arr, MemoryLayout.Array.LengthOffset), length) + storeInt(elemRawPtr(arr, MemoryLayout.Array.StrideOffset), 2) castRawPtrToObject(arr).asInstanceOf[ShortArray] } @@ -395,7 +355,7 @@ object ShortArray { val arr = alloc(length) val dst = arr.atRaw(0) val src = data - val size = (2 * length).toULong + val size = new ULong(2 * length) libc.memcpy(dst, src, size) arr } @@ -411,30 +371,16 @@ final class IntArray private () extends Array[Int] { throwOutOfBounds(i) } else { val rawptr = castObjectToRawPtr(this) - elemRawPtr(rawptr, 16 + 4 * i) + elemRawPtr(rawptr, MemoryLayout.Array.ValuesOffset + 4 * i) } - @inline def apply(i: Int): Int = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 4 * i) - loadInt(ith) - } + @inline def apply(i: Int): Int = loadInt(atRaw(i)) - @inline def update(i: Int, value: Int): Unit = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 4 * i) - storeInt(ith, value) - } + @inline def update(i: Int, value: Int): Unit = storeInt(atRaw(i), value) @inline override def clone(): IntArray = { val arrcls = classOf[IntArray] - val arrsize = (16 + 4 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 4 * length) val arr = GC.alloc_atomic(arrcls, arrsize) val src = castObjectToRawPtr(this) libc.memcpy(arr, src, arrsize) @@ -445,11 +391,15 @@ final class IntArray private () extends Array[Int] { object IntArray { @inline def alloc(length: Int): IntArray = { + if (length < 0) { + throw new NegativeArraySizeException + } + val arrcls = classOf[IntArray] - val arrsize = (16 + 4 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 4 * length) val arr = GC.alloc_atomic(arrcls, arrsize) - storeInt(elemRawPtr(arr, 8), length) - storeInt(elemRawPtr(arr, 12), 4.toInt) + storeInt(elemRawPtr(arr, MemoryLayout.Array.LengthOffset), length) + storeInt(elemRawPtr(arr, MemoryLayout.Array.StrideOffset), 4) castRawPtrToObject(arr).asInstanceOf[IntArray] } @@ -457,7 +407,7 @@ object IntArray { val arr = alloc(length) val dst = arr.atRaw(0) val src = data - val size = (4 * length).toULong + val size = new ULong(4 * length) libc.memcpy(dst, src, size) arr } @@ -473,30 +423,16 @@ final class LongArray private () extends Array[Long] { throwOutOfBounds(i) } else { val rawptr = castObjectToRawPtr(this) - elemRawPtr(rawptr, 16 + 8 * i) + elemRawPtr(rawptr, MemoryLayout.Array.ValuesOffset + 8 * i) } - @inline def apply(i: Int): Long = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 8 * i) - loadLong(ith) - } + @inline def apply(i: Int): Long = loadLong(atRaw(i)) - @inline def update(i: Int, value: Long): Unit = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 8 * i) - storeLong(ith, value) - } + @inline def update(i: Int, value: Long): Unit = storeLong(atRaw(i), value) @inline override def clone(): LongArray = { val arrcls = classOf[LongArray] - val arrsize = (16 + 8 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 8 * length) val arr = GC.alloc_atomic(arrcls, arrsize) val src = castObjectToRawPtr(this) libc.memcpy(arr, src, arrsize) @@ -507,11 +443,15 @@ final class LongArray private () extends Array[Long] { object LongArray { @inline def alloc(length: Int): LongArray = { + if (length < 0) { + throw new NegativeArraySizeException + } + val arrcls = classOf[LongArray] - val arrsize = (16 + 8 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 8 * length) val arr = GC.alloc_atomic(arrcls, arrsize) - storeInt(elemRawPtr(arr, 8), length) - storeInt(elemRawPtr(arr, 12), 8.toInt) + storeInt(elemRawPtr(arr, MemoryLayout.Array.LengthOffset), length) + storeInt(elemRawPtr(arr, MemoryLayout.Array.StrideOffset), 8) castRawPtrToObject(arr).asInstanceOf[LongArray] } @@ -519,7 +459,7 @@ object LongArray { val arr = alloc(length) val dst = arr.atRaw(0) val src = data - val size = (8 * length).toULong + val size = new ULong(8 * length) libc.memcpy(dst, src, size) arr } @@ -535,30 +475,16 @@ final class FloatArray private () extends Array[Float] { throwOutOfBounds(i) } else { val rawptr = castObjectToRawPtr(this) - elemRawPtr(rawptr, 16 + 4 * i) + elemRawPtr(rawptr, MemoryLayout.Array.ValuesOffset + 4 * i) } - @inline def apply(i: Int): Float = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 4 * i) - loadFloat(ith) - } + @inline def apply(i: Int): Float = loadFloat(atRaw(i)) - @inline def update(i: Int, value: Float): Unit = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 4 * i) - storeFloat(ith, value) - } + @inline def update(i: Int, value: Float): Unit = storeFloat(atRaw(i), value) @inline override def clone(): FloatArray = { val arrcls = classOf[FloatArray] - val arrsize = (16 + 4 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 4 * length) val arr = GC.alloc_atomic(arrcls, arrsize) val src = castObjectToRawPtr(this) libc.memcpy(arr, src, arrsize) @@ -569,11 +495,15 @@ final class FloatArray private () extends Array[Float] { object FloatArray { @inline def alloc(length: Int): FloatArray = { + if (length < 0) { + throw new NegativeArraySizeException + } + val arrcls = classOf[FloatArray] - val arrsize = (16 + 4 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 4 * length) val arr = GC.alloc_atomic(arrcls, arrsize) - storeInt(elemRawPtr(arr, 8), length) - storeInt(elemRawPtr(arr, 12), 4.toInt) + storeInt(elemRawPtr(arr, MemoryLayout.Array.LengthOffset), length) + storeInt(elemRawPtr(arr, MemoryLayout.Array.StrideOffset), 4) castRawPtrToObject(arr).asInstanceOf[FloatArray] } @@ -581,7 +511,7 @@ object FloatArray { val arr = alloc(length) val dst = arr.atRaw(0) val src = data - val size = (4 * length).toULong + val size = new ULong(4 * length) libc.memcpy(dst, src, size) arr } @@ -597,30 +527,16 @@ final class DoubleArray private () extends Array[Double] { throwOutOfBounds(i) } else { val rawptr = castObjectToRawPtr(this) - elemRawPtr(rawptr, 16 + 8 * i) + elemRawPtr(rawptr, MemoryLayout.Array.ValuesOffset + 8 * i) } - @inline def apply(i: Int): Double = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 8 * i) - loadDouble(ith) - } + @inline def apply(i: Int): Double = loadDouble(atRaw(i)) - @inline def update(i: Int, value: Double): Unit = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 8 * i) - storeDouble(ith, value) - } + @inline def update(i: Int, value: Double): Unit = storeDouble(atRaw(i), value) @inline override def clone(): DoubleArray = { val arrcls = classOf[DoubleArray] - val arrsize = (16 + 8 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 8 * length) val arr = GC.alloc_atomic(arrcls, arrsize) val src = castObjectToRawPtr(this) libc.memcpy(arr, src, arrsize) @@ -631,11 +547,15 @@ final class DoubleArray private () extends Array[Double] { object DoubleArray { @inline def alloc(length: Int): DoubleArray = { + if (length < 0) { + throw new NegativeArraySizeException + } + val arrcls = classOf[DoubleArray] - val arrsize = (16 + 8 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 8 * length) val arr = GC.alloc_atomic(arrcls, arrsize) - storeInt(elemRawPtr(arr, 8), length) - storeInt(elemRawPtr(arr, 12), 8.toInt) + storeInt(elemRawPtr(arr, MemoryLayout.Array.LengthOffset), length) + storeInt(elemRawPtr(arr, MemoryLayout.Array.StrideOffset), 8) castRawPtrToObject(arr).asInstanceOf[DoubleArray] } @@ -643,7 +563,7 @@ object DoubleArray { val arr = alloc(length) val dst = arr.atRaw(0) val src = data - val size = (8 * length).toULong + val size = new ULong(8 * length) libc.memcpy(dst, src, size) arr } @@ -659,30 +579,16 @@ final class ObjectArray private () extends Array[Object] { throwOutOfBounds(i) } else { val rawptr = castObjectToRawPtr(this) - elemRawPtr(rawptr, 16 + 8 * i) + elemRawPtr(rawptr, MemoryLayout.Array.ValuesOffset + 8 * i) } - @inline def apply(i: Int): Object = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 8 * i) - loadObject(ith) - } + @inline def apply(i: Int): Object = loadObject(atRaw(i)) - @inline def update(i: Int, value: Object): Unit = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 8 * i) - storeObject(ith, value) - } + @inline def update(i: Int, value: Object): Unit = storeObject(atRaw(i), value) @inline override def clone(): ObjectArray = { val arrcls = classOf[ObjectArray] - val arrsize = (16 + 8 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 8 * length) val arr = GC.alloc(arrcls, arrsize) val src = castObjectToRawPtr(this) libc.memcpy(arr, src, arrsize) @@ -693,11 +599,15 @@ final class ObjectArray private () extends Array[Object] { object ObjectArray { @inline def alloc(length: Int): ObjectArray = { + if (length < 0) { + throw new NegativeArraySizeException + } + val arrcls = classOf[ObjectArray] - val arrsize = (16 + 8 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 8 * length) val arr = GC.alloc(arrcls, arrsize) - storeInt(elemRawPtr(arr, 8), length) - storeInt(elemRawPtr(arr, 12), 8.toInt) + storeInt(elemRawPtr(arr, MemoryLayout.Array.LengthOffset), length) + storeInt(elemRawPtr(arr, MemoryLayout.Array.StrideOffset), 8) castRawPtrToObject(arr).asInstanceOf[ObjectArray] } @@ -705,7 +615,7 @@ object ObjectArray { val arr = alloc(length) val dst = arr.atRaw(0) val src = data - val size = (8 * length).toULong + val size = new ULong(8 * length) libc.memcpy(dst, src, size) arr } diff --git a/nativelib/src/main/scala/scala/scalanative/runtime/Arrays.scala.gyb b/nativelib/src/main/scala/scala/scalanative/runtime/Arrays.scala.gyb index 89954b67f0..e086aacdcc 100644 --- a/nativelib/src/main/scala/scala/scalanative/runtime/Arrays.scala.gyb +++ b/nativelib/src/main/scala/scala/scalanative/runtime/Arrays.scala.gyb @@ -32,7 +32,6 @@ import scalanative.unsigned._ import scalanative.runtime.Intrinsics._ % sizePtr = 8 -% sizeHeader = 16 sealed abstract class Array[T] extends java.io.Serializable with java.lang.Cloneable { @@ -40,7 +39,7 @@ sealed abstract class Array[T] /** Number of elements of the array. */ @inline def length: Int = { val rawptr = castObjectToRawPtr(this) - val lenptr = elemRawPtr(rawptr, ${sizePtr}) + val lenptr = elemRawPtr(rawptr, MemoryLayout.Array.LengthOffset) loadInt(lenptr) } @@ -181,30 +180,16 @@ final class ${T}Array private () extends Array[${T}] { throwOutOfBounds(i) } else { val rawptr = castObjectToRawPtr(this) - elemRawPtr(rawptr, ${sizeHeader} + ${sizeT} * i) + elemRawPtr(rawptr, MemoryLayout.Array.ValuesOffset + ${sizeT} * i) } - @inline def apply(i: Int): ${T} = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, ${sizeHeader} + ${sizeT} * i) - load${T}(ith) - } + @inline def apply(i: Int): ${T} = load${T}(atRaw(i)) - @inline def update(i: Int, value: ${T}): Unit = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, ${sizeHeader} + ${sizeT} * i) - store${T}(ith, value) - } + @inline def update(i: Int, value: ${T}): Unit = store${T}(atRaw(i), value) @inline override def clone(): ${T}Array = { val arrcls = classOf[${T}Array] - val arrsize = (${sizeHeader} + ${sizeT} * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + ${sizeT} * length) val arr = ${alloc}(arrcls, arrsize) val src = castObjectToRawPtr(this) libc.memcpy(arr, src, arrsize) @@ -215,11 +200,15 @@ final class ${T}Array private () extends Array[${T}] { object ${T}Array { @inline def alloc(length: Int): ${T}Array = { + if (length < 0) { + throw new NegativeArraySizeException + } + val arrcls = classOf[${T}Array] - val arrsize = (${sizeHeader} + ${sizeT} * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + ${sizeT} * length) val arr = ${alloc}(arrcls, arrsize) - storeInt(elemRawPtr(arr, 8), length) - storeInt(elemRawPtr(arr, 12), ${sizeT}.toInt) + storeInt(elemRawPtr(arr, MemoryLayout.Array.LengthOffset), length) + storeInt(elemRawPtr(arr, MemoryLayout.Array.StrideOffset), ${sizeT}) castRawPtrToObject(arr).asInstanceOf[${T}Array] } @@ -227,7 +216,7 @@ object ${T}Array { val arr = alloc(length) val dst = arr.atRaw(0) val src = data - val size = (${sizeT} * length).toULong + val size = new ULong(${sizeT} * length) libc.memcpy(dst, src, size) arr } diff --git a/nativelib/src/main/scala/scala/scalanative/runtime/LLVMIntrinsics.scala b/nativelib/src/main/scala/scala/scalanative/runtime/LLVMIntrinsics.scala index 577941f4ad..7ec2adfc1a 100644 --- a/nativelib/src/main/scala/scala/scalanative/runtime/LLVMIntrinsics.scala +++ b/nativelib/src/main/scala/scala/scalanative/runtime/LLVMIntrinsics.scala @@ -82,4 +82,6 @@ object LLVMIntrinsics { def `llvm.cttz.i16`(source: Short, iszeroundef: Boolean): Short = extern def `llvm.cttz.i32`(source: Int, iszeroundef: Boolean): Int = extern def `llvm.cttz.i64`(source: Long, iszeroundef: Boolean): Long = extern + def `llvm.stacksave`(): RawPtr = extern + def `llvm.stackrestore`(state: RawPtr): Unit = extern } diff --git a/nativelib/src/main/scala/scala/scalanative/runtime/MemoryLayout.scala b/nativelib/src/main/scala/scala/scalanative/runtime/MemoryLayout.scala new file mode 100644 index 0000000000..f93a54ea71 --- /dev/null +++ b/nativelib/src/main/scala/scala/scalanative/runtime/MemoryLayout.scala @@ -0,0 +1,45 @@ +package scala.scalanative.runtime + +import scala.scalanative.annotation.alwaysinline + +object MemoryLayout { + + /* Even though it might seem non-idiomatic to use `def` instead of `final val` + * for the constants it actual can be faster at runtime. Vals would require + * a fieldload operation and loading the module instance. Def would be + * evaluated and inlined in the optimizer - it would result with replacing + * method call with a constant value. + */ + + @alwaysinline private def PtrSize = 8 + @alwaysinline private def IntSize = 4 + + private[scalanative] object Rtti { + @alwaysinline def ClassOffset = 0 + @alwaysinline def IdOffset = ClassOffset + PtrSize + @alwaysinline def TraitIdOffset = IdOffset + IntSize + @alwaysinline def NameOffset = TraitIdOffset + IntSize + + @alwaysinline def size = NameOffset + PtrSize + } + + private[scalanative] object ClassRtti { + @alwaysinline def RttiOffset = 0 + @alwaysinline def SizeOffset = RttiOffset + Rtti.size + // Remaining fields has optional or contain intrinsic data, + // they should never be accessed in the runtime + } + + private[scalanative] object Object { + @alwaysinline def RttiOffset = 0 + @alwaysinline def FieldsOffset = RttiOffset + PtrSize + } + + private[scalanative] object Array { + @alwaysinline def RttiOffset = 0 + @alwaysinline def LengthOffset = RttiOffset + PtrSize + @alwaysinline def StrideOffset = LengthOffset + IntSize + @alwaysinline def ValuesOffset = StrideOffset + IntSize + } + +} diff --git a/nativelib/src/main/scala/scala/scalanative/runtime/Platform.scala b/nativelib/src/main/scala/scala/scalanative/runtime/Platform.scala index 43eca344c9..0fb521cbc0 100644 --- a/nativelib/src/main/scala/scala/scalanative/runtime/Platform.scala +++ b/nativelib/src/main/scala/scala/scalanative/runtime/Platform.scala @@ -38,4 +38,7 @@ object Platform { @name("scalanative_wide_char_size") final def SizeOfWChar: CSize = extern + + @name("scalanative_platform_is_msys") + def isMsys(): Boolean = extern } diff --git a/nativelib/src/main/scala/scala/scalanative/runtime/package.scala b/nativelib/src/main/scala/scala/scalanative/runtime/package.scala index f5ad137d41..1fe10116b9 100644 --- a/nativelib/src/main/scala/scala/scalanative/runtime/package.scala +++ b/nativelib/src/main/scala/scala/scalanative/runtime/package.scala @@ -64,8 +64,8 @@ package object runtime { /** Called by the generated code in case of incorrect class cast. */ @noinline def throwClassCast(from: RawPtr, to: RawPtr): Nothing = { - val fromName = loadObject(elemRawPtr(from, 16)) - val toName = loadObject(elemRawPtr(to, 16)) + val fromName = loadObject(elemRawPtr(from, MemoryLayout.Rtti.NameOffset)) + val toName = loadObject(elemRawPtr(to, MemoryLayout.Rtti.NameOffset)) throw new java.lang.ClassCastException( s"$fromName cannot be cast to $toName" ) diff --git a/nativelib/src/main/scala/scala/scalanative/runtime/unwind.scala b/nativelib/src/main/scala/scala/scalanative/runtime/unwind.scala index d2c2871879..5ec1957047 100644 --- a/nativelib/src/main/scala/scala/scalanative/runtime/unwind.scala +++ b/nativelib/src/main/scala/scala/scalanative/runtime/unwind.scala @@ -16,7 +16,7 @@ object unwind { cursor: Ptr[Byte], buffer: CString, length: CSize, - offset: Ptr[Byte] + offset: Ptr[Long] ): CInt = extern @name("scalanative_unwind_get_reg") def get_reg( @@ -27,4 +27,10 @@ object unwind { @name("scalanative_unw_reg_ip") def UNW_REG_IP: CInt = extern + + @name("scalanative_unwind_sizeof_context") + def sizeOfContext: CSize = extern + + @name("scalanative_unwind_sizeof_cursor") + def sizeOfCursor: CSize = extern } diff --git a/nativelib/src/main/scala/scala/scalanative/runtime/zlib.scala b/nativelib/src/main/scala/scala/scalanative/runtime/zlib.scala index e6af2d5c4e..415025490a 100644 --- a/nativelib/src/main/scala/scala/scalanative/runtime/zlib.scala +++ b/nativelib/src/main/scala/scala/scalanative/runtime/zlib.scala @@ -15,7 +15,7 @@ object zlib { type uLong = CUnsignedLong type uLongf = CUnsignedLong type alloc_func = CFuncPtr3[voidpf, uInt, uInt, voidpf] - type free_func = CFuncPtr2[voidpf, voidpf, Void] + type free_func = CFuncPtr2[voidpf, voidpf, Unit] type Bytef = Byte type z_size_t = CUnsignedLong type z_off_t = CLong diff --git a/nativelib/src/main/scala/scala/scalanative/unsafe/Tag.scala b/nativelib/src/main/scala/scala/scalanative/unsafe/Tag.scala index 9caf9ef465..b1c1ebf064 100644 --- a/nativelib/src/main/scala/scala/scalanative/unsafe/Tag.scala +++ b/nativelib/src/main/scala/scala/scalanative/unsafe/Tag.scala @@ -4434,4 +4434,33 @@ object Tag { } } } + + // Scala 3 defines scala.util.NotGiven, but it has a special handling in the compiler + // For Scala 2 we can use well known hack to get implicit negation (via ambigious defs) + type NotGivenCompat[+T] = NotGivenCompatDef.Proxy.NotGivenCompat[T] + object NotGivenCompatDef{ + import MockImpl._ + object Proxy { + import scala.util._ + type NotGivenCompat[+T] = NotGiven[T] + val NotGivenCompat = NotGiven + } + + object MockImpl { + final class NotGiven[+T] private () + sealed trait LowPriorityNotGiven { + implicit def default[T]: NotGiven[T] = NotGiven.value + } + object NotGiven extends LowPriorityNotGiven { + def value: NotGiven[Nothing] = new NotGiven[Nothing]() + + implicit def amb1[T](implicit ev: T): NotGiven[T] = ??? + implicit def amb2[T](implicit ev: T): NotGiven[T] = ??? + } + } + } + + private def TagOfPtrAnyClass = Tag.Ptr(Tag.Class(classOf[AnyRef])) + implicit def materializePtrWildcard: Tag[unsafe.Ptr[_]] = TagOfPtrAnyClass.asInstanceOf[Tag[unsafe.Ptr[_]]] + implicit def materializePtrClassNotGivenClassTag[T](implicit ev: NotGivenCompat[ClassTag[T]]): Tag[unsafe.Ptr[T]] = TagOfPtrAnyClass.asInstanceOf[Tag[unsafe.Ptr[T]]] } diff --git a/nativelib/src/main/scala/scala/scalanative/unsafe/Tag.scala.gyb b/nativelib/src/main/scala/scala/scalanative/unsafe/Tag.scala.gyb index 5eaaa4f45c..17390a9240 100644 --- a/nativelib/src/main/scala/scala/scalanative/unsafe/Tag.scala.gyb +++ b/nativelib/src/main/scala/scala/scalanative/unsafe/Tag.scala.gyb @@ -269,4 +269,33 @@ object Tag { } } % end + + // Scala 3 defines scala.util.NotGiven, but it has a special handling in the compiler + // For Scala 2 we can use well known hack to get implicit negation (via ambigious defs) + type NotGivenCompat[+T] = NotGivenCompatDef.Proxy.NotGivenCompat[T] + object NotGivenCompatDef{ + import MockImpl._ + object Proxy { + import scala.util._ + type NotGivenCompat[+T] = NotGiven[T] + val NotGivenCompat = NotGiven + } + + object MockImpl { + final class NotGiven[+T] private () + sealed trait LowPriorityNotGiven { + implicit def default[T]: NotGiven[T] = NotGiven.value + } + object NotGiven extends LowPriorityNotGiven { + def value: NotGiven[Nothing] = new NotGiven[Nothing]() + + implicit def amb1[T](implicit ev: T): NotGiven[T] = ??? + implicit def amb2[T](implicit ev: T): NotGiven[T] = ??? + } + } + } + + private def TagOfPtrAnyClass = Tag.Ptr(Tag.Class(classOf[AnyRef])) + implicit def materializePtrWildcard: Tag[unsafe.Ptr[_]] = TagOfPtrAnyClass.asInstanceOf[Tag[unsafe.Ptr[_]]] + implicit def materializePtrClassNotGivenClassTag[T](implicit ev: NotGivenCompat[ClassTag[T]]): Tag[unsafe.Ptr[T]] = TagOfPtrAnyClass.asInstanceOf[Tag[unsafe.Ptr[T]]] } diff --git a/nir/src/main/scala/scala/scalanative/nir/Buffer.scala b/nir/src/main/scala/scala/scalanative/nir/Buffer.scala index 2298e1d884..282094a95a 100644 --- a/nir/src/main/scala/scala/scalanative/nir/Buffer.scala +++ b/nir/src/main/scala/scala/scalanative/nir/Buffer.scala @@ -5,16 +5,13 @@ import scala.collection.mutable class Buffer(implicit fresh: Fresh) { private val buffer = mutable.UnrolledBuffer.empty[Inst] - def +=(inst: Inst): Unit = - buffer += inst - def ++=(insts: Seq[Inst]): Unit = - buffer ++= insts - def ++=(other: Buffer): Unit = - buffer ++= other.buffer - def toSeq: Seq[Inst] = - buffer.toSeq - def size: Int = - buffer.size + def +=(inst: Inst): Unit = buffer += inst + def ++=(insts: Seq[Inst]): Unit = buffer ++= insts + def ++=(other: Buffer): Unit = buffer ++= other.buffer + + def toSeq: Seq[Inst] = buffer.toSeq + def size: Int = buffer.size + def exists(pred: Inst => Boolean) = buffer.exists(pred) // Control-flow ops def label(name: Local)(implicit pos: Position): Unit = diff --git a/nir/src/main/scala/scala/scalanative/nir/Show.scala b/nir/src/main/scala/scala/scalanative/nir/Show.scala index 915404a88b..afabf43184 100644 --- a/nir/src/main/scala/scala/scalanative/nir/Show.scala +++ b/nir/src/main/scala/scala/scalanative/nir/Show.scala @@ -660,8 +660,9 @@ object Show { """([^\\]|^)\n""".r.replaceAllIn( s, _.matched.toSeq match { - case Seq(sngl) => s"""\\\\n""" - case Seq(fst, snd) => s"""${fst}\\\\n""" + case Seq(sngl) => raw"\\n" + case Seq('$', snd) => raw"\$$\\n" + case Seq(fst, snd) => raw"\${fst}\\n" } ) diff --git a/nir/src/main/scala/scala/scalanative/nir/Sig.scala b/nir/src/main/scala/scala/scalanative/nir/Sig.scala index f834605299..0d931d6424 100644 --- a/nir/src/main/scala/scala/scalanative/nir/Sig.scala +++ b/nir/src/main/scala/scala/scalanative/nir/Sig.scala @@ -38,29 +38,9 @@ final class Sig(val mangle: String) { final def isVirtual = !(isCtor || isClinit || isExtern) final def isPrivate: Boolean = privateIn.isDefined - final def isStatic: Boolean = { - def isPublicStatic = mangle.last == 'o' - def isPrivateStatic = { - val sigEnd = mangle.lastIndexOf('E') - val scopeIdx = sigEnd + 1 - def hasScope = mangle.length() > scopeIdx - sigEnd > 0 && hasScope && mangle(sigEnd + 1) == 'p' - } - isPublicStatic || isPrivateStatic - } - final lazy val privateIn: Option[Global.Top] = { - val sigEnd = mangle.lastIndexOf('E') - val scopeIdx = sigEnd + 1 - def hasScope = mangle.length() > scopeIdx - def isPrivate = { - val scopeIdent = mangle(scopeIdx) - scopeIdent == 'p' || scopeIdent == 'P' - } - if (sigEnd > 0 && hasScope && isPrivate) { - val global = Unmangle.unmangleGlobal(mangle.substring(sigEnd + 2)) - Some(global.top) - } else None - } + final def isStatic: Boolean = unmangled.sigScope.isStatic + final lazy val privateIn: Option[Global.Top] = + unmangled.sigScope.privateIn.map(_.top) } object Sig { sealed abstract class Scope( diff --git a/nir/src/main/scala/scala/scalanative/nir/Traverse.scala b/nir/src/main/scala/scala/scalanative/nir/Traverse.scala new file mode 100644 index 0000000000..f5f6fb6269 --- /dev/null +++ b/nir/src/main/scala/scala/scalanative/nir/Traverse.scala @@ -0,0 +1,180 @@ +package scala.scalanative.nir + +trait Traverse { + def onDefns(defns: Iterable[Defn]): Unit = defns.foreach(onDefn) + + def onDefn(defn: Defn): Unit = { + defn match { + case Defn.Var(_, _, ty, value) => + onType(ty) + onVal(value) + case Defn.Const(_, _, ty, value) => + onType(ty) + onVal(value) + case Defn.Declare(_, _, ty) => + onType(ty) + case Defn.Define(_, _, ty, insts) => + onInsts(insts) + case Defn.Trait(_, _, _) => () + case Defn.Class(_, _, _, _) => () + case Defn.Module(_, _, _, _) => () + } + } + + def onInsts(insts: Iterable[Inst]): Unit = + insts.foreach(onInst) + + def onInst(inst: Inst): Unit = { + inst match { + case Inst.Label(n, params) => + params.foreach { param => + onType(param.ty) + } + case Inst.Let(n, op, unwind) => + onOp(op) + onNext(unwind) + case Inst.Ret(v) => onVal(v) + case Inst.Jump(next) => onNext(next) + case Inst.If(v, thenp, elsep) => + onVal(v) + onNext(thenp) + onNext(elsep) + case Inst.Switch(v, default, cases) => + onVal(v) + onNext(default) + cases.foreach(onNext) + case Inst.Throw(v, unwind) => + onVal(v) + onNext(unwind) + case Inst.Unreachable(unwind) => + onNext(unwind) + case _: Inst.LinktimeCf => + () + } + } + + def onOp(op: Op): Unit = op match { + case Op.Call(ty, ptrv, argvs) => + onType(ty) + onVal(ptrv) + argvs.foreach(onVal) + case Op.Load(ty, ptrv) => + onType(ty) + onVal(ptrv) + case Op.Store(ty, ptrv, v) => + onType(ty) + onVal(ptrv) + onVal(v) + case Op.Elem(ty, ptrv, indexvs) => + onType(ty) + onVal(ptrv) + indexvs.foreach(onVal) + case Op.Extract(aggrv, indexvs) => + onVal(aggrv) + case Op.Insert(aggrv, v, indexvs) => + onVal(aggrv) + onVal(v) + case Op.Stackalloc(ty, v) => + onType(ty) + onVal(v) + case Op.Bin(bin, ty, lv, rv) => + onType(ty) + onVal(lv) + onVal(rv) + case Op.Comp(comp, ty, lv, rv) => + onType(ty) + onVal(lv) + onVal(rv) + case Op.Conv(conv, ty, v) => + onType(ty) + onVal(v) + + case Op.Classalloc(n) => () + case Op.Fieldload(ty, v, n) => + onType(ty) + onVal(v) + case Op.Fieldstore(ty, v1, n, v2) => + onType(ty) + onVal(v1) + onVal(v2) + case Op.Field(v, n) => + onVal(v) + case Op.Method(v, n) => + onVal(v) + case Op.Dynmethod(obj, signature) => + onVal(obj) + case Op.Module(n) => () + case Op.As(ty, v) => + onType(ty) + onVal(v) + case Op.Is(ty, v) => + onType(ty) + onVal(v) + case Op.Copy(v) => + onVal(v) + case Op.Sizeof(ty) => + onType(ty) + case Op.Box(code, obj) => + onVal(obj) + case Op.Unbox(code, obj) => + onVal(obj) + case Op.Var(ty) => + onType(ty) + case Op.Varload(elem) => + onVal(elem) + case Op.Varstore(elem, value) => + onVal(elem) + onVal(value) + case Op.Arrayalloc(ty, init) => + onType(ty) + onVal(init) + case Op.Arrayload(ty, arr, idx) => + onType(ty) + onVal(arr) + onVal(idx) + case Op.Arraystore(ty, arr, idx, value) => + onType(ty) + onVal(arr) + onVal(idx) + onVal(value) + case Op.Arraylength(arr) => + onVal(arr) + } + + def onVal(value: Val): Unit = value match { + case Val.Zero(ty) => onType(ty) + case Val.StructValue(values) => values.foreach(onVal) + case Val.ArrayValue(ty, values) => + onType(ty) + values.foreach(onVal) + case Val.Local(n, ty) => onType(ty) + case Val.Global(n, ty) => onType(ty) + case Val.Const(v) => onVal(v) + case _ => () + } + + def onType(ty: Type): Unit = ty match { + case Type.ArrayValue(ty, n) => + onType(ty) + case Type.Function(args, ty) => + args.foreach(onType) + onType(ty) + case Type.StructValue(tys) => + tys.foreach(onType) + case Type.Var(ty) => + onType(ty) + case Type.Array(ty, nullable) => + onType(ty) + case _ => + () + } + + def onNext(next: Next): Unit = next match { + case Next.None => () + case Next.Case(v, n) => + onVal(v) + onNext(n) + case Next.Unwind(n, next) => onNext(next) + case Next.Label(n, args) => args.foreach(onVal) + } +} diff --git a/nir/src/main/scala/scala/scalanative/nir/Types.scala b/nir/src/main/scala/scala/scalanative/nir/Types.scala index 805fc6c23b..245cb75244 100644 --- a/nir/src/main/scala/scala/scalanative/nir/Types.scala +++ b/nir/src/main/scala/scala/scalanative/nir/Types.scala @@ -89,11 +89,22 @@ object Type { final case class Var(ty: Type) extends SpecialKind final case class Function(args: Seq[Type], ret: Type) extends SpecialKind - val boxesTo = Seq[(Type, Type)]( - Type.Ref(Global.Top("scala.scalanative.unsigned.UByte")) -> Type.Byte, - Type.Ref(Global.Top("scala.scalanative.unsigned.UShort")) -> Type.Short, - Type.Ref(Global.Top("scala.scalanative.unsigned.UInt")) -> Type.Int, - Type.Ref(Global.Top("scala.scalanative.unsigned.ULong")) -> Type.Long, + private object unsigned { + val Byte = Type.Ref(Global.Top("scala.scalanative.unsigned.UByte")) + val Short = Type.Ref(Global.Top("scala.scalanative.unsigned.UShort")) + val Int = Type.Ref(Global.Top("scala.scalanative.unsigned.UInt")) + val Long = Type.Ref(Global.Top("scala.scalanative.unsigned.ULong")) + + val values: Set[nir.Type] = Set(Byte, Short, Int, Long) + } + private val unsignedBoxesTo = Seq[(Type, Type)]( + unsigned.Byte -> Type.Byte, + unsigned.Short -> Type.Short, + unsigned.Int -> Type.Int, + unsigned.Long -> Type.Long + ) + + val boxesTo: Seq[(Type, Type)] = unsignedBoxesTo ++ Seq( Type.Ref(Global.Top("scala.scalanative.unsafe.CArray")) -> Type.Ptr, Type.Ref(Global.Top("scala.scalanative.unsafe.CVarArgList")) -> Type.Ptr, Type.Ref(Global.Top("scala.scalanative.unsafe.Ptr")) -> Type.Ptr, @@ -120,11 +131,24 @@ object Type { unreachable }.toSeq - def isPtrBox(ty: Type): Boolean = ty match { - case refty: Type.RefKind => - unbox.get(Type.Ref(refty.className)).contains(Type.Ptr) - case _ => - false + private def isBoxOf(primitiveType: Type)(boxType: Type) = + unbox + .get(normalize(boxType)) + .contains(primitiveType) + def isPtrBox(ty: Type): Boolean = isBoxOf(Type.Ptr)(ty) + def isPtrType(ty: Type): Boolean = + ty == Type.Ptr || ty.isInstanceOf[Type.RefKind] + def isUnsignedType(ty: Type): Boolean = + unsigned.values.contains(normalize(ty)) + + def normalize(ty: Type): Type = ty match { + case ArrayValue(ty, n) => ArrayValue(normalize(ty), n) + case StructValue(tys) => StructValue(tys.map(normalize)) + case Array(ty, nullable) => Array(normalize(ty)) + case Ref(name, exact, nullable) => Ref(name) + case Var(ty) => Var(normalize(ty)) + case Function(args, ret) => Function(args.map(normalize), normalize(ret)) + case other => other } val typeToArray = Map[Type, Global]( diff --git a/nir/src/main/scala/scala/scalanative/nir/Versions.scala b/nir/src/main/scala/scala/scalanative/nir/Versions.scala index 8d4538b3a5..556855d222 100644 --- a/nir/src/main/scala/scala/scalanative/nir/Versions.scala +++ b/nir/src/main/scala/scala/scalanative/nir/Versions.scala @@ -25,7 +25,7 @@ object Versions { final val revision: Int = 9 // a.k.a. MINOR version /* Current public release version of Scala Native. */ - final val current: String = "0.4.10" + final val current: String = "0.4.12" final val currentBinaryVersion: String = binaryVersion(current) private object FullVersion { diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirDefinitions.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirDefinitions.scala index 8a9631958d..40a259307b 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirDefinitions.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirDefinitions.scala @@ -99,6 +99,10 @@ trait NirDefinitions { lazy val DoubleTagMethod = getDecl(TagModule, TermName("materializeDoubleTag")) lazy val PtrTagMethod = getDecl(TagModule, TermName("materializePtrTag")) + lazy val PtrWildcardTagMethod = + getDecl(TagModule, TermName("materializePtrWildcard")) + lazy val PtrClassNotGivenClassTagMethod = + getDecl(TagModule, TermName("materializePtrClassNotGivenClassTag")) lazy val ClassTagMethod = getDecl(TagModule, TermName("materializeClassTag")) lazy val NatBaseTagMethod = (0 to 9).map { n => diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExports.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExports.scala index 4af74306c6..164d8e4e17 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExports.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExports.scala @@ -28,7 +28,7 @@ trait NirGenExports[G <: nsc.Global with Singleton] { for { member <- owner.info.members if isExported(member) - if !owner.isExternModule + if !owner.isExternType // Externs combined with exports are not allowed, exception is handled in externs exported <- if (owner.isScalaModule) genModuleMember(owner, member) diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExpr.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExpr.scala index 793c9a1f6b..552bd4153a 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExpr.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExpr.scala @@ -205,15 +205,14 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => locally { buf.label(thenn)(thenp.pos) val thenv = genExpr(thenp) - buf.jump(mergen, Seq(thenv)) + buf.jumpExcludeUnitValue(retty)(mergen, thenv) } locally { buf.label(elsen)(elsep.pos) val elsev = genExpr(elsep) - buf.jump(mergen, Seq(elsev)) + buf.jumpExcludeUnitValue(retty)(mergen, elsev) } - buf.label(mergen, Seq(mergev)) - mergev + buf.labelExcludeUnitValue(mergen, mergev) } def genMatch(m: Match): Val = { @@ -261,15 +260,16 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => val scrut = genExpr(scrutp) buf.switch(scrut, defaultnext, casenexts) buf.label(defaultnext.name)(defaultp.pos) - buf.jump(merge, Seq(genExpr(defaultp)))(defaultp.pos) + buf.jumpExcludeUnitValue(retty)(merge, genExpr(defaultp))( + defaultp.pos + ) caseps.foreach { case (n, _, expr, pos) => buf.label(n)(pos) val caseres = genExpr(expr) - buf.jump(merge, Seq(caseres))(pos) + buf.jumpExcludeUnitValue(retty)(merge, caseres)(pos) } - buf.label(merge, Seq(mergev)) - mergev + buf.labelExcludeUnitValue(merge, mergev) } def genIfsChain(): Val = { @@ -378,13 +378,13 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => scoped(curUnwindHandler := Some(handler)) { nested.label(normaln) val res = nested.genExpr(expr) - nested.jump(mergen, Seq(res)) + nested.jumpExcludeUnitValue(retty)(mergen, res) } } locally { nested.label(handler, Seq(excv)) val res = nested.genTryCatch(retty, excv, mergen, catches)(expr.pos) - nested.jump(mergen, Seq(res)) + nested.jumpExcludeUnitValue(retty)(mergen, res) } // Append finally to the try/catch instructions and merge them back. @@ -398,8 +398,7 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => // Append try/catch instructions to the outher instruction buffer. buf.jump(Next(normaln)) buf ++= insts - buf.label(mergen, Seq(mergev)) - mergev + buf.labelExcludeUnitValue(mergen, mergev) } def genTryCatch( @@ -424,7 +423,7 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => curMethodEnv.enter(sym, cast) } val res = genExpr(body) - buf.jump(mergen, Seq(res)) + buf.jumpExcludeUnitValue(retty)(mergen, res) Val.Unit } (excty, f, exprPos) @@ -643,7 +642,7 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => } else { val ty = genType(tree.symbol.tpe) val name = genFieldName(tree.symbol) - if (sym.owner.isExternModule) { + if (sym.owner.isExternType) { val externTy = genExternType(tree.symbol.tpe) genLoadExtern(ty, externTy, tree.symbol) } else { @@ -670,7 +669,7 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => val qual = genExpr(qualp) val rhs = genExpr(rhsp) val name = genFieldName(sym) - if (sym.owner.isExternModule) { + if (sym.owner.isExternType) { val externTy = genExternType(sym.tpe) genStoreExtern(externTy, sym, rhs) } else { @@ -1857,6 +1856,8 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => case (Type.I(w1, _), Type.F(w2)) if w1 == w2 => Some(nir.Conv.Bitcast) case (Type.F(w1), Type.I(w2, _)) if w1 == w2 => Some(nir.Conv.Bitcast) case _ if fromty == toty => None + case (Type.Float, Type.Double) => Some(nir.Conv.Fpext) + case (Type.Double, Type.Float) => Some(nir.Conv.Fptrunc) case _ => unsupported(s"cast from $fromty to $toty") } @@ -2299,7 +2300,7 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => selfp: Tree, argsp: Seq[Tree] )(implicit pos: nir.Position): Val = { - if (sym.owner.isExternModule && sym.isAccessor) { + if (sym.owner.isExternType && sym.isAccessor) { genApplyExternAccessor(sym, argsp) } else if (sym.isStaticMember) { genApplyStaticMethod(sym, selfp, argsp) @@ -2314,7 +2315,7 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => receiver: Tree, argsp: Seq[Tree] )(implicit pos: nir.Position): Val = { - require(!sym.owner.isExternModule, sym.owner) + require(!sym.owner.isExternType, sym.owner) val name = genStaticMemberName(sym, receiver.symbol) val method = Val.Global(name, nir.Type.Ptr) val sig = genMethodSig(sym) @@ -2339,7 +2340,7 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => def genLoadExtern(ty: nir.Type, externTy: nir.Type, sym: Symbol)(implicit pos: nir.Position ): Val = { - assert(sym.owner.isExternModule, "loadExtern was not extern") + assert(sym.owner.isExternType, "loadExtern was not extern") val name = Val.Global(genName(sym), Type.Ptr) @@ -2349,7 +2350,7 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => def genStoreExtern(externTy: nir.Type, sym: Symbol, value: Val)(implicit pos: nir.Position ): Val = { - assert(sym.owner.isExternModule, "storeExtern was not extern") + assert(sym.owner.isExternType, "storeExtern was not extern") val name = Val.Global(genName(sym), Type.Ptr) val externValue = toExtern(externTy, value) @@ -2389,7 +2390,7 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => val owner = sym.owner val name = genMethodName(sym) val origSig = genMethodSig(sym) - val isExtern = owner.isExternModule + val isExtern = owner.isExternType val sig = if (isExtern) { genExternMethodSig(sym) @@ -2419,36 +2420,96 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => } def genMethodArgs(sym: Symbol, argsp: Seq[Tree]): Seq[Val] = - if (!sym.owner.isExternModule) { - genSimpleArgs(argsp) - } else { - val res = Seq.newBuilder[Val] - - argsp.zip(sym.tpe.params).foreach { - case (argp, paramSym) => - val externType = genExternType(paramSym.tpe) - val arg = (genExpr(argp), Type.box.get(externType)) match { - case (value @ Val.Null, Some(unboxedType)) => - externType match { - case Type.Ptr | _: Type.RefKind => value - case _ => - reporter.warning( - argp.pos, - s"Passing null as argument of ${paramSym}: ${paramSym.tpe} to the extern method is unsafe. " + - s"The argument would be unboxed to primitive value of type $externType." - ) - Val.Zero(unboxedType) + if (sym.owner.isExternType) genExternMethodArgs(sym, argsp) + else genSimpleArgs(argsp) + + private def genSimpleArgs(argsp: Seq[Tree]): Seq[Val] = argsp.map(genExpr) + + private def genExternMethodArgs(sym: Symbol, argsp: Seq[Tree]): Seq[Val] = { + val res = Seq.newBuilder[Val] + val nir.Type.Function(argTypes, _) = genExternMethodSig(sym) + val paramSyms = sym.tpe.params + assert( + argTypes.size == argsp.size && argTypes.size == paramSyms.size, + "Different number of arguments passed to method signature and apply method" + ) + + def genArg( + argp: Tree, + paramTpe: global.Type + ): nir.Val = { + implicit def pos: nir.Position = argp.pos + val externType = genExternType(paramTpe) + val value = (genExpr(argp), Type.box.get(externType)) match { + case (value @ Val.Null, Some(unboxedType)) => + externType match { + case Type.Ptr | _: Type.RefKind => value + case _ => + reporter.warning( + argp.pos, + s"Passing null as argument of type ${paramTpe} to the extern method is unsafe. " + + s"The argument would be unboxed to primitive value of type $externType." + ) + Val.Zero(unboxedType) + } + case (value, _) => value + } + toExtern(externType, value) + } + + for (((argp, sigType), paramSym) <- argsp zip argTypes zip paramSyms) { + sigType match { + case nir.Type.Vararg => + argp match { + case Apply(_, List(ArrayValue(_, args))) => + for (tree <- args) { + implicit def pos: nir.Position = tree.pos + val sym = tree.symbol + val tpe = + if (tree.symbol != null && tree.symbol.exists) + tree.symbol.tpe.finalResultType + else tree.tpe + val arg = genArg(tree, tpe) + def isUnsigned = Type.isUnsignedType(genType(tpe)) + // Decimal varargs needs to be promoted to at least Int, and float needs to be promoted to Double + val promotedArg = arg.ty match { + case Type.Float => + this.genCastOp(Type.Float, Type.Double, arg) + case Type.I(width, _) if width < Type.Int.width => + val conv = + if (isUnsigned) nir.Conv.Zext + else nir.Conv.Sext + buf.conv(conv, Type.Int, arg, unwind) + case _ => arg + } + res += promotedArg } - case (value, _) => value + case _ => + reporter.error( + argp.pos, + "Unable to extract vararg arguments, varargs to extern methods must be passed directly to the applied function" + ) } - res += toExtern(externType, arg)(argp.pos) + case _ => res += genArg(argp, paramSym.tpe) } + } + res.result() + } - res.result() + private def labelExcludeUnitValue(label: Local, value: nir.Val.Local)( + implicit pos: nir.Position + ): nir.Val = + value.ty match { + case Type.Unit => buf.label(label); Val.Unit + case _ => buf.label(label, Seq(value)); value } - def genSimpleArgs(argsp: Seq[Tree]): Seq[Val] = { - argsp.map(genExpr) - } + private def jumpExcludeUnitValue( + mergeType: nir.Type + )(label: Local, value: nir.Val)(implicit pos: nir.Position): Unit = + mergeType match { + case Type.Unit => buf.jump(label, Nil) + case _ => buf.jump(label, Seq(value)) + } } } diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenName.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenName.scala index 69be4fb373..4be1e760bd 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenName.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenName.scala @@ -28,12 +28,7 @@ trait NirGenName[G <: Global with Singleton] { def genTypeName(sym: Symbol): nir.Global.Top = { val id = { val fullName = sym.fullName - if (fullName == "java.lang._String") "java.lang.String" - else if (fullName == "java.lang._Object") "java.lang.Object" - else if (fullName == "java.lang._Class") "java.lang.Class" - else if (fullName == "scala.Nothing") "scala.runtime.Nothing$" - else if (fullName == "scala.Null") "scala.runtime.Null$" - else fullName + MappedNames.getOrElse(fullName, fullName) } val name = sym match { case ObjectClass => @@ -75,7 +70,7 @@ trait NirGenName[G <: Global with Singleton] { } owner.member { - if (sym.owner.isExternModule) { + if (sym.owner.isExternType) { nir.Sig.Extern(id) } else { nir.Sig.Field(id, scope) @@ -97,9 +92,11 @@ trait NirGenName[G <: Global with Singleton] { val paramTypes = tpe.params.toSeq.map(p => genType(p.info)) + def isExtern = sym.owner.isExternType + if (sym == String_+) genMethodName(StringConcatMethod) - else if (sym.owner.isExternModule) + else if (isExtern) owner.member(genExternSigImpl(sym, id)) else if (sym.name == nme.CONSTRUCTOR) owner.member(nir.Sig.Ctor(paramTypes)) @@ -157,7 +154,7 @@ trait NirGenName[G <: Global with Singleton] { private def nativeIdOf(sym: Symbol): String = { sym.getAnnotation(NameClass).flatMap(_.stringArg(0)).getOrElse { val name = sym.javaSimpleName.toString() - val id: String = if (sym.owner.isExternModule) { + val id: String = if (sym.owner.isExternType) { // Don't use encoded names for externs sym.decodedName.trim() } else if (sym.isField) { @@ -181,4 +178,18 @@ trait NirGenName[G <: Global with Singleton] { id.replace("\"", "$u0022") } } + + private val MappedNames = Map( + "java.lang._Class" -> "java.lang.Class", + "java.lang._Enum" -> "java.lang.Enum", + "java.lang._Object" -> "java.lang.Object", + "java.lang._String" -> "java.lang.String", + "scala.Nothing" -> "scala.runtime.Nothing$", + "scala.Null" -> "scala.runtime.Null$" + ).flatMap { + case classEntry @ (nativeName, javaName) => + classEntry :: + (nativeName + "$", javaName + "$") :: + Nil + } } diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenPhase.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenPhase.scala index 0bc6813ee9..a47066680f 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenPhase.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenPhase.scala @@ -39,6 +39,8 @@ abstract class NirGenPhase[G <: Global with Singleton](override val global: G) protected val curFresh = new util.ScopedVar[nir.Fresh] protected val curUnwindHandler = new util.ScopedVar[Option[nir.Local]] protected val curStatBuffer = new util.ScopedVar[StatBuffer] + protected val cachedMethodSig = + collection.mutable.Map.empty[(Symbol, Boolean), nir.Type.Function] protected def unwind(implicit fresh: Fresh): Next = curUnwindHandler.get.fold[Next](Next.None) { handler => @@ -158,6 +160,7 @@ abstract class NirGenPhase[G <: Global with Singleton](override val global: G) .forEach { case (path, stats) => genIRFile(path, stats) } } finally { generatedMirrorClasses.clear() + cachedMethodSig.clear() } } diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenStat.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenStat.scala index 49722c2878..daa8c551ce 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenStat.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenStat.scala @@ -88,11 +88,13 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => def nonEmpty = buf.nonEmpty def genClass(cd: ClassDef): Unit = { + val sym = cd.symbol + scoped( - curClassSym := cd.symbol, + curClassSym := sym, curClassFresh := nir.Fresh() ) { - if (cd.symbol.isStruct) genStruct(cd) + if (sym.isStruct) genStruct(cd) else genNormalClass(cd) } } @@ -118,30 +120,34 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => def traits = genClassInterfaces(sym) implicit val pos: nir.Position = cd.pos + + buf += { + if (sym.isScalaModule) Defn.Module(attrs, name, parent, traits) + else if (sym.isTraitOrInterface) Defn.Trait(attrs, name, traits) + else Defn.Class(attrs, name, parent, traits) + } + genReflectiveInstantiation(cd) genClassFields(cd) genMethods(cd) genMirrorClass(cd) + } - buf += { - if (sym.isScalaModule) { - Defn.Module(attrs, name, parent, traits) - } else if (sym.isTraitOrInterface) { - Defn.Trait(attrs, name, traits) - } else { - Defn.Class(attrs, name, parent, traits) - } + def genClassParent(sym: Symbol): Option[nir.Global] = { + if (sym.isExternType && + sym.superClass != ObjectClass) { + reporter.error( + sym.pos, + s"Extern object can only extend extern traits" + ) } - } - def genClassParent(sym: Symbol): Option[nir.Global] = - if (sym == NObjectClass) { - None - } else if (sym.superClass == NoSymbol || sym.superClass == ObjectClass) { + if (sym == NObjectClass) None + else if (sym.superClass == NoSymbol || sym.superClass == ObjectClass) Some(genTypeName(NObjectClass)) - } else { + else Some(genTypeName(sym.superClass)) - } + } def genClassAttrs(cd: ClassDef): Attrs = { val sym = cd.symbol @@ -160,21 +166,36 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => Attrs.fromSeq(annotationAttrs ++ abstractAttr) } - def genClassInterfaces(sym: Symbol) = - for { - parent <- sym.info.parents - psym = parent.typeSymbol if psym.isTraitOrInterface - } yield { - genTypeName(psym) + def genClassInterfaces(sym: Symbol) = { + val isExtern = sym.isExternType + def validate(psym: Symbol) = { + val parentIsExtern = psym.isExternType + if (isExtern && !parentIsExtern) + reporter.error( + sym.pos, + "Extern object can only extend extern traits" + ) + if (!isExtern && parentIsExtern) + reporter.error( + psym.pos, + "Extern traits can be only mixed with extern traits or objects" + ) } + for { + parent <- sym.parentSymbols + psym = parent.info.typeSymbol if psym.isTraitOrInterface + _ = validate(psym) + } yield genTypeName(psym) + } + def genClassFields(cd: ClassDef): Unit = { val sym = cd.symbol - val attrs = nir.Attrs(isExtern = sym.isExternModule) + val attrs = nir.Attrs(isExtern = sym.isExternType) for (f <- sym.info.decls if !f.isMethod && f.isTerm && !f.isModule) { - if (f.owner.isExternModule && !f.isMutable) { + if (f.owner.isExternType && !f.isMutable) { reporter.error(f.pos, "`extern` cannot be used in val definition") } val ty = genType(f.tpe) @@ -590,16 +611,22 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => dd.rhs match { case EmptyTree => - Some(Defn.Declare(attrs, name, sig)) + Some( + Defn.Declare( + attrs, + name, + if (attrs.isExtern) genExternMethodSig(sym) else sig + ) + ) - case _ if dd.name == nme.CONSTRUCTOR && owner.isExternModule => + case _ if dd.symbol.isConstructor && owner.isExternType => validateExternCtor(dd.rhs) None case _ if dd.name == nme.CONSTRUCTOR && owner.isStruct => None - case rhs if owner.isExternModule => + case rhs if owner.isExternType => checkExplicitReturnTypeAnnotation(dd, "extern method") genExternMethod(attrs, name, sig, dd) @@ -679,6 +706,21 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => dd: DefDef ): Option[nir.Defn] = { val rhs = dd.rhs + def externMethodDecl() = { + val externSig = genExternMethodSig(curMethodSym) + val externDefn = Defn.Declare(attrs, name, externSig)(rhs.pos) + + Some(externDefn) + } + + def isCallingExternMethod(sym: Symbol) = + sym.owner.isExternType + + def isExternMethodAlias(target: Symbol) = + (name, genName(target)) match { + case (Global.Member(_, lsig), Global.Member(_, rsig)) => lsig == rsig + case _ => false + } val defaultArgs = dd.symbol.paramss.flatten.filter(_.hasDefault) rhs match { case _ if defaultArgs.nonEmpty => @@ -688,16 +730,23 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => ) None case Apply(ref: RefTree, Seq()) if ref.symbol == ExternMethod => - val moduleName = genTypeName(curClassSym) - val externAttrs = Attrs(isExtern = true) - val externSig = genExternMethodSig(curMethodSym) - val externDefn = Defn.Declare(externAttrs, name, externSig)(rhs.pos) - Some(externDefn) + externMethodDecl() case _ if curMethodSym.hasFlag(ACCESSOR) => None - case rhs => - global.reporter.error( + case Apply(target, _) if isCallingExternMethod(target.symbol) => + val sym = target.symbol + if (isExternMethodAlias(sym)) externMethodDecl() + else { + reporter.error( + target.pos, + "Referencing other extern symbols in not supported" + ) + None + } + + case _ => + reporter.error( rhs.pos, "methods in extern objects must have extern body" ) @@ -706,21 +755,56 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => } def validateExternCtor(rhs: Tree): Unit = { - val Block(_ +: init, _) = rhs - val externs = init.map { - case Assign(ref: RefTree, Apply(extern, Seq())) - if extern.symbol == ExternMethod => - ref.symbol - case _ => - unsupported( - "extern objects may only contain extern fields and methods" - ) - }.toSet - for { - f <- curClassSym.info.decls if f.isField - if !externs.contains(f) - } { - unsupported("extern objects may only contain extern fields") + val classSym = curClassSym.get + def isExternCall(tree: Tree): Boolean = tree match { + case Typed(target, _) => isExternCall(target) + case Apply(extern, _) => extern.symbol == ExternMethod + case _ => false + } + + def isCurClassSetter(sym: Symbol) = + sym.isSetter && sym.owner.tpe <:< classSym.tpe + + rhs match { + case Block(Nil, _) => () // empty mixin constructor + case Block(inits, _) => + val externs = collection.mutable.Set.empty[Symbol] + inits.foreach { + case Assign(ref: RefTree, rhs) if isExternCall(rhs) => + externs += ref.symbol + + case Apply(fun, Seq(arg)) + if isCurClassSetter(fun.symbol) && isExternCall(arg) => + externs += fun.symbol + + case Apply(target, _) if target.symbol.isConstructor => () + + case tree => + reporter.error( + rhs.pos, + "extern objects may only contain extern fields and methods" + ) + } + def isInheritedField(f: Symbol) = { + def hasFieldGetter(cls: Symbol) = f.getterIn(cls) != NoSymbol + def inheritedTraits(cls: Symbol) = + cls.parentSymbols.filter(_.isTraitOrInterface) + def inheritsField(cls: Symbol): Boolean = + hasFieldGetter(cls) || inheritedTraits(cls).exists(inheritsField) + inheritsField(classSym) + } + + // Exclude fields derived from extern trait + for (f <- curClassSym.info.decls) { + if (f.isField && !isInheritedField(f)) { + if (!(externs.contains(f) || externs.contains(f.setter))) { + reporter.error( + f.pos, + "extern objects may only contain extern fields" + ) + } + } + } } } @@ -738,8 +822,9 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => case NoOptimizeClass => Attr.NoOpt case NoSpecializeClass => Attr.NoSpecialize } + val externAttrs = if (sym.owner.isExternType) Seq(Attr.Extern) else Nil - Attrs.fromSeq(inlineAttrs ++ annotatedAttrs) + Attrs.fromSeq(inlineAttrs ++ annotatedAttrs ++ externAttrs) } def genMethodBody( @@ -751,7 +836,7 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => val isSynchronized = dd.symbol.hasFlag(SYNCHRONIZED) val sym = dd.symbol val isStatic = sym.isStaticInNIR - val isExtern = sym.owner.isExternModule + val isExtern = sym.owner.isExternType implicit val pos: nir.Position = bodyp.pos @@ -900,7 +985,7 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => if (module == NoSymbol) Nil else { val moduleClass = module.moduleClass - if (moduleClass.isExternModule) Nil + if (moduleClass.isExternType) Nil else genStaticForwardersFromModuleClass(existingMembers, moduleClass) } } @@ -946,7 +1031,7 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => } m.isDeferred || m.isConstructor || m.hasAccessBoundary || - m.owner.isExternModule || + m.owner.isExternType || isOfJLObject } diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenType.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenType.scala index 9084999cc6..7fe5a894da 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenType.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenType.scala @@ -20,10 +20,11 @@ trait NirGenType[G <: Global with Singleton] { self: NirGenPhase[G] => sym.isModuleClass && !sym.isLifted def isStaticInNIR: Boolean = - sym.owner.isExternModule || sym.isStaticMember + sym.owner.isExternType || sym.isStaticMember - def isExternModule: Boolean = - isScalaModule && sym.annotations.exists(_.symbol == ExternClass) + def isExternType: Boolean = + (isScalaModule || sym.isTraitOrInterface) && + sym.annotations.exists(_.symbol == ExternClass) def isStruct: Boolean = sym.annotations.exists(_.symbol == StructClass) @@ -164,47 +165,46 @@ trait NirGenType[G <: Global with Singleton] { self: NirGenPhase[G] => sym: Symbol, isExtern: Boolean ): nir.Type.Function = { - require(sym.isMethod || sym.isStaticMember, "symbol is not a method") - - val tpe = sym.tpe - val owner = sym.owner - val paramtys = genMethodSigParamsImpl(sym, isExtern) - val selfty = - if (isExtern || sym.isStaticInNIR) None - else Some(genType(owner.tpe)) - val retty = - if (sym.isClassConstructor) nir.Type.Unit - else if (isExtern) genExternType(sym.tpe.resultType) - else genType(sym.tpe.resultType) - - nir.Type.Function(selfty ++: paramtys, retty) + def resolve() = { + require(sym.isMethod || sym.isStaticMember, "symbol is not a method") + + val tpe = sym.tpe + val owner = sym.owner + val paramtys = genMethodSigParamsImpl(sym, isExtern) + val selfty = + if (isExtern || sym.isStaticInNIR) None + else Some(genType(owner.tpe)) + val retty = + if (sym.isClassConstructor) nir.Type.Unit + else if (isExtern) genExternType(sym.tpe.resultType) + else genType(sym.tpe.resultType) + + nir.Type.Function(selfty ++: paramtys, retty) + } + cachedMethodSig.getOrElseUpdate((sym, isExtern), resolve()) } private def genMethodSigParamsImpl( sym: Symbol, isExtern: Boolean ): Seq[nir.Type] = { - val wereRepeated = exitingPhase(currentRun.typerPhase) { - for { - params <- sym.tpe.paramss - param <- params - } yield { - param.name -> isScalaRepeatedParamType(param.tpe) - } - }.toMap - - sym.tpe.params.map { - case p - if wereRepeated.getOrElse(p.name, false) && - sym.owner.isExternModule => - nir.Type.Vararg - - case p => - if (isExtern) { - genExternType(p.tpe) - } else { - genType(p.tpe) + val params = sym.tpe.params + if (!isExtern && !sym.owner.isExternType) + params.map { p => genType(p.tpe) } + else { + val wereRepeated = exitingPhase(currentRun.typerPhase) { + for { + params <- sym.tpe.paramss + param <- params + } yield { + param.name -> isScalaRepeatedParamType(param.tpe) } + }.toMap + + params.map { p => + if (wereRepeated.getOrElse(p.name, false)) nir.Type.Vararg + else genExternType(p.tpe) + } } } } diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenUtil.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenUtil.scala index 5fa35695dd..940ac21f80 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenUtil.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenUtil.scala @@ -58,21 +58,23 @@ trait NirGenUtil[G <: Global with Singleton] { self: NirGenPhase[G] => def wrap(sym: Symbol) = allsts.map(SimpleType(sym, _)) ref.symbol match { - case UnitTagMethod => just(UnitClass) - case BooleanTagMethod => just(BooleanClass) - case CharTagMethod => just(CharClass) - case ByteTagMethod => just(ByteClass) - case UByteTagMethod => just(UByteClass) - case ShortTagMethod => just(ShortClass) - case UShortTagMethod => just(UShortClass) - case IntTagMethod => just(IntClass) - case UIntTagMethod => just(UIntClass) - case LongTagMethod => just(LongClass) - case ULongTagMethod => just(ULongClass) - case FloatTagMethod => just(FloatClass) - case DoubleTagMethod => just(DoubleClass) - case PtrTagMethod => just(PtrClass) - case ClassTagMethod => just(unwrapClassTagOption(args.head).get) + case UnitTagMethod => just(UnitClass) + case BooleanTagMethod => just(BooleanClass) + case CharTagMethod => just(CharClass) + case ByteTagMethod => just(ByteClass) + case UByteTagMethod => just(UByteClass) + case ShortTagMethod => just(ShortClass) + case UShortTagMethod => just(UShortClass) + case IntTagMethod => just(IntClass) + case UIntTagMethod => just(UIntClass) + case LongTagMethod => just(LongClass) + case ULongTagMethod => just(ULongClass) + case FloatTagMethod => just(FloatClass) + case DoubleTagMethod => just(DoubleClass) + case PtrTagMethod => just(PtrClass) + case PtrWildcardTagMethod => just(PtrClass) + case PtrClassNotGivenClassTagMethod => just(PtrClass) + case ClassTagMethod => just(unwrapClassTagOption(args.head).get) case sym if CStructTagMethod.contains(sym) => wrap(CStructClass(args.length)) case CArrayTagMethod => diff --git a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirCodeGen.scala b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirCodeGen.scala index d3f40b8af3..8b356b1e00 100644 --- a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirCodeGen.scala +++ b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirCodeGen.scala @@ -29,6 +29,8 @@ class NirCodeGen(val settings: GenNIR.Settings)(using ctx: Context) protected val defnNir = NirDefinitions.get protected val nirPrimitives = new NirPrimitives() protected val positionsConversions = new NirPositions(settings.sourceURIMaps) + protected val cachedMethodSig = + collection.mutable.Map.empty[(Symbol, Boolean), nir.Type.Function] protected val curClassSym = new util.ScopedVar[ClassSymbol] protected val curClassFresh = new util.ScopedVar[nir.Fresh] @@ -60,6 +62,7 @@ class NirCodeGen(val settings: GenNIR.Settings)(using ctx: Context) generatedDefns.clear() generatedMirrorClasses.clear() reflectiveInstantiationBuffers.clear() + cachedMethodSig.clear() } } @@ -154,6 +157,7 @@ class NirCodeGen(val settings: GenNIR.Settings)(using ctx: Context) class MethodLabelsEnv(val fresh: nir.Fresh) { private val entries, exits = mutable.Map.empty[Symbol, Local] + private val exitTypes = mutable.Map.empty[Local, nir.Type] def enterLabel(ld: Labeled): (nir.Local, nir.Local) = { val sym = ld.bind.symbol @@ -168,6 +172,10 @@ class NirCodeGen(val settings: GenNIR.Settings)(using ctx: Context) def resolveExit(sym: Symbol): nir.Local = exits(sym) def resolveExit(label: Labeled): nir.Local = exits(label.bind.symbol) + + def enterExitType(local: nir.Local, exitType: nir.Type): Unit = + exitTypes += local -> exitType + def resolveExitType(local: nir.Local): nir.Type = exitTypes(local) } class MethodEnv(val fresh: nir.Fresh) { diff --git a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirDefinitions.scala b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirDefinitions.scala index dbb50f5333..4e85fcadfa 100644 --- a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirDefinitions.scala +++ b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirDefinitions.scala @@ -139,6 +139,9 @@ final class NirDefinitions()(using ctx: Context) { def UnsafeTag_materializeNatBaseTags(using Context) = UnsafeTag_materializeNatBaseTagsR.map(_.symbol) def UnsafeTag_materializeNatDigitTags(using Context) = UnsafeTag_materializeNatDigitTagsR.map(_.symbol) def UnsafeTag_materializeCStructTags(using Context) = UnsafeTag_materializeCStructTagsR.map(_.symbol) + @tu lazy val UnsafeTag_materializePtrWildcardTag = TagModule.requiredMethod("materializePtrWildcard") + @tu lazy val UnsafeTag_materializePtrClassNotGivenClassTag = + TagModule.requiredMethod("materializePtrClassNotGivenClassTag") // Native runtime package @tu lazy val RuntimePackageVal = requiredModuleRef("scala.scalanative.runtime.package") diff --git a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenExpr.scala b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenExpr.scala index 6e877e1bee..f012da59d9 100644 --- a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenExpr.scala +++ b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenExpr.scala @@ -452,16 +452,15 @@ trait NirGenExpr(using Context) { given nir.Position = thenp.span buf.label(thenn) val thenv = genExpr(thenp) - buf.jump(mergen, Seq(thenv)) + buf.jumpExcludeUnitValue(retty)(mergen, thenv) } locally { given nir.Position = elsep.span buf.label(elsen) val elsev = genExpr(elsep) - buf.jump(mergen, Seq(elsev)) + buf.jumpExcludeUnitValue(retty)(mergen, elsev) } - buf.label(mergen, Seq(mergev)) - mergev + buf.labelExcludeUnitValue(mergen, mergev) } def genJavaSeqLiteral(tree: JavaSeqLiteral): Val = { @@ -490,14 +489,17 @@ trait NirGenExpr(using Context) { val (labelEntry, labelExit) = curMethodLabels.enterLabel(label) val labelExitParam = Val.Local(fresh(), genType(bind.tpe)) + curMethodLabels.enterExitType(labelExit, labelExitParam.ty) buf.jump(Next(labelEntry)) buf.label(labelEntry, Nil) - buf.jump(labelExit, Seq(genExpr(label.expr))) + buf.jumpExcludeUnitValue(labelExitParam.ty)( + labelExit, + genExpr(label.expr) + ) - buf.label(labelExit, Seq(labelExitParam)) - labelExitParam + buf.labelExcludeUnitValue(labelExit, labelExitParam) } def genLiteral(lit: Literal): Val = { @@ -578,16 +580,17 @@ trait NirGenExpr(using Context) { val scrut = genExpr(scrutp) buf.switch(scrut, defaultnext, casenexts) buf.label(defaultnext.name)(using defaultCasePos) - buf.jump(merge, Seq(genExpr(defaultp)))(using defaultCasePos) + buf.jumpExcludeUnitValue(retty)(merge, genExpr(defaultp))(using + defaultCasePos + ) caseps.foreach { case Case(n, _, expr, pos) => given nir.Position = pos buf.label(n) val caseres = genExpr(expr) - buf.jump(merge, Seq(caseres)) + buf.jumpExcludeUnitValue(retty)(merge, caseres) } - buf.label(merge, Seq(mergev)) - mergev + buf.labelExcludeUnitValue(merge, mergev) } def genIfsChain(): Val = { @@ -695,8 +698,11 @@ trait NirGenExpr(using Context) { else value from match { - case Some(label) => buf.jump(label, Seq(retv)) - case _ => buf.ret(retv) + case Some(label) => + val retty = curMethodLabels.resolveExitType(label) + buf.jumpExcludeUnitValue(retty)(label, retv) + case _ if retv.ty == Type.Unit => buf.ret(Val.Unit) + case _ => buf.ret(retv) } Val.Unit } @@ -781,12 +787,12 @@ trait NirGenExpr(using Context) { scoped(curUnwindHandler := Some(handler)) { nested.label(normaln) val res = nested.genExpr(expr) - nested.jump(mergen, Seq(res)) + nested.jumpExcludeUnitValue(retty)(mergen, res) } locally { nested.label(handler, Seq(excv)) val res = nested.genTryCatch(retty, excv, mergen, catches) - nested.jump(mergen, Seq(res)) + nested.jumpExcludeUnitValue(retty)(mergen, res) } // Append finally to the try/catch instructions and merge them back. @@ -797,8 +803,7 @@ trait NirGenExpr(using Context) { // Append try/catch instructions to the outher instruction buffer. buf.jump(Next(normaln)) buf ++= insts - buf.label(mergen, Seq(mergev)) - mergev + buf.labelExcludeUnitValue(mergen, mergev) } private def genTryCatch( @@ -823,7 +828,7 @@ trait NirGenExpr(using Context) { curMethodEnv.enter(sym, cast) } val res = genExpr(body) - buf.jump(mergen, Seq(res)) + buf.jumpExcludeUnitValue(retty)(mergen, res) Val.Unit } (excty, f, exprPos) @@ -1453,36 +1458,77 @@ trait NirGenExpr(using Context) { } } - def genMethodArgs(sym: Symbol, argsp: Seq[Tree]): Seq[Val] = { - if !sym.isExtern then genSimpleArgs(argsp) - else { - val res = Seq.newBuilder[Val] - argsp.zip(sym.paramInfo.paramInfoss.flatten).foreach { - case (argp, paramTpe) => - given nir.Position = argp.span - val externType = genExternType(paramTpe.finalResultType) - val arg = (genExpr(argp), Type.box.get(externType)) match { - case (value @ Val.Null, Some(unboxedType)) => - externType match { - case Type.Ptr | _: Type.RefKind => value - case _ => - report.warning( - s"Passing null as argument of type ${paramTpe.show} to the extern method is unsafe. " + - s"The argument would be unboxed to primitive value of type $externType.", - argp.srcPos - ) - Val.Zero(unboxedType) - } - case (value, _) => value + def genMethodArgs(sym: Symbol, argsp: Seq[Tree]): Seq[Val] = + if sym.isExtern + then genExternMethodArgs(sym, argsp) + else genSimpleArgs(argsp) + + private def genSimpleArgs(argsp: Seq[Tree]): Seq[Val] = argsp.map(genExpr) + + private def genExternMethodArgs(sym: Symbol, argsp: Seq[Tree]): Seq[Val] = { + val res = Seq.newBuilder[Val] + val Type.Function(argTypes, _) = genExternMethodSig(sym) + val paramTypes = sym.paramInfo.paramInfoss.flatten + assert( + argTypes.size == argsp.size && argTypes.size == paramTypes.size, + "Different number of arguments passed to method signature and apply method" + ) + + def genArg( + argp: Tree, + paramTpe: Types.Type + ): nir.Val = { + given nir.Position = argp.span + val externType = genExternType(paramTpe.finalResultType) + val value = (genExpr(argp), Type.box.get(externType)) match { + case (value @ Val.Null, Some(unboxedType)) => + externType match { + case Type.Ptr | _: Type.RefKind => value + case _ => + report.warning( + s"Passing null as argument of type ${paramTpe.show} to the extern method is unsafe. " + + s"The argument would be unboxed to primitive value of type $externType.", + argp.srcPos + ) + Val.Zero(unboxedType) } - res += toExtern(externType, arg) + case (value, _) => value } - res.result() + toExtern(externType, value) } - } - private def genSimpleArgs(argsp: Seq[Tree]): Seq[Val] = { - argsp.map(genExpr) + for ((argp, sigType), paramTpe) <- argsp zip argTypes zip paramTypes + do + sigType match { + case nir.Type.Vararg => + argp match { + case Apply(_, List(seqLiteral: JavaSeqLiteral)) => + for tree <- seqLiteral.elems + do + given nir.Position = tree.span + val arg = genArg(tree, tree.tpe) + def isUnsigned = Type.isUnsignedType(genType(tree.tpe)) + // Decimal varargs needs to be promoted to at least Int, and Float needs to be promoted to Double + val promotedArg = arg.ty match { + case Type.Float => + this.genCastOp(Type.Float, Type.Double, arg) + case Type.I(width, _) if width < Type.Int.width => + val conv = + if (isUnsigned) nir.Conv.Zext + else nir.Conv.Sext + buf.conv(conv, Type.Int, arg, unwind) + case _ => arg + } + res += promotedArg + case _ => + report.error( + "Unable to extract vararg arguments, varargs to extern methods must be passed directly to the applied function", + argp.srcPos + ) + } + case _ => res += genArg(argp, paramTpe) + } + res.result() } private def genArrayOp(app: Apply, code: Int): Val = { @@ -1743,6 +1789,8 @@ trait NirGenExpr(using Context) { case (Type.I(w1, _), Type.F(w2)) if w1 == w2 => Some(nir.Conv.Bitcast) case (Type.F(w1), Type.I(w2, _)) if w1 == w2 => Some(nir.Conv.Bitcast) case _ if fromty == toty => None + case (Type.Float, Type.Double) => Some(nir.Conv.Fpext) + case (Type.Double, Type.Float) => Some(nir.Conv.Fptrunc) case _ => unsupported(s"cast from $fromty to $toty") } @@ -2505,6 +2553,22 @@ trait NirGenExpr(using Context) { } } + private def labelExcludeUnitValue(label: Local, value: nir.Val.Local)(using + nir.Position + ): nir.Val = + value.ty match + case Type.Unit => buf.label(label); Val.Unit + case _ => buf.label(label, Seq(value)); value + + private def jumpExcludeUnitValue( + mergeType: nir.Type + )(label: Local, value: nir.Val)(using + nir.Position + ): Unit = + mergeType match + case Type.Unit => buf.jump(label, Nil) + case _ => buf.jump(label, Seq(value)) + } sealed class FixupBuffer(using fresh: Fresh) extends nir.Buffer { diff --git a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenStat.scala b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenStat.scala index 9560587b30..716b7f8ce5 100644 --- a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenStat.scala +++ b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenStat.scala @@ -54,11 +54,7 @@ trait NirGenStat(using Context) { val name = genTypeName(sym) def parent = genClassParent(sym) - def traits = sym.info.parents - .map(_.classSymbol) - .filter(_.isTraitOrInterface) - .map(genTypeName) - + def traits = genClassInterfaces(sym) generatedDefns += { if (sym.isStaticModule) Defn.Module(attrs, name, parent, traits) else if (sym.isTraitOrInterface) Defn.Trait(attrs, name, traits) @@ -85,16 +81,40 @@ trait NirGenStat(using Context) { } private def genClassParent(sym: ClassSymbol): Option[nir.Global] = { - if (sym == defnNir.NObjectClass) - None - else - Some { - val superClass = sym.superClass - if (superClass == NoSymbol || superClass == defn.ObjectClass) - genTypeName(defnNir.NObjectClass) - else - genTypeName(superClass) - } + if sym.isExternType && sym.superClass != defn.ObjectClass then + report.error("Extern object can only extend extern traits", sym.sourcePos) + + Option.unless(sym == defnNir.NObjectClass) { + val superClass = sym.superClass + if superClass == NoSymbol || superClass == defn.ObjectClass + then genTypeName(defnNir.NObjectClass) + else genTypeName(superClass) + } + } + + private def genClassInterfaces(sym: ClassSymbol): Seq[nir.Global] = { + val isExtern = sym.isExternType + def validate(clsSym: ClassSymbol) = { + val parentIsExtern = clsSym.isExternType + if isExtern && !parentIsExtern then + report.error( + "Extern object can only extend extern traits", + clsSym.sourcePos + ) + + if !isExtern && parentIsExtern then + report.error( + "Extern traits can be only mixed with extern traits or objects", + sym.sourcePos + ) + } + + for + sym <- sym.info.parents + clsSym = sym.classSymbol.asClass + if clsSym.isTraitOrInterface + _ = validate(clsSym) + yield genTypeName(clsSym) } private def genClassFields(td: TypeDef): Unit = { @@ -183,7 +203,7 @@ trait NirGenStat(using Context) { dd.rhs match { case EmptyTree => Some(Defn.Declare(attrs, name, sig)) - case _ if sym.isClassConstructor && sym.isExtern => + case _ if sym.isConstructor && sym.isExtern => validateExternCtor(dd.rhs) None @@ -226,10 +246,10 @@ trait NirGenStat(using Context) { case defnNir.NoOptimizeType => Attr.NoOpt case defnNir.NoSpecializeType => Attr.NoSpecialize case defnNir.StubType => Attr.Stub - case defnNir.ExternType => Attr.Extern } + val externAttrs = if (sym.owner.isExternType) Seq(Attr.Extern) else Nil - Attrs.fromSeq(inlineAttrs ++ annotatedAttrs) + Attrs.fromSeq(inlineAttrs ++ annotatedAttrs ++ externAttrs) } protected val curExprBuffer = ScopedVar[ExprBuffer]() @@ -400,24 +420,48 @@ trait NirGenStat(using Context) { val rhs = dd.rhs given nir.Position = rhs.span val defaultArgs = dd.paramss.flatten.filter(_.symbol.is(HasDefault)) + def externMethodDecl() = { + val externAttrs = Attrs(isExtern = true) + val externSig = genExternMethodSig(curMethodSym) + val externDefn = Defn.Declare(externAttrs, name, externSig) + Some(externDefn) + } + + def isExternMethodAlias(target: Symbol) = (name, genName(target)) match { + case (Global.Member(_, lsig), Global.Member(_, rsig)) => lsig == rsig + case _ => false + } + rhs match { case _ - if defaultArgs.nonEmpty || dd.name.is( - NameKinds.DefaultGetterName - ) => + if defaultArgs.nonEmpty || dd.name.is(NameKinds.DefaultGetterName) => report.error("extern method cannot have default argument") None case Apply(ref: RefTree, Seq()) if ref.symbol == defnNir.UnsafePackage_extern => - val moduleName = genTypeName(curClassSym) - val externAttrs = Attrs(isExtern = true) - val externSig = genExternMethodSig(curMethodSym) - Some(Defn.Declare(externAttrs, name, externSig)) - case _ if curMethodSym.get.isOneOf(Accessor | Synthetic) => - None - case rhs => + externMethodDecl() + + case _ if curMethodSym.get.isOneOf(Accessor | Synthetic) => None + + case Apply(target, args) if target.symbol.isExtern => + val sym = target.symbol + val Global.Member(_, selfSig) = name: @unchecked + def isExternMethodForwarder = + genExternSig(sym) == selfSig && + genExternMethodSig(sym) == origSig + + if isExternMethodForwarder then externMethodDecl() + else { + report.error( + "Referencing other extern symbols in not supported", + dd.sourcePos + ) + None + } + + case _ => report.error( - s"methods in extern objects must have extern body - ${rhs}", + s"methods in extern objects must have extern body", rhs.sourcePos ) None @@ -425,24 +469,52 @@ trait NirGenStat(using Context) { } def validateExternCtor(rhs: Tree): Unit = { - val Block(_ +: init, _) = rhs: @unchecked - val externs = init.map { - case Assign(ref: RefTree, Apply(extern, Seq())) - if extern.symbol == defnNir.UnsafePackage_extern => - ref.symbol - case _ => + val Block(exprs, _) = rhs: @unchecked + val classSym = curClassSym.get + + val externs = collection.mutable.Set.empty[Symbol] + def isExternCall(tree: Tree): Boolean = tree match + case Apply(extern, _) => + extern.symbol == defnNir.UnsafePackage_extern + case _ => false + + def isCurClassSetter(sym: Symbol) = + sym.isSetter && sym.owner.typeRef <:< classSym.typeRef + + exprs.foreach { + case Assign(ref: RefTree, rhs) if isExternCall(rhs) => + externs += ref.symbol + + case Apply(ref: RefTree, Seq(arg)) + if isCurClassSetter(ref.symbol) && isExternCall(arg) => + externs += ref.symbol + + case tree @ Apply(ref, _) if ref.symbol.isConstructor => + () + + case tree => report.error( - "extern objects may only contain extern fields and methods", + s"extern objects may only contain extern fields and methods", rhs.sourcePos ) - }.toSet - for { - f <- curClassSym.get.info.decls.toList if f.isField - if !externs.contains(f) - } report.error( - "extern objects may only contain extern fields", - f.sourcePos - ) + } + + def isInheritedField(f: Symbol) = + classSym.directlyInheritedTraits.exists { + _.info.decls.exists(_ matches f.getter) + } + + for f <- classSym.info.decls + do { + // Exclude fields derived from extern trait + if (f.isField && !isInheritedField(f)) { + if !(externs.contains(f) || externs.contains(f.setter)) then + report.error( + s"extern objects may only contain extern fields", + f.sourcePos + ) + } + } } // Static forwarders ------------------------------------------------------- @@ -499,7 +571,7 @@ trait NirGenStat(using Context) { if (!module.exists) Nil else { val moduleClass = module.moduleClass - if (moduleClass.isExternModule) Nil + if (moduleClass.isExternType) Nil else genStaticForwardersFromModuleClass(existingMembers, moduleClass) } } diff --git a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenType.scala b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenType.scala index 091a386553..d3831e6954 100644 --- a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenType.scala +++ b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenType.scala @@ -11,6 +11,8 @@ import core.Types._ import core.Symbols._ import core.StdNames._ import core.TypeErasure._ +import dotty.tools.dotc.report +import dotty.tools.dotc.typer.TyperPhase import dotty.tools.dotc.transform.SymUtils._ import scala.scalanative.nir @@ -44,13 +46,14 @@ trait NirGenType(using Context) { sym.is(JavaStatic) || sym.isScalaStatic || sym.isExtern def isExtern: Boolean = sym.exists && { - sym.owner.isExternModule || + sym.owner.isExternType || sym.hasAnnotation(defnNir.ExternClass) || (sym.is(Accessor) && sym.field.isExtern) } - def isExternModule: Boolean = - isScalaModule && sym.hasAnnotation(defnNir.ExternClass) + def isExternType: Boolean = + (isScalaModule || sym.isTraitOrInterface) && + sym.hasAnnotation(defnNir.ExternClass) def isStruct: Boolean = sym.hasAnnotation(defnNir.StructClass) @@ -230,38 +233,60 @@ trait NirGenType(using Context) { sym: Symbol, isExtern: Boolean ): nir.Type.Function = { - require( - sym.is(Method) || sym.isStatic, - s"symbol ${sym.owner} $sym is not a method" - ) - - val owner = sym.owner - val paramtys = genMethodSigParamsImpl(sym, isExtern) - val selfty = Option.unless(isExtern || sym.isStaticInNIR) { - genType(owner) + def resolve() = { + require( + sym.is(Method) || sym.isStatic, + s"symbol ${sym.owner} $sym is not a method" + ) + + val owner = sym.owner + val paramtys = genMethodSigParamsImpl(sym, isExtern) + val selfty = Option.unless(isExtern || sym.isStaticInNIR) { + genType(owner) + } + val resultType = sym.info.resultType + val retty = + if (sym.isConstructor) nir.Type.Unit + else if (isExtern) genExternType(resultType) + else genType(resultType) + nir.Type.Function(selfty ++: paramtys, retty) } - val resultType = sym.info.resultType - val retty = - if (sym.isConstructor) nir.Type.Unit - else if (isExtern) genExternType(resultType) - else genType(resultType) - nir.Type.Function(selfty ++: paramtys, retty) + cachedMethodSig.getOrElseUpdate((sym, isExtern), resolve()) } private def genMethodSigParamsImpl( sym: Symbol, isExtern: Boolean - ): Seq[nir.Type] = { + )(using Context): Seq[nir.Type] = { + import core.Phases._ + val repeatedParams = if (sym.isExtern) { + atPhase(typerPhase) { + sym.paramInfo.stripPoly match { + // @extern def foo(a: Int): Int + case MethodTpe(paramNames, paramTypes, _) => + for (name, tpe) <- paramNames zip paramTypes + yield name -> tpe.isRepeatedParam + case t if t.isVarArgsMethod => + report.warning( + "Unable to resolve method sig params for symbol, extern VarArgs would not work", + sym.srcPos + ) + Nil + case _ => Nil + } + }.toMap + } else Map.empty + val info = sym.info for { - paramList <- sym.info.paramInfoss - param <- paramList + (paramTypes, paramNames) <- info.paramInfoss zip info.paramNamess + (paramType, paramName) <- paramTypes zip paramNames } yield { - if (param.isRepeatedParam && sym.isExtern) - nir.Type.Vararg - else if (isExtern) genExternType(param) - else genType(param) + def isRepeated = repeatedParams.getOrElse(paramName, false) + if (isExtern && isRepeated) nir.Type.Vararg + else if (isExtern) genExternType(paramType) + else genType(paramType) } } } diff --git a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenUtil.scala b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenUtil.scala index fe66e1e6c7..0f4a4361d8 100644 --- a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenUtil.scala +++ b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenUtil.scala @@ -45,7 +45,9 @@ trait NirGenUtil(using Context) { self: NirCodeGen => defnNir.UnsafeTag_materializeUShortTag -> defnNir.UShortClass, defnNir.UnsafeTag_materializeUIntTag -> defnNir.UIntClass, defnNir.UnsafeTag_materializeULongTag -> defnNir.ULongClass, - defnNir.UnsafeTag_materializePtrTag -> defnNir.PtrClass + defnNir.UnsafeTag_materializePtrTag -> defnNir.PtrClass, + defnNir.UnsafeTag_materializePtrWildcardTag -> defnNir.PtrClass, + defnNir.UnsafeTag_materializePtrClassNotGivenClassTag -> defnNir.PtrClass ) protected def desugarTree(tree: Tree): Tree = { @@ -120,12 +122,16 @@ trait NirGenUtil(using Context) { self: NirCodeGen => } def resolveGiven = Option.when(s.is(Given)) { - atPhase(Phases.postTyperPhase) { + val evidenceType = atPhase(Phases.postTyperPhase) { val givenTpe = s.denot.info.argInfos.head - // make sure to dealias the type here, - // information about underlying opaque type would not be available after erasue - fromType(givenTpe.widenDealias) + // In the backend (genBCode on JVM or this compiler plugin) opaque type should be dealiased + // to the underlying type otherwise non-existing types might be missing when classloading. + // Information about underlying opaque type would not be available after erasue + givenTpe.typeSymbol.opaqueAlias + .orElse(givenTpe) + .widenDealias } + fromType(evidenceType) } materializePrimitiveTypeMethodTypes diff --git a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/PrepNativeInterop.scala b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/PrepNativeInterop.scala index c910aec8de..cda0200390 100644 --- a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/PrepNativeInterop.scala +++ b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/PrepNativeInterop.scala @@ -4,7 +4,7 @@ import dotty.tools.dotc.plugins.PluginPhase import dotty.tools._ import dotc._ import dotc.ast.tpd._ -import dotc.transform.SymUtils.setter +import dotc.transform.SymUtils._ import core.Contexts._ import core.Definitions import core.Names._ @@ -12,8 +12,8 @@ import core.Symbols._ import core.Types._ import core.StdNames._ import core.Constants.Constant +import core.Flags._ import NirGenUtil.ContextCached -import dotty.tools.dotc.core.Flags /** This phase does: * - Rewrite calls to scala.Enumeration.Value (include name string) (Ported @@ -37,12 +37,47 @@ class PrepNativeInterop extends PluginPhase { dd.symbol.isWrappedToplevelDef } + extension (sym: Symbol) + def isTraitOrInterface(using Context): Boolean = + sym.is(Trait) || sym.isAllOf(JavaInterface) + + def isScalaModule(using Context): Boolean = + sym.is(ModuleClass, butNot = Lifted) + + def isExtern(using Context): Boolean = sym.exists && { + sym.owner.isExternType || + sym.hasAnnotation(defnNir.ExternClass) || + (sym.is(Accessor) && sym.field.isExtern) + } + + def isExternType(using Context): Boolean = + (isScalaModule || sym.isTraitOrInterface) && + sym.hasAnnotation(defnNir.ExternClass) + end extension + override def transformDefDef(dd: DefDef)(using Context): Tree = { + val sym = dd.symbol + lazy val rhsSym = dd.rhs.symbol // Set `@extern` annotation for top-level extern functions - if (isTopLevelExtern(dd) && !dd.symbol.hasAnnotation(defnNir.ExternClass)) { - dd.symbol.addAnnotation(defnNir.ExternClass) + if (isTopLevelExtern(dd) && !sym.hasAnnotation(defnNir.ExternClass)) { + sym.addAnnotation(defnNir.ExternClass) + } + + def usesVariadicArgs = sym.paramInfo.stripPoly match { + case MethodTpe(paramNames, paramTypes, _) => + paramTypes.exists(param => param.isRepeatedParam) + case t => t.isVarArgsMethod } - dd + + if sym.is(Exported) && rhsSym.isExtern && usesVariadicArgs + then + // Externs with varargs need to be called directly, replace proxy + // with redifintion of extern method + // from: def foo(args: Any*): Unit = origin.foo(args) + // into: def foo(args: Any*): Unit = extern + sym.addAnnotation(defnNir.ExternClass) + cpy.DefDef(dd)(rhs = ref(defnNir.UnsafePackage_extern)) + else dd } override def transformValDef(vd: ValDef)(using Context): Tree = { @@ -63,9 +98,7 @@ class PrepNativeInterop extends PluginPhase { if (isTopLevelExtern(vd) && !sym.hasAnnotation(defnNir.ExternClass)) { sym.addAnnotation(defnNir.ExternClass) - if (vd.symbol.is( - Flags.Mutable - )) { + if (vd.symbol.is(Mutable)) { sym.setter.addAnnotation(defnNir.ExternClass) } } diff --git a/posixlib/src/main/resources/scala-native/dlfcn.c b/posixlib/src/main/resources/scala-native/dlfcn.c new file mode 100644 index 0000000000..34f34afd32 --- /dev/null +++ b/posixlib/src/main/resources/scala-native/dlfcn.c @@ -0,0 +1,14 @@ +#if defined(__unix__) || defined(__unix) || defined(unix) || \ + (defined(__APPLE__) && defined(__MACH__)) + +#include + +int scalanative_rtld_lazy() { return RTLD_LAZY; }; + +int scalanative_rtld_now() { return RTLD_NOW; }; + +int scalanative_rtld_global() { return RTLD_GLOBAL; }; + +int scalanative_rtld_local() { return RTLD_LOCAL; }; + +#endif // Unix or Mac OS diff --git a/posixlib/src/main/resources/scala-native/monetary.c b/posixlib/src/main/resources/scala-native/monetary.c deleted file mode 100644 index 6d44f3b507..0000000000 --- a/posixlib/src/main/resources/scala-native/monetary.c +++ /dev/null @@ -1,40 +0,0 @@ -#ifdef _WIN32 -// No Windows support -#else -#if !(defined __STDC_VERSION__) || (__STDC_VERSION__ < 201112L) -#ifndef SCALANATIVE_SUPPRESS_STRUCT_CHECK_WARNING -#warning "Size and order of C structures are not checked when -std < c11." -#endif -#else // POSIX -#include -#include - -#include // FIXME - -#ifdef __APPLE__ -#include -#endif // __APPLE__ - -ssize_t scalanative_strfmon_10(char *restrict str, size_t max, - const char *restrict format, double arg0, - double arg1, double arg2, double arg3, - double arg4, double arg5, double arg6, - double arg7, double arg8, double arg9) { - - return strfmon(str, max, format, arg0, arg1, arg2, arg3, arg4, arg5, arg6, - arg7, arg8, arg9); -} - -ssize_t scalanative_strfmon_l_10(char *restrict str, size_t max, - locale_t locale, const char *restrict format, - double arg0, double arg1, double arg2, - double arg3, double arg4, double arg5, - double arg6, double arg7, double arg8, - double arg9) { - - return strfmon_l(str, max, locale, format, arg0, arg1, arg2, arg3, arg4, - arg5, arg6, arg7, arg8, arg9); -} - -#endif // POSIX -#endif // ! _WIN32 diff --git a/posixlib/src/main/resources/scala-native/stdio.c b/posixlib/src/main/resources/scala-native/stdio.c new file mode 100644 index 0000000000..74395a98fc --- /dev/null +++ b/posixlib/src/main/resources/scala-native/stdio.c @@ -0,0 +1,18 @@ +#include + +#if !defined(L_ctermid) +#if defined(_WIN32) +// Windows MAX_PATH is 260, plus 1 for terminating NUL/NULL/"\u0000". +#define L_ctermid 260 + 1 +#else +#error "L_ctermid is not defined in stdio.h" +#endif +#endif + +// This file contains functions that wrap posixlib +// built-in macros. We need this because Scala Native +// can not expand C macros, and that's the easiest way to +// get the values out of those in a portable manner. + +// CX extension +int scalanative_l_ctermid() { return L_ctermid; } diff --git a/posixlib/src/main/resources/scala-native/sys/socket.c b/posixlib/src/main/resources/scala-native/sys/socket.c index 61e9ae0b4f..5a530fe151 100644 --- a/posixlib/src/main/resources/scala-native/sys/socket.c +++ b/posixlib/src/main/resources/scala-native/sys/socket.c @@ -5,11 +5,18 @@ #include #include "socket_conversions.h" +#if defined(__MINGW64__) +#include +#include +#endif #ifdef _WIN32 #include #pragma comment(lib, "Ws2_32.lib") typedef SSIZE_T ssize_t; #else +#if defined(__FreeBSD__) +#import // u_long & friends. Required by Amazon FreeBSD64 arm64 +#endif // __FreeBSD__ #include #include #if !(defined __STDC_VERSION__) || (__STDC_VERSION__ < 201112L) diff --git a/posixlib/src/main/scala/scala/scalanative/posix/dirent.scala b/posixlib/src/main/scala/scala/scalanative/posix/dirent.scala index cf07986fe1..76b61963ba 100644 --- a/posixlib/src/main/scala/scala/scalanative/posix/dirent.scala +++ b/posixlib/src/main/scala/scala/scalanative/posix/dirent.scala @@ -7,7 +7,7 @@ import scala.scalanative.unsafe._, Nat._ object dirent { type _256 = Digit3[_2, _5, _6] - type DIR = Void + type DIR = CStruct0 type dirent = CStruct3[CUnsignedLongLong, CArray[CChar, _256], CShort] diff --git a/posixlib/src/main/scala/scala/scalanative/posix/dlfcn.scala b/posixlib/src/main/scala/scala/scalanative/posix/dlfcn.scala new file mode 100644 index 0000000000..031f0dd39b --- /dev/null +++ b/posixlib/src/main/scala/scala/scalanative/posix/dlfcn.scala @@ -0,0 +1,40 @@ +package scala.scalanative +package posix + +import scala.scalanative.unsafe._ + +/** POSIX dlfcn.h for Scala + * + * The Open Group Base Specifications + * [[https://pubs.opengroup.org/onlinepubs/9699919799 Issue 7, 2018]] edition. + */ + +@link("dl") +@extern object dlfcn { + +// Symbolic constants + + @name("scalanative_rtld_lazy") + def RTLD_LAZY: CInt = extern + + @name("scalanative_rtld_now") + def RTLD_NOW: CInt = extern + + @name("scalanative_rtld_global") + def RTLD_GLOBAL: CInt = extern + + @name("scalanative_rtld_local") + def RTLD_LOCAL: CInt = extern + +// Methods + + // Convention: A C "void *" is represented in Scala Native as a "Ptr[Byte]". + + def dlclose(handle: Ptr[Byte]): Int = extern + + def dlerror(): CString = extern + + def dlopen(filename: CString, flags: Int): Ptr[Byte] = extern + + def dlsym(handle: Ptr[Byte], symbol: CString): Ptr[Byte] = extern +} diff --git a/posixlib/src/main/scala/scala/scalanative/posix/monetary.scala b/posixlib/src/main/scala/scala/scalanative/posix/monetary.scala index 5a941d8603..c92afb7541 100644 --- a/posixlib/src/main/scala/scala/scalanative/posix/monetary.scala +++ b/posixlib/src/main/scala/scala/scalanative/posix/monetary.scala @@ -3,107 +3,30 @@ package posix import scalanative.unsafe._ -import scalanative.posix.sys.types._ +import scalanative.posix.sys.types.{ssize_t, size_t} /** POSIX monetary.h for Scala * * The Open Group Base Specifications * [[https://pubs.opengroup.org/onlinepubs/9699919799 Issue 7, 2018]] edition. - * - * POSIX defines strfmon() and strfmon_l() using the "..." form of C variable - * arguments. Scala Native "supports native interoperability with C’s variadic - * argument list type (i.e. va_list), but not ... varargs". - * - * This implementation supports up to 10 items in the variable arguments to - * strfmon() and strfmon_1(). */ +@extern object monetary { type locale_t = locale.locale_t - private final val maxOutArgs = 10 def strfmon( str: CString, max: size_t, format: CString, - argsIn: Double* - ): ssize_t = Zone { implicit z => - val argsOut = new Array[Double](maxOutArgs) - val limit = Math.min(argsIn.size, maxOutArgs) - - for (j <- 0 until limit) - argsOut(j) = argsIn(j) - - // format: off - val nFormatted = monetaryExtern.strfmon_10(str, max, format, - argsOut(0), argsOut(1), - argsOut(2), argsOut(3), - argsOut(4), argsOut(5), - argsOut(6), argsOut(7), - argsOut(8), argsOut(9) - ) - // format: on - - nFormatted - } + vargs: Double* + ): ssize_t = extern def strfmon_l( str: CString, max: size_t, locale: locale_t, format: CString, - argsIn: Double* - ): ssize_t = Zone { implicit z => - val argsOut = new Array[Double](maxOutArgs) - val limit = Math.min(argsIn.size, maxOutArgs) - - for (j <- 0 until limit) - argsOut(j) = argsIn(j) - - // format: off - val nFormatted = monetaryExtern.strfmon_l_10(str, max, locale, format, - argsOut(0), argsOut(1), - argsOut(2), argsOut(3), - argsOut(4), argsOut(5), - argsOut(6), argsOut(7), - argsOut(8), argsOut(9) - ) - // format: on - - nFormatted - } - -} - -@extern -object monetaryExtern { - - // format: off - @name("scalanative_strfmon_10") - def strfmon_10( - str: CString, - max: size_t, - format: CString, - arg0: Double, arg1: Double, - arg2: Double, arg3: Double, - arg4: Double, arg5: Double, - arg6: Double, arg7: Double, - arg8: Double, arg9: Double - ): ssize_t = extern - // format: on - - // format: off - @name("scalanative_strfmon_l_10") - def strfmon_l_10( - str: CString, - max: size_t, - locale: monetary.locale_t, - format: CString, - arg0: Double, arg1: Double, - arg2: Double, arg3: Double, - arg4: Double, arg5: Double, - arg6: Double, arg7: Double, - arg8: Double, arg9: Double + vargs: Double* ): ssize_t = extern - // format: on } diff --git a/posixlib/src/main/scala/scala/scalanative/posix/stdio.scala b/posixlib/src/main/scala/scala/scalanative/posix/stdio.scala new file mode 100644 index 0000000000..cd22c3328c --- /dev/null +++ b/posixlib/src/main/scala/scala/scalanative/posix/stdio.scala @@ -0,0 +1,93 @@ +package scala.scalanative +package posix + +import scalanative.unsafe, unsafe._ +import scalanative.posix.sys.types, types.{off_t, size_t} +import scalanative.libc.stdio._ + +@extern object stdio { + /* Open Group 2018 extensions to ISO/IEC C. + * Reference: + * https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdio.h.html + * + * These definitions are annotated CX (ISO/IEC C extension) + * in the above specification. + */ + + type ssize_t = types.ssize_t + + type va_list = unsafe.CVarArgList + +// Macros + + /* Open Group POSIX defines this as a C macro. + * To provide the value in a portable manner, it is implemented here as + * an external method. A slight but necessary deviation from the + * specification. The same idiom is used in an number of other posixlib + * files. + */ + @name("scalanative_l_ctermid") + def L_ctermid: CUnsignedInt = extern + +// Methods + + def ctermid(s: CString): CString = extern + + def dprintf(fd: Int, format: CString, valist: va_list): Int = extern + + def fdopen(fd: Int, mode: CString): Ptr[FILE] = extern + def fileno(stream: Ptr[FILE]): Int = extern + def flockfile(filehandle: Ptr[FILE]): Unit = extern + + def fmemopen( + buf: Ptr[Byte], + size: size_t, + mode: CString + ): Ptr[FILE] = extern + + def fseeko(stream: Ptr[FILE], offset: off_t, whence: Int): Int = + extern + + def ftello(stream: Ptr[FILE]): off_t = extern + + // Can not block; see "try" part of "ftry*" + def ftrylockfile(filehandle: Ptr[FILE]): Int = extern + + def funlockfile(filehandle: Ptr[FILE]): Unit = extern + + def getc_unlocked(stream: Ptr[CString]): Int = extern + def getchar_unlocked(): Int = extern + + def getdelim( + lineptr: Ptr[CString], + n: Ptr[size_t], + delim: Int, + stream: Ptr[FILE] + ): ssize_t = extern + + def getline( + lineptr: Ptr[CString], + n: Ptr[size_t], + stream: Ptr[FILE] + ): ssize_t = extern + + def open_memstream( + ptr: Ptr[CString], + sizeloc: Ptr[size_t] + ): Ptr[FILE] = + extern + + def pclose(stream: Ptr[FILE]): Int = extern + def popen(command: CString, typ: CString): Ptr[FILE] = extern + def putc_unlocked(c: Int, stream: Ptr[FILE]): Int = extern + def putchar_unlocked(c: Int): Int = extern + + def renameat( + olddirfd: Int, + oldpath: CString, + newdirdf: Int, + newpath: CString + ): Int = extern + + def vdprintf(fd: Int, format: CString, ap: va_list): Int = extern +} diff --git a/posixlib/src/main/scala/scala/scalanative/posix/unistd.scala b/posixlib/src/main/scala/scala/scalanative/posix/unistd.scala index efab0f3263..74fdb97523 100644 --- a/posixlib/src/main/scala/scala/scalanative/posix/unistd.scala +++ b/posixlib/src/main/scala/scala/scalanative/posix/unistd.scala @@ -25,13 +25,22 @@ object unistd { var optopt: CInt = extern // Methods/functions - def _exit(status: CInt): Unit = extern def access(pathname: CString, mode: CInt): CInt = extern def chdir(path: CString): CInt = extern def close(fildes: CInt): CInt = extern def dup(fildes: CInt): CInt = extern def dup2(fildes: CInt, fildesnew: CInt): CInt = extern - def execve(path: CString, argv: Ptr[CString], envp: Ptr[CString]): CInt = + + def _exit(status: CInt): Unit = extern + + // XSI + def encrypt(block: Ptr[Byte], edflag: Int): Unit = extern + + def execl(pathname: CString, arg: CString, vargs: Any*): CInt = extern + def execlp(file: CString, arg: CString, vargs: Any*): CInt = extern + def execle(pathname: CString, arg: CString, vargs: Any*): CInt = extern + def execv(pathname: CString, argv: Ptr[CString]): CInt = extern + def execve(pathname: CString, argv: Ptr[CString], envp: Ptr[CString]): CInt = extern def fork(): CInt = extern def fsync(fildes: CInt): CInt = extern diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index 9da61acdfb..120b499983 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -85,7 +85,8 @@ object BinaryIncompatibilities { "scala.scalanative.posix.limits.PATH_MAX" ), // Moved to javalib, used internally and in scripted-tests - exclude[MissingClassProblem]("scala.scalanative.runtime.SocketHelpers*") + exclude[MissingClassProblem]("scala.scalanative.runtime.SocketHelpers*"), + exclude[Problem]("scala.scalanative.posix.monetaryExtern*") ) final val WindowsLib: Filters = Nil diff --git a/project/Build.scala b/project/Build.scala index 5fd2dcf9a7..1a01460b51 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -441,11 +441,6 @@ object Build { lazy val sandbox = MultiScalaProject("sandbox", file("sandbox")) .enablePlugins(MyScalaNativePlugin) - .settings(nativeConfig ~= { c => - c.withLTO(LTO.default) - .withMode(Mode.default) - .withGC(GC.default) - }) .withNativeCompilerPlugin .withJUnitPlugin .dependsOn(scalalib, testInterface % "test") diff --git a/project/Commands.scala b/project/Commands.scala index 9d7dd15f84..1c1baee171 100644 --- a/project/Commands.scala +++ b/project/Commands.scala @@ -81,19 +81,14 @@ object Commands { | "-Dscala.version=$version" :+ | "-Dscala213.version=${ScalaVersions.scala213}" |}""".stripMargin - // Scala 3 is supported since sbt 1.5.0 + // Scala 3 is supported since sbt 1.5.0. 1.5.8 is used. // Older versions set incorrect binary version val isScala3 = version.startsWith("3.") - val overrideSbtVersion = - if (isScala3) - """set sbtScalaNative/sbtVersion := "1.5.0" """ :: Nil - else Nil val scalaVersionTests = if (isScala3) "scala3/*" else "" setScriptedLaunchOpts :: - overrideSbtVersion ::: s"sbtScalaNative/scripted ${scalaVersionTests} run/*" :: state } diff --git a/project/ScalaVersions.scala b/project/ScalaVersions.scala index 5d1445562d..1094487697 100644 --- a/project/ScalaVersions.scala +++ b/project/ScalaVersions.scala @@ -18,7 +18,8 @@ object ScalaVersions { val scala213: String = crossScala213.last val scala3: String = "3.1.3" - val sbt10Version: String = "1.1.6" // minimum version + // minimum version - 1.5 is required for Scala 3 and 1.5.8 has log4j vulnerability fixed + val sbt10Version: String = "1.5.8" val sbt10ScalaVersion: String = scala212 val libCrossScalaVersions: Seq[String] = diff --git a/project/Settings.scala b/project/Settings.scala index 2cc1737b78..0f2bfa8518 100644 --- a/project/Settings.scala +++ b/project/Settings.scala @@ -178,7 +178,7 @@ object Settings { ), mimaPreviousArtifacts ++= { // The previous releases of Scala Native with which this version is binary compatible. - val binCompatVersions = (0 to 9).map(v => s"0.4.$v").toSet + val binCompatVersions = (0 to 11).map(v => s"0.4.$v").toSet val toolsProjects = Set("util", "tools", "nir", "test-runner") lazy val neverPublishedProjects040 = Map( "2.11" -> (toolsProjects ++ Set("windowslib", "scala3lib")), @@ -415,13 +415,15 @@ object Settings { // baseDirectory = project/{native,jvm}/.{binVersion} val testsRootDir = baseDirectory.value.getParentFile.getParentFile val sharedTestsDir = testsRootDir / "shared/src/test" - def sources2_13OrAbove = sharedTestsDir / "scala-2.13+" - def sources3_2 = sharedTestsDir / "scala-3.2" + val `sources 2.12+` = Seq(sharedTestsDir / "scala-2.12+") + val `sources 2.13+` = `sources 2.12+` :+ sharedTestsDir / "scala-2.13+" + val `sources 3.2+` = `sources 2.13+` :+ sharedTestsDir / "scala-3.2" val extraSharedDirectories = - scalaVersionsDependendent(scalaVersion.value)(List.empty[File]) { - case (2, 13) => sources2_13OrAbove :: Nil - case (3, 1) => sources2_13OrAbove :: Nil - case (3, _) => sources2_13OrAbove :: sources3_2 :: Nil + scalaVersionsDependendent(scalaVersion.value)(Seq.empty[File]) { + case (2, 12) => `sources 2.12+` + case (2, 13) => `sources 2.13+` + case (3, 1) => `sources 2.13+` + case (3, _) => `sources 3.2+` } val sharedScalaSources = scalaVersionDirectories(sharedTestsDir, "scala", scalaVersion.value) diff --git a/sbt-scala-native/src/main/scala/scala/scalanative/sbtplugin/ScalaNativePluginInternal.scala b/sbt-scala-native/src/main/scala/scala/scalanative/sbtplugin/ScalaNativePluginInternal.scala index e7818809ea..25274b2f44 100644 --- a/sbt-scala-native/src/main/scala/scala/scalanative/sbtplugin/ScalaNativePluginInternal.scala +++ b/sbt-scala-native/src/main/scala/scala/scalanative/sbtplugin/ScalaNativePluginInternal.scala @@ -19,7 +19,8 @@ import scala.scalanative.testinterface.adapter.TestAdapter import scala.sys.process.Process import scala.util.Try import scala.scalanative.build.Platform -import java.nio.file.Files +import sjsonnew.BasicJsonProtocol._ +import java.nio.file.{Files, Path} object ScalaNativePluginInternal { @@ -335,6 +336,15 @@ object ScalaNativePluginInternal { case (tf, Some(adapter)) => (tf, adapter) } .toMap + }, + // Override default to avoid triggering a Test/nativeLink in a Test/compile + // without losing autocompletion. + definedTestNames := { + definedTests + .map(_.map(_.name).distinct) + .storeAs(definedTestNames) + .triggeredBy(loadedTestFrameworks) + .value } ) diff --git a/scripted-tests/run/hello-scala-app/build.sbt b/scripted-tests/run/hello-scala-app/build.sbt index f8e01b94ff..9bd1d8398e 100644 --- a/scripted-tests/run/hello-scala-app/build.sbt +++ b/scripted-tests/run/hello-scala-app/build.sbt @@ -9,13 +9,3 @@ scalaVersion := { ) else scalaVersion } - -// Old versions of sbt (like 1.1.6 which is being used) don't include -// Scala version specific directiories and has problem with finding files in them -Compile / sources ++= { - CrossVersion.partialVersion(scalaVersion.value) match { - case Some((2, _)) => - sourceDirectory.value / "main" / "scala-2" / "HelloScalaApp.scala" :: Nil - case _ => Nil - } -} diff --git a/scripts/portScalaJsSource.scala b/scripts/portScalaJsSource.scala new file mode 100755 index 0000000000..38c5efc0df --- /dev/null +++ b/scripts/portScalaJsSource.scala @@ -0,0 +1,78 @@ +#!/usr/bin/env -S scala-cli shebang +//> using lib "org.virtuslab::toolkit-alpha::0.1.13" +//> using scala "3.2" + +// Combine with bash loop +// SJSPath="sjs-abs-path/unit-tests/shared/src/test/scala/org/scalanative" +// SNPath="sn-abs-path /test-suite/shared/src/test/scala/org/scalajs" +// for file in $(ls ${SJSPath}/javalib/util/function/* ); do +// ./scripts/portScalaJsSource.scala $SNPath $SJSPath $file; +// done + +import scala.util.chaining.scalaUtilChainingOps + +@main def portScalaJSSource( + scalaNativeAbsPath: os.Path, + scalaJSAbsPath: os.Path, + fileAbsPath: os.Path +) = + val relPath = fileAbsPath.relativeTo(scalaJSAbsPath) + val sjsPath = scalaJSAbsPath / relPath + val snPath = scalaNativeAbsPath / relPath + + def getShortSha(sjsFile: os.Path): String = + val format = "// Ported from Scala.js, commit SHA: %h dated: %as" + val out = os + .proc("git", "log", "-n1", s"--pretty=format:${format}", sjsFile) + .call(cwd = scalaJSAbsPath, check = true, stdout = os.Pipe) + out.out.text() + + println(s""" + |ScalaNative base dir: $scalaNativeAbsPath + |ScalaJS base dir: $scalaJSAbsPath + |Rel path: ${relPath} + |Porting ${sjsPath} into ${snPath} + """.stripMargin) + + assert( + os.exists(scalaNativeAbsPath), + "Scala Native directory does not exist" + ) + assert( + os.exists(scalaNativeAbsPath), + "Scala JS directory does not exists" + ) + assert(os.exists(sjsPath), s"ScalaJS file does not exits ${sjsPath}") + if os.exists(snPath) + then println(s"ScalaNative file alread exists ${snPath}, skipping") + else + val revisionMessage = getShortSha(sjsPath) + os.write(snPath, revisionMessage + "\n", createFolders = true) + val sjsSource = os + .read(sjsPath) + .pipe(stripHeader) + os.write.append(snPath, sjsSource) + os.write.append(snPath, System.lineSeparator()) + +private def stripHeader(input: String) = { + val nl = System.lineSeparator() + val commentsCtrl = Seq("/*", "*/", "*", "//") + input + .split(nl) + .dropWhile { line => + val trimmed = line.trim() + trimmed.isEmpty || commentsCtrl.exists(trimmed.startsWith(_)) + } + .mkString(nl) +} + +import scala.util.CommandLineParser.FromString +private given FromString[os.Path] = { str => + val nio = java.nio.file.Paths.get(str) + os.Path(nio.toRealPath()) +} + +private given FromString[os.RelPath] = { str => + val nio = java.nio.file.Paths.get(str) + os.RelPath(nio) +} diff --git a/test-interface/src/main/scala/scala/scalanative/testinterface/signalhandling/SignalConfig.scala b/test-interface/src/main/scala/scala/scalanative/testinterface/signalhandling/SignalConfig.scala index 900bc776a9..c8bf69963b 100644 --- a/test-interface/src/main/scala/scala/scalanative/testinterface/signalhandling/SignalConfig.scala +++ b/test-interface/src/main/scala/scala/scalanative/testinterface/signalhandling/SignalConfig.scala @@ -1,6 +1,6 @@ package scala.scalanative.testinterface.signalhandling -import scala.scalanative.meta.LinktimeInfo.isWindows +import scala.scalanative.meta.LinktimeInfo._ import scala.scalanative.libc.stdlib._ import scala.scalanative.libc.signal._ import scala.scalanative.libc.string._ @@ -69,22 +69,21 @@ private[testinterface] object SignalConfig { str } - val stackTraceHeader: Ptr[CChar] = stackalloc[CChar](2048.toUInt) - stackTraceHeader(0.toUInt) = 0.toByte + val stackTraceHeader: Ptr[CChar] = stackalloc[CChar](100.toUInt) strcat(stackTraceHeader, errorTag) strcat(stackTraceHeader, c" Fatal signal ") strcat(stackTraceHeader, signalNumberStr) strcat(stackTraceHeader, c" caught\n") printError(stackTraceHeader) - val cursor: Ptr[scala.Byte] = stackalloc[scala.Byte](2048.toUInt) - val context: Ptr[scala.Byte] = stackalloc[scala.Byte](2048.toUInt) + val cursor = stackalloc[Byte](unwind.sizeOfCursor) + val context = stackalloc[Byte](unwind.sizeOfContext) unwind.get_context(context) unwind.init_local(cursor, context) while (unwind.step(cursor) > 0) { - val offset: Ptr[scala.Byte] = stackalloc[scala.Byte](8.toUInt) - val pc = stackalloc[CUnsignedLongLong]() + val offset = stackalloc[Long]() + val pc = stackalloc[CSize]() unwind.get_reg(cursor, unwind.UNW_REG_IP, pc) if (!pc == 0.toUInt) return val symMax = 1024 @@ -96,11 +95,11 @@ private[testinterface] object SignalConfig { offset ) == 0) { sym(symMax - 1) = 0.toByte - val className: Ptr[CChar] = stackalloc[CChar](1024.toUInt) - val methodName: Ptr[CChar] = stackalloc[CChar](1024.toUInt) + val className: Ptr[CChar] = stackalloc[CChar](512.toUInt) + val methodName: Ptr[CChar] = stackalloc[CChar](512.toUInt) SymbolFormatter.asyncSafeFromSymbol(sym, className, methodName) - val formattedSymbol: Ptr[CChar] = stackalloc[CChar](2048.toUInt) + val formattedSymbol: Ptr[CChar] = stackalloc[CChar](1100.toUInt) formattedSymbol(0) = 0.toByte strcat(formattedSymbol, errorTag) strcat(formattedSymbol, c" at ") diff --git a/test-runner/src/main/scala/scala/scalanative/testinterface/ProcessRunner.scala b/test-runner/src/main/scala/scala/scalanative/testinterface/ProcessRunner.scala index 88ee19e361..3ff8e4d6fe 100644 --- a/test-runner/src/main/scala/scala/scalanative/testinterface/ProcessRunner.scala +++ b/test-runner/src/main/scala/scala/scalanative/testinterface/ProcessRunner.scala @@ -54,7 +54,7 @@ private[testinterface] class ProcessRunner( else { runnerPromise.tryFailure( new RuntimeException( - s"Process $executableFile finished with non-zero value $exitCode" + s"Process $executableFile finished with non-zero value $exitCode (0x${exitCode.toHexString})" ) ) // Similarly to Bash programs, exitcode values higher diff --git a/tools/src/main/scala/scala/scalanative/build/Build.scala b/tools/src/main/scala/scala/scalanative/build/Build.scala index ee17f6f913..292377d2ea 100644 --- a/tools/src/main/scala/scala/scalanative/build/Build.scala +++ b/tools/src/main/scala/scala/scalanative/build/Build.scala @@ -76,7 +76,8 @@ object Build { // optimize and generate ll val generated = { val optimized = ScalaNative.optimize(fconfig, linked) - ScalaNative.codegen(fconfig, optimized) + ScalaNative.codegen(fconfig, optimized) ++: + ScalaNative.genBuildInfo(fconfig) // ident list may be empty } val objectPaths = config.logger.time("Compiling to native code") { diff --git a/tools/src/main/scala/scala/scalanative/build/Config.scala b/tools/src/main/scala/scala/scalanative/build/Config.scala index 7655a8391f..4ff29f4900 100644 --- a/tools/src/main/scala/scala/scalanative/build/Config.scala +++ b/tools/src/main/scala/scala/scalanative/build/Config.scala @@ -88,6 +88,22 @@ sealed trait Config { compilerConfig.targetTriple.exists { customTriple => Seq("mac", "apple", "darwin").exists(customTriple.contains(_)) } + + private[scalanative] lazy val targetsMsys: Boolean = { + compilerConfig.targetTriple.fold(Platform.isMsys) { customTriple => + customTriple.contains("windows-msys") + } + } + private[scalanative] lazy val targetsCygwin: Boolean = { + compilerConfig.targetTriple.fold(Platform.isCygwin) { customTriple => + customTriple.contains("windows-cygnus") + } + } + + private[scalanative] lazy val targetsLinux: Boolean = Platform.isLinux || + compilerConfig.targetTriple.exists { customTriple => + Seq("linux").exists(customTriple.contains(_)) + } } object Config { diff --git a/tools/src/main/scala/scala/scalanative/build/Discover.scala b/tools/src/main/scala/scala/scalanative/build/Discover.scala index 2a09829dbb..56bff1a781 100644 --- a/tools/src/main/scala/scala/scalanative/build/Discover.scala +++ b/tools/src/main/scala/scala/scalanative/build/Discover.scala @@ -91,41 +91,56 @@ object Discover { libs } + private case class ClangInfo( + majorVersion: Int, + fullVersion: String, + targetTriple: String + ) + private def clangInfo(implicit config: NativeConfig): ClangInfo = + cache("clang-info")(config => clangInfo(config.clang)) + + private def clangInfo(clang: Path): ClangInfo = { + val versionCommand = Seq(clang.abs, "--version") + val cmdString = versionCommand.mkString(" ") + val processLines = Process(versionCommand) + .lineStream_!(silentLogger()) + .toList + + val (versionString, targetString) = processLines match { + case version :: target :: _ => (version, target) + case _ => + throw new BuildException( + s"""Problem running '$cmdString'. Please check clang setup. + |Refer to ($docSetup)""".stripMargin + ) + } + + // Apple macOS clang is different vs brew installed or Linux + // Apple LLVM version 10.0.1 (clang-1001.0.46.4) + // clang version 11.0.0 + try { + val versionArray = versionString.split(" ") + val versionIndex = versionArray.indexWhere(_.equals("version")) + val version = versionArray(versionIndex + 1) + ClangInfo( + majorVersion = version.takeWhile(_.isDigit).toInt, + fullVersion = version, + targetTriple = targetString.drop("Target: ".size) + ) + } catch { + case t: Throwable => + throw new BuildException(s"""Output from '$versionCommand' unexpected. + |Was expecting '... version n.n.n ...'. + |Got '$versionString'. + |Cause: ${t}""".stripMargin) + } + } + /** Tests whether the clang compiler is greater or equal to the minumum * version required. */ private[scalanative] def checkClangVersion(pathToClangBinary: Path): Unit = { - def versionMajorFull(clang: String): (Int, String) = { - val versionCommand = Seq(clang, "--version") - val versionString = Process(versionCommand) - .lineStream_!(silentLogger()) - .headOption - .getOrElse { - throw new BuildException( - s"""Problem running '${versionCommand - .mkString(" ")}'. Please check clang setup. - |Refer to ($docSetup)""".stripMargin - ) - } - // Apple macOS clang is different vs brew installed or Linux - // Apple LLVM version 10.0.1 (clang-1001.0.46.4) - // clang version 11.0.0 - try { - val versionArray = versionString.split(" ") - val versionIndex = versionArray.indexWhere(_.equals("version")) - val version = versionArray(versionIndex + 1) - val majorVersion = version.split("\\.").head - (majorVersion.toInt, version) - } catch { - case t: Throwable => - throw new BuildException(s"""Output from '$versionCommand' unexpected. - |Was expecting '... version n.n.n ...'. - |Got '$versionString'. - |Cause: ${t}""".stripMargin) - } - } - - val (majorVersion, version) = versionMajorFull(pathToClangBinary.abs) + val ClangInfo(majorVersion, version, _) = clangInfo(pathToClangBinary) if (majorVersion < clangMinVersion) { throw new BuildException( @@ -178,9 +193,89 @@ object Discover { path } + /** Detect the target architecture. + * + * @param clang + * A path to the executable `clang`. + * @return + * The detected target triple describing the target architecture. + */ + def targetTriple(clang: Path): String = clangInfo(clang).targetTriple + + def targetTriple(implicit config: NativeConfig) = cache("target-triple") { + _ => clangInfo.targetTriple + } + private def silentLogger(): ProcessLogger = ProcessLogger(_ => (), _ => ()) private def getenv(key: String): Option[String] = Option(System.getenv.get(key)) + + private object cache extends ContextBasedCache[NativeConfig, String, AnyRef] + + private[scalanative] object features { + import FeatureSupport._ + + def opaquePointers(implicit config: NativeConfig): FeatureSupport = + cache("opaque-pointers") { _ => + try { + val version = clangInfo.majorVersion + // if version == 13 EnabledWithFlag("--force-opaque-pointers"): works on Unix and probably on Homebrew Clang; on Apple Clang missing or exists with different name + // if version == 14 EnabledWithFlag("--opaque-pointers"): might require additional flag `--plugin-opt=opaque-pointers` to ld.lld linker on Unix, this opt is missing on ld64.lld in MacOS + if (version < 15) Unavailable + else Enabled + } catch { + case ex: Exception => + System.err.println( + "Failed to detect version of clang, assuming opaque-pointers are not supported" + ) + Unavailable + } + } + + sealed trait FeatureSupport { + def isAvailable: Boolean = this match { + case Unavailable => false + case _ => true + } + def requiredFlag: Option[String] = this match { + case EnabledWithFlag(flag) => Some(flag) + case _ => None + } + } + object FeatureSupport { + case object Unavailable extends FeatureSupport + case object Enabled extends FeatureSupport + case class EnabledWithFlag(compilationFlag: String) extends FeatureSupport + } + } + + class ContextBasedCache[Ctx, Key, Value <: AnyRef] { + private val cachedValues = scala.collection.mutable.Map.empty[Key, Value] + private var lastContext: Ctx = _ + + def apply[T <: Value: reflect.ClassTag]( + key: Key + )(resolve: Ctx => T)(implicit context: Ctx): T = { + lastContext match { + case `context` => + val result = cachedValues.getOrElseUpdate(key, resolve(context)) + // Make sure stored value has correct type in case of duplicate keys + val expectedType = implicitly[reflect.ClassTag[T]].runtimeClass + assert( + expectedType.isAssignableFrom(result.getClass), + s"unexpected type of result for entry: `$key`, got ${result + .getClass()}, expected $expectedType" + ) + result.asInstanceOf[T] + + case _ => + // Context have changed + cachedValues.clear() + lastContext = context + this(key)(resolve) // retry with cleaned cached + } + } + } } diff --git a/tools/src/main/scala/scala/scalanative/build/LLVM.scala b/tools/src/main/scala/scala/scalanative/build/LLVM.scala index 36555519d7..c36aa05a0c 100644 --- a/tools/src/main/scala/scala/scalanative/build/LLVM.scala +++ b/tools/src/main/scala/scala/scalanative/build/LLVM.scala @@ -58,7 +58,7 @@ private[scalanative] object LLVM { val compiler = if (isCpp) config.clangPP.abs else config.clang.abs val stdflag = { - if (isLl) Seq.empty + if (isLl) llvmIrFeatures else if (isCpp) { // C++14 or newer standard is needed to compile code using Windows API // shipped with Windows 10 / Server 2016+ (we do not plan supporting older versions) @@ -67,8 +67,11 @@ private[scalanative] object LLVM { } else Seq("-std=gnu11") } val platformFlags = { - if (config.targetsWindows) Seq("-g") - else Nil + if (config.targetsWindows) { + val common = Seq("-g") // needed for debug symbols in stack traces + val optional = if (config.targetsMsys) msysExtras else Nil + common ++ optional + } else Nil } val exceptionsHandling = { val opt = if (isCpp) List("-fcxx-exceptions") else Nil @@ -82,12 +85,14 @@ private[scalanative] object LLVM { val compilec: Seq[String] = Seq(compiler, "-c", inpath, "-o", outpath) ++ flags + // compile config.logger.running(compilec) val result = Process(compilec, config.workdir.toFile) ! Logger.toProcessLogger(config.logger) if (result != 0) { throw new BuildException(s"Failed to compile ${inpath}") } + objPath } @@ -275,6 +280,14 @@ private[scalanative] object LLVM { case Mode.ReleaseFull => "-O3" } + private def llvmIrFeatures(implicit config: Config): Seq[String] = { + implicit def nativeConfig: NativeConfig = config.compilerConfig + val opaquePointers = Discover.features.opaquePointers.requiredFlag.toList + .flatMap(Seq("-mllvm", _)) + + opaquePointers + } + private def buildTargetCompileOpts(implicit config: Config): Seq[String] = config.compilerConfig.buildTarget match { case BuildTarget.Application => @@ -308,4 +321,45 @@ private[scalanative] object LLVM { else str } + lazy val msysExtras = Seq( + "-D_WIN64", + "-D__MINGW64__", + "-D_X86_64_ -D__X86_64__ -D__x86_64", + "-D__USING_SJLJ_EXCEPTIONS__", + "-DNO_OLDNAMES", + "-D_LIBUNWIND_BUILD_ZERO_COST_APIS" + ) + + private[scalanative] def generateLLVMIdent(config: Config): Seq[Path] = { + def constructIdent: String = { + val snVersion = scala.scalanative.nir.Versions.current + + val ident1 = s"Scala Native ${snVersion}" + val ident2 = s"Mode: ${config.mode}, LTO: ${config.LTO}, GC: ${config.gc}" + + s"${ident1} (${ident2})" + } + + /* Enable feature only where known to work. Add to list as experience grows + * FreeBSD uses elf format so it _should_ work, but it has not been + * exercised. + */ + if (!config.targetsLinux) Seq.empty[Path] + else { + // From lld.llvm.org doc: readelf --string-dump .comment + val workDir = config.workdir + val identPath = workDir.resolve("ScalaNativeIdent.ll") + val ident = constructIdent + + val pw = new java.io.PrintWriter(identPath.toFile) // truncate if exists + + try { + pw.println("!llvm.ident = !{!0}") + pw.println(s"""!0 = !{!"${ident}"}""") + } finally pw.close() + + Seq(identPath) + } + } + } diff --git a/tools/src/main/scala/scala/scalanative/build/NativeConfig.scala b/tools/src/main/scala/scala/scalanative/build/NativeConfig.scala index 2fca4142e1..f2e162c2d8 100644 --- a/tools/src/main/scala/scala/scalanative/build/NativeConfig.scala +++ b/tools/src/main/scala/scala/scalanative/build/NativeConfig.scala @@ -185,8 +185,14 @@ object NativeConfig { def withCompileOptions(value: Seq[String]): NativeConfig = copy(compileOptions = value) - def withTargetTriple(value: Option[String]): NativeConfig = + def withTargetTriple(value: Option[String]): NativeConfig = { + val propertyName = "target.triple" + value match { + case Some(triple) => System.setProperty(propertyName, triple) + case None => System.clearProperty(propertyName) + } copy(targetTriple = value) + } def withTargetTriple(value: String): NativeConfig = { withTargetTriple(Some(value)) diff --git a/tools/src/main/scala/scala/scalanative/build/Platform.scala b/tools/src/main/scala/scala/scalanative/build/Platform.scala index 263a393ca1..1c2f043769 100644 --- a/tools/src/main/scala/scala/scalanative/build/Platform.scala +++ b/tools/src/main/scala/scala/scalanative/build/Platform.scala @@ -11,4 +11,21 @@ object Platform { lazy val isUnix: Boolean = isLinux || osUsed.contains("unix") lazy val isMac: Boolean = osUsed.contains("mac") lazy val isFreeBSD: Boolean = osUsed.contains("freebsd") + + /** Test for the target type + * + * @return + * true if `msys`, false otherwise + */ + lazy val isMsys: Boolean = target.endsWith("msys") + + /** Test for the target type + * + * @return + * true if `cygnus`, false otherwise + */ + lazy val isCygwin: Boolean = target.endsWith("cygnus") + + private lazy val target = + System.getProperty("target.triple", "unknown").toLowerCase(Locale.ROOT) } diff --git a/tools/src/main/scala/scala/scalanative/build/core/IO.scala b/tools/src/main/scala/scala/scalanative/build/core/IO.scala index c6a6d74af2..ec499aa390 100644 --- a/tools/src/main/scala/scala/scalanative/build/core/IO.scala +++ b/tools/src/main/scala/scala/scalanative/build/core/IO.scala @@ -22,7 +22,12 @@ import java.security.{DigestInputStream, MessageDigest} private[scalanative] object IO { implicit class RichPath(val path: Path) extends AnyVal { - def abs: String = path.toAbsolutePath.toString + def abs: String = path.toAbsolutePath.toString.norm + } + implicit class RichString(val s: String) extends AnyVal { + // commands issued in shell environments require forward slash + // clang and llvm command line tools accept forward slash + def norm: String = s.replace('\\', '/') } /** Write bytes to given file. */ diff --git a/tools/src/main/scala/scala/scalanative/build/core/ScalaNative.scala b/tools/src/main/scala/scala/scalanative/build/core/ScalaNative.scala index 6071c9d536..0b9a6961d6 100644 --- a/tools/src/main/scala/scala/scalanative/build/core/ScalaNative.scala +++ b/tools/src/main/scala/scala/scalanative/build/core/ScalaNative.scala @@ -6,6 +6,7 @@ import java.nio.file.{Path, Files} import scala.collection.mutable import scala.scalanative.checker.Check import scala.scalanative.codegen.CodeGen +import scala.scalanative.interflow.Interflow import scala.scalanative.linker.Link import scala.scalanative.nir._ import scala.scalanative.util.Scope @@ -18,7 +19,8 @@ private[scalanative] object ScalaNative { */ def entries(config: Config): Seq[Global] = { val entry = encodedMainClass(config).map(_.member(Rt.ScalaMainSig)) - entry ++: CodeGen.depends + val dependencies = CodeGen.depends ++ Interflow.depends + entry ++: dependencies } /** Given the classpath and main entry point, link under closed-world @@ -185,4 +187,7 @@ private[scalanative] object ScalaNative { Global.Top(encoded) } + def genBuildInfo(config: Config): Seq[java.nio.file.Path] = + LLVM.generateLLVMIdent(config) + } diff --git a/tools/src/main/scala/scala/scalanative/codegen/AbstractCodeGen.scala b/tools/src/main/scala/scala/scalanative/codegen/AbstractCodeGen.scala index 87046f6aae..3cd0878259 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/AbstractCodeGen.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/AbstractCodeGen.scala @@ -3,6 +3,7 @@ package scala.scalanative.codegen import java.nio.file.{Path, Paths} import java.{lang => jl} import scala.collection.mutable +import scala.scalanative.build.Discover import scala.scalanative.codegen.compat.os.OsCompat import scala.scalanative.io.VirtualDirectory import scala.scalanative.nir.ControlFlow.{Block, Graph => CFG} @@ -12,13 +13,14 @@ import scala.scalanative.util.{ShowBuilder, unreachable, unsupported} import scala.scalanative.{build, linker, nir} private[codegen] abstract class AbstractCodeGen( - val config: build.Config, env: Map[Global, Defn], defns: Seq[Defn] -)(implicit meta: Metadata) { - val os: OsCompat +)(implicit val meta: Metadata) { + import meta.platform + import platform._ - private val targetTriple: Option[String] = config.compilerConfig.targetTriple + val os: OsCompat + val pointerType = if (useOpaquePointers) "ptr" else "i8*" private var currentBlockName: Local = _ private var currentBlockSplit: Int = _ @@ -174,7 +176,7 @@ private[codegen] abstract class AbstractCodeGen( newline() str(if (isDecl) "declare " else "define ") - if (config.targetsWindows && !isDecl && attrs.isExtern) { + if (targetsWindows && !isDecl && attrs.isExtern) { // Generate export modifier only for extern (C-ABI compliant) signatures val Global.Member(_, sig) = name: @unchecked if (sig.isExtern) str("dllexport ") @@ -227,14 +229,12 @@ private[codegen] abstract class AbstractCodeGen( private[codegen] def genFunctionReturnType( retty: Type - )(implicit sb: ShowBuilder): Unit = { - retty match { - case refty: Type.RefKind => - genReferenceTypeAttribute(refty) - case _ => - () - } - genType(retty) + )(implicit sb: ShowBuilder): Unit = retty match { + case refty: Type.RefKind if refty != Type.Unit => + genReferenceTypeAttribute(refty) + genType(retty) + case _ => + genType(retty) } private[codegen] def genReferenceTypeAttribute( @@ -308,6 +308,7 @@ private[codegen] abstract class AbstractCodeGen( if (!block.isEntry) { val params = block.params params.zipWithIndex.foreach { + case (Val.Local(name, Type.Unit), n) => () // skip case (Val.Local(name, ty), n) => newline() str("%") @@ -364,9 +365,11 @@ private[codegen] abstract class AbstractCodeGen( private[codegen] def genType(ty: Type)(implicit sb: ShowBuilder): Unit = { import sb._ ty match { - case Type.Vararg => str("...") - case _: Type.RefKind | Type.Ptr | Type.Null | Type.Nothing => str("i8*") - case Type.Bool => str("i1") + case Type.Vararg => str("...") + case Type.Unit => str("void") + case _: Type.RefKind | Type.Ptr | Type.Null | Type.Nothing => + str(pointerType) + case Type.Bool => str("i1") case i: Type.I => str("i"); str(i.width) case Type.Float => str("float") case Type.Double => str("double") @@ -423,6 +426,7 @@ private[codegen] abstract class AbstractCodeGen( case Val.True => str("true") case Val.False => str("false") case Val.Null => str("null") + case Val.Unit => str("void") case Val.Zero(ty) => str("zeroinitializer") case Val.Byte(v) => str(v) case Val.Char(v) => str(v.toInt) @@ -445,11 +449,17 @@ private[codegen] abstract class AbstractCodeGen( str("%") genLocal(n) case Val.Global(n, ty) => - str("bitcast (") - genType(lookup(n)) - str("* @") - genGlobal(n) - str(" to i8*)") + if (useOpaquePointers) { + lookup(n) + str("@") + genGlobal(n) + } else { + str("bitcast (") + genType(lookup(n)) + str("* @") + genGlobal(n) + str(" to i8*)") + } case _ => unsupported(v) } @@ -492,8 +502,10 @@ private[codegen] abstract class AbstractCodeGen( private[codegen] def genVal(value: Val)(implicit sb: ShowBuilder): Unit = { import sb._ - genType(value.ty) - str(" ") + if (value != Val.Unit) { + genType(value.ty) + str(" ") + } genJustVal(value) } @@ -649,23 +661,28 @@ private[codegen] abstract class AbstractCodeGen( case Op.Load(ty, ptr) => val pointee = fresh() - newline() - str("%") - genLocal(pointee) - str(" = bitcast ") - genVal(ptr) - str(" to ") - genType(ty) - str("*") + if (!useOpaquePointers) { + newline() + str("%") + genLocal(pointee) + str(" = bitcast ") + genVal(ptr) + str(" to ") + genType(ty) + str("*") + } newline() genBind() str("load ") genType(ty) str(", ") - genType(ty) - str("* %") - genLocal(pointee) + if (useOpaquePointers) genVal(ptr) + else { + genType(ty) + str("* %") + genLocal(pointee) + } ty match { case refty: Type.RefKind => val (nonnull, deref, size) = toDereferenceable(refty) @@ -684,76 +701,106 @@ private[codegen] abstract class AbstractCodeGen( case Op.Store(ty, ptr, value) => val pointee = fresh() - newline() - str("%") - genLocal(pointee) - str(" = bitcast ") - genVal(ptr) - str(" to ") - genType(ty) - str("*") + if (!useOpaquePointers) { + newline() + str("%") + genLocal(pointee) + str(" = bitcast ") + genVal(ptr) + str(" to ") + genType(ty) + str("*") + } newline() genBind() str("store ") genVal(value) - str(", ") - genType(ty) - str("* %") - genLocal(pointee) + if (useOpaquePointers) { + str(", ptr") + genJustVal(ptr) + } else { + str(", ") + genType(ty) + str("* %") + genLocal(pointee) + } + str(", align ") + str(MemoryLayout.alignmentOf(ty)) case Op.Elem(ty, ptr, indexes) => val pointee = fresh() val derived = fresh() - newline() - str("%") - genLocal(pointee) - str(" = bitcast ") - genVal(ptr) - str(" to ") - genType(ty) - str("*") + if (!useOpaquePointers) { + newline() + str("%") + genLocal(pointee) + str(" = bitcast ") + genVal(ptr) + str(" to ") + genType(ty) + str("*") + } newline() - str("%") - genLocal(derived) - str(" = getelementptr ") + if (useOpaquePointers) genBind() + else { + str("%") + genLocal(derived) + str(" = ") + } + str("getelementptr ") genType(ty) str(", ") - genType(ty) - str("* %") - genLocal(pointee) + if (ty.isInstanceOf[Type.AggregateKind] || !useOpaquePointers) { + genType(ty) + str("*") + } else str(pointerType) + str(" ") + if (useOpaquePointers) genJustVal(ptr) + else { + str("%") + genLocal(pointee) + } str(", ") rep(indexes, sep = ", ")(genVal) - newline() - genBind() - str("bitcast ") - genType(ty.elemty(indexes.tail)) - str("* %") - genLocal(derived) - str(" to i8*") + if (!useOpaquePointers) { + newline() + genBind() + str("bitcast ") + genType(ty.elemty(indexes.tail)) + str("* %") + genLocal(derived) + str(" to i8*") + } case Op.Stackalloc(ty, n) => val pointee = fresh() newline() - str("%") - genLocal(pointee) - str(" = alloca ") + if (useOpaquePointers) genBind() + else { + str("%") + genLocal(pointee) + str(" = ") + } + str("alloca ") genType(ty) str(", ") genVal(n) str(", align 8") - newline() - genBind() - str("bitcast ") - genType(ty) - str("* %") - genLocal(pointee) - str(" to i8*") + if (!useOpaquePointers) { + newline() + genBind() + str("bitcast ") + genType(ty) + str("* %") + genLocal(pointee) + str(" to i8*") + } case _ => newline() @@ -800,21 +847,27 @@ private[codegen] abstract class AbstractCodeGen( val pointee = fresh() - newline() - str("%") - genLocal(pointee) - str(" = bitcast ") - genVal(ptr) - str(" to ") - genType(ty) - str("*") + if (!useOpaquePointers) { + newline() + str("%") + genLocal(pointee) + str(" = bitcast ") + genVal(ptr) + str(" to ") + genType(ty) + str("*") + } newline() genBind() str(if (unwind ne Next.None) "invoke " else "call ") genCallFunctionType(ty) - str(" %") - genLocal(pointee) + str(" ") + if (useOpaquePointers) genJustVal(ptr) + else { + str("%") + genLocal(pointee) + } str("(") rep(args, sep = ", ")(genCallArgument) str(")") @@ -856,7 +909,9 @@ private[codegen] abstract class AbstractCodeGen( v match { case Val.Local(_, refty: Type.RefKind) => val (nonnull, deref, size) = toDereferenceable(refty) - genType(refty) + // Primitive unit value cannot be passed as argument, probably BoxedUnit is expected + if (refty == Type.Unit) genType(Type.Ptr) + else genType(refty) if (nonnull) { str(" nonnull") } diff --git a/tools/src/main/scala/scala/scalanative/codegen/CodeGen.scala b/tools/src/main/scala/scala/scalanative/codegen/CodeGen.scala index d2abe79af9..ac637b029a 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/CodeGen.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/CodeGen.scala @@ -19,6 +19,7 @@ object CodeGen { val defns = linked.defns val proxies = GenerateReflectiveProxies(linked.dynimpls, defns) + implicit val platform: PlatformInfo = PlatformInfo(config) implicit val meta: Metadata = new Metadata(linked, proxies) val generated = Generate(encodedMainClass(config), defns ++ proxies) @@ -58,7 +59,7 @@ object CodeGen { .map { case (id, defns) => val sorted = defns.sortBy(_.name.show) - Impl(config, env, sorted).gen(id.toString, workdir) + Impl(env, sorted).gen(id.toString, workdir) } .toSeq .seq @@ -91,7 +92,7 @@ object CodeGen { val sorted = defns.sortBy(_.name.show) if (!Files.exists(ownerDirectory)) Files.createDirectories(ownerDirectory) - Impl(config, env, sorted).gen(packagePath, workdir) + Impl(env, sorted).gen(packagePath, workdir) } else { assert(ownerDirectory.toFile.exists()) config.logger.debug( @@ -114,7 +115,7 @@ object CodeGen { // Clang's LTO is not available. def single(): Seq[Path] = { val sorted = assembly.sortBy(_.name.show) - Impl(config, env, sorted).gen(id = "out", workdir) :: Nil + Impl(env, sorted).gen(id = "out", workdir) :: Nil } import build.Mode._ @@ -132,12 +133,12 @@ object CodeGen { import scala.scalanative.codegen.AbstractCodeGen import scala.scalanative.codegen.compat.os._ - def apply(config: Config, env: Map[Global, Defn], defns: Seq[Defn])(implicit + def apply(env: Map[Global, Defn], defns: Seq[Defn])(implicit meta: Metadata ): AbstractCodeGen = { - new AbstractCodeGen(config, env, defns) { + new AbstractCodeGen(env, defns) { override val os: OsCompat = { - if (this.config.targetsWindows) new WindowsCompat(this) + if (this.meta.platform.targetsWindows) new WindowsCompat(this) else new UnixCompat(this) } } diff --git a/tools/src/main/scala/scala/scalanative/codegen/CommonMemoryLayouts.scala b/tools/src/main/scala/scala/scalanative/codegen/CommonMemoryLayouts.scala new file mode 100644 index 0000000000..f8c76dd8ff --- /dev/null +++ b/tools/src/main/scala/scala/scalanative/codegen/CommonMemoryLayouts.scala @@ -0,0 +1,72 @@ +package scala.scalanative.codegen + +import scala.scalanative.nir._ + +class CommonMemoryLayouts(implicit meta: Metadata) { + sealed trait Layout { + val layout: Type.StructValue + lazy val fields: Int = layout.tys.size + } + + object Rtti extends Layout { + val layout = Type.StructValue( + Type.Ptr :: // ClassRtti + Type.Int :: // ClassId + Type.Int :: // Traitid + Type.Ptr :: // ClassName + Nil + ) + final val RttiIdx = 0 + final val ClassIdIdx = RttiIdx + 1 + final val TraitIdIdx = ClassIdIdx + 1 + final val ClassNameIdx = TraitIdIdx + 1 + } + + // RTTI specific for classess, see class RuntimeTypeInformation + object ClassRtti extends Layout { + val usesDynMap = meta.linked.dynsigs.nonEmpty + private val dynMapType = if (usesDynMap) Some(DynamicHashMap.ty) else None + // Common layout not including variable-sized virtual table + private val baseLayout = + Rtti.layout :: + Type.Int :: // class size + Type.Int :: // id range + FieldLayout.referenceOffsetsTy :: // reference offsets + dynMapType.toList + + val layout = genLayout(vtable = Type.ArrayValue(Type.Ptr, 0)) + + def genLayout(vtable: Type): Type.StructValue = Type.StructValue( + baseLayout ::: vtable :: Nil + ) + + final val RttiIdx = 0 + final val SizeIdx = RttiIdx + 1 + final val IdRangeIdx = SizeIdx + 1 + final val ReferenceOffsetsIdx = IdRangeIdx + 1 + final val DynmapIdx = + if (usesDynMap) ReferenceOffsetsIdx + 1 else -1 + final val VtableIdx = + (if (usesDynMap) DynmapIdx else ReferenceOffsetsIdx) + 1 + } + + object ObjectHeader extends Layout { + val layout = Type.StructValue( + Type.Ptr :: // RTTI + Nil + ) + } + + object ArrayHeader extends Layout { + val layout = Type.StructValue( + Type.Ptr :: // RTTI + Type.Int :: // length + Type.Int :: // stride (used only by GC) + Nil + ) + + final val RttiIdx = 0 + final val LengthIdx = RttiIdx + 1 + final val StrideIdx = LengthIdx + 1 + } +} diff --git a/tools/src/main/scala/scala/scalanative/codegen/DynamicHashMap.scala b/tools/src/main/scala/scala/scalanative/codegen/DynamicHashMap.scala index fd7e3aeb73..230e437562 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/DynamicHashMap.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/DynamicHashMap.scala @@ -4,7 +4,11 @@ package codegen import scalanative.nir._ import scalanative.linker.{Class, Method} -class DynamicHashMap(meta: Metadata, cls: Class, proxies: Seq[Defn]) { +object DynamicHashMap { + final val ty: Type = Type.Ptr +} + +class DynamicHashMap(cls: Class, proxies: Seq[Defn])(implicit meta: Metadata) { val methods: Seq[Global.Member] = { val own = proxies.collect { case p if p.name.top == cls.name => @@ -15,8 +19,5 @@ class DynamicHashMap(meta: Metadata, cls: Class, proxies: Seq[Defn]) { .fold(Seq.empty[Global.Member])(meta.dynmap(_).methods) .filterNot(m => sigs.contains(m.sig)) ++ own } - val ty: Type = - Type.Ptr - val value: Val = - DynmethodPerfectHashMap(methods, meta.linked.dynsigs) + val value: Val = DynmethodPerfectHashMap(methods, meta.linked.dynsigs) } diff --git a/tools/src/main/scala/scala/scalanative/codegen/FieldLayout.scala b/tools/src/main/scala/scala/scalanative/codegen/FieldLayout.scala index 0e85018dd4..4d1130eec7 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/FieldLayout.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/FieldLayout.scala @@ -4,9 +4,15 @@ package codegen import scalanative.nir._ import scalanative.linker.{Class, Field} -class FieldLayout(meta: Metadata, cls: Class) { - def index(fld: Field) = - entries.indexOf(fld) + 1 +object FieldLayout { + val referenceOffsetsTy = Type.StructValue(Seq(Type.Ptr)) +} + +class FieldLayout(cls: Class)(implicit meta: Metadata) { + import meta.layouts.ObjectHeader + import meta.platform + + def index(fld: Field) = entries.indexOf(fld) + ObjectHeader.fields val entries: Seq[Field] = { val base = cls.parent.fold { Seq.empty[Field] @@ -15,15 +21,11 @@ class FieldLayout(meta: Metadata, cls: Class) { } val struct: Type.StructValue = { val data = entries.map(_.ty) - val body = Type.Ptr +: data - Type.StructValue(body) + Type.StructValue(ObjectHeader.layout +: data) } val layout = MemoryLayout(struct.tys) val size = layout.size - val referenceOffsetsTy = - Type.StructValue(Seq(Type.Ptr)) - val referenceOffsetsValue = - Val.StructValue( - Seq(Val.Const(Val.ArrayValue(Type.Long, layout.offsetArray))) - ) + val referenceOffsetsValue = Val.StructValue( + Seq(Val.Const(Val.ArrayValue(Type.Long, layout.offsetArray))) + ) } diff --git a/tools/src/main/scala/scala/scalanative/codegen/Generate.scala b/tools/src/main/scala/scala/scalanative/codegen/Generate.scala index a8ba839be3..281ed2c15d 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/Generate.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/Generate.scala @@ -276,7 +276,7 @@ object Generate { val instanceDefn = Defn.Const( Attrs.None, instanceName, - Type.StructValue(Seq(Type.Ptr)), + meta.layouts.ObjectHeader.layout, instanceVal ) diff --git a/tools/src/main/scala/scala/scalanative/codegen/GenerateReflectiveProxies.scala b/tools/src/main/scala/scala/scalanative/codegen/GenerateReflectiveProxies.scala index 66281e67e8..7d252dabf0 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/GenerateReflectiveProxies.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/GenerateReflectiveProxies.scala @@ -21,8 +21,7 @@ object GenerateReflectiveProxies { val unboxInsts = genArgUnboxes(label, defnType.args) val method = Inst.Let(Op.Method(label.params.head, sig), Next.None) val call = genCall(defnType, method, label.params, unboxInsts) - val box = genRetValBox(call.name, defnType.ret, proxyTy.ret) - val retInst = genRet(box.name, proxyTy.ret) + val retInsts = genRet(call.name, defnType.ret, proxyTy.ret) Defn.Define( Attrs.fromSeq(Seq(Attr.Dyn)), @@ -31,7 +30,8 @@ object GenerateReflectiveProxies { Seq( Seq(label), unboxInsts, - Seq(method, call, box, retInst) + Seq(method, call), + retInsts ).flatten ) } @@ -43,8 +43,8 @@ object GenerateReflectiveProxies { Type.Function( args, defnTy.ret match { - case Type.Unit => Type.Unit - case _ => Type.Ref(Global.Top("java.lang.Object")) + case Type.Unit => Rt.BoxedUnit + case _ => Rt.Object } ) @@ -105,7 +105,7 @@ object GenerateReflectiveProxies { implicit pos: nir.Position, fresh: Fresh - ) = + ): Inst.Let = Type.box.get(defnRetTy) match { case Some(boxTy) => Inst.Let(Op.Box(boxTy, Val.Local(callName, defnRetTy)), Next.None) @@ -113,13 +113,20 @@ object GenerateReflectiveProxies { Inst.Let(Op.Copy(Val.Local(callName, defnRetTy)), Next.None) } - private def genRet(retValBoxName: Local, proxyRetTy: Type)(implicit - pos: nir.Position - ) = - proxyRetTy match { - case Type.Unit => Inst.Ret(Val.Unit) - case _ => Inst.Ret(Val.Local(retValBoxName, proxyRetTy)) + private def genRet(callName: Local, defnRetTy: Type, proxyRetTy: Type)( + implicit + pos: nir.Position, + fresh: Fresh + ): Seq[Inst] = { + defnRetTy match { + case Type.Unit => + Inst.Ret(Val.Unit) :: Nil + case _ => + val box = genRetValBox(callName, defnRetTy, proxyRetTy) + val ret = Inst.Ret(Val.Local(box.name, proxyRetTy)) + Seq(box, ret) } + } def apply(dynimpls: Seq[Global], defns: Seq[Defn]): Seq[Defn.Define] = { diff --git a/tools/src/main/scala/scala/scalanative/codegen/Lower.scala b/tools/src/main/scala/scala/scalanative/codegen/Lower.scala index 30173b12aa..969a8159c8 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/Lower.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/Lower.scala @@ -4,17 +4,7 @@ package codegen import scala.collection.mutable import scalanative.util.{ScopedVar, unsupported} import scalanative.nir._ -import scalanative.linker.{ - Class, - Trait, - ScopeInfo, - ScopeRef, - ClassRef, - TraitRef, - FieldRef, - MethodRef, - Result -} +import scalanative.linker._ import scalanative.interflow.UseDef.eliminateDeadCode object Lower { @@ -24,11 +14,20 @@ object Lower { private final class Impl(implicit meta: Metadata) extends Transform { import meta._ + import meta.layouts.{Rtti, ClassRtti, ArrayHeader} implicit val linked: Result = meta.linked val Object = linked.infos(Rt.Object.name).asInstanceOf[Class] + private val zero = Val.Int(0) + private val one = Val.Int(1) + val RttiClassIdPath = Seq(zero, Val.Int(Rtti.ClassIdIdx)) + val RttiTraitIdPath = Seq(zero, Val.Int(Rtti.TraitIdIdx)) + val ClassRttiDynmapPath = Seq(zero, Val.Int(ClassRtti.DynmapIdx)) + val ClassRttiVtablePath = Seq(zero, Val.Int(ClassRtti.VtableIdx)) + val ArrayHeaderLengthPath = Seq(zero, Val.Int(ArrayHeader.LengthIdx)) + // Type of the bare runtime type information struct. private val classRttiType = rtti(linked.infos(Global.Top("java.lang.Object"))).struct @@ -43,6 +42,11 @@ object Lower { private val fresh = new util.ScopedVar[Fresh] private val unwindHandler = new util.ScopedVar[Option[Local]] + private val currentDefn = new util.ScopedVar[Defn.Define] + private def currentDefnRetType = { + val Type.Function(_, ret) = currentDefn.get.ty: @unchecked + ret + } private val unreachableSlowPath = mutable.Map.empty[Option[Local], Local] private val nullPointerSlowPath = mutable.Map.empty[Option[Local], Local] @@ -79,7 +83,8 @@ object Lower { case defn: Defn.Define => val Type.Function(_, ty) = defn.ty: @unchecked ScopedVar.scoped( - fresh := Fresh(defn.insts) + fresh := Fresh(defn.insts), + currentDefn := defn ) { super.onDefn(defn) } @@ -100,6 +105,17 @@ object Lower { } } + private def optionallyBoxedUnit(v: nir.Val)(implicit + pos: nir.Position + ): nir.Val = { + require( + v.ty == Type.Unit, + s"Definition is expected to return Unit type, found ${v.ty}" + ) + if (currentDefnRetType == Type.Unit) Val.Unit + else unit + } + override def onInsts(insts: Seq[Inst]): Seq[Inst] = { val buf = new nir.Buffer()(fresh) val handlers = new nir.Buffer()(fresh) @@ -121,11 +137,24 @@ object Lower { insts.foreach { case inst @ Inst.Let(n, Op.Var(ty), unwind) => - buf.let(n, Op.Stackalloc(ty, Val.Int(1)), unwind)(inst.pos) + buf.let(n, Op.Stackalloc(ty, one), unwind)(inst.pos) case _ => () } + val Inst.Label(firstLabel, _) = insts.head: @unchecked + val labelPositions = insts + .collect { case Inst.Label(id, _) => id } + .zipWithIndex + .toMap + var currentBlockPosition = labelPositions(firstLabel) + + genThisValueNullGuardIfUsed( + currentDefn.get, + buf, + () => newUnwindHandler(Next.None)(insts.head.pos) + ) + insts.tail.foreach { case inst @ Inst.Let(n, op, unwind) => ScopedVar.scoped( @@ -150,7 +179,10 @@ object Lower { case inst @ Inst.Ret(v) => implicit val pos: Position = inst.pos - buf += Inst.Ret(genVal(buf, v)) + val retVal = + if (v.ty == Type.Unit) optionallyBoxedUnit(v) + else genVal(buf, v) + buf += Inst.Ret(retVal) case inst @ Inst.Jump(next) => implicit val pos: Position = inst.pos @@ -177,7 +209,16 @@ object Lower { buf ++= handlers - eliminateDeadCode(buf.toSeq.map(super.onInst)) + eliminateDeadCode(buf.toSeq.map(onInst)) + } + + override def onInst(inst: Inst): Inst = { + implicit def pos: nir.Position = inst.pos + inst match { + case Inst.Ret(v) if v.ty == Type.Unit => + Inst.Ret(optionallyBoxedUnit(v)) + case _ => super.onInst(inst) + } } override def onVal(value: Val): Val = value match { @@ -377,7 +418,11 @@ object Lower { case Op.Varload(Val.Local(slot, Type.Var(ty))) => buf.let(n, Op.Load(ty, Val.Local(slot, Type.Ptr)), unwind) case Op.Varstore(Val.Local(slot, Type.Var(ty)), value) => - buf.let(n, Op.Store(ty, Val.Local(slot, Type.Ptr), value), unwind) + buf.let( + n, + Op.Store(ty, Val.Local(slot, Type.Ptr), genVal(buf, value)), + unwind + ) case op: Op.Arrayalloc => genArrayallocOp(buf, n, op) case op: Op.Arrayload => @@ -418,7 +463,7 @@ object Lower { val outOfBoundsL = outOfBoundsSlowPath.getOrElseUpdate(unwindHandler, fresh()) - val gt0 = comp(Comp.Sge, Type.Int, idx, Val.Int(0), unwind) + val gt0 = comp(Comp.Sge, Type.Int, idx, zero, unwind) val ltLen = comp(Comp.Slt, Type.Int, idx, len, unwind) val inBounds = bin(Bin.And, Type.Bool, gt0, ltLen, unwind) branch(inBounds, Next(inBoundsL), Next.Label(outOfBoundsL, Seq(idx))) @@ -437,7 +482,7 @@ object Lower { val index = layout.index(fld) genGuardNotNull(buf, v) - elem(ty, v, Seq(Val.Int(0), Val.Int(index)), unwind) + elem(ty, v, Seq(zero, Val.Int(index)), unwind) } def genFieldloadOp(buf: Buffer, n: Local, op: Op.Fieldload)(implicit @@ -517,7 +562,7 @@ object Lower { Op.Elem( rtti(cls).struct, typeptr, - meta.RttiVtableIndex :+ Val.Int(vindex) + ClassRttiVtablePath :+ Val.Int(vindex) ), unwind ) @@ -529,7 +574,7 @@ object Lower { val sigid = dispatchTable.traitSigIds(sig) val typeptr = let(Op.Load(Type.Ptr, obj), unwind) val idptr = - let(Op.Elem(meta.Rtti, typeptr, meta.RttiTraitIdIndex), unwind) + let(Op.Elem(Rtti.layout, typeptr, RttiTraitIdPath), unwind) val id = let(Op.Load(Type.Int, idptr), unwind) val rowptr = let( Op.Elem( @@ -630,7 +675,7 @@ object Lower { // Load the type information pointer val typeptr = load(Type.Ptr, obj, unwind) // Load the dynamic hash map for given type, make sure it's not null - val mapelem = elem(classRttiType, typeptr, meta.RttiDynmapIndex, unwind) + val mapelem = elem(classRttiType, typeptr, ClassRttiDynmapPath, unwind) val mapptr = load(Type.Ptr, mapelem, unwind) // If hash map is not null, it has to contain at least one entry throwIfNull(mapptr) @@ -694,7 +739,10 @@ object Lower { val range = meta.ranges(cls) val typeptr = let(Op.Load(Type.Ptr, obj), unwind) val idptr = - let(Op.Elem(meta.Rtti, typeptr, meta.RttiClassIdIndex), unwind) + let( + Op.Elem(Rtti.layout, typeptr, RttiClassIdPath), + unwind + ) val id = let(Op.Load(Type.Int, idptr), unwind) val ge = let(Op.Comp(Comp.Sle, Type.Int, Val.Int(range.start), id), unwind) @@ -705,13 +753,16 @@ object Lower { case TraitRef(trt) => val typeptr = let(Op.Load(Type.Ptr, obj), unwind) val idptr = - let(Op.Elem(meta.Rtti, typeptr, meta.RttiClassIdIndex), unwind) + let( + Op.Elem(Rtti.layout, typeptr, RttiClassIdPath), + unwind + ) val id = let(Op.Load(Type.Int, idptr), unwind) val boolptr = let( Op.Elem( hasTraitTables.classHasTraitTy, hasTraitTables.classHasTraitVal, - Seq(Val.Int(0), id, Val.Int(meta.ids(trt))) + Seq(zero, id, Val.Int(meta.ids(trt))) ), unwind ) @@ -746,7 +797,10 @@ object Lower { branch(isInstanceOf, Next(castL), Next.Label(failL, Seq(v, toTy))) label(castL) - let(n, Op.Conv(Conv.Bitcast, ty, v), unwind) + if (platform.useOpaquePointers) + let(n, Op.Copy(v), unwind) + else + let(n, Op.Conv(Conv.Bitcast, ty, v), unwind) case Op.As(to, v) => util.unsupported(s"can't cast from ${v.ty} to $to") @@ -758,7 +812,8 @@ object Lower { ): Unit = { val Op.Sizeof(ty) = op - buf.let(n, Op.Copy(Val.Long(MemoryLayout.sizeOf(ty))), unwind) + val memorySize = MemoryLayout.sizeOf(ty) + buf.let(n, Op.Copy(Val.Long(memorySize)), unwind) } def genClassallocOp(buf: Buffer, n: Local, op: Op.Classalloc)(implicit @@ -766,7 +821,7 @@ object Lower { ): Unit = { val Op.Classalloc(ClassRef(cls)) = op: @unchecked - val size = MemoryLayout.sizeOf(layout(cls).struct) + val size = layout(cls).size val allocMethod = if (size < LARGE_OBJECT_MIN_SIZE) alloc else largeAlloc @@ -1074,6 +1129,14 @@ object Lower { } } + private def arrayMemoryLayout( + ty: nir.Type, + length: Int = 0 + ): Type.StructValue = Type.StructValue( + Seq(ArrayHeader.layout, Type.ArrayValue(ty, length)) + ) + private def arrayValuePath(idx: Val) = Seq(zero, one, idx) + def genArrayloadOp(buf: Buffer, n: Local, op: Op.Arrayload)(implicit pos: Position ): Unit = { @@ -1085,11 +1148,8 @@ object Lower { genArraylengthOp(buf, len, Op.Arraylength(arr)) genGuardInBounds(buf, idx, Val.Local(len, Type.Int)) - val arrTy = Type.StructValue( - Seq(Type.Ptr, Type.Int, Type.Int, Type.ArrayValue(ty, 0)) - ) - val elemPath = Seq(Val.Int(0), Val.Int(3), idx) - val elemPtr = buf.elem(arrTy, arr, elemPath, unwind) + val arrTy = arrayMemoryLayout(ty) + val elemPtr = buf.elem(arrTy, arr, arrayValuePath(idx), unwind) buf.let(n, Op.Load(ty, elemPtr), unwind) } @@ -1103,11 +1163,8 @@ object Lower { genArraylengthOp(buf, len, Op.Arraylength(arr)) genGuardInBounds(buf, idx, Val.Local(len, Type.Int)) - val arrTy = Type.StructValue( - Seq(Type.Ptr, Type.Int, Type.Int, Type.ArrayValue(ty, 0)) - ) - val elemPtr = - buf.elem(arrTy, arr, Seq(Val.Int(0), Val.Int(3), idx), unwind) + val arrTy = arrayMemoryLayout(ty) + val elemPtr = buf.elem(arrTy, arr, arrayValuePath(idx), unwind) genStoreOp(buf, n, Op.Store(ty, elemPtr, value)) } @@ -1121,8 +1178,8 @@ object Lower { val func = arrayLength genGuardNotNull(buf, arr) - val arrTy = Type.StructValue(Seq(Type.Ptr, Type.Int)) - val lenPtr = buf.elem(arrTy, arr, Seq(Val.Int(0), Val.Int(1)), unwind) + val lenPtr = + buf.elem(ArrayHeader.layout, arr, ArrayHeaderLengthPath, unwind) buf.let(n, Op.Load(Type.Int, lenPtr), unwind) } @@ -1134,18 +1191,16 @@ object Lower { val charsLength = Val.Int(chars.length) val charsConst = Val.Const( Val.StructValue( - Seq( - rtti(CharArrayCls).const, - charsLength, - Val.Int(0), // padding to get next field aligned properly - Val.ArrayValue(Type.Char, chars.toSeq.map(Val.Char(_))) - ) + rtti(CharArrayCls).const :: + charsLength :: + zero :: // stride is used only by GC, global instances use it as padding + Val.ArrayValue(Type.Char, chars.toSeq.map(Val.Char(_))) :: Nil ) ) val fieldValues = stringFieldNames.map { case Rt.StringValueName => charsConst - case Rt.StringOffsetName => Val.Int(0) + case Rt.StringOffsetName => zero case Rt.StringCountName => charsLength case Rt.StringCachedHashCodeName => Val.Int(stringHashCode(value)) case _ => util.unreachable @@ -1153,6 +1208,51 @@ object Lower { Val.Const(Val.StructValue(rtti(StringCls).const +: fieldValues)) } + + private def genThisValueNullGuardIfUsed( + defn: Defn.Define, + buf: nir.Buffer, + createUnwindHandler: () => Option[Local] + ) = { + def usesValue(expected: Val): Boolean = { + var wasUsed = false + import scala.util.control.Breaks._ + breakable { + new Traverse { + override def onVal(value: Val): Unit = { + wasUsed = expected eq value + if (wasUsed) break() + else super.onVal(value) + } + // We're not intrested in cheecking these structures, skip them + override def onType(ty: Type): Unit = () + override def onNext(next: Next): Unit = () + }.onDefn(defn) + } + wasUsed + } + + val Global.Member(_, sig) = defn.name: @unchecked + val Inst.Label(_, args) = defn.insts.head: @unchecked + + val canHaveThisValue = + !(sig.isStatic || sig.isClinit || sig.isExtern) + + if (canHaveThisValue) { + args.headOption.foreach { thisValue => + thisValue.ty match { + case ref: Type.Ref if ref.isNullable && usesValue(thisValue) => + implicit def pos: Position = defn.pos + ScopedVar.scoped( + unwindHandler := createUnwindHandler() + ) { + genGuardNotNull(buf, thisValue) + } + case _ => () + } + } + } + } } // Update java.lang.String::hashCode whenever you change this method. diff --git a/tools/src/main/scala/scala/scalanative/codegen/MemoryLayout.scala b/tools/src/main/scala/scala/scalanative/codegen/MemoryLayout.scala index 0d884a3b7b..b30ce7ea9e 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/MemoryLayout.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/MemoryLayout.scala @@ -5,18 +5,19 @@ import scala.collection.mutable import scalanative.nir.Type.RefKind import scalanative.nir.{Type, Val} import scalanative.util.unsupported -import scalanative.codegen.MemoryLayout.PositionedType final case class MemoryLayout( size: Long, tys: Seq[MemoryLayout.PositionedType] ) { - lazy val offsetArray: Seq[Val] = { + def offsetArray(implicit meta: Metadata): Seq[Val] = { val ptrOffsets = tys.collect { - // offset in words without rtti + // offset in words without object header case MemoryLayout.PositionedType(_: RefKind, offset) => - Val.Long(offset / MemoryLayout.WORD_SIZE - 1) + Val.Long( + offset / MemoryLayout.WORD_SIZE - meta.layouts.ObjectHeader.fields + ) } ptrOffsets :+ Val.Long(-1) @@ -74,7 +75,10 @@ object MemoryLayout { offset += sizeOf(ty) } - val alignment = if (tys.isEmpty) 1 else tys.map(alignmentOf).max + val alignment = { + if (tys.isEmpty) 1 + else tys.map(alignmentOf(_)).max + } MemoryLayout(align(offset, alignment), pos.toSeq) } diff --git a/tools/src/main/scala/scala/scalanative/codegen/Metadata.scala b/tools/src/main/scala/scala/scalanative/codegen/Metadata.scala index 290e19735f..9825ce4471 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/Metadata.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/Metadata.scala @@ -5,7 +5,12 @@ import scala.collection.mutable import scalanative.nir._ import scalanative.linker.{Trait, Class} -class Metadata(val linked: linker.Result, proxies: Seq[Defn]) { +class Metadata(val linked: linker.Result, proxies: Seq[Defn])(implicit + val platform: PlatformInfo +) { + implicit private def self: Metadata = this + + val layouts = new CommonMemoryLayouts() val rtti = mutable.Map.empty[linker.Info, RuntimeTypeInformation] val vtable = mutable.Map.empty[linker.Class, VirtualTable] val layout = mutable.Map.empty[linker.Class, FieldLayout] @@ -19,14 +24,6 @@ class Metadata(val linked: linker.Result, proxies: Seq[Defn]) { val dispatchTable = new TraitDispatchTable(this) val hasTraitTables = new HasTraitTables(this) - val Rtti = Type.StructValue(Seq(Type.Ptr, Type.Int, Type.Int, Type.Ptr)) - val RttiClassIdIndex = Seq(Val.Int(0), Val.Int(1)) - val RttiTraitIdIndex = Seq(Val.Int(0), Val.Int(2)) - val RttiVtableIndex = - Seq(Val.Int(0), Val.Int(if (linked.dynsigs.isEmpty) 4 else 5)) - val RttiDynmapIndex = - Seq(Val.Int(0), Val.Int(if (linked.dynsigs.isEmpty) -1 else 4)) - initClassMetadata() initTraitMetadata() @@ -66,18 +63,18 @@ class Metadata(val linked: linker.Result, proxies: Seq[Defn]) { def initClassMetadata(): Unit = { classes.foreach { node => - vtable(node) = new VirtualTable(this, node) - layout(node) = new FieldLayout(this, node) - if (linked.dynsigs.nonEmpty) { - dynmap(node) = new DynamicHashMap(this, node, proxies) + vtable(node) = new VirtualTable(node) + layout(node) = new FieldLayout(node) + if (layouts.ClassRtti.usesDynMap) { + dynmap(node) = new DynamicHashMap(node, proxies) } - rtti(node) = new RuntimeTypeInformation(this, node) + rtti(node) = new RuntimeTypeInformation(node) } } def initTraitMetadata(): Unit = { traits.foreach { node => - rtti(node) = new RuntimeTypeInformation(this, node) + rtti(node) = new RuntimeTypeInformation(node) } } } diff --git a/tools/src/main/scala/scala/scalanative/codegen/PlatformInfo.scala b/tools/src/main/scala/scala/scalanative/codegen/PlatformInfo.scala new file mode 100644 index 0000000000..9dc875e281 --- /dev/null +++ b/tools/src/main/scala/scala/scalanative/codegen/PlatformInfo.scala @@ -0,0 +1,20 @@ +package scala.scalanative.codegen + +import scala.scalanative.build.{Config, Discover} + +private[scalanative] case class PlatformInfo( + targetTriple: Option[String], + targetsWindows: Boolean, + useOpaquePointers: Boolean +) { + val sizeOfPtr = 8 + val sizeOfPtrBits = sizeOfPtr * 8 +} +object PlatformInfo { + def apply(config: Config): PlatformInfo = PlatformInfo( + targetTriple = config.compilerConfig.targetTriple, + targetsWindows = config.targetsWindows, + useOpaquePointers = + Discover.features.opaquePointers(config.compilerConfig).isAvailable + ) +} diff --git a/tools/src/main/scala/scala/scalanative/codegen/ResourceEmbedder.scala b/tools/src/main/scala/scala/scalanative/codegen/ResourceEmbedder.scala index 36d4de2032..e26a2d51e7 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/ResourceEmbedder.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/ResourceEmbedder.scala @@ -54,21 +54,12 @@ private[scalanative] object ResourceEmbedder { s"Did not embed: $pathName - file in the ignored \'scala-native\' folder." ) None - } else if (isSourceFile((path))) { - config.logger.debug( - s"Did not embed: $pathName - source file extension detected." - ) - None - } else if (Files.isDirectory(correctedPath)) { - None - } else { - Some(ClasspathFile(path, pathName, virtualDir)) - } + } else if (isSourceFile((path))) None + else if (Files.isDirectory(correctedPath)) None + else Some(ClasspathFile(path, pathName, virtualDir)) } } - } else { - Seq.empty - } + } else Seq.empty def filterEqualPathNames( path: List[ClasspathFile] diff --git a/tools/src/main/scala/scala/scalanative/codegen/RuntimeTypeInformation.scala b/tools/src/main/scala/scala/scalanative/codegen/RuntimeTypeInformation.scala index 03dd20cf5d..3befbb9091 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/RuntimeTypeInformation.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/RuntimeTypeInformation.scala @@ -5,29 +5,16 @@ import scalanative.util.unreachable import scalanative.nir._ import scalanative.linker.{ScopeInfo, Class, Trait} -class RuntimeTypeInformation(meta: Metadata, info: ScopeInfo) { +class RuntimeTypeInformation(info: ScopeInfo)(implicit meta: Metadata) { + import RuntimeTypeInformation._ val name: Global = info.name.member(Sig.Generated("type")) val const: Val.Global = Val.Global(name, Type.Ptr) val struct: Type.StructValue = info match { case cls: Class => - val dynmap = - if (meta.linked.dynsigs.isEmpty) { - Seq.empty - } else { - Seq(meta.dynmap(cls).ty) - } - Type.StructValue( - Seq( - meta.Rtti, - Type.Int, // size - Type.Int, // idRangeUntil - meta.layout(cls).referenceOffsetsTy - ) ++ dynmap ++ Seq( - meta.vtable(cls).ty - ) + meta.layouts.ClassRtti.genLayout( + vtable = meta.vtable(cls).ty ) - case _ => - meta.Rtti + case _ => meta.layouts.Rtti.layout } val value: Val.StructValue = { val typeId = Val.Int(info match { @@ -36,37 +23,33 @@ class RuntimeTypeInformation(meta: Metadata, info: ScopeInfo) { }) val typeStr = Val.String(info.name.asInstanceOf[Global.Top].id) val traitId = Val.Int(info match { - case info: Class => - meta.dispatchTable.traitClassIds.get(info).getOrElse(-1) - case _ => - -1 + case info: Class => meta.dispatchTable.traitClassIds.getOrElse(info, -1) + case _ => -1 }) - val classConst = - Val.Global(Rt.Class.name.member(Sig.Generated("type")), Type.Ptr) val base = Val.StructValue( Seq(classConst, typeId, traitId, typeStr) ) info match { case cls: Class => val dynmap = - if (meta.linked.dynsigs.isEmpty) { - Seq.empty - } else { - Seq(meta.dynmap(cls).value) - } + if (!meta.layouts.ClassRtti.usesDynMap) Nil + else List(meta.dynmap(cls).value) val range = meta.ranges(cls) Val.StructValue( - Seq( - base, - Val.Int(meta.layout(cls).size.toInt), - Val.Int(range.last), - meta.layout(cls).referenceOffsetsValue - ) ++ dynmap ++ Seq( - meta.vtable(cls).value - ) + base :: + Val.Int(meta.layout(cls).size.toInt) :: + Val.Int(range.last) :: + meta.layout(cls).referenceOffsetsValue :: + dynmap ::: + meta.vtable(cls).value :: + Nil ) case _ => base } } } +object RuntimeTypeInformation { + private val classConst = + Val.Global(Rt.Class.name.member(Sig.Generated("type")), Type.Ptr) +} diff --git a/tools/src/main/scala/scala/scalanative/codegen/VirtualTable.scala b/tools/src/main/scala/scala/scalanative/codegen/VirtualTable.scala index 31db8e62dc..03c444994f 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/VirtualTable.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/VirtualTable.scala @@ -5,7 +5,7 @@ import scala.collection.mutable import scalanative.nir._ import scalanative.nir.Rt._ -class VirtualTable(meta: Metadata, cls: linker.Class) { +class VirtualTable(cls: linker.Class)(implicit meta: Metadata) { private val slots: mutable.UnrolledBuffer[Sig] = cls.parent.fold { mutable.UnrolledBuffer.empty[Sig] diff --git a/tools/src/main/scala/scala/scalanative/codegen/compat/os/OsCompat.scala b/tools/src/main/scala/scala/scalanative/codegen/compat/os/OsCompat.scala index 238908798e..3cb300c836 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/compat/os/OsCompat.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/compat/os/OsCompat.scala @@ -1,13 +1,16 @@ -package scala.scalanative.codegen.compat.os +package scala.scalanative.codegen +package compat.os import scala.scalanative.nir.ControlFlow.Block import scala.scalanative.nir.{Fresh, Next, Position} import scala.scalanative.util.ShowBuilder private[codegen] trait OsCompat { - + protected def codegen: AbstractCodeGen protected def osPersonalityType: String + def useOpaquePointers = codegen.meta.platform.useOpaquePointers + def genPrelude()(implicit sb: ShowBuilder): Unit def genLandingPad( unwind: Next.Unwind @@ -15,6 +18,6 @@ private[codegen] trait OsCompat { def genBlockAlloca(block: Block)(implicit sb: ShowBuilder): Unit final lazy val gxxPersonality = - s"personality i8* bitcast (i32 (...)* $osPersonalityType to i8*)" - + if (useOpaquePointers) s"personality ptr $osPersonalityType" + else s"personality i8* bitcast (i32 (...)* $osPersonalityType to i8*)" } diff --git a/tools/src/main/scala/scala/scalanative/codegen/compat/os/UnixCompat.scala b/tools/src/main/scala/scala/scalanative/codegen/compat/os/UnixCompat.scala index 58f85d1918..ab3a5ddc95 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/compat/os/UnixCompat.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/compat/os/UnixCompat.scala @@ -5,15 +5,20 @@ import scala.scalanative.nir.ControlFlow.Block import scala.scalanative.nir._ import scala.scalanative.util.ShowBuilder -private[codegen] class UnixCompat(codeGen: AbstractCodeGen) extends OsCompat { +private[codegen] class UnixCompat(protected val codegen: AbstractCodeGen) + extends OsCompat { + import codegen.{pointerType => ptrT} val ehWrapperTy = "@_ZTIN11scalanative16ExceptionWrapperE" - val excRecTy = "{ i8*, i32 }" + val excRecTy = s"{ $ptrT, i32 }" val beginCatch = "@__cxa_begin_catch" val endCatch = "@__cxa_end_catch" + val catchSig = + if (useOpaquePointers) s"$ptrT $ehWrapperTy" + else s"i8* bitcast ({ i8*, i8*, i8* }* $ehWrapperTy to i8*)" val landingpad = - s"landingpad $excRecTy catch i8* bitcast ({ i8*, i8*, i8* }* $ehWrapperTy to i8*)" + s"landingpad $excRecTy catch $catchSig" val typeid = - s"call i32 @llvm.eh.typeid.for(i8* bitcast ({ i8*, i8*, i8* }* $ehWrapperTy to i8*))" + s"call i32 @llvm.eh.typeid.for($catchSig)" protected val osPersonalityType: String = "@__gxx_personality_v0" @@ -48,12 +53,17 @@ private[codegen] class UnixCompat(codeGen: AbstractCodeGen) extends OsCompat { line(s"$excsucc:") indent() - line(s"$w0 = call i8* $beginCatch(i8* $r0)") - line(s"$w1 = bitcast i8* $w0 to i8**") - line(s"$w2 = getelementptr i8*, i8** $w1, i32 1") - line(s"$exc = load i8*, i8** $w2") + line(s"$w0 = call $ptrT $beginCatch($ptrT $r0)") + if (useOpaquePointers) { + line(s"$w2 = getelementptr ptr, ptr $w0, i32 1") + line(s"$exc = load ptr, ptr $w2") + } else { + line(s"$w1 = bitcast i8* $w0 to i8**") + line(s"$w2 = getelementptr i8*, i8** $w1, i32 1") + line(s"$exc = load i8*, i8** $w2") + } line(s"call void $endCatch()") - codeGen.genInst(Inst.Jump(next)) + codegen.genInst(Inst.Jump(next)) unindent() line(s"$excfail:") @@ -64,10 +74,10 @@ private[codegen] class UnixCompat(codeGen: AbstractCodeGen) extends OsCompat { def genPrelude()(implicit builder: ShowBuilder): Unit = { import builder._ - line("declare i32 @llvm.eh.typeid.for(i8*)") + line(s"declare i32 @llvm.eh.typeid.for($ptrT)") line(s"declare i32 $osPersonalityType(...)") - line(s"declare i8* $beginCatch(i8*)") + line(s"declare $ptrT $beginCatch($ptrT)") line(s"declare void $endCatch()") - line(s"$ehWrapperTy = external constant { i8*, i8*, i8* }") + line(s"$ehWrapperTy = external constant { $ptrT, $ptrT, $ptrT }") } } diff --git a/tools/src/main/scala/scala/scalanative/codegen/compat/os/WindowsCompat.scala b/tools/src/main/scala/scala/scalanative/codegen/compat/os/WindowsCompat.scala index 2cba8ba0d0..eb6d342c74 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/compat/os/WindowsCompat.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/compat/os/WindowsCompat.scala @@ -5,8 +5,9 @@ import scala.scalanative.nir.ControlFlow.Block import scala.scalanative.nir.{Fresh, Next, Position, Val} import scala.scalanative.util.ShowBuilder -private[codegen] class WindowsCompat(codegen: AbstractCodeGen) +private[codegen] class WindowsCompat(protected val codegen: AbstractCodeGen) extends OsCompat { + import codegen.{pointerType => ptrT} val ehWrapperTy = "\"??_R0?AVExceptionWrapper@scalanative@@@8\"" val ehWrapperName = "c\".?AVExceptionWrapper@scalanative@@\\00\"" val ehClass = "%\"class.scalanative::ExceptionWrapper\"" @@ -28,16 +29,20 @@ private[codegen] class WindowsCompat(codegen: AbstractCodeGen) override def genPrelude()(implicit sb: ShowBuilder): Unit = { import sb._ - line("declare i32 @llvm.eh.typeid.for(i8*)") + def PtrRef = if (useOpaquePointers) ptrT else s"$ptrT*" + line(s"declare i32 @llvm.eh.typeid.for($ptrT)") line(s"declare i32 $osPersonalityType(...)") - line(s"$typeDescriptor = type { i8**, i8*, [35 x i8] }") - line(s"%$stdExceptionData = type { i8*, i8 }") - line(s"%$stdExceptionClass = type { i32 (...)**, %$stdExceptionData }") - line(s"$ehClass = type { %$stdExceptionClass, i8* }") - line(s"@$typeInfo = external constant i8*") + line(s"$typeDescriptor = type { $PtrRef, $ptrT, [35 x i8] }") + line(s"%$stdExceptionData = type { $ptrT, i8 }") + if (useOpaquePointers) + line(s"%$stdExceptionClass = type { $ptrT, %$stdExceptionData }") + else + line(s"%$stdExceptionClass = type { i32 (...)**, %$stdExceptionData }") + line(s"$ehClass = type { %$stdExceptionClass, $ptrT }") + line(s"@$typeInfo = external constant $ptrT") line(s"$$$ehWrapperTy = comdat any") line( - s"@$ehWrapperTy = linkonce_odr global $typeDescriptor { i8** @$typeInfo, i8* null, [35 x i8] $ehWrapperName }, comdat" + s"@$ehWrapperTy = linkonce_odr global $typeDescriptor { $PtrRef @$typeInfo, $ptrT null, [35 x i8] $ehWrapperName }, comdat" ) } @@ -63,13 +68,25 @@ private[codegen] class WindowsCompat(codegen: AbstractCodeGen) line(s"$excsucc:") indent() - line( - s"$cpad = catchpad within $rec [$typeDescriptor* @$ehWrapperTy, i32 8, $ehClass** $ehVar]" - ) - line(s"$w1 = load $ehClass*, $ehClass** $ehVar, align 8") - line(s"$w2 = getelementptr inbounds $ehClass, $ehClass* $w1, i32 0, i32 1") - line(s"$exc = load i8*, i8** $w2, align 8") - line(s"catchret from $cpad to ") + if (useOpaquePointers) { + line( + s"$cpad = catchpad within $rec [ptr @$ehWrapperTy, i32 8, ptr $ehVar]" + ) + line(s"$w1 = load ptr, ptr $ehVar, align 8") + line(s"$w2 = getelementptr inbounds $ehClass, ptr $w1, i32 0, i32 1") + line(s"$exc = load ptr, ptr $w2, align 8") + line(s"catchret from $cpad to ") + } else { + line( + s"$cpad = catchpad within $rec [$typeDescriptor* @$ehWrapperTy, i32 8, $ehClass** $ehVar]" + ) + line(s"$w1 = load $ehClass*, $ehClass** $ehVar, align 8") + line( + s"$w2 = getelementptr inbounds $ehClass, $ehClass* $w1, i32 0, i32 1" + ) + line(s"$exc = load i8*, i8** $w2, align 8") + line(s"catchret from $cpad to ") + } genNext(next) unindent() } diff --git a/tools/src/main/scala/scala/scalanative/interflow/Inline.scala b/tools/src/main/scala/scala/scalanative/interflow/Inline.scala index dd03ce19c4..a782779689 100644 --- a/tools/src/main/scala/scala/scalanative/interflow/Inline.scala +++ b/tools/src/main/scala/scala/scalanative/interflow/Inline.scala @@ -154,10 +154,8 @@ trait Inline { self: Interflow => ): Val = in(s"inlining ${name.show}") { val defn = mode match { - case build.Mode.Debug => - getOriginal(name) - case _: build.Mode.Release => - getDone(name) + case build.Mode.Debug => getOriginal(name) + case _: build.Mode.Release => getDone(name) } val Type.Function(_, origRetTy) = defn.ty: @unchecked @@ -223,7 +221,21 @@ trait Inline { self: Interflow => } } - state.emit ++= emit + // Check if inlined function performed stack allocation, if so add + // insert stacksave/stackrestore LLVM Intrinsics to prevent affecting. + // By definition every stack allocation of inlined function is only needed within it's body + val allocatesOnStack = emit.exists { + case Inst.Let(_, _: Op.Stackalloc, _) => true + case _ => false + } + if (allocatesOnStack) { + import Interflow.LLVMIntrinsics._ + val stackState = state.emit + .call(StackSaveSig, StackSave, Nil, Next.None) + state.emit ++= emit + state.emit + .call(StackRestoreSig, StackRestore, Seq(stackState), Next.None) + } else state.emit ++= emit state.inherit(endState, res +: args) val Type.Function(_, retty) = defn.ty: @unchecked diff --git a/tools/src/main/scala/scala/scalanative/interflow/Interflow.scala b/tools/src/main/scala/scala/scalanative/interflow/Interflow.scala index 49ba0a9a8b..8a84c334dd 100644 --- a/tools/src/main/scala/scala/scalanative/interflow/Interflow.scala +++ b/tools/src/main/scala/scala/scalanative/interflow/Interflow.scala @@ -2,9 +2,10 @@ package scala.scalanative package interflow import scala.collection.mutable -import scalanative.nir._ -import scalanative.linker._ -import scalanative.util.ScopedVar +import scala.scalanative.codegen.PlatformInfo +import scala.scalanative.nir._ +import scala.scalanative.linker._ +import scala.scalanative.util.ScopedVar import java.util.function.Supplier class Interflow(val config: build.Config)(implicit @@ -18,6 +19,8 @@ class Interflow(val config: build.Config)(implicit with PolyInline with Intrinsics with Log { + implicit val platform: PlatformInfo = PlatformInfo(config) + private val originals = { val out = mutable.Map.empty[Global, Defn] linked.defns.foreach { defn => out(defn.name) = defn } @@ -157,4 +160,22 @@ object Interflow { interflow.visitLoop() interflow.result() } + + object LLVMIntrinsics { + private val externAttrs = Attrs(isExtern = true) + private val LLVMI = Global.Top("scala.scalanative.runtime.LLVMIntrinsics$") + private def llvmIntrinsic(id: String) = + Val.Global(LLVMI.member(Sig.Extern(id)), Type.Ptr) + + val StackSave = llvmIntrinsic("llvm.stacksave") + val StackSaveSig = Type.Function(Nil, Type.Ptr) + + val StackRestore = llvmIntrinsic("llvm.stackrestore") + val StackRestoreSig = Type.Function(Seq(Type.Ptr), Type.Unit) + } + + val depends: Seq[Global] = Seq( + LLVMIntrinsics.StackSave.name, + LLVMIntrinsics.StackRestore.name + ) } diff --git a/tools/src/main/scala/scala/scalanative/interflow/Opt.scala b/tools/src/main/scala/scala/scalanative/interflow/Opt.scala index f8c6bfc80d..14ba1a245c 100644 --- a/tools/src/main/scala/scala/scalanative/interflow/Opt.scala +++ b/tools/src/main/scala/scala/scalanative/interflow/Opt.scala @@ -98,11 +98,15 @@ trait Opt { self: Interflow => case Inst.Ret(v) => v.ty } - val retty = rets match { + val retty0 = rets match { case Seq() => Type.Nothing case Seq(ty) => ty case tys => Sub.lub(tys, Some(origRetTy)) } + // Make sure to not override expected BoxedUnit with primitive Unit + val retty = + if (retty0 == Type.Unit && origRetTy.isInstanceOf[Type.Ref]) origRetTy + else retty0 result(retty, insts) } diff --git a/tools/src/main/scala/scala/scalanative/interflow/Visit.scala b/tools/src/main/scala/scala/scalanative/interflow/Visit.scala index 46090d8b5f..0b255cc26e 100644 --- a/tools/src/main/scala/scala/scalanative/interflow/Visit.scala +++ b/tools/src/main/scala/scala/scalanative/interflow/Visit.scala @@ -160,7 +160,11 @@ trait Visit { self: Interflow => case (argty, origty) => // Duplicate argument type should not be // less specific than the original declare type. - if (!Sub.is(argty, origty)) origty else argty + val tpe = if (!Sub.is(argty, origty)) origty else argty + // Lift Unit to BoxedUnit, only in that form it can be passed as a function argument + // It would be better to eliminate void arguments, but currently generates lots of problmes + if (tpe == nir.Type.Unit) Rt.BoxedUnit + else tpe } val Global.Member(top, sig) = orig: @unchecked Global.Member(top, Sig.Duplicate(sig, dupargtys)) diff --git a/tools/src/main/scala/scala/scalanative/interflow/Whitelist.scala b/tools/src/main/scala/scala/scalanative/interflow/Whitelist.scala index f6ec402b86..9a7dab7487 100644 --- a/tools/src/main/scala/scala/scalanative/interflow/Whitelist.scala +++ b/tools/src/main/scala/scala/scalanative/interflow/Whitelist.scala @@ -9,6 +9,10 @@ object Whitelist { val constantModules = { val out = collection.mutable.Set.empty[Global] out += Global.Top("scala.scalanative.runtime.BoxedUnit$") + out += Global.Top("scala.scalanative.runtime.MemoryLayout$") + out += Global.Top("scala.scalanative.runtime.MemoryLayout$Array$") + out += Global.Top("scala.scalanative.runtime.MemoryLayout$Object$") + out += Global.Top("scala.scalanative.runtime.MemoryLayout$Rtti$") out += Global.Top("scala.scalanative.unsafe.Tag$") out += Global.Top("scala.scalanative.unsafe.Tag$Unit$") out += Global.Top("scala.scalanative.unsafe.Tag$Boolean$") diff --git a/tools/src/main/scala/scala/scalanative/linker/LinktimeValueResolver.scala b/tools/src/main/scala/scala/scalanative/linker/LinktimeValueResolver.scala index 2d7fd82608..77ff27dacd 100644 --- a/tools/src/main/scala/scala/scalanative/linker/LinktimeValueResolver.scala +++ b/tools/src/main/scala/scala/scalanative/linker/LinktimeValueResolver.scala @@ -20,7 +20,9 @@ trait LinktimeValueResolver { self: Reach => s"$linktimeInfo.isWeakReferenceSupported" -> { conf.gc == GC.Immix || conf.gc == GC.Commix - } + }, + s"$linktimeInfo.isMsys" -> Platform.isMsys, + s"$linktimeInfo.isCygwin" -> Platform.isCygwin ) NativeConfig.checkLinktimeProperties(predefined) predefined ++ conf.linktimeProperties diff --git a/tools/src/test/scala/scala/scalanative/NIRCompilerTest.scala b/tools/src/test/scala/scala/scalanative/NIRCompilerTest.scala index f54fa91e67..8849662541 100644 --- a/tools/src/test/scala/scala/scalanative/NIRCompilerTest.scala +++ b/tools/src/test/scala/scala/scalanative/NIRCompilerTest.scala @@ -99,6 +99,107 @@ class NIRCompilerTest extends AnyFlatSpec with Matchers with Inspectors { NIRCompiler(_.compile(code)) } + it should "not allow members of extern object to reference other externs" in { + val code = + """import scala.scalanative.unsafe.extern + | + |@extern object Dummy { + | def foo(): Int = extern + | def bar(): Int = foo() + |} + |""".stripMargin + intercept[CompilationFailedException] { + NIRCompiler(_.compile(code)) + }.getMessage() should include( + "Referencing other extern symbols in not supported" + ) + } + + it should "allow to extend extern traits" in { + val code = + """import scala.scalanative.unsafe.extern + | + |@extern trait Dummy { + | var x: Int = extern + | def foo(): Int = extern + |} + | + |@extern trait Dummy2 extends Dummy { + | def bar(): Int = extern + |} + | + |@extern object Dummy extends Dummy + |@extern object Dummy2 extends Dummy2 + |""".stripMargin + + NIRCompiler(_.compile(code)) + } + + it should "not allow to mix extern object with regular traits" in { + val code = + """ + |import scala.scalanative.unsafe.extern + | + |trait Dummy { + | def foo(): Int = ??? + |} + | + |@extern object Dummy extends Dummy + |""".stripMargin + intercept[CompilationFailedException](NIRCompiler(_.compile(code))) + .getMessage() should include( + "Extern object can only extend extern traits" + ) + } + + it should "not allow to mix extern object with class" in { + val code = + """import scala.scalanative.unsafe.extern + | + |class Dummy { + | def foo(): Int = ??? + |} + | + |@extern object Dummy extends Dummy + |""".stripMargin + intercept[CompilationFailedException](NIRCompiler(_.compile(code))) + .getMessage() should include( + "Extern object can only extend extern traits" + ) + } + + it should "not allow to mix extern traits with regular object" in { + val code = + """import scala.scalanative.unsafe.extern + | + |@extern trait Dummy { + | def foo(): Int = extern + |} + | + |object Dummy extends Dummy + |""".stripMargin + intercept[CompilationFailedException](NIRCompiler(_.compile(code))) + .getMessage() should include( + "Extern traits can be only mixed with extern traits or objects" + ) + } + + it should "not allow to mix extern traits with class" in { + val code = + """import scala.scalanative.unsafe.extern + | + |@extern trait Dummy { + | def foo(): Int = extern + |} + | + |class DummyImpl extends Dummy + |""".stripMargin + intercept[CompilationFailedException](NIRCompiler(_.compile(code))) + .getMessage() should include( + "Extern traits can be only mixed with extern traits or objects" + ) + } + it should "report error for intrinsic resolving of not existing field" in { intercept[CompilationFailedException] { NIRCompiler( @@ -320,4 +421,15 @@ class NIRCompilerTest extends AnyFlatSpec with Matchers with Inspectors { }.getMessage should include(CannotExportField) } + // https://github.com/scala-native/scala-native/issues/3228 + it should "allow to define fields in extern object" in NIRCompiler(_.compile { + """ + |import scala.scalanative.unsafe._ + | + |@extern + |object Foo { + | final val bar = 42 + |}""".stripMargin + }) + } diff --git a/tools/src/test/scala/scala/scalanative/linker/IssuesSpec.scala b/tools/src/test/scala/scala/scalanative/linker/IssuesSpec.scala index 39d4e92af1..91e5ae9ee7 100644 --- a/tools/src/test/scala/scala/scalanative/linker/IssuesSpec.scala +++ b/tools/src/test/scala/scala/scalanative/linker/IssuesSpec.scala @@ -4,6 +4,7 @@ import scala.scalanative.checker.Check import scala.scalanative.LinkerSpec import org.scalatest.matchers.should._ +import scala.scalanative.nir._ class IssuesSpec extends LinkerSpec with Matchers { private val mainClass = "Test" @@ -57,4 +58,70 @@ class IssuesSpec extends LinkerSpec with Matchers { |""" } + "Extern traits" should "have only primitive type in type signature" in { + testLinked(s""" + |import scala.scalanative.unsafe._ + |import scala.scalanative.unsigned._ + | + |@extern trait string { + | def memset(dest: Ptr[Byte], ch: Int, count: ULong): Ptr[Byte] = extern + |} + |@extern object string extends string + | + |object Test { + | def main(args: Array[String]): Unit = { + | val privilegeSetLength = stackalloc[ULong]() + | val privilegeSet: Ptr[Byte] = stackalloc[Byte](!privilegeSetLength) + | + | // real case + | string.memset(privilegeSet, 0, !privilegeSetLength) + | + | // possible case + | def str: string = ??? + | str.memset(privilegeSet, 0, !privilegeSetLength) + | } + |}""".stripMargin) { result => + val Memset = Sig.Extern("memset") + val StringMemset = Global.Top("string").member(Memset) + val decls = result.defns + .collectFirst { + case Defn.Declare(attrs, StringMemset, tpe) => + assert(attrs.isExtern) + tpe shouldEqual Type.Function( + Seq(Type.Ptr, Type.Int, Type.Long), + Type.Ptr + ) + } + .orElse { + fail("Not found extern declaration") + } + } + } + + it should "define extern fields with correct attributes" in { + testLinked(s""" + |import scala.scalanative.unsafe._ + | + |@extern trait lib { + | var field: CInt = extern + |} + |@extern object lib extends lib + | + |object Test { + | def main(args: Array[String]): Unit = { + | val read = lib.field + | lib.field = 42 + | } + |}""".stripMargin) { result => + val Field = Sig.Extern("field") + val LibField = Global.Top("lib").member(Field) + val decls = result.defns + .collect { + case defn @ Defn.Declare(attrs, LibField, tpe) => + assert(attrs.isExtern) + } + if (decls.isEmpty) fail("Not found extern declaration") + } + } + } diff --git a/unit-tests/native/src/test/scala-3/scala/scala/scalnative/IssuesTestScala3.scala b/unit-tests/native/src/test/scala-3/scala/scala/scalnative/IssuesTestScala3.scala index 35a39c2af2..71852bddef 100644 --- a/unit-tests/native/src/test/scala-3/scala/scala/scalnative/IssuesTestScala3.scala +++ b/unit-tests/native/src/test/scala-3/scala/scala/scalnative/IssuesTestScala3.scala @@ -23,6 +23,19 @@ class IssuesTestScala3 { // Check links (!ctx).text_width = CFuncPtr2.fromScalaFunction { (_, _) => 0 } } + + @Test def i3231(): Unit = { + @extern object extern_functions: + @name("sprintf") + def test(buffer: CString, format: CString, args: Any*): CInt = extern + + object functions: + export extern_functions.test // should compile + + val buff: Ptr[CChar] = stackalloc[CChar](128.toULong) + functions.test(buff, c"%d %d %d", -1, 1, 42) + assertEquals("-1 1 42", fromCString(buff)) + } } object issue2485: diff --git a/unit-tests/native/src/test/scala/org/scalanative/testsuite/javalib/lang/ref/WeakReferenceTest.scala b/unit-tests/native/src/test/scala/org/scalanative/testsuite/javalib/lang/ref/WeakReferenceTest.scala index 0b04f50634..89ea1616fa 100644 --- a/unit-tests/native/src/test/scala/org/scalanative/testsuite/javalib/lang/ref/WeakReferenceTest.scala +++ b/unit-tests/native/src/test/scala/org/scalanative/testsuite/javalib/lang/ref/WeakReferenceTest.scala @@ -38,26 +38,27 @@ class WeakReferenceTest { @deprecated @nooptimize @Test def addsToReferenceQueueAfterGC(): Unit = { assumeFalse( - "In the CI Scala 3 sometimes SN fails to clean weak references in some of Windows build configurations", - ScalaNativeBuildInfo.scalaVersion.startsWith("3.") && - Platform.isWindows + "In the CI Scala 3 sometimes SN fails to clean weak references in some build configurations", + ScalaNativeBuildInfo.scalaVersion.startsWith("3.") ) def assertEventuallyIsCollected( clue: String, ref: WeakReference[_], - retries: Int + deadline: Long ): Unit = { ref.get() match { case null => assertTrue("collected but not enqueued", ref.isEnqueued()) case v => - if (retries > 0) { + if (System.currentTimeMillis() < deadline) { // Give GC something to collect - System.err.println(s"$clue - not yet collected $ref ($retries)") + locally { + val _ = Seq.fill(1000)(new Object {}) + } Thread.sleep(200) GC.collect() - assertEventuallyIsCollected(clue, ref, retries - 1) + assertEventuallyIsCollected(clue, ref, deadline) } else { fail( s"$clue - expected that WeakReference would be collected, but it contains value ${v}" @@ -73,8 +74,9 @@ class WeakReferenceTest { val weakRefList = List(weakRef1, weakRef2) GC.collect() - assertEventuallyIsCollected("weakRef1", weakRef1, retries = 5) - assertEventuallyIsCollected("weakRef2", weakRef2, retries = 5) + def newDeadline() = System.currentTimeMillis() + 60 * 1000 + assertEventuallyIsCollected("weakRef1", weakRef1, deadline = newDeadline()) + assertEventuallyIsCollected("weakRef2", weakRef2, deadline = newDeadline()) assertEquals("weakRef1", null, weakRef1.get()) assertEquals("weakRef2", null, weakRef2.get()) diff --git a/unit-tests/native/src/test/scala/org/scalanative/testsuite/posixlib/DlfcnTest.scala b/unit-tests/native/src/test/scala/org/scalanative/testsuite/posixlib/DlfcnTest.scala new file mode 100644 index 0000000000..fefce50b87 --- /dev/null +++ b/unit-tests/native/src/test/scala/org/scalanative/testsuite/posixlib/DlfcnTest.scala @@ -0,0 +1,121 @@ +package org.scalanative.testsuite.posixlib + +import org.junit.Test +import org.junit.Assert._ +import org.junit.Assume._ + +import scala.scalanative.meta.LinktimeInfo.{isLinux, isMac} +import scala.scalanative.runtime.PlatformExt + +import java.io.File + +import scala.scalanative.unsafe._ + +import scala.scalanative.posix.dlfcn._ + +class DlfcnTest { + + /* Dlfcn is tested on Linux and macOS. + * With some additional work, it could be tested on FreeBSD. + * One would have to find a suitable "known" .so file and, + * possibly, adjust the message returned by dlerror(). + */ + + @Test def dlfcnOpensAndObtainsSymbolAddressLinux(): Unit = { + if (isLinux) Zone { implicit z => + val soFilePrefix = + if (PlatformExt.isArm64) + "/usr/lib/aarch64-linux-gnu" + else + "/lib/x86_64-linux-gnu" + + val soFile = s"${soFilePrefix}/libc.so.6" + + /* Ensure the file exists before trying to "dlopen()" it. + * Someday the ".so.6" suffix is going to change to ".so.7" or such. + * When it does do a "soft failure", rather than failing the entire + * build. + */ + assumeTrue( + s"shared library ${soFile} not found", + (new File(soFile)).exists() + ) + + val handle = dlopen(toCString(soFile), RTLD_LAZY | RTLD_LOCAL) + assertNotNull(s"dlopen of ${soFile} failed", handle) + + try { + val symbol = "strlen" + val symbolC = toCString(symbol) + + val cFunc = dlsym(handle, symbolC) + assertNotNull(s"dlsym lookup of '${symbol}' failed", cFunc) + + // Have symbol, does it function (sic)? + type StringLengthFn = CFuncPtr1[CString, Int] + val func: StringLengthFn = CFuncPtr.fromPtr[StringLengthFn](cFunc) + + assertEquals( + s"executing symbol '${symbol}' failed", + symbol.length(), + func(symbolC) + ) + + val missingSymbol = "NOT_IN_LIBC" + + val func2 = dlsym(handle, toCString(missingSymbol)) + assertNull(s"dlsym lookup of ${symbol} should have failed", func2) + + val msg = fromCString(dlerror()) + // It is always chancy trying to match exact text. Go for suffix here. + assertTrue( + s"dlerror returned msg: |${msg}|", + msg.endsWith(s"undefined symbol: ${missingSymbol}") + ) + } finally { + dlclose(handle) + } + } + } + + @Test def dlfcnOpensAndObtainsSymbolAddressMacOs(): Unit = { + if (isMac) Zone { implicit z => + val soFile = "/usr/lib/libSystem.dylib" + + val handle = dlopen(toCString(soFile), RTLD_LAZY | RTLD_LOCAL) + assertNotNull(s"dlopen of ${soFile} failed", handle) + + try { + val symbol = "strlen" + val symbolC = toCString(symbol) + + val cFunc = dlsym(handle, symbolC) + assertNotNull(s"dlsym lookup of '${symbol}' failed", cFunc) + + // Have symbol, does it function (sic)? + type StringLengthFn = CFuncPtr1[CString, Int] + val func: StringLengthFn = CFuncPtr.fromPtr[StringLengthFn](cFunc) + + assertEquals( + s"executing symbol '${symbol}' failed", + symbol.length(), + func(symbolC) + ) + + val missingSymbol = "NOT_IN_LIBC" + + val func2 = dlsym(handle, toCString(missingSymbol)) + assertNull(s"dlsym lookup of ${symbol} should have failed", func2) + + val msg = fromCString(dlerror()) + // It is always chancy trying to match exact text. Go for suffix here. + assertTrue( + s"dlerror returned msg: |${msg}|", + msg.endsWith(s"${missingSymbol}): symbol not found") + ) + } finally { + dlclose(handle) + } + } + } +} diff --git a/unit-tests/native/src/test/scala/scala/scalanative/IssuesTest.scala b/unit-tests/native/src/test/scala/scala/scalanative/IssuesTest.scala index b0d81a2c1f..37dd2e752a 100644 --- a/unit-tests/native/src/test/scala/scala/scalanative/IssuesTest.scala +++ b/unit-tests/native/src/test/scala/scala/scalanative/IssuesTest.scala @@ -7,6 +7,7 @@ import org.scalanative.testsuite.utils.AssertThrows.assertThrows import scalanative.unsigned._ import scalanative.unsafe._ import scala.annotation.nowarn +import scala.scalanative.annotation.alwaysinline class IssuesTest { @@ -589,6 +590,40 @@ class IssuesTest { } assertEquals(Foo.fooLiteral, Foo.fooLazy) } + @Test def i3147(): Unit = { + // It's not a runtime, but linktime bug related to nir.Show new lines escapes for string literals + println("$\n") + } + + @Test def i3195(): Unit = { + // Make sure that inlined calls are resetting the stack upon returning + // Otherwise calls to functions allocating on stack in loop might lead to stack overflow + @alwaysinline def allocatingFunction(): CSize = { + import scala.scalanative.unsafe.{CArray, Nat} + import Nat._ + def `64KB` = (64 * 1024).toUInt + val chunk = stackalloc[Byte](`64KB`) + assertNotNull("stackalloc was null", chunk) + `64KB` + } + // 32MB, may more then available stack 1MB on Windows, < 8 MB on Unix + val toAllocate = (32 * 1024 * 1024).toULong + var allocated = 0.toULong + while (allocated < toAllocate) { + allocated += allocatingFunction() + } + } + + @Test def issue3196(): Unit = { + object ctx { + type Foo + } + val ptr1 = stackalloc[Ptr[ctx.Foo]]() + println(!ptr1) // segfault + + val ptr2 = stackalloc[Ptr[_]]() + println(!ptr2) // segfault + } } diff --git a/unit-tests/native/src/test/scala/scala/scalanative/unsafe/CVarArgTest.scala b/unit-tests/native/src/test/scala/scala/scalanative/unsafe/CVarArgTest.scala new file mode 100644 index 0000000000..b9b10f8280 --- /dev/null +++ b/unit-tests/native/src/test/scala/scala/scalanative/unsafe/CVarArgTest.scala @@ -0,0 +1,831 @@ +package scala.scalanative +package unsafe + +import org.junit.{Test, BeforeClass} +import org.junit.Assert._ +import org.junit.Assume._ + +import scalanative.unsigned._ +import scalanative.unsafe._ +import scalanative.libc.{stdio, stdlib, string} + +import scala.scalanative.junit.utils.AssumesHelper._ + +class CVarArgTest { + def vatest(cstr: CString, output: String)( + generator: (CString, Ptr[CChar]) => Unit + ): Unit = { + val buff: Ptr[CChar] = stackalloc[CChar](1024.toUInt) + generator(buff, cstr) + val got = fromCString(buff) + assertEquals(got, output) + } + + @Test def byteValue0(): Unit = + vatest(c"%d", "0")(stdio.sprintf(_, _, 0.toByte)) + @Test def byteValue1(): Unit = + vatest(c"%d", "1")(stdio.sprintf(_, _, 1.toByte)) + @Test def byteValueMinus1(): Unit = + vatest(c"%d", "-1")(stdio.sprintf(_, _, -1.toByte)) + @Test def byteValueMin(): Unit = + vatest(c"%d", "-128")(stdio.sprintf(_, _, java.lang.Byte.MIN_VALUE)) + @Test def byteValueMax(): Unit = + vatest(c"%d", "127")(stdio.sprintf(_, _, java.lang.Byte.MAX_VALUE)) + @Test def byteArgs1(): Unit = + vatest(c"%d", "1")(stdio.sprintf(_, _, 1.toByte)) + @Test def byteArgs2(): Unit = + vatest(c"%d %d", "1 2")(stdio.sprintf(_, _, 1.toByte, 2.toByte)) + @Test def byteArgs3(): Unit = + vatest(c"%d %d %d", "1 2 3")( + stdio.sprintf(_, _, 1.toByte, 2.toByte, 3.toByte) + ) + @Test def byteArgs4(): Unit = + vatest(c"%d %d %d %d", "1 2 3 4")( + stdio.sprintf(_, _, 1.toByte, 2.toByte, 3.toByte, 4.toByte) + ) + @Test def byteArgs5(): Unit = + vatest(c"%d %d %d %d %d", "1 2 3 4 5")( + stdio.sprintf(_, _, 1.toByte, 2.toByte, 3.toByte, 4.toByte, 5.toByte) + ) + @Test def byteArgs6(): Unit = + vatest(c"%d %d %d %d %d %d", "1 2 3 4 5 6")( + stdio.sprintf( + _, + _, + 1.toByte, + 2.toByte, + 3.toByte, + 4.toByte, + 5.toByte, + 6.toByte + ) + ) + @Test def byteArgs7(): Unit = + vatest(c"%d %d %d %d %d %d %d", "1 2 3 4 5 6 7")( + stdio.sprintf( + _, + _, + 1.toByte, + 2.toByte, + 3.toByte, + 4.toByte, + 5.toByte, + 6.toByte, + 7.toByte + ) + ) + @Test def byteArgs8(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d", + "1 2 3 4 5 6 7 8" + )( + stdio.sprintf( + _, + _, + 1.toByte, + 2.toByte, + 3.toByte, + 4.toByte, + 5.toByte, + 6.toByte, + 7.toByte, + 8.toByte + ) + ) + @Test def byteArgs9(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d %d", + "1 2 3 4 5 6 7 8 9" + )( + stdio.sprintf( + _, + _, + 1.toByte, + 2.toByte, + 3.toByte, + 4.toByte, + 5.toByte, + 6.toByte, + 7.toByte, + 8.toByte, + 9.toByte + ) + ) + + @Test def shortValue0(): Unit = + vatest(c"%d", "0")(stdio.sprintf(_, _, 0.toShort)) + @Test def shortValue1(): Unit = + vatest(c"%d", "1")(stdio.sprintf(_, _, 1.toShort)) + @Test def shortValueMinus1(): Unit = + vatest(c"%d", "-1")(stdio.sprintf(_, _, -1.toShort)) + @Test def shortValueMin(): Unit = + vatest(c"%d", "-32768")(stdio.sprintf(_, _, java.lang.Short.MIN_VALUE)) + @Test def shortValueMax(): Unit = + vatest(c"%d", "32767")(stdio.sprintf(_, _, java.lang.Short.MAX_VALUE)) + @Test def shortArgs1(): Unit = + vatest(c"%d", "1")(stdio.sprintf(_, _, 1.toShort)) + @Test def shortArgs2(): Unit = + vatest(c"%d %d", "1 2")(stdio.sprintf(_, _, 1.toShort, 2.toShort)) + @Test def shortArgs3(): Unit = + vatest(c"%d %d %d", "1 2 3")( + stdio.sprintf(_, _, 1.toShort, 2.toShort, 3.toShort) + ) + @Test def shortArgs4(): Unit = + vatest(c"%d %d %d %d", "1 2 3 4")( + stdio.sprintf(_, _, 1.toShort, 2.toShort, 3.toShort, 4.toShort) + ) + @Test def shortArgs5(): Unit = + vatest(c"%d %d %d %d %d", "1 2 3 4 5")( + stdio.sprintf(_, _, 1.toShort, 2.toShort, 3.toShort, 4.toShort, 5.toShort) + ) + @Test def shortArgs6(): Unit = + vatest(c"%d %d %d %d %d %d", "1 2 3 4 5 6")( + stdio.sprintf( + _, + _, + 1.toShort, + 2.toShort, + 3.toShort, + 4.toShort, + 5.toShort, + 6.toShort + ) + ) + @Test def shortArgs7(): Unit = + vatest( + c"%d %d %d %d %d %d %d", + "1 2 3 4 5 6 7" + )( + stdio.sprintf( + _, + _, + 1.toShort, + 2.toShort, + 3.toShort, + 4.toShort, + 5.toShort, + 6.toShort, + 7.toShort + ) + ) + @Test def shortArgs8(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d", + "1 2 3 4 5 6 7 8" + )( + stdio.sprintf( + _, + _, + 1.toShort, + 2.toShort, + 3.toShort, + 4.toShort, + 5.toShort, + 6.toShort, + 7.toShort, + 8.toShort + ) + ) + @Test def shortArgs9(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d %d", + "1 2 3 4 5 6 7 8 9" + )( + stdio.sprintf( + _, + _, + 1.toShort, + 2.toShort, + 3.toShort, + 4.toShort, + 5.toShort, + 6.toShort, + 7.toShort, + 8.toShort, + 9.toShort + ) + ) + + @Test def intValue0(): Unit = + vatest(c"%d", "0")(stdio.sprintf(_, _, 0)) + @Test def intValue1(): Unit = + vatest(c"%d", "1")(stdio.sprintf(_, _, 1)) + @Test def intValueMinus1(): Unit = + vatest(c"%d", "-1")(stdio.sprintf(_, _, -1)) + @Test def intValueMin(): Unit = + vatest(c"%d", "-2147483648")( + stdio.sprintf(_, _, java.lang.Integer.MIN_VALUE) + ) + @Test def intValueMax(): Unit = + vatest(c"%d", "2147483647")( + stdio.sprintf(_, _, java.lang.Integer.MAX_VALUE) + ) + @Test def intArgs1(): Unit = + vatest(c"%d", "1")(stdio.sprintf(_, _, 1)) + @Test def intArgs2(): Unit = + vatest(c"%d %d", "1 2")(stdio.sprintf(_, _, 1, 2)) + @Test def intArgs3(): Unit = + vatest(c"%d %d %d", "1 2 3")(stdio.sprintf(_, _, 1, 2, 3)) + @Test def intArgs4(): Unit = + vatest(c"%d %d %d %d", "1 2 3 4")(stdio.sprintf(_, _, 1, 2, 3, 4)) + @Test def intArgs5(): Unit = + vatest(c"%d %d %d %d %d", "1 2 3 4 5")(stdio.sprintf(_, _, 1, 2, 3, 4, 5)) + @Test def intArgs6(): Unit = + vatest(c"%d %d %d %d %d %d", "1 2 3 4 5 6")( + stdio.sprintf(_, _, 1, 2, 3, 4, 5, 6) + ) + @Test def intArgs7(): Unit = + vatest(c"%d %d %d %d %d %d %d", "1 2 3 4 5 6 7")( + stdio.sprintf(_, _, 1, 2, 3, 4, 5, 6, 7) + ) + @Test def intArgs8(): Unit = + vatest(c"%d %d %d %d %d %d %d %d", "1 2 3 4 5 6 7 8")( + stdio.sprintf(_, _, 1, 2, 3, 4, 5, 6, 7, 8) + ) + @Test def intArgs9(): Unit = + vatest(c"%d %d %d %d %d %d %d %d %d", "1 2 3 4 5 6 7 8 9")( + stdio.sprintf(_, _, 1, 2, 3, 4, 5, 6, 7, 8, 9) + ) + + @Test def longValue0(): Unit = + vatest(c"%d", "0")(stdio.sprintf(_, _, 0L)) + @Test def longValue1(): Unit = + vatest(c"%d", "1")(stdio.sprintf(_, _, 1L)) + @Test def longValueMinus1(): Unit = + vatest(c"%d", "-1")(stdio.sprintf(_, _, -1L)) + @Test def longValueMin(): Unit = { + vatest(c"%lld", "-9223372036854775808")( + stdio.sprintf(_, _, java.lang.Long.MIN_VALUE) + ) + } + @Test def longValueMax(): Unit = { + vatest(c"%lld", "9223372036854775807")( + stdio.sprintf(_, _, java.lang.Long.MAX_VALUE) + ) + } + @Test def longArgs1(): Unit = + vatest(c"%d", "1")(stdio.sprintf(_, _, 1L)) + @Test def longArgs2(): Unit = + vatest(c"%d %d", "1 2")(stdio.sprintf(_, _, 1L, 2L)) + @Test def longArgs3(): Unit = + vatest(c"%d %d %d", "1 2 3")(stdio.sprintf(_, _, 1L, 2L, 3L)) + @Test def longArgs4(): Unit = + vatest(c"%d %d %d %d", "1 2 3 4")(stdio.sprintf(_, _, 1L, 2L, 3L, 4L)) + @Test def longArgs5(): Unit = + vatest(c"%d %d %d %d %d", "1 2 3 4 5")( + stdio.sprintf(_, _, 1L, 2L, 3L, 4L, 5L) + ) + @Test def longArgs6(): Unit = + vatest(c"%d %d %d %d %d %d", "1 2 3 4 5 6")( + stdio.sprintf(_, _, 1L, 2L, 3L, 4L, 5L, 6L) + ) + @Test def longArgs7(): Unit = + vatest(c"%d %d %d %d %d %d %d", "1 2 3 4 5 6 7")( + stdio.sprintf(_, _, 1L, 2L, 3L, 4L, 5L, 6L, 7L) + ) + @Test def longArgs8(): Unit = + vatest(c"%d %d %d %d %d %d %d %d", "1 2 3 4 5 6 7 8")( + stdio.sprintf(_, _, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L) + ) + @Test def longArgs9(): Unit = + vatest(c"%d %d %d %d %d %d %d %d %d", "1 2 3 4 5 6 7 8 9")( + stdio.sprintf(_, _, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L) + ) + + @Test def ubyteValueMin(): Unit = + vatest(c"%d", "0")(stdio.sprintf(_, _, UByte.MinValue)) + @Test def ubyteValueMax(): Unit = + vatest(c"%d", "255")(stdio.sprintf(_, _, UByte.MaxValue)) + @Test def ubyteArgs1(): Unit = + vatest(c"%d", "1")(stdio.sprintf(_, _, 1.toUByte)) + @Test def ubyteArgs2(): Unit = + vatest(c"%d %d", "1 2")(stdio.sprintf(_, _, 1.toUByte, 2.toUByte)) + @Test def ubyteArgs3(): Unit = + vatest(c"%d %d %d", "1 2 3")( + stdio.sprintf(_, _, 1.toUByte, 2.toUByte, 3.toUByte) + ) + @Test def ubyteArgs4(): Unit = + vatest(c"%d %d %d %d", "1 2 3 4")( + stdio.sprintf(_, _, 1.toUByte, 2.toUByte, 3.toUByte, 4.toUByte) + ) + @Test def ubyteArgs5(): Unit = + vatest(c"%d %d %d %d %d", "1 2 3 4 5")( + stdio.sprintf(_, _, 1.toUByte, 2.toUByte, 3.toUByte, 4.toUByte, 5.toUByte) + ) + @Test def ubyteArgs6(): Unit = + vatest(c"%d %d %d %d %d %d", "1 2 3 4 5 6")( + stdio.sprintf( + _, + _, + 1.toUByte, + 2.toUByte, + 3.toUByte, + 4.toUByte, + 5.toUByte, + 6.toUByte + ) + ) + @Test def ubyteArgs7(): Unit = + vatest( + c"%d %d %d %d %d %d %d", + "1 2 3 4 5 6 7" + )( + stdio.sprintf( + _, + _, + 1.toUByte, + 2.toUByte, + 3.toUByte, + 4.toUByte, + 5.toUByte, + 6.toUByte, + 7.toUByte + ) + ) + @Test def ubyteArgs8(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d", + "1 2 3 4 5 6 7 8" + )( + stdio.sprintf( + _, + _, + 1.toUByte, + 2.toUByte, + 3.toUByte, + 4.toUByte, + 5.toUByte, + 6.toUByte, + 7.toUByte, + 8.toUByte + ) + ) + @Test def ubyteArgs9(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d %d", + "1 2 3 4 5 6 7 8 9" + )( + stdio.sprintf( + _, + _, + 1.toUByte, + 2.toUByte, + 3.toUByte, + 4.toUByte, + 5.toUByte, + 6.toUByte, + 7.toUByte, + 8.toUByte, + 9.toUByte + ) + ) + + @Test def ushortValueMin(): Unit = + vatest(c"%d", "0")(stdio.sprintf(_, _, UShort.MinValue)) + @Test def ushortValueMax(): Unit = + vatest(c"%d", "65535")(stdio.sprintf(_, _, UShort.MaxValue)) + @Test def ushortArgs1(): Unit = + vatest(c"%d", "1")(stdio.sprintf(_, _, 1.toUShort)) + @Test def ushortArgs2(): Unit = + vatest(c"%d %d", "1 2")(stdio.sprintf(_, _, 1.toUShort, 2.toUShort)) + @Test def ushortArgs3(): Unit = + vatest(c"%d %d %d", "1 2 3")( + stdio.sprintf(_, _, 1.toUShort, 2.toUShort, 3.toUShort) + ) + @Test def ushortArgs4(): Unit = + vatest(c"%d %d %d %d", "1 2 3 4")( + stdio.sprintf(_, _, 1.toUShort, 2.toUShort, 3.toUShort, 4.toUShort) + ) + @Test def ushortArgs5(): Unit = + vatest(c"%d %d %d %d %d", "1 2 3 4 5")( + stdio.sprintf( + _, + _, + 1.toUShort, + 2.toUShort, + 3.toUShort, + 4.toUShort, + 5.toUShort + ) + ) + @Test def ushortArgs6(): Unit = + vatest( + c"%d %d %d %d %d %d", + "1 2 3 4 5 6" + )( + stdio.sprintf( + _, + _, + 1.toUShort, + 2.toUShort, + 3.toUShort, + 4.toUShort, + 5.toUShort, + 6.toUShort + ) + ) + @Test def ushortArgs7(): Unit = + vatest( + c"%d %d %d %d %d %d %d", + "1 2 3 4 5 6 7" + )( + stdio.sprintf( + _, + _, + 1.toUShort, + 2.toUShort, + 3.toUShort, + 4.toUShort, + 5.toUShort, + 6.toUShort, + 7.toUShort + ) + ) + @Test def ushortArgs8(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d", + "1 2 3 4 5 6 7 8" + )( + stdio.sprintf( + _, + _, + 1.toUShort, + 2.toUShort, + 3.toUShort, + 4.toUShort, + 5.toUShort, + 6.toUShort, + 7.toUShort, + 8.toUShort + ) + ) + @Test def ushortArgs9(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d %d", + "1 2 3 4 5 6 7 8 9" + )( + stdio.sprintf( + _, + _, + 1.toUShort, + 2.toUShort, + 3.toUShort, + 4.toUShort, + 5.toUShort, + 6.toUShort, + 7.toUShort, + 8.toUShort, + 9.toUShort + ) + ) + + @Test def uintValueMin(): Unit = + vatest(c"%u", "0")(stdio.sprintf(_, _, UInt.MinValue)) + @Test def uintValueMax(): Unit = + vatest(c"%u", "4294967295")(stdio.sprintf(_, _, UInt.MaxValue)) + @Test def uintArgs1(): Unit = + vatest(c"%d", "1")(stdio.sprintf(_, _, 1.toUInt)) + @Test def uintArgs2(): Unit = + vatest(c"%d %d", "1 2")(stdio.sprintf(_, _, 1.toUInt, 2.toUInt)) + @Test def uintArgs3(): Unit = + vatest(c"%d %d %d", "1 2 3")( + stdio.sprintf(_, _, 1.toUInt, 2.toUInt, 3.toUInt) + ) + @Test def uintArgs4(): Unit = + vatest(c"%d %d %d %d", "1 2 3 4")( + stdio.sprintf(_, _, 1.toUInt, 2.toUInt, 3.toUInt, 4.toUInt) + ) + @Test def uintArgs5(): Unit = + vatest(c"%d %d %d %d %d", "1 2 3 4 5")( + stdio.sprintf(_, _, 1.toUInt, 2.toUInt, 3.toUInt, 4.toUInt, 5.toUInt) + ) + @Test def uintArgs6(): Unit = + vatest(c"%d %d %d %d %d %d", "1 2 3 4 5 6")( + stdio.sprintf( + _, + _, + 1.toUInt, + 2.toUInt, + 3.toUInt, + 4.toUInt, + 5.toUInt, + 6.toUInt + ) + ) + @Test def uintArgs7(): Unit = + vatest(c"%d %d %d %d %d %d %d", "1 2 3 4 5 6 7")( + stdio.sprintf( + _, + _, + 1.toUInt, + 2.toUInt, + 3.toUInt, + 4.toUInt, + 5.toUInt, + 6.toUInt, + 7.toUInt + ) + ) + @Test def uintArgs8(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d", + "1 2 3 4 5 6 7 8" + )( + stdio.sprintf( + _, + _, + 1.toUInt, + 2.toUInt, + 3.toUInt, + 4.toUInt, + 5.toUInt, + 6.toUInt, + 7.toUInt, + 8.toUInt + ) + ) + @Test def uintArgs9(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d %d", + "1 2 3 4 5 6 7 8 9" + )( + stdio.sprintf( + _, + _, + 1.toUInt, + 2.toUInt, + 3.toUInt, + 4.toUInt, + 5.toUInt, + 6.toUInt, + 7.toUInt, + 8.toUInt, + 9.toUInt + ) + ) + + @Test def ulongValueMin(): Unit = { + vatest(c"%llu", "0")(stdio.sprintf(_, _, ULong.MinValue)) + } + @Test def ulongValueMax(): Unit = { + vatest(c"%llu", "18446744073709551615")(stdio.sprintf(_, _, ULong.MaxValue)) + } + @Test def ulongArgs1(): Unit = + vatest(c"%d", "1")(stdio.sprintf(_, _, 1.toULong)) + @Test def ulongArgs2(): Unit = + vatest(c"%d %d", "1 2")(stdio.sprintf(_, _, 1.toULong, 2.toULong)) + @Test def ulongArgs3(): Unit = + vatest(c"%d %d %d", "1 2 3")( + stdio.sprintf(_, _, 1.toULong, 2.toULong, 3.toULong) + ) + @Test def ulongArgs4(): Unit = + vatest(c"%d %d %d %d", "1 2 3 4")( + stdio.sprintf(_, _, 1.toULong, 2.toULong, 3.toULong, 4.toULong) + ) + @Test def ulongArgs5(): Unit = + vatest(c"%d %d %d %d %d", "1 2 3 4 5")( + stdio.sprintf(_, _, 1.toULong, 2.toULong, 3.toULong, 4.toULong, 5.toULong) + ) + @Test def ulongArgs6(): Unit = + vatest(c"%d %d %d %d %d %d", "1 2 3 4 5 6")( + stdio.sprintf( + _, + _, + 1.toULong, + 2.toULong, + 3.toULong, + 4.toULong, + 5.toULong, + 6.toULong + ) + ) + @Test def ulongArgs7(): Unit = + vatest( + c"%d %d %d %d %d %d %d", + "1 2 3 4 5 6 7" + )( + stdio.sprintf( + _, + _, + 1.toULong, + 2.toULong, + 3.toULong, + 4.toULong, + 5.toULong, + 6.toULong, + 7.toULong + ) + ) + @Test def ulongArgs8(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d", + "1 2 3 4 5 6 7 8" + )( + stdio.sprintf( + _, + _, + 1.toULong, + 2.toULong, + 3.toULong, + 4.toULong, + 5.toULong, + 6.toULong, + 7.toULong, + 8.toULong + ) + ) + @Test def ulongArgs9(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d %d", + "1 2 3 4 5 6 7 8 9" + )( + stdio.sprintf( + _, + _, + 1.toULong, + 2.toULong, + 3.toULong, + 4.toULong, + 5.toULong, + 6.toULong, + 7.toULong, + 8.toULong, + 9.toULong + ) + ) + + @Test def floatArgs1(): Unit = + vatest(c"%1.1f", "1.1")(stdio.sprintf(_, _, 1.1f)) + @Test def floatArgs2(): Unit = + vatest(c"%1.1f %1.1f", "1.1 2.2")(stdio.sprintf(_, _, 1.1f, 2.2f)) + @Test def floatArgs3(): Unit = + vatest(c"%1.1f %1.1f %1.1f", "1.1 2.2 3.3")( + stdio.sprintf(_, _, 1.1f, 2.2f, 3.3f) + ) + @Test def floatArgs4(): Unit = + vatest(c"%1.1f %1.1f %1.1f %1.1f", "1.1 2.2 3.3 4.4")( + stdio.sprintf(_, _, 1.1f, 2.2f, 3.3f, 4.4f) + ) + @Test def floatArgs5(): Unit = + vatest(c"%1.1f %1.1f %1.1f %1.1f %1.1f", "1.1 2.2 3.3 4.4 5.5")( + stdio.sprintf(_, _, 1.1f, 2.2f, 3.3f, 4.4f, 5.5f) + ) + @Test def floatArgs6(): Unit = + vatest(c"%1.1f %1.1f %1.1f %1.1f %1.1f %1.1f", "1.1 2.2 3.3 4.4 5.5 6.6")( + stdio.sprintf(_, _, 1.1f, 2.2f, 3.3f, 4.4f, 5.5f, 6.6f) + ) + @Test def floatArgs7(): Unit = + vatest( + c"%1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f", + "1.1 2.2 3.3 4.4 5.5 6.6 7.7" + )(stdio.sprintf(_, _, 1.1f, 2.2f, 3.3f, 4.4f, 5.5f, 6.6f, 7.7f)) + @Test def floatArgs8(): Unit = + vatest( + c"%1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f", + "1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8" + )(stdio.sprintf(_, _, 1.1f, 2.2f, 3.3f, 4.4f, 5.5f, 6.6f, 7.7f, 8.8f)) + @Test def floatArgs9(): Unit = + vatest( + c"%1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f", + "1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9" + )(stdio.sprintf(_, _, 1.1f, 2.2f, 3.3f, 4.4f, 5.5f, 6.6f, 7.7f, 8.8f, 9.9f)) + + @Test def doubleArgs1(): Unit = + vatest(c"%1.1f", "1.1")(stdio.sprintf(_, _, 1.1d)) + @Test def doubleArgs2(): Unit = + vatest(c"%1.1f %1.1f", "1.1 2.2")(stdio.sprintf(_, _, 1.1d, 2.2d)) + @Test def doubleArgs3(): Unit = + vatest(c"%1.1f %1.1f %1.1f", "1.1 2.2 3.3")( + stdio.sprintf(_, _, 1.1d, 2.2d, 3.3d) + ) + @Test def doubleArgs4(): Unit = + vatest(c"%1.1f %1.1f %1.1f %1.1f", "1.1 2.2 3.3 4.4")( + stdio.sprintf(_, _, 1.1d, 2.2d, 3.3d, 4.4d) + ) + @Test def doubleArgs5(): Unit = + vatest(c"%1.1f %1.1f %1.1f %1.1f %1.1f", "1.1 2.2 3.3 4.4 5.5")( + stdio.sprintf(_, _, 1.1d, 2.2d, 3.3d, 4.4d, 5.5d) + ) + @Test def doubleArgs6(): Unit = + vatest(c"%1.1f %1.1f %1.1f %1.1f %1.1f %1.1f", "1.1 2.2 3.3 4.4 5.5 6.6")( + stdio.sprintf(_, _, 1.1d, 2.2d, 3.3d, 4.4d, 5.5d, 6.6d) + ) + @Test def doubleArgs7(): Unit = + vatest( + c"%1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f", + "1.1 2.2 3.3 4.4 5.5 6.6 7.7" + )(stdio.sprintf(_, _, 1.1d, 2.2d, 3.3d, 4.4d, 5.5d, 6.6d, 7.7d)) + @Test def doubleArgs8(): Unit = + vatest( + c"%1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f", + "1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8" + )(stdio.sprintf(_, _, 1.1d, 2.2d, 3.3d, 4.4d, 5.5d, 6.6d, 7.7d, 8.8d)) + @Test def doubleArgs9(): Unit = + vatest( + c"%1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f", + "1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9" + )(stdio.sprintf(_, _, 1.1d, 2.2d, 3.3d, 4.4d, 5.5d, 6.6d, 7.7d, 8.8d, 9.9d)) + + @Test def mixArgs1(): Unit = + vatest(c"%d %1.1f", "1 1.1")(stdio.sprintf(_, _, 1, 1.1d)) + @Test def mixArgs2(): Unit = + vatest(c"%d %d %1.1f %1.1f", "1 2 1.1 2.2")( + stdio.sprintf(_, _, 1, 2, 1.1d, 2.2d) + ) + @Test def mixArgs3(): Unit = + vatest(c"%d %d %d %1.1f %1.1f %1.1f", "1 2 3 1.1 2.2 3.3")( + stdio.sprintf(_, _, 1, 2, 3, 1.1d, 2.2d, 3.3d) + ) + @Test def mixArgs4(): Unit = + vatest(c"%d %d %d %d %1.1f %1.1f %1.1f %1.1f", "1 2 3 4 1.1 2.2 3.3 4.4")( + stdio.sprintf(_, _, 1, 2, 3, 4, 1.1d, 2.2d, 3.3d, 4.4d) + ) + @Test def mixArgs5(): Unit = + vatest( + c"%d %d %d %d %d %1.1f %1.1f %1.1f %1.1f %1.1f", + "1 2 3 4 5 1.1 2.2 3.3 4.4 5.5" + )(stdio.sprintf(_, _, 1, 2, 3, 4, 5, 1.1d, 2.2d, 3.3d, 4.4d, 5.5d)) + @Test def mixArgs6(): Unit = + vatest( + c"%d %d %d %d %d %d %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f", + "1 2 3 4 5 6 1.1 2.2 3.3 4.4 5.5 6.6" + )(stdio.sprintf(_, _, 1, 2, 3, 4, 5, 6, 1.1d, 2.2d, 3.3d, 4.4d, 5.5d, 6.6d)) + @Test def mixArgs7(): Unit = + vatest( + c"%d %d %d %d %d %d %d %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f", + "1 2 3 4 5 6 7 1.1 2.2 3.3 4.4 5.5 6.6 7.7" + )( + stdio.sprintf( + _, + _, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 1.1d, + 2.2d, + 3.3d, + 4.4d, + 5.5d, + 6.6d, + 7.7d + ) + ) + @Test def mixArgs8(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f", + "1 2 3 4 5 6 7 8 1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8" + )( + stdio.sprintf( + _, + _, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 1.1d, + 2.2d, + 3.3d, + 4.4d, + 5.5d, + 6.6d, + 7.7d, + 8.8d + ) + ) + @Test def mixArgs9(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d %d %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f", + "1 2 3 4 5 6 7 8 9 1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9" + )( + stdio.sprintf( + _, + _, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 1.1d, + 2.2d, + 3.3d, + 4.4d, + 5.5d, + 6.6d, + 7.7d, + 8.8d, + 9.9d + ) + ) +} diff --git a/unit-tests/native/src/test/scala/scala/scalanative/unsafe/TagTest.scala b/unit-tests/native/src/test/scala/scala/scalanative/unsafe/TagTest.scala index 43ddf387d3..96eeed145e 100644 --- a/unit-tests/native/src/test/scala/scala/scalanative/unsafe/TagTest.scala +++ b/unit-tests/native/src/test/scala/scala/scalanative/unsafe/TagTest.scala @@ -232,4 +232,18 @@ class TagTest { assertTrue(tagof[sockaddr_in6].size == sizeof[sockaddr_in6]) assertTrue(tagof[ipv6_mreq].size == sizeof[ipv6_mreq]) } + + @Test def abstractTypeTag(): Unit = { + // https://github.com/scala-native/scala-native/issues/3196 + val PtrAnyClassTag = Tag.Ptr(Tag.Class(classOf[AnyRef])) + object abstractTagWrapper { + type Foo + } + assertEquals(PtrAnyClassTag, tagof[Ptr[abstractTagWrapper.Foo]]) + assertEquals(PtrAnyClassTag, tagof[Ptr[_]]) + assertEquals( + Tag.Ptr(PtrAnyClassTag), + tagof[Ptr[Ptr[abstractTagWrapper.Foo]]] + ) + } } diff --git a/unit-tests/shared/src/test/require-jdk11/org/scalanative/testsuite/javalib/nio/file/FilesTestOnJDK11.scala b/unit-tests/shared/src/test/require-jdk11/org/scalanative/testsuite/javalib/nio/file/FilesTestOnJDK11.scala new file mode 100644 index 0000000000..d7efa8aae8 --- /dev/null +++ b/unit-tests/shared/src/test/require-jdk11/org/scalanative/testsuite/javalib/nio/file/FilesTestOnJDK11.scala @@ -0,0 +1,428 @@ +package org.scalanative.testsuite +package javalib.nio.file + +import java.{lang => jl} +import java.{util => ju} + +import java.nio.charset.StandardCharsets +import java.nio.CharBuffer +import java.nio.file.Files +import java.nio.file.{Path, Paths} +import java.nio.file.{FileAlreadyExistsException, StandardOpenOption} + +import org.junit.Test +import org.junit.Assert._ +import org.junit.{BeforeClass, AfterClass} +import org.junit.Ignore + +import org.scalanative.testsuite.utils.AssertThrows.assertThrows + +class FilesTestOnJDK11 { + import FilesTestOnJDK11._ + + /* Design Notes: + * + * 1) This set of Tests is designed to clean up after itself. That is + * delete all directories and files created. For debugging one + * can comment-out the Files.delete() in the afterClass() static method. + * + * 2) To simplify implementation, The readString() Tests call writeString(). + * This leaves open the possibility of complementary and/or compensating + * errors in the two methods. + * + * In a better World, writeString() would be Test'ed before possibly + * being used by readString(). Currently the order of execution of tests + * is hard to determine and harder (not possible) to specify. + * This Suite is forced to rely on writeString() eventually getting + * strongly tested. + * + * Normally one would expect to see the writeString() tests at the top + * of the file, expecting them to be run before being used by + * readString(). Here, the writeString() methods are later in the file + * because there are hints, but not guarantees, that Tests later in the + * file are run before those earlier. No wonder why it is hard to + * find Test developers. + * + * 3) The comment above the largeWriteStringThenReadString() Test explains + * why it is @Ignore'd in normal Continuous Integration. + * + * 4) There are no tests for CharSequence javax.swing.text.Segment because + * Segment is not implemented by Scala Native. + */ + + @Test def readStringUsingDefaultCharsetUTF8(): Unit = { + val ioPath = getCleanIoPath("utf8_forReadBack") + val dataOut = getDataOut() + + Files.writeString(ioPath, dataOut) + + val dataIn = Files.readString(ioPath) + + assertEquals("data read back does not match data written", dataOut, dataIn) + } + + @Test def readStringUTF16LEUsingExplicitCharsetUTF16LE(): Unit = { + val ioPath = getCleanIoPath("utf16LE_forReadBack") + val dataOut = getDataOut() + + Files.writeString(ioPath, dataOut, StandardCharsets.UTF_16LE) + + val dataIn = Files.readString(ioPath, StandardCharsets.UTF_16LE) + + assertEquals("data read back does not match data written", dataOut, dataIn) + } + + @Test def readStringUTF16BEUsingExplicitCharsetUTF16BE(): Unit = { + val ioPath = getCleanIoPath("utf16BE_forReadBack") + val dataOut = getDataOut() + + Files.writeString(ioPath, dataOut, StandardCharsets.UTF_16BE) + + val dataIn = Files.readString(ioPath, StandardCharsets.UTF_16BE) + + assertEquals("data read back does not match data written", dataOut, dataIn) + } + + @Test def writeStringFromStringUsingDefaultCharsetUTF8(): Unit = { + val ioPath = getCleanIoPath("utf8_file") + val dataOut = getDataOut() + + /* Test, at the same time, correctness of both writing the file and of + * using a variable number of arguments whilst doing so. + * + * Call without "OpenOption" third argument. + * Java documention says this will cause options "CREATE", + * "TRUNCATE_EXISTING", and "WRITE" to be used. CREATE and + * WRITE are exercised here. + */ + + Files.writeString(ioPath, dataOut) + + verifySmallUtf8Payload(ioPath) + } + + @Test def writeStringFromStringUsingExplicitCharsetUTF16LE(): Unit = { + val ioPath = getCleanIoPath("utf16LE_file") + val dataOut = getDataOut() + + // format: off + val expectedValues = Array( + 0xAC, + 0x20, + 0xA3, + 0x00, + 0x24, + 0x00, + ).map (_.toByte) + // format: on + + /* Euro, Pound, and Dollar characters are all 2 bytes in UTF-16. + * End-of-line newline will be 2 bytes. Windows carriage-return (CR), + * if present, will be another two bytes. + */ + val expectedDataLength = expectedValues.size + (EOLlen * 2) + + /* Write String out in "odd, non-standard 16LE" format instead of + * Java standard 16BE just to shake things up and invite faults. + */ + Files.writeString(ioPath, dataOut, StandardCharsets.UTF_16LE) + + val bytesRead = Files.readAllBytes(ioPath) + + assertEquals("bytes read", expectedDataLength, bytesRead.length) + + for (j <- 0 until (expectedValues.length)) { + assertEquals( + s"write/read mismatch at index ${j}", + expectedValues(j), + bytesRead(j) + ) + } + + verifyUtf16LeEOL(bytesRead) + } + + @Test def writeStringFromStringUsingExplicitCharsetUTF16BE(): Unit = { + val ioPath = getCleanIoPath("utf16BE_file") + val dataOut = getDataOut() + + // format: off + val expectedValues = Array( + 0x20, + 0xAC, + 0x00, + 0xA3, + 0x00, + 0x24, + ).map (_.toByte) + // format: on + + /* Euro, Pound, and Dollar characters are all 2 bytes in UTF-16. + * End-of-line newline will be 2 bytes. Windows carriage-return (CR), + * if present, will be another two bytes. + */ + val expectedDataLength = expectedValues.size + (EOLlen * 2) + + // Write as represented in Java Characters, Big Endian or network order. + Files.writeString(ioPath, dataOut, StandardCharsets.UTF_16BE) + + val bytesRead = Files.readAllBytes(ioPath) + + assertEquals("bytes read", expectedDataLength, bytesRead.length) + + for (j <- 0 until (expectedValues.length)) { + assertEquals( + s"write/read mismatch at index ${j}", + expectedValues(j), + bytesRead(j) + ) + } + + verifyUtf16BeEOL(bytesRead) + } + + @Test def writeStringFromCharBufferWrapSmallArray(): Unit = { + val ioPath = getCleanIoPath("CharBufferWrapSmallArray") + val dataOut = getDataOut() + + val output = CharBuffer.wrap(dataOut.toArray[Char]) + Files.writeString(ioPath, output) + + verifySmallUtf8Payload(ioPath) + } + + @Test def writeStringFromCharBufferWrapSmallString(): Unit = { + val ioPath = getCleanIoPath("CharBufferWrapSmallString") + val dataOut = getDataOut() + + val output = CharBuffer.wrap(dataOut) + Files.writeString(ioPath, output) + + verifySmallUtf8Payload(ioPath) + } + + @Test def writeStringFromStringBuilderSmall(): Unit = { + val ioPath = getCleanIoPath("StringBuilderSmall") + val dataOut = getDataOut() + + val output = new jl.StringBuilder(dataOut) + Files.writeString(ioPath, output) + + verifySmallUtf8Payload(ioPath) + } + + @Test def writeStringFromStringBufferSmall(): Unit = { + val ioPath = getCleanIoPath("StringBufferSmall") + val dataOut = getDataOut() + + val output = new jl.StringBuffer(dataOut) + Files.writeString(ioPath, output) + + verifySmallUtf8Payload(ioPath) + } + + @Test def writeStringFromStringUsingOptionArg(): Unit = { + /* Check that both writeString() variants properly pass an explicitly + * specified file open attributes varargs argument. + */ + val ioPath = getCleanIoPath("utf8_forCreateNewOptionArg") + val dataOut = getDataOut() + + Files.createFile(ioPath) + + assertThrows( + classOf[FileAlreadyExistsException], + Files.writeString( + ioPath, + dataOut, + StandardCharsets.UTF_8, + StandardOpenOption.CREATE_NEW + ) + ) + + assertThrows( + classOf[FileAlreadyExistsException], + Files.writeString( + ioPath, + dataOut, + StandardCharsets.UTF_8, + StandardOpenOption.CREATE_NEW + ) + ) + } + + /* This Test is next to essential for both development & debugging. + * It is Ignore'd for normal Continuous Integration (CI) because, by + * its very purpose it creates and verifies a larger-than-a-breadbox + * file. In CI, this takes CPU & IO resources and has the possibility + * of leaving a large otherwise useless file lying around. + * + * The SN standard is to use "/* */" for multiline comments, as is done here. + * If someone if offended by the @Ignore, they could convert the contents + * below to "//" line comments then enclose the region in a "/* */" pair. + */ + @Ignore + @Test def largeWriteStringThenReadString(): Unit = { + /* Same logic as small readString() tests but with enough data to + * exercise any internal buffering. + */ + + val ioPath = getCleanIoPath("LargeFile") + + /* Use an unexpected string size to try to reveal defects in any + * underlying buffering. + * + * This test does not, and should not, know the sizes of any + * internal buffers used by writeString() and readString(). + * If any such exist, they are likely to have a power-of-two size + * and more likely to have an even size. Developers like even sizes. + * + * Add an odd, and preferably prime, increment to a "reasonable" string + * size to almost certainly force any last buffer to be partial. + */ + + /* For a String filled with all but two 1-byte UTF-8 and two 2-byte + * UTF-8 characters, expect an actual file size of + * (40960 + 2 + 41) = 41003 bytes. + */ + val maxStringSize = (40 * 1024) + 41 + + val startChar = '\u03B1' // Greek lowercase alpha; file bytes 0xCE 0xB1 + val endChar = '\u03A9' // Greek uppercase omega; file bytes 0xCE 0xA9 + + val dataOut = getLargeDataOut(maxStringSize, startChar, endChar) + + Files.writeString(ioPath, dataOut) + + val dataIn = Files.readString(ioPath) + + assertEquals("Unexpected dataIn size", maxStringSize, dataIn.size) + assertEquals( + "dataOut & dataIn sizes do not match", + dataOut.size, + dataIn.size + ) + + assertEquals("Unexpected first dataIn character", startChar, dataIn(0)) + assertEquals( + "Unexpected last dataIn character", + endChar, + dataIn(maxStringSize - 1) + ) + + assertEquals("data read back does not match data written", dataOut, dataIn) + } +} + +object FilesTestOnJDK11 { + private var orgPath: Path = _ + private var workPath: Path = _ + + final val testsuitePackagePrefix = "org.scalanative." + + val EOL = System.getProperty("line.separator") // end-of-line + val EOLlen = EOL.size + + private def getCleanIoPath(fileName: String): Path = { + val ioPath = workPath.resolve(fileName) + Files.deleteIfExists(ioPath) + ioPath + } + + def getDataOut(): String = { + /* Euro sign, Pound sign, dollarSign + * Ref: https://www.compart.com/en/unicode + */ + + "\u20AC\u00A3\u0024" + EOL // ensure file ends with OS end-of-line. + } + + def getLargeDataOut(maxSize: Int, startChar: Char, endChar: Char): String = { + val sb = new StringBuilder(maxSize) + sb.insert(0, startChar) + sb.setLength(maxSize - 1) // extend to size, filled with NUL characters + sb.append(endChar) // final size should be maxSize + // leave the string _without_ a terminal line.separator to trip things up. + sb.toString() + } + + def verifyUtf8EOL(bytes: Array[Byte]): Unit = { + if (EOLlen == 2) + assertEquals("Expected Windows CR", '\r', bytes(bytes.length - EOLlen)) + + assertEquals("Expected newline", '\n', bytes(bytes.length - 1)) + } + + def verifyUtf16LeEOL(bytes: Array[Byte]): Unit = { + if (EOLlen == 2) + assertEquals("Expected Windows CR", '\r', bytes(bytes.length - 4)) + + assertEquals("Expected newline", '\n', bytes(bytes.length - 2)) + } + + def verifyUtf16BeEOL(bytes: Array[Byte]): Unit = { + if (EOLlen == 2) + assertEquals("Expected Windows CR", '\r', bytes(bytes.length - 3)) + + assertEquals("Expected newline", '\n', bytes(bytes.length - 1)) + } + + def verifySmallUtf8Payload(ioPath: Path): Unit = { + // format: off + val expectedValues = Array( + 0xE2, 0x82, 0xAC, // EURO SIGN + 0xC2, 0xA3, // POUND (sterling) SIGN + 0x24, // DOLLAR SIGN + ).map (_.toByte) + // format: on + + val expectedDataLength = expectedValues.size + EOLlen + + val bytesRead = Files.readAllBytes(ioPath) + assertEquals("bytes read", expectedDataLength, bytesRead.length) + + for (j <- 0 until (expectedValues.length)) { + assertEquals( + s"write/read mismatch at index ${j}", + expectedValues(j), + bytesRead(j) + ) + } + + verifyUtf8EOL(bytesRead) + } + + @BeforeClass + def beforeClass(): Unit = { + /* Scala package statement does not allow "-", so the testsuite + * packages are all "scalanative", not the "scala-native" used + * in distribution artifacts or the name of the GitHub repository. + */ + orgPath = Files.createTempDirectory(s"${testsuitePackagePrefix}testsuite") + + val tmpPath = + orgPath.resolve(s"javalib/nio/file/${this.getClass().getSimpleName()}") + workPath = Files.createDirectories(tmpPath) + } + + @AfterClass + def afterClass(): Unit = { + // Delete items created by this test. + + // Avoid blind "rm -r /" and other oops! catastrophes. + if (!orgPath.toString().contains(s"${testsuitePackagePrefix}")) + fail(s"Refusing recursive delete of unknown path: ${orgPath}") + + // Avoid resize overhead; 64 is a high guess. deque will grow if needed. + val stack = new ju.ArrayDeque[Path](64) + val stream = Files.walk(orgPath) + + try { + // Delete Files; start with deepest & work upwards to beginning of walk. + stream.forEach(stack.addFirst(_)) // push() Path + stack.forEach(Files.delete(_)) // pop() a Path then delete it's File. + } finally { + stream.close() + } + } +} diff --git a/unit-tests/native/src/test/scala-2.12+/scala/Issues212PlusTest.scala b/unit-tests/shared/src/test/scala-2.12+/scala/Issues212PlusTest.scala similarity index 100% rename from unit-tests/native/src/test/scala-2.12+/scala/Issues212PlusTest.scala rename to unit-tests/shared/src/test/scala-2.12+/scala/Issues212PlusTest.scala diff --git a/unit-tests/shared/src/test/scala-3/scala/issues/Scala3IssuesTest.scala b/unit-tests/shared/src/test/scala-3/scala/issues/Scala3IssuesTest.scala index 860763789a..03f1e63ef1 100644 --- a/unit-tests/shared/src/test/scala-3/scala/issues/Scala3IssuesTest.scala +++ b/unit-tests/shared/src/test/scala-3/scala/issues/Scala3IssuesTest.scala @@ -76,6 +76,17 @@ class Scala3IssuesTest: assertEquals("42", q.baz(21)) } + @Test def issue3014(): Unit = { + import scala.issues.issue3014._ + def useUnit(unit: TimeUnit): Long = { + // Was throwing `MatchError` when calling `toNanos` + unit.toNanos(1L) + } + + assertEquals(1L, useUnit(TimeUnit.Nanos)) + assertThrows(classOf[NullPointerException], () => useUnit(null)) + } + end Scala3IssuesTest private object issue2484 { @@ -98,3 +109,15 @@ private object issue2484 { def map[A, B](fa: F[A])(f: A => B): F[B] } } + +private object issue3014 { + enum TimeUnit { + case Millis + case Nanos + + def toNanos(value: Long): Long = this match { + case Millis => value * 1000000 + case Nanos => value + } + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/lang/IterableSpliteratorTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/lang/IterableSpliteratorTest.scala new file mode 100644 index 0000000000..ec6ef6d986 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/lang/IterableSpliteratorTest.scala @@ -0,0 +1,50 @@ +package org.scalanative.testsuite.javalib.lang + +import java.util.Spliterator +import java.nio.file.{Path, Paths} + +import org.junit.Test +import org.junit.Assert._ + +import org.scalanative.testsuite.utils.AssertThrows.assertThrows + +class IterableSpliteratorTest { + /* nio.Path extends Iterable and does not override Iterable's default + * spliterator() method. Use that knowledge to test the default + * implementation of Iterable#spliterator. + * + * Do not use a class in the Collection hierarchy because Collection + * overrides the Iterable#spliterator method under test. + */ + @Test def defaultSpliteratorShouldBeWellFormed(): Unit = { + + // Let compiler check type returned is as expected. + val spliter: Spliterator[Path] = Paths.get(".").spliterator() + assertNotNull("Null coll.spliterator", spliter) + + assertEquals("estimateSize", Long.MaxValue, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + -1, // spliterator is known to not have SIZED characteristic + spliter.getExactSizeIfKnown() + ) + + // Default method always reports NO characteristics set. + assertEquals( + "characteristics", + 0, + spliter.characteristics() + ) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + // Check that both the count is right and that each element is as expected. + + var count = 0 + + spliter.forEachRemaining((p: Path) => count += 1) + + assertEquals("forEachRemaining size", 1, count) + + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/nio/file/DirectoryStreamTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/nio/file/DirectoryStreamTest.scala index 6f2df74623..d91a4bc19e 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/nio/file/DirectoryStreamTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/nio/file/DirectoryStreamTest.scala @@ -91,4 +91,68 @@ class DirectoryStreamTest { assertFalse(it.hasNext()) assertThrows(classOf[NoSuchElementException], it.next()) } + + /* Issue #2937 + * + * Note Well - This Test is fragile/sensitiveToChange/lessThanRobust. + * + * This Test is fragile in the sense that it assumes/requires that + * the current working directory contains at least one .sbt file. + * Such is true at the time this test is created. The current working + * directory when TestMain is started is the project directory. That + * directory contains a .sbt file because that file was used to start + * the execution of TestMain; Worm Ouroboros. + * + * An alternative approach of saving and restoring the current working + * directory was considered but judged to be more fragile. + */ + @Test def normalizesAcceptCandidatePathExpectMatch(): Unit = { + + val passGlob = "*.sbt" // passes in JVM + + // Path of current working directory, from empty string. + val emptyPathStream = Files.newDirectoryStream(Paths.get(""), passGlob) + val emptyPathPassed = emptyPathStream.iterator().hasNext() // count >= 1 + emptyPathStream.close() + + assertTrue( + s"current working directory stream has no match for '${passGlob}'", + emptyPathPassed + ) + + // Path of current working directory, from dot string. + val dotPathStream = Files.newDirectoryStream(Paths.get("."), passGlob) + val dotPathPassed = dotPathStream.iterator().hasNext() // count >= 1 + dotPathStream.close() + + assertTrue( + s"dot directory stream has no match for '${passGlob}'", + dotPathPassed + ) + } + + @Test def normalizesAcceptCandidatePathExpectNoMatch(): Unit = { + + val failGlob = "./*.sbt" // fails in JVM and should fail here + + // Path of current working directory, from empty string. + val emptyPathStream = Files.newDirectoryStream(Paths.get(""), failGlob) + val emptyPathPassed = emptyPathStream.iterator().hasNext() // count >= 1 + emptyPathStream.close() + + assertFalse( + s"current working directory stream has a match for '${failGlob}'", + emptyPathPassed + ) + + // Path of current working directory, from dot string. + val dotPathStream = Files.newDirectoryStream(Paths.get("."), failGlob) + val dotPathPassed = dotPathStream.iterator().hasNext() // count >= 1 + dotPathStream.close() + + assertFalse( + s"dot directory stream has a match for '${failGlob}'", + dotPathPassed + ) + } } diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/nio/file/PathMatcherGlobTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/nio/file/PathMatcherGlobTest.scala index 633982d57d..0e791cb4e2 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/nio/file/PathMatcherGlobTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/nio/file/PathMatcherGlobTest.scala @@ -1,4 +1,5 @@ -package org.scalanative.testsuite.javalib.nio.file +package org.scalanative.testsuite +package javalib.nio.file import java.nio.file._ @@ -200,4 +201,14 @@ class PathMatcherGlobTest { pass("*", "") } + /* Issue #2937 + * Glob itself should not match glob "*.sbt" with "./local.sbt". + * Files.getNewDirectoryStream() must normalize candidate path before + * handing it off to glob. + */ + @Test def correctMatchingOfInitialDotSlash(): Unit = { + pass("*.sbt", "local.sbt") // establish baseline + pass("./*.sbt", "./local.sbt") + fail("*.sbt", "./local.sbt") // glob "*" will not cross "/", so no match + } } diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/ArraysSpliteratorTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/ArraysSpliteratorTest.scala new file mode 100644 index 0000000000..77962f6275 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/ArraysSpliteratorTest.scala @@ -0,0 +1,492 @@ +package org.scalanative.testsuite.javalib.util + +import java.util.{Arrays, Spliterator} + +import org.junit.Test +import org.junit.Assert._ + +import org.scalanative.testsuite.utils.AssertThrows.assertThrows + +/* Test Arrays.spliterator() methods. They were added for Scala Native after + * the port from Scala.js. + * + * These Tests transitively exercise associated methods from + * Spliterators and Spliterator. + */ + +class ArraysSpliteratorTest { + + // characteristics returned by all javalib Arrays.spliterator() methods. + val stdRequiredPresentCharacteristics = Seq( + Spliterator.SIZED, + Spliterator.SUBSIZED, + Spliterator.ORDERED, + Spliterator.IMMUTABLE + ) + + // guard getComparator() throw + val stdRequiredAbsentCharacteristics = Seq(Spliterator.SORTED) + + @Test def spliteratorOfDoubleFromArray: Unit = { + type T = Double + val expectedElements = Array( + 0.0, 10.1, 20.2, 30.3, 44.4, 55.5, 66.6 + ) + + val expectedSize = expectedElements.size + + // Let compiler check returned type is as expected. + val spliter: Spliterator.OfDouble = Arrays.spliterator(expectedElements) + assertNotNull("Null array.spliterator", spliter) + + // check that spliterator has required characteristics and no others. + SpliteratorsTest.verifyCharacteristics( + spliter, + stdRequiredPresentCharacteristics, + stdRequiredAbsentCharacteristics + ) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the size & each element seen are as expected. + + assertTrue( + "tryAdvance itself failed", + spliter.tryAdvance((e: T) => + assertEquals( + "tryAdvance contents do not match,", + expectedElements(0), + e, + 0.0001 + ) + ) + ) + + var count = 1 + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e, + 0.0001 + ) + count += 1 + }) + assertEquals("forEachRemaining count", expectedSize, count) + } + + @Test def spliteratorOfDoubleFromArrayRange: Unit = { + type T = Double + val expectedElements = Array( + 1.0, 10.1, 20.2, 30.3, 44.4, 55.5, 66.6 + ) + + val sliceStartIndex = 1 + val sliceEndIndex = 5 + val expectedSliceSize = sliceEndIndex - sliceStartIndex + + // Let compiler check returned type is as expected. + val spliter: Spliterator.OfDouble = Arrays.spliterator( + expectedElements, + sliceStartIndex, + sliceEndIndex + ) + assertNotNull("Null array.spliterator", spliter) + + // check that spliterator has required characteristics and no others. + SpliteratorsTest.verifyCharacteristics( + spliter, + stdRequiredPresentCharacteristics, + stdRequiredAbsentCharacteristics + ) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSliceSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSliceSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + assertTrue( + "tryAdvance itself failed", + spliter.tryAdvance((e: T) => + assertEquals( + "tryAdvance contents do not match,", + expectedElements(sliceStartIndex), + e, + 0.0001 + ) + ) + ) + + var count = 1 + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(sliceStartIndex + count), + e, + 0.0001 + ) + count += 1 + }) + assertEquals("forEachRemaining count", expectedSliceSize, count) + } + + @Test def spliteratorOfIntFromArray: Unit = { + type T = Int + val expectedElements = Array( + 0, 1, 2, 3, 4, 5, 6 + ) + + val expectedSize = expectedElements.size + + // Let compiler check returned type is as expected. + val spliter: Spliterator.OfInt = Arrays.spliterator(expectedElements) + assertNotNull("Null array.spliterator", spliter) + + // check that spliterator has required characteristics and no others. + SpliteratorsTest.verifyCharacteristics( + spliter, + stdRequiredPresentCharacteristics, + stdRequiredAbsentCharacteristics + ) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the size & each element seen are as expected. + + assertTrue( + "tryAdvance itself failed", + spliter.tryAdvance((e: T) => + assertEquals( + "tryAdvance contents do not match,", + expectedElements(0), + e + ) + ) + ) + + var count = 1 + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining count", expectedSize, count) + } + + @Test def spliteratorOfIntFromArrayRange: Unit = { + type T = Int + val expectedElements = Array( + 1, 11, 22, 33, 44, 55, 66 + ) + + val sliceStartIndex = 1 + val sliceEndIndex = 5 + val expectedSliceSize = sliceEndIndex - sliceStartIndex + + // Let compiler check returned type is as expected. + val spliter: Spliterator.OfInt = Arrays.spliterator( + expectedElements, + sliceStartIndex, + sliceEndIndex + ) + assertNotNull("Null array.spliterator", spliter) + + // check that spliterator has required characteristics and no others. + SpliteratorsTest.verifyCharacteristics( + spliter, + stdRequiredPresentCharacteristics, + stdRequiredAbsentCharacteristics + ) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSliceSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSliceSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + assertTrue( + "tryAdvance itself failed", + spliter.tryAdvance((e: T) => + assertEquals( + "tryAdvance contents do not match,", + expectedElements(sliceStartIndex), + e + ) + ) + ) + + var count = 1 + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(sliceStartIndex + count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining count", expectedSliceSize, count) + } + + @Test def spliteratorOfLongFromArray: Unit = { + type T = Long + val expectedElements = Array( + 0L, 1L, 2L, 3L, 4L, 5L, 6L + ) + + val expectedSize = expectedElements.size + + // Let compiler check returned type is as expected. + val spliter: Spliterator.OfLong = Arrays.spliterator(expectedElements) + assertNotNull("Null array.spliterator", spliter) + + // check that spliterator has required characteristics and no others. + SpliteratorsTest.verifyCharacteristics( + spliter, + stdRequiredPresentCharacteristics, + stdRequiredAbsentCharacteristics + ) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the size & each element seen are as expected. + + assertTrue( + "tryAdvance itself failed", + spliter.tryAdvance((e: T) => + assertEquals( + "tryAdvance contents do not match,", + expectedElements(0), + e + ) + ) + ) + + var count = 1 + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining count", expectedSize, count) + } + + @Test def spliteratorOfLongFromArrayRange: Unit = { + type T = Long + val expectedElements = Array( + 0, 11L, 22L, 33L, 44L, 55L, 66L + ) + + val sliceStartIndex = 1 + val sliceEndIndex = 5 + val expectedSliceSize = sliceEndIndex - sliceStartIndex + + // Let compiler check returned type is as expected. + val spliter: Spliterator.OfLong = Arrays.spliterator( + expectedElements, + sliceStartIndex, + sliceEndIndex + ) + assertNotNull("Null array.spliterator", spliter) + + // check that spliterator has required characteristics and no others. + SpliteratorsTest.verifyCharacteristics( + spliter, + stdRequiredPresentCharacteristics, + stdRequiredAbsentCharacteristics + ) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSliceSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSliceSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + assertTrue( + "tryAdvance itself failed", + spliter.tryAdvance((e: T) => + assertEquals( + "tryAdvance contents do not match,", + expectedElements(sliceStartIndex), + e + ) + ) + ) + + var count = 1 + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(sliceStartIndex + count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining count", expectedSliceSize, count) + } + + @Test def spliteratorOfTypeFromArray: Unit = { + type T = String + val expectedElements = Array( + "Bertha von Suttner", + "Jane Addams", + "Emily Greene Balch", + "Betty Williams", + "Mairead Corrigan", + "Alva Myrdal" + ) + + val expectedSize = expectedElements.size + + // Let compiler check returned type is as expected. + val spliter: Spliterator[T] = Arrays.spliterator(expectedElements) + assertNotNull("Null array.spliterator", spliter) + + // check that spliterator has required characteristics and no others. + SpliteratorsTest.verifyCharacteristics( + spliter, + stdRequiredPresentCharacteristics, + stdRequiredAbsentCharacteristics + ) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the size & each element seen are as expected. + + assertTrue( + "tryAdvance itself failed", + spliter.tryAdvance((e: T) => + assertEquals( + "tryAdvance contents do not match,", + expectedElements(0), + e + ) + ) + ) + + var count = 1 + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining count", expectedSize, count) + } + + @Test def spliteratorOfTypeFromArrayRange: Unit = { + type T = String + val expectedElements = Array( + "nul'", + "odin", + "dva", + "tri", + "cotiri", + "p'at", + "sist'" + ) + + val sliceStartIndex = 1 + val sliceEndIndex = 5 + val expectedSliceSize = sliceEndIndex - sliceStartIndex + + // Let compiler check returned type is as expected. + val spliter: Spliterator[T] = Arrays.spliterator( + expectedElements, + sliceStartIndex, + sliceEndIndex + ) + assertNotNull("Null array.spliterator", spliter) + + // check that spliterator has required characteristics and no others. + SpliteratorsTest.verifyCharacteristics( + spliter, + stdRequiredPresentCharacteristics, + stdRequiredAbsentCharacteristics + ) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSliceSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSliceSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + assertTrue( + "tryAdvance itself failed", + spliter.tryAdvance((e: T) => + assertEquals( + "tryAdvance contents do not match,", + expectedElements(sliceStartIndex), + e + ) + ) + ) + + var count = 1 + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(sliceStartIndex + count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining count", expectedSliceSize, count) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/CollectionDefaultSpliteratorTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/CollectionDefaultSpliteratorTest.scala new file mode 100644 index 0000000000..1599b34e1e --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/CollectionDefaultSpliteratorTest.scala @@ -0,0 +1,70 @@ +package org.scalanative.testsuite.javalib.util + +import java.util.Spliterator + +import org.junit.Test +import org.junit.Assert._ + +import org.scalanative.testsuite.utils.AssertThrows.assertThrows + +class CollectionDefaultSpliteratorTest { + @Test def defaultSpliteratorShouldBeWellFormed(): Unit = { + val expectedElements = Array( + "Aiopis", + "Antheia", + "Donakis", + "Calypso", + "Mermesa", + "Nelisa", + "Tara" + ) + + val expectedSize = expectedElements.size + val foundElements = new Array[String](expectedSize) + + val coll = TrivialImmutableCollection(expectedElements: _*) + assertEquals(expectedSize, coll.size()) + + val spliter = coll.spliterator() + assertNotNull("Null coll.spliterator", spliter) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + val required = Spliterator.SIZED | Spliterator.SUBSIZED + + assertEquals( + "characteristics", + required, + spliter.characteristics() & required + ) + + assertTrue("hasCharacteristics", spliter.hasCharacteristics(required)) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + // Check that both the count is right and that each element is as expected. + + var count = 0 + + // forEachRemaining() exercises tryAdvance() internally. + spliter.forEachRemaining((str: String) => { + foundElements(count) = str + count += 1 + }) + + assertEquals("forEachRemaining size", expectedSize, count) + + // Are contents equal? + for (j <- 0 until expectedSize) + assertEquals( + s"forEachRemaining contents(${j})", + expectedElements(j), + foundElements(j) + ) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/CollectionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/CollectionTest.scala index e3f4463501..5ae621bc88 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/CollectionTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/CollectionTest.scala @@ -1,9 +1,10 @@ // Ported from Scala.js commit: f9fc1ae dated: 2020-03-06 +// Spliterator test added to celebrate Scala Native multithreading. package org.scalanative.testsuite.javalib.util import java.{util => ju, lang => jl} - +import java.util.Spliterator import org.junit.Test import org.junit.Assert._ @@ -15,6 +16,18 @@ import scala.reflect.ClassTag import org.scalanative.testsuite.utils.AssertThrows.assertThrows import Utils._ +/* Design Note: + * Note the "trait" keyword and not the "class" keyword. That + * means that CollectionTest does not run by itself. It is only run + * when other tests which extend this trait are run. + * "sbt tests3/testOnly *.CollectionTest" will fail. If you are lucky + * it will take only a few days of your life to understand the failure. + * If you are even luckier, you will remember the cause when you + * encounter the quirk six months or a year later. + * + * "sbt tests3/testOnly *.AbstractCollectionTest", for one, should work. + */ + trait CollectionTest extends IterableTest { def factory: CollectionFactory @@ -284,6 +297,30 @@ trait CollectionTest extends IterableTest { ) } + @Test def spliteratorShouldExist(): Unit = { + /* CollectionTest is a trait, which get mixed into the tests for + * several Collections. Spliterators() tend to be tailored to the + * individual collection: the whole reason for overriding the default + * implementation. + * + * Trying to account here for some Collections using the default + * Collection.spliterator() and some overriding it quickly leads to + * a tangled mess. + * + * CollectionDefaultSpliteratorTest.scala exercises the default + * Collection.spliterator() method using a collection know to use + * that implementation. Because it is a separate test (and a "class"), + * it is called once, in a known environment. + */ + val coll = + factory.fromElements[String]("Aegle", "Arethusa", "Hesperethusa") + + val expectedSize = 3 + assertEquals(expectedSize, coll.size()) + + val spliter = coll.spliterator() + assertNotNull("Null coll.spliterator", spliter) + } } trait CollectionFactory extends IterableFactory { diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/SpliteratorsTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/SpliteratorsTest.scala new file mode 100644 index 0000000000..f6531047fd --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/SpliteratorsTest.scala @@ -0,0 +1,1571 @@ +package org.scalanative.testsuite.javalib.util + +import java.util.{PrimitiveIterator, Spliterator, Spliterators} + +import org.junit.Test +import org.junit.Assert._ + +import org.scalanative.testsuite.utils.AssertThrows.assertThrows + +/* The vigilant observer will note that there is a SpliteratorsTest, with + * and 's', but no independent SpliteratorTest, without an 's'. This is + * because the generation (Spliterators) and verification (Spliterator) + * of a spliterator() closely depend on the specified conditions in + * each other: Tests are best kept close together, in time & space. + */ + +/* Java 8 introduced a number of ways of obtaining a spliterator. + * This file tests the static methods of Spliterator. It passes + * Arrays & Collections to Spliterator methods rather than calling + * the spliterator method of the Array or Collection. + * + * To get a proper overview, it is worth knowing that other files test + * other ways of obtaining spliterators. A partial list: + * javalib.lang.IterableSpliteratorTest + * javalib.util.ArraysSpliteratorTest + * javalib.util.CollectionDefaultSpliteratorTest + * + * Classes which override the default methods above may/should have + * their own Tests. + */ + +object SpliteratorsTest { + private val maskNames = Map( + 0x00000001 -> "DISTINCT", + 0x00000004 -> "SORTED", + 0x00000010 -> "ORDERED", + 0x00000040 -> "SIZED", + 0x00000100 -> "NONNULL", + 0x00000400 -> "IMMUTABLE", + 0x00001000 -> "CONCURRENT", + 0x00004000 -> "SUBSIZED" + ) + + // convert characteristics bit mask to its corresponding name, or else hex. + private def maskToName(mask: Int): String = + maskNames.getOrElse(mask, s"0x${mask.toHexString.toUpperCase}") + + def verifyCharacteristics[T]( + splItr: Spliterator[T], + requiredPresent: Seq[Int], + requiredAbsent: Seq[Int] + ): Unit = { + /* The splItr.hasCharacteristics() and splItr.characteristics() + * sections both seek the same information: Does the spliterator report + * the required characteristics and no other. They ask the question + * in slightly different ways to exercise each of the two Spliterator + * methods. The answers should match, belt & suspenders. + */ + + for (rp <- requiredPresent) { + assertTrue( + s"missing requiredPresent characteristic: ${maskToName(rp)}", + splItr.hasCharacteristics(rp) + ) + } + + for (rp <- requiredAbsent) { + assertFalse( + s"found requiredAbsent characteristic: ${maskToName(rp)}", + splItr.hasCharacteristics(rp) + ) + } + + val sc = splItr.characteristics() + val requiredPresentMask = requiredPresent.fold(0)((x, y) => x | y) + + val unknownBits = sc & ~requiredPresentMask + val unknownBitsMsg = s"0X${unknownBits.toHexString}" + assertEquals( + s"unexpected characteristics, unknown mask: ${unknownBitsMsg}", + 0, + unknownBits + ) + } +} + +class SpliteratorsTest { + import SpliteratorsTest._ + + @Test def emptyDoubleSpliterator(): Unit = { + type T = Double + val expectedSize = 0 + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfDouble = Spliterators.emptyDoubleSpliterator() + assertNotNull("Null coll.spliterator", spliter) + + // spliterator should have required characteristics and no others. + val requiredPresent = Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + val requiredAbsent = Seq(Spliterator.SORTED) // guard getComparator() throw + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + assertFalse("tryAdvance", spliter.tryAdvance((_: T) => ())) + + var count = 0 + spliter.forEachRemaining((_: T) => { count += 1 }) + assertEquals("forEachRemaining size", expectedSize, count) + } + + @Test def emptyIntSpliterator(): Unit = { + type T = Int + val expectedSize = 0 + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfInt = Spliterators.emptyIntSpliterator() + assertNotNull("Null coll.spliterator", spliter) + + // spliterator should have required characteristics and no others. + val requiredPresent = Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + val requiredAbsent = Seq(Spliterator.SORTED) // guard getComparator() throw + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + assertFalse("tryAdvance", spliter.tryAdvance((_: T) => ())) + + var count = 0 + spliter.forEachRemaining((_: T) => { count += 1 }) + assertEquals("forEachRemaining size", expectedSize, count) + } + + @Test def emptyLongSpliterator(): Unit = { + type T = Long + val expectedSize = 0 + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfLong = Spliterators.emptyLongSpliterator() + assertNotNull("Null coll.spliterator", spliter) + + // spliterator should have required characteristics and no others. + val requiredPresent = Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + val requiredAbsent = Seq(Spliterator.SORTED) // guard getComparator() throw + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + assertFalse("tryAdvance", spliter.tryAdvance((_: T) => ())) + + var count = 0 + spliter.forEachRemaining((_: T) => { count += 1 }) + assertEquals("forEachRemaining size", expectedSize, count) + } + + @Test def emptySpliterator(): Unit = { + type T = String + val expectedSize = 0 + + // Let compiler check type returned is expected. + val spliter: Spliterator[T] = Spliterators.emptySpliterator[T]() + assertNotNull("Null coll.spliterator", spliter) + + // spliterator should have required characteristics and no others. + val requiredPresent = Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + val requiredAbsent = Seq(Spliterator.SORTED) // guard getComparator() throw + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + assertFalse("tryAdvance", spliter.tryAdvance((_: T) => ())) + + var count = 0 + spliter.forEachRemaining((_: T) => { count += 1 }) + assertEquals("forEachRemaining size", expectedSize, count) + } + + @Test def primitiveIteratorFromSpliteratorDouble(): Unit = { + val expectedElements = Array( + 0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6 + ) + val expectedSize = expectedElements.size + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfDouble = Spliterators.spliterator( + expectedElements, + Spliterator.SIZED | Spliterator.SUBSIZED + ) + assertNotNull("Null array.spliterator", spliter) + + // Check that iterator() call returns expected type. + val pItrDouble: PrimitiveIterator.OfDouble = Spliterators.iterator(spliter) + assertNotNull("Null PrimitiveIterator.OfDouble", pItrDouble) + + // Now verify the PrimitiveIterator.OfDouble + + assertTrue( + s"unexpected empty PrimitiveIterator", + pItrDouble.hasNext() + ) + + var count = 0 + pItrDouble.forEachRemaining((e: Double) => { + assertEquals(s"failed match", expectedElements(count), e, 0.0001) + count += 1 + }) + } + + @Test def primitiveIteratorFromSpliteratorInt(): Unit = { + val expectedElements = Array( + 0, 1, 2, 3, 4, 5, 6 + ) + val expectedSize = expectedElements.size + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfInt = Spliterators.spliterator( + expectedElements, + Spliterator.SIZED | Spliterator.SUBSIZED + ) + assertNotNull("Null array.spliterator", spliter) + + // Check that iterator() call returns expected type. + val pItrInt: PrimitiveIterator.OfInt = Spliterators.iterator(spliter) + assertNotNull("Null PrimitiveIterator.OfInt", pItrInt) + + // Now verify the PrimitiveIterator.OfInt + + assertTrue( + s"unexpected empty PrimitiveIterator", + pItrInt.hasNext() + ) + + var count = 0 + pItrInt.forEachRemaining((e: Int) => { + assertEquals(s"failed match", expectedElements(count), e) + count += 1 + }) + } + + @Test def primitiveIteratorFromSpliteratorLong(): Unit = { + val expectedElements = Array(0, 11L, 22L, 33L, 44L, 55L, 66L) + val expectedSize = expectedElements.size + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfLong = Spliterators.spliterator( + expectedElements, + Spliterator.SIZED | Spliterator.SUBSIZED + ) + assertNotNull("Null array.spliterator", spliter) + + // Check that iterator() call returns expected type. + val pItrLong: PrimitiveIterator.OfLong = Spliterators.iterator(spliter) + assertNotNull("Null PrimitiveIterator.OfLong", pItrLong) + + // Now verify the PrimitiveIterator.OfLong + + assertTrue( + s"unexpected empty PrimitiveIterator", + pItrLong.hasNext() + ) + + var count = 0 + pItrLong.forEachRemaining((e: Long) => { + assertEquals(s"failed match", expectedElements(count), e) + count += 1 + }) + } + + @Test def iteratorFromSpliteratorType(): Unit = { + type T = String + val expectedElements = Array( + "lliu", + "hwi", + "kre", + "sei", + "mne", + "rhi", + "fve" + ) + + val expectedSize = expectedElements.size + + // Let compiler check type returned is expected. + val spliter: Spliterator[T] = Spliterators.spliterator( + expectedElements.asInstanceOf[Array[Object]], + Spliterator.SIZED | Spliterator.SUBSIZED + ) + assertNotNull("Null array.spliterator", spliter) + + // Check that iterator() call returns expected type. + val itr: java.util.Iterator[T] = Spliterators.iterator(spliter) + assertNotNull("Null Iterator", itr) + + // Now verify the Iterator + + assertTrue( + s"unexpected empty Iterator", + itr.hasNext() + ) + + var count = 0 + itr.forEachRemaining((e: T) => { + assertEquals(s"failed match", expectedElements(count), e) + count += 1 + }) + } + + @Test def spliteratorOfTypeFromCollection(): Unit = { + type T = String + val expectedElements = Array( + "Bertha von Suttner", + "Jane Addams", + "Emily Greene Balch", + "Betty Williams", + "Mairead Corrigan", + "Alva Myrdal" + ) + + val expectedSize = expectedElements.size + + val coll = TrivialImmutableCollection(expectedElements: _*) + assertEquals(expectedSize, coll.size()) + + // Example values used at the time of this writing by ArrayBlockingQueue + val requiredPresent = Seq( + Spliterator.ORDERED, + Spliterator.NONNULL, + Spliterator.CONCURRENT + ) + val requiredPresentMask = requiredPresent.fold(0)((x, y) => x | y) + + // Since CONCURRENT is given in requiredPresent, SIZED and SUBSIZED + // should not be turned on by constructor. + val requiredAbsent = Seq( + Spliterator.SIZED, + Spliterator.SUBSIZED, + Spliterator.SORTED // guard getComparator() throw + ) + + // Let compiler check type returned is expected. + val spliter: Spliterator[T] = + Spliterators.spliterator(coll, requiredPresentMask) + assertNotNull("Null coll.spliterator", spliter) + + // spliterator should have required characteristics and no others. + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + -1, // Because CONCURRENT, exact size is not known. + spliter.getExactSizeIfKnown() + ) + + // Check that both count & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + expectedSize, // on JVM estimateSize always returns initial expectedSize + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining size", expectedSize, count) + // on JVM estimateSize always returns initial expectedSize + assertEquals( + "forEachRemaining estimateSize", + expectedSize, + spliter.estimateSize() + ) + } + + @Test def spliteratorOfDoubleFromArray(): Unit = { + type T = Double + val expectedElements = Array( + 0.0, 10.1, 20.2, 30.3, 44.4, 55.5, 66.6 + ) + + val expectedSize = expectedElements.size + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfDouble = Spliterators.spliterator( + expectedElements, + 0 + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + val requiredPresent = Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + val requiredAbsent = Seq(Spliterator.SORTED) // guard getComparator() throw + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(count), + e, + 0.0001 + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + expectedSize - 1, + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e, + 0.0001 + ) + count += 1 + }) + assertEquals("forEachRemaining count", expectedSize, count) + assertEquals("forEachRemaining estimateSize", 0, spliter.estimateSize()) + } + + @Test def spliteratorOfDoubleFromArrayRange(): Unit = { + type T = Double + val expectedElements = Array( + 0.0, 10.1, 20.2, 30.3, 44.4, 55.5, 66.6 + ) + + val sliceStartIndex = 1 + val sliceEndIndex = 5 + val expectedSliceSize = sliceEndIndex - sliceStartIndex + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfDouble = Spliterators.spliterator( + expectedElements, + sliceStartIndex, + sliceEndIndex, + 0 + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + val requiredPresent = Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + val requiredAbsent = Seq(Spliterator.SORTED) // guard getComparator() throw + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSliceSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSliceSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(sliceStartIndex + count), + e, + 0.0001 + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + expectedSliceSize - 1, + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(sliceStartIndex + count), + e, + 0.0001 + ) + count += 1 + }) + assertEquals("forEachRemaining cursor", expectedSliceSize, count) + assertEquals("forEachRemaining estimateSize", 0, spliter.estimateSize()) + } + + @Test def spliteratorOfIntFromArray(): Unit = { + type T = Int + val expectedElements = Array( + 0, 1, 2, 3, 4, 5, 6 + ) + + val expectedSize = expectedElements.size + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfInt = Spliterators.spliterator( + expectedElements, + 0 + ) + assertNotNull("Null array.spliterator", spliter) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + // spliterator should have required characteristics and no others. + val requiredPresent = Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + val requiredAbsent = Seq(Spliterator.SORTED) // guard getComparator() throw + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + expectedSize - 1, + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining count", expectedSize, count) + assertEquals("forEachRemaining estimateSize", 0, spliter.estimateSize()) + } + + @Test def spliteratorOfIntFromArrayRange(): Unit = { + type T = Int + val expectedElements = Array( + 1, 11, 22, 33, 44, 55, 66 + ) + + val sliceStartIndex = 1 + val sliceEndIndex = 4 + val expectedSliceSize = sliceEndIndex - sliceStartIndex + + val coll = TrivialImmutableCollection(expectedElements: _*) + assertEquals(expectedElements.size, coll.size()) + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfInt = Spliterators.spliterator( + expectedElements, + sliceStartIndex, + sliceEndIndex, + 0 + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + val requiredPresent = Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + val requiredAbsent = Seq(Spliterator.SORTED) // guard getComparator() throw + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSliceSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSliceSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(sliceStartIndex + count), + e + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + expectedSliceSize - 1, + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(sliceStartIndex + count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining cursor", expectedSliceSize, count) + assertEquals("forEachRemaining estimateSize", 0, spliter.estimateSize()) + } + + @Test def spliteratorFromIteratorType(): Unit = { + type T = String + val expectedElements = Array( + "Arctic", + "North Atlantic", + "South Atlantic", + "Indian", + "North Pacific", + "South Pacific", + "Antarctic" + ) + + val expectedSize = expectedElements.size + + val coll = TrivialImmutableCollection(expectedElements: _*) + assertEquals(expectedSize, coll.size()) + + /* Test only the "astonishing" case, estimatedSize always return the + * initial size. No need to test CONCURRENT and SIZED separately. + */ + val requiredPresent = Seq(Spliterator.CONCURRENT) + val requiredPresentMask = requiredPresent.fold(0)((x, y) => x | y) + + // Since CONCURRENT specified as required, SIZED and SUBSIZED should be off. + val requiredAbsent = Seq( + Spliterator.SIZED, + Spliterator.SUBSIZED, + Spliterator.SORTED // guard getComparator() throw + ) + + /* Create spliterator specifying SIZED and SUBSIZED then check + * that the spliterator always reports them as absent, as documented. + */ + // Let compiler check type returned is expected. + val spliter: Spliterator[T] = Spliterators.spliterator( + coll.iterator, + expectedSize, + requiredPresentMask + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + -1, + spliter.getExactSizeIfKnown() + ) + + // Check that both the count & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + expectedSize, // on JVM estimateSize always returns initial expectedSize + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining", expectedSize, count) + // on JVM estimateSize always returns initial expectedSize + assertEquals( + "forEachRemaining estimateSize", + expectedSize, + spliter.estimateSize() + ) + } + + @Test def spliteratorOfLongFromArray(): Unit = { + type T = Long + val expectedElements = Array( + 0L, 1L, 2L, 3L, 4L, 5L, 6L + ) + + val expectedSize = expectedElements.size + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfLong = Spliterators.spliterator( + expectedElements, + 0 + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + val requiredPresent = Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + val requiredAbsent = Seq(Spliterator.SORTED) // guard getComparator() throw + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + expectedSize - 1, + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining count", expectedSize, count) + assertEquals("forEachRemaining estimateSize", 0, spliter.estimateSize()) + } + + @Test def spliteratorOfLongFromArrayRange(): Unit = { + type T = Long + val expectedElements = Array( + 1L, 11L, 22L, 33L, 44L, 55L, 66L + ) + + val sliceStartIndex = 1 + val sliceEndIndex = 4 + val expectedSliceSize = sliceEndIndex - sliceStartIndex + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfLong = Spliterators.spliterator( + expectedElements, + sliceStartIndex, + sliceEndIndex, + 0 + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + val requiredPresent = Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + val requiredAbsent = Seq(Spliterator.SORTED) // guard getComparator() throw + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSliceSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSliceSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(sliceStartIndex + count), + e + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + expectedSliceSize - 1, + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(sliceStartIndex + count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining cursor", expectedSliceSize, count) + assertEquals("forEachRemaining estimateSize", 0, spliter.estimateSize()) + } + + @Test def spliteratorOfTypeFromArrayRange(): Unit = { + type T = String + val expectedElements = Array( + "nul'", + "odin", + "dva", + "tri", + "cotiri", + "p'at", + "sist'" + ) + + val sliceStartIndex = 2 + val sliceEndIndex = 6 + val expectedSliceSize = sliceEndIndex - sliceStartIndex + + /* InitialPresent are the values used, at the time of this writing, + * by LinkedBlockingQueue. + * + * The spliterator-under-test is expected to always supply SIZED and + * SUBSIZED. Current implementation does not automatically add the + * "possibly more", from the documentation. If that ever changes, + * this test will begin to fail (unexpected bits set). + * + * Yes, having CONCURRENT and SIZED both set is unusual. Done here + * just to test wierd corner cases that are _bound_ to happen in the wild. + */ + + val initialPresent = Seq( + Spliterator.ORDERED, + Spliterator.NONNULL, + Spliterator.CONCURRENT + ) + val initialPresentMask = initialPresent.fold(0)((x, y) => x | y) + + val requiredPresent = initialPresent ++ + Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + + val requiredAbsent = Seq.empty[Int] + + // Let compiler check type returned is expected. + val spliter: Spliterator[T] = Spliterators.spliterator( + expectedElements.asInstanceOf[Array[AnyRef]], + sliceStartIndex, + sliceEndIndex, + initialPresentMask + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSliceSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSliceSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(sliceStartIndex + count), + e + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + expectedSliceSize - 1, + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(sliceStartIndex + count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining cursor", expectedSliceSize, count) + assertEquals("forEachRemaining estimateSize", 0, spliter.estimateSize()) + } + + @Test def spliteratorFromPrimitiveIteratorOfDouble(): Unit = { + type T = Double + val expectedElements = Array( + 0.0, 10.1, 20.2, 30.3, 44.4, 55.5, 66.6 + ) + val expectedSize = expectedElements.size + + val requiredPresent = Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + val requiredAbsent = Seq( + Spliterator.SORTED // guard getComparator() throw + ) + + // Let compiler check type returned is expected. + val siOfDouble: Spliterator.OfDouble = Spliterators.spliterator( + expectedElements, + 0 + ) + assertNotNull("Null array.spliterator", siOfDouble) + + // + // Let compiler check type returned is expected. + val piOfDouble: PrimitiveIterator.OfDouble = + Spliterators.iterator(siOfDouble) + assertNotNull("Null array.spliterator", piOfDouble) + + /* Create spliterator with characteristics of 0, then check + * that the spliterator always reports SIZED and SUBSIZED, unless + * CONCURRENT, as documented. + * + * Someday have a similar Test specifying CONCURRENT. + */ + // Let compiler check type returned is expected. + val spliter: Spliterator.OfDouble = Spliterators.spliterator( + piOfDouble, + expectedSize, + 0 + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(count), + e, + 0.0001 + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + expectedSize, // on JVM estimateSize always returns initial expectedSize + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e, + 0.0001 + ) + count += 1 + }) + assertEquals("forEachRemaining", expectedElements.size, count) + // on JVM estimateSize always returns initial expectedSize + assertEquals( + "forEachRemaining estimateSize", + expectedSize, + spliter.estimateSize() + ) + } + + @Test def spliteratorFromPrimitiveIteratorOfInt(): Unit = { + type T = Int + val expectedElements = Array( + 0, 1, 2, 3, 4, 5, 6 + ) + val expectedSize = expectedElements.size + + val requiredPresent = Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + val requiredAbsent = Seq( + Spliterator.SORTED // guard getComparator() throw + ) + + // Let compiler check type returned is expected. + val siOfInt: Spliterator.OfInt = Spliterators.spliterator( + expectedElements, + 0 + ) + assertNotNull("Null array.spliterator", siOfInt) + + // Let compiler check type returned is expected. + val piOfInt: PrimitiveIterator.OfInt = Spliterators.iterator(siOfInt) + assertNotNull("Null array.spliterator", piOfInt) + + /* Create spliterator with characteristics of 0, then check + * that the spliterator always reports SIZED and SUBSIZED, unless + * CONCURRENT, as documented. + * + * Someday have a similar Test specifying CONCURRENT. + */ + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfInt = Spliterators.spliterator( + piOfInt, + expectedSize, + 0 + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + expectedSize, // on JVM estimateSize always returns initial expectedSize + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining", expectedElements.size, count) + // on JVM estimateSize always returns initial expectedSize + assertEquals( + "forEachRemaining estimateSize", + expectedSize, + spliter.estimateSize() + ) + } + + @Test def spliteratorFromPrimitiveIteratorOfLong(): Unit = { + type T = Long + val expectedElements = Array( + 0L, 1L, 2L, 3L, 4L, 5L, 6L + ) + val expectedSize = expectedElements.size + + val requiredPresent = Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + val requiredAbsent = Seq( + Spliterator.SORTED // guard getComparator() throw + ) + + // Let compiler check type returned is expected. + val siOfLong: Spliterator.OfLong = Spliterators.spliterator( + expectedElements, + 0 + ) + assertNotNull("Null array.spliterator", siOfLong) + + // Let compiler check type returned is expected. + val piOfLong: PrimitiveIterator.OfLong = Spliterators.iterator(siOfLong) + assertNotNull("Null array.spliterator", piOfLong) + + /* Create spliterator with characteristics of 0, then check + * that the spliterator always reports SIZED and SUBSIZED, unless + * CONCURRENT, as documented. + * + * Someday have a similar Test specifying CONCURRENT. + */ + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfLong = Spliterators.spliterator( + piOfLong, + expectedSize, + 0 + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + expectedSize, // on JVM estimateSize always returns initial expectedSize + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining", expectedElements.size, count) + // on JVM estimateSize always returns initial expectedSize + assertEquals( + "forEachRemaining estimateSize", + expectedSize, + spliter.estimateSize() + ) + } + + @Test def spliteratorUnknownSizeFromIteratorType(): Unit = { + type T = String + val expectedElements = Array( + "pride", + "greed", + "wrath", + "envy", + "lust", + "gluttony", + "sloth" + ) + + val coll = TrivialImmutableCollection(expectedElements: _*) + assertEquals(expectedElements.size, coll.size()) + + val requiredPresent = Seq.empty[Int] + val requiredAbsent = Seq( + Spliterator.SIZED, + Spliterator.SUBSIZED, + Spliterator.SORTED // guard getComparator() throw + ) + + /* Create spliterator specifying SIZED and SUBSIZED then check + * that the spliterator always reports them as absent, as documented. + */ + + // Let compiler check type returned is expected. + val spliter: Spliterator[T] = Spliterators.spliteratorUnknownSize( + coll.iterator, + requiredAbsent.take(2).fold(0)((x, y) => x | y) + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", Long.MaxValue, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + -1, // By definition, size is Unknown. + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + Long.MaxValue, + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining", expectedElements.size, count) + assertEquals( + "forEachRemaining estimateSize", + Long.MaxValue, + spliter.estimateSize() + ) + } + + @Test def spliteratorUnknownSizeFromPrimitiveIteratorOfDouble(): Unit = { + type T = Double + val expectedElements = Array( + 0.0, 10.1, 20.2, 30.3, 44.4, 55.5, 66.6 + ) + + val requiredPresent = Seq.empty[Int] + val requiredAbsent = Seq( + Spliterator.SIZED, + Spliterator.SUBSIZED, + Spliterator.SORTED // guard getComparator() throw + ) + + // Let compiler check type returned is expected. + val siOfDouble: Spliterator.OfDouble = Spliterators.spliterator( + expectedElements, + 0 + ) + assertNotNull("Null array.spliterator", siOfDouble) + + // Let compiler check type returned is expected. + val piOfDouble: PrimitiveIterator.OfDouble = + Spliterators.iterator(siOfDouble) + assertNotNull("Null array.spliterator", piOfDouble) + + /* Create spliterator specifying SIZED and SUBSIZED then check + * that the spliterator always reports them as absent, as documented. + */ + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfDouble = Spliterators.spliteratorUnknownSize( + piOfDouble, + requiredAbsent.take(2).fold(0)((x, y) => x | y) + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", Long.MaxValue, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + -1, // By definition, size is Unknown. + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(count), + e, + 0.0001 + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + Long.MaxValue, + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e, + 0.0001 + ) + count += 1 + }) + assertEquals("forEachRemaining", expectedElements.size, count) + assertEquals( + "forEachRemaining estimateSize", + Long.MaxValue, + spliter.estimateSize() + ) + } + + @Test def spliteratorUnknownSizeFromPrimitiveIteratorOfInt(): Unit = { + type T = Int + val expectedElements = Array( + 0, 1, 2, 3, 4, 5, 6 + ) + + val requiredPresent = Seq.empty[Int] + val requiredAbsent = Seq( + Spliterator.SIZED, + Spliterator.SUBSIZED, + Spliterator.SORTED // guard getComparator() throw + ) + + // Let compiler check type returned is expected. + val siOfInt: Spliterator.OfInt = Spliterators.spliterator( + expectedElements, + 0 + ) + assertNotNull("Null array.spliterator", siOfInt) + + // Let compiler check type returned is expected. + val piOfInt: PrimitiveIterator.OfInt = Spliterators.iterator(siOfInt) + assertNotNull("Null array.spliterator", piOfInt) + + /* Create spliterator specifying SIZED and SUBSIZED then check + * that the spliterator always reports them as absent, as documented. + */ + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfInt = Spliterators.spliteratorUnknownSize( + piOfInt, + requiredAbsent.take(2).fold(0)((x, y) => x | y) + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", Long.MaxValue, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + -1, // By definition, size is Unknown. + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + Long.MaxValue, + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining", expectedElements.size, count) + assertEquals( + "forEachRemaining estimateSize", + Long.MaxValue, + spliter.estimateSize() + ) + } + + @Test def spliteratorUnknownSizeFromPrimitiveIteratorOfLong(): Unit = { + type T = Long + val expectedElements = Array( + 0L, 1L, 2L, 3L, 4L, 5L, 6L + ) + + val requiredPresent = Seq.empty[Int] + val requiredAbsent = Seq( + Spliterator.SIZED, + Spliterator.SUBSIZED, + Spliterator.SORTED // guard getComparator() throw + ) + + // Let compiler check type returned is expected. + val siOfLong: Spliterator.OfLong = Spliterators.spliterator( + expectedElements, + 0 + ) + assertNotNull("Null array.spliterator", siOfLong) + + // Let compiler check type returned is expected. + val piOfLong: PrimitiveIterator.OfLong = + Spliterators.iterator(siOfLong) + assertNotNull("Null array.spliterator", piOfLong) + + /* Create spliterator specifying SIZED and SUBSIZED then check + * that the spliterator always reports them as absent, as documented. + */ + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfLong = Spliterators.spliteratorUnknownSize( + piOfLong, + requiredAbsent.take(2).fold(0)((x, y) => x | y) + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", Long.MaxValue, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + -1, // By definition, size is Unknown. + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + Long.MaxValue, + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining", expectedElements.size, count) + assertEquals( + "forEachRemaining estimateSize", + Long.MaxValue, + spliter.estimateSize() + ) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BiConsumerTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BiConsumerTest.scala index 7c0801d6b2..72ddd8b656 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BiConsumerTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BiConsumerTest.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit: c2f5a43 dated: 2020-09-06 - +// Ported from Scala.js, commit SHA: c473689c9 dated: 2021-05-03 package org.scalanative.testsuite.javalib.util.function import java.util.function.BiConsumer @@ -77,11 +76,13 @@ object BiConsumerTest { extends Exception(s"throwing consumer called with ($x, $y)") private val throwingConsumer: BiConsumer[Int, Int] = makeBiConsumer { - (t, u) => throw new ThrowingConsumerException(t, u) + (t, u) => + throw new ThrowingConsumerException(t, u) } private val dontCallConsumer: BiConsumer[Int, Int] = makeBiConsumer { - (t, u) => throw new AssertionError(s"dontCallConsumer.accept($t, $u)") + (t, u) => + throw new AssertionError(s"dontCallConsumer.accept($t, $u)") } def makeBiConsumer[T, U](f: (T, U) => Unit): BiConsumer[T, U] = { diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BiFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BiFunctionTest.scala index 410e2c64d9..38f03fd7c5 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BiFunctionTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BiFunctionTest.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit: d3a9711 dated: 2020-09-06 - +// Ported from Scala.js, commit SHA: cbf86bbb8 dated: 2020-10-23 package org.scalanative.testsuite.javalib.util.function import java.util.function.{Function, BiFunction} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BiPredicateTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BiPredicateTest.scala index 15196ec646..068317246f 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BiPredicateTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BiPredicateTest.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit: 0c27b64 dated: 2020-09-06 - +// Ported from Scala.js, commit SHA: c473689c9 dated: 2021-05-03 package org.scalanative.testsuite.javalib.util.function import java.util.function.BiPredicate @@ -90,11 +89,13 @@ object BiPredicateTest { } private val throwingPredicate: BiPredicate[Int, Int] = makeBiPredicate { - (t, _) => throw new ThrowingPredicateException(t) + (t, _) => + throw new ThrowingPredicateException(t) } private val dontCallPredicate: BiPredicate[Int, Int] = makeBiPredicate { - (t, u) => throw new AssertionError(s"dontCallPredicate.test($t, $u)") + (t, u) => + throw new AssertionError(s"dontCallPredicate.test($t, $u)") } private[this] def makeBiPredicate[T, U]( diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BinaryOperatorTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BinaryOperatorTest.scala index 5b52bbf4c5..aa34e3a5c0 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BinaryOperatorTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BinaryOperatorTest.scala @@ -1,23 +1,19 @@ +// Ported from Scala.js, commit SHA: 1ef4c4e0f dated: 2020-09-06 package org.scalanative.testsuite.javalib.util.function -import java.util.function._ -import java.util.Collections +import java.util.function.BinaryOperator -import org.junit.Test import org.junit.Assert._ +import org.junit.Test class BinaryOperatorTest { - @Test def testMinBy(): Unit = { - val binaryOperator = BinaryOperator.minBy[Int](Collections.reverseOrder()) - val min = binaryOperator.apply(2004, 2018) - - assertTrue(min == 2018) + @Test def minBy(): Unit = { + val binOp: BinaryOperator[Int] = BinaryOperator.minBy(Ordering[Int]) + assertEquals(10, binOp.apply(10, 20)) } - @Test def testMaxBy(): Unit = { - val binaryOperator = BinaryOperator.maxBy[Int](Collections.reverseOrder()) - val max = binaryOperator.apply(2004, 2018) - - assertTrue(max == 2004) + @Test def maxBy(): Unit = { + val binOp: BinaryOperator[Int] = BinaryOperator.maxBy(Ordering[Int]) + assertEquals(20, binOp.apply(10, 20)) } } diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BooleanSupplierTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BooleanSupplierTest.scala new file mode 100644 index 0000000000..6afc27907e --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BooleanSupplierTest.scala @@ -0,0 +1,24 @@ +// Ported from Scala.js, commit SHA: db63dabed dated: 2020-10-06 +package org.scalanative.testsuite.javalib.util.function + +import java.util.function.BooleanSupplier + +import org.junit.Assert._ +import org.junit.Test + +class BooleanSupplierTest { + import BooleanSupplierTest._ + + @Test def getAsBoolean(): Unit = { + assertEquals(true, makeSupplier(true).getAsBoolean()) + assertEquals(false, makeSupplier(false).getAsBoolean()) + } +} + +object BooleanSupplierTest { + def makeSupplier(f: => Boolean): BooleanSupplier = { + new BooleanSupplier { + def getAsBoolean(): Boolean = f + } + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ConsumerTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ConsumerTest.scala index 95aa3ca876..455a41a433 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ConsumerTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ConsumerTest.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit: 7fd9ebb dated: 2020=01-06 - +// Ported from Scala.js, commit SHA: c473689c9 dated: 2021-05-03 package org.scalanative.testsuite.javalib.util.function import java.util.function.Consumer diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleBinaryOperatorTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleBinaryOperatorTest.scala new file mode 100644 index 0000000000..24d3889cda --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleBinaryOperatorTest.scala @@ -0,0 +1,17 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class DoubleBinaryOperatorTest { + @Test def applyAsDouble(): Unit = { + val sumOp = new DoubleBinaryOperator { + override def applyAsDouble(left: Double, right: Double): Double = + left + right + } + assertEquals(30, sumOp.applyAsDouble(10, 20), 0) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleConsumerTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleConsumerTest.scala new file mode 100644 index 0000000000..800b139a22 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleConsumerTest.scala @@ -0,0 +1,41 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class DoubleConsumerTest { + @Test def accept(): Unit = { + // Side-effects + var current: Double = 0 + + val add = new DoubleConsumer { + override def accept(value: Double): Unit = current += value + } + + add.accept(5) + assertEquals(5, current, 0) + add.accept(15) + assertEquals(20, current, 0) + } + + @Test def andThen(): Unit = { + // Side-effects + var buffer = scala.collection.mutable.ListBuffer.empty[Double] + + val add = new DoubleConsumer { + override def accept(value: Double): Unit = buffer += value + } + val add2x = new DoubleConsumer { + override def accept(value: Double): Unit = buffer += value * 2 + } + val merged: DoubleConsumer = add.andThen(add2x) + + merged.accept(1d) + assertEquals(List(1d, 2d), buffer.toList) + merged.accept(4d) + assertEquals(List(1d, 2d, 4d, 8d), buffer.toList) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleFunctionTest.scala new file mode 100644 index 0000000000..2db0f2c512 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleFunctionTest.scala @@ -0,0 +1,17 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class DoubleFunctionTest { + @Test def testApply(): Unit = { + val f = new DoubleFunction[String] { + override def apply(value: Double): String = s"${value}d" + } + assertEquals(f.apply(0.5), "0.5d") + assertEquals(f.apply(3.3), "3.3d") + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoublePredicateTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoublePredicateTest.scala new file mode 100644 index 0000000000..1e0dd2f604 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoublePredicateTest.scala @@ -0,0 +1,70 @@ +// Ported from Scala.js, commit SHA: c473689c9 dated: 2021-05-03 +package org.scalanative.testsuite.javalib.util.function + +import java.util.function.DoublePredicate + +import org.junit.Assert._ +import org.junit.Test + +class DoublePredicateTest { + import DoublePredicateTest._ + + private val largerThan10 = makePredicate(_ > 10.0d) + private val even = makePredicate(_ % 2 == 0.0d) + + private val throwingPredicate = + makePredicate(x => throw new ThrowingPredicateException(x)) + + private val dontCallPredicate = + makePredicate(x => throw new AssertionError(s"dontCallPredicate.test($x)")) + + @Test def and(): Unit = { + // Truth table + val evenAndLargerThan10 = largerThan10.and(even) + assertTrue(evenAndLargerThan10.test(22.0d)) + assertFalse(evenAndLargerThan10.test(21.0d)) + assertFalse(evenAndLargerThan10.test(6.0d)) + assertFalse(evenAndLargerThan10.test(5.0d)) + + // Short-circuit + assertFalse(largerThan10.and(dontCallPredicate).test(5.0d)) + assertThrows( + classOf[ThrowingPredicateException], + () => throwingPredicate.and(dontCallPredicate).test(5.0d) + ) + } + + @Test def negate(): Unit = { + // Truth table + val notLargerThan10 = largerThan10.negate() + assertTrue(notLargerThan10.test(5.0d)) + assertFalse(notLargerThan10.test(15.0d)) + } + + @Test def or(): Unit = { + // Truth table + val evenOrLargerThan10 = largerThan10.or(even) + assertTrue(evenOrLargerThan10.test(22.0d)) + assertTrue(evenOrLargerThan10.test(21.0d)) + assertTrue(evenOrLargerThan10.test(6.0d)) + assertFalse(evenOrLargerThan10.test(5.0)) + + // Short-circuit + assertTrue(largerThan10.or(dontCallPredicate).test(15.0d)) + assertThrows( + classOf[ThrowingPredicateException], + () => throwingPredicate.or(dontCallPredicate).test(15.0d) + ) + } +} + +object DoublePredicateTest { + final class ThrowingPredicateException(x: Any) + extends Exception(s"throwing predicate called with $x") + + def makePredicate(f: Double => Boolean): DoublePredicate = { + new DoublePredicate { + def test(value: Double): Boolean = f(value) + } + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleSupplierTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleSupplierTest.scala new file mode 100644 index 0000000000..0f342e0dce --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleSupplierTest.scala @@ -0,0 +1,23 @@ +// Ported from Scala.js, commit SHA: db63dabed dated: 2020-10-06 +package org.scalanative.testsuite.javalib.util.function + +import java.util.function.DoubleSupplier + +import org.junit.Assert._ +import org.junit.Test + +class DoubleSupplierTest { + import DoubleSupplierTest._ + + @Test def getAsDouble(): Unit = { + assertEquals(1.234d, makeSupplier(1.234d).getAsDouble(), 0.0d) + } +} + +object DoubleSupplierTest { + def makeSupplier(f: => Double): DoubleSupplier = { + new DoubleSupplier { + def getAsDouble(): Double = f + } + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleToIntFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleToIntFunctionTest.scala new file mode 100644 index 0000000000..1689f8cd40 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleToIntFunctionTest.scala @@ -0,0 +1,17 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class DoubleToIntFunctionTest { + @Test def applyAsInt(): Unit = { + val f = new DoubleToIntFunction { + override def applyAsInt(value: Double): Int = value.toInt + } + assertEquals(f.applyAsInt(0.5), 0) + assertEquals(f.applyAsInt(3.3), 3) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleToLongFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleToLongFunctionTest.scala new file mode 100644 index 0000000000..83c5edf1f8 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleToLongFunctionTest.scala @@ -0,0 +1,17 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class DoubleToLongFunctionTest { + @Test def applyAsLong(): Unit = { + val f = new DoubleToLongFunction { + override def applyAsLong(value: Double): Long = (10 * value).toLong + } + assertEquals(f.applyAsLong(0.5), 5L) + assertEquals(f.applyAsLong(3.3), 33L) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleUnaryOperatorTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleUnaryOperatorTest.scala new file mode 100644 index 0000000000..954c40148f --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleUnaryOperatorTest.scala @@ -0,0 +1,40 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class DoubleUnaryOperatorTest { + private val minus5 = new DoubleUnaryOperator { + override def applyAsDouble(operand: Double): Double = operand - 5 + } + private val times2 = new DoubleUnaryOperator { + override def applyAsDouble(operand: Double): Double = operand * 2 + } + + @Test def applyAsDouble(): Unit = { + val times4 = new DoubleUnaryOperator { + override def applyAsDouble(operand: Double): Double = operand * 4 + } + assertEquals(times4.applyAsDouble(0.5), 2.0, 0) + assertEquals(times4.applyAsDouble(3.3), 13.2, 0) + } + + @Test def andThen(): Unit = { + val f: DoubleUnaryOperator = minus5.andThen(times2) + assertEquals(f.applyAsDouble(3), -4, 0) + } + + @Test def compose(): Unit = { + val f: DoubleUnaryOperator = minus5.compose(times2) + assertEquals(f.applyAsDouble(3), 1, 0) + } + + @Test def identity(): Unit = { + val id: DoubleUnaryOperator = DoubleUnaryOperator.identity() + assertEquals(id.applyAsDouble(3), 3, 0) + assertEquals(id.applyAsDouble(10), 10, 0) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/FunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/FunctionTest.scala index c2e9f2d563..44b86f6c19 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/FunctionTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/FunctionTest.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit: eb637e3 dated: 200-09-06 - +// Ported from Scala.js, commit SHA: cbf86bbb8 dated: 2020-10-23 package org.scalanative.testsuite.javalib.util.function import java.util.function.Function @@ -14,7 +13,7 @@ class FunctionTest { assertEquals(10, identityFunc(10)) } - @Test def create_and_apply(): Unit = { + @Test def createAndApply(): Unit = { assertEquals(2, doubleFunc(1)) } @@ -28,7 +27,7 @@ class FunctionTest { assertEquals(22, incFunc.andThen(doubleFunc)(10)) } - @Test def identity_compose_andThen(): Unit = { + @Test def identityComposeAndThen(): Unit = { // i.e. (self + 1) * 2 val combined = identityFunc.andThen(doubleFunc).compose(incFunc) assertEquals(42, combined(20)) diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntBinaryOperatorTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntBinaryOperatorTest.scala new file mode 100644 index 0000000000..3947a87704 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntBinaryOperatorTest.scala @@ -0,0 +1,17 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class IntBinaryOperatorTest { + @Test def applyAsInt(): Unit = { + val max = new IntBinaryOperator { + override def applyAsInt(left: Int, right: Int): Int = left.max(right) + } + assertEquals(max.applyAsInt(3, 5), 5) + assertEquals(max.applyAsInt(0, -2), 0) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntConsumerTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntConsumerTest.scala new file mode 100644 index 0000000000..f76cfb7d10 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntConsumerTest.scala @@ -0,0 +1,41 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class IntConsumerTest { + @Test def accept(): Unit = { + // side-effects + var current: Int = 0 + + val add = new IntConsumer { + override def accept(value: Int): Unit = current += value + } + + add.accept(3) + assertEquals(current, 3) + add.accept(-10) + assertEquals(current, -7) + } + + @Test def andThen(): Unit = { + // side-effects + var buffer = scala.collection.mutable.ListBuffer.empty[Int] + + val add = new IntConsumer { + override def accept(value: Int): Unit = buffer += value + } + val add10x = new IntConsumer { + override def accept(value: Int): Unit = buffer += value * 10 + } + val f: IntConsumer = add.andThen(add10x) + + f.accept(1) + assertEquals(List(1, 10), buffer.toList) + f.accept(2) + assertEquals(List(1, 10, 2, 20), buffer.toList) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntFunctionTest.scala new file mode 100644 index 0000000000..a99d799f9e --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntFunctionTest.scala @@ -0,0 +1,17 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class IntFunctionTest { + @Test def testApply(): Unit = { + val repeat = new IntFunction[String] { + override def apply(value: Int): String = "." * value + } + assertEquals(repeat.apply(1), ".") + assertEquals(repeat.apply(3), "...") + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntPredicateTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntPredicateTest.scala new file mode 100644 index 0000000000..1a9d6dc03e --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntPredicateTest.scala @@ -0,0 +1,70 @@ +// Ported from Scala.js, commit SHA: c473689c9 dated: 2021-05-03 +package org.scalanative.testsuite.javalib.util.function + +import java.util.function.IntPredicate + +import org.junit.Assert._ +import org.junit.Test + +class IntPredicateTest { + import IntPredicateTest._ + + private val largerThan10 = makePredicate(_ > 10) + private val even = makePredicate(_ % 2 == 0) + + private val throwingPredicate = + makePredicate(x => throw new ThrowingPredicateException(x)) + + private val dontCallPredicate = + makePredicate(x => throw new AssertionError(s"dontCallPredicate.test($x)")) + + @Test def and(): Unit = { + // Truth table + val evenAndLargerThan10 = largerThan10.and(even) + assertTrue(evenAndLargerThan10.test(22)) + assertFalse(evenAndLargerThan10.test(21)) + assertFalse(evenAndLargerThan10.test(6)) + assertFalse(evenAndLargerThan10.test(5)) + + // Short-circuit + assertFalse(largerThan10.and(dontCallPredicate).test(5)) + assertThrows( + classOf[ThrowingPredicateException], + () => throwingPredicate.and(dontCallPredicate).test(5) + ) + } + + @Test def negate(): Unit = { + // Truth table + val notLargerThan10 = largerThan10.negate() + assertTrue(notLargerThan10.test(5)) + assertFalse(notLargerThan10.test(15)) + } + + @Test def or(): Unit = { + // Truth table + val evenOrLargerThan10 = largerThan10.or(even) + assertTrue(evenOrLargerThan10.test(22)) + assertTrue(evenOrLargerThan10.test(21)) + assertTrue(evenOrLargerThan10.test(6)) + assertFalse(evenOrLargerThan10.test(5)) + + // Short-circuit + assertTrue(largerThan10.or(dontCallPredicate).test(15)) + assertThrows( + classOf[ThrowingPredicateException], + () => throwingPredicate.or(dontCallPredicate).test(15) + ) + } +} + +object IntPredicateTest { + final class ThrowingPredicateException(x: Any) + extends Exception(s"throwing predicate called with $x") + + def makePredicate(f: Int => Boolean): IntPredicate = { + new IntPredicate { + def test(value: Int): Boolean = f(value) + } + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntSupplierTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntSupplierTest.scala new file mode 100644 index 0000000000..2090fe78cc --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntSupplierTest.scala @@ -0,0 +1,25 @@ +// Ported from Scala.js, commit SHA: db63dabed dated: 2020-10-06 +package org.scalanative.testsuite.javalib.util.function + +import java.util.function.IntSupplier + +import org.junit.Assert._ +import org.junit.Test + +class IntSupplierTest { + import IntSupplierTest._ + + @Test def getAsInt(): Unit = { + assertEquals(Int.MinValue, makeSupplier(Int.MinValue).getAsInt()) + assertEquals(1024, makeSupplier(1024).getAsInt()) + assertEquals(Int.MaxValue, makeSupplier(Int.MaxValue).getAsInt()) + } +} + +object IntSupplierTest { + def makeSupplier(f: => Int): IntSupplier = { + new IntSupplier { + def getAsInt(): Int = f + } + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntToDoubleFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntToDoubleFunctionTest.scala new file mode 100644 index 0000000000..63126417fc --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntToDoubleFunctionTest.scala @@ -0,0 +1,17 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class IntToDoubleFunctionTest { + @Test def testApply(): Unit = { + val f = new IntToDoubleFunction { + override def applyAsDouble(value: Int): Double = value.toDouble / 10d + } + assertEquals(f.applyAsDouble(3), 0.3, 0.0) + assertEquals(f.applyAsDouble(20), 2, 0.0) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntToLongFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntToLongFunctionTest.scala new file mode 100644 index 0000000000..7ad763396d --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntToLongFunctionTest.scala @@ -0,0 +1,16 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class IntToLongFunctionTest { + @Test def testApply(): Unit = { + val f = new IntToLongFunction { + override def applyAsLong(value: Int): Long = value.toLong * Int.MaxValue + } + assertEquals(f.applyAsLong(3), 6442450941L) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntUnaryOperatorTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntUnaryOperatorTest.scala index cfc313a22d..410a3235f7 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntUnaryOperatorTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntUnaryOperatorTest.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit: d028054 dated: 2022-05-16 - +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 package org.scalanative.testsuite.javalib.util.function import org.junit.Assert._ diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongBinaryOperatorTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongBinaryOperatorTest.scala new file mode 100644 index 0000000000..ac293e3f46 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongBinaryOperatorTest.scala @@ -0,0 +1,16 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class LongBinaryOperatorTest { + @Test def applyAsLong(): Unit = { + val sumOp = new LongBinaryOperator { + override def applyAsLong(left: Long, right: Long): Long = left + right + } + assertEquals(30, sumOp.applyAsLong(10, 20)) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongConsumerTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongConsumerTest.scala new file mode 100644 index 0000000000..5b5126c23c --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongConsumerTest.scala @@ -0,0 +1,41 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class LongConsumerTest { + @Test def accept(): Unit = { + // side-effects + var current: Long = 0 + + val add = new LongConsumer { + override def accept(value: Long): Unit = current += value + } + + add.accept(3) + assertEquals(current, 3) + add.accept(-10) + assertEquals(current, -7) + } + + @Test def andThen(): Unit = { + // side-effects + var buffer = scala.collection.mutable.ListBuffer.empty[Long] + + val add = new LongConsumer { + override def accept(value: Long): Unit = buffer += value + } + val add10x = new LongConsumer { + override def accept(value: Long): Unit = buffer += value * 10 + } + val f: LongConsumer = add.andThen(add10x) + + f.accept(1) + assertEquals(List(1L, 10L), buffer.toList) + f.accept(2) + assertEquals(List(1L, 10L, 2L, 20L), buffer.toList) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongFunctionTest.scala new file mode 100644 index 0000000000..9e4ad593dd --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongFunctionTest.scala @@ -0,0 +1,17 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class LongFunctionTest { + @Test def testApply(): Unit = { + val f = new LongFunction[Seq[Long]] { + override def apply(value: Long): Seq[Long] = List.fill(value.toInt)(value) + } + assertEquals(f.apply(1L), Seq(1L)) + assertEquals(f.apply(3L), Seq(3L, 3L, 3L)) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongPredicateTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongPredicateTest.scala new file mode 100644 index 0000000000..1f9fbeb298 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongPredicateTest.scala @@ -0,0 +1,70 @@ +// Ported from Scala.js, commit SHA: c473689c9 dated: 2021-05-03 +package org.scalanative.testsuite.javalib.util.function + +import java.util.function.LongPredicate + +import org.junit.Assert._ +import org.junit.Test + +class LongPredicateTest { + import LongPredicateTest._ + + private val largerThan10 = makePredicate(_ > 10L) + private val even = makePredicate(_ % 2 == 0L) + + private val throwingPredicate = + makePredicate(x => throw new ThrowingPredicateException(x)) + + private val dontCallPredicate = + makePredicate(x => throw new AssertionError(s"dontCallPredicate.test($x)")) + + @Test def and(): Unit = { + // Truth table + val evenAndLargerThan10 = largerThan10.and(even) + assertTrue(evenAndLargerThan10.test(22L)) + assertFalse(evenAndLargerThan10.test(21L)) + assertFalse(evenAndLargerThan10.test(6L)) + assertFalse(evenAndLargerThan10.test(5L)) + + // Short-circuit + assertFalse(largerThan10.and(dontCallPredicate).test(5L)) + assertThrows( + classOf[ThrowingPredicateException], + () => throwingPredicate.and(dontCallPredicate).test(5L) + ) + } + + @Test def negate(): Unit = { + // Truth table + val notLargerThan10 = largerThan10.negate() + assertTrue(notLargerThan10.test(5L)) + assertFalse(notLargerThan10.test(15L)) + } + + @Test def or(): Unit = { + // Truth table + val evenOrLargerThan10 = largerThan10.or(even) + assertTrue(evenOrLargerThan10.test(22L)) + assertTrue(evenOrLargerThan10.test(21L)) + assertTrue(evenOrLargerThan10.test(6L)) + assertFalse(evenOrLargerThan10.test(5L)) + + // Short-circuit + assertTrue(largerThan10.or(dontCallPredicate).test(15L)) + assertThrows( + classOf[ThrowingPredicateException], + () => throwingPredicate.or(dontCallPredicate).test(15L) + ) + } +} + +object LongPredicateTest { + final class ThrowingPredicateException(x: Any) + extends Exception(s"throwing predicate called with $x") + + def makePredicate(f: Long => Boolean): LongPredicate = { + new LongPredicate { + def test(value: Long): Boolean = f(value) + } + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongSupplierTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongSupplierTest.scala new file mode 100644 index 0000000000..4b5d12bb24 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongSupplierTest.scala @@ -0,0 +1,25 @@ +// Ported from Scala.js, commit SHA: db63dabed dated: 2020-10-06 +package org.scalanative.testsuite.javalib.util.function + +import java.util.function.LongSupplier + +import org.junit.Assert._ +import org.junit.Test + +class LongSupplierTest { + import LongSupplierTest._ + + @Test def getAsLong(): Unit = { + assertEquals(Long.MinValue, makeSupplier(Long.MinValue).getAsLong()) + assertEquals(1024L, makeSupplier(1024L).getAsLong()) + assertEquals(Long.MaxValue, makeSupplier(Long.MaxValue).getAsLong()) + } +} + +object LongSupplierTest { + def makeSupplier(f: => Long): LongSupplier = { + new LongSupplier { + def getAsLong(): Long = f + } + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongToDoubleFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongToDoubleFunctionTest.scala new file mode 100644 index 0000000000..e5274683f5 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongToDoubleFunctionTest.scala @@ -0,0 +1,16 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class LongToDoubleFunctionTest { + @Test def testApply(): Unit = { + val f = new LongToDoubleFunction { + override def applyAsDouble(value: Long): Double = value.toDouble * 0.5 + } + assertEquals(f.applyAsDouble(3), 1.5, 0.0) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongToIntFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongToIntFunctionTest.scala new file mode 100644 index 0000000000..4b7ace1ca9 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongToIntFunctionTest.scala @@ -0,0 +1,16 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class LongToIntFunctionTest { + @Test def testApply(): Unit = { + val f = new LongToIntFunction { + override def applyAsInt(value: Long): Int = value.toInt / 2 + } + assertEquals(f.applyAsInt(3), 1) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongUnaryOperatorTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongUnaryOperatorTest.scala new file mode 100644 index 0000000000..90fe194626 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongUnaryOperatorTest.scala @@ -0,0 +1,35 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class LongUnaryOperatorTest { + private val f = new LongUnaryOperator { + override def applyAsLong(operand: Long): Long = operand * 10 + } + private val g = new LongUnaryOperator { + override def applyAsLong(operand: Long): Long = operand - 20 + } + + @Test def applyAsLong(): Unit = { + assertEquals(f.applyAsLong(3), 30) + } + + @Test def andThen(): Unit = { + val h: LongUnaryOperator = f.andThen(g) + assertEquals(h.applyAsLong(5), 30) + } + + @Test def compose(): Unit = { + val h: LongUnaryOperator = f.compose(g) + assertEquals(h.applyAsLong(5), -150) + } + + @Test def identity(): Unit = { + val f: LongUnaryOperator = LongUnaryOperator.identity() + assertEquals(1L, f.applyAsLong(1)) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ObjDoubleConsumerTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ObjDoubleConsumerTest.scala new file mode 100644 index 0000000000..5f82e12f0a --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ObjDoubleConsumerTest.scala @@ -0,0 +1,23 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class ObjDoubleConsumerTest { + @Test def accept(): Unit = { + // side-effects + var current: String = "" + + val op = new ObjDoubleConsumer[String] { + override def accept(left: String, right: Double): Unit = + current += s"$left $right " + } + + op.accept("First", 1.1) + op.accept("Second", 2.2) + assertEquals(current, "First 1.1 Second 2.2 ") + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ObjIntConsumerTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ObjIntConsumerTest.scala new file mode 100644 index 0000000000..b6a4893d6e --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ObjIntConsumerTest.scala @@ -0,0 +1,23 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class ObjIntConsumerTest { + @Test def accept(): Unit = { + // side-effects + var current: String = "" + + val op = new ObjIntConsumer[String] { + override def accept(left: String, right: Int): Unit = + current += left * right + } + + op.accept("First", 1) + op.accept("Second", 2) + assertEquals(current, "FirstSecondSecond") + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ObjLongConsumerTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ObjLongConsumerTest.scala new file mode 100644 index 0000000000..959b6646f6 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ObjLongConsumerTest.scala @@ -0,0 +1,22 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class ObjLongConsumerTest { + @Test def accept(): Unit = { + // side-effects + var current: String = "" + + val op = new ObjLongConsumer[String] { + override def accept(left: String, right: Long): Unit = + current += s"$left $right " + } + op.accept("First", 2L) + op.accept("Second", 3L) + assertEquals(current, "First 2 Second 3 ") + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/PredicateTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/PredicateTest.scala index f0ee70bf8b..8d4cfa5822 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/PredicateTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/PredicateTest.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit: 137c11d dated: 2019-07-03 - +// Ported from Scala.js, commit SHA: c473689c9 dated: 2021-05-03 package org.scalanative.testsuite.javalib.util.function import java.util.function.Predicate diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/SupplierTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/SupplierTest.scala index 4c15644053..3a6f7811cc 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/SupplierTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/SupplierTest.scala @@ -1,17 +1,25 @@ -package org.scalanative.testsuite.javalib.util -package function +// Ported from Scala.js, commit SHA: 5df5a4142 dated: 2020-09-06 +package org.scalanative.testsuite.javalib.util.function -import java.util.function._ -import java.util._ +import java.util.function.Supplier -import org.junit.Test import org.junit.Assert._ +import org.junit.Test class SupplierTest { - @Test def testGet(): Unit = { - val string = new Supplier[String] { - override def get(): String = "scala" + import SupplierTest._ + + @Test def get(): Unit = { + val supplier: Supplier[String] = makeSupplier("scala") + + assertEquals("scala", supplier.get()) + } +} + +object SupplierTest { + def makeSupplier[T](f: => T): Supplier[T] = { + new Supplier[T] { + def get(): T = f } - assertTrue(string.get() == "scala") } } diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToDoubleBiFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToDoubleBiFunctionTest.scala new file mode 100644 index 0000000000..be553145e6 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToDoubleBiFunctionTest.scala @@ -0,0 +1,17 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class ToDoubleBiFunctionTest { + @Test def applyAsDouble(): Unit = { + val op = new ToDoubleBiFunction[String, String] { + override def applyAsDouble(t: String, u: String): Double = + s"$t.$u".toDouble + } + assertEquals(op.applyAsDouble("123", "456"), 123.456, 0.0) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToDoubleFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToDoubleFunctionTest.scala index bc4a0043a3..c5cc91eb8c 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToDoubleFunctionTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToDoubleFunctionTest.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit 00e462d dated: 2023-01-22 - +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 package org.scalanative.testsuite.javalib.util.function import org.junit.Assert._ diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToIntBiFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToIntBiFunctionTest.scala new file mode 100644 index 0000000000..9a1c4d513c --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToIntBiFunctionTest.scala @@ -0,0 +1,16 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class ToIntBiFunctionTest { + @Test def applyAsInt(): Unit = { + val op = new ToIntBiFunction[String, String] { + override def applyAsInt(t: String, u: String): Int = s"$t$u".toInt + } + assertEquals(op.applyAsInt("10", "24"), 1024) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToIntFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToIntFunctionTest.scala index eddeddb7a6..eabeeef4f6 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToIntFunctionTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToIntFunctionTest.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit 00e462d dated: 2023-01-22 - +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 package org.scalanative.testsuite.javalib.util.function import org.junit.Assert._ diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToLongBiFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToLongBiFunctionTest.scala new file mode 100644 index 0000000000..30ab5decc8 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToLongBiFunctionTest.scala @@ -0,0 +1,16 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class ToLongBiFunctionTest { + @Test def applyAsLong(): Unit = { + val op = new ToLongBiFunction[String, String] { + override def applyAsLong(t: String, u: String): Long = t.toLong * u.toLong + } + assertEquals(op.applyAsLong("11111111", "2222222"), 24691355308642L) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToLongFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToLongFunctionTest.scala index b8168b9769..d583719db7 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToLongFunctionTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToLongFunctionTest.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit 00e462d dated: 2023-01-22 - +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 package org.scalanative.testsuite.javalib.util.function import org.junit.Assert._ diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/UnaryOperatorTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/UnaryOperatorTest.scala index c4c6c87c64..5778523447 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/UnaryOperatorTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/UnaryOperatorTest.scala @@ -1,13 +1,30 @@ +// Ported from Scala.js, commit SHA: cbf86bbb8 dated: 2020-10-23 package org.scalanative.testsuite.javalib.util.function -import java.util.function._ +import java.util.function.UnaryOperator -import org.junit.Test import org.junit.Assert._ +import org.junit.Test class UnaryOperatorTest { - @Test def testUnaryOperator(): Unit = { + import UnaryOperatorTest._ + + @Test def identity(): Unit = { val unaryOperatorString: UnaryOperator[String] = UnaryOperator.identity() - assertTrue(unaryOperatorString.apply("scala") == "scala") + assertEquals("scala", unaryOperatorString.apply("scala")) + } + + @Test def createAndApply(): Unit = { + val double: UnaryOperator[Int] = makeUnaryOperator(_ * 2) + assertEquals(20, double.apply(10)) + assertEquals(20, double.apply(10)) + } +} + +object UnaryOperatorTest { + private def makeUnaryOperator[T](f: T => T): UnaryOperator[T] = { + new UnaryOperator[T] { + def apply(t: T): T = f(t) + } } } diff --git a/windowslib/src/main/resources/scala-native/windows/accCtrl/securityObjectType.c b/windowslib/src/main/resources/scala-native/windows/accCtrl/securityObjectType.c index 7046909d78..e01ae03f51 100644 --- a/windowslib/src/main/resources/scala-native/windows/accCtrl/securityObjectType.c +++ b/windowslib/src/main/resources/scala-native/windows/accCtrl/securityObjectType.c @@ -1,4 +1,4 @@ -#if defined(_WIN32) || defined(WIN32) +#if (defined(_WIN32) || defined(WIN32)) && !defined(__MINGW64__) #define WIN32_LEAN_AND_MEAN #include