Linux Modules
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Lorenzo Stoakes (ARM)" <ljs@kernel.org>,
	"Linus Torvalds" <torvalds@linux-foundation.org>,
	"Nathan Chancellor" <nathan@kernel.org>,
	"Nicolas Schier" <nsc@kernel.org>,
	"Nick Desaulniers" <ndesaulniers@google.com>,
	"Bill Wendling" <morbo@google.com>,
	"Justin Stitt" <justinstitt@google.com>,
	"Geert Uytterhoeven" <geert@linux-m68k.org>,
	"Luis Chamberlain" <mcgrof@kernel.org>,
	"Petr Pavlu" <petr.pavlu@suse.com>,
	"Daniel Gomez" <da.gomez@kernel.org>,
	"Sami Tolvanen" <samitolvanen@google.com>,
	"Aaron Tomlin" <atomlin@atomlin.com>,
	"Josh Poimboeuf" <jpoimboe@kernel.org>,
	"Peter Zijlstra" <peterz@infradead.org>,
	"Miguel Ojeda" <ojeda@kernel.org>,
	"Boqun Feng" <boqun@kernel.org>, "Gary Guo" <gary@garyguo.net>,
	"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
	"Benno Lossin" <lossin@kernel.org>,
	"Andreas Hindborg" <a.hindborg@kernel.org>,
	"Alice Ryhl" <aliceryhl@google.com>,
	"Trevor Gross" <tmgross@umich.edu>,
	"Danilo Krummrich" <dakr@kernel.org>,
	"Daniel Almeida" <daniel.almeida@collabora.com>,
	"Tamir Duberstein" <tamird@kernel.org>,
	"Alexandre Courbot" <acourbot@nvidia.com>,
	"Onur Özkan" <work@onurozkan.dev>
Cc: linux-kbuild@vger.kernel.org, linux-m68k@lists.linux-m68k.org,
	linux-modules@vger.kernel.org
Subject: Re: [PATCH v5 07/11] objtool: cache relocations, do less work, eliminate relocation hash
Date: Thu, 05 Nov 2026 18:22:21 +0000	[thread overview]
Message-ID: <sashiko-outbox-199749@kernel.org> (raw)
In-Reply-To: <20261104-build-speedup-v5-7-0f6018be8dae@kernel.org>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] objtool: trailing cache windows left at 0 in reloc_cache_resize()
- [Medium] objtool: repeated cache rebuild and free in elf_create_reloc()
- [Low] objtool: compare_reloc_order() returns 1 instead of 0 for identical elements
--

commit 93b28e01d1c19629db4e088766e42dd4cdfbed56
Author: Lorenzo Stoakes (ARM) <ljs@kernel.org>

objtool: cache relocations, do less work, eliminate relocation hash

This commit replaces the global relocation hash table in objtool with a
per-section relocation cache indexed by 64-byte windows. Out-of-order
relocation sections are sorted lazily into an index array on lookup.

> diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c
> index 98a110e3e8f57..bd463b5df38cd 100644
> --- a/tools/objtool/elf.c
> +++ b/tools/objtool/elf.c
> @@ -316,35 +316,220 @@ struct symbol *find_global_symbol_by_name(const struct elf *elf, const char *nam

[ ... ]

> +/* qsort() passes no context, so the relocations being sorted live here. */
> +static struct reloc *sort_relocs;
> +
> +static int compare_reloc_order(const void *a, const void *b)
> +{
> +	const unsigned int *idx_a = a;
> +	const unsigned int *idx_b = b;
> +	const unsigned long offset_a = reloc_offset(&sort_relocs[*idx_a]);
> +	const unsigned long offset_b = reloc_offset(&sort_relocs[*idx_b]);
> +
> +	if (offset_a < offset_b)
> +		return -1;
> +	if (offset_a > offset_b)
> +		return 1;
> +
> +	/* Equal offsets stay in file order. */
> +	if (*idx_a < *idx_b)
> +		return -1;
> +	return 1;
> +}

[Severity: Low]
As reported by Sashiko on v4 [1], when qsort() compares an element against
itself (a == b, so *idx_a == *idx_b), compare_reloc_order() returns 1
instead of 0.

Does this violate the C standard qsort() comparator contract if the libc
qsort() implementation compares the pivot element with itself during
partitioning? Returning (*idx_a > *idx_b) - (*idx_a < *idx_b) or checking
for *idx_a > *idx_b before returning 0 would ensure a strict total order.

[ ... ]

> +/* Grow the index to cover the base section, new windows start past the end. */
> +static int reloc_cache_resize(struct section *rsec)
> +{
> +	const unsigned int nr_windows = reloc_cache_index(sec_size(rsec->base)) + 1;
> +	unsigned int *cache;
> +
> +	if (nr_windows <= rsec->nr_cache_windows)
> +		return 0;
> +
> +	cache = realloc(rsec->reloc_cache, nr_windows * sizeof(*cache));
> +	if (!cache) {
> +		ERROR_GLIBC("realloc");
> +		return -1;
> +	}
> +
> +	while (rsec->nr_cache_windows < nr_windows)
> +		cache[rsec->nr_cache_windows++] = rsec->nr_indexed;
> +	rsec->reloc_cache = cache;
> +
> +	return 0;
> +}
> +
> +/*
> + * Index the next relocation. It must follow the previous one in position and
> + * offset, otherwise the index is dropped and the next lookup rebuilds it.
> + */
> +static int reloc_cache_add(struct section *rsec, unsigned int pos)
> +{
> +	struct reloc *reloc = reloc_at(rsec, pos);
> +	const unsigned long offset = reloc_offset(reloc);
> +	const unsigned long window = reloc_cache_index(offset);
> +	unsigned long first_window = 0;
> +
> +	if (pos != rsec->nr_indexed)
> +		goto unsorted;
> +
> +	if (pos) {
> +		struct reloc *prev = reloc_at(rsec, pos - 1);
> +		const unsigned long prev_offset = reloc_offset(prev);
> +
> +		if (offset < prev_offset)
> +			goto unsorted;
> +		first_window = reloc_cache_index(prev_offset) + 1;
> +	}
> +
> +	if (reloc_cache_resize(rsec))
> +		return -1;
> +	if (window >= rsec->nr_cache_windows)
> +		goto unsorted;
> +
> +	while (first_window <= window)
> +		rsec->reloc_cache[first_window++] = pos;
> +	rsec->nr_indexed = pos + 1;
> +
> +	return 0;
> +
> +unsorted:
> +	reloc_cache_free(rsec);
> +	return 0;
> +}

[ ... ]

> +/* If there are multiple matches, return the first one in the range. */
>  struct reloc *find_reloc_by_dest_range(const struct elf *elf, struct section *sec,
>  				       unsigned long offset, unsigned int len)
>  {
> -	struct reloc *reloc, *r = NULL;
> -	struct section *rsec;
> -	unsigned long o;
> +	struct section *rsec = sec->rsec;
> +	unsigned long cache_idx;
> +	unsigned int pos;
>  
> -	rsec = sec->rsec;
>  	if (!rsec)
>  		return NULL;
>  
> -	for_offset_range(o, offset, offset + len) {
> -		elf_hash_for_each_possible(elf, reloc, reloc, hash,
> -					   sec_offset_hash(rsec, o)) {
> -			if (reloc->sec != rsec)
> -				continue;
> +	if (!rsec->sorted && init_reloc_cache(rsec))
> +		exit(1);
>  
> -			if (reloc_offset(reloc) >= offset &&
> -			    reloc_offset(reloc) < offset + len) {
> -				if (!r || reloc_offset(reloc) < reloc_offset(r))
> -					r = reloc;
> -			}
> -		}
> -		if (r && (reloc_offset(r) & OFFSET_STRIDE_MASK) == o)
> -			return r;
> +	cache_idx = reloc_cache_index(offset);
> +	if (cache_idx >= rsec->nr_cache_windows)
> +		return NULL;
> +
> +	/* The index gives a lower bound, scan on from there. */
> +	for (pos = rsec->reloc_cache[cache_idx]; pos < rsec->nr_indexed; pos++) {
> +		struct reloc *reloc = reloc_at(rsec, pos);
> +
> +		if (reloc_offset(reloc) >= offset)
> +			return reloc_in_range(reloc, offset, len) ? reloc : NULL;
>  	}
>  
> -	return r;
> +	return NULL;
>  }

[Severity: Medium]
As reported by Sashiko on v4 [2], during init_reloc_cache(), the first
call to reloc_cache_add(rsec, 0) invokes reloc_cache_resize(rsec) while
rsec->nr_indexed is 0. Because sec_size(rsec->base) is already the full
size of the base section, reloc_cache_resize() allocates all nr_windows
entries for the entire section at once and initializes every entry in
cache[0 .. nr_windows - 1] to 0.

Subsequent calls to reloc_cache_add(rsec, pos) see nr_windows <=
rsec->nr_cache_windows in reloc_cache_resize() and only update
rsec->reloc_cache[first_window .. window] up to the window of the last
relocation. Even when a section grows incrementally, reloc_cache_resize()
runs before rsec->nr_indexed = pos + 1 in reloc_cache_add().

Won't any 64-byte window located after the last relocation in a section
remain set to 0 in rsec->reloc_cache[], causing find_reloc_by_dest_range()
to linearly scan all rsec->nr_indexed relocations from pos = 0 on every
lookup in the trailing part of the section?

[ ... ]

> @@ -1071,8 +1251,10 @@ struct reloc *elf_init_reloc(struct elf *elf, struct section *rsec,
>  	set_reloc_type(elf, reloc, type);
>  	set_reloc_addend(elf, reloc, addend);
>  
> -	if (!is_dwarf_section(rsec->base))
> -		elf_hash_add(reloc, &reloc->hash, reloc_hash(reloc));
> +	if (rsec->reloc_order)
> +		reloc_cache_free(rsec);
> +	else if (rsec->sorted && reloc_cache_add(rsec, reloc_idx))
> +		return NULL;

[Severity: Medium]
When elf_create_reloc() creates a new relocation, it first calls
find_reloc_by_dest(), and then calls elf_init_reloc():

elf_create_reloc()
  find_reloc_by_dest()
    find_reloc_by_dest_range()
      if (!rsec->sorted && init_reloc_cache(rsec))
  elf_init_reloc()
    if (rsec->reloc_order)
      reloc_cache_free(rsec);

In tools/objtool/klp-diff.c, clone_reloc() can recursively call
clone_symbol() on a target symbol in the same output section before
calling elf_create_reloc() for the caller's relocation, emitting a
relocation at a lower offset after one at a higher offset.

Once a single out-of-order relocation is added to rsec, won't every
subsequent elf_create_reloc() call on that section rebuild rsec->reloc_order
via qsort() and populate rsec->reloc_cache in find_reloc_by_dest(), only for
elf_init_reloc() to immediately free both via reloc_cache_free(rsec) because
rsec->reloc_order is non-NULL?

[1] https://lore.kernel.org/r/20260923-build-speedup-v4-17-73128809a4a4@kernel.org
[2] https://lore.kernel.org/r/20260923-build-speedup-v4-17-73128809a4a4@kernel.org

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261104-build-speedup-v5-0-0f6018be8dae@kernel.org?part=7

  reply	other threads:[~2026-11-05 18:22 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-04 18:20 [PATCH v5 00/11] kbuild: significantly speed up kernel builds Lorenzo Stoakes (ARM)
2026-11-04 18:20 ` [PATCH v5 01/11] kbuild: calculate list, composite object state per object Lorenzo Stoakes (ARM)
2026-11-04 18:20 ` [PATCH v5 02/11] kbuild: implement and use depcheck to check dependency timestamps Lorenzo Stoakes (ARM)
2026-11-05 18:22   ` sashiko-bot
2026-11-04 18:20 ` [PATCH v5 03/11] modpost: cache section relocation mismatch state Lorenzo Stoakes (ARM)
2026-11-04 18:20 ` [PATCH v5 04/11] modpost: emit module descriptors as assembly Lorenzo Stoakes (ARM)
2026-11-04 18:20 ` [PATCH v5 05/11] kbuild: batch module finalisation Lorenzo Stoakes (ARM)
2026-11-04 18:20 ` [PATCH v5 06/11] objtool: do not hash DWARF relocations Lorenzo Stoakes (ARM)
2026-11-04 18:20 ` [PATCH v5 07/11] objtool: cache relocations, do less work, eliminate relocation hash Lorenzo Stoakes (ARM)
2026-11-05 18:22   ` sashiko-bot [this message]
2026-11-04 18:20 ` [PATCH v5 08/11] objtool: size the instruction hash to the text Lorenzo Stoakes (ARM)
2026-11-05 18:22   ` sashiko-bot
2026-11-04 18:20 ` [PATCH v5 09/11] objtool: decode instructions and resolve branch targets in parallel Lorenzo Stoakes (ARM)
2026-11-04 18:20 ` [PATCH v5 10/11] rust: make exports.o depend on the headers generated for it Lorenzo Stoakes (ARM)
2026-11-04 18:20 ` [PATCH v5 11/11] kbuild: build rust crates in parallel with the rest of the build Lorenzo Stoakes (ARM)

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-199749@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=a.hindborg@kernel.org \
    --cc=acourbot@nvidia.com \
    --cc=aliceryhl@google.com \
    --cc=atomlin@atomlin.com \
    --cc=bjorn3_gh@protonmail.com \
    --cc=boqun@kernel.org \
    --cc=da.gomez@kernel.org \
    --cc=dakr@kernel.org \
    --cc=daniel.almeida@collabora.com \
    --cc=gary@garyguo.net \
    --cc=geert@linux-m68k.org \
    --cc=jpoimboe@kernel.org \
    --cc=justinstitt@google.com \
    --cc=linux-kbuild@vger.kernel.org \
    --cc=linux-m68k@lists.linux-m68k.org \
    --cc=linux-modules@vger.kernel.org \
    --cc=ljs@kernel.org \
    --cc=lossin@kernel.org \
    --cc=mcgrof@kernel.org \
    --cc=morbo@google.com \
    --cc=nathan@kernel.org \
    --cc=ndesaulniers@google.com \
    --cc=nsc@kernel.org \
    --cc=ojeda@kernel.org \
    --cc=peterz@infradead.org \
    --cc=petr.pavlu@suse.com \
    --cc=samitolvanen@google.com \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=tamird@kernel.org \
    --cc=tmgross@umich.edu \
    --cc=torvalds@linux-foundation.org \
    --cc=work@onurozkan.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox