goscript 0.2.3 → 0.2.5

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 (160) hide show
  1. package/README.md +8 -8
  2. package/cmd/go_js_wasm_exec/main.go +1 -1
  3. package/cmd/go_js_wasm_exec/main_test.go +1 -1
  4. package/cmd/goscript/cmd-compile.go +9 -1
  5. package/cmd/goscript/cmd-test.go +1 -1
  6. package/cmd/goscript/cmd_compile_test.go +44 -0
  7. package/cmd/goscript/deps.go +1 -1
  8. package/cmd/goscript-wasm/main.go +2 -2
  9. package/compiler/compile-request.go +19 -0
  10. package/compiler/compile_bench_test.go +121 -0
  11. package/compiler/compliance_test.go +17 -1
  12. package/compiler/config.go +2 -0
  13. package/compiler/gotest/result.go +1 -1
  14. package/compiler/gotest/runner.go +2 -2
  15. package/compiler/gotest/runner_test.go +4 -7
  16. package/compiler/index.test.ts +28 -0
  17. package/compiler/index.ts +32 -16
  18. package/compiler/lowering.go +1236 -143
  19. package/compiler/lowering_bench_test.go +4 -0
  20. package/compiler/override-facts.go +1 -1
  21. package/compiler/override-registry_test.go +125 -0
  22. package/compiler/package-graph.go +92 -0
  23. package/compiler/package-graph_test.go +113 -0
  24. package/compiler/runtime-contract.go +1 -1
  25. package/compiler/semantic-model.go +32 -0
  26. package/compiler/skeleton_test.go +284 -11
  27. package/compiler/wasm/compile.go +1 -1
  28. package/compiler/wasm/compile_test.go +1 -1
  29. package/dist/compiler/index.d.ts +4 -0
  30. package/dist/compiler/index.js +26 -15
  31. package/dist/compiler/index.js.map +1 -1
  32. package/dist/gs/compress/gzip/index.d.ts +41 -0
  33. package/dist/gs/compress/gzip/index.js +235 -0
  34. package/dist/gs/compress/gzip/index.js.map +1 -0
  35. package/dist/gs/database/sql/driver/index.d.ts +165 -0
  36. package/dist/gs/database/sql/driver/index.js +432 -0
  37. package/dist/gs/database/sql/driver/index.js.map +1 -0
  38. package/dist/gs/encoding/binary/index.d.ts +71 -0
  39. package/dist/gs/encoding/binary/index.js +778 -0
  40. package/dist/gs/encoding/binary/index.js.map +1 -0
  41. package/dist/gs/fmt/fmt.js +156 -57
  42. package/dist/gs/fmt/fmt.js.map +1 -1
  43. package/dist/gs/github.com/klauspost/cpuid/v2/index.d.ts +11 -0
  44. package/dist/gs/github.com/klauspost/cpuid/v2/index.js +28 -0
  45. package/dist/gs/github.com/klauspost/cpuid/v2/index.js.map +1 -0
  46. package/dist/gs/github.com/pkg/errors/errors.d.ts +0 -2
  47. package/dist/gs/github.com/pkg/errors/errors.js.map +1 -1
  48. package/dist/gs/github.com/pkg/errors/index.d.ts +2 -1
  49. package/dist/gs/github.com/pkg/errors/index.js +1 -1
  50. package/dist/gs/github.com/pkg/errors/index.js.map +1 -1
  51. package/dist/gs/github.com/pkg/errors/stack.d.ts +8 -19
  52. package/dist/gs/github.com/pkg/errors/stack.js +26 -61
  53. package/dist/gs/github.com/pkg/errors/stack.js.map +1 -1
  54. package/dist/gs/golang.org/x/crypto/cryptobyte/asn1/index.d.ts +19 -0
  55. package/dist/gs/golang.org/x/crypto/cryptobyte/asn1/index.js +25 -0
  56. package/dist/gs/golang.org/x/crypto/cryptobyte/asn1/index.js.map +1 -0
  57. package/dist/gs/golang.org/x/crypto/cryptobyte/index.d.ts +104 -0
  58. package/dist/gs/golang.org/x/crypto/cryptobyte/index.js +1107 -0
  59. package/dist/gs/golang.org/x/crypto/cryptobyte/index.js.map +1 -0
  60. package/dist/gs/golang.org/x/crypto/internal/alias/index.d.ts +3 -0
  61. package/dist/gs/golang.org/x/crypto/internal/alias/index.js +39 -0
  62. package/dist/gs/golang.org/x/crypto/internal/alias/index.js.map +1 -0
  63. package/dist/gs/io/fs/glob.js +1 -1
  64. package/dist/gs/io/fs/glob.js.map +1 -1
  65. package/dist/gs/io/fs/readlink.d.ts +1 -1
  66. package/dist/gs/io/fs/readlink.js +2 -2
  67. package/dist/gs/io/fs/readlink.js.map +1 -1
  68. package/dist/gs/io/fs/stat.d.ts +4 -2
  69. package/dist/gs/io/fs/stat.js +12 -73
  70. package/dist/gs/io/fs/stat.js.map +1 -1
  71. package/dist/gs/io/fs/sub.d.ts +2 -2
  72. package/dist/gs/io/fs/sub.js +7 -7
  73. package/dist/gs/io/fs/sub.js.map +1 -1
  74. package/dist/gs/io/fs/walk.js +1 -1
  75. package/dist/gs/io/fs/walk.js.map +1 -1
  76. package/dist/gs/net/http/index.d.ts +18 -14
  77. package/dist/gs/net/http/index.js +44 -23
  78. package/dist/gs/net/http/index.js.map +1 -1
  79. package/dist/gs/net/http/pprof/index.d.ts +5 -5
  80. package/dist/gs/net/http/pprof/index.js +21 -21
  81. package/dist/gs/net/http/pprof/index.js.map +1 -1
  82. package/dist/gs/runtime/runtime.d.ts +6 -1
  83. package/dist/gs/runtime/runtime.js +15 -8
  84. package/dist/gs/runtime/runtime.js.map +1 -1
  85. package/dist/gs/runtime/trace/index.d.ts +8 -5
  86. package/dist/gs/runtime/trace/index.js +324 -23
  87. package/dist/gs/runtime/trace/index.js.map +1 -1
  88. package/dist/gs/slices/slices.d.ts +2 -1
  89. package/dist/gs/slices/slices.js +9 -3
  90. package/dist/gs/slices/slices.js.map +1 -1
  91. package/dist/gs/sort/search.gs.d.ts +3 -1
  92. package/dist/gs/sort/search.gs.js +18 -53
  93. package/dist/gs/sort/search.gs.js.map +1 -1
  94. package/dist/gs/sync/sync.d.ts +1 -1
  95. package/dist/gs/sync/sync.js +3 -0
  96. package/dist/gs/sync/sync.js.map +1 -1
  97. package/dist/gs/time/time.d.ts +22 -29
  98. package/dist/gs/time/time.js +111 -32
  99. package/dist/gs/time/time.js.map +1 -1
  100. package/dist/gs/unsafe/unsafe.d.ts +3 -2
  101. package/dist/gs/unsafe/unsafe.js.map +1 -1
  102. package/go.mod +7 -5
  103. package/go.sum +12 -26
  104. package/gs/builtin/runtime-contract.test.ts +25 -0
  105. package/gs/compress/gzip/index.test.ts +86 -0
  106. package/gs/compress/gzip/index.ts +297 -0
  107. package/gs/compress/gzip/meta.json +6 -0
  108. package/gs/compress/gzip/parity.json +45 -0
  109. package/gs/database/sql/driver/index.test.ts +88 -0
  110. package/gs/database/sql/driver/index.ts +675 -0
  111. package/gs/database/sql/driver/meta.json +3 -0
  112. package/gs/database/sql/driver/parity.json +144 -0
  113. package/gs/embed/index.test.ts +1 -1
  114. package/gs/encoding/binary/index.test.ts +239 -0
  115. package/gs/encoding/binary/index.ts +999 -0
  116. package/gs/encoding/binary/meta.json +9 -0
  117. package/gs/encoding/binary/parity.json +72 -0
  118. package/gs/fmt/fmt.test.ts +28 -0
  119. package/gs/fmt/fmt.ts +198 -61
  120. package/gs/fmt/meta.json +2 -1
  121. package/gs/github.com/klauspost/cpuid/v2/index.ts +38 -0
  122. package/gs/github.com/klauspost/cpuid/v2/meta.json +3 -0
  123. package/gs/github.com/pkg/errors/errors.ts +1 -2
  124. package/gs/github.com/pkg/errors/index.ts +2 -1
  125. package/gs/github.com/pkg/errors/stack.ts +34 -62
  126. package/gs/golang.org/x/crypto/cryptobyte/asn1/index.test.ts +19 -0
  127. package/gs/golang.org/x/crypto/cryptobyte/asn1/index.ts +29 -0
  128. package/gs/golang.org/x/crypto/cryptobyte/index.test.ts +255 -0
  129. package/gs/golang.org/x/crypto/cryptobyte/index.ts +1441 -0
  130. package/gs/golang.org/x/crypto/cryptobyte/meta.json +3 -0
  131. package/gs/golang.org/x/crypto/internal/alias/index.test.ts +40 -0
  132. package/gs/golang.org/x/crypto/internal/alias/index.ts +40 -0
  133. package/gs/io/fs/glob.ts +1 -1
  134. package/gs/io/fs/meta.json +3 -0
  135. package/gs/io/fs/readlink.test.ts +2 -2
  136. package/gs/io/fs/readlink.ts +5 -2
  137. package/gs/io/fs/stat.test.ts +79 -0
  138. package/gs/io/fs/stat.ts +24 -10
  139. package/gs/io/fs/sub.test.ts +93 -0
  140. package/gs/io/fs/sub.ts +9 -9
  141. package/gs/io/fs/walk.ts +1 -1
  142. package/gs/net/http/index.test.ts +207 -2
  143. package/gs/net/http/index.ts +68 -37
  144. package/gs/net/http/meta.json +3 -1
  145. package/gs/net/http/pprof/index.test.ts +4 -4
  146. package/gs/net/http/pprof/index.ts +30 -27
  147. package/gs/runtime/runtime.test.ts +16 -0
  148. package/gs/runtime/runtime.ts +17 -9
  149. package/gs/runtime/trace/index.test.ts +113 -14
  150. package/gs/runtime/trace/index.ts +384 -34
  151. package/gs/runtime/trace/meta.json +1 -0
  152. package/gs/slices/slices.test.ts +24 -1
  153. package/gs/slices/slices.ts +14 -4
  154. package/gs/sort/meta.json +1 -0
  155. package/gs/sort/search.gs.ts +20 -5
  156. package/gs/sync/sync.ts +4 -1
  157. package/gs/time/time.test.ts +79 -2
  158. package/gs/time/time.ts +133 -33
  159. package/gs/unsafe/unsafe.ts +4 -2
  160. package/package.json +2 -2
@@ -4,6 +4,8 @@ import * as context from '@goscript/context/index.js'
4
4
  import * as errors from '@goscript/errors/index.js'
5
5
  import * as fs from '@goscript/io/fs/fs.js'
6
6
  import * as io from '@goscript/io/index.js'
7
+ import * as mime from '@goscript/mime/index.js'
8
+ import * as path from '@goscript/path/index.js'
7
9
  import * as strings from '@goscript/strings/index.js'
8
10
  import * as time from '@goscript/time/index.js'
9
11
 
@@ -248,6 +250,8 @@ export function StatusText(code: number): string {
248
250
  }
249
251
 
250
252
  export type Header = Map<string, $.Slice<string>>
253
+ type HeaderBox = { __goValue: HeaderValue }
254
+ type HeaderValue = Header | $.VarRef<Header> | HeaderBox
251
255
 
252
256
  export const Header = Map as {
253
257
  new (entries?: Iterable<readonly [string, $.Slice<string>]> | null): Header
@@ -257,48 +261,62 @@ export function CanonicalHeaderKey(s: string): string {
257
261
  return canonicalMIMEHeaderKey(s)
258
262
  }
259
263
 
260
- export function Header_Add(h: Header, key: string, value: string): void {
264
+ function headerMap(h: HeaderValue): Header {
265
+ let value: unknown = $.pointerValue(h as Header | $.VarRef<Header>)
266
+ while (
267
+ value !== null &&
268
+ value !== undefined &&
269
+ typeof value === 'object' &&
270
+ '__goValue' in value
271
+ ) {
272
+ value = $.pointerValue((value as HeaderBox).__goValue)
273
+ }
274
+ return value as Header
275
+ }
276
+
277
+ export function Header_Add(h: HeaderValue, key: string, value: string): void {
278
+ const headers = headerMap(h)
261
279
  key = canonicalMIMEHeaderKey(key)
262
- const values = Array.from(h.get(key) ?? [])
280
+ const values = Array.from(headers.get(key) ?? [])
263
281
  values.push(value)
264
- h.set(key, $.arrayToSlice(values))
282
+ headers.set(key, $.arrayToSlice(values))
265
283
  }
266
284
 
267
- export function Header_Del(h: Header, key: string): void {
268
- h.delete(canonicalMIMEHeaderKey(key))
285
+ export function Header_Del(h: HeaderValue, key: string): void {
286
+ headerMap(h).delete(canonicalMIMEHeaderKey(key))
269
287
  }
270
288
 
271
- export function Header_Get(h: Header, key: string): string {
272
- const values = h.get(canonicalMIMEHeaderKey(key))
289
+ export function Header_Get(h: HeaderValue, key: string): string {
290
+ const values = headerMap(h).get(canonicalMIMEHeaderKey(key))
273
291
  return values == null || values.length === 0 ? '' : String(values[0])
274
292
  }
275
293
 
276
- export function Header_Set(h: Header, key: string, value: string): void {
277
- h.set(canonicalMIMEHeaderKey(key), $.arrayToSlice([value]))
294
+ export function Header_Set(h: HeaderValue, key: string, value: string): void {
295
+ headerMap(h).set(canonicalMIMEHeaderKey(key), $.arrayToSlice([value]))
278
296
  }
279
297
 
280
- export function Header_Values(h: Header, key: string): $.Slice<string> {
281
- return h.get(canonicalMIMEHeaderKey(key)) ?? null
298
+ export function Header_Values(h: HeaderValue, key: string): $.Slice<string> {
299
+ return headerMap(h).get(canonicalMIMEHeaderKey(key)) ?? null
282
300
  }
283
301
 
284
- export function Header_Clone(h: Header): Header {
302
+ export function Header_Clone(h: HeaderValue): Header {
285
303
  const cloned = new Header()
286
- for (const [key, values] of h.entries()) {
304
+ for (const [key, values] of headerMap(h).entries()) {
287
305
  cloned.set(key, $.arrayToSlice(Array.from(values ?? [])))
288
306
  }
289
307
  return cloned
290
308
  }
291
309
 
292
- export function Header_Write(h: Header, w: io.Writer): $.GoError {
310
+ export function Header_Write(h: HeaderValue, w: io.Writer): $.GoError {
293
311
  return Header_WriteSubset(h, w, null)
294
312
  }
295
313
 
296
314
  export function Header_WriteSubset(
297
- h: Header,
315
+ h: HeaderValue,
298
316
  w: io.Writer,
299
317
  exclude: Map<string, boolean> | null,
300
318
  ): $.GoError {
301
- for (const [key, values] of h.entries()) {
319
+ for (const [key, values] of headerMap(h).entries()) {
302
320
  if (exclude?.get(key) === true) {
303
321
  continue
304
322
  }
@@ -615,15 +633,15 @@ function asciiLower(value: string): [string, boolean] {
615
633
  return [value.toLowerCase(), true]
616
634
  }
617
635
 
618
- export function SetCookie(
636
+ export async function SetCookie(
619
637
  w: ResponseWriter | null,
620
638
  cookie: Cookie | $.VarRef<Cookie> | null,
621
- ): void {
639
+ ): Promise<void> {
622
640
  const c = $.pointerValue<Cookie | null>(cookie)
623
641
  if (w == null || c == null) {
624
642
  return
625
643
  }
626
- Header_Add(w.Header(), 'Set-Cookie', c.String())
644
+ Header_Add(await w.Header(), 'Set-Cookie', c.String())
627
645
  }
628
646
 
629
647
  class memoryResponseWriter implements ResponseWriter {
@@ -694,9 +712,9 @@ function inProcessServerRequest(request: Request): Request {
694
712
  }
695
713
 
696
714
  export interface ResponseWriter {
697
- Header(): Header
698
- Write(p: $.Slice<number>): [number, $.GoError]
699
- WriteHeader(statusCode: number): void
715
+ Header(): Header | Promise<Header>
716
+ Write(p: $.Slice<number>): [number, $.GoError] | Promise<[number, $.GoError]>
717
+ WriteHeader(statusCode: number): void | Promise<void>
700
718
  }
701
719
 
702
720
  export class Request {
@@ -1491,7 +1509,7 @@ async function readFetchBody(
1491
1509
  type maybePromise<T> = T | Promise<T>
1492
1510
 
1493
1511
  export interface FileSystem {
1494
- Open(name: string): [File | null, $.GoError]
1512
+ Open(name: string): maybePromise<[File | null, $.GoError]>
1495
1513
  }
1496
1514
 
1497
1515
  export interface File extends io.Closer, io.Reader, io.Seeker {
@@ -1513,9 +1531,9 @@ interface fileServerFile {
1513
1531
 
1514
1532
  export function FS(fsys: fs.FS): FileSystem {
1515
1533
  return {
1516
- Open(name: string): [File | null, $.GoError] {
1534
+ async Open(name: string): Promise<[File | null, $.GoError]> {
1517
1535
  const cleaned = cleanFileServerPath(name)
1518
- const [file, err] = fsys?.Open(cleaned) ?? [null, fs.ErrInvalid]
1536
+ const [file, err] = (await fsys?.Open(cleaned)) ?? [null, fs.ErrInvalid]
1519
1537
  if (err != null || file == null) {
1520
1538
  return [null, err]
1521
1539
  }
@@ -1570,17 +1588,27 @@ export function FileServer(root: fileServerFileSystem | null): Handler {
1570
1588
  NotFound(w, req)
1571
1589
  return
1572
1590
  }
1573
- const [data, readErr] = await io.ReadAll(file as io.Reader)
1574
- if (readErr != null) {
1575
- Error(w, readErr.Error(), StatusInternalServerError)
1576
- return
1591
+ const header = await w.Header()
1592
+ if (Header_Get(header, 'Content-Type') === '') {
1593
+ const contentType = mime.TypeByExtension(
1594
+ path.Ext(info?.Name?.() || req.URL?.Path || ''),
1595
+ )
1596
+ if (contentType !== '') {
1597
+ Header_Set(header, 'Content-Type', contentType)
1598
+ }
1577
1599
  }
1578
1600
  if (info?.Size != null) {
1579
- Header_Set(w.Header(), 'Content-Length', String(info.Size()))
1601
+ Header_Set(header, 'Content-Length', String(info.Size()))
1580
1602
  }
1581
- w.WriteHeader(StatusOK)
1603
+ await w.WriteHeader(StatusOK)
1582
1604
  if (req.Method !== MethodHead) {
1583
- w.Write(data)
1605
+ const [, copyErr] = await io.Copy(
1606
+ w as unknown as io.Writer,
1607
+ file as io.Reader,
1608
+ )
1609
+ if (copyErr != null) {
1610
+ return
1611
+ }
1584
1612
  }
1585
1613
  } finally {
1586
1614
  await file.Close()
@@ -2131,7 +2159,7 @@ export function NotFoundHandler(): Handler {
2131
2159
  export function RedirectHandler(url: string, code: number): Handler {
2132
2160
  return {
2133
2161
  ServeHTTP(w, r) {
2134
- Redirect(w, r, url, code)
2162
+ return Redirect(w, r, url, code)
2135
2163
  },
2136
2164
  }
2137
2165
  }
@@ -2168,17 +2196,20 @@ export function NotFound(
2168
2196
  Error(w, '404 page not found', StatusNotFound)
2169
2197
  }
2170
2198
 
2171
- export function Redirect(
2199
+ export async function Redirect(
2172
2200
  w: ResponseWriter | null,
2173
2201
  _r: Request | $.VarRef<Request> | null,
2174
2202
  url: string,
2175
2203
  code: number,
2176
- ): void {
2177
- const header = w?.Header()
2204
+ ): Promise<void> {
2205
+ if (w == null) {
2206
+ return
2207
+ }
2208
+ const header = await w.Header()
2178
2209
  if (header != null) {
2179
2210
  Header_Set(header, 'Location', url)
2180
2211
  }
2181
- w?.WriteHeader(code)
2212
+ await w.WriteHeader(code)
2182
2213
  }
2183
2214
 
2184
2215
  export function ParseTime(text: string): [time.Time, $.GoError] {
@@ -4,7 +4,9 @@
4
4
  "Head": true,
5
5
  "Post": true,
6
6
  "PostForm": true,
7
- "ServeContent": true
7
+ "Redirect": true,
8
+ "ServeContent": true,
9
+ "SetCookie": true
8
10
  },
9
11
  "asyncMethods": {
10
12
  "Client.Do": true,
@@ -27,19 +27,19 @@ class writer implements ResponseWriter {
27
27
  }
28
28
 
29
29
  describe('net/http/pprof override', () => {
30
- it('serves the index text expected by debug handlers', () => {
30
+ it('serves the index text expected by debug handlers', async () => {
31
31
  const w = new writer()
32
32
 
33
- Index(w, null)
33
+ await Index(w, null)
34
34
 
35
35
  expect(Header_Get(w.Header(), 'Content-Type')).toContain('text/html')
36
36
  expect(w.String()).toContain('full goroutine stack dump')
37
37
  })
38
38
 
39
- it('serves named runtime profiles', () => {
39
+ it('serves named runtime profiles', async () => {
40
40
  const w = new writer()
41
41
 
42
- Handler('goroutine').ServeHTTP(w, null)
42
+ await Handler('goroutine').ServeHTTP(w, null)
43
43
 
44
44
  expect(Header_Get(w.Header(), 'Content-Type')).toContain('text/plain')
45
45
  expect(w.String()).toContain('goroutine profile')
@@ -2,82 +2,85 @@ import * as $ from '@goscript/builtin/index.js'
2
2
  import * as http from '@goscript/net/http/index.js'
3
3
  import * as pprof from '@goscript/runtime/pprof/index.js'
4
4
 
5
- function writeString(w: http.ResponseWriter | null, value: string): void {
6
- w?.Write($.stringToBytes(value))
5
+ async function writeString(
6
+ w: http.ResponseWriter | null,
7
+ value: string,
8
+ ): Promise<void> {
9
+ await w?.Write($.stringToBytes(value))
7
10
  }
8
11
 
9
- export function Index(
12
+ export async function Index(
10
13
  w: http.ResponseWriter | null,
11
14
  _r: http.Request | $.VarRef<http.Request> | null,
12
- ): void {
13
- const header = w?.Header()
15
+ ): Promise<void> {
16
+ const header = await w?.Header()
14
17
  if (header != null) {
15
18
  http.Header_Set(header, 'Content-Type', 'text/html; charset=utf-8')
16
19
  }
17
- writeString(
20
+ await writeString(
18
21
  w,
19
22
  '<html><body><a href="goroutine?debug=2">full goroutine stack dump</a></body></html>',
20
23
  )
21
24
  }
22
25
 
23
- export function Cmdline(
26
+ export async function Cmdline(
24
27
  w: http.ResponseWriter | null,
25
28
  _r: http.Request | $.VarRef<http.Request> | null,
26
- ): void {
27
- const header = w?.Header()
29
+ ): Promise<void> {
30
+ const header = await w?.Header()
28
31
  if (header != null) {
29
32
  http.Header_Set(header, 'Content-Type', 'text/plain; charset=utf-8')
30
33
  }
31
- writeString(w, 'goscript')
34
+ await writeString(w, 'goscript')
32
35
  }
33
36
 
34
- export function Profile(
37
+ export async function Profile(
35
38
  w: http.ResponseWriter | null,
36
39
  _r: http.Request | $.VarRef<http.Request> | null,
37
- ): void {
38
- const header = w?.Header()
40
+ ): Promise<void> {
41
+ const header = await w?.Header()
39
42
  if (header != null) {
40
43
  http.Header_Set(header, 'Content-Type', 'application/octet-stream')
41
44
  }
42
- writeString(w, 'cpu profile\n')
45
+ await writeString(w, 'cpu profile\n')
43
46
  }
44
47
 
45
- export function Symbol(
48
+ export async function Symbol(
46
49
  w: http.ResponseWriter | null,
47
50
  _r: http.Request | $.VarRef<http.Request> | null,
48
- ): void {
49
- const header = w?.Header()
51
+ ): Promise<void> {
52
+ const header = await w?.Header()
50
53
  if (header != null) {
51
54
  http.Header_Set(header, 'Content-Type', 'text/plain; charset=utf-8')
52
55
  }
53
- writeString(w, 'num_symbols: 0\n')
56
+ await writeString(w, 'num_symbols: 0\n')
54
57
  }
55
58
 
56
- export function Trace(
59
+ export async function Trace(
57
60
  w: http.ResponseWriter | null,
58
61
  _r: http.Request | $.VarRef<http.Request> | null,
59
- ): void {
60
- const header = w?.Header()
62
+ ): Promise<void> {
63
+ const header = await w?.Header()
61
64
  if (header != null) {
62
65
  http.Header_Set(header, 'Content-Type', 'application/octet-stream')
63
66
  }
64
- writeString(w, 'trace\n')
67
+ await writeString(w, 'trace\n')
65
68
  }
66
69
 
67
70
  export function Handler(name: string): http.Handler {
68
71
  return {
69
- ServeHTTP(w) {
70
- const header = w?.Header()
72
+ async ServeHTTP(w) {
73
+ const header = await w?.Header()
71
74
  if (header != null) {
72
75
  http.Header_Set(header, 'Content-Type', 'text/plain; charset=utf-8')
73
76
  }
74
77
  const profile = pprof.Lookup(name)
75
78
  if (profile == null) {
76
- http.NotFound(w, null)
79
+ await http.NotFound(w, null)
77
80
  return
78
81
  }
79
- writeString(w, `${name} profile\n`)
80
- profile.WriteTo(w, 1)
82
+ await writeString(w, `${name} profile\n`)
83
+ profile.WriteTo(w as unknown as any, 1)
81
84
  },
82
85
  }
83
86
  }
@@ -1,8 +1,12 @@
1
1
  import { describe, expect, it } from 'vitest'
2
2
 
3
+ import * as $ from '@goscript/builtin/index.js'
4
+
3
5
  import {
4
6
  Compiler,
5
7
  FuncForPC,
8
+ MemStats,
9
+ ReadMemStats,
6
10
  ReadTrace,
7
11
  SetFinalizer,
8
12
  StartTrace,
@@ -25,4 +29,16 @@ describe('runtime override', () => {
25
29
  expect(() => SetFinalizer(obj, () => {})).not.toThrow()
26
30
  expect(() => SetFinalizer(obj, null)).not.toThrow()
27
31
  })
32
+
33
+ it('exposes heap and stack MemStats fields', () => {
34
+ const stats = new MemStats()
35
+
36
+ ReadMemStats($.varRef(stats))
37
+
38
+ expect(stats.HeapAlloc).toBe(stats.Alloc)
39
+ expect(stats.HeapSys).toBe(stats.Sys)
40
+ expect(stats.HeapInuse).toBe(stats.Alloc)
41
+ expect(stats.StackInuse).toBe(0)
42
+ expect(stats.StackSys).toBe(0)
43
+ })
28
44
  })
@@ -170,6 +170,11 @@ export class MemStats {
170
170
  public Alloc: number = 0 // bytes allocated and not yet freed
171
171
  public TotalAlloc: number = 0 // bytes allocated (even if freed)
172
172
  public Sys: number = 0 // bytes obtained from system
173
+ public HeapAlloc: number = 0 // bytes allocated and not yet freed on the heap
174
+ public HeapSys: number = 0 // bytes obtained from system for the heap
175
+ public HeapInuse: number = 0 // bytes in in-use heap spans
176
+ public StackInuse: number = 0 // bytes in stack spans
177
+ public StackSys: number = 0 // bytes obtained from system for stacks
173
178
  public Lookups: number = 0 // number of pointer lookups
174
179
  public Mallocs: number = 0 // number of mallocs
175
180
  public Frees: number = 0 // number of frees
@@ -181,25 +186,28 @@ export class MemStats {
181
186
  }
182
187
 
183
188
  private updateMemoryStats(): void {
184
- // Use performance.memory if available (Chrome/Edge)
185
- if (typeof performance !== 'undefined' && (performance as any).memory) {
186
- const mem = (performance as any).memory
187
- this.Alloc = mem.usedJSHeapSize || 0
188
- this.Sys = mem.totalJSHeapSize || 0
189
- this.TotalAlloc = this.Alloc // Simplified
190
- }
189
+ updateMemoryStats(this)
191
190
  }
192
191
  }
193
192
 
194
193
  // ReadMemStats populates m with memory allocator statistics
195
- export function ReadMemStats(m: MemStats): void {
196
- // Update the provided MemStats object with current values
194
+ export function ReadMemStats(m: MemStats | $.VarRef<MemStats> | null): void {
195
+ m = $.pointerValue<MemStats>(m)
196
+ updateMemoryStats(m)
197
+ }
198
+
199
+ function updateMemoryStats(m: MemStats): void {
197
200
  if (typeof performance !== 'undefined' && (performance as any).memory) {
198
201
  const mem = (performance as any).memory
199
202
  m.Alloc = mem.usedJSHeapSize || 0
200
203
  m.Sys = mem.totalJSHeapSize || 0
201
204
  m.TotalAlloc = m.Alloc // Simplified
202
205
  }
206
+ m.HeapAlloc = m.Alloc
207
+ m.HeapSys = m.Sys
208
+ m.HeapInuse = m.Alloc
209
+ m.StackInuse = 0
210
+ m.StackSys = 0
203
211
  }
204
212
 
205
213
  // Error interface for runtime errors
@@ -12,8 +12,69 @@ import {
12
12
  WithRegion,
13
13
  } from './index.js'
14
14
 
15
+ function captureWriter(): { chunks: Uint8Array[]; bytes(): Uint8Array } {
16
+ const chunks: Uint8Array[] = []
17
+ return {
18
+ chunks,
19
+ bytes(): Uint8Array {
20
+ const total = chunks.reduce((n, c) => n + c.length, 0)
21
+ const out = new Uint8Array(total)
22
+ let off = 0
23
+ for (const c of chunks) {
24
+ out.set(c, off)
25
+ off += c.length
26
+ }
27
+ return out
28
+ },
29
+ }
30
+ }
31
+
32
+ const writerOf = (chunks: Uint8Array[]) => ({
33
+ Write(p: Uint8Array): [number, null] {
34
+ chunks.push(new Uint8Array(p))
35
+ return [p.length, null]
36
+ },
37
+ })
38
+
39
+ // maxBatchSize mirrors the trace v2 reader limit (tracev2.MaxBatchSize, 64<<10).
40
+ // A batch whose declared data length exceeds this is rejected with "invalid
41
+ // batch size", so the encoder must split large captures across batches.
42
+ const maxBatchSize = 64 * 1024
43
+
44
+ // batchSizes walks the trace v2 stream past the 16-byte header and returns the
45
+ // declared data length of every EvEventBatch frame. Each frame is the batch
46
+ // kind byte (1), then uvarints for generation, M id, base timestamp, and data
47
+ // length, then the data block.
48
+ function batchSizes(bytes: Uint8Array): number[] {
49
+ const sizes: number[] = []
50
+ let i = 16 // header: "go 1.22 trace\x00\x00\x00"
51
+ const readUvarint = (): number => {
52
+ let value = 0
53
+ let shift = 0
54
+ for (;;) {
55
+ const b = bytes[i++]
56
+ value += (b & 0x7f) * 2 ** shift
57
+ if ((b & 0x80) === 0) {
58
+ break
59
+ }
60
+ shift += 7
61
+ }
62
+ return value
63
+ }
64
+ while (i < bytes.length) {
65
+ i++ // EvEventBatch kind byte
66
+ readUvarint() // generation
67
+ readUvarint() // M id
68
+ readUvarint() // base timestamp
69
+ const size = readUvarint()
70
+ sizes.push(size)
71
+ i += size
72
+ }
73
+ return sizes
74
+ }
75
+
15
76
  describe('runtime/trace override', () => {
16
- it('creates no-op tasks from nil context', () => {
77
+ it('creates no-op tasks when tracing is disabled', () => {
17
78
  const [ctx, task] = NewTask(null, 'task')
18
79
 
19
80
  expect(ctx).not.toBeNull()
@@ -23,7 +84,7 @@ describe('runtime/trace override', () => {
23
84
  expect(IsEnabled()).toBe(false)
24
85
  })
25
86
 
26
- it('runs no-op regions', () => {
87
+ it('runs regions', () => {
27
88
  const [ctx] = NewTask(context.Background(), 'task')
28
89
  const called = { value: false }
29
90
 
@@ -35,21 +96,59 @@ describe('runtime/trace override', () => {
35
96
  expect(called.value).toBe(true)
36
97
  })
37
98
 
38
- it('reports execution tracing as unsupported', () => {
39
- const chunks: Uint8Array[] = []
40
- const writer = {
41
- Write(p: Uint8Array): [number, null] {
42
- chunks.push(new Uint8Array(p))
43
- return [p.length, null]
44
- },
99
+ it('rejects a nil trace writer', () => {
100
+ expect(Start(null)?.Error()).toBe('runtime/trace: nil trace writer')
101
+ })
102
+
103
+ it('emits a trace v2 stream for user tasks', () => {
104
+ const capture = captureWriter()
105
+
106
+ expect(Start(writerOf(capture.chunks))).toBeNull()
107
+ expect(IsEnabled()).toBe(true)
108
+
109
+ const [ctx, task] = NewTask(context.Background(), 'proof-task')
110
+ WithRegion(ctx, 'proof-region', () => {
111
+ Log(ctx, 'proof-key', 'proof-value')
112
+ })
113
+ task.End()
114
+
115
+ Stop()
116
+ expect(IsEnabled()).toBe(false)
117
+
118
+ const bytes = capture.bytes()
119
+ expect(bytes.length).toBeGreaterThan(0)
120
+
121
+ const header = new TextDecoder().decode(bytes.slice(0, 13))
122
+ expect(header).toBe('go 1.22 trace')
123
+
124
+ // The interned task and region names appear in the string batch.
125
+ const text = new TextDecoder('latin1').decode(bytes)
126
+ expect(text).toContain('proof-task')
127
+ expect(text).toContain('proof-region')
128
+ expect(text).toContain('proof-value')
129
+ })
130
+
131
+ it('splits a large capture into batches under the size limit', () => {
132
+ const capture = captureWriter()
133
+
134
+ expect(Start(writerOf(capture.chunks))).toBeNull()
135
+
136
+ // Record enough distinct tasks and logs to overflow the single-batch ceiling
137
+ // in both the string dictionary and the event stream.
138
+ const eventCount = 4000
139
+ for (let i = 0; i < eventCount; i++) {
140
+ const [ctx, task] = NewTask(context.Background(), `multibatch-task-${i}`)
141
+ Log(ctx, 'multibatch-key', `multibatch-message-${i}`)
142
+ task.End()
45
143
  }
46
144
 
47
- expect(Start(writer)?.Error()).toBe(
48
- 'runtime/trace: execution tracing is unsupported in GoScript',
49
- )
50
- Log(context.Background(), 'category', 'message')
51
145
  Stop()
52
146
 
53
- expect(chunks).toHaveLength(0)
147
+ const sizes = batchSizes(capture.bytes())
148
+ // Frequency, plus several string and event batches.
149
+ expect(sizes.length).toBeGreaterThan(3)
150
+ for (const size of sizes) {
151
+ expect(size).toBeLessThanOrEqual(maxBatchSize)
152
+ }
54
153
  })
55
154
  })