Skip to main content

wamex_cli/emit/memory_layout/
mod.rs

1use std::{
2    borrow::Cow,
3    collections::{BTreeMap, BTreeSet},
4    fmt::Debug,
5    io::IsTerminal,
6};
7
8use anyhow::Result;
9use wasmparser::{Data, DataKind, SymbolFlags};
10
11use crate::{
12    analysis::{
13        self,
14        symbols::{self, SymbolKind},
15    },
16    helpers::{RangeComp, RangeExt},
17    index::{Id, IdVec, Indexed, SymbolId},
18};
19mod hexdump;
20
21/// Describes how a data symbol relates to its neighboring symbols within a segment.
22#[derive(Clone, Debug)]
23pub enum SymbolRelation<'a> {
24    /// A standalone symbol with no binding constraints.
25    Regular {
26        chunk: &'a [u8],
27        // true if data was properly aligned.
28        // If data was not aligned, it will not be aligned in output.
29        // It can report false-positive. But it is okay to align data on bigger alignment.
30        //
31        // Linking table does not contain information about symbol alignment.
32        // We use size + segment alignment to calculate if data was aligned properly.
33        aligned: bool,
34    },
35
36    /// A symbol that must stay adjacent to the previous symbol
37    /// and cannot be moved or removed independently.
38    BoundToPrevious {
39        /// minus offset from end of previous symbol to start of this symbol.
40        offset: usize,
41        len: usize,
42    },
43}
44
45#[derive(Clone, Debug)]
46pub struct DataChunk<'a> {
47    name: Cow<'a, str>,
48    #[allow(dead_code)]
49    flags: SymbolFlags,
50    relation: SymbolRelation<'a>,
51    symbol_index: SymbolId,
52
53    // offset related to this symbol
54    relocations: Vec<wasmparser::RelocationEntry>,
55}
56
57impl DataChunk<'_> {
58    pub fn name(&self) -> &str {
59        &self.name
60    }
61    pub fn relocations(&self) -> &[wasmparser::RelocationEntry] {
62        &self.relocations
63    }
64    //TODO: Don't expose in public API
65    pub fn symbol_relation(&self) -> &SymbolRelation<'_> {
66        &self.relation
67    }
68}
69
70#[derive(Clone)]
71pub struct SegmentLayout<'a> {
72    data_parts: Vec<DataChunk<'a>>,
73    alignment: usize,
74    kind: DataKind<'a>,
75    mem_offset: usize,
76}
77
78impl Debug for SegmentLayout<'_> {
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        let kind = match &self.kind {
81            DataKind::Passive => "Passive".to_string(),
82            DataKind::Active {
83                offset_expr,
84                memory_index,
85            } => {
86                let offset_expr = offset_expr.get_operators_reader().into_iter().fold(
87                    String::new(),
88                    |mut val, op| {
89                        let operator = op.expect("Expected operator in offset expression");
90                        if !val.is_empty() {
91                            val.push(' ');
92                        }
93                        val.push_str(&format!("{:?}", operator));
94                        val
95                    },
96                );
97                format!("Active(memory:{memory_index}, offset:{})", offset_expr)
98            }
99        };
100        write!(
101            f,
102            "DataSegment {{ data_parts: {:?}, kind: {} }}",
103            self.data_parts, kind
104        )
105    }
106}
107
108impl<'src> SegmentLayout<'src> {
109    pub fn new_inner<'a>(
110        data: &Data<'src>,
111        segment_info: &wasmparser::Segment<'src>,
112        data_symbols: impl Iterator<Item = (SymbolId, &'a analysis::symbols::SymbolRecord<'src>)>,
113    ) -> Result<SegmentLayout<'src>>
114    where
115        'src: 'a,
116    {
117        let alignment = (2usize).pow(segment_info.alignment);
118
119        let mem_offset = match &data.kind {
120            DataKind::Passive => panic!("Passive data is not currently supported"),
121            DataKind::Active { offset_expr, .. } => {
122                crate::analysis::ModuleInfo::read_const_expr(offset_expr)?
123            }
124        };
125
126        log::debug!("Memory offset is {}", mem_offset);
127        log::debug!(
128            "Segment alignment is {}, aligned = {}",
129            alignment,
130            mem_offset % alignment as i32 == 0
131        );
132
133        let mut data_parts = vec![];
134        let mut last_regular = 0..0;
135        for (sym_id, sym) in data_symbols {
136            let SymbolKind::DataDefined { offset, length, .. } = &sym.kind else {
137                unreachable!("Expected DataDefined symbol kind for data segment symbol");
138            };
139
140            let symbol_in_data = offset.clone()..(offset + length);
141            let field_alignment = Self::data_symbol_alignment(alignment, symbol_in_data.len());
142
143            let relation = if last_regular.end > symbol_in_data.start {
144                log::warn!(
145                    "DataSymbol intersects with previous, this is currently in testing: {:?} range:{:?} prev_range: {:?}",
146                    sym,
147                    symbol_in_data,
148                    last_regular
149                );
150                assert!(matches!(
151                    last_regular.cmp_range(&symbol_in_data),
152                    RangeComp::Overlap | RangeComp::Equal
153                ));
154                let offset = last_regular.end - symbol_in_data.start;
155                SymbolRelation::BoundToPrevious {
156                    offset,
157                    len: symbol_in_data.len(),
158                }
159            } else {
160                if last_regular.end < symbol_in_data.start {
161                    let gap_range = last_regular.end..symbol_in_data.start;
162
163                    if gap_range.len() >= alignment {
164                        log::error!(
165                            "Data segment has gap larger than alignment: {:?} > {} before {}",
166                            gap_range,
167                            alignment,
168                            sym.name
169                        );
170                    } else {
171                        log::debug!(
172                            "Data segment has gap: {:?} ({} bytes) before {}",
173                            gap_range,
174                            gap_range.len(),
175                            sym.name
176                        );
177                    }
178                }
179                let aligned =
180                    (symbol_in_data.start + mem_offset as usize).is_multiple_of(field_alignment);
181
182                log::trace!(
183                    "Data symbol {}: offset: {}, size: {}, aligned: {}, alignment: {}",
184                    sym.name,
185                    symbol_in_data.start,
186                    symbol_in_data.len(),
187                    aligned,
188                    field_alignment
189                );
190                last_regular = symbol_in_data.clone();
191                SymbolRelation::Regular {
192                    chunk: &data.data[symbol_in_data.clone()],
193                    aligned,
194                }
195            };
196
197            let part = DataChunk {
198                name: sym.name.clone(),
199                flags: sym.flags,
200                relocations: sym.relocs.clone(),
201                symbol_index: sym_id,
202                relation,
203            };
204            log::trace!("Data part: {part:?}");
205            data_parts.push(part)
206        }
207
208        let kind = data.kind.clone();
209
210        Ok(SegmentLayout {
211            alignment,
212            data_parts,
213            kind,
214            mem_offset: mem_offset as usize,
215        })
216    }
217
218    pub fn debug_layout(
219        symbol_table: &symbols::SymbolMap,
220        module_name: String,
221        data_segments: &IdVec<SegmentLayout<'_>>,
222    ) {
223        use std::fmt::Write;
224        let mut print_data_format = String::new();
225        writeln!(print_data_format, "<Module {module_name}>").unwrap();
226
227        let mut base = 0;
228        for (i, segment) in data_segments.iter() {
229            for symbol in segment.data_parts.iter() {
230                writeln!(
231                    print_data_format,
232                    "Data symbol [{i}.{index}]: {name}",
233                    i = i,
234                    index = symbol.symbol_index,
235                    name = symbol.name(),
236                )
237                .unwrap();
238                match symbol.symbol_relation() {
239                    SymbolRelation::Regular { chunk, .. } => {
240                        let input_symbol = symbol_table.get(symbol.symbol_index).unwrap();
241                        let refs = input_symbol
242                            .relocs
243                            .iter()
244                            .map(|reloc| {
245                                let reloc_symbol =
246                                    symbol_table.get(Id::from_index(reloc.index)).unwrap();
247                                hexdump::Ref {
248                                    range: reloc.relocation_range(),
249                                    name: &reloc_symbol.name,
250                                }
251                            })
252                            .collect();
253                        let part = hexdump::DataPart { bytes: chunk, refs };
254                        hexdump::render_part(
255                            &mut print_data_format,
256                            base,
257                            &part,
258                            std::io::stderr().is_terminal(),
259                        );
260                        // TODO: add padding
261                        base += chunk.len();
262                    }
263                    SymbolRelation::BoundToPrevious { .. } => {
264                        writeln!(print_data_format, "<bound to previous>").unwrap()
265                    }
266                };
267            }
268        }
269        println!("Data segments {print_data_format}");
270    }
271    pub fn memory_offset(&self) -> usize {
272        self.mem_offset
273    }
274
275    // Keeps only symbols with id is in `indexes`.
276    pub fn new_with_whitelist(mut self, indexes: &BTreeSet<SymbolId>) -> Self {
277        let mut result = vec![];
278
279        {
280            let mut parts_iter = self.data_parts.drain(..).peekable();
281            let mut last_regular_removed = false;
282            for item in &mut parts_iter {
283                let remove = !indexes.contains(&item.symbol_index);
284
285                match item.relation {
286                    SymbolRelation::BoundToPrevious { .. } => {
287                        if remove != last_regular_removed {
288                            // TODO: Add dep in DepGraph for BoundToPrevious symbol
289                            log::error!(
290                                "BUG: Data segment symbol {} has bound to symbol that was removed, but previous symbol removed: {}",
291                                item.symbol_index,
292                                last_regular_removed
293                            );
294                        }
295                    }
296                    SymbolRelation::Regular { .. } => {
297                        last_regular_removed = remove;
298                    }
299                }
300                if remove {
301                    continue;
302                }
303                result.push(item);
304            }
305        }
306
307        self.data_parts = result;
308        self
309    }
310
311    pub fn data_len(&self, segment_offset: usize) -> usize {
312        let mut len = 0;
313
314        for data_part in &self.data_parts {
315            let SymbolRelation::Regular { chunk, aligned } = data_part.relation else {
316                // BoundToPrevious symbols are not counted in data length
317                continue;
318            };
319            let current_offset = segment_offset + len;
320
321            // If we're not aligned, add padding
322            if aligned {
323                let field_alignment = Self::data_symbol_alignment(self.alignment, chunk.len());
324                let padding = Self::calculate_padding(current_offset, field_alignment);
325                len += padding;
326            }
327
328            len += chunk.len();
329        }
330
331        len
332    }
333
334    /// Compute data init offset.
335    /// Returns (offset_expr, segment_offset)
336    fn segment_header(
337        &self,
338        mem_start: usize,
339        mut segment_offset: usize,
340        lib_base_global_id: Option<u32>,
341    ) -> (Option<wasm_encoder::ConstExpr>, usize) {
342        match self.kind {
343            DataKind::Passive => (None, 0),
344            DataKind::Active { .. } => {
345                let offset_expr = match lib_base_global_id {
346                    None => {
347                        segment_offset +=
348                            Self::calculate_padding(mem_start + segment_offset, self.alignment);
349                        wasm_encoder::ConstExpr::i32_const(
350                            (mem_start + segment_offset).try_into().unwrap(),
351                        )
352                    }
353                    Some(lib_base_global_id) => {
354                        // submodules use lib_base_id
355                        {
356                            segment_offset +=
357                                Self::calculate_padding(segment_offset, self.alignment);
358                            wasm_encoder::ConstExpr::global_get(lib_base_global_id)
359                                .with_i32_const(segment_offset.try_into().unwrap())
360                                .with_i32_add()
361                        }
362                    }
363                };
364                (Some(offset_expr), segment_offset)
365            }
366        }
367    }
368
369    fn data_symbol_alignment(segment_alignment: usize, chunk_size: usize) -> usize {
370        if chunk_size == 0 {
371            return 1;
372        }
373        let alignment = 1usize << chunk_size.trailing_zeros();
374        std::cmp::min(segment_alignment, alignment)
375    }
376
377    fn calculate_padding(starting_point: usize, alignment: usize) -> usize {
378        let misalignment = starting_point % alignment;
379        if misalignment == 0 {
380            0
381        } else {
382            alignment - misalignment
383        }
384    }
385
386    /// Compute data init offset and alligned segment_offset.
387    pub fn to_segment_output(
388        &self,
389        lib_base_global_id: Option<u32>,
390        mem_start: usize,
391        segment_offset: usize,
392        //TODO: move segment_offset padding outside
393    ) -> (usize, DataSegmentOutput) {
394        const BYTE_FILLER: u8 = 0;
395
396        let mut data = Vec::new();
397
398        log::debug!("Segment offset before is {}", mem_start + segment_offset);
399        let (data_init, segment_offset) =
400            self.segment_header(mem_start, segment_offset, lib_base_global_id);
401
402        log::debug!("Segment offset is {}", mem_start + segment_offset);
403        let mut globals = BTreeMap::new();
404        let segment_in_mem_start = mem_start + segment_offset;
405        for symbol in self.data_parts.iter() {
406            match symbol.relation {
407                SymbolRelation::BoundToPrevious { offset, .. } => {
408                    // BoundToPrevious symbols are not counted in data length
409                    log::debug!(
410                        "BoundToPrevious symbol {}: {offset} is not counted in data length",
411                        symbol.name
412                    );
413
414                    globals.insert(
415                        symbol.symbol_index,
416                        DataSymbolRefs {
417                            data_mem_offset: data.len() - offset,
418                        },
419                    );
420                }
421                SymbolRelation::Regular { chunk, aligned } => {
422                    let total_offset = data.len() + segment_in_mem_start;
423
424                    // add padding to align data
425                    if aligned {
426                        let field_alignment =
427                            Self::data_symbol_alignment(self.alignment, chunk.len());
428
429                        let padding = Self::calculate_padding(total_offset, field_alignment);
430                        if padding > 0 {
431                            log::debug!(
432                                "Add padding before data symbol {}: {padding} bytes",
433                                symbol.name
434                            );
435
436                            data.resize(data.len() + padding, BYTE_FILLER);
437                        }
438                    }
439                    log::trace!(
440                        "Data symbol {}: offset: {}, size: {}, aligned: {}",
441                        symbol.name,
442                        data.len(),
443                        chunk.len(),
444                        aligned
445                    );
446
447                    globals.insert(
448                        symbol.symbol_index,
449                        DataSymbolRefs {
450                            data_mem_offset: data.len(),
451                        },
452                    );
453                    data.extend_from_slice(chunk);
454                }
455            }
456        }
457        (
458            segment_offset,
459            DataSegmentOutput {
460                data_init: data_init.expect("Active data segment should have offset"),
461                data,
462                data_symbols: globals,
463                memory_offset: mem_start + segment_offset,
464            },
465        )
466    }
467}
468
469#[derive(Debug)]
470pub struct DataSymbolRefs {
471    // Relative to lib_base for submodules
472    pub data_mem_offset: usize,
473}
474
475// generate data segment and global initializers
476/// Representation of calculated data segment for output module.
477/// Contain data chunk
478#[derive(Debug)]
479pub struct DataSegmentOutput {
480    // only for active segments
481    data_init: wasm_encoder::ConstExpr,
482    memory_offset: usize,
483
484    data: Vec<u8>,
485    data_symbols: BTreeMap<SymbolId, DataSymbolRefs>,
486}
487
488impl DataSegmentOutput {
489    pub fn data_segment<'a>(&'a self, memory_index: u32) -> wasm_encoder::DataSegment<'a, Vec<u8>> {
490        wasm_encoder::DataSegment {
491            mode: wasm_encoder::DataSegmentMode::Active {
492                memory_index,
493                offset: &self.data_init,
494            },
495
496            data: self.data.clone(),
497        }
498    }
499    pub fn as_raw(&self) -> &[u8] {
500        &self.data
501    }
502    pub fn memory_offset(&self) -> usize {
503        self.memory_offset
504    }
505    pub fn symbols(&self) -> &BTreeMap<SymbolId, DataSymbolRefs> {
506        &self.data_symbols
507    }
508}
509
510impl<'a> Indexed for crate::emit::SegmentLayout<'a> {
511    type StaticTypeTagForIndex = Data<'static>;
512    type IndexType = crate::index::Id<Self::StaticTypeTagForIndex>;
513}
514
515impl Indexed for crate::emit::DataSegmentOutput {
516    type StaticTypeTagForIndex = Data<'static>;
517    type IndexType = crate::index::Id<Self::StaticTypeTagForIndex>;
518}