Browse Source

Merge remote-tracking branch 'upstream/master' into fix_odin_test

Jason Kercher 3 years ago
parent
commit
d2bc41a2df
100 changed files with 4778 additions and 2107 deletions
  1. 1 0
      .github/FUNDING.yml
  2. 15 0
      .github/workflows/ci.yml
  3. 2 0
      .gitignore
  4. 14 10
      Makefile
  5. 13 0
      core/c/libc/errno.odin
  6. 25 0
      core/c/libc/stdio.odin
  7. 7 2
      core/c/libc/time.odin
  8. 5 0
      core/c/libc/wctype.odin
  9. 8 2
      core/compress/common.odin
  10. 2 2
      core/compress/gzip/gzip.odin
  11. 1 1
      core/compress/zlib/zlib.odin
  12. 17 12
      core/container/bit_array/bit_array.odin
  13. 7 6
      core/container/bit_array/doc.odin
  14. 1 1
      core/crypto/rand_generic.odin
  15. 12 0
      core/crypto/rand_openbsd.odin
  16. 23 0
      core/crypto/rand_windows.odin
  17. 335 0
      core/crypto/siphash/siphash.odin
  18. 1 1
      core/dynlib/lib_unix.odin
  19. 27 0
      core/encoding/varint/doc.odin
  20. 139 0
      core/encoding/varint/leb128.odin
  21. 186 178
      core/fmt/fmt.odin
  22. 2 0
      core/intrinsics/intrinsics.odin
  23. 13 13
      core/mem/doc.odin
  24. 35 19
      core/mem/virtual/virtual.odin
  25. 5 5
      core/mem/virtual/virtual_linux.odin
  26. 21 5
      core/mem/virtual/virtual_platform.odin
  27. 5 5
      core/mem/virtual/virtual_windows.odin
  28. 174 3
      core/odin/ast/ast.odin
  29. 117 83
      core/odin/ast/clone.odin
  30. 77 77
      core/odin/ast/walk.odin
  31. 90 90
      core/odin/parser/parser.odin
  32. 124 86
      core/odin/printer/visit.odin
  33. 71 0
      core/os/dir_openbsd.odin
  34. 17 6
      core/os/file_windows.odin
  35. 12 2
      core/os/os_darwin.odin
  36. 281 287
      core/os/os_freebsd.odin
  37. 17 23
      core/os/os_linux.odin
  38. 707 0
      core/os/os_openbsd.odin
  39. 2 0
      core/os/os_wasi.odin
  40. 2 0
      core/os/os_windows.odin
  41. 1 1
      core/os/stat_unix.odin
  42. 10 33
      core/os/stat_windows.odin
  43. 3 2
      core/path/filepath/path.odin
  44. 6 1
      core/path/filepath/path_unix.odin
  45. 13 1
      core/reflect/reflect.odin
  46. 4 4
      core/reflect/types.odin
  47. 5 1
      core/runtime/core.odin
  48. 1 1
      core/runtime/entry_unix.odin
  49. 96 5
      core/slice/slice.odin
  50. 1 0
      core/strconv/strconv.odin
  51. 19 0
      core/strings/strings.odin
  52. 1 1
      core/sync/channel_unix.odin
  53. 78 0
      core/sync/sync2/futex_openbsd.odin
  54. 9 0
      core/sync/sync2/primitives_openbsd.odin
  55. 1 1
      core/sync/sync2/primitives_pthreads.odin
  56. 36 0
      core/sync/sync_openbsd.odin
  57. 1 1
      core/sync/sync_unix.odin
  58. 10 20
      core/sys/darwin/xnu_system_call_helpers.odin
  59. 65 0
      core/sys/unix/pthread_openbsd.odin
  60. 1 1
      core/sys/unix/pthread_unix.odin
  61. 3 0
      core/sys/win32/user32.odin
  62. 3 0
      core/sys/windows/types.odin
  63. 1 1
      core/thread/thread_unix.odin
  64. 23 11
      core/time/time_unix.odin
  65. 7 14
      examples/all/all_vendor.odin
  66. 10 0
      examples/all/all_vendor_directx.odin
  67. 15 0
      examples/all/all_vendor_stl.odin
  68. 13 7
      examples/demo/demo.odin
  69. 3 1
      src/array.cpp
  70. 24 3
      src/bug_report.cpp
  71. 57 12
      src/build_settings.cpp
  72. 45 29
      src/check_builtin.cpp
  73. 4 0
      src/check_decl.cpp
  74. 70 5
      src/check_expr.cpp
  75. 18 0
      src/check_type.cpp
  76. 17 3
      src/checker.cpp
  77. 6 0
      src/checker_builtin_procs.hpp
  78. 2 2
      src/common.cpp
  79. 17 2
      src/common_memory.cpp
  80. 5 0
      src/entity.cpp
  81. 16 5
      src/exact_value.cpp
  82. 87 2
      src/gb/gb.h
  83. 5 0
      src/llvm_backend.cpp
  84. 1 1
      src/llvm_backend_debug.cpp
  85. 15 0
      src/llvm_backend_expr.cpp
  86. 74 10
      src/llvm_backend_proc.cpp
  87. 1 1
      src/llvm_backend_utility.cpp
  88. 54 46
      src/main.cpp
  89. 73 56
      src/parser.cpp
  90. 5 5
      src/parser.hpp
  91. 1 1
      src/threading.cpp
  92. 40 20
      src/types.cpp
  93. 6 2
      tests/core/Makefile
  94. 2 1
      tests/core/build.bat
  95. 4 3
      tests/core/compress/test_core_compress.odin
  96. 895 850
      tests/core/crypto/test_core_crypto.odin
  97. BIN
      tests/core/crypto_hash
  98. 21 19
      tests/core/encoding/json/test_core_json.odin
  99. 156 0
      tests/core/encoding/varint/test_core_varint.odin
  100. 5 3
      tests/core/hash/test_core_hash.odin

+ 1 - 0
.github/FUNDING.yml

@@ -1,3 +1,4 @@
 # These are supported funding model platforms
 
+github: odin-lang
 patreon: gingerbill

+ 15 - 0
.github/workflows/ci.yml

@@ -38,6 +38,12 @@ jobs:
           cd tests/vendor
           make
         timeout-minutes: 10
+      - name: Odin check examples/all for Linux i386
+        run: ./odin check examples/all -vet -strict-style -target:linux_i386
+        timeout-minutes: 10
+      - name: Odin check examples/all for OpenBSD amd64
+        run: ./odin check examples/all -vet -strict-style -target:openbsd_amd64
+        timeout-minutes: 10
   build_macOS:
     runs-on: macos-latest
     steps:
@@ -78,6 +84,9 @@ jobs:
           cd tests/vendor
           make
         timeout-minutes: 10
+      - name: Odin check examples/all for Darwin arm64
+        run: ./odin check examples/all -vet -strict-style -target:darwin_arm64
+        timeout-minutes: 10
   build_windows:
     runs-on: windows-2019
     steps:
@@ -138,3 +147,9 @@ jobs:
           cd tests\core\math\big
           call build.bat
         timeout-minutes: 10
+      - name: Odin check examples/all for Windows 32bits
+        shell: cmd
+        run: |
+          call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat
+          odin check examples/all -strict-style -target:windows_i386
+        timeout-minutes: 10

+ 2 - 0
.gitignore

@@ -279,3 +279,5 @@ shared/
 *.ll
 
 *.sublime-workspace
+examples/bug/
+build.sh

+ 14 - 10
Makefile

@@ -1,6 +1,6 @@
 GIT_SHA=$(shell git rev-parse --short HEAD)
 DISABLED_WARNINGS=-Wno-switch -Wno-macro-redefined -Wno-unused-value
-LDFLAGS=-pthread -ldl -lm -lstdc++
+LDFLAGS=-pthread -lm -lstdc++
 CFLAGS=-std=c++14 -DGIT_SHA=\"$(GIT_SHA)\"
 CFLAGS:=$(CFLAGS) -DODIN_VERSION_RAW=\"dev-$(shell date +"%Y-%m")\"
 CC=clang
@@ -8,9 +8,9 @@ CC=clang
 OS=$(shell uname)
 
 ifeq ($(OS), Darwin)
-    
+
     ARCH=$(shell uname -m)
-    LLVM_CONFIG=
+    LLVM_CONFIG=llvm-config
 
     # allow for arm only llvm's with version 13
     ifeq ($(ARCH), arm64)
@@ -27,9 +27,7 @@ ifeq ($(OS), Darwin)
     LLMV_VERSION_PATTERN_REMOVE_SINGLE_STR = $(subst ",,$(LLVM_VERSION_PATTERN_REMOVE_ELEMENTS))
     LLVM_VERSION_PATTERN = "^(($(LLMV_VERSION_PATTERN_REMOVE_SINGLE_STR)))"
 
-    ifneq ($(shell llvm-config --version | grep -E $(LLVM_VERSION_PATTERN)),)
-        LLVM_CONFIG=llvm-config
-    else
+    ifeq ($(shell $(LLVM_CONFIG) --version | grep -E $(LLVM_VERSION_PATTERN)),)
         ifeq ($(ARCH), arm64)
             $(error "Requirement: llvm-config must be base version 13 for arm64")
         else 
@@ -37,7 +35,7 @@ ifeq ($(OS), Darwin)
         endif 
     endif 
 
-    LDFLAGS:=$(LDFLAGS) -liconv
+    LDFLAGS:=$(LDFLAGS) -liconv -ldl
     CFLAGS:=$(CFLAGS) $(shell $(LLVM_CONFIG) --cxxflags --ldflags)
     LDFLAGS:=$(LDFLAGS) -lLLVM-C
 endif
@@ -48,13 +46,19 @@ ifeq ($(OS), Linux)
     else ifneq ($(shell which llvm-config-11-64 2>/dev/null),)
         LLVM_CONFIG=llvm-config-11-64
     else
-        ifneq ($(shell llvm-config --version | grep '^11\.'),)
-            LLVM_CONFIG=llvm-config
-        else
+        ifeq ($(shell $(LLVM_CONFIG) --version | grep '^11\.'),)
             $(error "Requirement: llvm-config must be version 11")
         endif
     endif
 
+    LDFLAGS:=$(LDFLAGS) -ldl
+    CFLAGS:=$(CFLAGS) $(shell $(LLVM_CONFIG) --cxxflags --ldflags)
+    LDFLAGS:=$(LDFLAGS) $(shell $(LLVM_CONFIG) --libs core native --system-libs)
+endif
+ifeq ($(OS), OpenBSD)
+    LLVM_CONFIG=/usr/local/bin/llvm-config
+
+    LDFLAGS:=$(LDFLAGS) -liconv
     CFLAGS:=$(CFLAGS) $(shell $(LLVM_CONFIG) --cxxflags --ldflags)
     LDFLAGS:=$(LDFLAGS) $(shell $(LLVM_CONFIG) --libs core native --system-libs)
 endif

+ 13 - 0
core/c/libc/errno.odin

@@ -27,6 +27,19 @@ when ODIN_OS == .Linux || ODIN_OS == .FreeBSD {
 	ERANGE :: 34
 }
 
+when ODIN_OS == .OpenBSD {
+	@(private="file")
+	@(default_calling_convention="c")
+	foreign libc {
+		@(link_name="__errno")
+		_get_errno :: proc() -> ^int ---
+	}
+
+	EDOM   :: 33
+	EILSEQ :: 84
+	ERANGE :: 34
+}
+
 when ODIN_OS == .Windows {
 	@(private="file")
 	@(default_calling_convention="c")

+ 25 - 0
core/c/libc/stdio.odin

@@ -78,6 +78,31 @@ when ODIN_OS == .Linux {
 	}
 }
 
+when ODIN_OS == .OpenBSD {
+	fpos_t :: i64
+
+	_IOFBF :: 0
+	_IOLBF :: 1
+	_IONBF :: 1
+
+	BUFSIZ :: 1024
+
+	EOF :: int(-1)
+
+	FOPEN_MAX	:: 20
+	FILENAME_MAX	:: 1024
+
+	SEEK_SET :: 0
+	SEEK_CUR :: 1
+	SEEK_END :: 2
+
+	foreign libc {
+		stderr: ^FILE
+		stdin:  ^FILE
+		stdout: ^FILE
+	}
+}
+
 when ODIN_OS == .Darwin {
 	fpos_t :: distinct i64
 	

+ 7 - 2
core/c/libc/time.odin

@@ -45,7 +45,7 @@ when ODIN_OS == .Windows {
 	}
 }
 
-when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin {
+when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD {
 	@(default_calling_convention="c")
 	foreign libc {
 		// 7.27.2 Time manipulation functions
@@ -63,7 +63,12 @@ when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin {
 		strftime     :: proc(s: [^]char, maxsize: size_t, format: cstring, timeptr: ^tm) -> size_t ---
 	}
 
-	CLOCKS_PER_SEC :: 1000000
+	when ODIN_OS == .OpenBSD {
+		CLOCKS_PER_SEC :: 100
+	} else {
+		CLOCKS_PER_SEC :: 1000000
+	}
+
 	TIME_UTC       :: 1
 
 	time_t         :: distinct i64

+ 5 - 0
core/c/libc/wctype.odin

@@ -25,6 +25,11 @@ when ODIN_OS == .Darwin {
 	wctype_t  :: distinct u32
 }
 
+when ODIN_OS == .OpenBSD {
+	wctrans_t :: distinct rawptr
+	wctype_t  :: distinct rawptr
+}
+
 @(default_calling_convention="c")
 foreign libc {
 	// 7.30.2.1 Wide character classification functions

+ 8 - 2
core/compress/common.odin

@@ -139,7 +139,13 @@ Context_Memory_Input :: struct #packed {
 	size_packed:       i64,
 	size_unpacked:     i64,
 }
-#assert(size_of(Context_Memory_Input) == 64)
+when size_of(rawptr) == 8 {
+	#assert(size_of(Context_Memory_Input) == 64)
+} else {
+	// e.g. `-target:windows_i386`
+	#assert(size_of(Context_Memory_Input) == 52)
+}
+
 
 Context_Stream_Input :: struct #packed {
 	input_data:        []u8,
@@ -473,4 +479,4 @@ discard_to_next_byte_lsb_from_stream :: proc(z: ^Context_Stream_Input) {
 	consume_bits_lsb(z, discard)
 }
 
-discard_to_next_byte_lsb :: proc{discard_to_next_byte_lsb_from_memory, discard_to_next_byte_lsb_from_stream};
+discard_to_next_byte_lsb :: proc{discard_to_next_byte_lsb_from_memory, discard_to_next_byte_lsb_from_stream}

+ 2 - 2
core/compress/gzip/gzip.odin

@@ -100,7 +100,7 @@ E_GZIP    :: compress.GZIP_Error
 E_ZLIB    :: compress.ZLIB_Error
 E_Deflate :: compress.Deflate_Error
 
-GZIP_MAX_PAYLOAD_SIZE :: int(max(u32le))
+GZIP_MAX_PAYLOAD_SIZE :: i64(max(u32le))
 
 load :: proc{load_from_slice, load_from_file, load_from_context}
 
@@ -136,7 +136,7 @@ load_from_context :: proc(z: ^$C, buf: ^bytes.Buffer, known_gzip_size := -1, exp
 
 	z.output = buf
 
-	if expected_output_size > GZIP_MAX_PAYLOAD_SIZE {
+	if i64(expected_output_size) > i64(GZIP_MAX_PAYLOAD_SIZE) {
 		return E_GZIP.Payload_Size_Exceeds_Max_Payload
 	}
 

+ 1 - 1
core/compress/zlib/zlib.odin

@@ -677,4 +677,4 @@ inflate_from_byte_array_raw :: proc(input: []u8, buf: ^bytes.Buffer, raw := fals
 	return inflate_raw(z=&ctx, expected_output_size=expected_output_size)
 }
 
-inflate :: proc{inflate_from_context, inflate_from_byte_array};
+inflate :: proc{inflate_from_context, inflate_from_byte_array}

+ 17 - 12
core/container/bit_array/bit_array.odin

@@ -1,6 +1,7 @@
 package dynamic_bit_array
 
 import "core:intrinsics"
+import "core:mem"
 
 /*
 	Note that these constants are dependent on the backing being a u64.
@@ -15,15 +16,16 @@ INDEX_MASK  :: 63
 NUM_BITS :: 64
 
 Bit_Array :: struct {
-	bits: [dynamic]u64,
-	bias: int,
-	max_index: int,
+	bits:         [dynamic]u64,
+	bias:         int,
+	max_index:    int,
+	free_pointer: bool,
 }
 
 Bit_Array_Iterator :: struct {
-	array: ^Bit_Array,
+	array:    ^Bit_Array,
 	word_idx: int,
-	bit_idx: uint,
+	bit_idx:  uint,
 }
 
 /*
@@ -186,7 +188,7 @@ set :: proc(ba: ^Bit_Array, #any_int index: uint, allocator := context.allocator
 /*
 	A helper function to create a Bit Array with optional bias, in case your smallest index is non-zero (including negative).
 */
-create :: proc(max_index: int, min_index := 0, allocator := context.allocator) -> (res: Bit_Array, ok: bool) #optional_ok {
+create :: proc(max_index: int, min_index := 0, allocator := context.allocator) -> (res: ^Bit_Array, ok: bool) #optional_ok {
 	context.allocator = allocator
 	size_in_bits := max_index - min_index
 
@@ -194,11 +196,11 @@ create :: proc(max_index: int, min_index := 0, allocator := context.allocator) -
 
 	legs := size_in_bits >> INDEX_SHIFT
 
-	res = Bit_Array{
-		bias = min_index,
-		max_index = max_index,
-	}
-	return res, resize_if_needed(&res, legs)
+	res = new(Bit_Array)
+	res.bias         = min_index
+	res.max_index    = max_index
+	res.free_pointer = true
+	return res, resize_if_needed(res, legs)
 }
 
 /*
@@ -206,7 +208,7 @@ create :: proc(max_index: int, min_index := 0, allocator := context.allocator) -
 */
 clear :: proc(ba: ^Bit_Array) {
 	if ba == nil { return }
-	ba.bits = {}
+	mem.zero_slice(ba.bits[:])
 }
 
 /*
@@ -215,6 +217,9 @@ clear :: proc(ba: ^Bit_Array) {
 destroy :: proc(ba: ^Bit_Array) {
 	if ba == nil { return }
 	delete(ba.bits)
+	if ba.free_pointer { // Only free if this Bit_Array was created using `create`, not when on the stack.
+		free(ba)
+	}
 }
 
 /*

+ 7 - 6
core/container/bit_array/doc.odin

@@ -21,6 +21,7 @@ package dynamic_bit_array
 			// returns `false`, `false`, because this Bit Array wasn't created to allow negative indices.
 			was_set, was_retrieved := get(&bits, -1)
 			fmt.println(was_set, was_retrieved) 
+			destroy(&bits)
 		}
 
 	-- A Bit Array can optionally allow for negative indices, if the mininum value was given during creation:
@@ -40,13 +41,13 @@ package dynamic_bit_array
 			using bit_array
 
 			bits := create(int(max(Foo)), int(min(Foo)))
-			defer destroy(&bits)
+			defer destroy(bits)
 
-			fmt.printf("Set(Bar):           %v\n",     set(&bits, Foo.Bar))
-			fmt.printf("Get(Bar):           %v, %v\n", get(&bits, Foo.Bar))
-			fmt.printf("Set(Negative_Test): %v\n",     set(&bits, Foo.Negative_Test))
-			fmt.printf("Get(Leaves):        %v, %v\n", get(&bits, Foo.Leaves))
-			fmt.printf("Get(Negative_Test): %v, %v\n", get(&bits, Foo.Negative_Test))
+			fmt.printf("Set(Bar):           %v\n",     set(bits, Foo.Bar))
+			fmt.printf("Get(Bar):           %v, %v\n", get(bits, Foo.Bar))
+			fmt.printf("Set(Negative_Test): %v\n",     set(bits, Foo.Negative_Test))
+			fmt.printf("Get(Leaves):        %v, %v\n", get(bits, Foo.Leaves))
+			fmt.printf("Get(Negative_Test): %v, %v\n", get(bits, Foo.Negative_Test))
 			fmt.printf("Freed.\n")
 		}
 */

+ 1 - 1
core/crypto/rand_generic.odin

@@ -1,6 +1,6 @@
 package crypto
 
-when ODIN_OS != .Linux {
+when ODIN_OS != .Linux && ODIN_OS != .OpenBSD && ODIN_OS != .Windows {
 	_rand_bytes :: proc (dst: []byte) {
 		unimplemented("crypto: rand_bytes not supported on this OS")
 	}

+ 12 - 0
core/crypto/rand_openbsd.odin

@@ -0,0 +1,12 @@
+package crypto
+
+import "core:c"
+
+foreign import libc "system:c"
+foreign libc {
+	arc4random_buf :: proc "c" (buf: rawptr, nbytes: c.size_t) ---
+}
+
+_rand_bytes :: proc (dst: []byte) {
+	arc4random_buf(raw_data(dst), len(dst))
+}

+ 23 - 0
core/crypto/rand_windows.odin

@@ -0,0 +1,23 @@
+package crypto
+
+import win32 "core:sys/windows"
+import "core:os"
+import "core:fmt"
+
+_rand_bytes :: proc(dst: []byte) {
+	ret := (os.Errno)(win32.BCryptGenRandom(nil, raw_data(dst), u32(len(dst)), win32.BCRYPT_USE_SYSTEM_PREFERRED_RNG))
+	if ret != os.ERROR_NONE {
+		switch ret {
+			case os.ERROR_INVALID_HANDLE:
+				// The handle to the first parameter is invalid.
+				// This should not happen here, since we explicitly pass nil to it
+				panic("crypto: BCryptGenRandom Invalid handle for hAlgorithm")
+			case os.ERROR_INVALID_PARAMETER:
+				// One of the parameters was invalid
+				panic("crypto: BCryptGenRandom Invalid parameter")
+			case:
+				// Unknown error
+				panic(fmt.tprintf("crypto: BCryptGenRandom failed: %d\n", ret))
+		}
+	}
+}

+ 335 - 0
core/crypto/siphash/siphash.odin

@@ -0,0 +1,335 @@
+package siphash
+
+/*
+    Copyright 2022 zhibog
+    Made available under the BSD-3 license.
+
+    List of contributors:
+        zhibog:  Initial implementation.
+
+    Implementation of the SipHash hashing algorithm, as defined at <https://github.com/veorq/SipHash> and <https://www.aumasson.jp/siphash/siphash.pdf>
+
+    Use the specific procedures for a certain setup. The generic procdedures will default to Siphash 2-4
+*/
+
+import "core:crypto"
+import "core:crypto/util"
+
+/*
+    High level API
+*/
+
+KEY_SIZE    :: 16
+DIGEST_SIZE :: 8
+
+// sum_string_1_3 will hash the given message with the key and return
+// the computed hash as a u64
+sum_string_1_3 :: proc(msg, key: string) -> u64 {
+    return sum_bytes_1_3(transmute([]byte)(msg), transmute([]byte)(key))
+}
+
+// sum_bytes_1_3 will hash the given message with the key and return
+// the computed hash as a u64
+sum_bytes_1_3 :: proc (msg, key: []byte) -> u64 {
+    ctx: Context
+    hash: u64
+    init(&ctx, key, 1, 3)
+    update(&ctx, msg)
+    final(&ctx, &hash)
+    return hash
+}
+
+// sum_string_to_buffer_1_3 will hash the given message with the key and write
+// the computed hash into the provided destination buffer
+sum_string_to_buffer_1_3 :: proc(msg, key: string, dst: []byte) {
+    sum_bytes_to_buffer_1_3(transmute([]byte)(msg), transmute([]byte)(key), dst)
+}
+
+// sum_bytes_to_buffer_1_3 will hash the given message with the key and write
+// the computed hash into the provided destination buffer
+sum_bytes_to_buffer_1_3 :: proc(msg, key, dst: []byte) {
+    assert(len(dst) >= DIGEST_SIZE, "crypto/siphash: Destination buffer needs to be at least of size 8")
+    hash  := sum_bytes_1_3(msg, key)
+    _collect_output(dst[:], hash)
+}
+
+sum_1_3 :: proc {
+    sum_string_1_3,
+    sum_bytes_1_3,
+    sum_string_to_buffer_1_3,
+    sum_bytes_to_buffer_1_3,
+}
+
+// verify_u64_1_3 will check if the supplied tag matches with the output you 
+// will get from the provided message and key
+verify_u64_1_3 :: proc (tag: u64 msg, key: []byte) -> bool {
+    return sum_bytes_1_3(msg, key) == tag
+}
+
+// verify_bytes will check if the supplied tag matches with the output you 
+// will get from the provided message and key
+verify_bytes_1_3 :: proc (tag, msg, key: []byte) -> bool {
+    derived_tag: [8]byte
+    sum_bytes_to_buffer_1_3(msg, key, derived_tag[:])
+    return crypto.compare_constant_time(derived_tag[:], tag) == 1
+}
+
+verify_1_3 :: proc {
+    verify_bytes_1_3,
+    verify_u64_1_3,
+}
+
+// sum_string_2_4 will hash the given message with the key and return
+// the computed hash as a u64
+sum_string_2_4 :: proc(msg, key: string) -> u64 {
+    return sum_bytes_2_4(transmute([]byte)(msg), transmute([]byte)(key))
+}
+
+// sum_bytes_2_4 will hash the given message with the key and return
+// the computed hash as a u64
+sum_bytes_2_4 :: proc (msg, key: []byte) -> u64 {
+    ctx: Context
+    hash: u64
+    init(&ctx, key, 2, 4)
+    update(&ctx, msg)
+    final(&ctx, &hash)
+    return hash
+}
+
+// sum_string_to_buffer_2_4 will hash the given message with the key and write
+// the computed hash into the provided destination buffer
+sum_string_to_buffer_2_4 :: proc(msg, key: string, dst: []byte) {
+    sum_bytes_to_buffer_2_4(transmute([]byte)(msg), transmute([]byte)(key), dst)
+}
+
+// sum_bytes_to_buffer_2_4 will hash the given message with the key and write
+// the computed hash into the provided destination buffer
+sum_bytes_to_buffer_2_4 :: proc(msg, key, dst: []byte) {
+    assert(len(dst) >= DIGEST_SIZE, "crypto/siphash: Destination buffer needs to be at least of size 8")
+    hash  := sum_bytes_2_4(msg, key)
+    _collect_output(dst[:], hash)
+}
+
+sum_2_4 :: proc {
+    sum_string_2_4,
+    sum_bytes_2_4,
+    sum_string_to_buffer_2_4,
+    sum_bytes_to_buffer_2_4,
+}
+
+sum_string           :: sum_string_2_4
+sum_bytes            :: sum_bytes_2_4
+sum_string_to_buffer :: sum_string_to_buffer_2_4
+sum_bytes_to_buffer  :: sum_bytes_to_buffer_2_4
+sum :: proc {
+    sum_string,
+    sum_bytes,
+    sum_string_to_buffer,
+    sum_bytes_to_buffer,
+}
+
+// verify_u64_2_4 will check if the supplied tag matches with the output you 
+// will get from the provided message and key
+verify_u64_2_4 :: proc (tag: u64 msg, key: []byte) -> bool {
+    return sum_bytes_2_4(msg, key) == tag
+}
+
+// verify_bytes will check if the supplied tag matches with the output you 
+// will get from the provided message and key
+verify_bytes_2_4 :: proc (tag, msg, key: []byte) -> bool {
+    derived_tag: [8]byte
+    sum_bytes_to_buffer_2_4(msg, key, derived_tag[:])
+    return crypto.compare_constant_time(derived_tag[:], tag) == 1
+}
+
+verify_2_4 :: proc {
+    verify_bytes_2_4,
+    verify_u64_2_4,
+}
+
+verify_bytes :: verify_bytes_2_4
+verify_u64   :: verify_u64_2_4
+verify :: proc {
+    verify_bytes,
+    verify_u64,
+}
+
+// sum_string_4_8 will hash the given message with the key and return
+// the computed hash as a u64
+sum_string_4_8 :: proc(msg, key: string) -> u64 {
+    return sum_bytes_4_8(transmute([]byte)(msg), transmute([]byte)(key))
+}
+
+// sum_bytes_4_8 will hash the given message with the key and return
+// the computed hash as a u64
+sum_bytes_4_8 :: proc (msg, key: []byte) -> u64 {
+    ctx: Context
+    hash: u64
+    init(&ctx, key, 4, 8)
+    update(&ctx, msg)
+    final(&ctx, &hash)
+    return hash
+}
+
+// sum_string_to_buffer_4_8 will hash the given message with the key and write
+// the computed hash into the provided destination buffer
+sum_string_to_buffer_4_8 :: proc(msg, key: string, dst: []byte) {
+    sum_bytes_to_buffer_4_8(transmute([]byte)(msg), transmute([]byte)(key), dst)
+}
+
+// sum_bytes_to_buffer_4_8 will hash the given message with the key and write
+// the computed hash into the provided destination buffer
+sum_bytes_to_buffer_4_8 :: proc(msg, key, dst: []byte) {
+    assert(len(dst) >= DIGEST_SIZE, "crypto/siphash: Destination buffer needs to be at least of size 8")
+    hash  := sum_bytes_4_8(msg, key)
+    _collect_output(dst[:], hash)
+}
+
+sum_4_8 :: proc {
+    sum_string_4_8,
+    sum_bytes_4_8,
+    sum_string_to_buffer_4_8,
+    sum_bytes_to_buffer_4_8,
+}
+
+// verify_u64_4_8 will check if the supplied tag matches with the output you 
+// will get from the provided message and key
+verify_u64_4_8 :: proc (tag: u64 msg, key: []byte) -> bool {
+    return sum_bytes_4_8(msg, key) == tag
+}
+
+// verify_bytes will check if the supplied tag matches with the output you 
+// will get from the provided message and key
+verify_bytes_4_8 :: proc (tag, msg, key: []byte) -> bool {
+    derived_tag: [8]byte
+    sum_bytes_to_buffer_4_8(msg, key, derived_tag[:])
+    return crypto.compare_constant_time(derived_tag[:], tag) == 1
+}
+
+verify_4_8 :: proc {
+    verify_bytes_4_8,
+    verify_u64_4_8,
+}
+
+/*
+    Low level API
+*/
+
+init :: proc(ctx: ^Context, key: []byte, c_rounds, d_rounds: int) {
+    assert(len(key) == KEY_SIZE, "crypto/siphash: Invalid key size, want 16")
+    ctx.c_rounds = c_rounds
+    ctx.d_rounds = d_rounds
+    is_valid_setting := (ctx.c_rounds == 1 && ctx.d_rounds == 3) ||
+                        (ctx.c_rounds == 2 && ctx.d_rounds == 4) ||
+                        (ctx.c_rounds == 4 && ctx.d_rounds == 8)
+    assert(is_valid_setting, "crypto/siphash: Incorrect rounds set up. Valid pairs are (1,3), (2,4) and (4,8)")
+    ctx.k0 = util.U64_LE(key[:8])
+    ctx.k1 = util.U64_LE(key[8:])
+    ctx.v0 = 0x736f6d6570736575 ~ ctx.k0
+    ctx.v1 = 0x646f72616e646f6d ~ ctx.k1
+    ctx.v2 = 0x6c7967656e657261 ~ ctx.k0
+    ctx.v3 = 0x7465646279746573 ~ ctx.k1
+    ctx.is_initialized = true
+}
+
+update :: proc(ctx: ^Context, data: []byte) {
+    assert(ctx.is_initialized, "crypto/siphash: Context is not initalized")
+    ctx.last_block = len(data) / 8 * 8
+    ctx.buf = data
+    i := 0
+    m: u64
+    for i < ctx.last_block {
+        m = u64(ctx.buf[i] & 0xff)
+        i += 1
+
+        for r in u64(1)..<8 {
+            m |= u64(ctx.buf[i] & 0xff) << (r * 8)
+            i += 1
+        }
+
+        ctx.v3 ~= m
+        for _ in 0..<ctx.c_rounds {
+            _compress(ctx)
+        }
+
+        ctx.v0 ~= m
+    }
+}
+
+final :: proc(ctx: ^Context, dst: ^u64) {
+    m: u64
+    for i := len(ctx.buf) - 1; i >= ctx.last_block; i -= 1 {
+        m <<= 8
+        m |= u64(ctx.buf[i] & 0xff)
+    }
+    m |= u64(len(ctx.buf) << 56)
+
+    ctx.v3 ~= m
+
+    for _ in 0..<ctx.c_rounds {
+        _compress(ctx)
+    }
+
+    ctx.v0 ~= m
+    ctx.v2 ~= 0xff
+
+    for _ in 0..<ctx.d_rounds {
+        _compress(ctx)
+    }
+
+    dst^ = ctx.v0 ~ ctx.v1 ~ ctx.v2 ~ ctx.v3
+
+    reset(ctx)
+}
+
+reset :: proc(ctx: ^Context) {
+    ctx.k0, ctx.k1 = 0, 0
+    ctx.v0, ctx.v1 = 0, 0
+    ctx.v2, ctx.v3 = 0, 0
+    ctx.last_block = 0
+    ctx.c_rounds = 0
+    ctx.d_rounds = 0
+    ctx.is_initialized = false
+}
+
+Context :: struct {
+    v0, v1, v2, v3: u64,    // State values
+    k0, k1:         u64,    // Split key
+    c_rounds:       int,    // Number of message rounds
+    d_rounds:       int,    // Number of finalization rounds
+    buf:            []byte, // Provided data
+    last_block:     int,    // Offset from the last block
+    is_initialized: bool,
+}
+
+_get_byte :: #force_inline proc "contextless" (byte_num: byte, into: u64) -> byte {
+    return byte(into >> (((~byte_num) & (size_of(u64) - 1)) << 3))
+}
+
+_collect_output :: #force_inline proc "contextless" (dst: []byte, hash: u64) {
+    dst[0] = _get_byte(7, hash)
+    dst[1] = _get_byte(6, hash)
+    dst[2] = _get_byte(5, hash)
+    dst[3] = _get_byte(4, hash)
+    dst[4] = _get_byte(3, hash)
+    dst[5] = _get_byte(2, hash)
+    dst[6] = _get_byte(1, hash)
+    dst[7] = _get_byte(0, hash)
+}
+
+_compress :: #force_inline proc "contextless" (ctx: ^Context) {
+    ctx.v0 += ctx.v1
+    ctx.v1  = util.ROTL64(ctx.v1, 13)
+    ctx.v1 ~= ctx.v0
+    ctx.v0  = util.ROTL64(ctx.v0, 32)
+    ctx.v2 += ctx.v3
+    ctx.v3  = util.ROTL64(ctx.v3, 16)
+    ctx.v3 ~= ctx.v2
+    ctx.v0 += ctx.v3
+    ctx.v3  = util.ROTL64(ctx.v3, 21)
+    ctx.v3 ~= ctx.v0
+    ctx.v2 += ctx.v1
+    ctx.v1  = util.ROTL64(ctx.v1, 17)
+    ctx.v1 ~= ctx.v2
+    ctx.v2  = util.ROTL64(ctx.v2, 32)
+}

+ 1 - 1
core/dynlib/lib_unix.odin

@@ -1,4 +1,4 @@
-// +build linux, darwin, freebsd
+// +build linux, darwin, freebsd, openbsd
 package dynlib
 
 import "core:os"

+ 27 - 0
core/encoding/varint/doc.odin

@@ -0,0 +1,27 @@
+/*
+	Implementation of the LEB128 variable integer encoding as used by DWARF encoding and DEX files, among others.
+
+	Author of this Odin package: Jeroen van Rijn
+
+	Example:
+		```odin
+		import "core:encoding/varint"
+		import "core:fmt"
+
+		main :: proc() {
+			buf: [varint.LEB128_MAX_BYTES]u8
+
+			value := u128(42)
+
+			encode_size, encode_err := varint.encode_uleb128(buf[:], value)
+			assert(encode_size == 1 && encode_err == .None)
+
+			fmt.println(buf[:encode_size])
+
+			decoded_val, decode_size, decode_err := varint.decode_uleb128(buf[:encode_size])
+			assert(decoded_val == value && decode_size == encode_size && decode_err == .None)
+		}
+		```
+
+*/
+package varint

+ 139 - 0
core/encoding/varint/leb128.odin

@@ -0,0 +1,139 @@
+/*
+	Copyright 2022 Jeroen van Rijn <[email protected]>.
+	Made available under Odin's BSD-3 license.
+
+	List of contributors:
+		Jeroen van Rijn: Initial implementation.
+*/
+
+// package varint implements variable length integer encoding and decoding using
+// the LEB128 format as used by DWARF debug info, Android .dex and other file formats.
+package varint
+
+import "core:fmt"
+
+// In theory we should use the bigint package. In practice, varints bigger than this indicate a corrupted file.
+// Instead we'll set limits on the values we'll encode/decode
+// 18 * 7 bits = 126, which means that a possible 19th byte may at most be `0b0000_0011`.
+LEB128_MAX_BYTES    :: 19
+
+Error :: enum {
+	None             = 0,
+	Buffer_Too_Small = 1,
+	Value_Too_Large  = 2,
+}
+
+// Decode a slice of bytes encoding an unsigned LEB128 integer into value and number of bytes used.
+// Returns `size` == 0 for an invalid value, empty slice, or a varint > 18 bytes.
+decode_uleb128 :: proc(buf: []u8) -> (val: u128, size: int, err: Error) {
+	more := true
+
+	for v, i in buf {
+		size = i + 1
+
+		// 18 * 7 bits = 126, which means that a possible 19th byte may at most be 0b0000_0011.
+		if size > LEB128_MAX_BYTES || size == LEB128_MAX_BYTES && v > 0b0000_0011 {
+			return 0, 0, .Value_Too_Large
+		}
+
+		val |= u128(v & 0x7f) << uint(i * 7)
+
+		if v < 128 {
+			more = false
+			break
+		}
+	}
+
+	// If the buffer runs out before the number ends, return an error.
+	if more {
+		return 0, 0, .Buffer_Too_Small
+	}
+	return
+}
+
+// Decode a slice of bytes encoding a signed LEB128 integer into value and number of bytes used.
+// Returns `size` == 0 for an invalid value, empty slice, or a varint > 18 bytes.
+decode_ileb128 :: proc(buf: []u8) -> (val: i128, size: int, err: Error) {
+	shift: uint
+
+	if len(buf) == 0 {
+		return 0, 0, .Buffer_Too_Small
+	}
+
+	for v in buf {
+		size += 1
+
+		// 18 * 7 bits = 126, which including sign means we can have a 19th byte.
+		if size > LEB128_MAX_BYTES || size == LEB128_MAX_BYTES && v > 0x7f {
+			return 0, 0, .Value_Too_Large
+		}
+
+		val |= i128(v & 0x7f) << shift
+		shift += 7
+
+		if v < 128 { break }
+	}
+
+	if buf[size - 1] & 0x40 == 0x40 {
+		val |= max(i128) << shift
+	}
+	return
+}
+
+// Encode `val` into `buf` as an unsigned LEB128 encoded series of bytes.
+// `buf` must be appropriately sized.
+encode_uleb128 :: proc(buf: []u8, val: u128) -> (size: int, err: Error) {
+	val := val
+
+	for {
+		size += 1
+
+		if size > len(buf) {
+			fmt.println(val, buf[:size - 1])
+			return 0, .Buffer_Too_Small
+		}
+
+		low := val & 0x7f
+		val >>= 7
+
+		if val > 0 {
+			low |= 0x80 // more bytes to follow
+		}
+		buf[size - 1] = u8(low)
+
+		if val == 0 { break }
+	}
+	return
+}
+
+@(private)
+SIGN_MASK :: (i128(1) << 121) // sign extend mask
+
+// Encode `val` into `buf` as a signed LEB128 encoded series of bytes.
+// `buf` must be appropriately sized.
+encode_ileb128 :: proc(buf: []u8, val: i128) -> (size: int, err: Error) {
+	val      := val
+	more     := true
+
+	for more {
+		size += 1
+
+		if size > len(buf) {
+			return 0, .Buffer_Too_Small
+		}
+
+		low := val & 0x7f
+		val >>= 7
+
+		low = (low ~ SIGN_MASK) - SIGN_MASK
+
+		if (val == 0 && low & 0x40 != 0x40) || (val == -1 && low & 0x40 == 0x40) {
+			more = false
+		} else {
+			low |= 0x80
+		}
+
+		buf[size - 1] = u8(low)
+	}
+	return
+}

File diff suppressed because it is too large
+ 186 - 178
core/fmt/fmt.odin


+ 2 - 0
core/intrinsics/intrinsics.odin

@@ -41,6 +41,8 @@ mem_copy_non_overlapping :: proc(dst, src: rawptr, len: int) ---
 mem_zero                 :: proc(ptr: rawptr, len: int) ---
 mem_zero_volatile        :: proc(ptr: rawptr, len: int) ---
 
+unaligned_load           :: proc(src: ^$T) -> T ---
+unaligned_store          :: proc(dst: ^$T, val: T) -> T ---
 
 fixed_point_mul     :: proc(lhs, rhs: $T, #const scale: uint) -> T where type_is_integer(T) ---
 fixed_point_div     :: proc(lhs, rhs: $T, #const scale: uint) -> T where type_is_integer(T) ---

+ 13 - 13
core/mem/doc.odin

@@ -12,22 +12,22 @@ import "core:mem"
 import "core:fmt"
 
 _main :: proc() {
-   do stuff
+	do stuff
 }
 
 main :: proc() {
-    track: mem.Tracking_Allocator
-    mem.tracking_allocator_init(&track, context.allocator)
-    context.allocator = mem.tracking_allocator(&track)
-
-    _main()
-
-    for _, v in track.allocation_map {
-        fmt.printf("%v leaked %v bytes", v.location, v.size)
-    }
-    for bf in track.bad_free_array {
-        fmt.printf("%v allocation %p was freed badly", bf.location, bf.memory)
-    }
+	track: mem.Tracking_Allocator
+	mem.tracking_allocator_init(&track, context.allocator)
+	context.allocator = mem.tracking_allocator(&track)
+
+	_main()
+
+	for _, leak in track.allocation_map {
+		fmt.printf("%v leaked %v bytes\n", leak.location, leak.size)
+	}
+	for bad_free in track.bad_free_array {
+		fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory)
+	}
 }
 ```
 */

+ 35 - 19
core/mem/virtual/virtual.odin

@@ -6,25 +6,25 @@ DEFAULT_PAGE_SIZE := uint(4096)
 
 Allocator_Error :: mem.Allocator_Error
 
-reserve :: proc(size: uint) -> (data: []byte, err: Allocator_Error) {
+reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
 	return _reserve(size)
 }
 
-commit :: proc(data: rawptr, size: uint) -> Allocator_Error {
+commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
 	return _commit(data, size)
 }
 
-reserve_and_commit :: proc(size: uint) -> (data: []byte, err: Allocator_Error) {
+reserve_and_commit :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
 	data = reserve(size) or_return
 	commit(raw_data(data), size) or_return
 	return
 }
 
-decommit :: proc(data: rawptr, size: uint) {
+decommit :: proc "contextless" (data: rawptr, size: uint) {
 	_decommit(data, size)
 }
 
-release :: proc(data: rawptr, size: uint) {
+release :: proc "contextless" (data: rawptr, size: uint) {
 	_release(data, size)
 }
 
@@ -36,7 +36,7 @@ Protect_Flag :: enum u32 {
 Protect_Flags :: distinct bit_set[Protect_Flag; u32]
 Protect_No_Access :: Protect_Flags{}
 
-protect :: proc(data: rawptr, size: uint, flags: Protect_Flags) -> bool {
+protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool {
 	return _protect(data, size, flags)
 }
 
@@ -82,11 +82,13 @@ memory_block_alloc :: proc(committed, reserved: uint, flags: Memory_Block_Flags)
 	pmblock := platform_memory_alloc(0, total_size) or_return
 	
 	pmblock.block.base = ([^]byte)(uintptr(pmblock) + base_offset)
-	commit(pmblock.block.base, committed) or_return
+	commit_err := platform_memory_commit(pmblock, uint(base_offset) + committed)
+	assert(commit_err == nil)
+
 	// Should be zeroed
 	assert(pmblock.block.used == 0)
 	assert(pmblock.block.prev == nil)	
-	if (do_protection) {
+	if do_protection {
 		protect(rawptr(uintptr(pmblock) + protect_offset), page_size, Protect_No_Access)
 	}
 	
@@ -105,7 +107,7 @@ memory_block_alloc :: proc(committed, reserved: uint, flags: Memory_Block_Flags)
 }
 
 alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: int) -> (data: []byte, err: Allocator_Error) {
-	calc_alignment_offset :: proc(block: ^Memory_Block, alignment: uintptr) -> uint {
+	calc_alignment_offset :: proc "contextless" (block: ^Memory_Block, alignment: uintptr) -> uint {
 		alignment_offset := uint(0)
 		ptr := uintptr(block.base[block.used:])
 		mask := alignment-1
@@ -115,23 +117,37 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: int)
 		return alignment_offset
 		
 	}
-	
+	do_commit_if_necessary :: proc(block: ^Memory_Block, size: uint) -> (err: Allocator_Error) {
+		if block.committed - block.used < size {
+			pmblock := (^Platform_Memory_Block)(block)
+			base_offset := uint(uintptr(block) - uintptr(pmblock))
+			platform_total_commit := base_offset + block.used + size
+
+			assert(pmblock.committed <= pmblock.reserved)
+			assert(pmblock.committed < platform_total_commit)
+
+			platform_memory_commit(pmblock, platform_total_commit) or_return
+
+			pmblock.committed = platform_total_commit
+			block.committed = pmblock.committed - base_offset
+		}
+		return nil
+	}
+
+
 	alignment_offset := calc_alignment_offset(block, uintptr(alignment))
-	
 	size := uint(min_size) + alignment_offset
-	
+
 	if block.used + size > block.reserved {
 		err = .Out_Of_Memory
 		return
 	}
-	
-	ptr := block.base[block.used:]
-	ptr = ptr[alignment_offset:]
-	
+	assert(block.committed <= block.reserved)
+	do_commit_if_necessary(block, size) or_return
+
+	data = block.base[block.used+alignment_offset:][:min_size]
 	block.used += size
-	assert(block.used <= block.reserved)
-	
-	return ptr[:min_size], nil	
+	return
 }
 
 

+ 5 - 5
core/mem/virtual/virtual_linux.odin

@@ -58,7 +58,7 @@ madvise :: proc "contextless" (addr: rawptr, length: uint, advice: c.int) -> c.i
 }
 
 
-_reserve :: proc(size: uint) -> (data: []byte, err: Allocator_Error) {
+_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
 	MAP_FAILED := rawptr(~uintptr(0))
 	result := mmap(nil, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)
 	if result == MAP_FAILED {
@@ -67,7 +67,7 @@ _reserve :: proc(size: uint) -> (data: []byte, err: Allocator_Error) {
 	return ([^]byte)(result)[:size], nil
 }
 
-_commit :: proc(data: rawptr, size: uint) -> Allocator_Error {
+_commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
 	result := mprotect(data, size, PROT_READ|PROT_WRITE)
 	if result != 0 {
 		// TODO(bill): Handle error value correctly
@@ -75,14 +75,14 @@ _commit :: proc(data: rawptr, size: uint) -> Allocator_Error {
 	}
 	return nil
 }
-_decommit :: proc(data: rawptr, size: uint) {
+_decommit :: proc "contextless" (data: rawptr, size: uint) {
 	mprotect(data, size, PROT_NONE)
 	madvise(data, size, MADV_FREE)
 }
-_release :: proc(data: rawptr, size: uint) {
+_release :: proc "contextless" (data: rawptr, size: uint) {
 	munmap(data, size)
 }
-_protect :: proc(data: rawptr, size: uint, flags: Protect_Flags) -> bool {
+_protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool {
 	pflags: c.int
 	pflags = PROT_NONE
 	if .Read    in flags { pflags |= PROT_READ  }

+ 21 - 5
core/mem/virtual/virtual_platform.odin

@@ -4,12 +4,13 @@ package mem_virtual
 import sync "core:sync/sync2"
 
 Platform_Memory_Block :: struct {
-	block:    Memory_Block,
-	reserved: uint,
+	block:      Memory_Block,
+	committed:  uint,
+	reserved:   uint,
 	prev, next: ^Platform_Memory_Block,
 } 
 
-platform_memory_alloc :: proc(to_commit, to_reserve: uint) -> (block: ^Platform_Memory_Block, err: Allocator_Error) {
+platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint) -> (block: ^Platform_Memory_Block, err: Allocator_Error) {
 	to_commit, to_reserve := to_commit, to_reserve
 	to_reserve = max(to_commit, to_reserve)
 	
@@ -20,12 +21,13 @@ platform_memory_alloc :: proc(to_commit, to_reserve: uint) -> (block: ^Platform_
 	commit(raw_data(data), to_commit)
 	
 	block = (^Platform_Memory_Block)(raw_data(data))
-	block.reserved = to_reserve
+	block.committed = to_commit
+	block.reserved  = to_reserve
 	return
 }
 
 
-platform_memory_free :: proc(block: ^Platform_Memory_Block) {
+platform_memory_free :: proc "contextless" (block: ^Platform_Memory_Block) {
 	if block != nil {
 		release(block, block.reserved)
 	}
@@ -52,3 +54,17 @@ platform_memory_init :: proc() {
 		global_platform_memory_block_sentinel_set = true
 	}
 }
+
+platform_memory_commit :: proc "contextless" (block: ^Platform_Memory_Block, to_commit: uint) -> (err: Allocator_Error) {
+	if to_commit < block.committed {
+		return nil
+	}
+	if to_commit > block.reserved {
+		return .Out_Of_Memory
+	}
+
+
+	commit(block, to_commit) or_return
+	block.committed = to_commit
+	return nil
+}

+ 5 - 5
core/mem/virtual/virtual_windows.odin

@@ -62,7 +62,7 @@ foreign Kernel32 {
 }
 
 
-_reserve :: proc(size: uint) -> (data: []byte, err: Allocator_Error) {
+_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
 	result := VirtualAlloc(nil, size, MEM_RESERVE, PAGE_READWRITE)
 	if result == nil {
 		err = .Out_Of_Memory
@@ -72,7 +72,7 @@ _reserve :: proc(size: uint) -> (data: []byte, err: Allocator_Error) {
 	return
 }
 
-_commit :: proc(data: rawptr, size: uint) -> Allocator_Error {
+_commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
 	result := VirtualAlloc(data, size, MEM_COMMIT, PAGE_READWRITE)
 	if result == nil {
 		switch err := GetLastError(); err {
@@ -85,13 +85,13 @@ _commit :: proc(data: rawptr, size: uint) -> Allocator_Error {
 	}
 	return nil
 }
-_decommit :: proc(data: rawptr, size: uint) {
+_decommit :: proc "contextless" (data: rawptr, size: uint) {
 	VirtualFree(data, size, MEM_DECOMMIT)
 }
-_release :: proc(data: rawptr, size: uint) {
+_release :: proc "contextless" (data: rawptr, size: uint) {
 	VirtualFree(data, 0, MEM_RELEASE)
 }
-_protect :: proc(data: rawptr, size: uint, flags: Protect_Flags) -> bool {
+_protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool {
 	pflags: u32
 	pflags = PAGE_NOACCESS
 	switch flags {

+ 174 - 3
core/odin/ast/ast.odin

@@ -34,7 +34,7 @@ Node :: struct {
 	pos:         tokenizer.Pos,
 	end:         tokenizer.Pos,
 	state_flags: Node_State_Flags,
-	derived:     any,
+	derived:     Any_Node,
 }
 
 Comment_Group :: struct {
@@ -88,9 +88,11 @@ File :: struct {
 
 Expr :: struct {
 	using expr_base: Node,
+	derived_expr: Any_Expr,
 }
 Stmt :: struct {
 	using stmt_base: Node,
+	derived_stmt: Any_Stmt,
 }
 Decl :: struct {
 	using decl_base: Stmt,
@@ -541,7 +543,7 @@ unparen_expr :: proc(expr: ^Expr) -> (val: ^Expr) {
 		return
 	}
 	for {
-		e, ok := val.derived.(Paren_Expr)
+		e, ok := val.derived.(^Paren_Expr)
 		if !ok || e.expr == nil {
 			break
 		}
@@ -758,4 +760,173 @@ Matrix_Type :: struct {
 	row_count:    ^Expr,
 	column_count: ^Expr,
 	elem:         ^Expr,
-}
+}
+
+
+Any_Node :: union {
+	^Package,
+	^File,
+	^Comment_Group,
+
+	^Bad_Expr,
+	^Ident,
+	^Implicit,
+	^Undef,
+	^Basic_Lit,
+	^Basic_Directive,
+	^Ellipsis,
+	^Proc_Lit,
+	^Comp_Lit,
+	^Tag_Expr,
+	^Unary_Expr,
+	^Binary_Expr,
+	^Paren_Expr,
+	^Selector_Expr,
+	^Implicit_Selector_Expr,
+	^Selector_Call_Expr,
+	^Index_Expr,
+	^Deref_Expr,
+	^Slice_Expr,
+	^Matrix_Index_Expr,
+	^Call_Expr,
+	^Field_Value,
+	^Ternary_If_Expr,
+	^Ternary_When_Expr,
+	^Or_Else_Expr,
+	^Or_Return_Expr,
+	^Type_Assertion,
+	^Type_Cast,
+	^Auto_Cast,
+	^Inline_Asm_Expr,
+
+	^Proc_Group,
+
+	^Typeid_Type,
+	^Helper_Type,
+	^Distinct_Type,
+	^Poly_Type,
+	^Proc_Type,
+	^Pointer_Type,
+	^Multi_Pointer_Type,
+	^Array_Type,
+	^Dynamic_Array_Type,
+	^Struct_Type,
+	^Union_Type,
+	^Enum_Type,
+	^Bit_Set_Type,
+	^Map_Type,
+	^Relative_Type,
+	^Matrix_Type,
+
+	^Bad_Stmt,
+	^Empty_Stmt,
+	^Expr_Stmt,
+	^Tag_Stmt,
+	^Assign_Stmt,
+	^Block_Stmt,
+	^If_Stmt,
+	^When_Stmt,
+	^Return_Stmt,
+	^Defer_Stmt,
+	^For_Stmt,
+	^Range_Stmt,
+	^Inline_Range_Stmt,
+	^Case_Clause,
+	^Switch_Stmt,
+	^Type_Switch_Stmt,
+	^Branch_Stmt,
+	^Using_Stmt,
+
+	^Bad_Decl,
+	^Value_Decl,
+	^Package_Decl,
+	^Import_Decl,
+	^Foreign_Block_Decl,
+	^Foreign_Import_Decl,
+
+	^Attribute,
+	^Field,
+	^Field_List,
+}
+
+
+Any_Expr :: union {
+	^Bad_Expr,
+	^Ident,
+	^Implicit,
+	^Undef,
+	^Basic_Lit,
+	^Basic_Directive,
+	^Ellipsis,
+	^Proc_Lit,
+	^Comp_Lit,
+	^Tag_Expr,
+	^Unary_Expr,
+	^Binary_Expr,
+	^Paren_Expr,
+	^Selector_Expr,
+	^Implicit_Selector_Expr,
+	^Selector_Call_Expr,
+	^Index_Expr,
+	^Deref_Expr,
+	^Slice_Expr,
+	^Matrix_Index_Expr,
+	^Call_Expr,
+	^Field_Value,
+	^Ternary_If_Expr,
+	^Ternary_When_Expr,
+	^Or_Else_Expr,
+	^Or_Return_Expr,
+	^Type_Assertion,
+	^Type_Cast,
+	^Auto_Cast,
+	^Inline_Asm_Expr,
+
+	^Proc_Group,
+
+	^Typeid_Type,
+	^Helper_Type,
+	^Distinct_Type,
+	^Poly_Type,
+	^Proc_Type,
+	^Pointer_Type,
+	^Multi_Pointer_Type,
+	^Array_Type,
+	^Dynamic_Array_Type,
+	^Struct_Type,
+	^Union_Type,
+	^Enum_Type,
+	^Bit_Set_Type,
+	^Map_Type,
+	^Relative_Type,
+	^Matrix_Type,
+}
+
+
+Any_Stmt :: union {
+	^Bad_Stmt,
+	^Empty_Stmt,
+	^Expr_Stmt,
+	^Tag_Stmt,
+	^Assign_Stmt,
+	^Block_Stmt,
+	^If_Stmt,
+	^When_Stmt,
+	^Return_Stmt,
+	^Defer_Stmt,
+	^For_Stmt,
+	^Range_Stmt,
+	^Inline_Range_Stmt,
+	^Case_Clause,
+	^Switch_Stmt,
+	^Type_Switch_Stmt,
+	^Branch_Stmt,
+	^Using_Stmt,
+
+	^Bad_Decl,
+	^Value_Decl,
+	^Package_Decl,
+	^Import_Decl,
+	^Foreign_Block_Decl,
+	^Foreign_Import_Decl,
+}

+ 117 - 83
core/odin/ast/clone.odin

@@ -1,16 +1,25 @@
 package odin_ast
 
+import "core:intrinsics"
 import "core:mem"
 import "core:fmt"
+import "core:reflect"
 import "core:odin/tokenizer"
+_ :: intrinsics
 
 new :: proc($T: typeid, pos, end: tokenizer.Pos) -> ^T {
 	n, _ := mem.new(T)
 	n.pos = pos
 	n.end = end
-	n.derived = n^
+	n.derived = n
 	base: ^Node = n // dummy check
 	_ = base // "Use" type to make -vet happy
+	when intrinsics.type_has_field(T, "derived_expr") {
+		n.derived_expr = n
+	}
+	when intrinsics.type_has_field(T, "derived_stmt") {
+		n.derived_stmt = n
+	}
 	return n
 }
 
@@ -59,232 +68,257 @@ clone_node :: proc(node: ^Node) -> ^Node {
 		return nil
 	}
 
-	size := size_of(Node)
+	size  := size_of(Node)
 	align := align_of(Node)
-	ti := type_info_of(node.derived.id)
+	ti := reflect.union_variant_type_info(node.derived)
 	if ti != nil {
-		size = ti.size
-		align = ti.align
+		elem := ti.variant.(reflect.Type_Info_Pointer).elem
+		size  = elem.size
+		align = elem.align
 	}
 
-	switch in node.derived {
-	case Package, File:
+	#partial switch in node.derived {
+	case ^Package, ^File:
 		panic("Cannot clone this node type")
 	}
 
 	res := cast(^Node)mem.alloc(size, align)
 	src: rawptr = node
 	if node.derived != nil {
-		src = node.derived.data
+		src = (^rawptr)(&node.derived)^
 	}
 	mem.copy(res, src, size)
-	res.derived.data = rawptr(res)
-	res.derived.id = node.derived.id
+	res_ptr_any: any
+	res_ptr_any.data = &res
+	res_ptr_any.id = ti.id
+
+	reflect.set_union_value(res.derived, res_ptr_any)
 
-	switch r in &res.derived {
-	case Bad_Expr:
-	case Ident:
-	case Implicit:
-	case Undef:
-	case Basic_Lit:
+	res_ptr := reflect.deref(res_ptr_any)
+
+	if de := reflect.struct_field_value_by_name(res_ptr, "derived_expr", true); de != nil {
+		reflect.set_union_value(de, res_ptr_any)
+	}
+	if ds := reflect.struct_field_value_by_name(res_ptr, "derived_stmt", true); ds != nil {
+		reflect.set_union_value(ds, res_ptr_any)
+	}
 
-	case Ellipsis:
+	if res.derived != nil do switch r in res.derived {
+	case ^Package, ^File:
+	case ^Bad_Expr:
+	case ^Ident:
+	case ^Implicit:
+	case ^Undef:
+	case ^Basic_Lit:
+	case ^Basic_Directive:
+	case ^Comment_Group:
+
+	case ^Ellipsis:
 		r.expr = clone(r.expr)
-	case Proc_Lit:
+	case ^Proc_Lit:
 		r.type = auto_cast clone(r.type)
 		r.body = clone(r.body)
-	case Comp_Lit:
+	case ^Comp_Lit:
 		r.type  = clone(r.type)
 		r.elems = clone(r.elems)
 
-	case Tag_Expr:
+	case ^Tag_Expr:
 		r.expr = clone(r.expr)
-	case Unary_Expr:
+	case ^Unary_Expr:
 		r.expr = clone(r.expr)
-	case Binary_Expr:
+	case ^Binary_Expr:
 		r.left  = clone(r.left)
 		r.right = clone(r.right)
-	case Paren_Expr:
+	case ^Paren_Expr:
 		r.expr = clone(r.expr)
-	case Selector_Expr:
+	case ^Selector_Expr:
 		r.expr = clone(r.expr)
 		r.field = auto_cast clone(r.field)
-	case Implicit_Selector_Expr:
+	case ^Implicit_Selector_Expr:
 		r.field = auto_cast clone(r.field)
-	case Selector_Call_Expr:
+	case ^Selector_Call_Expr:
 		r.expr = clone(r.expr)
 		r.call = auto_cast clone(r.call)
-	case Index_Expr:
+	case ^Index_Expr:
 		r.expr = clone(r.expr)
 		r.index = clone(r.index)
-	case Matrix_Index_Expr:
+	case ^Matrix_Index_Expr:
 		r.expr = clone(r.expr)
 		r.row_index = clone(r.row_index)
 		r.column_index = clone(r.column_index)
-	case Deref_Expr:
+	case ^Deref_Expr:
 		r.expr = clone(r.expr)
-	case Slice_Expr:
+	case ^Slice_Expr:
 		r.expr = clone(r.expr)
 		r.low  = clone(r.low)
 		r.high = clone(r.high)
-	case Call_Expr:
+	case ^Call_Expr:
 		r.expr = clone(r.expr)
 		r.args = clone(r.args)
-	case Field_Value:
+	case ^Field_Value:
 		r.field = clone(r.field)
 		r.value = clone(r.value)
-	case Ternary_If_Expr:
+	case ^Ternary_If_Expr:
 		r.x    = clone(r.x)
 		r.cond = clone(r.cond)
 		r.y    = clone(r.y)
-	case Ternary_When_Expr:
+	case ^Ternary_When_Expr:
 		r.x    = clone(r.x)
 		r.cond = clone(r.cond)
 		r.y    = clone(r.y)
-	case Or_Else_Expr:
+	case ^Or_Else_Expr:
 		r.x    = clone(r.x)
 		r.y    = clone(r.y)
-	case Or_Return_Expr:
+	case ^Or_Return_Expr:
 		r.expr = clone(r.expr)
-	case Type_Assertion:
+	case ^Type_Assertion:
 		r.expr = clone(r.expr)
 		r.type = clone(r.type)
-	case Type_Cast:
+	case ^Type_Cast:
 		r.type = clone(r.type)
 		r.expr = clone(r.expr)
-	case Auto_Cast:
+	case ^Auto_Cast:
 		r.expr = clone(r.expr)
-	case Inline_Asm_Expr:
+	case ^Inline_Asm_Expr:
 		r.param_types        = clone(r.param_types)
 		r.return_type        = clone(r.return_type)
 		r.constraints_string = clone(r.constraints_string)
 		r.asm_string         = clone(r.asm_string)
 
-	case Bad_Stmt:
+	case ^Bad_Stmt:
 		// empty
-	case Empty_Stmt:
+	case ^Empty_Stmt:
 		// empty
-	case Expr_Stmt:
+	case ^Expr_Stmt:
 		r.expr = clone(r.expr)
-	case Tag_Stmt:
+	case ^Tag_Stmt:
 		r.stmt = clone(r.stmt)
 
-	case Assign_Stmt:
+	case ^Assign_Stmt:
 		r.lhs = clone(r.lhs)
 		r.rhs = clone(r.rhs)
-	case Block_Stmt:
+	case ^Block_Stmt:
 		r.label = clone(r.label)
 		r.stmts = clone(r.stmts)
-	case If_Stmt:
+	case ^If_Stmt:
 		r.label     = clone(r.label)
 		r.init      = clone(r.init)
 		r.cond      = clone(r.cond)
 		r.body      = clone(r.body)
 		r.else_stmt = clone(r.else_stmt)
-	case When_Stmt:
+	case ^When_Stmt:
 		r.cond      = clone(r.cond)
 		r.body      = clone(r.body)
 		r.else_stmt = clone(r.else_stmt)
-	case Return_Stmt:
+	case ^Return_Stmt:
 		r.results = clone(r.results)
-	case Defer_Stmt:
+	case ^Defer_Stmt:
 		r.stmt = clone(r.stmt)
-	case For_Stmt:
+	case ^For_Stmt:
 		r.label = clone(r.label)
 		r.init = clone(r.init)
 		r.cond = clone(r.cond)
 		r.post = clone(r.post)
 		r.body = clone(r.body)
-	case Range_Stmt:
+	case ^Range_Stmt:
 		r.label = clone(r.label)
 		r.vals = clone(r.vals)
 		r.expr = clone(r.expr)
 		r.body = clone(r.body)
-	case Case_Clause:
+	case ^Inline_Range_Stmt:
+		r.label = clone(r.label)
+		r.val0 = clone(r.val0)
+		r.val1 = clone(r.val1)
+		r.expr = clone(r.expr)
+		r.body = clone(r.body)
+	case ^Case_Clause:
 		r.list = clone(r.list)
 		r.body = clone(r.body)
-	case Switch_Stmt:
+	case ^Switch_Stmt:
 		r.label = clone(r.label)
 		r.init = clone(r.init)
 		r.cond = clone(r.cond)
 		r.body = clone(r.body)
-	case Type_Switch_Stmt:
+	case ^Type_Switch_Stmt:
 		r.label = clone(r.label)
 		r.tag  = clone(r.tag)
 		r.expr = clone(r.expr)
 		r.body = clone(r.body)
-	case Branch_Stmt:
+	case ^Branch_Stmt:
 		r.label = auto_cast clone(r.label)
-	case Using_Stmt:
+	case ^Using_Stmt:
 		r.list = clone(r.list)
-	case Bad_Decl:
-	case Value_Decl:
+	case ^Bad_Decl:
+	case ^Value_Decl:
 		r.attributes = clone(r.attributes)
 		r.names      = clone(r.names)
 		r.type       = clone(r.type)
 		r.values     = clone(r.values)
-	case Package_Decl:
-	case Import_Decl:
-	case Foreign_Block_Decl:
+	case ^Package_Decl:
+	case ^Import_Decl:
+	case ^Foreign_Block_Decl:
 		r.attributes      = clone(r.attributes)
 		r.foreign_library = clone(r.foreign_library)
 		r.body            = clone(r.body)
-	case Foreign_Import_Decl:
+	case ^Foreign_Import_Decl:
 		r.name = auto_cast clone(r.name)
-	case Proc_Group:
+	case ^Proc_Group:
 		r.args = clone(r.args)
-	case Attribute:
+	case ^Attribute:
 		r.elems = clone(r.elems)
-	case Field:
+	case ^Field:
 		r.names         = clone(r.names)
 		r.type          = clone(r.type)
 		r.default_value = clone(r.default_value)
-	case Field_List:
+	case ^Field_List:
 		r.list = clone(r.list)
-	case Typeid_Type:
+	case ^Typeid_Type:
 		r.specialization = clone(r.specialization)
-	case Helper_Type:
+	case ^Helper_Type:
 		r.type = clone(r.type)
-	case Distinct_Type:
+	case ^Distinct_Type:
 		r.type = clone(r.type)
-	case Poly_Type:
+	case ^Poly_Type:
 		r.type = auto_cast clone(r.type)
 		r.specialization = clone(r.specialization)
-	case Proc_Type:
+	case ^Proc_Type:
 		r.params  = auto_cast clone(r.params)
 		r.results = auto_cast clone(r.results)
-	case Pointer_Type:
+	case ^Pointer_Type:
 		r.elem = clone(r.elem)
-	case Multi_Pointer_Type:
+	case ^Multi_Pointer_Type:
 		r.elem = clone(r.elem)
-	case Array_Type:
+	case ^Array_Type:
 		r.len  = clone(r.len)
 		r.elem = clone(r.elem)
-	case Dynamic_Array_Type:
+	case ^Dynamic_Array_Type:
 		r.elem = clone(r.elem)
-	case Struct_Type:
+	case ^Struct_Type:
 		r.poly_params = auto_cast clone(r.poly_params)
 		r.align = clone(r.align)
 		r.fields = auto_cast clone(r.fields)
-	case Union_Type:
+	case ^Union_Type:
 		r.poly_params = auto_cast clone(r.poly_params)
 		r.align = clone(r.align)
 		r.variants = clone(r.variants)
-	case Enum_Type:
+	case ^Enum_Type:
 		r.base_type = clone(r.base_type)
 		r.fields = clone(r.fields)
-	case Bit_Set_Type:
+	case ^Bit_Set_Type:
 		r.elem = clone(r.elem)
 		r.underlying = clone(r.underlying)
-	case Map_Type:
+	case ^Map_Type:
 		r.key = clone(r.key)
 		r.value = clone(r.value)
-	case Matrix_Type:
+	case ^Matrix_Type:
 		r.row_count = clone(r.row_count)
 		r.column_count = clone(r.column_count)
 		r.elem = clone(r.elem)
+	case ^Relative_Type:
+		r.tag = clone(r.tag)
+		r.type = clone(r.type)
 	case:
-		fmt.panicf("Unhandled node kind: %T", r)
+		fmt.panicf("Unhandled node kind: %v", r)
 	}
 
 	return res

+ 77 - 77
core/odin/ast/walk.odin

@@ -59,64 +59,64 @@ walk :: proc(v: ^Visitor, node: ^Node) {
 	}
 
 	switch n in &node.derived {
-	case File:
+	case ^File:
 		if n.docs != nil {
 			walk(v, n.docs)
 		}
 		walk_stmt_list(v, n.decls[:])
-	case Package:
+	case ^Package:
 		for _, f in n.files {
 			walk(v, f)
 		}
 
-	case Comment_Group:
+	case ^Comment_Group:
 		// empty
-	case Bad_Expr:
-	case Ident:
-	case Implicit:
-	case Undef:
-	case Basic_Lit:
-	case Basic_Directive:
-	case Ellipsis:
+	case ^Bad_Expr:
+	case ^Ident:
+	case ^Implicit:
+	case ^Undef:
+	case ^Basic_Lit:
+	case ^Basic_Directive:
+	case ^Ellipsis:
 		if n.expr != nil {
 			walk(v, n.expr)
 		}
-	case Proc_Lit:
+	case ^Proc_Lit:
 		walk(v, n.type)
 		walk(v, n.body)
 		walk_expr_list(v, n.where_clauses)
-	case Comp_Lit:
+	case ^Comp_Lit:
 		if n.type != nil {
 			walk(v, n.type)
 		}
 		walk_expr_list(v, n.elems)
-	case Tag_Expr:
+	case ^Tag_Expr:
 		walk(v, n.expr)
-	case Unary_Expr:
+	case ^Unary_Expr:
 		walk(v, n.expr)
-	case Binary_Expr:
+	case ^Binary_Expr:
 		walk(v, n.left)
 		walk(v, n.right)
-	case Paren_Expr:
+	case ^Paren_Expr:
 		walk(v, n.expr)
-	case Selector_Expr:
+	case ^Selector_Expr:
 		walk(v, n.expr)
 		walk(v, n.field)
-	case Implicit_Selector_Expr:
+	case ^Implicit_Selector_Expr:
 		walk(v, n.field)
-	case Selector_Call_Expr:
+	case ^Selector_Call_Expr:
 		walk(v, n.expr)
 		walk(v, n.call)
-	case Index_Expr:
+	case ^Index_Expr:
 		walk(v, n.expr)
 		walk(v, n.index)
-	case Matrix_Index_Expr:
+	case ^Matrix_Index_Expr:
 		walk(v, n.expr)
 		walk(v, n.row_index)
 		walk(v, n.column_index)
-	case Deref_Expr:
+	case ^Deref_Expr:
 		walk(v, n.expr)
-	case Slice_Expr:
+	case ^Slice_Expr:
 		walk(v, n.expr)
 		if n.low != nil {
 			walk(v, n.low)
@@ -124,57 +124,57 @@ walk :: proc(v: ^Visitor, node: ^Node) {
 		if n.high != nil {
 			walk(v, n.high)
 		}
-	case Call_Expr:
+	case ^Call_Expr:
 		walk(v, n.expr)
 		walk_expr_list(v, n.args)
-	case Field_Value:
+	case ^Field_Value:
 		walk(v, n.field)
 		walk(v, n.value)
-	case Ternary_If_Expr:
+	case ^Ternary_If_Expr:
 		walk(v, n.x)
 		walk(v, n.cond)
 		walk(v, n.y)
-	case Ternary_When_Expr:
+	case ^Ternary_When_Expr:
 		walk(v, n.x)
 		walk(v, n.cond)
 		walk(v, n.y)
-	case Or_Else_Expr:
+	case ^Or_Else_Expr:
 		walk(v, n.x)
 		walk(v, n.y)
-	case Or_Return_Expr:
+	case ^Or_Return_Expr:
 		walk(v, n.expr)
-	case Type_Assertion:
+	case ^Type_Assertion:
 		walk(v, n.expr)
 		if n.type != nil {
 			walk(v, n.type)
 		}
-	case Type_Cast:
+	case ^Type_Cast:
 		walk(v, n.type)
 		walk(v, n.expr)
-	case Auto_Cast:
+	case ^Auto_Cast:
 		walk(v, n.expr)
-	case Inline_Asm_Expr:
+	case ^Inline_Asm_Expr:
 		walk_expr_list(v, n.param_types)
 		walk(v, n.return_type)
 		walk(v, n.constraints_string)
 		walk(v, n.asm_string)
 
 
-	case Bad_Stmt:
-	case Empty_Stmt:
-	case Expr_Stmt:
+	case ^Bad_Stmt:
+	case ^Empty_Stmt:
+	case ^Expr_Stmt:
 		walk(v, n.expr)
-	case Tag_Stmt:
+	case ^Tag_Stmt:
 		walk(v, n.stmt)
-	case Assign_Stmt:
+	case ^Assign_Stmt:
 		walk_expr_list(v, n.lhs)
 		walk_expr_list(v, n.rhs)
-	case Block_Stmt:
+	case ^Block_Stmt:
 		if n.label != nil {
 			walk(v, n.label)
 		}
 		walk_stmt_list(v, n.stmts)
-	case If_Stmt:
+	case ^If_Stmt:
 		if n.label != nil {
 			walk(v, n.label)
 		}
@@ -186,17 +186,17 @@ walk :: proc(v: ^Visitor, node: ^Node) {
 		if n.else_stmt != nil {
 			walk(v, n.else_stmt)
 		}
-	case When_Stmt:
+	case ^When_Stmt:
 		walk(v, n.cond)
 		walk(v, n.body)
 		if n.else_stmt != nil {
 			walk(v, n.else_stmt)
 		}
-	case Return_Stmt:
+	case ^Return_Stmt:
 		walk_expr_list(v, n.results)
-	case Defer_Stmt:
+	case ^Defer_Stmt:
 		walk(v, n.stmt)
-	case For_Stmt:
+	case ^For_Stmt:
 		if n.label != nil {
 			walk(v, n.label)
 		}
@@ -210,7 +210,7 @@ walk :: proc(v: ^Visitor, node: ^Node) {
 			walk(v, n.post)
 		}
 		walk(v, n.body)
-	case Range_Stmt:
+	case ^Range_Stmt:
 		if n.label != nil {
 			walk(v, n.label)
 		}
@@ -221,7 +221,7 @@ walk :: proc(v: ^Visitor, node: ^Node) {
 		}
 		walk(v, n.expr)
 		walk(v, n.body)
-	case Inline_Range_Stmt:
+	case ^Inline_Range_Stmt:
 		if n.label != nil {
 			walk(v, n.label)
 		}
@@ -233,10 +233,10 @@ walk :: proc(v: ^Visitor, node: ^Node) {
 		}
 		walk(v, n.expr)
 		walk(v, n.body)
-	case Case_Clause:
+	case ^Case_Clause:
 		walk_expr_list(v, n.list)
 		walk_stmt_list(v, n.body)
-	case Switch_Stmt:
+	case ^Switch_Stmt:
 		if n.label != nil {
 			walk(v, n.label)
 		}
@@ -247,7 +247,7 @@ walk :: proc(v: ^Visitor, node: ^Node) {
 			walk(v, n.cond)
 		}
 		walk(v, n.body)
-	case Type_Switch_Stmt:
+	case ^Type_Switch_Stmt:
 		if n.label != nil {
 			walk(v, n.label)
 		}
@@ -258,16 +258,16 @@ walk :: proc(v: ^Visitor, node: ^Node) {
 			walk(v, n.expr)
 		}
 		walk(v, n.body)
-	case Branch_Stmt:
+	case ^Branch_Stmt:
 		if n.label != nil {
 			walk(v, n.label)
 		}
-	case Using_Stmt:
+	case ^Using_Stmt:
 		walk_expr_list(v, n.list)
 
 
-	case Bad_Decl:
-	case Value_Decl:
+	case ^Bad_Decl:
+	case ^Value_Decl:
 		if n.docs != nil {
 			walk(v, n.docs)
 		}
@@ -280,21 +280,21 @@ walk :: proc(v: ^Visitor, node: ^Node) {
 		if n.comment != nil {
 			walk(v, n.comment)
 		}
-	case Package_Decl:
+	case ^Package_Decl:
 		if n.docs != nil {
 			walk(v, n.docs)
 		}
 		if n.comment != nil {
 			walk(v, n.comment)
 		}
-	case Import_Decl:
+	case ^Import_Decl:
 		if n.docs != nil {
 			walk(v, n.docs)
 		}
 		if n.comment != nil {
 			walk(v, n.comment)
 		}
-	case Foreign_Block_Decl:
+	case ^Foreign_Block_Decl:
 		if n.docs != nil {
 			walk(v, n.docs)
 		}
@@ -303,7 +303,7 @@ walk :: proc(v: ^Visitor, node: ^Node) {
 			walk(v, n.foreign_library)
 		}
 		walk(v, n.body)
-	case Foreign_Import_Decl:
+	case ^Foreign_Import_Decl:
 		if n.docs != nil {
 			walk(v, n.docs)
 		}
@@ -313,11 +313,11 @@ walk :: proc(v: ^Visitor, node: ^Node) {
 			walk(v, n.comment)
 		}
 
-	case Proc_Group:
+	case ^Proc_Group:
 		walk_expr_list(v, n.args)
-	case Attribute:
+	case ^Attribute:
 		walk_expr_list(v, n.elems)
-	case Field:
+	case ^Field:
 		if n.docs != nil {
 			walk(v, n.docs)
 		}
@@ -331,31 +331,31 @@ walk :: proc(v: ^Visitor, node: ^Node) {
 		if n.comment != nil {
 			walk(v, n.comment)
 		}
-	case Field_List:
+	case ^Field_List:
 		for x in n.list {
 			walk(v, x)
 		}
-	case Typeid_Type:
+	case ^Typeid_Type:
 		if n.specialization != nil {
 			walk(v, n.specialization)
 		}
-	case Helper_Type:
+	case ^Helper_Type:
 		walk(v, n.type)
-	case Distinct_Type:
+	case ^Distinct_Type:
 		walk(v, n.type)
-	case Poly_Type:
+	case ^Poly_Type:
 		walk(v, n.type)
 		if n.specialization != nil {
 			walk(v, n.specialization)
 		}
-	case Proc_Type:
+	case ^Proc_Type:
 		walk(v, n.params)
 		walk(v, n.results)
-	case Pointer_Type:
+	case ^Pointer_Type:
 		walk(v, n.elem)
-	case Multi_Pointer_Type:
+	case ^Multi_Pointer_Type:
 		walk(v, n.elem)
-	case Array_Type:
+	case ^Array_Type:
 		if n.tag != nil {
 			walk(v, n.tag)
 		}
@@ -363,12 +363,12 @@ walk :: proc(v: ^Visitor, node: ^Node) {
 			walk(v, n.len)
 		}
 		walk(v, n.elem)
-	case Dynamic_Array_Type:
+	case ^Dynamic_Array_Type:
 		if n.tag != nil {
 			walk(v, n.tag)
 		}
 		walk(v, n.elem)
-	case Struct_Type:
+	case ^Struct_Type:
 		if n.poly_params != nil {
 			walk(v, n.poly_params)
 		}
@@ -377,7 +377,7 @@ walk :: proc(v: ^Visitor, node: ^Node) {
 		}
 		walk_expr_list(v, n.where_clauses)
 		walk(v, n.fields)
-	case Union_Type:
+	case ^Union_Type:
 		if n.poly_params != nil {
 			walk(v, n.poly_params)
 		}
@@ -386,23 +386,23 @@ walk :: proc(v: ^Visitor, node: ^Node) {
 		}
 		walk_expr_list(v, n.where_clauses)
 		walk_expr_list(v, n.variants)
-	case Enum_Type:
+	case ^Enum_Type:
 		if n.base_type != nil {
 			walk(v, n.base_type)
 		}
 		walk_expr_list(v, n.fields)
-	case Bit_Set_Type:
+	case ^Bit_Set_Type:
 		walk(v, n.elem)
 		if n.underlying != nil {
 			walk(v, n.underlying)
 		}
-	case Map_Type:
+	case ^Map_Type:
 		walk(v, n.key)
 		walk(v, n.value)
-	case Relative_Type:
+	case ^Relative_Type:
 		walk(v, n.tag)
 		walk(v, n.type)
-	case Matrix_Type:
+	case ^Matrix_Type:
 		walk(v, n.row_count)
 		walk(v, n.column_count)
 		walk(v, n.elem)

+ 90 - 90
core/odin/parser/parser.odin

@@ -195,10 +195,10 @@ parse_file :: proc(p: ^Parser, file: ^ast.File) -> bool {
 	for p.curr_tok.kind != .EOF {
 		stmt := parse_stmt(p)
 		if stmt != nil {
-			if _, ok := stmt.derived.(ast.Empty_Stmt); !ok {
+			if _, ok := stmt.derived.(^ast.Empty_Stmt); !ok {
 				append(&p.file.decls, stmt)
-				if es, es_ok := stmt.derived.(ast.Expr_Stmt); es_ok && es.expr != nil {
-					if _, pl_ok := es.expr.derived.(ast.Proc_Lit); pl_ok {
+				if es, es_ok := stmt.derived.(^ast.Expr_Stmt); es_ok && es.expr != nil {
+					if _, pl_ok := es.expr.derived.(^ast.Proc_Lit); pl_ok {
 						error(p, stmt.pos, "procedure literal evaluated but not used")
 					}
 				}
@@ -459,7 +459,7 @@ is_blank_ident_token :: proc(tok: tokenizer.Token) -> bool {
 	return false
 }
 is_blank_ident_node :: proc(node: ^ast.Node) -> bool {
-	if ident, ok := node.derived.(ast.Ident); ok {
+	if ident, ok := node.derived.(^ast.Ident); ok {
 		return is_blank_ident(ident.name)
 	}
 	return true
@@ -502,34 +502,34 @@ is_semicolon_optional_for_node :: proc(p: ^Parser, node: ^ast.Node) -> bool {
 		return true
 	}
 
-	switch n in node.derived {
-	case ast.Empty_Stmt, ast.Block_Stmt:
+	#partial switch n in node.derived {
+	case ^ast.Empty_Stmt, ^ast.Block_Stmt:
 		return true
 
-	case ast.If_Stmt, ast.When_Stmt,
-	     ast.For_Stmt, ast.Range_Stmt, ast.Inline_Range_Stmt,
-	     ast.Switch_Stmt, ast.Type_Switch_Stmt:
+	case ^ast.If_Stmt, ^ast.When_Stmt,
+	     ^ast.For_Stmt, ^ast.Range_Stmt, ^ast.Inline_Range_Stmt,
+	     ^ast.Switch_Stmt, ^ast.Type_Switch_Stmt:
 		return true
 
-	case ast.Helper_Type:
+	case ^ast.Helper_Type:
 		return is_semicolon_optional_for_node(p, n.type)
-	case ast.Distinct_Type:
+	case ^ast.Distinct_Type:
 		return is_semicolon_optional_for_node(p, n.type)
-	case ast.Pointer_Type:
+	case ^ast.Pointer_Type:
 		return is_semicolon_optional_for_node(p, n.elem)
-	case ast.Struct_Type, ast.Union_Type, ast.Enum_Type:
+	case ^ast.Struct_Type, ^ast.Union_Type, ^ast.Enum_Type:
 		// Require semicolon within a procedure body
 		return p.curr_proc == nil
-	case ast.Proc_Lit:
+	case ^ast.Proc_Lit:
 		return true
 
-	case ast.Package_Decl, ast.Import_Decl, ast.Foreign_Import_Decl:
+	case ^ast.Package_Decl, ^ast.Import_Decl, ^ast.Foreign_Import_Decl:
 		return true
 
-	case ast.Foreign_Block_Decl:
+	case ^ast.Foreign_Block_Decl:
 		return is_semicolon_optional_for_node(p, n.body)
 
-	case ast.Value_Decl:
+	case ^ast.Value_Decl:
 		if n.is_mutable {
 			return false
 		}
@@ -641,10 +641,10 @@ parse_stmt_list :: proc(p: ^Parser) -> []^ast.Stmt {
 	    p.curr_tok.kind != .EOF  {
 		stmt := parse_stmt(p)
 		if stmt != nil {
-			if _, ok := stmt.derived.(ast.Empty_Stmt); !ok {
+			if _, ok := stmt.derived.(^ast.Empty_Stmt); !ok {
 				append(&list, stmt)
-				if es, es_ok := stmt.derived.(ast.Expr_Stmt); es_ok && es.expr != nil {
-					if _, pl_ok := es.expr.derived.(ast.Proc_Lit); pl_ok {
+				if es, es_ok := stmt.derived.(^ast.Expr_Stmt); es_ok && es.expr != nil {
+					if _, pl_ok := es.expr.derived.(^ast.Proc_Lit); pl_ok {
 						error(p, stmt.pos, "procedure literal evaluated but not used")
 					}
 				}
@@ -722,7 +722,7 @@ convert_stmt_to_expr :: proc(p: ^Parser, stmt: ^ast.Stmt, kind: string) -> ^ast.
 	if stmt == nil {
 		return nil
 	}
-	if es, ok := stmt.derived.(ast.Expr_Stmt); ok {
+	if es, ok := stmt.derived.(^ast.Expr_Stmt); ok {
 		return es.expr
 	}
 	error(p, stmt.pos, "expected %s, found a simple statement", kind)
@@ -864,7 +864,7 @@ parse_for_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
 
 		if p.curr_tok.kind != .Semicolon {
 			cond = parse_simple_stmt(p, {Stmt_Allow_Flag.In})
-			if as, ok := cond.derived.(ast.Assign_Stmt); ok && as.op.kind == .In {
+			if as, ok := cond.derived.(^ast.Assign_Stmt); ok && as.op.kind == .In {
 				is_range = true
 			}
 		}
@@ -906,7 +906,7 @@ parse_for_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
 
 
 	if is_range {
-		assign_stmt := cond.derived.(ast.Assign_Stmt)
+		assign_stmt := cond.derived.(^ast.Assign_Stmt)
 		vals := assign_stmt.lhs[:]
 
 		rhs: ^ast.Expr
@@ -987,7 +987,7 @@ parse_switch_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
 			tag = as
 		} else {
 			tag = parse_simple_stmt(p, {Stmt_Allow_Flag.In})
-			if as, ok := tag.derived.(ast.Assign_Stmt); ok && as.op.kind == .In {
+			if as, ok := tag.derived.(^ast.Assign_Stmt); ok && as.op.kind == .In {
 				is_type_switch = true
 			} else if parse_control_statement_semicolon_separator(p) {
 				init = tag
@@ -1074,14 +1074,14 @@ parse_attribute :: proc(p: ^Parser, tok: tokenizer.Token, open_kind, close_kind:
 	skip_possible_newline(p)
 
 	decl := parse_stmt(p)
-	switch d in &decl.derived {
-	case ast.Value_Decl:
+	#partial switch d in decl.derived_stmt {
+	case ^ast.Value_Decl:
 		if d.docs == nil { d.docs = docs }
 		append(&d.attributes, attribute)
-	case ast.Foreign_Block_Decl:
+	case ^ast.Foreign_Block_Decl:
 		if d.docs == nil { d.docs = docs }
 		append(&d.attributes, attribute)
-	case ast.Foreign_Import_Decl:
+	case ^ast.Foreign_Import_Decl:
 		if d.docs == nil { d.docs = docs }
 		append(&d.attributes, attribute)
 	case:
@@ -1095,11 +1095,11 @@ parse_attribute :: proc(p: ^Parser, tok: tokenizer.Token, open_kind, close_kind:
 
 parse_foreign_block_decl :: proc(p: ^Parser) -> ^ast.Stmt {
 	decl := parse_stmt(p)
-	switch in decl.derived {
-	case ast.Empty_Stmt, ast.Bad_Stmt, ast.Bad_Decl:
+	#partial switch in decl.derived_stmt {
+	case ^ast.Empty_Stmt, ^ast.Bad_Stmt, ^ast.Bad_Decl:
 		// Ignore
 		return nil
-	case ast.When_Stmt, ast.Value_Decl:
+	case ^ast.When_Stmt, ^ast.Value_Decl:
 		return decl
 	}
 
@@ -1303,13 +1303,13 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
 	case .Defer:
 		tok := advance_token(p)
 		stmt := parse_stmt(p)
-		switch s in stmt.derived {
-		case ast.Empty_Stmt:
+		#partial switch s in stmt.derived_stmt {
+		case ^ast.Empty_Stmt:
 			error(p, s.pos, "empty statement after defer (e.g. ';')")
-		case ast.Defer_Stmt:
+		case ^ast.Defer_Stmt:
 			error(p, s.pos, "you cannot defer a defer statement")
 			stmt = s.stmt
-		case ast.Return_Stmt:
+		case ^ast.Return_Stmt:
 			error(p, s.pos, "you cannot defer a return statement")
 		}
 		ds := ast.new(ast.Defer_Stmt, tok.pos, stmt.end)
@@ -1381,8 +1381,8 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
 		expect_token_after(p, .Colon, "identifier list")
 		decl := parse_value_decl(p, list, docs)
 		if decl != nil {
-			switch d in &decl.derived {
-			case ast.Value_Decl:
+			#partial switch d in decl.derived_stmt {
+			case ^ast.Value_Decl:
 				d.is_using = true
 				return decl
 			}
@@ -1413,9 +1413,9 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
 			return stmt
 		case "partial":
 			stmt := parse_stmt(p)
-			switch s in &stmt.derived {
-			case ast.Switch_Stmt:      s.partial = true
-			case ast.Type_Switch_Stmt: s.partial = true
+			#partial switch s in stmt.derived_stmt {
+			case ^ast.Switch_Stmt:      s.partial = true
+			case ^ast.Type_Switch_Stmt: s.partial = true
 			case: error(p, stmt.pos, "#partial can only be applied to a switch statement")
 			}
 			return stmt
@@ -1560,11 +1560,11 @@ parse_body :: proc(p: ^Parser) -> ^ast.Block_Stmt {
 }
 
 convert_stmt_to_body :: proc(p: ^Parser, stmt: ^ast.Stmt) -> ^ast.Stmt {
-	switch s in stmt.derived {
-	case ast.Block_Stmt:
+	#partial switch s in stmt.derived_stmt {
+	case ^ast.Block_Stmt:
 		error(p, stmt.pos, "expected a normal statement rather than a block statement")
 		return stmt
-	case ast.Empty_Stmt:
+	case ^ast.Empty_Stmt:
 		error(p, stmt.pos, "expected a non-empty statement")
 	}
 
@@ -1641,10 +1641,10 @@ convert_to_ident_list :: proc(p: ^Parser, list: []Expr_And_Flags, ignore_flags,
 
 		id: ^ast.Expr = ident.expr
 
-		switch n in ident.expr.derived {
-		case ast.Ident:
-		case ast.Bad_Expr:
-		case ast.Poly_Type:
+		#partial switch n in ident.expr.derived_expr {
+		case ^ast.Ident:
+		case ^ast.Bad_Expr:
+		case ^ast.Poly_Type:
 			if allow_poly_names {
 				if n.specialization == nil {
 					break
@@ -1806,21 +1806,21 @@ check_procedure_name_list :: proc(p: ^Parser, names: []^ast.Expr) -> bool {
 		return false
 	}
 
-	_, first_is_polymorphic := names[0].derived.(ast.Poly_Type)
+	_, first_is_polymorphic := names[0].derived.(^ast.Poly_Type)
 	any_polymorphic_names := first_is_polymorphic
 
 	for i := 1; i < len(names); i += 1 {
 		name := names[i]
 
 		if first_is_polymorphic {
-			if _, ok := name.derived.(ast.Poly_Type); ok {
+			if _, ok := name.derived.(^ast.Poly_Type); ok {
 				any_polymorphic_names = true
 			} else {
 				error(p, name.pos, "mixture of polymorphic and non-polymorphic identifiers")
 				return any_polymorphic_names
 			}
 		} else {
-			if _, ok := name.derived.(ast.Poly_Type); ok {
+			if _, ok := name.derived.(^ast.Poly_Type); ok {
 				any_polymorphic_names = true
 				error(p, name.pos, "mixture of polymorphic and non-polymorphic identifiers")
 				return any_polymorphic_names
@@ -1885,7 +1885,7 @@ parse_field_list :: proc(p: ^Parser, follow: tokenizer.Token_Kind, allowed_flags
 			if type == nil {
 				return false
 			}
-			_, ok := type.derived.(ast.Ellipsis)
+			_, ok := type.derived.(^ast.Ellipsis)
 			return ok
 		}
 
@@ -1903,7 +1903,7 @@ parse_field_list :: proc(p: ^Parser, follow: tokenizer.Token_Kind, allowed_flags
 			type = parse_var_type(p, allowed_flags)
 			tt := ast.unparen_expr(type)
 			if is_signature && !any_polymorphic_names {
-				if ti, ok := tt.derived.(ast.Typeid_Type); ok && ti.specialization != nil {
+				if ti, ok := tt.derived.(^ast.Typeid_Type); ok && ti.specialization != nil {
 					error(p, tt.pos, "specialization of typeid is not allowed without polymorphic names")
 				}
 			}
@@ -1979,7 +1979,7 @@ parse_field_list :: proc(p: ^Parser, follow: tokenizer.Token_Kind, allowed_flags
 	    p.curr_tok.kind != .EOF {
 		prefix_flags := parse_field_prefixes(p)
 		param := parse_var_type(p, allowed_flags & {.Typeid_Token, .Ellipsis})
-		if _, ok := param.derived.(ast.Ellipsis); ok {
+		if _, ok := param.derived.(^ast.Ellipsis); ok {
 			if seen_ellipsis {
 				error(p, param.pos, "extra variadic parameter after ellipsis")
 			}
@@ -2006,8 +2006,8 @@ parse_field_list :: proc(p: ^Parser, follow: tokenizer.Token_Kind, allowed_flags
 
 			names := make([]^ast.Expr, 1)
 			names[0] = ast.new(ast.Ident, tok.pos, end_pos(tok))
-			switch ident in &names[0].derived {
-			case ast.Ident:
+			#partial switch ident in names[0].derived_expr {
+			case ^ast.Ident:
 				ident.name = tok.text
 			case:
 				unreachable()
@@ -2137,12 +2137,12 @@ parse_proc_type :: proc(p: ^Parser, tok: tokenizer.Token) -> ^ast.Proc_Type {
 
 	loop: for param in params.list {
 		if param.type != nil {
-			if _, ok := param.type.derived.(ast.Poly_Type); ok {
+			if _, ok := param.type.derived.(^ast.Poly_Type); ok {
 				is_generic = true
 				break loop
 			}
 			for name in param.names {
-				if _, ok := name.derived.(ast.Poly_Type); ok {
+				if _, ok := name.derived.(^ast.Poly_Type); ok {
 					is_generic = true
 					break loop
 				}
@@ -2179,13 +2179,13 @@ parse_inlining_operand :: proc(p: ^Parser, lhs: bool, tok: tokenizer.Token) -> ^
 		}
 	}
 
-	switch e in &ast.unparen_expr(expr).derived {
-	case ast.Proc_Lit:
+	#partial switch e in ast.unparen_expr(expr).derived_expr {
+	case ^ast.Proc_Lit:
 		if e.inlining != .None && e.inlining != pi {
 			error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure literal")
 		}
 		e.inlining = pi
-	case ast.Call_Expr:
+	case ^ast.Call_Expr:
 		if e.inlining != .None && e.inlining != pi {
 			error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure call")
 		}
@@ -2276,9 +2276,9 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 			bd.name = name.text
 			original_type := parse_type(p)
 			type := ast.unparen_expr(original_type)
-			switch t in &type.derived {
-			case ast.Array_Type:         t.tag = bd
-			case ast.Dynamic_Array_Type: t.tag = bd
+			#partial switch t in type.derived_expr {
+			case ^ast.Array_Type:         t.tag = bd
+			case ^ast.Dynamic_Array_Type: t.tag = bd
 			case:
 				error(p, original_type.pos, "expected an array type after #%s", name.text)
 			}
@@ -2290,10 +2290,10 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 			tag.name = name.text
 			original_expr := parse_expr(p, lhs)
 			expr := ast.unparen_expr(original_expr)
-			switch t in &expr.derived {
-			case ast.Comp_Lit:
+			#partial switch t in expr.derived_expr {
+			case ^ast.Comp_Lit:
 				t.tag = tag
-			case ast.Array_Type:
+			case ^ast.Array_Type:
 				t.tag = tag
 				error(p, tok.pos, "#%s has been replaced with #sparse for non-contiguous enumerated array types", name.text)
 			case:
@@ -2308,8 +2308,8 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 			tag.name = name.text
 			original_type := parse_type(p)
 			type := ast.unparen_expr(original_type)
-			switch t in &type.derived {
-			case ast.Array_Type:
+			#partial switch t in type.derived_expr {
+			case ^ast.Array_Type:
 				t.tag = tag
 			case:
 				error(p, tok.pos, "expected an enumerated array type after #%s", name.text)
@@ -2689,7 +2689,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 		variants: [dynamic]^ast.Expr
 		for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF {
 			type := parse_type(p)
-			if _, ok := type.derived.(ast.Bad_Expr); !ok {
+			if _, ok := type.derived.(^ast.Bad_Expr); !ok {
 				append(&variants, type)
 			}
 			if !allow_token(p, .Comma) {
@@ -2864,19 +2864,19 @@ is_literal_type :: proc(expr: ^ast.Expr) -> bool {
 	if val == nil {
 		return false
 	}
-	switch _ in val.derived {
-	case ast.Bad_Expr,
-		ast.Ident,
-		ast.Selector_Expr,
-		ast.Array_Type,
-		ast.Struct_Type,
-		ast.Union_Type,
-		ast.Enum_Type,
-		ast.Dynamic_Array_Type,
-		ast.Map_Type,
-		ast.Bit_Set_Type,
-		ast.Matrix_Type,
-		ast.Call_Expr:
+	#partial switch _ in val.derived_expr {
+	case ^ast.Bad_Expr,
+		^ast.Ident,
+		^ast.Selector_Expr,
+		^ast.Array_Type,
+		^ast.Struct_Type,
+		^ast.Union_Type,
+		^ast.Enum_Type,
+		^ast.Dynamic_Array_Type,
+		^ast.Map_Type,
+		^ast.Bit_Set_Type,
+		^ast.Matrix_Type,
+		^ast.Call_Expr:
 		return true
 	}
 	return false
@@ -2998,7 +2998,7 @@ parse_call_expr :: proc(p: ^Parser, operand: ^ast.Expr) -> ^ast.Expr {
 	ce.close    = close.pos
 
 	o := ast.unparen_expr(operand)
-	if se, ok := o.derived.(ast.Selector_Expr); ok && se.op.kind == .Arrow_Right {
+	if se, ok := o.derived.(^ast.Selector_Expr); ok && se.op.kind == .Arrow_Right {
 		sce := ast.new(ast.Selector_Call_Expr, ce.pos, ce.end)
 		sce.expr = o
 		sce.call = ce
@@ -3428,13 +3428,13 @@ parse_simple_stmt :: proc(p: ^Parser, flags: Stmt_Allow_Flags) -> ^ast.Stmt {
 				stmt := parse_stmt(p)
 
 				if stmt != nil {
-					switch n in &stmt.derived {
-					case ast.Block_Stmt:       n.label = label
-					case ast.If_Stmt:          n.label = label
-					case ast.For_Stmt:         n.label = label
-					case ast.Switch_Stmt:      n.label = label
-					case ast.Type_Switch_Stmt: n.label = label
-					case ast.Range_Stmt:	   n.label = label
+					#partial switch n in stmt.derived_stmt {
+					case ^ast.Block_Stmt:       n.label = label
+					case ^ast.If_Stmt:          n.label = label
+					case ^ast.For_Stmt:         n.label = label
+					case ^ast.Switch_Stmt:      n.label = label
+					case ^ast.Type_Switch_Stmt: n.label = label
+					case ^ast.Range_Stmt:	    n.label = label
 					}
 				}
 

+ 124 - 86
core/odin/printer/visit.odin

@@ -342,16 +342,16 @@ visit_decl :: proc(p: ^Printer, decl: ^ast.Decl, called_in_stmt := false) {
 		return
 	}
 
-	switch v in &decl.derived {
-	case Expr_Stmt:
+	#partial switch v in decl.derived_stmt {
+	case ^Expr_Stmt:
 		move_line(p, decl.pos)
 		visit_expr(p, v.expr)
 		if p.config.semicolons {
 			push_generic_token(p, .Semicolon, 0)
 		}
-	case When_Stmt:
+	case ^When_Stmt:
 		visit_stmt(p, cast(^Stmt)decl)
-	case Foreign_Import_Decl:
+	case ^Foreign_Import_Decl:
 		if len(v.attributes) > 0 {
 			sort.sort(sort_attribute(&v.attributes))
 			move_line(p, v.attributes[0].pos)
@@ -370,7 +370,7 @@ visit_decl :: proc(p: ^Printer, decl: ^ast.Decl, called_in_stmt := false) {
 		for path in v.fullpaths {
 			push_ident_token(p, path, 0)
 		}
-	case Foreign_Block_Decl:
+	case ^Foreign_Block_Decl:
 		if len(v.attributes) > 0 {
 			sort.sort(sort_attribute(&v.attributes))
 			move_line(p, v.attributes[0].pos)
@@ -383,7 +383,7 @@ visit_decl :: proc(p: ^Printer, decl: ^ast.Decl, called_in_stmt := false) {
 
 		visit_expr(p, v.foreign_library)
 		visit_stmt(p, v.body)
-	case Import_Decl:
+	case ^Import_Decl:
 		move_line(p, decl.pos)
 
 		if v.name.text != "" {
@@ -395,7 +395,7 @@ visit_decl :: proc(p: ^Printer, decl: ^ast.Decl, called_in_stmt := false) {
 			push_ident_token(p, v.fullpath, 1)
 		}
 
-	case Value_Decl:
+	case ^Value_Decl:
 		if len(v.attributes) > 0 {
 			sort.sort(sort_attribute(&v.attributes))
 			move_line(p, v.attributes[0].pos)
@@ -446,10 +446,10 @@ visit_decl :: proc(p: ^Printer, decl: ^ast.Decl, called_in_stmt := false) {
 		add_semicolon := true
 
 		for value in v.values {
-			switch a in value.derived {
-			case Union_Type, Enum_Type, Struct_Type:
+			#partial switch a in value.derived {
+			case ^Union_Type, ^Enum_Type, ^Struct_Type:
 				add_semicolon = false || called_in_stmt
-			case Proc_Lit:
+			case ^Proc_Lit:
 				add_semicolon = false
 			}
 		}
@@ -516,23 +516,34 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 		return
 	}
 
-	switch v in stmt.derived {
-	case Import_Decl:
+
+	switch v in stmt.derived_stmt {
+	case ^Bad_Stmt:
+	case ^Bad_Decl:
+	case ^Package_Decl:
+
+	case ^Empty_Stmt:
+		push_generic_token(p, .Semicolon, 0)
+	case ^Tag_Stmt:
+		push_generic_token(p, .Hash, 1)
+		push_generic_token(p, v.op.kind, 1, v.op.text)
+		visit_stmt(p, v.stmt)
+
+
+	case ^Import_Decl:
 		visit_decl(p, cast(^Decl)stmt, true)
 		return
-	case Value_Decl:
+	case ^Value_Decl:
 		visit_decl(p, cast(^Decl)stmt, true)
 		return
-	case Foreign_Import_Decl:
+	case ^Foreign_Import_Decl:
 		visit_decl(p, cast(^Decl)stmt, true)
 		return
-	case Foreign_Block_Decl:
+	case ^Foreign_Block_Decl:
 		visit_decl(p, cast(^Decl)stmt, true)
 		return
-	}
 
-	switch v in stmt.derived {
-	case Using_Stmt:
+	case ^Using_Stmt:
 		move_line(p, v.pos)
 
 		push_generic_token(p, .Using, 1)
@@ -542,7 +553,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 		if p.config.semicolons {
 			push_generic_token(p, .Semicolon, 0)
 		}
-	case Block_Stmt:
+	case ^Block_Stmt:
 		move_line(p, v.pos)
 
 		if v.pos.line == v.end.line {
@@ -572,7 +583,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 				visit_end_brace(p, v.end)
 			}
 		}
-	case If_Stmt:
+	case ^If_Stmt:
 		move_line(p, v.pos)
 
 		if v.label != nil {
@@ -595,7 +606,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 
 		uses_do := false
 
-		if check_stmt, ok := v.body.derived.(Block_Stmt); ok && check_stmt.uses_do {
+		if check_stmt, ok := v.body.derived.(^Block_Stmt); ok && check_stmt.uses_do {
 			uses_do = true
 		}
 
@@ -626,7 +637,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 
 			visit_stmt(p, v.else_stmt)
 		}
-	case Switch_Stmt:
+	case ^Switch_Stmt:
 		move_line(p, v.pos)
 
 		if v.label != nil {
@@ -654,7 +665,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 
 		visit_expr(p, v.cond)
 		visit_stmt(p, v.body)
-	case Case_Clause:
+	case ^Case_Clause:
 		move_line(p, v.pos)
 
 		if !p.config.indent_cases {
@@ -678,7 +689,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 		if !p.config.indent_cases {
 			indent(p)
 		}
-	case Type_Switch_Stmt:
+	case ^Type_Switch_Stmt:
 		move_line(p, v.pos)
 
 		hint_current_line(p, {.Switch_Stmt})
@@ -696,7 +707,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 
 		visit_stmt(p, v.tag)
 		visit_stmt(p, v.body)
-	case Assign_Stmt:
+	case ^Assign_Stmt:
 		move_line(p, v.pos)
 
 		hint_current_line(p, {.Assign})
@@ -710,13 +721,13 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 		if block_stmt && p.config.semicolons {
 			push_generic_token(p, .Semicolon, 0)
 		}
-	case Expr_Stmt:
+	case ^Expr_Stmt:
 		move_line(p, v.pos)
 		visit_expr(p, v.expr)
 		if block_stmt && p.config.semicolons {
 			push_generic_token(p, .Semicolon, 0)
 		}
-	case For_Stmt:
+	case ^For_Stmt:
 		// this should be simplified
 		move_line(p, v.pos)
 
@@ -753,7 +764,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 
 		visit_stmt(p, v.body)
 
-	case Inline_Range_Stmt:
+	case ^Inline_Range_Stmt:
 		move_line(p, v.pos)
 
 		if v.label != nil {
@@ -779,7 +790,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 		visit_expr(p, v.expr)
 		visit_stmt(p, v.body)
 
-	case Range_Stmt:
+	case ^Range_Stmt:
 		move_line(p, v.pos)
 
 		if v.label != nil {
@@ -805,7 +816,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 		visit_expr(p, v.expr)
 
 		visit_stmt(p, v.body)
-	case Return_Stmt:
+	case ^Return_Stmt:
 		move_line(p, v.pos)
 
 		push_generic_token(p, .Return, 1)
@@ -817,7 +828,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 		if block_stmt && p.config.semicolons {
 			push_generic_token(p, .Semicolon, 0)
 		}
-	case Defer_Stmt:
+	case ^Defer_Stmt:
 		move_line(p, v.pos)
 		push_generic_token(p, .Defer, 0)
 
@@ -826,7 +837,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 		if p.config.semicolons {
 			push_generic_token(p, .Semicolon, 0)
 		}
-	case When_Stmt:
+	case ^When_Stmt:
 		move_line(p, v.pos)
 		push_generic_token(p, .When, 1)
 		visit_expr(p, v.cond)
@@ -846,7 +857,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 			visit_stmt(p, v.else_stmt)
 		}
 
-	case Branch_Stmt:
+	case ^Branch_Stmt:
 		move_line(p, v.pos)
 
 		push_generic_token(p, v.tok.kind, 0)
@@ -918,8 +929,15 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 
 	set_source_position(p, expr.pos)
 
-	switch v in expr.derived {
-	case Inline_Asm_Expr:
+	switch v in expr.derived_expr {
+	case ^Bad_Expr:
+
+	case ^Tag_Expr:
+		push_generic_token(p, .Hash, 1)
+		push_generic_token(p, v.op.kind, 1, v.op.text)
+		visit_expr(p, v.expr)
+
+	case ^Inline_Asm_Expr:
 		push_generic_token(p, v.tok.kind, 1, v.tok.text)
 
 		push_generic_token(p, .Open_Paren, 1)
@@ -936,42 +954,42 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 		push_generic_token(p, .Comma, 0)
 		visit_expr(p, v.constraints_string)
 		push_generic_token(p, .Close_Brace, 0)
-	case Undef:
+	case ^Undef:
 		push_generic_token(p, .Undef, 1)
-	case Auto_Cast:
+	case ^Auto_Cast:
 		push_generic_token(p, v.op.kind, 1)
 		visit_expr(p, v.expr)
-	case Ternary_If_Expr:
+	case ^Ternary_If_Expr:
 		visit_expr(p, v.x)
 		push_generic_token(p, v.op1.kind, 1)
 		visit_expr(p, v.cond)
 		push_generic_token(p, v.op2.kind, 1)
 		visit_expr(p, v.y)
-	case Ternary_When_Expr:
+	case ^Ternary_When_Expr:
 		visit_expr(p, v.x)
 		push_generic_token(p, v.op1.kind, 1)
 		visit_expr(p, v.cond)
 		push_generic_token(p, v.op2.kind, 1)
 		visit_expr(p, v.y)
-	case Or_Else_Expr:
+	case ^Or_Else_Expr:
 		visit_expr(p, v.x)
 		push_generic_token(p, v.token.kind, 1)
 		visit_expr(p, v.y)
-	case Or_Return_Expr:
+	case ^Or_Return_Expr:
 		visit_expr(p, v.expr)
 		push_generic_token(p, v.token.kind, 1)
-	case Selector_Call_Expr:
+	case ^Selector_Call_Expr:
 		visit_expr(p, v.call.expr)
 		push_generic_token(p, .Open_Paren, 1)
 		visit_exprs(p, v.call.args, {.Add_Comma})
 		push_generic_token(p, .Close_Paren, 0)
-	case Ellipsis:
+	case ^Ellipsis:
 		push_generic_token(p, .Ellipsis, 1)
 		visit_expr(p, v.expr)
-	case Relative_Type:
+	case ^Relative_Type:
 		visit_expr(p, v.tag)
 		visit_expr(p, v.type)
-	case Slice_Expr:
+	case ^Slice_Expr:
 		visit_expr(p, v.expr)
 		push_generic_token(p, .Open_Bracket, 0)
 		visit_expr(p, v.low)
@@ -981,37 +999,37 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 			visit_expr(p, v.high)
 		}
 		push_generic_token(p, .Close_Bracket, 0)
-	case Ident:
+	case ^Ident:
 		if .Enforce_Poly_Names in options {
 			push_generic_token(p, .Dollar, 1)
 			push_ident_token(p, v.name, 0)
 		} else {
 			push_ident_token(p, v.name, 1)
 		}
-	case Deref_Expr:
+	case ^Deref_Expr:
 		visit_expr(p, v.expr)
 		push_generic_token(p, v.op.kind, 0)
-	case Type_Cast:
+	case ^Type_Cast:
 		push_generic_token(p, v.tok.kind, 1)
 		push_generic_token(p, .Open_Paren, 0)
 		visit_expr(p, v.type)
 		push_generic_token(p, .Close_Paren, 0)
 		merge_next_token(p)
 		visit_expr(p, v.expr)
-	case Basic_Directive:
+	case ^Basic_Directive:
 		push_generic_token(p, v.tok.kind, 1)
 		push_ident_token(p, v.name, 0)
-	case Distinct_Type:
+	case ^Distinct_Type:
 		push_generic_token(p, .Distinct, 1)
 		visit_expr(p, v.type)
-	case Dynamic_Array_Type:
+	case ^Dynamic_Array_Type:
 		visit_expr(p, v.tag)
 		push_generic_token(p, .Open_Bracket, 1)
 		push_generic_token(p, .Dynamic, 0)
 		push_generic_token(p, .Close_Bracket, 0)
 		merge_next_token(p)
 		visit_expr(p, v.elem)
-	case Bit_Set_Type:
+	case ^Bit_Set_Type:
 		push_generic_token(p, .Bit_Set, 1)
 		push_generic_token(p, .Open_Bracket, 0)
 
@@ -1023,7 +1041,7 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 		}
 
 		push_generic_token(p, .Close_Bracket, 0)
-	case Union_Type:
+	case ^Union_Type:
 		push_generic_token(p, .Union, 1)
 
 		push_poly_params(p, v.poly_params)
@@ -1045,7 +1063,7 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 			visit_exprs(p, v.variants, {.Add_Comma, .Trailing})
 			visit_end_brace(p, v.end)
 		}
-	case Enum_Type:
+	case ^Enum_Type:
 		push_generic_token(p, .Enum, 1)
 
 		hint_current_line(p, {.Enum})
@@ -1068,7 +1086,7 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 		}
 
 		set_source_position(p, v.end)
-	case Struct_Type:
+	case ^Struct_Type:
 		push_generic_token(p, .Struct, 1)
 
 		hint_current_line(p, {.Struct})
@@ -1103,7 +1121,7 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 		}
 
 		set_source_position(p, v.end)
-	case Proc_Lit:
+	case ^Proc_Lit:
 		switch v.inlining {
 		case .None:
 		case .Inline:
@@ -1112,7 +1130,7 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 			push_ident_token(p, "#force_no_inline", 0)
 		}
 
-		visit_proc_type(p, v.type^, true)
+		visit_proc_type(p, v.type, true)
 
 		push_where_clauses(p, v.where_clauses)
 
@@ -1122,16 +1140,16 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 		} else {
 			push_generic_token(p, .Undef, 1)
 		}
-	case Proc_Type:
+	case ^Proc_Type:
 		visit_proc_type(p, v)
-	case Basic_Lit:
+	case ^Basic_Lit:
 		push_generic_token(p, v.tok.kind, 1, v.tok.text)
-	case Binary_Expr:
+	case ^Binary_Expr:
 		visit_binary_expr(p, v)
-	case Implicit_Selector_Expr:
+	case ^Implicit_Selector_Expr:
 		push_generic_token(p, .Period, 1)
 		push_ident_token(p, v.field.name, 0)
-	case Call_Expr:
+	case ^Call_Expr:
 		visit_expr(p, v.expr)
 
 		push_format_token(p,
@@ -1146,27 +1164,34 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 
 		visit_call_exprs(p, v.args, v.ellipsis.kind == .Ellipsis)
 		push_generic_token(p, .Close_Paren, 0)
-	case Typeid_Type:
+	case ^Typeid_Type:
 		push_generic_token(p, .Typeid, 1)
 
 		if v.specialization != nil {
 			push_generic_token(p, .Quo, 0)
 			visit_expr(p, v.specialization)
 		}
-	case Selector_Expr:
+	case ^Selector_Expr:
 		visit_expr(p, v.expr)
 		push_generic_token(p, v.op.kind, 0)
 		visit_expr(p, v.field)
-	case Paren_Expr:
+	case ^Paren_Expr:
 		push_generic_token(p, .Open_Paren, 1)
 		visit_expr(p, v.expr)
 		push_generic_token(p, .Close_Paren, 0)
-	case Index_Expr:
+	case ^Index_Expr:
 		visit_expr(p, v.expr)
 		push_generic_token(p, .Open_Bracket, 0)
 		visit_expr(p, v.index)
 		push_generic_token(p, .Close_Bracket, 0)
-	case Proc_Group:
+	case ^Matrix_Index_Expr:
+		visit_expr(p, v.expr)
+		push_generic_token(p, .Open_Bracket, 0)
+		visit_expr(p, v.row_index)
+		push_generic_token(p, .Comma, 0)
+		visit_expr(p, v.column_index)
+		push_generic_token(p, .Close_Bracket, 0)
+	case ^Proc_Group:
 		push_generic_token(p, v.tok.kind, 1)
 
 		if len(v.args) != 0 && v.pos.line != v.args[len(v.args) - 1].pos.line {
@@ -1181,7 +1206,7 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 			push_generic_token(p, .Close_Brace, 0)
 		}
 
-	case Comp_Lit:
+	case ^Comp_Lit:
 		if v.type != nil {
 			visit_expr(p, v.type)
 		}
@@ -1198,18 +1223,18 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 			push_generic_token(p, .Close_Brace, 0)
 		}
 
-	case Unary_Expr:
+	case ^Unary_Expr:
 		push_generic_token(p, v.op.kind, 1)
 		merge_next_token(p)
 		visit_expr(p, v.expr)
-	case Field_Value:
+	case ^Field_Value:
 		visit_expr(p, v.field)
 		push_generic_token(p, .Eq, 1)
 		visit_expr(p, v.value)
-	case Type_Assertion:
+	case ^Type_Assertion:
 		visit_expr(p, v.expr)
 
-		if unary, ok := v.type.derived.(Unary_Expr); ok && unary.op.text == "?" {
+		if unary, ok := v.type.derived.(^Unary_Expr); ok && unary.op.text == "?" {
 			push_generic_token(p, .Period, 0)
 			visit_expr(p, v.type)
 		} else {
@@ -1219,13 +1244,13 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 			push_generic_token(p, .Close_Paren, 0)
 		}
 
-	case Pointer_Type:
+	case ^Pointer_Type:
 		push_generic_token(p, .Pointer, 1)
 		merge_next_token(p)
 		visit_expr(p, v.elem)
-	case Implicit:
+	case ^Implicit:
 		push_generic_token(p, v.tok.kind, 1)
-	case Poly_Type:
+	case ^Poly_Type:
 		push_generic_token(p, .Dollar, 1)
 		merge_next_token(p)
 		visit_expr(p, v.type)
@@ -1235,22 +1260,35 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 			merge_next_token(p)
 			visit_expr(p, v.specialization)
 		}
-	case Array_Type:
+	case ^Array_Type:
 		visit_expr(p, v.tag)
 		push_generic_token(p, .Open_Bracket, 1)
 		visit_expr(p, v.len)
 		push_generic_token(p, .Close_Bracket, 0)
 		merge_next_token(p)
 		visit_expr(p, v.elem)
-	case Map_Type:
+	case ^Map_Type:
 		push_generic_token(p, .Map, 1)
 		push_generic_token(p, .Open_Bracket, 0)
 		visit_expr(p, v.key)
 		push_generic_token(p, .Close_Bracket, 0)
 		merge_next_token(p)
 		visit_expr(p, v.value)
-	case Helper_Type:
+	case ^Helper_Type:
 		visit_expr(p, v.type)
+	case ^Multi_Pointer_Type:
+		push_generic_token(p, .Open_Bracket, 1)
+		push_generic_token(p, .Pointer, 0)
+		push_generic_token(p, .Close_Bracket, 0)
+		visit_expr(p, v.elem)
+	case ^Matrix_Type:
+		push_generic_token(p, .Matrix, 1)
+		push_generic_token(p, .Open_Bracket, 0)
+		visit_expr(p, v.row_count)
+		push_generic_token(p, .Comma, 0)
+		visit_expr(p, v.column_count)
+		push_generic_token(p, .Close_Bracket, 0)
+		visit_expr(p, v.elem)
 	case:
 		panic(fmt.aprint(expr.derived))
 	}
@@ -1348,7 +1386,7 @@ visit_field_list :: proc(p: ^Printer, list: ^ast.Field_List, options := List_Opt
 	}
 }
 
-visit_proc_type :: proc(p: ^Printer, proc_type: ast.Proc_Type, is_proc_lit := false) {
+visit_proc_type :: proc(p: ^Printer, proc_type: ^ast.Proc_Type, is_proc_lit := false) {
 	if is_proc_lit {
 		push_format_token(p, Format_Token {
 			kind = .Proc,
@@ -1392,7 +1430,7 @@ visit_proc_type :: proc(p: ^Printer, proc_type: ast.Proc_Type, is_proc_lit := fa
 		} else if len(proc_type.results.list) == 1 {
 
 			for name in proc_type.results.list[0].names {
-				if ident, ok := name.derived.(ast.Ident); ok {
+				if ident, ok := name.derived.(^ast.Ident); ok {
 					if ident.name != "_" {
 						use_parens = true
 					}
@@ -1410,19 +1448,19 @@ visit_proc_type :: proc(p: ^Printer, proc_type: ast.Proc_Type, is_proc_lit := fa
 	}
 }
 
-visit_binary_expr :: proc(p: ^Printer, binary: ast.Binary_Expr) {
+visit_binary_expr :: proc(p: ^Printer, binary: ^ast.Binary_Expr) {
 	move_line(p, binary.left.pos)
 
-	if v, ok := binary.left.derived.(ast.Binary_Expr); ok {
+	if v, ok := binary.left.derived.(^ast.Binary_Expr); ok {
 		visit_binary_expr(p, v)
 	} else {
 		visit_expr(p, binary.left)
 	}
 
 	either_implicit_selector := false
-	if _, ok := binary.left.derived.(ast.Implicit_Selector_Expr); ok {
+	if _, ok := binary.left.derived.(^ast.Implicit_Selector_Expr); ok {
 		either_implicit_selector = true
-	} else if _, ok := binary.right.derived.(ast.Implicit_Selector_Expr); ok {
+	} else if _, ok := binary.right.derived.(^ast.Implicit_Selector_Expr); ok {
 		either_implicit_selector = true
 	}
 
@@ -1439,7 +1477,7 @@ visit_binary_expr :: proc(p: ^Printer, binary: ast.Binary_Expr) {
 	move_line(p, binary.right.pos)
 
 
-	if v, ok := binary.right.derived.(ast.Binary_Expr); ok {
+	if v, ok := binary.right.derived.(^ast.Binary_Expr); ok {
 		visit_binary_expr(p, v)
 	} else {
 		visit_expr(p, binary.right)
@@ -1499,7 +1537,7 @@ visit_signature_list :: proc(p: ^Printer, list: ^ast.Field_List, remove_blank :=
 		named := false
 
 		for name in field.names {
-			if ident, ok := name.derived.(ast.Ident); ok {
+			if ident, ok := name.derived.(^ast.Ident); ok {
 				//for some reason the parser uses _ to mean empty
 				if ident.name != "_" || !remove_blank {
 					named = true

+ 71 - 0
core/os/dir_openbsd.odin

@@ -0,0 +1,71 @@
+package os
+
+import "core:strings"
+import "core:mem"
+
+read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) {
+	dirp: Dir
+	dirp, err = _fdopendir(fd)
+	if err != ERROR_NONE {
+		return
+	}
+
+	defer _closedir(dirp)
+
+	// XXX OpenBSD
+	dirpath: string
+	dirpath, err = absolute_path_from_handle(fd)
+
+	if err != ERROR_NONE {
+		return
+	}
+
+	defer delete(dirpath)
+
+	n := n
+	size := n
+	if n <= 0 {
+		n = -1
+		size = 100
+	}
+
+	dfi := make([dynamic]File_Info, 0, size, allocator)
+
+	for {
+		entry: Dirent
+		end_of_stream: bool
+		entry, err, end_of_stream = _readdir(dirp)
+		if err != ERROR_NONE {
+			for fi_ in dfi {
+				file_info_delete(fi_, allocator)
+			}
+			delete(dfi)
+			return
+		} else if end_of_stream {
+			break
+		}
+
+		fi_: File_Info
+		filename := cast(string)(transmute(cstring)mem.Raw_Cstring{ data = &entry.name[0] })
+
+		if filename == "." || filename == ".." {
+			continue
+		}
+
+		fullpath := strings.join( []string{ dirpath, filename }, "/", context.temp_allocator)
+		defer delete(fullpath, context.temp_allocator)
+
+		fi_, err = stat(fullpath, allocator)
+		if err != ERROR_NONE {
+			for fi__ in dfi {
+				file_info_delete(fi__, allocator)
+			}
+			delete(dfi)
+			return
+		}
+
+		append(&dfi, fi_)
+	}
+
+	return dfi[:], ERROR_NONE
+}

+ 17 - 6
core/os/file_windows.odin

@@ -106,19 +106,23 @@ read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Errno) {
 	BUF_SIZE :: 386
 	buf16: [BUF_SIZE]u16
 	buf8: [4*BUF_SIZE]u8
-	
+
 	for n < len(b) && err == 0 {
-		max_read := u32(min(BUF_SIZE, len(b)/4))
-		
+		min_read := max(len(b)/4, 1 if len(b) > 0 else 0)
+		max_read := u32(min(BUF_SIZE, min_read))
+		if max_read == 0 {
+			break
+		}
+
 		single_read_length: u32
 		ok := win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil)
 		if !ok {
 			err = Errno(win32.GetLastError())
 		}
-		
+
 		buf8_len := utf16.decode_to_utf8(buf8[:], buf16[:single_read_length])
 		src := buf8[:buf8_len]
-		
+
 		ctrl_z := false
 		for i := 0; i < len(src) && n+i < len(b); i += 1 {
 			x := src[i]
@@ -129,9 +133,16 @@ read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Errno) {
 			b[n] = x
 			n += 1
 		}
-		if ctrl_z || single_read_length < len(buf16) {
+		if ctrl_z || single_read_length < max_read {
 			break
 		}
+
+		// NOTE(bill): if the last two values were a newline, then it is expected that
+		// this is the end of the input
+		if n >= 2 && single_read_length == max_read && string(b[n-2:n]) == "\r\n" {
+			break
+		}
+
 	}
 	
 	return

+ 12 - 2
core/os/os_darwin.odin

@@ -313,6 +313,7 @@ foreign libc {
 	@(link_name="getenv")   _unix_getenv   :: proc(cstring) -> cstring ---
 	@(link_name="getcwd")   _unix_getcwd   :: proc(buf: cstring, len: c.size_t) -> cstring ---
 	@(link_name="chdir")    _unix_chdir    :: proc(buf: cstring) -> c.int ---
+	@(link_name="mkdir")    _unix_mkdir    :: proc(buf: cstring, mode: u32) -> c.int ---
 	@(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr ---
 
 	@(link_name="strerror") _darwin_string_error :: proc(num : c.int) -> cstring ---
@@ -344,9 +345,8 @@ get_last_error_string :: proc() -> string {
 }
 
 open :: proc(path: string, flags: int = O_RDWR, mode: int = 0) -> (Handle, Errno) {
-	cstr := strings.clone_to_cstring(path)
+	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	handle := _unix_open(cstr, i32(flags), u16(mode))
-	delete(cstr)
 	if handle == -1 {
 		return INVALID_HANDLE, 1
 	}
@@ -670,7 +670,17 @@ set_current_directory :: proc(path: string) -> (err: Errno) {
 	return ERROR_NONE
 }
 
+make_directory :: proc(path: string, mode: u32 = 0o775) -> Errno {
+	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
+	res := _unix_mkdir(path_cstr, mode)
+	if res == -1 {
+		return Errno(get_last_error())
+	}
+	return ERROR_NONE
+}
+
 exit :: proc "contextless" (code: int) -> ! {
+	runtime._cleanup_runtime_contextless()
 	_unix_exit(i32(code))
 }
 

+ 281 - 287
core/os/os_freebsd.odin

@@ -7,149 +7,149 @@ import "core:runtime"
 import "core:strings"
 import "core:c"
 
-Handle :: distinct i32;
-File_Time :: distinct u64;
-Errno :: distinct i32;
-Syscall :: distinct i32;
-
-INVALID_HANDLE :: ~Handle(0);
-
-ERROR_NONE:      Errno : 0;
-EPERM:           Errno : 1;
-ENOENT:          Errno : 2;
-ESRCH:           Errno : 3;
-EINTR:           Errno : 4;
-EIO:             Errno : 5;
-ENXIO:           Errno : 6;
-E2BIG:           Errno : 7;
-ENOEXEC:         Errno : 8;
-EBADF:           Errno : 9;
-ECHILD:          Errno : 10;
-EBEADLK:         Errno : 11;
-ENOMEM:          Errno : 12;
-EACCESS:         Errno : 13;
-EFAULT:          Errno : 14;
-ENOTBLK:         Errno : 15;
-EBUSY:           Errno : 16;
-EEXIST:          Errno : 17;
-EXDEV:           Errno : 18;
-ENODEV:          Errno : 19;
-ENOTDIR:         Errno : 20;
-EISDIR:          Errno : 21;
-EINVAL:          Errno : 22;
-ENFILE:          Errno : 23;
-EMFILE:          Errno : 24;
-ENOTTY:          Errno : 25;
-ETXTBSY:         Errno : 26;
-EFBIG:           Errno : 27;
-ENOSPC:          Errno : 28;
-ESPIPE:          Errno : 29;
-EROFS:           Errno : 30;
-EMLINK:          Errno : 31;
-EPIPE:           Errno : 32;
-EDOM:            Errno : 33;
-ERANGE:          Errno : 34; /* Result too large */
-EAGAIN:          Errno : 35;
-EINPROGRESS:     Errno : 36;
-EALREADY:        Errno : 37;
-ENOTSOCK:        Errno : 38;
-EDESTADDRREQ:    Errno : 39;
-EMSGSIZE:        Errno : 40;
-EPROTOTYPE:      Errno : 41;
-ENOPROTOOPT:     Errno : 42;
-EPROTONOSUPPORT: Errno : 43;
-ESOCKTNOSUPPORT: Errno : 44;
-EOPNOTSUPP:      Errno : 45;
-EPFNOSUPPORT:    Errno : 46;
-EAFNOSUPPORT:    Errno : 47;
-EADDRINUSE:      Errno : 48;
-EADDRNOTAVAIL:   Errno : 49;
-ENETDOWN:        Errno : 50;
-ENETUNREACH:     Errno : 51;
-ENETRESET:       Errno : 52;
-ECONNABORTED:    Errno : 53;
-ECONNRESET:      Errno : 54;
-ENOBUFS:         Errno : 55;
-EISCONN:         Errno : 56;
-ENOTCONN:        Errno : 57;
-ESHUTDOWN:       Errno : 58;
-ETIMEDOUT:       Errno : 60;
-ECONNREFUSED:    Errno : 61;
-ELOOP:           Errno : 62;
-ENAMETOOLING:    Errno : 63;
-EHOSTDOWN:       Errno : 64;
-EHOSTUNREACH:    Errno : 65;
-ENOTEMPTY:       Errno : 66;
-EPROCLIM:        Errno : 67;
-EUSERS:          Errno : 68;
-EDQUOT:          Errno : 69;
-ESTALE:          Errno : 70;
-EBADRPC:         Errno : 72;
-ERPCMISMATCH:    Errno : 73;
-EPROGUNAVAIL:    Errno : 74;
-EPROGMISMATCH:   Errno : 75;
-EPROCUNAVAIL:    Errno : 76;
-ENOLCK:          Errno : 77;
-ENOSYS:          Errno : 78;
-EFTYPE:          Errno : 79;
-EAUTH:           Errno : 80;
-ENEEDAUTH:       Errno : 81;
-EIDRM:           Errno : 82;
-ENOMSG:          Errno : 83;
-EOVERFLOW:       Errno : 84;
-ECANCELED:       Errno : 85;
-EILSEQ:          Errno : 86;
-ENOATTR:         Errno : 87;
-EDOOFUS:         Errno : 88;
-EBADMSG:         Errno : 89;
-EMULTIHOP:       Errno : 90;
-ENOLINK:         Errno : 91;
-EPROTO:          Errno : 92;
-ENOTCAPABLE:     Errno : 93;
-ECAPMODE:        Errno : 94;
-ENOTRECOVERABLE: Errno : 95;
-EOWNERDEAD:      Errno : 96;
-
-O_RDONLY   :: 0x00000;
-O_WRONLY   :: 0x00001;
-O_RDWR     :: 0x00002;
-O_CREATE   :: 0x00040;
-O_EXCL     :: 0x00080;
-O_NOCTTY   :: 0x00100;
-O_TRUNC    :: 0x00200;
-O_NONBLOCK :: 0x00800;
-O_APPEND   :: 0x00400;
-O_SYNC     :: 0x01000;
-O_ASYNC    :: 0x02000;
-O_CLOEXEC  :: 0x80000;
-
-
-SEEK_SET   :: 0;
-SEEK_CUR   :: 1;
-SEEK_END   :: 2;
-SEEK_DATA  :: 3;
-SEEK_HOLE  :: 4;
-SEEK_MAX   :: SEEK_HOLE;
+Handle :: distinct i32
+File_Time :: distinct u64
+Errno :: distinct i32
+Syscall :: distinct i32
+
+INVALID_HANDLE :: ~Handle(0)
+
+ERROR_NONE:      Errno : 0
+EPERM:           Errno : 1
+ENOENT:          Errno : 2
+ESRCH:           Errno : 3
+EINTR:           Errno : 4
+EIO:             Errno : 5
+ENXIO:           Errno : 6
+E2BIG:           Errno : 7
+ENOEXEC:         Errno : 8
+EBADF:           Errno : 9
+ECHILD:          Errno : 10
+EBEADLK:         Errno : 11
+ENOMEM:          Errno : 12
+EACCESS:         Errno : 13
+EFAULT:          Errno : 14
+ENOTBLK:         Errno : 15
+EBUSY:           Errno : 16
+EEXIST:          Errno : 17
+EXDEV:           Errno : 18
+ENODEV:          Errno : 19
+ENOTDIR:         Errno : 20
+EISDIR:          Errno : 21
+EINVAL:          Errno : 22
+ENFILE:          Errno : 23
+EMFILE:          Errno : 24
+ENOTTY:          Errno : 25
+ETXTBSY:         Errno : 26
+EFBIG:           Errno : 27
+ENOSPC:          Errno : 28
+ESPIPE:          Errno : 29
+EROFS:           Errno : 30
+EMLINK:          Errno : 31
+EPIPE:           Errno : 32
+EDOM:            Errno : 33
+ERANGE:          Errno : 34 /* Result too large */
+EAGAIN:          Errno : 35
+EINPROGRESS:     Errno : 36
+EALREADY:        Errno : 37
+ENOTSOCK:        Errno : 38
+EDESTADDRREQ:    Errno : 39
+EMSGSIZE:        Errno : 40
+EPROTOTYPE:      Errno : 41
+ENOPROTOOPT:     Errno : 42
+EPROTONOSUPPORT: Errno : 43
+ESOCKTNOSUPPORT: Errno : 44
+EOPNOTSUPP:      Errno : 45
+EPFNOSUPPORT:    Errno : 46
+EAFNOSUPPORT:    Errno : 47
+EADDRINUSE:      Errno : 48
+EADDRNOTAVAIL:   Errno : 49
+ENETDOWN:        Errno : 50
+ENETUNREACH:     Errno : 51
+ENETRESET:       Errno : 52
+ECONNABORTED:    Errno : 53
+ECONNRESET:      Errno : 54
+ENOBUFS:         Errno : 55
+EISCONN:         Errno : 56
+ENOTCONN:        Errno : 57
+ESHUTDOWN:       Errno : 58
+ETIMEDOUT:       Errno : 60
+ECONNREFUSED:    Errno : 61
+ELOOP:           Errno : 62
+ENAMETOOLING:    Errno : 63
+EHOSTDOWN:       Errno : 64
+EHOSTUNREACH:    Errno : 65
+ENOTEMPTY:       Errno : 66
+EPROCLIM:        Errno : 67
+EUSERS:          Errno : 68
+EDQUOT:          Errno : 69
+ESTALE:          Errno : 70
+EBADRPC:         Errno : 72
+ERPCMISMATCH:    Errno : 73
+EPROGUNAVAIL:    Errno : 74
+EPROGMISMATCH:   Errno : 75
+EPROCUNAVAIL:    Errno : 76
+ENOLCK:          Errno : 77
+ENOSYS:          Errno : 78
+EFTYPE:          Errno : 79
+EAUTH:           Errno : 80
+ENEEDAUTH:       Errno : 81
+EIDRM:           Errno : 82
+ENOMSG:          Errno : 83
+EOVERFLOW:       Errno : 84
+ECANCELED:       Errno : 85
+EILSEQ:          Errno : 86
+ENOATTR:         Errno : 87
+EDOOFUS:         Errno : 88
+EBADMSG:         Errno : 89
+EMULTIHOP:       Errno : 90
+ENOLINK:         Errno : 91
+EPROTO:          Errno : 92
+ENOTCAPABLE:     Errno : 93
+ECAPMODE:        Errno : 94
+ENOTRECOVERABLE: Errno : 95
+EOWNERDEAD:      Errno : 96
+
+O_RDONLY   :: 0x00000
+O_WRONLY   :: 0x00001
+O_RDWR     :: 0x00002
+O_CREATE   :: 0x00040
+O_EXCL     :: 0x00080
+O_NOCTTY   :: 0x00100
+O_TRUNC    :: 0x00200
+O_NONBLOCK :: 0x00800
+O_APPEND   :: 0x00400
+O_SYNC     :: 0x01000
+O_ASYNC    :: 0x02000
+O_CLOEXEC  :: 0x80000
+
+
+SEEK_SET   :: 0
+SEEK_CUR   :: 1
+SEEK_END   :: 2
+SEEK_DATA  :: 3
+SEEK_HOLE  :: 4
+SEEK_MAX   :: SEEK_HOLE
 
 // NOTE: These are OS specific!
 // Do not mix these up!
-RTLD_LAZY         :: 0x001;
-RTLD_NOW          :: 0x002;
-//RTLD_BINDING_MASK :: 0x3; // Called MODEMASK in dlfcn.h
-RTLD_GLOBAL       :: 0x100;
-RTLD_LOCAL        :: 0x000;
-RTLD_TRACE        :: 0x200;
-RTLD_NODELETE     :: 0x01000;
-RTLD_NOLOAD       :: 0x02000;
+RTLD_LAZY         :: 0x001
+RTLD_NOW          :: 0x002
+//RTLD_BINDING_MASK :: 0x3 // Called MODEMASK in dlfcn.h
+RTLD_GLOBAL       :: 0x100
+RTLD_LOCAL        :: 0x000
+RTLD_TRACE        :: 0x200
+RTLD_NODELETE     :: 0x01000
+RTLD_NOLOAD       :: 0x02000
 
-args := _alloc_command_line_arguments();
+args := _alloc_command_line_arguments()
 
 Unix_File_Time :: struct {
 	seconds: i64,
 	nanoseconds: c.long,
 }
 
-pid_t :: u32;
+pid_t :: u32
 
 OS_Stat :: struct {
 	device_id: u64,
@@ -177,297 +177,291 @@ OS_Stat :: struct {
 }
 
 // File type
-S_IFMT   :: 0o170000; // Type of file mask
-S_IFIFO  :: 0o010000; // Named pipe (fifo)
-S_IFCHR  :: 0o020000; // Character special
-S_IFDIR  :: 0o040000; // Directory
-S_IFBLK  :: 0o060000; // Block special
-S_IFREG  :: 0o100000; // Regular
-S_IFLNK  :: 0o120000; // Symbolic link
-S_IFSOCK :: 0o140000; // Socket
-//S_ISVTX  :: 0o001000; // Save swapped text even after use
+S_IFMT   :: 0o170000 // Type of file mask
+S_IFIFO  :: 0o010000 // Named pipe (fifo)
+S_IFCHR  :: 0o020000 // Character special
+S_IFDIR  :: 0o040000 // Directory
+S_IFBLK  :: 0o060000 // Block special
+S_IFREG  :: 0o100000 // Regular
+S_IFLNK  :: 0o120000 // Symbolic link
+S_IFSOCK :: 0o140000 // Socket
+//S_ISVTX  :: 0o001000 // Save swapped text even after use
 
 // File mode
 // Read, write, execute/search by owner
-S_IRWXU :: 0o0700; // RWX mask for owner
-S_IRUSR :: 0o0400; // R for owner
-S_IWUSR :: 0o0200; // W for owner
-S_IXUSR :: 0o0100; // X for owner
+S_IRWXU :: 0o0700 // RWX mask for owner
+S_IRUSR :: 0o0400 // R for owner
+S_IWUSR :: 0o0200 // W for owner
+S_IXUSR :: 0o0100 // X for owner
 
 	// 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
+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
-S_IRWXO :: 0o0007; // RWX mask for other
-S_IROTH :: 0o0004; // R for other
-S_IWOTH :: 0o0002; // W for other
-S_IXOTH :: 0o0001; // X for other
+S_IRWXO :: 0o0007 // RWX mask for other
+S_IROTH :: 0o0004 // R for other
+S_IWOTH :: 0o0002 // W for other
+S_IXOTH :: 0o0001 // X for other
 
-S_ISUID :: 0o4000; // Set user id on execution
-S_ISGID :: 0o2000; // Set group id on execution
-S_ISVTX :: 0o1000; // Directory restrcted delete
+S_ISUID :: 0o4000 // Set user id on execution
+S_ISGID :: 0o2000 // Set group id on execution
+S_ISVTX :: 0o1000 // Directory restrcted delete
 
 
-S_ISLNK  :: #force_inline proc(m: u32) -> bool do return (m & S_IFMT) == S_IFLNK;
-S_ISREG  :: #force_inline proc(m: u32) -> bool do return (m & S_IFMT) == S_IFREG;
-S_ISDIR  :: #force_inline proc(m: u32) -> bool do return (m & S_IFMT) == S_IFDIR;
-S_ISCHR  :: #force_inline proc(m: u32) -> bool do return (m & S_IFMT) == S_IFCHR;
-S_ISBLK  :: #force_inline proc(m: u32) -> bool do return (m & S_IFMT) == S_IFBLK;
-S_ISFIFO :: #force_inline proc(m: u32) -> bool do return (m & S_IFMT) == S_IFIFO;
-S_ISSOCK :: #force_inline proc(m: u32) -> bool do return (m & S_IFMT) == S_IFSOCK;
+S_ISLNK  :: #force_inline proc(m: u32) -> bool do return (m & S_IFMT) == S_IFLNK
+S_ISREG  :: #force_inline proc(m: u32) -> bool do return (m & S_IFMT) == S_IFREG
+S_ISDIR  :: #force_inline proc(m: u32) -> bool do return (m & S_IFMT) == S_IFDIR
+S_ISCHR  :: #force_inline proc(m: u32) -> bool do return (m & S_IFMT) == S_IFCHR
+S_ISBLK  :: #force_inline proc(m: u32) -> bool do return (m & S_IFMT) == S_IFBLK
+S_ISFIFO :: #force_inline proc(m: u32) -> bool do return (m & S_IFMT) == S_IFIFO
+S_ISSOCK :: #force_inline proc(m: u32) -> bool do return (m & S_IFMT) == S_IFSOCK
 
-F_OK :: 0; // Test for file existance
-X_OK :: 1; // Test for execute permission
-W_OK :: 2; // Test for write permission
-R_OK :: 4; // Test for read permission
+F_OK :: 0 // Test for file existance
+X_OK :: 1 // Test for execute permission
+W_OK :: 2 // Test for write permission
+R_OK :: 4 // Test for read permission
 
 foreign libc {
-	@(link_name="__error") __errno_location :: proc() -> ^int ---;
-	@(link_name="syscall")          syscall          :: proc(number: Syscall, #c_vararg args: ..any) -> int ---;
-
-	@(link_name="open")             _unix_open       :: proc(path: cstring, flags: c.int, mode: c.int) -> Handle ---;
-	@(link_name="close")            _unix_close         :: proc(fd: Handle) -> c.int ---;
-	@(link_name="read")             _unix_read          :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t ---;
-	@(link_name="write")            _unix_write         :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t ---;
-	@(link_name="lseek64")          _unix_seek          :: proc(fd: Handle, offset: i64, whence: c.int) -> i64 ---;
-	@(link_name="gettid")           _unix_gettid        :: proc() -> u64 ---;
-	@(link_name="getpagesize")      _unix_getpagesize   :: proc() -> c.int ---;
-	@(link_name="stat64")           _unix_stat          :: proc(path: cstring, stat: ^OS_Stat) -> c.int ---;
-	@(link_name="fstat")            _unix_fstat         :: proc(fd: Handle, stat: ^OS_Stat) -> c.int ---;
-	@(link_name="access")           _unix_access        :: proc(path: cstring, mask: c.int) -> c.int ---;
-
-	@(link_name="malloc")           _unix_malloc        :: proc(size: c.size_t) -> rawptr ---;
-	@(link_name="calloc")           _unix_calloc        :: proc(num, size: c.size_t) -> rawptr ---;
-	@(link_name="free")             _unix_free          :: proc(ptr: rawptr) ---;
-	@(link_name="realloc")          _unix_realloc       :: proc(ptr: rawptr, size: c.size_t) -> rawptr ---;
-	@(link_name="getenv")           _unix_getenv        :: proc(cstring) -> cstring ---;
-	@(link_name="getcwd")           _unix_getcwd        :: proc(buf: cstring, len: c.size_t) -> cstring ---;
-	@(link_name="chdir")            _unix_chdir         :: proc(buf: cstring) -> c.int ---;
-
-	@(link_name="exit")             _unix_exit          :: proc(status: c.int) -> ! ---;
+	@(link_name="__error") __errno_location :: proc() -> ^int ---
+	@(link_name="syscall")          syscall          :: proc(number: Syscall, #c_vararg args: ..any) -> int ---
+
+	@(link_name="open")             _unix_open       :: proc(path: cstring, flags: c.int, mode: c.int) -> Handle ---
+	@(link_name="close")            _unix_close         :: proc(fd: Handle) -> c.int ---
+	@(link_name="read")             _unix_read          :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t ---
+	@(link_name="write")            _unix_write         :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t ---
+	@(link_name="lseek64")          _unix_seek          :: proc(fd: Handle, offset: i64, whence: c.int) -> i64 ---
+	@(link_name="gettid")           _unix_gettid        :: proc() -> u64 ---
+	@(link_name="getpagesize")      _unix_getpagesize   :: proc() -> c.int ---
+	@(link_name="stat64")           _unix_stat          :: proc(path: cstring, stat: ^OS_Stat) -> c.int ---
+	@(link_name="fstat")            _unix_fstat         :: proc(fd: Handle, stat: ^OS_Stat) -> c.int ---
+	@(link_name="access")           _unix_access        :: proc(path: cstring, mask: c.int) -> c.int ---
+
+	@(link_name="malloc")           _unix_malloc        :: proc(size: c.size_t) -> rawptr ---
+	@(link_name="calloc")           _unix_calloc        :: proc(num, size: c.size_t) -> rawptr ---
+	@(link_name="free")             _unix_free          :: proc(ptr: rawptr) ---
+	@(link_name="realloc")          _unix_realloc       :: proc(ptr: rawptr, size: c.size_t) -> rawptr ---
+	@(link_name="getenv")           _unix_getenv        :: proc(cstring) -> cstring ---
+	@(link_name="getcwd")           _unix_getcwd        :: proc(buf: cstring, len: c.size_t) -> cstring ---
+	@(link_name="chdir")            _unix_chdir         :: proc(buf: cstring) -> c.int ---
+
+	@(link_name="exit")             _unix_exit          :: proc(status: c.int) -> ! ---
 }
 foreign dl {
-	@(link_name="dlopen")           _unix_dlopen        :: proc(filename: cstring, flags: c.int) -> rawptr ---;
-	@(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="dlopen")           _unix_dlopen        :: proc(filename: cstring, flags: c.int) -> rawptr ---
+	@(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="pthread_getthreadid_np") pthread_getthreadid_np :: proc() -> c.int ---;
+	@(link_name="pthread_getthreadid_np") pthread_getthreadid_np :: proc() -> c.int ---
 }
 
 is_path_separator :: proc(r: rune) -> bool {
-	return r == '/';
+	return r == '/'
 }
 
 get_last_error :: proc() -> int {
-	return __errno_location()^;
+	return __errno_location()^
 }
 
 open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
-	cstr := strings.clone_to_cstring(path);
-	handle := _unix_open(cstr, c.int(flags), c.int(mode));
-	delete(cstr);
+	cstr := strings.clone_to_cstring(path, context.temp_allocator)
+	handle := _unix_open(cstr, c.int(flags), c.int(mode))
 	if handle == -1 {
-		return INVALID_HANDLE, Errno(get_last_error());
+		return INVALID_HANDLE, Errno(get_last_error())
 	}
-	return handle, ERROR_NONE;
+	return handle, ERROR_NONE
 }
 
 close :: proc(fd: Handle) -> Errno {
-	result := _unix_close(fd);
+	result := _unix_close(fd)
 	if result == -1 {
-		return Errno(get_last_error());
+		return Errno(get_last_error())
 	}
-	return ERROR_NONE;
+	return ERROR_NONE
 }
 
 read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
-	bytes_read := _unix_read(fd, &data[0], c.size_t(len(data)));
+	bytes_read := _unix_read(fd, &data[0], c.size_t(len(data)))
 	if bytes_read == -1 {
-		return -1, Errno(get_last_error());
+		return -1, Errno(get_last_error())
 	}
-	return int(bytes_read), ERROR_NONE;
+	return int(bytes_read), ERROR_NONE
 }
 
 write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
 	if len(data) == 0 {
-		return 0, ERROR_NONE;
+		return 0, ERROR_NONE
 	}
-	bytes_written := _unix_write(fd, &data[0], c.size_t(len(data)));
+	bytes_written := _unix_write(fd, &data[0], c.size_t(len(data)))
 	if bytes_written == -1 {
-		return -1, Errno(get_last_error());
+		return -1, Errno(get_last_error())
 	}
-	return int(bytes_written), ERROR_NONE;
+	return int(bytes_written), ERROR_NONE
 }
 
 seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
-	res := _unix_seek(fd, offset, c.int(whence));
+	res := _unix_seek(fd, offset, c.int(whence))
 	if res == -1 {
-		return -1, Errno(get_last_error());
+		return -1, Errno(get_last_error())
 	}
-	return res, ERROR_NONE;
+	return res, ERROR_NONE
 }
 
 file_size :: proc(fd: Handle) -> (i64, Errno) {
-	s, err := fstat(fd);
+	s, err := fstat(fd)
 	if err != ERROR_NONE {
-		return -1, err;
+		return -1, err
 	}
-	return s.size, ERROR_NONE;
+	return s.size, ERROR_NONE
 }
 
-stdin: Handle = 0;
-stdout: Handle = 1;
-stderr: Handle = 2;
+stdin: Handle = 0
+stdout: Handle = 1
+stderr: Handle = 2
 
 last_write_time :: proc(fd: Handle) -> (File_Time, Errno) {
-	s, err := fstat(fd);
+	s, err := fstat(fd)
 	if err != ERROR_NONE {
-		return 0, err;
+		return 0, err
 	}
-	modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds;
-	return File_Time(modified), ERROR_NONE;
+	modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
+	return File_Time(modified), ERROR_NONE
 }
 
 last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
-	s, err := stat(name);
+	s, err := stat(name)
 	if err != ERROR_NONE {
-		return 0, err;
+		return 0, err
 	}
-	modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds;
-	return File_Time(modified), ERROR_NONE;
+	modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
+	return File_Time(modified), ERROR_NONE
 }
 
 stat :: proc(path: string) -> (OS_Stat, Errno) {
-	cstr := strings.clone_to_cstring(path);
-	defer delete(cstr);
-
-	s: OS_Stat;
-	result := _unix_stat(cstr, &s);
+	cstr := strings.clone_to_cstring(path, context.temp_allocator)
+	s: OS_Stat
+	result := _unix_stat(cstr, &s)
 	if result == -1 {
-		return s, Errno(get_last_error());
+		return s, Errno(get_last_error())
 	}
-	return s, ERROR_NONE;
+	return s, ERROR_NONE
 }
 
 fstat :: proc(fd: Handle) -> (OS_Stat, Errno) {
-	s: OS_Stat;
-	result := _unix_fstat(fd, &s);
+	s: OS_Stat
+	result := _unix_fstat(fd, &s)
 	if result == -1 {
-		return s, Errno(get_last_error());
+		return s, Errno(get_last_error())
 	}
-	return s, ERROR_NONE;
+	return s, ERROR_NONE
 }
 
 access :: proc(path: string, mask: int) -> (bool, Errno) {
-	cstr := strings.clone_to_cstring(path);
-	defer delete(cstr);
-	result := _unix_access(cstr, c.int(mask));
+	cstr := strings.clone_to_cstring(path, context.temp_allocator)
+	result := _unix_access(cstr, c.int(mask))
 	if result == -1 {
-		return false, Errno(get_last_error());
+		return false, Errno(get_last_error())
 	}
-	return true, ERROR_NONE;
+	return true, ERROR_NONE
 }
 
 heap_alloc :: proc(size: int) -> rawptr {
-	assert(size >= 0);
-	return _unix_calloc(1, c.size_t(size));
+	assert(size >= 0)
+	return _unix_calloc(1, c.size_t(size))
 }
 
 heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
 	// NOTE: _unix_realloc doesn't guarantee new memory will be zeroed on
 	// POSIX platforms. Ensure your caller takes this into account.
-	return _unix_realloc(ptr, c.size_t(new_size));
+	return _unix_realloc(ptr, c.size_t(new_size))
 }
 
 heap_free :: proc(ptr: rawptr) {
-	_unix_free(ptr);
+	_unix_free(ptr)
 }
 
 getenv :: proc(name: string) -> (string, bool) {
-	path_str := strings.clone_to_cstring(name);
-	defer delete(path_str);
-	cstr := _unix_getenv(path_str);
+	path_str := strings.clone_to_cstring(name, context.temp_allocator)
+	cstr := _unix_getenv(path_str)
 	if cstr == nil {
-		return "", false;
+		return "", false
 	}
-	return string(cstr), true;
+	return string(cstr), true
 }
 
 get_current_directory :: proc() -> string {
 	// NOTE(tetra): I would use PATH_MAX here, but I was not able to find
 	// an authoritative value for it across all systems.
 	// The largest value I could find was 4096, so might as well use the page size.
-	page_size := get_page_size();
-	buf := make([dynamic]u8, page_size);
+	page_size := get_page_size()
+	buf := make([dynamic]u8, page_size)
 	#no_bounds_check for {
-		cwd := _unix_getcwd(cstring(&buf[0]), c.size_t(len(buf)));
+		cwd := _unix_getcwd(cstring(&buf[0]), c.size_t(len(buf)))
 		if cwd != nil {
-			return string(cwd);
+			return string(cwd)
 		}
 		if Errno(get_last_error()) != ERANGE {
-			return "";
+			return ""
 		}
-		resize(&buf, len(buf)+page_size);
+		resize(&buf, len(buf)+page_size)
 	}
-	unreachable();
+	unreachable()
 }
 
 set_current_directory :: proc(path: string) -> (err: Errno) {
-	cstr := strings.clone_to_cstring(path, context.temp_allocator);
-	res := _unix_chdir(cstr);
-	if res == -1 do return Errno(get_last_error());
-	return ERROR_NONE;
+	cstr := strings.clone_to_cstring(path, context.temp_allocator)
+	res := _unix_chdir(cstr)
+	if res == -1 do return Errno(get_last_error())
+	return ERROR_NONE
 }
 
 exit :: proc "contextless" (code: int) -> ! {
-	_unix_exit(c.int(code));
+	runtime._cleanup_runtime_contextless()
+	_unix_exit(c.int(code))
 }
 
 current_thread_id :: proc "contextless" () -> int {
-	return cast(int) pthread_getthreadid_np();
+	return cast(int) pthread_getthreadid_np()
 }
 
 dlopen :: proc(filename: string, flags: int) -> rawptr {
-	cstr := strings.clone_to_cstring(filename);
-	defer delete(cstr);
-	handle := _unix_dlopen(cstr, c.int(flags));
-	return handle;
+	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);
-	cstr := strings.clone_to_cstring(symbol);
-	defer delete(cstr);
-	proc_handle := _unix_dlsym(handle, cstr);
-	return proc_handle;
+	assert(handle != nil)
+	cstr := strings.clone_to_cstring(symbol, context.temp_allocator)
+	proc_handle := _unix_dlsym(handle, cstr)
+	return proc_handle
 }
 dlclose :: proc(handle: rawptr) -> bool {
-	assert(handle != nil);
-	return _unix_dlclose(handle) == 0;
+	assert(handle != nil)
+	return _unix_dlclose(handle) == 0
 }
 dlerror :: proc() -> string {
-	return string(_unix_dlerror());
+	return string(_unix_dlerror())
 }
 
 get_page_size :: proc() -> int {
 	// NOTE(tetra): The page size never changes, so why do anything complicated
 	// if we don't have to.
-	@static page_size := -1;
-	if page_size != -1 do return page_size;
+	@static page_size := -1
+	if page_size != -1 do return page_size
 
-	page_size = int(_unix_getpagesize());
-	return page_size;
+	page_size = int(_unix_getpagesize())
+	return page_size
 }
 
 
 _alloc_command_line_arguments :: proc() -> []string {
-	res := make([]string, len(runtime.args__));
+	res := make([]string, len(runtime.args__))
 	for arg, i in runtime.args__ {
-		res[i] = string(arg);
+		res[i] = string(arg)
 	}
-	return res;
+	return res
 }
 

+ 17 - 23
core/os/os_linux.odin

@@ -460,9 +460,8 @@ fork :: proc() -> (Pid, Errno) {
 }
 
 open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
-	cstr := strings.clone_to_cstring(path)
+	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	handle := _unix_open(cstr, flags, mode)
-	defer delete(cstr)
 	if handle < 0 {
 		return INVALID_HANDLE, _get_errno(int(handle))
 	}
@@ -501,11 +500,13 @@ seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
 }
 
 file_size :: proc(fd: Handle) -> (i64, Errno) {
-	s, err := _fstat(fd)
-	if err != ERROR_NONE {
-		return 0, err
-	}
-	return max(s.size, 0), ERROR_NONE
+    // 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
 }
 
 rename :: proc(old_path, new_path: string) -> Errno {
@@ -608,8 +609,7 @@ last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
 
 @private
 _stat :: proc(path: string) -> (OS_Stat, Errno) {
-	cstr := strings.clone_to_cstring(path)
-	defer delete(cstr)
+	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
 	// deliberately uninitialized; the syscall fills this buffer for us
 	s: OS_Stat = ---
@@ -622,8 +622,7 @@ _stat :: proc(path: string) -> (OS_Stat, Errno) {
 
 @private
 _lstat :: proc(path: string) -> (OS_Stat, Errno) {
-	cstr := strings.clone_to_cstring(path)
-	defer delete(cstr)
+	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
 	// deliberately uninitialized; the syscall fills this buffer for us
 	s: OS_Stat = ---
@@ -690,8 +689,7 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool)
 
 @private
 _readlink :: proc(path: string) -> (string, Errno) {
-	path_cstr := strings.clone_to_cstring(path)
-	defer delete(path_cstr)
+	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
 	bufsz : uint = 256
 	buf := make([]byte, bufsz)
@@ -727,8 +725,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
 		rel = "."
 	}
 
-	rel_cstr := strings.clone_to_cstring(rel)
-	defer delete(rel_cstr)
+	rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator)
 
 	path_ptr := _unix_realpath(rel_cstr, nil)
 	if path_ptr == nil {
@@ -743,8 +740,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
 }
 
 access :: proc(path: string, mask: int) -> (bool, Errno) {
-	cstr := strings.clone_to_cstring(path)
-	defer delete(cstr)
+	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	result := _unix_access(cstr, mask)
 	if result < 0 {
 		return false, _get_errno(result)
@@ -768,8 +764,7 @@ heap_free :: proc(ptr: rawptr) {
 }
 
 getenv :: proc(name: string) -> (string, bool) {
-	path_str := strings.clone_to_cstring(name)
-	defer delete(path_str)
+	path_str := strings.clone_to_cstring(name, context.temp_allocator)
 	cstr := _unix_getenv(path_str)
 	if cstr == nil {
 		return "", false
@@ -807,6 +802,7 @@ set_current_directory :: proc(path: string) -> (err: Errno) {
 }
 
 exit :: proc "contextless" (code: int) -> ! {
+	runtime._cleanup_runtime_contextless()
 	_unix_exit(c.int(code))
 }
 
@@ -815,15 +811,13 @@ current_thread_id :: proc "contextless" () -> int {
 }
 
 dlopen :: proc(filename: string, flags: int) -> rawptr {
-	cstr := strings.clone_to_cstring(filename)
-	defer delete(cstr)
+	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)
-	cstr := strings.clone_to_cstring(symbol)
-	defer delete(cstr)
+	cstr := strings.clone_to_cstring(symbol, context.temp_allocator)
 	proc_handle := _unix_dlsym(handle, cstr)
 	return proc_handle
 }

+ 707 - 0
core/os/os_openbsd.odin

@@ -0,0 +1,707 @@
+package os
+
+foreign import libc "system:c"
+
+import "core:strings"
+import "core:c"
+import "core:runtime"
+
+Handle    :: distinct i32
+Pid       :: distinct i32
+File_Time :: distinct u64
+Errno     :: distinct i32
+
+INVALID_HANDLE :: ~Handle(0)
+
+ERROR_NONE:	Errno: 0
+
+EPERM:		Errno: 1
+ENOENT:		Errno: 2
+ESRCH:		Errno: 3
+EINTR:		Errno: 4
+EIO:		Errno: 5
+ENXIO:		Errno: 6
+E2BIG:		Errno: 7
+ENOEXEC:	Errno: 8
+EBADF:		Errno: 9
+ECHILD:		Errno: 10
+EDEADLK:	Errno: 11
+ENOMEM:		Errno: 12
+EACCES:		Errno: 13
+EFAULT:		Errno: 14
+ENOTBLK:	Errno: 15
+EBUSY:		Errno: 16
+EEXIST:		Errno: 17
+EXDEV:		Errno: 18
+ENODEV:		Errno: 19
+ENOTDIR:	Errno: 20
+EISDIR:		Errno: 21
+EINVAL:		Errno: 22
+ENFILE:		Errno: 23
+EMFILE:		Errno: 24
+ENOTTY:		Errno: 25
+ETXTBSY:	Errno: 26
+EFBIG:		Errno: 27
+ENOSPC:		Errno: 28
+ESPIPE:		Errno: 29
+EROFS:		Errno: 30
+EMLINK:		Errno: 31
+EPIPE:		Errno: 32
+EDOM:		Errno: 33
+ERANGE:		Errno: 34
+EAGAIN:		Errno: 35
+EWOULDBLOCK:	Errno: EAGAIN
+EINPROGRESS:	Errno: 36
+EALREADY:	Errno: 37
+ENOTSOCK:	Errno: 38
+EDESTADDRREQ:	Errno: 39
+EMSGSIZE:	Errno: 40
+EPROTOTYPE:	Errno: 41
+ENOPROTOOPT:	Errno: 42
+EPROTONOSUPPORT: Errno: 43
+ESOCKTNOSUPPORT: Errno: 44
+EOPNOTSUPP:	Errno: 45
+EPFNOSUPPORT:	Errno: 46
+EAFNOSUPPORT:	Errno: 47
+EADDRINUSE:	Errno: 48
+EADDRNOTAVAIL:	Errno: 49
+ENETDOWN:	Errno: 50
+ENETUNREACH:	Errno: 51
+ENETRESET:	Errno: 52
+ECONNABORTED:	Errno: 53
+ECONNRESET:	Errno: 54
+ENOBUFS:	Errno: 55
+EISCONN:	Errno: 56
+ENOTCONN:	Errno: 57
+ESHUTDOWN:	Errno: 58
+ETOOMANYREFS:	Errno: 59
+ETIMEDOUT:	Errno: 60
+ECONNREFUSED:	Errno: 61
+ELOOP:		Errno: 62
+ENAMETOOLONG:	Errno: 63
+EHOSTDOWN:	Errno: 64
+EHOSTUNREACH:	Errno: 65
+ENOTEMPTY:	Errno: 66
+EPROCLIM:	Errno: 67
+EUSERS:		Errno: 68
+EDQUOT:		Errno: 69
+ESTALE:		Errno: 70
+EREMOTE:	Errno: 71
+EBADRPC:	Errno: 72
+ERPCMISMATCH:	Errno: 73
+EPROGUNAVAIL:	Errno: 74
+EPROGMISMATCH:	Errno: 75
+EPROCUNAVAIL:	Errno: 76
+ENOLCK:		Errno: 77
+ENOSYS:		Errno: 78
+EFTYPE:		Errno: 79
+EAUTH:		Errno: 80
+ENEEDAUTH:	Errno: 81
+EIPSEC:		Errno: 82
+ENOATTR:	Errno: 83
+EILSEQ:		Errno: 84
+ENOMEDIUM:	Errno: 85
+EMEDIUMTYPE:	Errno: 86
+EOVERFLOW:	Errno: 87
+ECANCELED:	Errno: 88
+EIDRM:		Errno: 89
+ENOMSG:		Errno: 90
+ENOTSUP:	Errno: 91
+EBADMSG:	Errno: 92
+ENOTRECOVERABLE: Errno: 93
+EOWNERDEAD:	Errno: 94
+EPROTO:		Errno: 95
+
+O_RDONLY   :: 0x00000
+O_WRONLY   :: 0x00001
+O_RDWR     :: 0x00002
+O_NONBLOCK :: 0x00004
+O_APPEND   :: 0x00008
+O_ASYNC    :: 0x00040
+O_SYNC     :: 0x00080
+O_CREATE   :: 0x00200
+O_TRUNC    :: 0x00400
+O_EXCL     :: 0x00800
+O_NOCTTY   :: 0x08000
+O_CLOEXEC  :: 0x10000
+
+SEEK_SET :: 0
+SEEK_CUR :: 1
+SEEK_END :: 2
+
+RTLD_LAZY     :: 0x001
+RTLD_NOW      :: 0x002
+RTLD_LOCAL    :: 0x000
+RTLD_GLOBAL   :: 0x100
+RTLD_TRACE    :: 0x200
+RTLD_NODELETE :: 0x400
+
+MAX_PATH :: 1024
+
+// "Argv" arguments converted to Odin strings
+args := _alloc_command_line_arguments()
+
+pid_t     :: i32
+time_t    :: i64
+mode_t    :: u32
+dev_t     :: i32
+ino_t     :: u64
+nlink_t   :: u32
+uid_t     :: u32
+gid_t     :: u32
+off_t     :: i64
+blkcnt_t  :: u64
+blksize_t :: i32
+
+Unix_File_Time :: struct {
+	seconds:     time_t,
+	nanoseconds: c.long,
+}
+
+OS_Stat :: struct {
+	mode: mode_t,			// inode protection mode
+	device_id: dev_t,		// inode's device
+	serial: ino_t,			// inode's number
+	nlink: nlink_t,			// number of hard links
+	uid: uid_t,			// user ID of the file's owner
+	gid: gid_t,			// group ID of the file's group
+	rdev: dev_t,			// device type
+
+	last_access: Unix_File_Time,	// time of last access
+	modified: Unix_File_Time,	// time of last data modification
+	status_change: Unix_File_Time,	// time of last file status change
+
+	size: off_t,			// file size, in bytes
+	blocks: blkcnt_t,		// blocks allocated for file
+	block_size:	blksize_t,	// optimal blocksize for I/O
+
+	flags:		u32,		// user defined flags for file
+	gen:		u32,		// file generation number
+	birthtime:	Unix_File_Time,	// time of file creation
+}
+
+MAXNAMLEN :: 255
+
+// NOTE(laleksic, 2021-01-21): Comment and rename these to match OS_Stat above
+Dirent :: struct {
+	ino:      ino_t,	// file number of entry
+	off:      off_t,	// offset after this entry
+	reclen:   u16,		// length of this record
+	type:     u8,		// file type
+	namlen:   u8,		// length of string in name
+	_padding: [4]u8,
+	name:     [MAXNAMLEN + 1]byte, // name
+}
+
+Dir :: distinct rawptr // DIR*
+
+// File type
+S_IFMT   :: 0o170000 // Type of file mask
+S_IFIFO  :: 0o010000 // Named pipe (fifo)
+S_IFCHR  :: 0o020000 // Character special
+S_IFDIR  :: 0o040000 // Directory
+S_IFBLK  :: 0o060000 // Block special
+S_IFREG  :: 0o100000 // Regular
+S_IFLNK  :: 0o120000 // Symbolic link
+S_IFSOCK :: 0o140000 // Socket
+S_ISVTX  :: 0o001000 // Save swapped text even after use
+
+// File mode
+	// Read, write, execute/search by owner
+S_IRWXU :: 0o0700 // RWX mask for owner
+S_IRUSR :: 0o0400 // R for owner
+S_IWUSR :: 0o0200 // W for owner
+S_IXUSR :: 0o0100 // X for owner
+
+	// 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
+S_IRWXO :: 0o0007 // RWX mask for other
+S_IROTH :: 0o0004 // R for other
+S_IWOTH :: 0o0002 // W for other
+S_IXOTH :: 0o0001 // X for other
+
+S_ISUID :: 0o4000 // Set user id on execution
+S_ISGID :: 0o2000 // Set group id on execution
+S_ISTXT :: 0o1000 // Sticky bit
+
+S_ISLNK  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK  }
+S_ISREG  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG  }
+S_ISDIR  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR  }
+S_ISCHR  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR  }
+S_ISBLK  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK  }
+S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO  }
+S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK }
+
+F_OK :: 0x00 // Test for file existance
+X_OK :: 0x01 // Test for execute permission
+W_OK :: 0x02 // Test for write permission
+R_OK :: 0x04 // Test for read permission
+
+AT_FDCWD            :: -100
+AT_EACCESS          :: 0x01
+AT_SYMLINK_NOFOLLOW :: 0x02
+AT_SYMLINK_FOLLOW   :: 0x04
+AT_REMOVEDIR        :: 0x08
+
+@(default_calling_convention="c")
+foreign libc {
+	@(link_name="__errno")	__errno		:: proc() -> ^int ---
+
+	@(link_name="fork")	_unix_fork	:: proc() -> pid_t ---
+	@(link_name="getthrid")	_unix_getthrid	:: proc() -> int ---
+
+	@(link_name="open")	_unix_open	:: proc(path: cstring, flags: c.int, mode: c.int) -> Handle ---
+	@(link_name="close")	_unix_close	:: proc(fd: Handle) -> c.int ---
+	@(link_name="read")	_unix_read	:: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t ---
+	@(link_name="write")	_unix_write	:: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t ---
+	@(link_name="lseek")	_unix_seek	:: proc(fd: Handle, offset: off_t, whence: c.int) -> off_t ---
+	@(link_name="stat")	_unix_stat	:: proc(path: cstring, sb: ^OS_Stat) -> c.int ---
+	@(link_name="fstat")	_unix_fstat	:: proc(fd: Handle, sb: ^OS_Stat) -> c.int ---
+	@(link_name="lstat")	_unix_lstat	:: proc(path: cstring, sb: ^OS_Stat) -> c.int ---
+	@(link_name="readlink")	_unix_readlink	:: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t ---
+	@(link_name="access")	_unix_access	:: proc(path: cstring, mask: c.int) -> c.int ---
+	@(link_name="getcwd")	_unix_getcwd	:: proc(buf: cstring, len: c.size_t) -> cstring ---
+	@(link_name="chdir")	_unix_chdir	:: proc(path: cstring) -> c.int ---
+	@(link_name="rename")	_unix_rename	:: proc(old, new: cstring) -> c.int ---
+	@(link_name="unlink")	_unix_unlink	:: proc(path: cstring) -> c.int ---
+	@(link_name="rmdir")	_unix_rmdir	:: proc(path: cstring) -> c.int ---
+	@(link_name="mkdir")	_unix_mkdir	:: proc(path: cstring, mode: mode_t) -> c.int ---
+
+	@(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int ---
+	@(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir ---
+	@(link_name="closedir")	_unix_closedir	:: proc(dirp: Dir) -> c.int ---
+	@(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) ---
+	@(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int ---
+
+	@(link_name="malloc")	_unix_malloc	:: proc(size: c.size_t) -> rawptr ---
+	@(link_name="calloc")	_unix_calloc	:: proc(num, size: c.size_t) -> rawptr ---
+	@(link_name="free")	_unix_free	:: proc(ptr: rawptr) ---
+	@(link_name="realloc")	_unix_realloc	:: proc(ptr: rawptr, size: c.size_t) -> rawptr ---
+
+	@(link_name="getenv")	_unix_getenv	:: proc(cstring) -> cstring ---
+	@(link_name="realpath")	_unix_realpath	:: proc(path: cstring, resolved_path: rawptr) -> rawptr ---
+
+	@(link_name="exit")	_unix_exit	:: proc(status: c.int) -> ! ---
+
+	@(link_name="dlopen")	_unix_dlopen	:: proc(filename: cstring, flags: c.int) -> rawptr ---
+	@(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 ---
+}
+
+is_path_separator :: proc(r: rune) -> bool {
+	return r == '/'
+}
+
+get_last_error :: proc() -> int {
+	return __errno()^
+}
+
+fork :: proc() -> (Pid, Errno) {
+	pid := _unix_fork()
+	if pid == -1 {
+		return Pid(-1), Errno(get_last_error())
+	}
+	return Pid(pid), ERROR_NONE
+}
+
+open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
+	cstr := strings.clone_to_cstring(path, context.temp_allocator)
+	handle := _unix_open(cstr, c.int(flags), c.int(mode))
+	if handle == -1 {
+		return INVALID_HANDLE, Errno(get_last_error())
+	}
+	return handle, ERROR_NONE
+}
+
+close :: proc(fd: Handle) -> Errno {
+	result := _unix_close(fd)
+	if result == -1 {
+		return Errno(get_last_error())
+	}
+	return ERROR_NONE
+}
+
+read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+	bytes_read := _unix_read(fd, &data[0], c.size_t(len(data)))
+	if bytes_read == -1 {
+		return -1, Errno(get_last_error())
+	}
+	return int(bytes_read), ERROR_NONE
+}
+
+write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+	if len(data) == 0 {
+		return 0, ERROR_NONE
+	}
+	bytes_written := _unix_write(fd, &data[0], c.size_t(len(data)))
+	if bytes_written == -1 {
+		return -1, Errno(get_last_error())
+	}
+	return int(bytes_written), ERROR_NONE
+}
+
+seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
+	res := _unix_seek(fd, offset, c.int(whence))
+	if res == -1 {
+		return -1, Errno(get_last_error())
+	}
+	return res, ERROR_NONE
+}
+
+file_size :: proc(fd: Handle) -> (i64, Errno) {
+	s, err := _fstat(fd)
+	if err != ERROR_NONE {
+		return -1, err
+	}
+	return s.size, ERROR_NONE
+}
+
+rename :: proc(old_path, new_path: string) -> Errno {
+	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)
+	if res == -1 {
+		return Errno(get_last_error())
+	}
+	return ERROR_NONE
+}
+
+remove :: proc(path: string) -> Errno {
+	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
+	res := _unix_unlink(path_cstr)
+	if res == -1 {
+		return Errno(get_last_error())
+	}
+	return ERROR_NONE
+}
+
+make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno {
+	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
+	res := _unix_mkdir(path_cstr, mode)
+	if res == -1 {
+		return Errno(get_last_error())
+	}
+	return ERROR_NONE
+}
+
+remove_directory :: proc(path: string) -> Errno {
+	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
+	res := _unix_rmdir(path_cstr)
+	if res == -1 {
+		return Errno(get_last_error())
+	}
+	return ERROR_NONE
+}
+
+is_file_handle :: proc(fd: Handle) -> bool {
+	s, err := _fstat(fd)
+	if err != ERROR_NONE {
+		return false
+	}
+	return S_ISREG(s.mode)
+}
+
+is_file_path :: proc(path: string, follow_links: bool = true) -> bool {
+	s: OS_Stat
+	err: Errno
+	if follow_links {
+		s, err = _stat(path)
+	} else {
+		s, err = _lstat(path)
+	}
+	if err != ERROR_NONE {
+		return false
+	}
+	return S_ISREG(s.mode)
+}
+
+is_dir_handle :: proc(fd: Handle) -> bool {
+	s, err := _fstat(fd)
+	if err != ERROR_NONE {
+		return false
+	}
+	return S_ISDIR(s.mode)
+}
+
+is_dir_path :: proc(path: string, follow_links: bool = true) -> bool {
+	s: OS_Stat
+	err: Errno
+	if follow_links {
+		s, err = _stat(path)
+	} else {
+		s, err = _lstat(path)
+	}
+	if err != ERROR_NONE {
+		return false
+	}
+	return S_ISDIR(s.mode)
+}
+
+is_file :: proc {is_file_path, is_file_handle}
+is_dir :: proc {is_dir_path, is_dir_handle}
+
+// NOTE(bill): Uses startup to initialize it
+
+stdin:  Handle = 0
+stdout: Handle = 1
+stderr: Handle = 2
+
+/* TODO(zangent): Implement these!                                                                                
+last_write_time :: proc(fd: Handle) -> File_Time {}                                                               
+last_write_time_by_name :: proc(name: string) -> File_Time {}                                                     
+*/
+last_write_time :: proc(fd: Handle) -> (File_Time, Errno) {
+	s, err := _fstat(fd)
+	if err != ERROR_NONE {
+		return 0, err
+	}
+	modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
+	return File_Time(modified), ERROR_NONE
+}
+
+last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
+	s, err := _stat(name)
+	if err != ERROR_NONE {
+		return 0, err
+	}
+	modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
+	return File_Time(modified), ERROR_NONE
+}
+
+@private
+_stat :: proc(path: string) -> (OS_Stat, Errno) {
+	cstr := strings.clone_to_cstring(path, context.temp_allocator)
+
+	// deliberately uninitialized
+	s: OS_Stat = ---
+	res := _unix_stat(cstr, &s)
+	if res == -1 {
+		return s, Errno(get_last_error())
+	}
+	return s, ERROR_NONE
+}
+
+@private
+_lstat :: proc(path: string) -> (OS_Stat, Errno) {
+	cstr := strings.clone_to_cstring(path, context.temp_allocator)
+
+	// deliberately uninitialized
+	s: OS_Stat = ---
+	res := _unix_lstat(cstr, &s)
+	if res == -1 {
+		return s, Errno(get_last_error())
+	}
+	return s, ERROR_NONE
+}
+
+@private
+_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) {
+	// deliberately uninitialized
+	s: OS_Stat = ---
+	res := _unix_fstat(fd, &s)
+	if res == -1 {
+		return s, Errno(get_last_error())
+	}
+	return s, ERROR_NONE
+}
+
+@private
+_fdopendir :: proc(fd: Handle) -> (Dir, Errno) {
+	dirp := _unix_fdopendir(fd)
+	if dirp == cast(Dir)nil {
+		return nil, Errno(get_last_error())
+	}
+	return dirp, ERROR_NONE
+}
+
+@private
+_closedir :: proc(dirp: Dir) -> Errno {
+	rc := _unix_closedir(dirp)
+	if rc != 0 {
+		return Errno(get_last_error())
+	}
+	return ERROR_NONE
+}
+
+@private
+_rewinddir :: proc(dirp: Dir) {
+	_unix_rewinddir(dirp)
+}
+
+@private
+_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) {
+	result: ^Dirent
+	rc := _unix_readdir_r(dirp, &entry, &result)
+
+	if rc != 0 {
+		err = Errno(get_last_error())
+		return
+	}
+	err = ERROR_NONE
+
+	if result == nil {
+		end_of_stream = true
+		return
+	}
+
+	return
+}
+
+@private
+_readlink :: proc(path: string) -> (string, Errno) {
+	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
+
+	bufsz : uint = MAX_PATH
+	buf := make([]byte, MAX_PATH)
+	for {
+		rc := _unix_readlink(path_cstr, &(buf[0]), bufsz)
+		if rc == -1 {
+			delete(buf)
+			return "", Errno(get_last_error())
+		} else if rc == int(bufsz) {
+			bufsz += MAX_PATH
+			delete(buf)
+			buf = make([]byte, bufsz)
+		} else {
+			return strings.string_from_ptr(&buf[0], rc), ERROR_NONE
+		}	
+	}
+	unreachable()
+}
+
+// XXX OpenBSD
+absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) {
+	return "", Errno(ENOSYS)
+}
+
+absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
+	rel := rel
+	if rel == "" {
+		rel = "."
+	}
+
+	rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator)
+
+	path_ptr := _unix_realpath(rel_cstr, nil)
+	if path_ptr == nil {
+		return "", Errno(get_last_error())
+	}
+	defer _unix_free(path_ptr)
+
+	path_cstr := transmute(cstring)path_ptr
+	path = strings.clone( string(path_cstr) )
+
+	return path, ERROR_NONE
+}
+
+access :: proc(path: string, mask: int) -> (bool, Errno) {
+	cstr := strings.clone_to_cstring(path, context.temp_allocator)
+	res := _unix_access(cstr, c.int(mask))
+	if res == -1 {
+		return false, Errno(get_last_error())
+	}
+	return true, ERROR_NONE
+}
+
+heap_alloc :: proc(size: int) -> rawptr {
+	assert(size >= 0)
+	return _unix_calloc(1, c.size_t(size))
+}
+
+heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
+	// NOTE: _unix_realloc doesn't guarantee new memory will be zeroed on
+	// POSIX platforms. Ensure your caller takes this into account.
+	return _unix_realloc(ptr, c.size_t(new_size))
+}
+
+heap_free :: proc(ptr: rawptr) {
+	_unix_free(ptr)
+}
+
+getenv :: proc(name: string) -> (string, bool) {
+	path_str := strings.clone_to_cstring(name, context.temp_allocator)
+	cstr := _unix_getenv(path_str)
+	if cstr == nil {
+		return "", false
+	}
+	return string(cstr), true
+}
+
+get_current_directory :: proc() -> string {
+	buf := make([dynamic]u8, MAX_PATH)
+	for {
+		cwd := _unix_getcwd(cstring(raw_data(buf)), c.size_t(len(buf)))
+		if cwd != nil {
+			return string(cwd)
+		}
+		if Errno(get_last_error()) != ERANGE {
+			return ""
+		}
+		resize(&buf, len(buf) + MAX_PATH)
+	}
+	unreachable()
+}
+
+set_current_directory :: proc(path: string) -> (err: Errno) {
+	cstr := strings.clone_to_cstring(path, context.temp_allocator)
+	res := _unix_chdir(cstr)
+	if res == -1 {
+		return Errno(get_last_error())
+	}
+	return ERROR_NONE
+}
+
+exit :: proc "contextless" (code: int) -> ! {
+	runtime._cleanup_runtime_contextless()
+	_unix_exit(c.int(code))
+}
+
+current_thread_id :: proc "contextless" () -> int {
+	return _unix_getthrid()
+}
+
+dlopen :: proc(filename: string, flags: int) -> rawptr {
+	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)
+	cstr := strings.clone_to_cstring(symbol, context.temp_allocator)
+	proc_handle := _unix_dlsym(handle, cstr)
+	return proc_handle
+}
+dlclose :: proc(handle: rawptr) -> bool {
+	assert(handle != nil)
+	return _unix_dlclose(handle) == 0
+}
+dlerror :: proc() -> string {
+	return string(_unix_dlerror())
+}
+
+get_page_size :: proc() -> int {
+	// NOTE(tetra): The page size never changes, so why do anything complicated
+	// if we don't have to.
+	@static page_size := -1
+	if page_size != -1 {
+		return page_size
+	}
+
+	page_size = int(_unix_getpagesize())
+	return page_size
+}
+
+
+_alloc_command_line_arguments :: proc() -> []string {
+	res := make([]string, len(runtime.args__))
+	for arg, i in runtime.args__ {
+		res[i] = string(arg)
+	}
+	return res
+}

+ 2 - 0
core/os/os_wasi.odin

@@ -1,6 +1,7 @@
 package os
 
 import "core:sys/wasm/wasi"
+import "core:runtime"
 
 Handle :: distinct i32
 Errno :: distinct i32
@@ -93,5 +94,6 @@ heap_free :: proc(ptr: rawptr) {
 
 
 exit :: proc "contextless" (code: int) -> ! {
+	runtime._cleanup_runtime_contextless()
 	wasi.proc_exit(wasi.exitcode_t(code))
 }

+ 2 - 0
core/os/os_windows.odin

@@ -2,6 +2,7 @@
 package os
 
 import win32 "core:sys/windows"
+import "core:runtime"
 
 Handle    :: distinct uintptr
 File_Time :: distinct u64
@@ -128,6 +129,7 @@ get_page_size :: proc() -> int {
 
 
 exit :: proc "contextless" (code: int) -> ! {
+	runtime._cleanup_runtime_contextless()
 	win32.ExitProcess(win32.DWORD(code))
 }
 

+ 1 - 1
core/os/stat_unix.odin

@@ -1,4 +1,4 @@
-//+build linux, darwin, freebsd
+//+build linux, darwin, freebsd, openbsd
 package os
 
 import "core:time"

+ 10 - 33
core/os/stat_windows.odin

@@ -132,26 +132,11 @@ cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 {
 
 @(private)
 cleanpath_from_handle :: proc(fd: Handle) -> (string, Errno) {
-	if fd == 0 {
-		return "", ERROR_INVALID_HANDLE
-	}
-	h := win32.HANDLE(fd)
-
-	MAX_PATH := win32.DWORD(260) + 1
-	buf: []u16
-	for {
-		buf = make([]u16, MAX_PATH, context.temp_allocator)
-		err := win32.GetFinalPathNameByHandleW(h, raw_data(buf), MAX_PATH, 0)
-		switch Errno(err) {
-		case ERROR_PATH_NOT_FOUND, ERROR_INVALID_PARAMETER:
-			return "", Errno(err)
-		case ERROR_NOT_ENOUGH_MEMORY:
-			MAX_PATH = MAX_PATH*2 + 1
-			continue
-		}
-		break
+	buf, err := cleanpath_from_handle_u16(fd)
+	if err != 0 {
+		return "", err
 	}
-	return cleanpath_from_buf(buf), ERROR_NONE
+	return win32.utf16_to_utf8(buf, context.allocator), err
 }
 @(private)
 cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Errno) {
@@ -160,21 +145,13 @@ cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Errno) {
 	}
 	h := win32.HANDLE(fd)
 
-	MAX_PATH := win32.DWORD(260) + 1
-	buf: []u16
-	for {
-		buf = make([]u16, MAX_PATH, context.temp_allocator)
-		err := win32.GetFinalPathNameByHandleW(h, raw_data(buf), MAX_PATH, 0)
-		switch Errno(err) {
-		case ERROR_PATH_NOT_FOUND, ERROR_INVALID_PARAMETER:
-			return nil, Errno(err)
-		case ERROR_NOT_ENOUGH_MEMORY:
-			MAX_PATH = MAX_PATH*2 + 1
-			continue
-		}
-		break
+	n := win32.GetFinalPathNameByHandleW(h, nil, 0, 0)
+	if n == 0 {
+		return nil, Errno(win32.GetLastError())
 	}
-	return cleanpath_strip_prefix(buf), ERROR_NONE
+	buf := make([]u16, max(n, win32.DWORD(260))+1, context.temp_allocator)
+	buf_len := win32.GetFinalPathNameByHandleW(h, raw_data(buf), n, 0)
+	return buf[:buf_len], ERROR_NONE
 }
 @(private)
 cleanpath_from_buf :: proc(buf: []u16) -> string {

+ 3 - 2
core/path/filepath/path.odin

@@ -284,13 +284,14 @@ rel :: proc(base_path, target_path: string, allocator := context.allocator) -> (
 }
 
 dir :: proc(path: string, allocator := context.allocator) -> string {
+        context.allocator = allocator
 	vol := volume_name(path)
 	i := len(path) - 1
 	for i >= len(vol) && !is_separator(path[i]) {
 		i -= 1
 	}
-	dir := clean(path[len(vol) : i+1], allocator)
-	defer delete(dir, allocator)
+	dir := clean(path[len(vol) : i+1])
+	defer delete(dir)
 	if dir == "." && len(vol) > 2 {
 		return strings.clone(vol)
 	}

+ 6 - 1
core/path/filepath/path_unix.odin

@@ -1,4 +1,4 @@
-//+build linux, darwin, freebsd
+//+build linux, darwin, freebsd, openbsd
 package filepath
 
 when ODIN_OS == .Darwin {
@@ -59,6 +59,11 @@ when ODIN_OS == .Darwin {
 	foreign libc {
 		@(link_name="__error")          __error :: proc() -> ^i32 ---
 	}
+} else when ODIN_OS == .OpenBSD {
+	@(private)
+	foreign libc {
+		@(link_name="__errno")		__error :: proc() -> ^i32 ---
+	}
 } else {
 	@(private)
 	foreign libc {

+ 13 - 1
core/reflect/reflect.odin

@@ -365,6 +365,19 @@ index :: proc(val: any, i: int, loc := #caller_location) -> any {
 	return nil
 }
 
+deref :: proc(val: any) -> any {
+	if val != nil {
+		ti := type_info_base(type_info_of(val.id))
+		if info, ok := ti.variant.(Type_Info_Pointer); ok {
+			return any{
+				(^rawptr)(val.data)^,
+				info.elem.id,
+			}
+		}
+	}
+	return val
+}
+
 
 
 // Struct_Tag represents the type of the string of a struct field
@@ -680,7 +693,6 @@ union_variant_typeid :: proc(a: any) -> typeid {
 		return nil
 	}
 	panic("expected a union to reflect.union_variant_typeid")
-
 }
 
 get_union_variant_raw_tag :: proc(a: any) -> i64 {

+ 4 - 4
core/reflect/types.odin

@@ -334,11 +334,11 @@ is_relative_slice :: proc(info: ^Type_Info) -> bool {
 
 
 
-write_typeid_builder :: proc(buf: ^strings.Builder, id: typeid) {
-	write_type(buf, type_info_of(id))
+write_typeid_builder :: proc(buf: ^strings.Builder, id: typeid, n_written: ^int = nil) -> (n: int, err: io.Error) {
+	return write_type_writer(strings.to_writer(buf), type_info_of(id))
 }
-write_typeid_writer :: proc(writer: io.Writer, id: typeid) {
-	write_type(writer, type_info_of(id))
+write_typeid_writer :: proc(writer: io.Writer, id: typeid, n_written: ^int = nil) -> (n: int, err: io.Error) {
+	return write_type_writer(writer, type_info_of(id), n_written)
 }
 
 write_typeid :: proc{

+ 5 - 1
core/runtime/core.odin

@@ -351,7 +351,6 @@ Context :: struct {
 	assertion_failure_proc: Assertion_Failure_Proc,
 	logger:                 Logger,
 
-	user_data:  any,
 	user_ptr:   rawptr,
 	user_index: int,
 
@@ -460,6 +459,11 @@ _cleanup_runtime :: proc() {
 	default_temp_allocator_destroy(&global_default_temp_allocator_data)
 }
 
+_cleanup_runtime_contextless :: proc "contextless" () {
+	context = default_context()
+	_cleanup_runtime()
+}
+
 
 /////////////////////////////
 /////////////////////////////

+ 1 - 1
core/runtime/entry_unix.odin

@@ -1,5 +1,5 @@
 //+private
-//+build linux, darwin, freebsd
+//+build linux, darwin, freebsd, openbsd
 package runtime
 
 import "core:intrinsics"

+ 96 - 5
core/slice/slice.odin

@@ -304,7 +304,7 @@ filter :: proc(s: $S/[]$U, f: proc(U) -> bool, allocator := context.allocator) -
 	return r[:]
 }
 
-scanner :: proc (s: $S/[]$U, initializer: $V, f: proc(V, U)->V, allocator := context.allocator) -> []V {
+scanner :: proc (s: $S/[]$U, initializer: $V, f: proc(V, U) -> V, allocator := context.allocator) -> []V {
 	if len(s) == 0 { return {} }
 
 	res := make([]V, len(s), allocator)
@@ -344,15 +344,106 @@ max :: proc(s: $S/[]$T) -> (res: T, ok: bool) where intrinsics.type_is_ordered(T
 	return
 }
 
+min_max :: proc(s: $S/[]$T) -> (min, max: T, ok: bool) where intrinsics.type_is_ordered(T) {
+	if len(s) != 0 {
+		min, max = s[0], s[0]
+		ok = true
+		for v in s[1:] {
+			min = builtin.min(min, v)
+			max = builtin.max(max, v)
+		}
+	}
+	return
+}
+
+any_of :: proc(s: $S/[]$T, value: T) -> bool where intrinsics.type_is_comparable(T) {
+	for v in s {
+		if v == value {
+			return true
+		}
+	}
+	return false
+}
+
+none_of :: proc(s: $S/[]$T, value: T) -> bool where intrinsics.type_is_comparable(T) {
+	for v in s {
+		if v == value {
+			return false
+		}
+	}
+	return true
+}
+
+all_of :: proc(s: $S/[]$T, value: T) -> bool where intrinsics.type_is_comparable(T) {
+	if len(s) == 0 {
+		return false
+	}
+	for v in s {
+		if v != value {
+			return false
+		}
+	}
+	return true
+}
+
 
-dot_product :: proc(a, b: $S/[]$T) -> T
+any_of_proc :: proc(s: $S/[]$T, f: proc(T) -> bool) -> bool {
+	for v in s {
+		if f(v) {
+			return true
+		}
+	}
+	return false
+}
+
+none_of_proc :: proc(s: $S/[]$T, f: proc(T) -> bool) -> bool {
+	for v in s {
+		if f(v) {
+			return false
+		}
+	}
+	return true
+}
+
+all_of_proc :: proc(s: $S/[]$T, f: proc(T) -> bool) -> bool {
+	if len(s) == 0 {
+		return false
+	}
+	for v in s {
+		if !f(v) {
+			return false
+		}
+	}
+	return true
+}
+
+
+count :: proc(s: $S/[]$T, value: T) -> (n: int) where intrinsics.type_is_comparable(T) {
+	for v in s {
+		if v == value {
+			n += 1
+		}
+	}
+	return
+}
+
+count_proc :: proc(s: $S/[]$T, f: proc(T) -> bool) -> (n: int) {
+	for v in s {
+		if f(v) {
+			n += 1
+		}
+	}
+	return
+}
+
+
+dot_product :: proc(a, b: $S/[]$T) -> (r: T, ok: bool)
 	where intrinsics.type_is_numeric(T) {
 	if len(a) != len(b) {
-		panic("slice.dot_product: slices of unequal length")
+		return
 	}
-	r: T
 	#no_bounds_check for _, i in a {
 		r += a[i] * b[i]
 	}
-	return r
+	return r, true
 }

+ 1 - 0
core/strconv/strconv.odin

@@ -895,6 +895,7 @@ unquote_string :: proc(lit: string, allocator := context.allocator) -> (res: str
 	if s == `""` {
 		return "", false, true
 	}
+	s = s[1:len(s)-1]
 
 	if contains_rune(s, '\n') >= 0 {
 		return s, false, false

+ 19 - 0
core/strings/strings.odin

@@ -280,10 +280,29 @@ _split :: proc(s_, sep: string, sep_save, n_: int, allocator := context.allocato
 	return res[:i+1]
 }
 
+/*
+	Splits a string into parts, based on a separator.
+	Returned strings are substrings of 's'.
+	```
+	s := "aaa.bbb.ccc.ddd.eee" // 5 parts
+	ss := split(s, ".")
+	fmt.println(ss)            // [aaa, bbb, ccc, ddd, eee]
+	```
+*/
 split :: proc(s, sep: string, allocator := context.allocator) -> []string {
 	return _split(s, sep, 0, -1, allocator)
 }
 
+/*
+	Splits a string into a total of 'n' parts, based on a separator.
+	Returns fewer parts if there wasn't enough occurrences of the separator.
+	Returned strings are substrings of 's'.
+	```
+	s := "aaa.bbb.ccc.ddd.eee" // 5 parts present
+	ss := split_n(s, ".", 3)   // total of 3 wanted
+	fmt.println(ss)            // [aaa, bbb, ccc.ddd.eee]
+	```
+*/
 split_n :: proc(s, sep: string, n: int, allocator := context.allocator) -> []string {
 	return _split(s, sep, 0, n, allocator)
 }

+ 1 - 1
core/sync/channel_unix.odin

@@ -1,4 +1,4 @@
-// +build linux, darwin, freebsd
+// +build linux, darwin, freebsd, openbsd
 package sync
 
 import "core:time"

+ 78 - 0
core/sync/sync2/futex_openbsd.odin

@@ -0,0 +1,78 @@
+//+private
+//+build openbsd
+package sync2
+
+import "core:c"
+import "core:os"
+import "core:time"
+
+FUTEX_WAIT :: 1
+FUTEX_WAKE :: 2
+
+FUTEX_PRIVATE_FLAG :: 128
+
+FUTEX_WAIT_PRIVATE :: (FUTEX_WAIT | FUTEX_PRIVATE_FLAG)
+FUTEX_WAKE_PRIVATE :: (FUTEX_WAKE | FUTEX_PRIVATE_FLAG)
+
+foreign import libc "system:c"
+
+foreign libc {
+	@(link_name="futex")
+	_unix_futex :: proc "c" (f: ^Futex, op: c.int, val: u32, timeout: rawptr) -> c.int ---
+}
+
+_futex_wait :: proc(f: ^Futex, expected: u32) -> bool {
+	res := _unix_futex(f, FUTEX_WAIT_PRIVATE, expected, nil)
+
+	if res != -1 {
+		return true
+	}
+
+	if os.Errno(os.get_last_error()) == os.ETIMEDOUT {
+		return false
+	}
+
+	panic("futex_wait failure")
+}
+
+_futex_wait_with_timeout :: proc(f: ^Futex, expected: u32, duration: time.Duration) -> bool {
+	if duration <= 0 {
+		return false
+	}
+
+	timespec_t :: struct {
+		tv_sec:  c.long,
+		tv_nsec: c.long,
+        }
+
+	res := _unix_futex(f, FUTEX_WAIT_PRIVATE, expected, &timespec_t{
+		tv_sec  = (c.long)(duration/1e9),
+		tv_nsec = (c.long)(duration%1e9),
+	})
+
+	if res != -1 {
+		return true
+	}
+
+	if os.Errno(os.get_last_error()) == os.ETIMEDOUT {
+		return false
+	}
+
+	panic("futex_wait_with_timeout failure")
+}
+
+_futex_signal :: proc(f: ^Futex) {
+	res := _unix_futex(f, FUTEX_WAKE_PRIVATE, 1, nil)
+
+	if res == -1 {
+		panic("futex_wake_single failure")
+	}
+}
+
+_futex_broadcast :: proc(f: ^Futex)  {
+	res := _unix_futex(f, FUTEX_WAKE_PRIVATE, u32(max(i32)), nil)
+
+	if res == -1 {
+		panic("_futex_wake_all failure")
+	}
+}

+ 9 - 0
core/sync/sync2/primitives_openbsd.odin

@@ -0,0 +1,9 @@
+//+build openbsd
+//+private
+package sync2
+
+import "core:os"
+
+_current_thread_id :: proc "contextless" () -> int {
+	return os.current_thread_id()
+}

+ 1 - 1
core/sync/sync2/primitives_pthreads.odin

@@ -1,4 +1,4 @@
-//+build linux, freebsd
+//+build linux, freebsd, openbsd
 //+private
 package sync2
 

+ 36 - 0
core/sync/sync_openbsd.odin

@@ -0,0 +1,36 @@
+package sync
+
+import "core:sys/unix"
+import "core:os"
+
+current_thread_id :: proc "contextless" () -> int {
+	return os.current_thread_id()
+}
+
+// The Darwin docs say it best:
+// A semaphore is much like a lock, except that a finite number of threads can hold it simultaneously.
+// Semaphores can be thought of as being much like piles of tokens; multiple threads can take these tokens, 
+// but when there are none left, a thread must wait until another thread returns one.
+Semaphore :: struct #align 16 {
+	handle: unix.sem_t,
+}
+
+semaphore_init :: proc(s: ^Semaphore, initial_count := 0) {
+	assert(unix.sem_init(&s.handle, 0, u32(initial_count)) == 0)
+}
+
+semaphore_destroy :: proc(s: ^Semaphore) {
+	assert(unix.sem_destroy(&s.handle) == 0)
+	s.handle = {}
+}
+
+semaphore_post :: proc(s: ^Semaphore, count := 1) {
+    // NOTE: SPEED: If there's one syscall to do this, we should use it instead of the loop.
+    for in 0..<count {
+	    assert(unix.sem_post(&s.handle) == 0)
+    }
+}
+
+semaphore_wait_for :: proc(s: ^Semaphore) {
+	assert(unix.sem_wait(&s.handle) == 0)
+}

+ 1 - 1
core/sync/sync_unix.odin

@@ -1,4 +1,4 @@
-// +build linux, darwin, freebsd
+// +build linux, darwin, freebsd, openbsd
 package sync
 
 import "core:sys/unix"

+ 10 - 20
core/sys/darwin/xnu_system_call_helpers.odin

@@ -91,8 +91,7 @@ sys_open :: proc(path: string, oflag: Open_Flags, mode: Permission) -> (c.int, b
 	
 	cmode: u32 = 0
 	cflags: u32 = 0
-	cpath: cstring = strings.clone_to_cstring(path)
-	defer delete(cpath)
+	cpath: cstring = strings.clone_to_cstring(path, context.temp_allocator)
 
 	cflags = _sys_permission_mode(mode)
 
@@ -124,39 +123,32 @@ sys_open :: proc(path: string, oflag: Open_Flags, mode: Permission) -> (c.int, b
 }
 
 sys_mkdir :: proc(path: string, mode: Permission) -> bool {
-	cpath: cstring = strings.clone_to_cstring(path)
-	defer delete(cpath)
+	cpath: cstring = strings.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 {
-	cpath: cstring = strings.clone_to_cstring(path)
-	defer delete(cpath)
+	cpath: cstring = strings.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 {
-	cpath: cstring = strings.clone_to_cstring(path)
-	defer delete(cpath)
+	cpath: cstring = strings.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 {
-	cpath: cstring = strings.clone_to_cstring(path)
-	defer delete(cpath)
-	cnpath: cstring = strings.clone_to_cstring(new_path)
-	defer delete(cnpath)
+	cpath: cstring = strings.clone_to_cstring(path, context.temp_allocator)
+	cnpath: cstring = strings.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 {
-	cpath: cstring = strings.clone_to_cstring(path)
-	defer delete(cpath)
-	cnpath: cstring = strings.clone_to_cstring(new_path)
-	defer delete(cnpath)
+	cpath: cstring = strings.clone_to_cstring(path, context.temp_allocator)
+	cnpath: cstring = strings.clone_to_cstring(new_path, context.temp_allocator)
 	return syscall_rename_at(fd, cpath, to_fd, cnpath) != -1
 }
 
@@ -165,14 +157,12 @@ sys_lseek :: proc(fd: c.int, offset: i64, whence: Offset_From) -> i64 {
 }
 
 sys_chmod :: proc(path: string, mode: Permission) -> bool {
-	cpath: cstring = strings.clone_to_cstring(path)
-	defer delete(cpath)
+	cpath: cstring = strings.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 {
-	cpath: cstring = strings.clone_to_cstring(path)
-	defer delete(cpath)
+	cpath: cstring = strings.clone_to_cstring(path, context.temp_allocator)
 	return syscall_lstat(cpath, status) != -1
 }

+ 65 - 0
core/sys/unix/pthread_openbsd.odin

@@ -0,0 +1,65 @@
+//+build openbsd
+package unix
+
+import "core:c"
+
+pthread_t             :: distinct rawptr
+pthread_attr_t        :: distinct rawptr
+pthread_mutex_t       :: distinct rawptr
+pthread_mutexattr_t   :: distinct rawptr
+pthread_cond_t        :: distinct rawptr
+pthread_condattr_t    :: distinct rawptr
+pthread_rwlock_t      :: distinct rawptr
+pthread_rwlockattr_t  :: distinct rawptr
+pthread_barrier_t     :: distinct rawptr
+pthread_barrierattr_t :: distinct rawptr
+pthread_spinlock_t    :: distinct rawptr
+
+pthread_key_t  :: distinct c.int
+pthread_once_t :: struct {
+	state: c.int,
+	mutex: pthread_mutex_t,
+}
+
+PTHREAD_MUTEX_ERRORCHECK :: 1
+PTHREAD_MUTEX_RECURSIVE  :: 2
+PTHREAD_MUTEX_NORMAL     :: 3
+PTHREAD_MUTEX_STRICT_NP  :: 4
+
+PTHREAD_DETACHED      :: 0x1
+PTHREAD_SCOPE_SYSTEM  :: 0x2
+PTHREAD_INHERIT_SCHED :: 0x4
+PTHREAD_NOFLOAT       :: 0x8
+
+PTHREAD_CREATE_DETACHED :: PTHREAD_DETACHED
+PTHREAD_CREATE_JOINABLE :: 0
+PTHREAD_SCOPE_PROCESS   :: 0
+PTHREAD_EXPLICIT_SCHED  :: 0
+
+SCHED_FIFO  :: 1
+SCHED_OTHER :: 2
+SCHED_RR    :: 3
+
+sched_param :: struct {
+	sched_priority: c.int,
+}
+
+sem_t :: distinct rawptr
+
+foreign import libc "system:c"
+
+@(default_calling_convention="c")
+foreign libc {
+	sem_open :: proc(name: cstring, flags: c.int) -> ^sem_t ---
+
+	sem_init :: proc(sem: ^sem_t, pshared: c.int, initial_value: c.uint) -> c.int ---
+	sem_destroy :: proc(sem: ^sem_t) -> c.int ---
+	sem_post :: proc(sem: ^sem_t) -> c.int ---
+	sem_wait :: proc(sem: ^sem_t) -> c.int ---
+	sem_trywait :: proc(sem: ^sem_t) -> c.int ---
+	//sem_timedwait :: proc(sem: ^sem_t, timeout: time.TimeSpec) -> c.int ---
+
+	// NOTE: unclear whether pthread_yield is well-supported on Linux systems,
+	// see https://linux.die.net/man/3/pthread_yield
+	pthread_yield :: proc() ---
+}

+ 1 - 1
core/sys/unix/pthread_unix.odin

@@ -1,4 +1,4 @@
-//+build linux, darwin, freebsd
+//+build linux, darwin, freebsd, openbsd
 package unix
 
 foreign import "system:pthread"

+ 3 - 0
core/sys/win32/user32.odin

@@ -101,6 +101,9 @@ foreign user32 {
 	}
 	@(link_name="GetCursorPos")     get_cursor_pos      :: proc(p: ^Point) -> Bool ---
 	@(link_name="SetCursorPos")     set_cursor_pos      :: proc(x, y: i32) -> Bool ---
+	@(link_name="GetCapure")        get_capture         :: proc(hwnd: Hwnd) -> Hwnd ---
+	@(link_name="SetCapture")       set_capture         :: proc(hwnd: Hwnd) -> Hwnd ---
+	@(link_name="ReleaseCapture")   release_capture     :: proc() -> Bool ---
 	@(link_name="ScreenToClient")   screen_to_client    :: proc(h: Hwnd, p: ^Point) -> Bool ---
 	@(link_name="ClientToScreen")   client_to_screen    :: proc(h: Hwnd, p: ^Point) -> Bool ---
 	@(link_name="PostQuitMessage")  post_quit_message   :: proc(exit_code: i32) ---

+ 3 - 0
core/sys/windows/types.odin

@@ -376,6 +376,9 @@ FILE_TYPE_DISK :: 0x0001
 FILE_TYPE_CHAR :: 0x0002
 FILE_TYPE_PIPE :: 0x0003
 
+RECT  :: struct {left, top, right, bottom: LONG}
+POINT :: struct {x, y: LONG}
+
 
 when size_of(uintptr) == 4 {
 	WSADATA :: struct {

+ 1 - 1
core/thread/thread_unix.odin

@@ -1,4 +1,4 @@
-// +build linux, darwin, freebsd
+// +build linux, darwin, freebsd, openbsd
 // +private
 package thread
 

+ 23 - 11
core/time/time_unix.odin

@@ -1,4 +1,4 @@
-//+build linux, darwin, freebsd
+//+build linux, darwin, freebsd, openbsd
 package time
 
 IS_SUPPORTED :: true // NOTE: Times on Darwin are UTC.
@@ -22,16 +22,28 @@ TimeSpec :: struct {
 	tv_nsec : i64,  /* nanoseconds */
 }
 
-CLOCK_REALTIME           :: 0 // NOTE(tetra): May jump in time, when user changes the system time.
-CLOCK_MONOTONIC          :: 1 // NOTE(tetra): May stand still while system is asleep.
-CLOCK_PROCESS_CPUTIME_ID :: 2
-CLOCK_THREAD_CPUTIME_ID  :: 3
-CLOCK_MONOTONIC_RAW      :: 4 // NOTE(tetra): "RAW" means: Not adjusted by NTP.
-CLOCK_REALTIME_COARSE    :: 5 // NOTE(tetra): "COARSE" clocks are apparently much faster, but not "fine-grained."
-CLOCK_MONOTONIC_COARSE   :: 6
-CLOCK_BOOTTIME           :: 7 // NOTE(tetra): Same as MONOTONIC, except also including time system was asleep.
-CLOCK_REALTIME_ALARM     :: 8
-CLOCK_BOOTTIME_ALARM     :: 9
+when ODIN_OS == .OpenBSD {
+	CLOCK_REALTIME           :: 0
+	CLOCK_PROCESS_CPUTIME_ID :: 2
+	CLOCK_MONOTONIC          :: 3
+	CLOCK_THREAD_CPUTIME_ID  :: 4
+	CLOCK_UPTIME             :: 5
+	CLOCK_BOOTTIME           :: 6
+
+	// CLOCK_MONOTONIC_RAW doesn't exist, use CLOCK_MONOTONIC
+	CLOCK_MONOTONIC_RAW :: CLOCK_MONOTONIC
+} else {
+	CLOCK_REALTIME           :: 0 // NOTE(tetra): May jump in time, when user changes the system time.
+	CLOCK_MONOTONIC          :: 1 // NOTE(tetra): May stand still while system is asleep.
+	CLOCK_PROCESS_CPUTIME_ID :: 2
+	CLOCK_THREAD_CPUTIME_ID  :: 3
+	CLOCK_MONOTONIC_RAW      :: 4 // NOTE(tetra): "RAW" means: Not adjusted by NTP.
+	CLOCK_REALTIME_COARSE    :: 5 // NOTE(tetra): "COARSE" clocks are apparently much faster, but not "fine-grained."
+	CLOCK_MONOTONIC_COARSE   :: 6
+	CLOCK_BOOTTIME           :: 7 // NOTE(tetra): Same as MONOTONIC, except also including time system was asleep.
+	CLOCK_REALTIME_ALARM     :: 8
+	CLOCK_BOOTTIME_ALARM     :: 9
+}
 
 // TODO(tetra, 2019-11-05): The original implementation of this package for Darwin used this constants.
 // I do not know if Darwin programmers are used to the existance of these constants or not, so

+ 7 - 14
examples/all/all_vendor.odin

@@ -1,7 +1,5 @@
-//+build windows
 package all
 
-
 import botan     "vendor:botan"
 import ENet      "vendor:ENet"
 import gl        "vendor:OpenGL"
@@ -17,14 +15,11 @@ import IMG    "vendor:sdl2/image"
 import MIX    "vendor:sdl2/mixer"
 import TTF    "vendor:sdl2/ttf"
 
-import stb_easy_font "vendor:stb/easy_font"
-import stbi          "vendor:stb/image"
-import stbrp         "vendor:stb/rect_pack"
-import stbtt         "vendor:stb/truetype"
-import stb_vorbis    "vendor:stb/vorbis"
-
 import vk "vendor:vulkan"
 
+import NS  "vendor:darwin/Foundation"
+import MTL "vendor:darwin/Metal"
+import CA  "vendor:darwin/QuartzCore"
 
 _ :: botan
 _ :: ENet
@@ -39,9 +34,7 @@ _ :: SDLNet
 _ :: IMG
 _ :: MIX
 _ :: TTF
-_ :: stb_easy_font
-_ :: stbi
-_ :: stbrp
-_ :: stbtt
-_ :: stb_vorbis
-_ :: vk
+_ :: vk
+_ :: NS
+_ :: MTL
+_ :: CA

+ 10 - 0
examples/all/all_vendor_directx.odin

@@ -0,0 +1,10 @@
+//+build windows
+package all
+
+import D3D11 "vendor:directx/d3d11"
+import D3D12 "vendor:directx/d3d12"
+import DXGI  "vendor:directx/dxgi"
+
+_ :: D3D11
+_ :: D3D12
+_ :: DXGI

+ 15 - 0
examples/all/all_vendor_stl.odin

@@ -0,0 +1,15 @@
+//+build windows, linux
+package all
+
+import stb_easy_font "vendor:stb/easy_font"
+import stbi          "vendor:stb/image"
+import stbrp         "vendor:stb/rect_pack"
+import stbtt         "vendor:stb/truetype"
+import stb_vorbis    "vendor:stb/vorbis"
+
+_ :: stb_easy_font
+_ :: stbi
+_ :: stbrp
+_ :: stbtt
+_ :: stb_vorbis
+

+ 13 - 7
examples/demo/demo.odin

@@ -11,22 +11,28 @@ import "core:intrinsics"
 import "core:math/big"
 
 /*
-	The Odin programming language is fast, concise, readable, pragmatic and open sourced.
-	It is designed with the intent of replacing C with the following goals:
-	 * simplicity
-	 * high performance
-	 * built for modern systems
-	 * joy of programming
+	Odin is a general-purpose programming language with distinct typing built
+	for high performance, modern systems and data-oriented programming.
+
+	Odin is the C alternative for the Joy of Programming.
 
 	# Installing Odin
 	Getting Started - https://odin-lang.org/docs/install/
 		Instructions for downloading and install the Odin compiler and libraries.
 
 	# Learning Odin
+	Getting Started - https://odin-lang.org/docs/install/
+		Getting Started with Odin. Downloading, installing, and getting your
+		first program to compile and run.
 	Overview of Odin - https://odin-lang.org/docs/overview/
-		An overview of the Odin programming language.
+		An overview of the Odin programming language and its features.
 	Frequently Asked Questions (FAQ) - https://odin-lang.org/docs/faq/
 		Answers to common questions about Odin.
+	Packages - https://pkg.odin-lang.org/
+		Documentation for all the official packages part of the
+		core and vendor library collections.
+	Nightly Builds - https://odin-lang.org/docs/nightly/
+		Get the latest nightly builds of Odin.
 */
 
 the_basics :: proc() {

+ 3 - 1
src/array.cpp

@@ -89,7 +89,9 @@ template <typename T>
 void slice_init(Slice<T> *s, gbAllocator const &allocator, isize count) {
 	GB_ASSERT(count >= 0);
 	s->data = gb_alloc_array(allocator, T, count);
-	GB_ASSERT(s->data != nullptr);
+	if (count > 0) {
+		GB_ASSERT(s->data != nullptr);
+	}
 	s->count = count;
 }
 

+ 24 - 3
src/bug_report.cpp

@@ -17,6 +17,11 @@
 	#include <sys/sysctl.h>
 #endif
 
+#if defined(GB_SYSTEM_OPENBSD)
+	#include <sys/sysctl.h>
+	#include <sys/utsname.h>
+#endif
+
 /*
 	NOTE(Jeroen): This prints the Windows product edition only, to be called from `print_platform_details`.
 */
@@ -242,6 +247,14 @@ void report_ram_info() {
 		if (sysctl(sysctls, 2, &ram_amount, &val_size, NULL, 0) != -1) {
 			gb_printf("%lld MiB\n", ram_amount / gb_megabytes(1));
 		}
+	#elif defined(GB_SYSTEM_OPENBSD)
+		uint64_t ram_amount;
+		size_t   val_size = sizeof(ram_amount);
+
+		int sysctls[] = { CTL_HW, HW_PHYSMEM64 };
+		if (sysctl(sysctls, 2, &ram_amount, &val_size, NULL, 0) != -1) {
+			gb_printf("%lld MiB\n", ram_amount / gb_megabytes(1));
+		}
 	#else
 		gb_printf("Unknown.\n");
 	#endif
@@ -473,11 +486,11 @@ void print_bug_report_help() {
 
 	#elif defined(GB_SYSTEM_LINUX)
 		/*
-			Try to parse `/usr/lib/os-release` for `PRETTY_NAME="Ubuntu 20.04.3 LTS`
+			Try to parse `/etc/os-release` for `PRETTY_NAME="Ubuntu 20.04.3 LTS`
 		*/
 		gbAllocator a = heap_allocator();
 
-		gbFileContents release = gb_file_read_contents(a, 1, "/usr/lib/os-release");
+		gbFileContents release = gb_file_read_contents(a, 1, "/etc/os-release");
 		defer (gb_file_free_contents(&release));
 
 		b32 found = 0;
@@ -643,6 +656,14 @@ void print_bug_report_help() {
 		} else {
 			gb_printf("macOS: Unknown\n");
 		}			
+	#elif defined(GB_SYSTEM_OPENBSD)
+		struct utsname un;
+		
+		if (uname(&un) != -1) {
+			gb_printf("%s %s %s %s\n", un.sysname, un.release, un.version, un.machine);
+		} else {
+			gb_printf("OpenBSD: Unknown\n");    
+		}
 	#else
 		gb_printf("Unknown\n");
 
@@ -657,4 +678,4 @@ void print_bug_report_help() {
 		And RAM info.
 	*/
 	report_ram_info();
-}
+}

+ 57 - 12
src/build_settings.cpp

@@ -1,4 +1,4 @@
-#if defined(GB_SYSTEM_FREEBSD)
+#if defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD)
 #include <sys/types.h>
 #include <sys/sysctl.h>
 #endif
@@ -16,6 +16,7 @@ enum TargetOsKind {
 	TargetOs_linux,
 	TargetOs_essence,
 	TargetOs_freebsd,
+	TargetOs_openbsd,
 	
 	TargetOs_wasi,
 	TargetOs_js,
@@ -53,6 +54,7 @@ String target_os_names[TargetOs_COUNT] = {
 	str_lit("linux"),
 	str_lit("essence"),
 	str_lit("freebsd"),
+	str_lit("openbsd"),
 	
 	str_lit("wasi"),
 	str_lit("js"),
@@ -278,7 +280,7 @@ bool global_ignore_warnings(void) {
 }
 
 
-gb_global TargetMetrics target_windows_386 = {
+gb_global TargetMetrics target_windows_i386 = {
 	TargetOs_windows,
 	TargetArch_i386,
 	4,
@@ -294,7 +296,7 @@ gb_global TargetMetrics target_windows_amd64 = {
 	str_lit("e-m:w-i64:64-f80:128-n8:16:32:64-S128"),
 };
 
-gb_global TargetMetrics target_linux_386 = {
+gb_global TargetMetrics target_linux_i386 = {
 	TargetOs_linux,
 	TargetArch_i386,
 	4,
@@ -337,7 +339,7 @@ gb_global TargetMetrics target_darwin_arm64 = {
 	str_lit("e-m:o-i64:64-i128:128-n32:64-S128"), // TODO(bill): Is this correct?
 };
 
-gb_global TargetMetrics target_freebsd_386 = {
+gb_global TargetMetrics target_freebsd_i386 = {
 	TargetOs_freebsd,
 	TargetArch_i386,
 	4,
@@ -354,6 +356,15 @@ gb_global TargetMetrics target_freebsd_amd64 = {
 	str_lit("e-m:w-i64:64-f80:128-n8:16:32:64-S128"),
 };
 
+gb_global TargetMetrics target_openbsd_amd64 = {
+	TargetOs_openbsd,
+	TargetArch_amd64,
+	8,
+	16,
+	str_lit("x86_64-unknown-openbsd-elf"),
+	str_lit("e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"),
+};
+
 gb_global TargetMetrics target_essence_amd64 = {
 	TargetOs_essence,
 	TargetArch_amd64,
@@ -410,13 +421,14 @@ gb_global NamedTargetMetrics named_targets[] = {
 	{ str_lit("darwin_amd64"),        &target_darwin_amd64   },
 	{ str_lit("darwin_arm64"),        &target_darwin_arm64   },
 	{ str_lit("essence_amd64"),       &target_essence_amd64  },
-	{ str_lit("linux_386"),           &target_linux_386      },
+	{ str_lit("linux_i386"),          &target_linux_i386     },
 	{ str_lit("linux_amd64"),         &target_linux_amd64    },
 	{ str_lit("linux_arm64"),         &target_linux_arm64    },
-	{ str_lit("windows_386"),         &target_windows_386    },
+	{ str_lit("windows_i386"),        &target_windows_i386   },
 	{ str_lit("windows_amd64"),       &target_windows_amd64  },
-	{ str_lit("freebsd_386"),         &target_freebsd_386    },
+	{ str_lit("freebsd_i386"),        &target_freebsd_i386   },
 	{ str_lit("freebsd_amd64"),       &target_freebsd_amd64  },
+	{ str_lit("openbsd_amd64"),       &target_openbsd_amd64  },
 	{ str_lit("freestanding_wasm32"), &target_freestanding_wasm32 },
 	{ str_lit("wasi_wasm32"),         &target_wasi_wasm32 },
 	{ str_lit("js_wasm32"),           &target_js_wasm32 },
@@ -722,10 +734,38 @@ String internal_odin_root_dir(void) {
 		len = readlink("/proc/curproc/exe", &path_buf[0], path_buf.count);
 #elif defined(GB_SYSTEM_DRAGONFLYBSD)
 		len = readlink("/proc/curproc/file", &path_buf[0], path_buf.count);
-#else
+#elif defined(GB_SYSTEM_LINUX)
 		len = readlink("/proc/self/exe", &path_buf[0], path_buf.count);
+#elif defined(GB_SYSTEM_OPENBSD)
+		int error;
+		int mib[] = {
+			CTL_KERN,
+			KERN_PROC_ARGS,
+			getpid(),
+			KERN_PROC_ARGV,
+		};
+		// get argv size
+		error = sysctl(mib, 4, NULL, (size_t *) &len, NULL, 0);
+		if (error == -1) {
+			// sysctl error
+			return make_string(nullptr, 0);
+		}
+		// get argv
+		char **argv = (char **)gb_malloc(len);
+		error = sysctl(mib, 4, argv, (size_t *) &len, NULL, 0);
+		if (error == -1) {
+			// sysctl error
+			gb_mfree(argv);
+			return make_string(nullptr, 0);
+		}
+		// copy argv[0] to path_buf
+		len = gb_strlen(argv[0]);
+		if(len < path_buf.count) {
+			gb_memmove(&path_buf[0], argv[0], len);
+		}
+		gb_mfree(argv);
 #endif
-		if(len == 0) {
+		if(len == 0 || len == -1) {
 			return make_string(nullptr, 0);
 		}
 		if (len < path_buf.count) {
@@ -922,6 +962,8 @@ void init_build_context(TargetMetrics *cross_target) {
 			#endif
 		#elif defined(GB_SYSTEM_FREEBSD)
 			metrics = &target_freebsd_amd64;
+		#elif defined(GB_SYSTEM_OPENBSD)
+			metrics = &target_openbsd_amd64;
 		#elif defined(GB_CPU_ARM)
 			metrics = &target_linux_arm64;
 		#else
@@ -929,13 +971,13 @@ void init_build_context(TargetMetrics *cross_target) {
 		#endif
 	#else
 		#if defined(GB_SYSTEM_WINDOWS)
-			metrics = &target_windows_386;
+			metrics = &target_windows_i386;
 		#elif defined(GB_SYSTEM_OSX)
 			#error "Build Error: Unsupported architecture"
 		#elif defined(GB_SYSTEM_FREEBSD)
-			metrics = &target_freebsd_386;
+			metrics = &target_freebsd_i386;
 		#else
-			metrics = &target_linux_386;
+			metrics = &target_linux_i386;
 		#endif
 	#endif
 
@@ -980,6 +1022,9 @@ void init_build_context(TargetMetrics *cross_target) {
 		case TargetOs_freebsd:
 			bc->link_flags = str_lit("-arch x86-64 ");
 			break;
+		case TargetOs_openbsd:
+			bc->link_flags = str_lit("-arch x86-64 ");
+			break;
 		}
 	} else if (bc->metrics.arch == TargetArch_i386) {
 		switch (bc->metrics.os) {

+ 45 - 29
src/check_builtin.cpp

@@ -223,36 +223,35 @@ void add_objc_proc_type(CheckerContext *c, Ast *call, Type *return_type, Slice<T
 	map_set(&c->info->objc_msgSend_types, call, data);
 	mutex_unlock(&c->info->objc_types_mutex);
 
-	add_package_dependency(c, "runtime", "objc_lookUpClass");
-	add_package_dependency(c, "runtime", "sel_registerName");
-	add_package_dependency(c, "runtime", "objc_allocateClassPair");
-
-	add_package_dependency(c, "runtime", "objc_msgSend");
-	add_package_dependency(c, "runtime", "objc_msgSend_fpret");
-	add_package_dependency(c, "runtime", "objc_msgSend_fp2ret");
-	add_package_dependency(c, "runtime", "objc_msgSend_stret");
+	try_to_add_package_dependency(c, "runtime", "objc_msgSend");
+	try_to_add_package_dependency(c, "runtime", "objc_msgSend_fpret");
+	try_to_add_package_dependency(c, "runtime", "objc_msgSend_fp2ret");
+	try_to_add_package_dependency(c, "runtime", "objc_msgSend_stret");
+}
+
+bool is_constant_string(CheckerContext *c, String const &builtin_name, Ast *expr, String *name_) {
+	Operand op = {};
+	check_expr(c, &op, expr);
+	if (op.mode == Addressing_Constant && op.value.kind == ExactValue_String) {
+		if (name_) *name_ = op.value.value_string;
+		return true;
+	}
+	gbString e = expr_to_string(op.expr);
+	gbString t = type_to_string(op.type);
+	error(op.expr, "'%.*s' expected a constant string value, got %s of type %s", LIT(builtin_name), e, t);
+	gb_string_free(t);
+	gb_string_free(e);
+	return false;
 }
 
 bool check_builtin_objc_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 id, Type *type_hint) {
-	auto const is_constant_string = [](CheckerContext *c, String const &builtin_name, Ast *expr, String *name_) -> bool {
-		Operand op = {};
-		check_expr(c, &op, expr);
-		if (op.mode == Addressing_Constant && op.value.kind == ExactValue_String) {
-			if (name_) *name_ = op.value.value_string;
-			return true;
-		}
-		gbString e = expr_to_string(op.expr);
-		gbString t = type_to_string(op.type);
-		error(op.expr, "'%.*s' expected a constant string value, got %s of type %s", LIT(builtin_name), e, t);
-		gb_string_free(t);
-		gb_string_free(e);
-		return false;
-	};
 	String builtin_name = builtin_procs[id].name;
 
 	if (build_context.metrics.os != TargetOs_darwin) {
-		error(call, "'%.*s' only works on darwin", LIT(builtin_name));
-		return false;
+		// allow on doc generation (e.g. Metal stuff)
+		if (build_context.command_kind != Command_doc && build_context.command_kind != Command_check) {
+			error(call, "'%.*s' only works on darwin", LIT(builtin_name));
+		}
 	}
 
 
@@ -371,6 +370,10 @@ bool check_builtin_objc_procedure(CheckerContext *c, Operand *operand, Ast *call
 
 		}
 		operand->mode = Addressing_Value;
+
+		try_to_add_package_dependency(c, "runtime", "objc_lookUpClass");
+		try_to_add_package_dependency(c, "runtime", "sel_registerName");
+		try_to_add_package_dependency(c, "runtime", "objc_allocateClassPair");
 		return true;
 	} break;
 	}
@@ -949,7 +952,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 			mode = Addressing_Constant;
 			value = exact_value_i64(at->EnumeratedArray.count);
 			type = t_untyped_integer;
-		} else if (is_type_slice(op_type) && id == BuiltinProc_len) {
+		} else if ((is_type_slice(op_type) || is_type_relative_slice(op_type)) && id == BuiltinProc_len) {
 			mode = Addressing_Value;
 		} else if (is_type_dynamic_array(op_type)) {
 			mode = Addressing_Value;
@@ -1103,7 +1106,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 		
 		Selection sel = lookup_field(type, field_name, false);
 		if (sel.entity == nullptr) {
-			gbString type_str = type_to_string(type);
+			gbString type_str = type_to_string_shorthand(type);
 			error(ce->args[0],
 			      "'%s' has no field named '%.*s'", type_str, LIT(field_name));
 			gb_string_free(type_str);
@@ -1115,7 +1118,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 			return false;
 		}
 		if (sel.indirect) {
-			gbString type_str = type_to_string(type);
+			gbString type_str = type_to_string_shorthand(type);
 			error(ce->args[0],
 			      "Field '%.*s' is embedded via a pointer in '%s'", LIT(field_name), type_str);
 			gb_string_free(type_str);
@@ -1176,7 +1179,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 		
 		Selection sel = lookup_field(type, field_name, false);
 		if (sel.entity == nullptr) {
-			gbString type_str = type_to_string(type);
+			gbString type_str = type_to_string_shorthand(type);
 			error(ce->args[0],
 			      "'%s' has no field named '%.*s'", type_str, LIT(field_name));
 			gb_string_free(type_str);
@@ -1188,7 +1191,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 			return false;
 		}
 		if (sel.indirect) {
-			gbString type_str = type_to_string(type);
+			gbString type_str = type_to_string_shorthand(type);
 			error(ce->args[0],
 			      "Field '%.*s' is embedded via a pointer in '%s'", LIT(field_name), type_str);
 			gb_string_free(type_str);
@@ -3503,6 +3506,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 			case TargetOs_linux:
 			case TargetOs_essence:
 			case TargetOs_freebsd:
+			case TargetOs_openbsd:
 				switch (build_context.metrics.arch) {
 				case TargetArch_i386:
 				case TargetArch_amd64:
@@ -4086,6 +4090,18 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 			break;
 		}
 
+	case BuiltinProc_constant_utf16_cstring:
+		{
+			String value = {};
+			if (!is_constant_string(c, builtin_name, ce->args[0], &value)) {
+				return false;
+			}
+			operand->mode = Addressing_Value;
+			operand->type = alloc_type_multi_pointer(t_u16);
+			operand->value = {};
+			break;
+		}
+
 
 	}
 

+ 4 - 0
src/check_decl.cpp

@@ -174,6 +174,10 @@ void check_init_constant(CheckerContext *ctx, Entity *e, Operand *operand) {
 		return;
 	}
 
+	if (is_type_proc(e->type)) {
+		error(e->token, "Illegal declaration of a constant procedure value");
+	}
+
 	e->parent_proc_decl = ctx->curr_proc_decl;
 
 	e->Constant.value = operand->value;

+ 70 - 5
src/check_expr.cpp

@@ -132,6 +132,62 @@ void check_did_you_mean_print(DidYouMeanAnswers *d, char const *prefix = "") {
 	}
 }
 
+void populate_check_did_you_mean_objc_entity(StringSet *set, Entity *e, bool is_type) {
+	if (e->kind != Entity_TypeName) {
+		return;
+	}
+	if (e->TypeName.objc_metadata == nullptr) {
+		return;
+	}
+	TypeNameObjCMetadata *objc_metadata = e->TypeName.objc_metadata;
+	Type *t = base_type(e->type);
+	GB_ASSERT(t->kind == Type_Struct);
+
+	if (is_type) {
+		for_array(i, objc_metadata->type_entries) {
+			String name = objc_metadata->type_entries[i].name;
+			string_set_add(set, name);
+		}
+	} else {
+		for_array(i, objc_metadata->value_entries) {
+			String name = objc_metadata->value_entries[i].name;
+			string_set_add(set, name);
+		}
+	}
+
+	for_array(i, t->Struct.fields) {
+		Entity *f = t->Struct.fields[i];
+		if (f->flags & EntityFlag_Using && f->type != nullptr) {
+			if (f->type->kind == Type_Named && f->type->Named.type_name) {
+				populate_check_did_you_mean_objc_entity(set, f->type->Named.type_name, is_type);
+			}
+		}
+	}
+}
+
+
+void check_did_you_mean_objc_entity(String const &name, Entity *e, bool is_type, char const *prefix = "") {
+	ERROR_BLOCK();
+	GB_ASSERT(e->kind == Entity_TypeName);
+	GB_ASSERT(e->TypeName.objc_metadata != nullptr);
+	auto *objc_metadata = e->TypeName.objc_metadata;
+	mutex_lock(objc_metadata->mutex);
+	defer (mutex_unlock(objc_metadata->mutex));
+
+	StringSet set = {};
+	string_set_init(&set, heap_allocator());
+	defer (string_set_destroy(&set));
+	populate_check_did_you_mean_objc_entity(&set, e, is_type);
+
+
+	DidYouMeanAnswers d = did_you_mean_make(heap_allocator(), set.entries.count, name);
+	defer (did_you_mean_destroy(&d));
+	for_array(i, set.entries) {
+		did_you_mean_append(&d, set.entries[i].value);
+	}
+	check_did_you_mean_print(&d, prefix);
+}
+
 void check_did_you_mean_type(String const &name, Array<Entity *> const &fields, char const *prefix = "") {
 	ERROR_BLOCK();
 
@@ -144,6 +200,7 @@ void check_did_you_mean_type(String const &name, Array<Entity *> const &fields,
 	check_did_you_mean_print(&d, prefix);
 }
 
+
 void check_did_you_mean_type(String const &name, Slice<Entity *> const &fields, char const *prefix = "") {
 	ERROR_BLOCK();
 
@@ -4413,14 +4470,19 @@ Entity *check_selector(CheckerContext *c, Operand *operand, Ast *node, Type *typ
 
 	if (entity == nullptr) {
 		gbString op_str   = expr_to_string(op_expr);
-		gbString type_str = type_to_string(operand->type);
+		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 (bt->kind == Type_Struct) {
+			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);
@@ -4449,7 +4511,7 @@ Entity *check_selector(CheckerContext *c, Operand *operand, Ast *node, Type *typ
 		}
 
 		gbString op_str   = expr_to_string(op_expr);
-		gbString type_str = type_to_string(operand->type);
+		gbString type_str = type_to_string_shorthand(operand->type);
 		gbString sel_str  = expr_to_string(selector);
 		error(op_expr, "Cannot access non-constant field '%s' from '%s'", sel_str, op_str);
 		gb_string_free(sel_str);
@@ -4474,7 +4536,7 @@ Entity *check_selector(CheckerContext *c, Operand *operand, Ast *node, Type *typ
 		}
 
 		gbString op_str   = expr_to_string(op_expr);
-		gbString type_str = type_to_string(operand->type);
+		gbString type_str = type_to_string_shorthand(operand->type);
 		gbString sel_str  = expr_to_string(selector);
 		error(op_expr, "Cannot access non-constant field '%s' from '%s'", sel_str, op_str);
 		gb_string_free(sel_str);
@@ -4487,7 +4549,7 @@ Entity *check_selector(CheckerContext *c, Operand *operand, Ast *node, Type *typ
 
 	if (expr_entity != nullptr && is_type_polymorphic(expr_entity->type)) {
 		gbString op_str   = expr_to_string(op_expr);
-		gbString type_str = type_to_string(operand->type);
+		gbString type_str = type_to_string_shorthand(operand->type);
 		gbString sel_str  = expr_to_string(selector);
 		error(op_expr, "Cannot access field '%s' from non-specialized polymorphic type '%s'", sel_str, op_str);
 		gb_string_free(sel_str);
@@ -9774,6 +9836,9 @@ gbString write_expr_to_string(gbString str, Ast *node, bool shorthand) {
 		if (f->flags&FieldFlag_const) {
 			str = gb_string_appendc(str, "#const ");
 		}
+		if (f->flags&FieldFlag_subtype) {
+			str = gb_string_appendc(str, "#subtype ");
+		}
 
 		for_array(i, f->names) {
 			Ast *name = f->names[i];

+ 18 - 0
src/check_type.cpp

@@ -144,6 +144,7 @@ void check_struct_fields(CheckerContext *ctx, Ast *node, Slice<Entity *> *fields
 		}
 
 		bool is_using = (p->flags&FieldFlag_using) != 0;
+		bool is_subtype = (p->flags&FieldFlag_subtype) != 0;
 
 		for_array(j, p->names) {
 			Ast *name = p->names[j];
@@ -158,6 +159,9 @@ void check_struct_fields(CheckerContext *ctx, Ast *node, Slice<Entity *> *fields
 			Entity *field = alloc_entity_field(ctx->scope, name_token, type, is_using, field_src_index);
 			add_entity(ctx, ctx->scope, name, field);
 			field->Variable.field_group_index = field_group_index;
+			if (is_subtype) {
+				field->flags |= EntityFlag_Subtype;
+			}
 
 			if (j == 0) {
 				field->Variable.docs = docs;
@@ -194,6 +198,20 @@ void check_struct_fields(CheckerContext *ctx, Ast *node, Slice<Entity *> *fields
 
 			populate_using_entity_scope(ctx, node, p, type);
 		}
+
+		if (is_subtype && p->names.count > 0) {
+			Type *first_type = fields_array[fields_array.count-1]->type;
+			Type *t = base_type(type_deref(first_type));
+
+			if (!does_field_type_allow_using(t) &&
+			    p->names.count >= 1 &&
+			    p->names[0]->kind == Ast_Ident) {
+				Token name_token = p->names[0]->Ident.token;
+				gbString type_str = type_to_string(first_type);
+				error(name_token, "'subtype' cannot be applied to the field '%.*s' of type '%s'", LIT(name_token.string), type_str);
+				gb_string_free(type_str);
+			}
+		}
 	}
 	
 	*fields = slice_from_array(fields_array);

+ 17 - 3
src/checker.cpp

@@ -225,8 +225,8 @@ bool decl_info_has_init(DeclInfo *d) {
 Scope *create_scope(CheckerInfo *info, Scope *parent, isize init_elements_capacity=DEFAULT_SCOPE_CAPACITY) {
 	Scope *s = gb_alloc_item(permanent_allocator(), Scope);
 	s->parent = parent;
-	string_map_init(&s->elements, permanent_allocator(), init_elements_capacity);
-	ptr_set_init(&s->imported, permanent_allocator(), 0);
+	string_map_init(&s->elements, heap_allocator(), init_elements_capacity);
+	ptr_set_init(&s->imported, heap_allocator(), 0);
 	mutex_init(&s->mutex);
 
 	if (parent != nullptr && parent != builtin_pkg->scope) {
@@ -733,12 +733,25 @@ void add_package_dependency(CheckerContext *c, char const *package_name, char co
 	String n = make_string_c(name);
 	AstPackage *p = get_core_package(&c->checker->info, make_string_c(package_name));
 	Entity *e = scope_lookup(p->scope, n);
-	e->flags |= EntityFlag_Used;
 	GB_ASSERT_MSG(e != nullptr, "%s", name);
 	GB_ASSERT(c->decl != nullptr);
+	e->flags |= EntityFlag_Used;
+	add_dependency(c->info, c->decl, e);
+}
+
+void try_to_add_package_dependency(CheckerContext *c, char const *package_name, char const *name) {
+	String n = make_string_c(name);
+	AstPackage *p = get_core_package(&c->checker->info, make_string_c(package_name));
+	Entity *e = scope_lookup(p->scope, n);
+	if (e == nullptr) {
+		return;
+	}
+	GB_ASSERT(c->decl != nullptr);
+	e->flags |= EntityFlag_Used;
 	add_dependency(c->info, c->decl, e);
 }
 
+
 void add_declaration_dependency(CheckerContext *c, Entity *e) {
 	if (e == nullptr) {
 		return;
@@ -893,6 +906,7 @@ void init_universal(void) {
 			{"Linux",        TargetOs_linux},
 			{"Essence",      TargetOs_essence},
 			{"FreeBSD",      TargetOs_freebsd},
+			{"OpenBSD",      TargetOs_openbsd},
 			{"WASI",         TargetOs_wasi},
 			{"JS",           TargetOs_js},
 			{"Freestanding", TargetOs_freestanding},

+ 6 - 0
src/checker_builtin_procs.hpp

@@ -258,6 +258,9 @@ BuiltinProc__type_end,
 	BuiltinProc_objc_register_selector,
 	BuiltinProc_objc_register_class,
 
+	BuiltinProc_constant_utf16_cstring,
+
+
 	BuiltinProc_COUNT,
 };
 gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
@@ -517,4 +520,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
 	{STR_LIT("objc_find_class"),        1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("objc_register_selector"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("objc_register_class"),    1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+
+	{STR_LIT("constant_utf16_cstring"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+
 };

+ 2 - 2
src/common.cpp

@@ -848,7 +848,7 @@ ReadDirectoryError read_directory(String path, Array<FileInfo> *fi) {
 
 	return ReadDirectory_None;
 }
-#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD)
+#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD)
 
 #include <dirent.h>
 
@@ -1021,7 +1021,7 @@ LoadedFileError load_file_32(char const *fullpath, LoadedFile *memory_mapped_fil
 	#endif
 	}
 	
-	gbFileContents fc = gb_file_read_contents(heap_allocator(), true, fullpath);
+	gbFileContents fc = gb_file_read_contents(permanent_allocator(), true, fullpath);
 
 	if (fc.size > I32_MAX) {
 		err = LoadedFile_FileTooLarge;

+ 17 - 2
src/common_memory.cpp

@@ -139,6 +139,7 @@ struct PlatformMemoryBlock {
 };
 
 
+gb_global std::atomic<isize> global_platform_memory_total_usage;
 gb_global PlatformMemoryBlock global_platform_memory_block_sentinel;
 
 PlatformMemoryBlock *platform_virtual_memory_alloc(isize total_size);
@@ -158,10 +159,17 @@ void platform_virtual_memory_protect(void *memory, isize size);
 
 	PlatformMemoryBlock *platform_virtual_memory_alloc(isize total_size) {
 		PlatformMemoryBlock *pmblock = (PlatformMemoryBlock *)VirtualAlloc(0, total_size, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
-		GB_ASSERT_MSG(pmblock != nullptr, "Out of Virtual Memory, oh no...");
+		if (pmblock == nullptr) {
+			gb_printf_err("Out of Virtual memory, oh no...\n");
+			gb_printf_err("Requested: %lld bytes\n", cast(long long)total_size);
+			gb_printf_err("Total Usage: %lld bytes\n", cast(long long)global_platform_memory_total_usage);
+			GB_ASSERT_MSG(pmblock != nullptr, "Out of Virtual Memory, oh no...");
+		}
+		global_platform_memory_total_usage += total_size;
 		return pmblock;
 	}
 	void platform_virtual_memory_free(PlatformMemoryBlock *block) {
+		global_platform_memory_total_usage -= block->total_size;
 		GB_ASSERT(VirtualFree(block, 0, MEM_RELEASE));
 	}
 	void platform_virtual_memory_protect(void *memory, isize size) {
@@ -180,11 +188,18 @@ void platform_virtual_memory_protect(void *memory, isize size);
 	
 	PlatformMemoryBlock *platform_virtual_memory_alloc(isize total_size) {
 		PlatformMemoryBlock *pmblock = (PlatformMemoryBlock *)mmap(nullptr, total_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
-		GB_ASSERT_MSG(pmblock != nullptr, "Out of Virtual Memory, oh no...");
+		if (pmblock == nullptr) {
+			gb_printf_err("Out of Virtual memory, oh no...\n");
+			gb_printf_err("Requested: %lld bytes\n", cast(long long)total_size);
+			gb_printf_err("Total Usage: %lld bytes\n", cast(long long)global_platform_memory_total_usage);
+			GB_ASSERT_MSG(pmblock != nullptr, "Out of Virtual Memory, oh no...");
+		}
+		global_platform_memory_total_usage += total_size;
 		return pmblock;
 	}
 	void platform_virtual_memory_free(PlatformMemoryBlock *block) {
 		isize size = block->total_size;
+		global_platform_memory_total_usage -= size;
 		munmap(block, size);
 	}
 	void platform_virtual_memory_protect(void *memory, isize size) {

+ 5 - 0
src/entity.cpp

@@ -74,6 +74,7 @@ enum EntityFlag : u64 {
 
 	EntityFlag_Test          = 1ull<<30,
 	EntityFlag_Init          = 1ull<<31,
+	EntityFlag_Subtype       = 1ull<<32,
 	
 	EntityFlag_CustomLinkName = 1ull<<40,
 	EntityFlag_CustomLinkage_Internal = 1ull<<41,
@@ -86,6 +87,10 @@ enum EntityFlag : u64 {
 	EntityFlag_Overridden    = 1ull<<63,
 };
 
+enum : u64 {
+	EntityFlags_IsSubtype = EntityFlag_Using|EntityFlag_Subtype,
+};
+
 enum EntityState : u32 {
 	EntityState_Unresolved = 0,
 	EntityState_InProgress = 1,

+ 16 - 5
src/exact_value.cpp

@@ -50,9 +50,9 @@ struct ExactValue {
 	union {
 		bool           value_bool;
 		String         value_string;
-		BigInt         value_integer; // NOTE(bill): This must be an integer and not a pointer
+		BigInt         value_integer;
 		f64            value_float;
-		i64            value_pointer;
+		i64            value_pointer; // NOTE(bill): This must be an integer and not a pointer
 		Complex128    *value_complex;
 		Quaternion256 *value_quaternion;
 		Ast *          value_compound;
@@ -630,6 +630,9 @@ void match_exact_values(ExactValue *x, ExactValue *y) {
 	case ExactValue_Bool:
 	case ExactValue_String:
 	case ExactValue_Quaternion:
+	case ExactValue_Pointer:
+	case ExactValue_Procedure:
+	case ExactValue_Typeid:
 		return;
 
 	case ExactValue_Integer:
@@ -671,9 +674,6 @@ void match_exact_values(ExactValue *x, ExactValue *y) {
 			return;
 		}
 		break;
-
-	case ExactValue_Procedure:
-		return;
 	}
 
 	compiler_error("match_exact_values: How'd you get here? Invalid ExactValueKind %d", x->kind);
@@ -932,6 +932,17 @@ bool compare_exact_values(TokenKind op, ExactValue x, ExactValue y) {
 		break;
 	}
 
+	case ExactValue_Pointer: {
+		switch (op) {
+		case Token_CmpEq: return x.value_pointer == y.value_pointer;
+		case Token_NotEq: return x.value_pointer != y.value_pointer;
+		case Token_Lt:    return x.value_pointer <  y.value_pointer;
+		case Token_LtEq:  return x.value_pointer <= y.value_pointer;
+		case Token_Gt:    return x.value_pointer >  y.value_pointer;
+		case Token_GtEq:  return x.value_pointer >= y.value_pointer;
+		}
+	}
+
 	case ExactValue_Typeid:
 		switch (op) {
 		case Token_CmpEq: return are_types_identical(x.value_typeid, y.value_typeid);

+ 87 - 2
src/gb/gb.h

@@ -79,6 +79,10 @@ extern "C" {
 		#ifndef GB_SYSTEM_FREEBSD
 		#define GB_SYSTEM_FREEBSD 1
 		#endif
+	#elif defined(__OpenBSD__)
+		#ifndef GB_SYSTEM_OPENBSD
+		#define GB_SYSTEM_OPENBSD 1
+		#endif
 	#else
 		#error This UNIX operating system is not supported
 	#endif
@@ -199,7 +203,7 @@ extern "C" {
 	#endif
 	#include <stdlib.h> // NOTE(bill): malloc on linux
 	#include <sys/mman.h>
-	#if !defined(GB_SYSTEM_OSX) && !defined(__FreeBSD__)
+	#if !defined(GB_SYSTEM_OSX) && !defined(__FreeBSD__) && !defined(__OpenBSD__)
 		#include <sys/sendfile.h>
 	#endif
 	#include <sys/stat.h>
@@ -235,6 +239,12 @@ extern "C" {
 	#define sendfile(out, in, offset, count) sendfile(out, in, offset, count, NULL, NULL, 0)
 #endif
 
+#if defined(GB_SYSTEM_OPENBSD)
+	#include <stdio.h>
+	#include <pthread_np.h>
+	#define lseek64 lseek
+#endif
+    
 #if defined(GB_SYSTEM_UNIX)
 	#include <semaphore.h>
 #endif
@@ -783,6 +793,13 @@ typedef struct gbAffinity {
 	isize thread_count;
 	isize threads_per_core;
 } gbAffinity;
+#elif defined(GB_SYSTEM_OPENBSD)
+typedef struct gbAffinity {
+	b32   is_accurate;
+	isize core_count;
+	isize thread_count;
+	isize threads_per_core;
+} gbAffinity;
 #else
 #error TODO(bill): Unknown system
 #endif
@@ -3678,6 +3695,30 @@ b32 gb_affinity_set(gbAffinity *a, isize core, isize thread_index) {
 	return true;
 }
 
+isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core) {
+	GB_ASSERT(0 <= core && core < a->core_count);
+	return a->threads_per_core;
+}
+
+#elif defined(GB_SYSTEM_OPENBSD)
+#include <unistd.h>
+
+void gb_affinity_init(gbAffinity *a) {
+	a->core_count       = sysconf(_SC_NPROCESSORS_ONLN);
+	a->threads_per_core = 1;
+	a->is_accurate      = a->core_count > 0;
+	a->core_count       = a->is_accurate ? a->core_count : 1;
+	a->thread_count     = a->core_count;
+}
+
+void gb_affinity_destroy(gbAffinity *a) {
+	gb_unused(a);
+}
+
+b32 gb_affinity_set(gbAffinity *a, isize core, isize thread_index) {
+	return true;
+}
+
 isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core) {
 	GB_ASSERT(0 <= core && core < a->core_count);
 	return a->threads_per_core;
@@ -6025,7 +6066,7 @@ gbFileTime gb_file_last_write_time(char const *filepath) {
 gb_inline b32 gb_file_copy(char const *existing_filename, char const *new_filename, b32 fail_if_exists) {
 #if defined(GB_SYSTEM_OSX)
 	return copyfile(existing_filename, new_filename, NULL, COPYFILE_DATA) == 0;
-#else
+#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_FREEBSD)
 	isize size;
 	int existing_fd = open(existing_filename, O_RDONLY, 0);
 	int new_fd      = open(new_filename, O_WRONLY|O_CREAT, 0666);
@@ -6041,6 +6082,49 @@ gb_inline b32 gb_file_copy(char const *existing_filename, char const *new_filena
 	close(new_fd);
 	close(existing_fd);
 
+	return size == stat_existing.st_size;
+#else
+	int new_flags = O_WRONLY | O_CREAT;
+	if (fail_if_exists) {
+		new_flags |= O_EXCL;
+	}
+	int existing_fd = open(existing_filename, O_RDONLY, 0);
+	int new_fd      = open(new_filename, new_flags, 0666);
+
+	struct stat stat_existing;
+	if (fstat(existing_fd, &stat_existing) == -1) {
+		return 0;
+	}
+
+	size_t bsize = stat_existing.st_blksize > BUFSIZ ? stat_existing.st_blksize : BUFSIZ;
+	char *buf = (char *)malloc(bsize);
+	if (buf == NULL) {
+		close(new_fd);
+		close(existing_fd);
+		return 0;
+	}
+
+	isize size = 0;
+	ssize_t nread, nwrite, offset;
+	while ((nread = read(existing_fd, buf, bsize)) != -1 && nread != 0) {
+		for (offset = 0; nread; nread -= nwrite, offset += nwrite) {
+			if ((nwrite = write(new_fd, buf + offset, nread)) == -1 || nwrite == 0) {
+				free(buf);
+				close(new_fd);
+				close(existing_fd);
+				return 0;
+			}
+			size += nwrite;
+		}
+	}
+	
+	free(buf);
+	close(new_fd);
+	close(existing_fd);
+
+	if (nread == -1) {
+		return 0;
+	}
 	return size == stat_existing.st_size;
 #endif
 }
@@ -6093,6 +6177,7 @@ gbFileContents gb_file_read_contents(gbAllocator a, b32 zero_terminate, char con
 }
 
 void gb_file_free_contents(gbFileContents *fc) {
+    if (fc == NULL || fc->size == 0) return;
 	GB_ASSERT_NOT_NULL(fc->data);
 	gb_free(fc->allocator, fc->data);
 	fc->data = NULL;

+ 5 - 0
src/llvm_backend.cpp

@@ -1295,6 +1295,11 @@ void lb_generate_code(lbGenerator *gen) {
 		reloc_mode = LLVMRelocPIC;
 	}
 
+	if (build_context.metrics.os == TargetOs_openbsd) {
+		// Always use PIC for OpenBSD: it defaults to PIE
+		reloc_mode = LLVMRelocPIC;
+	}
+
 	for_array(i, gen->modules.entries) {
 		target_machines[i] = LLVMCreateTargetMachine(
 			target, target_triple, llvm_cpu,

+ 1 - 1
src/llvm_backend_debug.cpp

@@ -962,7 +962,7 @@ void lb_add_debug_local_variable(lbProcedure *p, LLVMValueRef ptr, Type *type, T
 	LLVMMetadataRef llvm_debug_loc = lb_debug_location_from_token_pos(p, token.pos);
 	LLVMMetadataRef llvm_expr = LLVMDIBuilderCreateExpression(m->debug_builder, nullptr, 0);
 	lb_set_llvm_metadata(m, ptr, llvm_expr);
-	LLVMDIBuilderInsertDbgValueAtEnd(m->debug_builder, storage, var_info, llvm_expr, llvm_debug_loc, block);
+	LLVMDIBuilderInsertDeclareAtEnd(m->debug_builder, storage, var_info, llvm_expr, llvm_debug_loc, block);
 }
 
 

+ 15 - 0
src/llvm_backend_expr.cpp

@@ -2202,6 +2202,21 @@ lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left, lbValue ri
 		}
 	}
 
+	if (is_type_matrix(a) && (op_kind == Token_CmpEq || op_kind == Token_NotEq)) {
+		Type *tl = base_type(a);
+		lbValue lhs = lb_address_from_load_or_generate_local(p, left);
+		lbValue rhs = lb_address_from_load_or_generate_local(p, right);
+
+
+		// TODO(bill): Test to see if this is actually faster!!!!
+		auto args = array_make<lbValue>(permanent_allocator(), 3);
+		args[0] = lb_emit_conv(p, lhs, t_rawptr);
+		args[1] = lb_emit_conv(p, rhs, t_rawptr);
+		args[2] = lb_const_int(p->module, t_int, type_size_of(tl));
+		lbValue val = lb_emit_runtime_call(p, "memory_compare", args);
+		lbValue res = lb_emit_comp(p, op_kind, val, lb_const_nil(p->module, val.type));
+		return lb_emit_conv(p, res, t_bool);
+	}
 	if (is_type_array(a) || is_type_enumerated_array(a)) {
 		Type *tl = base_type(a);
 		lbValue lhs = lb_address_from_load_or_generate_local(p, left);

+ 74 - 10
src/llvm_backend_proc.cpp

@@ -57,6 +57,7 @@ void lb_mem_copy_non_overlapping(lbProcedure *p, lbValue dst, lbValue src, lbVal
 	LLVMBuildCall(p->builder, ip, args, gb_count_of(args), "");
 }
 
+
 lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool ignore_body) {
 	GB_ASSERT(entity != nullptr);
 	GB_ASSERT(entity->kind == Entity_Procedure);
@@ -163,14 +164,6 @@ lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool ignore_body)
 		break;
 	}
 
-
-
-	// lbCallingConventionKind cc_kind = lbCallingConvention_C;
-	// // TODO(bill): Clean up this logic
-	// if (build_context.metrics.os != TargetOs_js)  {
-	// 	cc_kind = lb_calling_convention_map[pt->Proc.calling_convention];
-	// }
-	// LLVMSetFunctionCallConv(p->value, cc_kind);
 	lbValue proc_value = {p->value, p->type};
 	lb_add_entity(m, entity,  proc_value);
 	lb_add_member(m, p->name, proc_value);
@@ -1049,7 +1042,7 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv,
 			return lb_string_len(p, v);
 		} else if (is_type_array(t)) {
 			GB_PANIC("Array lengths are constant");
-		} else if (is_type_slice(t)) {
+		} else if (is_type_slice(t) || is_type_relative_slice(t)) {
 			return lb_slice_len(p, v);
 		} else if (is_type_dynamic_array(t)) {
 			return lb_dynamic_array_len(p, v);
@@ -1075,7 +1068,7 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv,
 			GB_PANIC("Unreachable");
 		} else if (is_type_array(t)) {
 			GB_PANIC("Array lengths are constant");
-		} else if (is_type_slice(t)) {
+		} else if (is_type_slice(t) || is_type_relative_slice(t)) {
 			return lb_slice_len(p, v);
 		} else if (is_type_dynamic_array(t)) {
 			return lb_dynamic_array_cap(p, v);
@@ -2122,6 +2115,77 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv,
 	case BuiltinProc_objc_find_class:        return lb_handle_objc_find_class(p, expr);
 	case BuiltinProc_objc_register_selector: return lb_handle_objc_register_selector(p, expr);
 	case BuiltinProc_objc_register_class:    return lb_handle_objc_register_class(p, expr);
+
+
+	case BuiltinProc_constant_utf16_cstring:
+		{
+			auto const encode_surrogate_pair = [](Rune r, u16 *r1, u16 *r2) {
+				if (r < 0x10000 || r > 0x10ffff) {
+					*r1 = 0xfffd;
+					*r2 = 0xfffd;
+				} else {
+					r -= 0x10000;
+					*r1 = 0xd800 + ((r>>10)&0x3ff);
+					*r2 = 0xdc00 + (r&0x3ff);
+				}
+			};
+
+			lbModule *m = p->module;
+
+			auto tav = type_and_value_of_expr(ce->args[0]);
+			GB_ASSERT(tav.value.kind == ExactValue_String);
+			String value = tav.value.value_string;
+
+			LLVMTypeRef llvm_u16 = lb_type(m, t_u16);
+
+			isize max_len = value.len*2 + 1;
+			LLVMValueRef *buffer = gb_alloc_array(temporary_allocator(), LLVMValueRef, max_len);
+			isize n = 0;
+			while (value.len > 0) {
+				Rune r = 0;
+				isize w = gb_utf8_decode(value.text, value.len, &r);
+				value.text += w;
+				value.len  -= w;
+				if ((0 <= r && r < 0xd800) || (0xe000 <= r && r < 0x10000)) {
+					buffer[n++] = LLVMConstInt(llvm_u16, cast(u16)r, false);
+				} else if (0x10000 <= r && r <= 0x10ffff) {
+					u16 r1, r2;
+					encode_surrogate_pair(r, &r1, &r2);
+					buffer[n++] = LLVMConstInt(llvm_u16, r1, false);
+					buffer[n++] = LLVMConstInt(llvm_u16, r2, false);
+				} else {
+					buffer[n++] = LLVMConstInt(llvm_u16, 0xfffd, false);
+				}
+			}
+
+			buffer[n++] = LLVMConstInt(llvm_u16, 0, false);
+
+			LLVMValueRef array = LLVMConstArray(llvm_u16, buffer, cast(unsigned int)n);
+
+			char *name = nullptr;
+			{
+				isize max_len = 7+8+1;
+				name = gb_alloc_array(permanent_allocator(), char, max_len);
+				u32 id = m->gen->global_array_index.fetch_add(1);
+				isize len = gb_snprintf(name, max_len, "csbs$%x", id);
+				len -= 1;
+			}
+			LLVMValueRef global_data = LLVMAddGlobal(m->mod, LLVMTypeOf(array), name);
+			LLVMSetInitializer(global_data, array);
+			LLVMSetLinkage(global_data, LLVMInternalLinkage);
+
+
+
+			LLVMValueRef indices[] = {
+				LLVMConstInt(lb_type(m, t_u32), 0, false),
+				LLVMConstInt(lb_type(m, t_u32), 0, false),
+			};
+			lbValue res = {};
+			res.type = tv.type;
+			res.value = LLVMBuildInBoundsGEP(p->builder, global_data, indices, gb_count_of(indices), "");
+			return res;
+
+		}
 	}
 
 	GB_PANIC("Unhandled built-in procedure %.*s", LIT(builtin_procs[id].name));

+ 1 - 1
src/llvm_backend_utility.cpp

@@ -1373,7 +1373,7 @@ lbValue lb_slice_elem(lbProcedure *p, lbValue slice) {
 	return lb_emit_struct_ev(p, slice, 0);
 }
 lbValue lb_slice_len(lbProcedure *p, lbValue slice) {
-	GB_ASSERT(is_type_slice(slice.type));
+	GB_ASSERT(is_type_slice(slice.type) || is_type_relative_slice(slice.type));
 	return lb_emit_struct_ev(p, slice, 1);
 }
 lbValue lb_dynamic_array_elem(lbProcedure *p, lbValue da) {

+ 54 - 46
src/main.cpp

@@ -384,7 +384,7 @@ i32 linker_stage(lbGenerator *gen) {
 			// NOTE(zangent): Sometimes, you have to use -framework on MacOS.
 			//   This allows you to specify '-f' in a #foreign_system_library,
 			//   without having to implement any new syntax specifically for MacOS.
-			#if defined(GB_SYSTEM_OSX)
+			if (build_context.metrics.os == TargetOs_darwin) {
 				if (string_ends_with(lib, str_lit(".framework"))) {
 					// framework thingie
 					String lib_name = lib;
@@ -400,14 +400,14 @@ i32 linker_stage(lbGenerator *gen) {
 					// dynamic or static system lib, just link regularly searching system library paths
 					lib_str = gb_string_append_fmt(lib_str, " -l%.*s ", LIT(lib));
 				}
-			#else
+			} else {
 				// NOTE(vassvik): static libraries (.a files) in linux can be linked to directly using the full path,
 				//                since those are statically linked to at link time. shared libraries (.so) has to be
 				//                available at runtime wherever the executable is run, so we make require those to be
 				//                local to the executable (unless the system collection is used, in which case we search
 				//                the system library paths for the library file).
-				if (string_ends_with(lib, str_lit(".a"))) {
-					// static libs, absolute full path relative to the file in which the lib was imported from
+				if (string_ends_with(lib, str_lit(".a")) || string_ends_with(lib, str_lit(".o"))) {
+					// static libs and object files, absolute full path relative to the file in which the lib was imported from
 					lib_str = gb_string_append_fmt(lib_str, " -l:\"%.*s\" ", LIT(lib));
 				} else if (string_ends_with(lib, str_lit(".so"))) {
 					// dynamic lib, relative path to executable
@@ -418,7 +418,7 @@ i32 linker_stage(lbGenerator *gen) {
 					// dynamic or static system lib, just link regularly searching system library paths
 					lib_str = gb_string_append_fmt(lib_str, " -l%.*s ", LIT(lib));
 				}
-			#endif
+			}
 		}
 
 		gbString object_files = gb_string_make(heap_allocator(), "");
@@ -456,14 +456,15 @@ i32 linker_stage(lbGenerator *gen) {
 			// line arguments prepared previously are incompatible with ld.
 			//
 			// Shared libraries are .dylib on MacOS and .so on Linux.
-			#if defined(GB_SYSTEM_OSX)
+			if (build_context.metrics.os == TargetOs_darwin) {
 				output_ext = STR_LIT(".dylib");
-			#else
+			} else {
 				output_ext = STR_LIT(".so");
-			#endif
+			}
 			link_settings = gb_string_appendc(link_settings, "-Wl,-init,'_odin_entry_point' ");
 			link_settings = gb_string_appendc(link_settings, "-Wl,-fini,'_odin_exit_point' ");
-		} else {
+		} else if (build_context.metrics.os != TargetOs_openbsd) {
+			// OpenBSD defaults to PIE executable. do not pass -no-pie for it.
 			link_settings = gb_string_appendc(link_settings, "-no-pie ");
 		}
 		if (build_context.out_filepath.len > 0) {
@@ -474,34 +475,39 @@ i32 linker_stage(lbGenerator *gen) {
 			}
 		}
 
-		result = system_exec_command_line_app("ld-link",
-			"clang -Wno-unused-command-line-argument %s -o \"%.*s%.*s\" %s "
-			" %s "
-			" %.*s "
-			" %.*s "
-			" %s "
-			#if defined(GB_SYSTEM_OSX)
-				// This sets a requirement of Mountain Lion and up, but the compiler doesn't work without this limit.
-				// NOTE: If you change this (although this minimum is as low as you can go with Odin working)
-				//       make sure to also change the 'mtriple' param passed to 'opt'
-				#if defined(GB_CPU_ARM)
-				" -mmacosx-version-min=12.0.0 "
-				#else
-				" -mmacosx-version-min=10.8.0 "
-				#endif
-				// This points the linker to where the entry point is
-				" -e _main "
-			#endif
-			, object_files, LIT(output_base), LIT(output_ext),
-			#if defined(GB_SYSTEM_OSX)
-			  "-lSystem -lm -Wl,-syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -L/usr/local/lib",
-			#else
-			  "-lc -lm",
-			#endif
-			lib_str,
-			LIT(build_context.link_flags),
-			LIT(build_context.extra_linker_flags),
-			link_settings);
+		gbString platform_lib_str = gb_string_make(heap_allocator(), "");
+		defer (gb_string_free(platform_lib_str));
+		if (build_context.metrics.os == TargetOs_darwin) {
+			platform_lib_str = gb_string_appendc(platform_lib_str, "-lSystem -lm -Wl,-syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -L/usr/local/lib");
+		} else {
+			platform_lib_str = gb_string_appendc(platform_lib_str, "-lc -lm");
+		}
+
+		if (build_context.metrics.os == TargetOs_darwin) {
+			// This sets a requirement of Mountain Lion and up, but the compiler doesn't work without this limit.
+			// NOTE: If you change this (although this minimum is as low as you can go with Odin working)
+			//       make sure to also change the 'mtriple' param passed to 'opt'
+			if (build_context.metrics.arch == TargetArch_arm64) {
+				link_settings = gb_string_appendc(link_settings, " -mmacosx-version-min=12.0.0 ");
+			} else {
+				link_settings = gb_string_appendc(link_settings, " -mmacosx-version-min=10.8.0 ");
+			}
+			// This points the linker to where the entry point is
+			link_settings = gb_string_appendc(link_settings, " -e _main ");
+		}
+
+		gbString link_command_line = gb_string_make(heap_allocator(), "clang -Wno-unused-command-line-argument ");
+		defer (gb_string_free(link_command_line));
+
+		link_command_line = gb_string_appendc(link_command_line, object_files);
+		link_command_line = gb_string_append_fmt(link_command_line, " -o \"%.*s%.*s\" ", LIT(output_base), LIT(output_ext));
+		link_command_line = gb_string_append_fmt(link_command_line, " %s ", platform_lib_str);
+		link_command_line = gb_string_append_fmt(link_command_line, " %s ", lib_str);
+		link_command_line = gb_string_append_fmt(link_command_line, " %.*s ", LIT(build_context.link_flags));
+		link_command_line = gb_string_append_fmt(link_command_line, " %.*s ", LIT(build_context.extra_linker_flags));
+		link_command_line = gb_string_append_fmt(link_command_line, " %s ", link_settings);
+
+		result = system_exec_command_line_app("ld-link", link_command_line);
 
 		if (result) {
 			return result;
@@ -572,14 +578,16 @@ void usage(String argv0) {
 	print_usage_line(0, "Usage:");
 	print_usage_line(1, "%.*s command [arguments]", LIT(argv0));
 	print_usage_line(0, "Commands:");
-	print_usage_line(1, "build     compile .odin file, or directory of .odin files, as an executable.");
-	print_usage_line(1, "          one must contain the program's entry point, all must be in the same package.");
-	print_usage_line(1, "run       same as 'build', but also then runs the newly compiled executable.");
-	print_usage_line(1, "check     parse and type check .odin file");
-	print_usage_line(1, "query     parse, type check, and output a .json file containing information about the program");
-	print_usage_line(1, "doc       generate documentation .odin file, or directory of .odin files");
-	print_usage_line(1, "version   print version");
-	print_usage_line(1, "report    print information useful to reporting a bug");
+	print_usage_line(1, "build             compile .odin file, or directory of .odin files, as an executable.");
+	print_usage_line(1, "                  one must contain the program's entry point, all must be in the same package.");
+	print_usage_line(1, "run               same as 'build', but also then runs the newly compiled executable.");
+	print_usage_line(1, "check             parse, and type check an .odin file, or directory of .odin files");
+	print_usage_line(1, "query             parse, type check, and output a .json file containing information about the program");
+	print_usage_line(1, "strip-semicolon   parse, type check, and remove unneeded semicolons from the entire program");
+	print_usage_line(1, "test              build ands runs procedures with the attribute @(test) in the initial package");
+	print_usage_line(1, "doc               generate documentation .odin file, or directory of .odin files");
+	print_usage_line(1, "version           print version");
+	print_usage_line(1, "report            print information useful to reporting a bug");
 	print_usage_line(0, "");
 	print_usage_line(0, "For further details on a command, use -help after the command name");
 	print_usage_line(1, "e.g. odin build -help");
@@ -2577,7 +2585,7 @@ int main(int arg_count, char const **arg_ptr) {
 	// NOTE(bill): add 'shared' directory if it is not already set
 	if (!find_library_collection_path(str_lit("shared"), nullptr)) {
 		add_library_collection(str_lit("shared"),
-							   get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("shared")));
+			get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("shared")));
 	}
 
 

+ 73 - 56
src/parser.cpp

@@ -57,6 +57,9 @@ isize ast_node_size(AstKind kind) {
 	return align_formula_isize(gb_size_of(AstCommonStuff) + ast_variant_sizes[kind], gb_align_of(void *));
 
 }
+
+gb_global std::atomic<isize> global_total_node_memory_allocated;
+
 // NOTE(bill): And this below is why is I/we need a new language! Discriminated unions are a pain in C/C++
 Ast *alloc_ast_node(AstFile *f, AstKind kind) {
 	gbAllocator a = ast_allocator(f);
@@ -66,6 +69,9 @@ Ast *alloc_ast_node(AstFile *f, AstKind kind) {
 	Ast *node = cast(Ast *)gb_alloc(a, size);
 	node->kind = kind;
 	node->file_id = f ? f->id : 0;
+
+	global_total_node_memory_allocated += size;
+
 	return node;
 }
 
@@ -1532,7 +1538,7 @@ void fix_advance_to_next_stmt(AstFile *f) {
 Token expect_closing(AstFile *f, TokenKind kind, String context) {
 	if (f->curr_token.kind != kind &&
 	    f->curr_token.kind == Token_Semicolon &&
-	    f->curr_token.string == "\n") {
+	    (f->curr_token.string == "\n" || f->curr_token.kind == Token_EOF)) {
 		Token tok = f->prev_token;
 		tok.pos.column += cast(i32)tok.string.len;
 		syntax_error(tok, "Missing ',' before newline in %.*s", LIT(context));
@@ -1554,6 +1560,7 @@ void assign_removal_flag_to_semicolon(AstFile *f) {
 			switch (curr_token->kind) {
 			case Token_CloseBrace:
 			case Token_CloseParen:
+			case Token_EOF:
 				ok = true;
 				break;
 			}
@@ -3009,64 +3016,62 @@ i32 token_precedence(AstFile *f, TokenKind t) {
 
 Ast *parse_binary_expr(AstFile *f, bool lhs, i32 prec_in) {
 	Ast *expr = parse_unary_expr(f, lhs);
-	for (i32 prec = token_precedence(f, f->curr_token.kind); prec >= prec_in; prec--) {
-		for (;;) {
-			Token op = f->curr_token;
-			i32 op_prec = token_precedence(f, op.kind);
-			if (op_prec != prec) {
-				// NOTE(bill): This will also catch operators that are not valid "binary" operators
-				break;
+	for (;;) {
+		Token op = f->curr_token;
+		i32 op_prec = token_precedence(f, op.kind);
+		if (op_prec < prec_in) {
+			// NOTE(bill): This will also catch operators that are not valid "binary" operators
+			break;
+		}
+		Token prev = f->prev_token;
+		switch (op.kind) {
+		case Token_if:
+		case Token_when:
+			if (prev.pos.line < op.pos.line) {
+				// NOTE(bill): Check to see if the `if` or `when` is on the same line of the `lhs` condition
+				goto loop_end;
 			}
-			Token prev = f->prev_token;
+			break;
+		}
+		expect_operator(f); // NOTE(bill): error checks too
+
+		if (op.kind == Token_Question) {
+			Ast *cond = expr;
+			// Token_Question
+			Ast *x = parse_expr(f, lhs);
+			Token token_c = expect_token(f, Token_Colon);
+			Ast *y = parse_expr(f, lhs);
+			expr = ast_ternary_if_expr(f, x, cond, y);
+		} else if (op.kind == Token_if || op.kind == Token_when) {
+			Ast *x = expr;
+			Ast *cond = parse_expr(f, lhs);
+			Token tok_else = expect_token(f, Token_else);
+			Ast *y = parse_expr(f, lhs);
+
 			switch (op.kind) {
 			case Token_if:
+				expr = ast_ternary_if_expr(f, x, cond, y);
+				break;
 			case Token_when:
-				if (prev.pos.line < op.pos.line) {
-					// NOTE(bill): Check to see if the `if` or `when` is on the same line of the `lhs` condition
-					goto loop_end;
-				}
+				expr = ast_ternary_when_expr(f, x, cond, y);
 				break;
 			}
-			expect_operator(f); // NOTE(bill): error checks too
-
-			if (op.kind == Token_Question) {
-				Ast *cond = expr;
-				// Token_Question
-				Ast *x = parse_expr(f, lhs);
-				Token token_c = expect_token(f, Token_Colon);
-				Ast *y = parse_expr(f, lhs);
-				expr = ast_ternary_if_expr(f, x, cond, y);
-			} else if (op.kind == Token_if || op.kind == Token_when) {
-				Ast *x = expr;
-				Ast *cond = parse_expr(f, lhs);
-				Token tok_else = expect_token(f, Token_else);
-				Ast *y = parse_expr(f, lhs);
-				
-				switch (op.kind) {
-				case Token_if:
-					expr = ast_ternary_if_expr(f, x, cond, y);
-					break;
-				case Token_when:
-					expr = ast_ternary_when_expr(f, x, cond, y);
-					break;
-				}
+		} else {
+			Ast *right = parse_binary_expr(f, false, op_prec+1);
+			if (right == nullptr) {
+				syntax_error(op, "Expected expression on the right-hand side of the binary operator '%.*s'", LIT(op.string));
+			}
+			if (op.kind == Token_or_else) {
+				// NOTE(bill): easier to handle its logic different with its own AST kind
+				expr = ast_or_else_expr(f, expr, op, right);
 			} else {
-				Ast *right = parse_binary_expr(f, false, prec+1);
-				if (right == nullptr) {
-					syntax_error(op, "Expected expression on the right-hand side of the binary operator '%.*s'", LIT(op.string));
-				}
-				if (op.kind == Token_or_else) {
-					// NOTE(bill): easier to handle its logic different with its own AST kind
-					expr = ast_or_else_expr(f, expr, op, right);	
-				} else {
-					expr = ast_binary_expr(f, op, expr, right);
-				}
+				expr = ast_binary_expr(f, op, expr, right);
 			}
-
-			lhs = false;
 		}
-		loop_end:;
+
+		lhs = false;
 	}
+	loop_end:;
 	return expr;
 }
 
@@ -3510,12 +3515,13 @@ enum FieldPrefixKind : i32 {
 	FieldPrefix_Unknown = -1,
 	FieldPrefix_Invalid = 0,
 
-	FieldPrefix_using,
+	FieldPrefix_using, // implies #subtype
 	FieldPrefix_const,
 	FieldPrefix_no_alias,
 	FieldPrefix_c_vararg,
 	FieldPrefix_auto_cast,
 	FieldPrefix_any_int,
+	FieldPrefix_subtype, // does not imply `using` semantics
 };
 
 struct ParseFieldPrefixMapping {
@@ -3532,6 +3538,7 @@ gb_global ParseFieldPrefixMapping parse_field_prefix_mappings[] = {
 	{str_lit("c_vararg"),   Token_Hash,      FieldPrefix_c_vararg,  FieldFlag_c_vararg},
 	{str_lit("const"),      Token_Hash,      FieldPrefix_const,     FieldFlag_const},
 	{str_lit("any_int"),    Token_Hash,      FieldPrefix_any_int,   FieldFlag_any_int},
+	{str_lit("subtype"),    Token_Hash,      FieldPrefix_subtype,   FieldFlag_subtype},
 };
 
 
@@ -4849,12 +4856,6 @@ ParseFileError init_ast_file(AstFile *f, String fullpath, TokenPos *err_pos) {
 	f->prev_token = f->tokens[f->prev_token_index];
 	f->curr_token = f->tokens[f->curr_token_index];
 
-	isize const page_size = 4*1024;
-	isize block_size = 2*f->tokens.count*gb_size_of(Ast);
-	block_size = ((block_size + page_size-1)/page_size) * page_size;
-	block_size = gb_clamp(block_size, page_size, DEFAULT_MINIMUM_BLOCK_SIZE);
-	f->arena.minimum_block_size = block_size;
-
 	array_init(&f->comments, heap_allocator(), 0, 0);
 	array_init(&f->imports,  heap_allocator(), 0, 0);
 
@@ -5719,6 +5720,22 @@ ParseFileError parse_packages(Parser *p, String init_filename) {
 			error_line("Expected either a directory or a .odin file, got '%.*s'\n", LIT(init_filename));
 			return ParseFile_WrongExtension;
 		}
+	} else if (init_fullpath.len != 0) {
+		String path = init_fullpath;
+		if (path[path.len-1] == '/') {
+			path.len -= 1;
+		}
+		if ((build_context.command_kind & Command__does_build) &&
+		    build_context.build_mode == BuildMode_Executable) {
+			String short_path = filename_from_path(path);
+			char *cpath = alloc_cstring(heap_allocator(), short_path);
+			defer (gb_free(heap_allocator(), cpath));
+
+			if (gb_file_exists(cpath)) {
+			    	error_line("Please specify the executable name with -out:<string> as a directory exists with the same name in the current working directory");
+			    	return ParseFile_DirectoryAlreadyExists;
+			}
+		}
 	}
 	
 

+ 5 - 5
src/parser.hpp

@@ -46,6 +46,7 @@ enum ParseFileError {
 	ParseFile_InvalidToken,
 	ParseFile_GeneralError,
 	ParseFile_FileTooLarge,
+	ParseFile_DirectoryAlreadyExists,
 
 	ParseFile_Count,
 };
@@ -97,8 +98,6 @@ struct AstFile {
 	AstPackage * pkg;
 	Scope *      scope;
 
-	Arena  arena;
-
 	Ast *        pkg_decl;
 	String       fullpath;
 	Tokenizer    tokenizer;
@@ -300,6 +299,7 @@ enum FieldFlag : u32 {
 	FieldFlag_auto_cast = 1<<4,
 	FieldFlag_const     = 1<<5,
 	FieldFlag_any_int   = 1<<6,
+	FieldFlag_subtype   = 1<<7,
 
 	// Internal use by the parser only
 	FieldFlag_Tags      = 1<<10,
@@ -307,7 +307,7 @@ enum FieldFlag : u32 {
 
 	// Parameter List Restrictions
 	FieldFlag_Signature = FieldFlag_ellipsis|FieldFlag_using|FieldFlag_no_alias|FieldFlag_c_vararg|FieldFlag_auto_cast|FieldFlag_const|FieldFlag_any_int,
-	FieldFlag_Struct    = FieldFlag_using|FieldFlag_Tags,
+	FieldFlag_Struct    = FieldFlag_using|FieldFlag_subtype|FieldFlag_Tags,
 };
 
 enum StmtAllowFlag {
@@ -800,10 +800,10 @@ gb_inline bool is_ast_when_stmt(Ast *node) {
 	return node->kind == Ast_WhenStmt;
 }
 
-gb_global gb_thread_local Arena global_ast_arena = {};
+gb_global gb_thread_local Arena global_thread_local_ast_arena = {};
 
 gbAllocator ast_allocator(AstFile *f) {
-	Arena *arena = f ? &f->arena : &global_ast_arena;
+	Arena *arena = &global_thread_local_ast_arena;
 	return arena_allocator(arena);
 }
 

+ 1 - 1
src/threading.cpp

@@ -486,7 +486,7 @@ void thread_set_name(Thread *t, char const *name) {
 #elif defined(GB_SYSTEM_OSX)
 	// TODO(bill): Test if this works
 	pthread_setname_np(name);
-#elif defined(GB_SYSTEM_FREEBSD)
+#elif defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD)
 	pthread_set_name_np(t->posix_handle, name);
 #else
 	// TODO(bill): Test if this works

+ 40 - 20
src/types.cpp

@@ -703,7 +703,7 @@ struct TypePath;
 i64      type_size_of         (Type *t);
 i64      type_align_of        (Type *t);
 i64      type_offset_of       (Type *t, i32 index);
-gbString type_to_string       (Type *type);
+gbString type_to_string       (Type *type, bool shorthand=false);
 i64      type_size_of_internal(Type *t, TypePath *path);
 void     init_map_internal_types(Type *type);
 Type *   bit_set_to_int(Type *t);
@@ -2334,7 +2334,7 @@ String lookup_subtype_polymorphic_field(Type *dst, Type *src) {
 	GB_ASSERT(is_type_struct(src) || is_type_union(src));
 	for_array(i, src->Struct.fields) {
 		Entity *f = src->Struct.fields[i];
-		if (f->kind == Entity_Variable && f->flags & EntityFlag_Using) {
+		if (f->kind == Entity_Variable && f->flags & EntityFlags_IsSubtype) {
 			if (are_types_identical(dst, f->type)) {
 				return f->token.string;
 			}
@@ -2343,7 +2343,7 @@ String lookup_subtype_polymorphic_field(Type *dst, Type *src) {
 					return f->token.string;
 				}
 			}
-			if (is_type_struct(f->type)) {
+			if ((f->flags & EntityFlag_Using) != 0 && is_type_struct(f->type)) {
 				String name = lookup_subtype_polymorphic_field(dst, f->type);
 				if (name.len > 0) {
 					return name;
@@ -2489,9 +2489,9 @@ bool are_types_identical_internal(Type *x, Type *y, bool check_tuple_names) {
 					if (xf->token.string != yf->token.string) {
 						return false;
 					}
-					bool xf_is_using = (xf->flags&EntityFlag_Using) != 0;
-					bool yf_is_using = (yf->flags&EntityFlag_Using) != 0;
-					if (xf_is_using ^ yf_is_using) {
+					u64 xf_flags = (xf->flags&EntityFlags_IsSubtype);
+					u64 yf_flags = (yf->flags&EntityFlags_IsSubtype);
+					if (xf_flags != yf_flags) {
 						return false;
 					}
 				}
@@ -2623,6 +2623,17 @@ i64 union_tag_size(Type *u) {
 
 	// TODO(bill): Is this an okay approach?
 	i64 max_align = 1;
+
+	if (u->Union.variants.count < 1ull<<8) {
+		max_align = 1;
+	} else if (u->Union.variants.count < 1ull<<16) {
+		max_align = 2;
+	} else if (u->Union.variants.count < 1ull<<32) {
+		max_align = 4;
+	} else {
+		GB_PANIC("how many variants do you have?!");
+	}
+
 	for_array(i, u->Union.variants) {
 		Type *variant_type = u->Union.variants[i];
 		i64 align = type_align_of(variant_type);
@@ -3813,7 +3824,7 @@ isize check_is_assignable_to_using_subtype(Type *src, Type *dst, isize level = 0
 
 	for_array(i, src->Struct.fields) {
 		Entity *f = src->Struct.fields[i];
-		if (f->kind != Entity_Variable || (f->flags&EntityFlag_Using) == 0) {
+		if (f->kind != Entity_Variable || (f->flags&EntityFlags_IsSubtype) == 0) {
 			continue;
 		}
 
@@ -3925,7 +3936,7 @@ Type *alloc_type_proc_from_types(Type **param_types, unsigned param_count, Type
 
 
 
-gbString write_type_to_string(gbString str, Type *type) {
+gbString write_type_to_string(gbString str, Type *type, bool shorthand=false) {
 	if (type == nullptr) {
 		return gb_string_appendc(str, "<no type>");
 	}
@@ -4040,15 +4051,21 @@ gbString write_type_to_string(gbString str, Type *type) {
 		if (type->Struct.is_raw_union) str = gb_string_appendc(str, " #raw_union");
 		if (type->Struct.custom_align != 0) str = gb_string_append_fmt(str, " #align %d", cast(int)type->Struct.custom_align);
 		str = gb_string_appendc(str, " {");
-		for_array(i, type->Struct.fields) {
-			Entity *f = type->Struct.fields[i];
-			GB_ASSERT(f->kind == Entity_Variable);
-			if (i > 0) {
-				str = gb_string_appendc(str, ", ");
+
+
+		if (shorthand && type->Struct.fields.count > 16) {
+			str = gb_string_append_fmt(str, "%lld fields...", cast(long long)type->Struct.fields.count);
+		} else {
+			for_array(i, type->Struct.fields) {
+				Entity *f = type->Struct.fields[i];
+				GB_ASSERT(f->kind == Entity_Variable);
+				if (i > 0) {
+					str = gb_string_appendc(str, ", ");
+				}
+				str = gb_string_append_length(str, f->token.string.text, f->token.string.len);
+				str = gb_string_appendc(str, ": ");
+				str = write_type_to_string(str, f->type);
 			}
-			str = gb_string_append_length(str, f->token.string.text, f->token.string.len);
-			str = gb_string_appendc(str, ": ");
-			str = write_type_to_string(str, f->type);
 		}
 		str = gb_string_append_rune(str, '}');
 	} break;
@@ -4223,13 +4240,16 @@ gbString write_type_to_string(gbString str, Type *type) {
 }
 
 
-gbString type_to_string(Type *type, gbAllocator allocator) {
-	return write_type_to_string(gb_string_make(allocator, ""), type);
+gbString type_to_string(Type *type, gbAllocator allocator, bool shorthand=false) {
+	return write_type_to_string(gb_string_make(allocator, ""), type, shorthand);
 }
-gbString type_to_string(Type *type) {
-	return write_type_to_string(gb_string_make(heap_allocator(), ""), type);
+gbString type_to_string(Type *type, bool shorthand) {
+	return write_type_to_string(gb_string_make(heap_allocator(), ""), type, shorthand);
 }
 
+gbString type_to_string_shorthand(Type *type) {
+	return type_to_string(type, true);
+}
 
 
 

+ 6 - 2
tests/core/Makefile

@@ -1,7 +1,7 @@
 ODIN=../../odin
 PYTHON=$(shell which python3)
 
-all: download_test_assets image_test compress_test strings_test hash_test crypto_test noise_test
+all: download_test_assets image_test compress_test strings_test hash_test crypto_test noise_test encoding_test
 
 download_test_assets:
 	$(PYTHON) download_assets.py
@@ -22,4 +22,8 @@ crypto_test:
 	$(ODIN) run crypto -out=crypto_hash -o:speed -no-bounds-check
 
 noise_test:
-	$(ODIN) run math/noise -out=test_noise
+	$(ODIN) run math/noise -out=test_noise
+
+encoding_test:
+	$(ODIN) run encoding/json -out=test_json
+	$(ODIN) run encoding/varint -out=test_varint

+ 2 - 1
tests/core/build.bat

@@ -35,7 +35,8 @@ echo ---
 echo ---
 echo Running core:encoding tests
 echo ---
-%PATH_TO_ODIN% run encoding %COMMON%
+%PATH_TO_ODIN% run encoding/json %COMMON%
+%PATH_TO_ODIN% run encoding/varint %COMMON%
 
 echo ---
 echo Running core:math/noise tests

+ 4 - 3
tests/core/compress/test_core_compress.odin

@@ -30,14 +30,12 @@ when ODIN_TEST {
 	log     :: testing.log
 } else {
 	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-		fmt.printf("[%v] ", loc)
 		TEST_count += 1
 		if !condition {
 			TEST_fail += 1
-			fmt.println(message)
+			fmt.printf("[%v] %v\n", loc, message)
 			return
 		}
-		fmt.println(" PASS")
 	}
 	log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
 		fmt.printf("[%v] ", loc)
@@ -52,6 +50,9 @@ main :: proc() {
 	gzip_test(&t)
 
 	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
+	if TEST_fail > 0 {
+		os.exit(1)
+	}
 }
 
 @test

+ 895 - 850
tests/core/crypto/test_core_crypto.odin

@@ -1,15 +1,15 @@
 package test_core_crypto
 
 /*
-    Copyright 2021 zhibog
-    Made available under the BSD-3 license.
+	Copyright 2021 zhibog
+	Made available under the BSD-3 license.
 
-    List of contributors:
-        zhibog, dotbmp:  Initial implementation.
-        Jeroen van Rijn: Test runner setup.
+	List of contributors:
+		zhibog, dotbmp:  Initial implementation.
+		Jeroen van Rijn: Test runner setup.
 
-    Tests for the hashing algorithms within the crypto library.
-    Where possible, the official test vectors are used to validate the implementation.
+	Tests for the hashing algorithms within the crypto library.
+	Where possible, the official test vectors are used to validate the implementation.
 */
 
 import "core:testing"
@@ -36,1070 +36,1115 @@ import "core:crypto/sm3"
 import "core:crypto/jh"
 import "core:crypto/groestl"
 import "core:crypto/haval"
+import "core:crypto/siphash"
+import "core:os"
 
 TEST_count := 0
 TEST_fail  := 0
 
 when ODIN_TEST {
-    expect  :: testing.expect
-    log     :: testing.log
+	expect  :: testing.expect
+	log     :: testing.log
 } else {
-    expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-        fmt.printf("[%v] ", loc)
-        TEST_count += 1
-        if !condition {
-            TEST_fail += 1
-            fmt.println(message)
-            return
-        }
-        fmt.println(" PASS")
-    }
-    log :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-        fmt.printf("[%v] ", loc)
-        fmt.printf("log: %v\n", v)
-    }
+	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
+		TEST_count += 1
+		if !condition {
+			TEST_fail += 1
+			fmt.printf("[%v] %v\n", loc, message)
+			return
+		}
+	}
+	log :: proc(t: ^testing.T, v: any, loc := #caller_location) {
+		fmt.printf("[%v] ", loc)
+		fmt.printf("log: %v\n", v)
+	}
 }
 
 main :: proc() {
-    t := testing.T{}
-    test_md2(&t)
-    test_md4(&t)
-    test_md5(&t)
-    test_sha1(&t)
-    test_sha224(&t)
-    test_sha256(&t)
-    test_sha384(&t)
-    test_sha512(&t)
-    test_sha3_224(&t)
-    test_sha3_256(&t)
-    test_sha3_384(&t)
-    test_sha3_512(&t)
-    test_shake_128(&t)
-    test_shake_256(&t)
-    test_keccak_224(&t)
-    test_keccak_256(&t)
-    test_keccak_384(&t)
-    test_keccak_512(&t)
-    test_whirlpool(&t)
-    test_gost(&t)
-    test_streebog_256(&t)
-    test_streebog_512(&t)
-    test_blake_224(&t)
-    test_blake_256(&t)
-    test_blake_384(&t)
-    test_blake_512(&t)
-    test_blake2b(&t)
-    test_blake2s(&t)
-    test_ripemd_128(&t)
-    test_ripemd_160(&t)
-    test_ripemd_256(&t)
-    test_ripemd_320(&t)
-    test_tiger_128(&t)
-    test_tiger_160(&t)
-    test_tiger_192(&t)
-    test_tiger2_128(&t)
-    test_tiger2_160(&t)
-    test_tiger2_192(&t)
-    test_sm3(&t)
-    test_jh_224(&t)
-    test_jh_256(&t)
-    test_jh_384(&t)
-    test_jh_512(&t)
-    test_groestl_224(&t)
-    test_groestl_256(&t)
-    test_groestl_384(&t)
-    test_groestl_512(&t)
-    test_haval_128(&t)
-    test_haval_160(&t)
-    test_haval_192(&t)
-    test_haval_224(&t)
-    test_haval_256(&t)
-
-    // "modern" crypto tests
-    test_chacha20(&t)
-    test_poly1305(&t)
-    test_chacha20poly1305(&t)
-    test_x25519(&t)
-    test_rand_bytes(&t)
-
-    bench_modern(&t)
-
-    fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
+	t := testing.T{}
+	test_md2(&t)
+	test_md4(&t)
+	test_md5(&t)
+	test_sha1(&t)
+	test_sha224(&t)
+	test_sha256(&t)
+	test_sha384(&t)
+	test_sha512(&t)
+	test_sha3_224(&t)
+	test_sha3_256(&t)
+	test_sha3_384(&t)
+	test_sha3_512(&t)
+	test_shake_128(&t)
+	test_shake_256(&t)
+	test_keccak_224(&t)
+	test_keccak_256(&t)
+	test_keccak_384(&t)
+	test_keccak_512(&t)
+	test_whirlpool(&t)
+	test_gost(&t)
+	test_streebog_256(&t)
+	test_streebog_512(&t)
+	test_blake_224(&t)
+	test_blake_256(&t)
+	test_blake_384(&t)
+	test_blake_512(&t)
+	test_blake2b(&t)
+	test_blake2s(&t)
+	test_ripemd_128(&t)
+	test_ripemd_160(&t)
+	test_ripemd_256(&t)
+	test_ripemd_320(&t)
+	test_tiger_128(&t)
+	test_tiger_160(&t)
+	test_tiger_192(&t)
+	test_tiger2_128(&t)
+	test_tiger2_160(&t)
+	test_tiger2_192(&t)
+	test_sm3(&t)
+	test_jh_224(&t)
+	test_jh_256(&t)
+	test_jh_384(&t)
+	test_jh_512(&t)
+	test_groestl_224(&t)
+	test_groestl_256(&t)
+	test_groestl_384(&t)
+	test_groestl_512(&t)
+	test_haval_128(&t)
+	test_haval_160(&t)
+	test_haval_192(&t)
+	test_haval_224(&t)
+	test_haval_256(&t)
+	test_siphash_2_4(&t)
+
+	// "modern" crypto tests
+	test_chacha20(&t)
+	test_poly1305(&t)
+	test_chacha20poly1305(&t)
+	test_x25519(&t)
+	test_rand_bytes(&t)
+
+	bench_modern(&t)
+
+	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
+	if TEST_fail > 0 {
+		os.exit(1)
+	}
 }
 
 TestHash :: struct {
-    hash: string,
-    str:  string,
+	hash: string,
+	str:  string,
 }
 
 hex_string :: proc(bytes: []byte, allocator := context.temp_allocator) -> string {
-    lut: [16]byte = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}
-    buf := make([]byte, len(bytes) * 2, allocator)
-    for i := 0; i < len(bytes); i += 1 {
-        buf[i * 2 + 0] = lut[bytes[i] >> 4 & 0xf]
-        buf[i * 2 + 1] = lut[bytes[i]      & 0xf]
-    }
-    return string(buf)
+	lut: [16]byte = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}
+	buf := make([]byte, len(bytes) * 2, allocator)
+	for i := 0; i < len(bytes); i += 1 {
+		buf[i * 2 + 0] = lut[bytes[i] >> 4 & 0xf]
+		buf[i * 2 + 1] = lut[bytes[i]      & 0xf]
+	}
+	return string(buf)
 }
 
 @(test)
 test_md2 :: proc(t: ^testing.T) {
-    // Official test vectors from https://datatracker.ietf.org/doc/html/rfc1319
-    test_vectors := [?]TestHash {
-        TestHash{"8350e5a3e24c153df2275c9f80692773", ""},
-        TestHash{"32ec01ec4a6dac72c0ab96fb34c0b5d1", "a"},
-        TestHash{"da853b0d3f88d99b30283a69e6ded6bb", "abc"},
-        TestHash{"ab4f496bfb2a530b219ff33031fe06b0", "message digest"},
-        TestHash{"4e8ddff3650292ab5a4108c3aa47940b", "abcdefghijklmnopqrstuvwxyz"},
-        TestHash{"da33def2a42df13975352846c30338cd", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
-        TestHash{"d5976f79d83d3a0dc9806c3c66f3efd8", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
-    }
-    for v, _ in test_vectors {
-        computed     := md2.hash(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	// Official test vectors from https://datatracker.ietf.org/doc/html/rfc1319
+	test_vectors := [?]TestHash {
+		TestHash{"8350e5a3e24c153df2275c9f80692773", ""},
+		TestHash{"32ec01ec4a6dac72c0ab96fb34c0b5d1", "a"},
+		TestHash{"da853b0d3f88d99b30283a69e6ded6bb", "abc"},
+		TestHash{"ab4f496bfb2a530b219ff33031fe06b0", "message digest"},
+		TestHash{"4e8ddff3650292ab5a4108c3aa47940b", "abcdefghijklmnopqrstuvwxyz"},
+		TestHash{"da33def2a42df13975352846c30338cd", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
+		TestHash{"d5976f79d83d3a0dc9806c3c66f3efd8", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
+	}
+	for v, _ in test_vectors {
+		computed     := md2.hash(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_md4 :: proc(t: ^testing.T) {
-    // Official test vectors from https://datatracker.ietf.org/doc/html/rfc1320
-    test_vectors := [?]TestHash {
-        TestHash{"31d6cfe0d16ae931b73c59d7e0c089c0", ""},
-        TestHash{"bde52cb31de33e46245e05fbdbd6fb24", "a"},
-        TestHash{"a448017aaf21d8525fc10ae87aa6729d", "abc"},
-        TestHash{"d9130a8164549fe818874806e1c7014b", "message digest"},
-        TestHash{"d79e1c308aa5bbcdeea8ed63df412da9", "abcdefghijklmnopqrstuvwxyz"},
-        TestHash{"043f8582f241db351ce627e153e7f0e4", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
-        TestHash{"e33b4ddc9c38f2199c3e7b164fcc0536", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
-    }
-    for v, _ in test_vectors {
-        computed     := md4.hash(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	// Official test vectors from https://datatracker.ietf.org/doc/html/rfc1320
+	test_vectors := [?]TestHash {
+		TestHash{"31d6cfe0d16ae931b73c59d7e0c089c0", ""},
+		TestHash{"bde52cb31de33e46245e05fbdbd6fb24", "a"},
+		TestHash{"a448017aaf21d8525fc10ae87aa6729d", "abc"},
+		TestHash{"d9130a8164549fe818874806e1c7014b", "message digest"},
+		TestHash{"d79e1c308aa5bbcdeea8ed63df412da9", "abcdefghijklmnopqrstuvwxyz"},
+		TestHash{"043f8582f241db351ce627e153e7f0e4", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
+		TestHash{"e33b4ddc9c38f2199c3e7b164fcc0536", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
+	}
+	for v, _ in test_vectors {
+		computed     := md4.hash(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_md5 :: proc(t: ^testing.T) {
-    // Official test vectors from https://datatracker.ietf.org/doc/html/rfc1321
-    test_vectors := [?]TestHash {
-        TestHash{"d41d8cd98f00b204e9800998ecf8427e", ""},
-        TestHash{"0cc175b9c0f1b6a831c399e269772661", "a"},
-        TestHash{"900150983cd24fb0d6963f7d28e17f72", "abc"},
-        TestHash{"f96b697d7cb7938d525a2f31aaf161d0", "message digest"},
-        TestHash{"c3fcd3d76192e4007dfb496cca67e13b", "abcdefghijklmnopqrstuvwxyz"},
-        TestHash{"d174ab98d277d9f5a5611c2c9f419d9f", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
-        TestHash{"57edf4a22be3c955ac49da2e2107b67a", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
-    }
-    for v, _ in test_vectors {
-        computed     := md5.hash(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	// Official test vectors from https://datatracker.ietf.org/doc/html/rfc1321
+	test_vectors := [?]TestHash {
+		TestHash{"d41d8cd98f00b204e9800998ecf8427e", ""},
+		TestHash{"0cc175b9c0f1b6a831c399e269772661", "a"},
+		TestHash{"900150983cd24fb0d6963f7d28e17f72", "abc"},
+		TestHash{"f96b697d7cb7938d525a2f31aaf161d0", "message digest"},
+		TestHash{"c3fcd3d76192e4007dfb496cca67e13b", "abcdefghijklmnopqrstuvwxyz"},
+		TestHash{"d174ab98d277d9f5a5611c2c9f419d9f", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
+		TestHash{"57edf4a22be3c955ac49da2e2107b67a", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
+	}
+	for v, _ in test_vectors {
+		computed     := md5.hash(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_sha1 :: proc(t: ^testing.T) {
-    // Test vectors from 
-    // https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
-    // https://www.di-mgt.com.au/sha_testvectors.html
-    test_vectors := [?]TestHash {
-        TestHash{"da39a3ee5e6b4b0d3255bfef95601890afd80709", ""},
-        TestHash{"a9993e364706816aba3e25717850c26c9cd0d89d", "abc"},
-        TestHash{"f9537c23893d2014f365adf8ffe33b8eb0297ed1", "abcdbcdecdefdefgefghfghighijhi"},
-        TestHash{"346fb528a24b48f563cb061470bcfd23740427ad", "jkijkljklmklmnlmnomnopnopq"},
-        TestHash{"86f7e437faa5a7fce15d1ddcb9eaeaea377667b8", "a"},
-        TestHash{"c729c8996ee0a6f74f4f3248e8957edf704fb624", "01234567012345670123456701234567"},
-        TestHash{"84983e441c3bd26ebaae4aa1f95129e5e54670f1", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
-        TestHash{"a49b2446a02c645bf419f995b67091253a04a259", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"},
-    }
-    for v, _ in test_vectors {
-        computed     := sha1.hash(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	// Test vectors from
+	// https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
+	// https://www.di-mgt.com.au/sha_testvectors.html
+	test_vectors := [?]TestHash {
+		TestHash{"da39a3ee5e6b4b0d3255bfef95601890afd80709", ""},
+		TestHash{"a9993e364706816aba3e25717850c26c9cd0d89d", "abc"},
+		TestHash{"f9537c23893d2014f365adf8ffe33b8eb0297ed1", "abcdbcdecdefdefgefghfghighijhi"},
+		TestHash{"346fb528a24b48f563cb061470bcfd23740427ad", "jkijkljklmklmnlmnomnopnopq"},
+		TestHash{"86f7e437faa5a7fce15d1ddcb9eaeaea377667b8", "a"},
+		TestHash{"c729c8996ee0a6f74f4f3248e8957edf704fb624", "01234567012345670123456701234567"},
+		TestHash{"84983e441c3bd26ebaae4aa1f95129e5e54670f1", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
+		TestHash{"a49b2446a02c645bf419f995b67091253a04a259", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"},
+	}
+	for v, _ in test_vectors {
+		computed     := sha1.hash(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_sha224 :: proc(t: ^testing.T) {
-    // Test vectors from 
-    // https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
-    // https://www.di-mgt.com.au/sha_testvectors.html
-    test_vectors := [?]TestHash {
-        TestHash{"d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f", ""},
-        TestHash{"23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7", "abc"},
-        TestHash{"75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
-        TestHash{"c97ca9a559850ce97a04a96def6d99a9e0e0e2ab14e6b8df265fc0b3", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"},
-    }
-    for v, _ in test_vectors {
-        computed     := sha2.hash_224(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	// Test vectors from
+	// https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
+	// https://www.di-mgt.com.au/sha_testvectors.html
+	test_vectors := [?]TestHash {
+		TestHash{"d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f", ""},
+		TestHash{"23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7", "abc"},
+		TestHash{"75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
+		TestHash{"c97ca9a559850ce97a04a96def6d99a9e0e0e2ab14e6b8df265fc0b3", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"},
+	}
+	for v, _ in test_vectors {
+		computed     := sha2.hash_224(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_sha256 :: proc(t: ^testing.T) {
-    // Test vectors from 
-    // https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
-    // https://www.di-mgt.com.au/sha_testvectors.html
-    test_vectors := [?]TestHash {
-        TestHash{"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", ""},
-        TestHash{"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", "abc"},
-        TestHash{"248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
-        TestHash{"cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"},
-    }
-    for v, _ in test_vectors {
-        computed     := sha2.hash_256(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	// Test vectors from
+	// https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
+	// https://www.di-mgt.com.au/sha_testvectors.html
+	test_vectors := [?]TestHash {
+		TestHash{"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", ""},
+		TestHash{"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", "abc"},
+		TestHash{"248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
+		TestHash{"cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"},
+	}
+	for v, _ in test_vectors {
+		computed     := sha2.hash_256(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_sha384 :: proc(t: ^testing.T) {
-    // Test vectors from 
-    // https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
-    // https://www.di-mgt.com.au/sha_testvectors.html
-    test_vectors := [?]TestHash {
-        TestHash{"38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b", ""},
-        TestHash{"cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7", "abc"},
-        TestHash{"3391fdddfc8dc7393707a65b1b4709397cf8b1d162af05abfe8f450de5f36bc6b0455a8520bc4e6f5fe95b1fe3c8452b", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
-        TestHash{"09330c33f71147e83d192fc782cd1b4753111b173b3b05d22fa08086e3b0f712fcc7c71a557e2db966c3e9fa91746039", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"},
-    }
-    for v, _ in test_vectors {
-        computed     := sha2.hash_384(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	// Test vectors from
+	// https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
+	// https://www.di-mgt.com.au/sha_testvectors.html
+	test_vectors := [?]TestHash {
+		TestHash{"38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b", ""},
+		TestHash{"cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7", "abc"},
+		TestHash{"3391fdddfc8dc7393707a65b1b4709397cf8b1d162af05abfe8f450de5f36bc6b0455a8520bc4e6f5fe95b1fe3c8452b", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
+		TestHash{"09330c33f71147e83d192fc782cd1b4753111b173b3b05d22fa08086e3b0f712fcc7c71a557e2db966c3e9fa91746039", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"},
+	}
+	for v, _ in test_vectors {
+		computed     := sha2.hash_384(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_sha512 :: proc(t: ^testing.T) {
-    // Test vectors from 
-    // https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
-    // https://www.di-mgt.com.au/sha_testvectors.html
-    test_vectors := [?]TestHash {
-        TestHash{"cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", ""},
-        TestHash{"ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f", "abc"},
-        TestHash{"204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
-        TestHash{"8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"},
-    }
-    for v, _ in test_vectors {
-        computed     := sha2.hash_512(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	// Test vectors from
+	// https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
+	// https://www.di-mgt.com.au/sha_testvectors.html
+	test_vectors := [?]TestHash {
+		TestHash{"cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", ""},
+		TestHash{"ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f", "abc"},
+		TestHash{"204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
+		TestHash{"8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"},
+	}
+	for v, _ in test_vectors {
+		computed     := sha2.hash_512(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_sha3_224 :: proc(t: ^testing.T) {
-    // Test vectors from 
-    // https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
-    // https://www.di-mgt.com.au/sha_testvectors.html
-    test_vectors := [?]TestHash {
-        TestHash{"6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7", ""},
-        TestHash{"e642824c3f8cf24ad09234ee7d3c766fc9a3a5168d0c94ad73b46fdf", "abc"},
-        TestHash{"10241ac5187380bd501192e4e56b5280908727dd8fe0d10d4e5ad91e", "abcdbcdecdefdefgefghfghighijhi"},
-        TestHash{"fd645fe07d814c397e85e85f92fe58b949f55efa4d3468b2468da45a", "jkijkljklmklmnlmnomnopnopq"},
-        TestHash{"9e86ff69557ca95f405f081269685b38e3a819b309ee942f482b6a8b", "a"},
-        TestHash{"6961f694b2ff3ed6f0c830d2c66da0c5e7ca9445f7c0dca679171112", "01234567012345670123456701234567"},
-        TestHash{"8a24108b154ada21c9fd5574494479ba5c7e7ab76ef264ead0fcce33", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
-        TestHash{"543e6868e1666c1a643630df77367ae5a62a85070a51c14cbf665cbc", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"},
-    }
-    for v, _ in test_vectors {
-        computed     := sha3.hash_224(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	// Test vectors from
+	// https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
+	// https://www.di-mgt.com.au/sha_testvectors.html
+	test_vectors := [?]TestHash {
+		TestHash{"6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7", ""},
+		TestHash{"e642824c3f8cf24ad09234ee7d3c766fc9a3a5168d0c94ad73b46fdf", "abc"},
+		TestHash{"10241ac5187380bd501192e4e56b5280908727dd8fe0d10d4e5ad91e", "abcdbcdecdefdefgefghfghighijhi"},
+		TestHash{"fd645fe07d814c397e85e85f92fe58b949f55efa4d3468b2468da45a", "jkijkljklmklmnlmnomnopnopq"},
+		TestHash{"9e86ff69557ca95f405f081269685b38e3a819b309ee942f482b6a8b", "a"},
+		TestHash{"6961f694b2ff3ed6f0c830d2c66da0c5e7ca9445f7c0dca679171112", "01234567012345670123456701234567"},
+		TestHash{"8a24108b154ada21c9fd5574494479ba5c7e7ab76ef264ead0fcce33", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
+		TestHash{"543e6868e1666c1a643630df77367ae5a62a85070a51c14cbf665cbc", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"},
+	}
+	for v, _ in test_vectors {
+		computed     := sha3.hash_224(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_sha3_256 :: proc(t: ^testing.T) {
-    // Test vectors from 
-    // https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
-    // https://www.di-mgt.com.au/sha_testvectors.html
-    test_vectors := [?]TestHash {
-        TestHash{"a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a", ""},
-        TestHash{"3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532", "abc"},
-        TestHash{"565ada1ced21278cfaffdde00dea0107964121ac25e4e978abc59412be74550a", "abcdbcdecdefdefgefghfghighijhi"},
-        TestHash{"8cc1709d520f495ce972ece48b0d2e1f74ec80d53bc5c47457142158fae15d98", "jkijkljklmklmnlmnomnopnopq"},
-        TestHash{"80084bf2fba02475726feb2cab2d8215eab14bc6bdd8bfb2c8151257032ecd8b", "a"},
-        TestHash{"e4786de5f88f7d374b7288f225ea9f2f7654da200bab5d417e1fb52d49202767", "01234567012345670123456701234567"},
-        TestHash{"41c0dba2a9d6240849100376a8235e2c82e1b9998a999e21db32dd97496d3376", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
-        TestHash{"916f6061fe879741ca6469b43971dfdb28b1a32dc36cb3254e812be27aad1d18", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"},
-    }
-    for v, _ in test_vectors {
-        computed     := sha3.hash_256(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	// Test vectors from
+	// https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
+	// https://www.di-mgt.com.au/sha_testvectors.html
+	test_vectors := [?]TestHash {
+		TestHash{"a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a", ""},
+		TestHash{"3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532", "abc"},
+		TestHash{"565ada1ced21278cfaffdde00dea0107964121ac25e4e978abc59412be74550a", "abcdbcdecdefdefgefghfghighijhi"},
+		TestHash{"8cc1709d520f495ce972ece48b0d2e1f74ec80d53bc5c47457142158fae15d98", "jkijkljklmklmnlmnomnopnopq"},
+		TestHash{"80084bf2fba02475726feb2cab2d8215eab14bc6bdd8bfb2c8151257032ecd8b", "a"},
+		TestHash{"e4786de5f88f7d374b7288f225ea9f2f7654da200bab5d417e1fb52d49202767", "01234567012345670123456701234567"},
+		TestHash{"41c0dba2a9d6240849100376a8235e2c82e1b9998a999e21db32dd97496d3376", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
+		TestHash{"916f6061fe879741ca6469b43971dfdb28b1a32dc36cb3254e812be27aad1d18", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"},
+	}
+	for v, _ in test_vectors {
+		computed     := sha3.hash_256(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_sha3_384 :: proc(t: ^testing.T) {
-    // Test vectors from 
-    // https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
-    // https://www.di-mgt.com.au/sha_testvectors.html
-    test_vectors := [?]TestHash {
-        TestHash{"0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004", ""},
-        TestHash{"ec01498288516fc926459f58e2c6ad8df9b473cb0fc08c2596da7cf0e49be4b298d88cea927ac7f539f1edf228376d25", "abc"},
-        TestHash{"9aa92dbb716ebb573def0d5e3cdd28d6add38ada310b602b8916e690a3257b7144e5ddd3d0dbbc559c48480d34d57a9a", "abcdbcdecdefdefgefghfghighijhi"},
-        TestHash{"77c90323d7392bcdee8a3e7f74f19f47b7d1b1a825ac6a2d8d882a72317879cc26597035f1fc24fe65090b125a691282", "jkijkljklmklmnlmnomnopnopq"},
-        TestHash{"1815f774f320491b48569efec794d249eeb59aae46d22bf77dafe25c5edc28d7ea44f93ee1234aa88f61c91912a4ccd9", "a"},
-        TestHash{"51072590ad4c51b27ff8265590d74f92de7cc55284168e414ca960087c693285b08a283c6b19d77632994cb9eb93f1be", "01234567012345670123456701234567"},
-        TestHash{"991c665755eb3a4b6bbdfb75c78a492e8c56a22c5c4d7e429bfdbc32b9d4ad5aa04a1f076e62fea19eef51acd0657c22", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
-        TestHash{"79407d3b5916b59c3e30b09822974791c313fb9ecc849e406f23592d04f625dc8c709b98b43b3852b337216179aa7fc7", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"},
-    }
-    for v, _ in test_vectors {
-        computed     := sha3.hash_384(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	// Test vectors from
+	// https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
+	// https://www.di-mgt.com.au/sha_testvectors.html
+	test_vectors := [?]TestHash {
+		TestHash{"0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004", ""},
+		TestHash{"ec01498288516fc926459f58e2c6ad8df9b473cb0fc08c2596da7cf0e49be4b298d88cea927ac7f539f1edf228376d25", "abc"},
+		TestHash{"9aa92dbb716ebb573def0d5e3cdd28d6add38ada310b602b8916e690a3257b7144e5ddd3d0dbbc559c48480d34d57a9a", "abcdbcdecdefdefgefghfghighijhi"},
+		TestHash{"77c90323d7392bcdee8a3e7f74f19f47b7d1b1a825ac6a2d8d882a72317879cc26597035f1fc24fe65090b125a691282", "jkijkljklmklmnlmnomnopnopq"},
+		TestHash{"1815f774f320491b48569efec794d249eeb59aae46d22bf77dafe25c5edc28d7ea44f93ee1234aa88f61c91912a4ccd9", "a"},
+		TestHash{"51072590ad4c51b27ff8265590d74f92de7cc55284168e414ca960087c693285b08a283c6b19d77632994cb9eb93f1be", "01234567012345670123456701234567"},
+		TestHash{"991c665755eb3a4b6bbdfb75c78a492e8c56a22c5c4d7e429bfdbc32b9d4ad5aa04a1f076e62fea19eef51acd0657c22", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
+		TestHash{"79407d3b5916b59c3e30b09822974791c313fb9ecc849e406f23592d04f625dc8c709b98b43b3852b337216179aa7fc7", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"},
+	}
+	for v, _ in test_vectors {
+		computed     := sha3.hash_384(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_sha3_512 :: proc(t: ^testing.T) {
-    // Test vectors from 
-    // https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
-    // https://www.di-mgt.com.au/sha_testvectors.html
-    test_vectors := [?]TestHash {
-        TestHash{"a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26", ""},
-        TestHash{"b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e10e116e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0", "abc"},
-        TestHash{"9f9a327944a35988d67effc4fa748b3c07744f736ac70b479d8e12a3d10d6884d00a7ef593690305462e9e9030a67c51636fd346fd8fa0ee28a5ac2aee103d2e", "abcdbcdecdefdefgefghfghighijhi"},
-        TestHash{"dbb124a0deda966eb4d199d0844fa0beb0770ea1ccddabcd335a7939a931ac6fb4fa6aebc6573f462ced2e4e7178277803be0d24d8bc2864626d9603109b7891", "jkijkljklmklmnlmnomnopnopq"},
-        TestHash{"697f2d856172cb8309d6b8b97dac4de344b549d4dee61edfb4962d8698b7fa803f4f93ff24393586e28b5b957ac3d1d369420ce53332712f997bd336d09ab02a", "a"},
-        TestHash{"5679e353bc8eeea3e801ca60448b249bcfd3ac4a6c3abe429a807bcbd4c9cd12da87a5a9dc74fde64c0d44718632cae966b078397c6f9ec155c6a238f2347cf1", "01234567012345670123456701234567"},
-        TestHash{"04a371e84ecfb5b8b77cb48610fca8182dd457ce6f326a0fd3d7ec2f1e91636dee691fbe0c985302ba1b0d8dc78c086346b533b49c030d99a27daf1139d6e75e", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
-        TestHash{"afebb2ef542e6579c50cad06d2e578f9f8dd6881d7dc824d26360feebf18a4fa73e3261122948efcfd492e74e82e2189ed0fb440d187f382270cb455f21dd185", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"},
-    }
-    for v, _ in test_vectors {
-        computed     := sha3.hash_512(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	// Test vectors from
+	// https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
+	// https://www.di-mgt.com.au/sha_testvectors.html
+	test_vectors := [?]TestHash {
+		TestHash{"a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26", ""},
+		TestHash{"b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e10e116e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0", "abc"},
+		TestHash{"9f9a327944a35988d67effc4fa748b3c07744f736ac70b479d8e12a3d10d6884d00a7ef593690305462e9e9030a67c51636fd346fd8fa0ee28a5ac2aee103d2e", "abcdbcdecdefdefgefghfghighijhi"},
+		TestHash{"dbb124a0deda966eb4d199d0844fa0beb0770ea1ccddabcd335a7939a931ac6fb4fa6aebc6573f462ced2e4e7178277803be0d24d8bc2864626d9603109b7891", "jkijkljklmklmnlmnomnopnopq"},
+		TestHash{"697f2d856172cb8309d6b8b97dac4de344b549d4dee61edfb4962d8698b7fa803f4f93ff24393586e28b5b957ac3d1d369420ce53332712f997bd336d09ab02a", "a"},
+		TestHash{"5679e353bc8eeea3e801ca60448b249bcfd3ac4a6c3abe429a807bcbd4c9cd12da87a5a9dc74fde64c0d44718632cae966b078397c6f9ec155c6a238f2347cf1", "01234567012345670123456701234567"},
+		TestHash{"04a371e84ecfb5b8b77cb48610fca8182dd457ce6f326a0fd3d7ec2f1e91636dee691fbe0c985302ba1b0d8dc78c086346b533b49c030d99a27daf1139d6e75e", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
+		TestHash{"afebb2ef542e6579c50cad06d2e578f9f8dd6881d7dc824d26360feebf18a4fa73e3261122948efcfd492e74e82e2189ed0fb440d187f382270cb455f21dd185", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"},
+	}
+	for v, _ in test_vectors {
+		computed     := sha3.hash_512(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_shake_128 :: proc(t: ^testing.T) {
-    test_vectors := [?]TestHash {
-        TestHash{"7f9c2ba4e88f827d616045507605853e", ""},
-        TestHash{"f4202e3c5852f9182a0430fd8144f0a7", "The quick brown fox jumps over the lazy dog"},
-        TestHash{"853f4538be0db9621a6cea659a06c110", "The quick brown fox jumps over the lazy dof"},
-    }
-    for v, _ in test_vectors {
-        computed     := shake.hash_128(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors := [?]TestHash {
+		TestHash{"7f9c2ba4e88f827d616045507605853e", ""},
+		TestHash{"f4202e3c5852f9182a0430fd8144f0a7", "The quick brown fox jumps over the lazy dog"},
+		TestHash{"853f4538be0db9621a6cea659a06c110", "The quick brown fox jumps over the lazy dof"},
+	}
+	for v, _ in test_vectors {
+		computed     := shake.hash_128(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_shake_256 :: proc(t: ^testing.T) {
-    test_vectors := [?]TestHash {
-        TestHash{"46b9dd2b0ba88d13233b3feb743eeb243fcd52ea62b81b82b50c27646ed5762f", ""},
-        TestHash{"2f671343d9b2e1604dc9dcf0753e5fe15c7c64a0d283cbbf722d411a0e36f6ca", "The quick brown fox jumps over the lazy dog"},
-        TestHash{"46b1ebb2e142c38b9ac9081bef72877fe4723959640fa57119b366ce6899d401", "The quick brown fox jumps over the lazy dof"},
-    }
-    for v, _ in test_vectors {
-        computed     := shake.hash_256(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors := [?]TestHash {
+		TestHash{"46b9dd2b0ba88d13233b3feb743eeb243fcd52ea62b81b82b50c27646ed5762f", ""},
+		TestHash{"2f671343d9b2e1604dc9dcf0753e5fe15c7c64a0d283cbbf722d411a0e36f6ca", "The quick brown fox jumps over the lazy dog"},
+		TestHash{"46b1ebb2e142c38b9ac9081bef72877fe4723959640fa57119b366ce6899d401", "The quick brown fox jumps over the lazy dof"},
+	}
+	for v, _ in test_vectors {
+		computed     := shake.hash_256(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_keccak_224 :: proc(t: ^testing.T) {
-    // Test vectors from 
-    // https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
-    // https://www.di-mgt.com.au/sha_testvectors.html
-    test_vectors := [?]TestHash {
-        TestHash{"f71837502ba8e10837bdd8d365adb85591895602fc552b48b7390abd", ""},
-        TestHash{"c30411768506ebe1c2871b1ee2e87d38df342317300a9b97a95ec6a8", "abc"},
-    }
-    for v, _ in test_vectors {
-        computed     := keccak.hash_224(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	// Test vectors from
+	// https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
+	// https://www.di-mgt.com.au/sha_testvectors.html
+	test_vectors := [?]TestHash {
+		TestHash{"f71837502ba8e10837bdd8d365adb85591895602fc552b48b7390abd", ""},
+		TestHash{"c30411768506ebe1c2871b1ee2e87d38df342317300a9b97a95ec6a8", "abc"},
+	}
+	for v, _ in test_vectors {
+		computed     := keccak.hash_224(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_keccak_256 :: proc(t: ^testing.T) {
-    // Test vectors from 
-    // https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
-    // https://www.di-mgt.com.au/sha_testvectors.html
-    test_vectors := [?]TestHash {
-        TestHash{"c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", ""},
-        TestHash{"4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45", "abc"},
-    }
-    for v, _ in test_vectors {
-        computed     := keccak.hash_256(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	// Test vectors from
+	// https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
+	// https://www.di-mgt.com.au/sha_testvectors.html
+	test_vectors := [?]TestHash {
+		TestHash{"c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", ""},
+		TestHash{"4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45", "abc"},
+	}
+	for v, _ in test_vectors {
+		computed     := keccak.hash_256(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_keccak_384 :: proc(t: ^testing.T) {
-    // Test vectors from 
-    // https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
-    // https://www.di-mgt.com.au/sha_testvectors.html
-    test_vectors := [?]TestHash {
-        TestHash{"2c23146a63a29acf99e73b88f8c24eaa7dc60aa771780ccc006afbfa8fe2479b2dd2b21362337441ac12b515911957ff", ""},
-        TestHash{"f7df1165f033337be098e7d288ad6a2f74409d7a60b49c36642218de161b1f99f8c681e4afaf31a34db29fb763e3c28e", "abc"},
-    }
-    for v, _ in test_vectors {
-        computed     := keccak.hash_384(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	// Test vectors from
+	// https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
+	// https://www.di-mgt.com.au/sha_testvectors.html
+	test_vectors := [?]TestHash {
+		TestHash{"2c23146a63a29acf99e73b88f8c24eaa7dc60aa771780ccc006afbfa8fe2479b2dd2b21362337441ac12b515911957ff", ""},
+		TestHash{"f7df1165f033337be098e7d288ad6a2f74409d7a60b49c36642218de161b1f99f8c681e4afaf31a34db29fb763e3c28e", "abc"},
+	}
+	for v, _ in test_vectors {
+		computed     := keccak.hash_384(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_keccak_512 :: proc(t: ^testing.T) {
-    // Test vectors from 
-    // https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
-    // https://www.di-mgt.com.au/sha_testvectors.html
-    test_vectors := [?]TestHash {
-        TestHash{"0eab42de4c3ceb9235fc91acffe746b29c29a8c366b7c60e4e67c466f36a4304c00fa9caf9d87976ba469bcbe06713b435f091ef2769fb160cdab33d3670680e", ""},
-        TestHash{"18587dc2ea106b9a1563e32b3312421ca164c7f1f07bc922a9c83d77cea3a1e5d0c69910739025372dc14ac9642629379540c17e2a65b19d77aa511a9d00bb96", "abc"},
-    }
-    for v, _ in test_vectors {
-        computed     := keccak.hash_512(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	// Test vectors from
+	// https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf
+	// https://www.di-mgt.com.au/sha_testvectors.html
+	test_vectors := [?]TestHash {
+		TestHash{"0eab42de4c3ceb9235fc91acffe746b29c29a8c366b7c60e4e67c466f36a4304c00fa9caf9d87976ba469bcbe06713b435f091ef2769fb160cdab33d3670680e", ""},
+		TestHash{"18587dc2ea106b9a1563e32b3312421ca164c7f1f07bc922a9c83d77cea3a1e5d0c69910739025372dc14ac9642629379540c17e2a65b19d77aa511a9d00bb96", "abc"},
+	}
+	for v, _ in test_vectors {
+		computed     := keccak.hash_512(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_whirlpool :: proc(t: ^testing.T) {
-    // Test vectors from 
-    // https://web.archive.org/web/20171129084214/http://www.larc.usp.br/~pbarreto/WhirlpoolPage.html
-    test_vectors := [?]TestHash {
-        TestHash{"19fa61d75522a4669b44e39c1d2e1726c530232130d407f89afee0964997f7a73e83be698b288febcf88e3e03c4f0757ea8964e59b63d93708b138cc42a66eb3", ""},
-        TestHash{"8aca2602792aec6f11a67206531fb7d7f0dff59413145e6973c45001d0087b42d11bc645413aeff63a42391a39145a591a92200d560195e53b478584fdae231a", "a"},
-        TestHash{"33e24e6cbebf168016942df8a7174048f9cebc45cbd829c3b94b401a498acb11c5abcca7f2a1238aaf534371e87a4e4b19758965d5a35a7cad87cf5517043d97", "ab"},
-        TestHash{"4e2448a4c6f486bb16b6562c73b4020bf3043e3a731bce721ae1b303d97e6d4c7181eebdb6c57e277d0e34957114cbd6c797fc9d95d8b582d225292076d4eef5", "abc"},
-        TestHash{"bda164f0b930c43a1bacb5df880b205d15ac847add35145bf25d991ae74f0b72b1ac794f8aacda5fcb3c47038c954742b1857b5856519de4d1e54bfa2fa4eac5", "abcd"},
-        TestHash{"5d745e26ccb20fe655d39c9e7f69455758fbae541cb892b3581e4869244ab35b4fd6078f5d28b1f1a217452a67d9801033d92724a221255a5e377fe9e9e5f0b2", "abcde"},
-        TestHash{"a73e425459567308ba5f9eb2ae23570d0d0575eb1357ecf6ac88d4e0358b0ac3ea2371261f5d4c070211784b525911b9eec0ad968429bb7c7891d341cff4e811", "abcdef"},
-        TestHash{"08b388f68fd3eb51906ac3d3c699b8e9c3ac65d7ceb49d2e34f8a482cbc3082bc401cead90e85a97b8647c948bf35e448740b79659f3bee42145f0bd653d1f25", "abcdefg"},
-        TestHash{"1f1a84d30612820243afe2022712f9dac6d07c4c8bb41b40eacab0184c8d82275da5bcadbb35c7ca1960ff21c90acbae8c14e48d9309e4819027900e882c7ad9", "abcdefgh"},
-        TestHash{"11882bc9a31ac1cf1c41dcd9fd6fdd3ccdb9b017fc7f4582680134f314d7bb49af4c71f5a920bc0a6a3c1ff9a00021bf361d9867fe636b0bc1da1552e4237de4", "abcdefghi"},
-        TestHash{"717163de24809ffcf7ff6d5aba72b8d67c2129721953c252a4ddfb107614be857cbd76a9d5927de14633d6bdc9ddf335160b919db5c6f12cb2e6549181912eef", "abcdefghij"},
-        TestHash{"b97de512e91e3828b40d2b0fdce9ceb3c4a71f9bea8d88e75c4fa854df36725fd2b52eb6544edcacd6f8beddfea403cb55ae31f03ad62a5ef54e42ee82c3fb35", "The quick brown fox jumps over the lazy dog"},
-        TestHash{"c27ba124205f72e6847f3e19834f925cc666d0974167af915bb462420ed40cc50900d85a1f923219d832357750492d5c143011a76988344c2635e69d06f2d38c", "The quick brown fox jumps over the lazy eog"},
-    }
-    for v, _ in test_vectors {
-        computed     := whirlpool.hash(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	// Test vectors from
+	// https://web.archive.org/web/20171129084214/http://www.larc.usp.br/~pbarreto/WhirlpoolPage.html
+	test_vectors := [?]TestHash {
+		TestHash{"19fa61d75522a4669b44e39c1d2e1726c530232130d407f89afee0964997f7a73e83be698b288febcf88e3e03c4f0757ea8964e59b63d93708b138cc42a66eb3", ""},
+		TestHash{"8aca2602792aec6f11a67206531fb7d7f0dff59413145e6973c45001d0087b42d11bc645413aeff63a42391a39145a591a92200d560195e53b478584fdae231a", "a"},
+		TestHash{"33e24e6cbebf168016942df8a7174048f9cebc45cbd829c3b94b401a498acb11c5abcca7f2a1238aaf534371e87a4e4b19758965d5a35a7cad87cf5517043d97", "ab"},
+		TestHash{"4e2448a4c6f486bb16b6562c73b4020bf3043e3a731bce721ae1b303d97e6d4c7181eebdb6c57e277d0e34957114cbd6c797fc9d95d8b582d225292076d4eef5", "abc"},
+		TestHash{"bda164f0b930c43a1bacb5df880b205d15ac847add35145bf25d991ae74f0b72b1ac794f8aacda5fcb3c47038c954742b1857b5856519de4d1e54bfa2fa4eac5", "abcd"},
+		TestHash{"5d745e26ccb20fe655d39c9e7f69455758fbae541cb892b3581e4869244ab35b4fd6078f5d28b1f1a217452a67d9801033d92724a221255a5e377fe9e9e5f0b2", "abcde"},
+		TestHash{"a73e425459567308ba5f9eb2ae23570d0d0575eb1357ecf6ac88d4e0358b0ac3ea2371261f5d4c070211784b525911b9eec0ad968429bb7c7891d341cff4e811", "abcdef"},
+		TestHash{"08b388f68fd3eb51906ac3d3c699b8e9c3ac65d7ceb49d2e34f8a482cbc3082bc401cead90e85a97b8647c948bf35e448740b79659f3bee42145f0bd653d1f25", "abcdefg"},
+		TestHash{"1f1a84d30612820243afe2022712f9dac6d07c4c8bb41b40eacab0184c8d82275da5bcadbb35c7ca1960ff21c90acbae8c14e48d9309e4819027900e882c7ad9", "abcdefgh"},
+		TestHash{"11882bc9a31ac1cf1c41dcd9fd6fdd3ccdb9b017fc7f4582680134f314d7bb49af4c71f5a920bc0a6a3c1ff9a00021bf361d9867fe636b0bc1da1552e4237de4", "abcdefghi"},
+		TestHash{"717163de24809ffcf7ff6d5aba72b8d67c2129721953c252a4ddfb107614be857cbd76a9d5927de14633d6bdc9ddf335160b919db5c6f12cb2e6549181912eef", "abcdefghij"},
+		TestHash{"b97de512e91e3828b40d2b0fdce9ceb3c4a71f9bea8d88e75c4fa854df36725fd2b52eb6544edcacd6f8beddfea403cb55ae31f03ad62a5ef54e42ee82c3fb35", "The quick brown fox jumps over the lazy dog"},
+		TestHash{"c27ba124205f72e6847f3e19834f925cc666d0974167af915bb462420ed40cc50900d85a1f923219d832357750492d5c143011a76988344c2635e69d06f2d38c", "The quick brown fox jumps over the lazy eog"},
+	}
+	for v, _ in test_vectors {
+		computed     := whirlpool.hash(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_gost :: proc(t: ^testing.T) {
-    test_vectors := [?]TestHash {
-        TestHash{"981e5f3ca30c841487830f84fb433e13ac1101569b9c13584ac483234cd656c0", ""},
-        TestHash{"e74c52dd282183bf37af0079c9f78055715a103f17e3133ceff1aacf2f403011", "a"},
-        TestHash{"b285056dbf18d7392d7677369524dd14747459ed8143997e163b2986f92fd42c", "abc"},
-        TestHash{"bc6041dd2aa401ebfa6e9886734174febdb4729aa972d60f549ac39b29721ba0", "message digest"},
-        TestHash{"9004294a361a508c586fe53d1f1b02746765e71b765472786e4770d565830a76", "The quick brown fox jumps over the lazy dog"},
-        TestHash{"73b70a39497de53a6e08c67b6d4db853540f03e9389299d9b0156ef7e85d0f61", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
-        TestHash{"6bc7b38989b28cf93ae8842bf9d752905910a7528a61e5bce0782de43e610c90", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
-        TestHash{"2cefc2f7b7bdc514e18ea57fa74ff357e7fa17d652c75f69cb1be7893ede48eb", "This is message, length=32 bytes"},
-        TestHash{"c3730c5cbccacf915ac292676f21e8bd4ef75331d9405e5f1a61dc3130a65011", "Suppose the original message has length = 50 bytes"},
-    }
-    for v, _ in test_vectors {
-        computed     := gost.hash(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors := [?]TestHash {
+		TestHash{"981e5f3ca30c841487830f84fb433e13ac1101569b9c13584ac483234cd656c0", ""},
+		TestHash{"e74c52dd282183bf37af0079c9f78055715a103f17e3133ceff1aacf2f403011", "a"},
+		TestHash{"b285056dbf18d7392d7677369524dd14747459ed8143997e163b2986f92fd42c", "abc"},
+		TestHash{"bc6041dd2aa401ebfa6e9886734174febdb4729aa972d60f549ac39b29721ba0", "message digest"},
+		TestHash{"9004294a361a508c586fe53d1f1b02746765e71b765472786e4770d565830a76", "The quick brown fox jumps over the lazy dog"},
+		TestHash{"73b70a39497de53a6e08c67b6d4db853540f03e9389299d9b0156ef7e85d0f61", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
+		TestHash{"6bc7b38989b28cf93ae8842bf9d752905910a7528a61e5bce0782de43e610c90", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
+		TestHash{"2cefc2f7b7bdc514e18ea57fa74ff357e7fa17d652c75f69cb1be7893ede48eb", "This is message, length=32 bytes"},
+		TestHash{"c3730c5cbccacf915ac292676f21e8bd4ef75331d9405e5f1a61dc3130a65011", "Suppose the original message has length = 50 bytes"},
+	}
+	for v, _ in test_vectors {
+		computed     := gost.hash(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_streebog_256 :: proc(t: ^testing.T) {
-    test_vectors := [?]TestHash {
-        TestHash{"3f539a213e97c802cc229d474c6aa32a825a360b2a933a949fd925208d9ce1bb", ""},
-        TestHash{"3e7dea7f2384b6c5a3d0e24aaa29c05e89ddd762145030ec22c71a6db8b2c1f4", "The quick brown fox jumps over the lazy dog"},
-        TestHash{"36816a824dcbe7d6171aa58500741f2ea2757ae2e1784ab72c5c3c6c198d71da", "The quick brown fox jumps over the lazy dog."},
-    }
-    for v, _ in test_vectors {
-        computed     := streebog.hash_256(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors := [?]TestHash {
+		TestHash{"3f539a213e97c802cc229d474c6aa32a825a360b2a933a949fd925208d9ce1bb", ""},
+		TestHash{"3e7dea7f2384b6c5a3d0e24aaa29c05e89ddd762145030ec22c71a6db8b2c1f4", "The quick brown fox jumps over the lazy dog"},
+		TestHash{"36816a824dcbe7d6171aa58500741f2ea2757ae2e1784ab72c5c3c6c198d71da", "The quick brown fox jumps over the lazy dog."},
+	}
+	for v, _ in test_vectors {
+		computed     := streebog.hash_256(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_streebog_512 :: proc(t: ^testing.T) {
-    test_vectors := [?]TestHash {
-        TestHash{"8e945da209aa869f0455928529bcae4679e9873ab707b55315f56ceb98bef0a7362f715528356ee83cda5f2aac4c6ad2ba3a715c1bcd81cb8e9f90bf4c1c1a8a", ""},
-        TestHash{"d2b793a0bb6cb5904828b5b6dcfb443bb8f33efc06ad09368878ae4cdc8245b97e60802469bed1e7c21a64ff0b179a6a1e0bb74d92965450a0adab69162c00fe", "The quick brown fox jumps over the lazy dog"},
-        TestHash{"fe0c42f267d921f940faa72bd9fcf84f9f1bd7e9d055e9816e4c2ace1ec83be82d2957cd59b86e123d8f5adee80b3ca08a017599a9fc1a14d940cf87c77df070", "The quick brown fox jumps over the lazy dog."},
-    }
-    for v, _ in test_vectors {
-        computed     := streebog.hash_512(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors := [?]TestHash {
+		TestHash{"8e945da209aa869f0455928529bcae4679e9873ab707b55315f56ceb98bef0a7362f715528356ee83cda5f2aac4c6ad2ba3a715c1bcd81cb8e9f90bf4c1c1a8a", ""},
+		TestHash{"d2b793a0bb6cb5904828b5b6dcfb443bb8f33efc06ad09368878ae4cdc8245b97e60802469bed1e7c21a64ff0b179a6a1e0bb74d92965450a0adab69162c00fe", "The quick brown fox jumps over the lazy dog"},
+		TestHash{"fe0c42f267d921f940faa72bd9fcf84f9f1bd7e9d055e9816e4c2ace1ec83be82d2957cd59b86e123d8f5adee80b3ca08a017599a9fc1a14d940cf87c77df070", "The quick brown fox jumps over the lazy dog."},
+	}
+	for v, _ in test_vectors {
+		computed     := streebog.hash_512(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_blake_224 :: proc(t: ^testing.T) {
-    test_vectors := [?]TestHash {
-        TestHash{"7dc5313b1c04512a174bd6503b89607aecbee0903d40a8a569c94eed", ""},
-        TestHash{"304c27fdbf308aea06955e331adc6814223a21fccd24c09fde9eda7b", "ube"},
-        TestHash{"cfb6848add73e1cb47994c4765df33b8f973702705a30a71fe4747a3", "BLAKE"},
-    }
-    for v, _ in test_vectors {
-        computed     := blake.hash_224(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors := [?]TestHash {
+		TestHash{"7dc5313b1c04512a174bd6503b89607aecbee0903d40a8a569c94eed", ""},
+		TestHash{"304c27fdbf308aea06955e331adc6814223a21fccd24c09fde9eda7b", "ube"},
+		TestHash{"cfb6848add73e1cb47994c4765df33b8f973702705a30a71fe4747a3", "BLAKE"},
+	}
+	for v, _ in test_vectors {
+		computed     := blake.hash_224(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_blake_256 :: proc(t: ^testing.T) {
-    test_vectors := [?]TestHash {
-        TestHash{"716f6e863f744b9ac22c97ec7b76ea5f5908bc5b2f67c61510bfc4751384ea7a", ""},
-        TestHash{"e802fe2a73fbe5853408f051d040aeb3a76a4d7a0fc5c3415d1af090f76a2c81", "ube"},
-        TestHash{"07663e00cf96fbc136cf7b1ee099c95346ba3920893d18cc8851f22ee2e36aa6", "BLAKE"},
-    }
-    for v, _ in test_vectors {
-        computed     := blake.hash_256(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors := [?]TestHash {
+		TestHash{"716f6e863f744b9ac22c97ec7b76ea5f5908bc5b2f67c61510bfc4751384ea7a", ""},
+		TestHash{"e802fe2a73fbe5853408f051d040aeb3a76a4d7a0fc5c3415d1af090f76a2c81", "ube"},
+		TestHash{"07663e00cf96fbc136cf7b1ee099c95346ba3920893d18cc8851f22ee2e36aa6", "BLAKE"},
+	}
+	for v, _ in test_vectors {
+		computed     := blake.hash_256(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_blake_384 :: proc(t: ^testing.T) {
-    test_vectors := [?]TestHash {
-        TestHash{"c6cbd89c926ab525c242e6621f2f5fa73aa4afe3d9e24aed727faaadd6af38b620bdb623dd2b4788b1c8086984af8706", ""},
-        TestHash{"8f22f120b2b99dd4fd32b98c8c83bd87abd6413f7317be936b1997511247fc68ae781c6f42113224ccbc1567b0e88593", "ube"},
-        TestHash{"f28742f7243990875d07e6afcff962edabdf7e9d19ddea6eae31d094c7fa6d9b00c8213a02ddf1e2d9894f3162345d85", "BLAKE"},
-    }
-    for v, _ in test_vectors {
-        computed     := blake.hash_384(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors := [?]TestHash {
+		TestHash{"c6cbd89c926ab525c242e6621f2f5fa73aa4afe3d9e24aed727faaadd6af38b620bdb623dd2b4788b1c8086984af8706", ""},
+		TestHash{"8f22f120b2b99dd4fd32b98c8c83bd87abd6413f7317be936b1997511247fc68ae781c6f42113224ccbc1567b0e88593", "ube"},
+		TestHash{"f28742f7243990875d07e6afcff962edabdf7e9d19ddea6eae31d094c7fa6d9b00c8213a02ddf1e2d9894f3162345d85", "BLAKE"},
+	}
+	for v, _ in test_vectors {
+		computed     := blake.hash_384(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_blake_512 :: proc(t: ^testing.T) {
-    test_vectors := [?]TestHash {
-        TestHash{"a8cfbbd73726062df0c6864dda65defe58ef0cc52a5625090fa17601e1eecd1b628e94f396ae402a00acc9eab77b4d4c2e852aaaa25a636d80af3fc7913ef5b8", ""},
-        TestHash{"49a24ca8f230936f938c19484d46b58f13ea4448ddadafecdf01419b1e1dd922680be2de84069187973ab61b10574da2ee50cbeaade68ea9391c8ec041b76be0", "ube"},
-        TestHash{"7bf805d0d8de36802b882e65d0515aa7682a2be97a9d9ec1399f4be2eff7de07684d7099124c8ac81c1c7c200d24ba68c6222e75062e04feb0e9dd589aa6e3b7", "BLAKE"},
-    }
-    for v, _ in test_vectors {
-        computed     := blake.hash_512(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors := [?]TestHash {
+		TestHash{"a8cfbbd73726062df0c6864dda65defe58ef0cc52a5625090fa17601e1eecd1b628e94f396ae402a00acc9eab77b4d4c2e852aaaa25a636d80af3fc7913ef5b8", ""},
+		TestHash{"49a24ca8f230936f938c19484d46b58f13ea4448ddadafecdf01419b1e1dd922680be2de84069187973ab61b10574da2ee50cbeaade68ea9391c8ec041b76be0", "ube"},
+		TestHash{"7bf805d0d8de36802b882e65d0515aa7682a2be97a9d9ec1399f4be2eff7de07684d7099124c8ac81c1c7c200d24ba68c6222e75062e04feb0e9dd589aa6e3b7", "BLAKE"},
+	}
+	for v, _ in test_vectors {
+		computed     := blake.hash_512(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_blake2b :: proc(t: ^testing.T) {
-    test_vectors := [?]TestHash {
-        TestHash{"786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce", ""},
-        TestHash{"a8add4bdddfd93e4877d2746e62817b116364a1fa7bc148d95090bc7333b3673f82401cf7aa2e4cb1ecd90296e3f14cb5413f8ed77be73045b13914cdcd6a918", "The quick brown fox jumps over the lazy dog"},
-    }
-    for v, _ in test_vectors {
-        computed     := blake2b.hash(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors := [?]TestHash {
+		TestHash{"786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce", ""},
+		TestHash{"a8add4bdddfd93e4877d2746e62817b116364a1fa7bc148d95090bc7333b3673f82401cf7aa2e4cb1ecd90296e3f14cb5413f8ed77be73045b13914cdcd6a918", "The quick brown fox jumps over the lazy dog"},
+	}
+	for v, _ in test_vectors {
+		computed     := blake2b.hash(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_blake2s :: proc(t: ^testing.T) {
-    test_vectors := [?]TestHash {
-        TestHash{"69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9", ""},
-        TestHash{"606beeec743ccbeff6cbcdf5d5302aa855c256c29b88c8ed331ea1a6bf3c8812", "The quick brown fox jumps over the lazy dog"},
-    }
-    for v, _ in test_vectors {
-        computed     := blake2s.hash(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors := [?]TestHash {
+		TestHash{"69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9", ""},
+		TestHash{"606beeec743ccbeff6cbcdf5d5302aa855c256c29b88c8ed331ea1a6bf3c8812", "The quick brown fox jumps over the lazy dog"},
+	}
+	for v, _ in test_vectors {
+		computed     := blake2s.hash(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_ripemd_128 :: proc(t: ^testing.T) {
-    // Test vectors from 
-    // https://homes.esat.kuleuven.be/~bosselae/ripemd160.html
-    test_vectors := [?]TestHash {
-        TestHash{"cdf26213a150dc3ecb610f18f6b38b46", ""},
-        TestHash{"86be7afa339d0fc7cfc785e72f578d33", "a"},
-        TestHash{"c14a12199c66e4ba84636b0f69144c77", "abc"},
-        TestHash{"9e327b3d6e523062afc1132d7df9d1b8", "message digest"},
-        TestHash{"fd2aa607f71dc8f510714922b371834e", "abcdefghijklmnopqrstuvwxyz"},
-        TestHash{"a1aa0689d0fafa2ddc22e88b49133a06", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
-        TestHash{"d1e959eb179c911faea4624c60c5c702", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
-    }
-    for v, _ in test_vectors {
-        computed     := ripemd.hash_128(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	// Test vectors from
+	// https://homes.esat.kuleuven.be/~bosselae/ripemd160.html
+	test_vectors := [?]TestHash {
+		TestHash{"cdf26213a150dc3ecb610f18f6b38b46", ""},
+		TestHash{"86be7afa339d0fc7cfc785e72f578d33", "a"},
+		TestHash{"c14a12199c66e4ba84636b0f69144c77", "abc"},
+		TestHash{"9e327b3d6e523062afc1132d7df9d1b8", "message digest"},
+		TestHash{"fd2aa607f71dc8f510714922b371834e", "abcdefghijklmnopqrstuvwxyz"},
+		TestHash{"a1aa0689d0fafa2ddc22e88b49133a06", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
+		TestHash{"d1e959eb179c911faea4624c60c5c702", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
+	}
+	for v, _ in test_vectors {
+		computed     := ripemd.hash_128(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_ripemd_160 :: proc(t: ^testing.T) {
-    // Test vectors from 
-    // https://homes.esat.kuleuven.be/~bosselae/ripemd160.html
-    test_vectors := [?]TestHash {
-        TestHash{"9c1185a5c5e9fc54612808977ee8f548b2258d31", ""},
-        TestHash{"0bdc9d2d256b3ee9daae347be6f4dc835a467ffe", "a"},
-        TestHash{"8eb208f7e05d987a9b044a8e98c6b087f15a0bfc", "abc"},
-        TestHash{"5d0689ef49d2fae572b881b123a85ffa21595f36", "message digest"},
-        TestHash{"f71c27109c692c1b56bbdceb5b9d2865b3708dbc", "abcdefghijklmnopqrstuvwxyz"},
-        TestHash{"12a053384a9c0c88e405a06c27dcf49ada62eb2b", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
-        TestHash{"b0e20b6e3116640286ed3a87a5713079b21f5189", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
-    }
-    for v, _ in test_vectors {
-        computed     := ripemd.hash_160(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	// Test vectors from
+	// https://homes.esat.kuleuven.be/~bosselae/ripemd160.html
+	test_vectors := [?]TestHash {
+		TestHash{"9c1185a5c5e9fc54612808977ee8f548b2258d31", ""},
+		TestHash{"0bdc9d2d256b3ee9daae347be6f4dc835a467ffe", "a"},
+		TestHash{"8eb208f7e05d987a9b044a8e98c6b087f15a0bfc", "abc"},
+		TestHash{"5d0689ef49d2fae572b881b123a85ffa21595f36", "message digest"},
+		TestHash{"f71c27109c692c1b56bbdceb5b9d2865b3708dbc", "abcdefghijklmnopqrstuvwxyz"},
+		TestHash{"12a053384a9c0c88e405a06c27dcf49ada62eb2b", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
+		TestHash{"b0e20b6e3116640286ed3a87a5713079b21f5189", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
+	}
+	for v, _ in test_vectors {
+		computed     := ripemd.hash_160(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_ripemd_256 :: proc(t: ^testing.T) {
-    // Test vectors from 
-    // https://homes.esat.kuleuven.be/~bosselae/ripemd160.html
-    test_vectors := [?]TestHash {
-        TestHash{"02ba4c4e5f8ecd1877fc52d64d30e37a2d9774fb1e5d026380ae0168e3c5522d", ""},
-        TestHash{"f9333e45d857f5d90a91bab70a1eba0cfb1be4b0783c9acfcd883a9134692925", "a"},
-        TestHash{"afbd6e228b9d8cbbcef5ca2d03e6dba10ac0bc7dcbe4680e1e42d2e975459b65", "abc"},
-        TestHash{"87e971759a1ce47a514d5c914c392c9018c7c46bc14465554afcdf54a5070c0e", "message digest"},
-        TestHash{"649d3034751ea216776bf9a18acc81bc7896118a5197968782dd1fd97d8d5133", "abcdefghijklmnopqrstuvwxyz"},
-        TestHash{"3843045583aac6c8c8d9128573e7a9809afb2a0f34ccc36ea9e72f16f6368e3f", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
-        TestHash{"5740a408ac16b720b84424ae931cbb1fe363d1d0bf4017f1a89f7ea6de77a0b8", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"}, 
-    }
-    for v, _ in test_vectors {
-        computed     := ripemd.hash_256(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	// Test vectors from
+	// https://homes.esat.kuleuven.be/~bosselae/ripemd160.html
+	test_vectors := [?]TestHash {
+		TestHash{"02ba4c4e5f8ecd1877fc52d64d30e37a2d9774fb1e5d026380ae0168e3c5522d", ""},
+		TestHash{"f9333e45d857f5d90a91bab70a1eba0cfb1be4b0783c9acfcd883a9134692925", "a"},
+		TestHash{"afbd6e228b9d8cbbcef5ca2d03e6dba10ac0bc7dcbe4680e1e42d2e975459b65", "abc"},
+		TestHash{"87e971759a1ce47a514d5c914c392c9018c7c46bc14465554afcdf54a5070c0e", "message digest"},
+		TestHash{"649d3034751ea216776bf9a18acc81bc7896118a5197968782dd1fd97d8d5133", "abcdefghijklmnopqrstuvwxyz"},
+		TestHash{"3843045583aac6c8c8d9128573e7a9809afb2a0f34ccc36ea9e72f16f6368e3f", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
+		TestHash{"5740a408ac16b720b84424ae931cbb1fe363d1d0bf4017f1a89f7ea6de77a0b8", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
+	}
+	for v, _ in test_vectors {
+		computed     := ripemd.hash_256(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_ripemd_320 :: proc(t: ^testing.T) {
-    // Test vectors from 
-    // https://homes.esat.kuleuven.be/~bosselae/ripemd160.html
-    test_vectors := [?]TestHash {
-        TestHash{"22d65d5661536cdc75c1fdf5c6de7b41b9f27325ebc61e8557177d705a0ec880151c3a32a00899b8", ""},
-        TestHash{"ce78850638f92658a5a585097579926dda667a5716562cfcf6fbe77f63542f99b04705d6970dff5d", "a"},
-        TestHash{"de4c01b3054f8930a79d09ae738e92301e5a17085beffdc1b8d116713e74f82fa942d64cdbc4682d", "abc"},
-        TestHash{"3a8e28502ed45d422f68844f9dd316e7b98533fa3f2a91d29f84d425c88d6b4eff727df66a7c0197", "message digest"},
-        TestHash{"cabdb1810b92470a2093aa6bce05952c28348cf43ff60841975166bb40ed234004b8824463e6b009", "abcdefghijklmnopqrstuvwxyz"},
-        TestHash{"d034a7950cf722021ba4b84df769a5de2060e259df4c9bb4a4268c0e935bbc7470a969c9d072a1ac", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
-        TestHash{"ed544940c86d67f250d232c30b7b3e5770e0c60c8cb9a4cafe3b11388af9920e1b99230b843c86a4", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
-    }
-    for v, _ in test_vectors {
-        computed     := ripemd.hash_320(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	// Test vectors from
+	// https://homes.esat.kuleuven.be/~bosselae/ripemd160.html
+	test_vectors := [?]TestHash {
+		TestHash{"22d65d5661536cdc75c1fdf5c6de7b41b9f27325ebc61e8557177d705a0ec880151c3a32a00899b8", ""},
+		TestHash{"ce78850638f92658a5a585097579926dda667a5716562cfcf6fbe77f63542f99b04705d6970dff5d", "a"},
+		TestHash{"de4c01b3054f8930a79d09ae738e92301e5a17085beffdc1b8d116713e74f82fa942d64cdbc4682d", "abc"},
+		TestHash{"3a8e28502ed45d422f68844f9dd316e7b98533fa3f2a91d29f84d425c88d6b4eff727df66a7c0197", "message digest"},
+		TestHash{"cabdb1810b92470a2093aa6bce05952c28348cf43ff60841975166bb40ed234004b8824463e6b009", "abcdefghijklmnopqrstuvwxyz"},
+		TestHash{"d034a7950cf722021ba4b84df769a5de2060e259df4c9bb4a4268c0e935bbc7470a969c9d072a1ac", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
+		TestHash{"ed544940c86d67f250d232c30b7b3e5770e0c60c8cb9a4cafe3b11388af9920e1b99230b843c86a4", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
+	}
+	for v, _ in test_vectors {
+		computed     := ripemd.hash_320(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_tiger_128 :: proc(t: ^testing.T) {
-    test_vectors := [?]TestHash {
-        TestHash{"3293ac630c13f0245f92bbb1766e1616", ""},
-        TestHash{"77befbef2e7ef8ab2ec8f93bf587a7fc", "a"},
-        TestHash{"2aab1484e8c158f2bfb8c5ff41b57a52", "abc"},
-        TestHash{"d981f8cb78201a950dcf3048751e441c", "message digest"},
-        TestHash{"1714a472eee57d30040412bfcc55032a", "abcdefghijklmnopqrstuvwxyz"},
-        TestHash{"0f7bf9a19b9c58f2b7610df7e84f0ac3", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
-        TestHash{"8dcea680a17583ee502ba38a3c368651", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
-        TestHash{"1c14795529fd9f207a958f84c52f11e8", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
-        TestHash{"6d12a41e72e644f017b6f0e2f7b44c62", "The quick brown fox jumps over the lazy dog"},
-    }
-    for v, _ in test_vectors {
-        computed     := tiger.hash_128(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors := [?]TestHash {
+		TestHash{"3293ac630c13f0245f92bbb1766e1616", ""},
+		TestHash{"77befbef2e7ef8ab2ec8f93bf587a7fc", "a"},
+		TestHash{"2aab1484e8c158f2bfb8c5ff41b57a52", "abc"},
+		TestHash{"d981f8cb78201a950dcf3048751e441c", "message digest"},
+		TestHash{"1714a472eee57d30040412bfcc55032a", "abcdefghijklmnopqrstuvwxyz"},
+		TestHash{"0f7bf9a19b9c58f2b7610df7e84f0ac3", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
+		TestHash{"8dcea680a17583ee502ba38a3c368651", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
+		TestHash{"1c14795529fd9f207a958f84c52f11e8", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
+		TestHash{"6d12a41e72e644f017b6f0e2f7b44c62", "The quick brown fox jumps over the lazy dog"},
+	}
+	for v, _ in test_vectors {
+		computed     := tiger.hash_128(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_tiger_160 :: proc(t: ^testing.T) {
-    test_vectors := [?]TestHash {
-        TestHash{"3293ac630c13f0245f92bbb1766e16167a4e5849", ""},
-        TestHash{"77befbef2e7ef8ab2ec8f93bf587a7fc613e247f", "a"},
-        TestHash{"2aab1484e8c158f2bfb8c5ff41b57a525129131c", "abc"},
-        TestHash{"d981f8cb78201a950dcf3048751e441c517fca1a", "message digest"},
-        TestHash{"1714a472eee57d30040412bfcc55032a0b11602f", "abcdefghijklmnopqrstuvwxyz"},
-        TestHash{"0f7bf9a19b9c58f2b7610df7e84f0ac3a71c631e", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
-        TestHash{"8dcea680a17583ee502ba38a3c368651890ffbcc", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
-        TestHash{"1c14795529fd9f207a958f84c52f11e887fa0cab", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
-        TestHash{"6d12a41e72e644f017b6f0e2f7b44c6285f06dd5", "The quick brown fox jumps over the lazy dog"},
-    }
-    for v, _ in test_vectors {
-        computed     := tiger.hash_160(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors := [?]TestHash {
+		TestHash{"3293ac630c13f0245f92bbb1766e16167a4e5849", ""},
+		TestHash{"77befbef2e7ef8ab2ec8f93bf587a7fc613e247f", "a"},
+		TestHash{"2aab1484e8c158f2bfb8c5ff41b57a525129131c", "abc"},
+		TestHash{"d981f8cb78201a950dcf3048751e441c517fca1a", "message digest"},
+		TestHash{"1714a472eee57d30040412bfcc55032a0b11602f", "abcdefghijklmnopqrstuvwxyz"},
+		TestHash{"0f7bf9a19b9c58f2b7610df7e84f0ac3a71c631e", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
+		TestHash{"8dcea680a17583ee502ba38a3c368651890ffbcc", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
+		TestHash{"1c14795529fd9f207a958f84c52f11e887fa0cab", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
+		TestHash{"6d12a41e72e644f017b6f0e2f7b44c6285f06dd5", "The quick brown fox jumps over the lazy dog"},
+	}
+	for v, _ in test_vectors {
+		computed     := tiger.hash_160(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_tiger_192 :: proc(t: ^testing.T) {
-    test_vectors := [?]TestHash {
-        TestHash{"3293ac630c13f0245f92bbb1766e16167a4e58492dde73f3", ""},
-        TestHash{"77befbef2e7ef8ab2ec8f93bf587a7fc613e247f5f247809", "a"},
-        TestHash{"2aab1484e8c158f2bfb8c5ff41b57a525129131c957b5f93", "abc"},
-        TestHash{"d981f8cb78201a950dcf3048751e441c517fca1aa55a29f6", "message digest"},
-        TestHash{"1714a472eee57d30040412bfcc55032a0b11602ff37beee9", "abcdefghijklmnopqrstuvwxyz"},
-        TestHash{"0f7bf9a19b9c58f2b7610df7e84f0ac3a71c631e7b53f78e", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
-        TestHash{"8dcea680a17583ee502ba38a3c368651890ffbccdc49a8cc", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
-        TestHash{"1c14795529fd9f207a958f84c52f11e887fa0cabdfd91bfd", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
-        TestHash{"6d12a41e72e644f017b6f0e2f7b44c6285f06dd5d2c5b075", "The quick brown fox jumps over the lazy dog"},
-    }
-    for v, _ in test_vectors {
-        computed     := tiger.hash_192(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors := [?]TestHash {
+		TestHash{"3293ac630c13f0245f92bbb1766e16167a4e58492dde73f3", ""},
+		TestHash{"77befbef2e7ef8ab2ec8f93bf587a7fc613e247f5f247809", "a"},
+		TestHash{"2aab1484e8c158f2bfb8c5ff41b57a525129131c957b5f93", "abc"},
+		TestHash{"d981f8cb78201a950dcf3048751e441c517fca1aa55a29f6", "message digest"},
+		TestHash{"1714a472eee57d30040412bfcc55032a0b11602ff37beee9", "abcdefghijklmnopqrstuvwxyz"},
+		TestHash{"0f7bf9a19b9c58f2b7610df7e84f0ac3a71c631e7b53f78e", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
+		TestHash{"8dcea680a17583ee502ba38a3c368651890ffbccdc49a8cc", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
+		TestHash{"1c14795529fd9f207a958f84c52f11e887fa0cabdfd91bfd", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
+		TestHash{"6d12a41e72e644f017b6f0e2f7b44c6285f06dd5d2c5b075", "The quick brown fox jumps over the lazy dog"},
+	}
+	for v, _ in test_vectors {
+		computed     := tiger.hash_192(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_tiger2_128 :: proc(t: ^testing.T) {
-    test_vectors := [?]TestHash {
-        TestHash{"4441be75f6018773c206c22745374b92", ""},
-        TestHash{"976abff8062a2e9dcea3a1ace966ed9c", "The quick brown fox jumps over the lazy dog"},
-        TestHash{"09c11330283a27efb51930aa7dc1ec62", "The quick brown fox jumps over the lazy cog"},
-    }
-    for v, _ in test_vectors {
-        computed     := tiger2.hash_128(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors := [?]TestHash {
+		TestHash{"4441be75f6018773c206c22745374b92", ""},
+		TestHash{"976abff8062a2e9dcea3a1ace966ed9c", "The quick brown fox jumps over the lazy dog"},
+		TestHash{"09c11330283a27efb51930aa7dc1ec62", "The quick brown fox jumps over the lazy cog"},
+	}
+	for v, _ in test_vectors {
+		computed     := tiger2.hash_128(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_tiger2_160 :: proc(t: ^testing.T) {
-    test_vectors := [?]TestHash {
-        TestHash{"4441be75f6018773c206c22745374b924aa8313f", ""},
-        TestHash{"976abff8062a2e9dcea3a1ace966ed9c19cb8555", "The quick brown fox jumps over the lazy dog"},
-        TestHash{"09c11330283a27efb51930aa7dc1ec624ff738a8", "The quick brown fox jumps over the lazy cog"},
-    }
-    for v, _ in test_vectors {
-        computed     := tiger2.hash_160(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors := [?]TestHash {
+		TestHash{"4441be75f6018773c206c22745374b924aa8313f", ""},
+		TestHash{"976abff8062a2e9dcea3a1ace966ed9c19cb8555", "The quick brown fox jumps over the lazy dog"},
+		TestHash{"09c11330283a27efb51930aa7dc1ec624ff738a8", "The quick brown fox jumps over the lazy cog"},
+	}
+	for v, _ in test_vectors {
+		computed     := tiger2.hash_160(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_tiger2_192 :: proc(t: ^testing.T) {
-    test_vectors := [?]TestHash {
-        TestHash{"4441be75f6018773c206c22745374b924aa8313fef919f41", ""},
-        TestHash{"976abff8062a2e9dcea3a1ace966ed9c19cb85558b4976d8", "The quick brown fox jumps over the lazy dog"},
-        TestHash{"09c11330283a27efb51930aa7dc1ec624ff738a8d9bdd3df", "The quick brown fox jumps over the lazy cog"},
-    }
-    for v, _ in test_vectors {
-        computed     := tiger2.hash_192(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors := [?]TestHash {
+		TestHash{"4441be75f6018773c206c22745374b924aa8313fef919f41", ""},
+		TestHash{"976abff8062a2e9dcea3a1ace966ed9c19cb85558b4976d8", "The quick brown fox jumps over the lazy dog"},
+		TestHash{"09c11330283a27efb51930aa7dc1ec624ff738a8d9bdd3df", "The quick brown fox jumps over the lazy cog"},
+	}
+	for v, _ in test_vectors {
+		computed     := tiger2.hash_192(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_sm3 :: proc(t: ^testing.T) {
-    test_vectors := [?]TestHash {
-        TestHash{"1ab21d8355cfa17f8e61194831e81a8f22bec8c728fefb747ed035eb5082aa2b", ""},
-        TestHash{"66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0", "abc"},
-        TestHash{"debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c5732", "abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"}, 
-        TestHash{"5fdfe814b8573ca021983970fc79b2218c9570369b4859684e2e4c3fc76cb8ea", "The quick brown fox jumps over the lazy dog"},
-        TestHash{"ca27d14a42fc04c1e5ecf574a95a8c2d70ecb5805e9b429026ccac8f28b20098", "The quick brown fox jumps over the lazy cog"},
-    }
-    for v, _ in test_vectors {
-        computed     := sm3.hash(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors := [?]TestHash {
+		TestHash{"1ab21d8355cfa17f8e61194831e81a8f22bec8c728fefb747ed035eb5082aa2b", ""},
+		TestHash{"66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0", "abc"},
+		TestHash{"debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c5732", "abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"},
+		TestHash{"5fdfe814b8573ca021983970fc79b2218c9570369b4859684e2e4c3fc76cb8ea", "The quick brown fox jumps over the lazy dog"},
+		TestHash{"ca27d14a42fc04c1e5ecf574a95a8c2d70ecb5805e9b429026ccac8f28b20098", "The quick brown fox jumps over the lazy cog"},
+	}
+	for v, _ in test_vectors {
+		computed     := sm3.hash(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_jh_224 :: proc(t: ^testing.T) {
-    test_vectors := [?]TestHash {
-        TestHash{"2c99df889b019309051c60fecc2bd285a774940e43175b76b2626630", ""},
-        TestHash{"e715f969fb61b203a97e494aab92d91a9cec52f0933436b0d63bf722", "a"},
-        TestHash{"c2b1967e635bd55b6a4d36f863ac4a877be302251d68692873007281", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
-    }
-    for v, _ in test_vectors {
-        computed     := jh.hash_224(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors := [?]TestHash {
+		TestHash{"2c99df889b019309051c60fecc2bd285a774940e43175b76b2626630", ""},
+		TestHash{"e715f969fb61b203a97e494aab92d91a9cec52f0933436b0d63bf722", "a"},
+		TestHash{"c2b1967e635bd55b6a4d36f863ac4a877be302251d68692873007281", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
+	}
+	for v, _ in test_vectors {
+		computed     := jh.hash_224(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_jh_256 :: proc(t: ^testing.T) {
-    test_vectors := [?]TestHash {
-        TestHash{"46e64619c18bb0a92a5e87185a47eef83ca747b8fcc8e1412921357e326df434", ""},
-        TestHash{"d52c0c130a1bc0ae5136375637a52773e150c71efe1c968df8956f6745b05386", "a"},
-        TestHash{"fc4214867025a8af94c614353b3553b10e561ae749fc18c40e5fd44a7a4ecd1b", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
-    }
-    for v, _ in test_vectors {
-        computed     := jh.hash_256(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors := [?]TestHash {
+		TestHash{"46e64619c18bb0a92a5e87185a47eef83ca747b8fcc8e1412921357e326df434", ""},
+		TestHash{"d52c0c130a1bc0ae5136375637a52773e150c71efe1c968df8956f6745b05386", "a"},
+		TestHash{"fc4214867025a8af94c614353b3553b10e561ae749fc18c40e5fd44a7a4ecd1b", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
+	}
+	for v, _ in test_vectors {
+		computed     := jh.hash_256(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_jh_384 :: proc(t: ^testing.T) {
-    test_vectors := [?]TestHash {
-        TestHash{"2fe5f71b1b3290d3c017fb3c1a4d02a5cbeb03a0476481e25082434a881994b0ff99e078d2c16b105ad069b569315328", ""},
-        TestHash{"77de897ca4fd5dadfbcbd1d8d4ea3c3c1426855e38661325853e92b069f3fe156729f6bbb9a5892c7c18a77f1cb9d0bb", "a"},
-        TestHash{"6f73d9b9b8ed362f8180fb26020725b40bd6ca75b3b947405f26c4c37a885ce028876dc42e379d2faf6146fed3ea0e42", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
-    }
-    for v, _ in test_vectors {
-        computed     := jh.hash_384(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors := [?]TestHash {
+		TestHash{"2fe5f71b1b3290d3c017fb3c1a4d02a5cbeb03a0476481e25082434a881994b0ff99e078d2c16b105ad069b569315328", ""},
+		TestHash{"77de897ca4fd5dadfbcbd1d8d4ea3c3c1426855e38661325853e92b069f3fe156729f6bbb9a5892c7c18a77f1cb9d0bb", "a"},
+		TestHash{"6f73d9b9b8ed362f8180fb26020725b40bd6ca75b3b947405f26c4c37a885ce028876dc42e379d2faf6146fed3ea0e42", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
+	}
+	for v, _ in test_vectors {
+		computed     := jh.hash_384(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_jh_512 :: proc(t: ^testing.T) {
-    test_vectors := [?]TestHash {
-        TestHash{"90ecf2f76f9d2c8017d979ad5ab96b87d58fc8fc4b83060f3f900774faa2c8fabe69c5f4ff1ec2b61d6b316941cedee117fb04b1f4c5bc1b919ae841c50eec4f", ""},
-        TestHash{"f12c87e986daff17c481c81a99a39b603ca6bafcd320c5735523b97cb9a26f7681bad62ffad9aad0e21160a05f773fb0d1434ca4cbcb0483f480a171ada1561b", "a"},
-        TestHash{"bafb8e710b35eabeb1a48220c4b0987c2c985b6e73b7b31d164bfb9d67c94d99d7bc43b474a25e647cd6cc36334b6a00a5f2a85fae74907fd2885c6168132fe7", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
-    }
-    for v, _ in test_vectors {
-        computed     := jh.hash_512(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors := [?]TestHash {
+		TestHash{"90ecf2f76f9d2c8017d979ad5ab96b87d58fc8fc4b83060f3f900774faa2c8fabe69c5f4ff1ec2b61d6b316941cedee117fb04b1f4c5bc1b919ae841c50eec4f", ""},
+		TestHash{"f12c87e986daff17c481c81a99a39b603ca6bafcd320c5735523b97cb9a26f7681bad62ffad9aad0e21160a05f773fb0d1434ca4cbcb0483f480a171ada1561b", "a"},
+		TestHash{"bafb8e710b35eabeb1a48220c4b0987c2c985b6e73b7b31d164bfb9d67c94d99d7bc43b474a25e647cd6cc36334b6a00a5f2a85fae74907fd2885c6168132fe7", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
+	}
+	for v, _ in test_vectors {
+		computed     := jh.hash_512(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_groestl_224 :: proc(t: ^testing.T) {
-    test_vectors := [?]TestHash {
-        TestHash{"f2e180fb5947be964cd584e22e496242c6a329c577fc4ce8c36d34c3", ""},
-        TestHash{"2dfa5bd326c23c451b1202d99e6cee98a98c45927e1a31077f538712", "a"},
-        TestHash{"c8a3e7274d599900ae673419683c3626a2e49ed57308ed2687508bef", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
-    }
-    for v, _ in test_vectors {
-        computed     := groestl.hash_224(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors := [?]TestHash {
+		TestHash{"f2e180fb5947be964cd584e22e496242c6a329c577fc4ce8c36d34c3", ""},
+		TestHash{"2dfa5bd326c23c451b1202d99e6cee98a98c45927e1a31077f538712", "a"},
+		TestHash{"c8a3e7274d599900ae673419683c3626a2e49ed57308ed2687508bef", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
+	}
+	for v, _ in test_vectors {
+		computed     := groestl.hash_224(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_groestl_256 :: proc(t: ^testing.T) {
-    test_vectors := [?]TestHash {
-        TestHash{"1a52d11d550039be16107f9c58db9ebcc417f16f736adb2502567119f0083467", ""},
-        TestHash{"3645c245bb31223ad93c80885b719aa40b4bed0a9d9d6e7c11fe99e59ca350b5", "a"},
-        TestHash{"2679d98913bee62e57fdbdde97ddb328373548c6b24fc587cc3d08f2a02a529c", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
-    }
-    for v, _ in test_vectors {
-        computed     := groestl.hash_256(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors := [?]TestHash {
+		TestHash{"1a52d11d550039be16107f9c58db9ebcc417f16f736adb2502567119f0083467", ""},
+		TestHash{"3645c245bb31223ad93c80885b719aa40b4bed0a9d9d6e7c11fe99e59ca350b5", "a"},
+		TestHash{"2679d98913bee62e57fdbdde97ddb328373548c6b24fc587cc3d08f2a02a529c", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
+	}
+	for v, _ in test_vectors {
+		computed     := groestl.hash_256(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_groestl_384 :: proc(t: ^testing.T) {
-    test_vectors := [?]TestHash {
-        TestHash{"ac353c1095ace21439251007862d6c62f829ddbe6de4f78e68d310a9205a736d8b11d99bffe448f57a1cfa2934f044a5", ""},
-        TestHash{"13fce7bd9fc69b67cc12c77e765a0a97794c585f89df39fbff32408e060d7d9225c7e80fd87da647686888bda896c342", "a"},
-        TestHash{"1c446cd70a6de52c9db386f5305aae029fe5a4120bc6230b7cd3a5e1ef1949cc8e6d2548c24cd7347b5ba512628a62f6", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
-    }
-    for v, _ in test_vectors {
-        computed     := groestl.hash_384(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors := [?]TestHash {
+		TestHash{"ac353c1095ace21439251007862d6c62f829ddbe6de4f78e68d310a9205a736d8b11d99bffe448f57a1cfa2934f044a5", ""},
+		TestHash{"13fce7bd9fc69b67cc12c77e765a0a97794c585f89df39fbff32408e060d7d9225c7e80fd87da647686888bda896c342", "a"},
+		TestHash{"1c446cd70a6de52c9db386f5305aae029fe5a4120bc6230b7cd3a5e1ef1949cc8e6d2548c24cd7347b5ba512628a62f6", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
+	}
+	for v, _ in test_vectors {
+		computed     := groestl.hash_384(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_groestl_512 :: proc(t: ^testing.T) {
-    test_vectors := [?]TestHash {
-        TestHash{"6d3ad29d279110eef3adbd66de2a0345a77baede1557f5d099fce0c03d6dc2ba8e6d4a6633dfbd66053c20faa87d1a11f39a7fbe4a6c2f009801370308fc4ad8", ""},
-        TestHash{"9ef345a835ee35d6d0d462ce45f722d84b5ca41fde9c81a98a22cfb4f7425720511b03a258cdc055bf8e9179dc9bdb5d88bed906c71125d4cf0cd39d3d7bebc7", "a"},
-        TestHash{"862849fd911852cd54beefa88759db4cead0ef8e36aaf15398303c5c4cbc016d9b4c42b32081cbdcba710d2693e7663d244fae116ec29ffb40168baf44f944e7", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
-    }
-    for v, _ in test_vectors {
-        computed     := groestl.hash_512(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors := [?]TestHash {
+		TestHash{"6d3ad29d279110eef3adbd66de2a0345a77baede1557f5d099fce0c03d6dc2ba8e6d4a6633dfbd66053c20faa87d1a11f39a7fbe4a6c2f009801370308fc4ad8", ""},
+		TestHash{"9ef345a835ee35d6d0d462ce45f722d84b5ca41fde9c81a98a22cfb4f7425720511b03a258cdc055bf8e9179dc9bdb5d88bed906c71125d4cf0cd39d3d7bebc7", "a"},
+		TestHash{"862849fd911852cd54beefa88759db4cead0ef8e36aaf15398303c5c4cbc016d9b4c42b32081cbdcba710d2693e7663d244fae116ec29ffb40168baf44f944e7", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
+	}
+	for v, _ in test_vectors {
+		computed     := groestl.hash_512(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_haval_128 :: proc(t: ^testing.T) {
-    test_vectors_3 := [?]TestHash {
-        TestHash{"c68f39913f901f3ddf44c707357a7d70", ""},
-        TestHash{"0cd40739683e15f01ca5dbceef4059f1", "a"},
-        TestHash{"9e40ed883fb63e985d299b40cda2b8f2", "abc"},
-        TestHash{"3caf4a79e81adcd6d1716bcc1cef4573", "message digest"},
-        TestHash{"dc502247fb3eb8376109eda32d361d82", "abcdefghijklmnopqrstuvwxyz"},
-        TestHash{"44068770868768964d1f2c3bff4aa3d8", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
-        TestHash{"de5eb3f7d9eb08fae7a07d68e3047ec6", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},  
-    }
-    for v, _ in test_vectors_3 {
-        computed     := haval.hash_128_3(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
-    test_vectors_4 := [?]TestHash {
-        TestHash{"ee6bbf4d6a46a679b3a856c88538bb98", ""},
-        TestHash{"5cd07f03330c3b5020b29ba75911e17d", "a"},
-    }
-    for v, _ in test_vectors_4 {
-        computed     := haval.hash_128_4(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
-    test_vectors_5 := [?]TestHash {
-        TestHash{"184b8482a0c050dca54b59c7f05bf5dd", ""},
-        TestHash{"f23fbe704be8494bfa7a7fb4f8ab09e5", "a"},
-    }
-    for v, _ in test_vectors_5 {
-        computed     := haval.hash_128_5(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors_3 := [?]TestHash {
+		TestHash{"c68f39913f901f3ddf44c707357a7d70", ""},
+		TestHash{"0cd40739683e15f01ca5dbceef4059f1", "a"},
+		TestHash{"9e40ed883fb63e985d299b40cda2b8f2", "abc"},
+		TestHash{"3caf4a79e81adcd6d1716bcc1cef4573", "message digest"},
+		TestHash{"dc502247fb3eb8376109eda32d361d82", "abcdefghijklmnopqrstuvwxyz"},
+		TestHash{"44068770868768964d1f2c3bff4aa3d8", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
+		TestHash{"de5eb3f7d9eb08fae7a07d68e3047ec6", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
+	}
+	for v, _ in test_vectors_3 {
+		computed     := haval.hash_128_3(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
+	test_vectors_4 := [?]TestHash {
+		TestHash{"ee6bbf4d6a46a679b3a856c88538bb98", ""},
+		TestHash{"5cd07f03330c3b5020b29ba75911e17d", "a"},
+	}
+	for v, _ in test_vectors_4 {
+		computed     := haval.hash_128_4(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
+	test_vectors_5 := [?]TestHash {
+		TestHash{"184b8482a0c050dca54b59c7f05bf5dd", ""},
+		TestHash{"f23fbe704be8494bfa7a7fb4f8ab09e5", "a"},
+	}
+	for v, _ in test_vectors_5 {
+		computed     := haval.hash_128_5(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_haval_160 :: proc(t: ^testing.T) {
-    test_vectors_3 := [?]TestHash {
-        TestHash{"d353c3ae22a25401d257643836d7231a9a95f953", ""},
-        TestHash{"4da08f514a7275dbc4cece4a347385983983a830", "a"},
-        TestHash{"b21e876c4d391e2a897661149d83576b5530a089", "abc"},
-        TestHash{"43a47f6f1c016207f08be8115c0977bf155346da", "message digest"},
-        TestHash{"eba9fa6050f24c07c29d1834a60900ea4e32e61b", "abcdefghijklmnopqrstuvwxyz"},
-        TestHash{"c30bce448cf8cfe957c141e90c0a063497cdfeeb", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
-        TestHash{"97dc988d97caae757be7523c4e8d4ea63007a4b9", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"}, 
-    }
-    for v, _ in test_vectors_3 {
-        computed     := haval.hash_160_3(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
-    test_vectors_4 := [?]TestHash {
-        TestHash{"1d33aae1be4146dbaaca0b6e70d7a11f10801525", ""},
-        TestHash{"e0a5be29627332034d4dd8a910a1a0e6fe04084d", "a"},
-    }
-    for v, _ in test_vectors_4 {
-        computed     := haval.hash_160_4(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
-    test_vectors_5 := [?]TestHash {
-        TestHash{"255158cfc1eed1a7be7c55ddd64d9790415b933b", ""},
-        TestHash{"f5147df7abc5e3c81b031268927c2b5761b5a2b5", "a"},
-    }
-    for v, _ in test_vectors_5 {
-        computed     := haval.hash_160_5(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors_3 := [?]TestHash {
+		TestHash{"d353c3ae22a25401d257643836d7231a9a95f953", ""},
+		TestHash{"4da08f514a7275dbc4cece4a347385983983a830", "a"},
+		TestHash{"b21e876c4d391e2a897661149d83576b5530a089", "abc"},
+		TestHash{"43a47f6f1c016207f08be8115c0977bf155346da", "message digest"},
+		TestHash{"eba9fa6050f24c07c29d1834a60900ea4e32e61b", "abcdefghijklmnopqrstuvwxyz"},
+		TestHash{"c30bce448cf8cfe957c141e90c0a063497cdfeeb", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
+		TestHash{"97dc988d97caae757be7523c4e8d4ea63007a4b9", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
+	}
+	for v, _ in test_vectors_3 {
+		computed     := haval.hash_160_3(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
+	test_vectors_4 := [?]TestHash {
+		TestHash{"1d33aae1be4146dbaaca0b6e70d7a11f10801525", ""},
+		TestHash{"e0a5be29627332034d4dd8a910a1a0e6fe04084d", "a"},
+	}
+	for v, _ in test_vectors_4 {
+		computed     := haval.hash_160_4(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
+	test_vectors_5 := [?]TestHash {
+		TestHash{"255158cfc1eed1a7be7c55ddd64d9790415b933b", ""},
+		TestHash{"f5147df7abc5e3c81b031268927c2b5761b5a2b5", "a"},
+	}
+	for v, _ in test_vectors_5 {
+		computed     := haval.hash_160_5(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_haval_192 :: proc(t: ^testing.T) {
-    test_vectors_3 := [?]TestHash {
-        TestHash{"e9c48d7903eaf2a91c5b350151efcb175c0fc82de2289a4e", ""},
-        TestHash{"b359c8835647f5697472431c142731ff6e2cddcacc4f6e08", "a"},
-    }
-    for v, _ in test_vectors_3 {
-        computed     := haval.hash_192_3(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
-    test_vectors_4 := [?]TestHash {
-        TestHash{"4a8372945afa55c7dead800311272523ca19d42ea47b72da", ""},
-        TestHash{"856c19f86214ea9a8a2f0c4b758b973cce72a2d8ff55505c", "a"},
-    }
-    for v, _ in test_vectors_4 {
-        computed     := haval.hash_192_4(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
-    test_vectors_5 := [?]TestHash {
-        TestHash{"4839d0626f95935e17ee2fc4509387bbe2cc46cb382ffe85", ""},
-        TestHash{"5ffa3b3548a6e2cfc06b7908ceb5263595df67cf9c4b9341", "a"},
-    }
-    for v, _ in test_vectors_5 {
-        computed     := haval.hash_192_5(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors_3 := [?]TestHash {
+		TestHash{"e9c48d7903eaf2a91c5b350151efcb175c0fc82de2289a4e", ""},
+		TestHash{"b359c8835647f5697472431c142731ff6e2cddcacc4f6e08", "a"},
+	}
+	for v, _ in test_vectors_3 {
+		computed     := haval.hash_192_3(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
+	test_vectors_4 := [?]TestHash {
+		TestHash{"4a8372945afa55c7dead800311272523ca19d42ea47b72da", ""},
+		TestHash{"856c19f86214ea9a8a2f0c4b758b973cce72a2d8ff55505c", "a"},
+	}
+	for v, _ in test_vectors_4 {
+		computed     := haval.hash_192_4(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
+	test_vectors_5 := [?]TestHash {
+		TestHash{"4839d0626f95935e17ee2fc4509387bbe2cc46cb382ffe85", ""},
+		TestHash{"5ffa3b3548a6e2cfc06b7908ceb5263595df67cf9c4b9341", "a"},
+	}
+	for v, _ in test_vectors_5 {
+		computed     := haval.hash_192_5(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_haval_224 :: proc(t: ^testing.T) {
-    test_vectors_3 := [?]TestHash {
-        TestHash{"c5aae9d47bffcaaf84a8c6e7ccacd60a0dd1932be7b1a192b9214b6d", ""},
-        TestHash{"731814ba5605c59b673e4caae4ad28eeb515b3abc2b198336794e17b", "a"},
-    }
-    for v, _ in test_vectors_3 {
-        computed     := haval.hash_224_3(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
-    test_vectors_4 := [?]TestHash {
-        TestHash{"3e56243275b3b81561750550e36fcd676ad2f5dd9e15f2e89e6ed78e", ""},
-        TestHash{"742f1dbeeaf17f74960558b44f08aa98bdc7d967e6c0ab8f799b3ac1", "a"},
-    }
-    for v, _ in test_vectors_4 {
-        computed     := haval.hash_224_4(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
-    test_vectors_5 := [?]TestHash {
-        TestHash{"4a0513c032754f5582a758d35917ac9adf3854219b39e3ac77d1837e", ""},
-        TestHash{"67b3cb8d4068e3641fa4f156e03b52978b421947328bfb9168c7655d", "a"},
-    }
-    for v, _ in test_vectors_5 {
-        computed     := haval.hash_224_5(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors_3 := [?]TestHash {
+		TestHash{"c5aae9d47bffcaaf84a8c6e7ccacd60a0dd1932be7b1a192b9214b6d", ""},
+		TestHash{"731814ba5605c59b673e4caae4ad28eeb515b3abc2b198336794e17b", "a"},
+	}
+	for v, _ in test_vectors_3 {
+		computed     := haval.hash_224_3(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
+	test_vectors_4 := [?]TestHash {
+		TestHash{"3e56243275b3b81561750550e36fcd676ad2f5dd9e15f2e89e6ed78e", ""},
+		TestHash{"742f1dbeeaf17f74960558b44f08aa98bdc7d967e6c0ab8f799b3ac1", "a"},
+	}
+	for v, _ in test_vectors_4 {
+		computed     := haval.hash_224_4(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
+	test_vectors_5 := [?]TestHash {
+		TestHash{"4a0513c032754f5582a758d35917ac9adf3854219b39e3ac77d1837e", ""},
+		TestHash{"67b3cb8d4068e3641fa4f156e03b52978b421947328bfb9168c7655d", "a"},
+	}
+	for v, _ in test_vectors_5 {
+		computed     := haval.hash_224_5(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
 }
 
 @(test)
 test_haval_256 :: proc(t: ^testing.T) {
-    test_vectors_3 := [?]TestHash {
-        TestHash{"4f6938531f0bc8991f62da7bbd6f7de3fad44562b8c6f4ebf146d5b4e46f7c17", ""},
-        TestHash{"47c838fbb4081d9525a0ff9b1e2c05a98f625714e72db289010374e27db021d8", "a"},
-    }
-    for v, _ in test_vectors_3 {
-        computed     := haval.hash_256_3(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
-    test_vectors_4 := [?]TestHash {
-        TestHash{"c92b2e23091e80e375dadce26982482d197b1a2521be82da819f8ca2c579b99b", ""},
-        TestHash{"e686d2394a49b44d306ece295cf9021553221db132b36cc0ff5b593d39295899", "a"},
-    }
-    for v, _ in test_vectors_4 {
-        computed     := haval.hash_256_4(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
-    test_vectors_5 := [?]TestHash {
-        TestHash{"be417bb4dd5cfb76c7126f4f8eeb1553a449039307b1a3cd451dbfdc0fbbe330", ""},
-        TestHash{"de8fd5ee72a5e4265af0a756f4e1a1f65c9b2b2f47cf17ecf0d1b88679a3e22f", "a"},
-    }
-    for v, _ in test_vectors_5 {
-        computed     := haval.hash_256_5(v.str)
-        computed_str := hex_string(computed[:])
-        expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
-    }
+	test_vectors_3 := [?]TestHash {
+		TestHash{"4f6938531f0bc8991f62da7bbd6f7de3fad44562b8c6f4ebf146d5b4e46f7c17", ""},
+		TestHash{"47c838fbb4081d9525a0ff9b1e2c05a98f625714e72db289010374e27db021d8", "a"},
+	}
+	for v, _ in test_vectors_3 {
+		computed     := haval.hash_256_3(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
+	test_vectors_4 := [?]TestHash {
+		TestHash{"c92b2e23091e80e375dadce26982482d197b1a2521be82da819f8ca2c579b99b", ""},
+		TestHash{"e686d2394a49b44d306ece295cf9021553221db132b36cc0ff5b593d39295899", "a"},
+	}
+	for v, _ in test_vectors_4 {
+		computed     := haval.hash_256_4(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
+	test_vectors_5 := [?]TestHash {
+		TestHash{"be417bb4dd5cfb76c7126f4f8eeb1553a449039307b1a3cd451dbfdc0fbbe330", ""},
+		TestHash{"de8fd5ee72a5e4265af0a756f4e1a1f65c9b2b2f47cf17ecf0d1b88679a3e22f", "a"},
+	}
+	for v, _ in test_vectors_5 {
+		computed     := haval.hash_256_5(v.str)
+		computed_str := hex_string(computed[:])
+		expect(t, computed_str == v.hash, fmt.tprintf("Expected: %s for input of %s, but got %s instead", v.hash, v.str, computed_str))
+	}
+}
+
+@(test)
+test_siphash_2_4 :: proc(t: ^testing.T) {
+	// Test vectors from
+	// https://github.com/veorq/SipHash/blob/master/vectors.h
+	test_vectors := [?]u64 {
+		0x726fdb47dd0e0e31, 0x74f839c593dc67fd, 0x0d6c8009d9a94f5a, 0x85676696d7fb7e2d,
+		0xcf2794e0277187b7, 0x18765564cd99a68d, 0xcbc9466e58fee3ce, 0xab0200f58b01d137,
+		0x93f5f5799a932462, 0x9e0082df0ba9e4b0, 0x7a5dbbc594ddb9f3, 0xf4b32f46226bada7,
+		0x751e8fbc860ee5fb, 0x14ea5627c0843d90, 0xf723ca908e7af2ee, 0xa129ca6149be45e5,
+		0x3f2acc7f57c29bdb, 0x699ae9f52cbe4794, 0x4bc1b3f0968dd39c, 0xbb6dc91da77961bd,
+		0xbed65cf21aa2ee98, 0xd0f2cbb02e3b67c7, 0x93536795e3a33e88, 0xa80c038ccd5ccec8,
+		0xb8ad50c6f649af94, 0xbce192de8a85b8ea, 0x17d835b85bbb15f3, 0x2f2e6163076bcfad,
+		0xde4daaaca71dc9a5, 0xa6a2506687956571, 0xad87a3535c49ef28, 0x32d892fad841c342,
+		0x7127512f72f27cce, 0xa7f32346f95978e3, 0x12e0b01abb051238, 0x15e034d40fa197ae,
+		0x314dffbe0815a3b4, 0x027990f029623981, 0xcadcd4e59ef40c4d, 0x9abfd8766a33735c,
+		0x0e3ea96b5304a7d0, 0xad0c42d6fc585992, 0x187306c89bc215a9, 0xd4a60abcf3792b95,
+		0xf935451de4f21df2, 0xa9538f0419755787, 0xdb9acddff56ca510, 0xd06c98cd5c0975eb,
+		0xe612a3cb9ecba951, 0xc766e62cfcadaf96, 0xee64435a9752fe72, 0xa192d576b245165a,
+		0x0a8787bf8ecb74b2, 0x81b3e73d20b49b6f, 0x7fa8220ba3b2ecea, 0x245731c13ca42499,
+		0xb78dbfaf3a8d83bd, 0xea1ad565322a1a0b, 0x60e61c23a3795013, 0x6606d7e446282b93,
+		0x6ca4ecb15c5f91e1, 0x9f626da15c9625f3, 0xe51b38608ef25f57, 0x958a324ceb064572,
+	}
+
+	key: [16]byte
+	for i in 0..<16 {
+		key[i] = byte(i)
+	}
+
+	for i in 0..<len(test_vectors) {
+		data := make([]byte, i)
+		for j in 0..<i {
+			data[j] = byte(j)
+		}
+
+		vector   := test_vectors[i]
+		computed := siphash.sum_2_4(data[:], key[:])
+
+		expect(t, computed == vector, fmt.tprintf("Expected: 0x%x for input of %v, but got 0x%x instead", vector, data, computed))
+	}
 }

BIN
tests/core/crypto_hash


+ 21 - 19
tests/core/encoding/test_core_json.odin → tests/core/encoding/json/test_core_json.odin

@@ -3,37 +3,39 @@ package test_core_json
 import "core:encoding/json"
 import "core:testing"
 import "core:fmt"
+import "core:os"
 
 TEST_count := 0
 TEST_fail  := 0
 
 when ODIN_TEST {
-    expect  :: testing.expect
-    log     :: testing.log
+	expect  :: testing.expect
+	log     :: testing.log
 } else {
-    expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-        fmt.printf("[%v] ", loc)
-        TEST_count += 1
-        if !condition {
-            TEST_fail += 1
-            fmt.println(message)
-            return
-        }
-        fmt.println(" PASS")
-    }
-    log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-        fmt.printf("[%v] ", loc)
-        fmt.printf("log: %v\n", v)
-    }
+	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
+		TEST_count += 1
+		if !condition {
+			TEST_fail += 1
+			fmt.printf("[%v] %v\n", loc, message)
+			return
+		}
+	}
+	log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
+		fmt.printf("[%v] ", loc)
+		fmt.printf("log: %v\n", v)
+	}
 }
 
 main :: proc() {
-    t := testing.T{}
-	
+	t := testing.T{}
+
 	parse_json(&t)
 	marshal_json(&t)
 
-    fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
+	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
+	if TEST_fail > 0 {
+		os.exit(1)
+	}
 }
 
 @test

+ 156 - 0
tests/core/encoding/varint/test_core_varint.odin

@@ -0,0 +1,156 @@
+package test_core_varint
+
+import "core:encoding/varint"
+import "core:testing"
+import "core:fmt"
+import "core:os"
+import "core:slice"
+import "core:math/rand"
+
+TEST_count := 0
+TEST_fail  := 0
+
+RANDOM_TESTS :: 100
+
+when ODIN_TEST {
+	expect  :: testing.expect
+	log     :: testing.log
+} else {
+	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
+		TEST_count += 1
+		if !condition {
+			TEST_fail += 1
+			fmt.printf("[%v] %v\n", loc, message)
+			return
+		}
+	}
+	log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
+		fmt.printf("[%v] ", loc)
+		fmt.printf("log: %v\n", v)
+	}
+}
+
+main :: proc() {
+	t := testing.T{}
+
+	test_leb128(&t)
+
+	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
+	if TEST_fail > 0 {
+		os.exit(1)
+	}
+}
+
+@(test)
+test_leb128 :: proc(t: ^testing.T) {
+	buf: [varint.LEB128_MAX_BYTES]u8
+
+	for vector in ULEB_Vectors {
+		val, size, err := varint.decode_uleb128(vector.encoded)
+
+		msg := fmt.tprintf("Expected %02x to decode to %v consuming %v bytes, got %v and %v", vector.encoded, vector.value, vector.size, val, size)
+		expect(t, size == vector.size && val == vector.value, msg)
+
+		msg  = fmt.tprintf("Expected decoder to return error %v, got %v", vector.error, err)
+		expect(t, err == vector.error, msg)
+
+		if err == .None { // Try to roundtrip
+			size, err = varint.encode_uleb128(buf[:], vector.value)
+
+			msg = fmt.tprintf("Expected %v to encode to %02x, got %02x", vector.value, vector.encoded, buf[:size])
+			expect(t, size == vector.size && slice.simple_equal(vector.encoded, buf[:size]), msg)
+		}
+	}
+
+	for vector in ILEB_Vectors {
+		val, size, err := varint.decode_ileb128(vector.encoded)
+
+		msg := fmt.tprintf("Expected %02x to decode to %v consuming %v bytes, got %v and %v", vector.encoded, vector.value, vector.size, val, size)
+		expect(t, size == vector.size && val == vector.value, msg)
+
+		msg  = fmt.tprintf("Expected decoder to return error %v, got %v", vector.error, err)
+		expect(t, err == vector.error, msg)
+
+		if err == .None { // Try to roundtrip
+			size, err = varint.encode_ileb128(buf[:], vector.value)
+
+			msg = fmt.tprintf("Expected %v to encode to %02x, got %02x", vector.value, vector.encoded, buf[:size])
+			expect(t, size == vector.size && slice.simple_equal(vector.encoded, buf[:size]), msg)
+		}
+	}
+
+	for num_bytes in 1..uint(16) {
+		for _ in 0..RANDOM_TESTS {
+			unsigned, signed := get_random(num_bytes)
+
+			{
+				encode_size, encode_err := varint.encode_uleb128(buf[:], unsigned)
+				msg := fmt.tprintf("%v failed to encode as an unsigned LEB128 value, got %v", unsigned, encode_err)
+				expect(t, encode_err == .None, msg)
+
+				decoded, decode_size, decode_err := varint.decode_uleb128(buf[:])
+				msg = fmt.tprintf("Expected %02x to decode as %v, got %v", buf[:encode_size], unsigned, decoded)
+				expect(t, decode_err == .None && decode_size == encode_size && decoded == unsigned, msg)
+			}
+
+			{
+				encode_size, encode_err := varint.encode_ileb128(buf[:], signed)
+				msg := fmt.tprintf("%v failed to encode as a signed LEB128 value, got %v", signed, encode_err)
+				expect(t, encode_err == .None, msg)
+
+				decoded, decode_size, decode_err := varint.decode_ileb128(buf[:])
+				msg = fmt.tprintf("Expected %02x to decode as %v, got %v, err: %v", buf[:encode_size], signed, decoded, decode_err)
+				expect(t, decode_err == .None && decode_size == encode_size && decoded == signed, msg)
+			}
+		}
+	}
+}
+
+get_random :: proc(byte_count: uint) -> (u: u128, i: i128) {
+	assert(byte_count >= 0 && byte_count <= size_of(u128))
+
+	for _ in 1..byte_count {
+		u <<= 8
+		u |= u128(rand.uint32() & 0xff)
+	}
+
+	bias := i128(1 << (byte_count * 7)) - 1
+	i     = i128(u) - bias
+
+	return
+}
+
+ULEB_Test_Vector :: struct {
+	encoded: []u8,
+	value:   u128,
+	size:    int,
+	error:   varint.Error,
+}
+
+ULEB_Vectors :: []ULEB_Test_Vector{
+	{ []u8{0x00},             0,         1, .None },
+	{ []u8{0x7f},             127,       1, .None },
+	{ []u8{0xE5, 0x8E, 0x26}, 624485,    3, .None },
+	{ []u8{0x80},             0,         0, .Buffer_Too_Small },
+	{ []u8{},                 0,         0, .Buffer_Too_Small },
+
+	{ []u8{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03}, max(u128), 19, .None },
+}
+
+ILEB_Test_Vector :: struct {
+	encoded: []u8,
+	value:   i128,
+	size:    int,
+	error:   varint.Error,
+}
+
+ILEB_Vectors :: []ILEB_Test_Vector{
+	{ []u8{0x00},             0,       1, .None },
+	{ []u8{0x3f},             63,      1, .None },
+	{ []u8{0x40},             -64,     1, .None },
+	{ []u8{0xC0, 0xBB, 0x78}, -123456, 3, .None },
+	{ []u8{},                 0,       0, .Buffer_Too_Small },
+
+	{ []u8{0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7e}, min(i128), 19, .None },
+	{ []u8{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01}, max(i128), 19, .None },
+}

+ 5 - 3
tests/core/hash/test_core_hash.odin

@@ -5,6 +5,7 @@ import "core:hash"
 import "core:time"
 import "core:testing"
 import "core:fmt"
+import "core:os"
 
 TEST_count := 0
 TEST_fail  := 0
@@ -14,14 +15,12 @@ when ODIN_TEST {
 	log     :: testing.log
 } else {
 	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-		fmt.printf("[%v] ", loc)
 		TEST_count += 1
 		if !condition {
 			TEST_fail += 1
-			fmt.println(" FAIL:", message)
+			fmt.printf("[%v] %v\n", loc, message)
 			return
 		}
-		fmt.println(" PASS")
 	}
 	log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
 		fmt.printf("[%v] ", loc)
@@ -35,6 +34,9 @@ main :: proc() {
 	test_xxhash_vectors(&t)
 	test_crc64_vectors(&t)
 	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
+	if TEST_fail > 0 {
+		os.exit(1)
+	}
 }
 
 /*

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