diff --git a/.gitattributes b/.gitattributes index dd9d1a0c8..289340f27 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,6 +8,7 @@ vendor/box2d/lib/box2d_windows_amd64_sse2.lib filter=lfs diff=lfs merge=lfs -tex vendor/box3d/lib/linux-amd64/libbox3d.a filter=lfs diff=lfs merge=lfs -text vendor/box3d/lib/darwin/libbox3d.a filter=lfs diff=lfs merge=lfs -text vendor/box3d/lib/box3d.lib filter=lfs diff=lfs merge=lfs -text +vendor/box3d/lib/box3d_wasm.o filter=lfs diff=lfs merge=lfs -text vendor/miniaudio/lib/miniaudio.lib filter=lfs diff=lfs merge=lfs -text vendor/sdl3/SDL3.dll filter=lfs diff=lfs merge=lfs -text vendor/sdl3/SDL3.lib filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3ae67161..9810b17de 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -131,8 +131,8 @@ jobs: ./vendor/cgltf/src/build_cgltf.sh ./vendor/miniaudio/src/build_miniaudio.sh ./vendor/kb_text_shape/src/build_unix.sh - - name: Compile Box3D (Ubuntu ARM) - if: matrix.os == 'ubuntu-24.04-arm' + - name: Compile Box3D (Ubuntu) + if: matrix.os == 'ubuntu-latest' || matrix.os == 'ubuntu-24.04-arm' run: ./vendor/box3d/src/build.sh - name: Odin check run: ./odin check examples/demo -vet @@ -254,8 +254,9 @@ jobs: - name: Wycheproof tests shell: cmd run: | - call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" - odin test tests/core/crypto/wycheproof -vet -vet-tabs -strict-style -vet-style -vet-cast -warnings-as-errors -disallow-do -o:speed -microarch:native + rem call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + rem odin test tests/core/crypto/wycheproof -vet -vet-tabs -strict-style -vet-style -vet-cast -warnings-as-errors -disallow-do -o:speed -microarch:native + echo Skipping Wycheproof on Windows because of CI flakiness that can't be replicated on real hardware - name: Noise Protocol Framework tests shell: cmd run: | diff --git a/.gitignore b/.gitignore index bf0efe2a0..56a75c788 100644 --- a/.gitignore +++ b/.gitignore @@ -319,3 +319,5 @@ build/ cmake-build*/ CMakeLists.txt sandbox/ + +vendor/box3d/lib/box3d_wasm.o diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index d91bc2e79..c4ffd02dc 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -251,6 +251,8 @@ type_merge :: proc($U, $V: typeid) -> typeid where type_is_union(U), type_is_uni type_integer_to_unsigned :: proc($T: typeid) -> type where type_is_integer(T), !type_is_unsigned(T) --- type_integer_to_signed :: proc($T: typeid) -> type where type_is_integer(T), type_is_unsigned(T) --- +// Directional: true when U contains every field of V, matched on name and type. +// Swapping the arguments can change the answer, and an empty V will return true. type_has_shared_fields :: proc($U, $V: typeid) -> bool where type_is_struct(U), type_is_struct(V) --- diff --git a/base/runtime/LICENSE-compiler-rt.txt b/base/runtime/LICENSE-compiler-rt.txt new file mode 100644 index 000000000..39eadfc4f --- /dev/null +++ b/base/runtime/LICENSE-compiler-rt.txt @@ -0,0 +1,313 @@ +The below LLVM license applies to `runtime.fixuint` and `runtime.fixint`. + +============================================================================== +The LLVM Project is under the Apache License v2.0 with LLVM Exceptions: +============================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +---- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + +============================================================================== +Software from third parties included in the LLVM Project: +============================================================================== +The LLVM Project contains third party software which is under different license +terms. All such code will be identified clearly using at least one of two +mechanisms: +1) It will be in a separate directory tree with its own `LICENSE.txt` or + `LICENSE` file at the top containing the specific license and restrictions + which apply to that software, or +2) It will contain specific license and restriction terms at the top of every + file. + +============================================================================== +Legacy LLVM License (https://llvm.org/docs/DeveloperPolicy.html#legacy): +============================================================================== + +The compiler_rt library is dual licensed under both the University of Illinois +"BSD-Like" license and the MIT license. As a user of this code you may choose +to use it under either license. As a contributor, you agree to allow your code +to be used under both. + +Full text of the relevant licenses is included below. + +============================================================================== + +University of Illinois/NCSA +Open Source License + +Copyright (c) 2009-2019 by the contributors listed in CREDITS.TXT + +All rights reserved. + +Developed by: + + LLVM Team + + University of Illinois at Urbana-Champaign + + http://llvm.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal with +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + + * Neither the names of the LLVM Team, University of Illinois at + Urbana-Champaign, nor the names of its contributors may be used to + endorse or promote products derived from this Software without specific + prior written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. + +============================================================================== + +Copyright (c) 2009-2015 by the contributors listed in CREDITS.TXT + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/base/runtime/core.odin b/base/runtime/core.odin index 41d620bdd..0453583c0 100644 --- a/base/runtime/core.odin +++ b/base/runtime/core.odin @@ -945,5 +945,25 @@ default_assertion_contextless_failure_proc :: proc "contextless" (prefix, messag print_byte('\n') } } + when ODIN_CODEPAGE_MAGIC { + SetConsoleOutputCP(old_console_codepage) + } trap() } + +ODIN_CODEPAGE_MAGIC :: ODIN_OS == .Windows && #config(ODIN_CODEPAGE_MAGIC, !ODIN_BEDROCK) + +when ODIN_CODEPAGE_MAGIC { + @(private) + old_console_codepage: u32 + + @(private) + UTF_8 :: 65001 +} + +foreign import kernel32 "system:Kernel32.lib" +@(default_calling_convention="system") +foreign kernel32 { + GetConsoleOutputCP :: proc() -> u32 --- + SetConsoleOutputCP :: proc(codepage: u32) -> b32 --- +} \ No newline at end of file diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 4913b53d9..fccc8b1df 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -215,6 +215,8 @@ remove_range_fixed_capacity_dynamic_array :: proc(array: ^$D/[dynamic; $N]$E, #a unordered_remove :: proc{ unordered_remove_dynamic_array, unordered_remove_fixed_capacity_dynamic_array, + + unordered_remove_soa, } @@ -222,6 +224,8 @@ unordered_remove :: proc{ ordered_remove :: proc{ ordered_remove_dynamic_array, ordered_remove_fixed_capacity_dynamic_array, + + ordered_remove_soa, } @builtin @@ -271,6 +275,8 @@ pop_fixed_capacity_dynamic_array :: proc(array: ^$T/[dynamic; $N]$E, loc := #cal pop :: proc{ pop_dynamic_array, pop_fixed_capacity_dynamic_array, + + pop_soa, } // `pop_safe_dynamic_array` trys to remove and return the end value of dynamic array `array` and reduces the length of `array` by 1. @@ -303,6 +309,8 @@ pop_safe_fixed_capacity_dynamic_array :: proc "contextless" (array: ^$T/[dynamic pop_safe :: proc{ pop_safe_dynamic_array, pop_safe_fixed_capacity_dynamic_array, + + pop_safe_soa, } @@ -342,6 +350,8 @@ pop_front_fixed_capacity_dynamic_array :: proc(array: ^$T/[dynamic; $N]$E, loc : pop_front :: proc{ pop_front_dynamic_array, pop_front_fixed_capacity_dynamic_array, + + pop_front_soa, } @@ -381,6 +391,8 @@ pop_front_safe_fixed_capacity_dynamic_array :: proc "contextless" (array: ^$T/[d pop_front_safe :: proc { pop_front_safe_dynamic_array, pop_front_safe_fixed_capacity_dynamic_array, + + pop_front_safe_soa, } @@ -921,7 +933,7 @@ non_zero_append_elem_string :: proc(#no_alias array: ^$T/[dynamic]$E/u8, arg: $A // Note: Prefer using the procedure group `non_zero_append`. @builtin non_zero_append_elem_fixed_capacity_string :: proc "contextless" (array: ^$T/[dynamic; $N]$E/u8, arg: $A/string) -> (num_appended: int) { - return append_fixed_capacity_elem(array, transmute([]byte)arg) + return append_fixed_capacity_elems(array, ..transmute([]E)arg) } @@ -1212,6 +1224,9 @@ inject_at :: proc{ inject_at_elem_fixed_capacity_dynamic_array, inject_at_elems_fixed_capacity_dynamic_array, inject_at_elem_string_fixed_capacity_dynamic_array, + + inject_at_elem_soa, + inject_at_elems_soa, } diff --git a/base/runtime/core_builtin_soa.odin b/base/runtime/core_builtin_soa.odin index 61b93efd7..48c48f305 100644 --- a/base/runtime/core_builtin_soa.odin +++ b/base/runtime/core_builtin_soa.odin @@ -56,7 +56,7 @@ Raw_SOA_Footer_Dynamic_Array :: struct { // Multipointer indexing lowers to GEP and doesn't capture, so prefer that throughout. @(builtin, require_results) -raw_soa_footer_slice :: proc(array: ^$T/#soa[]$E) -> (footer: ^Raw_SOA_Footer_Slice) { +raw_soa_footer_slice :: proc "contextless" (array: ^$T/#soa[]$E) -> (footer: ^Raw_SOA_Footer_Slice) { if array == nil { return nil } @@ -65,7 +65,7 @@ raw_soa_footer_slice :: proc(array: ^$T/#soa[]$E) -> (footer: ^Raw_SOA_Footer_Sl return } @(builtin, require_results) -raw_soa_footer_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) -> (footer: ^Raw_SOA_Footer_Dynamic_Array) { +raw_soa_footer_dynamic_array :: proc "contextless" (array: ^$T/#soa[dynamic]$E) -> (footer: ^Raw_SOA_Footer_Dynamic_Array) { if array == nil { return nil } @@ -704,17 +704,68 @@ into_dynamic_soa :: proc(array: $T/#soa[]$E) -> #soa[dynamic]E { return d } +// `pop_soa` will remove and return the end value of the #soa dynamic array `array` and reduces the length of `array` by 1. +// +// Note: If the #soa dynamic array has no elements (`len(array) == 0`), this procedure will panic. +@builtin +pop_soa :: proc(#no_alias array: ^$T/#soa[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check { + assert(len(array) > 0, loc=loc) + res = array[len(array)-1] + raw_soa_footer_dynamic_array(array).len -= 1 + return +} + +// `pop_safe_soa` trys to remove and return the end value of the #soa dynamic array `array` and reduces the length of `array` by 1. +// If the operation is not possible, it will return false. +@builtin +pop_safe_soa :: proc "contextless" (#no_alias array: ^$T/#soa[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check { + if len(array) == 0 { + return + } + res, ok = array[len(array)-1], true + raw_soa_footer_dynamic_array(array).len -= 1 + return +} + +// `pop_front_soa` will remove and return the first value of the #soa dynamic array `array` and reduces the length of `array` by 1, +// whilst keeping the order of the other elements. +// +// Note: This is an O(N) operation. +// Note: If the #soa dynamic array has no elements (`len(array) == 0`), this procedure will panic. +@builtin +pop_front_soa :: proc(#no_alias array: ^$T/#soa[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check { + assert(len(array) > 0, loc=loc) + res = array[0] + _ordered_remove_soa(array, 0) + return +} + +// `pop_front_safe_soa` trys to remove and return the first value of the #soa dynamic array `array` and reduces the +// length of `array` by 1, whilst keeping the order of the other elements. +// If the operation is not possible, it will return false. +// +// Note: This is an O(N) operation. +@builtin +pop_front_safe_soa :: proc "contextless" (#no_alias array: ^$T/#soa[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check { + if len(array) == 0 { + return + } + + res, ok = array[0], true + _ordered_remove_soa(array, 0) + return +} + // `unordered_remove_soa` removed the element at the specified `index`. It does so by replacing the current end value // with the old value, and reducing the length of the dynamic array by 1. // // Note: This is an O(1) operation. -// Note: If you the elements to remain in their order, use `ordered_remove_soa`. +// Note: If you want the elements to remain in their order, use `ordered_remove_soa`. // Note: If the index is out of bounds, this procedure will panic. @builtin unordered_remove_soa :: proc(#no_alias array: ^$T/#soa[dynamic]$E, #any_int index: int, loc := #caller_location) #no_bounds_check { bounds_check_error_loc(loc, index, len(array)) if index+1 < len(array) { - // Use the compiler's #soa element load and store lowering. array[index] = array[len(array)-1] } raw_soa_footer_dynamic_array(array).len -= 1 @@ -723,11 +774,17 @@ unordered_remove_soa :: proc(#no_alias array: ^$T/#soa[dynamic]$E, #any_int inde // `ordered_remove_soa` removed the element at the specified `index` whilst keeping the order of the other elements. // // Note: This is an O(N) operation. -// Note: If you the elements do not have to remain in their order, prefer `unordered_remove_soa`. +// Note: If the elements do not have to remain in their order, prefer `unordered_remove_soa`. // Note: If the index is out of bounds, this procedure will panic. @builtin ordered_remove_soa :: proc(#no_alias array: ^$T/#soa[dynamic]$E, #any_int index: int, loc := #caller_location) #no_bounds_check { bounds_check_error_loc(loc, index, len(array)) + _ordered_remove_soa(array, index) +} + +// the unchecked body of ordered_remove_soa, shared with the front pops. +// index must already be known to be in bounds. +_ordered_remove_soa :: proc "contextless" (#no_alias array: ^$T/#soa[dynamic]$E, index: int) #no_bounds_check { if index+1 < len(array) { ti := type_info_of(typeid_of(T)) ti = type_info_base(ti) diff --git a/base/runtime/entry_windows.odin b/base/runtime/entry_windows.odin index d3b38bc9c..5e7753d6a 100644 --- a/base/runtime/entry_windows.odin +++ b/base/runtime/entry_windows.odin @@ -17,9 +17,14 @@ when ODIN_BUILD_MODE == .Dynamic { switch dll_forward_reason { case .Process_Attach: when !ODIN_BEDROCK { #force_no_inline _startup_runtime() } + when ODIN_CODEPAGE_MAGIC { + old_console_codepage = GetConsoleOutputCP() + SetConsoleOutputCP(UTF_8) + } intrinsics.__entry_point() case .Process_Detach: when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() } + when ODIN_CODEPAGE_MAGIC { SetConsoleOutputCP(old_console_codepage) } case .Thread_Attach: break case .Thread_Detach: @@ -36,8 +41,13 @@ when ODIN_BUILD_MODE == .Dynamic { args__ = argv[:argc] context = default_context() when !ODIN_BEDROCK { #force_no_inline _startup_runtime() } + when ODIN_CODEPAGE_MAGIC { + old_console_codepage = GetConsoleOutputCP() + SetConsoleOutputCP(UTF_8) + } intrinsics.__entry_point() - when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() } + when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() } + when ODIN_CODEPAGE_MAGIC { SetConsoleOutputCP(old_console_codepage) } return 0 } } else when ODIN_NO_CRT { @@ -45,8 +55,13 @@ when ODIN_BUILD_MODE == .Dynamic { mainCRTStartup :: proc "system" () -> i32 { context = default_context() when !ODIN_BEDROCK { #force_no_inline _startup_runtime() } + when ODIN_CODEPAGE_MAGIC { + old_console_codepage = GetConsoleOutputCP() + SetConsoleOutputCP(UTF_8) + } intrinsics.__entry_point() - when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() } + when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() } + when ODIN_CODEPAGE_MAGIC { SetConsoleOutputCP(old_console_codepage) } return 0 } } else { @@ -55,8 +70,13 @@ when ODIN_BUILD_MODE == .Dynamic { args__ = argv[:argc] context = default_context() when !ODIN_BEDROCK { #force_no_inline _startup_runtime() } + when ODIN_CODEPAGE_MAGIC { + old_console_codepage = GetConsoleOutputCP() + SetConsoleOutputCP(UTF_8) + } intrinsics.__entry_point() - when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() } + when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() } + when ODIN_CODEPAGE_MAGIC { SetConsoleOutputCP(old_console_codepage) } return 0 } } diff --git a/base/runtime/heap_allocator.odin b/base/runtime/heap_allocator.odin index e2667a78c..4dfe95393 100644 --- a/base/runtime/heap_allocator.odin +++ b/base/runtime/heap_allocator.odin @@ -42,8 +42,10 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, ptr := uintptr(aligned_mem) aligned_ptr := (ptr + uintptr(a)-1) & ~(uintptr(a)-1) if allocated_mem == nil { - aligned_free(old_ptr) - aligned_free(allocated_mem) + // On failure nothing must be freed: heap_resize (realloc) leaves the + // original block intact, and on the copy/fresh path old_ptr has not + // been copied or freed yet. Freeing old_ptr here left the caller's + // pointer dangling, causing a later double free. (#7262) return nil, .Out_Of_Memory } diff --git a/base/runtime/internal_i128.odin b/base/runtime/internal_i128.odin index f4a0f90ed..8c70002c9 100644 --- a/base/runtime/internal_i128.odin +++ b/base/runtime/internal_i128.odin @@ -91,22 +91,111 @@ floattidf_unsigned :: proc "c" (a: u128) -> f64 { } +// f64 -> unsigned integer conversion (truncating toward zero) +// decompose the f64 into significand and exponent, shift the significand into place, +// saturate out of range values +// +// Uses parts of compiler-rt's [fp_fixuint_impl.inc](https://github.com/llvm/llvm-project/blob/main/compiler-rt/lib/builtins/fp_fixuint_impl.inc), licensed under the [Apache License v2.0 with LLVM Exceptions](https://github.com/llvm/llvm-project/blob/main/compiler-rt/LICENSE.TXT). +// For a copy of the license, see `LICENSE-compiler-rt.txt`. +@(private="file") +fixuint :: proc "contextless" ($U: typeid, a: f64) -> U where intrinsics.type_is_unsigned(U) { + BITS :: 8 * size_of(U) + SIGNIFICAND_BITS :: 52 + EXPONENT_BIAS :: 1023 + + IMPLICIT_BIT :: (u64(1) << SIGNIFICAND_BITS) + SIGNIFICAND_MASK :: (IMPLICIT_BIT - 1) + + // Break a into sign, exponent, significand parts. + a_rep := transmute(u64)a + negative := (a_rep >> 63) != 0 + exponent := i32((a_rep >> SIGNIFICAND_BITS) & 0x7ff) - EXPONENT_BIAS + significand := (a_rep & SIGNIFICAND_MASK) | IMPLICIT_BIT + + // If either the value or the exponent is negative, the result is zero. + if negative || exponent < 0 { + return 0 + } + + // If the value is too large for the integer type, saturate. + if exponent >= BITS { + return max(U) + } + + // If 0 <= exponent < SIGNIFICAND_BITS, right shift to get the result. + // Otherwise, shift left. + if exponent < SIGNIFICAND_BITS { + return U(significand >> u32(SIGNIFICAND_BITS - exponent)) + } + return U(significand) << u32(exponent - SIGNIFICAND_BITS) +} + +// f64 -> signed integer conversion (truncating toward zero) +// decompose the f64 into significand and exponent, shift the significand into place, +// saturate out of range values +// +// Uses parts of compiler-rt's [fp_fixuint_impl.inc](https://github.com/llvm/llvm-project/blob/main/compiler-rt/lib/builtins/fp_fixuint_impl.inc), licensed under the [Apache License v2.0 with LLVM Exceptions](https://github.com/llvm/llvm-project/blob/main/compiler-rt/LICENSE.TXT). +// For a copy of the license, see `LICENSE-compiler-rt.txt`. +@(private="file") +fixint :: proc "contextless" ($T: typeid, a: f64) -> T where intrinsics.type_is_integer(T), !intrinsics.type_is_unsigned(T) { + BITS :: 8 * size_of(T) + SIGNIFICAND_BITS :: 52 + EXPONENT_BIAS :: 1023 + + IMPLICIT_BIT :: (u64(1) << SIGNIFICAND_BITS) + SIGNIFICAND_MASK :: (IMPLICIT_BIT - 1) + + // Break a into sign, exponent, significand parts. + a_rep := transmute(u64)a + negative := (a_rep >> 63) != 0 + exponent := i32((a_rep >> SIGNIFICAND_BITS) & 0x7ff) - EXPONENT_BIAS + significand := (a_rep & SIGNIFICAND_MASK) | IMPLICIT_BIT + + // If exponent is negative, the result is zero. + if exponent < 0 { + return 0 + } + + // If the value is too large for the integer type, saturate; + // (the only exactly representable value with exponent BITS-1 is min(T)) + // this deviates from clang/compiler-rt (it instead wraps from BITS-1 to BITS, + // that is, f64(1 << 127) would wrap to min(i128) (for T=i128)), + // while this saturates consistently + if exponent >= BITS - 1 { + return min(T) if negative else max(T) + } + + // If 0 <= exponent < SIGNIFICAND_BITS, right shift to get the result. + // Otherwise, shift left. After saturation the magnitude is below + // 1 << (BITS-1), so it fits in T and negation can't overflow + r: T + if exponent < SIGNIFICAND_BITS { + r = T(significand >> u32(SIGNIFICAND_BITS - exponent)) + } else { + r = T(significand) << u32(exponent - SIGNIFICAND_BITS) + } + return -r if negative else r +} @(link_name="__fixunsdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -fixunsdfti :: #force_no_inline proc "c" (a: f64) -> u128 { - // TODO(bill): implement `fixunsdfti` correctly - x := u64(a) - return u128(x) +fixunsdfti :: proc "c" (a: f64) -> u128 { + return fixuint(u128, a) } @(link_name="__fixunsdfdi", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -fixunsdfdi :: #force_no_inline proc "c" (a: f64) -> i128 { - // TODO(bill): implement `fixunsdfdi` correctly - x := i64(a) - return i128(x) +fixunsdfdi :: proc "c" (a: f64) -> u64 { + return fixuint(u64, a) } +@(link_name="__fixdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +fixdfti :: proc "c" (a: f64) -> i128 { + return fixint(i128, a) +} +@(link_name="__fixdfdi", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +fixdfdi :: proc "c" (a: f64) -> i64 { + return fixint(i64, a) +} @(link_name="__umodti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) @@ -171,47 +260,4 @@ divti3 :: proc "c" (a, b: i128) -> i128 { u_s_a := u128(s_a) return i128((udivmodti4(u128(an), u128(bn), nil) ~ u_s_a) - u_s_a) // negate if negative -} - - -@(link_name="__fixdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -fixdfti :: proc "c" (a: u64) -> i128 { - significandBits :: 52 - typeWidth :: (size_of(u64)*8) - exponentBits :: (typeWidth - significandBits - 1) - maxExponent :: ((1 << exponentBits) - 1) - exponentBias :: (maxExponent >> 1) - - implicitBit :: (u64(1) << significandBits) - significandMask :: (implicitBit - 1) - signBit :: (u64(1) << (significandBits + exponentBits)) - absMask :: (signBit - 1) - exponentMask :: (absMask ~ significandMask) - - // Break a into sign, exponent, significand - aRep := a - aAbs := aRep & absMask - sign := i128(-1 if aRep & signBit != 0 else 1) - exponent := u64((aAbs >> significandBits) - exponentBias) - significand := u64((aAbs & significandMask) | implicitBit) - - // If exponent is negative, the result is zero. - if exponent < 0 { - return 0 - } - - // If the value is too large for the integer type, saturate. - if exponent >= size_of(i128) * 8 { - return max(i128) if sign == 1 else min(i128) - } - - // If 0 <= exponent < significandBits, right shift to get the result. - // Otherwise, shift left. - if exponent < significandBits { - return sign * i128(significand >> (significandBits - exponent)) - } else { - return sign * (i128(significand) << (exponent - significandBits)) - } - -} - +} \ No newline at end of file diff --git a/base/runtime/procs_windows_amd64.odin b/base/runtime/procs_windows_amd64.odin index 81d2cfb5d..cedbc2bd8 100644 --- a/base/runtime/procs_windows_amd64.odin +++ b/base/runtime/procs_windows_amd64.odin @@ -11,8 +11,9 @@ foreign kernel32 { windows_trap_array_bounds :: proc "contextless" () -> ! { EXCEPTION_ARRAY_BOUNDS_EXCEEDED :: 0xC000008C - - + when ODIN_CODEPAGE_MAGIC { + SetConsoleOutputCP(old_console_codepage) + } RaiseException(EXCEPTION_ARRAY_BOUNDS_EXCEEDED, 0, 0, nil) } diff --git a/base/runtime/procs_windows_i386.odin b/base/runtime/procs_windows_i386.odin index 99c314228..9dd97116d 100644 --- a/base/runtime/procs_windows_i386.odin +++ b/base/runtime/procs_windows_i386.odin @@ -15,7 +15,9 @@ windows_trap_array_bounds :: proc "contextless" () -> ! { foreign kernel32 { RaiseException :: proc "system" (dwExceptionCode, dwExceptionFlags, nNumberOfArguments: DWORD, lpArguments: ^ULONG_PTR) -> ! --- } - + when ODIN_CODEPAGE_MAGIC { + SetConsoleOutputCP(old_console_codepage) + } RaiseException(EXCEPTION_ARRAY_BOUNDS_EXCEEDED, 0, 0, nil) } diff --git a/build_odin.sh b/build_odin.sh index 9eab985e9..ebf2804c6 100755 --- a/build_odin.sh +++ b/build_odin.sh @@ -125,7 +125,7 @@ NetBSD) ;; Linux) CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)" - LDFLAGS="$LDFLAGS -lstdc++ -ldl $($LLVM_CONFIG --libs core native --system-libs --libfiles)" + LDFLAGS="$LDFLAGS -lstdc++ -ldl $($LLVM_CONFIG --libs core native passes arm aarch64 x86 webassembly riscv --system-libs --libfiles)" # Copy libLLVM*.so into current directory for linking # NOTE: This is needed by the Linux release pipeline! # cp $(readlink -f $($LLVM_CONFIG --libfiles)) ./ diff --git a/core/encoding/hex/hex.odin b/core/encoding/hex/hex.odin index c4726d9e9..f7a39a081 100644 --- a/core/encoding/hex/hex.odin +++ b/core/encoding/hex/hex.odin @@ -95,12 +95,12 @@ Decodes a hex sequence into a byte slice *Allocates Using Provided Allocator* Inputs: -- dst: The hex sequence decoded into bytes - src: The `[]byte` to be hex-decoded - allocator: (default: context.allocator) - loc: The caller location for debugging purposes (default: #caller_location) Returns: +- dst: The hex sequence decoded into bytes - ok: A bool, `true` if decoding succeeded, `false` otherwise */ decode :: proc(src: []byte, allocator := context.allocator, loc := #caller_location) -> (dst: []byte, ok: bool) { @@ -123,6 +123,40 @@ decode :: proc(src: []byte, allocator := context.allocator, loc := #caller_locat return dst, true } +/* +Decodes a hex sequence into a byte slice + +Inputs: +- src: The `[]byte` to be hex-decoded +- buf: A buffer large enough to hold the decoded sequence + +Returns: +- dst: The hex sequence decoded into bytes +- ok: A bool, `true` if decoding succeeded, `false` otherwise +*/ +decode_into_buffer :: proc(src: []byte, buf: []byte) -> (dst: []byte, ok: bool) #optional_ok { + if len(src) % 2 == 1 { + return + } + dst_len := len(src) / 2 + if len(buf) < dst_len { + return + } + + #no_bounds_check for i, j := 0, 1; j < len(src); j += 2 { + p := src[j-1] + q := src[j] + + a := hex_digit(p) or_return + b := hex_digit(q) or_return + + buf[i] = (a << 4) | b + i += 1 + } + + return buf[:dst_len], true +} + /* Decodes the first byte in a hex sequence to a byte @@ -173,4 +207,4 @@ hex_digit :: proc(char: byte) -> (u8, bool) { case 'A' ..= 'F': return char - 'A' + 10, true case: return 0, false } -} \ No newline at end of file +} diff --git a/core/math/fixed/fixed.odin b/core/math/fixed/fixed.odin index 6b9ef364b..65d7af772 100644 --- a/core/math/fixed/fixed.odin +++ b/core/math/fixed/fixed.odin @@ -148,7 +148,7 @@ write :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string { } } - n := copy(dst, buf[:i]) + copy(dst, buf[:i]) return string(dst[:i]) } diff --git a/core/mem/allocators.odin b/core/mem/allocators.odin index dcd126230..92e95ea2c 100644 --- a/core/mem/allocators.odin +++ b/core/mem/allocators.odin @@ -1813,7 +1813,7 @@ dynamic_arena_alloc_bytes_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, align } memory := align_forward(a.current_pos, uintptr(actual_alignment)) margin := int(uintptr(memory) - uintptr(a.current_pos)) - if a.bytes_left < margin + n { + for a.bytes_left < margin + n { err := _dynamic_arena_cycle_new_block(a, alignment, loc) if err != nil { return nil, err @@ -1821,8 +1821,8 @@ dynamic_arena_alloc_bytes_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, align if a.current_block == nil { return nil, .Out_Of_Memory } - margin = 0 - memory = a.current_pos + memory = align_forward(a.current_pos, uintptr(actual_alignment)) + margin = int(uintptr(memory) - uintptr(a.current_pos)) } a.current_pos = ([^]byte)(memory)[n:] a.bytes_left -= margin + n diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index 3b2a1481e..8539ab2ab 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -1110,6 +1110,7 @@ parse_attribute :: proc(p: ^Parser, tok: tokenizer.Token, open_kind, close_kind: open, close: tokenizer.Token if p.curr_tok.kind == .Ident { + close = p.curr_tok elem := parse_ident(p) append(&elems, elem) } else { diff --git a/core/os/dir_windows.odin b/core/os/dir_windows.odin index 3fd045560..9d245f6ad 100644 --- a/core/os/dir_windows.odin +++ b/core/os/dir_windows.odin @@ -16,8 +16,15 @@ find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, al } temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - path := concatenate({base_path, `\`, win32_wstring_to_utf8(cstring16(raw_data(d.cFileName[:])), temp_allocator) or_else ""}, allocator) or_return + filename := win32_wstring_to_utf8(cstring16(raw_data(d.cFileName[:])), temp_allocator) or_else "" + pieces: []string + if len(base_path) > 0 && is_path_separator(base_path[len(base_path) - 1]) { + pieces = {base_path, filename} + } else { + pieces = {base_path, Path_Separator_String, filename} + } + path := concatenate(pieces, allocator) or_return handle := win32.HANDLE(_open_internal(path, {.Read}, Permissions_Read_Write_All) or_else 0) defer win32.CloseHandle(handle) diff --git a/core/simd/arm/neon.odin b/core/simd/arm/neon.odin index 30f7f8b21..17f0f2fa9 100644 --- a/core/simd/arm/neon.odin +++ b/core/simd/arm/neon.odin @@ -211,14 +211,6 @@ vcnt_u8 :: #force_inline proc "c" (a: uint8x8_t) -> uint8x8_t { return transmute(uint8x8_t)vcnt_s8(transmute(int8x8_t)a) } -// Population count per byte. -// -// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vcnt_p8) -@(require_results, enable_target_feature = "neon") -vcnt_p8 :: #force_inline proc "c" (a: poly8x8_t) -> poly8x8_t { - return transmute(poly8x8_t)vcnt_s8(transmute(int8x8_t)a) -} - // Population count per byte. // // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vcntq_s8) @@ -235,14 +227,6 @@ vcntq_u8 :: #force_inline proc "c" (a: uint8x16_t) -> uint8x16_t { return transmute(uint8x16_t)vcntq_s8(transmute(int8x16_t)a) } -// Population count per byte. -// -// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vcntq_p8) -@(require_results, enable_target_feature = "neon") -vcntq_p8 :: #force_inline proc "c" (a: poly8x16_t) -> poly8x16_t { - return transmute(poly8x16_t)vcntq_s8(transmute(int8x16_t)a) -} - // Vector bitwise bit clear. // // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbic_s8) @@ -822,6 +806,1030 @@ vtbl4_u8 :: #force_inline proc "c" (t: uint8x8x4_t, idx: uint8x8_t) -> uint8x8_t } } +// Extended Table Lookup. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbx1_s8) +@(require_results, enable_target_feature = "neon") +vtbx1_s8 :: #force_inline proc "c" (v: int8x8_t, t: int8x8_t, idx: int8x8_t) -> int8x8_t { + when ODIN_ARCH == .arm64 { + return simd.select( + simd.lanes_lt(idx, int8x8_t(8)), + vqtbx1_s8(v, vcombine_s8(t, int8x8_t{}), transmute(uint8x8_t)idx), + v, + ) + } else { + return _vtbx1(v, t, idx) + } +} + +// Extended Table Lookup. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbx1_u8) +@(require_results, enable_target_feature = "neon") +vtbx1_u8 :: #force_inline proc "c" (v: uint8x8_t, t: uint8x8_t, idx: uint8x8_t) -> uint8x8_t { + when ODIN_ARCH == .arm64 { + return simd.select( + simd.lanes_lt(idx, uint8x8_t(8)), + vqtbx1_u8(v, vcombine_u8(t, uint8x8_t{}), idx), + v, + ) + } else { + return transmute(uint8x8_t)_vtbx1( + transmute(int8x8_t)v, + transmute(int8x8_t)t, + transmute(int8x8_t)idx, + ) + } +} + +// Extended Table Lookup. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbx2_s8) +@(require_results, enable_target_feature = "neon") +vtbx2_s8 :: #force_inline proc "c" (v: int8x8_t, t: int8x8x2_t, idx: int8x8_t) -> int8x8_t { + when ODIN_ARCH == .arm64 { + return simd.select( + simd.lanes_lt(idx, int8x8_t(16)), + vqtbx1_s8(v, vcombine_s8(t.x, t.y), transmute(uint8x8_t)idx), + v, + ) + } else { + return _vtbx2(v, t.x, t.y, idx) + } +} + +// Extended Table Lookup. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbx2_u8) +@(require_results, enable_target_feature = "neon") +vtbx2_u8 :: #force_inline proc "c" (v: uint8x8_t, t: uint8x8x2_t, idx: uint8x8_t) -> uint8x8_t { + when ODIN_ARCH == .arm64 { + return simd.select( + simd.lanes_lt(idx, uint8x8_t(16)), + vqtbx1_u8(v, vcombine_u8(t.x, t.y), idx), + v, + ) + } else { + return transmute(uint8x8_t)_vtbx2( + transmute(int8x8_t)v, + transmute(int8x8_t)t.x, + transmute(int8x8_t)t.y, + transmute(int8x8_t)idx, + ) + } +} + +// Extended Table Lookup. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbx3_s8) +@(require_results, enable_target_feature = "neon") +vtbx3_s8 :: #force_inline proc "c" (v: int8x8_t, t: int8x8x3_t, idx: int8x8_t) -> int8x8_t { + when ODIN_ARCH == .arm64 { + x := int8x16x2_t { + vcombine_s8(t.x, t.y), + vcombine_s8(t.z, int8x8_t{}), + } + return simd.select( + simd.lanes_lt(idx, int8x8_t(24)), + vqtbx2_s8(v, x, transmute(uint8x8_t)idx), + v, + ) + } else { + return _vtbx3(v, t.x, t.y, t.z, idx) + } +} + +// Extended Table Lookup. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbx3_u8) +@(require_results, enable_target_feature = "neon") +vtbx3_u8 :: #force_inline proc "c" (v: uint8x8_t, t: uint8x8x3_t, idx: uint8x8_t) -> uint8x8_t { + when ODIN_ARCH == .arm64 { + x := uint8x16x2_t { + vcombine_u8(t.x, t.y), + vcombine_u8(t.z, uint8x8_t{}), + } + return simd.select( + simd.lanes_lt(idx, uint8x8_t(24)), + vqtbx2_u8(v, x, idx), + v, + ) + } else { + return transmute(uint8x8_t)_vtbx3( + transmute(int8x8_t)v, + transmute(int8x8_t)t.x, + transmute(int8x8_t)t.y, + transmute(int8x8_t)t.z, + transmute(int8x8_t)idx, + ) + } +} + +// Extended Table Lookup. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbx4_s8) +@(require_results, enable_target_feature = "neon") +vtbx4_s8 :: #force_inline proc "c" (v: int8x8_t, t: int8x8x4_t, idx: int8x8_t) -> int8x8_t { + when ODIN_ARCH == .arm64 { + x := int8x16x2_t { + vcombine_s8(t.x, t.y), + vcombine_s8(t.z, t.w), + } + return simd.select( + simd.lanes_lt(idx, int8x8_t(32)), + vqtbx2_s8(v, x, transmute(uint8x8_t)idx), + v, + ) + } else { + return _vtbx4(v, t.x, t.y, t.z, t.w, idx) + } +} + +// Extended Table Lookup. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbx4_u8) +@(require_results, enable_target_feature = "neon") +vtbx4_u8 :: #force_inline proc "c" (v: uint8x8_t, t: uint8x8x4_t, idx: uint8x8_t) -> uint8x8_t { + when ODIN_ARCH == .arm64 { + x := uint8x16x2_t { + vcombine_u8(t.x, t.y), + vcombine_u8(t.z, t.w), + } + return simd.select( + simd.lanes_lt(idx, uint8x8_t(32)), + vqtbx2_u8(v, x, idx), + v, + ) + } else { + return transmute(uint8x8_t)_vtbx4( + transmute(int8x8_t)v, + transmute(int8x8_t)t.x, + transmute(int8x8_t)t.y, + transmute(int8x8_t)t.z, + transmute(int8x8_t)t.w, + transmute(int8x8_t)idx, + ) + } +} + +// Duplicate vector element to vector or scalar +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vdup_n_s8) +@(require_results, enable_target_feature = "neon") +vdup_n_s8 :: #force_inline proc "c" (value: int8_t) -> int8x8_t { + return int8x8_t(value) +} + +// Duplicate vector element to vector or scalar +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vdup_n_s16) +@(require_results, enable_target_feature = "neon") +vdup_n_s16 :: #force_inline proc "c" (value: int16_t) -> int16x4_t { + return int16x4_t(value) +} + +// Duplicate vector element to vector or scalar +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vdup_n_s32) +@(require_results, enable_target_feature = "neon") +vdup_n_s32 :: #force_inline proc "c" (value: int32_t) -> int32x2_t { + return int32x2_t(value) +} + +// Duplicate vector element to vector or scalar +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vdup_n_s64) +@(require_results, enable_target_feature = "neon") +vdup_n_s64 :: #force_inline proc "c" (value: int64_t) -> int64x1_t { + return int64x1_t(value) +} + +// Move vector element to general-purpose register +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vget_lane_s8) +@(require_results, enable_target_feature = "neon") +vget_lane_s8 :: #force_inline proc "c" (v: int8x8_t, $LANE: int32_t) -> int8_t where 0 <= LANE, LANE < 8 { + when ODIN_ENDIAN == .Little { + return simd.extract(v, LANE) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + return simd.extract(v, LANE) + } +} + +// Move vector element to general-purpose register +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vget_lane_u8) +@(require_results, enable_target_feature = "neon") +vget_lane_u8 :: #force_inline proc "c" (v: uint8x8_t, $LANE: int32_t) -> uint8_t where 0 <= LANE, LANE < 8 { + when ODIN_ENDIAN == .Little { + return simd.extract(v, LANE) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + return simd.extract(v, LANE) + } +} + +// Move vector element to general-purpose register +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vget_lane_s16) +@(require_results, enable_target_feature = "neon") +vget_lane_s16 :: #force_inline proc "c" (v: int16x4_t, $LANE: int32_t) -> int16_t where 0 <= LANE, LANE < 4 { + when ODIN_ENDIAN == .Little { + return simd.extract(v, LANE) + } else { + v := simd.shuffle(v, v, 3, 2, 1, 0) + return simd.extract(v, LANE) + } +} + +// Move vector element to general-purpose register +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vget_lane_u16) +@(require_results, enable_target_feature = "neon") +vget_lane_u16 :: #force_inline proc "c" (v: uint16x4_t, $LANE: int32_t) -> uint16_t where 0 <= LANE, LANE < 4 { + when ODIN_ENDIAN == .Little { + return simd.extract(v, LANE) + } else { + v := simd.shuffle(v, v, 3, 2, 1, 0) + return simd.extract(v, LANE) + } +} + +// Move vector element to general-purpose register +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vget_lane_s32) +@(require_results, enable_target_feature = "neon") +vget_lane_s32 :: #force_inline proc "c" (v: int32x2_t, $LANE: int32_t) -> int32_t where 0 <= LANE, LANE < 2 { + when ODIN_ENDIAN == .Little { + return simd.extract(v, LANE) + } else { + v := simd.shuffle(v, v, 1, 0) + return simd.extract(v, LANE) + } +} + +// Move vector element to general-purpose register +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vget_lane_u32) +@(require_results, enable_target_feature = "neon") +vget_lane_u32 :: #force_inline proc "c" (v: uint32x2_t, $LANE: int32_t) -> uint32_t where 0 <= LANE, LANE < 2 { + when ODIN_ENDIAN == .Little { + return simd.extract(v, LANE) + } else { + v := simd.shuffle(v, v, 1, 0) + return simd.extract(v, LANE) + } +} + +// Move vector element to general-purpose register +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vget_lane_s64) +@(require_results, enable_target_feature = "neon") +vget_lane_s64 :: #force_inline proc "c" (v: int64x1_t, $LANE: int32_t) -> int64_t where LANE == 0 { + return simd.extract(v, LANE) +} + +// Move vector element to general-purpose register +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vget_lane_u64) +@(require_results, enable_target_feature = "neon") +vget_lane_u64 :: #force_inline proc "c" (v: uint64x1_t, $LANE: int32_t) -> uint64_t where LANE == 0 { + return simd.extract(v, LANE) +} + +// Negate. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vneg_s8) +@(require_results, enable_target_feature = "neon") +vneg_s8 :: #force_inline proc "c" (a: int8x8_t) -> int8x8_t { + return simd.neg(a) +} + +// Negate. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vnegq_s8) +@(require_results, enable_target_feature = "neon") +vnegq_s8 :: #force_inline proc "c" (a: int8x16_t) -> int8x16_t { + return simd.neg(a) +} + +// Negate. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vneg_s16) +@(require_results, enable_target_feature = "neon") +vneg_s16 :: #force_inline proc "c" (a: int16x4_t) -> int16x4_t { + return simd.neg(a) +} + +// Negate. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vnegq_s16) +@(require_results, enable_target_feature = "neon") +vnegq_s16 :: #force_inline proc "c" (a: int16x8_t) -> int16x8_t { + return simd.neg(a) +} + +// Negate. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vneg_s32) +@(require_results, enable_target_feature = "neon") +vneg_s32 :: #force_inline proc "c" (a: int32x2_t) -> int32x2_t { + return simd.neg(a) +} + +// Negate. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vnegq_s32) +@(require_results, enable_target_feature = "neon") +vnegq_s32 :: #force_inline proc "c" (a: int32x4_t) -> int32x4_t { + return simd.neg(a) +} + +// Signed saturating Negate. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqneg_s8) +@(require_results, enable_target_feature = "neon") +vqneg_s8 :: #force_inline proc "c" (a: int8x8_t) -> int8x8_t { + return _vqneg_s8(a) +} + +// Signed saturating Negate. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqneg_s16) +@(require_results, enable_target_feature = "neon") +vqneg_s16 :: #force_inline proc "c" (a: int16x4_t) -> int16x4_t { + return _vqneg_s16(a) +} + +// Signed saturating Negate. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqneg_s32) +@(require_results, enable_target_feature = "neon") +vqneg_s32 :: #force_inline proc "c" (a: int32x2_t) -> int32x2_t { + return _vqneg_s32(a) +} + +// Signed saturating Negate. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqnegq_s8) +@(require_results, enable_target_feature = "neon") +vqnegq_s8 :: #force_inline proc "c" (a: int8x16_t) -> int8x16_t { + return _vqnegq_s8(a) +} + +// Signed saturating Negate. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqnegq_s16) +@(require_results, enable_target_feature = "neon") +vqnegq_s16 :: #force_inline proc "c" (a: int16x8_t) -> int16x8_t { + return _vqnegq_s16(a) +} + +// Signed saturating Negate. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqnegq_s32) +@(require_results, enable_target_feature = "neon") +vqnegq_s32 :: #force_inline proc "c" (a: int32x4_t) -> int32x4_t { + return _vqnegq_s32(a) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvn_s8) +@(require_results, enable_target_feature = "neon") +vmvn_s8 :: #force_inline proc "c" (a: int8x8_t) -> int8x8_t { + b := int8x8_t(-1) + return simd.bit_xor(a, b) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvn_u8) +@(require_results, enable_target_feature = "neon") +vmvn_u8 :: #force_inline proc "c" (a: uint8x8_t) -> uint8x8_t { + b := uint8x8_t(max(uint8_t)) + return simd.bit_xor(a, b) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvn_s16) +@(require_results, enable_target_feature = "neon") +vmvn_s16 :: #force_inline proc "c" (a: int16x4_t) -> int16x4_t { + b := int16x4_t(-1) + return simd.bit_xor(a, b) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvn_u16) +@(require_results, enable_target_feature = "neon") +vmvn_u16 :: #force_inline proc "c" (a: uint16x4_t) -> uint16x4_t { + b := uint16x4_t(max(uint16_t)) + return simd.bit_xor(a, b) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvn_s32) +@(require_results, enable_target_feature = "neon") +vmvn_s32 :: #force_inline proc "c" (a: int32x2_t) -> int32x2_t { + b := int32x2_t(-1) + return simd.bit_xor(a, b) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvn_u32) +@(require_results, enable_target_feature = "neon") +vmvn_u32 :: #force_inline proc "c" (a: uint32x2_t) -> uint32x2_t { + b := uint32x2_t(max(uint32_t)) + return simd.bit_xor(a, b) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvnq_s8) +@(require_results, enable_target_feature = "neon") +vmvnq_s8 :: #force_inline proc "c" (a: int8x16_t) -> int8x16_t { + b := int8x16_t(-1) + return simd.bit_xor(a, b) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvnq_u8) +@(require_results, enable_target_feature = "neon") +vmvnq_u8 :: #force_inline proc "c" (a: uint8x16_t) -> uint8x16_t { + b := uint8x16_t(max(uint8_t)) + return simd.bit_xor(a, b) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvnq_s16) +@(require_results, enable_target_feature = "neon") +vmvnq_s16 :: #force_inline proc "c" (a: int16x8_t) -> int16x8_t { + b := int16x8_t(-1) + return simd.bit_xor(a, b) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvnq_u16) +@(require_results, enable_target_feature = "neon") +vmvnq_u16 :: #force_inline proc "c" (a: uint16x8_t) -> uint16x8_t { + b := uint16x8_t(max(uint16_t)) + return simd.bit_xor(a, b) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvnq_s32) +@(require_results, enable_target_feature = "neon") +vmvnq_s32 :: #force_inline proc "c" (a: int32x4_t) -> int32x4_t { + b := int32x4_t(-1) + return simd.bit_xor(a, b) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvnq_u32) +@(require_results, enable_target_feature = "neon") +vmvnq_u32 :: #force_inline proc "c" (a: uint32x4_t) -> uint32x4_t { + b := uint32x4_t(max(uint32_t)) + return simd.bit_xor(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vand_s8) +@(require_results, enable_target_feature = "neon") +vand_s8 :: #force_inline proc "c" (a, b: int8x8_t) -> int8x8_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vand_u8) +@(require_results, enable_target_feature = "neon") +vand_u8 :: #force_inline proc "c" (a, b: uint8x8_t) -> uint8x8_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vand_s16) +@(require_results, enable_target_feature = "neon") +vand_s16 :: #force_inline proc "c" (a, b: int16x4_t) -> int16x4_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vand_u16) +@(require_results, enable_target_feature = "neon") +vand_u16 :: #force_inline proc "c" (a, b: uint16x4_t) -> uint16x4_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vand_s32) +@(require_results, enable_target_feature = "neon") +vand_s32 :: #force_inline proc "c" (a, b: int32x2_t) -> int32x2_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vand_u32) +@(require_results, enable_target_feature = "neon") +vand_u32 :: #force_inline proc "c" (a, b: uint32x2_t) -> uint32x2_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vand_s64) +@(require_results, enable_target_feature = "neon") +vand_s64 :: #force_inline proc "c" (a, b: int64x1_t) -> int64x1_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vand_u64) +@(require_results, enable_target_feature = "neon") +vand_u64 :: #force_inline proc "c" (a, b: uint64x1_t) -> uint64x1_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vandq_s8) +@(require_results, enable_target_feature = "neon") +vandq_s8 :: #force_inline proc "c" (a, b: int8x16_t) -> int8x16_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vandq_u8) +@(require_results, enable_target_feature = "neon") +vandq_u8 :: #force_inline proc "c" (a, b: uint8x16_t) -> uint8x16_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vandq_s16) +@(require_results, enable_target_feature = "neon") +vandq_s16 :: #force_inline proc "c" (a, b: int16x8_t) -> int16x8_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vandq_u16) +@(require_results, enable_target_feature = "neon") +vandq_u16 :: #force_inline proc "c" (a, b: uint16x8_t) -> uint16x8_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vandq_s32) +@(require_results, enable_target_feature = "neon") +vandq_s32 :: #force_inline proc "c" (a, b: int32x4_t) -> int32x4_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vandq_u32) +@(require_results, enable_target_feature = "neon") +vandq_u32 :: #force_inline proc "c" (a, b: uint32x4_t) -> uint32x4_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vandq_s64) +@(require_results, enable_target_feature = "neon") +vandq_s64 :: #force_inline proc "c" (a, b: int64x2_t) -> int64x2_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vandq_u64) +@(require_results, enable_target_feature = "neon") +vandq_u64 :: #force_inline proc "c" (a, b: uint64x2_t) -> uint64x2_t { + return simd.bit_and(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorr_s8) +@(require_results, enable_target_feature = "neon") +vorr_s8 :: #force_inline proc "c" (a, b: int8x8_t) -> int8x8_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorr_u8) +@(require_results, enable_target_feature = "neon") +vorr_u8 :: #force_inline proc "c" (a, b: uint8x8_t) -> uint8x8_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorr_s16) +@(require_results, enable_target_feature = "neon") +vorr_s16 :: #force_inline proc "c" (a, b: int16x4_t) -> int16x4_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorr_u16) +@(require_results, enable_target_feature = "neon") +vorr_u16 :: #force_inline proc "c" (a, b: uint16x4_t) -> uint16x4_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorr_s32) +@(require_results, enable_target_feature = "neon") +vorr_s32 :: #force_inline proc "c" (a, b: int32x2_t) -> int32x2_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorr_u32) +@(require_results, enable_target_feature = "neon") +vorr_u32 :: #force_inline proc "c" (a, b: uint32x2_t) -> uint32x2_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorr_s64) +@(require_results, enable_target_feature = "neon") +vorr_s64 :: #force_inline proc "c" (a, b: int64x1_t) -> int64x1_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorr_u64) +@(require_results, enable_target_feature = "neon") +vorr_u64 :: #force_inline proc "c" (a, b: uint64x1_t) -> uint64x1_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorrq_s8) +@(require_results, enable_target_feature = "neon") +vorrq_s8 :: #force_inline proc "c" (a, b: int8x16_t) -> int8x16_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorrq_u8) +@(require_results, enable_target_feature = "neon") +vorrq_u8 :: #force_inline proc "c" (a, b: uint8x16_t) -> uint8x16_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorrq_s16) +@(require_results, enable_target_feature = "neon") +vorrq_s16 :: #force_inline proc "c" (a, b: int16x8_t) -> int16x8_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorrq_u16) +@(require_results, enable_target_feature = "neon") +vorrq_u16 :: #force_inline proc "c" (a, b: uint16x8_t) -> uint16x8_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorrq_s32) +@(require_results, enable_target_feature = "neon") +vorrq_s32 :: #force_inline proc "c" (a, b: int32x4_t) -> int32x4_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorrq_u32) +@(require_results, enable_target_feature = "neon") +vorrq_u32 :: #force_inline proc "c" (a, b: uint32x4_t) -> uint32x4_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorrq_s64) +@(require_results, enable_target_feature = "neon") +vorrq_s64 :: #force_inline proc "c" (a, b: int64x2_t) -> int64x2_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorrq_u64) +@(require_results, enable_target_feature = "neon") +vorrq_u64 :: #force_inline proc "c" (a, b: uint64x2_t) -> uint64x2_t { + return simd.bit_or(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veor_s8) +@(require_results, enable_target_feature = "neon") +veor_s8 :: #force_inline proc "c" (a, b: int8x8_t) -> int8x8_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veor_u8) +@(require_results, enable_target_feature = "neon") +veor_u8 :: #force_inline proc "c" (a, b: uint8x8_t) -> uint8x8_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veor_s16) +@(require_results, enable_target_feature = "neon") +veor_s16 :: #force_inline proc "c" (a, b: int16x4_t) -> int16x4_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veor_u16) +@(require_results, enable_target_feature = "neon") +veor_u16 :: #force_inline proc "c" (a, b: uint16x4_t) -> uint16x4_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veor_s32) +@(require_results, enable_target_feature = "neon") +veor_s32 :: #force_inline proc "c" (a, b: int32x2_t) -> int32x2_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veor_u32) +@(require_results, enable_target_feature = "neon") +veor_u32 :: #force_inline proc "c" (a, b: uint32x2_t) -> uint32x2_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veor_s64) +@(require_results, enable_target_feature = "neon") +veor_s64 :: #force_inline proc "c" (a, b: int64x1_t) -> int64x1_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veor_u64) +@(require_results, enable_target_feature = "neon") +veor_u64 :: #force_inline proc "c" (a, b: uint64x1_t) -> uint64x1_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veorq_s8) +@(require_results, enable_target_feature = "neon") +veorq_s8 :: #force_inline proc "c" (a, b: int8x16_t) -> int8x16_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veorq_u8) +@(require_results, enable_target_feature = "neon") +veorq_u8 :: #force_inline proc "c" (a, b: uint8x16_t) -> uint8x16_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veorq_s16) +@(require_results, enable_target_feature = "neon") +veorq_s16 :: #force_inline proc "c" (a, b: int16x8_t) -> int16x8_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veorq_u16) +@(require_results, enable_target_feature = "neon") +veorq_u16 :: #force_inline proc "c" (a, b: uint16x8_t) -> uint16x8_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veorq_s32) +@(require_results, enable_target_feature = "neon") +veorq_s32 :: #force_inline proc "c" (a, b: int32x4_t) -> int32x4_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veorq_u32) +@(require_results, enable_target_feature = "neon") +veorq_u32 :: #force_inline proc "c" (a, b: uint32x4_t) -> uint32x4_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veorq_s64) +@(require_results, enable_target_feature = "neon") +veorq_s64 :: #force_inline proc "c" (a, b: int64x2_t) -> int64x2_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veorq_u64) +@(require_results, enable_target_feature = "neon") +veorq_u64 :: #force_inline proc "c" (a, b: uint64x2_t) -> uint64x2_t { + return simd.bit_xor(a, b) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorn_s8) +@(require_results, enable_target_feature = "neon") +vorn_s8 :: #force_inline proc "c" (a, b: int8x8_t) -> int8x8_t { + c := int8x8_t(-1) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorn_u8) +@(require_results, enable_target_feature = "neon") +vorn_u8 :: #force_inline proc "c" (a, b: uint8x8_t) -> uint8x8_t { + c := uint8x8_t(max(uint8_t)) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorn_s16) +@(require_results, enable_target_feature = "neon") +vorn_s16 :: #force_inline proc "c" (a, b: int16x4_t) -> int16x4_t { + c := int16x4_t(-1) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorn_u16) +@(require_results, enable_target_feature = "neon") +vorn_u16 :: #force_inline proc "c" (a, b: uint16x4_t) -> uint16x4_t { + c := uint16x4_t(max(uint16_t)) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorn_s32) +@(require_results, enable_target_feature = "neon") +vorn_s32 :: #force_inline proc "c" (a, b: int32x2_t) -> int32x2_t { + c := int32x2_t(-1) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorn_u32) +@(require_results, enable_target_feature = "neon") +vorn_u32 :: #force_inline proc "c" (a, b: uint32x2_t) -> uint32x2_t { + c := uint32x2_t(max(uint32_t)) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorn_s64) +@(require_results, enable_target_feature = "neon") +vorn_s64 :: #force_inline proc "c" (a, b: int64x1_t) -> int64x1_t { + c := int64x1_t(-1) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorn_u64) +@(require_results, enable_target_feature = "neon") +vorn_u64 :: #force_inline proc "c" (a, b: uint64x1_t) -> uint64x1_t { + c := uint64x1_t(max(uint64_t)) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vornq_s8) +@(require_results, enable_target_feature = "neon") +vornq_s8 :: #force_inline proc "c" (a, b: int8x16_t) -> int8x16_t { + c := int8x16_t(-1) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vornq_u8) +@(require_results, enable_target_feature = "neon") +vornq_u8 :: #force_inline proc "c" (a, b: uint8x16_t) -> uint8x16_t { + c := uint8x16_t(max(uint8_t)) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vornq_s16) +@(require_results, enable_target_feature = "neon") +vornq_s16 :: #force_inline proc "c" (a, b: int16x8_t) -> int16x8_t { + c := int16x8_t(-1) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vornq_u16) +@(require_results, enable_target_feature = "neon") +vornq_u16 :: #force_inline proc "c" (a, b: uint16x8_t) -> uint16x8_t { + c := uint16x8_t(max(uint16_t)) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vornq_s32) +@(require_results, enable_target_feature = "neon") +vornq_s32 :: #force_inline proc "c" (a, b: int32x4_t) -> int32x4_t { + c := int32x4_t(-1) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vornq_u32) +@(require_results, enable_target_feature = "neon") +vornq_u32 :: #force_inline proc "c" (a, b: uint32x4_t) -> uint32x4_t { + c := uint32x4_t(max(uint32_t)) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vornq_s64) +@(require_results, enable_target_feature = "neon") +vornq_s64 :: #force_inline proc "c" (a, b: int64x2_t) -> int64x2_t { + c := int64x2_t(-1) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vornq_u64) +@(require_results, enable_target_feature = "neon") +vornq_u64 :: #force_inline proc "c" (a, b: uint64x2_t) -> uint64x2_t { + c := uint64x2_t(max(uint64_t)) + return simd.bit_or(simd.bit_xor(b, c), a) +} + when ODIN_ARCH == .arm64 { // Table Lookup. // @@ -891,12 +1899,12 @@ when ODIN_ARCH == .arm64 { when ODIN_ENDIAN == .Little { return _vqtbl2(t.x, t.y, idx) } else { - v := int8x16x2_t { + t := int8x16x2_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) - c := _vqtbl2(v.x, v.y, idx) + c := _vqtbl2(t.x, t.y, idx) return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) } } @@ -913,14 +1921,14 @@ when ODIN_ARCH == .arm64 { idx, ) } else { - v := int8x16x2_t { + t := uint8x16x2_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) c := transmute(uint8x8_t)_vqtbl2( - transmute(int8x16_t)v.x, - transmute(int8x16_t)v.y, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, idx, ) return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) @@ -935,12 +1943,12 @@ when ODIN_ARCH == .arm64 { when ODIN_ENDIAN == .Little { return _vqtbl2q(t.x, t.y, idx) } else { - v := int8x16x2_t { + t := int8x16x2_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) - c := _vqtbl2q(v.x, v.y, idx) + c := _vqtbl2q(t.x, t.y, idx) return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) } } @@ -957,14 +1965,14 @@ when ODIN_ARCH == .arm64 { idx, ) } else { - v := int8x16x2_t { + t := uint8x16x2_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) c := transmute(uint8x16_t)_vqtbl2q( - transmute(int8x16_t)v.x, - transmute(int8x16_t)v.y, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, idx, ) return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) @@ -979,13 +1987,13 @@ when ODIN_ARCH == .arm64 { when ODIN_ENDIAN == .Little { return _vqtbl3(t.x, t.y, t.z, idx) } else { - v := int8x16x3_t { + t := int8x16x3_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) - c := _vqtbl3(v.x, v.y, v.z, idx) + c := _vqtbl3(t.x, t.y, t.z, idx) return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) } } @@ -1003,16 +2011,16 @@ when ODIN_ARCH == .arm64 { idx, ) } else { - v := int8x16x3_t { + t := uint8x16x3_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) c := transmute(uint8x8_t)_vqtbl3( - transmute(int8x16_t)v.x, - transmute(int8x16_t)v.y, - transmute(int8x16_t)v.z, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, idx, ) return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) @@ -1027,13 +2035,13 @@ when ODIN_ARCH == .arm64 { when ODIN_ENDIAN == .Little { return _vqtbl3q(t.x, t.y, t.z, idx) } else { - v := int8x16x3_t { + t := int8x16x3_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) - c := _vqtbl3q(v.x, v.y, v.z, idx) + c := _vqtbl3q(t.x, t.y, t.z, idx) return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) } } @@ -1051,16 +2059,16 @@ when ODIN_ARCH == .arm64 { idx, ) } else { - v := int8x16x3_t { + t := uint8x16x3_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) c := transmute(uint8x16_t)_vqtbl3q( - transmute(int8x16_t)v.x, - transmute(int8x16_t)v.y, - transmute(int8x16_t)v.z, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, idx, ) return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) @@ -1075,14 +2083,14 @@ when ODIN_ARCH == .arm64 { when ODIN_ENDIAN == .Little { return _vqtbl4(t.x, t.y, t.z, t.w, idx) } else { - v := int8x16x4_t { + t := int8x16x4_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.w, t.w, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) - c := _vqtbl4(v.x, v.y, v.z, v.w, idx) + c := _vqtbl4(t.x, t.y, t.z, t.w, idx) return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) } } @@ -1101,7 +2109,7 @@ when ODIN_ARCH == .arm64 { idx, ) } else { - v := int8x16x4_t { + t := uint8x16x4_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), @@ -1109,10 +2117,10 @@ when ODIN_ARCH == .arm64 { } idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) c := transmute(uint8x8_t)_vqtbl4( - transmute(int8x16_t)v.x, - transmute(int8x16_t)v.y, - transmute(int8x16_t)v.z, - transmute(int8x16_t)v.w, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + transmute(int8x16_t)t.w, idx, ) return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) @@ -1127,14 +2135,14 @@ when ODIN_ARCH == .arm64 { when ODIN_ENDIAN == .Little { return _vqtbl4q(t.x, t.y, t.z, t.w, idx) } else { - v := int8x16x4_t { + t := int8x16x4_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.w, t.w, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) - c := _vqtbl4q(v.x, v.y, v.z, v.w, idx) + c := _vqtbl4q(t.x, t.y, t.z, t.w, idx) return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) } } @@ -1153,7 +2161,7 @@ when ODIN_ARCH == .arm64 { idx, ) } else { - v := int8x16x4_t { + t := uint8x16x4_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), @@ -1161,15 +2169,479 @@ when ODIN_ARCH == .arm64 { } idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) c := transmute(uint8x16_t)_vqtbl4q( - transmute(int8x16_t)v.x, - transmute(int8x16_t)v.y, - transmute(int8x16_t)v.z, - transmute(int8x16_t)v.w, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + transmute(int8x16_t)t.w, idx, ) return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) } } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx1_s8) + @(require_results, enable_target_feature = "neon") + vqtbx1_s8 :: #force_inline proc "c" (v: int8x8_t, t: int8x16_t, idx: uint8x8_t) -> int8x8_t { + when ODIN_ENDIAN == .Little { + return _vqtbx1(v, t, idx) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + t := simd.shuffle(t, t, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) + c := _vqtbx1(v, t, idx) + return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx1_u8) + @(require_results, enable_target_feature = "neon") + vqtbx1_u8 :: #force_inline proc "c" (v: uint8x8_t, t: uint8x16_t, idx: uint8x8_t) -> uint8x8_t { + when ODIN_ENDIAN == .Little { + return transmute(uint8x8_t)_vqtbx1( + transmute(int8x8_t)v, + transmute(int8x16_t)t, + idx, + ) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + t := simd.shuffle(t, t, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(uint8x8_t)_vqtbx1( + transmute(int8x8_t)v, + transmute(int8x16_t)t, + idx, + ) + return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx1q_s8) + @(require_results, enable_target_feature = "neon") + vqtbx1q_s8 :: #force_inline proc "c" (v: int8x16_t, t: int8x16_t, idx: uint8x16_t) -> int8x16_t { + when ODIN_ENDIAN == .Little { + return _vqtbx1q(v, t, idx) + } else { + v := simd.shuffle(v, v, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + t := simd.shuffle(t, t, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + c := _vqtbx1q(v, t, idx) + return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx1q_u8) + @(require_results, enable_target_feature = "neon") + vqtbx1q_u8 :: #force_inline proc "c" (v: uint8x16_t, t: uint8x16_t, idx: uint8x16_t) -> uint8x16_t { + when ODIN_ENDIAN == .Little { + return transmute(uint8x16_t)_vqtbx1q( + transmute(int8x16_t)v, + transmute(int8x16_t)t, + idx, + ) + } else { + v := simd.shuffle(v, v, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + t := simd.shuffle(t, t, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(uint8x16_t)_vqtbx1q( + transmute(int8x16_t)v, + transmute(int8x16_t)t, + idx, + ) + return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx2_s8) + @(require_results, enable_target_feature = "neon") + vqtbx2_s8 :: #force_inline proc "c" (v: int8x8_t, t: int8x16x2_t, idx: uint8x8_t) -> int8x8_t { + when ODIN_ENDIAN == .Little { + return _vqtbx2(v, t.x, t.y, idx) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + t := int8x16x2_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) + c := _vqtbx2(v, t.x, t.y, idx) + return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx2_u8) + @(require_results, enable_target_feature = "neon") + vqtbx2_u8 :: #force_inline proc "c" (v: uint8x8_t, t: uint8x16x2_t, idx: uint8x8_t) -> uint8x8_t { + when ODIN_ENDIAN == .Little { + return transmute(uint8x8_t)_vqtbx2( + transmute(int8x8_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + idx, + ) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + t := uint8x16x2_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(uint8x8_t)_vqtbx2( + transmute(int8x8_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + idx, + ) + return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx2q_s8) + @(require_results, enable_target_feature = "neon") + vqtbx2q_s8 :: #force_inline proc "c" (v: int8x16_t, t: int8x16x2_t, idx: uint8x16_t) -> int8x16_t { + when ODIN_ENDIAN == .Little { + return _vqtbx2q(v, t.x, t.y, idx) + } else { + v := simd.shuffle(v, v, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + t := int8x16x2_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + c := _vqtbx2q(v, t.x, t.y, idx) + return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx2q_u8) + @(require_results, enable_target_feature = "neon") + vqtbx2q_u8 :: #force_inline proc "c" (v: uint8x16_t, t: uint8x16x2_t, idx: uint8x16_t) -> uint8x16_t { + when ODIN_ENDIAN == .Little { + return transmute(uint8x16_t)_vqtbx2q( + transmute(int8x16_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + idx, + ) + } else { + v := simd.shuffle(v, v, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + t := uint8x16x2_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(uint8x16_t)_vqtbx2q( + transmute(int8x16_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + idx, + ) + return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx3_s8) + @(require_results, enable_target_feature = "neon") + vqtbx3_s8 :: #force_inline proc "c" (v: int8x8_t, t: int8x16x3_t, idx: uint8x8_t) -> int8x8_t { + when ODIN_ENDIAN == .Little { + return _vqtbx3(v, t.x, t.y, t.z, idx) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + t := int8x16x3_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) + c := _vqtbx3(v, t.x, t.y, t.z, idx) + return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx3_u8) + @(require_results, enable_target_feature = "neon") + vqtbx3_u8 :: #force_inline proc "c" (v: uint8x8_t, t: uint8x16x3_t, idx: uint8x8_t) -> uint8x8_t { + when ODIN_ENDIAN == .Little { + return transmute(uint8x8_t)_vqtbx3( + transmute(int8x8_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + idx, + ) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + t := uint8x16x3_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(uint8x8_t)_vqtbx3( + transmute(int8x8_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + idx, + ) + return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx3q_s8) + @(require_results, enable_target_feature = "neon") + vqtbx3q_s8 :: #force_inline proc "c" (v: int8x16_t, t: int8x16x3_t, idx: uint8x16_t) -> int8x16_t { + when ODIN_ENDIAN == .Little { + return _vqtbx3q(v, t.x, t.y, t.z, idx) + } else { + v := simd.shuffle(v, v, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + t := int8x16x3_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + c := _vqtbx3q(v, t.x, t.y, t.z, idx) + return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx3q_u8) + @(require_results, enable_target_feature = "neon") + vqtbx3q_u8 :: #force_inline proc "c" (v: uint8x16_t, t: uint8x16x3_t, idx: uint8x16_t) -> uint8x16_t { + when ODIN_ENDIAN == .Little { + return transmute(uint8x16_t)_vqtbx3q( + transmute(int8x16_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + idx, + ) + } else { + v := simd.shuffle(v, v, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + t := uint8x16x3_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(uint8x16_t)_vqtbx3q( + transmute(int8x16_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + idx, + ) + return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx4_s8) + @(require_results, enable_target_feature = "neon") + vqtbx4_s8 :: #force_inline proc "c" (v: int8x8_t, t: int8x16x4_t, idx: uint8x8_t) -> int8x8_t { + when ODIN_ENDIAN == .Little { + return _vqtbx4(v, t.x, t.y, t.z, t.w, idx) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + t := int8x16x4_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.w, t.w, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) + c := _vqtbx4(v, t.x, t.y, t.z, t.w, idx) + return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx4_u8) + @(require_results, enable_target_feature = "neon") + vqtbx4_u8 :: #force_inline proc "c" (v: uint8x8_t, t: uint8x16x4_t, idx: uint8x8_t) -> uint8x8_t { + when ODIN_ENDIAN == .Little { + return transmute(uint8x8_t)_vqtbx4( + transmute(int8x8_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + transmute(int8x16_t)t.w, + idx, + ) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + t := uint8x16x4_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.w, t.w, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(uint8x8_t)_vqtbx4( + transmute(int8x8_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + transmute(int8x16_t)t.w, + idx, + ) + return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx4q_s8) + @(require_results, enable_target_feature = "neon") + vqtbx4q_s8 :: #force_inline proc "c" (v: int8x16_t, t: int8x16x4_t, idx: uint8x16_t) -> int8x16_t { + when ODIN_ENDIAN == .Little { + return _vqtbx4q(v, t.x, t.y, t.z, t.w, idx) + } else { + v := simd.shuffle(v, v, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + t := int8x16x4_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.w, t.w, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + c := _vqtbx4q(v, t.x, t.y, t.z, t.w, idx) + return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx4q_u8) + @(require_results, enable_target_feature = "neon") + vqtbx4q_u8 :: #force_inline proc "c" (v: uint8x16_t, t: uint8x16x4_t, idx: uint8x16_t) -> uint8x16_t { + when ODIN_ENDIAN == .Little { + return transmute(uint8x16_t)_vqtbx4q( + transmute(int8x16_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + transmute(int8x16_t)t.w, + idx, + ) + } else { + v := simd.shuffle(v, v, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + t := uint8x16x4_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.w, t.w, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(uint8x16_t)_vqtbx4q( + transmute(int8x16_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + transmute(int8x16_t)t.w, + idx, + ) + return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Negate. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vneg_s64) + @(require_results, enable_target_feature = "neon") + vneg_s64 :: #force_inline proc "c" (a: int64x1_t) -> int64x1_t { + return simd.neg(a) + } + + // Negate. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vnegd_s64) + @(require_results, enable_target_feature = "neon") + vnegd_s64 :: #force_inline proc "c" (a: int64_t) -> int64_t { + return -a + } + + // Negate. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vnegq_s64) + @(require_results, enable_target_feature = "neon") + vnegq_s64 :: #force_inline proc "c" (a: int64x2_t) -> int64x2_t { + return simd.neg(a) + } + + // Signed saturating Negate. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqneg_s64) + @(require_results, enable_target_feature = "neon") + vqneg_s64 :: #force_inline proc "c" (a: int64x1_t) -> int64x1_t { + return _vqneg_s64(a) + } + + // Signed saturating Negate. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqnegq_s64) + @(require_results, enable_target_feature = "neon") + vqnegq_s64 :: #force_inline proc "c" (a: int64x2_t) -> int64x2_t { + return _vqnegq_s64(a) + } + + // Signed saturating Negate. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqnegb_s8) + @(require_results, enable_target_feature = "neon") + vqnegb_s8 :: #force_inline proc "c" (a: int8_t) -> int8_t { + return vget_lane_s8(vqneg_s8(vdup_n_s8(a)), 0) + } + + // Signed saturating Negate. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqnegh_s16) + @(require_results, enable_target_feature = "neon") + vqnegh_s16 :: #force_inline proc "c" (a: int16_t) -> int16_t { + return vget_lane_s16(vqneg_s16(vdup_n_s16(a)), 0) + } + + // Signed saturating Negate. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqnegs_s32) + @(require_results, enable_target_feature = "neon") + vqnegs_s32 :: #force_inline proc "c" (a: int32_t) -> int32_t { + return vget_lane_s32(vqneg_s32(vdup_n_s32(a)), 0) + } + + // Signed saturating Negate. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqnegd_s64) + @(require_results, enable_target_feature = "neon") + vqnegd_s64 :: #force_inline proc "c" (a: int64_t) -> int64_t { + return vget_lane_s64(vqneg_s64(vdup_n_s64(a)), 0) + } } @(private, default_calling_convention = "none") @@ -1186,6 +2658,18 @@ foreign _ { _vclsq_s16 :: proc(a: int16x8_t) -> int16x8_t --- @(link_name = "llvm.aarch64.neon.cls.v4i32" when ODIN_ARCH == .arm64 else "llvm.arm.neon.vcls.v4i32") _vclsq_s32 :: proc(a: int32x4_t) -> int32x4_t --- + @(link_name = "llvm.aarch64.neon.sqneg.v8i8" when ODIN_ARCH == .arm64 else "llvm.arm.neon.vqneg.v8i8") + _vqneg_s8 :: proc(a: int8x8_t) -> int8x8_t --- + @(link_name = "llvm.aarch64.neon.sqneg.v4i16" when ODIN_ARCH == .arm64 else "llvm.arm.neon.vqneg.v4i16") + _vqneg_s16 :: proc(a: int16x4_t) -> int16x4_t --- + @(link_name = "llvm.aarch64.neon.sqneg.v2i32" when ODIN_ARCH == .arm64 else "llvm.arm.neon.vqneg.v2i32") + _vqneg_s32 :: proc(a: int32x2_t) -> int32x2_t --- + @(link_name = "llvm.aarch64.neon.sqneg.v16i8" when ODIN_ARCH == .arm64 else "llvm.arm.neon.vqneg.v16i8") + _vqnegq_s8 :: proc(a: int8x16_t) -> int8x16_t --- + @(link_name = "llvm.aarch64.neon.sqneg.v8i16" when ODIN_ARCH == .arm64 else "llvm.arm.neon.vqneg.v8i16") + _vqnegq_s16 :: proc(a: int16x8_t) -> int16x8_t --- + @(link_name = "llvm.aarch64.neon.sqneg.v4i32" when ODIN_ARCH == .arm64 else "llvm.arm.neon.vqneg.v4i32") + _vqnegq_s32 :: proc(a: int32x4_t) -> int32x4_t --- } when ODIN_ARCH == .arm32 { @@ -1199,12 +2683,24 @@ when ODIN_ARCH == .arm32 { _vtbl3 :: proc(t0, t1, t2: int8x8_t, idx: int8x8_t) -> int8x8_t --- @(link_name = "llvm.arm.neon.vtbl4") _vtbl4 :: proc(t0, t1, t2, t3: int8x8_t, idx: int8x8_t) -> int8x8_t --- + @(link_name = "llvm.arm.neon.vtbx1") + _vtbx1 :: proc(v: int8x8_t, t: int8x8_t, idx: int8x8_t) -> int8x8_t --- + @(link_name = "llvm.arm.neon.vtbx2") + _vtbx2 :: proc(v: int8x8_t, t0, t1: int8x8_t, idx: int8x8_t) -> int8x8_t --- + @(link_name = "llvm.arm.neon.vtbx3") + _vtbx3 :: proc(v: int8x8_t, t0, t1, t2: int8x8_t, idx: int8x8_t) -> int8x8_t --- + @(link_name = "llvm.arm.neon.vtbx4") + _vtbx4 :: proc(v: int8x8_t, t0, t1, t2, t3: int8x8_t, idx: int8x8_t) -> int8x8_t --- } } when ODIN_ARCH == .arm64 { @(private, default_calling_convention = "none") foreign _ { + @(link_name = "llvm.aarch64.neon.sqneg.v1i64") + _vqneg_s64 :: proc(a: int64x1_t) -> int64x1_t --- + @(link_name = "llvm.aarch64.neon.sqneg.v2i64") + _vqnegq_s64 :: proc(a: int64x2_t) -> int64x2_t --- @(link_name = "llvm.aarch64.neon.tbl1.v8i8") _vqtbl1 :: proc(t: int8x16_t, idx: uint8x8_t) -> int8x8_t --- @(link_name = "llvm.aarch64.neon.tbl1.v16i8") @@ -1221,5 +2717,21 @@ when ODIN_ARCH == .arm64 { _vqtbl4 :: proc(t0, t1, t2, t3: int8x16_t, idx: uint8x8_t) -> int8x8_t --- @(link_name = "llvm.aarch64.neon.tbl4.v16i8") _vqtbl4q :: proc(t0, t1, t2, t3: int8x16_t, idx: uint8x16_t) -> int8x16_t --- + @(link_name = "llvm.aarch64.neon.tbx1.v8i8") + _vqtbx1 :: proc(v: int8x8_t, t: int8x16_t, idx: uint8x8_t) -> int8x8_t --- + @(link_name = "llvm.aarch64.neon.tbx1.v16i8") + _vqtbx1q :: proc(v: int8x16_t, t: int8x16_t, idx: uint8x16_t) -> int8x16_t --- + @(link_name = "llvm.aarch64.neon.tbx2.v8i8") + _vqtbx2 :: proc(v: int8x8_t, t0, t1: int8x16_t, idx: uint8x8_t) -> int8x8_t --- + @(link_name = "llvm.aarch64.neon.tbx2.v16i8") + _vqtbx2q :: proc(v: int8x16_t, t0, t1: int8x16_t, idx: uint8x16_t) -> int8x16_t --- + @(link_name = "llvm.aarch64.neon.tbx3.v8i8") + _vqtbx3 :: proc(v: int8x8_t, t0, t1, t2: int8x16_t, idx: uint8x8_t) -> int8x8_t --- + @(link_name = "llvm.aarch64.neon.tbx3.v16i8") + _vqtbx3q :: proc(v: int8x16_t, t0, t1, t2: int8x16_t, idx: uint8x16_t) -> int8x16_t --- + @(link_name = "llvm.aarch64.neon.tbx4.v8i8") + _vqtbx4 :: proc(v: int8x8_t, t0, t1, t2, t3: int8x16_t, idx: uint8x8_t) -> int8x8_t --- + @(link_name = "llvm.aarch64.neon.tbx4.v16i8") + _vqtbx4q :: proc(v: int8x16_t, t0, t1, t2, t3: int8x16_t, idx: uint8x16_t) -> int8x16_t --- } } diff --git a/core/simd/arm/pmull.odin b/core/simd/arm/pmull.odin index 5cfbe3117..cb2940b2d 100644 --- a/core/simd/arm/pmull.odin +++ b/core/simd/arm/pmull.odin @@ -502,6 +502,134 @@ vtbl4_p8 :: #force_inline proc "c" (t: poly8x8x4_t, idx: poly8x8_t) -> poly8x8_t } } +// Extended Table Lookup. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbx1_p8) +@(require_results, enable_target_feature = "neon") +vtbx1_p8 :: #force_inline proc "c" (v: poly8x8_t, t: poly8x8_t, idx: uint8x8_t) -> poly8x8_t { + when ODIN_ARCH == .arm64 { + return simd.select( + simd.lanes_lt(idx, uint8x8_t(8)), + vqtbx1_p8(v, vcombine_p8(t, poly8x8_t{}), idx), + v, + ) + } else { + return transmute(poly8x8_t)_vtbx1( + transmute(int8x8_t)v, + transmute(int8x8_t)t, + transmute(int8x8_t)idx, + ) + } +} + +// Extended Table Lookup. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbx2_p8) +@(require_results, enable_target_feature = "neon") +vtbx2_p8 :: #force_inline proc "c" (v: poly8x8_t, t: poly8x8x2_t, idx: uint8x8_t) -> poly8x8_t { + when ODIN_ARCH == .arm64 { + return simd.select( + simd.lanes_lt(idx, uint8x8_t(16)), + vqtbx1_p8(v, vcombine_p8(t.x, t.y), idx), + v, + ) + } else { + return transmute(poly8x8_t)_vtbx2( + transmute(int8x8_t)v, + transmute(int8x8_t)t.x, + transmute(int8x8_t)t.y, + transmute(int8x8_t)idx, + ) + } +} + +// Extended Table Lookup. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbx3_p8) +@(require_results, enable_target_feature = "neon") +vtbx3_p8 :: #force_inline proc "c" (v: poly8x8_t, t: poly8x8x3_t, idx: uint8x8_t) -> poly8x8_t { + when ODIN_ARCH == .arm64 { + x := poly8x16x2_t { + vcombine_p8(t.x, t.y), + vcombine_p8(t.z, poly8x8_t{}), + } + return simd.select( + simd.lanes_lt(idx, uint8x8_t(24)), + vqtbx2_p8(v, x, idx), + v, + ) + } else { + return transmute(poly8x8_t)_vtbx3( + transmute(int8x8_t)v, + transmute(int8x8_t)t.x, + transmute(int8x8_t)t.y, + transmute(int8x8_t)t.z, + transmute(int8x8_t)idx, + ) + } +} + +// Extended Table Lookup. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbx4_p8) +@(require_results, enable_target_feature = "neon") +vtbx4_p8 :: #force_inline proc "c" (v: poly8x8_t, t: poly8x8x4_t, idx: uint8x8_t) -> poly8x8_t { + when ODIN_ARCH == .arm64 { + x := poly8x16x2_t { + vcombine_p8(t.x, t.y), + vcombine_p8(t.z, t.w), + } + return simd.select( + simd.lanes_lt(idx, uint8x8_t(32)), + vqtbx2_p8(v, x, idx), + v, + ) + } else { + return transmute(poly8x8_t)_vtbx4( + transmute(int8x8_t)v, + transmute(int8x8_t)t.x, + transmute(int8x8_t)t.y, + transmute(int8x8_t)t.z, + transmute(int8x8_t)t.w, + transmute(int8x8_t)idx, + ) + } +} + +// Population count per byte. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vcnt_p8) +@(require_results, enable_target_feature = "neon") +vcnt_p8 :: #force_inline proc "c" (a: poly8x8_t) -> poly8x8_t { + return transmute(poly8x8_t)vcnt_s8(transmute(int8x8_t)a) +} + +// Population count per byte. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vcntq_p8) +@(require_results, enable_target_feature = "neon") +vcntq_p8 :: #force_inline proc "c" (a: poly8x16_t) -> poly8x16_t { + return transmute(poly8x16_t)vcntq_s8(transmute(int8x16_t)a) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvn_p8) +@(require_results, enable_target_feature = "neon") +vmvn_p8 :: #force_inline proc "c" (a: poly8x8_t) -> poly8x8_t { + b := poly8x8_t(max(poly8_t)) + return simd.bit_xor(a, b) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvnq_p8) +@(require_results, enable_target_feature = "neon") +vmvnq_p8 :: #force_inline proc "c" (a: poly8x16_t) -> poly8x16_t { + b := poly8x16_t(max(poly8_t)) + return simd.bit_xor(a, b) +} + when ODIN_ARCH == .arm64 { // Polynomial multiply long // @@ -571,14 +699,14 @@ when ODIN_ARCH == .arm64 { idx, ) } else { - v := int8x16x2_t { + t := poly8x16x2_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) c := transmute(poly8x8_t)_vqtbl2( - transmute(int8x16_t)v.x, - transmute(int8x16_t)v.y, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, idx, ) return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) @@ -597,14 +725,14 @@ when ODIN_ARCH == .arm64 { idx, ) } else { - v := int8x16x2_t { + t := poly8x16x2_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) c := transmute(poly8x16_t)_vqtbl2q( - transmute(int8x16_t)v.x, - transmute(int8x16_t)v.y, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, idx, ) return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) @@ -624,16 +752,16 @@ when ODIN_ARCH == .arm64 { idx, ) } else { - v := int8x16x3_t { + t := poly8x16x3_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) c := transmute(poly8x8_t)_vqtbl3( - transmute(int8x16_t)v.x, - transmute(int8x16_t)v.y, - transmute(int8x16_t)v.z, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, idx, ) return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) @@ -653,16 +781,16 @@ when ODIN_ARCH == .arm64 { idx, ) } else { - v := int8x16x3_t { + t := poly8x16x3_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) c := transmute(poly8x16_t)_vqtbl3q( - transmute(int8x16_t)v.x, - transmute(int8x16_t)v.y, - transmute(int8x16_t)v.z, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, idx, ) return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) @@ -683,7 +811,7 @@ when ODIN_ARCH == .arm64 { idx, ) } else { - v := int8x16x4_t { + t := poly8x16x4_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), @@ -691,10 +819,10 @@ when ODIN_ARCH == .arm64 { } idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) c := transmute(poly8x8_t)_vqtbl4( - transmute(int8x16_t)v.x, - transmute(int8x16_t)v.y, - transmute(int8x16_t)v.z, - transmute(int8x16_t)v.w, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + transmute(int8x16_t)t.w, idx, ) return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) @@ -715,7 +843,7 @@ when ODIN_ARCH == .arm64 { idx, ) } else { - v := int8x16x4_t { + t := poly8x16x4_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), @@ -723,10 +851,250 @@ when ODIN_ARCH == .arm64 { } idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) c := transmute(poly8x16_t)_vqtbl4q( - transmute(int8x16_t)v.x, - transmute(int8x16_t)v.y, - transmute(int8x16_t)v.z, - transmute(int8x16_t)v.w, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + transmute(int8x16_t)t.w, + idx, + ) + return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx1_p8) + @(require_results, enable_target_feature = "neon") + vqtbx1_p8 :: #force_inline proc "c" (v: poly8x8_t, t: poly8x16_t, idx: uint8x8_t) -> poly8x8_t { + when ODIN_ENDIAN == .Little { + return transmute(poly8x8_t)_vqtbx1( + transmute(int8x8_t)v, + transmute(int8x16_t)t, + idx, + ) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + t := simd.shuffle(t, t, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(poly8x8_t)_vqtbx1( + transmute(int8x8_t)v, + transmute(int8x16_t)t, + idx, + ) + return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx1q_p8) + @(require_results, enable_target_feature = "neon") + vqtbx1q_p8 :: #force_inline proc "c" (v: poly8x16_t, t: poly8x16_t, idx: uint8x16_t) -> poly8x16_t { + when ODIN_ENDIAN == .Little { + return transmute(poly8x16_t)_vqtbx1q( + transmute(int8x16_t)v, + transmute(int8x16_t)t, + idx, + ) + } else { + v := simd.shuffle(v, v, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + t := simd.shuffle(t, t, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(poly8x16_t)_vqtbx1q( + transmute(int8x16_t)v, + transmute(int8x16_t)t, + idx, + ) + return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx2_p8) + @(require_results, enable_target_feature = "neon") + vqtbx2_p8 :: #force_inline proc "c" (v: poly8x8_t, t: poly8x16x2_t, idx: uint8x8_t) -> poly8x8_t { + when ODIN_ENDIAN == .Little { + return transmute(poly8x8_t)_vqtbx2( + transmute(int8x8_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + idx, + ) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + t := poly8x16x2_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(poly8x8_t)_vqtbx2( + transmute(int8x8_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + idx, + ) + return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx2q_p8) + @(require_results, enable_target_feature = "neon") + vqtbx2q_p8 :: #force_inline proc "c" (v: poly8x16_t, t: poly8x16x2_t, idx: uint8x16_t) -> poly8x16_t { + when ODIN_ENDIAN == .Little { + return transmute(poly8x16_t)_vqtbx2q( + transmute(int8x16_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + idx, + ) + } else { + v := simd.shuffle(v, v, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + t := poly8x16x2_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(poly8x16_t)_vqtbx2q( + transmute(int8x16_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + idx, + ) + return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx3_p8) + @(require_results, enable_target_feature = "neon") + vqtbx3_p8 :: #force_inline proc "c" (v: poly8x8_t, t: poly8x16x3_t, idx: uint8x8_t) -> poly8x8_t { + when ODIN_ENDIAN == .Little { + return transmute(poly8x8_t)_vqtbx3( + transmute(int8x8_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + idx, + ) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + t := poly8x16x3_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(poly8x8_t)_vqtbx3( + transmute(int8x8_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + idx, + ) + return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx3q_p8) + @(require_results, enable_target_feature = "neon") + vqtbx3q_p8 :: #force_inline proc "c" (v: poly8x16_t, t: poly8x16x3_t, idx: uint8x16_t) -> poly8x16_t { + when ODIN_ENDIAN == .Little { + return transmute(poly8x16_t)_vqtbx3q( + transmute(int8x16_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + idx, + ) + } else { + v := simd.shuffle(v, v, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + t := poly8x16x3_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(poly8x16_t)_vqtbx3q( + transmute(int8x16_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + idx, + ) + return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx4_p8) + @(require_results, enable_target_feature = "neon") + vqtbx4_p8 :: #force_inline proc "c" (v: poly8x8_t, t: poly8x16x4_t, idx: uint8x8_t) -> poly8x8_t { + when ODIN_ENDIAN == .Little { + return transmute(poly8x8_t)_vqtbx4( + transmute(int8x8_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + transmute(int8x16_t)t.w, + idx, + ) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + t := poly8x16x4_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.w, t.w, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(poly8x8_t)_vqtbx4( + transmute(int8x8_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + transmute(int8x16_t)t.w, + idx, + ) + return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx4q_p8) + @(require_results, enable_target_feature = "neon") + vqtbx4q_p8 :: #force_inline proc "c" (v: poly8x16_t, t: poly8x16x4_t, idx: uint8x16_t) -> poly8x16_t { + when ODIN_ENDIAN == .Little { + return transmute(poly8x16_t)_vqtbx4q( + transmute(int8x16_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + transmute(int8x16_t)t.w, + idx, + ) + } else { + v := simd.shuffle(v, v, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + t := poly8x16x4_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.w, t.w, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(poly8x16_t)_vqtbx4q( + transmute(int8x16_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + transmute(int8x16_t)t.w, idx, ) return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) diff --git a/core/simd/simd.odin b/core/simd/simd.odin index fcbceb81a..7d61f523a 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -2899,7 +2899,7 @@ abs_diff :: #force_inline proc "contextless" (a, b: $T/#simd[$LANES]$E) -> T whe } pairwise_add :: intrinsics.simd_pairwise_add -pairwise_sub :: intrinsics.simd_pairwise_add +pairwise_sub :: intrinsics.simd_pairwise_sub interleave :: intrinsics.simd_interleave deinterleave :: intrinsics.simd_deinterleave \ No newline at end of file diff --git a/core/slice/sort_private.odin b/core/slice/sort_private.odin index efa9c596b..d0a2147b5 100644 --- a/core/slice/sort_private.odin +++ b/core/slice/sort_private.odin @@ -25,15 +25,104 @@ _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (O #panic("unhandled Sort_Kind") } } - - // insertion sort - // TODO(bill): use a different algorithm as insertion sort is O(n^2) - n := len(data) - for i in 1.. 0 && less(data[j], data[j-1], call); j -= 1 { - swap(data, j, j-1) + + merge_rotate(data, call) + + insertion_sort :: proc(data: T, call: P) { + for i in 1.. 0 && less(temp, data[j - 1], call); j -= 1 { + data[j] = data[j - 1] + } + data[j] = temp } } + + merge_rotate :: proc(data: T, call: P) { + if len(data) <= 200 { + insertion_sort(data, call) + return + } + + mid := len(data) / 2 + merge_rotate(data[:mid], call) + merge_rotate(data[mid:], call) + + merge(data, mid, len(data) - mid, call) + } + + bin_search_left :: proc(data: T, value: E, call: P) -> (from: int) { + n := len(data) + + for n > 0 { + half := n / 2 + mid := from + half + + if less(data[mid], value, call) { + from = mid + 1 + n -= half + 1 + } else { + n = half + } + } + + return from + } + + bin_search_right :: proc(data: T, value: E, call: P) -> (from: int) { + n := len(data) + + for n > 0 { + half := n / 2 + mid := from + half + + if less(value, data[mid], call) { + n = half + } else { + from = mid + 1 + n -= half + 1 + } + } + + return from + } + + merge :: proc(data: T, left, right: int, call: P) { + if left == 0 || right == 0 { + return + } + + if left + right == 2 { + if less(data[1], data[0], call) { + data[1], data[0] = data[0], data[1] + } + return + } + + first_cut, second_cut: int + left2, right2: int + + if left > right { + left2 = left / 2 + first_cut = left2 + + second_cut = left + bin_search_left(data[left:], data[first_cut], call) + right2 = second_cut - left + } else { + right2 = right / 2 + second_cut = left + right2 + + first_cut = bin_search_right(data[:left], data[second_cut], call) + left2 = first_cut + } + + rotate_left(data[first_cut:second_cut], left - first_cut) + new_mid := first_cut + right2 + + merge(data[:new_mid], left2 , right2 , call) + merge(data[new_mid:], left - left2, right - right2, call) + } } @(private) diff --git a/core/sys/windows/user32.odin b/core/sys/windows/user32.odin index d2f54b127..f137dc22c 100644 --- a/core/sys/windows/user32.odin +++ b/core/sys/windows/user32.odin @@ -34,6 +34,7 @@ foreign user32 { UnregisterClassW :: proc(lpClassName: LPCWSTR, hInstance: HINSTANCE) -> BOOL --- RegisterHotKey :: proc(hnwd: HWND, id: c_int, fsModifiers: UINT, vk: UINT) -> BOOL --- + UnregisterHotKey :: proc(hnwd: HWND, id: c_int) -> BOOL --- CreateWindowExW :: proc( dwExStyle: DWORD, diff --git a/core/text/edit/text_edit.odin b/core/text/edit/text_edit.odin index 58e184309..eebe938f4 100644 --- a/core/text/edit/text_edit.odin +++ b/core/text/edit/text_edit.odin @@ -311,7 +311,7 @@ translate_position :: proc(s: ^State, t: Translation) -> int { for { _, g = utf8.decode_grapheme_iterate(&it) or_break } - pos -= max(g.width, 1) + pos -= max(len(g.text), 1) } else { pos -= 1 for pos >= 0 && is_continuation_byte(buf[pos]) { @@ -323,7 +323,7 @@ translate_position :: proc(s: ^State, t: Translation) -> int { it := utf8.decode_grapheme_iterator_make(string(buf[pos:])) _, g, _ := utf8.decode_grapheme_iterate(&it) - pos += max(g.width, 1) + pos += max(len(g.text), 1) } else { pos += 1 for pos < len(buf) && is_continuation_byte(buf[pos]) { diff --git a/core/unicode/utf8/grapheme.odin b/core/unicode/utf8/grapheme.odin index bcd234972..c4ef3767e 100644 --- a/core/unicode/utf8/grapheme.odin +++ b/core/unicode/utf8/grapheme.odin @@ -21,6 +21,7 @@ normalized_east_asian_width :: unicode.normalized_east_asian_width Grapheme :: struct { + text: string, // The text of the grapheme, a slice of the string it was decoded from. byte_index: int, rune_index: int, width: int, @@ -152,10 +153,11 @@ decode_grapheme_iterate :: proc(it: ^Grapheme_Iterator) -> (text: string, graphe it.width += normalized_east_asian_width(this_rune) if it.continue_grapheme { grapheme = it.current_grapheme - text = it.str[it.current_grapheme.byte_index:byte_index] + grapheme.text = it.str[it.current_grapheme.byte_index:byte_index] + text = grapheme.text ok = true } - it.current_grapheme = Grapheme{byte_index, it.rune_count, it.width - it.last_width} + it.current_grapheme = Grapheme{byte_index = byte_index, rune_index = it.rune_count, width = it.width - it.last_width} it.continue_grapheme = true @@ -392,7 +394,8 @@ decode_grapheme_iterate :: proc(it: ^Grapheme_Iterator) -> (text: string, graphe // a new grapheme is encountered. if !ok && it.continue_grapheme { grapheme = it.current_grapheme - text = it.str[it.current_grapheme.byte_index:] + grapheme.text = it.str[it.current_grapheme.byte_index:] + text = grapheme.text ok = true it.continue_grapheme = false } diff --git a/examples/all/all_vendor_js.odin b/examples/all/all_vendor_js.odin index d7975541d..128747c05 100644 --- a/examples/all/all_vendor_js.odin +++ b/examples/all/all_vendor_js.odin @@ -2,6 +2,7 @@ package all @(require) import "vendor:box2d" +@(require) import "vendor:box3d" @(require) import "vendor:cgltf" @(require) import "vendor:fontstash" @(require) import "vendor:microui" diff --git a/src/big_int.cpp b/src/big_int.cpp index 6db3301da..1d81a0483 100644 --- a/src/big_int.cpp +++ b/src/big_int.cpp @@ -489,7 +489,8 @@ gb_internal void big_int_and(BigInt *dst, BigInt const *x, BigInt const *y) { gb_internal void big_int_and_not(BigInt *dst, BigInt const *x, BigInt const *y) { if (mp_iszero(x)) { - big_int_init(dst, y); + // 0 &~ y == 0 & ~y == 0 + big_int_from_i64(dst, 0); return; } if (mp_iszero(y)) { @@ -505,13 +506,13 @@ gb_internal void big_int_and_not(BigInt *dst, BigInt const *x, BigInt const *y) mp_decr(&x1); mp_decr(&y1); - BigInt ny1 = {}; - mp_complement(&y1, &ny1); - mp_and(&x1, &ny1, dst); + BigInt nx1 = {}; + mp_complement(&x1, &nx1); + mp_and(&y1, &nx1, dst); big_int_dealloc(&x1); big_int_dealloc(&y1); - big_int_dealloc(&ny1); + big_int_dealloc(&nx1); return; } @@ -532,6 +533,7 @@ gb_internal void big_int_and_not(BigInt *dst, BigInt const *x, BigInt const *y) BigInt z1 = {}; big_int_or(&z1, &x1, &y1); mp_add_d(&z1, 1, dst); + big_int_neg(dst, dst); big_int_dealloc(&x1); big_int_dealloc(&y1); diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 4910dafb0..2770ac430 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -949,6 +949,16 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan // don't return } + if (id == BuiltinProc_simd_pairwise_add || id == BuiltinProc_simd_pairwise_sub) { + i64 lanes = get_array_type_count(x.type); + if (lanes % 2 != 0) { + gbString xs = type_to_string(x.type); + error(x.expr, "'%.*s' expected a #simd type with an even lane count, got '%s'", LIT(builtin_name), xs); + gb_string_free(xs); + return false; + } + } + operand->mode = Addressing_Value; operand->type = x.type; return true; @@ -1295,10 +1305,6 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan if (!check_index_value(c, x.type, false, ce->args[1], max_count, &value)) { return false; } - if (max_count < 0) { - error(ce->args[1], "'%.*s' expected a constant integer index, got '%lld'", LIT(builtin_name), cast(long long)value); - return false; - } operand->mode = Addressing_Value; operand->type = elem; @@ -1320,10 +1326,6 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan if (!check_index_value(c, x.type, false, ce->args[1], max_count, &value)) { return false; } - if (max_count < 0) { - error(ce->args[1], "'%.*s' expected a constant integer index, got '%lld'", LIT(builtin_name), cast(long long)value); - return false; - } Operand y = {}; check_expr_with_type_hint(c, &y, ce->args[2], elem); if (y.mode == Addressing_Invalid) return false; @@ -1441,6 +1443,13 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan } i64 num_elems = get_array_type_count(x.type); + // the range is taken from the lane count; it has to meet the same limit a written bit_set does + if (num_elems > 128) { + gbString xs = type_to_string(x.type); + error(x.expr, "'%.*s' would produce a bit_set of %lld bits, exceeding the maximum of 128, got '%s'", LIT(builtin_name), cast(long long)num_elems, xs); + gb_string_free(xs); + return false; + } Type *result_type = alloc_type_bit_set(); result_type->BitSet.elem = t_int; @@ -1523,6 +1532,12 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan return false; } + // the result is as wide as the index list, which may be twice the operand width + if (arg_count > SIMD_ELEMENT_COUNT_MAX) { + error(call, "'%.*s' constructs a #simd vector beyond the maximum element count of %d, got %lld", LIT(builtin_name), SIMD_ELEMENT_COUNT_MAX, cast(long long)arg_count); + return false; + } + operand->mode = Addressing_Value; operand->type = alloc_type_simd_vector(arg_count, elem); return true; @@ -1691,6 +1706,11 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan return false; } + // convert constant from BigInt to a type before `exact_value_to_i64` + convert_to_typed(c, &y, t_int); + if (y.mode == Addressing_Invalid) { + return false; + } n = exact_value_to_u64(y.value); } @@ -1773,12 +1793,13 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan } Operand offset = {}; check_expr(c, &offset, ce->args[1]); if (offset.mode == Addressing_Invalid) return false; - convert_to_typed(c, &offset, t_i64); + // `base:intrinsics` declares the offset as `int` and does not mark it #any_int + convert_to_typed(c, &offset, t_int); if (!is_type_integer(offset.type) || offset.mode != Addressing_Constant) { - error(offset.expr, "'%.*s' expected a constant integer offset"); + error(offset.expr, "'%.*s' expected a constant integer offset", LIT(builtin_name)); return false; } - check_assignment(c, &offset, t_i64, builtin_name); + check_assignment(c, &offset, t_int, builtin_name); operand->type = x.type; operand->mode = Addressing_Value; @@ -1903,9 +1924,15 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan i64 base_count = get_array_type_count(x.type); i64 count = base_count * cast(i64)ce->args.count; - i64 max_count = 64; + i64 max_count = SIMD_ELEMENT_COUNT_MAX; if (count > max_count) { - error(ce->proc, "'%.*s' exceeds the maximum #simd count %lld, got %lld", cast(long long)max_count, cast(long long)count); + error(ce->proc, "'%.*s' exceeds the maximum #simd count %lld, got %lld", LIT(builtin_name), cast(long long)max_count, cast(long long)count); + return false; + } + // the lane count is the operand width times the argument count, so it is a power + // of two only when the argument count is + if (!is_power_of_two(count)) { + error(ce->proc, "'%.*s' must produce a power of two #simd count, got %lld", LIT(builtin_name), cast(long long)count); return false; } @@ -1936,6 +1963,11 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan error(n.expr, "'%.*s' expected a constant integer divisible by the count of the #simd vector", LIT(builtin_name)); return false; } + // convert constant from BigInt to a type before `exact_value_to_i64`; here it also sets the return arity + convert_to_typed(c, &n, t_int); + if (n.mode == Addressing_Invalid) { + return false; + } i64 divisor = exact_value_to_i64(n.value); if (divisor < 1 || divisor > max_count || (max_count % divisor != 0)) { error(n.expr, "'%.*s' expected a constant integer divisible by the count of the #simd vector , got %lld, which must have been divisible by %lld", LIT(builtin_name), cast(long long)divisor, cast(long long)max_count); @@ -1968,10 +2000,15 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan for (unsigned i = 0; i < 4; i++) { if (!is_type_integer(x[i].type) || x[i].mode != Addressing_Constant) { gbString xs = type_to_string(x[i].type); - error(x[i].expr, "'%.*s' expected a constant integer", LIT(builtin_name), xs); + error(x[i].expr, "'%.*s' expected a constant integer, got '%s'", LIT(builtin_name), xs); gb_string_free(xs); return false; } + // convert constant from BigInt to a type before `exact_value_to_i64` + convert_to_typed(c, x+i, t_int); + if (x[i].mode == Addressing_Invalid) { + return false; + } i64 val = exact_value_to_i64(x[i].value); if (val < 0 || val > 3) { error(x[i].expr, "'%.*s' expected a constant integer in the range 0..<4, got %lld", LIT(builtin_name), cast(long long)val); @@ -2222,7 +2259,10 @@ gb_internal LoadDirectiveResult check_load_directory_directive(CheckerContext *c String name = bd->name.string; GB_ASSERT(name == "load_directory"); - if (ce->args.count != 1) { + if (ce->args.count == 0) { + error(ce->close, "'#%.*s' expects 1 argument, got 0", LIT(name)); + return LoadDirective_Error; + } else if (ce->args.count != 1) { error(ce->args[0], "'#%.*s' expects 1 argument, got %td", LIT(name), ce->args.count); return LoadDirective_Error; } @@ -2667,7 +2707,7 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o error(call, "'#panic' expects 1 argument, got %td", ce->args.count); return false; } - if (!is_type_string(operand->type) && operand->mode != Addressing_Constant) { + if (!is_type_string(operand->type) || operand->mode != Addressing_Constant) { gbString str = expr_to_string(ce->args[0]); error(call, "'%s' is not a constant string", str); gb_string_free(str); @@ -2872,8 +2912,8 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As if (!ok) { operand->type = t_invalid; operand->mode = Addressing_Value; + operand->value = {}; } - operand->value = {}; operand->expr = call; return ok; } @@ -3466,10 +3506,8 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As arg_count++; } - if (false && arg_count > max_count) { - error(call, "Too many 'swizzle' indices, %td > %td", arg_count, max_count); - return false; - } else if (arg_count < 2) { + // No upper bound on the index count + if (arg_count < 2) { error(call, "Not enough 'swizzle' indices, %td < 2", arg_count); return false; } @@ -3489,6 +3527,11 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } + if (is_type_simd_vector(type) && arg_count > SIMD_ELEMENT_COUNT_MAX) { + error(call, "'swizzle' constructs a #simd vector beyond the maximum element count of %d, got %lld", SIMD_ELEMENT_COUNT_MAX, cast(long long)arg_count); + return false; + } + operand->type = determine_swizzle_array_type(original_type, type_hint, arg_count); break; } @@ -3824,10 +3867,6 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As default: GB_PANIC("Invalid type"); break; } - if (type_hint != nullptr && check_is_castable_to(c, operand, type_hint)) { - operand->type = type_hint; - } - break; } @@ -3844,7 +3883,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As if (is_type_untyped(x->type)) { if (x->mode == Addressing_Constant) { if (is_type_numeric(x->type)) { - x->type = t_untyped_complex; + x->type = t_untyped_quaternion; } } else{ convert_to_typed(c, x, t_quaternion256); @@ -3880,10 +3919,6 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As default: GB_PANIC("Invalid type"); break; } - if (type_hint != nullptr && check_is_castable_to(c, operand, type_hint)) { - operand->type = type_hint; - } - break; } @@ -4928,7 +4963,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } - if (!is_type_array(x.type) && !is_type_array(y.type)) { + if (!is_type_array(x.type) || !is_type_array(y.type)) { gbString s1 = type_to_string(x.type); gbString s2 = type_to_string(y.type); error(call, "'%.*s' expects only arrays, got %s and %s", LIT(builtin_name), s1, s2); @@ -5058,7 +5093,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As case BuiltinProc_is_package_imported: { bool value = false; - if (!is_type_string(operand->type) && (operand->mode != Addressing_Constant)) { + if (!is_type_string(operand->type) || operand->mode != Addressing_Constant) { error(ce->args[0], "Expected a constant string for '%.*s'", LIT(builtin_name)); } else if (operand->value.kind == ExactValue_String) { String pkg_name = operand->value.value_string; @@ -5116,7 +5151,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As Operand o = {}; check_expr(c, &o, ce->args[0]); - if (!is_type_integer(o.type) && (o.mode != Addressing_Constant)) { + if (!is_type_integer(o.type) || o.mode != Addressing_Constant) { error(ce->args[0], "Expected a constant integer for '%.*s'", LIT(builtin_name)); return false; } @@ -5137,7 +5172,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As Operand o = {}; check_expr(c, &o, ce->args[0]); - if (!is_type_integer_or_float(o.type) && (o.mode != Addressing_Constant)) { + if (!is_type_integer_or_float(o.type) || o.mode != Addressing_Constant) { error(ce->args[0], "Expected a constant number for '%.*s'", LIT(builtin_name)); return false; } @@ -5770,6 +5805,14 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As error(x.expr, "Invalid type passed to '%.*s', got %s", LIT(builtin_name), xts); gb_string_free(xts); } + // An untyped constant is integer-like, so it reaches the size check below, and + // `type_size_of` asserts on a type that has no size. + // no-op on typed types + convert_to_typed(c, &x, default_type(x.type)); + if (x.mode == Addressing_Invalid) { + return false; + } + i64 sz = type_size_of(x.type); if (sz < 2) { gbString xts = type_to_string(x.type); @@ -5815,7 +5858,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As GB_ASSERT(ct->kind == Type_Basic); if (ct->Basic.flags & (BasicFlag_EndianLittle|BasicFlag_EndianBig)) { gbString xts = type_to_string(x.type); - error(x.expr, "Expected an integer which does not specify the explicit endianness for '%.*s', got %s", LIT(builtin_name), xts); + error(x.expr, "Expected an integer type of the same platform endianness for '%.*s', got %s", LIT(builtin_name), xts); gb_string_free(xts); return false; } @@ -5866,7 +5909,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As GB_ASSERT(ct->kind == Type_Basic); if (ct->Basic.flags & (BasicFlag_EndianLittle|BasicFlag_EndianBig)) { gbString xts = type_to_string(x.type); - error(x.expr, "Expected an integer which does not specify the explicit endianness for '%.*s', got %s", LIT(builtin_name), xts); + error(x.expr, "Expected an integer type of the same platform endianness for '%.*s', got %s", LIT(builtin_name), xts); gb_string_free(xts); return false; } @@ -5903,7 +5946,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As GB_ASSERT(elem->kind == Type_Basic); if (elem->Basic.flags & (BasicFlag_EndianLittle|BasicFlag_EndianBig)) { gbString xts = type_to_string(x.type); - error(x.expr, "Expected a float which does not specify the explicit endianness for '%.*s', got %s", LIT(builtin_name), xts); + error(x.expr, "Expected a float type of the same platform endianness for '%.*s', got %s", LIT(builtin_name), xts); gb_string_free(xts); return false; } @@ -5952,7 +5995,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As GB_ASSERT(elem->kind == Type_Basic); if (elem->Basic.flags & (BasicFlag_EndianLittle|BasicFlag_EndianBig)) { gbString xts = type_to_string(x.type); - error(x.expr, "Expected a float which does not specify the explicit endianness for '%.*s', got %s", LIT(builtin_name), xts); + error(x.expr, "Expected a float type of the same platform endianness for '%.*s', got %s", LIT(builtin_name), xts); gb_string_free(xts); return false; } @@ -6016,6 +6059,12 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } + // convert constant from BigInt to a type before `exact_value_to_i64` + convert_to_typed(c, &len, t_int); + if (len.mode == Addressing_Invalid) { + return false; + } + if (len.mode == Addressing_Constant) { i64 n = exact_value_to_i64(len.value); if (n < 0) { @@ -6058,6 +6107,12 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } + // convert constant from BigInt to a type before `exact_value_to_i64` + convert_to_typed(c, &len, t_int); + if (len.mode == Addressing_Invalid) { + return false; + } + if (len.mode == Addressing_Constant) { i64 n = exact_value_to_i64(len.value); if (n < 0) { @@ -6586,6 +6641,11 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As error(z.expr, "Expected a constant integer for the scale in '%.*s'", LIT(builtin_name)); return false; } + // convert constant from BigInt to a type before `exact_value_to_i64` + convert_to_typed(c, &z, t_int); + if (z.mode == Addressing_Invalid) { + return false; + } i64 n = exact_value_to_i64(z.value); if (n <= 0) { error(z.expr, "Scale parameter in '%.*s' must be positive, got %lld", LIT(builtin_name), n); @@ -6709,10 +6769,15 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As if (x.mode == Addressing_Invalid) { return false; } - if (y.mode != Addressing_Constant && is_type_integer(y.type)) { + if (y.mode != Addressing_Constant || !is_type_integer(y.type)) { error(y.expr, "Second argument to '%.*s' representing the locality must be an integer in the range 0..=3", LIT(builtin_name)); return false; } + // convert constant from BigInt to a type before `exact_value_to_i64` + convert_to_typed(c, &y, t_int); + if (y.mode == Addressing_Invalid) { + return false; + } i64 locality = exact_value_to_i64(y.value); if (!(0 <= locality && locality <= 3)) { error(y.expr, "Second argument to '%.*s' representing the locality must be an integer in the range 0..=3", LIT(builtin_name)); @@ -6943,6 +7008,13 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } + if (bt->Basic.kind == Basic_rune) { + gbString t = type_to_string(operand->type); + error(operand->expr, "Type %s does not have an unsigned integer mapping for '%.*s'", t, LIT(builtin_name)); + gb_string_free(t); + return false; + } + Type *u_type = &basic_types[bt->Basic.kind + 1]; operand->type = u_type; @@ -7313,6 +7385,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As i64 bit_offset = 0; i64 bit_size = 0; + bool found = false; for_array(i, type->BitField.fields) { Entity *f = type->BitField.fields[i]; if (f->kind != Entity_Variable || (f->flags & EntityFlag_Field) == 0) { @@ -7322,9 +7395,18 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As if (field_name == str) { bit_offset = type->BitField.bit_offsets[i]; bit_size = type->BitField.bit_sizes[i]; + found = true; break; } } + // A missing field would otherwise return 0, which is also the correct offset of the + // first declared field, so the caller has nothing to test for. + if (!found) { + gbString t = type_to_string(type); + error(ce->args[1], "'%s' is not a field of type %s", field_name.cstring(), t); + gb_string_free(t); + return false; + } i64 value = 0; switch (id) { @@ -7754,6 +7836,11 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } + // convert constant from BigInt to a type before `exact_value_to_i64` + convert_to_typed(c, &op, t_int); + if (op.mode == Addressing_Invalid) { + return false; + } i64 index = exact_value_to_i64(op.value); if (index < 0) { error(op.expr, "Expected a non-negative integer for the index of procedure parameter value, got %lld", cast(long long)index); @@ -7813,6 +7900,11 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } + // convert constant from BigInt to a type before `exact_value_to_i64` + convert_to_typed(c, &op, t_int); + if (op.mode == Addressing_Invalid) { + return false; + } i64 index = exact_value_to_i64(op.value); if (index < 0) { error(op.expr, "Expected a non-negative integer for the index of procedure parameter value, got %lld", cast(long long)index); @@ -7907,6 +7999,11 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } + // convert constant from BigInt to a type before `exact_value_to_i64` + convert_to_typed(c, &op, t_int); + if (op.mode == Addressing_Invalid) { + return false; + } i64 index = exact_value_to_i64(op.value); if (index < 0) { error(op.expr, "Expected a non-negative integer for the index of record parameter value, got %lld", cast(long long)index); diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 416f60b9b..dd6449c61 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -2311,7 +2311,15 @@ gb_internal bool check_representable_as_constant(CheckerContext *c, ExactValue i if (in_value.kind == ExactValue_String16) { return is_type_string16(type) || is_type_cstring16(type); } - return in_value.kind == ExactValue_String; + if (in_value.kind != ExactValue_String) { + return false; + } + // NOTE: a UTF-8 constant has to be re-expressed in UTF-16, otherwise its length and + // indices stay those of the UTF-8 encoding + if (is_type_string16(type) || is_type_cstring16(type)) { + if (out_value) *out_value = exact_value_string16(string_to_string16(permanent_allocator(), in_value.value_string)); + } + return true; } else if (is_type_integer(type) || is_type_rune(type)) { ExactValue v = exact_value_to_integer(in_value); if (v.kind != ExactValue_Integer) { @@ -3001,8 +3009,13 @@ gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast * if (ast_node_expect(index_expr, Ast_IndexExpr)) { ast_node(ie, IndexExpr, index_expr); Type *soa_type = type_deref(type_of_expr(ie->expr)); - GB_ASSERT(is_type_soa_struct(soa_type)); - o->type = alloc_type_soa_pointer(soa_type); + if (is_type_soa_struct(soa_type)) { + o->type = alloc_type_soa_pointer(soa_type); + } else { + // &soa[i][j] + GB_ASSERT_MSG(is_type_array(soa_type), "%s", type_to_string(soa_type)); + o->type = alloc_type_pointer(o->type); + } } else { o->type = alloc_type_pointer(o->type); } @@ -3335,27 +3348,29 @@ gb_internal void check_comparison(CheckerContext *c, Ast *node, Operand *x, Oper case Token_Lt: case Token_LtEq: { + // subset: (lhs & rhs) == lhs. a proper subset also requires lhs != rhs ExactValue lhs = x->value; ExactValue rhs = y->value; - ExactValue res = exact_binary_operator_value(Token_And, lhs, rhs); - res = exact_value_bool(compare_exact_values(op, res, lhs)); + ExactValue both = exact_binary_operator_value(Token_And, lhs, rhs); + bool res = compare_exact_values(Token_CmpEq, both, lhs); if (op == Token_Lt) { - res = exact_binary_operator_value(Token_And, res, exact_value_bool(compare_exact_values(op, lhs, rhs))); + res = res && compare_exact_values(Token_NotEq, lhs, rhs); } - x->value = res; + x->value = exact_value_bool(res); break; } case Token_Gt: case Token_GtEq: { + // superset: (lhs & rhs) == rhs ExactValue lhs = x->value; ExactValue rhs = y->value; - ExactValue res = exact_binary_operator_value(Token_And, lhs, rhs); - res = exact_value_bool(compare_exact_values(op, res, rhs)); + ExactValue both = exact_binary_operator_value(Token_And, lhs, rhs); + bool res = compare_exact_values(Token_CmpEq, both, rhs); if (op == Token_Gt) { - res = exact_binary_operator_value(Token_And, res, exact_value_bool(compare_exact_values(op, lhs, rhs))); + res = res && compare_exact_values(Token_NotEq, lhs, rhs); } - x->value = res; + x->value = exact_value_bool(res); break; } } @@ -3974,7 +3989,7 @@ gb_internal void check_cast(CheckerContext *c, Operand *x, Type *type, bool forb add_package_dependency(c, "runtime", "floattidf", REQUIRE); } else if (is_type_integer_128bit(dst) && is_type_float(src)) { add_package_dependency(c, "runtime", "fixunsdfti", REQUIRE); - add_package_dependency(c, "runtime", "fixunsdfdi", REQUIRE); + add_package_dependency(c, "runtime", "fixdfti", REQUIRE); } else if (src == t_f16 && is_type_float(dst)) { add_package_dependency(c, "runtime", "gnu_h2f_ieee", REQUIRE); add_package_dependency(c, "runtime", "extendhfsf2", REQUIRE); @@ -4010,14 +4025,15 @@ gb_internal void check_cast(CheckerContext *c, Operand *x, Type *type, bool forb Type *dst = core_type(type); if (is_type_string(src) && is_type_string(dst)) { - bool src_utf16 = is_type_string16(src) || is_type_cstring16(src); bool dst_utf16 = is_type_string16(dst) || is_type_cstring16(dst); - if (!src_utf16 && dst_utf16) { + // NOTE: keyed off the value's encoding rather than the source type; it may have been re-expressed + // when it was checked against the target type + if (dst_utf16 && x->value.kind == ExactValue_String) { x->value = exact_value_string16(string_to_string16(permanent_allocator(), x->value.value_string)); } - if (src_utf16 && !dst_utf16) { + if (!dst_utf16 && x->value.kind == ExactValue_String16) { x->value = exact_value_string(string16_to_string(permanent_allocator(), x->value.value_string16)); } } @@ -4206,8 +4222,9 @@ gb_internal Type *check_matrix_type_hint(Type *matrix, Type *type_hint) { } else if (xt->kind == Type_Matrix && th->kind == Type_Matrix) { if (!are_types_identical(xt->Matrix.elem, th->Matrix.elem)) { // ignore - } if (xt->Matrix.row_count == th->Matrix.row_count && - xt->Matrix.column_count == th->Matrix.column_count) { + } else if (xt->Matrix.row_count == th->Matrix.row_count && + xt->Matrix.column_count == th->Matrix.column_count && + xt->Matrix.is_row_major == th->Matrix.is_row_major) { return type_hint; } } else if (xt->kind == Type_Matrix && th->kind == Type_Array) { @@ -4259,8 +4276,22 @@ gb_internal void check_binary_matrix(CheckerContext *c, Token const &op, Operand x->type = y->type; } } else { + // the result takes its rows from one operand and its columns from the other, + // so it can be larger than either. Each dimension is at least + // MATRIX_ELEMENT_COUNT_MIN, so testing them first keeps the product in range. + i64 row_count = xt->Matrix.row_count; + i64 column_count = yt->Matrix.column_count; + if (row_count > MATRIX_ELEMENT_COUNT_MAX || + column_count > MATRIX_ELEMENT_COUNT_MAX || + row_count*column_count > MATRIX_ELEMENT_COUNT_MAX) { + error(x->expr, "Matrix multiplication result exceeds the maximum matrix element count, got %lld, expected a maximum of %d", cast(long long)(row_count*column_count), MATRIX_ELEMENT_COUNT_MAX); + x->mode = Addressing_Invalid; + x->type = t_invalid; + return; + } + bool is_row_major = xt->Matrix.is_row_major && yt->Matrix.is_row_major; - x->type = alloc_type_matrix(xt->Matrix.elem, xt->Matrix.row_count, yt->Matrix.column_count, nullptr, nullptr, is_row_major); + x->type = alloc_type_matrix(xt->Matrix.elem, row_count, column_count, nullptr, nullptr, is_row_major); } goto matrix_success; } else if (yt->kind == Type_Array) { @@ -6896,7 +6927,12 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A bool ok = false; if (e && (e->flags & EntityFlag_AnyInt)) { if (o->mode != Addressing_Type && is_type_integer(param_type) && (is_type_integer(o->type) || is_type_enum(o->type))) { - ok = check_is_castable_to(c, o, param_type); + if (o->mode == Addressing_Constant) { + // constants have to fit the parameter + ok = check_representable_as_constant(c, o->value, param_type, &o->value); + } else { + ok = check_is_castable_to(c, o, param_type); + } } } if (!allow_array_programming && check_is_assignable_to_with_score(c, o, param_type, nullptr, param_is_variadic, !allow_array_programming)) { @@ -9168,7 +9204,11 @@ gb_internal bool check_set_index_data(Operand *o, Type *t, bool indirection, i64 if (indirection) { o->mode = Addressing_Variable; } else if (o->mode != Addressing_Variable && + o->mode != Addressing_SoaVariable && o->mode != Addressing_Constant) { + // NOTE: an #soa element of array type keeps SoaVariable, so soa[i][j] stays an + // lvalue. Its components are one per lane rather than contiguous, but a single + // component still has a real address, the same one soa[i].y denotes. o->mode = Addressing_Value; } o->type = t->Array.elem; @@ -10665,7 +10705,25 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * if (count != nullptr) { if (count->kind == Ast_UnaryExpr && count->UnaryExpr.op.kind == Token_Question) { - type = alloc_type_array(check_type(c, type_expr->ArrayType.elem), -1); + Type *elem = check_type(c, type_expr->ArrayType.elem); + + bool is_simd_tag = false; + if (type_expr->ArrayType.tag != nullptr) { + GB_ASSERT(type_expr->ArrayType.tag->kind == Ast_BasicDirective); + is_simd_tag = type_expr->ArrayType.tag->BasicDirective.name.string == "simd"; + } + if (is_simd_tag) { + if (!is_type_valid_vector_elem(elem) && !is_type_polymorphic(elem)) { + gbString str = type_to_string(elem); + error(type_expr->ArrayType.elem, "Invalid element type for #simd, expected an integer, float, boolean, or 'rawptr' with no specific endianness, got '%s'", str); + gb_string_free(str); + type = alloc_type_array(elem, -1); + } else { + type = alloc_type_simd_vector(-1, elem); + } + } else { + type = alloc_type_array(elem, -1); + } is_to_be_determined_array_count = true; } } else { @@ -10889,7 +10947,9 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * } else if (t->kind == Type_SimdVector) { elem_type = t->SimdVector.elem; context_name = str_lit("simd vector literal"); - max_type_count = t->SimdVector.count; + if (!is_to_be_determined_array_count) { + max_type_count = t->SimdVector.count; + } } else if (t->kind == Type_Matrix) { elem_type = t->Matrix.elem; context_name = str_lit("matrix literal"); @@ -11065,6 +11125,16 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * error(node, "Expected %lld values for this array literal, got %lld", cast(long long)t->Array.count, cast(long long)max); } } + } else if (t->kind == Type_SimdVector) { + // the length laws cannot be applied until the literal has supplied the count + if (is_to_be_determined_array_count) { + t->SimdVector.count = max; + if (max < 1 || !is_power_of_two(max)) { + error(node, "Invalid length for #simd, expected a power of two length, got '%lld'", cast(long long)max); + } else if (max > SIMD_ELEMENT_COUNT_MAX) { + error(node, "#simd support a maximum element count of %d, got %lld", SIMD_ELEMENT_COUNT_MAX, cast(long long)max); + } + } } else if (t->kind == Type_Struct) { GB_ASSERT(t->Struct.soa_kind == StructSoa_Fixed); if (is_to_be_determined_array_count) { @@ -12116,6 +12186,16 @@ gb_internal ExprKind check_slice_expr(CheckerContext *c, Operand *o, Ast *node, case Type_Array: valid = true; max_count = t->Array.count; + if (is_type_soa_pointer(o->type)) { + // #soa element pointer; the pointed element is scattered like soa[i] itself, + // so it can't be sliced through the ptr (nor directly -> soa[i][:] is also rejected below) + gbString str = expr_to_string(node); + error(node, "Cannot slice '%s' through an #soa pointer, element is not contiguous in memory", str); + gb_string_free(str); + o->mode = Addressing_Invalid; + o->expr = node; + return kind; + } if (o->mode != Addressing_Variable && !is_type_pointer(o->type)) { gbString str = expr_to_string(node); error(node, "Cannot slice array '%s', value is not addressable", str); @@ -12228,12 +12308,14 @@ gb_internal ExprKind check_slice_expr(CheckerContext *c, Operand *o, Ast *node, indices[i] = index; } + bool invalid_indices = false; for (isize i = 0; i < gb_count_of(indices); i++) { i64 a = indices[i]; for (isize j = i+1; j < gb_count_of(indices); j++) { i64 b = indices[j]; if (a > b && b >= 0) { error(se->close, "Invalid slice indices: [%td > %td]", a, b); + invalid_indices = true; } } } @@ -12259,7 +12341,7 @@ gb_internal ExprKind check_slice_expr(CheckerContext *c, Operand *o, Ast *node, o->mode = Addressing_Value; - if (is_type_string(t) && max_count >= 0) { + if (is_type_string(t) && max_count >= 0 && !invalid_indices) { bool all_constant = true; for (isize i = 0; i < gb_count_of(nodes); i++) { if (nodes[i] != nullptr) { diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index f9ab8198f..c5657f008 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -1883,7 +1883,12 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) break; case Type_Array: - is_possibly_addressable = operand.mode == Addressing_Variable || is_ptr; + // for #soa container with array element type, the element carries Addressing_SoaVariable, + // rather than Addressing_Variable; the element itself has no address, + // but each component does, so for &v in soa[i] is addressable + is_possibly_addressable = operand.mode == Addressing_Variable || + operand.mode == Addressing_SoaVariable || + is_ptr; array_add(&vals, t->Array.elem); array_add(&vals, t_int); break; diff --git a/src/check_type.cpp b/src/check_type.cpp index 9414b794e..68c332b3d 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -1009,6 +1009,7 @@ gb_internal void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *nam e->Constant.flags |= entity_flags; e->Constant.docs = docs; e->Constant.comment = comment; + e->Constant.init_expr = init; auto interned = entity_interned_name(e); @@ -1367,7 +1368,7 @@ gb_internal void check_bit_set_type(CheckerContext *c, Type *type, Type *named_t gb_free(a, s.text); return; } - if (!check_representable_as_constant(c, iv, t, nullptr)) { + if (!check_representable_as_constant(c, jv, t, nullptr)) { gbAllocator a = heap_allocator(); String s = big_int_to_string(a, &j); gbString ts = type_to_string(t); @@ -3130,9 +3131,20 @@ gb_internal void check_matrix_type(CheckerContext *ctx, Type **type, Ast *node) } } - if ((generic_row == nullptr && generic_column == nullptr) && row_count*column_count > MATRIX_ELEMENT_COUNT_MAX) { - i64 element_count = row_count*column_count; - error(node, "Matrix types are limited to a maximum of %d elements, got %lld", MATRIX_ELEMENT_COUNT_MAX, cast(long long)element_count); + if (generic_row == nullptr && generic_column == nullptr) { + // row_count*column_count can overflow and wrap back under the limit, so test the + // dimensions first; each is at least MATRIX_ELEMENT_COUNT_MIN. Either one exceeding + // the maximum means the product does too + if (row_count > MATRIX_ELEMENT_COUNT_MAX || column_count > MATRIX_ELEMENT_COUNT_MAX || + row_count*column_count > MATRIX_ELEMENT_COUNT_MAX) { + // the element count is only printable when the multiply cannot overflow, which is + // exactly the case the dimension test above catches + if (row_count != 0 && column_count > I64_MAX/row_count) { + error(node, "Matrix types are limited to a maximum of %d elements, got %lld by %lld", MATRIX_ELEMENT_COUNT_MAX, cast(long long)row_count, cast(long long)column_count); + } else { + error(node, "Matrix types are limited to a maximum of %d elements, got %lld by %lld (%lld elements)", MATRIX_ELEMENT_COUNT_MAX, cast(long long)row_count, cast(long long)column_count, cast(long long)(row_count*column_count)); + } + } } @@ -3476,6 +3488,22 @@ gb_internal void check_array_type_internal(CheckerContext *ctx, Ast *e, Type **t Type *bt = base_type(index); GB_ASSERT(bt->kind == Type_Enum); + // the length is `max - min + 1`, computed exactly and then narrowed to an i64. a + // wide enough enumeration wraps & nothing tests downstream; reject here + if (bt->Enum.fields.count > 0 && + bt->Enum.min_value != nullptr && bt->Enum.max_value != nullptr) { + ExactValue span = exact_value_sub(*bt->Enum.max_value, *bt->Enum.min_value); + ExactValue len = exact_value_add(span, exact_value_i64(1)); + if (len.kind == ExactValue_Integer && len.value_integer.used > 1) { + gbAllocator a = heap_allocator(); + String str = big_int_to_string(a, &len.value_integer); + error(e, "Enumerated array length too large, %.*s", LIT(str)); + gb_free(a, str.text); + *type = t_invalid; + return; + } + } + Type *t = alloc_type_enumerated_array(elem, index, bt->Enum.min_value, bt->Enum.max_value, bt->Enum.fields.count, Token_Invalid); bool is_sparse = false; @@ -3510,9 +3538,12 @@ gb_internal void check_array_type_internal(CheckerContext *ctx, Ast *e, Type **t return; } + // Track user input and recovery value seperate, since both could be '0' + bool count_recovered = false; if (count < 0) { error(at->count, "? can only be used in conjunction with compound literals"); count = 0; + count_recovered = true; } @@ -3534,7 +3565,12 @@ gb_internal void check_array_type_internal(CheckerContext *ctx, Ast *e, Type **t // Ignore } else if (count < 1 || !is_power_of_two(count)) { *type = alloc_type_array(elem, count, generic_type); - if (ctx->disallow_polymorphic_return_types && count == 0) { + if (count_recovered) { + return; + } + // a polymorphic value used as the count is still unresolved while the + // signature is checked and reads as 0; only a written count is constant + if (ctx->disallow_polymorphic_return_types && o.mode != Addressing_Constant) { return; } error(at->count, "Invalid length for #simd, expected a power of two length, got '%lld'", cast(long long)count); diff --git a/src/checker.cpp b/src/checker.cpp index 0d8c6a841..22f557275 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -2685,6 +2685,7 @@ gb_internal void add_min_dep_type_info(Checker *c, Type *t) { add_min_dep_type_info(c, alloc_type_pointer(bt->FixedCapacityDynamicArray.elem)); add_min_dep_type_info(c, alloc_type_array(bt->FixedCapacityDynamicArray.elem, bt->FixedCapacityDynamicArray.capacity)); add_min_dep_type_info(c, t_int); + break; case Type_Enum: add_min_dep_type_info(c, bt->Enum.base_type); diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 457591615..f1b2f1ab3 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -153,7 +153,6 @@ BuiltinProc__simd_begin, BuiltinProc_simd_sub, BuiltinProc_simd_mul, BuiltinProc_simd_div, - BuiltinProc_simd_rem, BuiltinProc_simd_shl, // Odin logic BuiltinProc_simd_shr, // Odin logic BuiltinProc_simd_shl_masked, // C logic @@ -564,7 +563,6 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_sub"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_mul"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_div"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("simd_rem"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_shl"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_shr"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_shl_masked"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/common.cpp b/src/common.cpp index 8338ec707..3b4557cde 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -65,6 +65,20 @@ template struct TypeIs64BitInteger { enum {value = false}; }; template <> struct TypeIs64BitInteger { enum {value = true}; }; template <> struct TypeIs64BitInteger { enum {value = true}; }; +#if defined(GB_SYSTEM_WINDOWS) +uint32_t old_console_codepage = 0; +void set_utf8_codepage() { + old_console_codepage = GetConsoleOutputCP(); +} + +void restore_old_codepage() { + // Nothing we can do if this fails, so we're not asserting or anything. + SetConsoleOutputCP(old_console_codepage); +} +#else +void set_utf8_codepage() {} +void restore_old_codepage() {} +#endif #include "unicode.cpp" diff --git a/src/docs_writer.cpp b/src/docs_writer.cpp index 67d350f24..5fbe5a58c 100644 --- a/src/docs_writer.cpp +++ b/src/docs_writer.cpp @@ -890,6 +890,9 @@ gb_internal OdinDocEntityIndex odin_doc_add_entity(OdinDocWriter *w, Entity *e) } break; case Entity_Constant: + if (init_expr == nullptr) { + init_expr = e->Constant.init_expr; + } field_group_index = e->Constant.field_group_index; break; case Entity_Procedure: diff --git a/src/entity.cpp b/src/entity.cpp index 535201013..bfe4c42c9 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -243,6 +243,7 @@ struct Entity { i32 field_group_index; CommentGroup *docs; CommentGroup *comment; + Ast *init_expr; // only used for enum values } Constant; struct { Ast *type_expr; // only used for some variables within procedure bodies diff --git a/src/error.cpp b/src/error.cpp index 53bc01654..ee2d79c27 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -432,8 +432,10 @@ gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end) { window_close_bytes = line_length_bytes; } - for (i32 i = error_start_index_graphemes; i > 0; i -= 1) { - if (graphemes[i].byte_index == window_open_bytes) { + // counts the graphemes before the error. It must not read the one at it: an error at the + // end of a line indexes one past the last grapheme + for (i32 i = error_start_index_graphemes-1; i >= 0; i -= 1) { + if (graphemes[i].byte_index < window_open_bytes) { break; } squiggle_padding += graphemes[i].width; diff --git a/src/exact_value.cpp b/src/exact_value.cpp index 2895f7e59..cb4c5a578 100644 --- a/src/exact_value.cpp +++ b/src/exact_value.cpp @@ -1024,6 +1024,10 @@ gb_internal bool compare_exact_values(TokenKind op, ExactValue x, ExactValue y) f64 b = x.value_complex->imag; f64 c = y.value_complex->real; f64 d = y.value_complex->imag; + if (isnan(a) || isnan(b) || isnan(c) || isnan(d)) { + return op == Token_NotEq; + } + switch (op) { case Token_CmpEq: return cmp_f64(a, c) == 0 && cmp_f64(b, d) == 0; case Token_NotEq: return cmp_f64(a, c) != 0 || cmp_f64(b, d) != 0; @@ -1031,6 +1035,29 @@ gb_internal bool compare_exact_values(TokenKind op, ExactValue x, ExactValue y) break; } + case ExactValue_Quaternion: { + Quaternion256 a = *x.value_quaternion; + Quaternion256 b = *y.value_quaternion; + if (isnan(a.real) || isnan(a.imag) || isnan(a.jmag) || isnan(a.kmag) || + isnan(b.real) || isnan(b.imag) || isnan(b.jmag) || isnan(b.kmag)) { + return op == Token_NotEq; + } + + switch (op) { + case Token_CmpEq: + return cmp_f64(a.real, b.real) == 0 && + cmp_f64(a.imag, b.imag) == 0 && + cmp_f64(a.jmag, b.jmag) == 0 && + cmp_f64(a.kmag, b.kmag) == 0; + case Token_NotEq: + return cmp_f64(a.real, b.real) != 0 || + cmp_f64(a.imag, b.imag) != 0 || + cmp_f64(a.jmag, b.jmag) != 0 || + cmp_f64(a.kmag, b.kmag) != 0; + } + break; + } + case ExactValue_String: { String a = x.value_string; String b = y.value_string; diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index 528ddca88..a760c47b8 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -16,6 +16,14 @@ struct lbArgType { i64 byval_alignment; bool is_byval; bool no_capture; + + // For RiscV (Optional for others): A `cast_type` is normally applied by reinterpreting the value's + // bits from offset zero. Only correct when the two layouts agree. When an ABI flattens an aggregate + // it drops padding, and the dense type it produces puts the surviving members at different offsets + // than they really have, eg `struct #min_field_align(16){i8, f32}` flattens to `{i8, float}`, moving + // the float from offset 16 to offset 4. These give the real byte offset of each `cast_type` element. + i64 *coerce_offsets; + isize coerce_offset_count; }; @@ -25,6 +33,14 @@ gb_internal i64 lb_alignof(LLVMTypeRef type); gb_internal lbArgType lb_arg_type_direct(LLVMTypeRef type, LLVMTypeRef cast_type, LLVMTypeRef pad_type, LLVMAttributeRef attr) { return lbArgType{lbArg_Direct, type, cast_type, pad_type, attr, nullptr, 0, false}; } +// Same as above, except coercion reads each element of `cast_type` from its real offset in `type` +// instead of reinterpreting the bits from offset zero. See `coerce_offsets`. +gb_internal lbArgType lb_arg_type_direct_fields(LLVMTypeRef type, LLVMTypeRef cast_type, i64 *offsets, isize count) { + lbArgType arg = lb_arg_type_direct(type, cast_type, nullptr, nullptr); + arg.coerce_offsets = offsets; + arg.coerce_offset_count = count; + return arg; +} gb_internal lbArgType lb_arg_type_direct(LLVMTypeRef type) { return lb_arg_type_direct(type, nullptr, nullptr, nullptr); } @@ -437,7 +453,9 @@ namespace lbAbi386 { if (sz == 0) { args[i] = lb_arg_type_ignore(t); } else { - args[i] = lb_arg_type_indirect(t, nullptr); + // Aggregates are pushed onto the stack by value, not passed as a pointer + // to a caller-owned copy. This is the rule for both i386 targets. + args[i] = lb_arg_type_indirect_byval(c, t); } } else { args[i] = non_struct(c, t, false); @@ -450,12 +468,21 @@ namespace lbAbi386 { if (!return_is_defined) { return lb_arg_type_direct(LLVMVoidTypeInContext(c)); } else if (lb_is_type_kind(return_type, LLVMStructTypeKind) || lb_is_type_kind(return_type, LLVMArrayTypeKind)) { + // Only some i386 targets return a small aggregate in EDX:EAX. The Intel386 System V + // psABI returns every structure and union through the hidden pointer, with no size + // threshold; Windows and the BSDs return one of eight bytes or fewer in registers. + bool small_in_registers = build_context.metrics.os == TargetOs_windows || + build_context.metrics.os == TargetOs_freebsd || + build_context.metrics.os == TargetOs_openbsd; + i64 sz = lb_sizeof(return_type); - switch (sz) { - case 1: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 8), nullptr, nullptr); - case 2: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 16), nullptr, nullptr); - case 4: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 32), nullptr, nullptr); - case 8: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 64), nullptr, nullptr); + if (small_in_registers) { + switch (sz) { + case 1: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 8), nullptr, nullptr); + case 2: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 16), nullptr, nullptr); + case 4: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 32), nullptr, nullptr); + case 8: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 64), nullptr, nullptr); + } } LB_ABI_MODIFY_RETURN_IF_TUPLE_MACRO(); @@ -599,11 +626,12 @@ namespace lbAbiAmd64SysV { }; gb_internal void classify_with(LLVMTypeRef t, Array *cls, i64 ix, i64 off); + gb_internal void unify(Array *cls, i64 i, RegClass const newv); gb_internal void fixup(LLVMTypeRef t, Array *cls); gb_internal lbArgType amd64_type(LLVMContextRef c, LLVMTypeRef type, Amd64TypeAttributeKind attribute_kind, ProcCallingConvention calling_convention, bool is_arg, - i32 *int_regs, i32 *sse_regs); - gb_internal Array classify(LLVMTypeRef t); + i32 *int_regs, i32 *sse_regs, Type *source_type); + gb_internal Array classify(LLVMTypeRef t, Type *source_type); gb_internal LLVMTypeRef llreg(LLVMContextRef c, Array const ®_classes, LLVMTypeRef type); gb_internal LB_ABI_COMPUTE_RETURN_TYPE(compute_return_type) { @@ -614,11 +642,11 @@ namespace lbAbiAmd64SysV { return amd64_type(c, return_type, Amd64TypeAttribute_StructRect, ft->calling_convention, false, - nullptr, nullptr); + nullptr, nullptr, nullptr); } gb_internal LB_ABI_INFO(abi_info) { - LLVMContextRef c = m->ctx; + LLVMContextRef c = m->ctx; lbFunctionType *ft = permanent_alloc_item(); ft->ctx = c; ft->calling_convention = calling_convention; @@ -626,14 +654,44 @@ namespace lbAbiAmd64SysV { i32 int_regs = 6; // rdi, rsi, rdx, rcx, r8, r9 i32 sse_regs = 8; // xmm0-xmm7 - ft->args = array_make(lb_function_type_args_allocator(), arg_count); - for (unsigned i = 0; i < arg_count; i++) { - ft->args[i] = amd64_type(c, arg_types[i], Amd64TypeAttribute_ByVal, calling_convention, - true, - &int_regs, &sse_regs); + // The source type of each parameter, where one exists. `arg_types` can carry entries + // with no counterpart. This walks the tuple the way lbAbiArm64 does and hands back nullptr once it runs out. + Entity **params = nullptr; + isize param_count = 0; + if (original_type != nullptr && original_type->kind == Type_Proc && original_type->Proc.params != nullptr) { + params = original_type->Proc.params->Tuple.variables.data; + param_count = original_type->Proc.params->Tuple.variables.count; } - ft->ret = compute_return_type(ft, c, return_type, return_is_defined, return_is_tuple); + ft->args = array_make(lb_function_type_args_allocator(), arg_count); + for (unsigned i = 0, j = 0; i < arg_count; i++, j++) { + while (cast(isize)j < param_count && params[j]->kind != Entity_Variable) { + j++; + } + Type *source_type = cast(isize)j < param_count ? params[j]->type : nullptr; + + ft->args[i] = amd64_type(c, arg_types[i], Amd64TypeAttribute_ByVal, calling_convention, + true, + &int_regs, &sse_regs, source_type); + } + + // A single result can be classified from its source type too. A tuple keeps the lowered + // path: it is split into out-pointers below, and C has no such return shape anyway. + Type *return_source = nullptr; + if (return_is_defined && !return_is_tuple && + original_type != nullptr && original_type->kind == Type_Proc && + original_type->Proc.results != nullptr && + original_type->Proc.results->Tuple.variables.count == 1) { + return_source = original_type->Proc.results->Tuple.variables[0]->type; + } + + if (return_source != nullptr) { + ft->ret = amd64_type(c, return_type, Amd64TypeAttribute_StructRect, calling_convention, + false, + nullptr, nullptr, return_source); + } else { + ft->ret = compute_return_type(ft, c, return_type, return_is_defined, return_is_tuple); + } return ft; } @@ -694,8 +752,8 @@ namespace lbAbiAmd64SysV { gb_internal lbArgType amd64_type(LLVMContextRef c, LLVMTypeRef type, Amd64TypeAttributeKind attribute_kind, ProcCallingConvention calling_convention, bool is_arg, - i32 *int_regs, i32 *sse_regs) { - auto cls = classify(type); + i32 *int_regs, i32 *sse_regs, Type *source_type) { + auto cls = classify(type, source_type); i32 needed_int = 0; i32 needed_sse = 0; for (auto c : cls) { @@ -782,15 +840,158 @@ namespace lbAbiAmd64SysV { return lb_arg_type_direct(type, nullptr, nullptr, attr); } - gb_internal Array classify(LLVMTypeRef t) { + // `classify_with` walks the LOWERED type, and lowering has already destroyed two + // distinctions the ABI rules need: Odin materializes padding as an explicit `[N x i8]` + // member, which is indistinguishable from a real `[N]u8` field, and a `#raw_union` becomes + // an opaque integer, which is indistinguishable from a real integer. §3.2.3 says padding + // contributes no class, and that a union merges the classes of all of its members. + // + // The source type still has both, so classify that instead where it is available. Only the + // kinds handled below are eligible; anything else falls back to the lowered walk, so an + // unrecognised type behaves exactly as it did before. + gb_internal bool source_is_classifiable(Type *t) { + Type *bt = base_type(t); + if (bt == nullptr) { + return false; + } + switch (bt->kind) { + case Type_Basic: + switch (bt->Basic.kind) { + case Basic_bool: case Basic_b8: case Basic_b16: case Basic_b32: case Basic_b64: + case Basic_i8: case Basic_u8: case Basic_i16: case Basic_u16: + case Basic_i32: case Basic_u32: case Basic_i64: case Basic_u64: + case Basic_i128: case Basic_u128: case Basic_int: case Basic_uint: + case Basic_uintptr: case Basic_rawptr: case Basic_rune: + case Basic_f16: case Basic_f32: case Basic_f64: + return true; + // Multi-word, but every word of them is a pointer or an integer, so the leaf rule + // below classifies them correctly without knowing their shape. + case Basic_string: case Basic_cstring: case Basic_any: case Basic_typeid: + return true; + } + return false; + case Type_Pointer: + case Type_MultiPointer: + case Type_Proc: + // Integer-backed, or aggregates of pointers and integers. None of them can contain a + // floating-point member, which is the only thing the leaf rule needs to tell apart. + case Type_Enum: + case Type_BitSet: + case Type_Slice: + case Type_DynamicArray: + return true; + case Type_Array: + return source_is_classifiable(bt->Array.elem); + // Odin matrices are laid out with no padding at all; see the note on + // matrix_type_stride_in_bytes + case Type_Matrix: + return source_is_classifiable(bt->Matrix.elem); + case Type_Struct: + if (bt->Struct.is_packed || bt->Struct.soa_kind != StructSoa_None) { + return false; + } + for (Entity *f : bt->Struct.fields) { + if (!source_is_classifiable(f->type)) { + return false; + } + } + return true; + } + return false; + } + + gb_internal void classify_source(Type *t, Array *cls, i64 ix, i64 off) { + Type *bt = base_type(t); + i64 t_size = type_size_of(bt); + i64 t_align = type_align_of(bt); + + if (t_align != 0 && (off % t_align) != 0) { + i64 e = (off + t_size + 7) / 8; + for (i64 i = off / 8; i < e; i++) { + unify(cls, ix+i, RegClass_Memory); + } + return; + } + + switch (bt->kind) { + case Type_Struct: + // A `#raw_union` has every member at offset zero, and §3.2.3 merges them all -- + // which is what makes `union{f32, u32}` INTEGER while `union{f32, f32}` is SSE. + if (bt->Struct.is_raw_union) { + for (Entity *f : bt->Struct.fields) { + classify_source(f->type, cls, ix, off); + } + } else { + for_array(i, bt->Struct.fields) { + Type *ft = nullptr; + i64 foff = type_offset_of(bt, i, &ft); + classify_source(ft, cls, ix, off + foff); + } + } + break; + case Type_Array: { + Type *elem = bt->Array.elem; + i64 stride = type_size_of(elem); + for (i64 i = 0; i < bt->Array.count; i++) { + classify_source(elem, cls, ix, off + i*stride); + } + break; + } + case Type_Matrix: { + Type *elem = bt->Matrix.elem; + i64 stride = type_size_of(elem); + i64 count = matrix_type_total_internal_elems(bt); + for (i64 i = 0; i < count; i++) { + classify_source(elem, cls, ix, off + i*stride); + } + break; + } + default: + if (is_type_float(bt)) { + switch (t_size) { + case 2: unify(cls, ix + off/8, (off%8 != 0) ? RegClass_SSEHv : RegClass_SSEHs); break; + case 4: unify(cls, ix + off/8, (off%8 == 4) ? RegClass_SSEFv : RegClass_SSEFs); break; + default: unify(cls, ix + off/8, RegClass_SSEDs); break; + } + } else { + i64 s = t_size; + while (s > 0) { + unify(cls, ix + off/8, RegClass_Int); + off += 8; + s -= 8; + } + } + break; + } + } + + gb_internal Array classify(LLVMTypeRef t, Type *source_type) { i64 sz = lb_sizeof(t); i64 words = (sz + 7)/8; auto reg_classes = array_make(heap_allocator(), cast(isize)words); if (words > 4) { all_mem(®_classes); } else { - classify_with(t, ®_classes, 0, 0); + bool from_source = source_type != nullptr && source_is_classifiable(source_type) && + type_size_of(base_type(source_type)) == sz; + if (from_source) { + classify_source(source_type, ®_classes, 0, 0); + } else { + classify_with(t, ®_classes, 0, 0); + } fixup(t, ®_classes); + if (from_source) { + // An eightbyte that ends up NO_CLASS is not passed at all. Only the source walk + // can produce one, the lowered walk classifies padding as INTEGER, and + // nothing downstream has a case for it. + // + // This has to come AFTER `fixup`, which counts eightbytes to apply "larger than + // two eightbytes is MEMORY". Dropping them first makes `#align(32){f32}` look + // like a single SSE eightbyte instead of the memory argument it is. + while (reg_classes.count > 0 && reg_classes[reg_classes.count-1] == RegClass_NoClass) { + array_pop(®_classes); + } + } } return reg_classes; } @@ -1125,6 +1326,10 @@ namespace lbAbiAmd64SysV { namespace lbAbiArm64 { gb_internal Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, Type* original_type); + gb_internal bool is_register(LLVMTypeRef type); + gb_internal bool is_homogenous_aggregate(LLVMContextRef c, LLVMTypeRef type, LLVMTypeRef *base_type_, unsigned *member_count_); + gb_internal bool is_homogenous_aggregate_source(LLVMContextRef c, Type *t, LLVMTypeRef *base_type_, unsigned *member_count_); + gb_internal unsigned is_homogenous_aggregate_small_enough(LLVMTypeRef base_type, unsigned member_count); gb_internal LB_ABI_COMPUTE_RETURN_TYPE(compute_return_type); gb_internal bool is_homogenous_aggregate(LLVMContextRef c, LLVMTypeRef type, LLVMTypeRef *base_type_, unsigned *member_count_); @@ -1133,7 +1338,30 @@ namespace lbAbiArm64 { lbFunctionType *ft = permanent_alloc_item(); ft->ctx = c; ft->args = compute_arg_types(c, arg_types, arg_count, original_type); - ft->ret = compute_return_type(ft, c, return_type, return_is_defined, return_is_tuple); + + // The same union case as in compute_arg_types, in return position. A tuple keeps the + // lowered path; C has no such return shape, and the split into out-pointers below is + // driven by the lowered type. + Type *return_source = nullptr; + if (return_is_defined && !return_is_tuple && + original_type != nullptr && original_type->kind == Type_Proc && + original_type->Proc.results != nullptr && + original_type->Proc.results->Tuple.variables.count == 1) { + return_source = original_type->Proc.results->Tuple.variables[0]->type; + } + + LLVMTypeRef ret_base_type = nullptr; + unsigned ret_member_count = 0; + if (return_source != nullptr && + !is_register(return_type) && + !is_homogenous_aggregate(c, return_type, nullptr, nullptr) && + is_homogenous_aggregate_source(c, return_source, &ret_base_type, &ret_member_count) && + is_homogenous_aggregate_small_enough(ret_base_type, ret_member_count)) { + ft->ret = lb_arg_type_direct(return_type, llvm_array_type(ret_base_type, ret_member_count), nullptr, nullptr); + } else { + ft->ret = compute_return_type(ft, c, return_type, return_is_defined, return_is_tuple); + } + ft->calling_convention = calling_convention; return ft; } @@ -1261,6 +1489,77 @@ namespace lbAbiArm64 { return false; } + // §5.9.5 defines a Homogeneous Floating-point Aggregate over Composite Types + // Odin lowers `#raw_union` to an opaque integer, so by the time the lowered type is + // inspected the members are gone and `union{f32, f32}` is indistinguishable from an `i32`. + // The source type still has them. + gb_internal bool is_homogenous_aggregate_source(LLVMContextRef c, Type *t, LLVMTypeRef *base_type_, unsigned *member_count_) { + if (t == nullptr) { + return false; + } + Type *bt = base_type(t); + if (bt == nullptr) { + return false; + } + switch (bt->kind) { + case Type_Basic: + switch (bt->Basic.kind) { + case Basic_f32: + if (base_type_) *base_type_ = LLVMFloatTypeInContext(c); + if (member_count_) *member_count_ = 1; + return true; + case Basic_f64: + if (base_type_) *base_type_ = LLVMDoubleTypeInContext(c); + if (member_count_) *member_count_ = 1; + return true; + } + return false; + case Type_Array: { + LLVMTypeRef elem_base = nullptr; + unsigned elem_count = 0; + if (!is_homogenous_aggregate_source(c, bt->Array.elem, &elem_base, &elem_count)) { + return false; + } + if (base_type_) *base_type_ = elem_base; + if (member_count_) *member_count_ = cast(unsigned)(elem_count * bt->Array.count); + return true; + } + case Type_Struct: { + if (bt->Struct.is_packed || bt->Struct.soa_kind != StructSoa_None) { + return false; + } + LLVMTypeRef found_base = nullptr; + unsigned total = 0; + for (Entity *f : bt->Struct.fields) { + LLVMTypeRef field_base = nullptr; + unsigned field_count = 0; + if (!is_homogenous_aggregate_source(c, f->type, &field_base, &field_count)) { + return false; + } + if (found_base == nullptr) { + found_base = field_base; + total = field_count; + } else if (found_base != field_base) { + return false; + } else { + total = bt->Struct.is_raw_union ? gb_max(total, field_count) : total + field_count; + } + } + if (found_base == nullptr) { + return false; + } + // Rejects anything with padding, matching is_homogenous_struct. + if (type_size_of(bt) != lb_sizeof(found_base) * cast(i64)total) { + return false; + } + if (base_type_) *base_type_ = found_base; + if (member_count_) *member_count_ = total; + return true; + } + } + return false; + } + gb_internal unsigned is_homogenous_aggregate_small_enough(LLVMTypeRef base_type, unsigned member_count) { return (member_count <= 4); } @@ -1325,6 +1624,12 @@ namespace lbAbiArm64 { LLVMTypeRef homo_base_type = {}; unsigned homo_member_count = 0; + // A `#raw_union` lowers to a struct wrapping an opaque integer, so it is not a + // homogeneous aggregate by the lowered type and falls through to the generic size + // path below. §5.9.5 counts a union as a Composite Type, so ask the source type. + LLVMTypeRef src_base_type = nullptr; + unsigned src_member_count = 0; + if (is_register(type)) { args[i] = non_struct(c, type, ptype); } else if (is_homogenous_aggregate(c, type, &homo_base_type, &homo_member_count)) { @@ -1333,6 +1638,9 @@ namespace lbAbiArm64 { } else { args[i] = lb_arg_type_indirect(type, nullptr);; } + } else if (is_homogenous_aggregate_source(c, ptype, &src_base_type, &src_member_count) && + is_homogenous_aggregate_small_enough(src_base_type, src_member_count)) { + args[i] = lb_arg_type_direct(type, llvm_array_type(src_base_type, src_member_count), nullptr, nullptr); } else { i64 size = lb_sizeof(type); if (size <= 16) { @@ -1738,6 +2046,85 @@ namespace lbAbiRiscv64 { } } + // `flatten` records which members survive; this records where they are. The two are walked + // together so the dense type it builds can still be read from the real object: an over-aligned + // member leaves a gap that the flatten removes, and reinterpreting the bits from offset zero + // then reads the member from where the padding used to be. + gb_internal void flatten_offsets(lbModule *m, Array *offsets, LLVMTypeRef type, i64 base) { + switch (LLVMGetTypeKind(type)) { + case LLVMStructTypeKind: { + if (LLVMIsPackedStruct(type)) { + array_add(offsets, base); + break; + } + unsigned elem_count = LLVMCountStructElementTypes(type); + // element offsets the way `lb_alignof` models LLVM's own layout + auto elem_offsets = array_make(temporary_allocator(), 0, elem_count); + i64 off = 0; + for (unsigned i = 0; i < elem_count; i += 1) { + LLVMTypeRef et = LLVMStructGetTypeAtIndex(type, i); + i64 a = lb_alignof(et); + if (a > 0) { + off = align_formula(off, a); + } + array_add(&elem_offsets, off); + off += lb_sizeof(et); + } + + auto field_remapping = map_get(&m->struct_field_remapping, cast(void *)type); + if (field_remapping) { + auto remap = *field_remapping; + for_array(i, remap) { + flatten_offsets(m, offsets, LLVMStructGetTypeAtIndex(type, remap[i]), base + elem_offsets[remap[i]]); + } + break; + } + for (unsigned i = 0; i < elem_count; i += 1) { + flatten_offsets(m, offsets, LLVMStructGetTypeAtIndex(type, i), base + elem_offsets[i]); + } + break; + } + case LLVMArrayTypeKind: { + unsigned len = LLVMGetArrayLength(type); + LLVMTypeRef elem = OdinLLVMGetArrayElementType(type); + i64 stride = lb_sizeof(elem); + for (unsigned i = 0; i < len; i += 1) { + flatten_offsets(m, offsets, elem, base + cast(i64)i*stride); + } + break; + } + default: + array_add(offsets, base); + } + } + + // The offsets the dense `cast_type` implies, so the two can be compared. When they agree the + // ordinary bit-reinterpreting coercion is right and nothing needs to change. + gb_internal bool flatten_moved_a_member(Array const &fields, Array const &offsets) { + if (fields.count != offsets.count) { + return false; + } + i64 off = 0; + for_array(i, fields) { + i64 a = lb_alignof(fields[i]); + if (a > 0) { + off = align_formula(off, a); + } + if (off != offsets[i]) { + return true; + } + off += lb_sizeof(fields[i]); + } + return false; + } + + // The psABI's rule is "one floating-point real and one integer (or bitfield)", and a pointer + // is not an integer. `is_register` admits pointers and keeps that meaning for its other + // callers, so the floating-point arms need their own predicate. + gb_internal bool is_int_member(LLVMTypeRef type) { + return LLVMGetTypeKind(type) == LLVMIntegerTypeKind && lb_sizeof(type) > 0; + } + gb_internal lbArgType compute_arg_type(lbModule *m, LLVMTypeRef type, int *gprs_left, int *fprs_left, Type *odin_type) { LLVMContextRef c = m->ctx; @@ -1764,49 +2151,84 @@ namespace lbAbiRiscv64 { // Flatten down the type so it is easier to check all the ABI conditions. // Note that we also need to remove all implicit padding fields Odin adds so we keep ABI // compatibility for struct declarations. - if (kind == LLVMStructTypeKind && size <= gb_max(2*xlen, 2*flen)) { + // The flattened form is for the floating-point rules, which are about the MEMBERS; the + // integer fallback below is about the OBJECT, so `size` stays the size of the original. + // The rules are stated over the members alone, so the aggregate's size does not gate + // them: over-alignment grows a struct without changing any member type. + LLVMTypeRef fp_type = type; + LLVMTypeKind fp_kind = kind; + i64 fp_size = size; + i64 *fp_offsets = nullptr; + isize fp_offset_count = 0; + if (kind == LLVMStructTypeKind) { Array fields = array_make(temporary_allocator(), 0, LLVMCountStructElementTypes(type)); flatten(m, &fields, type, false); - if (fields.count == 1) { - type = fields[0]; - } else { - type = LLVMStructTypeInContext(c, fields.data, cast(unsigned)fields.count, false); + auto offsets = array_make(temporary_allocator(), 0, fields.count); + flatten_offsets(m, &offsets, type, 0); + if (flatten_moved_a_member(fields, offsets)) { + fp_offsets = gb_alloc_array(permanent_allocator(), i64, offsets.count); + for_array(i, offsets) { + fp_offsets[i] = offsets[i]; + } + fp_offset_count = offsets.count; } - kind = LLVMGetTypeKind(type); - size = lb_sizeof(type); - GB_ASSERT_MSG(size == lb_sizeof(orig_type), "flattened: %s of size %d, original: %s of size %d", LLVMPrintTypeToString(type), size, LLVMPrintTypeToString(orig_type), lb_sizeof(orig_type)); + if (fields.count == 1) { + fp_type = fields[0]; + } else { + fp_type = LLVMStructTypeInContext(c, fields.data, cast(unsigned)fields.count, false); + } + + fp_kind = LLVMGetTypeKind(fp_type); + fp_size = lb_sizeof(fp_type); } - if (is_float(type) && size <= flen && *fprs_left >= 1) { + if (is_float(fp_type) && fp_size <= flen && *fprs_left >= 1) { *fprs_left -= 1; + if (fp_type != orig_type) { + // A struct that flattened to a single float has to be coerced to that float; + // handing back the original sends an over-aligned one to integer registers. + if (fp_offset_count > 0) { + return lb_arg_type_direct_fields(orig_type, fp_type, fp_offsets, fp_offset_count); + } + return lb_arg_type_direct(orig_type, fp_type, nullptr, nullptr); + } return non_struct(c, orig_type); } - if (kind == LLVMStructTypeKind && size <= 2*flen) { - unsigned elem_count = LLVMCountStructElementTypes(type); + if (fp_kind == LLVMStructTypeKind && fp_size <= 2*flen) { + unsigned elem_count = LLVMCountStructElementTypes(fp_type); if (elem_count == 2) { - LLVMTypeRef ty1 = LLVMStructGetTypeAtIndex(type, 0); + LLVMTypeRef ty1 = LLVMStructGetTypeAtIndex(fp_type, 0); i64 ty1s = lb_sizeof(ty1); - LLVMTypeRef ty2 = LLVMStructGetTypeAtIndex(type, 1); + LLVMTypeRef ty2 = LLVMStructGetTypeAtIndex(fp_type, 1); i64 ty2s = lb_sizeof(ty2); if (is_float(ty1) && is_float(ty2) && ty1s <= flen && ty2s <= flen && *fprs_left >= 2) { *fprs_left -= 2; - return lb_arg_type_direct(orig_type, type, nullptr, nullptr); + if (fp_offset_count > 0) { + return lb_arg_type_direct_fields(orig_type, fp_type, fp_offsets, fp_offset_count); + } + return lb_arg_type_direct(orig_type, fp_type, nullptr, nullptr); } - if (is_float(ty1) && is_register(ty2) && ty1s <= flen && ty2s <= xlen && *fprs_left >= 1 && *gprs_left >= 1) { + if (is_float(ty1) && is_int_member(ty2) && ty1s <= flen && ty2s <= xlen && *fprs_left >= 1 && *gprs_left >= 1) { *fprs_left -= 1; *gprs_left -= 1; - return lb_arg_type_direct(orig_type, type, nullptr, nullptr); + if (fp_offset_count > 0) { + return lb_arg_type_direct_fields(orig_type, fp_type, fp_offsets, fp_offset_count); + } + return lb_arg_type_direct(orig_type, fp_type, nullptr, nullptr); } - if (is_register(ty1) && is_float(ty2) && ty1s <= xlen && ty2s <= flen && *gprs_left >= 1 && *fprs_left >= 1) { + if (is_int_member(ty1) && is_float(ty2) && ty1s <= xlen && ty2s <= flen && *gprs_left >= 1 && *fprs_left >= 1) { *fprs_left -= 1; *gprs_left -= 1; - return lb_arg_type_direct(orig_type, type, nullptr, nullptr); + if (fp_offset_count > 0) { + return lb_arg_type_direct_fields(orig_type, fp_type, fp_offsets, fp_offset_count); + } + return lb_arg_type_direct(orig_type, fp_type, nullptr, nullptr); } } } diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 0021d5b76..7a633c352 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -2695,10 +2695,9 @@ gb_internal void lb_llvm_module_passes_and_verification(lbGenerator *gen, bool d } gb_internal String lb_filepath_ll_for_module(lbModule *m) { - String path = concatenate3_strings(permanent_allocator(), + String path = concatenate_strings(permanent_allocator(), build_context.build_paths[BuildPath_Output].basename, - STR_LIT("/"), - build_context.build_paths[BuildPath_Output].name + STR_LIT("/") ); GB_ASSERT(m->module_name != nullptr); @@ -3276,7 +3275,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { LLVMValueRef g = LLVMAddGlobal(m->mod, internal_llvm_type, LB_TYPE_INFO_DATA_NAME); LLVMSetInitializer(g, LLVMConstNull(internal_llvm_type)); LLVMSetLinkage(g, USE_SEPARATE_MODULES ? LLVMExternalLinkage : LLVMInternalLinkage); - // LLVMSetUnnamedAddress(g, LLVMGlobalUnnamedAddr); + LLVMSetUnnamedAddress(g, LLVMGlobalUnnamedAddr); LLVMSetGlobalConstant(g, true); lbValue value = {}; diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 419609645..3bacfaade 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -2538,15 +2538,17 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { TEMPORARY_ALLOCATOR_GUARD(); auto args = array_make(temporary_allocator(), 1); - args[0] = value; - char const *call = "fixunsdfdi"; + args[0] = lb_emit_conv(p, value, t_f64); + char const *call = "fixdfti"; if (is_type_unsigned(dst)) { call = "fixunsdfti"; } lbValue res_i128 = lb_emit_runtime_call(p, call, args); return lb_emit_conv(p, res_i128, t); } - i64 sz = type_size_of(src); + // the intermediate int must be at least as wide as the dest, + // otherwise e.g. f32 -> u64 truncates through a 32-bit fptoui + i64 sz = gb_max(type_size_of(src), type_size_of(dst)); lbValue res = {}; res.type = t; @@ -5035,6 +5037,32 @@ gb_internal void lb_build_addr_compound_lit_assign_array(lbProcedure *p, Arraykind == Type_Array, "%s", type_to_string(elem_type)); + + // this is a no-op when the index is in range by construction, like in a for-in loop + lb_emit_soa_index_bounds_check(p, soa_addr.addr, soa_addr.soa.index, soa_addr.soa.index_expr); + + i64 const component_count = elem_type->Array.count; + + auto const index_tv = type_and_value_of_expr(ie->index); + if (index_tv.mode == Addressing_Constant && index_tv.value.kind == ExactValue_Integer) { + i64 component = big_int_to_i64(&index_tv.value.value_integer); + GB_ASSERT_MSG(0 <= component && component < component_count, "%s", expr_to_string(expr)); + return lb_addr_soa_field_elem(lb_soa_field_elem_ptr(p, soa_addr.addr, cast(i32)component, soa_addr.soa.index)); + } + + // Note: a temp holding the whole element would be simpler but would not be an lvalue, + // and writes go through here + lbValue index = lb_emit_conv(p, lb_build_expr(p, ie->index), t_int); + lb_emit_bounds_check(p, ast_token(ie->index), index, lb_const_int(p->module, t_int, component_count)); + return lb_addr_soa_field_elem(lb_soa_array_component_elem_ptr(p, soa_addr.addr, index, soa_addr.soa.index, component_count)); +} + gb_internal lbAddr lb_build_addr_index_expr(lbProcedure *p, Ast *expr) { ast_node(ie, IndexExpr, expr); @@ -5053,10 +5081,10 @@ gb_internal lbAddr lb_build_addr_index_expr(lbProcedure *p, Ast *expr) { return lb_addr_soa_variable(val, index, ie->index); } - if (ie->expr->tav.mode == Addressing_SoaVariable) { - // SOA Structures for slices/dynamic arrays - GB_ASSERT_MSG(is_type_multi_pointer(type_of_expr(ie->expr)), "%s", type_to_string(type_of_expr(ie->expr))); - + if (ie->expr->tav.mode == Addressing_SoaVariable && is_type_multi_pointer(type_of_expr(ie->expr))) { + // soa.x[i], indexing one field's multipointer; + // the soa element of array type, soa[i][j] carries Addressing_SoaVariable too but has + // no multipointer to index; it is handled in the Type_Array case below lbValue field = lb_build_expr(p, ie->expr); lbValue index = lb_build_expr(p, ie->index); @@ -5112,8 +5140,22 @@ gb_internal lbAddr lb_build_addr_index_expr(lbProcedure *p, Ast *expr) { switch (t->kind) { case Type_Array: { - lbValue array = {}; - array = lb_build_addr_ptr(p, ie->expr); + lbAddr array_addr = lb_build_addr(p, ie->expr); + // soa[i][j], or v[j] in a for-in loop, with v being the looping var over soa container + // + // keyed on the lowered lbAddr kind rather than the addressing mode, + // because, unlike soa[i], v is seen by the checker as Addressing_Variable + if (array_addr.kind == lbAddr_SoaVariable) { + return lb_build_addr_soa_elem_index(p, expr, array_addr, t); + } + // p[j], where p is an #soa element pointer (e.g. p := &soa[i]) + if (is_type_soa_pointer(type_of_expr(ie->expr))) { + // build a soa variable from the soa ptr to get the address of the component + lbValue soa_ptr = lb_addr_load(p, array_addr); + lbAddr soa_addr = lb_addr_soa_variable_from_soa_ptr(p, soa_ptr); + return lb_build_addr_soa_elem_index(p, expr, soa_addr, t); + } + lbValue array = lb_addr_get_ptr(p, array_addr); if (deref) { array = lb_emit_load(p, array); } @@ -6497,10 +6539,7 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) { // base p doesn't lower to an lbAddr_SoaVariable on its own // (it is a local holding the soa pointer), so build the element // addr here the same way an explicit p^ does - lbValue value = lb_build_expr(p, se->expr); - lbValue ptr = lb_emit_struct_ev(p, value, 0); - lbValue idx = lb_emit_struct_ev(p, value, 1); - addr = lb_addr_soa_variable(ptr, idx, nullptr); + addr = lb_addr_soa_variable_from_soa_ptr(p, lb_build_expr(p, se->expr)); } else { addr = lb_build_addr(p, se->expr); } @@ -6722,10 +6761,7 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) { case_ast_node(de, DerefExpr, expr); Type *t = type_of_expr(de->expr); if (is_type_soa_pointer(t)) { - lbValue value = lb_build_expr(p, de->expr); - lbValue ptr = lb_emit_struct_ev(p, value, 0); - lbValue idx = lb_emit_struct_ev(p, value, 1); - return lb_addr_soa_variable(ptr, idx, nullptr); + return lb_addr_soa_variable_from_soa_ptr(p, lb_build_expr(p, de->expr)); } lbValue addr = lb_build_expr(p, de->expr); return lb_addr(addr); diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 72b11bac5..303a713f6 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -364,7 +364,7 @@ gb_internal void lb_loop_end(lbProcedure *p, lbLoopData const &data) { gb_internal void lb_make_global_private_const(LLVMValueRef global_data) { LLVMSetLinkage(global_data, LLVMLinkerPrivateLinkage); - // LLVMSetUnnamedAddress(global_data, LLVMGlobalUnnamedAddr); + LLVMSetUnnamedAddress(global_data, LLVMGlobalUnnamedAddr); LLVMSetGlobalConstant(global_data, true); } gb_internal void lb_make_global_private_const(lbAddr const &addr) { @@ -616,7 +616,23 @@ gb_internal lbAddr lb_addr_soa_variable(lbValue addr, lbValue index, Ast *index_ return v; } +// lbAddr_SoaVariable for the element pointed by an #soa pointer, +// unpacked from the ptr's {^container, index} pair +// +// the nullptr index_expr is deliberate, there is no source index expression, +// the index was already bounds checked when the pointer was formed (e.g. p := &soa[i]) +gb_internal lbAddr lb_addr_soa_variable_from_soa_ptr(lbProcedure *p, lbValue soa_ptr) { + GB_ASSERT_MSG(is_type_soa_pointer(soa_ptr.type), "%s", type_to_string(soa_ptr.type)); + return lb_addr_soa_variable(lb_emit_struct_ev(p, soa_ptr, 0), lb_emit_struct_ev(p, soa_ptr, 1), nullptr); +} + // pointer to the index element of the field_index component +// +// the returned pointer type depends on the soa kind (because the field types do): +// ^T for StructSoa_Fixed (field is [N]T array), but [^]T for the slice and dynamic +// kinds (field is the [^]T this offsets); +// loads and stores work with either, but an lbAddr must not hold the multipointer, +// so use lb_addr_soa_field_elem to build an lbAddr from this gb_internal lbValue lb_soa_field_elem_ptr(lbProcedure *p, lbValue soa_ptr, i32 field_index, lbValue index) { Type *t = base_type(type_deref(soa_ptr.type)); GB_ASSERT_MSG(t->kind == Type_Struct && t->Struct.soa_kind != StructSoa_None, "%s", type_to_string(t)); @@ -628,6 +644,49 @@ gb_internal lbValue lb_soa_field_elem_ptr(lbProcedure *p, lbValue soa_ptr, i32 f return lb_emit_ptr_offset(p, lb_emit_load(p, field), index); } +// the same address as lb_soa_field_elem_ptr, for a component index only known at runtime, +// this is array-element #soa only, unlike lb_soa_field_elem_ptr which serves any soa kind +// +// Note: the caller bounds checks component_index if needed +gb_internal lbValue lb_soa_array_component_elem_ptr(lbProcedure *p, lbValue soa_ptr, lbValue component_index, lbValue elem_index, i64 component_count) { + Type *t = base_type(type_deref(soa_ptr.type)); + GB_ASSERT_MSG(t->kind == Type_Struct && t->Struct.soa_kind != StructSoa_None, "%s", type_to_string(t)); + GB_ASSERT_MSG(base_type(t->Struct.soa_elem)->kind == Type_Array, + "indexing a component at runtime needs uniformly typed fields, got element %s", + type_to_string(t->Struct.soa_elem)); + if (component_count == 0) { + // a [0]T element has no components, nor does its soa struct have a field to get a ptr to; + // emit a typed nil so that code is well formed; + // callers either check bounds (driect indexing) or skip the code (e.g. range loop over array element), + // so this is never dereferenced when bounds checks are on; + // with bounds checks off the access is out of bounds by definition + return lb_const_nil(p->module, alloc_type_pointer(base_type(t->Struct.soa_elem)->Array.elem)); + } + // do chain select between the component pointers; + // an element array holds at most 4 components, so this is at most 3 compares and 3 selects (branchless), + // and it is folded if j is resolved to constant after inlining/unrolling; + // TODO: more efficient codegen can be done, most definitely for fixed kind, possibly for slice/dynamic, + // (but this works for both kinds) + // + // lb_emit_select evaluates both arms, so every candidate address is + // formed; only the selected one is dereferenced + lbValue ptr = lb_soa_field_elem_ptr(p, soa_ptr, 0, elem_index); + for (i64 component = 1; component < component_count; component++) { + lbValue candidate = lb_soa_field_elem_ptr(p, soa_ptr, cast(i32)component, elem_index); + lbValue is_component = lb_emit_comp(p, Token_CmpEq, component_index, lb_const_int(p->module, t_int, component)); + ptr = lb_emit_select(p, is_component, candidate, ptr); + } + return ptr; +} + +// lbAddr over an lb_soa_field_elem_ptr pointer, retyped ^T when it came as [^]T +gb_internal lbAddr lb_addr_soa_field_elem(lbValue ptr) { + if (is_type_multi_pointer(ptr.type)) { + ptr.type = alloc_type_multi_pointer_to_pointer(ptr.type); + } + return lb_addr(ptr); +} + // bounds check for an #soa element index gb_internal void lb_emit_soa_index_bounds_check(lbProcedure *p, lbValue soa_ptr, lbValue index, Ast *index_expr) { if (index_expr == nullptr) { @@ -1488,10 +1547,7 @@ gb_internal lbValue lb_emit_load(lbProcedure *p, lbValue value) { LLVMValueRef v = OdinLLVMBuildLoad(p, lb_type(p->module, t), value.value); return lbValue{v, t}; } else if (is_type_soa_pointer(value.type)) { - lbValue ptr = lb_emit_struct_ev(p, value, 0); - lbValue idx = lb_emit_struct_ev(p, value, 1); - lbAddr addr = lb_addr_soa_variable(ptr, idx, nullptr); - return lb_addr_load(p, addr); + return lb_addr_load(p, lb_addr_soa_variable_from_soa_ptr(p, value)); } GB_ASSERT_MSG(is_type_pointer(value.type), "%s", type_to_string(value.type)); @@ -3730,7 +3786,7 @@ gb_internal lbValue lb_generate_global_array(lbModule *m, Type *elem_type, i64 c g.type = alloc_type_pointer(t); LLVMSetInitializer(g.value, LLVMConstNull(lb_type(m, t))); LLVMSetLinkage(g.value, LLVMPrivateLinkage); - // LLVMSetUnnamedAddress(g.value, LLVMGlobalUnnamedAddr); + LLVMSetUnnamedAddress(g.value, LLVMGlobalUnnamedAddr); string_map_set(&m->members, s, g); return g; } diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 607dafde2..0c8cedb82 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1,3 +1,6 @@ +gb_internal LLVMValueRef lb_coerce_fields_load(lbProcedure *p, lbValue x, lbArgType const *arg); +gb_internal LLVMValueRef lb_coerce_fields_store(lbProcedure *p, LLVMValueRef coerced, Type *original_type, lbArgType const *arg); + gb_internal LLVMValueRef lb_call_intrinsic(lbProcedure *p, const char *name, LLVMValueRef* args, unsigned arg_count, LLVMTypeRef* types, unsigned type_count) { unsigned id = LLVMLookupIntrinsicID(name, gb_strlen(name)); GB_ASSERT_MSG(id != 0, "Unable to find %s", name); @@ -681,7 +684,9 @@ gb_internal void lb_begin_procedure_body(lbProcedure *p) { if (e->token.string.len != 0 && !is_blank_ident(e->token.string)) { LLVMTypeRef param_type = lb_type(p->module, e->type); LLVMValueRef original_value = LLVMGetParam(p->value, param_offset+llvm_param_index); - LLVMValueRef value = OdinLLVMBuildTransmute(p, original_value, param_type); + LLVMValueRef value = arg_type->coerce_offset_count > 0 + ? lb_coerce_fields_store(p, original_value, e->type, arg_type) + : OdinLLVMBuildTransmute(p, original_value, param_type); lbValue param = {}; param.value = value; @@ -922,6 +927,60 @@ gb_internal Array lb_value_to_array(lbProcedure *p, gbAllocator const & +// A `cast_type` is normally applied by reinterpreting the value's bits from offset zero. When the +// ABI flattened an aggregate and dropped padding, the dense type it produced puts the survivors at +// different offsets than they really have, and these two read and write them where they actually +// live. See `lbArgType::coerce_offsets`. +gb_internal LLVMValueRef lb_coerce_fields_load(lbProcedure *p, lbValue x, lbArgType const *arg) { + LLVMContextRef ctx = p->module->ctx; + LLVMTypeRef i8 = LLVMInt8TypeInContext(ctx); + LLVMTypeRef i64t = LLVMInt64TypeInContext(ctx); + + lbValue base = lb_address_from_load_or_generate_local(p, x); + + if (LLVMGetTypeKind(arg->cast_type) != LLVMStructTypeKind) { + GB_ASSERT(arg->coerce_offset_count == 1); + LLVMValueRef index = LLVMConstInt(i64t, cast(unsigned long long)arg->coerce_offsets[0], false); + LLVMValueRef ptr = LLVMBuildInBoundsGEP2(p->builder, i8, base.value, &index, 1, ""); + return LLVMBuildLoad2(p->builder, arg->cast_type, ptr, ""); + } + + unsigned count = LLVMCountStructElementTypes(arg->cast_type); + GB_ASSERT(cast(isize)count == arg->coerce_offset_count); + LLVMValueRef result = LLVMGetUndef(arg->cast_type); + for (unsigned i = 0; i < count; i += 1) { + LLVMTypeRef elem_type = LLVMStructGetTypeAtIndex(arg->cast_type, i); + LLVMValueRef index = LLVMConstInt(i64t, cast(unsigned long long)arg->coerce_offsets[i], false); + LLVMValueRef ptr = LLVMBuildInBoundsGEP2(p->builder, i8, base.value, &index, 1, ""); + LLVMValueRef elem = LLVMBuildLoad2(p->builder, elem_type, ptr, ""); + result = LLVMBuildInsertValue(p->builder, result, elem, i, ""); + } + return result; +} + +// The reverse: scatter a coerced value back into a full-sized object of the original type. +gb_internal LLVMValueRef lb_coerce_fields_store(lbProcedure *p, LLVMValueRef coerced, Type *original_type, lbArgType const *arg) { + LLVMContextRef ctx = p->module->ctx; + LLVMTypeRef i8 = LLVMInt8TypeInContext(ctx); + LLVMTypeRef i64t = LLVMInt64TypeInContext(ctx); + + lbAddr slot = lb_add_local_generated(p, original_type, true); + + unsigned count = 1; + bool is_struct = LLVMGetTypeKind(arg->cast_type) == LLVMStructTypeKind; + if (is_struct) { + count = LLVMCountStructElementTypes(arg->cast_type); + } + GB_ASSERT(cast(isize)count == arg->coerce_offset_count); + for (unsigned i = 0; i < count; i += 1) { + LLVMValueRef elem = is_struct ? LLVMBuildExtractValue(p->builder, coerced, i, "") : coerced; + LLVMValueRef index = LLVMConstInt(i64t, cast(unsigned long long)arg->coerce_offsets[i], false); + LLVMValueRef ptr = LLVMBuildInBoundsGEP2(p->builder, i8, slot.addr.value, &index, 1, ""); + LLVMBuildStore(p->builder, elem, ptr); + } + return lb_addr_load(p, slot).value; +} + gb_internal lbValue lb_emit_call_internal(lbProcedure *p, lbValue value, lbValue return_ptr, Array const &processed_args, Type *abi_rt, lbAddr context_ptr, ProcInlining inlining, ProcTailing tailing) { GB_ASSERT(p->module->ctx == LLVMGetTypeContext(LLVMTypeOf(value.value))); @@ -1191,7 +1250,10 @@ gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array c if (!abi_type) { abi_type = arg->type; } - if (xt == abi_type) { + if (arg->coerce_offset_count > 0) { + x.value = lb_coerce_fields_load(p, x, arg); + array_add(&processed_args, x); + } else if (xt == abi_type) { array_add(&processed_args, x); } else { x.value = OdinLLVMBuildTransmute(p, x.value, abi_type); @@ -1261,10 +1323,14 @@ gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array c result = lb_emit_load(p, return_ptr); } else if (rt != nullptr) { result = lb_emit_call_internal(p, value, {}, processed_args, rt, context_ptr, inlining, tailing); - if (ft->ret.cast_type) { - result.value = OdinLLVMBuildTransmute(p, result.value, ft->ret.cast_type); + if (ft->ret.coerce_offset_count > 0) { + result.value = lb_coerce_fields_store(p, result.value, rt, &ft->ret); + } else { + if (ft->ret.cast_type) { + result.value = OdinLLVMBuildTransmute(p, result.value, ft->ret.cast_type); + } + result.value = OdinLLVMBuildTransmute(p, result.value, ft->ret.type); } - result.value = OdinLLVMBuildTransmute(p, result.value, ft->ret.type); result.type = rt; if (LLVMTypeOf(result.value) == LLVMInt1TypeInContext(p->module->ctx)) { result.type = t_llvm_bool; @@ -1563,19 +1629,44 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn args[i] = arg.value; } - gbString name = gb_string_make(heap_allocator(), ""); - name = gb_string_append_fmt(name, "llvm.vector.interleave%d", n); - defer (gb_string_free(name)); + // `llvm.vector.interleave` is not usable across the supported targets: N > 2 does + // not exist before LLVM 22. Riscv & Darwin AMD64 has no `interleave2` either. + // A shuffle is the one primitive every target has, and it expresses a two-way + // interleave directly. + // + // The operand count is a power of two. The result is a riffle: pairing each operand + // with the one a half-step away is what makes the order come out right, as + // interleave4(a,b,c,d) == interleave2(interleave2(a,c), interleave2(b,d)). + LLVMTypeRef llvm_u32 = lb_type(m, t_u32); - LLVMTypeRef types[1] = {lb_type(m, tv.type)}; - res.value = lb_call_intrinsic(p, name, args, n, types, gb_count_of(types)); + LLVMValueRef *cur = args; + for (int count = n; count > 1; /**/) { + int half = count/2; + unsigned width = LLVMGetVectorSize(LLVMTypeOf(cur[0])); + + LLVMValueRef *mask = temporary_alloc_array(2*width); + for (unsigned i = 0; i < width; i++) { + mask[2*i + 0] = LLVMConstInt(llvm_u32, i, false); + mask[2*i + 1] = LLVMConstInt(llvm_u32, width+i, false); + } + LLVMValueRef mask_value = LLVMConstVector(mask, 2*width); + + LLVMValueRef *next = temporary_alloc_array(half); + for (int i = 0; i < half; i++) { + next[i] = LLVMBuildShuffleVector(p->builder, cur[i], cur[i+half], mask_value, ""); + } + + cur = next; + count = half; + } + + res.value = cur[0]; return res; } case BuiltinProc_simd_deinterleave: { lbValue arg0 = lb_build_expr(p, ce->args[0]); - LLVMTypeRef types[1] = {lb_type(m, arg0.type)}; GB_ASSERT(ce->args[1]->tav.value.kind == ExactValue_Integer); int n = cast(int)exact_value_to_i64(ce->args[1]->tav.value); @@ -1585,11 +1676,27 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn return res; } - gbString name = gb_string_make(heap_allocator(), ""); - name = gb_string_append_fmt(name, "llvm.vector.deinterleave%d", n); - defer (gb_string_free(name)); + // `llvm.vector.deinterleave` for N > 2 cannot be selected or legalized on most + // targets, only arm64 takes it. The split is done with shuffles, same as + // `simd_interleave`. Output `j` is the input strided by N starting at lane `j`. + LLVMTypeRef llvm_u32 = lb_type(m, t_u32); + LLVMTypeRef vector_type = LLVMTypeOf(arg0.value); + LLVMValueRef undef = LLVMGetUndef(vector_type); - res.value = lb_call_intrinsic(p, name, &arg0.value, 1, types, gb_count_of(types)); + unsigned width = LLVMGetVectorSize(vector_type); + unsigned part = width/cast(unsigned)n; + + LLVMValueRef agg = LLVMGetUndef(lb_type(m, tv.type)); + LLVMValueRef *mask = temporary_alloc_array(part); + for (int j = 0; j < n; j++) { + for (unsigned i = 0; i < part; i++) { + mask[i] = LLVMConstInt(llvm_u32, i*cast(unsigned)n + cast(unsigned)j, false); + } + LLVMValueRef lanes = LLVMBuildShuffleVector(p->builder, arg0.value, undef, LLVMConstVector(mask, part), ""); + agg = LLVMBuildInsertValue(p->builder, agg, lanes, cast(unsigned)j, ""); + } + + res.value = agg; return res; } } @@ -1610,7 +1717,6 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn case BuiltinProc_simd_sub: case BuiltinProc_simd_mul: case BuiltinProc_simd_div: - case BuiltinProc_simd_rem: if (is_float) { switch (builtin_id) { case BuiltinProc_simd_add: op_code = LLVMFAdd; break; @@ -1630,13 +1736,6 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn op_code = LLVMUDiv; } break; - case BuiltinProc_simd_rem: - if (is_signed) { - op_code = LLVMSRem; - } else { - op_code = LLVMURem; - } - break; } } if (op_code) { diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index c834dc192..9fbb2d15d 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -377,11 +377,22 @@ gb_internal void lb_build_when_stmt(lbProcedure *p, AstWhenStmt *ws) { gb_internal void lb_build_range_indexed(lbProcedure *p, lbValue expr, Type *val_type, lbValue count_ptr, lbValue *val_, lbValue *idx_, lbBlock **loop_, lbBlock **done_, - bool is_reverse, i64 unroll_count=0) { + bool is_reverse, i64 unroll_count=0, lbAddr const *soa_elem=nullptr) { lbModule *m = p->module; + // when ranging over an #soa element rather than an array, expr is unused, there being no array + // to point at, so expr_type (the [N]T being ranged over) comes from the container's soa_elem + bool const is_soa_elem = soa_elem != nullptr && soa_elem->kind == lbAddr_SoaVariable; + lbValue count = {}; - Type *expr_type = base_type(type_deref(expr.type)); + Type *expr_type = nullptr; + if (is_soa_elem) { + Type *soa = base_type(type_deref(soa_elem->addr.type)); + GB_ASSERT(soa->kind == Type_Struct && soa->Struct.soa_kind != StructSoa_None); + expr_type = base_type(soa->Struct.soa_elem); + } else { + expr_type = base_type(type_deref(expr.type)); + } switch (expr_type->kind) { case Type_Array: count = lb_const_int(m, t_int, expr_type->Array.count); @@ -472,18 +483,25 @@ gb_internal void lb_build_range_indexed(lbProcedure *p, lbValue expr, Type *val_ switch (expr_type->kind) { case Type_Array: { if (val_type != nullptr) { - val = lb_emit_load(p, lb_emit_array_ep(p, expr, idx)); + if (is_soa_elem) { + lbValue ptr = lb_soa_array_component_elem_ptr(p, soa_elem->addr, idx, soa_elem->soa.index, expr_type->Array.count); + lbAddr component = lb_addr_soa_field_elem(ptr); + val = lb_emit_load(p, component.addr); + } else { + val = lb_emit_load(p, lb_emit_array_ep(p, expr, idx)); + } } break; } case Type_EnumeratedArray: { if (val_type != nullptr) { val = lb_emit_load(p, lb_emit_array_ep(p, expr, idx)); - // NOTE(bill): Override the idx value for the enumeration - Type *index_type = expr_type->EnumeratedArray.index; - if (compare_exact_values(Token_NotEq, *expr_type->EnumeratedArray.min_value, exact_value_u64(0))) { - idx = lb_emit_arith(p, Token_Add, idx, lb_const_value(m, index_type, *expr_type->EnumeratedArray.min_value), index_type); - } + } + // NOTE(bill): Override the idx value for the enumeration + // this does not depend on the value operand, which may be blank + Type *index_type = expr_type->EnumeratedArray.index; + if (compare_exact_values(Token_NotEq, *expr_type->EnumeratedArray.min_value, exact_value_u64(0))) { + idx = lb_emit_arith(p, Token_Add, idx, lb_const_value(m, index_type, *expr_type->EnumeratedArray.min_value), index_type); } break; } @@ -1343,7 +1361,8 @@ gb_internal void lb_build_range_stmt(lbProcedure *p, AstRangeStmt *rs, Scope *sc break; } case Type_Array: { - lbValue array; + lbValue array = {}; + lbAddr const *soa_elem = nullptr; lbAddr addr = lb_build_addr(p, expr); switch (addr.kind) { case lbAddr_Swizzle: @@ -1352,9 +1371,28 @@ gb_internal void lb_build_range_stmt(lbProcedure *p, AstRangeStmt *rs, Scope *sc // NOTE(laytan): apply the swizzle. array = lb_address_from_load(p, lb_addr_load(p, addr)); break; + case lbAddr_SoaVariable: + // for v in soa[i] (and other direct forms); + // there is no array here to range over, the element is scattered across the field + // arrays and has no address of its own, only a container pointer and the element + // index, which is what this lbAddr carries. lb_build_range_indexed uses those + // with the loop counter as the component index to address each component directly + // + // the element index bounds check is hoisted here, + // the component index is in range by construction and needs none + lb_emit_soa_index_bounds_check(p, addr.addr, addr.soa.index, addr.soa.index_expr); + soa_elem = &addr; + break; default: array = lb_addr_get_ptr(p, addr); - if (is_type_pointer(type_deref(array.type))) { + if (is_type_soa_pointer(type_deref(array.type))) { + // for v in p, where p is an #soa element ptr (e.g. p := &soa[i]); + // we need to build the soa variable from the soa ptr + // and then produce the soa_elem as in the lbAddr_SoaVariable case above; + lbValue soa_ptr = lb_emit_load(p, array); + addr = lb_addr_soa_variable_from_soa_ptr(p, soa_ptr); + soa_elem = &addr; + } else if (is_type_pointer(type_deref(array.type))) { array = lb_emit_load(p, array); } break; @@ -1362,7 +1400,7 @@ gb_internal void lb_build_range_stmt(lbProcedure *p, AstRangeStmt *rs, Scope *sc lbAddr count_ptr = lb_add_local_generated(p, t_int, false); lb_addr_store(p, count_ptr, lb_const_int(p->module, t_int, et->Array.count)); - lb_build_range_indexed(p, array, val0_type, count_ptr.addr, &val, &key, &loop, &done, rs->reverse); + lb_build_range_indexed(p, array, val0_type, count_ptr.addr, &val, &key, &loop, &done, rs->reverse, 0, soa_elem); break; } case Type_EnumeratedArray: { @@ -1702,7 +1740,13 @@ gb_internal void lb_build_unroll_range_stmt(lbProcedure *p, AstUnrollRangeStmt * slice = lb_emit_load(p, slice); } else { count_ptr = lb_add_local_generated(p, t_int, false).addr; - lb_emit_store(p, count_ptr, lb_slice_len(p, slice)); + if (t->kind == Type_Slice) { + lb_emit_store(p, count_ptr, lb_slice_len(p, slice)); + } else if (t->kind == Type_DynamicArray) { + lb_emit_store(p, count_ptr, lb_dynamic_array_len(p, slice)); + } else { + GB_ASSERT_MSG(false, "Need to add support for this type."); + } } data_ptr = lb_emit_struct_ev(p, slice, 0); break; diff --git a/src/main.cpp b/src/main.cpp index 14d526043..28b1464cf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3796,6 +3796,13 @@ int main(int arg_count, char const **arg_ptr) { defer (timings_destroy(&global_timings)); MAIN_TIME_SECTION("initialization"); + // NOTE(Jeroen): Set codepage to UTF-8 (Windows only) and restore on exit. + // Keep in mind this is for the compiler's own output only. + // Like error messages on lines containing unicode. + // Child processes will inherit the default codepage, + // and so must do their own codepage management if they want. + set_utf8_codepage(); + defer (restore_old_codepage()); init_string_interner(); init_global_error_collector(); diff --git a/src/name_canonicalization.cpp b/src/name_canonicalization.cpp index 1e615d7c8..97f10e0f8 100644 --- a/src/name_canonicalization.cpp +++ b/src/name_canonicalization.cpp @@ -284,7 +284,7 @@ void typeid_hash_context_init(TypeidHashContext *hash_ctx) { u64 rotate_left64(u64 x, u64 k) { static u64 const n = 64; u64 s = k & (n-1); - return (x<>(n-2)); + return (x<>(n-s)); } void sip_compress(SipHashContext *sip) { diff --git a/src/parser.cpp b/src/parser.cpp index 417c4b418..6742f44f0 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -113,6 +113,12 @@ gb_internal gbString get_file_line_as_string(TokenPos const &pos, i32 *offset_) if (len < offset) { return nullptr; } + + // a column past the first cannot belong to a line the offset has already left + if (offset > 0 && pos.column > 1 && start[offset-1] == '\n') { + offset -= 1; + } + u8 *pos_offset = start+offset; u8 *line_start = pos_offset; diff --git a/src/types.cpp b/src/types.cpp index 0e41b1b9b..e9ac4260c 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -1446,11 +1446,13 @@ gb_internal bool is_type_ordered(Type *t) { return false; } gb_internal bool is_type_ordered_numeric(Type *t) { - t = core_type(t); + t = base_type(t); if (t == nullptr) { return false; } switch (t->kind) { case Type_Basic: return (t->Basic.flags & BasicFlag_OrderedNumeric) != 0; + case Type_Enum: + return is_type_ordered_numeric(t->Enum.base_type); } return false; } @@ -2091,10 +2093,11 @@ gb_internal bool is_type_endian_big(Type *t) { return build_context.endian_kind == TargetEndian_Big; } else if (t->kind == Type_BitSet) { return is_type_endian_big(bit_set_to_int(t)); - } else if (t->kind == Type_Pointer) { + } else if (t->kind == Type_Pointer || t->kind == Type_MultiPointer) { return is_type_endian_big(&basic_types[Basic_uintptr]); } - return build_context.endian_kind == TargetEndian_Big; + // a type with no endianness is neither little nor big + return false; } gb_internal bool is_type_endian_little(Type *t) { t = core_type(t); @@ -2108,10 +2111,11 @@ gb_internal bool is_type_endian_little(Type *t) { return build_context.endian_kind == TargetEndian_Little; } else if (t->kind == Type_BitSet) { return is_type_endian_little(bit_set_to_int(t)); - } else if (t->kind == Type_Pointer) { + } else if (t->kind == Type_Pointer || t->kind == Type_MultiPointer) { return is_type_endian_little(&basic_types[Basic_uintptr]); } - return build_context.endian_kind == TargetEndian_Little; + // a type with no endianness is neither little nor big + return false; } gb_internal bool is_type_endian_platform(Type *t) { @@ -2121,7 +2125,7 @@ gb_internal bool is_type_endian_platform(Type *t) { return (t->Basic.flags & (BasicFlag_EndianLittle|BasicFlag_EndianBig)) == 0; } else if (t->kind == Type_BitSet) { return is_type_endian_platform(bit_set_to_int(t)); - } else if (t->kind == Type_Pointer) { + } else if (t->kind == Type_Pointer || t->kind == Type_MultiPointer) { return is_type_endian_platform(&basic_types[Basic_uintptr]); } return false; @@ -2179,6 +2183,10 @@ gb_internal bool is_type_dereferenceable(Type *t) { gb_internal bool is_type_different_to_arch_endianness(Type *t) { + // a type with no endianness never needs swapping + if (!is_type_endian_specific(t)) { + return false; + } switch (build_context.endian_kind) { case TargetEndian_Little: return !is_type_endian_little(t); @@ -2824,6 +2832,10 @@ gb_internal bool is_type_comparable(Type *t) { if (t->Struct.soa_kind != StructSoa_None) { return false; } + // an unspecialized polymorphic record has no values to compare + if (is_type_polymorphic_record_unspecialized(t)) { + return false; + } if (t->Struct.is_raw_union) { return is_type_simple_compare(t); } @@ -4496,8 +4508,7 @@ gb_internal i64 type_align_of_internal(Type *t, TypePath *path) { if (t->Struct.custom_min_field_align > 0) { max = gb_max(max, t->Struct.custom_min_field_align); } - if (t->Struct.custom_max_field_align != 0 && - t->Struct.custom_max_field_align > t->Struct.custom_min_field_align) { + if (t->Struct.custom_max_field_align != 0) { max = gb_min(max, t->Struct.custom_max_field_align); } return max; @@ -4567,7 +4578,7 @@ gb_internal i64 *type_set_offsets_of(Slice const &fields, bool is_pack } else { Type *t = fields[i]->type; i64 align = gb_max(type_align_of_internal(t, &path), min_field_align); - if (max_field_align > min_field_align) { + if (max_field_align != 0) { align = gb_min(align, max_field_align); } i64 size = gb_max(type_size_of_internal(t, &path), 0); diff --git a/tests/core/encoding/hex/test_core_hex.odin b/tests/core/encoding/hex/test_core_hex.odin index 6a00c9705..fdb3a2734 100644 --- a/tests/core/encoding/hex/test_core_hex.odin +++ b/tests/core/encoding/hex/test_core_hex.odin @@ -47,6 +47,60 @@ hex_decode :: proc(t: ^testing.T) { } } +@(test) +hex_decode_into_buffer :: proc(t: ^testing.T) { + for test in CASES { + buffer := make([]u8, len(test[1]) / 2) + defer delete(buffer) + decoded, ok := hex.decode_into_buffer(transmute([]byte)test[1], buffer) + testing.expect(t, ok, "decode_into_buffer: not ok") + testing.expectf( + t, + ok, + "decode: %q not ok", + test[1], + ) + testing.expectf( + t, + string(decoded) == test[0], + "decode: %q -> %q (should be: %q)", + test[1], + string(decoded), + test[0], + ) + } + + // destination buffer is larger + for test in CASES { + buffer := make([]u8, len(test[1]) / 2 + 1) + defer delete(buffer) + decoded, ok := hex.decode_into_buffer(transmute([]byte)test[1], buffer) + testing.expect(t, ok, "decode_into_buffer: not ok") + testing.expectf( + t, + ok, + "decode: %q not ok", + test[1], + ) + testing.expectf( + t, + string(decoded) == test[0], + "decode: %q -> %q (should be: %q)", + test[1], + string(decoded), + test[0], + ) + } + + // destination buffer is too small + for test in CASES { + buffer := make([]u8, len(test[1]) / 2 - 1) + defer delete(buffer) + _, ok := hex.decode_into_buffer(transmute([]byte)test[1], buffer) + testing.expect(t, !ok, "decode_into_buffer: should not be ok") + } +} + @(test) hex_decode_sequence :: proc(t: ^testing.T) { b, ok := hex.decode_sequence("0x23") @@ -83,4 +137,4 @@ hex_decode_sequence :: proc(t: ^testing.T) { _, ok = hex.decode_sequence("123") testing.expect(t, !ok, "decode_sequence: 123 should be too long") -} \ No newline at end of file +} diff --git a/tests/core/mem/test_mem_dynamic_arena.odin b/tests/core/mem/test_mem_dynamic_arena.odin index 7d2b32f1a..81059cb5e 100644 --- a/tests/core/mem/test_mem_dynamic_arena.odin +++ b/tests/core/mem/test_mem_dynamic_arena.odin @@ -74,6 +74,59 @@ test_dynamic_arena_alloc_unaligned :: proc(t: ^testing.T) { expect_arena_allocation(t, expected_used_bytes = 16, num_bytes = 9, alignment = 8) } +expect_reused_block_alignment :: proc( + t: ^testing.T, + backing: []byte, + block_size: int, + should_reuse: bool, +) { + block_arena: mem.Arena + mem.arena_init(&block_arena, backing) + + arena: mem.Dynamic_Arena + mem.dynamic_arena_init( + &arena, + block_allocator = mem.arena_allocator(&block_arena), + block_size = block_size, + out_band_size = block_size + 1, + ) + defer mem.dynamic_arena_destroy(&arena) + + allocator := mem.dynamic_arena_allocator(&arena) + first, first_err := mem.alloc(1, 16, allocator) + testing.expect_value(t, first_err, mem.Allocator_Error.None) + testing.expect(t, uintptr(first) % 64 == 16) + + mem.dynamic_arena_reset(&arena) + + aligned, aligned_err := mem.alloc(1, 64, allocator) + testing.expect_value(t, aligned_err, mem.Allocator_Error.None) + testing.expect(t, uintptr(aligned) % 64 == 0) + testing.expect_value(t, arena.current_block == first, should_reuse) +} + +@(test) +test_dynamic_arena_realigns_reused_block :: proc(t: ^testing.T) { + Backing :: struct #align(64) { + _: [16]byte, + data: [128]byte, + } + + backing: Backing + expect_reused_block_alignment(t, backing.data[:], 128, true) +} + +@(test) +test_dynamic_arena_skips_incompatible_reused_block :: proc(t: ^testing.T) { + Backing :: struct #align(64) { + _: [16]byte, + data: [176]byte, + } + + backing: Backing + expect_reused_block_alignment(t, backing.data[:], 64, false) +} + @(test) test_dynamic_arena_alloc_out_of_band :: proc(t: ^testing.T) { expect_arena_allocation_out_of_band(t, num_bytes = 128, block_size = 512, out_band_size = 128) diff --git a/tests/core/net/test_core_net.odin b/tests/core/net/test_core_net.odin index b0cf57cad..17eb3bdd0 100644 --- a/tests/core/net/test_core_net.odin +++ b/tests/core/net/test_core_net.odin @@ -608,6 +608,11 @@ test_udp_echo :: proc(t: ^testing.T) { test_dns_resolve :: proc(t: ^testing.T) { // NOTE: This test depends on external factors, so if it fails, an IP // address may have changed or become unavailable. + // It regularly fails on the RISC-V CI runners, so we skip it there + when ODIN_ARCH == .riscv64 { + log.infof("Skipped on riscv64") + return + } // The net API returns only one address per protocol version, and DNS // records can store many, so we'll have to check all possibilities. diff --git a/tests/core/normal.odin b/tests/core/normal.odin index 99a30343c..303acd232 100644 --- a/tests/core/normal.odin +++ b/tests/core/normal.odin @@ -50,6 +50,7 @@ download_assets :: proc "contextless" () { @(require) import "sys/posix" @(require) import "sys/kqueue" @(require) import "sys/windows" +@(require) import "text/edit" @(require) import "text/i18n" @(require) import "text/match" @(require) import "text/regex" diff --git a/tests/core/runtime/test_core_runtime.odin b/tests/core/runtime/test_core_runtime.odin index 7db985dea..13b0f02a0 100644 --- a/tests/core/runtime/test_core_runtime.odin +++ b/tests/core/runtime/test_core_runtime.odin @@ -384,6 +384,440 @@ test_soa_array_elem_swizzle :: proc(t: ^testing.T) { testing.expect_value(t, fixed[1].xy, [2]u16{10, 9}) } +// chained indexing of #soa container when element type is an array -> soa[i][j] +@(test) +test_soa_array_elem_chained_indexing :: proc(t: ^testing.T) { + + fixed: #soa[3][4]u16 + for i in 0 ..< 3 { + fixed.x[i] = u16(i*10 + 0) + fixed.y[i] = u16(i*10 + 1) + fixed.z[i] = u16(i*10 + 2) + fixed.w[i] = u16(i*10 + 3) + } + + // const inner index + testing.expect_value(t, fixed[1][0], 10) + testing.expect_value(t, fixed[2][3], 23) + + // var inner index + for i in 0 ..< 3 { + tmp := fixed[i] + for j in 0 ..< 4 { + testing.expect_value(t, fixed[i][j], tmp[j]) + testing.expect_value(t, fixed[i][j], u16(i*10 + j)) + } + } + + // index and the .x/y/z/w name must agree + for i in 0 ..< 3 { + testing.expect_value(t, fixed[i][0], fixed[i].x) + testing.expect_value(t, fixed[i][3], fixed[i].w) + } + + // dynamic + slice + dyn := make(#soa[dynamic][4]u16, 2) + defer delete(dyn) + dyn[0] = [4]u16{10, 11, 12, 13} + dyn[1] = [4]u16{20, 21, 22, 23} + + testing.expect_value(t, dyn[0][3], 13) + testing.expect_value(t, dyn[1][0], 20) + + slice := dyn[:] + testing.expect_value(t, slice[0][3], 13) + testing.expect_value(t, slice[1][2], 22) + + for i in 0 ..< 2 { + dyn_tmp := dyn[i] + slice_tmp := slice[i] + for j in 0 ..< 4 { + testing.expect_value(t, dyn[i][j], dyn_tmp[j]) + testing.expect_value(t, slice[i][j], dyn_tmp[j]) + testing.expect_value(t, dyn[i][j], slice_tmp[j]) + testing.expect_value(t, slice[i][j], slice_tmp[j]) + } + } + + // soa[i][j] must equal v[j] where v is the for-in looping variable + // test fixed, dynamic and sliec + for v, i in fixed { + testing.expect_value(t, v[3], u16(i*10 + 3)) + for j in 0 ..< 4 { + testing.expect_value(t, v[j], fixed[i][j]) + } + } + for &v, i in fixed { + testing.expect_value(t, v[1], u16(i*10 + 1)) + j := 2 + testing.expect_value(t, v[j], u16(i*10 + j)) + } + for v, i in dyn { + for j in 0 ..< 4 { + testing.expect_value(t, v[j], u16((i + 1)*10 + j)) + } + } + for &v, i in dyn { + testing.expect_value(t, v[1], u16((i + 1)*10 + 1)) + j := 3 + testing.expect_value(t, v[j], u16((i + 1)*10 + j)) + } + for v, i in slice { + testing.expect_value(t, v[2], slice[i][2]) + } + for &v, i in slice { + testing.expect_value(t, v[2], slice[i][2]) + j := 0 + testing.expect_value(t, v[j], u16((i + 1)*10 + j)) + } + + // write access must work through the looping var + fixed_scatter: #soa[2][4]u16 + for &v, i in fixed_scatter { + v[0] = u16(i) + for j in 1 ..< 4 { + v[j] = u16(i*10 + j) + } + v[3] += 5 + } + for i in 0 ..< 2 { + testing.expect_value(t, fixed_scatter.x[i], u16(i)) + testing.expect_value(t, fixed_scatter.y[i], u16(i*10 + 1)) + testing.expect_value(t, fixed_scatter.z[i], u16(i*10 + 2)) + testing.expect_value(t, fixed_scatter.w[i], u16(i*10 + 3 + 5)) + } + // for fixed soa you can get fancy and select a whole .x/y/z/w lane + testing.expect_value(t, fixed_scatter.x, [2]u16{0, 1}) + testing.expect_value(t, fixed_scatter.y, [2]u16{1, 11}) + testing.expect_value(t, fixed_scatter.z, [2]u16{2, 12}) + testing.expect_value(t, fixed_scatter.w, [2]u16{3 + 5, 13 + 5}) + + dyn_scatter := make(#soa[dynamic][4]u16, 2) + defer delete(dyn_scatter) + for &v, i in dyn_scatter { + v[0] = u16(i + 1) + for j in 1 ..< 4 { + v[j] = u16((i + 1)*10 + j) + } + v[3] += 5 + } + for i in 0 ..< 2 { + testing.expect_value(t, dyn_scatter.x[i], u16(i + 1)) + testing.expect_value(t, dyn_scatter.y[i], u16((i + 1)*10 + 1)) + testing.expect_value(t, dyn_scatter.z[i], u16((i + 1)*10 + 2)) + testing.expect_value(t, dyn_scatter.w[i], u16((i + 1)*10 + 3 + 5)) + } + + slice_scatter := dyn_scatter[:] + for &v in slice_scatter { + v[1] += 100 + } + testing.expect_value(t, dyn_scatter.y[0], 111) + testing.expect_value(t, dyn_scatter.y[1], 121) + testing.expect_value(t, dyn_scatter.x[0], 1) + + // soa[i][j] writes + fixed_write: #soa[3][4]u16 + fixed_write[1][0] = 5 + k := 2 + fixed_write[1][k] = 6 + fixed_write[1][1] += 7 + fixed_write[1][0], fixed_write[1][3] = fixed_write[1][3], fixed_write[1][0] + testing.expect_value(t, fixed_write.x, [3]u16{0, 0, 0}) + testing.expect_value(t, fixed_write.y, [3]u16{0, 7, 0}) + testing.expect_value(t, fixed_write.z, [3]u16{0, 6, 0}) + testing.expect_value(t, fixed_write.w, [3]u16{0, 5, 0}) + + dyn_write := make(#soa[dynamic][4]u16, 2) + defer delete(dyn_write) + dyn_write[0][3] = 41 + dyn_write[1][k] = 42 + slice_write := dyn_write[:] + slice_write[0][1] = 43 + slice_write[0][k] = 44 + testing.expect_value(t, dyn_write.w[0], 41) + testing.expect_value(t, dyn_write.z[1], 42) + testing.expect_value(t, dyn_write.y[0], 43) + testing.expect_value(t, dyn_write.z[0], 44) + testing.expect_value(t, dyn_write.x[0], 0) + + // a single component has a real address + testing.expect_value(t, &fixed_write[1][2], &fixed_write.z[1]) + pw := &fixed_write[1][2] + pw^ = 60 + testing.expect_value(t, fixed_write.z[1], 60) + for j in 0 ..< 4 { + p := &fixed_write[1][j] + p^ = u16(70 + j) + } + testing.expect_value(t, fixed_write.x, [3]u16{0, 70, 0}) + testing.expect_value(t, fixed_write.y, [3]u16{0, 71, 0}) + testing.expect_value(t, fixed_write.z, [3]u16{0, 72, 0}) + testing.expect_value(t, fixed_write.w, [3]u16{0, 73, 0}) + + for j in 0 ..< 4 { + p := &dyn_write[0][j] + p^ = u16(80 + j) + } + testing.expect_value(t, dyn_write.x[0], 80) + testing.expect_value(t, dyn_write.w[0], 83) + for j in 0 ..< 4 { + p := &slice_write[1][j] + p^ = u16(90 + j) + } + testing.expect_value(t, dyn_write.x[1], 90) + testing.expect_value(t, dyn_write.w[1], 93) + + // through a pointer to the element + ep := &fixed_write[1] + k = 2 + testing.expect_value(t, ep[k], 72) + testing.expect_value(t, ep[1], ep^[1]) + ep[0] = 100 + testing.expect_value(t, fixed_write.x[1], 100) + ep[k] = 102 + testing.expect_value(t, fixed_write.z[1], 102) + pe := &ep[3] + pe^ = 103 + testing.expect_value(t, fixed_write.w[1], 103) + + eps := &slice_write[0] + eps[1] = 143 + testing.expect_value(t, dyn_write.y[0], 143) + epd := &dyn_write[1] + testing.expect_value(t, epd[k], 92) + epd[0] = 190 + testing.expect_value(t, dyn_write.x[1], 190) + + // multi dim array element type, only the outer index is scattered + nested: #soa[2][3][2]u16 + nested[1].x = {1, 3} + nested[1].z = {7, 11} + testing.expect_value(t, nested[1][0][1], 3) + testing.expect_value(t, nested[1][2][0], 7) + + nested[1][0][1] = 8 + nested[1][2][0] = 9 + testing.expect_value(t, nested[1][0], [2]u16{1, 8}) + testing.expect_value(t, nested[1][2], [2]u16{9, 11}) + + for j in 0 ..< 3 { + nested[1][j][0] = u16(50 + j) + } + for j in 0 ..< 3 { + testing.expect_value(t, nested[1][j][0], u16(50 + j)) + } + testing.expect_value(t, nested.x[1][0], 50) + testing.expect_value(t, nested.y[1][0], 51) + testing.expect_value(t, nested.z[1][0], 52) + testing.expect_value(t, nested[0][0], [2]u16{0, 0}) + + pn := &nested[1] + testing.expect_value(t, pn[0][0], 50) + pn[0][1] = 60 + testing.expect_value(t, nested.x[1][1], 60) +} + +// ranging over an element of array-element typed #soa, +// for v in soa[i], or for &v in soa[i] +@(test) +test_soa_array_elem_range :: proc(t: ^testing.T) { + fixed: #soa[3][4]u16 + fixed[0] = [4]u16{90, 91, 92, 93} + fixed[1] = [4]u16{10, 11, 12, 13} + + for v, j in fixed[1] { + testing.expect_value(t, v, fixed[1][j]) + } + + #reverse for v, j in fixed[0] { + testing.expect_value(t, v, fixed[0][j]) + } + + got: [4]u16 + i := 0 + for v in fixed[1] { + got[i] = v + i += 1 + } + testing.expect_value(t, got, [4]u16{10, 11, 12, 13}) + + // double loop + sum : u16 = 0 + for e in fixed { + for v in e { + sum += v + } + } + testing.expect_value(t, sum, 90 + 91 + 92 + 93 + 10 + 11 + 12 + 13) + + // through pointer to the container + got = {} + i = 0 + p := &fixed + k := 1 + for v in p[k] { + got[i] = v + i += 1 + } + testing.expect_value(t, got, [4]u16{10, 11, 12, 13}) + + // slice + slice := fixed[:] + got = {} + i = 0 + for v in slice[1] { + got[i] = v + i += 1 + } + testing.expect_value(t, got, [4]u16{10, 11, 12, 13}) + + // dynamic + dyn: #soa[dynamic][4]u16 + defer delete(dyn) + append_soa(&dyn, [4]u16{20, 21, 22, 23}) + append_soa(&dyn, [4]u16{30, 31, 32, 33}) + got = {} + i = 0 + for v in dyn[1] { + got[i] = v + i += 1 + } + testing.expect_value(t, got, [4]u16{30, 31, 32, 33}) + + // multi dim array type + nested: #soa[2][3][2]u16 + nested[1] = [3][2]u16{{1, 2}, {3, 4}, {5, 6}} + flat: [6]u16 + n := 0 + for row in nested[1] { + for v in row { + flat[n] = v + n += 1 + } + } + testing.expect_value(t, flat, [6]u16{1, 2, 3, 4, 5, 6}) + + // a write during the loop is visible to later iterations + // (same as normal arrays) + live: #soa[2][4]u16 + live[0] = [4]u16{1, 2, 3, 4} + seen: [4]u16 + for v, j in live[0] { + if j == 0 { + live.z[0] = 99 + } + seen[j] = v + } + testing.expect_value(t, seen, [4]u16{1, 2, 99, 4}) + + ////////////////////////// + // for &v in soa[i] + ////////////////////////// + + // fixed: #soa[3][4]u16 + fixed[1] = [4]u16{1, 2, 3, 4} + for &v in fixed[1] { + v *= 10 + } + testing.expect_value(t, fixed[1], [4]u16{10, 20, 30, 40}) + testing.expect_value(t, fixed.x[1], 10) + testing.expect_value(t, fixed.w[1], 40) + + fixed[1] = [4]u16{1, 2, 3, 4} + for &v, j in fixed[1] { + v += u16(j) + } + testing.expect_value(t, fixed[1], [4]u16{1, 3, 5, 7}) + + fixed[1] = [4]u16{1, 2, 3, 4} + #reverse for &v in fixed[1] { + v *= 2 + } + testing.expect_value(t, fixed[1], [4]u16{2, 4, 6, 8}) + + // through pointer to the container + fixed[1] = [4]u16{1, 2, 3, 4} + k = 1 + p = &fixed + for &v in p[k] { + v += 100 + } + testing.expect_value(t, fixed[1], [4]u16{101, 102, 103, 104}) + + // slice + fixed[1] = [4]u16{1, 2, 3, 4} + slice = fixed[:] + for &v in slice[1] { + v *= 3 + } + testing.expect_value(t, fixed[1], [4]u16{3, 6, 9, 12}) + + // dynamic + dyn[0] = [4]u16{1, 2, 3, 4} + for &v in dyn[0] { + v += 5 + } + testing.expect_value(t, dyn[0], [4]u16{6, 7, 8, 9}) + + // multi dim array type + nested[0] = [2]u16{1, 2} + nested[1] = [2]u16{5, 6} + for &e in nested { + for &v in e { + v += 1 + } + } + testing.expect_value(t, nested[0], [2]u16{2, 3}) + testing.expect_value(t, nested[1], [2]u16{6, 7}) + + ////////////////////////// + // through a pointer to the element + // for v in ep^, or for v in ep + ////////////////////////// + + fixed[1] = [4]u16{1, 2, 3, 4} + ep := &fixed[1] + + got = {} + i = 0 + for v in ep^ { + got[i] = v + i += 1 + } + testing.expect_value(t, got, [4]u16{1, 2, 3, 4}) + + for &v, j in ep^ { + v += u16(10 * (j + 1)) + } + testing.expect_value(t, fixed[1], [4]u16{11, 22, 33, 44}) + + // auto-deref + fixed[1] = [4]u16{1, 2, 3, 4} + got = {} + i = 0 + for v in ep { + got[i] = v + i += 1 + } + testing.expect_value(t, got, [4]u16{1, 2, 3, 4}) + + for &v in ep { + v *= 2 + } + testing.expect_value(t, fixed[1], [4]u16{2, 4, 6, 8}) + + // a [0]T element has no components; the loop must not run + // (and the compiler must not crash :) + c0: #soa[2][0]u16 + n = 0 + for v in c0[0] { + _ = v + n += 1 + } + testing.expect_value(t, n, 0) +} + // "using" on an #soa for-in looping variable @(test) test_soa_for_in_using :: proc(t: ^testing.T) { @@ -647,11 +1081,9 @@ test_memory_compare_zero :: proc(t: ^testing.T) { } } -// Runs identical append/inject/remove sequences for a #soa[dynamic] array +// Runs identical append/inject/remove/pop sequences for a #soa[dynamic] array // and an AoS [dynamic] reference, comparing all elements after each -// stage. Covers appends, injections (interior, at the end, and past the -// end where the gap must read as zero elements), unordered and ordered -// removes. +// stage. Covers appends, injections, pops, unordered and ordered removes. @(test) test_soa_array_append_inject_remove :: proc(t: ^testing.T) { check :: proc(t: ^testing.T, $E: typeid, mk: proc(i: int) -> E) { @@ -808,6 +1240,74 @@ test_soa_array_append_inject_remove :: proc(t: ^testing.T) { testing.expect_value(t, err, nil) non_zero_append(&ref, mk(42)) expect_same(t, soa, ref) + + // interleave pops from both ends + n, err = append(&soa, ..buf[:]) + testing.expect_value(t, n, 32) + testing.expect_value(t, err, nil) + append(&ref, ..buf[:]) + for _ in 0..<8 { + testing.expect_value(t, pop_soa(&soa), pop(&ref)) + testing.expect_value(t, pop_front_soa(&soa), pop_front(&ref)) + } + expect_same(t, soa, ref) + + // safe pops from both ends, down to empty + popped: E + for len(ref) > 0 { + popped, ok = pop_safe_soa(&soa) + testing.expect(t, ok) + testing.expect_value(t, popped, pop(&ref)) + if len(ref) == 0 { + break + } + popped, ok = pop_front_safe_soa(&soa) + testing.expect(t, ok) + testing.expect_value(t, popped, pop_front(&ref)) + } + expect_same(t, soa, ref) + testing.expect_value(t, len(soa), 0) + + // safe pops must report failure on an empty array + _, ok = pop_safe_soa(&soa) + testing.expect(t, !ok) + _, ok = pop_front_safe_soa(&soa) + testing.expect(t, !ok) + + // test procedure groups resolution for the builtin names + for i in 0..<10 { + append(&soa, mk(700 + i)) + append(&ref, mk(700 + i)) + } + ok, err = inject_at(&soa, 3, mk(800)) + testing.expect(t, ok) + testing.expect_value(t, err, nil) + inject_at(&ref, 3, mk(800)) + expect_same(t, soa, ref) + + ok, err = inject_at(&soa, 2, ..buf[:BATCH_LEN]) + testing.expect(t, ok) + testing.expect_value(t, err, nil) + inject_at(&ref, 2, ..buf[:BATCH_LEN]) + expect_same(t, soa, ref) + + ordered_remove(&soa, 4) + ordered_remove(&ref, 4) + unordered_remove(&soa, 1) + unordered_remove(&ref, 1) + expect_same(t, soa, ref) + + testing.expect_value(t, pop(&soa), pop(&ref)) + testing.expect_value(t, pop_front(&soa), pop_front(&ref)) + expect_same(t, soa, ref) + + popped, ok = pop_safe(&soa) + testing.expect(t, ok) + testing.expect_value(t, popped, pop(&ref)) + popped, ok = pop_front_safe(&soa) + testing.expect(t, ok) + testing.expect_value(t, popped, pop_front(&ref)) + expect_same(t, soa, ref) } // mixed field widths + padding diff --git a/tests/core/slice/test_core_slice.odin b/tests/core/slice/test_core_slice.odin index b0fb791a6..ee5168e37 100644 --- a/tests/core/slice/test_core_slice.odin +++ b/tests/core/slice/test_core_slice.odin @@ -140,6 +140,52 @@ test_sort_by_indices :: proc(t: ^testing.T) { } } +@test +test_sort_stability :: proc(t: ^testing.T) { + // Test sizes are all prime. + test_sizes :: []int{7, 13, 347, 1031, 10111, 100003} + Data :: struct { + rand: int, + index: int, + } + + for test_size in test_sizes { + rand.reset(t.seed) + + vals := make([]Data, test_size) + defer delete(vals) + + // Set up test values + for &val, i in vals { + val = {rand.int_max(10), i} + } + + // Sort + slice.stable_sort_by(vals, proc(l, r: Data) -> bool {return l.rand < r.rand}) + + // Verify sorted test values + rand.reset(t.seed) + + sum := vals[0].index + for i in 1.. vals[i].rand { + testing.expect(t, false, "Expected slice to be sorted") + } + if vals[i - 1].rand < vals[i].rand { + continue + } + if vals[i - 1].index > vals[i].index { + testing.expect(t, false, "Expected slice to be stable") + } + } + + testing.expect(t, sum == test_size * (test_size - 1) / 2, "Expected slice to have all indecies") + + } +} + + @test test_binary_search :: proc(t: ^testing.T) { index: int diff --git a/tests/core/sync/test_core_sync.odin b/tests/core/sync/test_core_sync.odin index d6a7a9517..28c144935 100644 --- a/tests/core/sync/test_core_sync.odin +++ b/tests/core/sync/test_core_sync.odin @@ -8,7 +8,6 @@ package test_core_sync import "base:intrinsics" -// import "core:log" import "core:sync" import "core:testing" import "core:thread" @@ -538,6 +537,12 @@ test_ticket_mutex :: proc(t: ^testing.T) { @test test_benaphore :: proc(t: ^testing.T) { + when ODIN_ARCH == .riscv64 { + // NOTE(Jeroen): Regularly times out on the simulated RISC-V CI runners, + // so we're skipping the test there. + return + } + testing.set_fail_timeout(t, FAIL_TIME) Data :: struct { diff --git a/tests/core/text/edit/test_core_text_edit.odin b/tests/core/text/edit/test_core_text_edit.odin new file mode 100644 index 000000000..ab651b3b9 --- /dev/null +++ b/tests/core/text/edit/test_core_text_edit.odin @@ -0,0 +1,113 @@ +package test_core_text_edit + +import "core:slice" +import "core:strings" +import "core:testing" +import "core:text/edit" + +// "hi", a thumbs-up with a skin tone modifier, an "e" with a combining acute, and "!". +// The escapes are spelled out so the byte offsets below do not depend on how this +// file happens to be normalized. +// +// byte: 0 1 2 6 10 11 13 14 +// rune: h i U+1F44D U+1F3FD e U+0301 ! +GRAPHEME_SAMPLE :: "hi\U0001F44D\U0001F3FDe\u0301!" + +WORD_SAMPLE :: "foo bar baz" + +State :: struct { + state: edit.State, + builder: strings.Builder, +} + +state_init :: proc(s: ^State, str: string, translate_by_grapheme: bool) { + s.builder = strings.builder_make() + strings.write_string(&s.builder, str) + + edit.init(&s.state, context.allocator, context.allocator) + edit.setup_once(&s.state, &s.builder) + s.state.translate_by_grapheme = translate_by_grapheme +} + +state_destroy :: proc(s: ^State) { + edit.destroy(&s.state) + strings.builder_destroy(&s.builder) +} + +// Walk the caret from `start` in direction `t` until it stops moving, collecting +// every position it comes to rest on. +walk :: proc(s: ^State, start: int, t: edit.Translation) -> (stops: [dynamic]int) { + s.state.selection = {start, start} + for { + prev := s.state.selection[0] + edit.move_to(&s.state, t) + if s.state.selection[0] == prev { + return + } + append(&stops, s.state.selection[0]) + } +} + +expect_walk :: proc(t: ^testing.T, s: ^State, start: int, translation: edit.Translation, expected: []int) { + stops := walk(s, start, translation) + defer delete(stops) + + testing.expectf(t, slice.equal(stops[:], expected), + "%v from %v: expected stops %v, got %v", translation, start, expected, stops[:]) +} + +// Moving by grapheme must stop on grapheme cluster boundaries, never inside the +// emoji modifier sequence or between a base rune and its combining marks. +@(test) +test_translate_by_grapheme :: proc(t: ^testing.T) { + s: State + state_init(&s, GRAPHEME_SAMPLE, true) + defer state_destroy(&s) + + expect_walk(t, &s, 0, .Right, {1, 2, 10, 13, 14}) + expect_walk(t, &s, len(GRAPHEME_SAMPLE), .Left, {13, 10, 2, 1, 0}) +} + +// The default translation moves by codepoint, so it steps through the two runes +// of the emoji sequence and the two runes of "e" + combining acute separately. +@(test) +test_translate_by_codepoint :: proc(t: ^testing.T) { + s: State + state_init(&s, GRAPHEME_SAMPLE, false) + defer state_destroy(&s) + + expect_walk(t, &s, 0, .Right, {1, 2, 6, 10, 11, 13, 14}) + expect_walk(t, &s, len(GRAPHEME_SAMPLE), .Left, {13, 11, 10, 6, 2, 1, 0}) +} + +@(test) +test_translate_by_word :: proc(t: ^testing.T) { + s: State + state_init(&s, WORD_SAMPLE, false) + defer state_destroy(&s) + + expect_walk(t, &s, 0, .Word_Right, {4, 9, 12}) + expect_walk(t, &s, len(WORD_SAMPLE), .Word_Left, {9, 4, 0}) + + // From inside "bar", to the edges of that word. + s.state.selection = {5, 5} + testing.expect_value(t, edit.translate_position(&s.state, .Word_Start), 4) + testing.expect_value(t, edit.translate_position(&s.state, .Word_End), 7) +} + +@(test) +test_translate_to_bounds :: proc(t: ^testing.T) { + s: State + state_init(&s, GRAPHEME_SAMPLE, true) + defer state_destroy(&s) + + s.state.selection = {5, 5} + testing.expect_value(t, edit.translate_position(&s.state, .Start), 0) + testing.expect_value(t, edit.translate_position(&s.state, .End), len(GRAPHEME_SAMPLE)) + + // Translating past either end must clamp rather than run off the buffer. + s.state.selection = {0, 0} + testing.expect_value(t, edit.translate_position(&s.state, .Left), 0) + s.state.selection = {len(GRAPHEME_SAMPLE), len(GRAPHEME_SAMPLE)} + testing.expect_value(t, edit.translate_position(&s.state, .Right), len(GRAPHEME_SAMPLE)) +} diff --git a/tests/internal/test_128_float_conversion.odin b/tests/internal/test_128_float_conversion.odin new file mode 100644 index 000000000..6d82dd817 --- /dev/null +++ b/tests/internal/test_128_float_conversion.odin @@ -0,0 +1,48 @@ +package test_internal + +import "core:testing" + +// #force_no_inline so conversions are not folded; +// they must go through the runtime __fixunsdfti/__fixdfti +@(private="file") f64_to_u128 :: #force_no_inline proc(f: f64) -> u128 { return u128(f) } +@(private="file") f64_to_i128 :: #force_no_inline proc(f: f64) -> i128 { return i128(f) } +@(private="file") f32_to_u128 :: #force_no_inline proc(f: f32) -> u128 { return u128(f) } +@(private="file") f32_to_i128 :: #force_no_inline proc(f: f32) -> i128 { return i128(f) } + +@test +test_f64_to_u128 :: proc(t: ^testing.T) { + testing.expect_value(t, f64_to_u128(2.75), 2) // trunc toward 0 + testing.expect_value(t, f64_to_u128(0.75), 0) // < 1 + testing.expect_value(t, f64_to_u128(-3.5), 0) // negative to 0 + testing.expect_value(t, f64_to_u128(f64(1 << 51)), u128(1) << 51) // significand shifts right + testing.expect_value(t, f64_to_u128(f64(1 << 52)), u128(1) << 52) // no shift + testing.expect_value(t, f64_to_u128(f64(1 << 53)), u128(1) << 53) // significand shifts left + testing.expect_value(t, f64_to_u128(1e19), 10_000_000_000_000_000_000) + testing.expect_value(t, f64_to_u128(f64(1 << 80)), u128(1) << 80) // > max(u64) + testing.expect_value(t, f64_to_u128(2 * f64(1 << 127)), max(u128)) // out of range saturates, implementation specific +} + +@test +test_f64_to_i128 :: proc(t: ^testing.T) { + testing.expect_value(t, f64_to_i128(-2.75), -2) // trunc toward 0 + testing.expect_value(t, f64_to_i128(-0.75), 0) // |f| < 1 + testing.expect_value(t, f64_to_i128(1e19), 10_000_000_000_000_000_000) // > max(i64) + testing.expect_value(t, f64_to_i128(f64(-(1 << 80))), -(i128(1) << 80)) + testing.expect_value(t, f64_to_i128(f64(1 << 126)), i128(1) << 126) + testing.expect_value(t, f64_to_i128(f64(min(i128))), min(i128)) // exact + // out of range saturates, implementation specific + testing.expect_value(t, f64_to_i128(f64(1 << 127)), max(i128)) + testing.expect_value(t, f64_to_i128(2 * f64(1 << 127)), max(i128)) +} + +@test +test_f32_to_u128 :: proc(t: ^testing.T) { + testing.expect_value(t, f32_to_u128(2.75), 2) // trunc toward 0 + testing.expect_value(t, f32_to_u128(f32(1 << 80)), u128(1) << 80) // > max(u64) +} + +@test +test_f32_to_i128 :: proc(t: ^testing.T) { + testing.expect_value(t, f32_to_i128(-2.75), -2) // trunc toward 0 + testing.expect_value(t, f32_to_i128(f32(-(1 << 80))), -(i128(1) << 80)) +} diff --git a/tests/internal/test_constant_folding.odin b/tests/internal/test_constant_folding.odin new file mode 100644 index 000000000..016976201 --- /dev/null +++ b/tests/internal/test_constant_folding.odin @@ -0,0 +1,213 @@ +package test_internal + +import "core:testing" + +// Constant folding against the answer the backend produces. A folded constant that is merely +// wrong still compiles, so a harness comparing accept/reject sees agreement +// Every case here pairs a constant with the same expression on variables for that reason. +// +// `a &~ b` is `a & ~b`. `big_int_and_not` had three independent faults: `0 &~ y` returned `y`, +// the both-negative branch used its operands the wrong way round, and the negative-left branch +// dropped the sign of its result. + +@(test) +and_not_constant_folding_matches_runtime :: proc(t: ^testing.T) { + // the zero short-circuit: 0 &~ anything is 0 + { + a, b := 0, 3 + testing.expect_value(t, 0 &~ 3, a &~ b) + testing.expect_value(t, 0 &~ 3, 0) + } + { + a, b := 0, -3 + testing.expect_value(t, 0 &~ -3, a &~ b) + testing.expect_value(t, 0 &~ -3, 0) + } + + // negative left operand: the result must stay negative + { + a, b := -7, 3 + testing.expect_value(t, -7 &~ 3, a &~ b) + testing.expect_value(t, -7 &~ 3, -8) + } + { + a, b := -255, 5 + testing.expect_value(t, -255 &~ 5, a &~ b) + testing.expect_value(t, -255 &~ 5, -256) + } + + // both negative + { + a, b := -7, -3 + testing.expect_value(t, -7 &~ -3, a &~ b) + testing.expect_value(t, -7 &~ -3, 0) + } + { + a, b := -3, -7 + testing.expect_value(t, -3 &~ -7, a &~ b) + testing.expect_value(t, -3 &~ -7, 4) + } + + // the cases that were already correct, so a fix cannot regress them + { + a, b := 7, 3 + testing.expect_value(t, 7 &~ 3, a &~ b) + testing.expect_value(t, 7 &~ 3, 4) + } + { + a, b := 7, -3 + testing.expect_value(t, 7 &~ -3, a &~ b) + testing.expect_value(t, 7 &~ -3, 2) + } + { + a, b := 7, 0 + testing.expect_value(t, 7 &~ 0, a &~ b) + testing.expect_value(t, 7 &~ 0, 7) + } +} + +@(test) +and_not_constant_folding_every_width :: proc(t: ^testing.T) { + // the zero-left shape reaches unsigned types too + { + a, b := u8(0), u8(1) + testing.expect_value(t, u8(0) &~ u8(1), a &~ b) + testing.expect_value(t, u8(0) &~ u8(1), u8(0)) + } + { + a, b := u64(0), u64(255) + testing.expect_value(t, u64(0) &~ u64(255), a &~ b) + testing.expect_value(t, u64(0) &~ u64(255), u64(0)) + } + + // signed, at the extremes of each width + { + a, b := i8(-128), i8(1) + testing.expect_value(t, i8(-128) &~ i8(1), a &~ b) + testing.expect_value(t, i8(-128) &~ i8(1), i8(-128)) + } + { + a, b := i8(-128), i8(127) + testing.expect_value(t, i8(-128) &~ i8(127), a &~ b) + testing.expect_value(t, i8(-128) &~ i8(127), i8(-128)) + } + { + a, b := i8(-7), i8(-128) + testing.expect_value(t, i8(-7) &~ i8(-128), a &~ b) + testing.expect_value(t, i8(-7) &~ i8(-128), i8(121)) + } + { + a, b := i16(-7), i16(3) + testing.expect_value(t, i16(-7) &~ i16(3), a &~ b) + testing.expect_value(t, i16(-7) &~ i16(3), i16(-8)) + } + { + a, b := i32(-255), i32(5) + testing.expect_value(t, i32(-255) &~ i32(5), a &~ b) + testing.expect_value(t, i32(-255) &~ i32(5), i32(-256)) + } + { + a, b := i64(-7), i64(-3) + testing.expect_value(t, i64(-7) &~ i64(-3), a &~ b) + testing.expect_value(t, i64(-7) &~ i64(-3), i64(0)) + } +} + +// `&~` was the only operator found divergent; the rest of the bitwise family shares the sign +// handling and must stay agreeing. + +@(test) +bitwise_constant_folding_matches_runtime :: proc(t: ^testing.T) { + { + a, b := -7, 3 + testing.expect_value(t, -7 & 3, a & b) + testing.expect_value(t, -7 | 3, a | b) + testing.expect_value(t, -7 ~ 3, a ~ b) + } + { + a, b := -7, -3 + testing.expect_value(t, -7 & -3, a & b) + testing.expect_value(t, -7 | -3, a | b) + testing.expect_value(t, -7 ~ -3, a ~ b) + } + { + a, b := 0, -3 + testing.expect_value(t, 0 & -3, a & b) + testing.expect_value(t, 0 | -3, a | b) + testing.expect_value(t, 0 ~ -3, a ~ b) + } +// `<=` and `<` on a `bit_set` are subset and proper subset, `>=` and `>` superset. The folder +// asked `(lhs & rhs) <= lhs` where the definition is `(lhs & rhs) == lhs`, which is true for +// any operands, so `<=` folded true unconditionally; `<` compounded it by requiring `lhs < rhs` +// where it needs `lhs != rhs`. Under `when` this decides which declarations exist. +} + +@(test) +bit_set_subset_folding_matches_runtime :: proc(t: ^testing.T) { + B :: bit_set[0..<4] + + { // disjoint: neither a subset nor a superset + a, b := B{0, 3}, B{0, 1} + testing.expect_value(t, B{0, 3} <= B{0, 1}, a <= b) + testing.expect_value(t, B{0, 3} <= B{0, 1}, false) + testing.expect_value(t, B{0, 3} >= B{0, 1}, a >= b) + testing.expect_value(t, B{0, 3} >= B{0, 1}, false) + } + { // proper subset + a, b := B{0}, B{0, 1} + testing.expect_value(t, B{0} <= B{0, 1}, a <= b) + testing.expect_value(t, B{0} <= B{0, 1}, true) + testing.expect_value(t, B{0} < B{0, 1}, a < b) + testing.expect_value(t, B{0} < B{0, 1}, true) + } + { // equal: a subset but not a proper one + a, b := B{0, 1}, B{0, 1} + testing.expect_value(t, B{0, 1} <= B{0, 1}, a <= b) + testing.expect_value(t, B{0, 1} <= B{0, 1}, true) + testing.expect_value(t, B{0, 1} < B{0, 1}, a < b) + testing.expect_value(t, B{0, 1} < B{0, 1}, false) + } + { // superset + a, b := B{0, 1}, B{0} + testing.expect_value(t, B{0, 1} <= B{0}, a <= b) + testing.expect_value(t, B{0, 1} <= B{0}, false) + testing.expect_value(t, B{0, 1} > B{0}, a > b) + testing.expect_value(t, B{0, 1} > B{0}, true) + } + { // the empty set is a subset of everything, and a proper one unless both are empty + a, b := B{}, B{0} + testing.expect_value(t, B{} < B{0}, a < b) + testing.expect_value(t, B{} < B{0}, true) + } + { + a, b := B{}, B{} + testing.expect_value(t, B{} <= B{}, a <= b) + testing.expect_value(t, B{} <= B{}, true) + testing.expect_value(t, B{} < B{}, a < b) + testing.expect_value(t, B{} < B{}, false) + } + + // equality was never affected, so a fix here must not disturb it + { + a, b := B{0, 1}, B{1, 0} + testing.expect_value(t, B{0, 1} == B{1, 0}, a == b) + testing.expect_value(t, B{0, 1} == B{1, 0}, true) + testing.expect_value(t, B{0, 1} != B{0}, a != B{0}) + } +} + +// a mis-folded subset test selects the wrong `when` arm, which changes which declarations exist +@(test) +bit_set_subset_folding_selects_the_right_when_arm :: proc(t: ^testing.T) { + B :: bit_set[0..<4] + + when (B{0} < B{0, 1}) { W1 :: 1 } else { W1 :: 0 } + when (B{0, 3} <= B{0, 1}) { W2 :: 0 } else { W2 :: 1 } + when (B{0, 1} <= B{0, 1}) { W3 :: 1 } else { W3 :: 0 } + when (B{0, 1} > B{0}) { W4 :: 1 } else { W4 :: 0 } + + testing.expect_value(t, W1, 1) + testing.expect_value(t, W2, 1) + testing.expect_value(t, W3, 1) + testing.expect_value(t, W4, 1) +} \ No newline at end of file diff --git a/tests/internal/test_intrinsics_integer_to.odin b/tests/internal/test_intrinsics_integer_to.odin index 108318c9a..8e80089aa 100644 --- a/tests/internal/test_intrinsics_integer_to.odin +++ b/tests/internal/test_intrinsics_integer_to.odin @@ -12,6 +12,18 @@ example_usage :: proc(#any_int x: int) -> intrinsics.type_integer_to_unsigned(ty @test test_intrinsic_integer_to :: proc(t: ^testing.T) { + testing.expect_value(t, typeid_of(intrinsics.type_integer_to_unsigned(i8)), typeid_of(u8)) + testing.expect_value(t, typeid_of(intrinsics.type_integer_to_unsigned(i16)), typeid_of(u16)) + testing.expect_value(t, typeid_of(intrinsics.type_integer_to_unsigned(i32)), typeid_of(u32)) + testing.expect_value(t, typeid_of(intrinsics.type_integer_to_unsigned(i64)), typeid_of(u64)) + testing.expect_value(t, typeid_of(intrinsics.type_integer_to_unsigned(i128)), typeid_of(u128)) + + testing.expect_value(t, typeid_of(intrinsics.type_integer_to_signed(u8)), typeid_of(i8)) + testing.expect_value(t, typeid_of(intrinsics.type_integer_to_signed(u16)), typeid_of(i16)) + testing.expect_value(t, typeid_of(intrinsics.type_integer_to_signed(u32)), typeid_of(i32)) + testing.expect_value(t, typeid_of(intrinsics.type_integer_to_signed(u64)), typeid_of(i64)) + testing.expect_value(t, typeid_of(intrinsics.type_integer_to_signed(u128)), typeid_of(i128)) + testing.expect_value(t, typeid_of(intrinsics.type_integer_to_unsigned(i16le)), typeid_of(u16le)) testing.expect_value(t, typeid_of(intrinsics.type_integer_to_unsigned(i32le)), typeid_of(u32le)) testing.expect_value(t, typeid_of(intrinsics.type_integer_to_unsigned(i64le)), typeid_of(u64le)) diff --git a/tests/internal/test_nan_comparison.odin b/tests/internal/test_nan_comparison.odin index 5740be477..b47cc9dfc 100644 --- a/tests/internal/test_nan_comparison.odin +++ b/tests/internal/test_nan_comparison.odin @@ -89,3 +89,100 @@ compare_variable_nans_f64 :: proc(t: ^testing.T) { testing.expect_value(t, NaN > NaN, false) testing.expect_value(t, NaN >= NaN, false) } + +// A complex or quaternion compares componentwise, so a NaN in any one lane makes the whole +// comparison fail. The folded form used to disagree: `cmp_f64` is `(a>b)-(a 2 lowers to shuffles for the same reason interleave does, only arm64 could +// select the intrinsic. The round trip is asserted, not just the widths. + +@(test) +simd_deinterleave_splits_by_stride :: proc(t: ^testing.T) { + v: #simd[16]i32 + for i in 0..<16 { + v = intrinsics.simd_replace(v, i, i32(i)) + } + + a2, b2 := intrinsics.simd_deinterleave(v, 2) + testing.expect_value(t, len(a2), 8) + testing.expect_value(t, simd_extract_i32(a2, 0), 0) + testing.expect_value(t, simd_extract_i32(a2, 1), 2) + testing.expect_value(t, simd_extract_i32(b2, 0), 1) + testing.expect_value(t, simd_extract_i32(b2, 7), 15) + + a4, b4, c4, d4 := intrinsics.simd_deinterleave(v, 4) + testing.expect_value(t, len(a4), 4) + testing.expect_value(t, simd_extract_i32(a4, 0), 0) + testing.expect_value(t, simd_extract_i32(b4, 0), 1) + testing.expect_value(t, simd_extract_i32(c4, 0), 2) + testing.expect_value(t, simd_extract_i32(d4, 0), 3) + testing.expect_value(t, simd_extract_i32(a4, 3), 12) + testing.expect_value(t, simd_extract_i32(d4, 3), 15) + + // interleave is the inverse, so the pair must round trip + r := intrinsics.simd_interleave(a4, b4, c4, d4) + testing.expect_value(t, len(r), 16) + testing.expect_value(t, simd_extract_i32(r, 0), 0) + testing.expect_value(t, simd_extract_i32(r, 7), 7) + testing.expect_value(t, simd_extract_i32(r, 15), 15) +} + +simd_extract_i32 :: #force_inline proc(v: $V/#simd[$N]i32, $I: int) -> i32 { + return intrinsics.simd_extract(v, I) +} + +// `simd_shuffle` and `swizzle` size their result from the index list, which can be twice the +// operand width, so both can construct a `#simd` wider than the syntax accepts. What is pinned +// here is the boundary that must keep working: exactly SIMD_ELEMENT_COUNT_MAX lanes, and a +// `swizzle` over a plain array, which is not bound by the `#simd` limit at all. + +@(test) +simd_construction_at_the_element_count_max :: proc(t: ^testing.T) { + a: #simd[32]i32 = 1 + at_max := intrinsics.simd_shuffle(a, a, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63) + testing.expect_value(t, len(at_max), 64) + + b: #simd[8]i32 = {1, 2, 3, 4, 5, 6, 7, 8} + under := intrinsics.simd_shuffle(b, b, 0, 1, 2, 3, 8, 9, 10, 11) + testing.expect_value(t, len(under), 8) + testing.expect_value(t, simd_extract_i32(under, 4), 1) + + sw := swizzle(b, 7, 6, 5, 4, 3, 2, 1, 0) + testing.expect_value(t, len(sw), 8) + testing.expect_value(t, simd_extract_i32(sw, 0), 8) + + // a plain array is not a #simd vector, so the element-count limit must not reach it + arr: [96]i32 + arr[95] = 7 + asw := swizzle(arr, 95, 0, 1) + testing.expect_value(t, len(asw), 3) + testing.expect_value(t, asw[0], 7) + + d, e: #simd[32]i32 + ilv := intrinsics.simd_interleave(d, e) + testing.expect_value(t, len(ilv), 64) +} + +// A swizzle may repeat indices to produce a result wider than its operand. `core:crypto` +// depends on it -- chacha20's simd256 path doubles a 4-lane state with eight indices -- so +// there is deliberately no upper bound on the index count. + +@(test) +swizzle_may_widen_its_operand :: proc(t: ^testing.T) { + v: [2]f32 = {1, 2} + testing.expect_value(t, swizzle(v, 0, 0, 0, 0), [4]f32{1, 1, 1, 1}) + testing.expect_value(t, swizzle(v, 0, 1, 0, 1), [4]f32{1, 2, 1, 2}) + + a: [3]i32 = {7, 8, 9} + testing.expect_value(t, swizzle(a, 2, 2, 2, 2, 2, 2), [6]i32{9, 9, 9, 9, 9, 9}) + + // the same shape on a #simd vector: four lanes widened to eight + q: #simd[4]u32 = {1, 2, 3, 4} + w := swizzle(q, 0, 1, 2, 3, 0, 1, 2, 3) + testing.expect_value(t, len(w), 8) + testing.expect_value(t, intrinsics.simd_extract(w, 4), u32(1)) + testing.expect_value(t, intrinsics.simd_extract(w, 7), u32(4)) +} + +// A pairwise operation folds adjacent lanes within each operand, so it needs an even lane +// count -- `base:intrinsics` declares `LANES % 2 == 0`. At one lane it has nothing to pair +// with and silently switches to combining the two operands instead, which is why that width +// is rejected rather than defined. + +@(test) +simd_pairwise_folds_adjacent_lanes :: proc(t: ^testing.T) { + a: #simd[2]i32 = {10, 3} + b: #simd[2]i32 = {20, 4} + + add := intrinsics.simd_pairwise_add(a, b) + testing.expect_value(t, intrinsics.simd_extract(add, 0), i32(13)) + testing.expect_value(t, intrinsics.simd_extract(add, 1), i32(24)) + + sub := intrinsics.simd_pairwise_sub(a, b) + testing.expect_value(t, intrinsics.simd_extract(sub, 0), i32(7)) + testing.expect_value(t, intrinsics.simd_extract(sub, 1), i32(16)) + + c: #simd[4]f32 = {1, 2, 3, 4} + d: #simd[4]f32 = {5, 6, 7, 8} + f := intrinsics.simd_pairwise_add(c, d) + testing.expect_value(t, intrinsics.simd_extract(f, 0), f32(3)) + testing.expect_value(t, intrinsics.simd_extract(f, 1), f32(7)) + + // the other six builtins sharing this arm take a single lane, and must stay unaffected + o: #simd[1]i32 = 7 + p: #simd[1]i32 = 2 + testing.expect_value(t, intrinsics.simd_extract(intrinsics.simd_add(o, p), 0), i32(9)) + testing.expect_value(t, intrinsics.simd_extract(intrinsics.simd_max(o, p), 0), i32(7)) +} + +// `#simd[?]T{...}` The inferred form must agree with the explicit one. + +@(test) +simd_inferred_length_from_a_compound_literal :: proc(t: ^testing.T) { + a := #simd[?]i32{1, 2, 3, 4} + testing.expect_value(t, len(a), 4) + testing.expect_value(t, intrinsics.type_is_simd_vector(type_of(a)), true) + testing.expect_value(t, typeid_of(type_of(a)), typeid_of(#simd[4]i32)) + testing.expect_value(t, intrinsics.simd_extract(a, 0), i32(1)) + testing.expect_value(t, intrinsics.simd_extract(a, 3), i32(4)) + + b := #simd[?]f32{1.5, 2.5} + testing.expect_value(t, len(b), 2) + testing.expect_value(t, typeid_of(type_of(b)), typeid_of(#simd[2]f32)) + testing.expect_value(t, intrinsics.simd_extract(b, 1), f32(2.5)) + + // the inferred vector must work with the intrinsics, which is the point of the tag + testing.expect_value(t, intrinsics.simd_extract(intrinsics.simd_add(a, a), 3), i32(8)) + + // a plain `[?]` array is unaffected + c := [?]i32{1, 2, 3} + testing.expect_value(t, len(c), 3) + testing.expect_value(t, intrinsics.type_is_array(type_of(c)), true) +} + +// `core:simd` aliased `pairwise_sub` to `simd_pairwise_add`, so it silently added +@(test) +simd_pairwise_aliases_are_distinct :: proc(t: ^testing.T) { + a: #simd[2]i32 = {10, 3} + b: #simd[2]i32 = {20, 4} + + add := simd.pairwise_add(a, b) + sub := simd.pairwise_sub(a, b) + testing.expect_value(t, intrinsics.simd_extract(add, 0), i32(13)) + testing.expect_value(t, intrinsics.simd_extract(sub, 0), i32(7)) + testing.expect_value(t, intrinsics.simd_extract(sub, 1), i32(16)) +} + +// The rotate offset is declared `$offset: int`; the checker demanded `i64`, so the declared +// spelling did not compile. Other integer types stay rejected -- the declaration is not #any_int. + +@(test) +simd_lanes_rotate_offset_is_int :: proc(t: ^testing.T) { + rot :: proc(v: #simd[4]u32, $offset: int) -> #simd[4]u32 { + return intrinsics.simd_lanes_rotate_right(v, offset) + } + + v: #simd[4]u32 = {0, 1, 2, 3} + r := rot(v, 1) + testing.expect_value(t, intrinsics.simd_extract(r, 0), u32(3)) + testing.expect_value(t, intrinsics.simd_extract(r, 1), u32(0)) + + l := intrinsics.simd_lanes_rotate_left(v, 1) + testing.expect_value(t, intrinsics.simd_extract(l, 0), u32(1)) + testing.expect_value(t, intrinsics.simd_extract(l, 3), u32(0)) +} + +// `simd_extract` / `simd_replace` declare a plain `idx: uint`, so a runtime index is allowed and +// lowers to a dynamic extractelement. Bounds are enforced only where the index is constant. + +@(test) +simd_extract_accepts_a_runtime_index :: proc(t: ^testing.T) { + v: #simd[4]u32 = {10, 20, 30, 40} + i := 2 + testing.expect_value(t, intrinsics.simd_extract(v, i), u32(30)) + + w := intrinsics.simd_replace(v, i, u32(99)) + testing.expect_value(t, intrinsics.simd_extract(w, 2), u32(99)) + testing.expect_value(t, intrinsics.simd_extract(w, 0), u32(10)) +} diff --git a/tests/internal/test_string16.odin b/tests/internal/test_string16.odin new file mode 100644 index 000000000..c67739168 --- /dev/null +++ b/tests/internal/test_string16.odin @@ -0,0 +1,136 @@ +package test_internal + +import "core:testing" + +// The compiler folds constant `string16` operations at check time and emits the same +// operations at runtime. Every case below computes a value both ways and compares them, +// because the interesting failures are the ones where the two disagree silently. + +// Related previous issue #6101 + +@(private="file") +opaque :: proc(v: $T) -> T { return v } + +@(private="file") +Ascii : string16 : "hello" // 5 bytes utf-8, 5 units utf-16 +@(private="file") +Latin : string16 : "héllo" // 6 bytes utf-8, 5 units utf-16 +@(private="file") +Cjk : string16 : "日本語" // 9 bytes utf-8, 3 units utf-16 +@(private="file") +NonBmp : string16 : "\U0001F63A" // 4 bytes utf-8, 2 units utf-16 (surrogate pair) +@(private="file") +Mixed : string16 : "a日\U0001F63A" // 8 bytes utf-8, 4 units utf-16 +@(private="file") +Empty : string16 : "" + +@test +string16_constant_length :: proc(t: ^testing.T) { + // lengths are in utf-16 code units, not utf-8 bytes + testing.expect_value(t, len(Ascii), 5) + testing.expect_value(t, len(Latin), 5) + testing.expect_value(t, len(Cjk), 3) + testing.expect_value(t, len(NonBmp), 2) + testing.expect_value(t, len(Mixed), 4) + testing.expect_value(t, len(Empty), 0) + + // the constant length must match the length of the same value at runtime + testing.expect_value(t, len(Ascii), len(opaque(Ascii))) + testing.expect_value(t, len(Latin), len(opaque(Latin))) + testing.expect_value(t, len(Cjk), len(opaque(Cjk))) + testing.expect_value(t, len(NonBmp), len(opaque(NonBmp))) + testing.expect_value(t, len(Mixed), len(opaque(Mixed))) + testing.expect_value(t, len(Empty), len(opaque(Empty))) +} + +@test +string16_constant_index :: proc(t: ^testing.T) { + testing.expect_value(t, Latin[0], 'h') + testing.expect_value(t, Latin[1], 0x00E9) // é stays one unit + testing.expect_value(t, Cjk[0], 0x65E5) + testing.expect_value(t, Cjk[2], 0x8A9E) + testing.expect_value(t, NonBmp[0], 0xD83D) // high surrogate + testing.expect_value(t, NonBmp[1], 0xDE3A) // low surrogate + + // each constant-folded unit must match the same unit read at runtime + m := opaque(Mixed) + testing.expect_value(t, Mixed[0], m[0]) + testing.expect_value(t, Mixed[1], m[1]) + testing.expect_value(t, Mixed[2], m[2]) + testing.expect_value(t, Mixed[3], m[3]) +} + +@test +string16_constant_slice :: proc(t: ^testing.T) { + A :: Latin[0:2] + B :: Cjk[1:3] + C :: NonBmp[0:2] + D :: Mixed[1:2] + E :: Ascii[2:2] + + testing.expect_value(t, len(A), 2) + testing.expect_value(t, len(B), 2) + testing.expect_value(t, len(C), 2) + testing.expect_value(t, len(D), 1) + testing.expect_value(t, len(E), 0) + + testing.expect_value(t, A[0], 'h') + testing.expect_value(t, A[1], 0x00E9) + testing.expect_value(t, B[0], 0x672C) + testing.expect_value(t, C[1], 0xDE3A) + testing.expect_value(t, D[0], 0x65E5) + + // open-ended and full slices + F :: Cjk[:] + G :: Cjk[1:] + H :: Cjk[:2] + testing.expect_value(t, len(F), 3) + testing.expect_value(t, len(G), 2) + testing.expect_value(t, len(H), 2) + + // folded slice must equal the same slice taken at runtime + l := opaque(Latin) + rt := l[0:2] + testing.expect_value(t, len(A), len(rt)) + testing.expect_value(t, A[0], rt[0]) + testing.expect_value(t, A[1], rt[1]) +} + +@test +string16_from_cast_and_assignment :: proc(t: ^testing.T) { + // the three ways a constant acquires a string16 type must agree + Typed : string16 : "日本語" + Casted :: string16("日本語") + testing.expect_value(t, len(Typed), len(Casted)) + testing.expect_value(t, Typed[0], Casted[0]) + testing.expect_value(t, Typed[2], Casted[2]) + + assigned: string16 = "日本語" + testing.expect_value(t, len(assigned), len(Typed)) + testing.expect_value(t, assigned[0], Typed[0]) +} + +@test +string16_underlying_units :: proc(t: ^testing.T) { + // transmute exposes the utf-16 code units directly + u := transmute([]u16)opaque(NonBmp) + testing.expect_value(t, len(u), 2) + testing.expect_value(t, u[0], 0xD83D) + testing.expect_value(t, u[1], 0xDE3A) + + c := transmute([]u16)opaque(Cjk) + testing.expect_value(t, len(c), 3) + testing.expect_value(t, c[0], 0x65E5) + + // a utf-8 string of the same text keeps its byte length + testing.expect_value(t, len("日本語"), 9) +} + +@test +string16_comparison :: proc(t: ^testing.T) { + X : string16 : "日本語" + testing.expect(t, X == Cjk) + testing.expect(t, X != Ascii) + testing.expect(t, opaque(X) == Cjk) + testing.expect(t, Empty == "") +} diff --git a/tests/issues/run.bat b/tests/issues/run.bat index 4bd301ac1..d01c85e14 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -29,7 +29,6 @@ set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style -ignore-unused ..\..\..\odin build ..\test_issue_5573.odin %COMMON% 2>&1 | find /c "Error:" | findstr /x "2" || exit /b ..\..\..\odin test ..\test_issue_5699.odin %COMMON% || exit /b ..\..\..\odin test ..\test_issue_6068.odin %COMMON% || exit /b -..\..\..\odin test ..\test_issue_6101.odin %COMMON% || exit /b ..\..\..\odin test ..\test_issue_6165.odin %COMMON% || exit /b ..\..\..\odin build ..\test_issue_6240.odin %COMMON% 2>&1 | find /c "Error:" | findstr /x "3" || exit /b ..\..\..\odin build ..\test_issue_6401.odin %COMMON% 2>&1 | find /c "Error:" | findstr /x "3" || exit /b @@ -44,6 +43,8 @@ set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style -ignore-unused ..\..\..\odin check ..\test_issue_7012.odin -no-entry-point %COMMON% || exit /b ..\..\..\odin build ..\test_issue_7037.odin %COMMON% -o:none || exit /b ..\..\..\odin build ..\test_issue_7188.odin %COMMON% || exit /b +clang -c ..\test_issue_sysv_abi.c -o test_issue_sysv_abi_c.o || exit /b +..\..\..\odin test ..\test_issue_sysv_abi.odin %COMMON% || exit /b ..\..\..\odin build ..\test_issue_7073-1.odin %COMMON% 2>&1 | find /c "Error:" | findstr /x "2" || exit /b @echo off diff --git a/tests/issues/run.sh b/tests/issues/run.sh index 9c3cfecc3..393bd1eda 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -41,7 +41,6 @@ else fi $ODIN test ../test_issue_5699.odin $COMMON $ODIN test ../test_issue_6068.odin $COMMON -$ODIN test ../test_issue_6101.odin $COMMON $ODIN test ../test_issue_6165.odin $COMMON $ODIN test ../test_issue_6344.odin $COMMON $ODIN test ../test_issue_6344.odin $COMMON -o:speed @@ -108,9 +107,19 @@ else exit 1 fi +if [[ $($ODIN check ../test_issue_7304.odin -no-entry-point $COMMON_CHECK 2>&1 >/dev/null | grep -c "9223372036854775808 is not representable by int") -eq 1 ]]; then + echo "SUCCESSFUL 1/1" +else + echo "SUCCESSFUL 0/1" + exit 1 +fi + clang -c ../test_issue_7010.c -o test_issue_7010_c.o $ODIN test ../test_issue_7010.odin $COMMON +clang -c ../test_issue_sysv_abi.c -o test_issue_sysv_abi_c.o +$ODIN test ../test_issue_sysv_abi.odin $COMMON + clang -c ../test_issue_6809_6816.c -o test_issue_6809_6816_c.o -O3 $ODIN test ../test_issue_6809_6816.odin -o:speed $COMMON diff --git a/tests/issues/test_issue_6101.odin b/tests/issues/test_issue_6101.odin deleted file mode 100644 index 9f24ade52..000000000 --- a/tests/issues/test_issue_6101.odin +++ /dev/null @@ -1,23 +0,0 @@ -// Tests issue #6101 https://github.com/odin-lang/Odin/issues/6101 -package test_issues - -import "core:testing" - -@(test) -test_issue_6101_bmp :: proc(t: ^testing.T) { - s := string16("\u732b") - testing.expect_value(t, len(s), 1) - - u := transmute([]u16)s - testing.expect_value(t, u[0], 0x732b) -} - -@(test) -test_issue_6101_non_bmp :: proc(t: ^testing.T) { - s := string16("\U0001F63A") - testing.expect_value(t, len(s), 2) - - u := transmute([]u16)s - testing.expect_value(t, u[0], 0xD83D) - testing.expect_value(t, u[1], 0xDE3A) -} diff --git a/tests/issues/test_issue_7304.odin b/tests/issues/test_issue_7304.odin new file mode 100644 index 000000000..c96a0bae2 --- /dev/null +++ b/tests/issues/test_issue_7304.odin @@ -0,0 +1,4 @@ +// Tests issue #7304 https://github.com/odin-lang/Odin/issues/7304 +package test_issues + +Bad_Bit_Set :: bit_set[-1 ..< 9223372036854775808] diff --git a/tests/issues/test_issue_sysv_abi.c b/tests/issues/test_issue_sysv_abi.c new file mode 100644 index 000000000..933775f94 --- /dev/null +++ b/tests/issues/test_issue_sysv_abi.c @@ -0,0 +1,26 @@ +// Support file for test_issue_sysv_abi.odin +// +// Each callee returns its second argument, so the value that comes back says +// where the struct before it went. If the aggregate consumes the wrong number or +// the wrong file of registers, the following argument is read from the wrong +// place deterministically rather than by scratch-register coincidence. +// + +#include + +typedef struct { int64_t a; float b; } Pad_Int_Float; +typedef struct { float a; double b; } Pad_Float_Double; +typedef struct { float a, b; } No_Pad; +typedef struct { struct { float x; } a; double b; } Nested; +typedef union { float x; float y; } Union_Float; +typedef struct { union { float x; float y; } u; double b; } Union_In_Struct; + +double c_pad_int_float (Pad_Int_Float s, double next) { (void)s; return next; } +double c_pad_float_double(Pad_Float_Double s, double next) { (void)s; return next; } +double c_no_pad (No_Pad s, double next) { (void)s; return next; } +double c_nested (Nested s, double next) { (void)s; return next; } +double c_union_float (Union_Float s, double next) { (void)s; return next; } +double c_union_in_struct (Union_In_Struct s, double next) { (void)s; return next; } + +Pad_Int_Float c_make_pad_int_float(void) { Pad_Int_Float s = {11, 2.5f}; return s; } +Union_Float c_make_union_float(void) { Union_Float s; s.x = 2.5f; return s; } diff --git a/tests/issues/test_issue_sysv_abi.odin b/tests/issues/test_issue_sysv_abi.odin new file mode 100644 index 000000000..1be3e70c3 --- /dev/null +++ b/tests/issues/test_issue_sysv_abi.odin @@ -0,0 +1,64 @@ +// The ABI classifiers ran over the lowered type, where Odin has already turned +// padding into an explicit `[N x i8]` member and a `#raw_union` into an opaque +// integer. SysV contributes no class for padding and merges a union's members, +// and AAPCS64 counts a union as a Composite Type. So a struct with an `f32` +// alone in an eightbyte went to an integer register where C uses SSE, and a +// union of floats never reached a floating-point register at all. +// +// Being an ABI guarantee, must be cross-checked against a c compiler +package test_issues + +import "core:testing" + +Pad_Int_Float :: struct { a: i64, b: f32 } // f32 alone in eightbyte 1 +Pad_Float_Double :: struct { a: f32, b: f64 } // f32 alone in eightbyte 0 +No_Pad :: struct { a: f32, b: f32 } // fills its eightbyte exactly +Nested :: struct { a: struct{ x: f32 }, b: f64 } +Union_Float :: struct #raw_union { x: f32, y: f32 } +Union_In_Struct :: struct { u: Union_Float, b: f64 } + +foreign import lib "build/test_issue_sysv_abi_c.o" + +@(default_calling_convention="c") +foreign lib { + c_pad_int_float :: proc(s: Pad_Int_Float, next: f64) -> f64 --- + c_pad_float_double :: proc(s: Pad_Float_Double, next: f64) -> f64 --- + c_no_pad :: proc(s: No_Pad, next: f64) -> f64 --- + c_nested :: proc(s: Nested, next: f64) -> f64 --- + c_union_float :: proc(s: Union_Float, next: f64) -> f64 --- + c_union_in_struct :: proc(s: Union_In_Struct, next: f64) -> f64 --- + + c_make_pad_int_float :: proc() -> Pad_Int_Float --- + c_make_union_float :: proc() -> Union_Float --- +} + +// The control. It has no padding and no union, so it was correct before the fix +// and must stay correct. Without it, "padding is misclassified" and "f32 pairs +// are broken" would look the same. +@(test) +test_no_padding_control :: proc(t: ^testing.T) { + testing.expect_value(t, c_no_pad(No_Pad{1, 3.5}, 7), f64(7)) +} + +@(test) +test_padded_struct_arguments :: proc(t: ^testing.T) { + testing.expect_value(t, c_pad_int_float(Pad_Int_Float{1, 3.5}, 7), f64(7)) + testing.expect_value(t, c_pad_float_double(Pad_Float_Double{3.5, 2}, 7), f64(7)) + testing.expect_value(t, c_nested(Nested{{3.5}, 2}, 7), f64(7)) +} + +@(test) +test_raw_union_arguments :: proc(t: ^testing.T) { + testing.expect_value(t, c_union_float(Union_Float{x = 3.5}, 7), f64(7)) + testing.expect_value(t, c_union_in_struct(Union_In_Struct{Union_Float{x = 3.5}, 2}, 7), f64(7)) +} + +@(test) +test_returns :: proc(t: ^testing.T) { + s := c_make_pad_int_float() + testing.expect_value(t, s.a, i64(11)) + testing.expect_value(t, s.b, f32(2.5)) + + u := c_make_union_float() + testing.expect_value(t, u.x, f32(2.5)) +} diff --git a/vendor/box3d/box3d.odin b/vendor/box3d/box3d.odin index 25be6ebad..a7de1325e 100644 --- a/vendor/box3d/box3d.odin +++ b/vendor/box3d/box3d.odin @@ -11,16 +11,15 @@ BOX3D_SHARED :: #config(BOX3D_SHARED, false) @(private) LIB_PATH :: ( - "lib/linux-amd64/libbox3d.a" when ODIN_OS == .Linux && ODIN_ARCH == .amd64 && !BOX3D_SHARED + "lib/box3d_wasm.o" when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 + else "lib/linux-amd64/libbox3d.a" when ODIN_OS == .Linux && ODIN_ARCH == .amd64 && !BOX3D_SHARED else "lib/linux-arm64/libbox3d.a" when ODIN_OS == .Linux && ODIN_ARCH == .arm64 && !BOX3D_SHARED else "lib/darwin/libbox3d.a" when ODIN_OS == .Darwin && (ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64) && !BOX3D_SHARED + else "lib/box3d.lib" when ODIN_OS == .Windows else "" ) -when ODIN_OS == .Windows { - @(export) - foreign import lib "lib/box3d.lib" -} else when LIB_PATH != "" { +when LIB_PATH != "" { when !#exists(LIB_PATH) { #panic("Could not find the compiled Box3D library at \"" + LIB_PATH + "\", it can be compiled by running `\"" + ODIN_ROOT + "vendor/box3d/src/build.sh\"`") } diff --git a/vendor/box3d/box3d_wasm.odin b/vendor/box3d/box3d_wasm.odin new file mode 100644 index 000000000..bb61e0a3d --- /dev/null +++ b/vendor/box3d/box3d_wasm.odin @@ -0,0 +1,4 @@ +#+build wasm32, wasm64p32 +package vendor_box3d + +@(require) import _ "vendor:libc-shim" diff --git a/vendor/box3d/lib/box3d_wasm.o b/vendor/box3d/lib/box3d_wasm.o new file mode 100644 index 000000000..b84ce8e8b --- /dev/null +++ b/vendor/box3d/lib/box3d_wasm.o @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d03c8fda79eed1028b9ad4788c9361d78f17717826b98616a70694806507c411 +size 1367290 diff --git a/vendor/box3d/src/build.sh b/vendor/box3d/src/build.sh index 0513d8ea5..b116d1672 100755 --- a/vendor/box3d/src/build.sh +++ b/vendor/box3d/src/build.sh @@ -5,6 +5,8 @@ cc=${CC:-cc} ar=${AR:-ar} ranlib=${RANLIB:-ranlib} lipo=${LIPO:-lipo} +wasm_cc=${WASM_CC:-clang} +wasm_ld=${WASM_LD:-wasm-ld} ODIN_ROOT=${ODIN_ROOT:-$(cd "$(dirname "$0")/../../.." && pwd)} cd "$ODIN_ROOT/vendor/box3d/src" || exit 1 @@ -87,3 +89,25 @@ Linux) exit 1 ;; esac + +echo "Building Box3D for wasm32" +mkdir -p build/wasm +for src in src/*.c; do + obj="build/wasm/$(basename "${src%.c}.o")" + "$wasm_cc" -c -O3 -std=gnu17 --target=wasm32 \ + --sysroot="$ODIN_ROOT/vendor/libc-shim" \ + -Iinclude \ + -include wasm_compat.h \ + -DBOX3D_DISABLE_SIMD \ + -DNDEBUG \ + "$src" -o "$obj" +done +"$wasm_cc" -c -O3 -std=gnu17 --target=wasm32 \ + --sysroot="$ODIN_ROOT/vendor/libc-shim" \ + -Iinclude \ + -include wasm_compat.h \ + -DBOX3D_DISABLE_SIMD \ + -DNDEBUG \ + wasm_compat.c -o build/wasm/wasm_compat.o +"$wasm_ld" -r -o ../lib/box3d_wasm.o build/wasm/*.o +rm -rf build/wasm diff --git a/vendor/box3d/src/wasm_compat.c b/vendor/box3d/src/wasm_compat.c new file mode 100644 index 000000000..c9c169c1d --- /dev/null +++ b/vendor/box3d/src/wasm_compat.c @@ -0,0 +1,8 @@ +#include + +int fscanf(FILE* restrict stream, const char* restrict format, ...) +{ + (void)stream; + (void)format; + return -1; +} diff --git a/vendor/box3d/src/wasm_compat.h b/vendor/box3d/src/wasm_compat.h new file mode 100644 index 000000000..60073a58b --- /dev/null +++ b/vendor/box3d/src/wasm_compat.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#ifndef PRIx64 +#define PRIx64 "llx" +#endif + +#ifndef PRIu64 +#define PRIu64 "llu" +#endif + +int fscanf(FILE* restrict stream, const char* restrict format, ...);