Merge remote-tracking branch 'upstream/master' into prototype-fmt

This commit is contained in:
Daniel Gavin
2021-04-19 02:13:29 +02:00
64 changed files with 6548 additions and 259 deletions
+42
View File
@@ -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
View File
@@ -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) {
+4 -2
View File
@@ -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 {
+261
View File
@@ -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;
}
+11
View File
@@ -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
+43
View File
@@ -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);
}
+80
View File
@@ -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[:];
}
+126
View File
@@ -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";
}
+14
View File
@@ -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 "";
}
+158
View File
@@ -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);
}
+98
View File
@@ -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);
},
};
+122
View File
@@ -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;
}
+136
View File
@@ -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)) {
}
+21
View File
@@ -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;
+107
View File
@@ -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;
}
+29
View File
@@ -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);
}
+31
View File
@@ -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;
}
+5
View File
@@ -0,0 +1,5 @@
package os2
pipe :: proc() -> (r, w: Handle, err: Error) {
return _pipe();
}
+13
View File
@@ -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;
}
+101
View File
@@ -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;
}
+42
View File
@@ -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);
}
+373
View File
@@ -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]);
}
+14
View File
@@ -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);
}
+29
View File
@@ -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);
}
}
+68
View File
@@ -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
View File
@@ -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;
}
+38 -6
View File
@@ -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 ---
}
}
+7 -7
View File
@@ -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;
}
+84 -84
View File
@@ -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;
}
+4 -4
View File
@@ -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);
+79
View File
@@ -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;
+886
View File
@@ -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;
}
+17
View File
@@ -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
}
+34
View File
@@ -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);
}
}
+238
View File
@@ -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);
}
}
+185
View File
@@ -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);
}
+244
View File
@@ -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
+154
View File
@@ -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
+73
View File
@@ -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);
}
+54
View File
@@ -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 ---;
}
+37
View File
@@ -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
View File
@@ -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,
}
+28
View File
@@ -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 ---
}
+374
View File
@@ -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
View File
@@ -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);
}
}
+28 -27
View File
@@ -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, &params);
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();
}
+16 -21
View File
@@ -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();
}
+2
View File
@@ -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,
};
+4
View File
@@ -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
View File
@@ -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
View File
@@ -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];
+12
View File
@@ -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;
}
+1
View File
@@ -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
View File
@@ -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]);
}
}
}
+216
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+9 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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 {};
+7
View File
@@ -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,
};
+5 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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) {
-1
View File
@@ -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;