Browse Source

merge upstream/master

CiD- 3 years ago
parent
commit
1f4e5e919f
45 changed files with 1685 additions and 323 deletions
  1. 9 3
      .github/workflows/ci.yml
  2. 9 3
      Makefile
  3. 140 0
      build_odin.sh
  4. 26 1
      core/c/libc/stdio.odin
  5. 8 6
      core/c/libc/wctype.odin
  6. 1 1
      core/hash/hash.odin
  7. 26 5
      core/math/math.odin
  8. 70 0
      core/os/dir_freebsd.odin
  9. 1 1
      core/os/file_windows.odin
  10. 13 13
      core/os/os_darwin.odin
  11. 274 37
      core/os/os_freebsd.odin
  12. 15 11
      core/os/os_linux.odin
  13. 1 1
      core/os/stat_unix.odin
  14. 12 10
      core/runtime/core.odin
  15. 89 53
      core/runtime/error_checks.odin
  16. 12 4
      core/runtime/print.odin
  17. 75 0
      core/sync/sync2/futex_freebsd.odin
  18. 1 1
      core/sync/sync2/primitives.odin
  19. 9 0
      core/sync/sync2/primitives_freebsd.odin
  20. 7 7
      core/sync/sync_freebsd.odin
  21. 45 45
      core/sys/unix/pthread_freebsd.odin
  22. 2 0
      core/sys/unix/syscalls_linux.odin
  23. 2 0
      examples/all/all_main.odin
  24. 1 4
      examples/demo/demo.odin
  25. 66 7
      src/build_settings.cpp
  26. 13 5
      src/check_builtin.cpp
  27. 12 0
      src/check_decl.cpp
  28. 2 0
      src/check_expr.cpp
  29. 4 1
      src/check_stmt.cpp
  30. 9 3
      src/check_type.cpp
  31. 66 35
      src/checker.cpp
  32. 3 1
      src/llvm_abi.cpp
  33. 53 8
      src/llvm_backend.cpp
  34. 14 3
      src/llvm_backend_expr.cpp
  35. 19 6
      src/llvm_backend_proc.cpp
  36. 10 0
      src/llvm_backend_type.cpp
  37. 23 9
      src/llvm_backend_utility.cpp
  38. 71 1
      src/main.cpp
  39. 21 29
      src/parser.cpp
  40. 0 8
      src/string.cpp
  41. 40 0
      tests/common/common.odin
  42. 14 0
      tests/core/Makefile
  43. 85 0
      tests/core/math/linalg/glsl/test_linalg_glsl_math.odin
  44. 310 0
      tests/core/math/test_core_math.odin
  45. 2 1
      vendor/directx/d3d12/d3d12_constants.odin

+ 9 - 3
.github/workflows/ci.yml

@@ -7,9 +7,9 @@ jobs:
     steps:
       - uses: actions/checkout@v1
       - name: Download LLVM, botan
-        run: sudo apt-get install llvm-11 clang-11 llvm libbotan-2-dev botan
+        run: sudo apt-get install llvm-11 clang-11 libbotan-2-dev botan
       - name: build odin
-        run: make release
+        run: ./build_odin.sh release
       - name: Odin version
         run: ./odin version
         timeout-minutes: 1
@@ -41,6 +41,9 @@ jobs:
       - 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 FreeBSD amd64
+        run: ./odin check examples/all -vet -strict-style -target:freebsd_amd64
+        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
@@ -55,7 +58,7 @@ jobs:
           TMP_PATH=$(xcrun --show-sdk-path)/user/include
           echo "CPATH=$TMP_PATH" >> $GITHUB_ENV
       - name: build odin
-        run: make release
+        run: ./build_odin.sh release
       - name: Odin version
         run: ./odin version
         timeout-minutes: 1
@@ -87,6 +90,9 @@ jobs:
       - name: Odin check examples/all for Darwin arm64
         run: ./odin check examples/all -vet -strict-style -target:darwin_arm64
         timeout-minutes: 10
+      - name: Odin check examples/all for Linux arm64
+        run: ./odin check examples/all -vet -strict-style -target:linux_arm64
+        timeout-minutes: 10
   build_windows:
     runs-on: windows-2019
     steps:

+ 9 - 3
Makefile

@@ -30,10 +30,10 @@ ifeq ($(OS), Darwin)
     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 
+        else
             $(error "Requirement: llvm-config must be base version greater than 11 for amd64/x86")
-        endif 
-    endif 
+        endif
+    endif
 
     LDFLAGS:=$(LDFLAGS) -liconv -ldl
     CFLAGS:=$(CFLAGS) $(shell $(LLVM_CONFIG) --cxxflags --ldflags)
@@ -62,6 +62,12 @@ ifeq ($(OS), OpenBSD)
     CFLAGS:=$(CFLAGS) $(shell $(LLVM_CONFIG) --cxxflags --ldflags)
     LDFLAGS:=$(LDFLAGS) $(shell $(LLVM_CONFIG) --libs core native --system-libs)
 endif
+ifeq ($(OS), FreeBSD)
+    LLVM_CONFIG=/usr/local/bin/llvm-config11
+
+    CFLAGS:=$(CFLAGS) $(shell $(LLVM_CONFIG) --cxxflags --ldflags)
+    LDFLAGS:=$(LDFLAGS) $(shell $(LLVM_CONFIG) --libs core native --system-libs)
+endif
 
 all: debug demo
 

+ 140 - 0
build_odin.sh

@@ -0,0 +1,140 @@
+#!/bin/bash
+set -eu
+
+GIT_SHA=$(git rev-parse --short HEAD)
+DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value"
+LDFLAGS="-pthread -lm -lstdc++"
+CFLAGS="-std=c++14 -DGIT_SHA=\"$GIT_SHA\""
+CFLAGS="$CFLAGS -DODIN_VERSION_RAW=\"dev-$(date +"%Y-%m")\""
+CC=clang
+OS=$(uname)
+
+panic() {
+	printf "%s\n" "$1"
+	exit 1
+}
+
+version() { echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; }
+
+config_darwin() {
+	ARCH=$(uname -m)
+	LLVM_CONFIG=llvm-config
+
+	# allow for arm only llvm's with version 13
+	if [ ARCH == arm64 ]; then
+		MIN_LLVM_VERSION=("13.0.0")
+	else
+		# allow for x86 / amd64 all llvm versions begining from 11
+		MIN_LLVM_VERSION=("11.1.0")
+	fi
+
+	if [ $(version $($LLVM_CONFIG --version)) -lt $(version $MIN_LLVM_VERSION) ]; then
+		if [ ARCH == arm64 ]; then
+			panic "Requirement: llvm-config must be base version 13 for arm64"
+		else
+			panic "Requirement: llvm-config must be base version greater than 11 for amd64/x86"
+		fi
+	fi
+
+	LDFLAGS="$LDFLAGS -liconv -ldl"
+	CFLAGS="$CFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
+	LDFLAGS="$LDFLAGS -lLLVM-C"
+}
+
+config_openbsd() {
+	LLVM_CONFIG=/usr/local/bin/llvm-config
+
+	LDFLAGS="$LDFLAGS -liconv"
+	CFLAGS="$CFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
+	LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
+}
+
+config_linux() {
+	if which llvm-config > /dev/null 2>&1; then
+		LLVM_CONFIG=llvm-config
+	elif which llvm-config-11 > /dev/null 2>&1; then
+		LLVM_CONFIG=llvm-config-11
+	elif which llvm-config-11-64 > /dev/null 2>&1; then
+		LLVM_CONFIG=llvm-config-11-64
+	else
+		panic "Unable to find LLVM-config"
+	fi
+
+	MIN_LLVM_VERSION=("11.0.0")
+	if [ $(version $($LLVM_CONFIG --version)) -lt $(version $MIN_LLVM_VERSION) ]; then
+		echo "Tried to use " $(which $LLVM_CONFIG) "version" $($LLVM_CONFIG --version)
+		panic "Requirement: llvm-config must be base version greater than 11"
+	fi
+
+	LDFLAGS="$LDFLAGS -ldl"
+	CFLAGS="$CFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
+	LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
+}
+
+build_odin() {
+	case $1 in
+	debug)
+		EXTRAFLAGS="-g"
+		;;
+	release)
+		EXTRAFLAGS="-O3"
+		;;
+	release-native)
+		EXTRAFLAGS="-O3 -march=native"
+		;;
+	nightly)
+		EXTRAFLAGS="-DNIGHTLY -O3"
+		;;
+	*)
+		panic "Build mode unsupported!"
+	esac
+
+	set -x
+	$CC src/main.cpp src/libtommath.cpp $DISABLED_WARNINGS $CFLAGS $EXTRAFLAGS $LDFLAGS -o odin
+	set +x
+}
+
+run_demo() {
+	./odin run examples/demo/demo.odin
+}
+
+case $OS in
+Linux)
+	config_linux
+	;;
+Darwin)
+	config_darwin
+	;;
+OpenBSD)
+	config_openbsd
+	;;
+*)
+	panic "Platform unsupported!"
+esac
+
+if [[ $# -eq 0 ]]; then
+	build_odin debug
+	run_demo
+	exit 0
+fi
+
+if [[ $# -eq 1 ]]; then
+	case $1 in
+	report)
+		if [[ ! -f "./odin" ]]; then
+			build_odin debug
+		fi
+
+		./odin report
+		exit 0
+		;;
+	*)
+		build_odin $1
+		;;
+	esac
+
+	run_demo
+	exit 0
+else
+	panic "Too many arguments!"
+fi

+ 26 - 1
core/c/libc/stdio.odin

@@ -79,7 +79,32 @@ when ODIN_OS == .Linux {
 }
 
 when ODIN_OS == .OpenBSD {
-	fpos_t :: i64
+	fpos_t :: distinct 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 == .FreeBSD {
+	fpos_t :: distinct i64
 
 	_IOFBF :: 0
 	_IOLBF :: 1

+ 8 - 6
core/c/libc/wctype.odin

@@ -13,21 +13,23 @@ when ODIN_OS == .Windows {
 when ODIN_OS == .Windows {
 	wctrans_t :: distinct wchar_t
 	wctype_t  :: distinct ushort
-}
 
-when ODIN_OS == .Linux {
+} else when ODIN_OS == .Linux {
 	wctrans_t :: distinct intptr_t
 	wctype_t  :: distinct ulong
-}
 
-when ODIN_OS == .Darwin {
+} else when ODIN_OS == .Darwin {
 	wctrans_t :: distinct int
 	wctype_t  :: distinct u32
-}
 
-when ODIN_OS == .OpenBSD {
+} else when ODIN_OS == .OpenBSD {
 	wctrans_t :: distinct rawptr
 	wctype_t  :: distinct rawptr
+
+} else when ODIN_OS == .FreeBSD {
+	wctrans_t :: distinct int
+	wctype_t  :: distinct ulong
+	
 }
 
 @(default_calling_convention="c")

+ 1 - 1
core/hash/hash.odin

@@ -151,7 +151,7 @@ murmur32 :: proc(data: []byte, seed := u32(0)) -> u32 {
 		k1 ~= u32(tail[2]) << 16
 		fallthrough
 	case 2:
-		k1 ~= u32(tail[2]) << 8
+		k1 ~= u32(tail[1]) << 8
 		fallthrough
 	case 1:
 		k1 ~= u32(tail[0])

+ 26 - 5
core/math/math.odin

@@ -396,7 +396,7 @@ trunc_f16   :: proc "contextless" (x: f16) -> f16 {
 		e := (x >> shift) & mask - bias
 
 		if e < shift {
-			x &= ~(1 << (shift-e)) - 1
+			x &~= 1 << (shift-e) - 1
 		}
 		return transmute(f16)x
 	}
@@ -428,7 +428,7 @@ trunc_f32   :: proc "contextless" (x: f32) -> f32 {
 		e := (x >> shift) & mask - bias
 
 		if e < shift {
-			x &= ~(1 << (shift-e)) - 1
+			x &~= 1 << (shift-e) - 1
 		}
 		return transmute(f32)x
 	}
@@ -460,7 +460,7 @@ trunc_f64   :: proc "contextless" (x: f64) -> f64 {
 		e := (x >> shift) & mask - bias
 
 		if e < shift {
-			x &= ~(1 << (shift-e)) - 1
+			x &~= 1 << (shift-e) - 1
 		}
 		return transmute(f64)x
 	}
@@ -473,6 +473,7 @@ trunc_f64   :: proc "contextless" (x: f64) -> f64 {
 }
 trunc_f64le :: proc "contextless" (x: f64le) -> f64le { return #force_inline f64le(trunc_f64(f64(x))) }
 trunc_f64be :: proc "contextless" (x: f64be) -> f64be { return #force_inline f64be(trunc_f64(f64(x))) }
+// Removes the fractional part of the value, i.e. rounds towards zero.
 trunc       :: proc{
 	trunc_f16, trunc_f16le, trunc_f16be,
 	trunc_f32, trunc_f32le, trunc_f32be, 
@@ -958,7 +959,7 @@ classify_f16   :: proc "contextless" (x: f16)   -> Float_Class {
 			return .Neg_Zero
 		}
 		return .Zero
-	case x*0.5 == x:
+	case x*0.25 == x:
 		if x < 0 {
 			return .Neg_Inf
 		}
@@ -1027,6 +1028,8 @@ classify_f64   :: proc "contextless" (x: f64)   -> Float_Class {
 }
 classify_f64le :: proc "contextless" (x: f64le) -> Float_Class { return #force_inline classify_f64(f64(x)) }
 classify_f64be :: proc "contextless" (x: f64be) -> Float_Class { return #force_inline classify_f64(f64(x)) }
+// Returns the `Float_Class` of the value, i.e. whether normal, subnormal, zero, negative zero, NaN, infinity or
+// negative infinity.
 classify       :: proc{
 	classify_f16, classify_f16le, classify_f16be,
 	classify_f32, classify_f32le, classify_f32be,
@@ -1715,4 +1718,22 @@ F32_BIAS  :: 0x7f
 
 F64_MASK  :: 0x7ff
 F64_SHIFT :: 64 - 12
-F64_BIAS  :: 0x3ff
+F64_BIAS  :: 0x3ff
+
+INF_F16     :f16: 0h7C00
+NEG_INF_F16 :f16: 0hFC00
+
+SNAN_F16    :f16: 0h7C01
+QNAN_F16    :f16: 0h7E01
+
+INF_F32     :f32: 0h7F80_0000
+NEG_INF_F32 :f32: 0hFF80_0000
+
+SNAN_F32    :f32: 0hFF80_0001
+QNAN_F32    :f32: 0hFFC0_0001
+
+INF_F64     :f64: 0h7FF0_0000_0000_0000
+NEG_INF_F64 :f64: 0hFFF0_0000_0000_0000
+
+SNAN_F64    :f64: 0h7FF0_0000_0000_0001
+QNAN_F64    :f64: 0h7FF8_0000_0000_0001

+ 70 - 0
core/os/dir_freebsd.odin

@@ -0,0 +1,70 @@
+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)
+
+	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
+}

+ 1 - 1
core/os/file_windows.odin

@@ -113,7 +113,7 @@ read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Errno) {
 		if max_read == 0 {
 			break
 		}
-
+		
 		single_read_length: u32
 		ok := win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil)
 		if !ok {

+ 13 - 13
core/os/os_darwin.odin

@@ -260,13 +260,13 @@ 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 { 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 }
+S_ISLNK  :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFLNK  }
+S_ISREG  :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFREG  }
+S_ISDIR  :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFDIR  }
+S_ISCHR  :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFCHR  }
+S_ISBLK  :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFBLK  }
+S_ISFIFO :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFIFO  }
+S_ISSOCK :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFSOCK }
 
 R_OK :: 4 // Test for read permission
 W_OK :: 2 // Test for write permission
@@ -313,7 +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="mkdir")    _unix_mkdir    :: proc(buf: cstring, mode: u16) -> 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 ---
@@ -433,7 +433,7 @@ is_file_handle :: proc(fd: Handle) -> bool {
 	if err != ERROR_NONE {
 		return false
 	}
-	return S_ISREG(cast(u32)s.mode)
+	return S_ISREG(s.mode)
 }
 
 is_file_path :: proc(path: string, follow_links: bool = true) -> bool {
@@ -447,7 +447,7 @@ is_file_path :: proc(path: string, follow_links: bool = true) -> bool {
 	if err != ERROR_NONE {
 		return false
 	}
-	return S_ISREG(cast(u32)s.mode)
+	return S_ISREG(s.mode)
 }
 
 
@@ -456,7 +456,7 @@ is_dir_handle :: proc(fd: Handle) -> bool {
 	if err != ERROR_NONE {
 		return false
 	}
-	return S_ISDIR(cast(u32)s.mode)
+	return S_ISDIR(s.mode)
 }
 
 is_dir_path :: proc(path: string, follow_links: bool = true) -> bool {
@@ -470,7 +470,7 @@ is_dir_path :: proc(path: string, follow_links: bool = true) -> bool {
 	if err != ERROR_NONE {
 		return false
 	}
-	return S_ISDIR(cast(u32)s.mode)
+	return S_ISDIR(s.mode)
 }
 
 is_file :: proc {is_file_path, is_file_handle}
@@ -670,7 +670,7 @@ set_current_directory :: proc(path: string) -> (err: Errno) {
 	return ERROR_NONE
 }
 
-make_directory :: proc(path: string, mode: u32 = 0o775) -> Errno {
+make_directory :: proc(path: string, mode: u16 = 0o775) -> Errno {
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_mkdir(path_cstr, mode)
 	if res == -1 {

+ 274 - 37
core/os/os_freebsd.odin

@@ -10,7 +10,6 @@ import "core:c"
 Handle :: distinct i32
 File_Time :: distinct u64
 Errno :: distinct i32
-Syscall :: distinct i32
 
 INVALID_HANDLE :: ~Handle(0)
 
@@ -142,40 +141,74 @@ RTLD_TRACE        :: 0x200
 RTLD_NODELETE     :: 0x01000
 RTLD_NOLOAD       :: 0x02000
 
+MAX_PATH :: 1024
+
 args := _alloc_command_line_arguments()
 
 Unix_File_Time :: struct {
-	seconds: i64,
+	seconds: time_t,
 	nanoseconds: c.long,
 }
 
+dev_t :: u64
+ino_t :: u64
+nlink_t :: u64
+off_t :: i64
+mode_t :: u16
 pid_t :: u32
+uid_t :: u32
+gid_t :: u32
+blkcnt_t :: i64
+blksize_t :: i32
+fflags_t :: u32
+
+when ODIN_ARCH == .amd64 /* LP64 */ {
+	time_t :: i64
+} else {
+	time_t :: i32
+}
+
 
 OS_Stat :: struct {
-	device_id: u64,
-	serial: u64,
-	nlink: u64,
-	mode: u32,
+	device_id: dev_t,
+	serial: ino_t,
+	nlink: nlink_t,
+	mode: mode_t,
 	_padding0: i16,
-	uid: u32,
-	gid: u32,
+	uid: uid_t,
+	gid: gid_t,
 	_padding1: i32,
-	rdev: u64,
+	rdev: dev_t,
 
 	last_access: Unix_File_Time,
 	modified: Unix_File_Time,
 	status_change: Unix_File_Time,
 	birthtime: Unix_File_Time,
 
-	size: i64,
-	blocks: i64,
-	block_size: i32,
+	size: off_t,
+	blocks: blkcnt_t,
+	block_size: blksize_t,
 
-	flags: u32,
+	flags: fflags_t,
 	gen: u64,
-	lspare: i64,
+	lspare: [10]u64,
 }
 
+
+// since FreeBSD v12
+Dirent :: struct {
+	ino: ino_t,
+	off: off_t,
+	reclen: u16,
+	type: u8,
+	_pad0: u8,
+	namlen: u16,
+	_pad1: u16,
+	name: [256]byte,
+}
+
+Dir :: distinct rawptr // DIR*
+
 // File type
 S_IFMT   :: 0o170000 // Type of file mask
 S_IFIFO  :: 0o010000 // Named pipe (fifo)
@@ -211,13 +244,13 @@ 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: mode_t) -> bool do return (m & S_IFMT) == S_IFLNK
+S_ISREG  :: #force_inline proc(m: mode_t) -> bool do return (m & S_IFMT) == S_IFREG
+S_ISDIR  :: #force_inline proc(m: mode_t) -> bool do return (m & S_IFMT) == S_IFDIR
+S_ISCHR  :: #force_inline proc(m: mode_t) -> bool do return (m & S_IFMT) == S_IFCHR
+S_ISBLK  :: #force_inline proc(m: mode_t) -> bool do return (m & S_IFMT) == S_IFBLK
+S_ISFIFO :: #force_inline proc(m: mode_t) -> bool do return (m & S_IFMT) == S_IFIFO
+S_ISSOCK :: #force_inline proc(m: mode_t) -> bool do return (m & S_IFMT) == S_IFSOCK
 
 F_OK :: 0 // Test for file existance
 X_OK :: 1 // Test for execute permission
@@ -225,27 +258,38 @@ 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="__error")		__errno_location :: 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="lseek64")          _unix_seek          :: proc(fd: Handle, offset: i64, whence: c.int) -> i64 ---
-	@(link_name="gettid")           _unix_gettid        :: proc() -> u64 ---
+	@(link_name="lseek")            _unix_seek          :: proc(fd: Handle, offset: i64, whence: c.int) -> i64 ---
 	@(link_name="getpagesize")      _unix_getpagesize   :: proc() -> c.int ---
-	@(link_name="stat64")           _unix_stat          :: proc(path: cstring, stat: ^OS_Stat) -> c.int ---
+	@(link_name="stat")             _unix_stat          :: proc(path: cstring, stat: ^OS_Stat) -> c.int ---
+	@(link_name="lstat")            _unix_lstat         :: proc(path: cstring, sb: ^OS_Stat) -> c.int ---
 	@(link_name="fstat")            _unix_fstat         :: proc(fd: Handle, stat: ^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(buf: 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="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="getcwd")           _unix_getcwd        :: proc(buf: cstring, len: c.size_t) -> cstring ---
-	@(link_name="chdir")            _unix_chdir         :: proc(buf: cstring) -> c.int ---
+	@(link_name="realpath")         _unix_realpath      :: proc(path: cstring, resolved_path: rawptr) -> rawptr ---
 
 	@(link_name="exit")             _unix_exit          :: proc(status: c.int) -> ! ---
 }
@@ -318,12 +362,102 @@ file_size :: proc(fd: Handle) -> (i64, Errno) {
 	return s.size, ERROR_NONE
 }
 
-stdin: Handle = 0
+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)
+	s, err := _fstat(fd)
 	if err != ERROR_NONE {
 		return 0, err
 	}
@@ -332,7 +466,7 @@ last_write_time :: proc(fd: Handle) -> (File_Time, Errno) {
 }
 
 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
 	}
@@ -340,18 +474,33 @@ last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
 	return File_Time(modified), ERROR_NONE
 }
 
-stat :: proc(path: string) -> (OS_Stat, Errno) {
+@private
+_stat :: proc(path: string) -> (OS_Stat, Errno) {
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
-	s: OS_Stat
-	result := _unix_stat(cstr, &s)
+	s: OS_Stat = ---
+	result := _unix_lstat(cstr, &s)
 	if result == -1 {
 		return s, Errno(get_last_error())
 	}
 	return s, ERROR_NONE
 }
 
-fstat :: proc(fd: Handle) -> (OS_Stat, Errno) {
-	s: OS_Stat
+@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) {
+	s: OS_Stat = ---
 	result := _unix_fstat(fd, &s)
 	if result == -1 {
 		return s, Errno(get_last_error())
@@ -359,6 +508,95 @@ fstat :: proc(fd: Handle) -> (OS_Stat, Errno) {
 	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 FreeBSD
+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)
 	result := _unix_access(cstr, c.int(mask))
@@ -464,4 +702,3 @@ _alloc_command_line_arguments :: proc() -> []string {
 	}
 	return res
 }
-

+ 15 - 11
core/os/os_linux.odin

@@ -269,7 +269,7 @@ X_OK :: 1 // Test for execute permission
 W_OK :: 2 // Test for write permission
 R_OK :: 4 // Test for read permission
 
-AT_FDCWD            :: -100
+AT_FDCWD            :: ~uintptr(99)	/* -100 */
 AT_REMOVEDIR        :: uintptr(0x200)
 AT_SYMLINK_NOFOLLOW :: uintptr(0x100)
 
@@ -278,7 +278,11 @@ _unix_personality :: proc(persona: u64) -> int {
 }
 
 _unix_fork :: proc() -> Pid {
-	res := int(intrinsics.syscall(unix.SYS_fork))
+	when ODIN_ARCH != .arm64 {
+		res := int(intrinsics.syscall(unix.SYS_fork))
+	} else {
+		res := int(intrinsics.syscall(unix.SYS_clone, unix.SIGCHLD))
+	}
 	return -1 if res < 0 else Pid(res)
 }
 
@@ -286,7 +290,7 @@ _unix_open :: proc(path: cstring, flags: int, mode: int = 0o000) -> Handle {
 	when ODIN_ARCH != .arm64 {
 		res := int(intrinsics.syscall(unix.SYS_open, uintptr(rawptr(path)), uintptr(flags), uintptr(mode)))
 	} else { // NOTE: arm64 does not have open
-		res := int(intrinsics.syscall(unix.SYS_openat, uintptr(AT_FDCWD), uintptr(rawptr(path), uintptr(flags), uintptr(mode))))
+		res := int(intrinsics.syscall(unix.SYS_openat, AT_FDCWD, uintptr(rawptr(path)), uintptr(flags), uintptr(mode)))
 	}
 	return -1 if res < 0 else Handle(res)
 }
@@ -321,7 +325,7 @@ _unix_stat :: proc(path: cstring, stat: ^OS_Stat) -> int {
 	} else when ODIN_ARCH != .arm64 {
 		return int(intrinsics.syscall(unix.SYS_stat64, uintptr(rawptr(path)), uintptr(stat)))
 	} else { // NOTE: arm64 does not have stat
-		return int(intrinsics.syscall(unix.SYS_fstatat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(stat), 0))
+		return int(intrinsics.syscall(unix.SYS_fstatat, AT_FDCWD, uintptr(rawptr(path)), uintptr(stat), 0))
 	}
 }
 
@@ -339,7 +343,7 @@ _unix_lstat :: proc(path: cstring, stat: ^OS_Stat) -> int {
 	} else when ODIN_ARCH != .arm64 {
 		return int(intrinsics.syscall(unix.SYS_lstat64, uintptr(rawptr(path)), uintptr(stat)))
 	} else { // NOTE: arm64 does not have any lstat
-		return int(intrinsics.syscall(unix.SYS_fstatat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(stat), AT_SYMLINK_NOFOLLOW))
+		return int(intrinsics.syscall(unix.SYS_fstatat, AT_FDCWD, uintptr(rawptr(path)), uintptr(stat), AT_SYMLINK_NOFOLLOW))
 	}
 }
 
@@ -347,7 +351,7 @@ _unix_readlink :: proc(path: cstring, buf: rawptr, bufsiz: uint) -> int {
 	when ODIN_ARCH != .arm64 {
 		return int(intrinsics.syscall(unix.SYS_readlink, uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz)))
 	} else { // NOTE: arm64 does not have readlink
-		return int(intrinsics.syscall(unix.SYS_readlinkat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz)))
+		return int(intrinsics.syscall(unix.SYS_readlinkat, AT_FDCWD, uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz)))
 	}
 }
 
@@ -355,7 +359,7 @@ _unix_access :: proc(path: cstring, mask: int) -> int {
 	when ODIN_ARCH != .arm64 {
 		return int(intrinsics.syscall(unix.SYS_access, uintptr(rawptr(path)), uintptr(mask)))
 	} else { // NOTE: arm64 does not have access
-		return int(intrinsics.syscall(unix.SYS_faccessat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(mask)))
+		return int(intrinsics.syscall(unix.SYS_faccessat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mask)))
 	}
 }
 
@@ -371,7 +375,7 @@ _unix_rename :: proc(old, new: cstring) -> int {
 	when ODIN_ARCH != .arm64 {
 		return int(intrinsics.syscall(unix.SYS_rename, uintptr(rawptr(old)), uintptr(rawptr(new))))
 	} else { // NOTE: arm64 does not have rename
-		return int(intrinsics.syscall(unix.SYS_renameat, uintptr(AT_FDCWD), uintptr(rawptr(old)), uintptr(rawptr(new))))
+		return int(intrinsics.syscall(unix.SYS_renameat, AT_FDCWD, uintptr(rawptr(old)), uintptr(rawptr(new))))
 	}
 }
 
@@ -379,7 +383,7 @@ _unix_unlink :: proc(path: cstring) -> int {
 	when ODIN_ARCH != .arm64 {
 		return int(intrinsics.syscall(unix.SYS_unlink, uintptr(rawptr(path))))
 	} else { // NOTE: arm64 does not have unlink
-		return int(intrinsics.syscall(unix.SYS_unlinkat, uintptr(AT_FDCWD), uintptr(rawptr(path), 0)))
+		return int(intrinsics.syscall(unix.SYS_unlinkat, AT_FDCWD, uintptr(rawptr(path)), 0))
 	}
 }
 
@@ -387,7 +391,7 @@ _unix_rmdir :: proc(path: cstring) -> int {
 	when ODIN_ARCH != .arm64 {
 		return int(intrinsics.syscall(unix.SYS_rmdir, uintptr(rawptr(path))))
 	} else { // NOTE: arm64 does not have rmdir
-		return int(intrinsics.syscall(unix.SYS_unlinkat, uintptr(AT_FDCWD), uintptr(rawptr(path)), AT_REMOVEDIR))
+		return int(intrinsics.syscall(unix.SYS_unlinkat, AT_FDCWD, uintptr(rawptr(path)), AT_REMOVEDIR))
 	}
 }
 
@@ -395,7 +399,7 @@ _unix_mkdir :: proc(path: cstring, mode: u32) -> int {
 	when ODIN_ARCH != .arm64 {
 		return int(intrinsics.syscall(unix.SYS_mkdir, uintptr(rawptr(path)), uintptr(mode)))
 	} else { // NOTE: arm64 does not have mkdir
-		return int(intrinsics.syscall(unix.SYS_mkdirat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(mode)))
+		return int(intrinsics.syscall(unix.SYS_mkdirat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mode)))
 	}
 }
 

+ 1 - 1
core/os/stat_unix.odin

@@ -61,7 +61,7 @@ _make_time_from_unix_file_time :: proc(uft: Unix_File_Time) -> time.Time {
 _fill_file_info_from_stat :: proc(fi: ^File_Info, s: OS_Stat) {
 	fi.size = s.size
 	fi.mode = cast(File_Mode)s.mode
-	fi.is_dir = S_ISDIR(u32(s.mode))
+	fi.is_dir = S_ISDIR(s.mode)
 
 	// NOTE(laleksic, 2021-01-21): Not really creation time, but closest we can get (maybe better to leave it 0?)
 	fi.creation_time = _make_time_from_unix_file_time(s.status_change)

+ 12 - 10
core/runtime/core.odin

@@ -451,7 +451,7 @@ Odin_Endian_Type :: type_of(ODIN_ENDIAN)
 // This is probably only useful for freestanding targets
 foreign {
 	@(link_name="__$startup_runtime")
-	_startup_runtime :: proc() ---
+	_startup_runtime :: proc "odin" () ---
 }
 
 @(link_name="__$cleanup_runtime")
@@ -513,16 +513,18 @@ __type_info_of :: proc "contextless" (id: typeid) -> ^Type_Info #no_bounds_check
 	return &type_table[n]
 }
 
-typeid_base :: proc "contextless" (id: typeid) -> typeid {
-	ti := type_info_of(id)
-	ti = type_info_base(ti)
-	return ti.id
-}
-typeid_core :: proc "contextless" (id: typeid) -> typeid {
-	ti := type_info_core(type_info_of(id))
-	return ti.id
+when !ODIN_DISALLOW_RTTI {
+	typeid_base :: proc "contextless" (id: typeid) -> typeid {
+		ti := type_info_of(id)
+		ti = type_info_base(ti)
+		return ti.id
+	}
+	typeid_core :: proc "contextless" (id: typeid) -> typeid {
+		ti := type_info_core(type_info_of(id))
+		return ti.id
+	}
+	typeid_base_without_enum :: typeid_core
 }
-typeid_base_without_enum :: typeid_core
 
 
 

+ 89 - 53
core/runtime/error_checks.odin

@@ -21,6 +21,7 @@ bounds_check_error :: proc "contextless" (file: string, line, column: i32, index
 	if 0 <= index && index < count {
 		return
 	}
+	@(cold)
 	handle_error :: proc "contextless" (file: string, line, column: i32, index, count: int) {
 		print_caller_location(Source_Code_Location{file, line, column, ""})
 		print_string(" Index ")
@@ -81,6 +82,7 @@ dynamic_array_expr_error :: proc "contextless" (file: string, line, column: i32,
 	if 0 <= low && low <= high && high <= max {
 		return
 	}
+	@(cold)
 	handle_error :: proc "contextless" (file: string, line, column: i32, low, high, max: int) {
 		print_caller_location(Source_Code_Location{file, line, column, ""})
 		print_string(" Invalid dynamic array indices ")
@@ -97,10 +99,11 @@ dynamic_array_expr_error :: proc "contextless" (file: string, line, column: i32,
 
 
 matrix_bounds_check_error :: proc "contextless" (file: string, line, column: i32, row_index, column_index, row_count, column_count: int) {
-	if 0 <= row_index && row_index < row_count && 
+	if 0 <= row_index && row_index < row_count &&
 	   0 <= column_index && column_index < column_count {
 		return
 	}
+	@(cold)
 	handle_error :: proc "contextless" (file: string, line, column: i32, row_index, column_index, row_count, column_count: int) {
 		print_caller_location(Source_Code_Location{file, line, column, ""})
 		print_string(" Matrix indices [")
@@ -119,71 +122,101 @@ matrix_bounds_check_error :: proc "contextless" (file: string, line, column: i32
 }
 
 
-type_assertion_check :: proc "contextless" (ok: bool, file: string, line, column: i32, from, to: typeid) {
-	if ok {
-		return
-	}
-	handle_error :: proc "contextless" (file: string, line, column: i32, from, to: typeid) {
-		print_caller_location(Source_Code_Location{file, line, column, ""})
-		print_string(" Invalid type assertion from ")
-		print_typeid(from)
-		print_string(" to ")
-		print_typeid(to)
-		print_byte('\n')
-		type_assertion_trap()
+when ODIN_DISALLOW_RTTI {
+	type_assertion_check :: proc "contextless" (ok: bool, file: string, line, column: i32) {
+		if ok {
+			return
+		}
+		@(cold)
+		handle_error :: proc "contextless" (file: string, line, column: i32) {
+			print_caller_location(Source_Code_Location{file, line, column, ""})
+			print_string(" Invalid type assertion\n")
+			type_assertion_trap()
+		}
+		handle_error(file, line, column)
 	}
-	handle_error(file, line, column, from, to)
-}
 
-type_assertion_check2 :: proc "contextless" (ok: bool, file: string, line, column: i32, from, to: typeid, from_data: rawptr) {
-	if ok {
-		return
+	type_assertion_check2 :: proc "contextless" (ok: bool, file: string, line, column: i32) {
+		if ok {
+			return
+		}
+		@(cold)
+		handle_error :: proc "contextless" (file: string, line, column: i32) {
+			print_caller_location(Source_Code_Location{file, line, column, ""})
+			print_string(" Invalid type assertion\n")
+			type_assertion_trap()
+		}
+		handle_error(file, line, column)
+	}
+} else {
+	type_assertion_check :: proc "contextless" (ok: bool, file: string, line, column: i32, from, to: typeid) {
+		if ok {
+			return
+		}
+		@(cold)
+		handle_error :: proc "contextless" (file: string, line, column: i32, from, to: typeid) {
+			print_caller_location(Source_Code_Location{file, line, column, ""})
+			print_string(" Invalid type assertion from ")
+			print_typeid(from)
+			print_string(" to ")
+			print_typeid(to)
+			print_byte('\n')
+			type_assertion_trap()
+		}
+		handle_error(file, line, column, from, to)
 	}
 
-	variant_type :: proc "contextless" (id: typeid, data: rawptr) -> typeid {
-		if id == nil || data == nil {
-			return id
+	type_assertion_check2 :: proc "contextless" (ok: bool, file: string, line, column: i32, from, to: typeid, from_data: rawptr) {
+		if ok {
+			return
 		}
-		ti := type_info_base(type_info_of(id))
-		#partial switch v in ti.variant {
-		case Type_Info_Any:
-			return (^any)(data).id
-		case Type_Info_Union:
-			tag_ptr := uintptr(data) + v.tag_offset
-			idx := 0
-			switch v.tag_type.size {
-			case 1:  idx = int((^u8)(tag_ptr)^)   - 1
-			case 2:  idx = int((^u16)(tag_ptr)^)  - 1
-			case 4:  idx = int((^u32)(tag_ptr)^)  - 1
-			case 8:  idx = int((^u64)(tag_ptr)^)  - 1
-			case 16: idx = int((^u128)(tag_ptr)^) - 1
+
+		variant_type :: proc "contextless" (id: typeid, data: rawptr) -> typeid {
+			if id == nil || data == nil {
+				return id
 			}
-			if idx < 0 {
-				return nil
-			} else if idx < len(v.variants) {
-				return v.variants[idx].id
+			ti := type_info_base(type_info_of(id))
+			#partial switch v in ti.variant {
+			case Type_Info_Any:
+				return (^any)(data).id
+			case Type_Info_Union:
+				tag_ptr := uintptr(data) + v.tag_offset
+				idx := 0
+				switch v.tag_type.size {
+				case 1:  idx = int((^u8)(tag_ptr)^)   - 1
+				case 2:  idx = int((^u16)(tag_ptr)^)  - 1
+				case 4:  idx = int((^u32)(tag_ptr)^)  - 1
+				case 8:  idx = int((^u64)(tag_ptr)^)  - 1
+				case 16: idx = int((^u128)(tag_ptr)^) - 1
+				}
+				if idx < 0 {
+					return nil
+				} else if idx < len(v.variants) {
+					return v.variants[idx].id
+				}
 			}
+			return id
 		}
-		return id
-	}
 
-	handle_error :: proc "contextless" (file: string, line, column: i32, from, to: typeid, from_data: rawptr) {
+		@(cold)
+		handle_error :: proc "contextless" (file: string, line, column: i32, from, to: typeid, from_data: rawptr) {
 
-		actual := variant_type(from, from_data)
+			actual := variant_type(from, from_data)
 
-		print_caller_location(Source_Code_Location{file, line, column, ""})
-		print_string(" Invalid type assertion from ")
-		print_typeid(from)
-		print_string(" to ")
-		print_typeid(to)
-		if actual != from {
-			print_string(", actual type: ")
-			print_typeid(actual)
+			print_caller_location(Source_Code_Location{file, line, column, ""})
+			print_string(" Invalid type assertion from ")
+			print_typeid(from)
+			print_string(" to ")
+			print_typeid(to)
+			if actual != from {
+				print_string(", actual type: ")
+				print_typeid(actual)
+			}
+			print_byte('\n')
+			type_assertion_trap()
 		}
-		print_byte('\n')
-		type_assertion_trap()
+		handle_error(file, line, column, from, to, from_data)
 	}
-	handle_error(file, line, column, from, to, from_data)
 }
 
 
@@ -191,6 +224,7 @@ make_slice_error_loc :: #force_inline proc "contextless" (loc := #caller_locatio
 	if 0 <= len {
 		return
 	}
+	@(cold)
 	handle_error :: proc "contextless" (loc: Source_Code_Location, len: int) {
 		print_caller_location(loc)
 		print_string(" Invalid slice length for make: ")
@@ -205,6 +239,7 @@ make_dynamic_array_error_loc :: #force_inline proc "contextless" (using loc := #
 	if 0 <= len && len <= cap {
 		return
 	}
+	@(cold)
 	handle_error :: proc "contextless" (loc: Source_Code_Location, len, cap: int) {
 		print_caller_location(loc)
 		print_string(" Invalid dynamic array parameters for make: ")
@@ -221,6 +256,7 @@ make_map_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_loca
 	if 0 <= cap {
 		return
 	}
+	@(cold)
 	handle_error :: proc "contextless" (loc: Source_Code_Location, cap: int) {
 		print_caller_location(loc)
 		print_string(" Invalid map capacity for make: ")

+ 12 - 4
core/runtime/print.odin

@@ -160,11 +160,19 @@ print_caller_location :: proc "contextless" (using loc: Source_Code_Location) {
 	}
 }
 print_typeid :: proc "contextless" (id: typeid) {
-	if id == nil {
-		print_string("nil")
+	when ODIN_DISALLOW_RTTI {
+		if id == nil {
+			print_string("nil")
+		} else {
+			print_string("<unknown type>")
+		}
 	} else {
-		ti := type_info_of(id)
-		print_type(ti)
+		if id == nil {
+			print_string("nil")
+		} else {
+			ti := type_info_of(id)
+			print_type(ti)
+		}
 	}
 }
 print_type :: proc "contextless" (ti: ^Type_Info) {

+ 75 - 0
core/sync/sync2/futex_freebsd.odin

@@ -0,0 +1,75 @@
+//+private
+//+build freebsd
+package sync2
+
+import "core:c"
+import "core:os"
+import "core:time"
+
+UMTX_OP_WAIT :: 2
+UMTX_OP_WAKE :: 3
+
+foreign import libc "system:c"
+
+foreign libc {
+	_umtx_op :: proc "c" (obj: rawptr, op: c.int, val: c.ulong, uaddr: rawptr, uaddr2: rawptr) -> c.int ---
+}
+
+_futex_wait :: proc(f: ^Futex, expected: u32) -> bool {
+	timeout := os.Unix_File_Time{
+		seconds = 5,
+		nanoseconds = 0,
+	}
+
+	for {
+		res := _umtx_op(f, UMTX_OP_WAIT, c.ulong(expected), nil, &timeout)
+
+		if res != -1 {
+			return true
+		}
+
+		if os.Errno(os.get_last_error()) == os.ETIMEDOUT {
+			continue
+		}
+
+		panic("_futex_wait failure")
+	}
+	unreachable()
+}
+
+_futex_wait_with_timeout :: proc(f: ^Futex, expected: u32, duration: time.Duration) -> bool {
+	if duration <= 0 {
+		return false
+	}
+
+	res := _umtx_op(f, UMTX_OP_WAIT, c.ulong(expected), nil, &os.Unix_File_Time{
+		seconds = (os.time_t)(duration/1e9),
+		nanoseconds = (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 := _umtx_op(f, UMTX_OP_WAKE, 1, nil, nil)
+
+	if res == -1 {
+		panic("_futex_signal failure")
+	}
+}
+
+_futex_broadcast :: proc(f: ^Futex)  {
+	res := _umtx_op(f, UMTX_OP_WAKE, c.ulong(max(i32)), nil, nil)
+
+	if res == -1 {
+		panic("_futex_broadcast failure")
+	}
+}

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

@@ -11,7 +11,7 @@ current_thread_id :: proc "contextless" () -> int {
 //
 // A Mutex must not be copied after first use
 Mutex :: struct {
-	impl: _Mutex `This is a tag`,
+	impl: _Mutex,
 }
 
 // mutex_lock locks m

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

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

+ 7 - 7
core/sync/sync_freebsd.odin

@@ -5,8 +5,8 @@ import "core:intrinsics"
 
 
 current_thread_id :: proc "contextless" () -> int {
-	SYS_GETTID :: 186;
-	return int(intrinsics.syscall(SYS_GETTID));
+	SYS_GETTID :: 186
+	return int(intrinsics.syscall(SYS_GETTID))
 }
 
 
@@ -19,22 +19,22 @@ Semaphore :: struct #align 16 {
 }
 
 semaphore_init :: proc(s: ^Semaphore, initial_count := 0) {
-	assert(unix.sem_init(&s.handle, 0, u32(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 = {};
+	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);
+		assert(unix.sem_post(&s.handle) == 0)
 	}
 }
 
 semaphore_wait_for :: proc(s: ^Semaphore) {
-	assert(unix.sem_wait(&s.handle) == 0);
+	assert(unix.sem_wait(&s.handle) == 0)
 }
 

+ 45 - 45
core/sys/unix/pthread_freebsd.odin

@@ -1,76 +1,76 @@
 //+build freebsd
 package unix
 
-import "core:c";
+import "core:c"
 
-pthread_t :: distinct u64;
-// pthread_t :: struct #align 16 { x: u64 };
+pthread_t :: distinct u64
+// pthread_t :: struct #align 16 { x: u64 }
 
-PTHREAD_COND_T_SIZE :: 8;
+PTHREAD_COND_T_SIZE :: 8
 
-PTHREAD_MUTEXATTR_T_SIZE :: 8;
-PTHREAD_CONDATTR_T_SIZE  :: 8;
-PTHREAD_RWLOCKATTR_T_SIZE  :: 8;
-PTHREAD_BARRIERATTR_T_SIZE :: 8;
+PTHREAD_MUTEXATTR_T_SIZE :: 8
+PTHREAD_CONDATTR_T_SIZE  :: 8
+PTHREAD_RWLOCKATTR_T_SIZE  :: 8
+PTHREAD_BARRIERATTR_T_SIZE :: 8
 
 // WARNING: The sizes of these things are different yet again
 // on non-X86!
 when size_of(int) == 8 {
-	PTHREAD_ATTR_T_SIZE  :: 8;
-	PTHREAD_MUTEX_T_SIZE :: 8;
-	PTHREAD_RWLOCK_T_SIZE  :: 8;
-	PTHREAD_BARRIER_T_SIZE :: 8;
+	PTHREAD_ATTR_T_SIZE  :: 8
+	PTHREAD_MUTEX_T_SIZE :: 8
+	PTHREAD_RWLOCK_T_SIZE  :: 8
+	PTHREAD_BARRIER_T_SIZE :: 8
 } else when size_of(int) == 4 { // TODO
-	PTHREAD_ATTR_T_SIZE  :: 32;
-	PTHREAD_MUTEX_T_SIZE :: 32;
-	PTHREAD_RWLOCK_T_SIZE  :: 44;
-	PTHREAD_BARRIER_T_SIZE :: 20;
+	PTHREAD_ATTR_T_SIZE  :: 32
+	PTHREAD_MUTEX_T_SIZE :: 32
+	PTHREAD_RWLOCK_T_SIZE  :: 44
+	PTHREAD_BARRIER_T_SIZE :: 20
 }
 
 pthread_cond_t :: struct #align 16 {
 	_: [PTHREAD_COND_T_SIZE] c.char,
-};
+}
 pthread_mutex_t :: struct #align 16 {
 	_: [PTHREAD_MUTEX_T_SIZE] c.char,
-};
+}
 pthread_rwlock_t :: struct #align 16 {
 	_: [PTHREAD_RWLOCK_T_SIZE] c.char,
-};
+}
 pthread_barrier_t :: struct #align 16 {
 	_: [PTHREAD_BARRIER_T_SIZE] c.char,
-};
+}
 
 pthread_attr_t :: struct #align 16 {
 	_: [PTHREAD_ATTR_T_SIZE] c.char,
-};
+}
 pthread_condattr_t :: struct #align 16 {
 	_: [PTHREAD_CONDATTR_T_SIZE] c.char,
-};
+}
 pthread_mutexattr_t :: struct #align 16 {
 	_: [PTHREAD_MUTEXATTR_T_SIZE] c.char,
-};
+}
 pthread_rwlockattr_t :: struct #align 16 {
 	_: [PTHREAD_RWLOCKATTR_T_SIZE] c.char,
-};
+}
 pthread_barrierattr_t :: struct #align 16 {
 	_: [PTHREAD_BARRIERATTR_T_SIZE] c.char,
-};
+}
 
-PTHREAD_MUTEX_ERRORCHECK :: 1;
-PTHREAD_MUTEX_RECURSIVE :: 2;
-PTHREAD_MUTEX_NORMAL :: 3;
+PTHREAD_MUTEX_ERRORCHECK :: 1
+PTHREAD_MUTEX_RECURSIVE :: 2
+PTHREAD_MUTEX_NORMAL :: 3
 
 
-PTHREAD_CREATE_JOINABLE :: 0;
-PTHREAD_CREATE_DETACHED :: 1;
-PTHREAD_INHERIT_SCHED :: 4;
-PTHREAD_EXPLICIT_SCHED :: 0;
-PTHREAD_PROCESS_PRIVATE :: 0;
-PTHREAD_PROCESS_SHARED :: 1;
+PTHREAD_CREATE_JOINABLE :: 0
+PTHREAD_CREATE_DETACHED :: 1
+PTHREAD_INHERIT_SCHED :: 4
+PTHREAD_EXPLICIT_SCHED :: 0
+PTHREAD_PROCESS_PRIVATE :: 0
+PTHREAD_PROCESS_SHARED :: 1
 
-SCHED_FIFO  :: 1;
-SCHED_OTHER :: 2;
-SCHED_RR :: 3; // Round robin.
+SCHED_FIFO  :: 1
+SCHED_OTHER :: 2
+SCHED_RR :: 3 // Round robin.
 
 
 sched_param :: struct {
@@ -98,17 +98,17 @@ foreign import "system:pthread"
 foreign pthread {
 	// create named semaphore.
 	// used in process-shared semaphores.
-	sem_open :: proc(name: cstring, flags: c.int) -> ^sem_t ---;
+	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 ---;
+	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() ---;
+	pthread_yield :: proc() ---
 }
 

+ 2 - 0
core/sys/unix/syscalls_linux.odin

@@ -675,6 +675,8 @@ when ODIN_ARCH == .amd64 {
 	SYS_landlock_create_ruleset : uintptr : 444
 	SYS_landlock_add_rule : uintptr : 445
 	SYS_landlock_restrict_self : uintptr : 446
+	
+	SIGCHLD :: 17
 } else when ODIN_ARCH == .i386 {
 	SYS_restart_syscall : uintptr : 0
 	SYS_exit : uintptr : 1

+ 2 - 0
examples/all/all_main.odin

@@ -54,6 +54,7 @@ import base64         "core:encoding/base64"
 import csv            "core:encoding/csv"
 import hxa            "core:encoding/hxa"
 import json           "core:encoding/json"
+import varint         "core:encoding/varint"
 
 import fmt            "core:fmt"
 import hash           "core:hash"
@@ -153,6 +154,7 @@ _ :: base64
 _ :: csv
 _ :: hxa
 _ :: json
+_ :: varint
 _ :: fmt
 _ :: hash
 _ :: image

+ 1 - 4
examples/demo/demo.odin

@@ -94,6 +94,7 @@ the_basics :: proc() {
 		z: f64 // `z` is typed of type `f64` (64-bit floating point number)
 		z = 1  // `1` is an untyped integer literal which can be implicitly converted to `f64`
 				// No need for any suffixes or decimal places like in other languages
+				// (with the exception of negative zero, which must be given as `-0.0`)
 				// CONSTANTS JUST WORK!!!
 
 
@@ -1714,7 +1715,6 @@ deprecated_attribute :: proc() {
 }
 
 range_statements_with_multiple_return_values :: proc() {
-	// IMPORTANT NOTE(bill, 2019-11-02): This feature is subject to be changed/removed
 	fmt.println("\n#range statements with multiple return values")
 	My_Iterator :: struct {
 		index: int,
@@ -2004,7 +2004,6 @@ relative_data_types :: proc() {
 
 or_else_operator :: proc() {
 	fmt.println("\n#'or_else'")
-	// IMPORTANT NOTE: 'or_else' is an experimental feature and subject to change/removal
 	{
 		m: map[string]int
 		i: int
@@ -2035,8 +2034,6 @@ or_else_operator :: proc() {
 
 or_return_operator :: proc() {
 	fmt.println("\n#'or_return'")
-	// IMPORTANT NOTE: 'or_return' is an experimental feature and subject to change/removal
-	//
 	// The concept of 'or_return' will work by popping off the end value in a multiple
 	// valued expression and checking whether it was not 'nil' or 'false', and if so,
 	// set the end return value to value if possible. If the procedure only has one

+ 66 - 7
src/build_settings.cpp

@@ -8,7 +8,7 @@
 // #define DEFAULT_TO_THREADED_CHECKER
 // #endif
 
-enum TargetOsKind {
+enum TargetOsKind : u16 {
 	TargetOs_Invalid,
 
 	TargetOs_windows,
@@ -26,7 +26,7 @@ enum TargetOsKind {
 	TargetOs_COUNT,
 };
 
-enum TargetArchKind {
+enum TargetArchKind : u16 {
 	TargetArch_Invalid,
 
 	TargetArch_amd64,
@@ -38,7 +38,7 @@ enum TargetArchKind {
 	TargetArch_COUNT,
 };
 
-enum TargetEndianKind {
+enum TargetEndianKind : u8 {
 	TargetEndian_Invalid,
 
 	TargetEndian_Little,
@@ -47,6 +47,16 @@ enum TargetEndianKind {
 	TargetEndian_COUNT,
 };
 
+enum TargetABIKind : u16 {
+	TargetABI_Default,
+
+	TargetABI_Win64,
+	TargetABI_SysV,
+
+	TargetABI_COUNT,
+};
+
+
 String target_os_names[TargetOs_COUNT] = {
 	str_lit(""),
 	str_lit("windows"),
@@ -77,6 +87,12 @@ String target_endian_names[TargetEndian_COUNT] = {
 	str_lit("big"),
 };
 
+String target_abi_names[TargetABI_COUNT] = {
+	str_lit(""),
+	str_lit("win64"),
+	str_lit("sysv"),
+};
+
 TargetEndianKind target_endians[TargetArch_COUNT] = {
 	TargetEndian_Invalid,
 	TargetEndian_Little,
@@ -100,6 +116,7 @@ struct TargetMetrics {
 	isize          max_align;
 	String         target_triplet;
 	String         target_data_layout;
+	TargetABIKind  abi;
 };
 
 
@@ -174,6 +191,13 @@ enum ErrorPosStyle {
 	ErrorPosStyle_COUNT
 };
 
+enum RelocMode : u8 {
+	RelocMode_Default,
+	RelocMode_Static,
+	RelocMode_PIC,
+	RelocMode_DynamicNoPIC,
+};
+
 // This stores the information for the specify architecture of this build
 struct BuildContext {
 	// Constants
@@ -185,6 +209,7 @@ struct BuildContext {
 	bool   ODIN_DEBUG;   // Odin in debug mode
 	bool   ODIN_DISABLE_ASSERT; // Whether the default 'assert' et al is disabled in code or not
 	bool   ODIN_DEFAULT_TO_NIL_ALLOCATOR; // Whether the default allocator is a "nil" allocator or not (i.e. it does nothing)
+	bool   ODIN_FOREIGN_ERROR_PROCEDURES;
 
 	ErrorPosStyle ODIN_ERROR_POS_STYLE;
 
@@ -209,6 +234,7 @@ struct BuildContext {
 	String extra_linker_flags;
 	String extra_assembler_flags;
 	String microarch;
+	String target_features;
 	BuildModeKind build_mode;
 	bool   generate_docs;
 	i32    optimization_level;
@@ -254,6 +280,12 @@ struct BuildContext {
 	
 	bool copy_file_contents;
 
+	bool disallow_rtti;
+
+	RelocMode reloc_mode;
+	bool disable_red_zone;
+
+
 	u32 cmd_doc_flags;
 	Array<String> extra_packages;
 
@@ -410,6 +442,16 @@ gb_global TargetMetrics target_wasi_wasm32 = {
 // 	str_lit(""),
 // };
 
+gb_global TargetMetrics target_freestanding_amd64_sysv = {
+	TargetOs_freestanding,
+	TargetArch_amd64,
+	8,
+	16,
+	str_lit("x86_64-pc-none-gnu"),
+	str_lit("e-m:w-i64:64-f80:128-n8:16:32:64-S128"),
+	TargetABI_SysV,
+};
+
 
 
 struct NamedTargetMetrics {
@@ -432,7 +474,8 @@ gb_global NamedTargetMetrics named_targets[] = {
 	{ str_lit("freestanding_wasm32"), &target_freestanding_wasm32 },
 	{ str_lit("wasi_wasm32"),         &target_wasi_wasm32 },
 	{ str_lit("js_wasm32"),           &target_js_wasm32 },
-	// { str_lit("freestanding_wasm64"), &target_freestanding_wasm64 },
+
+	{ str_lit("freestanding_amd64_sysv"), &target_freestanding_amd64_sysv },
 };
 
 NamedTargetMetrics *selected_target_metrics;
@@ -946,7 +989,6 @@ void init_build_context(TargetMetrics *cross_target) {
 		}
 	}
 
-	
 	bc->copy_file_contents = true;
 
 	TargetMetrics *metrics = nullptr;
@@ -1005,6 +1047,21 @@ void init_build_context(TargetMetrics *cross_target) {
 	bc->threaded_checker = true;
 	#endif
 
+	if (bc->disable_red_zone) {
+		if (!!is_arch_wasm() && bc->metrics.os == TargetOs_freestanding) {
+			gb_printf_err("-disable-red-zone is not support for this target");
+			gb_exit(1);
+		}
+	}
+
+	if (bc->metrics.os == TargetOs_freestanding) {
+		bc->no_entry_point = true;
+	} else {
+		if (bc->disallow_rtti) {
+			gb_printf_err("-disallow-rtti is only allowed on freestanding targets\n");
+			gb_exit(1);
+		}
+	}
 
 	// NOTE(zangent): The linker flags to set the build architecture are different
 	// across OSs. It doesn't make sense to allocate extra data on the heap
@@ -1059,14 +1116,14 @@ void init_build_context(TargetMetrics *cross_target) {
 		if (bc->metrics.arch == TargetArch_wasm64) {
 			link_flags = gb_string_appendc(link_flags, "-mwas64 ");
 		}
-		if (bc->metrics.os == TargetOs_freestanding) {
+		if (bc->no_entry_point) {
 			link_flags = gb_string_appendc(link_flags, "--no-entry ");
 		}
 		
 		bc->link_flags = make_string_c(link_flags);
 		
 		// Disallow on wasm
-		build_context.use_separate_modules = false;
+		bc->use_separate_modules = false;
 	} else {
 		gb_printf_err("Compiler Error: Unsupported architecture\n");
 		gb_exit(1);
@@ -1074,6 +1131,8 @@ void init_build_context(TargetMetrics *cross_target) {
 
 	bc->optimization_level = gb_clamp(bc->optimization_level, 0, 3);
 
+
+
 	#undef LINK_FLAG_X64
 	#undef LINK_FLAG_386
 }

+ 13 - 5
src/check_builtin.cpp

@@ -1241,6 +1241,10 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 		if (c->scope->flags&ScopeFlag_Global) {
 			compiler_error("'type_info_of' Cannot be declared within the runtime package due to how the internals of the compiler works");
 		}
+		if (build_context.disallow_rtti) {
+			error(call, "'%.*s' has been disallowed", LIT(builtin_name));
+			return false;
+		}
 
 		// NOTE(bill): The type information may not be setup yet
 		init_core_type_info(c->checker);
@@ -1253,9 +1257,9 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 		Type *t = o.type;
 		if (t == nullptr || t == t_invalid || is_type_asm_proc(o.type) || is_type_polymorphic(t)) {
 			if (is_type_polymorphic(t)) {
-				error(ce->args[0], "Invalid argument for 'type_info_of', unspecialized polymorphic type");
+				error(ce->args[0], "Invalid argument for '%.*s', unspecialized polymorphic type", LIT(builtin_name));
 			} else {
-				error(ce->args[0], "Invalid argument for 'type_info_of'");
+				error(ce->args[0], "Invalid argument for '%.*s'", LIT(builtin_name));
 			}
 			return false;
 		}
@@ -1266,7 +1270,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 		if (is_operand_value(o) && is_type_typeid(t)) {
 			add_package_dependency(c, "runtime", "__type_info_of");
 		} else if (o.mode != Addressing_Type) {
-			error(expr, "Expected a type or typeid for 'type_info_of'");
+			error(expr, "Expected a type or typeid for '%.*s'", LIT(builtin_name));
 			return false;
 		}
 
@@ -1280,6 +1284,10 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 		if (c->scope->flags&ScopeFlag_Global) {
 			compiler_error("'typeid_of' Cannot be declared within the runtime package due to how the internals of the compiler works");
 		}
+		if (build_context.disallow_rtti) {
+			error(call, "'%.*s' has been disallowed", LIT(builtin_name));
+			return false;
+		}
 
 		// NOTE(bill): The type information may not be setup yet
 		init_core_type_info(c->checker);
@@ -1291,7 +1299,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 		}
 		Type *t = o.type;
 		if (t == nullptr || t == t_invalid || is_type_asm_proc(o.type) || is_type_polymorphic(operand->type)) {
-			error(ce->args[0], "Invalid argument for 'typeid_of'");
+			error(ce->args[0], "Invalid argument for '%.*s'", LIT(builtin_name));
 			return false;
 		}
 		t = default_type(t);
@@ -1299,7 +1307,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 		add_type_info_type(c, t);
 
 		if (o.mode != Addressing_Type) {
-			error(expr, "Expected a type for 'typeid_of'");
+			error(expr, "Expected a type for '%.*s'", LIT(builtin_name));
 			return false;
 		}
 

+ 12 - 0
src/check_decl.cpp

@@ -440,6 +440,12 @@ void check_const_decl(CheckerContext *ctx, Entity *e, Ast *type_expr, Ast *init,
 
 	if (type_expr) {
 		e->type = check_type(ctx, type_expr);
+		if (are_types_identical(e->type, t_typeid)) {
+			e->type = nullptr;
+			e->kind = Entity_TypeName;
+			check_type_decl(ctx, e, init, named_type);
+			return;
+		}
 	}
 
 	Operand operand = {};
@@ -1130,6 +1136,10 @@ void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast *type_expr,
 	}
 	ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix);
 
+	if (is_arch_wasm() && e->Variable.thread_local_model.len != 0) {
+		error(e->token, "@(thread_local) is not supported for this target platform");
+	}
+
 	String context_name = str_lit("variable declaration");
 
 	if (type_expr != nullptr) {
@@ -1205,6 +1215,8 @@ void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast *type_expr,
 	Operand o = {};
 	check_expr_with_type_hint(ctx, &o, init_expr, e->type);
 	check_init_variable(ctx, e, &o, str_lit("variable declaration"));
+
+	check_rtti_type_disallowed(e->token, e->type, "A variable declaration is using a type, %s, which has been disallowed");
 }
 
 void check_proc_group_decl(CheckerContext *ctx, Entity *&pg_entity, DeclInfo *d) {

+ 2 - 0
src/check_expr.cpp

@@ -9352,6 +9352,8 @@ ExprKind check_expr_base(CheckerContext *c, Operand *o, Ast *node, Type *type_hi
 	if (o->type != nullptr && is_type_untyped(o->type)) {
 		add_untyped(c, node, o->mode, o->type, o->value);
 	}
+	check_rtti_type_disallowed(node, o->type, "An expression is using a type, %s, which has been disallowed");
+
 	add_type_and_value(c->info, node, o->mode, o->type, o->value);
 	return kind;
 }

+ 4 - 1
src/check_stmt.cpp

@@ -2152,7 +2152,6 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
 					e->state = EntityState_Resolved;
 				}
 				ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix);
-				e->Variable.thread_local_model = ac.thread_local_model;
 
 				if (ac.link_name.len > 0) {
 					e->Variable.link_name = ac.link_name;
@@ -2182,6 +2181,10 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
 					}
 					e->Variable.thread_local_model = ac.thread_local_model;
 				}
+
+				if (is_arch_wasm() && e->Variable.thread_local_model.len != 0) {
+					error(e->token, "@(thread_local) is not supported for this target platform");
+				}
 				
 
 				if (ac.is_static && ac.thread_local_model != "") {

+ 9 - 3
src/check_type.cpp

@@ -2031,10 +2031,14 @@ bool check_procedure_type(CheckerContext *ctx, Type *type, Ast *proc_type_node,
 	if (param_count > 0) {
 		Entity *end = params->Tuple.variables[param_count-1];
 		if (end->flags&EntityFlag_CVarArg) {
-			if (cc == ProcCC_StdCall || cc == ProcCC_CDecl) {
+			switch (cc) {
+			default:
 				type->Proc.c_vararg = true;
-			} else {
+				break;
+			case ProcCC_Odin:
+			case ProcCC_Contextless:
 				error(end->token, "Calling convention does not support #c_vararg");
+				break;
 			}
 		}
 	}
@@ -2170,7 +2174,7 @@ void init_map_entry_type(Type *type) {
 
 	/*
 	struct {
-		hash:  runtime.Map_Hash,
+		hash:  uintptr,
 		next:  int,
 		key:   Key,
 		value: Value,
@@ -3027,5 +3031,7 @@ Type *check_type_expr(CheckerContext *ctx, Ast *e, Type *named_type) {
 	}
 	set_base_type(named_type, type);
 
+	check_rtti_type_disallowed(e, type, "Use of a type, %s, which has been disallowed");
+
 	return type;
 }

+ 66 - 35
src/checker.cpp

@@ -29,6 +29,23 @@ bool is_operand_undef(Operand o) {
 	return o.mode == Addressing_Value && o.type == t_untyped_undef;
 }
 
+bool check_rtti_type_disallowed(Token const &token, Type *type, char const *format) {
+	if (build_context.disallow_rtti && type) {
+		if (is_type_any(type)) {
+			gbString t = type_to_string(type);
+			error(token, format, t);
+			gb_string_free(t);
+			return true;
+		}
+	}
+	return false;
+}
+
+bool check_rtti_type_disallowed(Ast *expr, Type *type, char const *format) {
+	GB_ASSERT(expr != nullptr);
+	return check_rtti_type_disallowed(ast_token(expr), type, format);
+}
+
 void scope_reset(Scope *scope) {
 	if (scope == nullptr) return;
 
@@ -875,7 +892,8 @@ void init_universal(void) {
 
 // Types
 	for (isize i = 0; i < gb_count_of(basic_types); i++) {
-		add_global_type_entity(basic_types[i].Basic.name, &basic_types[i]);
+		String const &name = basic_types[i].Basic.name;
+		add_global_type_entity(name, &basic_types[i]);
 	}
 	add_global_type_entity(str_lit("byte"), &basic_types[Basic_u8]);
 
@@ -977,6 +995,8 @@ void init_universal(void) {
 	add_global_bool_constant("ODIN_USE_SEPARATE_MODULES",     bc->use_separate_modules);
 	add_global_bool_constant("ODIN_TEST",                     bc->command_kind == Command_test);
 	add_global_bool_constant("ODIN_NO_ENTRY_POINT",           bc->no_entry_point);
+	add_global_bool_constant("ODIN_FOREIGN_ERROR_PROCEDURES", bc->ODIN_FOREIGN_ERROR_PROCEDURES);
+	add_global_bool_constant("ODIN_DISALLOW_RTTI",            bc->disallow_rtti);
 
 
 // Builtin Procedures
@@ -1669,6 +1689,10 @@ void add_implicit_entity(CheckerContext *c, Ast *clause, Entity *e) {
 void add_type_info_type(CheckerContext *c, Type *t) {
 	void add_type_info_type_internal(CheckerContext *c, Type *t);
 
+	if (build_context.disallow_rtti) {
+		return;
+	}
+
 	mutex_lock(&c->info->type_info_mutex);
 	add_type_info_type_internal(c, t);
 	mutex_unlock(&c->info->type_info_mutex);
@@ -2181,21 +2205,25 @@ void generate_minimum_dependency_set(Checker *c, Entity *start) {
 	ptr_set_init(&c->info.minimum_dependency_set, heap_allocator(), min_dep_set_cap);
 	ptr_set_init(&c->info.minimum_dependency_type_info_set, heap_allocator());
 
-	String required_runtime_entities[] = {
+#define FORCE_ADD_RUNTIME_ENTITIES(condition, ...) do {                                              \
+	if (condition) {                                                                             \
+		String entities[] = {__VA_ARGS__};                                                   \
+		for (isize i = 0; i < gb_count_of(entities); i++) {                                  \
+			force_add_dependency_entity(c, c->info.runtime_package->scope, entities[i]); \
+		}                                                                                    \
+	}                                                                                            \
+} while (0)
+
+	// required runtime entities
+	FORCE_ADD_RUNTIME_ENTITIES(true,
 		// Odin types
-		str_lit("Type_Info"),
 		str_lit("Source_Code_Location"),
 		str_lit("Context"),
 		str_lit("Allocator"),
 		str_lit("Logger"),
 
-		// Global variables
-		str_lit("args__"),
-		str_lit("type_table"),
-
 		// Odin internal procedures
 		str_lit("__init_context"),
-		str_lit("__type_info_of"),
 		str_lit("cstring_to_string"),
 		str_lit("_cleanup_runtime"),
 
@@ -2228,35 +2256,36 @@ void generate_minimum_dependency_set(Checker *c, Entity *start) {
 		// WASM Specific
 		str_lit("__ashlti3"),
 		str_lit("__multi3"),
-	};
-	for (isize i = 0; i < gb_count_of(required_runtime_entities); i++) {
-		force_add_dependency_entity(c, c->info.runtime_package->scope, required_runtime_entities[i]);
-	}
+	);
 
-	if (build_context.no_crt) {
-		String required_no_crt_entities[] = {
-			// NOTE(bill): Only if these exist
-			str_lit("_tls_index"),
-			str_lit("_fltused"),
-		};
-		for (isize i = 0; i < gb_count_of(required_no_crt_entities); i++) {
-			force_add_dependency_entity(c, c->info.runtime_package->scope, required_no_crt_entities[i]);
-		}
-	}
+	FORCE_ADD_RUNTIME_ENTITIES(!build_context.disallow_rtti,
+		// Odin types
+		str_lit("Type_Info"),
 
-	if (!build_context.no_bounds_check) {
-		String bounds_check_entities[] = {
-			// Bounds checking related procedures
-			str_lit("bounds_check_error"),
-			str_lit("matrix_bounds_check_error"),
-			str_lit("slice_expr_error_hi"),
-			str_lit("slice_expr_error_lo_hi"),
-			str_lit("multi_pointer_slice_expr_error"),
-		};
-		for (isize i = 0; i < gb_count_of(bounds_check_entities); i++) {
-			force_add_dependency_entity(c, c->info.runtime_package->scope, bounds_check_entities[i]);
-		}
-	}
+		// Global variables
+		str_lit("type_table"),
+		str_lit("__type_info_of"),
+	);
+
+	FORCE_ADD_RUNTIME_ENTITIES(!build_context.no_entry_point,
+		// Global variables
+		str_lit("args__"),
+	);
+
+	FORCE_ADD_RUNTIME_ENTITIES((build_context.no_crt && !is_arch_wasm()),
+		// NOTE(bill): Only if these exist
+		str_lit("_tls_index"),
+		str_lit("_fltused"),
+	);
+
+	FORCE_ADD_RUNTIME_ENTITIES(!build_context.no_bounds_check,
+		// Bounds checking related procedures
+		str_lit("bounds_check_error"),
+		str_lit("matrix_bounds_check_error"),
+		str_lit("slice_expr_error_hi"),
+		str_lit("slice_expr_error_lo_hi"),
+		str_lit("multi_pointer_slice_expr_error"),
+	);
 
 	for_array(i, c->info.definitions) {
 		Entity *e = c->info.definitions[i];
@@ -2378,6 +2407,8 @@ void generate_minimum_dependency_set(Checker *c, Entity *start) {
 		start->flags |= EntityFlag_Used;
 		add_dependency_to_set(c, start);
 	}
+
+#undef FORCE_ADD_RUNTIME_ENTITIES
 }
 
 bool is_entity_a_dependency(Entity *e) {

+ 3 - 1
src/llvm_abi.cpp

@@ -1194,8 +1194,10 @@ LB_ABI_INFO(lb_get_abi_info) {
 
 	switch (build_context.metrics.arch) {
 	case TargetArch_amd64:
-		if (build_context.metrics.os == TargetOs_windows) {
+		if (build_context.metrics.os == TargetOs_windows || build_context.metrics.abi == TargetABI_Win64) {
 			return lbAbiAmd64Win64::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention);
+		} else if (build_context.metrics.abi == TargetABI_SysV) {
+			return lbAbiAmd64SysV::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention);
 		} else {
 			return lbAbiAmd64SysV::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention);
 		}

+ 53 - 8
src/llvm_backend.cpp

@@ -624,6 +624,9 @@ struct lbGlobalVariable {
 };
 
 lbProcedure *lb_create_startup_type_info(lbModule *m) {
+	if (build_context.disallow_rtti) {
+		return nullptr;
+	}
 	LLVMPassManagerRef default_function_pass_manager = LLVMCreateFunctionPassManagerForModule(m->mod);
 	lb_populate_function_pass_manager(m, default_function_pass_manager, false, build_context.optimization_level);
 	LLVMFinalizeFunctionPassManager(default_function_pass_manager);
@@ -711,7 +714,9 @@ lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProcedure *start
 
 	lb_begin_procedure_body(p);
 
-	LLVMBuildCall2(p->builder, LLVMGetElementType(lb_type(main_module, startup_type_info->type)), startup_type_info->value, nullptr, 0, "");
+	if (startup_type_info) {
+		LLVMBuildCall2(p->builder, LLVMGetElementType(lb_type(main_module, startup_type_info->type)), startup_type_info->value, nullptr, 0, "");
+	}
 
 	if (objc_names) {
 		LLVMBuildCall2(p->builder, LLVMGetElementType(lb_type(main_module, objc_names->type)), objc_names->value, nullptr, 0, "");
@@ -996,6 +1001,19 @@ String lb_filepath_obj_for_module(lbModule *m) {
 			case TargetOs_essence:
 				ext = STR_LIT(".o");
 				break;
+
+			case TargetOs_freestanding:
+				switch (build_context.metrics.abi) {
+				default:
+				case TargetABI_Default:
+				case TargetABI_SysV:
+					ext = STR_LIT(".o");
+					break;
+				case TargetABI_Win64:
+					ext = STR_LIT(".obj");
+					break;
+				}
+				break;
 			}
 		}
 	}
@@ -1248,6 +1266,8 @@ void lb_generate_code(lbGenerator *gen) {
 	LLVMCodeModel code_mode = LLVMCodeModelDefault;
 	if (is_arch_wasm()) {
 		code_mode = LLVMCodeModelJITDefault;
+	} else if (build_context.metrics.os == TargetOs_freestanding) {
+		code_mode = LLVMCodeModelKernel;
 	}
 
 	char const *host_cpu_name = LLVMGetHostCPUName();
@@ -1270,10 +1290,18 @@ void lb_generate_code(lbGenerator *gen) {
 		// x86-64-v3: (close to Haswell) AVX, AVX2, BMI1, BMI2, F16C, FMA, LZCNT, MOVBE, XSAVE
 		// x86-64-v4: AVX512F, AVX512BW, AVX512CD, AVX512DQ, AVX512VL
 		if (ODIN_LLVM_MINIMUM_VERSION_12) {
-			llvm_cpu = "x86-64-v2";
+			if (build_context.metrics.os == TargetOs_freestanding) {
+				llvm_cpu = "x86-64";
+			} else {
+				llvm_cpu = "x86-64-v2";
+			}
 		}
 	}
 
+	if (build_context.target_features.len != 0) {
+		llvm_features = alloc_cstring(permanent_allocator(), build_context.target_features);
+	}
+
 	// GB_ASSERT_MSG(LLVMTargetHasAsmBackend(target));
 
 	LLVMCodeGenOptLevel code_gen_level = LLVMCodeGenLevelNone;
@@ -1295,9 +1323,22 @@ 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
+	switch (build_context.reloc_mode) {
+	case RelocMode_Default:
+		if (build_context.metrics.os == TargetOs_openbsd) {
+			// Always use PIC for OpenBSD: it defaults to PIE
+			reloc_mode = LLVMRelocPIC;
+		}
+		break;
+	case RelocMode_Static:
+		reloc_mode = LLVMRelocStatic;
+		break;
+	case RelocMode_PIC:
 		reloc_mode = LLVMRelocPIC;
+		break;
+	case RelocMode_DynamicNoPIC:
+		reloc_mode = LLVMRelocDynamicNoPic;
+		break;
 	}
 
 	for_array(i, gen->modules.entries) {
@@ -1366,7 +1407,7 @@ void lb_generate_code(lbGenerator *gen) {
 
 	TIME_SECTION("LLVM Global Variables");
 
-	{
+	if (!build_context.disallow_rtti) {
 		lbModule *m = default_module;
 
 		{ // Add type info data
@@ -1473,9 +1514,8 @@ void lb_generate_code(lbGenerator *gen) {
 			if ((e->scope->flags&ScopeFlag_Init) && name == "main") {
 				GB_ASSERT(e == info->entry_point);
 			}
-			if (e->Procedure.is_export ||
-			    (e->Procedure.link_name.len > 0) ||
-			    ((e->scope->flags&ScopeFlag_File) && e->Procedure.link_name.len > 0)) {
+			if (build_context.command_kind == Command_test &&
+			    (e->Procedure.is_export || e->Procedure.link_name.len > 0)) {
 				String link_name = e->Procedure.link_name;
 				if (e->pkg->kind == Package_Runtime) {
 					if (link_name == "main"           ||
@@ -1699,6 +1739,11 @@ void lb_generate_code(lbGenerator *gen) {
 		}
 	}
 
+	if (build_context.command_kind == Command_test && !already_has_entry_point) {
+		TIME_SECTION("LLVM main");
+		lb_create_main_procedure(default_module, startup_runtime);
+	}
+
 	for_array(j, gen->modules.entries) {
 		lbModule *m = gen->modules.entries[j].value;
 		for_array(i, m->missing_procedures_to_check) {

+ 14 - 3
src/llvm_backend_expr.cpp

@@ -2824,16 +2824,25 @@ lbValue lb_build_unary_and(lbProcedure *p, Ast *expr) {
 						src_tag = lb_emit_load(p, lb_emit_union_tag_ptr(p, v));
 						dst_tag = lb_const_union_tag(p->module, src_type, dst_type);
 					}
+
+
+					isize arg_count = 6;
+					if (build_context.disallow_rtti) {
+						arg_count = 4;
+					}
+
 					lbValue ok = lb_emit_comp(p, Token_CmpEq, src_tag, dst_tag);
-					auto args = array_make<lbValue>(permanent_allocator(), 6);
+					auto args = array_make<lbValue>(permanent_allocator(), arg_count);
 					args[0] = ok;
 
 					args[1] = lb_find_or_add_entity_string(p->module, get_file_path_string(pos.file_id));
 					args[2] = lb_const_int(p->module, t_i32, pos.line);
 					args[3] = lb_const_int(p->module, t_i32, pos.column);
 
-					args[4] = lb_typeid(p->module, src_type);
-					args[5] = lb_typeid(p->module, dst_type);
+					if (!build_context.disallow_rtti) {
+						args[4] = lb_typeid(p->module, src_type);
+						args[5] = lb_typeid(p->module, dst_type);
+					}
 					lb_emit_runtime_call(p, "type_assertion_check", args);
 				}
 
@@ -2846,6 +2855,8 @@ lbValue lb_build_unary_and(lbProcedure *p, Ast *expr) {
 				}
 				lbValue data_ptr = lb_emit_struct_ev(p, v, 0);
 				if ((p->state_flags & StateFlag_no_type_assert) == 0) {
+					GB_ASSERT(!build_context.disallow_rtti);
+
 					lbValue any_id = lb_emit_struct_ev(p, v, 1);
 
 					lbValue id = lb_typeid(p->module, type);

+ 19 - 6
src/llvm_backend_proc.cpp

@@ -135,6 +135,10 @@ lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool ignore_body)
 		lb_add_attribute_to_proc(m, p->value, "naked");
 	}
 
+	if (!entity->Procedure.is_foreign && build_context.disable_red_zone) {
+		lb_add_attribute_to_proc(m, p->value, "noredzone");
+	}
+
 	switch (p->inlining) {
 	case ProcInlining_inline:
 		lb_add_attribute_to_proc(m, p->value, "alwaysinline");
@@ -1402,14 +1406,23 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv,
 
 	case BuiltinProc_read_cycle_counter:
 		{
-			char const *name = "llvm.readcyclecounter";
-			unsigned id = LLVMLookupIntrinsicID(name, gb_strlen(name));
-			GB_ASSERT_MSG(id != 0, "Unable to find %s", name);
-			LLVMValueRef ip = LLVMGetIntrinsicDeclaration(p->module->mod, id, nullptr, 0);
-
 			lbValue res = {};
-			res.value = LLVMBuildCall(p->builder, ip, nullptr, 0, "");
 			res.type = tv.type;
+
+			if (build_context.metrics.arch == TargetArch_arm64) {
+				LLVMTypeRef func_type = LLVMFunctionType(LLVMInt64TypeInContext(p->module->ctx), nullptr, 0, false);
+				bool has_side_effects = false;
+				LLVMValueRef the_asm = llvm_get_inline_asm(func_type, str_lit("mrs x9, cntvct_el0"), str_lit("=r"), has_side_effects);
+				GB_ASSERT(the_asm != nullptr);
+				res.value = LLVMBuildCall2(p->builder, func_type, the_asm, nullptr, 0, "");
+			} else {
+				char const *name = "llvm.readcyclecounter";
+				unsigned id = LLVMLookupIntrinsicID(name, gb_strlen(name));
+				GB_ASSERT_MSG(id != 0, "Unable to find %s", name);
+				LLVMValueRef ip = LLVMGetIntrinsicDeclaration(p->module->mod, id, nullptr, 0);
+
+				res.value = LLVMBuildCall(p->builder, ip, nullptr, 0, "");
+			}
 			return res;
 		}
 

+ 10 - 0
src/llvm_backend_type.cpp

@@ -14,6 +14,8 @@ isize lb_type_info_index(CheckerInfo *info, Type *type, bool err_on_not_found=tr
 }
 
 lbValue lb_typeid(lbModule *m, Type *type) {
+	GB_ASSERT(!build_context.disallow_rtti);
+
 	type = default_type(type);
 
 	u64 id = cast(u64)lb_type_info_index(m->info, type);
@@ -88,6 +90,8 @@ lbValue lb_typeid(lbModule *m, Type *type) {
 }
 
 lbValue lb_type_info(lbModule *m, Type *type) {
+	GB_ASSERT(!build_context.disallow_rtti);
+
 	type = default_type(type);
 
 	isize index = lb_type_info_index(m->info, type);
@@ -106,6 +110,8 @@ lbValue lb_type_info(lbModule *m, Type *type) {
 }
 
 lbValue lb_get_type_info_ptr(lbModule *m, Type *type) {
+	GB_ASSERT(!build_context.disallow_rtti);
+
 	i32 index = cast(i32)lb_type_info_index(m->info, type);
 	GB_ASSERT(index >= 0);
 	// gb_printf_err("%d %s\n", index, type_to_string(type));
@@ -155,6 +161,10 @@ lbValue lb_type_info_member_tags_offset(lbProcedure *p, isize count) {
 
 
 void lb_setup_type_info_data(lbProcedure *p) { // NOTE(bill): Setup type_info data
+	if (build_context.disallow_rtti) {
+		return;
+	}
+
 	lbModule *m = p->module;
 	CheckerInfo *info = m->info;
 	

+ 23 - 9
src/llvm_backend_utility.cpp

@@ -676,17 +676,24 @@ lbValue lb_emit_union_cast(lbProcedure *p, lbValue value, Type *type, TokenPos p
 		// NOTE(bill): Panic on invalid conversion
 		Type *dst_type = tuple->Tuple.variables[0]->type;
 
+		isize arg_count = 7;
+		if (build_context.disallow_rtti) {
+			arg_count = 4;
+		}
+
 		lbValue ok = lb_emit_load(p, lb_emit_struct_ep(p, v.addr, 1));
-		auto args = array_make<lbValue>(permanent_allocator(), 7);
+		auto args = array_make<lbValue>(permanent_allocator(), arg_count);
 		args[0] = ok;
 
 		args[1] = lb_const_string(m, get_file_path_string(pos.file_id));
 		args[2] = lb_const_int(m, t_i32, pos.line);
 		args[3] = lb_const_int(m, t_i32, pos.column);
 
-		args[4] = lb_typeid(m, src_type);
-		args[5] = lb_typeid(m, dst_type);
-		args[6] = lb_emit_conv(p, value_, t_rawptr);
+		if (!build_context.disallow_rtti) {
+			args[4] = lb_typeid(m, src_type);
+			args[5] = lb_typeid(m, dst_type);
+			args[6] = lb_emit_conv(p, value_, t_rawptr);
+		}
 		lb_emit_runtime_call(p, "type_assertion_check2", args);
 
 		return lb_emit_load(p, lb_emit_struct_ep(p, v.addr, 0));
@@ -744,16 +751,23 @@ lbAddr lb_emit_any_cast_addr(lbProcedure *p, lbValue value, Type *type, TokenPos
 	if (!is_tuple) {
 		// NOTE(bill): Panic on invalid conversion
 		lbValue ok = lb_emit_load(p, lb_emit_struct_ep(p, v.addr, 1));
-		auto args = array_make<lbValue>(permanent_allocator(), 7);
+
+		isize arg_count = 7;
+		if (build_context.disallow_rtti) {
+			arg_count = 4;
+		}
+		auto args = array_make<lbValue>(permanent_allocator(), arg_count);
 		args[0] = ok;
 
 		args[1] = lb_const_string(m, get_file_path_string(pos.file_id));
 		args[2] = lb_const_int(m, t_i32, pos.line);
 		args[3] = lb_const_int(m, t_i32, pos.column);
 
-		args[4] = any_typeid;
-		args[5] = dst_typeid;
-		args[6] = lb_emit_struct_ev(p, value, 0);;
+		if (!build_context.disallow_rtti) {
+			args[4] = any_typeid;
+			args[5] = dst_typeid;
+			args[6] = lb_emit_struct_ev(p, value, 0);
+		}
 		lb_emit_runtime_call(p, "type_assertion_check2", args);
 
 		return lb_addr(lb_emit_struct_ep(p, v.addr, 0));
@@ -1779,7 +1793,7 @@ LLVMValueRef llvm_get_inline_asm(LLVMTypeRef func_type, String const &str, Strin
 	return LLVMGetInlineAsm(func_type,
 		cast(char *)str.text, cast(size_t)str.len,
 		cast(char *)clobbers.text, cast(size_t)clobbers.len,
-		/*HasSideEffects*/true, /*IsAlignStack*/false,
+		has_side_effects, is_align_stack,
 		dialect
 	#if LLVM_VERSION_MAJOR >= 13 
 		, /*CanThrow*/false

+ 71 - 1
src/main.cpp

@@ -632,6 +632,10 @@ enum BuildFlagKind {
 	BuildFlag_ExtraLinkerFlags,
 	BuildFlag_ExtraAssemblerFlags,
 	BuildFlag_Microarch,
+	BuildFlag_TargetFeatures,
+
+	BuildFlag_RelocMode,
+	BuildFlag_DisableRedZone,
 
 	BuildFlag_TestName,
 
@@ -640,6 +644,8 @@ enum BuildFlagKind {
 	BuildFlag_InsertSemicolon,
 	BuildFlag_StrictStyle,
 	BuildFlag_StrictStyleInitOnly,
+	BuildFlag_ForeignErrorProcedures,
+	BuildFlag_DisallowRTTI,
 
 	BuildFlag_Compact,
 	BuildFlag_GlobalDefinitions,
@@ -784,7 +790,11 @@ bool parse_build_flags(Array<String> args) {
 	add_flag(&build_flags, BuildFlag_IgnoreUnknownAttributes, str_lit("ignore-unknown-attributes"), BuildFlagParam_None, Command__does_check);
 	add_flag(&build_flags, BuildFlag_ExtraLinkerFlags,    str_lit("extra-linker-flags"),            BuildFlagParam_String, Command__does_build);
 	add_flag(&build_flags, BuildFlag_ExtraAssemblerFlags, str_lit("extra-assembler-flags"),         BuildFlagParam_String, Command__does_build);
-	add_flag(&build_flags, BuildFlag_Microarch,         str_lit("microarch"),                       BuildFlagParam_String, Command__does_build);
+	add_flag(&build_flags, BuildFlag_Microarch,           str_lit("microarch"),                       BuildFlagParam_String, Command__does_build);
+	add_flag(&build_flags, BuildFlag_TargetFeatures,      str_lit("target-features"),                 BuildFlagParam_String, Command__does_build);
+
+	add_flag(&build_flags, BuildFlag_RelocMode,        str_lit("reloc-mode"),                       BuildFlagParam_String, Command__does_build);
+	add_flag(&build_flags, BuildFlag_DisableRedZone,   str_lit("disable-red-zone"),                 BuildFlagParam_None, Command__does_build);
 
 	add_flag(&build_flags, BuildFlag_TestName,         str_lit("test-name"),                       BuildFlagParam_String, Command_test);
 
@@ -793,10 +803,16 @@ bool parse_build_flags(Array<String> args) {
 	add_flag(&build_flags, BuildFlag_InsertSemicolon,       str_lit("insert-semicolon"),         BuildFlagParam_None, Command__does_check);
 	add_flag(&build_flags, BuildFlag_StrictStyle,           str_lit("strict-style"),             BuildFlagParam_None, Command__does_check);
 	add_flag(&build_flags, BuildFlag_StrictStyleInitOnly,   str_lit("strict-style-init-only"),   BuildFlagParam_None, Command__does_check);
+	add_flag(&build_flags, BuildFlag_ForeignErrorProcedures, str_lit("foreign-error-procedures"), BuildFlagParam_None, Command__does_check);
+
+	add_flag(&build_flags, BuildFlag_DisallowRTTI,            str_lit("disallow-rtti"),              BuildFlagParam_None, Command__does_check);
+
+
 	add_flag(&build_flags, BuildFlag_Compact,           str_lit("compact"),            BuildFlagParam_None, Command_query);
 	add_flag(&build_flags, BuildFlag_GlobalDefinitions, str_lit("global-definitions"), BuildFlagParam_None, Command_query);
 	add_flag(&build_flags, BuildFlag_GoToDefinitions,   str_lit("go-to-definitions"),  BuildFlagParam_None, Command_query);
 
+
 	add_flag(&build_flags, BuildFlag_Short,         str_lit("short"),        BuildFlagParam_None, Command_doc);
 	add_flag(&build_flags, BuildFlag_AllPackages,   str_lit("all-packages"), BuildFlagParam_None, Command_doc);
 	add_flag(&build_flags, BuildFlag_DocFormat,     str_lit("doc-format"),   BuildFlagParam_None, Command_doc);
@@ -1343,6 +1359,37 @@ bool parse_build_flags(Array<String> args) {
 							string_to_lower(&build_context.microarch);
 							break;
 						}
+						case BuildFlag_TargetFeatures: {
+							GB_ASSERT(value.kind == ExactValue_String);
+							build_context.target_features = value.value_string;
+							string_to_lower(&build_context.target_features);
+							break;
+						}
+						case BuildFlag_RelocMode: {
+							GB_ASSERT(value.kind == ExactValue_String);
+							String v = value.value_string;
+							if (v == "default") {
+								build_context.reloc_mode = RelocMode_Default;
+							} else if (v == "static") {
+								build_context.reloc_mode = RelocMode_Static;
+							} else if (v == "pic") {
+								build_context.reloc_mode = RelocMode_PIC;
+							} else if (v == "dynamic-no-pic") {
+								build_context.reloc_mode = RelocMode_DynamicNoPIC;
+							} else {
+								gb_printf_err("-reloc-mode flag expected one of the following\n");
+								gb_printf_err("\tdefault\n");
+								gb_printf_err("\tstatic\n");
+								gb_printf_err("\tpic\n");
+								gb_printf_err("\tdynamic-no-pic\n");
+								bad_flags = true;
+							}
+
+							break;
+						}
+						case BuildFlag_DisableRedZone:
+							build_context.disable_red_zone = true;
+							break;
 						case BuildFlag_TestName: {
 							GB_ASSERT(value.kind == ExactValue_String);
 							{
@@ -1361,9 +1408,15 @@ bool parse_build_flags(Array<String> args) {
 						case BuildFlag_DisallowDo:
 							build_context.disallow_do = true;
 							break;
+						case BuildFlag_DisallowRTTI:
+							build_context.disallow_rtti = true;
+							break;
 						case BuildFlag_DefaultToNilAllocator:
 							build_context.ODIN_DEFAULT_TO_NIL_ALLOCATOR = true;
 							break;
+						case BuildFlag_ForeignErrorProcedures:
+							build_context.ODIN_FOREIGN_ERROR_PROCEDURES = true;
+							break;
 						case BuildFlag_InsertSemicolon: {
 							gb_printf_err("-insert-semicolon flag is not required any more\n");
 							bad_flags = true;
@@ -2062,6 +2115,18 @@ void print_show_help(String const arg0, String const &command) {
 		print_usage_line(3, "-microarch:sandybridge");
 		print_usage_line(3, "-microarch:native");
 		print_usage_line(0, "");
+
+		print_usage_line(1, "-reloc-mode:<string>");
+		print_usage_line(2, "Specifies the reloc mode");
+		print_usage_line(2, "Options:");
+		print_usage_line(3, "default");
+		print_usage_line(3, "static");
+		print_usage_line(3, "pic");
+		print_usage_line(3, "dynamic-no-pic");
+		print_usage_line(0, "");
+
+		print_usage_line(1, "-disable-red-zone");
+		print_usage_line(2, "Disable red zone on a supported freestanding target");
 	}
 
 	if (check) {
@@ -2092,6 +2157,11 @@ void print_show_help(String const arg0, String const &command) {
 		print_usage_line(1, "-verbose-errors");
 		print_usage_line(2, "Prints verbose error messages showing the code on that line and the location in that line");
 		print_usage_line(0, "");
+
+		print_usage_line(1, "-foreign-error-procedures");
+		print_usage_line(2, "States that the error procedues used in the runtime are defined in a separate translation unit");
+		print_usage_line(0, "");
+
 	}
 
 	if (run_or_build) {

+ 21 - 29
src/parser.cpp

@@ -1577,7 +1577,7 @@ void assign_removal_flag_to_semicolon(AstFile *f) {
 	}
 }
 
-void expect_semicolon(AstFile *f, Ast *s) {
+void expect_semicolon(AstFile *f) {
 	Token prev_token = {};
 
 	if (allow_token(f, Token_Semicolon)) {
@@ -1602,17 +1602,17 @@ void expect_semicolon(AstFile *f, Ast *s) {
 	if (f->curr_token.kind == Token_EOF) {
 		return;
 	}
-
-	if (s != nullptr) {
-		return;
-	} 
 	switch (f->curr_token.kind) {
 	case Token_EOF:
 		return;
 	}
-	String p = token_to_string(f->curr_token);
-	syntax_error(prev_token, "Expected ';', got %.*s", LIT(p));
-	fix_advance_to_next_stmt(f);
+
+	if (f->curr_token.pos.line == f->prev_token.pos.line) {
+		String p = token_to_string(f->curr_token);
+		prev_token.pos = token_pos_end(prev_token);
+		syntax_error(prev_token, "Expected ';', got %.*s", LIT(p));
+		fix_advance_to_next_stmt(f);
+	}
 }
 
 
@@ -3181,7 +3181,7 @@ Ast *parse_foreign_block(AstFile *f, Token token) {
 	Ast *body = ast_block_stmt(f, decls, open, close);
 
 	Ast *decl = ast_foreign_block_decl(f, token, foreign_library, body, docs);
-	expect_semicolon(f, decl);
+	expect_semicolon(f);
 	return decl;
 }
 
@@ -3227,15 +3227,11 @@ Ast *parse_value_decl(AstFile *f, Array<Ast *> names, CommentGroup *docs) {
 	}
 
 	if (f->expr_level >= 0) {
-		Ast *end = nullptr;
-		if (!is_mutable && values.count > 0) {
-			end = values[values.count-1];
-		}
 		if (f->curr_token.kind == Token_CloseBrace &&
 		    f->curr_token.pos.line == f->prev_token.pos.line) {
 
 		} else {
-			expect_semicolon(f, end);
+			expect_semicolon(f);
 		}
 	}
 
@@ -4143,11 +4139,7 @@ Ast *parse_return_stmt(AstFile *f) {
 		advance_token(f);
 	}
 
-	Ast *end = nullptr;
-	if (results.count > 0) {
-		end = results[results.count-1];
-	}
-	expect_semicolon(f, end);
+	expect_semicolon(f);
 	return ast_return_stmt(f, token, results);
 }
 
@@ -4398,7 +4390,7 @@ Ast *parse_import_decl(AstFile *f, ImportDeclKind kind) {
 		syntax_error(import_name, "'using import' is not allowed, please use the import name explicitly");
 	}
 
-	expect_semicolon(f, s);
+	expect_semicolon(f);
 	return s;
 }
 
@@ -4456,7 +4448,7 @@ Ast *parse_foreign_decl(AstFile *f) {
 		} else {
 			s = ast_foreign_import_decl(f, token, filepaths, lib_name, docs, f->line_comment);
 		}
-		expect_semicolon(f, s);
+		expect_semicolon(f);
 		return s;
 	}
 	}
@@ -4595,7 +4587,7 @@ Ast *parse_stmt(AstFile *f) {
 	case Token_Not:
 	case Token_And:
 		s = parse_simple_stmt(f, StmtAllowFlag_Label);
-		expect_semicolon(f, s);
+		expect_semicolon(f);
 		return s;
 
 
@@ -4623,7 +4615,7 @@ Ast *parse_stmt(AstFile *f) {
 			label = parse_ident(f);
 		}
 		s = ast_branch_stmt(f, token, label);
-		expect_semicolon(f, s);
+		expect_semicolon(f);
 		return s;
 	}
 
@@ -4638,12 +4630,12 @@ Ast *parse_stmt(AstFile *f) {
 		Array<Ast *> list = parse_lhs_expr_list(f);
 		if (list.count == 0) {
 			syntax_error(token, "Illegal use of 'using' statement");
-			expect_semicolon(f, nullptr);
+			expect_semicolon(f);
 			return ast_bad_stmt(f, token, f->curr_token);
 		}
 
 		if (f->curr_token.kind != Token_Colon) {
-			expect_semicolon(f, list[list.count-1]);
+			expect_semicolon(f);
 			return ast_using_stmt(f, token, list);
 		}
 		expect_token_after(f, Token_Colon, "identifier list");
@@ -4700,13 +4692,13 @@ Ast *parse_stmt(AstFile *f) {
 		} else if (tag == "assert" || tag == "panic") {
 			Ast *t = ast_basic_directive(f, hash_token, name);
 			Ast *stmt = ast_expr_stmt(f, parse_call_expr(f, t));
-			expect_semicolon(f, stmt);
+			expect_semicolon(f);
 			return stmt;
 		} else if (name.string == "force_inline" ||
 		           name.string == "force_no_inline") {
 			Ast *expr = parse_force_inlining_operand(f, name);
 			Ast *stmt =  ast_expr_stmt(f, expr);
-			expect_semicolon(f, stmt);
+			expect_semicolon(f);
 			return stmt;
 		} else if (tag == "unroll") {
 			return parse_unrolled_for_loop(f, name);
@@ -4728,7 +4720,7 @@ Ast *parse_stmt(AstFile *f) {
 
 	case Token_Semicolon:
 		s = ast_empty_stmt(f, token);
-		expect_semicolon(f, nullptr);
+		expect_semicolon(f);
 		return s;
 	}
 
@@ -5586,7 +5578,7 @@ bool parse_file(Parser *p, AstFile *f) {
 	}
 
 	Ast *pd = ast_package_decl(f, f->package_token, package_name, docs, f->line_comment);
-	expect_semicolon(f, pd);
+	expect_semicolon(f);
 	f->pkg_decl = pd;
 
 	if (f->error_count == 0) {

+ 0 - 8
src/string.cpp

@@ -10,10 +10,6 @@ struct String {
 	u8 *  text;
 	isize len;
 
-	// u8 &operator[](isize i) {
-	// 	GB_ASSERT_MSG(0 <= i && i < len, "[%td]", i);
-	// 	return text[i];
-	// }
 	u8 const &operator[](isize i) const {
 		GB_ASSERT_MSG(0 <= i && i < len, "[%td]", i);
 		return text[i];
@@ -33,10 +29,6 @@ struct String {
 struct String16 {
 	wchar_t *text;
 	isize    len;
-	wchar_t &operator[](isize i) {
-		GB_ASSERT_MSG(0 <= i && i < len, "[%td]", i);
-		return text[i];
-	}
 	wchar_t const &operator[](isize i) const {
 		GB_ASSERT_MSG(0 <= i && i < len, "[%td]", i);
 		return text[i];

+ 40 - 0
tests/common/common.odin

@@ -0,0 +1,40 @@
+// Boilerplate for tests
+package common
+
+import "core:testing"
+import "core:fmt"
+import "core:os"
+
+TEST_count := 0
+TEST_fail  := 0
+
+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] FAIL %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)
+	}
+}
+
+report :: proc(t: ^testing.T) {
+	if TEST_fail > 0 {
+		if TEST_fail > 1 {
+			fmt.printf("%v/%v tests successful, %v tests failed.\n", TEST_count - TEST_fail, TEST_count, TEST_fail)
+		} else {
+			fmt.printf("%v/%v tests successful, 1 test failed.\n", TEST_count - TEST_fail, TEST_count)
+		}
+		os.exit(1)
+	} else {
+		fmt.printf("%v/%v tests successful.\n", TEST_count, TEST_count)
+	}
+}

+ 14 - 0
tests/core/Makefile

@@ -1,7 +1,12 @@
 ODIN=../../odin
 PYTHON=$(shell which python3)
 
+<<<<<<< HEAD
 all: download_test_assets image_test compress_test strings_test hash_test crypto_test noise_test encoding_test os2_test
+=======
+all: download_test_assets image_test compress_test strings_test hash_test crypto_test noise_test encoding_test \
+	 math_test linalg_glsl_math_test
+>>>>>>> upstream/master
 
 download_test_assets:
 	$(PYTHON) download_assets.py
@@ -30,3 +35,12 @@ os2_test:
 encoding_test:
 	$(ODIN) run encoding/json -out=test_json
 	$(ODIN) run encoding/varint -out=test_varint
+<<<<<<< HEAD
+=======
+
+math_test:
+	$(ODIN) run math/test_core_math.odin -out=test_core_math -collection:tests=..
+
+linalg_glsl_math_test:
+	$(ODIN) run math/linalg/glsl/test_linalg_glsl_math.odin -out=test_linalg_glsl_math -collection:tests=..
+>>>>>>> upstream/master

+ 85 - 0
tests/core/math/linalg/glsl/test_linalg_glsl_math.odin

@@ -0,0 +1,85 @@
+// Tests "linalg_glsl_math.odin" in "core:math/linalg/glsl".
+// Must be run with `-collection:tests=` flag, e.g.
+// ./odin run tests/core/math/linalg/glsl/test_linalg_glsl_math.odin -collection:tests=./tests
+package test_core_math_linalg_glsl_math
+
+import glsl "core:math/linalg/glsl"
+
+import "core:fmt"
+import "core:math"
+import "core:testing"
+import tc "tests:common"
+
+main :: proc() {
+
+    t := testing.T{}
+
+	test_fract_f32(&t)
+	test_fract_f64(&t)
+
+	tc.report(&t)
+}
+
+@test
+test_fract_f32 :: proc(t: ^testing.T) {
+
+	using math
+
+	r: f32
+
+	Datum :: struct {
+		i: int,
+		v: f32,
+		e: f32,
+	}
+	@static data := []Datum{
+		{ 0, 10.5, 0.5 }, // Issue #1574 fract in linalg/glm is broken
+		{ 1, -10.5, -0.5 },
+		{ 2, F32_MIN, F32_MIN }, // 0x1p-126
+		{ 3, -F32_MIN, -F32_MIN },
+		{ 4, 0.0, 0.0 },
+		{ 5, -0.0, -0.0 },
+		{ 6, 1, 0.0 },
+		{ 7, -1, -0.0 },
+		{ 8, 0h3F80_0001, 0h3400_0000 }, // 0x1.000002p+0, 0x1p-23
+		{ 9, -0h3F80_0001, -0h3400_0000 },
+	}
+
+	for d, i in data {
+		assert(i == d.i)
+		r = glsl.fract(d.v)
+		tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%v (%h)) -> %v (%h) != %v", i, #procedure, d.v, d.v, r, r, d.e))
+	}
+}
+
+@test
+test_fract_f64 :: proc(t: ^testing.T) {
+
+	using math
+
+	r: f64
+
+	Datum :: struct {
+		i: int,
+		v: f64,
+		e: f64,
+	}
+	@static data := []Datum{
+		{ 0, 10.5, 0.5 }, // Issue #1574 fract in linalg/glm is broken
+		{ 1, -10.5, -0.5 },
+		{ 2, F64_MIN, F64_MIN }, // 0x1p-1022
+		{ 3, -F64_MIN, -F64_MIN },
+		{ 4, 0.0, 0.0 },
+		{ 5, -0.0, -0.0 },
+		{ 6, 1, 0.0 },
+		{ 7, -1, -0.0 },
+		{ 8, 0h3FF0_0000_0000_0001, 0h3CB0_0000_0000_0000 }, // 0x1.0000000000001p+0, 0x1p-52
+		{ 9, -0h3FF0_0000_0000_0001, -0h3CB0_0000_0000_0000 },
+	}
+
+	for d, i in data {
+		assert(i == d.i)
+		r = glsl.fract(d.v)
+		tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%v (%h)) -> %v (%h) != %v", i, #procedure, d.v, d.v, r, r, d.e))
+	}
+}

+ 310 - 0
tests/core/math/test_core_math.odin

@@ -0,0 +1,310 @@
+// Tests "math.odin" in "core:math".
+// Must be run with `-collection:tests=` flag, e.g.
+// ./odin run tests/core/math/test_core_math.odin -collection:tests=./tests
+package test_core_math
+
+import "core:fmt"
+import "core:math"
+import "core:testing"
+import tc "tests:common"
+
+main :: proc() {
+    t := testing.T{}
+
+	test_classify_f16(&t)
+	test_classify_f32(&t)
+	test_classify_f64(&t)
+
+	test_trunc_f16(&t)
+	test_trunc_f32(&t)
+	test_trunc_f64(&t)
+
+	tc.report(&t)
+}
+
+@test
+test_classify_f16 :: proc(t: ^testing.T) {
+
+	using math
+	using Float_Class
+
+	r: Float_Class
+
+	Datum :: struct {
+		i: int,
+		v: f16,
+		e: math.Float_Class,
+	}
+	@static data := []Datum{
+		{ 0, 1.2, Normal },
+		{ 1, 0h0001, Subnormal },
+		{ 2, 0.0, Zero },
+		{ 3, -0.0, Neg_Zero },
+		{ 4, SNAN_F16, NaN },
+		{ 5, QNAN_F16, NaN },
+		{ 6, INF_F16, Inf },
+		{ 7, NEG_INF_F16, Neg_Inf },
+	}
+
+	for d, i in data {
+		assert(i == d.i)
+		r = classify_f16(d.v)
+		tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %v != %v", i, #procedure, d.v, r, d.e))
+	}
+
+	/* Check all subnormals (exponent 0, 10-bit significand non-zero) */
+	for i :u16 = 1; i < 0x400; i += 1 {
+		v :f16 = transmute(f16)i
+		r = classify_f16(v)
+		e :Float_Class: Subnormal
+		tc.expect(t, r == e, fmt.tprintf("i:%d %s(%h) -> %v != %v", i, #procedure, v, r, e))
+	}
+}
+
+@test
+test_classify_f32 :: proc(t: ^testing.T) {
+
+	using math
+	using Float_Class
+
+	r: Float_Class
+
+	Datum :: struct {
+		i: int,
+		v: f32,
+		e: math.Float_Class,
+	}
+	@static data := []Datum{
+		{ 0, 1.2, Normal },
+		{ 1, 0h0000_0001, Subnormal },
+		{ 2, 0.0, Zero },
+		{ 3, -0.0, Neg_Zero },
+		{ 4, SNAN_F32, NaN },
+		{ 5, QNAN_F32, NaN },
+		{ 6, INF_F32, Inf },
+		{ 7, NEG_INF_F32, Neg_Inf },
+	}
+
+	for d, i in data {
+		assert(i == d.i)
+		r = classify_f32(d.v)
+		tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %v != %v", i, #procedure, d.v, r, d.e))
+	}
+}
+
+@test
+test_classify_f64 :: proc(t: ^testing.T) {
+
+	using math
+	using Float_Class
+
+	r: Float_Class
+
+	Datum :: struct {
+		i: int,
+		v: f64,
+		e: math.Float_Class,
+	}
+	@static data := []Datum{
+		{ 0, 1.2, Normal },
+		{ 1, 0h0000_0000_0000_0001, Subnormal },
+		{ 2, 0.0, Zero },
+		{ 3, -0.0, Neg_Zero },
+		{ 4, SNAN_F64, NaN },
+		{ 5, QNAN_F64, NaN },
+		{ 6, INF_F64, Inf },
+		{ 7, NEG_INF_F64, Neg_Inf },
+	}
+
+	for d, i in data {
+		assert(i == d.i)
+		r = classify_f64(d.v)
+		tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %v != %v", i, #procedure, d.v, r, d.e))
+	}
+}
+
+@test
+test_trunc_f16 :: proc(t: ^testing.T) {
+
+	using math
+
+	r, v: f16
+
+	Datum :: struct {
+		i: int,
+		v: f16,
+		e: f16,
+	}
+	@static data := []Datum{
+		{ 0, 10.5, 10 }, // Issue #1574 fract in linalg/glm is broken
+		{ 1, -10.5, -10 },
+
+		{ 2, F16_MAX, F16_MAX },
+		{ 3, -F16_MAX, -F16_MAX },
+		{ 4, F16_MIN, 0.0 },
+		{ 5, -F16_MIN, -0.0 },
+		{ 6, 0.0, 0.0 },
+		{ 7, -0.0, -0.0 },
+		{ 8, 1, 1 },
+		{ 9, -1, -1 },
+		{ 10, INF_F16, INF_F16 },
+		{ 11, NEG_INF_F16, NEG_INF_F16 },
+
+		/* From https://en.wikipedia.org/wiki/Half-precision_floating-point_format */
+		{ 12, 0h3C01, 1 }, // 0x1.004p+0 (smallest > 1)
+		{ 13, -0h3C01, -1 },
+		{ 14, 0h3BFF, 0.0 }, // 0x1.ffcp-1 (largest < 1)
+		{ 15, -0h3BFF, -0.0 },
+		{ 16, 0h0001, 0.0 }, // 0x0.004p-14 (smallest subnormal)
+		{ 17, -0h0001, -0.0 },
+		{ 18, 0h03FF, 0.0 }, // 0x0.ffcp-14 (largest subnormal)
+		{ 19, -0h03FF, -0.0 },
+
+		{ 20, 0hC809, -8 }, // -0x1.024p+3
+		{ 21, 0h4458, 4 }, // 0x1.16p+2
+	}
+
+	for d, i in data {
+		assert(i == d.i)
+		r = trunc_f16(d.v)
+		tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e))
+	}
+
+	v = SNAN_F16
+	r = trunc_f16(v)
+	tc.expect(t, is_nan_f16(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
+
+	v = QNAN_F16
+	r = trunc_f16(v)
+	tc.expect(t, is_nan_f16(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
+}
+
+@test
+test_trunc_f32 :: proc(t: ^testing.T) {
+
+	using math
+
+	r, v: f32
+
+	Datum :: struct {
+		i: int,
+		v: f32,
+		e: f32,
+	}
+	@static data := []Datum{
+		{ 0, 10.5, 10 }, // Issue #1574 fract in linalg/glm is broken
+		{ 1, -10.5, -10 },
+
+		{ 2, F32_MAX, F32_MAX },
+		{ 3, -F32_MAX, -F32_MAX },
+		{ 4, F32_MIN, 0.0 },
+		{ 5, -F32_MIN, -0.0 },
+		{ 6, 0.0, 0.0 },
+		{ 7, -0.0, -0.0 },
+		{ 8, 1, 1 },
+		{ 9, -1, -1 },
+		{ 10, INF_F32, INF_F32 },
+		{ 11, NEG_INF_F32, NEG_INF_F32 },
+
+		/* From https://en.wikipedia.org/wiki/Single-precision_floating-point_format */
+		{ 12, 0h3F80_0001, 1 }, // 0x1.000002p+0 (smallest > 1)
+		{ 13, -0h3F80_0001, -1 },
+		{ 14, 0h3F7F_FFFF, 0.0 }, // 0x1.fffffep-1 (largest < 1)
+		{ 15, -0h3F7F_FFFF, -0.0 },
+		{ 16, 0h0000_0001, 0.0 }, // 0x0.000002p-126 (smallest subnormal)
+		{ 17, -0h0000_0001, -0.0 },
+		{ 18, 0h007F_FFFF, 0.0 }, // 0x0.fffffep-126 (largest subnormal)
+		{ 19, -0h007F_FFFF, -0.0 },
+
+		/* From libc-test src/math/sanity/truncf.h */
+		{ 20, 0hC101_11D0, -8 }, // -0x1.0223ap+3
+		{ 21, 0h408B_0C34, 4 }, // 0x1.161868p+2
+		{ 22, 0hC106_1A5A, -8 }, // -0x1.0c34b4p+3
+		{ 23, 0hC0D1_0378, -6 }, // -0x1.a206fp+2
+		{ 24, 0h4114_45DE, 9 }, // 0x1.288bbcp+3
+		{ 25, 0h3F29_77E8, 0.0 }, // 0x1.52efdp-1
+		{ 26, 0hBED0_2E64, -0.0 }, // -0x1.a05cc8p-2
+		{ 27, 0h3F0F_CF7D, 0.0 }, // 0x1.1f9efap-1
+		{ 28, 0h3F46_2ED8, 0.0 }, // 0x1.8c5dbp-1
+		{ 29, 0hBF2D_C375, -0.0 }, // -0x1.5b86eap-1
+	}
+
+	for d, i in data {
+		assert(i == d.i)
+		r = trunc_f32(d.v)
+		tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e))
+	}
+
+	v = SNAN_F32
+	r = trunc_f32(v)
+	tc.expect(t, is_nan_f32(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
+
+	v = QNAN_F32
+	r = trunc_f32(v)
+	tc.expect(t, is_nan_f32(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
+}
+
+@test
+test_trunc_f64 :: proc(t: ^testing.T) {
+
+	using math
+
+	r, v: f64
+
+	Datum :: struct {
+		i: int,
+		v: f64,
+		e: f64,
+	}
+	data := []Datum{
+		{ 0, 10.5, 10 }, // Issue #1574 fract in linalg/glm is broken
+		{ 1, -10.5, -10 },
+
+		{ 2, F64_MAX, F64_MAX },
+		{ 3, -F64_MAX, -F64_MAX },
+		{ 4, F64_MIN, 0.0 },
+		{ 5, -F64_MIN, -0.0 },
+		{ 6, 0.0, 0.0 },
+		{ 7, -0.0, -0.0 },
+		{ 8, 1, 1 },
+		{ 9, -1, -1 },
+		{ 10, INF_F64, INF_F64 },
+		{ 11, NEG_INF_F64, NEG_INF_F64 },
+
+		/* From https://en.wikipedia.org/wiki/Double-precision_floating-point_format */
+		{ 12, 0h3FF0_0000_0000_0001, 1 }, // 0x1.0000000000001p+0 (smallest > 1)
+		{ 13, -0h3FF0_0000_0000_0001, -1 },
+		{ 14, 0h3FEF_FFFF_FFFF_FFFF, 0.0 }, // 0x1.fffffffffffffp-1 (largest < 1)
+		{ 15, -0h3FEF_FFFF_FFFF_FFFF, -0.0 },
+		{ 16, 0h0000_0000_0000_0001, 0.0 }, // 0x0.0000000000001p-1022 (smallest subnormal)
+		{ 17, -0h0000_0000_0000_0001, -0.0 },
+		{ 18, 0h000F_FFFF_FFFF_FFFF, 0.0 }, // 0x0.fffffffffffffp-1022 (largest subnormal)
+		{ 19, -0h000F_FFFF_FFFF_FFFF, -0.0 },
+
+		/* From libc-test src/math/sanity/trunc.h */
+		{ 20, 0hC020_2239_F3C6_A8F1, -8 }, // -0x1.02239f3c6a8f1p+3
+		{ 21, 0h4011_6186_8E18_BC67, 4 }, // 0x1.161868e18bc67p+2
+		{ 22, 0hC020_C34B_3E01_E6E7, -8 }, // -0x1.0c34b3e01e6e7p+3
+		{ 23, 0hC01A_206F_0A19_DCC4, -6 }, // -0x1.a206f0a19dcc4p+2
+		{ 24, 0h4022_88BB_B0D6_A1E6, 9 }, // 0x1.288bbb0d6a1e6p+3
+		{ 25, 0h3FE5_2EFD_0CD8_0497, 0.0 }, // 0x1.52efd0cd80497p-1
+		{ 26, 0hBFDA_05CC_7544_81D1, -0.0 }, // -0x1.a05cc754481d1p-2
+		{ 27, 0h3FE1_F9EF_9347_45CB, 0.0 }, // 0x1.1f9ef934745cbp-1
+		{ 28, 0h3FE8_C5DB_097F_7442, 0.0 }, // 0x1.8c5db097f7442p-1
+		{ 29, 0hBFE5_B86E_A811_8A0E, -0.0 }, // -0x1.5b86ea8118a0ep-1
+	}
+
+	for d, i in data {
+		assert(i == d.i)
+		r = trunc_f64(d.v)
+		tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e))
+	}
+
+	v = SNAN_F64
+	r = trunc_f64(v)
+	tc.expect(t, is_nan_f64(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
+
+	v = QNAN_F64
+	r = trunc_f64(v)
+	tc.expect(t, is_nan_f64(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
+}

+ 2 - 1
vendor/directx/d3d12/d3d12_constants.odin

@@ -292,7 +292,8 @@ MAX_DEPTH :: 1.0
 MAX_LIVE_STATIC_SAMPLERS :: 2032
 MAX_MAXANISOTROPY :: 16
 MAX_MULTISAMPLE_SAMPLE_COUNT :: 32
-MAX_POSITION_VALUE :: 3.402823466e+34D3D12_MAX_ROOT_COST :: 64
+MAX_POSITION_VALUE :: 3.402823466e+34
+MAX_ROOT_COST :: 64
 MAX_SHADER_VISIBLE_DESCRIPTOR_HEAP_SIZE_TIER_1 :: 1000000
 MAX_SHADER_VISIBLE_DESCRIPTOR_HEAP_SIZE_TIER_2 :: 1000000
 MAX_SHADER_VISIBLE_SAMPLER_HEAP_SIZE :: 2048