Browse Source

Merge branch 'master' into fix-sync-badopt

Tetralux 5 years ago
parent
commit
e7e936f480

+ 21 - 12
.github/workflows/nightly.yml

@@ -1,4 +1,5 @@
 name:  Nightly
+
 on:
   schedule:
     - cron: 0 20 * * *
@@ -91,36 +92,44 @@ jobs:
     needs: [build_windows, build_macos, build_ubuntu]
     steps:
       - uses: actions/checkout@v1
+      
       - name: Install B2 CLI
         shell: bash
         run: sudo pip install --upgrade b2
+      
       - name: Download Windows artifacts
         uses: actions/download-artifact@v1
         with:
           name: windows_artifacts
+      
       - name: Download Ubuntu artifacts
         uses: actions/download-artifact@v1
         with:
           name: ubuntu_artifacts
+      
       - name: Download macOS artifacts
         uses: actions/download-artifact@v1
         with:
           name: macos_artifacts
-      - name: Create archieves
-        run: |
-          now=$(date +'%Y-%m-%d')
-          7z a output/odin-windows-amd64-nightly+$now.zip -r windows_artifacts/
-          7z a output/odin-ubuntu-amd64-nightly+$now.zip -r ubuntu_artifacts/
-          7z a output/odin-macos-amd64-nightly+$now.zip -r macos_artifact/
-      - name: Upload artifacts to b2
+
+      - name: Create archives and upload
         shell: bash
         env:
           APPID: ${{ secrets.B2_APPID }}
           APPKEY: ${{ secrets.B2_APPKEY }}
+          BUCKET: ${{ secrets.B2_BUCKET }}
+          DAYS_TO_KEEP: ${{ secrets.B2_DAYS_TO_KEEP }}
         run: |
           b2 authorize-account "$APPID" "$APPKEY"
-          b2 sync --keepDays 7 output b2://odin-binaries/nightly
-          python3 ci/create_nightly_json.py > nightly.json
-          b2 upload-file odin-binaries nightly.json nightly.json
-          b2 clear-account
-        
+
+          chmod +x ./ci/upload_create_nightly.sh
+          ./ci/upload_create_nightly.sh "$BUCKET" windows-amd64 windows_artifacts/
+          ./ci/upload_create_nightly.sh "$BUCKET" ubuntu-amd64 ubuntu_artifacts/
+          ./ci/upload_create_nightly.sh "$BUCKET" macos-amd64 macos_artifacts/
+
+          python3 ci/delete_old_binaries.py "$BUCKET" "$DAYS_TO_KEEP"
+
+          python3 ci/create_nightly_json.py "$BUCKET" > nightly.json
+          b2 upload-file "$BUCKET" nightly.json nightly.json
+
+          b2 clear-account

+ 12 - 5
ci/build_ci.bat

@@ -1,8 +1,11 @@
 @echo off
 
+:: Make sure this is a decent name and not generic
 set exe_name=odin.exe
 
-set compiler_flags= -nologo -Oi -TP -fp:precise -Gm- -MP -FC -GS- -EHsc- -GR- -O2 -MT -Z7 -DNO_ARRAY_BOUNDS_CHECK
+set compiler_flags= -nologo -Oi -TP -fp:precise -Gm- -MP -FC -EHsc- -GR- -GF -O2 -MT -Z7
+set compiler_defines= -DLLVM_BACKEND_SUPPORT -DNO_ARRAY_BOUNDS_CHECK
+
 set compiler_warnings= ^
     -W4 -WX ^
     -wd4100 -wd4101 -wd4127 -wd4189 ^
@@ -12,13 +15,17 @@ set compiler_warnings= ^
 
 set compiler_includes=
 set libs= ^
-    kernel32.lib
+    kernel32.lib ^
+    bin\llvm\windows\LLVM-C.lib
 
 set linker_flags= -incremental:no -opt:ref -subsystem:console -debug
 
-set compiler_settings=%compiler_includes% %compiler_flags% %compiler_warnings%
+set compiler_settings=%compiler_includes% %compiler_flags% %compiler_warnings% %compiler_defines%
 set linker_settings=%libs% %linker_flags%
 
-cl %compiler_settings% "src\main.cpp" ^
-    /link %linker_settings% -OUT:%exe_name% ^
+del *.pdb > NUL 2> NUL
+del *.ilk > NUL 2> NUL
 
+cl %compiler_settings% "src\main.cpp" /link %linker_settings% -OUT:%exe_name%
+    
+:end_of_build

+ 4 - 2
ci/create_nightly_json.py

@@ -3,18 +3,20 @@ import sys
 import json
 import datetime
 import urllib.parse
+import sys
 
 def main():
     files_by_date = {}
+    bucket = sys.argv[1]
 
-    files_lines = execute_cli("b2 ls --long odin-binaries nightly").split("\n")
+    files_lines = execute_cli(f"b2 ls --long {bucket} nightly").split("\n")
     for x in files_lines:
         parts = x.split(" ", 1)
         if parts[0]:
             json_str = execute_cli(f"b2 get-file-info {parts[0]}")
             data = json.loads(json_str)
             name = remove_prefix(data['fileName'], "nightly/")
-            url = f"https://f001.backblazeb2.com/file/odin-binaries/nightly/{urllib.parse.quote_plus(name)}"
+            url = f"https://f001.backblazeb2.com/file/{bucket}/nightly/{urllib.parse.quote_plus(name)}"
             sha1 = data['contentSha1']
             ts = int(data['fileInfo']['src_last_modified_millis'])
             date = datetime.datetime.fromtimestamp(ts/1000).strftime('%Y-%m-%d')

+ 34 - 0
ci/delete_old_binaries.py

@@ -0,0 +1,34 @@
+import subprocess
+import sys
+import json
+import datetime
+import urllib.parse
+import sys
+
+def main():
+    files_by_date = {}
+    bucket = sys.argv[1]
+    days_to_keep = int(sys.argv[2])
+    print(f"Looking for binaries to delete older than {days_to_keep} days")
+
+    files_lines = execute_cli(f"b2 ls --long --versions {bucket} nightly").split("\n")
+    for x in files_lines:
+        parts = [y for y in x.split(' ') if y]
+
+        if parts and parts[0]:
+            date = datetime.datetime.strptime(parts[2], '%Y-%m-%d').replace(hour=0, minute=0, second=0, microsecond=0)
+            now = datetime.datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
+            delta = now - date
+
+            if delta.days > days_to_keep:
+                print(f'Deleting {parts[5]}')
+                execute_cli(f'b2 delete-file-version {parts[0]}')
+
+
+def execute_cli(command):
+    sb = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
+    return sb.stdout.read().decode("utf-8");
+
+if __name__ == '__main__':
+    sys.exit(main())
+

+ 13 - 0
ci/upload_create_nightly.sh

@@ -0,0 +1,13 @@
+#!/bin/bash
+
+bucket=$1
+platform=$2
+artifact=$3
+
+now=$(date +'%Y-%m-%d')
+filename="odin-$platform-nightly+$now.zip"
+
+echo "Creating archive $filename from $artifact and uploading to $bucket"
+
+7z a -bd "output/$filename" -r "$artifact"
+b2 upload-file --noProgress "$bucket" "output/$filename" "nightly/$filename"

+ 73 - 23
core/container/array.odin

@@ -1,6 +1,7 @@
 package container
 
 import "core:mem"
+import "core:runtime"
 
 Array :: struct(T: typeid) {
 	data:      ^T,
@@ -9,6 +10,38 @@ Array :: struct(T: typeid) {
 	allocator: mem.Allocator,
 }
 
+/*
+array_init :: proc {
+	array_init_none,
+	array_init_len,
+	array_init_len_cap,
+}
+array_init
+array_delete
+array_len
+array_cap
+array_space
+array_slice
+array_get
+array_get_ptr
+array_set
+array_reserve
+array_resize
+array_push = array_append :: proc{
+	array_push_back, 
+	array_push_back_elems,
+}
+array_push_front
+array_pop_back
+array_pop_font
+array_consume
+array_trim
+array_clear
+array_clone
+array_set_capacity
+array_grow
+*/
+
 array_init_none :: proc(a: ^$A/Array, allocator := context.allocator) {
 	array_init_len(a, 0, allocator);
 }
@@ -16,10 +49,10 @@ array_init_len :: proc(a: ^$A/Array, len: int, allocator := context.allocator) {
 	array_init_len_cap(a, 0, 16, allocator);
 }
 array_init_len_cap :: proc(a: ^$A/Array($T), len: int, cap: int, allocator := context.allocator) {
-	a.data = (^T)(mem.alloc(size_of(T)*cap, align_of(T), allocator));
+	a.allocator = allocator;
+	a.data = (^T)(mem.alloc(size_of(T)*cap, align_of(T), a.allocator));
 	a.len = len;
 	a.cap = cap;
-	a.allocator = allocator;
 }
 
 array_init :: proc{array_init_none, array_init_len, array_init_len_cap};
@@ -46,17 +79,23 @@ array_slice :: proc(a: $A/Array($T)) -> []T {
 }
 
 
-array_get :: proc(a: $A/Array($T), index: int) -> T {
+array_get :: proc(a: $A/Array($T), index: int, loc := #caller_location) -> T {
+	runtime.bounds_check_error_loc(loc, index, array_len(a));
 	return (^T)(uintptr(a.data) + size_of(T)*uintptr(index))^;
 }
+array_get_ptr :: proc(a: $A/Array($T), index: int, loc := #caller_location) -> ^T {
+	runtime.bounds_check_error_loc(loc, index, array_len(a));
+	return (^T)(uintptr(a.data) + size_of(T)*uintptr(index));
+}
 
-array_set :: proc(a: ^$A/Array($T), index: int, item: T)  {
+array_set :: proc(a: ^$A/Array($T), index: int, item: T, loc := #caller_location)  {
+	runtime.bounds_check_error_loc(loc, index, array_len(a^));
 	(^T)(uintptr(a.data) + size_of(T)*uintptr(index))^ = item;
 }
 
 
 array_reserve :: proc(a: ^$A/Array, capacity: int) {
-	if capacity > a.size {
+	if capacity > a.len {
 		array_set_capacity(a, capacity);
 	}
 }
@@ -75,8 +114,8 @@ array_push_back :: proc(a: ^$A/Array($T), item: T) {
 		array_grow(a);
 	}
 
-	a.size += 1;
-	array_set(a, a.size, item);
+	a.len += 1;
+	array_set(a, a.len-1, item);
 }
 
 array_push_front :: proc(a: ^$A/Array($T), item: T) {
@@ -90,15 +129,15 @@ array_push_front :: proc(a: ^$A/Array($T), item: T) {
 	data[0] = item;
 }
 
-array_pop_back :: proc(a: ^$A/Array($T)) -> T {
-	assert(a.len > 0);
+array_pop_back :: proc(a: ^$A/Array($T), loc := #caller_location) -> T {
+	assert(condition=a.len > 0, loc=loc);
 	item := array_get(a^, a.len-1);
 	a.len -= 1;
 	return item;
 }
 
-array_pop_font :: proc(a: ^$A/Array($T)) -> T {
-	assert(a.len > 0);
+array_pop_font :: proc(a: ^$A/Array($T), loc := #caller_location) -> T {
+	assert(condition=a.len > 0, loc=loc);
 	item := array_get(a^, 0);
 	s := array_slice(a^);
 	copy(s[:], s[1:]);
@@ -107,9 +146,9 @@ array_pop_font :: proc(a: ^$A/Array($T)) -> T {
 }
 
 
-array_consume :: proc(a: ^$A/Array($T), count: int) {
-	assert(a.size >= count);
-	a.size -= count;
+array_consume :: proc(a: ^$A/Array($T), count: int, loc := #caller_location) {
+	assert(condition=a.len >= count, loc=loc);
+	a.len -= count;
 }
 
 
@@ -117,22 +156,30 @@ array_trim :: proc(a: ^$A/Array($T)) {
 	array_set_capacity(a, a.len);
 }
 
-array_clear :: proc(q: ^$Q/Queue($T)) {
-	array_resize(q, 0);
+array_clear :: proc(a: ^$A/Array($T)) {
+	array_resize(a, 0);
+}
+
+array_clone :: proc(a: $A/Array($T), allocator := context.allocator) -> A {
+	res: A;
+	array_init(&res, array_len(a), array_len(a), allocator);
+	copy(array_slice(res), array_slice(a));
+	return res;
 }
 
 
-array_push :: proc(a: ^$A/Array($T), items: ..T) {
+array_push_back_elems :: proc(a: ^$A/Array($T), items: ..T) {
 	if array_space(a^) < len(items) {
-		array_grow(a, a.size + len(items));
+		array_grow(a, a.len + len(items));
 	}
 	offset := a.len;
-	a.len += len(items);
 	data := array_slice(a^);
-	n := copy(data[offset:], items);
-	a.len = offset + n;
+	n := copy(data[a.len:], items);
+	a.len += n;
 }
 
+array_push   :: proc{array_push_back, array_push_back_elems};
+array_append :: proc{array_push_back, array_push_back_elems};
 
 array_set_capacity :: proc(a: ^$A/Array($T), new_capacity: int) {
 	if new_capacity == a.cap {
@@ -145,16 +192,19 @@ array_set_capacity :: proc(a: ^$A/Array($T), new_capacity: int) {
 
 	new_data: ^T;
 	if new_capacity > 0 {
+		if a.allocator.procedure == nil {
+			a.allocator = context.allocator;
+		}
 		new_data = (^T)(mem.alloc(size_of(T)*new_capacity, align_of(T), a.allocator));
 		if new_data != nil {
 			mem.copy(new_data, a.data, size_of(T)*a.len);
 		}
 	}
-	mem.free(a.data);
+	mem.free(a.data, a.allocator);
 	a.data = new_data;
 	a.cap = new_capacity;
 }
 array_grow :: proc(a: ^$A/Array, min_capacity: int = 0) {
-	new_capacity := max(len(a.data)*2 + 8, min_capacity);
+	new_capacity := max(array_len(a^)*2 + 8, min_capacity);
 	array_set_capacity(a, new_capacity);
 }

+ 363 - 0
core/container/map.odin

@@ -0,0 +1,363 @@
+package container
+
+
+Map :: struct(Value: typeid) {
+	hash: Array(int),
+	entries: Array(Map_Entry(Value)),
+}
+
+Map_Entry :: struct(Value: typeid) {
+	key:   u64,
+	next:  int,
+	value: Value,
+}
+
+
+/*
+map_init :: proc{
+	map_init_none,
+	map_init_cap,
+}
+map_delete
+
+map_has
+map_get
+map_get_default
+map_get_ptr
+map_set
+map_remove
+map_reserve
+map_clear
+
+// Multi Map
+
+multi_map_find_first
+multi_map_find_next
+multi_map_count
+multi_map_get :: proc{
+	multi_map_get_array,
+	multi_map_get_slice,
+};
+multi_map_get_as_slice
+multi_map_insert
+multi_map_remove
+multi_map_remove_all
+
+*/
+
+map_init :: proc{map_init_none, map_init_cap};
+
+map_init_none :: proc(m: ^$M/Map($Value), allocator := context.allocator) {
+	m.hash.allocator = allocator;
+	m.entries.allocator = allocator;
+}
+
+map_init_cap :: proc(m: ^$M/Map($Value), cap: int, allocator := context.allocator) {
+	m.hash.allocator = allocator;
+	m.entries.allocator = allocator;
+	map_reserve(m, cap);
+}
+
+map_delete :: proc(m: $M/Map($Value)) {
+	array_delete(m.hash);
+	array_delete(m.entries);
+}
+
+
+map_has :: proc(m: $M/Map($Value), key: u64) -> bool {
+	return _map_find_or_fail(m, key) >= 0;
+}
+
+map_get :: proc(m: $M/Map($Value), key: u64) -> (res: Value, ok: bool) #optional_ok {
+	i := _map_find_or_fail(m, key);
+	if i < 0 {
+		return {}, false;
+	}
+	return array_get(m.entries, i).value, true;
+}
+
+map_get_default :: proc(m: $M/Map($Value), key: u64, default: Value) -> (res: Value, ok: bool) #optional_ok {
+	i := _map_find_or_fail(m, key);
+	if i < 0 {
+		return default, false;
+	}
+	return array_get(m.entries, i).value, true;
+}
+
+map_get_ptr :: proc(m: $M/Map($Value), key: u64) -> ^Value {
+	i := _map_find_or_fail(m, key);
+	if i < 0 {
+		return nil;
+	}
+	return array_get_ptr(m.entries, i).value;
+}
+
+map_set :: proc(m: ^$M/Map($Value), key: u64, value: Value) {
+	if array_len(m.hash) == 0 {
+		_map_grow(m);
+	}
+
+	i := _map_find_or_make(m, key);
+	array_get_ptr(m.entries, i).value = value;
+	if _map_full(m^) {
+		_map_grow(m);
+	}
+}
+
+map_remove :: proc(m: ^$M/Map($Value), key: u64) {
+	fr := _map_find_key(m^, key);
+	if fr.entry_index >= 0 {
+		_map_erase(m, fr);
+	}
+}
+
+
+map_reserve :: proc(m: ^$M/Map($Value), new_size: int) {
+	nm: M;
+	map_init(&nm, m.hash.allocator);
+	array_resize(&nm.hash, new_size);
+	array_reserve(&nm.entries, array_len(m.entries));
+
+	for i in 0..<new_size {
+		array_set(&nm.hash, i, -1);
+	}
+	for i in 0..<array_len(m.entries) {
+		e := array_get(m.entries, i);
+		multi_map_insert(&nm, e.key, e.value);
+	}
+
+	map_delete(m^);
+	m^ = nm;
+}
+
+map_clear :: proc(m: ^$M/Map($Value)) {
+	array_clear(&m.hash);
+	array_clear(&m.entries);
+}
+
+
+
+multi_map_find_first :: proc(m: $M/Map($Value), key: u64) -> ^Map_Entry(Value) {
+	i := _map_find_or_fail(m, key);
+	if i < 0 {
+		return nil;
+	}
+	return array_get_ptr(m.entries, i);
+}
+
+multi_map_find_next :: proc(m: $M/Map($Value), e: ^Map_Entry(Value)) -> ^Map_Entry(Value) {
+	i := e.next;
+	for i >= 0 {
+		it := array_get_ptr(m.entries, i);
+		if it.key == e.key {
+			return it;
+		}
+		i = it.next;
+	}
+	return nil;
+}
+
+multi_map_count :: proc(m: $M/Map($Value), key: u64) -> int {
+	n := 0;
+	e := multi_map_find_first(m, key);
+	for e != nil {
+		n += 1;
+		e = multi_map_find_next(m, e);
+	}
+	return n;
+}
+
+multi_map_get :: proc{multi_map_get_array, multi_map_get_slice};
+
+multi_map_get_array :: proc(m: $M/Map($Value), key: u64, items: ^Array(Value)) {
+	if items == nil do return;
+	e := multi_map_find_first(m, key);
+	for e != nil {
+		array_append(items, e.value);
+		e = multi_map_find_next(m, e);
+	}
+}
+
+multi_map_get_slice :: proc(m: $M/Map($Value), key: u64, items: []Value) {
+	e := multi_map_find_first(m, key);
+	i := 0;
+	for e != nil && i < len(items) {
+		items[i] = e.value;
+		i += 1;
+		e = multi_map_find_next(m, e);
+	}
+}
+
+multi_map_get_as_slice :: proc(m: $M/Map($Value), key: u64) -> []Value {
+	items: Array(Value);
+	array_init(&items, 0);
+
+	e := multi_map_find_first(m, key);
+	for e != nil {
+		array_append(&items, e.value);
+		e = multi_map_find_next(m, e);
+	}
+
+	return array_slice(items);
+}
+
+
+multi_map_insert :: proc(m: ^$M/Map($Value), key: u64, value: Value) {
+	if array_len(m.hash) == 0 {
+		_map_grow(m);
+	}
+
+	i := _map_make(m, key);
+	array_get_ptr(m.entries, i).value = value;
+	if _map_full(m^) {
+		_map_grow(m);
+	}
+}
+
+multi_map_remove :: proc(m: ^$M/Map($Value), e: ^Map_Entry(Value)) {
+	fr := _map_find_entry(m, e);
+	if fr.entry_index >= 0 {
+		_map_erase(m, fr);
+	}
+}
+
+multi_map_remove_all :: proc(m: ^$M/Map($Value), key: u64) {
+	for map_exist(m^, key) {
+		map_remove(m, key);
+	}
+}
+
+
+/// Internal
+
+
+Map_Find_Result :: struct {
+	hash_index:  int,
+	entry_prev:  int,
+	entry_index: int,
+}
+
+_map_add_entry :: proc(m: ^$M/Map($Value), key: u64) -> int {
+	e: Map_Entry(Value);
+	e.key = key;
+	e.next = -1;
+	idx := array_len(m.entries);
+	array_push(&m.entries, e);
+	return idx;
+}
+
+_map_erase :: proc(m: ^$M/Map, fr: Map_Find_Result) {
+	if fr.entry_prev < 0 {
+		array_set(&m.hash, fr.hash_index, array_get(m.entries, fr.entry_index).next);
+	} else {
+		array_get_ptr(m.entries, fr.entry_prev).next = array_get(m.entries, fr.entry_index).next;
+	}
+
+	if fr.entry_index == array_len(m.entries)-1 {
+		array_pop_back(&m.entries);
+		return;
+	}
+
+	array_set(&m.entries, fr.entry_index, array_get(m.entries, array_len(m.entries)-1));
+	last := _map_find_key(m^, array_get(m.entries, fr.entry_index).key);
+
+	if last.entry_prev < 0 {
+		array_get_ptr(m.entries, last.entry_prev).next = fr.entry_index;
+	} else {
+		array_set(&m.hash, last.hash_index, fr.entry_index);
+	}
+}
+
+
+_map_find_key :: proc(m: $M/Map($Value), key: u64) -> Map_Find_Result {
+	fr: Map_Find_Result;
+	fr.hash_index = -1;
+	fr.entry_prev = -1;
+	fr.entry_index = -1;
+
+	if array_len(m.hash) == 0 {
+		return fr;
+	}
+
+	fr.hash_index = int(key % u64(array_len(m.hash)));
+	fr.entry_index = array_get(m.hash, fr.hash_index);
+	for fr.entry_index >= 0 {
+		it := array_get_ptr(m.entries, fr.entry_index);
+		if it.key == key {
+			return fr;
+		}
+		fr.entry_prev = fr.entry_index;
+		fr.entry_index = it.next;
+	}
+	return fr;
+}
+
+_map_find_entry :: proc(m: ^$M/Map($Value), e: ^Map_Entry(Value)) -> Map_Find_Result {
+	fr: Map_Find_Result;
+	fr.hash_index = -1;
+	fr.entry_prev = -1;
+	fr.entry_index = -1;
+
+	if array_len(m.hash) == 0 {
+		return fr;
+	}
+
+	fr.hash_index = int(e.key % u64(array_len(m.hash)));
+	fr.entry_index = array_get(m.hash, fr.hash_index);
+	for fr.entry_index >= 0 {
+		it := array_get_ptr(m.entries, fr.entry_index);
+		if it == e {
+			return fr;
+		}
+		fr.entry_prev = fr.entry_index;
+		fr.entry_index = it.next;
+	}
+	return fr;
+}
+
+_map_find_or_fail :: proc(m: $M/Map($Value), key: u64) -> int {
+	return _map_find_key(m, key).entry_index;
+}
+_map_find_or_make :: proc(m: ^$M/Map($Value), key: u64) -> int {
+	fr := _map_find_key(m^, key);
+	if fr.entry_index >= 0 {
+		return fr.entry_index;
+	}
+
+	i := _map_add_entry(m, key);
+	if fr.entry_prev < 0 {
+		array_set(&m.hash, fr.hash_index, i);
+	} else {
+		array_get_ptr(m.entries, fr.entry_prev).next = i;
+	}
+	return i;
+}
+
+
+_map_make :: proc(m: ^$M/Map($Value), key: u64) -> int {
+	fr := _map_find_key(m^, key);
+	i := _map_add_entry(m, key);
+
+	if fr.entry_prev < 0 {
+		array_set(&m.hash, fr.hash_index, i);
+	} else {
+		array_get_ptr(m.entries, fr.entry_prev).next = i;
+	}
+
+	array_get_ptr(m.entries, i).next = fr.entry_index;
+
+	return i;
+}
+
+
+_map_full :: proc(m: $M/Map($Value)) -> bool {
+	// TODO(bill): Determine good max load factor
+	return array_len(m.entries) >= (array_len(m.hash) / 4)*3;
+}
+
+_map_grow :: proc(m: ^$M/Map($Value)) {
+	new_size := array_len(m.entries) * 4 + 7; // TODO(bill): Determine good grow rate
+	map_reserve(m, new_size);
+}
+
+

+ 25 - 1
core/container/queue.odin

@@ -6,6 +6,31 @@ Queue :: struct(T: typeid) {
 	offset: int,
 }
 
+/*
+queue_init :: proc{
+	queue_init_none,
+	queue_init_len,
+	queue_init_len_cap,
+}
+queue_delete
+queue_clear
+queue_len
+queue_cap
+queue_space
+queue_get
+queue_set
+queue_reserve
+queue_resize
+queue_push :: proc{
+	queue_push_back, 
+	queue_push_elems,
+};
+queue_push_front
+queue_pop_front
+queue_pop_back
+queue_consume
+*/
+
 queue_init_none :: proc(q: ^$Q/Queue($T), allocator := context.allocator) {
 	queue_init_len(q, 0, allocator);
 }
@@ -40,7 +65,6 @@ queue_space :: proc(q: $Q/Queue($T)) -> int {
 	return array_len(q.data) - q.len;
 }
 
-
 queue_get :: proc(q: $Q/Queue($T), index: int) -> T {
 	i := (index + q.offset) % array_len(q.data);
 	data := array_slice(q.data);

+ 240 - 0
core/container/set.odin

@@ -0,0 +1,240 @@
+package container
+
+Set :: struct {
+	hash:    Array(int),
+	entries: Array(Set_Entry),
+}
+
+Set_Entry :: struct {
+	key:   u64,
+	next:  int,
+}
+
+
+/*
+set_init :: proc{
+	set_init_none,
+	set_init_cap,
+}
+set_delete
+
+set_in
+set_not_in
+set_add
+set_remove
+set_reserve
+set_clear
+*/
+
+set_init :: proc{set_init_none, set_init_cap};
+
+set_init_none :: proc(m: ^Set, allocator := context.allocator) {
+	m.hash.allocator = allocator;
+	m.entries.allocator = allocator;
+}
+
+set_init_cap :: proc(m: ^Set, cap: int, allocator := context.allocator) {
+	m.hash.allocator = allocator;
+	m.entries.allocator = allocator;
+	set_reserve(m, cap);
+}
+
+set_delete :: proc(m: Set) {
+	array_delete(m.hash);
+	array_delete(m.entries);
+}
+
+
+set_in :: proc(m: Set, key: u64) -> bool {
+	return _set_find_or_fail(m, key) >= 0;
+}
+set_not_in :: proc(m: Set, key: u64) -> bool {
+	return _set_find_or_fail(m, key) < 0;
+}
+
+set_add :: proc(m: ^Set, key: u64) {
+	if array_len(m.hash) == 0 {
+		_set_grow(m);
+	}
+
+	i := _set_find_or_make(m, key);
+	if _set_full(m^) {
+		_set_grow(m);
+	}
+}
+
+set_remove :: proc(m: ^Set, key: u64) {
+	fr := _set_find_key(m^, key);
+	if fr.entry_index >= 0 {
+		_set_erase(m, fr);
+	}
+}
+
+
+set_reserve :: proc(m: ^Set, new_size: int) {
+	nm: Set;
+	set_init(&nm, m.hash.allocator);
+	array_resize(&nm.hash, new_size);
+	array_reserve(&nm.entries, array_len(m.entries));
+
+	for i in 0..<new_size {
+		array_set(&nm.hash, i, -1);
+	}
+	for i in 0..<array_len(m.entries) {
+		e := array_get(m.entries, i);
+		set_add(&nm, e.key);
+	}
+
+	set_delete(m^);
+	m^ = nm;
+}
+
+set_clear :: proc(m: ^Set) {
+	array_clear(&m.hash);
+	array_clear(&m.entries);
+}
+
+
+set_equal :: proc(a, b: Set) -> bool {
+	a_entries := array_slice(a.entries);
+	b_entries := array_slice(b.entries);
+	if len(a_entries) != len(b_entries) {
+		return false;
+	}
+	for e in a_entries {
+		if set_not_in(b, e.key) {
+			return false;
+		}
+	}
+
+	return true;
+}
+
+
+
+/// Internal
+
+_set_add_entry :: proc(m: ^Set, key: u64) -> int {
+	e: Set_Entry;
+	e.key = key;
+	e.next = -1;
+	idx := array_len(m.entries);
+	array_push(&m.entries, e);
+	return idx;
+}
+
+_set_erase :: proc(m: ^Set, fr: Map_Find_Result) {
+	if fr.entry_prev < 0 {
+		array_set(&m.hash, fr.hash_index, array_get(m.entries, fr.entry_index).next);
+	} else {
+		array_get_ptr(m.entries, fr.entry_prev).next = array_get(m.entries, fr.entry_index).next;
+	}
+
+	if fr.entry_index == array_len(m.entries)-1 {
+		array_pop_back(&m.entries);
+		return;
+	}
+
+	array_set(&m.entries, fr.entry_index, array_get(m.entries, array_len(m.entries)-1));
+	last := _set_find_key(m^, array_get(m.entries, fr.entry_index).key);
+
+	if last.entry_prev < 0 {
+		array_get_ptr(m.entries, last.entry_prev).next = fr.entry_index;
+	} else {
+		array_set(&m.hash, last.hash_index, fr.entry_index);
+	}
+}
+
+
+_set_find_key :: proc(m: Set, key: u64) -> Map_Find_Result {
+	fr: Map_Find_Result;
+	fr.hash_index = -1;
+	fr.entry_prev = -1;
+	fr.entry_index = -1;
+
+	if array_len(m.hash) == 0 {
+		return fr;
+	}
+
+	fr.hash_index = int(key % u64(array_len(m.hash)));
+	fr.entry_index = array_get(m.hash, fr.hash_index);
+	for fr.entry_index >= 0 {
+		it := array_get_ptr(m.entries, fr.entry_index);
+		if it.key == key {
+			return fr;
+		}
+		fr.entry_prev = fr.entry_index;
+		fr.entry_index = it.next;
+	}
+	return fr;
+}
+
+_set_find_entry :: proc(m: ^Set, e: ^Set_Entry) -> Map_Find_Result {
+	fr: Map_Find_Result;
+	fr.hash_index = -1;
+	fr.entry_prev = -1;
+	fr.entry_index = -1;
+
+	if array_len(m.hash) == 0 {
+		return fr;
+	}
+
+	fr.hash_index = int(e.key % u64(array_len(m.hash)));
+	fr.entry_index = array_get(m.hash, fr.hash_index);
+	for fr.entry_index >= 0 {
+		it := array_get_ptr(m.entries, fr.entry_index);
+		if it == e {
+			return fr;
+		}
+		fr.entry_prev = fr.entry_index;
+		fr.entry_index = it.next;
+	}
+	return fr;
+}
+
+_set_find_or_fail :: proc(m: Set, key: u64) -> int {
+	return _set_find_key(m, key).entry_index;
+}
+_set_find_or_make :: proc(m: ^Set, key: u64) -> int {
+	fr := _set_find_key(m^, key);
+	if fr.entry_index >= 0 {
+		return fr.entry_index;
+	}
+
+	i := _set_add_entry(m, key);
+	if fr.entry_prev < 0 {
+		array_set(&m.hash, fr.hash_index, i);
+	} else {
+		array_get_ptr(m.entries, fr.entry_prev).next = i;
+	}
+	return i;
+}
+
+
+_set_make :: proc(m: ^Set, key: u64) -> int {
+	fr := _set_find_key(m^, key);
+	i := _set_add_entry(m, key);
+
+	if fr.entry_prev < 0 {
+		array_set(&m.hash, fr.hash_index, i);
+	} else {
+		array_get_ptr(m.entries, fr.entry_prev).next = i;
+	}
+
+	array_get_ptr(m.entries, i).next = fr.entry_index;
+
+	return i;
+}
+
+
+_set_full :: proc(m: Set) -> bool {
+	// TODO(bill): Determine good max load factor
+	return array_len(m.entries) >= (array_len(m.hash) / 4)*3;
+}
+
+_set_grow :: proc(m: ^Set) {
+	new_size := array_len(m.entries) * 4 + 7; // TODO(bill): Determine good grow rate
+	set_reserve(m, new_size);
+}
+
+

+ 97 - 0
core/container/small_array.odin

@@ -0,0 +1,97 @@
+package container
+
+import "core:mem"
+
+Small_Array :: struct(N: int, T: typeid) where N >= 0 {
+	data: [N]T,
+	len:  int,
+}
+
+
+small_array_len :: proc(a: $A/Small_Array) -> int {
+	return a.len;
+}
+
+small_array_cap :: proc(a: $A/Small_Array) -> int {
+	return len(a.data);
+}
+
+small_array_space :: proc(a: $A/Small_Array) -> int {
+	return len(a.data) - a.len;
+}
+
+small_array_slice :: proc(a: ^$A/Small_Array($N, $T)) -> []T {
+	return a.data[:a.len];
+}
+
+
+small_array_get :: proc(a: $A/Small_Array($N, $T), index: int, loc := #caller_location) -> T {
+	return a.data[index];
+}
+small_array_get_ptr :: proc(a: $A/Small_Array($N, $T), index: int, loc := #caller_location) -> ^T {
+	return &a.data[index];
+}
+
+small_array_set :: proc(a: ^$A/Small_Array($N, $T), index: int, item: T, loc := #caller_location) {
+	a.data[index] = item;
+}
+
+small_array_resize :: proc(a: ^$A/Small_Array, length: int) {
+	a.len = min(length, len(a.data));
+}
+
+
+small_array_push_back :: proc(a: ^$A/Small_Array($N, $T), item: T) -> bool {
+	if a.len < len(a.data) {
+		a.len += 1;
+		a.data[a.len-1] = item;
+		return true;
+	}
+	return false;
+}
+
+small_array_push_front :: proc(a: ^$A/Small_Array($N, $T), item: T) -> bool {
+	if a.len < len(a.data) {
+		a.len += 1;
+		data := small_array_slice(a);
+		copy(data[1:], data[:]);
+		data[0] = item;
+		return true;
+	}
+	return false;
+}
+
+small_array_pop_back :: proc(a: ^$A/Small_Array($N, $T), loc := #caller_location) -> T {
+	assert(condition=a.len > 0, loc=loc);
+	item := a.data[a.len-1];
+	a.len -= 1;
+	return item;
+}
+
+small_array_pop_font :: proc(a: ^$A/Small_Array($N, $T), loc := #caller_location) -> T {
+	assert(condition=a.len > 0, loc=loc);
+	item := a.data[0];
+	s := small_array_slice(a);
+	copy(s[:], s[1:]);
+	a.len -= 1;
+	return item;
+}
+
+
+small_array_consume :: proc(a: ^$A/Small_Array($N, $T), count: int, loc := #caller_location) {
+	assert(condition=a.len >= count, loc=loc);
+	a.len -= count;
+}
+
+small_array_clear :: proc(a: ^$A/Small_Array($N, $T)) {
+	small_array_resize(a, 0);
+}
+
+small_array_push_back_elems :: proc(a: ^$A/Small_Array($N, $T), items: ..T) {
+	n := copy(a.data[a.len:], items[:]);
+	a.len += n;
+}
+
+small_array_push   :: proc{small_array_push_back, small_array_push_back_elems};
+small_array_append :: proc{small_array_push_back, small_array_push_back_elems};
+

+ 11 - 4
core/mem/mem.odin

@@ -107,8 +107,12 @@ ptr_sub :: inline proc "contextless" (a, b: $P/^$T) -> int {
 
 slice_ptr :: inline proc "contextless" (ptr: ^$T, len: int) -> []T {
 	assert(len >= 0);
-	slice := Raw_Slice{data = ptr, len = len};
-	return transmute([]T)slice;
+	return transmute([]T)Raw_Slice{data = ptr, len = len};
+}
+
+slice_ptr_to_bytes :: proc "contextless" (ptr: rawptr, len: int) -> []byte {
+	assert(len >= 0);
+	return transmute([]byte)Raw_Slice{data = ptr, len = len};
 }
 
 slice_to_bytes :: inline proc "contextless" (slice: $E/[]$T) -> []byte {
@@ -127,16 +131,19 @@ slice_data_cast :: inline proc "contextless" ($T: typeid/[]$A, slice: $S/[]$B) -
 	}
 }
 
+slice_to_components :: proc "contextless" (slice: $E/[]$T) -> (data: ^T, len: int) {
+	s := transmute(Raw_Slice)slice;
+	return s.data, s.len;
+}
 
 buffer_from_slice :: inline proc(backing: $T/[]$E) -> [dynamic]E {
 	s := transmute(Raw_Slice)backing;
-	d := Raw_Dynamic_Array{
+	return transmute([dynamic]E)Raw_Dynamic_Array{
 		data      = s.data,
 		len       = 0,
 		cap       = s.len,
 		allocator = nil_allocator(),
 	};
-	return transmute([dynamic]E)d;
 }
 
 ptr_to_bytes :: inline proc "contextless" (ptr: ^$T, len := 1) -> []byte {

+ 6 - 2
core/runtime/internal.odin

@@ -235,8 +235,12 @@ print_caller_location :: proc(fd: os.Handle, using loc: Source_Code_Location) {
 	os.write_byte(fd, ')');
 }
 print_typeid :: proc(fd: os.Handle, id: typeid) {
-	ti := type_info_of(id);
-	print_type(fd, ti);
+	if id == nil {
+		os.write_string(fd, "nil");
+	} else {
+		ti := type_info_of(id);
+		print_type(fd, ti);
+	}
 }
 print_type :: proc(fd: os.Handle, ti: ^Type_Info) {
 	if ti == nil {

+ 5 - 4
core/sync/sync.odin

@@ -1,8 +1,9 @@
 package sync
 
-foreign {
-	@(link_name="llvm.x86.sse2.pause")
-	yield_processor :: proc() ---
+import "core:intrinsics"
+
+cpu_relax :: inline proc() {
+	intrinsics.cpu_relax();
 }
 
 Ticket_Mutex :: struct {
@@ -18,7 +19,7 @@ ticket_mutex_init :: proc(m: ^Ticket_Mutex) {
 ticket_mutex_lock :: inline proc(m: ^Ticket_Mutex) {
 	ticket := atomic_add(&m.ticket, 1, .Relaxed);
 	for ticket != atomic_load(&m.serving, .Acquire) {
-		yield_processor();
+		intrinsics.cpu_relax();
 	}
 }
 

+ 2 - 10
core/thread/thread_unix.odin

@@ -2,6 +2,7 @@
 package thread;
 
 import "core:runtime"
+import "core:intrinsics"
 import "core:sync"
 import "core:sys/unix"
 
@@ -40,15 +41,6 @@ Thread_Priority :: enum {
 // Creates a thread which will run the given procedure.
 // It then waits for `start` to be called.
 //
-// You may provide a slice of bytes to use as the stack for the new thread,
-// but if you do, you are expected to set up the guard pages yourself.
-//
-// The stack must also be aligned appropriately for the platform.
-// We require it's at least 16 bytes aligned to help robustness; other
-// platforms may require page-size alignment.
-// Note also that pthreads requires the stack is at least 6 OS pages in size:
-// 4 are required by pthreads, and two extra for guards pages that will be applied.
-//
 create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread {
 	__linux_thread_entry_proc :: proc "c" (t: rawptr) -> rawptr {
 		t := (^Thread)(t);
@@ -134,7 +126,7 @@ join :: proc(t: ^Thread) {
 	if sync.atomic_swap(&t.already_joined, true, .Sequentially_Consistent) {
 		for {
 			if sync.atomic_load(&t.done, .Sequentially_Consistent) do return;
-			sync.yield_processor();
+			intrinsics.cpu_relax();
 		}
 	}
 

+ 61 - 13
src/check_expr.cpp

@@ -5750,19 +5750,45 @@ bool check_assignment_arguments(CheckerContext *ctx, Array<Operand> const &lhs,
 
 				optional_ok = true;
 				tuple_index += 2;
+			} else if (o.mode == Addressing_OptionalOk && is_type_tuple(o.type)) {
+				Type *tuple = o.type;
+				GB_ASSERT(tuple->Tuple.variables.count == 2);
+				Ast *expr = unparen_expr(o.expr);
+				if (expr->kind == Ast_CallExpr) {
+					expr->CallExpr.optional_ok_one = true;
+				}
+				Operand val = o;
+				val.type = tuple->Tuple.variables[0]->type;
+				val.mode = Addressing_Value;
+				array_add(operands, val);
+				tuple_index += tuple->Tuple.variables.count;
 			} else {
 				array_add(operands, o);
 				tuple_index += 1;
 			}
 		} else {
 			TypeTuple *tuple = &o.type->Tuple;
-			for_array(j, tuple->variables) {
-				o.type = tuple->variables[j]->type;
-				array_add(operands, o);
-			}
+			if (o.mode == Addressing_OptionalOk  && is_type_tuple(o.type) && lhs.count == 1) {
+				GB_ASSERT(tuple->variables.count == 2);
+				Ast *expr = unparen_expr(o.expr);
+				if (expr->kind == Ast_CallExpr) {
+					expr->CallExpr.optional_ok_one = true;
+				}
+				Operand val = o;
+				val.type = tuple->variables[0]->type;
+				val.mode = Addressing_Value;
+				array_add(operands, val);
+				tuple_index += tuple->variables.count;
 
-			isize count = tuple->variables.count;
-			tuple_index += 2;
+				add_type_and_value(c->info, val.expr, val.mode, val.type, val.value);
+			} else {
+				for_array(j, tuple->variables) {
+					o.type = tuple->variables[j]->type;
+					array_add(operands, o);
+				}
+
+				tuple_index += tuple->variables.count;
+			}
 		}
 	}
 
@@ -5839,13 +5865,30 @@ bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize lhs_count,
 			}
 		} else {
 			TypeTuple *tuple = &o.type->Tuple;
-			for_array(j, tuple->variables) {
-				o.type = tuple->variables[j]->type;
-				array_add(operands, o);
-			}
+			if (o.mode == Addressing_OptionalOk && lhs_count == 1) {
+				GB_ASSERT(tuple->variables.count == 2);
+				Ast *expr = unparen_expr(o.expr);
+				if (expr->kind == Ast_CallExpr) {
+					expr->CallExpr.optional_ok_one = true;
+				}
+				Operand val = o;
+				val.type = tuple->variables[0]->type;
+				val.mode = Addressing_Value;
+				array_add(operands, val);
+
+				isize count = tuple->variables.count;
+				tuple_index += add_dependencies_from_unpacking(c, lhs, lhs_count, tuple_index, count);
+				
+				add_type_and_value(c->info, val.expr, val.mode, val.type, val.value);
+			} else {
+				for_array(j, tuple->variables) {
+					o.type = tuple->variables[j]->type;
+					array_add(operands, o);
+				}
 
-			isize count = tuple->variables.count;
-			tuple_index += add_dependencies_from_unpacking(c, lhs, lhs_count, tuple_index, count);
+				isize count = tuple->variables.count;
+				tuple_index += add_dependencies_from_unpacking(c, lhs, lhs_count, tuple_index, count);
+			}
 		}
 	}
 
@@ -7332,7 +7375,7 @@ ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Type *t
 					if (pl->inlining == ProcInlining_no_inline) {
 						error(call, "'inline' cannot be applied to a procedure that has be marked as 'no_inline'");
 					}
-			}
+				}
 			}
 			break;
 		}
@@ -7342,6 +7385,11 @@ ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Type *t
 	}
 
 	operand->expr = call;
+
+	if (pt->kind == Type_Proc && pt->Proc.optional_ok) {
+		operand->mode = Addressing_OptionalOk;
+	}
+
 	return Expr_Expr;
 }
 

+ 17 - 0
src/check_type.cpp

@@ -2530,6 +2530,22 @@ bool check_procedure_type(CheckerContext *ctx, Type *type, Ast *proc_type_node,
 	}
 	GB_ASSERT(cc > 0);
 
+	bool optional_ok = (pt->tags & ProcTag_optional_ok) != 0;
+	if (optional_ok) {
+		if (result_count != 2) {
+			error(proc_type_node, "A procedure type with the #optional_ok tag requires 2 return values, got %td", result_count);
+		} else {
+			Entity *second = results->Tuple.variables[1];
+			if (is_type_polymorphic(second->type)) {
+				// ignore
+			} else if (is_type_boolean(second->type)) {
+				// GOOD
+			} else {
+				error(second->token, "Second return value of an #optional_ok procedure must be a boolean, got %s", type_to_string(second->type));
+			}
+		}
+	}
+
 	type->Proc.node                 = proc_type_node;
 	type->Proc.scope                = c->scope;
 	type->Proc.params               = params;
@@ -2542,6 +2558,7 @@ bool check_procedure_type(CheckerContext *ctx, Type *type, Ast *proc_type_node,
 	type->Proc.is_polymorphic       = pt->generic;
 	type->Proc.specialization_count = specialization_count;
 	type->Proc.diverging            = pt->diverging;
+	type->Proc.optional_ok          = optional_ok;
 	type->Proc.tags                 = pt->tags;
 
 	if (param_count > 0) {

+ 1 - 1
src/common.cpp

@@ -468,8 +468,8 @@ GB_ALLOCATOR_PROC(arena_allocator_proc) {
 
 
 struct StringIntern {
-	isize len;
 	StringIntern *next;
+	isize len;
 	char str[1];
 };
 

+ 311 - 297
src/ir.cpp

@@ -7081,6 +7081,311 @@ irValue *ir_build_expr(irProcedure *proc, Ast *expr) {
 	return v;
 }
 
+
+irValue *ir_build_call_expr(irProcedure *proc, Ast *expr) {
+	ast_node(ce, CallExpr, expr);
+	TypeAndValue tv = type_and_value_of_expr(expr);
+	TypeAndValue proc_tv = type_and_value_of_expr(ce->proc);
+	AddressingMode proc_mode = proc_tv.mode;
+	if (proc_mode == Addressing_Type) {
+		GB_ASSERT(ce->args.count == 1);
+		irValue *x = ir_build_expr(proc, ce->args[0]);
+		irValue *y = ir_emit_conv(proc, x, tv.type);
+		return y;
+	}
+
+	Ast *p = unparen_expr(ce->proc);
+	if (proc_mode == Addressing_Builtin) {
+		Entity *e = entity_of_node(p);
+		BuiltinProcId id = BuiltinProc_Invalid;
+		if (e != nullptr) {
+			id = cast(BuiltinProcId)e->Builtin.id;
+		} else {
+			id = BuiltinProc_DIRECTIVE;
+		}
+		return ir_build_builtin_proc(proc, expr, tv, id);
+	}
+
+	// NOTE(bill): Regular call
+	irValue *value = nullptr;
+	Ast *proc_expr = unparen_expr(ce->proc);
+	if (proc_expr->tav.mode == Addressing_Constant) {
+		ExactValue v = proc_expr->tav.value;
+		switch (v.kind) {
+		case ExactValue_Integer:
+			{
+				u64 u = big_int_to_u64(&v.value_integer);
+				irValue *x = ir_const_uintptr(u);
+				x = ir_emit_conv(proc, x, t_rawptr);
+				value = ir_emit_conv(proc, x, proc_expr->tav.type);
+				break;
+			}
+		case ExactValue_Pointer:
+			{
+				u64 u = cast(u64)v.value_pointer;
+				irValue *x = ir_const_uintptr(u);
+				x = ir_emit_conv(proc, x, t_rawptr);
+				value = ir_emit_conv(proc, x, proc_expr->tav.type);
+				break;
+			}
+		}
+	}
+
+	if (value == nullptr) {
+		value = ir_build_expr(proc, proc_expr);
+	}
+
+	GB_ASSERT(value != nullptr);
+	Type *proc_type_ = base_type(ir_type(value));
+	GB_ASSERT(proc_type_->kind == Type_Proc);
+	TypeProc *pt = &proc_type_->Proc;
+	set_procedure_abi_types(heap_allocator(), proc_type_);
+
+	if (is_call_expr_field_value(ce)) {
+		auto args = array_make<irValue *>(ir_allocator(), pt->param_count);
+
+		for_array(arg_index, ce->args) {
+			Ast *arg = ce->args[arg_index];
+			ast_node(fv, FieldValue, arg);
+			GB_ASSERT(fv->field->kind == Ast_Ident);
+			String name = fv->field->Ident.token.string;
+			isize index = lookup_procedure_parameter(pt, name);
+			GB_ASSERT(index >= 0);
+			TypeAndValue tav = type_and_value_of_expr(fv->value);
+			if (tav.mode == Addressing_Type) {
+				args[index] = ir_value_nil(tav.type);
+			} else {
+				args[index] = ir_build_expr(proc, fv->value);
+			}
+		}
+		TypeTuple *params = &pt->params->Tuple;
+		for (isize i = 0; i < args.count; i++) {
+			Entity *e = params->variables[i];
+			if (e->kind == Entity_TypeName) {
+				args[i] = ir_value_nil(e->type);
+			} else if (e->kind == Entity_Constant) {
+				continue;
+			} else {
+				GB_ASSERT(e->kind == Entity_Variable);
+				if (args[i] == nullptr) {
+					switch (e->Variable.param_value.kind) {
+					case ParameterValue_Constant:
+						args[i] = ir_value_constant(e->type, e->Variable.param_value.value);
+						break;
+					case ParameterValue_Nil:
+						args[i] = ir_value_nil(e->type);
+						break;
+					case ParameterValue_Location:
+						args[i] = ir_emit_source_code_location(proc, proc->entity->token.string, ast_token(expr).pos);
+						break;
+					case ParameterValue_Value:
+						args[i] = ir_build_expr(proc, e->Variable.param_value.ast_value);
+						break;
+					}
+				} else {
+					args[i] = ir_emit_conv(proc, args[i], e->type);
+				}
+			}
+		}
+
+		return ir_emit_call(proc, value, args, ce->inlining, proc->return_ptr_hint_ast == expr);
+	}
+
+	isize arg_index = 0;
+
+	isize arg_count = 0;
+	for_array(i, ce->args) {
+		Ast *arg = ce->args[i];
+		TypeAndValue tav = type_and_value_of_expr(arg);
+		GB_ASSERT_MSG(tav.mode != Addressing_Invalid, "%s %s", expr_to_string(arg), expr_to_string(expr));
+		GB_ASSERT_MSG(tav.mode != Addressing_ProcGroup, "%s", expr_to_string(arg));
+		Type *at = tav.type;
+		if (at->kind == Type_Tuple) {
+			arg_count += at->Tuple.variables.count;
+		} else {
+			arg_count++;
+		}
+	}
+
+	isize param_count = 0;
+	if (pt->params) {
+		GB_ASSERT(pt->params->kind == Type_Tuple);
+		param_count = pt->params->Tuple.variables.count;
+	}
+
+	auto args = array_make<irValue *>(ir_allocator(), cast(isize)gb_max(param_count, arg_count));
+	isize variadic_index = pt->variadic_index;
+	bool variadic = pt->variadic && variadic_index >= 0;
+	bool vari_expand = ce->ellipsis.pos.line != 0;
+	bool is_c_vararg = pt->c_vararg;
+
+	String proc_name = {};
+	if (proc->entity != nullptr) {
+		proc_name = proc->entity->token.string;
+	}
+	TokenPos pos = ast_token(ce->proc).pos;
+
+	TypeTuple *param_tuple = nullptr;
+	if (pt->params) {
+		GB_ASSERT(pt->params->kind == Type_Tuple);
+		param_tuple = &pt->params->Tuple;
+	}
+
+	for_array(i, ce->args) {
+		Ast *arg = ce->args[i];
+		TypeAndValue arg_tv = type_and_value_of_expr(arg);
+		if (arg_tv.mode == Addressing_Type) {
+			args[arg_index++] = ir_value_nil(arg_tv.type);
+		} else {
+			irValue *a = ir_build_expr(proc, arg);
+			Type *at = ir_type(a);
+			if (at->kind == Type_Tuple) {
+				for_array(i, at->Tuple.variables) {
+					Entity *e = at->Tuple.variables[i];
+					irValue *v = ir_emit_struct_ev(proc, a, cast(i32)i);
+					args[arg_index++] = v;
+				}
+			} else {
+				args[arg_index++] = a;
+			}
+		}
+	}
+
+
+	if (param_count > 0) {
+		GB_ASSERT_MSG(pt->params != nullptr, "%s %td", expr_to_string(expr), pt->param_count);
+		GB_ASSERT(param_count < 1000000);
+
+		if (arg_count < param_count) {
+			isize end = cast(isize)param_count;
+			if (variadic) {
+				end = variadic_index;
+			}
+			while (arg_index < end) {
+				Entity *e = param_tuple->variables[arg_index];
+				GB_ASSERT(e->kind == Entity_Variable);
+
+				switch (e->Variable.param_value.kind) {
+				case ParameterValue_Constant:
+					args[arg_index++] = ir_value_constant(e->type, e->Variable.param_value.value);
+					break;
+				case ParameterValue_Nil:
+					args[arg_index++] = ir_value_nil(e->type);
+					break;
+				case ParameterValue_Location:
+					args[arg_index++] = ir_emit_source_code_location(proc, proc_name, pos);
+					break;
+				case ParameterValue_Value:
+					args[arg_index++] = ir_build_expr(proc, e->Variable.param_value.ast_value);
+					break;
+				}
+			}
+		}
+
+		if (is_c_vararg) {
+			GB_ASSERT(variadic);
+			GB_ASSERT(!vari_expand);
+			isize i = 0;
+			for (; i < variadic_index; i++) {
+				Entity *e = param_tuple->variables[i];
+				if (e->kind == Entity_Variable) {
+					args[i] = ir_emit_conv(proc, args[i], e->type);
+				}
+			}
+			Type *variadic_type = param_tuple->variables[i]->type;
+			GB_ASSERT(is_type_slice(variadic_type));
+			variadic_type = base_type(variadic_type)->Slice.elem;
+			if (!is_type_any(variadic_type)) {
+				for (; i < arg_count; i++) {
+					args[i] = ir_emit_conv(proc, args[i], variadic_type);
+				}
+			} else {
+				for (; i < arg_count; i++) {
+					args[i] = ir_emit_conv(proc, args[i], default_type(ir_type(args[i])));
+				}
+			}
+		} else if (variadic) {
+			isize i = 0;
+			for (; i < variadic_index; i++) {
+				Entity *e = param_tuple->variables[i];
+				if (e->kind == Entity_Variable) {
+					args[i] = ir_emit_conv(proc, args[i], e->type);
+				}
+			}
+			if (!vari_expand) {
+				Type *variadic_type = param_tuple->variables[i]->type;
+				GB_ASSERT(is_type_slice(variadic_type));
+				variadic_type = base_type(variadic_type)->Slice.elem;
+				for (; i < arg_count; i++) {
+					args[i] = ir_emit_conv(proc, args[i], variadic_type);
+				}
+			}
+		} else {
+			for (isize i = 0; i < param_count; i++) {
+				Entity *e = param_tuple->variables[i];
+				if (e->kind == Entity_Variable) {
+					GB_ASSERT(args[i] != nullptr);
+					args[i] = ir_emit_conv(proc, args[i], e->type);
+				}
+			}
+		}
+
+		if (variadic && !vari_expand && !is_c_vararg) {
+			ir_emit_comment(proc, str_lit("variadic call argument generation"));
+			gbAllocator allocator = ir_allocator();
+			Type *slice_type = param_tuple->variables[variadic_index]->type;
+			Type *elem_type  = base_type(slice_type)->Slice.elem;
+			irValue *slice = ir_add_local_generated(proc, slice_type, true);
+			isize slice_len = arg_count+1 - (variadic_index+1);
+
+			if (slice_len > 0) {
+				irValue *base_array = ir_add_local_generated(proc, alloc_type_array(elem_type, slice_len), true);
+
+				for (isize i = variadic_index, j = 0; i < arg_count; i++, j++) {
+					irValue *addr = ir_emit_array_epi(proc, base_array, cast(i32)j);
+					ir_emit_store(proc, addr, args[i]);
+				}
+
+				irValue *base_elem = ir_emit_array_epi(proc, base_array, 0);
+				irValue *len = ir_const_int(slice_len);
+				ir_fill_slice(proc, slice, base_elem, len);
+			}
+
+			arg_count = param_count;
+			args[variadic_index] = ir_emit_load(proc, slice);
+		}
+	}
+
+	if (variadic && variadic_index+1 < param_count) {
+		for (isize i = variadic_index+1; i < param_count; i++) {
+			Entity *e = param_tuple->variables[i];
+			switch (e->Variable.param_value.kind) {
+			case ParameterValue_Constant:
+				args[i] = ir_value_constant(e->type, e->Variable.param_value.value);
+				break;
+			case ParameterValue_Nil:
+				args[i] = ir_value_nil(e->type);
+				break;
+			case ParameterValue_Location:
+				args[i] = ir_emit_source_code_location(proc, proc_name, pos);
+				break;
+			case ParameterValue_Value:
+				args[i] = ir_build_expr(proc, e->Variable.param_value.ast_value);
+				break;
+			}
+		}
+	}
+
+	isize final_count = param_count;
+	if (is_c_vararg) {
+		final_count = arg_count;
+	}
+
+	auto call_args = array_slice(args, 0, final_count);
+	return ir_emit_call(proc, value, call_args, ce->inlining, proc->return_ptr_hint_ast == expr);
+}
+
+
 irValue *ir_build_expr_internal(irProcedure *proc, Ast *expr) {
 	Ast *original_expr = expr;
 	expr = unparen_expr(expr);
@@ -7551,304 +7856,13 @@ irValue *ir_build_expr_internal(irProcedure *proc, Ast *expr) {
 
 
 	case_ast_node(ce, CallExpr, expr);
-		TypeAndValue proc_tv = type_and_value_of_expr(ce->proc);
-		AddressingMode proc_mode = proc_tv.mode;
-		if (proc_mode == Addressing_Type) {
-			GB_ASSERT(ce->args.count == 1);
-			irValue *x = ir_build_expr(proc, ce->args[0]);
-			irValue *y = ir_emit_conv(proc, x, tv.type);
-			return y;
-		}
-
-		Ast *p = unparen_expr(ce->proc);
-		if (proc_mode == Addressing_Builtin) {
-			Entity *e = entity_of_node(p);
-			BuiltinProcId id = BuiltinProc_Invalid;
-			if (e != nullptr) {
-				id = cast(BuiltinProcId)e->Builtin.id;
-			} else {
-				id = BuiltinProc_DIRECTIVE;
-			}
-			return ir_build_builtin_proc(proc, expr, tv, id);
-		}
-
-		// NOTE(bill): Regular call
-		irValue *value = nullptr;
-		Ast *proc_expr = unparen_expr(ce->proc);
-		if (proc_expr->tav.mode == Addressing_Constant) {
-			ExactValue v = proc_expr->tav.value;
-			switch (v.kind) {
-			case ExactValue_Integer:
-				{
-					u64 u = big_int_to_u64(&v.value_integer);
-					irValue *x = ir_const_uintptr(u);
-					x = ir_emit_conv(proc, x, t_rawptr);
-					value = ir_emit_conv(proc, x, proc_expr->tav.type);
-					break;
-				}
-			case ExactValue_Pointer:
-				{
-					u64 u = cast(u64)v.value_pointer;
-					irValue *x = ir_const_uintptr(u);
-					x = ir_emit_conv(proc, x, t_rawptr);
-					value = ir_emit_conv(proc, x, proc_expr->tav.type);
-					break;
-				}
-			}
-		}
-
-		if (value == nullptr) {
-			value = ir_build_expr(proc, proc_expr);
-		}
-
-		GB_ASSERT(value != nullptr);
-		Type *proc_type_ = base_type(ir_type(value));
-		GB_ASSERT(proc_type_->kind == Type_Proc);
-		TypeProc *pt = &proc_type_->Proc;
-		set_procedure_abi_types(heap_allocator(), proc_type_);
-
-		if (is_call_expr_field_value(ce)) {
-			auto args = array_make<irValue *>(ir_allocator(), pt->param_count);
-
-			for_array(arg_index, ce->args) {
-				Ast *arg = ce->args[arg_index];
-				ast_node(fv, FieldValue, arg);
-				GB_ASSERT(fv->field->kind == Ast_Ident);
-				String name = fv->field->Ident.token.string;
-				isize index = lookup_procedure_parameter(pt, name);
-				GB_ASSERT(index >= 0);
-				TypeAndValue tav = type_and_value_of_expr(fv->value);
-				if (tav.mode == Addressing_Type) {
-					args[index] = ir_value_nil(tav.type);
-				} else {
-					args[index] = ir_build_expr(proc, fv->value);
-				}
-			}
-			TypeTuple *params = &pt->params->Tuple;
-			for (isize i = 0; i < args.count; i++) {
-				Entity *e = params->variables[i];
-				if (e->kind == Entity_TypeName) {
-					args[i] = ir_value_nil(e->type);
-				} else if (e->kind == Entity_Constant) {
-					continue;
-				} else {
-					GB_ASSERT(e->kind == Entity_Variable);
-					if (args[i] == nullptr) {
-						switch (e->Variable.param_value.kind) {
-						case ParameterValue_Constant:
-							args[i] = ir_value_constant(e->type, e->Variable.param_value.value);
-							break;
-						case ParameterValue_Nil:
-							args[i] = ir_value_nil(e->type);
-							break;
-						case ParameterValue_Location:
-							args[i] = ir_emit_source_code_location(proc, proc->entity->token.string, ast_token(expr).pos);
-							break;
-						case ParameterValue_Value:
-							args[i] = ir_build_expr(proc, e->Variable.param_value.ast_value);
-							break;
-						}
-					} else {
-						args[i] = ir_emit_conv(proc, args[i], e->type);
-					}
-				}
-			}
-
-			return ir_emit_call(proc, value, args, ce->inlining, proc->return_ptr_hint_ast == expr);
-		}
-
-		isize arg_index = 0;
-
-		isize arg_count = 0;
-		for_array(i, ce->args) {
-			Ast *arg = ce->args[i];
-			TypeAndValue tav = type_and_value_of_expr(arg);
-			GB_ASSERT_MSG(tav.mode != Addressing_Invalid, "%s %s", expr_to_string(arg), expr_to_string(expr));
-			GB_ASSERT_MSG(tav.mode != Addressing_ProcGroup, "%s", expr_to_string(arg));
-			Type *at = tav.type;
-			if (at->kind == Type_Tuple) {
-				arg_count += at->Tuple.variables.count;
-			} else {
-				arg_count++;
-			}
-		}
-
-		isize param_count = 0;
-		if (pt->params) {
-			GB_ASSERT(pt->params->kind == Type_Tuple);
-			param_count = pt->params->Tuple.variables.count;
-		}
-
-		auto args = array_make<irValue *>(ir_allocator(), cast(isize)gb_max(param_count, arg_count));
-		isize variadic_index = pt->variadic_index;
-		bool variadic = pt->variadic && variadic_index >= 0;
-		bool vari_expand = ce->ellipsis.pos.line != 0;
-		bool is_c_vararg = pt->c_vararg;
-
-		String proc_name = {};
-		if (proc->entity != nullptr) {
-			proc_name = proc->entity->token.string;
-		}
-		TokenPos pos = ast_token(ce->proc).pos;
-
-		TypeTuple *param_tuple = nullptr;
-		if (pt->params) {
-			GB_ASSERT(pt->params->kind == Type_Tuple);
-			param_tuple = &pt->params->Tuple;
-		}
-
-		for_array(i, ce->args) {
-			Ast *arg = ce->args[i];
-			TypeAndValue arg_tv = type_and_value_of_expr(arg);
-			if (arg_tv.mode == Addressing_Type) {
-				args[arg_index++] = ir_value_nil(arg_tv.type);
-			} else {
-				irValue *a = ir_build_expr(proc, arg);
-				Type *at = ir_type(a);
-				if (at->kind == Type_Tuple) {
-					for_array(i, at->Tuple.variables) {
-						Entity *e = at->Tuple.variables[i];
-						irValue *v = ir_emit_struct_ev(proc, a, cast(i32)i);
-						args[arg_index++] = v;
-					}
-				} else {
-					args[arg_index++] = a;
-				}
-			}
+		irValue *res = ir_build_call_expr(proc, expr);
+		if (ce->optional_ok_one) { // TODO(bill): Minor hack for #optional_ok procedures
+			GB_ASSERT(is_type_tuple(ir_type(res)));
+			GB_ASSERT(ir_type(res)->Tuple.variables.count == 2);
+			return ir_emit_struct_ev(proc, res, 0);
 		}
-
-
-		if (param_count > 0) {
-			GB_ASSERT_MSG(pt->params != nullptr, "%s %td", expr_to_string(expr), pt->param_count);
-			GB_ASSERT(param_count < 1000000);
-
-			if (arg_count < param_count) {
-				isize end = cast(isize)param_count;
-				if (variadic) {
-					end = variadic_index;
-				}
-				while (arg_index < end) {
-					Entity *e = param_tuple->variables[arg_index];
-					GB_ASSERT(e->kind == Entity_Variable);
-
-					switch (e->Variable.param_value.kind) {
-					case ParameterValue_Constant:
-						args[arg_index++] = ir_value_constant(e->type, e->Variable.param_value.value);
-						break;
-					case ParameterValue_Nil:
-						args[arg_index++] = ir_value_nil(e->type);
-						break;
-					case ParameterValue_Location:
-						args[arg_index++] = ir_emit_source_code_location(proc, proc_name, pos);
-						break;
-					case ParameterValue_Value:
-						args[arg_index++] = ir_build_expr(proc, e->Variable.param_value.ast_value);
-						break;
-					}
-				}
-			}
-
-			if (is_c_vararg) {
-				GB_ASSERT(variadic);
-				GB_ASSERT(!vari_expand);
-				isize i = 0;
-				for (; i < variadic_index; i++) {
-					Entity *e = param_tuple->variables[i];
-					if (e->kind == Entity_Variable) {
-						args[i] = ir_emit_conv(proc, args[i], e->type);
-					}
-				}
-				Type *variadic_type = param_tuple->variables[i]->type;
-				GB_ASSERT(is_type_slice(variadic_type));
-				variadic_type = base_type(variadic_type)->Slice.elem;
-				if (!is_type_any(variadic_type)) {
-					for (; i < arg_count; i++) {
-						args[i] = ir_emit_conv(proc, args[i], variadic_type);
-					}
-				} else {
-					for (; i < arg_count; i++) {
-						args[i] = ir_emit_conv(proc, args[i], default_type(ir_type(args[i])));
-					}
-				}
-			} else if (variadic) {
-				isize i = 0;
-				for (; i < variadic_index; i++) {
-					Entity *e = param_tuple->variables[i];
-					if (e->kind == Entity_Variable) {
-						args[i] = ir_emit_conv(proc, args[i], e->type);
-					}
-				}
-				if (!vari_expand) {
-					Type *variadic_type = param_tuple->variables[i]->type;
-					GB_ASSERT(is_type_slice(variadic_type));
-					variadic_type = base_type(variadic_type)->Slice.elem;
-					for (; i < arg_count; i++) {
-						args[i] = ir_emit_conv(proc, args[i], variadic_type);
-					}
-				}
-			} else {
-				for (isize i = 0; i < param_count; i++) {
-					Entity *e = param_tuple->variables[i];
-					if (e->kind == Entity_Variable) {
-						GB_ASSERT(args[i] != nullptr);
-						args[i] = ir_emit_conv(proc, args[i], e->type);
-					}
-				}
-			}
-
-			if (variadic && !vari_expand && !is_c_vararg) {
-				ir_emit_comment(proc, str_lit("variadic call argument generation"));
-				gbAllocator allocator = ir_allocator();
-				Type *slice_type = param_tuple->variables[variadic_index]->type;
-				Type *elem_type  = base_type(slice_type)->Slice.elem;
-				irValue *slice = ir_add_local_generated(proc, slice_type, true);
-				isize slice_len = arg_count+1 - (variadic_index+1);
-
-				if (slice_len > 0) {
-					irValue *base_array = ir_add_local_generated(proc, alloc_type_array(elem_type, slice_len), true);
-
-					for (isize i = variadic_index, j = 0; i < arg_count; i++, j++) {
-						irValue *addr = ir_emit_array_epi(proc, base_array, cast(i32)j);
-						ir_emit_store(proc, addr, args[i]);
-					}
-
-					irValue *base_elem = ir_emit_array_epi(proc, base_array, 0);
-					irValue *len = ir_const_int(slice_len);
-					ir_fill_slice(proc, slice, base_elem, len);
-				}
-
-				arg_count = param_count;
-				args[variadic_index] = ir_emit_load(proc, slice);
-			}
-		}
-
-		if (variadic && variadic_index+1 < param_count) {
-			for (isize i = variadic_index+1; i < param_count; i++) {
-				Entity *e = param_tuple->variables[i];
-				switch (e->Variable.param_value.kind) {
-				case ParameterValue_Constant:
-					args[i] = ir_value_constant(e->type, e->Variable.param_value.value);
-					break;
-				case ParameterValue_Nil:
-					args[i] = ir_value_nil(e->type);
-					break;
-				case ParameterValue_Location:
-					args[i] = ir_emit_source_code_location(proc, proc_name, pos);
-					break;
-				case ParameterValue_Value:
-					args[i] = ir_build_expr(proc, e->Variable.param_value.ast_value);
-					break;
-				}
-			}
-		}
-
-		isize final_count = param_count;
-		if (is_c_vararg) {
-			final_count = arg_count;
-		}
-
-		auto call_args = array_slice(args, 0, final_count);
-		return ir_emit_call(proc, value, call_args, ce->inlining, proc->return_ptr_hint_ast == expr);
+		return res;
 	case_end;
 
 	case_ast_node(se, SliceExpr, expr);

+ 7 - 1
src/llvm_backend.cpp

@@ -8903,7 +8903,13 @@ lbValue lb_build_expr(lbProcedure *p, Ast *expr) {
 	case_end;
 
 	case_ast_node(ce, CallExpr, expr);
-		return lb_build_call_expr(p, expr);
+		lbValue res = lb_build_call_expr(p, expr);
+		if (ce->optional_ok_one) { // TODO(bill): Minor hack for #optional_ok procedures
+			GB_ASSERT(is_type_tuple(res.type));
+			GB_ASSERT(res.type->Tuple.variables.count == 2);
+			return lb_emit_struct_ev(p, res, 0);
+		}
+		return res;
 	case_end;
 
 	case_ast_node(se, SliceExpr, expr);

+ 1 - 0
src/parser.cpp

@@ -1618,6 +1618,7 @@ void parse_proc_tags(AstFile *f, u64 *tags) {
 		}
 
 		if (false) {}
+		ELSE_IF_ADD_TAG(optional_ok)
 		ELSE_IF_ADD_TAG(require_results)
 		ELSE_IF_ADD_TAG(bounds_check)
 		ELSE_IF_ADD_TAG(no_bounds_check)

+ 3 - 0
src/parser.hpp

@@ -165,7 +165,9 @@ enum ProcInlining {
 enum ProcTag {
 	ProcTag_bounds_check    = 1<<0,
 	ProcTag_no_bounds_check = 1<<1,
+	
 	ProcTag_require_results = 1<<4,
+	ProcTag_optional_ok     = 1<<5,
 };
 
 enum ProcCallingConvention {
@@ -282,6 +284,7 @@ AST_KIND(_ExprBegin,  "",  bool) \
 		Token        close; \
 		Token        ellipsis; \
 		ProcInlining inlining; \
+		bool         optional_ok_one; \
 	}) \
 	AST_KIND(FieldValue,      "field value",              struct { Token eq; Ast *field, *value; }) \
 	AST_KIND(TernaryExpr,     "ternary expression",       struct { Ast *cond, *x, *y; }) \

+ 6 - 3
src/types.cpp

@@ -232,6 +232,7 @@ struct TypeUnion {
 		Array<Type *> abi_compat_params;                  \
 		Type *   abi_compat_result_type;                  \
 		i32      variadic_index;                          \
+		/* TODO(bill): Make this a flag set rather than bools */ \
 		bool     variadic;                                \
 		bool     abi_types_set;                           \
 		bool     require_results;                         \
@@ -242,6 +243,7 @@ struct TypeUnion {
 		bool     has_named_results;                       \
 		bool     diverging; /* no return */               \
 		bool     return_by_pointer;                       \
+		bool     optional_ok;                             \
 		u64      tags;                                    \
 		isize    specialization_count;                    \
 		ProcCallingConvention calling_convention;         \
@@ -1979,9 +1981,10 @@ bool are_types_identical(Type *x, Type *y) {
 	case Type_Proc:
 		if (y->kind == Type_Proc) {
 			return x->Proc.calling_convention == y->Proc.calling_convention &&
-			       x->Proc.c_vararg  == y->Proc.c_vararg  &&
-			       x->Proc.variadic  == y->Proc.variadic  &&
-			       x->Proc.diverging == y->Proc.diverging &&
+			       x->Proc.c_vararg    == y->Proc.c_vararg    &&
+			       x->Proc.variadic    == y->Proc.variadic    &&
+			       x->Proc.diverging   == y->Proc.diverging   &&
+			       x->Proc.optional_ok == y->Proc.optional_ok &&
 			       are_types_identical(x->Proc.params, y->Proc.params) &&
 			       are_types_identical(x->Proc.results, y->Proc.results);
 		}