mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-04 08:04:37 +08:00
Find bridgePlainUnwindCalls' calls and mentions with indexOf
bridgePlainUnwindCalls rewrites the module text after emission on Linux. It split the text with a lookahead regular expression, ran a regular expression over every line to find mentions of raising functions, and split every function that has calls into lines to test each one. - The text splits before each `define` line with indexOf. - Mentions of raising functions are found by scanning for `@` and reading the name that follows it. - A function without a plain call of a raising function rewrites only the lines that hold `call `, marking them nounwind. A function with such a call takes the line-by-line path as before. The emitted text is unchanged.
This commit is contained in:
@@ -283,3 +283,43 @@ test("calls that cannot unwind are marked nounwind", () => {
|
||||
expect(out).toContain("%r = call ptr %b(ptr %b) nounwind");
|
||||
expect(out).toContain(" call void @scr_unwind_raise()\n");
|
||||
});
|
||||
|
||||
test("functions without a plain call of a raising function change only their call lines", () => {
|
||||
const raising = [
|
||||
"define internal void @sc_bf_raises() #0 {",
|
||||
"entry:",
|
||||
" call void @scr_unwind_raise()",
|
||||
" unreachable",
|
||||
"}",
|
||||
].join("\n");
|
||||
const other = [
|
||||
"define internal void @sc_bf_raises.copy() #0 {",
|
||||
"entry:",
|
||||
" ret void",
|
||||
"}",
|
||||
].join("\n");
|
||||
const plain = [
|
||||
"define internal double @sc_f_plain(ptr %p) #0 {",
|
||||
"entry: ; call sites below",
|
||||
" %a = call double @sc_f_other(ptr %p)",
|
||||
" %b = tail call double @sc_f_other(ptr %p) ; mentions @ and @sc_bf_raises.copy",
|
||||
" store ptr @sc_bf_raises.copy, ptr %p",
|
||||
" ret double %a",
|
||||
"}",
|
||||
].join("\n");
|
||||
const parts = [`${raising}\n${other}`, plain, "@table = internal constant ptr @sc_f_plain"];
|
||||
const out = bridgePlainUnwindCalls(parts);
|
||||
expect(out[0]).toBe(parts[0]);
|
||||
expect(out[1]).toBe(
|
||||
plain
|
||||
.replace("@sc_f_other(ptr %p)\n", "@sc_f_other(ptr %p) nounwind\n")
|
||||
.replace("@sc_f_other(ptr %p) ;", "@sc_f_other(ptr %p) nounwind ;"),
|
||||
);
|
||||
expect(out[2]).toBe(parts[2]);
|
||||
expect(() =>
|
||||
bridgePlainUnwindCalls([
|
||||
raising,
|
||||
plain.replace("store ptr @sc_bf_raises.copy", "store ptr @sc_bf_raises"),
|
||||
]),
|
||||
).toThrow(UnwindAbiViolation);
|
||||
});
|
||||
|
||||
@@ -301,6 +301,46 @@ function markNounwind(line: string): string {
|
||||
: `${line.slice(0, close + 1)} nounwind${line.slice(close + 1)}`;
|
||||
}
|
||||
|
||||
/** The functions of `part`: its text split before each `define` line
|
||||
* (`part.split(/\n(?=define )/)`), found with indexOf. */
|
||||
function splitFunctions(part: string): string[] {
|
||||
const chunks: string[] = [];
|
||||
let start = 0;
|
||||
for (let at = part.indexOf("\ndefine "); at >= 0; at = part.indexOf("\ndefine ", at + 1)) {
|
||||
chunks.push(part.slice(start, at));
|
||||
start = at + 1;
|
||||
}
|
||||
chunks.push(part.slice(start));
|
||||
return chunks;
|
||||
}
|
||||
|
||||
/** Whether `code` can be part of a symbol name (`[\w.$%-]`). */
|
||||
function isNameCode(code: number): boolean {
|
||||
return (
|
||||
(code >= 97 && code <= 122) ||
|
||||
(code >= 65 && code <= 90) ||
|
||||
(code >= 48 && code <= 57) ||
|
||||
code === 95 /* _ */ ||
|
||||
code === 46 /* . */ ||
|
||||
code === 36 /* $ */ ||
|
||||
code === 37 /* % */ ||
|
||||
code === 45 /* - */
|
||||
);
|
||||
}
|
||||
|
||||
/** Calls `each` with every line of `chunk` that holds `call `, by its start
|
||||
* and end offsets, once per line and in order. Lines without `call ` match
|
||||
* neither CALL_LINE nor PLAIN_CALL. */
|
||||
function eachCallLine(chunk: string, each: (start: number, end: number) => void): void {
|
||||
for (let at = chunk.indexOf("call "); at >= 0;) {
|
||||
const start = chunk.lastIndexOf("\n", at) + 1;
|
||||
let end = chunk.indexOf("\n", at);
|
||||
if (end < 0) end = chunk.length;
|
||||
each(start, end);
|
||||
at = chunk.indexOf("call ", end);
|
||||
}
|
||||
}
|
||||
|
||||
/** The value a bridged call yields on its landing pad: the dummy a
|
||||
* pending-flag callee returns with an exception pending. */
|
||||
function dummyValue(type: string): string {
|
||||
@@ -318,7 +358,7 @@ function dummyValue(type: string): string {
|
||||
* these functions that emitDirectCall lowered are invokes already. */
|
||||
export function bridgePlainUnwindCalls(parts: string[]): string[] {
|
||||
const raisers = new Set<string>();
|
||||
const split = parts.map((part) => part.split(/\n(?=define )/));
|
||||
const split = parts.map(splitFunctions);
|
||||
for (const chunks of split)
|
||||
for (const chunk of chunks) {
|
||||
const m = DEFINE_NAME.exec(chunk);
|
||||
@@ -359,20 +399,29 @@ export function bridgePlainUnwindCalls(parts: string[]): string[] {
|
||||
}
|
||||
}
|
||||
for (const name of invoked) if (!raisers.has(name) && testsFlag.has(name)) violations.add(name);
|
||||
// Every mention of a raising function outside its `define` line, other
|
||||
// than as the callee of a call or invoke, found with indexOf.
|
||||
for (const chunks of split)
|
||||
for (const chunk of chunks)
|
||||
for (const line of chunk.split("\n")) {
|
||||
if (line.startsWith("define ")) continue;
|
||||
for (const m of line.matchAll(/@([\w.$%-]+)/g)) {
|
||||
if (!raisers.has(m[1]!)) continue;
|
||||
const at = m.index!;
|
||||
if (
|
||||
/(?:call|invoke) [^@]*$/.test(line.slice(0, at)) &&
|
||||
line.charAt(at + m[0].length) === "("
|
||||
)
|
||||
continue;
|
||||
violations.add(m[1]!);
|
||||
for (let at = chunk.indexOf("@"); at >= 0;) {
|
||||
let end = at + 1;
|
||||
while (end < chunk.length && isNameCode(chunk.charCodeAt(end))) end++;
|
||||
const next = chunk.indexOf("@", end);
|
||||
if (end === at + 1) {
|
||||
at = next;
|
||||
continue;
|
||||
}
|
||||
const name = chunk.slice(at + 1, end);
|
||||
if (raisers.has(name)) {
|
||||
const start = chunk.lastIndexOf("\n", at) + 1;
|
||||
const line = chunk.slice(start, at);
|
||||
if (
|
||||
!line.startsWith("define ") &&
|
||||
!(/(?:call|invoke) [^@]*$/.test(line) && chunk.charAt(end) === "(")
|
||||
)
|
||||
violations.add(name);
|
||||
}
|
||||
at = next;
|
||||
}
|
||||
if (violations.size > 0) throw new UnwindAbiViolation(violations);
|
||||
let personality = false;
|
||||
@@ -381,6 +430,34 @@ export function bridgePlainUnwindCalls(parts: string[]): string[] {
|
||||
chunks
|
||||
.map((chunk) => {
|
||||
if (!DEFINE_NAME.test(chunk) || !chunk.includes("call ")) return chunk;
|
||||
// A function without a plain call of a raising function only marks
|
||||
// its calls nounwind: rewrite just its call lines.
|
||||
let bridged = false;
|
||||
const marks: { start: number; end: number; line: string }[] = [];
|
||||
eachCallLine(chunk, (start, end) => {
|
||||
if (bridged) return;
|
||||
const line = chunk.slice(start, end);
|
||||
const m = PLAIN_CALL.exec(line);
|
||||
const callee = m === null ? undefined : (m[4] ?? m[7]);
|
||||
if (m !== null && callee !== undefined && raisers.has(callee)) {
|
||||
bridged = true;
|
||||
return;
|
||||
}
|
||||
if (!CALL_LINE.test(line)) return;
|
||||
const marked = markNounwind(line);
|
||||
if (marked !== line) marks.push({ start, end, line: marked });
|
||||
});
|
||||
if (!bridged) {
|
||||
if (marks.length === 0) return chunk;
|
||||
const pieces: string[] = [];
|
||||
let from = 0;
|
||||
for (const mark of marks) {
|
||||
pieces.push(chunk.slice(from, mark.start), mark.line);
|
||||
from = mark.end;
|
||||
}
|
||||
pieces.push(chunk.slice(from));
|
||||
return pieces.join("");
|
||||
}
|
||||
const lines = chunk.split("\n");
|
||||
const resume = resumers.has(DEFINE_NAME.exec(chunk)![1]!);
|
||||
const result: string[] = [];
|
||||
|
||||
Reference in New Issue
Block a user