1use rucc_base::{Interner, Symbol};
42use rucc_ir as ir;
43use rucc_ir::{AliasKind, Datum, GlobalId, Linkage, Module, SymbolRef};
44use rucc_object::{Alias, 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}
125
126impl Piece {
127 #[must_use]
129 pub fn size(&self) -> u64 {
130 match self {
131 Piece::Zero(bytes) => *bytes,
132 Piece::Bytes(bytes) | Piece::Scalar(bytes) => bytes.len() as u64,
133 Piece::Addr { bytes, .. } => u64::from(*bytes),
134 Piece::Away { .. } => 4,
135 }
136 }
137}
138
139impl Globals {
140 #[must_use]
145 pub fn image(&self) -> Data {
146 let mut data = Data { weak: self.weak.clone(), ..Data::default() };
147 for var in &self.vars {
148 let mut object = Object {
149 name: var.name.clone(),
150 bytes: Vec::new(),
151 size: var.size,
152 align: var.align,
153 place: var.place.clone(),
154 binding: var.binding,
155 visibility: var.visibility,
156 relocs: Vec::new(),
157 };
158 if matches!(var.place, Place::Zero | Place::Merged | Place::Thread { zero: true }) {
159 data.objects.push(object);
160 continue;
161 }
162 for piece in &var.pieces {
163 match piece {
164 Piece::Zero(bytes) => {
165 object.bytes.resize(object.bytes.len() + *bytes as usize, 0);
166 }
167 Piece::Bytes(bytes) | Piece::Scalar(bytes) => {
168 object.bytes.extend_from_slice(bytes);
169 }
170 Piece::Addr { symbol, addend, bytes } => {
171 object.relocs.push(Reloc {
174 at: object.bytes.len(),
175 symbol: symbol.clone(),
176 kind: Reference::Address { bytes: *bytes },
177 addend: *addend,
178 after: 0,
181 });
182 object.bytes.resize(object.bytes.len() + usize::from(*bytes), 0);
183 }
184 Piece::Away { symbol, addend } => {
185 object.relocs.push(Reloc {
186 at: object.bytes.len(),
187 symbol: symbol.clone(),
188 kind: Reference::Away,
189 addend: *addend,
190 after: 0,
191 });
192 object.bytes.resize(object.bytes.len() + 4, 0);
193 }
194 }
195 }
196 data.objects.push(object);
197 }
198 data
199 }
200}
201
202pub fn globals(module: &Module, names: &Interner, format: ObjectFormat) -> Result<Globals, Error> {
214 let mut out = Globals::default();
215 for id in module.globals() {
216 if module[id].is_declaration() {
217 continue;
218 }
219 out.vars.push(variable(module, names, id, format)?);
220 }
221 for id in module.funcs() {
231 let func = &module[id];
232 if func.is_declaration() && func.linkage == Linkage::Weak {
233 out.weak.push(names.resolve(func.name).to_owned());
234 }
235 }
236 for id in module.globals() {
237 let global = &module[id];
238 if global.is_declaration() && global.linkage == Linkage::Weak {
239 out.weak.push(names.resolve(global.name).to_owned());
240 }
241 }
242 Ok(out)
243}
244
245pub fn aliases(module: &Module, names: &Interner) -> Result<Vec<Alias>, Error> {
256 let mut out = Vec::new();
257 for id in module.aliases() {
258 let alias = &module[id];
259 let name = names.resolve(alias.name).to_owned();
260 if alias.kind != AliasKind::Alias {
261 return Err(Error::IFunc { name });
262 }
263 out.push(Alias {
264 name,
265 target: names.resolve(alias.target).to_owned(),
266 binding: binding(alias.linkage),
267 visibility: visibility(alias.visibility),
268 });
269 }
270 Ok(out)
271}
272
273fn variable(
275 module: &Module,
276 names: &Interner,
277 id: GlobalId,
278 format: ObjectFormat,
279) -> Result<Variable, Error> {
280 let global = &module[id];
281 let name = names.resolve(global.name).to_owned();
282 if global.tls.is_some() && format != ObjectFormat::Elf {
283 return Err(Error::Thread { name, format: format.as_str() });
284 }
285 let init = global.init.expect("a definition has an image");
286
287 let mut pieces = Vec::new();
288 let mut addrs = Vec::new();
292 let mut written = 0;
293 for datum in &module[init] {
294 let piece = match *datum {
295 Datum::Zero(bytes) => Piece::Zero(bytes),
296 Datum::Bytes(range) => Piece::Bytes(module[range].to_vec()),
297 Datum::Scalar { ty, value } => {
298 if ty.lanes() != 1 {
299 let why = format!("a {ty} in an initializer");
300 return Err(Error::Image { name, why });
301 }
302 let bytes = usize::try_from(ty.bits().div_ceil(8)).expect("a scalar this wide");
303 let mut image = module[value].bits().to_le_bytes()[..bytes].to_vec();
304 if !module.datalayout.little_endian {
305 image.reverse();
306 }
307 Piece::Scalar(image)
308 }
309 Datum::Away(idx) => {
314 let reloc = module[idx];
315 if reloc.size != 4 {
316 let why = format!("a distance {} bytes wide", reloc.size);
317 return Err(Error::Image { name, why });
318 }
319 let symbol = names.resolve(reloc.symbol).to_owned();
320 Piece::Away { symbol, addend: reloc.addend }
321 }
322 Datum::Addr(idx) => {
323 let reloc = module[idx];
324 let bytes = match reloc.size {
328 4 | 8 => reloc.size as u8,
329 size => {
330 let why = format!("an address {size} bytes wide");
331 return Err(Error::Image { name, why });
332 }
333 };
334 let symbol = names.resolve(reloc.symbol).to_owned();
335 addrs.push(reloc.symbol);
336 Piece::Addr { symbol, addend: reloc.addend, bytes }
337 }
338 };
339 written += piece.size();
340 pieces.push(piece);
341 }
342 if written < global.size {
345 pieces.push(Piece::Zero(global.size - written));
346 }
347
348 let place = place(module, names, id, &pieces, &addrs);
349 let size = global.size.max(written);
350 let binding = binding(global.linkage);
351 let visibility = visibility(global.visibility);
352 Ok(Variable { name, size, align: u64::from(global.align), place, binding, visibility, pieces })
353}
354
355const fn binding(linkage: Linkage) -> Binding {
361 match linkage {
362 Linkage::Internal => Binding::Local,
363 Linkage::Weak | Linkage::LinkOnce => Binding::Weak,
364 Linkage::External | Linkage::Common => Binding::Global,
365 }
366}
367
368const fn visibility(visibility: ir::Visibility) -> Visibility {
374 match visibility {
375 ir::Visibility::Default => Visibility::Default,
376 ir::Visibility::Hidden => Visibility::Hidden,
377 ir::Visibility::Protected => Visibility::Protected,
378 }
379}
380
381fn place(
400 module: &Module,
401 names: &Interner,
402 id: GlobalId,
403 pieces: &[Piece],
404 addrs: &[Symbol],
405) -> Place {
406 let global = &module[id];
407 if global.tls.is_some() {
412 let zero = !pieces.is_empty() && pieces.iter().all(|piece| matches!(piece, Piece::Zero(_)));
413 return Place::Thread { zero };
414 }
415 if let Some(section) = global.section {
416 return Place::Named(names.resolve(section).to_owned());
417 }
418 if global.linkage == Linkage::Common {
419 return Place::Merged;
420 }
421 if !pieces.is_empty() && pieces.iter().all(|piece| matches!(piece, Piece::Zero(_))) {
422 return Place::Zero;
423 }
424 if global.constant {
425 return match addrs {
426 [] => Place::ReadOnly,
427 _ => Place::RelocReadOnly {
428 local: addrs.iter().all(|&symbol| resolved_here(module, symbol)),
429 },
430 };
431 }
432 Place::Written
433}
434
435fn resolved_here(module: &Module, symbol: Symbol) -> bool {
441 match module.lookup(symbol) {
442 Some(SymbolRef::Func(id)) => {
443 module[id].linkage == Linkage::Internal && !module[id].is_declaration()
444 }
445 Some(SymbolRef::Global(id)) => {
446 module[id].linkage == Linkage::Internal && !module[id].is_declaration()
447 }
448 Some(SymbolRef::Alias(id)) => module[id].linkage == Linkage::Internal,
449 None => false,
450 }
451}
452
453#[cfg(test)]
454mod tests {
455 use super::*;
456
457 use rucc_ir::{Alias as IrAlias, Global, Imm, Reloc as IrReloc, TlsModel, Type};
458 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
459
460 fn module(names: &mut Interner) -> Module {
462 let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
463 Module::new(names.intern("t.c"), &target)
464 }
465
466 fn defined(module: &mut Module, names: &mut Interner, name: &str, data: &[Datum]) -> GlobalId {
468 let list = module.push_data(data);
469 let mut global = Global::new(names.intern(name), 4, 4);
470 global.init = Some(list);
471 module.add_global(global)
472 }
473
474 #[test]
475 fn a_declaration_is_not_a_variable_this_file_defines() {
476 let mut names = Interner::new();
477 let mut module = module(&mut names);
478 module.add_global(Global::new(names.intern("x"), 4, 4));
479 defined(&mut module, &mut names, "y", &[Datum::Zero(4)]);
480 let vars =
481 globals(&module, &names, ObjectFormat::Elf).expect("a module of two globals").vars;
482 assert_eq!(vars.iter().map(|var| var.name.as_str()).collect::<Vec<_>>(), ["y"]);
483 }
484
485 #[test]
491 fn the_visibility_a_variable_asked_for_reaches_the_image() {
492 for (asked, wanted) in [
493 (ir::Visibility::Default, Visibility::Default),
494 (ir::Visibility::Hidden, Visibility::Hidden),
495 (ir::Visibility::Protected, Visibility::Protected),
496 ] {
497 let mut names = Interner::new();
498 let mut module = module(&mut names);
499 let id = defined(&mut module, &mut names, "x", &[Datum::Zero(4)]);
500 module[id].visibility = asked;
501 let out = globals(&module, &names, ObjectFormat::Elf).expect("a module of one global");
502 assert_eq!(out.vars[0].visibility, wanted, "{asked:?}");
503 assert_eq!(out.image().objects[0].visibility, wanted, "{asked:?} through the image");
504 }
505 }
506
507 #[test]
508 fn a_number_in_an_image_is_the_bytes_the_machine_reads_it_as() {
509 let mut names = Interner::new();
510 let mut module = module(&mut names);
511 let value = module.add_imm(Imm::int(258, Type::int(32)));
512 defined(&mut module, &mut names, "x", &[Datum::Scalar { ty: Type::int(32), value }]);
513 let vars =
514 globals(&module, &names, ObjectFormat::Elf).expect("a module of one global").vars;
515 assert_eq!(vars[0].pieces, [Piece::Scalar(vec![2, 1, 0, 0])]);
516 assert_eq!(vars[0].pieces[0].size(), 4);
519 }
520
521 #[test]
522 fn what_a_variable_is_decides_which_section_it_goes_in() {
523 let mut names = Interner::new();
524 let mut module = module(&mut names);
525 let value = module.add_imm(Imm::int(1, Type::int(32)));
526 let scalar = Datum::Scalar { ty: Type::int(32), value };
527
528 let zeroed = defined(&mut module, &mut names, "zeroed", &[Datum::Zero(4)]);
529 let written = defined(&mut module, &mut names, "written", &[scalar]);
530 let read_only = defined(&mut module, &mut names, "read_only", &[scalar]);
531 module[read_only].constant = true;
532 let named = defined(&mut module, &mut names, "named", &[scalar]);
533 module[named].section = Some(names.intern(".init_array"));
534 let merged = defined(&mut module, &mut names, "merged", &[Datum::Zero(4)]);
535 module[merged].linkage = Linkage::Common;
536
537 let vars =
538 globals(&module, &names, ObjectFormat::Elf).expect("a module of five globals").vars;
539 let places: Vec<&Place> = vars.iter().map(|var| &var.place).collect();
540 assert_eq!(
541 places,
542 [
543 &Place::Zero,
544 &Place::Written,
545 &Place::ReadOnly,
546 &Place::Named(".init_array".to_owned()),
547 &Place::Merged,
548 ]
549 );
550 let _ = (zeroed, written);
551 }
552
553 #[test]
561 fn a_constant_holding_an_address_goes_where_the_loader_may_write_it_once() {
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
566 let mine = defined(&mut module, &mut names, "mine", &[Datum::Zero(4)]);
567 module[mine].linkage = Linkage::Internal;
568 let theirs = module.add_global(Global::new(names.intern("theirs"), 4, 4));
569
570 let to_mine = module.add_reloc(IrReloc { symbol: module[mine].name, addend: 0, size: 8 });
571 let to_theirs =
572 module.add_reloc(IrReloc { symbol: module[theirs].name, addend: 0, size: 8 });
573
574 let plain = defined(
575 &mut module,
576 &mut names,
577 "plain",
578 &[Datum::Scalar { ty: Type::int(32), value }],
579 );
580 module[plain].constant = true;
581 let local = defined(&mut module, &mut names, "local", &[Datum::Addr(to_mine)]);
582 module[local].constant = true;
583 module[local].size = 8;
584 let far = defined(&mut module, &mut names, "far", &[Datum::Addr(to_theirs)]);
585 module[far].constant = true;
586 module[far].size = 8;
587 let both = defined(
588 &mut module,
589 &mut names,
590 "both",
591 &[Datum::Addr(to_mine), Datum::Addr(to_theirs)],
592 );
593 module[both].constant = true;
594 module[both].size = 16;
595
596 let vars =
597 globals(&module, &names, ObjectFormat::Elf).expect("a module of five globals").vars;
598 let places: Vec<(&str, &Place)> =
599 vars.iter().map(|var| (var.name.as_str(), &var.place)).collect();
600 assert_eq!(
601 places,
602 [
603 ("mine", &Place::Zero),
604 ("plain", &Place::ReadOnly),
605 ("local", &Place::RelocReadOnly { local: true }),
606 ("far", &Place::RelocReadOnly { local: false }),
607 ("both", &Place::RelocReadOnly { local: false }),
608 ]
609 );
610 }
611
612 #[test]
613 fn the_rest_of_an_image_the_front_end_left_off_is_zeros() {
614 let mut names = Interner::new();
615 let mut module = module(&mut names);
616 let value = module.add_imm(Imm::int(7, Type::int(8)));
617 let id =
618 defined(&mut module, &mut names, "x", &[Datum::Scalar { ty: Type::int(8), value }]);
619 module[id].size = 4;
620 let vars =
621 globals(&module, &names, ObjectFormat::Elf).expect("a module of one global").vars;
622 assert_eq!(vars[0].pieces, [Piece::Scalar(vec![7]), Piece::Zero(3)]);
623 assert_eq!(vars[0].size, 4);
624 }
625
626 #[test]
627 fn a_variable_holding_an_address_is_a_hole_and_a_name_for_the_linker() {
628 let mut names = Interner::new();
629 let mut module = module(&mut names);
630 let reloc = module.add_reloc(IrReloc { symbol: names.intern("y"), addend: 16, size: 8 });
631 let id = defined(&mut module, &mut names, "p", &[Datum::Addr(reloc)]);
632 module[id].size = 8;
633 let vars =
634 globals(&module, &names, ObjectFormat::Elf).expect("a module of one global").vars;
635 assert_eq!(vars[0].pieces, [Piece::Addr { symbol: "y".to_owned(), addend: 16, bytes: 8 }]);
636
637 let data = Globals { vars, weak: Vec::new() }.image();
638 assert_eq!(data.objects[0].bytes, vec![0; 8]);
639 assert_eq!(
640 data.objects[0].relocs,
641 [Reloc {
642 at: 0,
643 symbol: "y".to_owned(),
644 kind: Reference::Address { bytes: 8 },
645 addend: 16,
646 after: 0,
647 }]
648 );
649 }
650
651 #[test]
652 fn a_variable_holding_a_distance_is_a_hole_the_linker_measures_from_where_it_is() {
653 let mut names = Interner::new();
654 let mut module = module(&mut names);
655 let reloc = module.add_reloc(IrReloc { symbol: names.intern("y"), addend: 1, size: 4 });
656 let id = defined(&mut module, &mut names, "d", &[Datum::Away(reloc)]);
657 module[id].size = 4;
658 module[id].constant = true;
661 let vars =
662 globals(&module, &names, ObjectFormat::Elf).expect("a module of one global").vars;
663 assert_eq!(vars[0].pieces, [Piece::Away { symbol: "y".to_owned(), addend: 1 }]);
664 assert_eq!(vars[0].place, Place::ReadOnly);
665
666 let data = Globals { vars, weak: Vec::new() }.image();
667 assert_eq!(data.objects[0].bytes, vec![0; 4]);
668 assert_eq!(
669 data.objects[0].relocs,
670 [Reloc { at: 0, symbol: "y".to_owned(), kind: Reference::Away, addend: 1, after: 0 }]
671 );
672 }
673
674 #[test]
675 fn a_distance_of_a_width_no_relocation_writes_is_refused_by_the_width_it_asked_for() {
676 let mut names = Interner::new();
677 let mut module = module(&mut names);
678 let reloc = module.add_reloc(IrReloc { symbol: names.intern("y"), addend: 0, size: 8 });
679 let id = defined(&mut module, &mut names, "d", &[Datum::Away(reloc)]);
680 module[id].size = 8;
681 let failed = globals(&module, &names, ObjectFormat::Elf).expect_err("a distance that wide");
682 assert_eq!(
683 failed,
684 Error::Image { name: "d".to_owned(), why: "a distance 8 bytes wide".to_owned() }
685 );
686 }
687
688 #[test]
689 fn a_variable_in_a_section_that_carries_no_image_carries_its_size_and_nothing_else() {
690 let mut names = Interner::new();
691 let mut module = module(&mut names);
692 let id = defined(&mut module, &mut names, "x", &[Datum::Zero(4096)]);
693 module[id].size = 4096;
694 let data =
695 globals(&module, &names, ObjectFormat::Elf).expect("a module of one global").image();
696 assert_eq!(data.objects[0].place, Place::Zero);
697 assert_eq!(data.objects[0].size, 4096);
698 assert!(data.objects[0].bytes.is_empty());
700 }
701
702 #[test]
703 fn the_linkage_a_variable_had_decides_how_the_linker_sees_the_name() {
704 let mut names = Interner::new();
705 let mut module = module(&mut names);
706 for (index, (linkage, binding)) in [
707 (Linkage::External, Binding::Global),
708 (Linkage::Internal, Binding::Local),
709 (Linkage::Weak, Binding::Weak),
710 (Linkage::LinkOnce, Binding::Weak),
711 ]
712 .into_iter()
713 .enumerate()
714 {
715 let name = format!("x{index}");
716 let id = defined(&mut module, &mut names, &name, &[Datum::Zero(4)]);
717 module[id].linkage = linkage;
718 let vars =
719 globals(&module, &names, ObjectFormat::Elf).expect("a module of globals").vars;
720 assert_eq!(vars[index].binding, binding, "{linkage:?}");
721 }
722 }
723
724 #[test]
725 fn the_linkage_an_alias_had_decides_how_the_linker_sees_the_second_name() {
726 let mut names = Interner::new();
727 let mut module = module(&mut names);
728 let target = names.intern("a");
729 for (index, (linkage, binding)) in [
730 (Linkage::External, Binding::Global),
731 (Linkage::Internal, Binding::Local),
732 (Linkage::Weak, Binding::Weak),
733 ]
734 .into_iter()
735 .enumerate()
736 {
737 let mut alias = IrAlias::new(names.intern(&format!("b{index}")), target);
738 alias.linkage = linkage;
739 module.add_alias(alias);
740 let written = aliases(&module, &names).expect("a module of aliases");
741 assert_eq!(written[index].binding, binding, "{linkage:?}");
742 assert_eq!(written[index].target, "a", "{linkage:?}");
743 }
744 }
745
746 #[test]
749 fn an_ifunc_is_refused_rather_than_written_as_an_ordinary_second_name() {
750 let mut names = Interner::new();
751 let mut module = module(&mut names);
752 let mut memcpy = IrAlias::new(names.intern("memcpy"), names.intern("pick_memcpy"));
753 memcpy.kind = AliasKind::IFunc;
754 module.add_alias(memcpy);
755 let error = aliases(&module, &names).expect_err("an ifunc");
756 assert_eq!(error, Error::IFunc { name: "memcpy".to_owned() });
757 }
758
759 #[test]
761 fn a_thread_local_variable_goes_in_the_section_a_thread_gets_a_copy_of() {
762 let mut names = Interner::new();
763 let mut module = module(&mut names);
764 let value = module.add_imm(Imm::int(1, Type::int(32)));
765 let written = defined(
766 &mut module,
767 &mut names,
768 "counted",
769 &[Datum::Scalar { ty: Type::int(32), value }],
770 );
771 module[written].tls = Some(TlsModel::GlobalDynamic);
772 let zeroed = defined(&mut module, &mut names, "empty", &[Datum::Zero(4)]);
773 module[zeroed].tls = Some(TlsModel::GlobalDynamic);
774
775 let vars = globals(&module, &names, ObjectFormat::Elf).expect("two thread-locals").vars;
776 assert_eq!(vars[0].place, Place::Thread { zero: false }, ".tdata");
777 assert_eq!(vars[1].place, Place::Thread { zero: true }, ".tbss");
778 }
779
780 #[test]
782 fn a_constant_thread_local_is_still_in_the_section_a_thread_gets_a_copy_of() {
783 let mut names = Interner::new();
784 let mut module = module(&mut names);
785 let value = module.add_imm(Imm::int(1, Type::int(32)));
786 let id =
787 defined(&mut module, &mut names, "x", &[Datum::Scalar { ty: Type::int(32), value }]);
788 module[id].tls = Some(TlsModel::GlobalDynamic);
789 module[id].constant = true;
790
791 let vars = globals(&module, &names, ObjectFormat::Elf).expect("a thread-local").vars;
792 assert_eq!(vars[0].place, Place::Thread { zero: false });
793 }
794
795 #[test]
798 fn the_image_of_a_thread_local_is_carried_only_when_it_is_not_all_zeros() {
799 let mut names = Interner::new();
800 let mut module = module(&mut names);
801 let value = module.add_imm(Imm::int(258, Type::int(32)));
802 let written = defined(
803 &mut module,
804 &mut names,
805 "counted",
806 &[Datum::Scalar { ty: Type::int(32), value }],
807 );
808 module[written].tls = Some(TlsModel::GlobalDynamic);
809 let zeroed = defined(&mut module, &mut names, "empty", &[Datum::Zero(4)]);
810 module[zeroed].tls = Some(TlsModel::GlobalDynamic);
811
812 let data = globals(&module, &names, ObjectFormat::Elf).expect("two thread-locals").image();
813 assert_eq!(data.objects[0].bytes, [2, 1, 0, 0]);
814 assert_eq!(data.objects[0].size, 4);
815 assert!(data.objects[1].bytes.is_empty(), "a zeroed one carries its size and no bytes");
816 assert_eq!(data.objects[1].size, 4);
817 }
818
819 #[test]
822 fn a_thread_local_variable_is_refused_on_a_format_that_does_not_spell_one_this_way() {
823 for format in [ObjectFormat::MachO, ObjectFormat::Coff] {
824 let mut names = Interner::new();
825 let mut module = module(&mut names);
826 let id = defined(&mut module, &mut names, "x", &[Datum::Zero(4)]);
827 module[id].tls = Some(TlsModel::GlobalDynamic);
828 let error = globals(&module, &names, format).expect_err("a thread-local variable");
829 assert_eq!(error, Error::Thread { name: "x".to_owned(), format: format.as_str() });
830 }
831 }
832}