mirror of
https://github.com/odin-lang/Odin.git
synced 2026-06-19 00:31:13 +00:00
Merge branch 'master' into vendor/curl
This commit is contained in:
17
.github/workflows/ci.yml
vendored
17
.github/workflows/ci.yml
vendored
@@ -74,43 +74,44 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# MacOS 13 runs on Intel, 14 runs on ARM
|
||||
os: [macos-14, ubuntu-latest]
|
||||
os: [macos-15-intel, macos-latest, ubuntu-latest, ubuntu-24.04-arm]
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel') || (matrix.os == 'ubuntu-latest' && 'Ubuntu') }} Build, Check, and Test
|
||||
name: ${{ matrix.os == 'macos-latest' && 'MacOS ARM' || (matrix.os == 'macos-15-intel' && 'MacOS Intel') || (matrix.os == 'ubuntu-latest' && 'Ubuntu') || (matrix.os == 'ubuntu-24.04-arm' && 'Ubuntu ARM') }} Build, Check, and Test
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Download LLVM (MacOS Intel)
|
||||
if: matrix.os == 'macos-13'
|
||||
if: matrix.os == 'macos-15-intel'
|
||||
run: |
|
||||
brew update
|
||||
brew install llvm@20 lua@5.4 lld
|
||||
echo "$(brew --prefix llvm@20)/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Download LLVM (MacOS ARM)
|
||||
if: matrix.os == 'macos-14'
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: |
|
||||
brew update
|
||||
brew install llvm@20 wasmtime lua@5.4 lld
|
||||
echo "$(brew --prefix llvm@20)/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Download LLVM (Ubuntu)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
if: matrix.os == 'ubuntu-latest' || matrix.os == 'ubuntu-24.04-arm'
|
||||
run: |
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 20
|
||||
echo "/usr/lib/llvm-20/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Build Odin
|
||||
run: ./build_odin.sh release
|
||||
- name: Odin version
|
||||
run: ./odin version
|
||||
- name: Odin report
|
||||
run: ./odin report
|
||||
- name: Get needed vendor libs
|
||||
if: matrix.os == 'ubuntu-24.04-arm'
|
||||
run: sudo apt-get install -y liblua5.4-dev
|
||||
- name: Compile needed Vendor
|
||||
run: |
|
||||
make -C vendor/stb/src
|
||||
@@ -143,7 +144,7 @@ jobs:
|
||||
run: |
|
||||
./odin build examples/demo -target:wasi_wasm32 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -out:demo
|
||||
wasmtime ./demo.wasm
|
||||
if: matrix.os == 'macos-14'
|
||||
if: matrix.os == 'macos-latest'
|
||||
|
||||
- name: Check benchmarks
|
||||
run: ./odin check tests/benchmark -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point
|
||||
|
||||
4
.github/workflows/nightly.yml
vendored
4
.github/workflows/nightly.yml
vendored
@@ -124,7 +124,7 @@ jobs:
|
||||
build_macos:
|
||||
name: MacOS Build
|
||||
if: github.repository == 'odin-lang/Odin'
|
||||
runs-on: macos-14 # Intel machine
|
||||
runs-on: macos-15-intel
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Download LLVM and setup PATH
|
||||
@@ -163,7 +163,7 @@ jobs:
|
||||
build_macos_arm:
|
||||
name: MacOS ARM Build
|
||||
if: github.repository == 'odin-lang/Odin'
|
||||
runs-on: macos-14 # ARM machine
|
||||
runs-on: macos-latest # ARM machine
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Download LLVM and setup PATH
|
||||
|
||||
37
LICENSE
37
LICENSE
@@ -1,26 +1,17 @@
|
||||
Copyright (c) 2016-2024 Ginger Bill. All rights reserved.
|
||||
Copyright (c) 2016-2025 Ginger Bill. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
@@ -215,7 +215,8 @@ type_polymorphic_record_parameter_value :: proc($T: typeid, index: int) -> $V --
|
||||
type_is_specialized_polymorphic_record :: proc($T: typeid) -> bool ---
|
||||
type_is_unspecialized_polymorphic_record :: proc($T: typeid) -> bool ---
|
||||
|
||||
type_is_subtype_of :: proc($T, $U: typeid) -> bool ---
|
||||
type_is_subtype_of :: proc($T, $U: typeid) -> bool ---
|
||||
type_is_superset_of :: proc($Super, $Sub: typeid) -> bool ---
|
||||
|
||||
type_field_index_of :: proc($T: typeid, $name: string) -> uintptr ---
|
||||
|
||||
|
||||
@@ -118,10 +118,10 @@ Type_Info_Parameters :: struct { // Only used for procedures parameters and resu
|
||||
|
||||
Type_Info_Struct_Flags :: distinct bit_set[Type_Info_Struct_Flag; u8]
|
||||
Type_Info_Struct_Flag :: enum u8 {
|
||||
packed = 0,
|
||||
raw_union = 1,
|
||||
_ = 2,
|
||||
align = 3,
|
||||
packed = 0,
|
||||
raw_union = 1,
|
||||
all_or_none = 2,
|
||||
align = 3,
|
||||
}
|
||||
|
||||
Type_Info_Struct :: struct {
|
||||
|
||||
@@ -501,6 +501,121 @@ append_soa :: proc{
|
||||
}
|
||||
|
||||
|
||||
// `append_nothing_soa` appends an empty value to a dynamic SOA array. It returns `1, nil` if successful, and `0, err` when it was not possible,
|
||||
// whatever `err` happens to be.
|
||||
@builtin
|
||||
append_nothing_soa :: proc(array: ^$T/#soa[dynamic]$E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
if array == nil {
|
||||
return 0, nil
|
||||
}
|
||||
prev_len := len(array)
|
||||
resize_soa(array, len(array)+1, loc) or_return
|
||||
return len(array)-prev_len, nil
|
||||
}
|
||||
|
||||
|
||||
// `inject_at_elem_soa` injects an element in a dynamic SOA array at a specified index and moves the previous elements after that index "across"
|
||||
@builtin
|
||||
inject_at_elem_soa :: proc(array: ^$T/#soa[dynamic]$E, #any_int index: int, #no_broadcast arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
|
||||
when !ODIN_NO_BOUNDS_CHECK {
|
||||
ensure(index >= 0, "Index must be positive.", loc)
|
||||
}
|
||||
if array == nil {
|
||||
return
|
||||
}
|
||||
n := max(len(array), index)
|
||||
m :: 1
|
||||
new_len := n + m
|
||||
|
||||
resize_soa(array, new_len, loc) or_return
|
||||
|
||||
when size_of(E) != 0 {
|
||||
ti := type_info_base(type_info_of(typeid_of(T)))
|
||||
si := &ti.variant.(Type_Info_Struct)
|
||||
|
||||
field_count := len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)
|
||||
|
||||
item_offset := 0
|
||||
|
||||
arg_copy := arg
|
||||
arg_ptr := &arg_copy
|
||||
|
||||
for i in 0..<field_count {
|
||||
data := (^uintptr)(uintptr(array) + uintptr(si.offsets[i]))^
|
||||
type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
|
||||
item_offset = align_forward_int(item_offset, type.align)
|
||||
|
||||
src := data + uintptr(index * type.size)
|
||||
dst := data + uintptr((index + m) * type.size)
|
||||
mem_copy(rawptr(dst), rawptr(src), (n - index) * type.size)
|
||||
|
||||
mem_copy(rawptr(src), rawptr(uintptr(arg_ptr) + uintptr(item_offset)), type.size)
|
||||
|
||||
item_offset += type.size
|
||||
}
|
||||
}
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// `inject_at_elems_soa` injects multiple elements in a dynamic SOA array at a specified index and moves the previous elements after that index "across"
|
||||
@builtin
|
||||
inject_at_elems_soa :: proc(array: ^$T/#soa[dynamic]$E, #any_int index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
|
||||
when !ODIN_NO_BOUNDS_CHECK {
|
||||
ensure(index >= 0, "Index must be positive.", loc)
|
||||
}
|
||||
if array == nil {
|
||||
return
|
||||
}
|
||||
if len(args) == 0 {
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
n := max(len(array), index)
|
||||
m := len(args)
|
||||
new_len := n + m
|
||||
|
||||
resize_soa(array, new_len, loc) or_return
|
||||
|
||||
when size_of(E) != 0 {
|
||||
ti := type_info_base(type_info_of(typeid_of(T)))
|
||||
si := &ti.variant.(Type_Info_Struct)
|
||||
|
||||
field_count := len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)
|
||||
|
||||
item_offset := 0
|
||||
|
||||
args_ptr := &args[0]
|
||||
|
||||
for i in 0..<field_count {
|
||||
data := (^uintptr)(uintptr(array) + uintptr(si.offsets[i]))^
|
||||
type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
|
||||
item_offset = align_forward_int(item_offset, type.align)
|
||||
|
||||
src := data + uintptr(index * type.size)
|
||||
dst := data + uintptr((index + m) * type.size)
|
||||
mem_copy(rawptr(dst), rawptr(src), (n - index) * type.size)
|
||||
|
||||
for j in 0..<len(args) {
|
||||
d := rawptr(src + uintptr(j*type.size))
|
||||
s := rawptr(uintptr(args_ptr) + uintptr(item_offset) + uintptr(j*size_of(E)))
|
||||
mem_copy(d, s, type.size)
|
||||
}
|
||||
|
||||
item_offset += type.size
|
||||
}
|
||||
}
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// `inject_at_soa` injects something into a dynamic SOA array at a specified index and moves the previous elements after that index "across"
|
||||
@builtin inject_at_soa :: proc{inject_at_elem_soa, inject_at_elems_soa}
|
||||
|
||||
|
||||
delete_soa_slice :: proc(array: $T/#soa[]$E, allocator := context.allocator, loc := #caller_location) -> Allocator_Error {
|
||||
field_count :: len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)
|
||||
when field_count != 0 {
|
||||
|
||||
@@ -6,8 +6,6 @@ _ :: intrinsics
|
||||
// High performance, cache-friendly, open-addressed Robin Hood hashing hash map
|
||||
// data structure with various optimizations for Odin.
|
||||
//
|
||||
// Copyright 2022 (c) Dale Weiler
|
||||
//
|
||||
// The core of the hash map data structure is the Raw_Map struct which is a
|
||||
// type-erased representation of the map. This type-erased representation is
|
||||
// used in two ways: static and dynamic. When static type information is known,
|
||||
|
||||
@@ -2,6 +2,20 @@ package runtime
|
||||
|
||||
_OS_Errno :: distinct int
|
||||
|
||||
HAS_RAND_BYTES :: _HAS_RAND_BYTES
|
||||
|
||||
stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
|
||||
return _stderr_write(data)
|
||||
}
|
||||
|
||||
rand_bytes :: proc "contextless" (dst: []byte) {
|
||||
when HAS_RAND_BYTES {
|
||||
_rand_bytes(dst)
|
||||
} else {
|
||||
panic_contextless("base/runtime: no runtime entropy source")
|
||||
}
|
||||
}
|
||||
|
||||
exit :: proc "contextless" (code: int) -> ! {
|
||||
_exit(code)
|
||||
}
|
||||
@@ -4,6 +4,8 @@ package runtime
|
||||
|
||||
foreign import libc "system:c"
|
||||
|
||||
_HAS_RAND_BYTES :: true
|
||||
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
@(link_name="write")
|
||||
@@ -14,6 +16,8 @@ foreign libc {
|
||||
} else {
|
||||
__error :: proc() -> ^i32 ---
|
||||
}
|
||||
|
||||
arc4random_buf :: proc(buf: [^]byte, nbytes: uint) ---
|
||||
}
|
||||
|
||||
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
|
||||
@@ -24,3 +28,15 @@ _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
|
||||
}
|
||||
return int(ret), 0
|
||||
}
|
||||
|
||||
_rand_bytes :: proc "contextless" (dst: []byte) {
|
||||
arc4random_buf(raw_data(dst), len(dst))
|
||||
}
|
||||
|
||||
_exit :: proc "contextless" (code: int) -> ! {
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
exit :: proc(status: i32) -> ! ---
|
||||
}
|
||||
exit(i32(code))
|
||||
}
|
||||
@@ -4,6 +4,8 @@ package runtime
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
_HAS_RAND_BYTES :: true
|
||||
|
||||
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
|
||||
STDERR :: 2
|
||||
when ODIN_NO_CRT {
|
||||
@@ -26,3 +28,25 @@ _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
|
||||
return 0, _OS_Errno(__error()^)
|
||||
}
|
||||
}
|
||||
|
||||
foreign import libc "system:System"
|
||||
|
||||
_rand_bytes :: proc "contextless" (dst: []byte) {
|
||||
// This process used to use Security/RandomCopyBytes, however
|
||||
// on every version of MacOS (>= 10.12) that we care about,
|
||||
// arc4random is implemented securely.
|
||||
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
arc4random_buf :: proc(buf: [^]byte, nbytes: uint) ---
|
||||
}
|
||||
arc4random_buf(raw_data(dst), len(dst))
|
||||
}
|
||||
|
||||
_exit :: proc "contextless" (code: int) -> ! {
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
exit :: proc(status: i32) -> ! ---
|
||||
}
|
||||
exit(i32(code))
|
||||
}
|
||||
@@ -2,7 +2,13 @@
|
||||
#+private
|
||||
package runtime
|
||||
|
||||
_HAS_RAND_BYTES :: false
|
||||
|
||||
// TODO(bill): reimplement `os.write`
|
||||
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
|
||||
return 0, -1
|
||||
}
|
||||
|
||||
_exit :: proc "contextless" (code: int) -> ! {
|
||||
trap()
|
||||
}
|
||||
@@ -4,11 +4,15 @@ package runtime
|
||||
|
||||
foreign import libc "system:c"
|
||||
|
||||
_HAS_RAND_BYTES :: true
|
||||
|
||||
foreign libc {
|
||||
@(link_name="write")
|
||||
_unix_write :: proc(fd: i32, buf: rawptr, size: int) -> int ---
|
||||
|
||||
_errnop :: proc() -> ^i32 ---
|
||||
|
||||
arc4random_buf :: proc(buf: [^]byte, nbytes: uint) ---
|
||||
}
|
||||
|
||||
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
|
||||
@@ -19,3 +23,11 @@ _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
|
||||
}
|
||||
return int(ret), 0
|
||||
}
|
||||
|
||||
_rand_bytes :: proc "contextless" (dst: []byte) {
|
||||
arc4random_buf(raw_data(dst), len(dst))
|
||||
}
|
||||
|
||||
_exit :: proc "contextless" (code: int) -> ! {
|
||||
trap()
|
||||
}
|
||||
@@ -4,6 +4,8 @@ package runtime
|
||||
|
||||
foreign import "odin_env"
|
||||
|
||||
_HAS_RAND_BYTES :: true
|
||||
|
||||
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
|
||||
foreign odin_env {
|
||||
write :: proc "contextless" (fd: u32, p: []byte) ---
|
||||
@@ -11,3 +13,24 @@ _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
|
||||
write(1, data)
|
||||
return len(data), 0
|
||||
}
|
||||
|
||||
_rand_bytes :: proc "contextless" (dst: []byte) {
|
||||
foreign odin_env {
|
||||
@(link_name = "rand_bytes")
|
||||
env_rand_bytes :: proc "contextless" (buf: []byte) ---
|
||||
}
|
||||
|
||||
MAX_PER_CALL_BYTES :: 65536 // 64kiB
|
||||
|
||||
dst := dst
|
||||
for len(dst) > 0 {
|
||||
to_read := min(len(dst), MAX_PER_CALL_BYTES)
|
||||
env_rand_bytes(dst[:to_read])
|
||||
|
||||
dst = dst[to_read:]
|
||||
}
|
||||
}
|
||||
|
||||
_exit :: proc "contextless" (code: int) -> ! {
|
||||
trap()
|
||||
}
|
||||
@@ -3,6 +3,8 @@ package runtime
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
_HAS_RAND_BYTES :: true
|
||||
|
||||
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
|
||||
when ODIN_ARCH == .amd64 {
|
||||
SYS_write :: uintptr(1)
|
||||
@@ -24,3 +26,64 @@ _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
|
||||
}
|
||||
return ret, 0
|
||||
}
|
||||
|
||||
_rand_bytes :: proc "contextless" (dst: []byte) {
|
||||
when ODIN_ARCH == .amd64 {
|
||||
SYS_getrandom :: uintptr(318)
|
||||
} else when ODIN_ARCH == .arm64 {
|
||||
SYS_getrandom :: uintptr(278)
|
||||
} else when ODIN_ARCH == .i386 {
|
||||
SYS_getrandom :: uintptr(355)
|
||||
} else when ODIN_ARCH == .arm32 {
|
||||
SYS_getrandom :: uintptr(384)
|
||||
} else when ODIN_ARCH == .riscv64 {
|
||||
SYS_getrandom :: uintptr(278)
|
||||
} else {
|
||||
#panic("base/runtime: no SYS_getrandom definition for target")
|
||||
}
|
||||
|
||||
ERR_EINTR :: 4
|
||||
ERR_ENOSYS :: 38
|
||||
|
||||
MAX_PER_CALL_BYTES :: 33554431 // 2^25 - 1
|
||||
|
||||
dst := dst
|
||||
l := len(dst)
|
||||
|
||||
for l > 0 {
|
||||
to_read := min(l, MAX_PER_CALL_BYTES)
|
||||
ret := int(intrinsics.syscall(SYS_getrandom, uintptr(raw_data(dst[:to_read])), uintptr(to_read), uintptr(0)))
|
||||
switch ret {
|
||||
case -ERR_EINTR:
|
||||
// Call interupted by a signal handler, just retry the
|
||||
// request.
|
||||
continue
|
||||
case -ERR_ENOSYS:
|
||||
// The kernel is apparently prehistoric (< 3.17 circa 2014)
|
||||
// and does not support getrandom.
|
||||
panic_contextless("base/runtime: getrandom not available in kernel")
|
||||
case:
|
||||
if ret < 0 {
|
||||
// All other failures are things that should NEVER happen
|
||||
// unless the kernel interface changes (ie: the Linux
|
||||
// developers break userland).
|
||||
panic_contextless("base/runtime: getrandom failed")
|
||||
}
|
||||
}
|
||||
l -= ret
|
||||
dst = dst[ret:]
|
||||
}
|
||||
}
|
||||
|
||||
_exit :: proc "contextless" (code: int) -> ! {
|
||||
SYS_exit_group ::
|
||||
231 when ODIN_ARCH == .amd64 else
|
||||
248 when ODIN_ARCH == .arm32 else
|
||||
94 when ODIN_ARCH == .arm64 else
|
||||
252 when ODIN_ARCH == .i386 else
|
||||
94 when ODIN_ARCH == .riscv64 else
|
||||
0
|
||||
|
||||
intrinsics.syscall(uintptr(SYS_exit_group), uintptr(i32(code)))
|
||||
unreachable()
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ package runtime
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
_HAS_RAND_BYTES :: false
|
||||
|
||||
// Constants allowing to specify the level of logging verbosity.
|
||||
log_level :: enum u32 {
|
||||
// Only errors are logged.
|
||||
@@ -41,3 +43,8 @@ _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
|
||||
|
||||
return len(data), 0
|
||||
}
|
||||
|
||||
|
||||
_exit :: proc "contextless" (code: int) -> ! {
|
||||
trap()
|
||||
}
|
||||
@@ -4,6 +4,8 @@ package runtime
|
||||
|
||||
foreign import wasi "wasi_snapshot_preview1"
|
||||
|
||||
_HAS_RAND_BYTES :: true
|
||||
|
||||
@(default_calling_convention="contextless")
|
||||
foreign wasi {
|
||||
fd_write :: proc(
|
||||
@@ -23,6 +25,12 @@ foreign wasi {
|
||||
argv: [^]cstring,
|
||||
argv_buf: [^]byte,
|
||||
) -> u16 ---
|
||||
|
||||
@(private="file")
|
||||
proc_exit :: proc(rval: u32) -> ! ---
|
||||
|
||||
@(private ="file")
|
||||
random_get :: proc(buf: []u8) -> u16 ---
|
||||
}
|
||||
|
||||
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
|
||||
@@ -31,6 +39,12 @@ _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
|
||||
return int(n), _OS_Errno(err)
|
||||
}
|
||||
|
||||
_rand_bytes :: proc "contextless" (dst: []byte) {
|
||||
if errno := random_get(dst); errno != 0 {
|
||||
panic_contextless("base/runtime: wasi.random_get failed")
|
||||
}
|
||||
}
|
||||
|
||||
_wasi_setup_args :: proc() {
|
||||
num_of_args, size_of_args: uint
|
||||
if errno := args_sizes_get(&num_of_args, &size_of_args); errno != 0 {
|
||||
@@ -53,3 +67,8 @@ _wasi_setup_args :: proc() {
|
||||
delete(args_buf)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_exit :: proc "contextless" (code: int) -> ! {
|
||||
proc_exit(u32(code))
|
||||
}
|
||||
@@ -2,8 +2,11 @@
|
||||
#+private
|
||||
package runtime
|
||||
|
||||
foreign import bcrypt "system:Bcrypt.lib"
|
||||
foreign import kernel32 "system:Kernel32.lib"
|
||||
|
||||
_HAS_RAND_BYTES :: true
|
||||
|
||||
@(private="file")
|
||||
@(default_calling_convention="system")
|
||||
foreign kernel32 {
|
||||
@@ -14,6 +17,14 @@ foreign kernel32 {
|
||||
SetHandleInformation :: proc(hObject: rawptr, dwMask: u32, dwFlags: u32) -> b32 ---
|
||||
WriteFile :: proc(hFile: rawptr, lpBuffer: rawptr, nNumberOfBytesToWrite: u32, lpNumberOfBytesWritten: ^u32, lpOverlapped: rawptr) -> b32 ---
|
||||
GetLastError :: proc() -> u32 ---
|
||||
|
||||
ExitProcess :: proc(code: u32) -> ! ---
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
@(default_calling_convention="system")
|
||||
foreign bcrypt {
|
||||
BCryptGenRandom :: proc(hAlgorithm: rawptr, pBuffer: [^]u8, cbBuffer: u32, dwFlags: u32) -> i32 ---
|
||||
}
|
||||
|
||||
_stderr_write :: proc "contextless" (data: []byte) -> (n: int, err: _OS_Errno) #no_bounds_check {
|
||||
@@ -49,3 +60,31 @@ _stderr_write :: proc "contextless" (data: []byte) -> (n: int, err: _OS_Errno) #
|
||||
n = int(total_write)
|
||||
return
|
||||
}
|
||||
|
||||
_rand_bytes :: proc "contextless" (dst: []byte) {
|
||||
ensure_contextless(u64(len(dst)) <= u64(max(u32)), "base/runtime: oversized rand_bytes request")
|
||||
|
||||
BCRYPT_USE_SYSTEM_PREFERRED_RNG :: 0x00000002
|
||||
|
||||
ERROR_INVALID_HANDLE :: 6
|
||||
ERROR_INVALID_PARAMETER :: 87
|
||||
|
||||
ret := BCryptGenRandom(nil, raw_data(dst), u32(len(dst)), BCRYPT_USE_SYSTEM_PREFERRED_RNG)
|
||||
switch ret {
|
||||
case 0:
|
||||
case ERROR_INVALID_HANDLE:
|
||||
// The handle to the first parameter is invalid.
|
||||
// This should not happen here, since we explicitly pass nil to it
|
||||
panic_contextless("base/runtime: BCryptGenRandom Invalid handle for hAlgorithm")
|
||||
case ERROR_INVALID_PARAMETER:
|
||||
// One of the parameters was invalid
|
||||
panic_contextless("base/runtime: BCryptGenRandom Invalid parameter")
|
||||
case:
|
||||
// Unknown error
|
||||
panic_contextless("base/runtime: BCryptGenRandom failed")
|
||||
}
|
||||
}
|
||||
|
||||
_exit :: proc "contextless" (code: int) -> ! {
|
||||
ExitProcess(u32(code))
|
||||
}
|
||||
@@ -408,9 +408,9 @@ print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) {
|
||||
}
|
||||
|
||||
print_string("struct ")
|
||||
if .packed in info.flags { print_string("#packed ") }
|
||||
if .raw_union in info.flags { print_string("#raw_union ") }
|
||||
// if .no_copy in info.flags { print_string("#no_copy ") }
|
||||
if .packed in info.flags { print_string("#packed ") }
|
||||
if .raw_union in info.flags { print_string("#raw_union ") }
|
||||
if .all_or_none in info.flags { print_string("#all_or_none ") }
|
||||
if .align in info.flags {
|
||||
print_string("#align(")
|
||||
print_u64(u64(ti.align))
|
||||
|
||||
@@ -41,88 +41,3 @@ random_generator_reset_u64 :: proc(rg: Random_Generator, p: u64) {
|
||||
rg.procedure(rg.data, .Reset, ([^]byte)(&p)[:size_of(p)])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Default_Random_State :: struct {
|
||||
state: u64,
|
||||
inc: u64,
|
||||
}
|
||||
|
||||
default_random_generator_proc :: proc(data: rawptr, mode: Random_Generator_Mode, p: []byte) {
|
||||
@(require_results)
|
||||
read_u64 :: proc "contextless" (r: ^Default_Random_State) -> u64 {
|
||||
old_state := r.state
|
||||
r.state = old_state * 6364136223846793005 + (r.inc|1)
|
||||
xor_shifted := (((old_state >> 59) + 5) ~ old_state) * 12605985483714917081
|
||||
rot := (old_state >> 59)
|
||||
return (xor_shifted >> rot) | (xor_shifted << ((-rot) & 63))
|
||||
}
|
||||
|
||||
@(thread_local)
|
||||
global_rand_seed: Default_Random_State
|
||||
|
||||
init :: proc "contextless" (r: ^Default_Random_State, seed: u64) {
|
||||
seed := seed
|
||||
if seed == 0 {
|
||||
seed = u64(intrinsics.read_cycle_counter())
|
||||
}
|
||||
r.state = 0
|
||||
r.inc = (seed << 1) | 1
|
||||
_ = read_u64(r)
|
||||
r.state += seed
|
||||
_ = read_u64(r)
|
||||
}
|
||||
|
||||
r: ^Default_Random_State = ---
|
||||
if data == nil {
|
||||
r = &global_rand_seed
|
||||
} else {
|
||||
r = cast(^Default_Random_State)data
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case .Read:
|
||||
if r.state == 0 && r.inc == 0 {
|
||||
init(r, 0)
|
||||
}
|
||||
|
||||
switch len(p) {
|
||||
case size_of(u64):
|
||||
// Fast path for a 64-bit destination.
|
||||
intrinsics.unaligned_store((^u64)(raw_data(p)), read_u64(r))
|
||||
case:
|
||||
// All other cases.
|
||||
pos := i8(0)
|
||||
val := u64(0)
|
||||
for &v in p {
|
||||
if pos == 0 {
|
||||
val = read_u64(r)
|
||||
pos = 8
|
||||
}
|
||||
v = byte(val)
|
||||
val >>= 8
|
||||
pos -= 1
|
||||
}
|
||||
}
|
||||
|
||||
case .Reset:
|
||||
seed: u64
|
||||
mem_copy_non_overlapping(&seed, raw_data(p), min(size_of(seed), len(p)))
|
||||
init(r, seed)
|
||||
|
||||
case .Query_Info:
|
||||
if len(p) != size_of(Random_Generator_Query_Info) {
|
||||
return
|
||||
}
|
||||
info := (^Random_Generator_Query_Info)(raw_data(p))
|
||||
info^ += {.Uniform, .Resettable}
|
||||
}
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
default_random_generator :: proc "contextless" (state: ^Default_Random_State = nil) -> Random_Generator {
|
||||
return {
|
||||
procedure = default_random_generator_proc,
|
||||
data = state,
|
||||
}
|
||||
}
|
||||
164
base/runtime/random_generator_chacha8.odin
Normal file
164
base/runtime/random_generator_chacha8.odin
Normal file
@@ -0,0 +1,164 @@
|
||||
package runtime
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
// This is an implementation of the Chacha8Rand DRBG, as specified
|
||||
// in https://github.com/C2SP/C2SP/blob/main/chacha8rand.md
|
||||
//
|
||||
// There is a tradeoff to be made between state-size and performance,
|
||||
// in terms of the amount of rng output buffered.
|
||||
//
|
||||
// The sensible buffer sizes are:
|
||||
// - 256-bytes: 128-bit SIMD with 16x vector registers (SSE2)
|
||||
// - 512-bytes: 128-bit SIMD with 32x vector registers (ARMv8),
|
||||
// 256-bit SIMD with 16x vector registers (AVX2),
|
||||
// - 1024-bytes: AVX-512
|
||||
//
|
||||
// Notes:
|
||||
// - Smaller than 256-bytes is possible but would require redundant
|
||||
// calls to the ChaCha8 function, which is prohibitively expensive.
|
||||
// - Larger than 1024-bytes is possible but pointless as the construct
|
||||
// is defined around 992-bytes of RNG output and 32-bytes of input
|
||||
// per iteration.
|
||||
//
|
||||
// This implementation opts for a 1024-byte buffer for simplicity,
|
||||
// under the rationale that modern extremely memory constrained targets
|
||||
// provide suitable functionality in hardware, and the language makes
|
||||
// supporting the various SIMD flavors easy.
|
||||
|
||||
@(private = "file")
|
||||
RNG_SEED_SIZE :: 32
|
||||
@(private)
|
||||
RNG_OUTPUT_PER_ITER :: 1024 - RNG_SEED_SIZE
|
||||
|
||||
@(private)
|
||||
CHACHA_SIGMA_0: u32 : 0x61707865
|
||||
@(private)
|
||||
CHACHA_SIGMA_1: u32 : 0x3320646e
|
||||
@(private)
|
||||
CHACHA_SIGMA_2: u32 : 0x79622d32
|
||||
@(private)
|
||||
CHACHA_SIGMA_3: u32 : 0x6b206574
|
||||
@(private)
|
||||
CHACHA_ROUNDS :: 8
|
||||
|
||||
Default_Random_State :: struct {
|
||||
_buf: [1024]byte,
|
||||
_off: int,
|
||||
_seeded: bool,
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
default_random_generator :: proc "contextless" (state: ^Default_Random_State = nil) -> Random_Generator {
|
||||
return {
|
||||
procedure = default_random_generator_proc,
|
||||
data = state,
|
||||
}
|
||||
}
|
||||
|
||||
default_random_generator_proc :: proc(data: rawptr, mode: Random_Generator_Mode, p: []byte) {
|
||||
@(thread_local)
|
||||
state: Default_Random_State
|
||||
|
||||
r: ^Default_Random_State = &state
|
||||
if data != nil {
|
||||
r = cast(^Default_Random_State)data
|
||||
}
|
||||
next_seed := r._buf[RNG_OUTPUT_PER_ITER:]
|
||||
|
||||
switch mode {
|
||||
case .Read:
|
||||
if !r._seeded { // Unlikely.
|
||||
rand_bytes(next_seed)
|
||||
r._off = RNG_OUTPUT_PER_ITER // Force refill.
|
||||
r._seeded = true
|
||||
}
|
||||
|
||||
assert(r._off <= RNG_OUTPUT_PER_ITER, "chacha8rand/BUG: outputed key material")
|
||||
if r._off >= RNG_OUTPUT_PER_ITER { // Unlikely.
|
||||
chacha8rand_refill(r)
|
||||
}
|
||||
|
||||
// We are guaranteed to have at least some RNG output buffered.
|
||||
//
|
||||
// As an invariant each read will consume a multiple of 8-bytes
|
||||
// of output at a time.
|
||||
assert(r._off <= RNG_OUTPUT_PER_ITER - 8, "chacha8rand/BUG: less than 8-bytes of output available")
|
||||
assert(r._off % 8 == 0, "chacha8rand/BUG: buffered output is not a multiple of 8-bytes")
|
||||
|
||||
p_len := len(p)
|
||||
if p_len == size_of(u64) {
|
||||
#no_bounds_check {
|
||||
// Fast path for a 64-bit destination.
|
||||
src := (^u64)(raw_data(r._buf[r._off:]))
|
||||
intrinsics.unaligned_store((^u64)(raw_data(p)), src^)
|
||||
src^ = 0 // Erasure (backtrack resistance)
|
||||
r._off += 8
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
p_ := p
|
||||
for remaining := p_len; remaining > 0; {
|
||||
sz := min(remaining, RNG_OUTPUT_PER_ITER - r._off)
|
||||
#no_bounds_check {
|
||||
copy(p_[:sz], r._buf[r._off:])
|
||||
p_ = p_[sz:]
|
||||
remaining -= sz
|
||||
}
|
||||
rounded_sz := ((sz + 7) / 8) * 8
|
||||
new_off := r._off + rounded_sz
|
||||
#no_bounds_check if new_off < RNG_OUTPUT_PER_ITER {
|
||||
// Erasure (backtrack resistance)
|
||||
intrinsics.mem_zero(raw_data(r._buf[r._off:]), rounded_sz)
|
||||
r._off = new_off
|
||||
} else {
|
||||
// Can omit erasure since we are overwriting the entire
|
||||
// buffer.
|
||||
chacha8rand_refill(r)
|
||||
}
|
||||
}
|
||||
|
||||
case .Reset:
|
||||
// If no seed is passed, the next call to .Read will attempt to
|
||||
// reseed from the system entropy source.
|
||||
if len(p) == 0 {
|
||||
r._seeded = false
|
||||
return
|
||||
}
|
||||
|
||||
// The cryptographic security of the output depends entirely
|
||||
// on the quality of the entropy in the seed, we will allow
|
||||
// re-seeding (as it makes testing easier), but callers that
|
||||
// decide to provide arbitrary seeds are on their own as far
|
||||
// as ensuring high-quality entropy.
|
||||
intrinsics.mem_zero(raw_data(next_seed), RNG_SEED_SIZE)
|
||||
copy(next_seed, p)
|
||||
r._seeded = true
|
||||
r._off = RNG_OUTPUT_PER_ITER // Force a refill.
|
||||
|
||||
case .Query_Info:
|
||||
if len(p) != size_of(Random_Generator_Query_Info) {
|
||||
return
|
||||
}
|
||||
info := (^Random_Generator_Query_Info)(raw_data(p))
|
||||
info^ += {.Uniform, .Cryptographic, .Resettable}
|
||||
}
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
chacha8rand_refill :: proc(r: ^Default_Random_State) {
|
||||
assert(r._seeded == true, "chacha8rand/BUG: unseeded refill")
|
||||
|
||||
// i386 has insufficient vector registers to use the
|
||||
// accelerated path at the moment.
|
||||
when ODIN_ARCH == .amd64 && intrinsics.has_target_feature("avx2") {
|
||||
chacha8rand_refill_simd256(r)
|
||||
} else when HAS_HARDWARE_SIMD && ODIN_ARCH != .i386 {
|
||||
chacha8rand_refill_simd128(r)
|
||||
} else {
|
||||
chacha8rand_refill_ref(r)
|
||||
}
|
||||
|
||||
r._off = 0
|
||||
}
|
||||
145
base/runtime/random_generator_chacha8_ref.odin
Normal file
145
base/runtime/random_generator_chacha8_ref.odin
Normal file
@@ -0,0 +1,145 @@
|
||||
package runtime
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
@(private)
|
||||
chacha8rand_refill_ref :: proc(r: ^Default_Random_State) {
|
||||
// Initialize the base state.
|
||||
k: [^]u32 = (^u32)(raw_data(r._buf[RNG_OUTPUT_PER_ITER:]))
|
||||
when ODIN_ENDIAN == .Little {
|
||||
s4 := k[0]
|
||||
s5 := k[1]
|
||||
s6 := k[2]
|
||||
s7 := k[3]
|
||||
s8 := k[4]
|
||||
s9 := k[5]
|
||||
s10 := k[6]
|
||||
s11 := k[7]
|
||||
} else {
|
||||
s4 := intrinsics.byte_swap(k[0])
|
||||
s5 := intrinsics.byte_swap(k[1])
|
||||
s6 := intrinsics.byte_swap(k[2])
|
||||
s7 := intrinsics.byte_swap(k[3])
|
||||
s8 := intrinsics.byte_swap(k[4])
|
||||
s9 := intrinsics.byte_swap(k[5])
|
||||
s10 := intrinsics.byte_swap(k[6])
|
||||
s11 := intrinicss.byte_swap(k[7])
|
||||
}
|
||||
s12: u32 // Counter starts at 0.
|
||||
s13, s14, s15: u32 // IV of all 0s.
|
||||
|
||||
dst: [^]u32 = (^u32)(raw_data(r._buf[:]))
|
||||
|
||||
// At least with LLVM21 force_inline produces identical perf to
|
||||
// manual inlining, yay.
|
||||
quarter_round := #force_inline proc "contextless" (a, b, c, d: u32) -> (u32, u32, u32, u32) {
|
||||
a, b, c, d := a, b, c, d
|
||||
|
||||
a += b
|
||||
d ~= a
|
||||
d = rotl(d, 16)
|
||||
|
||||
c += d
|
||||
b ~= c
|
||||
b = rotl(b, 12)
|
||||
|
||||
a += b
|
||||
d ~= a
|
||||
d = rotl(d, 8)
|
||||
|
||||
c += d
|
||||
b ~= c
|
||||
b = rotl(b, 7)
|
||||
|
||||
return a, b, c, d
|
||||
}
|
||||
|
||||
// Filippo Valsorda made an observation that only one of the column
|
||||
// round depends on the counter (s12), so it is worth precomputing
|
||||
// and reusing across multiple blocks. As far as I know, only Go's
|
||||
// chacha implementation does this.
|
||||
|
||||
p1, p5, p9, p13 := quarter_round(CHACHA_SIGMA_1, s5, s9, s13)
|
||||
p2, p6, p10, p14 := quarter_round(CHACHA_SIGMA_2, s6, s10, s14)
|
||||
p3, p7, p11, p15 := quarter_round(CHACHA_SIGMA_3, s7, s11, s15)
|
||||
|
||||
// 4 groups
|
||||
for g := 0; g < 4; g = g + 1 {
|
||||
// 4 blocks per group
|
||||
for n := 0; n < 4; n = n + 1 {
|
||||
// First column round that depends on the counter
|
||||
p0, p4, p8, p12 := quarter_round(CHACHA_SIGMA_0, s4, s8, s12)
|
||||
|
||||
// First diagonal round
|
||||
x0, x5, x10, x15 := quarter_round(p0, p5, p10, p15)
|
||||
x1, x6, x11, x12 := quarter_round(p1, p6, p11, p12)
|
||||
x2, x7, x8, x13 := quarter_round(p2, p7, p8, p13)
|
||||
x3, x4, x9, x14 := quarter_round(p3, p4, p9, p14)
|
||||
|
||||
for i := CHACHA_ROUNDS - 2; i > 0; i = i - 2 {
|
||||
x0, x4, x8, x12 = quarter_round(x0, x4, x8, x12)
|
||||
x1, x5, x9, x13 = quarter_round(x1, x5, x9, x13)
|
||||
x2, x6, x10, x14 = quarter_round(x2, x6, x10, x14)
|
||||
x3, x7, x11, x15 = quarter_round(x3, x7, x11, x15)
|
||||
|
||||
x0, x5, x10, x15 = quarter_round(x0, x5, x10, x15)
|
||||
x1, x6, x11, x12 = quarter_round(x1, x6, x11, x12)
|
||||
x2, x7, x8, x13 = quarter_round(x2, x7, x8, x13)
|
||||
x3, x4, x9, x14 = quarter_round(x3, x4, x9, x14)
|
||||
}
|
||||
|
||||
// Interleave 4 blocks
|
||||
// NB: The additions of sigma and the counter are omitted
|
||||
STRIDE :: 4
|
||||
d_ := dst[n:]
|
||||
when ODIN_ENDIAN == .Little {
|
||||
d_[STRIDE*0] = x0
|
||||
d_[STRIDE*1] = x1
|
||||
d_[STRIDE*2] = x2
|
||||
d_[STRIDE*3] = x3
|
||||
d_[STRIDE*4] = x4 + s4
|
||||
d_[STRIDE*5] = x5 + s5
|
||||
d_[STRIDE*6] = x6 + s6
|
||||
d_[STRIDE*7] = x7 + s7
|
||||
d_[STRIDE*8] = x8 + s8
|
||||
d_[STRIDE*9] = x9 + s9
|
||||
d_[STRIDE*10] = x10 + s10
|
||||
d_[STRIDE*11] = x11 + s11
|
||||
d_[STRIDE*12] = x12
|
||||
d_[STRIDE*13] = x13 + s13
|
||||
d_[STRIDE*14] = x14 + s14
|
||||
d_[STRIDE*15] = x15 + s15
|
||||
} else {
|
||||
d_[STRIDE*0] = intrinsics.byte_swap(x0)
|
||||
d_[STRIDE*1] = intrinsics.byte_swap(x1)
|
||||
d_[STRIDE*2] = intrinsics.byte_swap(x2)
|
||||
d_[STRIDE*3] = intrinsics.byte_swap(x3)
|
||||
d_[STRIDE*4] = intrinsics.byte_swap(x4 + s4)
|
||||
d_[STRIDE*5] = intrinsics.byte_swap(x5 + s5)
|
||||
d_[STRIDE*6] = intrinsics.byte_swap(x6 + s6)
|
||||
d_[STRIDE*7] = intrinsics.byte_swap(x7 + s7)
|
||||
d_[STRIDE*8] = intrinsics.byte_swap(x8 + s8)
|
||||
d_[STRIDE*9] = intrinsics.byte_swap(x9 + s9)
|
||||
d_[STRIDE*10] = intrinsics.byte_swap(x10 + s10)
|
||||
d_[STRIDE*11] = intrinsics.byte_swap(x11 + s11)
|
||||
d_[STRIDE*12] = intrinsics.byte_swap(x12)
|
||||
d_[STRIDE*13] = intrinsics.byte_swap(x13 + s13)
|
||||
d_[STRIDE*14] = intrinsics.byte_swap(x14 + s14)
|
||||
d_[STRIDE*15] = intrinsics.byte_swap(x15 + s15)
|
||||
}
|
||||
|
||||
s12 = s12 + 1 // Increment the counter
|
||||
}
|
||||
|
||||
dst = dst[16*4:]
|
||||
}
|
||||
}
|
||||
|
||||
// This replicates `rotate_left32` from `core:math/bits`, under the
|
||||
// assumption that this will live in `base:runtime`.
|
||||
@(require_results, private = "file")
|
||||
rotl :: #force_inline proc "contextless" (x: u32, k: int) -> u32 {
|
||||
n :: 32
|
||||
s := uint(k) & (n-1)
|
||||
return x << s | x >> (n-s)
|
||||
}
|
||||
290
base/runtime/random_generator_chacha8_simd128.odin
Normal file
290
base/runtime/random_generator_chacha8_simd128.odin
Normal file
@@ -0,0 +1,290 @@
|
||||
#+build !i386
|
||||
package runtime
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
@(private = "file")
|
||||
u32x4 :: #simd[4]u32
|
||||
|
||||
@(private = "file")
|
||||
S0: u32x4 : {CHACHA_SIGMA_0, CHACHA_SIGMA_0, CHACHA_SIGMA_0, CHACHA_SIGMA_0}
|
||||
@(private = "file")
|
||||
S1: u32x4 : {CHACHA_SIGMA_1, CHACHA_SIGMA_1, CHACHA_SIGMA_1, CHACHA_SIGMA_1}
|
||||
@(private = "file")
|
||||
S2: u32x4 : {CHACHA_SIGMA_2, CHACHA_SIGMA_2, CHACHA_SIGMA_2, CHACHA_SIGMA_2}
|
||||
@(private = "file")
|
||||
S3: u32x4 : {CHACHA_SIGMA_3, CHACHA_SIGMA_3, CHACHA_SIGMA_3, CHACHA_SIGMA_3}
|
||||
|
||||
@(private = "file")
|
||||
_ROT_7L: u32x4 : {7, 7, 7, 7}
|
||||
@(private = "file")
|
||||
_ROT_7R: u32x4 : {25, 25, 25, 25}
|
||||
@(private = "file")
|
||||
_ROT_12L: u32x4 : {12, 12, 12, 12}
|
||||
@(private = "file")
|
||||
_ROT_12R: u32x4 : {20, 20, 20, 20}
|
||||
@(private = "file")
|
||||
_ROT_8L: u32x4 : {8, 8, 8, 8}
|
||||
@(private = "file")
|
||||
_ROT_8R: u32x4 : {24, 24, 24, 24}
|
||||
@(private = "file")
|
||||
_ROT_16: u32x4 : {16, 16, 16, 16}
|
||||
@(private = "file")
|
||||
_CTR_INC_4: u32x4 : {4, 4, 4, 4}
|
||||
@(private = "file")
|
||||
_CTR_INC_8: u32x4 : {8, 8, 8, 8}
|
||||
|
||||
when ODIN_ENDIAN == .Big {
|
||||
@(private = "file")
|
||||
_byteswap_u32x4 :: #force_inline proc "contextless" (v: u32x4) -> u32x4 {
|
||||
u8x16 :: #simd[16]u8
|
||||
return(
|
||||
transmute(u32x4)simd.shuffle(
|
||||
transmute(u8x16)v,
|
||||
transmute(u8x16)v,
|
||||
3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
chacha8rand_refill_simd128 :: proc(r: ^Default_Random_State) {
|
||||
// Initialize the base state.
|
||||
k: [^]u32 = (^u32)(raw_data(r._buf[RNG_OUTPUT_PER_ITER:]))
|
||||
when ODIN_ENDIAN == .Little {
|
||||
s4_ := k[0]
|
||||
s5_ := k[1]
|
||||
s6_ := k[2]
|
||||
s7_ := k[3]
|
||||
s8_ := k[4]
|
||||
s9_ := k[5]
|
||||
s10_ := k[6]
|
||||
s11_ := k[7]
|
||||
} else {
|
||||
s4_ := intrinsics.byte_swap(k[0])
|
||||
s5_ := intrinsics.byte_swap(k[1])
|
||||
s6_ := intrinsics.byte_swap(k[2])
|
||||
s7_ := intrinsics.byte_swap(k[3])
|
||||
s8_ := intrinsics.byte_swap(k[4])
|
||||
s9_ := intrinsics.byte_swap(k[5])
|
||||
s10_ := intrinsics.byte_swap(k[6])
|
||||
s11_ := intrinicss.byte_swap(k[7])
|
||||
}
|
||||
|
||||
// 4-lane ChaCha8.
|
||||
s4 := u32x4{s4_, s4_, s4_, s4_}
|
||||
s5 := u32x4{s5_, s5_, s5_, s5_}
|
||||
s6 := u32x4{s6_, s6_, s6_, s6_}
|
||||
s7 := u32x4{s7_, s7_, s7_, s7_}
|
||||
s8 := u32x4{s8_, s8_, s8_, s8_}
|
||||
s9 := u32x4{s9_, s9_, s9_, s9_}
|
||||
s10 := u32x4{s10_, s10_, s10_, s10_}
|
||||
s11 := u32x4{s11_, s11_, s11_, s11_}
|
||||
s12 := u32x4{0, 1, 2, 3}
|
||||
s13, s14, s15: u32x4
|
||||
|
||||
dst: [^]u32x4 = (^u32x4)(raw_data(r._buf[:]))
|
||||
|
||||
quarter_round := #force_inline proc "contextless" (a, b, c, d: u32x4) -> (u32x4, u32x4, u32x4, u32x4) {
|
||||
a, b, c, d := a, b, c, d
|
||||
|
||||
a = intrinsics.simd_add(a, b)
|
||||
d = intrinsics.simd_bit_xor(d, a)
|
||||
d = intrinsics.simd_bit_xor(intrinsics.simd_shl(d, _ROT_16), intrinsics.simd_shr(d, _ROT_16))
|
||||
|
||||
c = intrinsics.simd_add(c, d)
|
||||
b = intrinsics.simd_bit_xor(b, c)
|
||||
b = intrinsics.simd_bit_xor(intrinsics.simd_shl(b, _ROT_12L), intrinsics.simd_shr(b, _ROT_12R))
|
||||
|
||||
a = intrinsics.simd_add(a, b)
|
||||
d = intrinsics.simd_bit_xor(d, a)
|
||||
d = intrinsics.simd_bit_xor(intrinsics.simd_shl(d, _ROT_8L), intrinsics.simd_shr(d, _ROT_8R))
|
||||
|
||||
c = intrinsics.simd_add(c, d)
|
||||
b = intrinsics.simd_bit_xor(b, c)
|
||||
b = intrinsics.simd_bit_xor(intrinsics.simd_shl(b, _ROT_7L), intrinsics.simd_shr(b, _ROT_7R))
|
||||
|
||||
return a, b, c, d
|
||||
}
|
||||
|
||||
// 8 blocks at a time.
|
||||
//
|
||||
// Note:
|
||||
// This uses a ton of registers so it is only worth it on targets
|
||||
// that have something like 32 128-bit registers. This is currently
|
||||
// all ARMv8 targets, and RISC-V Zvl128b (`V` application profile)
|
||||
// targets.
|
||||
//
|
||||
// While our current definition of `.arm32` is 32-bit ARMv8, this
|
||||
// may change in the future (ARMv7 is still relevant), and things
|
||||
// like Cortex-A8/A9 does "pretend" 128-bit SIMD 64-bits at a time
|
||||
// thus needs bemchmarking.
|
||||
when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 {
|
||||
for _ in 0..<2 {
|
||||
x0_0, x1_0, x2_0, x3_0 := S0, S1, S2, S3
|
||||
x4_0, x5_0, x6_0, x7_0 := s4, s5, s6, s7
|
||||
x8_0, x9_0, x10_0, x11_0 := s8, s9, s10, s11
|
||||
x12_0, x13_0, x14_0, x15_0 := s12, s13, s14, s15
|
||||
|
||||
x0_1, x1_1, x2_1, x3_1 := S0, S1, S2, S3
|
||||
x4_1, x5_1, x6_1, x7_1 := s4, s5, s6, s7
|
||||
x8_1, x9_1, x10_1, x11_1 := s8, s9, s10, s11
|
||||
x12_1 := intrinsics.simd_add(s12, _CTR_INC_4)
|
||||
x13_1, x14_1, x15_1 := s13, s14, s15
|
||||
|
||||
for i := CHACHA_ROUNDS; i > 0; i = i - 2 {
|
||||
x0_0, x4_0, x8_0, x12_0 = quarter_round(x0_0, x4_0, x8_0, x12_0)
|
||||
x0_1, x4_1, x8_1, x12_1 = quarter_round(x0_1, x4_1, x8_1, x12_1)
|
||||
x1_0, x5_0, x9_0, x13_0 = quarter_round(x1_0, x5_0, x9_0, x13_0)
|
||||
x1_1, x5_1, x9_1, x13_1 = quarter_round(x1_1, x5_1, x9_1, x13_1)
|
||||
x2_0, x6_0, x10_0, x14_0 = quarter_round(x2_0, x6_0, x10_0, x14_0)
|
||||
x2_1, x6_1, x10_1, x14_1 = quarter_round(x2_1, x6_1, x10_1, x14_1)
|
||||
x3_0, x7_0, x11_0, x15_0 = quarter_round(x3_0, x7_0, x11_0, x15_0)
|
||||
x3_1, x7_1, x11_1, x15_1 = quarter_round(x3_1, x7_1, x11_1, x15_1)
|
||||
|
||||
x0_0, x5_0, x10_0, x15_0 = quarter_round(x0_0, x5_0, x10_0, x15_0)
|
||||
x0_1, x5_1, x10_1, x15_1 = quarter_round(x0_1, x5_1, x10_1, x15_1)
|
||||
x1_0, x6_0, x11_0, x12_0 = quarter_round(x1_0, x6_0, x11_0, x12_0)
|
||||
x1_1, x6_1, x11_1, x12_1 = quarter_round(x1_1, x6_1, x11_1, x12_1)
|
||||
x2_0, x7_0, x8_0, x13_0 = quarter_round(x2_0, x7_0, x8_0, x13_0)
|
||||
x2_1, x7_1, x8_1, x13_1 = quarter_round(x2_1, x7_1, x8_1, x13_1)
|
||||
x3_0, x4_0, x9_0, x14_0 = quarter_round(x3_0, x4_0, x9_0, x14_0)
|
||||
x3_1, x4_1, x9_1, x14_1 = quarter_round(x3_1, x4_1, x9_1, x14_1)
|
||||
}
|
||||
|
||||
when ODIN_ENDIAN == .Little {
|
||||
intrinsics.unaligned_store((^u32x4)(dst[0:]), x0_0)
|
||||
intrinsics.unaligned_store((^u32x4)(dst[1:]), x1_0)
|
||||
intrinsics.unaligned_store((^u32x4)(dst[2:]), x2_0)
|
||||
intrinsics.unaligned_store((^u32x4)(dst[3:]), x3_0)
|
||||
intrinsics.unaligned_store((^u32x4)(dst[4:]), intrinsics.simd_add(x4_0, s4))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[5:]), intrinsics.simd_add(x5_0, s5))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[6:]), intrinsics.simd_add(x6_0, s6))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[7:]), intrinsics.simd_add(x7_0, s7))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[8:]), intrinsics.simd_add(x8_0, s8))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[9:]), intrinsics.simd_add(x9_0, s9))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[10:]), intrinsics.simd_add(x10_0, s10))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[11:]), intrinsics.simd_add(x11_0, s11))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[12:]), x12_0)
|
||||
intrinsics.unaligned_store((^u32x4)(dst[13:]), intrinsics.simd_add(x13_0, s13))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[14:]), intrinsics.simd_add(x14_0, s14))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[15:]), intrinsics.simd_add(x15_0, s15))
|
||||
|
||||
intrinsics.unaligned_store((^u32x4)(dst[16:]), x0_1)
|
||||
intrinsics.unaligned_store((^u32x4)(dst[17:]), x1_1)
|
||||
intrinsics.unaligned_store((^u32x4)(dst[18:]), x2_1)
|
||||
intrinsics.unaligned_store((^u32x4)(dst[19:]), x3_1)
|
||||
intrinsics.unaligned_store((^u32x4)(dst[20:]), intrinsics.simd_add(x4_1, s4))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[21:]), intrinsics.simd_add(x5_1, s5))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[22:]), intrinsics.simd_add(x6_1, s6))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[23:]), intrinsics.simd_add(x7_1, s7))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[24:]), intrinsics.simd_add(x8_1, s8))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[25:]), intrinsics.simd_add(x9_1, s9))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[26:]), intrinsics.simd_add(x10_1, s10))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[27:]), intrinsics.simd_add(x11_1, s11))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[28:]), x12_1)
|
||||
intrinsics.unaligned_store((^u32x4)(dst[29:]), intrinsics.simd_add(x13_1, s13))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[30:]), intrinsics.simd_add(x14_1, s14))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[31:]), intrinsics.simd_add(x15_1, s15))
|
||||
} else {
|
||||
intrinsics.unaligned_store((^u32x4)(dst[0:]), _byteswap_u32x4(x0_0))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[1:]), _byteswap_u32x4(x1_0))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[2:]), _byteswap_u32x4(x2_0))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[3:]), _byteswap_u32x4(x3_0))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[4:]), _byteswap_u32x4(intrinsics.simd_add(x4_0, s4)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[5:]), _byteswap_u32x4(intrinsics.simd_add(x5_0, s5)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[6:]), _byteswap_u32x4(intrinsics.simd_add(x6_0, s6)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[7:]), _byteswap_u32x4(intrinsics.simd_add(x7_0, s7)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[8:]), _byteswap_u32x4(intrinsics.simd_add(x8_0, s8)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[9:]), _byteswap_u32x4(intrinsics.simd_add(x9_0, s9)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[10:]), _byteswap_u32x4(intrinsics.simd_add(x10_0, s10)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[11:]), _byteswap_u32x4(intrinsics.simd_add(x11_0, s11)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[12:]), _byteswap_u32x4(x12_0))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[13:]), _byteswap_u32x4(intrinsics.simd_add(x13_0, s13)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[14:]), _byteswap_u32x4(intrinsics.simd_add(x14_0, s14)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[15:]), _byteswap_u32x4(intrinsics.simd_add(x15_0, s15)))
|
||||
|
||||
intrinsics.unaligned_store((^u32x4)(dst[16:]), _byteswap_u32x4(x0_1))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[17:]), _byteswap_u32x4(x1_1))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[18:]), _byteswap_u32x4(x2_1))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[19:]), _byteswap_u32x4(x3_1))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[20:]), _byteswap_u32x4(intrinsics.simd_add(x4_1, s4)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[21:]), _byteswap_u32x4(intrinsics.simd_add(x5_1, s5)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[22:]), _byteswap_u32x4(intrinsics.simd_add(x6_1, s6)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[23:]), _byteswap_u32x4(intrinsics.simd_add(x7_1, s7)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[24:]), _byteswap_u32x4(intrinsics.simd_add(x8_1, s8)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[25:]), _byteswap_u32x4(intrinsics.simd_add(x9_1, s9)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[26:]), _byteswap_u32x4(intrinsics.simd_add(x10_1, s10)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[27:]), _byteswap_u32x4(intrinsics.simd_add(x11_1, s11)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[28:]), _byteswap_u32x4(x12_1))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[29:]), _byteswap_u32x4(intrinsics.simd_add(x13_1, s13)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[30:]), _byteswap_u32x4(intrinsics.simd_add(x14_1, s14)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[31:]), _byteswap_u32x4(intrinsics.simd_add(x15_1, s15)))
|
||||
}
|
||||
|
||||
s12 = intrinsics.simd_add(s12, _CTR_INC_8)
|
||||
|
||||
dst = dst[32:]
|
||||
}
|
||||
} else {
|
||||
for _ in 0..<4 {
|
||||
x0, x1, x2, x3 := S0, S1, S2, S3
|
||||
x4, x5, x6, x7 := s4, s5, s6, s7
|
||||
x8, x9, x10, x11 := s8, s9, s10, s11
|
||||
x12, x13, x14, x15 := s12, s13, s14, s15
|
||||
|
||||
for i := CHACHA_ROUNDS; i > 0; i = i - 2 {
|
||||
x0, x4, x8, x12 = quarter_round(x0, x4, x8, x12)
|
||||
x1, x5, x9, x13 = quarter_round(x1, x5, x9, x13)
|
||||
x2, x6, x10, x14 = quarter_round(x2, x6, x10, x14)
|
||||
x3, x7, x11, x15 = quarter_round(x3, x7, x11, x15)
|
||||
|
||||
x0, x5, x10, x15 = quarter_round(x0, x5, x10, x15)
|
||||
x1, x6, x11, x12 = quarter_round(x1, x6, x11, x12)
|
||||
x2, x7, x8, x13 = quarter_round(x2, x7, x8, x13)
|
||||
x3, x4, x9, x14 = quarter_round(x3, x4, x9, x14)
|
||||
}
|
||||
|
||||
when ODIN_ENDIAN == .Little {
|
||||
intrinsics.unaligned_store((^u32x4)(dst[0:]), x0)
|
||||
intrinsics.unaligned_store((^u32x4)(dst[1:]), x1)
|
||||
intrinsics.unaligned_store((^u32x4)(dst[2:]), x2)
|
||||
intrinsics.unaligned_store((^u32x4)(dst[3:]), x3)
|
||||
intrinsics.unaligned_store((^u32x4)(dst[4:]), intrinsics.simd_add(x4, s4))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[5:]), intrinsics.simd_add(x5, s5))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[6:]), intrinsics.simd_add(x6, s6))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[7:]), intrinsics.simd_add(x7, s7))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[8:]), intrinsics.simd_add(x8, s8))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[9:]), intrinsics.simd_add(x9, s9))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[10:]), intrinsics.simd_add(x10, s10))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[11:]), intrinsics.simd_add(x11, s11))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[12:]), x12)
|
||||
intrinsics.unaligned_store((^u32x4)(dst[13:]), intrinsics.simd_add(x13, s13))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[14:]), intrinsics.simd_add(x14, s14))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[15:]), intrinsics.simd_add(x15, s15))
|
||||
} else {
|
||||
intrinsics.unaligned_store((^u32x4)(dst[0:]), _byteswap_u32x4(x0))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[1:]), _byteswap_u32x4(x1))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[2:]), _byteswap_u32x4(x2))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[3:]), _byteswap_u32x4(x3))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[4:]), _byteswap_u32x4(intrinsics.simd_add(x4, s4)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[5:]), _byteswap_u32x4(intrinsics.simd_add(x5, s5)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[6:]), _byteswap_u32x4(intrinsics.simd_add(x6, s6)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[7:]), _byteswap_u32x4(intrinsics.simd_add(x7, s7)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[8:]), _byteswap_u32x4(intrinsics.simd_add(x8, s8)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[9:]), _byteswap_u32x4(intrinsics.simd_add(x9, s9)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[10:]), _byteswap_u32x4(intrinsics.simd_add(x10, s10)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[11:]), _byteswap_u32x4(intrinsics.simd_add(x11, s11)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[12:]), _byteswap_u32x4(x12))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[13:]), _byteswap_u32x4(intrinsics.simd_add(x13, s13)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[14:]), _byteswap_u32x4(intrinsics.simd_add(x14, s14)))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[15:]), _byteswap_u32x4(intrinsics.simd_add(x15, s15)))
|
||||
}
|
||||
|
||||
s12 = intrinsics.simd_add(s12, _CTR_INC_4)
|
||||
|
||||
dst = dst[16:]
|
||||
}
|
||||
}
|
||||
}
|
||||
197
base/runtime/random_generator_chacha8_simd256.odin
Normal file
197
base/runtime/random_generator_chacha8_simd256.odin
Normal file
@@ -0,0 +1,197 @@
|
||||
#+build amd64
|
||||
package runtime
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
#assert(ODIN_ENDIAN == .Little)
|
||||
|
||||
@(private = "file")
|
||||
u32x8 :: #simd[8]u32
|
||||
@(private = "file")
|
||||
u32x4 :: #simd[4]u32
|
||||
|
||||
@(private = "file")
|
||||
S0: u32x8 : {
|
||||
CHACHA_SIGMA_0, CHACHA_SIGMA_0, CHACHA_SIGMA_0, CHACHA_SIGMA_0,
|
||||
CHACHA_SIGMA_0, CHACHA_SIGMA_0, CHACHA_SIGMA_0, CHACHA_SIGMA_0,
|
||||
}
|
||||
@(private = "file")
|
||||
S1: u32x8 : {
|
||||
CHACHA_SIGMA_1, CHACHA_SIGMA_1, CHACHA_SIGMA_1, CHACHA_SIGMA_1,
|
||||
CHACHA_SIGMA_1, CHACHA_SIGMA_1, CHACHA_SIGMA_1, CHACHA_SIGMA_1,
|
||||
}
|
||||
@(private = "file")
|
||||
S2: u32x8 : {
|
||||
CHACHA_SIGMA_2, CHACHA_SIGMA_2, CHACHA_SIGMA_2, CHACHA_SIGMA_2,
|
||||
CHACHA_SIGMA_2, CHACHA_SIGMA_2, CHACHA_SIGMA_2, CHACHA_SIGMA_2,
|
||||
}
|
||||
@(private = "file")
|
||||
S3: u32x8 : {
|
||||
CHACHA_SIGMA_3, CHACHA_SIGMA_3, CHACHA_SIGMA_3, CHACHA_SIGMA_3,
|
||||
CHACHA_SIGMA_3, CHACHA_SIGMA_3, CHACHA_SIGMA_3, CHACHA_SIGMA_3,
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
_ROT_7L: u32x8 : {7, 7, 7, 7, 7, 7, 7, 7}
|
||||
@(private = "file")
|
||||
_ROT_7R: u32x8 : {25, 25, 25, 25, 25, 25, 25, 25}
|
||||
@(private = "file")
|
||||
_ROT_12L: u32x8 : {12, 12, 12, 12, 12, 12, 12, 12}
|
||||
@(private = "file")
|
||||
_ROT_12R: u32x8 : {20, 20, 20, 20, 20, 20, 20, 20}
|
||||
@(private = "file")
|
||||
_ROT_8L: u32x8 : {8, 8, 8, 8, 8, 8, 8, 8}
|
||||
@(private = "file")
|
||||
_ROT_8R: u32x8 : {24, 24, 24, 24, 24, 24, 24, 24}
|
||||
@(private = "file")
|
||||
_ROT_16: u32x8 : {16, 16, 16, 16, 16, 16, 16, 16}
|
||||
@(private = "file")
|
||||
_CTR_INC_8: u32x8 : {8, 8, 8, 8, 8, 8, 8, 8}
|
||||
|
||||
// To the best of my knowledge this is only really useful on
|
||||
// modern x86-64 as most ARM silicon is missing support for SVE2.
|
||||
|
||||
@(private, enable_target_feature = "avx,avx2")
|
||||
chacha8rand_refill_simd256 :: proc(r: ^Default_Random_State) {
|
||||
// Initialize the base state.
|
||||
k: [^]u32 = (^u32)(raw_data(r._buf[RNG_OUTPUT_PER_ITER:]))
|
||||
s4_ := k[0]
|
||||
s5_ := k[1]
|
||||
s6_ := k[2]
|
||||
s7_ := k[3]
|
||||
s8_ := k[4]
|
||||
s9_ := k[5]
|
||||
s10_ := k[6]
|
||||
s11_ := k[7]
|
||||
|
||||
// 8-lane ChaCha8.
|
||||
s4 := u32x8{s4_, s4_, s4_, s4_, s4_, s4_, s4_, s4_}
|
||||
s5 := u32x8{s5_, s5_, s5_, s5_, s5_, s5_, s5_, s5_}
|
||||
s6 := u32x8{s6_, s6_, s6_, s6_, s6_, s6_, s6_, s6_}
|
||||
s7 := u32x8{s7_, s7_, s7_, s7_, s7_, s7_, s7_, s7_}
|
||||
s8 := u32x8{s8_, s8_, s8_, s8_, s8_, s8_, s8_, s8_}
|
||||
s9 := u32x8{s9_, s9_, s9_, s9_, s9_, s9_, s9_, s9_}
|
||||
s10 := u32x8{s10_, s10_, s10_, s10_, s10_, s10_, s10_, s10_}
|
||||
s11 := u32x8{s11_, s11_, s11_, s11_, s11_, s11_, s11_, s11_}
|
||||
s12 := u32x8{0, 1, 2, 3, 4, 5, 6, 7}
|
||||
s13, s14, s15: u32x8
|
||||
|
||||
u32x4 :: #simd[4]u32
|
||||
dst: [^]u32x4 = (^u32x4)(raw_data(r._buf[:]))
|
||||
|
||||
quarter_round := #force_inline proc "contextless" (a, b, c, d: u32x8) -> (u32x8, u32x8, u32x8, u32x8) {
|
||||
a, b, c, d := a, b, c, d
|
||||
|
||||
a = intrinsics.simd_add(a, b)
|
||||
d = intrinsics.simd_bit_xor(d, a)
|
||||
d = intrinsics.simd_bit_xor(intrinsics.simd_shl(d, _ROT_16), intrinsics.simd_shr(d, _ROT_16))
|
||||
|
||||
c = intrinsics.simd_add(c, d)
|
||||
b = intrinsics.simd_bit_xor(b, c)
|
||||
b = intrinsics.simd_bit_xor(intrinsics.simd_shl(b, _ROT_12L), intrinsics.simd_shr(b, _ROT_12R))
|
||||
|
||||
a = intrinsics.simd_add(a, b)
|
||||
d = intrinsics.simd_bit_xor(d, a)
|
||||
d = intrinsics.simd_bit_xor(intrinsics.simd_shl(d, _ROT_8L), intrinsics.simd_shr(d, _ROT_8R))
|
||||
|
||||
c = intrinsics.simd_add(c, d)
|
||||
b = intrinsics.simd_bit_xor(b, c)
|
||||
b = intrinsics.simd_bit_xor(intrinsics.simd_shl(b, _ROT_7L), intrinsics.simd_shr(b, _ROT_7R))
|
||||
|
||||
return a, b, c, d
|
||||
}
|
||||
|
||||
for _ in 0..<2 {
|
||||
x0, x1, x2, x3 := S0, S1, S2, S3
|
||||
x4, x5, x6, x7 := s4, s5, s6, s7
|
||||
x8, x9, x10, x11 := s8, s9, s10, s11
|
||||
x12, x13, x14, x15 := s12, s13, s14, s15
|
||||
|
||||
for i := CHACHA_ROUNDS; i > 0; i = i - 2 {
|
||||
x0, x4, x8, x12 = quarter_round(x0, x4, x8, x12)
|
||||
x1, x5, x9, x13 = quarter_round(x1, x5, x9, x13)
|
||||
x2, x6, x10, x14 = quarter_round(x2, x6, x10, x14)
|
||||
x3, x7, x11, x15 = quarter_round(x3, x7, x11, x15)
|
||||
|
||||
x0, x5, x10, x15 = quarter_round(x0, x5, x10, x15)
|
||||
x1, x6, x11, x12 = quarter_round(x1, x6, x11, x12)
|
||||
x2, x7, x8, x13 = quarter_round(x2, x7, x8, x13)
|
||||
x3, x4, x9, x14 = quarter_round(x3, x4, x9, x14)
|
||||
}
|
||||
|
||||
x4 = intrinsics.simd_add(x4, s4)
|
||||
x5 = intrinsics.simd_add(x5, s5)
|
||||
x6 = intrinsics.simd_add(x6, s6)
|
||||
x7 = intrinsics.simd_add(x7, s7)
|
||||
x8 = intrinsics.simd_add(x8, s8)
|
||||
x9 = intrinsics.simd_add(x9, s9)
|
||||
x10 = intrinsics.simd_add(x10, s10)
|
||||
x11 = intrinsics.simd_add(x11, s11)
|
||||
x13 = intrinsics.simd_add(x13, s13)
|
||||
x14 = intrinsics.simd_add(x14, s14)
|
||||
x15 = intrinsics.simd_add(x15, s15)
|
||||
|
||||
// Ok, now we have x0->x15 with 8 lanes, but we need to
|
||||
// output the first 4 blocks, then the second 4 blocks.
|
||||
//
|
||||
// LLVM appears not to consider "this instruction is totally
|
||||
// awful on the given microarchitcture", which leads to
|
||||
// `VPCOMPRESSED` being generated iff AVX512 support is
|
||||
// enabled for `intrinsics.simd_masked_compress_store`.
|
||||
// On Zen 4, this leads to a 50% performance regression vs
|
||||
// the 128-bit SIMD code.
|
||||
//
|
||||
// The fake intrinsic (because LLVM doesn't appear to have
|
||||
// an amd64 specific one), doesn't generate `VEXTRACTI128`,
|
||||
// but instead does cleverness without horrible regressions.
|
||||
|
||||
intrinsics.unaligned_store((^u32x4)(dst[0:]), _mm_mm256_extracti128_si256(x0, 0))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[1:]), _mm_mm256_extracti128_si256(x1, 0))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[2:]), _mm_mm256_extracti128_si256(x2, 0))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[3:]), _mm_mm256_extracti128_si256(x3, 0))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[4:]), _mm_mm256_extracti128_si256(x4, 0))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[5:]), _mm_mm256_extracti128_si256(x5, 0))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[6:]), _mm_mm256_extracti128_si256(x6, 0))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[7:]), _mm_mm256_extracti128_si256(x7, 0))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[8:]), _mm_mm256_extracti128_si256(x8, 0))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[9:]), _mm_mm256_extracti128_si256(x9, 0))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[10:]), _mm_mm256_extracti128_si256(x10, 0))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[11:]), _mm_mm256_extracti128_si256(x11, 0))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[12:]), _mm_mm256_extracti128_si256(x12, 0))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[13:]), _mm_mm256_extracti128_si256(x13, 0))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[14:]), _mm_mm256_extracti128_si256(x14, 0))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[15:]), _mm_mm256_extracti128_si256(x15, 0))
|
||||
|
||||
intrinsics.unaligned_store((^u32x4)(dst[16:]), _mm_mm256_extracti128_si256(x0, 1))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[17:]), _mm_mm256_extracti128_si256(x1, 1))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[18:]), _mm_mm256_extracti128_si256(x2, 1))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[19:]), _mm_mm256_extracti128_si256(x3, 1))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[20:]), _mm_mm256_extracti128_si256(x4, 1))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[21:]), _mm_mm256_extracti128_si256(x5, 1))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[22:]), _mm_mm256_extracti128_si256(x6, 1))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[23:]), _mm_mm256_extracti128_si256(x7, 1))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[24:]), _mm_mm256_extracti128_si256(x8, 1))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[25:]), _mm_mm256_extracti128_si256(x9, 1))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[26:]), _mm_mm256_extracti128_si256(x10, 1))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[27:]), _mm_mm256_extracti128_si256(x11, 1))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[28:]), _mm_mm256_extracti128_si256(x12, 1))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[29:]), _mm_mm256_extracti128_si256(x13, 1))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[30:]), _mm_mm256_extracti128_si256(x14, 1))
|
||||
intrinsics.unaligned_store((^u32x4)(dst[31:]), _mm_mm256_extracti128_si256(x15, 1))
|
||||
|
||||
s12 = intrinsics.simd_add(s12, _CTR_INC_8)
|
||||
|
||||
dst = dst[32:]
|
||||
}
|
||||
}
|
||||
|
||||
@(private = "file", require_results, enable_target_feature="avx2")
|
||||
_mm_mm256_extracti128_si256 :: #force_inline proc "c" (a: u32x8, $OFFSET: int) -> u32x4 {
|
||||
when OFFSET == 0 {
|
||||
return intrinsics.simd_shuffle(a, a, 0, 1, 2, 3)
|
||||
} else when OFFSET == 1 {
|
||||
return intrinsics.simd_shuffle(a, a, 4, 5, 6, 7)
|
||||
} else {
|
||||
#panic("chacha8rand: invalid offset")
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
WASM_Allocator :: struct #no_copy {
|
||||
WASM_Allocator :: struct {
|
||||
// The minimum alignment of allocations.
|
||||
alignment: uint,
|
||||
// A region that contains as payload a single forward linked list of pointers to
|
||||
|
||||
@@ -38,5 +38,5 @@ _read_writer_procedure := proc(stream_data: rawptr, mode: io.Stream_Mode, p: []b
|
||||
case .Query:
|
||||
return io.query_utility({.Flush, .Read, .Write, .Query})
|
||||
}
|
||||
return 0, .Empty
|
||||
return 0, .Unsupported
|
||||
}
|
||||
@@ -347,7 +347,7 @@ _reader_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offse
|
||||
case .Query:
|
||||
return io.query_utility({.Read, .Destroy, .Query})
|
||||
}
|
||||
return 0, .Empty
|
||||
return 0, .Unsupported
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -249,5 +249,5 @@ _writer_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offse
|
||||
case .Query:
|
||||
return io.query_utility({.Flush, .Write, .Destroy, .Query})
|
||||
}
|
||||
return 0, .Empty
|
||||
return 0, .Unsupported
|
||||
}
|
||||
|
||||
@@ -434,5 +434,5 @@ _buffer_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offse
|
||||
case .Query:
|
||||
return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Destroy, .Query})
|
||||
}
|
||||
return 0, .Empty
|
||||
return 0, .Unsupported
|
||||
}
|
||||
|
||||
@@ -160,6 +160,6 @@ _reader_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offse
|
||||
case .Query:
|
||||
return io.query_utility({.Read, .Read_At, .Seek, .Size, .Query})
|
||||
}
|
||||
return 0, .Empty
|
||||
return 0, .Unsupported
|
||||
}
|
||||
|
||||
|
||||
@@ -390,7 +390,7 @@ to_stream :: proc(file: ^FILE) -> io.Stream {
|
||||
}
|
||||
|
||||
case .Destroy:
|
||||
return 0, .Empty
|
||||
return 0, .Unsupported
|
||||
|
||||
case .Query:
|
||||
return io.query_utility({ .Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query })
|
||||
|
||||
@@ -3,7 +3,7 @@ package compress
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation, optimization.
|
||||
@@ -297,7 +297,7 @@ peek_data_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid
|
||||
curr := z.input->impl_seek(0, .Current) or_return
|
||||
r, e1 := io.to_reader_at(z.input)
|
||||
if !e1 {
|
||||
return T{}, .Empty
|
||||
return T{}, .Unsupported
|
||||
}
|
||||
when size <= 128 {
|
||||
b: [size]u8
|
||||
@@ -306,7 +306,7 @@ peek_data_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid
|
||||
}
|
||||
_, e2 := io.read_at(r, b[:], curr)
|
||||
if e2 != .None {
|
||||
return T{}, .Empty
|
||||
return T{}, .Unsupported
|
||||
}
|
||||
|
||||
res = (^T)(&b[0])^
|
||||
@@ -324,7 +324,7 @@ peek_data_at_offset_from_stream :: #force_inline proc(z: ^Context_Stream_Input,
|
||||
|
||||
r, e3 := io.to_reader_at(z.input)
|
||||
if !e3 {
|
||||
return T{}, .Empty
|
||||
return T{}, .Unsupported
|
||||
}
|
||||
when size <= 128 {
|
||||
b: [size]u8
|
||||
@@ -333,7 +333,7 @@ peek_data_at_offset_from_stream :: #force_inline proc(z: ^Context_Stream_Input,
|
||||
}
|
||||
_, e4 := io.read_at(r, b[:], pos)
|
||||
if e4 != .None {
|
||||
return T{}, .Empty
|
||||
return T{}, .Unsupported
|
||||
}
|
||||
|
||||
// Return read head to original position.
|
||||
|
||||
@@ -82,7 +82,7 @@ package compress_gzip
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
|
||||
@@ -2,7 +2,7 @@ package compress_gzip
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
|
||||
@@ -3,7 +3,7 @@ package compress_shoco
|
||||
|
||||
/*
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
|
||||
@@ -44,7 +44,7 @@ package compress_zlib
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
|
||||
@@ -3,7 +3,7 @@ package compress_zlib
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation, optimization.
|
||||
|
||||
@@ -105,8 +105,13 @@ This operation assumes that the small-array is large enough.
|
||||
|
||||
This will result in:
|
||||
- the value if 0 <= index < len
|
||||
- the zero value of the type if len < index < capacity
|
||||
- 'crash' if capacity < index or index < 0
|
||||
- raise a bounds check error if capacity <= index
|
||||
- the previous value if len < index < capacity, which defauls to T's zero value.
|
||||
|
||||
e.g. if you call `small_array.push(&a, 0, 1, 2)`, and `i := pop_back(&a)`,
|
||||
then `get(a, 2)` will return the earlier value `2` at that location.
|
||||
|
||||
See also `get_safe`, which returns T's zero value and `false` if `index` is out of bounds.
|
||||
|
||||
**Inputs**
|
||||
- `a`: The small-array
|
||||
@@ -125,8 +130,13 @@ This operation assumes that the small-array is large enough.
|
||||
|
||||
This will result in:
|
||||
- the pointer if 0 <= index < len
|
||||
- the pointer to the zero value if len < index < capacity
|
||||
- 'crash' if capacity < index or index < 0
|
||||
- raise a bounds check error if capacity <= index
|
||||
- a pointer to the previous value if len < index < capacity, which defauls to T's zero value.
|
||||
|
||||
e.g. if you call `small_array.push(&a, 0, 1, 2)`, and `i := pop_back(&a)`,
|
||||
then `get_ptr(a, 2)` will return a pointer to the slot containing the earlier value `2` at that location.
|
||||
|
||||
See also `get_ptr_safe`, which returns a nil pointer, and `false` if `index` is out of bounds.
|
||||
|
||||
**Inputs**
|
||||
- `a`: A pointer to the small-array
|
||||
@@ -309,7 +319,7 @@ Example:
|
||||
import "core:container/small_array"
|
||||
import "core:fmt"
|
||||
|
||||
non_zero_resize :: proc() {
|
||||
non_zero_resize_example :: proc() {
|
||||
a: small_array.Small_Array(5, int)
|
||||
|
||||
small_array.push_back(&a, 1)
|
||||
|
||||
@@ -29,4 +29,4 @@ constant-time byte comparison.
|
||||
|
||||
## License
|
||||
|
||||
This library is made available under the BSD-3 license.
|
||||
This library is made available under the zlib license.
|
||||
@@ -2,7 +2,7 @@ package _blake2
|
||||
|
||||
/*
|
||||
Copyright 2021 zhibog
|
||||
Made available under the BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
List of contributors:
|
||||
zhibog, dotbmp: Initial implementation.
|
||||
|
||||
@@ -2,7 +2,7 @@ package _sha3
|
||||
|
||||
/*
|
||||
Copyright 2021 zhibog
|
||||
Made available under the BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
List of contributors:
|
||||
zhibog, dotbmp: Initial implementation.
|
||||
|
||||
@@ -9,7 +9,7 @@ package blake2b
|
||||
|
||||
/*
|
||||
Copyright 2021 zhibog
|
||||
Made available under the BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
List of contributors:
|
||||
zhibog, dotbmp: Initial implementation.
|
||||
|
||||
@@ -9,7 +9,7 @@ package blake2s
|
||||
|
||||
/*
|
||||
Copyright 2021 zhibog
|
||||
Made available under the BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
List of contributors:
|
||||
zhibog, dotbmp: Initial implementation.
|
||||
|
||||
@@ -4,6 +4,10 @@ package crypto
|
||||
import "base:runtime"
|
||||
import "core:mem"
|
||||
|
||||
// HAS_RAND_BYTES is true iff the runtime provides a cryptographic
|
||||
// entropy source.
|
||||
HAS_RAND_BYTES :: runtime.HAS_RAND_BYTES
|
||||
|
||||
// compare_constant_time returns 1 iff a and b are equal, 0 otherwise.
|
||||
//
|
||||
// The execution time of this routine is constant regardless of the contents
|
||||
@@ -54,7 +58,7 @@ rand_bytes :: proc (dst: []byte) {
|
||||
// zero-fill the buffer first
|
||||
mem.zero_explicit(raw_data(dst), len(dst))
|
||||
|
||||
_rand_bytes(dst)
|
||||
runtime.rand_bytes(dst)
|
||||
}
|
||||
|
||||
// random_generator returns a `runtime.Random_Generator` backed by the
|
||||
|
||||
@@ -2,7 +2,7 @@ package crypto_hash
|
||||
|
||||
/*
|
||||
Copyright 2021 zhibog
|
||||
Made available under the BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
List of contributors:
|
||||
zhibog, dotbmp: Initial implementation.
|
||||
|
||||
@@ -10,7 +10,7 @@ package keccak
|
||||
|
||||
/*
|
||||
Copyright 2021 zhibog
|
||||
Made available under the BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
List of contributors:
|
||||
zhibog, dotbmp: Initial implementation.
|
||||
|
||||
@@ -12,7 +12,7 @@ package md5
|
||||
|
||||
/*
|
||||
Copyright 2021 zhibog
|
||||
Made available under the BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
List of contributors:
|
||||
zhibog, dotbmp: Initial implementation.
|
||||
|
||||
@@ -13,7 +13,7 @@ package sha1
|
||||
|
||||
/*
|
||||
Copyright 2021 zhibog
|
||||
Made available under the BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
List of contributors:
|
||||
zhibog, dotbmp: Initial implementation.
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
#+build freebsd, openbsd, netbsd
|
||||
package crypto
|
||||
|
||||
foreign import libc "system:c"
|
||||
|
||||
HAS_RAND_BYTES :: true
|
||||
|
||||
foreign libc {
|
||||
arc4random_buf :: proc(buf: [^]byte, nbytes: uint) ---
|
||||
}
|
||||
|
||||
@(private)
|
||||
_rand_bytes :: proc(dst: []byte) {
|
||||
arc4random_buf(raw_data(dst), len(dst))
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package crypto
|
||||
|
||||
import "core:fmt"
|
||||
|
||||
import CF "core:sys/darwin/CoreFoundation"
|
||||
import Sec "core:sys/darwin/Security"
|
||||
|
||||
HAS_RAND_BYTES :: true
|
||||
|
||||
@(private)
|
||||
_rand_bytes :: proc(dst: []byte) {
|
||||
err := Sec.RandomCopyBytes(count=len(dst), bytes=raw_data(dst))
|
||||
if err != .Success {
|
||||
msg := CF.StringCopyToOdinString(Sec.CopyErrorMessageString(err))
|
||||
fmt.panicf("crypto/rand_bytes: SecRandomCopyBytes returned non-zero result: %v %s", err, msg)
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
#+build !linux
|
||||
#+build !windows
|
||||
#+build !openbsd
|
||||
#+build !freebsd
|
||||
#+build !netbsd
|
||||
#+build !darwin
|
||||
#+build !js
|
||||
#+build !wasi
|
||||
package crypto
|
||||
|
||||
HAS_RAND_BYTES :: false
|
||||
|
||||
@(private)
|
||||
_rand_bytes :: proc(dst: []byte) {
|
||||
unimplemented("crypto: rand_bytes not supported on this OS")
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package crypto
|
||||
|
||||
foreign import "odin_env"
|
||||
foreign odin_env {
|
||||
@(link_name = "rand_bytes")
|
||||
env_rand_bytes :: proc "contextless" (buf: []byte) ---
|
||||
}
|
||||
|
||||
HAS_RAND_BYTES :: true
|
||||
|
||||
@(private)
|
||||
_MAX_PER_CALL_BYTES :: 65536 // 64kiB
|
||||
|
||||
@(private)
|
||||
_rand_bytes :: proc(dst: []byte) {
|
||||
dst := dst
|
||||
|
||||
for len(dst) > 0 {
|
||||
to_read := min(len(dst), _MAX_PER_CALL_BYTES)
|
||||
env_rand_bytes(dst[:to_read])
|
||||
|
||||
dst = dst[to_read:]
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package crypto
|
||||
|
||||
import "core:fmt"
|
||||
|
||||
import "core:sys/linux"
|
||||
|
||||
HAS_RAND_BYTES :: true
|
||||
|
||||
@(private)
|
||||
_MAX_PER_CALL_BYTES :: 33554431 // 2^25 - 1
|
||||
|
||||
@(private)
|
||||
_rand_bytes :: proc (dst: []byte) {
|
||||
dst := dst
|
||||
l := len(dst)
|
||||
|
||||
for l > 0 {
|
||||
to_read := min(l, _MAX_PER_CALL_BYTES)
|
||||
n_read, errno := linux.getrandom(dst[:to_read], {})
|
||||
#partial switch errno {
|
||||
case .NONE:
|
||||
// Do nothing
|
||||
case .EINTR:
|
||||
// Call interupted by a signal handler, just retry the
|
||||
// request.
|
||||
continue
|
||||
case .ENOSYS:
|
||||
// The kernel is apparently prehistoric (< 3.17 circa 2014)
|
||||
// and does not support getrandom.
|
||||
panic("crypto: getrandom not available in kernel")
|
||||
case:
|
||||
// All other failures are things that should NEVER happen
|
||||
// unless the kernel interface changes (ie: the Linux
|
||||
// developers break userland).
|
||||
fmt.panicf("crypto: getrandom failed: %v", errno)
|
||||
}
|
||||
l -= n_read
|
||||
dst = dst[n_read:]
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package crypto
|
||||
|
||||
import "core:fmt"
|
||||
import "core:sys/wasm/wasi"
|
||||
|
||||
HAS_RAND_BYTES :: true
|
||||
|
||||
@(private)
|
||||
_rand_bytes :: proc(dst: []byte) {
|
||||
if err := wasi.random_get(dst); err != nil {
|
||||
fmt.panicf("crypto: wasi.random_get failed: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package crypto
|
||||
|
||||
import win32 "core:sys/windows"
|
||||
import "core:os"
|
||||
import "core:fmt"
|
||||
|
||||
HAS_RAND_BYTES :: true
|
||||
|
||||
@(private)
|
||||
_rand_bytes :: proc(dst: []byte) {
|
||||
ret := os.Platform_Error(win32.BCryptGenRandom(nil, raw_data(dst), u32(len(dst)), win32.BCRYPT_USE_SYSTEM_PREFERRED_RNG))
|
||||
if ret != nil {
|
||||
#partial switch ret {
|
||||
case os.ERROR_INVALID_HANDLE:
|
||||
// The handle to the first parameter is invalid.
|
||||
// This should not happen here, since we explicitly pass nil to it
|
||||
panic("crypto: BCryptGenRandom Invalid handle for hAlgorithm")
|
||||
case os.ERROR_INVALID_PARAMETER:
|
||||
// One of the parameters was invalid
|
||||
panic("crypto: BCryptGenRandom Invalid parameter")
|
||||
case:
|
||||
// Unknown error
|
||||
fmt.panicf("crypto: BCryptGenRandom failed: %d\n", ret)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ package sha2
|
||||
|
||||
/*
|
||||
Copyright 2021 zhibog
|
||||
Made available under the BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
List of contributors:
|
||||
zhibog, dotbmp: Initial implementation.
|
||||
|
||||
@@ -12,7 +12,7 @@ package sha3
|
||||
|
||||
/*
|
||||
Copyright 2021 zhibog
|
||||
Made available under the BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
List of contributors:
|
||||
zhibog, dotbmp: Initial implementation.
|
||||
|
||||
@@ -11,7 +11,7 @@ package shake
|
||||
|
||||
/*
|
||||
Copyright 2021 zhibog
|
||||
Made available under the BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
List of contributors:
|
||||
zhibog, dotbmp: Initial implementation.
|
||||
|
||||
@@ -10,11 +10,11 @@ See:
|
||||
package siphash
|
||||
|
||||
/*
|
||||
Copyright 2022 zhibog
|
||||
Made available under the BSD-3 license.
|
||||
Copyright 2022 zhibog
|
||||
Made available under Odin's license.
|
||||
|
||||
List of contributors:
|
||||
zhibog: Initial implementation.
|
||||
List of contributors:
|
||||
zhibog: Initial implementation.
|
||||
*/
|
||||
|
||||
import "core:crypto"
|
||||
@@ -22,7 +22,7 @@ import "core:encoding/endian"
|
||||
import "core:math/bits"
|
||||
|
||||
/*
|
||||
High level API
|
||||
High level API
|
||||
*/
|
||||
|
||||
KEY_SIZE :: 16
|
||||
@@ -215,7 +215,7 @@ verify_4_8 :: proc {
|
||||
}
|
||||
|
||||
/*
|
||||
Low level API
|
||||
Low level API
|
||||
*/
|
||||
|
||||
init :: proc(ctx: ^Context, key: []byte, c_rounds, d_rounds: int) {
|
||||
|
||||
@@ -8,7 +8,7 @@ package sm3
|
||||
|
||||
/*
|
||||
Copyright 2021 zhibog
|
||||
Made available under the BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
List of contributors:
|
||||
zhibog, dotbmp: Initial implementation.
|
||||
|
||||
@@ -26,23 +26,39 @@ ENC_TABLE := [64]byte {
|
||||
|
||||
PADDING :: '='
|
||||
|
||||
DEC_TABLE := [128]int {
|
||||
-1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, 62, -1, -1, -1, 63,
|
||||
DEC_TABLE := [256]u8 {
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 62, 0, 0, 0, 63,
|
||||
52, 53, 54, 55, 56, 57, 58, 59,
|
||||
60, 61, -1, -1, -1, -1, -1, -1,
|
||||
-1, 0, 1, 2, 3, 4, 5, 6,
|
||||
60, 61, 0, 0, 0, 0, 0, 0,
|
||||
0, 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, -1, -1, -1, -1, -1,
|
||||
-1, 26, 27, 28, 29, 30, 31, 32,
|
||||
23, 24, 25, 0, 0, 0, 0, 0,
|
||||
0, 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, -1, -1, -1, -1, -1,
|
||||
49, 50, 51, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
}
|
||||
|
||||
encode :: proc(data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocator) -> (encoded: string, err: mem.Allocator_Error) #optional_allocator_error {
|
||||
@@ -120,10 +136,10 @@ decode_into :: proc(w: io.Writer, data: string, DEC_TBL := DEC_TABLE) -> io.Erro
|
||||
i, j: int
|
||||
for ; j + 3 <= length; i, j = i + 4, j + 3 {
|
||||
#no_bounds_check {
|
||||
c0 = DEC_TBL[data[i]]
|
||||
c1 = DEC_TBL[data[i + 1]]
|
||||
c2 = DEC_TBL[data[i + 2]]
|
||||
c3 = DEC_TBL[data[i + 3]]
|
||||
c0 = int(DEC_TBL[data[i]])
|
||||
c1 = int(DEC_TBL[data[i + 1]])
|
||||
c2 = int(DEC_TBL[data[i + 2]])
|
||||
c3 = int(DEC_TBL[data[i + 3]])
|
||||
|
||||
b0 = (c0 << 2) | (c1 >> 4)
|
||||
b1 = (c1 << 4) | (c2 >> 2)
|
||||
@@ -140,9 +156,9 @@ decode_into :: proc(w: io.Writer, data: string, DEC_TBL := DEC_TABLE) -> io.Erro
|
||||
rest := length - j
|
||||
if rest > 0 {
|
||||
#no_bounds_check {
|
||||
c0 = DEC_TBL[data[i]]
|
||||
c1 = DEC_TBL[data[i + 1]]
|
||||
c2 = DEC_TBL[data[i + 2]]
|
||||
c0 = int(DEC_TBL[data[i]])
|
||||
c1 = int(DEC_TBL[data[i + 1]])
|
||||
c2 = int(DEC_TBL[data[i + 2]])
|
||||
|
||||
b0 = (c0 << 2) | (c1 >> 4)
|
||||
b1 = (c1 << 4) | (c2 >> 2)
|
||||
|
||||
@@ -130,9 +130,9 @@ tag_time_unmarshal :: proc(_: ^Tag_Implementation, d: Decoder, _: Tag_Number, v:
|
||||
case .U8, .U16, .U32, .U64, .Neg_U8, .Neg_U16, .Neg_U32, .Neg_U64:
|
||||
switch &dst in v {
|
||||
case time.Time:
|
||||
i: i64
|
||||
_unmarshal_any_ptr(d, &i, hdr) or_return
|
||||
dst = time.unix(i64(i), 0)
|
||||
secs: i64
|
||||
_unmarshal_any_ptr(d, &secs, hdr) or_return
|
||||
dst = time.unix(i64(secs), 0)
|
||||
return
|
||||
case:
|
||||
return _unmarshal_value(d, v, hdr)
|
||||
@@ -152,19 +152,23 @@ tag_time_unmarshal :: proc(_: ^Tag_Implementation, d: Decoder, _: Tag_Number, v:
|
||||
|
||||
case:
|
||||
maj, add := _header_split(hdr)
|
||||
if maj == .Other {
|
||||
i := _decode_tiny_u8(add) or_return
|
||||
|
||||
switch &dst in v {
|
||||
case time.Time:
|
||||
dst = time.unix(i64(i), 0)
|
||||
case:
|
||||
if _assign_int(v, i) { return }
|
||||
}
|
||||
secs: u8
|
||||
#partial switch maj {
|
||||
case .Unsigned:
|
||||
secs = _decode_tiny_u8(add) or_return
|
||||
case .Other:
|
||||
secs = u8(_decode_tiny_simple(add) or_return)
|
||||
case:
|
||||
return .Bad_Tag_Value
|
||||
}
|
||||
|
||||
// Only numbers and floats are allowed in this tag.
|
||||
return .Bad_Tag_Value
|
||||
switch &dst in v {
|
||||
case time.Time:
|
||||
dst = time.unix(i64(secs), 0)
|
||||
return
|
||||
case:
|
||||
if _assign_int(v, secs) { return }
|
||||
}
|
||||
}
|
||||
|
||||
return _unsupported(v, hdr)
|
||||
|
||||
@@ -15,7 +15,7 @@ package encoding_unicode_entity
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
|
||||
@@ -137,7 +137,7 @@ parse_value :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err:
|
||||
case .Ident:
|
||||
if p.spec == .MJSON {
|
||||
advance_token(p)
|
||||
return string(token.text), nil
|
||||
return clone_string(token.text, p.allocator, loc)
|
||||
}
|
||||
|
||||
case .String:
|
||||
|
||||
@@ -188,8 +188,18 @@ assign_float :: proc(val: any, f: $T) -> bool {
|
||||
|
||||
|
||||
@(private)
|
||||
unmarshal_string_token :: proc(p: ^Parser, val: any, str: string, ti: ^reflect.Type_Info) -> (ok: bool, err: Error) {
|
||||
val := val
|
||||
unmarshal_string_token :: proc(p: ^Parser, val: any, token: Token, ti: ^reflect.Type_Info) -> (ok: bool, err: Error) {
|
||||
str: string
|
||||
switch {
|
||||
case token.kind == .String:
|
||||
str = unquote_string(token, p.spec, p.allocator) or_return
|
||||
case:
|
||||
str = clone_string(token.text, p.allocator) or_return
|
||||
}
|
||||
defer if !ok || (val.id != string && val.id != cstring) {
|
||||
delete(str, p.allocator)
|
||||
}
|
||||
|
||||
switch &dst in val {
|
||||
case string:
|
||||
dst = str
|
||||
@@ -339,7 +349,7 @@ unmarshal_value :: proc(p: ^Parser, v: any) -> (err: Unmarshal_Error) {
|
||||
case .Ident:
|
||||
advance_token(p)
|
||||
if p.spec == .MJSON {
|
||||
if unmarshal_string_token(p, any{v.data, ti.id}, token.text, ti) or_return {
|
||||
if unmarshal_string_token(p, any{v.data, ti.id}, token, ti) or_return {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -347,18 +357,10 @@ unmarshal_value :: proc(p: ^Parser, v: any) -> (err: Unmarshal_Error) {
|
||||
|
||||
case .String:
|
||||
advance_token(p)
|
||||
str := unquote_string(token, p.spec, p.allocator) or_return
|
||||
dest := any{v.data, ti.id}
|
||||
if !(unmarshal_string_token(p, dest, str, ti) or_return) {
|
||||
delete(str, p.allocator)
|
||||
return UNSUPPORTED_TYPE
|
||||
if unmarshal_string_token(p, any{v.data, ti.id}, token, ti) or_return {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch destv in dest {
|
||||
case string, cstring:
|
||||
case: delete(str, p.allocator)
|
||||
}
|
||||
return nil
|
||||
return UNSUPPORTED_TYPE
|
||||
|
||||
case .Open_Brace:
|
||||
return unmarshal_object(p, v, .Close_Brace)
|
||||
|
||||
@@ -1,28 +1,17 @@
|
||||
BSD 3-Clause License
|
||||
Copyright (c) 2024-2025 Feoramund, Ginger Bill. All rights reserved.
|
||||
|
||||
Copyright (c) 2024, Feoramund
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
@@ -2,7 +2,7 @@ package encoding_varint
|
||||
|
||||
/*
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
|
||||
@@ -4,7 +4,7 @@ package encoding_xml
|
||||
An XML 1.0 / 1.1 parser
|
||||
|
||||
Copyright 2021-2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
A from-scratch XML implementation, loosely modeled on the [spec](https://www.w3.org/TR/2006/REC-xml11-20060816).
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ package encoding_xml
|
||||
An XML 1.0 / 1.1 parser
|
||||
|
||||
Copyright 2021-2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
This file contains helper functions.
|
||||
*/
|
||||
|
||||
@@ -4,7 +4,7 @@ package encoding_xml
|
||||
An XML 1.0 / 1.1 parser
|
||||
|
||||
Copyright 2021-2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
A from-scratch XML implementation, loosely modeled on the [spec](https://www.w3.org/TR/2006/REC-xml11-20060816).
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package encoding_xml
|
||||
An XML 1.0 / 1.1 parser
|
||||
|
||||
2021-2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
available under Odin's BSD-3 license.
|
||||
available under Odin's license.
|
||||
|
||||
List of contributors:
|
||||
- Jeroen van Rijn: Initial implementation.
|
||||
|
||||
@@ -1,28 +1,17 @@
|
||||
BSD 3-Clause License
|
||||
Copyright (c) 2024-2025 Feoramund, Ginger Bill. All rights reserved.
|
||||
|
||||
Copyright (c) 2024, Feoramund
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
@@ -693,7 +693,7 @@ wprintf :: proc(w: io.Writer, fmt: string, args: ..any, flush := true, newline :
|
||||
fi: Info
|
||||
end := len(fmt)
|
||||
unused_args: bit_set[0 ..< MAX_CHECKED_ARGS]
|
||||
for i in 0 ..< len(args) {
|
||||
for _, i in args {
|
||||
unused_args += {i}
|
||||
}
|
||||
|
||||
@@ -816,7 +816,7 @@ wprintf :: proc(w: io.Writer, fmt: string, args: ..any, flush := true, newline :
|
||||
}
|
||||
}
|
||||
|
||||
if unused_args != {} {
|
||||
if unused_args != nil {
|
||||
// Use default options when formatting extra arguments.
|
||||
extra_fi := Info { writer = fi.writer, n = fi.n }
|
||||
|
||||
@@ -1067,7 +1067,7 @@ _fmt_int :: proc(fi: ^Info, u: u64, base: int, is_signed: bool, bit_size: int, d
|
||||
}
|
||||
}
|
||||
|
||||
buf: [256]byte
|
||||
buf: [BUF_SIZE]byte
|
||||
start := 0
|
||||
|
||||
if fi.hash && !is_signed {
|
||||
@@ -1281,7 +1281,7 @@ _fmt_memory :: proc(fi: ^Info, u: u64, is_signed: bool, bit_size: int, units: st
|
||||
if !fi.plus {
|
||||
// Strip sign from "+<value>" but not "+Inf".
|
||||
if str[0] == '+' && str[1] != 'I' {
|
||||
str = str[1:]
|
||||
str = str[1:]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1711,7 +1711,7 @@ fmt_enum :: proc(fi: ^Info, v: any, verb: rune) {
|
||||
}
|
||||
|
||||
type_info := type_info_of(v.id)
|
||||
#partial switch e in type_info.variant {
|
||||
#partial switch &e in type_info.variant {
|
||||
case: fmt_bad_verb(fi, verb)
|
||||
case runtime.Type_Info_Enum:
|
||||
switch verb {
|
||||
@@ -1751,7 +1751,7 @@ stored_enum_value_to_string :: proc(enum_type: ^runtime.Type_Info, ev: runtime.T
|
||||
et := runtime.type_info_base(enum_type)
|
||||
ev := ev
|
||||
ev += runtime.Type_Info_Enum_Value(offset)
|
||||
#partial switch e in et.variant {
|
||||
#partial switch &e in et.variant {
|
||||
case: return "", false
|
||||
case runtime.Type_Info_Enum:
|
||||
if reflect.is_string(e.base) {
|
||||
@@ -1788,7 +1788,7 @@ fmt_bit_set :: proc(fi: ^Info, v: any, name: string = "", verb: rune = 'v') {
|
||||
return false
|
||||
}
|
||||
t := runtime.type_info_base(ti)
|
||||
#partial switch info in t.variant {
|
||||
#partial switch &info in t.variant {
|
||||
case runtime.Type_Info_Integer:
|
||||
switch info.endianness {
|
||||
case .Platform: return false
|
||||
@@ -1802,7 +1802,7 @@ fmt_bit_set :: proc(fi: ^Info, v: any, name: string = "", verb: rune = 'v') {
|
||||
byte_swap :: bits.byte_swap
|
||||
|
||||
type_info := type_info_of(v.id)
|
||||
#partial switch info in type_info.variant {
|
||||
#partial switch &info in type_info.variant {
|
||||
case runtime.Type_Info_Named:
|
||||
val := v
|
||||
val.id = info.base.id
|
||||
@@ -1937,7 +1937,7 @@ fmt_write_array :: proc(fi: ^Info, array_data: rawptr, count: int, elem_size: in
|
||||
}
|
||||
fi.record_level += 1
|
||||
defer fi.record_level -= 1
|
||||
|
||||
|
||||
if fi.hash {
|
||||
io.write_byte(fi.writer, '\n', &fi.n)
|
||||
defer fmt_write_indent(fi)
|
||||
@@ -2528,7 +2528,7 @@ fmt_named :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Named)
|
||||
}
|
||||
}
|
||||
|
||||
#partial switch b in info.base.variant {
|
||||
#partial switch &b in info.base.variant {
|
||||
case runtime.Type_Info_Struct:
|
||||
fmt_struct(fi, v, verb, b, info.name)
|
||||
case runtime.Type_Info_Bit_Field:
|
||||
@@ -2794,7 +2794,7 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
|
||||
fi.ignore_user_formatters = false
|
||||
|
||||
type_info := type_info_of(v.id)
|
||||
switch info in type_info.variant {
|
||||
switch &info in type_info.variant {
|
||||
case runtime.Type_Info_Any: // Ignore
|
||||
case runtime.Type_Info_Parameters: // Ignore
|
||||
|
||||
@@ -2819,7 +2819,7 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
|
||||
|
||||
elem := runtime.type_info_base(info.elem)
|
||||
if elem != nil {
|
||||
#partial switch e in elem.variant {
|
||||
#partial switch &e in elem.variant {
|
||||
case runtime.Type_Info_Array,
|
||||
runtime.Type_Info_Slice,
|
||||
runtime.Type_Info_Dynamic_Array,
|
||||
@@ -2881,7 +2881,7 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
|
||||
return
|
||||
}
|
||||
|
||||
#partial switch e in elem.variant {
|
||||
#partial switch &e in elem.variant {
|
||||
case runtime.Type_Info_Integer:
|
||||
switch verb {
|
||||
case 's', 'q':
|
||||
@@ -3238,7 +3238,7 @@ fmt_arg :: proc(fi: ^Info, arg: any, verb: rune) {
|
||||
|
||||
base_arg := arg
|
||||
base_arg.id = runtime.typeid_base(base_arg.id)
|
||||
switch a in base_arg {
|
||||
switch &a in base_arg {
|
||||
case bool: fmt_bool(fi, a, verb)
|
||||
case b8: fmt_bool(fi, bool(a), verb)
|
||||
case b16: fmt_bool(fi, bool(a), verb)
|
||||
|
||||
@@ -19,7 +19,7 @@ write_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte,
|
||||
write(fd, p)
|
||||
return i64(len(p)), nil
|
||||
}
|
||||
return 0, .Empty
|
||||
return 0, .Unsupported
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
|
||||
@@ -6,7 +6,7 @@ package xxhash
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
|
||||
Made available under Odin's BSD-3 license, based on the original C code.
|
||||
Made available under Odin's license, based on the original C code.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
|
||||
@@ -4,7 +4,7 @@ package xxhash
|
||||
An implementation of Yann Collet's [xxhash Fast Hash Algorithm](https://cyan4973.github.io/xxHash/).
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
|
||||
Made available under Odin's BSD-3 license, based on the original C code.
|
||||
Made available under Odin's license, based on the original C code.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
|
||||
@@ -4,7 +4,7 @@ package xxhash
|
||||
An implementation of Yann Collet's [xxhash Fast Hash Algorithm](https://cyan4973.github.io/xxHash/).
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
|
||||
Made available under Odin's BSD-3 license, based on the original C code.
|
||||
Made available under Odin's license, based on the original C code.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
|
||||
@@ -4,7 +4,7 @@ package xxhash
|
||||
An implementation of Yann Collet's [xxhash Fast Hash Algorithm](https://cyan4973.github.io/xxHash/).
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
|
||||
Made available under Odin's BSD-3 license, based on the original C code.
|
||||
Made available under Odin's license, based on the original C code.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
|
||||
@@ -4,7 +4,7 @@ package xxhash
|
||||
An implementation of Yann Collet's [xxhash Fast Hash Algorithm](https://cyan4973.github.io/xxHash/).
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
|
||||
Made available under Odin's BSD-3 license, based on the original C code.
|
||||
Made available under Odin's license, based on the original C code.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
|
||||
@@ -2,7 +2,7 @@ package image
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation, optimization.
|
||||
|
||||
@@ -3,7 +3,7 @@ package png
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
|
||||
@@ -5,7 +5,7 @@ package qoi
|
||||
|
||||
/*
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
|
||||
@@ -3,7 +3,7 @@ package tga
|
||||
|
||||
/*
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
|
||||
@@ -38,8 +38,11 @@ Error :: enum i32 {
|
||||
// This is usually a sign of a broken `io.Reader` implementation
|
||||
No_Progress,
|
||||
|
||||
// Invalid whence argument
|
||||
Invalid_Whence,
|
||||
// Invalid offset
|
||||
Invalid_Offset,
|
||||
// Invalid "unread" operation
|
||||
Invalid_Unread,
|
||||
|
||||
Negative_Read,
|
||||
@@ -50,8 +53,19 @@ Error :: enum i32 {
|
||||
// Unknown means that an error has occurred but cannot be categorized
|
||||
Unknown,
|
||||
|
||||
// Empty is returned when a procedure has not been implemented for an io.Stream
|
||||
Empty = -1,
|
||||
// Indicates that an attempt to retrieve a Stream's size was made, but the
|
||||
// stream doesn't have a size.
|
||||
No_Size,
|
||||
|
||||
Permission_Denied,
|
||||
|
||||
// Stream cannot be used since it has been Closed
|
||||
Closed,
|
||||
|
||||
// Unsupported is returned when a procedure has not been implemented for an io.Stream
|
||||
Unsupported = -1,
|
||||
|
||||
Empty = Unsupported,
|
||||
}
|
||||
|
||||
Stream_Mode :: enum {
|
||||
@@ -102,7 +116,7 @@ destroy :: proc(s: Stream) -> (err: Error) {
|
||||
if s.procedure != nil {
|
||||
_, err = s.procedure(s.data, .Destroy, nil, 0, nil)
|
||||
} else {
|
||||
err = .Empty
|
||||
err = .Unsupported
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -138,7 +152,7 @@ read :: proc(s: Reader, p: []byte, n_read: ^int = nil) -> (n: int, err: Error) {
|
||||
n = int(n64)
|
||||
if n_read != nil { n_read^ += n }
|
||||
} else {
|
||||
err = .Empty
|
||||
err = .Unsupported
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -151,7 +165,7 @@ write :: proc(s: Writer, p: []byte, n_written: ^int = nil) -> (n: int, err: Erro
|
||||
n = int(n64)
|
||||
if n_written != nil { n_written^ += n }
|
||||
} else {
|
||||
err = .Empty
|
||||
err = .Unsupported
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -167,7 +181,7 @@ seek :: proc(s: Seeker, offset: i64, whence: Seek_From) -> (n: i64, err: Error)
|
||||
if s.procedure != nil {
|
||||
n, err = s.procedure(s.data, .Seek, nil, offset, whence)
|
||||
} else {
|
||||
err = .Empty
|
||||
err = .Unsupported
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -192,7 +206,7 @@ flush :: proc(s: Flusher) -> (err: Error) {
|
||||
size :: proc(s: Stream) -> (n: i64, err: Error) {
|
||||
if s.procedure != nil {
|
||||
n, err = s.procedure(s.data, .Size, nil, 0, nil)
|
||||
if err == .Empty {
|
||||
if err == .Unsupported {
|
||||
n = 0
|
||||
curr := seek(s, 0, .Current) or_return
|
||||
end := seek(s, 0, .End) or_return
|
||||
@@ -200,7 +214,7 @@ size :: proc(s: Stream) -> (n: i64, err: Error) {
|
||||
n = end
|
||||
}
|
||||
} else {
|
||||
err = .Empty
|
||||
err = .Unsupported
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -217,7 +231,7 @@ read_at :: proc(r: Reader_At, p: []byte, offset: i64, n_read: ^int = nil) -> (n:
|
||||
if r.procedure != nil {
|
||||
n64: i64
|
||||
n64, err = r.procedure(r.data, .Read_At, p, offset, nil)
|
||||
if err != .Empty {
|
||||
if err != .Unsupported {
|
||||
n = int(n64)
|
||||
} else {
|
||||
curr := seek(r, offset, .Current) or_return
|
||||
@@ -229,7 +243,7 @@ read_at :: proc(r: Reader_At, p: []byte, offset: i64, n_read: ^int = nil) -> (n:
|
||||
}
|
||||
if n_read != nil { n_read^ += n }
|
||||
} else {
|
||||
err = .Empty
|
||||
err = .Unsupported
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -243,7 +257,7 @@ write_at :: proc(w: Writer_At, p: []byte, offset: i64, n_written: ^int = nil) ->
|
||||
if w.procedure != nil {
|
||||
n64: i64
|
||||
n64, err = w.procedure(w.data, .Write_At, p, offset, nil)
|
||||
if err != .Empty {
|
||||
if err != .Unsupported {
|
||||
n = int(n64)
|
||||
} else {
|
||||
curr := seek(w, offset, .Current) or_return
|
||||
@@ -255,7 +269,7 @@ write_at :: proc(w: Writer_At, p: []byte, offset: i64, n_written: ^int = nil) ->
|
||||
}
|
||||
if n_written != nil { n_written^ += n }
|
||||
} else {
|
||||
err = .Empty
|
||||
err = .Unsupported
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -440,7 +454,7 @@ copy_n :: proc(dst: Writer, src: Reader, n: i64) -> (written: i64, err: Error) {
|
||||
@(private)
|
||||
_copy_buffer :: proc(dst: Writer, src: Reader, buf: []byte) -> (written: i64, err: Error) {
|
||||
if dst.procedure == nil || src.procedure == nil {
|
||||
return 0, .Empty
|
||||
return 0, .Unsupported
|
||||
}
|
||||
buf := buf
|
||||
if buf == nil {
|
||||
|
||||
@@ -8,7 +8,7 @@ _multi_reader_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte, of
|
||||
if mode == .Query {
|
||||
return query_utility({.Read, .Query})
|
||||
} else if mode != .Read {
|
||||
return 0, .Empty
|
||||
return 0, .Unsupported
|
||||
}
|
||||
mr := (^Multi_Reader)(stream_data)
|
||||
for len(mr.readers) > 0 {
|
||||
@@ -61,7 +61,7 @@ _multi_writer_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte, of
|
||||
if mode == .Query {
|
||||
return query_utility({.Write, .Query})
|
||||
} else if mode != .Write {
|
||||
return 0, .Empty
|
||||
return 0, .Unsupported
|
||||
}
|
||||
mw := (^Multi_Writer)(stream_data)
|
||||
for w in mw.writers {
|
||||
|
||||
@@ -354,7 +354,7 @@ _tee_reader_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte, offs
|
||||
case .Query:
|
||||
return query_utility({.Read, .Query})
|
||||
}
|
||||
return 0, .Empty
|
||||
return 0, .Unsupported
|
||||
}
|
||||
|
||||
// tee_reader_init returns a Reader that writes to 'w' what it reads from 'r'
|
||||
@@ -404,7 +404,7 @@ _limited_reader_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte,
|
||||
case .Query:
|
||||
return query_utility({.Read, .Query})
|
||||
}
|
||||
return 0, .Empty
|
||||
return 0, .Unsupported
|
||||
}
|
||||
|
||||
limited_reader_init :: proc(l: ^Limited_Reader, r: Reader, n: i64) -> Reader {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#+build !freestanding
|
||||
package log
|
||||
|
||||
import "base:runtime"
|
||||
import "core:fmt"
|
||||
|
||||
@@ -2,7 +2,7 @@ package math_big
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
This file collects public proc maps and their aliases.
|
||||
*/
|
||||
|
||||
@@ -2,10 +2,11 @@ package math_big
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
*/
|
||||
|
||||
import "base:intrinsics"
|
||||
import "base:runtime"
|
||||
|
||||
/*
|
||||
TODO: Make the tunables runtime adjustable where practical.
|
||||
@@ -138,11 +139,12 @@ Flags :: bit_set[Flag; u8]
|
||||
/*
|
||||
Errors are a strict superset of runtime.Allocation_Error.
|
||||
*/
|
||||
Error :: enum int {
|
||||
Okay = 0,
|
||||
Error :: enum byte {
|
||||
None = 0,
|
||||
Out_Of_Memory = 1,
|
||||
Invalid_Pointer = 2,
|
||||
Invalid_Argument = 3,
|
||||
Mode_Not_Implemented = 4, // Allocation
|
||||
|
||||
Assignment_To_Immutable = 10,
|
||||
Max_Iterations_Reached = 11,
|
||||
@@ -158,13 +160,19 @@ Error :: enum int {
|
||||
Cannot_Write_File = 52,
|
||||
|
||||
Unimplemented = 127,
|
||||
|
||||
Okay = None,
|
||||
}
|
||||
|
||||
#assert(intrinsics.type_is_superset_of(Error, runtime.Allocator_Error))
|
||||
|
||||
|
||||
Error_String :: #sparse[Error]string{
|
||||
.Okay = "Okay",
|
||||
.None = "None",
|
||||
.Out_Of_Memory = "Out of memory",
|
||||
.Invalid_Pointer = "Invalid pointer",
|
||||
.Invalid_Argument = "Invalid argument",
|
||||
.Mode_Not_Implemented = "Allocation mode not implemented",
|
||||
|
||||
.Assignment_To_Immutable = "Assignment to immutable",
|
||||
.Max_Iterations_Reached = "Max iterations reached",
|
||||
@@ -215,9 +223,15 @@ when MATH_BIG_FORCE_64_BIT || (!MATH_BIG_FORCE_32_BIT && size_of(rawptr) == 8) {
|
||||
*/
|
||||
DIGIT :: distinct u64
|
||||
_WORD :: distinct u128
|
||||
// Base 10 extraction constants
|
||||
ITOA_DIVISOR :: DIGIT(1_000_000_000_000_000_000)
|
||||
ITOA_COUNT :: 18
|
||||
} else {
|
||||
DIGIT :: distinct u32
|
||||
_WORD :: distinct u64
|
||||
// Base 10 extraction constants
|
||||
ITOA_DIVISOR :: DIGIT(100_000_000)
|
||||
ITOA_COUNT :: 8
|
||||
}
|
||||
#assert(size_of(_WORD) == 2 * size_of(DIGIT))
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package math_big
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
*/
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
@@ -2,7 +2,7 @@ package math_big
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
========================== Low-level routines ==========================
|
||||
|
||||
@@ -746,19 +746,15 @@ internal_int_divmod :: proc(quotient, remainder, numerator, denominator: ^Int, a
|
||||
|
||||
if (denominator.used > 2 * MUL_KARATSUBA_CUTOFF) && (denominator.used <= (numerator.used / 3) * 2) {
|
||||
assert(denominator.used >= 160 && numerator.used >= 240, "MUL_KARATSUBA_CUTOFF global not properly set.")
|
||||
err = _private_int_div_recursive(quotient, remainder, numerator, denominator)
|
||||
return _private_int_div_recursive(quotient, remainder, numerator, denominator)
|
||||
} else {
|
||||
when true {
|
||||
err = #force_inline _private_int_div_school(quotient, remainder, numerator, denominator)
|
||||
} else {
|
||||
/*
|
||||
NOTE(Jeroen): We no longer need or use `_private_int_div_small`.
|
||||
We'll keep it around for a bit until we're reasonably certain div_school is bug free.
|
||||
*/
|
||||
err = _private_int_div_small(quotient, remainder, numerator, denominator)
|
||||
}
|
||||
return #force_inline _private_int_div_school(quotient, remainder, numerator, denominator)
|
||||
/*
|
||||
NOTE(Jeroen): We no longer need or use `_private_int_div_small`.
|
||||
We'll keep it around for a bit until we're reasonably certain div_school is bug free.
|
||||
*/
|
||||
// err = _private_int_div_small(quotient, remainder, numerator, denominator)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1203,14 +1199,14 @@ internal_cmp_mag :: internal_compare_magnitude
|
||||
bool := a < b
|
||||
*/
|
||||
internal_int_less_than :: #force_inline proc(a, b: ^Int) -> (less_than: bool) {
|
||||
return internal_cmp(a, b) == -1
|
||||
return internal_cmp(a, b) < 0
|
||||
}
|
||||
|
||||
/*
|
||||
bool := a < b
|
||||
*/
|
||||
internal_int_less_than_digit :: #force_inline proc(a: ^Int, b: DIGIT) -> (less_than: bool) {
|
||||
return internal_cmp_digit(a, b) == -1
|
||||
return internal_cmp_digit(a, b) < 0
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1218,7 +1214,7 @@ internal_int_less_than_digit :: #force_inline proc(a: ^Int, b: DIGIT) -> (less_t
|
||||
Compares the magnitudes only, ignores the sign.
|
||||
*/
|
||||
internal_int_less_than_abs :: #force_inline proc(a, b: ^Int) -> (less_than: bool) {
|
||||
return internal_cmp_mag(a, b) == -1
|
||||
return internal_cmp_mag(a, b) < 0
|
||||
}
|
||||
|
||||
internal_less_than :: proc {
|
||||
@@ -2181,7 +2177,11 @@ internal_int_grow :: proc(a: ^Int, digits: int, allow_shrink := false, allocator
|
||||
If not yet initialized, initialize the `digit` backing with the allocator we were passed.
|
||||
*/
|
||||
if cap == 0 {
|
||||
a.digit = make([dynamic]DIGIT, needed, allocator)
|
||||
mem_err: mem.Allocator_Error
|
||||
a.digit, mem_err = make([dynamic]DIGIT, needed, allocator)
|
||||
if mem_err != nil {
|
||||
return cast(Error)mem_err
|
||||
}
|
||||
} else if cap < needed {
|
||||
/*
|
||||
`[dynamic]DIGIT` already knows what allocator was used for it, so resize will do the right thing.
|
||||
@@ -2791,7 +2791,8 @@ internal_int_count_lsb :: proc(a: ^Int) -> (count: int, err: Error) {
|
||||
x *= _DIGIT_BITS
|
||||
x += internal_count_lsb(q)
|
||||
} else {
|
||||
lnz := []int{
|
||||
@(static, rodata)
|
||||
lnz := [?]int{
|
||||
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
||||
}
|
||||
|
||||
@@ -2933,7 +2934,7 @@ internal_int_zero_unused :: #force_inline proc(dest: ^Int, old_used := -1) {
|
||||
If we don't pass the number of previously used DIGITs, we zero all remaining ones.
|
||||
*/
|
||||
zero_count: int
|
||||
if old_used == -1 {
|
||||
if old_used < 0 {
|
||||
zero_count = len(dest.digit) - dest.used
|
||||
} else {
|
||||
zero_count = old_used - dest.used
|
||||
|
||||
@@ -2,7 +2,7 @@ package math_big
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
An arbitrary precision mathematics implementation in Odin.
|
||||
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
|
||||
|
||||
@@ -2,7 +2,7 @@ package math_big
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
An arbitrary precision mathematics implementation in Odin.
|
||||
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
|
||||
@@ -134,7 +134,8 @@ internal_int_kronecker :: proc(a, p: ^Int, allocator := context.allocator) -> (k
|
||||
a1, p1, r := &Int{}, &Int{}, &Int{}
|
||||
defer internal_destroy(a1, p1, r)
|
||||
|
||||
table := []int{0, 1, 0, -1, 0, -1, 0, 1}
|
||||
@(static, rodata)
|
||||
table := [?]int{0, 1, 0, -1, 0, -1, 0, 1}
|
||||
|
||||
if internal_int_is_zero(p) {
|
||||
if a.used == 1 && a.digit[0] == 1 {
|
||||
@@ -1207,7 +1208,7 @@ internal_random_prime :: proc(a: ^Int, size_in_bits: int, trials: int, flags :=
|
||||
/*
|
||||
Automatically choose the number of Rabin-Miller trials?
|
||||
*/
|
||||
if trials == -1 {
|
||||
if trials < 0 {
|
||||
trials = number_of_rabin_miller_trials(size_in_bits)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package math_big
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
An arbitrary precision mathematics implementation in Odin.
|
||||
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
|
||||
@@ -1050,7 +1050,6 @@ _private_int_div_school :: proc(quotient, remainder, numerator, denominator: ^In
|
||||
Normalize both x and y, ensure that y >= b/2, [b == 2**MP_DIGIT_BIT]
|
||||
*/
|
||||
norm := internal_count_bits(y) % _DIGIT_BITS
|
||||
|
||||
if norm < _DIGIT_BITS - 1 {
|
||||
norm = (_DIGIT_BITS - 1) - norm
|
||||
internal_shl(x, x, norm) or_return
|
||||
@@ -1070,33 +1069,29 @@ _private_int_div_school :: proc(quotient, remainder, numerator, denominator: ^In
|
||||
y = y*b**{n-t}
|
||||
*/
|
||||
|
||||
|
||||
_private_int_shl_leg(y, n - t) or_return
|
||||
|
||||
gte := internal_gte(x, y)
|
||||
for gte {
|
||||
for internal_gte(x, y) {
|
||||
q.digit[n - t] += 1
|
||||
internal_sub(x, x, y) or_return
|
||||
gte = internal_gte(x, y)
|
||||
}
|
||||
|
||||
/*
|
||||
Reset y by shifting it back down.
|
||||
*/
|
||||
_private_int_shr_leg(y, n - t)
|
||||
|
||||
/*
|
||||
Step 3. for i from n down to (t + 1).
|
||||
*/
|
||||
#no_bounds_check for i := n; i >= (t + 1); i -= 1 {
|
||||
if i > x.used { continue }
|
||||
|
||||
/*
|
||||
step 3.1 if xi == yt then set q{i-t-1} to b-1, otherwise set q{i-t-1} to (xi*b + x{i-1})/yt
|
||||
*/
|
||||
if x.digit[i] == y.digit[t] {
|
||||
q.digit[(i - t) - 1] = 1 << (_DIGIT_BITS - 1)
|
||||
q.digit[(i - t) - 1] = 1 << _DIGIT_BITS - 1
|
||||
} else {
|
||||
|
||||
tmp := _WORD(x.digit[i]) << _DIGIT_BITS
|
||||
tmp |= _WORD(x.digit[i - 1])
|
||||
tmp /= _WORD(y.digit[t])
|
||||
@@ -1667,7 +1662,7 @@ _private_int_log :: proc(a: ^Int, base: DIGIT, allocator := context.allocator) -
|
||||
defer internal_destroy(bracket_low, bracket_high, bracket_mid, t, bi_base)
|
||||
|
||||
ic := #force_inline internal_cmp(a, base)
|
||||
if ic == -1 || ic == 0 {
|
||||
if ic <= 0 {
|
||||
return 1 if ic == 0 else 0, nil
|
||||
}
|
||||
defer if err != nil {
|
||||
@@ -2497,9 +2492,9 @@ _private_int_exponent_mod :: proc(res, G, X, P: ^Int, redmode: int, allocator :=
|
||||
bitcnt -= 1
|
||||
if bitcnt == 0 {
|
||||
/*
|
||||
If digidx == -1 we are out of digits.
|
||||
If digidx < 0 we are out of digits.
|
||||
*/
|
||||
if digidx == -1 { break }
|
||||
if digidx < 0 { break }
|
||||
|
||||
/*
|
||||
Read next digit and reset the bitcnt.
|
||||
@@ -2753,9 +2748,9 @@ _private_int_exponent_mod_fast :: proc(res, G, X, P: ^Int, redmode: int, allocat
|
||||
bitcnt -= 1
|
||||
if bitcnt == 0 {
|
||||
/*
|
||||
If digidx == -1 we are out of digits so break.
|
||||
If digidx < 0 we are out of digits so break.
|
||||
*/
|
||||
if digidx == -1 { break }
|
||||
if digidx < 0 { break }
|
||||
|
||||
/*
|
||||
Read next digit and reset the bitcnt.
|
||||
@@ -3222,6 +3217,7 @@ _private_int_shr_leg :: proc(quotient: ^Int, digits: int, allocator := context.a
|
||||
Tables used by `internal_*` and `_*`.
|
||||
*/
|
||||
|
||||
@(rodata)
|
||||
_private_int_rem_128 := [?]DIGIT{
|
||||
0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1,
|
||||
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1,
|
||||
@@ -3234,6 +3230,7 @@ _private_int_rem_128 := [?]DIGIT{
|
||||
}
|
||||
#assert(128 * size_of(DIGIT) == size_of(_private_int_rem_128))
|
||||
|
||||
@(rodata)
|
||||
_private_int_rem_105 := [?]DIGIT{
|
||||
0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1,
|
||||
0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1,
|
||||
@@ -3246,6 +3243,7 @@ _private_int_rem_105 := [?]DIGIT{
|
||||
#assert(105 * size_of(DIGIT) == size_of(_private_int_rem_105))
|
||||
|
||||
_PRIME_TAB_SIZE :: 256
|
||||
@(rodata)
|
||||
_private_prime_table := [_PRIME_TAB_SIZE]DIGIT{
|
||||
0x0002, 0x0003, 0x0005, 0x0007, 0x000B, 0x000D, 0x0011, 0x0013,
|
||||
0x0017, 0x001D, 0x001F, 0x0025, 0x0029, 0x002B, 0x002F, 0x0035,
|
||||
@@ -3286,6 +3284,7 @@ _private_prime_table := [_PRIME_TAB_SIZE]DIGIT{
|
||||
#assert(_PRIME_TAB_SIZE * size_of(DIGIT) == size_of(_private_prime_table))
|
||||
|
||||
when MATH_BIG_FORCE_64_BIT || (!MATH_BIG_FORCE_32_BIT && size_of(rawptr) == 8) {
|
||||
@(rodata)
|
||||
_factorial_table := [35]_WORD{
|
||||
/* f(00): */ 1,
|
||||
/* f(01): */ 1,
|
||||
@@ -3324,6 +3323,7 @@ when MATH_BIG_FORCE_64_BIT || (!MATH_BIG_FORCE_32_BIT && size_of(rawptr) == 8) {
|
||||
/* f(34): */ 295_232_799_039_604_140_847_618_609_643_520_000_000,
|
||||
}
|
||||
} else {
|
||||
@(rodata)
|
||||
_factorial_table := [21]_WORD{
|
||||
/* f(00): */ 1,
|
||||
/* f(01): */ 1,
|
||||
|
||||
@@ -3,7 +3,7 @@ package math_big
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
An arbitrary precision mathematics implementation in Odin.
|
||||
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
|
||||
@@ -593,7 +593,7 @@ int_less_than :: #force_inline proc(a, b: ^Int, allocator := context.allocator)
|
||||
c: int
|
||||
c, err = cmp(a, b)
|
||||
|
||||
return c == -1, err
|
||||
return c < 0, err
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -608,7 +608,7 @@ int_less_than_digit :: #force_inline proc(a: ^Int, b: DIGIT, allocator := contex
|
||||
c: int
|
||||
c, err = cmp(a, b)
|
||||
|
||||
return c == -1, err
|
||||
return c < 0, err
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -624,7 +624,7 @@ int_less_than_abs :: #force_inline proc(a, b: ^Int, allocator := context.allocat
|
||||
c: int
|
||||
c, err = cmp_mag(a, b)
|
||||
|
||||
return c == -1, err
|
||||
return c < 0, err
|
||||
}
|
||||
|
||||
less_than :: proc {
|
||||
|
||||
@@ -2,7 +2,7 @@ package math_big
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
An arbitrary precision mathematics implementation in Odin.
|
||||
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
|
||||
@@ -44,7 +44,11 @@ int_itoa_string :: proc(a: ^Int, radix := i8(10), zero_terminate := false, alloc
|
||||
/*
|
||||
Allocate the buffer we need.
|
||||
*/
|
||||
buffer := make([]u8, size)
|
||||
buffer, mem_err := make([]u8, size)
|
||||
if mem_err != nil {
|
||||
err = cast(Error)mem_err
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Write the digits out into the buffer.
|
||||
@@ -106,7 +110,7 @@ int_itoa_raw :: proc(a: ^Int, radix: i8, buffer: []u8, size := int(-1), zero_ter
|
||||
/*
|
||||
We weren't given a size. Let's compute it.
|
||||
*/
|
||||
if size == -1 {
|
||||
if size < 0 {
|
||||
size = radix_size(a, radix, zero_terminate) or_return
|
||||
}
|
||||
|
||||
@@ -181,16 +185,15 @@ int_itoa_raw :: proc(a: ^Int, radix: i8, buffer: []u8, size := int(-1), zero_ter
|
||||
/*
|
||||
Fast path for radixes that are a power of two.
|
||||
*/
|
||||
count := count_bits(a) or_return
|
||||
|
||||
if is_power_of_two(int(radix)) {
|
||||
if zero_terminate {
|
||||
available -= 1
|
||||
buffer[available] = 0
|
||||
}
|
||||
|
||||
shift, count: int
|
||||
// mask := _WORD(radix - 1);
|
||||
shift, err = log(DIGIT(radix), 2)
|
||||
count, err = count_bits(a)
|
||||
shift := log(DIGIT(radix), 2) or_return
|
||||
digit: _WORD
|
||||
|
||||
for offset := 0; offset < count; offset += shift {
|
||||
@@ -220,7 +223,17 @@ int_itoa_raw :: proc(a: ^Int, radix: i8, buffer: []u8, size := int(-1), zero_ter
|
||||
return written, nil
|
||||
}
|
||||
|
||||
return _itoa_raw_full(a, radix, buffer, zero_terminate)
|
||||
// NOTE(Jeroen): The new method is faster for an `Int` up to ~32768 bits in size with optimizations.
|
||||
// At `.None` or `.Minimal`, it appears to always be faster.
|
||||
// If we optimize `itoa` further, this needs to be evaluated.
|
||||
itoa_method := _itoa_raw_full
|
||||
|
||||
when !MATH_BIG_FORCE_32_BIT && ODIN_OPTIMIZATION_MODE >= .Size {
|
||||
if count >= 32768 {
|
||||
itoa_method = _itoa_raw_old
|
||||
}
|
||||
}
|
||||
return itoa_method(a, radix, buffer, zero_terminate)
|
||||
}
|
||||
|
||||
itoa :: proc{int_itoa_string, int_itoa_raw}
|
||||
@@ -353,8 +366,7 @@ radix_size :: proc(a: ^Int, radix: i8, zero_terminate := false, allocator := con
|
||||
internal_set(la, bit_count) or_return
|
||||
|
||||
/* k = floor(2^29/log_2(radix)) + 1 */
|
||||
lb := _log_bases
|
||||
internal_set(k, lb[radix]) or_return
|
||||
internal_set(k, _log_bases[radix]) or_return
|
||||
|
||||
/* n = floor((la * k) / 2^29) + 1 */
|
||||
internal_mul(k, la, k) or_return
|
||||
@@ -560,8 +572,9 @@ internal_int_unpack :: proc(a: ^Int, buf: []$T, nails := 0, order := Order.LSB_F
|
||||
*/
|
||||
|
||||
_RADIX_SIZE_SCALE :: 29
|
||||
_log_bases :: [65]u32{
|
||||
0, 0, 0x20000001, 0x14309399, 0x10000001,
|
||||
@(rodata)
|
||||
_log_bases := [65]u32{
|
||||
0, 0, 0x20000001, 0x14309399, 0x10000001,
|
||||
0xdc81a35, 0xc611924, 0xb660c9e, 0xaaaaaab, 0xa1849cd,
|
||||
0x9a209a9, 0x94004e1, 0x8ed19c2, 0x8a5ca7d, 0x867a000,
|
||||
0x830cee3, 0x8000001, 0x7d42d60, 0x7ac8b32, 0x7887847,
|
||||
@@ -580,6 +593,7 @@ _log_bases :: [65]u32{
|
||||
Characters used in radix conversions.
|
||||
*/
|
||||
RADIX_TABLE := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/"
|
||||
@(rodata)
|
||||
RADIX_TABLE_REVERSE := [RADIX_TABLE_REVERSE_SIZE]u8{
|
||||
0x3e, 0xff, 0xff, 0xff, 0x3f, 0x00, 0x01, 0x02, 0x03, 0x04, /* +,-./01234 */
|
||||
0x05, 0x06, 0x07, 0x08, 0x09, 0xff, 0xff, 0xff, 0xff, 0xff, /* 56789:;<=> */
|
||||
@@ -596,14 +610,108 @@ RADIX_TABLE_REVERSE_SIZE :: 80
|
||||
Stores a bignum as a ASCII string in a given radix (2..64)
|
||||
The buffer must be appropriately sized. This routine doesn't check.
|
||||
*/
|
||||
|
||||
_itoa_raw_full :: proc(a: ^Int, radix: i8, buffer: []u8, zero_terminate := false, allocator := context.allocator) -> (written: int, err: Error) {
|
||||
assert_if_nil(a)
|
||||
context.allocator = allocator
|
||||
|
||||
temp, denominator := &Int{}, &Int{}
|
||||
// Calculate largest radix^n that fits within _DIGIT_BITS
|
||||
divisor := _WORD(ITOA_DIVISOR)
|
||||
digit_count := ITOA_COUNT
|
||||
_radix := DIGIT(radix)
|
||||
|
||||
internal_copy(temp, a) or_return
|
||||
internal_set(denominator, radix) or_return
|
||||
if radix != 10 {
|
||||
i := _WORD(1)
|
||||
digit_count = -1
|
||||
for i < _WORD(1 << _DIGIT_BITS) {
|
||||
divisor = _WORD(i)
|
||||
i *= _WORD(radix)
|
||||
digit_count += 1
|
||||
}
|
||||
}
|
||||
|
||||
temp := &Int{}
|
||||
internal_copy(temp, a) or_return
|
||||
defer internal_destroy(temp)
|
||||
|
||||
available := len(buffer)
|
||||
if zero_terminate {
|
||||
available -= 1
|
||||
buffer[available] = 0
|
||||
}
|
||||
|
||||
if a.sign == .Negative {
|
||||
temp.sign = .Zero_or_Positive
|
||||
}
|
||||
|
||||
q := &Int{}
|
||||
defer internal_destroy(q)
|
||||
|
||||
remainder: DIGIT
|
||||
for {
|
||||
internal_grow(q, temp.used) or_return
|
||||
q.used = temp.used
|
||||
q.sign = temp.sign
|
||||
|
||||
w := _WORD(0)
|
||||
|
||||
for ix := temp.used - 1; ix >= 0; ix -= 1 {
|
||||
t := DIGIT(0)
|
||||
w = (w << _WORD(_DIGIT_BITS) | _WORD(temp.digit[ix]))
|
||||
if w >= divisor {
|
||||
t = DIGIT(w / divisor)
|
||||
w -= _WORD(t) * divisor
|
||||
}
|
||||
q.digit[ix] = t
|
||||
}
|
||||
remainder = DIGIT(w)
|
||||
|
||||
internal_clamp(q)
|
||||
q, temp = temp, q
|
||||
|
||||
count := digit_count
|
||||
for available > 0 && count > 0 {
|
||||
available -= 1
|
||||
buffer[available] = RADIX_TABLE[remainder % _radix]
|
||||
remainder /= _radix
|
||||
count -= 1
|
||||
}
|
||||
|
||||
if temp.used == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Remove leading zero if we ended up with one.
|
||||
if buffer[available] == '0' {
|
||||
available += 1
|
||||
}
|
||||
|
||||
if a.sign == .Negative {
|
||||
available -= 1
|
||||
buffer[available] = '-'
|
||||
}
|
||||
|
||||
/*
|
||||
If we overestimated the size, we need to move the buffer left.
|
||||
*/
|
||||
written = len(buffer) - available
|
||||
if written < len(buffer) {
|
||||
diff := len(buffer) - written
|
||||
mem.copy(&buffer[0], &buffer[diff], written)
|
||||
}
|
||||
return written, nil
|
||||
}
|
||||
|
||||
// Old internal digit extraction procedure.
|
||||
// We're keeping this around as ground truth for the tests.
|
||||
_itoa_raw_old :: proc(a: ^Int, radix: i8, buffer: []u8, zero_terminate := false, allocator := context.allocator) -> (written: int, err: Error) {
|
||||
assert_if_nil(a)
|
||||
context.allocator = allocator
|
||||
|
||||
temp := &Int{}
|
||||
internal_copy(temp, a) or_return
|
||||
defer internal_destroy(temp)
|
||||
|
||||
available := len(buffer)
|
||||
if zero_terminate {
|
||||
@@ -618,7 +726,6 @@ _itoa_raw_full :: proc(a: ^Int, radix: i8, buffer: []u8, zero_terminate := false
|
||||
remainder: DIGIT
|
||||
for {
|
||||
if remainder, err = #force_inline internal_divmod(temp, temp, DIGIT(radix)); err != nil {
|
||||
internal_destroy(temp, denominator)
|
||||
return len(buffer) - available, err
|
||||
}
|
||||
available -= 1
|
||||
@@ -633,8 +740,6 @@ _itoa_raw_full :: proc(a: ^Int, radix: i8, buffer: []u8, zero_terminate := false
|
||||
buffer[available] = '-'
|
||||
}
|
||||
|
||||
internal_destroy(temp, denominator)
|
||||
|
||||
/*
|
||||
If we overestimated the size, we need to move the buffer left.
|
||||
*/
|
||||
|
||||
@@ -3,7 +3,7 @@ package math_big
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
Made available under Odin's license.
|
||||
|
||||
A BigInt implementation in Odin.
|
||||
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
// Easing procedures and flux easing used for animations.
|
||||
// Easing procedures used for animations.
|
||||
package ease
|
||||
|
||||
import "core:math"
|
||||
@require import "core:math"
|
||||
import "base:intrinsics"
|
||||
import "core:time"
|
||||
|
||||
@(private) PI_2 :: math.PI / 2
|
||||
|
||||
@@ -174,7 +173,7 @@ exponential_in_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_f
|
||||
if p == 0.0 || p == 1.0 {
|
||||
return p
|
||||
}
|
||||
|
||||
|
||||
if p < 0.5 {
|
||||
return 0.5 * math.pow(2, (20 * p) - 10)
|
||||
} else {
|
||||
@@ -307,224 +306,51 @@ Ease :: enum {
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
ease :: proc "contextless" (type: Ease, p: $T) -> T
|
||||
where intrinsics.type_is_float(T) {
|
||||
ease :: proc "contextless" (type: Ease, p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
switch type {
|
||||
case .Linear: return p
|
||||
case .Linear: return p
|
||||
|
||||
case .Quadratic_In: return quadratic_in(p)
|
||||
case .Quadratic_Out: return quadratic_out(p)
|
||||
case .Quadratic_In_Out: return quadratic_in_out(p)
|
||||
case .Quadratic_In: return quadratic_in(p)
|
||||
case .Quadratic_Out: return quadratic_out(p)
|
||||
case .Quadratic_In_Out: return quadratic_in_out(p)
|
||||
|
||||
case .Cubic_In: return cubic_in(p)
|
||||
case .Cubic_Out: return cubic_out(p)
|
||||
case .Cubic_In_Out: return cubic_in_out(p)
|
||||
case .Cubic_In: return cubic_in(p)
|
||||
case .Cubic_Out: return cubic_out(p)
|
||||
case .Cubic_In_Out: return cubic_in_out(p)
|
||||
|
||||
case .Quartic_In: return quartic_in(p)
|
||||
case .Quartic_Out: return quartic_out(p)
|
||||
case .Quartic_In_Out: return quartic_in_out(p)
|
||||
case .Quartic_In: return quartic_in(p)
|
||||
case .Quartic_Out: return quartic_out(p)
|
||||
case .Quartic_In_Out: return quartic_in_out(p)
|
||||
|
||||
case .Quintic_In: return quintic_in(p)
|
||||
case .Quintic_Out: return quintic_out(p)
|
||||
case .Quintic_In_Out: return quintic_in_out(p)
|
||||
case .Quintic_In: return quintic_in(p)
|
||||
case .Quintic_Out: return quintic_out(p)
|
||||
case .Quintic_In_Out: return quintic_in_out(p)
|
||||
|
||||
case .Sine_In: return sine_in(p)
|
||||
case .Sine_Out: return sine_out(p)
|
||||
case .Sine_In_Out: return sine_in_out(p)
|
||||
case .Sine_In: return sine_in(p)
|
||||
case .Sine_Out: return sine_out(p)
|
||||
case .Sine_In_Out: return sine_in_out(p)
|
||||
|
||||
case .Circular_In: return circular_in(p)
|
||||
case .Circular_Out: return circular_out(p)
|
||||
case .Circular_In_Out: return circular_in_out(p)
|
||||
case .Circular_In: return circular_in(p)
|
||||
case .Circular_Out: return circular_out(p)
|
||||
case .Circular_In_Out: return circular_in_out(p)
|
||||
|
||||
case .Exponential_In: return exponential_in(p)
|
||||
case .Exponential_Out: return exponential_out(p)
|
||||
case .Exponential_In: return exponential_in(p)
|
||||
case .Exponential_Out: return exponential_out(p)
|
||||
case .Exponential_In_Out: return exponential_in_out(p)
|
||||
|
||||
case .Elastic_In: return elastic_in(p)
|
||||
case .Elastic_Out: return elastic_out(p)
|
||||
case .Elastic_In_Out: return elastic_in_out(p)
|
||||
case .Elastic_In: return elastic_in(p)
|
||||
case .Elastic_Out: return elastic_out(p)
|
||||
case .Elastic_In_Out: return elastic_in_out(p)
|
||||
|
||||
case .Back_In: return back_in(p)
|
||||
case .Back_Out: return back_out(p)
|
||||
case .Back_In_Out: return back_in_out(p)
|
||||
case .Back_In: return back_in(p)
|
||||
case .Back_Out: return back_out(p)
|
||||
case .Back_In_Out: return back_in_out(p)
|
||||
|
||||
case .Bounce_In: return bounce_in(p)
|
||||
case .Bounce_Out: return bounce_out(p)
|
||||
case .Bounce_In_Out: return bounce_in_out(p)
|
||||
case .Bounce_In: return bounce_in(p)
|
||||
case .Bounce_Out: return bounce_out(p)
|
||||
case .Bounce_In_Out: return bounce_in_out(p)
|
||||
}
|
||||
|
||||
// in case type was invalid
|
||||
return 0
|
||||
}
|
||||
Flux_Map :: struct($T: typeid) {
|
||||
values: map[^T]Flux_Tween(T),
|
||||
keys_to_be_deleted: [dynamic]^T,
|
||||
}
|
||||
|
||||
Flux_Tween :: struct($T: typeid) {
|
||||
value: ^T,
|
||||
start: T,
|
||||
diff: T,
|
||||
goal: T,
|
||||
|
||||
delay: f64, // in seconds
|
||||
duration: time.Duration,
|
||||
|
||||
progress: f64,
|
||||
rate: f64,
|
||||
type: Ease,
|
||||
|
||||
inited: bool,
|
||||
|
||||
// callbacks, data can be set, will be pushed to callback
|
||||
data: rawptr, // by default gets set to value input
|
||||
on_start: proc(flux: ^Flux_Map(T), data: rawptr),
|
||||
on_update: proc(flux: ^Flux_Map(T), data: rawptr),
|
||||
on_complete: proc(flux: ^Flux_Map(T), data: rawptr),
|
||||
}
|
||||
|
||||
// init flux map to a float type and a wanted cap
|
||||
@(require_results)
|
||||
flux_init :: proc($T: typeid, value_capacity := 8) -> Flux_Map(T) where intrinsics.type_is_float(T) {
|
||||
return {
|
||||
values = make(map[^T]Flux_Tween(T), value_capacity),
|
||||
keys_to_be_deleted = make([dynamic]^T, 0, value_capacity),
|
||||
}
|
||||
}
|
||||
|
||||
// delete map content
|
||||
flux_destroy :: proc(flux: Flux_Map($T)) where intrinsics.type_is_float(T) {
|
||||
delete(flux.values)
|
||||
delete(flux.keys_to_be_deleted)
|
||||
}
|
||||
|
||||
// clear map content, stops all animations
|
||||
flux_clear :: proc(flux: ^Flux_Map($T)) where intrinsics.type_is_float(T) {
|
||||
clear(&flux.values)
|
||||
}
|
||||
|
||||
// append / overwrite existing tween value to parameters
|
||||
// rest is initialized in flux_tween_init, inside update
|
||||
// return value can be used to set callbacks
|
||||
@(require_results)
|
||||
flux_to :: proc(
|
||||
flux: ^Flux_Map($T),
|
||||
value: ^T,
|
||||
goal: T,
|
||||
type: Ease = .Quadratic_Out,
|
||||
duration: time.Duration = time.Second,
|
||||
delay: f64 = 0,
|
||||
) -> (tween: ^Flux_Tween(T)) where intrinsics.type_is_float(T) {
|
||||
if res, ok := &flux.values[value]; ok {
|
||||
tween = res
|
||||
} else {
|
||||
flux.values[value] = {}
|
||||
tween = &flux.values[value]
|
||||
}
|
||||
|
||||
tween^ = {
|
||||
value = value,
|
||||
goal = goal,
|
||||
duration = duration,
|
||||
delay = delay,
|
||||
type = type,
|
||||
data = value,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// init internal properties
|
||||
flux_tween_init :: proc(tween: ^Flux_Tween($T), duration: time.Duration) where intrinsics.type_is_float(T) {
|
||||
tween.inited = true
|
||||
tween.start = tween.value^
|
||||
tween.diff = tween.goal - tween.value^
|
||||
s := time.duration_seconds(duration)
|
||||
tween.rate = duration > 0 ? 1.0 / s : 0
|
||||
tween.progress = duration > 0 ? 0 : 1
|
||||
}
|
||||
|
||||
// update all tweens, wait for their delay if one exists
|
||||
// calls callbacks in all stages, when they're filled
|
||||
// deletes tween from the map after completion
|
||||
flux_update :: proc(flux: ^Flux_Map($T), dt: f64) where intrinsics.type_is_float(T) {
|
||||
clear(&flux.keys_to_be_deleted)
|
||||
|
||||
for key, &tween in flux.values {
|
||||
delay_remainder := f64(0)
|
||||
|
||||
// Update delay if necessary.
|
||||
if tween.delay > 0 {
|
||||
tween.delay -= dt
|
||||
|
||||
if tween.delay < 0 {
|
||||
// We finished the delay, but in doing so consumed part of this frame's `dt` budget.
|
||||
// Keep track of it so we can apply it to this tween without affecting others.
|
||||
delay_remainder = tween.delay
|
||||
// We're done with this delay.
|
||||
tween.delay = 0
|
||||
}
|
||||
}
|
||||
|
||||
// We either had no delay, or the delay has been consumed.
|
||||
if tween.delay <= 0 {
|
||||
if !tween.inited {
|
||||
flux_tween_init(&tween, tween.duration)
|
||||
|
||||
if tween.on_start != nil {
|
||||
tween.on_start(flux, tween.data)
|
||||
}
|
||||
}
|
||||
|
||||
// If part of the `dt` budget was consumed this frame, then `delay_remainder` will be
|
||||
// that remainder, a negative value. Adding it to `dt` applies what's left of the `dt`
|
||||
// to the tween so it advances properly, instead of too much or little.
|
||||
tween.progress += tween.rate * (dt + delay_remainder)
|
||||
x := tween.progress >= 1 ? 1 : ease(tween.type, tween.progress)
|
||||
tween.value^ = tween.start + tween.diff * T(x)
|
||||
|
||||
if tween.on_update != nil {
|
||||
tween.on_update(flux, tween.data)
|
||||
}
|
||||
|
||||
if tween.progress >= 1 {
|
||||
// append keys to array that will be deleted after the loop
|
||||
append(&flux.keys_to_be_deleted, key)
|
||||
|
||||
if tween.on_complete != nil {
|
||||
tween.on_complete(flux, tween.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// loop through keys that should be deleted from the map
|
||||
if len(flux.keys_to_be_deleted) != 0 {
|
||||
for key in flux.keys_to_be_deleted {
|
||||
delete_key(&flux.values, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// stop a specific key inside the map
|
||||
// returns true when it successfully removed the key
|
||||
@(require_results)
|
||||
flux_stop :: proc(flux: ^Flux_Map($T), key: ^T) -> bool where intrinsics.type_is_float(T) {
|
||||
if key in flux.values {
|
||||
delete_key(&flux.values, key)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// returns the amount of time left for the tween animation, if the key exists in the map
|
||||
// returns 0 if the tween doesnt exist on the map
|
||||
@(require_results)
|
||||
flux_tween_time_left :: proc(flux: Flux_Map($T), key: ^T) -> f64 {
|
||||
if tween, ok := flux.values[key]; ok {
|
||||
return ((1 - tween.progress) * tween.rate) + tween.delay
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
250
core/math/ease/ease_inverse.odin
Normal file
250
core/math/ease/ease_inverse.odin
Normal file
@@ -0,0 +1,250 @@
|
||||
// Inverse easing procedures
|
||||
// These are the mathematical inverses of the corresponding easing functions,
|
||||
// allowing you to reverse the transformation:
|
||||
// if y = ease_fn(x), then x = ease_fn_inverse(y) + some_imprecision
|
||||
package ease
|
||||
|
||||
@require import "core:math"
|
||||
import "base:intrinsics"
|
||||
|
||||
// Helper for handling negative bases with fractional exponents
|
||||
// since math.pow(negative, fraction) returns NaN
|
||||
@(private)
|
||||
_signed_pow :: proc "contextless" (x, exp: $T) -> T where intrinsics.type_is_float(T) {
|
||||
if x >= 0 {
|
||||
return math.pow(x, exp)
|
||||
} else {
|
||||
return -math.pow(-x, exp)
|
||||
}
|
||||
}
|
||||
|
||||
@(private) PI_2_INV :: 2 / math.PI
|
||||
|
||||
// Inverse of quadratic_in
|
||||
// x = sqrt(y)
|
||||
@(require_results)
|
||||
quadratic_in_inverse :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return math.sqrt(p)
|
||||
}
|
||||
|
||||
// Inverse of quadratic_out
|
||||
// x = 1 - sqrt(1 - y)
|
||||
@(require_results)
|
||||
quadratic_out_inverse :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return 1 - math.sqrt(1 - p)
|
||||
}
|
||||
|
||||
// Inverse of quadratic_in_out
|
||||
// x = sqrt(y/2) ; [0, 0.5)
|
||||
// x = 1 - sqrt((1-y)/2) ; [0.5, 1]
|
||||
@(require_results)
|
||||
quadratic_in_out_inverse :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
if p < 0.5 {
|
||||
return math.sqrt(p * 0.5)
|
||||
} else {
|
||||
return 1 - math.sqrt((1 - p) * 0.5)
|
||||
}
|
||||
}
|
||||
|
||||
// Inverse of cubic_in
|
||||
// x = y^(1/3)
|
||||
@(require_results)
|
||||
cubic_in_inverse :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return math.pow(p, 1.0/3.0)
|
||||
}
|
||||
|
||||
// Inverse of cubic_out
|
||||
// x = (y - 1)^(1/3) + 1
|
||||
@(require_results)
|
||||
cubic_out_inverse :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return _signed_pow(p - 1, 1.0/3.0) + 1
|
||||
}
|
||||
|
||||
// Inverse of cubic_in_out
|
||||
// x = (y/4)^(1/3) ; [0, 0.5)
|
||||
// x = ((y-1)*2)^(1/3)/2 + 1 ; [0.5, 1]
|
||||
@(require_results)
|
||||
cubic_in_out_inverse :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
if p < 0.5 {
|
||||
return math.pow(p * 0.25, 1.0/3.0)
|
||||
} else {
|
||||
return _signed_pow((p - 1) * 2, 1.0/3.0) * 0.5 + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Inverse of quartic_in
|
||||
// x = y^(1/4)
|
||||
@(require_results)
|
||||
quartic_in_inverse :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return math.pow(p, 0.25)
|
||||
}
|
||||
|
||||
// Inverse of quartic_out
|
||||
// x = 1 - (1 - y)^(1/4)
|
||||
@(require_results)
|
||||
quartic_out_inverse :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return 1 - math.pow(1 - p, 0.25)
|
||||
}
|
||||
|
||||
// Inverse of quartic_in_out
|
||||
// x = (y/8)^(1/4) ; [0, 0.5)
|
||||
// x = 1 - ((1-y)/8)^(1/4) ; [0.5, 1]
|
||||
@(require_results)
|
||||
quartic_in_out_inverse :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
if p < 0.5 {
|
||||
return math.pow(p * 0.125, 0.25)
|
||||
} else {
|
||||
return 1 - math.pow((1 - p) * 0.125, 0.25)
|
||||
}
|
||||
}
|
||||
|
||||
// Inverse of quintic_in
|
||||
// x = y^(1/5)
|
||||
@(require_results)
|
||||
quintic_in_inverse :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return math.pow(p, 0.2)
|
||||
}
|
||||
|
||||
// Inverse of quintic_out
|
||||
// x = (y - 1)^(1/5) + 1
|
||||
@(require_results)
|
||||
quintic_out_inverse :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return _signed_pow(p - 1, 0.2) + 1
|
||||
}
|
||||
|
||||
// Inverse of quintic_in_out
|
||||
// x = (y/16)^(1/5) ; [0, 0.5)
|
||||
// x = ((y-1)*2)^(1/5)/2 + 1 ; [0.5, 1]
|
||||
@(require_results)
|
||||
quintic_in_out_inverse :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
if p < 0.5 {
|
||||
return math.pow(0.0625 * p, 0.2)
|
||||
} else {
|
||||
return _signed_pow((p - 1) * 2, 0.2) * 0.5 + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Inverse of sine_in
|
||||
// x = asin(y - 1) * 2/π + 1
|
||||
@(require_results)
|
||||
sine_in_inverse :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return math.asin(p - 1) * PI_2_INV + 1
|
||||
}
|
||||
|
||||
// Inverse of sine_out
|
||||
// x = asin(y) * 2/π
|
||||
@(require_results)
|
||||
sine_out_inverse :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return math.asin(p) * PI_2_INV
|
||||
}
|
||||
|
||||
// Inverse of sine_in_out
|
||||
// x = acos(1 - 2y) / π
|
||||
@(require_results)
|
||||
sine_in_out_inverse :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return math.acos(1 - 2*p) / math.PI
|
||||
}
|
||||
|
||||
// Inverse of circular_in
|
||||
// x = sqrt(2y - y²)
|
||||
@(require_results)
|
||||
circular_in_inverse :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return math.sqrt(2*p - p*p)
|
||||
}
|
||||
|
||||
// Inverse of circular_out
|
||||
// x = 1 - sqrt(1 - y²)
|
||||
@(require_results)
|
||||
circular_out_inverse :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return 1 - math.sqrt(1 - p*p)
|
||||
}
|
||||
|
||||
// Inverse of circular_in_out
|
||||
// x = sqrt(1 - (1-2y)²) / 2 ; [0, 0.5)
|
||||
// x = 1 - sqrt(1 - (2y-1)²) / 2 ; [0.5, 1]
|
||||
@(require_results)
|
||||
circular_in_out_inverse :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
if p < 0.5 {
|
||||
q := 1 - 2*p
|
||||
return 0.5 * math.sqrt(1 - q*q)
|
||||
} else {
|
||||
q := 2*p - 1
|
||||
return 1 - 0.5 * math.sqrt(1 - q*q)
|
||||
}
|
||||
}
|
||||
|
||||
// Inverse of exponential_in
|
||||
// x = log₂(y) / 10 + 1
|
||||
@(require_results)
|
||||
exponential_in_inverse :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return p == 0.0 ? 0.0 : 0.1 * math.log2(p) + 1
|
||||
}
|
||||
|
||||
// Inverse of exponential_out
|
||||
// x = -log₂(1 - y) / 10
|
||||
@(require_results)
|
||||
exponential_out_inverse :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return p == 1.0 ? 1.0 : 0.1 * -math.log2(1 - p)
|
||||
}
|
||||
|
||||
// Inverse of exponential_in_out
|
||||
// x = (log₂(2y) + 10) / 20 ; [0, 0.5)
|
||||
// x = (10 - log₂(2(1-y))) / 20 ; [0.5, 1]
|
||||
@(require_results)
|
||||
exponential_in_out_inverse :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
if p == 0.0 || p == 1.0 {
|
||||
return p
|
||||
}
|
||||
|
||||
if p < 0.5 {
|
||||
return 0.05 * (math.log2(2*p) + 10)
|
||||
} else {
|
||||
return 0.05 * (10 - math.log2(2*(1-p)))
|
||||
}
|
||||
}
|
||||
|
||||
// Additional enum variant
|
||||
|
||||
@(require_results)
|
||||
ease_inverse :: proc "contextless" (type: Ease, p: $T) -> T where intrinsics.type_is_float(T) {
|
||||
switch type {
|
||||
case .Linear: return p
|
||||
|
||||
case .Quadratic_In: return quadratic_in_inverse(p)
|
||||
case .Quadratic_Out: return quadratic_out_inverse(p)
|
||||
case .Quadratic_In_Out: return quadratic_in_out_inverse(p)
|
||||
|
||||
case .Cubic_In: return cubic_in_inverse(p)
|
||||
case .Cubic_Out: return cubic_out_inverse(p)
|
||||
case .Cubic_In_Out: return cubic_in_out_inverse(p)
|
||||
|
||||
case .Quartic_In: return quartic_in_inverse(p)
|
||||
case .Quartic_Out: return quartic_out_inverse(p)
|
||||
case .Quartic_In_Out: return quartic_in_out_inverse(p)
|
||||
|
||||
case .Quintic_In: return quintic_in_inverse(p)
|
||||
case .Quintic_Out: return quintic_out_inverse(p)
|
||||
case .Quintic_In_Out: return quintic_in_out_inverse(p)
|
||||
|
||||
case .Sine_In: return sine_in_inverse(p)
|
||||
case .Sine_Out: return sine_out_inverse(p)
|
||||
case .Sine_In_Out: return sine_in_out_inverse(p)
|
||||
|
||||
case .Circular_In: return circular_in_inverse(p)
|
||||
case .Circular_Out: return circular_out_inverse(p)
|
||||
case .Circular_In_Out: return circular_in_out_inverse(p)
|
||||
|
||||
case .Exponential_In: return exponential_in_inverse(p)
|
||||
case .Exponential_Out: return exponential_out_inverse(p)
|
||||
case .Exponential_In_Out: return exponential_in_out_inverse(p)
|
||||
|
||||
case .Elastic_In, .Elastic_Out, .Elastic_In_Out,
|
||||
.Back_In, .Back_Out, .Back_In_Out,
|
||||
.Bounce_In, .Bounce_Out, .Bounce_In_Out:
|
||||
// These do not have simple closed-form inverses
|
||||
return 0
|
||||
}
|
||||
|
||||
// In case type was invalid
|
||||
return 0
|
||||
}
|
||||
178
core/math/ease/flux.odin
Normal file
178
core/math/ease/flux.odin
Normal file
@@ -0,0 +1,178 @@
|
||||
// Flux easing used for animations
|
||||
package ease
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:time"
|
||||
|
||||
Flux_Map :: struct($T: typeid) {
|
||||
values: map[^T]Flux_Tween(T),
|
||||
keys_to_be_deleted: [dynamic]^T,
|
||||
}
|
||||
|
||||
Flux_Tween :: struct($T: typeid) {
|
||||
value: ^T,
|
||||
start: T,
|
||||
diff: T,
|
||||
goal: T,
|
||||
|
||||
delay: f64, // in seconds
|
||||
duration: time.Duration,
|
||||
|
||||
progress: f64,
|
||||
rate: f64,
|
||||
type: Ease,
|
||||
|
||||
inited: bool,
|
||||
|
||||
// callbacks, data can be set, will be pushed to callback
|
||||
data: rawptr, // by default gets set to value input
|
||||
on_start: proc(flux: ^Flux_Map(T), data: rawptr),
|
||||
on_update: proc(flux: ^Flux_Map(T), data: rawptr),
|
||||
on_complete: proc(flux: ^Flux_Map(T), data: rawptr),
|
||||
}
|
||||
|
||||
// init flux map to a float type and a wanted cap
|
||||
@(require_results)
|
||||
flux_init :: proc($T: typeid, value_capacity := 8, allocator := context.allocator, loc := #caller_location) -> Flux_Map(T) where intrinsics.type_is_float(T) {
|
||||
return {
|
||||
values = make(map[^T]Flux_Tween(T), value_capacity, allocator, loc),
|
||||
keys_to_be_deleted = make([dynamic]^T, 0, value_capacity, allocator, loc),
|
||||
}
|
||||
}
|
||||
|
||||
// delete map content
|
||||
flux_destroy :: proc(flux: Flux_Map($T), allocator := context.allocator, loc := #caller_location) where intrinsics.type_is_float(T) {
|
||||
delete(flux.values, loc)
|
||||
delete(flux.keys_to_be_deleted, loc)
|
||||
}
|
||||
|
||||
// clear map content, stops all animations
|
||||
flux_clear :: proc(flux: ^Flux_Map($T)) where intrinsics.type_is_float(T) {
|
||||
clear(&flux.values)
|
||||
}
|
||||
|
||||
// append / overwrite existing tween value to parameters
|
||||
// rest is initialized in flux_tween_init, inside update
|
||||
// return value can be used to set callbacks
|
||||
@(require_results)
|
||||
flux_to :: proc(
|
||||
flux: ^Flux_Map($T),
|
||||
value: ^T,
|
||||
goal: T,
|
||||
type: Ease = .Quadratic_Out,
|
||||
duration: time.Duration = time.Second,
|
||||
delay: f64 = 0,
|
||||
) -> (tween: ^Flux_Tween(T)) where intrinsics.type_is_float(T) {
|
||||
if res, ok := &flux.values[value]; ok {
|
||||
tween = res
|
||||
} else {
|
||||
flux.values[value] = {}
|
||||
tween = &flux.values[value]
|
||||
}
|
||||
|
||||
tween^ = {
|
||||
value = value,
|
||||
goal = goal,
|
||||
duration = duration,
|
||||
delay = delay,
|
||||
type = type,
|
||||
data = value,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// init internal properties
|
||||
flux_tween_init :: proc(tween: ^Flux_Tween($T), duration: time.Duration) where intrinsics.type_is_float(T) {
|
||||
tween.inited = true
|
||||
tween.start = tween.value^
|
||||
tween.diff = tween.goal - tween.value^
|
||||
s := time.duration_seconds(duration)
|
||||
tween.rate = duration > 0 ? 1.0 / s : 0
|
||||
tween.progress = duration > 0 ? 0 : 1
|
||||
}
|
||||
|
||||
// update all tweens, wait for their delay if one exists
|
||||
// calls callbacks in all stages, when they're filled
|
||||
// deletes tween from the map after completion
|
||||
flux_update :: proc(flux: ^Flux_Map($T), dt: f64) where intrinsics.type_is_float(T) {
|
||||
clear(&flux.keys_to_be_deleted)
|
||||
|
||||
for key, &tween in flux.values {
|
||||
delay_remainder := f64(0)
|
||||
|
||||
// Update delay if necessary.
|
||||
if tween.delay > 0 {
|
||||
tween.delay -= dt
|
||||
|
||||
if tween.delay < 0 {
|
||||
// We finished the delay, but in doing so consumed part of this frame's `dt` budget.
|
||||
// Keep track of it so we can apply it to this tween without affecting others.
|
||||
delay_remainder = tween.delay
|
||||
// We're done with this delay.
|
||||
tween.delay = 0
|
||||
}
|
||||
}
|
||||
|
||||
// We either had no delay, or the delay has been consumed.
|
||||
if tween.delay <= 0 {
|
||||
if !tween.inited {
|
||||
flux_tween_init(&tween, tween.duration)
|
||||
|
||||
if tween.on_start != nil {
|
||||
tween.on_start(flux, tween.data)
|
||||
}
|
||||
}
|
||||
|
||||
// If part of the `dt` budget was consumed this frame, then `delay_remainder` will be
|
||||
// that remainder, a negative value. Adding it to `dt` applies what's left of the `dt`
|
||||
// to the tween so it advances properly, instead of too much or little.
|
||||
tween.progress += tween.rate * (dt + delay_remainder)
|
||||
x := tween.progress >= 1 ? 1 : ease(tween.type, tween.progress)
|
||||
tween.value^ = tween.start + tween.diff * T(x)
|
||||
|
||||
if tween.on_update != nil {
|
||||
tween.on_update(flux, tween.data)
|
||||
}
|
||||
|
||||
if tween.progress >= 1 {
|
||||
// append keys to array that will be deleted after the loop
|
||||
append(&flux.keys_to_be_deleted, key)
|
||||
|
||||
if tween.on_complete != nil {
|
||||
tween.on_complete(flux, tween.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// loop through keys that should be deleted from the map
|
||||
if len(flux.keys_to_be_deleted) != 0 {
|
||||
for key in flux.keys_to_be_deleted {
|
||||
delete_key(&flux.values, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// stop a specific key inside the map
|
||||
// returns true when it successfully removed the key
|
||||
@(require_results)
|
||||
flux_stop :: proc(flux: ^Flux_Map($T), key: ^T) -> bool where intrinsics.type_is_float(T) {
|
||||
if key in flux.values {
|
||||
delete_key(&flux.values, key)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// returns the amount of time left for the tween animation, if the key exists in the map
|
||||
// returns 0 if the tween doesn't exist on the map
|
||||
@(require_results)
|
||||
flux_tween_time_left :: proc(flux: Flux_Map($T), key: ^T) -> f64 {
|
||||
if tween, ok := flux.values[key]; ok {
|
||||
return ((1 - tween.progress) * tween.rate) + tween.delay
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -457,7 +457,6 @@ gain :: proc "contextless" (t, g: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return bias(t*2 - 1, 1 - g) * 0.5 + 0.5
|
||||
}
|
||||
|
||||
|
||||
@(require_results) sign_f16 :: proc "contextless" (x: f16) -> f16 { return f16(int(0 < x) - int(x < 0)) }
|
||||
@(require_results) sign_f16le :: proc "contextless" (x: f16le) -> f16le { return f16le(int(0 < x) - int(x < 0)) }
|
||||
@(require_results) sign_f16be :: proc "contextless" (x: f16be) -> f16be { return f16be(int(0 < x) - int(x < 0)) }
|
||||
@@ -467,10 +466,24 @@ gain :: proc "contextless" (t, g: $T) -> T where intrinsics.type_is_float(T) {
|
||||
@(require_results) sign_f64 :: proc "contextless" (x: f64) -> f64 { return f64(int(0 < x) - int(x < 0)) }
|
||||
@(require_results) sign_f64le :: proc "contextless" (x: f64le) -> f64le { return f64le(int(0 < x) - int(x < 0)) }
|
||||
@(require_results) sign_f64be :: proc "contextless" (x: f64be) -> f64be { return f64be(int(0 < x) - int(x < 0)) }
|
||||
@(require_results) sign_int :: proc "contextless" (x: int) -> int { return int(0 < x) - int(x < 0) }
|
||||
@(require_results) sign_i16 :: proc "contextless" (x: i16) -> i16 { return i16(int(0 < x) - int(x < 0)) }
|
||||
@(require_results) sign_i16le :: proc "contextless" (x: i16le) -> i16le { return i16le(int(0 < x) - int(x < 0)) }
|
||||
@(require_results) sign_i16be :: proc "contextless" (x: i16be) -> i16be { return i16be(int(0 < x) - int(x < 0)) }
|
||||
@(require_results) sign_i32 :: proc "contextless" (x: i32) -> i32 { return i32(int(0 < x) - int(x < 0)) }
|
||||
@(require_results) sign_i32le :: proc "contextless" (x: i32le) -> i32le { return i32le(int(0 < x) - int(x < 0)) }
|
||||
@(require_results) sign_i32be :: proc "contextless" (x: i32be) -> i32be { return i32be(int(0 < x) - int(x < 0)) }
|
||||
@(require_results) sign_i64 :: proc "contextless" (x: i64) -> i64 { return i64(int(0 < x) - int(x < 0)) }
|
||||
@(require_results) sign_i64le :: proc "contextless" (x: i64le) -> i64le { return i64le(int(0 < x) - int(x < 0)) }
|
||||
@(require_results) sign_i64be :: proc "contextless" (x: i64be) -> i64be { return i64be(int(0 < x) - int(x < 0)) }
|
||||
sign :: proc{
|
||||
sign_f16, sign_f16le, sign_f16be,
|
||||
sign_f32, sign_f32le, sign_f32be,
|
||||
sign_f64, sign_f64le, sign_f64be,
|
||||
sign_int,
|
||||
sign_i16, sign_i16le, sign_i16be,
|
||||
sign_i32, sign_i32le, sign_i32be,
|
||||
sign_i64, sign_i64le, sign_i64be,
|
||||
}
|
||||
|
||||
@(require_results) sign_bit_f16 :: proc "contextless" (x: f16) -> bool { return (transmute(u16)x) & (1<<15) != 0 }
|
||||
@@ -482,10 +495,24 @@ sign :: proc{
|
||||
@(require_results) sign_bit_f64 :: proc "contextless" (x: f64) -> bool { return (transmute(u64)x) & (1<<63) != 0 }
|
||||
@(require_results) sign_bit_f64le :: proc "contextless" (x: f64le) -> bool { return #force_inline sign_bit_f64(f64(x)) }
|
||||
@(require_results) sign_bit_f64be :: proc "contextless" (x: f64be) -> bool { return #force_inline sign_bit_f64(f64(x)) }
|
||||
@(require_results) sign_bit_int :: proc "contextless" (x: int) -> bool { return uint(x) & (1<<(size_of(int)*8 - 1)) != 0 }
|
||||
@(require_results) sign_bit_i16 :: proc "contextless" (x: i16) -> bool { return u16(x) & (1<<15) != 0 }
|
||||
@(require_results) sign_bit_i16le :: proc "contextless" (x: i16le) -> bool { return #force_inline sign_bit_i16(i16(x)) }
|
||||
@(require_results) sign_bit_i16be :: proc "contextless" (x: i16be) -> bool { return #force_inline sign_bit_i16(i16(x)) }
|
||||
@(require_results) sign_bit_i32 :: proc "contextless" (x: i32) -> bool { return u32(x) & (1<<31) != 0 }
|
||||
@(require_results) sign_bit_i32le :: proc "contextless" (x: i32le) -> bool { return #force_inline sign_bit_i32(i32(x)) }
|
||||
@(require_results) sign_bit_i32be :: proc "contextless" (x: i32be) -> bool { return #force_inline sign_bit_i32(i32(x)) }
|
||||
@(require_results) sign_bit_i64 :: proc "contextless" (x: i64) -> bool { return u64(x) & (1<<63) != 0 }
|
||||
@(require_results) sign_bit_i64le :: proc "contextless" (x: i64le) -> bool { return #force_inline sign_bit_i64(i64(x)) }
|
||||
@(require_results) sign_bit_i64be :: proc "contextless" (x: i64be) -> bool { return #force_inline sign_bit_i64(i64(x)) }
|
||||
sign_bit :: proc{
|
||||
sign_bit_f16, sign_bit_f16le, sign_bit_f16be,
|
||||
sign_bit_f32, sign_bit_f32le, sign_bit_f32be,
|
||||
sign_bit_f64, sign_bit_f64le, sign_bit_f64be,
|
||||
sign_bit_int,
|
||||
sign_bit_i16, sign_bit_i16le, sign_bit_i16be,
|
||||
sign_bit_i32, sign_bit_i32le, sign_bit_i32be,
|
||||
sign_bit_i64, sign_bit_i64le, sign_bit_i64be,
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user