Skip to content

Commit 72d0725

Browse files
committed
Auto merge of rust-lang#84732 - DrChat:asm_powerpc, r=Amanieu
Add asm!() support for PowerPC This includes GPRs and FPRs only. Note that this does not include PowerPC64. For my reference, this was mostly duplicated from PR rust-lang#73214.
2 parents 631e989 + b1bb5d6 commit 72d0725

File tree

6 files changed

+356
-1
lines changed

6 files changed

+356
-1
lines changed

compiler/rustc_codegen_llvm/src/asm.rs

+8
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ impl AsmBuilderMethods<'tcx> for Builder<'a, 'll, 'tcx> {
283283
}
284284
InlineAsmArch::RiscV32 | InlineAsmArch::RiscV64 => {}
285285
InlineAsmArch::Nvptx64 => {}
286+
InlineAsmArch::PowerPC => {}
286287
InlineAsmArch::Hexagon => {}
287288
InlineAsmArch::Mips | InlineAsmArch::Mips64 => {}
288289
InlineAsmArch::SpirV => {}
@@ -540,6 +541,9 @@ fn reg_to_llvm(reg: InlineAsmRegOrRegClass, layout: Option<&TyAndLayout<'tcx>>)
540541
InlineAsmRegClass::Nvptx(NvptxInlineAsmRegClass::reg16) => "h",
541542
InlineAsmRegClass::Nvptx(NvptxInlineAsmRegClass::reg32) => "r",
542543
InlineAsmRegClass::Nvptx(NvptxInlineAsmRegClass::reg64) => "l",
544+
InlineAsmRegClass::PowerPC(PowerPCInlineAsmRegClass::reg) => "r",
545+
InlineAsmRegClass::PowerPC(PowerPCInlineAsmRegClass::reg_nonzero) => "b",
546+
InlineAsmRegClass::PowerPC(PowerPCInlineAsmRegClass::freg) => "f",
543547
InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::reg) => "r",
544548
InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::freg) => "f",
545549
InlineAsmRegClass::X86(X86InlineAsmRegClass::reg) => "r",
@@ -590,6 +594,7 @@ fn modifier_to_llvm(
590594
InlineAsmRegClass::Hexagon(_) => None,
591595
InlineAsmRegClass::Mips(_) => None,
592596
InlineAsmRegClass::Nvptx(_) => None,
597+
InlineAsmRegClass::PowerPC(_) => None,
593598
InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::reg)
594599
| InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::freg) => None,
595600
InlineAsmRegClass::X86(X86InlineAsmRegClass::reg)
@@ -651,6 +656,9 @@ fn dummy_output_type(cx: &CodegenCx<'ll, 'tcx>, reg: InlineAsmRegClass) -> &'ll
651656
InlineAsmRegClass::Nvptx(NvptxInlineAsmRegClass::reg16) => cx.type_i16(),
652657
InlineAsmRegClass::Nvptx(NvptxInlineAsmRegClass::reg32) => cx.type_i32(),
653658
InlineAsmRegClass::Nvptx(NvptxInlineAsmRegClass::reg64) => cx.type_i64(),
659+
InlineAsmRegClass::PowerPC(PowerPCInlineAsmRegClass::reg) => cx.type_i32(),
660+
InlineAsmRegClass::PowerPC(PowerPCInlineAsmRegClass::reg_nonzero) => cx.type_i32(),
661+
InlineAsmRegClass::PowerPC(PowerPCInlineAsmRegClass::freg) => cx.type_f64(),
654662
InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::reg) => cx.type_i32(),
655663
InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::freg) => cx.type_f32(),
656664
InlineAsmRegClass::X86(X86InlineAsmRegClass::reg)

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,7 @@ symbols! {
948948
reg64,
949949
reg_abcd,
950950
reg_byte,
951+
reg_nonzero,
951952
reg_thumb,
952953
register_attr,
953954
register_tool,

compiler/rustc_target/src/asm/mod.rs

+25
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ mod arm;
154154
mod hexagon;
155155
mod mips;
156156
mod nvptx;
157+
mod powerpc;
157158
mod riscv;
158159
mod spirv;
159160
mod wasm;
@@ -164,6 +165,7 @@ pub use arm::{ArmInlineAsmReg, ArmInlineAsmRegClass};
164165
pub use hexagon::{HexagonInlineAsmReg, HexagonInlineAsmRegClass};
165166
pub use mips::{MipsInlineAsmReg, MipsInlineAsmRegClass};
166167
pub use nvptx::{NvptxInlineAsmReg, NvptxInlineAsmRegClass};
168+
pub use powerpc::{PowerPCInlineAsmReg, PowerPCInlineAsmRegClass};
167169
pub use riscv::{RiscVInlineAsmReg, RiscVInlineAsmRegClass};
168170
pub use spirv::{SpirVInlineAsmReg, SpirVInlineAsmRegClass};
169171
pub use wasm::{WasmInlineAsmReg, WasmInlineAsmRegClass};
@@ -181,6 +183,7 @@ pub enum InlineAsmArch {
181183
Hexagon,
182184
Mips,
183185
Mips64,
186+
PowerPC,
184187
SpirV,
185188
Wasm32,
186189
}
@@ -197,6 +200,7 @@ impl FromStr for InlineAsmArch {
197200
"riscv32" => Ok(Self::RiscV32),
198201
"riscv64" => Ok(Self::RiscV64),
199202
"nvptx64" => Ok(Self::Nvptx64),
203+
"powerpc" => Ok(Self::PowerPC),
200204
"hexagon" => Ok(Self::Hexagon),
201205
"mips" => Ok(Self::Mips),
202206
"mips64" => Ok(Self::Mips64),
@@ -225,6 +229,7 @@ pub enum InlineAsmReg {
225229
AArch64(AArch64InlineAsmReg),
226230
RiscV(RiscVInlineAsmReg),
227231
Nvptx(NvptxInlineAsmReg),
232+
PowerPC(PowerPCInlineAsmReg),
228233
Hexagon(HexagonInlineAsmReg),
229234
Mips(MipsInlineAsmReg),
230235
SpirV(SpirVInlineAsmReg),
@@ -240,6 +245,7 @@ impl InlineAsmReg {
240245
Self::Arm(r) => r.name(),
241246
Self::AArch64(r) => r.name(),
242247
Self::RiscV(r) => r.name(),
248+
Self::PowerPC(r) => r.name(),
243249
Self::Hexagon(r) => r.name(),
244250
Self::Mips(r) => r.name(),
245251
Self::Err => "<reg>",
@@ -252,6 +258,7 @@ impl InlineAsmReg {
252258
Self::Arm(r) => InlineAsmRegClass::Arm(r.reg_class()),
253259
Self::AArch64(r) => InlineAsmRegClass::AArch64(r.reg_class()),
254260
Self::RiscV(r) => InlineAsmRegClass::RiscV(r.reg_class()),
261+
Self::PowerPC(r) => InlineAsmRegClass::PowerPC(r.reg_class()),
255262
Self::Hexagon(r) => InlineAsmRegClass::Hexagon(r.reg_class()),
256263
Self::Mips(r) => InlineAsmRegClass::Mips(r.reg_class()),
257264
Self::Err => InlineAsmRegClass::Err,
@@ -283,6 +290,9 @@ impl InlineAsmReg {
283290
InlineAsmArch::Nvptx64 => {
284291
Self::Nvptx(NvptxInlineAsmReg::parse(arch, has_feature, target, &name)?)
285292
}
293+
InlineAsmArch::PowerPC => {
294+
Self::PowerPC(PowerPCInlineAsmReg::parse(arch, has_feature, target, &name)?)
295+
}
286296
InlineAsmArch::Hexagon => {
287297
Self::Hexagon(HexagonInlineAsmReg::parse(arch, has_feature, target, &name)?)
288298
}
@@ -311,6 +321,7 @@ impl InlineAsmReg {
311321
Self::Arm(r) => r.emit(out, arch, modifier),
312322
Self::AArch64(r) => r.emit(out, arch, modifier),
313323
Self::RiscV(r) => r.emit(out, arch, modifier),
324+
Self::PowerPC(r) => r.emit(out, arch, modifier),
314325
Self::Hexagon(r) => r.emit(out, arch, modifier),
315326
Self::Mips(r) => r.emit(out, arch, modifier),
316327
Self::Err => unreachable!("Use of InlineAsmReg::Err"),
@@ -323,6 +334,7 @@ impl InlineAsmReg {
323334
Self::Arm(r) => r.overlapping_regs(|r| cb(Self::Arm(r))),
324335
Self::AArch64(_) => cb(self),
325336
Self::RiscV(_) => cb(self),
337+
Self::PowerPC(_) => cb(self),
326338
Self::Hexagon(r) => r.overlapping_regs(|r| cb(Self::Hexagon(r))),
327339
Self::Mips(_) => cb(self),
328340
Self::Err => unreachable!("Use of InlineAsmReg::Err"),
@@ -348,6 +360,7 @@ pub enum InlineAsmRegClass {
348360
AArch64(AArch64InlineAsmRegClass),
349361
RiscV(RiscVInlineAsmRegClass),
350362
Nvptx(NvptxInlineAsmRegClass),
363+
PowerPC(PowerPCInlineAsmRegClass),
351364
Hexagon(HexagonInlineAsmRegClass),
352365
Mips(MipsInlineAsmRegClass),
353366
SpirV(SpirVInlineAsmRegClass),
@@ -364,6 +377,7 @@ impl InlineAsmRegClass {
364377
Self::AArch64(r) => r.name(),
365378
Self::RiscV(r) => r.name(),
366379
Self::Nvptx(r) => r.name(),
380+
Self::PowerPC(r) => r.name(),
367381
Self::Hexagon(r) => r.name(),
368382
Self::Mips(r) => r.name(),
369383
Self::SpirV(r) => r.name(),
@@ -382,6 +396,7 @@ impl InlineAsmRegClass {
382396
Self::AArch64(r) => r.suggest_class(arch, ty).map(InlineAsmRegClass::AArch64),
383397
Self::RiscV(r) => r.suggest_class(arch, ty).map(InlineAsmRegClass::RiscV),
384398
Self::Nvptx(r) => r.suggest_class(arch, ty).map(InlineAsmRegClass::Nvptx),
399+
Self::PowerPC(r) => r.suggest_class(arch, ty).map(InlineAsmRegClass::PowerPC),
385400
Self::Hexagon(r) => r.suggest_class(arch, ty).map(InlineAsmRegClass::Hexagon),
386401
Self::Mips(r) => r.suggest_class(arch, ty).map(InlineAsmRegClass::Mips),
387402
Self::SpirV(r) => r.suggest_class(arch, ty).map(InlineAsmRegClass::SpirV),
@@ -407,6 +422,7 @@ impl InlineAsmRegClass {
407422
Self::AArch64(r) => r.suggest_modifier(arch, ty),
408423
Self::RiscV(r) => r.suggest_modifier(arch, ty),
409424
Self::Nvptx(r) => r.suggest_modifier(arch, ty),
425+
Self::PowerPC(r) => r.suggest_modifier(arch, ty),
410426
Self::Hexagon(r) => r.suggest_modifier(arch, ty),
411427
Self::Mips(r) => r.suggest_modifier(arch, ty),
412428
Self::SpirV(r) => r.suggest_modifier(arch, ty),
@@ -428,6 +444,7 @@ impl InlineAsmRegClass {
428444
Self::AArch64(r) => r.default_modifier(arch),
429445
Self::RiscV(r) => r.default_modifier(arch),
430446
Self::Nvptx(r) => r.default_modifier(arch),
447+
Self::PowerPC(r) => r.default_modifier(arch),
431448
Self::Hexagon(r) => r.default_modifier(arch),
432449
Self::Mips(r) => r.default_modifier(arch),
433450
Self::SpirV(r) => r.default_modifier(arch),
@@ -448,6 +465,7 @@ impl InlineAsmRegClass {
448465
Self::AArch64(r) => r.supported_types(arch),
449466
Self::RiscV(r) => r.supported_types(arch),
450467
Self::Nvptx(r) => r.supported_types(arch),
468+
Self::PowerPC(r) => r.supported_types(arch),
451469
Self::Hexagon(r) => r.supported_types(arch),
452470
Self::Mips(r) => r.supported_types(arch),
453471
Self::SpirV(r) => r.supported_types(arch),
@@ -467,6 +485,7 @@ impl InlineAsmRegClass {
467485
Self::RiscV(RiscVInlineAsmRegClass::parse(arch, name)?)
468486
}
469487
InlineAsmArch::Nvptx64 => Self::Nvptx(NvptxInlineAsmRegClass::parse(arch, name)?),
488+
InlineAsmArch::PowerPC => Self::PowerPC(PowerPCInlineAsmRegClass::parse(arch, name)?),
470489
InlineAsmArch::Hexagon => Self::Hexagon(HexagonInlineAsmRegClass::parse(arch, name)?),
471490
InlineAsmArch::Mips | InlineAsmArch::Mips64 => {
472491
Self::Mips(MipsInlineAsmRegClass::parse(arch, name)?)
@@ -485,6 +504,7 @@ impl InlineAsmRegClass {
485504
Self::AArch64(r) => r.valid_modifiers(arch),
486505
Self::RiscV(r) => r.valid_modifiers(arch),
487506
Self::Nvptx(r) => r.valid_modifiers(arch),
507+
Self::PowerPC(r) => r.valid_modifiers(arch),
488508
Self::Hexagon(r) => r.valid_modifiers(arch),
489509
Self::Mips(r) => r.valid_modifiers(arch),
490510
Self::SpirV(r) => r.valid_modifiers(arch),
@@ -633,6 +653,11 @@ pub fn allocatable_registers(
633653
nvptx::fill_reg_map(arch, has_feature, target, &mut map);
634654
map
635655
}
656+
InlineAsmArch::PowerPC => {
657+
let mut map = powerpc::regclass_map();
658+
powerpc::fill_reg_map(arch, has_feature, target, &mut map);
659+
map
660+
}
636661
InlineAsmArch::Hexagon => {
637662
let mut map = hexagon::regclass_map();
638663
hexagon::fill_reg_map(arch, has_feature, target, &mut map);
+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
use super::{InlineAsmArch, InlineAsmType};
2+
use rustc_macros::HashStable_Generic;
3+
use std::fmt;
4+
5+
def_reg_class! {
6+
PowerPC PowerPCInlineAsmRegClass {
7+
reg,
8+
reg_nonzero,
9+
freg,
10+
}
11+
}
12+
13+
impl PowerPCInlineAsmRegClass {
14+
pub fn valid_modifiers(self, _arch: super::InlineAsmArch) -> &'static [char] {
15+
&[]
16+
}
17+
18+
pub fn suggest_class(self, _arch: InlineAsmArch, _ty: InlineAsmType) -> Option<Self> {
19+
None
20+
}
21+
22+
pub fn suggest_modifier(
23+
self,
24+
_arch: InlineAsmArch,
25+
_ty: InlineAsmType,
26+
) -> Option<(char, &'static str)> {
27+
None
28+
}
29+
30+
pub fn default_modifier(self, _arch: InlineAsmArch) -> Option<(char, &'static str)> {
31+
None
32+
}
33+
34+
pub fn supported_types(
35+
self,
36+
_arch: InlineAsmArch,
37+
) -> &'static [(InlineAsmType, Option<&'static str>)] {
38+
match self {
39+
Self::reg | Self::reg_nonzero => types! { _: I8, I16, I32; },
40+
Self::freg => types! { _: F32, F64; },
41+
}
42+
}
43+
}
44+
45+
def_regs! {
46+
PowerPC PowerPCInlineAsmReg PowerPCInlineAsmRegClass {
47+
r0: reg = ["r0", "0"],
48+
r3: reg, reg_nonzero = ["r3", "3"],
49+
r4: reg, reg_nonzero = ["r4", "4"],
50+
r5: reg, reg_nonzero = ["r5", "5"],
51+
r6: reg, reg_nonzero = ["r6", "6"],
52+
r7: reg, reg_nonzero = ["r7", "7"],
53+
r8: reg, reg_nonzero = ["r8", "8"],
54+
r9: reg, reg_nonzero = ["r9", "9"],
55+
r10: reg, reg_nonzero = ["r10", "10"],
56+
r11: reg, reg_nonzero = ["r11", "11"],
57+
r12: reg, reg_nonzero = ["r12", "12"],
58+
r14: reg, reg_nonzero = ["r14", "14"],
59+
r15: reg, reg_nonzero = ["r15", "15"],
60+
r16: reg, reg_nonzero = ["r16", "16"],
61+
r17: reg, reg_nonzero = ["r17", "17"],
62+
r18: reg, reg_nonzero = ["r18", "18"],
63+
r19: reg, reg_nonzero = ["r19", "19"],
64+
r20: reg, reg_nonzero = ["r20", "20"],
65+
r21: reg, reg_nonzero = ["r21", "21"],
66+
r22: reg, reg_nonzero = ["r22", "22"],
67+
r23: reg, reg_nonzero = ["r23", "23"],
68+
r24: reg, reg_nonzero = ["r24", "24"],
69+
r25: reg, reg_nonzero = ["r25", "25"],
70+
r26: reg, reg_nonzero = ["r26", "26"],
71+
r27: reg, reg_nonzero = ["r27", "27"],
72+
r28: reg, reg_nonzero = ["r28", "28"],
73+
f0: freg = ["f0", "fr0"],
74+
f1: freg = ["f1", "fr1"],
75+
f2: freg = ["f2", "fr2"],
76+
f3: freg = ["f3", "fr3"],
77+
f4: freg = ["f4", "fr4"],
78+
f5: freg = ["f5", "fr5"],
79+
f6: freg = ["f6", "fr6"],
80+
f7: freg = ["f7", "fr7"],
81+
f8: freg = ["f8", "fr8"],
82+
f9: freg = ["f9", "fr9"],
83+
f10: freg = ["f10", "fr10"],
84+
f11: freg = ["f11", "fr11"],
85+
f12: freg = ["f12", "fr12"],
86+
f13: freg = ["f13", "fr13"],
87+
f14: freg = ["f14", "fr14"],
88+
f15: freg = ["f15", "fr15"],
89+
f16: freg = ["f16", "fr16"],
90+
f17: freg = ["f17", "fr17"],
91+
f18: freg = ["f18", "fr18"],
92+
f19: freg = ["f19", "fr19"],
93+
f20: freg = ["f20", "fr20"],
94+
f21: freg = ["f21", "fr21"],
95+
f22: freg = ["f22", "fr22"],
96+
f23: freg = ["f23", "fr23"],
97+
f24: freg = ["f24", "fr24"],
98+
f25: freg = ["f25", "fr25"],
99+
f26: freg = ["f26", "fr26"],
100+
f27: freg = ["f27", "fr27"],
101+
f28: freg = ["f28", "fr28"],
102+
f29: freg = ["f29", "fr29"],
103+
f30: freg = ["f30", "fr30"],
104+
f31: freg = ["f31", "fr31"],
105+
#error = ["r1", "1", "sp"] =>
106+
"the stack pointer cannot be used as an operand for inline asm",
107+
#error = ["r2", "2"] =>
108+
"r2 is a system reserved register and cannot be used as an operand for inline asm",
109+
#error = ["r13", "13"] =>
110+
"r13 is a system reserved register and cannot be used as an operand for inline asm",
111+
#error = ["r29", "29"] =>
112+
"r29 is used internally by LLVM and cannot be used as an operand for inline asm",
113+
#error = ["r30", "30"] =>
114+
"r30 is used internally by LLVM and cannot be used as an operand for inline asm",
115+
#error = ["r31", "31", "fp"] =>
116+
"the frame pointer cannot be used as an operand for inline asm",
117+
#error = ["lr"] =>
118+
"the link register cannot be used as an operand for inline asm",
119+
#error = ["ctr"] =>
120+
"the counter register cannot be used as an operand for inline asm",
121+
#error = ["vrsave"] =>
122+
"the vrsave register cannot be used as an operand for inline asm",
123+
}
124+
}
125+
126+
impl PowerPCInlineAsmReg {
127+
pub fn emit(
128+
self,
129+
out: &mut dyn fmt::Write,
130+
_arch: InlineAsmArch,
131+
_modifier: Option<char>,
132+
) -> fmt::Result {
133+
// Strip off the leading prefix.
134+
if self as u32 <= Self::r28 as u32 {
135+
let index = self as u32 - Self::r28 as u32;
136+
write!(out, "{}", index)
137+
} else if self as u32 >= Self::f0 as u32 && self as u32 <= Self::f31 as u32 {
138+
let index = self as u32 - Self::f31 as u32;
139+
write!(out, "{}", index)
140+
} else {
141+
unreachable!()
142+
}
143+
}
144+
145+
pub fn overlapping_regs(self, mut _cb: impl FnMut(PowerPCInlineAsmReg)) {}
146+
}

src/doc/unstable-book/src/library-features/asm.md

+11-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Inline assembly is currently supported on the following architectures:
2626
- AArch64
2727
- RISC-V
2828
- NVPTX
29+
- PowerPC
2930
- Hexagon
3031
- MIPS32r2 and MIPS64r2
3132
- wasm32
@@ -459,7 +460,7 @@ options := "options(" option *["," option] [","] ")"
459460
asm := "asm!(" format_string *("," format_string) *("," [ident "="] operand) ["," options] [","] ")"
460461
```
461462

462-
The macro will initially be supported only on ARM, AArch64, Hexagon, x86, x86-64 and RISC-V targets. Support for more targets may be added in the future. The compiler will emit an error if `asm!` is used on an unsupported target.
463+
The macro will initially be supported only on ARM, AArch64, Hexagon, PowerPC, x86, x86-64 and RISC-V targets. Support for more targets may be added in the future. The compiler will emit an error if `asm!` is used on an unsupported target.
463464

464465
[format-syntax]: https://doc.rust-lang.org/std/fmt/#syntax
465466

@@ -565,6 +566,9 @@ Here is the list of currently supported register classes:
565566
| RISC-V | `reg` | `x1`, `x[5-7]`, `x[9-15]`, `x[16-31]` (non-RV32E) | `r` |
566567
| RISC-V | `freg` | `f[0-31]` | `f` |
567568
| Hexagon | `reg` | `r[0-28]` | `r` |
569+
| PowerPC | `reg` | `r[0-31]` | `r` |
570+
| PowerPC | `reg_nonzero` | | `r[1-31]` | `b` |
571+
| PowerPC | `freg` | `f[0-31]` | `f` |
568572
| wasm32 | `local` | None\* | `r` |
569573

570574
> **Note**: On x86 we treat `reg_byte` differently from `reg` because the compiler can allocate `al` and `ah` separately whereas `reg` reserves the whole register.
@@ -607,6 +611,9 @@ Each register class has constraints on which value types they can be used with.
607611
| RISC-V | `freg` | `f` | `f32` |
608612
| RISC-V | `freg` | `d` | `f64` |
609613
| Hexagon | `reg` | None | `i8`, `i16`, `i32`, `f32` |
614+
| PowerPC | `reg` | None | `i8`, `i16`, `i32` |
615+
| PowerPC | `reg_nonzero` | None | `i8`, `i16`, `i32` |
616+
| PowerPC | `freg` | None | `f32`, `f64` |
610617
| wasm32 | `local` | None | `i8` `i16` `i32` `i64` `f32` `f64` |
611618

612619
> **Note**: For the purposes of the above table pointers, function pointers and `isize`/`usize` are treated as the equivalent integer type (`i16`/`i32`/`i64` depending on the target).
@@ -744,6 +751,9 @@ The supported modifiers are a subset of LLVM's (and GCC's) [asm template argumen
744751
| RISC-V | `reg` | None | `x1` | None |
745752
| RISC-V | `freg` | None | `f0` | None |
746753
| Hexagon | `reg` | None | `r0` | None |
754+
| PowerPC | `reg` | None | `0` | None |
755+
| PowerPC | `reg_nonzero` | None | `3` | `b` |
756+
| PowerPC | `freg` | None | `0` | None |
747757

748758
> Notes:
749759
> - on ARM `e` / `f`: this prints the low or high doubleword register name of a NEON quad (128-bit) register.

0 commit comments

Comments
 (0)