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