Merge remote-tracking branch 'upstream/master' into sys-windows-2

# Conflicts:
#	core/sys/windows/shell32.odin
This commit is contained in:
Thomas la Cour
2024-07-25 10:05:41 +02:00
153 changed files with 9122 additions and 1426 deletions
+5 -7
View File
@@ -6,7 +6,7 @@ jobs:
name: NetBSD Build, Check, and Test name: NetBSD Build, Check, and Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
PKGSRC_BRANCH: 2024Q1 PKGSRC_BRANCH: 2024Q2
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Build, Check, and Test - name: Build, Check, and Test
@@ -19,10 +19,7 @@ jobs:
copyback: false copyback: false
prepare: | prepare: |
PKG_PATH="https://cdn.NetBSD.org/pub/pkgsrc/packages/NetBSD/$(uname -p)/$(uname -r | cut -d_ -f1)_${PKGSRC_BRANCH}/All" /usr/sbin/pkg_add pkgin PKG_PATH="https://cdn.NetBSD.org/pub/pkgsrc/packages/NetBSD/$(uname -p)/$(uname -r | cut -d_ -f1)_${PKGSRC_BRANCH}/All" /usr/sbin/pkg_add pkgin
pkgin -y in gmake git bash python311 pkgin -y in gmake git bash python311 llvm clang
pkgin -y in libxml2 perl zstd
/usr/sbin/pkg_add https://github.com/andreas-jonsson/llvm17-netbsd-bin/releases/download/pkgsrc-current/llvm-17.0.6.tgz
/usr/sbin/pkg_add https://github.com/andreas-jonsson/llvm17-netbsd-bin/releases/download/pkgsrc-current/clang-17.0.6.tgz
ln -s /usr/pkg/bin/python3.11 /usr/bin/python3 ln -s /usr/pkg/bin/python3.11 /usr/bin/python3
run: | run: |
git config --global --add safe.directory $(pwd) git config --global --add safe.directory $(pwd)
@@ -91,13 +88,13 @@ jobs:
- name: Download LLVM (MacOS Intel) - name: Download LLVM (MacOS Intel)
if: matrix.os == 'macos-13' if: matrix.os == 'macos-13'
run: | run: |
brew install llvm@17 brew install llvm@17 lua@5.4
echo "/usr/local/opt/llvm@17/bin" >> $GITHUB_PATH echo "/usr/local/opt/llvm@17/bin" >> $GITHUB_PATH
- name: Download LLVM (MacOS ARM) - name: Download LLVM (MacOS ARM)
if: matrix.os == 'macos-14' if: matrix.os == 'macos-14'
run: | run: |
brew install llvm@17 wasmtime brew install llvm@17 wasmtime lua@5.4
echo "/opt/homebrew/opt/llvm@17/bin" >> $GITHUB_PATH echo "/opt/homebrew/opt/llvm@17/bin" >> $GITHUB_PATH
- name: Build Odin - name: Build Odin
@@ -207,6 +204,7 @@ jobs:
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
copy vendor\lua\5.4\windows\*.dll .
odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false
- name: Odin internals tests - name: Odin internals tests
shell: cmd shell: cmd
+1 -32
View File
@@ -24,38 +24,6 @@ bld/
![Cc]ore/[Ll]og/ ![Cc]ore/[Ll]og/
tests/documentation/verify/ tests/documentation/verify/
tests/documentation/all.odin-doc tests/documentation/all.odin-doc
tests/internal/test_map
tests/internal/test_pow
tests/internal/test_rtti
tests/core/test_base64
tests/core/test_cbor
tests/core/test_core_compress
tests/core/test_core_container
tests/core/test_core_filepath
tests/core/test_core_fmt
tests/core/test_core_i18n
tests/core/test_core_image
tests/core/test_core_libc
tests/core/test_core_match
tests/core/test_core_math
tests/core/test_core_net
tests/core/test_core_os_exit
tests/core/test_core_reflect
tests/core/test_core_strings
tests/core/test_core_time
tests/core/test_crypto
tests/core/test_hash
tests/core/test_hex
tests/core/test_hxa
tests/core/test_json
tests/core/test_linalg_glsl_math
tests/core/test_noise
tests/core/test_varint
tests/core/test_xml
tests/core/test_core_slice
tests/core/test_core_thread
tests/core/test_core_runtime
tests/vendor/vendor_botan
# Visual Studio 2015 cache/options directory # Visual Studio 2015 cache/options directory
.vs/ .vs/
# Visual Studio Code options directory # Visual Studio Code options directory
@@ -63,6 +31,7 @@ tests/vendor/vendor_botan
# Uncomment if you have tasks that create the project's static files in wwwroot # Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/ #wwwroot/
demo demo
benchmark
# MSTest test Results # MSTest test Results
[Tt]est[Rr]esult*/ [Tt]est[Rr]esult*/
+6 -3
View File
@@ -38,9 +38,12 @@ count_leading_zeros :: proc(x: $T) -> T where type_is_integer(T) || type_is_sim
reverse_bits :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) --- reverse_bits :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) ---
byte_swap :: proc(x: $T) -> T where type_is_integer(T) || type_is_float(T) --- byte_swap :: proc(x: $T) -> T where type_is_integer(T) || type_is_float(T) ---
overflow_add :: proc(lhs, rhs: $T) -> (T, bool) --- overflow_add :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) #optional_ok ---
overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) --- overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) #optional_ok ---
overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) --- overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) #optional_ok ---
add_sat :: proc(lhs, rhs: $T) -> T where type_is_integer(T) ---
sub_sat :: proc(lhs, rhs: $T) -> T where type_is_integer(T) ---
sqrt :: proc(x: $T) -> T where type_is_float(T) || (type_is_simd_vector(T) && type_is_float(type_elem_type(T))) --- sqrt :: proc(x: $T) -> T where type_is_float(T) || (type_is_simd_vector(T) && type_is_float(type_elem_type(T))) ---
+28 -18
View File
@@ -66,7 +66,7 @@ Type_Info_Named :: struct {
name: string, name: string,
base: ^Type_Info, base: ^Type_Info,
pkg: string, pkg: string,
loc: Source_Code_Location, loc: ^Source_Code_Location,
} }
Type_Info_Integer :: struct {signed: bool, endianness: Platform_Endianness} Type_Info_Integer :: struct {signed: bool, endianness: Platform_Endianness}
Type_Info_Rune :: struct {} Type_Info_Rune :: struct {}
@@ -112,23 +112,32 @@ Type_Info_Parameters :: struct { // Only used for procedures parameters and resu
} }
Type_Info_Tuple :: Type_Info_Parameters // Will be removed eventually Type_Info_Tuple :: Type_Info_Parameters // Will be removed eventually
Type_Info_Struct :: struct { Type_Info_Struct_Flags :: distinct bit_set[Type_Info_Struct_Flag; u8]
types: []^Type_Info, Type_Info_Struct_Flag :: enum u8 {
names: []string, packed = 0,
offsets: []uintptr, raw_union = 1,
usings: []bool, no_copy = 2,
tags: []string, align = 3,
is_packed: bool, }
is_raw_union: bool,
is_no_copy: bool,
custom_align: bool,
equal: Equal_Proc, // set only when the struct has .Comparable set but does not have .Simple_Compare set Type_Info_Struct :: struct {
// Slice these with `field_count`
types: [^]^Type_Info `fmt:"v,field_count"`,
names: [^]string `fmt:"v,field_count"`,
offsets: [^]uintptr `fmt:"v,field_count"`,
usings: [^]bool `fmt:"v,field_count"`,
tags: [^]string `fmt:"v,field_count"`,
field_count: i32,
flags: Type_Info_Struct_Flags,
// These are only set iff this structure is an SOA structure // These are only set iff this structure is an SOA structure
soa_kind: Type_Info_Struct_Soa_Kind, soa_kind: Type_Info_Struct_Soa_Kind,
soa_len: i32,
soa_base_type: ^Type_Info, soa_base_type: ^Type_Info,
soa_len: int,
equal: Equal_Proc, // set only when the struct has .Comparable set but does not have .Simple_Compare set
} }
Type_Info_Union :: struct { Type_Info_Union :: struct {
variants: []^Type_Info, variants: []^Type_Info,
@@ -187,11 +196,12 @@ Type_Info_Soa_Pointer :: struct {
} }
Type_Info_Bit_Field :: struct { Type_Info_Bit_Field :: struct {
backing_type: ^Type_Info, backing_type: ^Type_Info,
names: []string, names: [^]string `fmt:"v,field_count"`,
types: []^Type_Info, types: [^]^Type_Info `fmt:"v,field_count"`,
bit_sizes: []uintptr, bit_sizes: [^]uintptr `fmt:"v,field_count"`,
bit_offsets: []uintptr, bit_offsets: [^]uintptr `fmt:"v,field_count"`,
tags: []string, tags: [^]string `fmt:"v,field_count"`,
field_count: int,
} }
Type_Info_Flag :: enum u8 { Type_Info_Flag :: enum u8 {
+97 -92
View File
@@ -333,16 +333,23 @@ make_dynamic_array_len :: proc($T: typeid/[dynamic]$E, #any_int len: int, alloca
// Note: Prefer using the procedure group `make`. // Note: Prefer using the procedure group `make`.
@(builtin, require_results) @(builtin, require_results)
make_dynamic_array_len_cap :: proc($T: typeid/[dynamic]$E, #any_int len: int, #any_int cap: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { make_dynamic_array_len_cap :: proc($T: typeid/[dynamic]$E, #any_int len: int, #any_int cap: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
make_dynamic_array_error_loc(loc, len, cap) err = _make_dynamic_array_len_cap((^Raw_Dynamic_Array)(&array), size_of(E), align_of(E), len, cap, allocator, loc)
array.allocator = allocator // initialize allocator before just in case it fails to allocate any memory
data := mem_alloc_bytes(size_of(E)*cap, align_of(E), allocator, loc) or_return
s := Raw_Dynamic_Array{raw_data(data), len, cap, allocator}
if data == nil && size_of(E) != 0 {
s.len, s.cap = 0, 0
}
array = transmute(T)s
return return
} }
@(require_results)
_make_dynamic_array_len_cap :: proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, #any_int len: int, #any_int cap: int, allocator := context.allocator, loc := #caller_location) -> (err: Allocator_Error) {
make_dynamic_array_error_loc(loc, len, cap)
array.allocator = allocator // initialize allocator before just in case it fails to allocate any memory
data := mem_alloc_bytes(size_of_elem*cap, align_of_elem, allocator, loc) or_return
use_zero := data == nil && size_of_elem != 0
array.data = raw_data(data)
array.len = 0 if use_zero else len
array.cap = 0 if use_zero else cap
array.allocator = allocator
return
}
// `make_map` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. // `make_map` allocates and initializes a dynamic array. 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. // Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it.
// //
@@ -440,107 +447,103 @@ delete_key :: proc(m: ^$T/map[$K]$V, key: K) -> (deleted_key: K, deleted_value:
return return
} }
_append_elem :: #force_inline proc(array: ^$T/[dynamic]$E, arg: E, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { _append_elem :: #force_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, arg_ptr: rawptr, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
if array == nil { if array == nil {
return 0, nil return
} }
when size_of(E) == 0 {
array := (^Raw_Dynamic_Array)(array) if array.cap < array.len+1 {
array.len += 1
return 1, nil
} else {
if cap(array) < len(array)+1 {
// Same behavior as _append_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY. // Same behavior as _append_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY.
cap := 2 * cap(array) + DEFAULT_DYNAMIC_ARRAY_CAPACITY cap := 2 * array.cap + DEFAULT_DYNAMIC_ARRAY_CAPACITY
// do not 'or_return' here as it could be a partial success // do not 'or_return' here as it could be a partial success
if should_zero { err = _reserve_dynamic_array(array, size_of_elem, align_of_elem, cap, should_zero, loc)
err = reserve(array, cap, loc)
} else {
err = non_zero_reserve(array, cap, loc)
} }
} if array.cap-array.len > 0 {
if cap(array)-len(array) > 0 { data := ([^]byte)(array.data)
a := (^Raw_Dynamic_Array)(array)
when size_of(E) != 0 {
data := ([^]E)(a.data)
assert(data != nil, loc=loc) assert(data != nil, loc=loc)
data[a.len] = arg data = data[array.len*size_of_elem:]
} intrinsics.mem_copy_non_overlapping(data, arg_ptr, size_of_elem)
a.len += 1 array.len += 1
return 1, err n = 1
}
return 0, err
} }
return
} }
@builtin @builtin
append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
return _append_elem(array, arg, true, loc=loc) when size_of(E) == 0 {
(^Raw_Dynamic_Array)(array).len += 1
return 1, nil
} else {
arg := arg
return _append_elem((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), &arg, true, loc=loc)
}
} }
@builtin @builtin
non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
return _append_elem(array, arg, false, loc=loc) when size_of(E) == 0 {
(^Raw_Dynamic_Array)(array).len += 1
return 1, nil
} else {
arg := arg
return _append_elem((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), &arg, false, loc=loc)
}
} }
_append_elems :: #force_inline proc(array: ^$T/[dynamic]$E, should_zero: bool, loc := #caller_location, args: ..E) -> (n: int, err: Allocator_Error) #optional_allocator_error { _append_elems :: #force_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, should_zero: bool, loc := #caller_location, args: rawptr, arg_len: int) -> (n: int, err: Allocator_Error) #optional_allocator_error {
if array == nil { if array == nil {
return 0, nil return 0, nil
} }
arg_len := len(args)
if arg_len <= 0 { if arg_len <= 0 {
return 0, nil return 0, nil
} }
when size_of(E) == 0 { if array.cap < array.len+arg_len {
array := (^Raw_Dynamic_Array)(array) cap := 2 * array.cap + max(DEFAULT_DYNAMIC_ARRAY_CAPACITY, arg_len)
array.len += arg_len
return arg_len, nil
} else {
if cap(array) < len(array)+arg_len {
cap := 2 * cap(array) + max(DEFAULT_DYNAMIC_ARRAY_CAPACITY, arg_len)
// do not 'or_return' here as it could be a partial success // do not 'or_return' here as it could be a partial success
if should_zero { err = _reserve_dynamic_array(array, size_of_elem, align_of_elem, cap, should_zero, loc)
err = reserve(array, cap, loc)
} else {
err = non_zero_reserve(array, cap, loc)
} }
} arg_len := arg_len
arg_len = min(cap(array)-len(array), arg_len) arg_len = min(array.cap-array.len, arg_len)
if arg_len > 0 { if arg_len > 0 {
a := (^Raw_Dynamic_Array)(array) data := ([^]byte)(array.data)
when size_of(E) != 0 {
data := ([^]E)(a.data)
assert(data != nil, loc=loc) assert(data != nil, loc=loc)
intrinsics.mem_copy(&data[a.len], raw_data(args), size_of(E) * arg_len) data = data[array.len*size_of_elem:]
} intrinsics.mem_copy(data, args, size_of_elem * arg_len) // must be mem_copy (overlapping)
a.len += arg_len array.len += arg_len
} }
return arg_len, err return arg_len, err
}
} }
@builtin @builtin
append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
return _append_elems(array, true, loc, ..args) when size_of(E) == 0 {
a := (^Raw_Dynamic_Array)(array)
a.len += len(args)
return len(args), nil
} else {
return _append_elems((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), true, loc, raw_data(args), len(args))
}
} }
@builtin @builtin
non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
return _append_elems(array, false, loc, ..args) when size_of(E) == 0 {
a := (^Raw_Dynamic_Array)(array)
a.len += len(args)
return len(args), nil
} else {
return _append_elems((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), false, loc, raw_data(args), len(args))
}
} }
// The append_string built-in procedure appends a string to the end of a [dynamic]u8 like type // The append_string built-in procedure appends a string to the end of a [dynamic]u8 like type
_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { _append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
args := transmute([]E)arg return _append_elems((^Raw_Dynamic_Array)(array), 1, 1, should_zero, loc, raw_data(arg), len(arg))
if should_zero {
return append_elems(array, ..args, loc=loc)
} else {
return non_zero_append_elems(array, ..args, loc=loc)
}
} }
@builtin @builtin
@@ -679,7 +682,7 @@ assign_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #calle
@builtin @builtin
assign_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { assign_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
new_size := index + len(args) new_size := index + len(args)
if len(args) == 0 { if len(args) == 0 {
ok = true ok = true
@@ -729,11 +732,10 @@ clear_dynamic_array :: proc "contextless" (array: ^$T/[dynamic]$E) {
// `reserve_dynamic_array` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`). // `reserve_dynamic_array` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`).
// //
// Note: Prefer the procedure group `reserve`. // Note: Prefer the procedure group `reserve`.
_reserve_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, capacity: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { _reserve_dynamic_array :: #force_inline proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, capacity: int, should_zero: bool, loc := #caller_location) -> Allocator_Error {
if array == nil { if a == nil {
return nil return nil
} }
a := (^Raw_Dynamic_Array)(array)
if capacity <= a.cap { if capacity <= a.cap {
return nil return nil
@@ -744,15 +746,15 @@ _reserve_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, capacity: i
} }
assert(a.allocator.procedure != nil) assert(a.allocator.procedure != nil)
old_size := a.cap * size_of(E) old_size := a.cap * size_of_elem
new_size := capacity * size_of(E) new_size := capacity * size_of_elem
allocator := a.allocator allocator := a.allocator
new_data: []byte new_data: []byte
if should_zero { if should_zero {
new_data = mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return new_data = mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return
} else { } else {
new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return 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 { if new_data == nil && new_size > 0 {
return .Out_Of_Memory return .Out_Of_Memory
@@ -765,26 +767,23 @@ _reserve_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, capacity: i
@builtin @builtin
reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error { reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error {
return _reserve_dynamic_array(array, capacity, true, loc) return _reserve_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), capacity, true, loc)
} }
@builtin @builtin
non_zero_reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error { non_zero_reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error {
return _reserve_dynamic_array(array, capacity, false, loc) return _reserve_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), capacity, false, loc)
} }
// `resize_dynamic_array` will try to resize memory of a passed dynamic array or map to the requested element count (setting the `len`, and possibly `cap`).
// _resize_dynamic_array :: #force_inline proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, length: int, should_zero: bool, loc := #caller_location) -> Allocator_Error {
// Note: Prefer the procedure group `resize` if a == nil {
_resize_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, length: int, should_zero: bool, loc := #caller_location) -> Allocator_Error {
if array == nil {
return nil return nil
} }
a := (^Raw_Dynamic_Array)(array)
if length <= a.cap { if length <= a.cap {
if should_zero && a.len < length { if should_zero && a.len < length {
intrinsics.mem_zero(([^]E)(a.data)[a.len:], (length-a.len)*size_of(E)) intrinsics.mem_zero(([^]byte)(a.data)[a.len*size_of_elem:], (length-a.len)*size_of_elem)
} }
a.len = max(length, 0) a.len = max(length, 0)
return nil return nil
@@ -795,15 +794,15 @@ _resize_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, length: int,
} }
assert(a.allocator.procedure != nil) assert(a.allocator.procedure != nil)
old_size := a.cap * size_of(E) old_size := a.cap * size_of_elem
new_size := length * size_of(E) new_size := length * size_of_elem
allocator := a.allocator allocator := a.allocator
new_data : []byte new_data : []byte
if should_zero { if should_zero {
new_data = mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return new_data = mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return
} else { } else {
new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return 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 { if new_data == nil && new_size > 0 {
return .Out_Of_Memory return .Out_Of_Memory
@@ -815,14 +814,17 @@ _resize_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, length: int,
return nil return nil
} }
// `resize_dynamic_array` will try to resize memory of a passed dynamic array or map to the requested element count (setting the `len`, and possibly `cap`).
//
// Note: Prefer the procedure group `resize`
@builtin @builtin
resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error { resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error {
return _resize_dynamic_array(array, length, true, loc=loc) return _resize_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), length, true, loc=loc)
} }
@builtin @builtin
non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error { non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error {
return _resize_dynamic_array(array, length, false, loc=loc) return _resize_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), length, false, loc=loc)
} }
/* /*
@@ -837,10 +839,13 @@ non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: i
Note: Prefer the procedure group `shrink` Note: Prefer the procedure group `shrink`
*/ */
shrink_dynamic_array :: proc(array: ^$T/[dynamic]$E, new_cap := -1, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { shrink_dynamic_array :: proc(array: ^$T/[dynamic]$E, new_cap := -1, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) {
if array == nil { return _shrink_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), new_cap, loc)
}
_shrink_dynamic_array :: proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, new_cap := -1, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) {
if a == nil {
return return
} }
a := (^Raw_Dynamic_Array)(array)
new_cap := new_cap if new_cap >= 0 else a.len new_cap := new_cap if new_cap >= 0 else a.len
@@ -853,10 +858,10 @@ shrink_dynamic_array :: proc(array: ^$T/[dynamic]$E, new_cap := -1, loc := #call
} }
assert(a.allocator.procedure != nil) assert(a.allocator.procedure != nil)
old_size := a.cap * size_of(E) old_size := a.cap * size_of_elem
new_size := new_cap * size_of(E) new_size := new_cap * size_of_elem
new_data := mem_resize(a.data, old_size, new_size, align_of(E), a.allocator, loc) or_return new_data := mem_resize(a.data, old_size, new_size, align_of_elem, a.allocator, loc) or_return
a.data = raw_data(new_data) a.data = raw_data(new_data)
a.len = min(new_cap, a.len) a.len = min(new_cap, a.len)
+1 -1
View File
@@ -352,7 +352,7 @@ non_zero_append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast args
} }
_append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, zero_memory: bool, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { _append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, zero_memory: bool, #no_broadcast args: []E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
if array == nil { if array == nil {
return return
} }
+6 -6
View File
@@ -577,7 +577,7 @@ map_grow_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Inf
@(require_results) @(require_results)
map_reserve_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, new_capacity: uintptr, loc := #caller_location) -> Allocator_Error { map_reserve_dynamic :: #force_no_inline proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, new_capacity: uintptr, loc := #caller_location) -> Allocator_Error {
@(require_results) @(require_results)
ceil_log2 :: #force_inline proc "contextless" (x: uintptr) -> uintptr { ceil_log2 :: #force_inline proc "contextless" (x: uintptr) -> uintptr {
z := intrinsics.count_leading_zeros(x) z := intrinsics.count_leading_zeros(x)
@@ -641,7 +641,7 @@ map_reserve_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_
@(require_results) @(require_results)
map_shrink_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { map_shrink_dynamic :: #force_no_inline proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) {
if m.allocator.procedure == nil { if m.allocator.procedure == nil {
m.allocator = context.allocator m.allocator = context.allocator
} }
@@ -688,7 +688,7 @@ map_shrink_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_I
} }
@(require_results) @(require_results)
map_free_dynamic :: proc "odin" (m: Raw_Map, info: ^Map_Info, loc := #caller_location) -> Allocator_Error { map_free_dynamic :: #force_no_inline proc "odin" (m: Raw_Map, info: ^Map_Info, loc := #caller_location) -> Allocator_Error {
ptr := rawptr(map_data(m)) ptr := rawptr(map_data(m))
size := int(map_total_allocation_size(uintptr(map_cap(m)), info)) size := int(map_total_allocation_size(uintptr(map_cap(m)), info))
err := mem_free_with_size(ptr, size, m.allocator, loc) err := mem_free_with_size(ptr, size, m.allocator, loc)
@@ -700,7 +700,7 @@ map_free_dynamic :: proc "odin" (m: Raw_Map, info: ^Map_Info, loc := #caller_loc
} }
@(require_results) @(require_results)
map_lookup_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (index: uintptr, ok: bool) { map_lookup_dynamic :: #force_no_inline proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (index: uintptr, ok: bool) {
if map_len(m) == 0 { if map_len(m) == 0 {
return 0, false return 0, false
} }
@@ -723,7 +723,7 @@ map_lookup_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info,
} }
} }
@(require_results) @(require_results)
map_exists_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (ok: bool) { map_exists_dynamic :: #force_no_inline proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (ok: bool) {
if map_len(m) == 0 { if map_len(m) == 0 {
return false return false
} }
@@ -749,7 +749,7 @@ map_exists_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info,
@(require_results) @(require_results)
map_erase_dynamic :: #force_inline proc "contextless" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (old_k, old_v: uintptr, ok: bool) { map_erase_dynamic :: #force_no_inline proc "contextless" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (old_k, old_v: uintptr, ok: bool) {
index := map_lookup_dynamic(m^, info, k) or_return index := map_lookup_dynamic(m^, info, k) or_return
ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info) ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info)
hs[index] |= TOMBSTONE_MASK hs[index] |= TOMBSTONE_MASK
+6 -5
View File
@@ -401,15 +401,16 @@ print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) {
} }
print_string("struct ") print_string("struct ")
if info.is_packed { print_string("#packed ") } if .packed in info.flags { print_string("#packed ") }
if info.is_raw_union { print_string("#raw_union ") } if .raw_union in info.flags { print_string("#raw_union ") }
if info.custom_align { if .no_copy in info.flags { print_string("#no_copy ") }
if .align in info.flags {
print_string("#align(") print_string("#align(")
print_u64(u64(ti.align)) print_u64(u64(ti.align))
print_string(") ") print_string(") ")
} }
print_byte('{') print_byte('{')
for name, i in info.names { for name, i in info.names[:info.field_count] {
if i > 0 { print_string(", ") } if i > 0 { print_string(", ") }
print_string(name) print_string(name)
print_string(": ") print_string(": ")
@@ -469,7 +470,7 @@ print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) {
print_string("bit_field ") print_string("bit_field ")
print_type(info.backing_type) print_type(info.backing_type)
print_string(" {") print_string(" {")
for name, i in info.names { for name, i in info.names[:info.field_count] {
if i > 0 { print_string(", ") } if i > 0 { print_string(", ") }
print_string(name) print_string(name)
print_string(": ") print_string(": ")
+2 -1
View File
@@ -297,7 +297,8 @@ lock :: proc(a: ^WASM_Allocator) {
return return
} }
assert(intrinsics.wasm_memory_atomic_wait32((^u32)(&a.mu), u32(new_state), -1) != 0) ret := intrinsics.wasm_memory_atomic_wait32((^u32)(&a.mu), u32(new_state), -1)
assert(ret != 0)
intrinsics.cpu_relax() intrinsics.cpu_relax()
} }
} }
+25
View File
@@ -1167,3 +1167,28 @@ fields_proc :: proc(s: []byte, f: proc(rune) -> bool, allocator := context.alloc
return subslices[:] return subslices[:]
} }
// alias returns true iff a and b have a non-zero length, and any part of
// a overlaps with b.
alias :: proc "contextless" (a, b: []byte) -> bool {
a_len, b_len := len(a), len(b)
if a_len == 0 || b_len == 0 {
return false
}
a_start, b_start := uintptr(raw_data(a)), uintptr(raw_data(b))
a_end, b_end := a_start + uintptr(a_len-1), b_start + uintptr(b_len-1)
return a_start <= b_end && b_start <= a_end
}
// alias_inexactly returns true iff a and b have a non-zero length,
// the base pointer of a and b are NOT equal, and any part of a overlaps
// with b (ie: `alias(a, b)` with an exception that returns false for
// `a == b`, `b = a[:len(a)-69]` and similar conditions).
alias_inexactly :: proc "contextless" (a, b: []byte) -> bool {
if raw_data(a) == raw_data(b) {
return false
}
return alias(a, b)
}
+1 -1
View File
@@ -235,7 +235,7 @@ allocate_huffman_table :: proc(allocator := context.allocator) -> (z: ^Huffman_T
} }
@(optimization_mode="favor_size") @(optimization_mode="favor_size")
build_huffman :: proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) { build_huffman :: #force_no_inline proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) {
sizes: [HUFFMAN_MAX_BITS+1]int sizes: [HUFFMAN_MAX_BITS+1]int
next_code: [HUFFMAN_MAX_BITS+1]int next_code: [HUFFMAN_MAX_BITS+1]int
+46
View File
@@ -0,0 +1,46 @@
/*
Package list implements an intrusive doubly-linked list.
An intrusive container requires a `Node` to be embedded in your own structure, like this:
My_String :: struct {
node: list.Node,
value: string,
}
Embedding the members of a `list.Node` in your structure with the `using` keyword is also allowed:
My_String :: struct {
using node: list.Node,
value: string,
}
Here is a full example:
package test
import "core:fmt"
import "core:container/intrusive/list"
main :: proc() {
l: list.List
one := My_String{value="Hello"}
two := My_String{value="World"}
list.push_back(&l, &one.node)
list.push_back(&l, &two.node)
iter := list.iterator_head(l, My_String, "node")
for s in list.iterate_next(&iter) {
fmt.println(s.value)
}
}
My_String :: struct {
node: list.Node,
value: string,
}
*/
package container_intrusive_list
@@ -18,11 +18,18 @@ List :: struct {
tail: ^Node, tail: ^Node,
} }
// The list link you must include in your own structure.
Node :: struct { Node :: struct {
prev, next: ^Node, prev, next: ^Node,
} }
/*
Inserts a new element at the front of the list with O(1) time complexity.
**Inputs**
- list: The container list
- node: The node member of the user-defined element structure
*/
push_front :: proc "contextless" (list: ^List, node: ^Node) { push_front :: proc "contextless" (list: ^List, node: ^Node) {
if list.head != nil { if list.head != nil {
list.head.prev = node list.head.prev = node
@@ -33,7 +40,13 @@ push_front :: proc "contextless" (list: ^List, node: ^Node) {
node.prev, node.next = nil, nil node.prev, node.next = nil, nil
} }
} }
/*
Inserts a new element at the back of the list with O(1) time complexity.
**Inputs**
- list: The container list
- node: The node member of the user-defined element structure
*/
push_back :: proc "contextless" (list: ^List, node: ^Node) { push_back :: proc "contextless" (list: ^List, node: ^Node) {
if list.tail != nil { if list.tail != nil {
list.tail.next = node list.tail.next = node
@@ -45,6 +58,13 @@ push_back :: proc "contextless" (list: ^List, node: ^Node) {
} }
} }
/*
Removes an element from a list with O(1) time complexity.
**Inputs**
- list: The container list
- node: The node member of the user-defined element structure to be removed
*/
remove :: proc "contextless" (list: ^List, node: ^Node) { remove :: proc "contextless" (list: ^List, node: ^Node) {
if node != nil { if node != nil {
if node.next != nil { if node.next != nil {
@@ -61,7 +81,13 @@ remove :: proc "contextless" (list: ^List, node: ^Node) {
} }
} }
} }
/*
Removes from the given list all elements that satisfy a condition with O(N) time complexity.
**Inputs**
- list: The container list
- to_erase: The condition procedure. It should return `true` if a node should be removed, `false` otherwise
*/
remove_by_proc :: proc(list: ^List, to_erase: proc(^Node) -> bool) { remove_by_proc :: proc(list: ^List, to_erase: proc(^Node) -> bool) {
for node := list.head; node != nil; { for node := list.head; node != nil; {
next := node.next next := node.next
@@ -82,7 +108,13 @@ remove_by_proc :: proc(list: ^List, to_erase: proc(^Node) -> bool) {
node = next node = next
} }
} }
/*
Removes from the given list all elements that satisfy a condition with O(N) time complexity.
**Inputs**
- list: The container list
- to_erase: The _contextless_ condition procedure. It should return `true` if a node should be removed, `false` otherwise
*/
remove_by_proc_contextless :: proc(list: ^List, to_erase: proc "contextless" (^Node) -> bool) { remove_by_proc_contextless :: proc(list: ^List, to_erase: proc "contextless" (^Node) -> bool) {
for node := list.head; node != nil; { for node := list.head; node != nil; {
next := node.next next := node.next
@@ -104,12 +136,26 @@ remove_by_proc_contextless :: proc(list: ^List, to_erase: proc "contextless" (^N
} }
} }
/*
Checks whether the given list does not contain any element.
**Inputs**
- list: The container list
**Returns** `true` if `list` is empty, `false` otherwise
*/
is_empty :: proc "contextless" (list: ^List) -> bool { is_empty :: proc "contextless" (list: ^List) -> bool {
return list.head == nil return list.head == nil
} }
/*
Removes and returns the element at the front of the list with O(1) time complexity.
**Inputs**
- list: The container list
**Returns** The node member of the user-defined element structure, or `nil` if the list is empty
*/
pop_front :: proc "contextless" (list: ^List) -> ^Node { pop_front :: proc "contextless" (list: ^List) -> ^Node {
link := list.head link := list.head
if link == nil { if link == nil {
@@ -130,6 +176,14 @@ pop_front :: proc "contextless" (list: ^List) -> ^Node {
return link return link
} }
/*
Removes and returns the element at the back of the list with O(1) time complexity.
**Inputs**
- list: The container list
**Returns** The node member of the user-defined element structure, or `nil` if the list is empty
*/
pop_back :: proc "contextless" (list: ^List) -> ^Node { pop_back :: proc "contextless" (list: ^List) -> ^Node {
link := list.tail link := list.tail
if link == nil { if link == nil {
@@ -151,29 +205,102 @@ pop_back :: proc "contextless" (list: ^List) -> ^Node {
} }
Iterator :: struct($T: typeid) { Iterator :: struct($T: typeid) {
curr: ^Node, curr: ^Node,
offset: uintptr, offset: uintptr,
} }
/*
Creates an iterator pointing at the head of the given list. For an example, see `iterate_next`.
**Inputs**
- list: The container list
- T: The type of the list's elements
- field_name: The name of the node field in the `T` structure
**Returns** An iterator pointing at the head of `list`
*/
iterator_head :: proc "contextless" (list: List, $T: typeid, $field_name: string) -> Iterator(T) iterator_head :: proc "contextless" (list: List, $T: typeid, $field_name: string) -> Iterator(T)
where intrinsics.type_has_field(T, field_name), where intrinsics.type_has_field(T, field_name),
intrinsics.type_field_type(T, field_name) == Node { intrinsics.type_field_type(T, field_name) == Node {
return {list.head, offset_of_by_string(T, field_name)} return {list.head, offset_of_by_string(T, field_name)}
} }
/*
Creates an iterator pointing at the tail of the given list. For an example, see `iterate_prev`.
**Inputs**
- list: The container list
- T: The type of the list's elements
- field_name: The name of the node field in the `T` structure
**Returns** An iterator pointing at the tail of `list`
*/
iterator_tail :: proc "contextless" (list: List, $T: typeid, $field_name: string) -> Iterator(T) iterator_tail :: proc "contextless" (list: List, $T: typeid, $field_name: string) -> Iterator(T)
where intrinsics.type_has_field(T, field_name), where intrinsics.type_has_field(T, field_name),
intrinsics.type_field_type(T, field_name) == Node { intrinsics.type_field_type(T, field_name) == Node {
return {list.tail, offset_of_by_string(T, field_name)} return {list.tail, offset_of_by_string(T, field_name)}
} }
/*
Creates an iterator pointing at the specified node of a list.
**Inputs**
- node: a list node
- T: The type of the list's elements
- field_name: The name of the node field in the `T` structure
**Returns** An iterator pointing at `node`
*/
iterator_from_node :: proc "contextless" (node: ^Node, $T: typeid, $field_name: string) -> Iterator(T) iterator_from_node :: proc "contextless" (node: ^Node, $T: typeid, $field_name: string) -> Iterator(T)
where intrinsics.type_has_field(T, field_name), where intrinsics.type_has_field(T, field_name),
intrinsics.type_field_type(T, field_name) == Node { intrinsics.type_field_type(T, field_name) == Node {
return {node, offset_of_by_string(T, field_name)} return {node, offset_of_by_string(T, field_name)}
} }
/*
Retrieves the next element in a list and advances the iterator.
**Inputs**
- it: The iterator
**Returns**
- ptr: The next list element
- ok: `true` if the element is valid (the iterator could advance), `false` otherwise
Example:
import "core:fmt"
import "core:container/intrusive/list"
iterate_next_example :: proc() {
l: list.List
one := My_Struct{value=1}
two := My_Struct{value=2}
list.push_back(&l, &one.node)
list.push_back(&l, &two.node)
it := list.iterator_head(l, My_Struct, "node")
for num in list.iterate_next(&it) {
fmt.println(num.value)
}
}
My_Struct :: struct {
node : list.Node,
value: int,
}
Output:
1
2
*/
iterate_next :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) { iterate_next :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) {
node := it.curr node := it.curr
if node == nil { if node == nil {
@@ -183,7 +310,47 @@ iterate_next :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) {
return (^T)(uintptr(node) - it.offset), true return (^T)(uintptr(node) - it.offset), true
} }
/*
Retrieves the previous element in a list and recede the iterator.
**Inputs**
- it: The iterator
**Returns**
- ptr: The previous list element
- ok: `true` if the element is valid (the iterator could recede), `false` otherwise
Example:
import "core:fmt"
import "core:container/intrusive/list"
iterate_next_example :: proc() {
l: list.List
one := My_Struct{value=1}
two := My_Struct{value=2}
list.push_back(&l, &one.node)
list.push_back(&l, &two.node)
it := list.iterator_tail(l, My_Struct, "node")
for num in list.iterate_prev(&it) {
fmt.println(num.value)
}
}
My_Struct :: struct {
node : list.Node,
value: int,
}
Output:
2
1
*/
iterate_prev :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) { iterate_prev :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) {
node := it.curr node := it.curr
if node == nil { if node == nil {
+2 -2
View File
@@ -95,11 +95,11 @@ front_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
} }
back :: proc(q: ^$Q/Queue($T)) -> T { back :: proc(q: ^$Q/Queue($T)) -> T {
idx := (q.offset+uint(q.len))%builtin.len(q.data) idx := (q.offset+uint(q.len - 1))%builtin.len(q.data)
return q.data[idx] return q.data[idx]
} }
back_ptr :: proc(q: ^$Q/Queue($T)) -> ^T { back_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
idx := (q.offset+uint(q.len))%builtin.len(q.data) idx := (q.offset+uint(q.len - 1))%builtin.len(q.data)
return &q.data[idx] return &q.data[idx]
} }
-10
View File
@@ -9,7 +9,6 @@ STRIDE :: 4
Context :: struct { Context :: struct {
_sk_exp: [120]u64, _sk_exp: [120]u64,
_num_rounds: int, _num_rounds: int,
_is_initialized: bool,
} }
// init initializes a context for AES with the provided key. // init initializes a context for AES with the provided key.
@@ -18,13 +17,10 @@ init :: proc(ctx: ^Context, key: []byte) {
ctx._num_rounds = keysched(skey[:], key) ctx._num_rounds = keysched(skey[:], key)
skey_expand(ctx._sk_exp[:], skey[:], ctx._num_rounds) skey_expand(ctx._sk_exp[:], skey[:], ctx._num_rounds)
ctx._is_initialized = true
} }
// encrypt_block sets `dst` to `AES-ECB-Encrypt(src)`. // encrypt_block sets `dst` to `AES-ECB-Encrypt(src)`.
encrypt_block :: proc(ctx: ^Context, dst, src: []byte) { encrypt_block :: proc(ctx: ^Context, dst, src: []byte) {
assert(ctx._is_initialized)
q: [8]u64 q: [8]u64
load_blockx1(&q, src) load_blockx1(&q, src)
_encrypt(&q, ctx._sk_exp[:], ctx._num_rounds) _encrypt(&q, ctx._sk_exp[:], ctx._num_rounds)
@@ -33,8 +29,6 @@ encrypt_block :: proc(ctx: ^Context, dst, src: []byte) {
// encrypt_block sets `dst` to `AES-ECB-Decrypt(src)`. // encrypt_block sets `dst` to `AES-ECB-Decrypt(src)`.
decrypt_block :: proc(ctx: ^Context, dst, src: []byte) { decrypt_block :: proc(ctx: ^Context, dst, src: []byte) {
assert(ctx._is_initialized)
q: [8]u64 q: [8]u64
load_blockx1(&q, src) load_blockx1(&q, src)
_decrypt(&q, ctx._sk_exp[:], ctx._num_rounds) _decrypt(&q, ctx._sk_exp[:], ctx._num_rounds)
@@ -43,8 +37,6 @@ decrypt_block :: proc(ctx: ^Context, dst, src: []byte) {
// encrypt_blocks sets `dst` to `AES-ECB-Encrypt(src[0], .. src[n])`. // encrypt_blocks sets `dst` to `AES-ECB-Encrypt(src[0], .. src[n])`.
encrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) { encrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) {
assert(ctx._is_initialized)
q: [8]u64 = --- q: [8]u64 = ---
src, dst := src, dst src, dst := src, dst
@@ -67,8 +59,6 @@ encrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) {
// decrypt_blocks sets dst to `AES-ECB-Decrypt(src[0], .. src[n])`. // decrypt_blocks sets dst to `AES-ECB-Decrypt(src[0], .. src[n])`.
decrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) { decrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) {
assert(ctx._is_initialized)
q: [8]u64 = --- q: [8]u64 = ---
src, dst := src, dst src, dst := src, dst
+43
View File
@@ -0,0 +1,43 @@
//+build amd64
package aes_hw_intel
import "core:sys/info"
// is_supporte returns true iff hardware accelerated AES
// is supported.
is_supported :: proc "contextless" () -> bool {
features, ok := info.cpu_features.?
if !ok {
return false
}
// Note: Everything with AES-NI and PCLMULQDQ has support for
// the required SSE extxtensions.
req_features :: info.CPU_Features{
.sse2,
.ssse3,
.sse41,
.aes,
.pclmulqdq,
}
return features >= req_features
}
// Context is a keyed AES (ECB) instance.
Context :: struct {
// Note: The ideal thing to do is for the expanded round keys to be
// arrays of `__m128i`, however that implies alignment (or using AVX).
//
// All the people using e-waste processors that don't support an
// insturction set that has been around for over 10 years are why
// we can't have nice things.
_sk_exp_enc: [15][16]byte,
_sk_exp_dec: [15][16]byte,
_num_rounds: int,
}
// init initializes a context for AES with the provided key.
init :: proc(ctx: ^Context, key: []byte) {
keysched(ctx, key)
}
+281
View File
@@ -0,0 +1,281 @@
// 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.
//+build amd64
package aes_hw_intel
import "base:intrinsics"
import "core:crypto/_aes"
import "core:simd"
import "core:simd/x86"
@(private = "file")
GHASH_STRIDE_HW :: 4
@(private = "file")
GHASH_STRIDE_BYTES_HW :: GHASH_STRIDE_HW * _aes.GHASH_BLOCK_SIZE
// GHASH is defined over elements of GF(2^128) with "full little-endian"
// representation: leftmost byte is least significant, and, within each
// byte, leftmost _bit_ is least significant. The natural ordering in
// x86 is "mixed little-endian": bytes are ordered from least to most
// significant, but bits within a byte are in most-to-least significant
// order. Going to full little-endian representation would require
// reversing bits within each byte, which is doable but expensive.
//
// Instead, we go to full big-endian representation, by swapping bytes
// around, which is done with a single _mm_shuffle_epi8() opcode (it
// comes with SSSE3; all CPU that offer pclmulqdq also have SSSE3). We
// can use a full big-endian representation because in a carryless
// multiplication, we have a nice bit reversal property:
//
// rev_128(x) * rev_128(y) = rev_255(x * y)
//
// So by using full big-endian, we still get the right result, except
// that it is right-shifted by 1 bit. The left-shift is relatively
// inexpensive, and it can be mutualised.
//
// Since SSE2 opcodes do not have facilities for shitfting full 128-bit
// values with bit precision, we have to break down values into 64-bit
// chunks. We number chunks from 0 to 3 in left to right order.
@(private = "file")
byteswap_index := transmute(x86.__m128i)simd.i8x16{
// Note: simd.i8x16 is reverse order from x86._mm_set_epi8.
15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
}
@(private = "file", require_results, enable_target_feature = "sse2,ssse3")
byteswap :: #force_inline proc "contextless" (x: x86.__m128i) -> x86.__m128i {
return x86._mm_shuffle_epi8(x, byteswap_index)
}
// From a 128-bit value kw, compute kx as the XOR of the two 64-bit
// halves of kw (into the right half of kx; left half is unspecified),
// and return kx.
@(private = "file", require_results, enable_target_feature = "sse2")
bk :: #force_inline proc "contextless" (kw: x86.__m128i) -> x86.__m128i {
return x86._mm_xor_si128(kw, x86._mm_shuffle_epi32(kw, 0x0e))
}
// Combine two 64-bit values (k0:k1) into a 128-bit (kw) value and
// the XOR of the two values (kx), and return (kw, kx).
@(private = "file", enable_target_feature = "sse2")
pbk :: #force_inline proc "contextless" (k0, k1: x86.__m128i) -> (x86.__m128i, x86.__m128i) {
kw := x86._mm_unpacklo_epi64(k1, k0)
kx := x86._mm_xor_si128(k0, k1)
return kw, kx
}
// Left-shift by 1 bit a 256-bit value (in four 64-bit words).
@(private = "file", require_results, enable_target_feature = "sse2")
sl_256 :: #force_inline proc "contextless" (x0, x1, x2, x3: x86.__m128i) -> (x86.__m128i, x86.__m128i, x86.__m128i, x86.__m128i) {
x0, x1, x2, x3 := x0, x1, x2, x3
x0 = x86._mm_or_si128(x86._mm_slli_epi64(x0, 1), x86._mm_srli_epi64(x1, 63))
x1 = x86._mm_or_si128(x86._mm_slli_epi64(x1, 1), x86._mm_srli_epi64(x2, 63))
x2 = x86._mm_or_si128(x86._mm_slli_epi64(x2, 1), x86._mm_srli_epi64(x3, 63))
x3 = x86._mm_slli_epi64(x3, 1)
return x0, x1, x2, x3
}
// Perform reduction in GF(2^128).
@(private = "file", require_results, enable_target_feature = "sse2")
reduce_f128 :: #force_inline proc "contextless" (x0, x1, x2, x3: x86.__m128i) -> (x86.__m128i, x86.__m128i) {
x0, x1, x2 := x0, x1, x2
x1 = x86._mm_xor_si128(
x1,
x86._mm_xor_si128(
x86._mm_xor_si128(
x3,
x86._mm_srli_epi64(x3, 1)),
x86._mm_xor_si128(
x86._mm_srli_epi64(x3, 2),
x86._mm_srli_epi64(x3, 7))))
x2 = x86._mm_xor_si128(
x86._mm_xor_si128(
x2,
x86._mm_slli_epi64(x3, 63)),
x86._mm_xor_si128(
x86._mm_slli_epi64(x3, 62),
x86._mm_slli_epi64(x3, 57)))
x0 = x86._mm_xor_si128(
x0,
x86._mm_xor_si128(
x86._mm_xor_si128(
x2,
x86._mm_srli_epi64(x2, 1)),
x86._mm_xor_si128(
x86._mm_srli_epi64(x2, 2),
x86._mm_srli_epi64(x2, 7))))
x1 = x86._mm_xor_si128(
x86._mm_xor_si128(
x1,
x86._mm_slli_epi64(x2, 63)),
x86._mm_xor_si128(
x86._mm_slli_epi64(x2, 62),
x86._mm_slli_epi64(x2, 57)))
return x0, x1
}
// Square value kw in GF(2^128) into (dw,dx).
@(private = "file", require_results, enable_target_feature = "sse2,pclmul")
square_f128 :: #force_inline proc "contextless" (kw: x86.__m128i) -> (x86.__m128i, x86.__m128i) {
z1 := x86._mm_clmulepi64_si128(kw, kw, 0x11)
z3 := x86._mm_clmulepi64_si128(kw, kw, 0x00)
z0 := x86._mm_shuffle_epi32(z1, 0x0E)
z2 := x86._mm_shuffle_epi32(z3, 0x0E)
z0, z1, z2, z3 = sl_256(z0, z1, z2, z3)
z0, z1 = reduce_f128(z0, z1, z2, z3)
return pbk(z0, z1)
}
// ghash calculates the GHASH of data, with the key `key`, and input `dst`
// and `data`, and stores the resulting digest in `dst`.
//
// Note: `dst` is both an input and an output, to support easy implementation
// of GCM.
@(enable_target_feature = "sse2,ssse3,pclmul")
ghash :: proc "contextless" (dst, key, data: []byte) #no_bounds_check {
if len(dst) != _aes.GHASH_BLOCK_SIZE || len(key) != _aes.GHASH_BLOCK_SIZE {
intrinsics.trap()
}
// Note: BearSSL opts to copy the remainder into a zero-filled
// 64-byte buffer. We do something slightly more simple.
// Load key and dst (h and y).
yw := intrinsics.unaligned_load((^x86.__m128i)(raw_data(dst)))
h1w := intrinsics.unaligned_load((^x86.__m128i)(raw_data(key)))
yw = byteswap(yw)
h1w = byteswap(h1w)
h1x := bk(h1w)
// Process 4 blocks at a time
buf := data
l := len(buf)
if l >= GHASH_STRIDE_BYTES_HW {
// Compute h2 = h^2
h2w, h2x := square_f128(h1w)
// Compute h3 = h^3 = h*(h^2)
t1 := x86._mm_clmulepi64_si128(h1w, h2w, 0x11)
t3 := x86._mm_clmulepi64_si128(h1w, h2w, 0x00)
t2 := x86._mm_xor_si128(
x86._mm_clmulepi64_si128(h1x, h2x, 0x00),
x86._mm_xor_si128(t1, t3))
t0 := x86._mm_shuffle_epi32(t1, 0x0E)
t1 = x86._mm_xor_si128(t1, x86._mm_shuffle_epi32(t2, 0x0E))
t2 = x86._mm_xor_si128(t2, x86._mm_shuffle_epi32(t3, 0x0E))
t0, t1, t2, t3 = sl_256(t0, t1, t2, t3)
t0, t1 = reduce_f128(t0, t1, t2, t3)
h3w, h3x := pbk(t0, t1)
// Compute h4 = h^4 = (h^2)^2
h4w, h4x := square_f128(h2w)
for l >= GHASH_STRIDE_BYTES_HW {
aw0 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(buf)))
aw1 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(buf[16:])))
aw2 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(buf[32:])))
aw3 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(buf[48:])))
aw0 = byteswap(aw0)
aw1 = byteswap(aw1)
aw2 = byteswap(aw2)
aw3 = byteswap(aw3)
buf, l = buf[GHASH_STRIDE_BYTES_HW:], l - GHASH_STRIDE_BYTES_HW
aw0 = x86._mm_xor_si128(aw0, yw)
ax1 := bk(aw1)
ax2 := bk(aw2)
ax3 := bk(aw3)
ax0 := bk(aw0)
t1 = x86._mm_xor_si128(
x86._mm_xor_si128(
x86._mm_clmulepi64_si128(aw0, h4w, 0x11),
x86._mm_clmulepi64_si128(aw1, h3w, 0x11)),
x86._mm_xor_si128(
x86._mm_clmulepi64_si128(aw2, h2w, 0x11),
x86._mm_clmulepi64_si128(aw3, h1w, 0x11)))
t3 = x86._mm_xor_si128(
x86._mm_xor_si128(
x86._mm_clmulepi64_si128(aw0, h4w, 0x00),
x86._mm_clmulepi64_si128(aw1, h3w, 0x00)),
x86._mm_xor_si128(
x86._mm_clmulepi64_si128(aw2, h2w, 0x00),
x86._mm_clmulepi64_si128(aw3, h1w, 0x00)))
t2 = x86._mm_xor_si128(
x86._mm_xor_si128(
x86._mm_clmulepi64_si128(ax0, h4x, 0x00),
x86._mm_clmulepi64_si128(ax1, h3x, 0x00)),
x86._mm_xor_si128(
x86._mm_clmulepi64_si128(ax2, h2x, 0x00),
x86._mm_clmulepi64_si128(ax3, h1x, 0x00)))
t2 = x86._mm_xor_si128(t2, x86._mm_xor_si128(t1, t3))
t0 = x86._mm_shuffle_epi32(t1, 0x0E)
t1 = x86._mm_xor_si128(t1, x86._mm_shuffle_epi32(t2, 0x0E))
t2 = x86._mm_xor_si128(t2, x86._mm_shuffle_epi32(t3, 0x0E))
t0, t1, t2, t3 = sl_256(t0, t1, t2, t3)
t0, t1 = reduce_f128(t0, t1, t2, t3)
yw = x86._mm_unpacklo_epi64(t1, t0)
}
}
// Process 1 block at a time
src: []byte
for l > 0 {
if l >= _aes.GHASH_BLOCK_SIZE {
src = buf
buf = buf[_aes.GHASH_BLOCK_SIZE:]
l -= _aes.GHASH_BLOCK_SIZE
} else {
tmp: [_aes.GHASH_BLOCK_SIZE]byte
copy(tmp[:], buf)
src = tmp[:]
l = 0
}
aw := intrinsics.unaligned_load((^x86.__m128i)(raw_data(src)))
aw = byteswap(aw)
aw = x86._mm_xor_si128(aw, yw)
ax := bk(aw)
t1 := x86._mm_clmulepi64_si128(aw, h1w, 0x11)
t3 := x86._mm_clmulepi64_si128(aw, h1w, 0x00)
t2 := x86._mm_clmulepi64_si128(ax, h1x, 0x00)
t2 = x86._mm_xor_si128(t2, x86._mm_xor_si128(t1, t3))
t0 := x86._mm_shuffle_epi32(t1, 0x0E)
t1 = x86._mm_xor_si128(t1, x86._mm_shuffle_epi32(t2, 0x0E))
t2 = x86._mm_xor_si128(t2, x86._mm_shuffle_epi32(t3, 0x0E))
t0, t1, t2, t3 = sl_256(t0, t1, t2, t3)
t0, t1 = reduce_f128(t0, t1, t2, t3)
yw = x86._mm_unpacklo_epi64(t1, t0)
}
// Write back the hash (dst, aka y)
yw = byteswap(yw)
intrinsics.unaligned_store((^x86.__m128i)(raw_data(dst)), yw)
}
@@ -0,0 +1,178 @@
// 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.
//+build amd64
package aes_hw_intel
import "base:intrinsics"
import "core:crypto/_aes"
import "core:mem"
import "core:simd/x86"
// Intel AES-NI based implementation. Inspiration taken from BearSSL.
//
// Note: This assumes that the SROA optimization pass is enabled to be
// anything resembling performat otherwise, LLVM will not elide a massive
// number of redundant loads/stores it generates for every intrinsic call.
@(private = "file", require_results, enable_target_feature = "sse2")
expand_step128 :: #force_inline proc(k1, k2: x86.__m128i) -> x86.__m128i {
k1, k2 := k1, k2
k2 = x86._mm_shuffle_epi32(k2, 0xff)
k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
return x86._mm_xor_si128(k1, k2)
}
@(private = "file", require_results, enable_target_feature = "sse,sse2")
expand_step192a :: #force_inline proc (k1_, k2_: ^x86.__m128i, k3: x86.__m128i) -> (x86.__m128i, x86.__m128i) {
k1, k2, k3 := k1_^, k2_^, k3
k3 = x86._mm_shuffle_epi32(k3, 0x55)
k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
k1 = x86._mm_xor_si128(k1, k3)
tmp := k2
k2 = x86._mm_xor_si128(k2, x86._mm_slli_si128(k2, 0x04))
k2 = x86._mm_xor_si128(k2, x86._mm_shuffle_epi32(k1, 0xff))
k1_, k2_ := k1_, k2_
k1_^, k2_^ = k1, k2
r1 := transmute(x86.__m128i)(x86._mm_shuffle_ps(transmute(x86.__m128)(tmp), transmute(x86.__m128)(k1), 0x44))
r2 := transmute(x86.__m128i)(x86._mm_shuffle_ps(transmute(x86.__m128)(k1), transmute(x86.__m128)(k2), 0x4e))
return r1, r2
}
@(private = "file", require_results, enable_target_feature = "sse2")
expand_step192b :: #force_inline proc (k1_, k2_: ^x86.__m128i, k3: x86.__m128i) -> x86.__m128i {
k1, k2, k3 := k1_^, k2_^, k3
k3 = x86._mm_shuffle_epi32(k3, 0x55)
k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
k1 = x86._mm_xor_si128(k1, k3)
k2 = x86._mm_xor_si128(k2, x86._mm_slli_si128(k2, 0x04))
k2 = x86._mm_xor_si128(k2, x86._mm_shuffle_epi32(k1, 0xff))
k1_, k2_ := k1_, k2_
k1_^, k2_^ = k1, k2
return k1
}
@(private = "file", require_results, enable_target_feature = "sse2")
expand_step256b :: #force_inline proc(k1, k2: x86.__m128i) -> x86.__m128i {
k1, k2 := k1, k2
k2 = x86._mm_shuffle_epi32(k2, 0xaa)
k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
return x86._mm_xor_si128(k1, k2)
}
@(private = "file", enable_target_feature = "aes")
derive_dec_keys :: proc(ctx: ^Context, sks: ^[15]x86.__m128i, num_rounds: int) {
intrinsics.unaligned_store((^x86.__m128i)(&ctx._sk_exp_dec[0]), sks[num_rounds])
for i in 1 ..< num_rounds {
tmp := x86._mm_aesimc_si128(sks[i])
intrinsics.unaligned_store((^x86.__m128i)(&ctx._sk_exp_dec[num_rounds - i]), tmp)
}
intrinsics.unaligned_store((^x86.__m128i)(&ctx._sk_exp_dec[num_rounds]), sks[0])
}
@(private, enable_target_feature = "sse,sse2,aes")
keysched :: proc(ctx: ^Context, key: []byte) {
sks: [15]x86.__m128i = ---
// Compute the encryption keys.
num_rounds, key_len := 0, len(key)
switch key_len {
case _aes.KEY_SIZE_128:
sks[0] = intrinsics.unaligned_load((^x86.__m128i)(raw_data(key)))
sks[1] = expand_step128(sks[0], x86._mm_aeskeygenassist_si128(sks[0], 0x01))
sks[2] = expand_step128(sks[1], x86._mm_aeskeygenassist_si128(sks[1], 0x02))
sks[3] = expand_step128(sks[2], x86._mm_aeskeygenassist_si128(sks[2], 0x04))
sks[4] = expand_step128(sks[3], x86._mm_aeskeygenassist_si128(sks[3], 0x08))
sks[5] = expand_step128(sks[4], x86._mm_aeskeygenassist_si128(sks[4], 0x10))
sks[6] = expand_step128(sks[5], x86._mm_aeskeygenassist_si128(sks[5], 0x20))
sks[7] = expand_step128(sks[6], x86._mm_aeskeygenassist_si128(sks[6], 0x40))
sks[8] = expand_step128(sks[7], x86._mm_aeskeygenassist_si128(sks[7], 0x80))
sks[9] = expand_step128(sks[8], x86._mm_aeskeygenassist_si128(sks[8], 0x1b))
sks[10] = expand_step128(sks[9], x86._mm_aeskeygenassist_si128(sks[9], 0x36))
num_rounds = _aes.ROUNDS_128
case _aes.KEY_SIZE_192:
k0 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(key)))
k1 := x86.__m128i{
intrinsics.unaligned_load((^i64)(raw_data(key[16:]))),
0,
}
sks[0] = k0
sks[1], sks[2] = expand_step192a(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x01))
sks[3] = expand_step192b(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x02))
sks[4], sks[5] = expand_step192a(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x04))
sks[6] = expand_step192b(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x08))
sks[7], sks[8] = expand_step192a(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x10))
sks[9] = expand_step192b(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x20))
sks[10], sks[11] = expand_step192a(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x40))
sks[12] = expand_step192b(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x80))
num_rounds = _aes.ROUNDS_192
case _aes.KEY_SIZE_256:
sks[0] = intrinsics.unaligned_load((^x86.__m128i)(raw_data(key)))
sks[1] = intrinsics.unaligned_load((^x86.__m128i)(raw_data(key[16:])))
sks[2] = expand_step128(sks[0], x86._mm_aeskeygenassist_si128(sks[1], 0x01))
sks[3] = expand_step256b(sks[1], x86._mm_aeskeygenassist_si128(sks[2], 0x01))
sks[4] = expand_step128(sks[2], x86._mm_aeskeygenassist_si128(sks[3], 0x02))
sks[5] = expand_step256b(sks[3], x86._mm_aeskeygenassist_si128(sks[4], 0x02))
sks[6] = expand_step128(sks[4], x86._mm_aeskeygenassist_si128(sks[5], 0x04))
sks[7] = expand_step256b(sks[5], x86._mm_aeskeygenassist_si128(sks[6], 0x04))
sks[8] = expand_step128(sks[6], x86._mm_aeskeygenassist_si128(sks[7], 0x08))
sks[9] = expand_step256b(sks[7], x86._mm_aeskeygenassist_si128(sks[8], 0x08))
sks[10] = expand_step128(sks[8], x86._mm_aeskeygenassist_si128(sks[9], 0x10))
sks[11] = expand_step256b(sks[9], x86._mm_aeskeygenassist_si128(sks[10], 0x10))
sks[12] = expand_step128(sks[10], x86._mm_aeskeygenassist_si128(sks[11], 0x20))
sks[13] = expand_step256b(sks[11], x86._mm_aeskeygenassist_si128(sks[12], 0x20))
sks[14] = expand_step128(sks[12], x86._mm_aeskeygenassist_si128(sks[13], 0x40))
num_rounds = _aes.ROUNDS_256
case:
panic("crypto/aes: invalid AES key size")
}
for i in 0 ..= num_rounds {
intrinsics.unaligned_store((^x86.__m128i)(&ctx._sk_exp_enc[i]), sks[i])
}
// Compute the decryption keys. GCM and CTR do not need this, however
// ECB, CBC, OCB3, etc do.
derive_dec_keys(ctx, &sks, num_rounds)
ctx._num_rounds = num_rounds
mem.zero_explicit(&sks, size_of(sks))
}
-1
View File
@@ -6,7 +6,6 @@ See:
- https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf - https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf
- https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf - https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
*/ */
package aes package aes
import "core:crypto/_aes" import "core:crypto/_aes"
+9 -7
View File
@@ -1,5 +1,6 @@
package aes package aes
import "core:bytes"
import "core:crypto/_aes/ct64" import "core:crypto/_aes/ct64"
import "core:encoding/endian" import "core:encoding/endian"
import "core:math/bits" import "core:math/bits"
@@ -37,14 +38,15 @@ init_ctr :: proc(ctx: ^Context_CTR, key, iv: []byte, impl := Implementation.Hard
xor_bytes_ctr :: proc(ctx: ^Context_CTR, dst, src: []byte) { xor_bytes_ctr :: proc(ctx: ^Context_CTR, dst, src: []byte) {
assert(ctx._is_initialized) assert(ctx._is_initialized)
// TODO: Enforcing that dst and src alias exactly or not at all
// is a good idea, though odd aliasing should be extremely uncommon.
src, dst := src, dst src, dst := src, dst
if dst_len := len(dst); dst_len < len(src) { if dst_len := len(dst); dst_len < len(src) {
src = src[:dst_len] src = src[:dst_len]
} }
if bytes.alias_inexactly(dst, src) {
panic("crypto/aes: dst and src alias inexactly")
}
for remaining := len(src); remaining > 0; { for remaining := len(src); remaining > 0; {
// Process multiple blocks at once // Process multiple blocks at once
if ctx._off == BLOCK_SIZE { if ctx._off == BLOCK_SIZE {
@@ -123,8 +125,8 @@ reset_ctr :: proc "contextless" (ctx: ^Context_CTR) {
ctx._is_initialized = false ctx._is_initialized = false
} }
@(private) @(private = "file")
ctr_blocks :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) { ctr_blocks :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) #no_bounds_check {
// Use the optimized hardware implementation if available. // Use the optimized hardware implementation if available.
if _, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw { if _, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw {
ctr_blocks_hw(ctx, dst, src, nr_blocks) ctr_blocks_hw(ctx, dst, src, nr_blocks)
@@ -183,8 +185,8 @@ xor_blocks :: #force_inline proc "contextless" (dst, src: []byte, blocks: [][]by
// performance of this implementation matters to where that // performance of this implementation matters to where that
// optimization would be worth it, use chacha20poly1305, or a // optimization would be worth it, use chacha20poly1305, or a
// CPU that isn't e-waste. // CPU that isn't e-waste.
if src != nil {
#no_bounds_check { #no_bounds_check {
if src != nil {
for i in 0 ..< len(blocks) { for i in 0 ..< len(blocks) {
off := i * BLOCK_SIZE off := i * BLOCK_SIZE
for j in 0 ..< BLOCK_SIZE { for j in 0 ..< BLOCK_SIZE {
@@ -192,8 +194,8 @@ xor_blocks :: #force_inline proc "contextless" (dst, src: []byte, blocks: [][]by
} }
} }
} }
}
for i in 0 ..< len(blocks) { for i in 0 ..< len(blocks) {
copy(dst[i * BLOCK_SIZE:], blocks[i]) copy(dst[i * BLOCK_SIZE:], blocks[i])
} }
}
} }
+151
View File
@@ -0,0 +1,151 @@
//+build amd64
package aes
import "base:intrinsics"
import "core:crypto/_aes"
import "core:math/bits"
import "core:mem"
import "core:simd/x86"
@(private)
CTR_STRIDE_HW :: 4
@(private)
CTR_STRIDE_BYTES_HW :: CTR_STRIDE_HW * BLOCK_SIZE
@(private, enable_target_feature = "sse2,aes")
ctr_blocks_hw :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) #no_bounds_check {
hw_ctx := ctx._impl.(Context_Impl_Hardware)
sks: [15]x86.__m128i = ---
for i in 0 ..= hw_ctx._num_rounds {
sks[i] = intrinsics.unaligned_load((^x86.__m128i)(&hw_ctx._sk_exp_enc[i]))
}
hw_inc_ctr := #force_inline proc "contextless" (hi, lo: u64) -> (x86.__m128i, u64, u64) {
ret := x86.__m128i{
i64(intrinsics.byte_swap(hi)),
i64(intrinsics.byte_swap(lo)),
}
hi, lo := hi, lo
carry: u64
lo, carry = bits.add_u64(lo, 1, 0)
hi, _ = bits.add_u64(hi, 0, carry)
return ret, hi, lo
}
// The latency of AESENC depends on mfg and microarchitecture:
// - 7 -> up to Broadwell
// - 4 -> AMD and Skylake - Cascade Lake
// - 3 -> Ice Lake and newer
//
// This implementation does 4 blocks at once, since performance
// should be "adequate" across most CPUs.
src, dst := src, dst
nr_blocks := nr_blocks
ctr_hi, ctr_lo := ctx._ctr_hi, ctx._ctr_lo
blks: [CTR_STRIDE_HW]x86.__m128i = ---
for nr_blocks >= CTR_STRIDE_HW {
#unroll for i in 0..< CTR_STRIDE_HW {
blks[i], ctr_hi, ctr_lo = hw_inc_ctr(ctr_hi, ctr_lo)
}
#unroll for i in 0 ..< CTR_STRIDE_HW {
blks[i] = x86._mm_xor_si128(blks[i], sks[0])
}
#unroll for i in 1 ..= 9 {
#unroll for j in 0 ..< CTR_STRIDE_HW {
blks[j] = x86._mm_aesenc_si128(blks[j], sks[i])
}
}
switch hw_ctx._num_rounds {
case _aes.ROUNDS_128:
#unroll for i in 0 ..< CTR_STRIDE_HW {
blks[i] = x86._mm_aesenclast_si128(blks[i], sks[10])
}
case _aes.ROUNDS_192:
#unroll for i in 10 ..= 11 {
#unroll for j in 0 ..< CTR_STRIDE_HW {
blks[j] = x86._mm_aesenc_si128(blks[j], sks[i])
}
}
#unroll for i in 0 ..< CTR_STRIDE_HW {
blks[i] = x86._mm_aesenclast_si128(blks[i], sks[12])
}
case _aes.ROUNDS_256:
#unroll for i in 10 ..= 13 {
#unroll for j in 0 ..< CTR_STRIDE_HW {
blks[j] = x86._mm_aesenc_si128(blks[j], sks[i])
}
}
#unroll for i in 0 ..< CTR_STRIDE_HW {
blks[i] = x86._mm_aesenclast_si128(blks[i], sks[14])
}
}
xor_blocks_hw(dst, src, blks[:])
if src != nil {
src = src[CTR_STRIDE_BYTES_HW:]
}
dst = dst[CTR_STRIDE_BYTES_HW:]
nr_blocks -= CTR_STRIDE_HW
}
// Handle the remainder.
for nr_blocks > 0 {
blks[0], ctr_hi, ctr_lo = hw_inc_ctr(ctr_hi, ctr_lo)
blks[0] = x86._mm_xor_si128(blks[0], sks[0])
#unroll for i in 1 ..= 9 {
blks[0] = x86._mm_aesenc_si128(blks[0], sks[i])
}
switch hw_ctx._num_rounds {
case _aes.ROUNDS_128:
blks[0] = x86._mm_aesenclast_si128(blks[0], sks[10])
case _aes.ROUNDS_192:
#unroll for i in 10 ..= 11 {
blks[0] = x86._mm_aesenc_si128(blks[0], sks[i])
}
blks[0] = x86._mm_aesenclast_si128(blks[0], sks[12])
case _aes.ROUNDS_256:
#unroll for i in 10 ..= 13 {
blks[0] = x86._mm_aesenc_si128(blks[0], sks[i])
}
blks[0] = x86._mm_aesenclast_si128(blks[0], sks[14])
}
xor_blocks_hw(dst, src, blks[:1])
if src != nil {
src = src[BLOCK_SIZE:]
}
dst = dst[BLOCK_SIZE:]
nr_blocks -= 1
}
// Write back the counter.
ctx._ctr_hi, ctx._ctr_lo = ctr_hi, ctr_lo
mem.zero_explicit(&blks, size_of(blks))
mem.zero_explicit(&sks, size_of(sks))
}
@(private, enable_target_feature = "sse2")
xor_blocks_hw :: proc(dst, src: []byte, blocks: []x86.__m128i) {
#no_bounds_check {
if src != nil {
for i in 0 ..< len(blocks) {
off := i * BLOCK_SIZE
tmp := intrinsics.unaligned_load((^x86.__m128i)(raw_data(src[off:])))
blocks[i] = x86._mm_xor_si128(blocks[i], tmp)
}
}
for i in 0 ..< len(blocks) {
intrinsics.unaligned_store((^x86.__m128i)(raw_data(dst[i * BLOCK_SIZE:])), blocks[i])
}
}
}
+58
View File
@@ -0,0 +1,58 @@
//+build amd64
package aes
import "base:intrinsics"
import "core:crypto/_aes"
import "core:simd/x86"
@(private, enable_target_feature = "sse2,aes")
encrypt_block_hw :: proc(ctx: ^Context_Impl_Hardware, dst, src: []byte) {
blk := intrinsics.unaligned_load((^x86.__m128i)(raw_data(src)))
blk = x86._mm_xor_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[0])))
#unroll for i in 1 ..= 9 {
blk = x86._mm_aesenc_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[i])))
}
switch ctx._num_rounds {
case _aes.ROUNDS_128:
blk = x86._mm_aesenclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[10])))
case _aes.ROUNDS_192:
#unroll for i in 10 ..= 11 {
blk = x86._mm_aesenc_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[i])))
}
blk = x86._mm_aesenclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[12])))
case _aes.ROUNDS_256:
#unroll for i in 10 ..= 13 {
blk = x86._mm_aesenc_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[i])))
}
blk = x86._mm_aesenclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[14])))
}
intrinsics.unaligned_store((^x86.__m128i)(raw_data(dst)), blk)
}
@(private, enable_target_feature = "sse2,aes")
decrypt_block_hw :: proc(ctx: ^Context_Impl_Hardware, dst, src: []byte) {
blk := intrinsics.unaligned_load((^x86.__m128i)(raw_data(src)))
blk = x86._mm_xor_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[0])))
#unroll for i in 1 ..= 9 {
blk = x86._mm_aesdec_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[i])))
}
switch ctx._num_rounds {
case _aes.ROUNDS_128:
blk = x86._mm_aesdeclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[10])))
case _aes.ROUNDS_192:
#unroll for i in 10 ..= 11 {
blk = x86._mm_aesdec_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[i])))
}
blk = x86._mm_aesdeclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[12])))
case _aes.ROUNDS_256:
#unroll for i in 10 ..= 13 {
blk = x86._mm_aesdec_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[i])))
}
blk = x86._mm_aesdeclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[14])))
}
intrinsics.unaligned_store((^x86.__m128i)(raw_data(dst)), blk)
}
+45 -29
View File
@@ -1,13 +1,16 @@
package aes package aes
import "core:bytes"
import "core:crypto" import "core:crypto"
import "core:crypto/_aes" import "core:crypto/_aes"
import "core:crypto/_aes/ct64" import "core:crypto/_aes/ct64"
import "core:encoding/endian" import "core:encoding/endian"
import "core:mem" import "core:mem"
// GCM_NONCE_SIZE is the size of the GCM nonce in bytes. // GCM_NONCE_SIZE is the default size of the GCM nonce in bytes.
GCM_NONCE_SIZE :: 12 GCM_NONCE_SIZE :: 12
// GCM_NONCE_SIZE_MAX is the maximum size of the GCM nonce in bytes.
GCM_NONCE_SIZE_MAX :: 0x2000000000000000 // floor((2^64 - 1) / 8) bits
// GCM_TAG_SIZE is the size of a GCM tag in bytes. // GCM_TAG_SIZE is the size of a GCM tag in bytes.
GCM_TAG_SIZE :: _aes.GHASH_TAG_SIZE GCM_TAG_SIZE :: _aes.GHASH_TAG_SIZE
@@ -39,6 +42,9 @@ seal_gcm :: proc(ctx: ^Context_GCM, dst, tag, nonce, aad, plaintext: []byte) {
if len(dst) != len(plaintext) { if len(dst) != len(plaintext) {
panic("crypto/aes: invalid destination ciphertext size") panic("crypto/aes: invalid destination ciphertext size")
} }
if bytes.alias_inexactly(dst, plaintext) {
panic("crypto/aes: dst and plaintext alias inexactly")
}
if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw { if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw {
gcm_seal_hw(&impl, dst, tag, nonce, aad, plaintext) gcm_seal_hw(&impl, dst, tag, nonce, aad, plaintext)
@@ -47,17 +53,19 @@ seal_gcm :: proc(ctx: ^Context_GCM, dst, tag, nonce, aad, plaintext: []byte) {
h: [_aes.GHASH_KEY_SIZE]byte h: [_aes.GHASH_KEY_SIZE]byte
j0: [_aes.GHASH_BLOCK_SIZE]byte j0: [_aes.GHASH_BLOCK_SIZE]byte
j0_enc: [_aes.GHASH_BLOCK_SIZE]byte
s: [_aes.GHASH_TAG_SIZE]byte s: [_aes.GHASH_TAG_SIZE]byte
init_ghash_ct64(ctx, &h, &j0, nonce) init_ghash_ct64(ctx, &h, &j0, &j0_enc, nonce)
// Note: Our GHASH implementation handles appending padding. // Note: Our GHASH implementation handles appending padding.
ct64.ghash(s[:], h[:], aad) ct64.ghash(s[:], h[:], aad)
gctr_ct64(ctx, dst, &s, plaintext, &h, nonce, true) gctr_ct64(ctx, dst, &s, plaintext, &h, &j0, true)
final_ghash_ct64(&s, &h, &j0, len(aad), len(plaintext)) final_ghash_ct64(&s, &h, &j0_enc, len(aad), len(plaintext))
copy(tag, s[:]) copy(tag, s[:])
mem.zero_explicit(&h, len(h)) mem.zero_explicit(&h, len(h))
mem.zero_explicit(&j0, len(j0)) mem.zero_explicit(&j0, len(j0))
mem.zero_explicit(&j0_enc, len(j0_enc))
} }
// open_gcm authenticates the aad and ciphertext, and decrypts the ciphertext, // open_gcm authenticates the aad and ciphertext, and decrypts the ciphertext,
@@ -73,6 +81,9 @@ open_gcm :: proc(ctx: ^Context_GCM, dst, nonce, aad, ciphertext, tag: []byte) ->
if len(dst) != len(ciphertext) { if len(dst) != len(ciphertext) {
panic("crypto/aes: invalid destination plaintext size") panic("crypto/aes: invalid destination plaintext size")
} }
if bytes.alias_inexactly(dst, ciphertext) {
panic("crypto/aes: dst and ciphertext alias inexactly")
}
if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw { if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw {
return gcm_open_hw(&impl, dst, nonce, aad, ciphertext, tag) return gcm_open_hw(&impl, dst, nonce, aad, ciphertext, tag)
@@ -80,12 +91,13 @@ open_gcm :: proc(ctx: ^Context_GCM, dst, nonce, aad, ciphertext, tag: []byte) ->
h: [_aes.GHASH_KEY_SIZE]byte h: [_aes.GHASH_KEY_SIZE]byte
j0: [_aes.GHASH_BLOCK_SIZE]byte j0: [_aes.GHASH_BLOCK_SIZE]byte
j0_enc: [_aes.GHASH_BLOCK_SIZE]byte
s: [_aes.GHASH_TAG_SIZE]byte s: [_aes.GHASH_TAG_SIZE]byte
init_ghash_ct64(ctx, &h, &j0, nonce) init_ghash_ct64(ctx, &h, &j0, &j0_enc, nonce)
ct64.ghash(s[:], h[:], aad) ct64.ghash(s[:], h[:], aad)
gctr_ct64(ctx, dst, &s, ciphertext, &h, nonce, false) gctr_ct64(ctx, dst, &s, ciphertext, &h, &j0, false)
final_ghash_ct64(&s, &h, &j0, len(aad), len(ciphertext)) final_ghash_ct64(&s, &h, &j0_enc, len(aad), len(ciphertext))
ok := crypto.compare_constant_time(s[:], tag) == 1 ok := crypto.compare_constant_time(s[:], tag) == 1
if !ok { if !ok {
@@ -94,6 +106,7 @@ open_gcm :: proc(ctx: ^Context_GCM, dst, nonce, aad, ciphertext, tag: []byte) ->
mem.zero_explicit(&h, len(h)) mem.zero_explicit(&h, len(h))
mem.zero_explicit(&j0, len(j0)) mem.zero_explicit(&j0, len(j0))
mem.zero_explicit(&j0_enc, len(j0_enc))
mem.zero_explicit(&s, len(s)) mem.zero_explicit(&s, len(s))
return ok return ok
@@ -106,19 +119,14 @@ reset_gcm :: proc "contextless" (ctx: ^Context_GCM) {
ctx._is_initialized = false ctx._is_initialized = false
} }
@(private) @(private = "file")
gcm_validate_common_slice_sizes :: proc(tag, nonce, aad, text: []byte) { gcm_validate_common_slice_sizes :: proc(tag, nonce, aad, text: []byte) {
if len(tag) != GCM_TAG_SIZE { if len(tag) != GCM_TAG_SIZE {
panic("crypto/aes: invalid GCM tag size") panic("crypto/aes: invalid GCM tag size")
} }
// The specification supports nonces in the range [1, 2^64) bits // The specification supports nonces in the range [1, 2^64) bits.
// however per NIST SP 800-38D 5.2.1.1: if l := len(nonce); l == 0 || u64(l) >= GCM_NONCE_SIZE_MAX {
//
// > For IVs, it is recommended that implementations restrict support
// > to the length of 96 bits, to promote interoperability, efficiency,
// > and simplicity of design.
if len(nonce) != GCM_NONCE_SIZE {
panic("crypto/aes: invalid GCM nonce size") panic("crypto/aes: invalid GCM nonce size")
} }
@@ -135,6 +143,7 @@ init_ghash_ct64 :: proc(
ctx: ^Context_GCM, ctx: ^Context_GCM,
h: ^[_aes.GHASH_KEY_SIZE]byte, h: ^[_aes.GHASH_KEY_SIZE]byte,
j0: ^[_aes.GHASH_BLOCK_SIZE]byte, j0: ^[_aes.GHASH_BLOCK_SIZE]byte,
j0_enc: ^[_aes.GHASH_BLOCK_SIZE]byte,
nonce: []byte, nonce: []byte,
) { ) {
impl := &ctx._impl.(ct64.Context) impl := &ctx._impl.(ct64.Context)
@@ -142,12 +151,25 @@ init_ghash_ct64 :: proc(
// 1. Let H = CIPH(k, 0^128) // 1. Let H = CIPH(k, 0^128)
ct64.encrypt_block(impl, h[:], h[:]) ct64.encrypt_block(impl, h[:], h[:])
// Define a block, J0, as follows:
if l := len(nonce); l == GCM_NONCE_SIZE {
// if len(IV) = 96, then let J0 = IV || 0^31 || 1
copy(j0[:], nonce)
j0[_aes.GHASH_BLOCK_SIZE - 1] = 1
} else {
// If len(IV) != 96, then let s = 128 ceil(len(IV)/128) - len(IV),
// and let J0 = GHASHH(IV || 0^(s+64) || ceil(len(IV))^64).
ct64.ghash(j0[:], h[:], nonce)
tmp: [_aes.GHASH_BLOCK_SIZE]byte
endian.unchecked_put_u64be(tmp[8:], u64(l) * 8)
ct64.ghash(j0[:], h[:], tmp[:])
}
// ECB encrypt j0, so that we can just XOR with the tag. In theory // ECB encrypt j0, so that we can just XOR with the tag. In theory
// this could be processed along with the final GCTR block, to // this could be processed along with the final GCTR block, to
// potentially save a call to AES-ECB, but... just use AES-NI. // potentially save a call to AES-ECB, but... just use AES-NI.
copy(j0[:], nonce) ct64.encrypt_block(impl, j0_enc[:], j0[:])
j0[_aes.GHASH_BLOCK_SIZE - 1] = 1
ct64.encrypt_block(impl, j0[:], j0[:])
} }
@(private = "file") @(private = "file")
@@ -175,33 +197,27 @@ gctr_ct64 :: proc(
s: ^[_aes.GHASH_BLOCK_SIZE]byte, s: ^[_aes.GHASH_BLOCK_SIZE]byte,
src: []byte, src: []byte,
h: ^[_aes.GHASH_KEY_SIZE]byte, h: ^[_aes.GHASH_KEY_SIZE]byte,
nonce: []byte, nonce: ^[_aes.GHASH_BLOCK_SIZE]byte,
is_seal: bool, is_seal: bool,
) { ) #no_bounds_check {
ct64_inc_ctr32 := #force_inline proc "contextless" (dst: []byte, ctr: u32) -> u32 { ct64_inc_ctr32 := #force_inline proc "contextless" (dst: []byte, ctr: u32) -> u32 {
endian.unchecked_put_u32be(dst[12:], ctr) endian.unchecked_put_u32be(dst[12:], ctr)
return ctr + 1 return ctr + 1
} }
// 2. Define a block J_0 as follows: // Setup the counter blocks.
// if len(IV) = 96, then let J0 = IV || 0^31 || 1
//
// Note: We only support 96 bit IVs.
tmp, tmp2: [ct64.STRIDE][BLOCK_SIZE]byte = ---, --- tmp, tmp2: [ct64.STRIDE][BLOCK_SIZE]byte = ---, ---
ctrs, blks: [ct64.STRIDE][]byte = ---, --- ctrs, blks: [ct64.STRIDE][]byte = ---, ---
ctr: u32 = 2 ctr := endian.unchecked_get_u32be(nonce[GCM_NONCE_SIZE:]) + 1
for i in 0 ..< ct64.STRIDE { for i in 0 ..< ct64.STRIDE {
// Setup scratch space for the keystream. // Setup scratch space for the keystream.
blks[i] = tmp2[i][:] blks[i] = tmp2[i][:]
// Pre-copy the IV to all the counter blocks. // Pre-copy the IV to all the counter blocks.
ctrs[i] = tmp[i][:] ctrs[i] = tmp[i][:]
copy(ctrs[i], nonce) copy(ctrs[i], nonce[:GCM_NONCE_SIZE])
} }
// We stitch the GCTR and GHASH operations together, so that only
// one pass over the ciphertext is required.
impl := &ctx._impl.(ct64.Context) impl := &ctx._impl.(ct64.Context)
src, dst := src, dst src, dst := src, dst
+243
View File
@@ -0,0 +1,243 @@
//+build amd64
package aes
import "base:intrinsics"
import "core:crypto"
import "core:crypto/_aes"
import "core:crypto/_aes/hw_intel"
import "core:encoding/endian"
import "core:mem"
import "core:simd/x86"
@(private)
gcm_seal_hw :: proc(ctx: ^Context_Impl_Hardware, dst, tag, nonce, aad, plaintext: []byte) {
h: [_aes.GHASH_KEY_SIZE]byte
j0: [_aes.GHASH_BLOCK_SIZE]byte
j0_enc: [_aes.GHASH_BLOCK_SIZE]byte
s: [_aes.GHASH_TAG_SIZE]byte
init_ghash_hw(ctx, &h, &j0, &j0_enc, nonce)
// Note: Our GHASH implementation handles appending padding.
hw_intel.ghash(s[:], h[:], aad)
gctr_hw(ctx, dst, &s, plaintext, &h, &j0, true)
final_ghash_hw(&s, &h, &j0_enc, len(aad), len(plaintext))
copy(tag, s[:])
mem.zero_explicit(&h, len(h))
mem.zero_explicit(&j0, len(j0))
mem.zero_explicit(&j0_enc, len(j0_enc))
}
@(private)
gcm_open_hw :: proc(ctx: ^Context_Impl_Hardware, dst, nonce, aad, ciphertext, tag: []byte) -> bool {
h: [_aes.GHASH_KEY_SIZE]byte
j0: [_aes.GHASH_BLOCK_SIZE]byte
j0_enc: [_aes.GHASH_BLOCK_SIZE]byte
s: [_aes.GHASH_TAG_SIZE]byte
init_ghash_hw(ctx, &h, &j0, &j0_enc, nonce)
hw_intel.ghash(s[:], h[:], aad)
gctr_hw(ctx, dst, &s, ciphertext, &h, &j0, false)
final_ghash_hw(&s, &h, &j0_enc, len(aad), len(ciphertext))
ok := crypto.compare_constant_time(s[:], tag) == 1
if !ok {
mem.zero_explicit(raw_data(dst), len(dst))
}
mem.zero_explicit(&h, len(h))
mem.zero_explicit(&j0, len(j0))
mem.zero_explicit(&j0_enc, len(j0_enc))
mem.zero_explicit(&s, len(s))
return ok
}
@(private = "file")
init_ghash_hw :: proc(
ctx: ^Context_Impl_Hardware,
h: ^[_aes.GHASH_KEY_SIZE]byte,
j0: ^[_aes.GHASH_BLOCK_SIZE]byte,
j0_enc: ^[_aes.GHASH_BLOCK_SIZE]byte,
nonce: []byte,
) {
// 1. Let H = CIPH(k, 0^128)
encrypt_block_hw(ctx, h[:], h[:])
// Define a block, J0, as follows:
if l := len(nonce); l == GCM_NONCE_SIZE {
// if len(IV) = 96, then let J0 = IV || 0^31 || 1
copy(j0[:], nonce)
j0[_aes.GHASH_BLOCK_SIZE - 1] = 1
} else {
// If len(IV) != 96, then let s = 128 ceil(len(IV)/128) - len(IV),
// and let J0 = GHASHH(IV || 0^(s+64) || ceil(len(IV))^64).
hw_intel.ghash(j0[:], h[:], nonce)
tmp: [_aes.GHASH_BLOCK_SIZE]byte
endian.unchecked_put_u64be(tmp[8:], u64(l) * 8)
hw_intel.ghash(j0[:], h[:], tmp[:])
}
// ECB encrypt j0, so that we can just XOR with the tag.
encrypt_block_hw(ctx, j0_enc[:], j0[:])
}
@(private = "file", enable_target_feature = "sse2")
final_ghash_hw :: proc(
s: ^[_aes.GHASH_BLOCK_SIZE]byte,
h: ^[_aes.GHASH_KEY_SIZE]byte,
j0: ^[_aes.GHASH_BLOCK_SIZE]byte,
a_len: int,
t_len: int,
) {
blk: [_aes.GHASH_BLOCK_SIZE]byte
endian.unchecked_put_u64be(blk[0:], u64(a_len) * 8)
endian.unchecked_put_u64be(blk[8:], u64(t_len) * 8)
hw_intel.ghash(s[:], h[:], blk[:])
j0_vec := intrinsics.unaligned_load((^x86.__m128i)(j0))
s_vec := intrinsics.unaligned_load((^x86.__m128i)(s))
s_vec = x86._mm_xor_si128(s_vec, j0_vec)
intrinsics.unaligned_store((^x86.__m128i)(s), s_vec)
}
@(private = "file", enable_target_feature = "sse2,sse4.1,aes")
gctr_hw :: proc(
ctx: ^Context_Impl_Hardware,
dst: []byte,
s: ^[_aes.GHASH_BLOCK_SIZE]byte,
src: []byte,
h: ^[_aes.GHASH_KEY_SIZE]byte,
nonce: ^[_aes.GHASH_BLOCK_SIZE]byte,
is_seal: bool,
) #no_bounds_check {
sks: [15]x86.__m128i = ---
for i in 0 ..= ctx._num_rounds {
sks[i] = intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[i]))
}
// Setup the counter block
ctr_blk := intrinsics.unaligned_load((^x86.__m128i)(nonce))
ctr := endian.unchecked_get_u32be(nonce[GCM_NONCE_SIZE:]) + 1
src, dst := src, dst
// Note: Instead of doing GHASH and CTR separately, it is more
// performant to interleave (stitch) the two operations together.
// This results in an unreadable mess, so we opt for simplicity
// as performance is adequate.
blks: [CTR_STRIDE_HW]x86.__m128i = ---
nr_blocks := len(src) / BLOCK_SIZE
for nr_blocks >= CTR_STRIDE_HW {
if !is_seal {
hw_intel.ghash(s[:], h[:], src[:CTR_STRIDE_BYTES_HW])
}
#unroll for i in 0 ..< CTR_STRIDE_HW {
blks[i], ctr = hw_inc_ctr32(&ctr_blk, ctr)
}
#unroll for i in 0 ..< CTR_STRIDE_HW {
blks[i] = x86._mm_xor_si128(blks[i], sks[0])
}
#unroll for i in 1 ..= 9 {
#unroll for j in 0 ..< CTR_STRIDE_HW {
blks[j] = x86._mm_aesenc_si128(blks[j], sks[i])
}
}
switch ctx._num_rounds {
case _aes.ROUNDS_128:
#unroll for i in 0 ..< CTR_STRIDE_HW {
blks[i] = x86._mm_aesenclast_si128(blks[i], sks[10])
}
case _aes.ROUNDS_192:
#unroll for i in 10 ..= 11 {
#unroll for j in 0 ..< CTR_STRIDE_HW {
blks[j] = x86._mm_aesenc_si128(blks[j], sks[i])
}
}
#unroll for i in 0 ..< CTR_STRIDE_HW {
blks[i] = x86._mm_aesenclast_si128(blks[i], sks[12])
}
case _aes.ROUNDS_256:
#unroll for i in 10 ..= 13 {
#unroll for j in 0 ..< CTR_STRIDE_HW {
blks[j] = x86._mm_aesenc_si128(blks[j], sks[i])
}
}
#unroll for i in 0 ..< CTR_STRIDE_HW {
blks[i] = x86._mm_aesenclast_si128(blks[i], sks[14])
}
}
xor_blocks_hw(dst, src, blks[:])
if is_seal {
hw_intel.ghash(s[:], h[:], dst[:CTR_STRIDE_BYTES_HW])
}
src = src[CTR_STRIDE_BYTES_HW:]
dst = dst[CTR_STRIDE_BYTES_HW:]
nr_blocks -= CTR_STRIDE_HW
}
// Handle the remainder.
for n := len(src); n > 0; {
l := min(n, BLOCK_SIZE)
if !is_seal {
hw_intel.ghash(s[:], h[:], src[:l])
}
blks[0], ctr = hw_inc_ctr32(&ctr_blk, ctr)
blks[0] = x86._mm_xor_si128(blks[0], sks[0])
#unroll for i in 1 ..= 9 {
blks[0] = x86._mm_aesenc_si128(blks[0], sks[i])
}
switch ctx._num_rounds {
case _aes.ROUNDS_128:
blks[0] = x86._mm_aesenclast_si128(blks[0], sks[10])
case _aes.ROUNDS_192:
#unroll for i in 10 ..= 11 {
blks[0] = x86._mm_aesenc_si128(blks[0], sks[i])
}
blks[0] = x86._mm_aesenclast_si128(blks[0], sks[12])
case _aes.ROUNDS_256:
#unroll for i in 10 ..= 13 {
blks[0] = x86._mm_aesenc_si128(blks[0], sks[i])
}
blks[0] = x86._mm_aesenclast_si128(blks[0], sks[14])
}
if l == BLOCK_SIZE {
xor_blocks_hw(dst, src, blks[:1])
} else {
blk: [BLOCK_SIZE]byte
copy(blk[:], src)
xor_blocks_hw(blk[:], blk[:], blks[:1])
copy(dst, blk[:l])
}
if is_seal {
hw_intel.ghash(s[:], h[:], dst[:l])
}
dst = dst[l:]
src = src[l:]
n -= l
}
mem.zero_explicit(&blks, size_of(blks))
mem.zero_explicit(&sks, size_of(sks))
}
// BUG: Sticking this in gctr_hw (like the other implementations) crashes
// the compiler.
//
// src/check_expr.cpp(7892): Assertion Failure: `c->curr_proc_decl->entity`
@(private = "file", enable_target_feature = "sse4.1")
hw_inc_ctr32 :: #force_inline proc "contextless" (src: ^x86.__m128i, ctr: u32) -> (x86.__m128i, u32) {
ret := x86._mm_insert_epi32(src^, i32(intrinsics.byte_swap(ctr)), 3)
return ret, ctr + 1
}
+1
View File
@@ -1,3 +1,4 @@
//+build !amd64
package aes package aes
@(private = "file") @(private = "file")
+18
View File
@@ -0,0 +1,18 @@
//+build amd64
package aes
import "core:crypto/_aes/hw_intel"
// is_hardware_accelerated returns true iff hardware accelerated AES
// is supported.
is_hardware_accelerated :: proc "contextless" () -> bool {
return hw_intel.is_supported()
}
@(private)
Context_Impl_Hardware :: hw_intel.Context
@(private, enable_target_feature = "sse2,aes")
init_impl_hw :: proc(ctx: ^Context_Impl_Hardware, key: []byte) {
hw_intel.init(ctx, key)
}
+5 -3
View File
@@ -7,6 +7,7 @@ See:
*/ */
package chacha20 package chacha20
import "core:bytes"
import "core:encoding/endian" import "core:encoding/endian"
import "core:math/bits" import "core:math/bits"
import "core:mem" import "core:mem"
@@ -121,14 +122,15 @@ seek :: proc(ctx: ^Context, block_nr: u64) {
xor_bytes :: proc(ctx: ^Context, dst, src: []byte) { xor_bytes :: proc(ctx: ^Context, dst, src: []byte) {
assert(ctx._is_initialized) assert(ctx._is_initialized)
// TODO: Enforcing that dst and src alias exactly or not at all
// is a good idea, though odd aliasing should be extremely uncommon.
src, dst := src, dst src, dst := src, dst
if dst_len := len(dst); dst_len < len(src) { if dst_len := len(dst); dst_len < len(src) {
src = src[:dst_len] src = src[:dst_len]
} }
if bytes.alias_inexactly(dst, src) {
panic("crypto/chacha20: dst and src alias inexactly")
}
for remaining := len(src); remaining > 0; { for remaining := len(src); remaining > 0; {
// Process multiple blocks at once // Process multiple blocks at once
if ctx._off == _BLOCK_SIZE { if ctx._off == _BLOCK_SIZE {
+5 -1
View File
@@ -60,7 +60,11 @@ rand_bytes :: proc (dst: []byte) {
_rand_bytes(dst) _rand_bytes(dst)
} }
// random_generator returns a `runtime.Random_Generator` backed by the
// system entropy source.
//
// Support for the system entropy source can be checked with the
// `HAS_RAND_BYTES` boolean constant.
random_generator :: proc() -> runtime.Random_Generator { random_generator :: proc() -> runtime.Random_Generator {
return { return {
procedure = proc(data: rawptr, mode: runtime.Random_Generator_Mode, p: []byte) { procedure = proc(data: rawptr, mode: runtime.Random_Generator_Mode, p: []byte) {
+5 -4
View File
@@ -351,7 +351,8 @@ _marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (er
builder := strings.builder_from_slice(res[:]) builder := strings.builder_from_slice(res[:])
e.writer = strings.to_stream(&builder) e.writer = strings.to_stream(&builder)
assert(_encode_u64(e, u64(len(str)), .Text) == nil) err := _encode_u64(e, u64(len(str)), .Text)
assert(err == nil)
res[9] = u8(len(builder.buf)) res[9] = u8(len(builder.buf))
assert(res[9] < 10) assert(res[9] < 10)
return return
@@ -506,7 +507,7 @@ _marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (er
} }
n: u64; { n: u64; {
for _, i in info.names { for _, i in info.names[:info.field_count] {
if field_name(info, i) != "-" { if field_name(info, i) != "-" {
n += 1 n += 1
} }
@@ -522,7 +523,7 @@ _marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (er
entries := make([dynamic]Name, 0, n, e.temp_allocator) or_return entries := make([dynamic]Name, 0, n, e.temp_allocator) or_return
defer delete(entries) defer delete(entries)
for _, i in info.names { for _, i in info.names[:info.field_count] {
fname := field_name(info, i) fname := field_name(info, i)
if fname == "-" { if fname == "-" {
continue continue
@@ -540,7 +541,7 @@ _marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (er
marshal_entry(e, info, v, entry.name, entry.field) or_return marshal_entry(e, info, v, entry.name, entry.field) or_return
} }
} else { } else {
for _, i in info.names { for _, i in info.names[:info.field_count] {
fname := field_name(info, i) fname := field_name(info, i)
if fname == "-" { if fname == "-" {
continue continue
+3 -2
View File
@@ -96,7 +96,8 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header, allocator := context.a
ti = reflect.type_info_base(variant) ti = reflect.type_info_base(variant)
if !reflect.is_pointer_internally(variant) { if !reflect.is_pointer_internally(variant) {
tag := any{rawptr(uintptr(v.data) + u.tag_offset), u.tag_type.id} tag := any{rawptr(uintptr(v.data) + u.tag_offset), u.tag_type.id}
assert(_assign_int(tag, 1)) assigned := _assign_int(tag, 1)
assert(assigned)
} }
} }
} }
@@ -618,7 +619,7 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header,
#partial switch t in ti.variant { #partial switch t in ti.variant {
case reflect.Type_Info_Struct: case reflect.Type_Info_Struct:
if t.is_raw_union { if .raw_union in t.flags {
return _unsupported(v, hdr) return _unsupported(v, hdr)
} }
+6 -3
View File
@@ -82,6 +82,7 @@ Map :: distinct map[string]map[string]string
load_map_from_string :: proc(src: string, allocator: runtime.Allocator, options := DEFAULT_OPTIONS) -> (m: Map, err: runtime.Allocator_Error) { load_map_from_string :: proc(src: string, allocator: runtime.Allocator, options := DEFAULT_OPTIONS) -> (m: Map, err: runtime.Allocator_Error) {
unquote :: proc(val: string) -> (string, runtime.Allocator_Error) { unquote :: proc(val: string) -> (string, runtime.Allocator_Error) {
if len(val) > 0 && (val[0] == '"' || val[0] == '\'') {
v, allocated, ok := strconv.unquote_string(val) v, allocated, ok := strconv.unquote_string(val)
if !ok { if !ok {
return strings.clone(val) return strings.clone(val)
@@ -89,8 +90,9 @@ load_map_from_string :: proc(src: string, allocator: runtime.Allocator, options
if allocated { if allocated {
return v, nil return v, nil
} }
return strings.clone(v) return strings.clone(v), nil
}
return strings.clone(val)
} }
context.allocator = allocator context.allocator = allocator
@@ -121,7 +123,7 @@ load_map_from_path :: proc(path: string, allocator: runtime.Allocator, options :
data := os.read_entire_file(path, allocator) or_return data := os.read_entire_file(path, allocator) or_return
defer delete(data, allocator) defer delete(data, allocator)
m, err = load_map_from_string(string(data), allocator, options) m, err = load_map_from_string(string(data), allocator, options)
ok = err != nil ok = err == nil
defer if !ok { defer if !ok {
delete_map(m) delete_map(m)
} }
@@ -142,6 +144,7 @@ delete_map :: proc(m: Map) {
delete(value, allocator) delete(value, allocator)
} }
delete(section) delete(section)
delete(pairs)
} }
delete(m) delete(m)
} }
+50 -33
View File
@@ -100,38 +100,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
case runtime.Type_Info_Integer: case runtime.Type_Info_Integer:
buf: [40]byte buf: [40]byte
u: u128 u := cast_any_int_to_u128(a)
switch i in a {
case i8: u = u128(i)
case i16: u = u128(i)
case i32: u = u128(i)
case i64: u = u128(i)
case i128: u = u128(i)
case int: u = u128(i)
case u8: u = u128(i)
case u16: u = u128(i)
case u32: u = u128(i)
case u64: u = u128(i)
case u128: u = u128(i)
case uint: u = u128(i)
case uintptr: u = u128(i)
case i16le: u = u128(i)
case i32le: u = u128(i)
case i64le: u = u128(i)
case u16le: u = u128(i)
case u32le: u = u128(i)
case u64le: u = u128(i)
case u128le: u = u128(i)
case i16be: u = u128(i)
case i32be: u = u128(i)
case i64be: u = u128(i)
case u16be: u = u128(i)
case u32be: u = u128(i)
case u64be: u = u128(i)
case u128be: u = u128(i)
}
s: string s: string
@@ -310,7 +279,12 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
case cstring: name = string(s) case cstring: name = string(s)
} }
opt_write_key(w, opt, name) or_return opt_write_key(w, opt, name) or_return
case runtime.Type_Info_Integer:
buf: [40]byte
u := cast_any_int_to_u128(ka)
name = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*kti.size, "0123456789", nil)
opt_write_key(w, opt, name) or_return
case: return .Unsupported_Type case: return .Unsupported_Type
} }
} }
@@ -406,10 +380,15 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
ti := runtime.type_info_base(type_info_of(v.id)) ti := runtime.type_info_base(type_info_of(v.id))
info := ti.variant.(runtime.Type_Info_Struct) info := ti.variant.(runtime.Type_Info_Struct)
first_iteration := true first_iteration := true
for name, i in info.names { for name, i in info.names[:info.field_count] {
omitempty := false omitempty := false
json_name, extra := json_name_from_tag_value(reflect.struct_tag_get(reflect.Struct_Tag(info.tags[i]), "json")) json_name, extra := json_name_from_tag_value(reflect.struct_tag_get(reflect.Struct_Tag(info.tags[i]), "json"))
if json_name == "-" {
continue
}
for flag in strings.split_iterator(&extra, ",") { for flag in strings.split_iterator(&extra, ",") {
switch flag { switch flag {
case "omitempty": case "omitempty":
@@ -657,3 +636,41 @@ opt_write_indentation :: proc(w: io.Writer, opt: ^Marshal_Options) -> (err: io.E
return return
} }
@(private)
cast_any_int_to_u128 :: proc(any_int_value: any) -> u128 {
u: u128 = 0
switch i in any_int_value {
case i8: u = u128(i)
case i16: u = u128(i)
case i32: u = u128(i)
case i64: u = u128(i)
case i128: u = u128(i)
case int: u = u128(i)
case u8: u = u128(i)
case u16: u = u128(i)
case u32: u = u128(i)
case u64: u = u128(i)
case u128: u = u128(i)
case uint: u = u128(i)
case uintptr: u = u128(i)
case i16le: u = u128(i)
case i32le: u = u128(i)
case i64le: u = u128(i)
case u16le: u = u128(i)
case u32le: u = u128(i)
case u64le: u = u128(i)
case u128le: u = u128(i)
case i16be: u = u128(i)
case i32be: u = u128(i)
case i64be: u = u128(i)
case u16be: u = u128(i)
case u32be: u = u128(i)
case u64be: u = u128(i)
case u128be: u = u128(i)
}
return u
}
+18 -5
View File
@@ -363,12 +363,11 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
} }
v := v v := v
v = reflect.any_base(v) ti := reflect.type_info_base(type_info_of(v.id))
ti := type_info_of(v.id)
#partial switch t in ti.variant { #partial switch t in ti.variant {
case reflect.Type_Info_Struct: case reflect.Type_Info_Struct:
if t.is_raw_union { if .raw_union in t.flags {
return UNSUPPORTED_TYPE return UNSUPPORTED_TYPE
} }
@@ -475,7 +474,7 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
} }
case reflect.Type_Info_Map: case reflect.Type_Info_Map:
if !reflect.is_string(t.key) { if !reflect.is_string(t.key) && !reflect.is_integer(t.key) {
return UNSUPPORTED_TYPE return UNSUPPORTED_TYPE
} }
raw_map := (^mem.Raw_Map)(v.data) raw_map := (^mem.Raw_Map)(v.data)
@@ -499,19 +498,33 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
return uerr return uerr
} }
key_ptr := rawptr(&key) key_ptr: rawptr
#partial switch tk in t.key.variant {
case runtime.Type_Info_String:
key_ptr = rawptr(&key)
key_cstr: cstring key_cstr: cstring
if reflect.is_cstring(t.key) { if reflect.is_cstring(t.key) {
key_cstr = cstring(raw_data(key)) key_cstr = cstring(raw_data(key))
key_ptr = &key_cstr key_ptr = &key_cstr
} }
case runtime.Type_Info_Integer:
i, ok := strconv.parse_i128(key)
if !ok { return UNSUPPORTED_TYPE }
key_ptr = rawptr(&i)
case: return UNSUPPORTED_TYPE
}
set_ptr := runtime.__dynamic_map_set_without_hash(raw_map, t.map_info, key_ptr, map_backing_value.data) set_ptr := runtime.__dynamic_map_set_without_hash(raw_map, t.map_info, key_ptr, map_backing_value.data)
if set_ptr == nil { if set_ptr == nil {
delete(key, p.allocator) delete(key, p.allocator)
} }
// there's no need to keep string value on the heap, since it was copied into map
if reflect.is_integer(t.key) {
delete(key, p.allocator)
}
if parse_comma(p) { if parse_comma(p) {
break map_loop break map_loop
} }
+44 -25
View File
@@ -334,6 +334,27 @@ panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! {
message := tprintf(fmt, ..args) message := tprintf(fmt, ..args)
p("Panic", message, loc) p("Panic", message, loc)
} }
// Creates a formatted C string
//
// *Allocates Using Context's Allocator*
//
// Inputs:
// - args: A variadic list of arguments to be formatted.
// - sep: An optional separator string (default is a single space).
//
// Returns: A formatted C string.
//
@(require_results)
caprint :: proc(args: ..any, sep := " ", allocator := context.allocator) -> cstring {
str: strings.Builder
strings.builder_init(&str, allocator)
sbprint(&str, ..args, sep=sep)
strings.write_byte(&str, 0)
s := strings.to_string(str)
return cstring(raw_data(s))
}
// Creates a formatted C string // Creates a formatted C string
// //
// *Allocates Using Context's Allocator* // *Allocates Using Context's Allocator*
@@ -346,9 +367,9 @@ panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! {
// Returns: A formatted C string // Returns: A formatted C string
// //
@(require_results) @(require_results)
caprintf :: proc(format: string, args: ..any, newline := false) -> cstring { caprintf :: proc(format: string, args: ..any, allocator := context.allocator, newline := false) -> cstring {
str: strings.Builder str: strings.Builder
strings.builder_init(&str) strings.builder_init(&str, allocator)
sbprintf(&str, format, ..args, newline=newline) sbprintf(&str, format, ..args, newline=newline)
strings.write_byte(&str, 0) strings.write_byte(&str, 0)
s := strings.to_string(str) s := strings.to_string(str)
@@ -365,8 +386,8 @@ caprintf :: proc(format: string, args: ..any, newline := false) -> cstring {
// Returns: A formatted C string // Returns: A formatted C string
// //
@(require_results) @(require_results)
caprintfln :: proc(format: string, args: ..any) -> cstring { caprintfln :: proc(format: string, args: ..any, allocator := context.allocator) -> cstring {
return caprintf(format, ..args, newline=true) return caprintf(format, ..args, allocator=allocator, newline=true)
} }
// Creates a formatted C string // Creates a formatted C string
// //
@@ -380,12 +401,7 @@ caprintfln :: proc(format: string, args: ..any) -> cstring {
// //
@(require_results) @(require_results)
ctprint :: proc(args: ..any, sep := " ") -> cstring { ctprint :: proc(args: ..any, sep := " ") -> cstring {
str: strings.Builder return caprint(args=args, sep=sep, allocator=context.temp_allocator)
strings.builder_init(&str, context.temp_allocator)
sbprint(&str, ..args, sep=sep)
strings.write_byte(&str, 0)
s := strings.to_string(str)
return cstring(raw_data(s))
} }
// Creates a formatted C string // Creates a formatted C string
// //
@@ -400,12 +416,7 @@ ctprint :: proc(args: ..any, sep := " ") -> cstring {
// //
@(require_results) @(require_results)
ctprintf :: proc(format: string, args: ..any, newline := false) -> cstring { ctprintf :: proc(format: string, args: ..any, newline := false) -> cstring {
str: strings.Builder return caprintf(format=format, args=args, allocator=context.temp_allocator, newline=newline)
strings.builder_init(&str, context.temp_allocator)
sbprintf(&str, format, ..args, newline=newline)
strings.write_byte(&str, 0)
s := strings.to_string(str)
return cstring(raw_data(s))
} }
// Creates a formatted C string, followed by a newline. // Creates a formatted C string, followed by a newline.
// //
@@ -419,7 +430,7 @@ ctprintf :: proc(format: string, args: ..any, newline := false) -> cstring {
// //
@(require_results) @(require_results)
ctprintfln :: proc(format: string, args: ..any) -> cstring { ctprintfln :: proc(format: string, args: ..any) -> cstring {
return ctprintf(format, ..args, newline=true) return caprintf(format=format, args=args, allocator=context.temp_allocator, newline=true)
} }
// Formats using the default print settings and writes to the given strings.Builder // Formats using the default print settings and writes to the given strings.Builder
// //
@@ -1861,7 +1872,7 @@ handle_tag :: proc(state: ^Info_State, data: rawptr, info: reflect.Type_Info_Str
if optional_len == nil { if optional_len == nil {
return return
} }
for f, i in info.names { for f, i in info.names[:info.field_count] {
if f != field_name { if f != field_name {
continue continue
} }
@@ -1965,7 +1976,7 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St
fmt_bad_verb(fi, the_verb) fmt_bad_verb(fi, the_verb)
return return
} }
if info.is_raw_union { if .raw_union in info.flags {
if type_name == "" { if type_name == "" {
io.write_string(fi.writer, "(raw union)", &fi.n) io.write_string(fi.writer, "(raw union)", &fi.n)
} else { } else {
@@ -1989,7 +2000,7 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St
// fi.hash = false; // fi.hash = false;
fi.indent += 1 fi.indent += 1
is_empty := len(info.names) == 0 is_empty := info.field_count == 0
if !is_soa && hash && !is_empty { if !is_soa && hash && !is_empty {
io.write_byte(fi.writer, '\n', &fi.n) io.write_byte(fi.writer, '\n', &fi.n)
@@ -2010,17 +2021,17 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St
base_type_name = v.name base_type_name = v.name
} }
actual_field_count := len(info.names) actual_field_count := info.field_count
n := uintptr(info.soa_len) n := uintptr(info.soa_len)
if info.soa_kind == .Slice { if info.soa_kind == .Slice {
actual_field_count = len(info.names)-1 // len actual_field_count = info.field_count-1 // len
n = uintptr((^int)(uintptr(v.data) + info.offsets[actual_field_count])^) n = uintptr((^int)(uintptr(v.data) + info.offsets[actual_field_count])^)
} else if info.soa_kind == .Dynamic { } else if info.soa_kind == .Dynamic {
actual_field_count = len(info.names)-3 // len, cap, allocator actual_field_count = info.field_count-3 // len, cap, allocator
n = uintptr((^int)(uintptr(v.data) + info.offsets[actual_field_count])^) n = uintptr((^int)(uintptr(v.data) + info.offsets[actual_field_count])^)
} }
@@ -2099,7 +2110,7 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St
} }
} else { } else {
field_count := -1 field_count := -1
for name, i in info.names { for name, i in info.names[:info.field_count] {
optional_len: int = -1 optional_len: int = -1
use_nul_termination: bool = false use_nul_termination: bool = false
verb := the_verb if the_verb == 'w' else 'v' verb := the_verb if the_verb == 'w' else 'v'
@@ -2605,7 +2616,7 @@ fmt_bit_field :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Bit
field_count := -1 field_count := -1
for name, i in info.names { for name, i in info.names[:info.field_count] {
field_verb := verb field_verb := verb
if handle_bit_field_tag(v.data, info, i, &field_verb) { if handle_bit_field_tag(v.data, info, i, &field_verb) {
continue continue
@@ -2751,9 +2762,11 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
elem := runtime.type_info_base(info.elem) elem := runtime.type_info_base(info.elem)
if elem != nil { if elem != nil {
if n, ok := fi.optional_len.?; ok { if n, ok := fi.optional_len.?; ok {
fi.optional_len = nil
fmt_array(fi, ptr, n, elem.size, elem, verb) fmt_array(fi, ptr, n, elem.size, elem, verb)
return return
} else if fi.use_nul_termination { } else if fi.use_nul_termination {
fi.use_nul_termination = false
fmt_array_nul_terminated(fi, ptr, -1, elem.size, elem, verb) fmt_array_nul_terminated(fi, ptr, -1, elem.size, elem, verb)
return return
} }
@@ -2855,8 +2868,10 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
n := info.count n := info.count
ptr := v.data ptr := v.data
if ol, ok := fi.optional_len.?; ok { if ol, ok := fi.optional_len.?; ok {
fi.optional_len = nil
n = min(n, ol) n = min(n, ol)
} else if fi.use_nul_termination { } else if fi.use_nul_termination {
fi.use_nul_termination = false
fmt_array_nul_terminated(fi, ptr, n, info.elem_size, info.elem, verb) fmt_array_nul_terminated(fi, ptr, n, info.elem_size, info.elem, verb)
return return
} }
@@ -2867,8 +2882,10 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
n := slice.len n := slice.len
ptr := slice.data ptr := slice.data
if ol, ok := fi.optional_len.?; ok { if ol, ok := fi.optional_len.?; ok {
fi.optional_len = nil
n = min(n, ol) n = min(n, ol)
} else if fi.use_nul_termination { } else if fi.use_nul_termination {
fi.use_nul_termination = false
fmt_array_nul_terminated(fi, ptr, n, info.elem_size, info.elem, verb) fmt_array_nul_terminated(fi, ptr, n, info.elem_size, info.elem, verb)
return return
} }
@@ -2879,8 +2896,10 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
n := array.len n := array.len
ptr := array.data ptr := array.data
if ol, ok := fi.optional_len.?; ok { if ol, ok := fi.optional_len.?; ok {
fi.optional_len = nil
n = min(n, ol) n = min(n, ol)
} else if fi.use_nul_termination { } else if fi.use_nul_termination {
fi.use_nul_termination = false
fmt_array_nul_terminated(fi, ptr, n, info.elem_size, info.elem, verb) fmt_array_nul_terminated(fi, ptr, n, info.elem_size, info.elem, verb)
return return
} }
+6 -56
View File
@@ -229,7 +229,7 @@ sqrt_complex128 :: proc "contextless" (x: complex128) -> complex128 {
} }
ln_complex32 :: proc "contextless" (x: complex32) -> complex32 { ln_complex32 :: proc "contextless" (x: complex32) -> complex32 {
return complex(math.ln(abs(x)), phase(x)) return complex32(ln_complex64(complex64(x)))
} }
ln_complex64 :: proc "contextless" (x: complex64) -> complex64 { ln_complex64 :: proc "contextless" (x: complex64) -> complex64 {
return complex(math.ln(abs(x)), phase(x)) return complex(math.ln(abs(x)), phase(x))
@@ -240,26 +240,7 @@ ln_complex128 :: proc "contextless" (x: complex128) -> complex128 {
exp_complex32 :: proc "contextless" (x: complex32) -> complex32 { exp_complex32 :: proc "contextless" (x: complex32) -> complex32 {
switch re, im := real(x), imag(x); { return complex32(exp_complex64(complex64(x)))
case math.is_inf(re, 0):
switch {
case re > 0 && im == 0:
return x
case math.is_inf(im, 0) || math.is_nan(im):
if re < 0 {
return complex(0, math.copy_sign(0, im))
} else {
return complex(math.inf_f64(1.0), math.nan_f64())
}
}
case math.is_nan(re):
if im == 0 {
return complex(math.nan_f16(), im)
}
}
r := math.exp(real(x))
s, c := math.sincos(imag(x))
return complex(r*c, r*s)
} }
exp_complex64 :: proc "contextless" (x: complex64) -> complex64 { exp_complex64 :: proc "contextless" (x: complex64) -> complex64 {
switch re, im := real(x), imag(x); { switch re, im := real(x), imag(x); {
@@ -308,37 +289,7 @@ exp_complex128 :: proc "contextless" (x: complex128) -> complex128 {
pow_complex32 :: proc "contextless" (x, y: complex32) -> complex32 { pow_complex32 :: proc "contextless" (x, y: complex32) -> complex32 {
if x == 0 { // Guaranteed also true for x == -0. return complex32(pow_complex64(complex64(x), complex64(y)))
if is_nan(y) {
return nan_complex32()
}
r, i := real(y), imag(y)
switch {
case r == 0:
return 1
case r < 0:
if i == 0 {
return complex(math.inf_f16(1), 0)
}
return inf_complex32()
case r > 0:
return 0
}
unreachable()
}
modulus := abs(x)
if modulus == 0 {
return complex(0, 0)
}
r := math.pow(modulus, real(y))
arg := phase(x)
theta := real(y) * arg
if imag(y) != 0 {
r *= math.exp(-imag(y) * arg)
theta += imag(y) * math.ln(modulus)
}
s, c := math.sincos(theta)
return complex(r*c, r*s)
} }
pow_complex64 :: proc "contextless" (x, y: complex64) -> complex64 { pow_complex64 :: proc "contextless" (x, y: complex64) -> complex64 {
if x == 0 { // Guaranteed also true for x == -0. if x == 0 { // Guaranteed also true for x == -0.
@@ -410,7 +361,7 @@ pow_complex128 :: proc "contextless" (x, y: complex128) -> complex128 {
log10_complex32 :: proc "contextless" (x: complex32) -> complex32 { log10_complex32 :: proc "contextless" (x: complex32) -> complex32 {
return math.LN10*ln(x) return complex32(log10_complex64(complex64(x)))
} }
log10_complex64 :: proc "contextless" (x: complex64) -> complex64 { log10_complex64 :: proc "contextless" (x: complex64) -> complex64 {
return math.LN10*ln(x) return math.LN10*ln(x)
@@ -421,7 +372,7 @@ log10_complex128 :: proc "contextless" (x: complex128) -> complex128 {
phase_complex32 :: proc "contextless" (x: complex32) -> f16 { phase_complex32 :: proc "contextless" (x: complex32) -> f16 {
return math.atan2(imag(x), real(x)) return f16(phase_complex64(complex64(x)))
} }
phase_complex64 :: proc "contextless" (x: complex64) -> f32 { phase_complex64 :: proc "contextless" (x: complex64) -> f32 {
return math.atan2(imag(x), real(x)) return math.atan2(imag(x), real(x))
@@ -432,8 +383,7 @@ phase_complex128 :: proc "contextless" (x: complex128) -> f64 {
rect_complex32 :: proc "contextless" (r, θ: f16) -> complex32 { rect_complex32 :: proc "contextless" (r, θ: f16) -> complex32 {
s, c := math.sincos(θ) return complex32(rect_complex64(f32(r), f32(θ)))
return complex(r*c, r*s)
} }
rect_complex64 :: proc "contextless" (r, θ: f32) -> complex64 { rect_complex64 :: proc "contextless" (r, θ: f32) -> complex64 {
s, c := math.sincos(θ) s, c := math.sincos(θ)
+3 -13
View File
@@ -61,8 +61,7 @@ atanh :: proc{
acos_complex32 :: proc "contextless" (x: complex32) -> complex32 { acos_complex32 :: proc "contextless" (x: complex32) -> complex32 {
w := asin(x) return complex32(acos_complex64(complex64(x)))
return complex(math.PI/2 - real(w), -imag(w))
} }
acos_complex64 :: proc "contextless" (x: complex64) -> complex64 { acos_complex64 :: proc "contextless" (x: complex64) -> complex64 {
w := asin(x) w := asin(x)
@@ -75,14 +74,7 @@ acos_complex128 :: proc "contextless" (x: complex128) -> complex128 {
acosh_complex32 :: proc "contextless" (x: complex32) -> complex32 { acosh_complex32 :: proc "contextless" (x: complex32) -> complex32 {
if x == 0 { return complex32(acosh_complex64(complex64(x)))
return complex(0, math.copy_sign(math.PI/2, imag(x)))
}
w := acos(x)
if imag(w) <= 0 {
return complex(-imag(w), real(w))
}
return complex(imag(w), -real(w))
} }
acosh_complex64 :: proc "contextless" (x: complex64) -> complex64 { acosh_complex64 :: proc "contextless" (x: complex64) -> complex64 {
if x == 0 { if x == 0 {
@@ -257,9 +249,7 @@ atan_complex128 :: proc "contextless" (x: complex128) -> complex128 {
} }
atanh_complex32 :: proc "contextless" (x: complex32) -> complex32 { atanh_complex32 :: proc "contextless" (x: complex32) -> complex32 {
z := complex(-imag(x), real(x)) // z = i * x return complex32(atanh_complex64(complex64(x)))
z = atan(z)
return complex(imag(z), -real(z)) // z = -i * z
} }
atanh_complex64 :: proc "contextless" (x: complex64) -> complex64 { atanh_complex64 :: proc "contextless" (x: complex64) -> complex64 {
z := complex(-imag(x), real(x)) // z = i * x z := complex(-imag(x), real(x)) // z = i * x
+7 -1
View File
@@ -618,10 +618,16 @@ shuffle :: proc(array: $T/[]$E, gen := context.random_generator) {
return return
} }
for i := i64(n - 1); i > 0; i -= 1 { i := n - 1
for ; i > (1<<31 - 2); i -= 1 {
j := int63_max(i + 1, gen) j := int63_max(i + 1, gen)
array[i], array[j] = array[j], array[i] array[i], array[j] = array[j], array[i]
} }
for ; i > 0; i -= 1 {
j := int31_max(i32(i + 1), gen)
array[i], array[j] = array[j], array[i]
}
} }
/* /*
+3 -3
View File
@@ -117,7 +117,7 @@ _wrap_os_addr :: proc "contextless" (addr: linux.Sock_Addr_Any)->(Endpoint) {
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (Any_Socket, Network_Error) { _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (Any_Socket, Network_Error) {
family := _unwrap_os_family(family) family := _unwrap_os_family(family)
proto, socktype := _unwrap_os_proto_socktype(protocol) proto, socktype := _unwrap_os_proto_socktype(protocol)
sock, errno := linux.socket(family, socktype, {}, proto) sock, errno := linux.socket(family, socktype, {.CLOEXEC}, proto)
if errno != .NONE { if errno != .NONE {
return {}, Create_Socket_Error(errno) return {}, Create_Socket_Error(errno)
} }
@@ -132,7 +132,7 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
} }
// Create new TCP socket // Create new TCP socket
os_sock: linux.Fd os_sock: linux.Fd
os_sock, errno = linux.socket(_unwrap_os_family(family_from_endpoint(endpoint)), .STREAM, {}, .TCP) os_sock, errno = linux.socket(_unwrap_os_family(family_from_endpoint(endpoint)), .STREAM, {.CLOEXEC}, .TCP)
if errno != .NONE { if errno != .NONE {
// TODO(flysand): should return invalid file descriptor here casted as TCP_Socket // TODO(flysand): should return invalid file descriptor here casted as TCP_Socket
return {}, Create_Socket_Error(errno) return {}, Create_Socket_Error(errno)
@@ -172,7 +172,7 @@ _listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (TCP_Socket, Network
ep_address := _unwrap_os_addr(endpoint) ep_address := _unwrap_os_addr(endpoint)
// Create TCP socket // Create TCP socket
os_sock: linux.Fd os_sock: linux.Fd
os_sock, errno = linux.socket(ep_family, .STREAM, {}, .TCP) os_sock, errno = linux.socket(ep_family, .STREAM, {.CLOEXEC}, .TCP)
if errno != .NONE { if errno != .NONE {
// TODO(flysand): should return invalid file descriptor here casted as TCP_Socket // TODO(flysand): should return invalid file descriptor here casted as TCP_Socket
return {}, Create_Socket_Error(errno) return {}, Create_Socket_Error(errno)
+3
View File
@@ -599,6 +599,7 @@ Field_Flag :: enum {
Subtype, Subtype,
By_Ptr, By_Ptr,
No_Broadcast, No_Broadcast,
No_Capture,
Results, Results,
Tags, Tags,
@@ -619,6 +620,7 @@ field_flag_strings := [Field_Flag]string{
.Subtype = "#subtype", .Subtype = "#subtype",
.By_Ptr = "#by_ptr", .By_Ptr = "#by_ptr",
.No_Broadcast = "#no_broadcast", .No_Broadcast = "#no_broadcast",
.No_Capture = "#no_capture",
.Results = "results", .Results = "results",
.Tags = "field tag", .Tags = "field tag",
@@ -634,6 +636,7 @@ field_hash_flag_strings := []struct{key: string, flag: Field_Flag}{
{"subtype", .Subtype}, {"subtype", .Subtype},
{"by_ptr", .By_Ptr}, {"by_ptr", .By_Ptr},
{"no_broadcast", .No_Broadcast}, {"no_broadcast", .No_Broadcast},
{"no_capture", .No_Capture},
} }
+9 -6
View File
@@ -2179,22 +2179,25 @@ parse_inlining_operand :: proc(p: ^Parser, lhs: bool, tok: tokenizer.Token) -> ^
} }
} }
if expr != nil {
#partial switch e in ast.strip_or_return_expr(expr).derived_expr { #partial switch e in ast.strip_or_return_expr(expr).derived_expr {
case ^ast.Proc_Lit: case ^ast.Proc_Lit:
if e.inlining != .None && e.inlining != pi { if e.inlining != .None && e.inlining != pi {
error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure literal") error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure literal")
} }
e.inlining = pi e.inlining = pi
return expr
case ^ast.Call_Expr: case ^ast.Call_Expr:
if e.inlining != .None && e.inlining != pi { if e.inlining != .None && e.inlining != pi {
error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure call") error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure call")
} }
e.inlining = pi e.inlining = pi
case: return expr
}
}
error(p, tok.pos, "'%s' must be followed by a procedure literal or call", tok.text) error(p, tok.pos, "'%s' must be followed by a procedure literal or call", tok.text)
return ast.new(ast.Bad_Expr, tok.pos, expr) return ast.new(ast.Bad_Expr, tok.pos, expr)
}
return expr
} }
parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
@@ -2258,18 +2261,18 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
hp.type = type hp.type = type
return hp return hp
case "file", "line", "procedure", "caller_location": case "file", "directory", "line", "procedure", "caller_location":
bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name)) bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name))
bd.tok = tok bd.tok = tok
bd.name = name.text bd.name = name.text
return bd return bd
case "location", "load", "assert", "defined", "config":
case "location", "exists", "load", "load_directory", "load_hash", "hash", "assert", "panic", "defined", "config":
bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name)) bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name))
bd.tok = tok bd.tok = tok
bd.name = name.text bd.name = name.text
return parse_call_expr(p, bd) return parse_call_expr(p, bd)
case "soa": case "soa":
bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name)) bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name))
bd.tok = tok bd.tok = tok
+80
View File
@@ -0,0 +1,80 @@
package os2
import "base:runtime"
import "core:slice"
@(require_results)
read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files: []File_Info, err: Error) {
if f == nil {
return nil, .Invalid_File
}
n := n
size := n
if n <= 0 {
n = -1
size = 100
}
TEMP_ALLOCATOR_GUARD()
it := read_directory_iterator_create(f) or_return
defer _read_directory_iterator_destroy(&it)
dfi := make([dynamic]File_Info, 0, size, temp_allocator())
defer if err != nil {
for fi in dfi {
file_info_delete(fi, allocator)
}
}
for fi, index in read_directory_iterator(&it) {
if n > 0 && index == n {
break
}
append(&dfi, file_info_clone(fi, allocator) or_return)
}
return slice.clone(dfi[:], allocator)
}
@(require_results)
read_all_directory :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) {
return read_directory(f, -1, allocator)
}
@(require_results)
read_directory_by_path :: proc(path: string, n: int, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) {
f := open(path) or_return
defer close(f)
return read_directory(f, n, allocator)
}
@(require_results)
read_all_directory_by_path :: proc(path: string, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) {
return read_directory_by_path(path, -1, allocator)
}
Read_Directory_Iterator :: struct {
f: ^File,
impl: Read_Directory_Iterator_Impl,
}
@(require_results)
read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) {
return _read_directory_iterator_create(f)
}
read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
_read_directory_iterator_destroy(it)
}
// NOTE(bill): `File_Info` does not need to deleted on each iteration. Any copies must be manually copied with `file_info_clone`
@(require_results)
read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
return _read_directory_iterator(it)
}
+20
View File
@@ -0,0 +1,20 @@
//+private
package os2
Read_Directory_Iterator_Impl :: struct {
}
@(require_results)
_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
return
}
@(require_results)
_read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) {
return {}, nil
}
_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
}
+141
View File
@@ -0,0 +1,141 @@
//+private
package os2
import "base:runtime"
import "core:time"
import win32 "core:sys/windows"
@(private="file")
find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
// Ignore "." and ".."
if d.cFileName[0] == '.' && d.cFileName[1] == 0 {
return
}
if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 {
return
}
path := concatenate({base_path, `\`, win32_utf16_to_utf8(d.cFileName[:], temp_allocator()) or_else ""}, allocator) or_return
fi.fullpath = path
fi.name = basename(path)
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
fi.type, fi.mode = _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, d.dwReserved0)
fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime))
fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime))
fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
handle := win32.HANDLE(_open_internal(path, {.Read}, 0o666) or_else 0)
defer win32.CloseHandle(handle)
if file_id_info: win32.FILE_ID_INFO; handle != nil && win32.GetFileInformationByHandleEx(handle, .FileIdInfo, &file_id_info, size_of(file_id_info)) {
#assert(size_of(fi.inode) == size_of(file_id_info.FileId))
#assert(size_of(fi.inode) == 16)
runtime.mem_copy_non_overlapping(&fi.inode, &file_id_info.FileId, 16)
}
return
}
Read_Directory_Iterator_Impl :: struct {
find_data: win32.WIN32_FIND_DATAW,
find_handle: win32.HANDLE,
path: string,
prev_fi: File_Info,
no_more_files: bool,
index: int,
}
@(require_results)
_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
if it.f == nil {
return
}
TEMP_ALLOCATOR_GUARD()
for !it.impl.no_more_files {
err: Error
file_info_delete(it.impl.prev_fi, file_allocator())
it.impl.prev_fi = {}
fi, err = find_data_to_file_info(it.impl.path, &it.impl.find_data, file_allocator())
if err != nil {
return
}
if fi.name != "" {
it.impl.prev_fi = fi
ok = true
index = it.impl.index
it.impl.index += 1
}
if !win32.FindNextFileW(it.impl.find_handle, &it.impl.find_data) {
e := _get_platform_error()
if pe, _ := is_platform_error(e); pe == i32(win32.ERROR_NO_MORE_FILES) {
it.impl.no_more_files = true
}
it.impl.no_more_files = true
}
if ok {
return
}
}
return
}
@(require_results)
_read_directory_iterator_create :: proc(f: ^File) -> (it: Read_Directory_Iterator, err: Error) {
if f == nil {
return
}
it.f = f
impl := (^File_Impl)(f.impl)
if !is_directory(impl.name) {
err = .Invalid_Dir
return
}
wpath: []u16
{
i := 0
for impl.wname[i] != 0 {
i += 1
}
wpath = impl.wname[:i]
}
TEMP_ALLOCATOR_GUARD()
wpath_search := make([]u16, len(wpath)+3, temp_allocator())
copy(wpath_search, wpath)
wpath_search[len(wpath)+0] = '\\'
wpath_search[len(wpath)+1] = '*'
wpath_search[len(wpath)+2] = 0
it.impl.find_handle = win32.FindFirstFileW(raw_data(wpath_search), &it.impl.find_data)
if it.impl.find_handle == win32.INVALID_HANDLE_VALUE {
err = _get_platform_error()
return
}
defer if err != nil {
win32.FindClose(it.impl.find_handle)
}
it.impl.path = _cleanpath_from_buf(wpath, file_allocator()) or_return
return
}
_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
if it.f == nil {
return
}
file_info_delete(it.impl.prev_fi, file_allocator())
win32.FindClose(it.impl.find_handle)
}
+9 -6
View File
@@ -8,7 +8,8 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
if key == "" { if key == "" {
return return
} }
wkey := win32.utf8_to_wstring(key) TEMP_ALLOCATOR_GUARD()
wkey, _ := win32_utf8_to_wstring(key, temp_allocator())
n := win32.GetEnvironmentVariableW(wkey, nil, 0) n := win32.GetEnvironmentVariableW(wkey, nil, 0)
if n == 0 { if n == 0 {
@@ -32,20 +33,22 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
return "", false return "", false
} }
value = win32.utf16_to_utf8(b[:n], allocator) or_else "" value = win32_utf16_to_utf8(b[:n], allocator) or_else ""
found = true found = true
return return
} }
_set_env :: proc(key, value: string) -> bool { _set_env :: proc(key, value: string) -> bool {
k := win32.utf8_to_wstring(key) TEMP_ALLOCATOR_GUARD()
v := win32.utf8_to_wstring(value) k, _ := win32_utf8_to_wstring(key, temp_allocator())
v, _ := win32_utf8_to_wstring(value, temp_allocator())
return bool(win32.SetEnvironmentVariableW(k, v)) return bool(win32.SetEnvironmentVariableW(k, v))
} }
_unset_env :: proc(key: string) -> bool { _unset_env :: proc(key: string) -> bool {
k := win32.utf8_to_wstring(key) TEMP_ALLOCATOR_GUARD()
k, _ := win32_utf8_to_wstring(key, temp_allocator())
return bool(win32.SetEnvironmentVariableW(k, nil)) return bool(win32.SetEnvironmentVariableW(k, nil))
} }
@@ -89,7 +92,7 @@ _environ :: proc(allocator: runtime.Allocator) -> []string {
break break
} }
w := ([^]u16)(p)[from:i] w := ([^]u16)(p)[from:i]
append(&r, win32.utf16_to_utf8(w, allocator) or_else "") append(&r, win32_utf16_to_utf8(w, allocator) or_else "")
from = i + 1 from = i + 1
} }
} }
+4
View File
@@ -22,6 +22,7 @@ General_Error :: enum u32 {
Invalid_File, Invalid_File,
Invalid_Dir, Invalid_Dir,
Invalid_Path, Invalid_Path,
Invalid_Callback,
Pattern_Has_Separator, Pattern_Has_Separator,
@@ -38,6 +39,8 @@ Error :: union #shared_nil {
} }
#assert(size_of(Error) == size_of(u64)) #assert(size_of(Error) == size_of(u64))
ERROR_NONE :: Error{}
is_platform_error :: proc(ferr: Error) -> (err: i32, ok: bool) { is_platform_error :: proc(ferr: Error) -> (err: i32, ok: bool) {
@@ -64,6 +67,7 @@ error_string :: proc(ferr: Error) -> string {
case .Invalid_File: return "invalid file" case .Invalid_File: return "invalid file"
case .Invalid_Dir: return "invalid directory" case .Invalid_Dir: return "invalid directory"
case .Invalid_Path: return "invalid path" case .Invalid_Path: return "invalid path"
case .Invalid_Callback: return "invalid callback"
case .Unsupported: return "unsupported" case .Unsupported: return "unsupported"
case .Pattern_Has_Separator: return "pattern has separator" case .Pattern_Has_Separator: return "pattern has separator"
} }
+10 -3
View File
@@ -1,6 +1,8 @@
//+private //+private
package os2 package os2
import "base:runtime"
import "core:slice"
import win32 "core:sys/windows" import win32 "core:sys/windows"
_error_string :: proc(errno: i32) -> string { _error_string :: proc(errno: i32) -> string {
@@ -8,9 +10,14 @@ _error_string :: proc(errno: i32) -> string {
if e == 0 { if e == 0 {
return "" return ""
} }
// TODO(bill): _error_string for windows
// FormatMessageW err := runtime.Type_Info_Enum_Value(e)
return ""
ti := &runtime.type_info_base(type_info_of(win32.System_Error)).variant.(runtime.Type_Info_Enum)
if idx, ok := slice.binary_search(ti.values, err); ok {
return ti.names[idx]
}
return "<unknown platform error>"
} }
_get_platform_error :: proc() -> Error { _get_platform_error :: proc() -> Error {
+88 -20
View File
@@ -4,20 +4,57 @@ import "core:io"
import "core:time" import "core:time"
import "base:runtime" import "base:runtime"
/*
Type representing a file handle.
This struct represents an OS-specific file-handle, which can be one of
the following:
- File
- Directory
- Pipe
- Named pipe
- Block Device
- Character device
- Symlink
- Socket
See `File_Type` enum for more information on file types.
*/
File :: struct { File :: struct {
impl: _File, impl: rawptr,
stream: io.Stream, stream: io.Stream,
user_fstat: Fstat_Callback, fstat: Fstat_Callback,
} }
File_Mode :: distinct u32 /*
File_Mode_Dir :: File_Mode(1<<16) Type representing the type of a file handle.
File_Mode_Named_Pipe :: File_Mode(1<<17)
File_Mode_Device :: File_Mode(1<<18)
File_Mode_Char_Device :: File_Mode(1<<19)
File_Mode_Sym_Link :: File_Mode(1<<20)
File_Mode_Perm :: File_Mode(0o777) // Unix permision bits **Note(windows)**: Socket handles can not be distinguished from
files, as they are just a normal file handle that is being treated by
a special driver. Windows also makes no distinction between block and
character devices.
*/
File_Type :: enum {
// The type of a file could not be determined for the current platform.
Undetermined,
// Represents a regular file.
Regular,
// Represents a directory.
Directory,
// Represents a symbolic link.
Symlink,
// Represents a named pipe (FIFO).
Named_Pipe,
// Represents a socket.
// **Note(windows)**: Not returned on windows
Socket,
// Represents a block device.
// **Note(windows)**: On windows represents all devices.
Block_Device,
// Represents a character device.
// **Note(windows)**: Not returned on windows
Character_Device,
}
File_Flags :: distinct bit_set[File_Flag; uint] File_Flags :: distinct bit_set[File_Flag; uint]
File_Flag :: enum { File_Flag :: enum {
@@ -29,7 +66,7 @@ File_Flag :: enum {
Sync, Sync,
Trunc, Trunc,
Sparse, Sparse,
Close_On_Exec, Inheritable,
Unbuffered_IO, Unbuffered_IO,
} }
@@ -43,7 +80,15 @@ O_EXCL :: File_Flags{.Excl}
O_SYNC :: File_Flags{.Sync} O_SYNC :: File_Flags{.Sync}
O_TRUNC :: File_Flags{.Trunc} O_TRUNC :: File_Flags{.Trunc}
O_SPARSE :: File_Flags{.Sparse} O_SPARSE :: File_Flags{.Sparse}
O_CLOEXEC :: File_Flags{.Close_On_Exec}
/*
If specified, the file handle is inherited upon the creation of a child
process. By default all handles are created non-inheritable.
**Note**: The standard file handles (stderr, stdout and stdin) are always
initialized as inheritable.
*/
O_INHERITABLE :: File_Flags{.Inheritable}
stdin: ^File = nil // OS-Specific stdin: ^File = nil // OS-Specific
stdout: ^File = nil // OS-Specific stdout: ^File = nil // OS-Specific
@@ -51,17 +96,17 @@ stderr: ^File = nil // OS-Specific
@(require_results) @(require_results)
create :: proc(name: string) -> (^File, Error) { create :: proc(name: string) -> (^File, Error) {
return open(name, {.Read, .Write, .Create}, File_Mode(0o777)) return open(name, {.Read, .Write, .Create}, 0o777)
} }
@(require_results) @(require_results)
open :: proc(name: string, flags := File_Flags{.Read}, perm := File_Mode(0o777)) -> (^File, Error) { open :: proc(name: string, flags := File_Flags{.Read}, perm := 0o777) -> (^File, Error) {
return _open(name, flags, perm) return _open(name, flags, perm)
} }
@(require_results) @(require_results)
new_file :: proc(handle: uintptr, name: string) -> ^File { new_file :: proc(handle: uintptr, name: string) -> ^File {
return _new_file(handle, name) return _new_file(handle, name) or_else panic("Out of memory")
} }
@(require_results) @(require_results)
@@ -161,44 +206,56 @@ read_link :: proc(name: string, allocator: runtime.Allocator) -> (string, Error)
chdir :: change_directory chdir :: change_directory
change_directory :: proc(name: string) -> Error { change_directory :: proc(name: string) -> Error {
return _chdir(name) return _chdir(name)
} }
chmod :: change_mode chmod :: change_mode
change_mode :: proc(name: string, mode: File_Mode) -> Error {
change_mode :: proc(name: string, mode: int) -> Error {
return _chmod(name, mode) return _chmod(name, mode)
} }
chown :: change_owner chown :: change_owner
change_owner :: proc(name: string, uid, gid: int) -> Error { change_owner :: proc(name: string, uid, gid: int) -> Error {
return _chown(name, uid, gid) return _chown(name, uid, gid)
} }
fchdir :: fchange_directory fchdir :: fchange_directory
fchange_directory :: proc(f: ^File) -> Error { fchange_directory :: proc(f: ^File) -> Error {
return _fchdir(f) return _fchdir(f)
} }
fchmod :: fchange_mode fchmod :: fchange_mode
fchange_mode :: proc(f: ^File, mode: File_Mode) -> Error {
fchange_mode :: proc(f: ^File, mode: int) -> Error {
return _fchmod(f, mode) return _fchmod(f, mode)
} }
fchown :: fchange_owner fchown :: fchange_owner
fchange_owner :: proc(f: ^File, uid, gid: int) -> Error { fchange_owner :: proc(f: ^File, uid, gid: int) -> Error {
return _fchown(f, uid, gid) return _fchown(f, uid, gid)
} }
lchown :: change_owner_do_not_follow_links lchown :: change_owner_do_not_follow_links
change_owner_do_not_follow_links :: proc(name: string, uid, gid: int) -> Error { change_owner_do_not_follow_links :: proc(name: string, uid, gid: int) -> Error {
return _lchown(name, uid, gid) return _lchown(name, uid, gid)
} }
chtimes :: change_times chtimes :: change_times
change_times :: proc(name: string, atime, mtime: time.Time) -> Error { change_times :: proc(name: string, atime, mtime: time.Time) -> Error {
return _chtimes(name, atime, mtime) return _chtimes(name, atime, mtime)
} }
fchtimes :: fchange_times fchtimes :: fchange_times
fchange_times :: proc(f: ^File, atime, mtime: time.Time) -> Error { fchange_times :: proc(f: ^File, atime, mtime: time.Time) -> Error {
return _fchtimes(f, atime, mtime) return _fchtimes(f, atime, mtime)
} }
@@ -210,13 +267,24 @@ exists :: proc(path: string) -> bool {
@(require_results) @(require_results)
is_file :: proc(path: string) -> bool { is_file :: proc(path: string) -> bool {
return _is_file(path) TEMP_ALLOCATOR_GUARD()
fi, err := stat(path, temp_allocator())
if err != nil {
return false
}
return fi.type == .Regular
} }
is_dir :: is_directory is_dir :: is_directory
@(require_results) @(require_results)
is_directory :: proc(path: string) -> bool { is_directory :: proc(path: string) -> bool {
return _is_dir(path) TEMP_ALLOCATOR_GUARD()
fi, err := stat(path, temp_allocator())
if err != nil {
return false
}
return fi.type == .Directory
} }
@@ -226,11 +294,11 @@ copy_file :: proc(dst_path, src_path: string) -> Error {
info := fstat(src, file_allocator()) or_return info := fstat(src, file_allocator()) or_return
defer file_info_delete(info, file_allocator()) defer file_info_delete(info, file_allocator())
if info.is_directory { if info.type == .Directory {
return .Invalid_File return .Invalid_File
} }
dst := open(dst_path, {.Read, .Write, .Create, .Trunc}, info.mode & File_Mode_Perm) or_return dst := open(dst_path, {.Read, .Write, .Create, .Trunc}, info.mode & 0o777) or_return
defer close(dst) defer close(dst)
_, err := io.copy(to_writer(dst), to_reader(src)) _, err := io.copy(to_writer(dst), to_reader(src))
+81 -102
View File
@@ -6,14 +6,15 @@ import "core:time"
import "base:runtime" import "base:runtime"
import "core:sys/linux" import "core:sys/linux"
_File :: struct { File_Impl :: struct {
file: File,
name: string, name: string,
fd: linux.Fd, fd: linux.Fd,
allocator: runtime.Allocator, allocator: runtime.Allocator,
} }
_stdin : File = { _stdin := File{
impl = { impl = &File_Impl{
name = "/proc/self/fd/0", name = "/proc/self/fd/0",
fd = 0, fd = 0,
allocator = _file_allocator(), allocator = _file_allocator(),
@@ -21,9 +22,10 @@ _stdin : File = {
stream = { stream = {
procedure = _file_stream_proc, procedure = _file_stream_proc,
}, },
fstat = _fstat,
} }
_stdout : File = { _stdout := File{
impl = { impl = &File_Impl{
name = "/proc/self/fd/1", name = "/proc/self/fd/1",
fd = 1, fd = 1,
allocator = _file_allocator(), allocator = _file_allocator(),
@@ -31,9 +33,10 @@ _stdout : File = {
stream = { stream = {
procedure = _file_stream_proc, procedure = _file_stream_proc,
}, },
fstat = _fstat,
} }
_stderr : File = { _stderr := File{
impl = { impl = &File_Impl{
name = "/proc/self/fd/2", name = "/proc/self/fd/2",
fd = 2, fd = 2,
allocator = _file_allocator(), allocator = _file_allocator(),
@@ -41,6 +44,7 @@ _stderr : File = {
stream = { stream = {
procedure = _file_stream_proc, procedure = _file_stream_proc,
}, },
fstat = _fstat,
} }
@init @init
@@ -59,70 +63,67 @@ _file_allocator :: proc() -> runtime.Allocator {
return heap_allocator() return heap_allocator()
} }
_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, err: Error) { _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) {
TEMP_ALLOCATOR_GUARD() TEMP_ALLOCATOR_GUARD()
name_cstr := temp_cstring(name) or_return name_cstr := temp_cstring(name) or_return
// Just default to using O_NOCTTY because needing to open a controlling // Just default to using O_NOCTTY because needing to open a controlling
// terminal would be incredibly rare. This has no effect on files while // terminal would be incredibly rare. This has no effect on files while
// allowing us to open serial devices. // allowing us to open serial devices.
sys_flags: linux.Open_Flags = {.NOCTTY} sys_flags: linux.Open_Flags = {.NOCTTY, .CLOEXEC}
switch flags & O_RDONLY|O_WRONLY|O_RDWR { switch flags & O_RDONLY|O_WRONLY|O_RDWR {
case O_RDONLY: case O_RDONLY:
case O_WRONLY: sys_flags += {.WRONLY} case O_WRONLY: sys_flags += {.WRONLY}
case O_RDWR: sys_flags += {.RDWR} case O_RDWR: sys_flags += {.RDWR}
} }
if .Append in flags { sys_flags += {.APPEND} } if .Append in flags { sys_flags += {.APPEND} }
if .Create in flags { sys_flags += {.CREAT} } if .Create in flags { sys_flags += {.CREAT} }
if .Excl in flags { sys_flags += {.EXCL} } if .Excl in flags { sys_flags += {.EXCL} }
if .Sync in flags { sys_flags += {.DSYNC} } if .Sync in flags { sys_flags += {.DSYNC} }
if .Trunc in flags { sys_flags += {.TRUNC} } if .Trunc in flags { sys_flags += {.TRUNC} }
if .Close_On_Exec in flags { sys_flags += {.CLOEXEC} } if .Inheritable in flags { sys_flags -= {.CLOEXEC} }
fd, errno := linux.open(name_cstr, sys_flags, transmute(linux.Mode)(u32(perm))) fd, errno := linux.open(name_cstr, sys_flags, transmute(linux.Mode)u32(perm))
if errno != .NONE { if errno != .NONE {
return nil, _get_platform_error(errno) return nil, _get_platform_error(errno)
} }
return _new_file(uintptr(fd), name), nil return _new_file(uintptr(fd), name)
} }
_new_file :: proc(fd: uintptr, _: string = "") -> ^File { _new_file :: proc(fd: uintptr, _: string = "") -> (f: ^File, err: Error) {
file := new(File, file_allocator()) impl := new(File_Impl, file_allocator()) or_return
_construct_file(file, fd, "") defer if err != nil {
return file free(impl, file_allocator())
} }
impl.file.impl = impl
_construct_file :: proc(file: ^File, fd: uintptr, _: string = "") { impl.fd = linux.Fd(fd)
file^ = { impl.allocator = file_allocator()
impl = { impl.name = _get_full_path(impl.fd, file_allocator()) or_return
fd = linux.Fd(fd), impl.file.stream = {
allocator = file_allocator(), data = impl,
name = _get_full_path(file.impl.fd, file.impl.allocator),
},
stream = {
data = file,
procedure = _file_stream_proc, procedure = _file_stream_proc,
},
} }
impl.file.fstat = _fstat
return &impl.file, nil
} }
_destroy :: proc(f: ^File) -> Error { _destroy :: proc(f: ^File_Impl) -> Error {
if f == nil { if f == nil {
return nil return nil
} }
delete(f.impl.name, f.impl.allocator) a := f.allocator
free(f, f.impl.allocator) delete(f.name, a)
free(f, a)
return nil return nil
} }
_close :: proc(f: ^File) -> Error { _close :: proc(f: ^File_Impl) -> Error {
if f == nil { if f == nil{
return nil return nil
} }
errno := linux.close(f.impl.fd) errno := linux.close(f.fd)
if errno == .EBADF { // avoid possible double free if errno == .EBADF { // avoid possible double free
return _get_platform_error(errno) return _get_platform_error(errno)
} }
@@ -131,41 +132,41 @@ _close :: proc(f: ^File) -> Error {
} }
_fd :: proc(f: ^File) -> uintptr { _fd :: proc(f: ^File) -> uintptr {
if f == nil { if f == nil || f.impl == nil {
return ~uintptr(0) return ~uintptr(0)
} }
return uintptr(f.impl.fd) impl := (^File_Impl)(f.impl)
return uintptr(impl.fd)
} }
_name :: proc(f: ^File) -> string { _name :: proc(f: ^File) -> string {
return f.impl.name if f != nil else "" return (^File_Impl)(f.impl).name if f != nil && f.impl != nil else ""
} }
_seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { _seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) {
n, errno := linux.lseek(f.impl.fd, offset, linux.Seek_Whence(whence)) n, errno := linux.lseek(f.fd, offset, linux.Seek_Whence(whence))
if errno != .NONE { if errno != .NONE {
return -1, _get_platform_error(errno) return -1, _get_platform_error(errno)
} }
return n, nil return n, nil
} }
_read :: proc(f: ^File, p: []byte) -> (i64, Error) { _read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) {
if len(p) == 0 { if len(p) == 0 {
return 0, nil return 0, nil
} }
n, errno := linux.read(f.impl.fd, p[:]) n, errno := linux.read(f.fd, p[:])
if errno != .NONE { if errno != .NONE {
return -1, _get_platform_error(errno) return -1, _get_platform_error(errno)
} }
return i64(n), n == 0 ? io.Error.EOF : nil return i64(n), io.Error.EOF if n == 0 else nil
} }
_read_at :: proc(f: ^File, p: []byte, offset: i64) -> (i64, Error) { _read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) {
if offset < 0 { if offset < 0 {
return 0, .Invalid_Offset return 0, .Invalid_Offset
} }
n, errno := linux.pread(f.fd, p[:], offset)
n, errno := linux.pread(f.impl.fd, p[:], offset)
if errno != .NONE { if errno != .NONE {
return -1, _get_platform_error(errno) return -1, _get_platform_error(errno)
} }
@@ -175,32 +176,31 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (i64, Error) {
return i64(n), nil return i64(n), nil
} }
_write :: proc(f: ^File, p: []byte) -> (i64, Error) { _write :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) {
if len(p) == 0 { if len(p) == 0 {
return 0, nil return 0, nil
} }
n, errno := linux.write(f.impl.fd, p[:]) n, errno := linux.write(f.fd, p[:])
if errno != .NONE { if errno != .NONE {
return -1, _get_platform_error(errno) return -1, _get_platform_error(errno)
} }
return i64(n), nil return i64(n), nil
} }
_write_at :: proc(f: ^File, p: []byte, offset: i64) -> (i64, Error) { _write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) {
if offset < 0 { if offset < 0 {
return 0, .Invalid_Offset return 0, .Invalid_Offset
} }
n, errno := linux.pwrite(f.fd, p[:], offset)
n, errno := linux.pwrite(f.impl.fd, p[:], offset)
if errno != .NONE { if errno != .NONE {
return -1, _get_platform_error(errno) return -1, _get_platform_error(errno)
} }
return i64(n), nil return i64(n), nil
} }
_file_size :: proc(f: ^File) -> (n: i64, err: Error) { _file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) {
s: linux.Stat = --- s: linux.Stat = ---
errno := linux.fstat(f.impl.fd, &s) errno := linux.fstat(f.fd, &s)
if errno != .NONE { if errno != .NONE {
return -1, _get_platform_error(errno) return -1, _get_platform_error(errno)
} }
@@ -208,27 +208,38 @@ _file_size :: proc(f: ^File) -> (n: i64, err: Error) {
} }
_sync :: proc(f: ^File) -> Error { _sync :: proc(f: ^File) -> Error {
return _get_platform_error(linux.fsync(f.impl.fd)) impl := (^File_Impl)(f.impl)
return _get_platform_error(linux.fsync(impl.fd))
} }
_flush :: proc(f: ^File) -> Error { _flush :: proc(f: ^File_Impl) -> Error {
return _get_platform_error(linux.fsync(f.impl.fd)) return _get_platform_error(linux.fsync(f.fd))
} }
_truncate :: proc(f: ^File, size: i64) -> Error { _truncate :: proc(f: ^File, size: i64) -> Error {
return _get_platform_error(linux.ftruncate(f.impl.fd, size)) impl := (^File_Impl)(f.impl)
return _get_platform_error(linux.ftruncate(impl.fd, size))
} }
_remove :: proc(name: string) -> Error { _remove :: proc(name: string) -> Error {
is_dir_fd :: proc(fd: linux.Fd) -> bool {
s: linux.Stat
if linux.fstat(fd, &s) != .NONE {
return false
}
return linux.S_ISDIR(s.mode)
}
TEMP_ALLOCATOR_GUARD() TEMP_ALLOCATOR_GUARD()
name_cstr := temp_cstring(name) or_return name_cstr := temp_cstring(name) or_return
fd, errno := linux.open(name_cstr, {.NOFOLLOW}) fd, errno := linux.open(name_cstr, {.NOFOLLOW})
#partial switch (errno) { #partial switch (errno) {
case .ELOOP: /* symlink */ case .ELOOP:
/* symlink */
case .NONE: case .NONE:
defer linux.close(fd) defer linux.close(fd)
if _is_dir_fd(fd) { if is_dir_fd(fd) {
return _get_platform_error(linux.rmdir(name_cstr)) return _get_platform_error(linux.rmdir(name_cstr))
} }
case: case:
@@ -292,17 +303,19 @@ _chdir :: proc(name: string) -> Error {
} }
_fchdir :: proc(f: ^File) -> Error { _fchdir :: proc(f: ^File) -> Error {
return _get_platform_error(linux.fchdir(f.impl.fd)) impl := (^File_Impl)(f.impl)
return _get_platform_error(linux.fchdir(impl.fd))
} }
_chmod :: proc(name: string, mode: File_Mode) -> Error { _chmod :: proc(name: string, mode: int) -> Error {
TEMP_ALLOCATOR_GUARD() TEMP_ALLOCATOR_GUARD()
name_cstr := temp_cstring(name) or_return name_cstr := temp_cstring(name) or_return
return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)(u32(mode)))) return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)(u32(mode))))
} }
_fchmod :: proc(f: ^File, mode: File_Mode) -> Error { _fchmod :: proc(f: ^File, mode: int) -> Error {
return _get_platform_error(linux.fchmod(f.impl.fd, transmute(linux.Mode)(u32(mode)))) impl := (^File_Impl)(f.impl)
return _get_platform_error(linux.fchmod(impl.fd, transmute(linux.Mode)(u32(mode))))
} }
// NOTE: will throw error without super user priviledges // NOTE: will throw error without super user priviledges
@@ -321,7 +334,8 @@ _lchown :: proc(name: string, uid, gid: int) -> Error {
// NOTE: will throw error without super user priviledges // NOTE: will throw error without super user priviledges
_fchown :: proc(f: ^File, uid, gid: int) -> Error { _fchown :: proc(f: ^File, uid, gid: int) -> Error {
return _get_platform_error(linux.fchown(f.impl.fd, linux.Uid(uid), linux.Gid(gid))) impl := (^File_Impl)(f.impl)
return _get_platform_error(linux.fchown(impl.fd, linux.Uid(uid), linux.Gid(gid)))
} }
_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
@@ -351,7 +365,8 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
uint(mtime._nsec) % uint(time.Second), uint(mtime._nsec) % uint(time.Second),
}, },
} }
return _get_platform_error(linux.utimensat(f.impl.fd, nil, &times[0], nil)) impl := (^File_Impl)(f.impl)
return _get_platform_error(linux.utimensat(impl.fd, nil, &times[0], nil))
} }
_exists :: proc(name: string) -> bool { _exists :: proc(name: string) -> bool {
@@ -361,42 +376,6 @@ _exists :: proc(name: string) -> bool {
return !res && errno == .NONE return !res && errno == .NONE
} }
_is_file :: proc(name: string) -> bool {
TEMP_ALLOCATOR_GUARD()
name_cstr, _ := temp_cstring(name)
s: linux.Stat
if linux.stat(name_cstr, &s) != .NONE {
return false
}
return linux.S_ISREG(s.mode)
}
_is_file_fd :: proc(fd: linux.Fd) -> bool {
s: linux.Stat
if linux.fstat(fd, &s) != .NONE {
return false
}
return linux.S_ISREG(s.mode)
}
_is_dir :: proc(name: string) -> bool {
TEMP_ALLOCATOR_GUARD()
name_cstr, _ := temp_cstring(name)
s: linux.Stat
if linux.stat(name_cstr, &s) != .NONE {
return false
}
return linux.S_ISDIR(s.mode)
}
_is_dir_fd :: proc(fd: linux.Fd) -> bool {
s: linux.Stat
if linux.fstat(fd, &s) != .NONE {
return false
}
return linux.S_ISDIR(s.mode)
}
/* Certain files in the Linux file system are not actual /* Certain files in the Linux file system are not actual
* files (e.g. everything in /proc/). Therefore, the * files (e.g. everything in /proc/). Therefore, the
* read_entire_file procs fail to actually read anything * read_entire_file procs fail to actually read anything
@@ -443,7 +422,7 @@ _read_entire_pseudo_file_cstring :: proc(name: cstring, allocator: runtime.Alloc
@(private="package") @(private="package")
_file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) {
f := (^File)(stream_data) f := (^File_Impl)(stream_data)
ferr: Error ferr: Error
switch mode { switch mode {
case .Read: case .Read:
+13 -1
View File
@@ -8,6 +8,18 @@ write_string :: proc(f: ^File, s: string) -> (n: int, err: Error) {
return write(f, transmute([]byte)s) return write(f, transmute([]byte)s)
} }
write_strings :: proc(f: ^File, strings: ..string) -> (n: int, err: Error) {
for s in strings {
m: int
m, err = write_string(f, s)
n += m
if err != nil {
return
}
}
return
}
write_byte :: proc(f: ^File, b: byte) -> (n: int, err: Error) { write_byte :: proc(f: ^File, b: byte) -> (n: int, err: Error) {
return write(f, []byte{b}) return write(f, []byte{b})
} }
@@ -138,7 +150,7 @@ read_entire_file_from_file :: proc(f: ^File, allocator: runtime.Allocator) -> (d
} }
@(require_results) @(require_results)
write_entire_file :: proc(name: string, data: []byte, perm: File_Mode, truncate := true) -> Error { write_entire_file :: proc(name: string, data: []byte, perm: int, truncate := true) -> Error {
flags := O_WRONLY|O_CREATE flags := O_WRONLY|O_CREATE
if truncate { if truncate {
flags |= O_TRUNC flags |= O_TRUNC
+198 -115
View File
@@ -17,17 +17,19 @@ _ERROR_BAD_NETPATH :: 53
MAX_RW :: 1<<30 MAX_RW :: 1<<30
_File_Kind :: enum u8 { File_Impl_Kind :: enum u8 {
File, File,
Console, Console,
Pipe, Pipe,
} }
_File :: struct { File_Impl :: struct {
file: File,
fd: rawptr, fd: rawptr,
name: string, name: string,
wname: win32.wstring, wname: win32.wstring,
kind: _File_Kind, kind: File_Impl_Kind,
allocator: runtime.Allocator, allocator: runtime.Allocator,
@@ -53,13 +55,14 @@ _handle :: proc(f: ^File) -> win32.HANDLE {
return win32.HANDLE(_fd(f)) return win32.HANDLE(_fd(f))
} }
_open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (handle: uintptr, err: Error) { _open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: uintptr, err: Error) {
if len(name) == 0 { if len(name) == 0 {
err = .Not_Exist err = .Not_Exist
return return
} }
TEMP_ALLOCATOR_GUARD()
path := _fix_long_path(name) path := _fix_long_path(name, temp_allocator()) or_return
access: u32 access: u32
switch flags & {.Read, .Write} { switch flags & {.Read, .Write} {
case {.Read}: access = win32.FILE_GENERIC_READ case {.Read}: access = win32.FILE_GENERIC_READ
@@ -75,11 +78,9 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (han
access |= win32.FILE_APPEND_DATA access |= win32.FILE_APPEND_DATA
} }
share_mode := u32(win32.FILE_SHARE_READ | win32.FILE_SHARE_WRITE) share_mode := u32(win32.FILE_SHARE_READ | win32.FILE_SHARE_WRITE)
sa: ^win32.SECURITY_ATTRIBUTES sa := win32.SECURITY_ATTRIBUTES {
if .Close_On_Exec not_in flags { nLength = size_of(win32.SECURITY_ATTRIBUTES),
sa = &win32.SECURITY_ATTRIBUTES{} bInheritHandle = .Inheritable in flags,
sa.nLength = size_of(win32.SECURITY_ATTRIBUTES)
sa.bInheritHandle = true
} }
create_mode: u32 = win32.OPEN_EXISTING create_mode: u32 = win32.OPEN_EXISTING
@@ -94,14 +95,14 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (han
create_mode = win32.TRUNCATE_EXISTING create_mode = win32.TRUNCATE_EXISTING
} }
attrs: u32 = win32.FILE_ATTRIBUTE_NORMAL attrs: u32 = win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS
if perm & S_IWRITE == 0 { if perm & S_IWRITE == 0 {
attrs = win32.FILE_ATTRIBUTE_READONLY attrs = win32.FILE_ATTRIBUTE_READONLY
if create_mode == win32.CREATE_ALWAYS { if create_mode == win32.CREATE_ALWAYS {
// NOTE(bill): Open has just asked to create a file in read-only mode. // NOTE(bill): Open has just asked to create a file in read-only mode.
// If the file already exists, to make it akin to a *nix open call, // If the file already exists, to make it akin to a *nix open call,
// the call preserves the existing permissions. // the call preserves the existing permissions.
h := win32.CreateFileW(path, access, share_mode, sa, win32.TRUNCATE_EXISTING, win32.FILE_ATTRIBUTE_NORMAL, nil) h := win32.CreateFileW(path, access, share_mode, &sa, win32.TRUNCATE_EXISTING, win32.FILE_ATTRIBUTE_NORMAL, nil)
if h == win32.INVALID_HANDLE { if h == win32.INVALID_HANDLE {
switch e := win32.GetLastError(); e { switch e := win32.GetLastError(); e {
case win32.ERROR_FILE_NOT_FOUND, _ERROR_BAD_NETPATH, win32.ERROR_PATH_NOT_FOUND: case win32.ERROR_FILE_NOT_FOUND, _ERROR_BAD_NETPATH, win32.ERROR_PATH_NOT_FOUND:
@@ -109,12 +110,13 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (han
case 0: case 0:
return uintptr(h), nil return uintptr(h), nil
case: case:
return 0, Platform_Error(e) return 0, _get_platform_error()
} }
} }
} }
} }
h := win32.CreateFileW(path, access, share_mode, sa, create_mode, attrs, nil)
h := win32.CreateFileW(path, access, share_mode, &sa, create_mode, attrs, nil)
if h == win32.INVALID_HANDLE { if h == win32.INVALID_HANDLE {
return 0, _get_platform_error() return 0, _get_platform_error()
} }
@@ -122,85 +124,95 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (han
} }
_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, err: Error) { _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) {
flags := flags if flags != nil else {.Read} flags := flags if flags != nil else {.Read}
handle := _open_internal(name, flags + {.Close_On_Exec}, perm) or_return handle := _open_internal(name, flags, perm) or_return
return _new_file(handle, name), nil return _new_file(handle, name)
} }
_new_file :: proc(handle: uintptr, name: string) -> ^File { _new_file :: proc(handle: uintptr, name: string) -> (f: ^File, err: Error) {
if handle == INVALID_HANDLE { if handle == INVALID_HANDLE {
return nil return
}
impl := new(File_Impl, file_allocator()) or_return
defer if err != nil {
free(impl, file_allocator())
} }
f := new(File, file_allocator())
f.impl.allocator = file_allocator() impl.file.impl = impl
f.impl.fd = rawptr(handle)
f.impl.name, _ = clone_string(name, f.impl.allocator)
f.impl.wname = win32.utf8_to_wstring(name, f.impl.allocator)
handle := _handle(f) impl.allocator = file_allocator()
kind := _File_Kind.File impl.fd = rawptr(handle)
impl.name = clone_string(name, impl.allocator) or_return
impl.wname = win32_utf8_to_wstring(name, impl.allocator) or_return
handle := _handle(&impl.file)
kind := File_Impl_Kind.File
if m: u32; win32.GetConsoleMode(handle, &m) { if m: u32; win32.GetConsoleMode(handle, &m) {
kind = .Console kind = .Console
} }
if win32.GetFileType(handle) == win32.FILE_TYPE_PIPE { if win32.GetFileType(handle) == win32.FILE_TYPE_PIPE {
kind = .Pipe kind = .Pipe
} }
f.impl.kind = kind impl.kind = kind
f.stream = { impl.file.stream = {
data = f, data = impl,
procedure = _file_stream_proc, procedure = _file_stream_proc,
} }
impl.file.fstat = _fstat
return f return &impl.file, nil
} }
_fd :: proc(f: ^File) -> uintptr { _fd :: proc(f: ^File) -> uintptr {
if f == nil { if f == nil || f.impl == nil {
return INVALID_HANDLE return INVALID_HANDLE
} }
return uintptr(f.impl.fd) return uintptr((^File_Impl)(f.impl).fd)
} }
_destroy :: proc(f: ^File) -> Error { _destroy :: proc(f: ^File_Impl) -> Error {
if f == nil { if f == nil {
return nil return nil
} }
a := f.impl.allocator a := f.allocator
free(f.impl.wname, a) err0 := free(f.wname, a)
delete(f.impl.name, a) err1 := delete(f.name, a)
free(f, a) err2 := free(f, a)
err0 or_return
err1 or_return
err2 or_return
return nil return nil
} }
_close :: proc(f: ^File) -> Error { _close :: proc(f: ^File_Impl) -> Error {
if f == nil { if f == nil {
return nil return nil
} }
if !win32.CloseHandle(win32.HANDLE(f.impl.fd)) { if !win32.CloseHandle(win32.HANDLE(f.fd)) {
return .Closed return .Closed
} }
return _destroy(f) return _destroy(f)
} }
_name :: proc(f: ^File) -> string { _name :: proc(f: ^File) -> string {
return f.impl.name if f != nil else "" return (^File_Impl)(f.impl).name if f != nil && f.impl != nil else ""
} }
_seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { _seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) {
handle := _handle(f) handle := _handle(&f.file)
if handle == win32.INVALID_HANDLE { if handle == win32.INVALID_HANDLE {
return 0, .Invalid_File return 0, .Invalid_File
} }
if f.impl.kind == .Pipe {
if f.kind == .Pipe {
return 0, .Invalid_File return 0, .Invalid_File
} }
sync.guard(&f.impl.rw_mutex) sync.guard(&f.rw_mutex)
w: u32 w: u32
switch whence { switch whence {
@@ -218,13 +230,13 @@ _seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Er
return i64(hi)<<32 + i64(dw_ptr), nil return i64(hi)<<32 + i64(dw_ptr), nil
} }
_read :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { _read :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) {
read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) { read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) {
if len(b) == 0 { if len(b) == 0 {
return 0, nil return 0, nil
} }
// TODO(bill): should this be moved to `_File` instead? // TODO(bill): should this be moved to `File_Impl` instead?
BUF_SIZE :: 386 BUF_SIZE :: 386
buf16: [BUF_SIZE]u16 buf16: [BUF_SIZE]u16
buf8: [4*BUF_SIZE]u8 buf8: [4*BUF_SIZE]u8
@@ -269,18 +281,18 @@ _read :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) {
return return
} }
handle := _handle(f) handle := _handle(&f.file)
single_read_length: win32.DWORD single_read_length: win32.DWORD
total_read: int total_read: int
length := len(p) length := len(p)
sync.shared_guard(&f.impl.rw_mutex) // multiple readers sync.shared_guard(&f.rw_mutex) // multiple readers
if sync.guard(&f.impl.p_mutex) { if sync.guard(&f.p_mutex) {
to_read := min(win32.DWORD(length), MAX_RW) to_read := min(win32.DWORD(length), MAX_RW)
ok: win32.BOOL ok: win32.BOOL
if f.impl.kind == .Console { if f.kind == .Console {
n, cerr := read_console(handle, p[total_read:][:to_read]) n, cerr := read_console(handle, p[total_read:][:to_read])
total_read += n total_read += n
if cerr != nil { if cerr != nil {
@@ -300,15 +312,15 @@ _read :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) {
return i64(total_read), err return i64(total_read), err
} }
_read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { _read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (n: i64, err: Error) {
pread :: proc(f: ^File, data: []byte, offset: i64) -> (n: i64, err: Error) { pread :: proc(f: ^File_Impl, data: []byte, offset: i64) -> (n: i64, err: Error) {
buf := data buf := data
if len(buf) > MAX_RW { if len(buf) > MAX_RW {
buf = buf[:MAX_RW] buf = buf[:MAX_RW]
} }
curr_offset := seek(f, offset, .Current) or_return curr_offset := _seek(f, offset, .Current) or_return
defer seek(f, curr_offset, .Start) defer _seek(f, curr_offset, .Start)
o := win32.OVERLAPPED{ o := win32.OVERLAPPED{
OffsetHigh = u32(offset>>32), OffsetHigh = u32(offset>>32),
@@ -317,7 +329,7 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) {
// TODO(bill): Determine the correct behaviour for consoles // TODO(bill): Determine the correct behaviour for consoles
h := _handle(f) h := _handle(&f.file)
done: win32.DWORD done: win32.DWORD
if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) { if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) {
err = _get_platform_error() err = _get_platform_error()
@@ -327,7 +339,7 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) {
return return
} }
sync.guard(&f.impl.p_mutex) sync.guard(&f.p_mutex)
p, offset := p, offset p, offset := p, offset
for len(p) > 0 { for len(p) > 0 {
@@ -339,7 +351,7 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) {
return return
} }
_write :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { _write :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) {
if len(p) == 0 { if len(p) == 0 {
return return
} }
@@ -348,9 +360,9 @@ _write :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) {
total_write: i64 total_write: i64
length := i64(len(p)) length := i64(len(p))
handle := _handle(f) handle := _handle(&f.file)
sync.guard(&f.impl.rw_mutex) sync.guard(&f.rw_mutex)
for total_write < length { for total_write < length {
remaining := length - total_write remaining := length - total_write
to_write := win32.DWORD(min(i32(remaining), MAX_RW)) to_write := win32.DWORD(min(i32(remaining), MAX_RW))
@@ -366,22 +378,22 @@ _write :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) {
return i64(total_write), nil return i64(total_write), nil
} }
_write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { _write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (n: i64, err: Error) {
pwrite :: proc(f: ^File, data: []byte, offset: i64) -> (n: i64, err: Error) { pwrite :: proc(f: ^File_Impl, data: []byte, offset: i64) -> (n: i64, err: Error) {
buf := data buf := data
if len(buf) > MAX_RW { if len(buf) > MAX_RW {
buf = buf[:MAX_RW] buf = buf[:MAX_RW]
} }
curr_offset := seek(f, offset, .Current) or_return curr_offset := _seek(f, offset, .Current) or_return
defer seek(f, curr_offset, .Start) defer _seek(f, curr_offset, .Start)
o := win32.OVERLAPPED{ o := win32.OVERLAPPED{
OffsetHigh = u32(offset>>32), OffsetHigh = u32(offset>>32),
Offset = u32(offset), Offset = u32(offset),
} }
h := _handle(f) h := _handle(&f.file)
done: win32.DWORD done: win32.DWORD
if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) { if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) {
err = _get_platform_error() err = _get_platform_error()
@@ -391,7 +403,7 @@ _write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) {
return return
} }
sync.guard(&f.impl.p_mutex) sync.guard(&f.p_mutex)
p, offset := p, offset p, offset := p, offset
for len(p) > 0 { for len(p) > 0 {
m := pwrite(f, p, offset) or_return m := pwrite(f, p, offset) or_return
@@ -402,12 +414,12 @@ _write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) {
return return
} }
_file_size :: proc(f: ^File) -> (n: i64, err: Error) { _file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) {
length: win32.LARGE_INTEGER length: win32.LARGE_INTEGER
if f.impl.kind == .Pipe { if f.kind == .Pipe {
return 0, .No_Size return 0, .No_Size
} }
handle := _handle(f) handle := _handle(&f.file)
if !win32.GetFileSizeEx(handle, &length) { if !win32.GetFileSizeEx(handle, &length) {
err = _get_platform_error() err = _get_platform_error()
} }
@@ -417,11 +429,14 @@ _file_size :: proc(f: ^File) -> (n: i64, err: Error) {
_sync :: proc(f: ^File) -> Error { _sync :: proc(f: ^File) -> Error {
return _flush(f) if f != nil && f.impl != nil {
return _flush((^File_Impl)(f.impl))
}
return nil
} }
_flush :: proc(f: ^File) -> Error { _flush :: proc(f: ^File_Impl) -> Error {
handle := _handle(f) handle := _handle(&f.file)
if !win32.FlushFileBuffers(handle) { if !win32.FlushFileBuffers(handle) {
return _get_platform_error() return _get_platform_error()
} }
@@ -429,7 +444,7 @@ _flush :: proc(f: ^File) -> Error {
} }
_truncate :: proc(f: ^File, size: i64) -> Error { _truncate :: proc(f: ^File, size: i64) -> Error {
if f == nil { if f == nil || f.impl == nil {
return nil return nil
} }
curr_off := seek(f, 0, .Current) or_return curr_off := seek(f, 0, .Current) or_return
@@ -443,7 +458,8 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
} }
_remove :: proc(name: string) -> Error { _remove :: proc(name: string) -> Error {
p := _fix_long_path(name) TEMP_ALLOCATOR_GUARD()
p := _fix_long_path(name, temp_allocator()) or_return
err, err1: Error err, err1: Error
if !win32.DeleteFileW(p) { if !win32.DeleteFileW(p) {
err = _get_platform_error() err = _get_platform_error()
@@ -480,8 +496,9 @@ _remove :: proc(name: string) -> Error {
} }
_rename :: proc(old_path, new_path: string) -> Error { _rename :: proc(old_path, new_path: string) -> Error {
from := _fix_long_path(old_path) TEMP_ALLOCATOR_GUARD()
to := _fix_long_path(new_path) from := _fix_long_path(old_path, temp_allocator()) or_return
to := _fix_long_path(new_path, temp_allocator()) or_return
if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) { if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) {
return nil return nil
} }
@@ -489,10 +506,10 @@ _rename :: proc(old_path, new_path: string) -> Error {
} }
_link :: proc(old_name, new_name: string) -> Error { _link :: proc(old_name, new_name: string) -> Error {
o := _fix_long_path(old_name) TEMP_ALLOCATOR_GUARD()
n := _fix_long_path(new_name) o := _fix_long_path(old_name, temp_allocator()) or_return
n := _fix_long_path(new_name, temp_allocator()) or_return
if win32.CreateHardLinkW(n, o, nil) { if win32.CreateHardLinkW(n, o, nil) {
return nil return nil
} }
@@ -532,16 +549,16 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st
} }
if !has_unc_prefix(p) { if !has_unc_prefix(p) {
return win32.utf16_to_utf8(p, allocator) return win32_utf16_to_utf8(p, allocator)
} }
ws := p[4:] ws := p[4:]
switch { switch {
case len(ws) >= 2 && ws[1] == ':': case len(ws) >= 2 && ws[1] == ':':
return win32.utf16_to_utf8(ws, allocator) return win32_utf16_to_utf8(ws, allocator)
case has_prefix(ws, `UNC\`): case has_prefix(ws, `UNC\`):
ws[3] = '\\' // override data in buffer ws[3] = '\\' // override data in buffer
return win32.utf16_to_utf8(ws[3:], allocator) return win32_utf16_to_utf8(ws[3:], allocator)
} }
@@ -566,9 +583,9 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st
ws = ws[4:] ws = ws[4:]
if len(ws) > 3 && has_prefix(ws, `UNC`) { if len(ws) > 3 && has_prefix(ws, `UNC`) {
ws[2] = '\\' ws[2] = '\\'
return win32.utf16_to_utf8(ws[2:], allocator) return win32_utf16_to_utf8(ws[2:], allocator)
} }
return win32.utf16_to_utf8(ws, allocator) return win32_utf16_to_utf8(ws, allocator)
} }
return "", .Invalid_Path return "", .Invalid_Path
} }
@@ -579,7 +596,9 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er
@thread_local @thread_local
rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte
p := _fix_long_path(name) TEMP_ALLOCATOR_GUARD()
p := _fix_long_path(name, temp_allocator()) or_return
handle := _open_sym_link(p) or_return handle := _open_sym_link(p) or_return
defer win32.CloseHandle(handle) defer win32.CloseHandle(handle)
@@ -599,7 +618,7 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er
pb[rb.SubstituteNameOffset+rb.SubstituteNameLength] = 0 pb[rb.SubstituteNameOffset+rb.SubstituteNameLength] = 0
p := pb[rb.SubstituteNameOffset:][:rb.SubstituteNameLength] p := pb[rb.SubstituteNameOffset:][:rb.SubstituteNameLength]
if rb.Flags & win32.SYMLINK_FLAG_RELATIVE != 0 { if rb.Flags & win32.SYMLINK_FLAG_RELATIVE != 0 {
return win32.utf16_to_utf8(p, allocator) return win32_utf16_to_utf8(p, allocator)
} }
return _normalize_link_path(p, allocator) return _normalize_link_path(p, allocator)
@@ -616,17 +635,18 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er
_fchdir :: proc(f: ^File) -> Error { _fchdir :: proc(f: ^File) -> Error {
if f == nil { if f == nil || f.impl == nil {
return nil return nil
} }
if !win32.SetCurrentDirectoryW(f.impl.wname) { impl := (^File_Impl)(f.impl)
if !win32.SetCurrentDirectoryW(impl.wname) {
return _get_platform_error() return _get_platform_error()
} }
return nil return nil
} }
_fchmod :: proc(f: ^File, mode: File_Mode) -> Error { _fchmod :: proc(f: ^File, mode: int) -> Error {
if f == nil { if f == nil || f.impl == nil {
return nil return nil
} }
d: win32.BY_HANDLE_FILE_INFORMATION d: win32.BY_HANDLE_FILE_INFORMATION
@@ -653,14 +673,15 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error {
} }
_chdir :: proc(name: string) -> Error { _chdir :: proc(name: string) -> Error {
p := _fix_long_path(name) TEMP_ALLOCATOR_GUARD()
p := _fix_long_path(name, temp_allocator()) or_return
if !win32.SetCurrentDirectoryW(p) { if !win32.SetCurrentDirectoryW(p) {
return _get_platform_error() return _get_platform_error()
} }
return nil return nil
} }
_chmod :: proc(name: string, mode: File_Mode) -> Error { _chmod :: proc(name: string, mode: int) -> Error {
f := open(name, {.Write}) or_return f := open(name, {.Write}) or_return
defer close(f) defer close(f)
return _fchmod(f, mode) return _fchmod(f, mode)
@@ -681,7 +702,7 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
return _fchtimes(f, atime, mtime) return _fchtimes(f, atime, mtime)
} }
_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
if f == nil { if f == nil || f.impl == nil {
return nil return nil
} }
d: win32.BY_HANDLE_FILE_INFORMATION d: win32.BY_HANDLE_FILE_INFORMATION
@@ -708,36 +729,16 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
return nil return nil
} }
_exists :: proc(path: string) -> bool { _exists :: proc(path: string) -> bool {
wpath := _fix_long_path(path) TEMP_ALLOCATOR_GUARD()
wpath, _ := _fix_long_path(path, temp_allocator())
attribs := win32.GetFileAttributesW(wpath) attribs := win32.GetFileAttributesW(wpath)
return attribs != win32.INVALID_FILE_ATTRIBUTES return attribs != win32.INVALID_FILE_ATTRIBUTES
} }
_is_file :: proc(path: string) -> bool {
wpath := _fix_long_path(path)
attribs := win32.GetFileAttributesW(wpath)
if attribs != win32.INVALID_FILE_ATTRIBUTES {
return attribs & win32.FILE_ATTRIBUTE_DIRECTORY == 0
}
return false
}
_is_dir :: proc(path: string) -> bool {
wpath := _fix_long_path(path)
attribs := win32.GetFileAttributesW(wpath)
if attribs != win32.INVALID_FILE_ATTRIBUTES {
return attribs & win32.FILE_ATTRIBUTE_DIRECTORY != 0
}
return false
}
@(private="package") @(private="package")
_file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) {
f := (^File)(stream_data) f := (^File_Impl)(stream_data)
ferr: Error ferr: Error
switch mode { switch mode {
case .Read: case .Read:
@@ -778,3 +779,85 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte,
return 0, .Empty return 0, .Empty
} }
@(private="package", require_results)
win32_utf8_to_wstring :: proc(s: string, allocator: runtime.Allocator) -> (ws: [^]u16, err: runtime.Allocator_Error) {
ws = raw_data(win32_utf8_to_utf16(s, allocator) or_return)
return
}
@(private="package", require_results)
win32_utf8_to_utf16 :: proc(s: string, allocator: runtime.Allocator) -> (ws: []u16, err: runtime.Allocator_Error) {
if len(s) < 1 {
return
}
b := transmute([]byte)s
cstr := raw_data(b)
n := win32.MultiByteToWideChar(win32.CP_UTF8, win32.MB_ERR_INVALID_CHARS, cstr, i32(len(s)), nil, 0)
if n == 0 {
return nil, nil
}
text := make([]u16, n+1, allocator) or_return
n1 := win32.MultiByteToWideChar(win32.CP_UTF8, win32.MB_ERR_INVALID_CHARS, cstr, i32(len(s)), raw_data(text), n)
if n1 == 0 {
delete(text, allocator)
return
}
text[n] = 0
for n >= 1 && text[n-1] == 0 {
n -= 1
}
ws = text[:n]
return
}
@(private="package", require_results)
win32_wstring_to_utf8 :: proc(s: [^]u16, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) {
if s == nil || s[0] == 0 {
return "", nil
}
n := 0
for s[n] != 0 {
n += 1
}
return win32_utf16_to_utf8(s[:n], allocator)
}
@(private="package", require_results)
win32_utf16_to_utf8 :: proc(s: []u16, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) {
if len(s) == 0 {
return
}
n := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, raw_data(s), i32(len(s)), nil, 0, nil, nil)
if n == 0 {
return
}
// If N < 0 the call to WideCharToMultiByte assume the wide string is null terminated
// and will scan it to find the first null terminated character. The resulting string will
// also be null terminated.
// If N > 0 it assumes the wide string is not null terminated and the resulting string
// will not be null terminated.
text := make([]byte, n, allocator) or_return
n1 := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, raw_data(s), i32(len(s)), raw_data(text), n, nil, nil)
if n1 == 0 {
delete(text, allocator)
return
}
for i in 0..<n {
if text[i] == 0 {
n = i
break
}
}
res = string(text[:n])
return
}
-4
View File
@@ -17,7 +17,3 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode
old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, runtime.Allocator_Error) { old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, runtime.Allocator_Error) {
return _heap_allocator_proc(allocator_data, mode, size, alignment, old_memory, old_size, loc) return _heap_allocator_proc(allocator_data, mode, size, alignment, old_memory, old_size, loc)
} }
@(private)
error_allocator := heap_allocator
+2
View File
@@ -126,3 +126,5 @@ random_string :: proc(buf: []byte) -> string {
buf[i] = digits[u % b] buf[i] = digits[u % b]
return string(buf[i:]) return string(buf[i:])
} }
+8 -5
View File
@@ -12,12 +12,14 @@ is_path_separator :: proc(c: byte) -> bool {
} }
mkdir :: make_directory mkdir :: make_directory
make_directory :: proc(name: string, perm: File_Mode) -> Error {
make_directory :: proc(name: string, perm: int) -> Error {
return _mkdir(name, perm) return _mkdir(name, perm)
} }
mkdir_all :: make_directory_all mkdir_all :: make_directory_all
make_directory_all :: proc(path: string, perm: File_Mode) -> Error {
make_directory_all :: proc(path: string, perm: int) -> Error {
return _mkdir_all(path, perm) return _mkdir_all(path, perm)
} }
@@ -25,14 +27,15 @@ remove_all :: proc(path: string) -> Error {
return _remove_all(path) return _remove_all(path)
} }
getwd :: get_working_directory getwd :: get_working_directory
@(require_results) @(require_results)
get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
return _getwd(allocator) return _get_working_directory(allocator)
} }
setwd :: set_working_directory setwd :: set_working_directory
set_working_directory :: proc(dir: string) -> (err: Error) { set_working_directory :: proc(dir: string) -> (err: Error) {
return _setwd(dir) return _set_working_directory(dir)
} }
+13 -32
View File
@@ -15,19 +15,13 @@ _is_path_separator :: proc(c: byte) -> bool {
return c == '/' return c == '/'
} }
_mkdir :: proc(path: string, perm: File_Mode) -> Error { _mkdir :: proc(path: string, perm: int) -> Error {
// TODO: These modes would require mknod, however, that would also
// require additional arguments to this function..
if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 {
return .Invalid_Argument
}
TEMP_ALLOCATOR_GUARD() TEMP_ALLOCATOR_GUARD()
path_cstr := temp_cstring(path) or_return path_cstr := temp_cstring(path) or_return
return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)(u32(perm) & 0o777))) return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)u32(perm)))
} }
_mkdir_all :: proc(path: string, perm: File_Mode) -> Error { _mkdir_all :: proc(path: string, perm: int) -> Error {
mkdirat :: proc(dfd: linux.Fd, path: []u8, perm: int, has_created: ^bool) -> Error { mkdirat :: proc(dfd: linux.Fd, path: []u8, perm: int, has_created: ^bool) -> Error {
i: int i: int
for ; i < len(path) - 1 && path[i] != '/'; i += 1 {} for ; i < len(path) - 1 && path[i] != '/'; i += 1 {}
@@ -38,7 +32,7 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error {
new_dfd, errno := linux.openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS) new_dfd, errno := linux.openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS)
#partial switch errno { #partial switch errno {
case .ENOENT: case .ENOENT:
if errno = linux.mkdirat(dfd, cstring(&path[0]), transmute(linux.Mode)(u32(perm))); errno != .NONE { if errno = linux.mkdirat(dfd, cstring(&path[0]), transmute(linux.Mode)u32(perm)); errno != .NONE {
return _get_platform_error(errno) return _get_platform_error(errno)
} }
has_created^ = true has_created^ = true
@@ -53,17 +47,9 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error {
// skip consecutive '/' // skip consecutive '/'
for i += 1; i < len(path) && path[i] == '/'; i += 1 {} for i += 1; i < len(path) && path[i] == '/'; i += 1 {}
return mkdirat(new_dfd, path[i:], perm, has_created) return mkdirat(new_dfd, path[i:], perm, has_created)
case: }
return _get_platform_error(errno) return _get_platform_error(errno)
} }
unreachable()
}
// TODO
if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 {
return .Invalid_Argument
}
TEMP_ALLOCATOR_GUARD() TEMP_ALLOCATOR_GUARD()
// need something we can edit, and use to generate cstrings // need something we can edit, and use to generate cstrings
path_bytes := make([]u8, len(path) + 1, temp_allocator()) path_bytes := make([]u8, len(path) + 1, temp_allocator())
@@ -85,12 +71,8 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error {
} }
has_created: bool has_created: bool
mkdirat(dfd, path_bytes, int(perm & 0o777), &has_created) or_return mkdirat(dfd, path_bytes, perm, &has_created) or_return
if has_created { return nil if has_created else .Exist
return nil
}
return .Exist
//return has_created ? nil : .Exist
} }
dirent64 :: struct { dirent64 :: struct {
@@ -181,7 +163,7 @@ _remove_all :: proc(path: string) -> Error {
return _get_platform_error(linux.rmdir(path_cstr)) return _get_platform_error(linux.rmdir(path_cstr))
} }
_getwd :: proc(allocator: runtime.Allocator) -> (string, Error) { _get_working_directory :: proc(allocator: runtime.Allocator) -> (string, Error) {
// NOTE(tetra): I would use PATH_MAX here, but I was not able to find // NOTE(tetra): I would use PATH_MAX here, but I was not able to find
// an authoritative value for it across all systems. // an authoritative value for it across all systems.
// The largest value I could find was 4096, so might as well use the page size. // The largest value I could find was 4096, so might as well use the page size.
@@ -201,12 +183,12 @@ _getwd :: proc(allocator: runtime.Allocator) -> (string, Error) {
unreachable() unreachable()
} }
_setwd :: proc(dir: string) -> Error { _set_working_directory :: proc(dir: string) -> Error {
dir_cstr := temp_cstring(dir) or_return dir_cstr := temp_cstring(dir) or_return
return _get_platform_error(linux.chdir(dir_cstr)) return _get_platform_error(linux.chdir(dir_cstr))
} }
_get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> string { _get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath: string, err: Error) {
PROC_FD_PATH :: "/proc/self/fd/" PROC_FD_PATH :: "/proc/self/fd/"
buf: [32]u8 buf: [32]u8
@@ -214,10 +196,9 @@ _get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> string {
strconv.itoa(buf[len(PROC_FD_PATH):], int(fd)) strconv.itoa(buf[len(PROC_FD_PATH):], int(fd))
fullpath: string
err: Error
if fullpath, err = _read_link_cstr(cstring(&buf[0]), allocator); err != nil || fullpath[0] != '/' { if fullpath, err = _read_link_cstr(cstring(&buf[0]), allocator); err != nil || fullpath[0] != '/' {
return "" delete(fullpath, allocator)
fullpath = ""
} }
return fullpath return
} }
+98 -25
View File
@@ -12,14 +12,15 @@ _is_path_separator :: proc(c: byte) -> bool {
return c == '\\' || c == '/' return c == '\\' || c == '/'
} }
_mkdir :: proc(name: string, perm: File_Mode) -> Error { _mkdir :: proc(name: string, perm: int) -> Error {
if !win32.CreateDirectoryW(_fix_long_path(name), nil) { TEMP_ALLOCATOR_GUARD()
if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator()) or_return, nil) {
return _get_platform_error() return _get_platform_error()
} }
return nil return nil
} }
_mkdir_all :: proc(path: string, perm: File_Mode) -> Error { _mkdir_all :: proc(path: string, perm: int) -> Error {
fix_root_directory :: proc(p: string) -> (s: string, allocated: bool, err: runtime.Allocator_Error) { fix_root_directory :: proc(p: string) -> (s: string, allocated: bool, err: runtime.Allocator_Error) {
if len(p) == len(`\\?\c:`) { if len(p) == len(`\\?\c:`) {
if is_path_separator(p[0]) && is_path_separator(p[1]) && p[2] == '?' && is_path_separator(p[3]) && p[5] == ':' { if is_path_separator(p[0]) && is_path_separator(p[1]) && p[2] == '?' && is_path_separator(p[3]) && p[5] == ':' {
@@ -33,9 +34,9 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error {
TEMP_ALLOCATOR_GUARD() TEMP_ALLOCATOR_GUARD()
dir, err := stat(path, temp_allocator()) dir_stat, err := stat(path, temp_allocator())
if err == nil { if err == nil {
if dir.is_directory { if dir_stat.type == .Directory {
return nil return nil
} }
return .Exist return .Exist
@@ -61,8 +62,8 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error {
err = mkdir(path, perm) err = mkdir(path, perm)
if err != nil { if err != nil {
dir1, err1 := lstat(path, temp_allocator()) new_dir_stat, err1 := lstat(path, temp_allocator())
if err1 == nil && dir1.is_directory { if err1 == nil && new_dir_stat.type == .Directory {
return nil return nil
} }
return err return err
@@ -71,41 +72,114 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error {
} }
_remove_all :: proc(path: string) -> Error { _remove_all :: proc(path: string) -> Error {
// TODO(bill): _remove_all for windows if path == "" {
return nil
}
err := remove(path)
if err == nil || err == .Not_Exist {
return nil
}
TEMP_ALLOCATOR_GUARD()
dir := win32_utf8_to_wstring(path, temp_allocator()) or_return
empty: [1]u16
file_op := win32.SHFILEOPSTRUCTW {
nil,
win32.FO_DELETE,
dir,
&empty[0],
win32.FOF_NOCONFIRMATION | win32.FOF_NOERRORUI | win32.FOF_SILENT,
false,
nil,
&empty[0],
}
res := win32.SHFileOperationW(&file_op)
if res != 0 {
return _get_platform_error()
}
return nil return nil
} }
_getwd :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { @private cwd_lock: win32.SRWLOCK // zero is initialized
// TODO(bill)
return "", nil _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
win32.AcquireSRWLockExclusive(&cwd_lock)
TEMP_ALLOCATOR_GUARD()
sz_utf16 := win32.GetCurrentDirectoryW(0, nil)
dir_buf_wstr := make([]u16, sz_utf16, temp_allocator()) or_return
sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr))
assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL.
win32.ReleaseSRWLockExclusive(&cwd_lock)
return win32_utf16_to_utf8(dir_buf_wstr, allocator)
} }
_setwd :: proc(dir: string) -> (err: Error) { _set_working_directory :: proc(dir: string) -> (err: Error) {
// TODO(bill) TEMP_ALLOCATOR_GUARD()
return nil wstr := win32_utf8_to_wstring(dir, temp_allocator()) or_return
}
win32.AcquireSRWLockExclusive(&cwd_lock)
if !win32.SetCurrentDirectoryW(wstr) {
err = _get_platform_error()
}
win32.ReleaseSRWLockExclusive(&cwd_lock)
return
}
can_use_long_paths: bool can_use_long_paths: bool
@(init) @(init)
init_long_path_support :: proc() { init_long_path_support :: proc() {
// TODO(bill): init_long_path_support
// ADD THIS SHIT
// registry_path := win32.L(`Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled`)
can_use_long_paths = false can_use_long_paths = false
key: win32.HKEY
res := win32.RegOpenKeyExW(win32.HKEY_LOCAL_MACHINE, win32.L(`SYSTEM\CurrentControlSet\Control\FileSystem`), 0, win32.KEY_READ, &key)
defer win32.RegCloseKey(key)
if res != 0 {
return
}
value: u32
size := u32(size_of(value))
res = win32.RegGetValueW(
key,
nil,
win32.L("LongPathsEnabled"),
win32.RRF_RT_ANY,
nil,
&value,
&size,
)
if res != 0 {
return
}
if value == 1 {
can_use_long_paths = true
}
} }
@(require_results)
_fix_long_path_slice :: proc(path: string) -> []u16 { _fix_long_path_slice :: proc(path: string, allocator: runtime.Allocator) -> ([]u16, runtime.Allocator_Error) {
return win32.utf8_to_utf16(_fix_long_path_internal(path)) return win32_utf8_to_utf16(_fix_long_path_internal(path), allocator)
} }
_fix_long_path :: proc(path: string) -> win32.wstring { @(require_results)
return win32.utf8_to_wstring(_fix_long_path_internal(path)) _fix_long_path :: proc(path: string, allocator: runtime.Allocator) -> (win32.wstring, runtime.Allocator_Error) {
return win32_utf8_to_wstring(_fix_long_path_internal(path), allocator)
} }
@(require_results)
_fix_long_path_internal :: proc(path: string) -> string { _fix_long_path_internal :: proc(path: string) -> string {
if can_use_long_paths { if can_use_long_paths {
return path return path
@@ -162,5 +236,4 @@ _fix_long_path_internal :: proc(path: string) -> string {
} }
return string(path_buf[:w]) return string(path_buf[:w])
} }
+3 -3
View File
@@ -5,13 +5,13 @@ import "core:sys/linux"
_pipe :: proc() -> (r, w: ^File, err: Error) { _pipe :: proc() -> (r, w: ^File, err: Error) {
fds: [2]linux.Fd fds: [2]linux.Fd
errno := linux.pipe2(&fds, {.CLOEXEC}) errno := linux.pipe2(&fds, {})
if errno != .NONE { if errno != .NONE {
return nil, nil,_get_platform_error(errno) return nil, nil,_get_platform_error(errno)
} }
r = _new_file(uintptr(fds[0])) r = _new_file(uintptr(fds[0])) or_return
w = _new_file(uintptr(fds[1])) w = _new_file(uintptr(fds[1])) or_return
return return
} }
+5 -1
View File
@@ -5,7 +5,11 @@ import win32 "core:sys/windows"
_pipe :: proc() -> (r, w: ^File, err: Error) { _pipe :: proc() -> (r, w: ^File, err: Error) {
p: [2]win32.HANDLE p: [2]win32.HANDLE
if !win32.CreatePipe(&p[0], &p[1], nil, 0) { sa := win32.SECURITY_ATTRIBUTES {
nLength = size_of(win32.SECURITY_ATTRIBUTES),
bInheritHandle = true,
}
if !win32.CreatePipe(&p[0], &p[1], &sa, 0) {
return nil, nil, _get_platform_error() return nil, nil, _get_platform_error()
} }
return new_file(uintptr(p[0]), ""), new_file(uintptr(p[1]), ""), nil return new_file(uintptr(p[0]), ""), new_file(uintptr(p[1]), ""), nil
+350 -46
View File
@@ -1,102 +1,406 @@
package os2 package os2
import "core:sync"
import "core:time"
import "base:runtime" import "base:runtime"
import "core:time"
args: []string /*
In procedures that explicitly state this as one of the allowed values,
specifies an infinite timeout.
*/
TIMEOUT_INFINITE :: time.MIN_DURATION // Note(flysand): Any negative duration will be treated as infinity
/*
Arguments to the current process.
*/
args := get_args()
@(private="file", require_results)
get_args :: proc() -> []string {
result := make([]string, len(runtime.args__), heap_allocator())
for rt_arg, i in runtime.args__ {
result[i] = string(rt_arg)
}
return result
}
/*
Exit the current process.
*/
exit :: proc "contextless" (code: int) -> ! { exit :: proc "contextless" (code: int) -> ! {
runtime.trap() _exit(code)
} }
/*
Obtain the UID of the current process.
**Note(windows)**: Windows doesn't follow the posix permissions model, so
the function simply returns -1.
*/
@(require_results)
get_uid :: proc() -> int { get_uid :: proc() -> int {
return -1 return _get_uid()
} }
/*
Obtain the effective UID of the current process.
The effective UID is typically the same as the UID of the process. In case
the process was run by a user with elevated permissions, the process may
lower the privilege to perform some tasks without privilege. In these cases
the real UID of the process and the effective UID are different.
**Note(windows)**: Windows doesn't follow the posix permissions model, so
the function simply returns -1.
*/
@(require_results)
get_euid :: proc() -> int { get_euid :: proc() -> int {
return -1 return _get_euid()
} }
/*
Obtain the GID of the current process.
**Note(windows)**: Windows doesn't follow the posix permissions model, so
the function simply returns -1.
*/
@(require_results)
get_gid :: proc() -> int { get_gid :: proc() -> int {
return -1 return _get_gid()
} }
/*
Obtain the effective GID of the current process.
The effective GID is typically the same as the GID of the process. In case
the process was run by a user with elevated permissions, the process may
lower the privilege to perform some tasks without privilege. In these cases
the real GID of the process and the effective GID are different.
**Note(windows)**: Windows doesn't follow the posix permissions model, so
the function simply returns -1.
*/
@(require_results)
get_egid :: proc() -> int { get_egid :: proc() -> int {
return -1 return _get_egid()
} }
/*
Obtain the ID of the current process.
*/
@(require_results)
get_pid :: proc() -> int { get_pid :: proc() -> int {
return -1 return _get_pid()
} }
/*
Obtain the ID of the parent process.
**Note(windows)**: Windows does not mantain strong relationships between
parent and child processes. This function returns the ID of the process
that has created the current process. In case the parent has died, the ID
returned by this function can identify a non-existent or a different
process.
*/
@(require_results)
get_ppid :: proc() -> int { get_ppid :: proc() -> int {
return -1 return _get_ppid()
} }
/*
Obtain ID's of all processes running in the system.
*/
@(require_results)
process_list :: proc(allocator: runtime.Allocator) -> ([]int, Error) {
return _process_list(allocator)
}
/*
Bit set specifying which fields of the `Process_Info` struct need to be
obtained by the `process_info()` procedure. Each bit corresponds to a
field in the `Process_Info` struct.
*/
Process_Info_Fields :: bit_set[Process_Info_Field]
Process_Info_Field :: enum {
Executable_Path,
PPid,
Priority,
Command_Line,
Command_Args,
Environment,
Username,
Working_Dir,
}
/*
Contains information about the process as obtained by the `process_info()`
procedure.
*/
Process_Info :: struct {
// The information about a process the struct contains. `pid` is always
// stored, no matter what.
fields: Process_Info_Fields,
// The ID of the process.
pid: int,
// The ID of the parent process.
ppid: int,
// The process priority.
priority: int,
// The path to the executable, which the process runs.
executable_path: string,
// The command line supplied to the process.
command_line: string,
// The arguments supplied to the process.
command_args: []string,
// The environment of the process.
environment: []string,
// The username of the user who started the process.
username: string,
// The current working directory of the process.
working_dir: string,
}
/*
Obtain information about a process.
This procedure obtains an information, specified by `selection` parameter of
a process given by `pid`.
Use `free_process_info` to free the memory allocated by this procedure. In
case the function returns an error all temporary allocations would be freed
and as such, calling `free_process_info()` is not needed.
**Note**: The resulting information may or may contain the fields specified
by the `selection` parameter. Always check whether the returned
`Process_Info` struct has the required fields before checking the error code
returned by this function.
*/
@(require_results)
process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) {
return _process_info_by_pid(pid, selection, allocator)
}
/*
Obtain information about a process.
This procedure obtains information, specified by `selection` parameter
about a process that has been opened by the application, specified in
the `process` parameter.
Use `free_process_info` to free the memory allocated by this procedure. In
case the function returns an error, all temporary allocations would be freed
and as such, calling `free_process_info` is not needed.
**Note**: The resulting information may or may contain the fields specified
by the `selection` parameter. Always check whether the returned
`Process_Info` struct has the required fields before checking the error code
returned by this function.
*/
@(require_results)
process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) {
return _process_info_by_handle(process, selection, allocator)
}
/*
Obtain information about the current process.
This procedure obtains the information, specified by `selection` parameter
about the currently running process.
Use `free_process_info` to free the memory allocated by this function. In
case this function returns an error, all temporary allocations would be
freed and as such calling `free_process_info()` is not needed.
**Note**: The resulting information may or may contain the fields specified
by the `selection` parameter. Always check whether the returned
`Process_Info` struct has the required fields before checking the error code
returned by this function.
*/
@(require_results)
current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) {
return _current_process_info(selection, allocator)
}
/*
Obtain information about the specified process.
*/
process_info :: proc {
process_info_by_pid,
process_info_by_handle,
current_process_info,
}
/*
Free the information about the process.
This procedure frees the memory occupied by process info using the provided
allocator. The allocator needs to be the same allocator that was supplied
to the `process_info` function.
*/
free_process_info :: proc(pi: Process_Info, allocator: runtime.Allocator) {
delete(pi.executable_path, allocator)
delete(pi.command_line, allocator)
delete(pi.command_args, allocator)
for s in pi.environment {
delete(s, allocator)
}
delete(pi.environment, allocator)
delete(pi.working_dir, allocator)
}
/*
Represents a process handle.
When a process dies, the OS is free to re-use the pid of that process. The
`Process` struct represents a handle to the process that will refer to a
specific process, even after it has died.
**Note(linux)**: The `handle` will be referring to pidfd.
*/
Process :: struct { Process :: struct {
pid: int, pid: int,
handle: uintptr, handle: uintptr,
is_done: b32,
signal_mutex: sync.RW_Mutex,
} }
Process_Open_Flags :: bit_set[Process_Open_Flag]
Process_Open_Flag :: enum {
// Request for reading from the virtual memory of another process.
Mem_Read,
// Request for writing to the virtual memory of another process.
Mem_Write,
}
Process_Attributes :: struct { /*
dir: string, Open a process handle using it's pid.
This procedure obtains a process handle of a process specified by `pid`.
This procedure can be subject to race conditions. See the description of
`Process`.
Use `process_close()` function to close the process handle.
*/
@(require_results)
process_open :: proc(pid: int, flags := Process_Open_Flags {}) -> (Process, Error) {
return _process_open(pid, flags)
}
/*
The description of how a process should be created.
*/
Process_Desc :: struct {
// OS-specific attributes.
sys_attr: _Sys_Process_Attributes,
// The working directory of the process. If the string has length 0, the
// working directory is assumed to be the current working directory of the
// current process.
working_dir: string,
// The command to run. Each element of the slice is a separate argument to
// the process. The first element of the slice would be the executable.
command: []string,
// A slice of strings, each having the format `KEY=VALUE` representing the
// full environment that the child process will receive.
// In case this slice is `nil`, the current process' environment is used.
env: []string, env: []string,
files: []^File, // The `stderr` handle to give to the child process. It can be either a file
sys: ^Process_Attributes_OS_Specific, // or a writeable end of a pipe. Passing `nil` will shut down the process'
// stderr output.
stderr: ^File,
// The `stdout` handle to give to the child process. It can be either a file
// or a writeabe end of a pipe. Passing a `nil` will shut down the process'
// stdout output.
stdout: ^File,
// The `stdin` handle to give to the child process. It can either be a file
// or a readable end of a pipe. Passing a `nil` will shut down the process'
// input.
stdin: ^File,
} }
Process_Attributes_OS_Specific :: struct{} /*
Create a new process and obtain its handle.
Process_Error :: enum { This procedure creates a new process, with a given command and environment
None, strings as parameters. Use `environ()` to inherit the environment of the
current process.
The `desc` parameter specifies the description of how the process should
be created. It contains information such as the command line, the
environment of the process, the starting directory and many other options.
Most of the fields in the struct can be set to `nil` or an empty value.
Use `process_close` to close the handle to the process. Note, that this
is not the same as terminating the process. One can terminate the process
and not close the handle, in which case the handle would be leaked. In case
the function returns an error, an invalid handle is returned.
This procedure is not thread-safe. It may alter the inheritance properties
of file handles in an unpredictable manner. In case multiple threads change
handle inheritance properties, make sure to serialize all those calls.
*/
@(require_results)
process_start :: proc(desc := Process_Desc {}) -> (Process, Error) {
return _process_start(desc)
} }
/*
The state of the process after it has finished execution.
*/
Process_State :: struct { Process_State :: struct {
// The ID of the process.
pid: int, pid: int,
exit_code: int, // Specifies whether the process has terminated or is still running.
exited: bool, exited: bool,
// The exit code of the process, if it has exited.
// Will also store the number of the exception or signal that has crashed the
// process.
exit_code: int,
// Specifies whether the termination of the process was successfull or not,
// i.e. whether it has crashed or not.
// **Note(windows)**: On windows `true` is always returned, as there is no
// reliable way to obtain information about whether the process has crashed.
success: bool, success: bool,
// The time the process has spend executing in kernel time.
system_time: time.Duration, system_time: time.Duration,
// The time the process has spend executing in userspace.
user_time: time.Duration, user_time: time.Duration,
sys: rawptr,
} }
Signal :: #type proc() /*
Wait for a process event.
Kill: Signal = nil This procedure blocks the execution until the process has exited or the
Interrupt: Signal = nil timeout (if specified) has reached zero. If the timeout is `TIMEOUT_INFINITE`,
no timeout restriction is imposed and the procedure can block indefinately.
If the timeout has expired, the `General_Error.Timeout` is returned as
the error.
find_process :: proc(pid: int) -> (^Process, Process_Error) { If an error is returned for any other reason, other than timeout, the
return nil, .None process state is considered undetermined.
*/
@(require_results)
process_wait :: proc(process: Process, timeout := TIMEOUT_INFINITE) -> (Process_State, Error) {
return _process_wait(process, timeout)
} }
/*
Close the handle to a process.
process_start :: proc(name: string, argv: []string, attr: ^Process_Attributes) -> (^Process, Process_Error) { This procedure closes the handle associated with a process. It **does not**
return nil, .None terminate a process, in case it was running. In case a termination is
desired, kill the process first, wait for the process to finish,
then close the handle.
*/
@(require_results)
process_close :: proc(process: Process) -> (Error) {
return _process_close(process)
} }
process_release :: proc(p: ^Process) -> Process_Error { /*
return .None Terminate a process.
This procedure terminates a process, specified by it's handle, `process`.
*/
@(require_results)
process_kill :: proc(process: Process) -> (Error) {
return _process_kill(process)
} }
process_kill :: proc(p: ^Process) -> Process_Error {
return .None
}
process_signal :: proc(p: ^Process, sig: Signal) -> Process_Error {
return .None
}
process_wait :: proc(p: ^Process) -> (Process_State, Process_Error) {
return {}, .None
}
+95
View File
@@ -0,0 +1,95 @@
//+private file
package os2
import "base:runtime"
import "core:time"
import "core:sys/linux"
@(private="package")
_exit :: proc "contextless" (code: int) -> ! {
linux.exit(i32(code))
}
@(private="package")
_get_uid :: proc() -> int {
return -1
}
@(private="package")
_get_euid :: proc() -> int {
return -1
}
@(private="package")
_get_gid :: proc() -> int {
return -1
}
@(private="package")
_get_egid :: proc() -> int {
return -1
}
@(private="package")
_get_pid :: proc() -> int {
return -1
}
@(private="package")
_get_ppid :: proc() -> int {
return -1
}
@(private="package")
_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) {
return
}
@(private="package")
_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
return
}
@(private="package")
_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
return
}
@(private="package")
_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
return
}
@(private="package")
_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) {
return
}
@(private="package")
_Sys_Process_Attributes :: struct {}
@(private="package")
_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
return
}
@(private="package")
_process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) {
return
}
@(private="package")
_process_close :: proc(process: Process) -> Error {
return nil
}
@(private="package")
_process_kill :: proc(process: Process) -> Error {
return nil
}
@(private="package")
_process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (exe_path: string, err: Error) {
return
}
+695
View File
@@ -0,0 +1,695 @@
//+private file
package os2
import "base:runtime"
import "core:strings"
import win32 "core:sys/windows"
import "core:time"
@(private="package")
_exit :: proc "contextless" (code: int) -> ! {
win32.ExitProcess(u32(code))
}
@(private="package")
_get_uid :: proc() -> int {
return -1
}
@(private="package")
_get_euid :: proc() -> int {
return -1
}
@(private="package")
_get_gid :: proc() -> int {
return -1
}
@(private="package")
_get_egid :: proc() -> int {
return -1
}
@(private="package")
_get_pid :: proc() -> int {
return int(win32.GetCurrentProcessId())
}
@(private="package")
_get_ppid :: proc() -> int {
our_pid := win32.GetCurrentProcessId()
snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0)
if snap == win32.INVALID_HANDLE_VALUE {
return -1
}
defer win32.CloseHandle(snap)
entry := win32.PROCESSENTRY32W { dwSize = size_of(win32.PROCESSENTRY32W) }
for status := win32.Process32FirstW(snap, &entry); status; /**/ {
if entry.th32ProcessID == our_pid {
return int(entry.th32ParentProcessID)
}
status = win32.Process32NextW(snap, &entry)
}
return -1
}
@(private="package")
_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) {
snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0)
if snap == win32.INVALID_HANDLE_VALUE {
err = _get_platform_error()
return
}
list_d := make([dynamic]int, allocator) or_return
entry := win32.PROCESSENTRY32W{dwSize = size_of(win32.PROCESSENTRY32W)}
status := win32.Process32FirstW(snap, &entry)
for status {
append(&list_d, int(entry.th32ProcessID))
status = win32.Process32NextW(snap, &entry)
}
list = list_d[:]
return
}
@(require_results)
read_memory_as_struct :: proc(h: win32.HANDLE, addr: rawptr, dest: ^$T) -> (bytes_read: uint, err: Error) {
if !win32.ReadProcessMemory(h, addr, dest, size_of(T), &bytes_read) {
err = _get_platform_error()
}
return
}
@(require_results)
read_memory_as_slice :: proc(h: win32.HANDLE, addr: rawptr, dest: []$T) -> (bytes_read: uint, err: Error) {
if !win32.ReadProcessMemory(h, addr, raw_data(dest), len(dest)*size_of(T), &bytes_read) {
err = _get_platform_error()
}
return
}
@(private="package")
_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
info.pid = pid
defer if err != nil {
free_process_info(info, allocator)
}
// Data obtained from process snapshots
if selection >= {.PPid, .Priority} {
entry, entry_err := _process_entry_by_pid(info.pid)
if entry_err != nil {
err = General_Error.Not_Exist
return
}
if .PPid in selection {
info.fields += {.PPid}
info.ppid = int(entry.th32ParentProcessID)
}
if .Priority in selection {
info.fields += {.Priority}
info.priority = int(entry.pcPriClassBase)
}
}
if .Executable_Path in selection { // snap module
info.executable_path = _process_exe_by_pid(pid, allocator) or_return
info.fields += {.Executable_Path}
}
ph := win32.INVALID_HANDLE_VALUE
if selection >= {.Command_Line, .Environment, .Working_Dir, .Username} { // need process handle
ph = win32.OpenProcess(
win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.PROCESS_VM_READ,
false,
u32(pid),
)
if ph == win32.INVALID_HANDLE_VALUE {
err = _get_platform_error()
return
}
}
defer if ph != win32.INVALID_HANDLE_VALUE {
win32.CloseHandle(ph)
}
if selection >= {.Command_Line, .Environment, .Working_Dir} { // need peb
process_info_size: u32
process_info: win32.PROCESS_BASIC_INFORMATION
status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size)
if status != 0 {
// TODO(flysand): There's probably a mismatch between NTSTATUS and
// windows userland error codes, I haven't checked.
err = Platform_Error(status)
return
}
if process_info.PebBaseAddress == nil {
// Not sure what the error is
err = General_Error.Unsupported
return
}
process_peb: win32.PEB
_ = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) or_return
process_params: win32.RTL_USER_PROCESS_PARAMETERS
_ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return
if selection >= {.Command_Line, .Command_Args} {
TEMP_ALLOCATOR_GUARD()
cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return
_ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return
if .Command_Line in selection {
info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return
info.fields += {.Command_Line}
}
if .Command_Args in selection {
info.command_args = _parse_command_line(raw_data(cmdline_w), allocator) or_return
info.fields += {.Command_Args}
}
}
if .Environment in selection {
TEMP_ALLOCATOR_GUARD()
env_len := process_params.EnvironmentSize / 2
envs_w := make([]u16, env_len, temp_allocator()) or_return
_ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return
info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return
info.fields += {.Environment}
}
if .Working_Dir in selection {
TEMP_ALLOCATOR_GUARD()
cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return
_ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return
info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return
info.fields += {.Working_Dir}
}
}
if .Username in selection {
info.username = _get_process_user(ph, allocator) or_return
info.fields += {.Username}
}
err = nil
return
}
@(private="package")
_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
pid := process.pid
info.pid = pid
defer if err != nil {
free_process_info(info, allocator)
}
// Data obtained from process snapshots
if selection >= {.PPid, .Priority} { // snap process
entry, entry_err := _process_entry_by_pid(info.pid)
if entry_err != nil {
err = General_Error.Not_Exist
return
}
if .PPid in selection {
info.fields += {.PPid}
info.ppid = int(entry.th32ParentProcessID)
}
if .Priority in selection {
info.fields += {.Priority}
info.priority = int(entry.pcPriClassBase)
}
}
if .Executable_Path in selection { // snap module
info.executable_path = _process_exe_by_pid(pid, allocator) or_return
info.fields += {.Executable_Path}
}
ph := win32.HANDLE(process.handle)
if selection >= {.Command_Line, .Environment, .Working_Dir} { // need peb
process_info_size: u32
process_info: win32.PROCESS_BASIC_INFORMATION
status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size)
if status != 0 {
// TODO(flysand): There's probably a mismatch between NTSTATUS and
// windows userland error codes, I haven't checked.
err = Platform_Error(status)
return
}
if process_info.PebBaseAddress == nil {
// Not sure what the error is
err = General_Error.Unsupported
return
}
process_peb: win32.PEB
_ = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) or_return
process_params: win32.RTL_USER_PROCESS_PARAMETERS
_ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return
if selection >= {.Command_Line, .Command_Args} {
TEMP_ALLOCATOR_GUARD()
cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return
_ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return
if .Command_Line in selection {
info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return
info.fields += {.Command_Line}
}
if .Command_Args in selection {
info.command_args = _parse_command_line(raw_data(cmdline_w), allocator) or_return
info.fields += {.Command_Args}
}
}
if .Environment in selection {
TEMP_ALLOCATOR_GUARD()
env_len := process_params.EnvironmentSize / 2
envs_w := make([]u16, env_len, temp_allocator()) or_return
_ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return
info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return
info.fields += {.Environment}
}
if .Working_Dir in selection {
TEMP_ALLOCATOR_GUARD()
cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return
_ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return
info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return
info.fields += {.Working_Dir}
}
}
if .Username in selection {
info.username = _get_process_user(ph, allocator) or_return
info.fields += {.Username}
}
err = nil
return
}
@(private="package")
_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
info.pid = get_pid()
defer if err != nil {
free_process_info(info, allocator)
}
if selection >= {.PPid, .Priority} { // snap process
entry, entry_err := _process_entry_by_pid(info.pid)
if entry_err != nil {
err = General_Error.Not_Exist
return
}
if .PPid in selection {
info.fields += {.PPid}
info.ppid = int(entry.th32ProcessID)
}
if .Priority in selection {
info.fields += {.Priority}
info.priority = int(entry.pcPriClassBase)
}
}
if .Executable_Path in selection {
exe_filename_w: [256]u16
path_len := win32.GetModuleFileNameW(nil, raw_data(exe_filename_w[:]), len(exe_filename_w))
info.executable_path = win32_utf16_to_utf8(exe_filename_w[:path_len], allocator) or_return
info.fields += {.Executable_Path}
}
if selection >= {.Command_Line, .Command_Args} {
command_line_w := win32.GetCommandLineW()
if .Command_Line in selection {
info.command_line = win32_wstring_to_utf8(command_line_w, allocator) or_return
info.fields += {.Command_Line}
}
if .Command_Args in selection {
info.command_args = _parse_command_line(command_line_w, allocator) or_return
info.fields += {.Command_Args}
}
}
if .Environment in selection {
env_block := win32.GetEnvironmentStringsW()
info.environment = _parse_environment_block(env_block, allocator) or_return
info.fields += {.Environment}
}
if .Username in selection {
process_handle := win32.GetCurrentProcess()
info.username = _get_process_user(process_handle, allocator) or_return
info.fields += {.Username}
}
if .Working_Dir in selection {
// TODO(flysand): Implement this by reading PEB
err = .Mode_Not_Implemented
return
}
err = nil
return
}
@(private="package")
_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) {
// Note(flysand): The handle will be used for querying information so we
// take the necessary permissions right away.
dwDesiredAccess := win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.SYNCHRONIZE
if .Mem_Read in flags {
dwDesiredAccess |= win32.PROCESS_VM_READ
}
if .Mem_Write in flags {
dwDesiredAccess |= win32.PROCESS_VM_WRITE
}
handle := win32.OpenProcess(
dwDesiredAccess,
false,
u32(pid),
)
if handle == win32.INVALID_HANDLE_VALUE {
err = _get_platform_error()
} else {
process = {pid = pid, handle = uintptr(handle)}
}
return
}
@(private="package")
_Sys_Process_Attributes :: struct {}
@(private="package")
_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
TEMP_ALLOCATOR_GUARD()
command_line := _build_command_line(desc.command, temp_allocator())
command_line_w := win32_utf8_to_wstring(command_line, temp_allocator()) or_return
environment := desc.env
if desc.env == nil {
environment = environ(temp_allocator())
}
environment_block := _build_environment_block(environment, temp_allocator())
environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator()) or_return
stderr_handle := win32.GetStdHandle(win32.STD_ERROR_HANDLE)
stdout_handle := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE)
stdin_handle := win32.GetStdHandle(win32.STD_INPUT_HANDLE)
if desc.stdout != nil {
stdout_handle = win32.HANDLE((^File_Impl)(desc.stdout.impl).fd)
}
if desc.stderr != nil {
stderr_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd)
}
if desc.stdin != nil {
stdin_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd)
}
working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator()) or_else nil) if len(desc.working_dir) > 0 else nil
process_info: win32.PROCESS_INFORMATION
ok := win32.CreateProcessW(
nil,
command_line_w,
nil,
nil,
true,
win32.CREATE_UNICODE_ENVIRONMENT|win32.NORMAL_PRIORITY_CLASS,
raw_data(environment_block_w),
working_dir_w,
&win32.STARTUPINFOW{
cb = size_of(win32.STARTUPINFOW),
hStdError = stderr_handle,
hStdOutput = stdout_handle,
hStdInput = stdin_handle,
dwFlags = win32.STARTF_USESTDHANDLES,
},
&process_info,
)
if !ok {
err = _get_platform_error()
return
}
process = {pid = int(process_info.dwProcessId), handle = uintptr(process_info.hProcess)}
return
}
@(private="package")
_process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) {
handle := win32.HANDLE(process.handle)
timeout_ms := u32(timeout / time.Millisecond) if timeout >= 0 else win32.INFINITE
switch win32.WaitForSingleObject(handle, timeout_ms) {
case win32.WAIT_OBJECT_0:
exit_code: u32
if !win32.GetExitCodeProcess(handle, &exit_code) {
err =_get_platform_error()
return
}
time_created: win32.FILETIME
time_exited: win32.FILETIME
time_kernel: win32.FILETIME
time_user: win32.FILETIME
if !win32.GetProcessTimes(handle, &time_created, &time_exited, &time_kernel, &time_user) {
err = _get_platform_error()
return
}
process_state = {
exit_code = int(exit_code),
exited = true,
pid = process.pid,
success = true,
system_time = _filetime_to_duration(time_kernel),
user_time = _filetime_to_duration(time_user),
}
return
case win32.WAIT_TIMEOUT:
err = General_Error.Timeout
return
case:
err = _get_platform_error()
return
}
}
@(private="package")
_process_close :: proc(process: Process) -> Error {
if !win32.CloseHandle(win32.HANDLE(process.handle)) {
return _get_platform_error()
}
return nil
}
@(private="package")
_process_kill :: proc(process: Process) -> Error {
// Note(flysand): This is different than what the task manager's "kill process"
// functionality does, as we don't try to send WM_CLOSE message first. This
// is quite a rough way to kill the process, which should be consistent with
// linux. The error code 9 is to mimic SIGKILL event.
if !win32.TerminateProcess(win32.HANDLE(process.handle), 9) {
return _get_platform_error()
}
return nil
}
_filetime_to_duration :: proc(filetime: win32.FILETIME) -> time.Duration {
ticks := u64(filetime.dwHighDateTime)<<32 | u64(filetime.dwLowDateTime)
return time.Duration(ticks * 100)
}
_process_entry_by_pid :: proc(pid: int) -> (entry: win32.PROCESSENTRY32W, err: Error) {
snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0)
if snap == win32.INVALID_HANDLE_VALUE {
err = _get_platform_error()
return
}
defer win32.CloseHandle(snap)
entry = win32.PROCESSENTRY32W{dwSize = size_of(win32.PROCESSENTRY32W)}
status := win32.Process32FirstW(snap, &entry)
for status {
if u32(pid) == entry.th32ProcessID {
return
}
status = win32.Process32NextW(snap, &entry)
}
err = General_Error.Not_Exist
return
}
// Note(flysand): Not sure which way it's better to get the executable path:
// via toolhelp snapshots or by reading other process' PEB memory. I have
// a slight suspicion that if both exe path and command line are desired,
// it's faster to just read both from PEB, but maybe the toolhelp snapshots
// are just better...?
@(private="package")
_process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (exe_path: string, err: Error) {
snap := win32.CreateToolhelp32Snapshot(
win32.TH32CS_SNAPMODULE|win32.TH32CS_SNAPMODULE32,
u32(pid),
)
if snap == win32.INVALID_HANDLE_VALUE {
err =_get_platform_error()
return
}
defer win32.CloseHandle(snap)
entry := win32.MODULEENTRY32W { dwSize = size_of(win32.MODULEENTRY32W) }
status := win32.Module32FirstW(snap, &entry)
if !status {
err =_get_platform_error()
return
}
return win32_wstring_to_utf8(raw_data(entry.szExePath[:]), allocator)
}
_get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) {
TEMP_ALLOCATOR_GUARD()
token_handle: win32.HANDLE
if !win32.OpenProcessToken(process_handle, win32.TOKEN_QUERY, &token_handle) {
err = _get_platform_error()
return
}
token_user_size: u32
if !win32.GetTokenInformation(token_handle, .TokenUser, nil, 0, &token_user_size) {
// Note(flysand): Make sure the buffer too small error comes out, and not any other error
err = _get_platform_error()
if v, ok := is_platform_error(err); !ok || v != i32(win32.ERROR_INSUFFICIENT_BUFFER) {
return
}
err = nil
}
token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator()) or_return))
if !win32.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) {
err = _get_platform_error()
return
}
sid_type: win32.SID_NAME_USE
username_w: [256]u16
domain_w: [256]u16
username_chrs := u32(256)
domain_chrs := u32(256)
if !win32.LookupAccountSidW(nil, token_user.User.Sid, &username_w[0], &username_chrs, &domain_w[0], &domain_chrs, &sid_type) {
err = _get_platform_error()
return
}
username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator()) or_return
domain := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator()) or_return
return strings.concatenate({domain, "\\", username}, allocator)
}
_parse_command_line :: proc(cmd_line_w: [^]u16, allocator: runtime.Allocator) -> (argv: []string, err: Error) {
argc: i32
argv_w := win32.CommandLineToArgvW(cmd_line_w, &argc)
if argv_w == nil {
return nil, _get_platform_error()
}
argv = make([]string, argc, allocator) or_return
defer if err != nil {
for arg in argv {
delete(arg, allocator)
}
delete(argv, allocator)
}
for arg_w, i in argv_w[:argc] {
argv[i] = win32_wstring_to_utf8(arg_w, allocator) or_return
}
return
}
_build_command_line :: proc(command: []string, allocator: runtime.Allocator) -> string {
_write_byte_n_times :: #force_inline proc(builder: ^strings.Builder, b: byte, n: int) {
for _ in 0 ..< n {
strings.write_byte(builder, b)
}
}
builder := strings.builder_make(allocator)
for arg, i in command {
if i != 0 {
strings.write_byte(&builder, ' ')
}
j := 0
strings.write_byte(&builder, '"')
for j < len(arg) {
backslashes := 0
for j < len(arg) && arg[j] == '\\' {
backslashes += 1
j += 1
}
if j == len(arg) {
_write_byte_n_times(&builder, '\\', 2*backslashes)
break
} else if arg[j] == '"' {
_write_byte_n_times(&builder, '\\', 2*backslashes+1)
strings.write_byte(&builder, '"')
} else {
_write_byte_n_times(&builder, '\\', backslashes)
strings.write_byte(&builder, arg[j])
}
j += 1
}
strings.write_byte(&builder, '"')
}
return strings.to_string(builder)
}
_parse_environment_block :: proc(block: [^]u16, allocator: runtime.Allocator) -> (envs: []string, err: Error) {
zt_count := 0
for idx := 0; true; {
if block[idx] == 0x0000 {
zt_count += 1
if block[idx+1] == 0x0000 {
zt_count += 1
break
}
}
idx += 1
}
// Note(flysand): Each string in the environment block is terminated
// by a NUL character. In addition, the environment block itself is
// terminated by a NUL character. So the number of strings in the
// environment block is the number of NUL character minus the
// block terminator.
env_count := zt_count - 1
envs = make([]string, env_count, allocator) or_return
defer if err != nil {
for env in envs {
delete(env, allocator)
}
delete(envs, allocator)
}
env_idx := 0
last_idx := 0
idx := 0
for block[idx] != 0x0000 {
for block[idx] != 0x0000 {
idx += 1
}
env_w := block[last_idx:idx]
envs[env_idx] = win32_utf16_to_utf8(env_w, allocator) or_return
env_idx += 1
idx += 1
last_idx = idx
}
return
}
_build_environment_block :: proc(environment: []string, allocator: runtime.Allocator) -> string {
builder := strings.builder_make(allocator)
loop: #reverse for kv, cur_idx in environment {
eq_idx := strings.index_byte(kv, '=')
assert(eq_idx >= 0, "Malformed environment string. Expected '=' to separate keys and values")
key := kv[:eq_idx]
for old_kv in environment[cur_idx+1:] {
old_key := old_kv[:strings.index_byte(old_kv, '=')]
if key == old_key {
continue loop
}
}
strings.write_string(&builder, kv)
strings.write_byte(&builder, 0)
}
// Note(flysand): In addition to the NUL-terminator for each string, the
// environment block itself is NUL-terminated.
strings.write_byte(&builder, 0)
return strings.to_string(builder)
}
+41 -7
View File
@@ -1,21 +1,34 @@
package os2 package os2
import "core:time"
import "base:runtime" import "base:runtime"
import "core:path/filepath"
import "core:strings"
import "core:time"
Fstat_Callback :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) Fstat_Callback :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error)
File_Info :: struct { File_Info :: struct {
fullpath: string, fullpath: string,
name: string, name: string,
size: i64,
mode: File_Mode, inode: u128, // might be zero if cannot be determined
is_directory: bool, size: i64 `fmt:"M"`,
mode: int `fmt:"o"`,
type: File_Type,
creation_time: time.Time, creation_time: time.Time,
modification_time: time.Time, modification_time: time.Time,
access_time: time.Time, access_time: time.Time,
} }
@(require_results)
file_info_clone :: proc(fi: File_Info, allocator: runtime.Allocator) -> (cloned: File_Info, err: runtime.Allocator_Error) {
cloned = fi
cloned.fullpath = strings.clone(fi.fullpath) or_return
cloned.name = filepath.base(cloned.fullpath)
return
}
file_info_slice_delete :: proc(infos: []File_Info, allocator: runtime.Allocator) { file_info_slice_delete :: proc(infos: []File_Info, allocator: runtime.Allocator) {
for i := len(infos)-1; i >= 0; i -= 1 { for i := len(infos)-1; i >= 0; i -= 1 {
file_info_delete(infos[i], allocator) file_info_delete(infos[i], allocator)
@@ -29,10 +42,12 @@ file_info_delete :: proc(fi: File_Info, allocator: runtime.Allocator) {
@(require_results) @(require_results)
fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
if f != nil && f.user_fstat != nil { if f == nil {
return f->user_fstat(allocator) return {}, nil
} else if f.fstat != nil {
return f->fstat(allocator)
} }
return _fstat(f, allocator) return {}, .Invalid_Callback
} }
@(require_results) @(require_results)
@@ -41,6 +56,7 @@ stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
} }
lstat :: stat_do_not_follow_links lstat :: stat_do_not_follow_links
@(require_results) @(require_results)
stat_do_not_follow_links :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { stat_do_not_follow_links :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
return _lstat(name, allocator) return _lstat(name, allocator)
@@ -51,3 +67,21 @@ stat_do_not_follow_links :: proc(name: string, allocator: runtime.Allocator) ->
same_file :: proc(fi1, fi2: File_Info) -> bool { same_file :: proc(fi1, fi2: File_Info) -> bool {
return _same_file(fi1, fi2) return _same_file(fi1, fi2)
} }
last_write_time :: modification_time
last_write_time_by_name :: modification_time_by_path
@(require_results)
modification_time :: proc(f: ^File) -> (time.Time, Error) {
TEMP_ALLOCATOR_GUARD()
fi, err := fstat(f, temp_allocator())
return fi.modification_time, err
}
@(require_results)
modification_time_by_path :: proc(path: string) -> (time.Time, Error) {
TEMP_ALLOCATOR_GUARD()
fi, err := stat(path, temp_allocator())
return fi.modification_time, err
}
+20 -8
View File
@@ -7,31 +7,43 @@ import "core:sys/linux"
import "core:path/filepath" import "core:path/filepath"
_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
return _fstat_internal(f.impl.fd, allocator) impl := (^File_Impl)(f.impl)
return _fstat_internal(impl.fd, allocator)
} }
_fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (File_Info, Error) { _fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
s: linux.Stat s: linux.Stat
errno := linux.fstat(fd, &s) errno := linux.fstat(fd, &s)
if errno != .NONE { if errno != .NONE {
return {}, _get_platform_error(errno) return {}, _get_platform_error(errno)
} }
type := File_Type.Regular
switch s.mode & linux.S_IFMT {
case linux.S_IFBLK: type = .Block_Device
case linux.S_IFCHR: type = .Character_Device
case linux.S_IFDIR: type = .Directory
case linux.S_IFIFO: type = .Named_Pipe
case linux.S_IFLNK: type = .Symlink
case linux.S_IFREG: type = .Regular
case linux.S_IFSOCK: type = .Socket
}
mode := int(0o7777 & transmute(u32)s.mode)
// TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time // TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time
fi := File_Info { fi = File_Info {
fullpath = _get_full_path(fd, allocator), fullpath = _get_full_path(fd, allocator) or_return,
name = "", name = "",
inode = u128(u64(s.ino)),
size = i64(s.size), size = i64(s.size),
mode = 0, mode = mode,
is_directory = linux.S_ISDIR(s.mode), type = type,
modification_time = time.Time {i64(s.mtime.time_sec) * i64(time.Second) + i64(s.mtime.time_nsec)}, modification_time = time.Time {i64(s.mtime.time_sec) * i64(time.Second) + i64(s.mtime.time_nsec)},
access_time = time.Time {i64(s.atime.time_sec) * i64(time.Second) + i64(s.atime.time_nsec)}, access_time = time.Time {i64(s.atime.time_sec) * i64(time.Second) + i64(s.atime.time_nsec)},
creation_time = time.Time{i64(s.ctime.time_sec) * i64(time.Second) + i64(s.ctime.time_nsec)}, // regular stat does not provide this creation_time = time.Time{i64(s.ctime.time_sec) * i64(time.Second) + i64(s.ctime.time_nsec)}, // regular stat does not provide this
} }
fi.creation_time = fi.modification_time fi.creation_time = fi.modification_time
fi.name = filepath.base(fi.fullpath) fi.name = filepath.base(fi.fullpath)
return fi, nil return
} }
// NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath // NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath
+36 -60
View File
@@ -7,7 +7,7 @@ import "core:strings"
import win32 "core:sys/windows" import win32 "core:sys/windows"
_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
if f == nil || f.impl.fd == nil { if f == nil || (^File_Impl)(f.impl).fd == nil {
return {}, nil return {}, nil
} }
@@ -19,28 +19,29 @@ _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
h := _handle(f) h := _handle(f)
switch win32.GetFileType(h) { switch win32.GetFileType(h) {
case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR: case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR:
fi: File_Info fi := File_Info {
fi.fullpath = path fullpath = path,
fi.name = basename(path) name = basename(path),
fi.mode |= file_type_mode(h) type = file_type(h),
}
return fi, nil return fi, nil
} }
return _file_info_from_get_file_information_by_handle(path, h, allocator) return _file_info_from_get_file_information_by_handle(path, h, allocator)
} }
_stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { _stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS, allocator) return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS, allocator)
} }
_lstat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { _lstat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS|win32.FILE_FLAG_OPEN_REPARSE_POINT, allocator) return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS|win32.FILE_FLAG_OPEN_REPARSE_POINT, allocator)
} }
_same_file :: proc(fi1, fi2: File_Info) -> bool { _same_file :: proc(fi1, fi2: File_Info) -> bool {
return fi1.fullpath == fi2.fullpath return fi1.fullpath == fi2.fullpath
} }
full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path: string, err: Error) { full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path: string, err: Error) {
name := name name := name
if name == "" { if name == "" {
@@ -48,7 +49,7 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path
} }
TEMP_ALLOCATOR_GUARD() TEMP_ALLOCATOR_GUARD()
p := win32.utf8_to_utf16(name, temp_allocator()) p := win32_utf8_to_utf16(name, temp_allocator()) or_return
n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil) n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil)
if n == 0 { if n == 0 {
@@ -59,16 +60,16 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path
if n == 0 { if n == 0 {
return "", _get_platform_error() return "", _get_platform_error()
} }
return win32.utf16_to_utf8(buf[:n], allocator) return win32_utf16_to_utf8(buf[:n], allocator)
} }
internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
if len(name) == 0 { if len(name) == 0 {
return {}, .Not_Exist return {}, .Not_Exist
} }
TEMP_ALLOCATOR_GUARD()
wname := _fix_long_path(name) wname := _fix_long_path(name, temp_allocator()) or_return
fa: win32.WIN32_FILE_ATTRIBUTE_DATA fa: win32.WIN32_FILE_ATTRIBUTE_DATA
ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa) ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa)
if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
@@ -99,7 +100,6 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runt
return _file_info_from_get_file_information_by_handle(name, h, allocator) return _file_info_from_get_file_information_by_handle(name, h, allocator)
} }
_cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 { _cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 {
buf := buf buf := buf
N := 0 N := 0
@@ -120,9 +120,8 @@ _cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 {
return buf return buf
} }
_cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (string, Error) { _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (string, Error) {
if f == nil || f.impl.fd == nil { if f == nil {
return "", nil return "", nil
} }
h := _handle(f) h := _handle(f)
@@ -138,7 +137,7 @@ _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (strin
} }
_cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) { _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) {
if f == nil || f.impl.fd == nil { if f == nil {
return nil, nil return nil, nil
} }
h := _handle(f) h := _handle(f)
@@ -156,10 +155,9 @@ _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) {
_cleanpath_from_buf :: proc(buf: []u16, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { _cleanpath_from_buf :: proc(buf: []u16, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) {
buf := buf buf := buf
buf = _cleanpath_strip_prefix(buf) buf = _cleanpath_strip_prefix(buf)
return win32.utf16_to_utf8(buf, allocator) return win32_utf16_to_utf8(buf, allocator)
} }
basename :: proc(name: string) -> (base: string) { basename :: proc(name: string) -> (base: string) {
name := name name := name
if len(name) > 3 && name[:3] == `\\?` { if len(name) > 3 && name[:3] == `\\?` {
@@ -185,83 +183,67 @@ basename :: proc(name: string) -> (base: string) {
return name return name
} }
file_type :: proc(h: win32.HANDLE) -> File_Type {
file_type_mode :: proc(h: win32.HANDLE) -> File_Mode {
switch win32.GetFileType(h) { switch win32.GetFileType(h) {
case win32.FILE_TYPE_PIPE: case win32.FILE_TYPE_PIPE: return .Named_Pipe
return File_Mode_Named_Pipe case win32.FILE_TYPE_CHAR: return .Character_Device
case win32.FILE_TYPE_CHAR: case win32.FILE_TYPE_DISK: return .Regular
return File_Mode_Device | File_Mode_Char_Device
} }
return 0 return .Undetermined
} }
_file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (type: File_Type, mode: int) {
_file_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (mode: File_Mode) {
if file_attributes & win32.FILE_ATTRIBUTE_READONLY != 0 { if file_attributes & win32.FILE_ATTRIBUTE_READONLY != 0 {
mode |= 0o444 mode |= 0o444
} else { } else {
mode |= 0o666 mode |= 0o666
} }
is_sym := false is_sym := false
if file_attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { if file_attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
is_sym = false is_sym = false
} else { } else {
is_sym = ReparseTag == win32.IO_REPARSE_TAG_SYMLINK || ReparseTag == win32.IO_REPARSE_TAG_MOUNT_POINT is_sym = ReparseTag == win32.IO_REPARSE_TAG_SYMLINK || ReparseTag == win32.IO_REPARSE_TAG_MOUNT_POINT
} }
if is_sym { if is_sym {
mode |= File_Mode_Sym_Link type = .Symlink
} else { } else {
if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
mode |= 0o111 | File_Mode_Dir type = .Directory
mode |= 0o111
} }
if h != nil { if h != nil {
mode |= file_type_mode(h) type = file_type(h)
} }
} }
return return
} }
_file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { _file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) fi.type = type
fi.is_directory = fi.mode & File_Mode_Dir != 0 fi.mode |= mode
fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime))
fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime))
fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
fi.fullpath, e = full_path_from_name(name, allocator) fi.fullpath, e = full_path_from_name(name, allocator)
fi.name = basename(fi.fullpath) fi.name = basename(fi.fullpath)
return return
} }
_file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { _file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) fi.type = type
fi.is_directory = fi.mode & File_Mode_Dir != 0 fi.mode |= mode
fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime))
fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime))
fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
fi.fullpath, e = full_path_from_name(name, allocator) fi.fullpath, e = full_path_from_name(name, allocator)
fi.name = basename(fi.fullpath) fi.name = basename(fi.fullpath)
return return
} }
_file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE, allocator: runtime.Allocator) -> (File_Info, Error) { _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE, allocator: runtime.Allocator) -> (File_Info, Error) {
d: win32.BY_HANDLE_FILE_INFORMATION d: win32.BY_HANDLE_FILE_INFORMATION
if !win32.GetFileInformationByHandle(h, &d) { if !win32.GetFileInformationByHandle(h, &d) {
@@ -278,25 +260,20 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA
// Indicate this is a symlink on FAT file systems // Indicate this is a symlink on FAT file systems
ti.ReparseTag = 0 ti.ReparseTag = 0
} }
fi: File_Info fi: File_Info
fi.fullpath = path fi.fullpath = path
fi.name = basename(path) fi.name = basename(path)
fi.inode = u128(u64(d.nFileIndexHigh)<<32 + u64(d.nFileIndexLow))
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
fi.mode |= _file_mode_from_file_attributes(ti.FileAttributes, h, ti.ReparseTag) fi.type = type
fi.is_directory = fi.mode & File_Mode_Dir != 0 fi.mode |= mode
fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime))
fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime))
fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
return fi, nil return fi, nil
} }
reserved_names := [?]string{ reserved_names := [?]string{
"CON", "PRN", "AUX", "NUL", "CON", "PRN", "AUX", "NUL",
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
@@ -357,7 +334,6 @@ _volume_name_len :: proc(path: string) -> int {
return 0 return 0
} }
_is_abs :: proc(path: string) -> bool { _is_abs :: proc(path: string) -> bool {
if _is_reserved_name(path) { if _is_reserved_name(path) {
return true return true
+1 -1
View File
@@ -26,7 +26,7 @@ create_temp_file :: proc(dir, pattern: string) -> (f: ^File, err: Error) {
attempts := 0 attempts := 0
for { for {
name := concatenate_strings_from_buffer(name_buf[:], prefix, random_string(rand_buf[:]), suffix) name := concatenate_strings_from_buffer(name_buf[:], prefix, random_string(rand_buf[:]), suffix)
f, err = open(name, {.Read, .Write, .Create, .Excl}, File_Mode(0o666)) f, err = open(name, {.Read, .Write, .Create, .Excl}, 0o666)
if err == .Exist { if err == .Exist {
close(f) close(f)
attempts += 1 attempts += 1
+1 -1
View File
@@ -19,5 +19,5 @@ _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Er
} else if n > 0 && b[n-1] == '\\' { } else if n > 0 && b[n-1] == '\\' {
n -= 1 n -= 1
} }
return win32.utf16_to_utf8(b[:n], allocator) return win32_utf16_to_utf8(b[:n], allocator)
} }
+1 -1
View File
@@ -1,4 +1,4 @@
//+freestanding //+build freestanding
package os package os
#panic("package os does not support a freestanding target") #panic("package os does not support a freestanding target")
+15 -15
View File
@@ -143,7 +143,7 @@ when !ODIN_NO_RTTI {
@(require_results) @(require_results)
any_base :: proc(v: any) -> any { any_base :: proc(v: any) -> any {
v := v v := v
if v != nil { if v.id != nil {
v.id = typeid_base(v.id) v.id = typeid_base(v.id)
} }
return v return v
@@ -151,7 +151,7 @@ any_base :: proc(v: any) -> any {
@(require_results) @(require_results)
any_core :: proc(v: any) -> any { any_core :: proc(v: any) -> any {
v := v v := v
if v != nil { if v.id != nil {
v.id = typeid_core(v.id) v.id = typeid_core(v.id)
} }
return v return v
@@ -391,7 +391,7 @@ Struct_Field :: struct {
struct_field_at :: proc(T: typeid, i: int) -> (field: Struct_Field) { struct_field_at :: proc(T: typeid, i: int) -> (field: Struct_Field) {
ti := runtime.type_info_base(type_info_of(T)) ti := runtime.type_info_base(type_info_of(T))
if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
if 0 <= i && i < len(s.names) { if 0 <= i && i < int(s.field_count) {
field.name = s.names[i] field.name = s.names[i]
field.type = s.types[i] field.type = s.types[i]
field.tag = Struct_Tag(s.tags[i]) field.tag = Struct_Tag(s.tags[i])
@@ -406,7 +406,7 @@ struct_field_at :: proc(T: typeid, i: int) -> (field: Struct_Field) {
struct_field_by_name :: proc(T: typeid, name: string) -> (field: Struct_Field) { struct_field_by_name :: proc(T: typeid, name: string) -> (field: Struct_Field) {
ti := runtime.type_info_base(type_info_of(T)) ti := runtime.type_info_base(type_info_of(T))
if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
for fname, i in s.names { for fname, i in s.names[:s.field_count] {
if fname == name { if fname == name {
field.name = s.names[i] field.name = s.names[i]
field.type = s.types[i] field.type = s.types[i]
@@ -427,7 +427,7 @@ struct_field_value_by_name :: proc(a: any, field: string, allow_using := false)
ti := runtime.type_info_base(type_info_of(a.id)) ti := runtime.type_info_base(type_info_of(a.id))
if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
for name, i in s.names { for name, i in s.names[:s.field_count] {
if name == field { if name == field {
return any{ return any{
rawptr(uintptr(a.data) + s.offsets[i]), rawptr(uintptr(a.data) + s.offsets[i]),
@@ -463,7 +463,7 @@ struct_field_value :: proc(a: any, field: Struct_Field) -> any {
struct_field_names :: proc(T: typeid) -> []string { struct_field_names :: proc(T: typeid) -> []string {
ti := runtime.type_info_base(type_info_of(T)) ti := runtime.type_info_base(type_info_of(T))
if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
return s.names return s.names[:s.field_count]
} }
return nil return nil
} }
@@ -472,7 +472,7 @@ struct_field_names :: proc(T: typeid) -> []string {
struct_field_types :: proc(T: typeid) -> []^Type_Info { struct_field_types :: proc(T: typeid) -> []^Type_Info {
ti := runtime.type_info_base(type_info_of(T)) ti := runtime.type_info_base(type_info_of(T))
if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
return s.types return s.types[:s.field_count]
} }
return nil return nil
} }
@@ -482,7 +482,7 @@ struct_field_types :: proc(T: typeid) -> []^Type_Info {
struct_field_tags :: proc(T: typeid) -> []Struct_Tag { struct_field_tags :: proc(T: typeid) -> []Struct_Tag {
ti := runtime.type_info_base(type_info_of(T)) ti := runtime.type_info_base(type_info_of(T))
if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
return transmute([]Struct_Tag)s.tags return transmute([]Struct_Tag)s.tags[:s.field_count]
} }
return nil return nil
} }
@@ -491,7 +491,7 @@ struct_field_tags :: proc(T: typeid) -> []Struct_Tag {
struct_field_offsets :: proc(T: typeid) -> []uintptr { struct_field_offsets :: proc(T: typeid) -> []uintptr {
ti := runtime.type_info_base(type_info_of(T)) ti := runtime.type_info_base(type_info_of(T))
if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
return s.offsets return s.offsets[:s.field_count]
} }
return nil return nil
} }
@@ -501,11 +501,11 @@ struct_fields_zipped :: proc(T: typeid) -> (fields: #soa[]Struct_Field) {
ti := runtime.type_info_base(type_info_of(T)) ti := runtime.type_info_base(type_info_of(T))
if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
return soa_zip( return soa_zip(
name = s.names, name = s.names[:s.field_count],
type = s.types, type = s.types[:s.field_count],
tag = transmute([]Struct_Tag)s.tags, tag = ([^]Struct_Tag)(s.tags)[:s.field_count],
offset = s.offsets, offset = s.offsets[:s.field_count],
is_using = s.usings, is_using = s.usings[:s.field_count],
) )
} }
return nil return nil
@@ -1569,7 +1569,7 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_
if v.equal != nil { if v.equal != nil {
return v.equal(a.data, b.data) return v.equal(a.data, b.data)
} else { } else {
for offset, i in v.offsets { for offset, i in v.offsets[:v.field_count] {
x := rawptr(uintptr(a.data) + offset) x := rawptr(uintptr(a.data) + offset)
y := rawptr(uintptr(b.data) + offset) y := rawptr(uintptr(b.data) + offset)
id := v.types[i].id id := v.types[i].id
+14 -15
View File
@@ -115,16 +115,14 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool {
case Type_Info_Struct: case Type_Info_Struct:
y := b.variant.(Type_Info_Struct) or_return y := b.variant.(Type_Info_Struct) or_return
switch { switch {
case len(x.types) != len(y.types), case x.field_count != y.field_count,
x.is_packed != y.is_packed, x.flags != y.flags,
x.is_raw_union != y.is_raw_union,
x.custom_align != y.custom_align,
x.soa_kind != y.soa_kind, x.soa_kind != y.soa_kind,
x.soa_base_type != y.soa_base_type, x.soa_base_type != y.soa_base_type,
x.soa_len != y.soa_len: x.soa_len != y.soa_len:
return false return false
} }
for _, i in x.types { for i in 0..<x.field_count {
xn, yn := x.names[i], y.names[i] xn, yn := x.names[i], y.names[i]
xt, yt := x.types[i], y.types[i] xt, yt := x.types[i], y.types[i]
xl, yl := x.tags[i], y.tags[i] xl, yl := x.tags[i], y.tags[i]
@@ -179,8 +177,8 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool {
case Type_Info_Bit_Field: case Type_Info_Bit_Field:
y := b.variant.(Type_Info_Bit_Field) or_return y := b.variant.(Type_Info_Bit_Field) or_return
if !are_types_identical(x.backing_type, y.backing_type) { return false } if !are_types_identical(x.backing_type, y.backing_type) { return false }
if len(x.names) != len(y.names) { return false } if x.field_count != y.field_count { return false }
for _, i in x.names { for _, i in x.names[:x.field_count] {
if x.names[i] != y.names[i] { if x.names[i] != y.names[i] {
return false return false
} }
@@ -368,13 +366,13 @@ is_tuple :: proc(info: ^Type_Info) -> bool {
is_struct :: proc(info: ^Type_Info) -> bool { is_struct :: proc(info: ^Type_Info) -> bool {
if info == nil { return false } if info == nil { return false }
s, ok := type_info_base(info).variant.(Type_Info_Struct) s, ok := type_info_base(info).variant.(Type_Info_Struct)
return ok && !s.is_raw_union return ok && .raw_union not_in s.flags
} }
@(require_results) @(require_results)
is_raw_union :: proc(info: ^Type_Info) -> bool { is_raw_union :: proc(info: ^Type_Info) -> bool {
if info == nil { return false } if info == nil { return false }
s, ok := type_info_base(info).variant.(Type_Info_Struct) s, ok := type_info_base(info).variant.(Type_Info_Struct)
return ok && s.is_raw_union return ok && .raw_union in s.flags
} }
@(require_results) @(require_results)
is_union :: proc(info: ^Type_Info) -> bool { is_union :: proc(info: ^Type_Info) -> bool {
@@ -495,7 +493,7 @@ write_type_builder :: proc(buf: ^strings.Builder, ti: ^Type_Info) -> int {
n, _ := write_type_writer(strings.to_writer(buf), ti) n, _ := write_type_writer(strings.to_writer(buf), ti)
return n return n
} }
write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) -> (n: int, err: io.Error) { write_type_writer :: #force_no_inline proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) -> (n: int, err: io.Error) {
defer if n_written != nil { defer if n_written != nil {
n_written^ += n n_written^ += n
} }
@@ -656,15 +654,16 @@ write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) -
} }
io.write_string(w, "struct ", &n) or_return io.write_string(w, "struct ", &n) or_return
if info.is_packed { io.write_string(w, "#packed ", &n) or_return } if .packed in info.flags { io.write_string(w, "#packed ", &n) or_return }
if info.is_raw_union { io.write_string(w, "#raw_union ", &n) or_return } if .raw_union in info.flags { io.write_string(w, "#raw_union ", &n) or_return }
if info.custom_align { if .no_copy in info.flags { io.write_string(w, "#no_copy ", &n) or_return }
if .align in info.flags {
io.write_string(w, "#align(", &n) or_return io.write_string(w, "#align(", &n) or_return
io.write_i64(w, i64(ti.align), 10, &n) or_return io.write_i64(w, i64(ti.align), 10, &n) or_return
io.write_string(w, ") ", &n) or_return io.write_string(w, ") ", &n) or_return
} }
io.write_byte(w, '{', &n) or_return io.write_byte(w, '{', &n) or_return
for name, i in info.names { for name, i in info.names[:info.field_count] {
if i > 0 { io.write_string(w, ", ", &n) or_return } if i > 0 { io.write_string(w, ", ", &n) or_return }
io.write_string(w, name, &n) or_return io.write_string(w, name, &n) or_return
io.write_string(w, ": ", &n) or_return io.write_string(w, ": ", &n) or_return
@@ -722,7 +721,7 @@ write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) -
io.write_string(w, "bit_field ", &n) or_return io.write_string(w, "bit_field ", &n) or_return
write_type(w, info.backing_type, &n) or_return write_type(w, info.backing_type, &n) or_return
io.write_string(w, " {", &n) or_return io.write_string(w, " {", &n) or_return
for name, i in info.names { for name, i in info.names[:info.field_count] {
if i > 0 { io.write_string(w, ", ", &n) or_return } if i > 0 { io.write_string(w, ", ", &n) or_return }
io.write_string(w, name, &n) or_return io.write_string(w, name, &n) or_return
io.write_string(w, ": ", &n) or_return io.write_string(w, ": ", &n) or_return
+8 -8
View File
@@ -2,33 +2,33 @@
package simd_x86 package simd_x86
@(require_results, enable_target_feature = "aes") @(require_results, enable_target_feature = "aes")
_mm_aesdec :: #force_inline proc "c" (a, b: __m128i) -> __m128i { _mm_aesdec_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i {
return aesdec(a, b) return aesdec(a, b)
} }
@(require_results, enable_target_feature = "aes") @(require_results, enable_target_feature = "aes")
_mm_aesdeclast :: #force_inline proc "c" (a, b: __m128i) -> __m128i { _mm_aesdeclast_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i {
return aesdeclast(a, b) return aesdeclast(a, b)
} }
@(require_results, enable_target_feature = "aes") @(require_results, enable_target_feature = "aes")
_mm_aesenc :: #force_inline proc "c" (a, b: __m128i) -> __m128i { _mm_aesenc_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i {
return aesenc(a, b) return aesenc(a, b)
} }
@(require_results, enable_target_feature = "aes") @(require_results, enable_target_feature = "aes")
_mm_aesenclast :: #force_inline proc "c" (a, b: __m128i) -> __m128i { _mm_aesenclast_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i {
return aesenclast(a, b) return aesenclast(a, b)
} }
@(require_results, enable_target_feature = "aes") @(require_results, enable_target_feature = "aes")
_mm_aesimc :: #force_inline proc "c" (a: __m128i) -> __m128i { _mm_aesimc_si128 :: #force_inline proc "c" (a: __m128i) -> __m128i {
return aesimc(a) return aesimc(a)
} }
@(require_results, enable_target_feature = "aes") @(require_results, enable_target_feature = "aes")
_mm_aeskeygenassist :: #force_inline proc "c" (a: __m128i, $IMM8: u8) -> __m128i { _mm_aeskeygenassist_si128 :: #force_inline proc "c" (a: __m128i, $IMM8: u8) -> __m128i {
return aeskeygenassist(a, u8(IMM8)) return aeskeygenassist(a, IMM8)
} }
@@ -45,5 +45,5 @@ foreign _ {
@(link_name = "llvm.x86.aesni.aesimc") @(link_name = "llvm.x86.aesni.aesimc")
aesimc :: proc(a: __m128i) -> __m128i --- aesimc :: proc(a: __m128i) -> __m128i ---
@(link_name = "llvm.x86.aesni.aeskeygenassist") @(link_name = "llvm.x86.aesni.aeskeygenassist")
aeskeygenassist :: proc(a: __m128i, imm8: u8) -> __m128i --- aeskeygenassist :: proc(a: __m128i, #const imm8: u8) -> __m128i ---
} }
+9 -4
View File
@@ -144,9 +144,16 @@ _mm_subs_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i {
_mm_slli_si128_impl :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { _mm_slli_si128_impl :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i {
shift :: IMM8 & 0xff shift :: IMM8 & 0xff
// This needs to emit behavior identical to PSLLDQ which is as follows:
//
// TEMP := COUNT
// IF (TEMP > 15) THEN TEMP := 16; FI
// DEST := DEST << (TEMP * 8)
// DEST[MAXVL-1:128] (Unmodified)
return transmute(__m128i)simd.shuffle( return transmute(__m128i)simd.shuffle(
transmute(i8x16)a,
i8x16(0), i8x16(0),
transmute(i8x16)a,
0 when shift > 15 else (16 - shift + 0), 0 when shift > 15 else (16 - shift + 0),
1 when shift > 15 else (16 - shift + 1), 1 when shift > 15 else (16 - shift + 1),
2 when shift > 15 else (16 - shift + 2), 2 when shift > 15 else (16 - shift + 2),
@@ -435,7 +442,7 @@ _mm_store_si128 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) {
} }
@(enable_target_feature="sse2") @(enable_target_feature="sse2")
_mm_storeu_si128 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) { _mm_storeu_si128 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) {
storeudq(mem_addr, a) intrinsics.unaligned_store(mem_addr, a)
} }
@(enable_target_feature="sse2") @(enable_target_feature="sse2")
_mm_storel_epi64 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) { _mm_storel_epi64 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) {
@@ -1178,8 +1185,6 @@ foreign _ {
cvttsd2si :: proc(a: __m128d) -> i32 --- cvttsd2si :: proc(a: __m128d) -> i32 ---
@(link_name="llvm.x86.sse2.cvttps2dq") @(link_name="llvm.x86.sse2.cvttps2dq")
cvttps2dq :: proc(a: __m128) -> i32x4 --- cvttps2dq :: proc(a: __m128) -> i32x4 ---
@(link_name="llvm.x86.sse2.storeu.dq")
storeudq :: proc(mem_addr: rawptr, a: __m128i) ---
@(link_name="llvm.x86.sse2.storeu.pd") @(link_name="llvm.x86.sse2.storeu.pd")
storeupd :: proc(mem_addr: rawptr, a: __m128d) --- storeupd :: proc(mem_addr: rawptr, a: __m128d) ---
+9 -1
View File
@@ -244,7 +244,7 @@ Mode_Bits :: enum {
ISVTX = 9, // 0o0001000 ISVTX = 9, // 0o0001000
ISGID = 10, // 0o0002000 ISGID = 10, // 0o0002000
ISUID = 11, // 0o0004000 ISUID = 11, // 0o0004000
IFFIFO = 12, // 0o0010000 IFIFO = 12, // 0o0010000
IFCHR = 13, // 0o0020000 IFCHR = 13, // 0o0020000
IFDIR = 14, // 0o0040000 IFDIR = 14, // 0o0040000
IFREG = 15, // 0o0100000 IFREG = 15, // 0o0100000
@@ -1815,3 +1815,11 @@ EPoll_Ctl_Opcode :: enum i32 {
DEL = 2, DEL = 2,
MOD = 3, MOD = 3,
} }
/*
Bits for execveat(2) flags.
*/
Execveat_Flags_Bits :: enum {
AT_SYMLINK_NOFOLLOW = 8,
AT_EMPTY_PATH = 12,
}
+3 -3
View File
@@ -39,11 +39,11 @@ PRIO_MIN :: -20
SIGRTMIN :: Signal(32) SIGRTMIN :: Signal(32)
SIGRTMAX :: Signal(64) SIGRTMAX :: Signal(64)
S_IFMT :: Mode{.IFREG, .IFDIR, .IFCHR, .IFFIFO} S_IFMT :: Mode{.IFREG, .IFDIR, .IFCHR, .IFIFO}
S_IFSOCK :: Mode{.IFREG, .IFDIR} S_IFSOCK :: Mode{.IFREG, .IFDIR}
S_IFLNK :: Mode{.IFREG, .IFCHR} S_IFLNK :: Mode{.IFREG, .IFCHR}
S_IFBLK :: Mode{.IFDIR, .IFCHR} S_IFBLK :: Mode{.IFDIR, .IFCHR}
S_IFFIFO :: Mode{.IFFIFO} S_IFIFO :: Mode{.IFIFO}
S_IFCHR :: Mode{.IFCHR} S_IFCHR :: Mode{.IFCHR}
S_IFDIR :: Mode{.IFDIR} S_IFDIR :: Mode{.IFDIR}
S_IFREG :: Mode{.IFREG} S_IFREG :: Mode{.IFREG}
@@ -51,7 +51,7 @@ S_IFREG :: Mode{.IFREG}
/* /*
Checks the Mode bits to see if the file is a named pipe (FIFO). Checks the Mode bits to see if the file is a named pipe (FIFO).
*/ */
S_ISFIFO :: #force_inline proc "contextless" (m: Mode) -> bool {return (S_IFFIFO == (m & S_IFMT))} S_ISFIFO :: #force_inline proc "contextless" (m: Mode) -> bool {return (S_IFIFO == (m & S_IFMT))}
/* /*
Check the Mode bits to see if the file is a character device. Check the Mode bits to see if the file is a character device.
+31 -28
View File
@@ -12,7 +12,7 @@ import "base:intrinsics"
@(private) @(private)
syscall0 :: #force_inline proc "contextless" (nr: uintptr) -> int { syscall0 :: #force_inline proc "contextless" (nr: uintptr) -> int {
return cast(int) intrinsics.syscall(nr) return int(intrinsics.syscall(nr))
} }
@(private) @(private)
@@ -20,7 +20,7 @@ syscall1 :: #force_inline proc "contextless" (nr: uintptr, p1: $T) -> int
where where
size_of(p1) <= size_of(uintptr) size_of(p1) <= size_of(uintptr)
{ {
return cast(int) intrinsics.syscall(nr, cast(uintptr) p1) return int(intrinsics.syscall(nr, uintptr(p1)))
} }
@(private) @(private)
@@ -29,8 +29,7 @@ where
size_of(p1) <= size_of(uintptr), size_of(p1) <= size_of(uintptr),
size_of(p2) <= size_of(uintptr) size_of(p2) <= size_of(uintptr)
{ {
return cast(int) intrinsics.syscall(nr, return int(intrinsics.syscall(nr, uintptr(p1), uintptr(p2)))
cast(uintptr) p1, cast(uintptr) p2)
} }
@(private) @(private)
@@ -40,10 +39,11 @@ where
size_of(p2) <= size_of(uintptr), size_of(p2) <= size_of(uintptr),
size_of(p3) <= size_of(uintptr) size_of(p3) <= size_of(uintptr)
{ {
return cast(int) intrinsics.syscall(nr, return int(intrinsics.syscall(nr,
cast(uintptr) p1, uintptr(p1),
cast(uintptr) p2, uintptr(p2),
cast(uintptr) p3) uintptr(p3),
))
} }
@(private) @(private)
@@ -54,11 +54,12 @@ where
size_of(p3) <= size_of(uintptr), size_of(p3) <= size_of(uintptr),
size_of(p4) <= size_of(uintptr) size_of(p4) <= size_of(uintptr)
{ {
return cast(int) intrinsics.syscall(nr, return int(intrinsics.syscall(nr,
cast(uintptr) p1, uintptr(p1),
cast(uintptr) p2, uintptr(p2),
cast(uintptr) p3, uintptr(p3),
cast(uintptr) p4) uintptr(p4),
))
} }
@(private) @(private)
@@ -70,12 +71,13 @@ where
size_of(p4) <= size_of(uintptr), size_of(p4) <= size_of(uintptr),
size_of(p5) <= size_of(uintptr) size_of(p5) <= size_of(uintptr)
{ {
return cast(int) intrinsics.syscall(nr, return int(intrinsics.syscall(nr,
cast(uintptr) p1, uintptr(p1),
cast(uintptr) p2, uintptr(p2),
cast(uintptr) p3, uintptr(p3),
cast(uintptr) p4, uintptr(p4),
cast(uintptr) p5) uintptr(p5),
))
} }
@(private) @(private)
@@ -88,13 +90,14 @@ where
size_of(p5) <= size_of(uintptr), size_of(p5) <= size_of(uintptr),
size_of(p6) <= size_of(uintptr) size_of(p6) <= size_of(uintptr)
{ {
return cast(int) intrinsics.syscall(nr, return int(intrinsics.syscall(nr,
cast(uintptr) p1, uintptr(p1),
cast(uintptr) p2, uintptr(p2),
cast(uintptr) p3, uintptr(p3),
cast(uintptr) p4, uintptr(p4),
cast(uintptr) p5, uintptr(p5),
cast(uintptr) p6) uintptr(p6),
))
} }
syscall :: proc {syscall0, syscall1, syscall2, syscall3, syscall4, syscall5, syscall6} syscall :: proc {syscall0, syscall1, syscall2, syscall3, syscall4, syscall5, syscall6}
@@ -113,7 +116,7 @@ where
default_value: T default_value: T
return default_value, Errno(-ret) return default_value, Errno(-ret)
} else { } else {
return cast(T) transmute(U) ret, Errno(.NONE) return T(transmute(U)ret), Errno(.NONE)
} }
} }
@@ -123,7 +126,7 @@ errno_unwrap2 :: #force_inline proc "contextless" (ret: $P, $T: typeid) -> (T, E
default_value: T default_value: T
return default_value, Errno(-ret) return default_value, Errno(-ret)
} else { } else {
return cast(T) ret, Errno(.NONE) return T(ret), Errno(.NONE)
} }
} }
+3 -7
View File
@@ -749,17 +749,13 @@ getsockopt :: proc {
getsockopt_base, getsockopt_base,
} }
// TODO(flysand): clone (probably not in this PR, maybe not ever)
/* /*
Creates a copy of the running process. Creates a copy of the running process.
Available since Linux 1.0. Available since Linux 1.0.
*/ */
fork :: proc "contextless" () -> (Pid, Errno) { fork :: proc "contextless" () -> (Pid, Errno) {
when ODIN_ARCH == .arm64 { when ODIN_ARCH == .arm64 {
// Note(flysand): this syscall is not documented, but the bottom 8 bits of flags ret := syscall(SYS_clone, u64(Signal.SIGCHLD), cast(rawptr) nil, cast(rawptr) nil, cast(rawptr) nil, u64(0))
// are for exit signal
ret := syscall(SYS_clone, Signal.SIGCHLD)
return errno_unwrap(ret, Pid) return errno_unwrap(ret, Pid)
} else { } else {
ret := syscall(SYS_fork) ret := syscall(SYS_fork)
@@ -789,7 +785,7 @@ execve :: proc "contextless" (name: cstring, argv: [^]cstring, envp: [^]cstring)
ret := syscall(SYS_execve, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp) ret := syscall(SYS_execve, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp)
return Errno(-ret) return Errno(-ret)
} else { } else {
ret := syscall(SYS_execveat, AT_FDCWD, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp) ret := syscall(SYS_execveat, AT_FDCWD, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp, i32(0))
return Errno(-ret) return Errno(-ret)
} }
} }
@@ -2818,7 +2814,7 @@ getrandom :: proc "contextless" (buf: []u8, flags: Get_Random_Flags) -> (int, Er
Execute program relative to a directory file descriptor. Execute program relative to a directory file descriptor.
Available since Linux 3.19. Available since Linux 3.19.
*/ */
execveat :: proc "contextless" (dirfd: Fd, name: cstring, argv: [^]cstring, envp: [^]cstring, flags: FD_Flags = {}) -> (Errno) { execveat :: proc "contextless" (dirfd: Fd, name: cstring, argv: [^]cstring, envp: [^]cstring, flags: Execveat_Flags = {}) -> (Errno) {
ret := syscall(SYS_execveat, dirfd, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp, transmute(i32) flags) ret := syscall(SYS_execveat, dirfd, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp, transmute(i32) flags)
return Errno(-ret) return Errno(-ret)
} }
+5
View File
@@ -1303,3 +1303,8 @@ EPoll_Event :: struct #packed {
events: EPoll_Event_Kind, events: EPoll_Event_Kind,
data: EPoll_Data, data: EPoll_Data,
} }
/*
Flags for execveat(2) syscall.
*/
Execveat_Flags :: bit_set[Execveat_Flags_Bits; i32]
+250
View File
@@ -6,4 +6,254 @@ foreign import ntdll_lib "system:ntdll.lib"
@(default_calling_convention="system") @(default_calling_convention="system")
foreign ntdll_lib { foreign ntdll_lib {
RtlGetVersion :: proc(lpVersionInformation: ^OSVERSIONINFOEXW) -> NTSTATUS --- RtlGetVersion :: proc(lpVersionInformation: ^OSVERSIONINFOEXW) -> NTSTATUS ---
NtQueryInformationProcess :: proc(
ProcessHandle: HANDLE,
ProcessInformationClass: PROCESS_INFO_CLASS,
ProcessInformation: rawptr,
ProcessInformationLength: u32,
ReturnLength: ^u32,
) -> u32 ---
NtQueryInformationFile :: proc(
FileHandle: HANDLE,
IoStatusBlock: PIO_STATUS_BLOCK,
FileInformation: rawptr,
Length: ULONG,
FileInformationClass: FILE_INFORMATION_CLASS,
) -> NTSTATUS ---
NtQueryDirectoryFileEx :: proc(
FileHandle: HANDLE,
Event: HANDLE,
ApcRoutine: PIO_APC_ROUTINE,
ApcContext: PVOID,
IoStatusBlock: PIO_STATUS_BLOCK,
FileInformation: PVOID,
Length: ULONG,
FileInformationClass: FILE_INFORMATION_CLASS,
QueryFlags: ULONG,
FileName : PUNICODE_STRING,
) -> NTSTATUS ---
}
PIO_APC_ROUTINE :: #type proc "system" (ApcContext: rawptr, IoStatusBlock: PIO_STATUS_BLOCK, Reserved: ULONG)
PIO_STATUS_BLOCK :: ^IO_STATUS_BLOCK
IO_STATUS_BLOCK :: struct {
using _: struct #raw_union {
Status: NTSTATUS,
Pointer: rawptr,
},
Information: ULONG_PTR,
}
PROCESS_INFO_CLASS :: enum c_int {
ProcessBasicInformation = 0,
ProcessDebugPort = 7,
ProcessWow64Information = 26,
ProcessImageFileName = 27,
ProcessBreakOnTermination = 29,
ProcessTelemetryIdInformation = 64,
ProcessSubsystemInformation = 75,
}
SL_RESTART_SCAN :: 0x00000001 // The scan will start at the first entry in the directory. If this flag is not set, the scan will resume from where the last query ended.
SL_RETURN_SINGLE_ENTRY :: 0x00000002 // Normally the return buffer is packed with as many matching directory entries that fit. If this flag is set, the file system will return only one directory entry at a time. This does make the operation less efficient.
SL_INDEX_SPECIFIED :: 0x00000004 // The scan should start at a specified indexed position in the directory. This flag can only be set if you generate your own IRP_MJ_DIRECTORY_CONTROL IRP; the index is specified in the IRP. How the position is specified varies from file system to file system.
SL_RETURN_ON_DISK_ENTRIES_ONLY :: 0x00000008 // Any file system filters that perform directory virtualization or just-in-time expansion should simply pass the request through to the file system and return entries that are currently on disk. Not all file systems support this flag.
SL_NO_CURSOR_UPDATE_QUERY :: 0x00000010 // File systems maintain per-FileObject directory cursor information. When multiple threads do queries using the same FileObject, access to the per-FileObject structure is single threaded to prevent corruption of the cursor state. This flag tells the file system to not update per-FileObject cursor state information thus allowing multiple threads to query in parallel using the same handle. It behaves as if SL_RESTART_SCAN is specified on each call. If a wild card pattern is given on the next call, the operation will not pick up where the last query ended. This allows for true asynchronous directory query support. If this flag is used inside a TxF transaction the operation will be failed. Not all file systems support this flag.
PFILE_INFORMATION_CLASS :: ^FILE_INFORMATION_CLASS
FILE_INFORMATION_CLASS :: enum c_int {
FileDirectoryInformation = 1,
FileFullDirectoryInformation = 2,
FileBothDirectoryInformation = 3,
FileBasicInformation = 4,
FileStandardInformation = 5,
FileInternalInformation = 6,
FileEaInformation = 7,
FileAccessInformation = 8,
FileNameInformation = 9,
FileRenameInformation = 10,
FileLinkInformation = 11,
FileNamesInformation = 12,
FileDispositionInformation = 13,
FilePositionInformation = 14,
FileFullEaInformation = 15,
FileModeInformation = 16,
FileAlignmentInformation = 17,
FileAllInformation = 18,
FileAllocationInformation = 19,
FileEndOfFileInformation = 20,
FileAlternateNameInformation = 21,
FileStreamInformation = 22,
FilePipeInformation = 23,
FilePipeLocalInformation = 24,
FilePipeRemoteInformation = 25,
FileMailslotQueryInformation = 26,
FileMailslotSetInformation = 27,
FileCompressionInformation = 28,
FileObjectIdInformation = 29,
FileCompletionInformation = 30,
FileMoveClusterInformation = 31,
FileQuotaInformation = 32,
FileReparsePointInformation = 33,
FileNetworkOpenInformation = 34,
FileAttributeTagInformation = 35,
FileTrackingInformation = 36,
FileIdBothDirectoryInformation = 37,
FileIdFullDirectoryInformation = 38,
FileValidDataLengthInformation = 39,
FileShortNameInformation = 40,
FileIoCompletionNotificationInformation = 41,
FileIoStatusBlockRangeInformation = 42,
FileIoPriorityHintInformation = 43,
FileSfioReserveInformation = 44,
FileSfioVolumeInformation = 45,
FileHardLinkInformation = 46,
FileProcessIdsUsingFileInformation = 47,
FileNormalizedNameInformation = 48,
FileNetworkPhysicalNameInformation = 49,
FileIdGlobalTxDirectoryInformation = 50,
FileIsRemoteDeviceInformation = 51,
FileUnusedInformation = 52,
FileNumaNodeInformation = 53,
FileStandardLinkInformation = 54,
FileRemoteProtocolInformation = 55,
FileRenameInformationBypassAccessCheck = 56,
FileLinkInformationBypassAccessCheck = 57,
FileVolumeNameInformation = 58,
FileIdInformation = 59,
FileIdExtdDirectoryInformation = 60,
FileReplaceCompletionInformation = 61,
FileHardLinkFullIdInformation = 62,
FileIdExtdBothDirectoryInformation = 63,
FileDispositionInformationEx = 64,
FileRenameInformationEx = 65,
FileRenameInformationExBypassAccessCheck = 66,
FileDesiredStorageClassInformation = 67,
FileStatInformation = 68,
FileMemoryPartitionInformation = 69,
FileStatLxInformation = 70,
FileCaseSensitiveInformation = 71,
FileLinkInformationEx = 72,
FileLinkInformationExBypassAccessCheck = 73,
FileStorageReserveIdInformation = 74,
FileCaseSensitiveInformationForceAccessCheck = 75,
FileKnownFolderInformation = 76,
FileStatBasicInformation = 77,
FileId64ExtdDirectoryInformation = 78,
FileId64ExtdBothDirectoryInformation = 79,
FileIdAllExtdDirectoryInformation = 80,
FileIdAllExtdBothDirectoryInformation = 81,
FileStreamReservationInformation,
FileMupProviderInfo,
FileMaximumInformation,
}
PFILE_ID_FULL_DIR_INFORMATION :: ^FILE_ID_FULL_DIR_INFORMATION
FILE_ID_FULL_DIR_INFORMATION :: struct {
NextEntryOffset: ULONG,
FileIndex: ULONG,
CreationTime: LARGE_INTEGER,
LastAccessTime: LARGE_INTEGER,
LastWriteTime: LARGE_INTEGER,
ChangeTime: LARGE_INTEGER,
EndOfFile: LARGE_INTEGER,
AllocationSize: LARGE_INTEGER,
FileAttributes: ULONG,
FileNameLength: ULONG,
EaSize: ULONG,
FileId: LARGE_INTEGER,
FileName: [1]WCHAR,
}
PROCESS_BASIC_INFORMATION :: struct {
ExitStatus: NTSTATUS,
PebBaseAddress: ^PEB,
AffinityMask: ULONG_PTR,
BasePriority: KPRIORITY,
UniqueProcessId: ULONG_PTR,
InheritedFromUniqueProcessId: ULONG_PTR,
}
KPRIORITY :: rawptr
PPS_POST_PROCESS_INIT_ROUTINE :: proc "system" ()
PEB :: struct {
_: [2]u8,
BeingDebugged: u8,
_: [1]u8,
_: [2]rawptr,
Ldr: ^PEB_LDR_DATA,
ProcessParameters: ^RTL_USER_PROCESS_PARAMETERS,
_: [104]u8,
_: [52]rawptr,
PostProcessInitRoutine: PPS_POST_PROCESS_INIT_ROUTINE,
_: [128]u8,
_: [1]rawptr,
SessionId: u32,
}
PEB_LDR_DATA :: struct {
_: [8]u8,
_: [3]rawptr,
InMemoryOrderModuleList: LIST_ENTRY,
}
RTL_USER_PROCESS_PARAMETERS :: struct {
MaximumLength: u32,
Length: u32,
Flags: u32,
DebugFlags: u32,
ConsoleHandle: rawptr,
ConsoleFlags: u32,
StdInputHandle: rawptr,
StdOutputHandle: rawptr,
StdErrorHandle: rawptr,
CurrentDirectoryPath: UNICODE_STRING,
CurrentDirectoryHandle: rawptr,
DllPath: UNICODE_STRING,
ImagePathName: UNICODE_STRING,
CommandLine: UNICODE_STRING,
Environment: rawptr,
StartingPositionLeft: u32,
StartingPositionTop: u32,
Width: u32,
Height: u32,
CharWidth: u32,
CharHeight: u32,
ConsoleTextAttributes: u32,
WindowFlags: u32,
ShowWindowFlags: u32,
WindowTitle: UNICODE_STRING,
DesktopName: UNICODE_STRING,
ShellInfo: UNICODE_STRING,
RuntimeData: UNICODE_STRING,
DLCurrentDirectory: [32]RTL_DRIVE_LETTER_CURDIR,
EnvironmentSize: u32,
}
RTL_DRIVE_LETTER_CURDIR :: struct {
Flags: u16,
Length: u16,
TimeStamp: u32,
DosPath: UNICODE_STRING,
}
LIST_ENTRY :: struct {
Flink: ^LIST_ENTRY,
Blink: ^LIST_ENTRY,
} }
+6
View File
@@ -32,6 +32,10 @@ foreign shell32 {
SHGetKnownFolderPath :: proc(rfid: REFKNOWNFOLDERID, dwFlags: /* KNOWN_FOLDER_FLAG */ DWORD, hToken: HANDLE, ppszPath: ^LPWSTR) -> HRESULT --- SHGetKnownFolderPath :: proc(rfid: REFKNOWNFOLDERID, dwFlags: /* KNOWN_FOLDER_FLAG */ DWORD, hToken: HANDLE, ppszPath: ^LPWSTR) -> HRESULT ---
ExtractIconExW :: proc(pszFile: LPCWSTR, nIconIndex: INT, phiconLarge: ^HICON, phiconSmall: ^HICON, nIcons: UINT) -> UINT --- ExtractIconExW :: proc(pszFile: LPCWSTR, nIconIndex: INT, phiconLarge: ^HICON, phiconSmall: ^HICON, nIcons: UINT) -> UINT ---
DragAcceptFiles :: proc(hWnd: HWND, fAccept: BOOL) ---
DragQueryPoint :: proc(hDrop: HDROP, ppt: ^POINT) -> BOOL ---
DragQueryFileW :: proc(hDrop: HDROP, iFile: UINT, lpszFile: LPWSTR, cch: UINT) -> UINT ---
DragFinish :: proc(hDrop: HDROP) --- // @New
} }
APPBARDATA :: struct { APPBARDATA :: struct {
@@ -69,6 +73,8 @@ ABE_BOTTOM :: 3
KNOWNFOLDERID :: GUID KNOWNFOLDERID :: GUID
REFKNOWNFOLDERID :: ^KNOWNFOLDERID REFKNOWNFOLDERID :: ^KNOWNFOLDERID
HDROP :: HANDLE
KNOWN_FOLDER_FLAG :: enum u32 { KNOWN_FOLDER_FLAG :: enum u32 {
DEFAULT = 0x00000000, DEFAULT = 0x00000000,
+25 -5
View File
@@ -1139,8 +1139,20 @@ WIN32_FIND_DATAW :: struct {
nFileSizeLow: DWORD, nFileSizeLow: DWORD,
dwReserved0: DWORD, dwReserved0: DWORD,
dwReserved1: DWORD, dwReserved1: DWORD,
cFileName: [260]wchar_t, // #define MAX_PATH 260 cFileName: [MAX_PATH]WCHAR,
cAlternateFileName: [14]wchar_t, cAlternateFileName: [14]WCHAR,
_OBSOLETE_dwFileType: DWORD, // Obsolete. Do not use.
_OBSOLETE_dwCreatorType: DWORD, // Obsolete. Do not use
_OBSOLETE_wFinderFlags: WORD, // Obsolete. Do not use
}
FILE_ID_128 :: struct {
Identifier: [16]BYTE,
}
FILE_ID_INFO :: struct {
VolumeSerialNumber: ULONGLONG,
FileId: FILE_ID_128,
} }
CREATESTRUCTA :: struct { CREATESTRUCTA :: struct {
@@ -1196,6 +1208,11 @@ NMHDR :: struct {
code: UINT, // NM_ code code: UINT, // NM_ code
} }
NCCALCSIZE_PARAMS :: struct {
rgrc: [3]RECT,
lppos: PWINDOWPOS,
}
// Generic WM_NOTIFY notification codes // Generic WM_NOTIFY notification codes
NM_OUTOFMEMORY :: ~uintptr(0) // -1 NM_OUTOFMEMORY :: ~uintptr(0) // -1
NM_CLICK :: NM_OUTOFMEMORY-1 // uses NMCLICK struct NM_CLICK :: NM_OUTOFMEMORY-1 // uses NMCLICK struct
@@ -2318,6 +2335,7 @@ FILE_TYPE_PIPE :: 0x0003
RECT :: struct {left, top, right, bottom: LONG} RECT :: struct {left, top, right, bottom: LONG}
POINT :: struct {x, y: LONG} POINT :: struct {x, y: LONG}
PWINDOWPOS :: ^WINDOWPOS
WINDOWPOS :: struct { WINDOWPOS :: struct {
hwnd: HWND, hwnd: HWND,
hwndInsertAfter: HWND, hwndInsertAfter: HWND,
@@ -2549,6 +2567,7 @@ CLSCTX_RESERVED6 :: 0x1000000
CLSCTX_ACTIVATE_ARM32_SERVER :: 0x2000000 CLSCTX_ACTIVATE_ARM32_SERVER :: 0x2000000
CLSCTX_ALLOW_LOWER_TRUST_REGISTRATION :: 0x4000000 CLSCTX_ALLOW_LOWER_TRUST_REGISTRATION :: 0x4000000
CLSCTX_PS_DLL :: 0x80000000 CLSCTX_PS_DLL :: 0x80000000
CLSCTX_ALL :: CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER
WSAPROTOCOLCHAIN :: struct { WSAPROTOCOLCHAIN :: struct {
ChainLen: c_int, ChainLen: c_int,
@@ -2608,10 +2627,11 @@ OBJECT_ATTRIBUTES :: struct {
SecurityQualityOfService: rawptr, SecurityQualityOfService: rawptr,
} }
PUNICODE_STRING :: ^UNICODE_STRING
UNICODE_STRING :: struct { UNICODE_STRING :: struct {
Length: u16, Length: u16 `fmt:"-"`,
MaximumLength: u16, MaximumLength: u16 `fmt:"-"`,
Buffer: ^u16, Buffer: [^]u16 `fmt:"s,Length"`,
} }
OVERLAPPED :: struct { OVERLAPPED :: struct {
+47 -3
View File
@@ -6,6 +6,7 @@ import "base:runtime"
import "core:bytes" import "core:bytes"
import "core:encoding/ansi" import "core:encoding/ansi"
@require import "core:encoding/base64" @require import "core:encoding/base64"
@require import "core:encoding/json"
import "core:fmt" import "core:fmt"
import "core:io" import "core:io"
@require import pkg_log "core:log" @require import pkg_log "core:log"
@@ -44,7 +45,8 @@ SHARED_RANDOM_SEED : u64 : #config(ODIN_TEST_RANDOM_SEED, 0)
LOG_LEVEL : string : #config(ODIN_TEST_LOG_LEVEL, "info") LOG_LEVEL : string : #config(ODIN_TEST_LOG_LEVEL, "info")
// Show only the most necessary logging information. // Show only the most necessary logging information.
USING_SHORT_LOGS : bool : #config(ODIN_TEST_SHORT_LOGS, false) USING_SHORT_LOGS : bool : #config(ODIN_TEST_SHORT_LOGS, false)
// Output a report of the tests to the given path.
JSON_REPORT : string : #config(ODIN_TEST_JSON_REPORT, "")
get_log_level :: #force_inline proc() -> runtime.Logger_Level { get_log_level :: #force_inline proc() -> runtime.Logger_Level {
when ODIN_DEBUG { when ODIN_DEBUG {
@@ -61,6 +63,18 @@ get_log_level :: #force_inline proc() -> runtime.Logger_Level {
} }
} }
JSON :: struct {
total: int,
success: int,
duration: time.Duration,
packages: map[string][dynamic]JSON_Test,
}
JSON_Test :: struct {
success: bool,
name: string,
}
end_t :: proc(t: ^T) { end_t :: proc(t: ^T) {
for i := len(t.cleanups)-1; i >= 0; i -= 1 { for i := len(t.cleanups)-1; i >= 0; i -= 1 {
#no_bounds_check c := t.cleanups[i] #no_bounds_check c := t.cleanups[i]
@@ -654,8 +668,8 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
#no_bounds_check pkg := report.packages_by_name[it.pkg] #no_bounds_check pkg := report.packages_by_name[it.pkg]
pkg.frame_ready = false pkg.frame_ready = false
fmt.assertf(thread.pool_stop_task(&pool, test_index), found := thread.pool_stop_task(&pool, test_index)
"A signal (%v) was raised to stop test #%i %s.%s, but it was unable to be found.", fmt.assertf(found, "A signal (%v) was raised to stop test #%i %s.%s, but it was unable to be found.",
reason, test_index, it.pkg, it.name) reason, test_index, it.pkg, it.name)
// The order this is handled in is a little particular. // The order this is handled in is a little particular.
@@ -847,5 +861,35 @@ To partly mitigate this, redirect STDERR to a file or use the -define:ODIN_TEST_
fmt.wprintln(stderr, bytes.buffer_to_string(&batch_buffer)) fmt.wprintln(stderr, bytes.buffer_to_string(&batch_buffer))
when JSON_REPORT != "" {
json_report: JSON
mode: int
when ODIN_OS != .Windows {
mode = os.S_IRUSR|os.S_IWUSR|os.S_IRGRP|os.S_IROTH
}
json_fd, errno := os.open(JSON_REPORT, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
fmt.assertf(errno == os.ERROR_NONE, "unable to open file %q for writing of JSON report, error: %v", JSON_REPORT, errno)
defer os.close(json_fd)
for test, i in report.all_tests {
#no_bounds_check state := report.all_test_states[i]
if test.pkg not_in json_report.packages {
json_report.packages[test.pkg] = {}
}
tests := &json_report.packages[test.pkg]
append(tests, JSON_Test{name = test.name, success = state == .Successful})
}
json_report.total = len(internal_tests)
json_report.success = total_success_count
json_report.duration = finished_in
err := json.marshal_to_writer(os.stream_from_handle(json_fd), json_report, &{ pretty = true })
fmt.assertf(err == nil, "Error writing JSON report: %v", err)
}
return total_success_count == total_test_count return total_success_count == total_test_count
} }
+237 -34
View File
@@ -6,12 +6,26 @@ import "base:intrinsics"
_ :: intrinsics _ :: intrinsics
/*
Value, specifying whether `core:thread` functionality is available on the
current platform.
*/
IS_SUPPORTED :: _IS_SUPPORTED IS_SUPPORTED :: _IS_SUPPORTED
/*
Type for a procedure that will be run in a thread, after that thread has been
started.
*/
Thread_Proc :: #type proc(^Thread) Thread_Proc :: #type proc(^Thread)
/*
Maximum number of user arguments for polymorphic thread procedures.
*/
MAX_USER_ARGUMENTS :: 8 MAX_USER_ARGUMENTS :: 8
/*
Type representing the state/flags of the thread.
*/
Thread_State :: enum u8 { Thread_State :: enum u8 {
Started, Started,
Joined, Joined,
@@ -19,44 +33,48 @@ Thread_State :: enum u8 {
Self_Cleanup, Self_Cleanup,
} }
/*
Type representing a thread handle and the associated with that thread data.
*/
Thread :: struct { Thread :: struct {
using specific: Thread_Os_Specific, using specific: Thread_Os_Specific,
flags: bit_set[Thread_State; u8], flags: bit_set[Thread_State; u8],
// Thread ID.
id: int, id: int,
// The thread procedure.
procedure: Thread_Proc, procedure: Thread_Proc,
// User-supplied pointer, that will be available to the thread once it is
/* // started. Should be set after the thread has been created, but before
These are values that the user can set as they wish, after the thread has been created. // it is started.
This data is easily available to the thread proc.
These fields can be assigned to directly.
Should be set after the thread is created, but before it is started.
*/
data: rawptr, data: rawptr,
// User-supplied integer, that will be available to the thread once it is
// started. Should be set after the thread has been created, but before
// it is started.
user_index: int, user_index: int,
// User-supplied array of arguments, that will be available to the thread,
// once it is started. Should be set after the thread has been created,
// but before it is started.
user_args: [MAX_USER_ARGUMENTS]rawptr, user_args: [MAX_USER_ARGUMENTS]rawptr,
// The thread context.
/* // This field can be assigned to directly, after the thread has been
The context to be used as 'context' in the thread proc. // created, but __before__ the thread has been started. This field must
// not be changed after the thread has started.
This field can be assigned to directly, after the thread has been created, but __before__ the thread has been started. //
This field must not be changed after the thread has started. // **Note**: If this field is **not** set, the temp allocator will be managed
// automatically. If it is set, the allocators must be handled manually.
NOTE: If you __don't__ set this, the temp allocator will be managed for you; //
If you __do__ set this, then you're expected to handle whatever allocators you set, yourself. // **IMPORTANT**:
// By default, the thread proc will get the same context as `main()` gets.
IMPORTANT: // In this situation, the thread will get a new temporary allocator which
By default, the thread proc will get the same context as `main()` gets. // will be cleaned up when the thread dies. ***This does NOT happen when
In this situation, the thread will get a new temporary allocator which will be cleaned up when the thread dies. // `init_context` field is initialized***.
***This does NOT happen when you set `init_context`.*** //
This means that if you set `init_context`, but still have the `temp_allocator` field set to the default temp allocator, // If `init_context` is initialized, and `temp_allocator` field is set to
then you'll need to call `runtime.default_temp_allocator_destroy(auto_cast the_thread.init_context.temp_allocator.data)` manually, // the default temp allocator, then `runtime.default_temp_allocator_destroy()`
in order to prevent any memory leaks. // procedure needs to be called from the thread procedure, in order to prevent
This call ***must*** be done ***in the thread proc*** because the default temporary allocator uses thread local state! // any memory leaks.
*/
init_context: Maybe(runtime.Context), init_context: Maybe(runtime.Context),
// The allocator used to allocate data for the thread.
creation_allocator: mem.Allocator, creation_allocator: mem.Allocator,
} }
@@ -64,6 +82,9 @@ when IS_SUPPORTED {
#assert(size_of(Thread{}.user_index) == size_of(uintptr)) #assert(size_of(Thread{}.user_index) == size_of(uintptr))
} }
/*
Type representing priority of a thread.
*/
Thread_Priority :: enum { Thread_Priority :: enum {
Normal, Normal,
Low, Low,
@@ -71,74 +92,178 @@ Thread_Priority :: enum {
} }
/* /*
Creates a thread in a suspended state with the given priority. Create a thread in a suspended state with the given priority.
To start the thread, call `thread.start()`.
See `thread.create_and_start()`. This procedure creates a thread that will be set to run the procedure
specified by `procedure` parameter with a specified priority. The returned
thread will be in a suspended state, until `start()` procedure is called.
To start the thread, call `start()`. Also the `create_and_start()`
procedure can be called to create and start the thread immediately.
*/ */
create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread { create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread {
return _create(procedure, priority) return _create(procedure, priority)
} }
/*
Wait for the thread to finish and free all data associated with it.
*/
destroy :: proc(thread: ^Thread) { destroy :: proc(thread: ^Thread) {
_destroy(thread) _destroy(thread)
} }
/*
Start a suspended thread.
*/
start :: proc(thread: ^Thread) { start :: proc(thread: ^Thread) {
_start(thread) _start(thread)
} }
/*
Check if the thread has finished work.
*/
is_done :: proc(thread: ^Thread) -> bool { is_done :: proc(thread: ^Thread) -> bool {
return _is_done(thread) return _is_done(thread)
} }
/*
Wait for the thread to finish work.
*/
join :: proc(thread: ^Thread) { join :: proc(thread: ^Thread) {
_join(thread) _join(thread)
} }
/*
Wait for all threads to finish work.
*/
join_multiple :: proc(threads: ..^Thread) { join_multiple :: proc(threads: ..^Thread) {
_join_multiple(..threads) _join_multiple(..threads)
} }
/*
Forcibly terminate a running thread.
*/
terminate :: proc(thread: ^Thread, exit_code: int) { terminate :: proc(thread: ^Thread, exit_code: int) {
_terminate(thread, exit_code) _terminate(thread, exit_code)
} }
/*
Yield the execution of the current thread to another OS thread or process.
*/
yield :: proc() { yield :: proc() {
_yield() _yield()
} }
/*
Run a procedure on a different thread.
This procedure runs the given procedure on another thread. The context
specified by `init_context` will be used as the context in which `fn` is going
to execute. The thread will have priority specified by the `priority` parameter.
**IMPORTANT**: If `init_context` is specified and the default temporary allocator
is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
in order to free the resources associated with the temporary allocations.
*/
run :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) { run :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) {
create_and_start(fn, init_context, priority, true) create_and_start(fn, init_context, priority, true)
} }
/*
Run a procedure with one pointer parameter on a different thread.
This procedure runs the given procedure on another thread. The context
specified by `init_context` will be used as the context in which `fn` is going
to execute. The thread will have priority specified by the `priority` parameter.
**IMPORTANT**: If `init_context` is specified and the default temporary allocator
is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
in order to free the resources associated with the temporary allocations.
*/
run_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) { run_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) {
create_and_start_with_data(data, fn, init_context, priority, true) create_and_start_with_data(data, fn, init_context, priority, true)
} }
/*
Run a procedure with one polymorphic parameter on a different thread.
This procedure runs the given procedure on another thread. The context
specified by `init_context` will be used as the context in which `fn` is going
to execute. The thread will have priority specified by the `priority` parameter.
**IMPORTANT**: If `init_context` is specified and the default temporary allocator
is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
in order to free the resources associated with the temporary allocations.
*/
run_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) run_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal)
where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
create_and_start_with_poly_data(data, fn, init_context, priority, true) create_and_start_with_poly_data(data, fn, init_context, priority, true)
} }
/*
Run a procedure with two polymorphic parameters on a different thread.
This procedure runs the given procedure on another thread. The context
specified by `init_context` will be used as the context in which `fn` is going
to execute. The thread will have priority specified by the `priority` parameter.
**IMPORTANT**: If `init_context` is specified and the default temporary allocator
is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
in order to free the resources associated with the temporary allocations.
*/
run_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) run_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal)
where size_of(T1) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { where size_of(T1) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
create_and_start_with_poly_data2(arg1, arg2, fn, init_context, priority, true) create_and_start_with_poly_data2(arg1, arg2, fn, init_context, priority, true)
} }
/*
Run a procedure with three polymorphic parameters on a different thread.
This procedure runs the given procedure on another thread. The context
specified by `init_context` will be used as the context in which `fn` is going
to execute. The thread will have priority specified by the `priority` parameter.
**IMPORTANT**: If `init_context` is specified and the default temporary allocator
is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
in order to free the resources associated with the temporary allocations.
*/
run_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) run_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal)
where size_of(T1) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { where size_of(T1) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
create_and_start_with_poly_data3(arg1, arg2, arg3, fn, init_context, priority, true) create_and_start_with_poly_data3(arg1, arg2, arg3, fn, init_context, priority, true)
} }
/*
Run a procedure with four polymorphic parameters on a different thread.
This procedure runs the given procedure on another thread. The context
specified by `init_context` will be used as the context in which `fn` is going
to execute. The thread will have priority specified by the `priority` parameter.
**IMPORTANT**: If `init_context` is specified and the default temporary allocator
is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
in order to free the resources associated with the temporary allocations.
*/
run_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) run_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal)
where size_of(T1) + size_of(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS { where size_of(T1) + size_of(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
create_and_start_with_poly_data4(arg1, arg2, arg3, arg4, fn, init_context, priority, true) create_and_start_with_poly_data4(arg1, arg2, arg3, arg4, fn, init_context, priority, true)
} }
/*
Run a procedure on a different thread.
This procedure runs the given procedure on another thread. The context
specified by `init_context` will be used as the context in which `fn` is going
to execute. The thread will have priority specified by the `priority` parameter.
If `self_cleanup` is specified, after the thread finishes the execution of the
`fn` procedure, the resources associated with the thread are going to be
automatically freed. **Do not** dereference the `^Thread` pointer, if this
flag is specified.
**IMPORTANT**: If `init_context` is specified and the default temporary allocator
is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
in order to free the resources associated with the temporary allocations.
*/
create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread { create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread {
thread_proc :: proc(t: ^Thread) { thread_proc :: proc(t: ^Thread) {
fn := cast(proc())t.data fn := cast(proc())t.data
@@ -154,9 +279,22 @@ create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil,
return t return t
} }
/*
Run a procedure with one pointer parameter on a different thread.
This procedure runs the given procedure on another thread. The context
specified by `init_context` will be used as the context in which `fn` is going
to execute. The thread will have priority specified by the `priority` parameter.
If `self_cleanup` is specified, after the thread finishes the execution of the
`fn` procedure, the resources associated with the thread are going to be
automatically freed. **Do not** dereference the `^Thread` pointer, if this
flag is specified.
**IMPORTANT**: If `init_context` is specified and the default temporary allocator
is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
in order to free the resources associated with the temporary allocations.
*/
create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread { create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread {
thread_proc :: proc(t: ^Thread) { thread_proc :: proc(t: ^Thread) {
fn := cast(proc(rawptr))t.data fn := cast(proc(rawptr))t.data
@@ -176,6 +314,22 @@ create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_co
return t return t
} }
/*
Run a procedure with one polymorphic parameter on a different thread.
This procedure runs the given procedure on another thread. The context
specified by `init_context` will be used as the context in which `fn` is going
to execute. The thread will have priority specified by the `priority` parameter.
If `self_cleanup` is specified, after the thread finishes the execution of the
`fn` procedure, the resources associated with the thread are going to be
automatically freed. **Do not** dereference the `^Thread` pointer, if this
flag is specified.
**IMPORTANT**: If `init_context` is specified and the default temporary allocator
is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
in order to free the resources associated with the temporary allocations.
*/
create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread
where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
thread_proc :: proc(t: ^Thread) { thread_proc :: proc(t: ^Thread) {
@@ -201,6 +355,22 @@ create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_contex
return t return t
} }
/*
Run a procedure with two polymorphic parameters on a different thread.
This procedure runs the given procedure on another thread. The context
specified by `init_context` will be used as the context in which `fn` is going
to execute. The thread will have priority specified by the `priority` parameter.
If `self_cleanup` is specified, after the thread finishes the execution of the
`fn` procedure, the resources associated with the thread are going to be
automatically freed. **Do not** dereference the `^Thread` pointer, if this
flag is specified.
**IMPORTANT**: If `init_context` is specified and the default temporary allocator
is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
in order to free the resources associated with the temporary allocations.
*/
create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread
where size_of(T1) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { where size_of(T1) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
thread_proc :: proc(t: ^Thread) { thread_proc :: proc(t: ^Thread) {
@@ -232,6 +402,22 @@ create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2),
return t return t
} }
/*
Run a procedure with three polymorphic parameters on a different thread.
This procedure runs the given procedure on another thread. The context
specified by `init_context` will be used as the context in which `fn` is going
to execute. The thread will have priority specified by the `priority` parameter.
If `self_cleanup` is specified, after the thread finishes the execution of the
`fn` procedure, the resources associated with the thread are going to be
automatically freed. **Do not** dereference the `^Thread` pointer, if this
flag is specified.
**IMPORTANT**: If `init_context` is specified and the default temporary allocator
is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
in order to free the resources associated with the temporary allocations.
*/
create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread
where size_of(T1) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { where size_of(T1) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
thread_proc :: proc(t: ^Thread) { thread_proc :: proc(t: ^Thread) {
@@ -264,6 +450,23 @@ create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: pr
start(t) start(t)
return t return t
} }
/*
Run a procedure with four polymorphic parameters on a different thread.
This procedure runs the given procedure on another thread. The context
specified by `init_context` will be used as the context in which `fn` is going
to execute. The thread will have priority specified by the `priority` parameter.
If `self_cleanup` is specified, after the thread finishes the execution of the
`fn` procedure, the resources associated with the thread are going to be
automatically freed. **Do not** dereference the `^Thread` pointer, if this
flag is specified.
**IMPORTANT**: If `init_context` is specified and the default temporary allocator
is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
in order to free the resources associated with the temporary allocations.
*/
create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread
where size_of(T1) + size_of(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS { where size_of(T1) + size_of(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
thread_proc :: proc(t: ^Thread) { thread_proc :: proc(t: ^Thread) {
+5 -3
View File
@@ -81,9 +81,12 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
defer unix.pthread_attr_destroy(&attrs) defer unix.pthread_attr_destroy(&attrs)
// NOTE(tetra, 2019-11-01): These only fail if their argument is invalid. // NOTE(tetra, 2019-11-01): These only fail if their argument is invalid.
assert(unix.pthread_attr_setdetachstate(&attrs, unix.PTHREAD_CREATE_JOINABLE) == 0) res: i32
res = unix.pthread_attr_setdetachstate(&attrs, unix.PTHREAD_CREATE_JOINABLE)
assert(res == 0)
when ODIN_OS != .Haiku && ODIN_OS != .NetBSD { when ODIN_OS != .Haiku && ODIN_OS != .NetBSD {
assert(unix.pthread_attr_setinheritsched(&attrs, unix.PTHREAD_EXPLICIT_SCHED) == 0) res = unix.pthread_attr_setinheritsched(&attrs, unix.PTHREAD_EXPLICIT_SCHED)
assert(res == 0)
} }
thread := new(Thread) thread := new(Thread)
@@ -94,7 +97,6 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
// Set thread priority. // Set thread priority.
policy: i32 policy: i32
res: i32
when ODIN_OS != .Haiku && ODIN_OS != .NetBSD { when ODIN_OS != .Haiku && ODIN_OS != .NetBSD {
res = unix.pthread_attr_getschedpolicy(&attrs, &policy) res = unix.pthread_attr_getschedpolicy(&attrs, &policy)
assert(res == 0) assert(res == 0)
+61 -5
View File
@@ -1,16 +1,46 @@
package datetime package datetime
// Ordinal 1 = Midnight Monday, January 1, 1 A.D. (Gregorian) /*
// | Midnight Monday, January 3, 1 A.D. (Julian) Type representing a mononotic day number corresponding to a date.
Ordinal 1 = Midnight Monday, January 1, 1 A.D. (Gregorian)
| Midnight Monday, January 3, 1 A.D. (Julian)
*/
Ordinal :: i64 Ordinal :: i64
/*
*/
EPOCH :: Ordinal(1) EPOCH :: Ordinal(1)
// Minimum and maximum dates and ordinals. Chosen for safe roundtripping. /*
Minimum valid value for date.
The value is chosen such that a conversion `date -> ordinal -> date` is always
safe.
*/
MIN_DATE :: Date{year = -25_252_734_927_766_552, month = 1, day = 1} MIN_DATE :: Date{year = -25_252_734_927_766_552, month = 1, day = 1}
/*
Maximum valid value for date
The value is chosen such that a conversion `date -> ordinal -> date` is always
safe.
*/
MAX_DATE :: Date{year = 25_252_734_927_766_552, month = 12, day = 31} MAX_DATE :: Date{year = 25_252_734_927_766_552, month = 12, day = 31}
/*
Minimum value for an ordinal
*/
MIN_ORD :: Ordinal(-9_223_372_036_854_775_234) MIN_ORD :: Ordinal(-9_223_372_036_854_775_234)
/*
Maximum value for an ordinal
*/
MAX_ORD :: Ordinal( 9_223_372_036_854_774_869) MAX_ORD :: Ordinal( 9_223_372_036_854_774_869)
/*
Possible errors returned by datetime functions.
*/
Error :: enum { Error :: enum {
None, None,
Invalid_Year, Invalid_Year,
@@ -24,12 +54,22 @@ Error :: enum {
Invalid_Delta, Invalid_Delta,
} }
/*
A type representing a date.
The minimum and maximum values for a year can be found in `MIN_DATE` and
`MAX_DATE` constants. The `month` field can range from 1 to 12, and the day
ranges from 1 to however many days there are in the specified month.
*/
Date :: struct { Date :: struct {
year: i64, year: i64,
month: i8, month: i8,
day: i8, day: i8,
} }
/*
A type representing a time within a single day within a nanosecond precision.
*/
Time :: struct { Time :: struct {
hour: i8, hour: i8,
minute: i8, minute: i8,
@@ -37,17 +77,30 @@ Time :: struct {
nano: i32, nano: i32,
} }
/*
A type representing datetime.
*/
DateTime :: struct { DateTime :: struct {
using date: Date, using date: Date,
using time: Time, using time: Time,
} }
/*
A type representing a difference between two instances of datetime.
**Note**: All fields are i64 because we can also use it to add a number of
seconds or nanos to a moment, that are then normalized within their respective
ranges.
*/
Delta :: struct { Delta :: struct {
days: i64, // These are all i64 because we can also use it to add a number of seconds or nanos to a moment, days: i64,
seconds: i64, // that are then normalized within their respective ranges. seconds: i64,
nanos: i64, nanos: i64,
} }
/*
Type representing one of the months.
*/
Month :: enum i8 { Month :: enum i8 {
January = 1, January = 1,
February, February,
@@ -63,6 +116,9 @@ Month :: enum i8 {
December, December,
} }
/*
Type representing one of the weekdays.
*/
Weekday :: enum i8 { Weekday :: enum i8 {
Sunday = 0, Sunday = 0,
Monday, Monday,
+168 -4
View File
@@ -1,56 +1,113 @@
/* /*
Calendrical conversions using a proleptic Gregorian calendar. Calendrical conversions using a proleptic Gregorian calendar.
Implemented using formulas from: Calendrical Calculations Ultimate Edition, Reingold & Dershowitz Implemented using formulas from: Calendrical Calculations Ultimate Edition,
Reingold & Dershowitz
*/ */
package datetime package datetime
import "base:intrinsics" import "base:intrinsics"
// Procedures that return an Ordinal /*
Obtain an ordinal from a date.
This procedure converts the specified date into an ordinal. If the specified
date is not a valid date, an error is returned.
*/
date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal, err: Error) { date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal, err: Error) {
validate(date) or_return validate(date) or_return
return unsafe_date_to_ordinal(date), .None return unsafe_date_to_ordinal(date), .None
} }
/*
Obtain an ordinal from date components.
This procedure converts the specified date, provided by its individual
components, into an ordinal. If the specified date is not a valid date, an error
is returned.
*/
components_to_ordinal :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (ordinal: Ordinal, err: Error) { components_to_ordinal :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (ordinal: Ordinal, err: Error) {
validate(year, month, day) or_return validate(year, month, day) or_return
return unsafe_date_to_ordinal({year, i8(month), i8(day)}), .None return unsafe_date_to_ordinal({year, i8(month), i8(day)}), .None
} }
// Procedures that return a Date /*
Obtain date using an Ordinal.
This provedure converts the specified ordinal into a date. If the ordinal is not
a valid ordinal, an error is returned.
*/
ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date, err: Error) { ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date, err: Error) {
validate(ordinal) or_return validate(ordinal) or_return
return unsafe_ordinal_to_date(ordinal), .None return unsafe_ordinal_to_date(ordinal), .None
} }
/*
Obtain a date from date components.
This procedure converts date components, specified by a year, a month and a day,
into a date object. If the provided date components don't represent a valid
date, an error is returned.
*/
components_to_date :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (date: Date, err: Error) { components_to_date :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (date: Date, err: Error) {
validate(year, month, day) or_return validate(year, month, day) or_return
return Date{i64(year), i8(month), i8(day)}, .None return Date{i64(year), i8(month), i8(day)}, .None
} }
/*
Obtain time from time components.
This procedure converts time components, specified by an hour, a minute, a second
and nanoseconds, into a time object. If the provided time components don't
represent a valid time, an error is returned.
*/
components_to_time :: proc "contextless" (#any_int hour, #any_int minute, #any_int second: i64, #any_int nanos := i64(0)) -> (time: Time, err: Error) { components_to_time :: proc "contextless" (#any_int hour, #any_int minute, #any_int second: i64, #any_int nanos := i64(0)) -> (time: Time, err: Error) {
validate(hour, minute, second, nanos) or_return validate(hour, minute, second, nanos) or_return
return Time{i8(hour), i8(minute), i8(second), i32(nanos)}, .None return Time{i8(hour), i8(minute), i8(second), i32(nanos)}, .None
} }
/*
Obtain datetime from components.
This procedure converts date components and time components into a datetime object.
If the provided date components or time components don't represent a valid
datetime, an error is returned.
*/
components_to_datetime :: proc "contextless" (#any_int year, #any_int month, #any_int day, #any_int hour, #any_int minute, #any_int second: i64, #any_int nanos := i64(0)) -> (datetime: DateTime, err: Error) { components_to_datetime :: proc "contextless" (#any_int year, #any_int month, #any_int day, #any_int hour, #any_int minute, #any_int second: i64, #any_int nanos := i64(0)) -> (datetime: DateTime, err: Error) {
date := components_to_date(year, month, day) or_return date := components_to_date(year, month, day) or_return
time := components_to_time(hour, minute, second, nanos) or_return time := components_to_time(hour, minute, second, nanos) or_return
return {date, time}, .None return {date, time}, .None
} }
/*
Obtain an datetime from an ordinal.
This procedure converts the value of an ordinal into a datetime. Since the
ordinal only has the amount of days, the resulting time in the datetime
object will always have the time equal to `00:00:00.000`.
*/
ordinal_to_datetime :: proc "contextless" (ordinal: Ordinal) -> (datetime: DateTime, err: Error) { ordinal_to_datetime :: proc "contextless" (ordinal: Ordinal) -> (datetime: DateTime, err: Error) {
d := ordinal_to_date(ordinal) or_return d := ordinal_to_date(ordinal) or_return
return {Date(d), {}}, .None return {Date(d), {}}, .None
} }
/*
Calculate the weekday from an ordinal.
This procedure takes the value of an ordinal and returns the day of week for
that ordinal.
*/
day_of_week :: proc "contextless" (ordinal: Ordinal) -> (day: Weekday) { day_of_week :: proc "contextless" (ordinal: Ordinal) -> (day: Weekday) {
return Weekday((ordinal - EPOCH + 1) %% 7) return Weekday((ordinal - EPOCH + 1) %% 7)
} }
/*
Calculate the difference between two dates.
This procedure calculates the difference between two dates `a - b`, and returns
a delta between the two dates in `days`. If either `a` or `b` is not a valid
date, an error is returned.
*/
subtract_dates :: proc "contextless" (a, b: Date) -> (delta: Delta, err: Error) { subtract_dates :: proc "contextless" (a, b: Date) -> (delta: Delta, err: Error) {
ord_a := date_to_ordinal(a) or_return ord_a := date_to_ordinal(a) or_return
ord_b := date_to_ordinal(b) or_return ord_b := date_to_ordinal(b) or_return
@@ -59,6 +116,16 @@ subtract_dates :: proc "contextless" (a, b: Date) -> (delta: Delta, err: Error)
return return
} }
/*
Calculate the difference between two datetimes.
This procedure calculates the difference between two datetimes, `a - b`, and
returns a delta between the two dates. The difference is returned in all three
fields of the `Delta` struct: the difference in days, the difference in seconds
and the difference in nanoseconds.
If either `a` or `b` is not a valid datetime, an error is returned.
*/
subtract_datetimes :: proc "contextless" (a, b: DateTime) -> (delta: Delta, err: Error) { subtract_datetimes :: proc "contextless" (a, b: DateTime) -> (delta: Delta, err: Error) {
ord_a := date_to_ordinal(a) or_return ord_a := date_to_ordinal(a) or_return
ord_b := date_to_ordinal(b) or_return ord_b := date_to_ordinal(b) or_return
@@ -73,19 +140,42 @@ subtract_datetimes :: proc "contextless" (a, b: DateTime) -> (delta: Delta, err:
return return
} }
/*
Calculate a difference between two deltas.
*/
subtract_deltas :: proc "contextless" (a, b: Delta) -> (delta: Delta, err: Error) { subtract_deltas :: proc "contextless" (a, b: Delta) -> (delta: Delta, err: Error) {
delta = Delta{a.days - b.days, a.seconds - b.seconds, a.nanos - b.nanos} delta = Delta{a.days - b.days, a.seconds - b.seconds, a.nanos - b.nanos}
delta = normalize_delta(delta) or_return delta = normalize_delta(delta) or_return
return return
} }
/*
Calculate a difference between two datetimes, dates or deltas.
*/
sub :: proc{subtract_datetimes, subtract_dates, subtract_deltas} sub :: proc{subtract_datetimes, subtract_dates, subtract_deltas}
/*
Add certain amount of days to a date.
This procedure adds the specified amount of days to a date and returns a new
date. The new date would have happened the specified amount of days after the
specified date.
*/
add_days_to_date :: proc "contextless" (a: Date, days: i64) -> (date: Date, err: Error) { add_days_to_date :: proc "contextless" (a: Date, days: i64) -> (date: Date, err: Error) {
ord := date_to_ordinal(a) or_return ord := date_to_ordinal(a) or_return
ord += days ord += days
return ordinal_to_date(ord) return ordinal_to_date(ord)
} }
/*
Add delta to a date.
This procedure adds a delta to a date, and returns a new date. The new date
would have happened the time specified by `delta` after the specified date.
**Note**: The delta is assumed to be normalized. That is, if it contains seconds
or milliseconds, regardless of the amount only the days will be added.
*/
add_delta_to_date :: proc "contextless" (a: Date, delta: Delta) -> (date: Date, err: Error) { add_delta_to_date :: proc "contextless" (a: Date, delta: Delta) -> (date: Date, err: Error) {
ord := date_to_ordinal(a) or_return ord := date_to_ordinal(a) or_return
// Because the input is a Date, we add only the days from the Delta. // Because the input is a Date, we add only the days from the Delta.
@@ -93,6 +183,13 @@ add_delta_to_date :: proc "contextless" (a: Date, delta: Delta) -> (date: Date,
return ordinal_to_date(ord) return ordinal_to_date(ord)
} }
/*
Add delta to datetime.
This procedure adds a delta to a datetime, and returns a new datetime. The new
datetime would have happened the time specified by `delta` after the specified
datetime.
*/
add_delta_to_datetime :: proc "contextless" (a: DateTime, delta: Delta) -> (datetime: DateTime, err: Error) { add_delta_to_datetime :: proc "contextless" (a: DateTime, delta: Delta) -> (datetime: DateTime, err: Error) {
days := date_to_ordinal(a) or_return days := date_to_ordinal(a) or_return
@@ -110,8 +207,18 @@ add_delta_to_datetime :: proc "contextless" (a: DateTime, delta: Delta) -> (date
datetime.time = components_to_time(hour, minute, second, sum_delta.nanos) or_return datetime.time = components_to_time(hour, minute, second, sum_delta.nanos) or_return
return return
} }
/*
Add days to a date, delta to a date or delta to datetime.
*/
add :: proc{add_days_to_date, add_delta_to_date, add_delta_to_datetime} add :: proc{add_days_to_date, add_delta_to_date, add_delta_to_datetime}
/*
Obtain the day number in a year
This procedure returns the number of the day in a year, starting from 1. If
the date is not a valid date, an error is returned.
*/
day_number :: proc "contextless" (date: Date) -> (day_number: i64, err: Error) { day_number :: proc "contextless" (date: Date) -> (day_number: i64, err: Error) {
validate(date) or_return validate(date) or_return
@@ -120,6 +227,13 @@ day_number :: proc "contextless" (date: Date) -> (day_number: i64, err: Error) {
return return
} }
/*
Obtain the remaining number of days in a year.
This procedure returns the number of days between the specified date and
December 31 of the same year. If the date is not a valid date, an error is
returned.
*/
days_remaining :: proc "contextless" (date: Date) -> (days_remaining: i64, err: Error) { days_remaining :: proc "contextless" (date: Date) -> (days_remaining: i64, err: Error) {
// Alternative formulation `day_number` subtracted from 365 or 366 depending on leap year // Alternative formulation `day_number` subtracted from 365 or 366 depending on leap year
validate(date) or_return validate(date) or_return
@@ -127,6 +241,12 @@ days_remaining :: proc "contextless" (date: Date) -> (days_remaining: i64, err:
return delta.days, .None return delta.days, .None
} }
/*
Obtain the last day of a given month on a given year.
This procedure returns the amount of days in a specified month on a specified
date. If the specified year or month is not valid, an error is returned.
*/
last_day_of_month :: proc "contextless" (#any_int year: i64, #any_int month: i8) -> (day: i8, err: Error) { last_day_of_month :: proc "contextless" (#any_int year: i64, #any_int month: i8) -> (day: i8, err: Error) {
// Not using formula 2.27 from the book. This is far simpler and gives the same answer. // Not using formula 2.27 from the book. This is far simpler and gives the same answer.
@@ -140,16 +260,33 @@ last_day_of_month :: proc "contextless" (#any_int year: i64, #any_int month: i8)
return return
} }
/*
Obtain the new year date of a given year.
This procedure returns the January 1st date of the specified year. If the year
is not valid, an error is returned.
*/
new_year :: proc "contextless" (#any_int year: i64) -> (new_year: Date, err: Error) { new_year :: proc "contextless" (#any_int year: i64) -> (new_year: Date, err: Error) {
validate(year, 1, 1) or_return validate(year, 1, 1) or_return
return {year, 1, 1}, .None return {year, 1, 1}, .None
} }
/*
Obtain the end year of a given date.
This procedure returns the December 31st date of the specified year. If the year
is not valid, an error is returned.
*/
year_end :: proc "contextless" (#any_int year: i64) -> (year_end: Date, err: Error) { year_end :: proc "contextless" (#any_int year: i64) -> (year_end: Date, err: Error) {
validate(year, 12, 31) or_return validate(year, 12, 31) or_return
return {year, 12, 31}, .None return {year, 12, 31}, .None
} }
/*
Obtain the range of dates for a given year.
This procedure returns dates, for every day of a given year in a slice.
*/
year_range :: proc (#any_int year: i64, allocator := context.allocator) -> (range: []Date) { year_range :: proc (#any_int year: i64, allocator := context.allocator) -> (range: []Date) {
is_leap := is_leap_year(year) is_leap := is_leap_year(year)
@@ -171,6 +308,15 @@ year_range :: proc (#any_int year: i64, allocator := context.allocator) -> (rang
return return
} }
/*
Normalize the delta.
This procedure normalizes the delta in such a way that the number of seconds
is between 0 and the number of seconds in the day and nanoseconds is between
0 and 10^9.
If the value for `days` overflows during this operation, an error is returned.
*/
normalize_delta :: proc "contextless" (delta: Delta) -> (normalized: Delta, err: Error) { normalize_delta :: proc "contextless" (delta: Delta) -> (normalized: Delta, err: Error) {
// Distribute nanos into seconds and remainder // Distribute nanos into seconds and remainder
seconds, nanos := divmod(delta.nanos, 1e9) seconds, nanos := divmod(delta.nanos, 1e9)
@@ -194,6 +340,12 @@ normalize_delta :: proc "contextless" (delta: Delta) -> (normalized: Delta, err:
// The following procedures don't check whether their inputs are in a valid range. // The following procedures don't check whether their inputs are in a valid range.
// They're still exported for those who know their inputs have been validated. // They're still exported for those who know their inputs have been validated.
/*
Obtain an ordinal from a date.
This procedure converts a date into an ordinal. If the date is not a valid date,
the result is unspecified.
*/
unsafe_date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal) { unsafe_date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal) {
year_minus_one := date.year - 1 year_minus_one := date.year - 1
@@ -223,6 +375,12 @@ unsafe_date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal)
return return
} }
/*
Obtain a year and a day of the year from an ordinal.
This procedure returns the year and the day of the year of a given ordinal.
Of the ordinal is outside of its valid range, the result is unspecified.
*/
unsafe_ordinal_to_year :: proc "contextless" (ordinal: Ordinal) -> (year: i64, day_ordinal: i64) { unsafe_ordinal_to_year :: proc "contextless" (ordinal: Ordinal) -> (year: i64, day_ordinal: i64) {
// Days after epoch // Days after epoch
d0 := ordinal - EPOCH d0 := ordinal - EPOCH
@@ -253,6 +411,12 @@ unsafe_ordinal_to_year :: proc "contextless" (ordinal: Ordinal) -> (year: i64, d
return year + 1, day_ordinal return year + 1, day_ordinal
} }
/*
Obtain a date from an ordinal.
This procedure converts an ordinal into a date. If the ordinal is outside of
its valid range, the result is unspecified.
*/
unsafe_ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date) { unsafe_ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date) {
year, _ := unsafe_ordinal_to_year(ordinal) year, _ := unsafe_ordinal_to_year(ordinal)
+1
View File
@@ -1,3 +1,4 @@
//+private
package datetime package datetime
// Internal helper functions for calendrical conversions // Internal helper functions for calendrical conversions
+43 -1
View File
@@ -1,14 +1,29 @@
package datetime package datetime
// Validation helpers // Validation helpers
/*
Check if a year is a leap year.
*/
is_leap_year :: proc "contextless" (#any_int year: i64) -> (leap: bool) { is_leap_year :: proc "contextless" (#any_int year: i64) -> (leap: bool) {
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
} }
/*
Check for errors in date formation.
This procedure validates all fields of a date, and if any of the fields is
outside of allowed range, an error is returned.
*/
validate_date :: proc "contextless" (date: Date) -> (err: Error) { validate_date :: proc "contextless" (date: Date) -> (err: Error) {
return validate(date.year, date.month, date.day) return validate(date.year, date.month, date.day)
} }
/*
Check for errors in date formation given date components.
This procedure checks whether a date formed by the specified year month and a
day is a valid date. If not, an error is returned.
*/
validate_year_month_day :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (err: Error) { validate_year_month_day :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (err: Error) {
if year < MIN_DATE.year || year > MAX_DATE.year { if year < MIN_DATE.year || year > MAX_DATE.year {
return .Invalid_Year return .Invalid_Year
@@ -29,6 +44,12 @@ validate_year_month_day :: proc "contextless" (#any_int year, #any_int month, #a
return .None return .None
} }
/*
Check for errors in Ordinal
This procedure checks if the ordinal is in a valid range for roundtrip
conversions with the dates. If not, an error is returned.
*/
validate_ordinal :: proc "contextless" (ordinal: Ordinal) -> (err: Error) { validate_ordinal :: proc "contextless" (ordinal: Ordinal) -> (err: Error) {
if ordinal < MIN_ORD || ordinal > MAX_ORD { if ordinal < MIN_ORD || ordinal > MAX_ORD {
return .Invalid_Ordinal return .Invalid_Ordinal
@@ -36,10 +57,22 @@ validate_ordinal :: proc "contextless" (ordinal: Ordinal) -> (err: Error) {
return return
} }
/*
Check for errors in time formation
This procedure checks whether time has all fields in valid ranges, and if not
an error is returned.
*/
validate_time :: proc "contextless" (time: Time) -> (err: Error) { validate_time :: proc "contextless" (time: Time) -> (err: Error) {
return validate(time.hour, time.minute, time.second, time.nano) return validate(time.hour, time.minute, time.second, time.nano)
} }
/*
Check for errors in time formed by its components.
This procedure checks whether the time formed by its components is valid, and
if not an error is returned.
*/
validate_hour_minute_second :: proc "contextless" (#any_int hour, #any_int minute, #any_int second, #any_int nano: i64) -> (err: Error) { validate_hour_minute_second :: proc "contextless" (#any_int hour, #any_int minute, #any_int second, #any_int nano: i64) -> (err: Error) {
if hour < 0 || hour > 23 { if hour < 0 || hour > 23 {
return .Invalid_Hour return .Invalid_Hour
@@ -56,12 +89,21 @@ validate_hour_minute_second :: proc "contextless" (#any_int hour, #any_int minut
return .None return .None
} }
/*
Check for errors in datetime formation.
This procedure checks whether all fields of date and time in the specified
datetime are valid, and if not, an error is returned.
*/
validate_datetime :: proc "contextless" (datetime: DateTime) -> (err: Error) { validate_datetime :: proc "contextless" (datetime: DateTime) -> (err: Error) {
validate(datetime.date) or_return validate(datetime.date) or_return
validate(datetime.time) or_return validate(datetime.time) or_return
return .None return .None
} }
/*
Check for errors in date, time or datetime.
*/
validate :: proc{ validate :: proc{
validate_date, validate_date,
validate_year_month_day, validate_year_month_day,
+75 -13
View File
@@ -3,23 +3,62 @@ package time
import dt "core:time/datetime" import dt "core:time/datetime"
// Parses an ISO 8601 string and returns Time in UTC, with any UTC offset applied to it. /*
// Only 4-digit years are accepted. Parse an ISO 8601 string into a time with UTC offset applied to it.
// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second.
// Leap seconds are smeared into 23:59:59. This procedure parses an ISO 8601 string of roughly the following format:
```text
YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm
```
And returns time, in UTC represented by that string. In case the timezone offset
is specified in the string, that timezone is applied to time.
**Inputs**:
- `iso_datetime`: The string to be parsed.
- `is_leap`: Optional output parameter, specifying if the moment was a leap second.
**Returns**:
- `res`: The time represented by `iso_datetime`, with UTC offset applied.
- `consumed`: Number of bytes consumed by parsing the string.
**Notes**:
- Only 4-digit years are accepted.
- Leap seconds are smeared into 23:59:59.
*/
iso8601_to_time_utc :: proc(iso_datetime: string, is_leap: ^bool = nil) -> (res: Time, consumed: int) { iso8601_to_time_utc :: proc(iso_datetime: string, is_leap: ^bool = nil) -> (res: Time, consumed: int) {
offset: int offset: int
res, offset, consumed = iso8601_to_time_and_offset(iso_datetime, is_leap) res, offset, consumed = iso8601_to_time_and_offset(iso_datetime, is_leap)
res._nsec += (i64(-offset) * i64(Minute)) res._nsec += (i64(-offset) * i64(Minute))
return res, consumed return res, consumed
} }
// Parses an ISO 8601 string and returns Time and a UTC offset in minutes. /*
// e.g. 1985-04-12T23:20:50.52Z Parse an ISO 8601 string into a time and a UTC offset in minutes.
// Note: Only 4-digit years are accepted.
// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second. This procedure parses an ISO 8601 string of roughly the following format:
// Leap seconds are smeared into 23:59:59.
```text
YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm
```
And returns time, in UTC represented by that string, and the UTC offset, in
minutes.
**Inputs**:
- `iso_datetime`: The string to be parsed.
- `is_leap`: Optional output parameter, specifying if the moment was a leap second.
**Returns**:
- `res`: The time in UTC.
- `utc_offset`: The UTC offset of the time, in minutes.
- `consumed`: Number of bytes consumed by parsing the string.
**Notes**:
- Only 4-digit years are accepted.
- Leap seconds are smeared into 23:59:59.
*/
iso8601_to_time_and_offset :: proc(iso_datetime: string, is_leap: ^bool = nil) -> (res: Time, utc_offset: int, consumed: int) { iso8601_to_time_and_offset :: proc(iso_datetime: string, is_leap: ^bool = nil) -> (res: Time, utc_offset: int, consumed: int) {
moment, offset, leap_second, count := iso8601_to_components(iso_datetime) moment, offset, leap_second, count := iso8601_to_components(iso_datetime)
if count == 0 { if count == 0 {
@@ -37,9 +76,32 @@ iso8601_to_time_and_offset :: proc(iso_datetime: string, is_leap: ^bool = nil) -
} }
} }
// Parses an ISO 8601 string and returns Time and a UTC offset in minutes. /*
// e.g. 1985-04-12T23:20:50.52Z Parse an ISO 8601 string into a datetime and a UTC offset in minutes.
// Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given
This procedure parses an ISO 8601 string of roughly the following format:
```text
YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm
```
And returns datetime, in UTC represented by that string, and the UTC offset, in
minutes.
**Inputs**:
- `iso_datetime`: The string to be parsed
**Returns**:
- `res`: The parsed datetime, in UTC.
- `utc_offset`: The UTC offset, in minutes.
- `is_leap`: Specifies whether the moment was a leap second.
- `consumed`: The number of bytes consumed by parsing the string.
**Notes**:
- This procedure performs no validation on whether components are valid,
e.g. it'll return hour = 25 if that's what it's given in the specified
string.
*/
iso8601_to_components :: proc(iso_datetime: string) -> (res: dt.DateTime, utc_offset: int, is_leap: bool, consumed: int) { iso8601_to_components :: proc(iso_datetime: string) -> (res: dt.DateTime, utc_offset: int, is_leap: bool, consumed: int) {
moment, offset, count, leap_second, ok := _iso8601_to_components(iso_datetime) moment, offset, count, leap_second, ok := _iso8601_to_components(iso_datetime)
if !ok { if !ok {
+81 -11
View File
@@ -3,18 +3,39 @@ package time
import "base:runtime" import "base:runtime"
import "base:intrinsics" import "base:intrinsics"
/*
Type representing monotonic time, useful for measuring durations.
*/
Tick :: struct { Tick :: struct {
_nsec: i64, // relative amount _nsec: i64, // relative amount
} }
/*
Obtain the current tick.
*/
tick_now :: proc "contextless" () -> Tick { tick_now :: proc "contextless" () -> Tick {
return _tick_now() return _tick_now()
} }
/*
Obtain the difference between ticks.
*/
tick_diff :: proc "contextless" (start, end: Tick) -> Duration { tick_diff :: proc "contextless" (start, end: Tick) -> Duration {
d := end._nsec - start._nsec d := end._nsec - start._nsec
return Duration(d) return Duration(d)
} }
/*
Incrementally obtain durations since last tick.
This procedure returns the duration between the current tick and the tick
stored in `prev` pointer, and then stores the current tick in location,
specified by `prev`. If the prev pointer contains an zero-initialized tick,
then the returned duration is 0.
This procedure is meant to be used in a loop, or in other scenarios, where one
might want to obtain time between multiple ticks at specific points.
*/
tick_lap_time :: proc "contextless" (prev: ^Tick) -> Duration { tick_lap_time :: proc "contextless" (prev: ^Tick) -> Duration {
d: Duration d: Duration
t := tick_now() t := tick_now()
@@ -25,17 +46,21 @@ tick_lap_time :: proc "contextless" (prev: ^Tick) -> Duration {
return d return d
} }
/*
Obtain the duration since last tick.
*/
tick_since :: proc "contextless" (start: Tick) -> Duration { tick_since :: proc "contextless" (start: Tick) -> Duration {
return tick_diff(start, tick_now()) return tick_diff(start, tick_now())
} }
/*
Capture the duration the code in the current scope takes to execute.
*/
@(deferred_in_out=_tick_duration_end) @(deferred_in_out=_tick_duration_end)
SCOPED_TICK_DURATION :: proc "contextless" (d: ^Duration) -> Tick { SCOPED_TICK_DURATION :: proc "contextless" (d: ^Duration) -> Tick {
return tick_now() return tick_now()
} }
_tick_duration_end :: proc "contextless" (d: ^Duration, t: Tick) { _tick_duration_end :: proc "contextless" (d: ^Duration, t: Tick) {
d^ = tick_since(t) d^ = tick_since(t)
} }
@@ -62,6 +87,13 @@ when ODIN_OS != .Darwin && ODIN_OS != .Linux && ODIN_OS != .FreeBSD {
} }
} }
/*
Check if the CPU has invariant TSC.
This procedure checks if the CPU contains an invariant TSC (Time stamp counter).
Invariant TSC is a feature of modern processors that allows them to run their
TSC at a fixed frequency, independent of ACPI state, and CPU frequency.
*/
has_invariant_tsc :: proc "contextless" () -> bool { has_invariant_tsc :: proc "contextless" () -> bool {
when ODIN_ARCH == .amd64 { when ODIN_ARCH == .amd64 {
return x86_has_invariant_tsc() return x86_has_invariant_tsc()
@@ -70,6 +102,17 @@ has_invariant_tsc :: proc "contextless" () -> bool {
return false return false
} }
/*
Obtain the CPU's TSC frequency, in hertz.
This procedure tries to obtain the CPU's TSC frequency in hertz. If the CPU
doesn't have an invariant TSC, this procedure returns with an error. Otherwise
an attempt is made to fetch the TSC frequency from the OS. If this fails,
the frequency is obtained by sleeping for the specified amount of time and
dividing the readings from TSC by the duration of the sleep.
The duration of sleep can be controlled by `fallback_sleep` parameter.
*/
tsc_frequency :: proc "contextless" (fallback_sleep := 2 * Second) -> (u64, bool) { tsc_frequency :: proc "contextless" (fallback_sleep := 2 * Second) -> (u64, bool) {
if !has_invariant_tsc() { if !has_invariant_tsc() {
return 0, false return 0, false
@@ -93,37 +136,64 @@ tsc_frequency :: proc "contextless" (fallback_sleep := 2 * Second) -> (u64, bool
return hz, true return hz, true
} }
/* // Benchmark helpers
Benchmark helpers
*/
/*
Errors returned by the `benchmark()` procedure.
*/
Benchmark_Error :: enum { Benchmark_Error :: enum {
Okay = 0, Okay = 0,
Allocation_Error, Allocation_Error,
} }
/*
Options for benchmarking.
*/
Benchmark_Options :: struct { Benchmark_Options :: struct {
// The initialization procedure. `benchmark()` will call this before taking measurements.
setup: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error), setup: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error),
// The procedure to benchmark.
bench: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error), bench: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error),
// The deinitialization procedure.
teardown: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error), teardown: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error),
// Field to be used by `bench()` procedure for any purpose.
rounds: int, rounds: int,
// Field to be used by `bench()` procedure for any purpose.
bytes: int, bytes: int,
// Field to be used by `bench()` procedure for any purpose.
input: []u8, input: []u8,
// `bench()` writes to specify the count of elements processed.
count: int, count: int,
// `bench()` writes to specify the number of bytes processed.
processed: int, processed: int,
// `bench()` can write the output slice here.
output: []u8, // Unused for hash benchmarks output: []u8, // Unused for hash benchmarks
// `bench()` can write the output hash here.
hash: u128, hash: u128,
// `benchmark()` procedure will output the duration of benchmark
/*
Performance
*/
duration: Duration, duration: Duration,
// `benchmark()` procedure will output the average count of elements
// processed per second, using the `count` field of this struct.
rounds_per_second: f64, rounds_per_second: f64,
// `benchmark()` procedure will output the average number of megabytes
// processed per second, using the `processed` field of this struct.
megabytes_per_second: f64, megabytes_per_second: f64,
} }
/*
Benchmark a procedure.
This procedure produces a benchmark. The procedure specified in the `bench`
field of the `options` parameter will be benchmarked. The following metrics
can be obtained:
- Run time of the procedure
- Number of elements per second processed on average
- Number of bytes per second this processed on average
In order to obtain these metrics, the `bench()` procedure writes to `options`
struct the number of elements or bytes it has processed.
*/
benchmark :: proc(options: ^Benchmark_Options, allocator := context.allocator) -> (err: Benchmark_Error) { benchmark :: proc(options: ^Benchmark_Options, allocator := context.allocator) -> (err: Benchmark_Error) {
assert(options != nil) assert(options != nil)
assert(options.bench != nil) assert(options.bench != nil)
+80 -12
View File
@@ -4,10 +4,33 @@ package time
import dt "core:time/datetime" import dt "core:time/datetime"
// Parses an RFC 3339 string and returns Time in UTC, with any UTC offset applied to it. /*
// Only 4-digit years are accepted. Parse an RFC 3339 string into time with a UTC offset applied to it.
// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second.
// Leap seconds are smeared into 23:59:59. This procedure parses the specified RFC 3339 strings of roughly the following
format:
```text
YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm
```
And returns the time that was represented by the RFC 3339 string, with the UTC
offset applied to it.
**Inputs**:
- `rfc_datetime`: An RFC 3339 string to parse.
- `is_leap`: Optional output parameter specifying whether the moment was a leap
second.
**Returns**:
- `res`: The time, with UTC offset applied, that was parsed from the RFC 3339
string.
- `consumed`: The number of bytes consumed by parsing the RFC 3339 string.
**Notes**:
- Only 4-digit years are accepted.
- Leap seconds are smeared into 23:59:59.
*/
rfc3339_to_time_utc :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res: Time, consumed: int) { rfc3339_to_time_utc :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res: Time, consumed: int) {
offset: int offset: int
@@ -16,11 +39,34 @@ rfc3339_to_time_utc :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res:
return res, consumed return res, consumed
} }
// Parses an RFC 3339 string and returns Time and a UTC offset in minutes. /*
// e.g. 1985-04-12T23:20:50.52Z Parse an RFC 3339 string into a time and a UTC offset in minutes.
// Note: Only 4-digit years are accepted.
// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second. This procedure parses the specified RFC 3339 strings of roughly the following
// Leap seconds are smeared into 23:59:59. format:
```text
YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm
```
And returns the time, in UTC and a UTC offset, in minutes, that were represented
by the RFC 3339 string.
**Inputs**:
- `rfc_datetime`: The RFC 3339 string to be parsed.
- `is_leap`: Optional output parameter specifying whether the moment was a
leap second.
**Returns**:
- `res`: The time, in UTC, that was parsed from the RFC 3339 string.
- `utc_offset`: The UTC offset, in minutes, that was parsed from the RFC 3339
string.
- `consumed`: The number of bytes consumed by parsing the string.
**Notes**:
- Only 4-digit years are accepted.
- Leap seconds are smeared into 23:59:59.
*/
rfc3339_to_time_and_offset :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res: Time, utc_offset: int, consumed: int) { rfc3339_to_time_and_offset :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res: Time, utc_offset: int, consumed: int) {
moment, offset, leap_second, count := rfc3339_to_components(rfc_datetime) moment, offset, leap_second, count := rfc3339_to_components(rfc_datetime)
if count == 0 { if count == 0 {
@@ -38,9 +84,31 @@ rfc3339_to_time_and_offset :: proc(rfc_datetime: string, is_leap: ^bool = nil) -
} }
} }
// Parses an RFC 3339 string and returns Time and a UTC offset in minutes. /*
// e.g. 1985-04-12T23:20:50.52Z Parse an RFC 3339 string into a datetime and a UTC offset in minutes.
// Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given
This procedure parses the specified RFC 3339 strings of roughly the following
format:
```text
YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm
```
And returns the datetime, in UTC and the UTC offset, in minutes, that were
represented by the RFC 3339 string.
**Inputs**:
- `rfc_datetime`: The RFC 3339 string to parse.
**Returns**:
- `res`: The datetime, in UTC, that was parsed from the RFC 3339 string.
- `utc_offset`: The UTC offset, in minutes, that was parsed from the RFC 3339
string.
- `is_leap`: Specifies whether the moment was a leap second.
- `consumed`: Number of bytes consumed by parsing the string.
Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given
*/
rfc3339_to_components :: proc(rfc_datetime: string) -> (res: dt.DateTime, utc_offset: int, is_leap: bool, consumed: int) { rfc3339_to_components :: proc(rfc_datetime: string) -> (res: dt.DateTime, utc_offset: int, is_leap: bool, consumed: int) {
moment, offset, count, leap_second, ok := _rfc3339_to_components(rfc_datetime) moment, offset, count, leap_second, ok := _rfc3339_to_components(rfc_datetime)
if !ok { if !ok {
+277 -8
View File
@@ -3,24 +3,73 @@ package time
import "base:intrinsics" import "base:intrinsics"
import dt "core:time/datetime" import dt "core:time/datetime"
/*
Type representing duration, with nanosecond precision.
This is the regular Unix timestamp, scaled to nanosecond precision.
*/
Duration :: distinct i64 Duration :: distinct i64
/*
The duration equal to one nanosecond (1e-9 seconds).
*/
Nanosecond :: Duration(1) Nanosecond :: Duration(1)
/*
The duration equal to one microsecond (1e-6 seconds).
*/
Microsecond :: 1000 * Nanosecond Microsecond :: 1000 * Nanosecond
/*
The duration equal to one millisecond (1e-3 seconds).
*/
Millisecond :: 1000 * Microsecond Millisecond :: 1000 * Microsecond
/*
The duration equal to one second.
*/
Second :: 1000 * Millisecond Second :: 1000 * Millisecond
/*
The duration equal to one minute (60 seconds).
*/
Minute :: 60 * Second Minute :: 60 * Second
/*
The duration equal to one hour (3600 seconds).
*/
Hour :: 60 * Minute Hour :: 60 * Minute
/*
Minimum representable duration.
*/
MIN_DURATION :: Duration(-1 << 63) MIN_DURATION :: Duration(-1 << 63)
/*
Maximum representable duration.
*/
MAX_DURATION :: Duration(1<<63 - 1) MAX_DURATION :: Duration(1<<63 - 1)
/*
Value specifying whether the time procedures are supported by the current
platform.
*/
IS_SUPPORTED :: _IS_SUPPORTED IS_SUPPORTED :: _IS_SUPPORTED
/*
Specifies time since the UNIX epoch, with nanosecond precision.
Capable of representing any time within the following range:
- `min: 1677-09-21 00:12:44.145224192 +0000 UTC`
- `max: 2262-04-11 23:47:16.854775807 +0000 UTC`
*/
Time :: struct { Time :: struct {
_nsec: i64, // Measured in UNIX nanonseconds _nsec: i64, // Measured in UNIX nanonseconds
} }
/*
Type representing a month.
*/
Month :: enum int { Month :: enum int {
January = 1, January = 1,
February, February,
@@ -36,6 +85,9 @@ Month :: enum int {
December, December,
} }
/*
Type representing a weekday.
*/
Weekday :: enum int { Weekday :: enum int {
Sunday = 0, Sunday = 0,
Monday, Monday,
@@ -46,20 +98,37 @@ Weekday :: enum int {
Saturday, Saturday,
} }
/*
Type representing a stopwatch.
The stopwatch is used for measuring the total time in multiple "runs". When the
stopwatch is started, it starts counting time. When the stopwatch is stopped,
the difference in time between the last start and the stop is added to the
total. When the stopwatch resets, the total is reset.
*/
Stopwatch :: struct { Stopwatch :: struct {
running: bool, running: bool,
_start_time: Tick, _start_time: Tick,
_accumulation: Duration, _accumulation: Duration,
} }
/*
Obtain the current time.
*/
now :: proc "contextless" () -> Time { now :: proc "contextless" () -> Time {
return _now() return _now()
} }
/*
Sleep for the specified duration.
*/
sleep :: proc "contextless" (d: Duration) { sleep :: proc "contextless" (d: Duration) {
_sleep(d) _sleep(d)
} }
/*
Start the stopwatch.
*/
stopwatch_start :: proc "contextless" (stopwatch: ^Stopwatch) { stopwatch_start :: proc "contextless" (stopwatch: ^Stopwatch) {
if !stopwatch.running { if !stopwatch.running {
stopwatch._start_time = tick_now() stopwatch._start_time = tick_now()
@@ -67,6 +136,9 @@ stopwatch_start :: proc "contextless" (stopwatch: ^Stopwatch) {
} }
} }
/*
Stop the stopwatch.
*/
stopwatch_stop :: proc "contextless" (stopwatch: ^Stopwatch) { stopwatch_stop :: proc "contextless" (stopwatch: ^Stopwatch) {
if stopwatch.running { if stopwatch.running {
stopwatch._accumulation += tick_diff(stopwatch._start_time, tick_now()) stopwatch._accumulation += tick_diff(stopwatch._start_time, tick_now())
@@ -74,11 +146,21 @@ stopwatch_stop :: proc "contextless" (stopwatch: ^Stopwatch) {
} }
} }
/*
Reset the stopwatch.
*/
stopwatch_reset :: proc "contextless" (stopwatch: ^Stopwatch) { stopwatch_reset :: proc "contextless" (stopwatch: ^Stopwatch) {
stopwatch._accumulation = {} stopwatch._accumulation = {}
stopwatch.running = false stopwatch.running = false
} }
/*
Obtain the total time, counted by the stopwatch.
This procedure obtains the total time, counted by the stopwatch. If the stopwatch
isn't stopped at the time of calling this procedure, the time between the last
start and the current time is also accounted for.
*/
stopwatch_duration :: proc "contextless" (stopwatch: Stopwatch) -> Duration { stopwatch_duration :: proc "contextless" (stopwatch: Stopwatch) -> Duration {
if !stopwatch.running { if !stopwatch.running {
return stopwatch._accumulation return stopwatch._accumulation
@@ -86,40 +168,92 @@ stopwatch_duration :: proc "contextless" (stopwatch: Stopwatch) -> Duration {
return stopwatch._accumulation + tick_diff(stopwatch._start_time, tick_now()) return stopwatch._accumulation + tick_diff(stopwatch._start_time, tick_now())
} }
/*
Calculate the duration elapsed between two times.
*/
diff :: proc "contextless" (start, end: Time) -> Duration { diff :: proc "contextless" (start, end: Time) -> Duration {
d := end._nsec - start._nsec d := end._nsec - start._nsec
return Duration(d) return Duration(d)
} }
/*
Calculate the duration elapsed since a specific time.
*/
since :: proc "contextless" (start: Time) -> Duration { since :: proc "contextless" (start: Time) -> Duration {
return diff(start, now()) return diff(start, now())
} }
/*
Obtain the number of nanoseconds in a duration.
*/
duration_nanoseconds :: proc "contextless" (d: Duration) -> i64 { duration_nanoseconds :: proc "contextless" (d: Duration) -> i64 {
return i64(d) return i64(d)
} }
/*
Obtain the number of microseconds in a duration.
*/
duration_microseconds :: proc "contextless" (d: Duration) -> f64 { duration_microseconds :: proc "contextless" (d: Duration) -> f64 {
return duration_seconds(d) * 1e6 return duration_seconds(d) * 1e6
} }
/*
Obtain the number of milliseconds in a duration.
*/
duration_milliseconds :: proc "contextless" (d: Duration) -> f64 { duration_milliseconds :: proc "contextless" (d: Duration) -> f64 {
return duration_seconds(d) * 1e3 return duration_seconds(d) * 1e3
} }
/*
Obtain the number of seconds in a duration.
*/
duration_seconds :: proc "contextless" (d: Duration) -> f64 { duration_seconds :: proc "contextless" (d: Duration) -> f64 {
sec := d / Second sec := d / Second
nsec := d % Second nsec := d % Second
return f64(sec) + f64(nsec)/1e9 return f64(sec) + f64(nsec)/1e9
} }
/*
Obtain the number of minutes in a duration.
*/
duration_minutes :: proc "contextless" (d: Duration) -> f64 { duration_minutes :: proc "contextless" (d: Duration) -> f64 {
min := d / Minute min := d / Minute
nsec := d % Minute nsec := d % Minute
return f64(min) + f64(nsec)/(60*1e9) return f64(min) + f64(nsec)/(60*1e9)
} }
/*
Obtain the number of hours in a duration.
*/
duration_hours :: proc "contextless" (d: Duration) -> f64 { duration_hours :: proc "contextless" (d: Duration) -> f64 {
hour := d / Hour hour := d / Hour
nsec := d % Hour nsec := d % Hour
return f64(hour) + f64(nsec)/(60*60*1e9) return f64(hour) + f64(nsec)/(60*60*1e9)
} }
/*
Round a duration to a specific unit.
This procedure rounds the duration to a specific unit.
**Inputs**:
- `d`: The duration to round.
- `m`: The unit to round to.
**Returns**:
- The duration `d`, rounded to the unit specified by `m`.
**Example**:
In order to obtain the rough amount of seconds in a duration, the following call
can be used:
```
time.duration_round(my_duration, time.Second)
```
**Note**: Any duration can be supplied as a unit.
*/
duration_round :: proc "contextless" (d, m: Duration) -> Duration { duration_round :: proc "contextless" (d, m: Duration) -> Duration {
_less_than_half :: #force_inline proc "contextless" (x, y: Duration) -> bool { _less_than_half :: #force_inline proc "contextless" (x, y: Duration) -> bool {
return u64(x)+u64(x) < u64(y) return u64(x)+u64(x) < u64(y)
@@ -149,50 +283,103 @@ duration_round :: proc "contextless" (d, m: Duration) -> Duration {
return MAX_DURATION return MAX_DURATION
} }
/*
Truncate the duration to the specified unit.
This procedure truncates the duration `d` to the unit specified by `m`.
**Inputs**:
- `d`: The duration to truncate.
- `m`: The unit to truncate to.
**Returns**:
- The duration `d`, truncated to the unit specified by `m`.
**Example**:
In order to obtain the amount of whole seconds in a duration, the following call
can be used:
```
time.duration_round(my_duration, time.Second)
```
**Note**: Any duration can be supplied as a unit.
*/
duration_truncate :: proc "contextless" (d, m: Duration) -> Duration { duration_truncate :: proc "contextless" (d, m: Duration) -> Duration {
return d if m <= 0 else d - d%m return d if m <= 0 else d - d%m
} }
/*
Parse time into date components.
*/
date :: proc "contextless" (t: Time) -> (year: int, month: Month, day: int) { date :: proc "contextless" (t: Time) -> (year: int, month: Month, day: int) {
year, month, day, _ = _abs_date(_time_abs(t), true) year, month, day, _ = _abs_date(_time_abs(t), true)
return return
} }
/*
Obtain the year of the date specified by time.
*/
year :: proc "contextless" (t: Time) -> (year: int) { year :: proc "contextless" (t: Time) -> (year: int) {
year, _, _, _ = _date(t, true) year, _, _, _ = _date(t, true)
return return
} }
/*
Obtain the month of the date specified by time.
*/
month :: proc "contextless" (t: Time) -> (month: Month) { month :: proc "contextless" (t: Time) -> (month: Month) {
_, month, _, _ = _date(t, true) _, month, _, _ = _date(t, true)
return return
} }
/*
Obtain the day of the date specified by time.
*/
day :: proc "contextless" (t: Time) -> (day: int) { day :: proc "contextless" (t: Time) -> (day: int) {
_, _, day, _ = _date(t, true) _, _, day, _ = _date(t, true)
return return
} }
/*
Obtain the week day of the date specified by time.
*/
weekday :: proc "contextless" (t: Time) -> (weekday: Weekday) { weekday :: proc "contextless" (t: Time) -> (weekday: Weekday) {
abs := _time_abs(t) abs := _time_abs(t)
sec := (abs + u64(Weekday.Monday) * SECONDS_PER_DAY) % SECONDS_PER_WEEK sec := (abs + u64(Weekday.Monday) * SECONDS_PER_DAY) % SECONDS_PER_WEEK
return Weekday(int(sec) / SECONDS_PER_DAY) return Weekday(int(sec) / SECONDS_PER_DAY)
} }
/*
Obtain the time components from a time, a duration or a stopwatch's total.
*/
clock :: proc { clock_from_time, clock_from_duration, clock_from_stopwatch } clock :: proc { clock_from_time, clock_from_duration, clock_from_stopwatch }
/*
Obtain the time components from a time.
*/
clock_from_time :: proc "contextless" (t: Time) -> (hour, min, sec: int) { clock_from_time :: proc "contextless" (t: Time) -> (hour, min, sec: int) {
return clock_from_seconds(_time_abs(t)) return clock_from_seconds(_time_abs(t))
} }
/*
Obtain the time components from a duration.
*/
clock_from_duration :: proc "contextless" (d: Duration) -> (hour, min, sec: int) { clock_from_duration :: proc "contextless" (d: Duration) -> (hour, min, sec: int) {
return clock_from_seconds(u64(d/1e9)) return clock_from_seconds(u64(d/1e9))
} }
/*
Obtain the time components from a stopwatch's total.
*/
clock_from_stopwatch :: proc "contextless" (s: Stopwatch) -> (hour, min, sec: int) { clock_from_stopwatch :: proc "contextless" (s: Stopwatch) -> (hour, min, sec: int) {
return clock_from_duration(stopwatch_duration(s)) return clock_from_duration(stopwatch_duration(s))
} }
/*
Obtain the time components from the number of seconds.
*/
clock_from_seconds :: proc "contextless" (nsec: u64) -> (hour, min, sec: int) { clock_from_seconds :: proc "contextless" (nsec: u64) -> (hour, min, sec: int) {
sec = int(nsec % SECONDS_PER_DAY) sec = int(nsec % SECONDS_PER_DAY)
hour = sec / SECONDS_PER_HOUR hour = sec / SECONDS_PER_HOUR
@@ -202,10 +389,16 @@ clock_from_seconds :: proc "contextless" (nsec: u64) -> (hour, min, sec: int) {
return return
} }
/*
Read the timestamp counter of the CPU.
*/
read_cycle_counter :: proc "contextless" () -> u64 { read_cycle_counter :: proc "contextless" () -> u64 {
return u64(intrinsics.read_cycle_counter()) return u64(intrinsics.read_cycle_counter())
} }
/*
Obtain time from unix seconds and unix nanoseconds.
*/
unix :: proc "contextless" (sec: i64, nsec: i64) -> Time { unix :: proc "contextless" (sec: i64, nsec: i64) -> Time {
sec, nsec := sec, nsec sec, nsec := sec, nsec
if nsec < 0 || nsec >= 1e9 { if nsec < 0 || nsec >= 1e9 {
@@ -220,31 +413,59 @@ unix :: proc "contextless" (sec: i64, nsec: i64) -> Time {
return Time{(sec*1e9 + nsec)} return Time{(sec*1e9 + nsec)}
} }
/*
Obtain time from unix nanoseconds.
*/
from_nanoseconds :: #force_inline proc "contextless" (nsec: i64) -> Time { from_nanoseconds :: #force_inline proc "contextless" (nsec: i64) -> Time {
return Time{nsec} return Time{nsec}
} }
/*
Alias for `time_to_unix`.
*/
to_unix_seconds :: time_to_unix to_unix_seconds :: time_to_unix
/*
Obtain the Unix timestamp in seconds from a Time.
*/
time_to_unix :: proc "contextless" (t: Time) -> i64 { time_to_unix :: proc "contextless" (t: Time) -> i64 {
return t._nsec/1e9 return t._nsec/1e9
} }
/*
Alias for `time_to_unix_nano`.
*/
to_unix_nanoseconds :: time_to_unix_nano to_unix_nanoseconds :: time_to_unix_nano
/*
Obtain the Unix timestamp in nanoseconds from a Time.
*/
time_to_unix_nano :: proc "contextless" (t: Time) -> i64 { time_to_unix_nano :: proc "contextless" (t: Time) -> i64 {
return t._nsec return t._nsec
} }
/*
Add duration to a time.
*/
time_add :: proc "contextless" (t: Time, d: Duration) -> Time { time_add :: proc "contextless" (t: Time, d: Duration) -> Time {
return Time{t._nsec + i64(d)} return Time{t._nsec + i64(d)}
} }
// Accurate sleep borrowed from: https://blat-blatnik.github.io/computerBear/making-accurate-sleep-function/ /*
// Accurate sleep
// Accuracy seems to be pretty good out of the box on Linux, to within around 4µs worst case.
// On Windows it depends but is comparable with regular sleep in the worst case. This procedure sleeps for the duration specified by `d`, very accurately.
// To get the same kind of accuracy as on Linux, have your program call `windows.timeBeginPeriod(1)` to
// tell Windows to use a more accurate timer for your process. **Note**: Implementation borrowed from: [this source](https://blat-blatnik.github.io/computerBear/making-accurate-sleep-function/)
// Additionally your program should call `windows.timeEndPeriod(1)` once you're done with `accurate_sleep`.
**Note(linux)**: The accuracy is within around 4µs (microseconds), in the worst case.
**Note(windows)**: The accuracy depends but is comparable with regular sleep in
the worst case. To get the same kind of accuracy as on Linux, have your program
call `windows.timeBeginPeriod(1)` to tell Windows to use a more accurate timer
for your process. Additionally your program should call `windows.timeEndPeriod(1)`
once you're done with `accurate_sleep`.
*/
accurate_sleep :: proc "contextless" (d: Duration) { accurate_sleep :: proc "contextless" (d: Duration) {
to_sleep, estimate, mean, m2, count: Duration to_sleep, estimate, mean, m2, count: Duration
@@ -362,6 +583,13 @@ _abs_date :: proc "contextless" (abs: u64, full: bool) -> (year: int, month: Mon
return return
} }
/*
Convert datetime components into time.
This procedure calculates the time from datetime components supplied in the
arguments to this procedure. If the datetime components don't represent a valid
datetime, the function returns `false` in the second argument.
*/
components_to_time :: proc "contextless" (#any_int year, #any_int month, #any_int day, #any_int hour, #any_int minute, #any_int second: i64, #any_int nsec := i64(0)) -> (t: Time, ok: bool) { components_to_time :: proc "contextless" (#any_int year, #any_int month, #any_int day, #any_int hour, #any_int minute, #any_int second: i64, #any_int nsec := i64(0)) -> (t: Time, ok: bool) {
this_date, err := dt.components_to_datetime(year, month, day, hour, minute, second, nsec) this_date, err := dt.components_to_datetime(year, month, day, hour, minute, second, nsec)
if err != .None { if err != .None {
@@ -370,6 +598,12 @@ components_to_time :: proc "contextless" (#any_int year, #any_int month, #any_in
return compound_to_time(this_date) return compound_to_time(this_date)
} }
/*
Convert datetime into time.
If the datetime represents a time outside of a valid range, `false` is returned
as the second return value. See `Time` for the representable range.
*/
compound_to_time :: proc "contextless" (datetime: dt.DateTime) -> (t: Time, ok: bool) { compound_to_time :: proc "contextless" (datetime: dt.DateTime) -> (t: Time, ok: bool) {
unix_epoch := dt.DateTime{{1970, 1, 1}, {0, 0, 0, 0}} unix_epoch := dt.DateTime{{1970, 1, 1}, {0, 0, 0, 0}}
delta, err := dt.sub(datetime, unix_epoch) delta, err := dt.sub(datetime, unix_epoch)
@@ -387,12 +621,21 @@ compound_to_time :: proc "contextless" (datetime: dt.DateTime) -> (t: Time, ok:
return Time{_nsec=i64(nanoseconds)}, true return Time{_nsec=i64(nanoseconds)}, true
} }
/*
Convert datetime components into time.
*/
datetime_to_time :: proc{components_to_time, compound_to_time} datetime_to_time :: proc{components_to_time, compound_to_time}
/*
Check if a year is a leap year.
*/
is_leap_year :: proc "contextless" (year: int) -> (leap: bool) { is_leap_year :: proc "contextless" (year: int) -> (leap: bool) {
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
} }
/*
Days before each month in a year, not counting the leap day on february 29th.
*/
@(rodata) @(rodata)
days_before := [?]i32{ days_before := [?]i32{
0, 0,
@@ -410,11 +653,37 @@ days_before := [?]i32{
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31,
} }
/*
Number of seconds in a minute (without leap seconds).
*/
SECONDS_PER_MINUTE :: 60 SECONDS_PER_MINUTE :: 60
/*
Number of seconds in an hour (without leap seconds).
*/
SECONDS_PER_HOUR :: 60 * SECONDS_PER_MINUTE SECONDS_PER_HOUR :: 60 * SECONDS_PER_MINUTE
/*
Number of seconds in a day (without leap seconds).
*/
SECONDS_PER_DAY :: 24 * SECONDS_PER_HOUR SECONDS_PER_DAY :: 24 * SECONDS_PER_HOUR
/*
Number of seconds in a week (without leap seconds).
*/
SECONDS_PER_WEEK :: 7 * SECONDS_PER_DAY SECONDS_PER_WEEK :: 7 * SECONDS_PER_DAY
/*
Days in 400 years, with leap days.
*/
DAYS_PER_400_YEARS :: 365*400 + 97 DAYS_PER_400_YEARS :: 365*400 + 97
/*
Days in 100 years, with leap days.
*/
DAYS_PER_100_YEARS :: 365*100 + 24 DAYS_PER_100_YEARS :: 365*100 + 24
/*
Days in 4 years, with leap days.
*/
DAYS_PER_4_YEARS :: 365*4 + 1 DAYS_PER_4_YEARS :: 365*4 + 1
+9
View File
@@ -0,0 +1,9 @@
# Examples
The `example` directory contains two packages:
A [demo](examples/demo) illustrating the basics of Odin.
It further contains [all](examples/all), which imports all [core](core) and [vendor](vendor) packages so we can conveniently run `odin check` on everything at once.
For additional example code, see the [examples](https://github.com/odin-lang/examples) repository.
+2
View File
@@ -61,6 +61,7 @@ import cbor "core:encoding/cbor"
import csv "core:encoding/csv" import csv "core:encoding/csv"
import endian "core:encoding/endian" import endian "core:encoding/endian"
import hxa "core:encoding/hxa" import hxa "core:encoding/hxa"
import ini "core:encoding/ini"
import json "core:encoding/json" import json "core:encoding/json"
import varint "core:encoding/varint" import varint "core:encoding/varint"
import xml "core:encoding/xml" import xml "core:encoding/xml"
@@ -193,6 +194,7 @@ _ :: base32
_ :: base64 _ :: base64
_ :: csv _ :: csv
_ :: hxa _ :: hxa
_ :: ini
_ :: json _ :: json
_ :: varint _ :: varint
_ :: xml _ :: xml
+15
View File
@@ -440,6 +440,8 @@ struct BuildContext {
bool cached; bool cached;
BuildCacheData build_cache_data; BuildCacheData build_cache_data;
bool internal_no_inline;
bool no_threaded_checker; bool no_threaded_checker;
bool show_debug_messages; bool show_debug_messages;
@@ -1649,11 +1651,24 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta
if (!bc->custom_optimization_level) { if (!bc->custom_optimization_level) {
// NOTE(bill): when building with `-debug` but not specifying an optimization level // NOTE(bill): when building with `-debug` but not specifying an optimization level
// default to `-o:none` to improve the debug symbol generation by default // default to `-o:none` to improve the debug symbol generation by default
if (bc->ODIN_DEBUG) {
bc->optimization_level = -1; // -o:none bc->optimization_level = -1; // -o:none
} else {
bc->optimization_level = 0; // -o:minimal
}
} }
bc->optimization_level = gb_clamp(bc->optimization_level, -1, 3); bc->optimization_level = gb_clamp(bc->optimization_level, -1, 3);
#if defined(GB_SYSTEM_WINDOWS)
if (bc->optimization_level <= 0) {
if (!is_arch_wasm()) {
bc->use_separate_modules = true;
}
}
#endif
// TODO: Static map calls are bugged on `amd64sysv` abi. // TODO: Static map calls are bugged on `amd64sysv` abi.
if (bc->metrics.os != TargetOs_windows && bc->metrics.arch == TargetArch_amd64) { if (bc->metrics.os != TargetOs_windows && bc->metrics.arch == TargetArch_amd64) {
// ENFORCE DYNAMIC MAP CALLS // ENFORCE DYNAMIC MAP CALLS
+8 -8
View File
@@ -17,11 +17,11 @@ gb_internal bool recursively_delete_directory(wchar_t *wpath_c) {
wchar_t dir_path[MAX_PATH] = {}; wchar_t dir_path[MAX_PATH] = {};
wchar_t filename[MAX_PATH] = {}; wchar_t filename[MAX_PATH] = {};
wcscpy(dir_path, wpath_c); wcscpy_s(dir_path, wpath_c);
wcscat(dir_path, L"\\*"); wcscat_s(dir_path, L"\\*");
wcscpy(filename, wpath_c); wcscpy_s(filename, wpath_c);
wcscat(filename, L"\\"); wcscat_s(filename, L"\\");
WIN32_FIND_DATAW find_file_data = {}; WIN32_FIND_DATAW find_file_data = {};
@@ -31,21 +31,21 @@ gb_internal bool recursively_delete_directory(wchar_t *wpath_c) {
} }
defer (FindClose(hfind)); defer (FindClose(hfind));
wcscpy(dir_path, filename); wcscpy_s(dir_path, filename);
for (;;) { for (;;) {
if (FindNextFileW(hfind, &find_file_data)) { if (FindNextFileW(hfind, &find_file_data)) {
if (is_dots_w(find_file_data.cFileName)) { if (is_dots_w(find_file_data.cFileName)) {
continue; continue;
} }
wcscat(filename, find_file_data.cFileName); wcscat_s(filename, find_file_data.cFileName);
if (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if (!recursively_delete_directory(filename)) { if (!recursively_delete_directory(filename)) {
return false; return false;
} }
RemoveDirectoryW(filename); RemoveDirectoryW(filename);
wcscpy(filename, dir_path); wcscpy_s(filename, dir_path);
} else { } else {
if (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_READONLY) { if (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
_wchmod(filename, _S_IWRITE); _wchmod(filename, _S_IWRITE);
@@ -53,7 +53,7 @@ gb_internal bool recursively_delete_directory(wchar_t *wpath_c) {
if (!DeleteFileW(filename)) { if (!DeleteFileW(filename)) {
return false; return false;
} }
wcscpy(filename, dir_path); wcscpy_s(filename, dir_path);
} }
} else { } else {
if (GetLastError() == ERROR_NO_MORE_FILES) { if (GetLastError() == ERROR_NO_MORE_FILES) {
+50 -3
View File
@@ -1079,7 +1079,7 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan
return false; return false;
} }
gb_internal bool cache_load_file_directive(CheckerContext *c, Ast *call, String const &original_string, bool err_on_not_found, LoadFileCache **cache_, LoadFileTier tier) { gb_internal bool cache_load_file_directive(CheckerContext *c, Ast *call, String const &original_string, bool err_on_not_found, LoadFileCache **cache_, LoadFileTier tier, bool use_mutex=true) {
ast_node(ce, CallExpr, call); ast_node(ce, CallExpr, call);
ast_node(bd, BasicDirective, ce->proc); ast_node(bd, BasicDirective, ce->proc);
String builtin_name = bd->name.string; String builtin_name = bd->name.string;
@@ -1101,7 +1101,8 @@ gb_internal bool cache_load_file_directive(CheckerContext *c, Ast *call, String
} }
} }
MUTEX_GUARD(&c->info->load_file_mutex); if (use_mutex) mutex_lock(&c->info->load_file_mutex);
defer (if (use_mutex) mutex_unlock(&c->info->load_file_mutex));
gbFileError file_error = gbFileError_None; gbFileError file_error = gbFileError_None;
String data = {}; String data = {};
@@ -1414,9 +1415,12 @@ gb_internal LoadDirectiveResult check_load_directory_directive(CheckerContext *c
file_caches = array_make<LoadFileCache *>(heap_allocator(), 0, files_to_reserve); file_caches = array_make<LoadFileCache *>(heap_allocator(), 0, files_to_reserve);
mutex_lock(&c->info->load_file_mutex);
defer (mutex_unlock(&c->info->load_file_mutex));
for (FileInfo fi : list) { for (FileInfo fi : list) {
LoadFileCache *cache = nullptr; LoadFileCache *cache = nullptr;
if (cache_load_file_directive(c, call, fi.fullpath, err_on_not_found, &cache, LoadFileTier_Contents)) { if (cache_load_file_directive(c, call, fi.fullpath, err_on_not_found, &cache, LoadFileTier_Contents, /*use_mutex*/false)) {
array_add(&file_caches, cache); array_add(&file_caches, cache);
} else { } else {
result = LoadDirective_Error; result = LoadDirective_Error;
@@ -4298,6 +4302,49 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
} }
break; break;
case BuiltinProc_add_sat:
case BuiltinProc_sub_sat:
{
Operand x = {};
Operand y = {};
check_expr(c, &x, ce->args[0]);
check_expr(c, &y, ce->args[1]);
if (x.mode == Addressing_Invalid) {
return false;
}
if (y.mode == Addressing_Invalid) {
return false;
}
convert_to_typed(c, &y, x.type); if (y.mode == Addressing_Invalid) return false;
convert_to_typed(c, &x, y.type);
if (is_type_untyped(x.type)) {
gbString xts = type_to_string(x.type);
error(x.expr, "Expected a typed integer for '%.*s', got %s", LIT(builtin_name), xts);
gb_string_free(xts);
return false;
}
if (!is_type_integer(x.type)) {
gbString xts = type_to_string(x.type);
error(x.expr, "Expected an integer for '%.*s', got %s", LIT(builtin_name), xts);
gb_string_free(xts);
return false;
}
Type *ct = core_type(x.type);
if (is_type_different_to_arch_endianness(ct)) {
GB_ASSERT(ct->kind == Type_Basic);
if (ct->Basic.flags & (BasicFlag_EndianLittle|BasicFlag_EndianBig)) {
gbString xts = type_to_string(x.type);
error(x.expr, "Expected an integer which does not specify the explicit endianness for '%.*s', got %s", LIT(builtin_name), xts);
gb_string_free(xts);
return false;
}
}
operand->mode = Addressing_Value;
operand->type = default_type(x.type);
}
break;
case BuiltinProc_sqrt: case BuiltinProc_sqrt:
{ {
Operand x = {}; Operand x = {};
+10 -2
View File
@@ -182,8 +182,7 @@ gb_internal void override_entity_in_scope(Entity *original_entity, Entity *new_e
original_entity->type = new_entity->type; original_entity->type = new_entity->type;
original_entity->aliased_of = new_entity; original_entity->aliased_of = new_entity;
Ast *empty_ident = nullptr; original_entity->identifier.store(new_entity->identifier);
original_entity->identifier.compare_exchange_strong(empty_ident, new_entity->identifier);
if (original_entity->identifier.load() != nullptr && if (original_entity->identifier.load() != nullptr &&
original_entity->identifier.load()->kind == Ast_Ident) { original_entity->identifier.load()->kind == Ast_Ident) {
@@ -1869,5 +1868,14 @@ gb_internal bool check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de
add_deps_from_child_to_parent(decl); add_deps_from_child_to_parent(decl);
for (VariadicReuseData const &vr : decl->variadic_reuses) {
GB_ASSERT(vr.slice_type->kind == Type_Slice);
Type *elem = vr.slice_type->Slice.elem;
i64 size = type_size_of(elem);
i64 align = type_align_of(elem);
decl->variadic_reuse_max_bytes = gb_max(decl->variadic_reuse_max_bytes, size*vr.max_count);
decl->variadic_reuse_max_align = gb_max(decl->variadic_reuse_max_align, align);
}
return true; return true;
} }
+28 -3
View File
@@ -500,7 +500,9 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E
nctx.no_polymorphic_errors = false; nctx.no_polymorphic_errors = false;
// NOTE(bill): Reset scope from the failed procedure type // NOTE(bill): Reset scope from the failed procedure type
scope_reset(scope); scope->head_child.store(nullptr, std::memory_order_relaxed);
string_map_clear(&scope->elements);
ptr_set_clear(&scope->imported);
// LEAK NOTE(bill): Cloning this AST may be leaky but this is not really an issue due to arena-based allocation // LEAK NOTE(bill): Cloning this AST may be leaky but this is not really an issue due to arena-based allocation
Ast *cloned_proc_type_node = clone_ast(pt->node); Ast *cloned_proc_type_node = clone_ast(pt->node);
@@ -6033,6 +6035,22 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A
Entity *vt = pt->params->Tuple.variables[pt->variadic_index]; Entity *vt = pt->params->Tuple.variables[pt->variadic_index];
o.type = vt->type; o.type = vt->type;
// NOTE(bill, 2024-07-14): minimize the stack usage for variadic parameters with the backing array
if (c->decl) {
bool found = false;
for (auto &vr : c->decl->variadic_reuses) {
if (are_types_identical(vt->type, vr.slice_type)) {
vr.max_count = gb_max(vr.max_count, variadic_operands.count);
found = true;
break;
}
}
if (!found) {
array_add(&c->decl->variadic_reuses, VariadicReuseData{vt->type, variadic_operands.count});
}
}
} else { } else {
dummy_argument_count += 1; dummy_argument_count += 1;
o.type = t_untyped_nil; o.type = t_untyped_nil;
@@ -7888,7 +7906,9 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c
// NOTE: Due to restrictions in LLVM you can not inline calls with a superset of features. // NOTE: Due to restrictions in LLVM you can not inline calls with a superset of features.
if (is_call_inlined) { if (is_call_inlined) {
GB_ASSERT(c->curr_proc_decl); if (c->curr_proc_decl == nullptr) {
error(call, "Calling a '#force_inline' procedure that enables target features is not allowed at file scope");
} else {
GB_ASSERT(c->curr_proc_decl->entity); GB_ASSERT(c->curr_proc_decl->entity);
GB_ASSERT(c->curr_proc_decl->entity->type->kind == Type_Proc); GB_ASSERT(c->curr_proc_decl->entity->type->kind == Type_Proc);
String scope_features = c->curr_proc_decl->entity->type->Proc.enable_target_feature; String scope_features = c->curr_proc_decl->entity->type->Proc.enable_target_feature;
@@ -7898,6 +7918,7 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c
} }
} }
} }
}
operand->expr = call; operand->expr = call;
@@ -9926,10 +9947,14 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast *
} }
Type *et = base_type(t->BitSet.elem); Type *et = base_type(t->BitSet.elem);
isize field_count = 0; isize field_count = 0;
if (et->kind == Type_Enum) { if (et != nullptr && et->kind == Type_Enum) {
field_count = et->Enum.fields.count; field_count = et->Enum.fields.count;
} }
if (is_type_array(bit_set_to_int(t))) {
is_constant = false;
}
if (cl->elems[0]->kind == Ast_FieldValue) { if (cl->elems[0]->kind == Ast_FieldValue) {
error(cl->elems[0], "'field = value' in a bit_set a literal is not allowed"); error(cl->elems[0], "'field = value' in a bit_set a literal is not allowed");
is_constant = false; is_constant = false;
+13 -2
View File
@@ -1060,6 +1060,9 @@ gb_internal void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags
if (ss->tag != nullptr) { if (ss->tag != nullptr) {
check_expr(ctx, &x, ss->tag); check_expr(ctx, &x, ss->tag);
check_assignment(ctx, &x, nullptr, str_lit("switch expression")); check_assignment(ctx, &x, nullptr, str_lit("switch expression"));
if (x.type == nullptr) {
return;
}
} else { } else {
x.mode = Addressing_Constant; x.mode = Addressing_Constant;
x.type = t_bool; x.type = t_bool;
@@ -1834,7 +1837,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags)
if (rs->vals.count == 1) { if (rs->vals.count == 1) {
Type *t = type_deref(operand.type); Type *t = type_deref(operand.type);
if (is_type_map(t) || is_type_bit_set(t)) { if (t != NULL && (is_type_map(t) || is_type_bit_set(t))) {
gbString v = expr_to_string(rs->vals[0]); gbString v = expr_to_string(rs->vals[0]);
defer (gb_string_free(v)); defer (gb_string_free(v));
error_line("\tSuggestion: place parentheses around the expression\n"); error_line("\tSuggestion: place parentheses around the expression\n");
@@ -2514,7 +2517,7 @@ gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) {
Entity *e = entity_of_node(x); Entity *e = entity_of_node(x);
if (is_entity_local_variable(e)) { if (is_entity_local_variable(e)) {
unsafe_return_error(o, "the address of a local variable"); unsafe_return_error(o, "the address of a local variable");
} else if(x->kind == Ast_CompoundLit) { } else if (x->kind == Ast_CompoundLit) {
unsafe_return_error(o, "the address of a compound literal"); unsafe_return_error(o, "the address of a compound literal");
} else if (x->kind == Ast_IndexExpr) { } else if (x->kind == Ast_IndexExpr) {
Entity *f = entity_of_node(x->IndexExpr.expr); Entity *f = entity_of_node(x->IndexExpr.expr);
@@ -2529,6 +2532,14 @@ gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) {
unsafe_return_error(o, "the address of an indexed variable", f->type); unsafe_return_error(o, "the address of an indexed variable", f->type);
} }
} }
} else if (expr->kind == Ast_SliceExpr) {
Ast *x = unparen_expr(expr->SliceExpr.expr);
Entity *e = entity_of_node(x);
if (is_entity_local_variable(e) && is_type_array(e->type)) {
unsafe_return_error(o, "a slice of a local variable");
} else if (x->kind == Ast_CompoundLit) {
unsafe_return_error(o, "a slice of a compound literal");
}
} else if (o.mode == Addressing_Constant && is_type_slice(o.type)) { } else if (o.mode == Addressing_Constant && is_type_slice(o.type)) {
ERROR_BLOCK(); ERROR_BLOCK();
unsafe_return_error(o, "a compound literal of a slice"); unsafe_return_error(o, "a compound literal of a slice");
+67 -18
View File
@@ -939,22 +939,6 @@ gb_internal void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *nam
enum_type->Enum.max_value_index = max_value_index; enum_type->Enum.max_value_index = max_value_index;
} }
gb_internal bool is_valid_bit_field_backing_type(Type *type) {
if (type == nullptr) {
return false;
}
type = base_type(type);
if (is_type_untyped(type)) {
return false;
}
if (is_type_integer(type)) {
return true;
}
if (type->kind == Type_Array) {
return is_type_integer(type->Array.elem);
}
return false;
}
gb_internal void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type, Type *named_type, Ast *node) { gb_internal void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type, Type *named_type, Ast *node) {
ast_node(bf, BitFieldType, node); ast_node(bf, BitFieldType, node);
@@ -1268,12 +1252,15 @@ gb_internal void check_bit_set_type(CheckerContext *c, Type *type, Type *named_t
Type *t = default_type(lhs.type); Type *t = default_type(lhs.type);
if (bs->underlying != nullptr) { if (bs->underlying != nullptr) {
Type *u = check_type(c, bs->underlying); Type *u = check_type(c, bs->underlying);
// if (!is_valid_bit_field_backing_type(u)) {
if (!is_type_integer(u)) { if (!is_type_integer(u)) {
gbString ts = type_to_string(u); gbString ts = type_to_string(u);
error(bs->underlying, "Expected an underlying integer for the bit set, got %s", ts); error(bs->underlying, "Expected an underlying integer for the bit set, got %s", ts);
gb_string_free(ts); gb_string_free(ts);
if (!is_valid_bit_field_backing_type(u)) {
return; return;
} }
}
type->BitSet.underlying = u; type->BitSet.underlying = u;
} }
@@ -1572,11 +1559,30 @@ gb_internal Type *determine_type_from_polymorphic(CheckerContext *ctx, Type *pol
return poly_type; return poly_type;
} }
if (show_error) { if (show_error) {
ERROR_BLOCK();
gbString pts = type_to_string(poly_type); gbString pts = type_to_string(poly_type);
gbString ots = type_to_string(operand.type, true); gbString ots = type_to_string(operand.type, true);
defer (gb_string_free(pts)); defer (gb_string_free(pts));
defer (gb_string_free(ots)); defer (gb_string_free(ots));
error(operand.expr, "Cannot determine polymorphic type from parameter: '%s' to '%s'", ots, pts); error(operand.expr, "Cannot determine polymorphic type from parameter: '%s' to '%s'", ots, pts);
Type *pt = poly_type;
while (pt && pt->kind == Type_Generic && pt->Generic.specialized) {
pt = pt->Generic.specialized;
}
if (is_type_slice(pt) &&
(is_type_dynamic_array(operand.type) || is_type_array(operand.type))) {
Ast *expr = unparen_expr(operand.expr);
if (expr->kind == Ast_CompoundLit) {
gbString es = type_to_string(base_any_array_type(operand.type));
error_line("\tSuggestion: Try using a slice compound literal instead '[]%s{...}'\n", es);
gb_string_free(es);
} else {
gbString os = expr_to_string(operand.expr);
error_line("\tSuggestion: Try slicing the value with '%s[:]'\n", os);
gb_string_free(os);
}
}
} }
return t_invalid; return t_invalid;
} }
@@ -1953,6 +1959,10 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para
error(name, "'#by_ptr' can only be applied to variable fields"); error(name, "'#by_ptr' can only be applied to variable fields");
p->flags &= ~FieldFlag_by_ptr; p->flags &= ~FieldFlag_by_ptr;
} }
if (p->flags&FieldFlag_no_capture) {
error(name, "'#no_capture' can only be applied to variable fields");
p->flags &= ~FieldFlag_no_capture;
}
param = alloc_entity_type_name(scope, name->Ident.token, type, EntityState_Resolved); param = alloc_entity_type_name(scope, name->Ident.token, type, EntityState_Resolved);
param->TypeName.is_type_alias = true; param->TypeName.is_type_alias = true;
@@ -2054,6 +2064,28 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para
p->flags &= ~FieldFlag_by_ptr; // Remove the flag p->flags &= ~FieldFlag_by_ptr; // Remove the flag
} }
} }
if (p->flags&FieldFlag_no_capture) {
if (is_variadic && variadic_index == variables.count) {
if (p->flags & FieldFlag_c_vararg) {
error(name, "'#no_capture' cannot be applied to a #c_vararg parameter");
p->flags &= ~FieldFlag_no_capture;
} else {
error(name, "'#no_capture' is already implied on all variadic parameter");
}
} else if (is_type_polymorphic(type)) {
// ignore
} else {
if (is_type_internally_pointer_like(type)) {
error(name, "'#no_capture' is currently reserved for future use");
} else {
ERROR_BLOCK();
error(name, "'#no_capture' can only be applied to pointer-like types");
error_line("\t'#no_capture' does not currently do anything useful\n");
p->flags &= ~FieldFlag_no_capture;
}
}
}
if (is_poly_name) { if (is_poly_name) {
if (p->flags&FieldFlag_no_alias) { if (p->flags&FieldFlag_no_alias) {
@@ -2072,6 +2104,11 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para
error(name, "'#by_ptr' can only be applied to variable fields"); error(name, "'#by_ptr' can only be applied to variable fields");
p->flags &= ~FieldFlag_by_ptr; p->flags &= ~FieldFlag_by_ptr;
} }
if (p->flags&FieldFlag_no_capture) {
error(name, "'#no_capture' can only be applied to variable fields");
p->flags &= ~FieldFlag_no_capture;
}
if (!is_type_polymorphic(type) && check_constant_parameter_value(type, params[i])) { if (!is_type_polymorphic(type) && check_constant_parameter_value(type, params[i])) {
// failed // failed
@@ -2091,6 +2128,8 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para
param->flags |= EntityFlag_Ellipsis; param->flags |= EntityFlag_Ellipsis;
if (is_c_vararg) { if (is_c_vararg) {
param->flags |= EntityFlag_CVarArg; param->flags |= EntityFlag_CVarArg;
} else {
param->flags |= EntityFlag_NoCapture;
} }
} }
@@ -2115,6 +2154,10 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para
if (p->flags&FieldFlag_by_ptr) { if (p->flags&FieldFlag_by_ptr) {
param->flags |= EntityFlag_ByPtr; param->flags |= EntityFlag_ByPtr;
} }
if (p->flags&FieldFlag_no_capture) {
param->flags |= EntityFlag_NoCapture;
}
param->state = EntityState_Resolved; // NOTE(bill): This should have be resolved whilst determining it param->state = EntityState_Resolved; // NOTE(bill): This should have be resolved whilst determining it
add_entity(ctx, scope, name, param); add_entity(ctx, scope, name, param);
@@ -2430,10 +2473,16 @@ gb_internal i64 check_array_count(CheckerContext *ctx, Operand *o, Ast *e) {
if (e == nullptr) { if (e == nullptr) {
return 0; return 0;
} }
if (e->kind == Ast_UnaryExpr && if (e->kind == Ast_UnaryExpr) {
e->UnaryExpr.op.kind == Token_Question) { Token op = e->UnaryExpr.op;
if (op.kind == Token_Question) {
return -1; return -1;
} }
if (e->UnaryExpr.expr == nullptr) {
error(op, "Invalid array count '[%.*s]'", LIT(op.string));
return 0;
}
}
check_expr_or_type(ctx, o, e); check_expr_or_type(ctx, o, e);
if (o->mode == Addressing_Type) { if (o->mode == Addressing_Type) {
+22 -17
View File
@@ -50,15 +50,6 @@ gb_internal bool check_rtti_type_disallowed(Ast *expr, Type *type, char const *f
return check_rtti_type_disallowed(ast_token(expr), type, format); return check_rtti_type_disallowed(ast_token(expr), type, format);
} }
gb_internal void scope_reset(Scope *scope) {
if (scope == nullptr) return;
rw_mutex_lock(&scope->mutex);
scope->head_child.store(nullptr, std::memory_order_relaxed);
string_map_clear(&scope->elements);
ptr_set_clear(&scope->imported);
rw_mutex_unlock(&scope->mutex);
}
gb_internal void scope_reserve(Scope *scope, isize count) { gb_internal void scope_reserve(Scope *scope, isize count) {
string_map_reserve(&scope->elements, 2*count); string_map_reserve(&scope->elements, 2*count);
@@ -168,9 +159,6 @@ gb_internal void import_graph_node_swap(ImportGraphNode **data, isize i, isize j
} }
gb_internal void init_decl_info(DeclInfo *d, Scope *scope, DeclInfo *parent) { gb_internal void init_decl_info(DeclInfo *d, Scope *scope, DeclInfo *parent) {
gb_zero_item(d); gb_zero_item(d);
if (parent) { if (parent) {
@@ -184,6 +172,9 @@ gb_internal void init_decl_info(DeclInfo *d, Scope *scope, DeclInfo *parent) {
ptr_set_init(&d->deps, 0); ptr_set_init(&d->deps, 0);
ptr_set_init(&d->type_info_deps, 0); ptr_set_init(&d->type_info_deps, 0);
d->labels.allocator = heap_allocator(); d->labels.allocator = heap_allocator();
d->variadic_reuses.allocator = heap_allocator();
d->variadic_reuse_max_bytes = 0;
d->variadic_reuse_max_align = 1;
} }
gb_internal DeclInfo *make_decl_info(Scope *scope, DeclInfo *parent) { gb_internal DeclInfo *make_decl_info(Scope *scope, DeclInfo *parent) {
@@ -381,6 +372,7 @@ gb_internal Entity *scope_lookup_current(Scope *s, String const &name) {
return nullptr; return nullptr;
} }
gb_internal void scope_lookup_parent(Scope *scope, String const &name, Scope **scope_, Entity **entity_) { gb_internal void scope_lookup_parent(Scope *scope, String const &name, Scope **scope_, Entity **entity_) {
if (scope != nullptr) { if (scope != nullptr) {
bool gone_thru_proc = false; bool gone_thru_proc = false;
@@ -508,9 +500,15 @@ end:;
return result; return result;
} }
gb_global bool in_single_threaded_checker_stage = false;
gb_internal Entity *scope_insert(Scope *s, Entity *entity) { gb_internal Entity *scope_insert(Scope *s, Entity *entity) {
String name = entity->token.string; String name = entity->token.string;
if (in_single_threaded_checker_stage) {
return scope_insert_with_name_no_mutex(s, name, entity);
} else {
return scope_insert_with_name(s, name, entity); return scope_insert_with_name(s, name, entity);
}
} }
gb_internal Entity *scope_insert_no_mutex(Scope *s, Entity *entity) { gb_internal Entity *scope_insert_no_mutex(Scope *s, Entity *entity) {
@@ -655,7 +653,7 @@ gb_internal bool check_vet_shadowing(Checker *c, Entity *e, VettedEntity *ve) {
} }
} }
zero_item(ve); gb_zero_item(ve);
ve->kind = VettedEntity_Shadowed; ve->kind = VettedEntity_Shadowed;
ve->entity = e; ve->entity = e;
ve->other = shadowed; ve->other = shadowed;
@@ -674,7 +672,7 @@ gb_internal bool check_vet_unused(Checker *c, Entity *e, VettedEntity *ve) {
} }
case Entity_ImportName: case Entity_ImportName:
case Entity_LibraryName: case Entity_LibraryName:
zero_item(ve); gb_zero_item(ve);
ve->kind = VettedEntity_Unused; ve->kind = VettedEntity_Unused;
ve->entity = e; ve->entity = e;
return true; return true;
@@ -1114,7 +1112,11 @@ gb_internal void init_universal(void) {
int minimum_os_version = 0; int minimum_os_version = 0;
if (build_context.minimum_os_version_string != "") { if (build_context.minimum_os_version_string != "") {
int major, minor, revision = 0; int major, minor, revision = 0;
#if defined(GB_SYSTEM_WINDOWS)
sscanf_s(cast(const char *)(build_context.minimum_os_version_string.text), "%d.%d.%d", &major, &minor, &revision);
#else
sscanf(cast(const char *)(build_context.minimum_os_version_string.text), "%d.%d.%d", &major, &minor, &revision); sscanf(cast(const char *)(build_context.minimum_os_version_string.text), "%d.%d.%d", &major, &minor, &revision);
#endif
minimum_os_version = (major*10000)+(minor*100)+revision; minimum_os_version = (major*10000)+(minor*100)+revision;
} }
add_global_constant("ODIN_MINIMUM_OS_VERSION", t_untyped_integer, exact_value_i64(minimum_os_version)); add_global_constant("ODIN_MINIMUM_OS_VERSION", t_untyped_integer, exact_value_i64(minimum_os_version));
@@ -1386,7 +1388,7 @@ gb_internal void reset_checker_context(CheckerContext *ctx, AstFile *file, Untyp
auto type_path = ctx->type_path; auto type_path = ctx->type_path;
array_clear(type_path); array_clear(type_path);
zero_size(&ctx->pkg, gb_size_of(CheckerContext) - gb_offset_of(CheckerContext, pkg)); gb_zero_size(&ctx->pkg, gb_size_of(CheckerContext) - gb_offset_of(CheckerContext, pkg));
ctx->file = nullptr; ctx->file = nullptr;
ctx->scope = builtin_pkg->scope; ctx->scope = builtin_pkg->scope;
@@ -1788,8 +1790,7 @@ gb_internal void add_entity_use(CheckerContext *c, Ast *identifier, Entity *enti
if (identifier == nullptr || identifier->kind != Ast_Ident) { if (identifier == nullptr || identifier->kind != Ast_Ident) {
return; return;
} }
Ast *empty_ident = nullptr; entity->identifier.store(identifier);
entity->identifier.compare_exchange_strong(empty_ident, identifier);
identifier->Ident.entity = entity; identifier->Ident.entity = entity;
@@ -4584,6 +4585,8 @@ gb_internal void check_single_global_entity(Checker *c, Entity *e, DeclInfo *d)
} }
gb_internal void check_all_global_entities(Checker *c) { gb_internal void check_all_global_entities(Checker *c) {
in_single_threaded_checker_stage = true;
// NOTE(bill): This must be single threaded // NOTE(bill): This must be single threaded
// Don't bother trying // Don't bother trying
for_array(i, c->info.entities) { for_array(i, c->info.entities) {
@@ -4603,6 +4606,8 @@ gb_internal void check_all_global_entities(Checker *c) {
(void)type_align_of(e->type); (void)type_align_of(e->type);
} }
} }
in_single_threaded_checker_stage = false;
} }
+9
View File
@@ -181,6 +181,11 @@ char const *ProcCheckedState_strings[ProcCheckedState_COUNT] {
"Checked", "Checked",
}; };
struct VariadicReuseData {
Type *slice_type; // ..elem_type
i64 max_count;
};
// DeclInfo is used to store information of certain declarations to allow for "any order" usage // DeclInfo is used to store information of certain declarations to allow for "any order" usage
struct DeclInfo { struct DeclInfo {
DeclInfo * parent; // NOTE(bill): only used for procedure literals at the moment DeclInfo * parent; // NOTE(bill): only used for procedure literals at the moment
@@ -219,6 +224,10 @@ struct DeclInfo {
Array<BlockLabel> labels; Array<BlockLabel> labels;
Array<VariadicReuseData> variadic_reuses;
i64 variadic_reuse_max_bytes;
i64 variadic_reuse_max_align;
// NOTE(bill): this is to prevent a race condition since these procedure literals can be created anywhere at any time // NOTE(bill): this is to prevent a race condition since these procedure literals can be created anywhere at any time
struct lbModule *code_gen_module; struct lbModule *code_gen_module;
}; };

Some files were not shown because too many files have changed in this diff Show More