Browse Source

Get Odin compiling and produced exe's running on FreeBSD

Christian Seibold 4 years ago
parent
commit
577be4a8ae

+ 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;
-}
+}

+ 377 - 0
core/os/os_freebsd.odin

@@ -0,0 +1,377 @@
+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;
+ERANGE:         Errno : 34; /* Result too large */
+
+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;
+// timespec
+
+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"

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

@@ -0,0 +1,125 @@
+package unix;
+
+import "core:c";
+
+pthread_t :: distinct u64;
+// pthread_t :: struct #align 16 { x: u64 };
+
+// NOTE(tetra): Got all the size constants from pthreadtypes-arch.h on my
+// Linux machine.
+
+PTHREAD_COND_T_SIZE :: 48;
+
+PTHREAD_MUTEXATTR_T_SIZE :: 4;
+PTHREAD_CONDATTR_T_SIZE  :: 4;
+PTHREAD_RWLOCKATTR_T_SIZE  :: 8;
+PTHREAD_BARRIERATTR_T_SIZE :: 4;
+
+// WARNING: The sizes of these things are different yet again
+// on non-X86!
+when size_of(int) == 8 {
+	PTHREAD_ATTR_T_SIZE  :: 56;
+	PTHREAD_MUTEX_T_SIZE :: 40;
+	PTHREAD_RWLOCK_T_SIZE  :: 56;
+	PTHREAD_BARRIER_T_SIZE :: 32;
+} else when size_of(int) == 4 {
+	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_OTHER :: 0;
+SCHED_FIFO  :: 1;
+SCHED_RR :: 2; // Round robin.
+*/
+
+sched_param :: struct {
+	sched_priority: c.int,
+}
+
+/*sem_t :: struct #align 16 {
+	_: [SEM_T_SIZE] c.char,
+}*/
+_usem :: struct {
+	_has_waiters: u32,
+	_count: u32,
+	_flags: u32,
+}
+_usem2 :: struct {
+	_count: u32,
+	_flags: u32,
+}
+sem_t :: struct {
+	_magic: u32,
+	_kern: _usem2,
+	_padding: u32,
+}
+
+when size_of(int) == 8 {
+	SEM_T_SIZE :: 32;
+} else when size_of(int) == 4 {
+	SEM_T_SIZE :: 16;
+}
+
+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.

+ 13 - 3
src/build_settings.cpp

@@ -216,15 +216,15 @@ gb_global TargetMetrics target_freebsd_386 = {
 	TargetArch_386,
 	4,
 	8,
-	str_lit("i386-unknown-freebsd"),
+	str_lit("i386-unknown-freebsd-elf"),
 };
 
 gb_global TargetMetrics target_freebsd_amd64 = {
 	TargetOs_freebsd,
-	TargetArch_386,
+	TargetArch_amd64,
 	8,
 	16,
-	str_lit("x86_64-unknown-freebsd"),
+	str_lit("x86_64-unknown-freebsd-elf"),
 	str_lit("e-m:w-i64:64-f80:128-n8:16:32:64-S128"),
 };
 
@@ -653,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
@@ -661,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
@@ -709,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 ");
@@ -724,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 ");