Merge branch 'master' into vendor/curl

This commit is contained in:
gingerBill
2025-12-01 11:53:08 +00:00
276 changed files with 11089 additions and 6104 deletions

View File

@@ -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

View File

@@ -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
View File

@@ -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.

View File

@@ -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 ---

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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)
}

View File

@@ -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))
}

View File

@@ -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))
}

View File

@@ -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()
}

View File

@@ -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()
}

View File

@@ -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()
}

View File

@@ -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()
}

View File

@@ -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()
}

View File

@@ -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))
}

View File

@@ -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))
}

View File

@@ -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))

View File

@@ -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,
}
}

View 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
}

View 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)
}

View 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:]
}
}
}

View 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")
}
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
}
//

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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 })

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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)

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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))
}

View File

@@ -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)
}
}

View File

@@ -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")
}

View File

@@ -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:]
}
}

View File

@@ -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:]
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}
}

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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) {

View File

@@ -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.

View File

@@ -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)

View File

@@ -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)

View File

@@ -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.

View File

@@ -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:

View File

@@ -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)

View File

@@ -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.

View File

@@ -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.

View File

@@ -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).

View File

@@ -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.
*/

View File

@@ -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).

View File

@@ -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.

View File

@@ -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.

View File

@@ -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)

View File

@@ -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")

View 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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -1,3 +1,5 @@
#+build !freestanding
package log
import "base:runtime"
import "core:fmt"

View File

@@ -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.
*/

View File

@@ -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))

View File

@@ -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"

View File

@@ -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

View File

@@ -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.

View File

@@ -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)
}

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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.
*/

View File

@@ -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.

View File

@@ -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
}
}

View 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
View 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
}
}

View File

@@ -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