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 9123 additions and 1427 deletions
+5 -7
View File
@@ -6,7 +6,7 @@ jobs:
name: NetBSD Build, Check, and Test
runs-on: ubuntu-latest
env:
PKGSRC_BRANCH: 2024Q1
PKGSRC_BRANCH: 2024Q2
steps:
- uses: actions/checkout@v4
- name: Build, Check, and Test
@@ -19,10 +19,7 @@ jobs:
copyback: false
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
pkgin -y in gmake git bash python311
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
pkgin -y in gmake git bash python311 llvm clang
ln -s /usr/pkg/bin/python3.11 /usr/bin/python3
run: |
git config --global --add safe.directory $(pwd)
@@ -91,13 +88,13 @@ jobs:
- name: Download LLVM (MacOS Intel)
if: matrix.os == 'macos-13'
run: |
brew install llvm@17
brew install llvm@17 lua@5.4
echo "/usr/local/opt/llvm@17/bin" >> $GITHUB_PATH
- name: Download LLVM (MacOS ARM)
if: matrix.os == 'macos-14'
run: |
brew install llvm@17 wasmtime
brew install llvm@17 wasmtime lua@5.4
echo "/opt/homebrew/opt/llvm@17/bin" >> $GITHUB_PATH
- name: Build Odin
@@ -207,6 +204,7 @@ jobs:
shell: cmd
run: |
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
- name: Odin internals tests
shell: cmd
+1 -32
View File
@@ -24,38 +24,6 @@ bld/
![Cc]ore/[Ll]og/
tests/documentation/verify/
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
.vs/
# 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
#wwwroot/
demo
benchmark
# MSTest test Results
[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) ---
byte_swap :: proc(x: $T) -> T where type_is_integer(T) || type_is_float(T) ---
overflow_add :: proc(lhs, rhs: $T) -> (T, bool) ---
overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) ---
overflow_mul :: 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) where type_is_integer(T) #optional_ok ---
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))) ---
+31 -21
View File
@@ -66,7 +66,7 @@ Type_Info_Named :: struct {
name: string,
base: ^Type_Info,
pkg: string,
loc: Source_Code_Location,
loc: ^Source_Code_Location,
}
Type_Info_Integer :: struct {signed: bool, endianness: Platform_Endianness}
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_Struct :: struct {
types: []^Type_Info,
names: []string,
offsets: []uintptr,
usings: []bool,
tags: []string,
is_packed: bool,
is_raw_union: bool,
is_no_copy: bool,
custom_align: bool,
Type_Info_Struct_Flags :: distinct bit_set[Type_Info_Struct_Flag; u8]
Type_Info_Struct_Flag :: enum u8 {
packed = 0,
raw_union = 1,
no_copy = 2,
align = 3,
}
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
soa_kind: Type_Info_Struct_Soa_Kind,
soa_len: i32,
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 {
variants: []^Type_Info,
@@ -142,9 +151,9 @@ Type_Info_Union :: struct {
shared_nil: bool,
}
Type_Info_Enum :: struct {
base: ^Type_Info,
names: []string,
values: []Type_Info_Enum_Value,
base: ^Type_Info,
names: []string,
values: []Type_Info_Enum_Value,
}
Type_Info_Map :: struct {
key: ^Type_Info,
@@ -187,11 +196,12 @@ Type_Info_Soa_Pointer :: struct {
}
Type_Info_Bit_Field :: struct {
backing_type: ^Type_Info,
names: []string,
types: []^Type_Info,
bit_sizes: []uintptr,
bit_offsets: []uintptr,
tags: []string,
names: [^]string `fmt:"v,field_count"`,
types: [^]^Type_Info `fmt:"v,field_count"`,
bit_sizes: [^]uintptr `fmt:"v,field_count"`,
bit_offsets: [^]uintptr `fmt:"v,field_count"`,
tags: [^]string `fmt:"v,field_count"`,
field_count: int,
}
Type_Info_Flag :: enum u8 {
+106 -101
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`.
@(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_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(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
err = _make_dynamic_array_len_cap((^Raw_Dynamic_Array)(&array), size_of(E), align_of(E), len, cap, allocator, loc)
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.
// 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
}
_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 {
return 0, nil
return
}
when size_of(E) == 0 {
array := (^Raw_Dynamic_Array)(array)
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.
cap := 2 * cap(array) + DEFAULT_DYNAMIC_ARRAY_CAPACITY
// do not 'or_return' here as it could be a partial success
if should_zero {
err = reserve(array, cap, loc)
} else {
err = non_zero_reserve(array, cap, loc)
}
}
if cap(array)-len(array) > 0 {
a := (^Raw_Dynamic_Array)(array)
when size_of(E) != 0 {
data := ([^]E)(a.data)
assert(data != nil, loc=loc)
data[a.len] = arg
}
a.len += 1
return 1, err
}
return 0, err
if array.cap < array.len+1 {
// Same behavior as _append_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY.
cap := 2 * array.cap + DEFAULT_DYNAMIC_ARRAY_CAPACITY
// do not 'or_return' here as it could be a partial success
err = _reserve_dynamic_array(array, size_of_elem, align_of_elem, cap, should_zero, loc)
}
if array.cap-array.len > 0 {
data := ([^]byte)(array.data)
assert(data != nil, loc=loc)
data = data[array.len*size_of_elem:]
intrinsics.mem_copy_non_overlapping(data, arg_ptr, size_of_elem)
array.len += 1
n = 1
}
return
}
@builtin
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
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 {
return 0, nil
}
arg_len := len(args)
if arg_len <= 0 {
return 0, nil
}
when size_of(E) == 0 {
array := (^Raw_Dynamic_Array)(array)
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)
if array.cap < array.len+arg_len {
cap := 2 * array.cap + max(DEFAULT_DYNAMIC_ARRAY_CAPACITY, arg_len)
// do not 'or_return' here as it could be a partial success
if should_zero {
err = reserve(array, cap, loc)
} else {
err = non_zero_reserve(array, cap, loc)
}
}
arg_len = min(cap(array)-len(array), arg_len)
if arg_len > 0 {
a := (^Raw_Dynamic_Array)(array)
when size_of(E) != 0 {
data := ([^]E)(a.data)
assert(data != nil, loc=loc)
intrinsics.mem_copy(&data[a.len], raw_data(args), size_of(E) * arg_len)
}
a.len += arg_len
}
return arg_len, err
// do not 'or_return' here as it could be a partial success
err = _reserve_dynamic_array(array, size_of_elem, align_of_elem, cap, should_zero, loc)
}
arg_len := arg_len
arg_len = min(array.cap-array.len, arg_len)
if arg_len > 0 {
data := ([^]byte)(array.data)
assert(data != nil, loc=loc)
data = data[array.len*size_of_elem:]
intrinsics.mem_copy(data, args, size_of_elem * arg_len) // must be mem_copy (overlapping)
array.len += arg_len
}
return arg_len, err
}
@builtin
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
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
_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
if should_zero {
return append_elems(array, ..args, loc=loc)
} else {
return non_zero_append_elems(array, ..args, loc=loc)
}
return _append_elems((^Raw_Dynamic_Array)(array), 1, 1, should_zero, loc, raw_data(arg), len(arg))
}
@builtin
@@ -679,7 +682,7 @@ assign_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #calle
@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)
if len(args) == 0 {
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`).
//
// 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 {
if array == nil {
_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 a == nil {
return nil
}
a := (^Raw_Dynamic_Array)(array)
if capacity <= a.cap {
return nil
@@ -744,15 +746,15 @@ _reserve_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, capacity: i
}
assert(a.allocator.procedure != nil)
old_size := a.cap * size_of(E)
new_size := capacity * size_of(E)
old_size := a.cap * size_of_elem
new_size := capacity * size_of_elem
allocator := a.allocator
new_data: []byte
if should_zero {
new_data = mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return
new_data = mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return
} else {
new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of(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 {
return .Out_Of_Memory
@@ -765,26 +767,23 @@ _reserve_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, capacity: i
@builtin
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
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`).
//
// Note: Prefer the procedure group `resize`
_resize_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, length: int, should_zero: bool, loc := #caller_location) -> Allocator_Error {
if array == nil {
_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 {
if a == nil {
return nil
}
a := (^Raw_Dynamic_Array)(array)
if length <= a.cap {
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)
return nil
@@ -795,15 +794,15 @@ _resize_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, length: int,
}
assert(a.allocator.procedure != nil)
old_size := a.cap * size_of(E)
new_size := length * size_of(E)
old_size := a.cap * size_of_elem
new_size := length * size_of_elem
allocator := a.allocator
new_data : []byte
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 {
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 {
return .Out_Of_Memory
@@ -815,14 +814,17 @@ _resize_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, length: int,
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
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
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`
*/
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
}
a := (^Raw_Dynamic_Array)(array)
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)
old_size := a.cap * size_of(E)
new_size := new_cap * size_of(E)
old_size := a.cap * size_of_elem
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.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 {
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)
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)
ceil_log2 :: #force_inline proc "contextless" (x: uintptr) -> uintptr {
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)
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 {
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)
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))
size := int(map_total_allocation_size(uintptr(map_cap(m)), info))
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)
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 {
return 0, false
}
@@ -723,7 +723,7 @@ map_lookup_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info,
}
}
@(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 {
return false
}
@@ -749,7 +749,7 @@ map_exists_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info,
@(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
ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info)
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 ")
if info.is_packed { print_string("#packed ") }
if info.is_raw_union { print_string("#raw_union ") }
if info.custom_align {
if .packed in info.flags { print_string("#packed ") }
if .raw_union in info.flags { print_string("#raw_union ") }
if .no_copy in info.flags { print_string("#no_copy ") }
if .align in info.flags {
print_string("#align(")
print_u64(u64(ti.align))
print_string(") ")
}
print_byte('{')
for name, i in info.names {
for name, i in info.names[:info.field_count] {
if i > 0 { print_string(", ") }
print_string(name)
print_string(": ")
@@ -469,7 +470,7 @@ print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) {
print_string("bit_field ")
print_type(info.backing_type)
print_string(" {")
for name, i in info.names {
for name, i in info.names[:info.field_count] {
if i > 0 { print_string(", ") }
print_string(name)
print_string(": ")
+2 -1
View File
@@ -297,7 +297,8 @@ lock :: proc(a: ^WASM_Allocator) {
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()
}
}
+25
View File
@@ -1167,3 +1167,28 @@ fields_proc :: proc(s: []byte, f: proc(rune) -> bool, allocator := context.alloc
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)
}
+2 -2
View File
@@ -235,7 +235,7 @@ allocate_huffman_table :: proc(allocator := context.allocator) -> (z: ^Huffman_T
}
@(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
next_code: [HUFFMAN_MAX_BITS+1]int
@@ -670,4 +670,4 @@ inflate_from_byte_array_raw :: proc(input: []u8, buf: ^bytes.Buffer, raw := fals
return inflate_raw(&ctx, expected_output_size=expected_output_size)
}
inflate :: proc{inflate_from_context, inflate_from_byte_array}
inflate :: proc{inflate_from_context, inflate_from_byte_array}
+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,
}
// The list link you must include in your own structure.
Node :: struct {
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) {
if list.head != nil {
list.head.prev = node
@@ -33,7 +40,13 @@ push_front :: proc "contextless" (list: ^List, node: ^Node) {
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) {
if list.tail != nil {
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) {
if node != 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) {
for node := list.head; node != nil; {
next := node.next
@@ -82,7 +108,13 @@ remove_by_proc :: proc(list: ^List, to_erase: proc(^Node) -> bool) {
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) {
for node := list.head; node != nil; {
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 {
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 {
link := list.head
if link == nil {
@@ -130,6 +176,14 @@ pop_front :: proc "contextless" (list: ^List) -> ^Node {
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 {
link := list.tail
if link == nil {
@@ -151,29 +205,102 @@ pop_back :: proc "contextless" (list: ^List) -> ^Node {
}
Iterator :: struct($T: typeid) {
curr: ^Node,
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)
where intrinsics.type_has_field(T, field_name),
intrinsics.type_field_type(T, field_name) == Node {
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)
where intrinsics.type_has_field(T, field_name),
intrinsics.type_field_type(T, field_name) == Node {
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)
where intrinsics.type_has_field(T, field_name),
intrinsics.type_field_type(T, field_name) == Node {
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) {
node := it.curr
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
}
/*
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) {
node := it.curr
if node == nil {
@@ -192,4 +359,4 @@ iterate_prev :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) {
it.curr = node.prev
return (^T)(uintptr(node) - it.offset), true
}
}
+2 -2
View File
@@ -95,11 +95,11 @@ front_ptr :: 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]
}
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]
}
+2 -12
View File
@@ -7,9 +7,8 @@ STRIDE :: 4
// Context is a keyed AES (ECB) instance.
Context :: struct {
_sk_exp: [120]u64,
_num_rounds: int,
_is_initialized: bool,
_sk_exp: [120]u64,
_num_rounds: int,
}
// 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)
skey_expand(ctx._sk_exp[:], skey[:], ctx._num_rounds)
ctx._is_initialized = true
}
// encrypt_block sets `dst` to `AES-ECB-Encrypt(src)`.
encrypt_block :: proc(ctx: ^Context, dst, src: []byte) {
assert(ctx._is_initialized)
q: [8]u64
load_blockx1(&q, src)
_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)`.
decrypt_block :: proc(ctx: ^Context, dst, src: []byte) {
assert(ctx._is_initialized)
q: [8]u64
load_blockx1(&q, src)
_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 :: proc(ctx: ^Context, dst, src: [][]byte) {
assert(ctx._is_initialized)
q: [8]u64 = ---
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 :: proc(ctx: ^Context, dst, src: [][]byte) {
assert(ctx._is_initialized)
q: [8]u64 = ---
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-38d.pdf
*/
package aes
import "core:crypto/_aes"
+17 -15
View File
@@ -1,5 +1,6 @@
package aes
import "core:bytes"
import "core:crypto/_aes/ct64"
import "core:encoding/endian"
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) {
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
if dst_len := len(dst); dst_len < len(src) {
src = src[:dst_len]
}
if bytes.alias_inexactly(dst, src) {
panic("crypto/aes: dst and src alias inexactly")
}
for remaining := len(src); remaining > 0; {
// Process multiple blocks at once
if ctx._off == BLOCK_SIZE {
@@ -123,8 +125,8 @@ reset_ctr :: proc "contextless" (ctx: ^Context_CTR) {
ctx._is_initialized = false
}
@(private)
ctr_blocks :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) {
@(private = "file")
ctr_blocks :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) #no_bounds_check {
// Use the optimized hardware implementation if available.
if _, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw {
ctr_blocks_hw(ctx, dst, src, nr_blocks)
@@ -183,17 +185,17 @@ xor_blocks :: #force_inline proc "contextless" (dst, src: []byte, blocks: [][]by
// performance of this implementation matters to where that
// optimization would be worth it, use chacha20poly1305, or a
// CPU that isn't e-waste.
if src != nil {
#no_bounds_check {
for i in 0 ..< len(blocks) {
off := i * BLOCK_SIZE
for j in 0 ..< BLOCK_SIZE {
blocks[i][j] ~= src[off + j]
#no_bounds_check {
if src != nil {
for i in 0 ..< len(blocks) {
off := i * BLOCK_SIZE
for j in 0 ..< BLOCK_SIZE {
blocks[i][j] ~= src[off + j]
}
}
}
}
for i in 0 ..< len(blocks) {
copy(dst[i * BLOCK_SIZE:], blocks[i])
}
}
for i in 0 ..< len(blocks) {
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
import "core:bytes"
import "core:crypto"
import "core:crypto/_aes"
import "core:crypto/_aes/ct64"
import "core:encoding/endian"
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_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 :: _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) {
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 {
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
j0: [_aes.GHASH_BLOCK_SIZE]byte
j0_enc: [_aes.GHASH_BLOCK_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.
ct64.ghash(s[:], h[:], aad)
gctr_ct64(ctx, dst, &s, plaintext, &h, nonce, true)
final_ghash_ct64(&s, &h, &j0, len(aad), len(plaintext))
gctr_ct64(ctx, dst, &s, plaintext, &h, &j0, true)
final_ghash_ct64(&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))
}
// 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) {
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 {
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
j0: [_aes.GHASH_BLOCK_SIZE]byte
j0_enc: [_aes.GHASH_BLOCK_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)
gctr_ct64(ctx, dst, &s, ciphertext, &h, nonce, false)
final_ghash_ct64(&s, &h, &j0, len(aad), len(ciphertext))
gctr_ct64(ctx, dst, &s, ciphertext, &h, &j0, false)
final_ghash_ct64(&s, &h, &j0_enc, len(aad), len(ciphertext))
ok := crypto.compare_constant_time(s[:], tag) == 1
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(&j0, len(j0))
mem.zero_explicit(&j0_enc, len(j0_enc))
mem.zero_explicit(&s, len(s))
return ok
@@ -106,19 +119,14 @@ reset_gcm :: proc "contextless" (ctx: ^Context_GCM) {
ctx._is_initialized = false
}
@(private)
@(private = "file")
gcm_validate_common_slice_sizes :: proc(tag, nonce, aad, text: []byte) {
if len(tag) != GCM_TAG_SIZE {
panic("crypto/aes: invalid GCM tag size")
}
// The specification supports nonces in the range [1, 2^64) bits
// however per NIST SP 800-38D 5.2.1.1:
//
// > 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 {
// The specification supports nonces in the range [1, 2^64) bits.
if l := len(nonce); l == 0 || u64(l) >= GCM_NONCE_SIZE_MAX {
panic("crypto/aes: invalid GCM nonce size")
}
@@ -135,6 +143,7 @@ init_ghash_ct64 :: proc(
ctx: ^Context_GCM,
h: ^[_aes.GHASH_KEY_SIZE]byte,
j0: ^[_aes.GHASH_BLOCK_SIZE]byte,
j0_enc: ^[_aes.GHASH_BLOCK_SIZE]byte,
nonce: []byte,
) {
impl := &ctx._impl.(ct64.Context)
@@ -142,12 +151,25 @@ init_ghash_ct64 :: proc(
// 1. Let H = CIPH(k, 0^128)
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
// this could be processed along with the final GCTR block, to
// potentially save a call to AES-ECB, but... just use AES-NI.
copy(j0[:], nonce)
j0[_aes.GHASH_BLOCK_SIZE - 1] = 1
ct64.encrypt_block(impl, j0[:], j0[:])
ct64.encrypt_block(impl, j0_enc[:], j0[:])
}
@(private = "file")
@@ -175,33 +197,27 @@ gctr_ct64 :: proc(
s: ^[_aes.GHASH_BLOCK_SIZE]byte,
src: []byte,
h: ^[_aes.GHASH_KEY_SIZE]byte,
nonce: []byte,
nonce: ^[_aes.GHASH_BLOCK_SIZE]byte,
is_seal: bool,
) {
) #no_bounds_check {
ct64_inc_ctr32 := #force_inline proc "contextless" (dst: []byte, ctr: u32) -> u32 {
endian.unchecked_put_u32be(dst[12:], ctr)
return ctr + 1
}
// 2. Define a block J_0 as follows:
// if len(IV) = 96, then let J0 = IV || 0^31 || 1
//
// Note: We only support 96 bit IVs.
// Setup the counter blocks.
tmp, tmp2: [ct64.STRIDE][BLOCK_SIZE]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 {
// Setup scratch space for the keystream.
blks[i] = tmp2[i][:]
// Pre-copy the IV to all the counter blocks.
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)
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
@(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
import "core:bytes"
import "core:encoding/endian"
import "core:math/bits"
import "core:mem"
@@ -121,14 +122,15 @@ seek :: proc(ctx: ^Context, block_nr: u64) {
xor_bytes :: proc(ctx: ^Context, dst, src: []byte) {
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
if dst_len := len(dst); dst_len < len(src) {
src = src[:dst_len]
}
if bytes.alias_inexactly(dst, src) {
panic("crypto/chacha20: dst and src alias inexactly")
}
for remaining := len(src); remaining > 0; {
// Process multiple blocks at once
if ctx._off == _BLOCK_SIZE {
+5 -1
View File
@@ -60,7 +60,11 @@ rand_bytes :: proc (dst: []byte) {
_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 {
return {
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[:])
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))
assert(res[9] < 10)
return
@@ -506,7 +507,7 @@ _marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (er
}
n: u64; {
for _, i in info.names {
for _, i in info.names[:info.field_count] {
if field_name(info, i) != "-" {
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
defer delete(entries)
for _, i in info.names {
for _, i in info.names[:info.field_count] {
fname := field_name(info, i)
if fname == "-" {
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
}
} else {
for _, i in info.names {
for _, i in info.names[:info.field_count] {
fname := field_name(info, i)
if fname == "-" {
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)
if !reflect.is_pointer_internally(variant) {
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 {
case reflect.Type_Info_Struct:
if t.is_raw_union {
if .raw_union in t.flags {
return _unsupported(v, hdr)
}
+12 -9
View File
@@ -82,15 +82,17 @@ 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) {
unquote :: proc(val: string) -> (string, runtime.Allocator_Error) {
v, allocated, ok := strconv.unquote_string(val)
if !ok {
return strings.clone(val)
if len(val) > 0 && (val[0] == '"' || val[0] == '\'') {
v, allocated, ok := strconv.unquote_string(val)
if !ok {
return strings.clone(val)
}
if allocated {
return v, nil
}
return strings.clone(v), nil
}
if allocated {
return v, nil
}
return strings.clone(v)
return strings.clone(val)
}
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
defer delete(data, allocator)
m, err = load_map_from_string(string(data), allocator, options)
ok = err != nil
ok = err == nil
defer if !ok {
delete_map(m)
}
@@ -142,6 +144,7 @@ delete_map :: proc(m: Map) {
delete(value, allocator)
}
delete(section)
delete(pairs)
}
delete(m)
}
+51 -34
View File
@@ -100,38 +100,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
case runtime.Type_Info_Integer:
buf: [40]byte
u: u128
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)
}
u := cast_any_int_to_u128(a)
s: string
@@ -310,7 +279,12 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
case cstring: name = string(s)
}
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
}
}
@@ -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))
info := ti.variant.(runtime.Type_Info_Struct)
first_iteration := true
for name, i in info.names {
for name, i in info.names[:info.field_count] {
omitempty := false
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, ",") {
switch flag {
case "omitempty":
@@ -657,3 +636,41 @@ opt_write_indentation :: proc(w: io.Writer, opt: ^Marshal_Options) -> (err: io.E
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
}
+24 -11
View File
@@ -363,12 +363,11 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
}
v := v
v = reflect.any_base(v)
ti := type_info_of(v.id)
ti := reflect.type_info_base(type_info_of(v.id))
#partial switch t in ti.variant {
case reflect.Type_Info_Struct:
if t.is_raw_union {
if .raw_union in t.flags {
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:
if !reflect.is_string(t.key) {
if !reflect.is_string(t.key) && !reflect.is_integer(t.key) {
return UNSUPPORTED_TYPE
}
raw_map := (^mem.Raw_Map)(v.data)
@@ -492,25 +491,39 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
key, _ := parse_object_key(p, p.allocator)
unmarshal_expect_token(p, .Colon)
mem.zero_slice(elem_backing)
if uerr := unmarshal_value(p, map_backing_value); uerr != nil {
delete(key, p.allocator)
return uerr
}
key_ptr := rawptr(&key)
key_ptr: rawptr
key_cstr: cstring
if reflect.is_cstring(t.key) {
key_cstr = cstring(raw_data(key))
key_ptr = &key_cstr
#partial switch tk in t.key.variant {
case runtime.Type_Info_String:
key_ptr = rawptr(&key)
key_cstr: cstring
if reflect.is_cstring(t.key) {
key_cstr = cstring(raw_data(key))
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)
if set_ptr == nil {
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) {
break map_loop
+44 -25
View File
@@ -334,6 +334,27 @@ panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! {
message := tprintf(fmt, ..args)
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
//
// *Allocates Using Context's Allocator*
@@ -346,9 +367,9 @@ panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! {
// Returns: A formatted C string
//
@(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
strings.builder_init(&str)
strings.builder_init(&str, allocator)
sbprintf(&str, format, ..args, newline=newline)
strings.write_byte(&str, 0)
s := strings.to_string(str)
@@ -365,8 +386,8 @@ caprintf :: proc(format: string, args: ..any, newline := false) -> cstring {
// Returns: A formatted C string
//
@(require_results)
caprintfln :: proc(format: string, args: ..any) -> cstring {
return caprintf(format, ..args, newline=true)
caprintfln :: proc(format: string, args: ..any, allocator := context.allocator) -> cstring {
return caprintf(format, ..args, allocator=allocator, newline=true)
}
// Creates a formatted C string
//
@@ -380,12 +401,7 @@ caprintfln :: proc(format: string, args: ..any) -> cstring {
//
@(require_results)
ctprint :: proc(args: ..any, sep := " ") -> cstring {
str: strings.Builder
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))
return caprint(args=args, sep=sep, allocator=context.temp_allocator)
}
// Creates a formatted C string
//
@@ -400,12 +416,7 @@ ctprint :: proc(args: ..any, sep := " ") -> cstring {
//
@(require_results)
ctprintf :: proc(format: string, args: ..any, newline := false) -> cstring {
str: strings.Builder
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))
return caprintf(format=format, args=args, allocator=context.temp_allocator, newline=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)
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
//
@@ -1861,7 +1872,7 @@ handle_tag :: proc(state: ^Info_State, data: rawptr, info: reflect.Type_Info_Str
if optional_len == nil {
return
}
for f, i in info.names {
for f, i in info.names[:info.field_count] {
if f != field_name {
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)
return
}
if info.is_raw_union {
if .raw_union in info.flags {
if type_name == "" {
io.write_string(fi.writer, "(raw union)", &fi.n)
} else {
@@ -1989,7 +2000,7 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St
// fi.hash = false;
fi.indent += 1
is_empty := len(info.names) == 0
is_empty := info.field_count == 0
if !is_soa && hash && !is_empty {
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
}
actual_field_count := len(info.names)
actual_field_count := info.field_count
n := uintptr(info.soa_len)
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])^)
} 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])^)
}
@@ -2099,7 +2110,7 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St
}
} else {
field_count := -1
for name, i in info.names {
for name, i in info.names[:info.field_count] {
optional_len: int = -1
use_nul_termination: bool = false
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
for name, i in info.names {
for name, i in info.names[:info.field_count] {
field_verb := verb
if handle_bit_field_tag(v.data, info, i, &field_verb) {
continue
@@ -2751,9 +2762,11 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
elem := runtime.type_info_base(info.elem)
if elem != nil {
if n, ok := fi.optional_len.?; ok {
fi.optional_len = nil
fmt_array(fi, ptr, n, elem.size, elem, verb)
return
} else if fi.use_nul_termination {
fi.use_nul_termination = false
fmt_array_nul_terminated(fi, ptr, -1, elem.size, elem, verb)
return
}
@@ -2855,8 +2868,10 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
n := info.count
ptr := v.data
if ol, ok := fi.optional_len.?; ok {
fi.optional_len = nil
n = min(n, ol)
} else if fi.use_nul_termination {
fi.use_nul_termination = false
fmt_array_nul_terminated(fi, ptr, n, info.elem_size, info.elem, verb)
return
}
@@ -2867,8 +2882,10 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
n := slice.len
ptr := slice.data
if ol, ok := fi.optional_len.?; ok {
fi.optional_len = nil
n = min(n, ol)
} else if fi.use_nul_termination {
fi.use_nul_termination = false
fmt_array_nul_terminated(fi, ptr, n, info.elem_size, info.elem, verb)
return
}
@@ -2879,8 +2896,10 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
n := array.len
ptr := array.data
if ol, ok := fi.optional_len.?; ok {
fi.optional_len = nil
n = min(n, ol)
} else if fi.use_nul_termination {
fi.use_nul_termination = false
fmt_array_nul_terminated(fi, ptr, n, info.elem_size, info.elem, verb)
return
}
+6 -56
View File
@@ -229,7 +229,7 @@ sqrt_complex128 :: proc "contextless" (x: complex128) -> complex128 {
}
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 {
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 {
switch re, im := real(x), imag(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)
return complex32(exp_complex64(complex64(x)))
}
exp_complex64 :: proc "contextless" (x: complex64) -> complex64 {
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 {
if x == 0 { // Guaranteed also true for x == -0.
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)
return complex32(pow_complex64(complex64(x), complex64(y)))
}
pow_complex64 :: proc "contextless" (x, y: complex64) -> complex64 {
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 {
return math.LN10*ln(x)
return complex32(log10_complex64(complex64(x)))
}
log10_complex64 :: proc "contextless" (x: complex64) -> complex64 {
return math.LN10*ln(x)
@@ -421,7 +372,7 @@ log10_complex128 :: proc "contextless" (x: complex128) -> complex128 {
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 {
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 {
s, c := math.sincos(θ)
return complex(r*c, r*s)
return complex32(rect_complex64(f32(r), f32(θ)))
}
rect_complex64 :: proc "contextless" (r, θ: f32) -> complex64 {
s, c := math.sincos(θ)
+3 -13
View File
@@ -61,8 +61,7 @@ atanh :: proc{
acos_complex32 :: proc "contextless" (x: complex32) -> complex32 {
w := asin(x)
return complex(math.PI/2 - real(w), -imag(w))
return complex32(acos_complex64(complex64(x)))
}
acos_complex64 :: proc "contextless" (x: complex64) -> complex64 {
w := asin(x)
@@ -75,14 +74,7 @@ acos_complex128 :: proc "contextless" (x: complex128) -> complex128 {
acosh_complex32 :: proc "contextless" (x: complex32) -> complex32 {
if x == 0 {
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))
return complex32(acosh_complex64(complex64(x)))
}
acosh_complex64 :: proc "contextless" (x: complex64) -> complex64 {
if x == 0 {
@@ -257,9 +249,7 @@ atan_complex128 :: proc "contextless" (x: complex128) -> complex128 {
}
atanh_complex32 :: proc "contextless" (x: complex32) -> complex32 {
z := complex(-imag(x), real(x)) // z = i * x
z = atan(z)
return complex(imag(z), -real(z)) // z = -i * z
return complex32(atanh_complex64(complex64(x)))
}
atanh_complex64 :: proc "contextless" (x: complex64) -> complex64 {
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
}
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)
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) {
family := _unwrap_os_family(family)
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 {
return {}, Create_Socket_Error(errno)
}
@@ -132,7 +132,7 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
}
// Create new TCP socket
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 {
// TODO(flysand): should return invalid file descriptor here casted as TCP_Socket
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)
// Create TCP socket
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 {
// TODO(flysand): should return invalid file descriptor here casted as TCP_Socket
return {}, Create_Socket_Error(errno)
+3
View File
@@ -599,6 +599,7 @@ Field_Flag :: enum {
Subtype,
By_Ptr,
No_Broadcast,
No_Capture,
Results,
Tags,
@@ -619,6 +620,7 @@ field_flag_strings := [Field_Flag]string{
.Subtype = "#subtype",
.By_Ptr = "#by_ptr",
.No_Broadcast = "#no_broadcast",
.No_Capture = "#no_capture",
.Results = "results",
.Tags = "field tag",
@@ -634,6 +636,7 @@ field_hash_flag_strings := []struct{key: string, flag: Field_Flag}{
{"subtype", .Subtype},
{"by_ptr", .By_Ptr},
{"no_broadcast", .No_Broadcast},
{"no_capture", .No_Capture},
}
+20 -17
View File
@@ -2179,22 +2179,25 @@ parse_inlining_operand :: proc(p: ^Parser, lhs: bool, tok: tokenizer.Token) -> ^
}
}
#partial switch e in ast.strip_or_return_expr(expr).derived_expr {
case ^ast.Proc_Lit:
if e.inlining != .None && e.inlining != pi {
error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure literal")
if expr != nil {
#partial switch e in ast.strip_or_return_expr(expr).derived_expr {
case ^ast.Proc_Lit:
if e.inlining != .None && e.inlining != pi {
error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure literal")
}
e.inlining = pi
return expr
case ^ast.Call_Expr:
if e.inlining != .None && e.inlining != pi {
error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure call")
}
e.inlining = pi
return expr
}
e.inlining = pi
case ^ast.Call_Expr:
if e.inlining != .None && e.inlining != pi {
error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure call")
}
e.inlining = pi
case:
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 expr
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)
}
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
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.tok = tok
bd.name = name.text
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.tok = tok
bd.name = name.text
return parse_call_expr(p, bd)
case "soa":
bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name))
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 == "" {
return
}
wkey := win32.utf8_to_wstring(key)
TEMP_ALLOCATOR_GUARD()
wkey, _ := win32_utf8_to_wstring(key, temp_allocator())
n := win32.GetEnvironmentVariableW(wkey, nil, 0)
if n == 0 {
@@ -32,20 +33,22 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
return "", false
}
value = win32.utf16_to_utf8(b[:n], allocator) or_else ""
value = win32_utf16_to_utf8(b[:n], allocator) or_else ""
found = true
return
}
_set_env :: proc(key, value: string) -> bool {
k := win32.utf8_to_wstring(key)
v := win32.utf8_to_wstring(value)
TEMP_ALLOCATOR_GUARD()
k, _ := win32_utf8_to_wstring(key, temp_allocator())
v, _ := win32_utf8_to_wstring(value, temp_allocator())
return bool(win32.SetEnvironmentVariableW(k, v))
}
_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))
}
@@ -89,7 +92,7 @@ _environ :: proc(allocator: runtime.Allocator) -> []string {
break
}
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
}
}
+4
View File
@@ -22,6 +22,7 @@ General_Error :: enum u32 {
Invalid_File,
Invalid_Dir,
Invalid_Path,
Invalid_Callback,
Pattern_Has_Separator,
@@ -38,6 +39,8 @@ Error :: union #shared_nil {
}
#assert(size_of(Error) == size_of(u64))
ERROR_NONE :: Error{}
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_Dir: return "invalid directory"
case .Invalid_Path: return "invalid path"
case .Invalid_Callback: return "invalid callback"
case .Unsupported: return "unsupported"
case .Pattern_Has_Separator: return "pattern has separator"
}
+10 -3
View File
@@ -1,6 +1,8 @@
//+private
package os2
import "base:runtime"
import "core:slice"
import win32 "core:sys/windows"
_error_string :: proc(errno: i32) -> string {
@@ -8,9 +10,14 @@ _error_string :: proc(errno: i32) -> string {
if e == 0 {
return ""
}
// TODO(bill): _error_string for windows
// FormatMessageW
return ""
err := runtime.Type_Info_Enum_Value(e)
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 {
+88 -20
View File
@@ -4,20 +4,57 @@ import "core:io"
import "core:time"
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 {
impl: _File,
impl: rawptr,
stream: io.Stream,
user_fstat: Fstat_Callback,
fstat: Fstat_Callback,
}
File_Mode :: distinct u32
File_Mode_Dir :: File_Mode(1<<16)
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)
/*
Type representing the type of a file handle.
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_Flag :: enum {
@@ -29,7 +66,7 @@ File_Flag :: enum {
Sync,
Trunc,
Sparse,
Close_On_Exec,
Inheritable,
Unbuffered_IO,
}
@@ -43,7 +80,15 @@ O_EXCL :: File_Flags{.Excl}
O_SYNC :: File_Flags{.Sync}
O_TRUNC :: File_Flags{.Trunc}
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
stdout: ^File = nil // OS-Specific
@@ -51,17 +96,17 @@ stderr: ^File = nil // OS-Specific
@(require_results)
create :: proc(name: string) -> (^File, Error) {
return open(name, {.Read, .Write, .Create}, File_Mode(0o777))
return open(name, {.Read, .Write, .Create}, 0o777)
}
@(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)
}
@(require_results)
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)
@@ -161,44 +206,56 @@ read_link :: proc(name: string, allocator: runtime.Allocator) -> (string, Error)
chdir :: change_directory
change_directory :: proc(name: string) -> Error {
return _chdir(name)
}
chmod :: change_mode
change_mode :: proc(name: string, mode: File_Mode) -> Error {
change_mode :: proc(name: string, mode: int) -> Error {
return _chmod(name, mode)
}
chown :: change_owner
change_owner :: proc(name: string, uid, gid: int) -> Error {
return _chown(name, uid, gid)
}
fchdir :: fchange_directory
fchange_directory :: proc(f: ^File) -> Error {
return _fchdir(f)
}
fchmod :: fchange_mode
fchange_mode :: proc(f: ^File, mode: File_Mode) -> Error {
fchange_mode :: proc(f: ^File, mode: int) -> Error {
return _fchmod(f, mode)
}
fchown :: fchange_owner
fchange_owner :: proc(f: ^File, uid, gid: int) -> Error {
return _fchown(f, uid, gid)
}
lchown :: change_owner_do_not_follow_links
change_owner_do_not_follow_links :: proc(name: string, uid, gid: int) -> Error {
return _lchown(name, uid, gid)
}
chtimes :: change_times
change_times :: proc(name: string, atime, mtime: time.Time) -> Error {
return _chtimes(name, atime, mtime)
}
fchtimes :: fchange_times
fchange_times :: proc(f: ^File, atime, mtime: time.Time) -> Error {
return _fchtimes(f, atime, mtime)
}
@@ -210,13 +267,24 @@ exists :: proc(path: string) -> bool {
@(require_results)
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
@(require_results)
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
defer file_info_delete(info, file_allocator())
if info.is_directory {
if info.type == .Directory {
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)
_, err := io.copy(to_writer(dst), to_reader(src))
+82 -103
View File
@@ -6,14 +6,15 @@ import "core:time"
import "base:runtime"
import "core:sys/linux"
_File :: struct {
File_Impl :: struct {
file: File,
name: string,
fd: linux.Fd,
allocator: runtime.Allocator,
}
_stdin : File = {
impl = {
_stdin := File{
impl = &File_Impl{
name = "/proc/self/fd/0",
fd = 0,
allocator = _file_allocator(),
@@ -21,9 +22,10 @@ _stdin : File = {
stream = {
procedure = _file_stream_proc,
},
fstat = _fstat,
}
_stdout : File = {
impl = {
_stdout := File{
impl = &File_Impl{
name = "/proc/self/fd/1",
fd = 1,
allocator = _file_allocator(),
@@ -31,9 +33,10 @@ _stdout : File = {
stream = {
procedure = _file_stream_proc,
},
fstat = _fstat,
}
_stderr : File = {
impl = {
_stderr := File{
impl = &File_Impl{
name = "/proc/self/fd/2",
fd = 2,
allocator = _file_allocator(),
@@ -41,6 +44,7 @@ _stderr : File = {
stream = {
procedure = _file_stream_proc,
},
fstat = _fstat,
}
@init
@@ -59,70 +63,67 @@ _file_allocator :: proc() -> runtime.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()
name_cstr := temp_cstring(name) or_return
// Just default to using O_NOCTTY because needing to open a controlling
// terminal would be incredibly rare. This has no effect on files while
// 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 {
case O_RDONLY:
case O_WRONLY: sys_flags += {.WRONLY}
case O_RDWR: sys_flags += {.RDWR}
}
if .Append in flags { sys_flags += {.APPEND} }
if .Create in flags { sys_flags += {.CREAT} }
if .Excl in flags { sys_flags += {.EXCL} }
if .Sync in flags { sys_flags += {.DSYNC} }
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 {
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 {
file := new(File, file_allocator())
_construct_file(file, fd, "")
return file
}
_construct_file :: proc(file: ^File, fd: uintptr, _: string = "") {
file^ = {
impl = {
fd = linux.Fd(fd),
allocator = file_allocator(),
name = _get_full_path(file.impl.fd, file.impl.allocator),
},
stream = {
data = file,
procedure = _file_stream_proc,
},
_new_file :: proc(fd: uintptr, _: string = "") -> (f: ^File, err: Error) {
impl := new(File_Impl, file_allocator()) or_return
defer if err != nil {
free(impl, file_allocator())
}
impl.file.impl = impl
impl.fd = linux.Fd(fd)
impl.allocator = file_allocator()
impl.name = _get_full_path(impl.fd, file_allocator()) or_return
impl.file.stream = {
data = impl,
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 {
return nil
}
delete(f.impl.name, f.impl.allocator)
free(f, f.impl.allocator)
a := f.allocator
delete(f.name, a)
free(f, a)
return nil
}
_close :: proc(f: ^File) -> Error {
if f == nil {
_close :: proc(f: ^File_Impl) -> Error {
if f == nil{
return nil
}
errno := linux.close(f.impl.fd)
errno := linux.close(f.fd)
if errno == .EBADF { // avoid possible double free
return _get_platform_error(errno)
}
@@ -131,41 +132,41 @@ _close :: proc(f: ^File) -> Error {
}
_fd :: proc(f: ^File) -> uintptr {
if f == nil {
if f == nil || f.impl == nil {
return ~uintptr(0)
}
return uintptr(f.impl.fd)
impl := (^File_Impl)(f.impl)
return uintptr(impl.fd)
}
_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) {
n, errno := linux.lseek(f.impl.fd, offset, linux.Seek_Whence(whence))
_seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) {
n, errno := linux.lseek(f.fd, offset, linux.Seek_Whence(whence))
if errno != .NONE {
return -1, _get_platform_error(errno)
}
return n, nil
}
_read :: proc(f: ^File, p: []byte) -> (i64, Error) {
_read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) {
if len(p) == 0 {
return 0, nil
}
n, errno := linux.read(f.impl.fd, p[:])
n, errno := linux.read(f.fd, p[:])
if errno != .NONE {
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 {
return 0, .Invalid_Offset
}
n, errno := linux.pread(f.impl.fd, p[:], offset)
n, errno := linux.pread(f.fd, p[:], offset)
if errno != .NONE {
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
}
_write :: proc(f: ^File, p: []byte) -> (i64, Error) {
_write :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) {
if len(p) == 0 {
return 0, nil
}
n, errno := linux.write(f.impl.fd, p[:])
n, errno := linux.write(f.fd, p[:])
if errno != .NONE {
return -1, _get_platform_error(errno)
}
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 {
return 0, .Invalid_Offset
}
n, errno := linux.pwrite(f.impl.fd, p[:], offset)
n, errno := linux.pwrite(f.fd, p[:], offset)
if errno != .NONE {
return -1, _get_platform_error(errno)
}
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 = ---
errno := linux.fstat(f.impl.fd, &s)
errno := linux.fstat(f.fd, &s)
if errno != .NONE {
return -1, _get_platform_error(errno)
}
@@ -208,27 +208,38 @@ _file_size :: proc(f: ^File) -> (n: i64, err: 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 {
return _get_platform_error(linux.fsync(f.impl.fd))
_flush :: proc(f: ^File_Impl) -> Error {
return _get_platform_error(linux.fsync(f.fd))
}
_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 {
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()
name_cstr := temp_cstring(name) or_return
fd, errno := linux.open(name_cstr, {.NOFOLLOW})
#partial switch (errno) {
case .ELOOP: /* symlink */
case .ELOOP:
/* symlink */
case .NONE:
defer linux.close(fd)
if _is_dir_fd(fd) {
if is_dir_fd(fd) {
return _get_platform_error(linux.rmdir(name_cstr))
}
case:
@@ -292,17 +303,19 @@ _chdir :: proc(name: string) -> 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()
name_cstr := temp_cstring(name) or_return
return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)(u32(mode))))
}
_fchmod :: proc(f: ^File, mode: File_Mode) -> Error {
return _get_platform_error(linux.fchmod(f.impl.fd, transmute(linux.Mode)(u32(mode))))
_fchmod :: proc(f: ^File, mode: int) -> Error {
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
@@ -321,7 +334,8 @@ _lchown :: proc(name: string, uid, gid: int) -> Error {
// NOTE: will throw error without super user priviledges
_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 {
@@ -351,7 +365,8 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
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 {
@@ -361,42 +376,6 @@ _exists :: proc(name: string) -> bool {
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
* files (e.g. everything in /proc/). Therefore, the
* 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")
_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
switch mode {
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)
}
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) {
return write(f, []byte{b})
}
@@ -138,7 +150,7 @@ read_entire_file_from_file :: proc(f: ^File, allocator: runtime.Allocator) -> (d
}
@(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
if truncate {
flags |= O_TRUNC
+199 -116
View File
@@ -17,17 +17,19 @@ _ERROR_BAD_NETPATH :: 53
MAX_RW :: 1<<30
_File_Kind :: enum u8 {
File_Impl_Kind :: enum u8 {
File,
Console,
Pipe,
}
_File :: struct {
File_Impl :: struct {
file: File,
fd: rawptr,
name: string,
wname: win32.wstring,
kind: _File_Kind,
kind: File_Impl_Kind,
allocator: runtime.Allocator,
@@ -53,13 +55,14 @@ _handle :: proc(f: ^File) -> win32.HANDLE {
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 {
err = .Not_Exist
return
}
TEMP_ALLOCATOR_GUARD()
path := _fix_long_path(name)
path := _fix_long_path(name, temp_allocator()) or_return
access: u32
switch flags & {.Read, .Write} {
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
}
share_mode := u32(win32.FILE_SHARE_READ | win32.FILE_SHARE_WRITE)
sa: ^win32.SECURITY_ATTRIBUTES
if .Close_On_Exec not_in flags {
sa = &win32.SECURITY_ATTRIBUTES{}
sa.nLength = size_of(win32.SECURITY_ATTRIBUTES)
sa.bInheritHandle = true
sa := win32.SECURITY_ATTRIBUTES {
nLength = size_of(win32.SECURITY_ATTRIBUTES),
bInheritHandle = .Inheritable in flags,
}
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
}
attrs: u32 = win32.FILE_ATTRIBUTE_NORMAL
attrs: u32 = win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS
if perm & S_IWRITE == 0 {
attrs = win32.FILE_ATTRIBUTE_READONLY
if create_mode == win32.CREATE_ALWAYS {
// 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,
// 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 {
switch e := win32.GetLastError(); e {
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:
return uintptr(h), nil
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 {
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}
handle := _open_internal(name, flags + {.Close_On_Exec}, perm) or_return
return _new_file(handle, name), nil
handle := _open_internal(name, flags, perm) or_return
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 {
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()
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)
impl.file.impl = impl
handle := _handle(f)
kind := _File_Kind.File
impl.allocator = file_allocator()
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) {
kind = .Console
}
if win32.GetFileType(handle) == win32.FILE_TYPE_PIPE {
kind = .Pipe
}
f.impl.kind = kind
impl.kind = kind
f.stream = {
data = f,
impl.file.stream = {
data = impl,
procedure = _file_stream_proc,
}
impl.file.fstat = _fstat
return f
return &impl.file, nil
}
_fd :: proc(f: ^File) -> uintptr {
if f == nil {
if f == nil || f.impl == nil {
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 {
return nil
}
a := f.impl.allocator
free(f.impl.wname, a)
delete(f.impl.name, a)
free(f, a)
a := f.allocator
err0 := free(f.wname, a)
err1 := delete(f.name, a)
err2 := free(f, a)
err0 or_return
err1 or_return
err2 or_return
return nil
}
_close :: proc(f: ^File) -> Error {
if f == nil {
_close :: proc(f: ^File_Impl) -> Error {
if f == nil {
return nil
}
if !win32.CloseHandle(win32.HANDLE(f.impl.fd)) {
if !win32.CloseHandle(win32.HANDLE(f.fd)) {
return .Closed
}
return _destroy(f)
}
_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) {
handle := _handle(f)
_seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) {
handle := _handle(&f.file)
if handle == win32.INVALID_HANDLE {
return 0, .Invalid_File
}
if f.impl.kind == .Pipe {
if f.kind == .Pipe {
return 0, .Invalid_File
}
sync.guard(&f.impl.rw_mutex)
sync.guard(&f.rw_mutex)
w: u32
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
}
_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) {
if len(b) == 0 {
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
buf16: [BUF_SIZE]u16
buf8: [4*BUF_SIZE]u8
@@ -269,18 +281,18 @@ _read :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) {
return
}
handle := _handle(f)
handle := _handle(&f.file)
single_read_length: win32.DWORD
total_read: int
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)
ok: win32.BOOL
if f.impl.kind == .Console {
if f.kind == .Console {
n, cerr := read_console(handle, p[total_read:][:to_read])
total_read += n
if cerr != nil {
@@ -300,15 +312,15 @@ _read :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) {
return i64(total_read), err
}
_read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) {
pread :: proc(f: ^File, data: []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_Impl, data: []byte, offset: i64) -> (n: i64, err: Error) {
buf := data
if len(buf) > MAX_RW {
buf = buf[:MAX_RW]
}
curr_offset := seek(f, offset, .Current) or_return
defer seek(f, curr_offset, .Start)
curr_offset := _seek(f, offset, .Current) or_return
defer _seek(f, curr_offset, .Start)
o := win32.OVERLAPPED{
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
h := _handle(f)
h := _handle(&f.file)
done: win32.DWORD
if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) {
err = _get_platform_error()
@@ -327,7 +339,7 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) {
return
}
sync.guard(&f.impl.p_mutex)
sync.guard(&f.p_mutex)
p, offset := p, offset
for len(p) > 0 {
@@ -339,7 +351,7 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) {
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 {
return
}
@@ -348,9 +360,9 @@ _write :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) {
total_write: i64
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 {
remaining := length - total_write
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
}
_write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) {
pwrite :: proc(f: ^File, data: []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_Impl, data: []byte, offset: i64) -> (n: i64, err: Error) {
buf := data
if len(buf) > MAX_RW {
buf = buf[:MAX_RW]
}
curr_offset := seek(f, offset, .Current) or_return
defer seek(f, curr_offset, .Start)
curr_offset := _seek(f, offset, .Current) or_return
defer _seek(f, curr_offset, .Start)
o := win32.OVERLAPPED{
OffsetHigh = u32(offset>>32),
Offset = u32(offset),
}
h := _handle(f)
h := _handle(&f.file)
done: win32.DWORD
if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) {
err = _get_platform_error()
@@ -391,7 +403,7 @@ _write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) {
return
}
sync.guard(&f.impl.p_mutex)
sync.guard(&f.p_mutex)
p, offset := p, offset
for len(p) > 0 {
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
}
_file_size :: proc(f: ^File) -> (n: i64, err: Error) {
_file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) {
length: win32.LARGE_INTEGER
if f.impl.kind == .Pipe {
if f.kind == .Pipe {
return 0, .No_Size
}
handle := _handle(f)
handle := _handle(&f.file)
if !win32.GetFileSizeEx(handle, &length) {
err = _get_platform_error()
}
@@ -417,11 +429,14 @@ _file_size :: proc(f: ^File) -> (n: i64, err: 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 {
handle := _handle(f)
_flush :: proc(f: ^File_Impl) -> Error {
handle := _handle(&f.file)
if !win32.FlushFileBuffers(handle) {
return _get_platform_error()
}
@@ -429,7 +444,7 @@ _flush :: proc(f: ^File) -> Error {
}
_truncate :: proc(f: ^File, size: i64) -> Error {
if f == nil {
if f == nil || f.impl == nil {
return nil
}
curr_off := seek(f, 0, .Current) or_return
@@ -443,7 +458,8 @@ _truncate :: proc(f: ^File, size: i64) -> 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
if !win32.DeleteFileW(p) {
err = _get_platform_error()
@@ -480,8 +496,9 @@ _remove :: proc(name: string) -> Error {
}
_rename :: proc(old_path, new_path: string) -> Error {
from := _fix_long_path(old_path)
to := _fix_long_path(new_path)
TEMP_ALLOCATOR_GUARD()
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) {
return nil
}
@@ -489,10 +506,10 @@ _rename :: proc(old_path, new_path: string) -> Error {
}
_link :: proc(old_name, new_name: string) -> Error {
o := _fix_long_path(old_name)
n := _fix_long_path(new_name)
TEMP_ALLOCATOR_GUARD()
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) {
return nil
}
@@ -532,16 +549,16 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st
}
if !has_unc_prefix(p) {
return win32.utf16_to_utf8(p, allocator)
return win32_utf16_to_utf8(p, allocator)
}
ws := p[4:]
switch {
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\`):
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:]
if len(ws) > 3 && has_prefix(ws, `UNC`) {
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
}
@@ -579,7 +596,9 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er
@thread_local
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
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
p := pb[rb.SubstituteNameOffset:][:rb.SubstituteNameLength]
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)
@@ -616,17 +635,18 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er
_fchdir :: proc(f: ^File) -> Error {
if f == nil {
if f == nil || f.impl == nil {
return nil
}
if !win32.SetCurrentDirectoryW(f.impl.wname) {
impl := (^File_Impl)(f.impl)
if !win32.SetCurrentDirectoryW(impl.wname) {
return _get_platform_error()
}
return nil
}
_fchmod :: proc(f: ^File, mode: File_Mode) -> Error {
if f == nil {
_fchmod :: proc(f: ^File, mode: int) -> Error {
if f == nil || f.impl == nil {
return nil
}
d: win32.BY_HANDLE_FILE_INFORMATION
@@ -653,14 +673,15 @@ _fchown :: proc(f: ^File, uid, gid: int) -> 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) {
return _get_platform_error()
}
return nil
}
_chmod :: proc(name: string, mode: File_Mode) -> Error {
_chmod :: proc(name: string, mode: int) -> Error {
f := open(name, {.Write}) or_return
defer close(f)
return _fchmod(f, mode)
@@ -681,7 +702,7 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
return _fchtimes(f, atime, mtime)
}
_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
if f == nil {
if f == nil || f.impl == nil {
return nil
}
d: win32.BY_HANDLE_FILE_INFORMATION
@@ -708,36 +729,16 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
return nil
}
_exists :: proc(path: string) -> bool {
wpath := _fix_long_path(path)
TEMP_ALLOCATOR_GUARD()
wpath, _ := _fix_long_path(path, temp_allocator())
attribs := win32.GetFileAttributesW(wpath)
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")
_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
switch mode {
case .Read:
@@ -778,3 +779,85 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte,
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) {
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]
return string(buf[i:])
}
+8 -5
View File
@@ -12,12 +12,14 @@ is_path_separator :: proc(c: byte) -> bool {
}
mkdir :: make_directory
make_directory :: proc(name: string, perm: File_Mode) -> Error {
make_directory :: proc(name: string, perm: int) -> Error {
return _mkdir(name, perm)
}
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)
}
@@ -25,14 +27,15 @@ remove_all :: proc(path: string) -> Error {
return _remove_all(path)
}
getwd :: get_working_directory
@(require_results)
get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
return _getwd(allocator)
return _get_working_directory(allocator)
}
setwd :: set_working_directory
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 == '/'
}
_mkdir :: proc(path: string, perm: File_Mode) -> 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
}
_mkdir :: proc(path: string, perm: int) -> Error {
TEMP_ALLOCATOR_GUARD()
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 {
i: int
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)
#partial switch errno {
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)
}
has_created^ = true
@@ -53,17 +47,9 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error {
// skip consecutive '/'
for i += 1; i < len(path) && path[i] == '/'; i += 1 {}
return mkdirat(new_dfd, path[i:], perm, has_created)
case:
return _get_platform_error(errno)
}
unreachable()
return _get_platform_error(errno)
}
// 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()
// need something we can edit, and use to generate cstrings
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
mkdirat(dfd, path_bytes, int(perm & 0o777), &has_created) or_return
if has_created {
return nil
}
return .Exist
//return has_created ? nil : .Exist
mkdirat(dfd, path_bytes, perm, &has_created) or_return
return nil if has_created else .Exist
}
dirent64 :: struct {
@@ -181,7 +163,7 @@ _remove_all :: proc(path: string) -> Error {
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
// an authoritative value for it across all systems.
// 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()
}
_setwd :: proc(dir: string) -> Error {
_set_working_directory :: proc(dir: string) -> Error {
dir_cstr := temp_cstring(dir) or_return
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/"
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))
fullpath: string
err: Error
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 == '/'
}
_mkdir :: proc(name: string, perm: File_Mode) -> Error {
if !win32.CreateDirectoryW(_fix_long_path(name), nil) {
_mkdir :: proc(name: string, perm: int) -> Error {
TEMP_ALLOCATOR_GUARD()
if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator()) or_return, nil) {
return _get_platform_error()
}
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) {
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] == ':' {
@@ -33,9 +34,9 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error {
TEMP_ALLOCATOR_GUARD()
dir, err := stat(path, temp_allocator())
dir_stat, err := stat(path, temp_allocator())
if err == nil {
if dir.is_directory {
if dir_stat.type == .Directory {
return nil
}
return .Exist
@@ -61,8 +62,8 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error {
err = mkdir(path, perm)
if err != nil {
dir1, err1 := lstat(path, temp_allocator())
if err1 == nil && dir1.is_directory {
new_dir_stat, err1 := lstat(path, temp_allocator())
if err1 == nil && new_dir_stat.type == .Directory {
return nil
}
return err
@@ -71,41 +72,114 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> 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
}
_getwd :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
// TODO(bill)
return "", nil
@private cwd_lock: win32.SRWLOCK // zero is initialized
_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) {
// TODO(bill)
return nil
}
_set_working_directory :: proc(dir: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD()
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
@(init)
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
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
}
}
_fix_long_path_slice :: proc(path: string) -> []u16 {
return win32.utf8_to_utf16(_fix_long_path_internal(path))
@(require_results)
_fix_long_path_slice :: proc(path: string, allocator: runtime.Allocator) -> ([]u16, runtime.Allocator_Error) {
return win32_utf8_to_utf16(_fix_long_path_internal(path), allocator)
}
_fix_long_path :: proc(path: string) -> win32.wstring {
return win32.utf8_to_wstring(_fix_long_path_internal(path))
@(require_results)
_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 {
if can_use_long_paths {
return path
@@ -162,5 +236,4 @@ _fix_long_path_internal :: proc(path: string) -> string {
}
return string(path_buf[:w])
}
+3 -3
View File
@@ -5,13 +5,13 @@ import "core:sys/linux"
_pipe :: proc() -> (r, w: ^File, err: Error) {
fds: [2]linux.Fd
errno := linux.pipe2(&fds, {.CLOEXEC})
errno := linux.pipe2(&fds, {})
if errno != .NONE {
return nil, nil,_get_platform_error(errno)
}
r = _new_file(uintptr(fds[0]))
w = _new_file(uintptr(fds[1]))
r = _new_file(uintptr(fds[0])) or_return
w = _new_file(uintptr(fds[1])) or_return
return
}
+5 -1
View File
@@ -5,7 +5,11 @@ import win32 "core:sys/windows"
_pipe :: proc() -> (r, w: ^File, err: Error) {
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 new_file(uintptr(p[0]), ""), new_file(uintptr(p[1]), ""), nil
+356 -52
View File
@@ -1,102 +1,406 @@
package os2
import "core:sync"
import "core:time"
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) -> ! {
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 {
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 {
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 {
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 {
return -1
return _get_egid()
}
/*
Obtain the ID of the current process.
*/
@(require_results)
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 {
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 {
pid: int,
handle: uintptr,
is_done: b32,
signal_mutex: sync.RW_Mutex,
pid: int,
handle: uintptr,
}
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,
files: []^File,
sys: ^Process_Attributes_OS_Specific,
// The `stderr` handle to give to the child process. It can be either a file
// 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 {
None,
This procedure creates a new process, with a given command and environment
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 {
pid: int,
exit_code: int,
exited: bool,
success: bool,
// The ID of the process.
pid: int,
// Specifies whether the process has terminated or is still running.
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,
// The time the process has spend executing in kernel time.
system_time: time.Duration,
user_time: time.Duration,
sys: rawptr,
// The time the process has spend executing in userspace.
user_time: time.Duration,
}
Signal :: #type proc()
/*
Wait for a process event.
Kill: Signal = nil
Interrupt: Signal = nil
This procedure blocks the execution until the process has exited or the
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) {
return nil, .None
If an error is returned for any other reason, other than timeout, the
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) {
return nil, .None
This procedure closes the handle associated with a process. It **does not**
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
import "core:time"
import "base:runtime"
import "core:path/filepath"
import "core:strings"
import "core:time"
Fstat_Callback :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error)
File_Info :: struct {
fullpath: string,
name: string,
size: i64,
mode: File_Mode,
is_directory: bool,
inode: u128, // might be zero if cannot be determined
size: i64 `fmt:"M"`,
mode: int `fmt:"o"`,
type: File_Type,
creation_time: time.Time,
modification_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) {
for i := len(infos)-1; i >= 0; i -= 1 {
file_info_delete(infos[i], allocator)
@@ -29,10 +42,12 @@ file_info_delete :: proc(fi: File_Info, allocator: runtime.Allocator) {
@(require_results)
fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
if f != nil && f.user_fstat != nil {
return f->user_fstat(allocator)
if f == nil {
return {}, nil
} else if f.fstat != nil {
return f->fstat(allocator)
}
return _fstat(f, allocator)
return {}, .Invalid_Callback
}
@(require_results)
@@ -41,6 +56,7 @@ stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
}
lstat :: stat_do_not_follow_links
@(require_results)
stat_do_not_follow_links :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
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 {
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
}
+24 -12
View File
@@ -7,31 +7,43 @@ import "core:sys/linux"
import "core:path/filepath"
_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
errno := linux.fstat(fd, &s)
if errno != .NONE {
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
fi := File_Info {
fullpath = _get_full_path(fd, allocator),
name = "",
size = i64(s.size),
mode = 0,
is_directory = linux.S_ISDIR(s.mode),
fi = File_Info {
fullpath = _get_full_path(fd, allocator) or_return,
name = "",
inode = u128(u64(s.ino)),
size = i64(s.size),
mode = mode,
type = type,
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)},
creation_time = time.Time{i64(s.ctime.time_sec) * i64(time.Second) + i64(s.ctime.time_nsec)}, // regular stat does not provide this
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
}
fi.creation_time = fi.modification_time
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
+37 -61
View File
@@ -7,7 +7,7 @@ import "core:strings"
import win32 "core:sys/windows"
_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
}
@@ -19,28 +19,29 @@ _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
h := _handle(f)
switch win32.GetFileType(h) {
case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR:
fi: File_Info
fi.fullpath = path
fi.name = basename(path)
fi.mode |= file_type_mode(h)
fi := File_Info {
fullpath = path,
name = basename(path),
type = file_type(h),
}
return fi, nil
}
return _file_info_from_get_file_information_by_handle(path, h, allocator)
}
_stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS, allocator)
}
_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)
}
_same_file :: proc(fi1, fi2: File_Info) -> bool {
return fi1.fullpath == fi2.fullpath
}
full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path: string, err: Error) {
name := name
if name == "" {
@@ -48,7 +49,7 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path
}
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)
if n == 0 {
@@ -59,16 +60,16 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path
if n == 0 {
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) {
if len(name) == 0 {
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
ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa)
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)
}
_cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 {
buf := buf
N := 0
@@ -120,9 +120,8 @@ _cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 {
return buf
}
_cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (string, Error) {
if f == nil || f.impl.fd == nil {
if f == nil {
return "", nil
}
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) {
if f == nil || f.impl.fd == nil {
if f == nil {
return nil, nil
}
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) {
buf := 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) {
name := name
if len(name) > 3 && name[:3] == `\\?` {
@@ -185,83 +183,67 @@ basename :: proc(name: string) -> (base: string) {
return name
}
file_type_mode :: proc(h: win32.HANDLE) -> File_Mode {
file_type :: proc(h: win32.HANDLE) -> File_Type {
switch win32.GetFileType(h) {
case win32.FILE_TYPE_PIPE:
return File_Mode_Named_Pipe
case win32.FILE_TYPE_CHAR:
return File_Mode_Device | File_Mode_Char_Device
case win32.FILE_TYPE_PIPE: return .Named_Pipe
case win32.FILE_TYPE_CHAR: return .Character_Device
case win32.FILE_TYPE_DISK: return .Regular
}
return 0
return .Undetermined
}
_file_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (mode: File_Mode) {
_file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (type: File_Type, mode: int) {
if file_attributes & win32.FILE_ATTRIBUTE_READONLY != 0 {
mode |= 0o444
} else {
mode |= 0o666
}
is_sym := false
if file_attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
is_sym = false
} else {
is_sym = ReparseTag == win32.IO_REPARSE_TAG_SYMLINK || ReparseTag == win32.IO_REPARSE_TAG_MOUNT_POINT
}
if is_sym {
mode |= File_Mode_Sym_Link
type = .Symlink
} else {
if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
mode |= 0o111 | File_Mode_Dir
type = .Directory
mode |= 0o111
}
if h != nil {
mode |= file_type_mode(h)
type = file_type(h)
}
}
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) {
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
fi.is_directory = fi.mode & File_Mode_Dir != 0
type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
fi.type = type
fi.mode |= mode
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))
fi.fullpath, e = full_path_from_name(name, allocator)
fi.name = basename(fi.fullpath)
return
}
_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.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
fi.is_directory = fi.mode & File_Mode_Dir != 0
type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
fi.type = type
fi.mode |= mode
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))
fi.fullpath, e = full_path_from_name(name, allocator)
fi.name = basename(fi.fullpath)
return
}
_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
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
ti.ReparseTag = 0
}
fi: File_Info
fi.fullpath = path
fi.name = basename(path)
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
fi.mode |= _file_mode_from_file_attributes(ti.FileAttributes, h, ti.ReparseTag)
fi.is_directory = fi.mode & File_Mode_Dir != 0
fi.inode = u128(u64(d.nFileIndexHigh)<<32 + u64(d.nFileIndexLow))
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
fi.type = type
fi.mode |= mode
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))
return fi, nil
}
reserved_names := [?]string{
"CON", "PRN", "AUX", "NUL",
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
@@ -357,7 +334,6 @@ _volume_name_len :: proc(path: string) -> int {
return 0
}
_is_abs :: proc(path: string) -> bool {
if _is_reserved_name(path) {
return true
+1 -1
View File
@@ -26,7 +26,7 @@ create_temp_file :: proc(dir, pattern: string) -> (f: ^File, err: Error) {
attempts := 0
for {
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 {
close(f)
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] == '\\' {
n -= 1
}
return win32.utf16_to_utf8(b[:n], allocator)
return win32_utf16_to_utf8(b[:n], allocator)
}
+2 -2
View File
@@ -1,4 +1,4 @@
//+freestanding
//+build freestanding
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)
any_base :: proc(v: any) -> any {
v := v
if v != nil {
if v.id != nil {
v.id = typeid_base(v.id)
}
return v
@@ -151,7 +151,7 @@ any_base :: proc(v: any) -> any {
@(require_results)
any_core :: proc(v: any) -> any {
v := v
if v != nil {
if v.id != nil {
v.id = typeid_core(v.id)
}
return v
@@ -391,7 +391,7 @@ Struct_Field :: struct {
struct_field_at :: proc(T: typeid, i: int) -> (field: Struct_Field) {
ti := runtime.type_info_base(type_info_of(T))
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.type = s.types[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) {
ti := runtime.type_info_base(type_info_of(T))
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 {
field.name = s.names[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))
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 {
return any{
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 {
ti := runtime.type_info_base(type_info_of(T))
if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
return s.names
return s.names[:s.field_count]
}
return nil
}
@@ -472,7 +472,7 @@ struct_field_names :: proc(T: typeid) -> []string {
struct_field_types :: proc(T: typeid) -> []^Type_Info {
ti := runtime.type_info_base(type_info_of(T))
if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
return s.types
return s.types[:s.field_count]
}
return nil
}
@@ -482,7 +482,7 @@ struct_field_types :: proc(T: typeid) -> []^Type_Info {
struct_field_tags :: proc(T: typeid) -> []Struct_Tag {
ti := runtime.type_info_base(type_info_of(T))
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
}
@@ -491,7 +491,7 @@ struct_field_tags :: proc(T: typeid) -> []Struct_Tag {
struct_field_offsets :: proc(T: typeid) -> []uintptr {
ti := runtime.type_info_base(type_info_of(T))
if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
return s.offsets
return s.offsets[:s.field_count]
}
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))
if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
return soa_zip(
name = s.names,
type = s.types,
tag = transmute([]Struct_Tag)s.tags,
offset = s.offsets,
is_using = s.usings,
name = s.names[:s.field_count],
type = s.types[:s.field_count],
tag = ([^]Struct_Tag)(s.tags)[:s.field_count],
offset = s.offsets[:s.field_count],
is_using = s.usings[:s.field_count],
)
}
return nil
@@ -1569,7 +1569,7 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_
if v.equal != nil {
return v.equal(a.data, b.data)
} else {
for offset, i in v.offsets {
for offset, i in v.offsets[:v.field_count] {
x := rawptr(uintptr(a.data) + offset)
y := rawptr(uintptr(b.data) + offset)
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:
y := b.variant.(Type_Info_Struct) or_return
switch {
case len(x.types) != len(y.types),
x.is_packed != y.is_packed,
x.is_raw_union != y.is_raw_union,
x.custom_align != y.custom_align,
case x.field_count != y.field_count,
x.flags != y.flags,
x.soa_kind != y.soa_kind,
x.soa_base_type != y.soa_base_type,
x.soa_len != y.soa_len:
return false
}
for _, i in x.types {
for i in 0..<x.field_count {
xn, yn := x.names[i], y.names[i]
xt, yt := x.types[i], y.types[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:
y := b.variant.(Type_Info_Bit_Field) or_return
if !are_types_identical(x.backing_type, y.backing_type) { return false }
if len(x.names) != len(y.names) { return false }
for _, i in x.names {
if x.field_count != y.field_count { return false }
for _, i in x.names[:x.field_count] {
if x.names[i] != y.names[i] {
return false
}
@@ -368,13 +366,13 @@ is_tuple :: proc(info: ^Type_Info) -> bool {
is_struct :: proc(info: ^Type_Info) -> bool {
if info == nil { return false }
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)
is_raw_union :: proc(info: ^Type_Info) -> bool {
if info == nil { return false }
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)
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)
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 {
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
if info.is_packed { io.write_string(w, "#packed ", &n) or_return }
if info.is_raw_union { io.write_string(w, "#raw_union ", &n) or_return }
if info.custom_align {
if .packed in info.flags { io.write_string(w, "#packed ", &n) or_return }
if .raw_union in info.flags { io.write_string(w, "#raw_union ", &n) or_return }
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_i64(w, i64(ti.align), 10, &n) or_return
io.write_string(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 }
io.write_string(w, name, &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
write_type(w, info.backing_type, &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 }
io.write_string(w, name, &n) or_return
io.write_string(w, ": ", &n) or_return
+8 -8
View File
@@ -2,33 +2,33 @@
package simd_x86
@(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)
}
@(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)
}
@(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)
}
@(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)
}
@(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)
}
@(require_results, enable_target_feature = "aes")
_mm_aeskeygenassist :: #force_inline proc "c" (a: __m128i, $IMM8: u8) -> __m128i {
return aeskeygenassist(a, u8(IMM8))
_mm_aeskeygenassist_si128 :: #force_inline proc "c" (a: __m128i, $IMM8: u8) -> __m128i {
return aeskeygenassist(a, IMM8)
}
@@ -45,5 +45,5 @@ foreign _ {
@(link_name = "llvm.x86.aesni.aesimc")
aesimc :: proc(a: __m128i) -> __m128i ---
@(link_name = "llvm.x86.aesni.aeskeygenassist")
aeskeygenassist :: proc(a: __m128i, imm8: u8) -> __m128i ---
aeskeygenassist :: proc(a: __m128i, #const imm8: u8) -> __m128i ---
}
+19 -14
View File
@@ -144,19 +144,26 @@ _mm_subs_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i {
_mm_slli_si128_impl :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i {
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(
transmute(i8x16)a,
i8x16(0),
0 when shift > 15 else (16 - shift + 0),
1 when shift > 15 else (16 - shift + 1),
2 when shift > 15 else (16 - shift + 2),
3 when shift > 15 else (16 - shift + 3),
4 when shift > 15 else (16 - shift + 4),
5 when shift > 15 else (16 - shift + 5),
6 when shift > 15 else (16 - shift + 6),
7 when shift > 15 else (16 - shift + 7),
8 when shift > 15 else (16 - shift + 8),
9 when shift > 15 else (16 - shift + 9),
transmute(i8x16)a,
0 when shift > 15 else (16 - shift + 0),
1 when shift > 15 else (16 - shift + 1),
2 when shift > 15 else (16 - shift + 2),
3 when shift > 15 else (16 - shift + 3),
4 when shift > 15 else (16 - shift + 4),
5 when shift > 15 else (16 - shift + 5),
6 when shift > 15 else (16 - shift + 6),
7 when shift > 15 else (16 - shift + 7),
8 when shift > 15 else (16 - shift + 8),
9 when shift > 15 else (16 - shift + 9),
10 when shift > 15 else (16 - shift + 10),
11 when shift > 15 else (16 - shift + 11),
12 when shift > 15 else (16 - shift + 12),
@@ -435,7 +442,7 @@ _mm_store_si128 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) {
}
@(enable_target_feature="sse2")
_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")
_mm_storel_epi64 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) {
@@ -1178,8 +1185,6 @@ foreign _ {
cvttsd2si :: proc(a: __m128d) -> i32 ---
@(link_name="llvm.x86.sse2.cvttps2dq")
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")
storeupd :: proc(mem_addr: rawptr, a: __m128d) ---
+9 -1
View File
@@ -244,7 +244,7 @@ Mode_Bits :: enum {
ISVTX = 9, // 0o0001000
ISGID = 10, // 0o0002000
ISUID = 11, // 0o0004000
IFFIFO = 12, // 0o0010000
IFIFO = 12, // 0o0010000
IFCHR = 13, // 0o0020000
IFDIR = 14, // 0o0040000
IFREG = 15, // 0o0100000
@@ -1815,3 +1815,11 @@ EPoll_Ctl_Opcode :: enum i32 {
DEL = 2,
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)
SIGRTMAX :: Signal(64)
S_IFMT :: Mode{.IFREG, .IFDIR, .IFCHR, .IFFIFO}
S_IFMT :: Mode{.IFREG, .IFDIR, .IFCHR, .IFIFO}
S_IFSOCK :: Mode{.IFREG, .IFDIR}
S_IFLNK :: Mode{.IFREG, .IFCHR}
S_IFBLK :: Mode{.IFDIR, .IFCHR}
S_IFFIFO :: Mode{.IFFIFO}
S_IFIFO :: Mode{.IFIFO}
S_IFCHR :: Mode{.IFCHR}
S_IFDIR :: Mode{.IFDIR}
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).
*/
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.
+31 -28
View File
@@ -12,7 +12,7 @@ import "base:intrinsics"
@(private)
syscall0 :: #force_inline proc "contextless" (nr: uintptr) -> int {
return cast(int) intrinsics.syscall(nr)
return int(intrinsics.syscall(nr))
}
@(private)
@@ -20,7 +20,7 @@ syscall1 :: #force_inline proc "contextless" (nr: uintptr, p1: $T) -> int
where
size_of(p1) <= size_of(uintptr)
{
return cast(int) intrinsics.syscall(nr, cast(uintptr) p1)
return int(intrinsics.syscall(nr, uintptr(p1)))
}
@(private)
@@ -29,8 +29,7 @@ where
size_of(p1) <= size_of(uintptr),
size_of(p2) <= size_of(uintptr)
{
return cast(int) intrinsics.syscall(nr,
cast(uintptr) p1, cast(uintptr) p2)
return int(intrinsics.syscall(nr, uintptr(p1), uintptr(p2)))
}
@(private)
@@ -40,10 +39,11 @@ where
size_of(p2) <= size_of(uintptr),
size_of(p3) <= size_of(uintptr)
{
return cast(int) intrinsics.syscall(nr,
cast(uintptr) p1,
cast(uintptr) p2,
cast(uintptr) p3)
return int(intrinsics.syscall(nr,
uintptr(p1),
uintptr(p2),
uintptr(p3),
))
}
@(private)
@@ -54,11 +54,12 @@ where
size_of(p3) <= size_of(uintptr),
size_of(p4) <= size_of(uintptr)
{
return cast(int) intrinsics.syscall(nr,
cast(uintptr) p1,
cast(uintptr) p2,
cast(uintptr) p3,
cast(uintptr) p4)
return int(intrinsics.syscall(nr,
uintptr(p1),
uintptr(p2),
uintptr(p3),
uintptr(p4),
))
}
@(private)
@@ -70,12 +71,13 @@ where
size_of(p4) <= size_of(uintptr),
size_of(p5) <= size_of(uintptr)
{
return cast(int) intrinsics.syscall(nr,
cast(uintptr) p1,
cast(uintptr) p2,
cast(uintptr) p3,
cast(uintptr) p4,
cast(uintptr) p5)
return int(intrinsics.syscall(nr,
uintptr(p1),
uintptr(p2),
uintptr(p3),
uintptr(p4),
uintptr(p5),
))
}
@(private)
@@ -88,13 +90,14 @@ where
size_of(p5) <= size_of(uintptr),
size_of(p6) <= size_of(uintptr)
{
return cast(int) intrinsics.syscall(nr,
cast(uintptr) p1,
cast(uintptr) p2,
cast(uintptr) p3,
cast(uintptr) p4,
cast(uintptr) p5,
cast(uintptr) p6)
return int(intrinsics.syscall(nr,
uintptr(p1),
uintptr(p2),
uintptr(p3),
uintptr(p4),
uintptr(p5),
uintptr(p6),
))
}
syscall :: proc {syscall0, syscall1, syscall2, syscall3, syscall4, syscall5, syscall6}
@@ -113,7 +116,7 @@ where
default_value: T
return default_value, Errno(-ret)
} 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
return default_value, Errno(-ret)
} else {
return cast(T) ret, Errno(.NONE)
return T(ret), Errno(.NONE)
}
}
+3 -7
View File
@@ -749,17 +749,13 @@ getsockopt :: proc {
getsockopt_base,
}
// TODO(flysand): clone (probably not in this PR, maybe not ever)
/*
Creates a copy of the running process.
Available since Linux 1.0.
*/
fork :: proc "contextless" () -> (Pid, Errno) {
when ODIN_ARCH == .arm64 {
// Note(flysand): this syscall is not documented, but the bottom 8 bits of flags
// are for exit signal
ret := syscall(SYS_clone, Signal.SIGCHLD)
ret := syscall(SYS_clone, u64(Signal.SIGCHLD), cast(rawptr) nil, cast(rawptr) nil, cast(rawptr) nil, u64(0))
return errno_unwrap(ret, Pid)
} else {
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)
return Errno(-ret)
} 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)
}
}
@@ -2818,7 +2814,7 @@ getrandom :: proc "contextless" (buf: []u8, flags: Get_Random_Flags) -> (int, Er
Execute program relative to a directory file descriptor.
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)
return Errno(-ret)
}
+6 -1
View File
@@ -688,7 +688,7 @@ Sock_Addr_In6 :: struct #packed {
}
/*
Struct representing Unix Domain Socket address
Struct representing Unix Domain Socket address
*/
Sock_Addr_Un :: struct #packed {
sun_family: Address_Family,
@@ -1303,3 +1303,8 @@ EPoll_Event :: struct #packed {
events: EPoll_Event_Kind,
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")
foreign ntdll_lib {
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 ---
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 {
@@ -69,6 +73,8 @@ ABE_BOTTOM :: 3
KNOWNFOLDERID :: GUID
REFKNOWNFOLDERID :: ^KNOWNFOLDERID
HDROP :: HANDLE
KNOWN_FOLDER_FLAG :: enum u32 {
DEFAULT = 0x00000000,
+50 -30
View File
@@ -1131,16 +1131,28 @@ TRACKMOUSEEVENT :: struct {
}
WIN32_FIND_DATAW :: struct {
dwFileAttributes: DWORD,
ftCreationTime: FILETIME,
ftLastAccessTime: FILETIME,
ftLastWriteTime: FILETIME,
nFileSizeHigh: DWORD,
nFileSizeLow: DWORD,
dwReserved0: DWORD,
dwReserved1: DWORD,
cFileName: [260]wchar_t, // #define MAX_PATH 260
cAlternateFileName: [14]wchar_t,
dwFileAttributes: DWORD,
ftCreationTime: FILETIME,
ftLastAccessTime: FILETIME,
ftLastWriteTime: FILETIME,
nFileSizeHigh: DWORD,
nFileSizeLow: DWORD,
dwReserved0: DWORD,
dwReserved1: DWORD,
cFileName: [MAX_PATH]WCHAR,
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 {
@@ -1196,6 +1208,11 @@ NMHDR :: struct {
code: UINT, // NM_ code
}
NCCALCSIZE_PARAMS :: struct {
rgrc: [3]RECT,
lppos: PWINDOWPOS,
}
// Generic WM_NOTIFY notification codes
NM_OUTOFMEMORY :: ~uintptr(0) // -1
NM_CLICK :: NM_OUTOFMEMORY-1 // uses NMCLICK struct
@@ -2318,6 +2335,7 @@ FILE_TYPE_PIPE :: 0x0003
RECT :: struct {left, top, right, bottom: LONG}
POINT :: struct {x, y: LONG}
PWINDOWPOS :: ^WINDOWPOS
WINDOWPOS :: struct {
hwnd: HWND,
hwndInsertAfter: HWND,
@@ -2549,6 +2567,7 @@ CLSCTX_RESERVED6 :: 0x1000000
CLSCTX_ACTIVATE_ARM32_SERVER :: 0x2000000
CLSCTX_ALLOW_LOWER_TRUST_REGISTRATION :: 0x4000000
CLSCTX_PS_DLL :: 0x80000000
CLSCTX_ALL :: CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER
WSAPROTOCOLCHAIN :: struct {
ChainLen: c_int,
@@ -2608,10 +2627,11 @@ OBJECT_ATTRIBUTES :: struct {
SecurityQualityOfService: rawptr,
}
PUNICODE_STRING :: ^UNICODE_STRING
UNICODE_STRING :: struct {
Length: u16,
MaximumLength: u16,
Buffer: ^u16,
Length: u16 `fmt:"-"`,
MaximumLength: u16 `fmt:"-"`,
Buffer: [^]u16 `fmt:"s,Length"`,
}
OVERLAPPED :: struct {
@@ -2822,41 +2842,41 @@ NEON128 :: struct {
EXCEPTION_POINTERS :: struct {
ExceptionRecord: ^EXCEPTION_RECORD,
ContextRecord: ^CONTEXT,
ContextRecord: ^CONTEXT,
}
PVECTORED_EXCEPTION_HANDLER :: #type proc "system" (ExceptionInfo: ^EXCEPTION_POINTERS) -> LONG
CONSOLE_READCONSOLE_CONTROL :: struct {
nLength: ULONG,
nInitialChars: ULONG,
dwCtrlWakeupMask: ULONG,
nLength: ULONG,
nInitialChars: ULONG,
dwCtrlWakeupMask: ULONG,
dwControlKeyState: ULONG,
}
PCONSOLE_READCONSOLE_CONTROL :: ^CONSOLE_READCONSOLE_CONTROL
BY_HANDLE_FILE_INFORMATION :: struct {
dwFileAttributes: DWORD,
ftCreationTime: FILETIME,
ftLastAccessTime: FILETIME,
ftLastWriteTime: FILETIME,
dwFileAttributes: DWORD,
ftCreationTime: FILETIME,
ftLastAccessTime: FILETIME,
ftLastWriteTime: FILETIME,
dwVolumeSerialNumber: DWORD,
nFileSizeHigh: DWORD,
nFileSizeLow: DWORD,
nNumberOfLinks: DWORD,
nFileIndexHigh: DWORD,
nFileIndexLow: DWORD,
nFileSizeHigh: DWORD,
nFileSizeLow: DWORD,
nNumberOfLinks: DWORD,
nFileIndexHigh: DWORD,
nFileIndexLow: DWORD,
}
LPBY_HANDLE_FILE_INFORMATION :: ^BY_HANDLE_FILE_INFORMATION
FILE_STANDARD_INFO :: struct {
AllocationSize: LARGE_INTEGER,
EndOfFile: LARGE_INTEGER,
NumberOfLinks: DWORD,
DeletePending: BOOLEAN,
Directory: BOOLEAN,
EndOfFile: LARGE_INTEGER,
NumberOfLinks: DWORD,
DeletePending: BOOLEAN,
Directory: BOOLEAN,
}
FILE_ATTRIBUTE_TAG_INFO :: struct {
+47 -3
View File
@@ -6,6 +6,7 @@ import "base:runtime"
import "core:bytes"
import "core:encoding/ansi"
@require import "core:encoding/base64"
@require import "core:encoding/json"
import "core:fmt"
import "core:io"
@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")
// Show only the most necessary logging information.
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 {
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) {
for i := len(t.cleanups)-1; i >= 0; i -= 1 {
#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]
pkg.frame_ready = false
fmt.assertf(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.",
found := thread.pool_stop_task(&pool, test_index)
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)
// 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))
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
}
+242 -39
View File
@@ -6,12 +6,26 @@ import "base:intrinsics"
_ :: intrinsics
/*
Value, specifying whether `core:thread` functionality is available on the
current platform.
*/
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)
/*
Maximum number of user arguments for polymorphic thread procedures.
*/
MAX_USER_ARGUMENTS :: 8
/*
Type representing the state/flags of the thread.
*/
Thread_State :: enum u8 {
Started,
Joined,
@@ -19,44 +33,48 @@ Thread_State :: enum u8 {
Self_Cleanup,
}
/*
Type representing a thread handle and the associated with that thread data.
*/
Thread :: struct {
using specific: Thread_Os_Specific,
flags: bit_set[Thread_State; u8],
id: int,
procedure: Thread_Proc,
/*
These are values that the user can set as they wish, after the thread has been created.
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,
user_index: int,
user_args: [MAX_USER_ARGUMENTS]rawptr,
/*
The context to be used as 'context' in the thread proc.
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 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.
In this situation, the thread will get a new temporary allocator which will be cleaned up when the thread dies.
***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,
then you'll need to call `runtime.default_temp_allocator_destroy(auto_cast the_thread.init_context.temp_allocator.data)` manually,
in order to prevent any memory leaks.
This call ***must*** be done ***in the thread proc*** because the default temporary allocator uses thread local state!
*/
// Thread ID.
id: int,
// The thread procedure.
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
// it is started.
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-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,
// The thread context.
// 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.
//
// **IMPORTANT**:
// By default, the thread proc will get the same context as `main()` gets.
// In this situation, the thread will get a new temporary allocator which
// will be cleaned up when the thread dies. ***This does NOT happen when
// `init_context` field is initialized***.
//
// If `init_context` is initialized, and `temp_allocator` field is set to
// the default temp allocator, then `runtime.default_temp_allocator_destroy()`
// procedure needs to be called from the thread procedure, in order to prevent
// any memory leaks.
init_context: Maybe(runtime.Context),
// The allocator used to allocate data for the thread.
creation_allocator: mem.Allocator,
}
@@ -64,6 +82,9 @@ when IS_SUPPORTED {
#assert(size_of(Thread{}.user_index) == size_of(uintptr))
}
/*
Type representing priority of a thread.
*/
Thread_Priority :: enum {
Normal,
Low,
@@ -71,74 +92,178 @@ Thread_Priority :: enum {
}
/*
Creates a thread in a suspended state with the given priority.
To start the thread, call `thread.start()`.
Create a thread in a suspended state with the given priority.
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 {
return _create(procedure, priority)
}
/*
Wait for the thread to finish and free all data associated with it.
*/
destroy :: proc(thread: ^Thread) {
_destroy(thread)
}
/*
Start a suspended thread.
*/
start :: proc(thread: ^Thread) {
_start(thread)
}
/*
Check if the thread has finished work.
*/
is_done :: proc(thread: ^Thread) -> bool {
return _is_done(thread)
}
/*
Wait for the thread to finish work.
*/
join :: proc(thread: ^Thread) {
_join(thread)
}
/*
Wait for all threads to finish work.
*/
join_multiple :: proc(threads: ..^Thread) {
_join_multiple(..threads)
}
/*
Forcibly terminate a running thread.
*/
terminate :: proc(thread: ^Thread, exit_code: int) {
_terminate(thread, exit_code)
}
/*
Yield the execution of the current thread to another OS thread or process.
*/
yield :: proc() {
_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) {
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) {
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)
where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
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)
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)
}
/*
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)
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)
}
/*
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)
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)
}
/*
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 {
thread_proc :: proc(t: ^Thread) {
fn := cast(proc())t.data
@@ -154,9 +279,22 @@ create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil,
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 {
thread_proc :: proc(t: ^Thread) {
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
}
/*
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
where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
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
}
/*
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
where size_of(T1) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
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
}
/*
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
where size_of(T1) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
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)
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
where size_of(T1) + size_of(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
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)
// 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 {
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)
@@ -94,7 +97,6 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
// Set thread priority.
policy: i32
res: i32
when ODIN_OS != .Haiku && ODIN_OS != .NetBSD {
res = unix.pthread_attr_getschedpolicy(&attrs, &policy)
assert(res == 0)
+61 -5
View File
@@ -1,16 +1,46 @@
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
/*
*/
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}
/*
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}
/*
Minimum value for an ordinal
*/
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)
/*
Possible errors returned by datetime functions.
*/
Error :: enum {
None,
Invalid_Year,
@@ -24,12 +54,22 @@ Error :: enum {
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 {
year: i64,
month: i8,
day: i8,
}
/*
A type representing a time within a single day within a nanosecond precision.
*/
Time :: struct {
hour: i8,
minute: i8,
@@ -37,17 +77,30 @@ Time :: struct {
nano: i32,
}
/*
A type representing datetime.
*/
DateTime :: struct {
using date: Date,
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 {
days: i64, // These are all i64 because we can also use it to add a number of seconds or nanos to a moment,
seconds: i64, // that are then normalized within their respective ranges.
days: i64,
seconds: i64,
nanos: i64,
}
/*
Type representing one of the months.
*/
Month :: enum i8 {
January = 1,
February,
@@ -63,6 +116,9 @@ Month :: enum i8 {
December,
}
/*
Type representing one of the weekdays.
*/
Weekday :: enum i8 {
Sunday = 0,
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
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) {
validate(date) or_return
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) {
validate(year, month, day) or_return
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) {
validate(ordinal) or_return
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) {
validate(year, month, day) or_return
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) {
validate(hour, minute, second, nanos) or_return
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) {
date := components_to_date(year, month, day) or_return
time := components_to_time(hour, minute, second, nanos) or_return
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) {
d := ordinal_to_date(ordinal) or_return
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) {
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) {
ord_a := date_to_ordinal(a) 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
}
/*
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) {
ord_a := date_to_ordinal(a) 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
}
/*
Calculate a difference between two deltas.
*/
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 = normalize_delta(delta) or_return
return
}
/*
Calculate a difference between two datetimes, dates or 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) {
ord := date_to_ordinal(a) or_return
ord += days
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) {
ord := date_to_ordinal(a) or_return
// 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)
}
/*
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) {
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
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}
/*
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) {
validate(date) or_return
@@ -120,6 +227,13 @@ day_number :: proc "contextless" (date: Date) -> (day_number: i64, err: Error) {
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) {
// Alternative formulation `day_number` subtracted from 365 or 366 depending on leap year
validate(date) or_return
@@ -127,6 +241,12 @@ days_remaining :: proc "contextless" (date: Date) -> (days_remaining: i64, err:
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) {
// 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
}
/*
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) {
validate(year, 1, 1) or_return
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) {
validate(year, 12, 31) or_return
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) {
is_leap := is_leap_year(year)
@@ -171,6 +308,15 @@ year_range :: proc (#any_int year: i64, allocator := context.allocator) -> (rang
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) {
// Distribute nanos into seconds and remainder
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.
// 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) {
year_minus_one := date.year - 1
@@ -223,6 +375,12 @@ unsafe_date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal)
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) {
// Days after epoch
d0 := ordinal - EPOCH
@@ -253,6 +411,12 @@ unsafe_ordinal_to_year :: proc "contextless" (ordinal: Ordinal) -> (year: i64, d
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) {
year, _ := unsafe_ordinal_to_year(ordinal)
+1
View File
@@ -1,3 +1,4 @@
//+private
package datetime
// Internal helper functions for calendrical conversions
+43 -1
View File
@@ -1,14 +1,29 @@
package datetime
// Validation helpers
/*
Check if a year is a leap year.
*/
is_leap_year :: proc "contextless" (#any_int year: i64) -> (leap: bool) {
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) {
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) {
if year < MIN_DATE.year || year > MAX_DATE.year {
return .Invalid_Year
@@ -29,6 +44,12 @@ validate_year_month_day :: proc "contextless" (#any_int year, #any_int month, #a
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) {
if ordinal < MIN_ORD || ordinal > MAX_ORD {
return .Invalid_Ordinal
@@ -36,10 +57,22 @@ validate_ordinal :: proc "contextless" (ordinal: Ordinal) -> (err: Error) {
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) {
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) {
if hour < 0 || hour > 23 {
return .Invalid_Hour
@@ -56,12 +89,21 @@ validate_hour_minute_second :: proc "contextless" (#any_int hour, #any_int minut
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.date) or_return
validate(datetime.time) or_return
return .None
}
/*
Check for errors in date, time or datetime.
*/
validate :: proc{
validate_date,
validate_year_month_day,
+75 -13
View File
@@ -3,23 +3,62 @@ package time
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.
// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second.
// Leap seconds are smeared into 23:59:59.
/*
Parse an ISO 8601 string into a time with UTC offset applied to it.
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) {
offset: int
res, offset, consumed = iso8601_to_time_and_offset(iso_datetime, is_leap)
res._nsec += (i64(-offset) * i64(Minute))
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
// Note: Only 4-digit years are accepted.
// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second.
// Leap seconds are smeared into 23:59:59.
/*
Parse an ISO 8601 string into a time and a UTC offset in minutes.
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, 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) {
moment, offset, leap_second, count := iso8601_to_components(iso_datetime)
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
// Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given
/*
Parse an ISO 8601 string into a datetime and a UTC offset in minutes.
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) {
moment, offset, count, leap_second, ok := _iso8601_to_components(iso_datetime)
if !ok {
+81 -11
View File
@@ -3,18 +3,39 @@ package time
import "base:runtime"
import "base:intrinsics"
/*
Type representing monotonic time, useful for measuring durations.
*/
Tick :: struct {
_nsec: i64, // relative amount
}
/*
Obtain the current tick.
*/
tick_now :: proc "contextless" () -> Tick {
return _tick_now()
}
/*
Obtain the difference between ticks.
*/
tick_diff :: proc "contextless" (start, end: Tick) -> Duration {
d := end._nsec - start._nsec
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 {
d: Duration
t := tick_now()
@@ -25,17 +46,21 @@ tick_lap_time :: proc "contextless" (prev: ^Tick) -> Duration {
return d
}
/*
Obtain the duration since last tick.
*/
tick_since :: proc "contextless" (start: Tick) -> Duration {
return tick_diff(start, tick_now())
}
/*
Capture the duration the code in the current scope takes to execute.
*/
@(deferred_in_out=_tick_duration_end)
SCOPED_TICK_DURATION :: proc "contextless" (d: ^Duration) -> Tick {
return tick_now()
}
_tick_duration_end :: proc "contextless" (d: ^Duration, t: Tick) {
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 {
when ODIN_ARCH == .amd64 {
return x86_has_invariant_tsc()
@@ -70,6 +102,17 @@ has_invariant_tsc :: proc "contextless" () -> bool {
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) {
if !has_invariant_tsc() {
return 0, false
@@ -93,37 +136,64 @@ tsc_frequency :: proc "contextless" (fallback_sleep := 2 * Second) -> (u64, bool
return hz, true
}
/*
Benchmark helpers
*/
// Benchmark helpers
/*
Errors returned by the `benchmark()` procedure.
*/
Benchmark_Error :: enum {
Okay = 0,
Allocation_Error,
}
/*
Options for benchmarking.
*/
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),
// The procedure to benchmark.
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),
// Field to be used by `bench()` procedure for any purpose.
rounds: int,
// Field to be used by `bench()` procedure for any purpose.
bytes: int,
// Field to be used by `bench()` procedure for any purpose.
input: []u8,
// `bench()` writes to specify the count of elements processed.
count: int,
// `bench()` writes to specify the number of bytes processed.
processed: int,
// `bench()` can write the output slice here.
output: []u8, // Unused for hash benchmarks
// `bench()` can write the output hash here.
hash: u128,
/*
Performance
*/
// `benchmark()` procedure will output the duration of benchmark
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,
// `benchmark()` procedure will output the average number of megabytes
// processed per second, using the `processed` field of this struct.
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) {
assert(options != nil)
assert(options.bench != nil)
+80 -12
View File
@@ -4,10 +4,33 @@ package time
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.
// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second.
// Leap seconds are smeared into 23:59:59.
/*
Parse an RFC 3339 string into time with a UTC offset applied to it.
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) {
offset: int
@@ -16,11 +39,34 @@ rfc3339_to_time_utc :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res:
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
// Note: Only 4-digit years are accepted.
// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second.
// Leap seconds are smeared into 23:59:59.
/*
Parse an RFC 3339 string into a time and a UTC offset in minutes.
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, 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) {
moment, offset, leap_second, count := rfc3339_to_components(rfc_datetime)
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
// Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given
/*
Parse an RFC 3339 string into a datetime and a UTC offset in minutes.
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) {
moment, offset, count, leap_second, ok := _rfc3339_to_components(rfc_datetime)
if !ok {
+277 -8
View File
@@ -3,24 +3,73 @@ package time
import "base:intrinsics"
import dt "core:time/datetime"
/*
Type representing duration, with nanosecond precision.
This is the regular Unix timestamp, scaled to nanosecond precision.
*/
Duration :: distinct i64
/*
The duration equal to one nanosecond (1e-9 seconds).
*/
Nanosecond :: Duration(1)
/*
The duration equal to one microsecond (1e-6 seconds).
*/
Microsecond :: 1000 * Nanosecond
/*
The duration equal to one millisecond (1e-3 seconds).
*/
Millisecond :: 1000 * Microsecond
/*
The duration equal to one second.
*/
Second :: 1000 * Millisecond
/*
The duration equal to one minute (60 seconds).
*/
Minute :: 60 * Second
/*
The duration equal to one hour (3600 seconds).
*/
Hour :: 60 * Minute
/*
Minimum representable duration.
*/
MIN_DURATION :: Duration(-1 << 63)
/*
Maximum representable duration.
*/
MAX_DURATION :: Duration(1<<63 - 1)
/*
Value specifying whether the time procedures are supported by the current
platform.
*/
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 {
_nsec: i64, // Measured in UNIX nanonseconds
}
/*
Type representing a month.
*/
Month :: enum int {
January = 1,
February,
@@ -36,6 +85,9 @@ Month :: enum int {
December,
}
/*
Type representing a weekday.
*/
Weekday :: enum int {
Sunday = 0,
Monday,
@@ -46,20 +98,37 @@ Weekday :: enum int {
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 {
running: bool,
_start_time: Tick,
_accumulation: Duration,
}
/*
Obtain the current time.
*/
now :: proc "contextless" () -> Time {
return _now()
}
/*
Sleep for the specified duration.
*/
sleep :: proc "contextless" (d: Duration) {
_sleep(d)
}
/*
Start the stopwatch.
*/
stopwatch_start :: proc "contextless" (stopwatch: ^Stopwatch) {
if !stopwatch.running {
stopwatch._start_time = tick_now()
@@ -67,6 +136,9 @@ stopwatch_start :: proc "contextless" (stopwatch: ^Stopwatch) {
}
}
/*
Stop the stopwatch.
*/
stopwatch_stop :: proc "contextless" (stopwatch: ^Stopwatch) {
if stopwatch.running {
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._accumulation = {}
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 {
if !stopwatch.running {
return stopwatch._accumulation
@@ -86,40 +168,92 @@ stopwatch_duration :: proc "contextless" (stopwatch: Stopwatch) -> Duration {
return stopwatch._accumulation + tick_diff(stopwatch._start_time, tick_now())
}
/*
Calculate the duration elapsed between two times.
*/
diff :: proc "contextless" (start, end: Time) -> Duration {
d := end._nsec - start._nsec
return Duration(d)
}
/*
Calculate the duration elapsed since a specific time.
*/
since :: proc "contextless" (start: Time) -> Duration {
return diff(start, now())
}
/*
Obtain the number of nanoseconds in a duration.
*/
duration_nanoseconds :: proc "contextless" (d: Duration) -> i64 {
return i64(d)
}
/*
Obtain the number of microseconds in a duration.
*/
duration_microseconds :: proc "contextless" (d: Duration) -> f64 {
return duration_seconds(d) * 1e6
}
/*
Obtain the number of milliseconds in a duration.
*/
duration_milliseconds :: proc "contextless" (d: Duration) -> f64 {
return duration_seconds(d) * 1e3
}
/*
Obtain the number of seconds in a duration.
*/
duration_seconds :: proc "contextless" (d: Duration) -> f64 {
sec := d / Second
nsec := d % Second
return f64(sec) + f64(nsec)/1e9
}
/*
Obtain the number of minutes in a duration.
*/
duration_minutes :: proc "contextless" (d: Duration) -> f64 {
min := d / Minute
nsec := d % Minute
return f64(min) + f64(nsec)/(60*1e9)
}
/*
Obtain the number of hours in a duration.
*/
duration_hours :: proc "contextless" (d: Duration) -> f64 {
hour := d / Hour
nsec := d % Hour
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 {
_less_than_half :: #force_inline proc "contextless" (x, y: Duration) -> bool {
return u64(x)+u64(x) < u64(y)
@@ -149,50 +283,103 @@ duration_round :: proc "contextless" (d, m: Duration) -> 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 {
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) {
year, month, day, _ = _abs_date(_time_abs(t), true)
return
}
/*
Obtain the year of the date specified by time.
*/
year :: proc "contextless" (t: Time) -> (year: int) {
year, _, _, _ = _date(t, true)
return
}
/*
Obtain the month of the date specified by time.
*/
month :: proc "contextless" (t: Time) -> (month: Month) {
_, month, _, _ = _date(t, true)
return
}
/*
Obtain the day of the date specified by time.
*/
day :: proc "contextless" (t: Time) -> (day: int) {
_, _, day, _ = _date(t, true)
return
}
/*
Obtain the week day of the date specified by time.
*/
weekday :: proc "contextless" (t: Time) -> (weekday: Weekday) {
abs := _time_abs(t)
sec := (abs + u64(Weekday.Monday) * SECONDS_PER_DAY) % SECONDS_PER_WEEK
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 }
/*
Obtain the time components from a time.
*/
clock_from_time :: proc "contextless" (t: Time) -> (hour, min, sec: int) {
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) {
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) {
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) {
sec = int(nsec % SECONDS_PER_DAY)
hour = sec / SECONDS_PER_HOUR
@@ -202,10 +389,16 @@ clock_from_seconds :: proc "contextless" (nsec: u64) -> (hour, min, sec: int) {
return
}
/*
Read the timestamp counter of the CPU.
*/
read_cycle_counter :: proc "contextless" () -> u64 {
return u64(intrinsics.read_cycle_counter())
}
/*
Obtain time from unix seconds and unix nanoseconds.
*/
unix :: proc "contextless" (sec: i64, nsec: i64) -> Time {
sec, nsec := sec, nsec
if nsec < 0 || nsec >= 1e9 {
@@ -220,31 +413,59 @@ unix :: proc "contextless" (sec: i64, nsec: i64) -> Time {
return Time{(sec*1e9 + nsec)}
}
/*
Obtain time from unix nanoseconds.
*/
from_nanoseconds :: #force_inline proc "contextless" (nsec: i64) -> Time {
return Time{nsec}
}
/*
Alias for `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 {
return t._nsec/1e9
}
/*
Alias for `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 {
return t._nsec
}
/*
Add duration to a time.
*/
time_add :: proc "contextless" (t: Time, d: Duration) -> Time {
return Time{t._nsec + i64(d)}
}
// Accurate sleep borrowed from: https://blat-blatnik.github.io/computerBear/making-accurate-sleep-function/
//
// 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.
// 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
This procedure sleeps for the duration specified by `d`, very accurately.
**Note**: Implementation borrowed from: [this source](https://blat-blatnik.github.io/computerBear/making-accurate-sleep-function/)
**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) {
to_sleep, estimate, mean, m2, count: Duration
@@ -362,6 +583,13 @@ _abs_date :: proc "contextless" (abs: u64, full: bool) -> (year: int, month: Mon
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) {
this_date, err := dt.components_to_datetime(year, month, day, hour, minute, second, nsec)
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)
}
/*
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) {
unix_epoch := dt.DateTime{{1970, 1, 1}, {0, 0, 0, 0}}
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
}
/*
Convert datetime components into 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) {
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)
days_before := [?]i32{
0,
@@ -410,11 +653,37 @@ days_before := [?]i32{
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
/*
Number of seconds in an hour (without leap seconds).
*/
SECONDS_PER_HOUR :: 60 * SECONDS_PER_MINUTE
/*
Number of seconds in a day (without leap seconds).
*/
SECONDS_PER_DAY :: 24 * SECONDS_PER_HOUR
/*
Number of seconds in a week (without leap seconds).
*/
SECONDS_PER_WEEK :: 7 * SECONDS_PER_DAY
/*
Days in 400 years, with leap days.
*/
DAYS_PER_400_YEARS :: 365*400 + 97
/*
Days in 100 years, with leap days.
*/
DAYS_PER_100_YEARS :: 365*100 + 24
/*
Days in 4 years, with leap days.
*/
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 endian "core:encoding/endian"
import hxa "core:encoding/hxa"
import ini "core:encoding/ini"
import json "core:encoding/json"
import varint "core:encoding/varint"
import xml "core:encoding/xml"
@@ -193,6 +194,7 @@ _ :: base32
_ :: base64
_ :: csv
_ :: hxa
_ :: ini
_ :: json
_ :: varint
_ :: xml
+16 -1
View File
@@ -440,6 +440,8 @@ struct BuildContext {
bool cached;
BuildCacheData build_cache_data;
bool internal_no_inline;
bool no_threaded_checker;
bool show_debug_messages;
@@ -1649,11 +1651,24 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta
if (!bc->custom_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
bc->optimization_level = -1; // -o:none
if (bc->ODIN_DEBUG) {
bc->optimization_level = -1; // -o:none
} else {
bc->optimization_level = 0; // -o:minimal
}
}
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.
if (bc->metrics.os != TargetOs_windows && bc->metrics.arch == TargetArch_amd64) {
// 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 filename[MAX_PATH] = {};
wcscpy(dir_path, wpath_c);
wcscat(dir_path, L"\\*");
wcscpy_s(dir_path, wpath_c);
wcscat_s(dir_path, L"\\*");
wcscpy(filename, wpath_c);
wcscat(filename, L"\\");
wcscpy_s(filename, wpath_c);
wcscat_s(filename, L"\\");
WIN32_FIND_DATAW find_file_data = {};
@@ -31,21 +31,21 @@ gb_internal bool recursively_delete_directory(wchar_t *wpath_c) {
}
defer (FindClose(hfind));
wcscpy(dir_path, filename);
wcscpy_s(dir_path, filename);
for (;;) {
if (FindNextFileW(hfind, &find_file_data)) {
if (is_dots_w(find_file_data.cFileName)) {
continue;
}
wcscat(filename, find_file_data.cFileName);
wcscat_s(filename, find_file_data.cFileName);
if (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if (!recursively_delete_directory(filename)) {
return false;
}
RemoveDirectoryW(filename);
wcscpy(filename, dir_path);
wcscpy_s(filename, dir_path);
} else {
if (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
_wchmod(filename, _S_IWRITE);
@@ -53,7 +53,7 @@ gb_internal bool recursively_delete_directory(wchar_t *wpath_c) {
if (!DeleteFileW(filename)) {
return false;
}
wcscpy(filename, dir_path);
wcscpy_s(filename, dir_path);
}
} else {
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;
}
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(bd, BasicDirective, ce->proc);
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;
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);
mutex_lock(&c->info->load_file_mutex);
defer (mutex_unlock(&c->info->load_file_mutex));
for (FileInfo fi : list) {
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);
} else {
result = LoadDirective_Error;
@@ -4298,6 +4302,49 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
}
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:
{
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->aliased_of = new_entity;
Ast *empty_ident = nullptr;
original_entity->identifier.compare_exchange_strong(empty_ident, new_entity->identifier);
original_entity->identifier.store(new_entity->identifier);
if (original_entity->identifier.load() != nullptr &&
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);
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;
}
+33 -8
View File
@@ -500,7 +500,9 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E
nctx.no_polymorphic_errors = false;
// 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
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];
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 {
dummy_argument_count += 1;
o.type = t_untyped_nil;
@@ -7888,12 +7906,15 @@ 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.
if (is_call_inlined) {
GB_ASSERT(c->curr_proc_decl);
GB_ASSERT(c->curr_proc_decl->entity);
GB_ASSERT(c->curr_proc_decl->entity->type->kind == Type_Proc);
String scope_features = c->curr_proc_decl->entity->type->Proc.enable_target_feature;
if (!check_target_feature_is_superset_of(scope_features, pt->Proc.enable_target_feature, &invalid)) {
error(call, "Inlined procedure enables target feature '%.*s', this requires the calling procedure to at least enable the same feature", LIT(invalid));
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->type->kind == Type_Proc);
String scope_features = c->curr_proc_decl->entity->type->Proc.enable_target_feature;
if (!check_target_feature_is_superset_of(scope_features, pt->Proc.enable_target_feature, &invalid)) {
error(call, "Inlined procedure enables target feature '%.*s', this requires the calling procedure to at least enable the same feature", LIT(invalid));
}
}
}
}
@@ -9926,10 +9947,14 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast *
}
Type *et = base_type(t->BitSet.elem);
isize field_count = 0;
if (et->kind == Type_Enum) {
if (et != nullptr && et->kind == Type_Enum) {
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) {
error(cl->elems[0], "'field = value' in a bit_set a literal is not allowed");
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) {
check_expr(ctx, &x, ss->tag);
check_assignment(ctx, &x, nullptr, str_lit("switch expression"));
if (x.type == nullptr) {
return;
}
} else {
x.mode = Addressing_Constant;
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) {
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]);
defer (gb_string_free(v));
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);
if (is_entity_local_variable(e)) {
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");
} else if (x->kind == Ast_IndexExpr) {
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);
}
}
} 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)) {
ERROR_BLOCK();
unsafe_return_error(o, "a compound literal of a slice");
+69 -20
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;
}
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) {
ast_node(bf, BitFieldType, node);
@@ -1268,11 +1252,14 @@ gb_internal void check_bit_set_type(CheckerContext *c, Type *type, Type *named_t
Type *t = default_type(lhs.type);
if (bs->underlying != nullptr) {
Type *u = check_type(c, bs->underlying);
// if (!is_valid_bit_field_backing_type(u)) {
if (!is_type_integer(u)) {
gbString ts = type_to_string(u);
error(bs->underlying, "Expected an underlying integer for the bit set, got %s", ts);
gb_string_free(ts);
return;
if (!is_valid_bit_field_backing_type(u)) {
return;
}
}
type->BitSet.underlying = u;
}
@@ -1572,11 +1559,30 @@ gb_internal Type *determine_type_from_polymorphic(CheckerContext *ctx, Type *pol
return poly_type;
}
if (show_error) {
ERROR_BLOCK();
gbString pts = type_to_string(poly_type);
gbString ots = type_to_string(operand.type, true);
defer (gb_string_free(pts));
defer (gb_string_free(ots));
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;
}
@@ -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");
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->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
}
}
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 (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");
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])) {
// failed
@@ -2091,6 +2128,8 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para
param->flags |= EntityFlag_Ellipsis;
if (is_c_vararg) {
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) {
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
add_entity(ctx, scope, name, param);
@@ -2430,9 +2473,15 @@ gb_internal i64 check_array_count(CheckerContext *ctx, Operand *o, Ast *e) {
if (e == nullptr) {
return 0;
}
if (e->kind == Ast_UnaryExpr &&
e->UnaryExpr.op.kind == Token_Question) {
return -1;
if (e->kind == Ast_UnaryExpr) {
Token op = e->UnaryExpr.op;
if (op.kind == Token_Question) {
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);
+23 -18
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);
}
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) {
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_zero_item(d);
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->type_info_deps, 0);
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) {
@@ -381,6 +372,7 @@ gb_internal Entity *scope_lookup_current(Scope *s, String const &name) {
return nullptr;
}
gb_internal void scope_lookup_parent(Scope *scope, String const &name, Scope **scope_, Entity **entity_) {
if (scope != nullptr) {
bool gone_thru_proc = false;
@@ -508,9 +500,15 @@ end:;
return result;
}
gb_global bool in_single_threaded_checker_stage = false;
gb_internal Entity *scope_insert(Scope *s, Entity *entity) {
String name = entity->token.string;
return scope_insert_with_name(s, name, entity);
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);
}
}
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->entity = e;
ve->other = shadowed;
@@ -674,7 +672,7 @@ gb_internal bool check_vet_unused(Checker *c, Entity *e, VettedEntity *ve) {
}
case Entity_ImportName:
case Entity_LibraryName:
zero_item(ve);
gb_zero_item(ve);
ve->kind = VettedEntity_Unused;
ve->entity = e;
return true;
@@ -1114,7 +1112,11 @@ gb_internal void init_universal(void) {
int minimum_os_version = 0;
if (build_context.minimum_os_version_string != "") {
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);
#endif
minimum_os_version = (major*10000)+(minor*100)+revision;
}
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;
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->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) {
return;
}
Ast *empty_ident = nullptr;
entity->identifier.compare_exchange_strong(empty_ident, identifier);
entity->identifier.store(identifier);
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) {
in_single_threaded_checker_stage = true;
// NOTE(bill): This must be single threaded
// Don't bother trying
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);
}
}
in_single_threaded_checker_stage = false;
}
+9
View File
@@ -181,6 +181,11 @@ char const *ProcCheckedState_strings[ProcCheckedState_COUNT] {
"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
struct DeclInfo {
DeclInfo * parent; // NOTE(bill): only used for procedure literals at the moment
@@ -219,6 +224,10 @@ struct DeclInfo {
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
struct lbModule *code_gen_module;
};

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