Procházet zdrojové kódy

Merge branch 'master' into update-tilde

gingerBill před 1 rokem
rodič
revize
8d654d9c6d
46 změnil soubory, kde provedl 1375 přidání a 306 odebrání
  1. 3 1
      .gitignore
  2. 8 2
      base/intrinsics/intrinsics.odin
  3. 1 1
      core/os/os_freebsd.odin
  4. 2 2
      core/simd/x86/sse3.odin
  5. 2 2
      core/simd/x86/sse41.odin
  6. 1 1
      core/sync/extended.odin
  7. 31 13
      core/sync/futex_wasm.odin
  8. 2 2
      core/sys/darwin/sync.odin
  9. 3 3
      core/sys/darwin/xnu_system_call_wrappers.odin
  10. 59 15
      core/sys/info/cpu_arm.odin
  11. 98 0
      core/sys/info/cpu_darwin_arm64.odin
  12. 65 0
      core/sys/info/cpu_linux_arm.odin
  13. 15 12
      core/sys/info/doc.odin
  14. 3 2
      core/sys/info/platform_darwin.odin
  15. 1 2
      core/sys/info/platform_freebsd.odin
  16. 58 47
      core/sys/info/platform_linux.odin
  17. 1 2
      core/sys/info/platform_openbsd.odin
  18. 1 2
      core/sys/info/platform_windows.odin
  19. 9 4
      core/sys/info/sysinfo.odin
  20. 26 4
      core/sys/linux/bits.odin
  21. 18 9
      core/sys/unix/sysctl_darwin.odin
  22. 2 2
      core/thread/thread_unix.odin
  23. 5 16
      core/time/tsc_darwin.odin
  24. 28 0
      misc/featuregen/README.md
  25. 37 0
      misc/featuregen/featuregen.cpp
  26. 116 0
      misc/featuregen/featuregen.py
  27. 20 7
      src/bug_report.cpp
  28. 33 14
      src/build_settings.cpp
  29. 47 2
      src/check_builtin.cpp
  30. 39 10
      src/check_decl.cpp
  31. 72 4
      src/check_expr.cpp
  32. 20 5
      src/check_type.cpp
  33. 4 0
      src/checker_builtin_procs.hpp
  34. 1 3
      src/entity.cpp
  35. 64 39
      src/error.cpp
  36. 2 0
      src/gb/gb.h
  37. 1 1
      src/linker.cpp
  38. 45 59
      src/llvm_backend.cpp
  39. 17 10
      src/llvm_backend_proc.cpp
  40. 2 1
      src/llvm_backend_utility.cpp
  41. 73 6
      src/main.cpp
  42. 5 1
      src/parser.cpp
  43. 19 0
      src/string.cpp
  44. 292 0
      src/string_map.cpp
  45. 2 0
      src/threading.cpp
  46. 22 0
      src/types.cpp

+ 3 - 1
.gitignore

@@ -322,4 +322,6 @@ build.sh
 !core/debug/
 
 # RAD debugger project file
-*.raddbg
+*.raddbg
+
+misc/featuregen/featuregen

+ 8 - 2
base/intrinsics/intrinsics.odin

@@ -282,6 +282,12 @@ simd_reverse :: proc(a: #simd[N]T) -> #simd[N]T ---
 simd_rotate_left  :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T ---
 simd_rotate_right :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T ---
 
+// Checks if the current target supports the given target features.
+//
+// Takes a constant comma-seperated string (eg: "sha512,sse4.1"), or a procedure type which has either
+// `@(require_target_feature)` or `@(enable_target_feature)` as its input and returns a boolean indicating
+// if all listed features are supported.
+has_target_feature :: proc($test: $T) -> bool where type_is_string(T) || type_is_proc(T) ---
 
 // WASM targets only
 wasm_memory_grow :: proc(index, delta: uintptr) -> int ---
@@ -293,9 +299,9 @@ wasm_memory_size :: proc(index: uintptr)        -> int ---
 // 0 - indicates that the thread blocked and then was woken up
 // 1 - the loaded value from `ptr` did not match `expected`, the thread did not block
 // 2 - the thread blocked, but the timeout
-@(enable_target_feature="atomics")
+@(require_target_feature="atomics")
 wasm_memory_atomic_wait32   :: proc(ptr: ^u32, expected: u32, timeout_ns: i64) -> u32 ---
-@(enable_target_feature="atomics")
+@(require_target_feature="atomics")
 wasm_memory_atomic_notify32 :: proc(ptr: ^u32, waiters: u32) -> (waiters_woken_up: u32) ---
 
 // x86 Targets (i386, amd64)

+ 1 - 1
core/os/os_freebsd.odin

@@ -159,7 +159,7 @@ blkcnt_t :: i64
 blksize_t :: i32
 fflags_t :: u32
 
-when ODIN_ARCH == .amd64 /* LP64 */ {
+when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 /* LP64 */ {
 	time_t :: i64
 } else {
 	time_t :: i32

+ 2 - 2
core/simd/x86/sse3.odin

@@ -36,7 +36,7 @@ _mm_lddqu_si128 :: #force_inline proc "c" (mem_addr: ^__m128i) -> __m128i {
 _mm_movedup_pd :: #force_inline proc "c" (a: __m128d) -> __m128d {
 	return simd.shuffle(a, a, 0, 0)
 }
-@(require_results, enable_target_feature="sse3")
+@(require_results, enable_target_feature="sse2,sse3")
 _mm_loaddup_pd :: #force_inline proc "c" (mem_addr: [^]f64) -> __m128d {
 	return _mm_load1_pd(mem_addr)
 }
@@ -65,4 +65,4 @@ foreign _ {
 	hsubps :: proc(a, b: __m128) -> __m128 ---
 	@(link_name = "llvm.x86.sse3.ldu.dq")
 	lddqu :: proc(mem_addr: rawptr) -> i8x16 ---
-}
+}

+ 2 - 2
core/simd/x86/sse41.odin

@@ -268,7 +268,7 @@ _mm_testnzc_si128 :: #force_inline proc "c" (a: __m128i, mask: __m128i) -> i32 {
 _mm_test_all_zeros :: #force_inline proc "c" (a: __m128i, mask: __m128i) -> i32 {
 	return _mm_testz_si128(a, mask)
 }
-@(require_results, enable_target_feature="sse4.1")
+@(require_results, enable_target_feature="sse2,sse4.1")
 _mm_test_all_ones :: #force_inline proc "c" (a: __m128i) -> i32 {
 	return _mm_testc_si128(a, _mm_cmpeq_epi32(a, a))
 }
@@ -349,4 +349,4 @@ foreign _ {
 	ptestc     :: proc(a, mask: i64x2) -> i32 ---
 	@(link_name = "llvm.x86.sse41.ptestnzc")
 	ptestnzc   :: proc(a, mask: i64x2) -> i32 ---
-}
+}

+ 1 - 1
core/sync/extended.odin

@@ -433,7 +433,7 @@ One_Shot_Event :: struct #no_copy {
 // Blocks the current thread until the event is made available with `one_shot_event_signal`.
 one_shot_event_wait :: proc "contextless" (e: ^One_Shot_Event) {
 	for atomic_load_explicit(&e.state, .Acquire) == 0 {
-		futex_wait(&e.state, 1)
+		futex_wait(&e.state, 0)
 	}
 }
 

+ 31 - 13
core/sync/futex_wasm.odin

@@ -5,31 +5,49 @@ package sync
 import "base:intrinsics"
 import "core:time"
 
+// NOTE: because `core:sync` is in the dependency chain of a lot of the core packages (mostly through `core:mem`)
+// without actually calling into it much, I opted for a runtime panic instead of a compile error here.
+
 _futex_wait :: proc "contextless" (f: ^Futex, expected: u32) -> bool {
-	s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, -1)
-	return s != 0
+	when !intrinsics.has_target_feature("atomics") {
+		_panic("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
+	} else {
+		s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, -1)
+		return s != 0
+	}
 }
 
 _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, duration: time.Duration) -> bool {
-	s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, i64(duration))
-	return s != 0
-
+	when !intrinsics.has_target_feature("atomics") {
+		_panic("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
+	} else {
+		s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, i64(duration))
+		return s != 0
+	}
 }
 
 _futex_signal :: proc "contextless" (f: ^Futex) {
-	loop: for {
-		s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), 1)
-		if s >= 1 {
-			return
+	when !intrinsics.has_target_feature("atomics") {
+		_panic("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
+	} else {
+		loop: for {
+			s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), 1)
+			if s >= 1 {
+				return
+			}
 		}
 	}
 }
 
 _futex_broadcast :: proc "contextless" (f: ^Futex) {
-	loop: for {
-		s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), ~u32(0))
-		if s >= 0 {
-			return
+	when !intrinsics.has_target_feature("atomics") {
+		_panic("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
+	} else {
+		loop: for {
+			s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), ~u32(0))
+			if s >= 0 {
+				return
+			}
 		}
 	}
 }

+ 2 - 2
core/sys/darwin/sync.odin

@@ -5,9 +5,9 @@ foreign import system "system:System.framework"
 // #define OS_WAIT_ON_ADDR_AVAILABILITY \
 // 	__API_AVAILABLE(macos(14.4), ios(17.4), tvos(17.4), watchos(10.4))
 when ODIN_OS == .Darwin {
-	when ODIN_PLATFORM_SUBTARGET == .iOS && ODIN_MINIMUM_OS_VERSION > 17_04_00 {
+	when ODIN_PLATFORM_SUBTARGET == .iOS && ODIN_MINIMUM_OS_VERSION >= 17_04_00 {
 		WAIT_ON_ADDRESS_AVAILABLE :: true
-	} else when ODIN_MINIMUM_OS_VERSION > 14_04_00 {
+	} else when ODIN_MINIMUM_OS_VERSION >= 14_04_00 {
 		WAIT_ON_ADDRESS_AVAILABLE :: true
 	} else {
 		WAIT_ON_ADDRESS_AVAILABLE :: false

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

@@ -337,7 +337,7 @@ syscall_ftruncate :: #force_inline proc "contextless" (fd: c.int, length: off_t)
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.ftruncate), uintptr(fd), uintptr(length))
 }
 
-syscall_sysctl :: #force_inline proc "contextless" (name: ^c.int, namelen: c.uint, oldp: rawptr, oldlenp: ^i64, newp: ^i8, newlen: i64) -> c.int {
+syscall_sysctl :: #force_inline proc "contextless" (name: [^]c.int, namelen: c.size_t, oldp: rawptr, oldlenp: ^c.size_t, newp: rawptr, newlen: c.size_t) -> c.int {
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.sysctl), uintptr(name), uintptr(namelen), uintptr(oldp), uintptr(oldlenp), uintptr(newp), uintptr(newlen))
 }
 
@@ -390,8 +390,8 @@ syscall_adjtime :: #force_inline proc "contextless" (delta: ^timeval, old_delta:
 	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.adjtime), uintptr(delta), uintptr(old_delta))
 }
 
-syscall_sysctlbyname :: #force_inline proc "contextless" (name: cstring, oldp: rawptr, oldlenp: ^i64, newp: rawptr, newlen: i64) -> c.int {
-	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.sysctlbyname), transmute(uintptr)name, uintptr(oldp), uintptr(oldlenp), uintptr(newp), uintptr(newlen))
+syscall_sysctlbyname :: #force_inline proc "contextless" (name: string, oldp: rawptr, oldlenp: ^c.size_t, newp: rawptr, newlen: c.size_t) -> c.int {
+	return cast(c.int)intrinsics.syscall(unix_offset_syscall(.sysctlbyname), uintptr(raw_data(name)), uintptr(len(name)), uintptr(oldp), uintptr(oldlenp), uintptr(newp), uintptr(newlen))
 }
 
 syscall_proc_info :: #force_inline proc "contextless" (num: c.int, pid: u32, flavor: c.int, arg: u64, buffer: rawptr, buffer_size: c.int) -> c.int {

+ 59 - 15
core/sys/info/cpu_arm.odin

@@ -1,26 +1,70 @@
 //+build arm32, arm64
 package sysinfo
 
-// TODO: Set up an enum with the ARM equivalent of the above.
-CPU_Feature :: enum u64 {}
+import "core:sys/unix"
 
-cpu_features: Maybe(CPU_Feature)
-cpu_name:     Maybe(string)
+_ :: unix
 
-@(init, private)
-init_cpu_features :: proc "c" () {
+CPU_Feature :: enum u64 {
+	// Advanced SIMD & floating-point capabilities:
+	asimd,         // General support for Advanced SIMD instructions/neon.
+	floatingpoint, // General support for floating-point instructions.
+	asimdhp,       // Advanced SIMD half-precision conversion instructions.
+	bf16,          // Storage and arithmetic instructions of the Brain Floating Point (BFloat16) data type.
+	fcma,          // Floating-point complex number instructions.
+	fhm,           // Floating-point half-precision multiplication instructions.
+	fp16,          // General half-precision floating-point data processing instructions.
+	frint,         // Floating-point to integral valued floating-point number rounding instructions.
+	i8mm,          // Advanced SIMD int8 matrix multiplication instructions.
+	jscvt,         // JavaScript conversion instruction.
+	rdm,           // Advanced SIMD rounding double multiply accumulate instructions.
+
+	flagm,  // Condition flag manipulation instructions.
+	flagm2, // Enhancements to condition flag manipulation instructions.
+	crc32,  // CRC32 instructions.
+
+	lse,    // Atomic instructions to support large systems.
+	lse2,   // Changes to single-copy atomicity and alignment requirements for loads and stores for large systems.
+	lrcpc,  // Load-acquire Release Consistency processor consistent (RCpc) instructions.
+	lrcpc2, // Load-acquire Release Consistency processor consistent (RCpc) instructions version 2.
+
+	aes,
+	pmull,
+	sha1,
+	sha256,
+	sha512,
+	sha3,
+
+	sb,   // Barrier instruction to control speculation.
+	ssbs, // Instructions to control speculation of loads and stores.
 }
 
+CPU_Features :: distinct bit_set[CPU_Feature; u64]
+
+cpu_features: Maybe(CPU_Features)
+cpu_name: Maybe(string)
+
 @(private)
-_cpu_name_buf: [72]u8
+cpu_name_buf: [128]byte
 
 @(init, private)
-init_cpu_name :: proc "c" () {
-	when ODIN_ARCH == .arm32 {
-		copy(_cpu_name_buf[:], "ARM")
-		cpu_name = string(_cpu_name_buf[:3])
-	} else {
-		copy(_cpu_name_buf[:], "ARM64")
-		cpu_name = string(_cpu_name_buf[:5])
+init_cpu_name :: proc "contextless" () {
+	generic := true
+
+	when ODIN_OS == .Darwin {
+		if unix.sysctlbyname("machdep.cpu.brand_string", &cpu_name_buf) {
+			cpu_name = string(cstring(rawptr(&cpu_name_buf)))
+			generic = false
+		}
 	}
-}
+
+	if generic {
+		when ODIN_ARCH == .arm64 {
+			copy(cpu_name_buf[:], "ARM64")
+			cpu_name = string(cpu_name_buf[:len("ARM64")])
+		} else {
+			copy(cpu_name_buf[:], "ARM")
+			cpu_name = string(cpu_name_buf[:len("ARM")])
+		}
+	}
+}

+ 98 - 0
core/sys/info/cpu_darwin_arm64.odin

@@ -0,0 +1,98 @@
+package sysinfo
+
+import "core:sys/unix"
+
+@(init, private)
+init_cpu_features :: proc "contextless" () {
+	@(static) features: CPU_Features
+	defer cpu_features = features
+
+	try_set :: proc "contextless" (name: string, feature: CPU_Feature) -> (ok: bool) {
+		support: b32
+		if ok = unix.sysctlbyname(name, &support); ok && support {
+			features += { feature }
+		}
+		return
+	}
+
+	// Docs from Apple: https://developer.apple.com/documentation/kernel/1387446-sysctlbyname/determining_instruction_set_characteristics
+	// Features from there that do not have (or I didn't find) an equivalent on Linux are commented out below.
+
+	// Advanced SIMD & floating-point capabilities:
+	{
+		if !try_set("hw.optional.AdvSIMD", .asimd) {
+			try_set("hw.optional.neon", .asimd)
+		}
+
+		try_set("hw.optional.floatingpoint", .floatingpoint)
+
+		if !try_set("hw.optional.AdvSIMD_HPFPCvt", .asimdhp) {
+			try_set("hw.optional.neon_hpfp", .asimdhp)
+		}
+
+		try_set("hw.optional.arm.FEAT_BF16", .bf16)
+		// try_set("hw.optional.arm.FEAT_DotProd", .dotprod)
+
+		if !try_set("hw.optional.arm.FEAT_FCMA", .fcma) {
+			try_set("hw.optional.armv8_3_compnum", .fcma)
+		}
+
+		if !try_set("hw.optional.arm.FEAT_FHM", .fhm) {
+			try_set("hw.optional.armv8_2_fhm", .fhm)
+		}
+
+		if !try_set("hw.optional.arm.FEAT_FP16", .fp16) {
+			try_set("hw.optional.neon_fp16", .fp16)
+		}
+
+		try_set("hw.optional.arm.FEAT_FRINTTS", .frint)
+		try_set("hw.optional.arm.FEAT_I8MM", .i8mm)
+		try_set("hw.optional.arm.FEAT_JSCVT", .jscvt)
+		try_set("hw.optional.arm.FEAT_RDM", .rdm)
+	}
+
+	// Integer capabilities:
+	{
+		try_set("hw.optional.arm.FEAT_FlagM", .flagm)
+		try_set("hw.optional.arm.FEAT_FlagM2", .flagm2)
+		try_set("hw.optional.armv8_crc32", .crc32)
+	}
+
+	// Atomic and memory ordering instruction capabilities:
+	{
+		try_set("hw.optional.arm.FEAT_LRCPC", .lrcpc)
+		try_set("hw.optional.arm.FEAT_LRCPC2", .lrcpc2)
+
+		if !try_set("hw.optional.arm.FEAT_LSE", .lse) {
+			try_set("hw.optional.armv8_1_atomics", .lse)
+		}
+
+		// try_set("hw.optional.arm.FEAT_LSE2", .lse2)
+	}
+
+	// Encryption capabilities:
+	{
+		try_set("hw.optional.arm.FEAT_AES", .aes)
+		try_set("hw.optional.arm.FEAT_PMULL", .pmull)
+		try_set("hw.optional.arm.FEAT_SHA1", .sha1)
+		try_set("hw.optional.arm.FEAT_SHA256", .sha256)
+
+		if !try_set("hw.optional.arm.FEAT_SHA512", .sha512) {
+			try_set("hw.optional.armv8_2_sha512", .sha512)
+		}
+
+		if !try_set("hw.optional.arm.FEAT_SHA3", .sha3) {
+			try_set("hw.optional.armv8_2_sha3", .sha3)
+		}
+	}
+
+	// General capabilities:
+	{
+		// try_set("hw.optional.arm.FEAT_BTI", .bti)
+		// try_set("hw.optional.arm.FEAT_DPB", .dpb)
+		// try_set("hw.optional.arm.FEAT_DPB2", .dpb2)
+		// try_set("hw.optional.arm.FEAT_ECV", .ecv)
+		try_set("hw.optional.arm.FEAT_SB", .sb)
+		try_set("hw.optional.arm.FEAT_SSBS", .ssbs)
+	}
+}

+ 65 - 0
core/sys/info/cpu_linux_arm.odin

@@ -0,0 +1,65 @@
+//+build arm32, arm64
+//+build linux
+package sysinfo
+
+import "core:sys/linux"
+import "core:strings"
+
+@(init, private)
+init_cpu_features :: proc() {
+	fd, err := linux.open("/proc/cpuinfo", {})
+	if err != .NONE { return }
+	defer linux.close(fd)
+
+	// This is probably enough right?
+	buf: [4096]byte
+	n, rerr := linux.read(fd, buf[:])
+	if rerr != .NONE || n == 0 { return }
+
+	features: CPU_Features
+	defer cpu_features = features
+
+	str := string(buf[:n])
+	for line in strings.split_lines_iterator(&str) {
+		key, _, value := strings.partition(line, ":")
+		key   = strings.trim_space(key)
+		value = strings.trim_space(value)
+
+		if key != "Features" { continue }
+
+		for feature in strings.split_by_byte_iterator(&value, ' ') {
+			switch feature {
+			case "asimd", "neon": features += { .asimd }
+			case "fp":            features += { .floatingpoint }
+			case "asimdhp":       features += { .asimdhp }
+			case "asimdbf16":     features += { .bf16 }
+			case "fcma":          features += { .fcma }
+			case "asimdfhm":      features += { .fhm }
+			case "fphp", "half":  features += { .fp16 }
+			case "frint":         features += { .frint }
+			case "i8mm":          features += { .i8mm }
+			case "jscvt":         features += { .jscvt }
+			case "asimdrdm":      features += { .rdm }
+
+			case "flagm":  features += { .flagm }
+			case "flagm2": features += { .flagm2 }
+			case "crc32":  features += { .crc32 }
+
+			case "atomics": features += { .lse }
+			case "lrcpc":   features += { .lrcpc }
+			case "ilrcpc":  features += { .lrcpc2 }
+
+			case "aes":    features += { .aes }
+			case "pmull":  features += { .pmull }
+			case "sha1":   features += { .sha1 }
+			case "sha2":   features += { .sha256 }
+			case "sha3":   features += { .sha3 }
+			case "sha512": features += { .sha512 }
+
+			case "sb":   features += { .sb }
+			case "ssbs": features += { .ssbs }
+			}
+		}
+		break
+	}
+}

+ 15 - 12
core/sys/info/doc.odin

@@ -19,18 +19,21 @@ Example:
 	import si "core:sys/info"
 
 	main :: proc() {
-		fmt.printf("Odin:  %v\n",     ODIN_VERSION)
-		fmt.printf("OS:    %v\n",     si.os_version.as_string)
-		fmt.printf("OS:    %#v\n",    si.os_version)
-		fmt.printf("CPU:   %v\n",     si.cpu_name)
-		fmt.printf("RAM:   %v MiB\n", si.ram.total_ram / 1024 / 1024)
+		fmt.printfln("Odin:  %v",    ODIN_VERSION)
+		fmt.printfln("OS:    %v",    si.os_version.as_string)
+		fmt.printfln("OS:    %#v",   si.os_version)
+		fmt.printfln("CPU:   %v",    si.cpu_name)
+		fmt.printfln("RAM:   %#.1M", si.ram.total_ram)
+
+		// fmt.printfln("Features: %v",      si.cpu_features)
+		// fmt.printfln("MacOS version: %v", si.macos_version)
 
 		fmt.println()
 		for gpu, i in si.gpus {
-			fmt.printf("GPU #%v:\n", i)
-			fmt.printf("\tVendor: %v\n",     gpu.vendor_name)
-			fmt.printf("\tModel:  %v\n",     gpu.model_name)
-			fmt.printf("\tVRAM:   %v MiB\n", gpu.total_ram / 1024 / 1024)
+			fmt.printfln("GPU #%v:", i)
+			fmt.printfln("\tVendor: %v",    gpu.vendor_name)
+			fmt.printfln("\tModel:  %v",    gpu.model_name)
+			fmt.printfln("\tVRAM:   %#.1M", gpu.total_ram)
 		}
 	}
 
@@ -51,11 +54,11 @@ Example:
 		as_string = "Windows 10 Professional (version: 20H2), build: 19042.1466",
 	}
 	CPU:   AMD Ryzen 7 1800X Eight-Core Processor
-	RAM:   65469 MiB
+	RAM:   64.0 GiB
 	GPU #0:
 		Vendor: Advanced Micro Devices, Inc.
 		Model:  Radeon RX Vega
-		VRAM:   8176 MiB
+		VRAM:   8.0 GiB
 
 - Example macOS output:
 
@@ -73,6 +76,6 @@ Example:
 			as_string = "macOS Monterey 12.4 (build 21F79, kernel 21.5.0)",
 	}
 	CPU:  Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
-	RAM:  8192 MiB
+	RAM:  8.0 GiB
 */
 package sysinfo

+ 3 - 2
core/sys/info/platform_darwin.odin

@@ -1,4 +1,3 @@
-// +build darwin
 package sysinfo
 
 import sys "core:sys/unix"
@@ -76,6 +75,8 @@ init_os_version :: proc () {
 	os_version.minor = rel.darwin.y
 	os_version.patch = rel.darwin.z
 
+	macos_version = transmute(Version)rel.release.version
+
 	strings.write_string(&b, rel.os_name)
 	if match == .Exact || match == .Nearest {
 		strings.write_rune(&b, ' ')
@@ -113,7 +114,7 @@ init_os_version :: proc () {
 	os_version.as_string = strings.to_string(b)
 }
 
-@(init)
+@(init, private)
 init_ram :: proc() {
 	// Retrieve RAM info using `sysctl`
 

+ 1 - 2
core/sys/info/platform_freebsd.odin

@@ -1,4 +1,3 @@
-// +build freebsd
 package sysinfo
 
 import sys "core:sys/unix"
@@ -68,7 +67,7 @@ init_os_version :: proc () {
 	}
 }
 
-@(init)
+@(init, private)
 init_ram :: proc() {
 	// Retrieve RAM info using `sysctl`
 	mib := []i32{sys.CTL_HW, sys.HW_PHYSMEM}

+ 58 - 47
core/sys/info/platform_linux.odin

@@ -1,11 +1,9 @@
-// +build linux
 package sysinfo
 
 import "base:intrinsics"
-import "base:runtime"
-import "core:strings"
-import "core:strconv"
 
+import "core:strconv"
+import "core:strings"
 import "core:sys/linux"
 
 @(private)
@@ -14,32 +12,37 @@ version_string_buf: [1024]u8
 @(init, private)
 init_os_version :: proc () {
 	os_version.platform = .Linux
-	// Try to parse `/etc/os-release` for `PRETTY_NAME="Ubuntu 20.04.3 LTS`
-	fd, errno := linux.open("/etc/os-release", {.RDONLY}, {})
-	assert(errno == .NONE, "Failed to read /etc/os-release")
-	defer {
-		cerrno := linux.close(fd)
-		assert(cerrno == .NONE, "Failed to close the file descriptor")
-	}
-	os_release_buf: [2048]u8
-	n, read_errno := linux.read(fd, os_release_buf[:])
-	assert(read_errno == .NONE, "Failed to read data from /etc/os-release")
-	release := string(os_release_buf[:n])
-	// Search the line in the file until we find "PRETTY_NAME="
-	NEEDLE :: "PRETTY_NAME=\""
-	pretty_start := strings.index(release, NEEDLE)
+
 	b := strings.builder_from_bytes(version_string_buf[:])
-	if pretty_start > 0 {
-		for r, i in release[pretty_start + len(NEEDLE):] {
-			if r == '"' {
-				strings.write_string(&b, release[pretty_start + len(NEEDLE):][:i])
-				break
-			} else if r == '\r' || r == '\n' {
-				strings.write_string(&b, "Unknown Linux Distro")
-				break
+
+	// Try to parse `/etc/os-release` for `PRETTY_NAME="Ubuntu 20.04.3 LTS`
+	{
+		fd, errno := linux.open("/etc/os-release", {})
+		assert(errno == .NONE, "Failed to read /etc/os-release")
+		defer {
+			cerrno := linux.close(fd)
+			assert(cerrno == .NONE, "Failed to close the file descriptor")
+		}
+
+		os_release_buf: [2048]u8
+		n, read_errno := linux.read(fd, os_release_buf[:])
+		assert(read_errno == .NONE, "Failed to read data from /etc/os-release")
+		release := string(os_release_buf[:n])
+
+		// Search the line in the file until we find "PRETTY_NAME="
+		NEEDLE :: "PRETTY_NAME=\""
+		_, _, post := strings.partition(release, NEEDLE)
+		if len(post) > 0 {
+			end := strings.index_any(post, "\"\n")
+			if end > -1 && post[end] == '"' {
+				strings.write_string(&b, post[:end])
 			}
 		}
+		if strings.builder_len(b) == 0 {
+			strings.write_string(&b, "Unknown Linux Distro")
+		}
 	}
+
 	// Grab kernel info using `uname()` syscall, https://linux.die.net/man/2/uname
 	uts: linux.UTS_Name
 	uname_errno := linux.uname(&uts)
@@ -48,31 +51,39 @@ init_os_version :: proc () {
 	strings.write_string(&b, ", ")
 	strings.write_string(&b, string(cstring(&uts.sysname[0])))
 	strings.write_rune(&b, ' ')
-	l := strings.builder_len(b)
+
+	release_i := strings.builder_len(b)
 	strings.write_string(&b, string(cstring(&uts.release[0])))
-	// Parse kernel version, as substrings of the version info in `version_string_buf`
-	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
-	version_bits := strings.split_n(strings.to_string(b)[l:], "-", 2, context.temp_allocator)
-	if len(version_bits) > 1 {
-		os_version.version = version_bits[1]
-	}
-	// Parse major, minor, patch from release info
-	triplet := strings.split(version_bits[0], ".", context.temp_allocator)
-	if len(triplet) == 3 {
-		major, major_ok := strconv.parse_int(triplet[0])
-		minor, minor_ok := strconv.parse_int(triplet[1])
-		patch, patch_ok := strconv.parse_int(triplet[2])
-		if major_ok && minor_ok && patch_ok {
-			os_version.major = major
-			os_version.minor = minor
-			os_version.patch = patch
+	release_str := string(b.buf[release_i:])
+
+	os_version.as_string = strings.to_string(b)
+
+	// Parse the Linux version out of the release string
+	{
+		version_num, _, version_suffix := strings.partition(release_str, "-")
+		os_version.version = version_suffix
+
+		i: int
+		for part in strings.split_iterator(&version_num, ".") {
+			defer i += 1
+
+			dst: ^int
+			switch i {
+			case 0: dst = &os_version.major
+			case 1: dst = &os_version.minor
+			case 2: dst = &os_version.patch
+			case:   break
+			}
+
+			num, ok := strconv.parse_int(part)
+			if !ok { break }
+
+			dst^ = num
 		}
 	}
-	// Finish the string
-	os_version.as_string = strings.to_string(b)
 }
 
-@(init)
+@(init, private)
 init_ram :: proc() {
 	// Retrieve RAM info using `sysinfo`
 	sys_info: linux.Sys_Info
@@ -84,4 +95,4 @@ init_ram :: proc() {
 		total_swap = int(sys_info.totalswap) * int(sys_info.mem_unit),
 		free_swap  = int(sys_info.freeswap)  * int(sys_info.mem_unit),
 	}
-}
+}

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

@@ -1,4 +1,3 @@
-// +build openbsd
 package sysinfo
 
 import sys "core:sys/unix"
@@ -61,7 +60,7 @@ init_os_version :: proc () {
 	os_version.as_string = strings.to_string(b)
 }
 
-@(init)
+@(init, private)
 init_ram :: proc() {
 	// Retrieve RAM info using `sysctl`
 	mib := []i32{sys.CTL_HW, sys.HW_PHYSMEM64}

+ 1 - 2
core/sys/info/platform_windows.odin

@@ -1,4 +1,3 @@
-// +build windows
 package sysinfo
 
 import sys "core:sys/windows"
@@ -259,7 +258,7 @@ init_os_version :: proc () {
 	}
 }
 
-@(init)
+@(init, private)
 init_ram :: proc() {
 	state: sys.MEMORYSTATUSEX
 

+ 9 - 4
core/sys/info/sysinfo.odin

@@ -8,6 +8,9 @@ os_version: OS_Version
 ram:        RAM
 gpus:       []GPU
 
+// Only on MacOS, contains the actual MacOS version, while the `os_version` contains the kernel version.
+macos_version: Version
+
 OS_Version_Platform :: enum {
 	Unknown,
 	Windows,
@@ -19,12 +22,14 @@ OS_Version_Platform :: enum {
 	NetBSD,
 }
 
+Version :: struct {
+	major, minor, patch: int,
+}
+
 OS_Version :: struct {
 	platform: OS_Version_Platform,
 
-	major:     int,
-	minor:     int,
-	patch:     int,
+	using _:   Version,
 	build:     [2]int,
 	version:   string,
 
@@ -42,4 +47,4 @@ GPU :: struct {
 	vendor_name: string,
 	model_name:  string,
 	total_ram:   int,
-}
+}

+ 26 - 4
core/sys/linux/bits.odin

@@ -145,12 +145,14 @@ Errno :: enum i32 {
 }
 
 /*
-	Bits for Open_Flags
+	Bits for Open_Flags.
+
+	RDONLY flag is not present, because it has the value of 0, i.e. it is the
+	default, unless WRONLY or RDWR is specified.
 */
 Open_Flags_Bits :: enum {
-	RDONLY    = 0,
-	WRONLY    = 1,
-	RDWR      = 2,
+	WRONLY    = 0,
+	RDWR      = 1,
 	CREAT     = 6,
 	EXCL      = 7,
 	NOCTTY    = 8,
@@ -160,6 +162,7 @@ Open_Flags_Bits :: enum {
 	DSYNC     = 12,
 	ASYNC     = 13,
 	DIRECT    = 14,
+	LARGEFILE = 15,
 	DIRECTORY = 16,
 	NOFOLLOW  = 17,
 	NOATIME   = 18,
@@ -167,6 +170,25 @@ Open_Flags_Bits :: enum {
 	PATH      = 21,
 }
 
+// https://github.com/torvalds/linux/blob/7367539ad4b0f8f9b396baf02110962333719a48/include/uapi/asm-generic/fcntl.h#L19
+#assert(1 << uint(Open_Flags_Bits.WRONLY)    == 0o0000000_1)
+#assert(1 << uint(Open_Flags_Bits.RDWR)      == 0o0000000_2)
+#assert(1 << uint(Open_Flags_Bits.CREAT)     == 0o00000_100)
+#assert(1 << uint(Open_Flags_Bits.EXCL)      == 0o00000_200)
+#assert(1 << uint(Open_Flags_Bits.NOCTTY)    == 0o00000_400)
+#assert(1 << uint(Open_Flags_Bits.TRUNC)     == 0o0000_1000)
+#assert(1 << uint(Open_Flags_Bits.APPEND)    == 0o0000_2000)
+#assert(1 << uint(Open_Flags_Bits.NONBLOCK)  == 0o0000_4000)
+#assert(1 << uint(Open_Flags_Bits.DSYNC)     == 0o000_10000)
+#assert(1 << uint(Open_Flags_Bits.ASYNC)     == 0o000_20000)
+#assert(1 << uint(Open_Flags_Bits.DIRECT)    == 0o000_40000)
+#assert(1 << uint(Open_Flags_Bits.LARGEFILE) == 0o00_100000)
+#assert(1 << uint(Open_Flags_Bits.DIRECTORY) == 0o00_200000)
+#assert(1 << uint(Open_Flags_Bits.NOFOLLOW)  == 0o00_400000)
+#assert(1 << uint(Open_Flags_Bits.NOATIME)   == 0o0_1000000)
+#assert(1 << uint(Open_Flags_Bits.CLOEXEC)   == 0o0_2000000)
+#assert(1 << uint(Open_Flags_Bits.PATH)      == 0o_10000000)
+
 /*
 	Bits for FD_Flags bitset
 */

+ 18 - 9
core/sys/unix/sysctl_darwin.odin

@@ -1,20 +1,29 @@
 //+build darwin
 package unix
 
-import "core:sys/darwin"
 import "base:intrinsics"
 
+import "core:c"
+import "core:sys/darwin"
+
 _ :: darwin
 
-sysctl :: proc(mib: []i32, val: ^$T) -> (ok: bool) {
-	mib := mib
-	result_size := i64(size_of(T))
+sysctl :: proc "contextless" (mib: []i32, val: ^$T) -> (ok: bool) {
+	result_size := c.size_t(size_of(T))
+	res := darwin.syscall_sysctl(
+		raw_data(mib), len(mib),
+		val, &result_size,
+		nil, 0,
+	)
+	return res == 0
+}
 
-	res := intrinsics.syscall(
-		darwin.unix_offset_syscall(.sysctl),
-		uintptr(raw_data(mib)), uintptr(len(mib)),
-		uintptr(val), uintptr(&result_size),
-		uintptr(0), uintptr(0),
+sysctlbyname :: proc "contextless" (name: string, val: ^$T) -> (ok: bool) {
+	result_size := c.size_t(size_of(T))
+	res := darwin.syscall_sysctlbyname(
+		name,
+		val, &result_size,
+		nil, 0,
 	)
 	return res == 0
 }

+ 2 - 2
core/thread/thread_unix.odin

@@ -25,7 +25,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
 
 		when ODIN_OS != .Darwin {
 			// We need to give the thread a moment to start up before we enable cancellation.
-			can_set_thread_cancel_state := unix.pthread_setcancelstate(unix.PTHREAD_CANCEL_DISABLE, nil) == 0
+			can_set_thread_cancel_state := unix.pthread_setcancelstate(unix.PTHREAD_CANCEL_ENABLE, nil) == 0
 		}
 
 		sync.lock(&t.mutex)
@@ -40,7 +40,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
 			// Enable thread's cancelability.
 			if can_set_thread_cancel_state {
 				unix.pthread_setcanceltype (unix.PTHREAD_CANCEL_ASYNCHRONOUS, nil)
-				unix.pthread_setcancelstate(unix.PTHREAD_CANCEL_DISABLE,      nil)
+				unix.pthread_setcancelstate(unix.PTHREAD_CANCEL_ENABLE,       nil)
 			}
 		}
 

+ 5 - 16
core/time/tsc_darwin.odin

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

+ 28 - 0
misc/featuregen/README.md

@@ -0,0 +1,28 @@
+# Featuregen
+
+This directory contains a python and CPP script that generates the needed information
+for features regarding microarchitecture and target features of the compiler.
+
+It is not pretty! But LLVM has no way to query this information with their C API.
+
+It generates these globals (intended for `src/build_settings.cpp`:
+
+- `target_microarch_list`: an array of strings indexed by the architecture, each string is a comma-seperated list of microarchitectures available on that architecture
+- `target_features_list`: an array of strings indexed by the architecture, each string is a comma-seperated list of target features available on that architecture
+- `target_microarch_counts`: an array of ints indexed by the architecture, each int represents the amount of microarchitectures available on that target, intended for easier iteration of the next global
+- `microarch_features_list`: an array of a tuple like struct where the first string is a microarchitecture and the second is a comma-seperated list of all features that are enabled by default for it
+
+In order to get the default features for a microarchitecture there is a small CPP program that takes
+a target triple and microarchitecture and spits out the default features, this is then parsed by the python script.
+
+This should be ran each time we update LLVM to stay in sync.
+
+If there are minor differences (like the Odin user using LLVM 14 and this table being generated on LLVM 17) it
+does not impact much at all, the only thing it will do is make LLVM print a message that the feature is ignored (if it was added between 14 and 17 in this case).
+
+## Usage
+
+1. Make sure the table of architectures at the top of the python script is up-to-date (the triple can be any valid triple for the architecture)
+1. `./build.sh`
+1. `python3 featuregen.py`
+1. Copy the output into `src/build_settings.cpp`

+ 37 - 0
misc/featuregen/featuregen.cpp

@@ -0,0 +1,37 @@
+#include <llvm/MC/MCSubtargetInfo.h>
+#include <llvm/MC/TargetRegistry.h>
+#include <llvm/Support/raw_ostream.h>
+#include <llvm/ADT/ArrayRef.h>
+#include <llvm/Support/InitLLVM.h>
+#include <llvm/Support/TargetSelect.h>
+
+// Dumps the default set of supported features for the given microarch.
+int main(int argc, char **argv) {
+	if (argc < 3) {
+		llvm::errs() << "Error: first arg should be triple, second should be microarch\n";
+		return 1;
+	}
+
+	llvm::InitializeAllTargets();
+	llvm::InitializeAllTargetMCs();
+
+	std::string error;
+	const llvm::Target* target = llvm::TargetRegistry::lookupTarget(argv[1], error);
+
+	if (!target) {
+		llvm::errs() << "Error: " << error << "\n";
+		return 1;
+	}
+
+	auto STI = target->createMCSubtargetInfo(argv[1], argv[2], "");
+
+	std::string plus = "+";
+	llvm::ArrayRef<llvm::SubtargetFeatureKV> features = STI->getAllProcessorFeatures();
+	for (const auto& feature : features) {
+		if (STI->checkFeatures(plus + feature.Key)) {
+			llvm::outs() << feature.Key << "\n";
+		}
+	}
+
+	return 0;
+}

+ 116 - 0
misc/featuregen/featuregen.py

@@ -0,0 +1,116 @@
+import subprocess
+import tempfile
+import os
+import sys
+
+archs = [
+	("amd64",     "linux_amd64", "x86_64-pc-linux-gnu", [], []),
+	("i386",      "linux_i386",  "i386-pc-linux-gnu",   [], []),
+	("arm32",     "linux_arm32", "arm-linux-gnu",       [], []),
+	("arm64",     "linux_arm64", "aarch64-linux-elf",   [], []),
+	("wasm32",    "js_wasm32",   "wasm32-js-js",        [], []),
+	("wasm64p32", "js_wasm64p32","wasm32-js-js",        [], []),
+];
+
+SEEKING_CPUS     = 0
+PARSING_CPUS     = 1
+PARSING_FEATURES = 2
+
+with tempfile.NamedTemporaryFile(suffix=".odin", delete=True) as temp_file:
+	temp_file.write(b"package main\n")
+
+	for arch, target, triple, cpus, features in archs:
+		cmd = ["odin", "build", temp_file.name, "-file", "-build-mode:llvm", "-out:temp", "-target-features:\"help\"", f"-target:\"{target}\""]
+		process = subprocess.Popen(cmd, stderr=subprocess.PIPE, text=True)
+
+		state = SEEKING_CPUS
+		for line in process.stderr:
+
+			if state == SEEKING_CPUS:
+				if line == "Available CPUs for this target:\n":
+					state = PARSING_CPUS
+			
+			elif state == PARSING_CPUS:
+				if line == "Available features for this target:\n":
+					state = PARSING_FEATURES
+					continue
+			
+				parts = line.split(" -", maxsplit=1)
+				if len(parts) < 2:
+					continue
+
+				cpu = parts[0].strip()
+				cpus.append(cpu)
+
+			elif state == PARSING_FEATURES:
+				if line == "\n" and len(features) > 0:
+					break
+
+				parts = line.split(" -", maxsplit=1)
+				if len(parts) < 2:
+					continue
+
+				feature = parts[0].strip()
+				features.append(feature)
+
+		process.wait()
+		if process.returncode != 0:
+			print(f"odin build returned with non-zero exit code {process.returncode}")
+			sys.exit(1)
+
+		os.remove("temp.ll")
+
+def print_default_features(triple, microarch):
+	cmd = ["./featuregen", triple, microarch]
+	process = subprocess.Popen(cmd, stdout=subprocess.PIPE, text=True)
+	first = True
+	for line in process.stdout:
+		print("" if first else ",", line.strip(), sep="", end="")
+		first = False
+	process.wait()
+	if process.returncode != 0:
+		print(f"featuregen returned with non-zero exit code {process.returncode}")
+		sys.exit(1)
+
+print("// Generated with the featuregen script in `misc/featuregen`")
+print("gb_global String target_microarch_list[TargetArch_COUNT] = {")
+print("\t// TargetArch_Invalid:")
+print('\tstr_lit(""),')
+for arch, target, triple, cpus, features in archs:
+	print(f"\t// TargetArch_{arch}:")
+	print(f'\tstr_lit("{','.join(cpus)}"),')
+print("};")
+
+print("")
+
+print("// Generated with the featuregen script in `misc/featuregen`")
+print("gb_global String target_features_list[TargetArch_COUNT] = {")
+print("\t// TargetArch_Invalid:")
+print('\tstr_lit(""),')
+for arch, target, triple, cpus, features in archs:
+	print(f"\t// TargetArch_{arch}:")
+	print(f'\tstr_lit("{','.join(features)}"),')
+print("};")
+
+print("")
+
+print("// Generated with the featuregen script in `misc/featuregen`")
+print("gb_global int target_microarch_counts[TargetArch_COUNT] = {")
+print("\t// TargetArch_Invalid:")
+print("\t0,")
+for arch, target, triple, cpus, feature in archs:
+	print(f"\t// TargetArch_{arch}:")
+	print(f"\t{len(cpus)},")
+print("};")
+
+print("")
+
+print("// Generated with the featuregen script in `misc/featuregen`")
+print("gb_global MicroarchFeatureList microarch_features_list[] = {")
+for arch, target, triple, cpus, features in archs:
+	print(f"\t// TargetArch_{arch}:")
+	for cpu in cpus:
+		print(f'\t{{ str_lit("{cpu}"), str_lit("', end="")
+		print_default_features(triple, cpu)
+		print('") },')
+print("};")

+ 20 - 7
src/bug_report.cpp

@@ -204,14 +204,27 @@ gb_internal void report_cpu_info() {
 	}
 
 	#elif defined(GB_CPU_ARM)
-		/*
-			TODO(Jeroen): On *nix, perhaps query `/proc/cpuinfo`.
-		*/
-		#if defined(GB_ARCH_64_BIT)
-			gb_printf("ARM64\n");
-		#else
-			gb_printf("ARM\n");
+		bool generic = true;
+
+		#if defined(GB_SYSTEM_OSX)
+			char cpu_name[128] = {};	
+			size_t cpu_name_size = 128;
+			if (sysctlbyname("machdep.cpu.brand_string", &cpu_name, &cpu_name_size, nullptr, 0) == 0) {
+				generic = false;
+				gb_printf("%s\n", (char *)&cpu_name[0]);
+			}
 		#endif
+
+		if (generic) {
+			/*
+				TODO(Jeroen): On *nix, perhaps query `/proc/cpuinfo`.
+			*/
+			#if defined(GB_ARCH_64_BIT)
+				gb_printf("ARM64\n");
+			#else
+				gb_printf("ARM\n");
+			#endif
+		}
 	#else
 		gb_printf("Unknown\n");
 	#endif

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 33 - 14
src/build_settings.cpp


+ 47 - 2
src/check_builtin.cpp

@@ -1719,6 +1719,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
 	case BuiltinProc_objc_register_selector: 
 	case BuiltinProc_objc_register_class: 
 	case BuiltinProc_atomic_type_is_lock_free:
+	case BuiltinProc_has_target_feature:
 		// NOTE(bill): The first arg may be a Type, this will be checked case by case
 		break;
 
@@ -2022,6 +2023,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
 		
 		Selection sel = lookup_field(type, field_name, false);
 		if (sel.entity == nullptr) {
+			ERROR_BLOCK();
 			gbString type_str = type_to_string_shorthand(type);
 			error(ce->args[0],
 			      "'%s' has no field named '%.*s'", type_str, LIT(field_name));
@@ -2095,6 +2097,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
 		
 		Selection sel = lookup_field(type, field_name, false);
 		if (sel.entity == nullptr) {
+			ERROR_BLOCK();
 			gbString type_str = type_to_string_shorthand(type);
 			error(ce->args[0],
 			      "'%s' has no field named '%.*s'", type_str, LIT(field_name));
@@ -3663,6 +3666,41 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
 		break;
 	}
 
+	case BuiltinProc_has_target_feature: {
+		String features = str_lit("");
+
+		check_expr_or_type(c, operand, ce->args[0]);
+
+		if (is_type_string(operand->type) && operand->mode == Addressing_Constant) {
+			GB_ASSERT(operand->value.kind == ExactValue_String);
+			features = operand->value.value_string;
+		} else {
+			Type *pt = base_type(operand->type);
+			if (pt->kind == Type_Proc) {
+				if (pt->Proc.require_target_feature.len != 0) {
+					GB_ASSERT(pt->Proc.enable_target_feature.len == 0);
+					features = pt->Proc.require_target_feature;
+				} else if (pt->Proc.enable_target_feature.len != 0) {
+					features = pt->Proc.enable_target_feature;
+				} else {
+					error(ce->args[0], "Expected the procedure type given to '%.*s' to have @(require_target_feature=\"...\") or @(enable_target_feature=\"...\")", LIT(builtin_name));
+				}
+			} else {
+				error(ce->args[0], "Expected a constant string or procedure type for '%.*s'", LIT(builtin_name));
+			}
+		}
+
+		String invalid;
+		if (!check_target_feature_is_valid_globally(features, &invalid)) {
+			error(ce->args[0], "Target feature '%.*s' is not a valid target feature", LIT(invalid));
+		}
+
+		operand->value = exact_value_bool(check_target_feature_is_enabled(features, nullptr));
+		operand->mode = Addressing_Constant;
+		operand->type = t_untyped_bool;
+		break;
+	}
+
 	case BuiltinProc_soa_struct: {
 		Operand x = {};
 		Operand y = {};
@@ -5801,6 +5839,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
 
 			Selection sel = lookup_field(type, field_name, false);
 			if (sel.entity == nullptr) {
+				ERROR_BLOCK();
 				gbString type_str = type_to_string(bt);
 				error(ce->args[0],
 				      "'%s' has no field named '%.*s'", type_str, LIT(field_name));
@@ -6014,7 +6053,10 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
 				return false;
 			}
 
-			enable_target_feature({}, str_lit("atomics"));
+			if (!check_target_feature_is_enabled(str_lit("atomics"), nullptr)) {
+				error(call, "'%.*s' requires target feature 'atomics' to be enabled, enable it with -target-features:\"atomics\" or choose a different -microarch", LIT(builtin_name));
+				return false;
+			}
 
 			Operand ptr = {};
 			Operand expected = {};
@@ -6068,7 +6110,10 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
 				return false;
 			}
 
-			enable_target_feature({}, str_lit("atomics"));
+			if (!check_target_feature_is_enabled(str_lit("atomics"), nullptr)) {
+				error(call, "'%.*s' requires target feature 'atomics' to be enabled, enable it with -target-features:\"atomics\" or choose a different -microarch", LIT(builtin_name));
+				return false;
+			}
 
 			Operand ptr = {};
 			Operand waiters = {};

+ 39 - 10
src/check_decl.cpp

@@ -886,17 +886,37 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
 
 	check_objc_methods(ctx, e, ac);
 
-	if (ac.require_target_feature.len != 0 && ac.enable_target_feature.len != 0) {
-		error(e->token, "Attributes @(require_target_feature=...) and @(enable_target_feature=...) cannot be used together");
-	} else if (ac.require_target_feature.len != 0) {
-		if (check_target_feature_is_enabled(e->token.pos, ac.require_target_feature)) {
-			e->Procedure.target_feature = ac.require_target_feature;
-		} else {
-			e->Procedure.target_feature_disabled = true;
+	{
+		if (ac.require_target_feature.len != 0 && ac.enable_target_feature.len != 0) {
+			error(e->token, "A procedure cannot have both @(require_target_feature=\"...\") and @(enable_target_feature=\"...\")");
+		}
+
+		if (build_context.strict_target_features && ac.enable_target_feature.len != 0) {
+			ac.require_target_feature = ac.enable_target_feature;
+			ac.enable_target_feature.len = 0;
+		}
+
+		if (ac.require_target_feature.len != 0) {
+			pt->require_target_feature = ac.require_target_feature;
+			String invalid;
+			if (!check_target_feature_is_valid_globally(ac.require_target_feature, &invalid)) {
+				error(e->token, "Required target feature '%.*s' is not a valid target feature", LIT(invalid));
+			} else if (!check_target_feature_is_enabled(ac.require_target_feature, nullptr)) {
+				e->flags |= EntityFlag_Disabled;
+			}
+		} else if (ac.enable_target_feature.len != 0) {
+
+			// NOTE: disallow wasm, features on that arch are always global to the module.
+			if (is_arch_wasm()) {
+				error(e->token, "@(enable_target_feature=\"...\") is not allowed on wasm, features for wasm must be declared globally");
+			}
+
+			pt->enable_target_feature = ac.enable_target_feature;
+			String invalid;
+			if (!check_target_feature_is_valid_globally(ac.enable_target_feature, &invalid)) {
+				error(e->token, "Procedure enabled target feature '%.*s' is not a valid target feature", LIT(invalid));
+			}
 		}
-	} else if (ac.enable_target_feature.len != 0) {
-		enable_target_feature(e->token.pos, ac.enable_target_feature);
-		e->Procedure.target_feature = ac.enable_target_feature;
 	}
 
 	switch (e->Procedure.optimization_mode) {
@@ -1370,6 +1390,10 @@ gb_internal void check_proc_group_decl(CheckerContext *ctx, Entity *pg_entity, D
 			continue;
 		}
 
+		if (p->flags & EntityFlag_Disabled) {
+			continue;
+		}
+
 		String name = p->token.string;
 
 		for (isize k = j+1; k < pge->entities.count; k++) {
@@ -1387,6 +1411,10 @@ gb_internal void check_proc_group_decl(CheckerContext *ctx, Entity *pg_entity, D
 
 			ERROR_BLOCK();
 
+			if (q->flags & EntityFlag_Disabled) {
+				continue;
+			}
+
 			ProcTypeOverloadKind kind = are_proc_types_overload_safe(p->type, q->type);
 			bool both_have_where_clauses = false;
 			if (p->decl_info->proc_lit != nullptr && q->decl_info->proc_lit != nullptr) {
@@ -1423,6 +1451,7 @@ gb_internal void check_proc_group_decl(CheckerContext *ctx, Entity *pg_entity, D
 				break;
 			case ProcOverload_ParamCount:
 			case ProcOverload_ParamTypes:
+			case ProcOverload_TargetFeatures:
 				// This is okay :)
 				break;
 

+ 72 - 4
src/check_expr.cpp

@@ -1802,11 +1802,13 @@ gb_internal bool check_unary_op(CheckerContext *c, Operand *o, Token op) {
 	case Token_Not:
 		if (!is_type_boolean(type) || is_type_array_like(o->type)) {
 			ERROR_BLOCK();
-			str = expr_to_string(o->expr);
 			error(op, "Operator '%.*s' is only allowed on boolean expressions", LIT(op.string));
-			gb_string_free(str);
 			if (is_type_integer(type)) {
-				error_line("\tSuggestion: Did you mean to use the bitwise not operator '~'?\n");
+				str = expr_to_string(o->expr);
+				error_line("\tSuggestion: Did you mean to do one of the following?\n");
+				error_line("\t\t'%s == 0'?\n", str);
+				error_line("\t\tUse of the bitwise not operator '~'?\n");
+				gb_string_free(str);
 			}
 		} else {
 			o->type = t_untyped_bool;
@@ -6526,12 +6528,17 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c,
 		array_add(&proc_entities, proc);
 	}
 
+	int max_matched_features = 0;
 
 	gbString expr_name = expr_to_string(operand->expr);
 	defer (gb_string_free(expr_name));
 
 	for_array(i, procs) {
 		Entity *p = procs[i];
+		if (p->flags & EntityFlag_Disabled) {
+			continue;
+		}
+
 		Type *pt = base_type(p->type);
 		if (pt != nullptr && is_type_proc(pt)) {
 			CallArgumentData data = {};
@@ -6562,11 +6569,24 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c,
 				item.score += assign_score_function(1);
 			}
 
+			max_matched_features = gb_max(max_matched_features, matched_target_features(&pt->Proc));
+
 			item.index = index;
 			array_add(&valids, item);
 		}
 	}
 
+	if (max_matched_features > 0) {
+		for_array(i, valids) {
+			Entity *p = procs[valids[i].index];
+			Type *t = base_type(p->type);
+			GB_ASSERT(t->kind == Type_Proc);
+
+			int matched = matched_target_features(&t->Proc);
+			valids[i].score += assign_score_function(max_matched_features-matched);
+		}
+	}
+
 	if (valids.count > 1) {
 		array_sort(valids, valid_index_and_score_cmp);
 		i64 best_score = valids[0].score;
@@ -6708,7 +6728,11 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c,
 		ERROR_BLOCK();
 
 		error(operand->expr, "Ambiguous procedure group call '%s' that match with the given arguments", expr_name);
-		print_argument_types();
+		if (positional_operands.count == 0 && named_operands.count == 0) {
+			error_line("\tNo given arguments\n");
+		} else {
+			print_argument_types();
+		}
 
 		for (auto const &valid : valids) {
 			Entity *proc = proc_entities[valid.index];
@@ -7553,8 +7577,11 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c
 		}
 	}
 
+	bool is_call_inlined = false;
+
 	switch (inlining) {
 	case ProcInlining_inline:
+		is_call_inlined = true;
 		if (proc != nullptr) {
 			Entity *e = entity_from_expr(proc);
 			if (e != nullptr && e->kind == Entity_Procedure) {
@@ -7570,6 +7597,47 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c
 		break;
 	case ProcInlining_no_inline:
 		break;
+	case ProcInlining_none:
+		if (proc != nullptr) {
+			Entity *e = entity_from_expr(proc);
+			if (e != nullptr && e->kind == Entity_Procedure) {
+				DeclInfo *decl = e->decl_info;
+				if (decl->proc_lit) {
+					ast_node(pl, ProcLit, decl->proc_lit);
+					if (pl->inlining == ProcInlining_inline) {
+						is_call_inlined = true;
+					}
+				}
+			}
+		}
+	}
+
+	{
+		String invalid;
+		if (pt->kind == Type_Proc && pt->Proc.require_target_feature.len != 0) {
+			if (!check_target_feature_is_valid_for_target_arch(pt->Proc.require_target_feature, &invalid)) {
+				error(call, "Called procedure requires target feature '%.*s' which is invalid for the build target", LIT(invalid));
+			} else if (!check_target_feature_is_enabled(pt->Proc.require_target_feature, &invalid)) {
+				error(call, "Calling this procedure requires target feature '%.*s' to be enabled", LIT(invalid));
+			}
+		}
+
+		if (pt->kind == Type_Proc && pt->Proc.enable_target_feature.len != 0) {
+			if (!check_target_feature_is_valid_for_target_arch(pt->Proc.enable_target_feature, &invalid)) {
+				error(call, "Called procedure enables target feature '%.*s' which is invalid for the build target", LIT(invalid));
+			}
+
+			// NOTE: Due to restrictions in LLVM you can not inline calls with a superset of features.
+			if (is_call_inlined) {
+				GB_ASSERT(c->curr_proc_decl);
+				GB_ASSERT(c->curr_proc_decl->entity);
+				GB_ASSERT(c->curr_proc_decl->entity->type->kind == Type_Proc);
+				String scope_features = c->curr_proc_decl->entity->type->Proc.enable_target_feature;
+				if (!check_target_feature_is_superset_of(scope_features, pt->Proc.enable_target_feature, &invalid)) {
+					error(call, "Inlined procedure enables target feature '%.*s', this requires the calling procedure to at least enable the same feature", LIT(invalid));
+				}
+			}
+		}
 	}
 
 	operand->expr = call;

+ 20 - 5
src/check_type.cpp

@@ -797,11 +797,11 @@ gb_internal void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *nam
 	enum_type->Enum.scope = ctx->scope;
 
 	Type *base_type = t_int;
-	if (et->base_type != nullptr) {
+	if (unparen_expr(et->base_type) != nullptr) {
 		base_type = check_type(ctx, et->base_type);
 	}
 
-	if (base_type == nullptr || !is_type_integer(base_type)) {
+	if (base_type == nullptr || base_type == t_invalid || !is_type_integer(base_type)) {
 		error(node, "Base type for enumeration must be an integer");
 		return;
 	}
@@ -1080,6 +1080,8 @@ gb_internal void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type,
 			array_add(&tags, tag);
 
 			add_entity_use(ctx, field, e);
+
+			total_bit_size += bit_size_u8;
 		}
 	}
 
@@ -1094,7 +1096,7 @@ gb_internal void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type,
 
 	if (total_bit_size > maximum_bit_size) {
 		gbString s = type_to_string(backing_type);
-		error(node, "The numbers required %llu exceeds the backing type's (%s) bit size %llu",
+		error(node, "The total bit size of a bit_field's fields (%llu) must fit into its backing type's (%s) bit size of %llu",
 		      cast(unsigned long long)total_bit_size,
 		      s,
 		      cast(unsigned long long)maximum_bit_size);
@@ -1428,6 +1430,10 @@ gb_internal bool check_type_specialization_to(CheckerContext *ctx, Type *special
 		bool can_convert = check_cast_internal(ctx, &o, specialization);
 		return can_convert;
 	} else if (t->kind == Type_Struct) {
+		if (t->Struct.polymorphic_parent == nullptr &&
+		    t == s) {
+			return true;
+		}
 		if (t->Struct.polymorphic_parent == specialization) {
 			return true;
 		}
@@ -1477,6 +1483,10 @@ gb_internal bool check_type_specialization_to(CheckerContext *ctx, Type *special
 			return true;
 		}
 	} else if (t->kind == Type_Union) {
+		if (t->Union.polymorphic_parent == nullptr &&
+		    t == s) {
+			return true;
+		}
 		if (t->Union.polymorphic_parent == specialization) {
 			return true;
 		}
@@ -2017,8 +2027,8 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para
 				}
 
 				if (p->flags&FieldFlag_no_alias) {
-					if (!is_type_pointer(type)) {
-						error(name, "'#no_alias' can only be applied pointer typed parameters");
+					if (!is_type_pointer(type) && !is_type_multi_pointer(type)) {
+						error(name, "'#no_alias' can only be applied pointer or multi-pointer typed parameters");
 						p->flags &= ~FieldFlag_no_alias; // Remove the flag
 					}
 				}
@@ -3263,6 +3273,11 @@ gb_internal bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, T
 	case_end;
 
 	case_ast_node(pe, ParenExpr, e);
+		if (pe->expr == nullptr) {
+			error(e, "Expected an expression or type within the parentheses");
+			*type = t_invalid;
+			return true;
+		}
 		*type = check_type_expr(ctx, pe->expr, named_type);
 		set_base_type(named_type, *type);
 		return true;

+ 4 - 0
src/checker_builtin_procs.hpp

@@ -44,6 +44,8 @@ enum BuiltinProcId {
 	// "Intrinsics"
 	BuiltinProc_is_package_imported,
 
+	BuiltinProc_has_target_feature,
+
 	BuiltinProc_transpose,
 	BuiltinProc_outer_product,
 	BuiltinProc_hadamard_product,
@@ -354,6 +356,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
 	// "Intrinsics"
 	{STR_LIT("is_package_imported"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 
+	{STR_LIT("has_target_feature"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+
 	{STR_LIT("transpose"),        1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("outer_product"),    2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("hadamard_product"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},

+ 1 - 3
src/entity.cpp

@@ -252,10 +252,8 @@ struct Entity {
 			bool    is_foreign                 : 1;
 			bool    is_export                  : 1;
 			bool    generated_from_polymorphic : 1;
-			bool    target_feature_disabled    : 1;
 			bool    entry_point_only           : 1;
 			bool    has_instrumentation        : 1;
-			String  target_feature;
 		} Procedure;
 		struct {
 			Array<Entity *> entities;
@@ -502,4 +500,4 @@ gb_internal bool is_entity_local_variable(Entity *e) {
 
 	return ((e->scope->flags &~ ScopeFlag_ContextDefined) == 0) ||
 	       (e->scope->flags & ScopeFlag_Proc) != 0;
-}
+}

+ 64 - 39
src/error.cpp

@@ -12,7 +12,7 @@ struct ErrorValue {
 };
 
 struct ErrorCollector {
-	TokenPos prev;
+	// TokenPos prev; // no point collecting because of the mulithreaded nature
 	std::atomic<i64>  count;
 	std::atomic<i64>  warning_count;
 	std::atomic<bool> in_block;
@@ -383,14 +383,13 @@ gb_internal void error_va(TokenPos const &pos, TokenPos end, char const *fmt, va
 	}
 
 	push_error_value(pos, ErrorValue_Error);
-	// NOTE(bill): Duplicate error, skip it
 	if (pos.line == 0) {
 		error_out_empty();
 		error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red);
 		error_out_va(fmt, va);
 		error_out("\n");
-	} else if (global_error_collector.prev != pos) {
-		global_error_collector.prev = pos;
+	} else {
+		// global_error_collector.prev = pos;
 		if (json_errors()) {
 			error_out_empty();
 		} else {
@@ -402,10 +401,6 @@ gb_internal void error_va(TokenPos const &pos, TokenPos end, char const *fmt, va
 		error_out_va(fmt, va);
 		error_out("\n");
 		show_error_on_line(pos, end);
-	} else {
-		global_error_collector.curr_error_value = {};
-		global_error_collector.curr_error_value_set.store(false);
-		global_error_collector.count.fetch_sub(1);
 	}
 	try_pop_error_value();
 	mutex_unlock(&global_error_collector.mutex);
@@ -422,14 +417,13 @@ gb_internal void warning_va(TokenPos const &pos, TokenPos end, char const *fmt,
 	push_error_value(pos, ErrorValue_Warning);
 
 	if (!global_ignore_warnings()) {
-		// NOTE(bill): Duplicate error, skip it
 		if (pos.line == 0) {
 			error_out_empty();
 			error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow);
 			error_out_va(fmt, va);
 			error_out("\n");
-		} else if (global_error_collector.prev != pos) {
-			global_error_collector.prev = pos;
+		} else {
+			// global_error_collector.prev = pos;
 			if (json_errors()) {
 				error_out_empty();
 			} else {
@@ -452,21 +446,20 @@ gb_internal void error_line_va(char const *fmt, va_list va) {
 
 gb_internal void error_no_newline_va(TokenPos const &pos, char const *fmt, va_list va) {
 	global_error_collector.count.fetch_add(1);
+	mutex_lock(&global_error_collector.mutex);
 	if (global_error_collector.count.load() > MAX_ERROR_COLLECTOR_COUNT()) {
 		print_all_errors();
 		gb_exit(1);
 	}
-	mutex_lock(&global_error_collector.mutex);
 
 	push_error_value(pos, ErrorValue_Error);
 
-	// NOTE(bill): Duplicate error, skip it
 	if (pos.line == 0) {
 		error_out_empty();
 		error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red);
 		error_out_va(fmt, va);
-	} else if (global_error_collector.prev != pos) {
-		global_error_collector.prev = pos;
+	} else {
+		// global_error_collector.prev = pos;
 		if (json_errors()) {
 			error_out_empty();
 		} else {
@@ -485,17 +478,21 @@ gb_internal void error_no_newline_va(TokenPos const &pos, char const *fmt, va_li
 
 gb_internal void syntax_error_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) {
 	global_error_collector.count.fetch_add(1);
+	mutex_lock(&global_error_collector.mutex);
 	if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT()) {
 		print_all_errors();
 		gb_exit(1);
 	}
-	mutex_lock(&global_error_collector.mutex);
 
 	push_error_value(pos, ErrorValue_Warning);
 
-	// NOTE(bill): Duplicate error, skip it
-	if (global_error_collector.prev != pos) {
-		global_error_collector.prev = pos;
+	if (pos.line == 0) {
+		error_out_empty();
+		error_out_coloured("Syntax Error: ", TerminalStyle_Normal, TerminalColour_Red);
+		error_out_va(fmt, va);
+		error_out("\n");
+	} else {
+		// global_error_collector.prev = pos;
 		if (json_errors()) {
 			error_out_empty();
 		} else {
@@ -505,11 +502,6 @@ gb_internal void syntax_error_va(TokenPos const &pos, TokenPos end, char const *
 		error_out_va(fmt, va);
 		error_out("\n");
 		show_error_on_line(pos, end);
-	} else if (pos.line == 0) {
-		error_out_empty();
-		error_out_coloured("Syntax Error: ", TerminalStyle_Normal, TerminalColour_Red);
-		error_out_va(fmt, va);
-		error_out("\n");
 	}
 
 	try_pop_error_value();
@@ -518,22 +510,21 @@ gb_internal void syntax_error_va(TokenPos const &pos, TokenPos end, char const *
 
 gb_internal void syntax_error_with_verbose_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) {
 	global_error_collector.count.fetch_add(1);
+	mutex_lock(&global_error_collector.mutex);
 	if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT()) {
 		print_all_errors();
 		gb_exit(1);
 	}
-	mutex_lock(&global_error_collector.mutex);
 
 	push_error_value(pos, ErrorValue_Warning);
 
-	// NOTE(bill): Duplicate error, skip it
 	if (pos.line == 0) {
 		error_out_empty();
 		error_out_coloured("Syntax_Error: ", TerminalStyle_Normal, TerminalColour_Red);
 		error_out_va(fmt, va);
 		error_out("\n");
-	} else if (global_error_collector.prev != pos) {
-		global_error_collector.prev = pos;
+	} else {
+		// global_error_collector.prev = pos;
 		if (json_errors()) {
 			error_out_empty();
 		} else {
@@ -564,9 +555,13 @@ gb_internal void syntax_warning_va(TokenPos const &pos, TokenPos end, char const
 	push_error_value(pos, ErrorValue_Warning);
 
 	if (!global_ignore_warnings()) {
-		// NOTE(bill): Duplicate error, skip it
-		if (global_error_collector.prev != pos) {
-			global_error_collector.prev = pos;
+		if (pos.line == 0) {
+			error_out_empty();
+			error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow);
+			error_out_va(fmt, va);
+			error_out("\n");
+		} else {
+			// global_error_collector.prev = pos;
 			if (json_errors()) {
 				error_out_empty();
 			} else {
@@ -576,11 +571,6 @@ gb_internal void syntax_warning_va(TokenPos const &pos, TokenPos end, char const
 			error_out_va(fmt, va);
 			error_out("\n");
 			// show_error_on_line(pos, end);
-		} else if (pos.line == 0) {
-			error_out_empty();
-			error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow);
-			error_out_va(fmt, va);
-			error_out("\n");
 		}
 	}
 
@@ -705,9 +695,42 @@ gb_internal void print_all_errors(void) {
 
 	GB_ASSERT(any_errors() || any_warnings());
 
-
 	array_sort(global_error_collector.error_values, error_value_cmp);
 
+
+	{ // NOTE(bill): merge neighbouring errors
+		isize default_lines_to_skip = 1;
+		if (show_error_line()) {
+			// NOTE(bill): this will always be 2 extra lines
+			default_lines_to_skip += 2;
+		}
+
+		ErrorValue *prev_ev = nullptr;
+		for (isize i = 0; i < global_error_collector.error_values.count; /**/) {
+			ErrorValue &ev = global_error_collector.error_values[i];
+
+			if (prev_ev && prev_ev->pos == ev.pos) {
+				String_Iterator it = {{ev.msg.data, ev.msg.count}, 0};
+
+				for (isize lines_to_skip = default_lines_to_skip; lines_to_skip > 0; lines_to_skip -= 1) {
+					String line = string_split_iterator(&it, '\n');
+					if (line.len == 0) {
+						break;
+					}
+				}
+
+				if (it.str.len-it.pos > 0) {
+					array_add_elems(&prev_ev->msg, it.str.text+it.pos, it.str.len-it.pos);
+				}
+				array_free(&ev.msg);
+				array_ordered_remove(&global_error_collector.error_values, i);
+			} else {
+				prev_ev = &ev;
+				i += 1;
+			}
+		}
+	}
+
 	gbString res = gb_string_make(heap_allocator(), "");
 	defer (gb_string_free(res));
 
@@ -715,6 +738,7 @@ gb_internal void print_all_errors(void) {
 		res = gb_string_append_fmt(res, "{\n");
 		res = gb_string_append_fmt(res, "\t\"error_count\": %td,\n", global_error_collector.error_values.count);
 		res = gb_string_append_fmt(res, "\t\"errors\": [\n");
+
 		for_array(i, global_error_collector.error_values) {
 			ErrorValue ev = global_error_collector.error_values[i];
 
@@ -728,9 +752,8 @@ gb_internal void print_all_errors(void) {
 			}
 			res = gb_string_append_fmt(res, "\",\n");
 
-			res = gb_string_append_fmt(res, "\t\t\t\"pos\": {\n");
-
 			if (ev.pos.file_id) {
+				res = gb_string_append_fmt(res, "\t\t\t\"pos\": {\n");
 				res = gb_string_append_fmt(res, "\t\t\t\t\"file\": \"");
 				String file = get_file_path_string(ev.pos.file_id);
 				for (isize k = 0; k < file.len; k++) {
@@ -743,6 +766,8 @@ gb_internal void print_all_errors(void) {
 				i32 end_column = gb_max(ev.end.column, ev.pos.column);
 				res = gb_string_append_fmt(res, "\t\t\t\t\"end_column\": %d\n", end_column);
 				res = gb_string_append_fmt(res, "\t\t\t},\n");
+			} else {
+				res = gb_string_append_fmt(res, "\t\t\t\"pos\": null,\n");
 			}
 
 			res = gb_string_append_fmt(res, "\t\t\t\"msgs\": [\n");

+ 2 - 0
src/gb/gb.h

@@ -3009,6 +3009,8 @@ gb_inline u32 gb_thread_current_id(void) {
 	thread_id = gettid();
 #elif defined(GB_SYSTEM_HAIKU)
 	thread_id = find_thread(NULL);
+#elif defined(GB_SYSTEM_FREEBSD)
+	thread_id = pthread_getthreadid_np();
 #else
 	#error Unsupported architecture for gb_thread_current_id()
 #endif

+ 1 - 1
src/linker.cpp

@@ -432,7 +432,7 @@ gb_internal i32 linker_stage(LinkerData *gen) {
 							if (string_ends_with(lib, str_lit(".a")) || string_ends_with(lib, str_lit(".o"))) {
 								// static libs and object files, absolute full path relative to the file in which the lib was imported from
 								lib_str = gb_string_append_fmt(lib_str, " -l:\"%.*s\" ", LIT(lib));
-							} else if (string_ends_with(lib, str_lit(".so"))) {
+							} else if (string_ends_with(lib, str_lit(".so")) || string_contains_string(lib, str_lit(".so."))) {
 								// dynamic lib, relative path to executable
 								// NOTE(vassvik): it is the user's responsibility to make sure the shared library files are visible
 								//                at runtime to the executable

+ 45 - 59
src/llvm_backend.cpp

@@ -41,6 +41,37 @@ String get_default_microarchitecture() {
 	return default_march;
 }
 
+String get_final_microarchitecture() {
+	BuildContext *bc = &build_context;
+
+	String microarch = bc->microarch;
+	if (microarch.len == 0) {
+		microarch = get_default_microarchitecture();
+	} else if (microarch == str_lit("native")) {
+		microarch = make_string_c(LLVMGetHostCPUName());
+	}
+	return microarch;
+}
+
+gb_internal String get_default_features() {
+	BuildContext *bc = &build_context;
+
+	int off = 0;
+	for (int i = 0; i < bc->metrics.arch; i += 1) {
+		off += target_microarch_counts[i];
+	}
+
+	String microarch = get_final_microarchitecture();
+	for (int i = off; i < off+target_microarch_counts[bc->metrics.arch]; i += 1) {
+		if (microarch_features_list[i].microarch == microarch) {
+			return microarch_features_list[i].features;
+		}
+	}
+
+	GB_PANIC("unknown microarch");
+	return {};
+}
+
 gb_internal void lb_add_foreign_library_path(lbModule *m, Entity *e) {
 	if (e == nullptr) {
 		return;
@@ -2468,69 +2499,24 @@ gb_internal bool lb_generate_code(lbGenerator *gen) {
 		code_mode = LLVMCodeModelKernel;
 	}
 
-	String      host_cpu_name = copy_string(permanent_allocator(), make_string_c(LLVMGetHostCPUName()));
-	String      llvm_cpu      = get_default_microarchitecture();
-	char const *llvm_features = "";
-	if (build_context.microarch.len != 0) {
-		if (build_context.microarch == "native") {
-			llvm_cpu = host_cpu_name;
-		} else {
-			llvm_cpu = copy_string(permanent_allocator(), build_context.microarch);
-		}
-		if (llvm_cpu == host_cpu_name) {
-			llvm_features = LLVMGetHostCPUFeatures();
+	String llvm_cpu = get_final_microarchitecture();
+
+	gbString llvm_features = gb_string_make(temporary_allocator(), "");
+	String_Iterator it = {build_context.target_features_string, 0};
+	bool first = true;
+	for (;;) {
+		String str = string_split_iterator(&it, ',');
+		if (str == "") break;
+		if (!first) {
+			llvm_features = gb_string_appendc(llvm_features, ",");
 		}
-	}
+		first = false;
 
-	// NOTE(Jeroen): Uncomment to get the list of supported microarchitectures.
-	/*
-	if (build_context.microarch == "?") {
-		string_set_add(&build_context.target_features_set, str_lit("+cpuhelp"));
+		llvm_features = gb_string_appendc(llvm_features, "+");
+		llvm_features = gb_string_append_length(llvm_features, str.text, str.len);
 	}
-	*/
 
-	if (build_context.target_features_set.entries.count != 0) {
-		// Prefix all of the features with a `+`, because we are
-		// enabling additional features.
-		char const *additional_features = target_features_set_to_cstring(permanent_allocator(), false, true);
-
-		String f_string = make_string_c(llvm_features);
-		String a_string = make_string_c(additional_features);
-		isize f_len = f_string.len;
-
-		if (f_len == 0) {
-			// The common case is that llvm_features is empty, so
-			// the target_features_set additions can be used as is.
-			llvm_features = additional_features;
-		} else {
-			// The user probably specified `-microarch:native`, so
-			// llvm_features is populated by LLVM's idea of what
-			// the host CPU supports.
-			//
-			// As far as I can tell, (which is barely better than
-			// wild guessing), a bitset is formed by parsing the
-			// string left to right.
-			//
-			// So, llvm_features + ',' + additonal_features, will
-			// makes the target_features_set override llvm_features.
-
-			char *tmp = gb_alloc_array(permanent_allocator(), char, f_len + 1 + a_string.len + 1);
-			isize len = 0;
-
-			// tmp = f_string
-			gb_memmove(tmp, f_string.text, f_string.len);
-			len += f_string.len;
-			// tmp += ','
-			tmp[len++] = ',';
-			// tmp += a_string
-			gb_memmove(tmp + len, a_string.text, a_string.len);
-			len += a_string.len;
-			// tmp += NUL
-			tmp[len++] = 0;
-
-			llvm_features = tmp;
-		}
-	}
+	debugf("CPU: %.*s, Features: %s\n", LIT(llvm_cpu), llvm_features);	
 
 	// GB_ASSERT_MSG(LLVMTargetHasAsmBackend(target));
 

+ 17 - 10
src/llvm_backend_proc.cpp

@@ -177,17 +177,24 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i
 		break;
 	}
 
-	if (!entity->Procedure.target_feature_disabled &&
-	    entity->Procedure.target_feature.len != 0) {
-	    	auto features = split_by_comma(entity->Procedure.target_feature);
-		for_array(i, features) {
-			String feature = features[i];
-			LLVMAttributeRef ref = LLVMCreateStringAttribute(
-				m->ctx,
-				cast(char const *)feature.text, cast(unsigned)feature.len,
-				"", 0);
-			LLVMAddAttributeAtIndex(p->value, LLVMAttributeIndex_FunctionIndex, ref);
+	if (pt->Proc.enable_target_feature.len != 0) {
+		gbString feature_str = gb_string_make(temporary_allocator(), "");
+
+		String_Iterator it = {pt->Proc.enable_target_feature, 0};
+		bool first = true;
+		for (;;) {
+			String str = string_split_iterator(&it, ',');
+			if (str == "") break;
+			if (!first) {
+				feature_str = gb_string_appendc(feature_str, ",");
+			}
+			first = false;
+
+			feature_str = gb_string_appendc(feature_str, "+");
+			feature_str = gb_string_append_length(feature_str, str.text, str.len);
 		}
+
+		lb_add_attribute_to_proc_with_string(m, p->value, make_string_c("target-features"), make_string_c(feature_str));
 	}
 
 	if (entity->flags & EntityFlag_Cold) {

+ 2 - 1
src/llvm_backend_utility.cpp

@@ -1708,7 +1708,8 @@ gb_internal lbValue lb_emit_mul_add(lbProcedure *p, lbValue a, lbValue b, lbValu
 	if (is_possible) {
 		switch (build_context.metrics.arch) {
 		case TargetArch_amd64:
-			if (type_size_of(t) == 2) {
+			// NOTE: using the intrinsic when not supported causes slow codegen (See #2928).
+			if (type_size_of(t) == 2 || !check_target_feature_is_enabled(str_lit("fma"), nullptr)) {
 				is_possible = false;
 			}
 			break;

+ 73 - 6
src/main.cpp

@@ -272,6 +272,7 @@ enum BuildFlagKind {
 	BuildFlag_ExtraAssemblerFlags,
 	BuildFlag_Microarch,
 	BuildFlag_TargetFeatures,
+	BuildFlag_StrictTargetFeatures,
 	BuildFlag_MinimumOSVersion,
 	BuildFlag_NoThreadLocal,
 
@@ -467,6 +468,7 @@ gb_internal bool parse_build_flags(Array<String> args) {
 	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_TargetFeatures,          str_lit("target-features"),           BuildFlagParam_String,  Command__does_build);
+	add_flag(&build_flags, BuildFlag_StrictTargetFeatures,    str_lit("strict-target-features"),    BuildFlagParam_None,    Command__does_build);
 	add_flag(&build_flags, BuildFlag_MinimumOSVersion,        str_lit("minimum-os-version"),        BuildFlagParam_String,  Command__does_build);
 
 	add_flag(&build_flags, BuildFlag_RelocMode,               str_lit("reloc-mode"),                BuildFlagParam_String,  Command__does_build);
@@ -1083,6 +1085,9 @@ gb_internal bool parse_build_flags(Array<String> args) {
 							string_to_lower(&build_context.target_features_string);
 							break;
 						}
+					    case BuildFlag_StrictTargetFeatures:
+						    build_context.strict_target_features = true;
+						    break;
 						case BuildFlag_MinimumOSVersion: {
 							GB_ASSERT(value.kind == ExactValue_String);
 							build_context.minimum_os_version_string = value.value_string;
@@ -1981,7 +1986,20 @@ gb_internal void print_show_help(String const arg0, String const &command) {
 		print_usage_line(2, "Examples:");
 		print_usage_line(3, "-microarch:sandybridge");
 		print_usage_line(3, "-microarch:native");
-		print_usage_line(3, "-microarch:? for a list");
+		print_usage_line(3, "-microarch:\"?\" for a list");
+		print_usage_line(0, "");
+
+		print_usage_line(1, "-target-features:<string>");
+		print_usage_line(2, "Specifies CPU features to enable on top of the enabled features implied by -microarch.");
+		print_usage_line(2, "Examples:");
+		print_usage_line(3, "-target-features:atomics");
+		print_usage_line(3, "-target-features:\"sse2,aes\"");
+		print_usage_line(3, "-target-features:\"?\" for a list");
+		print_usage_line(0, "");
+
+		print_usage_line(1, "-strict-target-features");
+		print_usage_line(2, "Makes @(enable_target_features=\"...\") behave the same way as @(require_target_features=\"...\").");
+		print_usage_line(2, "This enforces that all generated code uses features supported by the combination of -target, -microarch, and -target-features.");
 		print_usage_line(0, "");
 
 		print_usage_line(1, "-reloc-mode:<string>");
@@ -2663,7 +2681,7 @@ int main(int arg_count, char const **arg_ptr) {
 
 	// Check chosen microarchitecture. If not found or ?, print list.
 	bool print_microarch_list = true;
-	if (build_context.microarch.len == 0) {
+	if (build_context.microarch.len == 0 || build_context.microarch == str_lit("native")) {
 		// Autodetect, no need to print list.
 		print_microarch_list = false;
 	} else {
@@ -2680,6 +2698,11 @@ int main(int arg_count, char const **arg_ptr) {
 		}
 	}
 
+	// Set and check build paths...
+	if (!init_build_paths(init_filename)) {
+		return 1;
+	}
+
 	String default_march = get_default_microarchitecture();
 	if (print_microarch_list) {
 		if (build_context.microarch != "?") {
@@ -2703,13 +2726,57 @@ int main(int arg_count, char const **arg_ptr) {
 		return 0;
 	}
 
-	// Set and check build paths...
-	if (!init_build_paths(init_filename)) {
-		return 1;
+	String march = get_final_microarchitecture();
+	String default_features = get_default_features();
+	{
+		String_Iterator it = {default_features, 0};
+		for (;;) {
+			String str = string_split_iterator(&it, ',');
+			if (str == "") break;
+			string_set_add(&build_context.target_features_set, str);
+		}
+	}
+
+	if (build_context.target_features_string.len != 0) {
+		String_Iterator target_it = {build_context.target_features_string, 0};
+		for (;;) {
+			String item = string_split_iterator(&target_it, ',');
+			if (item == "") break;
+
+			String invalid;
+			if (!check_target_feature_is_valid_for_target_arch(item, &invalid) && item != str_lit("help")) {
+				if (item != str_lit("?")) {
+					gb_printf_err("Unkown target feature '%.*s'.\n", LIT(invalid));
+				}
+				gb_printf("Possible -target-features for target %.*s are:\n", LIT(target_arch_names[build_context.metrics.arch]));
+				gb_printf("\n");
+
+				String feature_list = target_features_list[build_context.metrics.arch];
+				String_Iterator it = {feature_list, 0};
+				for (;;) {
+					String str = string_split_iterator(&it, ',');
+					if (str == "") break;
+					if (check_single_target_feature_is_valid(default_features, str)) {
+						if (has_ansi_terminal_colours()) {
+							gb_printf("\t%.*s\x1b[38;5;244m (implied by target microarch %.*s)\x1b[0m\n", LIT(str), LIT(march));
+						} else {
+							gb_printf("\t%.*s (implied by current microarch %.*s)\n", LIT(str), LIT(march));
+						}
+					} else {
+						gb_printf("\t%.*s\n", LIT(str));
+					}
+				}
+
+				return 1;
+			}
+
+			string_set_add(&build_context.target_features_set, item);
+		}
 	}
 
 	if (build_context.show_debug_messages) {
-		debugf("Selected microarch: %.*s\n", LIT(default_march));
+		debugf("Selected microarch: %.*s\n", LIT(march));
+		debugf("Default microarch features: %.*s\n", LIT(default_features));
 		for_array(i, build_context.build_paths) {
 			String build_path = path_to_string(heap_allocator(), build_context.build_paths[i]);
 			debugf("build_paths[%ld]: %.*s\n", i, LIT(build_path));

+ 5 - 1
src/parser.cpp

@@ -3499,6 +3499,10 @@ gb_internal Ast *parse_type(AstFile *f) {
 		Token token = advance_token(f);
 		syntax_error(token, "Expected a type");
 		return ast_bad_expr(f, token, f->curr_token);
+	} else if (type->kind == Ast_ParenExpr &&
+	           unparen_expr(type) == nullptr) {
+		syntax_error(type, "Expected a type within the parentheses");
+		return ast_bad_expr(f, type->ParenExpr.open, type->ParenExpr.close);
 	}
 	return type;
 }
@@ -5710,7 +5714,7 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node
 		//                 working directory of the exe to the library search paths.
 		//                 Static libraries can be linked directly with the full pathname
 		//
-		if (node->kind == Ast_ForeignImportDecl && string_ends_with(file_str, str_lit(".so"))) {
+		if (node->kind == Ast_ForeignImportDecl && (string_ends_with(file_str, str_lit(".so")) || string_contains_string(file_str, str_lit(".so.")))) {
 			*path = file_str;
 			return true;
 		}

+ 19 - 0
src/string.cpp

@@ -328,6 +328,25 @@ gb_internal bool string_contains_char(String const &s, u8 c) {
 	return false;
 }
 
+gb_internal bool string_contains_string(String const &haystack, String const &needle) {
+    if (needle.len == 0) return true;
+    if (needle.len > haystack.len) return false;
+
+    for (isize i = 0; i <= haystack.len - needle.len; i++) {
+        bool found = true;
+        for (isize j = 0; j < needle.len; j++) {
+            if (haystack[i + j] != needle[j]) {
+                found = false;
+                break;
+            }
+        }
+        if (found) {
+            return true;
+        }
+    }
+    return false;
+}
+
 gb_internal String filename_from_path(String s) {
 	isize i = string_extension_position(s);
 	if (i >= 0) {

+ 292 - 0
src/string_map.cpp

@@ -24,6 +24,296 @@ gb_internal gb_inline StringHashKey string_hash_string(String const &s) {
 	return hash_key;
 }
 
+
+#if 1 /* old string map */
+
+template <typename T>
+struct StringMapEntry {
+	String        key;
+	u32           hash;
+	MapIndex      next;
+	T             value;
+};
+
+template <typename T>
+struct StringMap {
+	MapIndex *         hashes;
+	usize              hashes_count;
+	StringMapEntry<T> *entries;
+	u32                count;
+	u32                entries_capacity;
+};
+
+
+template <typename T> gb_internal void string_map_init    (StringMap<T> *h, usize capacity = 16);
+template <typename T> gb_internal void string_map_destroy (StringMap<T> *h);
+
+template <typename T> gb_internal T *  string_map_get     (StringMap<T> *h, char const *key);
+template <typename T> gb_internal T *  string_map_get     (StringMap<T> *h, String const &key);
+template <typename T> gb_internal T *  string_map_get     (StringMap<T> *h, StringHashKey const &key);
+
+template <typename T> gb_internal T &  string_map_must_get(StringMap<T> *h, char const *key);
+template <typename T> gb_internal T &  string_map_must_get(StringMap<T> *h, String const &key);
+template <typename T> gb_internal T &  string_map_must_get(StringMap<T> *h, StringHashKey const &key);
+
+template <typename T> gb_internal void string_map_set     (StringMap<T> *h, char const *key,   T const &value);
+template <typename T> gb_internal void string_map_set     (StringMap<T> *h, String const &key, T const &value);
+template <typename T> gb_internal void string_map_set     (StringMap<T> *h, StringHashKey const &key, T const &value);
+
+// template <typename T> gb_internal void string_map_remove  (StringMap<T> *h, StringHashKey const &key);
+template <typename T> gb_internal void string_map_clear   (StringMap<T> *h);
+template <typename T> gb_internal void string_map_grow    (StringMap<T> *h);
+template <typename T> gb_internal void string_map_reserve (StringMap<T> *h, usize new_count);
+
+gb_internal gbAllocator string_map_allocator(void) {
+	return heap_allocator();
+}
+
+template <typename T>
+gb_internal gb_inline void string_map_init(StringMap<T> *h, usize capacity) {
+	capacity = next_pow2_isize(capacity);
+	string_map_reserve(h, capacity);
+}
+
+template <typename T>
+gb_internal gb_inline void string_map_destroy(StringMap<T> *h) {
+	gb_free(string_map_allocator(), h->hashes);
+	gb_free(string_map_allocator(), h->entries);
+}
+
+
+template <typename T>
+gb_internal void string_map__resize_hashes(StringMap<T> *h, usize count) {
+	h->hashes_count = cast(u32)resize_array_raw(&h->hashes, string_map_allocator(), h->hashes_count, count, MAP_CACHE_LINE_SIZE);
+}
+
+
+template <typename T>
+gb_internal void string_map__reserve_entries(StringMap<T> *h, usize capacity) {
+	h->entries_capacity = cast(u32)resize_array_raw(&h->entries, string_map_allocator(), h->entries_capacity, capacity, MAP_CACHE_LINE_SIZE);
+}
+
+
+template <typename T>
+gb_internal MapIndex string_map__add_entry(StringMap<T> *h, u32 hash, String const &key) {
+	StringMapEntry<T> e = {};
+	e.key = key;
+	e.hash = hash;
+	e.next = MAP_SENTINEL;
+	if (h->count+1 >= h->entries_capacity) {
+		string_map__reserve_entries(h, gb_max(h->entries_capacity*2, 4));
+	}
+	h->entries[h->count++] = e;
+	return cast(MapIndex)(h->count-1);
+}
+
+template <typename T>
+gb_internal MapFindResult string_map__find(StringMap<T> *h, u32 hash, String const &key) {
+	MapFindResult fr = {MAP_SENTINEL, MAP_SENTINEL, MAP_SENTINEL};
+	if (h->hashes_count != 0) {
+		fr.hash_index = cast(MapIndex)(hash & (h->hashes_count-1));
+		fr.entry_index = h->hashes[fr.hash_index];
+		while (fr.entry_index != MAP_SENTINEL) {
+			auto *entry = &h->entries[fr.entry_index];
+			if (entry->hash == hash && entry->key == key) {
+				return fr;
+			}
+			fr.entry_prev = fr.entry_index;
+			fr.entry_index = entry->next;
+		}
+	}
+	return fr;
+}
+
+template <typename T>
+gb_internal MapFindResult string_map__find_from_entry(StringMap<T> *h, StringMapEntry<T> *e) {
+	MapFindResult fr = {MAP_SENTINEL, MAP_SENTINEL, MAP_SENTINEL};
+	if (h->hashes_count != 0) {
+		fr.hash_index  = cast(MapIndex)(e->hash & (h->hashes_count-1));
+		fr.entry_index = h->hashes[fr.hash_index];
+		while (fr.entry_index != MAP_SENTINEL) {
+			auto *entry = &h->entries[fr.entry_index];
+			if (entry == e) {
+				return fr;
+			}
+			fr.entry_prev = fr.entry_index;
+			fr.entry_index = entry->next;
+		}
+	}
+	return fr;
+}
+
+template <typename T>
+gb_internal b32 string_map__full(StringMap<T> *h) {
+	return 0.75f * h->hashes_count <= h->count;
+}
+
+template <typename T>
+gb_inline void string_map_grow(StringMap<T> *h) {
+	isize new_count = gb_max(h->hashes_count<<1, 16);
+	string_map_reserve(h, new_count);
+}
+
+
+template <typename T>
+gb_internal void string_map_reset_entries(StringMap<T> *h) {
+	for (u32 i = 0; i < h->hashes_count; i++) {
+		h->hashes[i] = MAP_SENTINEL;
+	}
+	for (isize i = 0; i < h->count; i++) {
+		MapFindResult fr;
+		StringMapEntry<T> *e = &h->entries[i];
+		e->next = MAP_SENTINEL;
+		fr = string_map__find_from_entry(h, e);
+		if (fr.entry_prev == MAP_SENTINEL) {
+			h->hashes[fr.hash_index] = cast(MapIndex)i;
+		} else {
+			h->entries[fr.entry_prev].next = cast(MapIndex)i;
+		}
+	}
+}
+
+template <typename T>
+gb_internal void string_map_reserve(StringMap<T> *h, usize cap) {
+	if (h->count*2 < h->hashes_count) {
+		return;
+	}
+	string_map__reserve_entries(h, cap);
+	string_map__resize_hashes(h,   cap*2);
+	string_map_reset_entries(h);
+}
+
+template <typename T>
+gb_internal T *string_map_get(StringMap<T> *h, u32 hash, String const &key) {
+	MapFindResult fr = {MAP_SENTINEL, MAP_SENTINEL, MAP_SENTINEL};
+	if (h->hashes_count != 0) {
+		fr.hash_index = cast(MapIndex)(hash & (h->hashes_count-1));
+		fr.entry_index = h->hashes[fr.hash_index];
+		while (fr.entry_index != MAP_SENTINEL) {
+			auto *entry = &h->entries[fr.entry_index];
+			if (entry->hash == hash && entry->key == key) {
+				return &entry->value;
+			}
+			fr.entry_prev = fr.entry_index;
+			fr.entry_index = entry->next;
+		}
+	}
+	return nullptr;
+}
+
+
+template <typename T>
+gb_internal gb_inline T *string_map_get(StringMap<T> *h, StringHashKey const &key) {
+	return string_map_get(h, key.hash, key.string);
+}
+
+template <typename T>
+gb_internal gb_inline T *string_map_get(StringMap<T> *h, String const &key) {
+	return string_map_get(h, string_hash(key), key);
+}
+
+template <typename T>
+gb_internal gb_inline T *string_map_get(StringMap<T> *h, char const *key) {
+	String k = make_string_c(key);
+	return string_map_get(h, string_hash(k), k);
+}
+
+template <typename T>
+gb_internal T &string_map_must_get(StringMap<T> *h, u32 hash, String const &key) {
+	isize index = string_map__find(h, hash, key).entry_index;
+	GB_ASSERT(index != MAP_SENTINEL);
+	return h->entries[index].value;
+}
+
+template <typename T>
+gb_internal T &string_map_must_get(StringMap<T> *h, StringHashKey const &key) {
+	return string_map_must_get(h, key.hash, key.string);
+}
+
+template <typename T>
+gb_internal gb_inline T &string_map_must_get(StringMap<T> *h, String const &key) {
+	return string_map_must_get(h, string_hash(key), key);
+}
+
+template <typename T>
+gb_internal gb_inline T &string_map_must_get(StringMap<T> *h, char const *key) {
+	String k = make_string_c(key);
+	return string_map_must_get(h, string_hash(k), k);
+}
+
+template <typename T>
+gb_internal void string_map_set(StringMap<T> *h, u32 hash, String const &key, T const &value) {
+	MapIndex index;
+	MapFindResult fr;
+	if (h->hashes_count == 0) {
+		string_map_grow(h);
+	}
+	fr = string_map__find(h, hash, key);
+	if (fr.entry_index != MAP_SENTINEL) {
+		index = fr.entry_index;
+	} else {
+		index = string_map__add_entry(h, hash, key);
+		if (fr.entry_prev != MAP_SENTINEL) {
+			h->entries[fr.entry_prev].next = index;
+		} else {
+			h->hashes[fr.hash_index] = index;
+		}
+	}
+	h->entries[index].value = value;
+
+	if (string_map__full(h)) {
+		string_map_grow(h);
+	}
+}
+
+template <typename T>
+gb_internal gb_inline void string_map_set(StringMap<T> *h, String const &key, T const &value) {
+	string_map_set(h, string_hash_string(key), value);
+}
+
+template <typename T>
+gb_internal gb_inline void string_map_set(StringMap<T> *h, char const *key, T const &value) {
+	string_map_set(h, string_hash_string(make_string_c(key)), value);
+}
+
+template <typename T>
+gb_internal gb_inline void string_map_set(StringMap<T> *h, StringHashKey const &key, T const &value) {
+	string_map_set(h, key.hash, key.string, value);
+}
+
+
+template <typename T>
+gb_internal gb_inline void string_map_clear(StringMap<T> *h) {
+	h->count = 0;
+	for (u32 i = 0; i < h->hashes_count; i++) {
+		h->hashes[i] = MAP_SENTINEL;
+	}
+}
+
+
+
+template <typename T>
+gb_internal StringMapEntry<T> *begin(StringMap<T> &m) noexcept {
+	return m.entries;
+}
+template <typename T>
+gb_internal StringMapEntry<T> const *begin(StringMap<T> const &m) noexcept {
+	return m.entries;
+}
+
+
+template <typename T>
+gb_internal StringMapEntry<T> *end(StringMap<T> &m) noexcept {
+	return m.entries + m.count;
+}
+
+template <typename T>
+gb_internal StringMapEntry<T> const *end(StringMap<T> const &m) noexcept {
+	return m.entries + m.count;
+}
+
+#else /* new string map */
+
 template <typename T>
 struct StringMapEntry {
 	String key;
@@ -305,3 +595,5 @@ gb_internal StringMapIterator<T> const begin(StringMap<T> const &m) noexcept {
 	}
 	return StringMapIterator<T>{&m, index};
 }
+
+#endif

+ 2 - 0
src/threading.cpp

@@ -492,6 +492,8 @@ gb_internal u32 thread_current_id(void) {
 	thread_id = gettid();
 #elif defined(GB_SYSTEM_HAIKU)
 	thread_id = find_thread(NULL);
+#elif defined(GB_SYSTEM_FREEBSD)
+	thread_id = pthread_getthreadid_np();
 #else
 	#error Unsupported architecture for thread_current_id()
 #endif

+ 22 - 0
src/types.cpp

@@ -184,6 +184,8 @@ struct TypeProc {
 	isize    specialization_count;
 	ProcCallingConvention calling_convention;
 	i32      variadic_index;
+	String   require_target_feature;
+	String   enable_target_feature;
 	// TODO(bill): Make this a flag set rather than bools
 	bool     variadic;
 	bool     require_results;
@@ -2991,7 +2993,22 @@ gb_internal Type *union_tag_type(Type *u) {
 	return t_uint;
 }
 
+gb_internal int matched_target_features(TypeProc *t) {
+	if (t->require_target_feature.len == 0) {
+		return 0;
+	}
 
+	int matches = 0;
+	String_Iterator it = {t->require_target_feature, 0};
+	for (;;) {
+		String str = string_split_iterator(&it, ',');
+		if (str == "") break;
+		if (check_target_feature_is_valid_for_target_arch(str, nullptr)) {
+			matches += 1;
+		}
+	}
+	return matches;
+}
 
 enum ProcTypeOverloadKind {
 	ProcOverload_Identical, // The types are identical
@@ -3003,6 +3020,7 @@ enum ProcTypeOverloadKind {
 	ProcOverload_ResultCount,
 	ProcOverload_ResultTypes,
 	ProcOverload_Polymorphic,
+	ProcOverload_TargetFeatures,
 
 	ProcOverload_NotProcedure,
 
@@ -3060,6 +3078,10 @@ gb_internal ProcTypeOverloadKind are_proc_types_overload_safe(Type *x, Type *y)
 		}
 	}
 
+	if (matched_target_features(&px) != matched_target_features(&py)) {
+		return ProcOverload_TargetFeatures;
+	}
+
 	if (px.params != nullptr && py.params != nullptr) {
 		Entity *ex = px.params->Tuple.variables[0];
 		Entity *ey = py.params->Tuple.variables[0];

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů