Merge branch 'master' into bill/bedrock

This commit is contained in:
gingerBill
2026-06-24 11:07:02 +01:00
committed by GitHub
296 changed files with 26882 additions and 12209 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
#+private
#+build linux, darwin, freebsd, openbsd, netbsd, haiku
#+build linux, darwin, freebsd, openbsd, netbsd
#+no-instrumentation
package runtime

View File

@@ -1,4 +1,4 @@
#+build linux, darwin, freebsd, openbsd, netbsd, haiku
#+build linux, darwin, freebsd, openbsd, netbsd
#+private
package runtime

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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

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

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

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

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

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

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

View 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

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

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

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

@@ -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."`,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 {
"&&",
"||",
"**",
"",
"+=",
"-=",

View File

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

View File

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

View File

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