mirror of
https://github.com/ldc-developers/ldc.git
synced 2025-05-07 11:26:02 +03:00
265 lines
7 KiB
C++
265 lines
7 KiB
C++
//===-- 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<size_t>(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<bool, 8> _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<LLValue *, 8> outputLVals;
|
||
LLSmallVector<LLType *, 8> outputTypes;
|
||
LLSmallVector<LLType *, 8> indirectTypes;
|
||
LLSmallVector<LLValue *, 8> 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]);
|
||
}
|
||
}
|
||
}
|