parseman 0.61.2 → 0.61.3
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/dist/analysis/diagnostics.js +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/index.js +26 -17
- package/dist/index.js.map +2 -2
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/index.d.ts +7 -3
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +93 -38
- package/dist/plugin/index.js.map +3 -3
- package/dist/table/compile-rule-map.d.ts.map +1 -1
- package/dist/table/index.js +26 -17
- package/dist/table/index.js.map +1 -1
- package/dist/table/select-hot.d.ts +20 -1
- package/dist/table/select-hot.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/plugin/evaluator.ts +42 -2
- package/src/plugin/index.ts +37 -13
- package/src/table/compile-rule-map.ts +8 -21
- package/src/table/select-hot.ts +35 -4
- package/src/version.ts +1 -1
|
@@ -1,6 +1,25 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* The sites to emit: highest weight per byte first, stopping at the first that
|
|
3
|
+
* does not fit in `budget` bytes. Stopping rather than skipping is what makes a
|
|
4
|
+
* larger budget a superset: a skip lets a budget that just admits a big site lose
|
|
5
|
+
* the small ones a smaller budget took after skipping it. Insertion order is rank.
|
|
6
|
+
*/
|
|
2
7
|
export declare function selectHotSites(code: readonly number[], entries: readonly number[], siteBytes: ReadonlyMap<number, number>, budget: number,
|
|
3
8
|
/** Measured calls per site (`countSiteVisits` over a corpus). Replaces the
|
|
4
9
|
* static estimate when given. */
|
|
5
10
|
profile?: ArrayLike<number>): Set<number>;
|
|
11
|
+
/**
|
|
12
|
+
* The largest prefix of the ranking whose real source fits `ceiling`. `siteBytes`
|
|
13
|
+
* leaves out what sites add around their bodies, so the estimate can overshoot:
|
|
14
|
+
* each overshoot drops the last site and measures again. Walking down the one
|
|
15
|
+
* order is what keeps a selection a prefix of any larger budget's: that budget has
|
|
16
|
+
* every candidate this one has, so it can never settle on fewer. `undefined` when
|
|
17
|
+
* not even the first site fits.
|
|
18
|
+
*/
|
|
19
|
+
export declare function fitHotSites(code: readonly number[], entries: readonly number[], siteBytes: ReadonlyMap<number, number>,
|
|
20
|
+
/** What any selection costs before its first site body. */
|
|
21
|
+
fixed: number, ceiling: number, sourceBytes: (sites: ReadonlySet<number>) => number, profile?: ArrayLike<number>): {
|
|
22
|
+
readonly sites: ReadonlySet<number>;
|
|
23
|
+
readonly bytes: number;
|
|
24
|
+
} | undefined;
|
|
6
25
|
//# sourceMappingURL=select-hot.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select-hot.d.ts","sourceRoot":"","sources":["../../src/table/select-hot.ts"],"names":[],"mappings":"AAuBA
|
|
1
|
+
{"version":3,"file":"select-hot.d.ts","sourceRoot":"","sources":["../../src/table/select-hot.ts"],"names":[],"mappings":"AAuBA;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EACtC,MAAM,EAAE,MAAM;AACd;iCACiC;AACjC,OAAO,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,GAC1B,GAAG,CAAC,MAAM,CAAC,CAgBb;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC;AACtC,2DAA2D;AAC3D,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,KAAK,MAAM,EACnD,OAAO,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,GAC1B;IAAE,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAQ7E"}
|
package/dist/version.d.ts
CHANGED
|
@@ -13,5 +13,5 @@
|
|
|
13
13
|
* Kept in sync with `package.json` by `test/unit/version-sync.test.ts`, which fails
|
|
14
14
|
* the build if this constant drifts from the published version.
|
|
15
15
|
*/
|
|
16
|
-
export declare const PARSEMAN_VERSION = "0.61.
|
|
16
|
+
export declare const PARSEMAN_VERSION = "0.61.3";
|
|
17
17
|
//# sourceMappingURL=version.d.ts.map
|
package/package.json
CHANGED
package/src/plugin/evaluator.ts
CHANGED
|
@@ -17,6 +17,7 @@ import type { Combinator } from '../types.ts'
|
|
|
17
17
|
import type { DispatchArm } from '../combinators/dispatch.ts'
|
|
18
18
|
import { ref } from '../combinators/ref.ts'
|
|
19
19
|
import { rules, RULES_RECIPE, type PriorRules, type RulesOptions, type RulesRecipe } from '../combinators/parser.ts'
|
|
20
|
+
import { parseSync } from 'oxc-parser'
|
|
20
21
|
import * as parseman from '../index.ts'
|
|
21
22
|
import { confirmedBuildParamUnused } from '../compiler/build-arity.ts'
|
|
22
23
|
import { directBuilderBindings } from './direct-builder-static.ts'
|
|
@@ -72,7 +73,7 @@ function stripTsFromSource(node: Node, code: string): string {
|
|
|
72
73
|
walk(node)
|
|
73
74
|
const start = (node as { start: number }).start
|
|
74
75
|
const end = (node as { end: number }).end
|
|
75
|
-
if (cuts.length === 0) return code.slice(start, end)
|
|
76
|
+
if (cuts.length === 0) return stripComments(code.slice(start, end))
|
|
76
77
|
cuts.sort((a, b) => a[0] - b[0])
|
|
77
78
|
let out = ''
|
|
78
79
|
let cur = start
|
|
@@ -81,7 +82,46 @@ function stripTsFromSource(node: Node, code: string): string {
|
|
|
81
82
|
out += code.slice(cur, s)
|
|
82
83
|
cur = e
|
|
83
84
|
}
|
|
84
|
-
return out + code.slice(cur, end)
|
|
85
|
+
return stripComments(out + code.slice(cur, end))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The four characters ECMAScript treats as ending a line. */
|
|
89
|
+
const LINE_TERMINATOR = /[\n\r\u2028\u2029]/
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* A reducer's source without its comments, and without the whitespace before them.
|
|
93
|
+
* A reducer ships as a string in the carried IR, where a bundler cannot strip it, and
|
|
94
|
+
* a comment must not change what a module ships. A comment still separates tokens and,
|
|
95
|
+
* if it crossed or followed a line end, still ends a line: automatic semicolon
|
|
96
|
+
* insertion and `return` depend on that line end.
|
|
97
|
+
*/
|
|
98
|
+
function stripComments(text: string): string {
|
|
99
|
+
if (!text.includes('/*') && !text.includes('//')) return text
|
|
100
|
+
const wrapped = `(${text})`
|
|
101
|
+
let out = ''
|
|
102
|
+
let cur = 0
|
|
103
|
+
let lineEnd = false
|
|
104
|
+
const comments = parseSync('reducer.ts', wrapped).comments
|
|
105
|
+
comments.forEach((c, i) => {
|
|
106
|
+
const before = text.slice(cur, c.start - 1)
|
|
107
|
+
const kept = before.trimEnd()
|
|
108
|
+
out += kept
|
|
109
|
+
lineEnd ||= LINE_TERMINATOR.test(before.slice(kept.length)) || LINE_TERMINATOR.test(c.value)
|
|
110
|
+
cur = c.end - 1
|
|
111
|
+
const after = /^\s*/.exec(text.slice(cur))![0]
|
|
112
|
+
const endsLine = LINE_TERMINATOR.test(after)
|
|
113
|
+
if (comments[i + 1]?.start === c.end + after.length) {
|
|
114
|
+
lineEnd ||= endsLine
|
|
115
|
+
cur += after.length
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
if (out !== '' && cur + after.length < text.length && !endsLine) {
|
|
119
|
+
if (lineEnd) out += '\n'
|
|
120
|
+
else if (after === '') out += ' '
|
|
121
|
+
}
|
|
122
|
+
lineEnd = false
|
|
123
|
+
})
|
|
124
|
+
return out + text.slice(cur)
|
|
85
125
|
}
|
|
86
126
|
|
|
87
127
|
/**
|
package/src/plugin/index.ts
CHANGED
|
@@ -170,11 +170,13 @@ export type ParsecraftPluginOptions = {
|
|
|
170
170
|
*/
|
|
171
171
|
assemblyProfile?: AssemblyProfile
|
|
172
172
|
/**
|
|
173
|
-
* Generated assembly bytes each module may spend, as a multiple of its own
|
|
174
|
-
*
|
|
173
|
+
* Generated assembly bytes each module may spend, as a multiple of its own code,
|
|
174
|
+
* comments and whitespace aside. **Default `0.5`.** `0` ships no assembly. The right number is
|
|
175
175
|
* per grammar: a larger one parses faster in steady state and costs import and
|
|
176
176
|
* first-parse time, so measure both, and keep the artifact within 5× its source
|
|
177
|
-
* (docs/design/runtime-and-size-contract.md, rules 4 and 6).
|
|
177
|
+
* (docs/design/runtime-and-size-contract.md, rules 4 and 6). A larger budget never
|
|
178
|
+
* loses a site, with one limit: a module with two AST grammars spends it in order, so
|
|
179
|
+
* a larger budget can buy the first a big site and leave the second fewer.
|
|
178
180
|
*/
|
|
179
181
|
assemblyBudget?: number
|
|
180
182
|
}
|
|
@@ -208,11 +210,26 @@ const PARSEMAN_MODULE = 'parseman'
|
|
|
208
210
|
|
|
209
211
|
/**
|
|
210
212
|
* The default `assemblyBudget`: generated assembly bytes a module may spend, as a
|
|
211
|
-
* multiple of its own
|
|
212
|
-
*
|
|
213
|
-
* measured curve.
|
|
213
|
+
* multiple of its own code (`codeBytes`). Half keeps every jess grammar far under the
|
|
214
|
+
* 5x ceiling (contract rule 4). A grammar that wants a different trade passes its own.
|
|
214
215
|
*/
|
|
215
|
-
const DEFAULT_ASSEMBLY_BUDGET = 0.
|
|
216
|
+
const DEFAULT_ASSEMBLY_BUDGET = 0.5
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* A module's code in bytes, without its comments and whitespace. It is what a budget
|
|
220
|
+
* is a share of, so editing a comment or reflowing a line cannot move a site in or
|
|
221
|
+
* out of the assembly, and with it the module's size and parse speed.
|
|
222
|
+
*/
|
|
223
|
+
function codeBytes(code: string, comments: readonly { start: number; end: number }[], end = code.length): number {
|
|
224
|
+
let bytes = 0
|
|
225
|
+
let at = 0
|
|
226
|
+
for (const c of comments) {
|
|
227
|
+
if (c.start >= end) break
|
|
228
|
+
bytes += code.slice(at, c.start).replace(/\s+/g, '').length
|
|
229
|
+
at = c.end
|
|
230
|
+
}
|
|
231
|
+
return bytes + code.slice(at, end).replace(/\s+/g, '').length
|
|
232
|
+
}
|
|
216
233
|
|
|
217
234
|
/**
|
|
218
235
|
* The REASON a lowering refused, appended to the warning that reports it.
|
|
@@ -769,6 +786,8 @@ export type TransformMacroResult = {
|
|
|
769
786
|
map: ReturnType<MagicString['generateMap']>
|
|
770
787
|
/** Diagnostics for macro-referencing shapes that fell back to the interpreter. */
|
|
771
788
|
warnings: string[]
|
|
789
|
+
/** The sites the module's `assemblyBudget` bought, by program offset, in rank order. */
|
|
790
|
+
assemblySites: number[]
|
|
772
791
|
}
|
|
773
792
|
|
|
774
793
|
/**
|
|
@@ -1751,7 +1770,9 @@ function transformMacroImpl(
|
|
|
1751
1770
|
|
|
1752
1771
|
// Stable, reproducible per-artifact namespace: hash of module id + a label
|
|
1753
1772
|
// (binding name / arg position) — never a counter, so rebuilds are byte-stable
|
|
1754
|
-
// and two artifacts never collide when fused.
|
|
1773
|
+
// and two artifacts never collide when fused. A call is labelled by where it sits
|
|
1774
|
+
// in the module's code (`codeBytes`), so a comment above it leaves the name alone.
|
|
1775
|
+
const callLabel = (fn: string, start: number): string => `${fn}${codeBytes(code, result.comments, start)}`
|
|
1755
1776
|
const nsFor = (label: string): string =>
|
|
1756
1777
|
`_${createHash('sha1').update(`${id}#${label}`).digest('hex').slice(0, 8)}_`
|
|
1757
1778
|
|
|
@@ -2255,7 +2276,7 @@ function transformMacroImpl(
|
|
|
2255
2276
|
const carried: CarriedItem[] = [] // re-lowerable; also SERIALIZED onto the value
|
|
2256
2277
|
const importedFactories: string[] = []
|
|
2257
2278
|
for (let i = 0; i < elements.length; i++) {
|
|
2258
|
-
const r = argPieces(elements[i]!,
|
|
2279
|
+
const r = argPieces(elements[i]!, `${callLabel('compose', init.start)}_${i}`, composing)
|
|
2259
2280
|
if (!r) {
|
|
2260
2281
|
// A build-compiled argument cannot fall back: runtime `compose()` links live
|
|
2261
2282
|
// `rules()` grammars only and would throw at import. Fail here instead.
|
|
@@ -2344,14 +2365,15 @@ function transformMacroImpl(
|
|
|
2344
2365
|
* SELECTIVE ASSEMBLY, BUDGETED PER MODULE. A fused grammar's default AST/no-lines
|
|
2345
2366
|
* table carries generated code for its hottest sites only (`select-hot.ts`); the
|
|
2346
2367
|
* closure engine runs the rest. The site bodies of every fused grammar in this
|
|
2347
|
-
* module together may not exceed `assemblyBudget` times the module's own
|
|
2368
|
+
* module together may not exceed `assemblyBudget` times the module's own code (`codeBytes`).
|
|
2348
2369
|
*
|
|
2349
2370
|
* A module that declares a `composeLeaf` has named its terminal parser, so its
|
|
2350
2371
|
* `compose()` results are composable PIECES: another build re-lowers them and
|
|
2351
2372
|
* never parses through their table, so they spend nothing. Without a
|
|
2352
2373
|
* `composeLeaf`, a `compose()` result is the module's parser.
|
|
2353
2374
|
*/
|
|
2354
|
-
let assemblyBudget = Math.floor(code.
|
|
2375
|
+
let assemblyBudget = Math.floor(codeBytes(code, result.comments) * assemblyBudgetShare)
|
|
2376
|
+
const assemblySites: number[] = []
|
|
2355
2377
|
let leafDeclared: boolean | undefined
|
|
2356
2378
|
const declaresLeaf = (): boolean => leafDeclared ??= (body as Statement[]).some(stmt => {
|
|
2357
2379
|
const vd = unwrapVd(stmt)
|
|
@@ -2375,6 +2397,7 @@ function transformMacroImpl(
|
|
|
2375
2397
|
warnings.push(`${id}:${lineOf(pos)} — ${label}: its precompiled assembly cannot emit ${refusal}; the artifact runs on the closure engine`)
|
|
2376
2398
|
}
|
|
2377
2399
|
assemblyBudget -= bytes
|
|
2400
|
+
assemblySites.push(...sites)
|
|
2378
2401
|
return sites
|
|
2379
2402
|
}
|
|
2380
2403
|
/** Measured site calls over the optional corpus, or `undefined` for the static
|
|
@@ -2445,7 +2468,7 @@ function transformMacroImpl(
|
|
|
2445
2468
|
let localRules: ReadonlyMap<string, Combinator<unknown>> | null = null
|
|
2446
2469
|
const importedFactories: string[] = []
|
|
2447
2470
|
if (isRulesCall(localArg)) {
|
|
2448
|
-
const evaluated = evaluateRulesFactory(localArg,
|
|
2471
|
+
const evaluated = evaluateRulesFactory(localArg, callLabel('composeLeaf', init.start))
|
|
2449
2472
|
localRules = evaluated?.ruleMap ?? null
|
|
2450
2473
|
if (evaluated?.importedFactory) importedFactories.push(evaluated.importedFactory)
|
|
2451
2474
|
} else if (localArg.type === 'Identifier') {
|
|
@@ -2472,7 +2495,7 @@ function transformMacroImpl(
|
|
|
2472
2495
|
const composing = composingTrivia(elements)
|
|
2473
2496
|
const carried: CarriedItem[] = []
|
|
2474
2497
|
for (let i = 0; i < elements.length - 1; i++) {
|
|
2475
|
-
const r = argPieces(elements[i]!,
|
|
2498
|
+
const r = argPieces(elements[i]!, `${callLabel('composeLeaf', init.start)}_${i}`, composing)
|
|
2476
2499
|
if (!r) {
|
|
2477
2500
|
warn(init.start, `composeLeaf(): argument ${i} isn't a build-resolvable recognition grammar`)
|
|
2478
2501
|
return null
|
|
@@ -3294,6 +3317,7 @@ function transformMacroImpl(
|
|
|
3294
3317
|
}
|
|
3295
3318
|
|
|
3296
3319
|
return {
|
|
3320
|
+
assemblySites: applied.length > 0 ? assemblySites : [],
|
|
3297
3321
|
code: ms.toString(),
|
|
3298
3322
|
map: ms.generateMap({ hires: true }),
|
|
3299
3323
|
warnings,
|
|
@@ -5,7 +5,7 @@ import { collectGrammarReflection, GRAMMAR_REFLECTION, type GrammarReflection }
|
|
|
5
5
|
import { encodeTableProgram, type TableSettings } from './encode.ts'
|
|
6
6
|
import { defaultAssemblyCfgs, emitTableExpression } from './emit.ts'
|
|
7
7
|
import { emitAssemblySource, Unemittable } from './emit-assembly.ts'
|
|
8
|
-
import {
|
|
8
|
+
import { fitHotSites } from './select-hot.ts'
|
|
9
9
|
|
|
10
10
|
/** The most the entry supercompiler may grow an assembly (`entry-supercompiler.ts`, its 5% source cap). */
|
|
11
11
|
const SUPERCOMPILE_GROWTH = 1.05
|
|
@@ -372,29 +372,16 @@ export function compileRuleMap(
|
|
|
372
372
|
// The estimate (prelude plus site bodies) leaves out what selected sites add
|
|
373
373
|
// around their bodies — shared helpers and the `_px` wrappers of the closure
|
|
374
374
|
// sites they call — and the supercompiler may grow the source by up to 5%. So
|
|
375
|
-
// the selection is judged by the source it actually emits, scaled for that 5
|
|
376
|
-
// and narrowed until that fits.
|
|
375
|
+
// the selection is judged by the source it actually emits, scaled for that 5%.
|
|
377
376
|
const ceiling = budget / SUPERCOMPILE_GROWTH
|
|
378
377
|
// A budget that cannot cover the prelude buys nothing, by design: no refusal.
|
|
379
378
|
if (ceiling <= fixed) return none
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const sites = selectHotSites(artifact.code, Object.values(artifact.rules), siteBytes, target - fixed, profile)
|
|
387
|
-
if (sites.size === 0) {
|
|
388
|
-
return {
|
|
389
|
-
...none,
|
|
390
|
-
refusal: `a site within its ${Math.floor(ceiling)} B selection ceiling (the prelude alone is ${fixed} B)`,
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
const bytes = emitAssemblySource(table, artifact, cfg, roots, true, sites, true).source.length
|
|
394
|
-
if (bytes <= ceiling) return { sites, bytes: Math.ceil(bytes * SUPERCOMPILE_GROWTH) }
|
|
395
|
-
// Scale by the overshoot, and by at least 1%: near the ceiling a site is
|
|
396
|
-
// the unit, and the ratio alone can stall a few bytes over it.
|
|
397
|
-
target *= Math.min(ceiling / bytes, 0.99)
|
|
379
|
+
const fit = fitHotSites(artifact.code, Object.values(artifact.rules), siteBytes, fixed, ceiling,
|
|
380
|
+
sites => emitAssemblySource(table, artifact, cfg, roots, true, sites, true).source.length, profile)
|
|
381
|
+
if (fit !== undefined) return { sites: fit.sites, bytes: Math.ceil(fit.bytes * SUPERCOMPILE_GROWTH) }
|
|
382
|
+
return {
|
|
383
|
+
...none,
|
|
384
|
+
refusal: `a site within its ${Math.floor(ceiling)} B selection ceiling (the prelude alone is ${fixed} B)`,
|
|
398
385
|
}
|
|
399
386
|
}
|
|
400
387
|
|
package/src/table/select-hot.ts
CHANGED
|
@@ -21,7 +21,12 @@ const LOOP = 8
|
|
|
21
21
|
const ROUNDS = 60
|
|
22
22
|
const CAP = 1e12
|
|
23
23
|
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* The sites to emit: highest weight per byte first, stopping at the first that
|
|
26
|
+
* does not fit in `budget` bytes. Stopping rather than skipping is what makes a
|
|
27
|
+
* larger budget a superset: a skip lets a budget that just admits a big site lose
|
|
28
|
+
* the small ones a smaller budget took after skipping it. Insertion order is rank.
|
|
29
|
+
*/
|
|
25
30
|
export function selectHotSites(
|
|
26
31
|
code: readonly number[],
|
|
27
32
|
entries: readonly number[],
|
|
@@ -41,14 +46,40 @@ export function selectHotSites(
|
|
|
41
46
|
let spent = 0
|
|
42
47
|
for (const ip of ranked) {
|
|
43
48
|
if (w(ip) === 0) break
|
|
44
|
-
|
|
45
|
-
if (spent
|
|
46
|
-
spent += bytes
|
|
49
|
+
spent += siteBytes.get(ip)!
|
|
50
|
+
if (spent > budget) break
|
|
47
51
|
selected.add(ip)
|
|
48
52
|
}
|
|
49
53
|
return selected
|
|
50
54
|
}
|
|
51
55
|
|
|
56
|
+
/**
|
|
57
|
+
* The largest prefix of the ranking whose real source fits `ceiling`. `siteBytes`
|
|
58
|
+
* leaves out what sites add around their bodies, so the estimate can overshoot:
|
|
59
|
+
* each overshoot drops the last site and measures again. Walking down the one
|
|
60
|
+
* order is what keeps a selection a prefix of any larger budget's: that budget has
|
|
61
|
+
* every candidate this one has, so it can never settle on fewer. `undefined` when
|
|
62
|
+
* not even the first site fits.
|
|
63
|
+
*/
|
|
64
|
+
export function fitHotSites(
|
|
65
|
+
code: readonly number[],
|
|
66
|
+
entries: readonly number[],
|
|
67
|
+
siteBytes: ReadonlyMap<number, number>,
|
|
68
|
+
/** What any selection costs before its first site body. */
|
|
69
|
+
fixed: number,
|
|
70
|
+
ceiling: number,
|
|
71
|
+
sourceBytes: (sites: ReadonlySet<number>) => number,
|
|
72
|
+
profile?: ArrayLike<number>,
|
|
73
|
+
): { readonly sites: ReadonlySet<number>; readonly bytes: number } | undefined {
|
|
74
|
+
const ranked = [...selectHotSites(code, entries, siteBytes, ceiling - fixed, profile)]
|
|
75
|
+
for (; ranked.length > 0; ranked.pop()) {
|
|
76
|
+
const sites = new Set(ranked)
|
|
77
|
+
const bytes = sourceBytes(sites)
|
|
78
|
+
if (bytes <= ceiling) return { sites, bytes }
|
|
79
|
+
}
|
|
80
|
+
return undefined
|
|
81
|
+
}
|
|
82
|
+
|
|
52
83
|
function staticWeights(code: readonly number[], entries: readonly number[]): Map<number, number> {
|
|
53
84
|
let weight = new Map<number, number>()
|
|
54
85
|
const kids: number[] = []
|
package/src/version.ts
CHANGED