github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/src/runtime/runtime_unix.go (about)

     1  //go:build (darwin || (linux && !baremetal && !wasip1 && !wasm_unknown)) && !nintendoswitch
     2  
     3  package runtime
     4  
     5  import (
     6  	"unsafe"
     7  )
     8  
     9  //export write
    10  func libc_write(fd int32, buf unsafe.Pointer, count uint) int
    11  
    12  //export usleep
    13  func usleep(usec uint) int
    14  
    15  // void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
    16  // Note: off_t is defined as int64 because:
    17  //   - musl (used on Linux) always defines it as int64
    18  //   - darwin is practically always 64-bit anyway
    19  //
    20  //export mmap
    21  func mmap(addr unsafe.Pointer, length uintptr, prot, flags, fd int, offset int64) unsafe.Pointer
    22  
    23  //export abort
    24  func abort()
    25  
    26  //export exit
    27  func exit(code int)
    28  
    29  //export clock_gettime
    30  func libc_clock_gettime(clk_id int32, ts *timespec)
    31  
    32  //export __clock_gettime64
    33  func libc_clock_gettime64(clk_id int32, ts *timespec)
    34  
    35  // Portable (64-bit) variant of clock_gettime.
    36  func clock_gettime(clk_id int32, ts *timespec) {
    37  	if TargetBits == 32 {
    38  		// This is a 32-bit architecture (386, arm, etc).
    39  		// We would like to use the 64-bit version of this function so that
    40  		// binaries will continue to run after Y2038.
    41  		// For more information:
    42  		//   - https://musl.libc.org/time64.html
    43  		//   - https://sourceware.org/glibc/wiki/Y2038ProofnessDesign
    44  		libc_clock_gettime64(clk_id, ts)
    45  	} else {
    46  		// This is a 64-bit architecture (amd64, arm64, etc).
    47  		// Use the regular variant, because it already fixes the Y2038 problem
    48  		// by using 64-bit integer types.
    49  		libc_clock_gettime(clk_id, ts)
    50  	}
    51  }
    52  
    53  type timeUnit int64
    54  
    55  // Note: tv_sec and tv_nsec normally vary in size by platform. However, we're
    56  // using the time64 variant (see clock_gettime above), so the formats are the
    57  // same between 32-bit and 64-bit architectures.
    58  // There is one issue though: on big-endian systems, tv_nsec would be incorrect.
    59  // But we don't support big-endian systems yet (as of 2021) so this is fine.
    60  type timespec struct {
    61  	tv_sec  int64 // time_t with time64 support (always 64-bit)
    62  	tv_nsec int64 // unsigned 64-bit integer on all time64 platforms
    63  }
    64  
    65  var stackTop uintptr
    66  
    67  // Entry point for Go. Initialize all packages and call main.main().
    68  //
    69  //export main
    70  func main(argc int32, argv *unsafe.Pointer) int {
    71  	preinit()
    72  
    73  	// Store argc and argv for later use.
    74  	main_argc = argc
    75  	main_argv = argv
    76  
    77  	// Obtain the initial stack pointer right before calling the run() function.
    78  	// The run function has been moved to a separate (non-inlined) function so
    79  	// that the correct stack pointer is read.
    80  	stackTop = getCurrentStackPointer()
    81  	runMain()
    82  
    83  	// For libc compatibility.
    84  	return 0
    85  }
    86  
    87  var (
    88  	main_argc int32
    89  	main_argv *unsafe.Pointer
    90  	args      []string
    91  )
    92  
    93  //go:linkname os_runtime_args os.runtime_args
    94  func os_runtime_args() []string {
    95  	if args == nil {
    96  		// Make args slice big enough so that it can store all command line
    97  		// arguments.
    98  		args = make([]string, main_argc)
    99  
   100  		// Initialize command line parameters.
   101  		argv := main_argv
   102  		for i := 0; i < int(main_argc); i++ {
   103  			// Convert the C string to a Go string.
   104  			length := strlen(*argv)
   105  			arg := (*_string)(unsafe.Pointer(&args[i]))
   106  			arg.length = length
   107  			arg.ptr = (*byte)(*argv)
   108  			// This is the Go equivalent of "argv++" in C.
   109  			argv = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(argv), unsafe.Sizeof(argv)))
   110  		}
   111  	}
   112  	return args
   113  }
   114  
   115  // Must be a separate function to get the correct stack pointer.
   116  //
   117  //go:noinline
   118  func runMain() {
   119  	run()
   120  }
   121  
   122  //go:extern environ
   123  var environ *unsafe.Pointer
   124  
   125  //go:linkname syscall_runtime_envs syscall.runtime_envs
   126  func syscall_runtime_envs() []string {
   127  	// Count how many environment variables there are.
   128  	env := environ
   129  	numEnvs := 0
   130  	for *env != nil {
   131  		numEnvs++
   132  		env = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(env), unsafe.Sizeof(environ)))
   133  	}
   134  
   135  	// Create a string slice of all environment variables.
   136  	// This requires just a single heap allocation.
   137  	env = environ
   138  	envs := make([]string, 0, numEnvs)
   139  	for *env != nil {
   140  		ptr := *env
   141  		length := strlen(ptr)
   142  		s := _string{
   143  			ptr:    (*byte)(ptr),
   144  			length: length,
   145  		}
   146  		envs = append(envs, *(*string)(unsafe.Pointer(&s)))
   147  		env = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(env), unsafe.Sizeof(environ)))
   148  	}
   149  
   150  	return envs
   151  }
   152  
   153  func putchar(c byte) {
   154  	buf := [1]byte{c}
   155  	libc_write(1, unsafe.Pointer(&buf[0]), 1)
   156  }
   157  
   158  func ticksToNanoseconds(ticks timeUnit) int64 {
   159  	// The OS API works in nanoseconds so no conversion necessary.
   160  	return int64(ticks)
   161  }
   162  
   163  func nanosecondsToTicks(ns int64) timeUnit {
   164  	// The OS API works in nanoseconds so no conversion necessary.
   165  	return timeUnit(ns)
   166  }
   167  
   168  func sleepTicks(d timeUnit) {
   169  	// timeUnit is in nanoseconds, so need to convert to microseconds here.
   170  	usleep(uint(d) / 1000)
   171  }
   172  
   173  func getTime(clock int32) uint64 {
   174  	ts := timespec{}
   175  	clock_gettime(clock, &ts)
   176  	return uint64(ts.tv_sec)*1000*1000*1000 + uint64(ts.tv_nsec)
   177  }
   178  
   179  // Return monotonic time in nanoseconds.
   180  func monotime() uint64 {
   181  	return getTime(clock_MONOTONIC_RAW)
   182  }
   183  
   184  func ticks() timeUnit {
   185  	return timeUnit(monotime())
   186  }
   187  
   188  //go:linkname now time.now
   189  func now() (sec int64, nsec int32, mono int64) {
   190  	ts := timespec{}
   191  	clock_gettime(clock_REALTIME, &ts)
   192  	sec = int64(ts.tv_sec)
   193  	nsec = int32(ts.tv_nsec)
   194  	mono = nanotime()
   195  	return
   196  }
   197  
   198  //go:linkname syscall_Exit syscall.Exit
   199  func syscall_Exit(code int) {
   200  	exit(code)
   201  }
   202  
   203  // TinyGo does not yet support any form of parallelism on an OS, so these can be
   204  // left empty.
   205  
   206  //go:linkname procPin sync/atomic.runtime_procPin
   207  func procPin() {
   208  }
   209  
   210  //go:linkname procUnpin sync/atomic.runtime_procUnpin
   211  func procUnpin() {
   212  }
   213  
   214  var heapSize uintptr = 128 * 1024 // small amount to start
   215  var heapMaxSize uintptr
   216  
   217  var heapStart, heapEnd uintptr
   218  
   219  func preinit() {
   220  	// Allocate a large chunk of virtual memory. Because it is virtual, it won't
   221  	// really be allocated in RAM. Memory will only be allocated when it is
   222  	// first touched.
   223  	heapMaxSize = 1 * 1024 * 1024 * 1024 // 1GB for the entire heap
   224  	for {
   225  		addr := mmap(nil, heapMaxSize, flag_PROT_READ|flag_PROT_WRITE, flag_MAP_PRIVATE|flag_MAP_ANONYMOUS, -1, 0)
   226  		if addr == unsafe.Pointer(^uintptr(0)) {
   227  			// Heap was too big to be mapped by mmap. Reduce the maximum size.
   228  			// We might want to make this a bit smarter than simply halving the
   229  			// heap size.
   230  			// This can happen on 32-bit systems.
   231  			heapMaxSize /= 2
   232  			continue
   233  		}
   234  		heapStart = uintptr(addr)
   235  		heapEnd = heapStart + heapSize
   236  		break
   237  	}
   238  }
   239  
   240  // growHeap tries to grow the heap size. It returns true if it succeeds, false
   241  // otherwise.
   242  func growHeap() bool {
   243  	if heapSize == heapMaxSize {
   244  		// Already at the max. If we run out of memory, we should consider
   245  		// increasing heapMaxSize on 64-bit systems.
   246  		return false
   247  	}
   248  	// Grow the heap size used by the program.
   249  	heapSize = (heapSize * 4 / 3) &^ 4095 // grow by around 33%
   250  	if heapSize > heapMaxSize {
   251  		heapSize = heapMaxSize
   252  	}
   253  	setHeapEnd(heapStart + heapSize)
   254  	return true
   255  }