Skip to content
This repository has been archived by the owner on Apr 23, 2021. It is now read-only.

Commit

Permalink
[llvm] Allow GlobalOp to take a region for complex initializers
Browse files Browse the repository at this point in the history
This allows GlobalOp to either take a value attribute (for simple constants) or a region that can
contain IR instructions (that must be constant-foldable) to create a ConstantExpr initializer.

Example:
  // A complex initializer is constructed with an initializer region.
  llvm.mlir.global constant @int_gep() : !llvm<"i32*"> {
    %0 = llvm.mlir.addressof @G2 : !llvm<"i32*">
    %1 = llvm.mlir.constant(2 : i32) : !llvm.i32
    %2 = llvm.getelementptr %0[%1] : (!llvm<"i32*">, !llvm.i32) -> !llvm<"i32*">
    llvm.return %2 : !llvm<"i32*">
  }
PiperOrigin-RevId: 278717836
  • Loading branch information
James Molloy authored and tensorflower-gardener committed Nov 5, 2019
1 parent b645d87 commit d4ad735
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 29 deletions.
49 changes: 42 additions & 7 deletions g3doc/Dialects/LLVM.md
Expand Up @@ -332,13 +332,40 @@ Examples:

Since MLIR allows for arbitrary operations to be present at the top level,
global variables are defined using the `llvm.mlir.global` operation. Both global
constants and variables can be defined, and the value must be initialized in
both cases. The initialization and type syntax is similar to
`llvm.mlir.constant` and may use two types: one for MLIR attribute and another
for the LLVM value. These types must be compatible. `llvm.mlir.global` must
appear at top-level of the enclosing module. It uses an @-identifier for its
value, which will be uniqued by the module with respect to other @-identifiers
in it.
constants and variables can be defined, and the value may also be initialized in
both cases.

There are two forms of initialization syntax. Simple constants that can be
represented as MLIR attributes can be given in-line:

```mlir {.mlir}
llvm.mlir.global @variable(32.0 : f32) : !llvm.float
```

This initialization and type syntax is similar to `llvm.mlir.constant` and may
use two types: one for MLIR attribute and another for the LLVM value. These
types must be compatible.

More complex constants that cannot be represented as MLIR attributes can be
given in an initializer region:

```mlir {.mlir}
// This global is initialized with the equivalent of:
// i32* getelementptr (i32* @g2, i32 2)
llvm.mlir.global constant @int_gep() : !llvm<"i32*"> {
%0 = llvm.mlir.addressof @g2 : !llvm<"i32*">
%1 = llvm.mlir.constant(2 : i32) : !llvm.i32
%2 = llvm.getelementptr %0[%1] : (!llvm<"i32*">, !llvm.i32) -> !llvm<"i32*">
// The initializer region must end with `llvm.return`.
llvm.return %2 : !llvm<"i32*">
}
```

Only one of the initializer attribute or initializer region may be provided.

`llvm.mlir.global` must appear at top-level of the enclosing module. It uses an
@-identifier for its value, which will be uniqued by the module with respect to
other @-identifiers in it.

Examples:

Expand All @@ -355,6 +382,14 @@ llvm.mlir.global @string("abc") : !llvm<"[3 x i8]">
// For strings globals, the trailing type may be omitted.
llvm.mlir.global constant @no_trailing_type("foo bar")
// A complex initializer is constructed with an initializer region.
llvm.mlir.global constant @int_gep() : !llvm<"i32*"> {
%0 = llvm.mlir.addressof @g2 : !llvm<"i32*">
%1 = llvm.mlir.constant(2 : i32) : !llvm.i32
%2 = llvm.getelementptr %0[%1] : (!llvm<"i32*">, !llvm.i32) -> !llvm<"i32*">
llvm.return %2 : !llvm<"i32*">
}
```

#### `llvm.mlir.null`
Expand Down
34 changes: 32 additions & 2 deletions include/mlir/Dialect/LLVMIR/LLVMOps.td
Expand Up @@ -495,10 +495,28 @@ def LLVM_AddressOfOp
}

def LLVM_GlobalOp
: LLVM_ZeroResultOp<"mlir.global", [Symbol]>,
: LLVM_ZeroResultOp<"mlir.global",
[IsolatedFromAbove,
SingleBlockImplicitTerminator<"ReturnOp">, Symbol]>,
Arguments<(ins TypeAttr:$type, UnitAttr:$constant, StrAttr:$sym_name,
OptionalAttr<AnyAttr>:$value,
DefaultValuedAttr<NonNegativeI32Attr, "0">:$addr_space)> {
let summary = "LLVM dialect global.";
let description = [{
Can contain an optional initializer region or attribute for simple
initializers.

Examples:
// Initialized using an attribute.
llvm.mlir.global @a("abc") : !llvm<"[3 x i8]">
// Initialized using a region.
llvm.mlir.global constant @b() : !llvm<"i32*"> {
%0 = llvm.constant(0 : i32) : !llvm.i32
%1 = llvm.inttoptr %0 : !llvm.i32 to !llvm<"i32*">
llvm.return %1 : !llvm<"i32*">
}
}];
let regions = (region AnyRegion:$initializer);

let builders = [
OpBuilder<"Builder *builder, OperationState &result, LLVMType type, "
Expand All @@ -511,10 +529,22 @@ def LLVM_GlobalOp
LLVMType getType() {
return type().cast<LLVMType>();
}
/// Return the value attribute if it exists, or a null attribute.
/// Return the initializer attribute if it exists, or a null attribute.
Attribute getValueOrNull() {
return value().getValueOr(Attribute());
}
/// Return the initializer region. This may be empty, but if it is not it
/// terminates in an `llvm.return` op with the initializer value.
Region &getInitializerRegion() {
return getOperation()->getRegion(0);
}
/// Return the initializer block. If the initializer region is empty this
/// is nullptr. If it is not nullptr, it terminates with an `llvm.return`
/// op with the initializer value.
Block *getInitializerBlock() {
return getInitializerRegion().empty() ?
nullptr : &getInitializerRegion().front();
}
}];

let printer = "printGlobalOp(p, *this);";
Expand Down
24 changes: 23 additions & 1 deletion lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
Expand Up @@ -875,6 +875,7 @@ void GlobalOp::build(Builder *builder, OperationState &result, LLVMType type,
if (value)
result.addAttribute("value", value);
result.attributes.append(attrs.begin(), attrs.end());
result.addRegion();
}

static void printGlobalOp(OpAsmPrinter &p, GlobalOp op) {
Expand All @@ -894,10 +895,14 @@ static void printGlobalOp(OpAsmPrinter &p, GlobalOp op) {
return;
p << " : ";
p.printType(op.type());

Region &initializer = op.getInitializerRegion();
if (!initializer.empty())
p.printRegion(initializer, /*printEntryBlockArgs=*/false);
}

// <operation> ::= `llvm.mlir.global` `constant`? `@` identifier
// `(` attribute? `)` attribute-list? (`:` type)?
// `(` attribute? `)` attribute-list? (`:` type)? region?
//
// The type can be omitted for string attributes, in which case it will be
// inferred from the value of the string as [strlen(value) x i8].
Expand Down Expand Up @@ -926,6 +931,7 @@ static ParseResult parseGlobalOp(OpAsmParser &parser, OperationState &result) {
if (types.size() > 1)
return parser.emitError(parser.getNameLoc(), "expected zero or one type");

Region &initRegion = *result.addRegion();
if (types.empty()) {
if (auto strAttr = value.dyn_cast_or_null<StringAttr>()) {
MLIRContext *context = parser.getBuilder().getContext();
Expand All @@ -937,6 +943,9 @@ static ParseResult parseGlobalOp(OpAsmParser &parser, OperationState &result) {
return parser.emitError(parser.getNameLoc(),
"type can only be omitted for string globals");
}
} else if (parser.parseOptionalRegion(initRegion, /*arguments=*/{},
/*argTypes=*/{})) {
return failure();
}

result.addAttribute("type", TypeAttr::get(types[0]));
Expand All @@ -959,6 +968,19 @@ static LogicalResult verify(GlobalOp op) {
"requires an i8 array type of the length equal to that of the string "
"attribute");
}

if (Block *b = op.getInitializerBlock()) {
ReturnOp ret = cast<ReturnOp>(b->getTerminator());
if (ret.operand_type_begin() == ret.operand_type_end())
return op.emitOpError("initializer region cannot return void");
if (*ret.operand_type_begin() != op.getType())
return op.emitOpError("initializer region type ")
<< *ret.operand_type_begin() << " does not match global type "
<< op.getType();

if (op.getValueOrNull())
return op.emitOpError("cannot have both initializer value and region");
}
return success();
}

Expand Down
22 changes: 14 additions & 8 deletions lib/Target/LLVMIR/ConvertFromLLVMIR.cpp
Expand Up @@ -212,8 +212,6 @@ Attribute Importer::getConstantAsAttr(llvm::Constant *value) {
if (auto *c = dyn_cast<llvm::ConstantDataArray>(value))
if (c->isString())
return b.getStringAttr(c->getAsString());
emitError(unknownLoc) << "unhandled constant type for attribute: "
<< diag(*value);
return Attribute();
}

Expand All @@ -226,17 +224,25 @@ GlobalOp Importer::processGlobal(llvm::GlobalVariable *GV) {
Attribute valueAttr;
if (GV->hasInitializer())
valueAttr = getConstantAsAttr(GV->getInitializer());
return globals[GV] = b.create<GlobalOp>(
UnknownLoc::get(context), processType(GV->getValueType()),
GV->isConstant(), GV->getName(), valueAttr);
GlobalOp op = b.create<GlobalOp>(UnknownLoc::get(context),
processType(GV->getValueType()),
GV->isConstant(), GV->getName(), valueAttr);
if (GV->hasInitializer() && !valueAttr) {
Region &r = op.getInitializerRegion();
currentEntryBlock = b.createBlock(&r);
b.setInsertionPoint(currentEntryBlock, currentEntryBlock->begin());
Value *v = processConstant(GV->getInitializer());
b.create<ReturnOp>(op.getLoc(), ArrayRef<Value *>({v}));
}
return globals[GV] = op;
}

Value *Importer::processConstant(llvm::Constant *c) {
if (isa<llvm::ConstantInt>(c) || isa<llvm::ConstantDataArray>(c)) {
if (Attribute attr = getConstantAsAttr(c)) {
// These constants can be represented as attributes.
OpBuilder b(currentEntryBlock, currentEntryBlock->begin());
return instMap[c] = b.create<ConstantOp>(
unknownLoc, processType(c->getType()), getConstantAsAttr(c));
return instMap[c] = b.create<ConstantOp>(unknownLoc,
processType(c->getType()), attr);
}
if (auto *cn = dyn_cast<llvm::ConstantPointerNull>(c)) {
OpBuilder b(currentEntryBlock, currentEntryBlock->begin());
Expand Down
34 changes: 23 additions & 11 deletions lib/Target/LLVMIR/ModuleTranslation.cpp
Expand Up @@ -283,17 +283,29 @@ LogicalResult ModuleTranslation::convertBlock(Block &bb, bool ignoreArguments) {
// definitions.
void ModuleTranslation::convertGlobals() {
for (auto op : mlirModule.getOps<LLVM::GlobalOp>()) {
llvm::Constant *cst;
llvm::Type *type;
// String attributes are treated separately because they cannot appear as
// in-function constants and are thus not supported by getLLVMConstant.
if (auto strAttr = op.getValueOrNull().dyn_cast_or_null<StringAttr>()) {
cst = llvm::ConstantDataArray::getString(
llvmModule->getContext(), strAttr.getValue(), /*AddNull=*/false);
type = cst->getType();
} else {
type = op.getType().getUnderlyingType();
cst = getLLVMConstant(type, op.getValueOrNull(), op.getLoc());
llvm::Type *type = op.getType().getUnderlyingType();
llvm::Constant *cst = llvm::UndefValue::get(type);
if (op.getValueOrNull()) {
// String attributes are treated separately because they cannot appear as
// in-function constants and are thus not supported by getLLVMConstant.
if (auto strAttr = op.getValueOrNull().dyn_cast_or_null<StringAttr>()) {
cst = llvm::ConstantDataArray::getString(
llvmModule->getContext(), strAttr.getValue(), /*AddNull=*/false);
type = cst->getType();
} else {
cst = getLLVMConstant(type, op.getValueOrNull(), op.getLoc());
}
} else if (Block *initializer = op.getInitializerBlock()) {
llvm::IRBuilder<> builder(llvmModule->getContext());
for (auto &op : initializer->without_terminator()) {
if (failed(convertOperation(op, builder)) ||
!isa<llvm::Constant>(valueMapping.lookup(op.getResult(0)))) {
emitError(op.getLoc(), "unemittable constant value");
return;
}
}
ReturnOp ret = cast<ReturnOp>(initializer->getTerminator());
cst = cast<llvm::Constant>(valueMapping.lookup(ret.getOperand(0)));
}

auto addrSpace = op.addr_space().getLimitedValue();
Expand Down
33 changes: 33 additions & 0 deletions test/Dialect/LLVMIR/global.mlir
Expand Up @@ -15,6 +15,15 @@ llvm.mlir.global @string_notype("1234567")
// CHECK: llvm.mlir.global @global_undef()
llvm.mlir.global @global_undef() : !llvm.i64

// CHECK: llvm.mlir.global @global_mega_initializer() : !llvm.i64 {
// CHECK-NEXT: %[[c:[0-9]+]] = llvm.mlir.constant(42 : i64) : !llvm.i64
// CHECK-NEXT: llvm.return %[[c]] : !llvm.i64
// CHECK-NEXT: }
llvm.mlir.global @global_mega_initializer() : !llvm.i64 {
%c = llvm.mlir.constant(42 : i64) : !llvm.i64
llvm.return %c : !llvm.i64
}

// CHECK-LABEL: references
func @references() {
// CHECK: llvm.mlir.addressof @global : !llvm<"i64*">
Expand Down Expand Up @@ -106,3 +115,27 @@ func @bar() {
// expected-error @+1 {{the type must be a pointer to the type of the referred global}}
llvm.mlir.addressof @foo : !llvm<"i64*">
}

// -----

// expected-error @+2 {{'llvm.mlir.global' op expects regions to end with 'llvm.return', found 'llvm.mlir.constant'}}
// expected-note @+1 {{in custom textual format, the absence of terminator implies 'llvm.return'}}
llvm.mlir.global @g() : !llvm.i64 {
%c = llvm.mlir.constant(42 : i64) : !llvm.i64
}

// -----

// expected-error @+1 {{'llvm.mlir.global' op initializer region type '!llvm.i64' does not match global type '!llvm.i32'}}
llvm.mlir.global @g() : !llvm.i32 {
%c = llvm.mlir.constant(42 : i64) : !llvm.i64
llvm.return %c : !llvm.i64
}

// -----

// expected-error @+1 {{'llvm.mlir.global' op cannot have both initializer value and region}}
llvm.mlir.global @g(43 : i64) : !llvm.i64 {
%c = llvm.mlir.constant(42 : i64) : !llvm.i64
llvm.return %c : !llvm.i64
}
9 changes: 9 additions & 0 deletions test/Target/import.ll
Expand Up @@ -13,6 +13,15 @@
; CHECK: llvm.mlir.global @g5() : !llvm<"<8 x i32>">
@g5 = external global <8 x i32>

@g4 = external global i32, align 8
; CHECK: llvm.mlir.global constant @int_gep() : !llvm<"i32*"> {
; CHECK-DAG: %[[addr:[0-9]+]] = llvm.mlir.addressof @g4 : !llvm<"i32*">
; CHECK-DAG: %[[c2:[0-9]+]] = llvm.mlir.constant(2 : i32) : !llvm.i32
; CHECK-NEXT: %[[gepinit:[0-9]+]] = llvm.getelementptr %[[addr]][%[[c2]]] : (!llvm<"i32*">, !llvm.i32) -> !llvm<"i32*">
; CHECK-NEXT: llvm.return %[[gepinit]] : !llvm<"i32*">
; CHECK-NEXT: }
@int_gep = internal constant i32* getelementptr (i32, i32* @g4, i32 2)

; CHECK: llvm.func @fe(!llvm.i32) -> !llvm.float
declare float @fe(i32)

Expand Down
8 changes: 8 additions & 0 deletions test/Target/llvmir.mlir
Expand Up @@ -24,6 +24,14 @@ llvm.mlir.global constant @string_const("foobar") : !llvm<"[6 x i8]">
// CHECK: @int_global_undef = internal global i64 undef
llvm.mlir.global @int_global_undef() : !llvm.i64

// CHECK: @int_gep = internal constant i32* getelementptr (i32, i32* @i32_global, i32 2)
llvm.mlir.global constant @int_gep() : !llvm<"i32*"> {
%addr = llvm.mlir.addressof @i32_global : !llvm<"i32*">
%_c0 = llvm.mlir.constant(2: i32) :!llvm.i32
%gepinit = llvm.getelementptr %addr[%_c0] : (!llvm<"i32*">, !llvm.i32) -> !llvm<"i32*">
llvm.return %gepinit : !llvm<"i32*">
}

//
// Declarations of the allocation functions to be linked against.
//
Expand Down

0 comments on commit d4ad735

Please sign in to comment.