commit cb5b48ffb690877237353e7f96d468cd5a847ce1
parent 6b470e4919a9d24103069c8beeb741ac0bf9dc9d
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 16 Jun 2026 14:21:31 -0700
link: honor inter-section dot assignments at textual position
Top-level assignments and output sections are stored in parallel arrays;
nothing recovered their textual order, so all '. = ...' assignments were
applied up-front rather than at their source position between two
sections. Add a shared monotonic seq stamped during SECTIONS{} parsing
(KitLinkAssignment.seq / KitLinkOutputSection.seq) and walk DOT
assignments in seq order via a forward cursor, applying those that
precede each output section just before it is laid out (trailing ones
after the last section). Non-DOT (symbol/PROVIDE) assignments keep their
post-layout pass.
Adds check_intersection_dot_align (red->green).
Diffstat:
3 files changed, 59 insertions(+), 5 deletions(-)
diff --git a/include/kit/link.h b/include/kit/link.h
@@ -101,6 +101,11 @@ typedef struct KitLinkAssignment {
uint8_t kind; /* KitLinkAsnKind */
KitSlice sym;
const KitLinkExpr* expr;
+ /* Textual position within the enclosing SECTIONS{} body, shared with the
+ * KitLinkOutputSection.seq sequence so layout can interleave top-level
+ * assignments (e.g. `. = ALIGN(N)` between two sections) with output
+ * sections in source order. Only meaningful for top_asns; 0 otherwise. */
+ uint32_t seq;
} KitLinkAssignment;
typedef struct KitLinkOutputSection {
@@ -119,6 +124,10 @@ typedef struct KitLinkOutputSection {
const KitLinkExpr* fill;
const KitLinkExpr* flags;
bool noload;
+ /* Textual position within the enclosing SECTIONS{} body (shared with
+ * KitLinkAssignment.seq); lets layout interleave inter-section dot
+ * assignments at their source position. */
+ uint32_t seq;
} KitLinkOutputSection;
typedef struct KitLinkPhdr {
diff --git a/src/link/link_layout.c b/src/link/link_layout.c
@@ -957,6 +957,24 @@ static void apply_asn(Linker* l, LinkImage* img, u64* dot,
}
}
+/* Apply every KIT_LAS_DOT top-level assignment whose textual `seq` is below
+ * `seq_limit` and that has not yet been applied (tracked by *cursor), so the
+ * location counter advances at the assignment's source position. top_asns is
+ * already in textual order, so a single forward cursor suffices. Non-DOT
+ * top-level assignments (symbol defs / PROVIDE) are applied in a later pass
+ * once all section addresses are known. */
+static void apply_dot_asns_before(Linker* l, LinkImage* img, u64* dot,
+ const ScriptOutInfo* outs, u32 nouts,
+ const KitLinkScript* script, u32* cursor,
+ u32 seq_limit) {
+ while (*cursor < script->ntop_asns &&
+ script->top_asns[*cursor].seq < seq_limit) {
+ const KitLinkAssignment* a = &script->top_asns[*cursor];
+ if (a->kind == KIT_LAS_DOT) apply_asn(l, img, dot, outs, nouts, a);
+ ++*cursor;
+ }
+}
+
static const char* link_basename(const char* s) {
const char* base = s;
if (!s) return NULL;
@@ -1096,11 +1114,13 @@ static void link_layout_sections_scripted(Linker* l, LinkImage* img,
}
}
- for (k = 0; k < script->ntop_asns; ++k) {
- const KitLinkAssignment* a = &script->top_asns[k];
- if (a->kind == KIT_LAS_DOT)
- apply_asn(l, img, &dot, outs, script->nsections, a);
- }
+ /* Inter-section dot assignments (`. = ALIGN(N)` / `. = expr` written
+ * between two output sections) must advance the location counter at their
+ * textual position. top_asns and sections each carry a shared `seq`; walk
+ * the DOT assignments in seq order, applying those that precede the next
+ * section right before that section is laid out. dot_cursor tracks the
+ * next unapplied top_asn. */
+ u32 dot_cursor = 0;
u32 nseg_max = 0;
for (si = 0; si < script->nsections; ++si)
@@ -1130,6 +1150,10 @@ static void link_layout_sections_scripted(Linker* l, LinkImage* img,
for (si = 0; si < script->nsections; ++si) {
const KitLinkOutputSection* os = &script->sections[si];
int is_discard = slice_eq_cstr(os->name, "/DISCARD/");
+ /* Apply any inter-section dot assignments that textually precede this
+ * output section before placing it. */
+ apply_dot_asns_before(l, img, &dot, outs, script->nsections, script,
+ &dot_cursor, os->seq);
if (outs) outs[si].name = os->name;
if (is_discard) {
@@ -1431,6 +1455,10 @@ static void link_layout_sections_scripted(Linker* l, LinkImage* img,
img->nsegments++;
}
+ /* Apply any trailing dot assignments after the last output section. */
+ apply_dot_asns_before(l, img, &dot, outs, script->nsections, script,
+ &dot_cursor, UINT32_MAX);
+
for (k = 0; k < script->ntop_asns; ++k) {
const KitLinkAssignment* a = &script->top_asns[k];
if (a->kind != KIT_LAS_DOT)
diff --git a/src/link/link_script.c b/src/link/link_script.c
@@ -1126,11 +1126,28 @@ fail:
/* ---- SECTIONS{...} ---- */
+/* Stamp a monotonic textual-order sequence onto any top_asns / sections
+ * pushed since the previous call. Top-level assignments and output sections
+ * share one counter (*seq) so layout can interleave them in source order;
+ * see KitLinkAssignment.seq / KitLinkOutputSection.seq. */
+static void stamp_seq(VecAsn* top_asns, u32* prev_as, VecSec* sections,
+ u32* prev_sec, u32* seq) {
+ while (*prev_as < top_asns->n) top_asns->p[(*prev_as)++].seq = (*seq)++;
+ while (*prev_sec < sections->n) sections->p[(*prev_sec)++].seq = (*seq)++;
+}
+
static int parse_sections_block(LSP* p, VecAsn* top_asns, VecSec* sections,
VecAssert* asserts) {
+ u32 seq = 0;
+ u32 prev_as = 0;
+ u32 prev_sec = 0;
if (expect_ch(p, '{')) return 1;
for (;;) {
int ch;
+ /* Assign source-order seq to whatever the previous iteration pushed.
+ * (A single iteration appends at most one section but may append a
+ * burst of assignments via parse_assignment_call.) */
+ stamp_seq(top_asns, &prev_as, sections, &prev_sec, &seq);
skip_ws(p);
if (p->err) return 1;
ch = peek_ch(p);