From 4bfbc64e3f6182b2521a63916ac608a52a08551f Mon Sep 17 00:00:00 2001
From: Damian Gryski <damian@gryski.com>
Date: Wed, 30 Apr 2025 10:31:15 -0700
Subject: [PATCH 1/2] runtime,os: add os.Executable() for Darwin

---
 src/os/executable_darwin.go | 10 ++++++++++
 src/os/executable_other.go  |  2 +-
 src/runtime/os_darwin.go    | 35 +++++++++++++++++++++++++++++++++++
 3 files changed, 46 insertions(+), 1 deletion(-)
 create mode 100644 src/os/executable_darwin.go

diff --git a/src/os/executable_darwin.go b/src/os/executable_darwin.go
new file mode 100644
index 0000000000..7c65a90c3d
--- /dev/null
+++ b/src/os/executable_darwin.go
@@ -0,0 +1,10 @@
+//go:build darwin
+
+package os
+
+// via runtime because we need argc/argv ptrs
+func runtime_executable_path() string
+
+func Executable() (string, error) {
+	return runtime_executable_path(), nil
+}
diff --git a/src/os/executable_other.go b/src/os/executable_other.go
index 5da50ede18..ce99c13009 100644
--- a/src/os/executable_other.go
+++ b/src/os/executable_other.go
@@ -1,4 +1,4 @@
-//go:build !linux || baremetal
+//go:build (!linux && !darwin) || baremetal
 
 package os
 
diff --git a/src/runtime/os_darwin.go b/src/runtime/os_darwin.go
index e7f7b368fb..df5c598807 100644
--- a/src/runtime/os_darwin.go
+++ b/src/runtime/os_darwin.go
@@ -229,3 +229,38 @@ func call_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) int32
 
 //export tinygo_syscall6X
 func call_syscall6X(fn, a1, a2, a3, a4, a5, a6 uintptr) uintptr
+
+//go:linkname os_runtime_executable_path os.runtime_executable_path
+func os_runtime_executable_path() string {
+	argv := (*unsafe.Pointer)(unsafe.Pointer(main_argv))
+
+	// skip over argv
+	argv = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(argv), (uintptr(main_argc)+1)*unsafe.Sizeof(argv)))
+
+	// skip over envv
+	for (*argv) != nil {
+		argv = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(argv), unsafe.Sizeof(argv)))
+	}
+
+	// next string is exe path
+	argv = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(argv), unsafe.Sizeof(argv)))
+
+	cstr := unsafe.Pointer(*argv)
+	length := strlen(cstr)
+	argString := _string{
+		length: length,
+		ptr:    (*byte)(cstr),
+	}
+	executablePath := *(*string)(unsafe.Pointer(&argString))
+
+	// strip "executable_path=" prefix if available, it's added after OS X 10.11.
+	executablePath = stringsTrimPrefix(executablePath, "executable_path=")
+	return executablePath
+}
+
+func stringsTrimPrefix(s, prefix string) string {
+	if len(s) >= len(prefix) && s[:len(prefix)] == prefix {
+		return s[len(prefix):]
+	}
+	return s
+}

From 228bbcb5eee4b318ccf7edfd03398a7b1ad476f7 Mon Sep 17 00:00:00 2001
From: Damian Gryski <damian@gryski.com>
Date: Wed, 30 Apr 2025 11:36:03 -0700
Subject: [PATCH 2/2] os: handle relative and abs paths in Executable()

---
 src/os/executable_darwin.go | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/src/os/executable_darwin.go b/src/os/executable_darwin.go
index 7c65a90c3d..e689db23fb 100644
--- a/src/os/executable_darwin.go
+++ b/src/os/executable_darwin.go
@@ -6,5 +6,14 @@ package os
 func runtime_executable_path() string
 
 func Executable() (string, error) {
-	return runtime_executable_path(), nil
+	p := runtime_executable_path()
+	if p != "" && p[0] == '/' {
+		// absolute path
+		return p, nil
+	}
+	cwd, err := Getwd()
+	if err != nil {
+		return "", err
+	}
+	return joinPath(cwd, p), nil
 }