1// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5//go:build js && wasm
6
7package runtime
8
9import _ "unsafe" // for go:linkname
10
11// js/wasm has no support for threads yet. There is no preemption.
12
13const (
14	mutex_unlocked = 0
15	mutex_locked   = 1
16
17	note_cleared = 0
18	note_woken   = 1
19	note_timeout = 2
20
21	active_spin     = 4
22	active_spin_cnt = 30
23	passive_spin    = 1
24)
25
26func mutexContended(l *mutex) bool {
27	return false
28}
29
30func lock(l *mutex) {
31	lockWithRank(l, getLockRank(l))
32}
33
34func lock2(l *mutex) {
35	if l.key == mutex_locked {
36		// js/wasm is single-threaded so we should never
37		// observe this.
38		throw("self deadlock")
39	}
40	gp := getg()
41	if gp.m.locks < 0 {
42		throw("lock count")
43	}
44	gp.m.locks++
45	l.key = mutex_locked
46}
47
48func unlock(l *mutex) {
49	unlockWithRank(l)
50}
51
52func unlock2(l *mutex) {
53	if l.key == mutex_unlocked {
54		throw("unlock of unlocked lock")
55	}
56	gp := getg()
57	gp.m.locks--
58	if gp.m.locks < 0 {
59		throw("lock count")
60	}
61	l.key = mutex_unlocked
62}
63
64// One-time notifications.
65
66type noteWithTimeout struct {
67	gp       *g
68	deadline int64
69}
70
71var (
72	notes            = make(map[*note]*g)
73	notesWithTimeout = make(map[*note]noteWithTimeout)
74)
75
76func noteclear(n *note) {
77	n.key = note_cleared
78}
79
80func notewakeup(n *note) {
81	// gp := getg()
82	if n.key == note_woken {
83		throw("notewakeup - double wakeup")
84	}
85	cleared := n.key == note_cleared
86	n.key = note_woken
87	if cleared {
88		goready(notes[n], 1)
89	}
90}
91
92func notesleep(n *note) {
93	throw("notesleep not supported by js")
94}
95
96func notetsleep(n *note, ns int64) bool {
97	throw("notetsleep not supported by js")
98	return false
99}
100
101// same as runtime·notetsleep, but called on user g (not g0)
102func notetsleepg(n *note, ns int64) bool {
103	gp := getg()
104	if gp == gp.m.g0 {
105		throw("notetsleepg on g0")
106	}
107
108	if ns >= 0 {
109		deadline := nanotime() + ns
110		delay := ns/1000000 + 1 // round up
111		if delay > 1<<31-1 {
112			delay = 1<<31 - 1 // cap to max int32
113		}
114
115		id := scheduleTimeoutEvent(delay)
116		mp := acquirem()
117		notes[n] = gp
118		notesWithTimeout[n] = noteWithTimeout{gp: gp, deadline: deadline}
119		releasem(mp)
120
121		gopark(nil, nil, waitReasonSleep, traceBlockSleep, 1)
122
123		clearTimeoutEvent(id) // note might have woken early, clear timeout
124
125		mp = acquirem()
126		delete(notes, n)
127		delete(notesWithTimeout, n)
128		releasem(mp)
129
130		return n.key == note_woken
131	}
132
133	for n.key != note_woken {
134		mp := acquirem()
135		notes[n] = gp
136		releasem(mp)
137
138		gopark(nil, nil, waitReasonZero, traceBlockGeneric, 1)
139
140		mp = acquirem()
141		delete(notes, n)
142		releasem(mp)
143	}
144	return true
145}
146
147// checkTimeouts resumes goroutines that are waiting on a note which has reached its deadline.
148// TODO(drchase): need to understand if write barriers are really okay in this context.
149//
150//go:yeswritebarrierrec
151func checkTimeouts() {
152	now := nanotime()
153	// TODO: map iteration has the write barriers in it; is that okay?
154	for n, nt := range notesWithTimeout {
155		if n.key == note_cleared && now >= nt.deadline {
156			n.key = note_timeout
157			goready(nt.gp, 1)
158		}
159	}
160}
161
162// events is a stack of calls from JavaScript into Go.
163var events []*event
164
165type event struct {
166	// g was the active goroutine when the call from JavaScript occurred.
167	// It needs to be active when returning to JavaScript.
168	gp *g
169	// returned reports whether the event handler has returned.
170	// When all goroutines are idle and the event handler has returned,
171	// then g gets resumed and returns the execution to JavaScript.
172	returned bool
173}
174
175type timeoutEvent struct {
176	id int32
177	// The time when this timeout will be triggered.
178	time int64
179}
180
181// diff calculates the difference of the event's trigger time and x.
182func (e *timeoutEvent) diff(x int64) int64 {
183	if e == nil {
184		return 0
185	}
186
187	diff := x - idleTimeout.time
188	if diff < 0 {
189		diff = -diff
190	}
191	return diff
192}
193
194// clear cancels this timeout event.
195func (e *timeoutEvent) clear() {
196	if e == nil {
197		return
198	}
199
200	clearTimeoutEvent(e.id)
201}
202
203// The timeout event started by beforeIdle.
204var idleTimeout *timeoutEvent
205
206// beforeIdle gets called by the scheduler if no goroutine is awake.
207// If we are not already handling an event, then we pause for an async event.
208// If an event handler returned, we resume it and it will pause the execution.
209// beforeIdle either returns the specific goroutine to schedule next or
210// indicates with otherReady that some goroutine became ready.
211// TODO(drchase): need to understand if write barriers are really okay in this context.
212//
213//go:yeswritebarrierrec
214func beforeIdle(now, pollUntil int64) (gp *g, otherReady bool) {
215	delay := int64(-1)
216	if pollUntil != 0 {
217		// round up to prevent setTimeout being called early
218		delay = (pollUntil-now-1)/1e6 + 1
219		if delay > 1e9 {
220			// An arbitrary cap on how long to wait for a timer.
221			// 1e9 ms == ~11.5 days.
222			delay = 1e9
223		}
224	}
225
226	if delay > 0 && (idleTimeout == nil || idleTimeout.diff(pollUntil) > 1e6) {
227		// If the difference is larger than 1 ms, we should reschedule the timeout.
228		idleTimeout.clear()
229
230		idleTimeout = &timeoutEvent{
231			id:   scheduleTimeoutEvent(delay),
232			time: pollUntil,
233		}
234	}
235
236	if len(events) == 0 {
237		// TODO: this is the line that requires the yeswritebarrierrec
238		go handleAsyncEvent()
239		return nil, true
240	}
241
242	e := events[len(events)-1]
243	if e.returned {
244		return e.gp, false
245	}
246	return nil, false
247}
248
249var idleStart int64
250
251func handleAsyncEvent() {
252	idleStart = nanotime()
253	pause(getcallersp() - 16)
254}
255
256// clearIdleTimeout clears our record of the timeout started by beforeIdle.
257func clearIdleTimeout() {
258	idleTimeout.clear()
259	idleTimeout = nil
260}
261
262// pause sets SP to newsp and pauses the execution of Go's WebAssembly code until an event is triggered.
263func pause(newsp uintptr)
264
265// scheduleTimeoutEvent tells the WebAssembly environment to trigger an event after ms milliseconds.
266// It returns a timer id that can be used with clearTimeoutEvent.
267//
268//go:wasmimport gojs runtime.scheduleTimeoutEvent
269func scheduleTimeoutEvent(ms int64) int32
270
271// clearTimeoutEvent clears a timeout event scheduled by scheduleTimeoutEvent.
272//
273//go:wasmimport gojs runtime.clearTimeoutEvent
274func clearTimeoutEvent(id int32)
275
276// handleEvent gets invoked on a call from JavaScript into Go. It calls the event handler of the syscall/js package
277// and then parks the handler goroutine to allow other goroutines to run before giving execution back to JavaScript.
278// When no other goroutine is awake any more, beforeIdle resumes the handler goroutine. Now that the same goroutine
279// is running as was running when the call came in from JavaScript, execution can be safely passed back to JavaScript.
280func handleEvent() {
281	sched.idleTime.Add(nanotime() - idleStart)
282
283	e := &event{
284		gp:       getg(),
285		returned: false,
286	}
287	events = append(events, e)
288
289	if !eventHandler() {
290		// If we did not handle a window event, the idle timeout was triggered, so we can clear it.
291		clearIdleTimeout()
292	}
293
294	// wait until all goroutines are idle
295	e.returned = true
296	gopark(nil, nil, waitReasonZero, traceBlockGeneric, 1)
297
298	events[len(events)-1] = nil
299	events = events[:len(events)-1]
300
301	// return execution to JavaScript
302	idleStart = nanotime()
303	pause(getcallersp() - 16)
304}
305
306// eventHandler retrieves and executes handlers for pending JavaScript events.
307// It returns true if an event was handled.
308var eventHandler func() bool
309
310//go:linkname setEventHandler syscall/js.setEventHandler
311func setEventHandler(fn func() bool) {
312	eventHandler = fn
313}
314