gofront 1.8.0 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/README.md +3 -3
  2. package/package.json +6 -5
  3. package/src/backend/js/expressions.js +1157 -408
  4. package/src/backend/js/index.js +327 -153
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +559 -2552
  7. package/src/backend/js/statements.js +670 -158
  8. package/src/backend/js/stdlib/index.js +45 -93
  9. package/src/backend/js/templ.js +32 -45
  10. package/src/backend/js/type-info.js +298 -19
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +91 -170
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +490 -965
  15. package/src/backend/wasm/emit-builtins.js +884 -256
  16. package/src/backend/wasm/emit-exprs.js +185 -77
  17. package/src/backend/wasm/emit-maps.js +17 -367
  18. package/src/backend/wasm/emit-shared.js +4 -1
  19. package/src/backend/wasm/emit-stmts.js +524 -240
  20. package/src/backend/wasm/emit.js +176 -38
  21. package/src/backend/wasm/globals.js +48 -8
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +44 -530
  24. package/src/backend/wasm/method-expr.js +12 -1
  25. package/src/backend/wasm/module-emitter.js +299 -770
  26. package/src/backend/wasm/monomorph.js +139 -8
  27. package/src/backend/wasm/natives.js +600 -0
  28. package/src/backend/wasm/prescan.js +5 -153
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -32
  31. package/src/backend/wasm/types.js +97 -20
  32. package/src/backend/wasm/variadic.js +9 -4
  33. package/src/cli/cli-core.js +5 -1
  34. package/src/cli/test-runner.js +115 -342
  35. package/src/compiler.js +113 -130
  36. package/src/lexer/lexer.js +106 -11
  37. package/src/lexer/templ-lexer.js +61 -18
  38. package/src/lexer/tokens.js +1 -0
  39. package/src/link/stdlib.js +355 -132
  40. package/src/link/type-names.js +204 -0
  41. package/src/link/wasm.js +30 -6
  42. package/src/lower/bounds.js +153 -0
  43. package/src/lower/captures.js +92 -33
  44. package/src/lower/embedding.js +62 -5
  45. package/src/lower/errors-as.js +196 -0
  46. package/src/lower/fmt-formatters.js +929 -0
  47. package/src/lower/functions.js +225 -7
  48. package/src/lower/gen-types.js +293 -0
  49. package/src/lower/index.js +14 -2
  50. package/src/lower/json-codecs.js +334 -245
  51. package/src/lower/ownership.js +25 -7
  52. package/src/lower/reachability.js +69 -51
  53. package/src/lower/walk.js +12 -0
  54. package/src/parser/declarations.js +30 -5
  55. package/src/parser/expressions.js +111 -57
  56. package/src/parser/statements.js +108 -0
  57. package/src/parser/templ.js +44 -5
  58. package/src/parser/types.js +12 -0
  59. package/src/resolver.js +1 -2
  60. package/src/typechecker/assignability.js +214 -19
  61. package/src/typechecker/const-fold.js +70 -27
  62. package/src/typechecker/constants.js +156 -8
  63. package/src/typechecker/expressions.js +490 -108
  64. package/src/typechecker/index.js +346 -42
  65. package/src/typechecker/init-order.js +136 -40
  66. package/src/typechecker/operand-text.js +41 -0
  67. package/src/typechecker/resolve.js +490 -65
  68. package/src/typechecker/statements.js +288 -27
  69. package/src/typechecker/stdlib/excluded.js +9 -5
  70. package/src/typechecker/stdlib/index.js +25 -9
  71. package/src/typechecker/termination.js +10 -1
  72. package/src/typechecker/types.js +287 -575
  73. package/stdlib/bytes/buffer.go +479 -0
  74. package/stdlib/bytes/bytes.go +1010 -0
  75. package/stdlib/bytes/reader.go +161 -0
  76. package/stdlib/context/context.go +115 -0
  77. package/stdlib/crypto/rand/rand.go +34 -0
  78. package/stdlib/crypto/sha256/sha256.go +249 -0
  79. package/stdlib/encoding/base64/base64.go +331 -0
  80. package/stdlib/encoding/binary/binary.go +332 -0
  81. package/stdlib/encoding/hex/hex.go +140 -0
  82. package/stdlib/encoding/json/json.go +62 -0
  83. package/stdlib/errors/errors.go +174 -0
  84. package/stdlib/fmt/errors.go +110 -0
  85. package/stdlib/fmt/format.go +732 -0
  86. package/stdlib/fmt/print.go +1232 -0
  87. package/stdlib/gom/gom.go +293 -0
  88. package/stdlib/html/html.go +251 -0
  89. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  90. package/stdlib/internal/jsonrt/dec.go +171 -38
  91. package/stdlib/internal/jsonrt/enc.go +186 -36
  92. package/stdlib/internal/jsonrt/scanner.go +17 -8
  93. package/stdlib/io/io.go +157 -1
  94. package/stdlib/maps/maps.go +131 -0
  95. package/stdlib/math/bits/bits.go +507 -0
  96. package/stdlib/math/extra.go +16 -14
  97. package/stdlib/math/math.go +439 -0
  98. package/stdlib/math/rand/rand.go +297 -0
  99. package/stdlib/os/os.go +93 -0
  100. package/stdlib/path/filepath/filepath.go +47 -0
  101. package/stdlib/path/path.go +394 -0
  102. package/stdlib/regexp/regexp.go +231 -0
  103. package/stdlib/slices/slices.go +526 -0
  104. package/stdlib/slices/sort.go +209 -0
  105. package/stdlib/slices/zsort_cmp.go +480 -0
  106. package/stdlib/slices/zsort_ordered.go +482 -0
  107. package/stdlib/sort/sort.go +391 -452
  108. package/stdlib/sort/zsort_func.go +480 -0
  109. package/stdlib/sort/zsort_iface.go +480 -0
  110. package/stdlib/strconv/float.go +1699 -0
  111. package/stdlib/strconv/isprint.go +752 -0
  112. package/stdlib/strconv/quote.go +235 -50
  113. package/stdlib/strconv/strconv.go +616 -0
  114. package/stdlib/strings/builder.go +95 -0
  115. package/stdlib/strings/reader.go +163 -0
  116. package/stdlib/strings/replacer.go +102 -0
  117. package/stdlib/strings/strings.go +807 -0
  118. package/stdlib/sync/sync.go +268 -0
  119. package/stdlib/testing/async_js.go +61 -0
  120. package/stdlib/testing/benchmark.go +297 -0
  121. package/stdlib/testing/testing.go +523 -0
  122. package/stdlib/time/duration.go +233 -0
  123. package/stdlib/time/format.go +745 -0
  124. package/stdlib/time/parse.go +790 -0
  125. package/stdlib/time/sleep_js.go +63 -0
  126. package/stdlib/time/time.go +834 -0
  127. package/stdlib/unicode/unicode.go +141 -3
  128. package/stdlib/unicode/utf8/utf8.go +464 -0
  129. package/src/backend/js/fmt-runtime.js +0 -573
  130. package/src/backend/js/stdlib/base64.js +0 -12
  131. package/src/backend/js/stdlib/binary.js +0 -102
  132. package/src/backend/js/stdlib/bits.js +0 -119
  133. package/src/backend/js/stdlib/builder.js +0 -98
  134. package/src/backend/js/stdlib/bytes.js +0 -368
  135. package/src/backend/js/stdlib/errors.js +0 -45
  136. package/src/backend/js/stdlib/fmt.js +0 -517
  137. package/src/backend/js/stdlib/gom.js +0 -155
  138. package/src/backend/js/stdlib/hex.js +0 -17
  139. package/src/backend/js/stdlib/html.js +0 -20
  140. package/src/backend/js/stdlib/io.js +0 -137
  141. package/src/backend/js/stdlib/json.js +0 -1167
  142. package/src/backend/js/stdlib/maps.js +0 -37
  143. package/src/backend/js/stdlib/math.js +0 -85
  144. package/src/backend/js/stdlib/os.js +0 -13
  145. package/src/backend/js/stdlib/path.js +0 -48
  146. package/src/backend/js/stdlib/rand.js +0 -32
  147. package/src/backend/js/stdlib/regexp.js +0 -54
  148. package/src/backend/js/stdlib/sha256.js +0 -12
  149. package/src/backend/js/stdlib/shared.js +0 -17
  150. package/src/backend/js/stdlib/simd.js +0 -34
  151. package/src/backend/js/stdlib/slices.js +0 -107
  152. package/src/backend/js/stdlib/sort.js +0 -30
  153. package/src/backend/js/stdlib/strconv.js +0 -79
  154. package/src/backend/js/stdlib/strings.js +0 -301
  155. package/src/backend/js/stdlib/sync.js +0 -13
  156. package/src/backend/js/stdlib/testing.js +0 -41
  157. package/src/backend/js/stdlib/time.js +0 -152
  158. package/src/backend/js/stdlib/unicode.js +0 -25
  159. package/src/backend/js/stdlib/utf8.js +0 -26
  160. package/src/backend/wasm/stdlib/bytes.js +0 -6716
  161. package/src/backend/wasm/stdlib/core.js +0 -10278
  162. package/src/backend/wasm/stdlib/io.js +0 -3578
  163. package/src/backend/wasm/stdlib/packages.js +0 -2402
  164. package/src/backend/wasm/stdlib/pure.js +0 -720
  165. package/src/backend/wasm/stdlib/time.js +0 -2229
  166. package/src/typechecker/stdlib/core.js +0 -1079
  167. package/src/typechecker/stdlib/extended.js +0 -1210
  168. package/stdlib/bytes/iter.go +0 -192
  169. package/stdlib/bytes/search.go +0 -240
  170. package/stdlib/bytes/split.go +0 -218
  171. package/stdlib/errors/overlay.go +0 -12
  172. package/stdlib/maps/iter.go +0 -65
  173. package/stdlib/slices/extra.go +0 -73
  174. package/stdlib/slices/iter.go +0 -113
  175. package/stdlib/strings/extra.go +0 -35
  176. package/stdlib/strings/iter.go +0 -122
  177. package/stdlib/sync/extra.go +0 -105
  178. package/stdlib/unicode/utf8/bytes.go +0 -239
@@ -0,0 +1,268 @@
1
+ //go:build gofront
2
+
3
+ //gofront:target both
4
+
5
+ // Package sync provides the basic synchronization primitives for a single
6
+ // thread of execution.
7
+ //
8
+ // GoFront has no goroutines, so nothing here ever blocks: a lock that would
9
+ // have to wait for another goroutine can never be released, and reports the
10
+ // deadlock Go's runtime would. Like Go, a Mutex, RWMutex, Once or WaitGroup
11
+ // must not be copied after first use; a copy is an independent value that
12
+ // starts in the state of the original (this is not diagnosed).
13
+ package sync
14
+
15
+ // deadlock is the message of Go's runtime when every goroutine is blocked.
16
+ const deadlock = "fatal error: all goroutines are asleep - deadlock!"
17
+
18
+ // A Locker represents an object that can be locked and unlocked.
19
+ type Locker interface {
20
+ Lock()
21
+ Unlock()
22
+ }
23
+
24
+ // A Mutex is a mutual exclusion lock. The zero value is an unlocked mutex.
25
+ type Mutex struct {
26
+ locked bool
27
+ }
28
+
29
+ // Lock locks m. Locking a mutex that is already locked can never succeed
30
+ // without another goroutine, so it reports a deadlock.
31
+ func (m *Mutex) Lock() {
32
+ if m.locked {
33
+ panic(deadlock)
34
+ }
35
+ m.locked = true
36
+ }
37
+
38
+ // TryLock tries to lock m and reports whether it succeeded.
39
+ func (m *Mutex) TryLock() bool {
40
+ if m.locked {
41
+ return false
42
+ }
43
+ m.locked = true
44
+ return true
45
+ }
46
+
47
+ // Unlock unlocks m. It is a run-time error if m is not locked on entry.
48
+ func (m *Mutex) Unlock() {
49
+ if !m.locked {
50
+ panic("sync: unlock of unlocked mutex")
51
+ }
52
+ m.locked = false
53
+ }
54
+
55
+ // A RWMutex is a reader/writer mutual exclusion lock. The zero value is an
56
+ // unlocked mutex.
57
+ type RWMutex struct {
58
+ writer bool
59
+ readers int
60
+ }
61
+
62
+ // Lock locks rw for writing. It reports a deadlock when rw is already locked
63
+ // for reading or writing.
64
+ func (rw *RWMutex) Lock() {
65
+ if rw.writer || rw.readers > 0 {
66
+ panic(deadlock)
67
+ }
68
+ rw.writer = true
69
+ }
70
+
71
+ // TryLock tries to lock rw for writing and reports whether it succeeded.
72
+ func (rw *RWMutex) TryLock() bool {
73
+ if rw.writer || rw.readers > 0 {
74
+ return false
75
+ }
76
+ rw.writer = true
77
+ return true
78
+ }
79
+
80
+ // Unlock unlocks rw for writing. It is a run-time error if rw is not locked
81
+ // for writing on entry.
82
+ func (rw *RWMutex) Unlock() {
83
+ if !rw.writer {
84
+ panic("sync: Unlock of unlocked RWMutex")
85
+ }
86
+ rw.writer = false
87
+ }
88
+
89
+ // RLock locks rw for reading. It reports a deadlock when rw is locked for
90
+ // writing.
91
+ func (rw *RWMutex) RLock() {
92
+ if rw.writer {
93
+ panic(deadlock)
94
+ }
95
+ rw.readers++
96
+ }
97
+
98
+ // TryRLock tries to lock rw for reading and reports whether it succeeded.
99
+ func (rw *RWMutex) TryRLock() bool {
100
+ if rw.writer {
101
+ return false
102
+ }
103
+ rw.readers++
104
+ return true
105
+ }
106
+
107
+ // RUnlock undoes a single RLock call. It is a run-time error if rw is not
108
+ // locked for reading on entry.
109
+ func (rw *RWMutex) RUnlock() {
110
+ if rw.readers <= 0 {
111
+ panic("sync: RUnlock of unlocked RWMutex")
112
+ }
113
+ rw.readers--
114
+ }
115
+
116
+ type rlocker struct {
117
+ rw *RWMutex
118
+ }
119
+
120
+ func (r *rlocker) Lock() { r.rw.RLock() }
121
+
122
+ func (r *rlocker) Unlock() { r.rw.RUnlock() }
123
+
124
+ // RLocker returns a Locker interface that implements the Lock and Unlock
125
+ // methods by calling rw.RLock and rw.RUnlock.
126
+ func (rw *RWMutex) RLocker() Locker {
127
+ return &rlocker{rw: rw}
128
+ }
129
+
130
+ // Once is an object that will perform exactly one action.
131
+ type Once struct {
132
+ done bool
133
+ m Mutex
134
+ }
135
+
136
+ // Do calls the function f if and only if Do is being called for the first
137
+ // time for this instance of Once. If f panics, Do considers it to have
138
+ // returned; future calls of Do return without calling f. Calling Do from
139
+ // within f reports a deadlock, as in Go.
140
+ func (o *Once) Do(f func()) {
141
+ if !o.done {
142
+ o.doSlow(f)
143
+ }
144
+ }
145
+
146
+ func (o *Once) doSlow(f func()) {
147
+ o.m.Lock()
148
+ defer o.m.Unlock()
149
+ if !o.done {
150
+ defer func() { o.done = true }()
151
+ f()
152
+ }
153
+ }
154
+
155
+ // A WaitGroup waits for a collection of goroutines to finish. Without
156
+ // goroutines the counter only moves with Add and Done.
157
+ type WaitGroup struct {
158
+ n int
159
+ }
160
+
161
+ // Add adds delta, which may be negative, to the WaitGroup counter.
162
+ // If the counter becomes negative, Add panics.
163
+ func (wg *WaitGroup) Add(delta int) {
164
+ n := wg.n + delta
165
+ if n < 0 {
166
+ panic("sync: negative WaitGroup counter")
167
+ }
168
+ wg.n = n
169
+ }
170
+
171
+ // Done decrements the WaitGroup counter by one.
172
+ func (wg *WaitGroup) Done() {
173
+ wg.Add(-1)
174
+ }
175
+
176
+ // Wait returns when the counter is zero. With a positive counter nothing
177
+ // could ever call Done, so it reports a deadlock.
178
+ func (wg *WaitGroup) Wait() {
179
+ if wg.n > 0 {
180
+ panic(deadlock)
181
+ }
182
+ }
183
+
184
+ // OnceFunc returns a function that invokes f only once. The returned function
185
+ // may be called concurrently.
186
+ //
187
+ // If f panics, the returned function will panic with the same value on every call.
188
+ func OnceFunc(f func()) func() {
189
+ var (
190
+ once Once
191
+ valid bool
192
+ p any
193
+ )
194
+ return func() {
195
+ once.Do(func() {
196
+ defer func() {
197
+ p = recover()
198
+ if !valid {
199
+ panic(p)
200
+ }
201
+ }()
202
+ f()
203
+ valid = true
204
+ })
205
+ if !valid {
206
+ panic(p)
207
+ }
208
+ }
209
+ }
210
+
211
+ // OnceValue returns a function that invokes f only once and returns the value
212
+ // returned by f. The returned function may be called concurrently.
213
+ //
214
+ // If f panics, the returned function will panic with the same value on every call.
215
+ func OnceValue[T any](f func() T) func() T {
216
+ var (
217
+ once Once
218
+ valid bool
219
+ p any
220
+ result T
221
+ )
222
+ return func() T {
223
+ once.Do(func() {
224
+ defer func() {
225
+ p = recover()
226
+ if !valid {
227
+ panic(p)
228
+ }
229
+ }()
230
+ result = f()
231
+ valid = true
232
+ })
233
+ if !valid {
234
+ panic(p)
235
+ }
236
+ return result
237
+ }
238
+ }
239
+
240
+ // OnceValues returns a function that invokes f only once and returns the values
241
+ // returned by f. The returned function may be called concurrently.
242
+ //
243
+ // If f panics, the returned function will panic with the same value on every call.
244
+ func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) {
245
+ var (
246
+ once Once
247
+ valid bool
248
+ p any
249
+ r1 T1
250
+ r2 T2
251
+ )
252
+ return func() (T1, T2) {
253
+ once.Do(func() {
254
+ defer func() {
255
+ p = recover()
256
+ if !valid {
257
+ panic(p)
258
+ }
259
+ }()
260
+ r1, r2 = f()
261
+ valid = true
262
+ })
263
+ if !valid {
264
+ panic(p)
265
+ }
266
+ return r1, r2
267
+ }
268
+ }
@@ -0,0 +1,61 @@
1
+ //go:build gofront
2
+
3
+ //gofront:target js
4
+
5
+ package testing
6
+
7
+ // Async tests and subtests, for js packages. The body of an `async func`
8
+ // test returns a promise that the call through testing_call keeps; the
9
+ // driver awaits it before it reports the test.
10
+
11
+ // call calls f(t) and returns what the function returned: a promise when f
12
+ // is async, nil otherwise.
13
+ //
14
+ //gofront:intrinsic testing_call
15
+ func call(f any, t any) any
16
+
17
+ func jsCall(f func(*T), t *T) any {
18
+ return call(f, t)
19
+ }
20
+
21
+ // settle waits for the async subtests of t, in the order they started, and
22
+ // reports them.
23
+ async func (t *T) settle() {
24
+ for len(t.pending) > 0 {
25
+ p := t.pending[0]
26
+ t.pending = t.pending[1:]
27
+ await p.sub.wait(p.res)
28
+ await p.sub.settle()
29
+ p.sub.finish()
30
+ }
31
+ }
32
+
33
+ // wait waits for the promise of a test body, recording a rejection as a
34
+ // failure of the test.
35
+ async func (t *T) wait(res any) {
36
+ defer t.Guard()
37
+ await res
38
+ }
39
+
40
+ // RunAsync is Run for a js package: the bodies of async tests are awaited,
41
+ // in order, and so are the async subtests they start. TestMain cannot use
42
+ // it.
43
+ async func (m *M) RunAsync() int {
44
+ callHook = jsCall
45
+ tests, benches := m.plan()
46
+ for _, tt := range tests {
47
+ t := &T{common: common{name: tt.Name}}
48
+ t.begin()
49
+ await t.wait(t.invoke(tt.F))
50
+ await t.settle()
51
+ t.finish()
52
+ if t.failed {
53
+ m.failures++
54
+ }
55
+ }
56
+ callHook = nil
57
+ for _, bb := range benches {
58
+ m.runBenchmark(bb)
59
+ }
60
+ return m.conclude()
61
+ }
@@ -0,0 +1,297 @@
1
+ //go:build gofront
2
+
3
+ //gofront:target both
4
+
5
+ package testing
6
+
7
+ import (
8
+ "fmt"
9
+ "time"
10
+ )
11
+
12
+ // B is a type passed to Benchmark functions to manage benchmark timing and
13
+ // control the number of iterations.
14
+ type B struct {
15
+ common
16
+ // N is the number of iterations the benchmark function must run.
17
+ N int
18
+ timerOn bool
19
+ timerStart int64
20
+ elapsedNs int64
21
+ bytes int64
22
+ targetNs int64
23
+
24
+ // hasSub is set once b.Run has run a sub-benchmark: the parent then
25
+ // reports no result of its own and is not grown.
26
+ hasSub bool
27
+
28
+ loopStarted bool
29
+ loopIters int
30
+ nextCheck int
31
+ }
32
+
33
+ func newB(name string, targetNs int64) *B {
34
+ return &B{
35
+ common: common{name: name},
36
+ N: 1,
37
+ timerOn: true,
38
+ timerStart: nanotime(),
39
+ targetNs: targetNs,
40
+ }
41
+ }
42
+
43
+ // ResetTimer zeroes the elapsed benchmark time.
44
+ func (b *B) ResetTimer() {
45
+ b.timerStart = nanotime()
46
+ b.elapsedNs = 0
47
+ }
48
+
49
+ // StartTimer starts timing a test.
50
+ func (b *B) StartTimer() {
51
+ if !b.timerOn {
52
+ b.timerStart = nanotime()
53
+ b.timerOn = true
54
+ }
55
+ }
56
+
57
+ // StopTimer stops timing a test.
58
+ func (b *B) StopTimer() {
59
+ if b.timerOn {
60
+ b.elapsedNs += nanotime() - b.timerStart
61
+ b.timerOn = false
62
+ }
63
+ }
64
+
65
+ // ReportAllocs enables malloc statistics. They are not collected.
66
+ func (b *B) ReportAllocs() {}
67
+
68
+ // ReportMetric adds a custom metric. It is not recorded.
69
+ func (b *B) ReportMetric(n float64, unit string) {}
70
+
71
+ // SetBytes records the number of bytes processed in a single operation.
72
+ func (b *B) SetBytes(n int64) { b.bytes = n }
73
+
74
+ // Elapsed returns the measured elapsed time of the benchmark.
75
+ func (b *B) Elapsed() time.Duration {
76
+ ns := b.elapsedNs
77
+ if b.timerOn {
78
+ ns += nanotime() - b.timerStart
79
+ }
80
+ return time.Duration(ns)
81
+ }
82
+
83
+ // Loop returns true as long as the benchmark should continue running.
84
+ func (b *B) Loop() bool {
85
+ if !b.loopStarted {
86
+ b.loopStarted = true
87
+ b.ResetTimer()
88
+ if b.targetNs == 0 {
89
+ b.targetNs = 1000000000
90
+ }
91
+ b.loopIters = 0
92
+ b.nextCheck = 1
93
+ }
94
+ if b.failed || b.skipped {
95
+ return false
96
+ }
97
+ b.loopIters++
98
+ b.N = b.loopIters
99
+ if b.loopIters >= b.nextCheck {
100
+ elapsed := nanotime() - b.timerStart
101
+ if elapsed >= b.targetNs || b.loopIters >= 1000000000 {
102
+ // This call starts no iteration.
103
+ b.loopIters--
104
+ b.N = b.loopIters
105
+ b.StopTimer()
106
+ return false
107
+ }
108
+ nsPerOp := max(int64(1), elapsed/int64(b.loopIters))
109
+ remaining := b.targetNs - elapsed
110
+ add := max(int64(1), min(int64(100000), remaining/(nsPerOp*2)))
111
+ b.nextCheck = b.loopIters + int(add)
112
+ }
113
+ return true
114
+ }
115
+
116
+ func (b *B) invoke(f func(*B)) {
117
+ defer b.Guard()
118
+ f(b)
119
+ }
120
+
121
+ // Run runs f as a sub-benchmark of b called name, growing b.N until the run
122
+ // takes the target time, and reports its result. It reports whether f
123
+ // succeeded.
124
+ func (b *B) Run(name string, f func(b *B)) bool {
125
+ full := name
126
+ if b.name != "" {
127
+ full = b.name + "/" + name
128
+ }
129
+ if !selected(full) {
130
+ return true
131
+ }
132
+ b.hasSub = true
133
+ sub := newB(full, b.targetNs)
134
+ sub.parent = &b.common
135
+ sub.begin()
136
+ sub.ResetTimer()
137
+ sub.invoke(f)
138
+ sub.grow(f)
139
+ sub.StopTimer()
140
+ sub.runCleanups()
141
+ if sub.failed {
142
+ // A failure in a regrown run did not reach the parent.
143
+ b.Fail()
144
+ }
145
+ if !sub.onlyParent() {
146
+ sub.reportBenchmark()
147
+ }
148
+ return !sub.failed
149
+ }
150
+
151
+ // BenchmarkResult contains the results of a benchmark run.
152
+ type BenchmarkResult struct {
153
+ N int
154
+ T time.Duration
155
+ Bytes int64
156
+ MemAllocs uint64
157
+ MemBytes uint64
158
+ Extra map[string]float64
159
+ }
160
+
161
+ // NsPerOp returns the "ns/op" metric.
162
+ func (r BenchmarkResult) NsPerOp() int64 {
163
+ if r.N <= 0 {
164
+ return 0
165
+ }
166
+ return int64(r.T) / int64(r.N)
167
+ }
168
+
169
+ // AllocsPerOp returns the "allocs/op" metric.
170
+ func (r BenchmarkResult) AllocsPerOp() int64 {
171
+ if r.N <= 0 {
172
+ return 0
173
+ }
174
+ return int64(r.MemAllocs) / int64(r.N)
175
+ }
176
+
177
+ // AllocedBytesPerOp returns the "B/op" metric.
178
+ func (r BenchmarkResult) AllocedBytesPerOp() int64 {
179
+ if r.N <= 0 {
180
+ return 0
181
+ }
182
+ return int64(r.MemBytes) / int64(r.N)
183
+ }
184
+
185
+ // MemString returns r.AllocedBytesPerOp and r.AllocsPerOp in the same format
186
+ // as 'go test'.
187
+ func (r BenchmarkResult) MemString() string {
188
+ return fmt.Sprintf("%d B/op\t%d allocs/op", r.AllocedBytesPerOp(), r.AllocsPerOp())
189
+ }
190
+
191
+ // String returns a summary of the benchmark results.
192
+ func (r BenchmarkResult) String() string {
193
+ s := fmt.Sprintf("%d\t%d ns/op", r.N, r.NsPerOp())
194
+ if r.Bytes > 0 && r.T > 0 {
195
+ mbPerSec := float64(r.Bytes*int64(r.N)) / (float64(r.T) / 1e9) / (1024 * 1024)
196
+ s += fmt.Sprintf("\t%.2f MB/s", mbPerSec)
197
+ }
198
+ return s
199
+ }
200
+
201
+ // grow reruns f with a larger b.N until the run takes the target time. A
202
+ // benchmark that calls b.Loop sizes itself.
203
+ func (b *B) grow(f func(*B)) {
204
+ if b.loopStarted || b.hasSub || b.failed || b.skipped {
205
+ return
206
+ }
207
+ b.StopTimer()
208
+ b.runCleanups()
209
+ n := 1
210
+ for !b.failed && !b.skipped {
211
+ elapsed := int64(b.Elapsed())
212
+ if elapsed >= b.targetNs || n >= 1000000000 {
213
+ break
214
+ }
215
+ var next int
216
+ if elapsed == 0 {
217
+ next = n * 100
218
+ } else {
219
+ next = max(n+1, min(1000000000, int(float64(n)*(float64(b.targetNs)/float64(elapsed))*1.2)))
220
+ }
221
+ if next <= n {
222
+ break
223
+ }
224
+ n = next
225
+ nb := newB(b.name, b.targetNs)
226
+ nb.N = n
227
+ nb.ResetTimer()
228
+ nb.invoke(f)
229
+ nb.StopTimer()
230
+ nb.runCleanups()
231
+ b.N = nb.N
232
+ b.elapsedNs = nb.elapsedNs
233
+ b.timerOn = false
234
+ b.failed = nb.failed
235
+ b.skipped = nb.skipped
236
+ b.logs = nb.logs
237
+ }
238
+ }
239
+
240
+ // Benchmark benchmarks a single function.
241
+ func Benchmark(f func(b *B)) BenchmarkResult {
242
+ b := newB("Benchmark", 1000000000)
243
+ b.ResetTimer()
244
+ b.invoke(f)
245
+ b.grow(f)
246
+ b.runCleanups()
247
+ return BenchmarkResult{N: b.N, T: b.Elapsed(), Bytes: b.bytes}
248
+ }
249
+
250
+ // runBenchmark runs one benchmark of the table and prints its line.
251
+ func (m *M) runBenchmark(bb GofrontBenchmark) {
252
+ if cfg.verbose {
253
+ say(dim("=== RUN ") + bb.Name)
254
+ }
255
+ b := newB(bb.Name, 100000000)
256
+ b.ResetTimer()
257
+ b.invoke(bb.F)
258
+ b.grow(bb.F)
259
+ b.runCleanups()
260
+ if b.failed {
261
+ m.failures++
262
+ }
263
+ if !b.onlyParent() {
264
+ b.reportBenchmark()
265
+ }
266
+ }
267
+
268
+ // onlyParent reports whether b just ran sub-benchmarks, which reported their
269
+ // own results, so it has none of its own.
270
+ func (b *B) onlyParent() bool {
271
+ return b.hasSub && !b.failed && !b.skipped
272
+ }
273
+
274
+ func (b *B) reportBenchmark() {
275
+ if b.skipped {
276
+ say("--- " + yellow("SKIP") + ": " + b.name)
277
+ b.printLogs(plain)
278
+ return
279
+ }
280
+ if b.failed {
281
+ say("--- " + bold(red("FAIL")) + ": " + b.name)
282
+ b.printLogs(red)
283
+ return
284
+ }
285
+ elapsed := int64(b.Elapsed())
286
+ nsPerOp := 0.0
287
+ if b.N > 0 {
288
+ nsPerOp = float64(elapsed) / float64(b.N)
289
+ }
290
+ line := fmt.Sprintf("%-20s\t%8d\t%10.0f ns/op", b.name, b.N, nsPerOp)
291
+ if b.bytes > 0 && elapsed > 0 {
292
+ mbPerSec := float64(b.bytes*int64(b.N)) / (float64(elapsed) / 1e9) / (1024 * 1024)
293
+ line += fmt.Sprintf("\t%8.2f MB/s", mbPerSec)
294
+ }
295
+ say(line)
296
+ b.printLogs(plain)
297
+ }