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 }