Skip to content

Commit ffb625e

Browse files
jgarzikclaude
andcommitted
cc: Fix TLS variable access on Linux x86-64
Thread-local variables (_Thread_local/__thread) were crashing on Linux because the codegen used RIP-relative addressing instead of FS segment. - Add MemAddr::TlsIE for %fs:symbol@TPOFF addressing - Track TLS symbols in X86_64CodeGen - Generate correct TLS access for loads/stores on Linux Before: movl tls_var(%rip), %eax (crash) After: movl %fs:tls_var@TPOFF, %eax (correct) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 1d55a3b commit ffb625e

File tree

3 files changed

+76
-19
lines changed

3 files changed

+76
-19
lines changed

cc/arch/x86_64/codegen.rs

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ pub struct X86_64CodeGen {
4949
pub(super) unique_label_counter: u32,
5050
/// External symbols (need GOT access on macOS)
5151
pub(super) extern_symbols: HashSet<String>,
52+
/// Thread-local storage symbols (need TLS access via FS segment)
53+
pub(super) tls_symbols: HashSet<String>,
5254
/// Position-independent code mode (for shared libraries)
5355
pic_mode: bool,
5456
/// Long double constants to emit (label_bits -> value_bits)
@@ -70,6 +72,7 @@ impl X86_64CodeGen {
7072
num_fixed_fp_params: 0,
7173
unique_label_counter: 0,
7274
extern_symbols: HashSet::new(),
75+
tls_symbols: HashSet::new(),
7376
pic_mode: false,
7477
ld_constants: HashMap::new(),
7578
double_constants: HashMap::new(),
@@ -110,7 +113,12 @@ impl X86_64CodeGen {
110113
} else {
111114
Symbol::global(name.clone())
112115
};
113-
GpOperand::Mem(MemAddr::RipRelative(symbol))
116+
// Use TLS addressing for thread-local variables (Linux only)
117+
if self.tls_symbols.contains(name) && self.base.target.os == Os::Linux {
118+
GpOperand::Mem(MemAddr::TlsIE(symbol))
119+
} else {
120+
GpOperand::Mem(MemAddr::RipRelative(symbol))
121+
}
114122
}
115123
}
116124
}
@@ -1448,8 +1456,14 @@ impl X86_64CodeGen {
14481456
}
14491457
}
14501458
Loc::Global(name) => {
1451-
// LIR: RIP-relative memory-to-register move
1459+
// LIR: memory-to-register move
14521460
// Use local symbol for labels starting with '.' (e.g., .LC0 for string constants)
1461+
let symbol = if name.starts_with('.') {
1462+
Symbol::local(name.clone())
1463+
} else {
1464+
Symbol::global(name.clone())
1465+
};
1466+
14531467
if self.needs_got_access(&name) {
14541468
// External symbols on macOS: load address from GOT, then load value
14551469
// Use R11 as temp if dst is R11, otherwise use dst
@@ -1467,12 +1481,15 @@ impl X86_64CodeGen {
14671481
}),
14681482
dst: GpOperand::Reg(dst),
14691483
});
1484+
} else if self.tls_symbols.contains(&name) && self.base.target.os == Os::Linux {
1485+
// Thread-local storage: use FS segment (initial-exec model)
1486+
self.push_lir(X86Inst::Mov {
1487+
size: op_size,
1488+
src: GpOperand::Mem(MemAddr::TlsIE(symbol)),
1489+
dst: GpOperand::Reg(dst),
1490+
});
14701491
} else {
1471-
let symbol = if name.starts_with('.') {
1472-
Symbol::local(name.clone())
1473-
} else {
1474-
Symbol::global(name.clone())
1475-
};
1492+
// Regular RIP-relative access
14761493
self.push_lir(X86Inst::Mov {
14771494
size: op_size,
14781495
src: GpOperand::Mem(MemAddr::RipRelative(symbol)),
@@ -1842,21 +1859,28 @@ impl X86_64CodeGen {
18421859
} else {
18431860
Symbol::global(name.clone())
18441861
};
1862+
// Choose addressing mode: TLS or RIP-relative
1863+
let mem_addr =
1864+
if self.tls_symbols.contains(&name) && self.base.target.os == Os::Linux {
1865+
MemAddr::TlsIE(symbol)
1866+
} else {
1867+
MemAddr::RipRelative(symbol)
1868+
};
18451869
if mem_size <= 16 {
18461870
// LIR: sign/zero extending load from global
18471871
let src_size = OperandSize::from_bits(mem_size);
18481872
if is_unsigned {
18491873
self.push_lir(X86Inst::Movzx {
18501874
src_size,
18511875
dst_size: OperandSize::B32,
1852-
src: GpOperand::Mem(MemAddr::RipRelative(symbol.clone())),
1876+
src: GpOperand::Mem(mem_addr.clone()),
18531877
dst: dst_reg,
18541878
});
18551879
} else {
18561880
self.push_lir(X86Inst::Movsx {
18571881
src_size,
18581882
dst_size: OperandSize::B32,
1859-
src: GpOperand::Mem(MemAddr::RipRelative(symbol.clone())),
1883+
src: GpOperand::Mem(mem_addr.clone()),
18601884
dst: dst_reg,
18611885
});
18621886
}
@@ -1865,7 +1889,7 @@ impl X86_64CodeGen {
18651889
let op_size = OperandSize::from_bits(reg_size);
18661890
self.push_lir(X86Inst::Mov {
18671891
size: op_size,
1868-
src: GpOperand::Mem(MemAddr::RipRelative(symbol)),
1892+
src: GpOperand::Mem(mem_addr),
18691893
dst: GpOperand::Reg(dst_reg),
18701894
});
18711895
}
@@ -2022,6 +2046,12 @@ impl X86_64CodeGen {
20222046
// Use local symbol for labels starting with '.' (e.g., .LC0 for string constants)
20232047
let is_local_label = name.starts_with('.');
20242048
let op_size = OperandSize::from_bits(mem_size);
2049+
let symbol = if is_local_label {
2050+
Symbol::local(name.clone())
2051+
} else {
2052+
Symbol::global(name.clone())
2053+
};
2054+
20252055
if self.needs_got_access(&name) {
20262056
// External symbols on macOS: load address from GOT, then store
20272057
self.push_lir(X86Inst::Mov {
@@ -2037,12 +2067,14 @@ impl X86_64CodeGen {
20372067
offset: insn.offset as i32,
20382068
}),
20392069
});
2070+
} else if self.tls_symbols.contains(&name) && self.base.target.os == Os::Linux {
2071+
// Thread-local storage: use FS segment (initial-exec model)
2072+
self.push_lir(X86Inst::Mov {
2073+
size: op_size,
2074+
src: GpOperand::Reg(value_reg),
2075+
dst: GpOperand::Mem(MemAddr::TlsIE(symbol)),
2076+
});
20402077
} else {
2041-
let symbol = if is_local_label {
2042-
Symbol::local(name.clone())
2043-
} else {
2044-
Symbol::global(name.clone())
2045-
};
20462078
// LIR: store to global via RIP-relative
20472079
self.push_lir(X86Inst::Mov {
20482080
size: op_size,
@@ -3578,6 +3610,14 @@ impl CodeGenerator for X86_64CodeGen {
35783610
self.base.emit_debug = module.debug;
35793611
self.extern_symbols = module.extern_symbols.clone();
35803612

3613+
// Collect thread-local storage symbols
3614+
self.tls_symbols = module
3615+
.globals
3616+
.iter()
3617+
.filter(|g| g.is_thread_local)
3618+
.map(|g| g.name.clone())
3619+
.collect();
3620+
35813621
// Emit file header
35823622
self.emit_header();
35833623

cc/arch/x86_64/lir.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ pub enum MemAddr {
3535

3636
/// symbol@GOTPCREL(%rip) - GOT-relative addressing for external symbols on macOS
3737
GotPcrel(Symbol),
38+
39+
/// %fs:symbol@TPOFF - Thread-local storage initial-exec model (Linux x86-64)
40+
/// Uses FS segment register to access thread-local variables
41+
TlsIE(Symbol),
3842
}
3943

4044
impl MemAddr {
@@ -54,6 +58,10 @@ impl MemAddr {
5458
MemAddr::GotPcrel(sym) => {
5559
format!("{}@GOTPCREL(%rip)", sym.format_for_target(target))
5660
}
61+
MemAddr::TlsIE(sym) => {
62+
// Thread-local storage initial-exec model: %fs:symbol@TPOFF
63+
format!("%fs:{}@TPOFF", sym.format_for_target(target))
64+
}
5765
}
5866
}
5967
}

m4/tests/integration.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ fn load_fixture(name: &str) -> TestPlan {
9494
}
9595
}
9696

97-
9897
/// Checker for expect_error tests - only check that stderr is non-empty if expected
9998
fn expect_error_checker(plan: &TestPlan, output: &Output) {
10099
let stdout = String::from_utf8_lossy(&output.stdout);
@@ -105,7 +104,11 @@ fn expect_error_checker(plan: &TestPlan, output: &Output) {
105104
assert!(!output.stderr.is_empty(), "expected stderr to be non-empty");
106105
}
107106

108-
assert_eq!(output.status.code(), Some(plan.expected_exit_code), "exit code mismatch");
107+
assert_eq!(
108+
output.status.code(),
109+
Some(plan.expected_exit_code),
110+
"exit code mismatch"
111+
);
109112
}
110113

111114
/// Checker for stdout_regex tests
@@ -123,7 +126,11 @@ fn stdout_regex_checker(regex_pattern: &str) -> impl Fn(&TestPlan, &Output) + '_
123126
let stderr = String::from_utf8_lossy(&output.stderr);
124127
assert_eq!(stderr.as_ref(), &plan.expected_err, "stderr mismatch");
125128

126-
assert_eq!(output.status.code(), Some(plan.expected_exit_code), "exit code mismatch");
129+
assert_eq!(
130+
output.status.code(),
131+
Some(plan.expected_exit_code),
132+
"exit code mismatch"
133+
);
127134
}
128135
}
129136

@@ -207,7 +214,9 @@ fn file() {
207214
cmd: String::from("m4"),
208215
args: vec![String::from("fixtures/integration_tests/file.m4")],
209216
stdin_data: String::new(),
210-
expected_out: String::from("fixtures/integration_tests/file.m4\nfixtures/integration_tests/include/file.m4"),
217+
expected_out: String::from(
218+
"fixtures/integration_tests/file.m4\nfixtures/integration_tests/include/file.m4",
219+
),
211220
expected_err: String::new(),
212221
expected_exit_code: 0,
213222
});

0 commit comments

Comments
 (0)