Przeglądaj źródła

Merge branch 'master' into windows-llvm-13.0.0

gingerBill 2 lat temu
rodzic
commit
a164438b5b
100 zmienionych plików z 8073 dodań i 982 usunięć
  1. 1 1
      core/builtin/builtin.odin
  2. 2 0
      core/compress/common.odin
  3. 1 1
      core/crypto/rand_linux.odin
  4. 7 0
      core/dynlib/doc.odin
  5. 81 2
      core/dynlib/lib.odin
  6. 3 0
      core/dynlib/lib_windows.odin
  7. 1 1
      core/encoding/json/marshal.odin
  8. 2 0
      core/encoding/json/unmarshal.odin
  9. 3 1
      core/encoding/xml/xml_reader.odin
  10. 2 2
      core/fmt/fmt.odin
  11. 3 0
      core/image/netpbm/netpbm.odin
  12. 17 14
      core/image/png/helpers.odin
  13. 11 7
      core/image/png/png.odin
  14. 2 2
      core/intrinsics/intrinsics.odin
  15. 61 14
      core/mem/virtual/arena.odin
  16. 744 0
      core/net/addr.odin
  17. 415 0
      core/net/common.odin
  18. 863 0
      core/net/dns.odin
  19. 83 0
      core/net/dns_unix.odin
  20. 159 0
      core/net/dns_windows.odin
  21. 46 0
      core/net/doc.odin
  22. 200 0
      core/net/errors_darwin.odin
  23. 193 0
      core/net/errors_linux.odin
  24. 261 0
      core/net/errors_windows.odin
  25. 79 0
      core/net/interface.odin
  26. 32 0
      core/net/interface_darwin.odin
  27. 140 0
      core/net/interface_linux.odin
  28. 177 0
      core/net/interface_windows.odin
  29. 176 0
      core/net/socket.odin
  30. 348 0
      core/net/socket_darwin.odin
  31. 384 0
      core/net/socket_linux.odin
  32. 355 0
      core/net/socket_windows.odin
  33. 235 0
      core/net/url.odin
  34. 1 1
      core/os/dir_freebsd.odin
  35. 2 0
      core/os/dir_linux.odin
  36. 6 2
      core/os/dir_windows.odin
  37. 4 0
      core/os/env_windows.odin
  38. 12 0
      core/os/file_windows.odin
  39. 5 3
      core/os/os2/env.odin
  40. 4 2
      core/os/os2/env_linux.odin
  41. 13 1
      core/os/os2/env_windows.odin
  42. 7 14
      core/os/os2/file_linux.odin
  43. 2 1
      core/os/os2/file_util.odin
  44. 1 1
      core/os/os2/path_linux.odin
  45. 3 3
      core/os/os2/stat_linux.odin
  46. 294 32
      core/os/os_darwin.odin
  47. 19 1
      core/os/os_freebsd.odin
  48. 347 165
      core/os/os_linux.odin
  49. 14 1
      core/os/os_openbsd.odin
  50. 1 0
      core/os/os_windows.odin
  51. 7 3
      core/os/stat_windows.odin
  52. 2 1
      core/path/filepath/match.odin
  53. 44 5
      core/path/filepath/path.odin
  54. 2 0
      core/path/filepath/path_unix.odin
  55. 15 12
      core/path/filepath/path_windows.odin
  56. 3 1
      core/path/slashpath/path.odin
  57. 208 0
      core/prof/spall/spall.odin
  58. 4 3
      core/reflect/reflect.odin
  59. 11 5
      core/reflect/types.odin
  60. 8 9
      core/runtime/core.odin
  61. 5 8
      core/runtime/core_builtin.odin
  62. 304 0
      core/runtime/default_allocators_arena.odin
  63. 40 161
      core/runtime/default_temporary_allocator.odin
  64. 11 10
      core/runtime/internal.odin
  65. 22 21
      core/runtime/print.odin
  66. 0 36
      core/sort/map.odin
  67. 3 0
      core/strings/strings.odin
  68. 1 1
      core/sync/primitives.odin
  69. 8 0
      core/sys/darwin/xnu_system_call_helpers.odin
  70. 1 1
      core/sys/darwin/xnu_system_call_numbers.odin
  71. 48 48
      core/sys/darwin/xnu_system_call_wrappers.odin
  72. 5 0
      core/sys/info/platform_darwin.odin
  73. 3 0
      core/sys/info/platform_freebsd.odin
  74. 3 0
      core/sys/info/platform_linux.odin
  75. 4 1
      core/sys/info/platform_openbsd.odin
  76. 3 0
      core/sys/info/platform_windows.odin
  77. 218 11
      core/sys/unix/syscalls_linux.odin
  78. 10 0
      core/sys/windows/dnsapi.odin
  79. 234 0
      core/sys/windows/ip_helper.odin
  80. 29 0
      core/sys/windows/kernel32.odin
  81. 274 139
      core/sys/windows/types.odin
  82. 21 0
      core/sys/windows/util.odin
  83. 7 7
      core/sys/windows/ws2_32.odin
  84. 55 2
      core/time/perf.odin
  85. 21 0
      core/time/tsc_darwin.odin
  86. 21 0
      core/time/tsc_freebsd.odin
  87. 35 0
      core/time/tsc_linux.odin
  88. 3 1
      examples/all/all_main.odin
  89. 10 3
      src/build_settings.cpp
  90. 1 1
      src/check_builtin.cpp
  91. 7 2
      src/check_decl.cpp
  92. 277 135
      src/check_expr.cpp
  93. 7 8
      src/check_stmt.cpp
  94. 10 4
      src/check_type.cpp
  95. 61 14
      src/checker.cpp
  96. 4 2
      src/checker.hpp
  97. 2 2
      src/checker_builtin_procs.hpp
  98. 9 4
      src/common.cpp
  99. 1 0
      src/entity.cpp
  100. 169 49
      src/error.cpp

+ 1 - 1
core/builtin/builtin.odin

@@ -109,7 +109,7 @@ jmag       :: proc(value: Quaternion) -> Float ---
 kmag       :: proc(value: Quaternion) -> Float ---
 conj       :: proc(value: Complex_Or_Quaternion) -> Complex_Or_Quaternion ---
 
-expand_to_tuple :: proc(value: Struct_Or_Array) -> (A, B, C, ...) ---
+expand_values :: proc(value: Struct_Or_Array) -> (A, B, C, ...) ---
 
 min   :: proc(values: ..T) -> T ---
 max   :: proc(values: ..T) -> T ---

+ 2 - 0
core/compress/common.odin

@@ -212,6 +212,8 @@ read_slice_from_memory :: #force_inline proc(z: ^Context_Memory_Input, size: int
 
 @(optimization_mode="speed")
 read_slice_from_stream :: #force_inline proc(z: ^Context_Stream_Input, size: int) -> (res: []u8, err: io.Error) {
+	// TODO: REMOVE ALL USE OF context.temp_allocator here
+	// the is literally no need for it
 	b := make([]u8, size, context.temp_allocator)
 	_, e := z.input->impl_read(b[:])
 	if e == .None {

+ 1 - 1
core/crypto/rand_linux.odin

@@ -12,7 +12,7 @@ _rand_bytes :: proc (dst: []byte) {
 
 	for l > 0 {
 		to_read := min(l, _MAX_PER_CALL_BYTES)
-		ret := unix.sys_getrandom(raw_data(dst), to_read, 0)
+		ret := unix.sys_getrandom(raw_data(dst), uint(to_read), 0)
 		if ret < 0 {
 			switch os.Errno(-ret) {
 			case os.EINTR:

+ 7 - 0
core/dynlib/doc.odin

@@ -0,0 +1,7 @@
+/*
+Package core:dynlib implements loading of shared libraries/DLLs and their symbols.
+
+The behaviour of dynamically loaded libraries is specific to the target platform of the program.
+For in depth detail on the underlying behaviour please refer to your target platform's documentation.
+*/
+package dynlib

+ 81 - 2
core/dynlib/lib.odin

@@ -1,15 +1,94 @@
 package dynlib
 
+/*
+A handle to a dynamically loaded library.
+*/
 Library :: distinct rawptr
 
-load_library :: proc(path: string, global_symbols := false) -> (Library, bool) {
+/*
+Loads a dynamic library from the filesystem. The paramater `global_symbols` makes the symbols in the loaded
+library available to resolve references in subsequently loaded libraries.
+
+The paramater `global_symbols` is only used for the platforms `linux`, `darwin`, `freebsd` and `openbsd`.
+On `windows` this paramater is ignored.
+
+The underlying behaviour is platform specific.  
+On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlopen`.  
+On `windows` refer to `LoadLibraryW`.
+
+**Implicit Allocators**  
+`context.temp_allocator`
+
+Example:
+	import "core:dynlib"
+	import "core:fmt"
+
+	load_my_library :: proc() {
+		LIBRARY_PATH :: "my_library.dll"
+		library, ok := dynlib.load_library(LIBRARY_PATH)
+		if ! ok {
+			return
+		}
+		fmt.println("The library %q was successfully loaded", LIBRARY_PATH)
+	}
+*/
+load_library :: proc(path: string, global_symbols := false) -> (library: Library, did_load: bool) {
 	return _load_library(path, global_symbols)
 }
 
-unload_library :: proc(library: Library) -> bool {
+/*
+Unloads a dynamic library.
+
+The underlying behaviour is platform specific.  
+On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlclose`.  
+On `windows` refer to `FreeLibrary`.
+
+Example:
+	import "core:dynlib"
+	import "core:fmt"
+
+	load_then_unload_my_library :: proc() {
+		LIBRARY_PATH :: "my_library.dll"
+		library, ok := dynlib.load_library(LIBRARY_PATH)
+		if ! ok {
+			return
+		}
+		did_unload := dynlib.unload_library(library)
+		if ! did_unload {
+			return
+		}
+		fmt.println("The library %q was successfully unloaded", LIBRARY_PATH)
+	}
+*/
+unload_library :: proc(library: Library) -> (did_unload: bool) {
 	return _unload_library(library)
 }
 
+/*
+Loads the address of a procedure/variable from a dynamic library.
+
+The underlying behaviour is platform specific.  
+On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlsym`.  
+On `windows` refer to `GetProcAddress`.
+
+**Implicit Allocators**  
+`context.temp_allocator`
+
+Example:
+	import "core:dynlib"
+	import "core:fmt"
+
+	find_a_in_my_library :: proc() {
+		LIBRARY_PATH :: "my_library.dll"
+		library, ok := dynlib.load_library(LIBRARY_PATH)
+		if ! ok {
+			return
+		}
+
+		a, found_a := dynlib.symbol_address(library, "a")
+		if found_a do fmt.printf("The symbol %q was found at the address %v", "a", a)
+	}
+*/
 symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) #optional_ok {
 	return _symbol_address(library, symbol)
 }

+ 3 - 0
core/dynlib/lib_windows.odin

@@ -4,10 +4,12 @@ package dynlib
 
 import win32 "core:sys/windows"
 import "core:strings"
+import "core:runtime"
 
 _load_library :: proc(path: string, global_symbols := false) -> (Library, bool) {
 	// NOTE(bill): 'global_symbols' is here only for consistency with POSIX which has RTLD_GLOBAL
 
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	wide_path := win32.utf8_to_wstring(path, context.temp_allocator)
 	handle := cast(Library)win32.LoadLibraryW(wide_path)
 	return handle, handle != nil
@@ -19,6 +21,7 @@ _unload_library :: proc(library: Library) -> bool {
 }
 
 _symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	c_str := strings.clone_to_cstring(symbol, context.temp_allocator)
 	ptr = win32.GetProcAddress(cast(win32.HMODULE)library, c_str)
 	found = ptr != nil

+ 1 - 1
core/encoding/json/marshal.odin

@@ -198,7 +198,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
 	case runtime.Type_Info_Procedure:
 		return .Unsupported_Type
 
-	case runtime.Type_Info_Tuple:
+	case runtime.Type_Info_Parameters:
 		return .Unsupported_Type
 
 	case runtime.Type_Info_Simd_Vector:

+ 2 - 0
core/encoding/json/unmarshal.odin

@@ -346,6 +346,8 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
 			
 			fields := reflect.struct_fields_zipped(ti.id)
 			
+			runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
+
 			field_used := make([]bool, len(fields), context.temp_allocator)
 			
 			use_field_idx := -1

+ 3 - 1
core/encoding/xml/xml_reader.odin

@@ -33,6 +33,7 @@ import "core:intrinsics"
 import "core:mem"
 import "core:os"
 import "core:strings"
+import "core:runtime"
 
 likely :: intrinsics.expect
 
@@ -408,7 +409,7 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
 				next := scan(t)
 				#partial switch next.kind {
 				case .Ident:
-					if len(next.text) == 3 && strings.to_lower(next.text, context.temp_allocator) == "xml" {
+					if len(next.text) == 3 && strings.equal_fold(next.text, "xml") {
 						parse_prologue(doc) or_return
 					} else if len(doc.prologue) > 0 {
 						/*
@@ -614,6 +615,7 @@ parse_prologue :: proc(doc: ^Document) -> (err: Error) {
 			}
 
 		case "encoding":
+			runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 			switch strings.to_lower(attr.val, context.temp_allocator) {
 			case "utf-8", "utf8":
 				doc.encoding = .UTF_8

+ 2 - 2
core/fmt/fmt.odin

@@ -1783,8 +1783,8 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
 
 	type_info := type_info_of(v.id)
 	switch info in type_info.variant {
-	case runtime.Type_Info_Any:   // Ignore
-	case runtime.Type_Info_Tuple: // Ignore
+	case runtime.Type_Info_Any:        // Ignore
+	case runtime.Type_Info_Parameters: // Ignore
 
 	case runtime.Type_Info_Named:
 		fmt_named(fi, v, verb, info)

+ 3 - 0
core/image/netpbm/netpbm.odin

@@ -8,6 +8,7 @@ import "core:os"
 import "core:strconv"
 import "core:strings"
 import "core:unicode"
+import "core:runtime"
 
 Image        :: image.Image
 Format       :: image.Netpbm_Format
@@ -407,6 +408,8 @@ _parse_header_pam :: proc(data: []byte, allocator := context.allocator) -> (head
 	}
 	length = header_end_index + len(HEADER_END)
 
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
+
 	// string buffer for the tupltype
 	tupltype: strings.Builder
 	strings.builder_init(&tupltype, context.temp_allocator); defer strings.builder_destroy(&tupltype)

+ 17 - 14
core/image/png/helpers.odin

@@ -16,6 +16,7 @@ import coretime "core:time"
 import "core:strings"
 import "core:bytes"
 import "core:mem"
+import "core:runtime"
 
 /*
 	Cleanup of image-specific data.
@@ -91,6 +92,8 @@ core_time :: proc(c: image.PNG_Chunk) -> (t: coretime.Time, ok: bool) {
 }
 
 text :: proc(c: image.PNG_Chunk) -> (res: Text, ok: bool) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
+
 	assert(len(c.data) == int(c.header.length))
 	#partial switch c.header.type {
 	case .tEXt:
@@ -194,18 +197,18 @@ text_destroy :: proc(text: Text) {
 }
 
 iccp :: proc(c: image.PNG_Chunk) -> (res: iCCP, ok: bool) {
-	ok = true
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
 
 	fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=3, allocator=context.temp_allocator)
 
 	if len(fields[0]) < 1 || len(fields[0]) > 79 {
 		// Invalid profile name
-		ok = false; return
+		return
 	}
 
 	if len(fields[1]) != 0 {
 		// Compression method should be a zero, which the split turned into an empty slice.
-		ok = false; return
+		return
 	}
 
 	// Set up ZLIB context and decompress iCCP payload
@@ -213,12 +216,12 @@ iccp :: proc(c: image.PNG_Chunk) -> (res: iCCP, ok: bool) {
 	zlib_error := zlib.inflate_from_byte_array(fields[2], &buf)
 	if zlib_error != nil {
 		bytes.buffer_destroy(&buf)
-		ok = false; return
+		return
 	}
 
 	res.name = strings.clone(string(fields[0]))
 	res.profile = bytes.buffer_to_bytes(&buf)
-
+	ok = true
 	return
 }
 
@@ -256,18 +259,18 @@ plte :: proc(c: image.PNG_Chunk) -> (res: PLTE, ok: bool) {
 
 splt :: proc(c: image.PNG_Chunk) -> (res: sPLT, ok: bool) {
 	if c.header.type != .sPLT {
-		return {}, false
+		return
 	}
-	ok = true
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
 
 	fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=2, allocator=context.temp_allocator)
 	if len(fields) != 2 {
-		return {}, false
+		return
 	}
 
 	res.depth = fields[1][0]
 	if res.depth != 8 && res.depth != 16 {
-		return {}, false
+		return
 	}
 
 	data := fields[1][1:]
@@ -275,21 +278,21 @@ splt :: proc(c: image.PNG_Chunk) -> (res: sPLT, ok: bool) {
 
 	if res.depth == 8 {
 		if len(data) % 6 != 0 {
-			return {}, false
+			return
 		}
 		count = len(data) / 6
 		if count > 256 {
-			return {}, false
+			return
 		}
 
 		res.entries = mem.slice_data_cast([][4]u8, data)
 	} else { // res.depth == 16
 		if len(data) % 10 != 0 {
-			return {}, false
+			return
 		}
 		count = len(data) / 10
 		if count > 256 {
-			return {}, false
+			return
 		}
 
 		res.entries = mem.slice_data_cast([][4]u16, data)
@@ -297,7 +300,7 @@ splt :: proc(c: image.PNG_Chunk) -> (res: sPLT, ok: bool) {
 
 	res.name = strings.clone(string(fields[0]))
 	res.used = u16(count)
-
+	ok = true
 	return
 }
 

+ 11 - 7
core/image/png/png.odin

@@ -23,6 +23,7 @@ import "core:bytes"
 import "core:io"
 import "core:mem"
 import "core:intrinsics"
+import "core:runtime"
 
 // Limit chunk sizes.
 // By default: IDAT = 8k x 8k x 16-bits + 8k filter bytes.
@@ -1247,6 +1248,8 @@ defilter_8 :: proc(params: ^Filter_Params) -> (ok: bool) {
 
 	// TODO: See about doing a Duff's #unroll where practicable
 
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+
 	// Apron so we don't need to special case first rows.
 	up := make([]u8, row_stride, context.temp_allocator)
 	ok = true
@@ -1299,10 +1302,9 @@ defilter_8 :: proc(params: ^Filter_Params) -> (ok: bool) {
 }
 
 // @(optimization_mode="speed")
-defilter_less_than_8 :: proc(params: ^Filter_Params) -> (ok: bool) #no_bounds_check {
+defilter_less_than_8 :: proc(params: ^Filter_Params) -> bool #no_bounds_check {
 
 	using params
-	ok = true
 
 	row_stride_in  := ((channels * width * depth) + 7) >> 3
 	row_stride_out := channels * width
@@ -1314,6 +1316,8 @@ defilter_less_than_8 :: proc(params: ^Filter_Params) -> (ok: bool) #no_bounds_ch
 
 	// TODO: See about doing a Duff's #unroll where practicable
 
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+
 	// Apron so we don't need to special case first rows.
 	up := make([]u8, row_stride_out, context.temp_allocator)
 
@@ -1457,18 +1461,18 @@ defilter_less_than_8 :: proc(params: ^Filter_Params) -> (ok: bool) #no_bounds_ch
 		}
 	}
 
-	return
+	return true
 }
 
 // @(optimization_mode="speed")
-defilter_16 :: proc(params: ^Filter_Params) -> (ok: bool) {
-
+defilter_16 :: proc(params: ^Filter_Params) -> bool {
 	using params
-	ok = true
 
 	stride := channels * 2
 	row_stride := width * stride
 
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+
 	// TODO: See about doing a Duff's #unroll where practicable
 	// Apron so we don't need to special case first rows.
 	up := make([]u8, row_stride, context.temp_allocator)
@@ -1518,7 +1522,7 @@ defilter_16 :: proc(params: ^Filter_Params) -> (ok: bool) {
 		dest    = dest[row_stride:]
 	}
 
-	return
+	return true
 }
 
 defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IHDR, options: Options) -> (err: Error) {

+ 2 - 2
core/intrinsics/intrinsics.odin

@@ -283,7 +283,7 @@ wasm_memory_atomic_wait32   :: proc(ptr: ^u32, expected: u32, timeout_ns: i64) -
 wasm_memory_atomic_notify32 :: proc(ptr: ^u32, waiters: u32) -> (waiters_woken_up: u32) ---
 
 // x86 Targets (i386, amd64)
-x86_cpuid  :: proc(ax, cx: u32) -> (eax, ebc, ecx, edx: u32) ---
+x86_cpuid  :: proc(ax, cx: u32) -> (eax, ebx, ecx, edx: u32) ---
 x86_xgetbv :: proc(cx: u32) -> (eax, edx: u32) ---
 
 
@@ -305,4 +305,4 @@ valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2,
 
 // Internal compiler use only
 
-__entry_point :: proc() ---
+__entry_point :: proc() ---

+ 61 - 14
core/mem/virtual/arena.odin

@@ -9,6 +9,13 @@ Arena_Kind :: enum uint {
 	Buffer  = 2, // Uses a fixed sized buffer.
 }
 
+/*
+	Arena is a generalized arena allocator that supports 3 different variants.
+
+	Growing: A linked list of `Memory_Block`s allocated with virtual memory.
+	Static: A single `Memory_Block` allocated with virtual memory.
+	Buffer: A single `Memory_Block` created from a user provided []byte.
+*/
 Arena :: struct {
 	kind:               Arena_Kind,
 	curr_block:         ^Memory_Block,
@@ -29,6 +36,8 @@ DEFAULT_ARENA_STATIC_RESERVE_SIZE :: mem.Gigabyte when size_of(uintptr) == 8 els
 
 
 
+// Initialization of an `Arena` to be a `.Growing` variant.
+// A growing arena is a linked list of `Memory_Block`s allocated with virtual memory.
 @(require_results)
 arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (err: Allocator_Error) {
 	arena.kind           = .Growing
@@ -39,6 +48,8 @@ arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING
 }
 
 
+// Initialization of an `Arena` to be a `.Static` variant.
+// A static arena contains a single `Memory_Block` allocated with virtual memory.
 @(require_results)
 arena_init_static :: proc(arena: ^Arena, reserved: uint, commit_size: uint = DEFAULT_ARENA_STATIC_COMMIT_SIZE) -> (err: Allocator_Error) {
 	arena.kind           = .Static
@@ -48,6 +59,8 @@ arena_init_static :: proc(arena: ^Arena, reserved: uint, commit_size: uint = DEF
 	return
 }
 
+// Initialization of an `Arena` to be a `.Buffer` variant.
+// A buffer arena contains single `Memory_Block` created from a user provided []byte.
 @(require_results)
 arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Error) {
 	if len(buffer) < size_of(Memory_Block) {
@@ -71,6 +84,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro
 	return
 }
 
+// Allocates memory from the provided arena.
 @(require_results)
 arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
 	assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc)
@@ -119,6 +133,7 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l
 	return
 }
 
+// Resets the memory of a Static or Buffer arena to a specific `pos`ition (offset) and zeroes the previously used memory.
 arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location) -> bool {
 	sync.mutex_guard(&arena.mutex)
 
@@ -140,50 +155,72 @@ arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location)
 	return false
 }
 
+// Frees the last memory block of a Growing Arena
 arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) {
-	sync.mutex_guard(&arena.mutex)
 	if free_block := arena.curr_block; free_block != nil {
 		assert(arena.kind == .Growing, "expected a .Growing arena", loc)
+		arena.total_used -= free_block.used
+		arena.total_reserved -= free_block.reserved
+
 		arena.curr_block = free_block.prev
 		memory_block_dealloc(free_block)
 	}
 }
 
-arena_free_all :: proc(arena: ^Arena) {
+// Deallocates all but the first memory block of the arena and resets the allocator's usage to 0.
+arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
 	switch arena.kind {
 	case .Growing:
 		sync.mutex_guard(&arena.mutex)
-		for arena.curr_block != nil {
-			arena_growing_free_last_memory_block(arena)
+		// NOTE(bill): Free all but the first memory block (if it exists)
+		for arena.curr_block != nil && arena.curr_block.prev != nil {
+			arena_growing_free_last_memory_block(arena, loc)
+		}
+		// Zero the first block's memory
+		if arena.curr_block != nil {
+			mem.zero(arena.curr_block.base, int(arena.curr_block.used))
+			arena.curr_block.used = 0
 		}
-		arena.total_reserved = 0
+		arena.total_used = 0
 	case .Static, .Buffer:
 		arena_static_reset_to(arena, 0)
 	}
 	arena.total_used = 0
 }
 
-arena_destroy :: proc(arena: ^Arena) {
-	arena_free_all(arena)
-	if arena.kind != .Buffer {
+// Frees all of the memory allocated by the arena and zeros all of the values of an arena.
+// A buffer based arena does not `delete` the provided `[]byte` bufffer.
+arena_destroy :: proc(arena: ^Arena, loc := #caller_location) {
+	sync.mutex_guard(&arena.mutex)
+	switch arena.kind {
+	case .Growing:
+		for arena.curr_block != nil {
+			arena_growing_free_last_memory_block(arena, loc)
+		}
+	case .Static:
 		memory_block_dealloc(arena.curr_block)
+	case .Buffer:
+		// nothing
 	}
-	arena.curr_block = nil
+	arena.curr_block     = nil
 	arena.total_used     = 0
 	arena.total_reserved = 0
 	arena.temp_count     = 0
 }
 
+// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
 arena_growing_bootstrap_new :: proc{
 	arena_growing_bootstrap_new_by_offset,
 	arena_growing_bootstrap_new_by_name,
 }
 
+// Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy.
 arena_static_bootstrap_new :: proc{
 	arena_static_bootstrap_new_by_offset,
 	arena_static_bootstrap_new_by_name,
 }
 
+// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
 @(require_results)
 arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
 	bootstrap: Arena
@@ -199,11 +236,13 @@ arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintp
 	return
 }
 
+// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
 @(require_results)
 arena_growing_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
 	return arena_growing_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), minimum_block_size)
 }
 
+// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
 @(require_results)
 arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
 	bootstrap: Arena
@@ -219,17 +258,20 @@ arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintpt
 	return
 }
 
+// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
 @(require_results)
 arena_static_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
 	return arena_static_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), reserved)
 }
 
 
+// Create an `Allocator` from the provided `Arena`
 @(require_results)
 arena_allocator :: proc(arena: ^Arena) -> mem.Allocator {
 	return mem.Allocator{arena_allocator_proc, arena}
 }
 
+// The allocator procedured by an `Allocator` produced by `arena_allocator`
 arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
                              size, alignment: int,
                              old_memory: rawptr, old_size: int,
@@ -241,17 +283,17 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
 
 	switch mode {
 	case .Alloc, .Alloc_Non_Zeroed:
-		return arena_alloc(arena, size, alignment)
+		return arena_alloc(arena, size, alignment, location)
 	case .Free:
 		err = .Mode_Not_Implemented
 	case .Free_All:
-		arena_free_all(arena)
+		arena_free_all(arena, location)
 	case .Resize:
 		old_data := ([^]byte)(old_memory)
 
 		switch {
 		case old_data == nil:
-			return arena_alloc(arena, size, alignment)
+			return arena_alloc(arena, size, alignment, location)
 		case size == old_size:
 			// return old memory
 			data = old_data[:size]
@@ -265,7 +307,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
 			return
 		}
 
-		new_memory := arena_alloc(arena, size, alignment) or_return
+		new_memory := arena_alloc(arena, size, alignment, location) or_return
 		if new_memory == nil {
 			return
 		}
@@ -286,12 +328,15 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
 
 
 
+// An `Arena_Temp` is a way to produce temporary watermarks to reset a arena to a previous state.
+// All uses of an `Arena_Temp` must be handled by ending them with `arena_temp_end` or ignoring them with `arena_temp_ignore`.
 Arena_Temp :: struct {
 	arena: ^Arena,
 	block: ^Memory_Block,
 	used:  uint,
 }
 
+// Begins the section of temporary arena memory.
 @(require_results)
 arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) {
 	assert(arena != nil, "nil arena", loc)
@@ -306,6 +351,7 @@ arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena
 	return
 }
 
+// Ends the section of temporary arena memory by resetting the memory to the stored position.
 arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
 	assert(temp.arena != nil, "nil arena", loc)
 	arena := temp.arena
@@ -339,7 +385,7 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
 	arena.temp_count -= 1
 }
 
-// Ignore the use of a `arena_temp_begin` entirely
+// Ignore the use of a `arena_temp_begin` entirely by __not__ resetting to the stored position.
 arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
 	assert(temp.arena != nil, "nil arena", loc)
 	arena := temp.arena
@@ -349,6 +395,7 @@ arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
 	arena.temp_count -= 1
 }
 
+// Asserts that all uses of `Arena_Temp` has been used by an `Arena`
 arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) {
 	assert(arena.temp_count == 0, "Arena_Temp not been ended", loc)
 }

+ 744 - 0
core/net/addr.odin

@@ -0,0 +1,744 @@
+// +build windows, linux, darwin
+package net
+
+/*
+	Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
+	For other protocols and their features, see subdirectories of this package.
+*/
+
+/*
+	Copyright 2022 Tetralux        <[email protected]>
+	Copyright 2022 Colin Davidson  <[email protected]>
+	Copyright 2022 Jeroen van Rijn <[email protected]>.
+	Made available under Odin's BSD-3 license.
+
+	List of contributors:
+		Tetralux:        Initial implementation
+		Colin Davidson:  Linux platform code, OSX platform code, Odin-native DNS resolver
+		Jeroen van Rijn: Cross platform unification, code style, documentation
+*/
+
+import "core:strconv"
+import "core:strings"
+import "core:fmt"
+
+/*
+	Expects an IPv4 address with no leading or trailing whitespace:
+	- a.b.c.d
+	- a.b.c.d:port
+	- [a.b.c.d]:port
+
+	If the IP address is bracketed, the port must be present and valid (though it will be ignored):
+	- [a.b.c.d] will be treated as a parsing failure.
+
+	The port, if present, is required to be a base 10 number in the range 0-65535, inclusive.
+
+	If `allow_non_decimal` is false, `aton` is told each component must be decimal and max 255.
+*/
+parse_ip4_address :: proc(address_and_maybe_port: string, allow_non_decimal := false) -> (addr: IP4_Address, ok: bool) {
+	res := aton(address_and_maybe_port, .IP4, !allow_non_decimal) or_return
+	return res.?
+}
+
+/*
+	Parses an IP address in "non-decimal" `inet_aton` form.
+
+	e.g."00377.0x0ff.65534" = 255.255.255.254
+		00377 = 255 in octal
+		0x0ff = 255 in hexadecimal
+		This leaves 16 bits worth of address
+		.65534 then accounts for the last two digits
+
+	For the address part the allowed forms are:
+		a.b.c.d - where each part represents a byte
+		a.b.c   - where `a` & `b` represent a byte and `c` a u16
+		a.b     - where `a` represents a byte and `b` supplies the trailing 24 bits
+		a       - where `a` gives the entire 32-bit value
+
+	The port, if present, is required to be a base 10 number in the range 0-65535, inclusive.
+*/
+aton :: proc(address_and_maybe_port: string, family: Address_Family, allow_decimal_only := false) -> (addr: Address, ok: bool) {
+	switch family {
+	case .IP4:
+		// There is no valid address shorter than `0.0.0.0`.
+		if len(address_and_maybe_port) < 7 {
+			return {}, false
+		}
+
+		address, _ := split_port(address_and_maybe_port) or_return // This call doesn't allocate
+
+		buf: [4]u64 = {}
+		i := 0
+
+		max_value := u64(max(u32))
+		bases     := DEFAULT_DIGIT_BASES
+
+		if allow_decimal_only {
+			max_value = 255
+			bases     = {.Dec}
+		}
+
+		for len(address) > 0 {
+			if i == 4 {
+				return {}, false
+			}
+
+			// Decimal-only addresses may not have a leading zero.
+			if allow_decimal_only && len(address) > 1 && address[0] == '0' && address[1] != '.' {
+				return
+			}
+
+			number, consumed, number_ok := parse_ip_component(address, max_value, bases)
+			if !number_ok || consumed == 0 {
+				return {}, false
+			}
+
+			buf[i] = number
+
+			address = address[consumed:]
+
+			if len(address) > 0 && address[0] == '.' {
+				address = address[1:]
+			}
+			i += 1
+		}
+
+		// Distribute parts.
+		switch i {
+		case 1:
+			buf[1] = buf[0] & 0xffffff
+			buf[0] >>= 24
+			fallthrough
+		case 2:
+			buf[2] = buf[1] & 0xffff
+			buf[1] >>= 16
+			fallthrough
+		case 3:
+			buf[3] = buf[2] & 0xff
+			buf[2] >>= 8
+		}
+
+		a: [4]u8 = ---
+		for v, i in buf {
+			if v > 255 { return {}, false }
+			a[i] = u8(v)
+		}
+		return IP4_Address(a), true
+
+	case .IP6:
+		return parse_ip6_address(address_and_maybe_port)
+
+	case:
+		return nil, false
+	}
+}
+
+/*
+	The minimum length of a valid IPv6 address string is 2, e.g. `::`
+
+	The maximum length of a valid IPv6 address string is 45, when it embeds an IPv4,
+	e.g. `0000:0000:0000:0000:0000:ffff:255.255.255.255`
+
+	An IPv6 address must contain at least 3 pieces, e.g. `::`,
+	and at most 9 (using `::` for a trailing or leading 0)
+*/
+IPv6_MIN_STRING_LENGTH :: 2
+IPv6_MAX_STRING_LENGTH :: 45
+IPv6_MIN_COLONS        :: 2
+IPv6_PIECE_COUNT       :: 8
+
+parse_ip6_address :: proc(address_and_maybe_port: string) -> (addr: IP6_Address, ok: bool) {
+	// If we have an IPv6 address of the form [IP]:Port, first get us just the IP.
+	address, _ := split_port(address_and_maybe_port) or_return
+
+	// Early bailouts based on length and number of pieces.
+	if len(address) < IPv6_MIN_STRING_LENGTH || len(address) > IPv6_MAX_STRING_LENGTH { return }
+
+	/*
+		Do a pre-pass on the string that checks how many `:` and `.` we have,
+		if they're in the right order, and if the things between them are digits as expected.
+
+		It's not strictly necessary considering we could use `strings.split`,
+		but this way we can avoid using an allocator and return earlier on bogus input. Win-win.
+	*/
+	colon_count  := 0
+	dot_count    := 0
+
+	pieces_temp:  [IPv6_PIECE_COUNT + 1]string
+
+	piece_start := 0
+	piece_end   := 0
+
+	for ch, i in address {
+		switch ch {
+		case '0'..='9', 'a'..='f', 'A'..='F':
+			piece_end += 1
+
+		case ':':
+			// If we see a `:` after a `.`, it means an IPv4 part was sandwiched between IPv6, instead of it being the tail: invalid.
+			if dot_count > 0 { return }
+
+			pieces_temp[colon_count] = address[piece_start:piece_end]
+
+			colon_count += 1
+			if colon_count > IPv6_PIECE_COUNT { return }
+
+			// If there's anything left, put it in the next piece.
+			piece_start = i + 1
+			piece_end   = piece_start
+
+		case '.':
+			// IPv4 address is treated as one piece. No need to update `piece_*`.
+			dot_count += 1
+
+		case: // Invalid character, return early
+			return
+		}
+	}
+
+	if colon_count < IPv6_MIN_COLONS { return }
+
+	// Assign the last piece string.
+	pieces_temp[colon_count] = address[piece_start:]
+
+	// `pieces` now holds the same output as it would if had used `strings.split`.
+	pieces := pieces_temp[:colon_count + 1]
+
+	// Check if we have what looks like an embedded IPv4 address.
+	ipv4:      IP4_Address
+	have_ipv4: bool
+
+	if dot_count > 0 {
+		/*
+			If we have an IPv4 address accounting for the last 32 bits,
+			this means we can have at most 6 IPv6 pieces, like so: `x:x:X:x:x:x:d.d.d.d`
+
+			Or, put differently: 6 pieces IPv6 (5 colons), a colon, 1 piece IPv4 (3 dots),
+			for a total of 6 colons and 3 dots.
+		*/
+		if dot_count != 3 || colon_count > 6 { return }
+
+		/*
+			Try to parse IPv4 address.
+			If successful, we have our least significant 32 bits.
+			If not, it invalidates the whole address and we can bail.
+		*/
+		ipv4, have_ipv4 = parse_ip4_address(pieces_temp[colon_count])
+		if !have_ipv4 { return }
+	}
+
+	// Check for `::` being used more than once, and save the skip.
+	zero_skip := -1
+	for i in 1..<colon_count {
+		if pieces[i] == "" {
+			// Return if skip has already been set.
+			if zero_skip != -1 { return }
+			zero_skip = i
+		}
+	}
+
+	/*
+		Now check if we have the necessary number pieces, accounting for any `::`,
+		and how many were skipped by it if applicable.
+	*/
+	before_skip := 0
+	after_skip  := 0
+	num_skipped := 0
+
+	if zero_skip != -1 {
+		before_skip = zero_skip
+		after_skip  = colon_count - zero_skip
+
+		// An IPv4 "piece" accounts for 2 IPv6 pieces we haven't added to the pieces slice, so add 1.
+		if have_ipv4 {
+			after_skip += 1
+		}
+
+		// Adjust for leading `::`.
+		if pieces[0] == "" {
+			before_skip -= 1
+			// Leading `:` can only be part of `::`.
+			if before_skip > 0 { return }
+		}
+
+		// Adjust for trailing `::`.
+		if pieces[colon_count] == "" {
+			after_skip -= 1
+			// Trailing `:` can only be part of `::`.
+			if after_skip > 0 { return }
+		}
+
+		/*
+			Calculate how many zero pieces we skipped.
+			It should be at least one, considering we encountered a `::`.
+		*/
+		num_skipped = IPv6_PIECE_COUNT - before_skip - after_skip
+		if num_skipped < 1 { return }
+
+	} else {
+		/*
+			No zero skip means everything is part of "before the skip".
+			An IPv4 "piece" accounts for 2 IPv6 pieces we haven't added to the pieces slice, so add 1.
+		*/
+		piece_count := colon_count + 1
+		if have_ipv4 {
+			piece_count += 1
+		}
+
+		// Do we have the complete set?
+		if piece_count != IPv6_PIECE_COUNT { return }
+
+		// Validate leading and trailing empty parts, as they can only be part of a `::`.
+		if pieces[0] == "" || pieces[colon_count] == "" { return }
+
+
+		before_skip = piece_count
+		after_skip  = 0
+		num_skipped = 0
+	}
+
+	// Now try to parse the pieces into a 8 16-bit pieces.
+	piece_values: [IPv6_PIECE_COUNT]u16be
+
+	idx     := 0
+	val_idx := 0
+
+	for _ in 0..<before_skip {
+		/*
+			An empty piece is the default zero. Otherwise, try to parse as an IPv6 hex piece.
+			If we have an IPv4 address, stop on the penultimate index.
+		*/
+		if have_ipv4 && val_idx == 6 {
+			break
+		}
+
+		piece := pieces[idx]
+
+		// An IPv6 piece can at most contain 4 hex digits.
+		if len(piece) > 4 { return }
+
+		if piece != "" {
+			val, _ := parse_ip_component(piece, 65535, {.IPv6}) or_return
+			piece_values[val_idx] = u16be(val)
+		}
+
+		idx     += 1
+		val_idx += 1
+	}
+
+	if before_skip == 0 {
+		idx += 1
+	}
+
+	if num_skipped > 0 {
+		idx     += 1
+		val_idx += num_skipped
+	}
+
+	if after_skip > 0 {
+		for _ in 0..<after_skip {
+			/*
+				An empty piece is the default zero. Otherwise, try to parse as an IPv6 hex piece.
+				If we have an IPv4 address, stop on the penultimate index.
+			*/
+			if have_ipv4 && val_idx == 6 {
+				break
+			}
+
+			piece := pieces[idx]
+
+			// An IPv6 piece can contain at most 4 hex digits.
+			if len(piece) > 4 { return }
+
+			if piece != "" {
+				val, _ := parse_ip_component(piece, 65535, {.IPv6}) or_return
+				piece_values[val_idx] = u16be(val)
+			}
+
+			idx     += 1
+			val_idx += 1
+		}
+	}
+
+	// Distribute IPv4 address into last two pieces, if applicable.
+	if have_ipv4 {
+		val := u16(ipv4[0]) << 8
+		val |= u16(ipv4[1])
+		piece_values[6] = u16be(val)
+
+		val  = u16(ipv4[2]) << 8
+		val |= u16(ipv4[3])
+		piece_values[7] = u16be(val)
+	}
+	return transmute(IP6_Address)piece_values, true
+}
+
+/*
+	Try parsing as an IPv6 address.
+	If it's determined not to be, try as an IPv4 address, optionally in non-decimal format.
+*/
+parse_address :: proc(address_and_maybe_port: string, non_decimal_address := false) -> Address {
+	if addr6, ok6 := parse_ip6_address(address_and_maybe_port); ok6 {
+		return addr6
+	}
+	if addr4, ok4 := parse_ip4_address(address_and_maybe_port, non_decimal_address); ok4 {
+		return addr4
+	}
+	return nil
+}
+
+parse_endpoint :: proc(endpoint_str: string) -> (ep: Endpoint, ok: bool) {
+	if addr_str, port, split_ok := split_port(endpoint_str); split_ok {
+		if addr := parse_address(addr_str); addr != nil {
+			return Endpoint { address = addr, port = port }, true
+		}
+	}
+	return
+}
+
+Host :: struct {
+	hostname: string,
+	port:     int,
+}
+Host_Or_Endpoint :: union {
+	Host,
+	Endpoint,
+}
+
+// Takes a string consisting of a hostname or IP address, and an optional port,
+// and return the component parts in a useful form.
+parse_hostname_or_endpoint :: proc(endpoint_str: string) -> (target: Host_Or_Endpoint, err: Parse_Endpoint_Error) {
+	host, port, port_ok := split_port(endpoint_str)
+	if !port_ok {
+		return nil, .Bad_Port
+	}
+	if addr := parse_address(host); addr != nil {
+		return Endpoint{addr, port}, .None
+	}
+	if !validate_hostname(host) {
+		return nil, .Bad_Hostname
+	}
+	return Host{host, port}, .None
+}
+
+
+// Takes an endpoint string and returns its parts.
+// Returns ok=false if port is not a number.
+split_port :: proc(endpoint_str: string) -> (addr_or_host: string, port: int, ok: bool) {
+	// IP6 [addr_or_host]:port
+	if i := strings.last_index(endpoint_str, "]:"); i >= 0 {
+		addr_or_host = endpoint_str[1:i]
+		port, ok = strconv.parse_int(endpoint_str[i+2:], 10)
+
+		if port > 65535 {
+			ok = false
+		}
+		return
+	}
+
+	if n := strings.count(endpoint_str, ":"); n == 1 {
+		// IP4 addr_or_host:port
+		i := strings.last_index(endpoint_str, ":")
+		assert(i != -1)
+
+		addr_or_host = endpoint_str[:i]
+		port, ok = strconv.parse_int(endpoint_str[i+1:], 10)
+
+		if port > 65535 {
+			ok = false
+		}
+		return
+	} else if n > 1 {
+		// IP6 address without port
+	}
+
+	// No port
+	addr_or_host = endpoint_str
+	port = 0
+	ok = true
+	return
+}
+
+// Joins an address or hostname with a port.
+join_port :: proc(address_or_host: string, port: int, allocator := context.allocator) -> string {
+	addr_or_host, _, ok := split_port(address_or_host)
+	if !ok do return addr_or_host
+
+	b := strings.builder_make(allocator)
+
+	addr := parse_address(addr_or_host)
+	if addr == nil {
+		// hostname
+		fmt.sbprintf(&b, "%v:%v", addr_or_host, port)
+	} else {
+		switch in addr {
+		case IP4_Address:
+			fmt.sbprintf(&b, "%v:%v", address_to_string(addr), port)
+		case IP6_Address:
+			fmt.sbprintf(&b, "[%v]:%v", address_to_string(addr), port)
+		}
+	}
+	return strings.to_string(b)
+}
+
+
+
+// TODO(tetra): Do we need this?
+map_to_ip6 :: proc(addr: Address) -> Address {
+	if addr6, ok := addr.(IP6_Address); ok {
+		return addr6
+	}
+	addr4 := addr.(IP4_Address)
+	addr4_u16 := transmute([2]u16be) addr4
+	addr6: IP6_Address
+	addr6[4] = 0xffff
+	copy(addr6[5:], addr4_u16[:])
+	return addr6
+}
+
+/*
+	Returns a temporarily-allocated string representation of the address.
+
+	See RFC 5952 section 4 for IPv6 representation recommendations.
+*/
+address_to_string :: proc(addr: Address, allocator := context.temp_allocator) -> string {
+	b := strings.builder_make(allocator)
+	switch v in addr {
+	case IP4_Address:
+		fmt.sbprintf(&b, "%v.%v.%v.%v", v[0], v[1], v[2], v[3])
+	case IP6_Address:
+		// First find the longest run of zeroes.
+		Zero_Run :: struct {
+			start: int,
+			end:   int,
+		}
+
+		/*
+			We're dealing with 0-based indices, appropriately enough for runs of zeroes.
+			Still, it means we need to initialize runs with some value outside of the possible range.
+		*/
+		run  := Zero_Run{-1, -1}
+		best := Zero_Run{-1, -1}
+
+		addr := transmute([8]u16be)v
+
+		last := u16be(1)
+		for val, i in addr {
+			/*
+				If we encounter adjacent zeroes, then start a new run if not already in one.
+				Also remember the rightmost index regardless, because it'll be the new
+				frontier of both new and existing runs.
+			*/
+			if last == 0 && val == 0 {
+				run.end = i
+				if run.start == -1 {
+					run.start = i - 1
+				}
+			}
+
+			/*
+				If we're in a run check if its length is better than the best recorded so far.
+				If so, update the best run's start and end.
+			*/
+			if run.start != -1 {
+				length_to_beat := best.end - best.start
+				length         := run.end  - run.start
+
+				if length > length_to_beat {
+					best = run
+				}
+			}
+
+			// If we were in a run, this is where we reset it.
+			if val != 0 {
+				run = {-1, -1}
+			}
+
+			last = val
+		}
+
+		for val, i in addr {
+			if best.start == i || best.end == i {
+				// For the left and right side of the best zero run, print a `:`.
+				fmt.sbprint(&b, ":")
+			} else if i < best.start {
+				/*
+					If we haven't made it to the best run yet, print the digit.
+					Make sure we only print a `:` after the digit if it's not
+					immediately followed by the run's own leftmost `:`.
+				*/
+				fmt.sbprintf(&b, "%x", val)
+				if i < best.start - 1 {
+					fmt.sbprintf(&b, ":")
+				}
+			} else if i > best.end {
+				/*
+					If there are any digits after the zero run, print them.
+					But don't print the `:` at the end of the IP number.
+				*/
+				fmt.sbprintf(&b, "%x", val)
+				if i != 7 {
+					fmt.sbprintf(&b, ":")
+				}
+			}
+		}
+	}
+	return strings.to_string(b)
+}
+
+// Returns a temporarily-allocated string representation of the endpoint.
+// If there's a port, uses the `[address]:port` format.
+endpoint_to_string :: proc(ep: Endpoint, allocator := context.temp_allocator) -> string {
+	if ep.port == 0 {
+		return address_to_string(ep.address, allocator)
+	} else {
+		s := address_to_string(ep.address, context.temp_allocator)
+		b := strings.builder_make(allocator)
+		switch a in ep.address {
+		case IP4_Address:  fmt.sbprintf(&b, "%v:%v",   s, ep.port)
+		case IP6_Address:  fmt.sbprintf(&b, "[%v]:%v", s, ep.port)
+		}
+		return strings.to_string(b)
+	}
+}
+
+to_string :: proc{address_to_string, endpoint_to_string}
+
+
+family_from_address :: proc(addr: Address) -> Address_Family {
+	switch in addr {
+	case IP4_Address: return .IP4
+	case IP6_Address: return .IP6
+	case:
+		unreachable()
+	}
+}
+family_from_endpoint :: proc(ep: Endpoint) -> Address_Family {
+	return family_from_address(ep.address)
+}
+
+
+Digit_Parse_Base :: enum u8 {
+	Dec  = 0, // No prefix
+	Oct  = 1, // Leading zero
+	Hex  = 2, // 0x prefix
+	IPv6 = 3, // Unprefixed IPv6 piece hex. Can't be used with other bases.
+}
+Digit_Parse_Bases :: bit_set[Digit_Parse_Base; u8]
+DEFAULT_DIGIT_BASES :: Digit_Parse_Bases{.Dec, .Oct, .Hex}
+
+/*
+	Parses a single unsigned number in requested `bases` from `input`.
+	`max_value` represents the maximum allowed value for this number.
+
+	Returns the `value`, the `bytes_consumed` so far, and `ok` to signal success or failure.
+
+	An out-of-range or invalid number will return the accumulated value so far (which can be out of range),
+	the number of bytes consumed leading up the error, and `ok = false`.
+
+	When `.` or `:` are encountered, they'll be considered valid separators and will stop parsing,
+	returning the valid number leading up to it.
+
+	Other non-digit characters are treated as an error.
+
+	Octal numbers are expected to have a leading zero, with no 'o' format specifier.
+	Hexadecimal numbers are expected to be preceded by '0x' or '0X'.
+	Numbers will otherwise be considered to be in base 10.
+*/
+parse_ip_component :: proc(input: string, max_value := u64(max(u32)), bases := DEFAULT_DIGIT_BASES) -> (value: u64, bytes_consumed: int, ok: bool) {
+	// Default to base 10
+	base         := u64(10)
+	input        := input
+
+	/*
+		We keep track of the number of prefix bytes and digit bytes separately.
+		This way if a prefix is consumed and we encounter a separator or the end of the string,
+		the number is only considered valid if at least 1 digit byte has been consumed and the value is within range.
+	*/
+	prefix_bytes := 0
+	digit_bytes  := 0
+
+	/*
+		IPv6 hex bytes are unprefixed and can't be disambiguated from octal or hex unless the digit is out of range.
+		If we got the `.IPv6` option, skip prefix scanning and other flags aren't also used.
+	*/
+	if .IPv6 in bases {
+		if bases != {.IPv6} { return } // Must be used on its own.
+		base = 16
+	} else {
+		// Scan for and consume prefix, if applicable.
+		if len(input) >= 2 && input[0] == '0' {
+			if .Hex in bases && (input[1] == 'x' || input[1] == 'X') {
+				base         = 16
+				input        = input[2:]
+				prefix_bytes = 2
+			}
+			if prefix_bytes == 0 && .Oct in bases {
+				base         = 8
+				input        = input[1:]
+				prefix_bytes = 1
+			}
+		}
+	}
+
+	parse_loop: for ch in input {
+		switch ch {
+		case '0'..='7':
+			digit_bytes += 1
+			value = value * base + u64(ch - '0')
+
+		case '8'..='9':
+			digit_bytes += 1
+
+			if base == 8 {
+				// Out of range for octal numbers.
+				return value, digit_bytes + prefix_bytes, false
+			}
+			value = value * base + u64(ch - '0')
+
+		case 'a'..='f':
+			digit_bytes += 1
+
+			if base == 8 || base == 10 {
+				// Out of range for octal and decimal numbers.
+				return value, digit_bytes + prefix_bytes, false
+			}
+			value = value * base + (u64(ch - 'a') + 10)
+
+		case 'A'..='F':
+			digit_bytes += 1
+
+			if base == 8 || base == 10 {
+				// Out of range for octal and decimal numbers.
+				return value, digit_bytes + prefix_bytes, false
+			}
+			value = value * base + (u64(ch - 'A') + 10)
+
+		case '.', ':':
+			/*
+				Number separator. Return early.
+				We don't need to check if the number is in range.
+				We do that each time through the loop.
+			*/
+			break parse_loop
+
+		case:
+			// Invalid character encountered.
+			return value, digit_bytes + prefix_bytes, false
+		}
+
+		if value > max_value {
+			// Out-of-range number.
+			return value, digit_bytes + prefix_bytes, false
+		}
+	}
+
+	// If we consumed at least 1 digit byte, `value` *should* continue a valid number in an appropriate base in the allowable range.
+	return value, digit_bytes + prefix_bytes, digit_bytes >= 1
+}
+
+// Returns an address for each interface that can be bound to.
+get_network_interfaces :: proc() -> []Address {
+	// TODO: Implement using `enumerate_interfaces` and returning only the addresses of active interfaces.
+	return nil
+}

+ 415 - 0
core/net/common.odin

@@ -0,0 +1,415 @@
+// +build windows, linux, darwin
+package net
+
+/*
+	Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
+	For other protocols and their features, see subdirectories of this package.
+
+	This file collects structs, enums and settings applicable to the entire package in one handy place.
+	Platform-specific ones can be found in their respective `*_windows.odin` and similar files.
+*/
+
+/*
+	Copyright 2022 Tetralux        <[email protected]>
+	Copyright 2022 Colin Davidson  <[email protected]>
+	Copyright 2022 Jeroen van Rijn <[email protected]>.
+	Made available under Odin's BSD-3 license.
+
+	List of contributors:
+		Tetralux:        Initial implementation
+		Colin Davidson:  Linux platform code, OSX platform code, Odin-native DNS resolver
+		Jeroen van Rijn: Cross platform unification, code style, documentation
+*/
+
+import "core:runtime"
+
+/*
+	TUNEABLES - See also top of `dns.odin` for DNS configuration.
+
+	Determines the default value for whether dial_tcp() and accept_tcp() will set TCP_NODELAY on the new
+	socket, and the client socket, respectively.
+	This can also be set on a per-socket basis using the 'options' optional parameter to those procedures.
+
+	When TCP_NODELAY is set, data will be sent out to the peer as quickly as possible, rather than being
+	coalesced into fewer network packets.
+
+	This makes the networking layer more eagerly send data when you ask it to,
+	which can reduce latency by up to 200ms.
+
+	This does mean that a lot of small writes will negatively effect throughput however,
+	since the Nagle algorithm will be disabled, and each write becomes one
+	IP packet. This will increase traffic by a factor of 40, with IP and TCP
+	headers for each payload.
+
+	However, you can avoid this by buffering things up yourself if you wish to send a lot of
+	short data chunks, when TCP_NODELAY is enabled on that socket.
+*/
+
+ODIN_NET_TCP_NODELAY_DEFAULT :: #config(ODIN_NET_TCP_NODELAY_DEFAULT, true)
+
+// COMMON DEFINITIONS
+Maybe :: runtime.Maybe
+
+Network_Error :: union #shared_nil {
+	General_Error,
+	Platform_Error,
+	Create_Socket_Error,
+	Dial_Error,
+	Listen_Error,
+	Accept_Error,
+	Bind_Error,
+	TCP_Send_Error,
+	UDP_Send_Error,
+	TCP_Recv_Error,
+	UDP_Recv_Error,
+	Shutdown_Error,
+	Socket_Option_Error,
+	Parse_Endpoint_Error,
+	Resolve_Error,
+	DNS_Error,
+}
+
+General_Error :: enum u32 {
+	None = 0,
+	Unable_To_Enumerate_Network_Interfaces = 1,
+}
+
+// `Platform_Error` is used to wrap errors returned by the different platforms that don't fit a common error.
+Platform_Error :: enum u32 {}
+
+Parse_Endpoint_Error :: enum {
+	None          = 0,
+	Bad_Port      = 1,
+	Bad_Address,
+	Bad_Hostname,
+}
+
+Resolve_Error :: enum u32 {
+	None = 0,
+	Unable_To_Resolve = 1,
+}
+
+DNS_Error :: enum u32 {
+	Invalid_Hostname_Error = 1,
+	Invalid_Hosts_Config_Error,
+	Invalid_Resolv_Config_Error,
+	Connection_Error,
+	Server_Error,
+	System_Error,
+}
+
+// SOCKET OPTIONS & DEFINITIONS
+TCP_Options :: struct {
+	no_delay: bool,
+}
+
+default_tcp_options := TCP_Options {
+	no_delay = ODIN_NET_TCP_NODELAY_DEFAULT,
+}
+
+/*
+	To allow freely using `Socket` in your own data structures in a cross-platform manner,
+	we treat it as a handle large enough to accomodate OS-specific notions of socket handles.
+
+	The platform code will perform the cast so you don't have to.
+*/
+Socket     :: distinct i64
+
+TCP_Socket :: distinct Socket
+UDP_Socket :: distinct Socket
+
+Socket_Protocol :: enum {
+	TCP,
+	UDP,
+}
+
+Any_Socket :: union {
+	TCP_Socket,
+	UDP_Socket,
+}
+
+/*
+	ADDRESS DEFINITIONS
+*/
+
+IP4_Address :: distinct [4]u8
+IP6_Address :: distinct [8]u16be
+Address :: union {IP4_Address, IP6_Address}
+
+IP4_Loopback := IP4_Address{127, 0, 0, 1}
+IP6_Loopback := IP6_Address{0, 0, 0, 0, 0, 0, 0, 1}
+
+IP4_Any := IP4_Address{}
+IP6_Any := IP6_Address{}
+
+Endpoint :: struct {
+	address: Address,
+	port:    int,
+}
+
+Address_Family :: enum {
+	IP4,
+	IP6,
+}
+
+Netmask :: distinct Address
+
+/*
+	INTERFACE / LINK STATE
+*/
+Network_Interface :: struct {
+	adapter_name:     string, // On Windows this is a GUID that we could parse back into its u128 for more compact storage.
+	friendly_name:    string,
+	description:      string,
+	dns_suffix:       string,
+
+	physical_address: string, // MAC address, etc.
+	mtu:              u32,
+
+	unicast:          [dynamic]Lease,
+	multicast:        [dynamic]Address,
+	anycast:          [dynamic]Address,
+
+	gateways:         [dynamic]Address,
+	dhcp_v4:          Address,
+	dhcp_v6:          Address,
+
+	tunnel_type:      Tunnel_Type,
+
+	link: struct {
+		state:          Link_State,
+		transmit_speed: u64,
+		receive_speed:  u64,
+	},
+}
+
+// Empty bit set is unknown state.
+Link_States :: enum u32 {
+	Up               = 1,
+	Down             = 2,
+	Testing          = 3,
+	Dormant          = 4,
+	Not_Present      = 5,
+	Lower_Layer_Down = 6,
+	Loopback         = 7,
+}
+Link_State :: bit_set[Link_States; u32]
+
+Lease :: struct {
+	address:  Address,
+	netmask:  Netmask,
+	lifetime: struct {
+		valid:     u32,
+		preferred: u32,
+		lease:     u32,
+	},
+	origin: struct {
+		prefix: Prefix_Origin,
+		suffix: Suffix_Origin,
+	},
+	address_duplication: Address_Duplication,
+}
+
+Tunnel_Type :: enum i32 {
+	None         = 0,
+	Other        = 1,
+	Direct       = 2,
+	IPv4_To_IPv6 = 11,
+	ISA_TAP      = 13,
+	Teredo       = 14,
+	IP_HTTPS     = 15,
+}
+
+Prefix_Origin :: enum i32 {
+	Other                = 0,
+	Manual               = 1,
+	Well_Known           = 2,
+	DHCP                 = 3,
+	Router_Advertisement = 4,
+	Unchanged            = 16,
+}
+
+Suffix_Origin :: enum i32 {
+	Other                = 0,
+	Manual               = 1,
+	Well_Known           = 2,
+	DHCP                 = 3,
+	Link_Layer_Address   = 4,
+	Random               = 5,
+	Unchanged            = 16,
+}
+
+Address_Duplication :: enum i32 {
+	Invalid    = 0,
+	Tentative  = 1,
+	Duplicate  = 2,
+	Deprecated = 3,
+	Preferred  = 4,
+}
+
+// DNS DEFINITIONS
+DNS_Configuration :: struct {
+	// Configuration files.
+	resolv_conf: string,
+	hosts_file:  string,
+
+	// TODO: Allow loading these up with `reload_configuration()` call or the like,
+	// so we don't have to do it each call.
+	name_servers:       []Endpoint,
+	hosts_file_entries: []DNS_Record,
+}
+
+DNS_Record_Type :: enum u16 {
+	DNS_TYPE_A     = 0x1,  // IP4 address.
+	DNS_TYPE_NS    = 0x2,  // IP6 address.
+	DNS_TYPE_CNAME = 0x5,  // Another host name.
+	DNS_TYPE_MX    = 0xf,  // Arbitrary binary data or text.
+	DNS_TYPE_AAAA  = 0x1c, // Address of a name (DNS) server.
+	DNS_TYPE_TEXT  = 0x10, // Address and preference priority of a mail exchange server.
+	DNS_TYPE_SRV   = 0x21, // Address, port, priority, and weight of a host that provides a particular service.
+
+	IP4            = DNS_TYPE_A,
+	IP6            = DNS_TYPE_AAAA,
+	CNAME          = DNS_TYPE_CNAME,
+	TXT            = DNS_TYPE_TEXT,
+	NS             = DNS_TYPE_NS,
+	MX             = DNS_TYPE_MX,
+	SRV            = DNS_TYPE_SRV,
+}
+
+// Base DNS Record. All DNS responses will carry a hostname and TTL (time to live) field.
+DNS_Record_Base :: struct {
+	record_name: string,
+	ttl_seconds: u32, // The time in seconds that this service will take to update, after the record is updated.
+}
+
+// An IP4 address that the domain name maps to. There can be any number of these.
+DNS_Record_IP4 :: struct {
+	using base: DNS_Record_Base,
+	address:    IP4_Address,
+}
+
+// An IPv6 address that the domain name maps to. There can be any number of these.
+DNS_Record_IP6 :: struct {
+	using base: DNS_Record_Base,
+	address:    IP6_Address,
+}
+
+/*
+	Another domain name that the domain name maps to.
+	Domains can be pointed to another domain instead of directly to an IP address.
+	`get_dns_records` will recursively follow these if you request this type of record.
+*/
+DNS_Record_CNAME :: struct {
+	using base: DNS_Record_Base,
+	host_name:  string,
+}
+
+/*
+	Arbitrary string data that is associated with the domain name.
+	Commonly of the form `key=value` to be parsed, though there is no specific format for them.
+	These can be used for any purpose.
+*/
+DNS_Record_TXT :: struct {
+	using base: DNS_Record_Base,
+	value:      string,
+}
+
+/*
+	Domain names of other DNS servers that are associated with the domain name.
+	TODO(tetra): Expand on what these records are used for, and when you should use pay attention to these.
+*/
+DNS_Record_NS :: struct {
+	using base: DNS_Record_Base,
+	host_name:  string,
+}
+
+// Domain names for email servers that are associated with the domain name.
+// These records also have values which ranks them in the order they should be preferred. Lower is more-preferred.
+DNS_Record_MX :: struct {
+	using base: DNS_Record_Base,
+	host_name:  string,
+	preference: int,
+}
+
+/*
+	An endpoint for a service that is available through the domain name.
+	This is the way to discover the services that a domain name provides.
+
+	Clients MUST attempt to contact the host with the lowest priority that they can reach.
+	If two hosts have the same priority, they should be contacted in the order according to their weight.
+	Hosts with larger weights should have a proportionally higher chance of being contacted by clients.
+	A weight of zero indicates a very low weight, or, when there is no choice (to reduce visual noise).
+
+	The host may be "." to indicate that it is "decidedly not available" on this domain.
+*/
+DNS_Record_SRV :: struct {
+	// base contains the full name of this record.
+	// e.g: _sip._tls.example.com
+	using base: DNS_Record_Base,
+
+	// The hostname or address where this service can be found.
+	target:        string,
+	// The port on which this service can be found.
+	port:          int,
+
+	service_name:  string, // NOTE(tetra): These are substrings of 'record_name'
+	protocol_name: string, // NOTE(tetra): These are substrings of 'record_name'
+
+	// Lower is higher priority
+	priority:      int,
+	// Relative weight of this host compared to other of same priority; the chance of using this host should be proporitional to this weight.
+	// The number of seconds that it will take to update the record.
+	weight:        int,
+}
+
+DNS_Record :: union {
+	DNS_Record_IP4,
+	DNS_Record_IP6,
+	DNS_Record_CNAME,
+	DNS_Record_TXT,
+	DNS_Record_NS,
+	DNS_Record_MX,
+	DNS_Record_SRV,
+}
+
+DNS_Response_Code :: enum u16be {
+	No_Error,
+	Format_Error,
+	Server_Failure,
+	Name_Error,
+	Not_Implemented,
+	Refused,
+}
+
+DNS_Query :: enum u16be {
+	Host_Address              = 1,
+	Authoritative_Name_Server = 2,
+	Mail_Destination          = 3,
+	Mail_Forwarder            = 4,
+	CNAME                     = 5,
+	All                       = 255,
+}
+
+DNS_Header :: struct {
+	id:                     u16be,
+	is_response:            bool,
+	opcode:                 u16be,
+	is_authoritative:       bool,
+	is_truncated:           bool,
+	is_recursion_desired:   bool,
+	is_recursion_available: bool,
+	response_code:          DNS_Response_Code,
+}
+
+DNS_Record_Header :: struct #packed {
+	type:   u16be,
+	class:  u16be,
+	ttl:    u32be,
+	length: u16be,
+}
+
+DNS_Host_Entry :: struct {
+	name: string,
+	addr: Address,
+}

+ 863 - 0
core/net/dns.odin

@@ -0,0 +1,863 @@
+// +build windows, linux, darwin
+package net
+
+/*
+	Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
+	For other protocols and their features, see subdirectories of this package.
+*/
+
+/*
+	Copyright 2022 Tetralux        <[email protected]>
+	Copyright 2022 Colin Davidson  <[email protected]>
+	Copyright 2022 Jeroen van Rijn <[email protected]>.
+	Made available under Odin's BSD-3 license.
+
+	List of contributors:
+		Tetralux:        Initial implementation
+		Colin Davidson:  Linux platform code, OSX platform code, Odin-native DNS resolver
+		Jeroen van Rijn: Cross platform unification, code style, documentation
+*/
+
+import "core:mem"
+import "core:strings"
+import "core:time"
+import "core:os"
+
+/*
+	Default configuration for DNS resolution.
+*/
+when ODIN_OS == .Windows {
+	DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{
+		resolv_conf        = "",
+		hosts_file         = "%WINDIR%\\system32\\drivers\\etc\\hosts",
+	}
+} else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD {
+	DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{
+		resolv_conf        = "/etc/resolv.conf",
+		hosts_file         = "/etc/hosts",
+	}
+} else {
+	#panic("Please add a configuration for this OS.")
+}
+
+@(init)
+init_dns_configuration :: proc() {
+	/*
+		Resolve %ENVIRONMENT% placeholders in their paths.
+	*/
+	dns_configuration.resolv_conf, _ = replace_environment_path(dns_configuration.resolv_conf)
+	dns_configuration.hosts_file,  _ = replace_environment_path(dns_configuration.hosts_file)
+}
+
+destroy_dns_configuration :: proc() {
+	delete(dns_configuration.resolv_conf)
+	delete(dns_configuration.hosts_file)
+}
+
+dns_configuration := DEFAULT_DNS_CONFIGURATION
+
+// Always allocates for consistency.
+replace_environment_path :: proc(path: string, allocator := context.allocator) -> (res: string, ok: bool) {
+	// Nothing to replace. Return a clone of the original.
+	if strings.count(path, "%") != 2 {
+		return strings.clone(path, allocator), true
+	}
+
+	left  := strings.index(path, "%") + 1
+	assert(left > 0 && left <= len(path)) // should be covered by there being two %
+
+	right := strings.index(path[left:], "%") + 1
+	assert(right > 0 && right <= len(path)) // should be covered by there being two %
+
+	env_key := path[left: right]
+	env_val := os.get_env(env_key, allocator)
+	defer delete(env_val)
+
+	res, _ = strings.replace(path, path[left - 1: right + 1], env_val, 1, allocator)
+	return res, true
+}
+
+
+/*
+	Resolves a hostname to exactly one IP4 and IP6 endpoint.
+	It's then up to you which one you use.
+	Note that which address you use to open a socket, determines the type of the socket you get.
+
+	Returns `ok=false` if the host name could not be resolved to any endpoints.
+
+	Returned endpoints have the same port as provided in the string, or 0 if absent.
+	If you want to use a specific port, just modify the field after the call to this procedure.
+
+	If the hostname part of the endpoint is actually a string representation of an IP address, DNS resolution will be skipped.
+	This allows you to pass both strings like "example.com:9000" and "1.2.3.4:9000" to this function end reliably get
+	back an endpoint in both cases.
+*/
+resolve :: proc(hostname_and_maybe_port: string) -> (ep4, ep6: Endpoint, err: Network_Error) {
+	target := parse_hostname_or_endpoint(hostname_and_maybe_port) or_return
+	switch t in target {
+	case Endpoint:
+		// NOTE(tetra): The hostname was actually an IP address; nothing to resolve, so just return it.
+		switch in t.address {
+		case IP4_Address: ep4 = t
+		case IP6_Address: ep6 = t
+		case:             unreachable()
+		}
+		return
+
+	case Host:
+		err4, err6: Network_Error = ---, ---
+		ep4, err4 = resolve_ip4(t.hostname)
+		ep6, err6 = resolve_ip6(t.hostname)
+		if err4 != nil && err6 != nil {
+			err = err4
+		}
+		return
+	}
+	unreachable()
+}
+
+resolve_ip4 :: proc(hostname_and_maybe_port: string) -> (ep4: Endpoint, err: Network_Error) {
+	target := parse_hostname_or_endpoint(hostname_and_maybe_port) or_return
+	switch t in target {
+	case Endpoint:
+		// NOTE(tetra): The hostname was actually an IP address; nothing to resolve, so just return it.
+		switch in t.address {
+		case IP4_Address:
+			return t, nil
+		case IP6_Address:
+			err = .Unable_To_Resolve
+			return
+		}
+	case Host:
+		recs, _ := get_dns_records_from_os(t.hostname, .IP4, context.temp_allocator)
+		if len(recs) == 0 {
+			err = .Unable_To_Resolve
+			return
+		}
+		ep4 = {
+			address = recs[0].(DNS_Record_IP4).address,
+			port = t.port,
+		}
+		return
+	}
+	unreachable()
+}
+
+resolve_ip6 :: proc(hostname_and_maybe_port: string) -> (ep6: Endpoint, err: Network_Error) {
+	target := parse_hostname_or_endpoint(hostname_and_maybe_port) or_return
+	switch t in target {
+	case Endpoint:
+		// NOTE(tetra): The hostname was actually an IP address; nothing to resolve, so just return it.
+		switch in t.address {
+		case IP4_Address:
+			err = .Unable_To_Resolve
+			return
+		case IP6_Address:
+			return t, nil
+		}
+	case Host:
+		recs, _ := get_dns_records_from_os(t.hostname, .IP6, context.temp_allocator)
+		if len(recs) == 0 {
+			err = .Unable_To_Resolve
+			return
+		}
+		ep6 = {
+			address = recs[0].(DNS_Record_IP6).address,
+			port = t.port,
+		}
+		return
+	}
+	unreachable()
+}
+
+/*
+	Performs a recursive DNS query for records of a particular type for the hostname using the OS.
+
+	NOTE: This procedure instructs the DNS resolver to recursively perform CNAME requests on our behalf,
+	meaning that DNS queries for a hostname will resolve through CNAME records until an
+	IP address is reached.
+
+	IMPORTANT: This procedure allocates memory for each record returned; deleting just the returned slice is not enough!
+	See `destroy_records`.
+*/
+get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
+	return _get_dns_records_os(hostname, type, allocator)
+}
+
+/*
+	A generic DNS client usable on any platform.
+	Performs a recursive DNS query for records of a particular type for the hostname.
+
+	NOTE: This procedure instructs the DNS resolver to recursively perform CNAME requests on our behalf,
+	meaning that DNS queries for a hostname will resolve through CNAME records until an
+	IP address is reached.
+
+	IMPORTANT: This procedure allocates memory for each record returned; deleting just the returned slice is not enough!
+	See `destroy_records`.
+*/
+get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type, name_servers: []Endpoint, host_overrides: []DNS_Record, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
+	context.allocator = allocator
+
+	if type != .SRV {
+		// NOTE(tetra): 'hostname' can contain underscores when querying SRV records
+		ok := validate_hostname(hostname)
+		if !ok {
+			return nil, .Invalid_Hostname_Error
+		}
+	}
+
+	hdr := DNS_Header{
+		id = 0,
+		is_response = false,
+		opcode = 0,
+		is_authoritative = false,
+		is_truncated = false,
+		is_recursion_desired = true,
+		is_recursion_available = false,
+		response_code = DNS_Response_Code.No_Error,
+	}
+
+	id, bits := pack_dns_header(hdr)
+	dns_hdr := [6]u16be{}
+	dns_hdr[0] = id
+	dns_hdr[1] = bits
+	dns_hdr[2] = 1
+
+	dns_query := [2]u16be{ u16be(type), 1 }
+
+	output := [(size_of(u16be) * 6) + NAME_MAX + (size_of(u16be) * 2)]u8{}
+	b := strings.builder_from_slice(output[:])
+
+	strings.write_bytes(&b, mem.slice_data_cast([]u8, dns_hdr[:]))
+	ok := encode_hostname(&b, hostname)
+	if !ok {
+		return nil, .Invalid_Hostname_Error
+	}
+	strings.write_bytes(&b, mem.slice_data_cast([]u8, dns_query[:]))
+
+	dns_packet := output[:strings.builder_len(b)]
+
+	dns_response_buf := [4096]u8{}
+	dns_response: []u8
+	for name_server in name_servers {
+		conn, sock_err := make_unbound_udp_socket(family_from_endpoint(name_server))
+		if sock_err != nil {
+			return nil, .Connection_Error
+		}
+		defer close(conn)
+
+		_, send_err := send(conn, dns_packet[:], name_server)
+		if send_err != nil {
+			continue
+		}
+
+		set_err := set_option(conn, .Receive_Timeout, time.Second * 1)
+		if set_err != nil {
+			return nil, .Connection_Error
+		}
+
+		recv_sz, _, recv_err := recv_udp(conn, dns_response_buf[:])
+		if recv_err == UDP_Recv_Error.Timeout {
+			continue
+		} else if recv_err != nil {
+			continue
+		}
+
+		if recv_sz == 0 {
+			continue
+		}
+
+		dns_response = dns_response_buf[:recv_sz]
+
+		rsp, _ok := parse_response(dns_response, type)
+		if !_ok {
+			return nil, .Server_Error
+		}
+
+		if len(rsp) == 0 {
+			continue
+		}
+
+		return rsp[:], nil
+	}
+
+	return
+}
+
+// `records` slice is also destroyed.
+destroy_dns_records :: proc(records: []DNS_Record, allocator := context.allocator) {
+	context.allocator = allocator
+
+	for rec in records {
+		switch r in rec {
+		case DNS_Record_IP4:
+			delete(r.base.record_name)
+
+		case DNS_Record_IP6:
+			delete(r.base.record_name)
+
+		case DNS_Record_CNAME:
+			delete(r.base.record_name)
+			delete(r.host_name)
+
+		case DNS_Record_TXT:
+			delete(r.base.record_name)
+			delete(r.value)
+
+		case DNS_Record_NS:
+			delete(r.base.record_name)
+			delete(r.host_name)
+
+		case DNS_Record_MX:
+			delete(r.base.record_name)
+			delete(r.host_name)
+
+		case DNS_Record_SRV:
+			delete(r.record_name)
+			delete(r.target)
+		}
+	}
+
+	delete(records, allocator)
+}
+
+/*
+	TODO(cloin): Does the DNS Resolver need to recursively hop through CNAMEs to get the IP
+	or is that what recursion desired does? Do we need to handle recursion unavailable?
+	How do we deal with is_authoritative / is_truncated?
+*/
+
+NAME_MAX  :: 255
+LABEL_MAX :: 63
+
+pack_dns_header :: proc(hdr: DNS_Header) -> (id: u16be, bits: u16be) {
+	id = hdr.id
+	bits = hdr.opcode << 1 | u16be(hdr.response_code)
+	if hdr.is_response {
+		bits |= 1 << 15
+	}
+	if hdr.is_authoritative {
+		bits |= 1 << 10
+	}
+	if hdr.is_truncated {
+		bits |= 1 << 9
+	}
+	if hdr.is_recursion_desired {
+		bits |= 1 << 8
+	}
+	if hdr.is_recursion_available {
+		bits |= 1 << 7
+	}
+
+	return id, bits
+}
+
+unpack_dns_header :: proc(id: u16be, bits: u16be) -> (hdr: DNS_Header) {
+	hdr.id = id
+	hdr.is_response            = (bits & (1 << 15)) != 0
+	hdr.opcode                 = (bits >> 11) & 0xF
+	hdr.is_authoritative       = (bits & (1 << 10)) != 0
+	hdr.is_truncated           = (bits & (1 <<  9)) != 0
+	hdr.is_recursion_desired   = (bits & (1 <<  8)) != 0
+	hdr.is_recursion_available = (bits & (1 <<  7)) != 0
+	hdr.response_code          = DNS_Response_Code(bits & 0xF)
+
+	return hdr
+}
+
+load_resolv_conf :: proc(resolv_conf_path: string, allocator := context.allocator) -> (name_servers: []Endpoint, ok: bool) {
+	context.allocator = allocator
+
+	res := os.read_entire_file_from_filename(resolv_conf_path) or_return
+	defer delete(res)
+	resolv_str := string(res)
+
+	_name_servers := make([dynamic]Endpoint, 0, allocator)
+	for line in strings.split_lines_iterator(&resolv_str) {
+		if len(line) == 0 || line[0] == '#' {
+			continue
+		}
+
+		id_str := "nameserver"
+		if strings.compare(line[:len(id_str)], id_str) != 0 {
+			continue
+		}
+
+		server_ip_str := strings.trim_left_space(line[len(id_str):])
+		if len(server_ip_str) == 0 {
+			continue
+		}
+
+		addr := parse_address(server_ip_str)
+		endpoint := Endpoint{
+			addr,
+			53,
+		}
+		append(&_name_servers, endpoint)
+	}
+
+	return _name_servers[:], true
+}
+
+load_hosts :: proc(hosts_file_path: string, allocator := context.allocator) -> (hosts: []DNS_Host_Entry, ok: bool) {
+	context.allocator = allocator
+
+	res := os.read_entire_file_from_filename(hosts_file_path, allocator) or_return
+	defer delete(res)
+
+	_hosts := make([dynamic]DNS_Host_Entry, 0, allocator)
+	hosts_str := string(res)
+	for line in strings.split_lines_iterator(&hosts_str) {
+		if len(line) == 0 || line[0] == '#' {
+			continue
+		}
+
+		splits := strings.fields(line)
+		defer delete(splits)
+
+		ip_str := splits[0]
+		addr := parse_address(ip_str)
+		if addr == nil {
+			continue
+		}
+
+		for hostname in splits[1:] {
+			if len(hostname) == 0 {
+				continue
+			}
+
+			append(&_hosts, DNS_Host_Entry{hostname, addr})
+		}
+	}
+
+	return _hosts[:], true
+}
+
+// www.google.com -> 3www6google3com0
+encode_hostname :: proc(b: ^strings.Builder, hostname: string) -> (ok: bool) {
+	_hostname := hostname
+	for section in strings.split_iterator(&_hostname, ".") {
+		if len(section) > LABEL_MAX {
+			return
+		}
+
+		strings.write_byte(b, u8(len(section)))
+		strings.write_string(b, section)
+	}
+	strings.write_byte(b, 0)
+
+	return true
+}
+
+skip_hostname :: proc(packet: []u8, start_idx: int) -> (encode_size: int, ok: bool) {
+	out_size := 0
+
+	cur_idx := start_idx
+	iteration_max := 0
+	top: for cur_idx < len(packet) {
+		if packet[cur_idx] == 0 {
+			out_size += 1
+			break
+		}
+
+		if iteration_max > 255 {
+			return
+		}
+
+		if packet[cur_idx] > 63 && packet[cur_idx] != 0xC0 {
+			return
+		}
+
+		switch packet[cur_idx] {
+		case 0xC0:
+			out_size += 2
+			break top
+		case:
+			label_size := int(packet[cur_idx]) + 1
+			idx2 := cur_idx + label_size
+
+			if idx2 < cur_idx + 1 || idx2 > len(packet) {
+				return
+			}
+
+			out_size += label_size
+			cur_idx = idx2
+		}
+
+		iteration_max += 1
+	}
+
+	if start_idx + out_size > len(packet) {
+		return
+	}
+
+	return out_size, true
+}
+
+decode_hostname :: proc(packet: []u8, start_idx: int, allocator := context.allocator) -> (hostname: string, encode_size: int, ok: bool) {
+	output := [NAME_MAX]u8{}
+	b := strings.builder_from_slice(output[:])
+
+	// If you're on level 0, update out_bytes, everything through a pointer
+	// doesn't count towards this hostname's packet length
+
+	// Evaluate tokens to generate the hostname
+	out_size := 0
+	level := 0
+	print_size := 0
+	cur_idx := start_idx
+	iteration_max := 0
+	labels_added := 0
+	for cur_idx < len(packet) {
+		if packet[cur_idx] == 0 {
+
+			if (level == 0) {
+				out_size += 1
+			}
+
+			break
+		}
+
+		if iteration_max > 255 {
+			return
+		}
+
+		if packet[cur_idx] > 63 && packet[cur_idx] != 0xC0 {
+			return
+		}
+
+		switch packet[cur_idx] {
+
+		// This is a offset to more data in the packet, jump to it
+		case 0xC0:
+			pkt := packet[cur_idx:cur_idx+2]
+			val := (^u16be)(raw_data(pkt))^
+			offset := int(val & 0x3FFF)
+			if offset > len(packet) {
+				return
+			}
+
+			cur_idx = offset
+
+			if (level == 0) {
+				out_size += 2
+				level += 1
+			}
+
+		// This is a label, insert it into the hostname
+		case:
+			label_size := int(packet[cur_idx])
+			idx2 := cur_idx + label_size + 1
+			if idx2 < cur_idx + 1 || idx2 > len(packet) {
+				return
+			}
+
+			if print_size + label_size + 1 > NAME_MAX {
+				return
+			}
+
+			if labels_added > 0 {
+				strings.write_byte(&b, '.')
+			}
+			strings.write_bytes(&b, packet[cur_idx+1:idx2])
+			print_size += label_size + 1
+			labels_added += 1
+
+			cur_idx = idx2
+
+			if (level == 0) {
+				out_size += label_size + 1
+			}
+		}
+
+		iteration_max += 1
+	}
+
+	if start_idx + out_size > len(packet) {
+		return
+	}
+
+	return strings.clone(strings.to_string(b), allocator), out_size, true
+}
+
+// Uses RFC 952 & RFC 1123
+validate_hostname :: proc(hostname: string) -> (ok: bool) {
+	if len(hostname) > 255 || len(hostname) == 0 {
+		return
+	}
+
+	if hostname[0] == '-' {
+		return
+	}
+
+	_hostname := hostname
+	for label in strings.split_iterator(&_hostname, ".") {
+		if len(label) > 63 || len(label) == 0 {
+			return
+		}
+
+		for ch in label {
+			switch ch {
+			case:
+				return
+			case 'a'..='z', 'A'..='Z', '0'..='9', '-':
+				continue
+			}
+		}
+	}
+
+	return true
+}
+
+parse_record :: proc(packet: []u8, cur_off: ^int, filter: DNS_Record_Type = nil) -> (record: DNS_Record, ok: bool) {
+	record_buf := packet[cur_off^:]
+
+	srv_record_name, hn_sz := decode_hostname(packet, cur_off^, context.temp_allocator) or_return
+	// TODO(tetra): Not sure what we should call this.
+	// Is it really only used in SRVs?
+	// Maybe some refactoring is required?
+
+	ahdr_sz := size_of(DNS_Record_Header)
+	if len(record_buf) - hn_sz < ahdr_sz {
+		return
+	}
+
+	record_hdr_bytes := record_buf[hn_sz:hn_sz+ahdr_sz]
+	record_hdr := cast(^DNS_Record_Header)raw_data(record_hdr_bytes)
+
+	data_sz := record_hdr.length
+	data_off := cur_off^ + int(hn_sz) + int(ahdr_sz)
+	data := packet[data_off:data_off+int(data_sz)]
+	cur_off^ += int(hn_sz) + int(ahdr_sz) + int(data_sz)
+
+	if u16be(filter) != record_hdr.type {
+		return nil, true
+	}
+
+	_record: DNS_Record
+	#partial switch DNS_Record_Type(record_hdr.type) {
+		case .IP4:
+			if len(data) != 4 {
+				return
+			}
+
+			addr := (^IP4_Address)(raw_data(data))^
+
+			_record = DNS_Record_IP4{
+				base = DNS_Record_Base{
+					record_name = strings.clone(srv_record_name),
+					ttl_seconds = u32(record_hdr.ttl),
+				},
+				address = addr,
+			}
+
+		case .IP6:
+			if len(data) != 16 {
+				return
+			}
+
+			addr := (^IP6_Address)(raw_data(data))^
+
+			_record = DNS_Record_IP6{
+				base = DNS_Record_Base{
+					record_name = strings.clone(srv_record_name),
+					ttl_seconds = u32(record_hdr.ttl),
+				},
+				address = addr,
+			}
+
+		case .CNAME:
+			hostname, _ := decode_hostname(packet, data_off) or_return
+
+			_record = DNS_Record_CNAME{
+				base = DNS_Record_Base{
+					record_name = strings.clone(srv_record_name),
+					ttl_seconds = u32(record_hdr.ttl),
+				},
+				host_name = hostname,
+			}
+
+		case .TXT:
+			_record = DNS_Record_TXT{
+				base = DNS_Record_Base{
+					record_name = strings.clone(srv_record_name),
+					ttl_seconds = u32(record_hdr.ttl),
+				},
+				value = strings.clone(string(data)),
+			}
+
+		case .NS:
+			name, _ := decode_hostname(packet, data_off) or_return
+
+			_record = DNS_Record_NS{
+				base = DNS_Record_Base{
+					record_name = strings.clone(srv_record_name),
+					ttl_seconds = u32(record_hdr.ttl),
+				},
+				host_name = name,
+			}
+
+		case .SRV:
+			if len(data) <= 6 {
+				return
+			}
+
+			_data := mem.slice_data_cast([]u16be, data)
+
+			priority, weight, port := _data[0], _data[1], _data[2]
+			target, _ := decode_hostname(packet, data_off + (size_of(u16be) * 3)) or_return
+
+			// NOTE(tetra): Srv record name should be of the form '_servicename._protocol.hostname'
+			// The record name is the name of the record.
+			// Not to be confused with the _target_ of the record, which is--in combination with the port--what we're looking up
+			// by making this request in the first place.
+
+			// NOTE(Jeroen): Service Name and Protocol Name can probably just be string slices into the record name.
+			// It's already cloned, after all. I wouldn't put them on the temp allocator like this.
+
+			parts := strings.split_n(srv_record_name, ".", 3, context.temp_allocator)
+			if len(parts) != 3 {
+				return
+			}
+			service_name, protocol_name := parts[0], parts[1]
+
+			_record = DNS_Record_SRV{
+				base = DNS_Record_Base{
+					record_name = strings.clone(srv_record_name),
+					ttl_seconds = u32(record_hdr.ttl),
+				},
+				target        = target,
+				service_name  = service_name,
+				protocol_name = protocol_name,
+				priority      = int(priority),
+				weight        = int(weight),
+				port          = int(port),
+			}
+
+		case .MX:
+			if len(data) <= 2 {
+				return
+			}
+
+			preference: u16be = mem.slice_data_cast([]u16be, data)[0]
+			hostname, _ := decode_hostname(packet, data_off + size_of(u16be)) or_return
+
+			_record = DNS_Record_MX{
+				base = DNS_Record_Base{
+					record_name = strings.clone(srv_record_name),
+					ttl_seconds = u32(record_hdr.ttl),
+				},
+				host_name  = hostname,
+				preference = int(preference),
+			}
+
+		case:
+			return
+
+	}
+
+	return _record, true
+}
+
+/*
+	DNS Query Response Format:
+	- DNS_Header (packed)
+	- Query Count
+	- Answer Count
+	- Authority Count
+	- Additional Count
+	- Query[]
+		- Hostname -- encoded
+		- Type
+		- Class
+	- Answer[]
+		- DNS Record Data
+	- Authority[]
+		- DNS Record Data
+	- Additional[]
+		- DNS Record Data
+
+	DNS Record Data:
+	- DNS_Record_Header
+	- Data[]
+*/
+
+parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator := context.allocator) -> (records: []DNS_Record, ok: bool) {
+	context.allocator = allocator
+
+	HEADER_SIZE_BYTES :: 12
+	if len(response) < HEADER_SIZE_BYTES {
+		return
+	}
+
+	_records := make([dynamic]DNS_Record, 0)
+
+	dns_hdr_chunks := mem.slice_data_cast([]u16be, response[:HEADER_SIZE_BYTES])
+	hdr := unpack_dns_header(dns_hdr_chunks[0], dns_hdr_chunks[1])
+	if !hdr.is_response {
+		return
+	}
+
+	question_count := int(dns_hdr_chunks[2])
+	if question_count != 1 {
+		return
+	}
+	answer_count := int(dns_hdr_chunks[3])
+	authority_count := int(dns_hdr_chunks[4])
+	additional_count := int(dns_hdr_chunks[5])
+
+	cur_idx := HEADER_SIZE_BYTES
+
+	for _ in 0..<question_count {
+		if cur_idx == len(response) {
+			continue
+		}
+
+		dq_sz :: 4
+		hn_sz := skip_hostname(response, cur_idx) or_return
+		dns_query := mem.slice_data_cast([]u16be, response[cur_idx+hn_sz:cur_idx+hn_sz+dq_sz])
+
+		cur_idx += hn_sz + dq_sz
+	}
+
+	for _ in 0..<answer_count {
+		if cur_idx == len(response) {
+			continue
+		}
+
+		rec := parse_record(response, &cur_idx, filter) or_return
+		if rec == nil {
+			continue
+		}
+
+		append(&_records, rec)
+	}
+
+	for _ in 0..<authority_count {
+		if cur_idx == len(response) {
+			continue
+		}
+
+		rec := parse_record(response, &cur_idx, filter) or_return
+		if rec == nil {
+			continue
+		}
+
+		append(&_records, rec)
+	}
+
+	for _ in 0..<additional_count {
+		if cur_idx == len(response) {
+			continue
+		}
+
+		rec := parse_record(response, &cur_idx, filter) or_return
+		if rec == nil {
+			continue
+		}
+
+		append(&_records, rec)
+	}
+
+	return _records[:], true
+}

+ 83 - 0
core/net/dns_unix.odin

@@ -0,0 +1,83 @@
+//+build linux, darwin
+package net
+/*
+	Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
+	For other protocols and their features, see subdirectories of this package.
+*/
+
+/*
+	Copyright 2022 Tetralux        <[email protected]>
+	Copyright 2022 Colin Davidson  <[email protected]>
+	Copyright 2022 Jeroen van Rijn <[email protected]>.
+	Made available under Odin's BSD-3 license.
+
+	List of contributors:
+		Tetralux:        Initial implementation
+		Colin Davidson:  Linux platform code, OSX platform code, Odin-native DNS resolver
+		Jeroen van Rijn: Cross platform unification, code style, documentation
+*/
+import "core:strings"
+
+@(private)
+_get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
+	context.allocator = allocator
+
+	if type != .SRV {
+		// NOTE(tetra): 'hostname' can contain underscores when querying SRV records
+		ok := validate_hostname(hostname)
+		if !ok {
+			return nil, .Invalid_Hostname_Error
+		}
+	}
+
+	name_servers, resolve_ok := load_resolv_conf(dns_configuration.resolv_conf)
+	defer delete(name_servers)
+	if !resolve_ok {
+		return nil, .Invalid_Resolv_Config_Error
+	}
+	if len(name_servers) == 0 {
+		return
+	}
+
+	hosts, hosts_ok := load_hosts(dns_configuration.hosts_file)
+	defer delete(hosts)
+	if !hosts_ok {
+		return nil, .Invalid_Hosts_Config_Error
+	}
+	if len(hosts) == 0 {
+		return
+	}
+
+	host_overrides := make([dynamic]DNS_Record)
+	for host in hosts {
+		if strings.compare(host.name, hostname) != 0 {
+			continue
+		}
+
+		if type == .IP4 && family_from_address(host.addr) == .IP4 {
+			record := DNS_Record_IP4{
+				base = {
+					record_name = strings.clone(hostname),
+					ttl_seconds = 0,
+				},
+				address = host.addr.(IP4_Address),
+			}
+			append(&host_overrides, record)
+		} else if type == .IP6 && family_from_address(host.addr) == .IP6 {
+			record := DNS_Record_IP6{
+				base = {
+					record_name = strings.clone(hostname),
+					ttl_seconds = 0,
+				},
+				address = host.addr.(IP6_Address),
+			}
+			append(&host_overrides, record)
+		}
+	}
+
+	if len(host_overrides) > 0 {
+		return host_overrides[:], nil
+	}
+
+	return get_dns_records_from_nameservers(hostname, type, name_servers, host_overrides[:])
+}

+ 159 - 0
core/net/dns_windows.odin

@@ -0,0 +1,159 @@
+//+build windows
+package net
+
+/*
+	Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
+	For other protocols and their features, see subdirectories of this package.
+*/
+
+/*
+	Copyright 2022 Tetralux        <[email protected]>
+	Copyright 2022 Colin Davidson  <[email protected]>
+	Copyright 2022 Jeroen van Rijn <[email protected]>.
+	Made available under Odin's BSD-3 license.
+
+	List of contributors:
+		Tetralux:        Initial implementation
+		Colin Davidson:  Linux platform code, OSX platform code, Odin-native DNS resolver
+		Jeroen van Rijn: Cross platform unification, code style, documentation
+*/
+
+import "core:strings"
+import "core:mem"
+
+import win "core:sys/windows"
+
+@(private)
+_get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
+	context.allocator = allocator
+
+	host_cstr := strings.clone_to_cstring(hostname, context.temp_allocator)
+	rec: ^win.DNS_RECORD
+	res := win.DnsQuery_UTF8(host_cstr, u16(type), 0, nil, &rec, nil)
+
+	switch u32(res) {
+	case 0:
+		// okay
+	case win.ERROR_INVALID_NAME:
+		return nil, .Invalid_Hostname_Error
+	case win.DNS_INFO_NO_RECORDS:
+		return
+	case:
+		return nil, .System_Error
+	}
+	defer win.DnsRecordListFree(rec, 1) // 1 means that we're freeing a list... because the proc name isn't enough.
+
+	count := 0
+	for r := rec; r != nil; r = r.pNext {
+		if r.wType != u16(type) do continue // NOTE(tetra): Should never happen, but...
+		count += 1
+	}
+
+	recs := make([dynamic]DNS_Record, 0, count)
+	if recs == nil do return nil, .System_Error // return no results if OOM.
+
+	for r := rec; r != nil; r = r.pNext {
+		if r.wType != u16(type) do continue // NOTE(tetra): Should never happen, but...
+
+		base_record := DNS_Record_Base{
+			record_name = strings.clone(string(r.pName)),
+			ttl_seconds = r.dwTtl,
+		}
+
+		switch DNS_Record_Type(r.wType) {
+		case .IP4:
+			addr := IP4_Address(transmute([4]u8)r.Data.A)
+			record := DNS_Record_IP4{
+				base    = base_record,
+				address = addr,
+			}
+			append(&recs, record)
+
+		case .IP6:
+			addr := IP6_Address(transmute([8]u16be) r.Data.AAAA)
+			record := DNS_Record_IP6{
+				base    = base_record,
+				address = addr,
+			}
+			append(&recs, record)
+
+		case .CNAME:
+
+			hostname := strings.clone(string(r.Data.CNAME))
+			record := DNS_Record_CNAME{
+				base      = base_record,
+				host_name = hostname,
+			}
+			append(&recs, record)
+
+		case .TXT:
+			n := r.Data.TXT.dwStringCount
+			ptr := &r.Data.TXT.pStringArray
+			c_strs := mem.slice_ptr(ptr, int(n))
+
+			for cstr in c_strs {
+				record := DNS_Record_TXT{
+					base  = base_record,
+					value = strings.clone(string(cstr)),
+				}
+				append(&recs, record)
+			}
+
+		case .NS:
+			hostname := strings.clone(string(r.Data.NS))
+			record := DNS_Record_NS{
+				base      = base_record,
+				host_name = hostname,
+			}
+			append(&recs, record)
+
+		case .MX:
+			/*
+				TODO(tetra): Order by preference priority? (Prefer hosts with lower preference values.)
+				Or maybe not because you're supposed to just use the first one that works
+				and which order they're in changes every few calls.
+			*/
+
+			record := DNS_Record_MX{
+				base       = base_record,
+				host_name  = strings.clone(string(r.Data.MX.pNameExchange)),
+				preference = int(r.Data.MX.wPreference),
+			}
+			append(&recs, record)
+
+		case .SRV:
+			target   := strings.clone(string(r.Data.SRV.pNameTarget)) // The target hostname/address that the service can be found on
+			priority := int(r.Data.SRV.wPriority)
+			weight   := int(r.Data.SRV.wWeight)
+			port     := int(r.Data.SRV.wPort)
+
+			// NOTE(tetra): Srv record name should be of the form '_servicename._protocol.hostname'
+			// The record name is the name of the record.
+			// Not to be confused with the _target_ of the record, which is--in combination with the port--what we're looking up
+			// by making this request in the first place.
+
+			// NOTE(Jeroen): Service Name and Protocol Name can probably just be string slices into the record name.
+			// It's already cloned, after all. I wouldn't put them on the temp allocator like this.
+
+			parts := strings.split_n(base_record.record_name, ".", 3, context.temp_allocator)
+			if len(parts) != 3 {
+				continue
+			}
+			service_name, protocol_name := parts[0], parts[1]
+
+			append(&recs, DNS_Record_SRV {
+				base          = base_record,
+				target        = target,
+				port          = port,
+				service_name  = service_name,
+				protocol_name = protocol_name,
+				priority      = priority,
+				weight        = weight,
+
+			})
+		}
+	}
+
+	records = recs[:]
+	return
+}

+ 46 - 0
core/net/doc.odin

@@ -0,0 +1,46 @@
+/*
+	Copyright 2022 Tetralux        <[email protected]>
+	Copyright 2022 Colin Davidson  <[email protected]>
+	Copyright 2022 Jeroen van Rijn <[email protected]>.
+	Made available under Odin's BSD-3 license.
+
+	List of contributors:
+		Tetralux:        Initial implementation
+		Colin Davidson:  Linux platform code, OSX platform code, Odin-native DNS resolver
+		Jeroen van Rijn: Cross platform unification, code style, documentation
+*/
+
+/*
+	Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
+	For other protocols and their features, see subdirectories of this package.
+
+	Features:
+		- Supports Windows, Linux and OSX.
+		- Opening and closing of TCP and UDP sockets.
+		- Sending to and receiving from these sockets.
+		- DNS name lookup, using either the OS or our own resolver.
+
+	Planned:
+		- Nonblocking IO
+		- `Connection` struct
+			A "fat socket" struct that remembers how you opened it, etc, instead of just being a handle.
+		- IP Range structs, CIDR/class ranges, netmask calculator and associated helper procedures.
+		- Use `context.temp_allocator` instead of stack-based arenas?
+			And check it's the default temp allocator or can give us 4 MiB worth of memory
+			without punting to the main allocator by comparing their addresses in an @(init) procedure.
+			Panic if this assumption is not met.
+
+		- Document assumptions about libc usage (or avoidance thereof) for each platform.
+
+	Assumptions:
+		- For performance reasons this package relies on the `context.temp_allocator` in some places.
+
+		  You can replace the default `context.temp_allocator` with your own as long as it meets
+		  this requirement: A minimum of 4 MiB of scratch space that's expected not to be freed.
+
+		  If this expectation is not met, the package's @(init) procedure will attempt to detect
+		  this and panic to avoid temp allocations prematurely overwriting data and garbling results,
+		  or worse. This means that should you replace the temp allocator with an insufficient one,
+		  we'll do our best to loudly complain the first time you try it.
+*/
+package net

+ 200 - 0
core/net/errors_darwin.odin

@@ -0,0 +1,200 @@
+package net
+// +build darwin
+
+/*
+	Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
+	For other protocols and their features, see subdirectories of this package.
+*/
+
+/*
+	Copyright 2022 Tetralux        <[email protected]>
+	Copyright 2022 Colin Davidson  <[email protected]>
+	Copyright 2022 Jeroen van Rijn <[email protected]>.
+	Made available under Odin's BSD-3 license.
+
+	List of contributors:
+		Tetralux:        Initial implementation
+		Colin Davidson:  Linux platform code, OSX platform code, Odin-native DNS resolver
+		Jeroen van Rijn: Cross platform unification, code style, documentation
+*/
+
+import "core:c"
+import "core:os"
+
+Create_Socket_Error :: enum c.int {
+	None                                 = 0,
+	Family_Not_Supported_For_This_Socket = c.int(os.EAFNOSUPPORT),
+	No_Socket_Descriptors_Available      = c.int(os.EMFILE),
+	No_Buffer_Space_Available            = c.int(os.ENOBUFS),
+	No_Memory_Available_Available        = c.int(os.ENOMEM),
+	Protocol_Unsupported_By_System       = c.int(os.EPROTONOSUPPORT),
+	Wrong_Protocol_For_Socket            = c.int(os.EPROTONOSUPPORT),
+	Family_And_Socket_Type_Mismatch      = c.int(os.EPROTONOSUPPORT),
+}
+
+Dial_Error :: enum c.int {
+	None                      = 0,
+	Port_Required             = -1,
+
+	Address_In_Use            = c.int(os.EADDRINUSE),
+	In_Progress               = c.int(os.EINPROGRESS),
+	Cannot_Use_Any_Address    = c.int(os.EADDRNOTAVAIL),
+	Wrong_Family_For_Socket   = c.int(os.EAFNOSUPPORT),
+	Refused                   = c.int(os.ECONNREFUSED),
+	Is_Listening_Socket       = c.int(os.EACCES),
+	Already_Connected         = c.int(os.EISCONN),
+	Network_Unreachable       = c.int(os.ENETUNREACH),  // Device is offline
+	Host_Unreachable          = c.int(os.EHOSTUNREACH), // Remote host cannot be reached
+	No_Buffer_Space_Available = c.int(os.ENOBUFS),
+	Not_Socket                = c.int(os.ENOTSOCK),
+	Timeout                   = c.int(os.ETIMEDOUT),
+
+	// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
+	Would_Block               = c.int(os.EWOULDBLOCK), 
+}
+
+Bind_Error :: enum c.int {
+	None                    = 0,
+	Address_In_Use          = c.int(os.EADDRINUSE),    // Another application is currently bound to this endpoint.
+	Given_Nonlocal_Address  = c.int(os.EADDRNOTAVAIL), // The address is not a local address on this machine.
+	Broadcast_Disabled      = c.int(os.EACCES),        // To bind a UDP socket to the broadcast address, the appropriate socket option must be set.
+	Address_Family_Mismatch = c.int(os.EFAULT),        // The address family of the address does not match that of the socket.
+	Already_Bound           = c.int(os.EINVAL),        // The socket is already bound to an address.
+	No_Ports_Available      = c.int(os.ENOBUFS),       // There are not enough ephemeral ports available.
+}
+
+Listen_Error :: enum c.int {
+	None                                    = 0,
+	Address_In_Use                          = c.int(os.EADDRINUSE),
+	Already_Connected                       = c.int(os.EISCONN),
+	No_Socket_Descriptors_Available         = c.int(os.EMFILE),
+	No_Buffer_Space_Available               = c.int(os.ENOBUFS),
+	Nonlocal_Address                        = c.int(os.EADDRNOTAVAIL),
+	Not_Socket                              = c.int(os.ENOTSOCK),
+	Listening_Not_Supported_For_This_Socket = c.int(os.EOPNOTSUPP),
+}
+
+Accept_Error :: enum c.int {
+	None                                              = 0,
+	// TODO(tetra): Is this error actually possible here? Or is like Linux, in which case we can remove it.
+	Reset                                             = c.int(os.ECONNRESET), 
+	Not_Listening                                     = c.int(os.EINVAL),
+	No_Socket_Descriptors_Available_For_Client_Socket = c.int(os.EMFILE),
+	No_Buffer_Space_Available                         = c.int(os.ENOBUFS),
+	Not_Socket                                        = c.int(os.ENOTSOCK),
+	Not_Connection_Oriented_Socket                    = c.int(os.EOPNOTSUPP),
+
+	// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
+	Would_Block                                       = c.int(os.EWOULDBLOCK), 
+}
+
+TCP_Recv_Error :: enum c.int {
+	None              = 0,
+	Shutdown          = c.int(os.ESHUTDOWN),
+	Not_Connected     = c.int(os.ENOTCONN),
+
+	// TODO(tetra): Is this error actually possible here?
+	Connection_Broken = c.int(os.ENETRESET),
+	Not_Socket        = c.int(os.ENOTSOCK),
+	Aborted           = c.int(os.ECONNABORTED),
+
+	// TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them?
+	Connection_Closed = c.int(os.ECONNRESET),
+	Offline           = c.int(os.ENETDOWN),
+	Host_Unreachable  = c.int(os.EHOSTUNREACH),
+	Interrupted       = c.int(os.EINTR),
+
+	// NOTE: No, really. Presumably this means something different for nonblocking sockets...
+	Timeout           = c.int(os.EWOULDBLOCK),
+}
+
+UDP_Recv_Error :: enum c.int {
+	None           = 0,
+	Truncated      = c.int(os.EMSGSIZE), // The buffer is too small to fit the entire message, and the message was truncated.
+	Not_Socket     = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
+	Not_Descriptor = c.int(os.EBADF),    // The so-called socket is, in fact, not even a valid descriptor.
+	Bad_Buffer     = c.int(os.EFAULT),   // The buffer did not point to a valid location in memory.
+	Interrupted    = c.int(os.EINTR),    // A signal occurred before any data was transmitted. See signal(7).
+
+	// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
+	// NOTE: No, really. Presumably this means something different for nonblocking sockets...
+	Timeout          = c.int(os.EWOULDBLOCK), 
+	Socket_Not_Bound = c.int(os.EINVAL), // The socket must be bound for this operation, but isn't.
+}
+
+// TODO
+TCP_Send_Error :: enum c.int {
+	None                      = 0,
+
+	// TODO: merge with other errors?
+	Aborted                   = c.int(os.ECONNABORTED), 
+	Connection_Closed         = c.int(os.ECONNRESET),
+	Not_Connected             = c.int(os.ENOTCONN),
+	Shutdown                  = c.int(os.ESHUTDOWN),
+
+	// The send queue was full.
+	// This is usually a transient issue.
+	//
+	// This also shouldn't normally happen on Linux, as data is dropped if it
+	// doesn't fit in the send queue.
+	No_Buffer_Space_Available = c.int(os.ENOBUFS),
+	Offline                   = c.int(os.ENETDOWN),
+	Host_Unreachable          = c.int(os.EHOSTUNREACH),
+	Interrupted               = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7).
+
+	// NOTE: No, really. Presumably this means something different for nonblocking sockets...
+	// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
+	Timeout                   = c.int(os.EWOULDBLOCK), 
+	Not_Socket                = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
+}
+
+// TODO
+UDP_Send_Error :: enum c.int {
+	None                        = 0,
+	Truncated                   = c.int(os.EMSGSIZE), // The message is too big. No data was sent.
+
+	// TODO: not sure what the exact circumstances for this is yet
+	Network_Unreachable         = c.int(os.ENETUNREACH),
+	No_Outbound_Ports_Available = c.int(os.EAGAIN),   // There are no more emphemeral outbound ports available to bind the socket to, in order to send.
+
+	// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
+	// NOTE: No, really. Presumably this means something different for nonblocking sockets...
+	Timeout                     = c.int(os.EWOULDBLOCK), 
+	Not_Socket                  = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
+	Not_Descriptor              = c.int(os.EBADF),    // The so-called socket is, in fact, not even a valid descriptor.
+	Bad_Buffer                  = c.int(os.EFAULT),   // The buffer did not point to a valid location in memory.
+	Interrupted                 = c.int(os.EINTR),    // A signal occurred before any data was transmitted. See signal(7).
+
+	// The send queue was full.
+	// This is usually a transient issue.
+	//
+	// This also shouldn't normally happen on Linux, as data is dropped if it
+	// doesn't fit in the send queue.
+	No_Buffer_Space_Available   = c.int(os.ENOBUFS),
+	No_Memory_Available         = c.int(os.ENOMEM),   // No memory was available to properly manage the send queue.
+}
+
+Shutdown_Manner :: enum c.int {
+	Receive = c.int(os.SHUT_RD),
+	Send    = c.int(os.SHUT_WR),
+	Both    = c.int(os.SHUT_RDWR),
+}
+
+Shutdown_Error :: enum c.int {
+	None           = 0,
+	Aborted        = c.int(os.ECONNABORTED),
+	Reset          = c.int(os.ECONNRESET),
+	Offline        = c.int(os.ENETDOWN),
+	Not_Connected  = c.int(os.ENOTCONN),
+	Not_Socket     = c.int(os.ENOTSOCK),
+	Invalid_Manner = c.int(os.EINVAL),
+}
+
+Socket_Option_Error :: enum c.int {
+	None                       = 0,
+	Offline                    = c.int(os.ENETDOWN),
+	Timeout_When_Keepalive_Set = c.int(os.ENETRESET),
+	Invalid_Option_For_Socket  = c.int(os.ENOPROTOOPT),
+	Reset_When_Keepalive_Set   = c.int(os.ENOTCONN),
+	Not_Socket                 = c.int(os.ENOTSOCK),
+}

+ 193 - 0
core/net/errors_linux.odin

@@ -0,0 +1,193 @@
+package net
+// +build linux
+
+/*
+	Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
+	For other protocols and their features, see subdirectories of this package.
+*/
+
+/*
+	Copyright 2022 Tetralux        <[email protected]>
+	Copyright 2022 Colin Davidson  <[email protected]>
+	Copyright 2022 Jeroen van Rijn <[email protected]>.
+	Made available under Odin's BSD-3 license.
+
+	List of contributors:
+		Tetralux:        Initial implementation
+		Colin Davidson:  Linux platform code, OSX platform code, Odin-native DNS resolver
+		Jeroen van Rijn: Cross platform unification, code style, documentation
+*/
+
+import "core:c"
+import "core:os"
+
+Create_Socket_Error :: enum c.int {
+	None                                 = 0,
+	Family_Not_Supported_For_This_Socket = c.int(os.EAFNOSUPPORT),
+	No_Socket_Descriptors_Available      = c.int(os.EMFILE),
+	No_Buffer_Space_Available            = c.int(os.ENOBUFS),
+	No_Memory_Available_Available        = c.int(os.ENOMEM),
+	Protocol_Unsupported_By_System       = c.int(os.EPROTONOSUPPORT),
+	Wrong_Protocol_For_Socket            = c.int(os.EPROTONOSUPPORT),
+	Family_And_Socket_Type_Mismatch      = c.int(os.EPROTONOSUPPORT),
+}
+
+Dial_Error :: enum c.int {
+	None                      = 0,
+	Port_Required             = -1,
+
+	Address_In_Use            = c.int(os.EADDRINUSE),
+	In_Progress               = c.int(os.EINPROGRESS),
+	Cannot_Use_Any_Address    = c.int(os.EADDRNOTAVAIL),
+	Wrong_Family_For_Socket   = c.int(os.EAFNOSUPPORT),
+	Refused                   = c.int(os.ECONNREFUSED),
+	Is_Listening_Socket       = c.int(os.EACCES),
+	Already_Connected         = c.int(os.EISCONN),
+	Network_Unreachable       = c.int(os.ENETUNREACH), // Device is offline
+	Host_Unreachable          = c.int(os.EHOSTUNREACH), // Remote host cannot be reached
+	No_Buffer_Space_Available = c.int(os.ENOBUFS),
+	Not_Socket                = c.int(os.ENOTSOCK),
+	Timeout                   = c.int(os.ETIMEDOUT),
+
+	// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
+	Would_Block               = c.int(os.EWOULDBLOCK), 
+}
+
+Bind_Error :: enum c.int {
+	None                    = 0,
+	Address_In_Use          = c.int(os.EADDRINUSE),    // Another application is currently bound to this endpoint.
+	Given_Nonlocal_Address  = c.int(os.EADDRNOTAVAIL), // The address is not a local address on this machine.
+	Broadcast_Disabled      = c.int(os.EACCES),        // To bind a UDP socket to the broadcast address, the appropriate socket option must be set.
+	Address_Family_Mismatch = c.int(os.EFAULT),        // The address family of the address does not match that of the socket.
+	Already_Bound           = c.int(os.EINVAL),        // The socket is already bound to an address.
+	No_Ports_Available      = c.int(os.ENOBUFS),       // There are not enough ephemeral ports available.
+}
+
+Listen_Error :: enum c.int {
+	None                                    = 0,
+	Address_In_Use                          = c.int(os.EADDRINUSE),
+	Already_Connected                       = c.int(os.EISCONN),
+	No_Socket_Descriptors_Available         = c.int(os.EMFILE),
+	No_Buffer_Space_Available               = c.int(os.ENOBUFS),
+	Nonlocal_Address                        = c.int(os.EADDRNOTAVAIL),
+	Not_Socket                              = c.int(os.ENOTSOCK),
+	Listening_Not_Supported_For_This_Socket = c.int(os.EOPNOTSUPP),
+}
+
+Accept_Error :: enum c.int {
+	None                                              = 0,
+	Not_Listening                                     = c.int(os.EINVAL),
+	No_Socket_Descriptors_Available_For_Client_Socket = c.int(os.EMFILE),
+	No_Buffer_Space_Available                         = c.int(os.ENOBUFS),
+	Not_Socket                                        = c.int(os.ENOTSOCK),
+	Not_Connection_Oriented_Socket                    = c.int(os.EOPNOTSUPP),
+
+	// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
+	Would_Block                                       = c.int(os.EWOULDBLOCK),
+}
+
+TCP_Recv_Error :: enum c.int {
+	None              = 0,
+	Shutdown          = c.int(os.ESHUTDOWN),
+	Not_Connected     = c.int(os.ENOTCONN),
+	Connection_Broken = c.int(os.ENETRESET),
+	Not_Socket        = c.int(os.ENOTSOCK),
+	Aborted           = c.int(os.ECONNABORTED),
+
+	// TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them?
+	Connection_Closed = c.int(os.ECONNRESET), 
+	Offline           = c.int(os.ENETDOWN),
+	Host_Unreachable  = c.int(os.EHOSTUNREACH),
+	Interrupted       = c.int(os.EINTR),
+	Timeout           = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets...
+}
+
+UDP_Recv_Error :: enum c.int {
+	None             = 0,
+
+	// The buffer is too small to fit the entire message, and the message was truncated.
+	// When this happens, the rest of message is lost.
+	Buffer_Too_Small = c.int(os.EMSGSIZE), 
+	Not_Socket       = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
+	Not_Descriptor   = c.int(os.EBADF),    // The so-called socket is, in fact, not even a valid descriptor.
+	Bad_Buffer       = c.int(os.EFAULT),   // The buffer did not point to a valid location in memory.
+	Interrupted      = c.int(os.EINTR),    // A signal occurred before any data was transmitted. See signal(7).
+
+	// The send timeout duration passed before all data was received. See Socket_Option.Receive_Timeout.
+	// NOTE: No, really. Presumably this means something different for nonblocking sockets...
+	Timeout          = c.int(os.EWOULDBLOCK), 
+	Socket_Not_Bound = c.int(os.EINVAL), // The socket must be bound for this operation, but isn't.
+}
+
+// TODO
+TCP_Send_Error :: enum c.int {
+	None                      = 0,
+	// TODO(tetra): merge with other errors?
+	Aborted                   = c.int(os.ECONNABORTED), 
+	Connection_Closed         = c.int(os.ECONNRESET),
+	Not_Connected             = c.int(os.ENOTCONN),
+	Shutdown                  = c.int(os.ESHUTDOWN),
+
+	// The send queue was full.
+	// This is usually a transient issue.
+	//
+	// This also shouldn't normally happen on Linux, as data is dropped if it
+	// doesn't fit in the send queue.
+	No_Buffer_Space_Available = c.int(os.ENOBUFS),
+	Offline                   = c.int(os.ENETDOWN),
+	Host_Unreachable          = c.int(os.EHOSTUNREACH), // A signal occurred before any data was transmitted. See signal(7).
+	Interrupted               = c.int(os.EINTR),        // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
+	Timeout                   = c.int(os.EWOULDBLOCK),  // NOTE: No, really. Presumably this means something different for nonblocking sockets...
+	Not_Socket                = c.int(os.ENOTSOCK),     // The so-called socket is not an open socket.
+}
+
+// TODO
+UDP_Send_Error :: enum c.int {
+	None                        = 0,
+	Message_Too_Long            = c.int(os.EMSGSIZE),    // The message is too big. No data was sent.
+
+	// TODO: not sure what the exact circumstances for this is yet
+	Network_Unreachable         = c.int(os.ENETUNREACH), 
+	No_Outbound_Ports_Available = c.int(os.EAGAIN),      // There are no more emphemeral outbound ports available to bind the socket to, in order to send.
+
+	// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
+	// NOTE: No, really. Presumably this means something different for nonblocking sockets...
+	Timeout                     = c.int(os.EWOULDBLOCK), 
+	Not_Socket                  = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
+	Not_Descriptor              = c.int(os.EBADF),    // The so-called socket is, in fact, not even a valid descriptor.
+	Bad_Buffer                  = c.int(os.EFAULT),   // The buffer did not point to a valid location in memory.
+	Interrupted                 = c.int(os.EINTR),    // A signal occurred before any data was transmitted. See signal(7).
+
+	// The send queue was full.
+	// This is usually a transient issue.
+	//
+	// This also shouldn't normally happen on Linux, as data is dropped if it
+	// doesn't fit in the send queue.
+	No_Buffer_Space_Available   = c.int(os.ENOBUFS),
+	No_Memory_Available         = c.int(os.ENOMEM), // No memory was available to properly manage the send queue.
+}
+
+Shutdown_Manner :: enum c.int {
+	Receive = c.int(os.SHUT_RD),
+	Send    = c.int(os.SHUT_WR),
+	Both    = c.int(os.SHUT_RDWR),
+}
+
+Shutdown_Error :: enum c.int {
+	None           = 0,
+	Aborted        = c.int(os.ECONNABORTED),
+	Reset          = c.int(os.ECONNRESET),
+	Offline        = c.int(os.ENETDOWN),
+	Not_Connected  = c.int(os.ENOTCONN),
+	Not_Socket     = c.int(os.ENOTSOCK),
+	Invalid_Manner = c.int(os.EINVAL),
+}
+
+Socket_Option_Error :: enum c.int {
+	None                       = 0,
+	Offline                    = c.int(os.ENETDOWN),
+	Timeout_When_Keepalive_Set = c.int(os.ENETRESET),
+	Invalid_Option_For_Socket  = c.int(os.ENOPROTOOPT),
+	Reset_When_Keepalive_Set   = c.int(os.ENOTCONN),
+	Not_Socket                 = c.int(os.ENOTSOCK),
+}

+ 261 - 0
core/net/errors_windows.odin

@@ -0,0 +1,261 @@
+package net
+// +build windows
+
+/*
+	Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
+	For other protocols and their features, see subdirectories of this package.
+*/
+
+/*
+	Copyright 2022 Tetralux        <[email protected]>
+	Copyright 2022 Colin Davidson  <[email protected]>
+	Copyright 2022 Jeroen van Rijn <[email protected]>.
+	Made available under Odin's BSD-3 license.
+
+	List of contributors:
+		Tetralux:        Initial implementation
+		Colin Davidson:  Linux platform code, OSX platform code, Odin-native DNS resolver
+		Jeroen van Rijn: Cross platform unification, code style, documentation
+*/
+
+import "core:c"
+import win "core:sys/windows"
+
+Create_Socket_Error :: enum c.int {
+	None                                 = 0,
+	Network_Subsystem_Failure            = win.WSAENETDOWN,
+	Family_Not_Supported_For_This_Socket = win.WSAEAFNOSUPPORT,
+	No_Socket_Descriptors_Available      = win.WSAEMFILE,
+	No_Buffer_Space_Available            = win.WSAENOBUFS,
+	Protocol_Unsupported_By_System       = win.WSAEPROTONOSUPPORT,
+	Wrong_Protocol_For_Socket            = win.WSAEPROTOTYPE,
+	Family_And_Socket_Type_Mismatch      = win.WSAESOCKTNOSUPPORT,
+}
+
+Dial_Error :: enum c.int {
+	None                      = 0,
+	Port_Required             = -1,
+	Address_In_Use            = win.WSAEADDRINUSE,
+	In_Progress               = win.WSAEALREADY,
+	Cannot_Use_Any_Address    = win.WSAEADDRNOTAVAIL,
+	Wrong_Family_For_Socket   = win.WSAEAFNOSUPPORT,
+	Refused                   = win.WSAECONNREFUSED,
+	Is_Listening_Socket       = win.WSAEINVAL,
+	Already_Connected         = win.WSAEISCONN,
+	Network_Unreachable       = win.WSAENETUNREACH,  // Device is offline
+	Host_Unreachable          = win.WSAEHOSTUNREACH, // Remote host cannot be reached
+	No_Buffer_Space_Available = win.WSAENOBUFS,
+	Not_Socket                = win.WSAENOTSOCK,
+	Timeout                   = win.WSAETIMEDOUT,
+	Would_Block               = win.WSAEWOULDBLOCK,  // TODO: we may need special handling for this; maybe make a socket a struct with metadata?
+}
+
+Bind_Error :: enum c.int {
+	None                    = 0,
+	Address_In_Use          = win.WSAEADDRINUSE,    // Another application is currently bound to this endpoint.
+	Given_Nonlocal_Address  = win.WSAEADDRNOTAVAIL, // The address is not a local address on this machine.
+	Broadcast_Disabled      = win.WSAEACCES,        // To bind a UDP socket to the broadcast address, the appropriate socket option must be set.
+	Address_Family_Mismatch = win.WSAEFAULT,        // The address family of the address does not match that of the socket.
+	Already_Bound           = win.WSAEINVAL,        // The socket is already bound to an address.
+	No_Ports_Available      = win.WSAENOBUFS,       // There are not enough ephemeral ports available.
+}
+
+Listen_Error :: enum c.int {
+	None                                    = 0,
+	Address_In_Use                          = win.WSAEADDRINUSE,
+	Already_Connected                       = win.WSAEISCONN,
+	No_Socket_Descriptors_Available         = win.WSAEMFILE,
+	No_Buffer_Space_Available               = win.WSAENOBUFS,
+	Nonlocal_Address                        = win.WSAEADDRNOTAVAIL,
+	Not_Socket                              = win.WSAENOTSOCK,
+	Listening_Not_Supported_For_This_Socket = win.WSAEOPNOTSUPP,
+}
+
+Accept_Error :: enum c.int {
+	None                                              = 0,
+	Not_Listening                                     = win.WSAEINVAL,
+	No_Socket_Descriptors_Available_For_Client_Socket = win.WSAEMFILE,
+	No_Buffer_Space_Available                         = win.WSAENOBUFS,
+	Not_Socket                                        = win.WSAENOTSOCK,
+	Not_Connection_Oriented_Socket                    = win.WSAEOPNOTSUPP,
+
+	// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
+	Would_Block                                       = win.WSAEWOULDBLOCK, 
+}
+
+TCP_Recv_Error :: enum c.int {
+	None                      = 0,
+	Network_Subsystem_Failure = win.WSAENETDOWN,
+	Not_Connected             = win.WSAENOTCONN,
+	Bad_Buffer                = win.WSAEFAULT,
+	Keepalive_Failure         = win.WSAENETRESET,
+	Not_Socket                = win.WSAENOTSOCK,
+	Shutdown                  = win.WSAESHUTDOWN,
+	Would_Block               = win.WSAEWOULDBLOCK,
+
+	// TODO: not functionally different from Reset; merge?
+	Aborted                   = win.WSAECONNABORTED, 
+	Timeout                   = win.WSAETIMEDOUT,
+
+	// TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them?
+	Connection_Closed         = win.WSAECONNRESET, 
+
+	// TODO: verify can actually happen
+	Host_Unreachable          = win.WSAEHOSTUNREACH,
+}
+
+UDP_Recv_Error :: enum c.int {
+	None                      = 0,
+	Network_Subsystem_Failure = win.WSAENETDOWN,
+
+	// TODO: not functionally different from Reset; merge?
+	// UDP packets are limited in size, and the length of the incoming message exceeded it.
+	Aborted                   = win.WSAECONNABORTED, 
+	Truncated                 = win.WSAEMSGSIZE,
+	Remote_Not_Listening      = win.WSAECONNRESET,   // The machine at the remote endpoint doesn't have the given port open to receiving UDP data.
+	Shutdown                  = win.WSAESHUTDOWN,
+	Broadcast_Disabled        = win.WSAEACCES,       // A broadcast address was specified, but the .Broadcast socket option isn't set.
+	Bad_Buffer                = win.WSAEFAULT,
+	No_Buffer_Space_Available = win.WSAENOBUFS,
+	Not_Socket                = win.WSAENOTSOCK,     // The socket is not valid socket handle.
+	Would_Block               = win.WSAEWOULDBLOCK,
+	Host_Unreachable          = win.WSAEHOSTUNREACH, // The remote host cannot be reached from this host at this time.
+	Offline                   = win.WSAENETUNREACH,  // The network cannot be reached from this host at this time.
+	Timeout                   = win.WSAETIMEDOUT,
+
+	// TODO: can this actually happen? The socket isn't bound; an unknown flag specified; or MSG_OOB specified with SO_OOBINLINE enabled.
+	Incorrectly_Configured    = win.WSAEINVAL, 
+	TTL_Expired               = win.WSAENETRESET,    // The message took more hops than was allowed (the Time To Live) to reach the remote endpoint.
+}
+
+// TODO: consider merging some errors to make handling them easier
+// TODO: verify once more what errors to actually expose
+TCP_Send_Error :: enum c.int {
+	None                      = 0,
+	
+	// TODO: not functionally different from Reset; merge?
+	Aborted                   = win.WSAECONNABORTED, 
+	Not_Connected             = win.WSAENOTCONN,
+	Shutdown                  = win.WSAESHUTDOWN,
+	Connection_Closed         = win.WSAECONNRESET,
+	No_Buffer_Space_Available = win.WSAENOBUFS,
+	Network_Subsystem_Failure = win.WSAENETDOWN,
+	Host_Unreachable          = win.WSAEHOSTUNREACH,
+
+	// TODO: verify possible, as not mentioned in docs
+	Offline                   = win.WSAENETUNREACH,  
+	Timeout                   = win.WSAETIMEDOUT,
+
+	// A broadcast address was specified, but the .Broadcast socket option isn't set.
+	Broadcast_Disabled        = win.WSAEACCES,
+	Bad_Buffer                = win.WSAEFAULT,
+
+	// Connection is broken due to keepalive activity detecting a failure during the operation.
+	Keepalive_Failure         = win.WSAENETRESET, // TODO: not functionally different from Reset; merge?
+	Not_Socket                = win.WSAENOTSOCK,  // The so-called socket is not an open socket.
+}
+
+UDP_Send_Error :: enum c.int {
+	None                      = 0,
+	Network_Subsystem_Failure = win.WSAENETDOWN,
+
+	// TODO: not functionally different from Reset; merge?
+	Aborted                   = win.WSAECONNABORTED, // UDP packets are limited in size, and len(buf) exceeded it.
+	Message_Too_Long          = win.WSAEMSGSIZE,     // The machine at the remote endpoint doesn't have the given port open to receiving UDP data.
+	Remote_Not_Listening      = win.WSAECONNRESET,
+	Shutdown                  = win.WSAESHUTDOWN,    // A broadcast address was specified, but the .Broadcast socket option isn't set.
+	Broadcast_Disabled        = win.WSAEACCES,
+	Bad_Buffer                = win.WSAEFAULT,       // Connection is broken due to keepalive activity detecting a failure during the operation.
+
+	// TODO: not functionally different from Reset; merge?
+	Keepalive_Failure         = win.WSAENETRESET, 
+	No_Buffer_Space_Available = win.WSAENOBUFS,
+	Not_Socket                = win.WSAENOTSOCK,     // The socket is not valid socket handle.
+
+	// This socket is unidirectional and cannot be used to send any data.
+	// TODO: verify possible; decide whether to keep if not
+	Receive_Only                         = win.WSAEOPNOTSUPP,
+	Would_Block                          = win.WSAEWOULDBLOCK,
+	Host_Unreachable                     = win.WSAEHOSTUNREACH,  // The remote host cannot be reached from this host at this time.
+	Cannot_Use_Any_Address               = win.WSAEADDRNOTAVAIL, // Attempt to send to the Any address.
+	Family_Not_Supported_For_This_Socket = win.WSAEAFNOSUPPORT,  // The address is of an incorrect address family for this socket.
+	Offline                              = win.WSAENETUNREACH,   // The network cannot be reached from this host at this time.
+	Timeout                              = win.WSAETIMEDOUT,
+}
+
+Shutdown_Manner :: enum c.int {
+	Receive = win.SD_RECEIVE,
+	Send    = win.SD_SEND,
+	Both    = win.SD_BOTH,
+}
+
+Shutdown_Error :: enum c.int {
+	None           = 0,
+	Aborted        = win.WSAECONNABORTED,
+	Reset          = win.WSAECONNRESET,
+	Offline        = win.WSAENETDOWN,
+	Not_Connected  = win.WSAENOTCONN,
+	Not_Socket     = win.WSAENOTSOCK,
+	Invalid_Manner = win.WSAEINVAL,
+}
+
+Socket_Option :: enum c.int {
+	// bool: Whether the address that this socket is bound to can be reused by other sockets.
+	//       This allows you to bypass the cooldown period if a program dies while the socket is bound.
+	Reuse_Address             = win.SO_REUSEADDR,
+
+	// bool: Whether other programs will be inhibited from binding the same endpoint as this socket.
+	Exclusive_Addr_Use        = win.SO_EXCLUSIVEADDRUSE,
+
+	// bool: When true, keepalive packets will be automatically be sent for this connection. TODO: verify this understanding
+	Keep_Alive                = win.SO_KEEPALIVE, 
+
+	// bool: When true, client connections will immediately be sent a TCP/IP RST response, rather than being accepted.
+	Conditional_Accept        = win.SO_CONDITIONAL_ACCEPT,
+
+	// bool: If true, when the socket is closed, but data is still waiting to be sent, discard that data.
+	Dont_Linger               = win.SO_DONTLINGER,
+
+	// bool: When true, 'out-of-band' data sent over the socket will be read by a normal net.recv() call, the same as normal 'in-band' data.
+	Out_Of_Bounds_Data_Inline = win.SO_OOBINLINE,   
+
+	// bool: When true, disables send-coalescing, therefore reducing latency.
+	TCP_Nodelay               = win.TCP_NODELAY, 
+
+	// win.LINGER: Customizes how long (if at all) the socket will remain open when there
+	// is some remaining data waiting to be sent, and net.close() is called.
+	Linger                    = win.SO_LINGER, 
+
+	// win.DWORD: The size, in bytes, of the OS-managed receive-buffer for this socket.
+	Receive_Buffer_Size       = win.SO_RCVBUF, 
+
+	// win.DWORD: The size, in bytes, of the OS-managed send-buffer for this socket.
+	Send_Buffer_Size          = win.SO_SNDBUF,
+
+	// win.DWORD: For blocking sockets, the time in milliseconds to wait for incoming data to be received, before giving up and returning .Timeout.
+	//            For non-blocking sockets, ignored.
+	//            Use a value of zero to potentially wait forever.
+	Receive_Timeout           = win.SO_RCVTIMEO,
+
+	// win.DWORD: For blocking sockets, the time in milliseconds to wait for outgoing data to be sent, before giving up and returning .Timeout.
+	//            For non-blocking sockets, ignored.
+	//            Use a value of zero to potentially wait forever.
+	Send_Timeout              = win.SO_SNDTIMEO,
+
+	// bool: Allow sending to, receiving from, and binding to, a broadcast address.
+	Broadcast                 = win.SO_BROADCAST, 
+}
+
+Socket_Option_Error :: enum c.int {
+	None                               = 0,
+	Linger_Only_Supports_Whole_Seconds = 1,
+
+	// The given value is too big or small to be given to the OS.
+	Value_Out_Of_Range, 
+
+	Network_Subsystem_Failure          = win.WSAENETDOWN,
+	Timeout_When_Keepalive_Set         = win.WSAENETRESET,
+	Invalid_Option_For_Socket          = win.WSAENOPROTOOPT,
+	Reset_When_Keepalive_Set           = win.WSAENOTCONN,
+	Not_Socket                         = win.WSAENOTSOCK,
+}

+ 79 - 0
core/net/interface.odin

@@ -0,0 +1,79 @@
+// +build windows, linux, darwin
+package net
+
+/*
+	Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
+	For other protocols and their features, see subdirectories of this package.
+*/
+
+/*
+	Copyright 2022 Tetralux        <[email protected]>
+	Copyright 2022 Colin Davidson  <[email protected]>
+	Copyright 2022 Jeroen van Rijn <[email protected]>.
+	Made available under Odin's BSD-3 license.
+
+	List of contributors:
+		Tetralux:        Initial implementation
+		Colin Davidson:  Linux platform code, OSX platform code, Odin-native DNS resolver
+		Jeroen van Rijn: Cross platform unification, code style, documentation
+*/
+
+import "core:strings"
+
+MAX_INTERFACE_ENUMERATION_TRIES :: 3
+
+/*
+	`enumerate_interfaces` retrieves a list of network interfaces with their associated properties.
+*/
+enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
+	return _enumerate_interfaces(allocator)
+}
+
+/*
+	`destroy_interfaces` cleans up a list of network interfaces retrieved by e.g. `enumerate_interfaces`.
+*/
+destroy_interfaces :: proc(interfaces: []Network_Interface, allocator := context.allocator) {
+	context.allocator = allocator
+
+	for i in interfaces {
+		delete(i.adapter_name)
+		delete(i.friendly_name)
+		delete(i.description)
+		delete(i.dns_suffix)
+
+		delete(i.physical_address)
+
+		delete(i.unicast)
+		delete(i.multicast)
+		delete(i.anycast)
+		delete(i.gateways)
+	}
+	delete(interfaces, allocator)
+}
+
+/*
+	Turns a slice of bytes (from e.g. `get_adapters_addresses`) into a "XX:XX:XX:..." string.
+*/
+physical_address_to_string :: proc(phy_addr: []u8, allocator := context.allocator) -> (phy_string: string) {
+	context.allocator = allocator
+
+	MAC_HEX := "0123456789ABCDEF"
+
+	if len(phy_addr) == 0 {
+		return ""
+	}
+
+	buf: strings.Builder
+
+	for b, i in phy_addr {
+		if i > 0 {
+			strings.write_rune(&buf, ':')
+		}
+
+		hi := rune(MAC_HEX[b >> 4])
+		lo := rune(MAC_HEX[b & 15])
+		strings.write_rune(&buf, hi)
+		strings.write_rune(&buf, lo)
+	}
+	return strings.to_string(buf)
+}

+ 32 - 0
core/net/interface_darwin.odin

@@ -0,0 +1,32 @@
+package net
+//+build darwin
+
+/*
+	Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
+	For other protocols and their features, see subdirectories of this package.
+*/
+
+/*
+	Copyright 2022 Tetralux        <[email protected]>
+	Copyright 2022 Colin Davidson  <[email protected]>
+	Copyright 2022 Jeroen van Rijn <[email protected]>.
+	Made available under Odin's BSD-3 license.
+
+	List of contributors:
+		Tetralux:        Initial implementation
+		Colin Davidson:  Linux platform code, OSX platform code, Odin-native DNS resolver
+		Jeroen van Rijn: Cross platform unification, code style, documentation
+
+*/
+
+@(private)
+_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
+	context.allocator = allocator
+
+
+	// TODO: Implement. Can probably use the (current) Linux implementation,
+	// which will itself be switched over to talking to the kernel via NETLINK protocol
+	// once we have raw sockets.
+
+	unimplemented()
+}

+ 140 - 0
core/net/interface_linux.odin

@@ -0,0 +1,140 @@
+package net
+//+build linux
+
+/*
+	Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
+	For other protocols and their features, see subdirectories of this package.
+*/
+
+/*
+	Copyright 2022 Tetralux        <[email protected]>
+	Copyright 2022 Colin Davidson  <[email protected]>
+	Copyright 2022 Jeroen van Rijn <[email protected]>.
+	Made available under Odin's BSD-3 license.
+
+	List of contributors:
+		Tetralux:        Initial implementation
+		Colin Davidson:  Linux platform code, OSX platform code, Odin-native DNS resolver
+		Jeroen van Rijn: Cross platform unification, code style, documentation
+
+	This file uses `getifaddrs` libc call to enumerate interfaces.
+	TODO: When we have raw sockets, split off into its own file for Linux so we can use the NETLINK protocol and bypass libc.
+*/
+
+import "core:os"
+import "core:strings"
+
+@(private)
+_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
+	context.allocator = allocator
+
+	head: ^os.ifaddrs
+
+	if res := os._getifaddrs(&head); res < 0 {
+		return {}, .Unable_To_Enumerate_Network_Interfaces
+	}
+
+	/*
+		Unlike Windows, *nix regrettably doesn't return all it knows about an interface in one big struct.
+		We're going to have to iterate over a list and coalesce information as we go.
+	*/
+	ifaces: map[string]^Network_Interface
+	defer delete(ifaces)
+
+	for ifaddr := head; ifaddr != nil; ifaddr = ifaddr.next {
+		adapter_name := string(ifaddr.name)
+
+		/*
+			Check if we have seen this interface name before so we can reuse the `Network_Interface`.
+			Else, create a new one.
+		*/
+		if adapter_name not_in ifaces {
+			ifaces[adapter_name] = new(Network_Interface)
+			ifaces[adapter_name].adapter_name = strings.clone(adapter_name)
+		}
+		iface := ifaces[adapter_name]
+
+		address: Address
+		netmask: Netmask
+
+		if ifaddr.address != nil {
+			switch int(ifaddr.address.sa_family) {
+			case os.AF_INET, os.AF_INET6:
+				address = _sockaddr_basic_to_endpoint(ifaddr.address).address
+
+			case os.AF_PACKET:
+				/*
+					For some obscure reason the 64-bit `getifaddrs` call returns a pointer to a
+					32-bit `RTNL_LINK_STATS` structure, which of course means that tx/rx byte count
+					is truncated beyond usefulness.
+
+					We're not going to retrieve stats now. Instead this serves as a reminder to use
+					the NETLINK protocol for this purpose.
+
+					But in case you were curious:
+						stats := transmute(^os.rtnl_link_stats)ifaddr.data
+						fmt.println(stats)
+				*/
+			case:
+			}
+		}
+
+		if ifaddr.netmask != nil {
+			switch int(ifaddr.netmask.sa_family) {
+			case os.AF_INET, os.AF_INET6:
+			 	netmask = Netmask(_sockaddr_basic_to_endpoint(ifaddr.netmask).address)
+			case:
+			}
+		}
+
+		if ifaddr.broadcast_or_dest != nil && .BROADCAST in ifaddr.flags {
+			switch int(ifaddr.broadcast_or_dest.sa_family) {
+			case os.AF_INET, os.AF_INET6:
+			 	broadcast := _sockaddr_basic_to_endpoint(ifaddr.broadcast_or_dest).address
+			 	append(&iface.multicast, broadcast)
+			case:
+			}
+		}
+
+		if address != nil {
+			lease := Lease{
+				address = address,
+				netmask = netmask,
+			}
+			append(&iface.unicast, lease)
+		}
+
+		/*
+			TODO: Refine this based on the type of adapter.
+		*/
+ 		state := Link_State{}
+
+ 		if .UP in ifaddr.flags {
+ 			state |= {.Up}
+ 		}
+
+ 		if .DORMANT in ifaddr.flags {
+ 			state |= {.Dormant}
+ 		}
+
+ 		if .LOOPBACK in ifaddr.flags {
+ 			state |= {.Loopback}
+ 		}
+		iface.link.state = state
+	}
+
+	/*
+		Free the OS structures.
+	*/
+	os._freeifaddrs(head)
+
+	/*
+		Turn the map into a slice to return.
+	*/
+	_interfaces := make([dynamic]Network_Interface, 0, allocator)
+	for _, iface in ifaces {
+		append(&_interfaces, iface^)
+		free(iface)
+	}
+	return _interfaces[:], {}
+}

+ 177 - 0
core/net/interface_windows.odin

@@ -0,0 +1,177 @@
+package net
+//+build windows
+
+/*
+	Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
+	For other protocols and their features, see subdirectories of this package.
+*/
+
+/*
+	Copyright 2022 Tetralux        <[email protected]>
+	Copyright 2022 Colin Davidson  <[email protected]>
+	Copyright 2022 Jeroen van Rijn <[email protected]>.
+	Made available under Odin's BSD-3 license.
+
+	List of contributors:
+		Tetralux:        Initial implementation
+		Colin Davidson:  Linux platform code, OSX platform code, Odin-native DNS resolver
+		Jeroen van Rijn: Cross platform unification, code style, documentation
+*/
+
+import sys     "core:sys/windows"
+import strings "core:strings"
+
+_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
+	context.allocator = allocator
+
+ 	buf:      []u8
+ 	defer delete(buf)
+
+ 	buf_size: u32
+ 	res:      u32
+
+ 	gaa: for _ in 1..=MAX_INTERFACE_ENUMERATION_TRIES {
+ 	 	res = sys.get_adapters_addresses(
+ 			.Unspecified, // Return both IPv4 and IPv6 adapters.
+			sys.GAA_Flags{
+				.Include_Prefix,               // (XP SP1+) Return a list of IP address prefixes on this adapter. When this flag is set, IP address prefixes are returned for both IPv6 and IPv4 addresses.
+				.Include_Gateways,             // (Vista+) Return the addresses of default gateways.
+				.Include_Tunnel_Binding_Order, // (Vista+) Return the adapter addresses sorted in tunnel binding order.
+			},
+ 			nil,          // Reserved
+ 			(^sys.IP_Adapter_Addresses)(raw_data(buf)),
+ 			&buf_size,
+ 		)
+
+ 	 	switch res {
+ 	 	case 111: // ERROR_BUFFER_OVERFLOW:
+ 	 		delete(buf)
+ 	 		buf = make([]u8, buf_size)
+ 	 	case 0:
+ 	 		break gaa
+ 	 	case:
+ 	 		return {}, Platform_Error(res)
+ 	 	}
+ 	}
+
+ 	if res != 0 {
+ 		return {}, .Unable_To_Enumerate_Network_Interfaces
+ 	}
+
+ 	_interfaces := make([dynamic]Network_Interface, 0, allocator)
+ 	for adapter := (^sys.IP_Adapter_Addresses)(raw_data(buf)); adapter != nil; adapter = adapter.Next {
+		friendly_name, err1 := sys.wstring_to_utf8(sys.wstring(adapter.FriendlyName), 256, allocator)
+		if err1 != nil { return {}, Platform_Error(err1) }
+
+		description, err2 :=  sys.wstring_to_utf8(sys.wstring(adapter.Description), 256, allocator)
+		if err2 != nil { return {}, Platform_Error(err2) }
+
+		dns_suffix, err3  :=  sys.wstring_to_utf8(sys.wstring(adapter.DnsSuffix), 256, allocator)
+		if err3 != nil { return {}, Platform_Error(err3) }
+
+		interface := Network_Interface{
+			adapter_name  = strings.clone(string(adapter.AdapterName)),
+ 			friendly_name = friendly_name,
+ 			description   = description,
+ 			dns_suffix    = dns_suffix,
+
+ 			mtu  = adapter.MTU,
+
+ 			link = {
+				transmit_speed = adapter.TransmitLinkSpeed,
+				receive_speed  = adapter.ReceiveLinkSpeed,
+ 			},
+ 		}
+
+ 		if adapter.PhysicalAddressLength > 0 && adapter.PhysicalAddressLength <= len(adapter.PhysicalAddress) {
+ 			interface.physical_address = physical_address_to_string(adapter.PhysicalAddress[:adapter.PhysicalAddressLength])
+ 		}
+
+ 		for u_addr := (^sys.IP_ADAPTER_UNICAST_ADDRESS_LH)(adapter.FirstUnicastAddress); u_addr != nil; u_addr = u_addr.Next {
+ 			win_addr := parse_socket_address(u_addr.Address)
+
+ 			lease := Lease{
+ 				address = win_addr.address,
+ 				origin  = {
+ 					prefix = Prefix_Origin(u_addr.PrefixOrigin),
+ 					suffix = Suffix_Origin(u_addr.SuffixOrigin),
+ 				},
+ 				lifetime = {
+ 					valid     = u_addr.ValidLifetime,
+ 					preferred = u_addr.PreferredLifetime,
+ 					lease     = u_addr.LeaseLifetime,
+ 				},
+ 				address_duplication = Address_Duplication(u_addr.DadState),
+ 			}
+ 			append(&interface.unicast, lease)
+ 		}
+
+ 		for a_addr := (^sys.IP_ADAPTER_ANYCAST_ADDRESS_XP)(adapter.FirstAnycastAddress); a_addr != nil; a_addr = a_addr.Next {
+ 			addr := parse_socket_address(a_addr.Address)
+ 			append(&interface.anycast, addr.address)
+ 		}
+
+ 		for m_addr := (^sys.IP_ADAPTER_MULTICAST_ADDRESS_XP)(adapter.FirstMulticastAddress); m_addr != nil; m_addr = m_addr.Next {
+ 			addr := parse_socket_address(m_addr.Address)
+ 			append(&interface.multicast, addr.address)
+ 		}
+
+ 		for g_addr := (^sys.IP_ADAPTER_GATEWAY_ADDRESS_LH)(adapter.FirstGatewayAddress); g_addr != nil; g_addr = g_addr.Next {
+ 			addr := parse_socket_address(g_addr.Address)
+ 			append(&interface.gateways, addr.address)
+ 		}
+
+		interface.dhcp_v4 = parse_socket_address(adapter.Dhcpv4Server).address
+		interface.dhcp_v6 = parse_socket_address(adapter.Dhcpv6Server).address
+
+ 		switch adapter.OperStatus {
+ 		case .Up:             interface.link.state = {.Up}
+ 		case .Down:           interface.link.state = {.Down}
+ 		case .Testing:        interface.link.state = {.Testing}
+ 		case .Dormant:        interface.link.state = {.Dormant}
+ 		case .NotPresent:     interface.link.state = {.Not_Present}
+ 		case .LowerLayerDown: interface.link.state = {.Lower_Layer_Down}
+ 		case .Unknown:        fallthrough
+ 		case:                 interface.link.state = {}
+ 		}
+
+ 		interface.tunnel_type = Tunnel_Type(adapter.TunnelType)
+
+ 		append(&_interfaces, interface)
+ 	}
+
+	return _interfaces[:], {}
+}
+
+/*
+	Interpret SOCKET_ADDRESS as an Address
+*/
+parse_socket_address :: proc(addr_in: sys.SOCKET_ADDRESS) -> (addr: Endpoint) {
+	if addr_in.lpSockaddr == nil {
+		return // Empty or invalid address type
+	}
+
+	sock := addr_in.lpSockaddr^
+
+	switch sock.sa_family {
+	case u16(sys.AF_INET):
+		win_addr := cast(^sys.sockaddr_in)addr_in.lpSockaddr
+		port     := int(win_addr.sin_port)
+		return Endpoint {
+			address = IP4_Address(transmute([4]byte)win_addr.sin_addr),
+			port    = port,
+		}
+
+	case u16(sys.AF_INET6):
+		win_addr := cast(^sys.sockaddr_in6)addr_in.lpSockaddr
+		port     := int(win_addr.sin6_port)
+		return Endpoint {
+			address = IP6_Address(transmute([8]u16be)win_addr.sin6_addr),
+			port = port,
+		}
+
+
+	case: return // Empty or invalid address type
+	}
+	unreachable()
+}

+ 176 - 0
core/net/socket.odin

@@ -0,0 +1,176 @@
+// +build windows, linux, darwin
+package net
+
+/*
+	Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
+	For other protocols and their features, see subdirectories of this package.
+*/
+
+/*
+	Copyright 2022-2023 Tetralux        <[email protected]>
+	Copyright 2022-2023 Colin Davidson  <[email protected]>
+	Copyright 2022-2023 Jeroen van Rijn <[email protected]>.
+	Made available under Odin's BSD-3 license.
+
+	List of contributors:
+		Tetralux:        Initial implementation
+		Colin Davidson:  Linux platform code, OSX platform code, Odin-native DNS resolver
+		Jeroen van Rijn: Cross platform unification, code style, documentation
+*/
+
+any_socket_to_socket :: proc(socket: Any_Socket) -> Socket {
+	switch s in socket {
+	case TCP_Socket:  return Socket(s)
+	case UDP_Socket:  return Socket(s)
+	case:
+		// TODO(tetra): Bluetooth, Raw
+		return Socket({})
+	}
+}
+
+/*
+    Expects both hostname and port to be present in the `hostname_and_port` parameter, either as:
+    `a.host.name:9999`, or as `1.2.3.4:9999`, or IP6 equivalent.
+
+    Calls `parse_hostname_or_endpoint` and `resolve`, then `dial_tcp_from_endpoint`.
+*/
+dial_tcp_from_hostname_and_port_string :: proc(hostname_and_port: string, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
+	target := parse_hostname_or_endpoint(hostname_and_port) or_return
+	switch t in target {
+	case Endpoint:
+		return dial_tcp_from_endpoint(t, options)
+	case Host:
+		if t.port == 0 {
+			return 0, .Port_Required
+		}
+		ep4, ep6 := resolve(t.hostname) or_return
+		ep := ep4 if ep4.address != nil else ep6 // NOTE(tetra): We don't know what family the server uses, so we just default to IP4.
+		ep.port = t.port
+		return dial_tcp_from_endpoint(ep, options)
+	}
+	unreachable()
+}
+
+/*
+    Expects the `hostname` as a string and `port` as a `int`.
+    `parse_hostname_or_endpoint` is called and the `hostname` will be resolved into an IP.
+
+    If a `hostname` of form `a.host.name:9999` is given, the port will be ignored in favor of the explicit `port` param.
+*/
+dial_tcp_from_hostname_with_port_override :: proc(hostname: string, port: int, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
+	target := parse_hostname_or_endpoint(hostname) or_return
+	switch t in target {
+	case Endpoint:
+		return dial_tcp_from_endpoint({t.address, port}, options)
+	case Host:
+		if port == 0 {
+			return 0, .Port_Required
+		}
+		ep4, ep6 := resolve(t.hostname) or_return
+		ep := ep4 if ep4.address != nil else ep6 // NOTE(tetra): We don't know what family the server uses, so we just default to IP4.
+		ep.port = port
+		return dial_tcp_from_endpoint(ep, options)
+	}
+	unreachable()
+}
+
+// Dial from an Address
+dial_tcp_from_address_and_port :: proc(address: Address, port: int, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
+	return dial_tcp_from_endpoint({address, port}, options)
+}
+
+dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
+	return _dial_tcp_from_endpoint(endpoint, options)
+}
+
+dial_tcp :: proc{
+	dial_tcp_from_endpoint,
+	dial_tcp_from_address_and_port,
+	dial_tcp_from_hostname_and_port_string,
+	dial_tcp_from_hostname_with_port_override,
+}
+
+create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
+	return _create_socket(family, protocol)
+}
+
+bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
+	return _bind(socket, ep)
+}
+
+/*
+	This type of socket becomes bound when you try to send data.
+	It is likely what you want if you want to send data unsolicited.
+
+	This is like a client TCP socket, except that it can send data to any remote endpoint without needing to establish a connection first.
+*/
+make_unbound_udp_socket :: proc(family: Address_Family) -> (socket: UDP_Socket, err: Network_Error) {
+	sock := create_socket(family, .UDP) or_return
+	socket = sock.(UDP_Socket)
+	return
+}
+
+/*
+	This type of socket is bound immediately, which enables it to receive data on the port.
+	Since it's UDP, it's also able to send data without receiving any first.
+
+	This is like a listening TCP socket, except that data packets can be sent and received without needing to establish a connection first.
+	The `bound_address` is the address of the network interface that you want to use, or a loopback address if you don't care which to use.
+*/
+make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (socket: UDP_Socket, err: Network_Error) {
+	socket = make_unbound_udp_socket(family_from_address(bound_address)) or_return
+	bind(socket, {bound_address, port}) or_return
+	return
+}
+
+listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, err: Network_Error) {
+	assert(backlog > 0 && backlog < int(max(i32)))
+
+	return _listen_tcp(interface_endpoint, backlog)
+}
+
+accept_tcp :: proc(socket: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
+	return _accept_tcp(socket, options)
+}
+
+close :: proc(socket: Any_Socket) {
+	_close(socket)
+}
+
+recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
+	return _recv_tcp(socket, buf)
+}
+
+recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
+	return _recv_udp(socket, buf)
+}
+
+recv :: proc{recv_tcp, recv_udp}
+
+/*
+	Repeatedly sends data until the entire buffer is sent.
+	If a send fails before all data is sent, returns the amount sent up to that point.
+*/
+send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
+	return _send_tcp(socket, buf)
+}
+
+/*
+	Sends a single UDP datagram packet.
+
+	Datagrams are limited in size; attempting to send more than this limit at once will result in a Message_Too_Long error.
+	UDP packets are not guarenteed to be received in order.
+*/
+send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
+	return _send_udp(socket, buf, to)
+}
+
+send :: proc{send_tcp, send_udp}
+
+shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
+	return _shutdown(socket, manner)
+}
+
+set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
+	return _set_option(socket, option, value, loc)
+}

+ 348 - 0
core/net/socket_darwin.odin

@@ -0,0 +1,348 @@
+package net
+// +build darwin
+
+/*
+	Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
+	For other protocols and their features, see subdirectories of this package.
+*/
+
+/*
+	Copyright 2022 Tetralux        <[email protected]>
+	Copyright 2022 Colin Davidson  <[email protected]>
+	Copyright 2022 Jeroen van Rijn <[email protected]>.
+	Made available under Odin's BSD-3 license.
+
+	List of contributors:
+		Tetralux:        Initial implementation
+		Colin Davidson:  Linux platform code, OSX platform code, Odin-native DNS resolver
+		Jeroen van Rijn: Cross platform unification, code style, documentation
+*/
+
+import "core:c"
+import "core:os"
+import "core:time"
+
+Socket_Option :: enum c.int {
+	Reuse_Address             = c.int(os.SO_REUSEADDR),
+	Keep_Alive                = c.int(os.SO_KEEPALIVE),
+	Out_Of_Bounds_Data_Inline = c.int(os.SO_OOBINLINE),
+	TCP_Nodelay               = c.int(os.TCP_NODELAY),
+	Linger                    = c.int(os.SO_LINGER),
+	Receive_Buffer_Size       = c.int(os.SO_RCVBUF),
+	Send_Buffer_Size          = c.int(os.SO_SNDBUF),
+	Receive_Timeout           = c.int(os.SO_RCVTIMEO),
+	Send_Timeout              = c.int(os.SO_SNDTIMEO),
+}
+
+@(private)
+_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
+	c_type, c_protocol, c_family: int
+
+	switch family {
+	case .IP4:  c_family = os.AF_INET
+	case .IP6:  c_family = os.AF_INET6
+	case:
+		unreachable()
+	}
+
+	switch protocol {
+	case .TCP:  c_type = os.SOCK_STREAM; c_protocol = os.IPPROTO_TCP
+	case .UDP:  c_type = os.SOCK_DGRAM;  c_protocol = os.IPPROTO_UDP
+	case:
+		unreachable()
+	}
+
+	sock, ok := os.socket(c_family, c_type, c_protocol)
+	if ok != os.ERROR_NONE {
+		err = Create_Socket_Error(ok)
+		return
+	}
+
+	switch protocol {
+	case .TCP:  return TCP_Socket(sock), nil
+	case .UDP:  return UDP_Socket(sock), nil
+	case:
+		unreachable()
+	}
+}
+
+@(private)
+_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) {
+	if endpoint.port == 0 {
+		return 0, .Port_Required
+	}
+
+	family := family_from_endpoint(endpoint)
+	sock := create_socket(family, .TCP) or_return
+	skt = sock.(TCP_Socket)
+
+	// NOTE(tetra): This is so that if we crash while the socket is open, we can
+	// bypass the cooldown period, and allow the next run of the program to
+	// use the same address immediately.
+	_ = set_option(skt, .Reuse_Address, true)
+
+	sockaddr := _endpoint_to_sockaddr(endpoint)
+	res := os.connect(os.Socket(skt), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len))
+	if res != os.ERROR_NONE {
+		err = Dial_Error(res)
+		return
+	}
+
+	return
+}
+
+@(private)
+_bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
+	sockaddr := _endpoint_to_sockaddr(ep)
+	s := any_socket_to_socket(skt)
+	res := os.bind(os.Socket(s), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len))
+	if res != os.ERROR_NONE {
+		err = Bind_Error(res)
+	}
+	return
+}
+
+@(private)
+_listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) {
+	assert(backlog > 0 && i32(backlog) < max(i32))
+
+	family := family_from_endpoint(interface_endpoint)
+	sock := create_socket(family, .TCP) or_return
+	skt = sock.(TCP_Socket)
+
+	// NOTE(tetra): This is so that if we crash while the socket is open, we can
+	// bypass the cooldown period, and allow the next run of the program to
+	// use the same address immediately.
+	//
+	// TODO(tetra, 2022-02-15): Confirm that this doesn't mean other processes can hijack the address!
+	set_option(sock, .Reuse_Address, true) or_return
+
+	bind(sock, interface_endpoint) or_return
+
+	res := os.listen(os.Socket(skt), backlog)
+	if res != os.ERROR_NONE {
+		err = Listen_Error(res)
+		return
+	}
+
+	return
+}
+
+@(private)
+_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
+	sockaddr: os.SOCKADDR_STORAGE_LH
+	sockaddrlen := c.int(size_of(sockaddr))
+
+	client_sock, ok := os.accept(os.Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen)
+	if ok != os.ERROR_NONE {
+		err = Accept_Error(ok)
+		return
+	}
+	client = TCP_Socket(client_sock)
+	source = _sockaddr_to_endpoint(&sockaddr)
+	return
+}
+
+@(private)
+_close :: proc(skt: Any_Socket) {
+	s := any_socket_to_socket(skt)
+	os.close(os.Handle(os.Socket(s)))
+}
+
+@(private)
+_recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
+	if len(buf) <= 0 {
+		return
+	}
+	res, ok := os.recv(os.Socket(skt), buf, 0)
+	if ok != os.ERROR_NONE {
+		err = TCP_Recv_Error(ok)
+		return
+	}
+	return int(res), nil
+}
+
+@(private)
+_recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
+	if len(buf) <= 0 {
+		return
+	}
+
+	from: os.SOCKADDR_STORAGE_LH
+	fromsize := c.int(size_of(from))
+	res, ok := os.recvfrom(os.Socket(skt), buf, 0, cast(^os.SOCKADDR) &from, &fromsize)
+	if ok != os.ERROR_NONE {
+		err = UDP_Recv_Error(ok)
+		return
+	}
+
+	bytes_read = int(res)
+	remote_endpoint = _sockaddr_to_endpoint(&from)
+	return
+}
+
+@(private)
+_send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
+	for bytes_written < len(buf) {
+		limit := min(int(max(i32)), len(buf) - bytes_written)
+		remaining := buf[bytes_written:][:limit]
+		res, ok := os.send(os.Socket(skt), remaining, 0)
+		if ok != os.ERROR_NONE {
+			err = TCP_Send_Error(ok)
+			return
+		}
+		bytes_written += int(res)
+	}
+	return
+}
+
+@(private)
+_send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
+	toaddr := _endpoint_to_sockaddr(to)
+	for bytes_written < len(buf) {
+		limit := min(1<<31, len(buf) - bytes_written)
+		remaining := buf[bytes_written:][:limit]
+		res, ok := os.sendto(os.Socket(skt), remaining, 0, cast(^os.SOCKADDR)&toaddr, i32(toaddr.len))
+		if ok != os.ERROR_NONE {
+			err = UDP_Send_Error(ok)
+			return
+		}
+		bytes_written += int(res)
+	}
+	return
+}
+
+@(private)
+_shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
+	s := any_socket_to_socket(skt)
+	res := os.shutdown(os.Socket(s), int(manner))
+	if res != os.ERROR_NONE {
+		return Shutdown_Error(res)
+	}
+	return
+}
+
+@(private)
+_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
+	level := os.SOL_SOCKET if option != .TCP_Nodelay else os.IPPROTO_TCP
+
+	// NOTE(tetra, 2022-02-15): On Linux, you cannot merely give a single byte for a bool;
+	//  it _has_ to be a b32.
+	//  I haven't tested if you can give more than that.
+	bool_value: b32
+	int_value: i32
+	timeval_value: os.Timeval
+
+	ptr: rawptr
+	len: os.socklen_t
+
+	switch option {
+	case
+		.Reuse_Address,
+		.Keep_Alive,
+		.Out_Of_Bounds_Data_Inline,
+		.TCP_Nodelay:
+		// TODO: verify whether these are options or not on Linux
+		// .Broadcast,
+		// .Conditional_Accept,
+		// .Dont_Linger:
+			switch x in value {
+			case bool, b8:
+				x2 := x
+				bool_value = b32((^bool)(&x2)^)
+			case b16:
+				bool_value = b32(x)
+			case b32:
+				bool_value = b32(x)
+			case b64:
+				bool_value = b32(x)
+			case:
+				panic("set_option() value must be a boolean here", loc)
+			}
+			ptr = &bool_value
+			len = size_of(bool_value)
+	case
+		.Linger,
+		.Send_Timeout,
+		.Receive_Timeout:
+			t, ok := value.(time.Duration)
+			if !ok do panic("set_option() value must be a time.Duration here", loc)
+
+			nanos := time.duration_nanoseconds(t)
+			timeval_value.nanoseconds = int(nanos % 1e9)
+			timeval_value.seconds = (nanos - i64(timeval_value.nanoseconds)) / 1e9
+
+			ptr = &timeval_value
+			len = size_of(timeval_value)
+	case
+		.Receive_Buffer_Size,
+		.Send_Buffer_Size:
+			// TODO: check for out of range values and return .Value_Out_Of_Range?
+			switch i in value {
+			case i8, u8:   i2 := i; int_value = os.socklen_t((^u8)(&i2)^)
+			case i16, u16: i2 := i; int_value = os.socklen_t((^u16)(&i2)^)
+			case i32, u32: i2 := i; int_value = os.socklen_t((^u32)(&i2)^)
+			case i64, u64: i2 := i; int_value = os.socklen_t((^u64)(&i2)^)
+			case i128, u128: i2 := i; int_value = os.socklen_t((^u128)(&i2)^)
+			case int, uint: i2 := i; int_value = os.socklen_t((^uint)(&i2)^)
+			case:
+				panic("set_option() value must be an integer here", loc)
+			}
+			ptr = &int_value
+			len = size_of(int_value)
+	}
+
+	skt := any_socket_to_socket(s)
+	res := os.setsockopt(os.Socket(skt), int(level), int(option), ptr, len)
+	if res != os.ERROR_NONE {
+		return Socket_Option_Error(res)
+	}
+
+	return nil
+}
+
+@private
+_endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: os.SOCKADDR_STORAGE_LH) {
+	switch a in ep.address {
+	case IP4_Address:
+		(^os.sockaddr_in)(&sockaddr)^ = os.sockaddr_in {
+			sin_port = u16be(ep.port),
+			sin_addr = transmute(os.in_addr) a,
+			sin_family = u8(os.AF_INET),
+			sin_len = size_of(os.sockaddr_in),
+		}
+		return
+	case IP6_Address:
+		(^os.sockaddr_in6)(&sockaddr)^ = os.sockaddr_in6 {
+			sin6_port = u16be(ep.port),
+			sin6_addr = transmute(os.in6_addr) a,
+			sin6_family = u8(os.AF_INET6),
+			sin6_len = size_of(os.sockaddr_in6),
+		}
+		return
+	}
+	unreachable()
+}
+
+@private
+_sockaddr_to_endpoint :: proc(native_addr: ^os.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) {
+	switch native_addr.family {
+	case u8(os.AF_INET):
+		addr := cast(^os.sockaddr_in) native_addr
+		port := int(addr.sin_port)
+		ep = Endpoint {
+			address = IP4_Address(transmute([4]byte) addr.sin_addr),
+			port = port,
+		}
+	case u8(os.AF_INET6):
+		addr := cast(^os.sockaddr_in6) native_addr
+		port := int(addr.sin6_port)
+		ep = Endpoint {
+			address = IP6_Address(transmute([8]u16be) addr.sin6_addr),
+			port = port,
+		}
+	case:
+		panic("native_addr is neither IP4 or IP6 address")
+	}
+	return
+}

+ 384 - 0
core/net/socket_linux.odin

@@ -0,0 +1,384 @@
+package net
+// +build linux
+
+/*
+	Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
+	For other protocols and their features, see subdirectories of this package.
+*/
+
+/*
+	Copyright 2022 Tetralux        <[email protected]>
+	Copyright 2022 Colin Davidson  <[email protected]>
+	Copyright 2022 Jeroen van Rijn <[email protected]>.
+	Made available under Odin's BSD-3 license.
+
+	List of contributors:
+		Tetralux:        Initial implementation
+		Colin Davidson:  Linux platform code, OSX platform code, Odin-native DNS resolver
+		Jeroen van Rijn: Cross platform unification, code style, documentation
+*/
+
+import "core:c"
+import "core:os"
+import "core:time"
+
+Socket_Option :: enum c.int {
+	Reuse_Address             = c.int(os.SO_REUSEADDR),
+	Keep_Alive                = c.int(os.SO_KEEPALIVE),
+	Out_Of_Bounds_Data_Inline = c.int(os.SO_OOBINLINE),
+	TCP_Nodelay               = c.int(os.TCP_NODELAY),
+	Linger                    = c.int(os.SO_LINGER),
+	Receive_Buffer_Size       = c.int(os.SO_RCVBUF),
+	Send_Buffer_Size          = c.int(os.SO_SNDBUF),
+	Receive_Timeout           = c.int(os.SO_RCVTIMEO_NEW),
+	Send_Timeout              = c.int(os.SO_SNDTIMEO_NEW),
+}
+
+@(private)
+_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
+	c_type, c_protocol, c_family: int
+
+	switch family {
+	case .IP4:  c_family = os.AF_INET
+	case .IP6:  c_family = os.AF_INET6
+	case:
+		unreachable()
+	}
+
+	switch protocol {
+	case .TCP:  c_type = os.SOCK_STREAM; c_protocol = os.IPPROTO_TCP
+	case .UDP:  c_type = os.SOCK_DGRAM;  c_protocol = os.IPPROTO_UDP
+	case:
+		unreachable()
+	}
+
+	sock, ok := os.socket(c_family, c_type, c_protocol)
+	if ok != os.ERROR_NONE {
+		err = Create_Socket_Error(ok)
+		return
+	}
+
+	switch protocol {
+	case .TCP:  return TCP_Socket(sock), nil
+	case .UDP:  return UDP_Socket(sock), nil
+	case:
+		unreachable()
+	}
+}
+
+@(private)
+_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) {
+	if endpoint.port == 0 {
+		return 0, .Port_Required
+	}
+
+	family := family_from_endpoint(endpoint)
+	sock := create_socket(family, .TCP) or_return
+	skt = sock.(TCP_Socket)
+
+	// NOTE(tetra): This is so that if we crash while the socket is open, we can
+	// bypass the cooldown period, and allow the next run of the program to
+	// use the same address immediately.
+	_ = set_option(skt, .Reuse_Address, true)
+
+	sockaddr := _endpoint_to_sockaddr(endpoint)
+	res := os.connect(os.Socket(skt), (^os.SOCKADDR)(&sockaddr), size_of(sockaddr))
+	if res != os.ERROR_NONE {
+		err = Dial_Error(res)
+		return
+	}
+
+	if options.no_delay {
+		_ = _set_option(sock, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored
+	}
+
+	return
+}
+
+@(private)
+_bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
+	sockaddr := _endpoint_to_sockaddr(ep)
+	s := any_socket_to_socket(skt)
+	res := os.bind(os.Socket(s), (^os.SOCKADDR)(&sockaddr), size_of(sockaddr))
+	if res != os.ERROR_NONE {
+		err = Bind_Error(res)
+	}
+	return
+}
+
+@(private)
+_listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) {
+	assert(backlog > 0 && i32(backlog) < max(i32))
+
+	family := family_from_endpoint(interface_endpoint)
+	sock := create_socket(family, .TCP) or_return
+	skt = sock.(TCP_Socket)
+
+	// NOTE(tetra): This is so that if we crash while the socket is open, we can
+	// bypass the cooldown period, and allow the next run of the program to
+	// use the same address immediately.
+	//
+	// TODO(tetra, 2022-02-15): Confirm that this doesn't mean other processes can hijack the address!
+	set_option(sock, .Reuse_Address, true) or_return
+
+	bind(sock, interface_endpoint) or_return
+
+	res := os.listen(os.Socket(skt), backlog)
+	if res != os.ERROR_NONE {
+		err = Listen_Error(res)
+		return
+	}
+
+	return
+}
+
+@(private)
+_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
+	sockaddr: os.SOCKADDR_STORAGE_LH
+	sockaddrlen := c.int(size_of(sockaddr))
+
+	client_sock, ok := os.accept(os.Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen)
+	if ok != os.ERROR_NONE {
+		err = Accept_Error(ok)
+		return
+	}
+	client = TCP_Socket(client_sock)
+	source = _sockaddr_storage_to_endpoint(&sockaddr)
+	if options.no_delay {
+		_ = _set_option(client, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored
+	}
+	return
+}
+
+@(private)
+_close :: proc(skt: Any_Socket) {
+	s := any_socket_to_socket(skt)
+	os.close(os.Handle(os.Socket(s)))
+}
+
+@(private)
+_recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
+	if len(buf) <= 0 {
+		return
+	}
+	res, ok := os.recv(os.Socket(skt), buf, 0)
+	if ok != os.ERROR_NONE {
+		err = TCP_Recv_Error(ok)
+		return
+	}
+	return int(res), nil
+}
+
+@(private)
+_recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
+	if len(buf) <= 0 {
+		return
+	}
+
+	from: os.SOCKADDR_STORAGE_LH = ---
+	fromsize := c.int(size_of(from))
+
+	// NOTE(tetra): On Linux, if the buffer is too small to fit the entire datagram payload, the rest is silently discarded,
+	// and no error is returned.
+	// However, if you pass MSG_TRUNC here, 'res' will be the size of the incoming message, rather than how much was read.
+	// We can use this fact to detect this condition and return .Buffer_Too_Small.
+	res, ok := os.recvfrom(os.Socket(skt), buf, os.MSG_TRUNC, cast(^os.SOCKADDR) &from, &fromsize)
+	if ok != os.ERROR_NONE {
+		err = UDP_Recv_Error(ok)
+		return
+	}
+
+	bytes_read = int(res)
+	remote_endpoint = _sockaddr_storage_to_endpoint(&from)
+
+	if bytes_read > len(buf) {
+		// NOTE(tetra): The buffer has been filled, with a partial message.
+		bytes_read = len(buf)
+		err = .Buffer_Too_Small
+	}
+
+	return
+}
+
+@(private)
+_send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
+	for bytes_written < len(buf) {
+		limit := min(int(max(i32)), len(buf) - bytes_written)
+		remaining := buf[bytes_written:][:limit]
+		res, ok := os.send(os.Socket(skt), remaining, 0)
+		if ok != os.ERROR_NONE {
+			err = TCP_Send_Error(ok)
+			return
+		}
+		bytes_written += int(res)
+	}
+	return
+}
+
+@(private)
+_send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
+	toaddr := _endpoint_to_sockaddr(to)
+	res, os_err := os.sendto(os.Socket(skt), buf, 0, cast(^os.SOCKADDR) &toaddr, size_of(toaddr))
+	if os_err != os.ERROR_NONE {
+		err = UDP_Send_Error(os_err)
+		return
+	}
+	bytes_written = int(res)
+	return
+}
+
+@(private)
+_shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
+	s := any_socket_to_socket(skt)
+	res := os.shutdown(os.Socket(s), int(manner))
+	if res != os.ERROR_NONE {
+		return Shutdown_Error(res)
+	}
+	return
+}
+
+@(private)
+_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
+	level := os.SOL_SOCKET if option != .TCP_Nodelay else os.IPPROTO_TCP
+
+	// NOTE(tetra, 2022-02-15): On Linux, you cannot merely give a single byte for a bool;
+	//  it _has_ to be a b32.
+	//  I haven't tested if you can give more than that.
+	bool_value: b32
+	int_value: i32
+	timeval_value: os.Timeval
+
+	ptr: rawptr
+	len: os.socklen_t
+
+	switch option {
+	case
+		.Reuse_Address,
+		.Keep_Alive,
+		.Out_Of_Bounds_Data_Inline,
+		.TCP_Nodelay:
+		// TODO: verify whether these are options or not on Linux
+		// .Broadcast,
+		// .Conditional_Accept,
+		// .Dont_Linger:
+			switch x in value {
+			case bool, b8:
+				x2 := x
+				bool_value = b32((^bool)(&x2)^)
+			case b16:
+				bool_value = b32(x)
+			case b32:
+				bool_value = b32(x)
+			case b64:
+				bool_value = b32(x)
+			case:
+				panic("set_option() value must be a boolean here", loc)
+			}
+			ptr = &bool_value
+			len = size_of(bool_value)
+	case
+		.Linger,
+		.Send_Timeout,
+		.Receive_Timeout:
+			t, ok := value.(time.Duration)
+			if !ok do panic("set_option() value must be a time.Duration here", loc)
+
+			nanos := time.duration_nanoseconds(t)
+			timeval_value.nanoseconds = int(nanos % 1e9)
+			timeval_value.seconds = (nanos - i64(timeval_value.nanoseconds)) / 1e9
+
+			ptr = &timeval_value
+			len = size_of(timeval_value)
+	case
+		.Receive_Buffer_Size,
+		.Send_Buffer_Size:
+			// TODO: check for out of range values and return .Value_Out_Of_Range?
+			switch i in value {
+			case   i8,   u8: i2 := i; int_value = os.socklen_t((^u8)(&i2)^)
+			case  i16,  u16: i2 := i; int_value = os.socklen_t((^u16)(&i2)^)
+			case  i32,  u32: i2 := i; int_value = os.socklen_t((^u32)(&i2)^)
+			case  i64,  u64: i2 := i; int_value = os.socklen_t((^u64)(&i2)^)
+			case i128, u128: i2 := i; int_value = os.socklen_t((^u128)(&i2)^)
+			case  int, uint: i2 := i; int_value = os.socklen_t((^uint)(&i2)^)
+			case:
+				panic("set_option() value must be an integer here", loc)
+			}
+			ptr = &int_value
+			len = size_of(int_value)
+	}
+
+	skt := any_socket_to_socket(s)
+	res := os.setsockopt(os.Socket(skt), int(level), int(option), ptr, len)
+	if res != os.ERROR_NONE {
+		return Socket_Option_Error(res)
+	}
+
+	return nil
+}
+
+@(private)
+_endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: os.SOCKADDR_STORAGE_LH) {
+	switch a in ep.address {
+	case IP4_Address:
+		(^os.sockaddr_in)(&sockaddr)^ = os.sockaddr_in {
+			sin_port = u16be(ep.port),
+			sin_addr = transmute(os.in_addr) a,
+			sin_family = u16(os.AF_INET),
+		}
+		return
+	case IP6_Address:
+		(^os.sockaddr_in6)(&sockaddr)^ = os.sockaddr_in6 {
+			sin6_port = u16be(ep.port),
+			sin6_addr = transmute(os.in6_addr) a,
+			sin6_family = u16(os.AF_INET6),
+		}
+		return
+	}
+	unreachable()
+}
+
+@(private)
+_sockaddr_storage_to_endpoint :: proc(native_addr: ^os.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) {
+	switch native_addr.ss_family {
+	case u16(os.AF_INET):
+		addr := cast(^os.sockaddr_in) native_addr
+		port := int(addr.sin_port)
+		ep = Endpoint {
+			address = IP4_Address(transmute([4]byte) addr.sin_addr),
+			port = port,
+		}
+	case u16(os.AF_INET6):
+		addr := cast(^os.sockaddr_in6) native_addr
+		port := int(addr.sin6_port)
+		ep = Endpoint {
+			address = IP6_Address(transmute([8]u16be) addr.sin6_addr),
+			port = port,
+		}
+	case:
+		panic("native_addr is neither IP4 or IP6 address")
+	}
+	return
+}
+
+@(private)
+_sockaddr_basic_to_endpoint :: proc(native_addr: ^os.SOCKADDR) -> (ep: Endpoint) {
+	switch native_addr.sa_family {
+	case u16(os.AF_INET):
+		addr := cast(^os.sockaddr_in) native_addr
+		port := int(addr.sin_port)
+		ep = Endpoint {
+			address = IP4_Address(transmute([4]byte) addr.sin_addr),
+			port = port,
+		}
+	case u16(os.AF_INET6):
+		addr := cast(^os.sockaddr_in6) native_addr
+		port := int(addr.sin6_port)
+		ep = Endpoint {
+			address = IP6_Address(transmute([8]u16be) addr.sin6_addr),
+			port = port,
+		}
+	case:
+		panic("native_addr is neither IP4 or IP6 address")
+	}
+	return
+}

+ 355 - 0
core/net/socket_windows.odin

@@ -0,0 +1,355 @@
+package net
+// +build windows
+
+/*
+	Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
+	For other protocols and their features, see subdirectories of this package.
+*/
+
+/*
+	Copyright 2022 Tetralux        <[email protected]>
+	Copyright 2022 Colin Davidson  <[email protected]>
+	Copyright 2022 Jeroen van Rijn <[email protected]>.
+	Made available under Odin's BSD-3 license.
+
+	List of contributors:
+		Tetralux:        Initial implementation
+		Colin Davidson:  Linux platform code, OSX platform code, Odin-native DNS resolver
+		Jeroen van Rijn: Cross platform unification, code style, documentation
+*/
+
+import "core:c"
+import win "core:sys/windows"
+import "core:time"
+
+@(init, private)
+ensure_winsock_initialized :: proc() {
+	win.ensure_winsock_initialized()
+}
+
+@(private)
+_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
+	c_type, c_protocol, c_family: c.int
+
+	switch family {
+	case .IP4:  c_family = win.AF_INET
+	case .IP6:  c_family = win.AF_INET6
+	case:
+		unreachable()
+	}
+
+	switch protocol {
+	case .TCP:  c_type = win.SOCK_STREAM; c_protocol = win.IPPROTO_TCP
+	case .UDP:  c_type = win.SOCK_DGRAM;  c_protocol = win.IPPROTO_UDP
+	case:
+		unreachable()
+	}
+
+	sock := win.socket(c_family, c_type, c_protocol)
+	if sock == win.INVALID_SOCKET {
+		err = Create_Socket_Error(win.WSAGetLastError())
+		return
+	}
+
+	switch protocol {
+	case .TCP:  return TCP_Socket(sock), nil
+	case .UDP:  return UDP_Socket(sock), nil
+	case:
+		unreachable()
+	}
+}
+
+@(private)
+_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
+	if endpoint.port == 0 {
+		err = .Port_Required
+		return
+	}
+
+	family := family_from_endpoint(endpoint)
+	sock := create_socket(family, .TCP) or_return
+	socket = sock.(TCP_Socket)
+
+	// NOTE(tetra): This is so that if we crash while the socket is open, we can
+	// bypass the cooldown period, and allow the next run of the program to
+	// use the same address immediately.
+	_ = set_option(socket, .Reuse_Address, true)
+
+	sockaddr := _endpoint_to_sockaddr(endpoint)
+	res := win.connect(win.SOCKET(socket), &sockaddr, size_of(sockaddr))
+	if res < 0 {
+		err = Dial_Error(win.WSAGetLastError())
+		return
+	}
+
+	if options.no_delay {
+		_ = set_option(sock, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored
+	}
+
+	return
+}
+
+@(private)
+_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
+	sockaddr := _endpoint_to_sockaddr(ep)
+	sock := any_socket_to_socket(socket)
+	res := win.bind(win.SOCKET(sock), &sockaddr, size_of(sockaddr))
+	if res < 0 {
+		err = Bind_Error(win.WSAGetLastError())
+	}
+	return
+}
+
+@(private)
+_listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, err: Network_Error) {
+	family := family_from_endpoint(interface_endpoint)
+	sock := create_socket(family, .TCP) or_return
+	socket = sock.(TCP_Socket)
+
+	// NOTE(tetra): While I'm not 100% clear on it, my understanding is that this will
+	// prevent hijacking of the server's endpoint by other applications.
+	set_option(socket, .Exclusive_Addr_Use, true) or_return
+
+	bind(sock, interface_endpoint) or_return
+
+	if res := win.listen(win.SOCKET(socket), i32(backlog)); res == win.SOCKET_ERROR {
+		err = Listen_Error(win.WSAGetLastError())
+	}
+	return
+}
+
+@(private)
+_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
+	for {
+		sockaddr: win.SOCKADDR_STORAGE_LH
+		sockaddrlen := c.int(size_of(sockaddr))
+		client_sock := win.accept(win.SOCKET(sock), &sockaddr, &sockaddrlen)
+		if int(client_sock) == win.SOCKET_ERROR {
+			e := win.WSAGetLastError()
+			if e == win.WSAECONNRESET {
+				// NOTE(tetra): Reset just means that a client that connection immediately lost the connection.
+				// There's no need to concern the user with this, so we handle it for them.
+				// On Linux, this error isn't possible in the first place according the man pages, so we also
+				// can do this to match the behaviour.
+				continue
+			}
+			err = Accept_Error(e)
+			return
+		}
+		client = TCP_Socket(client_sock)
+		source = _sockaddr_to_endpoint(&sockaddr)
+		if options.no_delay {
+			_ = set_option(client, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored
+		}
+		return
+	}
+}
+
+@(private)
+_close :: proc(socket: Any_Socket) {
+	if s := any_socket_to_socket(socket); s != {} {
+		win.closesocket(win.SOCKET(s))
+	}
+}
+
+@(private)
+_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
+	if len(buf) <= 0 {
+		return
+	}
+	res := win.recv(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0)
+	if res < 0 {
+		err = TCP_Recv_Error(win.WSAGetLastError())
+		return
+	}
+	return int(res), nil
+}
+
+@(private)
+_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
+	if len(buf) <= 0 {
+		return
+	}
+
+	from: win.SOCKADDR_STORAGE_LH
+	fromsize := c.int(size_of(from))
+	res := win.recvfrom(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0, &from, &fromsize)
+	if res < 0 {
+		err = UDP_Recv_Error(win.WSAGetLastError())
+		return
+	}
+
+	bytes_read = int(res)
+	remote_endpoint = _sockaddr_to_endpoint(&from)
+	return
+}
+
+@(private)
+_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
+	for bytes_written < len(buf) {
+		limit := min(int(max(i32)), len(buf) - bytes_written)
+		remaining := buf[bytes_written:]
+		res := win.send(win.SOCKET(socket), raw_data(remaining), c.int(limit), 0)
+		if res < 0 {
+			err = TCP_Send_Error(win.WSAGetLastError())
+			return
+		}
+		bytes_written += int(res)
+	}
+	return
+}
+
+@(private)
+_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
+	if len(buf) > int(max(c.int)) {
+		// NOTE(tetra): If we don't guard this, we'll return (0, nil) instead, which is misleading.
+		err = .Message_Too_Long
+		return
+	}
+	toaddr := _endpoint_to_sockaddr(to)
+	res := win.sendto(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0, &toaddr, size_of(toaddr))
+	if res < 0 {
+		err = UDP_Send_Error(win.WSAGetLastError())
+		return
+	}
+	bytes_written = int(res)
+	return
+}
+
+@(private)
+_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
+	s := any_socket_to_socket(socket)
+	res := win.shutdown(win.SOCKET(s), c.int(manner))
+	if res < 0 {
+		return Shutdown_Error(win.WSAGetLastError())
+	}
+	return
+}
+
+@(private)
+_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
+	level := win.SOL_SOCKET if option != .TCP_Nodelay else win.IPPROTO_TCP
+
+	bool_value: b32
+	int_value: i32
+	linger_value: win.LINGER
+
+	ptr: rawptr
+	len: c.int
+
+	switch option {
+	case
+		.Reuse_Address,
+		.Exclusive_Addr_Use,
+		.Keep_Alive,
+		.Out_Of_Bounds_Data_Inline,
+		.TCP_Nodelay,
+		.Broadcast,
+		.Conditional_Accept,
+		.Dont_Linger:
+			switch x in value {
+			case bool, b8:
+				x2 := x
+				bool_value = b32((^bool)(&x2)^)
+			case b16:
+				bool_value = b32(x)
+			case b32:
+				bool_value = b32(x)
+			case b64:
+				bool_value = b32(x)
+			case:
+				panic("set_option() value must be a boolean here", loc)
+			}
+			ptr = &bool_value
+			len = size_of(bool_value)
+	case .Linger:
+		t, ok := value.(time.Duration)
+		if !ok do panic("set_option() value must be a time.Duration here", loc)
+
+		num_secs := i64(time.duration_seconds(t))
+		if time.Duration(num_secs * 1e9) != t do return .Linger_Only_Supports_Whole_Seconds
+		if num_secs > i64(max(u16)) do return .Value_Out_Of_Range
+		linger_value.l_onoff = 1
+		linger_value.l_linger = c.ushort(num_secs)
+
+		ptr = &linger_value
+		len = size_of(linger_value)
+	case
+		.Receive_Timeout,
+		.Send_Timeout:
+			t, ok := value.(time.Duration)
+			if !ok do panic("set_option() value must be a time.Duration here", loc)
+
+			int_value = i32(time.duration_milliseconds(t))
+			ptr = &int_value
+			len = size_of(int_value)
+
+	case
+		.Receive_Buffer_Size,
+		.Send_Buffer_Size:
+			switch i in value {
+			case  i8,    u8: i2 := i; int_value = c.int((^u8)(&i2)^)
+			case  i16,  u16: i2 := i; int_value = c.int((^u16)(&i2)^)
+			case  i32,  u32: i2 := i; int_value = c.int((^u32)(&i2)^)
+			case  i64,  u64: i2 := i; int_value = c.int((^u64)(&i2)^)
+			case i128, u128: i2 := i; int_value = c.int((^u128)(&i2)^)
+			case  int, uint: i2 := i; int_value = c.int((^uint)(&i2)^)
+			case:
+				panic("set_option() value must be an integer here", loc)
+			}
+			ptr = &int_value
+			len = size_of(int_value)
+	}
+
+	socket := any_socket_to_socket(s)
+	res := win.setsockopt(win.SOCKET(socket), c.int(level), c.int(option), ptr, len)
+	if res < 0 {
+		return Socket_Option_Error(win.WSAGetLastError())
+	}
+
+	return nil
+}
+
+@(private)
+_endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: win.SOCKADDR_STORAGE_LH) {
+	switch a in ep.address {
+	case IP4_Address:
+		(^win.sockaddr_in)(&sockaddr)^ = win.sockaddr_in {
+			sin_port = u16be(win.USHORT(ep.port)),
+			sin_addr = transmute(win.in_addr) a,
+			sin_family = u16(win.AF_INET),
+		}
+		return
+	case IP6_Address:
+		(^win.sockaddr_in6)(&sockaddr)^ = win.sockaddr_in6 {
+			sin6_port = u16be(win.USHORT(ep.port)),
+			sin6_addr = transmute(win.in6_addr) a,
+			sin6_family = u16(win.AF_INET6),
+		}
+		return
+	}
+	unreachable()
+}
+
+@(private)
+_sockaddr_to_endpoint :: proc(native_addr: ^win.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) {
+	switch native_addr.ss_family {
+	case u16(win.AF_INET):
+		addr := cast(^win.sockaddr_in) native_addr
+		port := int(addr.sin_port)
+		ep = Endpoint {
+			address = IP4_Address(transmute([4]byte) addr.sin_addr),
+			port = port,
+		}
+	case u16(win.AF_INET6):
+		addr := cast(^win.sockaddr_in6) native_addr
+		port := int(addr.sin6_port)
+		ep = Endpoint {
+			address = IP6_Address(transmute([8]u16be) addr.sin6_addr),
+			port = port,
+		}
+	case:
+		panic("native_addr is neither IP4 or IP6 address")
+	}
+	return
+}

+ 235 - 0
core/net/url.odin

@@ -0,0 +1,235 @@
+package net
+/*
+	Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
+	For other protocols and their features, see subdirectories of this package.
+*/
+
+/*
+	Copyright 2022 Tetralux        <[email protected]>
+	Copyright 2022 Colin Davidson  <[email protected]>
+	Copyright 2022 Jeroen van Rijn <[email protected]>.
+	Made available under Odin's BSD-3 license.
+
+	List of contributors:
+		Tetralux:        Initial implementation
+		Colin Davidson:  Linux platform code, OSX platform code, Odin-native DNS resolver
+		Jeroen van Rijn: Cross platform unification, code style, documentation
+*/
+
+import "core:strings"
+import "core:strconv"
+import "core:unicode/utf8"
+import "core:mem"
+
+split_url :: proc(url: string, allocator := context.allocator) -> (scheme, host, path: string, queries: map[string]string) {
+	s := url
+
+	i := strings.last_index(s, "://")
+	if i >= 0 {
+		scheme = s[:i]
+		s = s[i+3:]
+	}
+
+	i = strings.index(s, "?")
+	if i != -1 {
+		query_str := s[i+1:]
+		s = s[:i]
+		if query_str != "" {
+			queries_parts := strings.split(query_str, "&")
+			queries = make(map[string]string, len(queries_parts), allocator)
+			for q in queries_parts {
+				parts := strings.split(q, "=")
+				switch len(parts) {
+				case 1:  queries[parts[0]] = ""        // NOTE(tetra): Query not set to anything, was but present.
+				case 2:  queries[parts[0]] = parts[1]  // NOTE(tetra): Query set to something.
+				case:    break
+				}
+			}
+		}
+	}
+
+	i = strings.index(s, "/")
+	if i == -1 {
+		host = s
+		path = "/"
+	} else {
+		host = s[:i]
+		path = s[i:]
+	}
+
+	return
+}
+
+join_url :: proc(scheme, host, path: string, queries: map[string]string, allocator := context.allocator) -> string {
+	using strings
+
+	b := builder_make(allocator)
+	builder_grow(&b, len(scheme) + 3 + len(host) + 1 + len(path))
+
+	write_string(&b, scheme)
+	write_string(&b, "://")
+	write_string(&b, trim_space(host))
+
+	if path != "" {
+		if path[0] != '/' do write_string(&b, "/")
+		write_string(&b, trim_space(path))
+	}
+
+
+	if len(queries) > 0 do write_string(&b, "?")
+	for query_name, query_value in queries {
+		write_string(&b, query_name)
+		if query_value != "" {
+			write_string(&b, "=")
+			write_string(&b, query_value)
+		}
+	}
+
+	return to_string(b)
+}
+
+percent_encode :: proc(s: string, allocator := context.allocator) -> string {
+	using strings
+
+	b := builder_make(allocator)
+	builder_grow(&b, len(s) + 16) // NOTE(tetra): A reasonable number to allow for the number of things we need to escape.
+
+	for ch in s {
+		switch ch {
+		case 'A'..='Z', 'a'..='z', '0'..='9', '-', '_', '.', '~':
+			write_rune(&b, ch)
+		case:
+			bytes, n := utf8.encode_rune(ch)
+			for byte in bytes[:n] {
+				buf: [2]u8 = ---
+				t := strconv.append_int(buf[:], i64(byte), 16)
+				write_rune(&b, '%')
+				write_string(&b, t)
+			}
+		}
+	}
+
+	return to_string(b)
+}
+
+percent_decode :: proc(encoded_string: string, allocator := context.allocator) -> (decoded_string: string, ok: bool) {
+	using strings
+
+	b := builder_make(allocator)
+	builder_grow(&b, len(encoded_string))
+	defer if !ok do builder_destroy(&b)
+
+	stack_buf: [4]u8
+	pending := mem.buffer_from_slice(stack_buf[:])
+	s := encoded_string
+
+	for len(s) > 0 {
+		i := index_rune(s, '%')
+		if i == -1 {
+			write_string(&b, s) // no '%'s; the string is already decoded
+			break
+		}
+
+		write_string(&b, s[:i])
+		s = s[i:]
+
+		if len(s) == 0 do return // percent without anything after it
+		s = s[1:]
+
+		if s[0] == '%' {
+			write_rune(&b, '%')
+			s = s[1:]
+			continue
+		}
+
+		if len(s) < 2 do return // percent without encoded value
+
+		n: int
+		n, _ = strconv.parse_int(s[:2], 16)
+		switch n {
+		case 0x20:  write_rune(&b, ' ')
+		case 0x21:  write_rune(&b, '!')
+		case 0x23:  write_rune(&b, '#')
+		case 0x24:  write_rune(&b, '$')
+		case 0x25:  write_rune(&b, '%')
+		case 0x26:  write_rune(&b, '&')
+		case 0x27:  write_rune(&b, '\'')
+		case 0x28:  write_rune(&b, '(')
+		case 0x29:  write_rune(&b, ')')
+		case 0x2A:  write_rune(&b, '*')
+		case 0x2B:  write_rune(&b, '+')
+		case 0x2C:  write_rune(&b, ',')
+		case 0x2F:  write_rune(&b, '/')
+		case 0x3A:  write_rune(&b, ':')
+		case 0x3B:  write_rune(&b, ';')
+		case 0x3D:  write_rune(&b, '=')
+		case 0x3F:  write_rune(&b, '?')
+		case 0x40:  write_rune(&b, '@')
+		case 0x5B:  write_rune(&b, '[')
+		case 0x5D:  write_rune(&b, ']')
+		case:
+			// utf-8 bytes
+			// TODO(tetra): Audit this - 4 bytes???
+			append(&pending, s[0])
+			append(&pending, s[1])
+			if len(pending) == 4 {
+				r, _ := utf8.decode_rune(pending[:])
+				write_rune(&b, r)
+				clear(&pending)
+			}
+		}
+		s = s[2:]
+	}
+
+	ok = true
+	decoded_string = to_string(b)
+	return
+}
+
+//
+// TODO: encoding/base64 is broken...
+//
+
+// // TODO(tetra): The whole "table" stuff in encoding/base64 is too impenetrable for me to
+// // make a table for this ... sigh - so this'll do for now.
+/*
+base64url_encode :: proc(data: []byte, allocator := context.allocator) -> string {
+	out := transmute([]byte) base64.encode(data, base64.ENC_TABLE, allocator);
+	for b, i in out {
+		switch b {
+		case '+': out[i] = '-';
+		case '/': out[i] = '_';
+		}
+	}
+	i := len(out)-1;
+	for ; i >= 0; i -= 1 {
+		if out[i] != '=' do break;
+	}
+	return string(out[:i+1]);
+}
+
+base64url_decode :: proc(s: string, allocator := context.allocator) -> []byte {
+	size := len(s);
+	padding := 0;
+	for size % 4 != 0 {
+		size += 1; // TODO: SPEED
+		padding += 1;
+	}
+
+	temp := make([]byte, size, context.temp_allocator);
+	copy(temp, transmute([]byte) s);
+
+	for b, i in temp {
+		switch b {
+		case '-': temp[i] = '+';
+		case '_': temp[i] = '/';
+		}
+	}
+
+	for in 0..padding-1 {
+		temp[len(temp)-1] = '=';
+	}
+
+	return base64.decode(string(temp), base64.DEC_TABLE, allocator);
+}
+*/

+ 1 - 1
core/os/dir_freebsd.odin

@@ -50,7 +50,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
 			continue
 		}
 
-		fullpath := make([]byte, len(dirpath)+1+len(filename))
+		fullpath := make([]byte, len(dirpath)+1+len(filename), context.temp_allocator)
 		copy(fullpath, dirpath)
 		copy(fullpath[len(dirpath):], "/")
 		copy(fullpath[len(dirpath)+1:], filename)

+ 2 - 0
core/os/dir_linux.odin

@@ -2,6 +2,7 @@ package os
 
 import "core:strings"
 import "core:mem"
+import "core:runtime"
 
 read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) {
 	dirp: Dir
@@ -51,6 +52,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
 			continue
 		}
 
+		runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 		fullpath := strings.join( []string{ dirpath, filename }, "/", context.temp_allocator)
 		defer delete(fullpath, context.temp_allocator)
 

+ 6 - 2
core/os/dir_windows.odin

@@ -2,6 +2,7 @@ package os
 
 import win32 "core:sys/windows"
 import "core:strings"
+import "core:runtime"
 
 read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) {
 	find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW) -> (fi: File_Info) {
@@ -65,13 +66,16 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
 		n = -1
 		size = 100
 	}
-	dfi := make([dynamic]File_Info, 0, size)
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 
 	wpath: []u16
-	wpath, err = cleanpath_from_handle_u16(fd)
+	wpath, err = cleanpath_from_handle_u16(fd, context.temp_allocator)
 	if len(wpath) == 0 || err != ERROR_NONE {
 		return
 	}
+
+	dfi := make([dynamic]File_Info, 0, size)
+
 	wpath_search := make([]u16, len(wpath)+3, context.temp_allocator)
 	copy(wpath_search, wpath)
 	wpath_search[len(wpath)+0] = '\\'

+ 4 - 0
core/os/env_windows.odin

@@ -1,6 +1,7 @@
 package os
 
 import win32 "core:sys/windows"
+import "core:runtime"
 
 // 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
@@ -18,6 +19,8 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
 			return "", false
 		}
 	}
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
+
 	b := make([dynamic]u16, n, context.temp_allocator)
 	n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b)))
 	if n == 0 {
@@ -87,6 +90,7 @@ environ :: proc(allocator := context.allocator) -> []string {
 
 // clear_env deletes all environment variables
 clear_env :: proc() {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	envs := environ(context.temp_allocator)
 	for env in envs {
 		for j in 1..<len(env) {

+ 12 - 0
core/os/file_windows.odin

@@ -2,6 +2,7 @@ package os
 
 import win32 "core:sys/windows"
 import "core:intrinsics"
+import "core:runtime"
 import "core:unicode/utf16"
 
 is_path_separator :: proc(c: byte) -> bool {
@@ -327,6 +328,7 @@ get_std_handle :: proc "contextless" (h: uint) -> Handle {
 
 
 exists :: proc(path: string) -> bool {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
 	attribs := win32.GetFileAttributesW(wpath)
 
@@ -334,6 +336,7 @@ exists :: proc(path: string) -> bool {
 }
 
 is_file :: proc(path: string) -> bool {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
 	attribs := win32.GetFileAttributesW(wpath)
 
@@ -344,6 +347,7 @@ is_file :: proc(path: string) -> bool {
 }
 
 is_dir :: proc(path: string) -> bool {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
 	attribs := win32.GetFileAttributesW(wpath)
 
@@ -359,6 +363,8 @@ is_dir :: proc(path: string) -> bool {
 get_current_directory :: proc(allocator := context.allocator) -> string {
 	win32.AcquireSRWLockExclusive(&cwd_lock)
 
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
+
 	sz_utf16 := win32.GetCurrentDirectoryW(0, nil)
 	dir_buf_wstr := make([]u16, sz_utf16, context.temp_allocator) // the first time, it _includes_ the NUL.
 
@@ -387,6 +393,7 @@ set_current_directory :: proc(path: string) -> (err: Errno) {
 
 
 change_directory :: proc(path: string) -> (err: Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
 
 	if !win32.SetCurrentDirectoryW(wpath) {
@@ -396,6 +403,7 @@ change_directory :: proc(path: string) -> (err: Errno) {
 }
 
 make_directory :: proc(path: string, mode: u32 = 0) -> (err: Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	// Mode is unused on Windows, but is needed on *nix
 	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
 
@@ -407,6 +415,7 @@ make_directory :: proc(path: string, mode: u32 = 0) -> (err: Errno) {
 
 
 remove_directory :: proc(path: string) -> (err: Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
 
 	if !win32.RemoveDirectoryW(wpath) {
@@ -479,12 +488,14 @@ fix_long_path :: proc(path: string) -> string {
 
 
 link :: proc(old_name, new_name: string) -> (err: Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	n := win32.utf8_to_wstring(fix_long_path(new_name))
 	o := win32.utf8_to_wstring(fix_long_path(old_name))
 	return Errno(win32.CreateHardLinkW(n, o, nil))
 }
 
 unlink :: proc(path: string) -> (err: Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
 
 	if !win32.DeleteFileW(wpath) {
@@ -496,6 +507,7 @@ unlink :: proc(path: string) -> (err: Errno) {
 
 
 rename :: proc(old_path, new_path: string) -> (err: Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	from := win32.utf8_to_wstring(old_path, context.temp_allocator)
 	to := win32.utf8_to_wstring(new_path, context.temp_allocator)
 

+ 5 - 3
core/os/os2/env.odin

@@ -1,10 +1,12 @@
 package os2
 
+import "core:runtime"
+
 // 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 {
+get_env :: proc(key: string, allocator: runtime.Allocator) -> string {
 	value, _ := lookup_env(key, allocator)
 	return value
 }
@@ -13,7 +15,7 @@ get_env :: proc(key: string, allocator := context.allocator) -> string {
 // 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) {
+lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
 	return _lookup_env(key, allocator)
 }
 
@@ -36,7 +38,7 @@ clear_env :: proc() {
 
 // 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 {
+environ :: proc(allocator: runtime.Allocator) -> []string {
 	return _environ(allocator)
 }
 

+ 4 - 2
core/os/os2/env_linux.odin

@@ -1,7 +1,9 @@
 //+private
 package os2
 
-_get_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
+import "core:runtime"
+
+_get_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
 	//TODO
 	return
 }
@@ -20,7 +22,7 @@ _clear_env :: proc() {
 	//TODO
 }
 
-_environ :: proc(allocator := context.allocator) -> []string {
+_environ :: proc(allocator: runtime.Allocator) -> []string {
 	//TODO
 	return nil
 }

+ 13 - 1
core/os/os2/env_windows.odin

@@ -65,7 +65,19 @@ _environ :: proc(allocator: runtime.Allocator) -> []string {
 	}
 	defer win32.FreeEnvironmentStringsW(envs)
 
-	r := make([dynamic]string, 0, 50, allocator)
+	n := 0
+	for from, i, p := 0, 0, envs; true; i += 1 {
+		c := ([^]u16)(p)[i]
+		if c == 0 {
+			if i <= from {
+				break
+			}
+			n += 1
+			from = i + 1
+		}
+	}
+
+	r := make([dynamic]string, 0, n, allocator)
 	for from, i, p := 0, 0, envs; true; i += 1 {
 		c := ([^]u16)(p)[i]
 		if c == 0 {

+ 7 - 14
core/os/os2/file_linux.odin

@@ -39,10 +39,8 @@ _file_allocator :: proc() -> runtime.Allocator {
 }
 
 _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (^File, Error) {
-	name_cstr, allocated := _name_to_cstring(name)
-	defer if allocated {
-		delete(name_cstr)
-	}
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+	name_cstr := _name_to_cstring(name)
 
 	flags_i: int
 	switch flags & O_RDONLY|O_WRONLY|O_RDWR {
@@ -254,7 +252,7 @@ _symlink :: proc(old_name, new_name: string) -> Error {
 	return _ok_or_error(unix.sys_symlink(old_name_cstr, new_name_cstr))
 }
 
-_read_link_cstr :: proc(name_cstr: cstring, allocator := context.allocator) -> (string, Error) {
+_read_link_cstr :: proc(name_cstr: cstring, allocator: runtime.Allocator) -> (string, Error) {
 	bufsz : uint = 256
 	buf := make([]byte, bufsz, allocator)
 	for {
@@ -272,7 +270,7 @@ _read_link_cstr :: proc(name_cstr: cstring, allocator := context.allocator) -> (
 	}
 }
 
-_read_link :: proc(name: string, allocator := context.allocator) -> (string, Error) {
+_read_link :: proc(name: string, allocator: runtime.Allocator) -> (string, Error) {
 	name_cstr, allocated := _name_to_cstring(name)
 	defer if allocated {
 		delete(name_cstr)
@@ -411,12 +409,7 @@ _is_dir_fd :: proc(fd: int) -> bool {
 // defined as 512, however, it is well known that paths can exceed that limit.
 // So, in theory you could have a path larger than the entire temp_allocator's
 // buffer. Therefor, any large paths will use context.allocator.
-_name_to_cstring :: proc(name: string) -> (cname: cstring, allocated: bool) {
-	if len(name) > _CSTRING_NAME_HEAP_THRESHOLD {
-		cname = strings.clone_to_cstring(name)
-		allocated = true
-		return
-	}
-	cname = strings.clone_to_cstring(name, context.temp_allocator)
-	return
+@(private="file")
+_temp_name_to_cstring :: proc(name: string) -> (cname: cstring) {
+	return strings.clone_to_cstring(name, context.temp_allocator)
 }

+ 2 - 1
core/os/os2/file_util.odin

@@ -1,6 +1,7 @@
 package os2
 
 import "core:mem"
+import "core:runtime"
 import "core:strconv"
 import "core:unicode/utf8"
 
@@ -74,7 +75,7 @@ read_ptr :: proc(f: ^File, data: rawptr, len: int) -> (n: int, err: Error) {
 
 
 
-read_entire_file :: proc(name: string, allocator := context.allocator) -> (data: []byte, err: Error) {
+read_entire_file :: proc(name: string, allocator: runtime.Allocator) -> (data: []byte, err: Error) {
 	f, ferr := open(name)
 	if ferr != nil {
 		return nil, ferr

+ 1 - 1
core/os/os2/path_linux.odin

@@ -229,7 +229,7 @@ _setwd :: proc(dir: string) -> Error {
 	return _ok_or_error(unix.sys_chdir(dir_cstr))
 }
 
-_get_full_path :: proc(fd: int, allocator := context.allocator) -> string {
+_get_full_path :: proc(fd: int, allocator: runtime.Allocator) -> string {
 	PROC_FD_PATH :: "/proc/self/fd/"
 
 	buf: [32]u8

+ 3 - 3
core/os/os2/stat_linux.odin

@@ -83,7 +83,7 @@ _Stat :: struct {
 }
 
 
-_fstat :: proc(f: ^File, allocator := context.allocator) -> (File_Info, Error) {
+_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
 	return _fstat_internal(f.impl.fd, allocator)
 }
 
@@ -111,7 +111,7 @@ _fstat_internal :: proc(fd: int, allocator: runtime.Allocator) -> (File_Info, Er
 }
 
 // NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath
-_stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) {
+_stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
 	name_cstr, allocated := _name_to_cstring(name)
 	defer if allocated {
 		delete(name_cstr)
@@ -125,7 +125,7 @@ _stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error
 	return _fstat_internal(fd, allocator)
 }
 
-_lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) {
+_lstat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
 	name_cstr, allocated := _name_to_cstring(name)
 	defer if allocated {
 		delete(name_cstr)

+ 294 - 32
core/os/os_darwin.odin

@@ -67,6 +67,7 @@ ENOPROTOOPT:		Errno : 42		/* Protocol not available */
 EPROTONOSUPPORT:	Errno : 43		/* Protocol not supported */
 ESOCKTNOSUPPORT:	Errno : 44		/* Socket type not supported */
 ENOTSUP:			Errno : 45		/* Operation not supported */
+EOPNOTSUPP::		ENOTSUP
 EPFNOSUPPORT:		Errno : 46		/* Protocol family not supported */
 EAFNOSUPPORT:		Errno : 47		/* Address family not supported by protocol family */
 EADDRINUSE:			Errno : 48		/* Address already in use */
@@ -179,6 +180,93 @@ RTLD_NODELETE :: 0x80
 RTLD_NOLOAD   :: 0x10
 RTLD_FIRST    :: 0x100
 
+SOL_SOCKET :: 0xFFFF
+
+SOCK_STREAM    :: 1
+SOCK_DGRAM     :: 2
+SOCK_RAW       :: 3
+SOCK_RDM       :: 4
+SOCK_SEQPACKET :: 5
+
+SO_DEBUG       :: 0x0001
+SO_ACCEPTCONN  :: 0x0002
+SO_REUSEADDR   :: 0x0004
+SO_KEEPALIVE   :: 0x0008
+SO_DONTROUTE   :: 0x0010
+SO_BROADCAST   :: 0x0020
+SO_USELOOPBACK :: 0x0040
+SO_LINGER      :: 0x0080
+SO_OOBINLINE   :: 0x0100
+SO_REUSEPORT   :: 0x0200
+SO_TIMESTAMP   :: 0x0400
+
+SO_DONTTRUNC   :: 0x2000
+SO_WANTMORE    :: 0x4000
+SO_WANTOOBFLAG :: 0x8000
+SO_SNDBUF      :: 0x1001
+SO_RCVBUF      :: 0x1002
+SO_SNDLOWAT	   :: 0x1003
+SO_RCVLOWAT    :: 0x1004
+SO_SNDTIMEO    :: 0x1005
+SO_RCVTIMEO    :: 0x1006
+SO_ERROR       :: 0x1007
+SO_TYPE        :: 0x1008
+SO_PRIVSTATE   :: 0x1009
+SO_NREAD       :: 0x1020
+SO_NKE         :: 0x1021
+
+AF_UNSPEC     :: 0
+AF_LOCAL      :: 1
+AF_UNIX       :: AF_LOCAL
+AF_INET       :: 2
+AF_IMPLINK    :: 3
+AF_PUP        :: 4
+AF_CHAOS      :: 5
+AF_NS         :: 6
+AF_ISO        :: 7
+AF_OSI        :: AF_ISO
+AF_ECMA       :: 8
+AF_DATAKIT    :: 9
+AF_CCITT      :: 10
+AF_SNA        :: 11
+AF_DECnet     :: 12
+AF_DLI        :: 13
+AF_LAT        :: 14
+AF_HYLINK     :: 15
+AF_APPLETALK  :: 16
+AF_ROUTE	  :: 17
+AF_LINK		  :: 18
+pseudo_AF_XTP :: 19
+AF_COIP		  :: 20
+AF_CNT		  :: 21
+pseudo_AF_RTIP :: 22
+AF_IPX         :: 23
+AF_SIP         :: 24
+pseudo_AF_PIP  :: 25
+pseudo_AF_BLUE :: 26
+AF_NDRV        :: 27
+AF_ISDN        :: 28
+AF_E164        :: AF_ISDN
+pseudo_AF_KEY  :: 29
+AF_INET6       :: 30
+AF_NATM        :: 31
+AF_SYSTEM      :: 32
+AF_NETBIOS     :: 33
+AF_PPP         :: 34
+
+TCP_NODELAY	:: 0x01
+TCP_MAXSEG	:: 0x02
+TCP_NOPUSH	:: 0x04
+TCP_NOOPT	:: 0x08
+
+IPPROTO_ICMP :: 1
+IPPROTO_TCP  :: 6
+IPPROTO_UDP  :: 17
+
+SHUT_RD   :: 0
+SHUT_WR   :: 1
+SHUT_RDWR :: 2
+
 
 // "Argv" arguments converted to Odin strings
 args := _alloc_command_line_arguments()
@@ -224,6 +312,58 @@ Dirent :: struct {
 
 Dir :: distinct rawptr // DIR*
 
+SOCKADDR :: struct #packed {
+	len: c.char,
+	family: c.char,
+	sa_data: [14]c.char,
+}
+
+SOCKADDR_STORAGE_LH :: struct #packed {
+	len: c.char,
+	family: c.char,
+	__ss_pad1: [6]c.char,
+	__ss_align: i64,
+	__ss_pad2: [112]c.char,
+}
+
+sockaddr_in :: struct #packed {
+	sin_len: c.char,
+	sin_family: c.char,
+	sin_port: u16be,
+	sin_addr: in_addr,
+	sin_zero: [8]c.char,
+}
+
+sockaddr_in6 :: struct #packed {
+	sin6_len: c.char,
+	sin6_family: c.char,
+	sin6_port: u16be,
+	sin6_flowinfo: c.uint,
+	sin6_addr: in6_addr,
+	sin6_scope_id: c.uint,
+}
+
+in_addr :: struct #packed {
+	s_addr: u32,
+}
+
+in6_addr :: struct #packed {
+	s6_addr: [16]u8,
+}
+
+Timeval :: struct {
+	seconds: i64,
+	nanoseconds: int,
+}
+
+Linger :: struct {
+	onoff: int,
+	linger: int,
+}
+
+Socket    :: distinct int
+socklen_t :: c.int
+
 // File type
 S_IFMT   :: 0o170000 // Type of file mask
 S_IFIFO  :: 0o010000 // Named pipe (fifo)
@@ -277,8 +417,10 @@ foreign libc {
 
 	@(link_name="open")             _unix_open          :: proc(path: cstring, flags: i32, mode: u16) -> Handle ---
 	@(link_name="close")            _unix_close         :: proc(handle: Handle) -> c.int ---
-	@(link_name="read")             _unix_read          :: proc(handle: Handle, buffer: rawptr, count: int) -> int ---
-	@(link_name="write")            _unix_write         :: proc(handle: Handle, buffer: rawptr, count: int) -> int ---
+	@(link_name="read")             _unix_read          :: proc(handle: Handle, buffer: rawptr, count: c.size_t) -> int ---
+	@(link_name="write")            _unix_write         :: proc(handle: Handle, buffer: rawptr, count: c.size_t) -> int ---
+	@(link_name="pread")            _unix_pread         :: proc(handle: Handle, buffer: rawptr, count: c.size_t, offset: i64) -> int ---
+	@(link_name="pwrite")           _unix_pwrite        :: proc(handle: Handle, buffer: rawptr, count: c.size_t, offset: i64) -> int ---
 	@(link_name="lseek")            _unix_lseek         :: proc(fs: Handle, offset: int, whence: int) -> int ---
 	@(link_name="gettid")           _unix_gettid        :: proc() -> u64 ---
 	@(link_name="getpagesize")      _unix_getpagesize   :: proc() -> i32 ---
@@ -316,6 +458,18 @@ foreign libc {
 	@(link_name="strerror") _darwin_string_error :: proc(num : c.int) -> cstring ---
 	@(link_name="sysctlbyname") _sysctlbyname    :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int ---
 
+	@(link_name="socket")           _unix_socket        :: proc(domain: int, type: int, protocol: int) -> int ---
+	@(link_name="listen")           _unix_listen        :: proc(socket: int, backlog: int) -> int ---
+	@(link_name="accept")           _unix_accept        :: proc(socket: int, addr: rawptr, addr_len: rawptr) -> int ---
+	@(link_name="connect")          _unix_connect       :: proc(socket: int, addr: rawptr, addr_len: socklen_t) -> int ---
+	@(link_name="bind")             _unix_bind          :: proc(socket: int, addr: rawptr, addr_len: socklen_t) -> int ---
+	@(link_name="setsockopt")       _unix_setsockopt    :: proc(socket: int, level: int, opt_name: int, opt_val: rawptr, opt_len: socklen_t) -> int ---
+	@(link_name="recvfrom")         _unix_recvfrom      :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int, addr: rawptr, addr_len: ^socklen_t) -> c.ssize_t ---
+	@(link_name="recv")             _unix_recv          :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int) -> c.ssize_t ---
+	@(link_name="sendto")           _unix_sendto        :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int, addr: rawptr, addr_len: socklen_t) -> c.ssize_t ---
+	@(link_name="send")             _unix_send          :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int) -> c.ssize_t ---
+	@(link_name="shutdown")         _unix_shutdown      :: proc(socket: int, how: int) -> int ---
+
 	@(link_name="exit")    _unix_exit :: proc(status: c.int) -> ! ---
 }
 
@@ -353,6 +507,7 @@ open :: proc(path: string, flags: int = O_RDWR, mode: int = 0) -> (Handle, Errno
 		flags = O_RDONLY
 	}
 
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	handle := _unix_open(cstr, i32(flags), u16(mode))
 	if handle == -1 {
@@ -385,45 +540,51 @@ close :: proc(fd: Handle) -> bool {
 @(private)
 MAX_RW :: 0x7fffffff // The limit on Darwin is max(i32), trying to read/write more than that fails.
 
-write :: proc(fd: Handle, data: []u8) -> (int, Errno) {
-	assert(fd != -1)
-
-	bytes_total := len(data)
-	bytes_written_total := 0
-
-	for bytes_written_total < bytes_total {
-		bytes_to_write := min(bytes_total - bytes_written_total, MAX_RW)
-		slice := data[bytes_written_total:bytes_written_total + bytes_to_write]
-		bytes_written := _unix_write(fd, raw_data(slice), bytes_to_write)
-		if bytes_written == -1 {
-			return bytes_written_total, 1
-		}
-		bytes_written_total += bytes_written
+write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+	if len(data) == 0 {
+		return 0, ERROR_NONE
 	}
 
-	return bytes_written_total, 0
+	bytes_written := _unix_write(fd, raw_data(data), c.size_t(len(data)))
+	if bytes_written < 0 {
+		return -1, Errno(get_last_error())
+	}
+	return bytes_written, ERROR_NONE
 }
 
 read :: proc(fd: Handle, data: []u8) -> (int, Errno) {
-	assert(fd != -1)
+	if len(data) == 0 {
+		return 0, ERROR_NONE
+	}
+
+	bytes_read := _unix_read(fd, raw_data(data), c.size_t(len(data)))
+	if bytes_read < 0 {
+		return -1, Errno(get_last_error())
+	}
+	return bytes_read, ERROR_NONE
+}
+read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
+	if len(data) == 0 {
+		return 0, ERROR_NONE
+	}
 
-	bytes_total := len(data)
-	bytes_read_total := 0
+	bytes_read := _unix_pread(fd, raw_data(data), c.size_t(len(data)), offset)
+	if bytes_read < 0 {
+		return -1, Errno(get_last_error())
+	}
+	return bytes_read, ERROR_NONE
+}
 
-	for bytes_read_total < bytes_total {
-		bytes_to_read := min(bytes_total - bytes_read_total, MAX_RW)
-		slice := data[bytes_read_total:bytes_read_total + bytes_to_read]
-		bytes_read := _unix_read(fd, raw_data(slice), bytes_to_read)
-		if bytes_read == -1 {
-			return bytes_read_total, 1
-		}
-		if bytes_read == 0 {
-			break
-		}
-		bytes_read_total += bytes_read
+write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
+	if len(data) == 0 {
+		return 0, ERROR_NONE
 	}
 
-	return bytes_read_total, 0
+	bytes_written := _unix_pwrite(fd, raw_data(data), c.size_t(len(data)), offset)
+	if bytes_written < 0 {
+		return -1, Errno(get_last_error())
+	}
+	return bytes_written, ERROR_NONE
 }
 
 seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
@@ -508,24 +669,28 @@ is_file :: proc {is_file_path, is_file_handle}
 is_dir :: proc {is_dir_path, is_dir_handle}
 
 exists :: proc(path: string) -> bool {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cpath := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_access(cpath, O_RDONLY)
 	return res == 0
 }
 
 rename :: proc(old: string, new: string) -> bool {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	old_cstr := strings.clone_to_cstring(old, context.temp_allocator)
 	new_cstr := strings.clone_to_cstring(new, context.temp_allocator)
 	return _unix_rename(old_cstr, new_cstr) != -1
 }
 
 remove :: proc(path: string) -> bool {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	return _unix_remove(path_cstr) != -1
 }
 
 @private
 _stat :: proc(path: string) -> (OS_Stat, Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
 	s: OS_Stat
@@ -538,6 +703,7 @@ _stat :: proc(path: string) -> (OS_Stat, Errno) {
 
 @private
 _lstat :: proc(path: string) -> (OS_Stat, Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
 	s: OS_Stat
@@ -603,6 +769,7 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool)
 
 @private
 _readlink :: proc(path: string) -> (string, Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
 	bufsz : uint = 256
@@ -640,6 +807,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
 		rel = "."
 	}
 
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
 	rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator)
 
 	path_ptr := _unix_realpath(rel_cstr, nil)
@@ -655,6 +823,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
 }
 
 access :: proc(path: string, mask: int) -> bool {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	return _unix_access(cstr, mask) == 0
 }
@@ -679,6 +848,7 @@ heap_free :: proc(ptr: rawptr) {
 }
 
 lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 	path_str := strings.clone_to_cstring(key, context.temp_allocator)
 	cstr := _unix_getenv(path_str)
 	if cstr == nil {
@@ -710,6 +880,7 @@ get_current_directory :: proc() -> string {
 }
 
 set_current_directory :: proc(path: string) -> (err: Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_chdir(cstr)
 	if res == -1 {
@@ -719,6 +890,7 @@ set_current_directory :: proc(path: string) -> (err: Errno) {
 }
 
 make_directory :: proc(path: string, mode: u16 = 0o775) -> Errno {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_mkdir(path_cstr, mode)
 	if res == -1 {
@@ -743,12 +915,14 @@ current_thread_id :: proc "contextless" () -> int {
 }
 
 dlopen :: proc(filename: string, flags: int) -> rawptr {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(filename, context.temp_allocator)
 	handle := _unix_dlopen(cstr, flags)
 	return handle
 }
 dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
 	assert(handle != nil)
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(symbol, context.temp_allocator)
 	proc_handle := _unix_dlsym(handle, cstr)
 	return proc_handle
@@ -793,3 +967,91 @@ _alloc_command_line_arguments :: proc() -> []string {
 	}
 	return res
 }
+
+socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Errno) {
+	result := _unix_socket(domain, type, protocol)
+	if result < 0 {
+		return 0, Errno(get_last_error())
+	}
+	return Socket(result), ERROR_NONE
+}
+
+connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) {
+	result := _unix_connect(int(sd), addr, len)
+	if result < 0 {
+		return Errno(get_last_error())
+	}
+	return ERROR_NONE
+}
+
+bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) {
+	result := _unix_bind(int(sd), addr, len)
+	if result < 0 {
+		return Errno(get_last_error())
+	}
+	return ERROR_NONE
+}
+
+accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Errno) {
+	result := _unix_accept(int(sd), rawptr(addr), len)
+	if result < 0 {
+		return 0, Errno(get_last_error())
+	}
+	return Socket(result), ERROR_NONE
+}
+
+listen :: proc(sd: Socket, backlog: int) -> (Errno) {
+	result := _unix_listen(int(sd), backlog)
+	if result < 0 {
+		return Errno(get_last_error())
+	}
+	return ERROR_NONE
+}
+
+setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> (Errno) {
+	result := _unix_setsockopt(int(sd), level, optname, optval, optlen)
+	if result < 0 {
+		return Errno(get_last_error())
+	}
+	return ERROR_NONE
+}
+
+recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Errno) {
+	result := _unix_recvfrom(int(sd), raw_data(data), len(data), flags, addr, addr_size)
+	if result < 0 {
+		return 0, Errno(get_last_error())
+	}
+	return u32(result), ERROR_NONE
+}
+
+recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) {
+	result := _unix_recv(int(sd), raw_data(data), len(data), flags)
+	if result < 0 {
+		return 0, Errno(get_last_error())
+	}
+	return u32(result), ERROR_NONE
+}
+
+sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Errno) {
+	result := _unix_sendto(int(sd), raw_data(data), len(data), flags, addr, addrlen)
+	if result < 0 {
+		return 0, Errno(get_last_error())
+	}
+	return u32(result), ERROR_NONE
+}
+
+send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) {
+	result := _unix_send(int(sd), raw_data(data), len(data), 0)
+	if result < 0 {
+		return 0, Errno(get_last_error())
+	}
+	return u32(result), ERROR_NONE
+}
+
+shutdown :: proc(sd: Socket, how: int) -> (Errno) {
+	result := _unix_shutdown(int(sd), how)
+	if result < 0 {
+		return Errno(get_last_error())
+	}
+	return ERROR_NONE
+}

+ 19 - 1
core/os/os_freebsd.odin

@@ -309,6 +309,7 @@ get_last_error :: proc "contextless" () -> int {
 }
 
 open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	handle := _unix_open(cstr, c.int(flags), c.int(mode))
 	if handle == -1 {
@@ -361,6 +362,7 @@ file_size :: proc(fd: Handle) -> (i64, Errno) {
 }
 
 rename :: proc(old_path, new_path: string) -> Errno {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator)
 	new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator)
 	res := _unix_rename(old_path_cstr, new_path_cstr)
@@ -371,6 +373,7 @@ rename :: proc(old_path, new_path: string) -> Errno {
 }
 
 remove :: proc(path: string) -> Errno {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_unlink(path_cstr)
 	if res == -1 {
@@ -380,6 +383,7 @@ remove :: proc(path: string) -> Errno {
 }
 
 make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_mkdir(path_cstr, mode)
 	if res == -1 {
@@ -389,6 +393,7 @@ make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno {
 }
 
 remove_directory :: proc(path: string) -> Errno {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_rmdir(path_cstr)
 	if res == -1 {
@@ -474,6 +479,7 @@ last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
 
 @private
 _stat :: proc(path: string) -> (OS_Stat, Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	s: OS_Stat = ---
 	result := _unix_lstat(cstr, &s)
@@ -485,6 +491,7 @@ _stat :: proc(path: string) -> (OS_Stat, Errno) {
 
 @private
 _lstat :: proc(path: string) -> (OS_Stat, Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	
 	// deliberately uninitialized
@@ -550,6 +557,8 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool)
 
 @private
 _readlink :: proc(path: string) -> (string, Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
+
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
 	bufsz : uint = MAX_PATH
@@ -567,7 +576,8 @@ _readlink :: proc(path: string) -> (string, Errno) {
 			return strings.string_from_ptr(&buf[0], rc), ERROR_NONE
 		}	
 	}
-	unreachable()
+
+	return "", Errno{}
 }
 
 // XXX FreeBSD
@@ -580,6 +590,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
 	if rel == "" {
 		rel = "."
 	}
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
 
 	rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator)
 
@@ -596,6 +607,8 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
 }
 
 access :: proc(path: string, mask: int) -> (bool, Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	result := _unix_access(cstr, c.int(mask))
 	if result == -1 {
@@ -626,6 +639,8 @@ heap_free :: proc(ptr: rawptr) {
 }
 
 lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
+
 	path_str := strings.clone_to_cstring(key, context.temp_allocator)
 	cstr := _unix_getenv(path_str)
 	if cstr == nil {
@@ -660,6 +675,7 @@ get_current_directory :: proc() -> string {
 }
 
 set_current_directory :: proc(path: string) -> (err: Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_chdir(cstr)
 	if res == -1 do return Errno(get_last_error())
@@ -676,12 +692,14 @@ current_thread_id :: proc "contextless" () -> int {
 }
 
 dlopen :: proc(filename: string, flags: int) -> rawptr {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(filename, context.temp_allocator)
 	handle := _unix_dlopen(cstr, c.int(flags))
 	return handle
 }
 dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
 	assert(handle != nil)
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(symbol, context.temp_allocator)
 	proc_handle := _unix_dlsym(handle, cstr)
 	return proc_handle

+ 347 - 165
core/os/os_linux.odin

@@ -14,6 +14,7 @@ Handle    :: distinct i32
 Pid       :: distinct i32
 File_Time :: distinct u64
 Errno     :: distinct i32
+Socket    :: distinct int
 
 INVALID_HANDLE :: ~Handle(0)
 
@@ -171,6 +172,59 @@ SEEK_DATA  :: 3
 SEEK_HOLE  :: 4
 SEEK_MAX   :: SEEK_HOLE
 
+
+AF_UNSPEC:    int : 0
+AF_UNIX:      int : 1
+AF_LOCAL:     int : AF_UNIX
+AF_INET:      int : 2
+AF_INET6:     int : 10
+AF_PACKET:    int : 17
+AF_BLUETOOTH: int : 31
+
+SOCK_STREAM:    int : 1
+SOCK_DGRAM:     int : 2
+SOCK_RAW:       int : 3
+SOCK_RDM:       int : 4
+SOCK_SEQPACKET: int : 5
+SOCK_PACKET:    int : 10
+
+INADDR_ANY:       c.ulong : 0
+INADDR_BROADCAST: c.ulong : 0xffffffff
+INADDR_NONE:      c.ulong : 0xffffffff
+INADDR_DUMMY:     c.ulong : 0xc0000008
+
+IPPROTO_IP:       int : 0
+IPPROTO_ICMP:     int : 1
+IPPROTO_TCP:      int : 6
+IPPROTO_UDP:      int : 17
+IPPROTO_IPV6:     int : 41
+IPPROTO_ETHERNET: int : 143
+IPPROTO_RAW:      int : 255
+
+SHUT_RD:   int : 0
+SHUT_WR:   int : 1
+SHUT_RDWR: int : 2
+
+
+SOL_SOCKET:   int : 1
+SO_DEBUG:     int : 1
+SO_REUSEADDR: int : 2
+SO_DONTROUTE: int : 5
+SO_BROADCAST: int : 6
+SO_SNDBUF:    int : 7
+SO_RCVBUF:    int : 8
+SO_KEEPALIVE: int : 9
+SO_OOBINLINE: int : 10
+SO_LINGER:    int : 13
+SO_REUSEPORT: int : 15
+SO_RCVTIMEO_NEW: int : 66
+SO_SNDTIMEO_NEW: int : 67
+
+TCP_NODELAY: int : 1
+TCP_CORK:    int : 3
+
+MSG_TRUNC : int : 0x20
+
 // NOTE(zangent): These are OS specific!
 // Do not mix these up!
 RTLD_LAZY         :: 0x001
@@ -178,6 +232,13 @@ RTLD_NOW          :: 0x002
 RTLD_BINDING_MASK :: 0x3
 RTLD_GLOBAL       :: 0x100
 
+socklen_t :: c.int
+
+Timeval :: struct {
+	seconds: i64,
+	nanoseconds: int,
+}
+
 // "Argv" arguments converted to Odin strings
 args := _alloc_command_line_arguments()
 
@@ -217,6 +278,102 @@ Dirent :: struct {
 	name:   [256]byte,
 }
 
+ADDRESS_FAMILY :: u16
+SOCKADDR :: struct #packed {
+	sa_family: ADDRESS_FAMILY,
+	sa_data: [14]c.char,
+}
+
+SOCKADDR_STORAGE_LH :: struct #packed {
+	ss_family: ADDRESS_FAMILY,
+	__ss_pad1: [6]c.char,
+	__ss_align: i64,
+	__ss_pad2: [112]c.char,
+}
+
+sockaddr_in :: struct #packed {
+	sin_family: ADDRESS_FAMILY,
+	sin_port: u16be,
+	sin_addr: in_addr,
+	sin_zero: [8]c.char,
+}
+
+sockaddr_in6 :: struct #packed {
+	sin6_family: ADDRESS_FAMILY,
+	sin6_port: u16be,
+	sin6_flowinfo: c.ulong,
+	sin6_addr: in6_addr,
+	sin6_scope_id: c.ulong,
+}
+
+in_addr :: struct #packed {
+	s_addr: u32,
+}
+
+in6_addr :: struct #packed {
+	s6_addr: [16]u8,
+}
+
+rtnl_link_stats :: struct #packed {
+	rx_packets:          u32,
+	tx_packets:          u32,
+	rx_bytes:            u32,
+	tx_bytes:            u32,
+	rx_errors:           u32,
+	tx_errors:           u32,
+	rx_dropped:          u32,
+	tx_dropped:          u32,
+	multicast:           u32,
+	collisions:          u32,
+	rx_length_errors:    u32,
+	rx_over_errors:      u32,
+	rx_crc_errors:       u32,
+	rx_frame_errors:     u32,
+	rx_fifo_errors:      u32,
+	rx_missed_errors:    u32,
+	tx_aborted_errors:   u32,
+	tx_carrier_errors:   u32,
+	tx_fifo_errors:      u32,
+	tx_heartbeat_errors: u32,
+	tx_window_errors:    u32,
+	rx_compressed:       u32,
+	tx_compressed:       u32,
+	rx_nohandler:        u32,
+}
+
+SIOCGIFFLAG :: enum c.int {
+	UP             = 0,  /* Interface is up.  */
+	BROADCAST      = 1,  /* Broadcast address valid.  */
+	DEBUG          = 2,  /* Turn on debugging.  */
+	LOOPBACK       = 3,  /* Is a loopback net.  */
+	POINT_TO_POINT = 4,  /* Interface is point-to-point link.  */
+	NO_TRAILERS    = 5,  /* Avoid use of trailers.  */
+	RUNNING        = 6,  /* Resources allocated.  */
+	NOARP          = 7,  /* No address resolution protocol.  */
+	PROMISC        = 8,  /* Receive all packets.  */
+	ALL_MULTI      = 9,  /* Receive all multicast packets. Unimplemented. */
+	MASTER         = 10, /* Master of a load balancer.  */
+	SLAVE          = 11, /* Slave of a load balancer.  */
+	MULTICAST      = 12, /* Supports multicast.  */
+	PORTSEL        = 13, /* Can set media type.  */
+	AUTOMEDIA      = 14, /* Auto media select active.  */
+	DYNAMIC        = 15, /* Dialup device with changing addresses.  */
+        LOWER_UP       = 16,
+        DORMANT        = 17,
+        ECHO           = 18,
+}
+SIOCGIFFLAGS :: bit_set[SIOCGIFFLAG; c.int]
+
+ifaddrs :: struct {
+	next:              ^ifaddrs,
+	name:              cstring,
+	flags:             SIOCGIFFLAGS,
+	address:           ^SOCKADDR,
+	netmask:           ^SOCKADDR,
+	broadcast_or_dest: ^SOCKADDR,  // Broadcast or Point-to-Point address
+	data:              rawptr,     // Address-specific data.
+}
+
 Dir :: distinct rawptr // DIR*
 
 // File type
@@ -236,13 +393,13 @@ S_IRUSR :: 0o0400 // R for owner
 S_IWUSR :: 0o0200 // W for owner
 S_IXUSR :: 0o0100 // X for owner
 
-	// Read, write, execute/search by group
+// Read, write, execute/search by group
 S_IRWXG :: 0o0070 // RWX mask for group
 S_IRGRP :: 0o0040 // R for group
 S_IWGRP :: 0o0020 // W for group
 S_IXGRP :: 0o0010 // X for group
 
-	// Read, write, execute/search by others
+// Read, write, execute/search by others
 S_IRWXO :: 0o0007 // RWX mask for other
 S_IROTH :: 0o0004 // R for other
 S_IWOTH :: 0o0002 // W for other
@@ -270,136 +427,6 @@ AT_FDCWD            :: ~uintptr(99)	/* -100 */
 AT_REMOVEDIR        :: uintptr(0x200)
 AT_SYMLINK_NOFOLLOW :: uintptr(0x100)
 
-_unix_personality :: proc(persona: u64) -> int {
-	return int(intrinsics.syscall(unix.SYS_personality, uintptr(persona)))
-}
-
-_unix_fork :: proc() -> Pid {
-	when ODIN_ARCH != .arm64 {
-		res := int(intrinsics.syscall(unix.SYS_fork))
-	} else {
-		res := int(intrinsics.syscall(unix.SYS_clone, unix.SIGCHLD))
-	}
-	return -1 if res < 0 else Pid(res)
-}
-
-_unix_open :: proc(path: cstring, flags: int, mode: int = 0o000) -> Handle {
-	when ODIN_ARCH != .arm64 {
-		res := int(intrinsics.syscall(unix.SYS_open, uintptr(rawptr(path)), uintptr(flags), uintptr(mode)))
-	} else { // NOTE: arm64 does not have open
-		res := int(intrinsics.syscall(unix.SYS_openat, AT_FDCWD, uintptr(rawptr(path)), uintptr(flags), uintptr(mode)))
-	}
-	return -1 if res < 0 else Handle(res)
-}
-
-_unix_close :: proc(fd: Handle) -> int {
-	return int(intrinsics.syscall(unix.SYS_close, uintptr(fd)))
-}
-
-_unix_read :: proc(fd: Handle, buf: rawptr, size: uint) -> int {
-	return int(intrinsics.syscall(unix.SYS_read, uintptr(fd), uintptr(buf), uintptr(size)))
-}
-
-_unix_write :: proc(fd: Handle, buf: rawptr, size: uint) -> int {
-	return int(intrinsics.syscall(unix.SYS_write, uintptr(fd), uintptr(buf), uintptr(size)))
-}
-
-_unix_seek :: proc(fd: Handle, offset: i64, whence: int) -> i64 {
-	when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 {
-		return i64(intrinsics.syscall(unix.SYS_lseek, uintptr(fd), uintptr(offset), uintptr(whence)))
-	} else {
-		low := uintptr(offset & 0xFFFFFFFF)
-		high := uintptr(offset >> 32)
-		result: i64
-		res := i64(intrinsics.syscall(unix.SYS__llseek, uintptr(fd), high, low, uintptr(&result), uintptr(whence)))
-		return -1 if res < 0 else result
-	}
-}
-
-_unix_stat :: proc(path: cstring, stat: ^OS_Stat) -> int {
-	when ODIN_ARCH == .amd64 {
-		return int(intrinsics.syscall(unix.SYS_stat, uintptr(rawptr(path)), uintptr(stat)))
-	} else when ODIN_ARCH != .arm64 {
-		return int(intrinsics.syscall(unix.SYS_stat64, uintptr(rawptr(path)), uintptr(stat)))
-	} else { // NOTE: arm64 does not have stat
-		return int(intrinsics.syscall(unix.SYS_fstatat, AT_FDCWD, uintptr(rawptr(path)), uintptr(stat), 0))
-	}
-}
-
-_unix_fstat :: proc(fd: Handle, stat: ^OS_Stat) -> int {
-	when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 {
-		return int(intrinsics.syscall(unix.SYS_fstat, uintptr(fd), uintptr(stat)))
-	} else {
-		return int(intrinsics.syscall(unix.SYS_fstat64, uintptr(fd), uintptr(stat)))
-	}
-}
-
-_unix_lstat :: proc(path: cstring, stat: ^OS_Stat) -> int {
-	when ODIN_ARCH == .amd64 {
-		return int(intrinsics.syscall(unix.SYS_lstat, uintptr(rawptr(path)), uintptr(stat)))
-	} else when ODIN_ARCH != .arm64 {
-		return int(intrinsics.syscall(unix.SYS_lstat64, uintptr(rawptr(path)), uintptr(stat)))
-	} else { // NOTE: arm64 does not have any lstat
-		return int(intrinsics.syscall(unix.SYS_fstatat, AT_FDCWD, uintptr(rawptr(path)), uintptr(stat), AT_SYMLINK_NOFOLLOW))
-	}
-}
-
-_unix_readlink :: proc(path: cstring, buf: rawptr, bufsiz: uint) -> int {
-	when ODIN_ARCH != .arm64 {
-		return int(intrinsics.syscall(unix.SYS_readlink, uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz)))
-	} else { // NOTE: arm64 does not have readlink
-		return int(intrinsics.syscall(unix.SYS_readlinkat, AT_FDCWD, uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz)))
-	}
-}
-
-_unix_access :: proc(path: cstring, mask: int) -> int {
-	when ODIN_ARCH != .arm64 {
-		return int(intrinsics.syscall(unix.SYS_access, uintptr(rawptr(path)), uintptr(mask)))
-	} else { // NOTE: arm64 does not have access
-		return int(intrinsics.syscall(unix.SYS_faccessat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mask)))
-	}
-}
-
-_unix_getcwd :: proc(buf: rawptr, size: uint) -> int {
-	return int(intrinsics.syscall(unix.SYS_getcwd, uintptr(buf), uintptr(size)))
-}
-
-_unix_chdir :: proc(path: cstring) -> int {
-	return int(intrinsics.syscall(unix.SYS_chdir, uintptr(rawptr(path))))
-}
-
-_unix_rename :: proc(old, new: cstring) -> int {
-	when ODIN_ARCH != .arm64 {
-		return int(intrinsics.syscall(unix.SYS_rename, uintptr(rawptr(old)), uintptr(rawptr(new))))
-	} else { // NOTE: arm64 does not have rename
-		return int(intrinsics.syscall(unix.SYS_renameat, AT_FDCWD, uintptr(rawptr(old)), uintptr(rawptr(new))))
-	}
-}
-
-_unix_unlink :: proc(path: cstring) -> int {
-	when ODIN_ARCH != .arm64 {
-		return int(intrinsics.syscall(unix.SYS_unlink, uintptr(rawptr(path))))
-	} else { // NOTE: arm64 does not have unlink
-		return int(intrinsics.syscall(unix.SYS_unlinkat, AT_FDCWD, uintptr(rawptr(path)), 0))
-	}
-}
-
-_unix_rmdir :: proc(path: cstring) -> int {
-	when ODIN_ARCH != .arm64 {
-		return int(intrinsics.syscall(unix.SYS_rmdir, uintptr(rawptr(path))))
-	} else { // NOTE: arm64 does not have rmdir
-		return int(intrinsics.syscall(unix.SYS_unlinkat, AT_FDCWD, uintptr(rawptr(path)), AT_REMOVEDIR))
-	}
-}
-
-_unix_mkdir :: proc(path: cstring, mode: u32) -> int {
-	when ODIN_ARCH != .arm64 {
-		return int(intrinsics.syscall(unix.SYS_mkdir, uintptr(rawptr(path)), uintptr(mode)))
-	} else { // NOTE: arm64 does not have mkdir
-		return int(intrinsics.syscall(unix.SYS_mkdirat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mode)))
-	}
-}
-
 foreign libc {
 	@(link_name="__errno_location") __errno_location    :: proc() -> ^int ---
 
@@ -415,6 +442,7 @@ foreign libc {
 	@(link_name="free")             _unix_free          :: proc(ptr: rawptr) ---
 	@(link_name="realloc")          _unix_realloc       :: proc(ptr: rawptr, size: c.size_t) -> rawptr ---
 
+	@(link_name="execvp")           _unix_execvp       :: proc(path: cstring, argv: [^]cstring) -> int ---
 	@(link_name="getenv")           _unix_getenv        :: proc(cstring) -> cstring ---
 	@(link_name="putenv")           _unix_putenv        :: proc(cstring) -> c.int ---
 	@(link_name="realpath")         _unix_realpath      :: proc(path: cstring, resolved_path: rawptr) -> rawptr ---
@@ -426,6 +454,9 @@ foreign dl {
 	@(link_name="dlsym")            _unix_dlsym         :: proc(handle: rawptr, symbol: cstring) -> rawptr ---
 	@(link_name="dlclose")          _unix_dlclose       :: proc(handle: rawptr) -> c.int ---
 	@(link_name="dlerror")          _unix_dlerror       :: proc() -> cstring ---
+
+	@(link_name="getifaddrs")       _getifaddrs         :: proc(ifap: ^^ifaddrs) -> (c.int) ---
+	@(link_name="freeifaddrs")      _freeifaddrs        :: proc(ifa: ^ifaddrs) ---
 }
 
 is_path_separator :: proc(r: rune) -> bool {
@@ -447,7 +478,7 @@ get_last_error :: proc "contextless" () -> int {
 }
 
 personality :: proc(persona: u64) -> (Errno) {
-	res := _unix_personality(persona)
+	res := unix.sys_personality(persona)
 	if res == -1 {
 		return _get_errno(res)
 	}
@@ -455,28 +486,48 @@ personality :: proc(persona: u64) -> (Errno) {
 }
 
 fork :: proc() -> (Pid, Errno) {
-	pid := _unix_fork()
+	pid := unix.sys_fork()
 	if pid == -1 {
-		return -1, _get_errno(int(pid))
+		return -1, _get_errno(pid)
+	}
+	return Pid(pid), ERROR_NONE
+}
+
+execvp :: proc(path: string, args: []string) -> Errno {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
+
+	args_cstrs := make([]cstring, len(args) + 2, context.temp_allocator)
+	args_cstrs[0] = strings.clone_to_cstring(path, context.temp_allocator)
+	for i := 0; i < len(args); i += 1 {
+		args_cstrs[i+1] = strings.clone_to_cstring(args[i], context.temp_allocator)
 	}
-	return pid, ERROR_NONE
+
+	_unix_execvp(path_cstr, raw_data(args_cstrs))
+	return Errno(get_last_error())
 }
 
-open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
+
+open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0o000) -> (Handle, Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
-	handle := _unix_open(cstr, flags, mode)
+	handle := unix.sys_open(cstr, flags, uint(mode))
 	if handle < 0 {
-		return INVALID_HANDLE, _get_errno(int(handle))
+		return INVALID_HANDLE, _get_errno(handle)
 	}
-	return handle, ERROR_NONE
+	return Handle(handle), ERROR_NONE
 }
 
 close :: proc(fd: Handle) -> Errno {
-	return _get_errno(_unix_close(fd))
+	return _get_errno(unix.sys_close(int(fd)))
 }
 
 read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
-	bytes_read := _unix_read(fd, &data[0], c.size_t(len(data)))
+	if len(data) == 0 {
+		return 0, ERROR_NONE
+	}
+
+	bytes_read := unix.sys_read(int(fd), raw_data(data), len(data))
 	if bytes_read < 0 {
 		return -1, _get_errno(bytes_read)
 	}
@@ -487,50 +538,78 @@ write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
 	if len(data) == 0 {
 		return 0, ERROR_NONE
 	}
-	bytes_written := _unix_write(fd, &data[0], uint(len(data)))
+
+	bytes_written := unix.sys_write(int(fd), raw_data(data), len(data))
 	if bytes_written < 0 {
 		return -1, _get_errno(bytes_written)
 	}
-	return int(bytes_written), ERROR_NONE
+	return bytes_written, ERROR_NONE
+}
+read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
+	if len(data) == 0 {
+		return 0, ERROR_NONE
+	}
+
+	bytes_read := unix.sys_pread(int(fd), raw_data(data), len(data), offset)
+	if bytes_read < 0 {
+		return -1, _get_errno(bytes_read)
+	}
+	return bytes_read, ERROR_NONE
+}
+
+write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
+	if len(data) == 0 {
+		return 0, ERROR_NONE
+	}
+
+	bytes_written := unix.sys_pwrite(int(fd), raw_data(data), uint(len(data)), offset)
+	if bytes_written < 0 {
+		return -1, _get_errno(bytes_written)
+	}
+	return bytes_written, ERROR_NONE
 }
 
 seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
-	res := _unix_seek(fd, offset, whence)
+	res := unix.sys_lseek(int(fd), offset, whence)
 	if res < 0 {
 		return -1, _get_errno(int(res))
 	}
-	return res, ERROR_NONE
+	return i64(res), ERROR_NONE
 }
 
 file_size :: proc(fd: Handle) -> (i64, Errno) {
-    // deliberately uninitialized; the syscall fills this buffer for us
-    s: OS_Stat = ---
-    result := _unix_fstat(fd, &s)
-    if result < 0 {
-        return 0, _get_errno(result)
-    }
-    return max(s.size, 0), ERROR_NONE
+	// deliberately uninitialized; the syscall fills this buffer for us
+	s: OS_Stat = ---
+	result := unix.sys_fstat(int(fd), rawptr(&s))
+	if result < 0 {
+		return 0, _get_errno(result)
+	}
+	return max(s.size, 0), ERROR_NONE
 }
 
 rename :: proc(old_path, new_path: string) -> Errno {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator)
 	new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator)
-	return _get_errno(_unix_rename(old_path_cstr, new_path_cstr))
+	return _get_errno(unix.sys_rename(old_path_cstr, new_path_cstr))
 }
 
 remove :: proc(path: string) -> Errno {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
-	return _get_errno(_unix_unlink(path_cstr))
+	return _get_errno(unix.sys_unlink(path_cstr))
 }
 
 make_directory :: proc(path: string, mode: u32 = 0o775) -> Errno {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
-	return _get_errno(_unix_mkdir(path_cstr, mode))
+	return _get_errno(unix.sys_mkdir(path_cstr, uint(mode)))
 }
 
 remove_directory :: proc(path: string) -> Errno {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
-	return _get_errno(_unix_rmdir(path_cstr))
+	return _get_errno(unix.sys_rmdir(path_cstr))
 }
 
 is_file_handle :: proc(fd: Handle) -> bool {
@@ -582,8 +661,9 @@ is_file :: proc {is_file_path, is_file_handle}
 is_dir :: proc {is_dir_path, is_dir_handle}
 
 exists :: proc(path: string) -> bool {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cpath := strings.clone_to_cstring(path, context.temp_allocator)
-	res := _unix_access(cpath, O_RDONLY)
+	res := unix.sys_access(cpath, O_RDONLY)
 	return res == 0
 }
 
@@ -617,11 +697,12 @@ last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
 
 @private
 _stat :: proc(path: string) -> (OS_Stat, Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
 	// deliberately uninitialized; the syscall fills this buffer for us
 	s: OS_Stat = ---
-	result := _unix_stat(cstr, &s)
+	result := unix.sys_stat(cstr, &s)
 	if result < 0 {
 		return s, _get_errno(result)
 	}
@@ -630,11 +711,12 @@ _stat :: proc(path: string) -> (OS_Stat, Errno) {
 
 @private
 _lstat :: proc(path: string) -> (OS_Stat, Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
 	// deliberately uninitialized; the syscall fills this buffer for us
 	s: OS_Stat = ---
-	result := _unix_lstat(cstr, &s)
+	result := unix.sys_lstat(cstr, &s)
 	if result < 0 {
 		return s, _get_errno(result)
 	}
@@ -645,7 +727,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Errno) {
 _fstat :: proc(fd: Handle) -> (OS_Stat, Errno) {
 	// deliberately uninitialized; the syscall fills this buffer for us
 	s: OS_Stat = ---
-	result := _unix_fstat(fd, &s)
+	result := unix.sys_fstat(int(fd), rawptr(&s))
 	if result < 0 {
 		return s, _get_errno(result)
 	}
@@ -697,12 +779,13 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool)
 
 @private
 _readlink :: proc(path: string) -> (string, Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
 	bufsz : uint = 256
 	buf := make([]byte, bufsz)
 	for {
-		rc := _unix_readlink(path_cstr, &(buf[0]), bufsz)
+		rc := unix.sys_readlink(path_cstr, &(buf[0]), bufsz)
 		if rc < 0 {
 			delete(buf)
 			return "", _get_errno(rc)
@@ -732,6 +815,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
 	if rel == "" {
 		rel = "."
 	}
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
 
 	rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator)
 
@@ -748,8 +832,9 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
 }
 
 access :: proc(path: string, mask: int) -> (bool, Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
-	result := _unix_access(cstr, mask)
+	result := unix.sys_access(cstr, mask)
 	if result < 0 {
 		return false, _get_errno(result)
 	}
@@ -778,6 +863,7 @@ heap_free :: proc(ptr: rawptr) {
 }
 
 lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 	path_str := strings.clone_to_cstring(key, context.temp_allocator)
 	// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
 	cstr := _unix_getenv(path_str)
@@ -793,6 +879,7 @@ get_env :: proc(key: string, allocator := context.allocator) -> (value: string)
 }
 
 set_env :: proc(key, value: string) -> Errno {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	s := strings.concatenate({key, "=", value, "\x00"}, context.temp_allocator)
 	res := _unix_putenv(strings.unsafe_string_to_cstring(s))
 	if res < 0 {
@@ -802,6 +889,7 @@ set_env :: proc(key, value: string) -> Errno {
 }
 
 unset_env :: proc(key: string) -> Errno {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	s := strings.clone_to_cstring(key, context.temp_allocator)
 	res := _unix_putenv(s)
 	if res < 0 {
@@ -817,7 +905,7 @@ get_current_directory :: proc() -> string {
 	page_size := get_page_size()
 	buf := make([dynamic]u8, page_size)
 	for {
-		#no_bounds_check res := _unix_getcwd(&buf[0], uint(len(buf)))
+		#no_bounds_check res := unix.sys_getcwd(&buf[0], uint(len(buf)))
 
 		if res >= 0 {
 			return strings.string_from_nul_terminated_ptr(&buf[0], len(buf))
@@ -832,8 +920,9 @@ get_current_directory :: proc() -> string {
 }
 
 set_current_directory :: proc(path: string) -> (err: Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
-	res := _unix_chdir(cstr)
+	res := unix.sys_chdir(cstr)
 	if res < 0 {
 		return _get_errno(res)
 	}
@@ -850,12 +939,14 @@ current_thread_id :: proc "contextless" () -> int {
 }
 
 dlopen :: proc(filename: string, flags: int) -> rawptr {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(filename, context.temp_allocator)
 	handle := _unix_dlopen(cstr, c.int(flags))
 	return handle
 }
 dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
 	assert(handle != nil)
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(symbol, context.temp_allocator)
 	proc_handle := _unix_dlsym(handle, cstr)
 	return proc_handle
@@ -892,3 +983,94 @@ _alloc_command_line_arguments :: proc() -> []string {
 	}
 	return res
 }
+
+socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Errno) {
+	result := unix.sys_socket(domain, type, protocol)
+	if result < 0 {
+		return 0, _get_errno(result)
+	}
+	return Socket(result), ERROR_NONE
+}
+
+bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) {
+	result := unix.sys_bind(int(sd), addr, len)
+	if result < 0 {
+		return _get_errno(result)
+	}
+	return ERROR_NONE
+}
+
+
+connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) {
+	result := unix.sys_connect(int(sd), addr, len)
+	if result < 0 {
+		return _get_errno(result)
+	}
+	return ERROR_NONE
+}
+
+accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Errno) {
+	result := unix.sys_accept(int(sd), rawptr(addr), len)
+	if result < 0 {
+		return 0, _get_errno(result)
+	}
+	return Socket(result), ERROR_NONE
+}
+
+listen :: proc(sd: Socket, backlog: int) -> (Errno) {
+	result := unix.sys_listen(int(sd), backlog)
+	if result < 0 {
+		return _get_errno(result)
+	}
+	return ERROR_NONE
+}
+
+setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> (Errno) {
+	result := unix.sys_setsockopt(int(sd), level, optname, optval, optlen)
+	if result < 0 {
+		return _get_errno(result)
+	}
+	return ERROR_NONE
+}
+
+
+recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Errno) {
+	result := unix.sys_recvfrom(int(sd), raw_data(data), len(data), flags, addr, uintptr(addr_size))
+	if result < 0 {
+		return 0, _get_errno(int(result))
+	}
+	return u32(result), ERROR_NONE
+}
+
+recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) {
+	result := unix.sys_recvfrom(int(sd), raw_data(data), len(data), flags, nil, 0)
+	if result < 0 {
+		return 0, _get_errno(int(result))
+	}
+	return u32(result), ERROR_NONE
+}
+
+
+sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Errno) {
+	result := unix.sys_sendto(int(sd), raw_data(data), len(data), flags, addr, addrlen)
+	if result < 0 {
+		return 0, _get_errno(int(result))
+	}
+	return u32(result), ERROR_NONE
+}
+
+send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) {
+	result := unix.sys_sendto(int(sd), raw_data(data), len(data), 0, nil, 0)
+	if result < 0 {
+		return 0, _get_errno(int(result))
+	}
+	return u32(result), ERROR_NONE
+}
+
+shutdown :: proc(sd: Socket, how: int) -> (Errno) {
+	result := unix.sys_shutdown(int(sd), how)
+	if result < 0 {
+		return _get_errno(result)
+	}
+	return ERROR_NONE
+}

+ 14 - 1
core/os/os_openbsd.odin

@@ -308,6 +308,7 @@ fork :: proc() -> (Pid, Errno) {
 }
 
 open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	handle := _unix_open(cstr, c.int(flags), c.int(mode))
 	if handle == -1 {
@@ -360,6 +361,7 @@ file_size :: proc(fd: Handle) -> (i64, Errno) {
 }
 
 rename :: proc(old_path, new_path: string) -> Errno {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator)
 	new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator)
 	res := _unix_rename(old_path_cstr, new_path_cstr)
@@ -370,6 +372,7 @@ rename :: proc(old_path, new_path: string) -> Errno {
 }
 
 remove :: proc(path: string) -> Errno {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_unlink(path_cstr)
 	if res == -1 {
@@ -379,6 +382,7 @@ remove :: proc(path: string) -> Errno {
 }
 
 make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_mkdir(path_cstr, mode)
 	if res == -1 {
@@ -388,6 +392,7 @@ make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno {
 }
 
 remove_directory :: proc(path: string) -> Errno {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_rmdir(path_cstr)
 	if res == -1 {
@@ -473,6 +478,7 @@ last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
 
 @private
 _stat :: proc(path: string) -> (OS_Stat, Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
 	// deliberately uninitialized
@@ -486,6 +492,7 @@ _stat :: proc(path: string) -> (OS_Stat, Errno) {
 
 @private
 _lstat :: proc(path: string) -> (OS_Stat, Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
 	// deliberately uninitialized
@@ -552,6 +559,7 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool)
 
 @private
 _readlink :: proc(path: string) -> (string, Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
 	bufsz : uint = MAX_PATH
@@ -569,7 +577,6 @@ _readlink :: proc(path: string) -> (string, Errno) {
 			return strings.string_from_ptr(&buf[0], rc), ERROR_NONE
 		}	
 	}
-	unreachable()
 }
 
 // XXX OpenBSD
@@ -583,6 +590,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
 		rel = "."
 	}
 
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
 	rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator)
 
 	path_ptr := _unix_realpath(rel_cstr, nil)
@@ -598,6 +606,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
 }
 
 access :: proc(path: string, mask: int) -> (bool, Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_access(cstr, c.int(mask))
 	if res == -1 {
@@ -628,6 +637,7 @@ heap_free :: proc(ptr: rawptr) {
 }
 
 lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 	path_str := strings.clone_to_cstring(key, context.temp_allocator)
 	cstr := _unix_getenv(path_str)
 	if cstr == nil {
@@ -658,6 +668,7 @@ get_current_directory :: proc() -> string {
 }
 
 set_current_directory :: proc(path: string) -> (err: Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_chdir(cstr)
 	if res == -1 {
@@ -676,12 +687,14 @@ current_thread_id :: proc "contextless" () -> int {
 }
 
 dlopen :: proc(filename: string, flags: int) -> rawptr {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(filename, context.temp_allocator)
 	handle := _unix_dlopen(cstr, c.int(flags))
 	return handle
 }
 dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
 	assert(handle != nil)
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(symbol, context.temp_allocator)
 	proc_handle := _unix_dlsym(handle, cstr)
 	return proc_handle

+ 1 - 0
core/os/os_windows.odin

@@ -134,6 +134,7 @@ _processor_core_count :: proc() -> int {
 
 	thread_count := 0
 	if !result && win32.GetLastError() == 122 && length > 0 {
+		runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 		processors := make([]win32.SYSTEM_LOGICAL_PROCESSOR_INFORMATION, length, context.temp_allocator)
 
 		result = win32.GetLogicalProcessorInformation(&processors[0], &length)

+ 7 - 3
core/os/stat_windows.odin

@@ -1,6 +1,7 @@
 package os
 
 import "core:time"
+import "core:runtime"
 import win32 "core:sys/windows"
 
 @(private)
@@ -11,6 +12,7 @@ full_path_from_name :: proc(name: string, allocator := context.allocator) -> (pa
 	if name == "" {
 		name = "."
 	}
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 	p := win32.utf8_to_utf16(name, context.temp_allocator)
 	buf := make([dynamic]u16, 100)
 	defer delete(buf)
@@ -36,6 +38,7 @@ _stat :: proc(name: string, create_file_attributes: u32, allocator := context.al
 
 	context.allocator = allocator
 
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 
 	wname := win32.utf8_to_wstring(fix_long_path(name), context.temp_allocator)
 	fa: win32.WIN32_FILE_ATTRIBUTE_DATA
@@ -132,14 +135,15 @@ cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 {
 
 @(private)
 cleanpath_from_handle :: proc(fd: Handle) -> (string, Errno) {
-	buf, err := cleanpath_from_handle_u16(fd)
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
+	buf, err := cleanpath_from_handle_u16(fd, context.temp_allocator)
 	if err != 0 {
 		return "", err
 	}
 	return win32.utf16_to_utf8(buf, context.allocator) or_else "", err
 }
 @(private)
-cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Errno) {
+cleanpath_from_handle_u16 :: proc(fd: Handle, allocator: runtime.Allocator) -> ([]u16, Errno) {
 	if fd == 0 {
 		return nil, ERROR_INVALID_HANDLE
 	}
@@ -149,7 +153,7 @@ cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Errno) {
 	if n == 0 {
 		return nil, Errno(win32.GetLastError())
 	}
-	buf := make([]u16, max(n, win32.DWORD(260))+1, context.temp_allocator)
+	buf := make([]u16, max(n, win32.DWORD(260))+1, allocator)
 	buf_len := win32.GetFinalPathNameByHandleW(h, raw_data(buf), n, 0)
 	return buf[:buf_len], ERROR_NONE
 }

+ 2 - 1
core/path/filepath/match.odin

@@ -218,7 +218,6 @@ get_escape :: proc(chunk: string) -> (r: rune, next_chunk: string, err: Match_Er
 //
 // glob ignores file system errors
 //
-
 glob :: proc(pattern: string, allocator := context.allocator) -> (matches: []string, err: Match_Error) {
 	context.allocator = allocator
 
@@ -261,6 +260,8 @@ glob :: proc(pattern: string, allocator := context.allocator) -> (matches: []str
 	}
 	return
 }
+
+// Internal implementation of `glob`, not meant to be used by the user. Prefer `glob`.
 _glob :: proc(dir, pattern: string, matches: ^[dynamic]string, allocator := context.allocator) -> (m: [dynamic]string, e: Match_Error) {
 	context.allocator = allocator
 

+ 44 - 5
core/path/filepath/path.odin

@@ -20,6 +20,8 @@ is_slash :: proc(c: byte) -> bool {
 	return c == '\\' || c == '/'
 }
 
+// Splits path immediate following the last separator; separating the path into a directory and file.
+// If no separator is found, `dir` will be empty and `path` set to `path`.
 split :: proc(path: string) -> (dir, file: string) {
 	vol := volume_name(path)
 	i := len(path) - 1
@@ -29,10 +31,18 @@ split :: proc(path: string) -> (dir, file: string) {
 	return path[:i+1], path[i+1:]
 }
 
+/*
+	Returns leading volume name.
+
+	e.g.
+	  "C:\foo\bar\baz" will return "C:" on Windows.
+	  Everything else will be "".
+*/
 volume_name :: proc(path: string) -> string {
 	return path[:volume_name_len(path)]
 }
 
+// Returns the length of the volume name in bytes.
 volume_name_len :: proc(path: string) -> int {
 	if ODIN_OS == .Windows {
 		if len(path) < 2 {
@@ -74,7 +84,7 @@ volume_name_len :: proc(path: string) -> int {
 /*
 	Gets the file name and extension from a path.
 
-	i.e:
+	e.g.
 	  'path/to/name.tar.gz' -> 'name.tar.gz'
 	  'path/to/name.txt'    -> 'name.txt'
 	  'path/to/name'        -> 'name'
@@ -114,7 +124,7 @@ base :: proc(path: string) -> string {
 	Only the last dot is considered when splitting the file extension.
 	See `short_stem`.
 
-	i.e:
+	e.g.
 	  'name.tar.gz' -> 'name.tar'
 	  'name.txt'    -> 'name'
 
@@ -147,7 +157,7 @@ stem :: proc(path: string) -> string {
 
 	The first dot is used to split off the file extension, unlike `stem` which uses the last dot.
 
-	i.e:
+	e.g.
 	  'name.tar.gz' -> 'name'
 	  'name.txt'    -> 'name'
 
@@ -170,7 +180,7 @@ short_stem :: proc(path: string) -> string {
 	Only the last dot is considered when splitting the file extension.
 	See `long_ext`.
 
-	i.e:
+	e.g.
 	  'name.tar.gz' -> '.gz'
 	  'name.txt'    -> '.txt'
 
@@ -193,7 +203,7 @@ ext :: proc(path: string) -> string {
 
 	The first dot is used to split off the file extension, unlike `ext` which uses the last dot.
 
-	i.e:
+	e.g.
 	  'name.tar.gz' -> '.tar.gz'
 	  'name.txt'    -> '.txt'
 
@@ -219,6 +229,21 @@ long_ext :: proc(path: string) -> string {
 	return ""
 }
 
+/*
+	Returns the shortest path name equivalent to `path` through solely lexical processing.
+	It applies the folliwng rules until none of them can be applied:
+
+	* Replace multiple separators with a single one
+	* Remove each current directory (`.`) path name element
+	* Remove each inner parent directory (`..`) path and the preceding paths
+	* Remove `..` that begin at the root of a path
+	* All possible separators are replaced with the OS specific separator
+
+	The return path ends in a slash only if it represents the root of a directory (`C:\` on Windows and  `/` on *nix systems).
+
+	If the result of the path is an empty string, the returned path with be `"."`.
+
+*/
 clean :: proc(path: string, allocator := context.allocator) -> string {
 	context.allocator = allocator
 
@@ -299,6 +324,7 @@ clean :: proc(path: string, allocator := context.allocator) -> string {
 	return cleaned
 }
 
+// Returns the result of replacing each forward slash `/` character in the path with the separate OS specific character.
 from_slash :: proc(path: string, allocator := context.allocator) -> (new_path: string, new_allocation: bool) {
 	if SEPARATOR == '/' {
 		return path, false
@@ -306,6 +332,7 @@ from_slash :: proc(path: string, allocator := context.allocator) -> (new_path: s
 	return strings.replace_all(path, "/", SEPARATOR_STRING, allocator)
 }
 
+// Returns the result of replacing each OS specific separator with a forward slash `/` character.
 to_slash :: proc(path: string, allocator := context.allocator) -> (new_path: string, new_allocation: bool) {
 	if SEPARATOR == '/' {
 		return path, false
@@ -320,6 +347,13 @@ Relative_Error :: enum {
 	Cannot_Relate,
 }
 
+/*
+	Returns a relative path that is lexically equivalent to the `target_path` when joined with the `base_path` with an OS specific separator.
+
+	e.g. `join(base_path, rel(base_path, target_path))` is equivalent to `target_path`
+
+	On failure, the `Relative_Error` will be state it cannot compute the necessary relative path.
+*/
 rel :: proc(base_path, target_path: string, allocator := context.allocator) -> (string, Relative_Error) {
 	context.allocator = allocator
 	base_clean, target_clean := clean(base_path), clean(target_path)
@@ -398,6 +432,11 @@ rel :: proc(base_path, target_path: string, allocator := context.allocator) -> (
 	return target[t0:], .None
 }
 
+/*
+	Returns all but the last element path, usually the path's directory. Once the final element has been removed,
+	`dir` calls `clean` on the path and trailing separators are removed. If the path consists purely of separators,
+	then `"."` is returned.
+*/
 dir :: proc(path: string, allocator := context.allocator) -> string {
         context.allocator = allocator
 	vol := volume_name(path)

+ 2 - 0
core/path/filepath/path_unix.odin

@@ -7,6 +7,7 @@ when ODIN_OS == .Darwin {
 	foreign import libc "system:c"
 }
 
+import "core:runtime"
 import "core:strings"
 
 SEPARATOR :: '/'
@@ -41,6 +42,7 @@ abs :: proc(path: string, allocator := context.allocator) -> (string, bool) {
 join :: proc(elems: []string, allocator := context.allocator) -> string {
 	for e, i in elems {
 		if e != "" {
+			runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 			p := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator)
 			return clean(p, allocator)
 		}

+ 15 - 12
core/path/filepath/path_windows.odin

@@ -1,6 +1,7 @@
 package filepath
 
 import "core:strings"
+import "core:runtime"
 import "core:os"
 import win32 "core:sys/windows"
 
@@ -60,25 +61,25 @@ temp_full_path :: proc(name: string) -> (path: string, err: os.Errno) {
 	}
 
 	p := win32.utf8_to_utf16(name, ta)
-	buf := make([dynamic]u16, 100, ta)
-	for {
-		n := win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil)
-		if n == 0 {
-			delete(buf)
-			return "", os.Errno(win32.GetLastError())
-		}
-		if n <= u32(len(buf)) {
-			return win32.utf16_to_utf8(buf[:n], ta) or_else "", os.ERROR_NONE
-		}
-		resize(&buf, len(buf)*2)
+	n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil)
+	if n == 0 {
+		return "", os.Errno(win32.GetLastError())
 	}
 
-	return
+	buf := make([]u16, n, ta)
+	n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil)
+	if n == 0 {
+		delete(buf)
+		return "", os.Errno(win32.GetLastError())
+	}
+
+	return win32.utf16_to_utf8(buf[:n], ta) or_else "", os.ERROR_NONE
 }
 
 
 
 abs :: proc(path: string, allocator := context.allocator) -> (string, bool) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = allocator == context.temp_allocator)
 	full_path, err := temp_full_path(path)
 	if err != 0 {
 		return "", false
@@ -99,6 +100,8 @@ join :: proc(elems: []string, allocator := context.allocator) -> string {
 
 join_non_empty :: proc(elems: []string, allocator := context.allocator) -> string {
 	context.allocator = allocator
+
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = allocator == context.temp_allocator)
 	
 	if len(elems[0]) == 2 && elems[0][1] == ':' {
 		i := 1

+ 3 - 1
core/path/slashpath/path.odin

@@ -5,6 +5,7 @@
 // To manipulate operating system specific paths, use the path/filepath package
 package slashpath
 
+import "core:runtime"
 import "core:strings"
 
 // is_separator checks whether the byte is a valid separator character
@@ -150,8 +151,9 @@ join :: proc(elems: []string, allocator := context.allocator) -> string {
 	context.allocator = allocator
 	for elem, i in elems {
 		if elem != "" {
+			runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 			s := strings.join(elems[i:], "/", context.temp_allocator)
-			return clean(s)
+			return clean(s, allocator)
 		}
 	}
 	return ""

+ 208 - 0
core/prof/spall/spall.odin

@@ -0,0 +1,208 @@
+package prof_spall
+
+import "core:os"
+import "core:time"
+import "core:intrinsics"
+import "core:mem"
+
+// File Format
+
+MANUAL_MAGIC :: u64le(0x0BADF00D)
+
+Manual_Header :: struct #packed {
+	magic:           u64le,
+	version:         u64le,
+	timestamp_scale: f64le,
+	reserved:        u64le,
+}
+
+Manual_Event_Type :: enum u8 {
+	Invalid             = 0,
+
+	Begin               = 3,
+	End                 = 4,
+	Instant             = 5,
+
+	Pad_Skip            = 7,
+}
+
+Begin_Event :: struct #packed {
+	type:     Manual_Event_Type,
+	category: u8,
+	pid:      u32le,
+	tid:      u32le,
+	ts:       f64le,
+	name_len: u8,
+	args_len: u8,
+}
+BEGIN_EVENT_MAX :: size_of(Begin_Event) + 255 + 255
+
+End_Event :: struct #packed {
+	type: Manual_Event_Type,
+	pid:  u32le,
+	tid:  u32le,
+	ts:   f64le,
+}
+
+Pad_Skip :: struct #packed {
+	type: Manual_Event_Type,
+	size: u32le,
+}
+
+// User Interface
+
+Context :: struct {
+	precise_time:    bool,
+	timestamp_scale: f64,
+	fd:              os.Handle,
+}
+
+Buffer :: struct {
+	data: []u8,
+	head: int,
+	tid:  u32,
+	pid:  u32,
+}
+
+BUFFER_DEFAULT_SIZE :: 0x10_0000
+
+
+context_create :: proc(filename: string) -> (ctx: Context, ok: bool) #optional_ok {
+	fd, err := os.open(filename, os.O_WRONLY | os.O_APPEND | os.O_CREATE | os.O_TRUNC, 0o600)
+	if err != os.ERROR_NONE {
+		return
+	}
+	ctx.fd = fd
+
+	freq, freq_ok := time.tsc_frequency()
+	ctx.precise_time = freq_ok
+	ctx.timestamp_scale = ((1 / f64(freq)) * 1_000_000) if freq_ok else 1
+
+	temp := [size_of(Manual_Header)]u8{}
+	_build_header(temp[:], ctx.timestamp_scale)
+	os.write(ctx.fd, temp[:])
+	ok = true
+	return
+}
+
+context_destroy :: proc(ctx: ^Context) {
+	if ctx == nil {
+		return
+	}
+
+	os.close(ctx.fd)
+	ctx^ = Context{}
+}
+
+buffer_create :: proc(data: []byte, tid: u32 = 0, pid: u32 = 0) -> (buffer: Buffer, ok: bool) #optional_ok {
+	assert(len(data) > 0)
+	buffer.data = data
+	buffer.tid  = tid
+	buffer.pid  = pid
+	buffer.head = 0
+	ok = true
+	return
+}
+
+buffer_flush :: proc(ctx: ^Context, buffer: ^Buffer) {
+	os.write(ctx.fd, buffer.data[:buffer.head])
+	buffer.head = 0
+}
+
+buffer_destroy :: proc(ctx: ^Context, buffer: ^Buffer) {
+	buffer_flush(ctx, buffer)
+
+	buffer^ = Buffer{}
+}
+
+
+
+@(deferred_in=_scoped_buffer_end)
+SCOPED_EVENT :: proc(ctx: ^Context, buffer: ^Buffer, name: string, args: string = "", location := #caller_location) -> bool {
+	_buffer_begin(ctx, buffer, name, args, location)
+	return true
+}
+
+@(private)
+_scoped_buffer_end :: proc(ctx: ^Context, buffer: ^Buffer, _, _: string, _ := #caller_location) {
+	_buffer_end(ctx, buffer)
+}
+
+
+_trace_now :: proc "contextless" (ctx: ^Context) -> f64 {
+	if !ctx.precise_time {
+		return f64(time.tick_now()._nsec) / 1_000
+	}
+
+	return f64(intrinsics.read_cycle_counter())
+}
+
+_build_header :: proc "contextless" (buffer: []u8, timestamp_scale: f64) -> (header_size: int, ok: bool) #optional_ok {
+	header_size = size_of(Manual_Header)
+	if header_size > len(buffer) {
+		return 0, false
+	}
+
+	hdr := (^Manual_Header)(raw_data(buffer))
+	hdr.magic = MANUAL_MAGIC
+	hdr.version = 1
+	hdr.timestamp_scale = f64le(timestamp_scale)
+	hdr.reserved = 0
+	ok = true
+	return
+}
+
+_build_begin :: proc "contextless" (buffer: []u8, name: string, args: string, ts: f64, tid: u32, pid: u32) -> (event_size: int, ok: bool) #optional_ok {
+	ev := (^Begin_Event)(raw_data(buffer))
+	name_len := min(len(name), 255)
+	args_len := min(len(args), 255)
+
+	event_size = size_of(Begin_Event) + name_len + args_len
+	if event_size > len(buffer) {
+		return 0, false
+	}
+
+	ev.type = .Begin
+	ev.pid  = u32le(pid)
+	ev.tid  = u32le(tid)
+	ev.ts   = f64le(ts)
+	ev.name_len = u8(name_len)
+	ev.args_len = u8(args_len)
+	mem.copy(raw_data(buffer[size_of(Begin_Event):]), raw_data(name), name_len)
+	mem.copy(raw_data(buffer[size_of(Begin_Event)+name_len:]), raw_data(args), args_len)
+	ok = true
+	return
+}
+
+_build_end :: proc(buffer: []u8, ts: f64, tid: u32, pid: u32) -> (event_size: int, ok: bool) #optional_ok {
+	ev := (^End_Event)(raw_data(buffer))
+	event_size = size_of(End_Event)
+	if event_size > len(buffer) {
+		return 0, false
+	}
+
+	ev.type = .End
+	ev.pid  = u32le(pid)
+	ev.tid  = u32le(tid)
+	ev.ts   = f64le(ts)
+	ok = true
+	return
+}
+
+_buffer_begin :: proc(ctx: ^Context, buffer: ^Buffer, name: string, args: string = "", location := #caller_location) {
+	if buffer.head + BEGIN_EVENT_MAX > len(buffer.data) {
+		buffer_flush(ctx, buffer)
+	}
+	name := location.procedure if name == "" else name
+	buffer.head += _build_begin(buffer.data[buffer.head:], name, args, _trace_now(ctx), buffer.tid, buffer.pid)
+}
+
+_buffer_end :: proc(ctx: ^Context, buffer: ^Buffer) {
+	ts := _trace_now(ctx)
+
+	if buffer.head + size_of(End_Event) > len(buffer.data) {
+		buffer_flush(ctx, buffer)
+	}
+
+	buffer.head += _build_end(buffer.data[buffer.head:], ts, buffer.tid, buffer.pid)
+}

+ 4 - 3
core/reflect/reflect.odin

@@ -25,7 +25,8 @@ Type_Info_Array            :: runtime.Type_Info_Array
 Type_Info_Enumerated_Array :: runtime.Type_Info_Enumerated_Array
 Type_Info_Dynamic_Array    :: runtime.Type_Info_Dynamic_Array
 Type_Info_Slice            :: runtime.Type_Info_Slice
-Type_Info_Tuple            :: runtime.Type_Info_Tuple
+Type_Info_Parameters       :: runtime.Type_Info_Parameters
+Type_Info_Tuple            :: runtime.Type_Info_Parameters
 Type_Info_Struct           :: runtime.Type_Info_Struct
 Type_Info_Union            :: runtime.Type_Info_Union
 Type_Info_Enum             :: runtime.Type_Info_Enum
@@ -96,7 +97,7 @@ type_kind :: proc(T: typeid) -> Type_Kind {
 		case Type_Info_Enumerated_Array: return .Enumerated_Array
 		case Type_Info_Dynamic_Array:    return .Dynamic_Array
 		case Type_Info_Slice:            return .Slice
-		case Type_Info_Tuple:            return .Tuple
+		case Type_Info_Parameters:       return .Tuple
 		case Type_Info_Struct:           return .Struct
 		case Type_Info_Union:            return .Union
 		case Type_Info_Enum:             return .Enum
@@ -1438,7 +1439,7 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_
 	switch v in t.variant {
 	case Type_Info_Named:
 		unreachable()
-	case Type_Info_Tuple:
+	case Type_Info_Parameters:
 		unreachable()
 	case Type_Info_Any:
 		if !including_indirect_array_recursion {

+ 11 - 5
core/reflect/types.odin

@@ -101,8 +101,8 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool {
 		y := b.variant.(Type_Info_Slice) or_return
 		return are_types_identical(x.elem, y.elem)
 
-	case Type_Info_Tuple:
-		y := b.variant.(Type_Info_Tuple) or_return
+	case Type_Info_Parameters:
+		y := b.variant.(Type_Info_Parameters) or_return
 		if len(x.types) != len(y.types) { return false }
 		for _, i in x.types {
 			xt, yt := x.types[i], y.types[i]
@@ -335,9 +335,15 @@ is_slice :: proc(info: ^Type_Info) -> bool {
 	return ok
 }
 @(require_results)
+is_parameters :: proc(info: ^Type_Info) -> bool {
+	if info == nil { return false }
+	_, ok := type_info_base(info).variant.(Type_Info_Parameters)
+	return ok
+}
+@(require_results, deprecated="prefer is_parameters")
 is_tuple :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
-	_, ok := type_info_base(info).variant.(Type_Info_Tuple)
+	_, ok := type_info_base(info).variant.(Type_Info_Parameters)
 	return ok
 }
 @(require_results)
@@ -490,7 +496,7 @@ write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) -
 		if info.params == nil {
 			io.write_string(w, "()", &n) or_return
 		} else {
-			t := info.params.variant.(Type_Info_Tuple)
+			t := info.params.variant.(Type_Info_Parameters)
 			io.write_string(w, "(", &n) or_return
 			for t, i in t.types {
 				if i > 0 {
@@ -504,7 +510,7 @@ write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) -
 			io.write_string(w, " -> ", &n)  or_return
 			write_type(w, info.results, &n) or_return
 		}
-	case Type_Info_Tuple:
+	case Type_Info_Parameters:
 		count := len(info.names)
 		if count != 1 { 
 			io.write_string(w, "(", &n) or_return 

+ 8 - 9
core/runtime/core.odin

@@ -83,8 +83,8 @@ Type_Info_Multi_Pointer :: struct {
 	elem: ^Type_Info,
 }
 Type_Info_Procedure :: struct {
-	params:     ^Type_Info, // Type_Info_Tuple
-	results:    ^Type_Info, // Type_Info_Tuple
+	params:     ^Type_Info, // Type_Info_Parameters
+	results:    ^Type_Info, // Type_Info_Parameters
 	variadic:   bool,
 	convention: Calling_Convention,
 }
@@ -104,10 +104,12 @@ Type_Info_Enumerated_Array :: struct {
 }
 Type_Info_Dynamic_Array :: struct {elem: ^Type_Info, elem_size: int}
 Type_Info_Slice         :: struct {elem: ^Type_Info, elem_size: int}
-Type_Info_Tuple :: struct { // Only used for procedures parameters and results
+
+Type_Info_Parameters :: struct { // Only used for procedures parameters and results
 	types:        []^Type_Info,
 	names:        []string,
 }
+Type_Info_Tuple :: Type_Info_Parameters // Will be removed eventually
 
 Type_Info_Struct :: struct {
 	types:        []^Type_Info,
@@ -208,7 +210,7 @@ Type_Info :: struct {
 		Type_Info_Enumerated_Array,
 		Type_Info_Dynamic_Array,
 		Type_Info_Slice,
-		Type_Info_Tuple,
+		Type_Info_Parameters,
 		Type_Info_Struct,
 		Type_Info_Union,
 		Type_Info_Enum,
@@ -505,11 +507,8 @@ Odin_Endian_Type :: type_of(ODIN_ENDIAN)
 foreign {
 	@(link_name="__$startup_runtime")
 	_startup_runtime :: proc "odin" () ---
-}
-
-@(link_name="__$cleanup_runtime")
-_cleanup_runtime :: proc() {
-	default_temp_allocator_destroy(&global_default_temp_allocator_data)
+	@(link_name="__$cleanup_runtime")
+	_cleanup_runtime :: proc "odin" () ---
 }
 
 _cleanup_runtime_contextless :: proc "contextless" () {

+ 5 - 8
core/runtime/core_builtin.odin

@@ -231,13 +231,12 @@ make_dynamic_array_len_cap :: proc($T: typeid/[dynamic]$E, #any_int len: int, #a
 	return
 }
 @(builtin)
-make_map :: proc($T: typeid/map[$K]$E, #any_int capacity: int = 1<<MAP_MIN_LOG2_CAPACITY, allocator := context.allocator, loc := #caller_location) -> T {
+make_map :: proc($T: typeid/map[$K]$E, #any_int capacity: int = 1<<MAP_MIN_LOG2_CAPACITY, allocator := context.allocator, loc := #caller_location) -> (m: T, err: Allocator_Error) #optional_allocator_error {
 	make_map_expr_error_loc(loc, capacity)
 	context.allocator = allocator
 
-	m: T
-	reserve_map(&m, capacity, loc)
-	return m
+	err = reserve_map(&m, capacity, loc)
+	return
 }
 @(builtin)
 make_multi_pointer :: proc($T: typeid/[^]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (mp: T, err: Allocator_Error) #optional_allocator_error {
@@ -276,10 +275,8 @@ clear_map :: proc "contextless" (m: ^$T/map[$K]$V) {
 }
 
 @builtin
-reserve_map :: proc(m: ^$T/map[$K]$V, capacity: int, loc := #caller_location) {
-	if m != nil {
-		__dynamic_map_reserve((^Raw_Map)(m), map_info(T), uint(capacity), loc)
-	}
+reserve_map :: proc(m: ^$T/map[$K]$V, capacity: int, loc := #caller_location) -> Allocator_Error {
+	return __dynamic_map_reserve((^Raw_Map)(m), map_info(T), uint(capacity), loc) if m != nil else nil
 }
 
 /*

+ 304 - 0
core/runtime/default_allocators_arena.odin

@@ -0,0 +1,304 @@
+package runtime
+
+import "core:intrinsics"
+
+DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE)
+
+Memory_Block :: struct {
+	prev:      ^Memory_Block,
+	allocator: Allocator,
+	base:      [^]byte,
+	used:      uint,
+	capacity:  uint,
+}
+
+Arena :: struct {
+	backing_allocator:  Allocator,
+	curr_block:         ^Memory_Block,
+	total_used:         uint,
+	total_capacity:     uint,
+	minimum_block_size: uint,
+	temp_count:         uint,
+}
+
+@(private, require_results)
+safe_add :: #force_inline proc "contextless" (x, y: uint) -> (uint, bool) {
+	z, did_overflow := intrinsics.overflow_add(x, y)
+	return z, !did_overflow
+}
+
+@(require_results)
+memory_block_alloc :: proc(allocator: Allocator, capacity: uint, loc := #caller_location) -> (block: ^Memory_Block, err: Allocator_Error) {
+	total_size := uint(capacity + size_of(Memory_Block))
+	base_offset    := uintptr(size_of(Memory_Block))
+
+	min_alignment: int = max(16, align_of(Memory_Block))
+	data := mem_alloc(int(total_size), min_alignment, allocator, loc) or_return
+	block = (^Memory_Block)(raw_data(data))
+	end := uintptr(raw_data(data)[len(data):])
+
+	block.allocator = allocator
+	block.base = ([^]byte)(uintptr(block) + base_offset)
+	block.capacity = uint(end - uintptr(block.base))
+
+	// Should be zeroed
+	assert(block.used == 0)
+	assert(block.prev == nil)
+	return
+}
+
+memory_block_dealloc :: proc(block_to_free: ^Memory_Block, loc := #caller_location) {
+	if block_to_free != nil {
+		allocator := block_to_free.allocator
+		mem_free(block_to_free, allocator, loc)
+	}
+}
+
+@(require_results)
+alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint) -> (data: []byte, err: Allocator_Error) {
+	calc_alignment_offset :: proc "contextless" (block: ^Memory_Block, alignment: uintptr) -> uint {
+		alignment_offset := uint(0)
+		ptr := uintptr(block.base[block.used:])
+		mask := alignment-1
+		if ptr & mask != 0 {
+			alignment_offset = uint(alignment - (ptr & mask))
+		}
+		return alignment_offset
+
+	}
+	if block == nil {
+		return nil, .Out_Of_Memory
+	}
+	alignment_offset := calc_alignment_offset(block, uintptr(alignment))
+	size, size_ok := safe_add(min_size, alignment_offset)
+	if !size_ok {
+		err = .Out_Of_Memory
+		return
+	}
+
+	if to_be_used, ok := safe_add(block.used, size); !ok || to_be_used > block.capacity {
+		err = .Out_Of_Memory
+		return
+	}
+	data = block.base[block.used+alignment_offset:][:min_size]
+	block.used += size
+	return
+}
+
+@(require_results)
+arena_alloc :: proc(arena: ^Arena, size, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
+	align_forward_uint :: proc "contextless" (ptr, align: uint) -> uint {
+		p := ptr
+		modulo := p & (align-1)
+		if modulo != 0 {
+			p += align - modulo
+		}
+		return p
+	}
+
+	assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc)
+
+	size := size
+	if size == 0 {
+		return
+	}
+
+	if arena.curr_block == nil || (safe_add(arena.curr_block.used, size) or_else 0) > arena.curr_block.capacity {
+		size = align_forward_uint(size, alignment)
+		if arena.minimum_block_size == 0 {
+			arena.minimum_block_size = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE
+		}
+
+		block_size := max(size, arena.minimum_block_size)
+
+		if arena.backing_allocator.procedure == nil {
+			arena.backing_allocator = default_allocator()
+		}
+
+		new_block := memory_block_alloc(arena.backing_allocator, block_size, loc) or_return
+		new_block.prev = arena.curr_block
+		arena.curr_block = new_block
+		arena.total_capacity += new_block.capacity
+	}
+
+	prev_used := arena.curr_block.used
+	data, err = alloc_from_memory_block(arena.curr_block, size, alignment)
+	arena.total_used += arena.curr_block.used - prev_used
+	return
+}
+
+// `arena_init` will initialize the arena with a usuable block.
+// This procedure is not necessary to use the Arena as the default zero as `arena_alloc` will set things up if necessary
+@(require_results)
+arena_init :: proc(arena: ^Arena, size: uint, backing_allocator: Allocator, loc := #caller_location) -> Allocator_Error {
+	arena^ = {}
+	arena.backing_allocator = backing_allocator
+	arena.minimum_block_size = max(size, 1<<12) // minimum block size of 4 KiB
+	new_block := memory_block_alloc(arena.backing_allocator, arena.minimum_block_size, loc) or_return
+	arena.curr_block = new_block
+	arena.total_capacity += new_block.capacity
+	return nil
+}
+
+
+arena_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) {
+	if free_block := arena.curr_block; free_block != nil {
+		arena.curr_block = free_block.prev
+
+		arena.total_capacity -= free_block.capacity
+		memory_block_dealloc(free_block, loc)
+	}
+}
+
+// `arena_free_all` will free all but the first memory block, and then reset the memory block
+arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
+	for arena.curr_block != nil && arena.curr_block.prev != nil {
+		arena_free_last_memory_block(arena, loc)
+	}
+
+	if arena.curr_block != nil {
+		intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used)
+		arena.curr_block.used = 0
+	}
+	arena.total_used = 0
+}
+
+arena_destroy :: proc(arena: ^Arena, loc := #caller_location) {
+	for arena.curr_block != nil {
+		free_block := arena.curr_block
+		arena.curr_block = free_block.prev
+
+		arena.total_capacity -= free_block.capacity
+		memory_block_dealloc(free_block, loc)
+	}
+	arena.total_used = 0
+	arena.total_capacity = 0
+}
+
+arena_allocator :: proc(arena: ^Arena) -> Allocator {
+	return Allocator{arena_allocator_proc, arena}
+}
+
+arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
+                             size, alignment: int,
+                             old_memory: rawptr, old_size: int,
+                             location := #caller_location) -> (data: []byte, err: Allocator_Error) {
+	arena := (^Arena)(allocator_data)
+
+	size, alignment := uint(size), uint(alignment)
+	old_size := uint(old_size)
+
+	switch mode {
+	case .Alloc, .Alloc_Non_Zeroed:
+		return arena_alloc(arena, size, alignment, location)
+	case .Free:
+		err = .Mode_Not_Implemented
+	case .Free_All:
+		arena_free_all(arena, location)
+	case .Resize:
+		old_data := ([^]byte)(old_memory)
+
+		switch {
+		case old_data == nil:
+			return arena_alloc(arena, size, alignment, location)
+		case size == old_size:
+			// return old memory
+			data = old_data[:size]
+			return
+		case size == 0:
+			err = .Mode_Not_Implemented
+			return
+		case (uintptr(old_data) & uintptr(alignment-1) == 0) && size < old_size:
+			// shrink data in-place
+			data = old_data[:size]
+			return
+		}
+
+		new_memory := arena_alloc(arena, size, alignment, location) or_return
+		if new_memory == nil {
+			return
+		}
+		copy(new_memory, old_data[:old_size])
+		return new_memory, nil
+	case .Query_Features:
+		set := (^Allocator_Mode_Set)(old_memory)
+		if set != nil {
+			set^ = {.Alloc, .Alloc_Non_Zeroed, .Free_All, .Resize, .Query_Features}
+		}
+	case .Query_Info:
+		err = .Mode_Not_Implemented
+	}
+
+	return
+}
+
+
+
+
+Arena_Temp :: struct {
+	arena: ^Arena,
+	block: ^Memory_Block,
+	used:  uint,
+}
+
+@(require_results)
+arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) {
+	assert(arena != nil, "nil arena", loc)
+
+	temp.arena = arena
+	temp.block = arena.curr_block
+	if arena.curr_block != nil {
+		temp.used = arena.curr_block.used
+	}
+	arena.temp_count += 1
+	return
+}
+
+arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
+	if temp.arena == nil {
+		assert(temp.block == nil)
+		assert(temp.used == 0)
+		return
+	}
+	arena := temp.arena
+
+	if temp.block != nil {
+		memory_block_found := false
+		for block := arena.curr_block; block != nil; block = block.prev {
+			if block == temp.block {
+				memory_block_found = true
+				break
+			}
+		}
+		if !memory_block_found {
+			assert(arena.curr_block == temp.block, "memory block stored within Arena_Temp not owned by Arena", loc)
+		}
+
+		for arena.curr_block != temp.block {
+			arena_free_last_memory_block(arena)
+		}
+
+		if block := arena.curr_block; block != nil {
+			assert(block.used >= temp.used, "out of order use of arena_temp_end", loc)
+			amount_to_zero := min(block.used-temp.used, block.capacity-block.used)
+			intrinsics.mem_zero(block.base[temp.used:], amount_to_zero)
+			block.used = temp.used
+		}
+	}
+
+	assert(arena.temp_count > 0, "double-use of arena_temp_end", loc)
+	arena.temp_count -= 1
+}
+
+// Ignore the use of a `arena_temp_begin` entirely
+arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
+	assert(temp.arena != nil, "nil arena", loc)
+	arena := temp.arena
+
+	assert(arena.temp_count > 0, "double-use of arena_temp_end", loc)
+	arena.temp_count -= 1
+}
+
+arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) {
+	assert(arena.temp_count == 0, "Arena_Temp not been ended", loc)
+}

+ 40 - 161
core/runtime/default_temporary_allocator.odin

@@ -6,154 +6,33 @@ DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE: int : #config(DEFAULT_TEMP_ALLOCATOR_BACKIN
 when ODIN_OS == .Freestanding || ODIN_OS == .JS || ODIN_DEFAULT_TO_NIL_ALLOCATOR {
 	Default_Temp_Allocator :: struct {}
 	
-	default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backup_allocator := context.allocator) {}
+	default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator := context.allocator) {}
 	
 	default_temp_allocator_destroy :: proc(s: ^Default_Temp_Allocator) {}
 	
 	default_temp_allocator_proc :: nil_allocator_proc
-} else {
-	Default_Temp_Allocator :: struct {
-		data:               []byte,
-		curr_offset:        int,
-		prev_allocation:    rawptr,
-		backup_allocator:   Allocator,
-		leaked_allocations: [dynamic][]byte,
-	}
-	
-	default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backup_allocator := context.allocator) {
-		s.data = make_aligned([]byte, size, 2*align_of(rawptr), backup_allocator)
-		s.curr_offset = 0
-		s.prev_allocation = nil
-		s.backup_allocator = backup_allocator
-		s.leaked_allocations.allocator = backup_allocator
-	}
 
-	default_temp_allocator_destroy :: proc(s: ^Default_Temp_Allocator) {
-		if s == nil {
-			return
-		}
-		for ptr in s.leaked_allocations {
-			free(raw_data(ptr), s.backup_allocator)
-		}
-		delete(s.leaked_allocations)
-		delete(s.data, s.backup_allocator)
-		s^ = {}
+	@(require_results)
+	default_temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: Arena_Temp) {
+		return
 	}
 
-	@(private)
-	default_temp_allocator_alloc :: proc(s: ^Default_Temp_Allocator, size, alignment: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
-		size := size
-		size = align_forward_int(size, alignment)
-
-		switch {
-		case s.curr_offset+size <= len(s.data):
-			start := uintptr(raw_data(s.data))
-			ptr := start + uintptr(s.curr_offset)
-			ptr = align_forward_uintptr(ptr, uintptr(alignment))
-			mem_zero(rawptr(ptr), size)
-
-			s.prev_allocation = rawptr(ptr)
-			offset := int(ptr - start)
-			s.curr_offset = offset + size
-			return byte_slice(rawptr(ptr), size), .None
-
-		case size <= len(s.data):
-			start := uintptr(raw_data(s.data))
-			ptr := align_forward_uintptr(start, uintptr(alignment))
-			mem_zero(rawptr(ptr), size)
-
-			s.prev_allocation = rawptr(ptr)
-			offset := int(ptr - start)
-			s.curr_offset = offset + size
-			return byte_slice(rawptr(ptr), size), .None
-		}
-		a := s.backup_allocator
-		if a.procedure == nil {
-			a = context.allocator
-			s.backup_allocator = a
-		}
-
-		data, err := mem_alloc_bytes(size, alignment, a, loc)
-		if err != nil {
-			return data, err
-		}
-		if s.leaked_allocations == nil {
-			s.leaked_allocations = make([dynamic][]byte, a)
-		}
-		append(&s.leaked_allocations, data)
-
-		// TODO(bill): Should leaks be notified about?
-		if logger := context.logger; logger.lowest_level <= .Warning {
-			if logger.procedure != nil {
-				logger.procedure(logger.data, .Warning, "default temp allocator resorted to backup_allocator" , logger.options, loc)
-			}
-		}
-
-		return data, .None
+	default_temp_allocator_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
 	}
-
-	@(private)
-	default_temp_allocator_free :: proc(s: ^Default_Temp_Allocator, old_memory: rawptr, loc := #caller_location) -> Allocator_Error {
-		if old_memory == nil {
-			return .None
-		}
-
-		start := uintptr(raw_data(s.data))
-		end := start + uintptr(len(s.data))
-		old_ptr := uintptr(old_memory)
-
-		if s.prev_allocation == old_memory {
-			s.curr_offset = int(uintptr(s.prev_allocation) - start)
-			s.prev_allocation = nil
-			return .None
-		}
-
-		if start <= old_ptr && old_ptr < end {
-			// NOTE(bill): Cannot free this pointer but it is valid
-			return .None
-		}
-
-		if len(s.leaked_allocations) != 0 {
-			for data, i in s.leaked_allocations {
-				ptr := raw_data(data)
-				if ptr == old_memory {
-					free(ptr, s.backup_allocator)
-					ordered_remove(&s.leaked_allocations, i)
-					return .None
-				}
-			}
-		}
-		return .Invalid_Pointer
-		// panic("invalid pointer passed to default_temp_allocator");
+} else {
+	Default_Temp_Allocator :: struct {
+		arena: Arena,
 	}
-
-	@(private)
-	default_temp_allocator_free_all :: proc(s: ^Default_Temp_Allocator, loc := #caller_location) {
-		s.curr_offset = 0
-		s.prev_allocation = nil
-		for data in s.leaked_allocations {
-			free(raw_data(data), s.backup_allocator)
-		}
-		clear(&s.leaked_allocations)
+	
+	default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator := context.allocator) {
+		_ = arena_init(&s.arena, uint(size), backing_allocator)
 	}
 
-	@(private)
-	default_temp_allocator_resize :: proc(s: ^Default_Temp_Allocator, old_memory: rawptr, old_size, size, alignment: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
-		begin := uintptr(raw_data(s.data))
-		end := begin + uintptr(len(s.data))
-		old_ptr := uintptr(old_memory)
-		if old_memory == s.prev_allocation && old_ptr & uintptr(alignment)-1 == 0 {
-			if old_ptr+uintptr(size) < end {
-				s.curr_offset = int(old_ptr-begin)+size
-				return byte_slice(old_memory, size), .None
-			}
-		}
-		data, err := default_temp_allocator_alloc(s, size, alignment, loc)
-		if err == .None {
-			copy(data, byte_slice(old_memory, old_size))
-			err = default_temp_allocator_free(s, old_memory, loc)
+	default_temp_allocator_destroy :: proc(s: ^Default_Temp_Allocator) {
+		if s != nil {
+			arena_destroy(&s.arena)
+			s^ = {}
 		}
-		return data, err
 	}
 
 	default_temp_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
@@ -161,40 +40,40 @@ when ODIN_OS == .Freestanding || ODIN_OS == .JS || ODIN_DEFAULT_TO_NIL_ALLOCATOR
 	                                    old_memory: rawptr, old_size: int, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
 
 		s := (^Default_Temp_Allocator)(allocator_data)
+		return arena_allocator_proc(&s.arena, mode, size, alignment, old_memory, old_size, loc)
+	}
 
-		if s.data == nil {
-			default_temp_allocator_init(s, DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE, default_allocator())
+	@(require_results)
+	default_temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: Arena_Temp) {
+		if context.temp_allocator.data == &global_default_temp_allocator_data {
+			temp = arena_temp_begin(&global_default_temp_allocator_data.arena, loc)
 		}
+		return
+	}
 
-		switch mode {
-		case .Alloc, .Alloc_Non_Zeroed:
-			data, err = default_temp_allocator_alloc(s, size, alignment, loc)
-		case .Free:
-			err = default_temp_allocator_free(s, old_memory, loc)
-
-		case .Free_All:
-			default_temp_allocator_free_all(s, loc)
-
-		case .Resize:
-			data, err = default_temp_allocator_resize(s, old_memory, old_size, size, alignment, loc)
-
-		case .Query_Features:
-			set := (^Allocator_Mode_Set)(old_memory)
-			if set != nil {
-				set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Query_Features}
-			}
-
-		case .Query_Info:
-			return nil, .Mode_Not_Implemented
-		}
+	default_temp_allocator_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
+		arena_temp_end(temp, loc)
+	}
+}
 
-		return
+@(deferred_out=default_temp_allocator_temp_end)
+DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD :: #force_inline proc(ignore := false, loc := #caller_location) -> (Arena_Temp, Source_Code_Location) {
+	if ignore {
+		return {}, loc
+	} else {
+		return default_temp_allocator_temp_begin(loc), loc
 	}
 }
 
+
 default_temp_allocator :: proc(allocator: ^Default_Temp_Allocator) -> Allocator {
 	return Allocator{
 		procedure = default_temp_allocator_proc,
-		data = allocator,
+		data      = allocator,
 	}
 }
+
+@(fini, private)
+_destroy_temp_allocator_fini :: proc() {
+	default_temp_allocator_destroy(&global_default_temp_allocator_data)
+}

+ 11 - 10
core/runtime/internal.odin

@@ -184,32 +184,33 @@ mem_free_all :: #force_inline proc(allocator := context.allocator, loc := #calle
 	return
 }
 
-mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) {
+mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
 	if allocator.procedure == nil {
 		return nil, nil
 	}
 	if new_size == 0 {
 		if ptr != nil {
-			_, err := allocator.procedure(allocator.data, .Free, 0, 0, ptr, old_size, loc)
-			return nil, err
+			_, err = allocator.procedure(allocator.data, .Free, 0, 0, ptr, old_size, loc)
+			return
 		}
-		return nil, nil
+		return
 	} else if ptr == nil {
 		return allocator.procedure(allocator.data, .Alloc, new_size, alignment, nil, 0, loc)
 	} else if old_size == new_size && uintptr(ptr) % uintptr(alignment) == 0 {
-		return ([^]byte)(ptr)[:old_size], nil
+		data = ([^]byte)(ptr)[:old_size]
+		return
 	}
 
-	data, err := allocator.procedure(allocator.data, .Resize, new_size, alignment, ptr, old_size, loc)
+	data, err = allocator.procedure(allocator.data, .Resize, new_size, alignment, ptr, old_size, loc)
 	if err == .Mode_Not_Implemented {
 		data, err = allocator.procedure(allocator.data, .Alloc, new_size, alignment, nil, 0, loc)
 		if err != nil {
-			return data, err
+			return
 		}
 		copy(data, ([^]byte)(ptr)[:old_size])
 		_, err = allocator.procedure(allocator.data, .Free, 0, 0, ptr, old_size, loc)
 	}
-	return data, err
+	return
 }
 
 memory_equal :: proc "contextless" (x, y: rawptr, n: int) -> bool {
@@ -223,7 +224,7 @@ memory_equal :: proc "contextless" (x, y: rawptr, n: int) -> bool {
 	
 	when size_of(uint) == 8 {
 		if word_length := length >> 3; word_length != 0 {
-			for i in 0..<word_length {
+			for _ in 0..<word_length {
 				if intrinsics.unaligned_load((^u64)(a)) != intrinsics.unaligned_load((^u64)(b)) {
 					return false
 				}
@@ -254,7 +255,7 @@ memory_equal :: proc "contextless" (x, y: rawptr, n: int) -> bool {
 		return true
 	} else {
 		if word_length := length >> 2; word_length != 0 {
-			for i in 0..<word_length {
+			for _ in 0..<word_length {
 				if intrinsics.unaligned_load((^u32)(a)) != intrinsics.unaligned_load((^u32)(b)) {
 					return false
 				}

+ 22 - 21
core/runtime/print.odin

@@ -2,6 +2,9 @@ package runtime
 
 _INTEGER_DIGITS :: "0123456789abcdefghijklmnopqrstuvwxyz"
 
+@(private="file")
+_INTEGER_DIGITS_VAR := _INTEGER_DIGITS
+
 when !ODIN_DISALLOW_RTTI {
 	print_any_single :: proc(arg: any) {
 		x := arg
@@ -105,14 +108,14 @@ encode_rune :: proc "contextless" (c: rune) -> ([4]u8, int) {
 	return buf, 4
 }
 
-print_string :: proc "contextless" (str: string) -> (int, _OS_Errno) {
-	return os_write(transmute([]byte)str)
+print_string :: proc "contextless" (str: string) -> (n: int) {
+	n, _ = os_write(transmute([]byte)str)
+	return
 }
 
-print_strings :: proc "contextless" (args: ..string) -> (n: int, err: _OS_Errno) {
+print_strings :: proc "contextless" (args: ..string) -> (n: int) {
 	for str in args {
-		m: int
-		m, err = os_write(transmute([]byte)str)
+		m, err := os_write(transmute([]byte)str)
 		n += m
 		if err != 0 {
 			break
@@ -121,8 +124,9 @@ print_strings :: proc "contextless" (args: ..string) -> (n: int, err: _OS_Errno)
 	return
 }
 
-print_byte :: proc "contextless" (b: byte) -> (int, _OS_Errno) {
-	return os_write([]byte{b})
+print_byte :: proc "contextless" (b: byte) -> (n: int) {
+	n, _ = os_write([]byte{b})
+	return
 }
 
 print_encoded_rune :: proc "contextless" (r: rune) {
@@ -141,11 +145,10 @@ print_encoded_rune :: proc "contextless" (r: rune) {
 		if r <= 0 {
 			print_string("\\x00")
 		} else if r < 32 {
-			digits := _INTEGER_DIGITS
 			n0, n1 := u8(r) >> 4, u8(r) & 0xf
 			print_string("\\x")
-			print_byte(digits[n0])
-			print_byte(digits[n1])
+			print_byte(_INTEGER_DIGITS_VAR[n0])
+			print_byte(_INTEGER_DIGITS_VAR[n1])
 		} else {
 			print_rune(r)
 		}
@@ -153,7 +156,7 @@ print_encoded_rune :: proc "contextless" (r: rune) {
 	print_byte('\'')
 }
 
-print_rune :: proc "contextless" (r: rune) -> (int, _OS_Errno) #no_bounds_check {
+print_rune :: proc "contextless" (r: rune) -> int #no_bounds_check {
 	RUNE_SELF :: 0x80
 
 	if r < RUNE_SELF {
@@ -161,29 +164,27 @@ print_rune :: proc "contextless" (r: rune) -> (int, _OS_Errno) #no_bounds_check
 	}
 
 	b, n := encode_rune(r)
-	return os_write(b[:n])
+	m, _ := os_write(b[:n])
+	return m
 }
 
 
 print_u64 :: proc "contextless" (x: u64) #no_bounds_check {
-	digits := _INTEGER_DIGITS
-
 	a: [129]byte
 	i := len(a)
 	b := u64(10)
 	u := x
 	for u >= b {
-		i -= 1; a[i] = digits[u % b]
+		i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b]
 		u /= b
 	}
-	i -= 1; a[i] = digits[u % b]
+	i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b]
 
 	os_write(a[i:])
 }
 
 
 print_i64 :: proc "contextless" (x: i64) #no_bounds_check {
-	digits := _INTEGER_DIGITS
 	b :: i64(10)
 
 	u := x
@@ -193,10 +194,10 @@ print_i64 :: proc "contextless" (x: i64) #no_bounds_check {
 	a: [129]byte
 	i := len(a)
 	for u >= b {
-		i -= 1; a[i] = digits[u % b]
+		i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b]
 		u /= b
 	}
-	i -= 1; a[i] = digits[u % b]
+	i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b]
 	if neg {
 		i -= 1; a[i] = '-'
 	}
@@ -303,7 +304,7 @@ print_type :: proc "contextless" (ti: ^Type_Info) {
 		if info.params == nil {
 			print_string("()")
 		} else {
-			t := info.params.variant.(Type_Info_Tuple)
+			t := info.params.variant.(Type_Info_Parameters)
 			print_byte('(')
 			for t, i in t.types {
 				if i > 0 { print_string(", ") }
@@ -315,7 +316,7 @@ print_type :: proc "contextless" (ti: ^Type_Info) {
 			print_string(" -> ")
 			print_type(info.results)
 		}
-	case Type_Info_Tuple:
+	case Type_Info_Parameters:
 		count := len(info.names)
 		if count != 1 { print_byte('(') }
 		for name, i in info.names {

+ 0 - 36
core/sort/map.odin

@@ -1,36 +0,0 @@
-package sort
-
-import "core:intrinsics"
-import "core:runtime"
-import "core:slice"
-
-_ :: runtime
-_ :: slice
-
-map_entries_by_key :: proc(m: ^$M/map[$K]$V, loc := #caller_location) where intrinsics.type_is_ordered(K) {
-	Entry :: struct {
-		hash:  uintptr,
-		next:  int,
-		key:   K,
-		value: V,
-	}
-	
-	header := runtime.__get_map_header(m)
-	entries := (^[dynamic]Entry)(&header.m.entries)
-	slice.sort_by_key(entries[:], proc(e: Entry) -> K { return e.key })
-	runtime.__dynamic_map_reset_entries(header, loc)
-}
-
-map_entries_by_value :: proc(m: ^$M/map[$K]$V, loc := #caller_location) where intrinsics.type_is_ordered(V) {
-	Entry :: struct {
-		hash:  uintptr,
-		next:  int,
-		key:   K,
-		value: V,
-	}
-	
-	header := runtime.__get_map_header(m)
-	entries := (^[dynamic]Entry)(&header.m.entries)
-	slice.sort_by_key(entries[:], proc(e: Entry) -> V { return e.value })
-	runtime.__dynamic_map_reset_entries(header, loc)
-}

+ 3 - 0
core/strings/strings.odin

@@ -5,6 +5,7 @@ import "core:io"
 import "core:mem"
 import "core:slice"
 import "core:unicode"
+import "core:runtime"
 import "core:unicode/utf8"
 
 // returns a clone of the string `s` allocated using the `allocator`
@@ -1425,7 +1426,9 @@ split_multi :: proc(s: string, substrs: []string, allocator := context.allocator
 
 	// TODO maybe remove duplicate substrs
 	// sort substrings by string size, largest to smallest
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	temp_substrs := slice.clone(substrs, context.temp_allocator)
+	defer delete(temp_substrs)
 	slice.sort_by(temp_substrs, proc(a, b: string) -> bool {
 		return len(a) > len(b)	
 	})

+ 1 - 1
core/sync/primitives.odin

@@ -236,4 +236,4 @@ _panic :: proc "contextless" (msg: string) -> ! {
 	runtime.print_string(msg)
 	runtime.print_byte('\n')
 	runtime.trap()
-}
+}

+ 8 - 0
core/sys/darwin/xnu_system_call_helpers.odin

@@ -97,6 +97,7 @@ clone_to_cstring :: proc(s: string, allocator: runtime.Allocator, loc := #caller
 
 
 sys_open :: proc(path: string, oflag: Open_Flags, mode: Permission) -> (c.int, bool) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	
 	cmode: u32 = 0
 	cflags: u32 = 0
@@ -132,30 +133,35 @@ sys_open :: proc(path: string, oflag: Open_Flags, mode: Permission) -> (c.int, b
 }
 
 sys_mkdir :: proc(path: string, mode: Permission) -> bool {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cpath: cstring = clone_to_cstring(path, context.temp_allocator)
 	cflags := _sys_permission_mode(mode)
 	return syscall_mkdir(cpath, cflags) != -1
 }
 
 sys_mkdir_at :: proc(fd: c.int, path: string, mode: Permission) -> bool {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cpath: cstring = clone_to_cstring(path, context.temp_allocator)
 	cflags := _sys_permission_mode(mode)
 	return syscall_mkdir_at(fd, cpath, cflags) != -1
 }
 
 sys_rmdir :: proc(path: string, mode: Permission) -> bool {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cpath: cstring = clone_to_cstring(path, context.temp_allocator)
 	cflags := _sys_permission_mode(mode)
 	return syscall_rmdir(cpath, cflags) != -1
 }
 
 sys_rename :: proc(path: string, new_path: string) -> bool {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cpath: cstring = clone_to_cstring(path, context.temp_allocator)
 	cnpath: cstring = clone_to_cstring(new_path, context.temp_allocator)
 	return syscall_rename(cpath, cnpath) != -1
 }
 
 sys_rename_at :: proc(fd: c.int, path: string, to_fd: c.int, new_path: string) -> bool {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cpath: cstring = clone_to_cstring(path, context.temp_allocator)
 	cnpath: cstring = clone_to_cstring(new_path, context.temp_allocator)
 	return syscall_rename_at(fd, cpath, to_fd, cnpath) != -1
@@ -166,12 +172,14 @@ sys_lseek :: proc(fd: c.int, offset: i64, whence: Offset_From) -> i64 {
 }
 
 sys_chmod :: proc(path: string, mode: Permission) -> bool {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cpath: cstring = clone_to_cstring(path, context.temp_allocator)
 	cmode := _sys_permission_mode(mode)
 	return syscall_chmod(cpath, cmode) != -1
 }
 
 sys_lstat :: proc(path: string, status: ^stat) -> bool {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cpath: cstring = clone_to_cstring(path, context.temp_allocator)
 	return syscall_lstat(cpath, status) != -1
 }

+ 1 - 1
core/sys/darwin/xnu_system_call_numbers.odin

@@ -1,6 +1,6 @@
 package darwin
 
-unix_offset_syscall :: proc(number: System_Call_Number) -> uintptr {
+unix_offset_syscall :: proc "contextless" (number: System_Call_Number) -> uintptr {
     return uintptr(number) + uintptr(0x2000000)
 }
 

+ 48 - 48
core/sys/darwin/xnu_system_call_wrappers.odin

@@ -229,191 +229,191 @@ _Proc_Bsdinfo :: struct {
 
 /*--==========================================================================--*/
 
-syscall_fsync :: #force_inline proc(fildes: c.int) -> bool {
+syscall_fsync :: #force_inline proc "contextless" (fildes: c.int) -> bool {
 	return !(cast(bool)intrinsics.syscall(unix_offset_syscall(.fsync), uintptr(fildes)))
 }
 
-syscall_write :: #force_inline proc (fildes: c.int, buf: ^byte, nbyte: u64) -> bool {
+syscall_write :: #force_inline proc "contextless" (fildes: c.int, buf: ^byte, nbyte: u64) -> bool {
 	return !(cast(bool)intrinsics.syscall(unix_offset_syscall(.write),  uintptr(fildes), uintptr(buf), uintptr(nbyte)))
 }
  
-syscall_read :: #force_inline proc(fildes: c.int, buf: ^byte, nbyte: u64) -> i64 {
+syscall_read :: #force_inline proc "contextless" (fildes: c.int, buf: ^byte, nbyte: u64) -> i64 {
 	return cast(i64)intrinsics.syscall(unix_offset_syscall(.read), uintptr(fildes), uintptr(buf), uintptr(nbyte))
 }
 
-syscall_open :: #force_inline proc(path: cstring, oflag: u32, mode: u32) -> c.int {
+syscall_open :: #force_inline proc "contextless" (path: cstring, oflag: u32, mode: u32) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.open), transmute(uintptr)path, uintptr(oflag), uintptr(mode))
 }
 
-syscall_close :: #force_inline proc(fd: c.int) -> bool {
+syscall_close :: #force_inline proc "contextless" (fd: c.int) -> bool {
 	return !(cast(bool)intrinsics.syscall(unix_offset_syscall(.close), uintptr(fd)))
 }
 
-syscall_fchmod :: #force_inline proc(fildes: c.int, mode: u32) -> c.int {
+syscall_fchmod :: #force_inline proc "contextless" (fildes: c.int, mode: u32) -> c.int {
 	return (cast(c.int)intrinsics.syscall(unix_offset_syscall(.fchmod), uintptr(fildes), uintptr(mode)))
 }
 
-syscall_chmod :: #force_inline proc(path: cstring, mode: u32) -> c.int {
+syscall_chmod :: #force_inline proc "contextless" (path: cstring, mode: u32) -> c.int {
 	return (cast(c.int)intrinsics.syscall(unix_offset_syscall(.chmod), transmute(uintptr)path, uintptr(mode)))
 }
 
-syscall_mkdir :: #force_inline proc(path: cstring, mode: u32) -> c.int {
+syscall_mkdir :: #force_inline proc "contextless" (path: cstring, mode: u32) -> c.int {
 	return (cast(c.int)intrinsics.syscall(unix_offset_syscall(.mkdir), transmute(uintptr)path, uintptr(mode)))
 }
 
-syscall_mkdir_at :: #force_inline proc(fd: c.int, path: cstring, mode: u32) -> c.int {
+syscall_mkdir_at :: #force_inline proc "contextless" (fd: c.int, path: cstring, mode: u32) -> c.int {
 	return (cast(c.int)intrinsics.syscall(unix_offset_syscall(.mkdir), uintptr(fd), transmute(uintptr)path, uintptr(mode)))
 }
 
-syscall_rmdir :: #force_inline proc(path: cstring, mode: u32) -> c.int {
+syscall_rmdir :: #force_inline proc "contextless" (path: cstring, mode: u32) -> c.int {
 	return (cast(c.int)intrinsics.syscall(unix_offset_syscall(.rmdir), transmute(uintptr)path, uintptr(mode)))
 }
 
-syscall_rename :: #force_inline proc(path_old: cstring, path_new: cstring) -> c.int {
+syscall_rename :: #force_inline proc "contextless" (path_old: cstring, path_new: cstring) -> c.int {
 	return (cast(c.int)intrinsics.syscall(unix_offset_syscall(.rename), transmute(uintptr)path_old, transmute(uintptr)path_new))
 }
 
-syscall_rename_at :: #force_inline proc(from_fd: c.int, from: cstring, to_fd: c.int, to: cstring) -> c.int {
+syscall_rename_at :: #force_inline proc "contextless" (from_fd: c.int, from: cstring, to_fd: c.int, to: cstring) -> c.int {
 	return (cast(c.int)intrinsics.syscall(unix_offset_syscall(.renameat), uintptr(from_fd), transmute(uintptr)from, uintptr(to_fd), transmute(uintptr)to))
 }
 
-syscall_lseek :: #force_inline proc(fd: c.int, offset: i64, whence: c.int) -> i64 {
+syscall_lseek :: #force_inline proc "contextless" (fd: c.int, offset: i64, whence: c.int) -> i64 {
 	return cast(i64)intrinsics.syscall(unix_offset_syscall(.lseek), uintptr(fd), uintptr(offset), uintptr(whence))
 }
 
-syscall_gettid :: #force_inline proc() -> u64 {
+syscall_gettid :: #force_inline proc "contextless" () -> u64 {
 	return cast(u64)intrinsics.syscall(unix_offset_syscall(.gettid))
 }
 
-syscall_fstat :: #force_inline proc(fd: c.int, status: ^stat) -> c.int {
+syscall_fstat :: #force_inline proc "contextless" (fd: c.int, status: ^stat) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.fstat), uintptr(fd), uintptr(status))
 }
 
-syscall_lstat :: #force_inline proc(path: cstring, status: ^stat) -> c.int {
+syscall_lstat :: #force_inline proc "contextless" (path: cstring, status: ^stat) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.lstat), transmute(uintptr)path, uintptr(status))
 }
 
-syscall_stat :: #force_inline proc(path: cstring, status: ^stat) -> c.int {
+syscall_stat :: #force_inline proc "contextless" (path: cstring, status: ^stat) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.stat), transmute(uintptr)path, uintptr(status))
 }
 
-syscall_fstatat :: #force_inline proc(fd: c.int, path: cstring, status: ^stat) -> c.int {
+syscall_fstatat :: #force_inline proc "contextless" (fd: c.int, path: cstring, status: ^stat) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.fstatat), uintptr(fd), transmute(uintptr)path, uintptr(status))
 }
 
-syscall_link :: #force_inline proc(path: cstring, to_link: cstring) -> c.int {
+syscall_link :: #force_inline proc "contextless" (path: cstring, to_link: cstring) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.link), transmute(uintptr)path, transmute(uintptr)to_link)
 }
 
-syscall_linkat :: #force_inline proc(fd: c.int, path: cstring, fd2: c.int, to_link: cstring) -> c.int {
+syscall_linkat :: #force_inline proc "contextless" (fd: c.int, path: cstring, fd2: c.int, to_link: cstring) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.linkat), uintptr(fd), transmute(uintptr)path, uintptr(fd2), transmute(uintptr)to_link)
 }
 
-syscall_readlink :: #force_inline proc(path: cstring, buf: ^u8, buf_size: u64) -> i64 {
+syscall_readlink :: #force_inline proc "contextless" (path: cstring, buf: ^u8, buf_size: u64) -> i64 {
 	return cast(i64)intrinsics.syscall(unix_offset_syscall(.readlink), transmute(uintptr)path, uintptr(buf), uintptr(buf_size))
 }
 
-syscall_readlinkat :: #force_inline proc(fd: c.int, path: cstring, buf: ^u8, buf_size: u64) -> i64 {
+syscall_readlinkat :: #force_inline proc "contextless" (fd: c.int, path: cstring, buf: ^u8, buf_size: u64) -> i64 {
 	return cast(i64)intrinsics.syscall(unix_offset_syscall(.readlinkat), uintptr(fd), transmute(uintptr)path, uintptr(buf), uintptr(buf_size))
 }
 
-syscall_access :: #force_inline proc(path: cstring, mode: c.int) -> c.int {
+syscall_access :: #force_inline proc "contextless" (path: cstring, mode: c.int) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.access), transmute(uintptr)path, uintptr(mode))
 }
 
-syscall_faccessat :: #force_inline proc(fd: c.int, path: cstring, mode: c.int, flag: c.int) -> c.int {
+syscall_faccessat :: #force_inline proc "contextless" (fd: c.int, path: cstring, mode: c.int, flag: c.int) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.faccessat), uintptr(fd), transmute(uintptr)path, uintptr(mode), uintptr(flag))
 }
 
-syscall_getdirentries :: #force_inline proc(fd: c.int, buf: ^u8, nbytes: c.int, base_pointer: ^u32) -> c.int {
+syscall_getdirentries :: #force_inline proc "contextless" (fd: c.int, buf: ^u8, nbytes: c.int, base_pointer: ^u32) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.getdirentries), uintptr(fd), uintptr(buf), uintptr(nbytes), uintptr(base_pointer))
 }
 
-syscall_truncate :: #force_inline proc (path: cstring, length: off_t) -> c.int {
+syscall_truncate :: #force_inline proc "contextless" (path: cstring, length: off_t) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.truncate), transmute(uintptr)path, uintptr(length))
 }
 
-syscall_ftruncate :: #force_inline proc (fd: c.int, length: off_t) -> c.int {
+syscall_ftruncate :: #force_inline proc "contextless" (fd: c.int, length: off_t) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.ftruncate), uintptr(fd), uintptr(length))
 }
 
-syscall_sysctl :: #force_inline proc (name: ^c.int, namelen: c.uint, oldp: rawptr, oldlenp: ^i64, newp: ^i8, newlen: i64) -> c.int {
+syscall_sysctl :: #force_inline proc "contextless" (name: ^c.int, namelen: c.uint, oldp: rawptr, oldlenp: ^i64, newp: ^i8, newlen: i64) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.sysctl), uintptr(name), uintptr(namelen), uintptr(oldp), uintptr(oldlenp), uintptr(newp), uintptr(newlen))
 }
 
-syscall_copyfile ::  #force_inline proc(from: cstring, to: cstring, state: rawptr, flags: u32) -> c.int {
+syscall_copyfile ::  #force_inline proc "contextless" (from: cstring, to: cstring, state: rawptr, flags: u32) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.copyfile), transmute(uintptr)from, transmute(uintptr)to, uintptr(state), uintptr(flags))
 } 
 
 // think about this? last arg should be more than one
-syscall_fcntl :: #force_inline proc(fd: c.int, cmd: c.int, other: rawptr) -> c.int {
+syscall_fcntl :: #force_inline proc "contextless" (fd: c.int, cmd: c.int, other: rawptr) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.fsctl), uintptr(fd), uintptr(cmd), uintptr(other))
 }
 
-syscall_exit :: #force_inline proc(code: c.int) {
+syscall_exit :: #force_inline proc "contextless" (code: c.int) {
 	intrinsics.syscall(unix_offset_syscall(.exit), uintptr(code))
 }
 
-syscall_kill :: #force_inline proc(pid: pid_t, sig: c.int) -> c.int {
+syscall_kill :: #force_inline proc "contextless" (pid: pid_t, sig: c.int) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.kill), uintptr(pid), uintptr(sig))
 }
 
-syscall_dup :: #force_inline proc(fd: c.int) -> c.int {
+syscall_dup :: #force_inline proc "contextless" (fd: c.int) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.dup), uintptr(fd))
 }
 
-syscall_execve :: #force_inline proc(path: cstring, argv: [^]cstring, env: [^]cstring) -> c.int {
+syscall_execve :: #force_inline proc "contextless" (path: cstring, argv: [^]cstring, env: [^]cstring) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.execve), transmute(uintptr)path, transmute(uintptr)argv, transmute(uintptr)env)
 }
 
-syscall_munmap :: #force_inline proc(addr: rawptr, len: u64) -> c.int {
+syscall_munmap :: #force_inline proc "contextless" (addr: rawptr, len: u64) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.mmap), uintptr(addr), uintptr(len))
 }
 
-syscall_mmap :: #force_inline proc(addr: ^u8, len: u64, port: c.int, flags: c.int, fd: int, offset: off_t) -> ^u8 {
+syscall_mmap :: #force_inline proc "contextless" (addr: ^u8, len: u64, port: c.int, flags: c.int, fd: int, offset: off_t) -> ^u8 {
 	return cast(^u8)intrinsics.syscall(unix_offset_syscall(.mmap), uintptr(addr), uintptr(len), uintptr(port), uintptr(flags), uintptr(fd), uintptr(offset))
 }
 
-syscall_flock :: #force_inline proc(fd: c.int, operation: c.int) -> c.int {
+syscall_flock :: #force_inline proc "contextless" (fd: c.int, operation: c.int) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.flock), uintptr(fd), uintptr(operation)) 
 }
 
-syscall_utimes :: #force_inline proc(path: cstring, times: ^timeval) -> c.int {
+syscall_utimes :: #force_inline proc "contextless" (path: cstring, times: ^timeval) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.utimes), transmute(uintptr)path, uintptr(times))
 }
 
-syscall_futimes :: #force_inline proc(fd: c.int, times: ^timeval) -> c.int {
+syscall_futimes :: #force_inline proc "contextless" (fd: c.int, times: ^timeval) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.futimes), uintptr(fd), uintptr(times))
 }
 
-syscall_adjtime :: #force_inline proc(delta: ^timeval, old_delta: ^timeval) -> c.int {
+syscall_adjtime :: #force_inline proc "contextless" (delta: ^timeval, old_delta: ^timeval) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.adjtime), uintptr(delta), uintptr(old_delta))
 }
 
-syscall_sysctlbyname :: #force_inline proc(name: cstring, oldp: rawptr, oldlenp: ^i64, newp: rawptr, newlen: i64) -> c.int {
+syscall_sysctlbyname :: #force_inline proc "contextless" (name: cstring, oldp: rawptr, oldlenp: ^i64, newp: rawptr, newlen: i64) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.sysctlbyname), transmute(uintptr)name, uintptr(oldp), uintptr(oldlenp), uintptr(newp), uintptr(newlen))
 }
 
-syscall_proc_info :: #force_inline proc(num: c.int, pid: u32, flavor: c.int, arg: u64, buffer: rawptr, buffer_size: c.int) -> c.int {
+syscall_proc_info :: #force_inline proc "contextless" (num: c.int, pid: u32, flavor: c.int, arg: u64, buffer: rawptr, buffer_size: c.int) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.proc_info), uintptr(num), uintptr(pid), uintptr(flavor), uintptr(arg), uintptr(buffer), uintptr(buffer_size))
 }
 
-syscall_openat :: #force_inline proc(fd: int, path: cstring, oflag: u32, mode: u32) -> c.int {
+syscall_openat :: #force_inline proc "contextless" (fd: int, path: cstring, oflag: u32, mode: u32) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.openat), uintptr(fd), transmute(uintptr)path, uintptr(oflag), uintptr(mode))
 } 
 
-syscall_getentropy :: #force_inline proc(buf: [^]u8, buflen: u64) -> c.int {
+syscall_getentropy :: #force_inline proc "contextless" (buf: [^]u8, buflen: u64) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.getentropy), uintptr(buf), uintptr(buflen))
 }
 
-syscall_pipe :: #force_inline proc(fds: [^]c.int) -> c.int {
+syscall_pipe :: #force_inline proc "contextless" (fds: [^]c.int) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.getentropy), uintptr(&fds[0]), uintptr(&fds[1]))
 }
 
-syscall_chdir :: #force_inline proc(path: cstring) -> c.int {
+syscall_chdir :: #force_inline proc "contextless" (path: cstring) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.getentropy), transmute(uintptr)path)
 }
 
-syscall_fchdir :: #force_inline proc(fd: c.int, path: cstring) -> c.int {
+syscall_fchdir :: #force_inline proc "contextless" (fd: c.int, path: cstring) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.getentropy), uintptr(fd), transmute(uintptr)path)
-}
+}

+ 5 - 0
core/sys/info/platform_darwin.odin

@@ -4,6 +4,7 @@ package sysinfo
 import sys "core:sys/unix"
 import "core:strconv"
 import "core:strings"
+import "core:runtime"
 
 @(private)
 version_string_buf: [1024]u8
@@ -41,6 +42,8 @@ init_os_version :: proc () {
 
 		major_ok, minor_ok, patch_ok: bool
 
+		tmp := runtime.default_temp_allocator_temp_begin()
+
 		triplet := strings.split(string(cstring(&version_bits[0])), ".", context.temp_allocator)
 		if len(triplet) != 3 {
 			have_kernel_version = false
@@ -54,6 +57,8 @@ init_os_version :: proc () {
 			}
 		}
 
+		runtime.default_temp_allocator_temp_end(tmp)
+
 		if !have_kernel_version {
 			// We don't know the kernel version, but we do know the build
 			strings.write_string(&b, "macOS Unknown (build ")

+ 3 - 0
core/sys/info/platform_freebsd.odin

@@ -4,6 +4,7 @@ package sysinfo
 import sys "core:sys/unix"
 import "core:strings"
 import "core:strconv"
+import "core:runtime"
 
 @(private)
 version_string_buf: [1024]u8
@@ -47,6 +48,8 @@ init_os_version :: proc () {
 		return
 	}
 
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+
 	// Parse kernel version
 	release := string(cstring(raw_data(kernel_version_buf[:])))
 	version_bits := strings.split_n(release, "-", 2, context.temp_allocator)

+ 3 - 0
core/sys/info/platform_linux.odin

@@ -4,6 +4,7 @@ package sysinfo
 import "core:c"
 import sys "core:sys/unix"
 import "core:intrinsics"
+import "core:runtime"
 import "core:os"
 import "core:strings"
 import "core:strconv"
@@ -69,6 +70,8 @@ init_os_version :: proc () {
 	l := strings.builder_len(b)
 	strings.write_string(&b, string(cstring(&uts.release[0])))
 
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+
 	// Parse kernel version, as substrings of the version info in `version_string_buf`
 	version_bits := strings.split_n(strings.to_string(b)[l:], "-", 2, context.temp_allocator)
 	if len(version_bits) > 1 {

+ 4 - 1
core/sys/info/platform_openbsd.odin

@@ -4,6 +4,7 @@ package sysinfo
 import sys "core:sys/unix"
 import "core:strings"
 import "core:strconv"
+import "core:runtime"
 
 @(private)
 version_string_buf: [1024]u8
@@ -32,7 +33,9 @@ init_os_version :: proc () {
 	version := string(cstring(raw_data(kernel_version_buf[:])))
 	strings.write_string(&b, version)
 
-	// // Parse kernel version
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+
+	// Parse kernel version
 	triplet := strings.split(version, ".", context.temp_allocator)
 	if len(triplet) == 2 {
 		major, major_ok := strconv.parse_int(triplet[0])

+ 3 - 0
core/sys/info/platform_windows.odin

@@ -7,6 +7,7 @@ import "core:strings"
 import "core:unicode/utf16"
 
 import "core:fmt"
+import "core:runtime"
 
 @(private)
 version_string_buf: [1024]u8
@@ -314,6 +315,8 @@ read_reg :: proc(hkey: sys.HKEY, subkey, val: string, $T: typeid) -> (res: T, ok
 		return {}, false
 	}
 
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+
 	key_name_wide := make([]u16, BUF_SIZE, context.temp_allocator)
 	val_name_wide := make([]u16, BUF_SIZE, context.temp_allocator)
 

+ 218 - 11
core/sys/unix/syscalls_linux.odin

@@ -1518,6 +1518,7 @@ when ODIN_ARCH == .amd64 {
 	#panic("Unsupported architecture")
 }
 
+
 // syscall related constants
 AT_FDCWD            :: ~uintptr(99)
 AT_REMOVEDIR        :: uintptr(0x200)
@@ -1563,15 +1564,167 @@ MADV_WIPEONFORK  :: 18
 MADV_KEEPONFORK  :: 19
 MADV_HWPOISON    :: 100
 
+// pipe2 flags
+O_CLOEXEC :: 0o2000000
+
+// perf event data
+Perf_Sample :: struct #raw_union {
+	period:    u64,
+	frequency: u64,
+}
+Perf_Wakeup :: struct #raw_union {
+	events:    u32,
+	watermark: u32,
+}
+Perf_Field1 :: struct #raw_union {
+	breakpoint_addr: u64,
+	kprobe_func:     u64,
+	uprobe_path:     u64,
+	config1:         u64,
+}
+Perf_Field2 :: struct #raw_union {
+	breakpoint_len: u64,
+	kprobe_addr:    u64,
+	uprobe_offset:  u64,
+	config2:        u64,
+}
+Perf_Event_Attr :: struct #packed {
+	type:   u32,
+	size:   u32,
+	config: u64,
+	sample: Perf_Sample,
+	sample_type: u64,
+	read_format: u64,
+	flags:       Perf_Flags,
+	wakeup: Perf_Wakeup,
+	breakpoint_type: u32,
+	field1: Perf_Field1,
+	field2: Perf_Field2,
+	branch_sample_type: u64,
+	sample_regs_user:   u64,
+	sample_stack_user:  u32,
+	clock_id:           i32,
+	sample_regs_intr:   u64,
+	aux_watermark:      u32,
+	sample_max_stack:   u16,
+	_padding:           u16,
+}
+
+Perf_Event_Flags :: distinct bit_set[Perf_Event_Flag; u64]
+Perf_Event_Flag :: enum u64 {
+	Bit0               = 0,
+	Bit0_Is_Deprecated = 1,
+	User_Rdpmc         = 2,
+	User_Time          = 3,
+	User_Time_Zero     = 4,
+	User_Time_Short    = 5,
+}
+Perf_Capabilities :: struct #raw_union {
+	capabilities: u64,
+	flags: Perf_Event_Flags,
+}
+Perf_Event_mmap_Page :: struct #packed {
+	version:        u32,
+	compat_version: u32,
+	lock:           u32,
+	index:          u32,
+	offset:         i64,
+	time_enabled:   u64,
+	time_running:   u64,
+	cap: Perf_Capabilities,
+	pmc_width:      u16,
+	time_shift:     u16,
+	time_mult:      u32,
+	time_offset:    u64,
+	time_zero:      u64,
+	size:           u32,
+	reserved1:      u32,
+	time_cycles:    u64,
+	time_mask:      u64,
+	reserved2:      [116*8]u8,
+	data_head:      u64,
+	data_tail:      u64,
+	data_offset:    u64,
+	data_size:      u64,
+	aux_head:       u64,
+	aux_tail:       u64,
+	aux_offset:     u64,
+	aux_size:       u64,
+}
+
+Perf_Type_Id :: enum u32 {
+	Hardware   = 0,
+	Software   = 1,
+	Tracepoint = 2,
+	HW_Cache   = 3,
+	Raw        = 4,
+	Breakpoint = 5,
+}
+
+Perf_Hardware_Id :: enum u64 {
+	CPU_Cycles              = 0,
+	Instructions            = 1,
+	Cache_References        = 2,
+	Cache_Misses            = 3,
+	Branch_Instructions     = 4,
+	Branch_Misses           = 5,
+	Bus_Cycles              = 6,
+	Stalled_Cycles_Frontend = 7,
+	Stalled_Cycles_Backend  = 8,
+	Ref_CPU_Cycles          = 9,
+}
+
+Perf_Flags :: distinct bit_set[Perf_Flag; u64]
+Perf_Flag :: enum u64 {
+	Disabled       = 0,
+	Inherit        = 1,
+	Pinned         = 2,
+	Exclusive      = 3,
+	Exclude_User   = 4,
+	Exclude_Kernel = 5,
+	Exclude_HV     = 6,
+	Exclude_Idle   = 7,
+	mmap           = 8,
+	Comm           = 9,
+	Freq           = 10,
+	Inherit_Stat   = 11,
+	Enable_On_Exec = 12,
+	Task           = 13,
+	Watermark      = 14,
+	Precise_IP_0   = 15,
+	Precise_IP_1   = 16,
+	mmap_Data      = 17,
+	Sample_Id_All  = 18,
+	Exclude_Host   = 19,
+	Exclude_Guest  = 20,
+	Exclude_Callchain_Kernel = 21,
+	Exclude_Callchain_User   = 22,
+	mmap2          = 23,
+	Comm_Exec      = 24,
+	Use_Clockid    = 25,
+	Context_Switch = 26,
+	Write_Backward = 27,
+	Namespaces     = 28,
+	KSymbol        = 29,
+	BPF_Event      = 30,
+	Aux_Output     = 31,
+	CGroup         = 32,
+	Text_Poke      = 33,
+	Build_Id       = 34,
+	Inherit_Thread = 35,
+	Remove_On_Exec = 36,
+	Sigtrap        = 37,
+}
+
 sys_gettid :: proc "contextless" () -> int {
-	return cast(int)intrinsics.syscall(SYS_gettid)
+	return int(intrinsics.syscall(SYS_gettid))
 }
 
-sys_getrandom :: proc "contextless" (buf: [^]byte, buflen: int, flags: uint) -> int {
-	return cast(int)intrinsics.syscall(SYS_getrandom, uintptr(buf), uintptr(buflen), uintptr(flags))
+sys_getrandom :: proc "contextless" (buf: [^]byte, buflen: uint, flags: int) -> int {
+	return int(intrinsics.syscall(SYS_getrandom, uintptr(buf), uintptr(buflen), uintptr(flags)))
 }
 
-sys_open :: proc "contextless" (path: cstring, flags: int, mode: int = 0o000) -> int {
+sys_open :: proc "contextless" (path: cstring, flags: int, mode: uint = 0o000) -> int {
 	when ODIN_ARCH != .arm64 {
 		return int(intrinsics.syscall(SYS_open, uintptr(rawptr(path)), uintptr(flags), uintptr(mode)))
 	} else { // NOTE: arm64 does not have open
@@ -1579,7 +1732,7 @@ sys_open :: proc "contextless" (path: cstring, flags: int, mode: int = 0o000) ->
 	}
 }
 
-sys_openat :: proc "contextless" (dfd: int, path: cstring, flags: int, mode: int = 0o000) -> int {
+sys_openat :: proc "contextless" (dfd: int, path: cstring, flags: int, mode: uint = 0o000) -> int {
 	return int(intrinsics.syscall(SYS_openat, uintptr(dfd), uintptr(rawptr(path)), uintptr(flags), uintptr(mode)))
 }
 
@@ -1691,7 +1844,7 @@ sys_fchdir :: proc "contextless" (fd: int) -> int {
 	return int(intrinsics.syscall(SYS_fchdir, uintptr(fd)))
 }
 
-sys_chmod :: proc "contextless" (path: cstring, mode: int) -> int {
+sys_chmod :: proc "contextless" (path: cstring, mode: uint) -> int {
 	when ODIN_ARCH != .arm64 {
 		return int(intrinsics.syscall(SYS_chmod, uintptr(rawptr(path)), uintptr(mode)))
 	} else { // NOTE: arm64 does not have chmod
@@ -1699,7 +1852,7 @@ sys_chmod :: proc "contextless" (path: cstring, mode: int) -> int {
 	}
 }
 
-sys_fchmod :: proc "contextless" (fd: int, mode: int) -> int {
+sys_fchmod :: proc "contextless" (fd: int, mode: uint) -> int {
 	return int(intrinsics.syscall(SYS_fchmod, uintptr(fd), uintptr(mode)))
 }
 
@@ -1759,7 +1912,7 @@ sys_rmdir :: proc "contextless" (path: cstring) -> int {
 	}
 }
 
-sys_mkdir :: proc "contextless" (path: cstring, mode: int) -> int {
+sys_mkdir :: proc "contextless" (path: cstring, mode: uint) -> int {
 	when ODIN_ARCH != .arm64 {
 		return int(intrinsics.syscall(SYS_mkdir, uintptr(rawptr(path)), uintptr(mode)))
 	} else { // NOTE: arm64 does not have mkdir
@@ -1767,11 +1920,11 @@ sys_mkdir :: proc "contextless" (path: cstring, mode: int) -> int {
 	}
 }
 
-sys_mkdirat :: proc "contextless" (dfd: int, path: cstring, mode: int) -> int {
+sys_mkdirat :: proc "contextless" (dfd: int, path: cstring, mode: uint) -> int {
 	return int(intrinsics.syscall(SYS_mkdirat, uintptr(dfd), uintptr(rawptr(path)), uintptr(mode)))
 }
 
-sys_mknod :: proc "contextless" (path: cstring, mode: int, dev: int) -> int {
+sys_mknod :: proc "contextless" (path: cstring, mode: uint, dev: int) -> int {
 	when ODIN_ARCH != .arm64 {
 		return int(intrinsics.syscall(SYS_mknod, uintptr(rawptr(path)), uintptr(mode), uintptr(dev)))
 	} else { // NOTE: arm64 does not have mknod
@@ -1779,7 +1932,7 @@ sys_mknod :: proc "contextless" (path: cstring, mode: int, dev: int) -> int {
 	}
 }
 
-sys_mknodat :: proc "contextless" (dfd: int, path: cstring, mode: int, dev: int) -> int {
+sys_mknodat :: proc "contextless" (dfd: int, path: cstring, mode: uint, dev: int) -> int {
 	return int(intrinsics.syscall(SYS_mknodat, uintptr(dfd), uintptr(rawptr(path)), uintptr(mode), uintptr(dev)))
 }
 
@@ -1818,6 +1971,16 @@ sys_fork :: proc "contextless" () -> int {
 		return int(intrinsics.syscall(SYS_clone, SIGCHLD))
 	}
 }
+sys_pipe2 :: proc "contextless" (fds: rawptr, flags: int) -> int {
+	return int(intrinsics.syscall(SYS_pipe2, uintptr(fds), uintptr(flags)))
+}
+sys_dup2 :: proc "contextless" (oldfd: int, newfd: int) -> int {
+	when ODIN_ARCH != .arm64 {
+		return int(intrinsics.syscall(SYS_dup2, uintptr(oldfd), uintptr(newfd)))
+	} else {
+		return int(intrinsics.syscall(SYS_dup3, uintptr(oldfd), uintptr(newfd), 0))
+	}
+}
 
 sys_mmap :: proc "contextless" (addr: rawptr, length: uint, prot, flags, fd: int, offset: uintptr) -> int {
 	return int(intrinsics.syscall(SYS_mmap, uintptr(addr), uintptr(length), uintptr(prot), uintptr(flags), uintptr(fd), offset))
@@ -1846,6 +2009,50 @@ sys_utimensat :: proc "contextless" (dfd: int, path: cstring, times: rawptr, fla
 	return int(intrinsics.syscall(SYS_utimensat, uintptr(dfd), uintptr(rawptr(path)), uintptr(times), uintptr(flags)))
 }
 
+sys_socket :: proc "contextless" (domain: int, type: int, protocol: int) -> int {
+	return int(intrinsics.syscall(SYS_socket, uintptr(domain), uintptr(type), uintptr(protocol)))
+}
+
+sys_connect :: proc "contextless" (sd: int, addr: rawptr, len: i32) -> int {
+	return int(intrinsics.syscall(SYS_connect, uintptr(sd), uintptr(addr), uintptr(len)))
+}
+
+sys_accept :: proc "contextless" (sd: int, addr: rawptr, len: rawptr) -> int {
+	return int(intrinsics.syscall(SYS_accept4, uintptr(sd), uintptr(addr), uintptr(len), uintptr(0)))
+}
+
+sys_listen :: proc "contextless" (sd: int, backlog: int) -> int {
+	return int(intrinsics.syscall(SYS_listen, uintptr(sd), uintptr(backlog)))
+}
+
+sys_bind :: proc "contextless" (sd: int, addr: rawptr, len: i32) -> int {
+	return int(intrinsics.syscall(SYS_bind, uintptr(sd), uintptr(addr), uintptr(len)))
+}
+
+sys_setsockopt :: proc "contextless" (sd: int, level: int, optname: int, optval: rawptr, optlen: i32) -> int {
+	return int(intrinsics.syscall(SYS_setsockopt, uintptr(sd), uintptr(level), uintptr(optname), uintptr(optval), uintptr(optlen)))
+}
+
+sys_recvfrom :: proc "contextless" (sd: int, buf: rawptr, len: uint, flags: int, addr: rawptr, alen: uintptr) -> i64 {
+	return i64(intrinsics.syscall(SYS_recvfrom, uintptr(sd), uintptr(buf), uintptr(len), uintptr(flags), uintptr(addr), uintptr(alen)))
+}
+
+sys_sendto :: proc "contextless" (sd: int, buf: rawptr, len: uint, flags: int, addr: rawptr, alen: i32) -> i64 {
+	return i64(intrinsics.syscall(SYS_sendto, uintptr(sd), uintptr(buf), uintptr(len), uintptr(flags), uintptr(addr), uintptr(alen)))
+}
+
+sys_shutdown :: proc "contextless" (sd: int, how: int) -> int {
+	return int(intrinsics.syscall(SYS_shutdown, uintptr(sd), uintptr(how)))
+}
+
+sys_perf_event_open :: proc "contextless" (event_attr: rawptr, pid: i32, cpu: i32, group_fd: i32, flags: u32) -> int {
+	return int(intrinsics.syscall(SYS_perf_event_open, uintptr(event_attr), uintptr(pid), uintptr(cpu), uintptr(group_fd), uintptr(flags)))
+}
+
+sys_personality :: proc(persona: u64) -> int {
+	return int(intrinsics.syscall(SYS_personality, uintptr(persona)))
+}
+
 get_errno :: proc "contextless" (res: int) -> i32 {
 	if res < 0 && res > -4096 {
 		return i32(-res)

+ 10 - 0
core/sys/windows/dnsapi.odin

@@ -0,0 +1,10 @@
+// +build windows
+package sys_windows
+
+foreign import "system:Dnsapi.lib"
+
+@(default_calling_convention="std")
+foreign Dnsapi {
+    DnsQuery_UTF8 :: proc(name: cstring, type: u16, options: DWORD, extra: PVOID, results: ^^DNS_RECORD, reserved: PVOID) -> DNS_STATUS ---
+    DnsRecordListFree :: proc(list: ^DNS_RECORD, options: DWORD) ---
+}

+ 234 - 0
core/sys/windows/ip_helper.odin

@@ -0,0 +1,234 @@
+// +build windows
+package sys_windows
+
+foreign import "system:iphlpapi.lib"
+
+Address_Family :: enum u32 {
+	Unspecified = 0,   // Return both IPv4 and IPv6 addresses associated with adapters with them enabled.
+	IPv4        = 2,   // Return only IPv4 addresses associated with adapters with it enabled.
+	IPv6        = 23,  // Return only IPv6 addresses associated with adapters with it enabled.
+}
+
+GAA_Flag :: enum u32 {
+	Skip_Unicast                 = 0,  // Do not return unicast addresses.
+	Skip_Anycast                 = 1,  // Do not return IPv6 anycast addresses.
+	Skip_Multicast               = 2,  // Do not return multicast addresses.
+	Skip_DNS_Server              = 3,  // Do not return addresses of DNS servers.
+	Include_Prefix               = 4,  // (XP SP1+) Return a list of IP address prefixes on this adapter. When this flag is set, IP address prefixes are returned for both IPv6 and IPv4 addresses.
+	Skip_Friendly_Name           = 5,  // Do not return the adapter friendly name.
+	Include_WINS_info            = 6,  // (Vista+) Return addresses of Windows Internet Name Service (WINS) servers.
+	Include_Gateways             = 7,  // (Vista+) Return the addresses of default gateways.
+	Include_All_Interfaces       = 8,  // (Vista+) Return addresses for all NDIS interfaces.
+	Include_All_Compartments     = 9,  // (Reserved, Unsupported) Return addresses in all routing compartments.
+	Include_Tunnel_Binding_Order = 10, // (Vista+) Return the adapter addresses sorted in tunnel binding order.
+}
+GAA_Flags :: bit_set[GAA_Flag; u32]
+
+IP_Adapter_Addresses :: struct {
+	Raw: struct #raw_union {
+		Alignment: u64,
+		Anonymous: struct {
+			Length:  u32,
+			IfIndex: u32,
+		},
+	},
+	Next:                   ^IP_Adapter_Addresses,
+	AdapterName:            cstring,
+	FirstUnicastAddress:    ^IP_ADAPTER_UNICAST_ADDRESS_LH,
+	FirstAnycastAddress:    ^IP_ADAPTER_ANYCAST_ADDRESS_XP,
+	FirstMulticastAddress:  ^IP_ADAPTER_MULTICAST_ADDRESS_XP,
+	FirstDnsServerAddress:  ^IP_ADAPTER_DNS_SERVER_ADDRESS_XP,
+	DnsSuffix:              ^u16,
+	Description:            ^u16,
+	FriendlyName:           ^u16,
+	PhysicalAddress:        [8]u8,
+	PhysicalAddressLength:  u32,
+	Anonymous2:             struct #raw_union {
+		Flags:     u32,
+		Anonymous: struct {
+			_bitfield: u32,
+		},
+	},
+	MTU:                    u32,
+	IfType:                 u32,
+	OperStatus:             IF_OPER_STATUS,
+	Ipv6IfIndex:            u32,
+	ZoneIndices:            [16]u32,
+	FirstPrefix:            rawptr, // ^IP_ADAPTER_PREFIX_XP,
+	TransmitLinkSpeed:      u64,
+	ReceiveLinkSpeed:       u64,
+	FirstWinsServerAddress: rawptr, // ^IP_ADAPTER_WINS_SERVER_ADDRESS_LH,
+	FirstGatewayAddress:    ^IP_ADAPTER_GATEWAY_ADDRESS_LH,
+	Ipv4Metric:             u32,
+	Ipv6Metric:             u32,
+	Luid:                   NET_LUID_LH,
+	Dhcpv4Server:           SOCKET_ADDRESS,
+	CompartmentId:          u32,
+	NetworkGuid:            GUID,
+	ConnectionType:         NET_IF_CONNECTION_TYPE,
+	TunnelType:             TUNNEL_TYPE,
+	Dhcpv6Server:           SOCKET_ADDRESS,
+	Dhcpv6ClientDuid:       [130]u8,
+	Dhcpv6ClientDuidLength: u32,
+	Dhcpv6Iaid:             u32,
+	FirstDnsSuffix:         rawptr, // ^IP_ADAPTER_DNS_SUFFIX,
+}
+
+IP_ADAPTER_UNICAST_ADDRESS_LH :: struct {
+	Anonymous:          struct #raw_union {
+		Alignment: u64,
+		Anonymous: struct {
+			Length: u32,
+			Flags:  u32,
+		},
+	},
+	Next:               ^IP_ADAPTER_UNICAST_ADDRESS_LH,
+	Address:            SOCKET_ADDRESS,
+	PrefixOrigin:       NL_PREFIX_ORIGIN,
+	SuffixOrigin:       NL_SUFFIX_ORIGIN,
+	DadState:           NL_DAD_STATE,
+	ValidLifetime:      u32,
+	PreferredLifetime:  u32,
+	LeaseLifetime:      u32,
+	OnLinkPrefixLength: u8,
+}
+
+IP_ADAPTER_ANYCAST_ADDRESS_XP :: struct {
+	Anonymous: struct #raw_union {
+		Alignment: u64,
+		Anonymous: struct {
+			Length: u32,
+			Flags:  u32,
+		},
+	},
+	Next:      ^IP_ADAPTER_ANYCAST_ADDRESS_XP,
+	Address:   SOCKET_ADDRESS,
+}
+
+IP_ADAPTER_MULTICAST_ADDRESS_XP :: struct {
+	Anonymous: struct #raw_union {
+		Alignment: u64,
+		Anonymous: struct {
+			Length: u32,
+			Flags:  u32,
+		},
+	},
+	Next:      ^IP_ADAPTER_MULTICAST_ADDRESS_XP,
+	Address:   SOCKET_ADDRESS,
+}
+
+IP_ADAPTER_GATEWAY_ADDRESS_LH :: struct {
+	Anonymous: struct #raw_union {
+		Alignment: u64,
+		Anonymous: struct {
+			Length:   u32,
+			Reserved: u32,
+		},
+	},
+	Next:      ^IP_ADAPTER_GATEWAY_ADDRESS_LH,
+	Address:   SOCKET_ADDRESS,
+}
+
+IP_ADAPTER_DNS_SERVER_ADDRESS_XP :: struct {
+	Anonymous: struct #raw_union {
+		Alignment: u64,
+		Anonymous: struct {
+			Length:   u32,
+			Reserved: u32,
+		},
+	},
+	Next:      ^IP_ADAPTER_DNS_SERVER_ADDRESS_XP,
+	Address:   SOCKET_ADDRESS,
+}
+
+IF_OPER_STATUS :: enum i32 {
+	Up             = 1,
+	Down           = 2,
+	Testing        = 3,
+	Unknown        = 4,
+	Dormant        = 5,
+	NotPresent     = 6,
+	LowerLayerDown = 7,
+}
+
+NET_LUID_LH :: struct #raw_union {
+	Value: u64,
+	Info:  struct {
+		_bitfield: u64,
+	},
+}
+
+SOCKET_ADDRESS :: struct {
+	lpSockaddr:      ^SOCKADDR,
+	iSockaddrLength: i32,
+}
+
+NET_IF_CONNECTION_TYPE :: enum i32 {
+	NET_IF_CONNECTION_DEDICATED = 1,
+	NET_IF_CONNECTION_PASSIVE   = 2,
+	NET_IF_CONNECTION_DEMAND    = 3,
+	NET_IF_CONNECTION_MAXIMUM   = 4,
+}
+
+TUNNEL_TYPE :: enum i32 {
+	TUNNEL_TYPE_NONE    = 0,
+	TUNNEL_TYPE_OTHER   = 1,
+	TUNNEL_TYPE_DIRECT  = 2,
+	TUNNEL_TYPE_6TO4    = 11,
+	TUNNEL_TYPE_ISATAP  = 13,
+	TUNNEL_TYPE_TEREDO  = 14,
+	TUNNEL_TYPE_IPHTTPS = 15,
+}
+NL_PREFIX_ORIGIN :: enum i32 {
+	IpPrefixOriginOther               = 0,
+	IpPrefixOriginManual              = 1,
+	IpPrefixOriginWellKnown           = 2,
+	IpPrefixOriginDhcp                = 3,
+	IpPrefixOriginRouterAdvertisement = 4,
+	IpPrefixOriginUnchanged           = 16,
+}
+
+NL_SUFFIX_ORIGIN :: enum i32 {
+	NlsoOther                      = 0,
+	NlsoManual                     = 1,
+	NlsoWellKnown                  = 2,
+	NlsoDhcp                       = 3,
+	NlsoLinkLayerAddress           = 4,
+	NlsoRandom                     = 5,
+	IpSuffixOriginOther            = 0,
+	IpSuffixOriginManual           = 1,
+	IpSuffixOriginWellKnown        = 2,
+	IpSuffixOriginDhcp             = 3,
+	IpSuffixOriginLinkLayerAddress = 4,
+	IpSuffixOriginRandom           = 5,
+	IpSuffixOriginUnchanged        = 16,
+}
+
+NL_DAD_STATE :: enum i32 {
+	NldsInvalid          = 0,
+	NldsTentative        = 1,
+	NldsDuplicate        = 2,
+	NldsDeprecated       = 3,
+	NldsPreferred        = 4,
+	IpDadStateInvalid    = 0,
+	IpDadStateTentative  = 1,
+	IpDadStateDuplicate  = 2,
+	IpDadStateDeprecated = 3,
+	IpDadStatePreferred  = 4,
+}
+
+@(default_calling_convention = "std")
+foreign iphlpapi {
+	/*
+		The GetAdaptersAddresses function retrieves the addresses associated with the adapters on the local computer.
+		See: https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses
+	*/
+	@(link_name="GetAdaptersAddresses") get_adapters_addresses :: proc(
+		family:            Address_Family,
+		flags:             GAA_Flags,
+		_reserved:         rawptr,
+		adapter_addresses: [^]IP_Adapter_Addresses,
+		size:              ^u32,
+	) -> ULONG ---
+
+}

+ 29 - 0
core/sys/windows/kernel32.odin

@@ -3,6 +3,23 @@ package sys_windows
 
 foreign import kernel32 "system:Kernel32.lib"
 
+FOREGROUND_BLUE            :: WORD(0x0001)
+FOREGROUND_GREEN           :: WORD(0x0002)
+FOREGROUND_RED             :: WORD(0x0004)
+FOREGROUND_INTENSITY       :: WORD(0x0008)
+BACKGROUND_BLUE            :: WORD(0x0010)
+BACKGROUND_GREEN           :: WORD(0x0020)
+BACKGROUND_RED             :: WORD(0x0040)
+BACKGROUND_INTENSITY       :: WORD(0x0080)
+COMMON_LVB_LEADING_BYTE    :: WORD(0x0100)
+COMMON_LVB_TRAILING_BYTE   :: WORD(0x0200)
+COMMON_LVB_GRID_HORIZONTAL :: WORD(0x0400)
+COMMON_LVB_GRID_LVERTICAL  :: WORD(0x0800)
+COMMON_LVB_GRID_RVERTICAL  :: WORD(0x1000)
+COMMON_LVB_REVERSE_VIDEO   :: WORD(0x4000)
+COMMON_LVB_UNDERSCORE      :: WORD(0x8000)
+COMMON_LVB_SBCSDBCS        :: WORD(0x0300)
+
 @(default_calling_convention="stdcall")
 foreign kernel32 {
 	OutputDebugStringA :: proc(lpOutputString: LPCSTR) --- // The only A thing that is allowed
@@ -26,6 +43,8 @@ foreign kernel32 {
 	                       dwMode: DWORD) -> BOOL ---
 	SetConsoleCursorPosition :: proc(hConsoleHandle: HANDLE,
 						   dwCursorPosition: COORD) -> BOOL ---
+	SetConsoleTextAttribute :: proc(hConsoleOutput: HANDLE,
+									wAttributes: WORD) -> BOOL ---
 
 	GetFileInformationByHandle :: proc(hFile: HANDLE, lpFileInformation: LPBY_HANDLE_FILE_INFORMATION) -> BOOL ---
 	SetHandleInformation :: proc(hObject: HANDLE,
@@ -315,6 +334,13 @@ foreign kernel32 {
 		lpOverlapped: LPOVERLAPPED,
 		lpCompletionRoutine: LPOVERLAPPED_COMPLETION_ROUTINE,
 	) -> BOOL ---
+	FindFirstChangeNotificationW :: proc(
+		lpPathName: LPWSTR,
+		bWatchSubtree: BOOL,
+		dwNotifyFilter: DWORD,
+	) -> HANDLE ---
+	FindNextChangeNotification :: proc(hChangeHandle: HANDLE) -> BOOL ---
+	FindCloseChangeNotification :: proc(hChangeHandle: HANDLE) -> BOOL ---
 
 	InitializeSRWLock          :: proc(SRWLock: ^SRWLOCK) ---
 	AcquireSRWLockExclusive    :: proc(SRWLock: ^SRWLOCK) ---
@@ -363,6 +389,9 @@ foreign kernel32 {
 	GenerateConsoleCtrlEvent :: proc(dwCtrlEvent: DWORD, dwProcessGroupId: DWORD) -> BOOL ---
 	FreeConsole :: proc() -> BOOL ---
 	GetConsoleWindow :: proc() -> HWND ---
+	GetConsoleScreenBufferInfo :: proc(hConsoleOutput: HANDLE, lpConsoleScreenBufferInfo: PCONSOLE_SCREEN_BUFFER_INFO) -> BOOL ---
+	SetConsoleScreenBufferSize :: proc(hConsoleOutput: HANDLE, dwSize: COORD) -> BOOL ---
+	SetConsoleWindowInfo :: proc(hConsoleOutput: HANDLE, bAbsolute : BOOL, lpConsoleWindow: ^SMALL_RECT) -> BOOL ---
 
 	GetDiskFreeSpaceExW :: proc(
 		lpDirectoryName: LPCWSTR,

+ 274 - 139
core/sys/windows/types.odin

@@ -154,10 +154,6 @@ TIMER_QUERY_STATE  :: 0x0001
 TIMER_MODIFY_STATE :: 0x0002
 TIMER_ALL_ACCESS   :: STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | TIMER_QUERY_STATE | TIMER_MODIFY_STATE
 
-SOCKET :: distinct uintptr // TODO
-socklen_t :: c_int
-ADDRESS_FAMILY :: USHORT
-
 TRUE  :: BOOL(true)
 FALSE :: BOOL(false)
 
@@ -1868,30 +1864,6 @@ BI_BITFIELDS :: 3
 BI_JPEG      :: 4
 BI_PNG       :: 5
 
-WSA_FLAG_OVERLAPPED: DWORD : 0x01
-WSA_FLAG_NO_HANDLE_INHERIT: DWORD : 0x80
-
-WSADESCRIPTION_LEN :: 256
-WSASYS_STATUS_LEN :: 128
-WSAPROTOCOL_LEN: DWORD : 255
-INVALID_SOCKET :: ~SOCKET(0)
-
-WSAEACCES: c_int : 10013
-WSAEINVAL: c_int : 10022
-WSAEWOULDBLOCK: c_int : 10035
-WSAEPROTOTYPE: c_int : 10041
-WSAEADDRINUSE: c_int : 10048
-WSAEADDRNOTAVAIL: c_int : 10049
-WSAECONNABORTED: c_int : 10053
-WSAECONNRESET: c_int : 10054
-WSAENOTCONN: c_int : 10057
-WSAESHUTDOWN: c_int : 10058
-WSAETIMEDOUT: c_int : 10060
-WSAECONNREFUSED: c_int : 10061
-WSATRY_AGAIN: c_int : 11002
-
-MAX_PROTOCOL_CHAIN: DWORD : 7
-
 MAXIMUM_REPARSE_DATA_BUFFER_SIZE :: 16 * 1024
 FSCTL_GET_REPARSE_POINT: DWORD : 0x900a8
 IO_REPARSE_TAG_SYMLINK: DWORD : 0xa000000c
@@ -1949,44 +1921,6 @@ CREATE_NEW_PROCESS_GROUP: DWORD : 0x00000200
 CREATE_UNICODE_ENVIRONMENT: DWORD : 0x00000400
 STARTF_USESTDHANDLES: DWORD : 0x00000100
 
-AF_INET: c_int : 2
-AF_INET6: c_int : 23
-SD_BOTH: c_int : 2
-SD_RECEIVE: c_int : 0
-SD_SEND: c_int : 1
-SOCK_DGRAM: c_int : 2
-SOCK_STREAM: c_int : 1
-SOL_SOCKET: c_int : 0xffff
-SO_RCVTIMEO: c_int : 0x1006
-SO_SNDTIMEO: c_int : 0x1005
-SO_REUSEADDR: c_int : 0x0004
-IPPROTO_IP: c_int : 0
-IPPROTO_TCP: c_int : 6
-IPPROTO_IPV6: c_int : 41
-TCP_NODELAY: c_int : 0x0001
-IP_TTL: c_int : 4
-IPV6_V6ONLY: c_int : 27
-SO_ERROR: c_int : 0x1007
-SO_BROADCAST: c_int : 0x0020
-IP_MULTICAST_LOOP: c_int : 11
-IPV6_MULTICAST_LOOP: c_int : 11
-IP_MULTICAST_TTL: c_int : 10
-IP_ADD_MEMBERSHIP: c_int : 12
-IP_DROP_MEMBERSHIP: c_int : 13
-IPV6_ADD_MEMBERSHIP: c_int : 12
-IPV6_DROP_MEMBERSHIP: c_int : 13
-MSG_PEEK: c_int : 0x2
-
-ip_mreq :: struct {
-	imr_multiaddr: in_addr,
-	imr_interface: in_addr,
-}
-
-ipv6_mreq :: struct {
-	ipv6mr_multiaddr: in6_addr,
-	ipv6mr_interface: c_uint,
-}
-
 VOLUME_NAME_DOS: DWORD : 0x0
 MOVEFILE_REPLACE_EXISTING: DWORD : 1
 
@@ -2369,11 +2303,6 @@ STARTUPINFO :: struct {
 	hStdError: HANDLE,
 }
 
-SOCKADDR :: struct {
-	sa_family: ADDRESS_FAMILY,
-	sa_data: [14]CHAR,
-}
-
 FILETIME :: struct {
 	dwLowDateTime: DWORD,
 	dwHighDateTime: DWORD,
@@ -2406,74 +2335,6 @@ ADDRESS_MODE :: enum c_int {
 	AddrModeFlat,
 }
 
-SOCKADDR_STORAGE_LH :: struct {
-	ss_family: ADDRESS_FAMILY,
-	__ss_pad1: [6]CHAR,
-	__ss_align: i64,
-	__ss_pad2: [112]CHAR,
-}
-
-ADDRINFOA :: struct {
-	ai_flags: c_int,
-	ai_family: c_int,
-	ai_socktype: c_int,
-	ai_protocol: c_int,
-	ai_addrlen: size_t,
-	ai_canonname: ^c_char,
-	ai_addr: ^SOCKADDR,
-	ai_next: ^ADDRINFOA,
-}
-
-PADDRINFOEXW  :: ^ADDRINFOEXW
-LPADDRINFOEXW :: ^ADDRINFOEXW
-ADDRINFOEXW :: struct {
-	ai_flags:     c_int,
-	ai_family:    c_int,
-	ai_socktype:  c_int,
-	ai_protocol:  c_int,
-	ai_addrlen:   size_t,
-	ai_canonname: wstring,
-	ai_addr:      ^sockaddr,
-	ai_blob:      rawptr,
-	ai_bloblen:   size_t,
-	ai_provider:  LPGUID,
-	ai_next:      ^ADDRINFOEXW,
-}
-
-LPLOOKUPSERVICE_COMPLETION_ROUTINE :: #type proc "stdcall" (
-	dwErrorCode: DWORD,
-	dwNumberOfBytesTransfered: DWORD,
-	lpOverlapped: LPOVERLAPPED,
-)
-
-sockaddr  :: struct {
-	sa_family: USHORT,
-	sa_data:   [14]byte,
-}
-
-sockaddr_in :: struct {
-	sin_family: ADDRESS_FAMILY,
-	sin_port: USHORT,
-	sin_addr: in_addr,
-	sin_zero: [8]CHAR,
-}
-
-sockaddr_in6 :: struct {
-	sin6_family: ADDRESS_FAMILY,
-	sin6_port: USHORT,
-	sin6_flowinfo: c_ulong,
-	sin6_addr: in6_addr,
-	sin6_scope_id: c_ulong,
-}
-
-in_addr :: struct {
-	s_addr: u32,
-}
-
-in6_addr :: struct {
-	s6_addr: [16]u8,
-}
-
 EXCEPTION_DISPOSITION :: enum c_int {
 	ExceptionContinueExecution,
 	ExceptionContinueSearch,
@@ -2564,6 +2425,27 @@ FILE_ATTRIBUTE_TAG_INFO :: struct {
 	ReparseTag: DWORD,
 }
 
+PADDRINFOEXW :: ^ADDRINFOEXW
+LPADDRINFOEXW :: ^ADDRINFOEXW
+ADDRINFOEXW :: struct {
+	ai_flags:     c_int,
+	ai_family:    c_int,
+	ai_socktype:  c_int,
+	ai_protocol:  c_int,
+	ai_addrlen:   size_t,
+	ai_canonname: wstring,
+	ai_addr:      ^sockaddr,
+	ai_blob:      rawptr,
+	ai_bloblen:   size_t,
+	ai_provider:  LPGUID,
+	ai_next:      ^ADDRINFOEXW,
+}
+
+LPLOOKUPSERVICE_COMPLETION_ROUTINE :: #type proc "stdcall" (
+	dwErrorCode: DWORD,
+	dwNumberOfBytesTransfered: DWORD,
+	lpOverlapped: LPOVERLAPPED,
+)
 
 
 // https://docs.microsoft.com/en-gb/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info
@@ -3884,3 +3766,256 @@ COORD :: struct {
 	X: SHORT,
 	Y: SHORT,
 }
+
+SMALL_RECT :: struct {
+	Left: SHORT,
+	Top: SHORT,
+	Right: SHORT,
+	Bottom: SHORT,
+}
+
+CONSOLE_SCREEN_BUFFER_INFO :: struct {
+	dwSize: COORD,
+	dwCursorPosition: COORD,
+	wAttributes: WORD,
+	srWindow: SMALL_RECT,
+	dwMaximumWindowSize: COORD,
+}
+
+
+PCONSOLE_SCREEN_BUFFER_INFO :: ^CONSOLE_SCREEN_BUFFER_INFO
+
+//
+// Networking
+//
+WSA_FLAG_OVERLAPPED             :: 1
+WSA_FLAG_MULTIPOINT_C_ROOT      :: 2
+WSA_FLAG_MULTIPOINT_C_LEAF      :: 4
+WSA_FLAG_MULTIPOINT_D_ROOT      :: 8
+WSA_FLAG_MULTIPOINT_D_LEAF      :: 16
+WSA_FLAG_ACCESS_SYSTEM_SECURITY :: 32
+WSA_FLAG_NO_HANDLE_INHERIT      :: 128
+WSADESCRIPTION_LEN :: 256
+WSASYS_STATUS_LEN  :: 128
+WSAPROTOCOL_LEN    :: 255
+INVALID_SOCKET :: ~SOCKET(0)
+SOMAXCONN    :: 128 // The number of messages that can be queued in memory after being received; use 2-4 for Bluetooth.
+                    // This is for the 'backlog' parameter to listen().
+SOCKET_ERROR :: -1
+
+// Networking errors
+WSAEINTR               :: 10004 // Call interrupted. CancelBlockingCall was called. (This is different on Linux.)
+WSAEACCES              :: 10013 // If you try to bind a Udp socket to the broadcast address without the socket option set.
+WSAEFAULT              :: 10014 // A pointer that was passed to a WSA function is invalid, such as a buffer size is smaller than you said it was
+WSAEINVAL              :: 10022 // Invalid argument supplied
+WSAEMFILE              :: 10024 // SOCKET handles exhausted
+WSAEWOULDBLOCK         :: 10035 // No data is ready yet
+WSAENOTSOCK            :: 10038 // Not a socket.
+WSAEINPROGRESS         :: 10036 // WS1.1 call is in progress or callback function is still being processed
+WSAEALREADY            :: 10037 // Already connecting in parallel.
+WSAEMSGSIZE            :: 10040 // Message was truncated because it exceeded max datagram size.
+WSAEPROTOTYPE          :: 10041 // Wrong protocol for the provided socket
+WSAENOPROTOOPT         :: 10042 // TODO
+WSAEPROTONOSUPPORT     :: 10043 // Protocol not supported
+WSAESOCKTNOSUPPORT     :: 10044 // Socket type not supported in the given address family
+WSAEAFNOSUPPORT        :: 10047 // Address family not supported
+WSAEOPNOTSUPP          :: 10045 // Attempt to accept on non-stream socket, etc.
+WSAEADDRINUSE          :: 10048 // Endpoint being bound is in use by another socket.
+WSAEADDRNOTAVAIL       :: 10049 // Not a valid local IP address on this computer.
+WSAENETDOWN            :: 10050 // Network subsystem failure on the local machine.
+WSAENETUNREACH         :: 10051 // The local machine is not connected to the network.
+WSAENETRESET           :: 10052 // Keepalive failure detected, or TTL exceeded when receiving UDP packets.
+WSAECONNABORTED        :: 10053 // Connection has been aborted by software in the host machine.
+WSAECONNRESET          :: 10054 // The connection was reset while trying to accept, read or write.
+WSAENOBUFS             :: 10055 // No buffer space is available. The outgoing queue may be full in which case you should probably try again after a pause.
+WSAEISCONN             :: 10056 // The socket is already connected.
+WSAENOTCONN            :: 10057 // The socket is not connected yet, or no address was supplied to sendto.
+WSAESHUTDOWN           :: 10058 // The socket has been shutdown in the direction required.
+WSAETIMEDOUT           :: 10060 // The timeout duration was reached before any data was received / before all data was sent.
+WSAECONNREFUSED        :: 10061 // The remote machine is not listening on that endpoint.
+WSAEHOSTDOWN           :: 10064 // Destination host was down.
+WSAEHOSTUNREACH        :: 10065 // The remote machine is not connected to the network.
+WSAENOTINITIALISED     :: 10093 // Needs WSAStartup call
+WSAEINVALIDPROCTABLE   :: 10104 // Invalid or incomplete procedure table was returned
+WSAEINVALIDPROVIDER    :: 10105 // Service provider version is not 2.2
+WSAEPROVIDERFAILEDINIT :: 10106 // Service provider failed to initialize
+
+// Address families
+AF_UNSPEC : c_int : 0  // Unspecified
+AF_INET   : c_int : 2  // IPv4
+AF_INET6  : c_int : 23 // IPv6
+AF_IRDA   : c_int : 26 // Infrared
+AF_BTH    : c_int : 32 // Bluetooth
+
+// Socket types
+SOCK_STREAM    : c_int : 1 // TCP
+SOCK_DGRAM     : c_int : 2 // UDP
+SOCK_RAW       : c_int : 3 // Requires options IP_HDRINCL for v4, IPV6_HDRINCL for v6, on the socket
+SOCK_RDM       : c_int : 4 // Requires "Reliable Multicast Protocol" to be installed - see WSAEnumProtocols
+SOCK_SEQPACKET : c_int : 5 // Provides psuedo-stream packet based on DGRAMs.
+
+// Protocols
+IPPROTO_IP      : c_int : 0
+IPPROTO_ICMP    : c_int : 1   // (AF_UNSPEC, AF_INET, AF_INET6) + SOCK_RAW | not specified
+IPPROTO_IGMP    : c_int : 2   // (AF_UNSPEC, AF_INET, AF_INET6) + SOCK_RAW | not specified
+BTHPROTO_RFCOMM : c_int : 3   // Bluetooth: AF_BTH + SOCK_STREAM
+IPPROTO_TCP     : c_int : 6   // (AF_INET, AF_INET6) + SOCK_STREAM
+IPPROTO_UDP     : c_int : 17  // (AF_INET, AF_INET6) + SOCK_DGRAM
+IPPROTO_ICMPV6  : c_int : 58  // (AF_UNSPEC, AF_INET, AF_INET6) + SOCK_RAW
+IPPROTO_RM      : c_int : 113 // AF_INET + SOCK_RDM [requires "Reliable Multicast Protocol" to be installed - see WSAEnumProtocols]
+
+// Shutdown manners
+SD_RECEIVE : c_int : 0
+SD_SEND    : c_int : 1
+SD_BOTH    : c_int : 2
+
+// Socket 'levels'
+SOL_SOCKET   : c_int : 0xffff // Socket options for any socket.
+IPPROTO_IPV6 : c_int : 41     // Socket options for IPV6.
+
+// Options for any sockets
+SO_ACCEPTCONN         : c_int : 0x0002
+SO_REUSEADDR          : c_int : 0x0004
+SO_KEEPALIVE          : c_int : 0x0008
+SO_SNDTIMEO           : c_int : 0x1005
+SO_RCVTIMEO           : c_int : 0x1006
+SO_EXCLUSIVEADDRUSE   : c_int : ~SO_REUSEADDR
+SO_CONDITIONAL_ACCEPT : c_int : 0x3002
+SO_DONTLINGER         : c_int : ~SO_LINGER
+SO_OOBINLINE          : c_int : 0x0100
+SO_LINGER             : c_int : 0x0080
+SO_RCVBUF             : c_int : 0x1002
+SO_SNDBUF             : c_int : 0x1001
+SO_ERROR              : c_int : 0x1007
+SO_BROADCAST          : c_int : 0x0020
+
+TCP_NODELAY: c_int : 0x0001
+IP_TTL: c_int : 4
+IPV6_V6ONLY: c_int : 27
+IP_MULTICAST_LOOP: c_int : 11
+IPV6_MULTICAST_LOOP: c_int : 11
+IP_MULTICAST_TTL: c_int : 10
+IP_ADD_MEMBERSHIP: c_int : 12
+
+IPV6_ADD_MEMBERSHIP: c_int : 12
+IPV6_DROP_MEMBERSHIP: c_int : 13
+
+MAX_PROTOCOL_CHAIN: DWORD : 7
+
+// Used with the SO_LINGER socket option to setsockopt().
+LINGER :: struct {
+	l_onoff: c.ushort,
+	l_linger: c.ushort,
+}
+// Send/Receive flags.
+MSG_OOB  : c_int : 1 // `send`/`recv` should process out-of-band data.
+MSG_PEEK : c_int : 2 // `recv` should not remove the data from the buffer. Only valid for non-overlapped operations.
+
+
+SOCKET :: distinct uintptr // TODO
+socklen_t :: c_int
+ADDRESS_FAMILY :: USHORT
+
+ip_mreq :: struct {
+	imr_multiaddr: in_addr,
+	imr_interface: in_addr,
+}
+
+ipv6_mreq :: struct {
+	ipv6mr_multiaddr: in6_addr,
+	ipv6mr_interface: c_uint,
+}
+
+SOCKADDR_STORAGE_LH :: struct {
+	ss_family:  ADDRESS_FAMILY,
+	__ss_pad1:  [6]CHAR,
+	__ss_align: i64,
+	__ss_pad2:  [112]CHAR,
+}
+
+ADDRINFOA :: struct {
+	ai_flags:     c_int,
+	ai_family:    c_int,
+	ai_socktype:  c_int,
+	ai_protocol:  c_int,
+	ai_addrlen:   size_t,
+	ai_canonname: ^c_char,
+	ai_addr:      ^SOCKADDR,
+	ai_next:      ^ADDRINFOA,
+}
+
+sockaddr :: struct {
+	sa_family: USHORT,
+	sa_data:   [14]byte,
+}
+
+sockaddr_in :: struct {
+	sin_family: ADDRESS_FAMILY,
+	sin_port:   u16be,
+	sin_addr:   in_addr,
+	sin_zero:   [8]CHAR,
+}
+sockaddr_in6 :: struct {
+	sin6_family:   ADDRESS_FAMILY,
+	sin6_port:     u16be,
+	sin6_flowinfo: c_ulong,
+	sin6_addr:     in6_addr,
+	sin6_scope_id: c_ulong,
+}
+
+in_addr :: struct {
+	s_addr: u32,
+}
+
+in6_addr :: struct {
+	s6_addr: [16]u8,
+}
+
+
+DNS_STATUS :: distinct DWORD // zero is success
+DNS_INFO_NO_RECORDS :: 9501
+DNS_QUERY_NO_RECURSION :: 0x00000004
+
+DNS_RECORD :: struct {
+    pNext: ^DNS_RECORD,
+    pName: cstring,
+    wType: WORD,
+    wDataLength: USHORT,
+    Flags: DWORD,
+    dwTtl: DWORD,
+    _: DWORD,
+    Data: struct #raw_union {
+        CNAME: DNS_PTR_DATAA,
+        A:     u32be,  // Ipv4 Address
+        AAAA:  u128be, // Ipv6 Address
+        TXT:   DNS_TXT_DATAA,
+        NS:    DNS_PTR_DATAA,
+        MX:    DNS_MX_DATAA,
+        SRV:   DNS_SRV_DATAA,
+    },
+}
+
+DNS_TXT_DATAA :: struct {
+    dwStringCount: DWORD,
+    pStringArray:  cstring,
+}
+
+DNS_PTR_DATAA :: cstring
+
+DNS_MX_DATAA :: struct {
+    pNameExchange: cstring, // the hostname
+    wPreference: WORD,      // lower values preferred
+    _: WORD,                // padding.
+}
+DNS_SRV_DATAA :: struct {
+	pNameTarget: cstring,
+	wPriority:   u16,
+	wWeight:     u16,
+	wPort:       u16,
+	_:           WORD, // padding
+}
+
+SOCKADDR :: struct {
+	sa_family: ADDRESS_FAMILY,
+	sa_data:   [14]CHAR,
+}

+ 21 - 0
core/sys/windows/util.odin

@@ -485,3 +485,24 @@ run_as_user :: proc(username, password, application, commandline: string, pi: ^P
 		return false
 	}
 }
+
+ensure_winsock_initialized :: proc() {
+	@static gate := false
+	@static initted := false
+
+	if initted {
+		return
+	}
+
+	for intrinsics.atomic_compare_exchange_strong(&gate, false, true) {
+		intrinsics.cpu_relax()
+	}
+	defer intrinsics.atomic_store(&gate, false)
+
+	unused_info: WSADATA
+	version_requested := WORD(2) << 8 | 2
+	res := WSAStartup(version_requested, &unused_info)
+	assert(res == 0, "unable to initialized Winsock2")
+
+	initted = true
+}

+ 7 - 7
core/sys/windows/ws2_32.odin

@@ -54,7 +54,7 @@ foreign ws2_32 {
 		buf: rawptr,
 		len: c_int,
 		flags: c_int,
-		addr: ^SOCKADDR,
+		addr: ^SOCKADDR_STORAGE_LH,
 		addrlen: ^c_int,
 	) -> c_int ---
 	sendto :: proc(
@@ -62,11 +62,11 @@ foreign ws2_32 {
 		buf: rawptr,
 		len: c_int,
 		flags: c_int,
-		addr: ^SOCKADDR,
+		addr: ^SOCKADDR_STORAGE_LH,
 		addrlen: c_int,
 	) -> c_int ---
 	shutdown :: proc(socket: SOCKET, how: c_int) -> c_int ---
-	accept :: proc(socket: SOCKET, address: ^SOCKADDR, address_len: ^c_int) -> SOCKET ---
+	accept :: proc(socket: SOCKET, address: ^SOCKADDR_STORAGE_LH, address_len: ^c_int) -> SOCKET ---
 
 	setsockopt :: proc(
 		s: SOCKET,
@@ -75,11 +75,11 @@ foreign ws2_32 {
 		optval: rawptr,
 		optlen: c_int,
 	) -> c_int ---
-	getsockname :: proc(socket: SOCKET, address: ^SOCKADDR, address_len: ^c_int) -> c_int ---
-	getpeername :: proc(socket: SOCKET, address: ^SOCKADDR, address_len: ^c_int) -> c_int ---
-	bind :: proc(socket: SOCKET, address: ^SOCKADDR, address_len: socklen_t) -> c_int ---
+	getsockname :: proc(socket: SOCKET, address: ^SOCKADDR_STORAGE_LH, address_len: ^c_int) -> c_int ---
+	getpeername :: proc(socket: SOCKET, address: ^SOCKADDR_STORAGE_LH, address_len: ^c_int) -> c_int ---
+	bind :: proc(socket: SOCKET, address: ^SOCKADDR_STORAGE_LH, address_len: socklen_t) -> c_int ---
 	listen :: proc(socket: SOCKET, backlog: c_int) -> c_int ---
-	connect :: proc(socket: SOCKET, address: ^SOCKADDR, len: c_int) -> c_int ---
+	connect :: proc(socket: SOCKET, address: ^SOCKADDR_STORAGE_LH, len: c_int) -> c_int ---
 	getaddrinfo :: proc(
 		node: cstring,
 		service: cstring,

+ 55 - 2
core/time/perf.odin

@@ -1,11 +1,11 @@
 package time
 
 import "core:runtime"
+import "core:intrinsics"
 
 Tick :: struct {
 	_nsec: i64, // relative amount
 }
-
 tick_now :: proc "contextless" () -> Tick {
 	return _tick_now()
 }
@@ -40,6 +40,59 @@ _tick_duration_end :: proc "contextless" (d: ^Duration, t: Tick) {
 	d^ = tick_since(t)
 }
 
+when ODIN_ARCH == .amd64 {
+	@(private)
+	x86_has_invariant_tsc :: proc "contextless" () -> bool {
+		eax, _, _, _ := intrinsics.x86_cpuid(0x80_000_000, 0)
+
+		// Is this processor *really* ancient?
+		if eax < 0x80_000_007 {
+			return false
+		}
+
+		// check if the invariant TSC bit is set
+		_, _, _, edx := intrinsics.x86_cpuid(0x80_000_007, 0)
+		return (edx & (1 << 8)) != 0
+	}
+}
+
+when ODIN_OS != .Darwin && ODIN_OS != .Linux && ODIN_OS != .FreeBSD {
+	_get_tsc_frequency :: proc "contextless" () -> (u64, bool) {
+		return 0, false
+	}
+}
+
+has_invariant_tsc :: proc "contextless" () -> bool {
+	when ODIN_ARCH == .amd64 {
+		return x86_has_invariant_tsc()
+	}
+
+	return false
+}
+
+tsc_frequency :: proc "contextless" () -> (u64, bool) {
+	if !has_invariant_tsc() {
+		return 0, false
+	}
+
+	hz, ok := _get_tsc_frequency()
+	if !ok {
+		// fallback to approximate TSC
+		tsc_begin := intrinsics.read_cycle_counter()
+		tick_begin := tick_now()
+
+		sleep(2 * Second)
+
+		tsc_end := intrinsics.read_cycle_counter()
+		tick_end := tick_now()
+
+		time_diff := u128(duration_nanoseconds(tick_diff(tick_begin, tick_end)))
+		hz = u64((u128(tsc_end - tsc_begin) * 1_000_000_000) / time_diff)
+	}
+
+	return hz, true
+}
+
 /*
 	Benchmark helpers
 */
@@ -94,4 +147,4 @@ benchmark :: proc(options: ^Benchmark_Options, allocator := context.allocator) -
 		options->teardown(allocator) or_return
 	}
 	return
-}
+}

+ 21 - 0
core/time/tsc_darwin.odin

@@ -0,0 +1,21 @@
+//+private
+//+build darwin
+package time
+
+import "core:c"
+
+foreign import libc "System.framework"
+foreign libc {
+	@(link_name="sysctlbyname") _sysctlbyname    :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int ---
+}
+
+_get_tsc_frequency :: proc "contextless" () -> (u64, bool) {
+	tmp_freq : u64 = 0
+	tmp_size : i64 = size_of(tmp_freq)
+	ret := _sysctlbyname("machdep.tsc.frequency", &tmp_freq, &tmp_size, nil, 0)
+	if ret < 0 {
+		return 0, false
+	}
+
+	return tmp_freq, true
+}

+ 21 - 0
core/time/tsc_freebsd.odin

@@ -0,0 +1,21 @@
+//+private
+//+build freebsd
+package time
+
+import "core:c"
+
+foreign import libc "system:c"
+foreign libc {
+	@(link_name="sysctlbyname") _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int ---
+}
+
+_get_tsc_frequency :: proc "contextless" () -> (u64, bool) {
+	tmp_freq : u64 = 0
+	tmp_size : i64 = size_of(tmp_freq)
+	ret := _sysctlbyname("machdep.tsc_freq", &tmp_freq, &tmp_size, nil, 0)
+	if ret < 0 {
+		return 0, false
+	}
+
+	return tmp_freq, true
+}

+ 35 - 0
core/time/tsc_linux.odin

@@ -0,0 +1,35 @@
+//+private
+//+build linux
+package time
+
+import "core:intrinsics"
+import "core:sys/unix"
+
+_get_tsc_frequency :: proc "contextless" () -> (u64, bool) {
+	perf_attr := unix.Perf_Event_Attr{}
+	perf_attr.type = u32(unix.Perf_Type_Id.Hardware)
+	perf_attr.config = u64(unix.Perf_Hardware_Id.Instructions)
+	perf_attr.size = size_of(perf_attr)
+	perf_attr.flags = {.Disabled, .Exclude_Kernel, .Exclude_HV}
+	fd := unix.sys_perf_event_open(&perf_attr, 0, -1, -1, 0)
+	if fd == -1 {
+		return 0, false
+	}
+	defer unix.sys_close(fd)
+
+	page_size : uint = 4096
+	ret := unix.sys_mmap(nil, page_size, unix.PROT_READ, unix.MAP_SHARED, fd, 0)
+	if ret < 0 && ret > -4096 {
+		return 0, false
+	}
+	addr := rawptr(uintptr(ret))
+	defer unix.sys_munmap(addr, page_size)
+
+	event_page := (^unix.Perf_Event_mmap_Page)(addr)
+	if .User_Time not_in event_page.cap.flags {
+		return 0, false
+	}
+
+	frequency := u64((u128(1_000_000_000) << u128(event_page.time_shift)) / u128(event_page.time_mult))
+	return frequency, true
+}

+ 3 - 1
examples/all/all_main.odin

@@ -49,6 +49,7 @@ import whirlpool        "core:crypto/whirlpool"
 import x25519           "core:crypto/x25519"
 
 import dynlib           "core:dynlib"
+import net              "core:net"
 
 import base32           "core:encoding/base32"
 import base64           "core:encoding/base64"
@@ -161,6 +162,7 @@ _ :: crypto_util
 _ :: whirlpool
 _ :: x25519
 _ :: dynlib
+_ :: net
 _ :: base32
 _ :: base64
 _ :: csv
@@ -214,4 +216,4 @@ _ :: sysinfo
 _ :: unicode
 _ :: utf8
 _ :: utf8string
-_ :: utf16
+_ :: utf16

+ 10 - 3
src/build_settings.cpp

@@ -288,7 +288,8 @@ struct BuildContext {
 
 	bool   ignore_warnings;
 	bool   warnings_as_errors;
-	bool   show_error_line;
+	bool   hide_error_line;
+	bool   has_ansi_terminal_colours;
 
 	bool   ignore_lazy;
 	bool   ignore_llvm_build;
@@ -306,7 +307,7 @@ struct BuildContext {
 
 	bool   disallow_rtti;
 
-	bool   use_static_map_calls;
+	bool   dynamic_map_calls;
 
 	RelocMode reloc_mode;
 	bool   disable_red_zone;
@@ -1033,7 +1034,10 @@ gb_internal String get_fullpath_core(gbAllocator a, String path) {
 }
 
 gb_internal bool show_error_line(void) {
-	return build_context.show_error_line;
+	return !build_context.hide_error_line;
+}
+gb_internal bool has_ansi_terminal_colours(void) {
+	return build_context.has_ansi_terminal_colours;
 }
 
 gb_internal bool has_asm_extension(String const &path) {
@@ -1253,6 +1257,9 @@ gb_internal void init_build_context(TargetMetrics *cross_target) {
 
 	bc->optimization_level = gb_clamp(bc->optimization_level, 0, 3);
 
+	// ENFORCE DYNAMIC MAP CALLS
+	bc->dynamic_map_calls = true;
+
 	bc->ODIN_VALGRIND_SUPPORT = false;
 	if (build_context.metrics.os != TargetOs_windows) {
 		switch (bc->metrics.arch) {

+ 1 - 1
src/check_builtin.cpp

@@ -2550,7 +2550,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
 		break;
 	}
 
-	case BuiltinProc_expand_to_tuple: {
+	case BuiltinProc_expand_values: {
 		Type *type = base_type(operand->type);
 		if (!is_type_struct(type) && !is_type_array(type)) {
 			gbString type_str = type_to_string(operand->type);

+ 7 - 2
src/check_decl.cpp

@@ -578,7 +578,7 @@ gb_internal void check_const_decl(CheckerContext *ctx, Entity *e, Ast *type_expr
 	if (operand.mode == Addressing_Invalid ||
 		base_type(operand.type) == t_invalid) {
 		gbString str = expr_to_string(init);
-		error(e->token, "Invalid declaration type '%s'", str);
+		error(init, "Invalid declaration value '%s'", str);
 		gb_string_free(str);
 	}
 
@@ -816,9 +816,14 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
 	if (ac.test) {
 		e->flags |= EntityFlag_Test;
 	}
-	if (ac.init) {
+	if (ac.init && ac.fini) {
+		error(e->token, "A procedure cannot be both declared as @(init) and @(fini)");
+	} else if (ac.init) {
 		e->flags |= EntityFlag_Init;
+	} else if (ac.fini) {
+		e->flags |= EntityFlag_Fini;
 	}
+
 	if (ac.set_cold) {
 		e->flags |= EntityFlag_Cold;
 	}

+ 277 - 135
src/check_expr.cpp

@@ -281,11 +281,11 @@ gb_internal void error_operand_no_value(Operand *o) {
 }
 
 gb_internal void add_map_get_dependencies(CheckerContext *c) {
-	if (build_context.use_static_map_calls) {
+	if (build_context.dynamic_map_calls) {
+		add_package_dependency(c, "runtime", "__dynamic_map_get");
+	} else {
 		add_package_dependency(c, "runtime", "map_desired_position");
 		add_package_dependency(c, "runtime", "map_probe_distance");
-	} else {
-		add_package_dependency(c, "runtime", "__dynamic_map_get");
 	}
 }
 
@@ -297,11 +297,11 @@ gb_internal void add_map_set_dependencies(CheckerContext *c) {
 		t_map_set_proc = alloc_type_proc_from_types(map_set_args, gb_count_of(map_set_args), t_rawptr, false, ProcCC_Odin);
 	}
 
-	if (build_context.use_static_map_calls) {
+	if (build_context.dynamic_map_calls) {
+		add_package_dependency(c, "runtime", "__dynamic_map_set");
+	} else {
 		add_package_dependency(c, "runtime", "__dynamic_map_check_grow");
 		add_package_dependency(c, "runtime", "map_insert_hash_dynamic");
-	} else {
-		add_package_dependency(c, "runtime", "__dynamic_map_set");
 	}
 }
 
@@ -1679,9 +1679,13 @@ gb_internal bool check_unary_op(CheckerContext *c, Operand *o, Token op) {
 
 	case Token_Not:
 		if (!is_type_boolean(type)) {
+			ERROR_BLOCK();
 			str = expr_to_string(o->expr);
-			error(op, "Operator '%.*s' is only allowed on boolean expression", LIT(op.string));
+			error(op, "Operator '%.*s' is only allowed on boolean expressions", LIT(op.string));
 			gb_string_free(str);
+			if (is_type_integer(type)) {
+				error_line("\tSuggestion: Did you mean to use the bitwise not operator '~'?\n");
+			}
 		}
 		break;
 
@@ -2019,6 +2023,47 @@ gb_internal bool check_representable_as_constant(CheckerContext *c, ExactValue i
 }
 
 
+gb_internal bool check_integer_exceed_suggestion(CheckerContext *c, Operand *o, Type *type) {
+	if (is_type_integer(type) && o->value.kind == ExactValue_Integer) {
+		gbString b = type_to_string(type);
+
+		i64 sz = type_size_of(type);
+		BigInt *bi = &o->value.value_integer;
+		if (is_type_unsigned(type)) {
+			if (big_int_is_neg(bi)) {
+				error_line("\tA negative value cannot be represented by the unsigned integer type '%s'\n", b);
+			} else {
+				BigInt one = big_int_make_u64(1);
+				BigInt max_size = big_int_make_u64(1);
+				BigInt bits = big_int_make_i64(8*sz);
+				big_int_shl_eq(&max_size, &bits);
+				big_int_sub_eq(&max_size, &one);
+				String max_size_str = big_int_to_string(temporary_allocator(), &max_size);
+				error_line("\tThe maximum value that can be represented by '%s' is '%.*s'\n", b, LIT(max_size_str));
+			}
+		} else {
+			BigInt zero = big_int_make_u64(0);
+			BigInt one = big_int_make_u64(1);
+			BigInt max_size = big_int_make_u64(1);
+			BigInt bits = big_int_make_i64(8*sz - 1);
+			big_int_shl_eq(&max_size, &bits);
+			if (big_int_is_neg(bi)) {
+				big_int_neg(&max_size, &max_size);
+				String max_size_str = big_int_to_string(temporary_allocator(), &max_size);
+				error_line("\tThe minimum value that can be represented by '%s' is '%.*s'\n", b, LIT(max_size_str));
+			} else {
+				big_int_sub_eq(&max_size, &one);
+				String max_size_str = big_int_to_string(temporary_allocator(), &max_size);
+				error_line("\tThe maximum value that can be represented by '%s' is '%.*s'\n", b, LIT(max_size_str));
+			}
+		}
+
+		gb_string_free(b);
+
+		return true;
+	}
+	return false;
+}
 gb_internal void check_assignment_error_suggestion(CheckerContext *c, Operand *o, Type *type) {
 	gbString a = expr_to_string(o->expr);
 	gbString b = type_to_string(type);
@@ -2050,6 +2095,8 @@ gb_internal void check_assignment_error_suggestion(CheckerContext *c, Operand *o
 		error_line("\t            whereas slices in general are assumed to be mutable.\n");
 	} else if (is_type_u8_slice(src) && are_types_identical(dst, t_string) && o->mode != Addressing_Constant) {
 		error_line("\tSuggestion: the expression may be casted to %s\n", b);
+	} else if (check_integer_exceed_suggestion(c, o, type)) {
+		return;
 	}
 }
 
@@ -2089,8 +2136,8 @@ gb_internal void check_cast_error_suggestion(CheckerContext *c, Operand *o, Type
 		}
 	} else if (are_types_identical(src, t_string) && is_type_u8_slice(dst)) {
 		error_line("\tSuggestion: a string may be transmuted to %s\n", b);
-	} else if (is_type_u8_slice(src) && are_types_identical(dst, t_string) && o->mode != Addressing_Constant) {
-		error_line("\tSuggestion: the expression may be casted to %s\n", b);
+	} else if (check_integer_exceed_suggestion(c, o, type)) {
+		return;
 	}
 }
 
@@ -2116,16 +2163,22 @@ gb_internal bool check_is_expressible(CheckerContext *ctx, Operand *o, Type *typ
 			o->mode = Addressing_Invalid;
 		);
 
+		ERROR_BLOCK();
+
+
 		if (is_type_numeric(o->type) && is_type_numeric(type)) {
 			if (!is_type_integer(o->type) && is_type_integer(type)) {
 				error(o->expr, "'%s' truncated to '%s', got %s", a, b, s);
 			} else {
-				ERROR_BLOCK();
-				error(o->expr, "Cannot convert numeric value '%s' to '%s' from '%s', got %s", a, b, c, s);
+				if (are_types_identical(o->type, type)) {
+					error(o->expr, "Numeric value '%s' from '%s' cannot be represented by '%s'", s, a, b);
+				} else {
+					error(o->expr, "Cannot convert numeric value '%s' from '%s' to '%s' from '%s'", s, a, b, c);
+				}
+
 				check_assignment_error_suggestion(ctx, o, type);
 			}
 		} else {
-			ERROR_BLOCK();
 			error(o->expr, "Cannot convert '%s' to '%s' from '%s', got %s", a, b, c, s);
 			check_assignment_error_suggestion(ctx, o, type);
 		}
@@ -2344,7 +2397,7 @@ gb_internal void add_comparison_procedures_for_fields(CheckerContext *c, Type *t
 }
 
 
-gb_internal void check_comparison(CheckerContext *c, Operand *x, Operand *y, TokenKind op) {
+gb_internal void check_comparison(CheckerContext *c, Ast *node, Operand *x, Operand *y, TokenKind op) {
 	if (x->mode == Addressing_Type && y->mode == Addressing_Type) {
 		bool comp = are_types_identical(x->type, y->type);
 		switch (op) {
@@ -2402,12 +2455,11 @@ gb_internal void check_comparison(CheckerContext *c, Operand *x, Operand *y, Tok
 		}
 
 		if (!defined) {
-			if (x->type == err_type && is_operand_nil(*x)) {
-				err_type = y->type;
-			}
-			gbString type_string = type_to_string(err_type, temporary_allocator());
+			gbString xs = type_to_string(x->type, temporary_allocator());
+			gbString ys = type_to_string(y->type, temporary_allocator());
 			err_str = gb_string_make(temporary_allocator(),
-				gb_bprintf("operator '%.*s' not defined for type '%s'", LIT(token_strings[op]), type_string));
+				gb_bprintf("operator '%.*s' not defined between the types '%s' and '%s'", LIT(token_strings[op]), xs, ys)
+			);
 		} else {
 			Type *comparison_type = x->type;
 			if (x->type == err_type && is_operand_nil(*x)) {
@@ -2432,7 +2484,7 @@ gb_internal void check_comparison(CheckerContext *c, Operand *x, Operand *y, Tok
 	}
 
 	if (err_str != nullptr) {
-		error(x->expr, "Cannot compare expression, %s", err_str);
+		error(node, "Cannot compare expression, %s", err_str);
 		x->type = t_untyped_bool;
 	} else {
 		if (x->mode == Addressing_Constant &&
@@ -2597,10 +2649,12 @@ gb_internal void check_shift(CheckerContext *c, Operand *x, Operand *y, Ast *nod
 				x->type = t_untyped_integer;
 			}
 
+			x->expr = node;
 			x->value = exact_value_shift(be->op.kind, x_val, y_val);
 
+
 			if (is_type_typed(x->type)) {
-				check_is_expressible(c, x, base_type(x->type));
+				check_is_expressible(c, x, x->type);
 			}
 			return;
 		}
@@ -2915,17 +2969,38 @@ gb_internal void check_cast(CheckerContext *c, Operand *x, Type *type) {
 	bool can_convert = check_cast_internal(c, x, type);
 
 	if (!can_convert) {
-		gbString expr_str = expr_to_string(x->expr);
-		gbString to_type  = type_to_string(type);
-		gbString from_type = type_to_string(x->type);
+		TEMPORARY_ALLOCATOR_GUARD();
+		gbString expr_str  = expr_to_string(x->expr, temporary_allocator());
+		gbString to_type   = type_to_string(type,    temporary_allocator());
+		gbString from_type = type_to_string(x->type, temporary_allocator());
+
+		x->mode = Addressing_Invalid;
+
+		begin_error_block();
 		error(x->expr, "Cannot cast '%s' as '%s' from '%s'", expr_str, to_type, from_type);
-		gb_string_free(from_type);
-		gb_string_free(to_type);
-		gb_string_free(expr_str);
+		if (is_const_expr) {
+			gbString val_str = exact_value_to_string(x->value);
+			if (is_type_float(x->type) && is_type_integer(type)) {
+				error_line("\t%s cannot be represented without truncation/rounding as the type '%s'\n", val_str, to_type);
+
+				// NOTE(bill): keep the mode and modify the type to minimize errors further on
+				x->mode = Addressing_Constant;
+				x->type = type;
+			} else {
+				error_line("\t'%s' cannot be represented as the type '%s'\n", val_str, to_type);
+				if (is_type_numeric(type)) {
+					// NOTE(bill): keep the mode and modify the type to minimize errors further on
+					x->mode = Addressing_Constant;
+					x->type = type;
+				}
+			}
+			gb_string_free(val_str);
 
+		}
 		check_cast_error_suggestion(c, x, type);
 
-		x->mode = Addressing_Invalid;
+		end_error_block();
+
 		return;
 	}
 
@@ -3422,7 +3497,7 @@ gb_internal void check_binary_expr(CheckerContext *c, Operand *x, Ast *node, Typ
 
 
 	if (token_is_comparison(op.kind)) {
-		check_comparison(c, x, y, op.kind);
+		check_comparison(c, node, x, y, op.kind);
 		return;
 	}
 
@@ -4539,7 +4614,7 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod
 			entity = scope_lookup_current(import_scope, entity_name);
 			bool allow_builtin = false;
 			if (!is_entity_declared_for_selector(entity, import_scope, &allow_builtin)) {
-				error(op_expr, "'%.*s' is not declared by '%.*s'", LIT(entity_name), LIT(import_name));
+				error(node, "'%.*s' is not declared by '%.*s'", LIT(entity_name), LIT(import_name));
 				operand->mode = Addressing_Invalid;
 				operand->expr = node;
 
@@ -4559,7 +4634,7 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod
 
 			if (!is_entity_exported(entity, allow_builtin)) {
 				gbString sel_str = expr_to_string(selector);
-				error(op_expr, "'%s' is not exported by '%.*s'", sel_str, LIT(import_name));
+				error(node, "'%s' is not exported by '%.*s'", sel_str, LIT(import_name));
 				gb_string_free(sel_str);
 				// NOTE(bill): make the state valid still, even if it's "invalid"
 				// operand->mode = Addressing_Invalid;
@@ -4730,20 +4805,29 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod
 		gbString op_str   = expr_to_string(op_expr);
 		gbString type_str = type_to_string_shorthand(operand->type);
 		gbString sel_str  = expr_to_string(selector);
-		error(op_expr, "'%s' of type '%s' has no field '%s'", op_str, type_str, sel_str);
 
-		if (operand->type != nullptr && selector->kind == Ast_Ident) {
-			String const &name = selector->Ident.token.string;
-			Type *bt = base_type(operand->type);
-			if (operand->type->kind == Type_Named &&
-			    operand->type->Named.type_name &&
-			    operand->type->Named.type_name->kind == Entity_TypeName &&
-			    operand->type->Named.type_name->TypeName.objc_metadata) {
-				check_did_you_mean_objc_entity(name, operand->type->Named.type_name, operand->mode == Addressing_Type);
-			} else if (bt->kind == Type_Struct) {
-				check_did_you_mean_type(name, bt->Struct.fields);
-			} else if (bt->kind == Type_Enum) {
-				check_did_you_mean_type(name, bt->Enum.fields);
+		if (operand->mode == Addressing_Type) {
+			if (is_type_polymorphic(operand->type, true)) {
+				error(op_expr, "Type '%s' has no field nor polymorphic parameter '%s'", op_str, sel_str);
+			} else {
+				error(op_expr, "Type '%s' has no field '%s'", op_str, sel_str);
+			}
+		} else {
+			error(op_expr, "'%s' of type '%s' has no field '%s'", op_str, type_str, sel_str);
+
+			if (operand->type != nullptr && selector->kind == Ast_Ident) {
+				String const &name = selector->Ident.token.string;
+				Type *bt = base_type(operand->type);
+				if (operand->type->kind == Type_Named &&
+				    operand->type->Named.type_name &&
+				    operand->type->Named.type_name->kind == Entity_TypeName &&
+				    operand->type->Named.type_name->TypeName.objc_metadata) {
+					check_did_you_mean_objc_entity(name, operand->type->Named.type_name, operand->mode == Addressing_Type);
+				} else if (bt->kind == Type_Struct) {
+					check_did_you_mean_type(name, bt->Struct.fields);
+				} else if (bt->kind == Type_Enum) {
+					check_did_you_mean_type(name, bt->Enum.fields);
+				}
 			}
 		}
 
@@ -5418,7 +5502,18 @@ gb_internal CALL_ARGUMENT_CHECKER(check_call_arguments_internal) {
 		data->score = score;
 		data->result_type = final_proc_type->Proc.results;
 		data->gen_entity = gen_entity;
-		add_type_and_value(c, ce->proc, Addressing_Value, final_proc_type, {});
+
+
+		Ast *proc_lit = nullptr;
+		if (ce->proc->tav.value.kind == ExactValue_Procedure) {
+			Ast *vp = unparen_expr(ce->proc->tav.value.value_procedure);
+			if (vp && vp->kind == Ast_ProcLit) {
+				proc_lit = vp;
+			}
+		}
+		if (proc_lit == nullptr) {
+			add_type_and_value(c, ce->proc, Addressing_Value, final_proc_type, {});
+		}
 	}
 
 	return err;
@@ -6744,6 +6839,10 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c
 
 	if (operand->mode == Addressing_Builtin) {
 		i32 id = operand->builtin_id;
+		Entity *e = entity_of_node(operand->expr);
+		if (e != nullptr && e->token.string == "expand_to_tuple") {
+			warning(operand->expr, "'expand_to_tuple' has been replaced with 'expand_values'");
+		}
 		if (!check_builtin_procedure(c, operand, call, id, type_hint)) {
 			operand->mode = Addressing_Invalid;
 			operand->type = t_invalid;
@@ -7029,7 +7128,7 @@ gb_internal bool ternary_compare_types(Type *x, Type *y) {
 }
 
 
-gb_internal bool check_range(CheckerContext *c, Ast *node, Operand *x, Operand *y, ExactValue *inline_for_depth_, Type *type_hint=nullptr) {
+gb_internal bool check_range(CheckerContext *c, Ast *node, bool is_for_loop, Operand *x, Operand *y, ExactValue *inline_for_depth_, Type *type_hint=nullptr) {
 	if (!is_ast_range(node)) {
 		return false;
 	}
@@ -7078,9 +7177,17 @@ gb_internal bool check_range(CheckerContext *c, Ast *node, Operand *x, Operand *
 	}
 
 	Type *type = x->type;
-	if (!is_type_integer(type) && !is_type_float(type) && !is_type_pointer(type) && !is_type_enum(type)) {
-		error(ie->op, "Only numerical and pointer types are allowed within interval expressions");
-		return false;
+
+	if (is_for_loop) {
+		if (!is_type_integer(type) && !is_type_float(type) && !is_type_enum(type)) {
+			error(ie->op, "Only numerical types are allowed within interval expressions");
+			return false;
+		}
+	} else {
+		if (!is_type_integer(type) && !is_type_float(type) && !is_type_pointer(type) && !is_type_enum(type)) {
+			error(ie->op, "Only numerical and pointer types are allowed within interval expressions");
+			return false;
+		}
 	}
 
 	if (x->mode == Addressing_Constant &&
@@ -7786,6 +7893,124 @@ gb_internal ExprKind check_or_return_expr(CheckerContext *c, Operand *o, Ast *no
 	return Expr_Expr;
 }
 
+
+gb_internal void check_compound_literal_field_values(CheckerContext *c, Slice<Ast *> const &elems, Operand *o, Type *type, bool &is_constant) {
+	Type *bt = base_type(type);
+
+	StringSet fields_visited = {};
+	defer (string_set_destroy(&fields_visited));
+
+	StringMap<String> fields_visited_through_raw_union = {};
+	defer (string_map_destroy(&fields_visited_through_raw_union));
+
+	for (Ast *elem : elems) {
+		if (elem->kind != Ast_FieldValue) {
+			error(elem, "Mixture of 'field = value' and value elements in a literal is not allowed");
+			continue;
+		}
+		ast_node(fv, FieldValue, elem);
+		if (fv->field->kind != Ast_Ident) {
+			gbString expr_str = expr_to_string(fv->field);
+			error(elem, "Invalid field name '%s' in structure literal", expr_str);
+			gb_string_free(expr_str);
+			continue;
+		}
+		String name = fv->field->Ident.token.string;
+
+		Selection sel = lookup_field(type, name, o->mode == Addressing_Type);
+		bool is_unknown = sel.entity == nullptr;
+		if (is_unknown) {
+			error(fv->field, "Unknown field '%.*s' in structure literal", LIT(name));
+			continue;
+		}
+
+		Entity *field = bt->Struct.fields[sel.index[0]];
+		add_entity_use(c, fv->field, field);
+		if (string_set_update(&fields_visited, name)) {
+			if (sel.index.count > 1) {
+				if (String *found = string_map_get(&fields_visited_through_raw_union, sel.entity->token.string)) {
+					error(fv->field, "Field '%.*s' is already initialized due to a previously assigned struct #raw_union field '%.*s'", LIT(sel.entity->token.string), LIT(*found));
+				} else {
+					error(fv->field, "Duplicate or reused field '%.*s' in structure literal", LIT(sel.entity->token.string));
+				}
+			} else {
+				error(fv->field, "Duplicate field '%.*s' in structure literal", LIT(field->token.string));
+			}
+			continue;
+		} else if (String *found = string_map_get(&fields_visited_through_raw_union, sel.entity->token.string)) {
+			error(fv->field, "Field '%.*s' is already initialized due to a previously assigned struct #raw_union field '%.*s'", LIT(sel.entity->token.string), LIT(*found));
+			continue;
+		}
+		if (sel.indirect) {
+			error(fv->field, "Cannot assign to the %d-nested anonymous indirect field '%.*s' in a structure literal", cast(int)sel.index.count-1, LIT(name));
+			continue;
+		}
+
+		if (sel.index.count > 1) {
+			if (is_constant) {
+				Type *ft = type;
+				for (i32 index : sel.index) {
+					Type *bt = base_type(ft);
+					switch (bt->kind) {
+					case Type_Struct:
+						if (bt->Struct.is_raw_union) {
+							is_constant = false;
+							break;
+						}
+						ft = bt->Struct.fields[index]->type;
+						break;
+					case Type_Array:
+						ft = bt->Array.elem;
+						break;
+					default:
+						GB_PANIC("invalid type: %s", type_to_string(ft));
+						break;
+					}
+				}
+				if (is_constant &&
+				    (is_type_any(ft) || is_type_union(ft) || is_type_raw_union(ft) || is_type_typeid(ft))) {
+					is_constant = false;
+				}
+			}
+
+			Type *nested_ft = bt;
+			for (i32 index : sel.index) {
+				Type *bt = base_type(nested_ft);
+				switch (bt->kind) {
+				case Type_Struct:
+					if (bt->Struct.is_raw_union) {
+						for (Entity *re : bt->Struct.fields) {
+							string_map_set(&fields_visited_through_raw_union, re->token.string, sel.entity->token.string);
+						}
+					}
+					nested_ft = bt->Struct.fields[index]->type;
+					break;
+				case Type_Array:
+					nested_ft = bt->Array.elem;
+					break;
+				default:
+					GB_PANIC("invalid type %s", type_to_string(nested_ft));
+					break;
+				}
+			}
+			field = sel.entity;
+		}
+
+
+		Operand o = {};
+		check_expr_or_type(c, &o, fv->value, field->type);
+
+		if (is_type_any(field->type) || is_type_union(field->type) || is_type_raw_union(field->type) || is_type_typeid(field->type)) {
+			is_constant = false;
+		}
+		if (is_constant) {
+			is_constant = check_is_operand_compound_lit_constant(c, &o);
+		}
+
+		check_assignment(c, &o, field->type, str_lit("structure literal"));
+	}
+}
+
 gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast *node, Type *type_hint) {
 	ExprKind kind = Expr_Expr;
 	ast_node(cl, CompoundLit, node);
@@ -7873,45 +8098,13 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast *
 						error(node, "%s ('struct #raw_union') compound literals are only allowed to contain up to 1 'field = value' element, got %td", type_str, cl->elems.count);
 						gb_string_free(type_str);
 					} else {
-						Ast *elem = cl->elems[0];
-						ast_node(fv, FieldValue, elem);
-						if (fv->field->kind != Ast_Ident) {
-							gbString expr_str = expr_to_string(fv->field);
-							error(elem, "Invalid field name '%s' in structure literal", expr_str);
-							gb_string_free(expr_str);
-							break;
-						}
-
-						String name = fv->field->Ident.token.string;
-
-						Selection sel = lookup_field(type, name, o->mode == Addressing_Type);
-						bool is_unknown = sel.entity == nullptr;
-						if (is_unknown) {
-							error(elem, "Unknown field '%.*s' in structure literal", LIT(name));
-							break;
-						}
-
-						if (sel.index.count > 1) {
-							error(elem, "Cannot assign to an anonymous field '%.*s' in a structure literal (at the moment)", LIT(name));
-							break;
-						}
-
-						Entity *field = t->Struct.fields[sel.index[0]];
-						add_entity_use(c, fv->field, field);
-
-						Operand o = {};
-						check_expr_or_type(c, &o, fv->value, field->type);
-
-
-						check_assignment(c, &o, field->type, str_lit("structure literal"));
+						check_compound_literal_field_values(c, cl->elems, o, type, is_constant);
 					}
-
 				}
 			}
 			break;
 		}
 
-
 		isize field_count = t->Struct.fields.count;
 		isize min_field_count = t->Struct.fields.count;
 		for (isize i = min_field_count-1; i >= 0; i--) {
@@ -7925,58 +8118,7 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast *
 		}
 
 		if (cl->elems[0]->kind == Ast_FieldValue) {
-			TEMPORARY_ALLOCATOR_GUARD();
-
-			bool *fields_visited = gb_alloc_array(temporary_allocator(), bool, field_count);
-
-			for (Ast *elem : cl->elems) {
-				if (elem->kind != Ast_FieldValue) {
-					error(elem, "Mixture of 'field = value' and value elements in a literal is not allowed");
-					continue;
-				}
-				ast_node(fv, FieldValue, elem);
-				if (fv->field->kind != Ast_Ident) {
-					gbString expr_str = expr_to_string(fv->field);
-					error(elem, "Invalid field name '%s' in structure literal", expr_str);
-					gb_string_free(expr_str);
-					continue;
-				}
-				String name = fv->field->Ident.token.string;
-
-				Selection sel = lookup_field(type, name, o->mode == Addressing_Type);
-				bool is_unknown = sel.entity == nullptr;
-				if (is_unknown) {
-					error(elem, "Unknown field '%.*s' in structure literal", LIT(name));
-					continue;
-				}
-
-				if (sel.index.count > 1) {
-					error(elem, "Cannot assign to an anonymous field '%.*s' in a structure literal (at the moment)", LIT(name));
-					continue;
-				}
-
-				Entity *field = t->Struct.fields[sel.index[0]];
-				add_entity_use(c, fv->field, field);
-
-				if (fields_visited[sel.index[0]]) {
-					error(elem, "Duplicate field '%.*s' in structure literal", LIT(name));
-					continue;
-				}
-
-				fields_visited[sel.index[0]] = true;
-
-				Operand o = {};
-				check_expr_or_type(c, &o, fv->value, field->type);
-
-				if (is_type_any(field->type) || is_type_union(field->type) || is_type_raw_union(field->type) || is_type_typeid(field->type)) {
-					is_constant = false;
-				}
-				if (is_constant) {
-					is_constant = check_is_operand_compound_lit_constant(c, &o);
-				}
-
-				check_assignment(c, &o, field->type, str_lit("structure literal"));
-			}
+			check_compound_literal_field_values(c, cl->elems, o, type, is_constant);
 		} else {
 			bool seen_field_value = false;
 
@@ -8093,7 +8235,7 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast *
 
 					Operand x = {};
 					Operand y = {};
-					bool ok = check_range(c, fv->field, &x, &y, nullptr);
+					bool ok = check_range(c, fv->field, false, &x, &y, nullptr);
 					if (!ok) {
 						continue;
 					}
@@ -8309,7 +8451,7 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast *
 
 					Operand x = {};
 					Operand y = {};
-					bool ok = check_range(c, fv->field, &x, &y, nullptr, index_type);
+					bool ok = check_range(c, fv->field, false, &x, &y, nullptr, index_type);
 					if (!ok) {
 						continue;
 					}

+ 7 - 8
src/check_stmt.cpp

@@ -725,7 +725,7 @@ gb_internal void check_inline_range_stmt(CheckerContext *ctx, Ast *node, u32 mod
 		Operand x = {};
 		Operand y = {};
 
-		bool ok = check_range(ctx, expr, &x, &y, &inline_for_depth);
+		bool ok = check_range(ctx, expr, true, &x, &y, &inline_for_depth);
 		if (!ok) {
 			goto skip_expr;
 		}
@@ -978,19 +978,19 @@ gb_internal void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags
 
 				Operand a = lhs;
 				Operand b = rhs;
-				check_comparison(ctx, &a, &x, Token_LtEq);
+				check_comparison(ctx, expr, &a, &x, Token_LtEq);
 				if (a.mode == Addressing_Invalid) {
 					continue;
 				}
 
-				check_comparison(ctx, &b, &x, upper_op);
+				check_comparison(ctx, expr, &b, &x, upper_op);
 				if (b.mode == Addressing_Invalid) {
 					continue;
 				}
 
 				Operand a1 = lhs;
 				Operand b1 = rhs;
-				check_comparison(ctx, &a1, &b1, Token_LtEq);
+				check_comparison(ctx, expr, &a1, &b1, Token_LtEq);
 
 				add_to_seen_map(ctx, &seen, upper_op, x, lhs, rhs);
 
@@ -1029,7 +1029,7 @@ gb_internal void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags
 
 					// NOTE(bill): the ordering here matters
 					Operand z = y;
-					check_comparison(ctx, &z, &x, Token_CmpEq);
+					check_comparison(ctx, expr, &z, &x, Token_CmpEq);
 					if (z.mode == Addressing_Invalid) {
 						continue;
 					}
@@ -1293,7 +1293,6 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_
 		for (Type *t : variants) {
 			if (!type_ptr_set_exists(&seen, t)) {
 				array_add(&unhandled, t);
-				gb_printf_err("HERE: %p %s\n", t, type_to_string(t));
 			}
 		}
 
@@ -1439,7 +1438,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags)
 		Operand x = {};
 		Operand y = {};
 
-		bool ok = check_range(ctx, expr, &x, &y, nullptr);
+		bool ok = check_range(ctx, expr, true, &x, &y, nullptr);
 		if (!ok) {
 			goto skip_expr_range_stmt;
 		}
@@ -1921,7 +1920,7 @@ gb_internal void check_expr_stmt(CheckerContext *ctx, Ast *node) {
 	case Addressing_Type:
 		{
 			gbString str = type_to_string(operand.type);
-			error(node, "'%s' is not an expression", str);
+			error(node, "'%s' is not an expression but a type and cannot be used as a statement", str);
 			gb_string_free(str);
 			break;
 		}

+ 10 - 4
src/check_type.cpp

@@ -1309,6 +1309,8 @@ gb_internal ParameterValue handle_parameter_value(CheckerContext *ctx, Type *in_
 		init_core_source_code_location(ctx->checker);
 		param_value.kind = ParameterValue_Location;
 		o.type = t_source_code_location;
+		o.mode = Addressing_Value;
+		o.expr = expr;
 
 		if (in_type) {
 			check_assignment(ctx, &o, in_type, str_lit("parameter value"));
@@ -1666,17 +1668,21 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para
 					if (is_poly_name) {
 						bool valid = false;
 						if (is_type_proc(op.type)) {
-							Entity *proc_entity = entity_from_expr(op.expr);
-							valid = (proc_entity != nullptr) && (op.value.kind == ExactValue_Procedure);
-							if (valid) {
+							Ast *expr = unparen_expr(op.expr);
+							Entity *proc_entity = entity_from_expr(expr);
+							if (proc_entity) {
 								poly_const = exact_value_procedure(proc_entity->identifier.load() ? proc_entity->identifier.load() : op.expr);
+								valid = true;
+							} else if (expr->kind == Ast_ProcLit) {
+								poly_const = exact_value_procedure(expr);
+								valid = true;
 							}
 						}
 						if (!valid) {
 							if (op.mode == Addressing_Constant) {
 								poly_const = op.value;
 							} else {
-								error(op.expr, "Expected a constant value for this polymorphic name parameter");
+								error(op.expr, "Expected a constant value for this polymorphic name parameter, got %s", expr_to_string(op.expr));
 								success = false;
 							}
 						}

+ 61 - 14
src/checker.cpp

@@ -1070,6 +1070,13 @@ gb_internal void init_universal(void) {
 			}
 		}
 	}
+	{
+		BuiltinProcId id = BuiltinProc_expand_values;
+		String name = str_lit("expand_to_tuple");
+		Entity *entity = alloc_entity(Entity_Builtin, nullptr, make_token_ident(name), t_invalid);
+		entity->Builtin.id = id;
+		add_global_entity(entity, builtin_pkg->scope);
+	}
 
 	bool defined_values_double_declaration = false;
 	for (auto const &entry : bc->defined_values) {
@@ -1148,6 +1155,7 @@ gb_internal void init_checker_info(CheckerInfo *i) {
 	array_init(&i->variable_init_order, a);
 	array_init(&i->testing_procedures, a, 0, 0);
 	array_init(&i->init_procedures, a, 0, 0);
+	array_init(&i->fini_procedures, a, 0, 0);
 	array_init(&i->required_foreign_imports_through_force, a, 0, 0);
 
 	map_init(&i->objc_msgSend_types);
@@ -1415,7 +1423,7 @@ gb_internal isize type_info_index(CheckerInfo *info, Type *type, bool error_on_f
 }
 
 
-gb_internal void add_untyped(CheckerContext *c, Ast *expr, AddressingMode mode, Type *type, ExactValue value) {
+gb_internal void add_untyped(CheckerContext *c, Ast *expr, AddressingMode mode, Type *type, ExactValue const &value) {
 	if (expr == nullptr) {
 		return;
 	}
@@ -1432,7 +1440,7 @@ gb_internal void add_untyped(CheckerContext *c, Ast *expr, AddressingMode mode,
 	check_set_expr_info(c, expr, mode, type, value);
 }
 
-gb_internal void add_type_and_value(CheckerContext *ctx, Ast *expr, AddressingMode mode, Type *type, ExactValue value) {
+gb_internal void add_type_and_value(CheckerContext *ctx, Ast *expr, AddressingMode mode, Type *type, ExactValue const &value) {
 	if (expr == nullptr) {
 		return;
 	}
@@ -1478,10 +1486,9 @@ gb_internal void add_type_and_value(CheckerContext *ctx, Ast *expr, AddressingMo
 
 gb_internal void add_entity_definition(CheckerInfo *i, Ast *identifier, Entity *entity) {
 	GB_ASSERT(identifier != nullptr);
-	GB_ASSERT(identifier->kind == Ast_Ident);
-	// if (is_blank_ident(identifier)) {
-		// return;
-	// }
+	if (identifier->kind != Ast_Ident) {
+		return;
+	}
 	if (identifier->Ident.entity != nullptr) {
 		// NOTE(bill): Identifier has already been handled
 		return;
@@ -1540,7 +1547,7 @@ gb_internal void add_entity_flags_from_file(CheckerContext *c, Entity *e, Scope
 		AstPackage *pkg = c->file->pkg;
 		if (pkg->kind == Package_Init && e->kind == Entity_Procedure && e->token.string == "main") {
 			// Do nothing
-		} else if (e->flags & (EntityFlag_Test|EntityFlag_Init)) {
+		} else if (e->flags & (EntityFlag_Test|EntityFlag_Init|EntityFlag_Fini)) {
 			// Do nothing
 		} else {
 			e->flags |= EntityFlag_Lazy;
@@ -1608,7 +1615,7 @@ gb_internal bool could_entity_be_lazy(Entity *e, DeclInfo *d) {
 		return false;
 	}
 
-	if (e->flags & (EntityFlag_Test|EntityFlag_Init)) {
+	if (e->flags & (EntityFlag_Test|EntityFlag_Init|EntityFlag_Fini)) {
 		return false;
 	} else if (e->kind == Entity_Variable && e->Variable.is_export) {
 		return false;
@@ -2417,6 +2424,28 @@ gb_internal void generate_minimum_dependency_set(Checker *c, Entity *start) {
 					add_dependency_to_set(c, e);
 					array_add(&c->info.init_procedures, e);
 				}
+			} else if (e->flags & EntityFlag_Fini) {
+				Type *t = base_type(e->type);
+				GB_ASSERT(t->kind == Type_Proc);
+
+				bool is_fini = true;
+
+				if (t->Proc.param_count != 0 || t->Proc.result_count != 0) {
+					gbString str = type_to_string(t);
+					error(e->token, "@(fini) procedures must have a signature type with no parameters nor results, got %s", str);
+					gb_string_free(str);
+					is_fini = false;
+				}
+
+				if ((e->scope->flags & (ScopeFlag_File|ScopeFlag_Pkg)) == 0) {
+					error(e->token, "@(fini) procedures must be declared at the file scope");
+					is_fini = false;
+				}
+
+				if (is_fini) {
+					add_dependency_to_set(c, e);
+					array_add(&c->info.fini_procedures, e);
+				}
 			}
 			break;
 		}
@@ -2744,7 +2773,7 @@ gb_internal void init_core_type_info(Checker *c) {
 	t_type_info_enumerated_array = find_core_type(c, str_lit("Type_Info_Enumerated_Array"));
 	t_type_info_dynamic_array    = find_core_type(c, str_lit("Type_Info_Dynamic_Array"));
 	t_type_info_slice            = find_core_type(c, str_lit("Type_Info_Slice"));
-	t_type_info_tuple            = find_core_type(c, str_lit("Type_Info_Tuple"));
+	t_type_info_parameters       = find_core_type(c, str_lit("Type_Info_Parameters"));
 	t_type_info_struct           = find_core_type(c, str_lit("Type_Info_Struct"));
 	t_type_info_union            = find_core_type(c, str_lit("Type_Info_Union"));
 	t_type_info_enum             = find_core_type(c, str_lit("Type_Info_Enum"));
@@ -2773,7 +2802,7 @@ gb_internal void init_core_type_info(Checker *c) {
 	t_type_info_enumerated_array_ptr = alloc_type_pointer(t_type_info_enumerated_array);
 	t_type_info_dynamic_array_ptr    = alloc_type_pointer(t_type_info_dynamic_array);
 	t_type_info_slice_ptr            = alloc_type_pointer(t_type_info_slice);
-	t_type_info_tuple_ptr            = alloc_type_pointer(t_type_info_tuple);
+	t_type_info_parameters_ptr       = alloc_type_pointer(t_type_info_parameters);
 	t_type_info_struct_ptr           = alloc_type_pointer(t_type_info_struct);
 	t_type_info_union_ptr            = alloc_type_pointer(t_type_info_union);
 	t_type_info_enum_ptr             = alloc_type_pointer(t_type_info_enum);
@@ -2968,6 +2997,12 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) {
 		}
 		ac->init = true;
 		return true;
+	} else if (name == "fini") {
+		if (value != nullptr) {
+			error(value, "'%.*s' expects no parameter, or a string literal containing \"file\" or \"package\"", LIT(name));
+		}
+		ac->fini = true;
+		return true;
 	} else if (name == "deferred") {
 		if (value != nullptr) {
 			Operand o = {};
@@ -3609,6 +3644,7 @@ gb_internal void check_collect_value_decl(CheckerContext *c, Ast *decl) {
 	EntityVisiblityKind entity_visibility_kind = c->foreign_context.visibility_kind;
 	bool is_test = false;
 	bool is_init = false;
+	bool is_fini = false;
 
 	for_array(i, vd->attributes) {
 		Ast *attr = vd->attributes[i];
@@ -3668,6 +3704,8 @@ gb_internal void check_collect_value_decl(CheckerContext *c, Ast *decl) {
 				is_test = true;
 			} else if (name == "init") {
 				is_init = true;
+			} else if (name == "fini") {
+				is_fini = true;
 			}
 		}
 	}
@@ -3801,8 +3839,12 @@ gb_internal void check_collect_value_decl(CheckerContext *c, Ast *decl) {
 				if (is_test) {
 					e->flags |= EntityFlag_Test;
 				}
-				if (is_init) {
+				if (is_init && is_fini) {
+					error(name, "A procedure cannot be both declared as @(init) and @(fini)");
+				} else if (is_init) {
 					e->flags |= EntityFlag_Init;
+				} else if (is_fini) {
+					e->flags |= EntityFlag_Fini;
 				}
 			} else if (init->kind == Ast_ProcGroup) {
 				ast_node(pg, ProcGroup, init);
@@ -5628,9 +5670,14 @@ gb_internal GB_COMPARE_PROC(init_procedures_cmp) {
 	return i32_cmp(x->token.pos.offset, y->token.pos.offset);
 }
 
+gb_internal GB_COMPARE_PROC(fini_procedures_cmp) {
+	return init_procedures_cmp(b, a);
+}
+
 
-gb_internal void check_sort_init_procedures(Checker *c) {
+gb_internal void check_sort_init_and_fini_procedures(Checker *c) {
 	gb_sort_array(c->info.init_procedures.data, c->info.init_procedures.count, init_procedures_cmp);
+	gb_sort_array(c->info.fini_procedures.data, c->info.fini_procedures.count, fini_procedures_cmp);
 }
 
 gb_internal void add_type_info_for_type_definitions(Checker *c) {
@@ -5840,8 +5887,8 @@ gb_internal void check_parsed_files(Checker *c) {
 		add_type_and_value(&c->builtin_ctx, u.expr, u.info->mode, u.info->type, u.info->value);
 	}
 
-	TIME_SECTION("sort init procedures");
-	check_sort_init_procedures(c);
+	TIME_SECTION("sort init and fini procedures");
+	check_sort_init_and_fini_procedures(c);
 
 	if (c->info.intrinsics_entry_point_usage.count > 0) {
 		TIME_SECTION("check intrinsics.__entry_point usage");

+ 4 - 2
src/checker.hpp

@@ -117,6 +117,7 @@ struct AttributeContext {
 	bool    disabled_proc       : 1;
 	bool    test                : 1;
 	bool    init                : 1;
+	bool    fini                : 1;
 	bool    set_cold            : 1;
 	u32 optimization_mode; // ProcedureOptimizationMode
 	i64 foreign_import_priority_index;
@@ -351,6 +352,7 @@ struct CheckerInfo {
 
 	Array<Entity *> testing_procedures;
 	Array<Entity *> init_procedures;
+	Array<Entity *> fini_procedures;
 
 	Array<Entity *> definitions;
 	Array<Entity *> entities;
@@ -483,9 +485,9 @@ gb_internal void    scope_lookup_parent (Scope *s, String const &name, Scope **s
 gb_internal Entity *scope_insert (Scope *s, Entity *entity);
 
 
-gb_internal void      add_type_and_value      (CheckerContext *c, Ast *expression, AddressingMode mode, Type *type, ExactValue value);
+gb_internal void      add_type_and_value      (CheckerContext *c, Ast *expression, AddressingMode mode, Type *type, ExactValue const &value);
 gb_internal ExprInfo *check_get_expr_info     (CheckerContext *c, Ast *expr);
-gb_internal void      add_untyped             (CheckerContext *c, Ast *expression, AddressingMode mode, Type *basic_type, ExactValue value);
+gb_internal void      add_untyped             (CheckerContext *c, Ast *expression, AddressingMode mode, Type *basic_type, ExactValue const &value);
 gb_internal void      add_entity_use          (CheckerContext *c, Ast *identifier, Entity *entity);
 gb_internal void      add_implicit_entity     (CheckerContext *c, Ast *node, Entity *e);
 gb_internal void      add_entity_and_decl_info(CheckerContext *c, Ast *identifier, Entity *e, DeclInfo *d, bool is_exported=true);

+ 2 - 2
src/checker_builtin_procs.hpp

@@ -25,7 +25,7 @@ enum BuiltinProcId {
 	BuiltinProc_kmag,
 	BuiltinProc_conj,
 
-	BuiltinProc_expand_to_tuple,
+	BuiltinProc_expand_values,
 
 	BuiltinProc_min,
 	BuiltinProc_max,
@@ -325,7 +325,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
 	{STR_LIT("kmag"),             1, false, Expr_Expr, BuiltinProcPkg_builtin},
 	{STR_LIT("conj"),             1, false, Expr_Expr, BuiltinProcPkg_builtin},
 
-	{STR_LIT("expand_to_tuple"),  1, false, Expr_Expr, BuiltinProcPkg_builtin},
+	{STR_LIT("expand_values"),    1, false, Expr_Expr, BuiltinProcPkg_builtin},
 
 	{STR_LIT("min"),              1, true,  Expr_Expr, BuiltinProcPkg_builtin},
 	{STR_LIT("max"),              1, true,  Expr_Expr, BuiltinProcPkg_builtin},

+ 9 - 4
src/common.cpp

@@ -53,6 +53,11 @@ struct TypeIsPointer<T *> {
 	enum {value = true};
 };
 
+template <typename T> struct TypeIsPtrSizedInteger { enum {value = false}; };
+template <> struct TypeIsPtrSizedInteger<isize> { enum {value = true}; };
+template <> struct TypeIsPtrSizedInteger<usize> { enum {value = true}; };
+
+
 #include "unicode.cpp"
 #include "array.cpp"
 #include "threading.cpp"
@@ -744,7 +749,7 @@ gb_internal LoadedFileError load_file_32(char const *fullpath, LoadedFile *memor
 			handle = nullptr;
 			goto window_handle_file_error;
 		}
-		
+
 		li_file_size = {};
 		if (!GetFileSizeEx(handle, &li_file_size)) {
 			goto window_handle_file_error;
@@ -754,7 +759,7 @@ gb_internal LoadedFileError load_file_32(char const *fullpath, LoadedFile *memor
 			CloseHandle(handle);
 			return LoadedFile_FileTooLarge;
 		}
-		
+
 		if (file_size == 0) {
 			CloseHandle(handle);
 			err = LoadedFile_Empty;
@@ -763,10 +768,10 @@ gb_internal LoadedFileError load_file_32(char const *fullpath, LoadedFile *memor
 			memory_mapped_file->size   = 0;
 			return err;
 		}
-		
+
 		file_mapping = CreateFileMappingW(handle, nullptr, PAGE_READONLY, 0, 0, nullptr);
 		CloseHandle(handle);
-		
+
 		file_data = MapViewOfFileEx(file_mapping, FILE_MAP_READ, 0, 0, 0/*file_size*/, nullptr/*base address*/);
 		memory_mapped_file->handle = cast(void *)file_mapping;
 		memory_mapped_file->data = file_data;

+ 1 - 0
src/entity.cpp

@@ -75,6 +75,7 @@ enum EntityFlag : u64 {
 	EntityFlag_Test          = 1ull<<30,
 	EntityFlag_Init          = 1ull<<31,
 	EntityFlag_Subtype       = 1ull<<32,
+	EntityFlag_Fini          = 1ull<<33,
 	
 	EntityFlag_CustomLinkName = 1ull<<40,
 	EntityFlag_CustomLinkage_Internal = 1ull<<41,

+ 169 - 49
src/error.cpp

@@ -97,15 +97,57 @@ gb_internal AstFile *thread_safe_get_ast_file_from_id(i32 index) {
 
 
 
+// NOTE: defined in build_settings.cpp
+gb_internal bool global_warnings_as_errors(void);
+gb_internal bool global_ignore_warnings(void);
+gb_internal bool show_error_line(void);
+gb_internal bool has_ansi_terminal_colours(void);
+gb_internal gbString get_file_line_as_string(TokenPos const &pos, i32 *offset);
+
+gb_internal void warning(Token const &token, char const *fmt, ...);
+gb_internal void error(Token const &token, char const *fmt, ...);
+gb_internal void error(TokenPos pos, char const *fmt, ...);
+gb_internal void error_line(char const *fmt, ...);
+gb_internal void syntax_error(Token const &token, char const *fmt, ...);
+gb_internal void syntax_error(TokenPos pos, char const *fmt, ...);
+gb_internal void syntax_warning(Token const &token, char const *fmt, ...);
+gb_internal void compiler_error(char const *fmt, ...);
+
 gb_internal void begin_error_block(void) {
 	mutex_lock(&global_error_collector.block_mutex);
 	global_error_collector.in_block.store(true);
 }
 
 gb_internal void end_error_block(void) {
-	if (global_error_collector.error_buffer.count > 0) {
-		isize n = global_error_collector.error_buffer.count;
-		u8 *text = gb_alloc_array(permanent_allocator(), u8, n+1);
+	isize n = global_error_collector.error_buffer.count;
+	if (n > 0) {
+		u8 *text = global_error_collector.error_buffer.data;
+
+		bool add_extra_newline = false;
+
+		if (show_error_line()) {
+			if (n >= 2 && !(text[n-2] == '\n' && text[n-1] == '\n')) {
+				add_extra_newline = true;
+			}
+		} else {
+			isize newline_count = 0;
+			for (isize i = 0; i < n; i++) {
+				if (text[i] == '\n') {
+					newline_count += 1;
+				}
+			}
+			if (newline_count > 1) {
+				add_extra_newline = true;
+			}
+		}
+
+		if (add_extra_newline) {
+			// add an extra new line as padding when the error line is being shown
+			error_line("\n");
+		}
+
+		n = global_error_collector.error_buffer.count;
+		text = gb_alloc_array(permanent_allocator(), u8, n+1);
 		gb_memmove(text, global_error_collector.error_buffer.data, n);
 		text[n] = 0;
 		String s = {text, n};
@@ -149,15 +191,8 @@ gb_internal ERROR_OUT_PROC(default_error_out_va) {
 	gb_file_write(f, buf, n);
 }
 
-
 gb_global ErrorOutProc *error_out_va = default_error_out_va;
 
-// NOTE: defined in build_settings.cpp
-gb_internal bool global_warnings_as_errors(void);
-gb_internal bool global_ignore_warnings(void);
-gb_internal bool show_error_line(void);
-gb_internal gbString get_file_line_as_string(TokenPos const &pos, i32 *offset);
-
 gb_internal void error_out(char const *fmt, ...) {
 	va_list va;
 	va_start(va, fmt);
@@ -165,6 +200,49 @@ gb_internal void error_out(char const *fmt, ...) {
 	va_end(va);
 }
 
+enum TerminalStyle {
+	TerminalStyle_Normal,
+	TerminalStyle_Bold,
+	TerminalStyle_Underline,
+};
+
+enum TerminalColour {
+	TerminalColour_White,
+	TerminalColour_Red,
+	TerminalColour_Yellow,
+	TerminalColour_Green,
+	TerminalColour_Cyan,
+	TerminalColour_Blue,
+	TerminalColour_Purple,
+	TerminalColour_Black,
+};
+
+gb_internal void terminal_set_colours(TerminalStyle style, TerminalColour foreground) {
+	if (has_ansi_terminal_colours()) {
+		char const *ss = "0";
+		switch (style) {
+		case TerminalStyle_Normal:    ss = "0"; break;
+		case TerminalStyle_Bold:      ss = "1"; break;
+		case TerminalStyle_Underline: ss = "4"; break;
+		}
+		switch (foreground) {
+		case TerminalColour_White:  error_out("\x1b[%s;37m", ss); break;
+		case TerminalColour_Red:    error_out("\x1b[%s;31m", ss); break;
+		case TerminalColour_Yellow: error_out("\x1b[%s;33m", ss); break;
+		case TerminalColour_Green:  error_out("\x1b[%s;32m", ss); break;
+		case TerminalColour_Cyan:   error_out("\x1b[%s;36m", ss); break;
+		case TerminalColour_Blue:   error_out("\x1b[%s;34m", ss); break;
+		case TerminalColour_Purple: error_out("\x1b[%s;35m", ss); break;
+		case TerminalColour_Black:  error_out("\x1b[%s;30m", ss); break;
+		}
+	}
+}
+gb_internal void terminal_reset_colours(void) {
+	if (has_ansi_terminal_colours()) {
+		error_out("\x1b[0m");
+	}
+}
+
 
 gb_internal bool show_error_on_line(TokenPos const &pos, TokenPos end) {
 	if (!show_error_line()) {
@@ -181,26 +259,33 @@ gb_internal bool show_error_on_line(TokenPos const &pos, TokenPos end) {
 		// TODO(bill): This assumes ASCII
 
 		enum {
-			MAX_LINE_LENGTH  = 76,
+			MAX_LINE_LENGTH  = 80,
 			MAX_TAB_WIDTH    = 8,
-			ELLIPSIS_PADDING = 8
+			ELLIPSIS_PADDING = 8, // `...  ...`
+			MAX_LINE_LENGTH_PADDED = MAX_LINE_LENGTH-MAX_TAB_WIDTH-ELLIPSIS_PADDING,
 		};
 
-		error_out("\n\t");
-		if (line.len+MAX_TAB_WIDTH+ELLIPSIS_PADDING > MAX_LINE_LENGTH) {
-			i32 const half_width = MAX_LINE_LENGTH/2;
-			i32 left  = cast(i32)(offset);
-			i32 right = cast(i32)(line.len - offset);
-			left  = gb_min(left, half_width);
-			right = gb_min(right, half_width);
+		error_out("\t");
+
+		terminal_set_colours(TerminalStyle_Bold, TerminalColour_White);
 
-			line.text += offset-left;
-			line.len  -= offset+right-left;
 
-			line = string_trim_whitespace(line);
+		i32 error_length = gb_max(end.offset - pos.offset, 1);
 
-			offset = left + ELLIPSIS_PADDING/2;
+		isize squiggle_extra = 0;
 
+		if (line.len > MAX_LINE_LENGTH_PADDED) {
+			i32 left = MAX_TAB_WIDTH;
+			line.text += offset-left;
+			line.len  -= offset-left;
+			offset = left+MAX_TAB_WIDTH/2;
+			if (line.len > MAX_LINE_LENGTH_PADDED) {
+				line.len = MAX_LINE_LENGTH_PADDED;
+				if (error_length > line.len-left) {
+					error_length = cast(i32)line.len - left;
+					squiggle_extra = 1;
+				}
+			}
 			error_out("... %.*s ...", LIT(line));
 		} else {
 			error_out("%.*s", LIT(line));
@@ -210,6 +295,9 @@ gb_internal bool show_error_on_line(TokenPos const &pos, TokenPos end) {
 		for (i32 i = 0; i < offset; i++) {
 			error_out(" ");
 		}
+
+		terminal_set_colours(TerminalStyle_Bold, TerminalColour_Green);
+
 		error_out("^");
 		if (end.file_id == pos.file_id) {
 			if (end.line > pos.line) {
@@ -217,34 +305,54 @@ gb_internal bool show_error_on_line(TokenPos const &pos, TokenPos end) {
 					error_out("~");
 				}
 			} else if (end.line == pos.line && end.column > pos.column) {
-				i32 length = gb_min(end.offset - pos.offset, cast(i32)(line.len-offset));
-				for (i32 i = 1; i < length-1; i++) {
+				for (i32 i = 1; i < error_length-1+squiggle_extra; i++) {
 					error_out("~");
 				}
-				if (length > 1) {
+				if (error_length > 1 && squiggle_extra == 0) {
 					error_out("^");
 				}
 			}
 		}
 
-		error_out("\n\n");
+		terminal_reset_colours();
+
+		error_out("\n");
 		return true;
 	}
 	return false;
 }
 
+gb_internal void error_out_pos(TokenPos pos) {
+	terminal_set_colours(TerminalStyle_Bold, TerminalColour_White);
+	error_out("%s ", token_pos_to_string(pos));
+	terminal_reset_colours();
+}
+
+gb_internal void error_out_coloured(char const *str, TerminalStyle style, TerminalColour foreground) {
+	terminal_set_colours(style, foreground);
+	error_out(str);
+	terminal_reset_colours();
+}
+
+
+
 gb_internal void error_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) {
 	global_error_collector.count.fetch_add(1);
 
 	mutex_lock(&global_error_collector.mutex);
 	// NOTE(bill): Duplicate error, skip it
 	if (pos.line == 0) {
-		error_out("Error: %s\n", gb_bprintf_va(fmt, va));
+		error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red);
+		error_out_va(fmt, va);
+		error_out("\n");
 	} else if (global_error_collector.prev != pos) {
 		global_error_collector.prev = pos;
-		error_out("%s %s\n",
-		          token_pos_to_string(pos),
-		          gb_bprintf_va(fmt, va));
+		error_out_pos(pos);
+		if (has_ansi_terminal_colours()) {
+			error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red);
+		}
+		error_out_va(fmt, va);
+		error_out("\n");
 		show_error_on_line(pos, end);
 	}
 	mutex_unlock(&global_error_collector.mutex);
@@ -263,12 +371,15 @@ gb_internal void warning_va(TokenPos const &pos, TokenPos end, char const *fmt,
 	if (!global_ignore_warnings()) {
 		// NOTE(bill): Duplicate error, skip it
 		if (pos.line == 0) {
-			error_out("Warning: %s\n", gb_bprintf_va(fmt, va));
+			error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow);
+			error_out_va(fmt, va);
+			error_out("\n");
 		} else if (global_error_collector.prev != pos) {
 			global_error_collector.prev = pos;
-			error_out("%s Warning: %s\n",
-			          token_pos_to_string(pos),
-			          gb_bprintf_va(fmt, va));
+			error_out_pos(pos);
+			error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow);
+			error_out_va(fmt, va);
+			error_out("\n");
 			show_error_on_line(pos, end);
 		}
 	}
@@ -285,12 +396,15 @@ gb_internal void error_no_newline_va(TokenPos const &pos, char const *fmt, va_li
 	global_error_collector.count++;
 	// NOTE(bill): Duplicate error, skip it
 	if (pos.line == 0) {
-		error_out("Error: %s", gb_bprintf_va(fmt, va));
+		error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red);
+		error_out_va(fmt, va);
 	} else if (global_error_collector.prev != pos) {
 		global_error_collector.prev = pos;
-		error_out("%s %s",
-		          token_pos_to_string(pos),
-		          gb_bprintf_va(fmt, va));
+		error_out_pos(pos);
+		if (has_ansi_terminal_colours()) {
+			error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red);
+		}
+		error_out_va(fmt, va);
 	}
 	mutex_unlock(&global_error_collector.mutex);
 	if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT) {
@@ -305,12 +419,15 @@ gb_internal void syntax_error_va(TokenPos const &pos, TokenPos end, char const *
 	// NOTE(bill): Duplicate error, skip it
 	if (global_error_collector.prev != pos) {
 		global_error_collector.prev = pos;
-		error_out("%s Syntax Error: %s\n",
-		          token_pos_to_string(pos),
-		          gb_bprintf_va(fmt, va));
-		show_error_on_line(pos, end);
+		error_out_pos(pos);
+		error_out_coloured("Syntax Error: ", TerminalStyle_Normal, TerminalColour_Red);
+		error_out_va(fmt, va);
+		error_out("\n");
+		// show_error_on_line(pos, end);
 	} else if (pos.line == 0) {
-		error_out("Syntax Error: %s\n", gb_bprintf_va(fmt, va));
+		error_out_coloured("Syntax Error: ", TerminalStyle_Normal, TerminalColour_Red);
+		error_out_va(fmt, va);
+		error_out("\n");
 	}
 
 	mutex_unlock(&global_error_collector.mutex);
@@ -330,12 +447,15 @@ gb_internal void syntax_warning_va(TokenPos const &pos, TokenPos end, char const
 		// NOTE(bill): Duplicate error, skip it
 		if (global_error_collector.prev != pos) {
 			global_error_collector.prev = pos;
-			error_out("%s Syntax Warning: %s\n",
-			          token_pos_to_string(pos),
-			          gb_bprintf_va(fmt, va));
-			show_error_on_line(pos, end);
+			error_out_pos(pos);
+			error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow);
+			error_out_va(fmt, va);
+			error_out("\n");
+			// show_error_on_line(pos, end);
 		} else if (pos.line == 0) {
-			error_out("Warning: %s\n", gb_bprintf_va(fmt, va));
+			error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow);
+			error_out_va(fmt, va);
+			error_out("\n");
 		}
 	}
 	mutex_unlock(&global_error_collector.mutex);

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików