瀏覽代碼

Merge pull request #734 from krixano/FreeBSD

FreeBSD Support
gingerBill 5 年之前
父節點
當前提交
3211e60018

+ 2 - 0
build.sh

@@ -23,6 +23,8 @@ if [[ "$(uname)" == "Darwin" ]]; then
 	compiler="clang"
 
 	other_args="${other_args} -liconv"
+elif [[ "$(uname)" == "FreeBSD" ]]; then
+	compiler="clang"
 fi
 
 ${compiler} src/main.cpp ${warnings_to_disable} ${libraries} ${other_args} -o odin && ./odin run examples/demo/demo.odin

+ 2 - 2
core/dynlib/lib_unix.odin

@@ -1,4 +1,4 @@
-// +build linux, darwin
+// +build linux, darwin, freebsd
 package dynlib
 
 import "core:os"
@@ -18,4 +18,4 @@ symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found:
     ptr = os.dlsym(rawptr(library), symbol);
     found = ptr != nil;
     return;
-}
+}

+ 469 - 0
core/os/os_freebsd.odin

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

@@ -1,4 +1,4 @@
-//+build linux, darwin
+//+build linux, darwin, freebsd
 package path
 
 foreign import libc "system:c"

+ 1 - 1
core/runtime/procs_unix.odin

@@ -1,4 +1,4 @@
-//+build linux, darwin
+//+build linux, darwin, freebsd
 package runtime
 
 @(link_name="memset")

+ 1 - 1
core/sync/channel_unix.odin

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

+ 32 - 0
core/sync/sync_freebsd.odin

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

+ 1 - 1
core/sync/sync_unix.odin

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

+ 113 - 0
core/sys/unix/pthread_freebsd.odin

@@ -0,0 +1,113 @@
+package unix;
+
+import "core:c";
+
+pthread_t :: distinct u64;
+// pthread_t :: struct #align 16 { x: u64 };
+
+PTHREAD_COND_T_SIZE :: 8;
+
+PTHREAD_MUTEXATTR_T_SIZE :: 8;
+PTHREAD_CONDATTR_T_SIZE  :: 8;
+PTHREAD_RWLOCKATTR_T_SIZE  :: 8;
+PTHREAD_BARRIERATTR_T_SIZE :: 8;
+
+// WARNING: The sizes of these things are different yet again
+// on non-X86!
+when size_of(int) == 8 {
+	PTHREAD_ATTR_T_SIZE  :: 8;
+	PTHREAD_MUTEX_T_SIZE :: 8;
+	PTHREAD_RWLOCK_T_SIZE  :: 8;
+	PTHREAD_BARRIER_T_SIZE :: 8;
+} else when size_of(int) == 4 { // TODO
+	PTHREAD_ATTR_T_SIZE  :: 32;
+	PTHREAD_MUTEX_T_SIZE :: 32;
+	PTHREAD_RWLOCK_T_SIZE  :: 44;
+	PTHREAD_BARRIER_T_SIZE :: 20;
+}
+
+pthread_cond_t :: opaque struct #align 16 {
+	_: [PTHREAD_COND_T_SIZE] c.char,
+};
+pthread_mutex_t :: opaque struct #align 16 {
+	_: [PTHREAD_MUTEX_T_SIZE] c.char,
+};
+pthread_rwlock_t :: opaque struct #align 16 {
+	_: [PTHREAD_RWLOCK_T_SIZE] c.char,
+};
+pthread_barrier_t :: opaque struct #align 16 {
+	_: [PTHREAD_BARRIER_T_SIZE] c.char,
+};
+
+pthread_attr_t :: opaque struct #align 16 {
+	_: [PTHREAD_ATTR_T_SIZE] c.char,
+};
+pthread_condattr_t :: opaque struct #align 16 {
+	_: [PTHREAD_CONDATTR_T_SIZE] c.char,
+};
+pthread_mutexattr_t :: opaque struct #align 16 {
+	_: [PTHREAD_MUTEXATTR_T_SIZE] c.char,
+};
+pthread_rwlockattr_t :: opaque struct #align 16 {
+	_: [PTHREAD_RWLOCKATTR_T_SIZE] c.char,
+};
+pthread_barrierattr_t :: opaque struct #align 16 {
+	_: [PTHREAD_BARRIERATTR_T_SIZE] c.char,
+};
+
+PTHREAD_MUTEX_ERRORCHECK :: 1;
+PTHREAD_MUTEX_RECURSIVE :: 2;
+PTHREAD_MUTEX_NORMAL :: 3;
+
+
+PTHREAD_CREATE_JOINABLE :: 0;
+PTHREAD_CREATE_DETACHED :: 1;
+PTHREAD_INHERIT_SCHED :: 4;
+PTHREAD_EXPLICIT_SCHED :: 0;
+PTHREAD_PROCESS_PRIVATE :: 0;
+PTHREAD_PROCESS_SHARED :: 1;
+
+SCHED_FIFO  :: 1;
+SCHED_OTHER :: 2;
+SCHED_RR :: 3; // Round robin.
+
+
+sched_param :: struct {
+	sched_priority: c.int,
+}
+
+_usem :: struct {
+	_has_waiters: u32,
+	_count: u32,
+	_flags: u32,
+}
+_usem2 :: struct {
+	_count: u32,
+	_flags: u32,
+}
+sem_t :: struct {
+	_magic: u32,
+	_kern: _usem2,
+	_padding: u32,
+}
+
+foreign import "system:pthread"
+
+@(default_calling_convention="c")
+foreign pthread {
+	// create named semaphore.
+	// used in process-shared semaphores.
+	sem_open :: proc(name: cstring, flags: c.int) -> ^sem_t ---;
+
+	sem_init :: proc(sem: ^sem_t, pshared: c.int, initial_value: c.uint) -> c.int ---;
+	sem_destroy :: proc(sem: ^sem_t) -> c.int ---;
+	sem_post :: proc(sem: ^sem_t) -> c.int ---;
+	sem_wait :: proc(sem: ^sem_t) -> c.int ---;
+	sem_trywait :: proc(sem: ^sem_t) -> c.int ---;
+	// sem_timedwait :: proc(sem: ^sem_t, timeout: time.TimeSpec) -> c.int ---;
+
+	// NOTE: unclear whether pthread_yield is well-supported on Linux systems,
+	// see https://linux.die.net/man/3/pthread_yield
+	pthread_yield :: proc() ---;
+}
+

+ 1 - 1
core/thread/thread_unix.odin

@@ -1,4 +1,4 @@
-// +build linux, darwin
+// +build linux, darwin, freebsd
 package thread;
 
 import "core:runtime"

+ 1 - 1
core/time/time_unix.odin

@@ -1,4 +1,4 @@
-//+build linux, darwin
+//+build linux, darwin, freebsd
 package time
 
 IS_SUPPORTED :: true; // NOTE: Times on Darwin are UTC.

+ 50 - 0
src/build_settings.cpp

@@ -1,3 +1,8 @@
+#if defined(GB_SYSTEM_FREEBSD)
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#endif
+
 enum TargetOsKind {
 	TargetOs_Invalid,
 
@@ -6,6 +11,7 @@ enum TargetOsKind {
 	TargetOs_linux,
 	TargetOs_essence,
 	TargetOs_js,
+	TargetOs_freebsd,
 
 	TargetOs_COUNT,
 };
@@ -36,6 +42,7 @@ String target_os_names[TargetOs_COUNT] = {
 	str_lit("linux"),
 	str_lit("essence"),
 	str_lit("js"),
+	str_lit("freebsd"),
 };
 
 String target_arch_names[TargetArch_COUNT] = {
@@ -204,6 +211,23 @@ gb_global TargetMetrics target_darwin_amd64 = {
 	str_lit("e-m:o-i64:64-f80:128-n8:16:32:64-S128"),
 };
 
+gb_global TargetMetrics target_freebsd_386 = {
+	TargetOs_freebsd,
+	TargetArch_386,
+	4,
+	8,
+	str_lit("i386-unknown-freebsd-elf"),
+};
+
+gb_global TargetMetrics target_freebsd_amd64 = {
+	TargetOs_freebsd,
+	TargetArch_amd64,
+	8,
+	16,
+	str_lit("x86_64-unknown-freebsd-elf"),
+	str_lit("e-m:w-i64:64-f80:128-n8:16:32:64-S128"),
+};
+
 gb_global TargetMetrics target_essence_amd64 = {
 	TargetOs_essence,
 	TargetArch_amd64,
@@ -235,6 +259,8 @@ gb_global NamedTargetMetrics named_targets[] = {
 	{ str_lit("linux_amd64"),   &target_linux_amd64 },
 	{ str_lit("windows_386"),   &target_windows_386 },
 	{ str_lit("windows_amd64"), &target_windows_amd64 },
+	{ str_lit("freebsd_386"),   &target_freebsd_386 },
+	{ str_lit("freebsd_amd64"), &target_freebsd_amd64 },
 };
 
 NamedTargetMetrics *selected_target_metrics;
@@ -476,7 +502,21 @@ String odin_root_dir(void) {
 		// of this compiler, it should be _good enough_.
 		// That said, there's no solid 100% method on Linux to get the program's
 		// path without checking this link. Sorry.
+#if defined(GB_SYSTEM_FREEBSD)
+		int mib[4];
+		mib[0] = CTL_KERN;
+		mib[1] = KERN_PROC;
+		mib[2] = KERN_PROC_PATHNAME;
+		mib[3] = -1;
+		len = path_buf.count;
+		sysctl(mib, 4, &path_buf[0], (size_t *) &len, NULL, 0);
+#elif defined(GB_SYSTEM_NETBSD)
+		len = readlink("/proc/curproc/exe", &path_buf[0], path_buf.count);
+#elif defined(GB_SYSTEM_DRAGONFLYBSD)
+		len = readlink("/proc/curproc/file", &path_buf[0], path_buf.count);
+#else
 		len = readlink("/proc/self/exe", &path_buf[0], path_buf.count);
+#endif
 		if(len == 0) {
 			return make_string(nullptr, 0);
 		}
@@ -613,6 +653,8 @@ void init_build_context(TargetMetrics *cross_target) {
 			metrics = &target_windows_amd64;
 		#elif defined(GB_SYSTEM_OSX)
 			metrics = &target_darwin_amd64;
+		#elif defined(GB_SYSTEM_FREEBSD)
+			metrics = &target_freebsd_amd64;
 		#else
 			metrics = &target_linux_amd64;
 		#endif
@@ -621,6 +663,8 @@ void init_build_context(TargetMetrics *cross_target) {
 			metrics = &target_windows_386;
 		#elif defined(GB_SYSTEM_OSX)
 			#error "Build Error: Unsupported architecture"
+		#elif defined(GB_SYSTEM_FREEBSD)
+			metrics = &target_freebsd_386;
 		#else
 			metrics = &target_linux_386;
 		#endif
@@ -669,6 +713,9 @@ void init_build_context(TargetMetrics *cross_target) {
 		case TargetOs_linux:
 			bc->link_flags = str_lit("-arch x86-64 ");
 			break;
+		case TargetOs_freebsd:
+			bc->link_flags = str_lit("-arch x86-64");
+			break;
 		}
 	} else if (bc->metrics.arch == TargetArch_386) {
 		llc_flags = gb_string_appendc(llc_flags, "-march=x86 ");
@@ -684,6 +731,9 @@ void init_build_context(TargetMetrics *cross_target) {
 		case TargetOs_linux:
 			bc->link_flags = str_lit("-arch x86 ");
 			break;
+		case TargetOs_freebsd:
+			bc->link_flags = str_lit("-arch x86");
+			break;
 		}
 	} else if (bc->metrics.arch == TargetArch_wasm32) {
 		bc->link_flags = str_lit("--no-entry --export-table --export-all --allow-undefined ");

+ 1 - 1
src/common.cpp

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

+ 84 - 1
src/gb/gb.h

@@ -317,7 +317,7 @@ extern "C" {
 	#endif
 	#include <stdlib.h> // NOTE(bill): malloc on linux
 	#include <sys/mman.h>
-	#if !defined(GB_SYSTEM_OSX)
+	#if !defined(GB_SYSTEM_OSX) && !defined(__FreeBSD__)
 		#include <sys/sendfile.h>
 	#endif
 	#include <sys/stat.h>
@@ -342,6 +342,17 @@ extern "C" {
 	#include <mach/clock.h>
 #endif
 
+#if defined(GB_SYSTEM_FREEBSD)
+	#include <sys/sysctl.h>
+	#include <pthread_np.h>
+	#include <sys/cpuset.h>
+	#include <sys/types.h>
+	#include <sys/socket.h>
+	#include <sys/uio.h>
+	#define lseek64 lseek
+	#define sendfile(out, in, offset, count) sendfile(out, in, offset, count, NULL, NULL, 0)
+#endif
+
 #if defined(GB_SYSTEM_UNIX)
 	#include <semaphore.h>
 #endif
@@ -1046,6 +1057,13 @@ typedef struct gbAffinity {
 	isize thread_count;
 	isize threads_per_core;
 } gbAffinity;
+#elif defined(GB_SYSTEM_FREEBSD)
+typedef struct gbAffinity {
+	b32 is_accurate;
+	isize core_count;
+	isize thread_count;
+	isize threads_per_core;
+} gbAffinity;
 #else
 #error TODO(bill): Unknown system
 #endif
@@ -4805,6 +4823,8 @@ void gb_thread_set_name(gbThread *t, char const *name) {
 #elif defined(GB_SYSTEM_OSX)
 	// TODO(bill): Test if this works
 	pthread_setname_np(name);
+#elif defined(GB_SYSTEM_FREEBSD)
+	pthread_set_name_np(t->posix_handle, name);
 #else
 	// TODO(bill): Test if this works
 	pthread_setname_np(t->posix_handle, name);
@@ -5090,6 +5110,69 @@ isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core) {
 	return a->threads_per_core;
 }
 
+#elif defined(GB_SYSTEM_FREEBSD)
+#include <stdio.h>
+
+void gb_affinity_init(gbAffinity *a) {
+	usize count = 0;
+	usize count_size = sizeof(count);
+
+	a->is_accurate      = false;
+	a->thread_count     = 1;
+	a->core_count       = 1;
+	a->threads_per_core = 1;
+
+	if (sysctlbyname("hw.logicalcpu", &count, &count_size, NULL, 0) == 0) {
+		if (count > 0) {
+			a->thread_count = count;
+			// Get # of physical cores
+			if (sysctlbyname("hw.physicalcpu", &count, &count_size, NULL, 0) == 0) {
+				if (count > 0) {
+					a->core_count = count;
+					a->threads_per_core = a->thread_count / count;
+					if (a->threads_per_core < 1)
+						a->threads_per_core = 1;
+					else
+						a->is_accurate = true;
+				}
+			}
+		}
+	}
+
+}
+
+void gb_affinity_destroy(gbAffinity *a) {
+	gb_unused(a);
+}
+
+b32 gb_affinity_set(gbAffinity *a, isize core, isize thread_index) {
+	isize index;
+	pthread_t thread;
+
+	int result;
+
+	GB_ASSERT(core < a->core_count);
+	GB_ASSERT(thread_index < a->threads_per_core);
+
+	index = core * a->threads_per_core + thread_index;
+	thread = pthread_self();
+	
+
+	cpuset_t mn;
+	CPU_ZERO(&mn);
+	CPU_SET(size_t(index), &mn);
+	//info.affinity_tag = cast(integer_t)index;
+	//result = thread_policy_set(thread, THREAD_AFFINITY_POLICY, cast(thread_policy_t)&info, THREAD_AFFINITY_POLICY_COUNT);
+
+	result = pthread_setaffinity_np(thread, sizeof(cpuset_t), &mn);
+	return result == 0;
+}
+
+isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core) {
+	GB_ASSERT(core >= 0 && core < a->core_count);
+	return a->threads_per_core;
+}
+
 #elif defined(GB_SYSTEM_LINUX)
 // IMPORTANT TODO(bill): This gbAffinity stuff for linux needs be improved a lot!
 // NOTE(zangent): I have to read /proc/cpuinfo to get the number of threads per core.