1use rucc_base::{Interner, Symbol};
42use rucc_ir as ir;
43use rucc_ir::{AliasKind, Datum, GlobalId, Linkage, Module, SymbolRef};
44use rucc_object::{Alias, Apart, Binding, Data, Object, Place, Reference, Reloc, Visibility};
45use rucc_target::ObjectFormat;
46
47use crate::Error;
48
49#[derive(Debug, Clone, Default, PartialEq, Eq)]
51pub struct Globals {
52 pub vars: Vec<Variable>,
56 pub weak: Vec<String>,
73}
74
75#[derive(Debug, Clone, PartialEq, Eq)]
77pub struct Variable {
78 pub name: String,
82 pub size: u64,
84 pub align: u64,
86 pub place: Place,
88 pub binding: Binding,
90 pub visibility: Visibility,
92 pub pieces: Vec<Piece>,
94}
95
96#[derive(Debug, Clone, PartialEq, Eq)]
98pub enum Piece {
99 Zero(u64),
102 Bytes(Vec<u8>),
104 Scalar(Vec<u8>),
106 Addr {
108 symbol: String,
110 addend: i64,
112 bytes: u8,
114 },
115 Away {
118 symbol: String,
120 addend: i64,
123 },
124 Apart {
127 to: String,
129 from: String,
131 addend: i64,
133 bytes: u8,
135 },
136}
137
138impl Piece {
139 #[must_use]
141 pub fn size(&self) -> u64 {
142 match self {
143 Piece::Zero(bytes) => *bytes,
144 Piece::Bytes(bytes) | Piece::Scalar(bytes) => bytes.len() as u64,
145 Piece::Addr { bytes, .. } | Piece::Apart { bytes, .. } => u64::from(*bytes),
146 Piece::Away { .. } => 4,
147 }
148 }
149}
150
151impl Globals {
152 #[must_use]
157 pub fn image(&self) -> Data {
158 let mut data = Data { weak: self.weak.clone(), ..Data::default() };
159 for var in &self.vars {
160 let mut object = Object {
161 name: var.name.clone(),
162 bytes: Vec::new(),
163 size: var.size,
164 align: var.align,
165 place: var.place.clone(),
166 binding: var.binding,
167 visibility: var.visibility,
168 relocs: Vec::new(),
169 };
170 if matches!(var.place, Place::Zero | Place::Merged | Place::Thread { zero: true }) {
171 data.objects.push(object);
172 continue;
173 }
174 for piece in &var.pieces {
175 match piece {
176 Piece::Zero(bytes) => {
177 object.bytes.resize(object.bytes.len() + *bytes as usize, 0);
178 }
179 Piece::Bytes(bytes) | Piece::Scalar(bytes) => {
180 object.bytes.extend_from_slice(bytes);
181 }
182 Piece::Addr { symbol, addend, bytes } => {
183 object.relocs.push(Reloc {
186 at: object.bytes.len(),
187 symbol: symbol.clone(),
188 kind: Reference::Address { bytes: *bytes },
189 addend: *addend,
190 after: 0,
193 });
194 object.bytes.resize(object.bytes.len() + usize::from(*bytes), 0);
195 }
196 Piece::Away { symbol, addend } => {
197 object.relocs.push(Reloc {
198 at: object.bytes.len(),
199 symbol: symbol.clone(),
200 kind: Reference::Away,
201 addend: *addend,
202 after: 0,
203 });
204 object.bytes.resize(object.bytes.len() + 4, 0);
205 }
206 Piece::Apart { to, from, addend, bytes } => {
207 data.apart.push(Apart {
208 object: data.objects.len(),
209 at: object.bytes.len(),
210 to: to.clone(),
211 from: from.clone(),
212 addend: *addend,
213 bytes: *bytes,
214 });
215 object.bytes.resize(object.bytes.len() + usize::from(*bytes), 0);
216 }
217 }
218 }
219 data.objects.push(object);
220 }
221 data
222 }
223}
224
225pub fn globals(module: &Module, names: &Interner, format: ObjectFormat) -> Result<Globals, Error> {
238 let mut out = Globals::default();
239 for id in module.globals() {
240 if module[id].is_declaration() {
241 continue;
242 }
243 out.vars.push(variable(module, names, id, format)?);
244 }
245 for id in module.funcs() {
255 let func = &module[id];
256 if func.is_declaration() && func.linkage == Linkage::Weak {
257 out.weak.push(names.resolve(func.name).to_owned());
258 }
259 }
260 for id in module.globals() {
261 let global = &module[id];
262 if global.is_declaration() && global.linkage == Linkage::Weak {
263 out.weak.push(names.resolve(global.name).to_owned());
264 }
265 }
266 Ok(out)
267}
268
269pub fn aliases(module: &Module, names: &Interner) -> Result<Vec<Alias>, Error> {
280 let mut out = Vec::new();
281 for id in module.aliases() {
282 let alias = &module[id];
283 let name = names.resolve(alias.name).to_owned();
284 if alias.kind != AliasKind::Alias {
285 return Err(Error::IFunc { name });
286 }
287 out.push(Alias {
288 name,
289 target: names.resolve(alias.target).to_owned(),
290 binding: binding(alias.linkage),
291 visibility: visibility(alias.visibility),
292 });
293 }
294 Ok(out)
295}
296
297fn variable(
299 module: &Module,
300 names: &Interner,
301 id: GlobalId,
302 format: ObjectFormat,
303) -> Result<Variable, Error> {
304 let global = &module[id];
305 let name = names.resolve(global.name).to_owned();
306 if global.tls.is_some() && !matches!(format, ObjectFormat::Elf | ObjectFormat::MachO) {
307 return Err(Error::Thread { name, format: format.as_str() });
308 }
309 let init = global.init.expect("a definition has an image");
310
311 let mut pieces = Vec::new();
312 let mut addrs = Vec::new();
316 let mut written = 0;
317 for datum in &module[init] {
318 let piece = match *datum {
319 Datum::Zero(bytes) => Piece::Zero(bytes),
320 Datum::Bytes(range) => Piece::Bytes(module[range].to_vec()),
321 Datum::Scalar { ty, value } => {
322 if ty.lanes() != 1 {
323 let why = format!("a {ty} in an initializer");
324 return Err(Error::Image { name, why });
325 }
326 let bytes = usize::try_from(ty.bits().div_ceil(8)).expect("a scalar this wide");
327 let mut image = module[value].bits().to_le_bytes()[..bytes].to_vec();
328 if !module.datalayout.little_endian {
329 image.reverse();
330 }
331 Piece::Scalar(image)
332 }
333 Datum::Away(idx) => {
338 let reloc = module[idx];
339 if reloc.size != 4 {
340 let why = format!("a distance {} bytes wide", reloc.size);
341 return Err(Error::Image { name, why });
342 }
343 let symbol = names.resolve(reloc.symbol).to_owned();
344 Piece::Away { symbol, addend: reloc.addend }
345 }
346 Datum::Apart { to, from } => {
349 let reloc = module[to];
350 let bytes = match reloc.size {
351 1 | 2 | 4 | 8 => reloc.size as u8,
352 size => {
353 let why = format!("a distance {size} bytes wide");
354 return Err(Error::Image { name, why });
355 }
356 };
357 let to = names.resolve(reloc.symbol).to_owned();
358 let from = names.resolve(from).to_owned();
359 Piece::Apart { to, from, addend: reloc.addend, bytes }
360 }
361 Datum::Addr(idx) => {
362 let reloc = module[idx];
363 let bytes = match reloc.size {
367 4 | 8 => reloc.size as u8,
368 size => {
369 let why = format!("an address {size} bytes wide");
370 return Err(Error::Image { name, why });
371 }
372 };
373 let symbol = names.resolve(reloc.symbol).to_owned();
374 addrs.push(reloc.symbol);
375 Piece::Addr { symbol, addend: reloc.addend, bytes }
376 }
377 };
378 written += piece.size();
379 pieces.push(piece);
380 }
381 if written < global.size {
384 pieces.push(Piece::Zero(global.size - written));
385 }
386
387 let place = place(module, names, id, &pieces, &addrs);
388 let size = global.size.max(written);
389 let binding = binding(global.linkage);
390 let visibility = visibility(global.visibility);
391 Ok(Variable { name, size, align: u64::from(global.align), place, binding, visibility, pieces })
392}
393
394const fn binding(linkage: Linkage) -> Binding {
400 match linkage {
401 Linkage::Internal => Binding::Local,
402 Linkage::Weak | Linkage::LinkOnce => Binding::Weak,
403 Linkage::External | Linkage::Common => Binding::Global,
404 }
405}
406
407const fn visibility(visibility: ir::Visibility) -> Visibility {
413 match visibility {
414 ir::Visibility::Default => Visibility::Default,
415 ir::Visibility::Hidden => Visibility::Hidden,
416 ir::Visibility::Protected => Visibility::Protected,
417 }
418}
419
420fn place(
439 module: &Module,
440 names: &Interner,
441 id: GlobalId,
442 pieces: &[Piece],
443 addrs: &[Symbol],
444) -> Place {
445 let global = &module[id];
446 if global.tls.is_some() {
451 let zero = !pieces.is_empty() && pieces.iter().all(|piece| matches!(piece, Piece::Zero(_)));
452 return Place::Thread { zero };
453 }
454 if let Some(section) = global.section {
455 return Place::Named(names.resolve(section).to_owned());
456 }
457 if global.linkage == Linkage::Common {
458 return Place::Merged;
459 }
460 if !pieces.is_empty() && pieces.iter().all(|piece| matches!(piece, Piece::Zero(_))) {
461 return Place::Zero;
462 }
463 if global.constant {
464 return match addrs {
465 [] => Place::ReadOnly,
466 _ => Place::RelocReadOnly {
467 local: addrs.iter().all(|&symbol| resolved_here(module, symbol)),
468 },
469 };
470 }
471 Place::Written
472}
473
474fn resolved_here(module: &Module, symbol: Symbol) -> bool {
480 match module.lookup(symbol) {
481 Some(SymbolRef::Func(id)) => {
482 module[id].linkage == Linkage::Internal && !module[id].is_declaration()
483 }
484 Some(SymbolRef::Global(id)) => {
485 module[id].linkage == Linkage::Internal && !module[id].is_declaration()
486 }
487 Some(SymbolRef::Alias(id)) => module[id].linkage == Linkage::Internal,
488 None => false,
489 }
490}
491
492#[cfg(test)]
493mod tests {
494 use super::*;
495
496 use rucc_ir::{Alias as IrAlias, Global, Imm, Reloc as IrReloc, TlsModel, Type};
497 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
498
499 fn module(names: &mut Interner) -> Module {
501 let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
502 Module::new(names.intern("t.c"), &target)
503 }
504
505 fn defined(module: &mut Module, names: &mut Interner, name: &str, data: &[Datum]) -> GlobalId {
507 let list = module.push_data(data);
508 let mut global = Global::new(names.intern(name), 4, 4);
509 global.init = Some(list);
510 module.add_global(global)
511 }
512
513 #[test]
514 fn a_declaration_is_not_a_variable_this_file_defines() {
515 let mut names = Interner::new();
516 let mut module = module(&mut names);
517 module.add_global(Global::new(names.intern("x"), 4, 4));
518 defined(&mut module, &mut names, "y", &[Datum::Zero(4)]);
519 let vars =
520 globals(&module, &names, ObjectFormat::Elf).expect("a module of two globals").vars;
521 assert_eq!(vars.iter().map(|var| var.name.as_str()).collect::<Vec<_>>(), ["y"]);
522 }
523
524 #[test]
530 fn the_visibility_a_variable_asked_for_reaches_the_image() {
531 for (asked, wanted) in [
532 (ir::Visibility::Default, Visibility::Default),
533 (ir::Visibility::Hidden, Visibility::Hidden),
534 (ir::Visibility::Protected, Visibility::Protected),
535 ] {
536 let mut names = Interner::new();
537 let mut module = module(&mut names);
538 let id = defined(&mut module, &mut names, "x", &[Datum::Zero(4)]);
539 module[id].visibility = asked;
540 let out = globals(&module, &names, ObjectFormat::Elf).expect("a module of one global");
541 assert_eq!(out.vars[0].visibility, wanted, "{asked:?}");
542 assert_eq!(out.image().objects[0].visibility, wanted, "{asked:?} through the image");
543 }
544 }
545
546 #[test]
547 fn a_number_in_an_image_is_the_bytes_the_machine_reads_it_as() {
548 let mut names = Interner::new();
549 let mut module = module(&mut names);
550 let value = module.add_imm(Imm::int(258, Type::int(32)));
551 defined(&mut module, &mut names, "x", &[Datum::Scalar { ty: Type::int(32), value }]);
552 let vars =
553 globals(&module, &names, ObjectFormat::Elf).expect("a module of one global").vars;
554 assert_eq!(vars[0].pieces, [Piece::Scalar(vec![2, 1, 0, 0])]);
555 assert_eq!(vars[0].pieces[0].size(), 4);
558 }
559
560 #[test]
561 fn what_a_variable_is_decides_which_section_it_goes_in() {
562 let mut names = Interner::new();
563 let mut module = module(&mut names);
564 let value = module.add_imm(Imm::int(1, Type::int(32)));
565 let scalar = Datum::Scalar { ty: Type::int(32), value };
566
567 let zeroed = defined(&mut module, &mut names, "zeroed", &[Datum::Zero(4)]);
568 let written = defined(&mut module, &mut names, "written", &[scalar]);
569 let read_only = defined(&mut module, &mut names, "read_only", &[scalar]);
570 module[read_only].constant = true;
571 let named = defined(&mut module, &mut names, "named", &[scalar]);
572 module[named].section = Some(names.intern(".init_array"));
573 let merged = defined(&mut module, &mut names, "merged", &[Datum::Zero(4)]);
574 module[merged].linkage = Linkage::Common;
575
576 let vars =
577 globals(&module, &names, ObjectFormat::Elf).expect("a module of five globals").vars;
578 let places: Vec<&Place> = vars.iter().map(|var| &var.place).collect();
579 assert_eq!(
580 places,
581 [
582 &Place::Zero,
583 &Place::Written,
584 &Place::ReadOnly,
585 &Place::Named(".init_array".to_owned()),
586 &Place::Merged,
587 ]
588 );
589 let _ = (zeroed, written);
590 }
591
592 #[test]
600 fn a_constant_holding_an_address_goes_where_the_loader_may_write_it_once() {
601 let mut names = Interner::new();
602 let mut module = module(&mut names);
603 let value = module.add_imm(Imm::int(1, Type::int(32)));
604
605 let mine = defined(&mut module, &mut names, "mine", &[Datum::Zero(4)]);
606 module[mine].linkage = Linkage::Internal;
607 let theirs = module.add_global(Global::new(names.intern("theirs"), 4, 4));
608
609 let to_mine = module.add_reloc(IrReloc { symbol: module[mine].name, addend: 0, size: 8 });
610 let to_theirs =
611 module.add_reloc(IrReloc { symbol: module[theirs].name, addend: 0, size: 8 });
612
613 let plain = defined(
614 &mut module,
615 &mut names,
616 "plain",
617 &[Datum::Scalar { ty: Type::int(32), value }],
618 );
619 module[plain].constant = true;
620 let local = defined(&mut module, &mut names, "local", &[Datum::Addr(to_mine)]);
621 module[local].constant = true;
622 module[local].size = 8;
623 let far = defined(&mut module, &mut names, "far", &[Datum::Addr(to_theirs)]);
624 module[far].constant = true;
625 module[far].size = 8;
626 let both = defined(
627 &mut module,
628 &mut names,
629 "both",
630 &[Datum::Addr(to_mine), Datum::Addr(to_theirs)],
631 );
632 module[both].constant = true;
633 module[both].size = 16;
634
635 let vars =
636 globals(&module, &names, ObjectFormat::Elf).expect("a module of five globals").vars;
637 let places: Vec<(&str, &Place)> =
638 vars.iter().map(|var| (var.name.as_str(), &var.place)).collect();
639 assert_eq!(
640 places,
641 [
642 ("mine", &Place::Zero),
643 ("plain", &Place::ReadOnly),
644 ("local", &Place::RelocReadOnly { local: true }),
645 ("far", &Place::RelocReadOnly { local: false }),
646 ("both", &Place::RelocReadOnly { local: false }),
647 ]
648 );
649 }
650
651 #[test]
652 fn the_rest_of_an_image_the_front_end_left_off_is_zeros() {
653 let mut names = Interner::new();
654 let mut module = module(&mut names);
655 let value = module.add_imm(Imm::int(7, Type::int(8)));
656 let id =
657 defined(&mut module, &mut names, "x", &[Datum::Scalar { ty: Type::int(8), value }]);
658 module[id].size = 4;
659 let vars =
660 globals(&module, &names, ObjectFormat::Elf).expect("a module of one global").vars;
661 assert_eq!(vars[0].pieces, [Piece::Scalar(vec![7]), Piece::Zero(3)]);
662 assert_eq!(vars[0].size, 4);
663 }
664
665 #[test]
666 fn a_variable_holding_an_address_is_a_hole_and_a_name_for_the_linker() {
667 let mut names = Interner::new();
668 let mut module = module(&mut names);
669 let reloc = module.add_reloc(IrReloc { symbol: names.intern("y"), addend: 16, size: 8 });
670 let id = defined(&mut module, &mut names, "p", &[Datum::Addr(reloc)]);
671 module[id].size = 8;
672 let vars =
673 globals(&module, &names, ObjectFormat::Elf).expect("a module of one global").vars;
674 assert_eq!(vars[0].pieces, [Piece::Addr { symbol: "y".to_owned(), addend: 16, bytes: 8 }]);
675
676 let data = Globals { vars, weak: Vec::new() }.image();
677 assert_eq!(data.objects[0].bytes, vec![0; 8]);
678 assert_eq!(
679 data.objects[0].relocs,
680 [Reloc {
681 at: 0,
682 symbol: "y".to_owned(),
683 kind: Reference::Address { bytes: 8 },
684 addend: 16,
685 after: 0,
686 }]
687 );
688 }
689
690 #[test]
691 fn a_variable_holding_a_distance_is_a_hole_the_linker_measures_from_where_it_is() {
692 let mut names = Interner::new();
693 let mut module = module(&mut names);
694 let reloc = module.add_reloc(IrReloc { symbol: names.intern("y"), addend: 1, size: 4 });
695 let id = defined(&mut module, &mut names, "d", &[Datum::Away(reloc)]);
696 module[id].size = 4;
697 module[id].constant = true;
700 let vars =
701 globals(&module, &names, ObjectFormat::Elf).expect("a module of one global").vars;
702 assert_eq!(vars[0].pieces, [Piece::Away { symbol: "y".to_owned(), addend: 1 }]);
703 assert_eq!(vars[0].place, Place::ReadOnly);
704
705 let data = Globals { vars, weak: Vec::new() }.image();
706 assert_eq!(data.objects[0].bytes, vec![0; 4]);
707 assert_eq!(
708 data.objects[0].relocs,
709 [Reloc { at: 0, symbol: "y".to_owned(), kind: Reference::Away, addend: 1, after: 0 }]
710 );
711 }
712
713 #[test]
714 fn a_distance_of_a_width_no_relocation_writes_is_refused_by_the_width_it_asked_for() {
715 let mut names = Interner::new();
716 let mut module = module(&mut names);
717 let reloc = module.add_reloc(IrReloc { symbol: names.intern("y"), addend: 0, size: 8 });
718 let id = defined(&mut module, &mut names, "d", &[Datum::Away(reloc)]);
719 module[id].size = 8;
720 let failed = globals(&module, &names, ObjectFormat::Elf).expect_err("a distance that wide");
721 assert_eq!(
722 failed,
723 Error::Image { name: "d".to_owned(), why: "a distance 8 bytes wide".to_owned() }
724 );
725 }
726
727 #[test]
728 fn a_variable_in_a_section_that_carries_no_image_carries_its_size_and_nothing_else() {
729 let mut names = Interner::new();
730 let mut module = module(&mut names);
731 let id = defined(&mut module, &mut names, "x", &[Datum::Zero(4096)]);
732 module[id].size = 4096;
733 let data =
734 globals(&module, &names, ObjectFormat::Elf).expect("a module of one global").image();
735 assert_eq!(data.objects[0].place, Place::Zero);
736 assert_eq!(data.objects[0].size, 4096);
737 assert!(data.objects[0].bytes.is_empty());
739 }
740
741 #[test]
742 fn the_linkage_a_variable_had_decides_how_the_linker_sees_the_name() {
743 let mut names = Interner::new();
744 let mut module = module(&mut names);
745 for (index, (linkage, binding)) in [
746 (Linkage::External, Binding::Global),
747 (Linkage::Internal, Binding::Local),
748 (Linkage::Weak, Binding::Weak),
749 (Linkage::LinkOnce, Binding::Weak),
750 ]
751 .into_iter()
752 .enumerate()
753 {
754 let name = format!("x{index}");
755 let id = defined(&mut module, &mut names, &name, &[Datum::Zero(4)]);
756 module[id].linkage = linkage;
757 let vars =
758 globals(&module, &names, ObjectFormat::Elf).expect("a module of globals").vars;
759 assert_eq!(vars[index].binding, binding, "{linkage:?}");
760 }
761 }
762
763 #[test]
764 fn the_linkage_an_alias_had_decides_how_the_linker_sees_the_second_name() {
765 let mut names = Interner::new();
766 let mut module = module(&mut names);
767 let target = names.intern("a");
768 for (index, (linkage, binding)) in [
769 (Linkage::External, Binding::Global),
770 (Linkage::Internal, Binding::Local),
771 (Linkage::Weak, Binding::Weak),
772 ]
773 .into_iter()
774 .enumerate()
775 {
776 let mut alias = IrAlias::new(names.intern(&format!("b{index}")), target);
777 alias.linkage = linkage;
778 module.add_alias(alias);
779 let written = aliases(&module, &names).expect("a module of aliases");
780 assert_eq!(written[index].binding, binding, "{linkage:?}");
781 assert_eq!(written[index].target, "a", "{linkage:?}");
782 }
783 }
784
785 #[test]
788 fn an_ifunc_is_refused_rather_than_written_as_an_ordinary_second_name() {
789 let mut names = Interner::new();
790 let mut module = module(&mut names);
791 let mut memcpy = IrAlias::new(names.intern("memcpy"), names.intern("pick_memcpy"));
792 memcpy.kind = AliasKind::IFunc;
793 module.add_alias(memcpy);
794 let error = aliases(&module, &names).expect_err("an ifunc");
795 assert_eq!(error, Error::IFunc { name: "memcpy".to_owned() });
796 }
797
798 #[test]
800 fn a_thread_local_variable_goes_in_the_section_a_thread_gets_a_copy_of() {
801 let mut names = Interner::new();
802 let mut module = module(&mut names);
803 let value = module.add_imm(Imm::int(1, Type::int(32)));
804 let written = defined(
805 &mut module,
806 &mut names,
807 "counted",
808 &[Datum::Scalar { ty: Type::int(32), value }],
809 );
810 module[written].tls = Some(TlsModel::GlobalDynamic);
811 let zeroed = defined(&mut module, &mut names, "empty", &[Datum::Zero(4)]);
812 module[zeroed].tls = Some(TlsModel::GlobalDynamic);
813
814 let vars = globals(&module, &names, ObjectFormat::Elf).expect("two thread-locals").vars;
815 assert_eq!(vars[0].place, Place::Thread { zero: false }, ".tdata");
816 assert_eq!(vars[1].place, Place::Thread { zero: true }, ".tbss");
817 }
818
819 #[test]
821 fn a_constant_thread_local_is_still_in_the_section_a_thread_gets_a_copy_of() {
822 let mut names = Interner::new();
823 let mut module = module(&mut names);
824 let value = module.add_imm(Imm::int(1, Type::int(32)));
825 let id =
826 defined(&mut module, &mut names, "x", &[Datum::Scalar { ty: Type::int(32), value }]);
827 module[id].tls = Some(TlsModel::GlobalDynamic);
828 module[id].constant = true;
829
830 let vars = globals(&module, &names, ObjectFormat::Elf).expect("a thread-local").vars;
831 assert_eq!(vars[0].place, Place::Thread { zero: false });
832 }
833
834 #[test]
837 fn the_image_of_a_thread_local_is_carried_only_when_it_is_not_all_zeros() {
838 let mut names = Interner::new();
839 let mut module = module(&mut names);
840 let value = module.add_imm(Imm::int(258, Type::int(32)));
841 let written = defined(
842 &mut module,
843 &mut names,
844 "counted",
845 &[Datum::Scalar { ty: Type::int(32), value }],
846 );
847 module[written].tls = Some(TlsModel::GlobalDynamic);
848 let zeroed = defined(&mut module, &mut names, "empty", &[Datum::Zero(4)]);
849 module[zeroed].tls = Some(TlsModel::GlobalDynamic);
850
851 let data = globals(&module, &names, ObjectFormat::Elf).expect("two thread-locals").image();
852 assert_eq!(data.objects[0].bytes, [2, 1, 0, 0]);
853 assert_eq!(data.objects[0].size, 4);
854 assert!(data.objects[1].bytes.is_empty(), "a zeroed one carries its size and no bytes");
855 assert_eq!(data.objects[1].size, 4);
856 }
857
858 #[test]
861 fn a_thread_local_variable_is_refused_on_a_format_that_does_not_spell_one_this_way() {
862 let mut names = Interner::new();
863 let mut module = module(&mut names);
864 let id = defined(&mut module, &mut names, "x", &[Datum::Zero(4)]);
865 module[id].tls = Some(TlsModel::GlobalDynamic);
866 let format = ObjectFormat::Coff;
867 let error = globals(&module, &names, format).expect_err("a thread-local variable");
868 assert_eq!(error, Error::Thread { name: "x".to_owned(), format: format.as_str() });
869 }
870}