github.com/gopherjs/gopherjs@v1.19.0-beta1.0.20240506212314-27071a8796e4/compiler/natives/src/runtime/runtime.go (about) 1 //go:build js 2 // +build js 3 4 package runtime 5 6 import ( 7 "github.com/gopherjs/gopherjs/js" 8 ) 9 10 const ( 11 GOOS = "js" 12 GOARCH = "ecmascript" 13 Compiler = "gopherjs" 14 ) 15 16 // The Error interface identifies a run time error. 17 type Error interface { 18 error 19 20 // RuntimeError is a no-op function but 21 // serves to distinguish types that are run time 22 // errors from ordinary errors: a type is a 23 // run time error if it has a RuntimeError method. 24 RuntimeError() 25 } 26 27 // TODO(nevkontakte): In the upstream, this struct is meant to be compatible 28 // with reflect.rtype, but here we use a minimal stub that satisfies the API 29 // TypeAssertionError expects, which we dynamically instantiate in $assertType(). 30 type _type struct{ str string } 31 32 func (t *_type) string() string { return t.str } 33 func (t *_type) pkgpath() string { return "" } 34 35 // A TypeAssertionError explains a failed type assertion. 36 type TypeAssertionError struct { 37 _interface *_type 38 concrete *_type 39 asserted *_type 40 missingMethod string // one method needed by Interface, missing from Concrete 41 } 42 43 func (*TypeAssertionError) RuntimeError() {} 44 45 func (e *TypeAssertionError) Error() string { 46 inter := "interface" 47 if e._interface != nil { 48 inter = e._interface.string() 49 } 50 as := e.asserted.string() 51 if e.concrete == nil { 52 return "interface conversion: " + inter + " is nil, not " + as 53 } 54 cs := e.concrete.string() 55 if e.missingMethod == "" { 56 msg := "interface conversion: " + inter + " is " + cs + ", not " + as 57 if cs == as { 58 // provide slightly clearer error message 59 if e.concrete.pkgpath() != e.asserted.pkgpath() { 60 msg += " (types from different packages)" 61 } else { 62 msg += " (types from different scopes)" 63 } 64 } 65 return msg 66 } 67 return "interface conversion: " + cs + " is not " + as + 68 ": missing method " + e.missingMethod 69 } 70 71 func init() { 72 jsPkg := js.Global.Get("$packages").Get("github.com/gopherjs/gopherjs/js") 73 js.Global.Set("$jsObjectPtr", jsPkg.Get("Object").Get("ptr")) 74 js.Global.Set("$jsErrorPtr", jsPkg.Get("Error").Get("ptr")) 75 js.Global.Set("$throwRuntimeError", js.InternalObject(throw)) 76 buildVersion = js.Global.Get("$goVersion").String() 77 // avoid dead code elimination 78 var e error 79 e = &TypeAssertionError{} 80 _ = e 81 } 82 83 func GOROOT() string { 84 process := js.Global.Get("process") 85 if process == js.Undefined || process.Get("env") == js.Undefined { 86 return "/" 87 } 88 if v := process.Get("env").Get("GOPHERJS_GOROOT"); v != js.Undefined && v.String() != "" { 89 // GopherJS-specific GOROOT value takes precedence. 90 return v.String() 91 } else if v := process.Get("env").Get("GOROOT"); v != js.Undefined && v.String() != "" { 92 return v.String() 93 } 94 // sys.DefaultGoroot is now gone, can't use it as fallback anymore. 95 // TODO: See if a better solution is needed. 96 return "/usr/local/go" 97 } 98 99 func Breakpoint() { js.Debugger() } 100 101 var ( 102 // JavaScript runtime doesn't provide access to low-level execution position 103 // counters, so we emulate them by recording positions we've encountered in 104 // Caller() and Callers() functions and assigning them arbitrary integer values. 105 // 106 // We use the map and the slice below to convert a "file:line" position 107 // into an integer position counter and then to a Func instance. 108 knownPositions = map[string]uintptr{} 109 positionCounters = []*Func{} 110 ) 111 112 func registerPosition(funcName string, file string, line int, col int) uintptr { 113 key := file + ":" + itoa(line) + ":" + itoa(col) 114 if pc, found := knownPositions[key]; found { 115 return pc 116 } 117 f := &Func{ 118 name: funcName, 119 file: file, 120 line: line, 121 } 122 pc := uintptr(len(positionCounters)) 123 positionCounters = append(positionCounters, f) 124 knownPositions[key] = pc 125 return pc 126 } 127 128 // itoa converts an integer to a string. 129 // 130 // Can't use strconv.Itoa() in the `runtime` package due to a cyclic dependency. 131 func itoa(i int) string { 132 return js.Global.Get("String").New(i).String() 133 } 134 135 // basicFrame contains stack trace information extracted from JS stack trace. 136 type basicFrame struct { 137 FuncName string 138 File string 139 Line int 140 Col int 141 } 142 143 func callstack(skip, limit int) []basicFrame { 144 skip = skip + 1 /*skip error message*/ + 1 /*skip callstack's own frame*/ 145 lines := js.Global.Get("Error").New().Get("stack").Call("split", "\n").Call("slice", skip, skip+limit) 146 return parseCallstack(lines) 147 } 148 149 var ( 150 // These functions are GopherJS-specific and don't have counterparts in 151 // upstream Go runtime. To improve interoperability, we filter them out from 152 // the stack trace. 153 hiddenFrames = map[string]bool{ 154 "$callDeferred": true, 155 } 156 // The following GopherJS prelude functions have differently-named 157 // counterparts in the upstream Go runtime. Some standard library code relies 158 // on the names matching, so we perform this substitution. 159 knownFrames = map[string]string{ 160 "$panic": "runtime.gopanic", 161 "$goroutine": "runtime.goexit", 162 } 163 ) 164 165 func parseCallstack(lines *js.Object) []basicFrame { 166 frames := []basicFrame{} 167 l := lines.Length() 168 for i := 0; i < l; i++ { 169 frame := ParseCallFrame(lines.Index(i)) 170 if hiddenFrames[frame.FuncName] { 171 continue 172 } 173 if alias, ok := knownFrames[frame.FuncName]; ok { 174 frame.FuncName = alias 175 } 176 frames = append(frames, frame) 177 if frame.FuncName == "runtime.goexit" { 178 break // We've reached the bottom of the goroutine stack. 179 } 180 } 181 return frames 182 } 183 184 // ParseCallFrame is exported for the sake of testing. See this discussion for context https://github.com/gopherjs/gopherjs/pull/1097/files/561e6381406f04ccb8e04ef4effedc5c7887b70f#r776063799 185 // 186 // TLDR; never use this function! 187 func ParseCallFrame(info *js.Object) basicFrame { 188 // FireFox 189 if info.Call("indexOf", "@").Int() >= 0 { 190 split := js.Global.Get("RegExp").New("[@:]") 191 parts := info.Call("split", split) 192 return basicFrame{ 193 File: parts.Call("slice", 1, parts.Length()-2).Call("join", ":").String(), 194 Line: parts.Index(parts.Length() - 2).Int(), 195 Col: parts.Index(parts.Length() - 1).Int(), 196 FuncName: parts.Index(0).String(), 197 } 198 } 199 200 // Chrome / Node.js 201 openIdx := info.Call("lastIndexOf", "(").Int() 202 if openIdx == -1 { 203 parts := info.Call("split", ":") 204 205 return basicFrame{ 206 File: parts.Call("slice", 0, parts.Length()-2).Call("join", ":"). 207 Call("replace", js.Global.Get("RegExp").New(`^\s*at `), "").String(), 208 Line: parts.Index(parts.Length() - 2).Int(), 209 Col: parts.Index(parts.Length() - 1).Int(), 210 FuncName: "<none>", 211 } 212 } 213 214 var file, funcName string 215 var line, col int 216 217 pos := info.Call("substring", openIdx+1, info.Call("indexOf", ")").Int()) 218 parts := pos.Call("split", ":") 219 220 if pos.String() == "<anonymous>" { 221 file = "<anonymous>" 222 } else { 223 file = parts.Call("slice", 0, parts.Length()-2).Call("join", ":").String() 224 line = parts.Index(parts.Length() - 2).Int() 225 col = parts.Index(parts.Length() - 1).Int() 226 } 227 fn := info.Call("substring", info.Call("indexOf", "at ").Int()+3, info.Call("indexOf", " (").Int()) 228 if idx := fn.Call("indexOf", "[as ").Int(); idx > 0 { 229 fn = fn.Call("substring", idx+4, fn.Call("indexOf", "]")) 230 } 231 funcName = fn.String() 232 233 return basicFrame{ 234 File: file, 235 Line: line, 236 Col: col, 237 FuncName: funcName, 238 } 239 } 240 241 func Caller(skip int) (pc uintptr, file string, line int, ok bool) { 242 skip = skip + 1 /*skip Caller's own frame*/ 243 frames := callstack(skip, 1) 244 if len(frames) != 1 { 245 return 0, "", 0, false 246 } 247 pc = registerPosition(frames[0].FuncName, frames[0].File, frames[0].Line, frames[0].Col) 248 return pc, frames[0].File, frames[0].Line, true 249 } 250 251 // Callers fills the slice pc with the return program counters of function 252 // invocations on the calling goroutine's stack. The argument skip is the number 253 // of stack frames to skip before recording in pc, with 0 identifying the frame 254 // for Callers itself and 1 identifying the caller of Callers. It returns the 255 // number of entries written to pc. 256 // 257 // The returned call stack represents the logical Go call stack, which excludes 258 // certain runtime-internal call frames that would be present in the raw 259 // JavaScript stack trace. This is done to improve interoperability with the 260 // upstream Go. Use JavaScript native APIs to access the raw call stack. 261 // 262 // To translate these PCs into symbolic information such as function names and 263 // line numbers, use CallersFrames. CallersFrames accounts for inlined functions 264 // and adjusts the return program counters into call program counters. Iterating 265 // over the returned slice of PCs directly is discouraged, as is using FuncForPC 266 // on any of the returned PCs, since these cannot account for inlining or return 267 // program counter adjustment. 268 func Callers(skip int, pc []uintptr) int { 269 frames := callstack(skip, len(pc)) 270 for i, frame := range frames { 271 pc[i] = registerPosition(frame.FuncName, frame.File, frame.Line, frame.Col) 272 } 273 return len(frames) 274 } 275 276 func CallersFrames(callers []uintptr) *Frames { 277 result := Frames{} 278 for _, pc := range callers { 279 fun := FuncForPC(pc) 280 result.frames = append(result.frames, Frame{ 281 PC: pc, 282 Func: fun, 283 Function: fun.name, 284 File: fun.file, 285 Line: fun.line, 286 Entry: fun.Entry(), 287 }) 288 } 289 return &result 290 } 291 292 type Frames struct { 293 frames []Frame 294 current int 295 } 296 297 func (ci *Frames) Next() (frame Frame, more bool) { 298 if ci.current >= len(ci.frames) { 299 return Frame{}, false 300 } 301 f := ci.frames[ci.current] 302 ci.current++ 303 return f, ci.current < len(ci.frames) 304 } 305 306 type Frame struct { 307 PC uintptr 308 Func *Func 309 Function string 310 File string 311 Line int 312 Entry uintptr 313 } 314 315 func GC() {} 316 317 func Goexit() { 318 js.Global.Get("$curGoroutine").Set("exit", true) 319 js.Global.Call("$throw", nil) 320 } 321 322 func GOMAXPROCS(int) int { return 1 } 323 324 func Gosched() { 325 c := make(chan struct{}) 326 js.Global.Call("$setTimeout", js.InternalObject(func() { close(c) }), 0) 327 <-c 328 } 329 330 func NumCPU() int { return 1 } 331 332 func NumGoroutine() int { 333 return js.Global.Get("$totalGoroutines").Int() 334 } 335 336 type MemStats struct { 337 // General statistics. 338 Alloc uint64 // bytes allocated and still in use 339 TotalAlloc uint64 // bytes allocated (even if freed) 340 Sys uint64 // bytes obtained from system (sum of XxxSys below) 341 Lookups uint64 // number of pointer lookups 342 Mallocs uint64 // number of mallocs 343 Frees uint64 // number of frees 344 345 // Main allocation heap statistics. 346 HeapAlloc uint64 // bytes allocated and still in use 347 HeapSys uint64 // bytes obtained from system 348 HeapIdle uint64 // bytes in idle spans 349 HeapInuse uint64 // bytes in non-idle span 350 HeapReleased uint64 // bytes released to the OS 351 HeapObjects uint64 // total number of allocated objects 352 353 // Low-level fixed-size structure allocator statistics. 354 // Inuse is bytes used now. 355 // Sys is bytes obtained from system. 356 StackInuse uint64 // bytes used by stack allocator 357 StackSys uint64 358 MSpanInuse uint64 // mspan structures 359 MSpanSys uint64 360 MCacheInuse uint64 // mcache structures 361 MCacheSys uint64 362 BuckHashSys uint64 // profiling bucket hash table 363 GCSys uint64 // GC metadata 364 OtherSys uint64 // other system allocations 365 366 // Garbage collector statistics. 367 NextGC uint64 // next collection will happen when HeapAlloc ≥ this amount 368 LastGC uint64 // end time of last collection (nanoseconds since 1970) 369 PauseTotalNs uint64 370 PauseNs [256]uint64 // circular buffer of recent GC pause durations, most recent at [(NumGC+255)%256] 371 PauseEnd [256]uint64 // circular buffer of recent GC pause end times 372 NumGC uint32 373 GCCPUFraction float64 // fraction of CPU time used by GC 374 EnableGC bool 375 DebugGC bool 376 377 // Per-size allocation statistics. 378 // 61 is NumSizeClasses in the C code. 379 BySize [61]struct { 380 Size uint32 381 Mallocs uint64 382 Frees uint64 383 } 384 } 385 386 func ReadMemStats(m *MemStats) { 387 // TODO(nevkontakte): This function is effectively unimplemented and may 388 // lead to silent unexpected behaviors. Consider panicing explicitly. 389 } 390 391 func SetFinalizer(x, f interface{}) { 392 // TODO(nevkontakte): This function is effectively unimplemented and may 393 // lead to silent unexpected behaviors. Consider panicing explicitly. 394 } 395 396 type Func struct { 397 name string 398 file string 399 line int 400 401 opaque struct{} // unexported field to disallow conversions 402 } 403 404 func (_ *Func) Entry() uintptr { return 0 } 405 406 func (f *Func) FileLine(pc uintptr) (file string, line int) { 407 if f == nil { 408 return "", 0 409 } 410 return f.file, f.line 411 } 412 413 func (f *Func) Name() string { 414 if f == nil || f.name == "" { 415 return "<unknown>" 416 } 417 return f.name 418 } 419 420 func FuncForPC(pc uintptr) *Func { 421 ipc := int(pc) 422 if ipc >= len(positionCounters) { 423 // Since we are faking position counters, the only valid way to obtain one 424 // is through a Caller() or Callers() function. If pc is out of positionCounters 425 // bounds it must have been obtained in some other way, which is unexpected. 426 // If a panic proves problematic, we can return a nil *Func, which will 427 // present itself as a generic "unknown" function. 428 panic("GopherJS: pc=" + itoa(ipc) + " is out of range of known position counters") 429 } 430 return positionCounters[ipc] 431 } 432 433 var MemProfileRate int = 512 * 1024 434 435 func SetBlockProfileRate(rate int) { 436 } 437 438 func SetMutexProfileFraction(rate int) int { 439 // TODO: Investigate this. If it's possible to implement, consider doing so, otherwise remove this comment. 440 return 0 441 } 442 443 // Stack formats a stack trace of the calling goroutine into buf and returns the 444 // number of bytes written to buf. If all is true, Stack formats stack traces of 445 // all other goroutines into buf after the trace for the current goroutine. 446 // 447 // Unlike runtime.Callers(), it returns an unprocessed, runtime-specific text 448 // representation of the JavaScript stack trace. 449 func Stack(buf []byte, all bool) int { 450 s := js.Global.Get("Error").New().Get("stack") 451 if s == js.Undefined { 452 return 0 453 } 454 return copy(buf, s.Call("substr", s.Call("indexOf", "\n").Int()+1).String()) 455 } 456 457 func LockOSThread() {} 458 459 func UnlockOSThread() {} 460 461 var buildVersion string // Set by init() 462 463 func Version() string { 464 return buildVersion 465 } 466 467 func StartTrace() error { return nil } 468 func StopTrace() {} 469 func ReadTrace() []byte 470 471 // We fake a cgo environment to catch errors. Therefore we have to implement this and always return 0 472 func NumCgoCall() int64 { 473 return 0 474 } 475 476 func KeepAlive(interface{}) {} 477 478 // An errorString represents a runtime error described by a single string. 479 type errorString string 480 481 func (e errorString) RuntimeError() {} 482 483 func (e errorString) Error() string { 484 return "runtime error: " + string(e) 485 } 486 487 func throw(s string) { 488 panic(errorString(s)) 489 } 490 491 func nanotime() int64 { 492 const millisecond = 1_000_000 493 return js.Global.Get("Date").New().Call("getTime").Int64() * millisecond 494 }