mirror of
https://github.com/Ed94/Odin.git
synced 2026-06-17 11:22:22 -07:00
Merge remote-tracking branch 'upstream/master' into prototype-fmt
This commit is contained in:
@@ -135,6 +135,22 @@ buffer_grow :: proc(b: ^Buffer, n: int) {
|
||||
resize(&b.buf, m);
|
||||
}
|
||||
|
||||
buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.Error) {
|
||||
b.last_read = .Invalid;
|
||||
if offset < 0 {
|
||||
err = .Invalid_Offset;
|
||||
return;
|
||||
}
|
||||
_, ok := _buffer_try_grow(b, offset+len(p));
|
||||
if !ok {
|
||||
_ = _buffer_grow(b, offset+len(p));
|
||||
}
|
||||
if len(b.buf) <= offset {
|
||||
return 0, .Short_Write;
|
||||
}
|
||||
return copy(b.buf[offset:], p), nil;
|
||||
}
|
||||
|
||||
|
||||
buffer_write :: proc(b: ^Buffer, p: []byte) -> (n: int, err: io.Error) {
|
||||
b.last_read = .Invalid;
|
||||
@@ -213,6 +229,24 @@ buffer_read :: proc(b: ^Buffer, p: []byte) -> (n: int, err: io.Error) {
|
||||
return;
|
||||
}
|
||||
|
||||
buffer_read_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.Error) {
|
||||
b.last_read = .Invalid;
|
||||
|
||||
if offset < 0 || offset >= len(b.buf) {
|
||||
err = .Invalid_Offset;
|
||||
return;
|
||||
}
|
||||
|
||||
if 0 <= offset && offset < len(b.buf) {
|
||||
n = copy(p, b.buf[offset:]);
|
||||
}
|
||||
if n > 0 {
|
||||
b.last_read = .Read;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
buffer_read_byte :: proc(b: ^Buffer) -> (byte, io.Error) {
|
||||
if buffer_is_empty(b) {
|
||||
buffer_reset(b);
|
||||
@@ -346,6 +380,10 @@ _buffer_vtable := &io.Stream_VTable{
|
||||
b := (^Buffer)(s.stream_data);
|
||||
return buffer_read(b, p);
|
||||
},
|
||||
impl_read_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) {
|
||||
b := (^Buffer)(s.stream_data);
|
||||
return buffer_read_at(b, p, int(offset));
|
||||
},
|
||||
impl_read_byte = proc(s: io.Stream) -> (byte, io.Error) {
|
||||
b := (^Buffer)(s.stream_data);
|
||||
return buffer_read_byte(b);
|
||||
@@ -358,6 +396,10 @@ _buffer_vtable := &io.Stream_VTable{
|
||||
b := (^Buffer)(s.stream_data);
|
||||
return buffer_write(b, p);
|
||||
},
|
||||
impl_write_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) {
|
||||
b := (^Buffer)(s.stream_data);
|
||||
return buffer_write_at(b, p, int(offset));
|
||||
},
|
||||
impl_write_byte = proc(s: io.Stream, c: byte) -> io.Error {
|
||||
b := (^Buffer)(s.stream_data);
|
||||
return buffer_write_byte(b, c);
|
||||
|
||||
+19
-9
@@ -23,6 +23,9 @@ Error :: enum i32 {
|
||||
// Short_Write means that a write accepted fewer bytes than requested but failed to return an explicit error
|
||||
Short_Write,
|
||||
|
||||
// Invalid_Write means that a write returned an impossible count
|
||||
Invalid_Write,
|
||||
|
||||
// Short_Buffer means that a read required a longer buffer than was provided
|
||||
Short_Buffer,
|
||||
|
||||
@@ -40,6 +43,9 @@ Error :: enum i32 {
|
||||
Negative_Count,
|
||||
Buffer_Full,
|
||||
|
||||
// Unknown means that an error has occurred but cannot be categorized
|
||||
Unknown,
|
||||
|
||||
// Empty is returned when a procedure has not been implemented for an io.Stream
|
||||
Empty = -1,
|
||||
}
|
||||
@@ -212,17 +218,17 @@ read_at :: proc(r: Reader_At, p: []byte, offset: i64) -> (n: int, err: Error) {
|
||||
return 0, .Empty;
|
||||
}
|
||||
|
||||
current_offset: i64;
|
||||
current_offset, err = r->impl_seek(offset, .Current);
|
||||
curr_offset: i64;
|
||||
curr_offset, err = r->impl_seek(offset, .Current);
|
||||
if err != nil {
|
||||
return 0, err;
|
||||
}
|
||||
|
||||
n, err = r->impl_read(p);
|
||||
if err != nil {
|
||||
return;
|
||||
_, err1 := r->impl_seek(curr_offset, .Start);
|
||||
if err1 != nil && err == nil {
|
||||
err = err1;
|
||||
}
|
||||
_, err = r->impl_seek(current_offset, .Start);
|
||||
return;
|
||||
|
||||
}
|
||||
@@ -238,14 +244,18 @@ write_at :: proc(w: Writer_At, p: []byte, offset: i64) -> (n: int, err: Error) {
|
||||
return 0, .Empty;
|
||||
}
|
||||
|
||||
current_offset: i64;
|
||||
current_offset, err = w->impl_seek(offset, .Current);
|
||||
curr_offset: i64;
|
||||
curr_offset, err = w->impl_seek(offset, .Current);
|
||||
if err != nil {
|
||||
return 0, err;
|
||||
}
|
||||
defer w->impl_seek(current_offset, .Start);
|
||||
|
||||
return w->impl_write(p);
|
||||
n, err = w->impl_write(p);
|
||||
_, err1 := w->impl_seek(curr_offset, .Start);
|
||||
if err1 != nil && err == nil {
|
||||
err = err1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
write_to :: proc(r: Writer_To, w: Writer) -> (n: i64, err: Error) {
|
||||
|
||||
@@ -711,7 +711,8 @@ quaternion_nlerp_f16 :: proc(a, b: Quaternionf16, t: f16) -> (c: Quaternionf16)
|
||||
c.z = a.z + (b.z-a.z)*t;
|
||||
c.w = a.w + (b.w-a.w)*t;
|
||||
return normalize(c);
|
||||
}quaternion_nlerp_f32 :: proc(a, b: Quaternionf32, t: f32) -> (c: Quaternionf32) {
|
||||
}
|
||||
quaternion_nlerp_f32 :: proc(a, b: Quaternionf32, t: f32) -> (c: Quaternionf32) {
|
||||
c.x = a.x + (b.x-a.x)*t;
|
||||
c.y = a.y + (b.y-a.y)*t;
|
||||
c.z = a.z + (b.z-a.z)*t;
|
||||
@@ -758,7 +759,8 @@ quaternion_slerp_f16 :: proc(x, y: Quaternionf16, t: f16) -> (q: Quaternionf16)
|
||||
q.z = factor_a * a.z + factor_b * b.z;
|
||||
q.w = factor_a * a.w + factor_b * b.w;
|
||||
return;
|
||||
}quaternion_slerp_f32 :: proc(x, y: Quaternionf32, t: f32) -> (q: Quaternionf32) {
|
||||
}
|
||||
quaternion_slerp_f32 :: proc(x, y: Quaternionf32, t: f32) -> (q: Quaternionf32) {
|
||||
a, b := x, y;
|
||||
cos_angle := dot(a, b);
|
||||
if cos_angle < 0 {
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
package odin_doc_format
|
||||
|
||||
import "core:mem"
|
||||
|
||||
Array :: struct($T: typeid) {
|
||||
offset: u32le,
|
||||
length: u32le,
|
||||
}
|
||||
|
||||
String :: distinct Array(byte);
|
||||
|
||||
Version_Type_Major :: 0;
|
||||
Version_Type_Minor :: 1;
|
||||
Version_Type_Patch :: 0;
|
||||
|
||||
Version_Type :: struct {
|
||||
major, minor, patch: u8,
|
||||
_: u8,
|
||||
};
|
||||
|
||||
Version_Type_Default :: Version_Type{
|
||||
major=Version_Type_Major,
|
||||
minor=Version_Type_Minor,
|
||||
patch=Version_Type_Patch,
|
||||
};
|
||||
|
||||
Magic_String :: "odindoc\x00";
|
||||
|
||||
Header_Base :: struct {
|
||||
magic: [8]byte,
|
||||
_: u32le,
|
||||
version: Version_Type,
|
||||
total_size: u32le,
|
||||
header_size: u32le,
|
||||
hash: u32le,
|
||||
}
|
||||
|
||||
Header :: struct {
|
||||
using base: Header_Base,
|
||||
|
||||
// NOTE: These arrays reserve the zero element as a sentinel value
|
||||
files: Array(File),
|
||||
pkgs: Array(Pkg),
|
||||
entities: Array(Entity),
|
||||
types: Array(Type),
|
||||
}
|
||||
|
||||
File_Index :: distinct u32le;
|
||||
Pkg_Index :: distinct u32le;
|
||||
Entity_Index :: distinct u32le;
|
||||
Type_Index :: distinct u32le;
|
||||
|
||||
|
||||
Position :: struct {
|
||||
file: File_Index,
|
||||
line: u32le,
|
||||
column: u32le,
|
||||
offset: u32le,
|
||||
};
|
||||
|
||||
File :: struct {
|
||||
pkg: Pkg_Index,
|
||||
name: String,
|
||||
}
|
||||
|
||||
Pkg_Flag :: enum u32le {
|
||||
Builtin = 0,
|
||||
Runtime = 1,
|
||||
Init = 2,
|
||||
}
|
||||
|
||||
Pkg_Flags :: distinct bit_set[Pkg_Flag; u32le];
|
||||
|
||||
Pkg :: struct {
|
||||
fullpath: String,
|
||||
name: String,
|
||||
flags: Pkg_Flags,
|
||||
docs: String,
|
||||
files: Array(File_Index),
|
||||
entities: Array(Entity_Index),
|
||||
}
|
||||
|
||||
Entity_Kind :: enum u32le {
|
||||
Invalid = 0,
|
||||
Constant = 1,
|
||||
Variable = 2,
|
||||
Type_Name = 3,
|
||||
Procedure = 4,
|
||||
Proc_Group = 5,
|
||||
Import_Name = 6,
|
||||
Library_Name = 7,
|
||||
}
|
||||
|
||||
Entity_Flag :: enum u32le {
|
||||
Foreign = 0,
|
||||
Export = 1,
|
||||
|
||||
Param_Using = 2,
|
||||
Param_Const = 3,
|
||||
Param_Auto_Cast = 4,
|
||||
Param_Ellipsis = 5,
|
||||
Param_CVararg = 6,
|
||||
Param_No_Alias = 7,
|
||||
|
||||
Type_Alias = 8,
|
||||
|
||||
Var_Thread_Local = 9,
|
||||
}
|
||||
|
||||
Entity_Flags :: distinct bit_set[Entity_Flag; u32le];
|
||||
|
||||
Entity :: struct {
|
||||
kind: Entity_Kind,
|
||||
flags: Entity_Flags,
|
||||
pos: Position,
|
||||
name: String,
|
||||
type: Type_Index,
|
||||
init_string: String,
|
||||
_: u32le,
|
||||
comment: String,
|
||||
docs: String,
|
||||
foreign_library: Entity_Index,
|
||||
link_name: String,
|
||||
attributes: Array(Attribute),
|
||||
grouped_entities: Array(Entity_Index), // Procedure Groups
|
||||
where_clauses: Array(String), // Procedures
|
||||
}
|
||||
|
||||
Attribute :: struct {
|
||||
name: String,
|
||||
value: String,
|
||||
}
|
||||
|
||||
Type_Kind :: enum u32le {
|
||||
Invalid = 0,
|
||||
Basic = 1,
|
||||
Named = 2,
|
||||
Generic = 3,
|
||||
Pointer = 4,
|
||||
Array = 5,
|
||||
Enumerated_Array = 6,
|
||||
Slice = 7,
|
||||
Dynamic_Array = 8,
|
||||
Map = 9,
|
||||
Struct = 10,
|
||||
Union = 11,
|
||||
Enum = 12,
|
||||
Tuple = 13,
|
||||
Proc = 14,
|
||||
Bit_Set = 15,
|
||||
Simd_Vector = 16,
|
||||
SOA_Struct_Fixed = 17,
|
||||
SOA_Struct_Slice = 18,
|
||||
SOA_Struct_Dynamic = 19,
|
||||
Relative_Pointer = 20,
|
||||
Relative_Slice = 21,
|
||||
}
|
||||
|
||||
Type_Elems_Cap :: 4;
|
||||
|
||||
Type :: struct {
|
||||
kind: Type_Kind,
|
||||
flags: u32le, // Type_Kind specific
|
||||
name: String,
|
||||
custom_align: String,
|
||||
|
||||
// Used by some types
|
||||
elem_count_len: u32le,
|
||||
elem_counts: [Type_Elems_Cap]i64le,
|
||||
|
||||
// Each of these is esed by some types, not all
|
||||
calling_convention: String, // Procedures
|
||||
types: Array(Type_Index),
|
||||
entities: Array(Entity_Index),
|
||||
polymorphic_params: Type_Index, // Struct, Union
|
||||
where_clauses: Array(String), // Struct, Union
|
||||
}
|
||||
|
||||
Type_Flags_Basic :: distinct bit_set[Type_Flag_Basic; u32le];
|
||||
Type_Flag_Basic :: enum u32le {
|
||||
Untyped = 1,
|
||||
}
|
||||
|
||||
Type_Flags_Struct :: distinct bit_set[Type_Flag_Struct; u32le];
|
||||
Type_Flag_Struct :: enum u32le {
|
||||
Polymorphic = 0,
|
||||
Packed = 1,
|
||||
Raw_Union = 2,
|
||||
}
|
||||
|
||||
Type_Flags_Union :: distinct bit_set[Type_Flag_Union; u32le];
|
||||
Type_Flag_Union :: enum u32le {
|
||||
Polymorphic = 0,
|
||||
No_Nil = 1,
|
||||
Maybe = 2,
|
||||
}
|
||||
|
||||
Type_Flags_Proc :: distinct bit_set[Type_Flag_Proc; u32le];
|
||||
Type_Flag_Proc :: enum u32le {
|
||||
Polymorphic = 0,
|
||||
Diverging = 1,
|
||||
Optional_Ok = 2,
|
||||
Variadic = 3,
|
||||
C_Vararg = 4,
|
||||
}
|
||||
|
||||
Type_Flags_Bit_Set :: distinct bit_set[Type_Flag_Bit_Set; u32le];
|
||||
Type_Flag_Bit_Set :: enum u32le {
|
||||
Range = 1,
|
||||
Op_Lt = 2,
|
||||
Op_Lt_Eq = 3,
|
||||
Underlying_Type = 4,
|
||||
}
|
||||
|
||||
Type_Flags_SimdVector :: distinct bit_set[Type_Flag_SimdVector; u32le];
|
||||
Type_Flag_SimdVector :: enum u32le {
|
||||
x86_mmx = 1,
|
||||
}
|
||||
|
||||
from_array :: proc(base: ^Header_Base, a: $A/Array($T)) -> []T {
|
||||
s: mem.Raw_Slice;
|
||||
s.data = rawptr(uintptr(base) + uintptr(a.offset));
|
||||
s.len = int(a.length);
|
||||
return transmute([]T)s;
|
||||
}
|
||||
from_string :: proc(base: ^Header_Base, s: String) -> string {
|
||||
return string(from_array(base, s));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reader_Error :: enum {
|
||||
None,
|
||||
Header_Too_Small,
|
||||
Invalid_Magic,
|
||||
Data_Too_Small,
|
||||
Invalid_Version,
|
||||
}
|
||||
|
||||
read_from_bytes :: proc(data: []byte) -> (h: ^Header, err: Reader_Error) {
|
||||
if len(data) < size_of(Header_Base) {
|
||||
err = .Header_Too_Small;
|
||||
return;
|
||||
}
|
||||
header_base := (^Header_Base)(raw_data(data));
|
||||
if header_base.magic != Magic_String {
|
||||
err = .Invalid_Magic;
|
||||
return;
|
||||
}
|
||||
if len(data) < int(header_base.total_size) {
|
||||
err = .Data_Too_Small;
|
||||
return;
|
||||
}
|
||||
if header_base.version != Version_Type_Default {
|
||||
err = .Invalid_Version;
|
||||
return;
|
||||
}
|
||||
h = (^Header)(header_base);
|
||||
return;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Package os provides a platform-independent interface to operating system functionality.
|
||||
// The design is UNIX-like but with Odin-like error handling. Failing calls return values with a specific error type rather than error number.
|
||||
//
|
||||
// The package os interface is intended to be uniform across all operating systems.
|
||||
// Features not generally available appear in the system-specific packages under core:sys/*.
|
||||
//
|
||||
//
|
||||
// IMPORTANT NOTE from Bill: this is purely a mockup of what I want the new package os to be, and NON-FUNCTIONING.
|
||||
// It is not complete but should give designers a better idea of the general interface and how to write things.
|
||||
// This entire interface is subject to change.
|
||||
package os2
|
||||
@@ -0,0 +1,43 @@
|
||||
package os2
|
||||
|
||||
// get_env retrieves the value of the environment variable named by the key
|
||||
// It returns the value, which will be empty if the variable is not present
|
||||
// To distinguish between an empty value and an unset value, use lookup_env
|
||||
// NOTE: the value will be allocated with the supplied allocator
|
||||
get_env :: proc(key: string, allocator := context.allocator) -> string {
|
||||
value, _ := lookup_env(key, allocator);
|
||||
return value;
|
||||
}
|
||||
|
||||
// lookup_env gets the value of the environment variable named by the key
|
||||
// If the variable is found in the environment the value (which can be empty) is returned and the boolean is true
|
||||
// Otherwise the returned value will be empty and the boolean will be false
|
||||
// NOTE: the value will be allocated with the supplied allocator
|
||||
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
return _lookup_env(key, allocator);
|
||||
}
|
||||
|
||||
// set_env sets the value of the environment variable named by the key
|
||||
// Returns true on success, false on failure
|
||||
set_env :: proc(key, value: string) -> bool {
|
||||
return _set_env(key, value);
|
||||
}
|
||||
|
||||
// unset_env unsets a single environment variable
|
||||
// Returns true on success, false on failure
|
||||
unset_env :: proc(key: string) -> bool {
|
||||
return _unset_env(key);
|
||||
}
|
||||
|
||||
clear_env :: proc() {
|
||||
_clear_env();
|
||||
}
|
||||
|
||||
|
||||
// environ returns a copy of strings representing the environment, in the form "key=value"
|
||||
// NOTE: the slice of strings and the strings with be allocated using the supplied allocator
|
||||
environ :: proc(allocator := context.allocator) -> []string {
|
||||
return _environ(allocator);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
//+private
|
||||
package os2
|
||||
|
||||
import "core:mem"
|
||||
import win32 "core:sys/windows"
|
||||
|
||||
_lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
if key == "" {
|
||||
return;
|
||||
}
|
||||
wkey := win32.utf8_to_wstring(key);
|
||||
b := make([dynamic]u16, 100, context.temp_allocator);
|
||||
for {
|
||||
n := win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b)));
|
||||
if n == 0 {
|
||||
err := win32.GetLastError();
|
||||
if err == win32.ERROR_ENVVAR_NOT_FOUND {
|
||||
return "", false;
|
||||
}
|
||||
}
|
||||
|
||||
if n <= u32(len(b)) {
|
||||
value = win32.utf16_to_utf8(b[:n], allocator);
|
||||
found = true;
|
||||
return;
|
||||
}
|
||||
|
||||
resize(&b, len(b)*2);
|
||||
}
|
||||
}
|
||||
|
||||
_set_env :: proc(key, value: string) -> bool {
|
||||
k := win32.utf8_to_wstring(key);
|
||||
v := win32.utf8_to_wstring(value);
|
||||
|
||||
return bool(win32.SetEnvironmentVariableW(k, v));
|
||||
}
|
||||
|
||||
_unset_env :: proc(key: string) -> bool {
|
||||
k := win32.utf8_to_wstring(key);
|
||||
return bool(win32.SetEnvironmentVariableW(k, nil));
|
||||
}
|
||||
|
||||
_clear_env :: proc() {
|
||||
envs := environ(context.temp_allocator);
|
||||
for env in envs {
|
||||
for j in 1..<len(env) {
|
||||
if env[j] == '=' {
|
||||
unset_env(env[0:j]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_environ :: proc(allocator := context.allocator) -> []string {
|
||||
envs := win32.GetEnvironmentStringsW();
|
||||
if envs == nil {
|
||||
return nil;
|
||||
}
|
||||
defer win32.FreeEnvironmentStringsW(envs);
|
||||
|
||||
r := make([dynamic]string, 0, 50, allocator);
|
||||
for from, i, p := 0, 0, envs; true; i += 1 {
|
||||
c := (^u16)(uintptr(p) + uintptr(i*2))^;
|
||||
if c == 0 {
|
||||
if i <= from {
|
||||
break;
|
||||
}
|
||||
w := mem.slice_ptr(mem.ptr_offset(p, from), i-from);
|
||||
|
||||
append(&r, win32.utf16_to_utf8(w, allocator));
|
||||
from = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return r[:];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
package os2
|
||||
|
||||
Platform_Error_Min_Bits :: 32;
|
||||
|
||||
Error :: enum u64 {
|
||||
None = 0,
|
||||
|
||||
// General Errors
|
||||
Invalid_Argument,
|
||||
|
||||
Permission_Denied,
|
||||
Exist,
|
||||
Not_Exist,
|
||||
Closed,
|
||||
|
||||
// Timeout Errors
|
||||
Timeout,
|
||||
|
||||
// I/O Errors
|
||||
// EOF is the error returned by `read` when no more input is available
|
||||
EOF,
|
||||
|
||||
// Unexpected_EOF means that EOF was encountered in the middle of reading a fixed-sized block of data
|
||||
Unexpected_EOF,
|
||||
|
||||
// Short_Write means that a write accepted fewer bytes than requested but failed to return an explicit error
|
||||
Short_Write,
|
||||
|
||||
// Invalid_Write means that a write returned an impossible count
|
||||
Invalid_Write,
|
||||
|
||||
// Short_Buffer means that a read required a longer buffer than was provided
|
||||
Short_Buffer,
|
||||
|
||||
// No_Progress is returned by some implementations of `io.Reader` when many calls
|
||||
// to `read` have failed to return any data or error.
|
||||
// This is usually a signed of a broken `io.Reader` implementation
|
||||
No_Progress,
|
||||
|
||||
Invalid_Whence,
|
||||
Invalid_Offset,
|
||||
Invalid_Unread,
|
||||
|
||||
Negative_Read,
|
||||
Negative_Write,
|
||||
Negative_Count,
|
||||
Buffer_Full,
|
||||
|
||||
// Platform Specific Errors
|
||||
Platform_Minimum = 1<<Platform_Error_Min_Bits,
|
||||
}
|
||||
|
||||
Path_Error :: struct {
|
||||
op: string,
|
||||
path: string,
|
||||
err: Error,
|
||||
}
|
||||
|
||||
Link_Error :: struct {
|
||||
op: string,
|
||||
old: string,
|
||||
new: string,
|
||||
err: Error,
|
||||
}
|
||||
|
||||
path_error_delete :: proc(perr: Maybe(Path_Error)) {
|
||||
if err, ok := perr.?; ok {
|
||||
context.allocator = error_allocator();
|
||||
delete(err.op);
|
||||
delete(err.path);
|
||||
}
|
||||
}
|
||||
|
||||
link_error_delete :: proc(lerr: Maybe(Link_Error)) {
|
||||
if err, ok := lerr.?; ok {
|
||||
context.allocator = error_allocator();
|
||||
delete(err.op);
|
||||
delete(err.old);
|
||||
delete(err.new);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
is_platform_error :: proc(ferr: Error) -> (err: i32, ok: bool) {
|
||||
if ferr >= .Platform_Minimum {
|
||||
err = i32(u64(ferr)>>Platform_Error_Min_Bits);
|
||||
ok = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
error_from_platform_error :: proc(errno: i32) -> Error {
|
||||
return Error(u64(errno) << Platform_Error_Min_Bits);
|
||||
}
|
||||
|
||||
error_string :: proc(ferr: Error) -> string {
|
||||
#partial switch ferr {
|
||||
case .None: return "";
|
||||
case .Invalid_Argument: return "invalid argument";
|
||||
case .Permission_Denied: return "permission denied";
|
||||
case .Exist: return "file already exists";
|
||||
case .Not_Exist: return "file does not exist";
|
||||
case .Closed: return "file already closed";
|
||||
case .Timeout: return "i/o timeout";
|
||||
case .EOF: return "eof";
|
||||
case .Unexpected_EOF: return "unexpected eof";
|
||||
case .Short_Write: return "short write";
|
||||
case .Invalid_Write: return "invalid write result";
|
||||
case .Short_Buffer: return "short buffer";
|
||||
case .No_Progress: return "multiple read calls return no data or error";
|
||||
case .Invalid_Whence: return "invalid whence";
|
||||
case .Invalid_Offset: return "invalid offset";
|
||||
case .Invalid_Unread: return "invalid unread";
|
||||
case .Negative_Read: return "negative read";
|
||||
case .Negative_Write: return "negative write";
|
||||
case .Negative_Count: return "negative count";
|
||||
case .Buffer_Full: return "buffer full";
|
||||
}
|
||||
|
||||
if errno, ok := is_platform_error(ferr); ok {
|
||||
return _error_string(errno);
|
||||
}
|
||||
|
||||
return "unknown error";
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
//+private
|
||||
package os2
|
||||
|
||||
import win32 "core:sys/windows"
|
||||
|
||||
_error_string :: proc(errno: i32) -> string {
|
||||
e := win32.DWORD(errno);
|
||||
if e == 0 {
|
||||
return "";
|
||||
}
|
||||
// TODO(bill): _error_string for windows
|
||||
// FormatMessageW
|
||||
return "";
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package os2
|
||||
|
||||
import "core:io"
|
||||
import "core:time"
|
||||
|
||||
Handle :: distinct uintptr;
|
||||
|
||||
Seek_From :: enum {
|
||||
Start = 0, // seek relative to the origin of the file
|
||||
Current = 1, // seek relative to the current offset
|
||||
End = 2, // seek relative to the end
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
||||
O_RDONLY :: int( 0);
|
||||
O_WRONLY :: int( 1);
|
||||
O_RDWR :: int( 2);
|
||||
O_APPEND :: int( 4);
|
||||
O_CREATE :: int( 8);
|
||||
O_EXCL :: int(16);
|
||||
O_SYNC :: int(32);
|
||||
O_TRUNC :: int(64);
|
||||
|
||||
|
||||
|
||||
stdin: Handle = 0; // OS-Specific
|
||||
stdout: Handle = 1; // OS-Specific
|
||||
stderr: Handle = 2; // OS-Specific
|
||||
|
||||
|
||||
create :: proc(name: string) -> (Handle, Error) {
|
||||
return _create(name);
|
||||
}
|
||||
|
||||
open :: proc(name: string) -> (Handle, Error) {
|
||||
return _open(name);
|
||||
}
|
||||
|
||||
open_file :: proc(name: string, flag: int, perm: File_Mode) -> (Handle, Error) {
|
||||
return _open_file(name, flag, perm);
|
||||
}
|
||||
|
||||
close :: proc(fd: Handle) -> Error {
|
||||
return _close(fd);
|
||||
}
|
||||
|
||||
name :: proc(fd: Handle, allocator := context.allocator) -> string {
|
||||
return _name(fd);
|
||||
}
|
||||
|
||||
seek :: proc(fd: Handle, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) {
|
||||
return _seek(fd, offset, whence);
|
||||
}
|
||||
|
||||
read :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) {
|
||||
return _read(fd, p);
|
||||
}
|
||||
|
||||
read_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) {
|
||||
return _read_at(fd, p, offset);
|
||||
}
|
||||
|
||||
read_from :: proc(fd: Handle, r: io.Reader) -> (n: i64, err: Error) {
|
||||
return _read_from(fd, r);
|
||||
}
|
||||
|
||||
write :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) {
|
||||
return _write(fd, p);
|
||||
}
|
||||
|
||||
write_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) {
|
||||
return _write_at(fd, p, offset);
|
||||
}
|
||||
|
||||
write_to :: proc(fd: Handle, w: io.Writer) -> (n: i64, err: Error) {
|
||||
return _write_to(fd, w);
|
||||
}
|
||||
|
||||
file_size :: proc(fd: Handle) -> (n: i64, err: Error) {
|
||||
return _file_size(fd);
|
||||
}
|
||||
|
||||
|
||||
sync :: proc(fd: Handle) -> Error {
|
||||
return _sync(fd);
|
||||
}
|
||||
|
||||
flush :: proc(fd: Handle) -> Error {
|
||||
return _flush(fd);
|
||||
}
|
||||
|
||||
truncate :: proc(fd: Handle, size: i64) -> Maybe(Path_Error) {
|
||||
return _truncate(fd, size);
|
||||
}
|
||||
|
||||
remove :: proc(name: string) -> Maybe(Path_Error) {
|
||||
return _remove(name);
|
||||
}
|
||||
|
||||
rename :: proc(old_path, new_path: string) -> Maybe(Path_Error) {
|
||||
return _rename(old_path, new_path);
|
||||
}
|
||||
|
||||
|
||||
link :: proc(old_name, new_name: string) -> Maybe(Link_Error) {
|
||||
return _link(old_name, new_name);
|
||||
}
|
||||
|
||||
symlink :: proc(old_name, new_name: string) -> Maybe(Link_Error) {
|
||||
return _symlink(old_name, new_name);
|
||||
}
|
||||
|
||||
read_link :: proc(name: string) -> (string, Maybe(Path_Error)) {
|
||||
return _read_link(name);
|
||||
}
|
||||
|
||||
|
||||
chdir :: proc(fd: Handle) -> Error {
|
||||
return _chdir(fd);
|
||||
}
|
||||
|
||||
chmod :: proc(fd: Handle, mode: File_Mode) -> Error {
|
||||
return _chmod(fd, mode);
|
||||
}
|
||||
|
||||
chown :: proc(fd: Handle, uid, gid: int) -> Error {
|
||||
return _chown(fd, uid, gid);
|
||||
}
|
||||
|
||||
|
||||
lchown :: proc(name: string, uid, gid: int) -> Error {
|
||||
return _lchown(name, uid, gid);
|
||||
}
|
||||
|
||||
|
||||
chtimes :: proc(name: string, atime, mtime: time.Time) -> Maybe(Path_Error) {
|
||||
return _chtimes(name, atime, mtime);
|
||||
}
|
||||
|
||||
exists :: proc(path: string) -> bool {
|
||||
return _exists(path);
|
||||
}
|
||||
|
||||
is_file :: proc(path: string) -> bool {
|
||||
return _is_file(path);
|
||||
}
|
||||
|
||||
is_dir :: proc(path: string) -> bool {
|
||||
return _is_dir(path);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package os2
|
||||
|
||||
import "core:io"
|
||||
|
||||
file_to_stream :: proc(fd: Handle) -> (s: io.Stream) {
|
||||
s.stream_data = rawptr(uintptr(fd));
|
||||
s.stream_vtable = _file_stream_vtable;
|
||||
return;
|
||||
}
|
||||
|
||||
@(private)
|
||||
error_to_io_error :: proc(ferr: Error) -> io.Error {
|
||||
#partial switch ferr {
|
||||
case .None: return .None;
|
||||
case .EOF: return .EOF;
|
||||
case .Unexpected_EOF: return .Unexpected_EOF;
|
||||
case .Short_Write: return .Short_Write;
|
||||
case .Invalid_Write: return .Invalid_Write;
|
||||
case .Short_Buffer: return .Short_Buffer;
|
||||
case .No_Progress: return .No_Progress;
|
||||
case .Invalid_Whence: return .Invalid_Whence;
|
||||
case .Invalid_Offset: return .Invalid_Offset;
|
||||
case .Invalid_Unread: return .Invalid_Unread;
|
||||
case .Negative_Read: return .Negative_Read;
|
||||
case .Negative_Write: return .Negative_Write;
|
||||
case .Negative_Count: return .Negative_Count;
|
||||
case .Buffer_Full: return .Buffer_Full;
|
||||
}
|
||||
return .Unknown;
|
||||
}
|
||||
|
||||
|
||||
@(private)
|
||||
_file_stream_vtable := &io.Stream_VTable{
|
||||
impl_read = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) {
|
||||
fd := Handle(uintptr(s.stream_data));
|
||||
ferr: Error;
|
||||
n, ferr = read(fd, p);
|
||||
err = error_to_io_error(ferr);
|
||||
return;
|
||||
},
|
||||
impl_read_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) {
|
||||
fd := Handle(uintptr(s.stream_data));
|
||||
ferr: Error;
|
||||
n, ferr = read_at(fd, p, offset);
|
||||
err = error_to_io_error(ferr);
|
||||
return;
|
||||
},
|
||||
impl_write_to = proc(s: io.Stream, w: io.Writer) -> (n: i64, err: io.Error) {
|
||||
fd := Handle(uintptr(s.stream_data));
|
||||
ferr: Error;
|
||||
n, ferr = write_to(fd, w);
|
||||
err = error_to_io_error(ferr);
|
||||
return;
|
||||
},
|
||||
impl_write = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) {
|
||||
fd := Handle(uintptr(s.stream_data));
|
||||
ferr: Error;
|
||||
n, ferr = write(fd, p);
|
||||
err = error_to_io_error(ferr);
|
||||
return;
|
||||
},
|
||||
impl_write_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) {
|
||||
fd := Handle(uintptr(s.stream_data));
|
||||
ferr: Error;
|
||||
n, ferr = write_at(fd, p, offset);
|
||||
err = error_to_io_error(ferr);
|
||||
return;
|
||||
},
|
||||
impl_read_from = proc(s: io.Stream, r: io.Reader) -> (n: i64, err: io.Error) {
|
||||
fd := Handle(uintptr(s.stream_data));
|
||||
ferr: Error;
|
||||
n, ferr = read_from(fd, r);
|
||||
err = error_to_io_error(ferr);
|
||||
return;
|
||||
},
|
||||
impl_seek = proc(s: io.Stream, offset: i64, whence: io.Seek_From) -> (i64, io.Error) {
|
||||
fd := Handle(uintptr(s.stream_data));
|
||||
n, ferr := seek(fd, offset, Seek_From(whence));
|
||||
err := error_to_io_error(ferr);
|
||||
return n, err;
|
||||
},
|
||||
impl_size = proc(s: io.Stream) -> i64 {
|
||||
fd := Handle(uintptr(s.stream_data));
|
||||
sz, _ := file_size(fd);
|
||||
return sz;
|
||||
},
|
||||
impl_flush = proc(s: io.Stream) -> io.Error {
|
||||
fd := Handle(uintptr(s.stream_data));
|
||||
ferr := flush(fd);
|
||||
return error_to_io_error(ferr);
|
||||
},
|
||||
impl_close = proc(s: io.Stream) -> io.Error {
|
||||
fd := Handle(uintptr(s.stream_data));
|
||||
ferr := close(fd);
|
||||
return error_to_io_error(ferr);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,122 @@
|
||||
package os2
|
||||
|
||||
import "core:mem"
|
||||
import "core:strconv"
|
||||
import "core:unicode/utf8"
|
||||
|
||||
write_string :: proc(fd: Handle, s: string) -> (n: int, err: Error) {
|
||||
return write(fd, transmute([]byte)s);
|
||||
}
|
||||
|
||||
write_byte :: proc(fd: Handle, b: byte) -> (n: int, err: Error) {
|
||||
return write(fd, []byte{b});
|
||||
}
|
||||
|
||||
write_rune :: proc(fd: Handle, r: rune) -> (n: int, err: Error) {
|
||||
if r < utf8.RUNE_SELF {
|
||||
return write_byte(fd, byte(r));
|
||||
}
|
||||
|
||||
b: [4]byte;
|
||||
b, n = utf8.encode_rune(r);
|
||||
return write(fd, b[:n]);
|
||||
}
|
||||
|
||||
write_encoded_rune :: proc(fd: Handle, r: rune) -> (n: int, err: Error) {
|
||||
wrap :: proc(m: int, merr: Error, n: ^int, err: ^Error) -> bool {
|
||||
n^ += m;
|
||||
if merr != nil {
|
||||
err^ = merr;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if wrap(write_byte(fd, '\''), &n, &err) { return; }
|
||||
|
||||
switch r {
|
||||
case '\a': if wrap(write_string(fd, "\\a"), &n, &err) { return; }
|
||||
case '\b': if wrap(write_string(fd, "\\b"), &n, &err) { return; }
|
||||
case '\e': if wrap(write_string(fd, "\\e"), &n, &err) { return; }
|
||||
case '\f': if wrap(write_string(fd, "\\f"), &n, &err) { return; }
|
||||
case '\n': if wrap(write_string(fd, "\\n"), &n, &err) { return; }
|
||||
case '\r': if wrap(write_string(fd, "\\r"), &n, &err) { return; }
|
||||
case '\t': if wrap(write_string(fd, "\\t"), &n, &err) { return; }
|
||||
case '\v': if wrap(write_string(fd, "\\v"), &n, &err) { return; }
|
||||
case:
|
||||
if r < 32 {
|
||||
if wrap(write_string(fd, "\\x"), &n, &err) { return; }
|
||||
b: [2]byte;
|
||||
s := strconv.append_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil);
|
||||
switch len(s) {
|
||||
case 0: if wrap(write_string(fd, "00"), &n, &err) { return; }
|
||||
case 1: if wrap(write_rune(fd, '0'), &n, &err) { return; }
|
||||
case 2: if wrap(write_string(fd, s), &n, &err) { return; }
|
||||
}
|
||||
} else {
|
||||
if wrap(write_rune(fd, r), &n, &err) { return; }
|
||||
}
|
||||
}
|
||||
_ = wrap(write_byte(fd, '\''), &n, &err);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
write_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (n: int, err: Error) {
|
||||
s := transmute([]byte)mem.Raw_Slice{data, len};
|
||||
return write(fd, s);
|
||||
}
|
||||
|
||||
read_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (n: int, err: Error) {
|
||||
s := transmute([]byte)mem.Raw_Slice{data, len};
|
||||
return read(fd, s);
|
||||
}
|
||||
|
||||
|
||||
|
||||
read_entire_file :: proc(name: string, allocator := context.allocator) -> ([]byte, Error) {
|
||||
f, ferr := open(name);
|
||||
if ferr != nil {
|
||||
return nil, ferr;
|
||||
}
|
||||
defer close(f);
|
||||
|
||||
size: int;
|
||||
if size64, err := file_size(f); err == nil {
|
||||
if i64(int(size64)) != size64 {
|
||||
size = int(size64);
|
||||
}
|
||||
}
|
||||
size += 1; // for EOF
|
||||
|
||||
// TODO(bill): Is this correct logic?
|
||||
total: int;
|
||||
data := make([]byte, size, allocator);
|
||||
for {
|
||||
n, err := read(f, data[total:]);
|
||||
total += n;
|
||||
if err != nil {
|
||||
if err == .EOF {
|
||||
err = nil;
|
||||
}
|
||||
return data[:total], err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
write_entire_file :: proc(name: string, data: []byte, perm: File_Mode, truncate := true) -> Error {
|
||||
flags := O_WRONLY|O_CREATE;
|
||||
if truncate {
|
||||
flags |= O_TRUNC;
|
||||
}
|
||||
f, err := open_file(name, flags, perm);
|
||||
if err != nil {
|
||||
return err;
|
||||
}
|
||||
_, err = write(f, data);
|
||||
if cerr := close(f); cerr != nil && err == nil {
|
||||
err = cerr;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
//+private
|
||||
package os2
|
||||
|
||||
import "core:io"
|
||||
import "core:time"
|
||||
|
||||
_create :: proc(name: string) -> (Handle, Error) {
|
||||
return 0, .None;
|
||||
}
|
||||
|
||||
_open :: proc(name: string) -> (Handle, Error) {
|
||||
return 0, .None;
|
||||
}
|
||||
|
||||
_open_file :: proc(name: string, flag: int, perm: File_Mode) -> (Handle, Error) {
|
||||
return 0, .None;
|
||||
}
|
||||
|
||||
_close :: proc(fd: Handle) -> Error {
|
||||
return .None;
|
||||
}
|
||||
|
||||
_name :: proc(fd: Handle, allocator := context.allocator) -> string {
|
||||
return "";
|
||||
}
|
||||
|
||||
_seek :: proc(fd: Handle, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) {
|
||||
return;
|
||||
}
|
||||
|
||||
_read :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) {
|
||||
return;
|
||||
}
|
||||
|
||||
_read_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) {
|
||||
return;
|
||||
}
|
||||
|
||||
_read_from :: proc(fd: Handle, r: io.Reader) -> (n: i64, err: Error) {
|
||||
return;
|
||||
}
|
||||
|
||||
_write :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) {
|
||||
return;
|
||||
}
|
||||
|
||||
_write_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) {
|
||||
return;
|
||||
}
|
||||
|
||||
_write_to :: proc(fd: Handle, w: io.Writer) -> (n: i64, err: Error) {
|
||||
return;
|
||||
}
|
||||
|
||||
_file_size :: proc(fd: Handle) -> (n: i64, err: Error) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
_sync :: proc(fd: Handle) -> Error {
|
||||
return .None;
|
||||
}
|
||||
|
||||
_flush :: proc(fd: Handle) -> Error {
|
||||
return .None;
|
||||
}
|
||||
|
||||
_truncate :: proc(fd: Handle, size: i64) -> Maybe(Path_Error) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_remove :: proc(name: string) -> Maybe(Path_Error) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_rename :: proc(old_path, new_path: string) -> Maybe(Path_Error) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
_link :: proc(old_name, new_name: string) -> Maybe(Link_Error) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_symlink :: proc(old_name, new_name: string) -> Maybe(Link_Error) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_read_link :: proc(name: string) -> (string, Maybe(Path_Error)) {
|
||||
return "", nil;
|
||||
}
|
||||
|
||||
|
||||
_chdir :: proc(fd: Handle) -> Error {
|
||||
return .None;
|
||||
}
|
||||
|
||||
_chmod :: proc(fd: Handle, mode: File_Mode) -> Error {
|
||||
return .None;
|
||||
}
|
||||
|
||||
_chown :: proc(fd: Handle, uid, gid: int) -> Error {
|
||||
return .None;
|
||||
}
|
||||
|
||||
|
||||
_lchown :: proc(name: string, uid, gid: int) -> Error {
|
||||
return .None;
|
||||
}
|
||||
|
||||
|
||||
_chtimes :: proc(name: string, atime, mtime: time.Time) -> Maybe(Path_Error) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
_exists :: proc(path: string) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
_is_file :: proc(path: string) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
_is_dir :: proc(path: string) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
_path_error_delete :: proc(perr: Maybe(Path_Error)) {
|
||||
|
||||
}
|
||||
|
||||
_link_error_delete :: proc(lerr: Maybe(Link_Error)) {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package os2
|
||||
|
||||
import "core:runtime"
|
||||
|
||||
heap_allocator :: proc() -> runtime.Allocator {
|
||||
return runtime.Allocator{
|
||||
procedure = heap_allocator_proc,
|
||||
data = nil,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
heap_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode,
|
||||
size, alignment: int,
|
||||
old_memory: rawptr, old_size: int, flags: u64 = 0, loc := #caller_location) -> rawptr {
|
||||
return _heap_allocator_proc(allocator_data, mode, size, alignment, old_memory, old_size, flags, loc);
|
||||
}
|
||||
|
||||
|
||||
@(private)
|
||||
error_allocator := heap_allocator;
|
||||
@@ -0,0 +1,107 @@
|
||||
//+private
|
||||
package os2
|
||||
|
||||
import "core:runtime"
|
||||
import "core:mem"
|
||||
import win32 "core:sys/windows"
|
||||
|
||||
heap_alloc :: proc(size: int) -> rawptr {
|
||||
return win32.HeapAlloc(win32.GetProcessHeap(), win32.HEAP_ZERO_MEMORY, uint(size));
|
||||
}
|
||||
|
||||
heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
|
||||
if new_size == 0 {
|
||||
heap_free(ptr);
|
||||
return nil;
|
||||
}
|
||||
if ptr == nil {
|
||||
return heap_alloc(new_size);
|
||||
}
|
||||
|
||||
return win32.HeapReAlloc(win32.GetProcessHeap(), win32.HEAP_ZERO_MEMORY, ptr, uint(new_size));
|
||||
}
|
||||
heap_free :: proc(ptr: rawptr) {
|
||||
if ptr == nil {
|
||||
return;
|
||||
}
|
||||
win32.HeapFree(win32.GetProcessHeap(), 0, ptr);
|
||||
}
|
||||
|
||||
_heap_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode,
|
||||
size, alignment: int,
|
||||
old_memory: rawptr, old_size: int, flags: u64 = 0, loc := #caller_location) -> rawptr {
|
||||
//
|
||||
// NOTE(tetra, 2020-01-14): The heap doesn't respect alignment.
|
||||
// Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert
|
||||
// padding. We also store the original pointer returned by heap_alloc right before
|
||||
// the pointer we return to the user.
|
||||
//
|
||||
|
||||
aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr = nil) -> rawptr {
|
||||
a := max(alignment, align_of(rawptr));
|
||||
space := size + a - 1;
|
||||
|
||||
allocated_mem: rawptr;
|
||||
if old_ptr != nil {
|
||||
original_old_ptr := mem.ptr_offset((^rawptr)(old_ptr), -1)^;
|
||||
allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr));
|
||||
} else {
|
||||
allocated_mem = heap_alloc(space+size_of(rawptr));
|
||||
}
|
||||
aligned_mem := rawptr(mem.ptr_offset((^u8)(allocated_mem), size_of(rawptr)));
|
||||
|
||||
ptr := uintptr(aligned_mem);
|
||||
aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a);
|
||||
diff := int(aligned_ptr - ptr);
|
||||
if (size + diff) > space {
|
||||
return nil;
|
||||
}
|
||||
|
||||
aligned_mem = rawptr(aligned_ptr);
|
||||
mem.ptr_offset((^rawptr)(aligned_mem), -1)^ = allocated_mem;
|
||||
|
||||
return aligned_mem;
|
||||
}
|
||||
|
||||
aligned_free :: proc(p: rawptr) {
|
||||
if p != nil {
|
||||
heap_free(mem.ptr_offset((^rawptr)(p), -1)^);
|
||||
}
|
||||
}
|
||||
|
||||
aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int) -> rawptr {
|
||||
if p == nil {
|
||||
return nil;
|
||||
}
|
||||
return aligned_alloc(new_size, new_alignment, p);
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case .Alloc:
|
||||
return aligned_alloc(size, alignment);
|
||||
|
||||
case .Free:
|
||||
aligned_free(old_memory);
|
||||
|
||||
case .Free_All:
|
||||
// NOTE(tetra): Do nothing.
|
||||
|
||||
case .Resize:
|
||||
if old_memory == nil {
|
||||
return aligned_alloc(size, alignment);
|
||||
}
|
||||
return aligned_resize(old_memory, old_size, size, alignment);
|
||||
|
||||
case .Query_Features:
|
||||
set := (^runtime.Allocator_Mode_Set)(old_memory);
|
||||
if set != nil {
|
||||
set^ = {.Alloc, .Free, .Resize, .Query_Features};
|
||||
}
|
||||
return set;
|
||||
|
||||
case .Query_Info:
|
||||
return nil;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package os2
|
||||
|
||||
Path_Separator :: _Path_Separator; // OS-Specific
|
||||
Path_List_Separator :: _Path_List_Separator; // OS-Specific
|
||||
|
||||
is_path_separator :: proc(c: byte) -> bool {
|
||||
return _is_path_separator(c);
|
||||
}
|
||||
|
||||
mkdir :: proc(name: string, perm: File_Mode) -> Maybe(Path_Error) {
|
||||
return _mkdir(name, perm);
|
||||
}
|
||||
|
||||
mkdir_all :: proc(path: string, perm: File_Mode) -> Maybe(Path_Error) {
|
||||
return _mkdir_all(path, perm);
|
||||
}
|
||||
|
||||
remove_all :: proc(path: string) -> Maybe(Path_Error) {
|
||||
return _remove_all(path);
|
||||
}
|
||||
|
||||
|
||||
|
||||
getwd :: proc(allocator := context.allocator) -> (dir: string, err: Error) {
|
||||
return _getwd(allocator);
|
||||
}
|
||||
setwd :: proc(dir: string) -> (err: Error) {
|
||||
return _setwd(dir);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
//+private
|
||||
package os2
|
||||
|
||||
_Path_Separator :: '\\';
|
||||
_Path_List_Separator :: ';';
|
||||
|
||||
_is_path_separator :: proc(c: byte) -> bool {
|
||||
return c == '\\' || c == '/';
|
||||
}
|
||||
|
||||
_mkdir :: proc(name: string, perm: File_Mode) -> Maybe(Path_Error) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_mkdir_all :: proc(path: string, perm: File_Mode) -> Maybe(Path_Error) {
|
||||
// TODO(bill): _mkdir_all for windows
|
||||
return nil;
|
||||
}
|
||||
|
||||
_remove_all :: proc(path: string) -> Maybe(Path_Error) {
|
||||
// TODO(bill): _remove_all for windows
|
||||
return nil;
|
||||
}
|
||||
|
||||
_getwd :: proc(allocator := context.allocator) -> (dir: string, err: Error) {
|
||||
return "", nil;
|
||||
}
|
||||
|
||||
_setwd :: proc(dir: string) -> (err: Error) {
|
||||
return nil;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package os2
|
||||
|
||||
pipe :: proc() -> (r, w: Handle, err: Error) {
|
||||
return _pipe();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
//+private
|
||||
package os2
|
||||
|
||||
import win32 "core:sys/windows"
|
||||
|
||||
_pipe :: proc() -> (r, w: Handle, err: Error) {
|
||||
p: [2]win32.HANDLE;
|
||||
if !win32.CreatePipe(&p[0], &p[1], nil, 0) {
|
||||
return 0, 0, error_from_platform_error(i32(win32.GetLastError()));
|
||||
}
|
||||
return Handle(p[0]), Handle(p[1]), nil;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package os2
|
||||
|
||||
import sync "core:sync/sync2"
|
||||
import "core:time"
|
||||
|
||||
args: []string;
|
||||
|
||||
exit :: proc "contextless" (code: int) -> ! {
|
||||
//
|
||||
}
|
||||
|
||||
get_uid :: proc() -> int {
|
||||
return -1;
|
||||
}
|
||||
|
||||
get_euid :: proc() -> int {
|
||||
return -1;
|
||||
}
|
||||
|
||||
get_gid :: proc() -> int {
|
||||
return -1;
|
||||
}
|
||||
|
||||
get_egid :: proc() -> int {
|
||||
return -1;
|
||||
}
|
||||
|
||||
get_pid :: proc() -> int {
|
||||
return -1;
|
||||
}
|
||||
|
||||
get_ppid :: proc() -> int {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
Process :: struct {
|
||||
pid: int,
|
||||
handle: uintptr,
|
||||
is_done: b32,
|
||||
signal_mutex: sync.RW_Mutex,
|
||||
}
|
||||
|
||||
|
||||
Process_Attributes :: struct {
|
||||
dir: string,
|
||||
env: []string,
|
||||
files: []Handle,
|
||||
sys: ^Process_Attributes_OS_Specific,
|
||||
}
|
||||
|
||||
Process_Attributes_OS_Specific :: struct{};
|
||||
|
||||
Process_Error :: enum {
|
||||
None,
|
||||
}
|
||||
|
||||
Process_State :: struct {
|
||||
pid: int,
|
||||
exit_code: int,
|
||||
exited: bool,
|
||||
success: bool,
|
||||
system_time: time.Duration,
|
||||
user_time: time.Duration,
|
||||
sys: rawptr,
|
||||
}
|
||||
|
||||
Signal :: #type proc();
|
||||
|
||||
Kill: Signal = nil;
|
||||
Interrupt: Signal = nil;
|
||||
|
||||
|
||||
find_process :: proc(pid: int) -> (^Process, Process_Error) {
|
||||
return nil, .None;
|
||||
}
|
||||
|
||||
|
||||
process_start :: proc(name: string, argv: []string, attr: ^Process_Attributes) -> (^Process, Process_Error) {
|
||||
return nil, .None;
|
||||
}
|
||||
|
||||
process_release :: proc(p: ^Process) -> Process_Error {
|
||||
return .None;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package os2
|
||||
|
||||
import "core:time"
|
||||
|
||||
File_Info :: struct {
|
||||
fullpath: string,
|
||||
name: string,
|
||||
size: i64,
|
||||
mode: File_Mode,
|
||||
is_dir: bool,
|
||||
creation_time: time.Time,
|
||||
modification_time: time.Time,
|
||||
access_time: time.Time,
|
||||
}
|
||||
|
||||
file_info_slice_delete :: proc(infos: []File_Info, allocator := context.allocator) {
|
||||
for i := len(infos)-1; i >= 0; i -= 1 {
|
||||
file_info_delete(infos[i], allocator);
|
||||
}
|
||||
delete(infos, allocator);
|
||||
}
|
||||
|
||||
file_info_delete :: proc(fi: File_Info, allocator := context.allocator) {
|
||||
delete(fi.fullpath, allocator);
|
||||
}
|
||||
|
||||
fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) {
|
||||
return _fstat(fd, allocator);
|
||||
}
|
||||
|
||||
stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) {
|
||||
return _stat(name, allocator);
|
||||
}
|
||||
|
||||
lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) {
|
||||
return _lstat(name, allocator);
|
||||
}
|
||||
|
||||
|
||||
same_file :: proc(fi1, fi2: File_Info) -> bool {
|
||||
return _same_file(fi1, fi2);
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
//+private
|
||||
package os2
|
||||
|
||||
import "core:time"
|
||||
import win32 "core:sys/windows"
|
||||
|
||||
_fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) {
|
||||
if fd == 0 {
|
||||
return {}, Path_Error{err = .Invalid_Argument};
|
||||
}
|
||||
context.allocator = allocator;
|
||||
|
||||
path, err := _cleanpath_from_handle(fd);
|
||||
if err != nil {
|
||||
return {}, err;
|
||||
}
|
||||
|
||||
h := win32.HANDLE(fd);
|
||||
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);
|
||||
return fi, nil;
|
||||
}
|
||||
|
||||
return _file_info_from_get_file_information_by_handle(path, h);
|
||||
}
|
||||
_stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) {
|
||||
return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS);
|
||||
}
|
||||
_lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) {
|
||||
return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS|win32.FILE_FLAG_OPEN_REPARSE_POINT);
|
||||
}
|
||||
_same_file :: proc(fi1, fi2: File_Info) -> bool {
|
||||
return fi1.fullpath == fi2.fullpath;
|
||||
}
|
||||
|
||||
|
||||
|
||||
_stat_errno :: proc(errno: win32.DWORD) -> Path_Error {
|
||||
return Path_Error{err = error_from_platform_error(i32(errno))};
|
||||
}
|
||||
|
||||
|
||||
full_path_from_name :: proc(name: string, allocator := context.allocator) -> (path: string, err: Maybe(Path_Error)) {
|
||||
name := name;
|
||||
if name == "" {
|
||||
name = ".";
|
||||
}
|
||||
p := win32.utf8_to_utf16(name, context.temp_allocator);
|
||||
buf := make([dynamic]u16, 100, allocator);
|
||||
for {
|
||||
n := win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil);
|
||||
if n == 0 {
|
||||
delete(buf);
|
||||
return "", _stat_errno(win32.GetLastError());
|
||||
}
|
||||
if n <= u32(len(buf)) {
|
||||
return win32.utf16_to_utf8(buf[:n]), nil;
|
||||
}
|
||||
resize(&buf, len(buf)*2);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
internal_stat :: proc(name: string, create_file_attributes: u32, allocator := context.allocator) -> (fi: File_Info, e: Maybe(Path_Error)) {
|
||||
if len(name) == 0 {
|
||||
return {}, Path_Error{err = .Not_Exist};
|
||||
}
|
||||
|
||||
context.allocator = allocator;
|
||||
|
||||
|
||||
wname := win32.utf8_to_wstring(_fix_long_path(name), context.temp_allocator);
|
||||
fa: win32.WIN32_FILE_ATTRIBUTE_DATA;
|
||||
ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa);
|
||||
if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
|
||||
// Not a symlink
|
||||
return _file_info_from_win32_file_attribute_data(&fa, name);
|
||||
}
|
||||
|
||||
err := 0 if ok else win32.GetLastError();
|
||||
|
||||
if err == win32.ERROR_SHARING_VIOLATION {
|
||||
fd: win32.WIN32_FIND_DATAW;
|
||||
sh := win32.FindFirstFileW(wname, &fd);
|
||||
if sh == win32.INVALID_HANDLE_VALUE {
|
||||
e = Path_Error{err = error_from_platform_error(i32(win32.GetLastError()))};
|
||||
return;
|
||||
}
|
||||
win32.FindClose(sh);
|
||||
|
||||
return _file_info_from_win32_find_data(&fd, name);
|
||||
}
|
||||
|
||||
h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil);
|
||||
if h == win32.INVALID_HANDLE_VALUE {
|
||||
e = Path_Error{err = error_from_platform_error(i32(win32.GetLastError()))};
|
||||
return;
|
||||
}
|
||||
defer win32.CloseHandle(h);
|
||||
return _file_info_from_get_file_information_by_handle(name, h);
|
||||
}
|
||||
|
||||
|
||||
_cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 {
|
||||
buf := buf;
|
||||
N := 0;
|
||||
for c, i in buf {
|
||||
if c == 0 { break; }
|
||||
N = i+1;
|
||||
}
|
||||
buf = buf[:N];
|
||||
|
||||
if len(buf) >= 4 {
|
||||
if buf[0] == '\\' &&
|
||||
buf[1] == '\\' &&
|
||||
buf[2] == '?' &&
|
||||
buf[3] == '\\' {
|
||||
buf = buf[4:];
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
_cleanpath_from_handle :: proc(fd: Handle) -> (string, Maybe(Path_Error)) {
|
||||
if fd == 0 {
|
||||
return "", Path_Error{err = .Invalid_Argument};
|
||||
}
|
||||
h := win32.HANDLE(fd);
|
||||
|
||||
MAX_PATH := win32.DWORD(260) + 1;
|
||||
buf: []u16;
|
||||
for {
|
||||
buf = make([]u16, MAX_PATH, context.temp_allocator);
|
||||
err := win32.GetFinalPathNameByHandleW(h, raw_data(buf), MAX_PATH, 0);
|
||||
switch err {
|
||||
case win32.ERROR_PATH_NOT_FOUND, win32.ERROR_INVALID_PARAMETER:
|
||||
return "", _stat_errno(err);
|
||||
case win32.ERROR_NOT_ENOUGH_MEMORY:
|
||||
MAX_PATH = MAX_PATH*2 + 1;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return _cleanpath_from_buf(buf), nil;
|
||||
}
|
||||
|
||||
_cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Maybe(Path_Error)) {
|
||||
if fd == 0 {
|
||||
return nil, Path_Error{err = .Invalid_Argument};
|
||||
}
|
||||
h := win32.HANDLE(fd);
|
||||
|
||||
MAX_PATH := win32.DWORD(260) + 1;
|
||||
buf: []u16;
|
||||
for {
|
||||
buf = make([]u16, MAX_PATH, context.temp_allocator);
|
||||
err := win32.GetFinalPathNameByHandleW(h, raw_data(buf), MAX_PATH, 0);
|
||||
switch err {
|
||||
case win32.ERROR_PATH_NOT_FOUND, win32.ERROR_INVALID_PARAMETER:
|
||||
return nil, _stat_errno(err);
|
||||
case win32.ERROR_NOT_ENOUGH_MEMORY:
|
||||
MAX_PATH = MAX_PATH*2 + 1;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return _cleanpath_strip_prefix(buf), nil;
|
||||
}
|
||||
|
||||
_cleanpath_from_buf :: proc(buf: []u16) -> string {
|
||||
buf := buf;
|
||||
buf = _cleanpath_strip_prefix(buf);
|
||||
return win32.utf16_to_utf8(buf, context.allocator);
|
||||
}
|
||||
|
||||
|
||||
basename :: proc(name: string) -> (base: string) {
|
||||
name := name;
|
||||
if len(name) > 3 && name[:3] == `\\?` {
|
||||
name = name[3:];
|
||||
}
|
||||
|
||||
if len(name) == 2 && name[1] == ':' {
|
||||
return ".";
|
||||
} else if len(name) > 2 && name[1] == ':' {
|
||||
name = name[2:];
|
||||
}
|
||||
i := len(name)-1;
|
||||
|
||||
for ; i > 0 && (name[i] == '/' || name[i] == '\\'); i -= 1 {
|
||||
name = name[:i];
|
||||
}
|
||||
for i -= 1; i >= 0; i -= 1 {
|
||||
if name[i] == '/' || name[i] == '\\' {
|
||||
name = name[i+1:];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
file_type_mode :: proc(h: win32.HANDLE) -> File_Mode {
|
||||
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;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
_file_mode_from_file_attributes :: proc(FileAttributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (mode: File_Mode) {
|
||||
if FileAttributes & win32.FILE_ATTRIBUTE_READONLY != 0 {
|
||||
mode |= 0o444;
|
||||
} else {
|
||||
mode |= 0o666;
|
||||
}
|
||||
|
||||
is_sym := false;
|
||||
if FileAttributes & 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;
|
||||
} else {
|
||||
if FileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
||||
mode |= 0o111 | File_Mode_Dir;
|
||||
}
|
||||
|
||||
if h != nil {
|
||||
mode |= file_type_mode(h);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
_file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string) -> (fi: File_Info, e: Maybe(Path_Error)) {
|
||||
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow);
|
||||
|
||||
fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0);
|
||||
fi.is_dir = fi.mode & File_Mode_Dir != 0;
|
||||
|
||||
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);
|
||||
fi.name = basename(fi.fullpath);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
_file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string) -> (fi: File_Info, e: Maybe(Path_Error)) {
|
||||
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow);
|
||||
|
||||
fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0);
|
||||
fi.is_dir = fi.mode & File_Mode_Dir != 0;
|
||||
|
||||
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);
|
||||
fi.name = basename(fi.fullpath);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
_file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE) -> (File_Info, Maybe(Path_Error)) {
|
||||
d: win32.BY_HANDLE_FILE_INFORMATION;
|
||||
if !win32.GetFileInformationByHandle(h, &d) {
|
||||
return {}, _stat_errno(win32.GetLastError());
|
||||
|
||||
}
|
||||
|
||||
ti: win32.FILE_ATTRIBUTE_TAG_INFO;
|
||||
if !win32.GetFileInformationByHandleEx(h, .FileAttributeTagInfo, &ti, size_of(ti)) {
|
||||
err := win32.GetLastError();
|
||||
if err != win32.ERROR_INVALID_PARAMETER {
|
||||
return {}, _stat_errno(err);
|
||||
}
|
||||
// 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_dir = fi.mode & File_Mode_Dir != 0;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
_is_abs :: proc(path: string) -> bool {
|
||||
if len(path) > 0 && path[0] == '/' {
|
||||
return true;
|
||||
}
|
||||
if len(path) > 2 {
|
||||
switch path[0] {
|
||||
case 'A'..'Z', 'a'..'z':
|
||||
return path[1] == ':' && is_path_separator(path[2]);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
_fix_long_path :: proc(path: string) -> string {
|
||||
if len(path) < 248 {
|
||||
return path;
|
||||
}
|
||||
|
||||
if len(path) >= 2 && path[:2] == `\\` {
|
||||
return path;
|
||||
}
|
||||
if !_is_abs(path) {
|
||||
return path;
|
||||
}
|
||||
|
||||
prefix :: `\\?`;
|
||||
|
||||
path_buf := make([]byte, len(prefix)+len(path)+len(`\`), context.temp_allocator);
|
||||
copy(path_buf, prefix);
|
||||
n := len(path);
|
||||
r, w := 0, len(prefix);
|
||||
for r < n {
|
||||
switch {
|
||||
case is_path_separator(path[r]):
|
||||
r += 1;
|
||||
case path[r] == '.' && (r+1 == n || is_path_separator(path[r+1])):
|
||||
r += 1;
|
||||
case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || is_path_separator(path[r+2])):
|
||||
return path;
|
||||
case:
|
||||
path_buf[w] = '\\';
|
||||
w += 1;
|
||||
for ; r < n && !is_path_separator(path[r]); r += 1 {
|
||||
path_buf[w] = path[r];
|
||||
w += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if w == len(`\\?\c:`) {
|
||||
path_buf[w] = '\\';
|
||||
w += 1;
|
||||
}
|
||||
return string(path_buf[:w]);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package os2
|
||||
|
||||
|
||||
create_temp :: proc(dir, pattern: string) -> (Handle, Error) {
|
||||
return _create_temp(dir, pattern);
|
||||
}
|
||||
|
||||
mkdir_temp :: proc(dir, pattern: string, allocator := context.allocator) -> (string, Error) {
|
||||
return _mkdir_temp(dir, pattern);
|
||||
}
|
||||
|
||||
temp_dir :: proc(allocator := context.allocator) -> string {
|
||||
return _temp_dir(allocator);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
//+private
|
||||
package os2
|
||||
|
||||
import win32 "core:sys/windows"
|
||||
|
||||
_create_temp :: proc(dir, pattern: string) -> (Handle, Error) {
|
||||
return 0, .None;
|
||||
}
|
||||
|
||||
_mkdir_temp :: proc(dir, pattern: string, allocator := context.allocator) -> (string, Error) {
|
||||
return "", .None;
|
||||
}
|
||||
|
||||
_temp_dir :: proc(allocator := context.allocator) -> string {
|
||||
b := make([dynamic]u16, u32(win32.MAX_PATH), context.temp_allocator);
|
||||
for {
|
||||
n := win32.GetTempPathW(u32(len(b)), raw_data(b));
|
||||
if n > u32(len(b)) {
|
||||
resize(&b, int(n));
|
||||
continue;
|
||||
}
|
||||
if n == 3 && b[1] == ':' && b[2] == '\\' {
|
||||
|
||||
} else if n > 0 && b[n-1] == '\\' {
|
||||
n -= 1;
|
||||
}
|
||||
return win32.utf16_to_utf8(b[:n], allocator);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package os2
|
||||
|
||||
import "core:strings"
|
||||
|
||||
user_cache_dir :: proc(allocator := context.allocator) -> (dir: string, is_defined: bool) {
|
||||
switch ODIN_OS {
|
||||
case "windows":
|
||||
dir = get_env("LocalAppData");
|
||||
if dir != "" {
|
||||
dir = strings.clone(dir, allocator);
|
||||
}
|
||||
case "darwin":
|
||||
dir = get_env("HOME");
|
||||
if dir != "" {
|
||||
dir = strings.concatenate({dir, "/Library/Caches"}, allocator);
|
||||
}
|
||||
case: // All other UNIX systems
|
||||
dir = get_env("XDG_CACHE_HOME");
|
||||
if dir == "" {
|
||||
dir = get_env("HOME");
|
||||
if dir == "" {
|
||||
return;
|
||||
}
|
||||
dir = strings.concatenate({dir, "/.cache"}, allocator);
|
||||
}
|
||||
}
|
||||
is_defined = dir != "";
|
||||
return;
|
||||
}
|
||||
|
||||
user_config_dir :: proc(allocator := context.allocator) -> (dir: string, is_defined: bool) {
|
||||
switch ODIN_OS {
|
||||
case "windows":
|
||||
dir = get_env("AppData");
|
||||
if dir != "" {
|
||||
dir = strings.clone(dir, allocator);
|
||||
}
|
||||
case "darwin":
|
||||
dir = get_env("HOME");
|
||||
if dir != "" {
|
||||
dir = strings.concatenate({dir, "/Library/Application Support"}, allocator);
|
||||
}
|
||||
case: // All other UNIX systems
|
||||
dir = get_env("XDG_CACHE_HOME");
|
||||
if dir == "" {
|
||||
dir = get_env("HOME");
|
||||
if dir == "" {
|
||||
return;
|
||||
}
|
||||
dir = strings.concatenate({dir, "/.config"}, allocator);
|
||||
}
|
||||
}
|
||||
is_defined = dir != "";
|
||||
return;
|
||||
}
|
||||
|
||||
user_home_dir :: proc() -> (dir: string, is_defined: bool) {
|
||||
env := "HOME";
|
||||
switch ODIN_OS {
|
||||
case "windows":
|
||||
env = "USERPROFILE";
|
||||
}
|
||||
if v := get_env(env); v != "" {
|
||||
return v, true;
|
||||
}
|
||||
return "", false;
|
||||
}
|
||||
|
||||
+33
-4
@@ -2,7 +2,6 @@
|
||||
package os
|
||||
|
||||
import "core:time"
|
||||
import "core:path"
|
||||
|
||||
/*
|
||||
For reference
|
||||
@@ -71,6 +70,36 @@ _fill_file_info_from_stat :: proc(fi: ^File_Info, s: OS_Stat) {
|
||||
fi.access_time = _make_time_from_unix_file_time(s.last_access);
|
||||
}
|
||||
|
||||
|
||||
@private
|
||||
path_base :: proc(path: string) -> string {
|
||||
is_separator :: proc(c: byte) -> bool {
|
||||
return c == '/';
|
||||
}
|
||||
|
||||
if path == "" {
|
||||
return ".";
|
||||
}
|
||||
|
||||
path := path;
|
||||
for len(path) > 0 && is_separator(path[len(path)-1]) {
|
||||
path = path[:len(path)-1];
|
||||
}
|
||||
|
||||
i := len(path)-1;
|
||||
for i >= 0 && !is_separator(path[i]) {
|
||||
i -= 1;
|
||||
}
|
||||
if i >= 0 {
|
||||
path = path[i+1:];
|
||||
}
|
||||
if path == "" {
|
||||
return "/";
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
lstat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Errno) {
|
||||
|
||||
context.allocator = allocator;
|
||||
@@ -85,7 +114,7 @@ lstat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, e
|
||||
if err != ERROR_NONE {
|
||||
return;
|
||||
}
|
||||
fi.name = path.base(fi.fullpath);
|
||||
fi.name = path_base(fi.fullpath);
|
||||
return fi, ERROR_NONE;
|
||||
}
|
||||
|
||||
@@ -103,7 +132,7 @@ stat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, er
|
||||
if err != ERROR_NONE {
|
||||
return;
|
||||
}
|
||||
fi.name = path.base(fi.fullpath);
|
||||
fi.name = path_base(fi.fullpath);
|
||||
return fi, ERROR_NONE;
|
||||
}
|
||||
|
||||
@@ -121,6 +150,6 @@ fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, err
|
||||
if err != ERROR_NONE {
|
||||
return;
|
||||
}
|
||||
fi.name = path.base(fi.fullpath);
|
||||
fi.name = path_base(fi.fullpath);
|
||||
return fi, ERROR_NONE;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
//+build linux, darwin, freebsd
|
||||
package filepath
|
||||
|
||||
when ODIN_OS == "darwin" {
|
||||
foreign import libc "System.framework"
|
||||
} else {
|
||||
foreign import libc "system:c"
|
||||
}
|
||||
|
||||
import "core:strings"
|
||||
import "core:os"
|
||||
|
||||
SEPARATOR :: '/';
|
||||
SEPARATOR_STRING :: `/`;
|
||||
@@ -17,11 +22,20 @@ is_abs :: proc(path: string) -> bool {
|
||||
}
|
||||
|
||||
abs :: proc(path: string, allocator := context.allocator) -> (string, bool) {
|
||||
full_path, err := os.absolute_path_from_relative(path);
|
||||
if err != os.ERROR_NONE {
|
||||
return "", false;
|
||||
rel := path;
|
||||
if rel == "" {
|
||||
rel = ".";
|
||||
}
|
||||
return full_path, true;
|
||||
rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator);
|
||||
path_ptr := realpath(rel_cstr, nil);
|
||||
if path_ptr == nil {
|
||||
return "", __error()^ == 0;
|
||||
}
|
||||
defer _unix_free(path_ptr);
|
||||
|
||||
path_cstr := cstring(path_ptr);
|
||||
path = strings.clone(string(path_cstr), allocator);
|
||||
return path, true;
|
||||
}
|
||||
|
||||
join :: proc(elems: ..string, allocator := context.allocator) -> string {
|
||||
@@ -32,4 +46,22 @@ join :: proc(elems: ..string, allocator := context.allocator) -> string {
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
foreign libc {
|
||||
realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr ---
|
||||
@(link_name="free") _unix_free :: proc(ptr: rawptr) ---
|
||||
|
||||
}
|
||||
when ODIN_OS == "darwin" {
|
||||
@(private)
|
||||
foreign libc {
|
||||
@(link_name="__error") __error :: proc() -> ^i32 ---
|
||||
}
|
||||
} else {
|
||||
@(private)
|
||||
foreign libc {
|
||||
@(link_name="__errno_location") __error :: proc() -> ^i32 ---
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ floattidf :: proc(a: i128) -> f64 {
|
||||
s := a >> (N-1);
|
||||
a = (a ~ s) - s;
|
||||
sd: = N - _clz_i128(a); // number of significant digits
|
||||
e := u32(sd - 1); // exponent
|
||||
e := u32(sd - 1); // exponent
|
||||
if sd > DBL_MANT_DIG {
|
||||
switch sd {
|
||||
case DBL_MANT_DIG + 1:
|
||||
@@ -115,8 +115,8 @@ floattidf :: proc(a: i128) -> f64 {
|
||||
i128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0);
|
||||
};
|
||||
|
||||
a |= i128((a & 4) != 0);
|
||||
a += 1;
|
||||
a |= i128((a & 4) != 0);
|
||||
a += 1;
|
||||
a >>= 2;
|
||||
|
||||
if a & (1 << DBL_MANT_DIG) != 0 {
|
||||
@@ -127,9 +127,9 @@ floattidf :: proc(a: i128) -> f64 {
|
||||
a <<= u128(DBL_MANT_DIG - sd);
|
||||
}
|
||||
fb: [2]u32;
|
||||
fb[1] = (u32(s) & 0x80000000) | // sign
|
||||
((e + 1023) << 20) | // exponent
|
||||
((u32(a) >> 32) & 0x000FFFFF); // mantissa-high
|
||||
fb[1] = u32(a); // mantissa-low
|
||||
fb[1] = (u32(s) & 0x80000000) | // sign
|
||||
((e + 1023) << 20) | // exponent
|
||||
u32((u64(a) >> 32) & 0x000FFFFF); // mantissa-high
|
||||
fb[1] = u32(a); // mantissa-low
|
||||
return transmute(f64)fb;
|
||||
}
|
||||
|
||||
@@ -2,134 +2,134 @@ package runtime
|
||||
|
||||
@(link_name="__umodti3")
|
||||
umodti3 :: proc "c" (a, b: u128) -> u128 {
|
||||
r: u128 = ---;
|
||||
_ = udivmod128(a, b, &r);
|
||||
return r;
|
||||
r: u128 = ---;
|
||||
_ = udivmod128(a, b, &r);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
@(link_name="__udivmodti4")
|
||||
udivmodti4 :: proc "c" (a, b: u128, rem: ^u128) -> u128 {
|
||||
return udivmod128(a, b, rem);
|
||||
return udivmod128(a, b, rem);
|
||||
}
|
||||
|
||||
@(link_name="__udivti3")
|
||||
udivti3 :: proc "c" (a, b: u128) -> u128 {
|
||||
return udivmodti4(a, b, nil);
|
||||
return udivmodti4(a, b, nil);
|
||||
}
|
||||
|
||||
|
||||
@(link_name="__modti3")
|
||||
modti3 :: proc "c" (a, b: i128) -> i128 {
|
||||
s_a := a >> (128 - 1);
|
||||
s_b := b >> (128 - 1);
|
||||
an := (a ~ s_a) - s_a;
|
||||
bn := (b ~ s_b) - s_b;
|
||||
s_a := a >> (128 - 1);
|
||||
s_b := b >> (128 - 1);
|
||||
an := (a ~ s_a) - s_a;
|
||||
bn := (b ~ s_b) - s_b;
|
||||
|
||||
r: u128 = ---;
|
||||
_ = udivmod128(transmute(u128)an, transmute(u128)bn, &r);
|
||||
return (transmute(i128)r ~ s_a) - s_a;
|
||||
r: u128 = ---;
|
||||
_ = udivmod128(transmute(u128)an, transmute(u128)bn, &r);
|
||||
return (transmute(i128)r ~ s_a) - s_a;
|
||||
}
|
||||
|
||||
|
||||
@(link_name="__divmodti4")
|
||||
divmodti4 :: proc "c" (a, b: i128, rem: ^i128) -> i128 {
|
||||
u := udivmod128(transmute(u128)a, transmute(u128)b, cast(^u128)rem);
|
||||
return transmute(i128)u;
|
||||
u := udivmod128(transmute(u128)a, transmute(u128)b, cast(^u128)rem);
|
||||
return transmute(i128)u;
|
||||
}
|
||||
|
||||
@(link_name="__divti3")
|
||||
divti3 :: proc "c" (a, b: i128) -> i128 {
|
||||
u := udivmodti4(transmute(u128)a, transmute(u128)b, nil);
|
||||
return transmute(i128)u;
|
||||
u := udivmodti4(transmute(u128)a, transmute(u128)b, nil);
|
||||
return transmute(i128)u;
|
||||
}
|
||||
|
||||
|
||||
@(link_name="__fixdfti")
|
||||
fixdfti :: proc(a: u64) -> i128 {
|
||||
significandBits :: 52;
|
||||
typeWidth :: (size_of(u64)*8);
|
||||
exponentBits :: (typeWidth - significandBits - 1);
|
||||
maxExponent :: ((1 << exponentBits) - 1);
|
||||
exponentBias :: (maxExponent >> 1);
|
||||
significandBits :: 52;
|
||||
typeWidth :: (size_of(u64)*8);
|
||||
exponentBits :: (typeWidth - significandBits - 1);
|
||||
maxExponent :: ((1 << exponentBits) - 1);
|
||||
exponentBias :: (maxExponent >> 1);
|
||||
|
||||
implicitBit :: (u64(1) << significandBits);
|
||||
significandMask :: (implicitBit - 1);
|
||||
signBit :: (u64(1) << (significandBits + exponentBits));
|
||||
absMask :: (signBit - 1);
|
||||
exponentMask :: (absMask ~ significandMask);
|
||||
implicitBit :: (u64(1) << significandBits);
|
||||
significandMask :: (implicitBit - 1);
|
||||
signBit :: (u64(1) << (significandBits + exponentBits));
|
||||
absMask :: (signBit - 1);
|
||||
exponentMask :: (absMask ~ significandMask);
|
||||
|
||||
// Break a into sign, exponent, significand
|
||||
aRep := a;
|
||||
aAbs := aRep & absMask;
|
||||
sign := i128(-1 if aRep & signBit != 0 else 1);
|
||||
exponent := (aAbs >> significandBits) - exponentBias;
|
||||
significand := (aAbs & significandMask) | implicitBit;
|
||||
// Break a into sign, exponent, significand
|
||||
aRep := a;
|
||||
aAbs := aRep & absMask;
|
||||
sign := i128(-1 if aRep & signBit != 0 else 1);
|
||||
exponent := (aAbs >> significandBits) - exponentBias;
|
||||
significand := (aAbs & significandMask) | implicitBit;
|
||||
|
||||
// If exponent is negative, the result is zero.
|
||||
if exponent < 0 {
|
||||
return 0;
|
||||
}
|
||||
// If exponent is negative, the result is zero.
|
||||
if exponent < 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If the value is too large for the integer type, saturate.
|
||||
if exponent >= size_of(i128) * 8 {
|
||||
return max(i128) if sign == 1 else min(i128);
|
||||
}
|
||||
// If the value is too large for the integer type, saturate.
|
||||
if exponent >= size_of(i128) * 8 {
|
||||
return max(i128) if sign == 1 else min(i128);
|
||||
}
|
||||
|
||||
// If 0 <= exponent < significandBits, right shift to get the result.
|
||||
// Otherwise, shift left.
|
||||
if exponent < significandBits {
|
||||
return sign * i128(significand >> (significandBits - exponent));
|
||||
} else {
|
||||
return sign * (i128(significand) << (exponent - significandBits));
|
||||
}
|
||||
// If 0 <= exponent < significandBits, right shift to get the result.
|
||||
// Otherwise, shift left.
|
||||
if exponent < significandBits {
|
||||
return sign * i128(significand >> (significandBits - exponent));
|
||||
} else {
|
||||
return sign * (i128(significand) << (exponent - significandBits));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@(default_calling_convention = "none")
|
||||
foreign {
|
||||
@(link_name="llvm.ctlz.i128") _clz_i128 :: proc(x: i128, is_zero_undef := false) -> i128 ---
|
||||
@(link_name="llvm.ctlz.i128") _clz_i128 :: proc(x: i128, is_zero_undef := false) -> i128 ---
|
||||
}
|
||||
|
||||
|
||||
@(link_name="__floattidf")
|
||||
floattidf :: proc(a: i128) -> f64 {
|
||||
DBL_MANT_DIG :: 53;
|
||||
if a == 0 {
|
||||
return 0.0;
|
||||
}
|
||||
a := a;
|
||||
N :: size_of(i128) * 8;
|
||||
s := a >> (N-1);
|
||||
a = (a ~ s) - s;
|
||||
sd: = N - _clz_i128(a); // number of significant digits
|
||||
e := u32(sd - 1); // exponent
|
||||
if sd > DBL_MANT_DIG {
|
||||
switch sd {
|
||||
case DBL_MANT_DIG + 1:
|
||||
a <<= 1;
|
||||
case DBL_MANT_DIG + 2:
|
||||
// okay
|
||||
case:
|
||||
a = i128(u128(a) >> u128(sd - (DBL_MANT_DIG+2))) |
|
||||
i128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0);
|
||||
};
|
||||
DBL_MANT_DIG :: 53;
|
||||
if a == 0 {
|
||||
return 0.0;
|
||||
}
|
||||
a := a;
|
||||
N :: size_of(i128) * 8;
|
||||
s := a >> (N-1);
|
||||
a = (a ~ s) - s;
|
||||
sd: = N - _clz_i128(a); // number of significant digits
|
||||
e := u32(sd - 1); // exponent
|
||||
if sd > DBL_MANT_DIG {
|
||||
switch sd {
|
||||
case DBL_MANT_DIG + 1:
|
||||
a <<= 1;
|
||||
case DBL_MANT_DIG + 2:
|
||||
// okay
|
||||
case:
|
||||
a = i128(u128(a) >> u128(sd - (DBL_MANT_DIG+2))) |
|
||||
i128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0);
|
||||
};
|
||||
|
||||
a |= i128((a & 4) != 0);
|
||||
a += 1;
|
||||
a >>= 2;
|
||||
a |= i128((a & 4) != 0);
|
||||
a += 1;
|
||||
a >>= 2;
|
||||
|
||||
if a & (1 << DBL_MANT_DIG) != 0 {
|
||||
a >>= 1;
|
||||
e += 1;
|
||||
}
|
||||
} else {
|
||||
a <<= u128(DBL_MANT_DIG - sd);
|
||||
}
|
||||
fb: [2]u32;
|
||||
fb[1] = (u32(s) & 0x80000000) | // sign
|
||||
((e + 1023) << 20) | // exponent
|
||||
((u32(a) >> 32) & 0x000FFFFF); // mantissa-high
|
||||
fb[1] = u32(a); // mantissa-low
|
||||
return transmute(f64)fb;
|
||||
if a & (1 << DBL_MANT_DIG) != 0 {
|
||||
a >>= 1;
|
||||
e += 1;
|
||||
}
|
||||
} else {
|
||||
a <<= u128(DBL_MANT_DIG - sd);
|
||||
}
|
||||
fb: [2]u32;
|
||||
fb[1] = (u32(s) & 0x80000000) | // sign
|
||||
((e + 1023) << 20) | // exponent
|
||||
u32((u64(a) >> 32) & 0x000FFFFF); // mantissa-high
|
||||
fb[1] = u32(a); // mantissa-low
|
||||
return transmute(f64)fb;
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ simple_equal :: proc(a, b: $T/[]$E) -> bool where intrinsics.type_is_simple_comp
|
||||
}
|
||||
|
||||
|
||||
has_prefix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_comparable(E) {
|
||||
has_prefix :: proc(array: $T/[]$E, needle: E) -> bool where intrinsics.type_is_comparable(E) {
|
||||
n := len(needle);
|
||||
if len(array) >= n {
|
||||
return equal(array[:n], needle);
|
||||
@@ -124,7 +124,7 @@ has_prefix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_c
|
||||
}
|
||||
|
||||
|
||||
has_suffix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_comparable(E) {
|
||||
has_suffix :: proc(array: $T/[]$E, needle: E) -> bool where intrinsics.type_is_comparable(E) {
|
||||
array := array;
|
||||
m, n := len(array), len(needle);
|
||||
if m >= n {
|
||||
@@ -133,7 +133,7 @@ has_suffix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_c
|
||||
return false;
|
||||
}
|
||||
|
||||
fill :: proc(array: $T/[]$E, value: T) {
|
||||
fill :: proc(array: $T/[]$E, value: E) {
|
||||
for _, i in array {
|
||||
array[i] = value;
|
||||
}
|
||||
@@ -281,7 +281,7 @@ reduce :: proc(s: $S/[]$U, initializer: $V, f: proc(V, U) -> V) -> V {
|
||||
}
|
||||
|
||||
filter :: proc(s: $S/[]$U, f: proc(U) -> bool, allocator := context.allocator) -> S {
|
||||
r := make([dynamic]S, 0, 0, allocator);
|
||||
r := make([dynamic]U, 0, 0, allocator);
|
||||
for v in s {
|
||||
if f(v) {
|
||||
append(&r, v);
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package sync2
|
||||
|
||||
import "intrinsics"
|
||||
|
||||
// TODO(bill): Is this even a good design? The intrinsics seem to be more than good enough and just as clean
|
||||
|
||||
cpu_relax :: intrinsics.cpu_relax;
|
||||
|
||||
atomic_fence :: intrinsics.atomic_fence;
|
||||
atomic_fence_acq :: intrinsics.atomic_fence_acq;
|
||||
atomic_fence_rel :: intrinsics.atomic_fence_rel;
|
||||
atomic_fence_acqrel :: intrinsics.atomic_fence_acqrel;
|
||||
|
||||
atomic_store :: intrinsics.atomic_store;
|
||||
atomic_store_rel :: intrinsics.atomic_store_rel;
|
||||
atomic_store_relaxed :: intrinsics.atomic_store_relaxed;
|
||||
atomic_store_unordered :: intrinsics.atomic_store_unordered;
|
||||
|
||||
atomic_load :: intrinsics.atomic_load;
|
||||
atomic_load_acq :: intrinsics.atomic_load_acq;
|
||||
atomic_load_relaxed :: intrinsics.atomic_load_relaxed;
|
||||
atomic_load_unordered :: intrinsics.atomic_load_unordered;
|
||||
|
||||
atomic_add :: intrinsics.atomic_add;
|
||||
atomic_add_acq :: intrinsics.atomic_add_acq;
|
||||
atomic_add_rel :: intrinsics.atomic_add_rel;
|
||||
atomic_add_acqrel :: intrinsics.atomic_add_acqrel;
|
||||
atomic_add_relaxed :: intrinsics.atomic_add_relaxed;
|
||||
atomic_sub :: intrinsics.atomic_sub;
|
||||
atomic_sub_acq :: intrinsics.atomic_sub_acq;
|
||||
atomic_sub_rel :: intrinsics.atomic_sub_rel;
|
||||
atomic_sub_acqrel :: intrinsics.atomic_sub_acqrel;
|
||||
atomic_sub_relaxed :: intrinsics.atomic_sub_relaxed;
|
||||
atomic_and :: intrinsics.atomic_and;
|
||||
atomic_and_acq :: intrinsics.atomic_and_acq;
|
||||
atomic_and_rel :: intrinsics.atomic_and_rel;
|
||||
atomic_and_acqrel :: intrinsics.atomic_and_acqrel;
|
||||
atomic_and_relaxed :: intrinsics.atomic_and_relaxed;
|
||||
atomic_nand :: intrinsics.atomic_nand;
|
||||
atomic_nand_acq :: intrinsics.atomic_nand_acq;
|
||||
atomic_nand_rel :: intrinsics.atomic_nand_rel;
|
||||
atomic_nand_acqrel :: intrinsics.atomic_nand_acqrel;
|
||||
atomic_nand_relaxed :: intrinsics.atomic_nand_relaxed;
|
||||
atomic_or :: intrinsics.atomic_or;
|
||||
atomic_or_acq :: intrinsics.atomic_or_acq;
|
||||
atomic_or_rel :: intrinsics.atomic_or_rel;
|
||||
atomic_or_acqrel :: intrinsics.atomic_or_acqrel;
|
||||
atomic_or_relaxed :: intrinsics.atomic_or_relaxed;
|
||||
atomic_xor :: intrinsics.atomic_xor;
|
||||
atomic_xor_acq :: intrinsics.atomic_xor_acq;
|
||||
atomic_xor_rel :: intrinsics.atomic_xor_rel;
|
||||
atomic_xor_acqrel :: intrinsics.atomic_xor_acqrel;
|
||||
atomic_xor_relaxed :: intrinsics.atomic_xor_relaxed;
|
||||
|
||||
atomic_xchg :: intrinsics.atomic_xchg;
|
||||
atomic_xchg_acq :: intrinsics.atomic_xchg_acq;
|
||||
atomic_xchg_rel :: intrinsics.atomic_xchg_rel;
|
||||
atomic_xchg_acqrel :: intrinsics.atomic_xchg_acqrel;
|
||||
atomic_xchg_relaxed :: intrinsics.atomic_xchg_relaxed;
|
||||
|
||||
atomic_cxchg :: intrinsics.atomic_cxchg;
|
||||
atomic_cxchg_acq :: intrinsics.atomic_cxchg_acq;
|
||||
atomic_cxchg_rel :: intrinsics.atomic_cxchg_rel;
|
||||
atomic_cxchg_acqrel :: intrinsics.atomic_cxchg_acqrel;
|
||||
atomic_cxchg_relaxed :: intrinsics.atomic_cxchg_relaxed;
|
||||
atomic_cxchg_failrelaxed :: intrinsics.atomic_cxchg_failrelaxed;
|
||||
atomic_cxchg_failacq :: intrinsics.atomic_cxchg_failacq;
|
||||
atomic_cxchg_acq_failrelaxed :: intrinsics.atomic_cxchg_acq_failrelaxed;
|
||||
atomic_cxchg_acqrel_failrelaxed :: intrinsics.atomic_cxchg_acqrel_failrelaxed;
|
||||
|
||||
atomic_cxchgweak :: intrinsics.atomic_cxchgweak;
|
||||
atomic_cxchgweak_acq :: intrinsics.atomic_cxchgweak_acq;
|
||||
atomic_cxchgweak_rel :: intrinsics.atomic_cxchgweak_rel;
|
||||
atomic_cxchgweak_acqrel :: intrinsics.atomic_cxchgweak_acqrel;
|
||||
atomic_cxchgweak_relaxed :: intrinsics.atomic_cxchgweak_relaxed;
|
||||
atomic_cxchgweak_failrelaxed :: intrinsics.atomic_cxchgweak_failrelaxed;
|
||||
atomic_cxchgweak_failacq :: intrinsics.atomic_cxchgweak_failacq;
|
||||
atomic_cxchgweak_acq_failrelaxed :: intrinsics.atomic_cxchgweak_acq_failrelaxed;
|
||||
atomic_cxchgweak_acqrel_failrelaxed :: intrinsics.atomic_cxchgweak_acqrel_failrelaxed;
|
||||
@@ -0,0 +1,886 @@
|
||||
package sync2
|
||||
|
||||
// TODO(bill): The Channel implementation needs a complete rewrite for this new package sync design
|
||||
// Especially how the `select` things work
|
||||
|
||||
import "core:mem"
|
||||
import "core:time"
|
||||
import "core:math/rand"
|
||||
|
||||
_, _ :: time, rand;
|
||||
|
||||
Channel_Direction :: enum i8 {
|
||||
Both = 0,
|
||||
Send = +1,
|
||||
Recv = -1,
|
||||
}
|
||||
|
||||
Channel :: struct(T: typeid, Direction := Channel_Direction.Both) {
|
||||
using _internal: ^Raw_Channel,
|
||||
}
|
||||
|
||||
channel_init :: proc(ch: ^$C/Channel($T, $D), cap := 0, allocator := context.allocator) {
|
||||
context.allocator = allocator;
|
||||
ch._internal = raw_channel_create(size_of(T), align_of(T), cap);
|
||||
return;
|
||||
}
|
||||
|
||||
channel_make :: proc($T: typeid, cap := 0, allocator := context.allocator) -> (ch: Channel(T, .Both)) {
|
||||
context.allocator = allocator;
|
||||
ch._internal = raw_channel_create(size_of(T), align_of(T), cap);
|
||||
return;
|
||||
}
|
||||
|
||||
channel_make_send :: proc($T: typeid, cap := 0, allocator := context.allocator) -> (ch: Channel(T, .Send)) {
|
||||
context.allocator = allocator;
|
||||
ch._internal = raw_channel_create(size_of(T), align_of(T), cap);
|
||||
return;
|
||||
}
|
||||
channel_make_recv :: proc($T: typeid, cap := 0, allocator := context.allocator) -> (ch: Channel(T, .Recv)) {
|
||||
context.allocator = allocator;
|
||||
ch._internal = raw_channel_create(size_of(T), align_of(T), cap);
|
||||
return;
|
||||
}
|
||||
|
||||
channel_destroy :: proc(ch: $C/Channel($T, $D)) {
|
||||
raw_channel_destroy(ch._internal);
|
||||
}
|
||||
|
||||
channel_as_send :: proc(ch: $C/Channel($T, .Both)) -> (res: Channel(T, .Send)) {
|
||||
res._internal = ch._internal;
|
||||
return;
|
||||
}
|
||||
|
||||
channel_as_recv :: proc(ch: $C/Channel($T, .Both)) -> (res: Channel(T, .Recv)) {
|
||||
res._internal = ch._internal;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
channel_len :: proc(ch: $C/Channel($T, $D)) -> int {
|
||||
return ch._internal.len if ch._internal != nil else 0;
|
||||
}
|
||||
channel_cap :: proc(ch: $C/Channel($T, $D)) -> int {
|
||||
return ch._internal.cap if ch._internal != nil else 0;
|
||||
}
|
||||
|
||||
|
||||
channel_send :: proc(ch: $C/Channel($T, $D), msg: T, loc := #caller_location) where D >= .Both {
|
||||
msg := msg;
|
||||
_ = raw_channel_send_impl(ch._internal, &msg, /*block*/true, loc);
|
||||
}
|
||||
channel_try_send :: proc(ch: $C/Channel($T, $D), msg: T, loc := #caller_location) -> bool where D >= .Both {
|
||||
msg := msg;
|
||||
return raw_channel_send_impl(ch._internal, &msg, /*block*/false, loc);
|
||||
}
|
||||
|
||||
channel_recv :: proc(ch: $C/Channel($T, $D), loc := #caller_location) -> (msg: T) where D <= .Both {
|
||||
c := ch._internal;
|
||||
if c == nil {
|
||||
panic(message="cannot recv message; channel is nil", loc=loc);
|
||||
}
|
||||
mutex_lock(&c.mutex);
|
||||
raw_channel_recv_impl(c, &msg, loc);
|
||||
mutex_unlock(&c.mutex);
|
||||
return;
|
||||
}
|
||||
channel_try_recv :: proc(ch: $C/Channel($T, $D), loc := #caller_location) -> (msg: T, ok: bool) where D <= .Both {
|
||||
c := ch._internal;
|
||||
if c != nil && mutex_try_lock(&c.mutex) {
|
||||
if c.len > 0 {
|
||||
raw_channel_recv_impl(c, &msg, loc);
|
||||
ok = true;
|
||||
}
|
||||
mutex_unlock(&c.mutex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
channel_try_recv_ptr :: proc(ch: $C/Channel($T, $D), msg: ^T, loc := #caller_location) -> (ok: bool) where D <= .Both {
|
||||
res: T;
|
||||
res, ok = channel_try_recv(ch, loc);
|
||||
if ok && msg != nil {
|
||||
msg^ = res;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
channel_is_nil :: proc(ch: $C/Channel($T, $D)) -> bool {
|
||||
return ch._internal == nil;
|
||||
}
|
||||
channel_is_open :: proc(ch: $C/Channel($T, $D)) -> bool {
|
||||
c := ch._internal;
|
||||
return c != nil && !c.closed;
|
||||
}
|
||||
|
||||
|
||||
channel_eq :: proc(a, b: $C/Channel($T, $D)) -> bool {
|
||||
return a._internal == b._internal;
|
||||
}
|
||||
channel_ne :: proc(a, b: $C/Channel($T, $D)) -> bool {
|
||||
return a._internal != b._internal;
|
||||
}
|
||||
|
||||
|
||||
channel_can_send :: proc(ch: $C/Channel($T, $D)) -> (ok: bool) where D >= .Both {
|
||||
return raw_channel_can_send(ch._internal);
|
||||
}
|
||||
channel_can_recv :: proc(ch: $C/Channel($T, $D)) -> (ok: bool) where D <= .Both {
|
||||
return raw_channel_can_recv(ch._internal);
|
||||
}
|
||||
|
||||
|
||||
channel_peek :: proc(ch: $C/Channel($T, $D)) -> int {
|
||||
c := ch._internal;
|
||||
if c == nil {
|
||||
return -1;
|
||||
}
|
||||
if atomic_load(&c.closed) {
|
||||
return -1;
|
||||
}
|
||||
return atomic_load(&c.len);
|
||||
}
|
||||
|
||||
|
||||
channel_close :: proc(ch: $C/Channel($T, $D), loc := #caller_location) {
|
||||
raw_channel_close(ch._internal, loc);
|
||||
}
|
||||
|
||||
|
||||
channel_iterator :: proc(ch: $C/Channel($T, $D)) -> (msg: T, ok: bool) where D <= .Both {
|
||||
c := ch._internal;
|
||||
if c == nil {
|
||||
return;
|
||||
}
|
||||
|
||||
if !c.closed || c.len > 0 {
|
||||
msg, ok = channel_recv(ch), true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
channel_drain :: proc(ch: $C/Channel($T, $D)) where D >= .Both {
|
||||
raw_channel_drain(ch._internal);
|
||||
}
|
||||
|
||||
|
||||
channel_move :: proc(dst: $C1/Channel($T, $D1) src: $C2/Channel(T, $D2)) where D1 <= .Both, D2 >= .Both {
|
||||
for msg in channel_iterator(src) {
|
||||
channel_send(dst, msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Raw_Channel_Wait_Queue :: struct {
|
||||
next: ^Raw_Channel_Wait_Queue,
|
||||
state: ^uintptr,
|
||||
}
|
||||
|
||||
|
||||
Raw_Channel :: struct {
|
||||
closed: bool,
|
||||
ready: bool, // ready to recv
|
||||
data_offset: u16, // data is stored at the end of this data structure
|
||||
elem_size: u32,
|
||||
len, cap: int,
|
||||
read, write: int,
|
||||
mutex: Mutex,
|
||||
cond: Cond,
|
||||
allocator: mem.Allocator,
|
||||
|
||||
sendq: ^Raw_Channel_Wait_Queue,
|
||||
recvq: ^Raw_Channel_Wait_Queue,
|
||||
}
|
||||
|
||||
raw_channel_wait_queue_insert :: proc(head: ^^Raw_Channel_Wait_Queue, val: ^Raw_Channel_Wait_Queue) {
|
||||
val.next = head^;
|
||||
head^ = val;
|
||||
}
|
||||
raw_channel_wait_queue_remove :: proc(head: ^^Raw_Channel_Wait_Queue, val: ^Raw_Channel_Wait_Queue) {
|
||||
p := head;
|
||||
for p^ != nil && p^ != val {
|
||||
p = &p^.next;
|
||||
}
|
||||
if p != nil {
|
||||
p^ = p^.next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
raw_channel_create :: proc(elem_size, elem_align: int, cap := 0) -> ^Raw_Channel {
|
||||
assert(int(u32(elem_size)) == elem_size);
|
||||
|
||||
s := size_of(Raw_Channel);
|
||||
s = mem.align_forward_int(s, elem_align);
|
||||
data_offset := uintptr(s);
|
||||
s += elem_size * max(cap, 1);
|
||||
|
||||
a := max(elem_align, align_of(Raw_Channel));
|
||||
|
||||
c := (^Raw_Channel)(mem.alloc(s, a));
|
||||
if c == nil {
|
||||
return nil;
|
||||
}
|
||||
|
||||
c.data_offset = u16(data_offset);
|
||||
c.elem_size = u32(elem_size);
|
||||
c.len, c.cap = 0, max(cap, 0);
|
||||
c.read, c.write = 0, 0;
|
||||
c.allocator = context.allocator;
|
||||
c.closed = false;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
raw_channel_destroy :: proc(c: ^Raw_Channel) {
|
||||
if c == nil {
|
||||
return;
|
||||
}
|
||||
context.allocator = c.allocator;
|
||||
atomic_store(&c.closed, true);
|
||||
free(c);
|
||||
}
|
||||
|
||||
raw_channel_close :: proc(c: ^Raw_Channel, loc := #caller_location) {
|
||||
if c == nil {
|
||||
panic(message="cannot close nil channel", loc=loc);
|
||||
}
|
||||
mutex_lock(&c.mutex);
|
||||
defer mutex_unlock(&c.mutex);
|
||||
atomic_store(&c.closed, true);
|
||||
|
||||
// Release readers and writers
|
||||
raw_channel_wait_queue_broadcast(c.recvq);
|
||||
raw_channel_wait_queue_broadcast(c.sendq);
|
||||
cond_broadcast(&c.cond);
|
||||
}
|
||||
|
||||
|
||||
|
||||
raw_channel_send_impl :: proc(c: ^Raw_Channel, msg: rawptr, block: bool, loc := #caller_location) -> bool {
|
||||
send :: proc(c: ^Raw_Channel, src: rawptr) {
|
||||
data := uintptr(c) + uintptr(c.data_offset);
|
||||
dst := data + uintptr(c.write * int(c.elem_size));
|
||||
mem.copy(rawptr(dst), src, int(c.elem_size));
|
||||
c.len += 1;
|
||||
c.write = (c.write + 1) % max(c.cap, 1);
|
||||
}
|
||||
|
||||
switch {
|
||||
case c == nil:
|
||||
panic(message="cannot send message; channel is nil", loc=loc);
|
||||
case c.closed:
|
||||
panic(message="cannot send message; channel is closed", loc=loc);
|
||||
}
|
||||
|
||||
mutex_lock(&c.mutex);
|
||||
defer mutex_unlock(&c.mutex);
|
||||
|
||||
if c.cap > 0 {
|
||||
if !block && c.len >= c.cap {
|
||||
return false;
|
||||
}
|
||||
|
||||
for c.len >= c.cap {
|
||||
cond_wait(&c.cond, &c.mutex);
|
||||
}
|
||||
} else if c.len > 0 { // TODO(bill): determine correct behaviour
|
||||
if !block {
|
||||
return false;
|
||||
}
|
||||
cond_wait(&c.cond, &c.mutex);
|
||||
} else if c.len == 0 && !block {
|
||||
return false;
|
||||
}
|
||||
|
||||
send(c, msg);
|
||||
cond_signal(&c.cond);
|
||||
raw_channel_wait_queue_signal(c.recvq);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
raw_channel_recv_impl :: proc(c: ^Raw_Channel, res: rawptr, loc := #caller_location) {
|
||||
recv :: proc(c: ^Raw_Channel, dst: rawptr, loc := #caller_location) {
|
||||
if c.len < 1 {
|
||||
panic(message="cannot recv message; channel is empty", loc=loc);
|
||||
}
|
||||
c.len -= 1;
|
||||
|
||||
data := uintptr(c) + uintptr(c.data_offset);
|
||||
src := data + uintptr(c.read * int(c.elem_size));
|
||||
mem.copy(dst, rawptr(src), int(c.elem_size));
|
||||
c.read = (c.read + 1) % max(c.cap, 1);
|
||||
}
|
||||
|
||||
if c == nil {
|
||||
panic(message="cannot recv message; channel is nil", loc=loc);
|
||||
}
|
||||
atomic_store(&c.ready, true);
|
||||
for c.len < 1 {
|
||||
raw_channel_wait_queue_signal(c.sendq);
|
||||
cond_wait(&c.cond, &c.mutex);
|
||||
}
|
||||
atomic_store(&c.ready, false);
|
||||
recv(c, res, loc);
|
||||
if c.cap > 0 {
|
||||
if c.len == c.cap - 1 {
|
||||
// NOTE(bill): Only signal on the last one
|
||||
cond_signal(&c.cond);
|
||||
}
|
||||
} else {
|
||||
cond_signal(&c.cond);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
raw_channel_can_send :: proc(c: ^Raw_Channel) -> (ok: bool) {
|
||||
if c == nil {
|
||||
return false;
|
||||
}
|
||||
mutex_lock(&c.mutex);
|
||||
switch {
|
||||
case c.closed:
|
||||
ok = false;
|
||||
case c.cap > 0:
|
||||
ok = c.ready && c.len < c.cap;
|
||||
case:
|
||||
ok = c.ready && c.len == 0;
|
||||
}
|
||||
mutex_unlock(&c.mutex);
|
||||
return;
|
||||
}
|
||||
raw_channel_can_recv :: proc(c: ^Raw_Channel) -> (ok: bool) {
|
||||
if c == nil {
|
||||
return false;
|
||||
}
|
||||
mutex_lock(&c.mutex);
|
||||
ok = c.len > 0;
|
||||
mutex_unlock(&c.mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
raw_channel_drain :: proc(c: ^Raw_Channel) {
|
||||
if c == nil {
|
||||
return;
|
||||
}
|
||||
mutex_lock(&c.mutex);
|
||||
c.len = 0;
|
||||
c.read = 0;
|
||||
c.write = 0;
|
||||
mutex_unlock(&c.mutex);
|
||||
}
|
||||
|
||||
|
||||
|
||||
MAX_SELECT_CHANNELS :: 64;
|
||||
SELECT_MAX_TIMEOUT :: max(time.Duration);
|
||||
|
||||
Select_Command :: enum {
|
||||
Recv,
|
||||
Send,
|
||||
}
|
||||
|
||||
Select_Channel :: struct {
|
||||
channel: ^Raw_Channel,
|
||||
command: Select_Command,
|
||||
}
|
||||
|
||||
|
||||
|
||||
select :: proc(channels: ..Select_Channel) -> (index: int) {
|
||||
return select_timeout(SELECT_MAX_TIMEOUT, ..channels);
|
||||
}
|
||||
select_timeout :: proc(timeout: time.Duration, channels: ..Select_Channel) -> (index: int) {
|
||||
switch len(channels) {
|
||||
case 0:
|
||||
panic("sync: select with no channels");
|
||||
}
|
||||
|
||||
assert(len(channels) <= MAX_SELECT_CHANNELS);
|
||||
|
||||
backing: [MAX_SELECT_CHANNELS]int;
|
||||
queues: [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue;
|
||||
candidates := backing[:];
|
||||
cap := len(channels);
|
||||
candidates = candidates[:cap];
|
||||
|
||||
count := u32(0);
|
||||
for c, i in channels {
|
||||
if c.channel == nil {
|
||||
continue;
|
||||
}
|
||||
switch c.command {
|
||||
case .Recv:
|
||||
if raw_channel_can_recv(c.channel) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
case .Send:
|
||||
if raw_channel_can_send(c.channel) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
wait_state: uintptr = 0;
|
||||
for _, i in channels {
|
||||
q := &queues[i];
|
||||
q.state = &wait_state;
|
||||
}
|
||||
|
||||
for c, i in channels {
|
||||
if c.channel == nil {
|
||||
continue;
|
||||
}
|
||||
q := &queues[i];
|
||||
switch c.command {
|
||||
case .Recv: raw_channel_wait_queue_insert(&c.channel.recvq, q);
|
||||
case .Send: raw_channel_wait_queue_insert(&c.channel.sendq, q);
|
||||
}
|
||||
}
|
||||
raw_channel_wait_queue_wait_on(&wait_state, timeout);
|
||||
for c, i in channels {
|
||||
if c.channel == nil {
|
||||
continue;
|
||||
}
|
||||
q := &queues[i];
|
||||
switch c.command {
|
||||
case .Recv: raw_channel_wait_queue_remove(&c.channel.recvq, q);
|
||||
case .Send: raw_channel_wait_queue_remove(&c.channel.sendq, q);
|
||||
}
|
||||
}
|
||||
|
||||
for c, i in channels {
|
||||
switch c.command {
|
||||
case .Recv:
|
||||
if raw_channel_can_recv(c.channel) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
case .Send:
|
||||
if raw_channel_can_send(c.channel) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if count == 0 && timeout == SELECT_MAX_TIMEOUT {
|
||||
index = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(count != 0);
|
||||
}
|
||||
|
||||
t := time.now();
|
||||
r := rand.create(transmute(u64)t);
|
||||
i := rand.uint32(&r);
|
||||
|
||||
index = candidates[i % count];
|
||||
return;
|
||||
}
|
||||
|
||||
select_recv :: proc(channels: ..^Raw_Channel) -> (index: int) {
|
||||
switch len(channels) {
|
||||
case 0:
|
||||
panic("sync: select with no channels");
|
||||
}
|
||||
|
||||
assert(len(channels) <= MAX_SELECT_CHANNELS);
|
||||
|
||||
backing: [MAX_SELECT_CHANNELS]int;
|
||||
queues: [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue;
|
||||
candidates := backing[:];
|
||||
cap := len(channels);
|
||||
candidates = candidates[:cap];
|
||||
|
||||
count := u32(0);
|
||||
for c, i in channels {
|
||||
if raw_channel_can_recv(c) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
state: uintptr;
|
||||
for c, i in channels {
|
||||
q := &queues[i];
|
||||
q.state = &state;
|
||||
raw_channel_wait_queue_insert(&c.recvq, q);
|
||||
}
|
||||
raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT);
|
||||
for c, i in channels {
|
||||
q := &queues[i];
|
||||
raw_channel_wait_queue_remove(&c.recvq, q);
|
||||
}
|
||||
|
||||
for c, i in channels {
|
||||
if raw_channel_can_recv(c) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
assert(count != 0);
|
||||
}
|
||||
|
||||
t := time.now();
|
||||
r := rand.create(transmute(u64)t);
|
||||
i := rand.uint32(&r);
|
||||
|
||||
index = candidates[i % count];
|
||||
return;
|
||||
}
|
||||
|
||||
select_recv_msg :: proc(channels: ..$C/Channel($T, $D)) -> (msg: T, index: int) {
|
||||
switch len(channels) {
|
||||
case 0:
|
||||
panic("sync: select with no channels");
|
||||
}
|
||||
|
||||
assert(len(channels) <= MAX_SELECT_CHANNELS);
|
||||
|
||||
queues: [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue;
|
||||
candidates: [MAX_SELECT_CHANNELS]int;
|
||||
|
||||
count := u32(0);
|
||||
for c, i in channels {
|
||||
if raw_channel_can_recv(c) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
state: uintptr;
|
||||
for c, i in channels {
|
||||
q := &queues[i];
|
||||
q.state = &state;
|
||||
raw_channel_wait_queue_insert(&c.recvq, q);
|
||||
}
|
||||
raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT);
|
||||
for c, i in channels {
|
||||
q := &queues[i];
|
||||
raw_channel_wait_queue_remove(&c.recvq, q);
|
||||
}
|
||||
|
||||
for c, i in channels {
|
||||
if raw_channel_can_recv(c) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
assert(count != 0);
|
||||
}
|
||||
|
||||
t := time.now();
|
||||
r := rand.create(transmute(u64)t);
|
||||
i := rand.uint32(&r);
|
||||
|
||||
index = candidates[i % count];
|
||||
msg = channel_recv(channels[index]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
select_send_msg :: proc(msg: $T, channels: ..$C/Channel(T, $D)) -> (index: int) {
|
||||
switch len(channels) {
|
||||
case 0:
|
||||
panic("sync: select with no channels");
|
||||
}
|
||||
|
||||
assert(len(channels) <= MAX_SELECT_CHANNELS);
|
||||
|
||||
backing: [MAX_SELECT_CHANNELS]int;
|
||||
queues: [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue;
|
||||
candidates := backing[:];
|
||||
cap := len(channels);
|
||||
candidates = candidates[:cap];
|
||||
|
||||
count := u32(0);
|
||||
for c, i in channels {
|
||||
if raw_channel_can_recv(c) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
state: uintptr;
|
||||
for c, i in channels {
|
||||
q := &queues[i];
|
||||
q.state = &state;
|
||||
raw_channel_wait_queue_insert(&c.recvq, q);
|
||||
}
|
||||
raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT);
|
||||
for c, i in channels {
|
||||
q := &queues[i];
|
||||
raw_channel_wait_queue_remove(&c.recvq, q);
|
||||
}
|
||||
|
||||
for c, i in channels {
|
||||
if raw_channel_can_recv(c) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
assert(count != 0);
|
||||
}
|
||||
|
||||
t := time.now();
|
||||
r := rand.create(transmute(u64)t);
|
||||
i := rand.uint32(&r);
|
||||
|
||||
index = candidates[i % count];
|
||||
|
||||
if msg != nil {
|
||||
channel_send(channels[index], msg);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
select_send :: proc(channels: ..^Raw_Channel) -> (index: int) {
|
||||
switch len(channels) {
|
||||
case 0:
|
||||
panic("sync: select with no channels");
|
||||
}
|
||||
|
||||
assert(len(channels) <= MAX_SELECT_CHANNELS);
|
||||
candidates: [MAX_SELECT_CHANNELS]int;
|
||||
queues: [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue;
|
||||
|
||||
count := u32(0);
|
||||
for c, i in channels {
|
||||
if raw_channel_can_send(c) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
state: uintptr;
|
||||
for c, i in channels {
|
||||
q := &queues[i];
|
||||
q.state = &state;
|
||||
raw_channel_wait_queue_insert(&c.sendq, q);
|
||||
}
|
||||
raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT);
|
||||
for c, i in channels {
|
||||
q := &queues[i];
|
||||
raw_channel_wait_queue_remove(&c.sendq, q);
|
||||
}
|
||||
|
||||
for c, i in channels {
|
||||
if raw_channel_can_send(c) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
assert(count != 0);
|
||||
}
|
||||
|
||||
t := time.now();
|
||||
r := rand.create(transmute(u64)t);
|
||||
i := rand.uint32(&r);
|
||||
|
||||
index = candidates[i % count];
|
||||
return;
|
||||
}
|
||||
|
||||
select_try :: proc(channels: ..Select_Channel) -> (index: int) {
|
||||
switch len(channels) {
|
||||
case 0:
|
||||
panic("sync: select with no channels");
|
||||
}
|
||||
|
||||
assert(len(channels) <= MAX_SELECT_CHANNELS);
|
||||
|
||||
backing: [MAX_SELECT_CHANNELS]int;
|
||||
candidates := backing[:];
|
||||
cap := len(channels);
|
||||
candidates = candidates[:cap];
|
||||
|
||||
count := u32(0);
|
||||
for c, i in channels {
|
||||
switch c.command {
|
||||
case .Recv:
|
||||
if raw_channel_can_recv(c.channel) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
case .Send:
|
||||
if raw_channel_can_send(c.channel) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
index = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
t := time.now();
|
||||
r := rand.create(transmute(u64)t);
|
||||
i := rand.uint32(&r);
|
||||
|
||||
index = candidates[i % count];
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
select_try_recv :: proc(channels: ..^Raw_Channel) -> (index: int) {
|
||||
switch len(channels) {
|
||||
case 0:
|
||||
index = -1;
|
||||
return;
|
||||
case 1:
|
||||
index = -1;
|
||||
if raw_channel_can_recv(channels[0]) {
|
||||
index = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
assert(len(channels) <= MAX_SELECT_CHANNELS);
|
||||
candidates: [MAX_SELECT_CHANNELS]int;
|
||||
|
||||
count := u32(0);
|
||||
for c, i in channels {
|
||||
if raw_channel_can_recv(c) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
index = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
t := time.now();
|
||||
r := rand.create(transmute(u64)t);
|
||||
i := rand.uint32(&r);
|
||||
|
||||
index = candidates[i % count];
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
select_try_send :: proc(channels: ..^Raw_Channel) -> (index: int) #no_bounds_check {
|
||||
switch len(channels) {
|
||||
case 0:
|
||||
return -1;
|
||||
case 1:
|
||||
if raw_channel_can_send(channels[0]) {
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
assert(len(channels) <= MAX_SELECT_CHANNELS);
|
||||
candidates: [MAX_SELECT_CHANNELS]int;
|
||||
|
||||
count := u32(0);
|
||||
for c, i in channels {
|
||||
if raw_channel_can_send(c) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
index = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
t := time.now();
|
||||
r := rand.create(transmute(u64)t);
|
||||
i := rand.uint32(&r);
|
||||
|
||||
index = candidates[i % count];
|
||||
return;
|
||||
}
|
||||
|
||||
select_try_recv_msg :: proc(channels: ..$C/Channel($T, $D)) -> (msg: T, index: int) {
|
||||
switch len(channels) {
|
||||
case 0:
|
||||
index = -1;
|
||||
return;
|
||||
case 1:
|
||||
ok: bool;
|
||||
if msg, ok = channel_try_recv(channels[0]); ok {
|
||||
index = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
assert(len(channels) <= MAX_SELECT_CHANNELS);
|
||||
candidates: [MAX_SELECT_CHANNELS]int;
|
||||
|
||||
count := u32(0);
|
||||
for c, i in channels {
|
||||
if channel_can_recv(c) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
index = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
t := time.now();
|
||||
r := rand.create(transmute(u64)t);
|
||||
i := rand.uint32(&r);
|
||||
|
||||
index = candidates[i % count];
|
||||
msg = channel_recv(channels[index]);
|
||||
return;
|
||||
}
|
||||
|
||||
select_try_send_msg :: proc(msg: $T, channels: ..$C/Channel(T, $D)) -> (index: int) {
|
||||
index = -1;
|
||||
switch len(channels) {
|
||||
case 0:
|
||||
return;
|
||||
case 1:
|
||||
if channel_try_send(channels[0], msg) {
|
||||
index = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
assert(len(channels) <= MAX_SELECT_CHANNELS);
|
||||
candidates: [MAX_SELECT_CHANNELS]int;
|
||||
|
||||
count := u32(0);
|
||||
for c, i in channels {
|
||||
if raw_channel_can_send(c) {
|
||||
candidates[count] = i;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
index = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
t := time.now();
|
||||
r := rand.create(transmute(u64)t);
|
||||
i := rand.uint32(&r);
|
||||
|
||||
index = candidates[i % count];
|
||||
channel_send(channels[index], msg);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
//+build linux, darwin, freebsd
|
||||
//+private
|
||||
package sync2
|
||||
|
||||
import "core:time"
|
||||
|
||||
raw_channel_wait_queue_wait_on :: proc(state: ^uintptr, timeout: time.Duration) {
|
||||
// stub
|
||||
}
|
||||
|
||||
raw_channel_wait_queue_signal :: proc(q: ^Raw_Channel_Wait_Queue) {
|
||||
// stub
|
||||
}
|
||||
|
||||
raw_channel_wait_queue_broadcast :: proc(q: ^Raw_Channel_Wait_Queue) {
|
||||
// stub
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//+build windows
|
||||
//+private
|
||||
package sync2
|
||||
|
||||
import win32 "core:sys/windows"
|
||||
import "core:time"
|
||||
|
||||
raw_channel_wait_queue_wait_on :: proc(state: ^uintptr, timeout: time.Duration) {
|
||||
ms: win32.DWORD = win32.INFINITE;
|
||||
if max(time.Duration) != SELECT_MAX_TIMEOUT {
|
||||
ms = win32.DWORD((max(time.duration_nanoseconds(timeout), 0) + 999999)/1000000);
|
||||
}
|
||||
|
||||
v := atomic_load(state);
|
||||
for v == 0 {
|
||||
win32.WaitOnAddress(state, &v, size_of(state^), ms);
|
||||
v = atomic_load(state);
|
||||
}
|
||||
atomic_store(state, 0);
|
||||
}
|
||||
|
||||
raw_channel_wait_queue_signal :: proc(q: ^Raw_Channel_Wait_Queue) {
|
||||
for x := q; x != nil; x = x.next {
|
||||
atomic_add(x.state, 1);
|
||||
win32.WakeByAddressSingle(x.state);
|
||||
}
|
||||
}
|
||||
|
||||
raw_channel_wait_queue_broadcast :: proc(q: ^Raw_Channel_Wait_Queue) {
|
||||
for x := q; x != nil; x = x.next {
|
||||
atomic_add(x.state, 1);
|
||||
win32.WakeByAddressAll(x.state);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
package sync2
|
||||
|
||||
import "core:runtime"
|
||||
|
||||
// A Wait_Group waits for a collection of threads to finish
|
||||
//
|
||||
// A Wait_Group must not be copied after first use
|
||||
Wait_Group :: struct {
|
||||
counter: int,
|
||||
mutex: Mutex,
|
||||
cond: Cond,
|
||||
}
|
||||
|
||||
wait_group_add :: proc(wg: ^Wait_Group, delta: int) {
|
||||
if delta == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
mutex_lock(&wg.mutex);
|
||||
defer mutex_unlock(&wg.mutex);
|
||||
|
||||
atomic_add(&wg.counter, delta);
|
||||
if wg.counter < 0 {
|
||||
panic("sync.Wait_Group negative counter");
|
||||
}
|
||||
if wg.counter == 0 {
|
||||
cond_broadcast(&wg.cond);
|
||||
if wg.counter != 0 {
|
||||
panic("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wait_group_done :: proc(wg: ^Wait_Group) {
|
||||
wait_group_add(wg, -1);
|
||||
}
|
||||
|
||||
wait_group_wait :: proc(wg: ^Wait_Group) {
|
||||
mutex_lock(&wg.mutex);
|
||||
defer mutex_unlock(&wg.mutex);
|
||||
|
||||
if wg.counter != 0 {
|
||||
cond_wait(&wg.cond, &wg.mutex);
|
||||
if wg.counter != 0 {
|
||||
panic("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// A barrier enabling multiple threads to synchronize the beginning of some computation
|
||||
/*
|
||||
* Example:
|
||||
*
|
||||
* package example
|
||||
*
|
||||
* import "core:fmt"
|
||||
* import "core:sync"
|
||||
* import "core:thread"
|
||||
*
|
||||
* barrier := &sync.Barrier{};
|
||||
*
|
||||
* main :: proc() {
|
||||
* fmt.println("Start");
|
||||
*
|
||||
* THREAD_COUNT :: 4;
|
||||
* threads: [THREAD_COUNT]^thread.Thread;
|
||||
*
|
||||
* sync.barrier_init(barrier, THREAD_COUNT);
|
||||
* defer sync.barrier_destroy(barrier);
|
||||
*
|
||||
*
|
||||
* for _, i in threads {
|
||||
* threads[i] = thread.create_and_start(proc(t: ^thread.Thread) {
|
||||
* // Same messages will be printed together but without any interleaving
|
||||
* fmt.println("Getting ready!");
|
||||
* sync.barrier_wait(barrier);
|
||||
* fmt.println("Off their marks they go!");
|
||||
* });
|
||||
* }
|
||||
*
|
||||
* for t in threads {
|
||||
* thread.destroy(t); // join and free thread
|
||||
* }
|
||||
* fmt.println("Finished");
|
||||
* }
|
||||
*
|
||||
*/
|
||||
Barrier :: struct {
|
||||
mutex: Mutex,
|
||||
cond: Cond,
|
||||
index: int,
|
||||
generation_id: int,
|
||||
thread_count: int,
|
||||
}
|
||||
|
||||
barrier_init :: proc(b: ^Barrier, thread_count: int) {
|
||||
b.index = 0;
|
||||
b.generation_id = 0;
|
||||
b.thread_count = thread_count;
|
||||
}
|
||||
|
||||
// Block the current thread until all threads have rendezvoused
|
||||
// Barrier can be reused after all threads rendezvoused once, and can be used continuously
|
||||
barrier_wait :: proc(b: ^Barrier) -> (is_leader: bool) {
|
||||
mutex_lock(&b.mutex);
|
||||
defer mutex_unlock(&b.mutex);
|
||||
local_gen := b.generation_id;
|
||||
b.index += 1;
|
||||
if b.index < b.thread_count {
|
||||
for local_gen == b.generation_id && b.index < b.thread_count {
|
||||
cond_wait(&b.cond, &b.mutex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
b.index = 0;
|
||||
b.generation_id += 1;
|
||||
cond_broadcast(&b.cond);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Ticket_Mutex :: struct {
|
||||
ticket: uint,
|
||||
serving: uint,
|
||||
}
|
||||
|
||||
ticket_mutex_lock :: #force_inline proc(m: ^Ticket_Mutex) {
|
||||
ticket := atomic_add_relaxed(&m.ticket, 1);
|
||||
for ticket != atomic_load_acq(&m.serving) {
|
||||
cpu_relax();
|
||||
}
|
||||
}
|
||||
|
||||
ticket_mutex_unlock :: #force_inline proc(m: ^Ticket_Mutex) {
|
||||
atomic_add_relaxed(&m.serving, 1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Benaphore :: struct {
|
||||
counter: int,
|
||||
sema: Sema,
|
||||
}
|
||||
|
||||
benaphore_lock :: proc(b: ^Benaphore) {
|
||||
if atomic_add_acq(&b.counter, 1) > 1 {
|
||||
sema_wait(&b.sema);
|
||||
}
|
||||
}
|
||||
|
||||
benaphore_try_lock :: proc(b: ^Benaphore) -> bool {
|
||||
v, _ := atomic_cxchg_acq(&b.counter, 1, 0);
|
||||
return v == 0;
|
||||
}
|
||||
|
||||
benaphore_unlock :: proc(b: ^Benaphore) {
|
||||
if atomic_sub_rel(&b.counter, 1) > 0 {
|
||||
sema_post(&b.sema);
|
||||
}
|
||||
}
|
||||
|
||||
Recursive_Benaphore :: struct {
|
||||
counter: int,
|
||||
owner: int,
|
||||
recursion: int,
|
||||
sema: Sema,
|
||||
}
|
||||
|
||||
recursive_benaphore_lock :: proc(b: ^Recursive_Benaphore) {
|
||||
tid := runtime.current_thread_id();
|
||||
if atomic_add_acq(&b.counter, 1) > 1 {
|
||||
if tid != b.owner {
|
||||
sema_wait(&b.sema);
|
||||
}
|
||||
}
|
||||
// inside the lock
|
||||
b.owner = tid;
|
||||
b.recursion += 1;
|
||||
}
|
||||
|
||||
recursive_benaphore_try_lock :: proc(b: ^Recursive_Benaphore) -> bool {
|
||||
tid := runtime.current_thread_id();
|
||||
if b.owner == tid {
|
||||
atomic_add_acq(&b.counter, 1);
|
||||
}
|
||||
|
||||
if v, _ := atomic_cxchg_acq(&b.counter, 1, 0); v != 0 {
|
||||
return false;
|
||||
}
|
||||
// inside the lock
|
||||
b.owner = tid;
|
||||
b.recursion += 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
recursive_benaphore_unlock :: proc(b: ^Recursive_Benaphore) {
|
||||
tid := runtime.current_thread_id();
|
||||
assert(tid == b.owner);
|
||||
b.recursion -= 1;
|
||||
recursion := b.recursion;
|
||||
if recursion == 0 {
|
||||
b.owner = 0;
|
||||
}
|
||||
if atomic_sub_rel(&b.counter, 1) > 0 {
|
||||
if recursion == 0 {
|
||||
sema_post(&b.sema);
|
||||
}
|
||||
}
|
||||
// outside the lock
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Once :: struct {
|
||||
m: Mutex,
|
||||
done: bool,
|
||||
}
|
||||
|
||||
once_do :: proc(o: ^Once, fn: proc()) {
|
||||
if atomic_load_acq(&o.done) == false {
|
||||
_once_do_slow(o, fn);
|
||||
}
|
||||
}
|
||||
|
||||
_once_do_slow :: proc(o: ^Once, fn: proc()) {
|
||||
mutex_lock(&o.m);
|
||||
defer mutex_unlock(&o.m);
|
||||
if !o.done {
|
||||
fn();
|
||||
atomic_store_rel(&o.done, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
package sync2
|
||||
|
||||
import "core:time"
|
||||
import "core:runtime"
|
||||
|
||||
// A Mutex is a mutual exclusion lock
|
||||
// The zero value for a Mutex is an unlocked mutex
|
||||
//
|
||||
// A Mutex must not be copied after first use
|
||||
Mutex :: struct {
|
||||
impl: _Mutex,
|
||||
}
|
||||
|
||||
// mutex_lock locks m
|
||||
mutex_lock :: proc(m: ^Mutex) {
|
||||
_mutex_lock(m);
|
||||
}
|
||||
|
||||
// mutex_lock unlocks m
|
||||
mutex_unlock :: proc(m: ^Mutex) {
|
||||
_mutex_unlock(m);
|
||||
}
|
||||
|
||||
// mutex_lock tries to lock m, will return true on success, and false on failure
|
||||
mutex_try_lock :: proc(m: ^Mutex) -> bool {
|
||||
return _mutex_try_lock(m);
|
||||
}
|
||||
|
||||
// A RW_Mutex is a reader/writer mutual exclusion lock
|
||||
// The lock can be held by any arbitrary number of readers or a single writer
|
||||
// The zero value for a RW_Mutex is an unlocked mutex
|
||||
//
|
||||
// A RW_Mutex must not be copied after first use
|
||||
RW_Mutex :: struct {
|
||||
impl: _RW_Mutex,
|
||||
}
|
||||
|
||||
// rw_mutex_lock locks rw for writing (with a single writer)
|
||||
// If the mutex is already locked for reading or writing, the mutex blocks until the mutex is available.
|
||||
rw_mutex_lock :: proc(rw: ^RW_Mutex) {
|
||||
_rw_mutex_lock(rw);
|
||||
}
|
||||
|
||||
// rw_mutex_unlock unlocks rw for writing (with a single writer)
|
||||
rw_mutex_unlock :: proc(rw: ^RW_Mutex) {
|
||||
_rw_mutex_unlock(rw);
|
||||
}
|
||||
|
||||
// rw_mutex_try_lock tries to lock rw for writing (with a single writer)
|
||||
rw_mutex_try_lock :: proc(rw: ^RW_Mutex) -> bool {
|
||||
return _rw_mutex_try_lock(rw);
|
||||
}
|
||||
|
||||
// rw_mutex_shared_lock locks rw for reading (with arbitrary number of readers)
|
||||
rw_mutex_shared_lock :: proc(rw: ^RW_Mutex) {
|
||||
_rw_mutex_shared_lock(rw);
|
||||
}
|
||||
|
||||
// rw_mutex_shared_unlock unlocks rw for reading (with arbitrary number of readers)
|
||||
rw_mutex_shared_unlock :: proc(rw: ^RW_Mutex) {
|
||||
_rw_mutex_shared_unlock(rw);
|
||||
}
|
||||
|
||||
// rw_mutex_try_shared_lock tries to lock rw for reading (with arbitrary number of readers)
|
||||
rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool {
|
||||
return _rw_mutex_try_shared_lock(rw);
|
||||
}
|
||||
|
||||
|
||||
// A Recusrive_Mutex is a recursive mutual exclusion lock
|
||||
// The zero value for a Recursive_Mutex is an unlocked mutex
|
||||
//
|
||||
// A Recursive_Mutex must not be copied after first use
|
||||
Recursive_Mutex :: struct {
|
||||
// TODO(bill): Is this implementation too lazy?
|
||||
// Can this be made to work on all OSes without construction and destruction, i.e. Zero is Initialized
|
||||
// CRITICAL_SECTION would be a perfect candidate for this on Windows but that cannot be "dumb"
|
||||
|
||||
owner: int,
|
||||
recursion: int,
|
||||
mutex: Mutex,
|
||||
}
|
||||
|
||||
recursive_mutex_lock :: proc(m: ^Recursive_Mutex) {
|
||||
tid := runtime.current_thread_id();
|
||||
if tid != m.owner {
|
||||
mutex_lock(&m.mutex);
|
||||
}
|
||||
// inside the lock
|
||||
m.owner = tid;
|
||||
m.recursion += 1;
|
||||
}
|
||||
|
||||
recursive_mutex_unlock :: proc(m: ^Recursive_Mutex) {
|
||||
tid := runtime.current_thread_id();
|
||||
assert(tid == m.owner);
|
||||
m.recursion -= 1;
|
||||
recursion := m.recursion;
|
||||
if recursion == 0 {
|
||||
m.owner = 0;
|
||||
}
|
||||
if recursion == 0 {
|
||||
mutex_unlock(&m.mutex);
|
||||
}
|
||||
// outside the lock
|
||||
|
||||
}
|
||||
|
||||
recursive_mutex_try_lock :: proc(m: ^Recursive_Mutex) -> bool {
|
||||
tid := runtime.current_thread_id();
|
||||
if m.owner == tid {
|
||||
return mutex_try_lock(&m.mutex);
|
||||
}
|
||||
if !mutex_try_lock(&m.mutex) {
|
||||
return false;
|
||||
}
|
||||
// inside the lock
|
||||
m.owner = tid;
|
||||
m.recursion += 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Cond implements a condition variable, a rendezvous point for threads
|
||||
// waiting for signalling the occurence of an event
|
||||
//
|
||||
// A Cond must not be copied after first use
|
||||
Cond :: struct {
|
||||
impl: _Cond,
|
||||
}
|
||||
|
||||
cond_wait :: proc(c: ^Cond, m: ^Mutex) {
|
||||
_cond_wait(c, m);
|
||||
}
|
||||
|
||||
cond_wait_with_timeout :: proc(c: ^Cond, m: ^Mutex, timeout: time.Duration) -> bool {
|
||||
return _cond_wait_with_timeout(c, m, timeout);
|
||||
}
|
||||
|
||||
cond_signal :: proc(c: ^Cond) {
|
||||
_cond_signal(c);
|
||||
}
|
||||
|
||||
cond_broadcast :: proc(c: ^Cond) {
|
||||
_cond_broadcast(c);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// When waited upon, blocks until the internal count is greater than zero, then subtracts one.
|
||||
// Posting to the semaphore increases the count by one, or the provided amount.
|
||||
//
|
||||
// A Sema must not be copied after first use
|
||||
Sema :: struct {
|
||||
// TODO(bill): Is this implementation too lazy?
|
||||
// Can this be made to work on all OSes without construction and destruction, i.e. Zero is Initialized
|
||||
|
||||
mutex: Mutex,
|
||||
cond: Cond,
|
||||
count: int,
|
||||
}
|
||||
|
||||
|
||||
sema_wait :: proc(s: ^Sema) {
|
||||
mutex_lock(&s.mutex);
|
||||
defer mutex_unlock(&s.mutex);
|
||||
|
||||
for s.count == 0 {
|
||||
cond_wait(&s.cond, &s.mutex);
|
||||
}
|
||||
|
||||
s.count -= 1;
|
||||
if s.count > 0 {
|
||||
cond_signal(&s.cond);
|
||||
}
|
||||
}
|
||||
|
||||
sema_post :: proc(s: ^Sema, count := 1) {
|
||||
mutex_lock(&s.mutex);
|
||||
defer mutex_unlock(&s.mutex);
|
||||
|
||||
s.count += count;
|
||||
cond_signal(&s.cond);
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
//+build linux, darwin, freebsd
|
||||
//+private
|
||||
package sync2
|
||||
|
||||
when !#config(ODIN_SYNC_USE_PTHREADS, true) {
|
||||
|
||||
import "core:time"
|
||||
|
||||
_Mutex_State :: enum i32 {
|
||||
Unlocked = 0,
|
||||
Locked = 1,
|
||||
Waiting = 2,
|
||||
}
|
||||
_Mutex :: struct {
|
||||
state: _Mutex_State,
|
||||
}
|
||||
|
||||
_mutex_lock :: proc(m: ^Mutex) {
|
||||
if atomic_xchg_rel(&m.impl.state, .Unlocked) != .Unlocked {
|
||||
_mutex_unlock_slow(m);
|
||||
}
|
||||
}
|
||||
|
||||
_mutex_unlock :: proc(m: ^Mutex) {
|
||||
switch atomic_xchg_rel(&m.impl.state, .Unlocked) {
|
||||
case .Unlocked:
|
||||
unreachable();
|
||||
case .Locked:
|
||||
// Okay
|
||||
case .Waiting:
|
||||
_mutex_unlock_slow(m);
|
||||
}
|
||||
}
|
||||
|
||||
_mutex_try_lock :: proc(m: ^Mutex) -> bool {
|
||||
_, ok := atomic_cxchg_acq(&m.impl.state, .Unlocked, .Locked);
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
||||
@(cold)
|
||||
_mutex_lock_slow :: proc(m: ^Mutex, curr_state: _Mutex_State) {
|
||||
new_state := curr_state; // Make a copy of it
|
||||
|
||||
spin_lock: for spin in 0..<i32(100) {
|
||||
state, ok := atomic_cxchgweak_acq(&m.impl.state, .Unlocked, new_state);
|
||||
if ok {
|
||||
return;
|
||||
}
|
||||
|
||||
if state == .Waiting {
|
||||
break spin_lock;
|
||||
}
|
||||
|
||||
for i := min(spin+1, 32); i > 0; i -= 1 {
|
||||
cpu_relax();
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
if atomic_xchg_acq(&m.impl.state, .Waiting) == .Unlocked {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(bill): Use a Futex here for Linux to improve performance and error handling
|
||||
cpu_relax();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@(cold)
|
||||
_mutex_unlock_slow :: proc(m: ^Mutex) {
|
||||
// TODO(bill): Use a Futex here for Linux to improve performance and error handling
|
||||
}
|
||||
|
||||
|
||||
RW_Mutex_State :: distinct uint;
|
||||
RW_Mutex_State_Half_Width :: size_of(RW_Mutex_State)*8/2;
|
||||
RW_Mutex_State_Is_Writing :: RW_Mutex_State(1);
|
||||
RW_Mutex_State_Writer :: RW_Mutex_State(1)<<1;
|
||||
RW_Mutex_State_Reader :: RW_Mutex_State(1)<<RW_Mutex_State_Half_Width;
|
||||
|
||||
RW_Mutex_State_Writer_Mask :: RW_Mutex_State(1<<(RW_Mutex_State_Half_Width-1) - 1) << 1;
|
||||
RW_Mutex_State_Reader_Mask :: RW_Mutex_State(1<<(RW_Mutex_State_Half_Width-1) - 1) << RW_Mutex_State_Half_Width;
|
||||
|
||||
|
||||
_RW_Mutex :: struct {
|
||||
state: RW_Mutex_State,
|
||||
mutex: Mutex,
|
||||
sema: Sema,
|
||||
}
|
||||
|
||||
_rw_mutex_lock :: proc(rw: ^RW_Mutex) {
|
||||
_ = atomic_add(&rw.impl.state, RW_Mutex_State_Writer);
|
||||
mutex_lock(&rw.impl.mutex);
|
||||
|
||||
state := atomic_or(&rw.impl.state, RW_Mutex_State_Writer);
|
||||
if state & RW_Mutex_State_Reader_Mask != 0 {
|
||||
sema_wait(&rw.impl.sema);
|
||||
}
|
||||
}
|
||||
|
||||
_rw_mutex_unlock :: proc(rw: ^RW_Mutex) {
|
||||
_ = atomic_and(&rw.impl.state, ~RW_Mutex_State_Is_Writing);
|
||||
mutex_unlock(&rw.impl.mutex);
|
||||
}
|
||||
|
||||
_rw_mutex_try_lock :: proc(rw: ^RW_Mutex) -> bool {
|
||||
if mutex_try_lock(&rw.impl.mutex) {
|
||||
state := atomic_load(&rw.impl.state);
|
||||
if state & RW_Mutex_State_Reader_Mask == 0 {
|
||||
_ = atomic_or(&rw.impl.state, RW_Mutex_State_Is_Writing);
|
||||
return true;
|
||||
}
|
||||
|
||||
mutex_unlock(&rw.impl.mutex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
_rw_mutex_shared_lock :: proc(rw: ^RW_Mutex) {
|
||||
state := atomic_load(&rw.impl.state);
|
||||
for state & (RW_Mutex_State_Is_Writing|RW_Mutex_State_Writer_Mask) == 0 {
|
||||
ok: bool;
|
||||
state, ok = atomic_cxchgweak(&rw.impl.state, state, state + RW_Mutex_State_Reader);
|
||||
if ok {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_lock(&rw.impl.mutex);
|
||||
_ = atomic_add(&rw.impl.state, RW_Mutex_State_Reader);
|
||||
mutex_unlock(&rw.impl.mutex);
|
||||
}
|
||||
|
||||
_rw_mutex_shared_unlock :: proc(rw: ^RW_Mutex) {
|
||||
state := atomic_sub(&rw.impl.state, RW_Mutex_State_Reader);
|
||||
|
||||
if (state & RW_Mutex_State_Reader_Mask == RW_Mutex_State_Reader) &&
|
||||
(state & RW_Mutex_State_Is_Writing != 0) {
|
||||
sema_post(&rw.impl.sema);
|
||||
}
|
||||
}
|
||||
|
||||
_rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool {
|
||||
state := atomic_load(&rw.impl.state);
|
||||
if state & (RW_Mutex_State_Is_Writing|RW_Mutex_State_Writer_Mask) == 0 {
|
||||
_, ok := atomic_cxchg(&rw.impl.state, state, state + RW_Mutex_State_Reader);
|
||||
if ok {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if mutex_try_lock(&rw.impl.mutex) {
|
||||
_ = atomic_add(&rw.impl.state, RW_Mutex_State_Reader);
|
||||
mutex_unlock(&rw.impl.mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Queue_Item :: struct {
|
||||
next: ^Queue_Item,
|
||||
futex: i32,
|
||||
}
|
||||
|
||||
queue_item_wait :: proc(item: ^Queue_Item) {
|
||||
for atomic_load_acq(&item.futex) == 0 {
|
||||
// TODO(bill): Use a Futex here for Linux to improve performance and error handling
|
||||
cpu_relax();
|
||||
}
|
||||
}
|
||||
queue_item_signal :: proc(item: ^Queue_Item) {
|
||||
atomic_store_rel(&item.futex, 1);
|
||||
// TODO(bill): Use a Futex here for Linux to improve performance and error handling
|
||||
}
|
||||
|
||||
|
||||
_Cond :: struct {
|
||||
queue_mutex: Mutex,
|
||||
queue_head: ^Queue_Item,
|
||||
pending: bool,
|
||||
}
|
||||
|
||||
_cond_wait :: proc(c: ^Cond, m: ^Mutex) {
|
||||
waiter := &Queue_Item{};
|
||||
|
||||
mutex_lock(&c.impl.queue_mutex);
|
||||
waiter.next = c.impl.queue_head;
|
||||
c.impl.queue_head = waiter;
|
||||
|
||||
atomic_store(&c.impl.pending, true);
|
||||
mutex_unlock(&c.impl.queue_mutex);
|
||||
|
||||
mutex_unlock(m);
|
||||
queue_item_wait(waiter);
|
||||
mutex_lock(m);
|
||||
}
|
||||
|
||||
_cond_wait_with_timeout :: proc(c: ^Cond, m: ^Mutex, timeout: time.Duration) -> bool {
|
||||
// TODO(bill): _cond_wait_with_timeout for unix
|
||||
return false;
|
||||
}
|
||||
|
||||
_cond_signal :: proc(c: ^Cond) {
|
||||
if !atomic_load(&c.impl.pending) {
|
||||
return;
|
||||
}
|
||||
|
||||
mutex_lock(&c.impl.queue_mutex);
|
||||
waiter := c.impl.queue_head;
|
||||
if c.impl.queue_head != nil {
|
||||
c.impl.queue_head = c.impl.queue_head.next;
|
||||
}
|
||||
atomic_store(&c.impl.pending, c.impl.queue_head != nil);
|
||||
mutex_unlock(&c.impl.queue_mutex);
|
||||
|
||||
if waiter != nil {
|
||||
queue_item_signal(waiter);
|
||||
}
|
||||
}
|
||||
|
||||
_cond_broadcast :: proc(c: ^Cond) {
|
||||
if !atomic_load(&c.impl.pending) {
|
||||
return;
|
||||
}
|
||||
|
||||
atomic_store(&c.impl.pending, false);
|
||||
|
||||
mutex_lock(&c.impl.queue_mutex);
|
||||
waiters := c.impl.queue_head;
|
||||
c.impl.queue_head = nil;
|
||||
mutex_unlock(&c.impl.queue_mutex);
|
||||
|
||||
for waiters != nil {
|
||||
queue_item_signal(waiters);
|
||||
waiters = waiters.next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // !ODIN_SYNC_USE_PTHREADS
|
||||
@@ -0,0 +1,154 @@
|
||||
//+build linux, darwin, freebsd
|
||||
//+private
|
||||
package sync2
|
||||
|
||||
when #config(ODIN_SYNC_USE_PTHREADS, true) {
|
||||
|
||||
import "core:time"
|
||||
import "core:sys/unix"
|
||||
|
||||
_Mutex_State :: enum i32 {
|
||||
Unlocked = 0,
|
||||
Locked = 1,
|
||||
Waiting = 2,
|
||||
}
|
||||
_Mutex :: struct {
|
||||
pthread_mutex: unix.pthread_mutex_t,
|
||||
}
|
||||
|
||||
_mutex_lock :: proc(m: ^Mutex) {
|
||||
err := unix.pthread_mutex_lock(&m.impl.pthread_mutex);
|
||||
assert(err == 0);
|
||||
}
|
||||
|
||||
_mutex_unlock :: proc(m: ^Mutex) {
|
||||
err := unix.pthread_mutex_unlock(&m.impl.pthread_mutex);
|
||||
assert(err == 0);
|
||||
}
|
||||
|
||||
_mutex_try_lock :: proc(m: ^Mutex) -> bool {
|
||||
err := unix.pthread_mutex_trylock(&m.impl.pthread_mutex);
|
||||
return err == 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
RW_Mutex_State :: distinct uint;
|
||||
RW_Mutex_State_Half_Width :: size_of(RW_Mutex_State)*8/2;
|
||||
RW_Mutex_State_Is_Writing :: RW_Mutex_State(1);
|
||||
RW_Mutex_State_Writer :: RW_Mutex_State(1)<<1;
|
||||
RW_Mutex_State_Reader :: RW_Mutex_State(1)<<RW_Mutex_State_Half_Width;
|
||||
|
||||
RW_Mutex_State_Writer_Mask :: RW_Mutex_State(1<<(RW_Mutex_State_Half_Width-1) - 1) << 1;
|
||||
RW_Mutex_State_Reader_Mask :: RW_Mutex_State(1<<(RW_Mutex_State_Half_Width-1) - 1) << RW_Mutex_State_Half_Width;
|
||||
|
||||
|
||||
_RW_Mutex :: struct {
|
||||
// NOTE(bill): pthread_rwlock_t cannot be used since pthread_rwlock_destroy is required on some platforms
|
||||
// TODO(bill): Can we determine which platforms exactly?
|
||||
state: RW_Mutex_State,
|
||||
mutex: Mutex,
|
||||
sema: Sema,
|
||||
}
|
||||
|
||||
_rw_mutex_lock :: proc(rw: ^RW_Mutex) {
|
||||
_ = atomic_add(&rw.impl.state, RW_Mutex_State_Writer);
|
||||
mutex_lock(&rw.impl.mutex);
|
||||
|
||||
state := atomic_or(&rw.impl.state, RW_Mutex_State_Writer);
|
||||
if state & RW_Mutex_State_Reader_Mask != 0 {
|
||||
sema_wait(&rw.impl.sema);
|
||||
}
|
||||
}
|
||||
|
||||
_rw_mutex_unlock :: proc(rw: ^RW_Mutex) {
|
||||
_ = atomic_and(&rw.impl.state, ~RW_Mutex_State_Is_Writing);
|
||||
mutex_unlock(&rw.impl.mutex);
|
||||
}
|
||||
|
||||
_rw_mutex_try_lock :: proc(rw: ^RW_Mutex) -> bool {
|
||||
if mutex_try_lock(&rw.impl.mutex) {
|
||||
state := atomic_load(&rw.impl.state);
|
||||
if state & RW_Mutex_State_Reader_Mask == 0 {
|
||||
_ = atomic_or(&rw.impl.state, RW_Mutex_State_Is_Writing);
|
||||
return true;
|
||||
}
|
||||
|
||||
mutex_unlock(&rw.impl.mutex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
_rw_mutex_shared_lock :: proc(rw: ^RW_Mutex) {
|
||||
state := atomic_load(&rw.impl.state);
|
||||
for state & (RW_Mutex_State_Is_Writing|RW_Mutex_State_Writer_Mask) == 0 {
|
||||
ok: bool;
|
||||
state, ok = atomic_cxchgweak(&rw.impl.state, state, state + RW_Mutex_State_Reader);
|
||||
if ok {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_lock(&rw.impl.mutex);
|
||||
_ = atomic_add(&rw.impl.state, RW_Mutex_State_Reader);
|
||||
mutex_unlock(&rw.impl.mutex);
|
||||
}
|
||||
|
||||
_rw_mutex_shared_unlock :: proc(rw: ^RW_Mutex) {
|
||||
state := atomic_sub(&rw.impl.state, RW_Mutex_State_Reader);
|
||||
|
||||
if (state & RW_Mutex_State_Reader_Mask == RW_Mutex_State_Reader) &&
|
||||
(state & RW_Mutex_State_Is_Writing != 0) {
|
||||
sema_post(&rw.impl.sema);
|
||||
}
|
||||
}
|
||||
|
||||
_rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool {
|
||||
state := atomic_load(&rw.impl.state);
|
||||
if state & (RW_Mutex_State_Is_Writing|RW_Mutex_State_Writer_Mask) == 0 {
|
||||
_, ok := atomic_cxchg(&rw.impl.state, state, state + RW_Mutex_State_Reader);
|
||||
if ok {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if mutex_try_lock(&rw.impl.mutex) {
|
||||
_ = atomic_add(&rw.impl.state, RW_Mutex_State_Reader);
|
||||
mutex_unlock(&rw.impl.mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_Cond :: struct {
|
||||
pthread_cond: unix.pthread_cond_t,
|
||||
}
|
||||
|
||||
_cond_wait :: proc(c: ^Cond, m: ^Mutex) {
|
||||
err := unix.pthread_cond_wait(&c.impl.pthread_cond, &m.impl.pthread_mutex);
|
||||
assert(err == 0);
|
||||
}
|
||||
|
||||
_cond_wait_with_timeout :: proc(c: ^Cond, m: ^Mutex, timeout: time.Duration) -> bool {
|
||||
ns := time.duration_nanoseconds(timeout);
|
||||
timeout_timespec := &time.TimeSpec{
|
||||
tv_sec = ns / 1e9,
|
||||
tv_nsec = ns % 1e9,
|
||||
};
|
||||
err := unix.pthread_cond_timedwait(&c.impl.pthread_cond, &m.impl.pthread_mutex, timeout_timespec);
|
||||
// TODO(bill):
|
||||
return err == 0;
|
||||
}
|
||||
|
||||
_cond_signal :: proc(c: ^Cond) {
|
||||
err := unix.pthread_cond_signal(&c.impl.pthread_cond);
|
||||
assert(err == 0);
|
||||
}
|
||||
|
||||
_cond_broadcast :: proc(c: ^Cond) {
|
||||
err := unix.pthread_cond_broadcast(&c.impl.pthread_cond);
|
||||
assert(err == 0);
|
||||
}
|
||||
|
||||
|
||||
} // ODIN_SYNC_USE_PTHREADS
|
||||
@@ -0,0 +1,73 @@
|
||||
//+build windows
|
||||
//+private
|
||||
package sync2
|
||||
|
||||
import "core:time"
|
||||
import win32 "core:sys/windows"
|
||||
|
||||
_Mutex :: struct {
|
||||
srwlock: win32.SRWLOCK,
|
||||
}
|
||||
|
||||
_mutex_lock :: proc(m: ^Mutex) {
|
||||
win32.AcquireSRWLockExclusive(&m.impl.srwlock);
|
||||
}
|
||||
|
||||
_mutex_unlock :: proc(m: ^Mutex) {
|
||||
win32.ReleaseSRWLockExclusive(&m.impl.srwlock);
|
||||
}
|
||||
|
||||
_mutex_try_lock :: proc(m: ^Mutex) -> bool {
|
||||
return bool(win32.TryAcquireSRWLockExclusive(&m.impl.srwlock));
|
||||
}
|
||||
|
||||
_RW_Mutex :: struct {
|
||||
srwlock: win32.SRWLOCK,
|
||||
}
|
||||
|
||||
_rw_mutex_lock :: proc(rw: ^RW_Mutex) {
|
||||
win32.AcquireSRWLockExclusive(&rw.impl.srwlock);
|
||||
}
|
||||
|
||||
_rw_mutex_unlock :: proc(rw: ^RW_Mutex) {
|
||||
win32.ReleaseSRWLockExclusive(&rw.impl.srwlock);
|
||||
}
|
||||
|
||||
_rw_mutex_try_lock :: proc(rw: ^RW_Mutex) -> bool {
|
||||
return bool(win32.TryAcquireSRWLockExclusive(&rw.impl.srwlock));
|
||||
}
|
||||
|
||||
_rw_mutex_shared_lock :: proc(rw: ^RW_Mutex) {
|
||||
win32.AcquireSRWLockShared(&rw.impl.srwlock);
|
||||
}
|
||||
|
||||
_rw_mutex_shared_unlock :: proc(rw: ^RW_Mutex) {
|
||||
win32.ReleaseSRWLockShared(&rw.impl.srwlock);
|
||||
}
|
||||
|
||||
_rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool {
|
||||
return bool(win32.TryAcquireSRWLockShared(&rw.impl.srwlock));
|
||||
}
|
||||
|
||||
|
||||
|
||||
_Cond :: struct {
|
||||
cond: win32.CONDITION_VARIABLE,
|
||||
}
|
||||
|
||||
_cond_wait :: proc(c: ^Cond, m: ^Mutex) {
|
||||
_ = win32.SleepConditionVariableSRW(&c.impl.cond, &m.impl.srwlock, win32.INFINITE, 0);
|
||||
}
|
||||
|
||||
_cond_wait_with_timeout :: proc(c: ^Cond, m: ^Mutex, timeout: time.Duration) -> bool {
|
||||
ms := win32.DWORD((max(time.duration_nanoseconds(timeout), 0) + 999999)/1000000);
|
||||
return cast(bool)win32.SleepConditionVariableSRW(&c.impl.cond, &m.impl.srwlock, ms, 0);
|
||||
}
|
||||
|
||||
_cond_signal :: proc(c: ^Cond) {
|
||||
win32.WakeConditionVariable(&c.impl.cond);
|
||||
}
|
||||
|
||||
_cond_broadcast :: proc(c: ^Cond) {
|
||||
win32.WakeAllConditionVariable(&c.impl.cond);
|
||||
}
|
||||
@@ -10,3 +10,57 @@ foreign advapi32 {
|
||||
DesiredAccess: DWORD,
|
||||
TokenHandle: ^HANDLE) -> BOOL ---
|
||||
}
|
||||
|
||||
// Necessary to create a token to impersonate a user with for CreateProcessAsUser
|
||||
@(default_calling_convention="stdcall")
|
||||
foreign advapi32 {
|
||||
LogonUserW :: proc(
|
||||
lpszUsername: LPCWSTR,
|
||||
lpszDomain: LPCWSTR,
|
||||
lpszPassword: LPCWSTR,
|
||||
dwLogonType: Logon32_Type,
|
||||
dwLogonProvider: Logon32_Provider,
|
||||
phToken: ^HANDLE,
|
||||
) -> BOOL ---
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lookupaccountnamew
|
||||
// To look up the SID to use with DeleteProfileW.
|
||||
LookupAccountNameW :: proc(
|
||||
lpSystemName: wstring,
|
||||
lpAccountName: wstring,
|
||||
Sid: ^SID,
|
||||
cbSid: ^DWORD,
|
||||
ReferencedDomainName: wstring,
|
||||
cchReferencedDomainName: ^DWORD,
|
||||
peUse: ^SID_TYPE,
|
||||
) -> BOOL ---
|
||||
|
||||
CreateProcessWithLogonW :: proc(
|
||||
lpUsername: wstring,
|
||||
lpDomain: wstring,
|
||||
lpPassword: wstring,
|
||||
dwLogonFlags: DWORD,
|
||||
lpApplicationName: wstring,
|
||||
lpCommandLine: wstring,
|
||||
dwCreationFlags: DWORD,
|
||||
lpEnvironment: LPVOID,
|
||||
lpCurrentDirectory: wstring,
|
||||
lpStartupInfo: LPSTARTUPINFO,
|
||||
lpProcessInformation: LPPROCESS_INFORMATION,
|
||||
) -> BOOL ---
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasuserw
|
||||
CreateProcessAsUserW :: proc(
|
||||
hToken: HANDLE,
|
||||
lpApplicationName: wstring,
|
||||
lpCommandLine: wstring,
|
||||
lpProcessAttributes: LPSECURITY_ATTRIBUTES,
|
||||
lpThreadAttributes: LPSECURITY_ATTRIBUTES,
|
||||
bInheritHandles: BOOL,
|
||||
dwCreationFlags: DWORD,
|
||||
lpEnvironment: LPVOID,
|
||||
lpCurrentDirectory: wstring,
|
||||
lpStartupInfo: LPSTARTUPINFO,
|
||||
lpProcessInformation: LPPROCESS_INFORMATION,
|
||||
) -> BOOL ---;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package sys_windows
|
||||
|
||||
foreign import netapi32 "system:Netapi32.lib"
|
||||
|
||||
@(default_calling_convention="stdcall")
|
||||
foreign netapi32 {
|
||||
NetUserAdd :: proc(
|
||||
servername: wstring,
|
||||
level: DWORD,
|
||||
user_info: ^USER_INFO_1, // Perhaps make this a #raw_union with USER_INFO1..4 when we need the other levels.
|
||||
parm_err: ^DWORD,
|
||||
) -> NET_API_STATUS ---;
|
||||
NetUserDel :: proc(
|
||||
servername: wstring,
|
||||
username: wstring,
|
||||
) -> NET_API_STATUS ---;
|
||||
NetUserGetInfo :: proc(
|
||||
servername: wstring,
|
||||
username: wstring,
|
||||
level: DWORD,
|
||||
user_info: ^USER_INFO_1,
|
||||
) -> NET_API_STATUS ---;
|
||||
NetLocalGroupAddMembers :: proc(
|
||||
servername: wstring,
|
||||
groupname: wstring,
|
||||
level: DWORD,
|
||||
group_members_info: ^LOCALGROUP_MEMBERS_INFO_0, // Actually a variably sized array of these.
|
||||
totalentries: DWORD,
|
||||
) -> NET_API_STATUS ---;
|
||||
NetLocalGroupDelMembers :: proc(
|
||||
servername: wstring,
|
||||
groupname: wstring,
|
||||
level: DWORD,
|
||||
group_members_info: ^LOCALGROUP_MEMBERS_INFO_0, // Actually a variably sized array of these.
|
||||
totalentries: DWORD,
|
||||
) -> NET_API_STATUS ---;
|
||||
}
|
||||
+449
-2
@@ -232,6 +232,7 @@ PROGRESS_CONTINUE: DWORD : 0;
|
||||
ERROR_FILE_NOT_FOUND: DWORD : 2;
|
||||
ERROR_PATH_NOT_FOUND: DWORD : 3;
|
||||
ERROR_ACCESS_DENIED: DWORD : 5;
|
||||
ERROR_NOT_ENOUGH_MEMORY: DWORD : 8;
|
||||
ERROR_INVALID_HANDLE: DWORD : 6;
|
||||
ERROR_NO_MORE_FILES: DWORD : 18;
|
||||
ERROR_SHARING_VIOLATION: DWORD : 32;
|
||||
@@ -570,7 +571,8 @@ PROCESS_INFORMATION :: struct {
|
||||
dwThreadId: DWORD,
|
||||
}
|
||||
|
||||
STARTUPINFO :: struct {
|
||||
// FYI: This is STARTUPINFOW, not STARTUPINFOA
|
||||
STARTUPINFO :: struct #packed {
|
||||
cb: DWORD,
|
||||
lpReserved: LPWSTR,
|
||||
lpDesktop: LPWSTR,
|
||||
@@ -580,7 +582,7 @@ STARTUPINFO :: struct {
|
||||
dwXSize: DWORD,
|
||||
dwYSize: DWORD,
|
||||
dwXCountChars: DWORD,
|
||||
dwYCountCharts: DWORD,
|
||||
dwYCountChars: DWORD,
|
||||
dwFillAttribute: DWORD,
|
||||
dwFlags: DWORD,
|
||||
wShowWindow: WORD,
|
||||
@@ -788,3 +790,448 @@ OSVERSIONINFOEXW :: struct {
|
||||
wProductType: UCHAR,
|
||||
wReserved: UCHAR,
|
||||
};
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-quota_limits
|
||||
// Used in LogonUserExW
|
||||
PQUOTA_LIMITS :: struct {
|
||||
PagedPoolLimit: SIZE_T,
|
||||
NonPagedPoolLimit: SIZE_T,
|
||||
MinimumWorkingSetSize: SIZE_T,
|
||||
MaximumWorkingSetSize: SIZE_T,
|
||||
PagefileLimit: SIZE_T,
|
||||
TimeLimit: LARGE_INTEGER,
|
||||
};
|
||||
|
||||
Logon32_Type :: enum DWORD {
|
||||
INTERACTIVE = 2,
|
||||
NETWORK = 3,
|
||||
BATCH = 4,
|
||||
SERVICE = 5,
|
||||
UNLOCK = 7,
|
||||
NETWORK_CLEARTEXT = 8,
|
||||
NEW_CREDENTIALS = 9,
|
||||
}
|
||||
|
||||
Logon32_Provider :: enum DWORD {
|
||||
DEFAULT = 0,
|
||||
WINNT35 = 1,
|
||||
WINNT40 = 2,
|
||||
WINNT50 = 3,
|
||||
VIRTUAL = 4,
|
||||
}
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/profinfo/ns-profinfo-profileinfow
|
||||
// Used in LoadUserProfileW
|
||||
|
||||
PROFILEINFOW :: struct {
|
||||
dwSize: DWORD,
|
||||
dwFlags: DWORD,
|
||||
lpUserName: LPWSTR,
|
||||
lpProfilePath: LPWSTR,
|
||||
lpDefaultPath: LPWSTR,
|
||||
lpServerName: LPWSTR,
|
||||
lpPolicyPath: LPWSTR,
|
||||
hProfile: HANDLE,
|
||||
};
|
||||
|
||||
// Used in LookupAccountNameW
|
||||
SID_NAME_USE :: distinct DWORD;
|
||||
|
||||
SID_TYPE :: enum SID_NAME_USE {
|
||||
User = 1,
|
||||
Group,
|
||||
Domain,
|
||||
Alias,
|
||||
WellKnownGroup,
|
||||
DeletedAccount,
|
||||
Invalid,
|
||||
Unknown,
|
||||
Computer,
|
||||
Label,
|
||||
LogonSession
|
||||
}
|
||||
|
||||
SECURITY_MAX_SID_SIZE :: 68;
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-sid
|
||||
SID :: struct #packed {
|
||||
Revision: byte,
|
||||
SubAuthorityCount: byte,
|
||||
IdentifierAuthority: SID_IDENTIFIER_AUTHORITY,
|
||||
SubAuthority: [15]DWORD, // Array of DWORDs
|
||||
};
|
||||
#assert(size_of(SID) == SECURITY_MAX_SID_SIZE);
|
||||
|
||||
SID_IDENTIFIER_AUTHORITY :: struct #packed {
|
||||
Value: [6]u8,
|
||||
};
|
||||
|
||||
// For NetAPI32
|
||||
// https://github.com/tpn/winsdk-10/blob/master/Include/10.0.14393.0/shared/lmerr.h
|
||||
// https://github.com/tpn/winsdk-10/blob/master/Include/10.0.14393.0/shared/LMaccess.h
|
||||
|
||||
UNLEN :: 256; // Maximum user name length
|
||||
LM20_UNLEN :: 20; // LM 2.0 Maximum user name length
|
||||
|
||||
GNLEN :: UNLEN; // Group name
|
||||
LM20_GNLEN :: LM20_UNLEN; // LM 2.0 Group name
|
||||
|
||||
PWLEN :: 256; // Maximum password length
|
||||
LM20_PWLEN :: 14; // LM 2.0 Maximum password length
|
||||
|
||||
USER_PRIV :: enum DWORD {
|
||||
Guest = 0,
|
||||
User = 1,
|
||||
Admin = 2,
|
||||
Mask = 0x3,
|
||||
}
|
||||
|
||||
USER_INFO_FLAG :: enum DWORD {
|
||||
Script = 0, // 1 << 0: 0x0001,
|
||||
AccountDisable = 1, // 1 << 1: 0x0002,
|
||||
HomeDir_Required = 3, // 1 << 3: 0x0008,
|
||||
Lockout = 4, // 1 << 4: 0x0010,
|
||||
Passwd_NotReqd = 5, // 1 << 5: 0x0020,
|
||||
Passwd_Cant_Change = 6, // 1 << 6: 0x0040,
|
||||
Encrypted_Text_Password_Allowed = 7, // 1 << 7: 0x0080,
|
||||
|
||||
Temp_Duplicate_Account = 8, // 1 << 8: 0x0100,
|
||||
Normal_Account = 9, // 1 << 9: 0x0200,
|
||||
InterDomain_Trust_Account = 11, // 1 << 11: 0x0800,
|
||||
Workstation_Trust_Account = 12, // 1 << 12: 0x1000,
|
||||
Server_Trust_Account = 13, // 1 << 13: 0x2000,
|
||||
}
|
||||
USER_INFO_FLAGS :: distinct bit_set[USER_INFO_FLAG];
|
||||
|
||||
USER_INFO_1 :: struct #packed {
|
||||
name: LPWSTR,
|
||||
password: LPWSTR, // Max password length is defined in LM20_PWLEN.
|
||||
password_age: DWORD,
|
||||
priv: USER_PRIV,
|
||||
home_dir: LPWSTR,
|
||||
comment: LPWSTR,
|
||||
flags: USER_INFO_FLAGS,
|
||||
script_path: LPWSTR,
|
||||
};
|
||||
#assert(size_of(USER_INFO_1) == 50);
|
||||
|
||||
LOCALGROUP_MEMBERS_INFO_0 :: struct #packed {
|
||||
sid: ^SID,
|
||||
};
|
||||
|
||||
NET_API_STATUS :: enum DWORD {
|
||||
Success = 0,
|
||||
ERROR_ACCESS_DENIED = 5,
|
||||
MemberInAlias = 1378,
|
||||
NetNotStarted = 2102,
|
||||
UnknownServer = 2103,
|
||||
ShareMem = 2104,
|
||||
NoNetworkResource = 2105,
|
||||
RemoteOnly = 2106,
|
||||
DevNotRedirected = 2107,
|
||||
ServerNotStarted = 2114,
|
||||
ItemNotFound = 2115,
|
||||
UnknownDevDir = 2116,
|
||||
RedirectedPath = 2117,
|
||||
DuplicateShare = 2118,
|
||||
NoRoom = 2119,
|
||||
TooManyItems = 2121,
|
||||
InvalidMaxUsers = 2122,
|
||||
BufTooSmall = 2123,
|
||||
RemoteErr = 2127,
|
||||
LanmanIniError = 2131,
|
||||
NetworkError = 2136,
|
||||
WkstaInconsistentState = 2137,
|
||||
WkstaNotStarted = 2138,
|
||||
BrowserNotStarted = 2139,
|
||||
InternalError = 2140,
|
||||
BadTransactConfig = 2141,
|
||||
InvalidAPI = 2142,
|
||||
BadEventName = 2143,
|
||||
DupNameReboot = 2144,
|
||||
CfgCompNotFound = 2146,
|
||||
CfgParamNotFound = 2147,
|
||||
LineTooLong = 2149,
|
||||
QNotFound = 2150,
|
||||
JobNotFound = 2151,
|
||||
DestNotFound = 2152,
|
||||
DestExists = 2153,
|
||||
QExists = 2154,
|
||||
QNoRoom = 2155,
|
||||
JobNoRoom = 2156,
|
||||
DestNoRoom = 2157,
|
||||
DestIdle = 2158,
|
||||
DestInvalidOp = 2159,
|
||||
ProcNoRespond = 2160,
|
||||
SpoolerNotLoaded = 2161,
|
||||
DestInvalidState = 2162,
|
||||
QInvalidState = 2163,
|
||||
JobInvalidState = 2164,
|
||||
SpoolNoMemory = 2165,
|
||||
DriverNotFound = 2166,
|
||||
DataTypeInvalid = 2167,
|
||||
ProcNotFound = 2168,
|
||||
ServiceTableLocked = 2180,
|
||||
ServiceTableFull = 2181,
|
||||
ServiceInstalled = 2182,
|
||||
ServiceEntryLocked = 2183,
|
||||
ServiceNotInstalled = 2184,
|
||||
BadServiceName = 2185,
|
||||
ServiceCtlTimeout = 2186,
|
||||
ServiceCtlBusy = 2187,
|
||||
BadServiceProgName = 2188,
|
||||
ServiceNotCtrl = 2189,
|
||||
ServiceKillProc = 2190,
|
||||
ServiceCtlNotValid = 2191,
|
||||
NotInDispatchTbl = 2192,
|
||||
BadControlRecv = 2193,
|
||||
ServiceNotStarting = 2194,
|
||||
AlreadyLoggedOn = 2200,
|
||||
NotLoggedOn = 2201,
|
||||
BadUsername = 2202,
|
||||
BadPassword = 2203,
|
||||
UnableToAddName_W = 2204,
|
||||
UnableToAddName_F = 2205,
|
||||
UnableToDelName_W = 2206,
|
||||
UnableToDelName_F = 2207,
|
||||
LogonsPaused = 2209,
|
||||
LogonServerConflict = 2210,
|
||||
LogonNoUserPath = 2211,
|
||||
LogonScriptError = 2212,
|
||||
StandaloneLogon = 2214,
|
||||
LogonServerNotFound = 2215,
|
||||
LogonDomainExists = 2216,
|
||||
NonValidatedLogon = 2217,
|
||||
ACFNotFound = 2219,
|
||||
GroupNotFound = 2220,
|
||||
UserNotFound = 2221,
|
||||
ResourceNotFound = 2222,
|
||||
GroupExists = 2223,
|
||||
UserExists = 2224,
|
||||
ResourceExists = 2225,
|
||||
NotPrimary = 2226,
|
||||
ACFNotLoaded = 2227,
|
||||
ACFNoRoom = 2228,
|
||||
ACFFileIOFail = 2229,
|
||||
ACFTooManyLists = 2230,
|
||||
UserLogon = 2231,
|
||||
ACFNoParent = 2232,
|
||||
CanNotGrowSegment = 2233,
|
||||
SpeGroupOp = 2234,
|
||||
NotInCache = 2235,
|
||||
UserInGroup = 2236,
|
||||
UserNotInGroup = 2237,
|
||||
AccountUndefined = 2238,
|
||||
AccountExpired = 2239,
|
||||
InvalidWorkstation = 2240,
|
||||
InvalidLogonHours = 2241,
|
||||
PasswordExpired = 2242,
|
||||
PasswordCantChange = 2243,
|
||||
PasswordHistConflict = 2244,
|
||||
PasswordTooShort = 2245,
|
||||
PasswordTooRecent = 2246,
|
||||
InvalidDatabase = 2247,
|
||||
DatabaseUpToDate = 2248,
|
||||
SyncRequired = 2249,
|
||||
UseNotFound = 2250,
|
||||
BadAsgType = 2251,
|
||||
DeviceIsShared = 2252,
|
||||
SameAsComputerName = 2253,
|
||||
NoComputerName = 2270,
|
||||
MsgAlreadyStarted = 2271,
|
||||
MsgInitFailed = 2272,
|
||||
NameNotFound = 2273,
|
||||
AlreadyForwarded = 2274,
|
||||
AddForwarded = 2275,
|
||||
AlreadyExists = 2276,
|
||||
TooManyNames = 2277,
|
||||
DelComputerName = 2278,
|
||||
LocalForward = 2279,
|
||||
GrpMsgProcessor = 2280,
|
||||
PausedRemote = 2281,
|
||||
BadReceive = 2282,
|
||||
NameInUse = 2283,
|
||||
MsgNotStarted = 2284,
|
||||
NotLocalName = 2285,
|
||||
NoForwardName = 2286,
|
||||
RemoteFull = 2287,
|
||||
NameNotForwarded = 2288,
|
||||
TruncatedBroadcast = 2289,
|
||||
InvalidDevice = 2294,
|
||||
WriteFault = 2295,
|
||||
DuplicateName = 2297,
|
||||
DeleteLater = 2298,
|
||||
IncompleteDel = 2299,
|
||||
MultipleNets = 2300,
|
||||
NetNameNotFound = 2310,
|
||||
DeviceNotShared = 2311,
|
||||
ClientNameNotFound = 2312,
|
||||
FileIdNotFound = 2314,
|
||||
ExecFailure = 2315,
|
||||
TmpFile = 2316,
|
||||
TooMuchData = 2317,
|
||||
DeviceShareConflict = 2318,
|
||||
BrowserTableIncomplete = 2319,
|
||||
NotLocalDomain = 2320,
|
||||
IsDfsShare = 2321,
|
||||
DevInvalidOpCode = 2331,
|
||||
DevNotFound = 2332,
|
||||
DevNotOpen = 2333,
|
||||
BadQueueDevString = 2334,
|
||||
BadQueuePriority = 2335,
|
||||
NoCommDevs = 2337,
|
||||
QueueNotFound = 2338,
|
||||
BadDevString = 2340,
|
||||
BadDev = 2341,
|
||||
InUseBySpooler = 2342,
|
||||
CommDevInUse = 2343,
|
||||
InvalidComputer = 2351,
|
||||
MaxLenExceeded = 2354,
|
||||
BadComponent = 2356,
|
||||
CantType = 2357,
|
||||
TooManyEntries = 2362,
|
||||
ProfileFileTooBig = 2370,
|
||||
ProfileOffset = 2371,
|
||||
ProfileCleanup = 2372,
|
||||
ProfileUnknownCmd = 2373,
|
||||
ProfileLoadErr = 2374,
|
||||
ProfileSaveErr = 2375,
|
||||
LogOverflow = 2377,
|
||||
LogFileChanged = 2378,
|
||||
LogFileCorrupt = 2379,
|
||||
SourceIsDir = 2380,
|
||||
BadSource = 2381,
|
||||
BadDest = 2382,
|
||||
DifferentServers = 2383,
|
||||
RunSrvPaused = 2385,
|
||||
ErrCommRunSrv = 2389,
|
||||
ErrorExecingGhost = 2391,
|
||||
ShareNotFound = 2392,
|
||||
InvalidLana = 2400,
|
||||
OpenFiles = 2401,
|
||||
ActiveConns = 2402,
|
||||
BadPasswordCore = 2403,
|
||||
DevInUse = 2404,
|
||||
LocalDrive = 2405,
|
||||
AlertExists = 2430,
|
||||
TooManyAlerts = 2431,
|
||||
NoSuchAlert = 2432,
|
||||
BadRecipient = 2433,
|
||||
AcctLimitExceeded = 2434,
|
||||
InvalidLogSeek = 2440,
|
||||
BadUasConfig = 2450,
|
||||
InvalidUASOp = 2451,
|
||||
LastAdmin = 2452,
|
||||
DCNotFound = 2453,
|
||||
LogonTrackingError = 2454,
|
||||
NetlogonNotStarted = 2455,
|
||||
CanNotGrowUASFile = 2456,
|
||||
TimeDiffAtDC = 2457,
|
||||
PasswordMismatch = 2458,
|
||||
NoSuchServer = 2460,
|
||||
NoSuchSession = 2461,
|
||||
NoSuchConnection = 2462,
|
||||
TooManyServers = 2463,
|
||||
TooManySessions = 2464,
|
||||
TooManyConnections = 2465,
|
||||
TooManyFiles = 2466,
|
||||
NoAlternateServers = 2467,
|
||||
TryDownLevel = 2470,
|
||||
UPSDriverNotStarted = 2480,
|
||||
UPSInvalidConfig = 2481,
|
||||
UPSInvalidCommPort = 2482,
|
||||
UPSSignalAsserted = 2483,
|
||||
UPSShutdownFailed = 2484,
|
||||
BadDosRetCode = 2500,
|
||||
ProgNeedsExtraMem = 2501,
|
||||
BadDosFunction = 2502,
|
||||
RemoteBootFailed = 2503,
|
||||
BadFileCheckSum = 2504,
|
||||
NoRplBootSystem = 2505,
|
||||
RplLoadrNetBiosErr = 2506,
|
||||
RplLoadrDiskErr = 2507,
|
||||
ImageParamErr = 2508,
|
||||
TooManyImageParams = 2509,
|
||||
NonDosFloppyUsed = 2510,
|
||||
RplBootRestart = 2511,
|
||||
RplSrvrCallFailed = 2512,
|
||||
CantConnectRplSrvr = 2513,
|
||||
CantOpenImageFile = 2514,
|
||||
CallingRplSrvr = 2515,
|
||||
StartingRplBoot = 2516,
|
||||
RplBootServiceTerm = 2517,
|
||||
RplBootStartFailed = 2518,
|
||||
RPL_CONNECTED = 2519,
|
||||
BrowserConfiguredToNotRun = 2550,
|
||||
RplNoAdaptersStarted = 2610,
|
||||
RplBadRegistry = 2611,
|
||||
RplBadDatabase = 2612,
|
||||
RplRplfilesShare = 2613,
|
||||
RplNotRplServer = 2614,
|
||||
RplCannotEnum = 2615,
|
||||
RplWkstaInfoCorrupted = 2616,
|
||||
RplWkstaNotFound = 2617,
|
||||
RplWkstaNameUnavailable = 2618,
|
||||
RplProfileInfoCorrupted = 2619,
|
||||
RplProfileNotFound = 2620,
|
||||
RplProfileNameUnavailable = 2621,
|
||||
RplProfileNotEmpty = 2622,
|
||||
RplConfigInfoCorrupted = 2623,
|
||||
RplConfigNotFound = 2624,
|
||||
RplAdapterInfoCorrupted = 2625,
|
||||
RplInternal = 2626,
|
||||
RplVendorInfoCorrupted = 2627,
|
||||
RplBootInfoCorrupted = 2628,
|
||||
RplWkstaNeedsUserAcct = 2629,
|
||||
RplNeedsRPLUSERAcct = 2630,
|
||||
RplBootNotFound = 2631,
|
||||
RplIncompatibleProfile = 2632,
|
||||
RplAdapterNameUnavailable = 2633,
|
||||
RplConfigNotEmpty = 2634,
|
||||
RplBootInUse = 2635,
|
||||
RplBackupDatabase = 2636,
|
||||
RplAdapterNotFound = 2637,
|
||||
RplVendorNotFound = 2638,
|
||||
RplVendorNameUnavailable = 2639,
|
||||
RplBootNameUnavailable = 2640,
|
||||
RplConfigNameUnavailable = 2641,
|
||||
DfsInternalCorruption = 2660,
|
||||
DfsVolumeDataCorrupt = 2661,
|
||||
DfsNoSuchVolume = 2662,
|
||||
DfsVolumeAlreadyExists = 2663,
|
||||
DfsAlreadyShared = 2664,
|
||||
DfsNoSuchShare = 2665,
|
||||
DfsNotALeafVolume = 2666,
|
||||
DfsLeafVolume = 2667,
|
||||
DfsVolumeHasMultipleServers = 2668,
|
||||
DfsCantCreateJunctionPoint = 2669,
|
||||
DfsServerNotDfsAware = 2670,
|
||||
DfsBadRenamePath = 2671,
|
||||
DfsVolumeIsOffline = 2672,
|
||||
DfsNoSuchServer = 2673,
|
||||
DfsCyclicalName = 2674,
|
||||
DfsNotSupportedInServerDfs = 2675,
|
||||
DfsDuplicateService = 2676,
|
||||
DfsCantRemoveLastServerShare = 2677,
|
||||
DfsVolumeIsInterDfs = 2678,
|
||||
DfsInconsistent = 2679,
|
||||
DfsServerUpgraded = 2680,
|
||||
DfsDataIsIdentical = 2681,
|
||||
DfsCantRemoveDfsRoot = 2682,
|
||||
DfsChildOrParentInDfs = 2683,
|
||||
DfsInternalError = 2690,
|
||||
SetupAlreadyJoined = 2691,
|
||||
SetupNotJoined = 2692,
|
||||
SetupDomainController = 2693,
|
||||
DefaultJoinRequired = 2694,
|
||||
InvalidWorkgroupName = 2695,
|
||||
NameUsesIncompatibleCodePage = 2696,
|
||||
ComputerAccountNotFound = 2697,
|
||||
PersonalSku = 2698,
|
||||
SetupCheckDNSConfig = 2699,
|
||||
PasswordMustChange = 2701,
|
||||
AccountLockedOut = 2702,
|
||||
PasswordTooLong = 2703,
|
||||
PasswordNotComplexEnough = 2704,
|
||||
PasswordFilterError = 2705,
|
||||
}
|
||||
|
||||
@@ -7,4 +7,32 @@ foreign userenv {
|
||||
GetUserProfileDirectoryW :: proc(hToken: HANDLE,
|
||||
lpProfileDir: LPWSTR,
|
||||
lpcchSize: ^DWORD) -> BOOL ---
|
||||
LoadUserProfileW :: proc(
|
||||
hToken: HANDLE,
|
||||
lpProfileInfo: ^PROFILEINFOW,
|
||||
) -> BOOL ---
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/userenv/nf-userenv-createprofile
|
||||
// The caller must have administrator privileges to call this function.
|
||||
CreateProfile :: proc(
|
||||
pszUserSid: LPCWSTR,
|
||||
pszUserName: LPCWSTR,
|
||||
pszProfilePath: wstring,
|
||||
cchProfilePath: DWORD,
|
||||
) -> u32 ---
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/userenv/nf-userenv-deleteprofilew
|
||||
// The caller must have administrative privileges to delete a user's profile.
|
||||
DeleteProfileW :: proc(
|
||||
lpSidString: LPCWSTR,
|
||||
lpProfilePath: LPCWSTR,
|
||||
lpComputerName: LPCWSTR,
|
||||
) -> BOOL ---
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/sddl/nf-sddl-convertsidtostringsida
|
||||
// To turn a SID into a string SID to use with CreateProfile & DeleteProfileW.
|
||||
ConvertSidToStringSidW :: proc(
|
||||
Sid: ^SID,
|
||||
StringSid: ^LPCWSTR,
|
||||
) -> BOOL ---
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package sys_windows
|
||||
|
||||
import "core:strings"
|
||||
import "core:sys/win32"
|
||||
|
||||
LOWORD :: #force_inline proc "contextless" (x: DWORD) -> WORD {
|
||||
return WORD(x & 0xffff);
|
||||
}
|
||||
@@ -81,3 +84,374 @@ utf16_to_utf8 :: proc(s: []u16, allocator := context.temp_allocator) -> string {
|
||||
return wstring_to_utf8(raw_data(s), len(s), allocator);
|
||||
}
|
||||
|
||||
// AdvAPI32, NetAPI32 and UserENV helpers.
|
||||
|
||||
allowed_username :: proc(username: string) -> bool {
|
||||
/*
|
||||
User account names are limited to 20 characters and group names are limited to 256 characters.
|
||||
In addition, account names cannot be terminated by a period and they cannot include commas or any of the following printable characters:
|
||||
", /, , [, ], :, |, <, >, +, =, ;, ?, *. Names also cannot include characters in the range 1-31, which are nonprintable.
|
||||
*/
|
||||
|
||||
_DISALLOWED :: "\"/ []:|<>+=;?*,";
|
||||
|
||||
if len(username) > LM20_UNLEN || len(username) == 0 {
|
||||
return false;
|
||||
}
|
||||
if username[len(username)-1] == '.' {
|
||||
return false;
|
||||
}
|
||||
|
||||
for r in username {
|
||||
if r > 0 && r < 32 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if strings.contains_any(username, _DISALLOWED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns .Success on success.
|
||||
_add_user :: proc(servername: string, username: string, password: string) -> (ok: NET_API_STATUS) {
|
||||
|
||||
servername_w: wstring;
|
||||
username_w: []u16;
|
||||
password_w: []u16;
|
||||
|
||||
if len(servername) == 0 {
|
||||
// Create account on this computer
|
||||
servername_w = nil;
|
||||
} else {
|
||||
server := utf8_to_utf16(servername, context.temp_allocator);
|
||||
servername_w = &server[0];
|
||||
}
|
||||
|
||||
if len(username) == 0 || len(username) > LM20_UNLEN {
|
||||
return .BadUsername;
|
||||
}
|
||||
if !allowed_username(username) {
|
||||
return .BadUsername;
|
||||
}
|
||||
if len(password) == 0 || len(password) > LM20_PWLEN {
|
||||
return .BadPassword;
|
||||
}
|
||||
|
||||
username_w = utf8_to_utf16(username, context.temp_allocator);
|
||||
password_w = utf8_to_utf16(password, context.temp_allocator);
|
||||
|
||||
|
||||
level := DWORD(1);
|
||||
parm_err: DWORD;
|
||||
|
||||
user_info := USER_INFO_1{
|
||||
name = &username_w[0],
|
||||
password = &password_w[0], // Max password length is defined in LM20_PWLEN.
|
||||
password_age = 0, // Ignored
|
||||
priv = .User,
|
||||
home_dir = nil, // We'll set it later
|
||||
comment = nil,
|
||||
flags = {.Script, .Normal_Account},
|
||||
script_path = nil,
|
||||
};
|
||||
|
||||
ok = NetUserAdd(
|
||||
servername_w,
|
||||
level,
|
||||
&user_info,
|
||||
&parm_err,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
get_computer_name_and_account_sid :: proc(username: string) -> (computer_name: string, sid := SID{}, ok: bool) {
|
||||
|
||||
username_w := utf8_to_utf16(username, context.temp_allocator);
|
||||
cbsid: DWORD;
|
||||
computer_name_size: DWORD;
|
||||
pe_use := SID_TYPE.User;
|
||||
|
||||
res := LookupAccountNameW(
|
||||
nil, // Look on this computer first
|
||||
&username_w[0],
|
||||
&sid,
|
||||
&cbsid,
|
||||
nil,
|
||||
&computer_name_size,
|
||||
&pe_use,
|
||||
);
|
||||
if computer_name_size == 0 {
|
||||
// User didn't exist, or we'd have a size here.
|
||||
return "", {}, false;
|
||||
}
|
||||
|
||||
cname_w := make([]u16, min(computer_name_size, 1), context.temp_allocator);
|
||||
|
||||
res = LookupAccountNameW(
|
||||
nil,
|
||||
&username_w[0],
|
||||
&sid,
|
||||
&cbsid,
|
||||
&cname_w[0],
|
||||
&computer_name_size,
|
||||
&pe_use,
|
||||
);
|
||||
|
||||
if !res {
|
||||
return "", {}, false;
|
||||
}
|
||||
computer_name = utf16_to_utf8(cname_w, context.temp_allocator);
|
||||
|
||||
ok = true;
|
||||
return;
|
||||
}
|
||||
|
||||
get_sid :: proc(username: string, sid: ^SID) -> (ok: bool) {
|
||||
|
||||
username_w := utf8_to_utf16(username, context.temp_allocator);
|
||||
cbsid: DWORD;
|
||||
computer_name_size: DWORD;
|
||||
pe_use := SID_TYPE.User;
|
||||
|
||||
res := LookupAccountNameW(
|
||||
nil, // Look on this computer first
|
||||
&username_w[0],
|
||||
sid,
|
||||
&cbsid,
|
||||
nil,
|
||||
&computer_name_size,
|
||||
&pe_use,
|
||||
);
|
||||
if computer_name_size == 0 {
|
||||
// User didn't exist, or we'd have a size here.
|
||||
return false;
|
||||
}
|
||||
|
||||
cname_w := make([]u16, min(computer_name_size, 1), context.temp_allocator);
|
||||
|
||||
res = LookupAccountNameW(
|
||||
nil,
|
||||
&username_w[0],
|
||||
sid,
|
||||
&cbsid,
|
||||
&cname_w[0],
|
||||
&computer_name_size,
|
||||
&pe_use,
|
||||
);
|
||||
|
||||
if !res {
|
||||
return false;
|
||||
}
|
||||
ok = true;
|
||||
return;
|
||||
}
|
||||
|
||||
add_user_to_group :: proc(sid: ^SID, group: string) -> (ok: NET_API_STATUS) {
|
||||
group_member := LOCALGROUP_MEMBERS_INFO_0{
|
||||
sid = sid,
|
||||
};
|
||||
group_name := utf8_to_utf16(group, context.temp_allocator);
|
||||
ok = NetLocalGroupAddMembers(
|
||||
nil,
|
||||
&group_name[0],
|
||||
0,
|
||||
&group_member,
|
||||
1,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
add_del_from_group :: proc(sid: ^SID, group: string) -> (ok: NET_API_STATUS) {
|
||||
group_member := LOCALGROUP_MEMBERS_INFO_0{
|
||||
sid = sid,
|
||||
};
|
||||
group_name := utf8_to_utf16(group, context.temp_allocator);
|
||||
ok = NetLocalGroupDelMembers(
|
||||
nil,
|
||||
&group_name[0],
|
||||
0,
|
||||
&group_member,
|
||||
1,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
add_user_profile :: proc(username: string) -> (ok: bool, profile_path: string) {
|
||||
username_w := utf8_to_utf16(username, context.temp_allocator);
|
||||
|
||||
sid := SID{};
|
||||
ok = get_sid(username, &sid);
|
||||
if ok == false {
|
||||
return false, "";
|
||||
}
|
||||
|
||||
sb: wstring;
|
||||
res := ConvertSidToStringSidW(&sid, &sb);
|
||||
if res == false {
|
||||
return false, "";
|
||||
}
|
||||
defer win32.local_free(sb);
|
||||
|
||||
pszProfilePath := make([]u16, 257, context.temp_allocator);
|
||||
res2 := CreateProfile(
|
||||
sb,
|
||||
&username_w[0],
|
||||
&pszProfilePath[0],
|
||||
257,
|
||||
);
|
||||
if res2 != 0 {
|
||||
return false, "";
|
||||
}
|
||||
profile_path = wstring_to_utf8(&pszProfilePath[0], 257);
|
||||
|
||||
return true, profile_path;
|
||||
}
|
||||
|
||||
|
||||
delete_user_profile :: proc(username: string) -> (ok: bool) {
|
||||
sid := SID{};
|
||||
ok = get_sid(username, &sid);
|
||||
if ok == false {
|
||||
return false;
|
||||
}
|
||||
|
||||
sb: wstring;
|
||||
res := ConvertSidToStringSidW(&sid, &sb);
|
||||
if res == false {
|
||||
return false;
|
||||
}
|
||||
defer win32.local_free(sb);
|
||||
|
||||
res2 := DeleteProfileW(
|
||||
sb,
|
||||
nil,
|
||||
nil,
|
||||
);
|
||||
return bool(res2);
|
||||
}
|
||||
|
||||
add_user :: proc(servername: string, username: string, password: string) -> (ok: bool) {
|
||||
/*
|
||||
Convenience function that creates a new user, adds it to the group Users and creates a profile directory for it.
|
||||
Requires elevated privileges (run as administrator).
|
||||
|
||||
TODO: Add a bool that governs whether to delete the user if adding to group and/or creating profile fail?
|
||||
TODO: SecureZeroMemory the password after use.
|
||||
*/
|
||||
|
||||
res := _add_user(servername, username, password);
|
||||
if res != .Success {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Grab the SID to add the user to the Users group.
|
||||
sid: SID;
|
||||
ok2 := get_sid(username, &sid);
|
||||
if ok2 == false {
|
||||
return false;
|
||||
}
|
||||
|
||||
ok3 := add_user_to_group(&sid, "Users");
|
||||
if ok3 != .Success {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
delete_user :: proc(servername: string, username: string) -> (ok: bool) {
|
||||
/*
|
||||
Convenience function that deletes a user.
|
||||
Requires elevated privileges (run as administrator).
|
||||
|
||||
TODO: Add a bool that governs whether to delete the profile from this wrapper?
|
||||
*/
|
||||
|
||||
servername_w: wstring;
|
||||
if len(servername) == 0 {
|
||||
// Delete account on this computer
|
||||
servername_w = nil;
|
||||
} else {
|
||||
server := utf8_to_utf16(servername, context.temp_allocator);
|
||||
servername_w = &server[0];
|
||||
}
|
||||
username_w := utf8_to_utf16(username);
|
||||
|
||||
res := NetUserDel(
|
||||
servername_w,
|
||||
&username_w[0],
|
||||
);
|
||||
if res != .Success {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
run_as_user :: proc(username, password, application, commandline: string, pi: ^PROCESS_INFORMATION, wait := true) -> (ok: bool) {
|
||||
/*
|
||||
Needs to be run as an account which has the "Replace a process level token" privilege.
|
||||
This can be added to an account from: Control Panel -> Administrative Tools -> Local Security Policy.
|
||||
The path to this policy is as follows: Local Policies -> User Rights Assignment -> Replace a process level token.
|
||||
A reboot may be required for this change to take effect and impersonating a user to work.
|
||||
|
||||
TODO: SecureZeroMemory the password after use.
|
||||
|
||||
*/
|
||||
|
||||
username_w := utf8_to_utf16(username);
|
||||
domain_w := utf8_to_utf16(".");
|
||||
password_w := utf8_to_utf16(password);
|
||||
app_w := utf8_to_utf16(application);
|
||||
|
||||
commandline_w: []u16 = {0};
|
||||
if len(commandline) > 0 {
|
||||
commandline_w = utf8_to_utf16(commandline);
|
||||
}
|
||||
|
||||
user_token: HANDLE;
|
||||
|
||||
ok = bool(LogonUserW(
|
||||
lpszUsername = &username_w[0],
|
||||
lpszDomain = &domain_w[0],
|
||||
lpszPassword = &password_w[0],
|
||||
dwLogonType = .NEW_CREDENTIALS,
|
||||
dwLogonProvider = .WINNT50,
|
||||
phToken = &user_token,
|
||||
));
|
||||
|
||||
if !ok {
|
||||
return false;
|
||||
// err := GetLastError();
|
||||
// fmt.printf("GetLastError: %v\n", err);
|
||||
}
|
||||
si := STARTUPINFO{};
|
||||
si.cb = size_of(STARTUPINFO);
|
||||
pi := pi;
|
||||
|
||||
ok = bool(CreateProcessAsUserW(
|
||||
user_token,
|
||||
&app_w[0],
|
||||
&commandline_w[0],
|
||||
nil, // lpProcessAttributes,
|
||||
nil, // lpThreadAttributes,
|
||||
false, // bInheritHandles,
|
||||
0, // creation flags
|
||||
nil, // environment,
|
||||
nil, // current directory: inherit from parent if nil
|
||||
&si,
|
||||
pi,
|
||||
));
|
||||
if ok {
|
||||
if wait {
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+40
-30
@@ -1,7 +1,6 @@
|
||||
package thread
|
||||
|
||||
import "core:runtime"
|
||||
import "core:sync"
|
||||
import "core:mem"
|
||||
import "intrinsics"
|
||||
|
||||
@@ -26,6 +25,46 @@ Thread :: struct {
|
||||
|
||||
#assert(size_of(Thread{}.user_index) == size_of(uintptr));
|
||||
|
||||
Thread_Priority :: enum {
|
||||
Normal,
|
||||
Low,
|
||||
High,
|
||||
}
|
||||
|
||||
create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread {
|
||||
return _create(procedure, priority);
|
||||
}
|
||||
destroy :: proc(thread: ^Thread) {
|
||||
_destroy(thread);
|
||||
}
|
||||
|
||||
start :: proc(thread: ^Thread) {
|
||||
_start(thread);
|
||||
}
|
||||
|
||||
is_done :: proc(thread: ^Thread) -> bool {
|
||||
return _is_done(thread);
|
||||
}
|
||||
|
||||
|
||||
join :: proc(thread: ^Thread) {
|
||||
_join(thread);
|
||||
}
|
||||
|
||||
|
||||
join_mulitple :: proc(threads: ..^Thread) {
|
||||
_join_multiple(..threads);
|
||||
}
|
||||
|
||||
terminate :: proc(thread: ^Thread, exit_code: int) {
|
||||
_terminate(thread, exit_code);
|
||||
}
|
||||
|
||||
yield :: proc() {
|
||||
_yield();
|
||||
}
|
||||
|
||||
|
||||
|
||||
run :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) {
|
||||
thread_proc :: proc(t: ^Thread) {
|
||||
@@ -39,7 +78,6 @@ run :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority :=
|
||||
start(t);
|
||||
}
|
||||
|
||||
|
||||
run_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) {
|
||||
thread_proc :: proc(t: ^Thread) {
|
||||
fn := cast(proc(rawptr))t.data;
|
||||
@@ -152,31 +190,3 @@ create_and_start :: proc(fn: Thread_Proc, init_context: Maybe(runtime.Context) =
|
||||
start(t);
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
Once :: struct {
|
||||
m: sync.Blocking_Mutex,
|
||||
done: bool,
|
||||
}
|
||||
once_init :: proc(o: ^Once) {
|
||||
sync.blocking_mutex_init(&o.m);
|
||||
intrinsics.atomic_store_rel(&o.done, false);
|
||||
}
|
||||
once_destroy :: proc(o: ^Once) {
|
||||
sync.blocking_mutex_destroy(&o.m);
|
||||
}
|
||||
|
||||
once_do :: proc(o: ^Once, fn: proc()) {
|
||||
if intrinsics.atomic_load(&o.done) == false {
|
||||
_once_do_slow(o, fn);
|
||||
}
|
||||
}
|
||||
|
||||
_once_do_slow :: proc(o: ^Once, fn: proc()) {
|
||||
sync.blocking_mutex_lock(&o.m);
|
||||
defer sync.blocking_mutex_unlock(&o.m);
|
||||
if !o.done {
|
||||
fn();
|
||||
intrinsics.atomic_store_rel(&o.done, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// +build linux, darwin, freebsd
|
||||
package thread;
|
||||
// +private
|
||||
package thread
|
||||
|
||||
import "core:runtime"
|
||||
import "core:intrinsics"
|
||||
@@ -19,7 +20,7 @@ Thread_Os_Specific :: struct #align 16 {
|
||||
// in a suspended state, we have it wait on this gate, which we
|
||||
// signal to start it.
|
||||
// destroyed after thread is started.
|
||||
start_gate: sync.Condition,
|
||||
start_gate: sync.Condition,
|
||||
start_mutex: sync.Mutex,
|
||||
|
||||
// if true, the thread has been started and the start_gate has been destroyed.
|
||||
@@ -31,18 +32,11 @@ Thread_Os_Specific :: struct #align 16 {
|
||||
// See the comment in `join`.
|
||||
already_joined: bool,
|
||||
}
|
||||
|
||||
Thread_Priority :: enum {
|
||||
Normal,
|
||||
Low,
|
||||
High,
|
||||
}
|
||||
|
||||
//
|
||||
// Creates a thread which will run the given procedure.
|
||||
// It then waits for `start` to be called.
|
||||
//
|
||||
create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread {
|
||||
_create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread {
|
||||
__linux_thread_entry_proc :: proc "c" (t: rawptr) -> rawptr {
|
||||
context = runtime.default_context();
|
||||
|
||||
@@ -67,7 +61,7 @@ create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^T
|
||||
}
|
||||
}
|
||||
|
||||
sync.atomic_store(&t.done, true, .Sequentially_Consistent);
|
||||
intrinsics.atomic_store(&t.done, true);
|
||||
return nil;
|
||||
}
|
||||
|
||||
@@ -104,29 +98,30 @@ create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^T
|
||||
res = unix.pthread_attr_setschedparam(&attrs, ¶ms);
|
||||
assert(res == 0);
|
||||
|
||||
sync.mutex_init(&thread.start_mutex);
|
||||
sync.condition_init(&thread.start_gate, &thread.start_mutex);
|
||||
if unix.pthread_create(&thread.unix_thread, &attrs, __linux_thread_entry_proc, thread) != 0 {
|
||||
free(thread, thread.creation_allocator);
|
||||
return nil;
|
||||
}
|
||||
thread.procedure = procedure;
|
||||
|
||||
sync.mutex_init(&thread.start_mutex);
|
||||
sync.condition_init(&thread.start_gate, &thread.start_mutex);
|
||||
|
||||
return thread;
|
||||
}
|
||||
|
||||
start :: proc(t: ^Thread) {
|
||||
if sync.atomic_swap(&t.started, true, .Sequentially_Consistent) {
|
||||
_start :: proc(t: ^Thread) {
|
||||
if intrinsics.atomic_xchg(&t.started, true) {
|
||||
return;
|
||||
}
|
||||
sync.condition_signal(&t.start_gate);
|
||||
}
|
||||
|
||||
is_done :: proc(t: ^Thread) -> bool {
|
||||
return sync.atomic_load(&t.done, .Sequentially_Consistent);
|
||||
_is_done :: proc(t: ^Thread) -> bool {
|
||||
return intrinsics.atomic_load(&t.done);
|
||||
}
|
||||
|
||||
join :: proc(t: ^Thread) {
|
||||
_join :: proc(t: ^Thread) {
|
||||
if unix.pthread_equal(unix.pthread_self(), t.unix_thread) {
|
||||
return;
|
||||
}
|
||||
@@ -138,9 +133,9 @@ join :: proc(t: ^Thread) {
|
||||
// See note on `already_joined` field.
|
||||
// TODO(tetra): I'm not sure if we should do this, or panic, since I'm not
|
||||
// sure it makes sense to need to join from multiple threads?
|
||||
if sync.atomic_swap(&t.already_joined, true, .Sequentially_Consistent) {
|
||||
if intrinsics.atomic_xchg(&t.already_joined, true) {
|
||||
for {
|
||||
if sync.atomic_load(&t.done, .Sequentially_Consistent) {
|
||||
if intrinsics.atomic_load(&t.done) {
|
||||
return;
|
||||
}
|
||||
intrinsics.cpu_relax();
|
||||
@@ -152,31 +147,37 @@ join :: proc(t: ^Thread) {
|
||||
// We do this instead because I don't know if there is a danger
|
||||
// that you may join a different thread from the one you called join on,
|
||||
// if the thread handle is reused.
|
||||
if sync.atomic_load(&t.done, .Sequentially_Consistent) {
|
||||
if intrinsics.atomic_load(&t.done) {
|
||||
return;
|
||||
}
|
||||
|
||||
ret_val: rawptr;
|
||||
_ = unix.pthread_join(t.unix_thread, &ret_val);
|
||||
if !sync.atomic_load(&t.done, .Sequentially_Consistent) {
|
||||
if !intrinsics.atomic_load(&t.done) {
|
||||
panic("thread not done after join");
|
||||
}
|
||||
}
|
||||
|
||||
join_multiple :: proc(threads: ..^Thread) {
|
||||
_join_multiple :: proc(threads: ..^Thread) {
|
||||
for t in threads {
|
||||
join(t);
|
||||
_join(t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
destroy :: proc(t: ^Thread) {
|
||||
join(t);
|
||||
_destroy :: proc(t: ^Thread) {
|
||||
_join(t);
|
||||
sync.condition_destroy(&t.start_gate);
|
||||
sync.mutex_destroy(&t.start_mutex);
|
||||
t.unix_thread = {};
|
||||
free(t, t.creation_allocator);
|
||||
}
|
||||
|
||||
|
||||
yield :: proc() {
|
||||
_terminate :: proc(t: ^Thread, exit_code: int) {
|
||||
// TODO(bill)
|
||||
}
|
||||
|
||||
_yield :: proc() {
|
||||
unix.sched_yield();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
//+build windows
|
||||
//+private
|
||||
package thread
|
||||
|
||||
import "core:runtime"
|
||||
import "core:sync"
|
||||
import sync "core:sync/sync2"
|
||||
import win32 "core:sys/windows"
|
||||
|
||||
Thread_Os_Specific :: struct {
|
||||
@@ -10,20 +12,13 @@ Thread_Os_Specific :: struct {
|
||||
done: bool, // see note in `is_done`
|
||||
}
|
||||
|
||||
|
||||
Thread_Priority :: enum {
|
||||
Normal,
|
||||
Low,
|
||||
High,
|
||||
}
|
||||
|
||||
_thread_priority_map := [Thread_Priority]i32{
|
||||
.Normal = 0,
|
||||
.Low = -2,
|
||||
.High = +2,
|
||||
};
|
||||
|
||||
create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread {
|
||||
_create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread {
|
||||
win32_thread_id: win32.DWORD;
|
||||
|
||||
__windows_thread_entry_proc :: proc "stdcall" (t_: rawptr) -> win32.DWORD {
|
||||
@@ -43,7 +38,7 @@ create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^T
|
||||
}
|
||||
}
|
||||
|
||||
sync.atomic_store(&t.done, true, .Sequentially_Consistent);
|
||||
sync.atomic_store(&t.done, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -70,18 +65,18 @@ create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^T
|
||||
return thread;
|
||||
}
|
||||
|
||||
start :: proc(using thread: ^Thread) {
|
||||
win32.ResumeThread(win32_thread);
|
||||
_start :: proc(thread: ^Thread) {
|
||||
win32.ResumeThread(thread.win32_thread);
|
||||
}
|
||||
|
||||
is_done :: proc(using thread: ^Thread) -> bool {
|
||||
_is_done :: proc(using thread: ^Thread) -> bool {
|
||||
// NOTE(tetra, 2019-10-31): Apparently using wait_for_single_object and
|
||||
// checking if it didn't time out immediately, is not good enough,
|
||||
// so we do it this way instead.
|
||||
return sync.atomic_load(&done, .Sequentially_Consistent);
|
||||
return sync.atomic_load(&done);
|
||||
}
|
||||
|
||||
join :: proc(using thread: ^Thread) {
|
||||
_join :: proc(using thread: ^Thread) {
|
||||
if win32_thread != win32.INVALID_HANDLE {
|
||||
win32.WaitForSingleObject(win32_thread, win32.INFINITE);
|
||||
win32.CloseHandle(win32_thread);
|
||||
@@ -89,7 +84,7 @@ join :: proc(using thread: ^Thread) {
|
||||
}
|
||||
}
|
||||
|
||||
join_multiple :: proc(threads: ..^Thread) {
|
||||
_join_multiple :: proc(threads: ..^Thread) {
|
||||
MAXIMUM_WAIT_OBJECTS :: 64;
|
||||
|
||||
handles: [MAXIMUM_WAIT_OBJECTS]win32.HANDLE;
|
||||
@@ -113,16 +108,16 @@ join_multiple :: proc(threads: ..^Thread) {
|
||||
}
|
||||
}
|
||||
|
||||
destroy :: proc(thread: ^Thread) {
|
||||
join(thread);
|
||||
_destroy :: proc(thread: ^Thread) {
|
||||
_join(thread);
|
||||
free(thread, thread.creation_allocator);
|
||||
}
|
||||
|
||||
terminate :: proc(using thread : ^Thread, exit_code: u32) {
|
||||
win32.TerminateThread(win32_thread, exit_code);
|
||||
_terminate :: proc(using thread : ^Thread, exit_code: int) {
|
||||
win32.TerminateThread(win32_thread, u32(exit_code));
|
||||
}
|
||||
|
||||
yield :: proc() {
|
||||
_yield :: proc() {
|
||||
win32.SwitchToThread();
|
||||
}
|
||||
|
||||
|
||||
@@ -127,6 +127,7 @@ char const *odin_command_strings[32] = {
|
||||
"query",
|
||||
"doc",
|
||||
"version",
|
||||
"test",
|
||||
};
|
||||
|
||||
|
||||
@@ -134,6 +135,7 @@ char const *odin_command_strings[32] = {
|
||||
enum CmdDocFlag : u32 {
|
||||
CmdDocFlag_Short = 1<<0,
|
||||
CmdDocFlag_AllPackages = 1<<1,
|
||||
CmdDocFlag_DocFormat = 1<<2,
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -690,6 +690,10 @@ void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
|
||||
if (ac.test) {
|
||||
e->flags |= EntityFlag_Test;
|
||||
}
|
||||
if (ac.set_cold) {
|
||||
e->flags |= EntityFlag_Cold;
|
||||
}
|
||||
|
||||
e->Procedure.is_export = ac.is_export;
|
||||
e->deprecated_message = ac.deprecated_message;
|
||||
ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix);
|
||||
|
||||
+16
-16
@@ -3515,7 +3515,7 @@ Entity *check_selector(CheckerContext *c, Operand *operand, Ast *node, Type *typ
|
||||
if (entity->kind == Entity_Builtin) {
|
||||
// NOTE(bill): Builtin's are in the universal scope which is part of every scopes hierarchy
|
||||
// This means that we should just ignore the found result through it
|
||||
allow_builtin = entity->scope == import_scope;
|
||||
allow_builtin = entity->scope == import_scope || entity->scope != builtin_pkg->scope;
|
||||
} else if ((entity->scope->flags&ScopeFlag_Global) == ScopeFlag_Global && (import_scope->flags&ScopeFlag_Global) == 0) {
|
||||
is_declared = false;
|
||||
}
|
||||
@@ -8175,24 +8175,24 @@ ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *pr
|
||||
}
|
||||
|
||||
switch (inlining) {
|
||||
case ProcInlining_inline: {
|
||||
if (proc != nullptr) {
|
||||
Entity *e = entity_from_expr(proc);
|
||||
if (e != nullptr && e->kind == Entity_Procedure) {
|
||||
DeclInfo *decl = e->decl_info;
|
||||
if (decl->proc_lit) {
|
||||
ast_node(pl, ProcLit, decl->proc_lit);
|
||||
if (pl->inlining == ProcInlining_no_inline) {
|
||||
error(call, "'inline' cannot be applied to a procedure that has be marked as 'no_inline'");
|
||||
}
|
||||
case ProcInlining_inline: {
|
||||
if (proc != nullptr) {
|
||||
Entity *e = entity_from_expr(proc);
|
||||
if (e != nullptr && e->kind == Entity_Procedure) {
|
||||
DeclInfo *decl = e->decl_info;
|
||||
if (decl->proc_lit) {
|
||||
ast_node(pl, ProcLit, decl->proc_lit);
|
||||
if (pl->inlining == ProcInlining_no_inline) {
|
||||
error(call, "'inline' cannot be applied to a procedure that has be marked as 'no_inline'");
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ProcInlining_no_inline:
|
||||
break;
|
||||
case ProcInlining_no_inline:
|
||||
break;
|
||||
}
|
||||
|
||||
operand->expr = call;
|
||||
@@ -11019,10 +11019,10 @@ gbString write_expr_to_string(gbString str, Ast *node, bool shorthand) {
|
||||
case_ast_node(ce, CallExpr, node);
|
||||
switch (ce->inlining) {
|
||||
case ProcInlining_inline:
|
||||
str = gb_string_appendc(str, "inline ");
|
||||
str = gb_string_appendc(str, "#force_inline ");
|
||||
break;
|
||||
case ProcInlining_no_inline:
|
||||
str = gb_string_appendc(str, "no_inline ");
|
||||
str = gb_string_appendc(str, "#force_no_inline ");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -731,6 +731,7 @@ void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *named_type, Ast
|
||||
Ast *field = et->fields[i];
|
||||
Ast *ident = nullptr;
|
||||
Ast *init = nullptr;
|
||||
u32 entity_flags = 0;
|
||||
if (field->kind == Ast_FieldValue) {
|
||||
ast_node(fv, FieldValue, field);
|
||||
if (fv->field == nullptr || fv->field->kind != Ast_Ident) {
|
||||
@@ -764,6 +765,7 @@ void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *named_type, Ast
|
||||
}
|
||||
} else {
|
||||
iota = exact_binary_operator_value(Token_Add, iota, exact_value_i64(1));
|
||||
entity_flags |= EntityConstantFlag_ImplicitEnumValue;
|
||||
}
|
||||
|
||||
|
||||
@@ -800,6 +802,7 @@ void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *named_type, Ast
|
||||
e->identifier = ident;
|
||||
e->flags |= EntityFlag_Visited;
|
||||
e->state = EntityState_Resolved;
|
||||
e->Constant.flags |= entity_flags;
|
||||
|
||||
if (scope_lookup_current(ctx->scope, name) != nullptr) {
|
||||
error(ident, "'%.*s' is already declared in this enumeration", LIT(name));
|
||||
@@ -2461,7 +2464,6 @@ bool check_procedure_type(CheckerContext *ctx, Type *type, Ast *proc_type_node,
|
||||
type->Proc.specialization_count = specialization_count;
|
||||
type->Proc.diverging = pt->diverging;
|
||||
type->Proc.optional_ok = optional_ok;
|
||||
type->Proc.tags = pt->tags;
|
||||
|
||||
if (param_count > 0) {
|
||||
Entity *end = params->Tuple.variables[param_count-1];
|
||||
|
||||
@@ -2562,6 +2562,18 @@ DECL_ATTRIBUTE_PROC(proc_decl_attribute) {
|
||||
error(elem, "Expected a boolean value for '%.*s'", LIT(name));
|
||||
}
|
||||
return true;
|
||||
} else if (name == "cold") {
|
||||
if (value == nullptr) {
|
||||
ac->set_cold = true;
|
||||
} else {
|
||||
ExactValue ev = check_decl_attribute_value(c, value);
|
||||
if (ev.kind == ExactValue_Bool) {
|
||||
ac->set_cold = ev.value_bool;
|
||||
} else {
|
||||
error(elem, "Expected a boolean value for '%.*s' or no value whatsoever", LIT(name));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@ struct AttributeContext {
|
||||
bool has_disabled_proc;
|
||||
bool disabled_proc;
|
||||
bool test;
|
||||
bool set_cold;
|
||||
String link_name;
|
||||
String link_prefix;
|
||||
isize init_expr_list_count;
|
||||
|
||||
+63
-16
@@ -34,9 +34,17 @@ GB_COMPARE_PROC(cmp_entities_for_printing) {
|
||||
Entity *x = *cast(Entity **)a;
|
||||
Entity *y = *cast(Entity **)b;
|
||||
int res = 0;
|
||||
res = string_compare(x->pkg->name, y->pkg->name);
|
||||
if (res != 0) {
|
||||
return res;
|
||||
if (x->pkg != y->pkg) {
|
||||
if (x->pkg == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
if (y->pkg == nullptr) {
|
||||
return +1;
|
||||
}
|
||||
res = string_compare(x->pkg->name, y->pkg->name);
|
||||
if (res != 0) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
int ox = print_entity_kind_ordering[x->kind];
|
||||
int oy = print_entity_kind_ordering[y->kind];
|
||||
@@ -56,6 +64,9 @@ GB_COMPARE_PROC(cmp_ast_package_by_name) {
|
||||
return string_compare(x->name, y->name);
|
||||
}
|
||||
|
||||
#include "docs_format.cpp"
|
||||
#include "docs_writer.cpp"
|
||||
|
||||
void print_doc_line(i32 indent, char const *fmt, ...) {
|
||||
while (indent --> 0) {
|
||||
gb_printf("\t");
|
||||
@@ -297,23 +308,59 @@ void print_doc_package(CheckerInfo *info, AstPackage *pkg) {
|
||||
void generate_documentation(Checker *c) {
|
||||
CheckerInfo *info = &c->info;
|
||||
|
||||
auto pkgs = array_make<AstPackage *>(permanent_allocator(), 0, info->packages.entries.count);
|
||||
for_array(i, info->packages.entries) {
|
||||
AstPackage *pkg = info->packages.entries[i].value;
|
||||
if (build_context.cmd_doc_flags & CmdDocFlag_AllPackages) {
|
||||
array_add(&pkgs, pkg);
|
||||
if (build_context.cmd_doc_flags & CmdDocFlag_DocFormat) {
|
||||
String init_fullpath = c->parser->init_fullpath;
|
||||
String output_name = {};
|
||||
String output_base = {};
|
||||
|
||||
if (build_context.out_filepath.len == 0) {
|
||||
output_name = remove_directory_from_path(init_fullpath);
|
||||
output_name = remove_extension_from_path(output_name);
|
||||
output_name = string_trim_whitespace(output_name);
|
||||
if (output_name.len == 0) {
|
||||
output_name = info->init_scope->pkg->name;
|
||||
}
|
||||
output_base = output_name;
|
||||
} else {
|
||||
if (pkg->kind == Package_Init) {
|
||||
array_add(&pkgs, pkg);
|
||||
} else if (pkg->is_extra) {
|
||||
array_add(&pkgs, pkg);
|
||||
output_name = build_context.out_filepath;
|
||||
output_name = string_trim_whitespace(output_name);
|
||||
if (output_name.len == 0) {
|
||||
output_name = info->init_scope->pkg->name;
|
||||
}
|
||||
isize pos = string_extension_position(output_name);
|
||||
if (pos < 0) {
|
||||
output_base = output_name;
|
||||
} else {
|
||||
output_base = substring(output_name, 0, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gb_sort_array(pkgs.data, pkgs.count, cmp_ast_package_by_name);
|
||||
output_base = path_to_full_path(permanent_allocator(), output_base);
|
||||
|
||||
for_array(i, pkgs) {
|
||||
print_doc_package(info, pkgs[i]);
|
||||
gbString output_file_path = gb_string_make_length(heap_allocator(), output_base.text, output_base.len);
|
||||
output_file_path = gb_string_appendc(output_file_path, ".odin-doc");
|
||||
defer (gb_string_free(output_file_path));
|
||||
|
||||
odin_doc_write(info, output_file_path);
|
||||
} else {
|
||||
auto pkgs = array_make<AstPackage *>(permanent_allocator(), 0, info->packages.entries.count);
|
||||
for_array(i, info->packages.entries) {
|
||||
AstPackage *pkg = info->packages.entries[i].value;
|
||||
if (build_context.cmd_doc_flags & CmdDocFlag_AllPackages) {
|
||||
array_add(&pkgs, pkg);
|
||||
} else {
|
||||
if (pkg->kind == Package_Init) {
|
||||
array_add(&pkgs, pkg);
|
||||
} else if (pkg->is_extra) {
|
||||
array_add(&pkgs, pkg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gb_sort_array(pkgs.data, pkgs.count, cmp_ast_package_by_name);
|
||||
|
||||
for_array(i, pkgs) {
|
||||
print_doc_package(info, pkgs[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
#define OdinDocHeader_MagicString "odindoc\0"
|
||||
|
||||
template <typename T>
|
||||
struct OdinDocArray {
|
||||
u32 offset;
|
||||
u32 length;
|
||||
};
|
||||
|
||||
using OdinDocString = OdinDocArray<u8>;
|
||||
|
||||
struct OdinDocVersionType {
|
||||
u8 major, minor, patch;
|
||||
u8 pad0;
|
||||
};
|
||||
|
||||
#define OdinDocVersionType_Major 0
|
||||
#define OdinDocVersionType_Minor 1
|
||||
#define OdinDocVersionType_Patch 0
|
||||
|
||||
struct OdinDocHeaderBase {
|
||||
u8 magic[8];
|
||||
u32 padding0;
|
||||
OdinDocVersionType version;
|
||||
u32 total_size;
|
||||
u32 header_size;
|
||||
u32 hash; // after header
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
Slice<T> from_array(OdinDocHeaderBase *base, OdinDocArray<T> const &a) {
|
||||
Slice<T> s = {};
|
||||
s.data = cast(T *)(cast(uintptr)base + cast(uintptr)a.offset);
|
||||
s.count = cast(isize)a.length;
|
||||
return s;
|
||||
}
|
||||
|
||||
String from_string(OdinDocHeaderBase *base, OdinDocString const &s) {
|
||||
String str = {};
|
||||
str.text = cast(u8 *)(cast(uintptr)base + cast(uintptr)s.offset);
|
||||
str.len = cast(isize)s.length;
|
||||
return str;
|
||||
}
|
||||
|
||||
typedef u32 OdinDocFileIndex;
|
||||
typedef u32 OdinDocPkgIndex;
|
||||
typedef u32 OdinDocEntityIndex;
|
||||
typedef u32 OdinDocTypeIndex;
|
||||
|
||||
struct OdinDocFile {
|
||||
OdinDocPkgIndex pkg;
|
||||
OdinDocString name;
|
||||
};
|
||||
|
||||
struct OdinDocPosition {
|
||||
OdinDocFileIndex file;
|
||||
u32 line;
|
||||
u32 column;
|
||||
u32 offset;
|
||||
};
|
||||
|
||||
enum OdinDocTypeKind : u32 {
|
||||
OdinDocType_Invalid = 0,
|
||||
OdinDocType_Basic = 1,
|
||||
OdinDocType_Named = 2,
|
||||
OdinDocType_Generic = 3,
|
||||
OdinDocType_Pointer = 4,
|
||||
OdinDocType_Array = 5,
|
||||
OdinDocType_EnumeratedArray = 6,
|
||||
OdinDocType_Slice = 7,
|
||||
OdinDocType_DynamicArray = 8,
|
||||
OdinDocType_Map = 9,
|
||||
OdinDocType_Struct = 10,
|
||||
OdinDocType_Union = 11,
|
||||
OdinDocType_Enum = 12,
|
||||
OdinDocType_Tuple = 13,
|
||||
OdinDocType_Proc = 14,
|
||||
OdinDocType_BitSet = 15,
|
||||
OdinDocType_SimdVector = 16,
|
||||
OdinDocType_SOAStructFixed = 17,
|
||||
OdinDocType_SOAStructSlice = 18,
|
||||
OdinDocType_SOAStructDynamic = 19,
|
||||
OdinDocType_RelativePointer = 20,
|
||||
OdinDocType_RelativeSlice = 21,
|
||||
};
|
||||
|
||||
enum OdinDocTypeFlag_Basic : u32 {
|
||||
OdinDocTypeFlag_Basic_untyped = 1<<1,
|
||||
};
|
||||
|
||||
enum OdinDocTypeFlag_Struct : u32 {
|
||||
OdinDocTypeFlag_Struct_polymorphic = 1<<0,
|
||||
OdinDocTypeFlag_Struct_packed = 1<<1,
|
||||
OdinDocTypeFlag_Struct_raw_union = 1<<2,
|
||||
};
|
||||
|
||||
enum OdinDocTypeFlag_Union : u32 {
|
||||
OdinDocTypeFlag_Union_polymorphic = 1<<0,
|
||||
OdinDocTypeFlag_Union_no_nil = 1<<1,
|
||||
OdinDocTypeFlag_Union_maybe = 1<<2,
|
||||
};
|
||||
|
||||
enum OdinDocTypeFlag_Proc : u32 {
|
||||
OdinDocTypeFlag_Proc_polymorphic = 1<<0,
|
||||
OdinDocTypeFlag_Proc_diverging = 1<<1,
|
||||
OdinDocTypeFlag_Proc_optional_ok = 1<<2,
|
||||
OdinDocTypeFlag_Proc_variadic = 1<<3,
|
||||
OdinDocTypeFlag_Proc_c_vararg = 1<<4,
|
||||
};
|
||||
|
||||
enum OdinDocTypeFlag_BitSet : u32 {
|
||||
OdinDocTypeFlag_BitSet_Range = 1<<1,
|
||||
OdinDocTypeFlag_BitSet_OpLt = 1<<2,
|
||||
OdinDocTypeFlag_BitSet_OpLtEq = 1<<3,
|
||||
OdinDocTypeFlag_BitSet_UnderlyingType = 1<<4,
|
||||
};
|
||||
|
||||
enum OdinDocTypeFlag_SimdVector : u32 {
|
||||
OdinDocTypeFlag_BitSet_x86_mmx = 1<<1,
|
||||
};
|
||||
|
||||
enum {
|
||||
// constants
|
||||
OdinDocType_ElemsCap = 4,
|
||||
};
|
||||
|
||||
struct OdinDocType {
|
||||
OdinDocTypeKind kind;
|
||||
u32 flags;
|
||||
OdinDocString name;
|
||||
OdinDocString custom_align;
|
||||
|
||||
// Used by some types
|
||||
u32 elem_count_len;
|
||||
i64 elem_counts[OdinDocType_ElemsCap];
|
||||
|
||||
// Each of these is esed by some types, not all
|
||||
OdinDocString calling_convention;
|
||||
OdinDocArray<OdinDocTypeIndex> types;
|
||||
OdinDocArray<OdinDocEntityIndex> entities;
|
||||
OdinDocTypeIndex polmorphic_params;
|
||||
OdinDocArray<OdinDocString> where_clauses;
|
||||
};
|
||||
|
||||
struct OdinDocAttribute {
|
||||
OdinDocString name;
|
||||
OdinDocString value;
|
||||
};
|
||||
|
||||
enum OdinDocEntityKind : u32 {
|
||||
OdinDocEntity_Invalid = 0,
|
||||
OdinDocEntity_Constant = 1,
|
||||
OdinDocEntity_Variable = 2,
|
||||
OdinDocEntity_TypeName = 3,
|
||||
OdinDocEntity_Procedure = 4,
|
||||
OdinDocEntity_ProcGroup = 5,
|
||||
OdinDocEntity_ImportName = 6,
|
||||
OdinDocEntity_LibraryName = 7,
|
||||
};
|
||||
|
||||
enum OdinDocEntityFlag : u32 {
|
||||
OdinDocEntityFlag_Foreign = 1<<0,
|
||||
OdinDocEntityFlag_Export = 1<<1,
|
||||
|
||||
OdinDocEntityFlag_Param_Using = 1<<2,
|
||||
OdinDocEntityFlag_Param_Const = 1<<3,
|
||||
OdinDocEntityFlag_Param_AutoCast = 1<<4,
|
||||
OdinDocEntityFlag_Param_Ellipsis = 1<<5,
|
||||
OdinDocEntityFlag_Param_CVararg = 1<<6,
|
||||
OdinDocEntityFlag_Param_NoAlias = 1<<7,
|
||||
|
||||
OdinDocEntityFlag_Type_Alias = 1<<8,
|
||||
|
||||
OdinDocEntityFlag_Var_Thread_Local = 1<<9,
|
||||
};
|
||||
|
||||
struct OdinDocEntity {
|
||||
OdinDocEntityKind kind;
|
||||
u32 flags;
|
||||
OdinDocPosition pos;
|
||||
OdinDocString name;
|
||||
OdinDocTypeIndex type;
|
||||
OdinDocString init_string;
|
||||
u32 reserved_for_init;
|
||||
OdinDocString comment;
|
||||
OdinDocString docs;
|
||||
OdinDocEntityIndex foreign_library;
|
||||
OdinDocString link_name;
|
||||
OdinDocArray<OdinDocAttribute> attributes;
|
||||
OdinDocArray<OdinDocEntityIndex> grouped_entities; // Procedure Groups
|
||||
OdinDocArray<OdinDocString> where_clauses; // Procedures
|
||||
};
|
||||
|
||||
enum OdinDocPkgFlags : u32 {
|
||||
OdinDocPkgFlag_Builtin = 1<<0,
|
||||
OdinDocPkgFlag_Runtime = 1<<1,
|
||||
OdinDocPkgFlag_Init = 1<<2,
|
||||
};
|
||||
|
||||
struct OdinDocPkg {
|
||||
OdinDocString fullpath;
|
||||
OdinDocString name;
|
||||
u32 flags;
|
||||
OdinDocString docs;
|
||||
OdinDocArray<OdinDocFileIndex> files;
|
||||
OdinDocArray<OdinDocEntityIndex> entities;
|
||||
};
|
||||
|
||||
struct OdinDocHeader {
|
||||
OdinDocHeaderBase base;
|
||||
|
||||
OdinDocArray<OdinDocFile> files;
|
||||
OdinDocArray<OdinDocPkg> pkgs;
|
||||
OdinDocArray<OdinDocEntity> entities;
|
||||
OdinDocArray<OdinDocType> types;
|
||||
};
|
||||
|
||||
+1092
File diff suppressed because it is too large
Load Diff
+9
-5
@@ -32,7 +32,7 @@ String const entity_strings[] = {
|
||||
#undef ENTITY_KIND
|
||||
};
|
||||
|
||||
enum EntityFlag : u32 {
|
||||
enum EntityFlag : u64 {
|
||||
EntityFlag_Visited = 1<<0,
|
||||
EntityFlag_Used = 1<<1,
|
||||
EntityFlag_Using = 1<<2,
|
||||
@@ -63,12 +63,13 @@ enum EntityFlag : u32 {
|
||||
EntityFlag_AutoCast = 1<<22,
|
||||
|
||||
EntityFlag_Disabled = 1<<24,
|
||||
EntityFlag_Cold = 1<<25, // procedure is rarely called
|
||||
|
||||
EntityFlag_Test = 1<<25,
|
||||
EntityFlag_Test = 1<<30,
|
||||
|
||||
};
|
||||
|
||||
enum EntityState {
|
||||
enum EntityState : u32 {
|
||||
EntityState_Unresolved = 0,
|
||||
EntityState_InProgress = 1,
|
||||
EntityState_Resolved = 2,
|
||||
@@ -92,13 +93,15 @@ struct ParameterValue {
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
enum EntityConstantFlags : u32 {
|
||||
EntityConstantFlag_ImplicitEnumValue = 1<<0,
|
||||
};
|
||||
|
||||
// An Entity is a named "thing" in the language
|
||||
struct Entity {
|
||||
EntityKind kind;
|
||||
u64 id;
|
||||
u32 flags;
|
||||
u64 flags;
|
||||
EntityState state;
|
||||
Token token;
|
||||
Scope * scope;
|
||||
@@ -125,6 +128,7 @@ struct Entity {
|
||||
struct {
|
||||
ExactValue value;
|
||||
ParameterValue param_value;
|
||||
u32 flags;
|
||||
} Constant;
|
||||
struct {
|
||||
Ast *init_expr; // only used for some variables within procedure bodies
|
||||
|
||||
+7
-1
@@ -132,6 +132,7 @@ struct irProcedure {
|
||||
irModule * module;
|
||||
String name;
|
||||
Type * type;
|
||||
Ast * proc_lit; // only for actual anonymous procedure literals
|
||||
Ast * type_expr;
|
||||
Ast * body;
|
||||
u64 tags;
|
||||
@@ -6894,7 +6895,7 @@ irTargetList *ir_push_target_list(irProcedure *proc, Ast *label, irBlock *break_
|
||||
}
|
||||
}
|
||||
|
||||
GB_PANIC("ir_set_label_blocks: Unreachable");
|
||||
GB_PANIC("ir_set_label_blocks: Unreachable; Unable to find label: %s", expr_to_string(label->Label.name));
|
||||
}
|
||||
|
||||
return tl;
|
||||
@@ -6927,6 +6928,7 @@ irValue *ir_gen_anonymous_proc_lit(irModule *m, String prefix_name, Ast *expr, i
|
||||
set_procedure_abi_types(type);
|
||||
irValue *value = ir_value_procedure(m, nullptr, type, pl->type, pl->body, name);
|
||||
|
||||
value->Proc.proc_lit = expr;
|
||||
value->Proc.tags = pl->tags;
|
||||
value->Proc.inlining = pl->inlining;
|
||||
value->Proc.parent = proc;
|
||||
@@ -11390,6 +11392,10 @@ void ir_begin_procedure_body(irProcedure *proc) {
|
||||
array_init(&proc->context_stack, heap_allocator());
|
||||
|
||||
DeclInfo *decl = decl_info_of_entity(proc->entity);
|
||||
if (decl == nullptr && proc->proc_lit != nullptr) {
|
||||
GB_ASSERT(proc->proc_lit->kind == Ast_ProcLit);
|
||||
decl = proc->proc_lit->ProcLit.decl;
|
||||
}
|
||||
if (decl != nullptr) {
|
||||
for_array(i, decl->labels) {
|
||||
BlockLabel bl = decl->labels[i];
|
||||
|
||||
+43
-3
@@ -2464,9 +2464,12 @@ void lb_add_proc_attribute_at_index(lbProcedure *p, isize index, char const *nam
|
||||
}
|
||||
|
||||
void lb_add_proc_attribute_at_index(lbProcedure *p, isize index, char const *name) {
|
||||
lb_add_proc_attribute_at_index(p, index, name, cast(u64)true);
|
||||
lb_add_proc_attribute_at_index(p, index, name, 0);
|
||||
}
|
||||
|
||||
void lb_add_attribute_to_proc(lbModule *m, LLVMValueRef proc_value, char const *name, u64 value=0) {
|
||||
LLVMAddAttributeAtIndex(proc_value, LLVMAttributeIndex_FunctionIndex, lb_create_enum_attribute(m->ctx, name, value));
|
||||
}
|
||||
|
||||
|
||||
void lb_ensure_abi_function_type(lbModule *m, lbProcedure *p) {
|
||||
@@ -2520,7 +2523,6 @@ lbProcedure *lb_create_procedure(lbModule *m, Entity *entity) {
|
||||
p->type = entity->type;
|
||||
p->type_expr = decl->type_expr;
|
||||
p->body = pl->body;
|
||||
p->tags = pt->Proc.tags;
|
||||
p->inlining = ProcInlining_none;
|
||||
p->is_foreign = entity->Procedure.is_foreign;
|
||||
p->is_export = entity->Procedure.is_export;
|
||||
@@ -2556,6 +2558,23 @@ lbProcedure *lb_create_procedure(lbModule *m, Entity *entity) {
|
||||
LLVMSetFunctionCallConv(p->value, cc_kind);
|
||||
}
|
||||
|
||||
if (entity->flags & EntityFlag_Cold) {
|
||||
lb_add_attribute_to_proc(m, p->value, "cold");
|
||||
}
|
||||
|
||||
if (pt->Proc.diverging) {
|
||||
lb_add_attribute_to_proc(m, p->value, "noreturn");
|
||||
}
|
||||
|
||||
switch (p->inlining) {
|
||||
case ProcInlining_inline:
|
||||
lb_add_attribute_to_proc(m, p->value, "alwaysinline");
|
||||
break;
|
||||
case ProcInlining_no_inline:
|
||||
lb_add_attribute_to_proc(m, p->value, "noinline");
|
||||
break;
|
||||
}
|
||||
|
||||
// lbCallingConventionKind cc_kind = lbCallingConvention_C;
|
||||
// // TODO(bill): Clean up this logic
|
||||
// if (build_context.metrics.os != TargetOs_js) {
|
||||
@@ -8073,7 +8092,18 @@ lbValue lb_emit_call_internal(lbProcedure *p, lbValue value, lbValue return_ptr,
|
||||
}
|
||||
|
||||
LLVMValueRef ret = LLVMBuildCall2(p->builder, fnp, fn, args, arg_count, "");
|
||||
// LLVMValueRef ret = LLVMBuildCall(p->builder, fn, args, arg_count, "");
|
||||
|
||||
switch (inlining) {
|
||||
case ProcInlining_none:
|
||||
break;
|
||||
case ProcInlining_inline:
|
||||
LLVMAddCallSiteAttribute(ret, LLVMAttributeIndex_FunctionIndex, lb_create_enum_attribute(p->module->ctx, "alwaysinline"));
|
||||
break;
|
||||
case ProcInlining_no_inline:
|
||||
LLVMAddCallSiteAttribute(ret, LLVMAttributeIndex_FunctionIndex, lb_create_enum_attribute(p->module->ctx, "noinline"));
|
||||
break;
|
||||
}
|
||||
|
||||
lbValue res = {};
|
||||
res.value = ret;
|
||||
res.type = abi_rt;
|
||||
@@ -8988,6 +9018,16 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv,
|
||||
);
|
||||
GB_ASSERT(the_asm != nullptr);
|
||||
LLVMBuildCall2(p->builder, func_type, the_asm, nullptr, 0, "");
|
||||
} else if (build_context.metrics.arch == TargetArch_arm64) {
|
||||
LLVMTypeRef func_type = LLVMFunctionType(LLVMVoidTypeInContext(p->module->ctx), nullptr, 0, false);
|
||||
LLVMValueRef the_asm = LLVMGetInlineAsm(func_type,
|
||||
cast(char *)"yield", 5,
|
||||
cast(char *)"", 0,
|
||||
/*HasSideEffects*/true, /*IsAlignStack*/false,
|
||||
LLVMInlineAsmDialectATT
|
||||
);
|
||||
GB_ASSERT(the_asm != nullptr);
|
||||
LLVMBuildCall2(p->builder, func_type, the_asm, nullptr, 0, "");
|
||||
}
|
||||
return {};
|
||||
|
||||
|
||||
@@ -499,3 +499,10 @@ enum {
|
||||
DW_TAG_subroutine_type = 21,
|
||||
DW_TAG_inheritance = 28,
|
||||
};
|
||||
|
||||
|
||||
enum : LLVMAttributeIndex {
|
||||
LLVMAttributeIndex_ReturnIndex = 0u,
|
||||
LLVMAttributeIndex_FunctionIndex = ~0u,
|
||||
LLVMAttributeIndex_FirstArgIndex = 1,
|
||||
};
|
||||
|
||||
@@ -107,12 +107,11 @@ void lb_populate_module_pass_manager(LLVMTargetMachineRef target_machine, LLVMPa
|
||||
|
||||
if (optimization_level >= 2) {
|
||||
// NOTE(bill, 2021-03-29: use this causes invalid code generation)
|
||||
LLVMPassManagerBuilderRef pmb = LLVMPassManagerBuilderCreate();
|
||||
LLVMPassManagerBuilderSetOptLevel(pmb, optimization_level);
|
||||
LLVMPassManagerBuilderPopulateModulePassManager(pmb, mpm);
|
||||
LLVMPassManagerBuilderPopulateLTOPassManager(pmb, mpm, false, true);
|
||||
// LLVMPassManagerBuilderSetSizeLevel(pmb, optimization_level);
|
||||
return;
|
||||
// LLVMPassManagerBuilderRef pmb = LLVMPassManagerBuilderCreate();
|
||||
// LLVMPassManagerBuilderSetOptLevel(pmb, optimization_level);
|
||||
// LLVMPassManagerBuilderPopulateModulePassManager(pmb, mpm);
|
||||
// LLVMPassManagerBuilderPopulateLTOPassManager(pmb, mpm, false, true);
|
||||
// return;
|
||||
}
|
||||
|
||||
LLVMAddIPSCCPPass(mpm);
|
||||
|
||||
+18
-13
@@ -273,7 +273,7 @@ i32 linker_stage(lbGenerator *gen) {
|
||||
LIT(output_base),
|
||||
LIT(build_context.resource_filepath)
|
||||
);
|
||||
|
||||
|
||||
if(result == 0) {
|
||||
result = system_exec_command_line_app("msvc-link",
|
||||
"\"%.*slink.exe\" %s \"%.*s.res\" -OUT:\"%.*s.%s\" %s "
|
||||
@@ -425,7 +425,7 @@ i32 linker_stage(lbGenerator *gen) {
|
||||
output_ext = substring(build_context.out_filepath, pos, build_context.out_filepath.len);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
result = system_exec_command_line_app("ld-link",
|
||||
"%s %s -o \"%.*s%.*s\" %s "
|
||||
" %s "
|
||||
@@ -467,7 +467,7 @@ i32 linker_stage(lbGenerator *gen) {
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
@@ -607,6 +607,7 @@ enum BuildFlagKind {
|
||||
|
||||
BuildFlag_Short,
|
||||
BuildFlag_AllPackages,
|
||||
BuildFlag_DocFormat,
|
||||
|
||||
BuildFlag_IgnoreWarnings,
|
||||
BuildFlag_WarningsAsErrors,
|
||||
@@ -696,7 +697,7 @@ bool parse_build_flags(Array<String> args) {
|
||||
add_flag(&build_flags, BuildFlag_Collection, str_lit("collection"), BuildFlagParam_String, Command__does_check);
|
||||
add_flag(&build_flags, BuildFlag_Define, str_lit("define"), BuildFlagParam_String, Command__does_check, true);
|
||||
add_flag(&build_flags, BuildFlag_BuildMode, str_lit("build-mode"), BuildFlagParam_String, Command__does_build); // Commands_build is not used to allow for a better error message
|
||||
add_flag(&build_flags, BuildFlag_Target, str_lit("target"), BuildFlagParam_String, Command__does_build);
|
||||
add_flag(&build_flags, BuildFlag_Target, str_lit("target"), BuildFlagParam_String, Command__does_check);
|
||||
add_flag(&build_flags, BuildFlag_Debug, str_lit("debug"), BuildFlagParam_None, Command__does_check);
|
||||
add_flag(&build_flags, BuildFlag_DisableAssert, str_lit("disable-assert"), BuildFlagParam_None, Command__does_check);
|
||||
add_flag(&build_flags, BuildFlag_NoBoundsCheck, str_lit("no-bounds-check"), BuildFlagParam_None, Command__does_check);
|
||||
@@ -721,6 +722,7 @@ bool parse_build_flags(Array<String> args) {
|
||||
|
||||
add_flag(&build_flags, BuildFlag_Short, str_lit("short"), BuildFlagParam_None, Command_doc);
|
||||
add_flag(&build_flags, BuildFlag_AllPackages, str_lit("all-packages"), BuildFlagParam_None, Command_doc);
|
||||
add_flag(&build_flags, BuildFlag_DocFormat, str_lit("doc-format"), BuildFlagParam_None, Command_doc);
|
||||
|
||||
add_flag(&build_flags, BuildFlag_IgnoreWarnings, str_lit("ignore-warnings"), BuildFlagParam_None, Command_all);
|
||||
add_flag(&build_flags, BuildFlag_WarningsAsErrors, str_lit("warnings-as-errors"), BuildFlagParam_None, Command_all);
|
||||
@@ -1227,6 +1229,9 @@ bool parse_build_flags(Array<String> args) {
|
||||
case BuildFlag_AllPackages:
|
||||
build_context.cmd_doc_flags |= CmdDocFlag_AllPackages;
|
||||
break;
|
||||
case BuildFlag_DocFormat:
|
||||
build_context.cmd_doc_flags |= CmdDocFlag_DocFormat;
|
||||
break;
|
||||
case BuildFlag_IgnoreWarnings:
|
||||
if (build_context.warnings_as_errors) {
|
||||
gb_printf_err("-ignore-warnings cannot be used with -warnings-as-errors\n");
|
||||
@@ -2349,11 +2354,11 @@ int main(int arg_count, char const **arg_ptr) {
|
||||
LIT(output_base),
|
||||
LIT(build_context.resource_filepath)
|
||||
);
|
||||
|
||||
|
||||
if(result != 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
result = system_exec_command_line_app("msvc-link",
|
||||
"\"%.*slink.exe\" \"%.*s.obj\" \"%.*s.res\" -OUT:\"%.*s.%s\" %s "
|
||||
"/nologo /incremental:no /opt:ref /subsystem:%s "
|
||||
@@ -2368,11 +2373,11 @@ int main(int arg_count, char const **arg_ptr) {
|
||||
LIT(build_context.extra_linker_flags),
|
||||
lib_str
|
||||
);
|
||||
|
||||
|
||||
if(result != 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
i32 result = system_exec_command_line_app("msvc-link",
|
||||
"\"%.*slink.exe\" \"%.*s.obj\" -OUT:\"%.*s.%s\" %s "
|
||||
@@ -2388,7 +2393,7 @@ int main(int arg_count, char const **arg_ptr) {
|
||||
LIT(build_context.extra_linker_flags),
|
||||
lib_str
|
||||
);
|
||||
|
||||
|
||||
if(result != 0) {
|
||||
return 1;
|
||||
}
|
||||
@@ -2409,7 +2414,7 @@ int main(int arg_count, char const **arg_ptr) {
|
||||
LIT(build_context.extra_linker_flags),
|
||||
lib_str
|
||||
);
|
||||
|
||||
|
||||
if(result != 0) {
|
||||
return 1;
|
||||
}
|
||||
@@ -2547,11 +2552,11 @@ int main(int arg_count, char const **arg_ptr) {
|
||||
LIT(build_context.link_flags),
|
||||
LIT(build_context.extra_linker_flags),
|
||||
link_settings);
|
||||
|
||||
|
||||
if(result != 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
#if defined(GB_SYSTEM_OSX)
|
||||
if (build_context.ODIN_DEBUG) {
|
||||
// NOTE: macOS links DWARF symbols dynamically. Dsymutil will map the stubs in the exe
|
||||
@@ -2578,6 +2583,6 @@ int main(int arg_count, char const **arg_ptr) {
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
+1
-1
@@ -478,7 +478,7 @@ void syntax_warning_va(Token token, char const *fmt, va_list va) {
|
||||
// NOTE(bill): Duplicate error, skip it
|
||||
if (global_error_collector.prev != token.pos) {
|
||||
global_error_collector.prev = token.pos;
|
||||
error_out("%S Syntax Warning: %s\n",
|
||||
error_out("%s Syntax Warning: %s\n",
|
||||
token_pos_to_string(token.pos),
|
||||
gb_bprintf_va(fmt, va));
|
||||
} else if (token.pos.line == 0) {
|
||||
|
||||
@@ -195,7 +195,6 @@ struct TypeProc {
|
||||
Type * results; // Type_Tuple
|
||||
i32 param_count;
|
||||
i32 result_count;
|
||||
u64 tags;
|
||||
isize specialization_count;
|
||||
ProcCallingConvention calling_convention;
|
||||
i32 variadic_index;
|
||||
|
||||
Reference in New Issue
Block a user