Skip to content

Commit 9e7f36f

Browse files
authored
perf: improve linking implementation (#324)
Collect all references at once, then replace all of them with a simple copy. We can avoid using `string.replace` which creates a copy and is not vectorized because the placeholders are the same length as an address (40 bytes in hex).
1 parent 42a909e commit 9e7f36f

File tree

3 files changed

+40
-11
lines changed

3 files changed

+40
-11
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ alloy-json-abi = { version = "1.3", features = ["serde_json"] }
4646
alloy-primitives = { version = "1.3", features = ["serde", "rand"] }
4747
cfg-if = "1.0"
4848
dunce = "1.0"
49+
memchr = "2.7"
4950
memmap2 = "0.9"
5051
path-slash = "0.2"
5152
rayon = "1.11"

crates/artifacts/solc/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ foundry-compilers-core.workspace = true
1919

2020
alloy-json-abi.workspace = true
2121
alloy-primitives.workspace = true
22+
memchr.workspace = true
2223
semver.workspace = true
2324
serde_json.workspace = true
2425
serde.workspace = true

crates/artifacts/solc/src/bytecode.rs

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -304,17 +304,7 @@ impl BytecodeObject {
304304
/// See also: <https://docs.soliditylang.org/en/develop/using-the-compiler.html#library-linking>
305305
pub fn link_fully_qualified(&mut self, name: &str, addr: Address) -> &mut Self {
306306
if let Self::Unlinked(unlinked) = self {
307-
let place_holder = utils::library_hash_placeholder(name);
308-
// the address as hex without prefix
309-
let hex_addr = hex::encode(addr);
310-
311-
// the library placeholder used to be the fully qualified name of the library instead of
312-
// the hash. This is also still supported by `solc` so we handle this as well
313-
let fully_qualified_placeholder = utils::library_fully_qualified_placeholder(name);
314-
315-
*unlinked = unlinked
316-
.replace(&format!("__{fully_qualified_placeholder}__"), &hex_addr)
317-
.replace(&format!("__{place_holder}__"), &hex_addr)
307+
link(unlinked, name, addr);
318308
}
319309
self
320310
}
@@ -386,6 +376,43 @@ impl AsRef<[u8]> for BytecodeObject {
386376
}
387377
}
388378

379+
/// Reference: <https://github.com/argotorg/solidity/blob/965166317bbc2b02067eb87f222a2dce9d24e289/libevmasm/LinkerObject.cpp#L38>
380+
fn link(unlinked: &mut String, name: &str, addr: Address) {
381+
const LEN: usize = 40;
382+
383+
let mut refs = vec![];
384+
let mut find = |needle: &str| {
385+
assert_eq!(needle.len(), LEN, "{needle:?}");
386+
refs.extend(memchr::memmem::find_iter(unlinked.as_bytes(), needle));
387+
};
388+
389+
let placeholder = utils::library_hash_placeholder(name);
390+
find(&format!("__{placeholder}__"));
391+
392+
// The library placeholder used to be the fully qualified name of the library instead of
393+
// the hash. This is also still supported by `solc` so we handle this as well.
394+
let fully_qualified_placeholder = utils::library_fully_qualified_placeholder(name);
395+
find(&format!("__{fully_qualified_placeholder}__"));
396+
397+
if refs.is_empty() {
398+
debug!("no references found while linking {name} -> {addr}");
399+
return;
400+
}
401+
402+
// The address as hex without prefix.
403+
let mut buffer = hex::Buffer::<20, false>::new();
404+
let hex_addr = &*buffer.format(&addr);
405+
assert_eq!(hex_addr.len(), LEN, "{hex_addr:?}");
406+
407+
// The ranges are non-overlapping, so we don't need to sort, and can iterate in whatever order
408+
// because of equal lengths.
409+
// SAFETY: We're replacing LEN bytes at a time, and all the indexes come from the same string.
410+
let unlinked = unsafe { unlinked.as_bytes_mut() };
411+
for &idx in &refs {
412+
unlinked[idx..idx + LEN].copy_from_slice(hex_addr.as_bytes());
413+
}
414+
}
415+
389416
/// This will serialize the bytecode data without a `0x` prefix, which the `ethers::types::Bytes`
390417
/// adds by default.
391418
///

0 commit comments

Comments
 (0)