mirror of
https://github.com/odin-lang/Odin.git
synced 2026-08-27 07:21:31 +00:00
Merge branch 'master' into bill/bedrock
This commit is contained in:
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -138,7 +138,7 @@ jobs:
|
||||
- name: Normal Core library tests
|
||||
run: ./odin test tests/core/normal.odin -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
|
||||
- name: Optimized Core library tests
|
||||
run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
|
||||
run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
|
||||
- name: Wycheproof tests
|
||||
run: ./odin test tests/core/crypto/wycheproof -vet -vet-tabs -strict-style -vet-style -vet-cast -warnings-as-errors -disallow-do -o:speed
|
||||
- name: Noise Protocol Framework tests
|
||||
@@ -239,7 +239,7 @@ jobs:
|
||||
shell: cmd
|
||||
run: |
|
||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
|
||||
odin test tests/core/speed.odin -o:speed -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
|
||||
odin test tests/core/speed.odin -o:speed -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
|
||||
- name: Wycheproof tests
|
||||
shell: cmd
|
||||
run: |
|
||||
|
||||
121
.github/workflows/nightly.yml
vendored
121
.github/workflows/nightly.yml
vendored
@@ -44,18 +44,24 @@ jobs:
|
||||
name: windows_artifacts
|
||||
path: dist
|
||||
build_linux:
|
||||
name: Linux Build
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [amd64, arm64]
|
||||
env:
|
||||
ARCH: ${{ matrix.arch }}
|
||||
DOCKER_IMAGE: ${{ matrix.arch == 'amd64' && 'alpine:3.24' || 'arm64v8/alpine:3.24' }}
|
||||
name: Linux Build ${{ matrix.arch }}
|
||||
if: github.repository == 'odin-lang/Odin'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ matrix.arch == 'amd64' && 'ubuntu-latest' || 'ubuntu-24.04-arm' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: true
|
||||
- name: (Linux) Download LLVM and Build Odin
|
||||
run: |
|
||||
docker run --rm -v "$PWD:/src" -w /src alpine sh -c '
|
||||
docker run --rm -v "$PWD:/src" -w /src $DOCKER_IMAGE sh -c '
|
||||
apk add --no-cache \
|
||||
musl-dev llvm20-dev clang20 git mold lz4 \
|
||||
musl-dev llvm20-dev clang20 build-base git mold lz4 \
|
||||
libxml2-static llvm20-static zlib-static zstd-static \
|
||||
make &&
|
||||
git config --global --add safe.directory /src &&
|
||||
@@ -65,7 +71,7 @@ jobs:
|
||||
run: ./odin run examples/demo
|
||||
- name: Copy artifacts
|
||||
run: |
|
||||
FILE="odin-linux-amd64-nightly+$(date -I)"
|
||||
FILE="odin-linux-$ARCH-nightly+$(date -I)"
|
||||
mkdir $FILE
|
||||
cp odin $FILE
|
||||
cp LICENSE $FILE
|
||||
@@ -79,59 +85,22 @@ jobs:
|
||||
tar -czvf dist.tar.gz $FILE
|
||||
- name: Odin run
|
||||
run: |
|
||||
FILE="odin-linux-amd64-nightly+$(date -I)"
|
||||
FILE="odin-linux-$ARCH-nightly+$(date -I)"
|
||||
$FILE/odin run examples/demo
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux_artifacts
|
||||
path: dist.tar.gz
|
||||
build_linux_arm:
|
||||
name: Linux ARM Build
|
||||
if: github.repository == 'odin-lang/Odin'
|
||||
runs-on: ubuntu-24.04-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: true
|
||||
- name: (Linux ARM) Download LLVM and Build Odin
|
||||
run: |
|
||||
docker run --rm -v "$PWD:/src" -w /src arm64v8/alpine sh -c '
|
||||
apk add --no-cache \
|
||||
musl-dev llvm20-dev clang20 git mold lz4 \
|
||||
libxml2-static llvm20-static zlib-static zstd-static \
|
||||
make &&
|
||||
./ci/build_linux_static.sh
|
||||
'
|
||||
- name: Odin run
|
||||
run: ./odin run examples/demo
|
||||
- name: Copy artifacts
|
||||
run: |
|
||||
FILE="odin-linux-arm64-nightly+$(date -I)"
|
||||
mkdir $FILE
|
||||
cp odin $FILE
|
||||
cp LICENSE $FILE
|
||||
cp -r shared $FILE
|
||||
cp -r base $FILE
|
||||
cp -r core $FILE
|
||||
cp -r vendor $FILE
|
||||
cp -r examples $FILE
|
||||
./ci/remove_windows_binaries.sh $FILE
|
||||
# Creating a tarball so executable permissions are retained, see https://github.com/actions/upload-artifact/issues/38
|
||||
tar -czvf dist.tar.gz $FILE
|
||||
- name: Odin run
|
||||
run: |
|
||||
FILE="odin-linux-arm64-nightly+$(date -I)"
|
||||
$FILE/odin run examples/demo
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux_arm_artifacts
|
||||
name: ${{ matrix.arch == 'amd64' && 'linux_artifacts' || 'linux_arm_artifacts' }}
|
||||
path: dist.tar.gz
|
||||
build_macos:
|
||||
name: MacOS Build
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [amd64, arm64]
|
||||
env:
|
||||
ARCH: ${{ matrix.arch }}
|
||||
name: Macos Build ${{ matrix.arch }}
|
||||
if: github.repository == 'odin-lang/Odin'
|
||||
runs-on: macos-15-intel
|
||||
runs-on: ${{ matrix.arch == 'amd64' && 'macos-15-intel' || 'macos-latest' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -140,6 +109,7 @@ jobs:
|
||||
run: |
|
||||
brew update
|
||||
brew install llvm@20 dylibbundler lld@20
|
||||
brew link llvm@20
|
||||
|
||||
- name: build odin
|
||||
# These -L makes the linker prioritize system libraries over LLVM libraries, this is mainly to
|
||||
@@ -147,7 +117,7 @@ jobs:
|
||||
run: CXXFLAGS="-L/usr/lib/system -L/usr/lib" make nightly
|
||||
- name: Bundle
|
||||
run: |
|
||||
FILE="odin-macos-amd64-nightly+$(date -I)"
|
||||
FILE="odin-macos-$ARCH-nightly+$(date -I)"
|
||||
mkdir $FILE
|
||||
cp odin $FILE
|
||||
cp LICENSE $FILE
|
||||
@@ -162,57 +132,16 @@ jobs:
|
||||
tar -czvf dist.tar.gz $FILE
|
||||
- name: Odin run
|
||||
run: |
|
||||
FILE="odin-macos-amd64-nightly+$(date -I)"
|
||||
FILE="odin-macos-$ARCH-nightly+$(date -I)"
|
||||
$FILE/odin run examples/demo
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos_artifacts
|
||||
path: dist.tar.gz
|
||||
build_macos_arm:
|
||||
name: MacOS ARM Build
|
||||
if: github.repository == 'odin-lang/Odin'
|
||||
runs-on: macos-latest # ARM machine
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: true
|
||||
- name: Download LLVM and setup PATH
|
||||
run: |
|
||||
brew update
|
||||
brew install llvm@20 dylibbundler lld@20
|
||||
|
||||
- name: build odin
|
||||
# These -L makes the linker prioritize system libraries over LLVM libraries, this is mainly to
|
||||
# not link with libunwind bundled with LLVM but link with libunwind on the system.
|
||||
run: CXXFLAGS="-L/usr/lib/system -L/usr/lib" make nightly
|
||||
- name: Bundle
|
||||
run: |
|
||||
FILE="odin-macos-arm64-nightly+$(date -I)"
|
||||
mkdir $FILE
|
||||
cp odin $FILE
|
||||
cp LICENSE $FILE
|
||||
cp -r shared $FILE
|
||||
cp -r base $FILE
|
||||
cp -r core $FILE
|
||||
cp -r vendor $FILE
|
||||
cp -r examples $FILE
|
||||
./ci/remove_windows_binaries.sh $FILE
|
||||
dylibbundler -b -x $FILE/odin -d $FILE/libs -od -p @executable_path/libs
|
||||
# Creating a tarball so executable permissions are retained, see https://github.com/actions/upload-artifact/issues/38
|
||||
tar -czvf dist.tar.gz $FILE
|
||||
- name: Odin run
|
||||
run: |
|
||||
FILE="odin-macos-arm64-nightly+$(date -I)"
|
||||
$FILE/odin run examples/demo
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos_arm_artifacts
|
||||
name: ${{ matrix.arch == 'amd64' && 'macos_artifacts' || 'macos_arm_artifacts' }}
|
||||
path: dist.tar.gz
|
||||
upload_b2:
|
||||
runs-on: [ubuntu-latest]
|
||||
needs: [build_windows, build_macos, build_macos_arm, build_linux, build_linux_arm]
|
||||
needs: [build_windows, build_macos, build_linux]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
|
||||
@@ -4,6 +4,7 @@ package intrinsics
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
|
||||
// Package-Related
|
||||
is_package_imported :: proc(package_name: string) -> bool ---
|
||||
|
||||
@@ -370,8 +371,11 @@ simd_odd_even :: proc(a, b: #simd[N]T) -> #simd[N]T ---
|
||||
// Returns the sums of N consecutive lanes
|
||||
simd_sums_of_n :: proc(a: #simd[LANES]T, $N: uint) -> #simd[LANES/N]T where is_power_of_two(N) ---
|
||||
|
||||
simd_pairwise_add :: proc(a, b: #simd[LANES]T) -> #simd[LANES/N]T ---
|
||||
simd_pairwise_sub :: proc(a, b: #simd[LANES]T) -> #simd[LANES/N]T ---
|
||||
simd_pairwise_add :: proc(a, b: #simd[LANES]T) -> #simd[LANES]T where LANES % 2 == 0 ---
|
||||
simd_pairwise_sub :: proc(a, b: #simd[LANES]T) -> #simd[LANES]T where LANES % 2 == 0 ---
|
||||
|
||||
simd_interleave :: proc(a, ..#simd[LANES/N]T) -> #simd[LANES]T where N >= 1 ---
|
||||
simd_deinterleave :: proc(a: #simd[LANES]T, $N: uint) -> (..#simd[LANES/N]T) where N >= 1, LANES % N == 0 --- // returns N multiple vectors
|
||||
|
||||
|
||||
// Checks if the current target supports the given target features.
|
||||
|
||||
@@ -23,6 +23,23 @@ package runtime
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
/*
|
||||
Fast_Math_Flag :: enum u8 {
|
||||
Allow_Reassoc = 0,
|
||||
No_NaNs = 1,
|
||||
No_Infs = 2,
|
||||
No_Signed_Zeros = 3,
|
||||
Allow_Reciprocal = 4,
|
||||
Allow_Contract = 5,
|
||||
Approx_Func = 6,
|
||||
}
|
||||
*/
|
||||
Fast_Math_Flag :: intrinsics.Fast_Math_Flag
|
||||
|
||||
// Fast_Math_Flags :: distinct bit_set[Fast_Math_Flag; u32]
|
||||
Fast_Math_Flags :: intrinsics.Fast_Math_Flags
|
||||
|
||||
|
||||
// NOTE(bill): This must match the compiler's
|
||||
|
||||
/*
|
||||
@@ -588,7 +605,6 @@ Raw_Quaternion256_Vector_Scalar :: struct {vector: [3]f64, scalar: f64}
|
||||
FreeBSD,
|
||||
OpenBSD,
|
||||
NetBSD,
|
||||
Haiku,
|
||||
WASI,
|
||||
JS,
|
||||
Orca,
|
||||
@@ -655,7 +671,6 @@ ALL_ODIN_OS_TYPES :: Odin_OS_Types{
|
||||
.FreeBSD,
|
||||
.OpenBSD,
|
||||
.NetBSD,
|
||||
.Haiku,
|
||||
.WASI,
|
||||
.JS,
|
||||
.Orca,
|
||||
|
||||
@@ -517,7 +517,7 @@ new :: proc($T: typeid, allocator := context.allocator, loc := #caller_location)
|
||||
t = (^T)(raw_data(mem_alloc_bytes(size_of(T), align_of(T), allocator, loc) or_return))
|
||||
return
|
||||
}
|
||||
@(require_results)
|
||||
@(builtin, require_results)
|
||||
new_aligned :: proc($T: typeid, alignment: int, allocator := context.allocator, loc := #caller_location) -> (t: ^T, err: Allocator_Error) {
|
||||
t = (^T)(raw_data(mem_alloc_bytes(size_of(T), alignment, allocator, loc) or_return))
|
||||
return
|
||||
@@ -534,7 +534,7 @@ new_clone :: proc(data: $T, allocator := context.allocator, loc := #caller_locat
|
||||
|
||||
DEFAULT_DYNAMIC_ARRAY_CAPACITY :: 8
|
||||
|
||||
@(require_results)
|
||||
@(builtin, require_results)
|
||||
make_aligned :: proc($T: typeid/[]$E, #any_int len: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (res: T, err: Allocator_Error) #optional_allocator_error {
|
||||
err = _make_aligned_type_erased(&res, size_of(E), len, alignment, allocator, loc)
|
||||
return
|
||||
@@ -716,17 +716,17 @@ when MAP_ENABLED {
|
||||
}
|
||||
}
|
||||
|
||||
_append_elem :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, arg_ptr: rawptr, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
_append_elem :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, arg_ptr: rawptr, should_zero: bool, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error {
|
||||
if array == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if array.cap < array.len+1 {
|
||||
// Same behavior as _append_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY.
|
||||
cap := 2 * array.cap + DEFAULT_DYNAMIC_ARRAY_CAPACITY
|
||||
cap := max(2 * array.cap, DEFAULT_DYNAMIC_ARRAY_CAPACITY)
|
||||
|
||||
// do not 'or_return' here as it could be a partial success
|
||||
err = _reserve_dynamic_array(array, size_of_elem, align_of_elem, cap, should_zero, loc)
|
||||
err = _reserve_dynamic_array_unsafe(array, size_of_elem, align_of_elem, cap, should_zero, loc)
|
||||
}
|
||||
if array.cap-array.len > 0 {
|
||||
data := ([^]byte)(array.data)
|
||||
@@ -734,20 +734,46 @@ _append_elem :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, a
|
||||
data = data[array.len*size_of_elem:]
|
||||
intrinsics.mem_copy_non_overlapping(data, arg_ptr, size_of_elem)
|
||||
array.len += 1
|
||||
n = 1
|
||||
num_appended = 1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// `append_elem` appends an element to the end of a dynamic array.
|
||||
@builtin
|
||||
append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error {
|
||||
when size_of(E) == 0 {
|
||||
if array == nil {
|
||||
return
|
||||
}
|
||||
(^Raw_Dynamic_Array)(array).len += 1
|
||||
return 1, nil
|
||||
} else {
|
||||
} else when ODIN_OPTIMIZATION_MODE <= .Size {
|
||||
arg := arg
|
||||
return _append_elem((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), &arg, true, loc=loc)
|
||||
} else {
|
||||
if array == nil {
|
||||
return
|
||||
}
|
||||
arg := arg
|
||||
arr := (^Raw_Dynamic_Array)(array)
|
||||
if arr.cap < arr.len+1 {
|
||||
// Same behavior as _append_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY.
|
||||
cap := max(2 * arr.cap, DEFAULT_DYNAMIC_ARRAY_CAPACITY)
|
||||
|
||||
// do not 'or_return' here as it could be a partial success
|
||||
err = _reserve_dynamic_array_unsafe(arr, size_of(E), align_of(E), cap, true, loc)
|
||||
}
|
||||
if arr.cap-arr.len > 0 {
|
||||
// NOTE(bill, 2026-06-19): When this is in the hot path with -o:speed or -o:aggressive enabled,
|
||||
// this code path cannot rely on type erasure and `mem_copy_non_overlapping`.
|
||||
// So directly inlining the call and storing the argument like this helps the optimize a lot
|
||||
assert(arr.data != nil, loc=loc)
|
||||
([^]E)(arr.data)[arr.len] = arg
|
||||
arr.len += 1
|
||||
num_appended = 1
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -755,7 +781,7 @@ append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller
|
||||
//
|
||||
// Note: Prefer using the procedure group `non_zero_append
|
||||
@builtin
|
||||
non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error {
|
||||
when size_of(E) == 0 {
|
||||
(^Raw_Dynamic_Array)(array).len += 1
|
||||
return 1, nil
|
||||
@@ -765,7 +791,7 @@ non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc :
|
||||
}
|
||||
}
|
||||
|
||||
_append_elems :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, should_zero: bool, loc := #caller_location, args: rawptr, arg_len: int) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
_append_elems :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, should_zero: bool, loc := #caller_location, args: rawptr, arg_len: int) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error {
|
||||
if array == nil {
|
||||
return 0, nil
|
||||
}
|
||||
@@ -796,7 +822,7 @@ _append_elems :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem,
|
||||
//
|
||||
// Note: Prefer using the procedure group `append`.
|
||||
@builtin
|
||||
append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error {
|
||||
when size_of(E) == 0 {
|
||||
a := (^Raw_Dynamic_Array)(array)
|
||||
a.len += len(args)
|
||||
@@ -810,7 +836,7 @@ append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #ca
|
||||
//
|
||||
// Note: Prefer using the procedure group `non_zero_append
|
||||
@builtin
|
||||
non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error {
|
||||
when size_of(E) == 0 {
|
||||
a := (^Raw_Dynamic_Array)(array)
|
||||
a.len += len(args)
|
||||
@@ -821,7 +847,7 @@ non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, l
|
||||
}
|
||||
|
||||
// The append_string built-in procedure appends a string to the end of a [dynamic]u8 like type
|
||||
_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, should_zero: bool, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error {
|
||||
return _append_elems((^Raw_Dynamic_Array)(array), 1, 1, should_zero, loc, raw_data(arg), len(arg))
|
||||
}
|
||||
|
||||
@@ -829,14 +855,14 @@ _append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, should_ze
|
||||
//
|
||||
// Note: Prefer using the procedure group `append`.
|
||||
@builtin
|
||||
append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error {
|
||||
return _append_elem_string(array, arg, true, loc)
|
||||
}
|
||||
// `non_zero_append_elem_string` appends a string to the end of a dynamic array of bytes, without zeroing any reserved memory
|
||||
//
|
||||
// Note: Prefer using the procedure group `non_zero_append`.
|
||||
@builtin
|
||||
non_zero_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
non_zero_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error {
|
||||
return _append_elem_string(array, arg, false, loc)
|
||||
}
|
||||
|
||||
@@ -844,7 +870,7 @@ non_zero_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, l
|
||||
//
|
||||
// Note: Prefer using the procedure group `non_zero_append`.
|
||||
@builtin
|
||||
non_zero_append_elem_fixed_capacity_string :: proc "contextless" (array: ^$T/[dynamic; $N]$E/u8, arg: $A/string) -> (n: int) {
|
||||
non_zero_append_elem_fixed_capacity_string :: proc "contextless" (array: ^$T/[dynamic; $N]$E/u8, arg: $A/string) -> (num_appended: int) {
|
||||
return append_fixed_capacity_elem(array, transmute([]byte)arg)
|
||||
}
|
||||
|
||||
@@ -854,11 +880,11 @@ non_zero_append_elem_fixed_capacity_string :: proc "contextless" (array: ^$T/[dy
|
||||
//
|
||||
// Note: Prefer using the procedure group `append`.
|
||||
@builtin
|
||||
append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error {
|
||||
n_arg: int
|
||||
for arg in args {
|
||||
n_arg, err = append(array, ..transmute([]E)(arg), loc=loc)
|
||||
n += n_arg
|
||||
num_appended += n_arg
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -869,7 +895,7 @@ append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_
|
||||
|
||||
// `append_fixed_capacity_elem` appends an element to the end of a fixed capacity dynamic array. Returns 0 on failure
|
||||
@builtin
|
||||
append_fixed_capacity_elem :: proc "contextless" (array: ^$T/[dynamic; $N]$E, #no_broadcast arg: E) -> (n: int) {
|
||||
append_fixed_capacity_elem :: proc "contextless" (array: ^$T/[dynamic; $N]$E, #no_broadcast arg: E) -> (num_appended: int) {
|
||||
Raw :: Raw_Fixed_Capacity_Dynamic_Array(N, E)
|
||||
|
||||
if (^Raw)(array).len >= N {
|
||||
@@ -886,29 +912,29 @@ append_fixed_capacity_elem :: proc "contextless" (array: ^$T/[dynamic; $N]$E, #n
|
||||
|
||||
// `append_fixed_capacity_elem` appends an element to the end of a fixed capacity dynamic array. Returns 0 on failure
|
||||
@builtin
|
||||
append_fixed_capacity_elems :: proc "contextless" (array: ^$T/[dynamic; $N]$E, #no_broadcast args: ..E) -> (n: int) {
|
||||
append_fixed_capacity_elems :: proc "contextless" (array: ^$T/[dynamic; $N]$E, #no_broadcast args: ..E) -> (num_appended: int) {
|
||||
Raw :: Raw_Fixed_Capacity_Dynamic_Array(N, E)
|
||||
raw := (^Raw)(array)
|
||||
|
||||
n = min(N - len(array), len(args))
|
||||
num_appended = min(N - len(array), len(args))
|
||||
|
||||
#no_bounds_check when size_of(E) != 0 {
|
||||
intrinsics.mem_copy(&raw.data[raw.len], raw_data(args), n*size_of(E))
|
||||
intrinsics.mem_copy(&raw.data[raw.len], raw_data(args), num_appended*size_of(E))
|
||||
}
|
||||
|
||||
raw.len += n
|
||||
return n
|
||||
raw.len += num_appended
|
||||
return num_appended
|
||||
}
|
||||
|
||||
// The append_fixed_capacity_string built-in procedure appends multiple strings to the end of a [dynamic]u8 like type
|
||||
//
|
||||
// Note: Prefer using the procedure group `append`.
|
||||
@builtin
|
||||
append_fixed_capacity_string :: proc "contextless" (array: ^$T/[dynamic; $N]$E/u8, args: ..string) -> (n: int) {
|
||||
append_fixed_capacity_string :: proc "contextless" (array: ^$T/[dynamic; $N]$E/u8, args: ..string) -> (num_appended: int) {
|
||||
n_arg: int
|
||||
for arg in args {
|
||||
n_arg = append_fixed_capacity_elems(array, ..transmute([]E)(arg))
|
||||
n += n_arg
|
||||
num_appended += n_arg
|
||||
if n_arg < len(arg) {
|
||||
return
|
||||
}
|
||||
@@ -1319,6 +1345,35 @@ _reserve_dynamic_array :: #force_no_inline proc(a: ^Raw_Dynamic_Array, size_of_e
|
||||
return nil
|
||||
}
|
||||
|
||||
_reserve_dynamic_array_unsafe :: #force_no_inline proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, capacity: int, should_zero: bool, loc := #caller_location) -> Allocator_Error {
|
||||
if capacity <= a.cap {
|
||||
return nil
|
||||
}
|
||||
|
||||
if a.allocator.procedure == nil {
|
||||
a.allocator = context.allocator
|
||||
assert(a.allocator.procedure != nil)
|
||||
}
|
||||
|
||||
old_size := a.cap * size_of_elem
|
||||
new_size := capacity * size_of_elem
|
||||
allocator := a.allocator
|
||||
|
||||
new_data: []byte
|
||||
if should_zero {
|
||||
new_data = mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return
|
||||
} else {
|
||||
new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return
|
||||
}
|
||||
if new_data == nil && new_size > 0 {
|
||||
return .Out_Of_Memory
|
||||
}
|
||||
|
||||
a.data = raw_data(new_data)
|
||||
a.cap = capacity
|
||||
return nil
|
||||
}
|
||||
|
||||
// `reserve_dynamic_array` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`).
|
||||
//
|
||||
// When a memory resize allocation is required, the memory will be asked to be zeroed (i.e. it calls `mem_resize`).
|
||||
@@ -1479,7 +1534,6 @@ _shrink_dynamic_array :: proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem
|
||||
}
|
||||
|
||||
when MAP_ENABLED {
|
||||
|
||||
@builtin
|
||||
map_insert :: proc(m: ^$T/map[$K]$V, key: K, value: V, loc := #caller_location) -> (ptr: ^V) {
|
||||
key, value := key, value
|
||||
@@ -1635,14 +1689,14 @@ ensure_contextless :: proc "contextless" (condition: bool, message := #caller_ex
|
||||
}
|
||||
}
|
||||
|
||||
// Panics the program with a message to indicate something has yet to be implemented.
|
||||
// Panics the program with a message.
|
||||
// This uses the `default_assertion_contextless_failure_proc` to assert.
|
||||
@builtin
|
||||
panic_contextless :: proc "contextless" (message: string, loc := #caller_location) -> ! {
|
||||
default_assertion_contextless_failure_proc("panic", message, loc)
|
||||
}
|
||||
|
||||
// Panics the program with a message.
|
||||
// Panics the program with a message to indicate something has yet to be implemented.
|
||||
// This uses the `default_assertion_contextless_failure_proc` to assert.
|
||||
@builtin
|
||||
unimplemented_contextless :: proc "contextless" (message := "", loc := #caller_location) -> ! {
|
||||
|
||||
@@ -833,22 +833,22 @@ __dynamic_map_check_grow :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info:
|
||||
return nil, false
|
||||
}
|
||||
|
||||
__dynamic_map_set_without_hash :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, key, value: rawptr, loc := #caller_location) -> rawptr {
|
||||
__dynamic_map_set_without_hash :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, key, value: rawptr, loc := #caller_location) -> (value_ptr: rawptr, err: Allocator_Error) #optional_allocator_error {
|
||||
return __dynamic_map_set(m, info, info.key_hasher(key, map_seed(m^)), key, value, loc)
|
||||
}
|
||||
|
||||
|
||||
// IMPORTANT: USED WITHIN THE COMPILER
|
||||
__dynamic_map_set :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, hash: Map_Hash, key, value: rawptr, loc := #caller_location) -> rawptr {
|
||||
__dynamic_map_set :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, hash: Map_Hash, key, value: rawptr, loc := #caller_location) -> (value_ptr: rawptr, err: Allocator_Error) #optional_allocator_error {
|
||||
if found := __dynamic_map_get(m, info, hash, key); found != nil {
|
||||
intrinsics.mem_copy_non_overlapping(found, value, info.vs.size_of_type)
|
||||
return found
|
||||
return found, nil
|
||||
}
|
||||
|
||||
hash := hash
|
||||
err, has_grown := __dynamic_map_check_grow(m, info, loc)
|
||||
if err != nil {
|
||||
return nil
|
||||
err_grow, has_grown := __dynamic_map_check_grow(m, info, loc)
|
||||
if err_grow != nil {
|
||||
return nil, err_grow
|
||||
}
|
||||
if has_grown {
|
||||
hash = info.key_hasher(key, map_seed(m^))
|
||||
@@ -858,7 +858,7 @@ __dynamic_map_set :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_In
|
||||
if result != 0 {
|
||||
m.len += 1
|
||||
}
|
||||
return rawptr(result)
|
||||
return rawptr(result), nil
|
||||
}
|
||||
__dynamic_map_set_extra_without_hash :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, key, value: rawptr, loc := #caller_location) -> (prev_key_ptr, value_ptr: rawptr) {
|
||||
return __dynamic_map_set_extra(m, info, info.key_hasher(key, map_seed(m^)), key, value, loc)
|
||||
@@ -988,4 +988,4 @@ default_hasher_quaternion256 :: proc "contextless" (x, y, z, w: f64, seed: uintp
|
||||
seed = default_hasher_f64(z, seed)
|
||||
seed = default_hasher_f64(w, seed)
|
||||
return seed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#+private
|
||||
#+build linux, darwin, freebsd, openbsd, netbsd, haiku
|
||||
#+build linux, darwin, freebsd, openbsd, netbsd
|
||||
#+no-instrumentation
|
||||
package runtime
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#+build linux, darwin, freebsd, openbsd, netbsd, haiku
|
||||
#+build linux, darwin, freebsd, openbsd, netbsd
|
||||
#+private
|
||||
package runtime
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@ IS_WASM :: ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32
|
||||
|
||||
@(private)
|
||||
RUNTIME_LINKAGE :: "strong" when ODIN_USE_SEPARATE_MODULES else
|
||||
"internal" when ODIN_NO_ENTRY_POINT && (ODIN_BUILD_MODE == .Static || ODIN_BUILD_MODE == .Dynamic || ODIN_BUILD_MODE == .Object) else
|
||||
"strong" when ODIN_BUILD_MODE == .Dynamic else
|
||||
"strong" when !ODIN_NO_CRT else
|
||||
"internal"
|
||||
"internal" when ODIN_NO_ENTRY_POINT && (ODIN_BUILD_MODE == .Static || ODIN_BUILD_MODE == .Dynamic || ODIN_BUILD_MODE == .Object) else
|
||||
"strong" when ODIN_BUILD_MODE == .Dynamic else
|
||||
"strong" when !ODIN_NO_CRT else
|
||||
"internal"
|
||||
RUNTIME_REQUIRE :: false // !ODIN_TILDE
|
||||
|
||||
@(private)
|
||||
@@ -24,7 +24,7 @@ HAS_HARDWARE_SIMD :: false when (ODIN_ARCH == .amd64 || ODIN_ARCH == .i386) && !
|
||||
true
|
||||
|
||||
// Size of a native SIMD register for the current compilation target
|
||||
NATIVE_SIMD_BIT_WIDTH ::
|
||||
NATIVE_SIMD_BIT_WIDTH ::
|
||||
512 when (ODIN_ARCH == .amd64) && intrinsics.has_target_feature("avx512f") else
|
||||
256 when (ODIN_ARCH == .amd64) && (intrinsics.has_target_feature("avx2") || intrinsics.has_target_feature("avx")) else
|
||||
// Fallback for no hardware SIMD, but also SSE, NEON, SVE, RVV and WASM SIMD128.
|
||||
@@ -1165,25 +1165,215 @@ extendhfsf2 :: proc "c" (value: __float16) -> f32 {
|
||||
}
|
||||
|
||||
|
||||
@(link_name="__floattidf", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
floattidf :: proc "c" (a: i128) -> f64 {
|
||||
DBL_MANT_DIG :: 53
|
||||
if a == 0 {
|
||||
return 0.0
|
||||
}
|
||||
a := a
|
||||
N :: size_of(i128) * 8
|
||||
s := a >> (N-1)
|
||||
a = (a ~ s) - s
|
||||
sd: = N - intrinsics.count_leading_zeros(a) // number of significant digits
|
||||
e := i32(sd - 1) // exponent
|
||||
if sd > DBL_MANT_DIG {
|
||||
switch sd {
|
||||
case DBL_MANT_DIG + 1:
|
||||
a <<= 1
|
||||
case DBL_MANT_DIG + 2:
|
||||
// okay
|
||||
case:
|
||||
a = i128(u128(a) >> u128(sd - (DBL_MANT_DIG+2))) |
|
||||
i128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0)
|
||||
}
|
||||
|
||||
__write_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uintptr) {
|
||||
for i in 0..<size {
|
||||
j := offset+i
|
||||
the_bit := byte((src[i>>3]) & (1<<(i&7)) != 0)
|
||||
dst[j>>3] &~= 1<<(j&7)
|
||||
dst[j>>3] |= the_bit<<(j&7)
|
||||
a |= i128((a & 4) != 0)
|
||||
a += 1
|
||||
a >>= 2
|
||||
|
||||
if a & (i128(1) << DBL_MANT_DIG) != 0 {
|
||||
a >>= 1
|
||||
e += 1
|
||||
}
|
||||
} else {
|
||||
a <<= u128(DBL_MANT_DIG - sd) & 127
|
||||
}
|
||||
fb: [2]u32
|
||||
fb[1] = (u32(s) & 0x80000000) | // sign
|
||||
(u32(e + 1023) << 20) | // exponent
|
||||
u32((u64(a) >> 32) & 0x000FFFFF) // mantissa-high
|
||||
fb[0] = u32(a) // mantissa-low
|
||||
return transmute(f64)fb
|
||||
}
|
||||
|
||||
|
||||
@(link_name="__floattidf_unsigned", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
floattidf_unsigned :: proc "c" (a: u128) -> f64 {
|
||||
DBL_MANT_DIG :: 53
|
||||
if a == 0 {
|
||||
return 0.0
|
||||
}
|
||||
a := a
|
||||
N :: size_of(u128) * 8
|
||||
sd: = N - intrinsics.count_leading_zeros(a) // number of significant digits
|
||||
e := i32(sd - 1) // exponent
|
||||
if sd > DBL_MANT_DIG {
|
||||
switch sd {
|
||||
case DBL_MANT_DIG + 1:
|
||||
a <<= 1
|
||||
case DBL_MANT_DIG + 2:
|
||||
// okay
|
||||
case:
|
||||
a = u128(u128(a) >> u128(sd - (DBL_MANT_DIG+2))) |
|
||||
u128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0)
|
||||
}
|
||||
|
||||
a |= u128((a & 4) != 0)
|
||||
a += 1
|
||||
a >>= 2
|
||||
|
||||
if a & (1 << DBL_MANT_DIG) != 0 {
|
||||
a >>= 1
|
||||
e += 1
|
||||
}
|
||||
} else {
|
||||
a <<= u128(DBL_MANT_DIG - sd)
|
||||
}
|
||||
fb: [2]u32
|
||||
fb[1] = (0) | // sign
|
||||
u32((e + 1023) << 20) | // exponent
|
||||
u32((u64(a) >> 32) & 0x000FFFFF) // mantissa-high
|
||||
fb[0] = u32(a) // mantissa-low
|
||||
return transmute(f64)fb
|
||||
}
|
||||
|
||||
|
||||
|
||||
@(link_name="__fixunsdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
fixunsdfti :: #force_no_inline proc "c" (a: f64) -> u128 {
|
||||
// TODO(bill): implement `fixunsdfti` correctly
|
||||
x := u64(a)
|
||||
return u128(x)
|
||||
}
|
||||
|
||||
@(link_name="__fixunsdfdi", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
fixunsdfdi :: #force_no_inline proc "c" (a: f64) -> i128 {
|
||||
// TODO(bill): implement `fixunsdfdi` correctly
|
||||
x := i64(a)
|
||||
return i128(x)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@(link_name="__umodti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
umodti3 :: proc "c" (a, b: u128) -> u128 {
|
||||
r: u128 = ---
|
||||
_ = udivmod128(a, b, &r)
|
||||
return r
|
||||
}
|
||||
|
||||
|
||||
@(link_name="__udivmodti4", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
udivmodti4 :: proc "c" (a, b: u128, rem: ^u128) -> u128 {
|
||||
return udivmod128(a, b, rem)
|
||||
}
|
||||
|
||||
when !IS_WASM {
|
||||
@(link_name="__udivti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
udivti3 :: proc "c" (a, b: u128) -> u128 {
|
||||
return udivmodti4(a, b, nil)
|
||||
}
|
||||
}
|
||||
|
||||
__read_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uintptr) {
|
||||
for j in 0..<size {
|
||||
i := offset+j
|
||||
the_bit := byte((src[i>>3]) & (1<<(i&7)) != 0)
|
||||
dst[j>>3] &~= 1<<(j&7)
|
||||
dst[j>>3] |= the_bit<<(j&7)
|
||||
}
|
||||
|
||||
@(link_name="__modti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
modti3 :: proc "c" (a, b: i128) -> i128 {
|
||||
s_a := a >> (128 - 1)
|
||||
s_b := b >> (128 - 1)
|
||||
an := (a ~ s_a) - s_a
|
||||
bn := (b ~ s_b) - s_b
|
||||
|
||||
r: u128 = ---
|
||||
_ = udivmod128(u128(an), u128(bn), &r)
|
||||
return (i128(r) ~ s_a) - s_a
|
||||
}
|
||||
|
||||
|
||||
@(link_name="__divmodti4", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
divmodti4 :: proc "c" (a, b: i128, rem: ^i128) -> i128 {
|
||||
s_a := a >> (128 - 1) // -1 if negative or 0
|
||||
s_b := b >> (128 - 1)
|
||||
an := (a ~ s_a) - s_a // absolute
|
||||
bn := (b ~ s_b) - s_b
|
||||
|
||||
s_b ~= s_a // quotient sign
|
||||
u_s_b := u128(s_b)
|
||||
u_s_a := u128(s_a)
|
||||
|
||||
r: u128 = ---
|
||||
u := i128((udivmodti4(u128(an), u128(bn), &r) ~ u_s_b) - u_s_b) // negate if negative
|
||||
rem^ = i128((r ~ u_s_a) - u_s_a)
|
||||
return u
|
||||
}
|
||||
|
||||
@(link_name="__divti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
divti3 :: proc "c" (a, b: i128) -> i128 {
|
||||
s_a := a >> (128 - 1) // -1 if negative or 0
|
||||
s_b := b >> (128 - 1)
|
||||
an := (a ~ s_a) - s_a // absolute
|
||||
bn := (b ~ s_b) - s_b
|
||||
|
||||
s_a ~= s_b // quotient sign
|
||||
u_s_a := u128(s_a)
|
||||
|
||||
return i128((udivmodti4(u128(an), u128(bn), nil) ~ u_s_a) - u_s_a) // negate if negative
|
||||
}
|
||||
|
||||
|
||||
@(link_name="__fixdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
fixdfti :: proc "c" (a: u64) -> i128 {
|
||||
significandBits :: 52
|
||||
typeWidth :: (size_of(u64)*8)
|
||||
exponentBits :: (typeWidth - significandBits - 1)
|
||||
maxExponent :: ((1 << exponentBits) - 1)
|
||||
exponentBias :: (maxExponent >> 1)
|
||||
|
||||
implicitBit :: (u64(1) << significandBits)
|
||||
significandMask :: (implicitBit - 1)
|
||||
signBit :: (u64(1) << (significandBits + exponentBits))
|
||||
absMask :: (signBit - 1)
|
||||
exponentMask :: (absMask ~ significandMask)
|
||||
|
||||
// Break a into sign, exponent, significand
|
||||
aRep := a
|
||||
aAbs := aRep & absMask
|
||||
sign := i128(-1 if aRep & signBit != 0 else 1)
|
||||
exponent := u64((aAbs >> significandBits) - exponentBias)
|
||||
significand := u64((aAbs & significandMask) | implicitBit)
|
||||
|
||||
// If exponent is negative, the result is zero.
|
||||
if exponent < 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// If the value is too large for the integer type, saturate.
|
||||
if exponent >= size_of(i128) * 8 {
|
||||
return max(i128) if sign == 1 else min(i128)
|
||||
}
|
||||
|
||||
// If 0 <= exponent < significandBits, right shift to get the result.
|
||||
// Otherwise, shift left.
|
||||
if exponent < significandBits {
|
||||
return sign * i128(significand >> (significandBits - exponent))
|
||||
} else {
|
||||
return sign * (i128(significand) << (exponent - significandBits))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
when .Address in ODIN_SANITIZER_FLAGS {
|
||||
foreign {
|
||||
@(require)
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
#+build haiku
|
||||
#+private
|
||||
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) {
|
||||
ret := _unix_write(2, raw_data(data), len(data))
|
||||
if ret < len(data) {
|
||||
err := _errnop()
|
||||
return int(ret), _OS_Errno(err^ if err != nil else 0)
|
||||
}
|
||||
return int(ret), 0
|
||||
}
|
||||
|
||||
_rand_bytes :: proc "contextless" (dst: []byte) {
|
||||
arc4random_buf(raw_data(dst), len(dst))
|
||||
}
|
||||
|
||||
_exit :: proc "contextless" (code: int) -> ! {
|
||||
trap()
|
||||
}
|
||||
@@ -114,12 +114,7 @@ Linux)
|
||||
;;
|
||||
OpenBSD)
|
||||
CXXFLAGS="$CXXFLAGS -I/usr/local/include $($LLVM_CONFIG --cxxflags --ldflags)"
|
||||
LDFLAGS="$LDFLAGS -lstdc++ -L/usr/local/lib -liconv"
|
||||
LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
|
||||
;;
|
||||
Haiku)
|
||||
CXXFLAGS="$CXXFLAGS -D_GNU_SOURCE $($LLVM_CONFIG --cxxflags --ldflags) -I/system/develop/headers/private/shared -I/system/develop/headers/private/kernel"
|
||||
LDFLAGS="$LDFLAGS -lstdc++ -liconv"
|
||||
LDFLAGS="$LDFLAGS -lstdc++ -L/usr/local/lib -Wl,-rpath,$($LLVM_CONFIG --libdir) -liconv"
|
||||
LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
|
||||
;;
|
||||
*)
|
||||
|
||||
@@ -51,8 +51,6 @@ if "%1" == "freestanding" (
|
||||
if "%1" == "rare" (
|
||||
echo Checking freebsd_i386
|
||||
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_i386
|
||||
echo Checking haiku_amd64
|
||||
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:haiku_amd64
|
||||
)
|
||||
|
||||
if "%1" == "wasm" (
|
||||
|
||||
@@ -21,8 +21,6 @@ freestanding)
|
||||
rare)
|
||||
echo Checking freebsd_i386
|
||||
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_i386
|
||||
echo Checking haiku_amd64
|
||||
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:haiku_amd64
|
||||
;;
|
||||
|
||||
wasm)
|
||||
|
||||
@@ -355,16 +355,15 @@ _reader_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offse
|
||||
//
|
||||
|
||||
|
||||
// reader_read_slice reads until the first occurrence of delim from the reader
|
||||
// It returns a slice pointing at the bytes in the buffer
|
||||
// The bytes stop being valid at the next read
|
||||
// If reader_read_slice encounters an error before finding a delimiter
|
||||
// reader_read_slice fails with error .Buffer_Full if the buffer fills without a delim
|
||||
// Because the data returned from reader_read_slice will be overwritten on the
|
||||
// next IO operation, reader_read_bytes or reader_read_string is usually preferred
|
||||
//
|
||||
// reader_read_slice returns err != nil if and only if line does not end in delim
|
||||
// reader_read_slice reads until the first occurrence of delim in the input,
|
||||
// returning a slice pointing at the bytes in the internal buffer.
|
||||
// The returned slice is only valid until the next read call.
|
||||
// If the buffer fills without finding delim, it returns .Buffer_Full.
|
||||
// If the underlying reader returns an error before finding delim, that error is returned.
|
||||
// Because the returned data will be overwritten by the next I/O operation,
|
||||
// reader_read_bytes or reader_read_string is usually preferred.
|
||||
//
|
||||
// reader_read_slice returns err != nil if and only if line does not end in delim.
|
||||
reader_read_slice :: proc(b: ^Reader, delim: byte) -> (line: []byte, err: io.Error) {
|
||||
s := 0
|
||||
for {
|
||||
|
||||
@@ -80,25 +80,6 @@ when ODIN_OS == .Darwin {
|
||||
ERANGE :: 34
|
||||
}
|
||||
|
||||
when ODIN_OS == .Haiku {
|
||||
@(private="file")
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
@(link_name="_errnop")
|
||||
_get_errno :: proc() -> ^int ---
|
||||
}
|
||||
|
||||
_HAIKU_USE_POSITIVE_POSIX_ERRORS :: #config(HAIKU_USE_POSITIVE_POSIX_ERRORS, false)
|
||||
_POSIX_ERROR_FACTOR :: -1 when _HAIKU_USE_POSITIVE_POSIX_ERRORS else 1
|
||||
|
||||
@(private="file") _GENERAL_ERROR_BASE :: min(int)
|
||||
@(private="file") _POSIX_ERROR_BASE :: _GENERAL_ERROR_BASE + 0x7000
|
||||
|
||||
EDOM :: _POSIX_ERROR_FACTOR * (_POSIX_ERROR_BASE + 16)
|
||||
EILSEQ :: _POSIX_ERROR_FACTOR * (_POSIX_ERROR_BASE + 38)
|
||||
ERANGE :: _POSIX_ERROR_FACTOR * (_POSIX_ERROR_BASE + 17)
|
||||
}
|
||||
|
||||
when ODIN_OS == .JS {
|
||||
_ :: libc
|
||||
_get_errno :: proc "c" () -> ^int {
|
||||
|
||||
@@ -110,7 +110,7 @@ when ODIN_OS == .Windows {
|
||||
}
|
||||
}
|
||||
|
||||
when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Haiku || ODIN_OS == .Windows {
|
||||
when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Windows {
|
||||
|
||||
LC_ALL :: 0
|
||||
LC_COLLATE :: 1
|
||||
|
||||
@@ -34,7 +34,7 @@ when ODIN_OS == .Windows {
|
||||
SIGTERM :: 15
|
||||
}
|
||||
|
||||
when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Haiku || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Darwin {
|
||||
when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Darwin {
|
||||
SIG_ERR :: rawptr(~uintptr(0))
|
||||
SIG_DFL :: rawptr(uintptr(0))
|
||||
SIG_IGN :: rawptr(uintptr(1))
|
||||
|
||||
@@ -200,36 +200,6 @@ when ODIN_OS == .Darwin {
|
||||
}
|
||||
}
|
||||
|
||||
when ODIN_OS == .Haiku {
|
||||
fpos_t :: distinct i64
|
||||
|
||||
_IOFBF :: 0
|
||||
_IOLBF :: 1
|
||||
_IONBF :: 2
|
||||
|
||||
BUFSIZ :: 8192
|
||||
|
||||
EOF :: int(-1)
|
||||
|
||||
FOPEN_MAX :: 128
|
||||
|
||||
FILENAME_MAX :: 256
|
||||
|
||||
L_tmpnam :: 512
|
||||
|
||||
SEEK_SET :: 0
|
||||
SEEK_CUR :: 1
|
||||
SEEK_END :: 2
|
||||
|
||||
TMP_MAX :: 32768
|
||||
|
||||
foreign libc {
|
||||
stderr: ^FILE
|
||||
stdin: ^FILE
|
||||
stdout: ^FILE
|
||||
}
|
||||
}
|
||||
|
||||
when ODIN_OS == .NetBSD {
|
||||
@(private) LRENAME :: "__posix_rename"
|
||||
@(private) LFGETPOS :: "__fgetpos50"
|
||||
|
||||
@@ -42,22 +42,6 @@ when ODIN_OS == .Linux {
|
||||
}
|
||||
}
|
||||
|
||||
when ODIN_OS == .Haiku {
|
||||
RAND_MAX :: 0x7fffffff
|
||||
|
||||
// GLIBC and MUSL only
|
||||
@(private="file")
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
__ctype_get_mb_cur_max :: proc() -> ushort ---
|
||||
}
|
||||
|
||||
MB_CUR_MAX :: #force_inline proc() -> size_t {
|
||||
return size_t(__ctype_get_mb_cur_max())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD {
|
||||
RAND_MAX :: 0x7fffffff
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ when ODIN_OS == .Windows {
|
||||
}
|
||||
}
|
||||
|
||||
when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku || ODIN_OS == .JS {
|
||||
when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .JS {
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
// 7.27.2 Time manipulation functions
|
||||
@@ -95,7 +95,7 @@ when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS =
|
||||
|
||||
time_t :: distinct i64
|
||||
|
||||
when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku {
|
||||
when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD {
|
||||
clock_t :: distinct int32_t
|
||||
} else {
|
||||
clock_t :: distinct long
|
||||
|
||||
@@ -30,10 +30,6 @@ when ODIN_OS == .Windows {
|
||||
wctrans_t :: distinct int
|
||||
wctype_t :: distinct ulong
|
||||
|
||||
} else when ODIN_OS == .Haiku {
|
||||
wctrans_t :: distinct i32
|
||||
wctype_t :: distinct i32
|
||||
|
||||
}
|
||||
|
||||
@(default_calling_convention="c")
|
||||
|
||||
@@ -29,6 +29,13 @@ Pool :: struct($T: typeid) {
|
||||
num_outstanding: int,
|
||||
num_ready: int,
|
||||
link_off: uintptr,
|
||||
// Guards free_list. An untagged Treiber stack is vulnerable to ABA:
|
||||
// between get's head load and its CAS, another thread can pop the head,
|
||||
// reuse it, and push it back — the CAS then succeeds and installs a
|
||||
// stale next pointer, handing an in-flight element to two owners.
|
||||
// A tagged head would keep this lock-free but requires a double-width
|
||||
// CAS, which is not portably available.
|
||||
mu: sync.Mutex,
|
||||
free_list: ^T,
|
||||
}
|
||||
|
||||
@@ -61,20 +68,20 @@ destroy :: proc(p: ^Pool($T)) {
|
||||
get :: proc(p: ^Pool($T)) -> (elem: ^T, err: runtime.Allocator_Error) #optional_allocator_error {
|
||||
defer sync.atomic_add_explicit(&p.num_outstanding, 1, .Relaxed)
|
||||
|
||||
for {
|
||||
elem = sync.atomic_load_explicit(&p.free_list, .Acquire)
|
||||
if elem == nil {
|
||||
// NOTE: pool arena has an internal lock.
|
||||
return new(T, _pool_arena_allocator(&p.arena))
|
||||
}
|
||||
|
||||
if _, ok := sync.atomic_compare_exchange_weak_explicit(&p.free_list, elem, _get_next(p, elem), .Acquire, .Relaxed); ok {
|
||||
_set_next(p, elem, nil)
|
||||
_unpoison_elem(p, elem)
|
||||
sync.atomic_sub_explicit(&p.num_ready, 1, .Relaxed)
|
||||
return
|
||||
}
|
||||
sync.mutex_lock(&p.mu)
|
||||
elem = p.free_list
|
||||
if elem == nil {
|
||||
sync.mutex_unlock(&p.mu)
|
||||
// NOTE: pool arena has an internal lock.
|
||||
return new(T, _pool_arena_allocator(&p.arena))
|
||||
}
|
||||
p.free_list = _get_next(p, elem)
|
||||
sync.mutex_unlock(&p.mu)
|
||||
|
||||
_set_next(p, elem, nil)
|
||||
_unpoison_elem(p, elem)
|
||||
sync.atomic_sub_explicit(&p.num_ready, 1, .Relaxed)
|
||||
return
|
||||
}
|
||||
|
||||
put :: proc(p: ^Pool($T), elem: ^T) {
|
||||
@@ -84,13 +91,10 @@ put :: proc(p: ^Pool($T), elem: ^T) {
|
||||
defer sync.atomic_sub_explicit(&p.num_outstanding, 1, .Relaxed)
|
||||
defer sync.atomic_add_explicit(&p.num_ready, 1, .Relaxed)
|
||||
|
||||
for {
|
||||
head := sync.atomic_load_explicit(&p.free_list, .Relaxed)
|
||||
_set_next(p, elem, head)
|
||||
if _, ok := sync.atomic_compare_exchange_weak_explicit(&p.free_list, head, elem, .Release, .Relaxed); ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
sync.mutex_lock(&p.mu)
|
||||
_set_next(p, elem, p.free_list)
|
||||
p.free_list = elem
|
||||
sync.mutex_unlock(&p.mu)
|
||||
}
|
||||
|
||||
num_outstanding :: proc(p: ^Pool($T)) -> int {
|
||||
|
||||
72
core/crypto/_mldsa/constants.odin
Normal file
72
core/crypto/_mldsa/constants.odin
Normal file
@@ -0,0 +1,72 @@
|
||||
#+private
|
||||
package _mldsa
|
||||
|
||||
CRHBYTES :: 64
|
||||
TRBYTES :: 64
|
||||
|
||||
N :: 256
|
||||
Q :: 8380417
|
||||
D :: 13
|
||||
|
||||
K_MAX :: 8
|
||||
L_MAX :: 7
|
||||
|
||||
POLYZ_PACKEDBYTES_MAX :: 640
|
||||
|
||||
POLYT1_PACKEDBYTES :: 320
|
||||
POLYT0_PACKEDBYTES :: 416
|
||||
|
||||
POLYVECT1_PACKEDBYTES_MAX :: K_MAX * POLYT1_PACKEDBYTES
|
||||
POLYW1_PACKEDBYTES_MAX :: 192
|
||||
|
||||
CTILDBYTES_MAX :: 64
|
||||
|
||||
@(require_results)
|
||||
polyeta_packedbytes :: #force_inline proc "contextless" (params: ^Params) -> int {
|
||||
POLYETA_PACKEDBYTES_2 :: 96
|
||||
POLYETA_PACKEDBYTES_4 :: 128
|
||||
|
||||
switch params.eta {
|
||||
case 2:
|
||||
return POLYETA_PACKEDBYTES_2
|
||||
case 4:
|
||||
return POLYETA_PACKEDBYTES_4
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
polyz_packedbytes :: #force_inline proc "contextless" (params: ^Params) -> int {
|
||||
POLYZ_PACKEDBYTES_GAMMA1_17 :: 576
|
||||
POLYZ_PACKEDBYTES_GAMMA1_19 :: 640
|
||||
|
||||
switch params.gamma1 {
|
||||
case 1 << 17:
|
||||
return POLYZ_PACKEDBYTES_GAMMA1_17
|
||||
case 1 << 19:
|
||||
return POLYZ_PACKEDBYTES_GAMMA1_19
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
polyw1_packedbytes :: #force_inline proc "contextless" (params: ^Params) -> int {
|
||||
POLYW1_PACKEDBYTES_GAMMA2_95232 :: 192
|
||||
POLYW1_PACKEDBYTES_GAMMA2_261888 :: 128
|
||||
|
||||
switch params.gamma2 {
|
||||
case (Q-1)/88:
|
||||
return POLYW1_PACKEDBYTES_GAMMA2_95232
|
||||
case (Q-1)/32:
|
||||
return POLYW1_PACKEDBYTES_GAMMA2_261888
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
polyvech_packedbytes :: #force_inline proc "contextless" (params: ^Params) -> int {
|
||||
return params.omega + params.k
|
||||
}
|
||||
394
core/crypto/_mldsa/dsa_internal.odin
Normal file
394
core/crypto/_mldsa/dsa_internal.odin
Normal file
@@ -0,0 +1,394 @@
|
||||
package _mldsa
|
||||
|
||||
import "core:crypto"
|
||||
import "core:crypto/shake"
|
||||
|
||||
// This implementation is derived from the PQ-CRYSTALS reference
|
||||
// implementation [[ https://github.com/pq-crystals/dilithium ]],
|
||||
// primarily for licensing reasons. Arguably mldsa-native is
|
||||
// a more "up to date" codebase, but the changes to the
|
||||
// ref code is minor and they slapped an attribution-required
|
||||
// license on something that was originally CC-0/Apache 2.0.
|
||||
|
||||
SEEDBYTES :: 32
|
||||
RNDBYTES :: 32
|
||||
CTXBYTES_MAX :: 255
|
||||
|
||||
Params :: struct {
|
||||
k: int,
|
||||
l: int,
|
||||
eta: i32,
|
||||
tau: int,
|
||||
beta: i32,
|
||||
gamma1: i32,
|
||||
gamma2: i32,
|
||||
omega: int,
|
||||
ctild_bytes: int,
|
||||
}
|
||||
|
||||
@(rodata)
|
||||
Params_44 := Params{
|
||||
k = 4,
|
||||
l = 4,
|
||||
eta = 2,
|
||||
tau = 39,
|
||||
beta = 78,
|
||||
gamma1 = 1 << 17,
|
||||
gamma2 = (Q-1)/88,
|
||||
omega = 80,
|
||||
ctild_bytes = 32,
|
||||
}
|
||||
|
||||
@(rodata)
|
||||
Params_65 := Params{
|
||||
k = 6,
|
||||
l = 5,
|
||||
eta = 4,
|
||||
tau = 49,
|
||||
beta = 196,
|
||||
gamma1 = 1 << 19,
|
||||
gamma2 = (Q-1)/32,
|
||||
omega = 55,
|
||||
ctild_bytes = 48,
|
||||
}
|
||||
|
||||
@(rodata)
|
||||
Params_87 := Params{
|
||||
k = 8,
|
||||
l = 7,
|
||||
eta = 2,
|
||||
tau = 60,
|
||||
beta = 120,
|
||||
gamma1 = 1 << 19,
|
||||
gamma2 = (Q-1)/32,
|
||||
omega = 75,
|
||||
ctild_bytes = 64,
|
||||
}
|
||||
|
||||
Private_Key :: struct {
|
||||
params: ^Params,
|
||||
|
||||
rho: [SEEDBYTES]byte,
|
||||
tr: [TRBYTES]byte,
|
||||
key: [SEEDBYTES]byte,
|
||||
t0: Polyvec_K,
|
||||
s1: Polyvec_L,
|
||||
s2: Polyvec_K,
|
||||
|
||||
pub_key: Public_Key,
|
||||
seed: [SEEDBYTES]byte,
|
||||
}
|
||||
|
||||
Public_Key :: struct {
|
||||
params: ^Params,
|
||||
|
||||
t1: Polyvec_K,
|
||||
rho: [SEEDBYTES]byte,
|
||||
mu: [TRBYTES]byte,
|
||||
}
|
||||
|
||||
@(private)
|
||||
Signature :: struct {
|
||||
params: ^Params,
|
||||
|
||||
c: [CTILDBYTES_MAX]byte,
|
||||
z: Polyvec_L,
|
||||
h: Polyvec_K,
|
||||
}
|
||||
|
||||
dsa_keygen_internal :: proc(
|
||||
priv_key: ^Private_Key,
|
||||
seed: []byte,
|
||||
params: ^Params,
|
||||
) {
|
||||
ensure(len(seed) == SEEDBYTES, "crypto/mldsa: invalid seed")
|
||||
|
||||
pub_key := &priv_key.pub_key
|
||||
pub_key.params = params
|
||||
priv_key.params = params
|
||||
|
||||
copy(priv_key.seed[:], seed)
|
||||
|
||||
seedbuf: [2*SEEDBYTES + CRHBYTES]byte = ---
|
||||
mat_: [K_MAX]Polyvec_L = ---
|
||||
defer crypto.zero_explicit(&seedbuf, size_of(seedbuf))
|
||||
defer crypto.zero_explicit(&mat_, size_of(mat_))
|
||||
|
||||
// Expand randomness for rho, rhoprime and key
|
||||
copy(seedbuf[:], seed)
|
||||
seedbuf[SEEDBYTES] = byte(params.k)
|
||||
seedbuf[SEEDBYTES+1] = byte(params.l)
|
||||
shake256(seedbuf[:], seedbuf[:SEEDBYTES+2])
|
||||
copy(priv_key.rho[:], seedbuf[:SEEDBYTES])
|
||||
rhoprime := seedbuf[SEEDBYTES:SEEDBYTES+CRHBYTES]
|
||||
copy(priv_key.key[:], seedbuf[SEEDBYTES+CRHBYTES:])
|
||||
|
||||
// Expand matrix
|
||||
mat := mat_[:params.k]
|
||||
polyvec_matrix_expand(mat, priv_key.rho[:], params)
|
||||
|
||||
// Sample short vectors s1 and s2
|
||||
polyvec_l_uniform_eta(&priv_key.s1, rhoprime, 0, params)
|
||||
polyvec_k_uniform_eta(&priv_key.s2, rhoprime, u16(params.l), params)
|
||||
|
||||
// Matrix-vector multiplication
|
||||
s1hat: Polyvec_L = ---
|
||||
defer crypto.zero_explicit(&s1hat, size_of(Polyvec_L))
|
||||
polyvec_copy(&s1hat, &priv_key.s1, params)
|
||||
polyvec_l_ntt(&s1hat, params)
|
||||
polyvec_matrix_pointwise_montgomery(&pub_key.t1, mat, &s1hat, params)
|
||||
polyvec_k_reduce(&pub_key.t1, params)
|
||||
polyvec_k_invntt_tomont(&pub_key.t1, params)
|
||||
|
||||
// Add error vector s2
|
||||
polyvec_k_add(&pub_key.t1, &pub_key.t1, &priv_key.s2, params)
|
||||
|
||||
// Extract t1 and write public key
|
||||
pk_bytes_: [SEEDBYTES+POLYVECT1_PACKEDBYTES_MAX]byte = ---
|
||||
pk_bytes := pk_bytes_[:public_key_size(params)]
|
||||
polyvec_k_caddq(&pub_key.t1, params)
|
||||
polyvec_k_power2round(&pub_key.t1, &priv_key.t0, &pub_key.t1, params)
|
||||
copy(pub_key.rho[:], priv_key.rho[:])
|
||||
_ = pack_pk(pk_bytes, pub_key)
|
||||
|
||||
// Compute H(rho, t1) and write secret key
|
||||
shake256(pub_key.mu[:], pk_bytes)
|
||||
copy(priv_key.tr[:], pub_key.mu[:])
|
||||
}
|
||||
|
||||
dsa_sign_internal :: proc(
|
||||
sig_bytes: []byte,
|
||||
m: []byte,
|
||||
ctx: []byte,
|
||||
rnd: []byte,
|
||||
priv_key: ^Private_Key,
|
||||
external_mu: []byte = nil
|
||||
) -> bool {
|
||||
params := priv_key.params
|
||||
switch params {
|
||||
case &Params_44, &Params_65, &Params_87:
|
||||
case:
|
||||
return false
|
||||
}
|
||||
if len(sig_bytes) != signature_size(params) {
|
||||
return false
|
||||
}
|
||||
ensure(len(ctx) <= CTXBYTES_MAX, "crypto/mlkem: invalid contxt size")
|
||||
ensure(len(rnd) == RNDBYTES, "crypto/mlkem: invalid rnd size")
|
||||
|
||||
mu, rhoprime: [CRHBYTES]byte = ---, ---
|
||||
mat_: [K_MAX]Polyvec_L
|
||||
w1_bytes_: [SEEDBYTES+POLYVECT1_PACKEDBYTES_MAX]byte = ---
|
||||
s1, y: Polyvec_L = ---, ---
|
||||
t0, s2, w1, w0: Polyvec_K = ---, ---, ---, ---
|
||||
cp: Poly
|
||||
|
||||
polyvec_copy(&s1, &priv_key.s1, params)
|
||||
polyvec_copy(&s2, &priv_key.s2, params)
|
||||
polyvec_copy(&t0, &priv_key.t0, params)
|
||||
|
||||
defer crypto.zero_explicit(&mu, size_of(mu))
|
||||
defer crypto.zero_explicit(&rhoprime, size_of(rhoprime))
|
||||
defer crypto.zero_explicit(&mat_, size_of(mat_))
|
||||
defer crypto.zero_explicit(&w1_bytes_, size_of(w1_bytes_))
|
||||
defer polyvec_clear([]^Polyvec_L{&s1, &y})
|
||||
defer polyvec_clear([]^Polyvec_K{&t0, &s2, &w1, &w0})
|
||||
defer crypto.zero_explicit(&cp, size_of(cp))
|
||||
|
||||
sig: Signature = ---
|
||||
sig.params = params
|
||||
h := &sig.h
|
||||
z := &sig.z
|
||||
c := sig.c[:params.ctild_bytes]
|
||||
|
||||
w1_bytes := w1_bytes_[:params.k*polyw1_packedbytes(params)]
|
||||
|
||||
// Compute mu = CRH(tr, pre, msg)
|
||||
if len(external_mu) == 0 {
|
||||
// The FIPS publication handles the shake prefix
|
||||
// in the public sign operation, but doing it
|
||||
// here makes more sense.
|
||||
ctx_buf: [2]byte
|
||||
shake_ctx: shake.Context = ---
|
||||
defer shake.reset(&shake_ctx)
|
||||
|
||||
ctx_len := len(ctx)
|
||||
|
||||
shake.init_256(&shake_ctx)
|
||||
shake.write(&shake_ctx, priv_key.tr[:])
|
||||
if ctx_len > 0 {
|
||||
ctx_buf[1] = byte(ctx_len)
|
||||
}
|
||||
shake.write(&shake_ctx, ctx_buf[:])
|
||||
if ctx_len > 0 {
|
||||
shake.write(&shake_ctx, ctx)
|
||||
}
|
||||
shake.write(&shake_ctx, m)
|
||||
shake.read(&shake_ctx, mu[:])
|
||||
} else {
|
||||
ensure(len(external_mu) == CRHBYTES, "crypto/mlkem: invalid external mu")
|
||||
copy(mu[:], external_mu)
|
||||
}
|
||||
|
||||
// Compute rhoprime = CRH(key, rnd, mu)
|
||||
shake256(rhoprime[:], priv_key.key[:], rnd, mu[:])
|
||||
|
||||
// Expand matrix and transform vectors
|
||||
mat := mat_[:params.k]
|
||||
polyvec_matrix_expand(mat, priv_key.rho[:], params)
|
||||
polyvec_l_ntt(&s1, params)
|
||||
polyvec_k_ntt(&s2, params)
|
||||
polyvec_k_ntt(&t0, params)
|
||||
|
||||
// Rejection-sampling loop
|
||||
iv: u32 // ref uses u16, but ML-DSA-87 will reuse the IV at p = ~2^{-23400}
|
||||
for {
|
||||
// Sample intermediate vector y
|
||||
polyvec_l_uniform_gamma1(&y, rhoprime[:], iv, params)
|
||||
iv += 1
|
||||
|
||||
// Matrix-vector multiplication
|
||||
polyvec_copy(z, &y, params)
|
||||
polyvec_l_ntt(z, params)
|
||||
polyvec_matrix_pointwise_montgomery(&w1, mat, z, params)
|
||||
polyvec_k_reduce(&w1, params)
|
||||
polyvec_k_invntt_tomont(&w1, params)
|
||||
|
||||
// Decompose w and call the random oracle
|
||||
polyvec_k_caddq(&w1, params)
|
||||
polyvec_k_decompose(&w1, &w0, &w1, params)
|
||||
polyvec_k_pack_w1(w1_bytes, &w1, params)
|
||||
|
||||
shake256(c, mu[:], w1_bytes)
|
||||
poly_challenge(&cp, c, params)
|
||||
poly_ntt(&cp)
|
||||
|
||||
// Compute z, reject if it reveals secret
|
||||
polyvec_l_pointwise_poly_montgomery(z, &cp, &s1, params)
|
||||
polyvec_l_invntt_tomont(z, params)
|
||||
polyvec_l_add(z, z, &y, params)
|
||||
polyvec_l_reduce(z, params)
|
||||
if polyvec_l_chknorm(z, params.gamma1 - params.beta, params) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check that subtracting cs2 does not change high bits of w
|
||||
// and low bits do not reveal secret information
|
||||
polyvec_k_pointwise_poly_montgomery(h, &cp, &s2, params)
|
||||
polyvec_k_invntt_tomont(h, params)
|
||||
polyvec_k_sub(&w0, &w0, h, params)
|
||||
polyvec_k_reduce(&w0, params)
|
||||
if polyvec_k_chknorm(&w0, params.gamma2 - params.beta, params) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Compute hints for w1
|
||||
polyvec_k_pointwise_poly_montgomery(h, &cp, &t0, params)
|
||||
polyvec_k_invntt_tomont(h, params)
|
||||
polyvec_k_reduce(h, params)
|
||||
if polyvec_k_chknorm(h, params.gamma2, params) {
|
||||
continue
|
||||
}
|
||||
|
||||
polyvec_k_add(&w0, &w0, h, params)
|
||||
n := polyvec_k_make_hint(h, &w0, &w1, params)
|
||||
if n <= uint(params.omega) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Write signature
|
||||
return pack_sig(sig_bytes, &sig)
|
||||
}
|
||||
|
||||
dsa_verify_internal :: proc(
|
||||
sig_bytes: []byte,
|
||||
m: []byte,
|
||||
ctx: []byte,
|
||||
pub_key: ^Public_Key,
|
||||
) -> bool {
|
||||
ensure(len(ctx) <= CTXBYTES_MAX, "crypto/mlkem: invalid contxt size")
|
||||
|
||||
params := pub_key.params
|
||||
switch params {
|
||||
case &Params_44, &Params_65, &Params_87:
|
||||
case:
|
||||
return false
|
||||
}
|
||||
|
||||
sig: Signature = ---
|
||||
if !unpack_sig(&sig, sig_bytes, params) {
|
||||
return false
|
||||
}
|
||||
if polyvec_l_chknorm(&sig.z, params.gamma1 - params.beta, params) {
|
||||
return false
|
||||
}
|
||||
c := sig.c[:params.ctild_bytes]
|
||||
z := &sig.z
|
||||
h := &sig.h
|
||||
|
||||
t1: Polyvec_K = ---
|
||||
polyvec_copy(&t1, &pub_key.t1, params)
|
||||
rho := pub_key.rho[:]
|
||||
|
||||
// Compute CRH(H(rho, t1), pre, msg)
|
||||
mu: [CRHBYTES]byte
|
||||
{
|
||||
// The FIPS publication handles the shake prefix
|
||||
// in the public sign operation, but doing it
|
||||
// here makes more sense.
|
||||
ctx_buf: [2]byte
|
||||
shake_ctx: shake.Context = ---
|
||||
defer shake.reset(&shake_ctx)
|
||||
|
||||
ctx_len := len(ctx)
|
||||
|
||||
shake.init_256(&shake_ctx)
|
||||
shake.write(&shake_ctx, pub_key.mu[:])
|
||||
if ctx_len > 0 {
|
||||
ctx_buf[1] = byte(ctx_len)
|
||||
}
|
||||
shake.write(&shake_ctx, ctx_buf[:])
|
||||
if ctx_len > 0 {
|
||||
shake.write(&shake_ctx, ctx)
|
||||
}
|
||||
shake.write(&shake_ctx, m)
|
||||
shake.read(&shake_ctx, mu[:])
|
||||
}
|
||||
|
||||
// Matrix-vector multiplication; compute Az - c2^dt1
|
||||
mat_: [K_MAX]Polyvec_L
|
||||
w1: Polyvec_K = ---
|
||||
cp: Poly = ---
|
||||
mat := mat_[:params.l]
|
||||
|
||||
poly_challenge(&cp, c, params)
|
||||
polyvec_matrix_expand(mat, rho, params)
|
||||
|
||||
polyvec_l_ntt(z, params)
|
||||
polyvec_matrix_pointwise_montgomery(&w1, mat, z, params)
|
||||
|
||||
poly_ntt(&cp)
|
||||
polyvec_k_shiftl(&t1, params)
|
||||
polyvec_k_ntt(&t1, params)
|
||||
polyvec_k_pointwise_poly_montgomery(&t1, &cp, &t1, params)
|
||||
|
||||
polyvec_k_sub(&w1, &w1, &t1, params)
|
||||
polyvec_k_reduce(&w1, params)
|
||||
polyvec_k_invntt_tomont(&w1, params)
|
||||
|
||||
// Reconstruct w1
|
||||
buf_: [K_MAX*POLYW1_PACKEDBYTES_MAX]byte = ---
|
||||
buf := buf_[:params.k*polyw1_packedbytes(params)]
|
||||
polyvec_k_caddq(&w1, params)
|
||||
polyvec_k_use_hint(&w1, &w1, h, params)
|
||||
polyvec_k_pack_w1(buf, &w1, params)
|
||||
|
||||
// Call random oracle and verify challenge
|
||||
c2_: [CTILDBYTES_MAX]byte
|
||||
c2 := c2_[:params.ctild_bytes]
|
||||
shake256(c2, mu[:], buf)
|
||||
|
||||
// Note/perf: Can be vartime
|
||||
return crypto.compare_constant_time(c, c2) == 1
|
||||
}
|
||||
75
core/crypto/_mldsa/ntt.odin
Normal file
75
core/crypto/_mldsa/ntt.odin
Normal file
@@ -0,0 +1,75 @@
|
||||
#+private
|
||||
package _mldsa
|
||||
|
||||
@(rodata)
|
||||
ZETAS := [N]i32 {
|
||||
0, 25847, -2608894, -518909, 237124, -777960, -876248, 466468,
|
||||
1826347, 2353451, -359251, -2091905, 3119733, -2884855, 3111497, 2680103,
|
||||
2725464, 1024112, -1079900, 3585928, -549488, -1119584, 2619752, -2108549,
|
||||
-2118186, -3859737, -1399561, -3277672, 1757237, -19422, 4010497, 280005,
|
||||
2706023, 95776, 3077325, 3530437, -1661693, -3592148, -2537516, 3915439,
|
||||
-3861115, -3043716, 3574422, -2867647, 3539968, -300467, 2348700, -539299,
|
||||
-1699267, -1643818, 3505694, -3821735, 3507263, -2140649, -1600420, 3699596,
|
||||
811944, 531354, 954230, 3881043, 3900724, -2556880, 2071892, -2797779,
|
||||
-3930395, -1528703, -3677745, -3041255, -1452451, 3475950, 2176455, -1585221,
|
||||
-1257611, 1939314, -4083598, -1000202, -3190144, -3157330, -3632928, 126922,
|
||||
3412210, -983419, 2147896, 2715295, -2967645, -3693493, -411027, -2477047,
|
||||
-671102, -1228525, -22981, -1308169, -381987, 1349076, 1852771, -1430430,
|
||||
-3343383, 264944, 508951, 3097992, 44288, -1100098, 904516, 3958618,
|
||||
-3724342, -8578, 1653064, -3249728, 2389356, -210977, 759969, -1316856,
|
||||
189548, -3553272, 3159746, -1851402, -2409325, -177440, 1315589, 1341330,
|
||||
1285669, -1584928, -812732, -1439742, -3019102, -3881060, -3628969, 3839961,
|
||||
2091667, 3407706, 2316500, 3817976, -3342478, 2244091, -2446433, -3562462,
|
||||
266997, 2434439, -1235728, 3513181, -3520352, -3759364, -1197226, -3193378,
|
||||
900702, 1859098, 909542, 819034, 495491, -1613174, -43260, -522500,
|
||||
-655327, -3122442, 2031748, 3207046, -3556995, -525098, -768622, -3595838,
|
||||
342297, 286988, -2437823, 4108315, 3437287, -3342277, 1735879, 203044,
|
||||
2842341, 2691481, -2590150, 1265009, 4055324, 1247620, 2486353, 1595974,
|
||||
-3767016, 1250494, 2635921, -3548272, -2994039, 1869119, 1903435, -1050970,
|
||||
-1333058, 1237275, -3318210, -1430225, -451100, 1312455, 3306115, -1962642,
|
||||
-1279661, 1917081, -2546312, -1374803, 1500165, 777191, 2235880, 3406031,
|
||||
-542412, -2831860, -1671176, -1846953, -2584293, -3724270, 594136, -3776993,
|
||||
-2013608, 2432395, 2454455, -164721, 1957272, 3369112, 185531, -1207385,
|
||||
-3183426, 162844, 1616392, 3014001, 810149, 1652634, -3694233, -1799107,
|
||||
-3038916, 3523897, 3866901, 269760, 2213111, -975884, 1717735, 472078,
|
||||
-426683, 1723600, -1803090, 1910376, -1667432, -1104333, -260646, -3833893,
|
||||
-2939036, -2235985, -420899, -2286327, 183443, -976891, 1612842, -3545687,
|
||||
-554416, 3919660, -48306, -1362209, 3937738, 1400424, -846154, 1976782,
|
||||
}
|
||||
|
||||
ntt :: proc "contextless" (a: ^[N]i32) #no_bounds_check {
|
||||
j, k := 0, 1
|
||||
for l := 128; l > 0; l >>= 1 {
|
||||
for start := 0; start < N; start = j + l {
|
||||
zeta := ZETAS[k]
|
||||
k += 1
|
||||
for j = start; j < start + l; j += 1 {
|
||||
t := montgomery_reduce(i64(zeta) * i64(a[j + l]))
|
||||
a[j + l] = a[j] - t
|
||||
a[j] = a[j] + t
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
invntt_tomont :: proc "contextless" (a: ^[N]i32) #no_bounds_check {
|
||||
F :: 41978 // mont^2/256
|
||||
|
||||
j, k := 0, 255
|
||||
for l := 1; l < N; l <<= 1 {
|
||||
for start := 0; start < N; start = j + l {
|
||||
zeta := -ZETAS[k]
|
||||
k -= 1
|
||||
for j = start; j < start + l; j += 1 {
|
||||
t := a[j]
|
||||
a[j] = t + a[j + l]
|
||||
a[j + l] = t - a[j + l]
|
||||
a[j + l] = montgomery_reduce(i64(zeta) * i64(a[j + l]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..<N {
|
||||
a[i] = montgomery_reduce(F * i64(a[i]))
|
||||
}
|
||||
}
|
||||
208
core/crypto/_mldsa/packing.odin
Normal file
208
core/crypto/_mldsa/packing.odin
Normal file
@@ -0,0 +1,208 @@
|
||||
package _mldsa
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:crypto"
|
||||
|
||||
@(require_results)
|
||||
pack_pk :: proc "contextless" (pk_bytes: []byte, pub_key: ^Public_Key) -> bool {
|
||||
if len(pk_bytes) != public_key_size(pub_key.params) {
|
||||
return false
|
||||
}
|
||||
|
||||
seed_bytes, t1_bytes := pk_bytes[:SEEDBYTES], pk_bytes[SEEDBYTES:]
|
||||
|
||||
copy(seed_bytes, pub_key.rho[:])
|
||||
|
||||
for i in 0..<pub_key.params.k {
|
||||
polyt1_pack(t1_bytes[i*POLYT1_PACKEDBYTES:], &pub_key.t1.vec[i])
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
unpack_pk :: proc(pub_key: ^Public_Key, pk_bytes: []byte, params: ^Params) -> bool {
|
||||
if len(pk_bytes) != public_key_size(params) {
|
||||
return false
|
||||
}
|
||||
|
||||
seed_bytes, t1_bytes := pk_bytes[:SEEDBYTES], pk_bytes[SEEDBYTES:]
|
||||
|
||||
pub_key.params = params
|
||||
|
||||
copy(pub_key.rho[:], seed_bytes)
|
||||
|
||||
for i in 0..<params.k {
|
||||
polyt1_unpack(&pub_key.t1.vec[i], t1_bytes[i*POLYT1_PACKEDBYTES:])
|
||||
}
|
||||
|
||||
shake256(pub_key.mu[:], pk_bytes)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
set_pk :: proc(dst, src: ^Public_Key) {
|
||||
dst.params = src.params
|
||||
|
||||
polyvec_copy(&dst.t1, &src.t1, src.params)
|
||||
copy(dst.rho[:], src.rho[:])
|
||||
copy(dst.mu[:], src.mu[:])
|
||||
}
|
||||
|
||||
clear_pk :: proc "contextless" (pub_key: ^Public_Key) {
|
||||
crypto.zero_explicit(pub_key, size_of(Public_Key))
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
pack_sk :: proc "contextless" (sk_bytes: []byte, priv_key: ^Private_Key) -> bool {
|
||||
params := priv_key.params
|
||||
if len(sk_bytes) != private_key_size(params) {
|
||||
return false
|
||||
}
|
||||
|
||||
sk_bytes := sk_bytes
|
||||
polyeta_len := polyeta_packedbytes(params)
|
||||
|
||||
copy(sk_bytes, priv_key.rho[:])
|
||||
sk_bytes = sk_bytes[SEEDBYTES:]
|
||||
|
||||
copy(sk_bytes, priv_key.key[:])
|
||||
sk_bytes = sk_bytes[SEEDBYTES:]
|
||||
|
||||
copy(sk_bytes, priv_key.tr[:])
|
||||
sk_bytes = sk_bytes[TRBYTES:]
|
||||
|
||||
for i in 0..<params.l {
|
||||
polyeta_pack(sk_bytes[i*polyeta_len:], &priv_key.s1.vec[i], params)
|
||||
}
|
||||
sk_bytes = sk_bytes[polyeta_len*params.l:]
|
||||
|
||||
for i in 0..<params.k {
|
||||
polyeta_pack(sk_bytes[i*polyeta_len:], &priv_key.s2.vec[i], params)
|
||||
}
|
||||
sk_bytes = sk_bytes[polyeta_len*params.k:]
|
||||
|
||||
for i in 0..<params.k {
|
||||
polyt1_pack(sk_bytes[i*POLYT1_PACKEDBYTES:], &priv_key.t0.vec[i])
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
set_sk :: proc(dst, src: ^Private_Key) {
|
||||
dst.params = src.params
|
||||
|
||||
copy(dst.rho[:], src.rho[:])
|
||||
copy(dst.tr[:], src.tr[:])
|
||||
copy(dst.key[:], src.key[:])
|
||||
polyvec_copy(&dst.t0, &src.t0, src.params)
|
||||
polyvec_copy(&dst.s1, &src.s1, src.params)
|
||||
polyvec_copy(&dst.s2, &src.s2, src.params)
|
||||
|
||||
set_pk(&dst.pub_key, &src.pub_key)
|
||||
|
||||
copy(dst.seed[:], src.seed[:])
|
||||
}
|
||||
|
||||
clear_sk :: proc "contextless" (priv_key: ^Private_Key) {
|
||||
crypto.zero_explicit(priv_key, size_of(Private_Key))
|
||||
}
|
||||
|
||||
@(private,require_results)
|
||||
pack_sig :: proc "contextless" (sig_bytes: []byte, sig: ^Signature) -> bool {
|
||||
if len(sig_bytes) != signature_size(sig.params) {
|
||||
return false
|
||||
}
|
||||
|
||||
sig_bytes := sig_bytes
|
||||
polyz_len := polyz_packedbytes(sig.params)
|
||||
|
||||
copy(sig_bytes, sig.c[:sig.params.ctild_bytes])
|
||||
sig_bytes = sig_bytes[sig.params.ctild_bytes:]
|
||||
|
||||
for i in 0..<sig.params.l {
|
||||
polyz_pack(sig_bytes[i*polyz_len:], &sig.z.vec[i], sig.params)
|
||||
}
|
||||
sig_bytes = sig_bytes[sig.params.l*polyz_len:]
|
||||
|
||||
intrinsics.mem_zero(raw_data(sig_bytes), len(sig_bytes))
|
||||
|
||||
k: int
|
||||
for i in 0..<sig.params.k {
|
||||
for j in 0..<N {
|
||||
if sig.h.vec[i].coeffs[j] != 0 {
|
||||
sig_bytes[k] = byte(j)
|
||||
k += 1
|
||||
}
|
||||
}
|
||||
|
||||
sig_bytes[sig.params.omega + i] = byte(k)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@(private,require_results)
|
||||
unpack_sig :: proc "contextless" (sig: ^Signature, sig_bytes: []byte, params: ^Params) -> bool {
|
||||
if len(sig_bytes) != signature_size(params) {
|
||||
return false
|
||||
}
|
||||
|
||||
intrinsics.mem_zero(sig, size_of(Signature))
|
||||
|
||||
sig_bytes := sig_bytes
|
||||
polyz_len := polyz_packedbytes(params)
|
||||
omega := params.omega
|
||||
|
||||
copy(sig.c[:], sig_bytes[:params.ctild_bytes])
|
||||
sig_bytes = sig_bytes[params.ctild_bytes:]
|
||||
|
||||
for i in 0..<params.l {
|
||||
polyz_unpack(&sig.z.vec[i], sig_bytes[i*polyz_len:], params)
|
||||
}
|
||||
sig_bytes = sig_bytes[params.l*polyz_len:]
|
||||
|
||||
// Decode h
|
||||
k: int
|
||||
for i in 0..<params.k {
|
||||
if sig_bytes[omega + i] < byte(k) || sig_bytes[omega + i] > byte(omega) {
|
||||
return false
|
||||
}
|
||||
|
||||
for j := k; j < int(sig_bytes[omega + i]); j += 1 {
|
||||
// Coefficients are ordered for strong unforgeability
|
||||
if j > k && sig_bytes[j] <= sig_bytes[j-1] {
|
||||
return false
|
||||
}
|
||||
sig.h.vec[i].coeffs[sig_bytes[j]] = 1
|
||||
}
|
||||
|
||||
k = int(sig_bytes[omega + i])
|
||||
}
|
||||
|
||||
// Extra indices are zero for strong unforgeability
|
||||
for j := k; j < omega; j += 1 {
|
||||
if sig_bytes[j] != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
sig.params = params
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@(private,require_results)
|
||||
public_key_size :: #force_inline proc "contextless" (params: ^Params) -> int {
|
||||
return SEEDBYTES + params.k * POLYT1_PACKEDBYTES
|
||||
}
|
||||
|
||||
@(private,require_results)
|
||||
private_key_size :: #force_inline proc "contextless" (params: ^Params) -> int {
|
||||
return 2*SEEDBYTES + TRBYTES + (params.l + params.k) * polyeta_packedbytes(params) + params.k * POLYT0_PACKEDBYTES
|
||||
}
|
||||
|
||||
@(private,require_results)
|
||||
signature_size :: #force_inline proc "contextless" (params: ^Params) -> int {
|
||||
return params.ctild_bytes + params.l * polyz_packedbytes(params) + polyvech_packedbytes(params)
|
||||
}
|
||||
564
core/crypto/_mldsa/poly.odin
Normal file
564
core/crypto/_mldsa/poly.odin
Normal file
@@ -0,0 +1,564 @@
|
||||
#+private
|
||||
package _mldsa
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:crypto"
|
||||
import "core:crypto/shake"
|
||||
|
||||
Poly :: struct {
|
||||
coeffs: [N]i32,
|
||||
}
|
||||
|
||||
poly_reduce :: proc "contextless" (a: ^Poly) {
|
||||
for v, i in a.coeffs {
|
||||
a.coeffs[i] = reduce32(v)
|
||||
}
|
||||
}
|
||||
|
||||
poly_caddq :: proc "contextless" (a: ^Poly) {
|
||||
for v, i in a.coeffs {
|
||||
a.coeffs[i] = caddq(v)
|
||||
}
|
||||
}
|
||||
|
||||
poly_add :: proc "contextless" (c, a, b: ^Poly) #no_bounds_check {
|
||||
for i in 0..<N {
|
||||
c.coeffs[i] = a.coeffs[i] + b.coeffs[i]
|
||||
}
|
||||
}
|
||||
|
||||
poly_sub :: proc "contextless" (c, a, b: ^Poly) #no_bounds_check {
|
||||
for i in 0..<N {
|
||||
c.coeffs[i] = a.coeffs[i] - b.coeffs[i]
|
||||
}
|
||||
}
|
||||
|
||||
poly_shiftl :: proc "contextless" (a: ^Poly) {
|
||||
for i in 0..<N {
|
||||
a.coeffs[i] <<= D
|
||||
}
|
||||
}
|
||||
|
||||
poly_ntt :: proc "contextless" (a: ^Poly) {
|
||||
ntt(&a.coeffs)
|
||||
}
|
||||
|
||||
poly_invntt_tomont :: proc "contextless" (a: ^Poly) {
|
||||
invntt_tomont(&a.coeffs)
|
||||
}
|
||||
|
||||
poly_pointwise_montgomery :: proc "contextless" (c, a, b: ^Poly) #no_bounds_check {
|
||||
for i in 0..<N {
|
||||
c.coeffs[i] = montgomery_reduce(i64(a.coeffs[i]) * i64(b.coeffs[i]))
|
||||
}
|
||||
}
|
||||
|
||||
poly_power2round :: proc "contextless" (a1, a0, a: ^Poly) #no_bounds_check {
|
||||
for i in 0..<N {
|
||||
a0.coeffs[i], a1.coeffs[i] = power2round(a.coeffs[i])
|
||||
}
|
||||
}
|
||||
|
||||
poly_decompose :: proc "contextless" (a1, a0, a: ^Poly, params: ^Params) #no_bounds_check {
|
||||
for i in 0..<N {
|
||||
a0.coeffs[i], a1.coeffs[i] = decompose(a.coeffs[i], params.gamma2)
|
||||
}
|
||||
}
|
||||
|
||||
poly_make_hint :: proc "contextless" (h, a0, a1: ^Poly, params: ^Params) -> uint #no_bounds_check {
|
||||
s: uint
|
||||
|
||||
for i in 0..<N {
|
||||
h.coeffs[i] = i32(make_hint(a0.coeffs[i], a1.coeffs[i], params.gamma2))
|
||||
s += uint(h.coeffs[i])
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
poly_use_hint :: proc "contextless" (b, a, h: ^Poly, params: ^Params) {
|
||||
for i in 0..<N {
|
||||
b.coeffs[i] = use_hint(a.coeffs[i], uint(h.coeffs[i]), params.gamma2)
|
||||
}
|
||||
}
|
||||
|
||||
poly_chknorm :: proc "contextless" (a: ^Poly, bound: i32) -> bool #no_bounds_check {
|
||||
// It is ok to leak which coefficient violates the bound since
|
||||
// the probability for each coefficient is independent of secret
|
||||
// data but we must not leak the sign of the centralized
|
||||
// representative.
|
||||
for i in 0..<N {
|
||||
// Absolute value
|
||||
t := a.coeffs[i] >> 31
|
||||
t = a.coeffs[i] - (t & 2 * a.coeffs[i])
|
||||
|
||||
if t >= bound {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
unchecked_get_u24le :: #force_inline proc "contextless" (b: []byte) -> u32 #no_bounds_check {
|
||||
r := u32(b[0])
|
||||
r |= u32(b[1]) << 8
|
||||
r |= u32(b[2]) << 16
|
||||
return r
|
||||
}
|
||||
|
||||
rej_uniform :: proc "contextless" (a: []i32, buf: []byte) -> int #no_bounds_check {
|
||||
ctr, pos: int
|
||||
|
||||
a_len, b_len := len(a), len(buf)
|
||||
for ctr < a_len && pos + 3 <= b_len {
|
||||
t := unchecked_get_u24le(buf[pos:])
|
||||
t &= 0x7FFFFF
|
||||
pos += 3
|
||||
|
||||
if t < Q {
|
||||
a[ctr] = i32(t)
|
||||
ctr += 1
|
||||
}
|
||||
}
|
||||
|
||||
return ctr
|
||||
}
|
||||
|
||||
poly_uniform :: proc(a: ^Poly, seed: []byte, iv: u16) #no_bounds_check {
|
||||
// Note/yawning: The dilithium reference code does something
|
||||
// inexplicably more complicated, but this is identical in
|
||||
// behavior, and simpler.
|
||||
#assert(STREAM128_BLOCKBYTES % 3 == 0)
|
||||
POLY_UNIFORM_NBLOCKS :: ((768 + STREAM128_BLOCKBYTES - 1)/STREAM128_BLOCKBYTES)
|
||||
|
||||
buf: [POLY_UNIFORM_NBLOCKS*STREAM128_BLOCKBYTES]byte = ---
|
||||
defer crypto.zero_explicit(&buf, size_of(buf))
|
||||
|
||||
ctx: shake.Context = ---
|
||||
defer shake.reset(&ctx)
|
||||
stream128_init(&ctx, seed, iv)
|
||||
|
||||
shake.read(&ctx, buf[:])
|
||||
ctr := rej_uniform(a.coeffs[:], buf[:])
|
||||
|
||||
b := buf[:STREAM128_BLOCKBYTES]
|
||||
for ctr < N {
|
||||
shake.read(&ctx, b)
|
||||
ctr += rej_uniform(a.coeffs[ctr:], b)
|
||||
}
|
||||
}
|
||||
|
||||
rej_eta :: proc "contextless" (a: []i32, buf: []byte, params: ^Params) -> int {
|
||||
ctr, pos: int
|
||||
a_len, b_len := len(a), len(buf)
|
||||
switch params.eta {
|
||||
case 2:
|
||||
for ctr < a_len && pos < b_len {
|
||||
t0 := u32(buf[pos] & 0x0F)
|
||||
t1 := u32(buf[pos] >> 4)
|
||||
pos += 1
|
||||
|
||||
if t0 < 15 {
|
||||
t0 = t0 - (205 * t0 >> 10) * 5
|
||||
a[ctr] = i32(2 - t0)
|
||||
ctr += 1
|
||||
}
|
||||
if t1 < 15 && ctr < a_len {
|
||||
t1 = t1 - (205 * t1 >> 10) * 5
|
||||
a[ctr] = i32(2 - t1)
|
||||
ctr += 1
|
||||
}
|
||||
}
|
||||
case 4:
|
||||
for ctr < a_len && pos < b_len {
|
||||
t0 := u32(buf[pos] & 0x0F)
|
||||
t1 := u32(buf[pos] >> 4)
|
||||
pos += 1
|
||||
|
||||
if t0 < 9 {
|
||||
a[ctr] = i32(4 - t0)
|
||||
ctr += 1
|
||||
}
|
||||
if t1 < 9 && ctr < a_len {
|
||||
a[ctr] = i32(4 - t1)
|
||||
ctr += 1
|
||||
}
|
||||
}
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
|
||||
return ctr
|
||||
}
|
||||
|
||||
poly_uniform_eta :: proc(a: ^Poly, seed: []byte, iv: u16, params: ^Params) {
|
||||
POLY_UNIFORM_ETA2_NBLOCKS :: ((136 + STREAM256_BLOCKBYTES - 1)/STREAM256_BLOCKBYTES)
|
||||
POLY_UNIFORM_ETA4_NBLOCKS :: ((227 + STREAM256_BLOCKBYTES - 1)/STREAM256_BLOCKBYTES)
|
||||
|
||||
buf_: [POLY_UNIFORM_ETA4_NBLOCKS*STREAM256_BLOCKBYTES]byte = ---
|
||||
buf: []byte
|
||||
switch params.eta {
|
||||
case 2:
|
||||
buf = buf_[:POLY_UNIFORM_ETA2_NBLOCKS*STREAM256_BLOCKBYTES]
|
||||
case 4:
|
||||
buf = buf_[:POLY_UNIFORM_ETA4_NBLOCKS*STREAM256_BLOCKBYTES]
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
defer crypto.zero_explicit(&buf_, size_of(buf_))
|
||||
|
||||
ctx: shake.Context = ---
|
||||
defer shake.reset(&ctx)
|
||||
stream256_init(&ctx, seed, iv)
|
||||
|
||||
shake.read(&ctx, buf)
|
||||
ctr := rej_eta(a.coeffs[:], buf, params)
|
||||
|
||||
b := buf[:STREAM256_BLOCKBYTES]
|
||||
for ctr < N {
|
||||
shake.read(&ctx, b)
|
||||
ctr += rej_eta(a.coeffs[ctr:], b, params)
|
||||
}
|
||||
}
|
||||
|
||||
poly_uniform_gamma1 :: proc(a: ^Poly, seed: []byte, iv: u16, params: ^Params) {
|
||||
POLY_UNIFORM_GAMMA1_NBLOCKS_MAX :: ((POLYZ_PACKEDBYTES_MAX + STREAM256_BLOCKBYTES - 1)/STREAM256_BLOCKBYTES)
|
||||
|
||||
n_blocks := (polyz_packedbytes(params) + STREAM256_BLOCKBYTES - 1)/STREAM256_BLOCKBYTES
|
||||
|
||||
buf_: [POLY_UNIFORM_GAMMA1_NBLOCKS_MAX*STREAM256_BLOCKBYTES]byte = ---
|
||||
buf := buf_[:n_blocks*STREAM256_BLOCKBYTES]
|
||||
defer crypto.zero_explicit(&buf_, size_of(buf_))
|
||||
|
||||
ctx: shake.Context = ---
|
||||
defer shake.reset(&ctx)
|
||||
stream256_init(&ctx, seed, iv)
|
||||
|
||||
shake.read(&ctx, buf)
|
||||
polyz_unpack(a, buf, params)
|
||||
}
|
||||
|
||||
poly_challenge :: proc(c: ^Poly, seed: []byte, params: ^Params) #no_bounds_check {
|
||||
buf: [STREAM256_BLOCKBYTES]byte = ---
|
||||
defer crypto.zero_explicit(&buf, size_of(buf))
|
||||
|
||||
ctx: shake.Context = ---
|
||||
defer shake.reset(&ctx)
|
||||
|
||||
shake.init_256(&ctx)
|
||||
shake.write(&ctx, seed)
|
||||
shake.read(&ctx, buf[:])
|
||||
|
||||
signs: u64
|
||||
for i in uint(0)..<8 {
|
||||
signs |= u64(buf[i]) << (8*i)
|
||||
}
|
||||
pos := 8
|
||||
|
||||
b: int
|
||||
intrinsics.mem_zero(c, size_of(Poly))
|
||||
for i := N - params.tau; i < N; i+= 1 {
|
||||
for {
|
||||
if pos >= STREAM256_BLOCKBYTES {
|
||||
shake.read(&ctx, buf[:])
|
||||
pos = 0
|
||||
}
|
||||
|
||||
b = int(buf[pos])
|
||||
pos += 1
|
||||
if b <= i {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
c.coeffs[i] = c.coeffs[b]
|
||||
c.coeffs[b] = i32(1 - 2 * (signs & 1))
|
||||
signs >>= 1
|
||||
}
|
||||
}
|
||||
|
||||
polyeta_pack :: proc "contextless" (r: []byte, a: ^Poly, params: ^Params) #no_bounds_check {
|
||||
t: [8]byte = ---
|
||||
defer crypto.zero_explicit(&t, size_of(t))
|
||||
|
||||
eta := params.eta
|
||||
switch eta {
|
||||
case 2:
|
||||
for i in 0..<N/8 {
|
||||
t[0] = byte(eta - a.coeffs[8*i+0])
|
||||
t[1] = byte(eta - a.coeffs[8*i+1])
|
||||
t[2] = byte(eta - a.coeffs[8*i+2])
|
||||
t[3] = byte(eta - a.coeffs[8*i+3])
|
||||
t[4] = byte(eta - a.coeffs[8*i+4])
|
||||
t[5] = byte(eta - a.coeffs[8*i+5])
|
||||
t[6] = byte(eta - a.coeffs[8*i+6])
|
||||
t[7] = byte(eta - a.coeffs[8*i+7])
|
||||
|
||||
r[3*i+0] = (t[0] >> 0) | (t[1] << 3) | (t[2] << 6)
|
||||
r[3*i+1] = (t[2] >> 2) | (t[3] << 1) | (t[4] << 4) | (t[5] << 7)
|
||||
r[3*i+2] = (t[5] >> 1) | (t[6] << 2) | (t[7] << 5)
|
||||
}
|
||||
case 4:
|
||||
for i in 0..<N/2 {
|
||||
t[0] = byte(eta - a.coeffs[2*i+0])
|
||||
t[1] = byte(eta - a.coeffs[2*i+1])
|
||||
r[i] = t[0] | (t[1] << 4)
|
||||
}
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
|
||||
polyeta_unpack :: proc "contextless" (r: ^Poly, a: []byte, params: ^Params) #no_bounds_check {
|
||||
eta := params.eta
|
||||
switch eta {
|
||||
case 2:
|
||||
for i in 0..<N/8 {
|
||||
r.coeffs[8*i+0] = i32((a[3*i+0] >> 0) & 7)
|
||||
r.coeffs[8*i+1] = i32((a[3*i+0] >> 3) & 7)
|
||||
r.coeffs[8*i+2] = i32(((a[3*i+0] >> 6) | (a[3*i+1] << 2)) & 7)
|
||||
r.coeffs[8*i+3] = i32((a[3*i+1] >> 1) & 7)
|
||||
r.coeffs[8*i+4] = i32((a[3*i+1] >> 4) & 7)
|
||||
r.coeffs[8*i+5] = i32(((a[3*i+1] >> 7) | (a[3*i+2] << 1)) & 7)
|
||||
r.coeffs[8*i+6] = i32((a[3*i+2] >> 2) & 7)
|
||||
r.coeffs[8*i+7] = i32((a[3*i+2] >> 5) & 7)
|
||||
|
||||
r.coeffs[8*i+0] = eta - r.coeffs[8*i+0]
|
||||
r.coeffs[8*i+1] = eta - r.coeffs[8*i+1]
|
||||
r.coeffs[8*i+2] = eta - r.coeffs[8*i+2]
|
||||
r.coeffs[8*i+3] = eta - r.coeffs[8*i+3]
|
||||
r.coeffs[8*i+4] = eta - r.coeffs[8*i+4]
|
||||
r.coeffs[8*i+5] = eta - r.coeffs[8*i+5]
|
||||
r.coeffs[8*i+6] = eta - r.coeffs[8*i+6]
|
||||
r.coeffs[8*i+7] = eta - r.coeffs[8*i+7]
|
||||
}
|
||||
case 4:
|
||||
for i in 0..<N/2 {
|
||||
r.coeffs[2*i+0] = i32(a[i] & 0x0F)
|
||||
r.coeffs[2*i+1] = i32(a[i] >> 4)
|
||||
r.coeffs[2*i+0] = eta - r.coeffs[2*i+0]
|
||||
r.coeffs[2*i+1] = eta - r.coeffs[2*i+1]
|
||||
}
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
|
||||
polyt1_pack :: proc "contextless" (r: []byte, a: ^Poly) #no_bounds_check {
|
||||
for i in 0..<N/4 {
|
||||
r[5*i+0] = byte(a.coeffs[4*i+0] >> 0)
|
||||
r[5*i+1] = byte((a.coeffs[4*i+0] >> 8) | (a.coeffs[4*i+1] << 2))
|
||||
r[5*i+2] = byte((a.coeffs[4*i+1] >> 6) | (a.coeffs[4*i+2] << 4))
|
||||
r[5*i+3] = byte((a.coeffs[4*i+2] >> 4) | (a.coeffs[4*i+3] << 6))
|
||||
r[5*i+4] = byte(a.coeffs[4*i+3] >> 2)
|
||||
}
|
||||
}
|
||||
|
||||
polyt1_unpack :: proc "contextless" (r: ^Poly, a: []byte) #no_bounds_check {
|
||||
for i in 0..<N/4 {
|
||||
r.coeffs[4*i+0] = i32((u32(a[5*i+0] >> 0) | (u32(a[5*i+1]) << 8)) & 0x3FF)
|
||||
r.coeffs[4*i+1] = i32((u32(a[5*i+1] >> 2) | (u32(a[5*i+2]) << 6)) & 0x3FF)
|
||||
r.coeffs[4*i+2] = i32((u32(a[5*i+2] >> 4) | (u32(a[5*i+3]) << 4)) & 0x3FF)
|
||||
r.coeffs[4*i+3] = i32((u32(a[5*i+3] >> 6) | (u32(a[5*i+4]) << 2)) & 0x3FF)
|
||||
}
|
||||
}
|
||||
|
||||
polyt0_pack :: proc "contextless" (r: []byte, a: ^Poly) #no_bounds_check {
|
||||
t: [8]byte = ---
|
||||
defer crypto.zero_explicit(&t, size_of(t))
|
||||
|
||||
for i in 0..<N/8 {
|
||||
t[0] = byte((1 << (D-1)) - a.coeffs[8*i+0])
|
||||
t[1] = byte((1 << (D-1)) - a.coeffs[8*i+1])
|
||||
t[2] = byte((1 << (D-1)) - a.coeffs[8*i+2])
|
||||
t[3] = byte((1 << (D-1)) - a.coeffs[8*i+3])
|
||||
t[4] = byte((1 << (D-1)) - a.coeffs[8*i+4])
|
||||
t[5] = byte((1 << (D-1)) - a.coeffs[8*i+5])
|
||||
t[6] = byte((1 << (D-1)) - a.coeffs[8*i+6])
|
||||
t[7] = byte((1 << (D-1)) - a.coeffs[8*i+7])
|
||||
|
||||
r[13*i+ 0] = t[0]
|
||||
r[13*i+ 1] = t[0] >> 8
|
||||
r[13*i+ 1] |= t[1] << 5
|
||||
r[13*i+ 2] = t[1] >> 3
|
||||
r[13*i+ 3] = t[1] >> 11
|
||||
r[13*i+ 3] |= t[2] << 2
|
||||
r[13*i+ 4] = t[2] >> 6
|
||||
r[13*i+ 4] |= t[3] << 7
|
||||
r[13*i+ 5] = t[3] >> 1
|
||||
r[13*i+ 6] = t[3] >> 9
|
||||
r[13*i+ 6] |= t[4] << 4
|
||||
r[13*i+ 7] = t[4] >> 4
|
||||
r[13*i+ 8] = t[4] >> 12
|
||||
r[13*i+ 8] |= t[5] << 1
|
||||
r[13*i+ 9] = t[5] >> 7
|
||||
r[13*i+ 9] |= t[6] << 6
|
||||
r[13*i+10] = t[6] >> 2
|
||||
r[13*i+11] = t[6] >> 10
|
||||
r[13*i+11] |= t[7] << 3
|
||||
r[13*i+12] = t[7] >> 5
|
||||
}
|
||||
}
|
||||
|
||||
polyt0_unpack :: proc "contextless" (r: ^Poly, a: []byte) #no_bounds_check {
|
||||
for i in 0..<N/8 {
|
||||
r.coeffs[8*i+0] = i32(a[13*i+0])
|
||||
r.coeffs[8*i+0] |= i32(u32(a[13*i+1]) << 8)
|
||||
r.coeffs[8*i+0] &= 0x1FFF
|
||||
|
||||
r.coeffs[8*i+1] = i32(a[13*i+1] >> 5)
|
||||
r.coeffs[8*i+1] |= i32(u32(a[13*i+2]) << 3)
|
||||
r.coeffs[8*i+1] |= i32(u32(a[13*i+3]) << 11)
|
||||
r.coeffs[8*i+1] &= 0x1FFF
|
||||
|
||||
r.coeffs[8*i+2] = i32(a[13*i+3] >> 2)
|
||||
r.coeffs[8*i+2] |= i32(u32(a[13*i+4]) << 6)
|
||||
r.coeffs[8*i+2] &= 0x1FFF
|
||||
|
||||
r.coeffs[8*i+3] = i32(a[13*i+4] >> 7)
|
||||
r.coeffs[8*i+3] |= i32(u32(a[13*i+5]) << 1)
|
||||
r.coeffs[8*i+3] |= i32(u32(a[13*i+6]) << 9)
|
||||
r.coeffs[8*i+3] &= 0x1FFF
|
||||
|
||||
r.coeffs[8*i+4] = i32(a[13*i+6] >> 4)
|
||||
r.coeffs[8*i+4] |= i32(u32(a[13*i+7]) << 4)
|
||||
r.coeffs[8*i+4] |= i32(u32(a[13*i+8]) << 12)
|
||||
r.coeffs[8*i+4] &= 0x1FFF
|
||||
|
||||
r.coeffs[8*i+5] = i32(a[13*i+8] >> 1)
|
||||
r.coeffs[8*i+5] |= i32(u32(a[13*i+9]) << 7)
|
||||
r.coeffs[8*i+5] &= 0x1FFF
|
||||
|
||||
r.coeffs[8*i+6] = i32(a[13*i+9] >> 6)
|
||||
r.coeffs[8*i+6] |= i32(u32(a[13*i+10]) << 2)
|
||||
r.coeffs[8*i+6] |= i32(u32(a[13*i+11]) << 10)
|
||||
r.coeffs[8*i+6] &= 0x1FFF
|
||||
|
||||
r.coeffs[8*i+7] = i32(a[13*i+11] >> 3)
|
||||
r.coeffs[8*i+7] |= i32(u32(a[13*i+12]) << 5)
|
||||
r.coeffs[8*i+7] &= 0x1FFF
|
||||
|
||||
r.coeffs[8*i+0] = (1 << (D-1)) - r.coeffs[8*i+0]
|
||||
r.coeffs[8*i+1] = (1 << (D-1)) - r.coeffs[8*i+1]
|
||||
r.coeffs[8*i+2] = (1 << (D-1)) - r.coeffs[8*i+2]
|
||||
r.coeffs[8*i+3] = (1 << (D-1)) - r.coeffs[8*i+3]
|
||||
r.coeffs[8*i+4] = (1 << (D-1)) - r.coeffs[8*i+4]
|
||||
r.coeffs[8*i+5] = (1 << (D-1)) - r.coeffs[8*i+5]
|
||||
r.coeffs[8*i+6] = (1 << (D-1)) - r.coeffs[8*i+6]
|
||||
r.coeffs[8*i+7] = (1 << (D-1)) - r.coeffs[8*i+7]
|
||||
}
|
||||
}
|
||||
|
||||
polyz_pack :: proc "contextless" (r: []byte, a: ^Poly, params: ^Params) #no_bounds_check {
|
||||
t: [4]u32 = ---
|
||||
defer crypto.zero_explicit(&t, size_of(t))
|
||||
|
||||
gamma1 := params.gamma1
|
||||
switch gamma1 {
|
||||
case 1 << 17:
|
||||
for i in 0..<N/4 {
|
||||
t[0] = u32(gamma1 - a.coeffs[4*i+0])
|
||||
t[1] = u32(gamma1 - a.coeffs[4*i+1])
|
||||
t[2] = u32(gamma1 - a.coeffs[4*i+2])
|
||||
t[3] = u32(gamma1 - a.coeffs[4*i+3])
|
||||
|
||||
r[9*i+0] = byte(t[0])
|
||||
r[9*i+1] = byte(t[0] >> 8)
|
||||
r[9*i+2] = byte(t[0] >> 16)
|
||||
r[9*i+2] |= byte(t[1] << 2)
|
||||
r[9*i+3] = byte(t[1] >> 6)
|
||||
r[9*i+4] = byte(t[1] >> 14)
|
||||
r[9*i+4] |= byte(t[2] << 4)
|
||||
r[9*i+5] = byte(t[2] >> 4)
|
||||
r[9*i+6] = byte(t[2] >> 12)
|
||||
r[9*i+6] |= byte(t[3] << 6)
|
||||
r[9*i+7] = byte(t[3] >> 2)
|
||||
r[9*i+8] = byte(t[3] >> 10)
|
||||
}
|
||||
case 1 << 19:
|
||||
for i in 0..<N/2 {
|
||||
t[0] = u32(gamma1 - a.coeffs[2*i+0])
|
||||
t[1] = u32(gamma1 - a.coeffs[2*i+1])
|
||||
|
||||
r[5*i+0] = byte(t[0])
|
||||
r[5*i+1] = byte(t[0] >> 8)
|
||||
r[5*i+2] = byte(t[0] >> 16)
|
||||
r[5*i+2] |= byte(t[1] << 4)
|
||||
r[5*i+3] = byte(t[1] >> 4)
|
||||
r[5*i+4] = byte(t[1] >> 12)
|
||||
}
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
|
||||
polyz_unpack :: proc "contextless" (r: ^Poly, a: []byte, params: ^Params) #no_bounds_check {
|
||||
gamma1 := params.gamma1
|
||||
switch gamma1 {
|
||||
case 1 << 17:
|
||||
for i in 0..<N/4 {
|
||||
r.coeffs[4*i+0] = i32(a[9*i+0])
|
||||
r.coeffs[4*i+0] |= i32(u32(a[9*i+1]) << 8)
|
||||
r.coeffs[4*i+0] |= i32(u32(a[9*i+2]) << 16)
|
||||
r.coeffs[4*i+0] &= 0x3FFFF
|
||||
|
||||
r.coeffs[4*i+1] = i32(a[9*i+2] >> 2)
|
||||
r.coeffs[4*i+1] |= i32(u32(a[9*i+3]) << 6)
|
||||
r.coeffs[4*i+1] |= i32(u32(a[9*i+4]) << 14)
|
||||
r.coeffs[4*i+1] &= 0x3FFFF
|
||||
|
||||
r.coeffs[4*i+2] = i32(a[9*i+4] >> 4)
|
||||
r.coeffs[4*i+2] |= i32(u32(a[9*i+5]) << 4)
|
||||
r.coeffs[4*i+2] |= i32(u32(a[9*i+6]) << 12)
|
||||
r.coeffs[4*i+2] &= 0x3FFFF
|
||||
|
||||
r.coeffs[4*i+3] = i32(a[9*i+6] >> 6)
|
||||
r.coeffs[4*i+3] |= i32(u32(a[9*i+7]) << 2)
|
||||
r.coeffs[4*i+3] |= i32(u32(a[9*i+8]) << 10)
|
||||
r.coeffs[4*i+3] &= 0x3FFFF
|
||||
|
||||
r.coeffs[4*i+0] = gamma1 - r.coeffs[4*i+0]
|
||||
r.coeffs[4*i+1] = gamma1 - r.coeffs[4*i+1]
|
||||
r.coeffs[4*i+2] = gamma1 - r.coeffs[4*i+2]
|
||||
r.coeffs[4*i+3] = gamma1 - r.coeffs[4*i+3]
|
||||
}
|
||||
case 1 << 19:
|
||||
for i in 0..<N/2 {
|
||||
r.coeffs[2*i+0] = i32(a[5*i+0])
|
||||
r.coeffs[2*i+0] |= i32(u32(a[5*i+1]) << 8)
|
||||
r.coeffs[2*i+0] |= i32(u32(a[5*i+2]) << 16)
|
||||
r.coeffs[2*i+0] &= 0xFFFFF
|
||||
|
||||
r.coeffs[2*i+1] = i32(a[5*i+2] >> 4)
|
||||
r.coeffs[2*i+1] |= i32(u32(a[5*i+3]) << 4)
|
||||
r.coeffs[2*i+1] |= i32(u32(a[5*i+4]) << 12)
|
||||
/* r.coeffs[2*i+1] &= 0xFFFFF */ /* No effect, since we're anyway at 20 bits */
|
||||
|
||||
r.coeffs[2*i+0] = gamma1 - r.coeffs[2*i+0]
|
||||
r.coeffs[2*i+1] = gamma1 - r.coeffs[2*i+1]
|
||||
}
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
|
||||
polyw1_pack :: proc "contextless" (r: []byte, a: ^Poly, params: ^Params) #no_bounds_check {
|
||||
switch params.gamma2 {
|
||||
case (Q-1)/88:
|
||||
for i in 0..<N/4 {
|
||||
r[3*i+0] = byte(a.coeffs[4*i+0])
|
||||
r[3*i+0] |= byte(a.coeffs[4*i+1] << 6)
|
||||
r[3*i+1] = byte(a.coeffs[4*i+1] >> 2)
|
||||
r[3*i+1] |= byte(a.coeffs[4*i+2] << 4)
|
||||
r[3*i+2] = byte(a.coeffs[4*i+2] >> 4)
|
||||
r[3*i+2] |= byte(a.coeffs[4*i+3] << 2)
|
||||
}
|
||||
case (Q-1)/32:
|
||||
for i in 0..<N/2 {
|
||||
r[i] = byte(a.coeffs[2*i+0] | (a.coeffs[2*i+1] << 4))
|
||||
}
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
209
core/crypto/_mldsa/polyvec.odin
Normal file
209
core/crypto/_mldsa/polyvec.odin
Normal file
@@ -0,0 +1,209 @@
|
||||
#+private
|
||||
package _mldsa
|
||||
|
||||
import "core:crypto"
|
||||
|
||||
Polyvec_L :: struct {
|
||||
vec: [L_MAX]Poly,
|
||||
}
|
||||
|
||||
Polyvec_K :: struct {
|
||||
vec: [K_MAX]Poly,
|
||||
}
|
||||
|
||||
polyvec_copy :: proc "contextless" (dst, src: ^$T, params: ^Params) where T == Polyvec_L || T == Polyvec_K {
|
||||
when T == Polyvec_L {
|
||||
n := params.l
|
||||
} else {
|
||||
n := params.k
|
||||
}
|
||||
|
||||
for i in 0..<n {
|
||||
copy(dst.vec[i].coeffs[:], src.vec[i].coeffs[:])
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_clear :: proc "contextless" (vecs: []^$T) where T == Polyvec_L || T == Polyvec_K {
|
||||
for _, i in vecs {
|
||||
crypto.zero_explicit(vecs[i], size_of(T))
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_matrix_expand :: proc(mat: []Polyvec_L, rho: []byte, params: ^Params) #no_bounds_check {
|
||||
for i in 0..<params.k {
|
||||
for j in 0..<params.l {
|
||||
poly_uniform(&mat[i].vec[j], rho, u16((i << 8) + j))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_matrix_pointwise_montgomery :: proc "contextless" (t: ^Polyvec_K, mat: []Polyvec_L, v: ^Polyvec_L, params: ^Params) #no_bounds_check {
|
||||
for i in 0..<params.k {
|
||||
polyvec_l_pointwise_acc_montgomery(&t.vec[i], &mat[i], v, params)
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_l_uniform_eta :: proc(v: ^Polyvec_L, seed: []byte, iv: u16, params: ^Params) #no_bounds_check {
|
||||
iv := iv
|
||||
for i in 0..<params.l {
|
||||
poly_uniform_eta(&v.vec[i], seed, iv, params)
|
||||
iv += 1
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_l_uniform_gamma1 :: proc(v: ^Polyvec_L, seed: []byte, iv: u32, params: ^Params) #no_bounds_check {
|
||||
for i in 0..<params.l {
|
||||
poly_uniform_gamma1(&v.vec[i], seed, u16(u32(params.l) * iv + u32(i)), params)
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_l_reduce :: proc "contextless" (v: ^Polyvec_L, params: ^Params) #no_bounds_check {
|
||||
for i in 0..<params.l {
|
||||
poly_reduce(&v.vec[i])
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_l_add :: proc "contextless" (w, u, v: ^Polyvec_L, params: ^Params) #no_bounds_check {
|
||||
for i in 0..<params.l {
|
||||
poly_add(&w.vec[i], &u.vec[i], &v.vec[i])
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_l_ntt :: proc "contextless" (v: ^Polyvec_L, params: ^Params) {
|
||||
for i in 0..<params.l {
|
||||
poly_ntt(&v.vec[i])
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_l_invntt_tomont :: proc "contextless" (v: ^Polyvec_L, params: ^Params) {
|
||||
for i in 0..<params.l {
|
||||
poly_invntt_tomont(&v.vec[i])
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_l_pointwise_poly_montgomery :: proc "contextless" (r: ^Polyvec_L, a: ^Poly, v: ^Polyvec_L, params: ^Params) #no_bounds_check {
|
||||
for i in 0..<params.l {
|
||||
poly_pointwise_montgomery(&r.vec[i], a, &v.vec[i])
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_l_pointwise_acc_montgomery :: proc "contextless" (w: ^Poly, u, v: ^Polyvec_L, params: ^Params) #no_bounds_check {
|
||||
t: Poly
|
||||
|
||||
poly_pointwise_montgomery(w, &u.vec[0], &v.vec[0])
|
||||
for i in 1..<params.l {
|
||||
poly_pointwise_montgomery(&t, &u.vec[i], &v.vec[i])
|
||||
poly_add(w, w, &t)
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_l_chknorm :: proc "contextless" (v: ^Polyvec_L, bound: i32, params: ^Params) -> bool #no_bounds_check {
|
||||
for i in 0..<params.l {
|
||||
if poly_chknorm(&v.vec[i],bound) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
polyvec_k_uniform_eta :: proc (v: ^Polyvec_K, seed: []byte, iv: u16, params: ^Params) #no_bounds_check {
|
||||
iv := iv
|
||||
for i in 0..<params.k {
|
||||
poly_uniform_eta(&v.vec[i], seed, iv, params)
|
||||
iv += 1
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_k_reduce :: proc "contextless" (v: ^Polyvec_K, params: ^Params) #no_bounds_check {
|
||||
for i in 0..<params.k {
|
||||
poly_reduce(&v.vec[i])
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_k_caddq :: proc "contextless" (v: ^Polyvec_K, params: ^Params) #no_bounds_check {
|
||||
for i in 0..<params.k {
|
||||
poly_caddq(&v.vec[i])
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_k_add :: proc "contextless" (w, u, v: ^Polyvec_K, params: ^Params) #no_bounds_check {
|
||||
for i in 0..<params.k {
|
||||
poly_add(&w.vec[i], &u.vec[i], &v.vec[i])
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_k_sub :: proc "contextless" (w, u, v: ^Polyvec_K, params: ^Params) #no_bounds_check {
|
||||
for i in 0..<params.k {
|
||||
poly_sub(&w.vec[i], &u.vec[i], &v.vec[i])
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_k_shiftl :: proc "contextless" (v: ^Polyvec_K, params: ^Params) #no_bounds_check {
|
||||
for i in 0..<params.k {
|
||||
poly_shiftl(&v.vec[i])
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_k_ntt :: proc "contextless" (v: ^Polyvec_K, params: ^Params) {
|
||||
for i in 0..<params.k {
|
||||
poly_ntt(&v.vec[i])
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_k_invntt_tomont :: proc "contextless" (v: ^Polyvec_K, params: ^Params) {
|
||||
for i in 0..<params.k {
|
||||
poly_invntt_tomont(&v.vec[i])
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_k_pointwise_poly_montgomery :: proc "contextless" (r: ^Polyvec_K, a: ^Poly, v: ^Polyvec_K, params: ^Params) #no_bounds_check {
|
||||
for i in 0..<params.k {
|
||||
poly_pointwise_montgomery(&r.vec[i], a, &v.vec[i])
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_k_chknorm :: proc "contextless" (v: ^Polyvec_K, bound: i32, params: ^Params) -> bool #no_bounds_check {
|
||||
for i in 0..<params.k {
|
||||
if poly_chknorm(&v.vec[i],bound) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
polyvec_k_power2round :: proc "contextless" (v1, v0, v: ^Polyvec_K, params: ^Params) #no_bounds_check {
|
||||
for i in 0..<params.k {
|
||||
poly_power2round(&v1.vec[i], &v0.vec[i], &v.vec[i])
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_k_decompose :: proc "contextless" (v1, v0, v: ^Polyvec_K, params: ^Params) #no_bounds_check {
|
||||
for i in 0..<params.k {
|
||||
poly_decompose(&v1.vec[i], &v0.vec[i], &v.vec[i], params)
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_k_make_hint :: proc "contextless" (h, v0, v1: ^Polyvec_K, params: ^Params) -> uint #no_bounds_check {
|
||||
s: uint
|
||||
|
||||
for i in 0..<params.k {
|
||||
s += poly_make_hint(&h.vec[i], &v0.vec[i], &v1.vec[i], params)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
polyvec_k_use_hint :: proc "contextless" (w, u, h: ^Polyvec_K, params: ^Params) #no_bounds_check {
|
||||
for i in 0..<params.k {
|
||||
poly_use_hint(&w.vec[i],&u.vec[i], &h.vec[i], params)
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_k_pack_w1 :: proc "contextless" (r: []byte, w1: ^Polyvec_K, params: ^Params) #no_bounds_check {
|
||||
packed_len := polyw1_packedbytes(params)
|
||||
for i in 0..<params.k {
|
||||
polyw1_pack(r[i*packed_len:], &w1.vec[i], params)
|
||||
}
|
||||
}
|
||||
35
core/crypto/_mldsa/reduce.odin
Normal file
35
core/crypto/_mldsa/reduce.odin
Normal file
@@ -0,0 +1,35 @@
|
||||
#+private
|
||||
package _mldsa
|
||||
|
||||
// MONT :: -4186625 // 2^32 % Q
|
||||
|
||||
@(require_results)
|
||||
montgomery_reduce :: proc "contextless" (a: i64) -> i32 {
|
||||
QINV :: 58728449 // q^(-1) mod 2^32
|
||||
|
||||
t := i32(i64(i32(a)) * QINV)
|
||||
t = i32((a - i64(t) * Q) >> 32)
|
||||
return t
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
reduce32 :: #force_inline proc "contextless" (a: i32) -> i32 {
|
||||
t := (a + (1 << 22)) >> 23
|
||||
t = a - t * Q
|
||||
return t
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
caddq :: #force_inline proc "contextless" (a: i32) -> i32 {
|
||||
a := a
|
||||
a += (a >> 31) & Q
|
||||
return a
|
||||
}
|
||||
|
||||
// @(require_results)
|
||||
// freeze :: #force_inline proc "contextless" (a: i32) -> i32 {
|
||||
// a := a
|
||||
// a = reduce32(a)
|
||||
// a = caddq(a)
|
||||
// return a
|
||||
// }
|
||||
56
core/crypto/_mldsa/rounding.odin
Normal file
56
core/crypto/_mldsa/rounding.odin
Normal file
@@ -0,0 +1,56 @@
|
||||
#+private
|
||||
package _mldsa
|
||||
|
||||
power2round :: proc "contextless" (a: i32) -> (i32, i32) {
|
||||
a1 := (a + (1 << (D-1)) - 1) >> D
|
||||
a0 := a - (a1 << D)
|
||||
return a0, a1
|
||||
}
|
||||
|
||||
decompose :: proc "contextless" (a: i32, gamma2: i32) -> (i32, i32) {
|
||||
a1 := (a + 127) >> 7
|
||||
switch gamma2 {
|
||||
case (Q - 1)/32:
|
||||
a1 = (a1 * 1025 + (1 << 21)) >> 22
|
||||
a1 &= 15
|
||||
case (Q - 1)/88:
|
||||
a1 = (a1 * 11275 + (1 << 23)) >> 24
|
||||
a1 ~= ((43 - a1) >> 31) & a1
|
||||
}
|
||||
|
||||
a0 := a - a1 * 2 * gamma2
|
||||
a0 -= (((Q - 1)/2 - a0) >> 31) & Q
|
||||
return a0, a1
|
||||
}
|
||||
|
||||
make_hint :: proc "contextless" (a0, a1: i32, gamma2: i32) -> uint {
|
||||
if (a0 > gamma2 || a0 < -gamma2 || (a0 == -gamma2 && a1 != 0)) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
use_hint :: proc "contextless" (a: i32, hint: uint, gamma2: i32) -> i32 {
|
||||
a0, a1 := decompose(a, gamma2)
|
||||
if hint == 0 {
|
||||
return a1
|
||||
}
|
||||
|
||||
switch gamma2 {
|
||||
case (Q - 1)/32:
|
||||
if (a0 > 0) {
|
||||
return (a1 + 1) & 15
|
||||
} else {
|
||||
return (a1 -1) & 15
|
||||
}
|
||||
case (Q - 1)/88:
|
||||
if (a0 > 0) {
|
||||
return (a1 == 43) ? 0 : a1 + 1
|
||||
} else {
|
||||
return (a1 == 0) ? 43 : a1 - 1
|
||||
}
|
||||
}
|
||||
|
||||
unreachable()
|
||||
}
|
||||
|
||||
39
core/crypto/_mldsa/symmetric_shake.odin
Normal file
39
core/crypto/_mldsa/symmetric_shake.odin
Normal file
@@ -0,0 +1,39 @@
|
||||
#+private
|
||||
package _mldsa
|
||||
|
||||
import "core:crypto/_sha3"
|
||||
import "core:crypto/shake"
|
||||
|
||||
STREAM128_BLOCKBYTES :: _sha3.RATE_128
|
||||
STREAM256_BLOCKBYTES :: _sha3.RATE_256
|
||||
|
||||
stream128_init :: proc(ctx: ^shake.Context, seed: []byte, iv: u16) {
|
||||
t: [2]byte = ---
|
||||
t[0] = byte(iv)
|
||||
t[1] = byte(iv >> 8)
|
||||
|
||||
shake.init_128(ctx)
|
||||
shake.write(ctx, seed)
|
||||
shake.write(ctx, t[:])
|
||||
}
|
||||
|
||||
stream256_init :: proc(ctx: ^shake.Context, seed: []byte, iv: u16) {
|
||||
t: [2]byte = ---
|
||||
t[0] = byte(iv)
|
||||
t[1] = byte(iv >> 8)
|
||||
|
||||
shake.init_256(ctx)
|
||||
shake.write(ctx, seed)
|
||||
shake.write(ctx, t[:])
|
||||
}
|
||||
|
||||
shake256 :: proc(dst: []byte, srcs: ..[]byte) {
|
||||
ctx: shake.Context = ---
|
||||
defer shake.reset(&ctx)
|
||||
|
||||
shake.init_256(&ctx)
|
||||
for src in srcs {
|
||||
shake.write(&ctx, src)
|
||||
}
|
||||
shake.read(&ctx, dst)
|
||||
}
|
||||
52
core/crypto/_mlkem/cbd.odin
Normal file
52
core/crypto/_mlkem/cbd.odin
Normal file
@@ -0,0 +1,52 @@
|
||||
#+private
|
||||
package _mlkem
|
||||
|
||||
import "core:encoding/endian"
|
||||
|
||||
unchecked_get_u24le :: #force_inline proc "contextless" (b: []byte) -> u32 #no_bounds_check {
|
||||
r := u32(b[0])
|
||||
r |= u32(b[1]) << 8
|
||||
r |= u32(b[2]) << 16
|
||||
return r
|
||||
}
|
||||
|
||||
cbd3 :: proc "contextless" (r: ^Poly, buf: ^[3*N/4]byte) #no_bounds_check {
|
||||
for i in 0..<N/4 {
|
||||
t := unchecked_get_u24le(buf[3*i:])
|
||||
d := t & 0x00249249
|
||||
d += (t>>1) & 0x00249249
|
||||
d += (t>>2) & 0x00249249
|
||||
|
||||
for j in uint(0)..<4 {
|
||||
a := i16((d >> (6*j+0)) & 0x7)
|
||||
b := i16((d >> (6*j+3)) & 0x7)
|
||||
r.coeffs[4*i+int(j)] = a - b
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cbd2 :: proc "contextless" (r: ^Poly, buf: ^[2*N/4]byte) #no_bounds_check {
|
||||
for i in 0..<N/8 {
|
||||
t := endian.unchecked_get_u32le(buf[4*i:])
|
||||
d := t & 0x55555555
|
||||
d += (t>>1) & 0x55555555
|
||||
|
||||
for j in uint(0)..<8 {
|
||||
a := i16((d >> (4*j+0)) & 0x3)
|
||||
b := i16((d >> (4*j+2)) & 0x3)
|
||||
r.coeffs[8*i+int(j)] = a - b
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
poly_cbd_eta1_512 :: proc "contextless" (r: ^Poly, buf: ^[ETA1_512*N/4]byte) {
|
||||
cbd3(r, buf)
|
||||
}
|
||||
|
||||
poly_cbd_eta1 :: proc "contextless" (r: ^Poly, buf: ^[ETA1*N/4]byte) {
|
||||
cbd2(r, buf)
|
||||
}
|
||||
|
||||
poly_cbd_eta2 :: proc "contextless" (r: ^Poly, buf: ^[ETA2*N/4]byte) {
|
||||
cbd2(r, buf)
|
||||
}
|
||||
53
core/crypto/_mlkem/constants.odin
Normal file
53
core/crypto/_mlkem/constants.odin
Normal file
@@ -0,0 +1,53 @@
|
||||
package _mlkem
|
||||
|
||||
K_512 :: 2
|
||||
K_768 :: 3
|
||||
K_1024 :: 4
|
||||
K_MAX :: K_1024
|
||||
|
||||
N :: 256
|
||||
Q :: 3329
|
||||
|
||||
ETA1_512 :: 3
|
||||
ETA1 :: 2
|
||||
ETA2 :: 2
|
||||
|
||||
POLYBYTES :: 384
|
||||
SYMBYTES :: 32
|
||||
|
||||
POLYCOMPRESSEDBYTES_512 :: 128
|
||||
POLYCOMPRESSEDBYTES_768 :: 128
|
||||
POLYCOMPRESSEDBYTES_1024 :: 160
|
||||
|
||||
POLYVECBYTES_512 :: K_512 * POLYBYTES
|
||||
POLYVECBYTES_768 :: K_768 * POLYBYTES
|
||||
POLYVECBYTES_1024 :: K_1024 * POLYBYTES
|
||||
|
||||
POLYVECCOMPRESSEDBYTES_512 :: K_512 * 320
|
||||
POLYVECCOMPRESSEDBYTES_768 :: K_768 * 320
|
||||
POLYVECCOMPRESSEDBYTES_1024 :: K_1024 * 352
|
||||
|
||||
INDCPA_MSGBYTES :: SYMBYTES
|
||||
INDCPA_PUBLICKEYBYTES_512 :: POLYVECBYTES_512 + SYMBYTES
|
||||
INDCPA_SECRETKEYBYTES_512 :: POLYVECBYTES_512
|
||||
INDCPA_PUBLICKEYBYTES_768 :: POLYVECBYTES_768 + SYMBYTES
|
||||
INDCPA_SECRETKEYBYTES_768 :: POLYVECBYTES_768
|
||||
INDCPA_PUBLICKEYBYTES_1024 :: POLYVECBYTES_1024 + SYMBYTES
|
||||
INDCPA_SECRETKEYBYTES_1024 :: POLYVECBYTES_1024
|
||||
INDCPA_PUBLICKEYBYTES_MAX :: INDCPA_PUBLICKEYBYTES_1024
|
||||
INDCPA_BYTES_512 :: POLYVECCOMPRESSEDBYTES_512 + POLYCOMPRESSEDBYTES_512
|
||||
INDCPA_BYTES_768 :: POLYVECCOMPRESSEDBYTES_768 + POLYCOMPRESSEDBYTES_768
|
||||
INDCPA_BYTES_1024 :: POLYVECCOMPRESSEDBYTES_1024 + POLYCOMPRESSEDBYTES_1024
|
||||
|
||||
ENCAPSKEYBYTES_512 :: INDCPA_PUBLICKEYBYTES_512
|
||||
ENCAPSKEYBYTES_768 :: INDCPA_PUBLICKEYBYTES_768
|
||||
ENCAPSKEYBYTES_1024 :: INDCPA_PUBLICKEYBYTES_1024
|
||||
|
||||
DECAPSKEYBYTES_512 :: INDCPA_SECRETKEYBYTES_512 + INDCPA_PUBLICKEYBYTES_512 + 2 * SYMBYTES
|
||||
DECAPSKEYBYTES_768 :: INDCPA_SECRETKEYBYTES_768 + INDCPA_PUBLICKEYBYTES_768 + 2 * SYMBYTES
|
||||
DECAPSKEYBYTES_1024 :: INDCPA_SECRETKEYBYTES_1024 + INDCPA_PUBLICKEYBYTES_1024 + 2 * SYMBYTES
|
||||
|
||||
CIPHERTEXTBYTES_512 :: INDCPA_BYTES_512
|
||||
CIPHERTEXTBYTES_768 :: INDCPA_BYTES_768
|
||||
CIPHERTEXTBYTES_1024 :: INDCPA_BYTES_1024
|
||||
CIPHERTEXTBYTES_MAX :: INDCPA_BYTES_1024
|
||||
349
core/crypto/_mlkem/k_pke.odin
Normal file
349
core/crypto/_mlkem/k_pke.odin
Normal file
@@ -0,0 +1,349 @@
|
||||
#+private
|
||||
package _mlkem
|
||||
|
||||
import "core:crypto"
|
||||
import "core:crypto/shake"
|
||||
|
||||
@(require_results)
|
||||
pack_pk :: proc "contextless" (r: []byte, pk: ^Polyvec, seed: []byte, k: int) -> bool {
|
||||
pk_len := polyvec_byte_size(k)
|
||||
switch {
|
||||
case pk_len == 0:
|
||||
return false
|
||||
case len(seed) != SYMBYTES || len(r) != pk_len + SYMBYTES:
|
||||
return false
|
||||
}
|
||||
|
||||
polyvec_tobytes(r[:pk_len], pk, k)
|
||||
copy(r[pk_len:], seed)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
unpack_pk :: proc "contextless" (pk: ^Polyvec, seed, packedpk: []byte) -> bool {
|
||||
pk_len := len(packedpk) - SYMBYTES
|
||||
k: int
|
||||
switch {
|
||||
case pk_len == POLYVECBYTES_512:
|
||||
k = K_512
|
||||
case pk_len == POLYVECBYTES_768:
|
||||
k = K_768
|
||||
case pk_len == POLYVECBYTES_1024:
|
||||
k = K_1024
|
||||
case len(packedpk) - pk_len != SYMBYTES:
|
||||
return false
|
||||
case len(seed) != SYMBYTES:
|
||||
return false
|
||||
}
|
||||
if k == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
ok := polyvec_frombytes(pk, packedpk[:pk_len], k)
|
||||
copy(seed, packedpk[pk_len:])
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
pack_sk :: proc "contextless" (r: []byte, sk: ^Polyvec, k: int) -> bool {
|
||||
r_len := len(r)
|
||||
if r_len == 0 || r_len != polyvec_byte_size(k) {
|
||||
return false
|
||||
}
|
||||
|
||||
polyvec_tobytes(r, sk, k)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
unpack_sk :: proc "contextless" (sk: ^Polyvec, packedsk: []byte) -> bool {
|
||||
k: int
|
||||
switch len(packedsk) {
|
||||
case POLYVECBYTES_512:
|
||||
k = K_512
|
||||
case POLYVECBYTES_768:
|
||||
k = K_768
|
||||
case POLYVECBYTES_1024:
|
||||
k = K_1024
|
||||
case:
|
||||
return false
|
||||
}
|
||||
if k == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return polyvec_frombytes(sk, packedsk, k)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
pack_ciphertext :: proc "contextless" (r: []byte, b: ^Polyvec, v: ^Poly, k: int) -> bool {
|
||||
b_len := polyvec_compressed_byte_size(k)
|
||||
if len(r) != b_len + poly_compressed_bytes(k) {
|
||||
return false
|
||||
}
|
||||
|
||||
polyvec_compress(r[:b_len], b, k)
|
||||
poly_compress(r[b_len:], v)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
unpack_ciphertext :: proc "contextless" (b: ^Polyvec, v: ^Poly, c: []byte) -> int {
|
||||
b_len: int
|
||||
k: int
|
||||
switch len(c) {
|
||||
case INDCPA_BYTES_512:
|
||||
b_len = POLYVECCOMPRESSEDBYTES_512
|
||||
k = K_512
|
||||
case INDCPA_BYTES_768:
|
||||
b_len = POLYVECCOMPRESSEDBYTES_768
|
||||
k = K_768
|
||||
case INDCPA_BYTES_1024:
|
||||
b_len = POLYVECCOMPRESSEDBYTES_1024
|
||||
k = K_1024
|
||||
case:
|
||||
return 0
|
||||
}
|
||||
|
||||
polyvec_decompress(b, c[:b_len], k)
|
||||
poly_decompress(v, c[b_len:])
|
||||
|
||||
return k
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
rej_uniform :: proc "contextless" (r: []i16, buf: []byte) -> int {
|
||||
r_len, b_len := len(r), len(buf)
|
||||
|
||||
ctr, pos: int
|
||||
for ctr < r_len && pos + 3 <= b_len {
|
||||
val0 := (u16(buf[pos+0] >> 0) | (u16(buf[pos+1]) << 8)) & 0xFFF
|
||||
val1 := (u16(buf[pos+1] >> 4) | (u16(buf[pos+2]) << 4)) & 0xFFF
|
||||
pos += 3
|
||||
|
||||
if val0 < Q {
|
||||
r[ctr] = i16(val0)
|
||||
ctr += 1
|
||||
}
|
||||
if(ctr < r_len && val1 < Q) {
|
||||
r[ctr] = i16(val1)
|
||||
ctr += 1
|
||||
}
|
||||
}
|
||||
|
||||
return ctr
|
||||
}
|
||||
|
||||
gen_matrix :: proc(a: []Polyvec, seed: []byte, transposed: bool, k: int) {
|
||||
GEN_MATRIX_NBLOCKS :: ((12*N/8*(1 << 12)/Q + XOF_BLOCKBYTES)/XOF_BLOCKBYTES)
|
||||
|
||||
buf: [GEN_MATRIX_NBLOCKS*XOF_BLOCKBYTES]byte = ---
|
||||
ctx: shake.Context = ---
|
||||
ctr: int
|
||||
|
||||
defer shake.reset(&ctx)
|
||||
defer crypto.zero_explicit(&buf, size_of(buf))
|
||||
|
||||
for i in 0..<k {
|
||||
for j in 0..<k {
|
||||
switch transposed {
|
||||
case true:
|
||||
xof_absorb(&ctx, seed, byte(i), byte(j))
|
||||
case false:
|
||||
xof_absorb(&ctx, seed, byte(j), byte(i))
|
||||
}
|
||||
|
||||
shake.read(&ctx, buf[:])
|
||||
ctr = rej_uniform(a[i].vec[j].coeffs[:], buf[:])
|
||||
|
||||
b := buf[:XOF_BLOCKBYTES]
|
||||
for ctr < N {
|
||||
shake.read(&ctx, b)
|
||||
ctr += rej_uniform(a[i].vec[j].coeffs[ctr:], b)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
K_PKE_Decryption_Key :: struct {
|
||||
pv: Polyvec,
|
||||
k: int,
|
||||
}
|
||||
|
||||
K_PKE_Encryption_Key :: struct {
|
||||
pv: Polyvec,
|
||||
p: [SYMBYTES]byte,
|
||||
k: int,
|
||||
}
|
||||
|
||||
k_pke_encryption_key_set :: proc(dst, src: ^K_PKE_Encryption_Key) {
|
||||
k_pke_key_clear(dst)
|
||||
|
||||
for i in 0..<src.k {
|
||||
copy(dst.pv.vec[i].coeffs[:], src.pv.vec[i].coeffs[:])
|
||||
}
|
||||
copy(dst.p[:], src.p[:])
|
||||
dst.k = src.k
|
||||
}
|
||||
|
||||
k_pke_key_clear :: proc(k: $T) where T == ^K_PKE_Encryption_Key || T == ^K_PKE_Decryption_Key {
|
||||
crypto.zero_explicit(k, size_of(k^))
|
||||
}
|
||||
|
||||
k_pke_keygen :: proc(
|
||||
ek: ^K_PKE_Encryption_Key,
|
||||
dk: ^K_PKE_Decryption_Key,
|
||||
d: []byte,
|
||||
k: int,
|
||||
) {
|
||||
assert(len(d) == SYMBYTES, "crypto/mlkem: invalid K-PKE d")
|
||||
ensure(k == K_512 || k == K_768 || k == K_1024, "crypto/mlkem: invalid k")
|
||||
|
||||
buf: [2*SYMBYTES]byte = ---
|
||||
defer crypto.zero_explicit(&buf, size_of(buf))
|
||||
|
||||
a_: [K_MAX]Polyvec = ---
|
||||
e: Polyvec = ---
|
||||
defer crypto.zero_explicit(&a_, size_of(Polyvec) * k)
|
||||
defer polyvec_clear(&e)
|
||||
|
||||
a := a_[:k]
|
||||
|
||||
copy(buf[:], d)
|
||||
buf[SYMBYTES] = byte(k)
|
||||
hash_g(buf[:], buf[:SYMBYTES+1])
|
||||
|
||||
p, sigma := buf[:SYMBYTES], buf[SYMBYTES:]
|
||||
|
||||
gen_matrix(a, p, false, k)
|
||||
|
||||
n := byte(0)
|
||||
for i in 0..<k {
|
||||
if k != K_512 {
|
||||
poly_getnoise_eta1(&dk.pv.vec[i], sigma, n)
|
||||
} else {
|
||||
poly_getnoise_eta1_512(&dk.pv.vec[i], sigma, n)
|
||||
}
|
||||
n += 1
|
||||
}
|
||||
for i in 0..<k {
|
||||
if k != K_512 {
|
||||
poly_getnoise_eta1(&e.vec[i], sigma, n)
|
||||
} else {
|
||||
poly_getnoise_eta1_512(&e.vec[i], sigma, n)
|
||||
}
|
||||
n += 1
|
||||
}
|
||||
|
||||
polyvec_ntt(&dk.pv, k)
|
||||
polyvec_ntt(&e, k)
|
||||
|
||||
for i in 0..<k {
|
||||
polyvec_basemul_acc_montgomery(&ek.pv.vec[i], &a[i], &dk.pv, k)
|
||||
poly_tomont(&ek.pv.vec[i])
|
||||
}
|
||||
|
||||
polyvec_add(&ek.pv, &ek.pv, &e, k)
|
||||
polyvec_reduce(&ek.pv, k)
|
||||
|
||||
copy(ek.p[:], p)
|
||||
|
||||
dk.k = k
|
||||
ek.k = k
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
k_pke_encrypt :: proc(
|
||||
ciphertext: []byte,
|
||||
ek: ^K_PKE_Encryption_Key,
|
||||
m: []byte,
|
||||
r: []byte,
|
||||
) -> bool {
|
||||
ensure(len(m) == INDCPA_MSGBYTES, "crypto/mlkem: invalid K-PKE m")
|
||||
ensure(len(r) == SYMBYTES, "crypto/mlkem: invalid K-PKE r")
|
||||
|
||||
k := ek.k
|
||||
|
||||
at_: [K_MAX]Polyvec = ---
|
||||
sp, ep, b: Polyvec = ---, ---, ---
|
||||
kay, epp, v: Poly = ---, ---, ---
|
||||
defer crypto.zero_explicit(&at_, size_of(Polyvec) * k)
|
||||
defer polyvec_clear(&sp, &ep, &b)
|
||||
defer poly_clear(&kay, &epp, &v)
|
||||
|
||||
poly_frommsg(&kay, m)
|
||||
|
||||
at := at_[:k]
|
||||
|
||||
gen_matrix(at, ek.p[:], true, k)
|
||||
|
||||
n := byte(0)
|
||||
for i in 0..<k {
|
||||
if k != K_512 {
|
||||
poly_getnoise_eta1(&sp.vec[i], r, n)
|
||||
} else {
|
||||
poly_getnoise_eta1_512(&sp.vec[i], r, n)
|
||||
}
|
||||
n += 1
|
||||
}
|
||||
for i in 0..<k {
|
||||
poly_getnoise_eta2(&ep.vec[i], r, n)
|
||||
n += 1
|
||||
}
|
||||
poly_getnoise_eta2(&epp, r, n)
|
||||
|
||||
polyvec_ntt(&sp, k)
|
||||
|
||||
for i in 0..<k {
|
||||
polyvec_basemul_acc_montgomery(&b.vec[i], &at[i], &sp, k)
|
||||
}
|
||||
|
||||
polyvec_basemul_acc_montgomery(&v, &ek.pv, &sp, k)
|
||||
|
||||
polyvec_invntt_tomont(&b, k)
|
||||
poly_invntt_tomont(&v)
|
||||
|
||||
polyvec_add(&b, &b, &ep, k)
|
||||
poly_add(&v, &v, &epp)
|
||||
poly_add(&v, &v, &kay)
|
||||
polyvec_reduce(&b, k)
|
||||
poly_reduce(&v)
|
||||
|
||||
return pack_ciphertext(ciphertext, &b, &v, k)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
k_pke_decrypt :: proc(
|
||||
plaintext: []byte,
|
||||
dk: ^K_PKE_Decryption_Key,
|
||||
c: []byte,
|
||||
) -> bool {
|
||||
if len(plaintext) != INDCPA_MSGBYTES {
|
||||
return false
|
||||
}
|
||||
|
||||
k := dk.k
|
||||
|
||||
b: Polyvec = ---
|
||||
v, mp: Poly = ---, ---
|
||||
defer poly_clear(&v, &mp)
|
||||
|
||||
if unpack_ciphertext(&b, &v, c) != k {
|
||||
return false
|
||||
}
|
||||
|
||||
polyvec_ntt(&b, k)
|
||||
polyvec_basemul_acc_montgomery(&mp, &dk.pv, &b, k)
|
||||
poly_invntt_tomont(&mp)
|
||||
|
||||
poly_sub(&mp, &v, &mp)
|
||||
poly_reduce(&mp)
|
||||
|
||||
poly_tomsg(plaintext, &mp)
|
||||
|
||||
return true
|
||||
}
|
||||
195
core/crypto/_mlkem/kem_internal.odin
Normal file
195
core/crypto/_mlkem/kem_internal.odin
Normal file
@@ -0,0 +1,195 @@
|
||||
package _mlkem
|
||||
|
||||
import "core:crypto"
|
||||
import subtle "core:crypto/_subtle"
|
||||
|
||||
// This implementation is derived from the PQ-CRYSTALS reference
|
||||
// implementation [[ https://github.com/pq-crystals/kyber ]],
|
||||
// primarily for licensing reasons. Arguably mlkem-native is
|
||||
// a more "up to date" codebase, but the changes to the
|
||||
// ref code is minor and they slapped an attribution-required
|
||||
// license on something that was originally CC-0/Apache 2.0.
|
||||
|
||||
// "Private Key"
|
||||
Decapsulation_Key :: struct {
|
||||
pke_dk: K_PKE_Decryption_Key,
|
||||
ek: Encapsulation_Key,
|
||||
seed: [SYMBYTES*2]byte, // (d, z)
|
||||
}
|
||||
|
||||
// "Public Key"
|
||||
Encapsulation_Key :: struct {
|
||||
pke_ek: K_PKE_Encryption_Key,
|
||||
raw_bytes: [INDCPA_PUBLICKEYBYTES_MAX]byte,
|
||||
h: [SYMBYTES]byte,
|
||||
}
|
||||
|
||||
decapsulation_key_expanded_bytes :: proc(
|
||||
dk: ^Decapsulation_Key,
|
||||
dst: []byte,
|
||||
) {
|
||||
sk := &dk.pke_dk
|
||||
pv_len := polyvec_byte_size(sk.k)
|
||||
ek_len := pv_len + SYMBYTES
|
||||
|
||||
ek_bytes := dk.ek.raw_bytes[:ek_len]
|
||||
|
||||
dst := dst
|
||||
_ = pack_sk(dst[:pv_len], &sk.pv, sk.k)
|
||||
|
||||
dst = dst[pv_len:]
|
||||
copy(dst, ek_bytes)
|
||||
dst = dst[ek_len:]
|
||||
hash_h(dst[:SYMBYTES], ek_bytes)
|
||||
dst = dst[SYMBYTES:]
|
||||
copy(dst, dk.seed[SYMBYTES:])
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
encapsulation_key_set_bytes :: proc(
|
||||
ek: ^Encapsulation_Key,
|
||||
k: int,
|
||||
b: []byte,
|
||||
) -> bool {
|
||||
k_len: int
|
||||
switch k {
|
||||
case K_512:
|
||||
k_len = ENCAPSKEYBYTES_512
|
||||
case K_768:
|
||||
k_len = ENCAPSKEYBYTES_768
|
||||
case K_1024:
|
||||
k_len = ENCAPSKEYBYTES_1024
|
||||
case:
|
||||
return false
|
||||
}
|
||||
if len(b) != k_len {
|
||||
return false
|
||||
}
|
||||
|
||||
pke_ek := &ek.pke_ek
|
||||
ok := unpack_pk(&pke_ek.pv, pke_ek.p[:], b)
|
||||
pke_ek.k = k
|
||||
copy(ek.raw_bytes[:k_len], b)
|
||||
hash_h(ek.h[:], b)
|
||||
|
||||
// FIPS 203 unlike Kyber requires canonical encoding of
|
||||
// encapsulation keys (Section 7,2), which is checked in
|
||||
// unpack_pk.
|
||||
|
||||
if !ok {
|
||||
crypto.zero_explicit(ek, size_of(Encapsulation_Key))
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
encapsulation_key_set_decaps :: proc(ek: ^Encapsulation_Key, dk: ^Decapsulation_Key) {
|
||||
dk_ek := &dk.ek.pke_ek
|
||||
ensure(dk_ek.k == K_512 || dk_ek.k == K_768 || dk_ek.k == K_1024, "crypto/mlkem: invalid decaps k")
|
||||
|
||||
k_pke_encryption_key_set(&ek.pke_ek, dk_ek)
|
||||
copy(ek.raw_bytes[:], dk.ek.raw_bytes[:])
|
||||
copy(ek.h[:], dk.ek.h[:])
|
||||
}
|
||||
|
||||
// NIST's version of this also returns an encapsulation key, but our
|
||||
// internal representation includes it as part of the decapsulation key
|
||||
// in a more traditional "keypair" approach.
|
||||
kem_keygen_internal :: proc(
|
||||
dk: ^Decapsulation_Key,
|
||||
seed: []byte, // (d, z)
|
||||
k: int,
|
||||
) {
|
||||
ensure(len(seed) == 2 * SYMBYTES, "crypto/mlkem: invalid seed")
|
||||
|
||||
dk_ek := &dk.ek
|
||||
d, z := seed[:SYMBYTES], seed[SYMBYTES:]
|
||||
|
||||
k_pke_keygen(&dk_ek.pke_ek, &dk.pke_dk, d, k)
|
||||
|
||||
ek_len := polyvec_byte_size(k) + SYMBYTES
|
||||
ek_bytes := dk_ek.raw_bytes[:ek_len]
|
||||
ensure(
|
||||
pack_pk(ek_bytes, &dk_ek.pke_ek.pv, dk_ek.pke_ek.p[:], k),
|
||||
"crypto/mlkem: failed to pack K-PKE ek",
|
||||
)
|
||||
hash_h(dk_ek.h[:], ek_bytes)
|
||||
copy(dk.seed[:SYMBYTES], d)
|
||||
copy(dk.seed[SYMBYTES:], z)
|
||||
}
|
||||
|
||||
// The `_internal` "de-randomized" versions of ML-KEM.Encaps and
|
||||
// ML-KEM.Decaps are only ever to be called by the actual non-interal
|
||||
// implementation or test cases.
|
||||
|
||||
kem_encaps_internal :: proc(
|
||||
shared_secret: []byte,
|
||||
ciphertext: []byte,
|
||||
ek: ^Encapsulation_Key,
|
||||
randomness: []byte,
|
||||
) {
|
||||
ensure(len(shared_secret) == SYMBYTES, "crypto/mlkem: invalid K")
|
||||
ensure(len(randomness) == SYMBYTES, "crypto/mlkem: invalid m")
|
||||
ensure(
|
||||
len(ciphertext) == ct_len_for_k(ek.pke_ek.k),
|
||||
"crypto/mlkem: invalid ciphertext length",
|
||||
)
|
||||
|
||||
buf: [2*SYMBYTES]byte = ---
|
||||
defer crypto.zero_explicit(&buf, size_of(buf))
|
||||
|
||||
hash_g(buf[:], randomness, ek.h[:])
|
||||
|
||||
// Can't fail, ciphertext length is valid.
|
||||
_ = k_pke_encrypt(ciphertext, &ek.pke_ek, randomness, buf[SYMBYTES:])
|
||||
|
||||
copy(shared_secret, buf[:SYMBYTES])
|
||||
}
|
||||
|
||||
kem_decaps_internal :: proc(
|
||||
shared_secret: []byte,
|
||||
dk: ^Decapsulation_Key,
|
||||
ciphertext: []byte,
|
||||
) {
|
||||
ct_len := ct_len_for_k(dk.pke_dk.k)
|
||||
ensure(
|
||||
len(ciphertext) == ct_len,
|
||||
"crypto/mlkem: invalid ciphertext length",
|
||||
)
|
||||
|
||||
m_: [SYMBYTES]byte
|
||||
defer crypto.zero_explicit(&m_, size_of(m_))
|
||||
|
||||
// Can't fail, ciphertext length is valid.
|
||||
_ = k_pke_decrypt(m_[:], &dk.pke_dk, ciphertext)
|
||||
|
||||
buf: [2*SYMBYTES]byte = ---
|
||||
defer crypto.zero_explicit(&buf, size_of(buf))
|
||||
|
||||
ek := &dk.ek
|
||||
hash_g(buf[:], m_[:], ek.h[:])
|
||||
|
||||
rkprf(shared_secret, dk.seed[SYMBYTES:], ciphertext)
|
||||
|
||||
ct_buf: [CIPHERTEXTBYTES_MAX]byte = ---
|
||||
defer crypto.zero_explicit(&ct_buf, size_of(ct_buf))
|
||||
ct_ := ct_buf[:ct_len]
|
||||
_ = k_pke_encrypt(ct_, &ek.pke_ek, m_[:], buf[SYMBYTES:])
|
||||
|
||||
ok := crypto.compare_constant_time(ciphertext, ct_)
|
||||
subtle.cmov_bytes(shared_secret, buf[:SYMBYTES], ok)
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
ct_len_for_k :: proc(k: int) -> int {
|
||||
switch k {
|
||||
case K_512:
|
||||
return CIPHERTEXTBYTES_512
|
||||
case K_768:
|
||||
return CIPHERTEXTBYTES_768
|
||||
case K_1024:
|
||||
return CIPHERTEXTBYTES_1024
|
||||
case:
|
||||
panic("crypto/mlkem: invalid k for ciphertext length")
|
||||
}
|
||||
}
|
||||
75
core/crypto/_mlkem/ntt.odin
Normal file
75
core/crypto/_mlkem/ntt.odin
Normal file
@@ -0,0 +1,75 @@
|
||||
#+private
|
||||
package _mlkem
|
||||
|
||||
@(rodata)
|
||||
ZETAS := [128]i16 {
|
||||
-1044, -758, -359, -1517, 1493, 1422, 287, 202,
|
||||
-171, 622, 1577, 182, 962, -1202, -1474, 1468,
|
||||
573, -1325, 264, 383, -829, 1458, -1602, -130,
|
||||
-681, 1017, 732, 608, -1542, 411, -205, -1571,
|
||||
1223, 652, -552, 1015, -1293, 1491, -282, -1544,
|
||||
516, -8, -320, -666, -1618, -1162, 126, 1469,
|
||||
-853, -90, -271, 830, 107, -1421, -247, -951,
|
||||
-398, 961, -1508, -725, 448, -1065, 677, -1275,
|
||||
-1103, 430, 555, 843, -1251, 871, 1550, 105,
|
||||
422, 587, 177, -235, -291, -460, 1574, 1653,
|
||||
-246, 778, 1159, -147, -777, 1483, -602, 1119,
|
||||
-1590, 644, -872, 349, 418, 329, -156, -75,
|
||||
817, 1097, 603, 610, 1322, -1285, -1465, 384,
|
||||
-1215, -136, 1218, -1335, -874, 220, -1187, -1659,
|
||||
-1185, -1530, -1278, 794, -1510, -854, -870, 478,
|
||||
-108, -308, 996, 991, 958, -1460, 1522, 1628,
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
fqmul :: #force_inline proc "contextless" (a, b: i16) -> i16 {
|
||||
return montgomery_reduce(i32(a) * i32(b))
|
||||
}
|
||||
|
||||
ntt :: proc "contextless" (r: ^[N]i16) #no_bounds_check {
|
||||
j, k := 0, 1
|
||||
for l := 128; l >= 2; l >>= 1 {
|
||||
for start := 0; start < N; start = j + l {
|
||||
zeta := ZETAS[k]
|
||||
k += 1
|
||||
for j = start; j < start + l; j += 1 {
|
||||
t := fqmul(zeta, r[j+l])
|
||||
r[j+l] = r[j] - t
|
||||
r[j] = r[j] + t
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
invntt :: proc "contextless" (r: ^[N]i16) #no_bounds_check {
|
||||
F : i16 : 1441 // mont^2/128
|
||||
|
||||
j, k := 0, 127
|
||||
for l := 2; l <= 128; l <<= 1 {
|
||||
for start := 0; start < 256; start = j+l {
|
||||
zeta := ZETAS[k]
|
||||
k -= 1
|
||||
for j = start; j < start + l; j += 1 {
|
||||
t := r[j]
|
||||
r[j] = barrett_reduce(t + r[j+l])
|
||||
r[j+l] = r[j+l] - t
|
||||
r[j+l] = fqmul(zeta, r[j+l])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for v, i in r {
|
||||
r[i] = fqmul(v, F)
|
||||
}
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
base_case_multiply :: proc "contextless" (a_0, a_1, b_0, b_1, zeta: i16) -> (i16, i16) {
|
||||
r_0 := fqmul(a_1, b_1)
|
||||
r_0 = fqmul(r_0, zeta)
|
||||
r_0 += fqmul(a_0, b_0)
|
||||
r_1 := fqmul(a_0, b_1)
|
||||
r_1 += fqmul(a_1, b_0)
|
||||
|
||||
return r_0, r_1
|
||||
}
|
||||
241
core/crypto/_mlkem/poly.odin
Normal file
241
core/crypto/_mlkem/poly.odin
Normal file
@@ -0,0 +1,241 @@
|
||||
#+private
|
||||
package _mlkem
|
||||
|
||||
import "core:crypto"
|
||||
import subtle "core:crypto/_subtle"
|
||||
|
||||
// Elements of R_q = Z_q[X]/(X^n + 1). Represents polynomial
|
||||
// coeffs[0] + X*coeffs[1] + X^2*coeffs[2] + ... + X^{n-1}*coeffs[n-1]
|
||||
Poly :: struct {
|
||||
coeffs: [N]i16,
|
||||
}
|
||||
|
||||
poly_compress :: proc "contextless" (r: []byte, a: ^Poly) #no_bounds_check {
|
||||
t: [8]byte = ---
|
||||
defer crypto.zero_explicit(&t, size_of(t))
|
||||
|
||||
r := r
|
||||
switch len(r) {
|
||||
case POLYCOMPRESSEDBYTES_768: // Also covers _512
|
||||
for i in 0..<N/8 {
|
||||
for j in 0..<8 {
|
||||
// map to positive standard representatives
|
||||
u := a.coeffs[8*i+j]
|
||||
u += (u >> 15) & Q
|
||||
// t[j] = ((((uint16_t)u << 4) + Q/2)/Q) & 15
|
||||
d0 := u32(u) << 4
|
||||
d0 += 1665
|
||||
d0 *= 80635
|
||||
d0 >>= 28
|
||||
t[j] = byte(d0) & 0xf
|
||||
}
|
||||
|
||||
r[0] = t[0] | (t[1] << 4)
|
||||
r[1] = t[2] | (t[3] << 4)
|
||||
r[2] = t[4] | (t[5] << 4)
|
||||
r[3] = t[6] | (t[7] << 4)
|
||||
r = r[4:]
|
||||
}
|
||||
case POLYCOMPRESSEDBYTES_1024:
|
||||
for i in 0..<N/8 {
|
||||
for j in 0..<8 {
|
||||
// map to positive standard representatives
|
||||
u := a.coeffs[8*i+j]
|
||||
u += (u >> 15) & Q
|
||||
// t[j] = ((((uint16_t)u << 5) + Q/2)/Q) & 31
|
||||
d0 := u32(u) << 5
|
||||
d0 += 1664
|
||||
d0 *= 40318
|
||||
d0 >>= 27
|
||||
t[j] = byte(d0) & 0x1f
|
||||
}
|
||||
|
||||
r[0] = (t[0] >> 0) | (t[1] << 5)
|
||||
r[1] = (t[1] >> 3) | (t[2] << 2) | (t[3] << 7)
|
||||
r[2] = (t[3] >> 1) | (t[4] << 4)
|
||||
r[3] = (t[4] >> 4) | (t[5] << 1) | (t[6] << 6)
|
||||
r[4] = (t[6] >> 2) | (t[7] << 3)
|
||||
r = r[5:]
|
||||
}
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
|
||||
poly_decompress :: proc "contextless" (r: ^Poly, a: []byte) {
|
||||
a := a
|
||||
switch len(a) {
|
||||
case POLYCOMPRESSEDBYTES_768: // Also covers _512
|
||||
for i in 0..<N/2 {
|
||||
r.coeffs[2*i+0] = i16(((u16(a[0] & 15) * Q) + 8) >> 4)
|
||||
r.coeffs[2*i+1] = i16(((u16(a[0] >> 4) * Q) + 8) >> 4)
|
||||
a = a[1:]
|
||||
}
|
||||
case POLYCOMPRESSEDBYTES_1024:
|
||||
t: [8]byte = ---
|
||||
defer crypto.zero_explicit(&t, size_of(t))
|
||||
|
||||
for i in 0..<N/8 {
|
||||
t[0] = (a[0] >> 0)
|
||||
t[1] = (a[0] >> 5) | (a[1] << 3)
|
||||
t[2] = (a[1] >> 2)
|
||||
t[3] = (a[1] >> 7) | (a[2] << 1)
|
||||
t[4] = (a[2] >> 4) | (a[3] << 4)
|
||||
t[5] = (a[3] >> 1)
|
||||
t[6] = (a[3] >> 6) | (a[4] << 2)
|
||||
t[7] = (a[4] >> 3)
|
||||
a = a[5:]
|
||||
|
||||
for j in 0..<8 {
|
||||
r.coeffs[8*i+j] = i16((u32(t[j] & 31) * Q + 16) >> 5)
|
||||
}
|
||||
}
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
|
||||
poly_tobytes :: proc "contextless" (r: []byte, a: ^Poly) #no_bounds_check {
|
||||
ensure_contextless(len(r) >= POLYBYTES)
|
||||
|
||||
for i in 0..<N/2 {
|
||||
// map to positive standard representatives
|
||||
t0 := u16(a.coeffs[2*i])
|
||||
t0 += u16((i16(t0) >> 15) & Q)
|
||||
t1 := u16(a.coeffs[2*i+1])
|
||||
t1 += u16((i16(t1) >> 15) & Q)
|
||||
r[3*i+0] = byte(t0 >> 0)
|
||||
r[3*i+1] = byte(t0 >> 8) | byte(t1 << 4)
|
||||
r[3*i+2] = byte(t1 >> 4)
|
||||
}
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
poly_frombytes :: proc "contextless" (r: ^Poly, a: []byte) -> bool #no_bounds_check {
|
||||
ensure_contextless(len(a) >= POLYBYTES)
|
||||
|
||||
ok := true
|
||||
for i in 0..<N/2 {
|
||||
r.coeffs[2*i] = i16(((u16(a[3*i+0]) >> 0) | (u16(a[3*i+1]) << 8)) & 0xFFF)
|
||||
r.coeffs[2*i+1] = i16(((u16(a[3*i+1]) >> 4) | (u16(a[3*i+2]) << 4)) & 0xFFF)
|
||||
ok &= r.coeffs[2*i] < Q && r.coeffs[2*i+1] < Q
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
poly_frommsg :: proc "contextless" (r: ^Poly, msg: []byte) #no_bounds_check {
|
||||
#assert(INDCPA_MSGBYTES == N/8)
|
||||
ensure_contextless(len(msg) == INDCPA_MSGBYTES)
|
||||
|
||||
for i in 0..<N/8 {
|
||||
for j in 0..<8 {
|
||||
r.coeffs[8*i+j] = subtle.csel_i16(0, (Q+1)/2, int(msg[i] >> uint(j))&1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
poly_tomsg :: proc "contextless" (msg: []byte, a: ^Poly) #no_bounds_check {
|
||||
ensure_contextless(len(msg) == INDCPA_MSGBYTES)
|
||||
|
||||
for i in 0..<N/8 {
|
||||
msg[i] = 0
|
||||
for j in uint(0)..<8 {
|
||||
t := u32(a.coeffs[8*i+int(j)])
|
||||
// t += ((int16_t)t >> 15) & Q
|
||||
// t = (((t << 1) + Q/2)/Q) & 1
|
||||
t <<= 1
|
||||
t += 1665
|
||||
t *= 80635
|
||||
t >>= 28
|
||||
t &= 1
|
||||
msg[i] |= byte(t << j)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
poly_getnoise_eta1_512 :: proc(r: ^Poly, seed: []byte, iv: byte) {
|
||||
buf: [ETA1_512*N/4]byte = ---
|
||||
defer crypto.zero_explicit(&buf, size_of(buf))
|
||||
|
||||
prf(buf[:], seed, iv)
|
||||
poly_cbd_eta1_512(r, &buf)
|
||||
}
|
||||
|
||||
poly_getnoise_eta1 :: proc(r: ^Poly, seed: []byte, iv: byte) {
|
||||
buf: [ETA1*N/4]byte = ---
|
||||
defer crypto.zero_explicit(&buf, size_of(buf))
|
||||
|
||||
prf(buf[:], seed, iv)
|
||||
poly_cbd_eta1(r, &buf)
|
||||
}
|
||||
|
||||
poly_getnoise_eta2 :: proc(r: ^Poly, seed: []byte, iv: byte) {
|
||||
buf: [ETA2*N/4]byte = ---
|
||||
defer crypto.zero_explicit(&buf, size_of(buf))
|
||||
|
||||
prf(buf[:], seed, iv)
|
||||
poly_cbd_eta2(r, &buf)
|
||||
}
|
||||
|
||||
poly_ntt :: proc "contextless" (r: ^Poly) {
|
||||
ntt(&r.coeffs)
|
||||
poly_reduce(r)
|
||||
}
|
||||
|
||||
poly_invntt_tomont :: proc "contextless" (r: ^Poly) {
|
||||
invntt(&r.coeffs)
|
||||
}
|
||||
|
||||
poly_basemul_montgomery :: proc "contextless" (r, a, b: ^Poly) #no_bounds_check {
|
||||
for i in 0..<N/4 {
|
||||
j := 4 * i
|
||||
r.coeffs[j], r.coeffs[j+1] = base_case_multiply(a.coeffs[j], a.coeffs[j+1], b.coeffs[j], b.coeffs[j+1], ZETAS[64+i])
|
||||
r.coeffs[j+2], r.coeffs[j+3] = base_case_multiply(a.coeffs[j+2], a.coeffs[j+3], b.coeffs[j+2], b.coeffs[j+3], -ZETAS[64+i])
|
||||
}
|
||||
}
|
||||
|
||||
poly_tomont :: proc "contextless" (r: ^Poly) {
|
||||
F : i16 : (1 << 32) % Q
|
||||
for v, i in r.coeffs {
|
||||
r.coeffs[i] = montgomery_reduce(i32(v)*i32(F))
|
||||
}
|
||||
}
|
||||
|
||||
poly_reduce :: proc "contextless" (r: ^Poly) {
|
||||
for v, i in r.coeffs {
|
||||
r.coeffs[i] = barrett_reduce(v)
|
||||
}
|
||||
}
|
||||
|
||||
poly_add :: proc "contextless" (r, a, b: ^Poly) {
|
||||
for i in 0..<N {
|
||||
r.coeffs[i] = a.coeffs[i] + b.coeffs[i]
|
||||
}
|
||||
}
|
||||
|
||||
poly_sub :: proc "contextless" (r, a, b: ^Poly) {
|
||||
for i in 0..<N {
|
||||
r.coeffs[i] = a.coeffs[i] - b.coeffs[i]
|
||||
}
|
||||
}
|
||||
|
||||
poly_clear :: proc "contextless" (a: ..^Poly) {
|
||||
for j in 0..<len(a) {
|
||||
p := a[j]
|
||||
crypto.zero_explicit(p, size_of(Poly))
|
||||
}
|
||||
}
|
||||
|
||||
poly_compressed_bytes :: #force_inline proc "contextless" (k: int) -> int {
|
||||
switch k {
|
||||
case K_512:
|
||||
return POLYCOMPRESSEDBYTES_512
|
||||
case K_768:
|
||||
return POLYCOMPRESSEDBYTES_768
|
||||
case K_1024:
|
||||
return POLYCOMPRESSEDBYTES_1024
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
224
core/crypto/_mlkem/polyvec.odin
Normal file
224
core/crypto/_mlkem/polyvec.odin
Normal file
@@ -0,0 +1,224 @@
|
||||
#+private
|
||||
package _mlkem
|
||||
|
||||
import "core:crypto"
|
||||
|
||||
Polyvec :: struct {
|
||||
vec: [K_MAX]Poly,
|
||||
}
|
||||
|
||||
polyvec_compress :: proc "contextless" (r: []byte, a: ^Polyvec, kay: int) #no_bounds_check {
|
||||
d0: u64
|
||||
|
||||
r := r
|
||||
switch len(r) {
|
||||
case POLYVECCOMPRESSEDBYTES_512, POLYVECCOMPRESSEDBYTES_768:
|
||||
ensure_contextless(kay == K_512 || kay == K_768)
|
||||
|
||||
t: [4]u16 = ---
|
||||
defer crypto.zero_explicit(&t, size_of(t))
|
||||
|
||||
for i in 0..<kay {
|
||||
for j in 0..<N/4 {
|
||||
for k in 0..<4 {
|
||||
t[k] = u16(a.vec[i].coeffs[4*j+k])
|
||||
t[k] += u16((i16(t[k]) >> 15) & Q)
|
||||
// t[k] = ((((uint32_t)t[k] << 10) + Q/2)/Q) & 0x3ff
|
||||
d0 = u64(t[k])
|
||||
d0 <<= 10
|
||||
d0 += 1665
|
||||
d0 *= 1290167
|
||||
d0 >>= 32
|
||||
t[k] = u16(d0 & 0x3ff)
|
||||
}
|
||||
|
||||
r[0] = byte(t[0] >> 0)
|
||||
r[1] = byte((t[0] >> 8) | (t[1] << 2))
|
||||
r[2] = byte((t[1] >> 6) | (t[2] << 4))
|
||||
r[3] = byte((t[2] >> 4) | (t[3] << 6))
|
||||
r[4] = byte(t[3] >> 2)
|
||||
r = r[5:]
|
||||
}
|
||||
}
|
||||
case POLYVECCOMPRESSEDBYTES_1024:
|
||||
ensure_contextless(kay == K_1024)
|
||||
|
||||
t: [8]u16 = ---
|
||||
defer crypto.zero_explicit(&t, size_of(t))
|
||||
|
||||
for i in 0..<K_1024 {
|
||||
for j in 0..<N/8 {
|
||||
for k in 0..<8 {
|
||||
t[k] = u16(a.vec[i].coeffs[8*j+k])
|
||||
t[k] += u16((i16(t[k]) >> 15) & Q)
|
||||
// t[k] = ((((uint32_t)t[k] << 11) + Q/2)/Q) & 0x7ff
|
||||
d0 = u64(t[k])
|
||||
d0 <<= 11
|
||||
d0 += 1664
|
||||
d0 *= 645084
|
||||
d0 >>= 31
|
||||
t[k] = u16(d0 & 0x7ff)
|
||||
}
|
||||
|
||||
r[0] = byte(t[0] >> 0)
|
||||
r[1] = byte((t[0] >> 8) | (t[1] << 3))
|
||||
r[2] = byte((t[1] >> 5) | (t[2] << 6))
|
||||
r[3] = byte(t[2] >> 2)
|
||||
r[4] = byte((t[2] >> 10) | (t[3] << 1))
|
||||
r[5] = byte((t[3] >> 7) | (t[4] << 4))
|
||||
r[6] = byte((t[4] >> 4) | (t[5] << 7))
|
||||
r[7] = byte(t[5] >> 1)
|
||||
r[8] = byte((t[5] >> 9) | (t[6] << 2))
|
||||
r[9] = byte((t[6] >> 6) | (t[7] << 5))
|
||||
r[10] = byte(t[7] >> 3)
|
||||
r = r[11:]
|
||||
}
|
||||
}
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_decompress :: proc "contextless" (r: ^Polyvec, a: []byte, kay: int) #no_bounds_check {
|
||||
a := a
|
||||
switch len(a) {
|
||||
case POLYVECCOMPRESSEDBYTES_512, POLYVECCOMPRESSEDBYTES_768:
|
||||
ensure_contextless(kay == K_512 || kay == K_768)
|
||||
|
||||
t: [4]u16 = ---
|
||||
defer crypto.zero_explicit(&t, size_of(t))
|
||||
|
||||
for i in 0..<kay {
|
||||
for j in 0..<N/4 {
|
||||
t[0] = u16(a[0] >> 0) | (u16(a[1]) << 8)
|
||||
t[1] = u16(a[1] >> 2) | (u16(a[2]) << 6)
|
||||
t[2] = u16(a[2] >> 4) | (u16(a[3]) << 4)
|
||||
t[3] = u16(a[3] >> 6) | (u16(a[4]) << 2)
|
||||
a = a[5:]
|
||||
|
||||
for k in 0..<4 {
|
||||
r.vec[i].coeffs[4*j+k] = i16((u32(t[k] & 0x3FF) * Q + 512) >> 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
case POLYVECCOMPRESSEDBYTES_1024:
|
||||
t: [8]u16 = ---
|
||||
defer crypto.zero_explicit(&t, size_of(t))
|
||||
|
||||
for i in 0..<K_1024 {
|
||||
for j in 0..<N/8 {
|
||||
t[0] = u16(a[0] >> 0) | (u16(a[1]) << 8)
|
||||
t[1] = u16(a[1] >> 3) | (u16(a[2]) << 5)
|
||||
t[2] = u16(a[2] >> 6) | (u16(a[3]) << 2) | (u16(a[4]) << 10)
|
||||
t[3] = u16(a[4] >> 1) | (u16(a[5]) << 7)
|
||||
t[4] = u16(a[5] >> 4) | (u16(a[6]) << 4)
|
||||
t[5] = u16(a[6] >> 7) | (u16(a[7]) << 1) | (u16(a[8]) << 9)
|
||||
t[6] = u16(a[8] >> 2) | (u16(a[9]) << 6)
|
||||
t[7] = u16(a[9] >> 5) | (u16(a[10]) << 3)
|
||||
a = a[11:]
|
||||
|
||||
for k in 0..<8 {
|
||||
r.vec[i].coeffs[8*j+k] = i16((u32(t[k] & 0x7FF) * Q + 1024) >> 11)
|
||||
}
|
||||
}
|
||||
}
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_tobytes :: proc "contextless" (r: []byte, a: ^Polyvec, k: int) #no_bounds_check {
|
||||
ensure_contextless(len(r) == k * POLYBYTES, "crypto/mlkem: invalid buffer")
|
||||
|
||||
r := r
|
||||
for i in 0..<k {
|
||||
poly_tobytes(r, &a.vec[i])
|
||||
r = r[POLYBYTES:]
|
||||
}
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
polyvec_frombytes :: proc "contextless" (r: ^Polyvec, a: []byte, k: int) -> bool #no_bounds_check {
|
||||
switch k {
|
||||
case K_512, K_768, K_1024:
|
||||
case:
|
||||
panic_contextless("crypto/mlkem: invalid POLYVECBYTES")
|
||||
}
|
||||
ensure_contextless(len(a) == k * POLYBYTES, "crypto/mlkem: invalid buffer")
|
||||
|
||||
a := a
|
||||
ok := true
|
||||
for i in 0..<k {
|
||||
ok &= poly_frombytes(&r.vec[i], a)
|
||||
a = a[POLYBYTES:]
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
polyvec_byte_size :: #force_inline proc "contextless" (k: int) -> int {
|
||||
switch k {
|
||||
case K_512, K_768, K_1024:
|
||||
return k * POLYBYTES
|
||||
case:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
polyvec_compressed_byte_size :: #force_inline proc "contextless" (k: int) -> int {
|
||||
switch k {
|
||||
case K_512:
|
||||
return POLYVECCOMPRESSEDBYTES_512
|
||||
case K_768:
|
||||
return POLYVECCOMPRESSEDBYTES_768
|
||||
case K_1024:
|
||||
return POLYVECCOMPRESSEDBYTES_1024
|
||||
case:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_ntt :: proc "contextless" (r: ^Polyvec, k: int) {
|
||||
for i in 0..<k {
|
||||
poly_ntt(&r.vec[i])
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_invntt_tomont :: proc "contextless" (r: ^Polyvec, k: int) {
|
||||
for i in 0..<k {
|
||||
poly_invntt_tomont(&r.vec[i])
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_basemul_acc_montgomery :: proc "contextless" (r: ^Poly, a, b: ^Polyvec, k: int) {
|
||||
t: Poly = ---
|
||||
defer crypto.zero_explicit(&t, size_of(t))
|
||||
|
||||
poly_basemul_montgomery(r, &a.vec[0], &b.vec[0])
|
||||
for i in 1..<k {
|
||||
poly_basemul_montgomery(&t, &a.vec[i], &b.vec[i])
|
||||
poly_add(r, r, &t)
|
||||
}
|
||||
|
||||
poly_reduce(r)
|
||||
}
|
||||
|
||||
polyvec_reduce :: proc "contextless" (r: ^Polyvec, k: int) {
|
||||
for i in 0..<k {
|
||||
poly_reduce(&r.vec[i])
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_add :: proc "contextless" (r, a, b: ^Polyvec, k: int) {
|
||||
for i in 0..<k {
|
||||
poly_add(&r.vec[i], &a.vec[i], &b.vec[i])
|
||||
}
|
||||
}
|
||||
|
||||
polyvec_clear :: proc "contextless" (rs: ..^Polyvec) {
|
||||
for j in 0..<len(rs) {
|
||||
r := rs[j]
|
||||
crypto.zero_explicit(r, size_of(Polyvec))
|
||||
}
|
||||
}
|
||||
19
core/crypto/_mlkem/reduce.odin
Normal file
19
core/crypto/_mlkem/reduce.odin
Normal file
@@ -0,0 +1,19 @@
|
||||
#+private
|
||||
package _mlkem
|
||||
|
||||
@(require_results)
|
||||
montgomery_reduce :: #force_inline proc "contextless" (a: i32) -> i16 {
|
||||
QINV :: -3327 // q^-1 mod 2^16
|
||||
|
||||
t := i16(a) * QINV
|
||||
return i16((a - i32(t) * Q) >> 16)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
barrett_reduce :: #force_inline proc "contextless" (a: i16) -> i16 {
|
||||
V : i16 : ((1<<26) + Q / 2) / Q
|
||||
|
||||
t := i16((i32(V)*i32(a) + (1<<25)) >> 26)
|
||||
t *= Q
|
||||
return a - t
|
||||
}
|
||||
61
core/crypto/_mlkem/symmetric_shake.odin
Normal file
61
core/crypto/_mlkem/symmetric_shake.odin
Normal file
@@ -0,0 +1,61 @@
|
||||
#+private
|
||||
package _mlkem
|
||||
|
||||
import "core:crypto"
|
||||
import "core:crypto/_sha3"
|
||||
import "core:crypto/sha3"
|
||||
import "core:crypto/shake"
|
||||
|
||||
XOF_BLOCKBYTES :: _sha3.RATE_128
|
||||
#assert(XOF_BLOCKBYTES % 3 == 0)
|
||||
|
||||
prf :: proc(out, key: []byte, iv: byte) {
|
||||
ctx: shake.Context = ---
|
||||
defer shake.reset(&ctx)
|
||||
|
||||
shake.init_256(&ctx)
|
||||
shake.write(&ctx, key)
|
||||
shake.write(&ctx, []byte{iv})
|
||||
shake.read(&ctx, out)
|
||||
}
|
||||
|
||||
rkprf :: proc(out, key, input: []byte) {
|
||||
ctx: shake.Context = ---
|
||||
defer shake.reset(&ctx)
|
||||
|
||||
shake.init_256(&ctx)
|
||||
shake.write(&ctx, key)
|
||||
shake.write(&ctx, input)
|
||||
shake.read(&ctx, out)
|
||||
}
|
||||
|
||||
xof_absorb :: proc(ctx: ^shake.Context, seed: []byte, x, y: byte) {
|
||||
shake.init_128(ctx)
|
||||
|
||||
extseed: [SYMBYTES+2]byte = ---
|
||||
defer crypto.zero_explicit(&extseed, size_of(extseed))
|
||||
|
||||
copy(extseed[:], seed)
|
||||
extseed[SYMBYTES+0] = x
|
||||
extseed[SYMBYTES+1] = y
|
||||
|
||||
shake.write(ctx, extseed[:])
|
||||
}
|
||||
|
||||
hash_h :: proc(dst, src: []byte) {
|
||||
ctx: sha3.Context = ---
|
||||
|
||||
sha3.init_256(&ctx)
|
||||
sha3.update(&ctx, src)
|
||||
sha3.final(&ctx, dst)
|
||||
}
|
||||
|
||||
hash_g :: proc(dst: []byte, srcs: ..[]byte) {
|
||||
ctx: sha3.Context = ---
|
||||
|
||||
sha3.init_512(&ctx)
|
||||
for src in srcs {
|
||||
sha3.update(&ctx, src)
|
||||
}
|
||||
sha3.final(&ctx, dst)
|
||||
}
|
||||
@@ -3,6 +3,7 @@ Various useful bit operations in constant time.
|
||||
*/
|
||||
package _subtle
|
||||
|
||||
import "core:crypto/_fiat"
|
||||
import "core:math/bits"
|
||||
|
||||
// byte_eq returns 1 if and only if (⟺) a == b, 0 otherwise.
|
||||
@@ -40,3 +41,41 @@ u64_is_non_zero :: proc "contextless" (a: u64) -> u64 {
|
||||
is_zero := u64_is_zero(a)
|
||||
return (~is_zero) & 1
|
||||
}
|
||||
|
||||
@(optimization_mode="none")
|
||||
cmov_bytes :: proc "contextless" (dst, src: []byte, ctrl: int) {
|
||||
s_len := len(src)
|
||||
ensure_contextless(s_len == len(dst), "crypto: cmov length mismatch")
|
||||
|
||||
c := -(byte)(ctrl)
|
||||
for i in 0..<s_len {
|
||||
dst[i] ~= c & (dst[i] ~ src[i])
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="none")
|
||||
csel_i16 :: proc "contextless" (a, b: i16, ctrl: int) -> i16 {
|
||||
c := -(u16)(ctrl)
|
||||
return a ~ i16(c & u16(a ~ b))
|
||||
}
|
||||
|
||||
@(optimization_mode="none")
|
||||
csel_u16 :: proc "contextless" (a, b: u16, ctrl: int) -> u16 {
|
||||
c := -(u16)(ctrl)
|
||||
return a ~ (c & (a ~ b))
|
||||
}
|
||||
|
||||
csel_u32 :: proc "contextless" (a, b: u32, ctrl: int) -> u32 {
|
||||
return _fiat.cmovznz_u32(_fiat.u1(ctrl), a, b)
|
||||
}
|
||||
|
||||
csel_u64 :: proc "contextless" (a, b: u64, ctrl: int) -> u64 {
|
||||
return _fiat.cmovznz_u64(_fiat.u1(ctrl), a, b)
|
||||
}
|
||||
|
||||
csel :: proc {
|
||||
csel_i16,
|
||||
csel_u16,
|
||||
csel_u32,
|
||||
csel_u64,
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ derive :: proc(
|
||||
m_ := 4 * u64(p) * (m / u64(4 * p))
|
||||
b := mem.alloc_bytes_non_zeroed(
|
||||
int(m_) * BLOCK_SIZE_BYTES,
|
||||
alignment = mem.DEFAULT_PAGE_SIZE,
|
||||
alignment = mem.PAGE_SIZE,
|
||||
allocator = allocator,
|
||||
) or_return
|
||||
defer delete(b, allocator)
|
||||
|
||||
@@ -85,18 +85,6 @@ zero_explicit :: proc "contextless" (data: rawptr, len: int) -> rawptr {
|
||||
return data
|
||||
}
|
||||
|
||||
/*
|
||||
Set each byte of a memory range to a specific value.
|
||||
|
||||
This procedure copies value specified by the `value` parameter into each of the
|
||||
`len` bytes of a memory range, located at address `data`.
|
||||
|
||||
This procedure returns the pointer to `data`.
|
||||
*/
|
||||
set :: proc "contextless" (data: rawptr, value: byte, len: int) -> rawptr {
|
||||
return runtime.memset(data, i32(value), len)
|
||||
}
|
||||
|
||||
// rand_bytes fills the dst buffer with cryptographic entropy taken from
|
||||
// the system entropy source. This routine will block if the system entropy
|
||||
// source is not ready yet. All system entropy source failures are treated
|
||||
|
||||
@@ -106,6 +106,7 @@ Public_Key :: struct {
|
||||
// private_key_generate uses the system entropy source to generate a new
|
||||
// Private_Key. This will only fail if and only if (⟺) the system entropy source is
|
||||
// missing or broken.
|
||||
@(require_results)
|
||||
private_key_generate :: proc(priv_key: ^Private_Key, curve: Curve) -> bool {
|
||||
private_key_clear(priv_key)
|
||||
|
||||
@@ -143,6 +144,7 @@ private_key_generate :: proc(priv_key: ^Private_Key, curve: Curve) -> bool {
|
||||
|
||||
// private_key_set_bytes decodes a byte-encoded private key, and returns
|
||||
// true if and only if (⟺) the operation was successful.
|
||||
@(require_results)
|
||||
private_key_set_bytes :: proc(priv_key: ^Private_Key, curve: Curve, b: []byte) -> bool {
|
||||
private_key_clear(priv_key)
|
||||
|
||||
@@ -279,8 +281,15 @@ private_key_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
// private_key_public_bytes sets dst to the byte-encoding of the public
|
||||
// key corresponding to priv_key.
|
||||
private_key_public_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
|
||||
public_key_bytes(&priv_key._pub_key, dst)
|
||||
}
|
||||
|
||||
// private_key_equal returns true if and only if (⟺) the private keys are equal,
|
||||
// in constant time.
|
||||
@(require_results)
|
||||
private_key_equal :: proc(p, q: ^Private_Key) -> bool {
|
||||
if p._curve != q._curve {
|
||||
return false
|
||||
@@ -311,6 +320,7 @@ private_key_clear :: proc "contextless" (priv_key: ^Private_Key) {
|
||||
|
||||
// public_key_set_bytes decodes a byte-encoded public key, and returns
|
||||
// true if and only if (⟺) the operation was successful.
|
||||
@(require_results)
|
||||
public_key_set_bytes :: proc(pub_key: ^Public_Key, curve: Curve, b: []byte) -> bool {
|
||||
public_key_clear(pub_key)
|
||||
|
||||
@@ -411,6 +421,7 @@ public_key_bytes :: proc(pub_key: ^Public_Key, dst: []byte) {
|
||||
|
||||
// public_key_equal returns true if and only if (⟺) the public keys are equal,
|
||||
// in constant time.
|
||||
@(require_results)
|
||||
public_key_equal :: proc(p, q: ^Public_Key) -> bool {
|
||||
if p._curve != q._curve {
|
||||
return false
|
||||
@@ -479,12 +490,14 @@ ecdh :: proc(priv_key: ^Private_Key, pub_key: ^Public_Key, dst: []byte) -> bool
|
||||
}
|
||||
|
||||
// curve returns the Curve used by a Private_Key or Public_Key instance.
|
||||
curve :: proc(k: ^$T) -> Curve where(T == Private_Key || T == Public_Key) {
|
||||
@(require_results)
|
||||
curve :: proc(k: ^$T) -> Curve where (T == Private_Key || T == Public_Key) {
|
||||
return k._curve
|
||||
}
|
||||
|
||||
// key_size returns the key size of a Private_Key or Public_Key in bytes.
|
||||
key_size :: proc(k: ^$T) -> int where(T == Private_Key || T == Public_Key) {
|
||||
@(require_results)
|
||||
key_size :: proc(k: ^$T) -> int where (T == Private_Key || T == Public_Key) {
|
||||
when T == Private_Key {
|
||||
return PRIVATE_KEY_SIZES[k._curve]
|
||||
} else {
|
||||
@@ -494,6 +507,7 @@ key_size :: proc(k: ^$T) -> int where(T == Private_Key || T == Public_Key) {
|
||||
|
||||
// shared_secret_size returns the shared secret size of a key exchange
|
||||
// in bytes.
|
||||
shared_secret_size :: proc(k: ^$T) -> int where(T == Private_Key || T == Public_Key) {
|
||||
@(require_results)
|
||||
shared_secret_size :: proc(k: ^$T) -> int where (T == Private_Key || T == Public_Key) {
|
||||
return SHARED_SECRET_SIZES[k._curve]
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package ecdsa
|
||||
|
||||
import "core:crypto"
|
||||
import secec "core:crypto/_weierstrass"
|
||||
import "core:mem"
|
||||
import "core:reflect"
|
||||
|
||||
// Curve the curve identifier associated with a given Private_Key
|
||||
@@ -81,6 +80,7 @@ Public_Key :: struct {
|
||||
// private_key_generate uses the system entropy source to generate a new
|
||||
// Private_Key. This will only fail if and only if (⟺) the system entropy source is
|
||||
// missing or broken.
|
||||
@(require_results)
|
||||
private_key_generate :: proc(priv_key: ^Private_Key, curve: Curve) -> bool {
|
||||
private_key_clear(priv_key)
|
||||
|
||||
@@ -112,6 +112,7 @@ private_key_generate :: proc(priv_key: ^Private_Key, curve: Curve) -> bool {
|
||||
|
||||
// private_key_set_bytes decodes a byte-encoded private key, and returns
|
||||
// true if and only if (⟺) the operation was successful.
|
||||
@(require_results)
|
||||
private_key_set_bytes :: proc(priv_key: ^Private_Key, curve: Curve, b: []byte) -> bool {
|
||||
private_key_clear(priv_key)
|
||||
|
||||
@@ -194,6 +195,12 @@ private_key_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
// private_key_public_bytes sets dst to the byte-encoding of the public
|
||||
// key corresponding to priv_key.
|
||||
private_key_public_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
|
||||
public_key_bytes(&priv_key._pub_key, dst)
|
||||
}
|
||||
|
||||
// private_key_set sets priv_key to src.
|
||||
private_key_set :: proc(priv_key, src: ^Private_Key) {
|
||||
if src == nil || src._curve == .Invalid {
|
||||
@@ -222,6 +229,7 @@ private_key_set :: proc(priv_key, src: ^Private_Key) {
|
||||
|
||||
// private_key_equal returns true if and only if (⟺) the private keys are equal,
|
||||
// in constant time.
|
||||
@(require_results)
|
||||
private_key_equal :: proc(p, q: ^Private_Key) -> bool {
|
||||
if p._curve != q._curve {
|
||||
return false
|
||||
@@ -241,11 +249,12 @@ private_key_equal :: proc(p, q: ^Private_Key) -> bool {
|
||||
|
||||
// private_key_clear clears priv_key to the uninitialized state.
|
||||
private_key_clear :: proc "contextless" (priv_key: ^Private_Key) {
|
||||
mem.zero_explicit(priv_key, size_of(Private_Key))
|
||||
crypto.zero_explicit(priv_key, size_of(Private_Key))
|
||||
}
|
||||
|
||||
// public_key_set_bytes decodes a byte-encoded public key, and returns
|
||||
// true if and only if (⟺) the operation was successful.
|
||||
@(require_results)
|
||||
public_key_set_bytes :: proc(pub_key: ^Public_Key, curve: Curve, b: []byte) -> bool {
|
||||
public_key_clear(pub_key)
|
||||
|
||||
@@ -334,6 +343,7 @@ public_key_bytes :: proc(pub_key: ^Public_Key, dst: []byte) {
|
||||
|
||||
// public_key_equal returns true if and only if (⟺) the public keys are equal,
|
||||
// in constant time.
|
||||
@(require_results)
|
||||
public_key_equal :: proc(p, q: ^Public_Key) -> bool {
|
||||
if p._curve != q._curve {
|
||||
return false
|
||||
@@ -353,5 +363,21 @@ public_key_equal :: proc(p, q: ^Public_Key) -> bool {
|
||||
|
||||
// public_key_clear clears pub_key to the uninitialized state.
|
||||
public_key_clear :: proc "contextless" (pub_key: ^Public_Key) {
|
||||
mem.zero_explicit(pub_key, size_of(Public_Key))
|
||||
crypto.zero_explicit(pub_key, size_of(Public_Key))
|
||||
}
|
||||
|
||||
// curve returns the Curve used by a Private_Key or Public_Key instance.
|
||||
@(require_results)
|
||||
curve :: proc(k: ^$T) -> Curve where (T == Private_Key || T == Public_Key) {
|
||||
return k._curve
|
||||
}
|
||||
|
||||
// key_size returns the key size of a Private_Key or Public_Key in bytes.
|
||||
@(require_results)
|
||||
key_size :: proc(k: ^$T) -> int where (T == Private_Key || T == Public_Key) {
|
||||
when T == Private_Key {
|
||||
return PRIVATE_KEY_SIZES[k._curve]
|
||||
} else {
|
||||
return PUBLIC_KEY_SIZES[k._curve]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ import secec "core:crypto/_weierstrass"
|
||||
// The signature format is ASN1. `SEQUECE `{ r INTEGER, s INTEGER }`.
|
||||
@(require_results)
|
||||
sign_asn1 :: proc(priv_key: ^Private_Key, hash_algo: hash.Algorithm, msg: []byte, allocator: runtime.Allocator, deterministic := !crypto.HAS_RAND_BYTES) -> ([]byte, bool) {
|
||||
ensure(hash_algo != .Invalid, "crypto/edsa: invalid hash algorithm")
|
||||
ensure(priv_key._curve != .Invalid, "crypto/edsa: invalid curve")
|
||||
ensure(hash_algo != .Invalid, "crypto/ecdsa: invalid hash algorithm")
|
||||
ensure(priv_key._curve != .Invalid, "crypto/ecdsa: invalid curve")
|
||||
|
||||
if !deterministic && !crypto.HAS_RAND_BYTES {
|
||||
return nil, false
|
||||
@@ -49,8 +49,8 @@ sign_asn1 :: proc(priv_key: ^Private_Key, hash_algo: hash.Algorithm, msg: []byte
|
||||
// The signature format is `r | s`.
|
||||
@(require_results)
|
||||
sign_raw :: proc(priv_key: ^Private_Key, hash_algo: hash.Algorithm, msg, sig: []byte, deterministic := !crypto.HAS_RAND_BYTES) -> bool {
|
||||
ensure(hash_algo != .Invalid, "crypto/edsa: invalid hash algorithm")
|
||||
ensure(priv_key._curve != .Invalid, "crypto/edsa: invalid curve")
|
||||
ensure(hash_algo != .Invalid, "crypto/ecdsa: invalid hash algorithm")
|
||||
ensure(priv_key._curve != .Invalid, "crypto/ecdsa: invalid curve")
|
||||
ensure(len(sig) == RAW_SIGNATURE_SIZES[priv_key._curve], "crypto/ecdsa: invalid destination size")
|
||||
|
||||
if !deterministic && !crypto.HAS_RAND_BYTES {
|
||||
|
||||
@@ -10,8 +10,8 @@ import secec "core:crypto/_weierstrass"
|
||||
// The signature format is `r | s`.
|
||||
@(require_results)
|
||||
verify_raw :: proc(pub_key: ^Public_Key, hash_algo: hash.Algorithm, msg, sig: []byte) -> bool {
|
||||
ensure(hash_algo != .Invalid, "crypto/edsa: invalid hash algorithm")
|
||||
ensure(pub_key._curve != .Invalid, "crypto/edsa: invalid curve")
|
||||
ensure(hash_algo != .Invalid, "crypto/ecdsa: invalid hash algorithm")
|
||||
ensure(pub_key._curve != .Invalid, "crypto/ecdsa: invalid curve")
|
||||
|
||||
if len(sig) != RAW_SIGNATURE_SIZES[pub_key._curve] {
|
||||
return false
|
||||
@@ -40,8 +40,8 @@ verify_raw :: proc(pub_key: ^Public_Key, hash_algo: hash.Algorithm, msg, sig: []
|
||||
// The signature format is ASN.1 `SEQUENCE { r INTEGER, s INTEGER }`.
|
||||
@(require_results)
|
||||
verify_asn1 :: proc(pub_key: ^Public_Key, hash_algo: hash.Algorithm, msg, sig: []byte) -> bool {
|
||||
ensure(hash_algo != .Invalid, "crypto/edsa: invalid hash algorithm")
|
||||
ensure(pub_key._curve != .Invalid, "crypto/edsa: invalid curve")
|
||||
ensure(hash_algo != .Invalid, "crypto/ecdsa: invalid hash algorithm")
|
||||
ensure(pub_key._curve != .Invalid, "crypto/ecdsa: invalid curve")
|
||||
|
||||
r_bytes, s_bytes, ok := parse_asn1_sig(sig)
|
||||
if !ok {
|
||||
|
||||
@@ -120,6 +120,12 @@ private_key_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
|
||||
copy(dst, priv_key._b[:])
|
||||
}
|
||||
|
||||
// private_key_public_bytes sets dst to the byte-encoding of the public
|
||||
// key corresponding to priv_key.
|
||||
private_key_public_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
|
||||
public_key_bytes(&priv_key._pub_key, dst)
|
||||
}
|
||||
|
||||
// private_key_clear clears priv_key to the uninitialized state.
|
||||
private_key_clear :: proc "contextless" (priv_key: ^Private_Key) {
|
||||
crypto.zero_explicit(priv_key, size_of(Private_Key))
|
||||
|
||||
@@ -18,6 +18,7 @@ package md5
|
||||
zhibog, dotbmp: Initial implementation.
|
||||
*/
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:crypto"
|
||||
import "core:encoding/endian"
|
||||
import "core:math/bits"
|
||||
@@ -100,7 +101,7 @@ final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
|
||||
i += 1
|
||||
}
|
||||
transform(ctx, ctx.data[:])
|
||||
crypto.set(&ctx.data, 0, 56)
|
||||
intrinsics.mem_zero(&ctx.data, 56)
|
||||
}
|
||||
|
||||
ctx.bitlen += u64(ctx.datalen * 8)
|
||||
|
||||
@@ -19,6 +19,7 @@ package sha1
|
||||
zhibog, dotbmp: Initial implementation.
|
||||
*/
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:crypto"
|
||||
import "core:encoding/endian"
|
||||
import "core:math/bits"
|
||||
@@ -107,7 +108,7 @@ final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
|
||||
i += 1
|
||||
}
|
||||
transform(ctx, ctx.data[:])
|
||||
crypto.set(&ctx.data, 0, 56)
|
||||
intrinsics.mem_zero(&ctx.data, 56)
|
||||
}
|
||||
|
||||
ctx.bitlen += u64(ctx.datalen * 8)
|
||||
|
||||
290
core/crypto/mldsa/api.odin
Normal file
290
core/crypto/mldsa/api.odin
Normal file
@@ -0,0 +1,290 @@
|
||||
package mldsa
|
||||
|
||||
import "core:crypto"
|
||||
import "core:crypto/_mldsa"
|
||||
|
||||
// Parameters are the supported ML-DSA parameter sets.
|
||||
Parameters :: enum {
|
||||
Invalid,
|
||||
ML_DSA_44,
|
||||
ML_DSA_65,
|
||||
ML_DSA_87,
|
||||
}
|
||||
|
||||
// PRIVATE_KEY_SEED_SIZE is the size of a private key in bytes.
|
||||
PRIVATE_KEY_SEED_SIZE :: _mldsa.SEEDBYTES // 32-bytes
|
||||
|
||||
// MAX_CTX_SIZE is the maximum size of the signature context
|
||||
// (domain separation tag) in bytes.
|
||||
MAX_CTX_SIZE :: _mldsa.CTXBYTES_MAX // 255-bytes
|
||||
|
||||
// PUBLIC_KEY_SIZES are the per-parameter sizes of a public
|
||||
// key in bytes.
|
||||
PUBLIC_KEY_SIZES := [Parameters]int {
|
||||
.Invalid = 0,
|
||||
.ML_DSA_44 = 1312,
|
||||
.ML_DSA_65 = 1952,
|
||||
.ML_DSA_87 = 2592,
|
||||
}
|
||||
|
||||
// SIGNATURE_SIZES are the per-parameter sizes of a signature
|
||||
// in byte.
|
||||
SIGNATURE_SIZES := [Parameters]int {
|
||||
.Invalid = 0,
|
||||
.ML_DSA_44 = 2420,
|
||||
.ML_DSA_65 = 3309,
|
||||
.ML_DSA_87 = 4627,
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
_PARAMS_TO_INTERNAL := [Parameters]^_mldsa.Params {
|
||||
.Invalid = nil,
|
||||
.ML_DSA_44 = &_mldsa.Params_44,
|
||||
.ML_DSA_65 = &_mldsa.Params_65,
|
||||
.ML_DSA_87 = &_mldsa.Params_87,
|
||||
}
|
||||
|
||||
// Private_Key is a ML-DSA private key.
|
||||
Private_Key :: _mldsa.Private_Key
|
||||
|
||||
// Public_Key is a ML-DSA public key.
|
||||
Public_Key :: _mldsa.Public_Key
|
||||
|
||||
// private_key_generate uses the system entropy source to generate a new
|
||||
// Private_Key. This will only fail if and only if (⟺) the system entropy
|
||||
// source is missing or broken.
|
||||
@(require_results)
|
||||
private_key_generate :: proc(priv_key: ^Private_Key, params: Parameters) -> bool {
|
||||
private_key_clear(priv_key)
|
||||
|
||||
if !crypto.HAS_RAND_BYTES {
|
||||
return false
|
||||
}
|
||||
|
||||
params_ := _PARAMS_TO_INTERNAL[params]
|
||||
if params_ == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
seed: [PRIVATE_KEY_SEED_SIZE]byte = ---
|
||||
defer crypto.zero_explicit(&seed, size_of(seed))
|
||||
|
||||
crypto.rand_bytes(seed[:])
|
||||
|
||||
_mldsa.dsa_keygen_internal(priv_key, seed[:], params_)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// private_key_set_bytes decodes a byte-encoded private key in "seed" format,
|
||||
// and returns true if and only if (⟺) the operation was successful.
|
||||
@(require_results)
|
||||
private_key_set_bytes :: proc(priv_key: ^Private_Key, params: Parameters, b: []byte) -> bool {
|
||||
private_key_clear(priv_key)
|
||||
|
||||
params_ := _PARAMS_TO_INTERNAL[params]
|
||||
if params_ == nil {
|
||||
return false
|
||||
}
|
||||
if len(b) != PRIVATE_KEY_SEED_SIZE {
|
||||
return false
|
||||
}
|
||||
|
||||
_mldsa.dsa_keygen_internal(priv_key, b, params_)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// private_key_bytes sets dst to byte-encoding of priv_key in the "seed"
|
||||
// format.
|
||||
private_key_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
|
||||
ensure(priv_key.params != nil, "crypto/mldsa: uninitialized private key")
|
||||
ensure(len(dst) == PRIVATE_KEY_SEED_SIZE, "crypto/mldsa: invalid destination size")
|
||||
|
||||
copy(dst, priv_key.seed[:])
|
||||
}
|
||||
|
||||
// private_key_public_bytes sets dst to the byte-encoding of the public
|
||||
// key corresponding to priv_key.
|
||||
private_key_public_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
|
||||
public_key_bytes(&priv_key.pub_key, dst)
|
||||
}
|
||||
|
||||
// private_key_set sets priv_key to src.
|
||||
private_key_set :: proc(priv_key, src: ^Private_Key) {
|
||||
if src == nil || internal_to_params(src.params) == .Invalid {
|
||||
private_key_clear(priv_key)
|
||||
return
|
||||
}
|
||||
|
||||
_mldsa.set_sk(priv_key, src)
|
||||
}
|
||||
|
||||
// private_key_equal returns true if and only if (⟺) the private keys are
|
||||
// equal, in constant time.
|
||||
@(require_results)
|
||||
private_key_equal :: proc(p, q: ^Private_Key) -> bool {
|
||||
if p.params != q.params {
|
||||
return false
|
||||
}
|
||||
if p.params == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// Just compare the seed that was passed to dsa_keygen_internal,
|
||||
// since the process is completely deterministic.
|
||||
return crypto.compare_constant_time(p.seed[:], q.seed[:]) == 1
|
||||
}
|
||||
|
||||
// private_key_clear clears priv_key to the uninitialized state.
|
||||
private_key_clear :: proc "contextless" (priv_key: ^Private_Key) {
|
||||
_mldsa.clear_sk(priv_key)
|
||||
}
|
||||
|
||||
// public_key_set_bytes decodes a byte-encoded public key, and returns
|
||||
// true if and only if (⟺) the operation was successful.
|
||||
@(require_results)
|
||||
public_key_set_bytes :: proc(pub_key: ^Public_Key, params: Parameters, b: []byte) -> bool {
|
||||
params_ := _PARAMS_TO_INTERNAL[params]
|
||||
if params_ == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return _mldsa.unpack_pk(pub_key, b, params_)
|
||||
}
|
||||
|
||||
// public_key_set sets pub_key to src.
|
||||
public_key_set :: proc(pub_key, src: ^Public_Key) {
|
||||
if src == nil || internal_to_params(src.params) == .Invalid {
|
||||
public_key_clear(pub_key)
|
||||
return
|
||||
}
|
||||
|
||||
_mldsa.set_pk(pub_key, src)
|
||||
}
|
||||
|
||||
// public_key_set_priv sets pub_key to the public component of priv_key.
|
||||
public_key_set_priv :: proc(pub_key: ^Public_Key, priv_key: ^Private_Key) {
|
||||
ensure(priv_key.params != nil, "crypto/mldsa: uninitialized private key")
|
||||
public_key_set(pub_key, &priv_key.pub_key)
|
||||
}
|
||||
|
||||
// public_key_bytes sets dst to byte-encoding of pub_key.
|
||||
public_key_bytes :: proc(pub_key: ^Public_Key, dst: []byte) {
|
||||
ensure(pub_key.params != nil, "crypto/mldsa: uninitialized public key")
|
||||
params := internal_to_params(pub_key.params)
|
||||
ensure(len(dst) == PUBLIC_KEY_SIZES[params], "crypto/mldsa: invalid destination size")
|
||||
|
||||
_ = _mldsa.pack_pk(dst, pub_key)
|
||||
}
|
||||
|
||||
// public_key_equal returns true if and only if (⟺) the public keys are equal,
|
||||
// in constant time.
|
||||
@(require_results)
|
||||
public_key_equal :: proc(p, q: ^Public_Key) -> bool {
|
||||
if p.params != q.params {
|
||||
return false
|
||||
}
|
||||
if p.params == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// Comparing the pre-computed hash should be enough, but pack
|
||||
// both public keys and do the comparisons.
|
||||
PUBLIC_KEY_SIZE_MAX :: 2592
|
||||
|
||||
l := PUBLIC_KEY_SIZES[internal_to_params(p.params)]
|
||||
p_buf_, q_buf_: [PUBLIC_KEY_SIZE_MAX]byte = ---, ---
|
||||
p_buf, q_buf := p_buf_[:l], q_buf_[:l]
|
||||
|
||||
_ = _mldsa.pack_pk(p_buf, p)
|
||||
_ = _mldsa.pack_pk(q_buf, q)
|
||||
|
||||
return crypto.compare_constant_time(p_buf, q_buf) == 1
|
||||
}
|
||||
|
||||
// public_key_clear clears pub_key to the uninitialized state.
|
||||
public_key_clear :: proc "contextless" (pub_key: ^Public_Key) {
|
||||
_mldsa.clear_pk(pub_key)
|
||||
}
|
||||
|
||||
// sign writes the signature by priv_key over (ctx, msg) to sig and
|
||||
// returns true if and only if (⟺) the signing succeeded.
|
||||
//
|
||||
// ctx is an optional domain separation tag and may be omitted (nil).
|
||||
@(require_results)
|
||||
sign :: proc(priv_key: ^Private_Key, ctx, msg, sig: []byte, deterministic := !crypto.HAS_RAND_BYTES) -> bool {
|
||||
params := internal_to_params(priv_key.params)
|
||||
ensure(params != .Invalid, "crypto/mldsa: invalid private key")
|
||||
ensure(len(sig) == SIGNATURE_SIZES[params], "crypto/mldsa: invalid destination size")
|
||||
|
||||
if !deterministic && !crypto.HAS_RAND_BYTES {
|
||||
return false
|
||||
}
|
||||
if len(ctx) > MAX_CTX_SIZE {
|
||||
return false
|
||||
}
|
||||
|
||||
rnd: [_mldsa.RNDBYTES]byte
|
||||
defer crypto.zero_explicit(&rnd, size_of(rnd))
|
||||
|
||||
if !deterministic {
|
||||
crypto.rand_bytes(rnd[:])
|
||||
}
|
||||
|
||||
return _mldsa.dsa_sign_internal(sig, msg, ctx, rnd[:], priv_key)
|
||||
}
|
||||
|
||||
// verify returns true if and only if (⟺) sig is a valid signature by pub_key
|
||||
// over (ctx, msg).
|
||||
@(require_results)
|
||||
verify :: proc(pub_key: ^Public_Key, ctx, msg, sig: []byte) -> bool {
|
||||
params := internal_to_params(pub_key.params)
|
||||
ensure(params != .Invalid, "crypto/mldsa: invalid public key")
|
||||
|
||||
if len(sig) != SIGNATURE_SIZES[params] {
|
||||
return false
|
||||
}
|
||||
if len(ctx) > MAX_CTX_SIZE {
|
||||
return false
|
||||
}
|
||||
|
||||
return _mldsa.dsa_verify_internal(sig, msg, ctx, pub_key)
|
||||
}
|
||||
|
||||
// params returns the Parameters used by a Private_Key or Public_Key
|
||||
// instance.
|
||||
@(require_results)
|
||||
params :: proc(k: ^$T) -> Parameters where (T == Private_Key || T == Public_Key) {
|
||||
return internal_to_params(k.params)
|
||||
}
|
||||
|
||||
// key_size returns the key size of a Private_Key or Public_Key in bytes.
|
||||
@(require_results)
|
||||
key_size :: proc(k: ^$T) -> int where (T == Private_Key || T == Public_Key) {
|
||||
when T == Private_Key {
|
||||
return PRIVATE_KEY_SEED_SIZE
|
||||
} else {
|
||||
return PUBLIC_KEY_SIZES[internal_to_params(k.params)]
|
||||
}
|
||||
}
|
||||
|
||||
// signature_size returns the key size of a signature in bytes.
|
||||
@(require_results)
|
||||
signature_size :: proc(k: ^$T) -> int where (T == Private_Key || T == Public_Key) {
|
||||
return SIGNATURE_SIZES[internal_to_params(k.params)]
|
||||
}
|
||||
|
||||
@(private="file",require_results)
|
||||
internal_to_params :: proc "contextless" (params: ^_mldsa.Params) -> Parameters {
|
||||
switch params {
|
||||
case &_mldsa.Params_44:
|
||||
return .ML_DSA_44
|
||||
case &_mldsa.Params_65:
|
||||
return .ML_DSA_65
|
||||
case &_mldsa.Params_87:
|
||||
return .ML_DSA_87
|
||||
case:
|
||||
return .Invalid
|
||||
}
|
||||
}
|
||||
7
core/crypto/mldsa/doc.odin
Normal file
7
core/crypto/mldsa/doc.odin
Normal file
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
Module-Lattice-Based Digital Signature Algorithm.
|
||||
|
||||
See:
|
||||
- [[ https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf ]]
|
||||
*/
|
||||
package mldsa
|
||||
304
core/crypto/mlkem/api.odin
Normal file
304
core/crypto/mlkem/api.odin
Normal file
@@ -0,0 +1,304 @@
|
||||
package mlkem
|
||||
|
||||
import "core:crypto"
|
||||
import "core:crypto/_mlkem"
|
||||
|
||||
// Parameters are the supported ML-KEM parameter sets.
|
||||
Parameters :: enum {
|
||||
Invalid,
|
||||
ML_KEM_512,
|
||||
ML_KEM_768,
|
||||
ML_KEM_1024,
|
||||
}
|
||||
|
||||
// DECAPSULATION_KEY_SEED_SIZE is the size of a Decapsulation key in bytes.
|
||||
DECAPSULATION_KEY_SEED_SIZE :: 64 // (d, z) in NIST terms.
|
||||
|
||||
// DECAPSULATION_KEY_EXPANDED_SIZES are the per-parameter sizes of the
|
||||
// decapsulation key in bytes.
|
||||
DECAPSULATION_KEY_EXPANDED_SIZES := [Parameters]int {
|
||||
.Invalid = 0,
|
||||
.ML_KEM_512 = _mlkem.DECAPSKEYBYTES_512, // 1632-bytes
|
||||
.ML_KEM_768 = _mlkem.DECAPSKEYBYTES_768, // 2400-bytes
|
||||
.ML_KEM_1024 = _mlkem.DECAPSKEYBYTES_1024, // 3168-bytes
|
||||
}
|
||||
|
||||
// ENCAPSULATION_KEY_SIZES are the per-parameter sizes of the encapsulation
|
||||
// key in bytes.
|
||||
ENCAPSULATION_KEY_SIZES := [Parameters]int {
|
||||
.Invalid = 0,
|
||||
.ML_KEM_512 = _mlkem.ENCAPSKEYBYTES_512, // 800-bytes
|
||||
.ML_KEM_768 = _mlkem.ENCAPSKEYBYTES_768, // 1184-bytes
|
||||
.ML_KEM_1024 = _mlkem.ENCAPSKEYBYTES_1024, // 1568-bytes
|
||||
}
|
||||
|
||||
// CIPHERTEXT_SIZES are the per-parameter set sizes of the ciphertext
|
||||
// in bytes.
|
||||
CIPHERTEXT_SIZES := [Parameters]int {
|
||||
.Invalid = 0,
|
||||
.ML_KEM_512 = _mlkem.CIPHERTEXTBYTES_512, // 768-bytes
|
||||
.ML_KEM_768 = _mlkem.CIPHERTEXTBYTES_768, // 1088-bytes
|
||||
.ML_KEM_1024 = _mlkem.CIPHERTEXTBYTES_1024, // 1568-bytes
|
||||
}
|
||||
|
||||
// SHARED_SECRET_SIZE is the size of the final shared secret in bytes.
|
||||
SHARED_SECRET_SIZE :: 32
|
||||
|
||||
// Decapsulation_Key is a ML-KEM decapsulation (aka "private") key.
|
||||
// This implementation opts to include the encapsulation (aka "public")
|
||||
// key as well for cases where the decapsulation key is reused (eg: HPKE
|
||||
// with X-Wing).
|
||||
Decapsulation_Key :: _mlkem.Decapsulation_Key
|
||||
|
||||
// Encapsulation_Key is a ML-KEM encapsulation (aka "public") key.
|
||||
Encapsulation_Key :: _mlkem.Encapsulation_Key
|
||||
|
||||
// decapsulation_key_generate uses the system entropy source to generate
|
||||
// a decapsulation key. This will only fail if and only if (⟺) the system
|
||||
// entropy source is missing or broken.
|
||||
@(require_results)
|
||||
decapsulation_key_generate :: proc(dk: ^Decapsulation_Key, params: Parameters) -> bool {
|
||||
decapsulation_key_clear(dk)
|
||||
|
||||
if !crypto.HAS_RAND_BYTES {
|
||||
return false
|
||||
}
|
||||
|
||||
k := params_to_k(params)
|
||||
if k == 0 {
|
||||
panic("crypto/mlkem: invalid parameter set")
|
||||
}
|
||||
|
||||
seed: [DECAPSULATION_KEY_SEED_SIZE]byte = ---
|
||||
defer crypto.zero_explicit(&seed, size_of(seed))
|
||||
|
||||
crypto.rand_bytes(seed[:])
|
||||
_mlkem.kem_keygen_internal(dk, seed[:], k)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// decapsulation_key_set_bytes decodes a byte-encoded decapsulation key
|
||||
// in (d, z) "seed" format, and returns true if and only if (⟺) the
|
||||
// operation was successful.
|
||||
@(require_results)
|
||||
decapsulation_key_set_bytes :: proc(dk: ^Decapsulation_Key, params: Parameters, seed: []byte) -> bool {
|
||||
k := params_to_k(params)
|
||||
if k == 0 {
|
||||
return false
|
||||
}
|
||||
if len(seed) != DECAPSULATION_KEY_SEED_SIZE {
|
||||
return false
|
||||
}
|
||||
|
||||
_mlkem.kem_keygen_internal(dk, seed, k)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// decapsulation_key_bytes sets dst to byte-encoding of dk in the (d, z)
|
||||
// "seed" format.
|
||||
decapsulation_key_bytes :: proc(dk: ^Decapsulation_Key, dst: []byte) {
|
||||
ensure(dk.pke_dk.k != 0, "crypto/mlkem: uninitialized Decapsulation_Key")
|
||||
ensure(len(dst) == DECAPSULATION_KEY_SEED_SIZE, "crypto/mlkem: invalid destination size")
|
||||
|
||||
copy(dst, dk.seed[:])
|
||||
}
|
||||
|
||||
// decapsulation_key_expanded_bytes sets dst to the byte-encoding of dk.
|
||||
// in the expanded FIPS 203 format. This primarily exists for export
|
||||
// purposes.
|
||||
decapsulation_key_expanded_bytes :: proc(dk: ^Decapsulation_Key, dst: []byte) {
|
||||
dk_len: int
|
||||
switch dk.pke_dk.k {
|
||||
case _mlkem.K_512:
|
||||
dk_len = DECAPSULATION_KEY_EXPANDED_SIZES[.ML_KEM_512]
|
||||
case _mlkem.K_768:
|
||||
dk_len = DECAPSULATION_KEY_EXPANDED_SIZES[.ML_KEM_768]
|
||||
case _mlkem.K_1024:
|
||||
dk_len = DECAPSULATION_KEY_EXPANDED_SIZES[.ML_KEM_1024]
|
||||
case:
|
||||
panic("crypto/mlkem: uninitialized Decapsulation_Key")
|
||||
}
|
||||
ensure(len(dst) == dk_len, "crypto/mlkem: invalid destination size")
|
||||
|
||||
_mlkem.decapsulation_key_expanded_bytes(dk, dst)
|
||||
}
|
||||
|
||||
// decapsulation_key_encaps_bytes sets dst to the byte-encoding of the
|
||||
// encasulation key corresponding to dk.
|
||||
decapsulation_key_encaps_bytes :: proc(dk: ^Decapsulation_Key, dst: []byte) {
|
||||
encapsulation_key_bytes(&dk.ek, dst)
|
||||
}
|
||||
|
||||
// decapsulation_key_clear clears dk to the uninitialized state.
|
||||
decapsulation_key_clear :: proc(dk: ^Decapsulation_Key) {
|
||||
crypto.zero_explicit(dk, size_of(Decapsulation_Key))
|
||||
}
|
||||
|
||||
// encapsulation_key_set_bytes decodes a byte-encoded encapsulation key,
|
||||
// and returns true if and only if (⟺) the operation was successful.
|
||||
@(require_results)
|
||||
encapsulation_key_set_bytes :: proc(ek: ^Encapsulation_Key, params: Parameters, b: []byte) -> bool {
|
||||
k := params_to_k(params)
|
||||
if k == 0 {
|
||||
return false
|
||||
}
|
||||
if len(b) != ENCAPSULATION_KEY_SIZES[params] {
|
||||
return false
|
||||
}
|
||||
|
||||
return _mlkem.encapsulation_key_set_bytes(ek, k, b)
|
||||
}
|
||||
|
||||
// encapsulation_key_set_decaps sets ek to the encapsulation key corresponding
|
||||
// to dk.
|
||||
encapsulation_key_set_decaps :: proc(ek: ^Encapsulation_Key, dk: ^Decapsulation_Key) {
|
||||
ensure(dk.pke_dk.k != 0, "crypto/mlkem: uninitialized Decapsulation_Key")
|
||||
_mlkem.encapsulation_key_set_decaps(ek, dk)
|
||||
}
|
||||
|
||||
// encapsulation_key_encaps_bytes sets dst to the byte-encoding of ek.
|
||||
encapsulation_key_bytes :: proc(ek: ^Encapsulation_Key, dst: []byte) {
|
||||
ensure(ek.pke_ek.k != 0, "crypto/mlkem: uninitialized Encapsulation_Key")
|
||||
|
||||
k_len: int
|
||||
switch ek.pke_ek.k {
|
||||
case _mlkem.K_512:
|
||||
k_len = ENCAPSULATION_KEY_SIZES[.ML_KEM_512]
|
||||
case _mlkem.K_768:
|
||||
k_len = ENCAPSULATION_KEY_SIZES[.ML_KEM_768]
|
||||
case _mlkem.K_1024:
|
||||
k_len = ENCAPSULATION_KEY_SIZES[.ML_KEM_1024]
|
||||
case:
|
||||
panic("crypto/mlkem: invalid destination size")
|
||||
}
|
||||
|
||||
copy(dst, ek.raw_bytes[:k_len])
|
||||
}
|
||||
|
||||
// encapsulation_key_clear clears ek to the uninitialized state.
|
||||
encapsulation_key_clear :: proc(ek: ^Encapsulation_Key) {
|
||||
crypto.zero_explicit(ek, size_of(Encapsulation_Key))
|
||||
}
|
||||
|
||||
// encaps_raw_ek_bytes uses the byte encoded encapsulation key to generate
|
||||
// a shared secret and an associated ciphertext. This routine will fail
|
||||
// if the system entropy source is unavailable, or of the encapsulation key
|
||||
// is invalid.
|
||||
@(require_results)
|
||||
encaps_ek_raw_bytes :: proc(params: Parameters, raw_ek, shared_secret, ciphertext: []byte) -> bool {
|
||||
ek: Encapsulation_Key = ---
|
||||
if !encapsulation_key_set_bytes(&ek, params, raw_ek) {
|
||||
return false
|
||||
}
|
||||
defer encapsulation_key_clear(&ek)
|
||||
|
||||
return encaps_ek(&ek, shared_secret, ciphertext)
|
||||
}
|
||||
|
||||
// encaps_ek uses the encapsulation key to generate a shared secret and an
|
||||
// associated ciphertext. This routine will fail if the system entropy source
|
||||
// is unavailable.
|
||||
@(require_results)
|
||||
encaps_ek :: proc(ek: ^Encapsulation_Key, shared_secret, ciphertext: []byte) -> bool {
|
||||
ensure(len(shared_secret) == SHARED_SECRET_SIZE, "crypto/mlkem: invalid shared_seret size")
|
||||
|
||||
if !crypto.HAS_RAND_BYTES {
|
||||
return false
|
||||
}
|
||||
|
||||
m: [_mlkem.SYMBYTES]byte = ---
|
||||
defer crypto.zero_explicit(&m, size_of(m))
|
||||
|
||||
crypto.rand_bytes(m[:])
|
||||
_mlkem.kem_encaps_internal(shared_secret, ciphertext, ek, m[:])
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
encaps :: proc {
|
||||
encaps_ek,
|
||||
encaps_ek_raw_bytes,
|
||||
}
|
||||
|
||||
// decaps uses the decapsulation key to generate a shared secret from a
|
||||
// ciphertext. Due to ML-KEM's implicit rejection mechanism, this function
|
||||
// will only return false if and only if (⟺) the lengths of the inputs
|
||||
// are invalid or the decapsulation key is uninitialized.
|
||||
//
|
||||
// This routine returning true does not guarantee that the shared secret
|
||||
// matches that generated by the peer.
|
||||
@(require_results)
|
||||
decaps :: proc(dk: ^Decapsulation_Key, ciphertext, shared_secret: []byte) -> bool {
|
||||
ensure(len(shared_secret) == SHARED_SECRET_SIZE, "crypto/mlkem: invalid shared_seret size")
|
||||
|
||||
ct_len: int
|
||||
switch dk.pke_dk.k {
|
||||
case _mlkem.K_512:
|
||||
ct_len = CIPHERTEXT_SIZES[.ML_KEM_512]
|
||||
case _mlkem.K_768:
|
||||
ct_len = CIPHERTEXT_SIZES[.ML_KEM_768]
|
||||
case _mlkem.K_1024:
|
||||
ct_len = CIPHERTEXT_SIZES[.ML_KEM_1024]
|
||||
case:
|
||||
return false
|
||||
}
|
||||
if len(ciphertext) != ct_len {
|
||||
return false
|
||||
}
|
||||
|
||||
_mlkem.kem_decaps_internal(shared_secret, dk, ciphertext)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// params returns the Parameters used by a Decapsulation_Key or
|
||||
// Encapsulation_Key instance.
|
||||
@(require_results)
|
||||
params :: proc(k: ^$T) -> Parameters where (T == Encapsulation_Key || T == Decapsulation_Key) {
|
||||
when T == Encapsulation_Key {
|
||||
return k_to_params(k.pke_ek.k)
|
||||
} else {
|
||||
return k_to_params(k.pke_dk.k)
|
||||
}
|
||||
}
|
||||
|
||||
// key_size returns the key size of a Decapsulation_Key or Encapsulation_Key
|
||||
// in bytes.
|
||||
@(require_results)
|
||||
key_size :: proc(k: ^$T) -> int where (T == Encapsulation_Key || T == Decapsulation_Key) {
|
||||
when T == Encapsulation_Key {
|
||||
return ENCAPSULATION_KEY_SIZES[k.pke_ek.k]
|
||||
} else {
|
||||
return DECAPSULATION_KEY_SEED_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
params_to_k :: #force_inline proc "contextless" (params: Parameters) -> int {
|
||||
#partial switch params {
|
||||
case .ML_KEM_512:
|
||||
return _mlkem.K_512
|
||||
case .ML_KEM_768:
|
||||
return _mlkem.K_768
|
||||
case .ML_KEM_1024:
|
||||
return _mlkem.K_1024
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
k_to_params :: #force_inline proc "contextless" (k: int) -> Parameters {
|
||||
switch k {
|
||||
case _mlkem.K_512:
|
||||
return .ML_KEM_512
|
||||
case _mlkem.K_768:
|
||||
return .ML_KEM_768
|
||||
case _mlkem.K_1024:
|
||||
return .ML_KEM_1024
|
||||
}
|
||||
|
||||
return .Invalid
|
||||
}
|
||||
7
core/crypto/mlkem/doc.odin
Normal file
7
core/crypto/mlkem/doc.odin
Normal file
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
ML-KEM Module-Lattice-Based Key-Encapsulation Mechanism.
|
||||
|
||||
See:
|
||||
- [[ https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.203.pdf ]]
|
||||
*/
|
||||
package mlkem
|
||||
@@ -58,7 +58,9 @@ generate_keypair :: proc(protocol: ^Protocol, private_key: ^ecdh.Private_Key) {
|
||||
case: panic("crypto/noise: unsupported DH curve in protocol")
|
||||
}
|
||||
|
||||
ecdh.private_key_generate(private_key, protocol.dh)
|
||||
if !ecdh.private_key_generate(private_key, protocol.dh) {
|
||||
panic("crypto/noise: entropy source unavailable")
|
||||
}
|
||||
}
|
||||
|
||||
// Performs a Diffie-Hellman calculation between the private key in key_pair
|
||||
@@ -552,7 +554,7 @@ handshakestate_initialize :: proc(
|
||||
|
||||
if initiator {
|
||||
if slice.contains(message_pattern.pre_messages, Pre_Token.ini_s) {
|
||||
ecdh.public_key_bytes(&s._pub_key, dst)
|
||||
ecdh.private_key_public_bytes(s, dst)
|
||||
symmetricstate_mix_hash(symmetric_state, dst)
|
||||
}
|
||||
if slice.contains(message_pattern.pre_messages, Pre_Token.res_s) {
|
||||
@@ -565,7 +567,7 @@ handshakestate_initialize :: proc(
|
||||
symmetricstate_mix_hash(symmetric_state, dst)
|
||||
}
|
||||
if slice.contains(message_pattern.pre_messages, Pre_Token.res_s) {
|
||||
ecdh.public_key_bytes(&s._pub_key, dst)
|
||||
ecdh.private_key_public_bytes(s, dst)
|
||||
symmetricstate_mix_hash(symmetric_state, dst)
|
||||
}
|
||||
}
|
||||
@@ -663,7 +665,7 @@ handshakestate_write_message :: proc(self: ^Handshake_State, payload, dst: []byt
|
||||
generate_keypair(protocol, &self.e)
|
||||
}
|
||||
e_public := dh_buf[:d_len]
|
||||
ecdh.public_key_bytes(&self.e._pub_key, e_public)
|
||||
ecdh.private_key_public_bytes(&self.e, e_public)
|
||||
n := append(&pattern_buf, ..e_public)
|
||||
ensure(n == d_len, "crypto/noise: truncated append `e`")
|
||||
|
||||
@@ -674,7 +676,7 @@ handshakestate_write_message :: proc(self: ^Handshake_State, payload, dst: []byt
|
||||
|
||||
case .s:
|
||||
s_public := dh_buf[:d_len]
|
||||
ecdh.public_key_bytes(&self.s._pub_key, s_public)
|
||||
ecdh.private_key_public_bytes(&self.s, s_public)
|
||||
|
||||
tmp: [MAX_DH_SIZE+TAG_SIZE]byte = ---
|
||||
dh_buf := tmp[:d_len+TAG_SIZE]
|
||||
@@ -837,7 +839,9 @@ handshakestate_read_message :: proc(self: ^Handshake_State, message, dst: []byte
|
||||
panic("crypto/noise: re was not empty when processing token 'e' during ReadMessage")
|
||||
}
|
||||
|
||||
ecdh.public_key_set_bytes(&self.re, protocol.dh, re)
|
||||
if !ecdh.public_key_set_bytes(&self.re, protocol.dh, re) {
|
||||
return nil, .Invalid_Handshake_Message
|
||||
}
|
||||
symmetricstate_mix_hash(&self.symmetric_state, re)
|
||||
if self.message_pattern.is_psk {
|
||||
symmetricstate_mix_key(&self.symmetric_state, re)
|
||||
@@ -864,7 +868,10 @@ handshakestate_read_message :: proc(self: ^Handshake_State, message, dst: []byte
|
||||
panic("crypto/noise: rs was not empty when processing token 's' during ReadMessage")
|
||||
}
|
||||
|
||||
ecdh.public_key_set_bytes(&self.rs, protocol.dh, rs)
|
||||
if !ecdh.public_key_set_bytes(&self.rs, protocol.dh, rs) {
|
||||
self.status = .Handshake_Failed
|
||||
return nil, .Invalid_Handshake_Message
|
||||
}
|
||||
msg = msg[rs_len:]
|
||||
|
||||
case .ee:
|
||||
|
||||
@@ -8,7 +8,7 @@ import "core:strings"
|
||||
import "core:c"
|
||||
|
||||
// NOTE: Relies on C++23 which adds <stacktrace> and becomes ABI and that can be used
|
||||
foreign import stdcpplibbacktrace "system:stdc++_libbacktrace"
|
||||
foreign import stdcpplibbacktrace "system:stdc++exp"
|
||||
|
||||
foreign import libdl "system:dl"
|
||||
|
||||
@@ -191,4 +191,4 @@ _resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> F
|
||||
)
|
||||
|
||||
return btc.frame
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
#+build haiku
|
||||
#+private
|
||||
package dynlib
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
_LIBRARY_FILE_EXTENSION :: ""
|
||||
|
||||
_load_library :: proc(path: string, global_symbols: bool, allocator: runtime.Allocator) -> (Library, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
_unload_library :: proc(library: Library) -> bool {
|
||||
return false
|
||||
}
|
||||
|
||||
_symbol_address :: proc(library: Library, symbol: string, allocator: runtime.Allocator) -> (ptr: rawptr, found: bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
_last_error :: proc() -> string {
|
||||
return ""
|
||||
}
|
||||
@@ -9,10 +9,11 @@ truncate it from the encoded output.
|
||||
*/
|
||||
package encoding_base64
|
||||
|
||||
import "base:intrinsics"
|
||||
import "base:runtime"
|
||||
import "core:io"
|
||||
import "core:strings"
|
||||
|
||||
@(rodata)
|
||||
ENC_TABLE := [64]byte {
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||
@@ -25,6 +26,7 @@ ENC_TABLE := [64]byte {
|
||||
}
|
||||
|
||||
// Encoding table for Base64url variant
|
||||
@(rodata)
|
||||
ENC_URL_TABLE := [64]byte {
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||
@@ -38,77 +40,89 @@ ENC_URL_TABLE := [64]byte {
|
||||
|
||||
PADDING :: '='
|
||||
|
||||
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,
|
||||
@(rodata)
|
||||
DEC_TABLE := [256]i8 {
|
||||
-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,
|
||||
52, 53, 54, 55, 56, 57, 58, 59,
|
||||
60, 61, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 1, 2, 3, 4, 5, 6,
|
||||
60, 61, -1, -1, -1, -1, -1, -1,
|
||||
-1, 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, 0, 0, 0, 0, 0,
|
||||
0, 26, 27, 28, 29, 30, 31, 32,
|
||||
23, 24, 25, -1, -1, -1, -1, -1,
|
||||
-1, 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, 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,
|
||||
49, 50, 51, -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, -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, -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, -1, -1, -1, -1,
|
||||
}
|
||||
|
||||
// Decoding table for Base64url variant
|
||||
DEC_URL_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, 0, 0, 62, 0, 0,
|
||||
@(rodata)
|
||||
DEC_URL_TABLE := [256]i8 {
|
||||
-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, -1, -1, 62, -1, -1,
|
||||
52, 53, 54, 55, 56, 57, 58, 59,
|
||||
60, 61, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 1, 2, 3, 4, 5, 6,
|
||||
60, 61, -1, -1, -1, -1, -1, -1,
|
||||
-1, 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, 0, 0, 0, 0, 63,
|
||||
0, 26, 27, 28, 29, 30, 31, 32,
|
||||
23, 24, 25, -1, -1, -1, -1, 63,
|
||||
-1, 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, 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,
|
||||
49, 50, 51, -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, -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, -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, -1, -1, -1, -1,
|
||||
}
|
||||
|
||||
Error :: union #shared_nil {
|
||||
runtime.Allocator_Error,
|
||||
io.Error,
|
||||
Decode_Error,
|
||||
}
|
||||
|
||||
Decode_Error :: enum {
|
||||
None,
|
||||
Invalid_Character,
|
||||
}
|
||||
|
||||
encode :: proc(data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocator) -> (encoded: string, err: runtime.Allocator_Error) #optional_allocator_error {
|
||||
out_length := encoded_len(data)
|
||||
@@ -116,23 +130,47 @@ encode :: proc(data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocato
|
||||
return
|
||||
}
|
||||
|
||||
out := strings.builder_make(0, out_length, allocator) or_return
|
||||
ioerr := encode_into(strings.to_stream(&out), data, ENC_TBL)
|
||||
out := make([]byte, out_length, allocator) or_return
|
||||
_, ioerr := encode_impl(out, data, ENC_TBL)
|
||||
assert(ioerr == nil, "encode should not IO error")
|
||||
assert(len(out) == out_length, "buffer resized, `encoded_len` was wrong")
|
||||
|
||||
assert(ioerr == nil, "string builder should not IO error")
|
||||
assert(strings.builder_cap(out) == out_length, "buffer resized, `encoded_len` was wrong")
|
||||
encoded = transmute(string)(out)
|
||||
|
||||
return strings.to_string(out), nil
|
||||
return
|
||||
}
|
||||
|
||||
encode_into_buf :: proc(dst, data: []byte, ENC_TBL := ENC_TABLE) -> (encoded: []byte, err: Error) {
|
||||
out_length := encoded_len(data)
|
||||
if out_length == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
return encode_impl(dst, data, ENC_TBL)
|
||||
}
|
||||
|
||||
encode_into :: proc(w: io.Writer, data: []byte, ENC_TBL := ENC_TABLE) -> io.Error {
|
||||
_, err := encode_impl(w, data, ENC_TBL)
|
||||
return err
|
||||
}
|
||||
|
||||
@(private)
|
||||
encode_impl :: proc(dst: $T, data: []byte, ENC_TBL := ENC_TABLE) -> ([]byte, io.Error) where T == io.Writer || T == []byte {
|
||||
length := len(data)
|
||||
if length == 0 {
|
||||
return nil
|
||||
when T == []byte {
|
||||
out_length := encoded_len(data)
|
||||
if len(dst) < out_length {
|
||||
return nil, io.Error.Short_Buffer
|
||||
}
|
||||
buf := dst
|
||||
} else {
|
||||
if length == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
buf: [4]byte
|
||||
}
|
||||
|
||||
c0, c1, c2, block: int
|
||||
out: [4]byte
|
||||
for i := 0; i < length; i += 3 {
|
||||
#no_bounds_check {
|
||||
c0, c1, c2 = int(data[i]), -1, -1
|
||||
@@ -141,15 +179,27 @@ encode_into :: proc(w: io.Writer, data: []byte, ENC_TBL := ENC_TABLE) -> io.Erro
|
||||
if i + 2 < length { c2 = int(data[i + 2]) }
|
||||
|
||||
block = (c0 << 16) | (max(c1, 0) << 8) | max(c2, 0)
|
||||
|
||||
out[0] = ENC_TBL[block >> 18 & 63]
|
||||
out[1] = ENC_TBL[block >> 12 & 63]
|
||||
out[2] = c1 == -1 ? PADDING : ENC_TBL[block >> 6 & 63]
|
||||
out[3] = c2 == -1 ? PADDING : ENC_TBL[block & 63]
|
||||
|
||||
buf[0] = ENC_TBL[block >> 18 & 63]
|
||||
buf[1] = ENC_TBL[block >> 12 & 63]
|
||||
buf[2] = c1 == -1 ? PADDING : ENC_TBL[block >> 6 & 63]
|
||||
buf[3] = c2 == -1 ? PADDING : ENC_TBL[block & 63]
|
||||
when T == []byte {
|
||||
buf = buf[4:]
|
||||
}
|
||||
}
|
||||
when T == io.Writer {
|
||||
if _, err := io.write_full(dst, buf[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
io.write_full(w, out[:]) or_return
|
||||
}
|
||||
return nil
|
||||
|
||||
when T == io.Writer {
|
||||
return nil, nil
|
||||
} else {
|
||||
return dst[:out_length], nil
|
||||
}
|
||||
}
|
||||
|
||||
encoded_len :: proc(data: []byte) -> int {
|
||||
@@ -161,65 +211,140 @@ encoded_len :: proc(data: []byte) -> int {
|
||||
return ((4 * length / 3) + 3) &~ 3
|
||||
}
|
||||
|
||||
decode :: proc(data: string, DEC_TBL := DEC_TABLE, allocator := context.allocator) -> (decoded: []byte, err: runtime.Allocator_Error) #optional_allocator_error {
|
||||
decode :: proc(data: string, DEC_TBL := DEC_TABLE, dst: []byte = nil, allocator := context.allocator) -> (decoded: []byte, err: Error) {
|
||||
out_length := decoded_len(data)
|
||||
if out_length == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
out := strings.builder_make(0, out_length, allocator) or_return
|
||||
ioerr := decode_into(strings.to_stream(&out), data, DEC_TBL)
|
||||
buf: []byte
|
||||
if buf, err = make([]byte, out_length, allocator); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
assert(ioerr == nil, "string builder should not IO error")
|
||||
assert(strings.builder_cap(out) == out_length, "buffer resized, `decoded_len` was wrong")
|
||||
decoded, err = decode_impl(buf, data, DEC_TBL)
|
||||
if err != nil {
|
||||
delete(buf, allocator)
|
||||
}
|
||||
assert(err != nil || len(decoded) == out_length, "buffer unexpectedly resized, `decoded_len` was wrong")
|
||||
|
||||
return out.buf[:], nil
|
||||
return
|
||||
}
|
||||
|
||||
decode_into :: proc(w: io.Writer, data: string, DEC_TBL := DEC_TABLE) -> io.Error {
|
||||
decode_into_buf :: proc(dst: []byte, data: string, DEC_TBL := DEC_TABLE) -> (decoded: []byte, err: Error) {
|
||||
out_length := decoded_len(data)
|
||||
if out_length == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
return decode_impl(dst, data, DEC_TBL)
|
||||
}
|
||||
|
||||
decode_into :: proc(w: io.Writer, data: string, DEC_TBL := DEC_TABLE) -> Error {
|
||||
_, err := decode_impl(w, data, DEC_TBL)
|
||||
return err
|
||||
}
|
||||
|
||||
@(private)
|
||||
decode_impl :: proc(dst: $T, data: string, DEC_TBL := DEC_TABLE) -> ([]byte, Error) where T == io.Writer || T == []byte {
|
||||
length := decoded_len(data)
|
||||
if length == 0 {
|
||||
return nil
|
||||
when T == []byte {
|
||||
if len(dst) < length {
|
||||
return nil, io.Error.Short_Buffer
|
||||
}
|
||||
off: int
|
||||
} else {
|
||||
if length == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
buf: [3]byte
|
||||
}
|
||||
|
||||
c0, c1, c2, c3: int
|
||||
d0, d1, d2, d3: i8
|
||||
b0, b1, b2: int
|
||||
buf: [3]byte
|
||||
i, j: int
|
||||
for ; j + 3 <= length; i, j = i + 4, j + 3 {
|
||||
#no_bounds_check {
|
||||
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]])
|
||||
d0 = DEC_TBL[data[i]]
|
||||
d1 = DEC_TBL[data[i + 1]]
|
||||
d2 = DEC_TBL[data[i + 2]]
|
||||
d3 = DEC_TBL[data[i + 3]]
|
||||
|
||||
if intrinsics.unlikely((d0 | d1 | d2 | d3) & ~i8(0x3f) != 0) {
|
||||
return nil, Decode_Error.Invalid_Character
|
||||
}
|
||||
|
||||
c0, c1, c2, c3 = int(d0), int(d1), int(d2), int(d3)
|
||||
|
||||
b0 = (c0 << 2) | (c1 >> 4)
|
||||
b1 = (c1 << 4) | (c2 >> 2)
|
||||
b2 = (c2 << 6) | c3
|
||||
|
||||
buf[0] = byte(b0)
|
||||
buf[1] = byte(b1)
|
||||
buf[2] = byte(b2)
|
||||
when T == []byte {
|
||||
dst[off+0] = byte(b0)
|
||||
dst[off+1] = byte(b1)
|
||||
dst[off+2] = byte(b2)
|
||||
off += 3
|
||||
} else {
|
||||
buf[0] = byte(b0)
|
||||
buf[1] = byte(b1)
|
||||
buf[2] = byte(b2)
|
||||
}
|
||||
}
|
||||
|
||||
io.write_full(w, buf[:]) or_return
|
||||
when T == io.Writer {
|
||||
if _, err := io.write_full(dst, buf[:]); err != .None {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rest := length - j
|
||||
if rest > 0 {
|
||||
#no_bounds_check {
|
||||
c0 = int(DEC_TBL[data[i]])
|
||||
c1 = int(DEC_TBL[data[i + 1]])
|
||||
c2 = int(DEC_TBL[data[i + 2]])
|
||||
// Note: decoded_len handles removing padding.
|
||||
d0 = DEC_TBL[data[i]]
|
||||
d1 = DEC_TBL[data[i + 1]]
|
||||
if d2 = 0; rest == 2 {
|
||||
d2 = DEC_TBL[data[i + 2]]
|
||||
}
|
||||
|
||||
if intrinsics.unlikely((d0 | d1 | d2) & ~i8(0x3f) != 0) {
|
||||
return nil, Decode_Error.Invalid_Character
|
||||
}
|
||||
|
||||
c0, c1, c2 = int(d0), int(d1), int(d2)
|
||||
|
||||
b0 = (c0 << 2) | (c1 >> 4)
|
||||
b1 = (c1 << 4) | (c2 >> 2)
|
||||
|
||||
when T == []byte {
|
||||
switch rest {
|
||||
case 2:
|
||||
dst[off+1] = byte(b1)
|
||||
fallthrough
|
||||
case 1:
|
||||
dst[off] = byte(b0)
|
||||
}
|
||||
} else {
|
||||
buf[0] = byte(b0)
|
||||
buf[1] = byte(b1)
|
||||
}
|
||||
}
|
||||
|
||||
switch rest {
|
||||
case 1: io.write_byte(w, byte(b0)) or_return
|
||||
case 2: io.write_full(w, {byte(b0), byte(b1)}) or_return
|
||||
when T == io.Writer {
|
||||
if _, err := io.write_full(dst, buf[:rest]); err != .None {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
when T == io.Writer {
|
||||
return nil, nil
|
||||
} else {
|
||||
return dst[:length], nil
|
||||
}
|
||||
}
|
||||
|
||||
decoded_len :: proc(data: string) -> int {
|
||||
|
||||
@@ -308,13 +308,13 @@ tag_base64_unmarshal :: proc(_: ^Tag_Implementation, d: Decoder, _: Tag_Number,
|
||||
if t.is_cstring {
|
||||
length := base64.decoded_len(bytes)
|
||||
builder := strings.builder_make(0, length+1)
|
||||
base64.decode_into(strings.to_stream(&builder), bytes) or_return
|
||||
b64_decode_into(strings.to_stream(&builder), bytes) or_return
|
||||
|
||||
raw := (^cstring)(v.data)
|
||||
raw^ = cstring(raw_data(builder.buf))
|
||||
} else {
|
||||
raw := (^string)(v.data)
|
||||
raw^ = string(base64.decode(bytes) or_return)
|
||||
raw^ = string(b64_decode(bytes) or_return)
|
||||
}
|
||||
|
||||
return
|
||||
@@ -325,16 +325,16 @@ tag_base64_unmarshal :: proc(_: ^Tag_Implementation, d: Decoder, _: Tag_Number,
|
||||
if elem_base.id != byte { return _unsupported(v, hdr) }
|
||||
|
||||
raw := (^[]byte)(v.data)
|
||||
raw^ = base64.decode(bytes) or_return
|
||||
raw^ = b64_decode(bytes) or_return
|
||||
return
|
||||
|
||||
|
||||
case reflect.Type_Info_Dynamic_Array:
|
||||
elem_base := reflect.type_info_base(t.elem)
|
||||
|
||||
if elem_base.id != byte { return _unsupported(v, hdr) }
|
||||
|
||||
decoded := base64.decode(bytes) or_return
|
||||
|
||||
decoded := b64_decode(bytes) or_return
|
||||
|
||||
raw := (^mem.Raw_Dynamic_Array)(v.data)
|
||||
raw.data = raw_data(decoded)
|
||||
raw.len = len(decoded)
|
||||
@@ -348,9 +348,9 @@ tag_base64_unmarshal :: proc(_: ^Tag_Implementation, d: Decoder, _: Tag_Number,
|
||||
if elem_base.id != byte { return _unsupported(v, hdr) }
|
||||
|
||||
if base64.decoded_len(bytes) > t.count { return _unsupported(v, hdr) }
|
||||
|
||||
|
||||
slice := ([^]byte)(v.data)[:len(bytes)]
|
||||
copy(slice, base64.decode(bytes) or_return)
|
||||
copy(slice, b64_decode(bytes) or_return)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -384,3 +384,33 @@ tag_base64_marshal :: proc(_: ^Tag_Implementation, e: Encoder, v: any) -> Marsha
|
||||
err_conv(_encode_u64(e, u64(out_len), .Text)) or_return
|
||||
return base64.encode_into(e.writer, bytes)
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
err_from_b64 :: proc(err: base64.Error) -> Unmarshal_Error {
|
||||
switch e in err {
|
||||
case runtime.Allocator_Error:
|
||||
return e
|
||||
case io.Error:
|
||||
return e
|
||||
case base64.Decode_Error:
|
||||
return Decode_Data_Error.Bad_Tag_Value
|
||||
}
|
||||
|
||||
// Should NEVER happen, but fail gracefully.
|
||||
return io.Error.Unknown
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
b64_decode :: proc(data: string) -> ([]byte, Unmarshal_Error) {
|
||||
decoded, err := base64.decode(data)
|
||||
if err == nil {
|
||||
return decoded, nil
|
||||
}
|
||||
|
||||
return nil, err_from_b64(err)
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
b64_decode_into :: proc(w: io.Writer, data: string) -> Unmarshal_Error {
|
||||
return err_from_b64(base64.decode_into(w, data))
|
||||
}
|
||||
|
||||
@@ -474,7 +474,10 @@ unquote_string :: proc(token: Token, spec: Specification, allocator := context.a
|
||||
i += width
|
||||
|
||||
buf, buf_width := utf8.encode_rune(r)
|
||||
assert(buf_width <= width)
|
||||
// If we have an invalid utf8 character, width can be smaller than the width of RUNE_ERROR
|
||||
if r != utf8.RUNE_ERROR {
|
||||
assert(buf_width <= width)
|
||||
}
|
||||
copy(b[w:], buf[:buf_width])
|
||||
w += buf_width
|
||||
}
|
||||
|
||||
@@ -113,6 +113,7 @@ destroy_value :: proc(value: Value, allocator := context.allocator, loc := #call
|
||||
}
|
||||
|
||||
clone_value :: proc(value: Value, allocator := context.allocator) -> Value {
|
||||
value := value
|
||||
context.allocator = allocator
|
||||
|
||||
#partial switch &v in value {
|
||||
|
||||
@@ -808,6 +808,16 @@ unmarshal_array :: proc(p: ^Parser, v: any) -> (err: Unmarshal_Error) {
|
||||
raw.allocator = p.allocator
|
||||
|
||||
return assign_array(p, raw.data, t.elem, length)
|
||||
|
||||
case reflect.Type_Info_Fixed_Capacity_Dynamic_Array:
|
||||
if int(length) > t.capacity {
|
||||
return UNSUPPORTED_TYPE
|
||||
}
|
||||
base_ptr := cast(uintptr)v.data
|
||||
len_ptr := base_ptr + t.len_offset
|
||||
len_val := cast(^int)len_ptr
|
||||
len_val^ = int(length)
|
||||
return assign_array(p, rawptr(base_ptr), t.elem, length)
|
||||
|
||||
case reflect.Type_Info_Array:
|
||||
// NOTE(bill): Allow lengths which are less than the dst array
|
||||
|
||||
89
core/encoding/json/unparse.odin
Normal file
89
core/encoding/json/unparse.odin
Normal file
@@ -0,0 +1,89 @@
|
||||
package encoding_json
|
||||
|
||||
import "base:runtime"
|
||||
import "core:strings"
|
||||
import "core:io"
|
||||
import "core:slice"
|
||||
|
||||
Unparse_Error :: union #shared_nil {
|
||||
io.Error,
|
||||
runtime.Allocator_Error,
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
unparse :: proc(v: Value, opt: Marshal_Options = {}, allocator := context.allocator, loc := #caller_location) -> (data: string, err: Unparse_Error) {
|
||||
b := strings.builder_make(allocator, loc)
|
||||
defer if err != nil {
|
||||
strings.builder_destroy(&b)
|
||||
}
|
||||
|
||||
// temp guard in case we are sorting map keys, which will use temp allocations
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = allocator == context.temp_allocator)
|
||||
|
||||
opt := opt
|
||||
unparse_to_builder(&b, v, &opt) or_return
|
||||
data = string(b.buf[:])
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
unparse_to_builder :: proc(b: ^strings.Builder, v: Value, opt: ^Marshal_Options) -> Unparse_Error {
|
||||
return unparse_to_writer(strings.to_writer(b), v, opt)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
unparse_to_writer :: proc(w: io.Writer, value: Value, opt: ^Marshal_Options) -> Unparse_Error {
|
||||
switch v in value {
|
||||
case nil, Null:
|
||||
io.write_string(w, "null") or_return
|
||||
case Integer:
|
||||
base := 16 if opt.write_uint_as_hex && (opt.spec == .JSON5 || opt.spec == .MJSON) else 10
|
||||
io.write_i64(w, v, base) or_return
|
||||
case Float:
|
||||
io.write_f64(w, v) or_return
|
||||
case Boolean:
|
||||
io.write_string(w, "true" if v else "false") or_return
|
||||
case String:
|
||||
io.write_quoted_string(w, v, '"', nil, true) or_return
|
||||
case Array:
|
||||
opt_write_start(w, opt, '[') or_return
|
||||
for e, i in v {
|
||||
opt_write_iteration(w, opt, i == 0) or_return
|
||||
unparse_to_writer (w, e, opt) or_return
|
||||
}
|
||||
opt_write_end(w, opt, ']') or_return
|
||||
case Object:
|
||||
if !opt.sort_maps_by_key {
|
||||
opt_write_start(w, opt, '{') or_return
|
||||
for first_iteration := true; key, val in v {
|
||||
opt_write_iteration(w, opt, first_iteration) or_return
|
||||
opt_write_key (w, opt, key) or_return
|
||||
unparse_to_writer (w, val, opt) or_return
|
||||
first_iteration = false
|
||||
}
|
||||
opt_write_end(w, opt, '}') or_return
|
||||
} else {
|
||||
Map_Entry :: struct {
|
||||
key: string,
|
||||
value: Value,
|
||||
}
|
||||
|
||||
entries := make([dynamic]Map_Entry, 0, len(v), context.temp_allocator) or_return
|
||||
for key, val in v {
|
||||
_, _ = append(&entries, Map_Entry{key, val})
|
||||
}
|
||||
|
||||
slice.sort_by(entries[:], proc(i, j: Map_Entry) -> bool { return i.key < j.key })
|
||||
|
||||
opt_write_start(w, opt, '{') or_return
|
||||
for e, i in entries {
|
||||
opt_write_iteration(w, opt, i == 0) or_return
|
||||
opt_write_key (w, opt, e.key) or_return
|
||||
unparse_to_writer (w, e.value, opt) or_return
|
||||
}
|
||||
opt_write_end(w, opt, '}') or_return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
7
core/encoding/pem/doc.odin
Normal file
7
core/encoding/pem/doc.odin
Normal file
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
Encodes and decodes PEM formatted data.
|
||||
|
||||
See:
|
||||
- [[ https://www.rfc-editor.org/rfc/rfc7468.html ]]
|
||||
*/
|
||||
package pem
|
||||
299
core/encoding/pem/pem.odin
Normal file
299
core/encoding/pem/pem.odin
Normal file
@@ -0,0 +1,299 @@
|
||||
package pem
|
||||
|
||||
import "base:runtime"
|
||||
import "core:bufio"
|
||||
import "core:bytes"
|
||||
import "core:crypto"
|
||||
import "core:encoding/base64"
|
||||
import "core:strings"
|
||||
|
||||
@(private)
|
||||
BASE64_FULL_LINE_LENGTH :: 64
|
||||
@(private)
|
||||
BASE64_FULL_LINE_BYTES :: (BASE64_FULL_LINE_LENGTH / 4) * 3
|
||||
@(private)
|
||||
PREFIX_BEGIN : string : "-----BEGIN "
|
||||
@(private)
|
||||
PREFIX_END : string : "-----END "
|
||||
@(private)
|
||||
SUFFIX : string : "-----"
|
||||
@(private)
|
||||
LF :: "\n"
|
||||
@(private)
|
||||
PREEB_OVERHEAD :: len(PREFIX_BEGIN) + len(SUFFIX) + len(LF)
|
||||
@(private)
|
||||
POSTEB_OVERHEAD :: len(PREFIX_END) + len(SUFFIX)
|
||||
|
||||
// Block is a block of PEM encoded data.
|
||||
Block :: struct {
|
||||
label: string,
|
||||
data: [dynamic]byte,
|
||||
}
|
||||
|
||||
LABEL_CERTIFICATE :: "CERTIFICATE" // RFC 5280
|
||||
LABEL_X509_CRL :: "X509_CRL" // RFC 5280
|
||||
LABEL_CERTIFICATE_REQUEST :: "CERTIFICATE REQUEST" // RFC 2986
|
||||
LABEL_PKCS7 :: "PKCS7" // RFC 2315
|
||||
LABEL_CMS :: "CMS" // RFC 5652
|
||||
LABEL_PRIVATE_KEY :: "PRIVATE KEY" // RFC 5208/ RFC 5958
|
||||
LABEL_ENCRYPTED_PRIVATE_KEY :: "ENCRYPTED PRIVATE KEY" // RFC 5958
|
||||
LABEL_ATTRIBUTE_CERTIFICATE :: "ATTRIBUTE CERTIFICATE" // RFC 5755
|
||||
LABEL_PUBLIC_KEY :: "PUBLIC KEY" // RFC 5280
|
||||
|
||||
Decode_Error :: enum {
|
||||
None,
|
||||
Bad_Boundary, // Invalid boundary line.
|
||||
Bad_Label, // Invalid label in BEGIN/END boundary line.
|
||||
Bad_Data, // Invalid base64 data.
|
||||
Label_Mismatch, // Label in END boundary line does not match.
|
||||
Missing_End_Boundary, // End of data without END boundary.
|
||||
}
|
||||
|
||||
Error :: union #shared_nil {
|
||||
runtime.Allocator_Error,
|
||||
Decode_Error,
|
||||
}
|
||||
|
||||
// decode decodes the first encountered PEM block, returning the resulting
|
||||
// block, remaining data, and nil if and only if (⟺) the process was
|
||||
// successful.
|
||||
//
|
||||
// Note: No PEM blocks will result in this procedure returning all nils,
|
||||
// and is not considered an error.
|
||||
@(require_results)
|
||||
decode :: proc(data: []byte, allocator := context.allocator) -> (blk: ^Block, remaining: []byte, err: Error) {
|
||||
line: []byte
|
||||
remaining = data
|
||||
|
||||
// Search for the first `preeb`.
|
||||
label: string
|
||||
found := false // Label is allowed to be empty.
|
||||
for len(remaining) > 0 {
|
||||
line, remaining = get_line(remaining)
|
||||
|
||||
label, found, err = parse_eb(line, true)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if found {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// RFC 1421: Parse header block.
|
||||
// RFC 7468 (lax): Skip whitespace.
|
||||
|
||||
// Initialize the block.
|
||||
blk = new(Block, allocator) or_return
|
||||
if blk.data, err = make([dynamic]byte, 0, 32, allocator); err != nil {
|
||||
free(blk, allocator)
|
||||
return nil, nil, err
|
||||
}
|
||||
if blk.label, err = strings.clone(label, allocator); err != nil {
|
||||
block_delete(blk)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Parse the `strictbase64text`.
|
||||
l_buf: [BASE64_FULL_LINE_BYTES]byte
|
||||
defer crypto.zero_explicit(&l_buf, size_of(l_buf))
|
||||
base64text_loop: for len(remaining) > 0 {
|
||||
line, remaining = get_line(remaining)
|
||||
l := len(line)
|
||||
switch {
|
||||
case l == 0:
|
||||
block_delete(blk)
|
||||
return nil, nil, .Bad_Data
|
||||
case line[0] == '-':
|
||||
// Looks like we hit the `posteb`, break.
|
||||
break base64text_loop
|
||||
case l > BASE64_FULL_LINE_LENGTH || l & 3 != 0:
|
||||
// Padding is mandatory, so the line length will always
|
||||
// be a multiple of 4.
|
||||
block_delete(blk)
|
||||
return nil, nil, .Bad_Data
|
||||
}
|
||||
|
||||
decoded, dec_err := base64.decode_into_buf(l_buf[:], transmute(string)(line))
|
||||
if dec_err != nil {
|
||||
block_delete(blk)
|
||||
return nil, nil, .Bad_Data
|
||||
}
|
||||
|
||||
if _, err = append(&blk.data, ..decoded); err != nil {
|
||||
block_delete(blk)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// As `strictbase64text = *base64fullline strictbase64finl`,
|
||||
// if we did not have a full line, we must have reached
|
||||
// `strictbase64finl`. Grab what should be the `posteb`
|
||||
// and break.
|
||||
if l < BASE64_FULL_LINE_LENGTH {
|
||||
line, remaining = get_line(remaining)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the `posteb`.
|
||||
post_label: string
|
||||
post_label, found, err = parse_eb(line, false)
|
||||
if err == nil {
|
||||
switch {
|
||||
case !found:
|
||||
err = .Missing_End_Boundary
|
||||
case label != post_label:
|
||||
err = .Label_Mismatch
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
block_delete(blk)
|
||||
blk, remaining = nil, nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// encode encodes the specified label and data into PEM format.
|
||||
@(require_results)
|
||||
encode :: proc(label: string, data: []byte, newline := false, allocator := context.allocator) -> (res: []byte, err: runtime.Allocator_Error) #optional_allocator_error {
|
||||
sanitize_sb := proc(sb: ^strings.Builder) {
|
||||
buf := sb.buf[:]
|
||||
b, l := raw_data(buf), len(buf)
|
||||
crypto.zero_explicit(b, l)
|
||||
strings.builder_destroy(sb)
|
||||
}
|
||||
|
||||
sb := strings.builder_make_none(allocator) or_return
|
||||
defer sanitize_sb(&sb)
|
||||
|
||||
label_len := len(label)
|
||||
|
||||
// Write `preeb`.
|
||||
n := strings.write_string(&sb, PREFIX_BEGIN)
|
||||
n += strings.write_string(&sb, label)
|
||||
n += strings.write_string(&sb, SUFFIX)
|
||||
n += strings.write_string(&sb, LF)
|
||||
if n != PREEB_OVERHEAD + label_len {
|
||||
return nil, .Out_Of_Memory
|
||||
}
|
||||
|
||||
// RFC 1421: Write header block.
|
||||
|
||||
// Write `base64text`.
|
||||
l: [BASE64_FULL_LINE_LENGTH]byte
|
||||
defer crypto.zero_explicit(&l, size_of(l))
|
||||
|
||||
d := data
|
||||
for len(d) > 0 {
|
||||
n = min(len(d), BASE64_FULL_LINE_BYTES)
|
||||
encoded, _ := base64.encode_into_buf(l[:], d[:n])
|
||||
d = d[n:]
|
||||
|
||||
expected_len := len(encoded) + len(LF)
|
||||
n = strings.write_bytes(&sb, encoded)
|
||||
n += strings.write_string(&sb, LF)
|
||||
if n != expected_len {
|
||||
return nil, .Out_Of_Memory
|
||||
}
|
||||
}
|
||||
|
||||
// Write `posteb`.
|
||||
expected_len := POSTEB_OVERHEAD + label_len + (len(LF) if newline else 0)
|
||||
n = strings.write_string(&sb, PREFIX_END)
|
||||
n += strings.write_string(&sb, label)
|
||||
n += strings.write_string(&sb, SUFFIX)
|
||||
if newline {
|
||||
n += strings.write_string(&sb, LF)
|
||||
}
|
||||
if n != expected_len {
|
||||
return nil, .Out_Of_Memory
|
||||
}
|
||||
|
||||
res = transmute([]byte)(strings.clone(strings.to_string(sb), allocator) or_return)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// block_bytes returns a slice to the Block's data.
|
||||
block_bytes :: proc(blk: ^Block) -> []byte {
|
||||
return blk.data[:]
|
||||
}
|
||||
|
||||
// block_delete frees a Block returned from decode.
|
||||
//
|
||||
// Note: No allocator is specified as decode uses the same allocator
|
||||
// for everything.
|
||||
block_delete :: proc(blk: ^Block) {
|
||||
allocator := ((^runtime.Raw_Dynamic_Array)(&blk.data)).allocator
|
||||
|
||||
delete(blk.label, allocator)
|
||||
sanitize_and_delete(blk.data)
|
||||
free(blk, allocator)
|
||||
}
|
||||
|
||||
@(private)
|
||||
get_line :: proc(data: []byte) -> (line, rest: []byte) {
|
||||
adv: int
|
||||
adv, line, _, _ = bufio.scan_lines(data, true)
|
||||
rest = data[adv:]
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
parse_eb :: proc(line: []byte, is_pre: bool) -> (label: string, found: bool, err: Error) {
|
||||
line := line
|
||||
|
||||
prefix: string
|
||||
switch is_pre {
|
||||
case true:
|
||||
prefix = PREFIX_BEGIN
|
||||
case false:
|
||||
prefix = PREFIX_END
|
||||
}
|
||||
|
||||
l := len(line)
|
||||
line = bytes.trim_prefix(line, transmute([]byte)(prefix))
|
||||
if len(line) == l {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
l = len(line)
|
||||
line = bytes.trim_suffix(line, transmute([]byte)(SUFFIX))
|
||||
if len(line) == l {
|
||||
return "", false, .Bad_Boundary
|
||||
}
|
||||
|
||||
// labelchar = %x21-2C / %x2E-7E ; any printable character,
|
||||
// ; except hyphen-minus
|
||||
// label = [ labelchar *( ["-" / SP] labelchar ) ] ; empty ok
|
||||
l = len(line)
|
||||
line = bytes.trim(line, []byte{'-', ' '})
|
||||
if len(line) != l {
|
||||
return "", false, .Bad_Label
|
||||
}
|
||||
for b in line {
|
||||
// We already ruled out non-labelchar start/end, so this
|
||||
// allows ' '/'-'.
|
||||
if b < 0x20 || b > 0x7e {
|
||||
return "", false, .Bad_Label
|
||||
}
|
||||
}
|
||||
|
||||
found = true
|
||||
label = transmute(string)(line)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
sanitize_and_delete :: proc(data: [dynamic]byte) {
|
||||
b, l := raw_data(data), len(data)
|
||||
crypto.zero_explicit(b, l)
|
||||
|
||||
delete(data)
|
||||
}
|
||||
@@ -11,13 +11,21 @@ Command-Line Syntax:
|
||||
Arguments are treated differently depending on how they're formatted.
|
||||
The format is similar to the Odin binary's way of handling compiler flags.
|
||||
|
||||
type handling
|
||||
------------ ------------------------
|
||||
<positional> depends on struct layout
|
||||
-<flag> set a bool true
|
||||
-<flag:option> set flag to option
|
||||
-<flag=option> set flag to option, alternative syntax
|
||||
-<map>:<key>=<value> set map[key] to value
|
||||
type handling
|
||||
------------ ------------------------
|
||||
<positional> depends on struct layout
|
||||
-<flag> set a bool true
|
||||
-<flag:option> set flag to option
|
||||
-<flag=option> set flag to option, alternative syntax
|
||||
-<flag:option1,opt...> set bit_set flag to one or more options
|
||||
-<flag=option1,opt...> set bit_set flag to one or more options, alternative syntax
|
||||
-<map>:<key>=<value> set map[key] to value
|
||||
|
||||
Underscores (`_`) in a flag will be replaced with dashes (`-`).
|
||||
|
||||
Bit sets may be set with a strictly comma-separated list of options.
|
||||
|
||||
Bit sets may also be set with a binary string of 0s and 1s, and will be parsed from left to right, from least significant bit to most significant bit. Underscores are allowed and will be ignored. However, starting with an underscore is disallowed.
|
||||
|
||||
|
||||
Unhandled Arguments:
|
||||
|
||||
@@ -20,6 +20,14 @@ Optimization_Level :: enum {
|
||||
Ludicrous_Speed,
|
||||
}
|
||||
|
||||
Vet_Flag :: enum {
|
||||
Unused,
|
||||
Unused_Variables,
|
||||
Unused_Imports,
|
||||
Shadowing,
|
||||
Using_Stmt,
|
||||
}
|
||||
|
||||
// It's simple but powerful.
|
||||
my_custom_type_setter :: proc(
|
||||
data: rawptr,
|
||||
@@ -83,6 +91,8 @@ main :: proc() {
|
||||
schedule: datetime.DateTime `usage:"Launch tasks at this time."`,
|
||||
|
||||
opt: Optimization_Level `usage:"Optimization level."`,
|
||||
vet_flags: bit_set[Vet_Flag] `usage:"Vet flags. Example usage:
|
||||
-vet-flags:Unused,Shadowing"`,
|
||||
todo: [dynamic]string `usage:"Todo items."`,
|
||||
|
||||
accuracy: Fixed_Point1_1 `args:"required" usage:"Lenience in FLOP calculations."`,
|
||||
|
||||
@@ -110,7 +110,7 @@ parse_and_set_pointer_by_base_type :: proc(ptr: rawptr, str: string, type_info:
|
||||
case f32be: (^f32be)(ptr)^ = cast(f32be) value
|
||||
case f64be: (^f64be)(ptr)^ = cast(f64be) value
|
||||
}
|
||||
|
||||
|
||||
case runtime.Type_Info_Complex:
|
||||
value := strconv.parse_complex128(str) or_return
|
||||
switch type_info.id {
|
||||
@@ -118,7 +118,7 @@ parse_and_set_pointer_by_base_type :: proc(ptr: rawptr, str: string, type_info:
|
||||
case complex64: (^complex64) (ptr)^ = (complex64)(value)
|
||||
case complex128: (^complex128)(ptr)^ = value
|
||||
}
|
||||
|
||||
|
||||
case runtime.Type_Info_Quaternion:
|
||||
value := strconv.parse_quaternion256(str) or_return
|
||||
switch type_info.id {
|
||||
@@ -152,15 +152,16 @@ parse_and_set_pointer_by_base_type :: proc(ptr: rawptr, str: string, type_info:
|
||||
}
|
||||
|
||||
case runtime.Type_Info_Bit_Set:
|
||||
// Parse a string of 1's and 0's, from left to right,
|
||||
// Parse a string of 1s and 0s, from left to right,
|
||||
// least significant bit to most significant bit.
|
||||
value: u128
|
||||
|
||||
// NOTE: `upper` is inclusive, i.e: `0..=31`
|
||||
max_bit_index := u128(1 + specific_type_info.upper - specific_type_info.lower)
|
||||
underscores := u128(strings.count(str, "_"))
|
||||
bit_index_limit := u128(1 + specific_type_info.upper - specific_type_info.lower) + underscores
|
||||
bit_index := u128(0)
|
||||
#no_bounds_check for string_index in 0..<uint(len(str)) {
|
||||
if bit_index == max_bit_index {
|
||||
if bit_index == bit_index_limit {
|
||||
// The string's too long for this bit_set.
|
||||
return false
|
||||
}
|
||||
@@ -421,7 +422,7 @@ parse_and_set_pointer_by_type :: proc(ptr: rawptr, str: string, type_info: ^runt
|
||||
}
|
||||
} else {
|
||||
parse_and_set_pointer_by_named_type(ptr, str, type_info.id, arg_tag, &error)
|
||||
|
||||
|
||||
if error != nil {
|
||||
// So far, it's none of the types that we recognize.
|
||||
// Check to see if we can set it by base type, if allowed.
|
||||
@@ -471,6 +472,70 @@ parse_and_set_pointer_by_type :: proc(ptr: rawptr, str: string, type_info: ^runt
|
||||
}
|
||||
}
|
||||
|
||||
case runtime.Type_Info_Bit_Set:
|
||||
if str[0] == '0' || str[0] == '1' {
|
||||
if !parse_and_set_pointer_by_base_type(ptr, str, type_info) {
|
||||
return Parse_Error {
|
||||
// The caller will add more details.
|
||||
.Bad_Value,
|
||||
"",
|
||||
}
|
||||
} else {
|
||||
error = nil
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
value: u128
|
||||
|
||||
et := runtime.type_info_base(specific_type_info.elem)
|
||||
if enum_type_info, is_enum := et.variant.(runtime.Type_Info_Enum); is_enum {
|
||||
names, _ := strings.split(str, ",", context.temp_allocator)
|
||||
valid_names := enum_type_info.names
|
||||
underlying_values := enum_type_info.values
|
||||
|
||||
#no_bounds_check outer_loop: for name in names {
|
||||
found: bool
|
||||
#no_bounds_check for valid_name, index in valid_names {
|
||||
if name == valid_name {
|
||||
shift := u128(underlying_values[index]) - u128(specific_type_info.lower)
|
||||
value |= u128(1 << shift)
|
||||
found = true
|
||||
continue outer_loop
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return Parse_Error {
|
||||
.Bad_Value,
|
||||
fmt.tprintf(
|
||||
"Invalid value name: `%s`. Valid names are: %s",
|
||||
name,
|
||||
valid_names,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Parse_Error {
|
||||
// The caller will add more details.
|
||||
.Bad_Value,
|
||||
"",
|
||||
}
|
||||
}
|
||||
|
||||
if specific_type_info.underlying != nil {
|
||||
set_unbounded_integer_by_type(ptr, value, specific_type_info.underlying.id)
|
||||
} else {
|
||||
switch 8*type_info.size {
|
||||
case 8: (^u8) (ptr)^ = cast(u8) value
|
||||
case 16: (^u16) (ptr)^ = cast(u16) value
|
||||
case 32: (^u32) (ptr)^ = cast(u32) value
|
||||
case 64: (^u64) (ptr)^ = cast(u64) value
|
||||
case 128: (^u128)(ptr)^ = value
|
||||
}
|
||||
}
|
||||
error = nil
|
||||
|
||||
case:
|
||||
if type_info.id == ^os.File {
|
||||
parse_and_set_pointer_by_named_type(ptr, str, type_info.id, arg_tag, &error)
|
||||
|
||||
@@ -1456,9 +1456,11 @@ fmt_float :: proc(fi: ^Info, v: f64, bit_size: int, verb: rune) {
|
||||
prev_fi := fi^
|
||||
defer fi^ = prev_fi
|
||||
fi.hash = false
|
||||
fi.width = bit_size
|
||||
fi.zero = true
|
||||
fi.plus = false
|
||||
// force the width to always be bit_size/4 to accurately represent the number
|
||||
fi.width = bit_size/4
|
||||
fi.width_set = true
|
||||
|
||||
u: u64
|
||||
switch bit_size {
|
||||
|
||||
@@ -10,7 +10,7 @@ read_ptr :: proc(r: Reader, p: rawptr, byte_size: int, n_read: ^int = nil) -> (n
|
||||
|
||||
read_slice :: proc(r: Reader, slice: $S/[]$T, n_read: ^int = nil) -> (n: int, err: Error) {
|
||||
size := len(slice)*size_of(T)
|
||||
return read_ptr(w, raw_data(slice), size, n_read)
|
||||
return read_ptr(r, raw_data(slice), size, n_read)
|
||||
}
|
||||
|
||||
write_ptr :: proc(w: Writer, p: rawptr, byte_size: int, n_written: ^int = nil) -> (n: int, err: Error) {
|
||||
|
||||
68
core/log/doc.odin
Normal file
68
core/log/doc.odin
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
Implementation of logging facilities.
|
||||
|
||||
Odin has builtin support for logging using procedure `context`. After a logger is created it can then be assigned to
|
||||
`context.logger` and used implicitly in future log calls.
|
||||
|
||||
While it is ok for simple apps to use the `core:fmt` package, libraries and complex apps should prefer the `core:log`
|
||||
package. By using the implicit logger library and application authors allow the caller to decide how to process log
|
||||
messages.
|
||||
|
||||
When starting out you can easily just init the logger with a single line.
|
||||
Example:
|
||||
|
||||
package main
|
||||
|
||||
import "core:log"
|
||||
|
||||
main :: proc() {
|
||||
context.logger = log.create_console_logger()
|
||||
log.info("Hello World!")
|
||||
}
|
||||
|
||||
However when the application gets more involved you might want to try a more complex setup.
|
||||
Example:
|
||||
|
||||
package main
|
||||
|
||||
import "core:log"
|
||||
import "core:os"
|
||||
|
||||
main :: proc() {
|
||||
handle, err := os.open("logs.txt", os.O_RDWR | os.O_APPEND | os.O_CREATE, 0o666)
|
||||
assert(err == nil, "Cannot open log file")
|
||||
|
||||
file_logger := log.create_file_logger(handle)
|
||||
// This closes the file handle
|
||||
defer log.destroy_file_logger(file_logger)
|
||||
|
||||
console_logger := log.create_console_logger()
|
||||
defer log.destroy_console_logger(console_logger)
|
||||
|
||||
multi_logger := log.create_multi_logger(console_logger, file_logger)
|
||||
defer log.destroy_multi_logger(multi_logger)
|
||||
|
||||
context.logger = multi_logger
|
||||
|
||||
log.info("Application started!")
|
||||
}
|
||||
|
||||
It is also possible to create an allocator that logs all allocations.
|
||||
Example:
|
||||
|
||||
package main
|
||||
|
||||
import "core:log"
|
||||
|
||||
main :: proc() {
|
||||
context.logger = log.create_console_logger()
|
||||
|
||||
alloc: log.Log_Allocator
|
||||
log.log_allocator_init(&alloc, .Debug)
|
||||
context.allocator = log.log_allocator(&alloc)
|
||||
|
||||
a := new(i32)
|
||||
free(a)
|
||||
}
|
||||
*/
|
||||
package log
|
||||
@@ -11,6 +11,7 @@ import "core:terminal"
|
||||
import "core:terminal/ansi"
|
||||
import "core:time"
|
||||
|
||||
// Strings to output when `.Level` is included in the logger options.
|
||||
Level_Headers := [?]string{
|
||||
0..<10 = "[DEBUG] --- ",
|
||||
10..<20 = "[INFO ] --- ",
|
||||
@@ -19,22 +20,49 @@ Level_Headers := [?]string{
|
||||
40..<50 = "[FATAL] --- ",
|
||||
}
|
||||
|
||||
/*
|
||||
The default option set for a console logger.
|
||||
|
||||
It is similar to the file logger default option set, but the output includes colors.
|
||||
|
||||
When you use this set of options you can expect the following output:
|
||||
|
||||
[LEVEL] --- [YYYY-MM-DD HH:MM:SS] [file.odin:L:proc()] Message
|
||||
|
||||
For example:
|
||||
|
||||
[INFO ] --- [2025-01-02 12:34:56] [main.odin:8:main()] Hello World!
|
||||
*/
|
||||
Default_Console_Logger_Opts :: Options{
|
||||
.Level,
|
||||
.Terminal_Color,
|
||||
.Short_File_Path,
|
||||
.Line,
|
||||
.Procedure,
|
||||
} | Full_Timestamp_Opts
|
||||
} + Full_Timestamp_Opts
|
||||
|
||||
/*
|
||||
The default option set for a file logger.
|
||||
|
||||
It is similar to the console logger default option set, but the output is not colored.
|
||||
|
||||
When you use this set of options you can expect the following output:
|
||||
|
||||
[LEVEL] --- [YYYY-MM-DD HH:MM:SS] [file.odin:L:proc()] Message
|
||||
|
||||
For example:
|
||||
|
||||
[INFO ] --- [2025-01-02 12:34:56] [main.odin:8:main()] Hello World!
|
||||
*/
|
||||
Default_File_Logger_Opts :: Options{
|
||||
.Level,
|
||||
.Short_File_Path,
|
||||
.Line,
|
||||
.Procedure,
|
||||
} | Full_Timestamp_Opts
|
||||
} + Full_Timestamp_Opts
|
||||
|
||||
|
||||
//Data backing a file or console logger.
|
||||
File_Console_Logger_Data :: struct {
|
||||
file_handle: ^os.File,
|
||||
ident: string,
|
||||
@@ -67,6 +95,21 @@ init_standard_stream_status :: proc "contextless" () {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Create a logger that outputs to a file.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
When no longer needed can be destroyed with `destroy_file_logger`.
|
||||
|
||||
Inputs:
|
||||
- `h`: A handle to the output file
|
||||
- `lowest`: Log level to use (default is `.Debug`)
|
||||
- `opt`: Specifies additional data present in the log output (default is `log.Default_File_Logger_Opts`)
|
||||
- `ident`: Identifier to include in the output (default is `""`)
|
||||
- `allocator`: Allocator to use for data backing the logger (default is `context.allocator`)
|
||||
*/
|
||||
create_file_logger :: proc(f: ^os.File, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger {
|
||||
data := new(File_Console_Logger_Data, allocator)
|
||||
data.file_handle = f
|
||||
@@ -74,6 +117,13 @@ create_file_logger :: proc(f: ^os.File, lowest := Level.Debug, opt := Default_Fi
|
||||
return Logger{file_logger_proc, data, lowest, opt}
|
||||
}
|
||||
|
||||
/*
|
||||
Free the state allocated with `create_file_logger` and close the file handle.
|
||||
|
||||
Inputs:
|
||||
- `log`: Logger created with `create_file_logger`
|
||||
- `allocator`: Allocator passed to `create_file_logger` (default is `context.allocator`)
|
||||
*/
|
||||
destroy_file_logger :: proc(log: Logger, allocator := context.allocator) {
|
||||
data := cast(^File_Console_Logger_Data)log.data
|
||||
if data.file_handle != nil {
|
||||
@@ -82,6 +132,19 @@ destroy_file_logger :: proc(log: Logger, allocator := context.allocator) {
|
||||
free(data, allocator)
|
||||
}
|
||||
|
||||
/*
|
||||
Create a logger that outputs to the terminal.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
When no longer needed can be destroyed with `destroy_console_logger`.
|
||||
|
||||
Inputs:
|
||||
- `lowest`: Log level to use (default is `.Debug`)
|
||||
- `opt`: Specifies additional data present in the log output (default is `log.Default_Console_Logger_Opts`)
|
||||
- `ident`: Identifier to include in the output (default is `""`)
|
||||
- `allocator`: Allocator to use for data backing the logger (default is `context.allocator`)
|
||||
*/
|
||||
create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logger_Opts, ident := "", allocator := context.allocator) -> Logger {
|
||||
data := new(File_Console_Logger_Data, allocator)
|
||||
data.file_handle = nil
|
||||
@@ -89,6 +152,13 @@ create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logg
|
||||
return Logger{console_logger_proc, data, lowest, opt}
|
||||
}
|
||||
|
||||
/*
|
||||
Free the state allocated with `create_console_logger`.
|
||||
|
||||
Inputs:
|
||||
- `log`: Logger created with `create_console_logger`
|
||||
- `allocator`: Allocator passed to `create_console_logger` (default is `context.allocator`)
|
||||
*/
|
||||
destroy_console_logger :: proc(log: Logger, allocator := context.allocator) {
|
||||
free(log.data, allocator)
|
||||
}
|
||||
@@ -117,6 +187,7 @@ _file_console_logger_proc :: proc(h: ^os.File, ident: string, level: Level, text
|
||||
fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text)
|
||||
}
|
||||
|
||||
|
||||
file_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
|
||||
data := cast(^File_Console_Logger_Data)logger_data
|
||||
_file_console_logger_proc(data.file_handle, data.ident, level, text, options, location)
|
||||
@@ -136,6 +207,7 @@ console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, opt
|
||||
_file_console_logger_proc(h, data.ident, level, text, options, location)
|
||||
}
|
||||
|
||||
// Helper used to build the part of the message including the log level.
|
||||
do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) {
|
||||
|
||||
RESET :: ansi.CSI + ansi.RESET + ansi.SGR
|
||||
@@ -162,6 +234,7 @@ do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper used to build the part of the message including the data and time.
|
||||
do_time_header :: proc(opts: Options, buf: ^strings.Builder, t: time.Time) {
|
||||
when time.IS_SUPPORTED {
|
||||
if Full_Timestamp_Opts & opts != nil {
|
||||
@@ -180,6 +253,7 @@ do_time_header :: proc(opts: Options, buf: ^strings.Builder, t: time.Time) {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper used to build the part of the message including the file location.
|
||||
do_location_header :: proc(opts: Options, buf: ^strings.Builder, location := #caller_location) {
|
||||
if Location_Header_Opts & opts == nil {
|
||||
return
|
||||
|
||||
@@ -1,52 +1,102 @@
|
||||
// Implementations of the `context.Logger` interface.
|
||||
package log
|
||||
|
||||
import "base:runtime"
|
||||
import "core:fmt"
|
||||
|
||||
|
||||
// NOTE(bill, 2019-12-31): These are defined in `package runtime` as they are used in the `context`. This is to prevent an import definition cycle.
|
||||
//These are defined in package `base:runtime` as they are used in the `context`. This is to prevent an import definition cycle.
|
||||
|
||||
/*
|
||||
Logger_Level :: enum {
|
||||
Debug = 0,
|
||||
Info = 10,
|
||||
Warning = 20,
|
||||
Error = 30,
|
||||
Fatal = 40,
|
||||
}
|
||||
Logger_Level :: enum {
|
||||
Debug = 0,
|
||||
Info = 10,
|
||||
Warning = 20,
|
||||
Error = 30,
|
||||
Fatal = 40,
|
||||
}
|
||||
*/
|
||||
Level :: runtime.Logger_Level
|
||||
|
||||
/*
|
||||
Option :: enum {
|
||||
Level,
|
||||
Date,
|
||||
Time,
|
||||
Short_File_Path,
|
||||
Long_File_Path,
|
||||
Line,
|
||||
Procedure,
|
||||
Terminal_Color
|
||||
}
|
||||
Specifies additional data present in the log output.
|
||||
|
||||
Defined in `package runtime` as it is used in the `context`. This is to prevent an import definition cycle.
|
||||
|
||||
Option :: enum {
|
||||
// The log level, e.g. "[DEBUG] ---"
|
||||
Level,
|
||||
// The date, e.g. [2025-01-02]
|
||||
Date,
|
||||
// The time, e.g. [12:34:56]
|
||||
Time,
|
||||
// Just the filename, e.g. [main.odin]
|
||||
Short_File_Path,
|
||||
// Full file path, e.g. [/tmp/project/main.odin]
|
||||
Long_File_Path,
|
||||
// File line of the log statement, e.g. [8]
|
||||
Line,
|
||||
// Calling procedure, e.g. [main()]
|
||||
Procedure,
|
||||
// Enables colored output
|
||||
Terminal_Color
|
||||
}
|
||||
*/
|
||||
Option :: runtime.Logger_Option
|
||||
|
||||
/*
|
||||
Options :: bit_set[Option];
|
||||
Specifies additional data present in the log output.
|
||||
|
||||
Defined in `package runtime` as it is used in the `context`. This is to prevent an import definition cycle.
|
||||
|
||||
Options :: bit_set[Option];
|
||||
*/
|
||||
Options :: runtime.Logger_Options
|
||||
|
||||
/*
|
||||
A preset option set for a logger.
|
||||
|
||||
When you use this set of options you can expect the following output:
|
||||
|
||||
[YYYY-MM-DD HH:MM:SS] Message
|
||||
|
||||
For example:
|
||||
|
||||
[2025-01-02 12:34:56] Hello World!
|
||||
*/
|
||||
Full_Timestamp_Opts :: Options{
|
||||
.Date,
|
||||
.Time,
|
||||
}
|
||||
|
||||
/*
|
||||
A preset option set for a logger.
|
||||
|
||||
When you use this set of options you can expect the following output:
|
||||
|
||||
[file.odin:L:proc()] Message
|
||||
|
||||
For example:
|
||||
|
||||
[main.odin:8:main()] Hello World!
|
||||
*/
|
||||
Location_Header_Opts :: Options{
|
||||
.Short_File_Path,
|
||||
.Long_File_Path,
|
||||
.Line,
|
||||
.Procedure,
|
||||
}
|
||||
|
||||
/*
|
||||
A preset option set for a logger.
|
||||
|
||||
When you use this set of options you can expect the following output:
|
||||
|
||||
[file.odin] Message
|
||||
|
||||
For example:
|
||||
|
||||
[main.odin] Hello World!
|
||||
*/
|
||||
Location_File_Opts :: Options{
|
||||
.Short_File_Path,
|
||||
.Long_File_Path,
|
||||
@@ -54,67 +104,206 @@ Location_File_Opts :: Options{
|
||||
|
||||
|
||||
/*
|
||||
Logger_Proc :: #type proc(data: rawptr, level: Level, text: string, options: Options, location := #caller_location);
|
||||
Implementation of the logger.
|
||||
|
||||
Defined in `package runtime` as it is used in the `context`. This is to prevent an import definition cycle.
|
||||
|
||||
Logger_Proc :: #type proc(data: rawptr, level: Level, text: string, options: Options, location := #caller_location);
|
||||
|
||||
*/
|
||||
Logger_Proc :: runtime.Logger_Proc
|
||||
|
||||
/*
|
||||
Logger :: struct {
|
||||
procedure: Logger_Proc,
|
||||
data: rawptr,
|
||||
lowest_level: Level,
|
||||
options: Logger_Options,
|
||||
}
|
||||
Data backing the logger.
|
||||
|
||||
Defined in `package runtime` as it is used in the `context`. This is to prevent an import definition cycle.
|
||||
|
||||
Logger :: struct {
|
||||
// Implementation
|
||||
procedure: Logger_Proc,
|
||||
// Configuration data passed to the implementation
|
||||
data: rawptr,
|
||||
// Minimum level for messages passed to the implementation
|
||||
lowest_level: Level,
|
||||
// Additional data present in the log output
|
||||
options: Logger_Options,
|
||||
}
|
||||
*/
|
||||
Logger :: runtime.Logger
|
||||
|
||||
/*
|
||||
Do nothing.
|
||||
|
||||
Defined in `package runtime` as it is used in the `context`. This is to prevent an import definition cycle.
|
||||
*/
|
||||
nil_logger_proc :: runtime.default_logger_proc
|
||||
|
||||
/*
|
||||
Create a logger that does nothing.
|
||||
|
||||
Returns:
|
||||
- A logger that does nothing
|
||||
*/
|
||||
nil_logger :: proc() -> Logger {
|
||||
return Logger{nil_logger_proc, nil, Level.Debug, nil}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Log a formatted message at the `Debug` level.
|
||||
|
||||
Inputs:
|
||||
- `fmt_str`: A format string, e.g. `"a: %v, b: %v"
|
||||
- `args`: Arguments for the format string
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
debugf :: proc(fmt_str: string, args: ..any, location := #caller_location) {
|
||||
logf(.Debug, fmt_str, ..args, location=location)
|
||||
}
|
||||
|
||||
/*
|
||||
Log a formatted message at the `Info` level.
|
||||
|
||||
Inputs:
|
||||
- `fmt_str`: A format string, e.g. `"a: %v, b: %v"
|
||||
- `args`: Arguments for the format string
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
infof :: proc(fmt_str: string, args: ..any, location := #caller_location) {
|
||||
logf(.Info, fmt_str, ..args, location=location)
|
||||
}
|
||||
|
||||
/*
|
||||
Log a formatted message at the `Warn` level.
|
||||
|
||||
Inputs:
|
||||
- `fmt_str`: A format string, e.g. `"a: %v, b: %v"
|
||||
- `args`: Arguments for the format string
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
warnf :: proc(fmt_str: string, args: ..any, location := #caller_location) {
|
||||
logf(.Warning, fmt_str, ..args, location=location)
|
||||
}
|
||||
|
||||
/*
|
||||
Log a formatted message at the `Error` level.
|
||||
|
||||
Inputs:
|
||||
- `fmt_str`: A format string, e.g. `"a: %v, b: %v"
|
||||
- `args`: Arguments for the format string
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
errorf :: proc(fmt_str: string, args: ..any, location := #caller_location) {
|
||||
logf(.Error, fmt_str, ..args, location=location)
|
||||
}
|
||||
|
||||
/*
|
||||
Log a formatted message at the `Fatal` level.
|
||||
|
||||
Inputs:
|
||||
- `fmt_str`: A format string, e.g. `"a: %v, b: %v"
|
||||
- `args`: Arguments for the format string
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
fatalf :: proc(fmt_str: string, args: ..any, location := #caller_location) {
|
||||
logf(.Fatal, fmt_str, ..args, location=location)
|
||||
}
|
||||
|
||||
/*
|
||||
Log a message at the `Debug` level.
|
||||
|
||||
Inputs:
|
||||
- `args`: values to be concatenated into the output
|
||||
- `sep`: separator to use when concatenating (default is `" "`)
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
debug :: proc(args: ..any, sep := " ", location := #caller_location) {
|
||||
log(.Debug, ..args, sep=sep, location=location)
|
||||
}
|
||||
|
||||
/*
|
||||
Log a message at the `Info` level.
|
||||
|
||||
Inputs:
|
||||
- `args`: values to be concatenated into the output
|
||||
- `sep`: separator to use when concatenating (default is `" "`)
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
info :: proc(args: ..any, sep := " ", location := #caller_location) {
|
||||
log(.Info, ..args, sep=sep, location=location)
|
||||
}
|
||||
|
||||
/*
|
||||
Log a message at the `Warn` level.
|
||||
|
||||
Inputs:
|
||||
- `args`: values to be concatenated into the output
|
||||
- `sep`: separator to use when concatenating (default is `" "`)
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
warn :: proc(args: ..any, sep := " ", location := #caller_location) {
|
||||
log(.Warning, ..args, sep=sep, location=location)
|
||||
}
|
||||
|
||||
/*
|
||||
Log a message at the `Error` level.
|
||||
|
||||
Inputs:
|
||||
- `args`: values to be concatenated into the output
|
||||
- `sep`: separator to use when concatenating (default is `" "`)
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
error :: proc(args: ..any, sep := " ", location := #caller_location) {
|
||||
log(.Error, ..args, sep=sep, location=location)
|
||||
}
|
||||
|
||||
/*
|
||||
Log a message at the `Fatal` level.
|
||||
|
||||
Inputs:
|
||||
- `args`: values to be concatenated into the output
|
||||
- `sep`: separator to use when concatenating (default is `" "`)
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
fatal :: proc(args: ..any, sep := " ", location := #caller_location) {
|
||||
log(.Fatal, ..args, sep=sep, location=location)
|
||||
}
|
||||
|
||||
/*
|
||||
Log a message at the `Fatal` level and abort the program.
|
||||
|
||||
Inputs:
|
||||
- `args`: values to be concatenated into the output
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
panic :: proc(args: ..any, location := #caller_location) -> ! {
|
||||
log(.Fatal, ..args, location=location)
|
||||
runtime.panic("log.panic", location)
|
||||
}
|
||||
|
||||
/*
|
||||
Log a formatted message at the `Fatal` level and abort the program.
|
||||
|
||||
Inputs:
|
||||
- `fmt_str`: A format string, e.g. `"a: %v, b: %v"
|
||||
- `args`: Arguments for the format string
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
panicf :: proc(fmt_str: string, args: ..any, location := #caller_location) -> ! {
|
||||
logf(.Fatal, fmt_str, ..args, location=location)
|
||||
runtime.panic("log.panicf", location)
|
||||
}
|
||||
|
||||
/*
|
||||
When condition is `false` log a message at the `Fatal` level and abort the program.
|
||||
|
||||
Can be disabled using `ODIN_DISABLE_ASSERT`.
|
||||
|
||||
Inputs:
|
||||
- `condition`: A boolean to check
|
||||
- `message`: Message to log when condition is false (a default is provided)
|
||||
- `loc`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
@(disabled=ODIN_DISABLE_ASSERT)
|
||||
assert :: proc(condition: bool, message := #caller_expression(condition), loc := #caller_location) {
|
||||
if !condition {
|
||||
@@ -131,6 +320,17 @@ assert :: proc(condition: bool, message := #caller_expression(condition), loc :=
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
When condition is `false` log a formatted message at the `Fatal` level and abort the program.
|
||||
|
||||
Can be disabled using `ODIN_DISABLE_ASSERT`.
|
||||
|
||||
Inputs:
|
||||
- `condition`: A boolean to check
|
||||
- `fmt_str`: A format string to use when condition is false, e.g. `"a: %v, b: %v"
|
||||
- `args`: Arguments for the format string
|
||||
- `loc`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
@(disabled=ODIN_DISABLE_ASSERT)
|
||||
assertf :: proc(condition: bool, fmt_str: string, args: ..any, loc := #caller_location) {
|
||||
if !condition {
|
||||
@@ -152,6 +352,16 @@ assertf :: proc(condition: bool, fmt_str: string, args: ..any, loc := #caller_lo
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
When condition is `false` log a message at the `Fatal` level and abort the program.
|
||||
|
||||
Unlike `assert` this procedure cannot be disabled with `ODIN_DISABLE_ASSERT` and will always execute.
|
||||
|
||||
Inputs:
|
||||
- `condition`: A boolean to check
|
||||
- `message`: Message to log when condition is false (a default is provided)
|
||||
- `loc`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
ensure :: proc(condition: bool, message := #caller_expression(condition), loc := #caller_location) {
|
||||
if !condition {
|
||||
@(cold)
|
||||
@@ -167,6 +377,17 @@ ensure :: proc(condition: bool, message := #caller_expression(condition), loc :=
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
When condition is `false` log a formatted message at the `Fatal` level and abort the program.
|
||||
|
||||
Unlike `assertf` this procedure cannot be disabled with `ODIN_DISABLE_ASSERT` and will always execute.
|
||||
|
||||
Inputs:
|
||||
- `condition`: A boolean to check
|
||||
- `fmt_str`: A format string to use when condition is false, e.g. `"a: %v, b: %v"
|
||||
- `args`: Arguments for the format string
|
||||
- `loc`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
ensuref :: proc(condition: bool, fmt_str: string, args: ..any, loc := #caller_location) {
|
||||
if !condition {
|
||||
@(cold)
|
||||
@@ -184,7 +405,15 @@ ensuref :: proc(condition: bool, fmt_str: string, args: ..any, loc := #caller_lo
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Log a message at the desired level.
|
||||
|
||||
Inputs:
|
||||
- `level`: The level of the message
|
||||
- `args`: values to be concatenated into the output
|
||||
- `sep`: separator to use when concatenating (default is `" "`)
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
log :: proc(level: Level, args: ..any, sep := " ", location := #caller_location) {
|
||||
logger := context.logger
|
||||
if logger.procedure == nil || logger.procedure == nil_logger_proc {
|
||||
@@ -198,6 +427,15 @@ log :: proc(level: Level, args: ..any, sep := " ", location := #caller_location)
|
||||
logger.procedure(logger.data, level, str, logger.options, location)
|
||||
}
|
||||
|
||||
/*
|
||||
Log a formatted message at the desired level.
|
||||
|
||||
Inputs:
|
||||
- `level`: The level of the message
|
||||
- `fmt_str`: A format string, e.g. `"a: %v, b: %v"
|
||||
- `args`: Arguments for the format string
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
logf :: proc(level: Level, fmt_str: string, args: ..any, location := #caller_location) {
|
||||
logger := context.logger
|
||||
if logger.procedure == nil || logger.procedure == nil_logger_proc {
|
||||
|
||||
@@ -6,6 +6,7 @@ import "base:runtime"
|
||||
import "core:sync"
|
||||
|
||||
// Format to use when logging allocations.
|
||||
Log_Allocator_Format :: enum {
|
||||
Bytes, // Actual number of bytes.
|
||||
Human, // Bytes in human units like bytes, kibibytes, etc. as appropriate.
|
||||
}
|
||||
@@ -15,13 +16,23 @@ Log_Allocator_Format :: enum {
|
||||
// The format can be changed by setting the `size_fmt: Log_Allocator_Format` field to either `Bytes` or `Human`.
|
||||
Log_Allocator :: struct {
|
||||
allocator: runtime.Allocator, // Wrapped allocator
|
||||
level: Level,
|
||||
prefix: string,
|
||||
lock: sync.Mutex,
|
||||
level: Level, // Log Level used for allocations
|
||||
prefix: string, // Prefix to use in log messages
|
||||
lock: sync.Mutex,
|
||||
size_fmt: Log_Allocator_Format, // Format to use when logging allocations
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Initialize the backing data for the allocator that logs all allocations.
|
||||
|
||||
Inputs:
|
||||
- `la`: Pointer to the data structure to initialize
|
||||
- `level`: Log level to use for allocations
|
||||
- `size_fmt`: Format to use when logging allocations (default is `.Bytes`)
|
||||
- `allocator`: Wrapped allocator (default is `context.allocator`)
|
||||
- `prefix`: Prefix to use in log messages (default is `""`)
|
||||
*/
|
||||
log_allocator_init :: proc(la: ^Log_Allocator, level: Level, size_fmt := Log_Allocator_Format.Bytes,
|
||||
allocator := context.allocator, prefix := "") {
|
||||
la.allocator = allocator
|
||||
la.level = level
|
||||
@@ -31,7 +42,15 @@ log_allocator_init :: proc(la: ^Log_Allocator, level: Level, size_fmt := Log_All
|
||||
}
|
||||
|
||||
/*
|
||||
Create an allocator that logs all allocations.
|
||||
|
||||
Inputs:
|
||||
- `la`: Pointer to the data structure backing the allocator
|
||||
|
||||
Returns:
|
||||
- An allocator that logs all allocations
|
||||
*/
|
||||
log_allocator :: proc(la: ^Log_Allocator) -> runtime.Allocator {
|
||||
return runtime.Allocator{
|
||||
procedure = log_allocator_proc,
|
||||
data = la,
|
||||
@@ -39,6 +58,7 @@ log_allocator :: proc(la: ^Log_Allocator) -> runtime.Allocator {
|
||||
}
|
||||
|
||||
// Backing procedure for allocator that logs all allocations.
|
||||
log_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode,
|
||||
size, alignment: int,
|
||||
old_memory: rawptr, old_size: int, location := #caller_location) -> ([]byte, runtime.Allocator_Error) {
|
||||
la := (^Log_Allocator)(allocator_data)
|
||||
|
||||
@@ -1,10 +1,27 @@
|
||||
package log
|
||||
|
||||
|
||||
// A container backing for multiple loggers.
|
||||
Multi_Logger_Data :: struct {
|
||||
loggers: []Logger,
|
||||
}
|
||||
|
||||
/*
|
||||
Create a logger that logs to all backing loggers.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
When no longer needed can be destroyed with `destroy_multi_logger`.
|
||||
|
||||
Note: Logs using a multi logger take both the multi logger and the backing loggers' log levels into account.
|
||||
|
||||
Inputs:
|
||||
- `logs` - Backing loggers passed as multiple arguments
|
||||
- `allocator` - An allocator used to allocate data to store backing loggers (default is `context.allocator`)
|
||||
|
||||
Returns:
|
||||
- A multi logger
|
||||
*/
|
||||
create_multi_logger :: proc(logs: ..Logger, allocator := context.allocator) -> Logger {
|
||||
data := new(Multi_Logger_Data, allocator)
|
||||
data.loggers = make([]Logger, len(logs), allocator)
|
||||
@@ -12,12 +29,20 @@ create_multi_logger :: proc(logs: ..Logger, allocator := context.allocator) -> L
|
||||
return Logger{multi_logger_proc, data, Level.Debug, nil}
|
||||
}
|
||||
|
||||
/*
|
||||
Free the state allocated with `create_multi_logger`.
|
||||
|
||||
Inputs:
|
||||
- `log`: Logger created with `create_multi_logger`
|
||||
- `allocator`: Allocator passed to `create_multi_logger` (default is `context.allocator`)
|
||||
*/
|
||||
destroy_multi_logger :: proc(log: Logger, allocator := context.allocator) {
|
||||
data := (^Multi_Logger_Data)(log.data)
|
||||
delete(data.loggers, allocator)
|
||||
free(data, allocator)
|
||||
}
|
||||
|
||||
// Backing procedure for the multi logger.
|
||||
multi_logger_proc :: proc(logger_data: rawptr, level: Level, text: string,
|
||||
options: Options, location := #caller_location) {
|
||||
data := cast(^Multi_Logger_Data)logger_data
|
||||
|
||||
@@ -7,7 +7,7 @@ import "core:math"
|
||||
@(require_results)
|
||||
to_radians :: proc "contextless" (degrees: $T) -> (out: T) where IS_NUMERIC(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = degrees[i] * RAD_PER_DEG
|
||||
}
|
||||
} else {
|
||||
@@ -19,7 +19,7 @@ to_radians :: proc "contextless" (degrees: $T) -> (out: T) where IS_NUMERIC(ELEM
|
||||
@(require_results)
|
||||
to_degrees :: proc "contextless" (radians: $T) -> (out: T) where IS_NUMERIC(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = radians[i] * DEG_PER_RAD
|
||||
}
|
||||
} else {
|
||||
@@ -31,7 +31,7 @@ to_degrees :: proc "contextless" (radians: $T) -> (out: T) where IS_NUMERIC(ELEM
|
||||
@(require_results)
|
||||
min_double :: proc "contextless" (a, b: $T) -> (out: T) where IS_NUMERIC(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = builtin.min(a[i], b[i])
|
||||
}
|
||||
} else {
|
||||
@@ -51,7 +51,7 @@ min_single :: proc "contextless" (a: $T) -> (out: ELEM_TYPE(T)) where IS_NUMERIC
|
||||
out = builtin.min(a[0], a[1])
|
||||
} else {
|
||||
out = builtin.min(a[0], a[1])
|
||||
for i in 2..<N {
|
||||
#no_bounds_check for i in 2..<N {
|
||||
out = builtin.min(out, a[i])
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ min :: proc{min_single, min_double, min_triple}
|
||||
@(require_results)
|
||||
max_double :: proc "contextless" (a, b: $T) -> (out: T) where IS_NUMERIC(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = builtin.max(a[i], b[i])
|
||||
}
|
||||
} else {
|
||||
@@ -93,7 +93,7 @@ max_single :: proc "contextless" (a: $T) -> (out: ELEM_TYPE(T)) where IS_NUMERIC
|
||||
out = builtin.max(a[0], a[1], a[2])
|
||||
}else {
|
||||
out = builtin.max(a[0], a[1])
|
||||
for i in 2..<N {
|
||||
#no_bounds_check for i in 2..<N {
|
||||
out = builtin.max(out, a[i])
|
||||
}
|
||||
}
|
||||
@@ -113,7 +113,7 @@ max :: proc{max_single, max_double, max_triple}
|
||||
@(require_results)
|
||||
abs :: proc "contextless" (a: $T) -> (out: T) where IS_NUMERIC(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = auto_cast builtin.abs(a[i])
|
||||
}
|
||||
} else {
|
||||
@@ -125,7 +125,7 @@ abs :: proc "contextless" (a: $T) -> (out: T) where IS_NUMERIC(ELEM_TYPE(T)) {
|
||||
@(require_results)
|
||||
sign :: proc "contextless" (a: $T) -> (out: T) where IS_NUMERIC(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = #force_inline math.sign(a[i])
|
||||
}
|
||||
} else {
|
||||
@@ -137,7 +137,7 @@ sign :: proc "contextless" (a: $T) -> (out: T) where IS_NUMERIC(ELEM_TYPE(T)) {
|
||||
@(require_results)
|
||||
clamp :: proc "contextless" (x, a, b: $T) -> (out: T) where IS_NUMERIC(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = builtin.clamp(x[i], a[i], b[i])
|
||||
}
|
||||
} else {
|
||||
@@ -155,7 +155,7 @@ saturate :: proc "contextless" (x: $T) -> T where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
@(require_results)
|
||||
lerp :: proc "contextless" (a, b, t: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = a[i]*(1-t[i]) + b[i]*t[i]
|
||||
}
|
||||
} else {
|
||||
@@ -166,7 +166,7 @@ lerp :: proc "contextless" (a, b, t: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)
|
||||
@(require_results)
|
||||
mix :: proc "contextless" (a, b, t: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = a[i]*(1-t[i]) + b[i]*t[i]
|
||||
}
|
||||
} else {
|
||||
@@ -183,7 +183,7 @@ unlerp :: proc "contextless" (a, b, x: $T) -> T where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
@(require_results)
|
||||
step :: proc "contextless" (e, x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = x[i] < e[i] ? 0.0 : 1.0
|
||||
}
|
||||
} else {
|
||||
@@ -208,7 +208,7 @@ smootherstep :: proc "contextless" (e0, e1, x: $T) -> T where IS_FLOAT(ELEM_TYPE
|
||||
@(require_results)
|
||||
sqrt :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = math.sqrt(x[i])
|
||||
}
|
||||
} else {
|
||||
@@ -220,7 +220,7 @@ sqrt :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
@(require_results)
|
||||
inverse_sqrt :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = 1.0/math.sqrt(x[i])
|
||||
}
|
||||
} else {
|
||||
@@ -232,7 +232,7 @@ inverse_sqrt :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(
|
||||
@(require_results)
|
||||
cos :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = math.cos(x[i])
|
||||
}
|
||||
} else {
|
||||
@@ -244,7 +244,7 @@ cos :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
@(require_results)
|
||||
sin :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = math.sin(x[i])
|
||||
}
|
||||
} else {
|
||||
@@ -256,7 +256,7 @@ sin :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
@(require_results)
|
||||
tan :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = math.tan(x[i])
|
||||
}
|
||||
} else {
|
||||
@@ -268,7 +268,7 @@ tan :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
@(require_results)
|
||||
acos :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = math.acos(x[i])
|
||||
}
|
||||
} else {
|
||||
@@ -280,7 +280,7 @@ acos :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
@(require_results)
|
||||
asin :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = math.asin(x[i])
|
||||
}
|
||||
} else {
|
||||
@@ -292,7 +292,7 @@ asin :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
@(require_results)
|
||||
atan :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = math.atan(x[i])
|
||||
}
|
||||
} else {
|
||||
@@ -303,7 +303,7 @@ atan :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
@(require_results)
|
||||
atan2 :: proc "contextless" (y, x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = math.atan2(y[i], x[i])
|
||||
}
|
||||
} else {
|
||||
@@ -316,7 +316,7 @@ atan2 :: proc "contextless" (y, x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T))
|
||||
@(require_results)
|
||||
ln :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = math.ln(x[i])
|
||||
}
|
||||
} else {
|
||||
@@ -329,7 +329,7 @@ ln :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
log2 :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
INVLN2 :: 1.4426950408889634073599246810018921374266459541529859341354494069
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = INVLN2 * math.ln(x[i])
|
||||
}
|
||||
} else {
|
||||
@@ -342,7 +342,7 @@ log2 :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
log10 :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
INVLN10 :: 0.4342944819032518276511289189166050822943970058036665661144537831
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = INVLN10 * math.ln(x[i])
|
||||
}
|
||||
} else {
|
||||
@@ -354,7 +354,7 @@ log10 :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
@(require_results)
|
||||
log :: proc "contextless" (x, b: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = math.ln(x[i]) / math.ln(cast(ELEM_TYPE(T))b[i])
|
||||
}
|
||||
} else {
|
||||
@@ -366,7 +366,7 @@ log :: proc "contextless" (x, b: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
@(require_results)
|
||||
exp :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = math.exp(x[i])
|
||||
}
|
||||
} else {
|
||||
@@ -378,7 +378,7 @@ exp :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
@(require_results)
|
||||
exp2 :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = math.exp(LN2 * x[i])
|
||||
}
|
||||
} else {
|
||||
@@ -390,7 +390,7 @@ exp2 :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
@(require_results)
|
||||
exp10 :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = math.exp(LN10 * x[i])
|
||||
}
|
||||
} else {
|
||||
@@ -402,7 +402,7 @@ exp10 :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
@(require_results)
|
||||
pow :: proc "contextless" (x, e: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = math.pow(x[i], e[i])
|
||||
}
|
||||
} else {
|
||||
@@ -425,7 +425,7 @@ floor :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
@(require_results)
|
||||
round :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
#no_bounds_check for i in 0..<len(T) {
|
||||
out[i] = #force_inline math.round(x[i])
|
||||
}
|
||||
} else {
|
||||
@@ -486,7 +486,7 @@ is_nan_single :: proc "contextless" (x: $T) -> bool where IS_FLOAT(T) {
|
||||
|
||||
@(require_results)
|
||||
is_nan_array :: proc "contextless" (x: $A/[$N]$T) -> (out: [N]bool) where IS_FLOAT(T) {
|
||||
for i in 0..<N {
|
||||
#no_bounds_check for i in 0..<N {
|
||||
out[i] = #force_inline is_nan(x[i])
|
||||
}
|
||||
return
|
||||
@@ -499,7 +499,7 @@ is_inf_single :: proc "contextless" (x: $T) -> bool where IS_FLOAT(T) {
|
||||
|
||||
@(require_results)
|
||||
is_inf_array :: proc "contextless" (x: $A/[$N]$T) -> (out: [N]bool) where IS_FLOAT(T) {
|
||||
for i in 0..<N {
|
||||
#no_bounds_check for i in 0..<N {
|
||||
out[i] = #force_inline is_inf(x[i])
|
||||
}
|
||||
return
|
||||
@@ -512,7 +512,7 @@ classify_single :: proc "contextless" (x: $T) -> math.Float_Class where IS_FLOAT
|
||||
|
||||
@(require_results)
|
||||
classify_array :: proc "contextless" (x: $A/[$N]$T) -> (out: [N]math.Float_Class) where IS_FLOAT(T) {
|
||||
for i in 0..<N {
|
||||
#no_bounds_check for i in 0..<N {
|
||||
out[i] = #force_inline classify_single(x[i])
|
||||
}
|
||||
return
|
||||
@@ -532,42 +532,42 @@ classify :: proc{classify_single, classify_array}
|
||||
|
||||
@(require_results)
|
||||
less_than_array :: proc "contextless" (x, y: $A/[$N]$T) -> (out: [N]bool) where IS_ARRAY(A), IS_FLOAT(ELEM_TYPE(A)) {
|
||||
for i in 0..<N {
|
||||
#no_bounds_check for i in 0..<N {
|
||||
out[i] = x[i] < y[i]
|
||||
}
|
||||
return
|
||||
}
|
||||
@(require_results)
|
||||
less_than_equal_array :: proc "contextless" (x, y: $A/[$N]$T) -> (out: [N]bool) where IS_ARRAY(A), IS_FLOAT(ELEM_TYPE(A)) {
|
||||
for i in 0..<N {
|
||||
#no_bounds_check for i in 0..<N {
|
||||
out[i] = x[i] <= y[i]
|
||||
}
|
||||
return
|
||||
}
|
||||
@(require_results)
|
||||
greater_than_array :: proc "contextless" (x, y: $A/[$N]$T) -> (out: [N]bool) where IS_ARRAY(A), IS_FLOAT(ELEM_TYPE(A)) {
|
||||
for i in 0..<N {
|
||||
#no_bounds_check for i in 0..<N {
|
||||
out[i] = x[i] > y[i]
|
||||
}
|
||||
return
|
||||
}
|
||||
@(require_results)
|
||||
greater_than_equal_array :: proc "contextless" (x, y: $A/[$N]$T) -> (out: [N]bool) where IS_ARRAY(A), IS_FLOAT(ELEM_TYPE(A)) {
|
||||
for i in 0..<N {
|
||||
#no_bounds_check for i in 0..<N {
|
||||
out[i] = x[i] >= y[i]
|
||||
}
|
||||
return
|
||||
}
|
||||
@(require_results)
|
||||
equal_array :: proc "contextless" (x, y: $A/[$N]$T) -> (out: [N]bool) where IS_ARRAY(A), IS_FLOAT(ELEM_TYPE(A)) {
|
||||
for i in 0..<N {
|
||||
#no_bounds_check for i in 0..<N {
|
||||
out[i] = x[i] == y[i]
|
||||
}
|
||||
return
|
||||
}
|
||||
@(require_results)
|
||||
not_equal_array :: proc "contextless" (x, y: $A/[$N]$T) -> (out: [N]bool) where IS_ARRAY(A), IS_FLOAT(ELEM_TYPE(A)) {
|
||||
for i in 0..<N {
|
||||
#no_bounds_check for i in 0..<N {
|
||||
out[i] = x[i] != y[i]
|
||||
}
|
||||
return
|
||||
@@ -601,7 +601,7 @@ all :: proc "contextless" (x: $A/[$N]bool) -> (out: bool) {
|
||||
@(require_results)
|
||||
not :: proc "contextless" (x: $A/[$N]bool) -> (out: A) {
|
||||
for e, i in x {
|
||||
out[i] = !e
|
||||
#no_bounds_check out[i] = !e
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -46,18 +46,17 @@ scalar_dot :: proc "contextless" (a, b: $T) -> T where IS_FLOAT(T), !IS_ARRAY(T)
|
||||
|
||||
@(require_results)
|
||||
vector_dot :: proc "contextless" (a, b: $T/[$N]$E) -> (c: E) where IS_NUMERIC(E) #no_bounds_check {
|
||||
ab := a * b
|
||||
when N == 1 {
|
||||
return ab.x
|
||||
return a.x*b.x
|
||||
} else when N == 2 {
|
||||
return ab.x + ab.y
|
||||
return a.x*b.x + a.y*b.y
|
||||
} else when N == 3 {
|
||||
return ab.x + ab.y + ab.z
|
||||
return a.x*b.x + a.y*b.y + a.z*b.z
|
||||
} else when N == 4 {
|
||||
return ab.x + ab.y + ab.z + ab.w
|
||||
return a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w
|
||||
} else {
|
||||
for elem in ab {
|
||||
c += elem
|
||||
#unroll for _, i in a {
|
||||
c += a[i]*b[i]
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -2768,52 +2768,16 @@ matrix2_orthonormalize :: proc{
|
||||
|
||||
|
||||
@(require_results)
|
||||
matrix3_orthonormalize_f16 :: proc "contextless" (m: Matrix3f16) -> (r: Matrix3f16) #no_bounds_check {
|
||||
r = m
|
||||
r[0] = normalize(m[0])
|
||||
|
||||
d0 := dot(r[0], r[1])
|
||||
r[1] -= r[0] * d0
|
||||
r[1] = normalize(r[1])
|
||||
|
||||
d1 := dot(r[1], r[2])
|
||||
d0 = dot(r[0], r[2])
|
||||
r[2] -= r[0]*d0 + r[1]*d1
|
||||
r[2] = normalize(r[2])
|
||||
|
||||
return
|
||||
matrix3_orthonormalize_f16 :: proc "contextless" (m: Matrix3f16) -> Matrix3f16 #no_bounds_check {
|
||||
return matrix3_gram_schmidt(m, 0, 1, 2)
|
||||
}
|
||||
@(require_results)
|
||||
matrix3_orthonormalize_f32 :: proc "contextless" (m: Matrix3f32) -> (r: Matrix3f32) #no_bounds_check {
|
||||
r = m
|
||||
r[0] = normalize(m[0])
|
||||
|
||||
d0 := dot(r[0], r[1])
|
||||
r[1] -= r[0] * d0
|
||||
r[1] = normalize(r[1])
|
||||
|
||||
d1 := dot(r[1], r[2])
|
||||
d0 = dot(r[0], r[2])
|
||||
r[2] -= r[0]*d0 + r[1]*d1
|
||||
r[2] = normalize(r[2])
|
||||
|
||||
return
|
||||
matrix3_orthonormalize_f32 :: proc "contextless" (m: Matrix3f32) -> Matrix3f32 #no_bounds_check {
|
||||
return matrix3_gram_schmidt(m, 0, 1, 2)
|
||||
}
|
||||
@(require_results)
|
||||
matrix3_orthonormalize_f64 :: proc "contextless" (m: Matrix3f64) -> (r: Matrix3f64) #no_bounds_check {
|
||||
r = m
|
||||
r[0] = normalize(m[0])
|
||||
|
||||
d0 := dot(r[0], r[1])
|
||||
r[1] -= r[0] * d0
|
||||
r[1] = normalize(r[1])
|
||||
|
||||
d1 := dot(r[1], r[2])
|
||||
d0 = dot(r[0], r[2])
|
||||
r[2] -= r[0]*d0 + r[1]*d1
|
||||
r[2] = normalize(r[2])
|
||||
|
||||
return
|
||||
matrix3_orthonormalize_f64 :: proc "contextless" (m: Matrix3f64) -> Matrix3f64 #no_bounds_check {
|
||||
return matrix3_gram_schmidt(m, 0, 1, 2)
|
||||
}
|
||||
matrix3_orthonormalize :: proc{
|
||||
matrix3_orthonormalize_f16,
|
||||
@@ -2822,6 +2786,27 @@ matrix3_orthonormalize :: proc{
|
||||
}
|
||||
|
||||
|
||||
@(require_results)
|
||||
matrix3_gram_schmidt :: proc "contextless" (m: matrix[3, 3]$E, $A, $B, $C: int) -> (r: matrix[3, 3]E)
|
||||
where A != B, A != C, B != C #no_bounds_check
|
||||
{
|
||||
r = m
|
||||
r[A] = normalize(m[A])
|
||||
|
||||
d0 := dot(r[A], r[B])
|
||||
r[B] -= r[A] * d0
|
||||
r[B] = normalize(r[B])
|
||||
|
||||
d1 := dot(r[B], r[C])
|
||||
d0 = dot(r[A], r[C])
|
||||
r[C] -= r[A]*d0 + r[B]*d1
|
||||
r[C] = normalize(r[C])
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
@(require_results)
|
||||
vector3_orthonormalize_f16 :: proc "contextless" (x, y: Vector3f16) -> (z: Vector3f16) {
|
||||
return normalize(x - y * dot(y, x))
|
||||
|
||||
@@ -1613,17 +1613,11 @@ is_power_of_two :: proc "contextless" (x: int) -> bool {
|
||||
|
||||
@(require_results)
|
||||
next_power_of_two :: proc "contextless" (x: int) -> int {
|
||||
k := x -1
|
||||
when size_of(int) == 8 {
|
||||
k = k | (k >> 32)
|
||||
if x <= 1 {
|
||||
return 1
|
||||
}
|
||||
k = k | (k >> 16)
|
||||
k = k | (k >> 8)
|
||||
k = k | (k >> 4)
|
||||
k = k | (k >> 2)
|
||||
k = k | (k >> 1)
|
||||
k += 1 + int(x <= 0)
|
||||
return k
|
||||
n := uint(size_of(x) * 8) - uint(intrinsics.count_leading_zeros(uint(x) - 1))
|
||||
return int(1) << n
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
|
||||
103
core/math/math_cbrt.odin
Normal file
103
core/math/math_cbrt.odin
Normal file
@@ -0,0 +1,103 @@
|
||||
package math
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
@(require_results) cbrt_f16le :: proc "contextless" (x: f16le) -> f16le { return #force_inline f16le(cbrt_f16(f16(x))) }
|
||||
@(require_results) cbrt_f16be :: proc "contextless" (x: f16be) -> f16be { return #force_inline f16be(cbrt_f16(f16(x))) }
|
||||
@(require_results) cbrt_f32le :: proc "contextless" (x: f32le) -> f32le { return #force_inline f32le(cbrt_f32(f32(x))) }
|
||||
@(require_results) cbrt_f32be :: proc "contextless" (x: f32be) -> f32be { return #force_inline f32be(cbrt_f32(f32(x))) }
|
||||
@(require_results) cbrt_f64le :: proc "contextless" (x: f64le) -> f64le { return #force_inline f64le(cbrt_f64(f64(x))) }
|
||||
@(require_results) cbrt_f64be :: proc "contextless" (x: f64be) -> f64be { return #force_inline f64be(cbrt_f64(f64(x))) }
|
||||
|
||||
// cbrt returns the cube root of x.
|
||||
//
|
||||
// Special cases are:
|
||||
//
|
||||
// cbrt(±0) = ±0
|
||||
// cbrt(±Inf) = ±Inf
|
||||
// cbrt(NaN) = NaN
|
||||
cbrt :: proc{
|
||||
cbrt_f16, cbrt_f16le, cbrt_f16be,
|
||||
cbrt_f32, cbrt_f32le, cbrt_f32be,
|
||||
cbrt_f64, cbrt_f64le, cbrt_f64be,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@(require_results)
|
||||
cbrt_f16 :: proc "contextless" (x: f16) -> f16 { return #force_inline f16(cbrt_f64(f64(x))) }
|
||||
@(require_results)
|
||||
cbrt_f32 :: proc "contextless" (x: f32) -> f32 { return #force_inline f32(cbrt_f64(f64(x))) }
|
||||
|
||||
// cbrt returns the cube root of x.
|
||||
//
|
||||
// Special cases are:
|
||||
//
|
||||
// cbrt(±0) = ±0
|
||||
// cbrt(±Inf) = ±Inf
|
||||
// cbrt(NaN) = NaN
|
||||
@(require_results)
|
||||
cbrt_f64 :: proc "contextless" (x: f64) -> f64 {
|
||||
// http://www.netlib.org/fdlibm/s_cbrt.c and came with this notice.
|
||||
//
|
||||
// ====================================================
|
||||
// Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
|
||||
//
|
||||
// Developed at SunSoft, a Sun Microsystems, Inc. business.
|
||||
// Permission to use, copy, modify, and distribute this
|
||||
// software is freely granted, provided that this notice
|
||||
// is preserved.
|
||||
// ====================================================
|
||||
|
||||
B1 :: 715094163 // (682-0.03306235651)*2**20
|
||||
B2 :: 696219795 // (664-0.03306235651)*2**20
|
||||
SMALLEST_NORMAL :: 0h0010000000000000 // 2.22507385850720138309e-308 == 2**-1022
|
||||
|
||||
C :: 0h3FE15F15F15F15F1 // 5.42857142857142815906e-01 == 19/35
|
||||
D :: 0hBFE691DE2532C834 // -7.05306122448979611050e-01 == -864/1225
|
||||
E :: 0h3FF6A0EA0EA0EA0F // 1.41428571428571436819e+00 == 99/70
|
||||
F :: 0h3FF9B6DB6DB6DB6E // 1.60714285714285720630e+00 == 45/28
|
||||
G :: 0h3FD6DB6DB6DB6DB7 // 3.57142857142857150787e-01 == 5/14
|
||||
|
||||
x := x
|
||||
|
||||
switch {
|
||||
case x == 0 || is_nan(x) || is_inf(x, 0):
|
||||
return x
|
||||
}
|
||||
|
||||
sign := false
|
||||
if x < 0 {
|
||||
x = -x
|
||||
sign = true
|
||||
}
|
||||
|
||||
// Approximate cbrt (5-bits)
|
||||
t := transmute(f64)((transmute(u64)x)/3 + B1<<32)
|
||||
if x < SMALLEST_NORMAL {
|
||||
t = f64(1 << 54)
|
||||
t *= x
|
||||
t = transmute(f64)((transmute(u64)x)/3 + B2<<32)
|
||||
}
|
||||
|
||||
// Approximate cbrt (23-bits)
|
||||
r := t * t / x
|
||||
s := C + r*t
|
||||
t *= G + F/(s+E + D/s)
|
||||
|
||||
// Truncate to 22 bits, make larger than cbrt(x)
|
||||
t = transmute(f64)((transmute(u64)t)&(0xffffffffc<<28) + 1<<30)
|
||||
|
||||
// Single step of Newton-Raphson iteration to 53 bits with error less than 0.667ulps
|
||||
s = t * t // t*t is exact
|
||||
r = x / s
|
||||
w := t + t
|
||||
r = (r - t) / (w + r) // r-s is exact
|
||||
t = t + t*r
|
||||
|
||||
// Restore sign
|
||||
if sign {
|
||||
t = -t
|
||||
}
|
||||
return t
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package rand
|
||||
|
||||
import "base:runtime"
|
||||
import "core:math"
|
||||
|
||||
float64_uniform :: float64_range
|
||||
@@ -336,3 +337,76 @@ float64_gompertz :: proc(eta, b: f64, gen := context.random_generator) -> f64 {
|
||||
float32_gompertz :: proc(eta, b: f32, gen := context.random_generator) -> f32 {
|
||||
return f32(float64_gompertz(f64(eta), f64(b), gen))
|
||||
}
|
||||
|
||||
|
||||
|
||||
// A contextual structure for generating Zipf distributed variates.
|
||||
Zipf :: struct {
|
||||
gen: runtime.Random_Generator,
|
||||
imax: f64,
|
||||
v: f64,
|
||||
q: f64,
|
||||
s: f64,
|
||||
oneminus_Q: f64,
|
||||
oneminus_Qinv: f64,
|
||||
hxm: f64,
|
||||
hx0_minus_hxm: f64,
|
||||
}
|
||||
|
||||
|
||||
// Creates a Zipf variate generator.
|
||||
// The generator produces values k ∈ [0, imax] such that P(k) is proportional to (v + k) ** (-s).
|
||||
// The parameters must be: s > 1 and v >= 1
|
||||
//
|
||||
// W.Hormann, G.Derflinger:
|
||||
// "Rejection-Inversion to Generate Variates from Monotone Discrete Distributions"
|
||||
// [[ http://eeyore.wu-wien.ac.at/papers/96-04-04.wh-der.ps.gz ]]
|
||||
@(require_results)
|
||||
zipf_create :: proc(s, v: f64, imax: u64, gen := context.random_generator) -> (z: Zipf, ok: bool) {
|
||||
if s <= 1 || v < 1 {
|
||||
return
|
||||
}
|
||||
z.gen = gen
|
||||
z.imax = f64(imax)
|
||||
z.v = v
|
||||
z.q = s
|
||||
z.oneminus_Q = 1.0 - z.q
|
||||
z.oneminus_Qinv = 1.0 / z.oneminus_Q
|
||||
z.hxm = zipf_h(z, z.imax + 0.5)
|
||||
z.hx0_minus_hxm = zipf_h(z, 0.5) - math.exp(math.ln(z.v)*(-z.q)) - z.hxm
|
||||
z.s = 1 - zipf_hinv(z, zipf_h(z, 1.5)-math.exp(-z.q * math.ln(z.v+1.0)))
|
||||
return z, true
|
||||
}
|
||||
|
||||
|
||||
@(require_results)
|
||||
zipf_h :: proc(z: Zipf, x: f64) -> f64 {
|
||||
return math.exp(z.oneminus_Q * math.ln(z.v + x) * z.oneminus_Qinv)
|
||||
}
|
||||
@(require_results)
|
||||
zipf_hinv :: proc(z: Zipf, x: f64) -> f64 {
|
||||
return math.exp(z.oneminus_Qinv * math.ln(z.oneminus_Q * x)) - z.v
|
||||
}
|
||||
|
||||
|
||||
// Returns a value drawn from the zipf distribution described by the `Zipf` contextual structure.
|
||||
@(require_results)
|
||||
zipf_uint64 :: proc(z: Zipf) -> u64 {
|
||||
assert(z.gen.procedure != nil)
|
||||
k := f64(0.0)
|
||||
|
||||
for {
|
||||
r := float64(z.gen) // [0, 1)
|
||||
ur := r * z.hx0_minus_hxm + z.hxm
|
||||
x := zipf_hinv(z, ur)
|
||||
k = math.floor(x + 0.5)
|
||||
if k-x <= z.s {
|
||||
break
|
||||
}
|
||||
if ur >= zipf_h(z, k+0.5) - math.exp(-math.ln(k + z.v)*z.q) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return u64(k)
|
||||
}
|
||||
@@ -255,11 +255,10 @@ alignment is not specified explicitly.
|
||||
DEFAULT_ALIGNMENT :: 2*align_of(rawptr)
|
||||
|
||||
/*
|
||||
Default page size.
|
||||
|
||||
This value is the default page size for the current platform.
|
||||
On platforms where we were able to query a configurable size, we use that value instead.
|
||||
See `query_page_size_init()`
|
||||
*/
|
||||
DEFAULT_PAGE_SIZE ::
|
||||
PAGE_SIZE: int =
|
||||
64 * 1024 when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 else
|
||||
16 * 1024 when ODIN_OS == .Darwin && ODIN_ARCH == .arm64 else
|
||||
4 * 1024
|
||||
|
||||
@@ -1621,20 +1621,6 @@ small_stack_allocator_proc :: proc(
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
||||
/* Preserved for compatibility */
|
||||
Dynamic_Pool :: Dynamic_Arena
|
||||
DYNAMIC_POOL_BLOCK_SIZE_DEFAULT :: DYNAMIC_ARENA_BLOCK_SIZE_DEFAULT
|
||||
DYNAMIC_POOL_OUT_OF_BAND_SIZE_DEFAULT :: DYNAMIC_ARENA_OUT_OF_BAND_SIZE_DEFAULT
|
||||
dynamic_pool_allocator_proc :: dynamic_arena_allocator_proc
|
||||
dynamic_pool_free_all :: dynamic_arena_free_all
|
||||
dynamic_pool_reset :: dynamic_arena_reset
|
||||
dynamic_pool_alloc_bytes :: dynamic_arena_alloc_bytes
|
||||
dynamic_pool_alloc :: dynamic_arena_alloc
|
||||
dynamic_pool_init :: dynamic_arena_init
|
||||
dynamic_pool_allocator :: dynamic_arena_allocator
|
||||
dynamic_pool_destroy :: dynamic_arena_destroy
|
||||
|
||||
/*
|
||||
Default block size for dynamic arena.
|
||||
*/
|
||||
@@ -1651,7 +1637,7 @@ Dynamic arena allocator data.
|
||||
Dynamic_Arena :: struct {
|
||||
block_size: int,
|
||||
out_band_size: int,
|
||||
alignment: int,
|
||||
minimum_alignment: int,
|
||||
unused_blocks: [dynamic]rawptr,
|
||||
used_blocks: [dynamic]rawptr,
|
||||
out_band_allocations: [dynamic]rawptr,
|
||||
@@ -1668,23 +1654,23 @@ This procedure initializes a dynamic arena. The specified `block_allocator`
|
||||
will be used to allocate arena blocks, and `array_allocator` to allocate
|
||||
arrays of blocks and out-band blocks. The blocks have the default size of
|
||||
`block_size` and out-band threshold will be `out_band_size`. All allocations
|
||||
will be aligned to a boundary specified by `alignment`.
|
||||
will be aligned at a minimum to a boundary specified by `minimum_alignment`.
|
||||
*/
|
||||
dynamic_arena_init :: proc(
|
||||
pool: ^Dynamic_Arena,
|
||||
block_allocator := context.allocator,
|
||||
array_allocator := context.allocator,
|
||||
block_size := DYNAMIC_ARENA_BLOCK_SIZE_DEFAULT,
|
||||
out_band_size := DYNAMIC_ARENA_OUT_OF_BAND_SIZE_DEFAULT,
|
||||
alignment := DEFAULT_ALIGNMENT,
|
||||
arena: ^Dynamic_Arena,
|
||||
block_allocator := context.allocator,
|
||||
array_allocator := context.allocator,
|
||||
block_size := DYNAMIC_ARENA_BLOCK_SIZE_DEFAULT,
|
||||
out_band_size := DYNAMIC_ARENA_OUT_OF_BAND_SIZE_DEFAULT,
|
||||
minimum_alignment := DEFAULT_ALIGNMENT,
|
||||
) {
|
||||
pool.block_size = block_size
|
||||
pool.out_band_size = out_band_size
|
||||
pool.alignment = alignment
|
||||
pool.block_allocator = block_allocator
|
||||
pool.out_band_allocations.allocator = array_allocator
|
||||
pool.unused_blocks.allocator = array_allocator
|
||||
pool.used_blocks.allocator = array_allocator
|
||||
arena.block_size = block_size
|
||||
arena.out_band_size = out_band_size
|
||||
arena.minimum_alignment = minimum_alignment
|
||||
arena.block_allocator = block_allocator
|
||||
arena.out_band_allocations.allocator = array_allocator
|
||||
arena.unused_blocks.allocator = array_allocator
|
||||
arena.used_blocks.allocator = array_allocator
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1728,7 +1714,7 @@ dynamic_arena_destroy :: proc(a: ^Dynamic_Arena) {
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
_dynamic_arena_cycle_new_block :: proc(a: ^Dynamic_Arena, loc := #caller_location) -> (err: Allocator_Error) {
|
||||
_dynamic_arena_cycle_new_block :: proc(a: ^Dynamic_Arena, alignment: int, loc := #caller_location) -> (err: Allocator_Error) {
|
||||
if a.block_allocator.procedure == nil {
|
||||
panic("You must call `dynamic_arena_init` on a Dynamic Arena before using it.", loc)
|
||||
}
|
||||
@@ -1744,7 +1730,7 @@ _dynamic_arena_cycle_new_block :: proc(a: ^Dynamic_Arena, loc := #caller_locatio
|
||||
a.block_allocator.data,
|
||||
Allocator_Mode.Alloc,
|
||||
a.block_size,
|
||||
a.alignment,
|
||||
max(a.minimum_alignment, alignment),
|
||||
nil,
|
||||
0,
|
||||
)
|
||||
@@ -1766,8 +1752,8 @@ zero-initialized. This procedure returns a pointer to the newly allocated memory
|
||||
region.
|
||||
*/
|
||||
@(require_results)
|
||||
dynamic_arena_alloc :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> (rawptr, Allocator_Error) {
|
||||
data, err := dynamic_arena_alloc_bytes(a, size, loc)
|
||||
dynamic_arena_alloc :: proc(a: ^Dynamic_Arena, size: int, alignment: int = DEFAULT_ALIGNMENT, loc := #caller_location) -> (rawptr, Allocator_Error) {
|
||||
data, err := dynamic_arena_alloc_bytes(a, size, alignment, loc)
|
||||
return raw_data(data), err
|
||||
}
|
||||
|
||||
@@ -1780,8 +1766,8 @@ zero-initialized. This procedure returns a slice of the newly allocated memory
|
||||
region.
|
||||
*/
|
||||
@(require_results)
|
||||
dynamic_arena_alloc_bytes :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
|
||||
bytes, err := dynamic_arena_alloc_bytes_non_zeroed(a, size, loc)
|
||||
dynamic_arena_alloc_bytes :: proc(a: ^Dynamic_Arena, size: int, alignment: int = DEFAULT_ALIGNMENT, loc := #caller_location) -> ([]byte, Allocator_Error) {
|
||||
bytes, err := dynamic_arena_alloc_bytes_non_zeroed(a, size, alignment, loc)
|
||||
if bytes != nil {
|
||||
zero_slice(bytes)
|
||||
}
|
||||
@@ -1797,8 +1783,8 @@ zero-initialized. This procedure returns a pointer to the newly allocated
|
||||
memory region.
|
||||
*/
|
||||
@(require_results)
|
||||
dynamic_arena_alloc_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> (rawptr, Allocator_Error) {
|
||||
data, err := dynamic_arena_alloc_bytes_non_zeroed(a, size, loc)
|
||||
dynamic_arena_alloc_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, alignment: int = DEFAULT_ALIGNMENT, loc := #caller_location) -> (rawptr, Allocator_Error) {
|
||||
data, err := dynamic_arena_alloc_bytes_non_zeroed(a, size, alignment, loc)
|
||||
return raw_data(data), err
|
||||
}
|
||||
|
||||
@@ -1811,31 +1797,35 @@ zero-initialized. This procedure returns a slice of the newly allocated
|
||||
memory region.
|
||||
*/
|
||||
@(require_results)
|
||||
dynamic_arena_alloc_bytes_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
|
||||
dynamic_arena_alloc_bytes_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, alignment: int = DEFAULT_ALIGNMENT, loc := #caller_location) -> ([]byte, Allocator_Error) {
|
||||
if size >= a.out_band_size {
|
||||
assert(a.out_band_allocations.allocator.procedure != nil, "Backing array allocator must be initialized", loc=loc)
|
||||
memory, err := alloc_bytes_non_zeroed(size, a.alignment, a.out_band_allocations.allocator, loc)
|
||||
memory, err := alloc_bytes_non_zeroed(size, alignment, a.out_band_allocations.allocator, loc)
|
||||
if memory != nil {
|
||||
append(&a.out_band_allocations, raw_data(memory), loc = loc)
|
||||
}
|
||||
return memory, err
|
||||
}
|
||||
n := align_formula(size, a.alignment)
|
||||
actual_alignment := max(a.minimum_alignment, alignment)
|
||||
n := align_formula(size, actual_alignment)
|
||||
if n > a.block_size {
|
||||
return nil, .Invalid_Argument
|
||||
}
|
||||
if a.bytes_left < n {
|
||||
err := _dynamic_arena_cycle_new_block(a, loc)
|
||||
memory := align_forward(a.current_pos, uintptr(actual_alignment))
|
||||
margin := int(uintptr(memory) - uintptr(a.current_pos))
|
||||
if a.bytes_left < margin + n {
|
||||
err := _dynamic_arena_cycle_new_block(a, alignment, loc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if a.current_block == nil {
|
||||
return nil, .Out_Of_Memory
|
||||
}
|
||||
margin = 0
|
||||
memory = a.current_pos
|
||||
}
|
||||
memory := a.current_pos
|
||||
a.current_pos = ([^]byte)(a.current_pos)[n:]
|
||||
a.bytes_left -= n
|
||||
a.current_pos = ([^]byte)(memory)[n:]
|
||||
a.bytes_left -= margin + n
|
||||
result := ([^]byte)(memory)[:size]
|
||||
// ensure_poisoned(result)
|
||||
// sanitizer.address_unpoison(result)
|
||||
@@ -1900,9 +1890,10 @@ dynamic_arena_resize :: proc(
|
||||
old_memory: rawptr,
|
||||
old_size: int,
|
||||
size: int,
|
||||
alignment: int = DEFAULT_ALIGNMENT,
|
||||
loc := #caller_location,
|
||||
) -> (rawptr, Allocator_Error) {
|
||||
bytes, err := dynamic_arena_resize_bytes(a, byte_slice(old_memory, old_size), size, loc)
|
||||
bytes, err := dynamic_arena_resize_bytes(a, byte_slice(old_memory, old_size), size, alignment, loc)
|
||||
return raw_data(bytes), err
|
||||
}
|
||||
|
||||
@@ -1921,16 +1912,17 @@ This procedure returns the slice of the resized memory region.
|
||||
*/
|
||||
@(require_results)
|
||||
dynamic_arena_resize_bytes :: proc(
|
||||
a: ^Dynamic_Arena,
|
||||
old_data: []byte,
|
||||
size: int,
|
||||
a: ^Dynamic_Arena,
|
||||
old_data: []byte,
|
||||
size: int,
|
||||
alignment: int = DEFAULT_ALIGNMENT,
|
||||
loc := #caller_location,
|
||||
) -> ([]byte, Allocator_Error) {
|
||||
if size == 0 {
|
||||
// NOTE: This allocator has no Free mode.
|
||||
return nil, nil
|
||||
}
|
||||
bytes, err := dynamic_arena_resize_bytes_non_zeroed(a, old_data, size, loc)
|
||||
bytes, err := dynamic_arena_resize_bytes_non_zeroed(a, old_data, size, alignment, loc)
|
||||
if bytes != nil {
|
||||
if old_data == nil {
|
||||
zero_slice(bytes)
|
||||
@@ -1960,9 +1952,10 @@ dynamic_arena_resize_non_zeroed :: proc(
|
||||
old_memory: rawptr,
|
||||
old_size: int,
|
||||
size: int,
|
||||
alignment: int = DEFAULT_ALIGNMENT,
|
||||
loc := #caller_location,
|
||||
) -> (rawptr, Allocator_Error) {
|
||||
bytes, err := dynamic_arena_resize_bytes_non_zeroed(a, byte_slice(old_memory, old_size), size, loc)
|
||||
bytes, err := dynamic_arena_resize_bytes_non_zeroed(a, byte_slice(old_memory, old_size), size, alignment, loc)
|
||||
return raw_data(bytes), err
|
||||
}
|
||||
|
||||
@@ -1981,9 +1974,10 @@ This procedure returns the slice of the resized memory region.
|
||||
*/
|
||||
@(require_results)
|
||||
dynamic_arena_resize_bytes_non_zeroed :: proc(
|
||||
a: ^Dynamic_Arena,
|
||||
old_data: []byte,
|
||||
size: int,
|
||||
a: ^Dynamic_Arena,
|
||||
old_data: []byte,
|
||||
size: int,
|
||||
alignment: int = DEFAULT_ALIGNMENT,
|
||||
loc := #caller_location,
|
||||
) -> ([]byte, Allocator_Error) {
|
||||
if size == 0 {
|
||||
@@ -1998,7 +1992,7 @@ dynamic_arena_resize_bytes_non_zeroed :: proc(
|
||||
}
|
||||
// No information is kept about allocations in this allocator, thus we
|
||||
// cannot truly resize anything and must reallocate.
|
||||
data, err := dynamic_arena_alloc_bytes_non_zeroed(a, size, loc)
|
||||
data, err := dynamic_arena_alloc_bytes_non_zeroed(a, size, alignment, loc)
|
||||
if err == nil {
|
||||
runtime.copy(data, byte_slice(old_memory, old_size))
|
||||
}
|
||||
@@ -2017,17 +2011,17 @@ dynamic_arena_allocator_proc :: proc(
|
||||
arena := (^Dynamic_Arena)(allocator_data)
|
||||
switch mode {
|
||||
case .Alloc:
|
||||
return dynamic_arena_alloc_bytes(arena, size, loc)
|
||||
return dynamic_arena_alloc_bytes(arena, size, alignment, loc)
|
||||
case .Alloc_Non_Zeroed:
|
||||
return dynamic_arena_alloc_bytes_non_zeroed(arena, size, loc)
|
||||
return dynamic_arena_alloc_bytes_non_zeroed(arena, size, alignment, loc)
|
||||
case .Free:
|
||||
return nil, .Mode_Not_Implemented
|
||||
case .Free_All:
|
||||
dynamic_arena_free_all(arena, loc)
|
||||
case .Resize:
|
||||
return dynamic_arena_resize_bytes(arena, byte_slice(old_memory, old_size), size, loc)
|
||||
return dynamic_arena_resize_bytes(arena, byte_slice(old_memory, old_size), size, alignment, loc)
|
||||
case .Resize_Non_Zeroed:
|
||||
return dynamic_arena_resize_bytes_non_zeroed(arena, byte_slice(old_memory, old_size), size, loc)
|
||||
return dynamic_arena_resize_bytes_non_zeroed(arena, byte_slice(old_memory, old_size), size, alignment, loc)
|
||||
case .Query_Features:
|
||||
set := (^Allocator_Mode_Set)(old_memory)
|
||||
if set != nil {
|
||||
@@ -2038,7 +2032,7 @@ dynamic_arena_allocator_proc :: proc(
|
||||
info := (^Allocator_Query_Info)(old_memory)
|
||||
if info != nil && info.pointer != nil {
|
||||
info.size = arena.block_size
|
||||
info.alignment = arena.alignment
|
||||
info.alignment = arena.minimum_alignment
|
||||
return byte_slice(info, size_of(info^)), nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
13
core/mem/mem_posix.odin
Normal file
13
core/mem/mem_posix.odin
Normal file
@@ -0,0 +1,13 @@
|
||||
#+build linux, darwin, netbsd, freebsd, openbsd
|
||||
package mem
|
||||
|
||||
import "core:sys/posix"
|
||||
|
||||
@(init, private, no_sanitize_address)
|
||||
query_page_size_init :: proc "contextless" () {
|
||||
size := posix.sysconf(._PAGESIZE)
|
||||
PAGE_SIZE = max(PAGE_SIZE, int(size))
|
||||
|
||||
// is power of two
|
||||
assert_contextless(PAGE_SIZE != 0 && (PAGE_SIZE & (PAGE_SIZE-1)) == 0)
|
||||
}
|
||||
16
core/mem/mem_windows.odin
Normal file
16
core/mem/mem_windows.odin
Normal file
@@ -0,0 +1,16 @@
|
||||
#+build windows
|
||||
package mem
|
||||
|
||||
import "core:sys/windows"
|
||||
|
||||
@(init, private, no_sanitize_address)
|
||||
query_page_size_init :: proc "contextless" () {
|
||||
info: windows.SYSTEM_INFO
|
||||
windows.GetSystemInfo(&info)
|
||||
|
||||
size := info.dwPageSize
|
||||
PAGE_SIZE = max(PAGE_SIZE, int(size))
|
||||
|
||||
// is power of two
|
||||
assert_contextless(PAGE_SIZE != 0 && (PAGE_SIZE & (PAGE_SIZE-1)) == 0)
|
||||
}
|
||||
@@ -344,7 +344,7 @@ rb_resize_bytes_non_zeroed :: proc(
|
||||
}
|
||||
}
|
||||
result = rb_alloc_bytes_non_zeroed(stack, size, alignment) or_return
|
||||
runtime.mem_copy_non_overlapping(raw_data(result), ptr, old_size)
|
||||
runtime.mem_copy_non_overlapping(raw_data(result), ptr, min(old_size, size))
|
||||
err = rb_free(stack, ptr)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -130,11 +130,11 @@ the tracking allocator to the old behavior, where the bad_free_array was used.
|
||||
*/
|
||||
@(no_sanitize_address)
|
||||
tracking_allocator_bad_free_callback_panic :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) {
|
||||
runtime.print_caller_location(location)
|
||||
runtime.print_string(" Tracking allocator error: Bad free of pointer ")
|
||||
runtime.print_uintptr(uintptr(memory))
|
||||
runtime.print_string("\n")
|
||||
runtime.trap()
|
||||
buf: [256]byte
|
||||
offset := 0
|
||||
_ = runtime.write_string(&offset, buf[:], "Tracking allocator error: Bad free of pointer ")
|
||||
_ = runtime.write_u64(&offset, buf[:], u64(uintptr(memory)))
|
||||
panic(string(buf[:offset]), loc = location)
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -126,11 +126,11 @@ arena_alloc_unguarded :: proc(arena: ^Arena, size: uint, alignment: uint, loc :=
|
||||
if err == .Out_Of_Memory {
|
||||
if arena.minimum_block_size == 0 {
|
||||
arena.minimum_block_size = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE
|
||||
arena.minimum_block_size = mem.align_forward_uint(arena.minimum_block_size, DEFAULT_PAGE_SIZE)
|
||||
arena.minimum_block_size = mem.align_forward_uint(arena.minimum_block_size, uint(mem.PAGE_SIZE))
|
||||
}
|
||||
if arena.default_commit_size == 0 {
|
||||
arena.default_commit_size = min(DEFAULT_ARENA_GROWING_COMMIT_SIZE, arena.minimum_block_size)
|
||||
arena.default_commit_size = mem.align_forward_uint(arena.default_commit_size, DEFAULT_PAGE_SIZE)
|
||||
arena.default_commit_size = mem.align_forward_uint(arena.default_commit_size, uint(mem.PAGE_SIZE))
|
||||
}
|
||||
|
||||
if arena.default_commit_size != 0 {
|
||||
|
||||
@@ -6,13 +6,6 @@ import "base:intrinsics"
|
||||
import "base:runtime"
|
||||
_ :: runtime
|
||||
|
||||
DEFAULT_PAGE_SIZE := uint(4096)
|
||||
|
||||
@(init, private)
|
||||
platform_memory_init :: proc "contextless" () {
|
||||
_platform_memory_init()
|
||||
}
|
||||
|
||||
Allocator_Error :: mem.Allocator_Error
|
||||
|
||||
@(require_results, no_sanitize_address)
|
||||
@@ -79,7 +72,7 @@ align_formula :: #force_inline proc "contextless" (size, align: uint) -> uint {
|
||||
|
||||
@(require_results, no_sanitize_address)
|
||||
memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags: Memory_Block_Flags = {}) -> (block: ^Memory_Block, err: Allocator_Error) {
|
||||
page_size := DEFAULT_PAGE_SIZE
|
||||
page_size := uint(mem.PAGE_SIZE)
|
||||
assert(mem.is_power_of_two(uintptr(page_size)))
|
||||
|
||||
committed := committed
|
||||
@@ -144,7 +137,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint,
|
||||
// TODO(bill): determine a better heuristic for this behaviour
|
||||
extra_size := max(size, block.committed>>1)
|
||||
platform_total_commit := base_offset + block.used + extra_size
|
||||
platform_total_commit = align_formula(platform_total_commit, DEFAULT_PAGE_SIZE)
|
||||
platform_total_commit = align_formula(platform_total_commit, uint(mem.PAGE_SIZE))
|
||||
platform_total_commit = min(max(platform_total_commit, default_commit_size), pmblock.reserved)
|
||||
|
||||
assert(pmblock.committed <= pmblock.reserved)
|
||||
|
||||
@@ -43,12 +43,6 @@ _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags)
|
||||
return errno == .NONE
|
||||
}
|
||||
|
||||
_platform_memory_init :: proc "contextless" () {
|
||||
DEFAULT_PAGE_SIZE = 4096
|
||||
// is power of two
|
||||
assert_contextless(DEFAULT_PAGE_SIZE != 0 && (DEFAULT_PAGE_SIZE & (DEFAULT_PAGE_SIZE-1)) == 0)
|
||||
}
|
||||
|
||||
_map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
|
||||
prot: linux.Mem_Protection
|
||||
if .Read in flags {
|
||||
|
||||
@@ -25,10 +25,6 @@ _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags)
|
||||
return false
|
||||
}
|
||||
|
||||
_platform_memory_init :: proc "contextless" () {
|
||||
|
||||
}
|
||||
|
||||
_map_file :: proc "contextless" (f: any, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
|
||||
return nil, .Map_Failure
|
||||
}
|
||||
|
||||
@@ -28,15 +28,6 @@ _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags)
|
||||
return posix.mprotect(data, size, transmute(posix.Prot_Flags)flags) == .OK
|
||||
}
|
||||
|
||||
_platform_memory_init :: proc "contextless" () {
|
||||
// NOTE: `posix.PAGESIZE` due to legacy reasons could be wrong so we use `sysconf`.
|
||||
size := posix.sysconf(._PAGESIZE)
|
||||
DEFAULT_PAGE_SIZE = uint(max(size, posix.PAGESIZE))
|
||||
|
||||
// is power of two
|
||||
assert_contextless(DEFAULT_PAGE_SIZE != 0 && (DEFAULT_PAGE_SIZE & (DEFAULT_PAGE_SIZE-1)) == 0)
|
||||
}
|
||||
|
||||
_map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
|
||||
#assert(i32(posix.Prot_Flag_Bits.READ) == i32(Map_File_Flag.Read))
|
||||
#assert(i32(posix.Prot_Flag_Bits.WRITE) == i32(Map_File_Flag.Write))
|
||||
|
||||
@@ -146,18 +146,6 @@ _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags)
|
||||
return bool(ok)
|
||||
}
|
||||
|
||||
|
||||
@(no_sanitize_address)
|
||||
_platform_memory_init :: proc "contextless" () {
|
||||
sys_info: SYSTEM_INFO
|
||||
GetSystemInfo(&sys_info)
|
||||
DEFAULT_PAGE_SIZE = max(DEFAULT_PAGE_SIZE, uint(sys_info.dwPageSize))
|
||||
|
||||
// is power of two
|
||||
assert_contextless(DEFAULT_PAGE_SIZE != 0 && (DEFAULT_PAGE_SIZE & (DEFAULT_PAGE_SIZE-1)) == 0)
|
||||
}
|
||||
|
||||
|
||||
@(no_sanitize_address)
|
||||
_map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
|
||||
page_flags: u32
|
||||
|
||||
@@ -120,6 +120,7 @@ _init :: proc(l: ^Event_Loop, allocator: mem.Allocator) -> (rerr: General_Error)
|
||||
filter = .User,
|
||||
flags = {.Add, .Enable, .Clear},
|
||||
})
|
||||
__tick(l, 0) // Tick to enqueue wake up, allowing wake ups before the user's first tick.
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -406,13 +406,18 @@ exec :: proc(op: ^Operation, trigger_wake_up := true) {
|
||||
if op.l == &_tls_event_loop {
|
||||
_exec(op)
|
||||
} else {
|
||||
for !mpsc_enqueue(&op.l.queue, op) {
|
||||
// Capture the loop pointer before the enqueue publishes `op`: the
|
||||
// target loop can complete the operation and return it to the
|
||||
// operation pool before `op.l` is re-read below, and `l` is in a
|
||||
// raw union with the pool's free-list link.
|
||||
l := op.l
|
||||
for !mpsc_enqueue(&l.queue, op) {
|
||||
warn("operation queue on event loop filled up")
|
||||
wake_up(op.l)
|
||||
wake_up(l)
|
||||
_yield()
|
||||
}
|
||||
if trigger_wake_up {
|
||||
wake_up(op.l)
|
||||
wake_up(l)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,7 +378,14 @@ advance_token :: proc(p: ^Parser) -> tokenizer.Token {
|
||||
prev := p.prev_tok
|
||||
|
||||
if next_token0(p) {
|
||||
consume_comment_groups(p, prev)
|
||||
#partial switch p.curr_tok.kind {
|
||||
case .Comment:
|
||||
consume_comment_groups(p, prev)
|
||||
case .Semicolon:
|
||||
if p.expr_level > 0 && p.curr_tok.text == "\n" {
|
||||
advance_token(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
return prev
|
||||
}
|
||||
@@ -898,7 +905,7 @@ parse_for_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
|
||||
if allow_token(p, .Do) {
|
||||
body = convert_stmt_to_body(p, parse_stmt(p))
|
||||
if tok.pos.line != body.pos.line {
|
||||
error(p, body.pos, "the body of a 'do' must be on the same line as 'else'")
|
||||
error(p, body.pos, "the body of a 'do' must be on the same line as 'for'")
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -1390,7 +1397,7 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
|
||||
.Pointer,
|
||||
.Asm, // Inline assembly
|
||||
// Unary Expressions
|
||||
.Add, .Sub, .Xor, .Not, .And:
|
||||
.Add, .Sub, .Xor, .Not, .And, .Increment, .Decrement:
|
||||
|
||||
s := parse_simple_stmt(p, {Stmt_Allow_Flag.Label})
|
||||
expect_semicolon(p, s)
|
||||
@@ -2199,6 +2206,57 @@ parse_proc_tags :: proc(p: ^Parser) -> (tags: ast.Proc_Tags) {
|
||||
return
|
||||
}
|
||||
|
||||
is_expr_generic :: proc(expr : ^ast.Expr) -> bool {
|
||||
is_generic := false
|
||||
if expr != nil {
|
||||
#partial switch e in expr.derived_expr {
|
||||
case ^ast.Typeid_Type:
|
||||
is_generic = e.specialization != nil
|
||||
case ^ast.Poly_Type:
|
||||
is_generic = true
|
||||
case ^ast.Proc_Type:
|
||||
is_generic = e.generic
|
||||
case ^ast.Pointer_Type:
|
||||
is_generic = is_expr_generic(e.elem)
|
||||
case ^ast.Multi_Pointer_Type:
|
||||
is_generic = is_expr_generic(e.elem)
|
||||
case ^ast.Array_Type:
|
||||
is_generic = is_expr_generic(e.len) || is_expr_generic(e.elem)
|
||||
case ^ast.Dynamic_Array_Type:
|
||||
is_generic = is_expr_generic(e.elem)
|
||||
case ^ast.Fixed_Capacity_Dynamic_Array_Type:
|
||||
is_generic = is_expr_generic(e.capacity) || is_expr_generic(e.elem)
|
||||
case ^ast.Bit_Set_Type:
|
||||
is_generic = is_expr_generic(e.elem)
|
||||
case ^ast.Map_Type:
|
||||
is_generic = is_expr_generic(e.key) || is_expr_generic(e.value)
|
||||
case ^ast.Matrix_Type:
|
||||
is_generic = is_expr_generic(e.row_count) || is_expr_generic(e.column_count) || is_expr_generic(e.elem)
|
||||
}
|
||||
}
|
||||
return is_generic
|
||||
}
|
||||
|
||||
is_field_list_generic :: proc(field_list : ^ast.Field_List, check_names : bool) -> bool {
|
||||
is_generic := false
|
||||
loop: for param in field_list.list {
|
||||
if is_expr_generic(param.type) {
|
||||
is_generic = true
|
||||
break loop
|
||||
}
|
||||
if !check_names || param.type == nil {
|
||||
continue
|
||||
}
|
||||
for name in param.names {
|
||||
if _, ok := name.derived.(^ast.Poly_Type); ok {
|
||||
is_generic = true
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
return is_generic
|
||||
}
|
||||
|
||||
parse_proc_type :: proc(p: ^Parser, tok: tokenizer.Token) -> ^ast.Proc_Type {
|
||||
cc: ast.Proc_Calling_Convention
|
||||
if p.curr_tok.kind == .String {
|
||||
@@ -2220,21 +2278,9 @@ parse_proc_type :: proc(p: ^Parser, tok: tokenizer.Token) -> ^ast.Proc_Type {
|
||||
expect_closing_parentheses_of_field_list(p)
|
||||
results, diverging := parse_results(p)
|
||||
|
||||
is_generic := false
|
||||
|
||||
loop: for param in params.list {
|
||||
if param.type != nil {
|
||||
if _, ok := param.type.derived.(^ast.Poly_Type); ok {
|
||||
is_generic = true
|
||||
break loop
|
||||
}
|
||||
for name in param.names {
|
||||
if _, ok := name.derived.(^ast.Poly_Type); ok {
|
||||
is_generic = true
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
is_generic := is_field_list_generic(params, true)
|
||||
if !is_generic && results != nil {
|
||||
is_generic = is_field_list_generic(results, false)
|
||||
}
|
||||
|
||||
end := end_pos(p.prev_tok)
|
||||
@@ -2327,10 +2373,10 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
|
||||
|
||||
case .Open_Paren:
|
||||
open := expect_token(p, .Open_Paren)
|
||||
p.expr_level += 1
|
||||
prev_expr_level := p.expr_level
|
||||
p.expr_level = max(p.expr_level, 0) + 1
|
||||
expr := parse_expr(p, false)
|
||||
skip_possible_newline(p)
|
||||
p.expr_level -= 1
|
||||
p.expr_level = prev_expr_level
|
||||
close := expect_token(p, .Close_Paren)
|
||||
|
||||
pe := ast.new(ast.Paren_Expr, open.pos, end_pos(close))
|
||||
@@ -2766,11 +2812,6 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
|
||||
}
|
||||
p.expr_level = prev_level
|
||||
|
||||
if is_raw_union && is_packed {
|
||||
is_packed = false
|
||||
error(p, tok.pos, "'#raw_union' cannot also be '#packed")
|
||||
}
|
||||
|
||||
if is_raw_union && is_all_or_none {
|
||||
is_all_or_none = false
|
||||
error(p, tok.pos, "'#raw_union' cannot also be '#all_or_none")
|
||||
@@ -3135,9 +3176,6 @@ is_literal_type :: proc(expr: ^ast.Expr) -> bool {
|
||||
}
|
||||
|
||||
parse_value :: proc(p: ^Parser) -> ^ast.Expr {
|
||||
if p.curr_tok.kind == .Open_Brace {
|
||||
return parse_literal_value(p, nil)
|
||||
}
|
||||
prev_allow_range := p.allow_range
|
||||
defer p.allow_range = prev_allow_range
|
||||
p.allow_range = true
|
||||
@@ -3172,11 +3210,12 @@ parse_elem_list :: proc(p: ^Parser) -> []^ast.Expr {
|
||||
parse_literal_value :: proc(p: ^Parser, type: ^ast.Expr) -> ^ast.Comp_Lit {
|
||||
elems: []^ast.Expr
|
||||
open := expect_token(p, .Open_Brace)
|
||||
p.expr_level += 1
|
||||
prev_expr_level := p.expr_level
|
||||
p.expr_level = 0
|
||||
if p.curr_tok.kind != .Close_Brace {
|
||||
elems = parse_elem_list(p)
|
||||
}
|
||||
p.expr_level -= 1
|
||||
p.expr_level = prev_expr_level
|
||||
|
||||
skip_possible_newline(p)
|
||||
close := expect_closing_brace_of_field_list(p)
|
||||
@@ -3195,7 +3234,8 @@ parse_call_expr :: proc(p: ^Parser, operand: ^ast.Expr) -> ^ast.Expr {
|
||||
|
||||
ellipsis: tokenizer.Token
|
||||
|
||||
p.expr_level += 1
|
||||
prev_expr_level := p.expr_level
|
||||
p.expr_level = 0
|
||||
open := expect_token(p, .Open_Paren)
|
||||
|
||||
seen_ellipsis := false
|
||||
@@ -3242,8 +3282,8 @@ parse_call_expr :: proc(p: ^Parser, operand: ^ast.Expr) -> ^ast.Expr {
|
||||
allow_token(p, .Comma) or_break
|
||||
}
|
||||
|
||||
p.expr_level = prev_expr_level
|
||||
close := expect_closing_token_of_field_list(p, .Close_Paren, "argument list")
|
||||
p.expr_level -= 1
|
||||
|
||||
ce := ast.new(ast.Call_Expr, operand.pos, end_pos(close))
|
||||
ce.expr = operand
|
||||
@@ -3329,8 +3369,8 @@ parse_atom_expr :: proc(p: ^Parser, value: ^ast.Expr, lhs: bool) -> (operand: ^a
|
||||
}
|
||||
}
|
||||
|
||||
close := expect_token(p, .Close_Bracket)
|
||||
p.expr_level -= 1
|
||||
close := expect_token(p, .Close_Bracket)
|
||||
|
||||
if is_slice_op {
|
||||
if interval.kind == .Comma {
|
||||
@@ -3514,7 +3554,8 @@ parse_unary_expr :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
|
||||
|
||||
case .Add, .Sub,
|
||||
.Not, .Xor,
|
||||
.And:
|
||||
.And,
|
||||
.Mul_Mul:
|
||||
op := advance_token(p)
|
||||
expr := parse_unary_expr(p, lhs)
|
||||
|
||||
|
||||
@@ -67,6 +67,8 @@ Token_Kind :: enum u32 {
|
||||
Cmp_And, // &&
|
||||
Cmp_Or, // ||
|
||||
|
||||
Mul_Mul, // **
|
||||
|
||||
B_Assign_Op_Begin,
|
||||
Add_Eq, // +=
|
||||
Sub_Eq, // -=
|
||||
@@ -202,6 +204,8 @@ tokens := [Token_Kind.COUNT]string {
|
||||
"&&",
|
||||
"||",
|
||||
|
||||
"**",
|
||||
|
||||
"",
|
||||
"+=",
|
||||
"-=",
|
||||
|
||||
@@ -679,6 +679,9 @@ scan :: proc(t: ^Tokenizer) -> Token {
|
||||
if t.ch == '=' {
|
||||
advance_rune(t)
|
||||
kind = .Mul_Eq
|
||||
} else if t.ch == '*' {
|
||||
advance_rune(t)
|
||||
kind = .Mul_Mul
|
||||
}
|
||||
case '=':
|
||||
kind = .Eq
|
||||
|
||||
@@ -97,8 +97,8 @@ _read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) {
|
||||
return
|
||||
}
|
||||
|
||||
stat: linux.Stat
|
||||
errno := linux.fstat(linux.Fd(fd(f)), &stat)
|
||||
stat: linux.Statx
|
||||
errno := linux.statx(linux.Fd(fd(f)), "", {.EMPTY_PATH}, {.TYPE}, &stat)
|
||||
if errno != .NONE {
|
||||
read_directory_iterator_set_error(it, name(f), _get_platform_error(errno))
|
||||
return
|
||||
|
||||
@@ -279,10 +279,8 @@ _write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (nt: i64, err: Error
|
||||
|
||||
@(no_sanitize_memory)
|
||||
_file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) {
|
||||
// TODO: Identify 0-sized "pseudo" files and return No_Size. This would
|
||||
// eliminate the need for the _read_entire_pseudo_file procs.
|
||||
s: linux.Stat = ---
|
||||
errno := linux.fstat(f.fd, &s)
|
||||
s: linux.Statx = ---
|
||||
errno := linux.statx(f.fd, "", {.EMPTY_PATH}, {.SIZE, .TYPE}, &s)
|
||||
if errno != .NONE {
|
||||
return 0, _get_platform_error(errno)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user