mirror of
https://github.com/odin-lang/Odin.git
synced 2026-09-03 10:40:20 +00:00
Merge branch 'master' into x.508
This commit is contained in:
19
.gitattributes
vendored
19
.gitattributes
vendored
@@ -14,3 +14,22 @@ vendor/wgpu/lib/wgpu-windows-x86_64-msvc-release/lib/wgpu_native.dll filter=lfs
|
||||
vendor/wgpu/lib/wgpu-windows-x86_64-msvc-release/lib/wgpu_native.dll.lib filter=lfs diff=lfs merge=lfs -text
|
||||
vendor/wgpu/lib/wgpu-windows-x86_64-msvc-release/lib/wgpu_native.lib filter=lfs diff=lfs merge=lfs -text
|
||||
vendor/wgpu/lib/wgpu-windows-x86_64-msvc-release/lib/wgpu_native.pdb filter=lfs diff=lfs merge=lfs -text
|
||||
vendor/raylib/linux/libraygui.so filter=lfs diff=lfs merge=lfs -text
|
||||
vendor/raylib/linux/libraygui.a filter=lfs diff=lfs merge=lfs -text
|
||||
vendor/raylib/linux/libraylib.a filter=lfs diff=lfs merge=lfs -text
|
||||
vendor/raylib/linux/libraylib.so.600 filter=lfs diff=lfs merge=lfs -text
|
||||
vendor/raylib/linux-arm64/libraylib.a filter=lfs diff=lfs merge=lfs -text
|
||||
vendor/raylib/linux-arm64/libraylib.so.600 filter=lfs diff=lfs merge=lfs -text
|
||||
vendor/raylib/macos/libraygui-arm64.dylib filter=lfs diff=lfs merge=lfs -text
|
||||
vendor/raylib/macos/libraygui.dylib filter=lfs diff=lfs merge=lfs -text
|
||||
vendor/raylib/macos/libraylib.a filter=lfs diff=lfs merge=lfs -text
|
||||
vendor/raylib/macos/libraylib.600.dylib filter=lfs diff=lfs merge=lfs -text
|
||||
vendor/raylib/wasm/libraylib.web.a filter=lfs diff=lfs merge=lfs -text
|
||||
vendor/raylib/windows/raylib.dll filter=lfs diff=lfs merge=lfs -text
|
||||
vendor/raylib/windows/raylib.lib filter=lfs diff=lfs merge=lfs -text
|
||||
vendor/raylib/windows/raylibdll.lib filter=lfs diff=lfs merge=lfs -text
|
||||
vendor/raylib/windows/raygui.dll filter=lfs diff=lfs merge=lfs -text
|
||||
vendor/raylib/windows/raygui.lib filter=lfs diff=lfs merge=lfs -text
|
||||
vendor/raylib/windows/rayguidll.lib filter=lfs diff=lfs merge=lfs -text
|
||||
vendor/box3d/lib/linux-amd64/libbox3d.a filter=lfs diff=lfs merge=lfs -text
|
||||
vendor/box3d/lib/darwin/libbox3d.a filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -138,7 +138,7 @@ jobs:
|
||||
- name: Normal Core library tests
|
||||
run: ./odin test tests/core/normal.odin -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
|
||||
- name: Optimized Core library tests
|
||||
run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
|
||||
run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
|
||||
- name: Wycheproof tests
|
||||
run: ./odin test tests/core/crypto/wycheproof -vet -vet-tabs -strict-style -vet-style -vet-cast -warnings-as-errors -disallow-do -o:speed
|
||||
- name: Noise Protocol Framework tests
|
||||
@@ -239,7 +239,7 @@ jobs:
|
||||
shell: cmd
|
||||
run: |
|
||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
|
||||
odin test tests/core/speed.odin -o:speed -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
|
||||
odin test tests/core/speed.odin -o:speed -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
|
||||
- name: Wycheproof tests
|
||||
shell: cmd
|
||||
run: |
|
||||
|
||||
120
.github/workflows/nightly.yml
vendored
120
.github/workflows/nightly.yml
vendored
@@ -44,16 +44,22 @@ 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:3.24 sh -c '
|
||||
docker run --rm -v "$PWD:/src" -w /src $DOCKER_IMAGE sh -c '
|
||||
apk add --no-cache \
|
||||
musl-dev llvm20-dev clang20 build-base git mold lz4 \
|
||||
libxml2-static llvm20-static zlib-static zstd-static \
|
||||
@@ -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,60 +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 build-base git mold lz4 \
|
||||
libxml2-static llvm20-static zlib-static zstd-static \
|
||||
make &&
|
||||
git config --global --add safe.directory /src &&
|
||||
./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:
|
||||
@@ -141,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
|
||||
@@ -148,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
|
||||
@@ -163,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
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -277,7 +277,6 @@ odin
|
||||
*.bin
|
||||
demo.bin
|
||||
libLLVM*.so*
|
||||
*.a
|
||||
|
||||
# WASM
|
||||
*.wasm
|
||||
|
||||
@@ -185,6 +185,8 @@ type_is_simd_vector :: proc($T: typeid) -> bool ---
|
||||
type_is_matrix :: proc($T: typeid) -> bool ---
|
||||
type_is_fixed_capacity_dynamic_array :: proc($T: typeid) -> bool ---
|
||||
|
||||
type_is_internally_pointer_like :: proc($T: typeid) -> bool ---
|
||||
|
||||
type_has_nil :: proc($T: typeid) -> bool ---
|
||||
|
||||
type_is_matrix_row_major :: proc($T: typeid) -> bool where type_is_matrix(T) ---
|
||||
@@ -215,6 +217,8 @@ type_proc_return_count :: proc($T: typeid) -> int where type_is_proc(T) ---
|
||||
type_proc_parameter_type :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) ---
|
||||
type_proc_return_type :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) ---
|
||||
|
||||
type_proc_calling_convention :: proc($T: typeid) -> Odin_Calling_Convention where type_is_proc(T) ---
|
||||
|
||||
type_struct_field_count :: proc($T: typeid) -> int where type_is_struct(T) ---
|
||||
type_struct_has_implicit_padding :: proc($T: typeid) -> bool where type_is_struct(T) ---
|
||||
|
||||
@@ -249,6 +253,8 @@ type_integer_to_signed :: proc($T: typeid) -> type where type_is_integer(T), t
|
||||
|
||||
type_has_shared_fields :: proc($U, $V: typeid) -> bool where type_is_struct(U), type_is_struct(V) ---
|
||||
|
||||
|
||||
|
||||
// Returns the canonicalized name of the type, of which is used to produce the pseudo-unique 'typeid'
|
||||
type_canonical_name :: proc($T: typeid) -> string ---
|
||||
|
||||
|
||||
@@ -41,7 +41,9 @@ Fast_Math_Flags :: intrinsics.Fast_Math_Flags
|
||||
|
||||
|
||||
// NOTE(bill): This must match the compiler's
|
||||
Calling_Convention :: enum u8 {
|
||||
|
||||
/*
|
||||
enum u8 {
|
||||
Invalid = 0,
|
||||
Odin = 1,
|
||||
Contextless = 2,
|
||||
@@ -61,6 +63,8 @@ Calling_Convention :: enum u8 {
|
||||
Preserve_Most = 12,
|
||||
Preserve_All = 13,
|
||||
}
|
||||
*/
|
||||
Calling_Convention :: type_of(ODIN_DEFAULT_CALLING_CONVENTION)
|
||||
|
||||
Type_Info_Enum_Value :: distinct i64
|
||||
|
||||
@@ -296,7 +300,79 @@ when ODIN_OS == .Windows {
|
||||
dll_instance: rawptr
|
||||
}
|
||||
|
||||
// IMPORTANT NOTE(bill): Must be in this order (as the compiler relies upon it)
|
||||
|
||||
// This is safe to change. The log2 size of a cache-line. At minimum it has to
|
||||
// be six though. Higher cache line sizes are permitted.
|
||||
MAP_CACHE_LINE_LOG2 :: 6
|
||||
|
||||
// The size of a cache-line.
|
||||
MAP_CACHE_LINE_SIZE :: 1 << MAP_CACHE_LINE_LOG2
|
||||
|
||||
// The minimum cache-line size allowed by this implementation is 64 bytes since
|
||||
// we need 6 bits in the base pointer to store the integer log2 capacity, which
|
||||
// at maximum is 63. Odin uses signed integers to represent length and capacity,
|
||||
// so only 63 bits are needed in the maximum case.
|
||||
#assert(MAP_CACHE_LINE_SIZE >= 64)
|
||||
|
||||
// Map_Cell type that packs multiple T in such a way to ensure that each T stays
|
||||
// aligned by align_of(T) and such that align_of(Map_Cell(T)) % MAP_CACHE_LINE_SIZE == 0
|
||||
//
|
||||
// This means a value of type T will never straddle a cache-line.
|
||||
//
|
||||
// When multiple Ts can fit in a single cache-line the data array will have more
|
||||
// than one element. When it cannot, the data array will have one element and
|
||||
// an array of Map_Cell(T) will be padded to stay a multiple of MAP_CACHE_LINE_SIZE.
|
||||
//
|
||||
// We rely on the type system to do all the arithmetic and padding for us here.
|
||||
//
|
||||
// The usual array[index] indexing for []T backed by a []Map_Cell(T) becomes a bit
|
||||
// more involved as there now may be internal padding. The indexing now becomes
|
||||
//
|
||||
// N :: len(Map_Cell(T){}.data)
|
||||
// i := index / N
|
||||
// j := index % N
|
||||
// cell[i].data[j]
|
||||
//
|
||||
// However, since len(Map_Cell(T){}.data) is a compile-time constant, there are some
|
||||
// optimizations we can do to eliminate the need for any divisions as N will
|
||||
// be bounded by [1, 64).
|
||||
//
|
||||
// In the optimal case, len(Map_Cell(T){}.data) = 1 so the cell array can be treated
|
||||
// as a regular array of T, which is the case for hashes.
|
||||
Map_Cell :: struct($T: typeid) #align(MAP_CACHE_LINE_SIZE) {
|
||||
data: [MAP_CACHE_LINE_SIZE / size_of(T) when 0 < size_of(T) && size_of(T) < MAP_CACHE_LINE_SIZE else 1]T,
|
||||
}
|
||||
|
||||
// So we can operate on a cell data structure at runtime without any type
|
||||
// information, we have a simple table that stores some traits about the cell.
|
||||
//
|
||||
// 32-bytes on 64-bit
|
||||
// 16-bytes on 32-bit
|
||||
Map_Cell_Info :: struct {
|
||||
size_of_type: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits
|
||||
align_of_type: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits
|
||||
size_of_cell: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits
|
||||
elements_per_cell: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits
|
||||
}
|
||||
|
||||
Map_Hash :: uintptr
|
||||
|
||||
// When working with the type-erased structure at runtime we need information
|
||||
// about the map to make working with it possible. This info structure stores
|
||||
// that.
|
||||
//
|
||||
// `Map_Info` and `Map_Cell_Info` are read only data structures and cannot be
|
||||
// modified after creation
|
||||
//
|
||||
// 32-bytes on 64-bit
|
||||
// 16-bytes on 32-bit
|
||||
Map_Info :: struct {
|
||||
ks: ^Map_Cell_Info, // 8-bytes on 64-bit, 4-bytes on 32-bit
|
||||
vs: ^Map_Cell_Info, // 8-bytes on 64-bit, 4-bytes on 32-bit
|
||||
key_hasher: proc "contextless" (key: rawptr, seed: Map_Hash) -> Map_Hash, // 8-bytes on 64-bit, 4-bytes on 32-bit
|
||||
key_equal: proc "contextless" (lhs, rhs: rawptr) -> bool, // 8-bytes on 64-bit, 4-bytes on 32-bit
|
||||
}
|
||||
|
||||
|
||||
|
||||
Source_Code_Location :: struct {
|
||||
|
||||
@@ -2,6 +2,8 @@ package runtime
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
MAP_ENABLED :: !ODIN_BEDROCK
|
||||
|
||||
@builtin
|
||||
Maybe :: union($T: typeid) {T}
|
||||
|
||||
@@ -65,7 +67,7 @@ when !NO_DEFAULT_TEMP_ALLOCATOR {
|
||||
// Initializes the global temporary allocator used as the default `context.temp_allocator`.
|
||||
// This is ignored when `NO_DEFAULT_TEMP_ALLOCATOR` is true.
|
||||
@(builtin, disabled=NO_DEFAULT_TEMP_ALLOCATOR)
|
||||
init_global_temporary_allocator :: proc(size: int, backup_allocator := context.allocator) {
|
||||
init_global_temporary_allocator :: proc "odin" (size: int, backup_allocator := context.allocator) {
|
||||
when !NO_DEFAULT_TEMP_ALLOCATOR {
|
||||
default_temp_allocator_init(&global_default_temp_allocator_data, size, backup_allocator)
|
||||
}
|
||||
@@ -387,7 +389,7 @@ pop_front_safe :: proc {
|
||||
@builtin
|
||||
clear :: proc{
|
||||
clear_dynamic_array,
|
||||
clear_map,
|
||||
clear_map where MAP_ENABLED,
|
||||
clear_fixed_capacity_dynamic_array,
|
||||
|
||||
clear_soa_dynamic_array,
|
||||
@@ -397,7 +399,7 @@ clear :: proc{
|
||||
@builtin
|
||||
reserve :: proc{
|
||||
reserve_dynamic_array,
|
||||
reserve_map,
|
||||
reserve_map where MAP_ENABLED,
|
||||
|
||||
reserve_soa,
|
||||
}
|
||||
@@ -430,7 +432,7 @@ non_zero_resize :: proc{
|
||||
@builtin
|
||||
shrink :: proc{
|
||||
shrink_dynamic_array,
|
||||
shrink_map,
|
||||
shrink_map where MAP_ENABLED,
|
||||
}
|
||||
|
||||
// `free` will try to free the passed pointer, with the given `allocator` if the allocator supports this operation.
|
||||
@@ -471,14 +473,6 @@ delete_dynamic_array :: proc(array: $T/[dynamic]$E, loc := #caller_location) ->
|
||||
delete_slice :: proc(array: $T/[]$E, allocator := context.allocator, loc := #caller_location) -> Allocator_Error {
|
||||
return mem_free_with_size(raw_data(array), len(array)*size_of(E), allocator, loc)
|
||||
}
|
||||
// `delete_map` will try to free the underlying data of the passed map, with the given `allocator` if the allocator supports this operation.
|
||||
//
|
||||
// Note: Prefer the procedure group `delete`.
|
||||
@builtin
|
||||
delete_map :: proc(m: $T/map[$K]$V, loc := #caller_location) -> Allocator_Error {
|
||||
return map_free_dynamic(transmute(Raw_Map)m, map_info(T), loc)
|
||||
}
|
||||
|
||||
|
||||
@builtin
|
||||
delete_string16 :: proc(str: string16, allocator := context.allocator, loc := #caller_location) -> Allocator_Error {
|
||||
@@ -489,6 +483,16 @@ delete_cstring16 :: proc(str: cstring16, allocator := context.allocator, loc :=
|
||||
return mem_free((^u16)(str), allocator, loc)
|
||||
}
|
||||
|
||||
when MAP_ENABLED {
|
||||
// `delete_map` will try to free the underlying data of the passed map, with the given `allocator` if the allocator supports this operation.
|
||||
//
|
||||
// Note: Prefer the procedure group `delete`.
|
||||
@builtin
|
||||
delete_map :: proc(m: $T/map[$K]$V, loc := #caller_location) -> Allocator_Error {
|
||||
return map_free_dynamic(transmute(Raw_Map)m, map_info(T), loc)
|
||||
}
|
||||
}
|
||||
|
||||
// `delete` will try to free the underlying data of the passed built-in data structure (string, cstring, dynamic array, slice, or map), with the given `allocator` if the allocator supports this operation.
|
||||
//
|
||||
// Note: Prefer `delete` over the specific `delete_*` procedures where possible.
|
||||
@@ -498,7 +502,7 @@ delete :: proc{
|
||||
delete_cstring,
|
||||
delete_dynamic_array,
|
||||
delete_slice,
|
||||
delete_map,
|
||||
delete_map where MAP_ENABLED,
|
||||
delete_soa_slice,
|
||||
delete_soa_dynamic_array,
|
||||
delete_string16,
|
||||
@@ -597,29 +601,32 @@ _make_dynamic_array_len_cap :: proc(array: ^Raw_Dynamic_Array, size_of_elem, ali
|
||||
return
|
||||
}
|
||||
|
||||
// `make_map` initializes a map with an allocator. Like `new`, the first argument is a type, not a value.
|
||||
// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it.
|
||||
//
|
||||
// Note: Prefer using the procedure group `make`.
|
||||
@(builtin, require_results)
|
||||
make_map :: proc($T: typeid/map[$K]$E, allocator := context.allocator, loc := #caller_location) -> (m: T) {
|
||||
m.allocator = allocator
|
||||
return m
|
||||
when MAP_ENABLED {
|
||||
// `make_map` initializes a map with an allocator. Like `new`, the first argument is a type, not a value.
|
||||
// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it.
|
||||
//
|
||||
// Note: Prefer using the procedure group `make`.
|
||||
@(builtin, require_results)
|
||||
make_map :: proc($T: typeid/map[$K]$E, allocator := context.allocator, loc := #caller_location) -> (m: T) {
|
||||
m.allocator = allocator
|
||||
return m
|
||||
}
|
||||
|
||||
// `make_map_cap` initializes a map with an allocator and allocates space using `capacity`.
|
||||
// Like `new`, the first argument is a type, not a value.
|
||||
// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it.
|
||||
//
|
||||
// Note: Prefer using the procedure group `make`.
|
||||
@(builtin, require_results)
|
||||
make_map_cap :: proc($T: typeid/map[$K]$E, #any_int capacity: int, allocator := context.allocator, loc := #caller_location) -> (m: T, err: Allocator_Error) #optional_allocator_error {
|
||||
make_map_expr_error_loc(loc, capacity)
|
||||
context.allocator = allocator
|
||||
|
||||
err = reserve_map(&m, capacity, loc)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// `make_map_cap` initializes a map with an allocator and allocates space using `capacity`.
|
||||
// Like `new`, the first argument is a type, not a value.
|
||||
// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it.
|
||||
//
|
||||
// Note: Prefer using the procedure group `make`.
|
||||
@(builtin, require_results)
|
||||
make_map_cap :: proc($T: typeid/map[$K]$E, #any_int capacity: int, allocator := context.allocator, loc := #caller_location) -> (m: T, err: Allocator_Error) #optional_allocator_error {
|
||||
make_map_expr_error_loc(loc, capacity)
|
||||
context.allocator = allocator
|
||||
|
||||
err = reserve_map(&m, capacity, loc)
|
||||
return
|
||||
}
|
||||
// `make_multi_pointer` allocates and initializes a multi-pointer. Like `new`, the first argument is a type, not a value.
|
||||
// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it.
|
||||
//
|
||||
@@ -649,8 +656,8 @@ make :: proc{
|
||||
make_dynamic_array,
|
||||
make_dynamic_array_len,
|
||||
make_dynamic_array_len_cap,
|
||||
make_map,
|
||||
make_map_cap,
|
||||
make_map where MAP_ENABLED,
|
||||
make_map_cap where MAP_ENABLED,
|
||||
make_multi_pointer,
|
||||
|
||||
make_soa_slice,
|
||||
@@ -659,53 +666,54 @@ make :: proc{
|
||||
make_soa_dynamic_array_len_cap,
|
||||
}
|
||||
|
||||
when MAP_ENABLED {
|
||||
|
||||
// `clear_map` will set the length of a passed map to `0`
|
||||
//
|
||||
// Note: Prefer the procedure group `clear`
|
||||
@builtin
|
||||
clear_map :: proc "contextless" (m: ^$T/map[$K]$V) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
map_clear_dynamic((^Raw_Map)(m), map_info(T))
|
||||
}
|
||||
|
||||
// `clear_map` will set the length of a passed map to `0`
|
||||
//
|
||||
// Note: Prefer the procedure group `clear`
|
||||
@builtin
|
||||
clear_map :: proc "contextless" (m: ^$T/map[$K]$V) {
|
||||
if m == nil {
|
||||
// `reserve_map` will try to reserve memory of a passed map to the requested element count (setting the `cap`).
|
||||
//
|
||||
// Note: Prefer the procedure group `reserve`
|
||||
@builtin
|
||||
reserve_map :: proc(m: ^$T/map[$K]$V, #any_int capacity: int, loc := #caller_location) -> Allocator_Error {
|
||||
return __dynamic_map_reserve((^Raw_Map)(m), map_info(T), uint(capacity), loc)
|
||||
}
|
||||
|
||||
// Shrinks the capacity of a map down to the current length.
|
||||
//
|
||||
// Note: Prefer the procedure group `shrink`
|
||||
@builtin
|
||||
shrink_map :: proc(m: ^$T/map[$K]$V, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) {
|
||||
if m != nil {
|
||||
return map_shrink_dynamic((^Raw_Map)(m), map_info(T), loc)
|
||||
}
|
||||
return
|
||||
}
|
||||
map_clear_dynamic((^Raw_Map)(m), map_info(T))
|
||||
}
|
||||
|
||||
// `reserve_map` will try to reserve memory of a passed map to the requested element count (setting the `cap`).
|
||||
//
|
||||
// Note: Prefer the procedure group `reserve`
|
||||
@builtin
|
||||
reserve_map :: proc(m: ^$T/map[$K]$V, #any_int capacity: int, loc := #caller_location) -> Allocator_Error {
|
||||
return __dynamic_map_reserve((^Raw_Map)(m), map_info(T), uint(capacity), loc)
|
||||
}
|
||||
|
||||
// Shrinks the capacity of a map down to the current length.
|
||||
//
|
||||
// Note: Prefer the procedure group `shrink`
|
||||
@builtin
|
||||
shrink_map :: proc(m: ^$T/map[$K]$V, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) {
|
||||
if m != nil {
|
||||
return map_shrink_dynamic((^Raw_Map)(m), map_info(T), loc)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// The delete_key built-in procedure deletes the element with the specified key (m[key]) from the map.
|
||||
// If m is nil, or there is no such element, this procedure is a no-op
|
||||
// It is safe to use `delete_key` while iterating a map.
|
||||
// But if you iterate across a map and insert a new key, it could resize which means you are not iterating across all of the elements.
|
||||
@builtin
|
||||
delete_key :: proc(m: ^$T/map[$K]$V, key: K) -> (deleted_key: K, deleted_value: V) {
|
||||
if m != nil {
|
||||
key := key
|
||||
old_k, old_v, ok := map_erase_dynamic((^Raw_Map)(m), map_info(T), uintptr(&key))
|
||||
if ok {
|
||||
deleted_key = (^K)(old_k)^
|
||||
deleted_value = (^V)(old_v)^
|
||||
// The delete_key built-in procedure deletes the element with the specified key (m[key]) from the map.
|
||||
// If m is nil, or there is no such element, this procedure is a no-op
|
||||
// It is safe to use `delete_key` while iterating a map.
|
||||
// But if you iterate across a map and insert a new key, it could resize which means you are not iterating across all of the elements.
|
||||
@builtin
|
||||
delete_key :: proc(m: ^$T/map[$K]$V, key: K) -> (deleted_key: K, deleted_value: V) {
|
||||
if m != nil {
|
||||
key := key
|
||||
old_k, old_v, ok := map_erase_dynamic((^Raw_Map)(m), map_info(T), uintptr(&key))
|
||||
if ok {
|
||||
deleted_key = (^K)(old_k)^
|
||||
deleted_value = (^V)(old_v)^
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
_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 {
|
||||
@@ -715,10 +723,10 @@ _append_elem :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, a
|
||||
|
||||
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)
|
||||
@@ -731,15 +739,66 @@ _append_elem :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, a
|
||||
return
|
||||
}
|
||||
|
||||
_append_elem_ptr :: #force_no_inline proc(array: ^Raw_Dynamic_Array, arg: 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 := max(2 * array.cap, DEFAULT_DYNAMIC_ARRAY_CAPACITY)
|
||||
|
||||
// do not 'or_return' here as it could be a partial success
|
||||
err = _reserve_dynamic_array_unsafe(array, size_of(rawptr), align_of(rawptr), cap, should_zero, loc)
|
||||
}
|
||||
if array.cap-array.len > 0 {
|
||||
data := ([^]rawptr)(array.data)
|
||||
assert(data != nil, loc=loc)
|
||||
data[array.len] = arg
|
||||
array.len += 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) -> (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 intrinsics.type_is_internally_pointer_like(E) {
|
||||
return _append_elem_ptr((^Raw_Dynamic_Array)(array), rawptr(arg), should_zero=true, loc=loc)
|
||||
} 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)
|
||||
return _append_elem((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), &arg, should_zero=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, should_zero=true, loc=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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -751,9 +810,34 @@ non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc :
|
||||
when size_of(E) == 0 {
|
||||
(^Raw_Dynamic_Array)(array).len += 1
|
||||
return 1, nil
|
||||
} else {
|
||||
} else when intrinsics.type_is_internally_pointer_like(E) {
|
||||
return _append_elem_ptr((^Raw_Dynamic_Array)(array), rawptr(arg), should_zero=false, loc=loc)
|
||||
} else when ODIN_OPTIMIZATION_MODE <= .Size {
|
||||
arg := arg
|
||||
return _append_elem((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), &arg, false, loc=loc)
|
||||
return _append_elem((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), &arg, should_zero=false, 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, should_zero=false, loc=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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1193,7 +1277,7 @@ assign_at_elem_fixed_capacity_dynamic_array :: proc "contextless" (array: ^$T/[d
|
||||
array[index] = arg
|
||||
ok = true
|
||||
} else {
|
||||
resize(array, index+1, loc) or_return
|
||||
resize(array, index+1) or_return
|
||||
array[index] = arg
|
||||
ok = true
|
||||
}
|
||||
@@ -1212,7 +1296,7 @@ assign_at_elems_fixed_capacity_dynamic_array :: proc "contextless" (array: ^$T/[
|
||||
copy(array[index:], args)
|
||||
ok = true
|
||||
} else {
|
||||
resize(array, new_size, loc) or_return
|
||||
resize(array, new_size) or_return
|
||||
copy(array[index:], args)
|
||||
ok = true
|
||||
}
|
||||
@@ -1230,7 +1314,7 @@ assign_at_elem_string_fixed_capacity_dynamic_array :: proc "contextless" (array:
|
||||
copy(array[index:], arg)
|
||||
ok = true
|
||||
} else {
|
||||
resize(array, new_size, loc) or_return
|
||||
resize(array, new_size) or_return
|
||||
copy(array[index:], arg)
|
||||
ok = true
|
||||
}
|
||||
@@ -1311,6 +1395,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`).
|
||||
@@ -1470,52 +1583,54 @@ _shrink_dynamic_array :: proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@builtin
|
||||
map_insert :: proc(m: ^$T/map[$K]$V, key: K, value: V, loc := #caller_location) -> (ptr: ^V) {
|
||||
key, value := key, value
|
||||
return (^V)(__dynamic_map_set_without_hash((^Raw_Map)(m), map_info(T), rawptr(&key), rawptr(&value), loc))
|
||||
}
|
||||
|
||||
// Explicitly inserts a key and value into a map `m`, the same as `map_insert`, but the return values differ.
|
||||
// - `prev_key` will return the previous pointer of a key if it exists, check `found_previous` if was previously found
|
||||
// - `value_ptr` will return the pointer of the memory where the insertion happens, and `nil` if the map failed to resize
|
||||
// - `found_previous` will be true a previous key was found
|
||||
@(builtin, require_results)
|
||||
map_upsert :: proc(m: ^$T/map[$K]$V, key: K, value: V, loc := #caller_location) -> (prev_key: K, value_ptr: ^V, found_previous: bool) {
|
||||
key, value := key, value
|
||||
kp, vp := __dynamic_map_set_extra_without_hash((^Raw_Map)(m), map_info(T), rawptr(&key), rawptr(&value), loc)
|
||||
if kp != nil {
|
||||
prev_key = (^K)(kp)^
|
||||
found_previous = true
|
||||
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
|
||||
return (^V)(__dynamic_map_set_without_hash((^Raw_Map)(m), map_info(T), rawptr(&key), rawptr(&value), loc))
|
||||
}
|
||||
value_ptr = (^V)(vp)
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Retrieves a pointer to the key and value for a possibly just inserted entry into the map.
|
||||
// Explicitly inserts a key and value into a map `m`, the same as `map_insert`, but the return values differ.
|
||||
// - `prev_key` will return the previous pointer of a key if it exists, check `found_previous` if was previously found
|
||||
// - `value_ptr` will return the pointer of the memory where the insertion happens, and `nil` if the map failed to resize
|
||||
// - `found_previous` will be true a previous key was found
|
||||
@(builtin, require_results)
|
||||
map_upsert :: proc(m: ^$T/map[$K]$V, key: K, value: V, loc := #caller_location) -> (prev_key: K, value_ptr: ^V, found_previous: bool) {
|
||||
key, value := key, value
|
||||
kp, vp := __dynamic_map_set_extra_without_hash((^Raw_Map)(m), map_info(T), rawptr(&key), rawptr(&value), loc)
|
||||
if kp != nil {
|
||||
prev_key = (^K)(kp)^
|
||||
found_previous = true
|
||||
}
|
||||
value_ptr = (^V)(vp)
|
||||
return
|
||||
}
|
||||
|
||||
If the `key` was not in the map `m`, an entry is inserted with the zero value and `just_inserted` will be `true`.
|
||||
Otherwise the existing entry is left untouched and pointers to its key and value are returned.
|
||||
/*
|
||||
Retrieves a pointer to the key and value for a possibly just inserted entry into the map.
|
||||
|
||||
If the map has to grow in order to insert the entry and the allocation fails, `err` is set and returned.
|
||||
If the `key` was not in the map `m`, an entry is inserted with the zero value and `just_inserted` will be `true`.
|
||||
Otherwise the existing entry is left untouched and pointers to its key and value are returned.
|
||||
|
||||
If `err` is `nil`, `key_ptr` and `value_ptr` are valid pointers and will not be `nil`.
|
||||
If the map has to grow in order to insert the entry and the allocation fails, `err` is set and returned.
|
||||
|
||||
WARN: User modification of the key pointed at by `key_ptr` should only be done if the new key is equal to (in hash) the old key.
|
||||
If that is not the case you will corrupt the map.
|
||||
*/
|
||||
@(builtin, require_results)
|
||||
map_entry :: proc(m: ^$T/map[$K]$V, key: K, loc := #caller_location) -> (key_ptr: ^K, value_ptr: ^V, just_inserted: bool, err: Allocator_Error) {
|
||||
key := key
|
||||
zero: V
|
||||
If `err` is `nil`, `key_ptr` and `value_ptr` are valid pointers and will not be `nil`.
|
||||
|
||||
_key_ptr, _value_ptr: rawptr
|
||||
_key_ptr, _value_ptr, just_inserted, err = __dynamic_map_entry((^Raw_Map)(m), map_info(T), &key, &zero, loc)
|
||||
WARN: User modification of the key pointed at by `key_ptr` should only be done if the new key is equal to (in hash) the old key.
|
||||
If that is not the case you will corrupt the map.
|
||||
*/
|
||||
@(builtin, require_results)
|
||||
map_entry :: proc(m: ^$T/map[$K]$V, key: K, loc := #caller_location) -> (key_ptr: ^K, value_ptr: ^V, just_inserted: bool, err: Allocator_Error) {
|
||||
key := key
|
||||
zero: V
|
||||
|
||||
key_ptr = (^K)(_key_ptr)
|
||||
value_ptr = (^V)(_value_ptr)
|
||||
return
|
||||
_key_ptr, _value_ptr: rawptr
|
||||
_key_ptr, _value_ptr, just_inserted, err = __dynamic_map_entry((^Raw_Map)(m), map_info(T), &key, &zero, loc)
|
||||
|
||||
key_ptr = (^K)(_key_ptr)
|
||||
value_ptr = (^V)(_value_ptr)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ when NO_DEFAULT_TEMP_ALLOCATOR {
|
||||
// `Default_Temp_Allocator` is a `nil_allocator` when `NO_DEFAULT_TEMP_ALLOCATOR` is `true`.
|
||||
Default_Temp_Allocator :: struct {}
|
||||
|
||||
default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator := context.allocator) {}
|
||||
default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator: Allocator) {}
|
||||
|
||||
default_temp_allocator_destroy :: proc "contextless" (s: ^Default_Temp_Allocator) {}
|
||||
|
||||
@@ -30,7 +30,7 @@ when NO_DEFAULT_TEMP_ALLOCATOR {
|
||||
arena: Arena,
|
||||
}
|
||||
|
||||
default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator := context.allocator) {
|
||||
default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator: Allocator) {
|
||||
_ = arena_init(&s.arena, uint(size), backing_allocator)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#+build !bedrock
|
||||
package runtime
|
||||
|
||||
import "base:intrinsics"
|
||||
@@ -47,60 +48,6 @@ MAP_MIN_LOG2_CAPACITY :: 3 // 8 elements
|
||||
// Has to be less than 100% though.
|
||||
#assert(MAP_LOAD_FACTOR < 100)
|
||||
|
||||
// This is safe to change. The log2 size of a cache-line. At minimum it has to
|
||||
// be six though. Higher cache line sizes are permitted.
|
||||
MAP_CACHE_LINE_LOG2 :: 6
|
||||
|
||||
// The size of a cache-line.
|
||||
MAP_CACHE_LINE_SIZE :: 1 << MAP_CACHE_LINE_LOG2
|
||||
|
||||
// The minimum cache-line size allowed by this implementation is 64 bytes since
|
||||
// we need 6 bits in the base pointer to store the integer log2 capacity, which
|
||||
// at maximum is 63. Odin uses signed integers to represent length and capacity,
|
||||
// so only 63 bits are needed in the maximum case.
|
||||
#assert(MAP_CACHE_LINE_SIZE >= 64)
|
||||
|
||||
// Map_Cell type that packs multiple T in such a way to ensure that each T stays
|
||||
// aligned by align_of(T) and such that align_of(Map_Cell(T)) % MAP_CACHE_LINE_SIZE == 0
|
||||
//
|
||||
// This means a value of type T will never straddle a cache-line.
|
||||
//
|
||||
// When multiple Ts can fit in a single cache-line the data array will have more
|
||||
// than one element. When it cannot, the data array will have one element and
|
||||
// an array of Map_Cell(T) will be padded to stay a multiple of MAP_CACHE_LINE_SIZE.
|
||||
//
|
||||
// We rely on the type system to do all the arithmetic and padding for us here.
|
||||
//
|
||||
// The usual array[index] indexing for []T backed by a []Map_Cell(T) becomes a bit
|
||||
// more involved as there now may be internal padding. The indexing now becomes
|
||||
//
|
||||
// N :: len(Map_Cell(T){}.data)
|
||||
// i := index / N
|
||||
// j := index % N
|
||||
// cell[i].data[j]
|
||||
//
|
||||
// However, since len(Map_Cell(T){}.data) is a compile-time constant, there are some
|
||||
// optimizations we can do to eliminate the need for any divisions as N will
|
||||
// be bounded by [1, 64).
|
||||
//
|
||||
// In the optimal case, len(Map_Cell(T){}.data) = 1 so the cell array can be treated
|
||||
// as a regular array of T, which is the case for hashes.
|
||||
Map_Cell :: struct($T: typeid) #align(MAP_CACHE_LINE_SIZE) {
|
||||
data: [MAP_CACHE_LINE_SIZE / size_of(T) when 0 < size_of(T) && size_of(T) < MAP_CACHE_LINE_SIZE else 1]T,
|
||||
}
|
||||
|
||||
// So we can operate on a cell data structure at runtime without any type
|
||||
// information, we have a simple table that stores some traits about the cell.
|
||||
//
|
||||
// 32-bytes on 64-bit
|
||||
// 16-bytes on 32-bit
|
||||
Map_Cell_Info :: struct {
|
||||
size_of_type: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits
|
||||
align_of_type: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits
|
||||
size_of_cell: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits
|
||||
elements_per_cell: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits
|
||||
}
|
||||
|
||||
// map_cell_info :: proc "contextless" ($T: typeid) -> ^Map_Cell_Info {...}
|
||||
map_cell_info :: intrinsics.type_map_cell_info
|
||||
|
||||
@@ -226,8 +173,6 @@ map_data :: #force_inline proc "contextless" (m: Raw_Map) -> uintptr {
|
||||
}
|
||||
|
||||
|
||||
Map_Hash :: uintptr
|
||||
|
||||
TOMBSTONE_MASK :: 1<<(size_of(Map_Hash)*8 - 1)
|
||||
|
||||
// Procedure to check if a slot is empty for a given hash. This is represented
|
||||
@@ -288,23 +233,6 @@ map_probe_distance :: #force_inline proc "contextless" (m: Raw_Map, hash: Map_Ha
|
||||
return (slot - uintptr(hash)) & (capacity - 1) // NOTE(bill): this is equivalent to the above, but less operations
|
||||
}
|
||||
|
||||
// When working with the type-erased structure at runtime we need information
|
||||
// about the map to make working with it possible. This info structure stores
|
||||
// that.
|
||||
//
|
||||
// `Map_Info` and `Map_Cell_Info` are read only data structures and cannot be
|
||||
// modified after creation
|
||||
//
|
||||
// 32-bytes on 64-bit
|
||||
// 16-bytes on 32-bit
|
||||
Map_Info :: struct {
|
||||
ks: ^Map_Cell_Info, // 8-bytes on 64-bit, 4-bytes on 32-bit
|
||||
vs: ^Map_Cell_Info, // 8-bytes on 64-bit, 4-bytes on 32-bit
|
||||
key_hasher: proc "contextless" (key: rawptr, seed: Map_Hash) -> Map_Hash, // 8-bytes on 64-bit, 4-bytes on 32-bit
|
||||
key_equal: proc "contextless" (lhs, rhs: rawptr) -> bool, // 8-bytes on 64-bit, 4-bytes on 32-bit
|
||||
}
|
||||
|
||||
|
||||
// The Map_Info structure is basically a pseudo-table of information for a given K and V pair.
|
||||
// map_info :: proc "contextless" ($T: typeid/map[$K]$V) -> ^Map_Info {...}
|
||||
map_info :: intrinsics.type_map_info
|
||||
@@ -905,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^))
|
||||
@@ -930,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)
|
||||
@@ -1060,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,13 +9,15 @@ when ODIN_BUILD_MODE == .Dynamic {
|
||||
@(link_name="_odin_entry_point", linkage="strong", require/*, link_section=".init"*/)
|
||||
_odin_entry_point :: proc "c" () {
|
||||
context = default_context()
|
||||
#force_no_inline _startup_runtime()
|
||||
when !ODIN_BEDROCK { #force_no_inline _startup_runtime() }
|
||||
intrinsics.__entry_point()
|
||||
}
|
||||
@(link_name="_odin_exit_point", linkage="strong", require/*, link_section=".fini"*/)
|
||||
_odin_exit_point :: proc "c" () {
|
||||
context = default_context()
|
||||
#force_no_inline _cleanup_runtime()
|
||||
when !ODIN_BEDROCK {
|
||||
#force_no_inline _cleanup_runtime()
|
||||
}
|
||||
}
|
||||
@(link_name="main", linkage="strong", require)
|
||||
main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 {
|
||||
@@ -42,9 +44,9 @@ when ODIN_BUILD_MODE == .Dynamic {
|
||||
_start_odin :: proc "c" (argc: i32, argv: [^]cstring) -> ! {
|
||||
args__ = argv[:argc]
|
||||
context = default_context()
|
||||
#force_no_inline _startup_runtime()
|
||||
when !ODIN_BEDROCK { #force_no_inline _startup_runtime() }
|
||||
intrinsics.__entry_point()
|
||||
#force_no_inline _cleanup_runtime()
|
||||
when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() }
|
||||
intrinsics.syscall(SYS_exit, 0)
|
||||
unreachable()
|
||||
}
|
||||
@@ -53,9 +55,9 @@ when ODIN_BUILD_MODE == .Dynamic {
|
||||
main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 {
|
||||
args__ = argv[:argc]
|
||||
context = default_context()
|
||||
#force_no_inline _startup_runtime()
|
||||
when !ODIN_BEDROCK { #force_no_inline _startup_runtime() }
|
||||
intrinsics.__entry_point()
|
||||
#force_no_inline _cleanup_runtime()
|
||||
when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() }
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@ when ODIN_BUILD_MODE == .Dynamic {
|
||||
|
||||
switch dll_forward_reason {
|
||||
case .Process_Attach:
|
||||
#force_no_inline _startup_runtime()
|
||||
when !ODIN_BEDROCK { #force_no_inline _startup_runtime() }
|
||||
intrinsics.__entry_point()
|
||||
case .Process_Detach:
|
||||
#force_no_inline _cleanup_runtime()
|
||||
when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() }
|
||||
case .Thread_Attach:
|
||||
break
|
||||
case .Thread_Detach:
|
||||
@@ -35,18 +35,18 @@ when ODIN_BUILD_MODE == .Dynamic {
|
||||
main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 {
|
||||
args__ = argv[:argc]
|
||||
context = default_context()
|
||||
#force_no_inline _startup_runtime()
|
||||
when !ODIN_BEDROCK { #force_no_inline _startup_runtime() }
|
||||
intrinsics.__entry_point()
|
||||
#force_no_inline _cleanup_runtime()
|
||||
when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() }
|
||||
return 0
|
||||
}
|
||||
} else when ODIN_NO_CRT {
|
||||
@(link_name="mainCRTStartup", linkage="strong", require)
|
||||
mainCRTStartup :: proc "system" () -> i32 {
|
||||
context = default_context()
|
||||
#force_no_inline _startup_runtime()
|
||||
when !ODIN_BEDROCK { #force_no_inline _startup_runtime() }
|
||||
intrinsics.__entry_point()
|
||||
#force_no_inline _cleanup_runtime()
|
||||
when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() }
|
||||
return 0
|
||||
}
|
||||
} else {
|
||||
@@ -54,9 +54,9 @@ when ODIN_BUILD_MODE == .Dynamic {
|
||||
main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 {
|
||||
args__ = argv[:argc]
|
||||
context = default_context()
|
||||
#force_no_inline _startup_runtime()
|
||||
when !ODIN_BEDROCK { #force_no_inline _startup_runtime() }
|
||||
intrinsics.__entry_point()
|
||||
#force_no_inline _cleanup_runtime()
|
||||
when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() }
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -1164,236 +1164,6 @@ extendhfsf2 :: proc "c" (value: __float16) -> f32 {
|
||||
return gnu_h2f_ieee(value)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@(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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@(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))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
__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)
|
||||
}
|
||||
}
|
||||
|
||||
__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)
|
||||
}
|
||||
}
|
||||
|
||||
when .Address in ODIN_SANITIZER_FLAGS {
|
||||
foreign {
|
||||
@(require)
|
||||
|
||||
217
base/runtime/internal_i128.odin
Normal file
217
base/runtime/internal_i128.odin
Normal file
@@ -0,0 +1,217 @@
|
||||
#+vet !cast
|
||||
#+build !bedrock
|
||||
package runtime
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
@(private="file")
|
||||
IS_WASM :: ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32
|
||||
|
||||
@(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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@(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))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#+build !bedrock
|
||||
package runtime
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
@@ -21,7 +21,7 @@ if "%VSCMD_ARG_TGT_ARCH%" neq "x64" (
|
||||
|
||||
where /Q git.exe || goto skip_git_hash
|
||||
if not exist .git\ goto skip_git_hash
|
||||
for /f "tokens=1,2" %%i IN ('git show "--pretty=%%cd %%h" "--date=format:%%Y-%%m-%%d" --no-patch --no-notes HEAD') do (
|
||||
for /f "tokens=1,2" %%i IN ('git -c "log.showSignature=false" show "--pretty=%%cd %%h" "--date=format:%%Y-%%m-%%d" --no-patch --no-notes HEAD') do (
|
||||
set CURR_DATE_TIME=%%i
|
||||
set GIT_SHA=%%j
|
||||
)
|
||||
@@ -138,4 +138,4 @@ if %release_mode% EQU 0 echo: & echo Debug compiler built. Note: run "build.bat
|
||||
|
||||
del *.obj > NUL 2> NUL
|
||||
|
||||
:end_of_build
|
||||
:end_of_build
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
SUPPORTED_LLVM_VERSIONS="22 21 20 19 18 17"
|
||||
SUGGESTED_LLVM_VERSION="22"
|
||||
MINIMUM_LLVM_VERSION="17"
|
||||
|
||||
: ${CPPFLAGS=}
|
||||
: ${CXXFLAGS=}
|
||||
: ${LDFLAGS=}
|
||||
@@ -13,8 +17,10 @@ OS_ARCH="$(uname -m)"
|
||||
OS_NAME="$(uname -s)"
|
||||
|
||||
if [ -d ".git" ] && [ -n "$(command -v git)" ]; then
|
||||
GIT_SHA=$(git show --pretty='%h' --no-patch --no-notes HEAD)
|
||||
GIT_DATE=$(git show "--pretty=%cd" "--date=format:%Y-%m" --no-patch --no-notes HEAD)
|
||||
# Counter the user's git config to show the signature in logs.
|
||||
gitnosig="-c log.showSignature=false"
|
||||
GIT_SHA=$(git $gitnosig show --pretty='%h' --no-patch --no-notes HEAD)
|
||||
GIT_DATE=$(git $gitnosig show "--pretty=%cd" "--date=format:%Y-%m" --no-patch --no-notes HEAD)
|
||||
CPPFLAGS="$CPPFLAGS -DGIT_SHA=\"$GIT_SHA\""
|
||||
else
|
||||
GIT_DATE=$(date +"%Y-%m")
|
||||
@@ -26,8 +32,6 @@ error() {
|
||||
exit 1
|
||||
}
|
||||
|
||||
SUPPORTED_LLVM_VERSIONS="22 21 20 19 18 17 14"
|
||||
|
||||
# Brew advises people not to add llvm to their $PATH, so try and use brew to find it.
|
||||
if [ -z "$LLVM_CONFIG" ] && [ -n "$(command -v brew)" ]; then
|
||||
for V in $SUPPORTED_LLVM_VERSIONS; do
|
||||
@@ -61,7 +65,22 @@ if [ -z "$LLVM_CONFIG" ]; then
|
||||
done
|
||||
|
||||
if [ -z "$LLVM_CONFIG" ]; then
|
||||
error "No supported llvm-config command found. Set LLVM_CONFIG to proceed."
|
||||
if [ -f "/etc/os-release" ]; then
|
||||
. /etc/os-release
|
||||
case "$ID" in
|
||||
ubuntu|debian|linuxmint|pop|zorin|kali|elementary|raspbian)
|
||||
echo "ERROR: No supported llvm-config command found. Set LLVM_CONFIG to proceed."
|
||||
echo "We suggest installing LLVM $SUGGESTED_LLVM_VERSION from https://apt.llvm.org"
|
||||
exit 1
|
||||
;;
|
||||
fedora|centos|rocky|almalinux|rhel|amzn|ol|centos-stream)
|
||||
echo "ERROR: No supported llvm-config command found. Set LLVM_CONFIG to proceed."
|
||||
echo "We suggest installing LLVM $SUGGESTED_LLVM_VERSION via COPR, e.g. dnf copr enable fedora-llvm-team/llvm-snapshots"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
error "No supported llvm-config command found. Set LLVM_CONFIG to proceed. We suggest LLVM $SUGGESTED_LLVM_VERSION."
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -78,8 +97,8 @@ LLVM_VERSION_MAJOR="$(echo $LLVM_VERSION | awk -F. '{print $1}')"
|
||||
LLVM_VERSION_MINOR="$(echo $LLVM_VERSION | awk -F. '{print $2}')"
|
||||
LLVM_VERSION_PATCH="$(echo $LLVM_VERSION | awk -F. '{print $3}')"
|
||||
|
||||
if [ $LLVM_VERSION_MAJOR -lt 14 ] || ([ $LLVM_VERSION_MAJOR -gt 14 ] && [ $LLVM_VERSION_MAJOR -lt 17 ]) || [ $LLVM_VERSION_MAJOR -gt 22 ]; then
|
||||
error "Invalid LLVM version $LLVM_VERSION: must be 14, 17, 18, 19, 20, 21 or 22"
|
||||
if [ $LLVM_VERSION_MAJOR -lt $MINIMUM_LLVM_VERSION ]; then
|
||||
error "Unsupported LLVM version $LLVM_VERSION: must be 17, 18, 19, 20, 21 or 22"
|
||||
fi
|
||||
|
||||
case "$OS_NAME" in
|
||||
@@ -114,7 +133,7 @@ Linux)
|
||||
;;
|
||||
OpenBSD)
|
||||
CXXFLAGS="$CXXFLAGS -I/usr/local/include $($LLVM_CONFIG --cxxflags --ldflags)"
|
||||
LDFLAGS="$LDFLAGS -lstdc++ -L/usr/local/lib -liconv"
|
||||
LDFLAGS="$LDFLAGS -lstdc++ -L/usr/local/lib -Wl,-rpath,$($LLVM_CONFIG --libdir) -liconv"
|
||||
LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
|
||||
;;
|
||||
*)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -60,13 +60,13 @@ Iterator :: struct($Value: typeid) {
|
||||
_called_next: bool,
|
||||
}
|
||||
|
||||
// init initializes a tree.
|
||||
// `init` initializes a tree.
|
||||
init :: proc {
|
||||
init_ordered,
|
||||
init_cmp,
|
||||
}
|
||||
|
||||
// init_cmp initializes a tree.
|
||||
// `init_cmp` initializes a tree.
|
||||
init_cmp :: proc(
|
||||
t: ^$T/Tree($Value),
|
||||
cmp_fn: proc(a, b: Value) -> Ordering,
|
||||
@@ -78,7 +78,7 @@ init_cmp :: proc(
|
||||
t._size = 0
|
||||
}
|
||||
|
||||
// init_ordered initializes a tree containing ordered items, with
|
||||
// `init_ordered` initializes a tree containing ordered items, with
|
||||
// a comparison function that results in an ascending order sort.
|
||||
init_ordered :: proc(
|
||||
t: ^$T/Tree($Value),
|
||||
@@ -87,7 +87,7 @@ init_ordered :: proc(
|
||||
init_cmp(t, slice.cmp_proc(Value), node_allocator)
|
||||
}
|
||||
|
||||
// destroy de-initializes a tree.
|
||||
// `destroy` de-initializes a tree.
|
||||
destroy :: proc(t: ^$T/Tree($Value), call_on_remove: bool = true) {
|
||||
iter := iterator(t, Direction.Forward)
|
||||
for _ in iterator_next(&iter) {
|
||||
@@ -95,24 +95,24 @@ destroy :: proc(t: ^$T/Tree($Value), call_on_remove: bool = true) {
|
||||
}
|
||||
}
|
||||
|
||||
// len returns the number of elements in the tree.
|
||||
// `len` returns the number of elements in the tree.
|
||||
len :: proc "contextless" (t: ^$T/Tree($Value)) -> int {
|
||||
return t._size
|
||||
}
|
||||
|
||||
// first returns the first node in the tree (in-order) or nil if and only if (⟺)
|
||||
// `first` returns the first node in the tree (in-order) or nil if and only if (⟺)
|
||||
// the tree is empty.
|
||||
first :: proc "contextless" (t: ^$T/Tree($Value)) -> ^Node(Value) {
|
||||
return tree_first_or_last_in_order(t, Direction.Backward)
|
||||
}
|
||||
|
||||
// last returns the last element in the tree (in-order) or nil if and only if (⟺)
|
||||
// `last` returns the last element in the tree (in-order) or nil if and only if (⟺)
|
||||
// the tree is empty.
|
||||
last :: proc "contextless" (t: ^$T/Tree($Value)) -> ^Node(Value) {
|
||||
return tree_first_or_last_in_order(t, Direction.Forward)
|
||||
}
|
||||
|
||||
// find finds the value in the tree, and returns the corresponding
|
||||
// `find` finds the value in the tree, and returns the corresponding
|
||||
// node or nil if and only if (⟺) the value is not present.
|
||||
find :: proc(t: ^$T/Tree($Value), value: Value) -> ^Node(Value) {
|
||||
cur := t._root
|
||||
@@ -130,7 +130,7 @@ find :: proc(t: ^$T/Tree($Value), value: Value) -> ^Node(Value) {
|
||||
return cur
|
||||
}
|
||||
|
||||
// find_or_insert attempts to insert the value into the tree, and returns
|
||||
// `find_or_insert` attempts to insert the value into the tree, and returns
|
||||
// the node, a boolean indicating if the value was inserted, and the
|
||||
// node allocator error if relevant. If the value is already
|
||||
// present, the existing node is returned un-altered.
|
||||
@@ -168,7 +168,7 @@ find_or_insert :: proc(
|
||||
return
|
||||
}
|
||||
|
||||
// remove removes a node or value from the tree, and returns true if and only if (⟺) the
|
||||
// `remove` removes a node or value from the tree, and returns true if and only if (⟺) the
|
||||
// removal was successful. While the node's value will be left intact,
|
||||
// the node itself will be freed via the tree's node allocator.
|
||||
remove :: proc {
|
||||
@@ -176,7 +176,7 @@ remove :: proc {
|
||||
remove_node,
|
||||
}
|
||||
|
||||
// remove_value removes a value from the tree, and returns true if and only if (⟺) the
|
||||
// `remove_value` removes a value from the tree, and returns true if and only if (⟺) the
|
||||
// removal was successful. While the node's value will be left intact,
|
||||
// the node itself will be freed via the tree's node allocator.
|
||||
remove_value :: proc(t: ^$T/Tree($Value), value: Value, call_on_remove: bool = true) -> bool {
|
||||
@@ -187,7 +187,7 @@ remove_value :: proc(t: ^$T/Tree($Value), value: Value, call_on_remove: bool = t
|
||||
return remove_node(t, n, call_on_remove)
|
||||
}
|
||||
|
||||
// remove_node removes a node from the tree, and returns true if and only if (⟺) the
|
||||
// `remove_node` removes a node from the tree, and returns true if and only if (⟺) the
|
||||
// removal was successful. While the node's value will be left intact,
|
||||
// the node itself will be freed via the tree's node allocator.
|
||||
remove_node :: proc(t: ^$T/Tree($Value), node: ^Node(Value), call_on_remove: bool = true) -> bool {
|
||||
@@ -249,7 +249,7 @@ remove_node :: proc(t: ^$T/Tree($Value), node: ^Node(Value), call_on_remove: boo
|
||||
return true
|
||||
}
|
||||
|
||||
// iterator returns a tree iterator in the specified direction.
|
||||
// `iterator` returns a tree iterator in the specified direction.
|
||||
iterator :: proc "contextless" (t: ^$T/Tree($Value), direction: Direction) -> Iterator(Value) {
|
||||
it: Iterator(Value)
|
||||
it._tree = transmute(^Tree(Value))t
|
||||
@@ -260,7 +260,7 @@ iterator :: proc "contextless" (t: ^$T/Tree($Value), direction: Direction) -> It
|
||||
return it
|
||||
}
|
||||
|
||||
// iterator_from_pos returns a tree iterator in the specified direction,
|
||||
// `iterator_from_pos` returns a tree iterator in the specified direction,
|
||||
// spanning the range [pos, last] (inclusive).
|
||||
iterator_from_pos :: proc "contextless" (
|
||||
t: ^$T/Tree($Value),
|
||||
@@ -280,14 +280,14 @@ iterator_from_pos :: proc "contextless" (
|
||||
return it
|
||||
}
|
||||
|
||||
// iterator_get returns the node currently pointed to by the iterator,
|
||||
// `iterator_get` returns the node currently pointed to by the iterator,
|
||||
// or nil if and only if (⟺) the node has been removed, the tree is empty, or the end
|
||||
// of the tree has been reached.
|
||||
iterator_get :: proc "contextless" (it: ^$I/Iterator($Value)) -> ^Node(Value) {
|
||||
return it._cur
|
||||
}
|
||||
|
||||
// iterator_remove removes the node currently pointed to by the iterator,
|
||||
// `iterator_remove` removes the node currently pointed to by the iterator,
|
||||
// and returns true if and only if (⟺) the removal was successful. Semantics are the
|
||||
// same as the Tree remove.
|
||||
iterator_remove :: proc(it: ^$I/Iterator($Value), call_on_remove: bool = true) -> bool {
|
||||
@@ -303,7 +303,7 @@ iterator_remove :: proc(it: ^$I/Iterator($Value), call_on_remove: bool = true) -
|
||||
return ok
|
||||
}
|
||||
|
||||
// iterator_next advances the iterator and returns the (node, true) or
|
||||
// `iterator_next` advances the iterator and returns the (node, true) or
|
||||
// or (nil, false) if and only if (⟺) the end of the tree has been reached.
|
||||
//
|
||||
// Note: The first call to iterator_next will return the first node instead
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package container_handle_map
|
||||
|
||||
import "base:runtime"
|
||||
import "base:builtin"
|
||||
import "base:intrinsics"
|
||||
@(require) import "core:container/xar"
|
||||
|
||||
@@ -139,4 +138,4 @@ dynamic_iterate :: proc "contextless" (it: ^$DHI/Dynamic_Handle_Map_Iterator($D/
|
||||
}
|
||||
it.index = 0
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -406,7 +406,7 @@ push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T, loc := #caller_location) -
|
||||
}
|
||||
|
||||
/*
|
||||
Consume `n` elements from the back of the queue.
|
||||
Consume `n` elements from the front of the queue.
|
||||
|
||||
This will raise a bounds checking error if the queue does not have enough elements.
|
||||
*/
|
||||
|
||||
@@ -63,13 +63,13 @@ Iterator :: struct($Key: typeid, $Value: typeid) {
|
||||
_called_next: bool,
|
||||
}
|
||||
|
||||
// init initializes a tree.
|
||||
// `init` initializes a tree.
|
||||
init :: proc {
|
||||
init_ordered,
|
||||
init_cmp,
|
||||
}
|
||||
|
||||
// init_cmp initializes a tree.
|
||||
// `init_cmp` initializes a tree.
|
||||
init_cmp :: proc(t: ^$T/Tree($Key, $Value), cmp_fn: proc(a, b: Key) -> Ordering, node_allocator := context.allocator) {
|
||||
t._root = nil
|
||||
t._node_allocator = node_allocator
|
||||
@@ -77,13 +77,13 @@ init_cmp :: proc(t: ^$T/Tree($Key, $Value), cmp_fn: proc(a, b: Key) -> Ordering,
|
||||
t._size = 0
|
||||
}
|
||||
|
||||
// init_ordered initializes a tree containing ordered keys, with
|
||||
// `init_ordered` initializes a tree containing ordered keys, with
|
||||
// a comparison function that results in an ascending order sort.
|
||||
init_ordered :: proc(t: ^$T/Tree($Key, $Value), node_allocator := context.allocator) where intrinsics.type_is_ordered(Key) {
|
||||
init_cmp(t, slice.cmp_proc(Key), node_allocator)
|
||||
}
|
||||
|
||||
// destroy de-initializes a tree.
|
||||
// `destroy` de-initializes a tree.
|
||||
destroy :: proc(t: ^$T/Tree($Key, $Value), call_on_remove: bool = true) {
|
||||
iter := iterator(t, .Forward)
|
||||
for _ in iterator_next(&iter) {
|
||||
@@ -95,19 +95,19 @@ len :: proc "contextless" (t: $T/Tree($Key, $Value)) -> (node_count: int) {
|
||||
return t._size
|
||||
}
|
||||
|
||||
// first returns the first node in the tree (in-order) or nil if and only if (⟺)
|
||||
// `first` returns the first node in the tree (in-order) or nil if and only if (⟺)
|
||||
// the tree is empty.
|
||||
first :: proc "contextless" (t: ^$T/Tree($Key, $Value)) -> ^Node(Key, Value) {
|
||||
return tree_first_or_last_in_order(t, Direction.Backward)
|
||||
}
|
||||
|
||||
// last returns the last element in the tree (in-order) or nil if and only if (⟺)
|
||||
// `last` returns the last element in the tree (in-order) or nil if and only if (⟺)
|
||||
// the tree is empty.
|
||||
last :: proc "contextless" (t: ^$T/Tree($Key, $Value)) -> ^Node(Key, Value) {
|
||||
return tree_first_or_last_in_order(t, Direction.Forward)
|
||||
}
|
||||
|
||||
// find finds the key in the tree, and returns the corresponding node, or nil if and only if (⟺) the value is not present.
|
||||
// `find` finds the key in the tree, and returns the corresponding node, or nil if and only if (⟺) the value is not present.
|
||||
find :: proc(t: $T/Tree($Key, $Value), key: Key) -> (node: ^Node(Key, Value)) {
|
||||
node = t._root
|
||||
for node != nil {
|
||||
@@ -120,7 +120,7 @@ find :: proc(t: $T/Tree($Key, $Value), key: Key) -> (node: ^Node(Key, Value)) {
|
||||
return node
|
||||
}
|
||||
|
||||
// find_value finds the key in the tree, and returns the corresponding value, or nil if and only if (⟺) the value is not present.
|
||||
// `find_value` finds the key in the tree, and returns the corresponding value, or nil if and only if (⟺) the value is not present.
|
||||
find_value :: proc(t: $T/Tree($Key, $Value), key: Key) -> (value: Value, ok: bool) #optional_ok {
|
||||
if n := find(t, key); n != nil {
|
||||
return n.value, true
|
||||
@@ -128,10 +128,36 @@ find_value :: proc(t: $T/Tree($Key, $Value), key: Key) -> (value: Value, ok: boo
|
||||
return
|
||||
}
|
||||
|
||||
// find_or_insert attempts to insert the key-value pair into the tree, and returns
|
||||
// `find_or_insert` attempts to insert the key-value pair into the tree, and returns
|
||||
// the node, a boolean indicating if a new node was inserted, and the
|
||||
// node allocator error if relevant. If the key is already present, the existing node is updated and returned.
|
||||
// node allocator error if relevant. If the key is already present, the existing node is returned un-altered.
|
||||
find_or_insert :: proc(t: ^$T/Tree($Key, $Value), key: Key, value: Value) -> (n: ^Node(Key, Value), inserted: bool, err: runtime.Allocator_Error) {
|
||||
n_ptr := &t._root
|
||||
for n_ptr^ != nil {
|
||||
n = n_ptr^
|
||||
switch t._cmp_fn(key, n.key) {
|
||||
case .Less:
|
||||
n_ptr = &n._left
|
||||
case .Greater:
|
||||
n_ptr = &n._right
|
||||
case .Equal:
|
||||
return
|
||||
}
|
||||
}
|
||||
_parent := n
|
||||
|
||||
n = new_clone(Node(Key, Value){key=key, value=value, _parent=_parent, _color=.Red}, t._node_allocator) or_return
|
||||
n_ptr^ = n
|
||||
insert_case1(t, n)
|
||||
t._size += 1
|
||||
return n, true, nil
|
||||
}
|
||||
|
||||
|
||||
// `upsert` attempts to insert the key-value pair into the tree, and returns
|
||||
// the node, a boolean indicating if a new node was inserted, and the
|
||||
// node allocator error if relevant. If the key is already present, the existing node's value is updated.
|
||||
upsert :: proc(t: ^$T/Tree($Key, $Value), key: Key, value: Value) -> (n: ^Node(Key, Value), inserted: bool, err: runtime.Allocator_Error) {
|
||||
n_ptr := &t._root
|
||||
for n_ptr^ != nil {
|
||||
n = n_ptr^
|
||||
@@ -154,7 +180,7 @@ find_or_insert :: proc(t: ^$T/Tree($Key, $Value), key: Key, value: Value) -> (n:
|
||||
return n, true, nil
|
||||
}
|
||||
|
||||
// remove removes a node or value from the tree, and returns true if and only if (⟺) the
|
||||
// `remove` removes a node or value from the tree, and returns true if and only if (⟺) the
|
||||
// removal was successful. While the node's value will be left intact,
|
||||
// the node itself will be freed via the tree's node allocator.
|
||||
remove :: proc {
|
||||
@@ -162,7 +188,7 @@ remove :: proc {
|
||||
remove_node,
|
||||
}
|
||||
|
||||
// remove_value removes a value from the tree, and returns true if and only if (⟺) the
|
||||
// `remove_value` removes a value from the tree, and returns true if and only if (⟺) the
|
||||
// removal was successful. While the node's key + value will be left intact,
|
||||
// the node itself will be freed via the tree's node allocator.
|
||||
remove_key :: proc(t: ^$T/Tree($Key, $Value), key: Key, call_on_remove := true) -> bool {
|
||||
@@ -173,7 +199,7 @@ remove_key :: proc(t: ^$T/Tree($Key, $Value), key: Key, call_on_remove := true)
|
||||
return remove_node(t, n, call_on_remove)
|
||||
}
|
||||
|
||||
// remove_node removes a node from the tree, and returns true if and only if (⟺) the
|
||||
// `remove_node` removes a node from the tree, and returns true if and only if (⟺) the
|
||||
// removal was successful. While the node's key + value will be left intact,
|
||||
// the node itself will be freed via the tree's node allocator.
|
||||
remove_node :: proc(t: ^$T/Tree($Key, $Value), node: ^$N/Node(Key, Value), call_on_remove := true) -> (found: bool) {
|
||||
@@ -207,7 +233,7 @@ remove_node :: proc(t: ^$T/Tree($Key, $Value), node: ^$N/Node(Key, Value), call_
|
||||
return true
|
||||
}
|
||||
|
||||
// iterator returns a tree iterator in the specified direction.
|
||||
// `iterator` returns a tree iterator in the specified direction.
|
||||
iterator :: proc "contextless" (t: ^$T/Tree($Key, $Value), direction: Direction) -> Iterator(Key, Value) {
|
||||
it: Iterator(Key, Value)
|
||||
it._tree = cast(^Tree(Key, Value))t
|
||||
@@ -218,7 +244,7 @@ iterator :: proc "contextless" (t: ^$T/Tree($Key, $Value), direction: Direction)
|
||||
return it
|
||||
}
|
||||
|
||||
// iterator_from_pos returns a tree iterator in the specified direction,
|
||||
// `iterator_from_pos` returns a tree iterator in the specified direction,
|
||||
// spanning the range [pos, last] (inclusive).
|
||||
iterator_from_pos :: proc "contextless" (t: ^$T/Tree($Key, $Value), pos: ^Node(Key, Value), direction: Direction) -> Iterator(Key, Value) {
|
||||
it: Iterator(Key, Value)
|
||||
@@ -234,14 +260,14 @@ iterator_from_pos :: proc "contextless" (t: ^$T/Tree($Key, $Value), pos: ^Node(K
|
||||
return it
|
||||
}
|
||||
|
||||
// iterator_get returns the node currently pointed to by the iterator,
|
||||
// `iterator_get` returns the node currently pointed to by the iterator,
|
||||
// or nil if and only if (⟺) the node has been removed, the tree is empty, or the end
|
||||
// of the tree has been reached.
|
||||
iterator_get :: proc "contextless" (it: ^$I/Iterator($Key, $Value)) -> ^Node(Key, Value) {
|
||||
return it._cur
|
||||
}
|
||||
|
||||
// iterator_remove removes the node currently pointed to by the iterator,
|
||||
// `iterator_remove` removes the node currently pointed to by the iterator,
|
||||
// and returns true if and only if (⟺) the removal was successful. Semantics are the
|
||||
// same as the Tree remove.
|
||||
iterator_remove :: proc(it: ^$I/Iterator($Key, $Value), call_on_remove: bool = true) -> bool {
|
||||
@@ -257,7 +283,7 @@ iterator_remove :: proc(it: ^$I/Iterator($Key, $Value), call_on_remove: bool = t
|
||||
return ok
|
||||
}
|
||||
|
||||
// iterator_next advances the iterator and returns the (node, true) or
|
||||
// `iterator_next` advances the iterator and returns the (node, true) or
|
||||
// or (nil, false) if and only if (⟺) the end of the tree has been reached.
|
||||
//
|
||||
// Note: The first call to iterator_next will return the first node instead
|
||||
|
||||
@@ -265,7 +265,7 @@ array_push_back_elem :: proc(x: ^$X/Array($T, $SHIFT), value: T, loc := #caller_
|
||||
|
||||
chunk_idx, elem_idx, chunk_cap := _meta_get(SHIFT, uint(x.len))
|
||||
if x.chunks[chunk_idx] == nil {
|
||||
x.chunks[chunk_idx] = make([^]T, chunk_cap, x.allocator) or_return
|
||||
x.chunks[chunk_idx] = make([^]T, chunk_cap, x.allocator, loc) or_return
|
||||
}
|
||||
x.chunks[chunk_idx][elem_idx] = value
|
||||
x.len += 1
|
||||
@@ -328,7 +328,7 @@ array_push_back_elem_and_get_ptr :: proc(x: ^$X/Array($T, $SHIFT), value: T, loc
|
||||
|
||||
chunk_idx, elem_idx, chunk_cap := _meta_get(SHIFT, uint(x.len))
|
||||
if x.chunks[chunk_idx] == nil {
|
||||
x.chunks[chunk_idx] = make([^]T, chunk_cap, x.allocator) or_return
|
||||
x.chunks[chunk_idx] = make([^]T, chunk_cap, x.allocator, loc) or_return
|
||||
}
|
||||
x.chunks[chunk_idx][elem_idx] = value
|
||||
x.len += 1
|
||||
|
||||
@@ -13,10 +13,11 @@ constant-time byte comparison.
|
||||
- The crypto packages are not thread-safe.
|
||||
- Best-effort is make to mitigate timing side-channels on reasonable
|
||||
architectures. Architectures that are known to be unreasonable include
|
||||
but are not limited to i386, i486, and WebAssembly.
|
||||
but are not limited to i386, i486, VIA Nano 2000, ARM7T/ARM9T/Cortex-M3,
|
||||
and WASM.
|
||||
- Implementations assume a 64-bit architecture (64-bit integer arithmetic
|
||||
is fast, and includes add-with-carry, sub-with-borrow, and full-result
|
||||
multiply).
|
||||
is fast, and includes contant-time add-with-carry, sub-with-borrow, and
|
||||
full-result multiply).
|
||||
- Hardware sidechannels are explicitly out of scope for this package.
|
||||
Notable examples include but are not limited to:
|
||||
- Power/RF side-channels etc.
|
||||
@@ -29,4 +30,4 @@ constant-time byte comparison.
|
||||
|
||||
## License
|
||||
|
||||
This library is made available under the zlib license.
|
||||
This library is made available under the zlib license.
|
||||
|
||||
904
core/crypto/_bigint/i31.odin
Normal file
904
core/crypto/_bigint/i31.odin
Normal file
@@ -0,0 +1,904 @@
|
||||
// Constant time Big Integers
|
||||
package _bigint
|
||||
|
||||
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:crypto"
|
||||
import subtle "core:crypto/_subtle"
|
||||
import "core:slice"
|
||||
|
||||
// Integers 'i31'
|
||||
// --------------
|
||||
//
|
||||
// The 'i31' functions implement computations on big integers using
|
||||
// an internal representation as an array of 32-bit integers. For
|
||||
// an array `x`:
|
||||
// -- x[0] encodes the array length and the "announced bit length"
|
||||
// of the integer: namely, if the announced bit length is k,
|
||||
// then x[0] = ((k / 31) << 5) + (k % 31).
|
||||
// -- x[1], x[2]... contain the value in little-endian order, 31
|
||||
// bits per word (x[1] contains the least significant 31 bits).
|
||||
// The upper bit of each word is 0.
|
||||
//
|
||||
// Multiplications rely on the elementary 32x32->64 multiplication.
|
||||
//
|
||||
// The announced bit length specifies the number of bits that are
|
||||
// significant in the subsequent 32-bit words. Unused bits in the
|
||||
// last (most significant) word are set to 0; subsequent words are
|
||||
// uninitialized and need not exist at all.
|
||||
//
|
||||
// The execution time and memory access patterns of all computations
|
||||
// depend on the announced bit length, but not on the actual word
|
||||
// values. For modular integers, the announced bit length of any integer
|
||||
// modulo `n` is equal to the actual bit length of `n`; thus, computations
|
||||
// on modular integers are "constant-time" (only the modulus length may leak).
|
||||
|
||||
I31_MASK :: 0x7fff_ffff
|
||||
|
||||
// Compute the bit length of a 32-bit integer.
|
||||
// Returned value is between 0 and 32 (inclusive).
|
||||
@(require_results)
|
||||
_u32_bit_length :: proc "contextless" (x: u32) -> (length: u32) {
|
||||
x := x
|
||||
k := subtle.neq(x, 0)
|
||||
c := subtle.gt(x, 0xFFFF); x = subtle.csel(x, x >> 16, c); k += c << 4
|
||||
c = subtle.gt(x, 0x00FF); x = subtle.csel(x, x >> 8, c); k += c << 3
|
||||
c = subtle.gt(x, 0x000F); x = subtle.csel(x, x >> 4, c); k += c << 2
|
||||
c = subtle.gt(x, 0x0003); x = subtle.csel(x, x >> 2, c); k += c << 1
|
||||
k += subtle.gt(x, 0x0001)
|
||||
return k
|
||||
}
|
||||
|
||||
// Multiply two 31-bit integers, with a 62-bit result. This default
|
||||
// implementation assumes that the basic multiplication operator
|
||||
// yields constant-time code.
|
||||
//
|
||||
// The mul31_lo() returns only the low 31 bits of the product.
|
||||
//
|
||||
// Note/Odin:
|
||||
// The original BearSSL code provides alternative implemenetations
|
||||
// of these routines gated behind `BR_CT_MUL31`, however that macro
|
||||
// is only useful on Intel 80386/80486, VIA Nano 2000, and ARM7T/ARM9T.
|
||||
@(require_results)
|
||||
_mul31 :: #force_inline proc "contextless" (x, y: u32) -> (res: u64) {
|
||||
return u64(x) * u64(y)
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
_mul31_lo :: #force_inline proc "contextless" (x, y: u32) -> (res: u32) {
|
||||
return (x * y) & I31_MASK
|
||||
}
|
||||
|
||||
// Wrapper for `div_rem`; the remainder is returned, and the quotient is
|
||||
// discarded.
|
||||
@(private, require_results)
|
||||
_rem_u32 :: #force_inline proc "contextless" (hi: u32, lo: u32, d: u32) -> (res: u32) {
|
||||
_, rem := div_rem_u32(hi, lo, d)
|
||||
return rem
|
||||
}
|
||||
|
||||
// Wrapper for `div_rem`; the quotient is returned, and the remainder is
|
||||
// discarded.
|
||||
@(private="file", require_results)
|
||||
_div_u32 :: #force_inline proc "contextless" (hi: u32, lo: u32, d: u32) -> (quo: u32) {
|
||||
q, _ := div_rem_u32(hi, lo, d)
|
||||
return q
|
||||
}
|
||||
|
||||
// Constant-time division. The dividend `hi:lo` is divided by the divisor `d`;
|
||||
// the quotient and remainder are returned.
|
||||
//
|
||||
// If `hi == d`, then the quotient does not fit on 32 bits; returned value is thus truncated.
|
||||
// If `hi > d`, returned values are indeterminate.
|
||||
@(require_results)
|
||||
div_rem_u32 :: proc "contextless" (hi: u32, lo: u32, d: u32) -> (quo: u32, rem: u32) {
|
||||
// TODO: optimize this
|
||||
hi := hi
|
||||
lo := lo
|
||||
ch := subtle.eq(hi, d)
|
||||
hi = subtle.csel(hi, 0, ch)
|
||||
for k := uint(31); k > 0; k -= 1 {
|
||||
j := 32 - k
|
||||
w := (hi << j) | (lo >> k)
|
||||
ctl := subtle.ge(w, d) | (hi >> k)
|
||||
hi2 := (w - d) >> j
|
||||
lo2 := lo - (d << k)
|
||||
hi = subtle.csel(hi, hi2, ctl)
|
||||
lo = subtle.csel(lo, lo2, ctl)
|
||||
quo |= ctl << k
|
||||
}
|
||||
cf := subtle.ge(lo, d) | hi
|
||||
quo |= cf
|
||||
rem = subtle.csel(lo, lo - d, cf)
|
||||
return
|
||||
}
|
||||
|
||||
// i31_rem computes x / y and returns the remainder.
|
||||
@(require_results)
|
||||
i31_rem :: proc "contextless" (x: []u32, y: u32) -> u32 {
|
||||
words := uint(x[0] + 31) >> 5
|
||||
x_ := x[1:]
|
||||
|
||||
r: u32
|
||||
for i := int(words-1); i >= 0; i -= 1 {
|
||||
r = _rem_u32(r, x_[i], y)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Test whether an integer `x` is zero.
|
||||
@(optimization_mode="none", require_results)
|
||||
i31_is_zero :: proc "contextless" (x: []u32) -> (res: u32) {
|
||||
z: u32
|
||||
|
||||
for u := (x[0] + 31) >> 5; u > 0; u -= 1 {
|
||||
z |= x[u]
|
||||
}
|
||||
return ~(z | -z) >> 31
|
||||
}
|
||||
|
||||
// Add `b` to `a` and return the `carry` (`0` or `1`). if `ctl` is `1`.
|
||||
// If `ctl` is `0`, `a` is left alone but the `carry` will still be computed.
|
||||
//
|
||||
// The slices `a` and `b` MUST have the same announced bit length (in subscript `0`)
|
||||
//
|
||||
// `a` and `b` MAY be the same array, but partial overlap is not allowed.
|
||||
@(require_results)
|
||||
i31_add :: proc "contextless" (a: []u32, b: []u32, ctl: u32) -> (carry: u32) {
|
||||
words := uint(a[0] + 63) >> 5
|
||||
for u in 1..<words {
|
||||
aw := a[u]
|
||||
bw := b[u]
|
||||
naw := aw + bw + carry
|
||||
carry = naw >> 31
|
||||
a[u] = subtle.csel(aw, naw & I31_MASK, ctl)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Subtract `b` from `a` and return the `carry` (`0` or `1`), if `ctl` is `1`.
|
||||
// If `ctl` is `0`, then `a` is unmodified, but the carry is still computed
|
||||
// and returned.
|
||||
//
|
||||
// The slices `a` and `b` MUST have the same announced bit length (in subscript `0`)
|
||||
//
|
||||
// `a` and `b` MAY be the same array, but partial overlap is not allowed.
|
||||
@(require_results)
|
||||
i31_sub :: proc "contextless" (a: []u32, b: []u32, ctl: u32) -> (carry: u32) {
|
||||
words := uint(a[0] + 63) >> 5
|
||||
for u in 1..<words {
|
||||
aw := a[u]
|
||||
bw := b[u]
|
||||
naw := aw - bw - carry
|
||||
carry = naw >> 31
|
||||
a[u] = subtle.csel(aw, naw & I31_MASK, ctl)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Compute the ENCODED actual bit length of an integer `x`.
|
||||
// The argument `x` should point to the first (least significant)
|
||||
// value word of the integer.
|
||||
//
|
||||
// The upper bit of each value word MUST be `0`.
|
||||
//
|
||||
// Returned value is `((k / 31) << 5) + (k % 31)` if the bit length is `k`.
|
||||
//
|
||||
// CT: value or length of `x` does not leak.
|
||||
@(require_results)
|
||||
i31_bit_length :: proc "contextless" (x: []u32) -> (res: u32) {
|
||||
tw, twk: u32
|
||||
|
||||
xlen := len(x)
|
||||
for xlen > 0 {
|
||||
xlen -= 1
|
||||
c := subtle.eq(tw, 0)
|
||||
w := x[xlen]
|
||||
|
||||
tw = subtle.csel(tw, w, c)
|
||||
twk = subtle.csel(twk, u32(xlen), c)
|
||||
}
|
||||
return (twk << 5) + _u32_bit_length(tw)
|
||||
}
|
||||
|
||||
// Decode an integer from its big-endian unsigned representation. The
|
||||
// "true" bit length of the integer is computed and set in the encoded
|
||||
// announced bit length (`x[0]`), but all words of `x` corresponding to
|
||||
// the full slice of source bytes.
|
||||
//
|
||||
// `x` needs to have a minimum length of: `1 + ((len(src) * 8) + 31) / 31`
|
||||
//
|
||||
// CT: value or length of `x` does not leak.
|
||||
i31_decode :: proc "contextless" (x: []u32, src: []byte) {
|
||||
u := len(src) - 1
|
||||
v := 1
|
||||
acc := u32(0)
|
||||
acc_len := uint(0)
|
||||
for u >= 0 {
|
||||
b := u32(src[u])
|
||||
acc |= b << acc_len
|
||||
acc_len += 8
|
||||
if acc_len >= 31 {
|
||||
x[v] = acc & I31_MASK
|
||||
acc_len -= 31
|
||||
acc = b >> (8 - acc_len)
|
||||
v += 1
|
||||
}
|
||||
u -= 1
|
||||
}
|
||||
if acc_len != 0 {
|
||||
x[v] = acc
|
||||
v += 1
|
||||
}
|
||||
x[0] = i31_bit_length(x[1:])
|
||||
}
|
||||
|
||||
// Decode an integer from its big-endian unsigned representation.
|
||||
// The integer MUST be lower than `m`; the (encoded) announced bit length
|
||||
// written in `x` will be equal to that of `m`. All bytes from the
|
||||
// `src` slice are read.
|
||||
//
|
||||
// Returned value is `1` if the decode value fits within the modulus, `0`
|
||||
// otherwise. In the latter case, the `x` buffer will be set to `0` (but
|
||||
// still with the announced bit length of `m`).
|
||||
//
|
||||
// CT: value or length of `x` does not leak. Memory access pattern depends
|
||||
// only `src`'s length and the announced bit length of `m`. Whether `x` fits or
|
||||
// not does not leak either.
|
||||
@(require_results)
|
||||
i31_decode_mod :: proc "contextless" (x: []u32, src: []byte, m: []u32) -> (res: u32) {
|
||||
// Two-pass algorithm: in the first pass, we determine whether the
|
||||
// value fits; in the second pass, we do the actual write.
|
||||
//
|
||||
// During the first pass, `res` contains the comparison result so far:
|
||||
// 0x00000000 value is equal to the modulus
|
||||
// 0x00000001 value is greater than the modulus
|
||||
// 0xFFFFFFFF value is lower than the modulus
|
||||
//
|
||||
// Since we iterate starting with the least significant bytes (at
|
||||
// the end of `src`), each new comparison overrides the previous
|
||||
// except when the comparison yields 0 (equal).
|
||||
//
|
||||
// During the second pass, `res` is either 0xFFFFFFFF (value fits) 0x00000000 (value does not fit).
|
||||
// We must iterate over all bytes of the source, _and_ possibly
|
||||
// some extra virtual bytes (with value 0) so as to cover the
|
||||
// complete modulus as well. We also add 4 such extra bytes beyond
|
||||
// the modulus length because it then guarantees that no accumulated
|
||||
// partial word remains to be processed.
|
||||
_len := uint(len(src))
|
||||
mlen := uint((m[0] + 31) >> 5)
|
||||
tlen := uint(mlen << 2)
|
||||
if tlen < _len {
|
||||
tlen = _len
|
||||
}
|
||||
tlen += 4
|
||||
|
||||
for pass in 0..<2 {
|
||||
v := uint(1)
|
||||
acc := u32(0)
|
||||
acc_len := u32(0)
|
||||
|
||||
for u in uint(0)..<tlen {
|
||||
b: u32 = ---
|
||||
|
||||
if u < _len {
|
||||
b = u32(src[_len - 1 - u])
|
||||
} else {
|
||||
b = 0
|
||||
}
|
||||
|
||||
acc |= (b << acc_len)
|
||||
acc_len += 8
|
||||
if acc_len >= 31 {
|
||||
xw := acc & I31_MASK
|
||||
acc_len -= 31
|
||||
|
||||
acc = b >> (8 - acc_len)
|
||||
if v <= mlen {
|
||||
if pass == 1 {
|
||||
x[v] = res & xw
|
||||
} else {
|
||||
cc := u32(subtle.cmp(xw, m[v]))
|
||||
res = subtle.csel(cc, res, subtle.eq(cc, 0))
|
||||
}
|
||||
} else {
|
||||
if pass == 0 {
|
||||
res = subtle.csel(1, res, subtle.eq(xw, 0))
|
||||
}
|
||||
}
|
||||
v += 1
|
||||
}
|
||||
}
|
||||
|
||||
// When we reach this point at the end of the first pass:
|
||||
// r is either 0, 1 or -1; we want to set r to 0 if it
|
||||
// is equal to 0 or 1, and leave it to -1 otherwise.
|
||||
//
|
||||
// When we reach this point at the end of the second pass:
|
||||
// r is either 0 or -1; we want to leave that value
|
||||
// untouched. This is a subcase of the previous.
|
||||
res >>= 1
|
||||
res |= (res << 1)
|
||||
}
|
||||
|
||||
x[0] = m[0]
|
||||
|
||||
return res & 1
|
||||
}
|
||||
|
||||
// Zeroize integer `x`. The announced bit length is set to the provided value,
|
||||
// and the corresponding words are set to 0. The ENCODED bit length is expected
|
||||
//here.
|
||||
i31_zero :: proc "contextless" (x: []u32, bit_len: u32) {
|
||||
x[0] = bit_len
|
||||
intrinsics.mem_zero(raw_data(x[1:]), ((bit_len + 31) >> 5) * size_of(u32))
|
||||
}
|
||||
|
||||
// Make a random integer of the provided size. The size is encoded.
|
||||
// The header word is untouched.
|
||||
i31_mkrand :: proc(x: []u32, esize: u32) {
|
||||
_len := (esize + 31) >> 5
|
||||
x_ := slice.reinterpret([]byte, x)
|
||||
crypto.rand_bytes(x_[4:4 + _len * size_of(u32)])
|
||||
for u in 1..<_len {
|
||||
x[u] &= I31_MASK
|
||||
}
|
||||
m := _len & 31
|
||||
if m == 0 {
|
||||
x[_len] &= I31_MASK
|
||||
} else {
|
||||
x[_len] &= I31_MASK >> (31 - m)
|
||||
}
|
||||
}
|
||||
|
||||
// Right-shift an integer. The shift amount must be lower than 31 bits.
|
||||
i31_rshift :: proc "contextless" (x: []u32, shift_amount: i32) {
|
||||
_len := uint(x[0] + 31) >> 5
|
||||
if _len == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
count := uint(shift_amount)
|
||||
|
||||
r := x[1] >> count
|
||||
for u in 2..= _len {
|
||||
w := u32(x[u])
|
||||
|
||||
x[u - 1] = ((w << (31 - count)) | r) & I31_MASK
|
||||
r = w >> count
|
||||
}
|
||||
x[_len] = r
|
||||
}
|
||||
|
||||
// Reduce integer `a` modulo `m`. The result is written to `x`,
|
||||
// and its announced bit length is set to be equal to that of `m`.
|
||||
//
|
||||
// `x` MUST be distinct from `a` and `m`.
|
||||
//
|
||||
// CT: only announced bit lengths leak, not values of `x`, `a` or `m`.
|
||||
i31_reduce :: proc "contextless" (x: []u32, a: []u32, m: []u32) {
|
||||
m_bitlen := m[0]
|
||||
mlen := uint(m_bitlen + 31) >> 5
|
||||
|
||||
x[0] = m_bitlen
|
||||
if m_bitlen == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// If the source is shorter, then simply copy all words from a[]
|
||||
// and zero out the upper words.
|
||||
a_bitlen := a[0]
|
||||
alen := uint(a_bitlen + 31) >> 5
|
||||
if a_bitlen < m_bitlen {
|
||||
copy(x[1:], a[1:][:alen])
|
||||
for u in alen..<mlen {
|
||||
x[u + 1] = 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// The source length is at least equal to that of the modulus.
|
||||
// We must thus copy N-1 words, and input the remaining words one
|
||||
// by one.
|
||||
copy(x[1:], a[2 + (alen - mlen):][:mlen - 1])
|
||||
x[mlen] = 0
|
||||
for u := 1 + alen - mlen; u > 0; u -= 1 {
|
||||
i31_muladd_small(x, a[u], m)
|
||||
}
|
||||
}
|
||||
|
||||
// Decode an integer from its big-endian unsigned representation, and
|
||||
// reduce it modulo the provided modulus `m`. The announced bit length
|
||||
// of the result is set to be equal to that of the modulus.
|
||||
//
|
||||
// `x` MUST be distinct from `m`.
|
||||
i31_decode_reduce :: proc "contextless" (x: []u32, src: []byte, m: []u32) {
|
||||
// Get the encoded bit length.
|
||||
m_ebitlen := m[0]
|
||||
|
||||
// Special case for an invalid (null) modulus.
|
||||
if m_ebitlen == 0 {
|
||||
x[0] = 0
|
||||
return
|
||||
}
|
||||
|
||||
// Clear the destination.
|
||||
i31_zero(x, m_ebitlen)
|
||||
|
||||
// First decode directly as many bytes as possible.
|
||||
// This requires computing the actual bit length.
|
||||
m_rbitlen := m_ebitlen >> 5
|
||||
m_rbitlen = (m_ebitlen & 31) + (m_rbitlen << 5) - m_rbitlen
|
||||
|
||||
mblen := uint(m_rbitlen + 7) >> 3
|
||||
k := mblen - 1
|
||||
_len := uint(len(src))
|
||||
|
||||
if k >= _len {
|
||||
i31_decode(x, src)
|
||||
x[0] = m_ebitlen
|
||||
return
|
||||
}
|
||||
|
||||
i31_decode(x, src[:k])
|
||||
x[0] = m_ebitlen
|
||||
|
||||
// Input remaining bytes, using 31-bit words.
|
||||
acc := u32(0)
|
||||
acc_len := uint(0)
|
||||
|
||||
for {
|
||||
v := u32(src[k])
|
||||
|
||||
if acc_len >= 23 {
|
||||
acc_len -= 23
|
||||
acc <<= (8 - acc_len)
|
||||
acc |= v >> acc_len
|
||||
i31_muladd_small(x, acc, m)
|
||||
acc = v & (0xFF >> (8 - acc_len))
|
||||
} else {
|
||||
acc = (acc << 8) | v
|
||||
acc_len += 8
|
||||
}
|
||||
|
||||
if k += 1; k >= _len {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// We may have some bits accumulated. We then perform a shift to
|
||||
// be able to inject these bits as a full 31-bit word.
|
||||
if acc_len != 0 {
|
||||
acc = (acc | (x[1] << acc_len)) & I31_MASK
|
||||
i31_rshift(x, i32(31 - acc_len))
|
||||
i31_muladd_small(x, acc, m)
|
||||
}
|
||||
}
|
||||
|
||||
// Multiply `x` by 2^31 and then add integer `z`, modulo `m`.
|
||||
// This function assumes that `x` and `m` have the same announced bit
|
||||
// length, the announced bit length of `m` matches its true bit length.
|
||||
//
|
||||
// `x` and `m` MUST be distinct arrays.
|
||||
// `z` MUST fit in 31 bits (upper bit set to 0).
|
||||
//
|
||||
// CT: only the common announced bit length of `x` and `m` leaks, not
|
||||
// the values of `x`, `z` or `m`.
|
||||
i31_muladd_small :: proc "contextless" (x: []u32, z: u32, m: []u32) {
|
||||
// We can test on the modulus bit length since we accept to leak
|
||||
// that length.
|
||||
m_bitlen := m[0]
|
||||
if m_bitlen == 0 {
|
||||
return
|
||||
}
|
||||
hi: u32
|
||||
if m_bitlen <= 31 {
|
||||
hi = x[1] >> 1
|
||||
lo := (x[1] << 31) | z
|
||||
x[1] = _rem_u32(hi, lo, m[1])
|
||||
return
|
||||
}
|
||||
mlen := uint(m_bitlen + 31) >> 5
|
||||
mblr := uint(m_bitlen) & 31
|
||||
|
||||
// Principle: we estimate the quotient (x*2^31+z)/m by
|
||||
// doing a 64/32 division with the high words.
|
||||
//
|
||||
// Let:
|
||||
// w = 2^31
|
||||
// a = (w*a0 + a1) * w^N + a2
|
||||
// b = b0 * w^N + b2
|
||||
// such that:
|
||||
// 0 <= a0 < w
|
||||
// 0 <= a1 < w
|
||||
// 0 <= a2 < w^N
|
||||
// w/2 <= b0 < w
|
||||
// 0 <= b2 < w^N
|
||||
// a < w*b
|
||||
// I.e. the two top words of a are a0:a1, the top word of b is
|
||||
// b0, we ensured that b0 is "full" (high bit set), and a is
|
||||
// such that the quotient q = a/b fits on one word (0 <= q < w).
|
||||
//
|
||||
// If a = b*q + r (with 0 <= r < q), we can estimate q by
|
||||
// doing an Euclidean division on the top words:
|
||||
// a0*w+a1 = b0*u + v (with 0 <= v < b0)
|
||||
// Then the following holds:
|
||||
// 0 <= u <= w
|
||||
// u-2 <= q <= u
|
||||
hi = x[mlen]
|
||||
a0, a1, b0: u32
|
||||
if mblr == 0 {
|
||||
a0 = x[mlen]
|
||||
intrinsics.mem_copy(raw_data(x[2:]), raw_data(x[1:]), (mlen - 1) * size_of(u32))
|
||||
x[1] = z
|
||||
a1 = x[mlen]
|
||||
b0 = m[mlen]
|
||||
} else {
|
||||
a0 = ((x[mlen] << (31 - mblr)) | (x[mlen - 1] >> mblr)) & I31_MASK
|
||||
intrinsics.mem_copy(raw_data(x[2:]), raw_data(x[1:]), (mlen - 1) * size_of(u32))
|
||||
x[1] = z
|
||||
a1 = ((x[mlen] << (31 - mblr)) | (x[mlen - 1] >> mblr)) & I31_MASK
|
||||
b0 = ((m[mlen] << (31 - mblr)) | (m[mlen - 1] >> mblr)) & I31_MASK
|
||||
}
|
||||
|
||||
// We estimate a divisor q. If the quotient returned by div()
|
||||
// is g:
|
||||
// -- If a0 == b0 then g == 0; we want q = 0x7FFFFFFF.
|
||||
// -- Otherwise:
|
||||
// -- if g == 0 then we set q = 0;
|
||||
// -- otherwise, we set q = g - 1.
|
||||
// The properties described above then ensure that the true
|
||||
// quotient is q-1, q or q+1.
|
||||
//
|
||||
// Take care that a0, a1 and b0 are 31-bit words, not 32-bit. We
|
||||
// must adjust the parameters to br_div() accordingly.
|
||||
g := _div_u32(a0 >> 1, a1 | (a0 << 31), b0)
|
||||
q := subtle.csel(subtle.csel(g - 1, 0, subtle.eq(g, 0)), I31_MASK, subtle.eq(a0, b0))
|
||||
|
||||
// We subtract q*m from x (with the extra high word of value 'hi').
|
||||
// Since q may be off by 1 (in either direction), we may have to
|
||||
// add or subtract m afterwards.
|
||||
//
|
||||
// The 'tb' flag will be true (1) at the end of the loop if the
|
||||
// result is greater than or equal to the modulus (not counting
|
||||
// 'hi' or the carry).
|
||||
cc := u32(0)
|
||||
tb := u32(1)
|
||||
for u in 1..= mlen {
|
||||
mw := m[u]
|
||||
zl := _mul31(mw, q) + u64(cc)
|
||||
cc = u32(zl >> 31)
|
||||
zw := u32(zl) & I31_MASK
|
||||
xw := x[u]
|
||||
nxw := xw - zw
|
||||
cc += nxw >> 31
|
||||
nxw &= I31_MASK
|
||||
x[u] = nxw
|
||||
tb = subtle.csel(subtle.gt(nxw, mw), tb, subtle.eq(nxw, mw))
|
||||
}
|
||||
|
||||
// If we underestimated q, then either cc < hi (one extra bit
|
||||
// beyond the top array word), or cc == hi and tb is true (no
|
||||
// extra bit, but the result is not lower than the modulus). In
|
||||
// these cases we must subtract m once.
|
||||
//
|
||||
// Otherwise, we may have overestimated, which will show as
|
||||
// cc > hi (thus a negative result). Correction is adding m once.
|
||||
over := subtle.gt(cc, hi)
|
||||
under := ~over & (tb | subtle.lt(cc, hi))
|
||||
_ = i31_add(x, m, over)
|
||||
_ = i31_sub(x, m, under)
|
||||
}
|
||||
|
||||
// Encode an integer into its big-endian unsigned representation. The
|
||||
// output length in bytes is provided (parameter 'len'); if the length
|
||||
// is too short then the integer is appropriately truncated; if it is
|
||||
// too long then the extra bytes are set to 0.
|
||||
i31_encode :: proc "contextless" (dst: []byte, x: []u32) {
|
||||
xlen := uint(x[0] + 31) >> 5
|
||||
if xlen == 0 {
|
||||
intrinsics.mem_zero(raw_data(dst[:]), len(dst) * size_of(u32))
|
||||
return
|
||||
}
|
||||
_len := uint(len(dst))
|
||||
k := uint(1)
|
||||
acc := u32(0)
|
||||
acc_len := uint(0)
|
||||
for _len != 0 {
|
||||
w := (k <= xlen) ? x[k] : 0
|
||||
k += 1
|
||||
if (acc_len == 0) {
|
||||
acc = w
|
||||
acc_len = 31
|
||||
} else {
|
||||
z := acc | (w << acc_len)
|
||||
acc_len -= 1
|
||||
acc = w >> (31 - acc_len)
|
||||
if _len >= 4 {
|
||||
_len -= 4
|
||||
ptr := (^u32be)(raw_data(dst[_len:]))
|
||||
intrinsics.unaligned_store(ptr, u32be(z))
|
||||
} else {
|
||||
switch _len {
|
||||
case 3:
|
||||
dst[_len - 3] = byte(z >> 16)
|
||||
fallthrough
|
||||
case 2:
|
||||
dst[_len - 2] = byte(z >> 8)
|
||||
fallthrough
|
||||
case 1:
|
||||
dst[_len - 1] = byte(z)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute `-(1/x) % 2^31`. If `x` is even, then this function returns `0`.
|
||||
i31_ninv31 :: proc "contextless" (x: u32) -> (y: u32) {
|
||||
y = 2 - x
|
||||
y *= 2 - y * x
|
||||
y *= 2 - y * x
|
||||
y *= 2 - y * x
|
||||
y *= 2 - y * x
|
||||
return subtle.csel(0, -y, x & 1) & I31_MASK
|
||||
}
|
||||
|
||||
// Compute a modular Montgomery multiplication. `d` is filled with the
|
||||
// value of `x*y/R % m` (where `R` is the Montgomery factor).
|
||||
//
|
||||
// The array `d` MUST be distinct from `x`, `y` and `m`[].
|
||||
// `x` and `y` MUST be numerically lower than `m`.
|
||||
//
|
||||
// `x` and `y` MAY be the same array.
|
||||
//
|
||||
// The `m0i` parameter is equal to `-(1/m0) mod 2^31`, where `m0` is the least
|
||||
// significant value word of `m` (this works only if `m` is an odd integer).
|
||||
i31_montymul :: proc "contextless" (d: []u32, x: []u32, y: []u32, m: []u32, m0i: u32) {
|
||||
// Each outer loop iteration computes:
|
||||
// `d <- (d + xu*y + f*m) / 2^31`
|
||||
// We have `xu <= 2^31-1` and `f <= 2^31-1`.
|
||||
// Thus, if `d <= 2*m-1` on input, then:
|
||||
// `2*m-1 + 2*(2^31-1)*m <= (2^32)*m-1`
|
||||
// and the new `d` value is less than `2*m`.
|
||||
//
|
||||
// We represent `d` over 31-bit words, with an extra word `dh`,
|
||||
// which can thus be only 0 or 1.
|
||||
_len := uint((m[0] + 31) >> 5)
|
||||
len4 := _len & ~uint(3)
|
||||
i31_zero(d, m[0])
|
||||
dh := u32(0)
|
||||
for u in 0..<_len {
|
||||
// The carry for each operation fits on 32 bits:
|
||||
// `d[v+1] <= 2^31-1`
|
||||
// `xu*y[v+1] <= (2^31-1)*(2^31-1)`
|
||||
// `f*m[v+1] <= (2^31-1)*(2^31-1)`
|
||||
// `r <= 2^32-1`
|
||||
// `(2^31-1) + 2*(2^31-1)*(2^31-1) + (2^32-1) = 2^63 - 2^31`
|
||||
//
|
||||
// After division by `2^31`, the new `r` is then at most `2^32-1`
|
||||
//
|
||||
// Using a 32-bit carry has performance benefits on 32-bit
|
||||
// systems; however, on 64-bit architectures, we prefer to
|
||||
// keep the carry (r) in a 64-bit register, thus avoiding some
|
||||
// "clear high bits" operations.
|
||||
xu := x[u + 1]
|
||||
f := _mul31_lo((d[1] + _mul31_lo(xu, y[1])), m0i)
|
||||
|
||||
r := u64(0)
|
||||
v := uint(0)
|
||||
for ; v < len4; v += 4 {
|
||||
z := u64(d[v + 1]) + _mul31(xu, y[v + 1]) + _mul31(f, m[v + 1]) + r
|
||||
r = z >> 31
|
||||
d[v + 0] = u32(z) & I31_MASK
|
||||
z = u64(d[v + 2]) + _mul31(xu, y[v + 2]) + _mul31(f, m[v + 2]) + r
|
||||
r = z >> 31
|
||||
d[v + 1] = u32(z) & I31_MASK
|
||||
z = u64(d[v + 3]) + _mul31(xu, y[v + 3]) + _mul31(f, m[v + 3]) + r
|
||||
r = z >> 31
|
||||
d[v + 2] = u32(z) & I31_MASK
|
||||
z = u64(d[v + 4]) + _mul31(xu, y[v + 4]) + _mul31(f, m[v + 4]) + r
|
||||
r = z >> 31
|
||||
d[v + 3] = u32(z) & I31_MASK
|
||||
}
|
||||
for ; v < _len; v += 1 {
|
||||
z := u64(d[v + 1]) + _mul31(xu, y[v + 1]) + _mul31(f, m[v + 1]) + r
|
||||
r = z >> 31
|
||||
d[v] = u32(z) & I31_MASK
|
||||
}
|
||||
|
||||
// Since the new `dh` can only be `0` or `1`, the addition of
|
||||
// the old dh with the carry MUST fit on 32 bits, and
|
||||
// thus can be done into dh itself.
|
||||
dh += u32(r)
|
||||
d[_len] = dh & I31_MASK
|
||||
dh >>= 31
|
||||
}
|
||||
|
||||
// We must write back the bit length because it was overwritten in
|
||||
// the loop (not overwriting it would require a test in the loop,
|
||||
// which would yield bigger and slower code).
|
||||
d[0] = m[0]
|
||||
|
||||
// `d` may still be greater than `m` at that point; notably, the `dh`
|
||||
// word may be non-zero.
|
||||
_ = i31_sub(d, m, subtle.neq(dh, 0) | subtle.not(i31_sub(d, m, 0)))
|
||||
}
|
||||
|
||||
// Convert a modular integer to Montgomery representation.
|
||||
//
|
||||
// The integer `x` MUST be lower than `m`, but with the same announced bit length.
|
||||
i31_to_monty :: proc "contextless" (x: []u32, m: []u32) {
|
||||
// uint32_t k;
|
||||
for k := (m[0] + 31) >> 5; k > 0; k -= 1 {
|
||||
i31_muladd_small(x, 0, m)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert a modular integer back from Montgomery representation.
|
||||
//
|
||||
// The integer `x` MUST be lower than `m`[], but with the same announced bit
|
||||
// length.
|
||||
//
|
||||
// The `m0i` parameter is equal to `-(1/m0) mod 2^32`, where `m0` is the least
|
||||
// significant value word of `m` (this works only if `m` is an odd integer).
|
||||
i31_from_monty :: proc "contextless" (x: []u32, m: []u32, m0i: u32) {
|
||||
_len := uint(m[0] + 31) >> 5
|
||||
for _ in 0..<_len {
|
||||
f := _mul31_lo(x[1], m0i)
|
||||
cc := u64(0)
|
||||
for v in 0..<_len {
|
||||
z := u64(x[v + 1]) + _mul31(f, m[v + 1]) + cc
|
||||
cc = z >> 31
|
||||
if v != 0 {
|
||||
x[v] = u32(z & I31_MASK)
|
||||
}
|
||||
}
|
||||
x[_len] = u32(cc)
|
||||
}
|
||||
|
||||
// We may have to do an extra subtraction, but only if the value in `x`
|
||||
// is indeed greater than or equal to that of `m`, which is why we must
|
||||
// do two calls:
|
||||
// - First call computes the carry
|
||||
// - Second call performs the subtraction only if the carry is 0).
|
||||
_ = i31_sub(x, m, subtle.not(i31_sub(x, m, 0)))
|
||||
}
|
||||
|
||||
// Compute a modular exponentiation.
|
||||
//
|
||||
// `x` MUST be an integer modulo `m` (same announced bit length, lower value).
|
||||
// `m` MUST be odd.
|
||||
//
|
||||
// The exponent `e` is in big-endian unsigned notation.
|
||||
//
|
||||
// The `m0i` parameter is equal to `-(1/m0) mod 2^31`, where `m0` is the least
|
||||
// significant value word of `m` (this works only if `m` is an odd integer).
|
||||
//
|
||||
// The `t1` and `t2` parameters must be temporary arrays, each large enough to
|
||||
// accommodate an integer with the same size as `m`.
|
||||
i31_modpow :: proc "contextless" (x: []u32, e: []byte, m: []u32, m0i: u32, t1: []u32, t2: []u32) {
|
||||
// `mlen` is the length of `m` expressed in `u32`'s (including the
|
||||
// "bit length" first field).
|
||||
mlen := uint((m[0] + 63) >> 5)
|
||||
elen := u32(len(e))
|
||||
|
||||
// Throughout the algorithm:
|
||||
// -- `t1` is in Montgomery representation; it contains x, x^2, x^4, x^8...
|
||||
// -- The result is accumulated, in normal representation, in the `x` array.
|
||||
// -- `t2` is used as destination buffer for each multiplication.
|
||||
//
|
||||
// Note that there is no need to call `i32_from_monty()`.
|
||||
copy(t1[:mlen], x[:mlen])
|
||||
i31_to_monty(t1, m)
|
||||
i31_zero(x, m[0])
|
||||
x[1] = 1
|
||||
for k := u32(0); k < (elen << 3); k += 1 {
|
||||
ctl := (e[elen - 1 - (k >> 3)] >> (k & 7)) & 1
|
||||
|
||||
i31_montymul(t2, x, t1, m, m0i)
|
||||
|
||||
for &d, i in x[:mlen] {
|
||||
d = subtle.csel(d, t2[i], ctl)
|
||||
}
|
||||
|
||||
i31_montymul(t2, t1, t1, m, m0i)
|
||||
copy(t1[:mlen], t2[:mlen])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Compute a modular exponentiation.
|
||||
//
|
||||
// `x` MUST be an integer modulo `m` (same announced bit length, lower value).
|
||||
// `m` MUST be odd.
|
||||
//
|
||||
// The exponent `e` is in big-endian unsigned notation.
|
||||
//
|
||||
// The `m0i` parameter is equal to `-(1/m0) mod 2^31`, where `m0` is the least
|
||||
// significant value word of `m`[] (this works only if m[] is an odd integer).
|
||||
//
|
||||
// The `tmp` array is used for temporaries; it must be large enough to accommodate
|
||||
// at least two temporary values with the same size as `m` (including the leading
|
||||
// "bit length" word).
|
||||
//
|
||||
// If there is room for more temporaries, then this function may use the extra
|
||||
// room for window-based optimisation, resulting in faster computations.
|
||||
//
|
||||
// Returned value is `true` on success, `false` on error. An error is reported if
|
||||
// the provided `tmp`array is too short.
|
||||
i31_modpow_opt :: proc "contextless" (x: []u32, e: []byte, m: []u32, m0i: u32, tmp: []u32) -> u32 {
|
||||
// NOTE/yawning: This is only used by the rsa_i31 code, with the key
|
||||
// generation taking a function pointer to either this routine,
|
||||
// or the i62 variant.
|
||||
//
|
||||
// If we ever need to support the i32 version, it is used extensively,
|
||||
// but non e-waste architecutures will all do the right thing with
|
||||
// the i62 version, albeit with a perforance hit on 32-bit CPUs.
|
||||
|
||||
unimplemented_contextless()
|
||||
|
||||
// i31_mod_pow(x, e, m, m0i, tmp[:len(m)], tmp[len(m):])
|
||||
// return 1
|
||||
}
|
||||
|
||||
// Compute `d+a*b`, result in `d`.
|
||||
//
|
||||
// The initial announced bit length of `d` MUST match that of `a`[].
|
||||
//
|
||||
// The `d` array MUST be large enough to accommodate the full result,
|
||||
// plus (possibly) an extra word. The resulting announced bit length
|
||||
// of `d` will be the sum of the announced bit lengths of `a` and `b`
|
||||
// (therefore, it may be larger than the actual bit length of the numerical result).
|
||||
//
|
||||
// `a` and `b` may be the same array. `d` must be disjoint from both `a` and `b`.
|
||||
i31_mulacc :: proc "contextless" (d: []u32, a: []u32, b: []u32) {
|
||||
a_len := uint((a[0] + 31) >> 5)
|
||||
b_len := uint((b[0] + 31) >> 5)
|
||||
|
||||
// We want to add the two bit lengths, but these are encoded,
|
||||
// which requires some extra care.
|
||||
d_l := (a[0] & 31) + (b[0] & 31)
|
||||
d_h := (a[0] >> 5) + (b[0] >> 5)
|
||||
d[0] = (d_h << 5) + d_l + (~u32(d_l - 31) >> 31)
|
||||
|
||||
for u in 0..<b_len {
|
||||
// Carry always fits on 31 bits; we want to keep it in a
|
||||
// 32-bit register on 32-bit architectures (on a 64-bit
|
||||
// architecture, cast down from 64 to 32 bits means
|
||||
// clearing the high bits, which is not free; on a 32-bit
|
||||
// architecture, the same operation really means ignoring
|
||||
// the top register, which has negative or zero cost).
|
||||
f := b[1 + u]
|
||||
cc := u64(0)
|
||||
for v in 0..<a_len {
|
||||
z := u64(d[1 + u + v]) + _mul31(f, a[1 + v]) + cc
|
||||
cc = z >> 31
|
||||
d[1 + u + v] = u32(z) & I31_MASK
|
||||
}
|
||||
d[1 + u + a_len] = u32(cc)
|
||||
}
|
||||
}
|
||||
361
core/crypto/_bigint/i62.odin
Normal file
361
core/crypto/_bigint/i62.odin
Normal file
@@ -0,0 +1,361 @@
|
||||
package _bigint
|
||||
|
||||
// Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:math/bits"
|
||||
import subtle "core:crypto/_subtle"
|
||||
import "core:slice"
|
||||
|
||||
@(private="file")
|
||||
I62_MASK :: 0x3fff_ffff_ffff_ffff
|
||||
|
||||
// Compute x*y+v1+v2. Operands are 64-bit, and result is 128-bit, with
|
||||
// high word in "hi" and low word in "lo".
|
||||
@(private="file", require_results)
|
||||
_fma1 :: #force_inline proc "contextless" (x, y, v1, v2: u64) -> (hi, lo: u64) {
|
||||
hi, lo = bits.mul_u64(x, y)
|
||||
|
||||
carry: u64
|
||||
lo, carry = bits.add_u64(lo, v1, 0)
|
||||
hi += carry
|
||||
|
||||
lo, carry = bits.add_u64(lo, v2, 0)
|
||||
hi += carry
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Compute x1*y1+x2*y2+v1+v2. Operands are 64-bit, and result is 128-bit,
|
||||
// with high word in "hi" and low word in "lo".
|
||||
//
|
||||
// Callers should ensure that the two inner products, and the v1 and v2
|
||||
// operands, are multiple of 4 (this is not used by this specific definition
|
||||
// but may help other implementations).
|
||||
@(private="file", require_results)
|
||||
_fma2 :: #force_inline proc "contextless" (x1, y1, x2, y2, v1, v2: u64) -> (hi, lo: u64) {
|
||||
hi_1, lo_1 := bits.mul_u64(x1, y1)
|
||||
hi_2, lo_2 := bits.mul_u64(x2, y2)
|
||||
|
||||
carry: u64
|
||||
lo, carry = bits.add_u64(lo_1, lo_2, 0)
|
||||
hi, _ = bits.add_u64(hi_1, hi_2, carry)
|
||||
|
||||
lo, carry = bits.add_u64(lo, v1, 0)
|
||||
hi += carry
|
||||
|
||||
lo, carry = bits.add_u64(lo, v2, 0)
|
||||
hi += carry
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
_mul62_lo :: #force_inline proc "contextless" (x, y: u64) -> u64 {
|
||||
return (x * y) & I62_MASK
|
||||
}
|
||||
|
||||
// Subtract b from a, and return the final carry. If 'ctl32' is 0, then
|
||||
// a[] is kept unmodified, but the final carry is still computed and
|
||||
// returned.
|
||||
@(private="file", require_results)
|
||||
_i62_sub :: proc "contextless" (a, b: []u64, num: int, ctl32: u32) -> u32 {
|
||||
cc: u64
|
||||
|
||||
ctl := -ctl32
|
||||
mask := u64(ctl) | (u64(ctl) << 32)
|
||||
for u in 0..<num {
|
||||
aw := a[u]
|
||||
bw := b[u]
|
||||
dw := aw - bw - cc
|
||||
cc = dw >> 63
|
||||
dw &= I62_MASK
|
||||
a[u] = aw ~ (mask & (dw ~ aw))
|
||||
}
|
||||
|
||||
return u32(cc)
|
||||
}
|
||||
|
||||
// Montgomery multiplication, over arrays of 62-bit values. The
|
||||
// destination array (d) must be distinct from the other operands
|
||||
// (x, y and m). All arrays are in little-endian format (least
|
||||
// significant word comes first) over 'num' words.
|
||||
@(private="file")
|
||||
_i62_montymul :: proc "contextless" (d, x, y, m: []u64, num: int, m0i: u64) {
|
||||
dh: u64
|
||||
|
||||
num4 := 1 + u64((num - 1) & ~int(3))
|
||||
intrinsics.mem_zero(raw_data(d), num * size_of(u64))
|
||||
for u in 0..<num {
|
||||
xu := x[u] << 2
|
||||
f := _mul62_lo(d[0] + _mul62_lo(x[u], y[0]), m0i) << 2
|
||||
|
||||
hi, lo := _fma2(xu, y[0], f, m[0], d[0] << 2, 0)
|
||||
r := hi
|
||||
|
||||
v: int
|
||||
for v = 1; v < int(num4); v += 4 {
|
||||
hi, lo = _fma2(xu, y[v + 0], f, m[v + 0], d[v + 0] << 2, r << 2)
|
||||
r = hi + (r >> 62)
|
||||
d[v - 1] = lo >> 2
|
||||
hi, lo = _fma2(xu, y[v + 1], f, m[v + 1], d[v + 1] << 2, r << 2)
|
||||
r = hi + (r >> 62)
|
||||
d[v + 0] = lo >> 2
|
||||
hi, lo = _fma2(xu, y[v + 2], f, m[v + 2], d[v + 2] << 2, r << 2)
|
||||
r = hi + (r >> 62)
|
||||
d[v + 1] = lo >> 2
|
||||
hi, lo = _fma2(xu, y[v + 3], f, m[v + 3], d[v + 3] << 2, r << 2)
|
||||
r = hi + (r >> 62)
|
||||
d[v + 2] = lo >> 2
|
||||
}
|
||||
for ; v < num; v += 1 {
|
||||
hi, lo = _fma2(xu, y[v], f, m[v], d[v] << 2, r << 2)
|
||||
r = hi + (r >> 62)
|
||||
d[v - 1] = lo >> 2
|
||||
}
|
||||
|
||||
zh := dh + r
|
||||
d[num - 1] = zh & I62_MASK
|
||||
dh = zh >> 62
|
||||
}
|
||||
_ = _i62_sub(d, m, num, u32(dh) | subtle.not(_i62_sub(d, m, num, 0)))
|
||||
}
|
||||
|
||||
// Conversion back from Montgomery representation.
|
||||
@(private="file")
|
||||
_i62_frommonty :: proc "contextless" (x, m: []u64, num: int, m0i: u64) {
|
||||
for _ in 0..<num {
|
||||
cc: u64
|
||||
f := _mul62_lo(x[0], m0i) << 2
|
||||
for v in 0..<num {
|
||||
hi, lo := _fma1(f, m[v], x[v] << 2, cc)
|
||||
cc = hi << 2
|
||||
if (v != 0) {
|
||||
x[v - 1] = lo >> 2
|
||||
}
|
||||
}
|
||||
x[num - 1] = cc >> 2
|
||||
}
|
||||
_ = _i62_sub(x, m, num, subtle.not(_i62_sub(x, m, num, 0)))
|
||||
}
|
||||
|
||||
// Variant of i31_modpow_opt() that internally uses 64x64->128
|
||||
// multiplications. It expects the same parameters as i31_modpow_opt(),
|
||||
// except that the temporaries should be 64-bit integers, not 32-bit
|
||||
// integers.
|
||||
i62_modpow_opt :: proc "contextless" (x31: []u32, e: []byte, m31: []u32, m0i31: u32, tmp: []u64) -> u32 {
|
||||
twlen := len(tmp)
|
||||
|
||||
// Get modulus size, in words.
|
||||
mw31num := int((m31[0] + 31) >> 5)
|
||||
mw62num := int((mw31num + 1) >> 1)
|
||||
|
||||
// In order to apply this function, we must have enough room to
|
||||
// copy the operand and modulus into the temporary array, along
|
||||
// with at least two temporaries. If there is not enough room,
|
||||
// switch to br_i31_modpow(). We also use br_i31_modpow() if the
|
||||
// modulus length is not at least four words (94 bits or more).
|
||||
if mw31num < 4 || mw62num << 2 > twlen {
|
||||
// We assume here that we can split an aligned uint64_t
|
||||
// into two properly aligned uint32_t. Since both types
|
||||
// are supposed to have an exact width with no padding,
|
||||
// then this property must hold.
|
||||
|
||||
txlen := mw31num + 1
|
||||
if twlen < txlen {
|
||||
return 0
|
||||
}
|
||||
|
||||
tmp_as_u32s := slice.reinterpret([]u32, tmp)
|
||||
t1, t2 := tmp_as_u32s[:txlen], tmp_as_u32s[txlen:]
|
||||
|
||||
i31_modpow(x31, e, m31, m0i31, t1, t2)
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
// Convert x to Montgomery representation: this means that
|
||||
// we replace x with x*2^z mod m, where z is the smallest multiple
|
||||
// of the word size such that 2^z >= m. We want to reuse the 31-bit
|
||||
// functions here (for constant-time operation), but we need z
|
||||
// for a 62-bit word size.
|
||||
for _ in 0..<mw62num {
|
||||
i31_muladd_small(x31, 0, m31)
|
||||
i31_muladd_small(x31, 0, m31)
|
||||
}
|
||||
|
||||
// Assemble operands into arrays of 62-bit words. Note that
|
||||
// all the arrays of 62-bit words that we will handle here
|
||||
// are without any leading size word.
|
||||
//
|
||||
// We also adjust tmp and twlen to account for the words used
|
||||
// for these extra arrays.
|
||||
m := tmp[:mw62num]
|
||||
x := tmp[mw62num:mw62num*2]
|
||||
tmp_ := tmp[mw62num << 1:]
|
||||
twlen -= mw62num << 1
|
||||
for u := 0; u < mw31num; u += 2 {
|
||||
v := u >> 1
|
||||
if u + 1 == mw31num {
|
||||
m[v] = u64(m31[u + 1])
|
||||
x[v] = u64(x31[u + 1])
|
||||
} else {
|
||||
m[v] = u64(m31[u + 1]) + (u64(m31[u + 2]) << 31)
|
||||
x[v] = u64(x31[u + 1]) + (u64(x31[u + 2]) << 31)
|
||||
}
|
||||
}
|
||||
|
||||
// Compute window size. We support windows up to 5 bits; for a
|
||||
// window of size k bits, we need 2^k+1 temporaries (for k = 1,
|
||||
// we use special code that uses only 2 temporaries).
|
||||
win_len: int
|
||||
for win_len = 5; win_len > 1; win_len -= 1 {
|
||||
if (1 << uint(win_len) + 1) * mw62num <= twlen {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
t1 := tmp_[:mw62num]
|
||||
t2 := tmp_[mw62num:]
|
||||
|
||||
// Compute m0i, which is equal to -(1/m0) mod 2^62. We were
|
||||
// provided with m0i31, which already fulfills this property
|
||||
// modulo 2^31; the single expression below is then sufficient.
|
||||
m0i := u64(m0i31)
|
||||
m0i = _mul62_lo(m0i, 2 + _mul62_lo(m0i, m[0]))
|
||||
|
||||
// Compute window contents. If the window has size one bit only,
|
||||
// then t2 is set to x; otherwise, t2[0] is left untouched, and
|
||||
// t2[k] is set to x^k (for k >= 1).
|
||||
if win_len == 1 {
|
||||
copy(t2, x)
|
||||
} else {
|
||||
copy(t2[mw62num:], x)
|
||||
|
||||
base := t2[mw62num:]
|
||||
for u := 2; u < 1 << uint(win_len); u += 1 {
|
||||
_i62_montymul(base[mw62num:], base, x, m, mw62num, m0i)
|
||||
base = base[mw62num:]
|
||||
}
|
||||
}
|
||||
|
||||
// Set x to 1, in Montgomery representation. We again use the
|
||||
// 31-bit code.
|
||||
i31_zero(x31, m31[0])
|
||||
x31[(m31[0] + 31) >> 5] = 1
|
||||
i31_muladd_small(x31, 0, m31)
|
||||
if mw31num & 1 != 0 {
|
||||
i31_muladd_small(x31, 0, m31)
|
||||
}
|
||||
for u := 0; u < mw31num; u+= 2 {
|
||||
v := u >> 1
|
||||
if u + 1 == mw31num {
|
||||
x[v] = u64(x31[u + 1])
|
||||
} else {
|
||||
x[v] = u64(x31[u + 1]) + (u64(x31[u + 2]) << 31)
|
||||
}
|
||||
}
|
||||
|
||||
e_, e_len := e, len(e)
|
||||
// We process bits from most to least significant. At each
|
||||
// loop iteration, we have acc_len bits in acc.
|
||||
acc: u32
|
||||
acc_len: uint
|
||||
for acc_len > 0 || e_len > 0 {
|
||||
// Get the next bits.
|
||||
k := uint(win_len)
|
||||
if acc_len < uint(win_len) {
|
||||
if e_len > 0 {
|
||||
acc = (acc << 8) | u32(e_[0])
|
||||
e_ = e_[1:]
|
||||
e_len -= 1
|
||||
acc_len += 8
|
||||
} else {
|
||||
k = acc_len
|
||||
}
|
||||
}
|
||||
bits := (acc >> (acc_len - k)) & ((u32(1) << k) - 1)
|
||||
acc_len -= k
|
||||
|
||||
// We could get exactly k bits. Compute k squarings.
|
||||
for _ in 0..<k {
|
||||
_i62_montymul(t1, x, x, m, mw62num, m0i)
|
||||
copy(x, t1)
|
||||
}
|
||||
|
||||
// Window lookup: we want to set t2 to the window
|
||||
// lookup value, assuming the bits are non-zero. If
|
||||
// the window length is 1 bit only, then t2 is
|
||||
// already set; otherwise, we do a constant-time lookup.
|
||||
if win_len > 1 {
|
||||
intrinsics.mem_zero(raw_data(t2), mw62num * size_of(u64))
|
||||
|
||||
base := t2[mw62num:]
|
||||
for u := u32(1); u < u32(1) << k; u += 1 {
|
||||
mask := -u64(subtle.eq(u, bits))
|
||||
for v in 0..<mw62num {
|
||||
t2[v] |= mask & base[v]
|
||||
}
|
||||
base = base[mw62num:]
|
||||
}
|
||||
}
|
||||
|
||||
// Multiply with the looked-up value. We keep the product
|
||||
// only if the exponent bits are not all-zero.
|
||||
_i62_montymul(t1, x, t2, m, mw62num, m0i)
|
||||
mask1 := -u64(subtle.eq(bits, 0))
|
||||
mask2 := ~mask1
|
||||
for u in 0..<mw62num {
|
||||
x[u] = (mask1 & x[u]) | (mask2 & t1[u])
|
||||
}
|
||||
}
|
||||
|
||||
// Convert back from Montgomery representation.
|
||||
_i62_frommonty(x, m, mw62num, m0i)
|
||||
|
||||
// Convert result into 31-bit words.
|
||||
for u := 0; u < mw31num; u += 2 {
|
||||
zw := u64(x[u >> 1])
|
||||
x31[u + 1] = u32(zw) & I31_MASK
|
||||
if u + 1 < mw31num {
|
||||
x31[u + 2] = u32(zw >> 31)
|
||||
}
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
// Wrapper for i62_modpow_opt() that uses the same type as
|
||||
// i31_modpow_opt(); however, it requires its 'tmp' argument to the
|
||||
// 64-bit aligned.
|
||||
i62_modpow_opt_as_i31 :: proc "contextless" (x31: []u32, e: []byte, m31: []u32, m0i31: u32, tmp: []u32) -> u32 {
|
||||
// As documented, this function expects the 'tmp' argument to be
|
||||
// 64-bit aligned. This is OK since this function is internal (it
|
||||
// is not part of BearSSL's public API).
|
||||
ensure_contextless(uintptr(raw_data(tmp)) & 7 == 0)
|
||||
ensure_contextless(len(tmp) & 1 == 0) // Length MUST be even.
|
||||
|
||||
tmp_as_u64s := slice.reinterpret([]u64, tmp)
|
||||
|
||||
return i62_modpow_opt(x31, e, m31, m0i31, tmp_as_u64s)
|
||||
}
|
||||
265
core/crypto/_bigint/i62_primes.odin
Normal file
265
core/crypto/_bigint/i62_primes.odin
Normal file
@@ -0,0 +1,265 @@
|
||||
package _bigint
|
||||
|
||||
// Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import subtle "core:crypto/_subtle"
|
||||
import "core:math/big"
|
||||
import "core:slice"
|
||||
|
||||
// Perform trial divisions on a candidate prime. We opt for the simple
|
||||
// route and "just" compute a series of trial divisions.
|
||||
//
|
||||
// Returned value is 1 on success (none of the small primes
|
||||
// divides x), 0 on error (a non-trivial GCD is obtained).
|
||||
@(private="file", require_results)
|
||||
trial_divisions :: proc "contextless" (x: []u32) -> u32 {
|
||||
for factor in big._private_prime_table {
|
||||
if factor <= 11 {
|
||||
continue
|
||||
}
|
||||
if i31_rem(x, u32(factor)) == 0 {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
// Perform n rounds of Miller-Rabin on the candidate prime x. This
|
||||
// function assumes that x = 3 mod 4.
|
||||
//
|
||||
// WARNING: t MUST be 64-bit aligned, and be large enough such that
|
||||
// it can hold 4 encoded integers that have the same number of limbs
|
||||
// as x.
|
||||
//
|
||||
// Returned value is 1 on success (all rounds completed successfully),
|
||||
// 0 otherwise.
|
||||
@(private="file", require_results)
|
||||
i62_miller_rabin :: proc(x: []u32, n: int, t: []u32) -> u32 {
|
||||
// Since x = 3 mod 4, the Miller-Rabin test is simple:
|
||||
// - get a random base a (such that 1 < a < x-1)
|
||||
// - compute z = a^((x-1)/2) mod x
|
||||
// - if z != 1 and z != x-1, the number x is composite
|
||||
//
|
||||
// We generate bases 'a' randomly with a size which is
|
||||
// one bit less than x, which ensures that a < x-1. It
|
||||
// is not useful to verify that a > 1 because the probability
|
||||
// that we get a value a equal to 0 or 1 is much smaller
|
||||
// than the probability of our Miller-Rabin tests not to
|
||||
// detect a composite, which is already quite smaller than the
|
||||
// probability of the hardware misbehaving and return a
|
||||
// composite integer because of some glitch (e.g. bad RAM
|
||||
// or ill-timed cosmic ray).
|
||||
|
||||
// Compute (x-1)/2 (encoded).
|
||||
xm1d2 := slice.reinterpret([]byte, t)
|
||||
xm1d2_len := ((x[0] - (x[0] >> 5)) + 7) >> 3
|
||||
i31_encode(xm1d2[:xm1d2_len], x)
|
||||
cc: u32
|
||||
for u in 0..<xm1d2_len {
|
||||
w := u32(xm1d2[u])
|
||||
xm1d2[u] = byte((w >> 1) | cc)
|
||||
cc = w << 7
|
||||
}
|
||||
|
||||
// We used some words of the provided buffer for (x-1)/2.
|
||||
xm1d2_len_u32 := (xm1d2_len + 3) >> 2
|
||||
t_ := t[xm1d2_len_u32:]
|
||||
tlen := len(t_)
|
||||
|
||||
xlen := (x[0] + 31) >> 5
|
||||
asize := x[0] - 1 - subtle.eq0(x[0] & 31)
|
||||
x0i := i31_ninv31(x[1])
|
||||
for _ in 0..<n {
|
||||
// Generate a random base. We don't need the base to be
|
||||
// really uniform modulo x, so we just get a random
|
||||
// number which is one bit shorter than x.
|
||||
a := t_
|
||||
a[0] = x[0]
|
||||
a[xlen] = 0
|
||||
i31_mkrand(a, asize)
|
||||
|
||||
// Compute a^((x-1)/2) mod x. We assume here that the
|
||||
// function will not fail (the temporary array is large
|
||||
// enough).
|
||||
t2 := t_[1 + xlen:]
|
||||
t2len := tlen - 1 - int(xlen)
|
||||
if (t2len & 1) != 0 {
|
||||
// Since the source array is 64-bit aligned and
|
||||
// has an even number of elements (TEMPS), we
|
||||
// can use the parity of the remaining length to
|
||||
// detect and adjust alignment.
|
||||
t2 = t2[1:]
|
||||
t2len -= 1
|
||||
}
|
||||
i62_modpow_opt_as_i31(a, xm1d2[:xm1d2_len], x, x0i, t2[:t2len])
|
||||
|
||||
// We must obtain either 1 or x-1. Note that x is odd,
|
||||
// hence x-1 differs from x only in its low word (no
|
||||
// carry).
|
||||
eq1 := a[1] ~ 1
|
||||
eqm1 := a[1] ~ (x[1] - 1)
|
||||
for u in 2..=xlen {
|
||||
eq1 |= a[u]
|
||||
eqm1 |= a[u] ~ x[u]
|
||||
}
|
||||
|
||||
if ((subtle.eq0(eq1) | subtle.eq0(eqm1)) == 0) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
// Create a random prime of the provided size. 'esize' is the _encoded_
|
||||
// bit length. The two top bits and the two bottom bits are set to 1.
|
||||
i62_mkprime :: proc(x: []u32, esize: u32, pubexp: u32, t: []u32) {
|
||||
x[0] = esize
|
||||
_len := (esize + 31) >> 5
|
||||
|
||||
for {
|
||||
// Generate random bits. We force the two top bits and the
|
||||
// two bottom bits to 1.
|
||||
i31_mkrand(x, esize)
|
||||
if (esize & 31) == 0 {
|
||||
x[_len] |= 0x60000000
|
||||
} else if (esize & 31) == 1 {
|
||||
x[_len] |= 0x00000001
|
||||
x[_len - 1] |= 0x40000000
|
||||
} else {
|
||||
x[_len] |= 0x00000003 << ((esize & 31) - 2)
|
||||
}
|
||||
x[1] |= 0x00000003
|
||||
|
||||
// Trial division with low primes (3, 5, 7 and 11). We
|
||||
// use the following properties:
|
||||
//
|
||||
// 2^2 = 1 mod 3
|
||||
// 2^4 = 1 mod 5
|
||||
// 2^3 = 1 mod 7
|
||||
// 2^10 = 1 mod 11
|
||||
m3, m5, m7, m11: u32
|
||||
s7, s11: uint
|
||||
for u in 0..<_len {
|
||||
w := x[1 + u]
|
||||
w3 := (w & 0xFFFF) + (w >> 16) // max: 98302
|
||||
w5 := (w & 0xFFFF) + (w >> 16) // max: 98302
|
||||
w7 := (w & 0x7FFF) + (w >> 15) // max: 98302
|
||||
w11 := (w & 0xFFFFF) + (w >> 20) // max: 1050622
|
||||
|
||||
m3 += w3 << (u & 1)
|
||||
m3 = (m3 & 0xFF) + (m3 >> 8) // max: 1025
|
||||
|
||||
m5 += w5 << ((4 - u) & 3)
|
||||
m5 = (m5 & 0xFFF) + (m5 >> 12) // max: 4479
|
||||
|
||||
m7 += w7 << s7
|
||||
m7 = (m7 & 0x1FF) + (m7 >> 9) // max: 1280
|
||||
s7 += 1
|
||||
if s7 == 3 {
|
||||
s7 = 0
|
||||
}
|
||||
|
||||
m11 += w11 << s11
|
||||
s11 += 1
|
||||
if s11 == 10 {
|
||||
s11 = 0
|
||||
}
|
||||
m11 = (m11 & 0x3FF) + (m11 >> 10) // max: 526847
|
||||
}
|
||||
|
||||
m3 = (m3 & 0x3F) + (m3 >> 6) // max: 78
|
||||
m3 = (m3 & 0x0F) + (m3 >> 4) // max: 18
|
||||
m3 = ((m3 * 43) >> 5) & 3
|
||||
|
||||
m5 = (m5 & 0xFF) + (m5 >> 8) // max: 271
|
||||
m5 = (m5 & 0x0F) + (m5 >> 4) // max: 31
|
||||
m5 -= 20 & -subtle.gt(m5, 19)
|
||||
m5 -= 10 & -subtle.gt(m5, 9)
|
||||
m5 -= 5 & -subtle.gt(m5, 4)
|
||||
|
||||
m7 = (m7 & 0x3F) + (m7 >> 6) // max: 82
|
||||
m7 = (m7 & 0x07) + (m7 >> 3) // max: 16
|
||||
m7 = ((m7 * 147) >> 7) & 7
|
||||
|
||||
// 2^5 = 32 = -1 mod 11.
|
||||
m11 = (m11 & 0x3FF) + (m11 >> 10) // max: 1536
|
||||
m11 = (m11 & 0x3FF) + (m11 >> 10) // max: 1023
|
||||
m11 = (m11 & 0x1F) + 33 - (m11 >> 5) // max: 64
|
||||
m11 -= 44 & -subtle.gt(m11, 43)
|
||||
m11 -= 22 & -subtle.gt(m11, 21)
|
||||
m11 -= 11 & -subtle.gt(m11, 10)
|
||||
|
||||
// If any of these modulo is 0, then the candidate is
|
||||
// not prime. Also, if pubexp is 3, 5, 7 or 11, and the
|
||||
// corresponding modulus is 1, then the candidate must
|
||||
// be rejected, because we need e to be invertible
|
||||
// modulo p-1. We can use simple comparisons here
|
||||
// because they won't leak information on a candidate
|
||||
// that we keep, only on one that we reject (and is thus
|
||||
// not secret).
|
||||
if m3 == 0 || m5 == 0 || m7 == 0 || m11 == 0 {
|
||||
continue
|
||||
}
|
||||
if (pubexp == 3 && m3 == 1) || (pubexp == 5 && m5 == 1) || (pubexp == 7 && m7 == 1) || (pubexp == 11 && m11 == 1) {
|
||||
continue
|
||||
}
|
||||
|
||||
// More trial divisions.
|
||||
if trial_divisions(x) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Miller-Rabin algorithm. Since we selected a random
|
||||
// integer, not a maliciously crafted integer, we can use
|
||||
// relatively few rounds to lower the risk of a false
|
||||
// positive (i.e. declaring prime a non-prime) under
|
||||
// 2^(-80). It is not useful to lower the probability much
|
||||
// below that, since that would be substantially below
|
||||
// the probability of the hardware misbehaving. Sufficient
|
||||
// numbers of rounds are extracted from the Handbook of
|
||||
// Applied Cryptography, note 4.49 (page 149).
|
||||
//
|
||||
// Since we work on the encoded size (esize), we need to
|
||||
// compare with encoded thresholds.
|
||||
rounds: int
|
||||
switch {
|
||||
case esize < 309:
|
||||
rounds = 12
|
||||
case esize < 464:
|
||||
rounds = 9
|
||||
case esize < 670:
|
||||
rounds = 6
|
||||
case esize < 877:
|
||||
rounds = 4
|
||||
case esize < 1341:
|
||||
rounds = 3
|
||||
case:
|
||||
rounds = 2
|
||||
}
|
||||
|
||||
if i62_miller_rabin(x, rounds, t) == 1 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ fe_equal :: proc "contextless" (arg1, arg2: ^Montgomery_Domain_Field_Element) ->
|
||||
|
||||
// This will only underflow if and only if (⟺) arg1 == arg2, and we return the borrow,
|
||||
// which will be 1.
|
||||
is_eq := subtle.u64_is_zero(fe_non_zero(&tmp))
|
||||
is_eq := subtle.eq0(fe_non_zero(&tmp))
|
||||
|
||||
fe_clear(&tmp)
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ fe_equal :: proc "contextless" (arg1, arg2: ^Montgomery_Domain_Field_Element) ->
|
||||
|
||||
// This will only underflow if and only if (⟺) arg1 == arg2, and we return the borrow,
|
||||
// which will be 1.
|
||||
is_eq := subtle.u64_is_zero(fe_non_zero(&tmp))
|
||||
is_eq := subtle.eq0(fe_non_zero(&tmp))
|
||||
|
||||
fe_clear(&tmp)
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ fe_from_bytes :: proc "contextless" (
|
||||
reduced[3], borrow = bits.sub_u64(tmp[3], ELL[3], borrow)
|
||||
reduced[4], borrow = bits.sub_u64(tmp[4], ELL[4], borrow)
|
||||
reduced[5], borrow = bits.sub_u64(tmp[5], ELL[5], borrow)
|
||||
need_reduced := subtle.u64_is_zero(borrow)
|
||||
need_reduced := subtle.eq0(borrow)
|
||||
|
||||
fe_cond_select(&tmp, &tmp, &reduced, int(need_reduced))
|
||||
fe_to_montgomery(out1, &tmp)
|
||||
|
||||
@@ -130,7 +130,7 @@ poly_frommsg :: proc "contextless" (r: ^Poly, msg: []byte) #no_bounds_check {
|
||||
|
||||
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)
|
||||
r.coeffs[8*i+j] = subtle.csel_i16(0, (Q+1)/2, (msg[i] >> uint(j))&1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,76 +3,245 @@ Various useful bit operations in constant time.
|
||||
*/
|
||||
package _subtle
|
||||
|
||||
import "core:crypto/_fiat"
|
||||
import "core:math/bits"
|
||||
import "base:intrinsics"
|
||||
|
||||
// byte_eq returns 1 if and only if (⟺) a == b, 0 otherwise.
|
||||
@(optimization_mode="none")
|
||||
byte_eq :: proc "contextless" (a, b: byte) -> int {
|
||||
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Constant-time primitives. These functions manipulate integer values in
|
||||
// order to provide constant-time comparisons and multiplexers.
|
||||
//
|
||||
// Boolean values (the "ctl" bits) MUST have value 0 or 1.
|
||||
//
|
||||
// Implementation notes:
|
||||
// =====================
|
||||
//
|
||||
// The uintN_t types are unsigned and with width exactly N bits; the C
|
||||
// standard guarantees that computations are performed modulo 2^N, and
|
||||
// there can be no overflow. Negation (unary '-') works on unsigned types
|
||||
// as well.
|
||||
//
|
||||
// The intN_t types are guaranteed to have width exactly N bits, with no
|
||||
// padding bit, and using two's complement representation. Casting
|
||||
// intN_t to uintN_t really is conversion modulo 2^N. Beware that intN_t
|
||||
// types, being signed, trigger implementation-defined behaviour on
|
||||
// overflow (including raising some signal): with GCC, while modular
|
||||
// arithmetics are usually applied, the optimizer may assume that
|
||||
// overflows don't occur (unless the -fwrapv command-line option is
|
||||
// added); Clang has the additional -ftrapv option to explicitly trap on
|
||||
// integer overflow or underflow.
|
||||
|
||||
// This code only works on a two's complement system.
|
||||
#assert((-1 & 3) == 3)
|
||||
|
||||
// not negates a boolean which MUST be `0` or `1`
|
||||
@(optimization_mode="none", require_results)
|
||||
not :: proc "contextless" (ctrl: $T) -> T where intrinsics.type_is_unsigned(T) {
|
||||
return ctrl ~ 1
|
||||
}
|
||||
|
||||
@(optimization_mode="none", require_results)
|
||||
byte_eq :: proc "contextless" (a, b: byte) -> byte {
|
||||
v := a ~ b
|
||||
|
||||
// v == 0 if and only if (⟺) a == b. The subtraction will underflow, setting the
|
||||
// sign bit, which will get returned.
|
||||
return int((u32(v)-1) >> 31)
|
||||
return byte((u32(v)-1) >> 31)
|
||||
}
|
||||
|
||||
// u64_eq returns 1 if and only if (⟺) a == b, 0 otherwise.
|
||||
@(optimization_mode="none")
|
||||
@(optimization_mode="none", require_results)
|
||||
u32_eq :: proc "contextless" (a, b: u32) -> u32 {
|
||||
q := a ~ b
|
||||
return ((q | -q) >> 31) ~ 1
|
||||
}
|
||||
|
||||
@(optimization_mode="none", require_results)
|
||||
u64_eq :: proc "contextless" (a, b: u64) -> u64 {
|
||||
_, borrow := bits.sub_u64(0, a ~ b, 0)
|
||||
return (~borrow) & 1
|
||||
q := a ~ b
|
||||
return ((q | -q) >> 63) ~ 1
|
||||
}
|
||||
|
||||
// eq returns 1 if and only if (⟺) a == b, 0 otherwise.
|
||||
eq :: proc {
|
||||
byte_eq,
|
||||
u32_eq,
|
||||
u64_eq,
|
||||
}
|
||||
|
||||
// u64_is_zero returns 1 if and only if (⟺) a == 0, 0 otherwise.
|
||||
@(optimization_mode="none")
|
||||
u64_is_zero :: proc "contextless" (a: u64) -> u64 {
|
||||
_, borrow := bits.sub_u64(a, 1, 0)
|
||||
return borrow
|
||||
@(require_results)
|
||||
byte_neq :: proc "contextless" (a, b: byte) -> byte {
|
||||
return #force_inline byte_eq(a, b) ~ 1
|
||||
}
|
||||
|
||||
// u64_is_non_zero returns 1 if and only if (⟺) a != 0, 0 otherwise.
|
||||
@(optimization_mode="none")
|
||||
u64_is_non_zero :: proc "contextless" (a: u64) -> u64 {
|
||||
is_zero := u64_is_zero(a)
|
||||
return (~is_zero) & 1
|
||||
@(optimization_mode="none", require_results)
|
||||
u32_neq :: proc "contextless" (a, b: u32) -> u32 {
|
||||
q := a ~ b
|
||||
return (q | -q) >> 31
|
||||
}
|
||||
|
||||
@(optimization_mode="none")
|
||||
cmov_bytes :: proc "contextless" (dst, src: []byte, ctrl: int) {
|
||||
@(optimization_mode="none", require_results)
|
||||
u64_neq :: proc "contextless" (a, b: u64) -> u64 {
|
||||
q := a ~ b
|
||||
return (q | -q) >> 63
|
||||
}
|
||||
|
||||
// neq returns 1 if and only if (⟺) a != b, 0 otherwise.
|
||||
neq :: proc {
|
||||
byte_neq,
|
||||
u32_neq,
|
||||
u64_neq,
|
||||
}
|
||||
|
||||
@(optimization_mode="none", require_results)
|
||||
u32_gt :: proc "contextless" (x, y: u32) -> u32 {
|
||||
/*
|
||||
* If both x < 2^31 and y < 2^31, then y-x will have its high
|
||||
* bit set if x > y, cleared otherwise.
|
||||
*
|
||||
* If either x >= 2^31 or y >= 2^31 (but not both), then the
|
||||
* result is the high bit of x.
|
||||
*
|
||||
* If both x >= 2^31 and y >= 2^31, then we can virtually
|
||||
* subtract 2^31 from both, and we are back to the first case.
|
||||
* Since (y-2^31)-(x-2^31) = y-x, the subtraction is already
|
||||
* fine.
|
||||
*/
|
||||
z := y - x
|
||||
return (z ~ ((x ~ y) & (x ~ z))) >> 31
|
||||
}
|
||||
|
||||
@(optimization_mode="none", require_results)
|
||||
u64_gt :: proc "contextless" (x, y: u64) -> u64 {
|
||||
z := y - x
|
||||
return (z ~ ((x ~ y) & (x ~ z))) >> 63
|
||||
}
|
||||
|
||||
// gt returns 1 if x > y, 0 otherwise.
|
||||
gt :: proc {
|
||||
u32_gt,
|
||||
u64_gt,
|
||||
}
|
||||
|
||||
// gt returns 1 if x >= y, 0 otherwise.
|
||||
@(require_results)
|
||||
ge :: proc "contextless" (x, y: $T) -> T where T == u32 || T == u64 {
|
||||
return #force_inline(gt(y, x)) ~ 1
|
||||
}
|
||||
|
||||
// lt returns 1 if x < y, 0 otherwise.
|
||||
@(require_results)
|
||||
lt :: proc "contextless" (x, y: $T) -> T where T == u32 || T == u64 {
|
||||
return #force_inline(gt(y, x))
|
||||
}
|
||||
|
||||
// le returns 1 if x <= y, 0 otherwise.
|
||||
@(require_results)
|
||||
le :: proc "contextless" (x, y: $T) -> T where T == u32 || T == u64 {
|
||||
return #force_inline(gt(x, y)) ~ 1
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
u32_cmp :: proc "contextless" (x, y: u32) -> i32 {
|
||||
return i32(#force_inline gt(x, y)) | -i32(#force_inline gt(y, x))
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
u64_cmp :: proc "contextless" (x, y: u64) -> i64 {
|
||||
return i64(#force_inline gt(x, y)) | -i64(#force_inline gt(y, x))
|
||||
}
|
||||
|
||||
// cmp returns -1, 0, or 1, depending on wheter x is lower than, equal
|
||||
// to, or greater than y.
|
||||
cmp :: proc {
|
||||
u32_cmp,
|
||||
u64_cmp,
|
||||
}
|
||||
|
||||
// eq0 returns 1 if and only if (⟺) a == 0, 0 otherwise.
|
||||
@(require_results)
|
||||
eq0 :: proc "contextless" (a: $T) -> T where T == u32 || T == u64 {
|
||||
return #force_inline eq(a, 0)
|
||||
}
|
||||
|
||||
// neq0 returns 1 if and only if (⟺) a != 0, 0 otherwise.
|
||||
@(require_results)
|
||||
neq0 :: proc "contextless" (a: $T) -> T where T == u32 || T == u64 {
|
||||
return #force_inline eq(a, 0) ~ 1
|
||||
}
|
||||
|
||||
cmov_bytes :: proc "contextless" (dst, src: []byte, #any_int ctrl: int) {
|
||||
ensure_contextless(len(src) == len(dst), "crypto: cmov length mismatch")
|
||||
|
||||
cmov_impl(dst, src, ctrl)
|
||||
}
|
||||
|
||||
cmov_u32s :: proc "contextless" (dst, src: []u32, #any_int ctrl: int) {
|
||||
ensure_contextless(len(src) == len(dst), "crypto: cmov length mismatch")
|
||||
|
||||
cmov_impl(dst, src, ctrl)
|
||||
}
|
||||
|
||||
@(private="file", optimization_mode="none")
|
||||
cmov_impl :: proc "contextless"(dst, src: []$T, ctrl: int) {
|
||||
s_len := len(src)
|
||||
ensure_contextless(s_len == len(dst), "crypto: cmov length mismatch")
|
||||
|
||||
c := -(byte)(ctrl)
|
||||
c := -(T)(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)
|
||||
// cmov copies `src` into `dst` if and only if (⟺) ctrl == 1. `dst` and
|
||||
// `src` may overlap completely (but not partially).
|
||||
cmov :: proc {
|
||||
cmov_bytes,
|
||||
cmov_u32s,
|
||||
}
|
||||
|
||||
@(optimization_mode="none", require_results)
|
||||
csel_i16 :: proc "contextless" (a, b: i16, #any_int ctrl: u16) -> i16 {
|
||||
c := -ctrl
|
||||
return a ~ i16(c & u16(a ~ b))
|
||||
}
|
||||
|
||||
@(optimization_mode="none")
|
||||
csel_u16 :: proc "contextless" (a, b: u16, ctrl: int) -> u16 {
|
||||
c := -(u16)(ctrl)
|
||||
@(optimization_mode="none", require_results)
|
||||
csel_u16 :: proc "contextless" (a, b: u16, #any_int ctrl: u16) -> u16 {
|
||||
c := -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)
|
||||
@(optimization_mode="none", require_results)
|
||||
csel_u32 :: proc "contextless" (a, b: u32, #any_int ctrl: u32) -> u32 {
|
||||
c := -ctrl
|
||||
return a ~ (c & (a ~ b))
|
||||
}
|
||||
|
||||
csel_u64 :: proc "contextless" (a, b: u64, ctrl: int) -> u64 {
|
||||
return _fiat.cmovznz_u64(_fiat.u1(ctrl), a, b)
|
||||
@(optimization_mode="none", require_results)
|
||||
csel_u64 :: proc "contextless" (a, b: u64, #any_int ctrl: u64) -> u64 {
|
||||
c := -ctrl
|
||||
return a ~ (c & (a ~ b))
|
||||
}
|
||||
|
||||
// csel returns `a` if ctl == `0`, `b` if ctl == `1`.
|
||||
csel :: proc {
|
||||
csel_i16,
|
||||
csel_u16,
|
||||
|
||||
@@ -196,10 +196,10 @@ fe_gen_y_p384r1 :: proc "contextless" (fe: ^Field_Element_p384r1) {
|
||||
|
||||
@(require_results)
|
||||
fe_is_zero_p256r1 :: proc "contextless" (fe: ^Field_Element_p256r1) -> int {
|
||||
return int(subtle.u64_is_zero(p256r1.fe_non_zero(fe)))
|
||||
return int(subtle.eq0(p256r1.fe_non_zero(fe)))
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
fe_is_zero_p384r1 :: proc "contextless" (fe: ^Field_Element_p384r1) -> int {
|
||||
return int(subtle.u64_is_zero(p384r1.fe_non_zero(fe)))
|
||||
return int(subtle.eq0(p384r1.fe_non_zero(fe)))
|
||||
}
|
||||
|
||||
@@ -133,10 +133,10 @@ sc_is_zero :: proc {
|
||||
|
||||
@(require_results)
|
||||
sc_is_zero_p256r1 :: proc "contextless" (fe: ^Scalar_p256r1) -> int {
|
||||
return int(subtle.u64_is_zero(p256r1.fe_non_zero(fe)))
|
||||
return int(subtle.eq0(p256r1.fe_non_zero(fe)))
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
sc_is_zero_p384r1 :: proc "contextless" (fe: ^Scalar_p384r1) -> int {
|
||||
return int(subtle.u64_is_zero(p384r1.fe_non_zero(fe)))
|
||||
return int(subtle.eq0(p384r1.fe_non_zero(fe)))
|
||||
}
|
||||
|
||||
@@ -293,7 +293,7 @@ when crypto.COMPACT_IMPLS == false {
|
||||
// conditionally select the right result.
|
||||
pt_add_mixed(tmp, point, &tmp.x, &tmp.y)
|
||||
|
||||
ctrl := subtle.u64_is_non_zero(idx)
|
||||
ctrl := subtle.neq0(idx)
|
||||
pt_cond_select(point, point, tmp, int(ctrl))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -49,7 +49,7 @@ compare_byte_ptrs_constant_time :: proc "contextless" (a, b: ^byte, n: int) -> i
|
||||
|
||||
// After the loop, v == 0 if and only if (⟺) a == b. The subtraction will underflow
|
||||
// if and only if (⟺) v == 0, setting the sign-bit, which gets returned.
|
||||
return subtle.eq(0, v)
|
||||
return int(subtle.eq(0, v))
|
||||
}
|
||||
|
||||
// is_zero_constant_time returns 1 if and only if (⟺) b is all 0s, 0 otherwise.
|
||||
@@ -59,7 +59,7 @@ is_zero_constant_time :: proc "contextless" (b: []byte) -> int {
|
||||
v |= b_
|
||||
}
|
||||
|
||||
return subtle.byte_eq(0, v)
|
||||
return int(subtle.byte_eq(0, v))
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -50,6 +50,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) -> bool {
|
||||
private_key_clear(priv_key)
|
||||
|
||||
@@ -61,13 +62,12 @@ private_key_generate :: proc(priv_key: ^Private_Key) -> bool {
|
||||
defer crypto.zero_explicit(&b, size_of(b))
|
||||
|
||||
crypto.rand_bytes(b[:])
|
||||
private_key_set_bytes(priv_key, b[:])
|
||||
|
||||
return true
|
||||
return private_key_set_bytes(priv_key, b[:])
|
||||
}
|
||||
|
||||
// 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, b: []byte) -> bool {
|
||||
if len(b) != PRIVATE_KEY_SIZE {
|
||||
return false
|
||||
@@ -189,6 +189,7 @@ sign :: proc(priv_key: ^Private_Key, msg, sig: []byte) {
|
||||
|
||||
// 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 "contextless" (pub_key: ^Public_Key, b: []byte) -> bool {
|
||||
if len(b) != PUBLIC_KEY_SIZE {
|
||||
return false
|
||||
@@ -237,6 +238,7 @@ public_key_bytes :: proc(pub_key: ^Public_Key, dst: []byte) {
|
||||
}
|
||||
|
||||
// public_key_equal returns true if and only if (⟺) pub_key is equal to other.
|
||||
@(require_results)
|
||||
public_key_equal :: proc(pub_key, other: ^Public_Key) -> bool {
|
||||
ensure(pub_key._is_initialized && other._is_initialized, "crypto/ed25519: uninitialized public key")
|
||||
|
||||
@@ -254,6 +256,7 @@ public_key_clear :: proc "contextless" (pub_key: ^Public_Key) {
|
||||
// implementation strictly compatible with FIPS 186-5, at the expense of
|
||||
// SBS-security. Doing so is NOT recommended, and the disallowed
|
||||
// public keys all have a known discrete-log.
|
||||
@(require_results)
|
||||
verify :: proc(pub_key: ^Public_Key, msg, sig: []byte, allow_small_order_A := false) -> bool {
|
||||
switch {
|
||||
case !pub_key._is_initialized:
|
||||
|
||||
7
core/crypto/rsa/doc.odin
Normal file
7
core/crypto/rsa/doc.odin
Normal file
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
RSA (Rivest–Shamir–Adleman) cryptosystem.
|
||||
|
||||
See:
|
||||
- [[ https://www.rfc-editor.org/info/rfc8017/ ]]
|
||||
*/
|
||||
package rsa
|
||||
444
core/crypto/rsa/rsa.odin
Normal file
444
core/crypto/rsa/rsa.odin
Normal file
@@ -0,0 +1,444 @@
|
||||
package rsa
|
||||
|
||||
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import "core:bytes"
|
||||
import "core:crypto"
|
||||
import subtle "core:crypto/_subtle"
|
||||
import "core:encoding/endian"
|
||||
|
||||
// Minimum size for a RSA modulus (in bits).
|
||||
//
|
||||
// Note: 1024-bits is arguably insufficient as of this writing, with
|
||||
// 2048-bits being a more sensible value, however 1024-bits is likely
|
||||
// still in frequent enough use.
|
||||
//
|
||||
// Note: CA signed TLS certificates have a strict requirement of a modulus
|
||||
// size that is at least 2048-bits [[ https://cabforum.org/working-groups/server/baseline-requirements/documents/]].
|
||||
MODULUS_MIN_SIZE :: 1024
|
||||
|
||||
// Maximum size for a RSA modulus (in bits).
|
||||
//
|
||||
// This value MUST be a multiple of 64. This value MUST NOT exceed 47666
|
||||
// (some computations in RSA key generation rely on the factor size being
|
||||
// no more than 23833 bits). RSA key sizes beyond 3072 bits don't make a
|
||||
// lot of sense anyway.
|
||||
MODULUS_MAX_SIZE :: 4096
|
||||
|
||||
// Maxmimum size for a RSA public exponent (in bits).
|
||||
//
|
||||
// Note: This implementation supports arbitrary size exponents, however
|
||||
// limit it to something sensible (some implementations are known to
|
||||
// choke on exponents >= 2^32), with the most common choice being
|
||||
// `65537`.
|
||||
EXPONENT_MAX_SIZE :: 32
|
||||
|
||||
// Maximum size for a RSA factor (in bits). This is for RSA private-key
|
||||
// operations. Default is to support factors up to a bit more than half
|
||||
// the maximum modulus size.
|
||||
//
|
||||
// This value MUST be a multiple of 32.
|
||||
FACTOR_MAX_SIZE :: (MODULUS_MAX_SIZE + 64) >> 1
|
||||
|
||||
// Default size for a RSA key (in bits).
|
||||
DEFAULT_MODULUS_SIZE :: 2048
|
||||
|
||||
// RSA public exponent used for key generation. This MUST be a prime
|
||||
// number greater than 2.
|
||||
@(private)
|
||||
PUBLIC_EXPONENT :: 65537
|
||||
|
||||
#assert(EXPONENT_MAX_SIZE <= 32)
|
||||
|
||||
// Private_Key is a RSA private key.
|
||||
Private_Key :: struct {
|
||||
_pub_key: Public_Key,
|
||||
_d: Modulus, // Private exponent has the same size as n.
|
||||
_p: Factor,
|
||||
_q: Factor,
|
||||
|
||||
// CRT coefficients.
|
||||
_dp: Factor, // d % (p - 1)
|
||||
_dq: Factor, // d % (q - 1)
|
||||
_iq: Factor, // q^(-1) mod p
|
||||
|
||||
_is_initialized: bool,
|
||||
}
|
||||
|
||||
// Public_Key is a RSA public key.
|
||||
Public_Key :: struct {
|
||||
_n: Modulus,
|
||||
_e: u32,
|
||||
_is_initialized: bool,
|
||||
}
|
||||
|
||||
// private_key_generate uses the system entropy source to generate a new
|
||||
// Private_Key. The key size is specified in bits, and must be a multiple
|
||||
// of 8.
|
||||
@(require_results)
|
||||
private_key_generate :: proc(priv_key: ^Private_Key, key_size := DEFAULT_MODULUS_SIZE) -> bool {
|
||||
if !crypto.HAS_RAND_BYTES {
|
||||
return false
|
||||
}
|
||||
if key_size < MODULUS_MIN_SIZE || key_size > MODULUS_MAX_SIZE {
|
||||
return false
|
||||
}
|
||||
if key_size % 8 != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
private_key_clear(priv_key)
|
||||
defer if !priv_key._is_initialized {
|
||||
private_key_clear(priv_key)
|
||||
}
|
||||
|
||||
for {
|
||||
// The only way this can fail is if we get extremely unlucky
|
||||
// and we fail to derive `iq` (1/d mod p).
|
||||
if keygen_inner(priv_key, key_size) == 1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
priv_key._is_initialized = true
|
||||
priv_key._pub_key._is_initialized = true
|
||||
|
||||
// Self-test the key.
|
||||
priv_key._is_initialized = pkcs1_sig_selftest(priv_key)
|
||||
|
||||
return priv_key._is_initialized
|
||||
}
|
||||
|
||||
// private_key_n copies the private key's public modulus to dst if dst is
|
||||
// non-nil and of sufficient size, and returns the number of bytes
|
||||
// copied/would be copied (ie: calling with `dst = nil` gets the required
|
||||
// size).
|
||||
@(require_results)
|
||||
private_key_n :: proc(priv_key: ^Private_Key, dst: []byte) -> (n_len: int) {
|
||||
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
|
||||
|
||||
return public_key_n(&priv_key._pub_key, dst)
|
||||
}
|
||||
|
||||
// private_key_e returns the private key's public exponent as a u32.
|
||||
@(require_results)
|
||||
private_key_e :: proc(priv_key: ^Private_Key) -> u32 {
|
||||
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
|
||||
|
||||
return public_key_e(&priv_key._pub_key)
|
||||
}
|
||||
|
||||
// private_key_d copies the private key's private exponent `d` to dst if
|
||||
// dst is non-nil and of sufficient size, and returns the number of bytes
|
||||
// copied/would be copied (ie: calling with `dst = nil` gets the required
|
||||
// size).
|
||||
//
|
||||
// Note: The data returned MUST be kept confidential.
|
||||
@(require_results)
|
||||
private_key_d :: proc(priv_key: ^Private_Key, dst: []byte) -> (n_len: int) {
|
||||
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
|
||||
|
||||
return modulus_copyout(&priv_key._d, dst)
|
||||
}
|
||||
|
||||
// private_key_p copies the private key's first prime factor `p` to dst
|
||||
// if dst is non-nil and of sufficient size, and returns the number of
|
||||
// bytes copied/would be copied (ie: calling with `dst = nil` gets the
|
||||
// required size).
|
||||
//
|
||||
// Note: The data returned MUST be kept confidential.
|
||||
@(require_results)
|
||||
private_key_p :: proc(priv_key: ^Private_Key, dst: []byte) -> (n_len: int) {
|
||||
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
|
||||
|
||||
return factor_copyout(&priv_key._p, dst)
|
||||
}
|
||||
|
||||
// private_key_q copies the private key's second prime factor `q` to dst
|
||||
// if dst is non-nil and of sufficient size, and returns the number of
|
||||
// bytes copied/would be copied (ie: calling with `dst = nil` gets the
|
||||
// required size).
|
||||
//
|
||||
// Note: The data returned MUST be kept confidential.
|
||||
@(require_results)
|
||||
private_key_q :: proc(priv_key: ^Private_Key, dst: []byte) -> (n_len: int) {
|
||||
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
|
||||
|
||||
return factor_copyout(&priv_key._q, dst)
|
||||
}
|
||||
|
||||
// private_key_dp copies the private key's first reduced exponent
|
||||
// `d % (p-1)` to dst if dst is non-nil and of sufficient size, and
|
||||
// returns the number of bytes copied/would be copied (ie: calling with
|
||||
//`dst = nil` gets the required size).
|
||||
//
|
||||
// Note: The data returned MUST be kept confidential.
|
||||
@(require_results)
|
||||
private_key_dp :: proc(priv_key: ^Private_Key, dst: []byte) -> (n_len: int) {
|
||||
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
|
||||
|
||||
return factor_copyout(&priv_key._dp, dst)
|
||||
}
|
||||
|
||||
// private_key_dq copies the private key's second reduced exponent
|
||||
// `d % (q-1)` to dst if dst is non-nil and of sufficient size, and
|
||||
// returns the number of bytes copied/would be copied (ie: calling with
|
||||
//`dst = nil` gets the required size).
|
||||
//
|
||||
// Note: The data returned MUST be kept confidential.
|
||||
@(require_results)
|
||||
private_key_dq :: proc(priv_key: ^Private_Key, dst: []byte) -> (n_len: int) {
|
||||
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
|
||||
|
||||
return factor_copyout(&priv_key._dq, dst)
|
||||
}
|
||||
|
||||
// private_key_iq copies the private key's CRT coefficient `iq` to dst if
|
||||
// dst is non-nil and of sufficient size, and returns the number of bytes
|
||||
// copied/would be copied (ie: calling with`dst = nil` gets the required
|
||||
// size).
|
||||
//
|
||||
// Note: The data returned MUST be kept confidential.
|
||||
@(require_results)
|
||||
private_key_iq :: proc(priv_key: ^Private_Key, dst: []byte) -> (n_len: int) {
|
||||
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
|
||||
|
||||
return factor_copyout(&priv_key._iq, dst)
|
||||
}
|
||||
|
||||
// private_key_size returns the size of the private key's public modulus
|
||||
// in bytes. All ciphertexts and signatures will also be this size.
|
||||
@(require_results)
|
||||
private_key_size :: proc(priv_key: ^Private_Key) -> int {
|
||||
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
|
||||
|
||||
return priv_key._pub_key._n.v_len
|
||||
}
|
||||
|
||||
// private_key_set_bytes sets a private key from byte-encoded components,
|
||||
// and returns true if and only if (⟺) the operation was successful.
|
||||
//
|
||||
// Note: All values are mandatory, and match the values included in the
|
||||
// PKCS private key format.
|
||||
//
|
||||
// WARNING: This routine validates that it is possible to sign/verify with
|
||||
// the deserialized values, however d is not checked at all, nor is the
|
||||
// primality of p and q.
|
||||
@(require_results)
|
||||
private_key_set_bytes :: proc(
|
||||
priv_key: ^Private_Key,
|
||||
n: []byte,
|
||||
e: []byte,
|
||||
d: []byte,
|
||||
p: []byte,
|
||||
q: []byte,
|
||||
dp: []byte,
|
||||
dq: []byte,
|
||||
iq: []byte,
|
||||
) -> bool {
|
||||
private_key_clear(priv_key)
|
||||
defer if !priv_key._is_initialized {
|
||||
private_key_clear(priv_key)
|
||||
}
|
||||
|
||||
if !public_key_set_bytes(&priv_key._pub_key, n, e) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !modulus_set_bytes(&priv_key._d, d) {
|
||||
return false
|
||||
}
|
||||
if !factor_set_bytes(&priv_key._p, p) {
|
||||
return false
|
||||
}
|
||||
if !factor_set_bytes(&priv_key._q, q) {
|
||||
return false
|
||||
}
|
||||
if !factor_set_bytes(&priv_key._dp, dp) {
|
||||
return false
|
||||
}
|
||||
if !factor_set_bytes(&priv_key._dq, dq) {
|
||||
return false
|
||||
}
|
||||
if !factor_set_bytes(&priv_key._iq, iq) {
|
||||
return false
|
||||
}
|
||||
|
||||
priv_key._is_initialized = true
|
||||
|
||||
// Test the key.
|
||||
//
|
||||
// Note: This DOES NOT check that p/q are prime and if d is
|
||||
// consistent (as it is not used by our implementation).
|
||||
priv_key._is_initialized = pkcs1_sig_selftest(priv_key)
|
||||
|
||||
return priv_key._is_initialized
|
||||
}
|
||||
|
||||
// private_key_set sets priv_key to src.
|
||||
private_key_set :: proc(priv_key, src: ^Private_Key) {
|
||||
if src == nil || !src._is_initialized {
|
||||
private_key_clear(priv_key)
|
||||
return
|
||||
}
|
||||
|
||||
public_key_set(&priv_key._pub_key, &src._pub_key)
|
||||
modulus_set(&priv_key._d, &src._d)
|
||||
factor_set(&priv_key._p, &src._p)
|
||||
factor_set(&priv_key._q, &src._q)
|
||||
factor_set(&priv_key._dp, &src._dp)
|
||||
factor_set(&priv_key._dq, &src._dq)
|
||||
factor_set(&priv_key._iq, &src._iq)
|
||||
|
||||
priv_key._is_initialized = true
|
||||
}
|
||||
|
||||
// private_key_equal returns true if and only if (⟺) priv_key is equal to other.
|
||||
@(require_results)
|
||||
private_key_equal :: proc(priv_key, other: ^Private_Key) -> bool {
|
||||
ensure(priv_key._is_initialized && other._is_initialized, "crypto/rsa: uninitialized private key")
|
||||
|
||||
pk_eq := public_key_equal(&priv_key._pub_key, &other._pub_key)
|
||||
|
||||
eq := crypto.compare_constant_time(modulus_bytes(&priv_key._d), modulus_bytes(&other._d))
|
||||
eq &= crypto.compare_constant_time(factor_bytes(&priv_key._p), factor_bytes(&other._p))
|
||||
eq &= crypto.compare_constant_time(factor_bytes(&priv_key._q), factor_bytes(&other._q))
|
||||
eq &= crypto.compare_constant_time(factor_bytes(&priv_key._dp), factor_bytes(&other._dp))
|
||||
eq &= crypto.compare_constant_time(factor_bytes(&priv_key._dq), factor_bytes(&other._dq))
|
||||
eq &= crypto.compare_constant_time(factor_bytes(&priv_key._iq), factor_bytes(&other._iq))
|
||||
|
||||
return pk_eq & (eq == 1)
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
|
||||
// public_key_n copies the public key's modulus `n` to dst if dst is
|
||||
// non-nil and of sufficient size, and returns the number of bytes
|
||||
// copied/would be copied (ie: calling with `dst = nil` gets the
|
||||
// required size).
|
||||
@(require_results)
|
||||
public_key_n :: proc(pub_key: ^Public_Key, dst: []byte) -> (n_len: int) {
|
||||
ensure(pub_key._is_initialized, "crypto/rsa: uninitialized public key")
|
||||
|
||||
return modulus_copyout(&pub_key._n, dst)
|
||||
}
|
||||
|
||||
// public_key_e returns the public key's exponent `e` as a u32.
|
||||
@(require_results)
|
||||
public_key_e :: proc(pub_key: ^Public_Key) -> u32 {
|
||||
ensure(pub_key._is_initialized, "crypto/rsa: uninitialized public key")
|
||||
|
||||
return pub_key._e
|
||||
}
|
||||
|
||||
// public_key_size returns the size of the public key's modulus in bytes.
|
||||
// All ciphertexts and signatures will also be this size.
|
||||
@(require_results)
|
||||
public_key_size :: proc(pub_key: ^Public_Key) -> int {
|
||||
ensure(pub_key._is_initialized, "crypto/rsa: uninitialized public key")
|
||||
|
||||
return pub_key._n.v_len
|
||||
}
|
||||
|
||||
// public_key_set_bytes sets a public key from byte-encoded components,
|
||||
// and returns true if and only if (⟺) the operation was successful.
|
||||
@(require_results)
|
||||
public_key_set_bytes :: proc(pub_key: ^Public_Key, n, e: []byte) -> bool {
|
||||
public_key_clear(pub_key)
|
||||
defer if !pub_key._is_initialized {
|
||||
public_key_clear(pub_key)
|
||||
}
|
||||
|
||||
ok := modulus_set_bytes(&pub_key._n, n)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if modulus_len(&pub_key._n) < MODULUS_MIN_SIZE >> 3 {
|
||||
return false
|
||||
}
|
||||
if !modulus_is_odd(&pub_key._n) {
|
||||
return false
|
||||
}
|
||||
|
||||
e_ := bytes.trim_left(e, []byte{0x00})
|
||||
e_len := len(e_)
|
||||
if e_len > EXPONENT_MAX_SIZE >> 3 {
|
||||
return false
|
||||
}
|
||||
e_buf: [4]byte
|
||||
copy(e_buf[4 - e_len:], e)
|
||||
e_u32 := endian.unchecked_get_u32be(e_buf[:])
|
||||
if e_u32 < 3 || e_u32 & 1 == 0 {
|
||||
return false
|
||||
}
|
||||
pub_key._e = e_u32
|
||||
|
||||
pub_key._is_initialized = true
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// public_key_set sets pub_key to src.
|
||||
public_key_set :: proc(pub_key, src: ^Public_Key) {
|
||||
if src == nil || !src._is_initialized {
|
||||
public_key_clear(pub_key)
|
||||
return
|
||||
}
|
||||
|
||||
modulus_set(&pub_key._n, &src._n)
|
||||
pub_key._e = src._e
|
||||
pub_key._is_initialized = true
|
||||
}
|
||||
|
||||
// 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._is_initialized, "crypto/rsa: uninitialized private key")
|
||||
pub_key^ = priv_key._pub_key
|
||||
}
|
||||
|
||||
// public_key_equal returns true if and only if (⟺) pub_key is equal to other.
|
||||
public_key_equal :: proc(pub_key, other: ^Public_Key) -> bool {
|
||||
ensure(pub_key._is_initialized && other._is_initialized, "crypto/rsa: uninitialized public key")
|
||||
|
||||
eq := crypto.compare_constant_time(modulus_bytes(&pub_key._n), modulus_bytes(&other._n))
|
||||
eq &= int(subtle.eq(pub_key._e, other._e))
|
||||
|
||||
return eq == 1
|
||||
}
|
||||
|
||||
// public_key_clear clears pub_key to the uninitialized state.
|
||||
public_key_clear :: proc "contextless" (pub_key: ^Public_Key) {
|
||||
crypto.zero_explicit(pub_key, size_of(Public_Key))
|
||||
}
|
||||
|
||||
// size returns the size of the key's public modulus in bytes.
|
||||
// All ciphertexts and signatures will also be this size.
|
||||
size :: proc "contextless" (key: ^$T) -> int where T == Private_Key || T == Private_Key {
|
||||
when T == Private_Key {
|
||||
return private_key_size(key)
|
||||
} else {
|
||||
return public_key_size(key)
|
||||
}
|
||||
}
|
||||
197
core/crypto/rsa/rsa_dec_oaep.odin
Normal file
197
core/crypto/rsa/rsa_dec_oaep.odin
Normal file
@@ -0,0 +1,197 @@
|
||||
package rsa
|
||||
|
||||
// Copyright (c) 2018 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import "core:crypto"
|
||||
import subtle "core:crypto/_subtle"
|
||||
import "core:crypto/hash"
|
||||
|
||||
// decrypt_oaep returns the plaintext and true if and only if (⟺) it
|
||||
// successfully decrypts the ciphertext with OAEP parameterized by
|
||||
// label, hash_algo, and mgf1_algo, and writes the plaintext into dst.
|
||||
// If mgf1_algo is unspecified, hash_algo will be used.
|
||||
//
|
||||
// Note: dst MUST be large enough to contain the plaintext.
|
||||
@(require_results)
|
||||
decrypt_oaep :: proc(
|
||||
priv_key: ^Private_Key,
|
||||
hash_algo: hash.Algorithm,
|
||||
ciphertext: []byte,
|
||||
dst: []byte,
|
||||
label: []byte = nil,
|
||||
mgf1_algo := hash.Algorithm.Invalid,
|
||||
) -> (plaintext: []byte, ok: bool) {
|
||||
if !priv_key._is_initialized {
|
||||
return
|
||||
}
|
||||
ct_len := len(ciphertext)
|
||||
if ct_len != modulus_len(&priv_key._pub_key._n) {
|
||||
return
|
||||
}
|
||||
if hash_algo == .Invalid {
|
||||
return
|
||||
}
|
||||
mgf1_algo_ := mgf1_algo
|
||||
if mgf1_algo == .Invalid {
|
||||
mgf1_algo_ = hash_algo
|
||||
}
|
||||
|
||||
tmp: [MODULUS_MAX_SIZE >> 3]byte
|
||||
pt_buf := tmp[:ct_len]
|
||||
defer crypto.zero_explicit(raw_data(pt_buf), ct_len)
|
||||
|
||||
copy(pt_buf, ciphertext)
|
||||
r := private_modpow(pt_buf, priv_key)
|
||||
r_, l := oaep_dec_unpad(hash_algo, mgf1_algo_, label, pt_buf)
|
||||
|
||||
// Conditional branches are ok as we are past the padding
|
||||
// verification.
|
||||
if ok = r & r_ == 1; ok {
|
||||
if l <= len(dst) {
|
||||
copy(dst, pt_buf[:l])
|
||||
plaintext = dst[:l]
|
||||
} else {
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// oaep_max_plaintext_size returns the maximum supported plaintext size
|
||||
// for a given key, with OAEP parameterized by hash_algo and mgf1_algo.
|
||||
// If mgf1_algo is unspecified, hash_algo will be used.
|
||||
@(require_results)
|
||||
oaep_max_plaintext_size :: proc(
|
||||
k: ^$T,
|
||||
hash_algo: hash.Algorithm,
|
||||
mgf1_algo := hash.Algorithm.Invalid,
|
||||
) -> int where T == Private_Key || T == Public_Key {
|
||||
if !k._is_initialized {
|
||||
return 0
|
||||
}
|
||||
if hash_algo == .Invalid {
|
||||
return 0
|
||||
}
|
||||
mgf1_algo_ := mgf1_algo
|
||||
if mgf1_algo == .Invalid {
|
||||
mgf1_algo_ = hash_algo
|
||||
}
|
||||
|
||||
overhead := 2 + hash.DIGEST_SIZES[hash_algo] + hash.DIGEST_SIZES[mgf1_algo_]
|
||||
|
||||
pub_key: ^Public_Key
|
||||
when T == Private_Key {
|
||||
pub_keyk = &k._pub_key
|
||||
} else {
|
||||
pub_key = k
|
||||
}
|
||||
return modulus_len(&k._n) - overhead
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
xor_hash_data :: proc(hash_algo: hash.Algorithm, dst: []byte, src: []byte) {
|
||||
tmp: [hash.MAX_DIGEST_SIZE]byte = ---
|
||||
hash_len := hash.DIGEST_SIZES[hash_algo]
|
||||
digest := tmp[:hash_len]
|
||||
defer crypto.zero_explicit(raw_data(digest), hash_len)
|
||||
|
||||
hash.hash_bytes_to_buffer(hash_algo, src, digest)
|
||||
for v, u in digest {
|
||||
dst[u] ~= v
|
||||
}
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
oaep_dec_unpad :: proc(
|
||||
hash_algo: hash.Algorithm,
|
||||
mgf1_algo: hash.Algorithm,
|
||||
label: []byte,
|
||||
data: []byte,
|
||||
) -> (u32, int) {
|
||||
hash_len := hash.DIGEST_SIZES[hash_algo]
|
||||
k := len(data)
|
||||
buf := data
|
||||
|
||||
// There must be room for the padding.
|
||||
if k < (hash_len << 1) + 2 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// Unmask the seed, then the DB value.
|
||||
seed, db := buf[1:1+hash_len], buf[1+hash_len:]
|
||||
mgf1_xor(seed, mgf1_algo, db)
|
||||
mgf1_xor(db, mgf1_algo, seed)
|
||||
|
||||
// Hash the label and XOR it with the value in the array; if
|
||||
// they are equal then these should yield only zeros.
|
||||
xor_hash_data(hash_algo, db, label)
|
||||
|
||||
// At that point, if the padding was correct, when we should
|
||||
// have: 0x00 || seed || 0x00 ... 0x00 0x01 || M
|
||||
// Padding is valid as long as:
|
||||
// - There is at least hlen+1 leading bytes of value 0x00.
|
||||
// - There is at least one non-zero byte.
|
||||
// - The first (leftmost) non-zero byte has value 0x01.
|
||||
//
|
||||
// Ultimately, we may leak the resulting message length, i.e.
|
||||
// the position of the byte of value 0x01, but we must take care
|
||||
// to do so only if the number of zero bytes has been verified
|
||||
// to be at least hlen+1.
|
||||
//
|
||||
// The loop below counts the number of bytes of value 0x00, and
|
||||
// checks that the next byte has value 0x01, in constant-time.
|
||||
//
|
||||
// - If the initial byte (before the seed) is not 0x00, then
|
||||
// r and s are set to 0, and stay there.
|
||||
// - Value r is 1 until the first non-zero byte is reached
|
||||
// (after the seed); it switches to 0 at that point.
|
||||
// - Value s is set to 1 if and only if the data encountered
|
||||
// at the time of the transition of r from 1 to 0 has value
|
||||
// exactly 0x01.
|
||||
// - Value zlen counts the number of leading bytes of value zero
|
||||
// (after the seed).
|
||||
r := u32(subtle.eq(buf[0], 0))
|
||||
s, zlen: u32
|
||||
for u in hash_len + 1..<k {
|
||||
w := u32(buf[u])
|
||||
|
||||
// nz == 1 only for the first non-zero byte.
|
||||
nz := r & ((w + 0xFF) >> 8)
|
||||
s |= nz & subtle.eq(w, 0x01)
|
||||
r &= subtle.not(nz)
|
||||
zlen += r
|
||||
}
|
||||
|
||||
// Padding is correct only if s == 1, _and_ zlen >= hlen.
|
||||
s &= subtle.ge(zlen, u32(hash_len))
|
||||
|
||||
// At that point, padding was verified, and we are now allowed
|
||||
// to make conditional jumps.
|
||||
if s != 0 {
|
||||
plen := 2 + hash_len + int(zlen)
|
||||
k -= plen
|
||||
copy(buf[:k], buf[plen:])
|
||||
}
|
||||
return s, k
|
||||
}
|
||||
55
core/crypto/rsa/rsa_dec_tls_pms.odin
Normal file
55
core/crypto/rsa/rsa_dec_tls_pms.odin
Normal file
@@ -0,0 +1,55 @@
|
||||
package rsa
|
||||
|
||||
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import subtle "core:crypto/_subtle"
|
||||
|
||||
// unsafe_decrypt_tls_pms decrypts a TLS RSA-Encrypted Premaster Secret
|
||||
// Message, unconditionally moves the decrypted plaintext to `data[:48]`,
|
||||
// and returns 1 if and only if (⟺) the operation was successful.
|
||||
//
|
||||
// WARNING: This routine MUST only be used when implementing server-side
|
||||
// support for TLS 1.2's Client Key Exchange message, and extreme care
|
||||
// MUST be taken when handling failures. This key exchange scheme was
|
||||
// removed in TLS 1.3, and not implementing support in the first place
|
||||
// is strongly RECOMMENDED even for TLS 1.2 servers.
|
||||
@(require_results)
|
||||
unsafe_decrypt_tls_pms :: proc(priv_key: ^Private_Key, data: []byte) -> u32 {
|
||||
// A first check on length. Since this test works only on the
|
||||
// buffer length, it needs not (and cannot) be constant-time.
|
||||
_len := len(data)
|
||||
if _len < 59 || _len != priv_key._pub_key._n.v_len {
|
||||
return 0
|
||||
}
|
||||
x := private_modpow(data, priv_key)
|
||||
|
||||
x &= u32(subtle.eq(data[0], 0x00))
|
||||
x &= u32(subtle.eq(data[1], 0x02))
|
||||
for u in 2..<(_len-49) {
|
||||
x &= u32(subtle.neq(data[u], 0))
|
||||
}
|
||||
x &= u32(subtle.eq(data[_len - 49], 0x00))
|
||||
copy(data[:48], data[_len - 48:])
|
||||
|
||||
return x
|
||||
}
|
||||
105
core/crypto/rsa/rsa_enc_oaep.odin
Normal file
105
core/crypto/rsa/rsa_enc_oaep.odin
Normal file
@@ -0,0 +1,105 @@
|
||||
package rsa
|
||||
|
||||
// Copyright (c) 2018 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:crypto"
|
||||
import "core:crypto/hash"
|
||||
|
||||
// encrypt_oaep returns true if and only if (⟺) it successfully
|
||||
// encrypts the plaintext with OAEP parameterized by label, hash_algo,
|
||||
// and mgf1_algo, and writes the cipherttext into dst. If mgf1_algo is
|
||||
// unspecified, hash_algo will be used.
|
||||
//
|
||||
// This routine will fail if the system entropy source is unavailable.
|
||||
encrypt_oaep :: proc(
|
||||
pub_key: ^Public_Key,
|
||||
hash_algo: hash.Algorithm,
|
||||
plaintext: []byte,
|
||||
dst: []byte,
|
||||
label: []byte = nil,
|
||||
mgf1_algo := hash.Algorithm.Invalid,
|
||||
) -> bool {
|
||||
if !pub_key._is_initialized {
|
||||
return false
|
||||
}
|
||||
if hash_algo == .Invalid {
|
||||
return false
|
||||
}
|
||||
mgf1_algo_ := mgf1_algo
|
||||
if mgf1_algo == .Invalid {
|
||||
mgf1_algo_ = hash_algo
|
||||
}
|
||||
if len(dst) != modulus_len(&pub_key._n) {
|
||||
return false
|
||||
}
|
||||
if len(plaintext) > oaep_max_plaintext_size(pub_key, hash_algo, mgf1_algo_) {
|
||||
return false
|
||||
}
|
||||
|
||||
if oaep_enc_pad(hash_algo, mgf1_algo_, label, dst, plaintext) != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
return public_modpow(dst, pub_key) == 1
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
oaep_enc_pad :: proc(
|
||||
hash_algo: hash.Algorithm,
|
||||
mgf1_algo: hash.Algorithm,
|
||||
label: []byte,
|
||||
dst: []byte,
|
||||
src: []byte,
|
||||
) -> u32 {
|
||||
hash_len := hash.DIGEST_SIZES[hash_algo]
|
||||
src_len := len(src)
|
||||
k := len(dst)
|
||||
|
||||
// Note: Length checks are handled by the caller.
|
||||
|
||||
// Apply padding. At this point, things cannot fail.
|
||||
buf := dst
|
||||
|
||||
// Assemble: DB = lHash || PS || 0x01 || M
|
||||
// We first place the source message M with copy(), so that
|
||||
// overlaps between source and destination buffers are supported.
|
||||
copy(buf[k - src_len:], src)
|
||||
hash.hash_bytes_to_buffer(hash_algo, label, buf[1+hash_len:1+hash_len << 1])
|
||||
intrinsics.mem_zero(raw_data(buf[1 + hash_len << 1:]), k - src_len - (hash_len << 1) - 2)
|
||||
buf[k - src_len - 1] = 0x01
|
||||
|
||||
// Make the random seed.
|
||||
seed, db := buf[1:1+hash_len], buf[1+hash_len:]
|
||||
crypto.rand_bytes(seed)
|
||||
|
||||
// Mask DB with the mask generated from the seed.
|
||||
mgf1_xor(db, mgf1_algo, seed)
|
||||
|
||||
// Mask the seed with the mask generated from the masked DB.
|
||||
mgf1_xor(seed, mgf1_algo, db)
|
||||
|
||||
// Padding result: EM = 0x00 || maskedSeed || maskedDB.
|
||||
buf[0] = 0x00
|
||||
return 1
|
||||
}
|
||||
110
core/crypto/rsa/rsa_int.odin
Normal file
110
core/crypto/rsa/rsa_int.odin
Normal file
@@ -0,0 +1,110 @@
|
||||
#+private
|
||||
package rsa
|
||||
|
||||
import "core:bytes"
|
||||
|
||||
Big_Int :: struct($N: int) {
|
||||
v: [N]byte,
|
||||
v_len: int,
|
||||
}
|
||||
|
||||
Modulus :: Big_Int(MODULUS_MAX_SIZE >> 3)
|
||||
Factor :: Big_Int(FACTOR_MAX_SIZE >> 3)
|
||||
|
||||
@(require_results)
|
||||
modulus_set_bytes :: proc(n: ^Modulus, b: []byte) -> bool {
|
||||
b_ := bytes.trim_left(b, []byte{0x00})
|
||||
b_len := len(b_)
|
||||
|
||||
if b_len > size_of(n.v) || b_len == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
copy(n.v[:], b_)
|
||||
n.v_len = b_len
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
modulus_set :: proc "contextless" (n, other: ^Modulus) {
|
||||
// Copy the full thing.
|
||||
copy(n.v[:], other.v[:])
|
||||
n.v_len = other.v_len
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
modulus_bytes :: #force_inline proc "contextless" (n: ^Modulus) -> []byte {
|
||||
return n.v[:n.v_len]
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
modulus_len :: #force_inline proc "contextless" (n: ^Modulus) -> int {
|
||||
return n.v_len
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
modulus_copyout :: proc(n: ^Modulus, dst: []byte) -> (n_len: int) {
|
||||
if n_len = modulus_len(n); n_len == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if len(dst) > 0 {
|
||||
ensure(len(dst) >= n_len, "crypto/rsa: insufficent buffer size")
|
||||
copy(dst, modulus_bytes(n))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
modulus_is_odd :: proc "contextless" (n: ^Modulus) -> bool {
|
||||
if n.v_len == 0 || n.v[n.v_len-1] & 1 == 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
factor_set_bytes :: proc(n: ^Factor, b: []byte) -> bool {
|
||||
b_ := bytes.trim_left(b, []byte{0x00})
|
||||
b_len := len(b_)
|
||||
|
||||
if b_len > size_of(n.v) || b_len == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
copy(n.v[:], b_)
|
||||
n.v_len = b_len
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
factor_set :: proc "contextless" (n, other: ^Factor) {
|
||||
// Copy the full thing.
|
||||
copy(n.v[:], other.v[:])
|
||||
n.v_len = other.v_len
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
factor_bytes :: #force_inline proc "contextless" (n: ^Factor) -> []byte {
|
||||
return n.v[:n.v_len]
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
factor_len :: #force_inline proc "contextless" (n: ^Factor) -> int {
|
||||
return n.v_len
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
factor_copyout :: proc(n: ^Factor, dst: []byte) -> (n_len: int) {
|
||||
if n_len = factor_len(n); n_len == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if len(dst) > 0 {
|
||||
ensure(len(dst) >= n_len, "crypto/rsa: insufficent buffer size")
|
||||
copy(dst, factor_bytes(n))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
367
core/crypto/rsa/rsa_keygen.odin
Normal file
367
core/crypto/rsa/rsa_keygen.odin
Normal file
@@ -0,0 +1,367 @@
|
||||
package rsa
|
||||
|
||||
// Copyright (c) 2018 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import "core:crypto"
|
||||
import bigint "core:crypto/_bigint"
|
||||
import subtle "core:crypto/_subtle"
|
||||
import "core:slice"
|
||||
|
||||
// Swap two buffers in RAM. They must be disjoint.
|
||||
@(private="file")
|
||||
bufswap_u32 :: proc "contextless" (b1, b2: []u32) {
|
||||
l := len(b1)
|
||||
|
||||
for u in 0..<l {
|
||||
b1[u], b2[u] = b2[u], b1[u]
|
||||
}
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
keygen_inner :: proc(sk: ^Private_Key, key_size: int) -> u32 {
|
||||
// We need temporary values for at least 7 integers of the same size
|
||||
// as a factor (including header word); more space helps with performance
|
||||
// (in modular exponentiations), but we much prefer to remain under
|
||||
// 2 kilobytes in total, to save stack space. The macro TEMPS below
|
||||
// exceeds 512 (which is a count in 32-bit words) when MODULUS_MAX_SIZE
|
||||
// is greater than 4464 (default value is 4096, so the 2-kB limit is
|
||||
// maintained unless MODULUS_MAX_SIZE was modified).
|
||||
TEMPS :: max(512, ((((7 * ((((MODULUS_MAX_SIZE + 1) >> 1) + 61) / 31))) + 1) >> 1) << 1)
|
||||
|
||||
assert(key_size >= MODULUS_MIN_SIZE && key_size <= MODULUS_MAX_SIZE)
|
||||
|
||||
t64: [TEMPS >> 1]u64
|
||||
t32 := slice.reinterpret([]u32, t64[:])
|
||||
defer crypto.zero_explicit(&t64, size_of(t64))
|
||||
|
||||
esize_p := u32(key_size + 1) >> 1
|
||||
esize_q := u32(key_size) - esize_p
|
||||
sk._p.v_len = int((esize_p + 7) >> 3)
|
||||
sk._q.v_len = int((esize_q + 7) >> 3)
|
||||
sk._dp.v_len = sk._p.v_len
|
||||
sk._dq.v_len = sk._q.v_len
|
||||
sk._iq.v_len = sk._p.v_len
|
||||
|
||||
pk := &sk._pub_key
|
||||
pk._n.v_len = (key_size + 7) >> 3
|
||||
pk._e = PUBLIC_EXPONENT
|
||||
|
||||
sk._d.v_len = pk._n.v_len // Private exponent length is that of the modulus.
|
||||
|
||||
// We now switch to encoded sizes.
|
||||
//
|
||||
// floor((x * 16913) / (2^19)) is equal to floor(x/31) for all
|
||||
// integers x from 0 to 34966; the intermediate product fits on
|
||||
// 30 bits, thus we can use MUL31().
|
||||
esize_p += u32(bigint._mul31(esize_p, 16913) >> 19)
|
||||
esize_q += u32(bigint._mul31(esize_q, 16913) >> 19)
|
||||
plen := (esize_p + 31) >> 5
|
||||
qlen := (esize_q + 31) >> 5
|
||||
p := t32
|
||||
q := p[1 + plen:]
|
||||
t := q[1 + qlen:]
|
||||
|
||||
// Since we use a prime exponent, when searching for candidate primes,
|
||||
// checking if `GCD(e, prime - 1) = 1` is a simple matter of euclidian
|
||||
// division.
|
||||
for {
|
||||
bigint.i62_mkprime(p, esize_p, PUBLIC_EXPONENT, t)
|
||||
p[1] -= 1
|
||||
if bigint.i31_rem(p, PUBLIC_EXPONENT) != 0 {
|
||||
p[1] += 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
bigint.i62_mkprime(q, esize_q, PUBLIC_EXPONENT, t)
|
||||
q[1] -= 1
|
||||
if bigint.i31_rem(q, PUBLIC_EXPONENT) != 0 {
|
||||
q[1] += 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If p and q have the same size, then it is possible that q > p
|
||||
// (when the target modulus size is odd, we generate p with a
|
||||
// greater bit length than q). If q > p, we want to swap p and q
|
||||
// for two reasons:
|
||||
// - The final step below (inversion of q modulo p) is easier if
|
||||
// p > q.
|
||||
// - While BearSSL's RSA code is perfectly happy with RSA keys such
|
||||
// that p < q, some other implementations have restrictions and
|
||||
// require p > q.
|
||||
//
|
||||
// Note that we can do a simple non-constant-time swap here,
|
||||
// because the only information we leak here is that we insist on
|
||||
// returning p and q such that p > q, which is not a secret.
|
||||
if esize_p == esize_q && bigint.i31_sub(p, q, 0) == 1 {
|
||||
bufswap_u32(p[:1+plen], q)
|
||||
}
|
||||
|
||||
sk_p, sk_q := factor_bytes(&sk._p), factor_bytes(&sk._q)
|
||||
bigint.i31_encode(sk_p, p)
|
||||
bigint.i31_encode(sk_q, q)
|
||||
// The odds of this happening are infinitesimally small, however
|
||||
// checking for it is cheap.
|
||||
if crypto.compare_constant_time(sk_p, sk_q) == 1 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Compute the public modulus too.
|
||||
bigint.i31_zero(t, p[0])
|
||||
bigint.i31_mulacc(t, p, q)
|
||||
bigint.i31_encode(modulus_bytes(&pk._n), t)
|
||||
|
||||
// Compute the private exponent.
|
||||
//
|
||||
// Computing p - 1 and q - 1 this way is safe as p and q
|
||||
// are guaranteed to be odd, thus the LSB will always be
|
||||
// set.
|
||||
p[1], q[1] = p[1] - 1, q[1] - 1 // p = p - 1, q = q - 1
|
||||
if compute_privexp(sk, p, q, pk._e, t) != 1 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Compute `d % (p - 1)`.
|
||||
d_mod := t[:1+plen]
|
||||
bigint.i31_decode_reduce(d_mod, modulus_bytes(&sk._d), p)
|
||||
bigint.i31_encode(factor_bytes(&sk._dp), d_mod)
|
||||
|
||||
// Compute `d % (q - 1)`.
|
||||
bigint.i31_decode_reduce(d_mod, modulus_bytes(&sk._d), q)
|
||||
bigint.i31_encode(factor_bytes(&sk._dq), d_mod)
|
||||
|
||||
// Compute `q^(-1) mod p`.
|
||||
p[1], q[1] = p[1] + 1, q[1] + 1 // Restore p, q.
|
||||
return compute_qinv(sk, p, q, plen, t)
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
compute_qinv :: proc "contextless" (sk: ^Private_Key, p, q: []u32, plen: u32, t: []u32) -> u32 {
|
||||
// Per Fermat's Little Theorem, `q^(-1) mod p = q^(p-2) mod p`.
|
||||
//
|
||||
// Note: p is guaranteed to be odd as it is a large prime.
|
||||
|
||||
// Compute and encode `p-2`.
|
||||
p_minus_two := t[:1+plen]
|
||||
copy(p_minus_two, p[:1+plen])
|
||||
two := t[1+plen:] // Temporarily use this for 2.
|
||||
bigint.i31_zero(two, p[0])
|
||||
bigint.i31_decode(two, []byte{2})
|
||||
_ = bigint.i31_sub(p_minus_two, two, 1)
|
||||
iq := factor_bytes(&sk._iq) // Temporarily use this for p - 2.
|
||||
bigint.i31_encode(iq, p_minus_two)
|
||||
|
||||
// Enforce 64-bit alignment.
|
||||
t_ := t
|
||||
if len(t_) & 1 != 0 {
|
||||
t_ = t_[1:]
|
||||
}
|
||||
|
||||
m0i := bigint.i31_ninv31(p[1])
|
||||
ret := bigint.i62_modpow_opt_as_i31(q, iq, p, m0i, t)
|
||||
if ret != 0 {
|
||||
bigint.i31_encode(iq, q)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
compute_privexp :: proc "contextless" (sk: ^Private_Key, p_minus_one, q_minus_one: []u32, e: u32, tmp: []u32) -> u32 {
|
||||
// Compute phi = (p-1)*(q-1). The mulacc function sets the announced
|
||||
// bit length of t to be the sum of the announced bit lengths of
|
||||
// p-1 and q-1, which is usually exact but may overshoot by one 1
|
||||
// bit in some cases; we readjust it to its true length.
|
||||
phi := tmp
|
||||
bigint.i31_zero(phi, p_minus_one[0])
|
||||
bigint.i31_mulacc(phi, p_minus_one, q_minus_one)
|
||||
_len := (phi[0] + 31) >> 5
|
||||
phi[0] = bigint.i31_bit_length(phi[1:1+_len])
|
||||
_len = (phi[0] + 31) >> 5
|
||||
|
||||
// Divide phi by public exponent e. The final remainder r must be
|
||||
// non-zero (otherwise, the key is invalid). The quotient is k,
|
||||
// which we write over phi, since we don't need phi after that.
|
||||
r: u32
|
||||
for u := _len; u >= 1; u -= 1 {
|
||||
// Upon entry, r < e, and phi[u] < 2^31; hence,
|
||||
// hi:lo < e*2^31. Thus, the produced word k[u]
|
||||
// must be lower than 2^31, and the new remainder r
|
||||
// is lower than e.
|
||||
hi := r >> 1
|
||||
lo := (r << 31) + phi[u]
|
||||
phi[u], r = bigint.div_rem_u32(hi, lo, e)
|
||||
}
|
||||
if r == 0 {
|
||||
return 0
|
||||
}
|
||||
k := phi
|
||||
|
||||
// Compute u and v such that u*e - v*r = GCD(e,r). We use
|
||||
// a binary GCD algorithm, with 6 extra integers a, b,
|
||||
// u0, u1, v0 and v1. Initial values are:
|
||||
// a = e u0 = 1 v0 = 0
|
||||
// b = r u1 = r v1 = e-1
|
||||
// The following invariants are maintained:
|
||||
// a = u0*e - v0*r
|
||||
// b = u1*e - v1*r
|
||||
// 0 < a <= e
|
||||
// 0 < b <= r
|
||||
// 0 <= u0 <= r
|
||||
// 0 <= v0 <= e
|
||||
// 0 <= u1 <= r
|
||||
// 0 <= v1 <= e
|
||||
//
|
||||
// At each iteration, we reduce either a or b by one bit, and
|
||||
// adjust u0, u1, v0 and v1 to maintain the invariants:
|
||||
// - if a is even, then a <- a/2
|
||||
// - otherwise, if b is even, then b <- b/2
|
||||
// - otherwise, if a > b, then a <- (a-b)/2
|
||||
// - otherwise, if b > a, then b <- (b-a)/2
|
||||
// Algorithm stops when a = b. At that point, the common value
|
||||
// is the GCD of e and r; it must be 1 (otherwise, the private
|
||||
// key or public exponent is not valid). The (u0,v0) or (u1,v1)
|
||||
// pairs are the solution we are looking for.
|
||||
//
|
||||
// Since either a or b is reduced by at least 1 bit at each
|
||||
// iteration, 62 iterations are enough to reach the end
|
||||
// condition.
|
||||
//
|
||||
// To maintain the invariants, we must compute the same operations
|
||||
// on the u* and v* values that we do on a and b:
|
||||
// - When a is divided by 2, u0 and v0 must be divided by 2.
|
||||
// - When b is divided by 2, u1 and v1 must be divided by 2.
|
||||
// - When b is subtracted from a, u1 and v1 are subtracted from
|
||||
// u0 and v0, respectively.
|
||||
// - When a is subtracted from b, u0 and v0 are subtracted from
|
||||
// u1 and v1, respectively.
|
||||
//
|
||||
// However, we want to keep the u* and v* values in their proper
|
||||
// ranges. The following remarks apply:
|
||||
//
|
||||
// - When a is divided by 2, then a is even. Therefore:
|
||||
//
|
||||
// * If r is odd, then u0 and v0 must have the same parity;
|
||||
// if they are both odd, then adding r to u0 and e to v0
|
||||
// makes them both even, and the division by 2 brings them
|
||||
// back to the proper range.
|
||||
//
|
||||
// * If r is even, then u0 must be even; if v0 is odd, then
|
||||
// adding r to u0 and e to v0 makes them both even, and the
|
||||
// division by 2 brings them back to the proper range.
|
||||
//
|
||||
// Thus, all we need to do is to look at the parity of v0,
|
||||
// and add (r,e) to (u0,v0) when v0 is odd. In order to avoid
|
||||
// a 32-bit overflow, we can add ((r+1)/2,(e/2)+1) after the
|
||||
// division (r+1 does not overflow since r < e; and (e/2)+1
|
||||
// is equal to (e+1)/2 since e is odd).
|
||||
//
|
||||
// - When we subtract b from a, three cases may occur:
|
||||
//
|
||||
// * u1 <= u0 and v1 <= v0: just do the subtractions
|
||||
//
|
||||
// * u1 > u0 and v1 > v0: compute:
|
||||
// (u0, v0) <- (u0 + r - u1, v0 + e - v1)
|
||||
//
|
||||
// * u1 <= u0 and v1 > v0: compute:
|
||||
// (u0, v0) <- (u0 + r - u1, v0 + e - v1)
|
||||
//
|
||||
// The fourth case (u1 > u0 and v1 <= v0) is not possible
|
||||
// because it would contradict "b < a" (which is the reason
|
||||
// why we subtract b from a).
|
||||
//
|
||||
// The tricky case is the third one: from the equations, it
|
||||
// seems that u0 may go out of range. However, the invariants
|
||||
// and ranges of other values imply that, in that case, the
|
||||
// new u0 does not actually exceed the range.
|
||||
//
|
||||
// We can thus handle the subtraction by adding (r,e) based
|
||||
// solely on the comparison between v0 and v1.
|
||||
a, b: u32 = e, r
|
||||
u0, v0: u32 = 1, 0
|
||||
u1, v1: u32 = r, e - 1
|
||||
hr, he := (r + 1) >> 1, (e >> 1) + 1
|
||||
for _ in 0..<62 {
|
||||
oa := a & 1 // 1 if a is odd
|
||||
ob := b & 1 // 1 if b is odd
|
||||
agtb := subtle.gt(a, b) // 1 if a > b
|
||||
bgta := subtle.gt(b, a) // 1 if b > a
|
||||
|
||||
sab := oa & ob & agtb // 1 if a <- a-b
|
||||
sba := oa & ob & bgta // 1 if b <- b-a
|
||||
|
||||
// a <- a-b, u0 <- u0-u1, v0 <- v0-v1
|
||||
ctl := subtle.gt(v1, v0)
|
||||
a -= b & -sab
|
||||
u0 -= (u1 - (r & -ctl)) & -sab
|
||||
v0 -= (v1 - (e & -ctl)) & -sab
|
||||
|
||||
// b <- b-a, u1 <- u1-u0 mod r, v1 <- v1-v0 mod e
|
||||
ctl = subtle.gt(v0, v1)
|
||||
b -= a & -sba
|
||||
u1 -= (u0 - (r & -ctl)) & -sba
|
||||
v1 -= (v0 - (e & -ctl)) & -sba
|
||||
|
||||
da := subtle.not(oa) | sab // 1 if a <- a/2
|
||||
db := (oa & subtle.not(ob)) | sba // 1 if b <- b/2
|
||||
|
||||
// a <- a/2, u0 <- u0/2, v0 <- v0/2
|
||||
ctl = v0 & 1
|
||||
a ~= (a ~ (a >> 1)) & -da
|
||||
u0 ~= (u0 ~ ((u0 >> 1) + (hr & -ctl))) & -da
|
||||
v0 ~= (v0 ~ ((v0 >> 1) + (he & -ctl))) & -da
|
||||
|
||||
// b <- b/2, u1 <- u1/2 mod r, v1 <- v1/2 mod e
|
||||
ctl = v1 & 1
|
||||
b ~= (b ~ (b >> 1)) & -db
|
||||
u1 ~= (u1 ~ ((u1 >> 1) + (hr & -ctl))) & -db
|
||||
v1 ~= (v1 ~ ((v1 >> 1) + (he & -ctl))) & -db
|
||||
}
|
||||
|
||||
// Check that the GCD is indeed 1. If not, then the key is invalid
|
||||
// (and there's no harm in leaking that piece of information).
|
||||
if (a != 1) {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Now we have u0*e - v0*r = 1. Let's compute the result as:
|
||||
// d = u0 + v0*k
|
||||
// We still have k in the tmp[] array, and its announced bit
|
||||
// length is that of phi.
|
||||
m := k[1+_len:]
|
||||
m[0] = (1 << 5) + 1 // bit length is 32 bits, encoded
|
||||
m[1] = v0 & bigint.I31_MASK
|
||||
m[2] = v0 >> 31
|
||||
z := m[3:]
|
||||
bigint.i31_zero(z, k[0])
|
||||
z[1] = u0 & bigint.I31_MASK
|
||||
z[2] = u0 >> 31
|
||||
bigint.i31_mulacc(z, k, m)
|
||||
|
||||
// Encode the result.
|
||||
bigint.i31_encode(modulus_bytes(&sk._d), z)
|
||||
|
||||
return 1
|
||||
}
|
||||
49
core/crypto/rsa/rsa_mgf1.odin
Normal file
49
core/crypto/rsa/rsa_mgf1.odin
Normal file
@@ -0,0 +1,49 @@
|
||||
package rsa
|
||||
|
||||
// Copyright (c) 2018 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import "core:crypto/hash"
|
||||
import "core:encoding/endian"
|
||||
|
||||
@(private)
|
||||
mgf1_xor :: proc(data: []byte, hash_algo: hash.Algorithm, seed: []byte) {
|
||||
tmp: [hash.MAX_DIGEST_SIZE]byte = ---
|
||||
ctx: hash.Context = ---
|
||||
|
||||
buf, blen := data, len(data)
|
||||
hlen := hash.DIGEST_SIZES[hash_algo]
|
||||
digest := tmp[:hlen]
|
||||
for u, c := int(0), u32(0); u < blen; u, c = u + hlen, c + 1 {
|
||||
hash.init(&ctx, hash_algo)
|
||||
hash.update(&ctx, seed)
|
||||
endian.unchecked_put_u32be(tmp[:], c)
|
||||
hash.update(&ctx, tmp[:4])
|
||||
hash.final(&ctx, digest)
|
||||
for v in 0..<hlen {
|
||||
if u + v >= blen {
|
||||
break
|
||||
}
|
||||
buf[u + v] ~= digest[v]
|
||||
}
|
||||
}
|
||||
}
|
||||
165
core/crypto/rsa/rsa_modpow_priv.odin
Normal file
165
core/crypto/rsa/rsa_modpow_priv.odin
Normal file
@@ -0,0 +1,165 @@
|
||||
package rsa
|
||||
|
||||
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import "core:crypto"
|
||||
import bigint "core:crypto/_bigint"
|
||||
import "core:slice"
|
||||
|
||||
@(private, require_results)
|
||||
private_modpow :: proc(x: []byte, sk: ^Private_Key) -> u32 {
|
||||
U :: (2 + ((FACTOR_MAX_SIZE + 30) / 31))
|
||||
TLEN :: (4 * U) // TLEN is counted in 64-bit words
|
||||
|
||||
ensure(sk._is_initialized, "crypto/rsa: uninitialized private key")
|
||||
|
||||
// Compute the actual lengths of p and q, in bytes.
|
||||
// These lengths are not considered secret (we cannot really hide
|
||||
// them anyway in constant-time code).
|
||||
//
|
||||
// Note/yawning: The factors should already be the correct size,
|
||||
// with leading `0x00`s stripped.
|
||||
p := factor_bytes(&sk._p)
|
||||
plen := len(p)
|
||||
for plen > 0 && p[0] == 0 {
|
||||
p = p[1:]
|
||||
plen -= 1
|
||||
}
|
||||
q := factor_bytes(&sk._q)
|
||||
qlen := len(q)
|
||||
for qlen > 0 && q[0] == 0 {
|
||||
q = q[1:]
|
||||
qlen -= 1
|
||||
}
|
||||
|
||||
// Compute the maximum factor length, in 31-bit words.
|
||||
z := max(plen, qlen) << 3
|
||||
fwlen := 1
|
||||
for z > 0 {
|
||||
z -= 31
|
||||
fwlen += 1
|
||||
}
|
||||
|
||||
// Convert size to 62-bit words.
|
||||
fwlen = (fwlen + 1) >> 1
|
||||
|
||||
// We need to fit at least 6 values in the stack buffer.
|
||||
if 6 * fwlen > TLEN {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Compute signature length (in bytes).
|
||||
xlen := modulus_len(&sk._pub_key._n)
|
||||
|
||||
tmp_: [TLEN]u64 // WARNING: This must be zeroed out.
|
||||
defer crypto.zero_explicit(&tmp_, size_of(tmp_))
|
||||
tmp := tmp_[:]
|
||||
|
||||
// Decode q.
|
||||
mq := slice.reinterpret([]u32, tmp)
|
||||
bigint.i31_decode(mq, q)
|
||||
|
||||
// Decode p.
|
||||
t1 := slice.reinterpret([]u32, tmp[fwlen:])
|
||||
bigint.i31_decode(t1, p)
|
||||
|
||||
// Upstream recomputes the public modulus n, but we can just
|
||||
// decode it as our key representation stores all PKCS#1
|
||||
// private key values,
|
||||
t2 := slice.reinterpret([]u32, tmp[2*fwlen:])
|
||||
bigint.i31_decode(t2, modulus_bytes(&sk._pub_key._n))
|
||||
|
||||
// We encode the modulus into bytes, to perform the comparison
|
||||
// with bytes. We know that the product length, in bytes, is
|
||||
// exactly xlen.
|
||||
// The comparison actually computes the carry when subtracting
|
||||
// the modulus from the source value; that carry must be 1 for
|
||||
// a value in the correct range. We keep it in r, which is our
|
||||
// accumulator for the error code.
|
||||
m_buf := slice.reinterpret([]byte, tmp[4*fwlen:])
|
||||
bigint.i31_encode(m_buf[:xlen], t2)
|
||||
u := xlen
|
||||
r: u32
|
||||
for u > 0 {
|
||||
u -= 1
|
||||
wn := u32(m_buf[u])
|
||||
wx := u32(x[u])
|
||||
r = ((wx - (wn + r)) >> 8) & 1
|
||||
}
|
||||
|
||||
// Move the decoded p to another temporary buffer.
|
||||
mp := t2
|
||||
copy(mp, t1[:2*fwlen])
|
||||
|
||||
// Compute s2 = x^dq mod q.
|
||||
q0i := bigint.i31_ninv31(mq[1])
|
||||
s2 := t1
|
||||
bigint.i31_decode_reduce(s2, x, mq)
|
||||
r &= bigint.i62_modpow_opt(s2, factor_bytes(&sk._dq), mq, q0i, tmp[3*fwlen:])
|
||||
|
||||
// Compute s1 = x^dp mod p.
|
||||
p0i := bigint.i31_ninv31(mp[1])
|
||||
s1 := slice.reinterpret([]u32, tmp[3*fwlen:])
|
||||
bigint.i31_decode_reduce(s1, x, mp)
|
||||
r &= bigint.i62_modpow_opt(s1, factor_bytes(&sk._dp), mp, p0i, tmp[4*fwlen:])
|
||||
|
||||
// Compute:
|
||||
// h = (s1 - s2)*(1/q) mod p
|
||||
// s1 is an integer modulo p, but s2 is modulo q. PKCS#1 is
|
||||
// unclear about whether p may be lower than q (some existing,
|
||||
// widely deployed implementations of RSA don't tolerate p < q),
|
||||
// but we want to support that occurrence, so we need to use the
|
||||
// reduction function.
|
||||
//
|
||||
// Since we use br_i31_decode_reduce() for iq (purportedly, the
|
||||
// inverse of q modulo p), we also tolerate improperly large
|
||||
// values for this parameter.
|
||||
t1 = slice.reinterpret([]u32, tmp[4*fwlen:])
|
||||
t2 = slice.reinterpret([]u32, tmp[5*fwlen:])
|
||||
bigint.i31_reduce(t2, s2, mp)
|
||||
_ = bigint.i31_add(s1, mp, bigint.i31_sub(s1, t2, 1))
|
||||
bigint.i31_to_monty(s1, mp)
|
||||
bigint.i31_decode_reduce(t1, factor_bytes(&sk._iq), mp)
|
||||
bigint.i31_montymul(t2, s1, t1, mp, p0i)
|
||||
|
||||
// h is now in t2. We compute the final result:
|
||||
// s = s2 + q*h
|
||||
// All these operations are non-modular.
|
||||
//
|
||||
// We need mq, s2 and t2. We use the t3 buffer as destination.
|
||||
// The buffers mp, s1 and t1 are no longer needed, so we can
|
||||
// reuse them for t3. Moreover, the first step of the computation
|
||||
// is to copy s2 into t3, after which s2 is not needed. Right
|
||||
// now, mq is in slot 0, s2 is in slot 1, and t2 is in slot 5.
|
||||
// Therefore, we have ample room for t3 by simply using s2.
|
||||
t3 := s2
|
||||
bigint.i31_mulacc(t3, mq, t2)
|
||||
|
||||
// Encode the result. Since we already checked the value of xlen,
|
||||
// we can just use it right away.
|
||||
bigint.i31_encode(x, t3)
|
||||
|
||||
// The only error conditions remaining at that point are invalid
|
||||
// values for p and q (even integers).
|
||||
return p0i & q0i & r
|
||||
}
|
||||
89
core/crypto/rsa/rsa_modpow_pub.odin
Normal file
89
core/crypto/rsa/rsa_modpow_pub.odin
Normal file
@@ -0,0 +1,89 @@
|
||||
package rsa
|
||||
|
||||
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import bigint "core:crypto/_bigint"
|
||||
import "core:encoding/endian"
|
||||
import "core:slice"
|
||||
|
||||
@(private, require_results)
|
||||
public_modpow :: proc(x: []byte, pk: ^Public_Key) -> u32 {
|
||||
TLEN :: (2 * (2 + ((MODULUS_MAX_SIZE + 30) / 31)))
|
||||
|
||||
ensure(pk._is_initialized, "crypto/rsa: uninitialized public key")
|
||||
|
||||
// Get the actual length of the modulus, and see if it fits within
|
||||
// our stack buffer. We also check that the length of x[] is valid.
|
||||
//
|
||||
// Note/yawning: The modulus should already be the correct size,
|
||||
// with leading `0x00`s stripped.
|
||||
n := modulus_bytes(&pk._n)
|
||||
nlen := modulus_len(&pk._n)
|
||||
for nlen > 0 && n[0] == 0 {
|
||||
n = n[1:]
|
||||
nlen -= 1
|
||||
}
|
||||
if nlen == 0 || nlen > (MODULUS_MAX_SIZE >> 3) || len(x) != nlen {
|
||||
return 0
|
||||
}
|
||||
z := nlen << 3
|
||||
fwlen := 1
|
||||
for z > 0 {
|
||||
z -= 31
|
||||
fwlen += 1
|
||||
}
|
||||
// Convert fwlen to a count in 62-bit words.
|
||||
fwlen = (fwlen + 1) >> 1
|
||||
|
||||
// The modulus gets decoded into m[].
|
||||
// The value to exponentiate goes into a[].
|
||||
tmp: [TLEN]u64 // WARNING: This must be zeroed out.
|
||||
m := slice.reinterpret([]u32, tmp[:fwlen])
|
||||
a := slice.reinterpret([]u32, tmp[fwlen:2*fwlen])
|
||||
|
||||
// Decode the modulus.
|
||||
bigint.i31_decode(m, n)
|
||||
m0i := bigint.i31_ninv31(m[1])
|
||||
|
||||
// Note: if m[] is even, then m0i == 0. Otherwise, m0i must be
|
||||
// an odd integer.
|
||||
r := m0i & 1
|
||||
|
||||
// Decode x[] into a[]; we also check that its value is proper.
|
||||
r &= bigint.i31_decode_mod(a, x, m)
|
||||
|
||||
// Compute the modular exponentiation.
|
||||
e_: [EXPONENT_MAX_SIZE >> 3]byte
|
||||
e_off: int
|
||||
endian.unchecked_put_u32be(e_[:], pk._e)
|
||||
if e_[0] == 0 {
|
||||
// `e = 65537` is the most common and sensible value, so this
|
||||
// is the most sensible value.
|
||||
e_off = 1
|
||||
}
|
||||
bigint.i62_modpow_opt(a, e_[e_off:], m, m0i, tmp[2*fwlen:])
|
||||
|
||||
// Encode the result.
|
||||
bigint.i31_encode(x, a)
|
||||
return r
|
||||
}
|
||||
233
core/crypto/rsa/rsa_sig_pkcs1.odin
Normal file
233
core/crypto/rsa/rsa_sig_pkcs1.odin
Normal file
@@ -0,0 +1,233 @@
|
||||
package rsa
|
||||
|
||||
// Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import "core:bytes"
|
||||
import "core:crypto"
|
||||
import "core:crypto/hash"
|
||||
|
||||
// PKCS1_HASH_OIDS maps common hash algorithms to the OIDs for
|
||||
// use with PKCS#1 signatures.
|
||||
@(rodata)
|
||||
PKCS1_HASH_OIDS := #partial [hash.Algorithm][]byte {
|
||||
// WARNING: Legacy verification ONLY.
|
||||
.Insecure_SHA1 = []byte{
|
||||
0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A,
|
||||
},
|
||||
.SHA224 = []byte{
|
||||
0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04,
|
||||
},
|
||||
.SHA256 = []byte{
|
||||
0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01,
|
||||
},
|
||||
.SHA384 = []byte{
|
||||
0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02,
|
||||
},
|
||||
.SHA512 = []byte{
|
||||
0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03,
|
||||
},
|
||||
.SHA512_256 = []byte{
|
||||
0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x06,
|
||||
},
|
||||
}
|
||||
|
||||
@(private="file", rodata)
|
||||
PKCS1_SELFTEST_DIGEST_SHA256 := []byte{
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
|
||||
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
|
||||
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
|
||||
}
|
||||
|
||||
// verify_pkcs1 returns true if and only if (⟺) sig is a valid PKCS#1
|
||||
// signature by pub_key over msg, hased using hash_algo. If pre_hashed
|
||||
// is set to true, it is assumed that msg is already hashed.
|
||||
@(require_results)
|
||||
verify_pkcs1 :: proc(pub_key: ^Public_Key, hash_algo: hash.Algorithm, msg, sig: []byte, is_prehashed := false) -> bool {
|
||||
if !pub_key._is_initialized {
|
||||
return false
|
||||
}
|
||||
if len(sig) != modulus_len(&pub_key._n) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Lookup the OID.
|
||||
oid := PKCS1_HASH_OIDS[hash_algo]
|
||||
if oid == nil {
|
||||
return false
|
||||
}
|
||||
hash_len := hash.DIGEST_SIZES[hash_algo]
|
||||
|
||||
// Compute the message hash.
|
||||
msg_hash_buf: [hash.MAX_DIGEST_SIZE]byte = ---
|
||||
msg_hash: []byte
|
||||
switch is_prehashed {
|
||||
case true:
|
||||
if len(msg) != hash_len {
|
||||
return false
|
||||
}
|
||||
msg_hash = msg
|
||||
case false:
|
||||
msg_hash = hash.hash_bytes_to_buffer(hash_algo, msg, msg_hash_buf[:])
|
||||
}
|
||||
|
||||
// PKCS #1 V2.2 (RFC 8017) 8.2.2 specifies this as computing
|
||||
// and comparing the padded hash, with unpadding and extracting
|
||||
// the hash being an alternative. Upstream BearSSL implements
|
||||
// the latter, which is not a problem if done correctly (which
|
||||
// it does), however we will opt to go for implementing this
|
||||
// as specified as it is more robust against implementation
|
||||
// errors.
|
||||
|
||||
// Compute the expected hash.
|
||||
sig_buf, padded_hash_buf: [MODULUS_MAX_SIZE >> 3]byte = ---, ---
|
||||
if len(sig) > len(sig_buf) {
|
||||
return false
|
||||
}
|
||||
padded_hash_ := padded_hash_buf[:len(sig)]
|
||||
if pkcs1_sig_pad(oid, msg_hash, padded_hash_) != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Compute the signature's padded hash.
|
||||
sig_ := sig_buf[:len(sig)]
|
||||
copy(sig_, sig)
|
||||
if public_modpow(sig_, pub_key) != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
return bytes.equal(sig_, padded_hash_)
|
||||
}
|
||||
|
||||
// sign_pkcs1 returns true if and only if (⟺) it successfully writes
|
||||
// the PKCS#1 signature by priv_key over msg, hashed using hash_algo.
|
||||
// If pre_hashed is set to true, it is assumed that msg is already hashed.
|
||||
@(require_results)
|
||||
sign_pkcs1 :: proc(priv_key: ^Private_Key, hash_algo: hash.Algorithm, msg, sig: []byte, is_prehashed := false) -> bool {
|
||||
if !priv_key._is_initialized {
|
||||
return false
|
||||
}
|
||||
if len(sig) != modulus_len(&priv_key._pub_key._n) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Lookup the OID.
|
||||
oid := PKCS1_HASH_OIDS[hash_algo]
|
||||
if oid == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Compute the message hash.
|
||||
msg_hash_buf: [hash.MAX_DIGEST_SIZE]byte = ---
|
||||
msg_hash: []byte
|
||||
switch is_prehashed {
|
||||
case true:
|
||||
if len(msg) != hash.DIGEST_SIZES[hash_algo] {
|
||||
return false
|
||||
}
|
||||
msg_hash = msg
|
||||
case false:
|
||||
msg_hash = hash.hash_bytes_to_buffer(hash_algo, msg, msg_hash_buf[:])
|
||||
}
|
||||
|
||||
if pkcs1_sig_pad(oid, msg_hash, sig) != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
return private_modpow(sig, priv_key) == 1
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
pkcs1_sig_pad :: proc "contextless" (hash_oid, hash, x: []byte) -> u32 {
|
||||
// Padded hash value has format:
|
||||
// 00 01 FF .. FF 00 30 x1 30 x2 06 x3 OID 05 00 04 x4 HASH
|
||||
//
|
||||
// with the following rules:
|
||||
//
|
||||
// -- Total length is equal to the modulus length (unsigned
|
||||
// encoding).
|
||||
//
|
||||
// -- There must be at least eight bytes of value 0xFF.
|
||||
//
|
||||
// -- x4 is equal to the hash length (hash_len).
|
||||
//
|
||||
// -- x3 is equal to the encoded OID value length (hash_oid[0]).
|
||||
//
|
||||
// -- x2 = x3 + 4.
|
||||
//
|
||||
// -- x1 = x2 + x4 + 4 = x3 + x4 + 8.
|
||||
//
|
||||
// Note: the "05 00" is optional (signatures with and without
|
||||
// that sequence exist in practice), but notes in PKCS#1 seem to
|
||||
// indicate that the presence of that sequence (specifically,
|
||||
// an ASN.1 NULL value for the hash parameters) may be slightly
|
||||
// more "standard" than the opposite.
|
||||
xlen, hash_len := len(x), len(hash)
|
||||
|
||||
// Note/yawning: The hash OID is mandatory, as is the "05 00".
|
||||
x3 := hash_oid[0]
|
||||
|
||||
// Check that there is enough room for all the elements,
|
||||
// including at least eight bytes of value 0xFF.
|
||||
if xlen < int(x3) + hash_len + 21 {
|
||||
return 0
|
||||
}
|
||||
x[0] = 0x00
|
||||
x[1] = 0x01
|
||||
u := xlen - int(x3) - hash_len - 11
|
||||
for i in 2..< u {
|
||||
x[i] = 0xff
|
||||
}
|
||||
x[u] = 0x00
|
||||
x[u + 1] = 0x30
|
||||
x[u + 2] = x3 + byte(hash_len) + 8
|
||||
x[u + 3] = 0x30
|
||||
x[u + 4] = x3 + 4
|
||||
x[u + 5] = 0x06
|
||||
copy(x[u+6:], hash_oid)
|
||||
u += int(x3) + 7
|
||||
x[u] = 0x05
|
||||
u += 1
|
||||
x[u] = 0x00
|
||||
u += 1
|
||||
x[u] = 0x04
|
||||
u += 1
|
||||
x[u] = byte(hash_len)
|
||||
u += 1
|
||||
copy(x[u:], hash)
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
@(private)
|
||||
pkcs1_sig_selftest :: proc(priv_key: ^Private_Key) -> bool {
|
||||
sig_buf: [MODULUS_MAX_SIZE >> 3]byte = ---
|
||||
defer crypto.zero_explicit(&sig_buf, size_of(sig_buf))
|
||||
|
||||
sig := sig_buf[:private_key_size(priv_key)]
|
||||
if !sign_pkcs1(priv_key, .SHA256, PKCS1_SELFTEST_DIGEST_SHA256, sig, true) {
|
||||
return false
|
||||
}
|
||||
|
||||
return verify_pkcs1(&priv_key._pub_key, .SHA256, PKCS1_SELFTEST_DIGEST_SHA256, sig, true)
|
||||
}
|
||||
293
core/crypto/rsa/rsa_sig_pss.odin
Normal file
293
core/crypto/rsa/rsa_sig_pss.odin
Normal file
@@ -0,0 +1,293 @@
|
||||
package rsa
|
||||
|
||||
// Copyright (c) 2018 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:crypto"
|
||||
import bigint "core:crypto/_bigint"
|
||||
import subtle "core:crypto/_subtle"
|
||||
import "core:crypto/hash"
|
||||
|
||||
// verify_pss returns true if and only if (⟺) sig is a valid PSS
|
||||
// signature by pub_key over msg, hashed using hash_algo, and MGF1
|
||||
// parameterized by mgf1_algo and salt_len. If mgf1_algo is
|
||||
// unspecified, hash_algo will be used. If pre_hashed is set
|
||||
// to true, it is assumed that msg is already hashed.
|
||||
@(require_results)
|
||||
verify_pss :: proc(
|
||||
pub_key: ^Public_Key,
|
||||
hash_algo: hash.Algorithm,
|
||||
salt_len: int,
|
||||
msg: []byte,
|
||||
sig: []byte,
|
||||
is_prehashed := false,
|
||||
mgf1_algo := hash.Algorithm.Invalid,
|
||||
) -> bool {
|
||||
if !pub_key._is_initialized {
|
||||
return false
|
||||
}
|
||||
if hash_algo == .Invalid {
|
||||
return false
|
||||
}
|
||||
mgf1_algo_ := mgf1_algo
|
||||
if mgf1_algo == .Invalid {
|
||||
mgf1_algo_ = hash_algo
|
||||
}
|
||||
if len(sig) != modulus_len(&pub_key._n) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Compute the message hash.
|
||||
msg_hash_buf: [hash.MAX_DIGEST_SIZE]byte = ---
|
||||
hash_len := hash.DIGEST_SIZES[hash_algo]
|
||||
msg_hash: []byte
|
||||
switch is_prehashed {
|
||||
case true:
|
||||
if len(msg) != hash_len {
|
||||
return false
|
||||
}
|
||||
msg_hash = msg
|
||||
case false:
|
||||
msg_hash = hash.hash_bytes_to_buffer(hash_algo, msg, msg_hash_buf[:])
|
||||
}
|
||||
|
||||
sig_buf: [MODULUS_MAX_SIZE >> 3]byte = ---
|
||||
sig_ := sig_buf[:len(sig)]
|
||||
copy(sig_, sig)
|
||||
if public_modpow(sig_, pub_key) != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
return pss_sig_unpad(hash_algo, mgf1_algo_, msg_hash, salt_len, pub_key, sig_) == 1
|
||||
}
|
||||
|
||||
// sign_pss returns true if and only if (⟺) it successfully writes
|
||||
// the PKCS#1 signature by priv_key over msg, hashed using hash_algo, and
|
||||
// MGF1 parameterized by mgf1_algo and salt_len. If mgf1_algo is
|
||||
// unspecified, hash_algo will be used. If pre_hashed is set to true,
|
||||
// it is assumed that msg is already hashed. A reasonable choice for
|
||||
// salt_len is the digest size of hash_algo, and FIPS 140-3 mandates
|
||||
// that as the maximum permissible size.
|
||||
//
|
||||
// This routine will fail if the system entropy source is unavailable.
|
||||
@(require_results)
|
||||
sign_pss :: proc(
|
||||
priv_key: ^Private_Key,
|
||||
hash_algo: hash.Algorithm,
|
||||
salt_len: int,
|
||||
msg: []byte,
|
||||
sig: []byte,
|
||||
is_prehashed := false,
|
||||
mgf1_algo := hash.Algorithm.Invalid,
|
||||
) -> bool {
|
||||
if !priv_key._is_initialized {
|
||||
return false
|
||||
}
|
||||
if len(sig) != modulus_len(&priv_key._pub_key._n) {
|
||||
return false
|
||||
}
|
||||
if hash_algo == .Invalid {
|
||||
return false
|
||||
}
|
||||
mgf1_algo_ := mgf1_algo
|
||||
if mgf1_algo == .Invalid {
|
||||
mgf1_algo_ = hash_algo
|
||||
}
|
||||
if !crypto.HAS_RAND_BYTES && salt_len != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Compute the message hash.
|
||||
msg_hash_buf: [hash.MAX_DIGEST_SIZE]byte = ---
|
||||
hash_len := hash.DIGEST_SIZES[hash_algo]
|
||||
msg_hash: []byte
|
||||
switch is_prehashed {
|
||||
case true:
|
||||
if len(msg) != hash_len {
|
||||
return false
|
||||
}
|
||||
msg_hash = msg
|
||||
case false:
|
||||
msg_hash = hash.hash_bytes_to_buffer(hash_algo, msg, msg_hash_buf[:])
|
||||
}
|
||||
|
||||
// Work out the exact length of n in bits.
|
||||
n := modulus_bytes(&priv_key._pub_key._n)
|
||||
assert(len(n) > 0 && n[0] != 0)
|
||||
n_bitlen := int(bigint._u32_bit_length(u32(n[0]))) + (len(n) - 1) * 8
|
||||
|
||||
if pss_sig_pad(hash_algo, mgf1_algo_, msg_hash, salt_len, n_bitlen, sig) != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
return private_modpow(sig, priv_key) == 1
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
pss_sig_unpad :: proc(
|
||||
data_algo: hash.Algorithm,
|
||||
mgf1_algo: hash.Algorithm,
|
||||
digest: []byte,
|
||||
salt_len: int,
|
||||
pk: ^Public_Key,
|
||||
sig: []byte,
|
||||
) -> u32 {
|
||||
hash_len := hash.DIGEST_SIZES[data_algo]
|
||||
x := sig
|
||||
|
||||
// Value r will be set to a non-zero value is any test fails.
|
||||
r: u32
|
||||
|
||||
// The value bit length (as an integer) must be strictly less than
|
||||
// that of the modulus.
|
||||
//
|
||||
// Note/yawning: The modulus should already be the correct size,
|
||||
// with leading `0x00`s stripped.
|
||||
n := modulus_bytes(&pk._n)
|
||||
nlen := modulus_len(&pk._n)
|
||||
u: int
|
||||
for u = 0; u < nlen; u += 1 {
|
||||
if n[u] != 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if u == nlen {
|
||||
return 0
|
||||
}
|
||||
n_bitlen := bigint._u32_bit_length(u32(n[u])) + (u32(nlen - u - 1) << 3)
|
||||
n_bitlen -= 1
|
||||
if (n_bitlen & 7) == 0 {
|
||||
r |= u32(x[0])
|
||||
x = x[1:]
|
||||
} else {
|
||||
r |= u32(x[0] & (0xFF << (n_bitlen & 7)))
|
||||
}
|
||||
xlen := int((n_bitlen + 7) >> 3)
|
||||
|
||||
// Check that the modulus is large enough for the hash value
|
||||
// length combined with the intended salt length.
|
||||
if hash_len > xlen || salt_len > xlen || (hash_len + salt_len + 2) > xlen {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Check value of rightmost byte.
|
||||
r |= u32(x[xlen - 1] ~ 0xBC)
|
||||
|
||||
// Generate the mask and XOR it into the first bytes to reveal PS;
|
||||
// we must also mask out the leading bits.
|
||||
seed := x[xlen - hash_len - 1:]
|
||||
mgf1_xor(x[:xlen - hash_len - 1], mgf1_algo, seed[:hash_len])
|
||||
if (n_bitlen & 7) != 0 {
|
||||
x[0] &= 0xFF >> (8 - (n_bitlen & 7))
|
||||
}
|
||||
|
||||
// Check that all padding bytes have the expected value.
|
||||
for u = 0; u < (xlen - hash_len - salt_len - 2); u += 1 {
|
||||
r |= u32(x[u])
|
||||
}
|
||||
r |= u32(x[xlen - hash_len - salt_len - 2] ~ 0x01)
|
||||
|
||||
// Recompute H.
|
||||
salt := x[xlen - hash_len - salt_len - 1:]
|
||||
tmp: [hash.MAX_DIGEST_SIZE]byte
|
||||
h := tmp[:hash_len]
|
||||
ctx: hash.Context = ---
|
||||
hash.init(&ctx, data_algo)
|
||||
hash.update(&ctx, tmp[:8])
|
||||
hash.update(&ctx, digest)
|
||||
hash.update(&ctx, salt[:salt_len])
|
||||
hash.final(&ctx, h)
|
||||
|
||||
// Check that the recomputed H value matches the one appearing
|
||||
// in the string.
|
||||
x = x[xlen - hash_len - 1:]
|
||||
r |= subtle.eq0(u32(crypto.compare_constant_time(h, x[:hash_len])))
|
||||
|
||||
return subtle.eq0(r)
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
pss_sig_pad :: proc(
|
||||
data_algo: hash.Algorithm,
|
||||
mgf1_algo: hash.Algorithm,
|
||||
digest: []byte,
|
||||
salt_len: int,
|
||||
n_bitlen_: int,
|
||||
sig: []byte,
|
||||
) -> u32 {
|
||||
x, n_bitlen := sig, n_bitlen_
|
||||
hash_len := hash.DIGEST_SIZES[data_algo]
|
||||
|
||||
// The padded string is one bit smaller than the modulus;
|
||||
// notably, if the modulus length is equal to 1 modulo 8, then
|
||||
// the padded string will be one _byte_ smaller, and the first
|
||||
// byte will be set to 0. We apply these transformations here.
|
||||
n_bitlen -= 1
|
||||
if (n_bitlen & 7) == 0 {
|
||||
x[0] = 0
|
||||
x = x[1:]
|
||||
}
|
||||
xlen := int((n_bitlen + 7) >> 3)
|
||||
|
||||
// Check that the modulus is large enough for the hash value
|
||||
// length combined with the intended salt length.
|
||||
if hash_len > xlen || salt_len > xlen || (hash_len + salt_len + 2) > xlen {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Produce a random salt.
|
||||
salt := x[xlen - hash_len - salt_len - 1:]
|
||||
salt = salt[:salt_len]
|
||||
if salt_len != 0 {
|
||||
crypto.rand_bytes(salt)
|
||||
}
|
||||
|
||||
// Compute the seed for MGF1.
|
||||
seed := x[xlen - hash_len - 1:]
|
||||
seed = seed[:hash_len]
|
||||
ctx: hash.Context = ---
|
||||
hash.init(&ctx, data_algo)
|
||||
intrinsics.mem_zero(raw_data(seed), 8)
|
||||
hash.update(&ctx, seed[:8])
|
||||
hash.update(&ctx, digest)
|
||||
hash.update(&ctx, salt)
|
||||
hash.final(&ctx, seed)
|
||||
|
||||
// Prepare string PS (padded salt). The salt is already at the
|
||||
// right place.
|
||||
intrinsics.mem_zero(raw_data(x), xlen - salt_len - hash_len - 2)
|
||||
x[xlen - salt_len - hash_len - 2] = 0x01
|
||||
|
||||
// Generate the mask and XOR it into PS.
|
||||
mgf1_xor(x[:xlen - hash_len - 1], mgf1_algo, seed)
|
||||
|
||||
// Clear the top bits to ensure the value is lower than the
|
||||
// modulus.
|
||||
x[0] &= 0xFF >> ((u32(xlen) << 3) - u32(n_bitlen))
|
||||
|
||||
// The seed (H) is already in the right place. We just set the
|
||||
// last byte.
|
||||
x[xlen - 1] = 0xBC
|
||||
|
||||
return 1
|
||||
}
|
||||
180
core/crypto/rsa/rsa_test_key.odin
Normal file
180
core/crypto/rsa/rsa_test_key.odin
Normal file
@@ -0,0 +1,180 @@
|
||||
package rsa
|
||||
|
||||
// private_key_set_insecure_test sets the private key to the
|
||||
// pregenerated INSECURE test key "testRSA2048" from RFC 9500 2.1.
|
||||
//
|
||||
// WARNING: This key MUST only be used for testing purposes.
|
||||
@(require_results)
|
||||
private_key_set_insecure_test :: proc(priv_key: ^Private_Key) -> bool {
|
||||
// RFC 9500 2.1 "testRSA2048"
|
||||
return private_key_set_bytes(
|
||||
priv_key,
|
||||
// n
|
||||
[]byte{
|
||||
0xB0, 0xF9, 0xE8, 0x19, 0x43, 0xA7, 0xAE, 0x98,
|
||||
0x92, 0xAA, 0xDE, 0x17, 0xCA, 0x7C, 0x40, 0xF8,
|
||||
0x74, 0x4F, 0xED, 0x2F, 0x81, 0x48, 0xE6, 0xC8,
|
||||
0xEA, 0xA2, 0x7B, 0x7D, 0x00, 0x15, 0x48, 0xFB,
|
||||
0x51, 0x92, 0xAB, 0x28, 0xB5, 0x6C, 0x50, 0x60,
|
||||
0xB1, 0x18, 0xCC, 0xD1, 0x31, 0xE5, 0x94, 0x87,
|
||||
0x4C, 0x6C, 0xA9, 0x89, 0xB5, 0x6C, 0x27, 0x29,
|
||||
0x6F, 0x09, 0xFB, 0x93, 0xA0, 0x34, 0xDF, 0x32,
|
||||
0xE9, 0x7C, 0x6F, 0xF0, 0x99, 0x8C, 0xFD, 0x8E,
|
||||
0x6F, 0x42, 0xDD, 0xA5, 0x8A, 0xCD, 0x1F, 0xA9,
|
||||
0x79, 0x86, 0xF1, 0x44, 0xF3, 0xD1, 0x54, 0xD6,
|
||||
0x76, 0x50, 0x17, 0x5E, 0x68, 0x54, 0xB3, 0xA9,
|
||||
0x52, 0x00, 0x3B, 0xC0, 0x68, 0x87, 0xB8, 0x45,
|
||||
0x5A, 0xC2, 0xB1, 0x9F, 0x7B, 0x2F, 0x76, 0x50,
|
||||
0x4E, 0xBC, 0x98, 0xEC, 0x94, 0x55, 0x71, 0xB0,
|
||||
0x78, 0x92, 0x15, 0x0D, 0xDC, 0x6A, 0x74, 0xCA,
|
||||
0x0F, 0xBC, 0xD3, 0x54, 0x97, 0xCE, 0x81, 0x53,
|
||||
0x4D, 0xAF, 0x94, 0x18, 0x84, 0x4B, 0x13, 0xAE,
|
||||
0xA3, 0x1F, 0x9D, 0x5A, 0x6B, 0x95, 0x57, 0xBB,
|
||||
0xDF, 0x61, 0x9E, 0xFD, 0x4E, 0x88, 0x7F, 0x2D,
|
||||
0x42, 0xB8, 0xDD, 0x8B, 0xC9, 0x87, 0xEA, 0xE1,
|
||||
0xBF, 0x89, 0xCA, 0xB8, 0x5E, 0xE2, 0x1E, 0x35,
|
||||
0x63, 0x05, 0xDF, 0x6C, 0x07, 0xA8, 0x83, 0x8E,
|
||||
0x3E, 0xF4, 0x1C, 0x59, 0x5D, 0xCC, 0xE4, 0x3D,
|
||||
0xAF, 0xC4, 0x91, 0x23, 0xEF, 0x4D, 0x8A, 0xBB,
|
||||
0xA9, 0x3D, 0x39, 0x05, 0xE4, 0x02, 0x8D, 0x7B,
|
||||
0xA9, 0x14, 0x84, 0xA2, 0x75, 0x96, 0xE0, 0x7B,
|
||||
0x4B, 0x6E, 0xD9, 0x92, 0xF0, 0x77, 0xB5, 0x24,
|
||||
0xD3, 0xDC, 0xFE, 0x7D, 0xDD, 0x55, 0x49, 0xBE,
|
||||
0x7C, 0xCE, 0x8D, 0xA0, 0x35, 0xCF, 0xA0, 0xB3,
|
||||
0xFB, 0x8F, 0x9E, 0x46, 0xF7, 0x32, 0xB2, 0xA8,
|
||||
0x6B, 0x46, 0x01, 0x65, 0xC0, 0x8F, 0x53, 0x13,
|
||||
},
|
||||
// e
|
||||
[]byte{0x01, 0x00, 0x01},
|
||||
// d
|
||||
[]byte{
|
||||
0x41, 0x18, 0x8B, 0x20, 0xCF, 0xDB, 0xDB, 0xC2,
|
||||
0xCF, 0x1F, 0xFE, 0x75, 0x2D, 0xCB, 0xAA, 0x72,
|
||||
0x39, 0x06, 0x35, 0x2E, 0x26, 0x15, 0xD4, 0x9D,
|
||||
0xCE, 0x80, 0x59, 0x7F, 0xCF, 0x0A, 0x05, 0x40,
|
||||
0x3B, 0xEF, 0x00, 0xFA, 0x06, 0x51, 0x82, 0xF7,
|
||||
0x2D, 0xEC, 0xFB, 0x59, 0x6F, 0x4B, 0x0C, 0xE8,
|
||||
0xFF, 0x59, 0x70, 0xBA, 0xF0, 0x7A, 0x89, 0xA5,
|
||||
0x19, 0xEC, 0xC8, 0x16, 0xB2, 0xF4, 0xFF, 0xAC,
|
||||
0x50, 0x69, 0xAF, 0x1B, 0x06, 0xBF, 0xEF, 0x7B,
|
||||
0xF6, 0xBC, 0xD7, 0x9E, 0x4E, 0x81, 0xC8, 0xC5,
|
||||
0xA3, 0xA7, 0xD9, 0x13, 0x0D, 0xC3, 0xCF, 0xBA,
|
||||
0xDA, 0xE5, 0xF6, 0xD2, 0x88, 0xF9, 0xAE, 0xE3,
|
||||
0xF6, 0xFF, 0x92, 0xFA, 0xE0, 0xF8, 0x1A, 0xF5,
|
||||
0x97, 0xBE, 0xC9, 0x6A, 0xE9, 0xFA, 0xB9, 0x40,
|
||||
0x2C, 0xD5, 0xFE, 0x41, 0xF7, 0x05, 0xBE, 0xBD,
|
||||
0xB4, 0x7B, 0xB7, 0x36, 0xD3, 0xFE, 0x6C, 0x5A,
|
||||
0x51, 0xE0, 0xE2, 0x07, 0x32, 0xA9, 0x7B, 0x5E,
|
||||
0x46, 0xC1, 0xCB, 0xDB, 0x26, 0xD7, 0x48, 0x54,
|
||||
0xC6, 0xB6, 0x60, 0x4A, 0xED, 0x46, 0x37, 0x35,
|
||||
0xFF, 0x90, 0x76, 0x04, 0x65, 0x57, 0xCA, 0xF9,
|
||||
0x49, 0xBF, 0x44, 0x88, 0x95, 0xC2, 0x04, 0x32,
|
||||
0xC1, 0xE0, 0x9C, 0x01, 0x4E, 0xA7, 0x56, 0x60,
|
||||
0x43, 0x4F, 0x1A, 0x0F, 0x3B, 0xE2, 0x94, 0xBA,
|
||||
0xBC, 0x5D, 0x53, 0x0E, 0x6A, 0x10, 0x21, 0x3F,
|
||||
0x53, 0xB6, 0x03, 0x75, 0xFC, 0x84, 0xA7, 0x57,
|
||||
0x3F, 0x2A, 0xF1, 0x21, 0x55, 0x84, 0xF5, 0xB4,
|
||||
0xBD, 0xA6, 0xD4, 0xE8, 0xF9, 0xE1, 0x7A, 0x78,
|
||||
0xD9, 0x7E, 0x77, 0xB8, 0x6D, 0xA4, 0xA1, 0x84,
|
||||
0x64, 0x75, 0x31, 0x8A, 0x7A, 0x10, 0xA5, 0x61,
|
||||
0x01, 0x4E, 0xFF, 0xA2, 0x3A, 0x81, 0xEC, 0x56,
|
||||
0xE9, 0xE4, 0x10, 0x9D, 0xEF, 0x8C, 0xB3, 0xF7,
|
||||
0x97, 0x22, 0x3F, 0x7D, 0x8D, 0x0D, 0x43, 0x51,
|
||||
},
|
||||
// p
|
||||
[]byte{
|
||||
0xDD, 0x10, 0x57, 0x02, 0x38, 0x2F, 0x23, 0x2B,
|
||||
0x36, 0x81, 0xF5, 0x37, 0x91, 0xE2, 0x26, 0x17,
|
||||
0xC7, 0xBF, 0x4E, 0x9A, 0xCB, 0x81, 0xED, 0x48,
|
||||
0xDA, 0xF6, 0xD6, 0x99, 0x5D, 0xA3, 0xEA, 0xB6,
|
||||
0x42, 0x83, 0x9A, 0xFF, 0x01, 0x2D, 0x2E, 0xA6,
|
||||
0x28, 0xB9, 0x0A, 0xF2, 0x79, 0xFD, 0x3E, 0x6F,
|
||||
0x7C, 0x93, 0xCD, 0x80, 0xF0, 0x72, 0xF0, 0x1F,
|
||||
0xF2, 0x44, 0x3B, 0x3E, 0xE8, 0xF2, 0x4E, 0xD4,
|
||||
0x69, 0xA7, 0x96, 0x13, 0xA4, 0x1B, 0xD2, 0x40,
|
||||
0x20, 0xF9, 0x2F, 0xD1, 0x10, 0x59, 0xBD, 0x1D,
|
||||
0x0F, 0x30, 0x1B, 0x5B, 0xA7, 0xA9, 0xD3, 0x63,
|
||||
0x7C, 0xA8, 0xD6, 0x5C, 0x1A, 0x98, 0x15, 0x41,
|
||||
0x7D, 0x8E, 0xAB, 0x73, 0x4B, 0x0B, 0x4F, 0x3A,
|
||||
0x2C, 0x66, 0x1D, 0x9A, 0x1A, 0x82, 0xF3, 0xAC,
|
||||
0x73, 0x4C, 0x40, 0x53, 0x06, 0x69, 0xAB, 0x8E,
|
||||
0x47, 0x30, 0x45, 0xA5, 0x8E, 0x65, 0x53, 0x9D,
|
||||
},
|
||||
// q
|
||||
[]byte{
|
||||
0xCC, 0xF1, 0xE5, 0xBB, 0x90, 0xC8, 0xE9, 0x78,
|
||||
0x1E, 0xA7, 0x5B, 0xEB, 0xF1, 0x0B, 0xC2, 0x52,
|
||||
0xE1, 0x1E, 0xB0, 0x23, 0xA0, 0x26, 0x0F, 0x18,
|
||||
0x87, 0x55, 0x2A, 0x56, 0x86, 0x3F, 0x4A, 0x64,
|
||||
0x21, 0xE8, 0xC6, 0x00, 0xBF, 0x52, 0x3D, 0x6C,
|
||||
0xB1, 0xB0, 0xAD, 0xBD, 0xD6, 0x5B, 0xFE, 0xE4,
|
||||
0xA8, 0x8A, 0x03, 0x7E, 0x3D, 0x1A, 0x41, 0x5E,
|
||||
0x5B, 0xB9, 0x56, 0x48, 0xDA, 0x5A, 0x0C, 0xA2,
|
||||
0x6B, 0x54, 0xF4, 0xA6, 0x39, 0x48, 0x52, 0x2C,
|
||||
0x3D, 0x5F, 0x89, 0xB9, 0x4A, 0x72, 0xEF, 0xFF,
|
||||
0x95, 0x13, 0x4D, 0x59, 0x40, 0xCE, 0x45, 0x75,
|
||||
0x8F, 0x30, 0x89, 0x80, 0x90, 0x89, 0x56, 0x58,
|
||||
0x8E, 0xEF, 0x57, 0x5B, 0x3E, 0x4B, 0xC4, 0xC3,
|
||||
0x68, 0xCF, 0xE8, 0x13, 0xEE, 0x9C, 0x25, 0x2C,
|
||||
0x2B, 0x02, 0xE0, 0xDF, 0x91, 0xF1, 0xAA, 0x01,
|
||||
0x93, 0x8D, 0x38, 0x68, 0x5D, 0x60, 0xBA, 0x6F,
|
||||
},
|
||||
// dp
|
||||
[]byte{
|
||||
0x09, 0xED, 0x54, 0xEA, 0xED, 0x98, 0xF8, 0x4C,
|
||||
0x55, 0x7B, 0x4A, 0x86, 0xBF, 0x4F, 0x57, 0x84,
|
||||
0x93, 0xDC, 0xBC, 0x6B, 0xE9, 0x1D, 0xA1, 0x89,
|
||||
0x37, 0x04, 0x04, 0xA9, 0x08, 0x72, 0x76, 0xF4,
|
||||
0xCE, 0x51, 0xD8, 0xA1, 0x00, 0xED, 0x85, 0x7D,
|
||||
0xC2, 0xB0, 0x64, 0x94, 0x74, 0xF3, 0xF1, 0x5C,
|
||||
0xD2, 0x4C, 0x54, 0xDB, 0x28, 0x71, 0x10, 0xE5,
|
||||
0x6E, 0x5C, 0xB0, 0x08, 0x68, 0x2F, 0x91, 0x68,
|
||||
0xAA, 0x81, 0xF3, 0x14, 0x58, 0xB7, 0x43, 0x1E,
|
||||
0xCC, 0x1C, 0x44, 0x90, 0x6F, 0xDA, 0x87, 0xCA,
|
||||
0x89, 0x47, 0x10, 0xC3, 0x71, 0xE9, 0x07, 0x6C,
|
||||
0x1D, 0x49, 0xFB, 0xAE, 0x51, 0x27, 0x69, 0x34,
|
||||
0xF2, 0xAD, 0x78, 0x77, 0x89, 0xF4, 0x2D, 0x0F,
|
||||
0xA0, 0xB4, 0xC9, 0x39, 0x85, 0x5D, 0x42, 0x12,
|
||||
0x09, 0x6F, 0x70, 0x28, 0x0A, 0x4E, 0xAE, 0x7C,
|
||||
0x8A, 0x27, 0xD9, 0xC8, 0xD0, 0x77, 0x2E, 0x65,
|
||||
},
|
||||
// dq
|
||||
[]byte{
|
||||
0x8C, 0xB6, 0x85, 0x7A, 0x7B, 0xD5, 0x46, 0x5F,
|
||||
0x80, 0x04, 0x7E, 0x9B, 0x87, 0xBC, 0x00, 0x27,
|
||||
0x31, 0x84, 0x05, 0x81, 0xE0, 0x62, 0x61, 0x39,
|
||||
0x01, 0x2A, 0x5B, 0x50, 0x5F, 0x0A, 0x33, 0x84,
|
||||
0x7E, 0xB7, 0xB8, 0xC3, 0x28, 0x99, 0x49, 0xAD,
|
||||
0x48, 0x6F, 0x3B, 0x4B, 0x3D, 0x53, 0x9A, 0xB5,
|
||||
0xDA, 0x76, 0x30, 0x21, 0xCB, 0xC8, 0x2C, 0x1B,
|
||||
0xA2, 0x34, 0xA5, 0x66, 0x8D, 0xED, 0x08, 0x01,
|
||||
0xB8, 0x59, 0xF3, 0x43, 0xF1, 0xCE, 0x93, 0x04,
|
||||
0xE6, 0xFA, 0xA2, 0xB0, 0x02, 0xCA, 0xD9, 0xB7,
|
||||
0x8C, 0xDE, 0x5C, 0xDC, 0x2C, 0x1F, 0xB4, 0x17,
|
||||
0x1C, 0x42, 0x42, 0x16, 0x70, 0xA6, 0xAB, 0x0F,
|
||||
0x50, 0xCC, 0x4A, 0x19, 0x4E, 0xB3, 0x6D, 0x1C,
|
||||
0x91, 0xE9, 0x35, 0xBA, 0x01, 0xB9, 0x59, 0xD8,
|
||||
0x72, 0x8B, 0x9E, 0x64, 0x42, 0x6B, 0x3F, 0xC3,
|
||||
0xA7, 0x50, 0x6D, 0xEB, 0x52, 0x39, 0xA8, 0xA7,
|
||||
},
|
||||
// iq (aka u)
|
||||
[]byte{
|
||||
0x0A, 0x81, 0xD8, 0xA6, 0x18, 0x31, 0x4A, 0x80,
|
||||
0x3A, 0xF6, 0x1C, 0x06, 0x71, 0x1F, 0x2C, 0x39,
|
||||
0xB2, 0x66, 0xFF, 0x41, 0x4D, 0x53, 0x47, 0x6D,
|
||||
0x1D, 0xA5, 0x2A, 0x43, 0x18, 0xAA, 0xFE, 0x4B,
|
||||
0x96, 0xF0, 0xDA, 0x07, 0x15, 0x5F, 0x8A, 0x51,
|
||||
0x34, 0xDA, 0xB8, 0x8E, 0xE2, 0x9E, 0x81, 0x68,
|
||||
0x07, 0x6F, 0xCD, 0x78, 0xCA, 0x79, 0x1A, 0xC6,
|
||||
0x34, 0x42, 0xA8, 0x1C, 0xD0, 0x69, 0x39, 0x27,
|
||||
0xD8, 0x08, 0xE3, 0x35, 0xE8, 0xD8, 0xCB, 0xF2,
|
||||
0x12, 0x19, 0x07, 0x50, 0x9A, 0x57, 0x75, 0x9B,
|
||||
0x4F, 0x9A, 0x18, 0xFA, 0x3A, 0x7B, 0x33, 0x37,
|
||||
0x79, 0xED, 0xDE, 0x7A, 0x45, 0x93, 0x84, 0xF8,
|
||||
0x44, 0x4A, 0xDA, 0xEC, 0xFF, 0xEC, 0x95, 0xFD,
|
||||
0x55, 0x2B, 0x0C, 0xFC, 0xB6, 0xC7, 0xF6, 0x92,
|
||||
0x62, 0x6D, 0xDE, 0x1E, 0xF2, 0x68, 0xA4, 0x0D,
|
||||
0x2F, 0x67, 0xB5, 0xC8, 0xAA, 0x38, 0x7F, 0xF7,
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -411,12 +411,13 @@ _decode_bytes :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := c
|
||||
io.copy_n(buf_stream, d.reader, i64(n)) or_return
|
||||
}
|
||||
|
||||
v = buf.buf[:]
|
||||
|
||||
// Write zero byte so this can be converted to cstring.
|
||||
strings.write_byte(&buf, 0)
|
||||
|
||||
if .Shrink_Excess in d.flags { shrink(&buf.buf) }
|
||||
|
||||
v = buf.buf[:len(buf.buf)-1]
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -884,4 +885,4 @@ _encode_deterministic_f64 :: proc(w: io.Writer, v: f64) -> io.Error {
|
||||
}
|
||||
|
||||
return _encode_f64_exact(w, v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -509,7 +509,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
||||
case reflect.Type_Info_Slice:
|
||||
length, scap := err_conv(_decode_len_container(d, add)) or_return
|
||||
|
||||
data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align, allocator=allocator, loc=loc) or_return
|
||||
data := mem.alloc_bytes(t.elem.size * scap, t.elem.align, allocator=allocator, loc=loc) or_return
|
||||
defer if err != nil { mem.free_bytes(data, allocator=allocator, loc=loc) }
|
||||
|
||||
da := mem.Raw_Dynamic_Array{raw_data(data), 0, scap, context.allocator }
|
||||
@@ -529,7 +529,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
||||
case reflect.Type_Info_Dynamic_Array:
|
||||
length, scap := err_conv(_decode_len_container(d, add)) or_return
|
||||
|
||||
data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align, loc=loc) or_return
|
||||
data := mem.alloc_bytes(t.elem.size * scap, t.elem.align, loc=loc) or_return
|
||||
defer if err != nil { mem.free_bytes(data, allocator=allocator, loc=loc) }
|
||||
|
||||
raw := (^mem.Raw_Dynamic_Array)(v.data)
|
||||
|
||||
@@ -146,6 +146,7 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator :=
|
||||
for i in 0..<count {
|
||||
write_rune(&builder, decoded[i])
|
||||
}
|
||||
prev = decoded[count - 1]
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +134,7 @@ register_user_marshaler :: proc(id: typeid, marshaler: User_Marshaler) -> Regist
|
||||
return .None
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
marshal :: proc(v: any, opt: Marshal_Options = {}, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Marshal_Error) {
|
||||
b := strings.builder_make(allocator, loc)
|
||||
defer if err != nil {
|
||||
|
||||
68
core/encoding/json/match.odin
Normal file
68
core/encoding/json/match.odin
Normal file
@@ -0,0 +1,68 @@
|
||||
package encoding_json
|
||||
|
||||
Match_Key_Variant :: union #no_nil {
|
||||
int, // Index
|
||||
string, // Key
|
||||
}
|
||||
|
||||
Match_Error :: enum {
|
||||
None,
|
||||
Invalid_Argument,
|
||||
Invalid_Type_For_Index,
|
||||
Invalid_Type_For_Key,
|
||||
Key_Not_Found,
|
||||
Out_Of_Bounds_Index,
|
||||
}
|
||||
|
||||
Match_Flags :: distinct bit_set[Match_Flag]
|
||||
Match_Flag :: enum {
|
||||
Ignore_Key_Not_Found,
|
||||
Allow_String_Indexing_By_Byte,
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
match :: proc(value: Value, args: ..Match_Key_Variant, flags: Match_Flags = nil) -> (found: Value, err: Match_Error) {
|
||||
found = value
|
||||
arg_loop: for arg in args {
|
||||
switch k in arg {
|
||||
case int:
|
||||
#partial switch v in found {
|
||||
case Array:
|
||||
if 0 <= k && k < len(v) {
|
||||
found = v[k]
|
||||
continue arg_loop
|
||||
}
|
||||
err = .Out_Of_Bounds_Index
|
||||
return
|
||||
case String:
|
||||
if .Allow_String_Indexing_By_Byte in flags {
|
||||
if 0 <= k && k < len(v) {
|
||||
found = Integer(v[k])
|
||||
continue arg_loop
|
||||
}
|
||||
err = .Out_Of_Bounds_Index
|
||||
return
|
||||
}
|
||||
}
|
||||
err = .Invalid_Type_For_Index
|
||||
return
|
||||
case string:
|
||||
v, ok := found.(Object)
|
||||
if !ok {
|
||||
err = .Invalid_Type_For_Key
|
||||
return
|
||||
}
|
||||
vfound, vok := v[k]
|
||||
if vok || (.Ignore_Key_Not_Found in flags) {
|
||||
found = vfound
|
||||
continue arg_loop
|
||||
}
|
||||
err = .Key_Not_Found
|
||||
return
|
||||
case:
|
||||
err = .Invalid_Argument
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -14,9 +14,16 @@ Parser :: struct {
|
||||
parse_integers: bool,
|
||||
}
|
||||
|
||||
make_parser :: proc(data: []byte, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator) -> Parser {
|
||||
make_parser :: proc{
|
||||
make_parser_from_bytes,
|
||||
make_parser_from_string,
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
make_parser_from_bytes :: proc(data: []byte, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator) -> Parser {
|
||||
return make_parser_from_string(string(data), spec, parse_integers, allocator)
|
||||
}
|
||||
@(require_results)
|
||||
make_parser_from_string :: proc(data: string, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator) -> Parser {
|
||||
p: Parser
|
||||
p.tok = make_tokenizer(data, spec, parse_integers)
|
||||
@@ -27,11 +34,18 @@ make_parser_from_string :: proc(data: string, spec := DEFAULT_SPECIFICATION, par
|
||||
return p
|
||||
}
|
||||
|
||||
parse :: proc{
|
||||
parse_bytes,
|
||||
parse_string,
|
||||
}
|
||||
|
||||
parse :: proc(data: []byte, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator, loc := #caller_location) -> (Value, Error) {
|
||||
|
||||
@(require_results)
|
||||
parse_bytes :: proc(data: []byte, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator, loc := #caller_location) -> (Value, Error) {
|
||||
return parse_string(string(data), spec, parse_integers, allocator, loc)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
parse_string :: proc(data: string, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator, loc := #caller_location) -> (Value, Error) {
|
||||
context.allocator = allocator
|
||||
p := make_parser_from_string(data, spec, parse_integers, allocator)
|
||||
@@ -51,6 +65,7 @@ parse_string :: proc(data: string, spec := DEFAULT_SPECIFICATION, parse_integers
|
||||
return parse_object(&p, loc)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
token_end_pos :: proc(tok: Token) -> Pos {
|
||||
end := tok.pos
|
||||
end.offset += len(tok.text)
|
||||
@@ -65,6 +80,7 @@ advance_token :: proc(p: ^Parser) -> (Token, Error) {
|
||||
}
|
||||
|
||||
|
||||
@(require_results)
|
||||
allow_token :: proc(p: ^Parser, kind: Token_Kind) -> bool {
|
||||
if p.curr_token.kind == kind {
|
||||
advance_token(p)
|
||||
@@ -73,6 +89,7 @@ allow_token :: proc(p: ^Parser, kind: Token_Kind) -> bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
expect_token :: proc(p: ^Parser, kind: Token_Kind) -> Error {
|
||||
prev := p.curr_token
|
||||
advance_token(p)
|
||||
@@ -83,6 +100,7 @@ expect_token :: proc(p: ^Parser, kind: Token_Kind) -> Error {
|
||||
}
|
||||
|
||||
|
||||
@(require_results)
|
||||
parse_colon :: proc(p: ^Parser) -> (err: Error) {
|
||||
colon_err := expect_token(p, .Colon)
|
||||
if colon_err == nil {
|
||||
@@ -91,6 +109,7 @@ parse_colon :: proc(p: ^Parser) -> (err: Error) {
|
||||
return .Expected_Colon_After_Key
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
parse_comma :: proc(p: ^Parser) -> (do_break: bool) {
|
||||
switch p.spec {
|
||||
case .JSON5, .MJSON:
|
||||
@@ -106,6 +125,7 @@ parse_comma :: proc(p: ^Parser) -> (do_break: bool) {
|
||||
return false
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
parse_value :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err: Error) {
|
||||
err = .None
|
||||
token := p.curr_token
|
||||
@@ -176,6 +196,7 @@ parse_value :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err:
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
parse_array :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err: Error) {
|
||||
err = .None
|
||||
expect_token(p, .Open_Bracket) or_return
|
||||
@@ -203,7 +224,7 @@ parse_array :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err:
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
@(private, require_results)
|
||||
bytes_make :: proc(size, alignment: int, allocator: mem.Allocator, loc := #caller_location) -> (bytes: []byte, err: Error) {
|
||||
b, berr := mem.alloc_bytes(size, alignment, allocator, loc)
|
||||
if berr != nil {
|
||||
@@ -217,6 +238,7 @@ bytes_make :: proc(size, alignment: int, allocator: mem.Allocator, loc := #calle
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
clone_string :: proc(s: string, allocator: mem.Allocator, loc := #caller_location) -> (str: string, err: Error) {
|
||||
n := len(s)
|
||||
b := bytes_make(n+1, 1, allocator, loc) or_return
|
||||
@@ -228,6 +250,7 @@ clone_string :: proc(s: string, allocator: mem.Allocator, loc := #caller_locatio
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
parse_object_key :: proc(p: ^Parser, key_allocator: mem.Allocator, loc := #caller_location) -> (key: string, err: Error) {
|
||||
tok := p.curr_token
|
||||
if p.spec != .JSON {
|
||||
@@ -242,6 +265,7 @@ parse_object_key :: proc(p: ^Parser, key_allocator: mem.Allocator, loc := #calle
|
||||
return unquote_string(tok, p.spec, key_allocator, loc)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
parse_object_body :: proc(p: ^Parser, end_token: Token_Kind, loc := #caller_location) -> (obj: Object, err: Error) {
|
||||
obj = make(Object, allocator=p.allocator, loc=loc)
|
||||
|
||||
@@ -282,6 +306,7 @@ parse_object_body :: proc(p: ^Parser, end_token: Token_Kind, loc := #caller_loca
|
||||
return obj, .None
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
parse_object :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err: Error) {
|
||||
expect_token(p, .Open_Brace) or_return
|
||||
obj := parse_object_body(p, .Close_Brace, loc) or_return
|
||||
@@ -291,6 +316,7 @@ parse_object :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err:
|
||||
|
||||
|
||||
// IMPORTANT NOTE(bill): unquote_string assumes a mostly valid string
|
||||
@(require_results)
|
||||
unquote_string :: proc(token: Token, spec: Specification, allocator := context.allocator, loc := #caller_location) -> (value: string, err: Error) {
|
||||
get_u2_rune :: proc(s: string) -> rune {
|
||||
if len(s) < 4 || s[0] != '\\' || s[1] != 'x' {
|
||||
|
||||
@@ -49,11 +49,12 @@ Tokenizer :: struct {
|
||||
curr_line_offset: int,
|
||||
spec: Specification,
|
||||
parse_integers: bool,
|
||||
insert_comma: bool,
|
||||
insert_comma: bool,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@(require_results)
|
||||
make_tokenizer :: proc(data: string, spec := DEFAULT_SPECIFICATION, parse_integers := false) -> Tokenizer {
|
||||
t := Tokenizer{pos = {line=1}, data = data, spec = spec, parse_integers = parse_integers}
|
||||
next_rune(&t)
|
||||
@@ -78,6 +79,7 @@ next_rune :: proc(t: ^Tokenizer) -> rune #no_bounds_check {
|
||||
}
|
||||
|
||||
|
||||
@(require_results)
|
||||
get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) {
|
||||
skip_digits :: proc(t: ^Tokenizer) {
|
||||
for t.offset < len(t.data) {
|
||||
@@ -101,6 +103,7 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) {
|
||||
}
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
scan_escape :: proc(t: ^Tokenizer) -> bool {
|
||||
switch t.r {
|
||||
case '"', '\'', '\\', '/', 'b', 'n', 'r', 't', 'f':
|
||||
@@ -125,7 +128,7 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) {
|
||||
return false
|
||||
}
|
||||
|
||||
skip_whitespace :: proc(t: ^Tokenizer, on_newline: bool) -> rune {
|
||||
skip_whitespace :: proc(t: ^Tokenizer, on_newline: bool) {
|
||||
loop: for t.offset < len(t.data) {
|
||||
switch t.r {
|
||||
case ' ', '\t', '\v', '\f', '\r':
|
||||
@@ -149,7 +152,7 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) {
|
||||
break loop
|
||||
}
|
||||
}
|
||||
return t.r
|
||||
return
|
||||
}
|
||||
|
||||
skip_to_next_line :: proc(t: ^Tokenizer) {
|
||||
@@ -310,7 +313,7 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) {
|
||||
break
|
||||
}
|
||||
if r == '\\' {
|
||||
scan_escape(t)
|
||||
_ = scan_escape(t)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,10 +350,8 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) {
|
||||
case '*':
|
||||
// None-nested multi-line comments
|
||||
for t.offset < len(t.data) {
|
||||
next_rune(t)
|
||||
if t.r == '*' {
|
||||
next_rune(t)
|
||||
if t.r == '/' {
|
||||
if next_rune(t) == '*' {
|
||||
if next_rune(t) == '/' {
|
||||
next_rune(t)
|
||||
return get_token(t)
|
||||
}
|
||||
@@ -385,6 +386,7 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) {
|
||||
|
||||
|
||||
|
||||
@(require_results)
|
||||
is_valid_number :: proc(str: string, spec: Specification) -> bool {
|
||||
s := str
|
||||
if s == "" {
|
||||
@@ -473,6 +475,7 @@ is_valid_number :: proc(str: string, spec: Specification) -> bool {
|
||||
return s == ""
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_valid_string_literal :: proc(str: string, spec: Specification) -> bool {
|
||||
s := str
|
||||
if len(s) < 2 {
|
||||
|
||||
@@ -112,7 +112,9 @@ destroy_value :: proc(value: Value, allocator := context.allocator, loc := #call
|
||||
}
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
clone_value :: proc(value: Value, allocator := context.allocator) -> Value {
|
||||
value := value
|
||||
context.allocator = allocator
|
||||
|
||||
#partial switch &v in value {
|
||||
|
||||
@@ -635,7 +635,7 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
|
||||
defer p.allocator = allocator
|
||||
p.allocator = mem.nil_allocator()
|
||||
|
||||
parse_value(p) or_return
|
||||
_ = parse_value(p) or_return
|
||||
if parse_comma(p) {
|
||||
break struct_loop
|
||||
}
|
||||
|
||||
89
core/encoding/json/unparse.odin
Normal file
89
core/encoding/json/unparse.odin
Normal file
@@ -0,0 +1,89 @@
|
||||
package encoding_json
|
||||
|
||||
import "base:runtime"
|
||||
import "core:strings"
|
||||
import "core:io"
|
||||
import "core:slice"
|
||||
|
||||
Unparse_Error :: union #shared_nil {
|
||||
io.Error,
|
||||
runtime.Allocator_Error,
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
unparse :: proc(v: Value, opt: Marshal_Options = {}, allocator := context.allocator, loc := #caller_location) -> (data: string, err: Unparse_Error) {
|
||||
b := strings.builder_make(allocator, loc)
|
||||
defer if err != nil {
|
||||
strings.builder_destroy(&b)
|
||||
}
|
||||
|
||||
// temp guard in case we are sorting map keys, which will use temp allocations
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = allocator == context.temp_allocator)
|
||||
|
||||
opt := opt
|
||||
unparse_to_builder(&b, v, &opt) or_return
|
||||
data = string(b.buf[:])
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
unparse_to_builder :: proc(b: ^strings.Builder, v: Value, opt: ^Marshal_Options) -> Unparse_Error {
|
||||
return unparse_to_writer(strings.to_writer(b), v, opt)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
unparse_to_writer :: proc(w: io.Writer, value: Value, opt: ^Marshal_Options) -> Unparse_Error {
|
||||
switch v in value {
|
||||
case nil, Null:
|
||||
io.write_string(w, "null") or_return
|
||||
case Integer:
|
||||
base := 16 if opt.write_uint_as_hex && (opt.spec == .JSON5 || opt.spec == .MJSON) else 10
|
||||
io.write_i64(w, v, base) or_return
|
||||
case Float:
|
||||
io.write_f64(w, v) or_return
|
||||
case Boolean:
|
||||
io.write_string(w, "true" if v else "false") or_return
|
||||
case String:
|
||||
io.write_quoted_string(w, v, '"', nil, true) or_return
|
||||
case Array:
|
||||
opt_write_start(w, opt, '[') or_return
|
||||
for e, i in v {
|
||||
opt_write_iteration(w, opt, i == 0) or_return
|
||||
unparse_to_writer (w, e, opt) or_return
|
||||
}
|
||||
opt_write_end(w, opt, ']') or_return
|
||||
case Object:
|
||||
if !opt.sort_maps_by_key {
|
||||
opt_write_start(w, opt, '{') or_return
|
||||
for first_iteration := true; key, val in v {
|
||||
opt_write_iteration(w, opt, first_iteration) or_return
|
||||
opt_write_key (w, opt, key) or_return
|
||||
unparse_to_writer (w, val, opt) or_return
|
||||
first_iteration = false
|
||||
}
|
||||
opt_write_end(w, opt, '}') or_return
|
||||
} else {
|
||||
Map_Entry :: struct {
|
||||
key: string,
|
||||
value: Value,
|
||||
}
|
||||
|
||||
entries := make([dynamic]Map_Entry, 0, len(v), context.temp_allocator) or_return
|
||||
for key, val in v {
|
||||
_, _ = append(&entries, Map_Entry{key, val})
|
||||
}
|
||||
|
||||
slice.sort_by(entries[:], proc(i, j: Map_Entry) -> bool { return i.key < j.key })
|
||||
|
||||
opt_write_start(w, opt, '{') or_return
|
||||
for e, i in entries {
|
||||
opt_write_iteration(w, opt, i == 0) or_return
|
||||
opt_write_key (w, opt, e.key) or_return
|
||||
unparse_to_writer (w, e.value, opt) or_return
|
||||
}
|
||||
opt_write_end(w, opt, '}') or_return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package encoding_json
|
||||
import "core:mem"
|
||||
|
||||
// NOTE(bill): is_valid will not check for duplicate keys
|
||||
@(require_results)
|
||||
is_valid :: proc(data: []byte, spec := DEFAULT_SPECIFICATION, parse_integers := false) -> bool {
|
||||
p := make_parser(data, spec, parse_integers, mem.nil_allocator())
|
||||
|
||||
@@ -21,6 +22,7 @@ is_valid :: proc(data: []byte, spec := DEFAULT_SPECIFICATION, parse_integers :=
|
||||
return validate_object(&p)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
validate_object_key :: proc(p: ^Parser) -> bool {
|
||||
if p.spec != .JSON {
|
||||
if allow_token(p, .Ident) {
|
||||
@@ -31,6 +33,7 @@ validate_object_key :: proc(p: ^Parser) -> bool {
|
||||
return err == .None
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
validate_object_body :: proc(p: ^Parser, end_token: Token_Kind) -> bool {
|
||||
for p.curr_token.kind != end_token {
|
||||
if !validate_object_key(p) {
|
||||
@@ -48,6 +51,7 @@ validate_object_body :: proc(p: ^Parser, end_token: Token_Kind) -> bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
validate_object :: proc(p: ^Parser) -> bool {
|
||||
if err := expect_token(p, .Open_Brace); err != .None {
|
||||
return false
|
||||
@@ -61,6 +65,7 @@ validate_object :: proc(p: ^Parser) -> bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
validate_array :: proc(p: ^Parser) -> bool {
|
||||
if err := expect_token(p, .Open_Bracket); err != .None {
|
||||
return false
|
||||
@@ -83,6 +88,7 @@ validate_array :: proc(p: ^Parser) -> bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
validate_value :: proc(p: ^Parser) -> bool {
|
||||
token := p.curr_token
|
||||
|
||||
|
||||
@@ -11,13 +11,21 @@ Command-Line Syntax:
|
||||
Arguments are treated differently depending on how they're formatted.
|
||||
The format is similar to the Odin binary's way of handling compiler flags.
|
||||
|
||||
type handling
|
||||
------------ ------------------------
|
||||
<positional> depends on struct layout
|
||||
-<flag> set a bool true
|
||||
-<flag:option> set flag to option
|
||||
-<flag=option> set flag to option, alternative syntax
|
||||
-<map>:<key>=<value> set map[key] to value
|
||||
type handling
|
||||
------------ ------------------------
|
||||
<positional> depends on struct layout
|
||||
-<flag> set a bool true
|
||||
-<flag:option> set flag to option
|
||||
-<flag=option> set flag to option, alternative syntax
|
||||
-<flag:option1,opt...> set bit_set flag to one or more options
|
||||
-<flag=option1,opt...> set bit_set flag to one or more options, alternative syntax
|
||||
-<map>:<key>=<value> set map[key] to value
|
||||
|
||||
Underscores (`_`) in a flag will be replaced with dashes (`-`).
|
||||
|
||||
Bit sets may be set with a strictly comma-separated list of options.
|
||||
|
||||
Bit sets may also be set with a binary string of 0s and 1s, and will be parsed from left to right, from least significant bit to most significant bit. Underscores are allowed and will be ignored. However, starting with an underscore is disallowed.
|
||||
|
||||
|
||||
Unhandled Arguments:
|
||||
|
||||
@@ -20,6 +20,14 @@ Optimization_Level :: enum {
|
||||
Ludicrous_Speed,
|
||||
}
|
||||
|
||||
Vet_Flag :: enum {
|
||||
Unused,
|
||||
Unused_Variables,
|
||||
Unused_Imports,
|
||||
Shadowing,
|
||||
Using_Stmt,
|
||||
}
|
||||
|
||||
// It's simple but powerful.
|
||||
my_custom_type_setter :: proc(
|
||||
data: rawptr,
|
||||
@@ -83,6 +91,8 @@ main :: proc() {
|
||||
schedule: datetime.DateTime `usage:"Launch tasks at this time."`,
|
||||
|
||||
opt: Optimization_Level `usage:"Optimization level."`,
|
||||
vet_flags: bit_set[Vet_Flag] `usage:"Vet flags. Example usage:
|
||||
-vet-flags:Unused,Shadowing"`,
|
||||
todo: [dynamic]string `usage:"Todo items."`,
|
||||
|
||||
accuracy: Fixed_Point1_1 `args:"required" usage:"Lenience in FLOP calculations."`,
|
||||
|
||||
@@ -110,7 +110,7 @@ parse_and_set_pointer_by_base_type :: proc(ptr: rawptr, str: string, type_info:
|
||||
case f32be: (^f32be)(ptr)^ = cast(f32be) value
|
||||
case f64be: (^f64be)(ptr)^ = cast(f64be) value
|
||||
}
|
||||
|
||||
|
||||
case runtime.Type_Info_Complex:
|
||||
value := strconv.parse_complex128(str) or_return
|
||||
switch type_info.id {
|
||||
@@ -118,7 +118,7 @@ parse_and_set_pointer_by_base_type :: proc(ptr: rawptr, str: string, type_info:
|
||||
case complex64: (^complex64) (ptr)^ = (complex64)(value)
|
||||
case complex128: (^complex128)(ptr)^ = value
|
||||
}
|
||||
|
||||
|
||||
case runtime.Type_Info_Quaternion:
|
||||
value := strconv.parse_quaternion256(str) or_return
|
||||
switch type_info.id {
|
||||
@@ -152,15 +152,16 @@ parse_and_set_pointer_by_base_type :: proc(ptr: rawptr, str: string, type_info:
|
||||
}
|
||||
|
||||
case runtime.Type_Info_Bit_Set:
|
||||
// Parse a string of 1's and 0's, from left to right,
|
||||
// Parse a string of 1s and 0s, from left to right,
|
||||
// least significant bit to most significant bit.
|
||||
value: u128
|
||||
|
||||
// NOTE: `upper` is inclusive, i.e: `0..=31`
|
||||
max_bit_index := u128(1 + specific_type_info.upper - specific_type_info.lower)
|
||||
underscores := u128(strings.count(str, "_"))
|
||||
bit_index_limit := u128(1 + specific_type_info.upper - specific_type_info.lower) + underscores
|
||||
bit_index := u128(0)
|
||||
#no_bounds_check for string_index in 0..<uint(len(str)) {
|
||||
if bit_index == max_bit_index {
|
||||
if bit_index == bit_index_limit {
|
||||
// The string's too long for this bit_set.
|
||||
return false
|
||||
}
|
||||
@@ -421,7 +422,7 @@ parse_and_set_pointer_by_type :: proc(ptr: rawptr, str: string, type_info: ^runt
|
||||
}
|
||||
} else {
|
||||
parse_and_set_pointer_by_named_type(ptr, str, type_info.id, arg_tag, &error)
|
||||
|
||||
|
||||
if error != nil {
|
||||
// So far, it's none of the types that we recognize.
|
||||
// Check to see if we can set it by base type, if allowed.
|
||||
@@ -471,6 +472,70 @@ parse_and_set_pointer_by_type :: proc(ptr: rawptr, str: string, type_info: ^runt
|
||||
}
|
||||
}
|
||||
|
||||
case runtime.Type_Info_Bit_Set:
|
||||
if str[0] == '0' || str[0] == '1' {
|
||||
if !parse_and_set_pointer_by_base_type(ptr, str, type_info) {
|
||||
return Parse_Error {
|
||||
// The caller will add more details.
|
||||
.Bad_Value,
|
||||
"",
|
||||
}
|
||||
} else {
|
||||
error = nil
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
value: u128
|
||||
|
||||
et := runtime.type_info_base(specific_type_info.elem)
|
||||
if enum_type_info, is_enum := et.variant.(runtime.Type_Info_Enum); is_enum {
|
||||
names, _ := strings.split(str, ",", context.temp_allocator)
|
||||
valid_names := enum_type_info.names
|
||||
underlying_values := enum_type_info.values
|
||||
|
||||
#no_bounds_check outer_loop: for name in names {
|
||||
found: bool
|
||||
#no_bounds_check for valid_name, index in valid_names {
|
||||
if name == valid_name {
|
||||
shift := u128(underlying_values[index]) - u128(specific_type_info.lower)
|
||||
value |= u128(1 << shift)
|
||||
found = true
|
||||
continue outer_loop
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return Parse_Error {
|
||||
.Bad_Value,
|
||||
fmt.tprintf(
|
||||
"Invalid value name: `%s`. Valid names are: %s",
|
||||
name,
|
||||
valid_names,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Parse_Error {
|
||||
// The caller will add more details.
|
||||
.Bad_Value,
|
||||
"",
|
||||
}
|
||||
}
|
||||
|
||||
if specific_type_info.underlying != nil {
|
||||
set_unbounded_integer_by_type(ptr, value, specific_type_info.underlying.id)
|
||||
} else {
|
||||
switch 8*type_info.size {
|
||||
case 8: (^u8) (ptr)^ = cast(u8) value
|
||||
case 16: (^u16) (ptr)^ = cast(u16) value
|
||||
case 32: (^u32) (ptr)^ = cast(u32) value
|
||||
case 64: (^u64) (ptr)^ = cast(u64) value
|
||||
case 128: (^u128)(ptr)^ = value
|
||||
}
|
||||
}
|
||||
error = nil
|
||||
|
||||
case:
|
||||
if type_info.id == ^os.File {
|
||||
parse_and_set_pointer_by_named_type(ptr, str, type_info.id, arg_tag, &error)
|
||||
|
||||
@@ -1456,9 +1456,11 @@ fmt_float :: proc(fi: ^Info, v: f64, bit_size: int, verb: rune) {
|
||||
prev_fi := fi^
|
||||
defer fi^ = prev_fi
|
||||
fi.hash = false
|
||||
fi.width = bit_size
|
||||
fi.zero = true
|
||||
fi.plus = false
|
||||
// force the width to always be bit_size/4 to accurately represent the number
|
||||
fi.width = bit_size/4
|
||||
fi.width_set = true
|
||||
|
||||
u: u64
|
||||
switch bit_size {
|
||||
|
||||
68
core/log/doc.odin
Normal file
68
core/log/doc.odin
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
Implementation of logging facilities.
|
||||
|
||||
Odin has builtin support for logging using procedure `context`. After a logger is created it can then be assigned to
|
||||
`context.logger` and used implicitly in future log calls.
|
||||
|
||||
While it is ok for simple apps to use the `core:fmt` package, libraries and complex apps should prefer the `core:log`
|
||||
package. By using the implicit logger library and application authors allow the caller to decide how to process log
|
||||
messages.
|
||||
|
||||
When starting out you can easily just init the logger with a single line.
|
||||
Example:
|
||||
|
||||
package main
|
||||
|
||||
import "core:log"
|
||||
|
||||
main :: proc() {
|
||||
context.logger = log.create_console_logger()
|
||||
log.info("Hello World!")
|
||||
}
|
||||
|
||||
However when the application gets more involved you might want to try a more complex setup.
|
||||
Example:
|
||||
|
||||
package main
|
||||
|
||||
import "core:log"
|
||||
import "core:os"
|
||||
|
||||
main :: proc() {
|
||||
handle, err := os.open("logs.txt", os.O_RDWR | os.O_APPEND | os.O_CREATE, 0o666)
|
||||
assert(err == nil, "Cannot open log file")
|
||||
|
||||
file_logger := log.create_file_logger(handle)
|
||||
// This closes the file handle
|
||||
defer log.destroy_file_logger(file_logger)
|
||||
|
||||
console_logger := log.create_console_logger()
|
||||
defer log.destroy_console_logger(console_logger)
|
||||
|
||||
multi_logger := log.create_multi_logger(console_logger, file_logger)
|
||||
defer log.destroy_multi_logger(multi_logger)
|
||||
|
||||
context.logger = multi_logger
|
||||
|
||||
log.info("Application started!")
|
||||
}
|
||||
|
||||
It is also possible to create an allocator that logs all allocations.
|
||||
Example:
|
||||
|
||||
package main
|
||||
|
||||
import "core:log"
|
||||
|
||||
main :: proc() {
|
||||
context.logger = log.create_console_logger()
|
||||
|
||||
alloc: log.Log_Allocator
|
||||
log.log_allocator_init(&alloc, .Debug)
|
||||
context.allocator = log.log_allocator(&alloc)
|
||||
|
||||
a := new(i32)
|
||||
free(a)
|
||||
}
|
||||
*/
|
||||
package log
|
||||
@@ -11,6 +11,7 @@ import "core:terminal"
|
||||
import "core:terminal/ansi"
|
||||
import "core:time"
|
||||
|
||||
// Strings to output when `.Level` is included in the logger options.
|
||||
Level_Headers := [?]string{
|
||||
0..<10 = "[DEBUG] --- ",
|
||||
10..<20 = "[INFO ] --- ",
|
||||
@@ -19,22 +20,49 @@ Level_Headers := [?]string{
|
||||
40..<50 = "[FATAL] --- ",
|
||||
}
|
||||
|
||||
/*
|
||||
The default option set for a console logger.
|
||||
|
||||
It is similar to the file logger default option set, but the output includes colors.
|
||||
|
||||
When you use this set of options you can expect the following output:
|
||||
|
||||
[LEVEL] --- [YYYY-MM-DD HH:MM:SS] [file.odin:L:proc()] Message
|
||||
|
||||
For example:
|
||||
|
||||
[INFO ] --- [2025-01-02 12:34:56] [main.odin:8:main()] Hello World!
|
||||
*/
|
||||
Default_Console_Logger_Opts :: Options{
|
||||
.Level,
|
||||
.Terminal_Color,
|
||||
.Short_File_Path,
|
||||
.Line,
|
||||
.Procedure,
|
||||
} | Full_Timestamp_Opts
|
||||
} + Full_Timestamp_Opts
|
||||
|
||||
/*
|
||||
The default option set for a file logger.
|
||||
|
||||
It is similar to the console logger default option set, but the output is not colored.
|
||||
|
||||
When you use this set of options you can expect the following output:
|
||||
|
||||
[LEVEL] --- [YYYY-MM-DD HH:MM:SS] [file.odin:L:proc()] Message
|
||||
|
||||
For example:
|
||||
|
||||
[INFO ] --- [2025-01-02 12:34:56] [main.odin:8:main()] Hello World!
|
||||
*/
|
||||
Default_File_Logger_Opts :: Options{
|
||||
.Level,
|
||||
.Short_File_Path,
|
||||
.Line,
|
||||
.Procedure,
|
||||
} | Full_Timestamp_Opts
|
||||
} + Full_Timestamp_Opts
|
||||
|
||||
|
||||
//Data backing a file or console logger.
|
||||
File_Console_Logger_Data :: struct {
|
||||
file_handle: ^os.File,
|
||||
ident: string,
|
||||
@@ -67,6 +95,21 @@ init_standard_stream_status :: proc "contextless" () {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Create a logger that outputs to a file.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
When no longer needed can be destroyed with `destroy_file_logger`.
|
||||
|
||||
Inputs:
|
||||
- `h`: A handle to the output file
|
||||
- `lowest`: Log level to use (default is `.Debug`)
|
||||
- `opt`: Specifies additional data present in the log output (default is `log.Default_File_Logger_Opts`)
|
||||
- `ident`: Identifier to include in the output (default is `""`)
|
||||
- `allocator`: Allocator to use for data backing the logger (default is `context.allocator`)
|
||||
*/
|
||||
create_file_logger :: proc(f: ^os.File, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger {
|
||||
data := new(File_Console_Logger_Data, allocator)
|
||||
data.file_handle = f
|
||||
@@ -74,6 +117,13 @@ create_file_logger :: proc(f: ^os.File, lowest := Level.Debug, opt := Default_Fi
|
||||
return Logger{file_logger_proc, data, lowest, opt}
|
||||
}
|
||||
|
||||
/*
|
||||
Free the state allocated with `create_file_logger` and close the file handle.
|
||||
|
||||
Inputs:
|
||||
- `log`: Logger created with `create_file_logger`
|
||||
- `allocator`: Allocator passed to `create_file_logger` (default is `context.allocator`)
|
||||
*/
|
||||
destroy_file_logger :: proc(log: Logger, allocator := context.allocator) {
|
||||
data := cast(^File_Console_Logger_Data)log.data
|
||||
if data.file_handle != nil {
|
||||
@@ -82,6 +132,19 @@ destroy_file_logger :: proc(log: Logger, allocator := context.allocator) {
|
||||
free(data, allocator)
|
||||
}
|
||||
|
||||
/*
|
||||
Create a logger that outputs to the terminal.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
When no longer needed can be destroyed with `destroy_console_logger`.
|
||||
|
||||
Inputs:
|
||||
- `lowest`: Log level to use (default is `.Debug`)
|
||||
- `opt`: Specifies additional data present in the log output (default is `log.Default_Console_Logger_Opts`)
|
||||
- `ident`: Identifier to include in the output (default is `""`)
|
||||
- `allocator`: Allocator to use for data backing the logger (default is `context.allocator`)
|
||||
*/
|
||||
create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logger_Opts, ident := "", allocator := context.allocator) -> Logger {
|
||||
data := new(File_Console_Logger_Data, allocator)
|
||||
data.file_handle = nil
|
||||
@@ -89,6 +152,13 @@ create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logg
|
||||
return Logger{console_logger_proc, data, lowest, opt}
|
||||
}
|
||||
|
||||
/*
|
||||
Free the state allocated with `create_console_logger`.
|
||||
|
||||
Inputs:
|
||||
- `log`: Logger created with `create_console_logger`
|
||||
- `allocator`: Allocator passed to `create_console_logger` (default is `context.allocator`)
|
||||
*/
|
||||
destroy_console_logger :: proc(log: Logger, allocator := context.allocator) {
|
||||
free(log.data, allocator)
|
||||
}
|
||||
@@ -117,6 +187,7 @@ _file_console_logger_proc :: proc(h: ^os.File, ident: string, level: Level, text
|
||||
fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text)
|
||||
}
|
||||
|
||||
|
||||
file_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
|
||||
data := cast(^File_Console_Logger_Data)logger_data
|
||||
_file_console_logger_proc(data.file_handle, data.ident, level, text, options, location)
|
||||
@@ -136,6 +207,7 @@ console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, opt
|
||||
_file_console_logger_proc(h, data.ident, level, text, options, location)
|
||||
}
|
||||
|
||||
// Helper used to build the part of the message including the log level.
|
||||
do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) {
|
||||
|
||||
RESET :: ansi.CSI + ansi.RESET + ansi.SGR
|
||||
@@ -162,6 +234,7 @@ do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper used to build the part of the message including the data and time.
|
||||
do_time_header :: proc(opts: Options, buf: ^strings.Builder, t: time.Time) {
|
||||
when time.IS_SUPPORTED {
|
||||
if Full_Timestamp_Opts & opts != nil {
|
||||
@@ -180,6 +253,7 @@ do_time_header :: proc(opts: Options, buf: ^strings.Builder, t: time.Time) {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper used to build the part of the message including the file location.
|
||||
do_location_header :: proc(opts: Options, buf: ^strings.Builder, location := #caller_location) {
|
||||
if Location_Header_Opts & opts == nil {
|
||||
return
|
||||
|
||||
@@ -1,52 +1,102 @@
|
||||
// Implementations of the `context.Logger` interface.
|
||||
package log
|
||||
|
||||
import "base:runtime"
|
||||
import "core:fmt"
|
||||
|
||||
|
||||
// NOTE(bill, 2019-12-31): These are defined in `package runtime` as they are used in the `context`. This is to prevent an import definition cycle.
|
||||
//These are defined in package `base:runtime` as they are used in the `context`. This is to prevent an import definition cycle.
|
||||
|
||||
/*
|
||||
Logger_Level :: enum {
|
||||
Debug = 0,
|
||||
Info = 10,
|
||||
Warning = 20,
|
||||
Error = 30,
|
||||
Fatal = 40,
|
||||
}
|
||||
Logger_Level :: enum {
|
||||
Debug = 0,
|
||||
Info = 10,
|
||||
Warning = 20,
|
||||
Error = 30,
|
||||
Fatal = 40,
|
||||
}
|
||||
*/
|
||||
Level :: runtime.Logger_Level
|
||||
|
||||
/*
|
||||
Option :: enum {
|
||||
Level,
|
||||
Date,
|
||||
Time,
|
||||
Short_File_Path,
|
||||
Long_File_Path,
|
||||
Line,
|
||||
Procedure,
|
||||
Terminal_Color
|
||||
}
|
||||
Specifies additional data present in the log output.
|
||||
|
||||
Defined in `package runtime` as it is used in the `context`. This is to prevent an import definition cycle.
|
||||
|
||||
Option :: enum {
|
||||
// The log level, e.g. "[DEBUG] ---"
|
||||
Level,
|
||||
// The date, e.g. [2025-01-02]
|
||||
Date,
|
||||
// The time, e.g. [12:34:56]
|
||||
Time,
|
||||
// Just the filename, e.g. [main.odin]
|
||||
Short_File_Path,
|
||||
// Full file path, e.g. [/tmp/project/main.odin]
|
||||
Long_File_Path,
|
||||
// File line of the log statement, e.g. [8]
|
||||
Line,
|
||||
// Calling procedure, e.g. [main()]
|
||||
Procedure,
|
||||
// Enables colored output
|
||||
Terminal_Color
|
||||
}
|
||||
*/
|
||||
Option :: runtime.Logger_Option
|
||||
|
||||
/*
|
||||
Options :: bit_set[Option];
|
||||
Specifies additional data present in the log output.
|
||||
|
||||
Defined in `package runtime` as it is used in the `context`. This is to prevent an import definition cycle.
|
||||
|
||||
Options :: bit_set[Option];
|
||||
*/
|
||||
Options :: runtime.Logger_Options
|
||||
|
||||
/*
|
||||
A preset option set for a logger.
|
||||
|
||||
When you use this set of options you can expect the following output:
|
||||
|
||||
[YYYY-MM-DD HH:MM:SS] Message
|
||||
|
||||
For example:
|
||||
|
||||
[2025-01-02 12:34:56] Hello World!
|
||||
*/
|
||||
Full_Timestamp_Opts :: Options{
|
||||
.Date,
|
||||
.Time,
|
||||
}
|
||||
|
||||
/*
|
||||
A preset option set for a logger.
|
||||
|
||||
When you use this set of options you can expect the following output:
|
||||
|
||||
[file.odin:L:proc()] Message
|
||||
|
||||
For example:
|
||||
|
||||
[main.odin:8:main()] Hello World!
|
||||
*/
|
||||
Location_Header_Opts :: Options{
|
||||
.Short_File_Path,
|
||||
.Long_File_Path,
|
||||
.Line,
|
||||
.Procedure,
|
||||
}
|
||||
|
||||
/*
|
||||
A preset option set for a logger.
|
||||
|
||||
When you use this set of options you can expect the following output:
|
||||
|
||||
[file.odin] Message
|
||||
|
||||
For example:
|
||||
|
||||
[main.odin] Hello World!
|
||||
*/
|
||||
Location_File_Opts :: Options{
|
||||
.Short_File_Path,
|
||||
.Long_File_Path,
|
||||
@@ -54,67 +104,206 @@ Location_File_Opts :: Options{
|
||||
|
||||
|
||||
/*
|
||||
Logger_Proc :: #type proc(data: rawptr, level: Level, text: string, options: Options, location := #caller_location);
|
||||
Implementation of the logger.
|
||||
|
||||
Defined in `package runtime` as it is used in the `context`. This is to prevent an import definition cycle.
|
||||
|
||||
Logger_Proc :: #type proc(data: rawptr, level: Level, text: string, options: Options, location := #caller_location);
|
||||
|
||||
*/
|
||||
Logger_Proc :: runtime.Logger_Proc
|
||||
|
||||
/*
|
||||
Logger :: struct {
|
||||
procedure: Logger_Proc,
|
||||
data: rawptr,
|
||||
lowest_level: Level,
|
||||
options: Logger_Options,
|
||||
}
|
||||
Data backing the logger.
|
||||
|
||||
Defined in `package runtime` as it is used in the `context`. This is to prevent an import definition cycle.
|
||||
|
||||
Logger :: struct {
|
||||
// Implementation
|
||||
procedure: Logger_Proc,
|
||||
// Configuration data passed to the implementation
|
||||
data: rawptr,
|
||||
// Minimum level for messages passed to the implementation
|
||||
lowest_level: Level,
|
||||
// Additional data present in the log output
|
||||
options: Logger_Options,
|
||||
}
|
||||
*/
|
||||
Logger :: runtime.Logger
|
||||
|
||||
/*
|
||||
Do nothing.
|
||||
|
||||
Defined in `package runtime` as it is used in the `context`. This is to prevent an import definition cycle.
|
||||
*/
|
||||
nil_logger_proc :: runtime.default_logger_proc
|
||||
|
||||
/*
|
||||
Create a logger that does nothing.
|
||||
|
||||
Returns:
|
||||
- A logger that does nothing
|
||||
*/
|
||||
nil_logger :: proc() -> Logger {
|
||||
return Logger{nil_logger_proc, nil, Level.Debug, nil}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Log a formatted message at the `Debug` level.
|
||||
|
||||
Inputs:
|
||||
- `fmt_str`: A format string, e.g. `"a: %v, b: %v"
|
||||
- `args`: Arguments for the format string
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
debugf :: proc(fmt_str: string, args: ..any, location := #caller_location) {
|
||||
logf(.Debug, fmt_str, ..args, location=location)
|
||||
}
|
||||
|
||||
/*
|
||||
Log a formatted message at the `Info` level.
|
||||
|
||||
Inputs:
|
||||
- `fmt_str`: A format string, e.g. `"a: %v, b: %v"
|
||||
- `args`: Arguments for the format string
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
infof :: proc(fmt_str: string, args: ..any, location := #caller_location) {
|
||||
logf(.Info, fmt_str, ..args, location=location)
|
||||
}
|
||||
|
||||
/*
|
||||
Log a formatted message at the `Warn` level.
|
||||
|
||||
Inputs:
|
||||
- `fmt_str`: A format string, e.g. `"a: %v, b: %v"
|
||||
- `args`: Arguments for the format string
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
warnf :: proc(fmt_str: string, args: ..any, location := #caller_location) {
|
||||
logf(.Warning, fmt_str, ..args, location=location)
|
||||
}
|
||||
|
||||
/*
|
||||
Log a formatted message at the `Error` level.
|
||||
|
||||
Inputs:
|
||||
- `fmt_str`: A format string, e.g. `"a: %v, b: %v"
|
||||
- `args`: Arguments for the format string
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
errorf :: proc(fmt_str: string, args: ..any, location := #caller_location) {
|
||||
logf(.Error, fmt_str, ..args, location=location)
|
||||
}
|
||||
|
||||
/*
|
||||
Log a formatted message at the `Fatal` level.
|
||||
|
||||
Inputs:
|
||||
- `fmt_str`: A format string, e.g. `"a: %v, b: %v"
|
||||
- `args`: Arguments for the format string
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
fatalf :: proc(fmt_str: string, args: ..any, location := #caller_location) {
|
||||
logf(.Fatal, fmt_str, ..args, location=location)
|
||||
}
|
||||
|
||||
/*
|
||||
Log a message at the `Debug` level.
|
||||
|
||||
Inputs:
|
||||
- `args`: values to be concatenated into the output
|
||||
- `sep`: separator to use when concatenating (default is `" "`)
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
debug :: proc(args: ..any, sep := " ", location := #caller_location) {
|
||||
log(.Debug, ..args, sep=sep, location=location)
|
||||
}
|
||||
|
||||
/*
|
||||
Log a message at the `Info` level.
|
||||
|
||||
Inputs:
|
||||
- `args`: values to be concatenated into the output
|
||||
- `sep`: separator to use when concatenating (default is `" "`)
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
info :: proc(args: ..any, sep := " ", location := #caller_location) {
|
||||
log(.Info, ..args, sep=sep, location=location)
|
||||
}
|
||||
|
||||
/*
|
||||
Log a message at the `Warn` level.
|
||||
|
||||
Inputs:
|
||||
- `args`: values to be concatenated into the output
|
||||
- `sep`: separator to use when concatenating (default is `" "`)
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
warn :: proc(args: ..any, sep := " ", location := #caller_location) {
|
||||
log(.Warning, ..args, sep=sep, location=location)
|
||||
}
|
||||
|
||||
/*
|
||||
Log a message at the `Error` level.
|
||||
|
||||
Inputs:
|
||||
- `args`: values to be concatenated into the output
|
||||
- `sep`: separator to use when concatenating (default is `" "`)
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
error :: proc(args: ..any, sep := " ", location := #caller_location) {
|
||||
log(.Error, ..args, sep=sep, location=location)
|
||||
}
|
||||
|
||||
/*
|
||||
Log a message at the `Fatal` level.
|
||||
|
||||
Inputs:
|
||||
- `args`: values to be concatenated into the output
|
||||
- `sep`: separator to use when concatenating (default is `" "`)
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
fatal :: proc(args: ..any, sep := " ", location := #caller_location) {
|
||||
log(.Fatal, ..args, sep=sep, location=location)
|
||||
}
|
||||
|
||||
/*
|
||||
Log a message at the `Fatal` level and abort the program.
|
||||
|
||||
Inputs:
|
||||
- `args`: values to be concatenated into the output
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
panic :: proc(args: ..any, location := #caller_location) -> ! {
|
||||
log(.Fatal, ..args, location=location)
|
||||
runtime.panic("log.panic", location)
|
||||
}
|
||||
|
||||
/*
|
||||
Log a formatted message at the `Fatal` level and abort the program.
|
||||
|
||||
Inputs:
|
||||
- `fmt_str`: A format string, e.g. `"a: %v, b: %v"
|
||||
- `args`: Arguments for the format string
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
panicf :: proc(fmt_str: string, args: ..any, location := #caller_location) -> ! {
|
||||
logf(.Fatal, fmt_str, ..args, location=location)
|
||||
runtime.panic("log.panicf", location)
|
||||
}
|
||||
|
||||
/*
|
||||
When condition is `false` log a message at the `Fatal` level and abort the program.
|
||||
|
||||
Can be disabled using `ODIN_DISABLE_ASSERT`.
|
||||
|
||||
Inputs:
|
||||
- `condition`: A boolean to check
|
||||
- `message`: Message to log when condition is false (a default is provided)
|
||||
- `loc`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
@(disabled=ODIN_DISABLE_ASSERT)
|
||||
assert :: proc(condition: bool, message := #caller_expression(condition), loc := #caller_location) {
|
||||
if !condition {
|
||||
@@ -131,6 +320,17 @@ assert :: proc(condition: bool, message := #caller_expression(condition), loc :=
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
When condition is `false` log a formatted message at the `Fatal` level and abort the program.
|
||||
|
||||
Can be disabled using `ODIN_DISABLE_ASSERT`.
|
||||
|
||||
Inputs:
|
||||
- `condition`: A boolean to check
|
||||
- `fmt_str`: A format string to use when condition is false, e.g. `"a: %v, b: %v"
|
||||
- `args`: Arguments for the format string
|
||||
- `loc`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
@(disabled=ODIN_DISABLE_ASSERT)
|
||||
assertf :: proc(condition: bool, fmt_str: string, args: ..any, loc := #caller_location) {
|
||||
if !condition {
|
||||
@@ -152,6 +352,16 @@ assertf :: proc(condition: bool, fmt_str: string, args: ..any, loc := #caller_lo
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
When condition is `false` log a message at the `Fatal` level and abort the program.
|
||||
|
||||
Unlike `assert` this procedure cannot be disabled with `ODIN_DISABLE_ASSERT` and will always execute.
|
||||
|
||||
Inputs:
|
||||
- `condition`: A boolean to check
|
||||
- `message`: Message to log when condition is false (a default is provided)
|
||||
- `loc`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
ensure :: proc(condition: bool, message := #caller_expression(condition), loc := #caller_location) {
|
||||
if !condition {
|
||||
@(cold)
|
||||
@@ -167,6 +377,17 @@ ensure :: proc(condition: bool, message := #caller_expression(condition), loc :=
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
When condition is `false` log a formatted message at the `Fatal` level and abort the program.
|
||||
|
||||
Unlike `assertf` this procedure cannot be disabled with `ODIN_DISABLE_ASSERT` and will always execute.
|
||||
|
||||
Inputs:
|
||||
- `condition`: A boolean to check
|
||||
- `fmt_str`: A format string to use when condition is false, e.g. `"a: %v, b: %v"
|
||||
- `args`: Arguments for the format string
|
||||
- `loc`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
ensuref :: proc(condition: bool, fmt_str: string, args: ..any, loc := #caller_location) {
|
||||
if !condition {
|
||||
@(cold)
|
||||
@@ -184,7 +405,15 @@ ensuref :: proc(condition: bool, fmt_str: string, args: ..any, loc := #caller_lo
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Log a message at the desired level.
|
||||
|
||||
Inputs:
|
||||
- `level`: The level of the message
|
||||
- `args`: values to be concatenated into the output
|
||||
- `sep`: separator to use when concatenating (default is `" "`)
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
log :: proc(level: Level, args: ..any, sep := " ", location := #caller_location) {
|
||||
logger := context.logger
|
||||
if logger.procedure == nil || logger.procedure == nil_logger_proc {
|
||||
@@ -198,6 +427,15 @@ log :: proc(level: Level, args: ..any, sep := " ", location := #caller_location)
|
||||
logger.procedure(logger.data, level, str, logger.options, location)
|
||||
}
|
||||
|
||||
/*
|
||||
Log a formatted message at the desired level.
|
||||
|
||||
Inputs:
|
||||
- `level`: The level of the message
|
||||
- `fmt_str`: A format string, e.g. `"a: %v, b: %v"
|
||||
- `args`: Arguments for the format string
|
||||
- `location`: Location of the caller (default is #caller_location)
|
||||
*/
|
||||
logf :: proc(level: Level, fmt_str: string, args: ..any, location := #caller_location) {
|
||||
logger := context.logger
|
||||
if logger.procedure == nil || logger.procedure == nil_logger_proc {
|
||||
|
||||
@@ -6,6 +6,7 @@ import "base:runtime"
|
||||
import "core:sync"
|
||||
|
||||
// Format to use when logging allocations.
|
||||
Log_Allocator_Format :: enum {
|
||||
Bytes, // Actual number of bytes.
|
||||
Human, // Bytes in human units like bytes, kibibytes, etc. as appropriate.
|
||||
}
|
||||
@@ -15,13 +16,23 @@ Log_Allocator_Format :: enum {
|
||||
// The format can be changed by setting the `size_fmt: Log_Allocator_Format` field to either `Bytes` or `Human`.
|
||||
Log_Allocator :: struct {
|
||||
allocator: runtime.Allocator, // Wrapped allocator
|
||||
level: Level,
|
||||
prefix: string,
|
||||
lock: sync.Mutex,
|
||||
level: Level, // Log Level used for allocations
|
||||
prefix: string, // Prefix to use in log messages
|
||||
lock: sync.Mutex,
|
||||
size_fmt: Log_Allocator_Format, // Format to use when logging allocations
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Initialize the backing data for the allocator that logs all allocations.
|
||||
|
||||
Inputs:
|
||||
- `la`: Pointer to the data structure to initialize
|
||||
- `level`: Log level to use for allocations
|
||||
- `size_fmt`: Format to use when logging allocations (default is `.Bytes`)
|
||||
- `allocator`: Wrapped allocator (default is `context.allocator`)
|
||||
- `prefix`: Prefix to use in log messages (default is `""`)
|
||||
*/
|
||||
log_allocator_init :: proc(la: ^Log_Allocator, level: Level, size_fmt := Log_Allocator_Format.Bytes,
|
||||
allocator := context.allocator, prefix := "") {
|
||||
la.allocator = allocator
|
||||
la.level = level
|
||||
@@ -31,7 +42,15 @@ log_allocator_init :: proc(la: ^Log_Allocator, level: Level, size_fmt := Log_All
|
||||
}
|
||||
|
||||
/*
|
||||
Create an allocator that logs all allocations.
|
||||
|
||||
Inputs:
|
||||
- `la`: Pointer to the data structure backing the allocator
|
||||
|
||||
Returns:
|
||||
- An allocator that logs all allocations
|
||||
*/
|
||||
log_allocator :: proc(la: ^Log_Allocator) -> runtime.Allocator {
|
||||
return runtime.Allocator{
|
||||
procedure = log_allocator_proc,
|
||||
data = la,
|
||||
@@ -39,6 +58,7 @@ log_allocator :: proc(la: ^Log_Allocator) -> runtime.Allocator {
|
||||
}
|
||||
|
||||
// Backing procedure for allocator that logs all allocations.
|
||||
log_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode,
|
||||
size, alignment: int,
|
||||
old_memory: rawptr, old_size: int, location := #caller_location) -> ([]byte, runtime.Allocator_Error) {
|
||||
la := (^Log_Allocator)(allocator_data)
|
||||
|
||||
@@ -1,10 +1,27 @@
|
||||
package log
|
||||
|
||||
|
||||
// A container backing for multiple loggers.
|
||||
Multi_Logger_Data :: struct {
|
||||
loggers: []Logger,
|
||||
}
|
||||
|
||||
/*
|
||||
Create a logger that logs to all backing loggers.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
When no longer needed can be destroyed with `destroy_multi_logger`.
|
||||
|
||||
Note: Logs using a multi logger take both the multi logger and the backing loggers' log levels into account.
|
||||
|
||||
Inputs:
|
||||
- `logs` - Backing loggers passed as multiple arguments
|
||||
- `allocator` - An allocator used to allocate data to store backing loggers (default is `context.allocator`)
|
||||
|
||||
Returns:
|
||||
- A multi logger
|
||||
*/
|
||||
create_multi_logger :: proc(logs: ..Logger, allocator := context.allocator) -> Logger {
|
||||
data := new(Multi_Logger_Data, allocator)
|
||||
data.loggers = make([]Logger, len(logs), allocator)
|
||||
@@ -12,12 +29,20 @@ create_multi_logger :: proc(logs: ..Logger, allocator := context.allocator) -> L
|
||||
return Logger{multi_logger_proc, data, Level.Debug, nil}
|
||||
}
|
||||
|
||||
/*
|
||||
Free the state allocated with `create_multi_logger`.
|
||||
|
||||
Inputs:
|
||||
- `log`: Logger created with `create_multi_logger`
|
||||
- `allocator`: Allocator passed to `create_multi_logger` (default is `context.allocator`)
|
||||
*/
|
||||
destroy_multi_logger :: proc(log: Logger, allocator := context.allocator) {
|
||||
data := (^Multi_Logger_Data)(log.data)
|
||||
delete(data.loggers, allocator)
|
||||
free(data, allocator)
|
||||
}
|
||||
|
||||
// Backing procedure for the multi logger.
|
||||
multi_logger_proc :: proc(logger_data: rawptr, level: Level, text: string,
|
||||
options: Options, location := #caller_location) {
|
||||
data := cast(^Multi_Logger_Data)logger_data
|
||||
|
||||
@@ -244,27 +244,18 @@ quaternion_mul_quaternion :: proc "contextless" (q1, q2: $Q) -> Q where IS_QUATE
|
||||
|
||||
@(require_results)
|
||||
quaternion64_mul_vector3 :: proc "contextless" (q: $Q/quaternion64, v: $V/[3]$F/f16) -> V {
|
||||
q := transmute(runtime.Raw_Quaternion64_Vector_Scalar)q
|
||||
v := v
|
||||
|
||||
t := cross(2*q.vector, v)
|
||||
return V(v + q.scalar*t + cross(q.vector, t))
|
||||
t := cross(2*q.xyz, v)
|
||||
return V(v + q.w*t + cross(q.xyz, t))
|
||||
}
|
||||
@(require_results)
|
||||
quaternion128_mul_vector3 :: proc "contextless" (q: $Q/quaternion128, v: $V/[3]$F/f32) -> V {
|
||||
q := transmute(runtime.Raw_Quaternion128_Vector_Scalar)q
|
||||
v := v
|
||||
|
||||
t := cross(2*q.vector, v)
|
||||
return V(v + q.scalar*t + cross(q.vector, t))
|
||||
t := cross(2*q.xyz, v)
|
||||
return V(v + q.w*t + cross(q.xyz, t))
|
||||
}
|
||||
@(require_results)
|
||||
quaternion256_mul_vector3 :: proc "contextless" (q: $Q/quaternion256, v: $V/[3]$F/f64) -> V {
|
||||
q := transmute(runtime.Raw_Quaternion256_Vector_Scalar)q
|
||||
v := v
|
||||
|
||||
t := cross(2*q.vector, v)
|
||||
return V(v + q.scalar*t + cross(q.vector, t))
|
||||
t := cross(2*q.xyz, v)
|
||||
return V(v + q.w*t + cross(q.xyz, t))
|
||||
}
|
||||
quaternion_mul_vector3 :: proc{quaternion64_mul_vector3, quaternion128_mul_vector3, quaternion256_mul_vector3}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1044,8 +1044,8 @@ Example:
|
||||
|
||||
Possible Output:
|
||||
|
||||
[7201011, 3, 9123, 231131]
|
||||
[19578, 910081, 131, 7]
|
||||
[3, 2, 0, 1]
|
||||
[1, 0, 2, 3]
|
||||
|
||||
*/
|
||||
@(require_results)
|
||||
|
||||
@@ -255,11 +255,10 @@ alignment is not specified explicitly.
|
||||
DEFAULT_ALIGNMENT :: 2*align_of(rawptr)
|
||||
|
||||
/*
|
||||
Default page size.
|
||||
|
||||
This value is the default page size for the current platform.
|
||||
On platforms where we were able to query a configurable size, we use that value instead.
|
||||
See `query_page_size_init()`
|
||||
*/
|
||||
DEFAULT_PAGE_SIZE ::
|
||||
PAGE_SIZE: int =
|
||||
64 * 1024 when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 else
|
||||
16 * 1024 when ODIN_OS == .Darwin && ODIN_ARCH == .arm64 else
|
||||
4 * 1024
|
||||
|
||||
@@ -1621,20 +1621,6 @@ small_stack_allocator_proc :: proc(
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
||||
/* Preserved for compatibility */
|
||||
Dynamic_Pool :: Dynamic_Arena
|
||||
DYNAMIC_POOL_BLOCK_SIZE_DEFAULT :: DYNAMIC_ARENA_BLOCK_SIZE_DEFAULT
|
||||
DYNAMIC_POOL_OUT_OF_BAND_SIZE_DEFAULT :: DYNAMIC_ARENA_OUT_OF_BAND_SIZE_DEFAULT
|
||||
dynamic_pool_allocator_proc :: dynamic_arena_allocator_proc
|
||||
dynamic_pool_free_all :: dynamic_arena_free_all
|
||||
dynamic_pool_reset :: dynamic_arena_reset
|
||||
dynamic_pool_alloc_bytes :: dynamic_arena_alloc_bytes
|
||||
dynamic_pool_alloc :: dynamic_arena_alloc
|
||||
dynamic_pool_init :: dynamic_arena_init
|
||||
dynamic_pool_allocator :: dynamic_arena_allocator
|
||||
dynamic_pool_destroy :: dynamic_arena_destroy
|
||||
|
||||
/*
|
||||
Default block size for dynamic arena.
|
||||
*/
|
||||
@@ -1651,7 +1637,7 @@ Dynamic arena allocator data.
|
||||
Dynamic_Arena :: struct {
|
||||
block_size: int,
|
||||
out_band_size: int,
|
||||
alignment: int,
|
||||
minimum_alignment: int,
|
||||
unused_blocks: [dynamic]rawptr,
|
||||
used_blocks: [dynamic]rawptr,
|
||||
out_band_allocations: [dynamic]rawptr,
|
||||
@@ -1668,23 +1654,23 @@ This procedure initializes a dynamic arena. The specified `block_allocator`
|
||||
will be used to allocate arena blocks, and `array_allocator` to allocate
|
||||
arrays of blocks and out-band blocks. The blocks have the default size of
|
||||
`block_size` and out-band threshold will be `out_band_size`. All allocations
|
||||
will be aligned to a boundary specified by `alignment`.
|
||||
will be aligned at a minimum to a boundary specified by `minimum_alignment`.
|
||||
*/
|
||||
dynamic_arena_init :: proc(
|
||||
pool: ^Dynamic_Arena,
|
||||
block_allocator := context.allocator,
|
||||
array_allocator := context.allocator,
|
||||
block_size := DYNAMIC_ARENA_BLOCK_SIZE_DEFAULT,
|
||||
out_band_size := DYNAMIC_ARENA_OUT_OF_BAND_SIZE_DEFAULT,
|
||||
alignment := DEFAULT_ALIGNMENT,
|
||||
arena: ^Dynamic_Arena,
|
||||
block_allocator := context.allocator,
|
||||
array_allocator := context.allocator,
|
||||
block_size := DYNAMIC_ARENA_BLOCK_SIZE_DEFAULT,
|
||||
out_band_size := DYNAMIC_ARENA_OUT_OF_BAND_SIZE_DEFAULT,
|
||||
minimum_alignment := DEFAULT_ALIGNMENT,
|
||||
) {
|
||||
pool.block_size = block_size
|
||||
pool.out_band_size = out_band_size
|
||||
pool.alignment = alignment
|
||||
pool.block_allocator = block_allocator
|
||||
pool.out_band_allocations.allocator = array_allocator
|
||||
pool.unused_blocks.allocator = array_allocator
|
||||
pool.used_blocks.allocator = array_allocator
|
||||
arena.block_size = block_size
|
||||
arena.out_band_size = out_band_size
|
||||
arena.minimum_alignment = minimum_alignment
|
||||
arena.block_allocator = block_allocator
|
||||
arena.out_band_allocations.allocator = array_allocator
|
||||
arena.unused_blocks.allocator = array_allocator
|
||||
arena.used_blocks.allocator = array_allocator
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1728,7 +1714,7 @@ dynamic_arena_destroy :: proc(a: ^Dynamic_Arena) {
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
_dynamic_arena_cycle_new_block :: proc(a: ^Dynamic_Arena, loc := #caller_location) -> (err: Allocator_Error) {
|
||||
_dynamic_arena_cycle_new_block :: proc(a: ^Dynamic_Arena, alignment: int, loc := #caller_location) -> (err: Allocator_Error) {
|
||||
if a.block_allocator.procedure == nil {
|
||||
panic("You must call `dynamic_arena_init` on a Dynamic Arena before using it.", loc)
|
||||
}
|
||||
@@ -1744,7 +1730,7 @@ _dynamic_arena_cycle_new_block :: proc(a: ^Dynamic_Arena, loc := #caller_locatio
|
||||
a.block_allocator.data,
|
||||
Allocator_Mode.Alloc,
|
||||
a.block_size,
|
||||
a.alignment,
|
||||
max(a.minimum_alignment, alignment),
|
||||
nil,
|
||||
0,
|
||||
)
|
||||
@@ -1766,8 +1752,8 @@ zero-initialized. This procedure returns a pointer to the newly allocated memory
|
||||
region.
|
||||
*/
|
||||
@(require_results)
|
||||
dynamic_arena_alloc :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> (rawptr, Allocator_Error) {
|
||||
data, err := dynamic_arena_alloc_bytes(a, size, loc)
|
||||
dynamic_arena_alloc :: proc(a: ^Dynamic_Arena, size: int, alignment: int = DEFAULT_ALIGNMENT, loc := #caller_location) -> (rawptr, Allocator_Error) {
|
||||
data, err := dynamic_arena_alloc_bytes(a, size, alignment, loc)
|
||||
return raw_data(data), err
|
||||
}
|
||||
|
||||
@@ -1780,8 +1766,8 @@ zero-initialized. This procedure returns a slice of the newly allocated memory
|
||||
region.
|
||||
*/
|
||||
@(require_results)
|
||||
dynamic_arena_alloc_bytes :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
|
||||
bytes, err := dynamic_arena_alloc_bytes_non_zeroed(a, size, loc)
|
||||
dynamic_arena_alloc_bytes :: proc(a: ^Dynamic_Arena, size: int, alignment: int = DEFAULT_ALIGNMENT, loc := #caller_location) -> ([]byte, Allocator_Error) {
|
||||
bytes, err := dynamic_arena_alloc_bytes_non_zeroed(a, size, alignment, loc)
|
||||
if bytes != nil {
|
||||
zero_slice(bytes)
|
||||
}
|
||||
@@ -1797,8 +1783,8 @@ zero-initialized. This procedure returns a pointer to the newly allocated
|
||||
memory region.
|
||||
*/
|
||||
@(require_results)
|
||||
dynamic_arena_alloc_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> (rawptr, Allocator_Error) {
|
||||
data, err := dynamic_arena_alloc_bytes_non_zeroed(a, size, loc)
|
||||
dynamic_arena_alloc_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, alignment: int = DEFAULT_ALIGNMENT, loc := #caller_location) -> (rawptr, Allocator_Error) {
|
||||
data, err := dynamic_arena_alloc_bytes_non_zeroed(a, size, alignment, loc)
|
||||
return raw_data(data), err
|
||||
}
|
||||
|
||||
@@ -1811,31 +1797,35 @@ zero-initialized. This procedure returns a slice of the newly allocated
|
||||
memory region.
|
||||
*/
|
||||
@(require_results)
|
||||
dynamic_arena_alloc_bytes_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
|
||||
dynamic_arena_alloc_bytes_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, alignment: int = DEFAULT_ALIGNMENT, loc := #caller_location) -> ([]byte, Allocator_Error) {
|
||||
if size >= a.out_band_size {
|
||||
assert(a.out_band_allocations.allocator.procedure != nil, "Backing array allocator must be initialized", loc=loc)
|
||||
memory, err := alloc_bytes_non_zeroed(size, a.alignment, a.out_band_allocations.allocator, loc)
|
||||
memory, err := alloc_bytes_non_zeroed(size, alignment, a.out_band_allocations.allocator, loc)
|
||||
if memory != nil {
|
||||
append(&a.out_band_allocations, raw_data(memory), loc = loc)
|
||||
}
|
||||
return memory, err
|
||||
}
|
||||
n := align_formula(size, a.alignment)
|
||||
actual_alignment := max(a.minimum_alignment, alignment)
|
||||
n := align_formula(size, actual_alignment)
|
||||
if n > a.block_size {
|
||||
return nil, .Invalid_Argument
|
||||
}
|
||||
if a.bytes_left < n {
|
||||
err := _dynamic_arena_cycle_new_block(a, loc)
|
||||
memory := align_forward(a.current_pos, uintptr(actual_alignment))
|
||||
margin := int(uintptr(memory) - uintptr(a.current_pos))
|
||||
if a.bytes_left < margin + n {
|
||||
err := _dynamic_arena_cycle_new_block(a, alignment, loc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if a.current_block == nil {
|
||||
return nil, .Out_Of_Memory
|
||||
}
|
||||
margin = 0
|
||||
memory = a.current_pos
|
||||
}
|
||||
memory := a.current_pos
|
||||
a.current_pos = ([^]byte)(a.current_pos)[n:]
|
||||
a.bytes_left -= n
|
||||
a.current_pos = ([^]byte)(memory)[n:]
|
||||
a.bytes_left -= margin + n
|
||||
result := ([^]byte)(memory)[:size]
|
||||
// ensure_poisoned(result)
|
||||
// sanitizer.address_unpoison(result)
|
||||
@@ -1900,9 +1890,10 @@ dynamic_arena_resize :: proc(
|
||||
old_memory: rawptr,
|
||||
old_size: int,
|
||||
size: int,
|
||||
alignment: int = DEFAULT_ALIGNMENT,
|
||||
loc := #caller_location,
|
||||
) -> (rawptr, Allocator_Error) {
|
||||
bytes, err := dynamic_arena_resize_bytes(a, byte_slice(old_memory, old_size), size, loc)
|
||||
bytes, err := dynamic_arena_resize_bytes(a, byte_slice(old_memory, old_size), size, alignment, loc)
|
||||
return raw_data(bytes), err
|
||||
}
|
||||
|
||||
@@ -1921,16 +1912,17 @@ This procedure returns the slice of the resized memory region.
|
||||
*/
|
||||
@(require_results)
|
||||
dynamic_arena_resize_bytes :: proc(
|
||||
a: ^Dynamic_Arena,
|
||||
old_data: []byte,
|
||||
size: int,
|
||||
a: ^Dynamic_Arena,
|
||||
old_data: []byte,
|
||||
size: int,
|
||||
alignment: int = DEFAULT_ALIGNMENT,
|
||||
loc := #caller_location,
|
||||
) -> ([]byte, Allocator_Error) {
|
||||
if size == 0 {
|
||||
// NOTE: This allocator has no Free mode.
|
||||
return nil, nil
|
||||
}
|
||||
bytes, err := dynamic_arena_resize_bytes_non_zeroed(a, old_data, size, loc)
|
||||
bytes, err := dynamic_arena_resize_bytes_non_zeroed(a, old_data, size, alignment, loc)
|
||||
if bytes != nil {
|
||||
if old_data == nil {
|
||||
zero_slice(bytes)
|
||||
@@ -1960,9 +1952,10 @@ dynamic_arena_resize_non_zeroed :: proc(
|
||||
old_memory: rawptr,
|
||||
old_size: int,
|
||||
size: int,
|
||||
alignment: int = DEFAULT_ALIGNMENT,
|
||||
loc := #caller_location,
|
||||
) -> (rawptr, Allocator_Error) {
|
||||
bytes, err := dynamic_arena_resize_bytes_non_zeroed(a, byte_slice(old_memory, old_size), size, loc)
|
||||
bytes, err := dynamic_arena_resize_bytes_non_zeroed(a, byte_slice(old_memory, old_size), size, alignment, loc)
|
||||
return raw_data(bytes), err
|
||||
}
|
||||
|
||||
@@ -1981,9 +1974,10 @@ This procedure returns the slice of the resized memory region.
|
||||
*/
|
||||
@(require_results)
|
||||
dynamic_arena_resize_bytes_non_zeroed :: proc(
|
||||
a: ^Dynamic_Arena,
|
||||
old_data: []byte,
|
||||
size: int,
|
||||
a: ^Dynamic_Arena,
|
||||
old_data: []byte,
|
||||
size: int,
|
||||
alignment: int = DEFAULT_ALIGNMENT,
|
||||
loc := #caller_location,
|
||||
) -> ([]byte, Allocator_Error) {
|
||||
if size == 0 {
|
||||
@@ -1998,7 +1992,7 @@ dynamic_arena_resize_bytes_non_zeroed :: proc(
|
||||
}
|
||||
// No information is kept about allocations in this allocator, thus we
|
||||
// cannot truly resize anything and must reallocate.
|
||||
data, err := dynamic_arena_alloc_bytes_non_zeroed(a, size, loc)
|
||||
data, err := dynamic_arena_alloc_bytes_non_zeroed(a, size, alignment, loc)
|
||||
if err == nil {
|
||||
runtime.copy(data, byte_slice(old_memory, old_size))
|
||||
}
|
||||
@@ -2017,17 +2011,17 @@ dynamic_arena_allocator_proc :: proc(
|
||||
arena := (^Dynamic_Arena)(allocator_data)
|
||||
switch mode {
|
||||
case .Alloc:
|
||||
return dynamic_arena_alloc_bytes(arena, size, loc)
|
||||
return dynamic_arena_alloc_bytes(arena, size, alignment, loc)
|
||||
case .Alloc_Non_Zeroed:
|
||||
return dynamic_arena_alloc_bytes_non_zeroed(arena, size, loc)
|
||||
return dynamic_arena_alloc_bytes_non_zeroed(arena, size, alignment, loc)
|
||||
case .Free:
|
||||
return nil, .Mode_Not_Implemented
|
||||
case .Free_All:
|
||||
dynamic_arena_free_all(arena, loc)
|
||||
case .Resize:
|
||||
return dynamic_arena_resize_bytes(arena, byte_slice(old_memory, old_size), size, loc)
|
||||
return dynamic_arena_resize_bytes(arena, byte_slice(old_memory, old_size), size, alignment, loc)
|
||||
case .Resize_Non_Zeroed:
|
||||
return dynamic_arena_resize_bytes_non_zeroed(arena, byte_slice(old_memory, old_size), size, loc)
|
||||
return dynamic_arena_resize_bytes_non_zeroed(arena, byte_slice(old_memory, old_size), size, alignment, loc)
|
||||
case .Query_Features:
|
||||
set := (^Allocator_Mode_Set)(old_memory)
|
||||
if set != nil {
|
||||
@@ -2038,7 +2032,7 @@ dynamic_arena_allocator_proc :: proc(
|
||||
info := (^Allocator_Query_Info)(old_memory)
|
||||
if info != nil && info.pointer != nil {
|
||||
info.size = arena.block_size
|
||||
info.alignment = arena.alignment
|
||||
info.alignment = arena.minimum_alignment
|
||||
return byte_slice(info, size_of(info^)), nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
13
core/mem/mem_posix.odin
Normal file
13
core/mem/mem_posix.odin
Normal file
@@ -0,0 +1,13 @@
|
||||
#+build linux, darwin, netbsd, freebsd, openbsd
|
||||
package mem
|
||||
|
||||
import "core:sys/posix"
|
||||
|
||||
@(init, private, no_sanitize_address)
|
||||
query_page_size_init :: proc "contextless" () {
|
||||
size := posix.sysconf(._PAGESIZE)
|
||||
PAGE_SIZE = max(PAGE_SIZE, int(size))
|
||||
|
||||
// is power of two
|
||||
assert_contextless(PAGE_SIZE != 0 && (PAGE_SIZE & (PAGE_SIZE-1)) == 0)
|
||||
}
|
||||
16
core/mem/mem_windows.odin
Normal file
16
core/mem/mem_windows.odin
Normal file
@@ -0,0 +1,16 @@
|
||||
#+build windows
|
||||
package mem
|
||||
|
||||
import "core:sys/windows"
|
||||
|
||||
@(init, private, no_sanitize_address)
|
||||
query_page_size_init :: proc "contextless" () {
|
||||
info: windows.SYSTEM_INFO
|
||||
windows.GetSystemInfo(&info)
|
||||
|
||||
size := info.dwPageSize
|
||||
PAGE_SIZE = max(PAGE_SIZE, int(size))
|
||||
|
||||
// is power of two
|
||||
assert_contextless(PAGE_SIZE != 0 && (PAGE_SIZE & (PAGE_SIZE-1)) == 0)
|
||||
}
|
||||
@@ -126,11 +126,11 @@ arena_alloc_unguarded :: proc(arena: ^Arena, size: uint, alignment: uint, loc :=
|
||||
if err == .Out_Of_Memory {
|
||||
if arena.minimum_block_size == 0 {
|
||||
arena.minimum_block_size = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE
|
||||
arena.minimum_block_size = mem.align_forward_uint(arena.minimum_block_size, DEFAULT_PAGE_SIZE)
|
||||
arena.minimum_block_size = mem.align_forward_uint(arena.minimum_block_size, uint(mem.PAGE_SIZE))
|
||||
}
|
||||
if arena.default_commit_size == 0 {
|
||||
arena.default_commit_size = min(DEFAULT_ARENA_GROWING_COMMIT_SIZE, arena.minimum_block_size)
|
||||
arena.default_commit_size = mem.align_forward_uint(arena.default_commit_size, DEFAULT_PAGE_SIZE)
|
||||
arena.default_commit_size = mem.align_forward_uint(arena.default_commit_size, uint(mem.PAGE_SIZE))
|
||||
}
|
||||
|
||||
if arena.default_commit_size != 0 {
|
||||
|
||||
@@ -6,13 +6,6 @@ import "base:intrinsics"
|
||||
import "base:runtime"
|
||||
_ :: runtime
|
||||
|
||||
DEFAULT_PAGE_SIZE := uint(4096)
|
||||
|
||||
@(init, private)
|
||||
platform_memory_init :: proc "contextless" () {
|
||||
_platform_memory_init()
|
||||
}
|
||||
|
||||
Allocator_Error :: mem.Allocator_Error
|
||||
|
||||
@(require_results, no_sanitize_address)
|
||||
@@ -79,7 +72,7 @@ align_formula :: #force_inline proc "contextless" (size, align: uint) -> uint {
|
||||
|
||||
@(require_results, no_sanitize_address)
|
||||
memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags: Memory_Block_Flags = {}) -> (block: ^Memory_Block, err: Allocator_Error) {
|
||||
page_size := DEFAULT_PAGE_SIZE
|
||||
page_size := uint(mem.PAGE_SIZE)
|
||||
assert(mem.is_power_of_two(uintptr(page_size)))
|
||||
|
||||
committed := committed
|
||||
@@ -144,7 +137,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint,
|
||||
// TODO(bill): determine a better heuristic for this behaviour
|
||||
extra_size := max(size, block.committed>>1)
|
||||
platform_total_commit := base_offset + block.used + extra_size
|
||||
platform_total_commit = align_formula(platform_total_commit, DEFAULT_PAGE_SIZE)
|
||||
platform_total_commit = align_formula(platform_total_commit, uint(mem.PAGE_SIZE))
|
||||
platform_total_commit = min(max(platform_total_commit, default_commit_size), pmblock.reserved)
|
||||
|
||||
assert(pmblock.committed <= pmblock.reserved)
|
||||
|
||||
@@ -43,12 +43,6 @@ _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags)
|
||||
return errno == .NONE
|
||||
}
|
||||
|
||||
_platform_memory_init :: proc "contextless" () {
|
||||
DEFAULT_PAGE_SIZE = 4096
|
||||
// is power of two
|
||||
assert_contextless(DEFAULT_PAGE_SIZE != 0 && (DEFAULT_PAGE_SIZE & (DEFAULT_PAGE_SIZE-1)) == 0)
|
||||
}
|
||||
|
||||
_map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
|
||||
prot: linux.Mem_Protection
|
||||
if .Read in flags {
|
||||
|
||||
@@ -25,10 +25,6 @@ _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags)
|
||||
return false
|
||||
}
|
||||
|
||||
_platform_memory_init :: proc "contextless" () {
|
||||
|
||||
}
|
||||
|
||||
_map_file :: proc "contextless" (f: any, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
|
||||
return nil, .Map_Failure
|
||||
}
|
||||
|
||||
@@ -28,15 +28,6 @@ _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags)
|
||||
return posix.mprotect(data, size, transmute(posix.Prot_Flags)flags) == .OK
|
||||
}
|
||||
|
||||
_platform_memory_init :: proc "contextless" () {
|
||||
// NOTE: `posix.PAGESIZE` due to legacy reasons could be wrong so we use `sysconf`.
|
||||
size := posix.sysconf(._PAGESIZE)
|
||||
DEFAULT_PAGE_SIZE = uint(max(size, posix.PAGESIZE))
|
||||
|
||||
// is power of two
|
||||
assert_contextless(DEFAULT_PAGE_SIZE != 0 && (DEFAULT_PAGE_SIZE & (DEFAULT_PAGE_SIZE-1)) == 0)
|
||||
}
|
||||
|
||||
_map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
|
||||
#assert(i32(posix.Prot_Flag_Bits.READ) == i32(Map_File_Flag.Read))
|
||||
#assert(i32(posix.Prot_Flag_Bits.WRITE) == i32(Map_File_Flag.Write))
|
||||
|
||||
@@ -146,18 +146,6 @@ _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags)
|
||||
return bool(ok)
|
||||
}
|
||||
|
||||
|
||||
@(no_sanitize_address)
|
||||
_platform_memory_init :: proc "contextless" () {
|
||||
sys_info: SYSTEM_INFO
|
||||
GetSystemInfo(&sys_info)
|
||||
DEFAULT_PAGE_SIZE = max(DEFAULT_PAGE_SIZE, uint(sys_info.dwPageSize))
|
||||
|
||||
// is power of two
|
||||
assert_contextless(DEFAULT_PAGE_SIZE != 0 && (DEFAULT_PAGE_SIZE & (DEFAULT_PAGE_SIZE-1)) == 0)
|
||||
}
|
||||
|
||||
|
||||
@(no_sanitize_address)
|
||||
_map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
|
||||
page_flags: u32
|
||||
|
||||
@@ -378,7 +378,17 @@ 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)
|
||||
if p.curr_tok.kind == .Semicolon && p.expr_level > 0 && p.curr_tok.text == "\n" {
|
||||
advance_token(p)
|
||||
}
|
||||
case .Semicolon:
|
||||
if p.expr_level > 0 && p.curr_tok.text == "\n" {
|
||||
advance_token(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
return prev
|
||||
}
|
||||
@@ -1785,6 +1795,13 @@ is_token_field_prefix :: proc(p: ^Parser) -> ast.Field_Flag {
|
||||
advance_token(p)
|
||||
return .Using
|
||||
case .Hash:
|
||||
if tok := peek_token(p); tok.kind == .Ident {
|
||||
switch tok.text {
|
||||
case "simd", "type", "row_major", "column_major", "sparse", "soa":
|
||||
return .Invalid
|
||||
}
|
||||
}
|
||||
|
||||
tok: tokenizer.Token
|
||||
advance_token(p)
|
||||
tok = p.curr_tok
|
||||
@@ -2366,10 +2383,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))
|
||||
@@ -2539,6 +2556,17 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
|
||||
for p.curr_tok.kind != .Close_Brace &&
|
||||
p.curr_tok.kind != .EOF {
|
||||
elem := parse_expr(p, false)
|
||||
|
||||
if p.curr_tok.kind == .Where {
|
||||
tok_where := expect_token(p, .Where)
|
||||
cond := parse_expr(p, false)
|
||||
|
||||
be := ast.new(ast.Binary_Expr, elem.pos, end_pos(p.prev_tok))
|
||||
be.left = elem
|
||||
be.op = tok_where
|
||||
be.right = cond
|
||||
elem = be
|
||||
}
|
||||
append(&args, elem)
|
||||
|
||||
allow_token(p, .Comma) or_break
|
||||
@@ -2805,11 +2833,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")
|
||||
@@ -3208,11 +3231,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)
|
||||
@@ -3231,7 +3255,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
|
||||
@@ -3278,8 +3303,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
|
||||
@@ -3365,8 +3390,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 {
|
||||
@@ -3600,7 +3625,9 @@ parse_binary_expr :: proc(p: ^Parser, lhs: bool, prec_in: int) -> ^ast.Expr {
|
||||
case .If, .When:
|
||||
if p.prev_tok.pos.line < op.pos.line {
|
||||
// NOTE(bill): Check to see if the `if` or `when` is on the same line of the `lhs` condition
|
||||
break loop
|
||||
if p.expr_level <= 0 {
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -536,7 +536,7 @@ is_directory :: proc(path: string) -> bool {
|
||||
}
|
||||
|
||||
/*
|
||||
`copy_file` copies a file from `src_path` to `dst_path` and returns an error if any was encountered.
|
||||
`is_tty` returns `true` if `f` is a TTY, `false` if not.
|
||||
*/
|
||||
@(require_results)
|
||||
is_tty :: proc "contextless" (f: ^File) -> bool {
|
||||
|
||||
@@ -216,18 +216,18 @@ read_entire_file_from_file :: proc(f: ^File, allocator: runtime.Allocator, loc :
|
||||
return
|
||||
} else {
|
||||
buffer: [1024]u8
|
||||
out_buffer := make([dynamic]u8, 0, 0, allocator, loc)
|
||||
total := 0
|
||||
out_buffer := make([dynamic]u8, 0, 0, allocator, loc) or_return
|
||||
for {
|
||||
n: int
|
||||
n, err = read(f, buffer[:])
|
||||
total += n
|
||||
append_elems(&out_buffer, ..buffer[:n], loc=loc) or_return
|
||||
if _, aerr := append_elems(&out_buffer, ..buffer[:n], loc=loc); aerr != nil {
|
||||
return out_buffer[:], aerr
|
||||
}
|
||||
if err != nil {
|
||||
if err == .EOF || err == .Broken_Pipe {
|
||||
err = nil
|
||||
}
|
||||
data = out_buffer[:total]
|
||||
data = out_buffer[:]
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,7 +433,7 @@ fill :: proc "contextless" (array: $T/[]$E, value: E) #no_bounds_check {
|
||||
}
|
||||
|
||||
rotate_left :: proc "contextless" (array: $T/[]$E, mid: int) {
|
||||
if len(a) == 0 {
|
||||
if len(array) == 0 {
|
||||
return
|
||||
}
|
||||
n := len(array)
|
||||
|
||||
@@ -263,8 +263,8 @@ _ram_stats :: proc "contextless" () -> (total_ram, free_ram, total_swap, free_sw
|
||||
|
||||
total_ram = i64(state.ullTotalPhys)
|
||||
free_ram = i64(state.ullAvailPhys)
|
||||
total_swap = i64(state.ullTotalPageFil)
|
||||
free_swap = i64(state.ullAvailPageFil)
|
||||
total_swap = i64(state.ullTotalPageFile)
|
||||
free_swap = i64(state.ullAvailPageFile)
|
||||
ok = true
|
||||
|
||||
return
|
||||
|
||||
@@ -571,17 +571,18 @@ foreign kernel32 {
|
||||
DisconnectNamedPipe :: proc(hNamedPipe: HANDLE) -> BOOL ---
|
||||
WaitNamedPipeW :: proc(lpNamedPipeName: LPCWSTR, nTimeOut: DWORD) -> BOOL ---
|
||||
|
||||
AllocConsole :: proc() -> BOOL ---
|
||||
AttachConsole :: proc(dwProcessId: DWORD) -> BOOL ---
|
||||
SetConsoleCtrlHandler :: proc(HandlerRoutine: PHANDLER_ROUTINE, Add: BOOL) -> BOOL ---
|
||||
GenerateConsoleCtrlEvent :: proc(dwCtrlEvent: DWORD, dwProcessGroupId: DWORD) -> BOOL ---
|
||||
FreeConsole :: proc() -> BOOL ---
|
||||
GetConsoleWindow :: proc() -> HWND ---
|
||||
GetConsoleScreenBufferInfo :: proc(hConsoleOutput: HANDLE, lpConsoleScreenBufferInfo: PCONSOLE_SCREEN_BUFFER_INFO) -> BOOL ---
|
||||
SetConsoleScreenBufferSize :: proc(hConsoleOutput: HANDLE, dwSize: COORD) -> BOOL ---
|
||||
SetConsoleWindowInfo :: proc(hConsoleOutput: HANDLE, bAbsolute: BOOL, lpConsoleWindow: ^SMALL_RECT) -> BOOL ---
|
||||
GetConsoleCursorInfo :: proc(hConsoleOutput: HANDLE, lpConsoleCursorInfo: PCONSOLE_CURSOR_INFO) -> BOOL ---
|
||||
SetConsoleCursorInfo :: proc(hConsoleOutput: HANDLE, lpConsoleCursorInfo: PCONSOLE_CURSOR_INFO) -> BOOL ---
|
||||
AllocConsole :: proc() -> BOOL ---
|
||||
AttachConsole :: proc(dwProcessId: DWORD) -> BOOL ---
|
||||
SetConsoleCtrlHandler :: proc(HandlerRoutine: PHANDLER_ROUTINE, Add: BOOL) -> BOOL ---
|
||||
GenerateConsoleCtrlEvent :: proc(dwCtrlEvent: DWORD, dwProcessGroupId: DWORD) -> BOOL ---
|
||||
FreeConsole :: proc() -> BOOL ---
|
||||
GetConsoleWindow :: proc() -> HWND ---
|
||||
GetConsoleScreenBufferInfo :: proc(hConsoleOutput: HANDLE, lpConsoleScreenBufferInfo: PCONSOLE_SCREEN_BUFFER_INFO) -> BOOL ---
|
||||
GetConsoleScreenBufferInfoEx :: proc(hConsoleOutput: HANDLE, lpConsoleScreenBufferInfoEx: PCONSOLE_SCREEN_BUFFER_INFOEX) -> BOOL ---
|
||||
SetConsoleScreenBufferSize :: proc(hConsoleOutput: HANDLE, dwSize: COORD) -> BOOL ---
|
||||
SetConsoleWindowInfo :: proc(hConsoleOutput: HANDLE, bAbsolute: BOOL, lpConsoleWindow: ^SMALL_RECT) -> BOOL ---
|
||||
GetConsoleCursorInfo :: proc(hConsoleOutput: HANDLE, lpConsoleCursorInfo: PCONSOLE_CURSOR_INFO) -> BOOL ---
|
||||
SetConsoleCursorInfo :: proc(hConsoleOutput: HANDLE, lpConsoleCursorInfo: PCONSOLE_CURSOR_INFO) -> BOOL ---
|
||||
|
||||
GetDiskFreeSpaceExW :: proc(
|
||||
lpDirectoryName: LPCWSTR,
|
||||
|
||||
@@ -3411,9 +3411,21 @@ CONSOLE_READCONSOLE_CONTROL :: struct {
|
||||
dwCtrlWakeupMask: ULONG,
|
||||
dwControlKeyState: ULONG,
|
||||
}
|
||||
|
||||
PCONSOLE_READCONSOLE_CONTROL :: ^CONSOLE_READCONSOLE_CONTROL
|
||||
|
||||
CONSOLE_SCREEN_BUFFER_INFOEX :: struct {
|
||||
cbSize: ULONG,
|
||||
dwSize: COORD,
|
||||
dwCursorPosition: COORD,
|
||||
wAttributes: WORD,
|
||||
srWindow: SMALL_RECT,
|
||||
dwMaximumWindowSize: COORD,
|
||||
wPopupAttributes: WORD,
|
||||
bFullscreenSupported: BOOL,
|
||||
ColorTable: [16]COLORREF,
|
||||
}
|
||||
PCONSOLE_SCREEN_BUFFER_INFOEX :: ^CONSOLE_SCREEN_BUFFER_INFOEX
|
||||
|
||||
BY_HANDLE_FILE_INFORMATION :: struct {
|
||||
dwFileAttributes: DWORD,
|
||||
ftCreationTime: FILETIME,
|
||||
@@ -3426,7 +3438,6 @@ BY_HANDLE_FILE_INFORMATION :: struct {
|
||||
nFileIndexHigh: DWORD,
|
||||
nFileIndexLow: DWORD,
|
||||
}
|
||||
|
||||
LPBY_HANDLE_FILE_INFORMATION :: ^BY_HANDLE_FILE_INFORMATION
|
||||
|
||||
FILE_STANDARD_INFO :: struct {
|
||||
@@ -4791,8 +4802,8 @@ MEMORYSTATUSEX :: struct {
|
||||
dwMemoryLoad: DWORD,
|
||||
ullTotalPhys: DWORDLONG,
|
||||
ullAvailPhys: DWORDLONG,
|
||||
ullTotalPageFil: DWORDLONG,
|
||||
ullAvailPageFil: DWORDLONG,
|
||||
ullTotalPageFile: DWORDLONG,
|
||||
ullAvailPageFile: DWORDLONG,
|
||||
ullTotalVirtual: DWORDLONG,
|
||||
ullAvailVirtual: DWORDLONG,
|
||||
ullAvailExtendedVirtual: DWORDLONG,
|
||||
|
||||
@@ -51,8 +51,6 @@ Example:
|
||||
Tn :: i18n.get_n
|
||||
|
||||
mo :: proc() {
|
||||
using fmt
|
||||
|
||||
err: i18n.Error
|
||||
|
||||
// Parse MO file and set it as the active translation so we can omit `get`'s "catalog" parameter.
|
||||
@@ -62,26 +60,24 @@ Example:
|
||||
if err != .None { return }
|
||||
|
||||
// These are in the .MO catalog.
|
||||
println("-----")
|
||||
println(T(""))
|
||||
println("-----")
|
||||
println(T("There are 69,105 leaves here."))
|
||||
println("-----")
|
||||
println(T("Hellope, World!"))
|
||||
println("-----")
|
||||
fmt.println("-----")
|
||||
fmt.println(T(""))
|
||||
fmt.println("-----")
|
||||
fmt.println(T("There are 69,105 leaves here."))
|
||||
fmt.println("-----")
|
||||
fmt.println(T("Hellope, World!"))
|
||||
fmt.println("-----")
|
||||
// We pass 1 into `T` to get the singular format string, then 1 again into printf.
|
||||
printf(Tn("There is %d leaf.\n", 1), 1)
|
||||
fmt.printf(Tn("There is %d leaf.\n", 1), 1)
|
||||
// We pass 42 into `T` to get the plural format string, then 42 again into printf.
|
||||
printf(Tn("There is %d leaf.\n", 42), 42)
|
||||
fmt.printf(Tn("There is %d leaf.\n", 42), 42)
|
||||
|
||||
// This isn't in the translation catalog, so the key is passed back untranslated.
|
||||
println("-----")
|
||||
println(T("Come visit us on Discord!"))
|
||||
fmt.println("-----")
|
||||
fmt.println(T("Come visit us on Discord!"))
|
||||
}
|
||||
|
||||
qt :: proc() {
|
||||
using fmt
|
||||
|
||||
err: i18n.Error
|
||||
|
||||
// Parse QT file and set it as the active translation so we can omit `get`'s "catalog" parameter.
|
||||
@@ -91,18 +87,18 @@ Example:
|
||||
if err != .None { return }
|
||||
|
||||
// These are in the .TS catalog. As you can see they have sections.
|
||||
println("--- Page section ---")
|
||||
println("Page:Text for translation =", T("Page", "Text for translation"))
|
||||
println("-----")
|
||||
println("Page:Also text to translate =", T("Page", "Also text to translate"))
|
||||
println("-----")
|
||||
println("--- installscript section ---")
|
||||
println("installscript:99 bottles of beer on the wall =", T("installscript", "99 bottles of beer on the wall"))
|
||||
println("-----")
|
||||
println("--- apple_count section ---")
|
||||
println("apple_count:%d apple(s) =")
|
||||
println("\t 1 =", Tn("apple_count", "%d apple(s)", 1))
|
||||
println("\t 42 =", Tn("apple_count", "%d apple(s)", 42))
|
||||
fmt.println("--- Page section ---")
|
||||
fmt.println("Page:Text for translation =", T("Page", "Text for translation"))
|
||||
fmt.println("-----")
|
||||
fmt.println("Page:Also text to translate =", T("Page", "Also text to translate"))
|
||||
fmt.println("-----")
|
||||
fmt.println("--- installscript section ---")
|
||||
fmt.println("installscript:99 bottles of beer on the wall =", T("installscript", "99 bottles of beer on the wall"))
|
||||
fmt.println("-----")
|
||||
fmt.println("--- apple_count section ---")
|
||||
fmt.println("apple_count:%d apple(s) =")
|
||||
fmt.println("\t 1 =", Tn("apple_count", "%d apple(s)", 1))
|
||||
fmt.println("\t 42 =", Tn("apple_count", "%d apple(s)", 42))
|
||||
}
|
||||
*/
|
||||
package i18n
|
||||
|
||||
@@ -43,9 +43,16 @@ Node_Match_All_And_Escape :: parser.Node_Match_All_And_Escape
|
||||
Opcode :: virtual_machine.Opcode
|
||||
Program :: [dynamic]Opcode
|
||||
|
||||
JUMP_SIZE :: size_of(Opcode) + 1 * size_of(u16)
|
||||
SPLIT_SIZE :: size_of(Opcode) + 2 * size_of(u16)
|
||||
Jump :: virtual_machine.Jump
|
||||
Split :: virtual_machine.Split
|
||||
Wait_For_Byte :: virtual_machine.Wait_For_Byte
|
||||
Wait_For_Rune :: virtual_machine.Wait_For_Rune
|
||||
Wait_For_Rune_Class :: virtual_machine.Wait_For_Rune_Class
|
||||
Wait_For_Rune_Class_Negated :: virtual_machine.Wait_For_Rune_Class_Negated
|
||||
Save :: virtual_machine.Save
|
||||
|
||||
JUMP_SIZE :: size_of(Jump)
|
||||
SPLIT_SIZE :: size_of(Split)
|
||||
|
||||
Compiler :: struct {
|
||||
flags: common.Flags,
|
||||
@@ -141,15 +148,13 @@ map_all_classes :: proc(tree: Node, collection: ^[dynamic]Rune_Class_Data) {
|
||||
|
||||
append_raw :: #force_inline proc(code: ^Program, data: $T) {
|
||||
// NOTE: This is system-dependent endian.
|
||||
for b in transmute([size_of(T)]byte)data {
|
||||
append(code, cast(Opcode)b)
|
||||
}
|
||||
data := transmute([size_of(T)]Opcode)data
|
||||
append(code, ..data[:])
|
||||
}
|
||||
inject_raw :: #force_inline proc(code: ^Program, start: int, data: $T) {
|
||||
// NOTE: This is system-dependent endian.
|
||||
for b, i in transmute([size_of(T)]byte)data {
|
||||
inject_at(code, start + i, cast(Opcode)b)
|
||||
}
|
||||
data := transmute([size_of(T)]Opcode)data
|
||||
inject_at(code, start, ..data[:])
|
||||
}
|
||||
|
||||
@require_results
|
||||
@@ -220,8 +225,8 @@ generate_code :: proc(c: ^Compiler, node: Node) -> (code: Program) {
|
||||
code = generate_code(c, specific.inner)
|
||||
|
||||
if specific.capture && .No_Capture not_in c.flags {
|
||||
inject_at(&code, 0, Opcode.Save)
|
||||
inject_at(&code, 1, Opcode(2 * specific.capture_id))
|
||||
save := Save{.Save, Opcode(2 * specific.capture_id)}
|
||||
inject_raw(&code, 0, save)
|
||||
|
||||
append(&code, Opcode.Save)
|
||||
append(&code, Opcode(2 * specific.capture_id + 1))
|
||||
@@ -236,9 +241,8 @@ generate_code :: proc(c: ^Compiler, node: Node) -> (code: Program) {
|
||||
// Avoiding duplicate allocation by reusing `left`.
|
||||
code = left
|
||||
|
||||
inject_at(&code, 0, Opcode.Split)
|
||||
inject_raw(&code, size_of(byte) , i16(SPLIT_SIZE))
|
||||
inject_raw(&code, size_of(byte) + size_of(i16), i16(SPLIT_SIZE + left_len + JUMP_SIZE))
|
||||
split := Split{.Split, i16(SPLIT_SIZE), i16(SPLIT_SIZE + left_len + JUMP_SIZE)}
|
||||
inject_raw(&code, 0, split)
|
||||
|
||||
append(&code, Opcode.Jump)
|
||||
append_raw(&code, i16(len(right) + JUMP_SIZE))
|
||||
@@ -259,9 +263,8 @@ generate_code :: proc(c: ^Compiler, node: Node) -> (code: Program) {
|
||||
code = generate_code(c, specific.inner)
|
||||
original_len := len(code)
|
||||
|
||||
inject_at(&code, 0, Opcode.Split)
|
||||
inject_raw(&code, size_of(byte) , i16(SPLIT_SIZE))
|
||||
inject_raw(&code, size_of(byte) + size_of(i16), i16(SPLIT_SIZE + original_len + JUMP_SIZE))
|
||||
split := Split{.Split, i16(SPLIT_SIZE), i16(SPLIT_SIZE + original_len + JUMP_SIZE)}
|
||||
inject_raw(&code, 0, split)
|
||||
|
||||
append(&code, Opcode.Jump)
|
||||
append_raw(&code, i16(-original_len - SPLIT_SIZE))
|
||||
@@ -270,9 +273,8 @@ generate_code :: proc(c: ^Compiler, node: Node) -> (code: Program) {
|
||||
code = generate_code(c, specific.inner)
|
||||
original_len := len(code)
|
||||
|
||||
inject_at(&code, 0, Opcode.Split)
|
||||
inject_raw(&code, size_of(byte) , i16(SPLIT_SIZE + original_len + JUMP_SIZE))
|
||||
inject_raw(&code, size_of(byte) + size_of(i16), i16(SPLIT_SIZE))
|
||||
split := Split{.Split, i16(SPLIT_SIZE + original_len + JUMP_SIZE), i16(SPLIT_SIZE)}
|
||||
inject_raw(&code, 0, split)
|
||||
|
||||
append(&code, Opcode.Jump)
|
||||
append_raw(&code, i16(-original_len - SPLIT_SIZE))
|
||||
@@ -359,17 +361,15 @@ generate_code :: proc(c: ^Compiler, node: Node) -> (code: Program) {
|
||||
code = generate_code(c, specific.inner)
|
||||
original_len := len(code)
|
||||
|
||||
inject_at(&code, 0, Opcode.Split)
|
||||
inject_raw(&code, size_of(byte) , i16(SPLIT_SIZE))
|
||||
inject_raw(&code, size_of(byte) + size_of(i16), i16(SPLIT_SIZE + original_len))
|
||||
split := Split{.Split, i16(SPLIT_SIZE), i16(SPLIT_SIZE + original_len)}
|
||||
inject_raw(&code, 0, split)
|
||||
|
||||
case ^Node_Optional_Non_Greedy:
|
||||
code = generate_code(c, specific.inner)
|
||||
original_len := len(code)
|
||||
|
||||
inject_at(&code, 0, Opcode.Split)
|
||||
inject_raw(&code, size_of(byte) , i16(SPLIT_SIZE + original_len))
|
||||
inject_raw(&code, size_of(byte) + size_of(i16), i16(SPLIT_SIZE))
|
||||
split := Split{.Split, i16(SPLIT_SIZE + original_len), i16(SPLIT_SIZE)}
|
||||
inject_raw(&code, 0, split)
|
||||
|
||||
case ^Node_Match_All_And_Escape:
|
||||
append(&code, Opcode.Match_All_And_Escape)
|
||||
@@ -412,32 +412,28 @@ compile :: proc(tree: Node, flags: common.Flags) -> (code: Program, class_data:
|
||||
seek_loop: for opcode, pc in virtual_machine.iterate_opcodes(&iter) {
|
||||
#partial switch opcode {
|
||||
case .Byte:
|
||||
inject_at(&code, pc_open, Opcode.Wait_For_Byte)
|
||||
pc_open += size_of(Opcode)
|
||||
inject_at(&code, pc_open, Opcode(code[pc + size_of(Opcode) + pc_open]))
|
||||
pc_open += size_of(u8)
|
||||
wait := Wait_For_Byte{.Wait_For_Byte, code[pc + size_of(Opcode) + pc_open]}
|
||||
inject_raw(&code, pc_open, wait)
|
||||
pc_open += size_of(Wait_For_Byte)
|
||||
break optimize_opening
|
||||
|
||||
case .Rune:
|
||||
operand := intrinsics.unaligned_load(cast(^rune)&code[pc+1])
|
||||
inject_at(&code, pc_open, Opcode.Wait_For_Rune)
|
||||
pc_open += size_of(Opcode)
|
||||
inject_raw(&code, pc_open, operand)
|
||||
pc_open += size_of(rune)
|
||||
wait := Wait_For_Rune{.Wait_For_Rune, operand}
|
||||
inject_raw(&code, pc_open, wait)
|
||||
pc_open += size_of(Wait_For_Rune)
|
||||
break optimize_opening
|
||||
|
||||
case .Rune_Class:
|
||||
inject_at(&code, pc_open, Opcode.Wait_For_Rune_Class)
|
||||
pc_open += size_of(Opcode)
|
||||
inject_at(&code, pc_open, Opcode(code[pc + size_of(Opcode) + pc_open]))
|
||||
pc_open += size_of(u8)
|
||||
wait := Wait_For_Rune_Class{.Wait_For_Rune_Class, code[pc + size_of(Opcode) + pc_open]}
|
||||
inject_raw(&code, pc_open, wait)
|
||||
pc_open += size_of(Wait_For_Rune_Class)
|
||||
break optimize_opening
|
||||
|
||||
case .Rune_Class_Negated:
|
||||
inject_at(&code, pc_open, Opcode.Wait_For_Rune_Class_Negated)
|
||||
pc_open += size_of(Opcode)
|
||||
inject_at(&code, pc_open, Opcode(code[pc + size_of(Opcode) + pc_open]))
|
||||
pc_open += size_of(u8)
|
||||
wait := Wait_For_Rune_Class_Negated{.Wait_For_Rune_Class_Negated, code[pc + size_of(Opcode) + pc_open]}
|
||||
inject_raw(&code, pc_open, wait)
|
||||
pc_open += size_of(Wait_For_Rune_Class_Negated)
|
||||
break optimize_opening
|
||||
|
||||
case .Save:
|
||||
@@ -452,27 +448,21 @@ compile :: proc(tree: Node, flags: common.Flags) -> (code: Program, class_data:
|
||||
}
|
||||
|
||||
// `.*?`
|
||||
inject_at(&code, pc_open, Opcode.Split)
|
||||
pc_open += size_of(byte)
|
||||
inject_raw(&code, pc_open, i16(SPLIT_SIZE + size_of(byte) + JUMP_SIZE))
|
||||
pc_open += size_of(i16)
|
||||
inject_raw(&code, pc_open, i16(SPLIT_SIZE))
|
||||
pc_open += size_of(i16)
|
||||
|
||||
inject_at(&code, pc_open, Opcode.Wildcard)
|
||||
pc_open += size_of(byte)
|
||||
|
||||
inject_at(&code, pc_open, Opcode.Jump)
|
||||
pc_open += size_of(byte)
|
||||
inject_raw(&code, pc_open, i16(-size_of(byte) - SPLIT_SIZE))
|
||||
pc_open += size_of(i16)
|
||||
|
||||
split := Split{.Split, i16(SPLIT_SIZE + size_of(byte) + JUMP_SIZE), i16(SPLIT_SIZE)}
|
||||
jump := Jump{.Jump, i16(-size_of(byte) - SPLIT_SIZE)}
|
||||
pack := struct {
|
||||
a: Split,
|
||||
b: Opcode,
|
||||
c: Jump,
|
||||
} { split, Opcode.Wildcard, jump }
|
||||
inject_raw(&code, pc_open, pack)
|
||||
pc_open += size_of(Split) + size_of(byte) + size_of(Jump)
|
||||
}
|
||||
|
||||
if .No_Capture not_in flags {
|
||||
// `(` <generated code>
|
||||
inject_at(&code, pc_open, Opcode.Save)
|
||||
inject_at(&code, pc_open + size_of(byte), Opcode(0x00))
|
||||
save := Save{.Save, Opcode(0x00)}
|
||||
inject_raw(&code, pc_open, save)
|
||||
|
||||
// `)`
|
||||
append(&code, Opcode.Save); append(&code, Opcode(0x01))
|
||||
|
||||
@@ -49,6 +49,35 @@ Opcode :: enum u8 {
|
||||
Wait_For_Rune_Class_Negated = 0x14, // | u8
|
||||
Match_All_And_Escape = 0x15, // |
|
||||
}
|
||||
Jump :: struct #packed {
|
||||
opcode: Opcode,
|
||||
target: i16,
|
||||
}
|
||||
Split :: struct #packed {
|
||||
opcode: Opcode,
|
||||
left: i16,
|
||||
right: i16,
|
||||
}
|
||||
Wait_For_Byte :: struct #packed {
|
||||
opcode: Opcode,
|
||||
operand: Opcode,
|
||||
}
|
||||
Wait_For_Rune :: struct #packed {
|
||||
opcode: Opcode,
|
||||
operand: rune,
|
||||
}
|
||||
Wait_For_Rune_Class :: struct #packed {
|
||||
opcode: Opcode,
|
||||
operand: Opcode,
|
||||
}
|
||||
Wait_For_Rune_Class_Negated :: struct #packed {
|
||||
opcode: Opcode,
|
||||
operand: Opcode,
|
||||
}
|
||||
Save :: struct #packed {
|
||||
opcode: Opcode,
|
||||
operand: Opcode,
|
||||
}
|
||||
|
||||
Thread :: struct {
|
||||
pc: int,
|
||||
|
||||
@@ -65,7 +65,7 @@ Capable of representing any time within the following range:
|
||||
- `max: 2262-04-11 23:47:16.854775807 +0000 UTC`
|
||||
*/
|
||||
Time :: struct {
|
||||
_nsec: i64, // Measured in UNIX nanonseconds
|
||||
_nsec: i64, // Measured in UNIX nanoseconds
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -49,6 +49,7 @@ package all
|
||||
@(require) import "core:crypto/pbkdf2"
|
||||
@(require) import "core:crypto/poly1305"
|
||||
@(require) import "core:crypto/ristretto255"
|
||||
@(require) import "core:crypto/rsa"
|
||||
@(require) import "core:crypto/sha2"
|
||||
@(require) import "core:crypto/sha3"
|
||||
@(require) import "core:crypto/shake"
|
||||
|
||||
@@ -54,6 +54,7 @@ package all
|
||||
@(require) import "core:crypto/pbkdf2"
|
||||
@(require) import "core:crypto/poly1305"
|
||||
@(require) import "core:crypto/ristretto255"
|
||||
@(require) import "core:crypto/rsa"
|
||||
@(require) import "core:crypto/sha2"
|
||||
@(require) import "core:crypto/sha3"
|
||||
@(require) import "core:crypto/shake"
|
||||
|
||||
@@ -6,5 +6,6 @@ package all
|
||||
@(require) import "vendor:wgpu/sdl2glue"
|
||||
@(require) import "vendor:wgpu"
|
||||
@(require) import "vendor:box2d"
|
||||
@(require) import "vendor:box3d"
|
||||
@(require) import "vendor:windows/GameInput"
|
||||
@(require) import "vendor:windows/XAudio2"
|
||||
@@ -670,7 +670,7 @@ union_type :: proc() {
|
||||
{
|
||||
// NOTE(bill): A union can be used to achieve something similar. Instead
|
||||
// of embedding the base data into the derived types, the derived data
|
||||
// in embedded into the base type. Below is the same example of the
|
||||
// is embedded into the base type. Below is the same example of the
|
||||
// basic game Entity but using an union.
|
||||
|
||||
Entity :: struct {
|
||||
@@ -757,7 +757,7 @@ using_statement :: proc() {
|
||||
// `using` as a struct field modifier remains available always
|
||||
|
||||
fmt.println("\n# using statement")
|
||||
// using can used to bring entities declared in a scope/namespace
|
||||
// using can be used to bring entities declared in a scope/namespace
|
||||
// into the current scope. This can be applied to import names, struct
|
||||
// fields, procedure fields, and struct values.
|
||||
|
||||
@@ -768,7 +768,7 @@ using_statement :: proc() {
|
||||
orientation: quaternion128,
|
||||
}
|
||||
|
||||
// It can used like this:
|
||||
// It can be used like this:
|
||||
foo0 :: proc(entity: ^Entity) {
|
||||
fmt.println(entity.position.x, entity.position.y, entity.position.z)
|
||||
}
|
||||
|
||||
@@ -449,6 +449,20 @@ gb_internal void array_unordered_remove(Array<T> *array, isize index) {
|
||||
array_pop(array);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
gb_internal void array_inject_at(Array<T> *array, isize index, T value) {
|
||||
GB_ASSERT(0 <= index);
|
||||
|
||||
isize n = gb_max(array->count, index);
|
||||
isize new_size = n+1;
|
||||
array_resize(array, new_size);
|
||||
|
||||
gb_memmove(array->data+index+1, array->data+index, gb_size_of(T)*(array->count-index-1));
|
||||
array->data[index] = value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
template <typename T>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user