//===-- asm-gcc.cpp -------------------------------------------------------===// // // LDC – the LLVM D compiler // // Converts a GDC/GCC-style inline assembly statement to an LLVM inline // assembler expression. // //===----------------------------------------------------------------------===// #include "dmd/errors.h" #include "dmd/expression.h" #include "dmd/statement.h" #include "gen/irstate.h" #include "gen/llvm.h" #include "gen/llvmhelpers.h" #include "gen/logger.h" namespace { llvm::StringRef peekString(StringExp *se) { DString slice = se->peekString(); return {slice.ptr, slice.length}; } // Translates a GCC inline asm template string to LLVM's expected format. std::string translateTemplate(GccAsmStatement *stmt) { const auto insn = peekString(stmt->insn->isStringExp()); const auto N = insn.size(); std::string result; result.reserve(static_cast(N * 1.2)); for (size_t i = 0; i < N; ++i) { const char c = insn[i]; switch (c) { case '$': result += "$$"; // escape for LLVM: $ => $$ break; case '%': if (i < N - 1 && insn[i + 1] == '%') { // unescape for LLVM: %% => % result += '%'; ++i; } else { result += '$'; // e.g., %0 => $0 } break; default: result += c; break; } } return result; } class ConstraintsBuilder { bool isAnyX86; std::ostringstream str; // LLVM constraints string being built LLSmallVector _isIndirectOperand; // Appends a constraint string expression with an optional prefix. // Returns true if the string describes an indirect operand. bool append(Expression *e, char prefix = 0) { #if LDC_LLVM_VER >= 1800 #define startswith starts_with #endif auto se = e->isStringExp(); assert(se); llvm::StringRef code = peekString(se); assert(!code.empty()); // commit prefix and strip from `code`, if present if (prefix) { str << prefix; if (code[0] == prefix) code = code.substr(1); } // commit any modifier and strip from `code` bool isIndirect = false; if (code.startswith("&")) { // early clobber str << '&'; code = code.substr(1); } else if (code.startswith("*")) { // indirect in/output isIndirect = true; // delay the commit code = code.substr(1); } const std::string name = translateName(code, isIndirect); if (isIndirect) str << '*'; str << name; str << ','; return isIndirect; #if LDC_LLVM_VER >= 1800 #undef startswith #endif } // Might set `isIndirect` to true (but never resets to false). std::string translateName(llvm::StringRef gccName, bool &isIndirect) const { // clang translates GCC `m` to LLVM `*m` (indirect operand) if (gccName == "m") { isIndirect = true; return "m"; } // some variable-width x86[_64] GCC register names aren't supported by LLVM // directly if (isAnyX86 && gccName.size() == 1) { switch (gccName[0]) { case 'a': return "{ax}"; case 'b': return "{bx}"; case 'c': return "{cx}"; case 'd': return "{dx}"; case 'S': return "{si}"; case 'D': return "{di}"; default: break; } } return needsCurlyBraces(gccName) ? ("{" + gccName + "}").str() : gccName.str(); } // Register names need to be enclosed in curly braces for LLVM. bool needsCurlyBraces(llvm::StringRef gccName) const { auto N = gccName.size(); if (N == 1 || (N == 3 && gccName[0] == '^')) return false; return !gccName.contains('{'); } public: ConstraintsBuilder() { auto arch = global.params.targetTriple->getArch(); isAnyX86 = (arch == llvm::Triple::x86 || arch == llvm::Triple::x86_64); } // Returns the final constraints string for LLVM for a GCC-style asm // statement. std::string build(GccAsmStatement *stmt) { str.clear(); _isIndirectOperand.clear(); if (auto c = stmt->constraints) { _isIndirectOperand.reserve(c->length); for (size_t i = 0; i < c->length; ++i) { bool isOutput = (i < stmt->outputargs); bool isIndirect = append((*c)[i], isOutput ? '=' : 0); _isIndirectOperand.push_back(isIndirect); } } if (stmt->clobbers) { for (auto e : *stmt->clobbers) { append(e, '~'); } } // remove excessive comma std::string result = str.str(); if (auto size = result.size()) result.resize(size - 1); return result; } bool isIndirectOperand(size_t operandIndex) const { return _isIndirectOperand[operandIndex]; } }; } void GccAsmStatement_toIR(GccAsmStatement *stmt, IRState *irs) { IF_LOG Logger::println("GccAsmStatement::toIR(): %s", stmt->loc.toChars()); LOG_SCOPE; if (stmt->labels) { error(stmt->loc, "goto labels for GCC-style asm statements are not supported yet"); fatal(); } if (stmt->names) { for (Identifier *name : *stmt->names) { if (name) { error(stmt->loc, "symbolic names for operands in GCC-style assembly are not " "supported yet"); fatal(); } } } const std::string insn = translateTemplate(stmt); ConstraintsBuilder constraintsBuilder; const std::string constraints = constraintsBuilder.build(stmt); LLSmallVector outputLVals; LLSmallVector outputTypes; LLSmallVector indirectTypes; LLSmallVector operands; if (stmt->args) { for (size_t i = 0; i < stmt->args->length; ++i) { Expression *e = (*stmt->args)[i]; const bool isOutput = (i < stmt->outputargs); const bool isIndirect = constraintsBuilder.isIndirectOperand(i); if (isOutput) { assert(e->isLvalue() && "should have been caught by front-end"); LLValue *lval = DtoLVal(e); if (isIndirect) { operands.push_back(lval); indirectTypes.push_back(DtoType(e->type)); } else { outputLVals.push_back(lval); outputTypes.push_back(DtoType(e->type)); } } else { if (isIndirect && !e->isLvalue()) { error(e->loc, "indirect `\"m\"` input operands require an lvalue, but `%s` " "is an rvalue", e->toChars()); fatal(); } LLValue *inputVal = isIndirect ? DtoLVal(e) : DtoRVal(e); operands.push_back(inputVal); if (isIndirect) indirectTypes.push_back(DtoType(e->type)); } } } const size_t N = outputTypes.size(); LLType *returnType = N == 0 ? llvm::Type::getVoidTy(irs->context()) : N == 1 ? outputTypes[0] : LLStructType::get(irs->context(), outputTypes); LLValue *rval = DtoInlineAsmExpr(stmt->loc, insn, constraints, operands, indirectTypes, returnType); if (N == 1) { DtoStore(rval, outputLVals[0]); } else { for (size_t i = 0; i < N; ++i) { auto element = DtoExtractValue(rval, i); DtoStore(element, outputLVals[i]); } } }