LLVM: lib/Target/AMDGPU/MCTargetDesc/AMDGPUMCCodeEmitter.cpp Source File (original) (raw)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

29#include

30

31using namespace llvm;

32

33namespace {

34

36 const MCRegisterInfo &MRI;

37 const MCInstrInfo &MCII;

38

39public:

40 AMDGPUMCCodeEmitter(const MCInstrInfo &MCII, const MCRegisterInfo &MRI)

41 : MRI(MRI), MCII(MCII) {}

42

43

44 void encodeInstruction(const MCInst &MI, SmallVectorImpl &CB,

45 SmallVectorImpl &Fixups,

46 const MCSubtargetInfo &STI) const override;

47

48 void getMachineOpValue(const MCInst &MI, const MCOperand &MO, APInt &Op,

49 SmallVectorImpl &Fixups,

50 const MCSubtargetInfo &STI) const;

51

52 void getMachineOpValueT16(const MCInst &MI, unsigned OpNo, APInt &Op,

53 SmallVectorImpl &Fixups,

54 const MCSubtargetInfo &STI) const;

55

56 void getMachineOpValueT16Lo128(const MCInst &MI, unsigned OpNo, APInt &Op,

57 SmallVectorImpl &Fixups,

58 const MCSubtargetInfo &STI) const;

59

60

61

62 void getSOPPBrEncoding(const MCInst &MI, unsigned OpNo, APInt &Op,

63 SmallVectorImpl &Fixups,

64 const MCSubtargetInfo &STI) const;

65

66 void getSMEMOffsetEncoding(const MCInst &MI, unsigned OpNo, APInt &Op,

67 SmallVectorImpl &Fixups,

68 const MCSubtargetInfo &STI) const;

69

70 void getSDWASrcEncoding(const MCInst &MI, unsigned OpNo, APInt &Op,

71 SmallVectorImpl &Fixups,

72 const MCSubtargetInfo &STI) const;

73

74 void getSDWAVopcDstEncoding(const MCInst &MI, unsigned OpNo, APInt &Op,

75 SmallVectorImpl &Fixups,

76 const MCSubtargetInfo &STI) const;

77

78 void getAVOperandEncoding(const MCInst &MI, unsigned OpNo, APInt &Op,

79 SmallVectorImpl &Fixups,

80 const MCSubtargetInfo &STI) const;

81

82private:

83 uint64_t getImplicitOpSelHiEncoding(int Opcode) const;

84 void getMachineOpValueCommon(const MCInst &MI, const MCOperand &MO,

85 unsigned OpNo, APInt &Op,

86 SmallVectorImpl &Fixups,

87 const MCSubtargetInfo &STI) const;

88

89

90 std::optional<uint64_t>

91 getLitEncoding(const MCInstrDesc &Desc, const MCOperand &MO, unsigned OpNo,

92 const MCSubtargetInfo &STI,

93 bool HasMandatoryLiteral = false) const;

94

95 void getBinaryCodeForInstr(const MCInst &MI, SmallVectorImpl &Fixups,

96 APInt &Inst, APInt &Scratch,

97 const MCSubtargetInfo &STI) const;

98};

99

100}

101

104 return new AMDGPUMCCodeEmitter(MCII, *Ctx.getRegisterInfo());

105}

106

111

112

113

114template

116 if (Imm >= 0 && Imm <= 64)

117 return 128 + Imm;

118

119 if (Imm >= -16 && Imm <= -1)

120 return 192 + std::abs(Imm);

121

122 return 0;

123}

124

127 if (IntImm != 0)

128 return IntImm;

129

130 if (Val == 0x3800)

131 return 240;

132

133 if (Val == 0xB800)

134 return 241;

135

136 if (Val == 0x3C00)

137 return 242;

138

139 if (Val == 0xBC00)

140 return 243;

141

142 if (Val == 0x4000)

143 return 244;

144

145 if (Val == 0xC000)

146 return 245;

147

148 if (Val == 0x4400)

149 return 246;

150

151 if (Val == 0xC400)

152 return 247;

153

154 if (Val == 0x3118 &&

155 STI.hasFeature(AMDGPU::FeatureInv2PiInlineImm))

156 return 248;

157

158 return 255;

159}

160

163 if (IntImm != 0)

164 return IntImm;

165

166

167 switch (Val) {

168 case 0x3F00: return 240;

169 case 0xBF00: return 241;

170 case 0x3F80: return 242;

171 case 0xBF80: return 243;

172 case 0x4000: return 244;

173 case 0xC000: return 245;

174 case 0x4080: return 246;

175 case 0xC080: return 247;

176 case 0x3E22: return 248;

177 default: return 255;

178 }

179

180}

181

184 if (IntImm != 0)

185 return IntImm;

186

188 return 240;

189

191 return 241;

192

194 return 242;

195

197 return 243;

198

200 return 244;

201

203 return 245;

204

206 return 246;

207

209 return 247;

210

211 if (Val == 0x3e22f983 &&

212 STI.hasFeature(AMDGPU::FeatureInv2PiInlineImm))

213 return 248;

214

215 return 255;

216}

217

221

225 if (IntImm != 0)

226 return IntImm;

227

229 return 240;

230

232 return 241;

233

235 return 242;

236

238 return 243;

239

241 return 244;

242

244 return 245;

245

247 return 246;

248

250 return 247;

251

252 if (Val == 0x3fc45f306dc9c882 &&

253 STI.hasFeature(AMDGPU::FeatureInv2PiInlineImm))

254 return 248;

255

256

257

258 bool CanUse64BitLiterals =

259 STI.hasFeature(AMDGPU::Feature64BitLiterals) &&

261 if (IsFP) {

262 return CanUse64BitLiterals && Lo_32(Val) ? 254 : 255;

263 }

264

265 return CanUse64BitLiterals && (isInt<32>(Val) || isUInt<32>(Val)) ? 254

266 : 255;

267}

268

269std::optional<uint64_t> AMDGPUMCCodeEmitter::getLitEncoding(

271 const MCSubtargetInfo &STI, bool HasMandatoryLiteral) const {

272 const MCOperandInfo &OpInfo = Desc.operands()[OpNo];

273 int64_t Imm = 0;

275 if (!MO.getExpr()->evaluateAsAbsolute(Imm) ||

280 return Imm;

281 if (STI.hasFeature(AMDGPU::Feature64BitLiterals) &&

283 return 254;

284 return 255;

285 }

286 } else {

288

290 return {};

291

293 }

294

306

310

314

317 return (HasMandatoryLiteral && Enc == 255) ? 254 : Enc;

318 }

319

323

326

327

329

332

333

335

339 .value_or(255);

340

344 .value_or(255);

345

349 .value_or(255);

350

352 return 255;

353

357 return Imm;

358 default:

360 }

361}

362

363uint64_t AMDGPUMCCodeEmitter::getImplicitOpSelHiEncoding(int Opcode) const {

364 using namespace AMDGPU::VOP3PEncoding;

365

368 return 0;

373 }

375}

376

379 Desc.hasImplicitDefOfPhysReg(AMDGPU::EXEC);

380}

381

382void AMDGPUMCCodeEmitter::encodeInstruction(const MCInst &MI,

383 SmallVectorImpl &CB,

384 SmallVectorImpl &Fixups,

385 const MCSubtargetInfo &STI) const {

386 int Opcode = MI.getOpcode();

387 APInt Encoding, Scratch;

388 getBinaryCodeForInstr(MI, Fixups, Encoding, Scratch, STI);

389 const MCInstrDesc &Desc = MCII.get(MI.getOpcode());

390 unsigned bytes = Desc.getSize();

391

392

393

395 Opcode == AMDGPU::V_ACCVGPR_READ_B32_vi ||

396 Opcode == AMDGPU::V_ACCVGPR_WRITE_B32_vi) &&

397

399

401

403 Encoding |= getImplicitOpSelHiEncoding(Opcode);

404 }

405

406

407

408

409

410

411

413 assert((Encoding & 0xFF) == 0);

414 Encoding |= MRI.getEncodingValue(AMDGPU::EXEC_LO) &

416 }

417

418 for (unsigned i = 0; i < bytes; i++) {

420 }

421

422

424 int vaddr0 = AMDGPU::getNamedOperandIdx(MI.getOpcode(),

425 AMDGPU::OpName::vaddr0);

426 int srsrc = AMDGPU::getNamedOperandIdx(MI.getOpcode(),

427 AMDGPU::OpName::srsrc);

428 assert(vaddr0 >= 0 && srsrc > vaddr0);

429 unsigned NumExtraAddrs = srsrc - vaddr0 - 1;

430 unsigned NumPadding = (-NumExtraAddrs) & 3;

431

432 for (unsigned i = 0; i < NumExtraAddrs; ++i) {

433 getMachineOpValue(MI, MI.getOperand(vaddr0 + 1 + i), Encoding, Fixups,

434 STI);

436 }

437 CB.append(NumPadding, 0);

438 }

439

440 if ((bytes > 8 && STI.hasFeature(AMDGPU::FeatureVOP3Literal)) ||

441 (bytes > 4 && !STI.hasFeature(AMDGPU::FeatureVOP3Literal)))

442 return;

443

444

446 return;

447

448

449 for (unsigned i = 0, e = Desc.getNumOperands(); i < e; ++i) {

450

451

453 continue;

454

455

456 const MCOperand &Op = MI.getOperand(i);

457 auto Enc = getLitEncoding(Desc, Op, i, STI);

458 if (!Enc || (*Enc != 255 && *Enc != 254))

459 continue;

460

461

462 int64_t Imm = 0;

463

464 bool IsLit = false;

465 if (Op.isImm())

466 Imm = Op.getImm();

467 else if (Op.isExpr()) {

469 Imm = C->getValue();

471 IsLit = true;

473 }

474 } else

476

477 if (*Enc == 254) {

480 } else {

481 auto OpType =

485 }

486

487

488 break;

489 }

490}

491

492void AMDGPUMCCodeEmitter::getSOPPBrEncoding(const MCInst &MI, unsigned OpNo,

493 APInt &Op,

494 SmallVectorImpl &Fixups,

495 const MCSubtargetInfo &STI) const {

496 const MCOperand &MO = MI.getOperand(OpNo);

497

499 const MCExpr *Expr = MO.getExpr();

502 } else {

503 getMachineOpValue(MI, MO, Op, Fixups, STI);

504 }

505}

506

507void AMDGPUMCCodeEmitter::getSMEMOffsetEncoding(

508 const MCInst &MI, unsigned OpNo, APInt &Op,

509 SmallVectorImpl &Fixups, const MCSubtargetInfo &STI) const {

510 auto Offset = MI.getOperand(OpNo).getImm();

511

514}

515

516void AMDGPUMCCodeEmitter::getSDWASrcEncoding(const MCInst &MI, unsigned OpNo,

517 APInt &Op,

518 SmallVectorImpl &Fixups,

519 const MCSubtargetInfo &STI) const {

520 using namespace AMDGPU::SDWA;

521

522 uint64_t RegEnc = 0;

523

524 const MCOperand &MO = MI.getOperand(OpNo);

525

526 if (MO.isReg()) {

528 RegEnc |= MRI.getEncodingValue(Reg);

529 RegEnc &= SDWA9EncValues::SRC_VGPR_MASK;

531 RegEnc |= SDWA9EncValues::SRC_SGPR_MASK;

532 }

533 Op = RegEnc;

534 return;

535 } else {

536 const MCInstrDesc &Desc = MCII.get(MI.getOpcode());

537 auto Enc = getLitEncoding(Desc, MO, OpNo, STI);

538 if (Enc && *Enc != 255) {

539 Op = *Enc | SDWA9EncValues::SRC_SGPR_MASK;

540 return;

541 }

542 }

543

545}

546

547void AMDGPUMCCodeEmitter::getSDWAVopcDstEncoding(

548 const MCInst &MI, unsigned OpNo, APInt &Op,

549 SmallVectorImpl &Fixups, const MCSubtargetInfo &STI) const {

550 using namespace AMDGPU::SDWA;

551

552 uint64_t RegEnc = 0;

553

554 const MCOperand &MO = MI.getOperand(OpNo);

555

557 if (Reg != AMDGPU::VCC && Reg != AMDGPU::VCC_LO) {

558 RegEnc |= MRI.getEncodingValue(Reg);

559 RegEnc &= SDWA9EncValues::VOPC_DST_SGPR_MASK;

560 RegEnc |= SDWA9EncValues::VOPC_DST_VCC_MASK;

561 }

562 Op = RegEnc;

563}

564

565void AMDGPUMCCodeEmitter::getAVOperandEncoding(

566 const MCInst &MI, unsigned OpNo, APInt &Op,

567 SmallVectorImpl &Fixups, const MCSubtargetInfo &STI) const {

568 MCRegister Reg = MI.getOperand(OpNo).getReg();

569 unsigned Enc = MRI.getEncodingValue(Reg);

571 bool IsVGPROrAGPR =

573

574

575

576

578

579 Op = Idx | (IsVGPROrAGPR << 8) | (IsAGPR << 9);

580}

581

583 switch (Expr->getKind()) {

589 }

593 return false;

595 }

601 return false;

602 }

604}

605

606void AMDGPUMCCodeEmitter::getMachineOpValue(const MCInst &MI,

607 const MCOperand &MO, APInt &Op,

608 SmallVectorImpl &Fixups,

609 const MCSubtargetInfo &STI) const {

611 unsigned Enc = MRI.getEncodingValue(MO.getReg());

613 bool IsVGPROrAGPR =

615 Op = Idx | (IsVGPROrAGPR << 8);

616 return;

617 }

618 unsigned OpNo = &MO - MI.begin();

619 getMachineOpValueCommon(MI, MO, OpNo, Op, Fixups, STI);

620}

621

622void AMDGPUMCCodeEmitter::getMachineOpValueT16(

623 const MCInst &MI, unsigned OpNo, APInt &Op,

624 SmallVectorImpl &Fixups, const MCSubtargetInfo &STI) const {

625 const MCOperand &MO = MI.getOperand(OpNo);

626 if (MO.isReg()) {

627 unsigned Enc = MRI.getEncodingValue(MO.getReg());

630 Op = Idx | (IsVGPR << 8);

631 return;

632 }

633 getMachineOpValueCommon(MI, MO, OpNo, Op, Fixups, STI);

634

635

636

637

638 int SrcMOIdx = -1;

639 assert(OpNo < INT_MAX);

640 if ((int)OpNo == AMDGPU::getNamedOperandIdx(MI.getOpcode(),

641 AMDGPU::OpName::src0_modifiers)) {

642 SrcMOIdx = AMDGPU::getNamedOperandIdx(MI.getOpcode(), AMDGPU::OpName::src0);

643 int VDstMOIdx =

644 AMDGPU::getNamedOperandIdx(MI.getOpcode(), AMDGPU::OpName::vdst);

645 if (VDstMOIdx != -1) {

646 auto DstReg = MI.getOperand(VDstMOIdx).getReg();

649 }

650 } else if ((int)OpNo == AMDGPU::getNamedOperandIdx(

651 MI.getOpcode(), AMDGPU::OpName::src1_modifiers))

652 SrcMOIdx = AMDGPU::getNamedOperandIdx(MI.getOpcode(), AMDGPU::OpName::src1);

653 else if ((int)OpNo == AMDGPU::getNamedOperandIdx(

654 MI.getOpcode(), AMDGPU::OpName::src2_modifiers))

655 SrcMOIdx = AMDGPU::getNamedOperandIdx(MI.getOpcode(), AMDGPU::OpName::src2);

656 if (SrcMOIdx == -1)

657 return;

658

659 const MCOperand &SrcMO = MI.getOperand(SrcMOIdx);

660 if (!SrcMO.isReg())

661 return;

662 auto SrcReg = SrcMO.getReg();

664 return;

667}

668

669void AMDGPUMCCodeEmitter::getMachineOpValueT16Lo128(

670 const MCInst &MI, unsigned OpNo, APInt &Op,

671 SmallVectorImpl &Fixups, const MCSubtargetInfo &STI) const {

672 const MCOperand &MO = MI.getOperand(OpNo);

673 if (MO.isReg()) {

674 uint16_t Encoding = MRI.getEncodingValue(MO.getReg());

678 assert((!IsVGPR || isUInt<7>(RegIdx)) && "VGPR0-VGPR127 expected!");

679 Op = (IsVGPR ? 0x100 : 0) | (IsHi ? 0x80 : 0) | RegIdx;

680 return;

681 }

682 getMachineOpValueCommon(MI, MO, OpNo, Op, Fixups, STI);

683}

684

685void AMDGPUMCCodeEmitter::getMachineOpValueCommon(

686 const MCInst &MI, const MCOperand &MO, unsigned OpNo, APInt &Op,

687 SmallVectorImpl &Fixups, const MCSubtargetInfo &STI) const {

688 bool isLikeImm = false;

689 int64_t Val;

690

691 if (MO.isImm()) {

693 isLikeImm = true;

694 } else if (MO.isExpr() && MO.getExpr()->evaluateAsAbsolute(Val)) {

695 isLikeImm = true;

696 } else if (MO.isExpr()) {

697

698

699

700

701

702

703

704

705

706

708 const MCInstrDesc &Desc = MCII.get(MI.getOpcode());

714 }

715

716 const MCInstrDesc &Desc = MCII.get(MI.getOpcode());

718 bool HasMandatoryLiteral =

720 if (auto Enc = getLitEncoding(Desc, MO, OpNo, STI, HasMandatoryLiteral)) {

721 Op = *Enc;

722 return;

723 }

724

726 }

727

728 if (isLikeImm) {

729 Op = Val;

730 return;

731 }

732

733 llvm_unreachable("Encoding of this operand type is not supported yet.");

734}

735

736#include "AMDGPUGenMCCodeEmitter.inc"

unsigned const MachineRegisterInfo * MRI

static void addFixup(SmallVectorImpl< MCFixup > &Fixups, uint32_t Offset, const MCExpr *Value, uint16_t Kind, bool PCRel=false)

assert(UImm &&(UImm !=~static_cast< T >(0)) &&"Invalid immediate!")

static uint32_t getLit64Encoding(const MCInstrDesc &Desc, uint64_t Val, const MCSubtargetInfo &STI, bool IsFP)

Definition AMDGPUMCCodeEmitter.cpp:222

static uint32_t getLit16IntEncoding(uint32_t Val, const MCSubtargetInfo &STI)

Definition AMDGPUMCCodeEmitter.cpp:218

static void addFixup(SmallVectorImpl< MCFixup > &Fixups, uint32_t Offset, const MCExpr *Value, uint16_t Kind, bool PCRel=false)

Definition AMDGPUMCCodeEmitter.cpp:107

static uint32_t getLitBF16Encoding(uint16_t Val)

Definition AMDGPUMCCodeEmitter.cpp:161

static bool isVCMPX64(const MCInstrDesc &Desc)

Definition AMDGPUMCCodeEmitter.cpp:377

static uint32_t getLit16Encoding(uint16_t Val, const MCSubtargetInfo &STI)

Definition AMDGPUMCCodeEmitter.cpp:125

static uint32_t getIntInlineImmEncoding(IntTy Imm)

Definition AMDGPUMCCodeEmitter.cpp:115

static bool needsPCRel(const MCExpr *Expr)

Definition AMDGPUMCCodeEmitter.cpp:582

static uint32_t getLit32Encoding(uint32_t Val, const MCSubtargetInfo &STI)

Definition AMDGPUMCCodeEmitter.cpp:182

Provides AMDGPU specific target descriptions.

This file implements a class to represent arbitrary precision integral constant values and operations...

LLVM_ABI uint64_t extractBitsAsZExtValue(unsigned numBits, unsigned bitPosition) const

uint64_t getLimitedValue(uint64_t Limit=UINT64_MAX) const

If this value is smaller than the specified limit, return it, otherwise return the limit value.

static APInt getZero(unsigned numBits)

Get the '0' value for the specified bit-width.

MCCodeEmitter - Generic instruction encoding interface.

Context object for machine code objects.

Base class for the full range of assembler expressions which are needed for parsing.

@ Unary

Unary expressions.

@ Constant

Constant expressions.

@ SymbolRef

References to labels and assigned expressions.

@ Target

Target specific expression.

@ Specifier

Expression with a relocation specifier.

@ Binary

Binary expressions.

static MCFixupKind getDataKindForSize(unsigned Size)

Return the generic fixup kind for a value with the given size.

static MCFixup create(uint32_t Offset, const MCExpr *Value, MCFixupKind Kind, bool PCRel=false)

Consider bit fields if we need more flags.

Describe properties that are true of each instruction in the target description file.

Interface to description of machine instruction set.

const MCInstrDesc & get(unsigned Opcode) const

Return the machine instruction descriptor that corresponds to the specified instruction opcode.

uint8_t OperandType

Information about the type of the operand.

Instances of this class represent operands of the MCInst class.

MCRegister getReg() const

Returns the register number.

const MCExpr * getExpr() const

Generic base class for all target subtargets.

bool hasFeature(unsigned Feature) const

This class consists of common code factored out of the SmallVector class to reduce code duplication b...

void append(ItTy in_start, ItTy in_end)

Add the specified range to the end of the SmallVector.

void push_back(const T &Elt)

LLVM Value Representation.

#define llvm_unreachable(msg)

Marks that the current location is not supposed to be reachable.

bool isSGPR(MCRegister Reg, const MCRegisterInfo *TRI)

Is Reg - scalar register.

bool isHi16Reg(MCRegister Reg, const MCRegisterInfo &MRI)

static AMDGPUMCExpr::Specifier getSpecifier(const MCSymbolRefExpr *SRE)

LLVM_READONLY bool isLitExpr(const MCExpr *Expr)

@ fixup_si_sopp_br

16-bit PC relative fixup for SOPP branch instructions.

LLVM_READONLY bool hasNamedOperand(uint64_t Opcode, OpName NamedIdx)

constexpr bool isSISrcOperand(const MCOperandInfo &OpInfo)

Is this an AMDGPU specific source operand?

LLVM_READONLY int64_t getLitValue(const MCExpr *Expr)

std::optional< unsigned > getInlineEncodingV2F16(uint32_t Literal)

bool isGFX10Plus(const MCSubtargetInfo &STI)

int64_t encode32BitLiteral(int64_t Imm, OperandType Type, bool IsLit)

@ OPERAND_KIMM32

Operand with 32-bit immediate that uses the constant bus.

@ OPERAND_REG_INLINE_C_FP64

@ OPERAND_REG_INLINE_C_BF16

@ OPERAND_REG_INLINE_C_V2BF16

@ OPERAND_REG_IMM_V2INT16

@ OPERAND_REG_IMM_INT32

Operands with register, 32-bit, or 64-bit immediate.

@ OPERAND_REG_INLINE_C_INT64

@ OPERAND_REG_INLINE_C_INT16

Operands with register or inline constant.

@ OPERAND_REG_IMM_NOINLINE_V2FP16

@ OPERAND_REG_INLINE_C_V2FP16

@ OPERAND_REG_INLINE_AC_INT32

Operands with an AccVGPR register or inline constant.

@ OPERAND_REG_INLINE_AC_FP32

@ OPERAND_REG_IMM_V2INT32

@ OPERAND_REG_INLINE_C_FP32

@ OPERAND_REG_INLINE_C_INT32

@ OPERAND_REG_INLINE_C_V2INT16

@ OPERAND_REG_INLINE_AC_FP64

@ OPERAND_REG_INLINE_C_FP16

@ OPERAND_INLINE_SPLIT_BARRIER_INT32

std::optional< unsigned > getInlineEncodingV2I16(uint32_t Literal)

bool isVI(const MCSubtargetInfo &STI)

MCRegister mc2PseudoReg(MCRegister Reg)

Convert hardware register Reg to a pseudo register.

std::optional< unsigned > getInlineEncodingV2BF16(uint32_t Literal)

LLVM_READNONE unsigned getOperandSize(const MCOperandInfo &OpInfo)

@ C

The default llvm calling convention, compatible with C.

void write(void *memory, value_type value, endianness endian)

Write a value to memory with a particular endianness.

This is an optimization pass for GlobalISel generic memory operations.

constexpr bool isInt(int64_t x)

Checks if an integer fits into the given bit width.

decltype(auto) dyn_cast(const From &Val)

dyn_cast - Return the argument parameter cast to the specified type.

uint16_t MCFixupKind

Extensible enumeration to represent the type of a fixup.

constexpr bool isUInt(uint64_t x)

Checks if an unsigned integer fits into the given bit width.

constexpr uint32_t Lo_32(uint64_t Value)

Return the low 32 bits of a 64 bit value.

To bit_cast(const From &from) noexcept

DWARFExpression::Operation Op

decltype(auto) cast(const From &Val)

cast - Return the argument parameter cast to the specified type.

MCCodeEmitter * createAMDGPUMCCodeEmitter(const MCInstrInfo &MCII, MCContext &Ctx)

Definition AMDGPUMCCodeEmitter.cpp:102