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#[derive(Clone, Debug)]
23pub enum SymbolRelation<'a> {
24 Regular {
26 chunk: &'a [u8],
27 aligned: bool,
34 },
35
36 BoundToPrevious {
39 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 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 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 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 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 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 continue;
318 };
319 let current_offset = segment_offset + len;
320
321 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 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 {
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 pub fn to_segment_output(
388 &self,
389 lib_base_global_id: Option<u32>,
390 mem_start: usize,
391 segment_offset: usize,
392 ) -> (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 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 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 pub data_mem_offset: usize,
473}
474
475#[derive(Debug)]
479pub struct DataSegmentOutput {
480 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}