Skip to content

Commit edfb600

Browse files
authored
Merge pull request #118 from overdrivenpotato/lto
Add support for LTO and remove link-dead-code
2 parents c04d034 + 760bc3d commit edfb600

File tree

12 files changed

+299
-152
lines changed

12 files changed

+299
-152
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ implementations of graphics functions, and the addition of missing libraries.
5656
## Dependencies
5757

5858
To compile for the PSP, you will need a Rust **nightly** version equal to or
59-
later than `2022-05-22` and the `rust-src` component. Please install Rust using
59+
later than `2022-06-11` and the `rust-src` component. Please install Rust using
6060
https://rustup.rs/
6161

6262
Use the following if you are new to Rust. (Feel free to set an override manually

cargo-psp/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,5 @@ rustc_version = "0.2.3"
2929

3030
serde = "1.0.111"
3131
serde_derive = "1.0.111"
32+
bincode = "1.3.1"
3233
toml = "0.5.6"

cargo-psp/src/fix_imports.rs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
use std::{mem, path::Path, collections::HashMap};
2+
use goblin::elf::Elf;
3+
4+
/// A stub library entry like the one found in the `psp` crate, but with types
5+
/// changed to be cross-platform.
6+
///
7+
/// In particular, pointers are swapped to `u32`.
8+
#[derive(serde_derive::Deserialize, serde_derive::Serialize, Debug)]
9+
struct SceStubLibraryEntry {
10+
name: u32,
11+
version: [u8; 2],
12+
flags: u16,
13+
len: u8,
14+
v_stub_count: u8,
15+
stub_count: u16,
16+
nid_table: u32,
17+
stub_table: u32,
18+
}
19+
20+
/// Fixes the stub count for all imported modules.
21+
///
22+
/// When linking, sometimes it is possible for imported functions to be
23+
/// completely stripped. This can happen with LTO, for example. Because of this,
24+
/// we need a way to write the stub count post-linking. This function does
25+
/// exactly this.
26+
pub fn fix<T: AsRef<Path>>(path: T) {
27+
let mut bytes = std::fs::read(&path).unwrap();
28+
let elf = Elf::parse(&bytes).unwrap();
29+
30+
let shstrtab = {
31+
let sh = &elf.section_headers[elf.header.e_shstrndx as usize];
32+
let start = sh.sh_offset as usize;
33+
let end = (sh.sh_offset + sh.sh_size) as usize;
34+
35+
&bytes[start..end]
36+
};
37+
38+
// Map of name -> section header
39+
let sections = elf.section_headers
40+
.iter()
41+
.map(|sh| {
42+
let name = shstrtab[sh.sh_name..]
43+
.iter()
44+
.take_while(|b| **b != 0)
45+
.map(|b| *b as char)
46+
.collect::<String>();
47+
48+
(name, sh)
49+
})
50+
.collect::<HashMap<_, _>>();
51+
52+
let lib_stub = match sections.get(".lib.stub") {
53+
Some(s) => s,
54+
55+
// The binary might not import any functions, in some rare cases.
56+
None => return,
57+
};
58+
59+
// TODO: Use module info instead of these sections, as they can technically
60+
// be stripped.
61+
62+
// If we have .lib.stub, then .lib.stub.btm must exist.
63+
let lib_stub_btm = sections.get(".lib.stub.btm")
64+
.expect("could not find .lib.stub.btm section");
65+
66+
let rodata_sce_nid = sections.get(".rodata.sceNid")
67+
.expect("Could not find .rodata.sceNid section");
68+
69+
let start = lib_stub.sh_offset as usize;
70+
let end = lib_stub_btm.sh_offset as usize;
71+
72+
// Rough check for the length.
73+
assert_eq!((end - start) % mem::size_of::<SceStubLibraryEntry>(), 0);
74+
75+
// List of (stub index in .lib.stub, stub entry). Ordered by appearance in
76+
// .rodata.sceNid section.
77+
let stubs_nid_sorted = {
78+
let mut entries = bytes[start..end]
79+
.chunks(mem::size_of::<SceStubLibraryEntry>())
80+
.map(bincode::deserialize)
81+
.map(Result::unwrap)
82+
.enumerate()
83+
.collect::<Vec<(_, SceStubLibraryEntry)>>();
84+
85+
// Sort by order of appearance in .rodata.sceNid section.
86+
entries.sort_unstable_by_key(|(_, s)| s.nid_table);
87+
88+
entries
89+
};
90+
91+
// Iterator of mutable byte slices (of stub lib entries) in raw ELF binary.
92+
let stub_entry_bufs = bytes[start..end]
93+
.chunks_mut(mem::size_of::<SceStubLibraryEntry>());
94+
95+
for (i, stub_entry_buf) in stub_entry_bufs.enumerate() {
96+
// A NID is a 32-bit value.
97+
const NID_SIZE: u32 = 4;
98+
99+
let mut stub: SceStubLibraryEntry = bincode::deserialize(stub_entry_buf)
100+
.unwrap();
101+
102+
let nid_end = stubs_nid_sorted.get(
103+
1 + stubs_nid_sorted.iter()
104+
.position(|&(j, _)| i == j)
105+
.unwrap()
106+
)
107+
.map(|(_, s)| s.nid_table)
108+
.unwrap_or(rodata_sce_nid.sh_addr as u32 + rodata_sce_nid.sh_size as u32);
109+
110+
stub.stub_count = ((nid_end - stub.nid_table) / NID_SIZE) as u16;
111+
112+
// Re-serialize the stub and save.
113+
let serialized = bincode::serialize(&stub).unwrap();
114+
stub_entry_buf.copy_from_slice(&serialized);
115+
}
116+
117+
std::fs::write(path, bytes).unwrap();
118+
}

cargo-psp/src/main.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ use std::{
66
process::{self, Command, Stdio},
77
};
88

9+
mod fix_imports;
10+
911
const CONFIG_NAME: &str = "Psp.toml";
1012

1113
#[derive(serde_derive::Deserialize, Default)]
@@ -118,11 +120,13 @@ impl fmt::Display for CommitDate {
118120
}
119121
}
120122

121-
// Minimum 2022-05-22, remember to update both commit date and version too, below.
122-
const MINIMUM_COMMIT_DATE: CommitDate = CommitDate { year: 2022, month: 05, day: 22 };
123+
// Minimum 2022-06-11, remember to update both commit date and version too,
124+
// below. Note that the `day` field lags by one day, as the toolchain always
125+
// contains the previous days' nightly rustc.
126+
const MINIMUM_COMMIT_DATE: CommitDate = CommitDate { year: 2022, month: 06, day: 10 };
123127
const MINIMUM_RUSTC_VERSION: Version = Version {
124128
major: 1,
125-
minor: 62,
129+
minor: 63,
126130
patch: 0,
127131
pre: Vec::new(),
128132
build: Vec::new(),
@@ -191,18 +195,13 @@ fn main() {
191195
},
192196
};
193197

194-
// FIXME: This is a workaround. This should eventually be removed.
195-
let rustflags = env::var("RUSTFLAGS").unwrap_or("".into())
196-
+ " -C link-dead-code";
197-
198198
let mut process = Command::new("cargo")
199199
.arg("build")
200200
.arg("-Z")
201201
.arg(build_std_flag)
202202
.arg("--target")
203203
.arg("mipsel-sony-psp")
204204
.args(args)
205-
.env("RUSTFLAGS", rustflags)
206205
.stdin(Stdio::inherit())
207206
.stdout(Stdio::inherit())
208207
.stderr(Stdio::inherit())
@@ -239,6 +238,7 @@ fn main() {
239238
for id in metadata.clone().workspace_members {
240239
let package = metadata[&id].clone();
241240

241+
// TODO: Error if no bin is ever found.
242242
for target in package.targets {
243243
if target.kind.iter().any(|k| k == "bin") {
244244
let elf_path = bin_dir.join(&target.name);
@@ -247,6 +247,8 @@ fn main() {
247247
let sfo_path = bin_dir.join("PARAM.SFO");
248248
let pbp_path = bin_dir.join("EBOOT.PBP");
249249

250+
fix_imports::fix(&elf_path);
251+
250252
Command::new("prxgen")
251253
.arg(&elf_path)
252254
.arg(&prx_path)

ci/concourse/build-rust.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ image_resource:
1111
tag: 1.44-slim
1212

1313
params:
14-
RUSTFLAGS: "-C link-dead-code"
15-
RUSTUP_TOOLCHAIN: nightly-2022-05-22
14+
RUSTUP_TOOLCHAIN: nightly-2022-06-11
1615

1716
inputs:
1817
- name: repo

libunwind/.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
src
22
include
3-
libunwind.a
3+
libunwind*.a
44
*.o

libunwind/Makefile

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
SUFFIX ?=
2+
CPPFLAGS ?=
3+
4+
OUTLIB := libunwind$(SUFFIX).a
5+
16
CC := clang
27
CXX := clang++
38
AR := llvm-ar
@@ -9,20 +14,25 @@ S_OBJLIST := UnwindRegistersRestore.o UnwindRegistersSave.o
914

1015
CFLAGS := -std=c99
1116
CXXFLAGS := -std=c++11 -nostdinc++ -fno-exceptions -fno-rtti
12-
CPPFLAGS := -target mipsel-unknown-unknown -mcpu=mips2 -msingle-float \
13-
-fstrict-aliasing -funwind-tables -O3 \
17+
CPPFLAGS := $(CPPFLAGS) -target mipsel-unknown-unknown -mcpu=mips2 \
18+
-msingle-float -fstrict-aliasing -funwind-tables -O3 \
1419
-D __LITTLE_ENDIAN__ -D __ELF__ -D _LIBUNWIND_IS_BAREMETAL \
1520
-D _LIBUNWIND_HAS_NO_THREADS -D _LIBUNWIND_IS_NATIVE_ONLY \
1621
-DNDEBUG \
1722
-I /usr/local/pspdev/psp/include/ \
1823
-I include
1924

20-
../psp/libunwind.a: libunwind.a
21-
cp libunwind.a ../psp/
25+
all:
26+
make ../psp/libunwind.a
27+
SUFFIX=_lto CPPFLAGS=-flto make ../psp/libunwind_lto.a
28+
29+
../psp/$(OUTLIB): $(OUTLIB)
30+
cp $^ ../psp/
2231
touch ../psp/build.rs
32+
make clean
2333

24-
libunwind.a: $(CPP_OBJLIST) $(C_OBJLIST) $(S_OBJLIST)
25-
$(AR) $(ARFLAGS) libunwind.a $^
34+
$(OUTLIB): $(CPP_OBJLIST) $(C_OBJLIST) $(S_OBJLIST)
35+
$(AR) $(ARFLAGS) $(OUTLIB) $^
2636

2737
$(CPP_OBJLIST): %.o: src/%.cpp
2838
$(COMPILE.cc) $^
@@ -32,7 +42,7 @@ $(S_OBJLIST): %.o: src/%.S
3242
$(C_OBJLIST) $(S_OBJLIST):
3343
$(COMPILE.c) $^
3444

35-
.PHONY: clean patch
45+
.PHONY: all clean patch
3646

3747
patch:
3848
git submodule update --init --depth 1 -- ./rustc
@@ -41,4 +51,4 @@ patch:
4151
patch -p0 < ./no-sdc1.patch
4252

4353
clean:
44-
rm -r *.o libunwind.a
54+
rm -r *.o

psp/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ std = []
2323
stub-only = []
2424

2525
[dependencies]
26-
paste = "0.1.12"
26+
paste = "1.0.1"
2727
bitflags = "1.2.1"
2828
embedded-graphics = { version = "0.7.1", optional = true, features = ["fixed_point"] }
2929
unstringify = "0.1.4"

psp/build.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,31 @@
1-
use std::{env, path::Path};
1+
use std::{env, path::Path, os::unix::prelude::OsStrExt};
22

33
fn main() {
44
println!("cargo:rerun-if-changed=build.rs");
55
println!("cargo:rerun-if-changed=libunwind.a");
6+
println!("cargo:rerun-if-env-changed=RUSTFLAGS");
67

78
if env::var("CARGO_FEATURE_STUB_ONLY").is_ok() {
89
return;
910
}
1011

12+
// Figure out whether to use the LTO libunwind, or the regular one.
13+
let libunwind = if env::var_os("CARGO_ENCODED_RUSTFLAGS")
14+
.expect("could not get `CARGO_ENCODED_RUSTFLAGS` variable")
15+
.as_bytes()
16+
.split(|b| *b == 0x1f)
17+
.any(|flags| flags.starts_with(b"-Clinker-plugin-lto"))
18+
{
19+
"./libunwind_lto.a"
20+
} else {
21+
"./libunwind.a"
22+
};
23+
1124
// TODO: Do we even need to copy the library over? Maybe we can just link
1225
// directly from the current directory.
1326
let out_dir = env::var("OUT_DIR").unwrap();
1427
let out_file = Path::new(&out_dir).join("libunwind.a");
15-
std::fs::copy("./libunwind.a", out_file).unwrap();
28+
std::fs::copy(libunwind, out_file).unwrap();
1629

1730
println!("cargo:rustc-link-lib=static=unwind");
1831
println!("cargo:rustc-link-search=native={}", out_dir);

psp/libunwind.a

-6.01 KB
Binary file not shown.

psp/libunwind_lto.a

62.7 KB
Binary file not shown.

0 commit comments

Comments
 (0)