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// Waiting for a mutex is implemented by allowing other goroutines
15// to run until the mutex gets unlocked.
16
17const (
18	mutex_unlocked = 0
19	mutex_locked   = 1
20
21	note_cleared = 0
22	note_woken   = 1
23	note_timeout = 2
24
25	active_spin     = 4
26	active_spin_cnt = 30
27	passive_spin    = 1
28)
29
30func lock(l *mutex) {
31	for l.key == mutex_locked {
32		mcall(gosched_m)
33	}
34	l.key = mutex_locked
35}
36
37func unlock(l *mutex) {
38	if l.key == mutex_unlocked {
39		throw("unlock of unlocked lock")
40	}
41	l.key = mutex_unlocked
42}
43
44// One-time notifications.
45
46type noteWithTimeout struct {
47	gp       *g
48	deadline int64
49}
50
51var (
52	notes            = make(map[*note]*g)
53	notesWithTimeout = make(map[*note]noteWithTimeout)
54)
55
56func noteclear(n *note) {
57	n.key = note_cleared
58}
59
60func notewakeup(n *note) {
61	// gp := getg()
62	if n.key == note_woken {
63		throw("notewakeup - double wakeup")
64	}
65	cleared := n.key == note_cleared
66	n.key = note_woken
67	if cleared {
68		goready(notes[n], 1)
69	}
70}
71
72func notesleep(n *note) {
73	throw("notesleep not supported by js")
74}
75
76func notetsleep(n *note, ns int64) bool {
77	throw("notetsleep not supported by js")
78	return false
79}
80
81// same as runtime·notetsleep, but called on user g (not g0)
82func notetsleepg(n *note, ns int64) bool {
83	gp := getg()
84	if gp == gp.m.g0 {
85		throw("notetsleepg on g0")
86	}
87
88	if ns >= 0 {
89		deadline := nanotime() + ns
90		delay := ns/1000000 + 1 // round up
91		if delay > 1<<31-1 {
92			delay = 1<<31 - 1 // cap to max int32
93		}
94
95		id := scheduleTimeoutEvent(delay)
96		mp := acquirem()
97		notes[n] = gp
98		notesWithTimeout[n] = noteWithTimeout{gp: gp, deadline: deadline}
99		releasem(mp)
100
101		gopark(nil, nil, waitReasonSleep, traceEvNone, 1)
102
103		clearTimeoutEvent(id) // note might have woken early, clear timeout
104		mp = acquirem()
105		delete(notes, n)
106		delete(notesWithTimeout, n)
107		releasem(mp)
108
109		return n.key == note_woken
110	}
111
112	for n.key != note_woken {
113		mp := acquirem()
114		notes[n] = gp
115		releasem(mp)
116
117		gopark(nil, nil, waitReasonZero, traceEvNone, 1)
118
119		mp = acquirem()
120		delete(notes, n)
121		releasem(mp)
122	}
123	return true
124}
125
126// checkTimeouts resumes goroutines that are waiting on a note which has reached its deadline.
127func checkTimeouts() {
128	now := nanotime()
129	for n, nt := range notesWithTimeout {
130		if n.key == note_cleared && now >= nt.deadline {
131			n.key = note_timeout
132			goready(nt.gp, 1)
133		}
134	}
135}
136
137var returnedEventHandler *g
138
139func init() {
140	// At the toplevel we need an extra goroutine that handles asynchronous events.
141	initg := getg()
142	go func() {
143		returnedEventHandler = getg()
144		goready(initg, 1)
145
146		gopark(nil, nil, waitReasonZero, traceEvNone, 1)
147		returnedEventHandler = nil
148
149		pause(getcallersp() - 16)
150	}()
151	gopark(nil, nil, waitReasonZero, traceEvNone, 1)
152}
153
154// beforeIdle gets called by the scheduler if no goroutine is awake.
155// We resume the event handler (if available) which will pause the execution.
156func beforeIdle() bool {
157	if returnedEventHandler != nil {
158		goready(returnedEventHandler, 1)
159		return true
160	}
161	return false
162}
163
164// pause sets SP to newsp and pauses the execution of Go's WebAssembly code until an event is triggered.
165func pause(newsp uintptr)
166
167// scheduleTimeoutEvent tells the WebAssembly environment to trigger an event after ms milliseconds.
168// It returns a timer id that can be used with clearTimeoutEvent.
169func scheduleTimeoutEvent(ms int64) int32
170
171// clearTimeoutEvent clears a timeout event scheduled by scheduleTimeoutEvent.
172func clearTimeoutEvent(id int32)
173
174func handleEvent() {
175	prevReturnedEventHandler := returnedEventHandler
176	returnedEventHandler = nil
177
178	checkTimeouts()
179	eventHandler()
180
181	returnedEventHandler = getg()
182	gopark(nil, nil, waitReasonZero, traceEvNone, 1)
183
184	returnedEventHandler = prevReturnedEventHandler
185
186	pause(getcallersp() - 16)
187}
188
189var eventHandler func()
190
191//go:linkname setEventHandler syscall/js.setEventHandler
192func setEventHandler(fn func()) {
193	eventHandler = fn
194}
195