Browse Source

Merge pull request #3439 from andreas-jonsson/netbsd

NetBSD support
gingerBill 1 year ago
parent
commit
f9fd8f0c25

+ 1 - 1
base/runtime/entry_unix.odin

@@ -1,5 +1,5 @@
 //+private
 //+private
-//+build linux, darwin, freebsd, openbsd, haiku
+//+build linux, darwin, freebsd, openbsd, netbsd, haiku
 //+no-instrumentation
 //+no-instrumentation
 package runtime
 package runtime
 
 

+ 1 - 1
base/runtime/heap_allocator_unix.odin

@@ -1,4 +1,4 @@
-//+build linux, darwin, freebsd, openbsd, haiku
+//+build linux, darwin, freebsd, openbsd, netbsd, haiku
 //+private
 //+private
 package runtime
 package runtime
 
 

+ 6 - 2
base/runtime/os_specific_bsd.odin

@@ -1,4 +1,4 @@
-//+build freebsd, openbsd
+//+build freebsd, openbsd, netbsd
 //+private
 //+private
 package runtime
 package runtime
 
 
@@ -9,7 +9,11 @@ foreign libc {
 	@(link_name="write")
 	@(link_name="write")
 	_unix_write :: proc(fd: i32, buf: rawptr, size: int) -> int ---
 	_unix_write :: proc(fd: i32, buf: rawptr, size: int) -> int ---
 
 
-	__error :: proc() -> ^i32 ---
+	when ODIN_OS == .NetBSD {
+		@(link_name="__errno") __error :: proc() -> ^i32 ---
+	} else {
+		__error :: proc() -> ^i32 ---
+	}
 }
 }
 
 
 _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
 _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {

+ 4 - 0
build_odin.sh

@@ -71,6 +71,10 @@ FreeBSD)
 	CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
 	CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
 	LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
 	LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
 	;;
 	;;
+NetBSD)
+	CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
+	LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
+	;;
 Linux)
 Linux)
 	CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
 	CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
 	LDFLAGS="$LDFLAGS -ldl $($LLVM_CONFIG --libs core native --system-libs --libfiles)"
 	LDFLAGS="$LDFLAGS -ldl $($LLVM_CONFIG --libs core native --system-libs --libfiles)"

+ 1 - 1
core/c/libc/errno.odin

@@ -40,7 +40,7 @@ when ODIN_OS == .FreeBSD {
 	ERANGE :: 34
 	ERANGE :: 34
 }
 }
 
 
-when ODIN_OS == .OpenBSD {
+when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
 	@(private="file")
 	@(private="file")
 	@(default_calling_convention="c")
 	@(default_calling_convention="c")
 	foreign libc {
 	foreign libc {

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

@@ -83,7 +83,7 @@ when ODIN_OS == .Linux {
 	}
 	}
 }
 }
 
 
-when ODIN_OS == .OpenBSD {
+when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
 	fpos_t :: distinct i64
 	fpos_t :: distinct i64
 
 
 	_IOFBF :: 0
 	_IOFBF :: 0

+ 1 - 1
core/c/libc/time.odin

@@ -45,7 +45,7 @@ when ODIN_OS == .Windows {
 	}
 	}
 }
 }
 
 
-when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .Haiku {
+when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku {
 	@(default_calling_convention="c")
 	@(default_calling_convention="c")
 	foreign libc {
 	foreign libc {
 		// 7.27.2 Time manipulation functions
 		// 7.27.2 Time manipulation functions

+ 1 - 1
core/c/libc/wctype.odin

@@ -22,7 +22,7 @@ when ODIN_OS == .Windows {
 	wctrans_t :: distinct int
 	wctrans_t :: distinct int
 	wctype_t  :: distinct u32
 	wctype_t  :: distinct u32
 
 
-} else when ODIN_OS == .OpenBSD {
+} else when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
 	wctrans_t :: distinct rawptr
 	wctrans_t :: distinct rawptr
 	wctype_t  :: distinct rawptr
 	wctype_t  :: distinct rawptr
 
 

+ 1 - 1
core/crypto/rand_bsd.odin

@@ -1,4 +1,4 @@
-//+build freebsd, openbsd
+//+build freebsd, openbsd, netbsd
 package crypto
 package crypto
 
 
 foreign import libc "system:c"
 foreign import libc "system:c"

+ 1 - 0
core/crypto/rand_generic.odin

@@ -2,6 +2,7 @@
 //+build !windows
 //+build !windows
 //+build !openbsd
 //+build !openbsd
 //+build !freebsd
 //+build !freebsd
+//+build !netbsd
 //+build !darwin
 //+build !darwin
 //+build !js
 //+build !js
 package crypto
 package crypto

+ 1 - 1
core/dynlib/lib_unix.odin

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

+ 4 - 4
core/mem/virtual/virtual_bsd.odin

@@ -1,9 +1,7 @@
-//+build freebsd, openbsd
+//+build freebsd, openbsd, netbsd
 //+private
 //+private
 package mem_virtual
 package mem_virtual
 
 
-
-
 _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
 _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
 	return nil, nil
 	return nil, nil
 }
 }
@@ -11,16 +9,18 @@ _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Err
 _commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
 _commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
 	return nil
 	return nil
 }
 }
+
 _decommit :: proc "contextless" (data: rawptr, size: uint) {
 _decommit :: proc "contextless" (data: rawptr, size: uint) {
 }
 }
+
 _release :: proc "contextless" (data: rawptr, size: uint) {
 _release :: proc "contextless" (data: rawptr, size: uint) {
 }
 }
+
 _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool {
 _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool {
 	return false
 	return false
 }
 }
 
 
 _platform_memory_init :: proc() {
 _platform_memory_init :: proc() {
-
 }
 }
 
 
 _map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
 _map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {

+ 2 - 2
core/net/dns.odin

@@ -30,7 +30,7 @@ when ODIN_OS == .Windows {
 		resolv_conf        = "",
 		resolv_conf        = "",
 		hosts_file         = "%WINDIR%\\system32\\drivers\\etc\\hosts",
 		hosts_file         = "%WINDIR%\\system32\\drivers\\etc\\hosts",
 	}
 	}
-} else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD {
+} else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
 	DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{
 	DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{
 		resolv_conf        = "/etc/resolv.conf",
 		resolv_conf        = "/etc/resolv.conf",
 		hosts_file         = "/etc/hosts",
 		hosts_file         = "/etc/hosts",
@@ -854,4 +854,4 @@ parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator
 	}
 	}
 
 
 	return _records[:], true
 	return _records[:], true
-}
+}

+ 1 - 0
core/os/dir_freebsd.odin → core/os/dir_bsd.odin

@@ -1,3 +1,4 @@
+//+build freebsd, netbsd
 package os
 package os
 
 
 import "core:mem"
 import "core:mem"

+ 780 - 0
core/os/os_netbsd.odin

@@ -0,0 +1,780 @@
+package os
+
+foreign import dl "system:dl"
+foreign import libc "system:c"
+
+import "base:runtime"
+import "core:strings"
+import "core:sys/unix"
+import "core:c"
+
+Handle :: distinct i32
+File_Time :: distinct u64
+Errno :: distinct i32
+
+INVALID_HANDLE :: ~Handle(0)
+
+ERROR_NONE:     Errno : 0 		/* No error */
+EPERM:          Errno : 1		/* Operation not permitted */
+ENOENT:         Errno : 2		/* No such file or directory */
+EINTR:          Errno : 4		/* Interrupted system call */
+ESRCH:          Errno : 3		/* No such process */
+EIO:            Errno : 5		/* Input/output error */
+ENXIO:          Errno : 6		/* Device not configured */
+E2BIG:          Errno : 7		/* Argument list too long */
+ENOEXEC:        Errno : 8		/* Exec format error */
+EBADF:          Errno : 9		/* Bad file descriptor */
+ECHILD:         Errno : 10		/* No child processes */
+EDEADLK:        Errno : 11		/* Resource deadlock avoided. 11 was EAGAIN */
+ENOMEM:         Errno : 12		/* Cannot allocate memory */
+EACCES:         Errno : 13		/* Permission denied */
+EFAULT:         Errno : 14		/* Bad address */
+ENOTBLK:        Errno : 15		/* Block device required */
+EBUSY:          Errno : 16		/* Device busy */
+EEXIST:         Errno : 17		/* File exists */
+EXDEV:          Errno : 18		/* Cross-device link */
+ENODEV:         Errno : 19		/* Operation not supported by device */
+ENOTDIR:        Errno : 20		/* Not a directory */
+EISDIR:         Errno : 21		/* Is a directory */
+EINVAL:         Errno : 22		/* Invalid argument */
+ENFILE:         Errno : 23		/* Too many open files in system */
+EMFILE:         Errno : 24		/* Too many open files */
+ENOTTY:         Errno : 25		/* Inappropriate ioctl for device */
+ETXTBSY:        Errno : 26		/* Text file busy */
+EFBIG:          Errno : 27		/* File too large */
+ENOSPC:         Errno : 28		/* No space left on device */
+ESPIPE:         Errno : 29		/* Illegal seek */
+EROFS:          Errno : 30		/* Read-only file system */
+EMLINK:         Errno : 31		/* Too many links */
+EPIPE:          Errno : 32		/* Broken pipe */
+
+/* math software */
+EDOM:           Errno : 33		/* Numerical argument out of domain */
+ERANGE:         Errno : 34		/* Result too large or too small */
+
+/* non-blocking and interrupt i/o */
+EAGAIN:         Errno : 35		/* Resource temporarily unavailable */
+EWOULDBLOCK:    Errno : EAGAIN	/* Operation would block */
+EINPROGRESS:    Errno : 36		/* Operation now in progress */
+EALREADY:       Errno : 37		/* Operation already in progress */
+
+/* ipc/network software -- argument errors */
+ENOTSOCK:       Errno : 38		/* Socket operation on non-socket */
+EDESTADDRREQ:   Errno : 39		/* Destination address required */
+EMSGSIZE:       Errno : 40		/* Message too long */
+EPROTOTYPE:     Errno : 41		/* Protocol wrong type for socket */
+ENOPROTOOPT:    Errno : 42		/* Protocol option not available */
+EPROTONOSUPPORT:    Errno : 43		/* Protocol not supported */
+ESOCKTNOSUPPORT:    Errno : 44		/* Socket type not supported */
+EOPNOTSUPP:     Errno : 45		/* Operation not supported */
+EPFNOSUPPORT:   Errno : 46		/* Protocol family not supported */
+EAFNOSUPPORT:   Errno : 47		/* Address family not supported by protocol family */
+EADDRINUSE:     Errno : 48		/* Address already in use */
+EADDRNOTAVAIL:  Errno : 49		/* Can't assign requested address */
+
+/* ipc/network software -- operational errors */
+ENETDOWN:       Errno : 50		/* Network is down */
+ENETUNREACH:    Errno : 51		/* Network is unreachable */
+ENETRESET:      Errno : 52		/* Network dropped connection on reset */
+ECONNABORTED:   Errno : 53		/* Software caused connection abort */
+ECONNRESET:     Errno : 54		/* Connection reset by peer */
+ENOBUFS:        Errno : 55		/* No buffer space available */
+EISCONN:        Errno : 56		/* Socket is already connected */
+ENOTCONN:       Errno : 57		/* Socket is not connected */
+ESHUTDOWN:      Errno : 58		/* Can't send after socket shutdown */
+ETOOMANYREFS:   Errno : 59		/* Too many references: can't splice */
+ETIMEDOUT:      Errno : 60		/* Operation timed out */
+ECONNREFUSED:   Errno : 61		/* Connection refused */
+
+ELOOP:          Errno : 62		/* Too many levels of symbolic links */
+ENAMETOOLONG:   Errno : 63		/* File name too long */
+
+/* should be rearranged */
+EHOSTDOWN:      Errno : 64		/* Host is down */
+EHOSTUNREACH:   Errno : 65		/* No route to host */
+ENOTEMPTY:      Errno : 66		/* Directory not empty */
+
+/* quotas & mush */
+EPROCLIM:       Errno : 67		/* Too many processes */
+EUSERS:         Errno : 68		/* Too many users */
+EDQUOT:         Errno : 69		/* Disc quota exceeded */
+
+/* Network File System */
+ESTALE:         Errno : 70		/* Stale NFS file handle */
+EREMOTE:        Errno : 71		/* Too many levels of remote in path */
+EBADRPC:        Errno : 72		/* RPC struct is bad */
+ERPCMISMATCH:   Errno : 73		/* RPC version wrong */
+EPROGUNAVAIL:   Errno : 74		/* RPC prog. not avail */
+EPROGMISMATCH:  Errno : 75		/* Program version wrong */
+EPROCUNAVAIL:   Errno : 76		/* Bad procedure for program */
+
+ENOLCK:         Errno : 77		/* No locks available */
+ENOSYS:         Errno : 78		/* Function not implemented */
+
+EFTYPE:         Errno : 79		/* Inappropriate file type or format */
+EAUTH:          Errno : 80		/* Authentication error */
+ENEEDAUTH:      Errno : 81		/* Need authenticator */
+
+/* SystemV IPC */
+EIDRM:          Errno : 82		/* Identifier removed */
+ENOMSG:         Errno : 83		/* No message of desired type */
+EOVERFLOW:      Errno : 84		/* Value too large to be stored in data type */
+
+/* Wide/multibyte-character handling, ISO/IEC 9899/AMD1:1995 */
+EILSEQ:         Errno : 85		/* Illegal byte sequence */
+
+/* From IEEE Std 1003.1-2001 */
+/* Base, Realtime, Threads or Thread Priority Scheduling option errors */
+ENOTSUP:        Errno : 86		/* Not supported */
+
+/* Realtime option errors */
+ECANCELED:      Errno : 87		/* Operation canceled */
+
+/* Realtime, XSI STREAMS option errors */
+EBADMSG:        Errno : 88		/* Bad or Corrupt message */
+
+/* XSI STREAMS option errors  */
+ENODATA:        Errno : 89		/* No message available */
+ENOSR:          Errno : 90		/* No STREAM resources */
+ENOSTR:         Errno : 91		/* Not a STREAM */
+ETIME:          Errno : 92		/* STREAM ioctl timeout */
+
+/* File system extended attribute errors */
+ENOATTR:        Errno : 93		/* Attribute not found */
+
+/* Realtime, XSI STREAMS option errors */
+EMULTIHOP:      Errno : 94		/* Multihop attempted */
+ENOLINK:        Errno : 95		/* Link has been severed */
+EPROTO:         Errno : 96		/* Protocol error */
+
+/* Robust mutexes */
+EOWNERDEAD:     Errno : 97		/* Previous owner died */
+ENOTRECOVERABLE:    Errno : 98		/* State not recoverable */
+
+ELAST:          Errno : 98		/* Must equal largest errno */
+
+/* end of errno */
+
+O_RDONLY   :: 0x000000000
+O_WRONLY   :: 0x000000001
+O_RDWR     :: 0x000000002
+O_CREATE   :: 0x000000200
+O_EXCL     :: 0x000000800
+O_NOCTTY   :: 0x000008000
+O_TRUNC    :: 0x000000400
+O_NONBLOCK :: 0x000000004
+O_APPEND   :: 0x000000008
+O_SYNC     :: 0x000000080
+O_ASYNC    :: 0x000000040
+O_CLOEXEC  :: 0x000400000
+
+RTLD_LAZY         :: 0x001
+RTLD_NOW          :: 0x002
+RTLD_GLOBAL       :: 0x100
+RTLD_LOCAL        :: 0x200
+RTLD_TRACE        :: 0x200
+RTLD_NODELETE     :: 0x01000
+RTLD_NOLOAD       :: 0x02000
+
+F_GETPATH :: 15
+
+MAX_PATH :: 1024
+MAXNAMLEN :: 511
+
+args := _alloc_command_line_arguments()
+
+Unix_File_Time :: struct {
+	seconds: time_t,
+	nanoseconds: c.long,
+}
+
+dev_t :: u64
+ino_t :: u64
+nlink_t :: u32
+off_t :: i64
+mode_t :: u16
+pid_t :: u32
+uid_t :: u32
+gid_t :: u32
+blkcnt_t :: i64
+blksize_t :: i32
+fflags_t :: u32
+time_t :: i64
+
+OS_Stat :: struct {
+	device_id: dev_t,
+	mode: mode_t,
+	_padding0: i16,
+	ino: ino_t,
+	nlink: nlink_t,
+	uid: uid_t,
+	gid: gid_t,
+	_padding1: i32,
+	rdev: dev_t,
+
+	last_access: Unix_File_Time,
+	modified: Unix_File_Time,
+	status_change: Unix_File_Time,
+	birthtime: Unix_File_Time,
+
+	size: off_t,
+	blocks: blkcnt_t,
+	block_size: blksize_t,
+
+	flags: fflags_t,
+	gen: u32,
+	lspare: [2]u32,
+}
+
+Dirent :: struct {
+	ino: ino_t,
+	reclen: u16,
+	namlen: u16,
+	type: u8,
+	name: [MAXNAMLEN + 1]byte,
+}
+
+Dir :: distinct rawptr // DIR*
+
+// File type
+S_IFMT   :: 0o170000 // Type of file mask
+S_IFIFO  :: 0o010000 // Named pipe (fifo)
+S_IFCHR  :: 0o020000 // Character special
+S_IFDIR  :: 0o040000 // Directory
+S_IFBLK  :: 0o060000 // Block special
+S_IFREG  :: 0o100000 // Regular
+S_IFLNK  :: 0o120000 // Symbolic link
+S_IFSOCK :: 0o140000 // Socket
+
+// File mode
+// Read, write, execute/search by owner
+S_IRWXU :: 0o0700 // RWX mask for owner
+S_IRUSR :: 0o0400 // R for owner
+S_IWUSR :: 0o0200 // W for owner
+S_IXUSR :: 0o0100 // X for owner
+
+// Read, write, execute/search by group
+S_IRWXG :: 0o0070 // RWX mask for group
+S_IRGRP :: 0o0040 // R for group
+S_IWGRP :: 0o0020 // W for group
+S_IXGRP :: 0o0010 // X for group
+
+// Read, write, execute/search by others
+S_IRWXO :: 0o0007 // RWX mask for other
+S_IROTH :: 0o0004 // R for other
+S_IWOTH :: 0o0002 // W for other
+S_IXOTH :: 0o0001 // X for other
+
+S_ISUID :: 0o4000 // Set user id on execution
+S_ISGID :: 0o2000 // Set group id on execution
+S_ISVTX :: 0o1000 // Directory restrcted delete
+
+S_ISLNK  :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFLNK  }
+S_ISREG  :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFREG  }
+S_ISDIR  :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFDIR  }
+S_ISCHR  :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFCHR  }
+S_ISBLK  :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFBLK  }
+S_ISFIFO :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFIFO  }
+S_ISSOCK :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFSOCK }
+
+F_OK :: 0 // Test for file existance
+X_OK :: 1 // Test for execute permission
+W_OK :: 2 // Test for write permission
+R_OK :: 4 // Test for read permission
+
+foreign libc {
+	@(link_name="__errno")          __errno_location    :: proc() -> ^c.int ---
+
+	@(link_name="open")             _unix_open          :: proc(path: cstring, flags: c.int, mode: c.int) -> Handle ---
+	@(link_name="close")            _unix_close         :: proc(fd: Handle) -> c.int ---
+	@(link_name="read")             _unix_read          :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t ---
+	@(link_name="write")            _unix_write         :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t ---
+	@(link_name="lseek")            _unix_seek          :: proc(fd: Handle, offset: i64, whence: c.int) -> i64 ---
+	@(link_name="getpagesize")      _unix_getpagesize   :: proc() -> c.int ---
+	@(link_name="stat")             _unix_stat          :: proc(path: cstring, stat: ^OS_Stat) -> c.int ---
+	@(link_name="__lstat50")        _unix_lstat         :: proc(path: cstring, sb: ^OS_Stat) -> c.int ---
+	@(link_name="__fstat50")        _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="fcntl")            _unix_fcntl         :: proc(fd: Handle, cmd: c.int, arg: uintptr) -> c.int ---
+	
+	@(link_name="fdopendir")        _unix_fdopendir     :: proc(fd: Handle) -> Dir ---
+	@(link_name="closedir")         _unix_closedir      :: proc(dirp: Dir) -> c.int ---
+	@(link_name="rewinddir")        _unix_rewinddir     :: proc(dirp: Dir) ---
+	@(link_name="readdir_r")        _unix_readdir_r     :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int ---
+
+	@(link_name="malloc")           _unix_malloc        :: proc(size: c.size_t) -> rawptr ---
+	@(link_name="calloc")           _unix_calloc        :: proc(num, size: c.size_t) -> rawptr ---
+	@(link_name="free")             _unix_free          :: proc(ptr: rawptr) ---
+	@(link_name="realloc")          _unix_realloc       :: proc(ptr: rawptr, size: c.size_t) -> rawptr ---
+	
+	@(link_name="getenv")           _unix_getenv        :: proc(cstring) -> cstring ---
+	@(link_name="realpath")         _unix_realpath      :: proc(path: cstring, resolved_path: rawptr) -> rawptr ---
+	@(link_name="sysctlbyname")     _sysctlbyname       :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int ---
+
+	@(link_name="exit")             _unix_exit          :: proc(status: c.int) -> ! ---
+}
+
+foreign dl {
+	@(link_name="dlopen")           _unix_dlopen        :: proc(filename: cstring, flags: c.int) -> rawptr ---
+	@(link_name="dlsym")            _unix_dlsym         :: proc(handle: rawptr, symbol: cstring) -> rawptr ---
+	@(link_name="dlclose")          _unix_dlclose       :: proc(handle: rawptr) -> c.int ---
+	@(link_name="dlerror")          _unix_dlerror       :: proc() -> cstring ---
+}
+
+// NOTE(phix): Perhaps share the following functions with FreeBSD if they turn out to be the same in the end.
+
+is_path_separator :: proc(r: rune) -> bool {
+	return r == '/'
+}
+
+get_last_error :: proc "contextless" () -> int {
+	return int(__errno_location()^)
+}
+
+open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+	cstr := strings.clone_to_cstring(path, context.temp_allocator)
+	handle := _unix_open(cstr, c.int(flags), c.int(mode))
+	if handle == -1 {
+		return INVALID_HANDLE, Errno(get_last_error())
+	}
+	return handle, ERROR_NONE
+}
+
+close :: proc(fd: Handle) -> Errno {
+	result := _unix_close(fd)
+	if result == -1 {
+		return Errno(get_last_error())
+	}
+	return ERROR_NONE
+}
+
+// We set a max of 1GB to keep alignment and to be safe.
+@(private)
+MAX_RW :: 1 << 30
+
+read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+	to_read    := min(c.size_t(len(data)), MAX_RW)
+	bytes_read := _unix_read(fd, &data[0], to_read)
+	if bytes_read == -1 {
+		return -1, Errno(get_last_error())
+	}
+	return int(bytes_read), ERROR_NONE
+}
+
+write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+	if len(data) == 0 {
+		return 0, ERROR_NONE
+	}
+
+	to_write      := min(c.size_t(len(data)), MAX_RW)
+	bytes_written := _unix_write(fd, &data[0], to_write)
+	if bytes_written == -1 {
+		return -1, Errno(get_last_error())
+	}
+	return int(bytes_written), ERROR_NONE
+}
+
+seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
+	res := _unix_seek(fd, offset, c.int(whence))
+	if res == -1 {
+		return -1, Errno(get_last_error())
+	}
+	return res, ERROR_NONE
+}
+
+file_size :: proc(fd: Handle) -> (i64, Errno) {
+	s, err := _fstat(fd)
+	if err != ERROR_NONE {
+		return -1, err
+	}
+	return s.size, ERROR_NONE
+}
+
+rename :: proc(old_path, new_path: string) -> Errno {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+	old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator)
+	new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator)
+	res := _unix_rename(old_path_cstr, new_path_cstr)
+	if res == -1 {
+		return Errno(get_last_error())
+	}
+	return ERROR_NONE
+}
+
+remove :: proc(path: string) -> Errno {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
+	res := _unix_unlink(path_cstr)
+	if res == -1 {
+		return Errno(get_last_error())
+	}
+	return ERROR_NONE
+}
+
+make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
+	res := _unix_mkdir(path_cstr, mode)
+	if res == -1 {
+		return Errno(get_last_error())
+	}
+	return ERROR_NONE
+}
+
+remove_directory :: proc(path: string) -> Errno {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
+	res := _unix_rmdir(path_cstr)
+	if res == -1 {
+		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}
+
+exists :: proc(path: string) -> bool {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+	cpath := strings.clone_to_cstring(path, context.temp_allocator)
+	res := _unix_access(cpath, O_RDONLY)
+	return res == 0
+}
+
+fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Errno) {
+	result := _unix_fcntl(Handle(fd), c.int(cmd), uintptr(arg))
+	if result < 0 {
+		return 0, Errno(get_last_error())
+	}
+	return int(result), ERROR_NONE
+}
+
+// NOTE(bill): Uses startup to initialize it
+
+stdin: Handle  = 0
+stdout: Handle = 1
+stderr: Handle = 2
+
+last_write_time :: proc(fd: Handle) -> (File_Time, Errno) {
+	s, err := _fstat(fd)
+	if err != ERROR_NONE {
+		return 0, err
+	}
+	modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
+	return File_Time(modified), ERROR_NONE
+}
+
+last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
+	s, err := _stat(name)
+	if err != ERROR_NONE {
+		return 0, err
+	}
+	modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
+	return File_Time(modified), ERROR_NONE
+}
+
+@private
+_stat :: proc(path: string) -> (OS_Stat, Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+	cstr := strings.clone_to_cstring(path, context.temp_allocator)
+	s: OS_Stat = ---
+	result := _unix_lstat(cstr, &s)
+	if result == -1 {
+		return s, Errno(get_last_error())
+	}
+	return s, ERROR_NONE
+}
+
+@private
+_lstat :: proc(path: string) -> (OS_Stat, Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+	cstr := strings.clone_to_cstring(path, context.temp_allocator)
+	
+	// deliberately uninitialized
+	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())
+	}
+	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) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
+
+	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
+
+	bufsz : uint = MAX_PATH
+	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
+		}	
+	}
+
+	return "", Errno{}
+}
+
+absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) {
+	buf: [MAX_PATH]byte
+	_, err := fcntl(int(fd), F_GETPATH, int(uintptr(&buf[0])))
+	if err != ERROR_NONE {
+		return "", err
+	}
+
+	path := strings.clone_from_cstring(cstring(&buf[0]))
+	return path, err
+}
+
+absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
+	rel := rel
+	if rel == "" {
+		rel = "."
+	}
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
+
+	rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator)
+
+	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) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+
+	cstr := strings.clone_to_cstring(path, context.temp_allocator)
+	result := _unix_access(cstr, c.int(mask))
+	if result == -1 {
+		return false, Errno(get_last_error())
+	}
+	return true, ERROR_NONE
+}
+
+lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
+
+	path_str := strings.clone_to_cstring(key, context.temp_allocator)
+	cstr := _unix_getenv(path_str)
+	if cstr == nil {
+		return "", false
+	}
+	return strings.clone(string(cstr), allocator), true
+}
+
+get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
+	value, _ = lookup_env(key, allocator)
+	return
+}
+
+get_current_directory :: proc() -> string {
+	// NOTE(tetra): I would use PATH_MAX here, but I was not able to find
+	// an authoritative value for it across all systems.
+	// The largest value I could find was 4096, so might as well use the page size.
+	page_size := get_page_size()
+	buf := make([dynamic]u8, page_size)
+	#no_bounds_check for {
+		cwd := _unix_getcwd(cstring(&buf[0]), c.size_t(len(buf)))
+		if cwd != nil {
+			return string(cwd)
+		}
+		if Errno(get_last_error()) != ERANGE {
+			delete(buf)
+			return ""
+		}
+		resize(&buf, len(buf)+page_size)
+	}
+	unreachable()
+}
+
+set_current_directory :: proc(path: string) -> (err: Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+	cstr := strings.clone_to_cstring(path, context.temp_allocator)
+	res := _unix_chdir(cstr)
+	if res == -1 do return Errno(get_last_error())
+	return ERROR_NONE
+}
+
+exit :: proc "contextless" (code: int) -> ! {
+	runtime._cleanup_runtime_contextless()
+	_unix_exit(c.int(code))
+}
+
+current_thread_id :: proc "contextless" () -> int {
+	return cast(int) unix.pthread_self()
+}
+
+dlopen :: proc(filename: string, flags: int) -> rawptr {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+	cstr := strings.clone_to_cstring(filename, context.temp_allocator)
+	handle := _unix_dlopen(cstr, c.int(flags))
+	return handle
+}
+
+dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
+	assert(handle != nil)
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+	cstr := strings.clone_to_cstring(symbol, context.temp_allocator)
+	proc_handle := _unix_dlsym(handle, cstr)
+	return proc_handle
+}
+
+dlclose :: proc(handle: rawptr) -> bool {
+	assert(handle != nil)
+	return _unix_dlclose(handle) == 0
+}
+
+dlerror :: proc() -> string {
+	return string(_unix_dlerror())
+}
+
+get_page_size :: proc() -> int {
+	// NOTE(tetra): The page size never changes, so why do anything complicated
+	// if we don't have to.
+	@static page_size := -1
+	if page_size != -1 do return page_size
+
+	page_size = int(_unix_getpagesize())
+	return page_size
+}
+
+@(private)
+_processor_core_count :: proc() -> int {
+	count : int = 0
+	count_size := size_of(count)
+	if _sysctlbyname("hw.logicalcpu", &count, &count_size, nil, 0) == 0 {
+		if count > 0 {
+			return count
+		}
+	}
+
+	return 1
+}
+
+_alloc_command_line_arguments :: proc() -> []string {
+	res := make([]string, len(runtime.args__))
+	for arg, i in runtime.args__ {
+		res[i] = string(arg)
+	}
+	return res
+}

+ 1 - 1
core/os/stat_unix.odin

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

+ 3 - 3
core/os/stream.odin

@@ -32,7 +32,7 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte,
 		}
 		}
 
 
 	case .Read_At:
 	case .Read_At:
-		when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Haiku) {
+		when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku) {
 			n_int, os_err = read_at(fd, p, offset)
 			n_int, os_err = read_at(fd, p, offset)
 			n = i64(n_int)
 			n = i64(n_int)
 			if n == 0 && os_err == 0 {
 			if n == 0 && os_err == 0 {
@@ -46,7 +46,7 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte,
 			err = .EOF
 			err = .EOF
 		}
 		}
 	case .Write_At:
 	case .Write_At:
-		when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Haiku) {
+		when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku) {
 			n_int, os_err = write_at(fd, p, offset)
 			n_int, os_err = write_at(fd, p, offset)
 			n = i64(n_int)
 			n = i64(n_int)
 			if n == 0 && os_err == 0 {
 			if n == 0 && os_err == 0 {
@@ -60,7 +60,7 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte,
 	case .Destroy:
 	case .Destroy:
 		err = .Empty
 		err = .Empty
 	case .Query:
 	case .Query:
-		when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Haiku {
+		when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku {
 			return io.query_utility({.Close, .Flush, .Read, .Write, .Seek, .Size, .Query})
 			return io.query_utility({.Close, .Flush, .Read, .Write, .Seek, .Size, .Query})
 		} else {
 		} else {
 			return io.query_utility({.Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query})
 			return io.query_utility({.Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query})

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

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

+ 1 - 1
core/prof/spall/spall_unix.odin

@@ -1,5 +1,5 @@
 //+private
 //+private
-//+build darwin, freebsd, openbsd
+//+build darwin, freebsd, openbsd, netbsd
 package spall
 package spall
 
 
 // Only for types.
 // Only for types.

+ 74 - 0
core/sync/futex_netbsd.odin

@@ -0,0 +1,74 @@
+//+private
+package sync
+
+import "base:intrinsics"
+import "core:time"
+import "core:c"
+import "core:sys/unix"
+
+foreign import libc "system:c"
+
+FUTEX_PRIVATE_FLAG :: 128
+
+FUTEX_WAIT_PRIVATE :: 0 | FUTEX_PRIVATE_FLAG
+FUTEX_WAKE_PRIVATE :: 1 | FUTEX_PRIVATE_FLAG
+
+EINTR     :: 4		/* Interrupted system call */
+EAGAIN    :: 35		/* Resource temporarily unavailable */
+ETIMEDOUT :: 60		/* Operation timed out */
+
+Time_Spec :: struct {
+	time_sec:  uint,
+	time_nsec: uint,
+}
+
+get_last_error :: proc "contextless" () -> int {
+	foreign libc {
+		__errno :: proc() -> ^c.int ---
+	}
+	return int(__errno()^)
+}
+
+_futex_wait :: proc "contextless" (futex: ^Futex, expected: u32) -> bool {
+	if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAIT_PRIVATE, uintptr(expected), 0, 0, 0) == -1 {
+		switch get_last_error() {
+		case EINTR, EAGAIN:
+			return true
+		case:
+			_panic("futex_wait failure")
+		}	
+	}
+	return true
+}
+
+_futex_wait_with_timeout :: proc "contextless" (futex: ^Futex, expected: u32, duration: time.Duration) -> bool {
+	if duration <= 0 {
+		return false
+	}
+	if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAIT_PRIVATE, uintptr(expected), cast(uintptr) &Time_Spec{
+		time_sec  = cast(uint)(duration / 1e9),
+		time_nsec = cast(uint)(duration % 1e9),
+	}, 0, 0) == -1 {
+		switch get_last_error() {
+		case EINTR, EAGAIN:
+			return true
+		case ETIMEDOUT:
+			return false
+		case:
+			_panic("futex_wait_with_timeout failure")
+		}
+	}
+	return true
+}
+
+_futex_signal :: proc "contextless" (futex: ^Futex) {
+	if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, 1, 0, 0, 0) == -1 {
+		_panic("futex_wake_single failure")
+	}
+}
+
+_futex_broadcast :: proc "contextless" (futex: ^Futex)  {
+	if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, uintptr(max(i32)), 0, 0, 0) == -1 {
+		_panic("_futex_wake_all failure")
+	}
+}

+ 8 - 0
core/sync/primitives_netbsd.odin

@@ -0,0 +1,8 @@
+//+private
+package sync
+
+import "core:sys/unix"
+
+_current_thread_id :: proc "contextless" () -> int {
+	return cast(int) unix.pthread_self()
+}

+ 3 - 3
core/sys/info/cpu_intel.odin

@@ -67,8 +67,8 @@ init_cpu_features :: proc "c" () {
 	try_set(&set, .os_xsave,  27, ecx1)
 	try_set(&set, .os_xsave,  27, ecx1)
 	try_set(&set, .rdrand,    30, ecx1)
 	try_set(&set, .rdrand,    30, ecx1)
 
 
-	when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD {
-		// xgetbv is an illegal instruction under FreeBSD 13 & OpenBSD 7.1
+	when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
+		// xgetbv is an illegal instruction under FreeBSD 13, OpenBSD 7.1 and NetBSD 10
 		// return before probing further
 		// return before probing further
 		cpu_features = set
 		cpu_features = set
 		return
 		return
@@ -133,4 +133,4 @@ init_cpu_name :: proc "c" () {
 		brand = brand[:len(brand) - 1]
 		brand = brand[:len(brand) - 1]
 	}
 	}
 	cpu_name = brand
 	cpu_name = brand
-}
+}

+ 8 - 3
core/sys/info/platform_openbsd.odin → core/sys/info/platform_bsd.odin

@@ -1,3 +1,4 @@
+//+build openbsd, netbsd
 package sysinfo
 package sysinfo
 
 
 import sys "core:sys/unix"
 import sys "core:sys/unix"
@@ -10,12 +11,16 @@ version_string_buf: [1024]u8
 
 
 @(init, private)
 @(init, private)
 init_os_version :: proc () {
 init_os_version :: proc () {
-	os_version.platform = .OpenBSD
+	when ODIN_OS == .NetBSD {
+		os_version.platform = .NetBSD
+	} else {
+		os_version.platform = .OpenBSD
+	}
 
 
 	kernel_version_buf: [1024]u8
 	kernel_version_buf: [1024]u8
 
 
 	b := strings.builder_from_bytes(version_string_buf[:])
 	b := strings.builder_from_bytes(version_string_buf[:])
-	// Retrieve kernel info using `sysctl`, e.g. OpenBSD
+	// Retrieve kernel info using `sysctl`, e.g. OpenBSD and NetBSD
 	mib := []i32{sys.CTL_KERN, sys.KERN_OSTYPE}
 	mib := []i32{sys.CTL_KERN, sys.KERN_OSTYPE}
 	if !sys.sysctl(mib, &kernel_version_buf) {
 	if !sys.sysctl(mib, &kernel_version_buf) {
 		return
 		return
@@ -68,4 +73,4 @@ init_ram :: proc() {
 	if sys.sysctl(mib, &mem_size) {
 	if sys.sysctl(mib, &mem_size) {
 		ram.total_ram = int(mem_size)
 		ram.total_ram = int(mem_size)
 	}
 	}
-}
+}

+ 102 - 0
core/sys/unix/pthread_netbsd.odin

@@ -0,0 +1,102 @@
+package unix
+
+import "core:c"
+
+pthread_t :: distinct u64
+
+SEM_T_SIZE :: 8
+
+PTHREAD_CONDATTR_T_SIZE    :: 16
+PTHREAD_MUTEXATTR_T_SIZE   :: 16
+PTHREAD_RWLOCKATTR_T_SIZE  :: 16
+PTHREAD_BARRIERATTR_T_SIZE :: 16
+
+PTHREAD_COND_T_SIZE    :: 40
+PTHREAD_MUTEX_T_SIZE   :: 48
+PTHREAD_RWLOCK_T_SIZE  :: 64
+PTHREAD_BARRIER_T_SIZE :: 48
+PTHREAD_ATTR_T_SIZE    :: 16
+
+pthread_cond_t :: struct #align(8) {
+	_: [PTHREAD_COND_T_SIZE] c.char,
+}
+
+pthread_mutex_t :: struct #align(8) {
+	_: [PTHREAD_MUTEX_T_SIZE] c.char,
+}
+
+pthread_rwlock_t :: struct #align(8) {
+	_: [PTHREAD_RWLOCK_T_SIZE] c.char,
+}
+
+pthread_barrier_t :: struct #align(8) {
+	_: [PTHREAD_BARRIER_T_SIZE] c.char,
+}
+
+pthread_attr_t :: struct #align(8) {
+	_: [PTHREAD_ATTR_T_SIZE] c.char,
+}
+
+pthread_condattr_t :: struct #align(8) {
+	_: [PTHREAD_CONDATTR_T_SIZE] c.char,
+}
+
+pthread_mutexattr_t :: struct #align(8) {
+	_: [PTHREAD_MUTEXATTR_T_SIZE] c.char,
+}
+
+pthread_rwlockattr_t :: struct #align(8) {
+	_: [PTHREAD_RWLOCKATTR_T_SIZE] c.char,
+}
+
+pthread_barrierattr_t :: struct #align(8) {
+	_: [PTHREAD_BARRIERATTR_T_SIZE] c.char,
+}
+
+PTHREAD_MUTEX_NORMAL     :: 0
+PTHREAD_MUTEX_ERRORCHECK :: 1
+PTHREAD_MUTEX_RECURSIVE  :: 2
+
+PTHREAD_CREATE_JOINABLE :: 0
+PTHREAD_CREATE_DETACHED :: 1
+PTHREAD_INHERIT_SCHED   :: 0
+PTHREAD_EXPLICIT_SCHED  :: 1
+PTHREAD_PROCESS_PRIVATE :: 0
+PTHREAD_PROCESS_SHARED  :: 1
+
+SCHED_NONE  :: -1
+SCHED_OTHER :: 0
+SCHED_FIFO  :: 1
+SCHED_RR    :: 3
+
+sched_param :: struct {
+	sched_priority: c.int,
+}
+
+sem_t :: struct #align(16) {
+	_: [SEM_T_SIZE] c.char,
+}
+
+PTHREAD_CANCEL_ENABLE       :: 0
+PTHREAD_CANCEL_DISABLE      :: 1
+PTHREAD_CANCEL_DEFERRED     :: 0
+PTHREAD_CANCEL_ASYNCHRONOUS :: 1
+
+foreign import "system:pthread"
+
+@(default_calling_convention="c")
+foreign pthread {
+	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 ---
+
+	pthread_yield :: proc() ---
+
+	pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int ---
+	pthread_setcanceltype  :: proc (type:  c.int, old_type:  ^c.int) -> c.int ---
+	pthread_cancel         :: proc (thread: pthread_t) -> c.int ---
+}

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

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

+ 3 - 0
core/sys/unix/syscalls_netbsd.odin

@@ -0,0 +1,3 @@
+package unix
+
+SYS___futex  : uintptr : 166

+ 44 - 0
core/sys/unix/sysctl_netbsd.odin

@@ -0,0 +1,44 @@
+package unix
+
+import "core:c"
+foreign import libc "system:c"
+
+@(default_calling_convention="c")
+foreign libc {
+	@(link_name="sysctl") _unix_sysctl :: proc(name: [^]i32, namelen: u32, oldp: rawptr, oldlenp: ^c.size_t, newp: rawptr, newlen: c.size_t) -> i32 ---
+}
+
+sysctl :: proc(mib: []i32, val: ^$T) -> (ok: bool) {
+	mib := mib
+	result_size := c.size_t(size_of(T))
+	res := _unix_sysctl(raw_data(mib), u32(len(mib)), val, &result_size, nil, 0)
+	return res == 0
+}
+
+// See /usr/include/sys/sysctl.h for details
+CTL_KERN   :: 1
+	KERN_OSTYPE     :: 1
+	KERN_OSRELEASE  :: 2
+	KERN_OSREV      :: 3
+	KERN_VERSION    :: 4
+CTL_VM     :: 2
+CTL_FS     :: 3
+CTL_NET    :: 4
+CTL_DEBUG  :: 5
+CTL_HW     :: 6
+	HW_MACHINE      :: 1
+	HW_MODEL        :: 2
+	HW_NCPU         :: 3
+	HW_BYTEORDER    :: 4
+	HW_PHYSMEM	    :: 5
+	HW_USERMEM      :: 6
+	HW_PAGESIZE     :: 7
+	HW_DISKNAMES    :: 8
+	HW_IOSTATS      :: 9
+	HW_MACHINE_ARCH :: 10
+	HW_ALIGNBYTES   :: 11
+	HW_CNMAGIC      :: 12
+	HW_PHYSMEM64    :: 13
+	HW_USERMEM64    :: 14
+	HW_IOSTATNAMES  :: 15
+	HW_NCPUONLINE   :: 16

+ 15 - 7
core/sys/unix/time_unix.odin

@@ -1,4 +1,4 @@
-//+build linux, darwin, freebsd, openbsd, haiku
+//+build linux, darwin, freebsd, openbsd, netbsd, haiku
 package unix
 package unix
 
 
 when ODIN_OS == .Darwin {
 when ODIN_OS == .Darwin {
@@ -9,11 +9,20 @@ when ODIN_OS == .Darwin {
 
 
 import "core:c"
 import "core:c"
 
 
-@(default_calling_convention="c")
-foreign libc {
-	clock_gettime :: proc(clock_id: u64, timespec: ^timespec) -> c.int ---
-	sleep         :: proc(seconds: c.uint) -> c.int ---
-	nanosleep     :: proc(requested, remaining: ^timespec) -> c.int ---
+when ODIN_OS == .NetBSD {
+	@(default_calling_convention="c")
+		foreign libc {
+			@(link_name="__clock_gettime50") clock_gettime :: proc(clock_id: u64, timespec: ^timespec) -> c.int ---
+			@(link_name="__nanosleep50")     nanosleep     :: proc(requested, remaining: ^timespec) -> c.int ---
+			@(link_name="sleep")             sleep         :: proc(seconds: c.uint) -> c.int ---
+	}
+} else {
+	@(default_calling_convention="c")
+	foreign libc {
+		clock_gettime :: proc(clock_id: u64, timespec: ^timespec) -> c.int ---
+		sleep         :: proc(seconds: c.uint) -> c.int ---
+		nanosleep     :: proc(requested, remaining: ^timespec) -> c.int ---
+	}
 }
 }
 
 
 timespec :: struct {
 timespec :: struct {
@@ -65,7 +74,6 @@ seconds_since_boot :: proc "c" () -> f64 {
 	return f64(ts_boottime.tv_sec) + f64(ts_boottime.tv_nsec) / 1e9
 	return f64(ts_boottime.tv_sec) + f64(ts_boottime.tv_nsec) / 1e9
 }
 }
 
 
-
 inline_nanosleep :: proc "c" (nanoseconds: i64) -> (remaining: timespec, res: i32) {
 inline_nanosleep :: proc "c" (nanoseconds: i64) -> (remaining: timespec, res: i32) {
 	s, ns := nanoseconds / 1e9, nanoseconds % 1e9
 	s, ns := nanoseconds / 1e9, nanoseconds % 1e9
 	requested := timespec{tv_sec=s, tv_nsec=ns}
 	requested := timespec{tv_sec=s, tv_nsec=ns}

+ 5 - 5
core/thread/thread_unix.odin

@@ -1,4 +1,4 @@
-// +build linux, darwin, freebsd, openbsd, haiku
+// +build linux, darwin, freebsd, openbsd, netbsd, haiku
 // +private
 // +private
 package thread
 package thread
 
 
@@ -20,7 +20,7 @@ Thread_Os_Specific :: struct #align(16) {
 // It then waits for `start` to be called.
 // It then waits for `start` to be called.
 //
 //
 _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
 _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
-	__linux_thread_entry_proc :: proc "c" (t: rawptr) -> rawptr {
+	__unix_thread_entry_proc :: proc "c" (t: rawptr) -> rawptr {
 		t := (^Thread)(t)
 		t := (^Thread)(t)
 
 
 		when ODIN_OS != .Darwin {
 		when ODIN_OS != .Darwin {
@@ -82,7 +82,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
 
 
 	// NOTE(tetra, 2019-11-01): These only fail if their argument is invalid.
 	// NOTE(tetra, 2019-11-01): These only fail if their argument is invalid.
 	assert(unix.pthread_attr_setdetachstate(&attrs, unix.PTHREAD_CREATE_JOINABLE) == 0)
 	assert(unix.pthread_attr_setdetachstate(&attrs, unix.PTHREAD_CREATE_JOINABLE) == 0)
-	when ODIN_OS != .Haiku {
+	when ODIN_OS != .Haiku && ODIN_OS != .NetBSD {
 		assert(unix.pthread_attr_setinheritsched(&attrs, unix.PTHREAD_EXPLICIT_SCHED) == 0)
 		assert(unix.pthread_attr_setinheritsched(&attrs, unix.PTHREAD_EXPLICIT_SCHED) == 0)
 	}
 	}
 
 
@@ -95,7 +95,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
 	// Set thread priority.
 	// Set thread priority.
 	policy: i32
 	policy: i32
 	res: i32
 	res: i32
-	when ODIN_OS != .Haiku {
+	when ODIN_OS != .Haiku && ODIN_OS != .NetBSD {
 		res = unix.pthread_attr_getschedpolicy(&attrs, &policy)
 		res = unix.pthread_attr_getschedpolicy(&attrs, &policy)
 		assert(res == 0)
 		assert(res == 0)
 	}
 	}
@@ -113,7 +113,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
 	assert(res == 0)
 	assert(res == 0)
 
 
 	thread.procedure = procedure
 	thread.procedure = procedure
-	if unix.pthread_create(&thread.unix_thread, &attrs, __linux_thread_entry_proc, thread) != 0 {
+	if unix.pthread_create(&thread.unix_thread, &attrs, __unix_thread_entry_proc, thread) != 0 {
 		free(thread, thread.creation_allocator)
 		free(thread, thread.creation_allocator)
 		return nil
 		return nil
 	}
 	}

+ 1 - 1
core/time/time_unix.odin

@@ -1,5 +1,5 @@
 //+private
 //+private
-//+build linux, darwin, freebsd, openbsd, haiku
+//+build linux, darwin, freebsd, openbsd, netbsd, haiku
 package time
 package time
 
 
 import "core:sys/unix"
 import "core:sys/unix"

+ 15 - 3
src/bug_report.cpp

@@ -17,7 +17,7 @@
 	#include <sys/sysctl.h>
 	#include <sys/sysctl.h>
 #endif
 #endif
 
 
-#if defined(GB_SYSTEM_OPENBSD)
+#if defined(GB_SYSTEM_OPENBSD) || defined(GB_SYSTEM_NETBSD)
 	#include <sys/sysctl.h>
 	#include <sys/sysctl.h>
 	#include <sys/utsname.h>
 	#include <sys/utsname.h>
 #endif
 #endif
@@ -263,6 +263,14 @@ gb_internal void report_ram_info() {
 		if (sysctl(mibs, 2, &ram_amount, &val_size, NULL, 0) != -1) {
 		if (sysctl(mibs, 2, &ram_amount, &val_size, NULL, 0) != -1) {
 			gb_printf("%lld MiB\n", ram_amount / gb_megabytes(1));
 			gb_printf("%lld MiB\n", ram_amount / gb_megabytes(1));
 		}
 		}
+	#elif defined(GB_SYSTEM_NETBSD)
+		uint64_t ram_amount;
+		size_t   val_size = sizeof(ram_amount);
+
+		int mibs[] = { CTL_HW, HW_PHYSMEM64 };
+		if (sysctl(mibs, 2, &ram_amount, &val_size, NULL, 0) != -1) {
+			gb_printf("%lu MiB\n", ram_amount / gb_megabytes(1));
+		}
 	#elif defined(GB_SYSTEM_OPENBSD)
 	#elif defined(GB_SYSTEM_OPENBSD)
 		uint64_t ram_amount;
 		uint64_t ram_amount;
 		size_t   val_size = sizeof(ram_amount);
 		size_t   val_size = sizeof(ram_amount);
@@ -998,13 +1006,17 @@ gb_internal void report_os_info() {
 			gb_printf("macOS Unknown (kernel: %d.%d.%d)\n", major, minor, patch);
 			gb_printf("macOS Unknown (kernel: %d.%d.%d)\n", major, minor, patch);
 			return;
 			return;
 		}
 		}
-	#elif defined(GB_SYSTEM_OPENBSD)
+	#elif defined(GB_SYSTEM_OPENBSD) || defined(GB_SYSTEM_NETBSD)
 		struct utsname un;
 		struct utsname un;
 		
 		
 		if (uname(&un) != -1) {
 		if (uname(&un) != -1) {
 			gb_printf("%s %s %s %s\n", un.sysname, un.release, un.version, un.machine);
 			gb_printf("%s %s %s %s\n", un.sysname, un.release, un.version, un.machine);
 		} else {
 		} else {
-			gb_printf("OpenBSD: Unknown\n");    
+			#if defined(GB_SYSTEM_NETBSD)
+				gb_printf("NetBSD: Unknown\n");
+			#else
+				gb_printf("OpenBSD: Unknown\n");    
+			#endif
 		}
 		}
 	#elif defined(GB_SYSTEM_FREEBSD)
 	#elif defined(GB_SYSTEM_FREEBSD)
 		#define freebsd_version_buffer 129
 		#define freebsd_version_buffer 129

+ 13 - 0
src/build_settings.cpp

@@ -18,6 +18,7 @@ enum TargetOsKind : u16 {
 	TargetOs_essence,
 	TargetOs_essence,
 	TargetOs_freebsd,
 	TargetOs_freebsd,
 	TargetOs_openbsd,
 	TargetOs_openbsd,
+	TargetOs_netbsd,
 	TargetOs_haiku,
 	TargetOs_haiku,
 	
 	
 	TargetOs_wasi,
 	TargetOs_wasi,
@@ -84,6 +85,7 @@ gb_global String target_os_names[TargetOs_COUNT] = {
 	str_lit("essence"),
 	str_lit("essence"),
 	str_lit("freebsd"),
 	str_lit("freebsd"),
 	str_lit("openbsd"),
 	str_lit("openbsd"),
+	str_lit("netbsd"),
 	str_lit("haiku"),
 	str_lit("haiku"),
 	
 	
 	str_lit("wasi"),
 	str_lit("wasi"),
@@ -1020,6 +1022,13 @@ gb_global TargetMetrics target_openbsd_amd64 = {
 	str_lit("x86_64-unknown-openbsd-elf"),
 	str_lit("x86_64-unknown-openbsd-elf"),
 };
 };
 
 
+gb_global TargetMetrics target_netbsd_amd64 = {
+	TargetOs_netbsd,
+	TargetArch_amd64,
+	8, 8, AMD64_MAX_ALIGNMENT, 16,
+	str_lit("x86_64-unknown-netbsd-elf"),
+};
+
 gb_global TargetMetrics target_haiku_amd64 = {
 gb_global TargetMetrics target_haiku_amd64 = {
 	TargetOs_haiku,
 	TargetOs_haiku,
 	TargetArch_amd64,
 	TargetArch_amd64,
@@ -1127,6 +1136,7 @@ gb_global NamedTargetMetrics named_targets[] = {
 	{ str_lit("freebsd_arm64"),       &target_freebsd_arm64  },
 	{ str_lit("freebsd_arm64"),       &target_freebsd_arm64  },
 
 
 	{ str_lit("openbsd_amd64"),       &target_openbsd_amd64  },
 	{ str_lit("openbsd_amd64"),       &target_openbsd_amd64  },
+	{ str_lit("netbsd_amd64"),        &target_netbsd_amd64   },
 	{ str_lit("haiku_amd64"),         &target_haiku_amd64    },
 	{ str_lit("haiku_amd64"),         &target_haiku_amd64    },
 
 
 	{ str_lit("freestanding_wasm32"), &target_freestanding_wasm32 },
 	{ str_lit("freestanding_wasm32"), &target_freestanding_wasm32 },
@@ -1886,6 +1896,8 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta
 			#endif
 			#endif
 		#elif defined(GB_SYSTEM_OPENBSD)
 		#elif defined(GB_SYSTEM_OPENBSD)
 			metrics = &target_openbsd_amd64;
 			metrics = &target_openbsd_amd64;
+		#elif defined(GB_SYSTEM_NETBSD)
+			metrics = &target_netbsd_amd64;
 		#elif defined(GB_SYSTEM_HAIKU)
 		#elif defined(GB_SYSTEM_HAIKU)
 			metrics = &target_haiku_amd64;
 			metrics = &target_haiku_amd64;
 		#elif defined(GB_CPU_ARM)
 		#elif defined(GB_CPU_ARM)
@@ -2423,6 +2435,7 @@ gb_internal bool init_build_paths(String init_filename) {
 		case TargetOs_essence:
 		case TargetOs_essence:
 		case TargetOs_freebsd:
 		case TargetOs_freebsd:
 		case TargetOs_openbsd:
 		case TargetOs_openbsd:
+		case TargetOs_netbsd:
 		case TargetOs_haiku:
 		case TargetOs_haiku:
 			gb_printf_err("-no-crt on unix systems requires either -default-to-nil-allocator or -default-to-panic-allocator to also be present because the default allocator requires crt\n");
 			gb_printf_err("-no-crt on unix systems requires either -default-to-nil-allocator or -default-to-panic-allocator to also be present because the default allocator requires crt\n");
 			return false;
 			return false;

+ 1 - 0
src/checker.cpp

@@ -1013,6 +1013,7 @@ gb_internal void init_universal(void) {
 			{"FreeBSD",      TargetOs_freebsd},
 			{"FreeBSD",      TargetOs_freebsd},
 			{"Haiku",        TargetOs_haiku},
 			{"Haiku",        TargetOs_haiku},
 			{"OpenBSD",      TargetOs_openbsd},
 			{"OpenBSD",      TargetOs_openbsd},
+			{"NetBSD",       TargetOs_netbsd},
 			{"WASI",         TargetOs_wasi},
 			{"WASI",         TargetOs_wasi},
 			{"JS",           TargetOs_js},
 			{"JS",           TargetOs_js},
 			{"Freestanding", TargetOs_freestanding},
 			{"Freestanding", TargetOs_freestanding},

+ 42 - 1
src/gb/gb.h

@@ -83,6 +83,10 @@ extern "C" {
 		#ifndef GB_SYSTEM_OPENBSD
 		#ifndef GB_SYSTEM_OPENBSD
 		#define GB_SYSTEM_OPENBSD 1
 		#define GB_SYSTEM_OPENBSD 1
 		#endif
 		#endif
+	#elif defined(__NetBSD__)
+		#ifndef GB_SYSTEM_NETBSD
+		#define GB_SYSTEM_NETBSD 1
+		#endif
 	#elif defined(__HAIKU__) || defined(__haiku__)
 	#elif defined(__HAIKU__) || defined(__haiku__)
 		#ifndef GB_SYSTEM_HAIKU
 		#ifndef GB_SYSTEM_HAIKU
 		#define GB_SYSTEM_HAIKU 1
 		#define GB_SYSTEM_HAIKU 1
@@ -208,7 +212,7 @@ extern "C" {
 	#endif
 	#endif
 	#include <stdlib.h> // NOTE(bill): malloc on linux
 	#include <stdlib.h> // NOTE(bill): malloc on linux
 	#include <sys/mman.h>
 	#include <sys/mman.h>
-	#if !defined(GB_SYSTEM_OSX) && !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__HAIKU__)
+	#if !defined(GB_SYSTEM_OSX) && !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) && !defined(__HAIKU__)
 		#include <sys/sendfile.h>
 		#include <sys/sendfile.h>
 	#endif
 	#endif
 	#include <sys/stat.h>
 	#include <sys/stat.h>
@@ -250,6 +254,11 @@ extern "C" {
 	#define lseek64 lseek
 	#define lseek64 lseek
 #endif
 #endif
 
 
+#if defined(GB_SYSTEM_NETBSD)
+	#include <stdio.h>
+	#define lseek64 lseek
+#endif
+
 #if defined(GB_SYSTEM_HAIKU)
 #if defined(GB_SYSTEM_HAIKU)
 	#include <stdio.h>
 	#include <stdio.h>
 	#include <pthread.h>
 	#include <pthread.h>
@@ -817,6 +826,13 @@ typedef struct gbAffinity {
 	isize thread_count;
 	isize thread_count;
 	isize threads_per_core;
 	isize threads_per_core;
 } gbAffinity;
 } gbAffinity;
+#elif defined(GB_SYSTEM_NETBSD)
+typedef struct gbAffinity {
+	b32 is_accurate;
+	isize core_count;
+	isize thread_count;
+	isize threads_per_core;
+} gbAffinity;
 #elif defined(GB_SYSTEM_HAIKU)
 #elif defined(GB_SYSTEM_HAIKU)
 typedef struct gbAffinity {
 typedef struct gbAffinity {
 	b32   is_accurate;
 	b32   is_accurate;
@@ -3269,6 +3285,31 @@ isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core) {
 	GB_ASSERT(0 <= core && core < a->core_count);
 	GB_ASSERT(0 <= core && core < a->core_count);
 	return a->threads_per_core;
 	return a->threads_per_core;
 }
 }
+
+#elif defined(GB_SYSTEM_NETBSD)
+#include <unistd.h>
+
+void gb_affinity_init(gbAffinity *a) {
+	a->core_count       = sysconf(_SC_NPROCESSORS_ONLN);
+	a->threads_per_core = 1;
+	a->is_accurate      = a->core_count > 0;
+	a->core_count       = a->is_accurate ? a->core_count : 1;
+	a->thread_count     = a->core_count;
+}
+
+void gb_affinity_destroy(gbAffinity *a) {
+	gb_unused(a);
+}
+
+b32 gb_affinity_set(gbAffinity *a, isize core, isize thread_index) {
+	return true;
+}
+
+isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core) {
+	GB_ASSERT(0 <= core && core < a->core_count);
+	return a->threads_per_core;
+}
+
 #elif defined(GB_SYSTEM_HAIKU)
 #elif defined(GB_SYSTEM_HAIKU)
 #include <unistd.h>
 #include <unistd.h>
 
 

+ 1 - 1
src/path.cpp

@@ -341,7 +341,7 @@ gb_internal ReadDirectoryError read_directory(String path, Array<FileInfo> *fi)
 
 
 	return ReadDirectory_None;
 	return ReadDirectory_None;
 }
 }
-#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD) || defined(GB_SYSTEM_HAIKU)
+#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD) || defined(GB_SYSTEM_NETBSD) || defined(GB_SYSTEM_HAIKU)
 
 
 #include <dirent.h>
 #include <dirent.h>
 
 

+ 10 - 2
src/threading.cpp

@@ -628,15 +628,23 @@ gb_internal void thread_set_name(Thread *t, char const *name) {
 	pthread_setname_np(name);
 	pthread_setname_np(name);
 #elif defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD)
 #elif defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD)
 	pthread_set_name_np(t->posix_handle, name);
 	pthread_set_name_np(t->posix_handle, name);
+#elif defined(GB_SYSTEM_NETBSD)
+	pthread_setname_np(t->posix_handle, "%s", (void*)name);
 #else
 #else
 	// TODO(bill): Test if this works
 	// TODO(bill): Test if this works
 	pthread_setname_np(t->posix_handle, name);
 	pthread_setname_np(t->posix_handle, name);
 #endif
 #endif
 }
 }
 
 
-#if defined(GB_SYSTEM_LINUX)
-#include <linux/futex.h>
+#if defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_NETBSD)
+
 #include <sys/syscall.h>
 #include <sys/syscall.h>
+#ifdef GB_SYSTEM_LINUX
+	#include <linux/futex.h>
+#else
+	#include <sys/futex.h>
+	#define SYS_futex SYS___futex
+#endif
 
 
 gb_internal void futex_signal(Futex *addr) {
 gb_internal void futex_signal(Futex *addr) {
 	int ret = syscall(SYS_futex, addr, FUTEX_WAKE | FUTEX_PRIVATE_FLAG, 1, NULL, NULL, 0);
 	int ret = syscall(SYS_futex, addr, FUTEX_WAKE | FUTEX_PRIVATE_FLAG, 1, NULL, NULL, 0);

+ 27 - 25
tests/core/Makefile

@@ -3,30 +3,32 @@ PYTHON=$(shell which python3)
 COMMON=-vet -strict-style
 COMMON=-vet -strict-style
 COLLECTION=-collection:tests=..
 COLLECTION=-collection:tests=..
 
 
-all: c_libc_test \
-	 compress_test \
-	 container_test \
-	 crypto_test \
-	 download_test_assets \
-	 encoding_test \
-	 filepath_test \
-	 fmt_test \
-	 hash_test \
-	 i18n_test \
-	 image_test \
-	 linalg_glsl_math_test \
-	 match_test \
-	 math_test \
-	 net_test \
-	 noise_test \
-	 os_exit_test \
-	 reflect_test \
-	 slice_test \
-	 strings_test \
-	 thread_test \
-	 runtime_test \
-	 time_test \
-	 fmt_test
+all: all_bsd \
+     net_test
+
+all_bsd: c_libc_test \
+         compress_test \
+         container_test \
+         crypto_test \
+         download_test_assets \
+         encoding_test \
+         filepath_test \
+         fmt_test \
+         hash_test \
+         i18n_test \
+         image_test \
+         linalg_glsl_math_test \
+         match_test \
+         math_test \
+         noise_test \
+         os_exit_test \
+         reflect_test \
+         slice_test \
+         strings_test \
+         thread_test \
+         runtime_test \
+         time_test \
+         fmt_test
 
 
 download_test_assets:
 download_test_assets:
 	$(PYTHON) download_assets.py
 	$(PYTHON) download_assets.py
@@ -101,4 +103,4 @@ runtime_test:
 	$(ODIN) run runtime $(COMMON) -out:test_core_runtime
 	$(ODIN) run runtime $(COMMON) -out:test_core_runtime
 
 
 time_test:
 time_test:
-	$(ODIN) run time $(COMMON) -out:test_core_time
+	$(ODIN) run time $(COMMON) -out:test_core_time

+ 4 - 2
tests/internal/Makefile

@@ -1,6 +1,8 @@
 ODIN=../../odin
 ODIN=../../odin
 
 
-all: rtti_test map_test pow_test 128_test asan_test string_compare_test
+all: all_bsd asan_test
+
+all_bsd: rtti_test map_test pow_test 128_test string_compare_test
 
 
 rtti_test:
 rtti_test:
 	$(ODIN) run test_rtti.odin -file -vet -strict-style -o:minimal
 	$(ODIN) run test_rtti.odin -file -vet -strict-style -o:minimal
@@ -18,4 +20,4 @@ asan_test:
 	$(ODIN) run test_asan.odin -file -sanitize:address -debug
 	$(ODIN) run test_asan.odin -file -sanitize:address -debug
 
 
 string_compare_test:
 string_compare_test:
-	$(ODIN) run test_string_compare.odin -file -vet -strict-style -o:minimal
+	$(ODIN) run test_string_compare.odin -file -vet -strict-style -o:minimal

+ 1 - 1
vendor/ENet/unix.odin

@@ -1,4 +1,4 @@
-//+build linux, darwin, freebsd, openbsd
+//+build linux, darwin, freebsd, openbsd, netbsd
 package ENet
 package ENet
 
 
 // When we implement the appropriate bindings for Unix, the section separated
 // When we implement the appropriate bindings for Unix, the section separated

+ 1 - 1
vendor/directx/dxc/dxcdef_unix.odin

@@ -1,4 +1,4 @@
-//+build linux, darwin, freebsd, openbsd
+//+build linux, darwin, freebsd, openbsd, netbsd
 package directx_dxc
 package directx_dxc
 import "core:c"
 import "core:c"