Skip to content

Commit 0c2545b

Browse files
gitoleglanza
authored andcommitted
[CIR][CodeGen] Inline assembly: store the results (llvm#512)
This PR adds storing of the results of inline assembly operation. This is a **final** step (I hope: ) ) from my side to support inline assembly. There are some features that remains unimplemented, but basic things should work now, For example, we can do addition and get the results - I explicitly added several tests for that, so you can test them in real. For instance, the next program being compiled with CIR should give you 7 as the result: ``` int add(int x, int y) { int a; __asm__("addl %[y], %[x]" : "=r" (a) : [x] "r" (x), [y] "r" (y) ); return a; } int main() { printf("run %d\n", add(3, 4)); return 0; } ``` So, the main thing remains is pretty printing. As I said I added several examples, and may be it will become more clear how to print better. Also, I added several tests from original codegen in order to check that we don't fail. And I can add some checks there as well when we come to better solution on printing.
1 parent ab16420 commit 0c2545b

File tree

8 files changed

+796
-82
lines changed

8 files changed

+796
-82
lines changed

clang/include/clang/CIR/Dialect/IR/CIROps.td

+44-19
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ include "mlir/Interfaces/SideEffectInterfaces.td"
3131
include "mlir/IR/BuiltinAttributeInterfaces.td"
3232
include "mlir/IR/EnumAttr.td"
3333
include "mlir/IR/SymbolInterfaces.td"
34+
include "mlir/IR/CommonAttrConstraints.td"
3435

3536
//===----------------------------------------------------------------------===//
3637
// CIR Ops
@@ -3329,9 +3330,13 @@ def CIR_InlineAsmOp : CIR_Op<"asm", [RecursiveMemoryEffects]> {
33293330
- the output variable index referenced by the input operands.
33303331
- the index of early-clobber operand
33313332

3332-
Operand attributes is a storage of attributes, where each element corresponds
3333-
to the operand with the same index. The first index relates to the operation
3334-
result.
3333+
Operand attributes is a storage, where each element corresponds to the operand with
3334+
the same index. The first index relates to the operation result (if any).
3335+
Note, the operands themselves are stored as VariadicOfVariadic in the next order:
3336+
output, input and then in/out operands.
3337+
3338+
Note, when several output operands are present, the result type may be represented as
3339+
an anon struct type.
33353340

33363341
Example:
33373342
```C++
@@ -3341,38 +3346,58 @@ def CIR_InlineAsmOp : CIR_Op<"asm", [RecursiveMemoryEffects]> {
33413346
```
33423347

33433348
```mlir
3349+
!ty_22anon2E022 = !cir.struct<struct "anon.0" {!cir.int<s, 32>, !cir.int<s, 32>}>
3350+
!ty_22anon2E122 = !cir.struct<struct "anon.1" {!cir.int<s, 32>, !cir.int<s, 32>}>
3351+
...
33443352
%0 = cir.alloca !s32i, cir.ptr <!s32i>, ["x", init]
33453353
%1 = cir.alloca !s32i, cir.ptr <!s32i>, ["y", init]
33463354
...
33473355
%2 = cir.load %0 : cir.ptr <!s32i>, !s32i
33483356
%3 = cir.load %1 : cir.ptr <!s32i>, !s32i
3349-
cir.asm(x86_att, {"foo" "~{dirflag},~{fpsr},~{flags}"}) side_effects : () -> ()
3350-
cir.asm(x86_att, {"bar $$42 $0" "=r,=&r,1,~{dirflag},~{fpsr},~{flags}"}) %2 : (!s32i) -> ()
3351-
cir.asm(x86_att, {"baz $$42 $0" "=r,=&r,0,1,~{dirflag},~{fpsr},~{flags}"}) %3, %2 : (!s32i, !s32i) -> ()
3357+
3358+
cir.asm(x86_att,
3359+
out = [],
3360+
in = [],
3361+
in_out = [],
3362+
{"foo" "~{dirflag},~{fpsr},~{flags}"}) side_effects
3363+
3364+
cir.asm(x86_att,
3365+
out = [],
3366+
in = [],
3367+
in_out = [%2 : !s32i],
3368+
{"bar $$42 $0" "=r,=&r,1,~{dirflag},~{fpsr},~{flags}"}) -> !ty_22anon2E022
3369+
3370+
cir.asm(x86_att,
3371+
out = [],
3372+
in = [%3 : !s32i],
3373+
in_out = [%2 : !s32i],
3374+
{"baz $$42 $0" "=r,=&r,0,1,~{dirflag},~{fpsr},~{flags}"}) -> !ty_22anon2E122
33523375
```
33533376
}];
33543377

33553378
let results = (outs Optional<CIR_AnyType>:$res);
33563379

33573380
let arguments = (
3358-
ins Variadic<AnyType>:$operands,
3381+
ins VariadicOfVariadic<AnyType, "operands_segments">:$operands,
33593382
StrAttr:$asm_string,
33603383
StrAttr:$constraints,
33613384
UnitAttr:$side_effects,
33623385
AsmFlavor:$asm_flavor,
3363-
OptionalAttr<ArrayAttr>:$operand_attrs);
3364-
3365-
let assemblyFormat = [{
3366-
`(`
3367-
$asm_flavor`,`
3368-
`{` $asm_string $constraints `}`
3369-
`)`
3370-
(`operand_attrs` `=` $operand_attrs^)?
3371-
(`side_effects` $side_effects^)?
3372-
attr-dict
3373-
operands `:` functional-type(operands, results)
3374-
}];
3386+
ArrayAttr:$operand_attrs,
3387+
DenseI32ArrayAttr:$operands_segments
3388+
);
33753389

3390+
let builders = [OpBuilder<(ins
3391+
"ArrayRef<ValueRange>":$operands,
3392+
"StringRef":$asm_string,
3393+
"StringRef":$constraints,
3394+
"bool":$side_effects,
3395+
"AsmFlavor":$asm_flavor,
3396+
"ArrayRef<Attribute>":$operand_attrs
3397+
)>
3398+
];
3399+
3400+
let hasCustomAssemblyFormat = 1;
33763401
}
33773402

33783403
//===----------------------------------------------------------------------===//

clang/lib/CIR/CodeGen/CIRAsm.cpp

+163-15
Original file line numberDiff line numberDiff line change
@@ -251,15 +251,96 @@ CIRGenFunction::buildAsmInput(const TargetInfo::ConstraintInfo &Info,
251251

252252
if (Info.allowsRegister() || !Info.allowsMemory())
253253
if (CIRGenFunction::hasScalarEvaluationKind(InputExpr->getType()))
254-
return {buildScalarExpr(InputExpr), nullptr};
254+
return {buildScalarExpr(InputExpr), mlir::Type()};
255255
if (InputExpr->getStmtClass() == Expr::CXXThisExprClass)
256-
return {buildScalarExpr(InputExpr), nullptr};
256+
return {buildScalarExpr(InputExpr), mlir::Type()};
257257
InputExpr = InputExpr->IgnoreParenNoopCasts(getContext());
258258
LValue Dest = buildLValue(InputExpr);
259259
return buildAsmInputLValue(Info, Dest, InputExpr->getType(), ConstraintStr,
260260
InputExpr->getExprLoc());
261261
}
262262

263+
static void buildAsmStores(CIRGenFunction &CGF, const AsmStmt &S,
264+
const llvm::ArrayRef<mlir::Value> RegResults,
265+
const llvm::ArrayRef<mlir::Type> ResultRegTypes,
266+
const llvm::ArrayRef<mlir::Type> ResultTruncRegTypes,
267+
const llvm::ArrayRef<LValue> ResultRegDests,
268+
const llvm::ArrayRef<QualType> ResultRegQualTys,
269+
const llvm::BitVector &ResultTypeRequiresCast,
270+
const llvm::BitVector &ResultRegIsFlagReg) {
271+
CIRGenBuilderTy &Builder = CGF.getBuilder();
272+
CIRGenModule &CGM = CGF.CGM;
273+
auto CTX = Builder.getContext();
274+
275+
assert(RegResults.size() == ResultRegTypes.size());
276+
assert(RegResults.size() == ResultTruncRegTypes.size());
277+
assert(RegResults.size() == ResultRegDests.size());
278+
// ResultRegDests can be also populated by addReturnRegisterOutputs() above,
279+
// in which case its size may grow.
280+
assert(ResultTypeRequiresCast.size() <= ResultRegDests.size());
281+
assert(ResultRegIsFlagReg.size() <= ResultRegDests.size());
282+
283+
for (unsigned i = 0, e = RegResults.size(); i != e; ++i) {
284+
mlir::Value Tmp = RegResults[i];
285+
mlir::Type TruncTy = ResultTruncRegTypes[i];
286+
287+
if ((i < ResultRegIsFlagReg.size()) && ResultRegIsFlagReg[i]) {
288+
assert(!UnimplementedFeature::asm_llvm_assume());
289+
}
290+
291+
// If the result type of the LLVM IR asm doesn't match the result type of
292+
// the expression, do the conversion.
293+
if (ResultRegTypes[i] != TruncTy) {
294+
295+
// Truncate the integer result to the right size, note that TruncTy can be
296+
// a pointer.
297+
if (TruncTy.isa<mlir::FloatType>())
298+
Tmp = Builder.createFloatingCast(Tmp, TruncTy);
299+
else if (isa<mlir::cir::PointerType>(TruncTy) &&
300+
isa<mlir::cir::IntType>(Tmp.getType())) {
301+
uint64_t ResSize = CGM.getDataLayout().getTypeSizeInBits(TruncTy);
302+
Tmp = Builder.createIntCast(
303+
Tmp, mlir::cir::IntType::get(CTX, (unsigned)ResSize, false));
304+
Tmp = Builder.createIntToPtr(Tmp, TruncTy);
305+
} else if (isa<mlir::cir::PointerType>(Tmp.getType()) &&
306+
isa<mlir::cir::IntType>(TruncTy)) {
307+
uint64_t TmpSize = CGM.getDataLayout().getTypeSizeInBits(Tmp.getType());
308+
Tmp = Builder.createPtrToInt(
309+
Tmp, mlir::cir::IntType::get(CTX, (unsigned)TmpSize, false));
310+
Tmp = Builder.createIntCast(Tmp, TruncTy);
311+
} else if (isa<mlir::cir::IntType>(TruncTy)) {
312+
Tmp = Builder.createIntCast(Tmp, TruncTy);
313+
} else if (false /*TruncTy->isVectorTy()*/) {
314+
assert(!UnimplementedFeature::asm_vector_type());
315+
}
316+
}
317+
318+
LValue Dest = ResultRegDests[i];
319+
// ResultTypeRequiresCast elements correspond to the first
320+
// ResultTypeRequiresCast.size() elements of RegResults.
321+
if ((i < ResultTypeRequiresCast.size()) && ResultTypeRequiresCast[i]) {
322+
unsigned Size = CGF.getContext().getTypeSize(ResultRegQualTys[i]);
323+
Address A = Dest.getAddress().withElementType(ResultRegTypes[i]);
324+
if (CGF.getTargetHooks().isScalarizableAsmOperand(CGF, TruncTy)) {
325+
Builder.createStore(CGF.getLoc(S.getAsmLoc()), Tmp, A);
326+
continue;
327+
}
328+
329+
QualType Ty =
330+
CGF.getContext().getIntTypeForBitwidth(Size, /*Signed=*/false);
331+
if (Ty.isNull()) {
332+
const Expr *OutExpr = S.getOutputExpr(i);
333+
CGM.getDiags().Report(OutExpr->getExprLoc(),
334+
diag::err_store_value_to_reg);
335+
return;
336+
}
337+
Dest = CGF.makeAddrLValue(A, Ty);
338+
}
339+
340+
CGF.buildStoreThroughLValue(RValue::get(Tmp), Dest);
341+
}
342+
}
343+
263344
mlir::LogicalResult CIRGenFunction::buildAsmStmt(const AsmStmt &S) {
264345
// Assemble the final asm string.
265346
std::string AsmString = S.generateAsmString(getContext());
@@ -277,19 +358,24 @@ mlir::LogicalResult CIRGenFunction::buildAsmStmt(const AsmStmt &S) {
277358
std::vector<mlir::Type> ResultTruncRegTypes;
278359
std::vector<mlir::Type> ArgTypes;
279360
std::vector<mlir::Type> ArgElemTypes;
361+
std::vector<mlir::Value> OutArgs;
362+
std::vector<mlir::Value> InArgs;
363+
std::vector<mlir::Value> InOutArgs;
280364
std::vector<mlir::Value> Args;
281365
llvm::BitVector ResultTypeRequiresCast;
282366
llvm::BitVector ResultRegIsFlagReg;
283367

284368
// Keep track of input constraints.
285369
std::string InOutConstraints;
286-
std::vector<mlir::Value> InOutArgs;
287370
std::vector<mlir::Type> InOutArgTypes;
288371
std::vector<mlir::Type> InOutArgElemTypes;
289372

290373
// Keep track of out constraints for tied input operand.
291374
std::vector<std::string> OutputConstraints;
292375

376+
// Keep track of defined physregs.
377+
llvm::SmallSet<std::string, 8> PhysRegOutputs;
378+
293379
// An inline asm can be marked readonly if it meets the following conditions:
294380
// - it doesn't have any sideeffects
295381
// - it doesn't clobber memory
@@ -314,6 +400,10 @@ mlir::LogicalResult CIRGenFunction::buildAsmStmt(const AsmStmt &S) {
314400
AddVariableConstraints(OutputConstraint, *OutExpr, getTarget(), CGM, S,
315401
Info.earlyClobber(), &GCCReg);
316402

403+
// Give an error on multiple outputs to same physreg.
404+
if (!GCCReg.empty() && !PhysRegOutputs.insert(GCCReg).second)
405+
CGM.Error(S.getAsmLoc(), "multiple outputs to hard register: " + GCCReg);
406+
317407
OutputConstraints.push_back(OutputConstraint);
318408
LValue Dest = buildLValue(OutExpr);
319409

@@ -392,6 +482,7 @@ mlir::LogicalResult CIRGenFunction::buildAsmStmt(const AsmStmt &S) {
392482

393483
ArgTypes.push_back(DestAddr.getType());
394484
ArgElemTypes.push_back(DestAddr.getElementType());
485+
OutArgs.push_back(DestAddr.getPointer());
395486
Args.push_back(DestAddr.getPointer());
396487
Constraints += "=*";
397488
Constraints += OutputConstraint;
@@ -412,6 +503,9 @@ mlir::LogicalResult CIRGenFunction::buildAsmStmt(const AsmStmt &S) {
412503
*this, OutputConstraint, Arg.getType()))
413504
Arg = builder.createBitcast(Arg, AdjTy);
414505

506+
// Update largest vector width for any vector types.
507+
assert(!UnimplementedFeature::asm_vector_type());
508+
415509
// Only tie earlyclobber physregs.
416510
if (Info.allowsRegister() && (GCCReg.empty() || Info.earlyClobber()))
417511
InOutConstraints += llvm::utostr(i);
@@ -424,11 +518,28 @@ mlir::LogicalResult CIRGenFunction::buildAsmStmt(const AsmStmt &S) {
424518
}
425519
} // iterate over output operands
426520

521+
// If this is a Microsoft-style asm blob, store the return registers (EAX:EDX)
522+
// to the return value slot. Only do this when returning in registers.
523+
if (isa<MSAsmStmt>(&S)) {
524+
const ABIArgInfo &RetAI = CurFnInfo->getReturnInfo();
525+
if (RetAI.isDirect() || RetAI.isExtend()) {
526+
// Make a fake lvalue for the return value slot.
527+
LValue ReturnSlot = makeAddrLValue(ReturnValue, FnRetTy);
528+
CGM.getTargetCIRGenInfo().addReturnRegisterOutputs(
529+
*this, ReturnSlot, Constraints, ResultRegTypes, ResultTruncRegTypes,
530+
ResultRegDests, AsmString, S.getNumOutputs());
531+
SawAsmBlock = true;
532+
}
533+
}
534+
427535
for (unsigned i = 0, e = S.getNumInputs(); i != e; i++) {
428536
const Expr *InputExpr = S.getInputExpr(i);
429537

430538
TargetInfo::ConstraintInfo &Info = InputConstraintInfos[i];
431539

540+
if (Info.allowsMemory())
541+
ReadNone = false;
542+
432543
if (!Constraints.empty())
433544
Constraints += ',';
434545

@@ -481,17 +592,21 @@ mlir::LogicalResult CIRGenFunction::buildAsmStmt(const AsmStmt &S) {
481592
CGM.getDiags().Report(S.getAsmLoc(), diag::err_asm_invalid_type_in_input)
482593
<< InputExpr->getType() << InputConstraint;
483594

595+
// Update largest vector width for any vector types.
596+
assert(!UnimplementedFeature::asm_vector_type());
597+
484598
ArgTypes.push_back(Arg.getType());
485599
ArgElemTypes.push_back(ArgElemType);
600+
InArgs.push_back(Arg);
486601
Args.push_back(Arg);
487602
Constraints += InputConstraint;
488603
} // iterate over input operands
489604

490605
// Append the "input" part of inout constraints.
491606
for (unsigned i = 0, e = InOutArgs.size(); i != e; i++) {
607+
Args.push_back(InOutArgs[i]);
492608
ArgTypes.push_back(InOutArgTypes[i]);
493609
ArgElemTypes.push_back(InOutArgElemTypes[i]);
494-
Args.push_back(InOutArgs[i]);
495610
}
496611
Constraints += InOutConstraints;
497612

@@ -509,9 +624,15 @@ mlir::LogicalResult CIRGenFunction::buildAsmStmt(const AsmStmt &S) {
509624
}
510625

511626
bool HasSideEffect = S.isVolatile() || S.getNumOutputs() == 0;
627+
std::vector<mlir::Value> RegResults;
628+
629+
llvm::SmallVector<mlir::ValueRange, 8> operands;
630+
operands.push_back(OutArgs);
631+
operands.push_back(InArgs);
632+
operands.push_back(InOutArgs);
512633

513634
auto IA = builder.create<mlir::cir::InlineAsmOp>(
514-
getLoc(S.getAsmLoc()), ResultType, Args, AsmString, Constraints,
635+
getLoc(S.getAsmLoc()), ResultType, operands, AsmString, Constraints,
515636
HasSideEffect, inferFlavor(CGM, S), mlir::ArrayAttr());
516637

517638
if (false /*IsGCCAsmGoto*/) {
@@ -525,28 +646,55 @@ mlir::LogicalResult CIRGenFunction::buildAsmStmt(const AsmStmt &S) {
525646
if (IA.getNumResults())
526647
result = IA.getResult(0);
527648

528-
std::vector<mlir::Attribute> operandAttrs;
529-
530-
// this is for the lowering to LLVM from LLVm dialect. Otherwise, if we
531-
// don't have the result (i.e. void type as a result of operation), the
532-
// element type attribute will be attached to the whole instruction, but not
533-
// to the operand
534-
if (!IA.getNumResults())
535-
operandAttrs.push_back(OptNoneAttr::get(builder.getContext()));
649+
llvm::SmallVector<mlir::Attribute> operandAttrs;
536650

651+
int i = 0;
537652
for (auto typ : ArgElemTypes) {
538653
if (typ) {
539-
operandAttrs.push_back(mlir::TypeAttr::get(typ));
654+
auto op = Args[i++];
655+
assert(op.getType().isa<mlir::cir::PointerType>() &&
656+
"pointer type expected");
657+
assert(cast<mlir::cir::PointerType>(op.getType()).getPointee() == typ &&
658+
"element type differs from pointee type!");
659+
660+
operandAttrs.push_back(mlir::UnitAttr::get(builder.getContext()));
540661
} else {
541662
// We need to add an attribute for every arg since later, during
542663
// the lowering to LLVM IR the attributes will be assigned to the
543664
// CallInsn argument by index, i.e. we can't skip null type here
544-
operandAttrs.push_back(OptNoneAttr::get(builder.getContext()));
665+
operandAttrs.push_back(mlir::Attribute());
545666
}
546667
}
547668

669+
assert(Args.size() == operandAttrs.size() &&
670+
"The number of attributes is not even with the number of operands");
671+
548672
IA.setOperandAttrsAttr(builder.getArrayAttr(operandAttrs));
673+
674+
if (ResultRegTypes.size() == 1) {
675+
RegResults.push_back(result);
676+
} else if (ResultRegTypes.size() > 1) {
677+
auto alignment = CharUnits::One();
678+
auto sname = cast<mlir::cir::StructType>(ResultType).getName();
679+
auto dest = buildAlloca(sname, ResultType, getLoc(S.getAsmLoc()),
680+
alignment, false);
681+
auto addr = Address(dest, alignment);
682+
builder.createStore(getLoc(S.getAsmLoc()), result, addr);
683+
684+
for (unsigned i = 0, e = ResultRegTypes.size(); i != e; ++i) {
685+
auto typ = builder.getPointerTo(ResultRegTypes[i]);
686+
auto ptr =
687+
builder.createGetMember(getLoc(S.getAsmLoc()), typ, dest, "", i);
688+
auto tmp =
689+
builder.createLoad(getLoc(S.getAsmLoc()), Address(ptr, alignment));
690+
RegResults.push_back(tmp);
691+
}
692+
}
549693
}
550694

695+
buildAsmStores(*this, S, RegResults, ResultRegTypes, ResultTruncRegTypes,
696+
ResultRegDests, ResultRegQualTys, ResultTypeRequiresCast,
697+
ResultRegIsFlagReg);
698+
551699
return mlir::success();
552700
}

0 commit comments

Comments
 (0)