runtime_unix.mx raw

   1  //go:build darwin || (linux && !baremetal && !wasip1 && !wasm_unknown && !wasip2 && !nintendoswitch)
   2  
   3  package runtime
   4  
   5  import (
   6  	"internal/futex"
   7  	"internal/task"
   8  	"math/bits"
   9  	"sync/atomic"
  10  	"moxie"
  11  	"unsafe"
  12  )
  13  
  14  //export write
  15  func libc_write(fd int32, buf unsafe.Pointer, count uint) int
  16  
  17  //export usleep
  18  func usleep(usec uint) int
  19  
  20  //export pause
  21  func pause() int32
  22  
  23  // void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
  24  // Note: off_t is defined as int64 because:
  25  //   - musl (used on Linux) always defines it as int64
  26  //   - darwin is practically always 64-bit anyway
  27  //
  28  //export mmap
  29  func mmap(addr unsafe.Pointer, length uintptr, prot, flags, fd int, offset int64) unsafe.Pointer
  30  
  31  //export abort
  32  func abort()
  33  
  34  //export exit
  35  func exit(code int)
  36  
  37  //export raise
  38  func raise(sig int32)
  39  
  40  //export clock_gettime
  41  func libc_clock_gettime(clk_id int32, ts *timespec)
  42  
  43  //export __clock_gettime64
  44  func libc_clock_gettime64(clk_id int32, ts *timespec)
  45  
  46  // Portable (64-bit) variant of clock_gettime.
  47  func clock_gettime(clk_id int32, ts *timespec) {
  48  	if TargetBits == 32 {
  49  		// This is a 32-bit architecture (386, arm, etc).
  50  		// We would like to use the 64-bit version of this function so that
  51  		// binaries will continue to run after Y2038.
  52  		// For more information:
  53  		//   - https://musl.libc.org/time64.html
  54  		//   - https://sourceware.org/glibc/wiki/Y2038ProofnessDesign
  55  		libc_clock_gettime64(clk_id, ts)
  56  	} else {
  57  		// This is a 64-bit architecture (amd64, arm64, etc).
  58  		// Use the regular variant, because it already fixes the Y2038 problem
  59  		// by using 64-bit integer types.
  60  		libc_clock_gettime(clk_id, ts)
  61  	}
  62  }
  63  
  64  // Note: tv_sec and tv_nsec normally vary in size by platform. However, we're
  65  // using the time64 variant (see clock_gettime above), so the formats are the
  66  // same between 32-bit and 64-bit architectures.
  67  // There is one issue though: on big-endian systems, tv_nsec would be incorrect.
  68  // But we don't support big-endian systems yet (as of 2021) so this is fine.
  69  type timespec struct {
  70  	tv_sec  int64 // time_t with time64 support (always 64-bit)
  71  	tv_nsec int64 // unsigned 64-bit integer on all time64 platforms
  72  }
  73  
  74  // Highest address of the stack of the main thread.
  75  var stackTop uintptr
  76  
  77  // Entry point for Go. Initialize all packages and call main.main().
  78  //
  79  //export main
  80  func main(argc int32, argv *unsafe.Pointer) int {
  81  	if needsStaticHeap {
  82  		// Allocate area for the heap if the GC needs it.
  83  		allocateHeap()
  84  	}
  85  
  86  	// Store argc and argv for later use.
  87  	main_argc = argc
  88  	main_argv = argv
  89  
  90  	// Register some fatal signals, so that we can print slightly better error
  91  	// messages.
  92  	moxie_register_fatal_signals()
  93  
  94  	// Obtain the initial stack pointer right before calling the run() function.
  95  	// The run function has been moved to a separate (non-inlined) function so
  96  	// that the correct stack pointer is read.
  97  	stackTop = getCurrentStackPointer()
  98  	runMain()
  99  
 100  	// For libc compatibility.
 101  	return 0
 102  }
 103  
 104  var (
 105  	main_argc int32
 106  	main_argv *unsafe.Pointer
 107  	args      []string
 108  )
 109  
 110  //go:linkname os_runtime_args os.runtime_args
 111  func os_runtime_args() []string {
 112  	if args == nil {
 113  		// Make args slice big enough so that it can store all command line
 114  		// arguments.
 115  		args = make([]string, main_argc)
 116  
 117  		// Initialize command line parameters.
 118  		argv := main_argv
 119  		for i := 0; i < int(main_argc); i++ {
 120  			// Convert the C string to a Go string.
 121  			length := strlen(*argv)
 122  			arg := (*_string)(unsafe.Pointer(&args[i]))
 123  			arg.length = length
 124  			arg.cap = length
 125  			arg.ptr = (*byte)(*argv)
 126  			// This is the Go equivalent of "argv++" in C.
 127  			argv = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(argv), unsafe.Sizeof(argv)))
 128  		}
 129  	}
 130  	return args
 131  }
 132  
 133  // Must be a separate function to get the correct stack pointer.
 134  //
 135  //go:noinline
 136  func runMain() {
 137  	run()
 138  }
 139  
 140  //export moxie_register_fatal_signals
 141  func moxie_register_fatal_signals()
 142  
 143  // Print fatal errors when they happen, including the instruction location.
 144  // With the particular formatting below, `moxie run` can extract the location
 145  // where the signal happened and try to show the source location based on DWARF
 146  // information.
 147  //
 148  //export moxie_handle_fatal_signal
 149  func moxie_handle_fatal_signal(sig int32, addr uintptr) {
 150  	if panicStrategy() == moxie.PanicStrategyTrap {
 151  		trap()
 152  	}
 153  
 154  	// Print signal including the faulting instruction.
 155  	if addr != 0 {
 156  		printstring("panic: runtime error at ")
 157  		printptr(addr)
 158  	} else {
 159  		printstring("panic: runtime error")
 160  	}
 161  	printstring(": caught signal ")
 162  	switch sig {
 163  	case sig_SIGBUS:
 164  		println("SIGBUS")
 165  	case sig_SIGILL:
 166  		println("SIGILL")
 167  	case sig_SIGSEGV:
 168  		println("SIGSEGV")
 169  	default:
 170  		println(sig)
 171  	}
 172  
 173  	// TODO: it might be interesting to also print the invalid address for
 174  	// SIGSEGV and SIGBUS.
 175  
 176  	// Do *not* abort here, instead raise the same signal again. The signal is
 177  	// registered with SA_RESETHAND which means it executes only once. So when
 178  	// we raise the signal again below, the signal isn't handled specially but
 179  	// is handled in the default way (probably exiting the process, maybe with a
 180  	// core dump).
 181  	raise(sig)
 182  }
 183  
 184  //go:extern environ
 185  var environ *unsafe.Pointer
 186  
 187  //go:linkname syscall_runtime_envs syscall.runtime_envs
 188  func syscall_runtime_envs() []string {
 189  	// Count how many environment variables there are.
 190  	env := environ
 191  	numEnvs := 0
 192  	for *env != nil {
 193  		numEnvs++
 194  		env = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(env), unsafe.Sizeof(environ)))
 195  	}
 196  
 197  	// Create a string slice of all environment variables.
 198  	// This requires just a single heap allocation.
 199  	env = environ
 200  	envs := make([]string, 0, numEnvs)
 201  	for *env != nil {
 202  		ptr := *env
 203  		length := strlen(ptr)
 204  		s := _string{
 205  			ptr:    (*byte)(ptr),
 206  			length: length,
 207  			cap:    length,
 208  		}
 209  		envs = append(envs, *(*string)(unsafe.Pointer(&s)))
 210  		env = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(env), unsafe.Sizeof(environ)))
 211  	}
 212  
 213  	return envs
 214  }
 215  
 216  func putchar(c byte) {
 217  	buf := [1]byte{c}
 218  	libc_write(1, unsafe.Pointer(&buf[0]), 1)
 219  }
 220  
 221  func ticksToNanoseconds(ticks timeUnit) int64 {
 222  	// The OS API works in nanoseconds so no conversion necessary.
 223  	return int64(ticks)
 224  }
 225  
 226  func nanosecondsToTicks(ns int64) timeUnit {
 227  	// The OS API works in nanoseconds so no conversion necessary.
 228  	return timeUnit(ns)
 229  }
 230  
 231  func sleepTicks(d timeUnit) {
 232  	until := ticks() + d
 233  
 234  	for {
 235  		if hasPoll {
 236  			// Use epoll_wait instead of futex so I/O-parked goroutines
 237  			// (e.g. Accept) get woken during timer sleeps.
 238  			ms := int(ticksToNanoseconds(d) / 1000000)
 239  			if ms < 1 {
 240  				ms = 1
 241  			}
 242  			netpollBlock(ms)
 243  		} else {
 244  			// No netpoller — fall back to futex sleep.
 245  			// If a signal arrived before or during sleep, it exits early.
 246  			signalFutex.WaitUntil(0, uint64(ticksToNanoseconds(d)))
 247  		}
 248  
 249  		// Check whether there was a signal before or during the sleep.
 250  		if signalFutex.Swap(0) != 0 {
 251  			if checkSignals() && hasScheduler {
 252  				// We got a signal, so return to the scheduler.
 253  				// (If there is no scheduler, there is no other goroutine that
 254  				// might need to run now).
 255  				return
 256  			}
 257  		}
 258  
 259  		// Set duration (in next loop iteration) to the remaining time.
 260  		d = until - ticks()
 261  		if d <= 0 {
 262  			return
 263  		}
 264  	}
 265  }
 266  
 267  func getTime(clock int32) uint64 {
 268  	ts := timespec{}
 269  	clock_gettime(clock, &ts)
 270  	return uint64(ts.tv_sec)*1000*1000*1000 + uint64(ts.tv_nsec)
 271  }
 272  
 273  // Return monotonic time in nanoseconds.
 274  func monotime() uint64 {
 275  	return getTime(clock_MONOTONIC_RAW)
 276  }
 277  
 278  func ticks() timeUnit {
 279  	return timeUnit(monotime())
 280  }
 281  
 282  //go:linkname now time.now
 283  func now() (sec int64, nsec int32, mono int64) {
 284  	ts := timespec{}
 285  	clock_gettime(clock_REALTIME, &ts)
 286  	sec = int64(ts.tv_sec)
 287  	nsec = int32(ts.tv_nsec)
 288  	mono = nanotime()
 289  	return
 290  }
 291  
 292  //go:linkname syscall_Exit syscall.Exit
 293  func syscall_Exit(code int) {
 294  	exit(code)
 295  }
 296  
 297  // Moxie does not yet support any form of parallelism on an OS, so these can be
 298  // left empty.
 299  
 300  //go:linkname procPin sync/atomic.runtime_procPin
 301  func procPin() {
 302  }
 303  
 304  //go:linkname procUnpin sync/atomic.runtime_procUnpin
 305  func procUnpin() {
 306  }
 307  
 308  var heapSize uintptr = 128 * 1024 // small amount to start
 309  var heapMaxSize uintptr
 310  
 311  var heapStart, heapEnd uintptr
 312  
 313  func allocateHeap() {
 314  	// Allocate a large chunk of virtual memory. Because it is virtual, it won't
 315  	// really be allocated in RAM. Memory will only be allocated when it is
 316  	// first touched.
 317  	heapMaxSize = 1 * 1024 * 1024 * 1024 // 1GB for the entire heap
 318  	for {
 319  		addr := mmap(nil, heapMaxSize, flag_PROT_READ|flag_PROT_WRITE, flag_MAP_PRIVATE|flag_MAP_ANONYMOUS, -1, 0)
 320  		if addr == unsafe.Pointer(^uintptr(0)) {
 321  			// Heap was too big to be mapped by mmap. Reduce the maximum size.
 322  			// We might want to make this a bit smarter than simply halving the
 323  			// heap size.
 324  			// This can happen on 32-bit systems.
 325  			heapMaxSize /= 2
 326  			if heapMaxSize < 4096 {
 327  				runtimePanic("cannot allocate heap memory")
 328  			}
 329  			continue
 330  		}
 331  		heapStart = uintptr(addr)
 332  		heapEnd = heapStart + heapSize
 333  		break
 334  	}
 335  }
 336  
 337  // growHeap tries to grow the heap size. It returns true if it succeeds, false
 338  // otherwise.
 339  func growHeap() bool {
 340  	if heapSize == heapMaxSize {
 341  		// Already at the max. If we run out of memory, we should consider
 342  		// increasing heapMaxSize on 64-bit systems.
 343  		return false
 344  	}
 345  	// Grow the heap size used by the program.
 346  	heapSize = (heapSize * 4 / 3) &^ 4095 // grow by around 33%
 347  	if heapSize > heapMaxSize {
 348  		heapSize = heapMaxSize
 349  	}
 350  	setHeapEnd(heapStart + heapSize)
 351  	return true
 352  }
 353  
 354  // Indicate whether signals have been registered.
 355  var hasSignals bool
 356  
 357  // Futex for the signal handler.
 358  // The value is 0 when there are no new signals, or 1 when there are unhandled
 359  // signals and the main thread doesn't know about it yet.
 360  // When a signal arrives, the futex value is changed to 1 and if it was 0
 361  // before, all waiters are awoken.
 362  // When a wait exits, the value is changed to 0 and if it wasn't 0 before, the
 363  // signals are checked.
 364  var signalFutex futex.Futex
 365  
 366  // Mask of signals that have been received. The signal handler atomically ORs
 367  // signals into this value.
 368  var receivedSignals atomic.Uint32
 369  
 370  //go:linkname signal_enable os/signal.signal_enable
 371  func signal_enable(s uint32) {
 372  	if s >= 32 {
 373  		// TODO: to support higher signal numbers, we need to turn
 374  		// receivedSignals into a uint32 array.
 375  		runtimePanicAt(returnAddress(0), "unsupported signal number")
 376  	}
 377  
 378  	// This is intentonally a non-atomic store. This is safe, since hasSignals
 379  	// is only used in waitForEvents which is only called when there's a
 380  	// scheduler (and therefore there is no parallelism).
 381  	hasSignals = true
 382  
 383  	// It's easier to implement this function in C.
 384  	moxie_signal_enable(s)
 385  }
 386  
 387  //go:linkname signal_ignore os/signal.signal_ignore
 388  func signal_ignore(s uint32) {
 389  	if s >= 32 {
 390  		// TODO: to support higher signal numbers, we need to turn
 391  		// receivedSignals into a uint32 array.
 392  		runtimePanicAt(returnAddress(0), "unsupported signal number")
 393  	}
 394  	moxie_signal_ignore(s)
 395  }
 396  
 397  //go:linkname signal_disable os/signal.signal_disable
 398  func signal_disable(s uint32) {
 399  	if s >= 32 {
 400  		// TODO: to support higher signal numbers, we need to turn
 401  		// receivedSignals into a uint32 array.
 402  		runtimePanicAt(returnAddress(0), "unsupported signal number")
 403  	}
 404  	moxie_signal_disable(s)
 405  }
 406  
 407  //go:linkname signal_waitUntilIdle os/signal.signalWaitUntilIdle
 408  func signal_waitUntilIdle() {
 409  	// Wait until signal_recv has processed all signals.
 410  	for receivedSignals.Load() != 0 {
 411  		// TODO: this becomes a busy loop when using threads.
 412  		// We might want to pause until signal_recv has no more incoming signals
 413  		// to process.
 414  		Gosched()
 415  	}
 416  }
 417  
 418  //export moxie_signal_enable
 419  func moxie_signal_enable(s uint32)
 420  
 421  //export moxie_signal_ignore
 422  func moxie_signal_ignore(s uint32)
 423  
 424  //export moxie_signal_disable
 425  func moxie_signal_disable(s uint32)
 426  
 427  // void moxie_signal_handler(int sig);
 428  //
 429  //export moxie_signal_handler
 430  func moxie_signal_handler(s int32) {
 431  	// The following loop is equivalent to the following:
 432  	//
 433  	//     receivedSignals.Or(uint32(1) << uint32(s))
 434  	//
 435  	// TODO: use this instead of a loop once we drop support for Go 1.22.
 436  	for {
 437  		mask := uint32(1) << uint32(s)
 438  		val := receivedSignals.Load()
 439  		swapped := receivedSignals.CompareAndSwap(val, val|mask)
 440  		if swapped {
 441  			break
 442  		}
 443  	}
 444  
 445  	// Notify the main thread that there was a signal.
 446  	// This will exit the call to Wait or WaitUntil early.
 447  	if signalFutex.Swap(1) == 0 {
 448  		// Changed from 0 to 1, so there may have been a waiting goroutine.
 449  		// This could be optimized to avoid a syscall when there are no waiting
 450  		// goroutines.
 451  		signalFutex.WakeAll()
 452  	}
 453  }
 454  
 455  // Task waiting for a signal to arrive, or nil if it is running or there are no
 456  // signals.
 457  var signalRecvWaiter atomic.Pointer[task.Task]
 458  
 459  //go:linkname signal_recv os/signal.signal_recv
 460  func signal_recv() uint32 {
 461  	// Function called from os/signal to get the next received signal.
 462  	for {
 463  		val := receivedSignals.Load()
 464  		if val == 0 {
 465  			// There are no signals to receive. Sleep until there are.
 466  			if signalRecvWaiter.Swap(task.Current()) != nil {
 467  				// We expect only a single goroutine to call signal_recv.
 468  				runtimePanic("signal_recv called concurrently")
 469  			}
 470  			task.Pause()
 471  			continue
 472  		}
 473  
 474  		// Extract the lowest numbered signal number from receivedSignals.
 475  		num := uint32(bits.TrailingZeros32(val))
 476  
 477  		// Atomically clear the signal number from receivedSignals.
 478  		// TODO: use atomic.Uint32.And once we drop support for Go 1.22 instead
 479  		// of this loop, like so:
 480  		//
 481  		//   receivedSignals.And(^(uint32(1) << num))
 482  		//
 483  		for {
 484  			newVal := val &^ (1 << num)
 485  			swapped := receivedSignals.CompareAndSwap(val, newVal)
 486  			if swapped {
 487  				break
 488  			}
 489  			val = receivedSignals.Load()
 490  		}
 491  
 492  		return num
 493  	}
 494  }
 495  
 496  // Reactivate the goroutine waiting for signals, if there are any.
 497  // Return true if it was reactivated (and therefore the scheduler should run
 498  // again), and false otherwise.
 499  func checkSignals() bool {
 500  	if receivedSignals.Load() != 0 {
 501  		if waiter := signalRecvWaiter.Swap(nil); waiter != nil {
 502  			scheduleTask(waiter)
 503  			return true
 504  		}
 505  	}
 506  	return false
 507  }
 508  
 509  func waitForEvents() {
 510  	if hasPoll {
 511  		// Block on epoll (100ms timeout) to avoid busy-spinning.
 512  		netpollBlock(100)
 513  		// Also check signals if applicable.
 514  		if hasSignals && signalFutex.Swap(0) != 0 {
 515  			checkSignals()
 516  		}
 517  		return
 518  	}
 519  	if hasSignals {
 520  		signalFutex.Wait(0)
 521  		if signalFutex.Swap(0) != 0 {
 522  			checkSignals()
 523  		}
 524  	} else {
 525  		runtimePanic("deadlocked: no event source")
 526  	}
 527  }
 528