decorations: allow zero-copy deserialization of strings.

This commit is contained in:
Eduard-Mihai Burtescu 2023-04-18 12:07:49 +03:00 committed by Eduard-Mihai Burtescu
parent b3067494e8
commit e7921fbf20
6 changed files with 81 additions and 35 deletions

View File

@ -656,13 +656,8 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
fn set_span(&mut self, span: Span) {
self.current_span = Some(span);
let loc = self.cx.tcx.sess.source_map().lookup_char_pos(span.lo());
let file = self.builder.def_debug_file(loc.file);
self.emit().line(
file.file_name_op_string_id,
loc.line as u32,
loc.col_display as u32,
);
let (file, line, col) = self.builder.file_line_col_for_op_line(span);
self.emit().line(file.file_name_op_string_id, line, col);
}
// FIXME(eddyb) change `Self::Function` to be more like a function index.

View File

@ -9,16 +9,19 @@ use rspirv::spirv::{
AddressingModel, Capability, MemoryModel, Op, SourceLanguage, StorageClass, Word,
};
use rspirv::{binary::Assemble, binary::Disassemble};
use rustc_arena::DroplessArena;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::sync::Lrc;
use rustc_middle::bug;
use rustc_middle::ty::TyCtxt;
use rustc_span::source_map::SourceMap;
use rustc_span::symbol::Symbol;
use rustc_span::{SourceFile, Span, DUMMY_SP};
use rustc_span::{FileName, FileNameDisplayPreference, SourceFile, Span, DUMMY_SP};
use std::assert_matches::assert_matches;
use std::cell::{RefCell, RefMut};
use std::hash::{Hash, Hasher};
use std::iter;
use std::str;
use std::{fs::File, io::Write, path::Path};
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
@ -354,9 +357,11 @@ impl Hash for DebugFileKey {
}
#[derive(Copy, Clone)]
pub struct DebugFileSpirv {
pub struct DebugFileSpirv<'tcx> {
pub file_name: &'tcx str,
/// The SPIR-V ID for the result of the `OpString` instruction containing
/// the file name - this is what e.g. `OpLine` uses to identify the file.
/// `file_name` - this is what e.g. `OpLine` uses to identify the file.
///
/// All other details about the file are also attached to this ID, using
/// other instructions that don't produce their own IDs (e.g. `OpSource`).
@ -395,6 +400,7 @@ pub struct BuilderCursor {
pub struct BuilderSpirv<'tcx> {
// HACK(eddyb) public only for `decorations`.
pub(crate) source_map: &'tcx SourceMap,
dropless_arena: &'tcx DroplessArena,
builder: RefCell<Builder>,
@ -405,7 +411,7 @@ pub struct BuilderSpirv<'tcx> {
const_to_id: RefCell<FxHashMap<WithType<SpirvConst<'tcx>>, WithConstLegality<Word>>>,
id_to_const: RefCell<FxHashMap<Word, WithConstLegality<SpirvConst<'tcx>>>>,
debug_file_cache: RefCell<FxHashMap<DebugFileKey, DebugFileSpirv>>,
debug_file_cache: RefCell<FxHashMap<DebugFileKey, DebugFileSpirv<'tcx>>>,
enabled_capabilities: FxHashSet<Capability>,
enabled_extensions: FxHashSet<Symbol>,
@ -413,7 +419,7 @@ pub struct BuilderSpirv<'tcx> {
impl<'tcx> BuilderSpirv<'tcx> {
pub fn new(
source_map: &'tcx SourceMap,
tcx: TyCtxt<'tcx>,
sym: &Symbols,
target: &SpirvTarget,
features: &[TargetFeature],
@ -478,7 +484,8 @@ impl<'tcx> BuilderSpirv<'tcx> {
builder.memory_model(AddressingModel::Logical, memory_model);
Self {
source_map,
source_map: tcx.sess.source_map(),
dropless_arena: &tcx.arena.dropless,
builder: RefCell::new(builder),
const_to_id: Default::default(),
id_to_const: Default::default(),
@ -689,7 +696,17 @@ impl<'tcx> BuilderSpirv<'tcx> {
}
}
pub fn def_debug_file(&self, sf: Lrc<SourceFile>) -> DebugFileSpirv {
pub fn file_line_col_for_op_line(&self, span: Span) -> (DebugFileSpirv<'tcx>, u32, u32) {
let loc = self.source_map.lookup_char_pos(span.lo());
(
self.def_debug_file(loc.file),
loc.line as u32,
loc.col_display as u32,
)
}
// HACK(eddyb) public only for `decorations`.
pub(crate) fn def_debug_file(&self, sf: Lrc<SourceFile>) -> DebugFileSpirv<'tcx> {
*self
.debug_file_cache
.borrow_mut()
@ -697,7 +714,34 @@ impl<'tcx> BuilderSpirv<'tcx> {
.or_insert_with_key(|DebugFileKey(sf)| {
let mut builder = self.builder(Default::default());
let file_name_op_string_id = builder.string(sf.name.prefer_remapped().to_string());
// FIXME(eddyb) it would be nicer if we could just rely on
// `RealFileName::to_string_lossy` returning `Cow<'_, str>`,
// but sadly that `'_` is the lifetime of the temporary `Lrc`,
// not `'tcx`, so we have to arena-allocate to get `&'tcx str`.
let file_name = match &sf.name {
FileName::Real(name) => {
name.to_string_lossy(FileNameDisplayPreference::Remapped)
}
_ => sf.name.prefer_remapped().to_string().into(),
};
let file_name = {
// FIXME(eddyb) it should be possible to arena-allocate a
// `&str` directly, but it would require upstream changes,
// and strings are handled by string interning in `rustc`.
fn arena_alloc_slice<'tcx, T: Copy>(
dropless_arena: &'tcx DroplessArena,
xs: &[T],
) -> &'tcx [T] {
if xs.is_empty() {
&[]
} else {
dropless_arena.alloc_slice(xs)
}
}
str::from_utf8(arena_alloc_slice(self.dropless_arena, file_name.as_bytes()))
.unwrap()
};
let file_name_op_string_id = builder.string(file_name.to_owned());
let file_contents = self
.source_map
@ -752,6 +796,7 @@ impl<'tcx> BuilderSpirv<'tcx> {
}
DebugFileSpirv {
file_name,
file_name_op_string_id,
}
})

View File

@ -53,7 +53,7 @@ pub struct CodegenCx<'tcx> {
/// Invalid spir-v IDs that should be stripped from the final binary,
/// each with its own reason and span that should be used for reporting
/// (in the event that the value is actually needed)
zombie_decorations: RefCell<FxHashMap<Word, ZombieDecoration>>,
zombie_decorations: RefCell<FxHashMap<Word, ZombieDecoration<'tcx>>>,
/// Cache of all the builtin symbols we need
pub sym: Rc<Symbols>,
pub instruction_table: InstructionTable,
@ -117,7 +117,7 @@ impl<'tcx> CodegenCx<'tcx> {
Self {
tcx,
codegen_unit,
builder: BuilderSpirv::new(tcx.sess.source_map(), &sym, &target, &features),
builder: BuilderSpirv::new(tcx, &sym, &target, &features),
instances: Default::default(),
function_parameter_values: Default::default(),
type_cache: Default::default(),
@ -188,7 +188,9 @@ impl<'tcx> CodegenCx<'tcx> {
self.zombie_decorations.borrow_mut().insert(
word,
ZombieDecoration {
reason: reason.to_string(),
// FIXME(eddyb) this could take advantage of `Cow` and use
// either `&'static str` or `String`, on a case-by-case basis.
reason: reason.to_string().into(),
span: SerializedSpan::from_rustc(span, &self.builder),
},
);

View File

@ -108,8 +108,11 @@ impl<D> Clone for LazilyDeserialized<'_, D> {
}
}
impl<D: for<'a> Deserialize<'a>> LazilyDeserialized<'_, D> {
pub fn deserialize(&self) -> D {
impl<D> LazilyDeserialized<'_, D> {
pub fn deserialize<'a>(&'a self) -> D
where
D: Deserialize<'a>,
{
serde_json::from_str(&self.json).unwrap()
}
@ -123,29 +126,29 @@ impl<D: for<'a> Deserialize<'a>> LazilyDeserialized<'_, D> {
}
#[derive(Deserialize, Serialize)]
pub struct ZombieDecoration {
pub reason: String,
pub struct ZombieDecoration<'a> {
pub reason: Cow<'a, str>,
#[serde(flatten)]
pub span: Option<SerializedSpan>,
pub span: Option<SerializedSpan<'a>>,
}
impl CustomDecoration for ZombieDecoration {
impl CustomDecoration for ZombieDecoration<'_> {
const ENCODING_PREFIX: &'static str = "Z";
}
/// Representation of a `rustc` `Span` that can be turned into a `Span` again
/// in another compilation, by regenerating the `rustc` `SourceFile`.
#[derive(Deserialize, Serialize)]
pub struct SerializedSpan {
file_name: String,
pub struct SerializedSpan<'a> {
file_name: Cow<'a, str>,
// NOTE(eddyb) by keeping `lo` but not `hi`, we mimick `OpLine` limitations
// (which could be lifted in the future using custom SPIR-T debuginfo).
lo: u32,
}
impl SerializedSpan {
pub fn from_rustc(span: Span, builder: &BuilderSpirv<'_>) -> Option<Self> {
impl<'tcx> SerializedSpan<'tcx> {
pub fn from_rustc(span: Span, builder: &BuilderSpirv<'tcx>) -> Option<Self> {
// Decorations may not always have valid spans.
// FIXME(eddyb) reduce the sources of this as much as possible.
if span.is_dummy() {
@ -154,18 +157,18 @@ impl SerializedSpan {
let lo = span.lo();
let file = builder.source_map.lookup_source_file(lo);
if !(file.start_pos..=file.end_pos).contains(&lo) {
let sf = builder.source_map.lookup_source_file(lo);
if !(sf.start_pos..=sf.end_pos).contains(&lo) {
// FIXME(eddyb) broken `Span` - potentially turn this into an assert?
return None;
}
// NOTE(eddyb) this emits necessary `OpString`/`OpSource` instructions.
builder.def_debug_file(file.clone());
let file = builder.def_debug_file(sf.clone());
Some(Self {
file_name: file.name.prefer_remapped().to_string(),
lo: (lo - file.start_pos).to_u32(),
file_name: file.file_name.into(),
lo: (lo - sf.start_pos).to_u32(),
})
}
}
@ -287,7 +290,7 @@ impl<'a> SpanRegenerator<'a> {
file.as_deref()
}
pub fn serialized_span_to_rustc(&mut self, span: &SerializedSpan) -> Option<Span> {
pub fn serialized_span_to_rustc(&mut self, span: &SerializedSpan<'_>) -> Option<Span> {
let file = self.regenerate_rustc_source_file(&span.file_name[..])?;
// Sanity check - assuming `SerializedSpan` isn't corrupted, this assert

View File

@ -40,6 +40,7 @@ compile_error!(
);
extern crate rustc_apfloat;
extern crate rustc_arena;
extern crate rustc_ast;
extern crate rustc_attr;
extern crate rustc_codegen_ssa;

View File

@ -12,7 +12,7 @@ use std::iter::once;
// FIXME(eddyb) change this to chain through IDs instead of wasting allocations.
#[derive(Clone)]
struct ZombieInfo<'a> {
serialized: &'a LazilyDeserialized<'static, ZombieDecoration>,
serialized: &'a LazilyDeserialized<'static, ZombieDecoration<'a>>,
stack: Vec<Word>,
}