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.
- package/README.md +8 -8
- package/cmd/go_js_wasm_exec/main.go +1 -1
- package/cmd/go_js_wasm_exec/main_test.go +1 -1
- package/cmd/goscript/cmd-compile.go +9 -1
- package/cmd/goscript/cmd-test.go +1 -1
- package/cmd/goscript/cmd_compile_test.go +44 -0
- package/cmd/goscript/deps.go +1 -1
- package/cmd/goscript-wasm/main.go +2 -2
- package/compiler/compile-request.go +19 -0
- package/compiler/compile_bench_test.go +121 -0
- package/compiler/compliance_test.go +17 -1
- package/compiler/config.go +2 -0
- package/compiler/gotest/result.go +1 -1
- package/compiler/gotest/runner.go +2 -2
- package/compiler/gotest/runner_test.go +4 -7
- package/compiler/index.test.ts +28 -0
- package/compiler/index.ts +32 -16
- package/compiler/lowering.go +1236 -143
- package/compiler/lowering_bench_test.go +4 -0
- package/compiler/override-facts.go +1 -1
- package/compiler/override-registry_test.go +125 -0
- package/compiler/package-graph.go +92 -0
- package/compiler/package-graph_test.go +113 -0
- package/compiler/runtime-contract.go +1 -1
- package/compiler/semantic-model.go +32 -0
- package/compiler/skeleton_test.go +284 -11
- package/compiler/wasm/compile.go +1 -1
- package/compiler/wasm/compile_test.go +1 -1
- package/dist/compiler/index.d.ts +4 -0
- package/dist/compiler/index.js +26 -15
- package/dist/compiler/index.js.map +1 -1
- package/dist/gs/compress/gzip/index.d.ts +41 -0
- package/dist/gs/compress/gzip/index.js +235 -0
- package/dist/gs/compress/gzip/index.js.map +1 -0
- package/dist/gs/database/sql/driver/index.d.ts +165 -0
- package/dist/gs/database/sql/driver/index.js +432 -0
- package/dist/gs/database/sql/driver/index.js.map +1 -0
- package/dist/gs/encoding/binary/index.d.ts +71 -0
- package/dist/gs/encoding/binary/index.js +778 -0
- package/dist/gs/encoding/binary/index.js.map +1 -0
- package/dist/gs/fmt/fmt.js +156 -57
- package/dist/gs/fmt/fmt.js.map +1 -1
- package/dist/gs/github.com/klauspost/cpuid/v2/index.d.ts +11 -0
- package/dist/gs/github.com/klauspost/cpuid/v2/index.js +28 -0
- package/dist/gs/github.com/klauspost/cpuid/v2/index.js.map +1 -0
- package/dist/gs/github.com/pkg/errors/errors.d.ts +0 -2
- package/dist/gs/github.com/pkg/errors/errors.js.map +1 -1
- package/dist/gs/github.com/pkg/errors/index.d.ts +2 -1
- package/dist/gs/github.com/pkg/errors/index.js +1 -1
- package/dist/gs/github.com/pkg/errors/index.js.map +1 -1
- package/dist/gs/github.com/pkg/errors/stack.d.ts +8 -19
- package/dist/gs/github.com/pkg/errors/stack.js +26 -61
- package/dist/gs/github.com/pkg/errors/stack.js.map +1 -1
- package/dist/gs/golang.org/x/crypto/cryptobyte/asn1/index.d.ts +19 -0
- package/dist/gs/golang.org/x/crypto/cryptobyte/asn1/index.js +25 -0
- package/dist/gs/golang.org/x/crypto/cryptobyte/asn1/index.js.map +1 -0
- package/dist/gs/golang.org/x/crypto/cryptobyte/index.d.ts +104 -0
- package/dist/gs/golang.org/x/crypto/cryptobyte/index.js +1107 -0
- package/dist/gs/golang.org/x/crypto/cryptobyte/index.js.map +1 -0
- package/dist/gs/golang.org/x/crypto/internal/alias/index.d.ts +3 -0
- package/dist/gs/golang.org/x/crypto/internal/alias/index.js +39 -0
- package/dist/gs/golang.org/x/crypto/internal/alias/index.js.map +1 -0
- package/dist/gs/io/fs/glob.js +1 -1
- package/dist/gs/io/fs/glob.js.map +1 -1
- package/dist/gs/io/fs/readlink.d.ts +1 -1
- package/dist/gs/io/fs/readlink.js +2 -2
- package/dist/gs/io/fs/readlink.js.map +1 -1
- package/dist/gs/io/fs/stat.d.ts +4 -2
- package/dist/gs/io/fs/stat.js +12 -73
- package/dist/gs/io/fs/stat.js.map +1 -1
- package/dist/gs/io/fs/sub.d.ts +2 -2
- package/dist/gs/io/fs/sub.js +7 -7
- package/dist/gs/io/fs/sub.js.map +1 -1
- package/dist/gs/io/fs/walk.js +1 -1
- package/dist/gs/io/fs/walk.js.map +1 -1
- package/dist/gs/net/http/index.d.ts +18 -14
- package/dist/gs/net/http/index.js +44 -23
- package/dist/gs/net/http/index.js.map +1 -1
- package/dist/gs/net/http/pprof/index.d.ts +5 -5
- package/dist/gs/net/http/pprof/index.js +21 -21
- package/dist/gs/net/http/pprof/index.js.map +1 -1
- package/dist/gs/runtime/runtime.d.ts +6 -1
- package/dist/gs/runtime/runtime.js +15 -8
- package/dist/gs/runtime/runtime.js.map +1 -1
- package/dist/gs/runtime/trace/index.d.ts +8 -5
- package/dist/gs/runtime/trace/index.js +324 -23
- package/dist/gs/runtime/trace/index.js.map +1 -1
- package/dist/gs/slices/slices.d.ts +2 -1
- package/dist/gs/slices/slices.js +9 -3
- package/dist/gs/slices/slices.js.map +1 -1
- package/dist/gs/sort/search.gs.d.ts +3 -1
- package/dist/gs/sort/search.gs.js +18 -53
- package/dist/gs/sort/search.gs.js.map +1 -1
- package/dist/gs/sync/sync.d.ts +1 -1
- package/dist/gs/sync/sync.js +3 -0
- package/dist/gs/sync/sync.js.map +1 -1
- package/dist/gs/time/time.d.ts +22 -29
- package/dist/gs/time/time.js +111 -32
- package/dist/gs/time/time.js.map +1 -1
- package/dist/gs/unsafe/unsafe.d.ts +3 -2
- package/dist/gs/unsafe/unsafe.js.map +1 -1
- package/go.mod +7 -5
- package/go.sum +12 -26
- package/gs/builtin/runtime-contract.test.ts +25 -0
- package/gs/compress/gzip/index.test.ts +86 -0
- package/gs/compress/gzip/index.ts +297 -0
- package/gs/compress/gzip/meta.json +6 -0
- package/gs/compress/gzip/parity.json +45 -0
- package/gs/database/sql/driver/index.test.ts +88 -0
- package/gs/database/sql/driver/index.ts +675 -0
- package/gs/database/sql/driver/meta.json +3 -0
- package/gs/database/sql/driver/parity.json +144 -0
- package/gs/embed/index.test.ts +1 -1
- package/gs/encoding/binary/index.test.ts +239 -0
- package/gs/encoding/binary/index.ts +999 -0
- package/gs/encoding/binary/meta.json +9 -0
- package/gs/encoding/binary/parity.json +72 -0
- package/gs/fmt/fmt.test.ts +28 -0
- package/gs/fmt/fmt.ts +198 -61
- package/gs/fmt/meta.json +2 -1
- package/gs/github.com/klauspost/cpuid/v2/index.ts +38 -0
- package/gs/github.com/klauspost/cpuid/v2/meta.json +3 -0
- package/gs/github.com/pkg/errors/errors.ts +1 -2
- package/gs/github.com/pkg/errors/index.ts +2 -1
- package/gs/github.com/pkg/errors/stack.ts +34 -62
- package/gs/golang.org/x/crypto/cryptobyte/asn1/index.test.ts +19 -0
- package/gs/golang.org/x/crypto/cryptobyte/asn1/index.ts +29 -0
- package/gs/golang.org/x/crypto/cryptobyte/index.test.ts +255 -0
- package/gs/golang.org/x/crypto/cryptobyte/index.ts +1441 -0
- package/gs/golang.org/x/crypto/cryptobyte/meta.json +3 -0
- package/gs/golang.org/x/crypto/internal/alias/index.test.ts +40 -0
- package/gs/golang.org/x/crypto/internal/alias/index.ts +40 -0
- package/gs/io/fs/glob.ts +1 -1
- package/gs/io/fs/meta.json +3 -0
- package/gs/io/fs/readlink.test.ts +2 -2
- package/gs/io/fs/readlink.ts +5 -2
- package/gs/io/fs/stat.test.ts +79 -0
- package/gs/io/fs/stat.ts +24 -10
- package/gs/io/fs/sub.test.ts +93 -0
- package/gs/io/fs/sub.ts +9 -9
- package/gs/io/fs/walk.ts +1 -1
- package/gs/net/http/index.test.ts +207 -2
- package/gs/net/http/index.ts +68 -37
- package/gs/net/http/meta.json +3 -1
- package/gs/net/http/pprof/index.test.ts +4 -4
- package/gs/net/http/pprof/index.ts +30 -27
- package/gs/runtime/runtime.test.ts +16 -0
- package/gs/runtime/runtime.ts +17 -9
- package/gs/runtime/trace/index.test.ts +113 -14
- package/gs/runtime/trace/index.ts +384 -34
- package/gs/runtime/trace/meta.json +1 -0
- package/gs/slices/slices.test.ts +24 -1
- package/gs/slices/slices.ts +14 -4
- package/gs/sort/meta.json +1 -0
- package/gs/sort/search.gs.ts +20 -5
- package/gs/sync/sync.ts +4 -1
- package/gs/time/time.test.ts +79 -2
- package/gs/time/time.ts +133 -33
- package/gs/unsafe/unsafe.ts +4 -2
- package/package.json +2 -2
|
@@ -1,17 +1,132 @@
|
|
|
1
1
|
import * as $ from '@goscript/builtin/index.js'
|
|
2
2
|
import * as context from '@goscript/context/index.js'
|
|
3
3
|
import * as errors from '@goscript/errors/index.js'
|
|
4
|
+
import * as fmt from '@goscript/fmt/index.js'
|
|
4
5
|
import * as io from '@goscript/io/index.js'
|
|
5
6
|
|
|
7
|
+
// traceContextKey keys the active *Task stored in a context by NewTask.
|
|
6
8
|
class traceContextKey {}
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
// Go execution trace v2 event type codes (iota order from the runtime spec).
|
|
11
|
+
// GoScript emits only the user-task subset plus the minimal proc/goroutine
|
|
12
|
+
// status needed for the upstream trace reader to bind a running context.
|
|
13
|
+
const evEventBatch = 1
|
|
14
|
+
const evStrings = 4
|
|
15
|
+
const evString = 5
|
|
16
|
+
const evFrequency = 8
|
|
17
|
+
const evProcStatus = 13
|
|
18
|
+
const evGoStatus = 25
|
|
19
|
+
const evUserTaskBegin = 40
|
|
20
|
+
const evUserTaskEnd = 41
|
|
21
|
+
const evUserRegionBegin = 42
|
|
22
|
+
const evUserRegionEnd = 43
|
|
23
|
+
const evUserLog = 44
|
|
24
|
+
|
|
25
|
+
// procRunning and goRunning are the running status codes for the synthetic P
|
|
26
|
+
// and G that own every GoScript user event (GoScript has no real scheduler).
|
|
27
|
+
const procRunning = 1
|
|
28
|
+
const goRunning = 2
|
|
29
|
+
|
|
30
|
+
// traceGen, traceProc, and traceGoroutine are the fixed generation, P, and G
|
|
31
|
+
// identities for the single synthetic GoScript execution context.
|
|
32
|
+
const traceGen = 1
|
|
33
|
+
const traceMID = 0
|
|
34
|
+
const traceProc = 0
|
|
35
|
+
const traceGoroutine = 1
|
|
36
|
+
|
|
37
|
+
// traceFreqHz is the timestamp frequency reported to the reader. GoScript
|
|
38
|
+
// records nanosecond timestamps, so the frequency is one billion ticks/sec
|
|
39
|
+
// and the reader's tick->nanosecond conversion is the identity.
|
|
40
|
+
const traceFreqHz = 1_000_000_000
|
|
41
|
+
|
|
42
|
+
// maxBatchData bounds the post-header data block of every per-M batch. The
|
|
43
|
+
// trace v2 reader rejects any batch whose declared size exceeds 64 KiB
|
|
44
|
+
// (tracev2.MaxBatchSize = 64<<10), so a realistic capture must split its string
|
|
45
|
+
// and event records across multiple batches. The cap sits below the hard limit
|
|
46
|
+
// to leave margin for the next record, which is written in full before the size
|
|
47
|
+
// is rechecked.
|
|
48
|
+
const maxBatchData = 60 * 1024
|
|
49
|
+
|
|
50
|
+
// maxEventBytes is a safe upper bound on the encoded size of one user event
|
|
51
|
+
// (kind byte plus a handful of base-128 varints; strings are interned, so no
|
|
52
|
+
// inline payload). The event loop flushes the current batch before its data
|
|
53
|
+
// block can grow within this margin of maxBatchData.
|
|
54
|
+
const maxEventBytes = 128
|
|
55
|
+
|
|
56
|
+
// recEvent kinds.
|
|
57
|
+
const kindTaskBegin = 0
|
|
58
|
+
const kindTaskEnd = 1
|
|
59
|
+
const kindRegionBegin = 2
|
|
60
|
+
const kindRegionEnd = 3
|
|
61
|
+
const kindLog = 4
|
|
62
|
+
|
|
63
|
+
// recEvent is one buffered user trace event with its monotonic timestamp.
|
|
64
|
+
interface recEvent {
|
|
65
|
+
kind: number
|
|
66
|
+
ts: number
|
|
67
|
+
task: number
|
|
68
|
+
parent: number
|
|
69
|
+
s0: string
|
|
70
|
+
s1: string
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// recorder buffers user trace events between Start and Stop. GoScript has no
|
|
74
|
+
// streaming runtime, so events accumulate in memory and the full trace v2 byte
|
|
75
|
+
// stream is written to the output writer on Stop.
|
|
76
|
+
interface recorder {
|
|
77
|
+
enabled: boolean
|
|
78
|
+
writer: io.Writer | null
|
|
79
|
+
startTs: number
|
|
80
|
+
events: recEvent[]
|
|
10
81
|
}
|
|
11
82
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
83
|
+
function newRecorder(): recorder {
|
|
84
|
+
return { enabled: false, writer: null, startTs: 0, events: [] }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// rec is the active capture. nextTaskID is process-global and monotonic like
|
|
88
|
+
// the Go runtime task counter, so it persists across Start/Stop cycles.
|
|
89
|
+
let rec = newRecorder()
|
|
90
|
+
let nextTaskID = 0
|
|
91
|
+
|
|
92
|
+
// nowNs reads the high-resolution monotonic clock in nanoseconds, the same
|
|
93
|
+
// source that backs time.Time monotonic readings.
|
|
94
|
+
function nowNs(): number {
|
|
95
|
+
if (typeof performance !== 'undefined' && performance.now) {
|
|
96
|
+
return performance.now() * 1_000_000
|
|
97
|
+
}
|
|
98
|
+
return globalThis.Date.now() * 1_000_000
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function contextKey(): any {
|
|
102
|
+
return $.interfaceValue(
|
|
103
|
+
$.markAsStructValue(new traceContextKey()),
|
|
104
|
+
'trace.traceContextKey',
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// taskIDFromContext returns the active task ID carried by ctx, or 0 when ctx
|
|
109
|
+
// carries no task (the background task).
|
|
110
|
+
function taskIDFromContext(ctx: context.Context | null): number {
|
|
111
|
+
if (ctx == null) {
|
|
112
|
+
return 0
|
|
113
|
+
}
|
|
114
|
+
const value = ctx.Value(contextKey())
|
|
115
|
+
if (value instanceof Task) {
|
|
116
|
+
return value.id
|
|
117
|
+
}
|
|
118
|
+
return 0
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function record(
|
|
122
|
+
kind: number,
|
|
123
|
+
task: number,
|
|
124
|
+
parent: number,
|
|
125
|
+
s0: string,
|
|
126
|
+
s1: string,
|
|
127
|
+
): void {
|
|
128
|
+
rec.events.push({ kind, ts: nowNs(), task, parent, s0, s1 })
|
|
129
|
+
}
|
|
15
130
|
|
|
16
131
|
export class Task {
|
|
17
132
|
public id: number
|
|
@@ -20,62 +135,93 @@ export class Task {
|
|
|
20
135
|
this.id = id
|
|
21
136
|
}
|
|
22
137
|
|
|
23
|
-
public End(): void {
|
|
138
|
+
public End(): void {
|
|
139
|
+
if (rec.enabled) {
|
|
140
|
+
record(kindTaskEnd, this.id, 0, '', '')
|
|
141
|
+
}
|
|
142
|
+
}
|
|
24
143
|
}
|
|
25
144
|
|
|
26
145
|
export class Region {
|
|
27
|
-
|
|
146
|
+
private task: number
|
|
147
|
+
private name: string
|
|
148
|
+
|
|
149
|
+
constructor(task = 0, name = '') {
|
|
150
|
+
this.task = task
|
|
151
|
+
this.name = name
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
public End(): void {
|
|
155
|
+
if (rec.enabled) {
|
|
156
|
+
record(kindRegionEnd, this.task, 0, this.name, '')
|
|
157
|
+
}
|
|
158
|
+
}
|
|
28
159
|
}
|
|
29
160
|
|
|
30
161
|
export function NewTask(
|
|
31
162
|
pctx: context.Context | null,
|
|
32
|
-
|
|
163
|
+
taskType: string,
|
|
33
164
|
): [context.Context | null, Task] {
|
|
34
165
|
const parent = pctx ?? context.Background()
|
|
35
|
-
const task = new Task(++
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
$.interfaceValue(task, '*trace.Task'),
|
|
41
|
-
),
|
|
42
|
-
task,
|
|
43
|
-
]
|
|
166
|
+
const task = new Task(++nextTaskID)
|
|
167
|
+
if (rec.enabled) {
|
|
168
|
+
record(kindTaskBegin, task.id, taskIDFromContext(pctx), taskType, '')
|
|
169
|
+
}
|
|
170
|
+
return [context.WithValue(parent, contextKey(), task), task]
|
|
44
171
|
}
|
|
45
172
|
|
|
46
173
|
export function Log(
|
|
47
|
-
|
|
174
|
+
ctx: context.Context | null,
|
|
48
175
|
category: string,
|
|
49
176
|
message: string,
|
|
50
177
|
): void {
|
|
51
|
-
|
|
52
|
-
|
|
178
|
+
if (rec.enabled) {
|
|
179
|
+
record(kindLog, taskIDFromContext(ctx), 0, category, message)
|
|
180
|
+
}
|
|
53
181
|
}
|
|
54
182
|
|
|
55
183
|
export function Logf(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
...
|
|
60
|
-
): void {
|
|
184
|
+
ctx: context.Context | null,
|
|
185
|
+
category: string,
|
|
186
|
+
format: string,
|
|
187
|
+
...args: unknown[]
|
|
188
|
+
): void {
|
|
189
|
+
if (rec.enabled) {
|
|
190
|
+
Log(ctx, category, fmt.Sprintf(format, ...(args as unknown[])))
|
|
191
|
+
}
|
|
192
|
+
}
|
|
61
193
|
|
|
62
194
|
export function WithRegion(
|
|
63
|
-
|
|
64
|
-
|
|
195
|
+
ctx: context.Context | null,
|
|
196
|
+
regionType: string,
|
|
65
197
|
fn: (() => void) | null,
|
|
66
198
|
): void {
|
|
67
|
-
|
|
199
|
+
const task = taskIDFromContext(ctx)
|
|
200
|
+
if (rec.enabled) {
|
|
201
|
+
record(kindRegionBegin, task, 0, regionType, '')
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
fn?.()
|
|
205
|
+
} finally {
|
|
206
|
+
if (rec.enabled) {
|
|
207
|
+
record(kindRegionEnd, task, 0, regionType, '')
|
|
208
|
+
}
|
|
209
|
+
}
|
|
68
210
|
}
|
|
69
211
|
|
|
70
212
|
export function StartRegion(
|
|
71
|
-
|
|
72
|
-
|
|
213
|
+
ctx: context.Context | null,
|
|
214
|
+
regionType: string,
|
|
73
215
|
): Region {
|
|
74
|
-
|
|
216
|
+
const task = taskIDFromContext(ctx)
|
|
217
|
+
if (rec.enabled) {
|
|
218
|
+
record(kindRegionBegin, task, 0, regionType, '')
|
|
219
|
+
}
|
|
220
|
+
return new Region(task, regionType)
|
|
75
221
|
}
|
|
76
222
|
|
|
77
223
|
export function IsEnabled(): boolean {
|
|
78
|
-
return
|
|
224
|
+
return rec.enabled
|
|
79
225
|
}
|
|
80
226
|
|
|
81
227
|
export function Start(w: io.Writer | $.VarRef<io.Writer> | null): $.GoError {
|
|
@@ -83,7 +229,211 @@ export function Start(w: io.Writer | $.VarRef<io.Writer> | null): $.GoError {
|
|
|
83
229
|
if (writer == null) {
|
|
84
230
|
return errors.New('runtime/trace: nil trace writer')
|
|
85
231
|
}
|
|
86
|
-
|
|
232
|
+
if (rec.enabled) {
|
|
233
|
+
return errors.New('runtime/trace: tracing already enabled')
|
|
234
|
+
}
|
|
235
|
+
rec = newRecorder()
|
|
236
|
+
rec.enabled = true
|
|
237
|
+
rec.writer = writer
|
|
238
|
+
rec.startTs = nowNs()
|
|
239
|
+
return null
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function Stop(): void {
|
|
243
|
+
if (!rec.enabled) {
|
|
244
|
+
return
|
|
245
|
+
}
|
|
246
|
+
const writer = rec.writer
|
|
247
|
+
const buffered = rec
|
|
248
|
+
rec = newRecorder()
|
|
249
|
+
const payload = encodeTrace(buffered)
|
|
250
|
+
if (writer != null) {
|
|
251
|
+
writer.Write(payload)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// putUvarint appends value to out as a base-128 varint. It uses arithmetic
|
|
256
|
+
// rather than bit operations so values above 2^32 (nanosecond timestamps)
|
|
257
|
+
// encode correctly.
|
|
258
|
+
function putUvarint(out: number[], value: number): void {
|
|
259
|
+
let v = Math.floor(value)
|
|
260
|
+
if (v < 0) {
|
|
261
|
+
v = 0
|
|
262
|
+
}
|
|
263
|
+
while (v >= 0x80) {
|
|
264
|
+
out.push((v % 0x80) + 0x80)
|
|
265
|
+
v = Math.floor(v / 0x80)
|
|
266
|
+
}
|
|
267
|
+
out.push(v)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function utf8Bytes(s: string): Uint8Array {
|
|
271
|
+
if (typeof TextEncoder !== 'undefined') {
|
|
272
|
+
return new TextEncoder().encode(s)
|
|
273
|
+
}
|
|
274
|
+
const out = new Uint8Array(s.length)
|
|
275
|
+
for (let i = 0; i < s.length; i++) {
|
|
276
|
+
out[i] = s.charCodeAt(i) & 0xff
|
|
277
|
+
}
|
|
278
|
+
return out
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// appendBatch frames data as a trace v2 per-M batch (EvEventBatch header plus
|
|
282
|
+
// the generation, M, base timestamp, and length prefix).
|
|
283
|
+
function appendBatch(out: number[], data: number[]): void {
|
|
284
|
+
out.push(evEventBatch)
|
|
285
|
+
putUvarint(out, traceGen)
|
|
286
|
+
putUvarint(out, traceMID)
|
|
287
|
+
putUvarint(out, 0)
|
|
288
|
+
putUvarint(out, data.length)
|
|
289
|
+
for (const b of data) {
|
|
290
|
+
out.push(b)
|
|
291
|
+
}
|
|
87
292
|
}
|
|
88
293
|
|
|
89
|
-
|
|
294
|
+
// encodeTrace serializes the buffered user events as a single-generation Go
|
|
295
|
+
// trace v2 byte stream containing only the user-task subset.
|
|
296
|
+
function encodeTrace(r: recorder): Uint8Array {
|
|
297
|
+
const out: number[] = []
|
|
298
|
+
|
|
299
|
+
// Header: "go 1.22 trace\x00\x00\x00". The reader selects the Go 1.22 event
|
|
300
|
+
// table, whose final event is EvUserLog, covering every event GoScript emits.
|
|
301
|
+
const header = 'go 1.22 trace\x00\x00\x00'
|
|
302
|
+
for (let i = 0; i < header.length; i++) {
|
|
303
|
+
out.push(header.charCodeAt(i) & 0xff)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Frequency batch (required): ticks per second.
|
|
307
|
+
const freq: number[] = [evFrequency]
|
|
308
|
+
putUvarint(freq, traceFreqHz)
|
|
309
|
+
appendBatch(out, freq)
|
|
310
|
+
|
|
311
|
+
// Intern the strings referenced by user events.
|
|
312
|
+
const stringIDs = new Map<string, number>()
|
|
313
|
+
const internedStrings: string[] = []
|
|
314
|
+
const intern = (s: string): number => {
|
|
315
|
+
let id = stringIDs.get(s)
|
|
316
|
+
if (id === undefined) {
|
|
317
|
+
id = internedStrings.length + 1
|
|
318
|
+
stringIDs.set(s, id)
|
|
319
|
+
internedStrings.push(s)
|
|
320
|
+
}
|
|
321
|
+
return id
|
|
322
|
+
}
|
|
323
|
+
for (const ev of r.events) {
|
|
324
|
+
if (
|
|
325
|
+
ev.kind === kindTaskBegin ||
|
|
326
|
+
ev.kind === kindRegionBegin ||
|
|
327
|
+
ev.kind === kindRegionEnd
|
|
328
|
+
) {
|
|
329
|
+
intern(ev.s0)
|
|
330
|
+
} else if (ev.kind === kindLog) {
|
|
331
|
+
intern(ev.s0)
|
|
332
|
+
intern(ev.s1)
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Strings batches. The reader concatenates the string dictionary across every
|
|
337
|
+
// EvStrings batch in the generation, so a large table splits into multiple
|
|
338
|
+
// batches whose data blocks each stay under maxBatchData. Each EvString record
|
|
339
|
+
// (tag, id, length, bytes) stays whole within one batch.
|
|
340
|
+
if (internedStrings.length > 0) {
|
|
341
|
+
let strings: number[] = [evStrings]
|
|
342
|
+
for (let i = 0; i < internedStrings.length; i++) {
|
|
343
|
+
const bytes = utf8Bytes(internedStrings[i])
|
|
344
|
+
const recordSize = 1 + 10 + 10 + bytes.length
|
|
345
|
+
if (strings.length > 1 && strings.length + recordSize > maxBatchData) {
|
|
346
|
+
appendBatch(out, strings)
|
|
347
|
+
strings = [evStrings]
|
|
348
|
+
}
|
|
349
|
+
strings.push(evString)
|
|
350
|
+
putUvarint(strings, i + 1)
|
|
351
|
+
putUvarint(strings, bytes.length)
|
|
352
|
+
for (const b of bytes) {
|
|
353
|
+
strings.push(b)
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
appendBatch(out, strings)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Event batches: synthetic running P and G, then any user events in order. The
|
|
360
|
+
// running context is emitted even with no user events, so every capture is a
|
|
361
|
+
// complete single-generation trace the reader accepts rather than a bare
|
|
362
|
+
// header plus frequency batch. User events split across multiple per-M batches
|
|
363
|
+
// so no data block exceeds maxBatchData. The reader resets its delta clock to
|
|
364
|
+
// each batch's base timestamp (0 here), so flushing resets lastTs to 0 and the
|
|
365
|
+
// first event of every batch re-emits its absolute offset as the delta.
|
|
366
|
+
let data: number[] = []
|
|
367
|
+
let lastTs = 0
|
|
368
|
+
const emitDelta = (ts: number): void => {
|
|
369
|
+
const offset = Math.max(0, Math.floor(ts - r.startTs))
|
|
370
|
+
const dt = Math.max(0, offset - lastTs)
|
|
371
|
+
lastTs = offset
|
|
372
|
+
putUvarint(data, dt)
|
|
373
|
+
}
|
|
374
|
+
const flushEventBatch = (): void => {
|
|
375
|
+
appendBatch(out, data)
|
|
376
|
+
data = []
|
|
377
|
+
lastTs = 0
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// EvProcStatus(dt, p, status): bind the running P to the context.
|
|
381
|
+
data.push(evProcStatus)
|
|
382
|
+
putUvarint(data, 0)
|
|
383
|
+
putUvarint(data, traceProc)
|
|
384
|
+
putUvarint(data, procRunning)
|
|
385
|
+
|
|
386
|
+
// EvGoStatus(dt, g, m, status): bind the running G to the context.
|
|
387
|
+
data.push(evGoStatus)
|
|
388
|
+
putUvarint(data, 0)
|
|
389
|
+
putUvarint(data, traceGoroutine)
|
|
390
|
+
putUvarint(data, traceMID)
|
|
391
|
+
putUvarint(data, goRunning)
|
|
392
|
+
|
|
393
|
+
for (const ev of r.events) {
|
|
394
|
+
if (data.length + maxEventBytes > maxBatchData) {
|
|
395
|
+
flushEventBatch()
|
|
396
|
+
}
|
|
397
|
+
switch (ev.kind) {
|
|
398
|
+
case kindTaskBegin:
|
|
399
|
+
data.push(evUserTaskBegin)
|
|
400
|
+
emitDelta(ev.ts)
|
|
401
|
+
putUvarint(data, ev.task)
|
|
402
|
+
putUvarint(data, ev.parent)
|
|
403
|
+
putUvarint(data, intern(ev.s0))
|
|
404
|
+
putUvarint(data, 0)
|
|
405
|
+
break
|
|
406
|
+
case kindTaskEnd:
|
|
407
|
+
data.push(evUserTaskEnd)
|
|
408
|
+
emitDelta(ev.ts)
|
|
409
|
+
putUvarint(data, ev.task)
|
|
410
|
+
putUvarint(data, 0)
|
|
411
|
+
break
|
|
412
|
+
case kindRegionBegin:
|
|
413
|
+
data.push(evUserRegionBegin)
|
|
414
|
+
emitDelta(ev.ts)
|
|
415
|
+
putUvarint(data, ev.task)
|
|
416
|
+
putUvarint(data, intern(ev.s0))
|
|
417
|
+
putUvarint(data, 0)
|
|
418
|
+
break
|
|
419
|
+
case kindRegionEnd:
|
|
420
|
+
data.push(evUserRegionEnd)
|
|
421
|
+
emitDelta(ev.ts)
|
|
422
|
+
putUvarint(data, ev.task)
|
|
423
|
+
putUvarint(data, intern(ev.s0))
|
|
424
|
+
putUvarint(data, 0)
|
|
425
|
+
break
|
|
426
|
+
case kindLog:
|
|
427
|
+
data.push(evUserLog)
|
|
428
|
+
emitDelta(ev.ts)
|
|
429
|
+
putUvarint(data, ev.task)
|
|
430
|
+
putUvarint(data, intern(ev.s0))
|
|
431
|
+
putUvarint(data, intern(ev.s1))
|
|
432
|
+
putUvarint(data, 0)
|
|
433
|
+
break
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
flushEventBatch()
|
|
437
|
+
|
|
438
|
+
return new Uint8Array(out)
|
|
439
|
+
}
|
package/gs/slices/slices.test.ts
CHANGED
|
@@ -4,6 +4,7 @@ import * as $ from '@goscript/builtin/index.js'
|
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
All,
|
|
7
|
+
AppendSeq,
|
|
7
8
|
Backward,
|
|
8
9
|
BinarySearch,
|
|
9
10
|
Clip,
|
|
@@ -143,7 +144,29 @@ describe('slices.Sorted', () => {
|
|
|
143
144
|
yieldValue('b')
|
|
144
145
|
})
|
|
145
146
|
|
|
146
|
-
expect(values).toEqual(['a', 'b', 'c'])
|
|
147
|
+
expect(Array.from(values ?? [])).toEqual(['a', 'b', 'c'])
|
|
148
|
+
})
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
describe('slices.AppendSeq', () => {
|
|
152
|
+
it('appends iterator values to an existing slice', () => {
|
|
153
|
+
const values = AppendSeq($.arrayToSlice([1]), (yieldValue) => {
|
|
154
|
+
yieldValue(2)
|
|
155
|
+
yieldValue(3)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
expect(Array.from(values ?? [])).toEqual([1, 2, 3])
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('collects into a nil slice and preserves nilness for empty sequences', () => {
|
|
162
|
+
const values = AppendSeq<number>(null, (yieldValue) => {
|
|
163
|
+
yieldValue(4)
|
|
164
|
+
yieldValue(5)
|
|
165
|
+
})
|
|
166
|
+
const empty = AppendSeq<number>(null, () => {})
|
|
167
|
+
|
|
168
|
+
expect(Array.from(values ?? [])).toEqual([4, 5])
|
|
169
|
+
expect(empty).toBeNull()
|
|
147
170
|
})
|
|
148
171
|
})
|
|
149
172
|
|
package/gs/slices/slices.ts
CHANGED
|
@@ -266,13 +266,23 @@ export function MinFunc<T>(x: $.Slice<T>, compare: CompareCallback<T>): T {
|
|
|
266
266
|
return min
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
-
export function Collect<T>(seq: iter.Seq<T>): $.Slice<T> {
|
|
270
|
-
|
|
269
|
+
export function Collect<T>(seq: iter.Seq<T> | null): $.Slice<T> {
|
|
270
|
+
return AppendSeq(null, seq)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function AppendSeq<T>(
|
|
274
|
+
s: $.Slice<T>,
|
|
275
|
+
seq: iter.Seq<T> | null,
|
|
276
|
+
): $.Slice<T> {
|
|
277
|
+
if (seq == null) {
|
|
278
|
+
throw new Error('slices: nil iterator')
|
|
279
|
+
}
|
|
280
|
+
let out = s
|
|
271
281
|
seq((value: T) => {
|
|
272
|
-
out
|
|
282
|
+
out = $.append(out, value)
|
|
273
283
|
return true
|
|
274
284
|
})
|
|
275
|
-
return out
|
|
285
|
+
return out
|
|
276
286
|
}
|
|
277
287
|
|
|
278
288
|
export function Sorted<T extends string | number>(
|
package/gs/sort/meta.json
CHANGED
package/gs/sort/search.gs.ts
CHANGED
|
@@ -49,7 +49,23 @@ import * as $ from "@goscript/builtin/index.js";
|
|
|
49
49
|
// })
|
|
50
50
|
// fmt.Printf("Your number is %d.\n", answer)
|
|
51
51
|
// }
|
|
52
|
-
|
|
52
|
+
type SearchPredicate = (i: number) => boolean | Promise<boolean>
|
|
53
|
+
|
|
54
|
+
export async function Search(n: number, f: SearchPredicate): Promise<number> {
|
|
55
|
+
let left = 0
|
|
56
|
+
let right = n
|
|
57
|
+
while (left < right) {
|
|
58
|
+
const mid = Math.floor((left + right) / 2)
|
|
59
|
+
if (await f(mid)) {
|
|
60
|
+
right = mid
|
|
61
|
+
} else {
|
|
62
|
+
left = mid + 1
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return left
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function searchSync(n: number, f: (i: number) => boolean): number {
|
|
53
69
|
let left = 0
|
|
54
70
|
let right = n
|
|
55
71
|
while (left < right) {
|
|
@@ -107,7 +123,7 @@ export function Find(n: number, cmp: (i: number) => number): [number, boolean] {
|
|
|
107
123
|
// not present (it could be len(a)).
|
|
108
124
|
// The slice must be sorted in ascending order.
|
|
109
125
|
export function SearchInts(a: $.Slice<number>, x: number): number {
|
|
110
|
-
return
|
|
126
|
+
return searchSync($.len(a), (i: number) => ($.index(a, i) as number) >= x)
|
|
111
127
|
}
|
|
112
128
|
|
|
113
129
|
// SearchFloat64s searches for x in a sorted slice of float64s and returns the index
|
|
@@ -115,7 +131,7 @@ export function SearchInts(a: $.Slice<number>, x: number): number {
|
|
|
115
131
|
// present (it could be len(a)).
|
|
116
132
|
// The slice must be sorted in ascending order.
|
|
117
133
|
export function SearchFloat64s(a: $.Slice<number>, x: number): number {
|
|
118
|
-
return
|
|
134
|
+
return searchSync($.len(a), (i: number) => ($.index(a, i) as number) >= x)
|
|
119
135
|
}
|
|
120
136
|
|
|
121
137
|
// SearchStrings searches for x in a sorted slice of strings and returns the index
|
|
@@ -123,6 +139,5 @@ export function SearchFloat64s(a: $.Slice<number>, x: number): number {
|
|
|
123
139
|
// present (it could be len(a)).
|
|
124
140
|
// The slice must be sorted in ascending order.
|
|
125
141
|
export function SearchStrings(a: $.Slice<string>, x: string): number {
|
|
126
|
-
return
|
|
142
|
+
return searchSync($.len(a), (i: number) => ($.index(a, i) as string) >= x)
|
|
127
143
|
}
|
|
128
|
-
|
package/gs/sync/sync.ts
CHANGED
|
@@ -243,7 +243,10 @@ export class Once {
|
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
// Do calls the function f if and only if Do is being called for the first time for this instance of Once
|
|
246
|
-
public async Do(f: () => void | Promise<void>): Promise<void> {
|
|
246
|
+
public async Do(f: (() => void | Promise<void>) | null): Promise<void> {
|
|
247
|
+
if (f == null) {
|
|
248
|
+
throw new Error('sync: nil function in Once.Do')
|
|
249
|
+
}
|
|
247
250
|
if (this._done) {
|
|
248
251
|
return
|
|
249
252
|
}
|