// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build js && wasm package runtime import ( _ "unsafe" ) // js/wasm has no support for threads yet. There is no preemption. const ( mutex_unlocked = 0 mutex_locked = 1 note_cleared = 0 note_woken = 1 note_timeout = 2 active_spin = 4 active_spin_cnt = 30 passive_spin = 1 ) func lock(l *mutex) { lockWithRank(l, getLockRank(l)) } func lock2(l *mutex) { if l.key == mutex_locked { // js/wasm is single-threaded so we should never // observe this. throw("self deadlock") } gp := getg() if gp.m.locks < 0 { throw("lock count") } gp.m.locks++ l.key = mutex_locked } func unlock(l *mutex) { unlockWithRank(l) } func unlock2(l *mutex) { if l.key == mutex_unlocked { throw("unlock of unlocked lock") } gp := getg() gp.m.locks-- if gp.m.locks < 0 { throw("lock count") } l.key = mutex_unlocked } // One-time notifications. type noteWithTimeout struct { gp *g deadline int64 } var ( notes = make(map[*note]*g) notesWithTimeout = make(map[*note]noteWithTimeout) ) func noteclear(n *note) { n.key = note_cleared } func notewakeup(n *note) { // gp := getg() if n.key == note_woken { throw("notewakeup - double wakeup") } cleared := n.key == note_cleared n.key = note_woken if cleared { goready(notes[n], 1) } } func notesleep(n *note) { throw("notesleep not supported by js") } func notetsleep(n *note, ns int64) bool { throw("notetsleep not supported by js") return false } // same as runtimeĀ·notetsleep, but called on user g (not g0) func notetsleepg(n *note, ns int64) bool { gp := getg() if gp == gp.m.g0 { throw("notetsleepg on g0") } if ns >= 0 { deadline := nanotime() + ns delay := ns/1000000 + 1 // round up if delay > 1<<31-1 { delay = 1<<31 - 1 // cap to max int32 } id := scheduleTimeoutEvent(delay) mp := acquirem() notes[n] = gp notesWithTimeout[n] = noteWithTimeout{gp: gp, deadline: deadline} releasem(mp) gopark(nil, nil, waitReasonSleep, traceEvNone, 1) clearTimeoutEvent(id) // note might have woken early, clear timeout clearIdleID() mp = acquirem() delete(notes, n) delete(notesWithTimeout, n) releasem(mp) return n.key == note_woken } for n.key != note_woken { mp := acquirem() notes[n] = gp releasem(mp) gopark(nil, nil, waitReasonZero, traceEvNone, 1) mp = acquirem() delete(notes, n) releasem(mp) } return true } // checkTimeouts resumes goroutines that are waiting on a note which has reached its deadline. // TODO(drchase): need to understand if write barriers are really okay in this context. // //go:yeswritebarrierrec func checkTimeouts() { now := nanotime() // TODO: map iteration has the write barriers in it; is that okay? for n, nt := range notesWithTimeout { if n.key == note_cleared && now >= nt.deadline { n.key = note_timeout goready(nt.gp, 1) } } } // events is a stack of calls from JavaScript into Go. var events []*event type event struct { // g was the active goroutine when the call from JavaScript occurred. // It needs to be active when returning to JavaScript. gp *g // returned reports whether the event handler has returned. // When all goroutines are idle and the event handler has returned, // then g gets resumed and returns the execution to JavaScript. returned bool } // The timeout event started by beforeIdle. var idleID int32 // beforeIdle gets called by the scheduler if no goroutine is awake. // If we are not already handling an event, then we pause for an async event. // If an event handler returned, we resume it and it will pause the execution. // beforeIdle either returns the specific goroutine to schedule next or // indicates with otherReady that some goroutine became ready. // TODO(drchase): need to understand if write barriers are really okay in this context. // //go:yeswritebarrierrec func beforeIdle(now, pollUntil int64) (gp *g, otherReady bool) { delay := int64(-1) if pollUntil != 0 { delay = pollUntil - now } if delay > 0 { clearIdleID() if delay < 1e6 { delay = 1 } else if delay < 1e15 { delay = delay / 1e6 } else { // An arbitrary cap on how long to wait for a timer. // 1e9 ms == ~11.5 days. delay = 1e9 } idleID = scheduleTimeoutEvent(delay) } if len(events) == 0 { // TODO: this is the line that requires the yeswritebarrierrec go handleAsyncEvent() return nil, true } e := events[len(events)-1] if e.returned { return e.gp, false } return nil, false } func handleAsyncEvent() { pause(getcallersp() - 16) } // clearIdleID clears our record of the timeout started by beforeIdle. func clearIdleID() { if idleID != 0 { clearTimeoutEvent(idleID) idleID = 0 } } // pause sets SP to newsp and pauses the execution of Go's WebAssembly code until an event is triggered. func pause(newsp uintptr) // scheduleTimeoutEvent tells the WebAssembly environment to trigger an event after ms milliseconds. // It returns a timer id that can be used with clearTimeoutEvent. func scheduleTimeoutEvent(ms int64) int32 // clearTimeoutEvent clears a timeout event scheduled by scheduleTimeoutEvent. func clearTimeoutEvent(id int32) // handleEvent gets invoked on a call from JavaScript into Go. It calls the event handler of the syscall/js package // and then parks the handler goroutine to allow other goroutines to run before giving execution back to JavaScript. // When no other goroutine is awake any more, beforeIdle resumes the handler goroutine. Now that the same goroutine // is running as was running when the call came in from JavaScript, execution can be safely passed back to JavaScript. func handleEvent() { e := &event{ gp: getg(), returned: false, } events = append(events, e) eventHandler() clearIdleID() // wait until all goroutines are idle e.returned = true gopark(nil, nil, waitReasonZero, traceEvNone, 1) events[len(events)-1] = nil events = events[:len(events)-1] // return execution to JavaScript pause(getcallersp() - 16) } var eventHandler func() //go:linkname setEventHandler syscall/js.setEventHandler func setEventHandler(fn func()) { eventHandler = fn }