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// +build js,wasm
6
7package runtime
8
9import (
10	_ "unsafe"
11)
12
13// js/wasm has no support for threads yet. There is no preemption.
14
15const (
16	mutex_unlocked = 0
17	mutex_locked   = 1
18
19	note_cleared = 0
20	note_woken   = 1
21	note_timeout = 2
22
23	active_spin     = 4
24	active_spin_cnt = 30
25	passive_spin    = 1
26)
27
28func lock(l *mutex) {
29	if l.key == mutex_locked {
30		// js/wasm is single-threaded so we should never
31		// observe this.
32		throw("self deadlock")
33	}
34	gp := getg()
35	if gp.m.locks < 0 {
36		throw("lock count")
37	}
38	gp.m.locks++
39	l.key = mutex_locked
40}
41
42func unlock(l *mutex) {
43	if l.key == mutex_unlocked {
44		throw("unlock of unlocked lock")
45	}
46	gp := getg()
47	gp.m.locks--
48	if gp.m.locks < 0 {
49		throw("lock count")
50	}
51	l.key = mutex_unlocked
52}
53
54// One-time notifications.
55
56type noteWithTimeout struct {
57	gp       *g
58	deadline int64
59}
60
61var (
62	notes            = make(map[*note]*g)
63	notesWithTimeout = make(map[*note]noteWithTimeout)
64)
65
66func noteclear(n *note) {
67	n.key = note_cleared
68}
69
70func notewakeup(n *note) {
71	// gp := getg()
72	if n.key == note_woken {
73		throw("notewakeup - double wakeup")
74	}
75	cleared := n.key == note_cleared
76	n.key = note_woken
77	if cleared {
78		goready(notes[n], 1)
79	}
80}
81
82func notesleep(n *note) {
83	throw("notesleep not supported by js")
84}
85
86func notetsleep(n *note, ns int64) bool {
87	throw("notetsleep not supported by js")
88	return false
89}
90
91// same as runtime·notetsleep, but called on user g (not g0)
92func notetsleepg(n *note, ns int64) bool {
93	gp := getg()
94	if gp == gp.m.g0 {
95		throw("notetsleepg on g0")
96	}
97
98	if ns >= 0 {
99		deadline := nanotime() + ns
100		delay := ns/1000000 + 1 // round up
101		if delay > 1<<31-1 {
102			delay = 1<<31 - 1 // cap to max int32
103		}
104
105		id := scheduleTimeoutEvent(delay)
106		mp := acquirem()
107		notes[n] = gp
108		notesWithTimeout[n] = noteWithTimeout{gp: gp, deadline: deadline}
109		releasem(mp)
110
111		gopark(nil, nil, waitReasonSleep, traceEvNone, 1)
112
113		clearTimeoutEvent(id) // note might have woken early, clear timeout
114		clearIdleID()
115
116		mp = acquirem()
117		delete(notes, n)
118		delete(notesWithTimeout, n)
119		releasem(mp)
120
121		return n.key == note_woken
122	}
123
124	for n.key != note_woken {
125		mp := acquirem()
126		notes[n] = gp
127		releasem(mp)
128
129		gopark(nil, nil, waitReasonZero, traceEvNone, 1)
130
131		mp = acquirem()
132		delete(notes, n)
133		releasem(mp)
134	}
135	return true
136}
137
138// checkTimeouts resumes goroutines that are waiting on a note which has reached its deadline.
139func checkTimeouts() {
140	now := nanotime()
141	for n, nt := range notesWithTimeout {
142		if n.key == note_cleared && now >= nt.deadline {
143			n.key = note_timeout
144			goready(nt.gp, 1)
145		}
146	}
147}
148
149// events is a stack of calls from JavaScript into Go.
150var events []*event
151
152type event struct {
153	// g was the active goroutine when the call from JavaScript occurred.
154	// It needs to be active when returning to JavaScript.
155	gp *g
156	// returned reports whether the event handler has returned.
157	// When all goroutines are idle and the event handler has returned,
158	// then g gets resumed and returns the execution to JavaScript.
159	returned bool
160}
161
162// The timeout event started by beforeIdle.
163var idleID int32
164
165// beforeIdle gets called by the scheduler if no goroutine is awake.
166// If we are not already handling an event, then we pause for an async event.
167// If an event handler returned, we resume it and it will pause the execution.
168func beforeIdle(delay int64) bool {
169	if delay > 0 {
170		clearIdleID()
171		if delay < 1e6 {
172			delay = 1
173		} else if delay < 1e15 {
174			delay = delay / 1e6
175		} else {
176			// An arbitrary cap on how long to wait for a timer.
177			// 1e9 ms == ~11.5 days.
178			delay = 1e9
179		}
180		idleID = scheduleTimeoutEvent(delay)
181	}
182
183	if len(events) == 0 {
184		go handleAsyncEvent()
185		return true
186	}
187
188	e := events[len(events)-1]
189	if e.returned {
190		goready(e.gp, 1)
191		return true
192	}
193	return false
194}
195
196func handleAsyncEvent() {
197	pause(getcallersp() - 16)
198}
199
200// clearIdleID clears our record of the timeout started by beforeIdle.
201func clearIdleID() {
202	if idleID != 0 {
203		clearTimeoutEvent(idleID)
204		idleID = 0
205	}
206}
207
208// pause sets SP to newsp and pauses the execution of Go's WebAssembly code until an event is triggered.
209func pause(newsp uintptr)
210
211// scheduleTimeoutEvent tells the WebAssembly environment to trigger an event after ms milliseconds.
212// It returns a timer id that can be used with clearTimeoutEvent.
213func scheduleTimeoutEvent(ms int64) int32
214
215// clearTimeoutEvent clears a timeout event scheduled by scheduleTimeoutEvent.
216func clearTimeoutEvent(id int32)
217
218// handleEvent gets invoked on a call from JavaScript into Go. It calls the event handler of the syscall/js package
219// and then parks the handler goroutine to allow other goroutines to run before giving execution back to JavaScript.
220// When no other goroutine is awake any more, beforeIdle resumes the handler goroutine. Now that the same goroutine
221// is running as was running when the call came in from JavaScript, execution can be safely passed back to JavaScript.
222func handleEvent() {
223	e := &event{
224		gp:       getg(),
225		returned: false,
226	}
227	events = append(events, e)
228
229	eventHandler()
230
231	clearIdleID()
232
233	// wait until all goroutines are idle
234	e.returned = true
235	gopark(nil, nil, waitReasonZero, traceEvNone, 1)
236
237	events[len(events)-1] = nil
238	events = events[:len(events)-1]
239
240	// return execution to JavaScript
241	pause(getcallersp() - 16)
242}
243
244var eventHandler func()
245
246//go:linkname setEventHandler syscall/js.setEventHandler
247func setEventHandler(fn func()) {
248	eventHandler = fn
249}
250