Skip to content

Commit 7a2a604

Browse files
committed
reserve space in constant memory for statics explicitly placed by the user
1 parent 68a9509 commit 7a2a604

1 file changed

Lines changed: 87 additions & 11 deletions

File tree

crates/rustc_codegen_nvvm/src/context.rs

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use rustc_middle::ty::layout::{FnAbiOfHelpers, LayoutOfHelpers};
2828
use rustc_middle::ty::{Ty, TypeVisitableExt};
2929
use rustc_middle::{bug, span_bug, ty};
3030
use rustc_middle::{
31-
mir::mono::CodegenUnit,
31+
mir::mono::{CodegenUnit, MonoItem},
3232
ty::{Instance, TyCtxt},
3333
};
3434
use rustc_session::Session;
@@ -110,6 +110,9 @@ pub(crate) struct CodegenCx<'ll, 'tcx> {
110110

111111
/// Tracks cumulative constant memory usage in bytes for compile-time diagnostics
112112
constant_memory_usage: Cell<u64>,
113+
/// Pre-reserved constant memory bytes for statics with explicit placement overrides.
114+
/// Computed lazily on first call to `static_addrspace`.
115+
constant_memory_reserved: Cell<Option<u64>>,
113116
}
114117

115118
impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> {
@@ -181,6 +184,7 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> {
181184
codegen_args: CodegenArgs::from_session(tcx.sess()),
182185
last_call_llfn: Cell::new(None),
183186
constant_memory_usage: Cell::new(0),
187+
constant_memory_reserved: Cell::new(None),
184188
};
185189
cx.build_intrinsics_map();
186190
cx
@@ -307,13 +311,68 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> {
307311
None
308312
}
309313

314+
/// Computes the total constant memory bytes reserved for statics with explicit
315+
/// placement overrides (`place_static`/`crate_memory_space` requesting constant).
316+
/// This is computed lazily on the first call and cached.
317+
///
318+
/// By pre-reserving this space, automatic placement only fills whatever remains,
319+
/// ensuring explicitly requested statics always fit (as long as they fit together).
320+
fn get_constant_memory_reserved(&self) -> u64 {
321+
if let Some(reserved) = self.constant_memory_reserved.get() {
322+
return reserved;
323+
}
324+
325+
let mut reserved: u64 = 0;
326+
for (&item, _) in self.codegen_unit.items() {
327+
if let MonoItem::Static(def_id) = item {
328+
let instance = Instance::mono(self.tcx, def_id);
329+
let ty = instance.ty(self.tcx, self.typing_env());
330+
331+
// Skip statics that can't go in constant memory
332+
let is_mutable = self.tcx().is_mutable_static(def_id);
333+
if is_mutable || !self.type_is_freeze(ty) {
334+
continue;
335+
}
336+
337+
// Skip statics with explicit #[address_space] attributes
338+
let attrs = self.tcx.get_all_attrs(def_id);
339+
let nvvm_attrs = NvvmAttributes::parse(self, attrs);
340+
if nvvm_attrs.addrspace.is_some() {
341+
continue;
342+
}
343+
344+
// Only reserve for explicit overrides requesting constant memory
345+
if let Some(MemorySpace::Constant) = self.resolve_memory_space(instance) {
346+
let layout = self.layout_of(ty);
347+
reserved += layout.size.bytes();
348+
}
349+
}
350+
}
351+
352+
if reserved > CONSTANT_MEMORY_SIZE_LIMIT_BYTES {
353+
self.tcx.sess.dcx().warn(format!(
354+
"explicitly placed statics require {reserved} bytes of constant memory, \
355+
which exceeds the {} byte limit; this will likely cause runtime errors",
356+
CONSTANT_MEMORY_SIZE_LIMIT_BYTES
357+
));
358+
}
359+
360+
self.constant_memory_reserved.set(Some(reserved));
361+
trace!("Pre-reserved {reserved} bytes of constant memory for explicitly placed statics");
362+
reserved
363+
}
364+
310365
/// Computes the address space for a static.
311366
///
312367
/// Priority system (highest to lowest):
313368
/// 1. Explicit `#[cuda_std::address_space(...)]` attribute
314369
/// 2. Per-static path override via CudaBuilder (`place_static`)
315370
/// 3. Per-crate override via CudaBuilder (`crate_memory_space`)
316371
/// 4. Global `use_constant_memory_space` flag
372+
///
373+
/// Statics with explicit overrides (priorities 2-3) requesting constant memory
374+
/// have their space pre-reserved, so automatic placement (priority 4) only fills
375+
/// whatever remains. This ensures explicitly placed statics are packed first.
317376
pub fn static_addrspace(&self, instance: Instance<'tcx>) -> AddressSpace {
318377
let ty = instance.ty(self.tcx, self.typing_env());
319378
let is_mutable = self.tcx().is_mutable_static(instance.def_id());
@@ -331,7 +390,9 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> {
331390
}
332391

333392
// Resolve memory space from overrides (priorities 2-4)
334-
let want_constant = match self.resolve_memory_space(instance) {
393+
let resolved = self.resolve_memory_space(instance);
394+
let explicit_constant = matches!(resolved, Some(MemorySpace::Constant));
395+
let want_constant = match resolved {
335396
Some(MemorySpace::Constant) => true,
336397
Some(MemorySpace::Global) => false,
337398
None => self.codegen_args.use_constant_memory_space,
@@ -347,15 +408,24 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> {
347408
let current_usage = self.constant_memory_usage.get();
348409
let new_usage = current_usage + size_bytes;
349410

411+
// For automatic placement, the effective limit is reduced by the space
412+
// reserved for explicitly placed statics (so they always fit).
413+
// For explicit overrides, use the full limit.
414+
let effective_limit = if explicit_constant {
415+
CONSTANT_MEMORY_SIZE_LIMIT_BYTES
416+
} else {
417+
let reserved = self.get_constant_memory_reserved();
418+
CONSTANT_MEMORY_SIZE_LIMIT_BYTES.saturating_sub(reserved)
419+
};
420+
350421
// Check if this single static is too large for constant memory
351-
if size_bytes > CONSTANT_MEMORY_SIZE_LIMIT_BYTES {
422+
if size_bytes > effective_limit {
352423
let def_id = instance.def_id();
353424
let span = self.tcx.def_span(def_id);
354425
let mut diag = self.tcx.sess.dcx().struct_span_warn(
355426
span,
356427
format!(
357-
"static `{instance}` is {size_bytes} bytes, exceeds the constant memory limit of {} bytes",
358-
CONSTANT_MEMORY_SIZE_LIMIT_BYTES
428+
"static `{instance}` is {size_bytes} bytes, exceeds the constant memory limit of {effective_limit} bytes",
359429
),
360430
);
361431
diag.span_label(span, "static exceeds constant memory limit");
@@ -367,23 +437,29 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> {
367437

368438
// Check if adding this static would exceed the cumulative limit
369439
// Auto-spill to global memory with a warning instead of failing
370-
if new_usage > CONSTANT_MEMORY_SIZE_LIMIT_BYTES {
440+
if new_usage > effective_limit {
371441
let def_id = instance.def_id();
372442
let span = self.tcx.def_span(def_id);
373-
let remaining = CONSTANT_MEMORY_SIZE_LIMIT_BYTES.saturating_sub(current_usage);
443+
let remaining = effective_limit.saturating_sub(current_usage);
374444
let mut diag = self.tcx.sess.dcx().struct_span_warn(
375445
span,
376446
format!(
377447
"constant memory overflow: static `{instance}` ({size_bytes} bytes) does not fit in remaining \
378-
constant memory ({remaining} bytes free of {} bytes total)",
379-
CONSTANT_MEMORY_SIZE_LIMIT_BYTES
448+
constant memory ({remaining} bytes free of {effective_limit} bytes available)",
380449
),
381450
);
382451
diag.span_label(span, "automatically placed in global memory");
383452
diag.note(format!(
384-
"current constant memory usage: {current_usage} / {} bytes",
385-
CONSTANT_MEMORY_SIZE_LIMIT_BYTES
453+
"current constant memory usage: {current_usage} / {effective_limit} bytes",
386454
));
455+
if !explicit_constant {
456+
let reserved = self.get_constant_memory_reserved();
457+
if reserved > 0 {
458+
diag.note(format!(
459+
"{reserved} bytes reserved for explicitly placed statics",
460+
));
461+
}
462+
}
387463
diag.help("use `.place_static(\"path\", MemorySpace::Constant)` in build.rs to prioritize specific statics for constant memory");
388464
diag.emit();
389465
return AddressSpace(1);

0 commit comments

Comments
 (0)