SparcAsmPrinter.cpp revision 1c4f4356032195f05c715b113b4ee5e2d4909915
1//===-- SparcV8AsmPrinter.cpp - SparcV8 LLVM assembly writer --------------===// 2// 3// The LLVM Compiler Infrastructure 4// 5// This file was developed by the LLVM research group and is distributed under 6// the University of Illinois Open Source License. See LICENSE.TXT for details. 7// 8//===----------------------------------------------------------------------===// 9// 10// This file contains a printer that converts from our internal representation 11// of machine-dependent LLVM code to GAS-format Sparc V8 assembly language. 12// 13//===----------------------------------------------------------------------===// 14 15#include "SparcV8.h" 16#include "SparcV8InstrInfo.h" 17#include "llvm/Constants.h" 18#include "llvm/DerivedTypes.h" 19#include "llvm/Module.h" 20#include "llvm/Assembly/Writer.h" 21#include "llvm/CodeGen/MachineFunctionPass.h" 22#include "llvm/CodeGen/MachineConstantPool.h" 23#include "llvm/CodeGen/MachineInstr.h" 24#include "llvm/Target/TargetMachine.h" 25#include "llvm/Support/Mangler.h" 26#include "llvm/ADT/Statistic.h" 27#include "llvm/ADT/StringExtras.h" 28#include "llvm/Support/CommandLine.h" 29#include "llvm/Support/MathExtras.h" 30#include <cctype> 31using namespace llvm; 32 33namespace { 34 Statistic<> EmittedInsts("asm-printer", "Number of machine instrs printed"); 35 36 struct SparcV8AsmPrinter : public MachineFunctionPass { 37 /// Output stream on which we're printing assembly code. 38 /// 39 std::ostream &O; 40 41 /// Target machine description which we query for reg. names, data 42 /// layout, etc. 43 /// 44 TargetMachine &TM; 45 46 /// Name-mangler for global names. 47 /// 48 Mangler *Mang; 49 50 SparcV8AsmPrinter(std::ostream &o, TargetMachine &tm) : O(o), TM(tm) { } 51 52 /// We name each basic block in a Function with a unique number, so 53 /// that we can consistently refer to them later. This is cleared 54 /// at the beginning of each call to runOnMachineFunction(). 55 /// 56 typedef std::map<const Value *, unsigned> ValueMapTy; 57 ValueMapTy NumberForBB; 58 59 /// Cache of mangled name for current function. This is 60 /// recalculated at the beginning of each call to 61 /// runOnMachineFunction(). 62 /// 63 std::string CurrentFnName; 64 65 virtual const char *getPassName() const { 66 return "SparcV8 Assembly Printer"; 67 } 68 69 void emitConstantValueOnly(const Constant *CV); 70 void emitGlobalConstant(const Constant *CV); 71 void printConstantPool(MachineConstantPool *MCP); 72 void printOperand(const MachineInstr *MI, int opNum); 73 void printBaseOffsetPair (const MachineInstr *MI, int i); 74 void printMachineInstruction(const MachineInstr *MI); 75 bool printInstruction(const MachineInstr *MI); // autogenerated. 76 bool runOnMachineFunction(MachineFunction &F); 77 bool doInitialization(Module &M); 78 bool doFinalization(Module &M); 79 }; 80} // end of anonymous namespace 81 82#include "SparcV8GenAsmWriter.inc" 83 84/// createSparcV8CodePrinterPass - Returns a pass that prints the SparcV8 85/// assembly code for a MachineFunction to the given output stream, 86/// using the given target machine description. This should work 87/// regardless of whether the function is in SSA form. 88/// 89FunctionPass *llvm::createSparcV8CodePrinterPass (std::ostream &o, 90 TargetMachine &tm) { 91 return new SparcV8AsmPrinter(o, tm); 92} 93 94/// toOctal - Convert the low order bits of X into an octal digit. 95/// 96static inline char toOctal(int X) { 97 return (X&7)+'0'; 98} 99 100/// getAsCString - Return the specified array as a C compatible 101/// string, only if the predicate isStringCompatible is true. 102/// 103static void printAsCString(std::ostream &O, const ConstantArray *CVA) { 104 assert(CVA->isString() && "Array is not string compatible!"); 105 106 O << "\""; 107 for (unsigned i = 0; i != CVA->getNumOperands(); ++i) { 108 unsigned char C = cast<ConstantInt>(CVA->getOperand(i))->getRawValue(); 109 110 if (C == '"') { 111 O << "\\\""; 112 } else if (C == '\\') { 113 O << "\\\\"; 114 } else if (isprint(C)) { 115 O << C; 116 } else { 117 switch(C) { 118 case '\b': O << "\\b"; break; 119 case '\f': O << "\\f"; break; 120 case '\n': O << "\\n"; break; 121 case '\r': O << "\\r"; break; 122 case '\t': O << "\\t"; break; 123 default: 124 O << '\\'; 125 O << toOctal(C >> 6); 126 O << toOctal(C >> 3); 127 O << toOctal(C >> 0); 128 break; 129 } 130 } 131 } 132 O << "\""; 133} 134 135// Print out the specified constant, without a storage class. Only the 136// constants valid in constant expressions can occur here. 137void SparcV8AsmPrinter::emitConstantValueOnly(const Constant *CV) { 138 if (CV->isNullValue() || isa<UndefValue> (CV)) 139 O << "0"; 140 else if (const ConstantBool *CB = dyn_cast<ConstantBool>(CV)) { 141 assert(CB == ConstantBool::True); 142 O << "1"; 143 } else if (const ConstantSInt *CI = dyn_cast<ConstantSInt>(CV)) 144 if (((CI->getValue() << 32) >> 32) == CI->getValue()) 145 O << CI->getValue(); 146 else 147 O << (unsigned long long)CI->getValue(); 148 else if (const ConstantUInt *CI = dyn_cast<ConstantUInt>(CV)) 149 O << CI->getValue(); 150 else if (const GlobalValue *GV = dyn_cast<GlobalValue>(CV)) 151 // This is a constant address for a global variable or function. Use the 152 // name of the variable or function as the address value. 153 O << Mang->getValueName(GV); 154 else if (const ConstantExpr *CE = dyn_cast<ConstantExpr>(CV)) { 155 const TargetData &TD = TM.getTargetData(); 156 switch(CE->getOpcode()) { 157 case Instruction::GetElementPtr: { 158 // generate a symbolic expression for the byte address 159 const Constant *ptrVal = CE->getOperand(0); 160 std::vector<Value*> idxVec(CE->op_begin()+1, CE->op_end()); 161 if (unsigned Offset = TD.getIndexedOffset(ptrVal->getType(), idxVec)) { 162 O << "("; 163 emitConstantValueOnly(ptrVal); 164 O << ") + " << Offset; 165 } else { 166 emitConstantValueOnly(ptrVal); 167 } 168 break; 169 } 170 case Instruction::Cast: { 171 // Support only non-converting or widening casts for now, that is, ones 172 // that do not involve a change in value. This assertion is really gross, 173 // and may not even be a complete check. 174 Constant *Op = CE->getOperand(0); 175 const Type *OpTy = Op->getType(), *Ty = CE->getType(); 176 177 // Pointers on ILP32 machines can be losslessly converted back and 178 // forth into 32-bit or wider integers, regardless of signedness. 179 assert(((isa<PointerType>(OpTy) 180 && (Ty == Type::LongTy || Ty == Type::ULongTy 181 || Ty == Type::IntTy || Ty == Type::UIntTy)) 182 || (isa<PointerType>(Ty) 183 && (OpTy == Type::LongTy || OpTy == Type::ULongTy 184 || OpTy == Type::IntTy || OpTy == Type::UIntTy)) 185 || (((TD.getTypeSize(Ty) >= TD.getTypeSize(OpTy)) 186 && OpTy->isLosslesslyConvertibleTo(Ty)))) 187 && "FIXME: Don't yet support this kind of constant cast expr"); 188 O << "("; 189 emitConstantValueOnly(Op); 190 O << ")"; 191 break; 192 } 193 case Instruction::Add: 194 O << "("; 195 emitConstantValueOnly(CE->getOperand(0)); 196 O << ") + ("; 197 emitConstantValueOnly(CE->getOperand(1)); 198 O << ")"; 199 break; 200 default: 201 assert(0 && "Unsupported operator!"); 202 } 203 } else { 204 assert(0 && "Unknown constant value!"); 205 } 206} 207 208// Print a constant value or values, with the appropriate storage class as a 209// prefix. 210void SparcV8AsmPrinter::emitGlobalConstant(const Constant *CV) { 211 const TargetData &TD = TM.getTargetData(); 212 213 if (const ConstantArray *CVA = dyn_cast<ConstantArray>(CV)) { 214 if (CVA->isString()) { 215 O << "\t.ascii\t"; 216 printAsCString(O, CVA); 217 O << "\n"; 218 } else { // Not a string. Print the values in successive locations 219 for (unsigned i = 0, e = CVA->getNumOperands(); i != e; i++) 220 emitGlobalConstant(CVA->getOperand(i)); 221 } 222 return; 223 } else if (const ConstantStruct *CVS = dyn_cast<ConstantStruct>(CV)) { 224 // Print the fields in successive locations. Pad to align if needed! 225 const StructLayout *cvsLayout = TD.getStructLayout(CVS->getType()); 226 unsigned sizeSoFar = 0; 227 for (unsigned i = 0, e = CVS->getNumOperands(); i != e; i++) { 228 const Constant* field = CVS->getOperand(i); 229 230 // Check if padding is needed and insert one or more 0s. 231 unsigned fieldSize = TD.getTypeSize(field->getType()); 232 unsigned padSize = ((i == e-1? cvsLayout->StructSize 233 : cvsLayout->MemberOffsets[i+1]) 234 - cvsLayout->MemberOffsets[i]) - fieldSize; 235 sizeSoFar += fieldSize + padSize; 236 237 // Now print the actual field value 238 emitGlobalConstant(field); 239 240 // Insert the field padding unless it's zero bytes... 241 if (padSize) 242 O << "\t.skip\t " << padSize << "\n"; 243 } 244 assert(sizeSoFar == cvsLayout->StructSize && 245 "Layout of constant struct may be incorrect!"); 246 return; 247 } else if (const ConstantFP *CFP = dyn_cast<ConstantFP>(CV)) { 248 // FP Constants are printed as integer constants to avoid losing 249 // precision... 250 double Val = CFP->getValue(); 251 switch (CFP->getType()->getTypeID()) { 252 default: assert(0 && "Unknown floating point type!"); 253 case Type::FloatTyID: { 254 O << ".long\t" << FloatToBits(Val) << "\t! float " << Val << "\n"; 255 return; 256 } 257 case Type::DoubleTyID: { 258 O << ".word\t0x" << std::hex << (DoubleToBits(Val) >> 32) << std::dec << "\t! double " << Val << "\n"; 259 O << ".word\t0x" << std::hex << (DoubleToBits(Val) & 0xffffffffUL) << std::dec << "\t! double " << Val << "\n"; 260 return; 261 } 262 } 263 } else if (isa<UndefValue> (CV)) { 264 unsigned size = TD.getTypeSize (CV->getType ()); 265 O << "\t.skip\t " << size << "\n"; 266 return; 267 } else if (isa<ConstantAggregateZero> (CV)) { 268 unsigned size = TD.getTypeSize (CV->getType ()); 269 for (unsigned i = 0; i < size; ++i) 270 O << "\t.byte 0\n"; 271 return; 272 } 273 274 const Type *type = CV->getType(); 275 O << "\t"; 276 switch (type->getTypeID()) { 277 case Type::BoolTyID: case Type::UByteTyID: case Type::SByteTyID: 278 O << ".byte"; 279 break; 280 case Type::UShortTyID: case Type::ShortTyID: 281 O << ".half"; 282 break; 283 case Type::FloatTyID: case Type::PointerTyID: 284 case Type::UIntTyID: case Type::IntTyID: 285 O << ".word"; 286 break; 287 case Type::DoubleTyID: 288 case Type::ULongTyID: case Type::LongTyID: 289 O << ".xword"; 290 break; 291 default: 292 assert (0 && "Can't handle printing this type of thing"); 293 break; 294 } 295 O << "\t"; 296 emitConstantValueOnly(CV); 297 O << "\n"; 298} 299 300/// printConstantPool - Print to the current output stream assembly 301/// representations of the constants in the constant pool MCP. This is 302/// used to print out constants which have been "spilled to memory" by 303/// the code generator. 304/// 305void SparcV8AsmPrinter::printConstantPool(MachineConstantPool *MCP) { 306 const std::vector<Constant*> &CP = MCP->getConstants(); 307 const TargetData &TD = TM.getTargetData(); 308 309 if (CP.empty()) return; 310 311 for (unsigned i = 0, e = CP.size(); i != e; ++i) { 312 O << "\t.section \".rodata\"\n"; 313 O << "\t.align " << (unsigned)TD.getTypeAlignment(CP[i]->getType()) 314 << "\n"; 315 O << ".CPI" << CurrentFnName << "_" << i << ":\t\t\t\t\t!" 316 << *CP[i] << "\n"; 317 emitGlobalConstant(CP[i]); 318 } 319} 320 321/// runOnMachineFunction - This uses the printMachineInstruction() 322/// method to print assembly for each instruction. 323/// 324bool SparcV8AsmPrinter::runOnMachineFunction(MachineFunction &MF) { 325 // BBNumber is used here so that a given Printer will never give two 326 // BBs the same name. (If you have a better way, please let me know!) 327 static unsigned BBNumber = 0; 328 329 O << "\n\n"; 330 // What's my mangled name? 331 CurrentFnName = Mang->getValueName(MF.getFunction()); 332 333 // Print out constants referenced by the function 334 printConstantPool(MF.getConstantPool()); 335 336 // Print out labels for the function. 337 O << "\t.text\n"; 338 O << "\t.align 16\n"; 339 O << "\t.globl\t" << CurrentFnName << "\n"; 340 O << "\t.type\t" << CurrentFnName << ", #function\n"; 341 O << CurrentFnName << ":\n"; 342 343 // Number each basic block so that we can consistently refer to them 344 // in PC-relative references. 345 NumberForBB.clear(); 346 for (MachineFunction::const_iterator I = MF.begin(), E = MF.end(); 347 I != E; ++I) { 348 NumberForBB[I->getBasicBlock()] = BBNumber++; 349 } 350 351 // Print out code for the function. 352 for (MachineFunction::const_iterator I = MF.begin(), E = MF.end(); 353 I != E; ++I) { 354 // Print a label for the basic block. 355 O << ".LBB" << Mang->getValueName(MF.getFunction ()) 356 << "_" << I->getNumber () << ":\t! " 357 << I->getBasicBlock ()->getName () << "\n"; 358 for (MachineBasicBlock::const_iterator II = I->begin(), E = I->end(); 359 II != E; ++II) { 360 // Print the assembly for the instruction. 361 printMachineInstruction(II); 362 } 363 } 364 365 // We didn't modify anything. 366 return false; 367} 368 369void SparcV8AsmPrinter::printOperand(const MachineInstr *MI, int opNum) { 370 const MachineOperand &MO = MI->getOperand (opNum); 371 const MRegisterInfo &RI = *TM.getRegisterInfo(); 372 bool CloseParen = false; 373 if (MI->getOpcode() == V8::SETHIi && !MO.isRegister() && !MO.isImmediate()) { 374 O << "%hi("; 375 CloseParen = true; 376 } else if (MI->getOpcode() ==V8::ORri &&!MO.isRegister() &&!MO.isImmediate()) 377 { 378 O << "%lo("; 379 CloseParen = true; 380 } 381 switch (MO.getType()) { 382 case MachineOperand::MO_VirtualRegister: 383 if (Value *V = MO.getVRegValueOrNull()) { 384 O << "<" << V->getName() << ">"; 385 break; 386 } 387 // FALLTHROUGH 388 case MachineOperand::MO_MachineRegister: 389 if (MRegisterInfo::isPhysicalRegister(MO.getReg())) 390 O << "%" << LowercaseString (RI.get(MO.getReg()).Name); 391 else 392 O << "%reg" << MO.getReg(); 393 break; 394 395 case MachineOperand::MO_SignExtendedImmed: 396 case MachineOperand::MO_UnextendedImmed: 397 O << (int)MO.getImmedValue(); 398 break; 399 case MachineOperand::MO_MachineBasicBlock: { 400 MachineBasicBlock *MBBOp = MO.getMachineBasicBlock(); 401 O << ".LBB" << Mang->getValueName(MBBOp->getParent()->getFunction()) 402 << "_" << MBBOp->getNumber () << "\t! " 403 << MBBOp->getBasicBlock ()->getName (); 404 return; 405 } 406 case MachineOperand::MO_PCRelativeDisp: 407 std::cerr << "Shouldn't use addPCDisp() when building SparcV8 MachineInstrs"; 408 abort (); 409 return; 410 case MachineOperand::MO_GlobalAddress: 411 O << Mang->getValueName(MO.getGlobal()); 412 break; 413 case MachineOperand::MO_ExternalSymbol: 414 O << MO.getSymbolName(); 415 break; 416 case MachineOperand::MO_ConstantPoolIndex: 417 O << ".CPI" << CurrentFnName << "_" << MO.getConstantPoolIndex(); 418 break; 419 default: 420 O << "<unknown operand type>"; abort (); break; 421 } 422 if (CloseParen) O << ")"; 423} 424 425static bool isLoadInstruction (const MachineInstr *MI) { 426 switch (MI->getOpcode ()) { 427 case V8::LDSB: 428 case V8::LDSH: 429 case V8::LDUB: 430 case V8::LDUH: 431 case V8::LD: 432 case V8::LDD: 433 case V8::LDFrr: 434 case V8::LDFri: 435 case V8::LDDFrr: 436 case V8::LDDFri: 437 return true; 438 default: 439 return false; 440 } 441} 442 443static bool isStoreInstruction (const MachineInstr *MI) { 444 switch (MI->getOpcode ()) { 445 case V8::STB: 446 case V8::STH: 447 case V8::ST: 448 case V8::STD: 449 case V8::STFrr: 450 case V8::STFri: 451 case V8::STDFrr: 452 case V8::STDFri: 453 return true; 454 default: 455 return false; 456 } 457} 458 459static bool isPseudoInstruction (const MachineInstr *MI) { 460 switch (MI->getOpcode ()) { 461 case V8::PHI: 462 case V8::ADJCALLSTACKUP: 463 case V8::ADJCALLSTACKDOWN: 464 case V8::IMPLICIT_USE: 465 case V8::IMPLICIT_DEF: 466 return true; 467 default: 468 return false; 469 } 470} 471 472/// printBaseOffsetPair - Print two consecutive operands of MI, starting at #i, 473/// which form a base + offset pair (which may have brackets around it, if 474/// brackets is true, or may be in the form base - constant, if offset is a 475/// negative constant). 476/// 477void SparcV8AsmPrinter::printBaseOffsetPair (const MachineInstr *MI, int i) { 478 O << "["; 479 printOperand (MI, i); 480 if (MI->getOperand (i + 1).isImmediate()) { 481 int Val = (int) MI->getOperand (i + 1).getImmedValue (); 482 if (Val != 0) { 483 O << ((Val >= 0) ? " + " : " - "); 484 O << ((Val >= 0) ? Val : -Val); 485 } 486 } else { 487 O << " + "; 488 printOperand (MI, i + 1); 489 } 490 O << "]"; 491} 492 493/// printMachineInstruction -- Print out a single SparcV8 LLVM instruction 494/// MI in GAS syntax to the current output stream. 495/// 496void SparcV8AsmPrinter::printMachineInstruction(const MachineInstr *MI) { 497 O << "\t"; 498 if (printInstruction(MI)) return; 499 500 unsigned Opcode = MI->getOpcode(); 501 const TargetInstrInfo &TII = *TM.getInstrInfo(); 502 const TargetInstrDescriptor &Desc = TII.get(Opcode); 503 504 // If it's a pseudo-instruction, comment it out. 505 if (isPseudoInstruction (MI)) 506 O << "! "; 507 508 O << Desc.Name << " "; 509 510 // Printing memory instructions is a special case. 511 // for loads: %dest = op %base, offset --> op [%base + offset], %dest 512 // for stores: op %base, offset, %src --> op %src, [%base + offset] 513 if (isLoadInstruction (MI)) { 514 printBaseOffsetPair (MI, 1); 515 O << ", "; 516 printOperand (MI, 0); 517 O << "\n"; 518 return; 519 } else if (isStoreInstruction (MI)) { 520 printOperand (MI, 2); 521 O << ", "; 522 printBaseOffsetPair (MI, 0); 523 O << "\n"; 524 return; 525 } 526 527 // print non-immediate, non-register-def operands 528 // then print immediate operands 529 // then print register-def operands. 530 std::vector<int> print_order; 531 for (unsigned i = 0; i < MI->getNumOperands (); ++i) 532 if (!(MI->getOperand (i).isImmediate () 533 || (MI->getOperand (i).isRegister () 534 && MI->getOperand (i).isDef ()))) 535 print_order.push_back (i); 536 for (unsigned i = 0; i < MI->getNumOperands (); ++i) 537 if (MI->getOperand (i).isImmediate ()) 538 print_order.push_back (i); 539 for (unsigned i = 0; i < MI->getNumOperands (); ++i) 540 if (MI->getOperand (i).isRegister () && MI->getOperand (i).isDef ()) 541 print_order.push_back (i); 542 for (unsigned i = 0, e = print_order.size (); i != e; ++i) { 543 printOperand (MI, print_order[i]); 544 if (i != (print_order.size () - 1)) 545 O << ", "; 546 } 547 O << "\n"; 548} 549 550bool SparcV8AsmPrinter::doInitialization(Module &M) { 551 Mang = new Mangler(M); 552 return false; // success 553} 554 555// SwitchSection - Switch to the specified section of the executable if we are 556// not already in it! 557// 558static void SwitchSection(std::ostream &OS, std::string &CurSection, 559 const char *NewSection) { 560 if (CurSection != NewSection) { 561 CurSection = NewSection; 562 if (!CurSection.empty()) 563 OS << "\t.section \"" << NewSection << "\"\n"; 564 } 565} 566 567bool SparcV8AsmPrinter::doFinalization(Module &M) { 568 const TargetData &TD = TM.getTargetData(); 569 std::string CurSection; 570 571 // Print out module-level global variables here. 572 for (Module::const_global_iterator I = M.global_begin(), E = M.global_end(); I != E; ++I) 573 if (I->hasInitializer()) { // External global require no code 574 O << "\n\n"; 575 std::string name = Mang->getValueName(I); 576 Constant *C = I->getInitializer(); 577 unsigned Size = TD.getTypeSize(C->getType()); 578 unsigned Align = TD.getTypeAlignment(C->getType()); 579 580 if (C->isNullValue() && 581 (I->hasLinkOnceLinkage() || I->hasInternalLinkage() || 582 I->hasWeakLinkage() /* FIXME: Verify correct */)) { 583 SwitchSection(O, CurSection, ".data"); 584 if (I->hasInternalLinkage()) 585 O << "\t.local " << name << "\n"; 586 587 O << "\t.comm " << name << "," << TD.getTypeSize(C->getType()) 588 << "," << (unsigned)TD.getTypeAlignment(C->getType()); 589 O << "\t\t! "; 590 WriteAsOperand(O, I, true, true, &M); 591 O << "\n"; 592 } else { 593 switch (I->getLinkage()) { 594 case GlobalValue::LinkOnceLinkage: 595 case GlobalValue::WeakLinkage: // FIXME: Verify correct for weak. 596 // Nonnull linkonce -> weak 597 O << "\t.weak " << name << "\n"; 598 SwitchSection(O, CurSection, ""); 599 O << "\t.section\t\".llvm.linkonce.d." << name << "\",\"aw\",@progbits\n"; 600 break; 601 602 case GlobalValue::AppendingLinkage: 603 // FIXME: appending linkage variables should go into a section of 604 // their name or something. For now, just emit them as external. 605 case GlobalValue::ExternalLinkage: 606 // If external or appending, declare as a global symbol 607 O << "\t.globl " << name << "\n"; 608 // FALL THROUGH 609 case GlobalValue::InternalLinkage: 610 if (C->isNullValue()) 611 SwitchSection(O, CurSection, ".bss"); 612 else 613 SwitchSection(O, CurSection, ".data"); 614 break; 615 case GlobalValue::GhostLinkage: 616 std::cerr << "Should not have any unmaterialized functions!\n"; 617 abort(); 618 } 619 620 O << "\t.align " << Align << "\n"; 621 O << "\t.type " << name << ",#object\n"; 622 O << "\t.size " << name << "," << Size << "\n"; 623 O << name << ":\t\t\t\t! "; 624 WriteAsOperand(O, I, true, true, &M); 625 O << " = "; 626 WriteAsOperand(O, C, false, false, &M); 627 O << "\n"; 628 emitGlobalConstant(C); 629 } 630 } 631 632 delete Mang; 633 return false; // success 634} 635