Skip to content

Commit feb7bea

Browse files
authored
[mlir][LLVM] Model side effects of volatile and atomic load-store (#65730)
According to the LLVM language reference, both volatile memory operations and atomic operations (except unordered) do not simply read memory but also perform write operations on arbitrary memory[0][1]. In the case of volatile memory operations, this is the case due to the read possibly having target specific properties. A common real-world situation where this happens is reading memory mapped registers on an MCU for example. Atomic operations are more special. They form a kind of memory barrier which from the perspective of the optimizer/lang-ref makes writes from other threads visible in the current thread. Any kind of synchronization can therefore conservatively be modeled as a write-effect. This PR therefore adjusts the side effects of `llvm.load` and `llvm.store` to add unknown global read and write effects if they are either atomic or volatile. Regarding testing: I am not sure how to best test this change for `llvm.store` and the "globalness" of the effect that isn't just a unit test checking that the output matches exactly. For the time being, I added a test making sure that `llvm.load` does not get DCEd in aforementioned cases. Related logic in LLVM proper: https://github.com/llvm/llvm-project/blob/3398744a6106c83993611bd3c5e79ec6b94417dc/llvm/lib/IR/Instruction.cpp#L638-L676 https://github.com/llvm/llvm-project/blob/3398744a6106c83993611bd3c5e79ec6b94417dc/llvm/include/llvm/IR/Instructions.h#L258-L262 [0] https://llvm.org/docs/LangRef.html#volatile-memory-accesses [1] https://llvm.org/docs/Atomics.html#monotonic
1 parent da1eb88 commit feb7bea

File tree

3 files changed

+55
-4
lines changed

3 files changed

+55
-4
lines changed

mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -326,9 +326,10 @@ def LLVM_GEPOp : LLVM_Op<"getelementptr", [Pure,
326326
}
327327

328328
def LLVM_LoadOp : LLVM_MemAccessOpBase<"load",
329-
[DeclareOpInterfaceMethods<PromotableMemOpInterface>,
329+
[DeclareOpInterfaceMethods<MemoryEffectsOpInterface>,
330+
DeclareOpInterfaceMethods<PromotableMemOpInterface>,
330331
DeclareOpInterfaceMethods<SafeMemorySlotAccessOpInterface>]> {
331-
dag args = (ins Arg<LLVM_PointerTo<LLVM_LoadableType>, "", [MemRead]>:$addr,
332+
dag args = (ins LLVM_PointerTo<LLVM_LoadableType>:$addr,
332333
OptionalAttr<I64Attr>:$alignment,
333334
UnitAttr:$volatile_,
334335
UnitAttr:$nontemporal,
@@ -399,10 +400,11 @@ def LLVM_LoadOp : LLVM_MemAccessOpBase<"load",
399400
}
400401

401402
def LLVM_StoreOp : LLVM_MemAccessOpBase<"store",
402-
[DeclareOpInterfaceMethods<PromotableMemOpInterface>,
403+
[DeclareOpInterfaceMethods<MemoryEffectsOpInterface>,
404+
DeclareOpInterfaceMethods<PromotableMemOpInterface>,
403405
DeclareOpInterfaceMethods<SafeMemorySlotAccessOpInterface>]> {
404406
dag args = (ins LLVM_LoadableType:$value,
405-
Arg<LLVM_PointerTo<LLVM_LoadableType>,"",[MemWrite]>:$addr,
407+
LLVM_PointerTo<LLVM_LoadableType>:$addr,
406408
OptionalAttr<I64Attr>:$alignment,
407409
UnitAttr:$volatile_,
408410
UnitAttr:$nontemporal,

mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,22 @@ Type GEPOp::getResultPtrElementType() {
812812
// LoadOp
813813
//===----------------------------------------------------------------------===//
814814

815+
void LoadOp::getEffects(
816+
SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
817+
&effects) {
818+
effects.emplace_back(MemoryEffects::Read::get(), getAddr());
819+
// Volatile operations can have target-specific read-write effects on
820+
// memory besides the one referred to by the pointer operand.
821+
// Similarly, atomic operations that are monotonic or stricter cause
822+
// synchronization that from a language point-of-view, are arbitrary
823+
// read-writes into memory.
824+
if (getVolatile_() || (getOrdering() != AtomicOrdering::not_atomic &&
825+
getOrdering() != AtomicOrdering::unordered)) {
826+
effects.emplace_back(MemoryEffects::Write::get());
827+
effects.emplace_back(MemoryEffects::Read::get());
828+
}
829+
}
830+
815831
/// Returns true if the given type is supported by atomic operations. All
816832
/// integer and float types with limited bit width are supported. Additionally,
817833
/// depending on the operation pointers may be supported as well.
@@ -932,6 +948,22 @@ static void printLoadType(OpAsmPrinter &printer, Operation *op, Type type,
932948
// StoreOp
933949
//===----------------------------------------------------------------------===//
934950

951+
void StoreOp::getEffects(
952+
SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
953+
&effects) {
954+
effects.emplace_back(MemoryEffects::Write::get(), getAddr());
955+
// Volatile operations can have target-specific read-write effects on
956+
// memory besides the one referred to by the pointer operand.
957+
// Similarly, atomic operations that are monotonic or stricter cause
958+
// synchronization that from a language point-of-view, are arbitrary
959+
// read-writes into memory.
960+
if (getVolatile_() || (getOrdering() != AtomicOrdering::not_atomic &&
961+
getOrdering() != AtomicOrdering::unordered)) {
962+
effects.emplace_back(MemoryEffects::Write::get());
963+
effects.emplace_back(MemoryEffects::Read::get());
964+
}
965+
}
966+
935967
LogicalResult StoreOp::verify() {
936968
Type valueType = getValue().getType();
937969
return verifyAtomicMemOp(*this, valueType,

mlir/test/Dialect/LLVMIR/canonicalize.mlir

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,3 +191,20 @@ llvm.func @alloca_dce() {
191191
%0 = llvm.alloca %c1_i64 x i32 : (i64) -> !llvm.ptr
192192
llvm.return
193193
}
194+
195+
// -----
196+
197+
// CHECK-LABEL: func @volatile_load
198+
llvm.func @volatile_load(%x : !llvm.ptr) {
199+
// A volatile load may have side-effects such as a write operation to arbitrary memory.
200+
// Make sure it is not removed.
201+
// CHECK: llvm.load volatile
202+
%0 = llvm.load volatile %x : !llvm.ptr -> i8
203+
// Same with monotonic atomics and any stricter modes.
204+
// CHECK: llvm.load %{{.*}} atomic monotonic
205+
%2 = llvm.load %x atomic monotonic { alignment = 1 } : !llvm.ptr -> i8
206+
// But not unordered!
207+
// CHECK-NOT: llvm.load %{{.*}} atomic unordered
208+
%3 = llvm.load %x atomic unordered { alignment = 1 } : !llvm.ptr -> i8
209+
llvm.return
210+
}

0 commit comments

Comments
 (0)