@asmlift/core 0.8.0 → 0.9.0
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 +34 -30
- package/package.json +1 -1
- package/src/aggregate.ts +248 -0
- package/src/backend/c.ts +5 -2
- package/src/backend/cfamily.ts +236 -37
- package/src/backend/cpp.ts +114 -9
- package/src/backend/pascal.ts +39 -0
- package/src/cdecl/constant.ts +59 -0
- package/src/cdecl/lex.ts +209 -0
- package/src/cdecl/parse.ts +975 -0
- package/src/cdecl/spell.ts +69 -0
- package/src/codegen-flags.ts +38 -6
- package/src/compiler-diagnostics.ts +194 -0
- package/src/contracts.ts +615 -79
- package/src/declare.ts +75 -5
- package/src/frontend/asmdata.ts +19 -5
- package/src/frontend/call-plan.ts +686 -0
- package/src/frontend/device-pins.ts +277 -0
- package/src/frontend/errors.ts +5 -0
- package/src/frontend/flags-edge.ts +101 -0
- package/src/frontend/fpu.ts +167 -0
- package/src/frontend/frame-objects.ts +2131 -0
- package/src/frontend/high-half.ts +2 -1
- package/src/frontend/local-object.ts +490 -0
- package/src/frontend/mips.ts +363 -40
- package/src/frontend/opaque.ts +97 -18
- package/src/frontend/ppc.ts +520 -129
- package/src/frontend/reloc-symbol.ts +94 -13
- package/src/frontend/splat.ts +108 -5
- package/src/frontend/ssa.ts +669 -76
- package/src/frontend/stackargs.ts +120 -39
- package/src/frontend/thumb.ts +2764 -2018
- package/src/ir/alias.ts +29 -0
- package/src/ir/core.ts +101 -3
- package/src/ir/discipline.ts +248 -0
- package/src/ir/float-bits.ts +47 -0
- package/src/ir/opcodes.ts +84 -112
- package/src/ir/parse.ts +1 -0
- package/src/ir/types.ts +118 -2
- package/src/ir/verify.ts +150 -3
- package/src/l3/address.ts +5 -0
- package/src/l3/argcopy.ts +38 -6
- package/src/l3/ast.ts +186 -14
- package/src/l3/coalesce.ts +21 -13
- package/src/l3/dce.ts +32 -10
- package/src/l3/gates.ts +9 -3
- package/src/l3/hoist.ts +8 -7
- package/src/l3/initfirst.ts +27 -9
- package/src/l3/intcell.ts +97 -0
- package/src/l3/mentions.ts +10 -4
- package/src/l3/narrowdecl.ts +218 -0
- package/src/l3/nearbase.ts +10 -5
- package/src/l3/parkfirst.ts +8 -5
- package/src/l3/pollguard.ts +3 -2
- package/src/l3/ptrcell.ts +357 -0
- package/src/l3/regspell.ts +17 -3
- package/src/l3/reindex.ts +10 -7
- package/src/l3/symbol-refs.ts +104 -23
- package/src/l3/tailmerge.ts +6 -19
- package/src/l3/typing.ts +137 -25
- package/src/l3/unreduce.ts +4 -4
- package/src/l3/volstore.ts +96 -7
- package/src/mangle.ts +11 -1
- package/src/pattern/engine.ts +19 -16
- package/src/pipeline.ts +57 -12
- package/src/proto-context.ts +788 -0
- package/src/proto.ts +689 -50
- package/src/raise/arrays.ts +4 -3
- package/src/raise/declared-volatile.ts +131 -0
- package/src/raise/divpow2.ts +2 -2
- package/src/raise/errors.ts +17 -0
- package/src/raise/floathelpers.ts +243 -0
- package/src/raise/globalshape.ts +11 -5
- package/src/raise/gvn.ts +26 -5
- package/src/raise/narrow.ts +29 -2
- package/src/raise/narrowlocal.ts +11 -6
- package/src/raise/offsetnames.ts +510 -0
- package/src/raise/pairparams.ts +81 -0
- package/src/raise/paramwidth.ts +7 -0
- package/src/raise/pre-recovery.ts +78 -4
- package/src/raise/recover.ts +14 -10
- package/src/raise/retsink.ts +1 -1
- package/src/raise/shortcircuit.ts +41 -19
- package/src/raise/softdiv.ts +21 -23
- package/src/raise/struct-arrays.ts +5 -4
- package/src/raise/structs.ts +311 -98
- package/src/raise/truncload.ts +366 -0
- package/src/raise/widehelpers.ts +106 -0
- package/src/rank-declare.ts +30 -5
- package/src/rank-variations.ts +36 -8
- package/src/rank.ts +276 -80
- package/src/runtime-helpers.ts +287 -0
- package/src/stillborn.ts +289 -0
- package/src/structure/analysis.ts +1285 -448
- package/src/structure/hazards.ts +633 -58
- package/src/structure/local-statics.ts +381 -0
- package/src/structure/namecoalesce.ts +1 -1
- package/src/structure/redundant-test.ts +99 -0
- package/src/structure/structure.ts +1772 -395
- package/src/structure/switch-recover.ts +159 -78
- package/src/symbols.ts +38 -3
- package/src/target.ts +662 -59
- package/src/trace.ts +44 -24
- package/src/variation-definitions.ts +119 -1
- package/src/variation-tokens.ts +20 -6
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// asmlift — the PRINTER of what the parser reads: a declared type in asmlift's type vocabulary
|
|
2
|
+
// (`ParamType`): `u8 *`, `void (*)(s32 channel)`, `f32 (*)[3]`, and every run of tokens a spelling
|
|
3
|
+
// holds, as written.
|
|
4
|
+
//
|
|
5
|
+
// The derivations come from the parser (`parse.ts`), listed from the declarator's name outward, so
|
|
6
|
+
// the printer rebuilds the abstract declarator from the inside: a pointer is written ahead of what it
|
|
7
|
+
// binds, an array or a function after it, and a pointer that an array or a function binds next is
|
|
8
|
+
// grouped in parentheses.
|
|
9
|
+
//
|
|
10
|
+
// A RUN OF TOKENS IS PRINTED AS WRITTEN, one space wherever the source separates two of them: a type's
|
|
11
|
+
// name (`A::B`, `TVec3<const u8 *>`), a member pointer's class, a function's parameter list and an
|
|
12
|
+
// array's extent alike. A function pointer's own parameters are part of its spelling, not types this
|
|
13
|
+
// reads.
|
|
14
|
+
import type { Tokens } from './lex';
|
|
15
|
+
import type { DeclaredType, Range } from './parse';
|
|
16
|
+
|
|
17
|
+
/** The type `t` spells: its qualifiers, its base and its abstract declarator, `const u8 *`. */
|
|
18
|
+
export function spellType(t: DeclaredType, tokens: Tokens): string {
|
|
19
|
+
const base = t.qualifiers.length === 0 ? t.spelling : [...t.qualifiers, t.spelling].filter((w) => w !== '').join(' ');
|
|
20
|
+
let declarator = '';
|
|
21
|
+
// whether the last derivation was written ahead: an array or a function after it groups it in parentheses
|
|
22
|
+
let prefixed = false;
|
|
23
|
+
// whether the declarator is only the extents and lists written straight after the absent name
|
|
24
|
+
let suffixes = true;
|
|
25
|
+
for (const d of t.derivations) {
|
|
26
|
+
if (d.kind === 'pointer' || d.kind === 'reference') {
|
|
27
|
+
const own =
|
|
28
|
+
d.kind === 'reference' ? '&' : [`${d.member !== undefined ? `${d.member}::` : ''}*`, ...d.qualifiers].join(' ');
|
|
29
|
+
// a pointer is a word kept apart from what it binds, `u8 * *`, unless that is the name's own suffix
|
|
30
|
+
declarator = declarator === '' || suffixes ? own + declarator : `${own} ${declarator}`;
|
|
31
|
+
prefixed = true;
|
|
32
|
+
suffixes = false;
|
|
33
|
+
} else {
|
|
34
|
+
const grouped = prefixed ? `(${declarator})` : declarator;
|
|
35
|
+
declarator =
|
|
36
|
+
d.kind === 'array'
|
|
37
|
+
? `${grouped}[${d.size === undefined ? '' : written(tokens, d.size)}]`
|
|
38
|
+
: `${grouped}(${written(tokens, d.list)})`;
|
|
39
|
+
prefixed = false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return declarator === '' ? base : `${base} ${declarator}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** A parameter's type as C adjusts it: an array nearest the name is a pointer to what it holds, and a
|
|
46
|
+
* function nearest the name a pointer to it. `f32 m[2][3]` is `f32 (*)[3]`, and `int fn(int)` is
|
|
47
|
+
* `int (*)(int)`. */
|
|
48
|
+
export function asParameter(t: DeclaredType): DeclaredType {
|
|
49
|
+
const [first, ...rest] = t.derivations;
|
|
50
|
+
if (first?.kind === 'array') {
|
|
51
|
+
return { ...t, derivations: [{ kind: 'pointer', qualifiers: [] }, ...rest] };
|
|
52
|
+
}
|
|
53
|
+
if (first?.kind === 'function') {
|
|
54
|
+
return { ...t, derivations: [{ kind: 'pointer', qualifiers: [] }, ...t.derivations] };
|
|
55
|
+
}
|
|
56
|
+
return t;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The tokens of `r` as written, one space wherever the source separates two of them. */
|
|
60
|
+
export function written(tokens: Tokens, r: Range): string {
|
|
61
|
+
let out = '';
|
|
62
|
+
for (let k = r.from; k < r.to; k++) {
|
|
63
|
+
if (k > r.from && tokens.start(k) > tokens.end(k - 1)) {
|
|
64
|
+
out += ' ';
|
|
65
|
+
}
|
|
66
|
+
out += tokens.text(k);
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
package/src/codegen-flags.ts
CHANGED
|
@@ -117,6 +117,9 @@ interface OptionSpec {
|
|
|
117
117
|
/** the value the compiler acts on, from the spelled one (the argument, else the pattern's last
|
|
118
118
|
* capture group, else empty) */
|
|
119
119
|
value?: (spelled: string, m: Match) => string;
|
|
120
|
+
/** an inert word that makes the compiler STOP REPORTING after a number of errors; a run that
|
|
121
|
+
* reads a rejection compiles without it (cli flags.ts) */
|
|
122
|
+
limitsErrors?: true;
|
|
120
123
|
}
|
|
121
124
|
|
|
122
125
|
const toggle = (_: string, m: Match) => (m[1] ? 'off' : 'on');
|
|
@@ -140,6 +143,12 @@ const gccFamily: OptionSpec[] = [
|
|
|
140
143
|
// -Os sets optimize_size beside level 2, so it is its own level: agbcc hoist.c differs at `-O2` and
|
|
141
144
|
// `-Os`, where the address load moves above the branch.
|
|
142
145
|
{ match: '-Os', slot: 'O', value: () => 's' },
|
|
146
|
+
// Error limits, ahead of the `-W` diagnostics: gcc stops at the first error under
|
|
147
|
+
// `-Wfatal-errors` and at N under `-fmax-errors=N`; clang takes both and spells its own
|
|
148
|
+
// `-ferror-limit=N`, and under `-Wfatal-errors` prints the first error as `fatal error:` with no
|
|
149
|
+
// trailer, so what it printed never says whether it was all.
|
|
150
|
+
{ match: '-Wfatal-errors', limitsErrors: true },
|
|
151
|
+
{ match: /^-f(?:max-errors|error-limit)=\d+$/, limitsErrors: true },
|
|
143
152
|
...cppAndDiagnostics,
|
|
144
153
|
// -B tells the driver where its own programs are: which compiler runs is the toolchain, not a flag
|
|
145
154
|
{ match: /^-B(.+)$/ },
|
|
@@ -192,7 +201,10 @@ const TABLES: Record<FlagFamily, readonly OptionSpec[]> = {
|
|
|
192
201
|
// both, and `mwccLevel` reads what a sequence of them compiles at.
|
|
193
202
|
{ match: /^-O((?:[0-4](?:,[ps])?|[ps])?)$/, slot: 'O' },
|
|
194
203
|
{ match: /^-[ID](.+)$/ },
|
|
195
|
-
{ match: /^-(d|D|i|I|ir|
|
|
204
|
+
{ match: /^-(d|D|i|I|ir|w|msgstyle)$/, takesArg: true },
|
|
205
|
+
// Under `-maxerrors 1` mwcc 2.3.3 and 2.4.2 end EVERY rejection with `User break, cancelled...`,
|
|
206
|
+
// a file with exactly one error included, so what they printed never says whether it was all.
|
|
207
|
+
{ match: '-maxerrors', takesArg: true, limitsErrors: true },
|
|
196
208
|
{ match: /^-(nodefaults|nosyspath|nostdinc|stderr|c|multibyte|requireprotos)$/ },
|
|
197
209
|
// `-dialect | -lang keyword` is one option under two names (mwcceppc -help), and all four
|
|
198
210
|
// spellings compile the same source to the same object in the container.
|
|
@@ -307,6 +319,9 @@ export interface CodegenProfile {
|
|
|
307
319
|
overriddenAt: readonly number[];
|
|
308
320
|
/** argv indices of every inert word and its argument */
|
|
309
321
|
inertAt: readonly number[];
|
|
322
|
+
/** argv indices of every error-limit word and its argument, a subset of `inertAt`: what a compiler
|
|
323
|
+
* cut short printed is a prefix of its verdict (compiler-diagnostics.ts `verdictMessages`) */
|
|
324
|
+
errorLimitAt: readonly number[];
|
|
310
325
|
/** argv indices of every word that is neither an option nor an option's argument, such as the file
|
|
311
326
|
* a compile command names or `-` for standard input; each is also unclassified */
|
|
312
327
|
operandAt: readonly number[];
|
|
@@ -421,6 +436,7 @@ export function parseFlags(family: FlagFamily, argv: readonly string[]): Codegen
|
|
|
421
436
|
const lastAt = new Map<string, { at: number[]; spelled: string }>();
|
|
422
437
|
const overriddenAt: number[] = [];
|
|
423
438
|
const inertAt: number[] = [];
|
|
439
|
+
const errorLimitAt: number[] = [];
|
|
424
440
|
const overrides: LevelReading['overrides'] = [];
|
|
425
441
|
const mwccLevelWords: { at: number; word: string }[] = [];
|
|
426
442
|
const spans: number[][] = [];
|
|
@@ -482,6 +498,9 @@ export function parseFlags(family: FlagFamily, argv: readonly string[]): Codegen
|
|
|
482
498
|
spans.push(at);
|
|
483
499
|
if (hit.spec.slot === undefined) {
|
|
484
500
|
inertAt.push(...at);
|
|
501
|
+
if (hit.spec.limitsErrors) {
|
|
502
|
+
errorLimitAt.push(...at);
|
|
503
|
+
}
|
|
485
504
|
continue;
|
|
486
505
|
}
|
|
487
506
|
const slot = typeof hit.spec.slot === 'string' ? hit.spec.slot : hit.spec.slot(hit.m);
|
|
@@ -557,6 +576,7 @@ export function parseFlags(family: FlagFamily, argv: readonly string[]): Codegen
|
|
|
557
576
|
unclassified,
|
|
558
577
|
overriddenAt: overriddenAt.sort((a, b) => a - b),
|
|
559
578
|
inertAt,
|
|
579
|
+
errorLimitAt,
|
|
560
580
|
operandAt: unknown.filter((k) => isOperand(argv[k])),
|
|
561
581
|
spans: grouped,
|
|
562
582
|
overrides: overrides.sort((a, b) => a.at - b.at).map((o) => o.line),
|
|
@@ -617,14 +637,26 @@ export function storedFlags(family: FlagFamily, argv: readonly string[]): string
|
|
|
617
637
|
* option, and only mwcc has a C++ front end, so every other family answers `c` unless its unit is
|
|
618
638
|
* named for C++ — which none is, and where one is, real.ts refuses the row by name. */
|
|
619
639
|
export function unitLanguage(unit: string, cflags: readonly string[]): 'c' | 'c++' {
|
|
620
|
-
|
|
640
|
+
return dialectOf(parseFlags('mwcc', cflags).slots.lang, unit);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/** A `-lang` word CodeWarrior does not accept. */
|
|
644
|
+
export class UnreadableDialectError extends Error {}
|
|
645
|
+
|
|
646
|
+
/** The dialect a `-lang` word states — the one reading of it, for `unitLanguage` and for the target
|
|
647
|
+
* `targetFor` resolves at a flag set. With no word, the unit's extension when there is a unit, C
|
|
648
|
+
* otherwise. `objc` is C to this reading: its functions are C's. A word CodeWarrior itself refuses
|
|
649
|
+
* is refused. */
|
|
650
|
+
export function dialectOf(stated: string | undefined, unit?: string): 'c' | 'c++' {
|
|
621
651
|
if (stated === undefined) {
|
|
622
|
-
return /\.(cc|cp|cpp|cxx)$/i.test(unit) ? 'c++' : 'c';
|
|
652
|
+
return unit !== undefined && /\.(cc|cp|cpp|cxx)$/i.test(unit) ? 'c++' : 'c';
|
|
623
653
|
}
|
|
624
|
-
if (stated !== 'c' && stated !== 'c++' && stated !== 'ec++') {
|
|
625
|
-
throw new
|
|
654
|
+
if (stated !== 'c' && stated !== 'c++' && stated !== 'ec++' && stated !== 'objc') {
|
|
655
|
+
throw new UnreadableDialectError(
|
|
656
|
+
`${unit === undefined ? '' : `${unit}: `}CodeWarrior dialect '${stated}' is not one of c, c++, ec++, objc`,
|
|
657
|
+
);
|
|
626
658
|
}
|
|
627
|
-
return stated === 'c' ? 'c' : 'c
|
|
659
|
+
return stated === 'c++' || stated === 'ec++' ? 'c++' : 'c';
|
|
628
660
|
}
|
|
629
661
|
|
|
630
662
|
/** The optimisation level the compiler acts on (`-O2`, `-O4,p`), or null when the flags name none and
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// asmlift — reading a compiler's diagnostic text: which lines are errors, and when two failed
|
|
2
|
+
// compiles failed THE SAME WAY. Pure string logic (core is browser-pure): the ranking drivers, the
|
|
3
|
+
// benchmark's compile modules and the CLI read a compiler's output through here, so "an error"
|
|
4
|
+
// means one thing on all of them.
|
|
5
|
+
|
|
6
|
+
/** A scorer's refusal that IS the compiler's verdict: it ran to completion, exited nonzero and
|
|
7
|
+
* said why. Everything else a scorer throws — a timeout, a killed process, a missing binary, a
|
|
8
|
+
* differ that could not find the symbol — is this machine or this harness having a bad minute,
|
|
9
|
+
* and says nothing about the candidate.
|
|
10
|
+
*
|
|
11
|
+
* A CLASS because the distinction cannot be read back off a message: a compiler killed mid-run
|
|
12
|
+
* has already printed half its diagnostics, and that text has exactly the shape of a rejection.
|
|
13
|
+
* The seam that saw the spawn result is the only place that knows, so it says so in the type.
|
|
14
|
+
*
|
|
15
|
+
* `diagnostic` is the compiler's WHOLE output. `message` is what the seam publishes, which may be
|
|
16
|
+
* a bounded selection of it; a comparison made over a bounded selection compares the bound. */
|
|
17
|
+
export class CompilerRejection extends Error {
|
|
18
|
+
readonly diagnostic: string;
|
|
19
|
+
constructor(message: string, diagnostic: string = message, options?: ErrorOptions) {
|
|
20
|
+
super(message, options);
|
|
21
|
+
this.name = 'CompilerRejection';
|
|
22
|
+
this.diagnostic = diagnostic;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The FIRST class word a line carries, with the colon that makes it a tag: `warning:`, `note:`,
|
|
27
|
+
* `error:`, `fatal error:`, IDO's `Fatal:` and its numbered `Warning 712:`. The first, because a
|
|
28
|
+
* message can quote a tag of its own — `expected ';' before 'note' token; note: …` is an error.
|
|
29
|
+
* A line is ADVISORY when its tag is a warning or a note: the compiler may still accept the text. */
|
|
30
|
+
const TAG = /\b(?:fatal(?:\s+error)?|error|warning|note)\b(?:\s+\d+)?\s*:/i;
|
|
31
|
+
const isAdvisory = (line: string): boolean => {
|
|
32
|
+
const tag = TAG.exec(line);
|
|
33
|
+
return tag !== null && /^(?:warning|note)/i.test(tag[0]);
|
|
34
|
+
};
|
|
35
|
+
/** An mwcc caret line (`# Error: ^`). It carries no message: the line(s) after it do, and
|
|
36
|
+
* together they are one diagnostic. */
|
|
37
|
+
const MWCC_CARET = /#\s*(Error|Warning):[\s^~]*$/i;
|
|
38
|
+
/** A line of an mwcc diagnostic's MESSAGE: `#`, then prose. Not the caret, not a source excerpt
|
|
39
|
+
* (`# 12: x = y;`), not the banner (`### mwcceppc.exe Compiler:`, `# File: …`, `# ----`).
|
|
40
|
+
* mwcc wraps a message at the terminal's width, so a message is every such line up to the first
|
|
41
|
+
* that is not one: a conversion error's source type ends one line and its target type `'int *'`
|
|
42
|
+
* opens the next, and the first line alone equals the same conversion to `'int'`. */
|
|
43
|
+
const MWCC_MESSAGE = /^\s*#(?!#)\s*(?!(?:Error|Warning)\s*:|\d+:|File:|-{3,})\S/i;
|
|
44
|
+
|
|
45
|
+
/** `lines` with every ERROR ahead of every warning and note, each class in its original order.
|
|
46
|
+
*
|
|
47
|
+
* A compiler prints diagnostics in source order, and a candidate that casts its way through a
|
|
48
|
+
* project's types can print dozens of warnings above the one error that refused it. Whatever
|
|
49
|
+
* bounds the list downstream then keeps warnings only, and the reader is shown a cause that is
|
|
50
|
+
* not why the compile failed. An mwcc message travels with its caret line, whichever class that
|
|
51
|
+
* is. */
|
|
52
|
+
export function errorsFirst(lines: readonly string[]): string[] {
|
|
53
|
+
const errors: string[] = [];
|
|
54
|
+
const advisories: string[] = [];
|
|
55
|
+
/** the class of the mwcc diagnostic whose message lines are still arriving */
|
|
56
|
+
let open: boolean | undefined;
|
|
57
|
+
for (const l of lines) {
|
|
58
|
+
let advisory: boolean;
|
|
59
|
+
if (open !== undefined && MWCC_MESSAGE.test(l)) {
|
|
60
|
+
advisory = open;
|
|
61
|
+
} else {
|
|
62
|
+
advisory = isAdvisory(l);
|
|
63
|
+
open = MWCC_CARET.test(l) ? advisory : undefined;
|
|
64
|
+
}
|
|
65
|
+
(advisory ? advisories : errors).push(l);
|
|
66
|
+
}
|
|
67
|
+
return [...errors, ...advisories];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** `file:line:` or `file:line:column:` opening a diagnostic, the gcc family's spelling (clang's
|
|
71
|
+
* too). Behind an optional wrapper, because a seam's message puts its own words ahead of the
|
|
72
|
+
* first line (`agbcc failed: c.c:12: …`). */
|
|
73
|
+
const LOCATED = /(?:^|\s)[^\s:]+:\d+:(?:\d+:)?\s*(.*)$/;
|
|
74
|
+
/** The word a located error may carry ahead of its message (gcc 3+, clang); pre-3.0 gcc has none. */
|
|
75
|
+
const ERROR_TAG = /^(?:fatal\s+)?error\s*:\s*/i;
|
|
76
|
+
/** An error spelled with the word and no `file:line:`: `cc1: error: …`, `ld: fatal error: …`, and
|
|
77
|
+
* IDO's `cfe: Error: c.c, line 12: …`, whose own position is dropped with the rest. */
|
|
78
|
+
const TAGGED = /\b(?:fatal(?:\s+error)?|error)\s*:\s*(?:\S+, line \d+:\s*)?(.*)$/i;
|
|
79
|
+
/** Lines that belong to a diagnostic without being one: where the OTHER declaration was, and
|
|
80
|
+
* pre-3.0 gcc's two-line footnote to its first `undeclared`. None of them says what is wrong with
|
|
81
|
+
* the statement, and a `previous declaration` line points into the context rather than at the
|
|
82
|
+
* candidate. */
|
|
83
|
+
const CONTINUATION =
|
|
84
|
+
/^(?:previous (?:implicit )?(?:declaration|definition)\b|this is the location of\b|\(Each undeclared identifier\b|for each function it appears in\b|.*\bpreviously (?:declared|defined) here$)/i;
|
|
85
|
+
/** A position spelled INSIDE a message rather than ahead of it: IDO's `redeclaration of 'a';
|
|
86
|
+
* previous declaration at line 2 in file '/tmp/x/cand.c'`, whose file is a scratch directory that
|
|
87
|
+
* differs per compile. */
|
|
88
|
+
const POSITION_IN_MESSAGE = /\s*\bat line \d+ in file '[^']*'/g;
|
|
89
|
+
|
|
90
|
+
/** The compiler's ERROR lines, each reduced to its message: no file, no line, no column, no
|
|
91
|
+
* `error:` tag. Warnings, notes, `In function` banners, `previous declaration` lines and source
|
|
92
|
+
* excerpts are not errors and are not here. Empty when the text holds nothing this recognises —
|
|
93
|
+
* which is an answer the caller must treat as "unknown", never as "no errors". */
|
|
94
|
+
export function errorMessages(diagnostic: string): string[] {
|
|
95
|
+
const lines = diagnostic
|
|
96
|
+
.split('\n')
|
|
97
|
+
.map((l) => l.trim())
|
|
98
|
+
.filter(Boolean);
|
|
99
|
+
const out: string[] = [];
|
|
100
|
+
for (let i = 0; i < lines.length; i++) {
|
|
101
|
+
const caret = MWCC_CARET.exec(lines[i]);
|
|
102
|
+
if (caret !== null) {
|
|
103
|
+
const parts: string[] = [];
|
|
104
|
+
while (i + 1 < lines.length && MWCC_MESSAGE.test(lines[i + 1])) {
|
|
105
|
+
parts.push(lines[++i].replace(/^#\s*/, ''));
|
|
106
|
+
}
|
|
107
|
+
if (caret[1].toLowerCase() === 'error' && parts.length > 0) {
|
|
108
|
+
out.push(parts.join(' '));
|
|
109
|
+
}
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (isAdvisory(lines[i])) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const located = LOCATED.exec(lines[i]);
|
|
116
|
+
const message = located === null ? TAGGED.exec(lines[i])?.[1] : located[1].replace(ERROR_TAG, '');
|
|
117
|
+
if (message !== undefined && message !== '' && !CONTINUATION.test(message)) {
|
|
118
|
+
out.push(message.replace(POSITION_IN_MESSAGE, ''));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** A compiler that STOPPED REPORTING before it was done: IDO after 30 errors, clang at its
|
|
125
|
+
* `-ferror-limit`, gcc under `-fmax-errors` or `-Wfatal-errors`, mwcc under `-maxerrors`. What it
|
|
126
|
+
* printed is a prefix of its verdict, and two prefixes equal each other whatever follows them.
|
|
127
|
+
* clang under `-Wfatal-errors` prints no trailer at all, so the flag side (codegen-flags.ts
|
|
128
|
+
* `limitsErrors`) is what keeps that one out of a ranked compile. */
|
|
129
|
+
const TRUNCATED =
|
|
130
|
+
/Too many errors\.\.\. goodbye|too many errors emitted|compilation terminated due to -(?:fmax-errors|Wfatal-errors)|^User break, cancelled/im;
|
|
131
|
+
|
|
132
|
+
/** The error messages of a diagnostic that can be READ AS A VERDICT: `errorMessages`, or null when
|
|
133
|
+
* it recognises none, and null when the compiler stopped reporting — what an unfinished
|
|
134
|
+
* diagnostic printed is a prefix of its verdict, and two prefixes agree whatever follows them. */
|
|
135
|
+
export function verdictMessages(diagnostic: string): string[] | null {
|
|
136
|
+
if (TRUNCATED.test(diagnostic)) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
const messages = errorMessages(diagnostic);
|
|
140
|
+
return messages.length === 0 ? null : messages;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** One compile ATTEMPT inside a diagnostic that holds several: what the harness called the
|
|
144
|
+
* attempt, and what the compiler printed for it. */
|
|
145
|
+
export interface Attempt {
|
|
146
|
+
label: string;
|
|
147
|
+
diagnostic: string;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** The line that opens an attempt. Owned here because it is read here: a harness that spelled its
|
|
151
|
+
* own could not be split back, and every one of its rejections would read as a single attempt. */
|
|
152
|
+
const ATTEMPT_HEADER = /^--- (.+) ---$/;
|
|
153
|
+
|
|
154
|
+
/** ONE REJECTION, SEVERAL COMPILES. A harness that tries a candidate in more than one world before
|
|
155
|
+
* it gives up — the benchmark's real tier climbs a ladder of contexts, in two dialects — throws one
|
|
156
|
+
* rejection for all of them, and whoever compares two rejections has to ask what EACH attempt
|
|
157
|
+
* said: a world no candidate can compile in prints the same sentence for every candidate, and read
|
|
158
|
+
* as one text that sentence would say the whole fan fails alike. So the attempts travel under a
|
|
159
|
+
* header line per attempt, in the order they were tried, and `attemptsOf` splits them back.
|
|
160
|
+
*
|
|
161
|
+
* The header is no error line to `errorMessages`, so the joined text reads as the whole of what
|
|
162
|
+
* every attempt printed, which is what a store of rejections keeps and a printer prints. A label
|
|
163
|
+
* holds no line break; a compiler line that happens to read as a header would split its attempt
|
|
164
|
+
* in two, alike in every candidate that printed it. */
|
|
165
|
+
export function joinAttempts(attempts: readonly Attempt[]): string {
|
|
166
|
+
return attempts.map((a) => `--- ${a.label} ---\n${a.diagnostic}`).join('\n');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** The attempts `joinAttempts` joined, in order. A diagnostic with no header is ONE attempt,
|
|
170
|
+
* labelled `''`: a single compile is the ordinary case, not a degenerate one. Text ahead of the
|
|
171
|
+
* first header is an attempt of its own when it holds anything, because dropping it would drop
|
|
172
|
+
* what it said. */
|
|
173
|
+
export function attemptsOf(diagnostic: string): Attempt[] {
|
|
174
|
+
const out: Attempt[] = [];
|
|
175
|
+
let label: string | null = null;
|
|
176
|
+
let lines: string[] = [];
|
|
177
|
+
const close = () => {
|
|
178
|
+
if (label !== null || lines.some((l) => l.trim() !== '')) {
|
|
179
|
+
out.push({ label: label ?? '', diagnostic: lines.join('\n') });
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
for (const line of diagnostic.split('\n')) {
|
|
183
|
+
const header = ATTEMPT_HEADER.exec(line);
|
|
184
|
+
if (header === null) {
|
|
185
|
+
lines.push(line);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
close();
|
|
189
|
+
label = header[1];
|
|
190
|
+
lines = [];
|
|
191
|
+
}
|
|
192
|
+
close();
|
|
193
|
+
return out.length > 0 ? out : [{ label: '', diagnostic }];
|
|
194
|
+
}
|