diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h --- a/clang/lib/CodeGen/CGHLSLRuntime.h +++ b/clang/lib/CodeGen/CGHLSLRuntime.h @@ -17,6 +17,7 @@ #include "llvm/IR/IRBuilder.h" +#include "clang/AST/Attr.h" #include "clang/Basic/HLSLRuntime.h" #include "llvm/ADT/SmallVector.h" @@ -83,6 +84,8 @@ void addBuffer(const HLSLBufferDecl *D); void finishCodeGen(); + void setHLSLEntryNumThreadsAttribute(const HLSLNumThreadsAttr *attribute, + llvm::Function *Fn); void setHLSLEntryAttributes(const FunctionDecl *FD, llvm::Function *Fn); void emitEntryFunction(const FunctionDecl *FD, llvm::Function *Fn); diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp --- a/clang/lib/CodeGen/CGHLSLRuntime.cpp +++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp @@ -15,6 +15,7 @@ #include "CGHLSLRuntime.h" #include "CGDebugInfo.h" #include "CodeGenModule.h" +#include "clang/AST/Attr.h" #include "clang/AST/Decl.h" #include "clang/Basic/TargetOptions.h" #include "llvm/IR/IntrinsicsDirectX.h" @@ -309,6 +310,29 @@ } } +void clang::CodeGen::CGHLSLRuntime::setHLSLEntryNumThreadsAttribute( + const HLSLNumThreadsAttr *attribute, llvm::Function *Fn) { + + llvm::Module &M = CGM.getModule(); + auto &Ctx = M.getContext(); + Triple T(M.getTargetTriple()); + + if (T.isSPIRV()) { + IRBuilder<> Builder(M.getContext()); + llvm::Metadata *AttrMDArgs[] = { + ConstantAsMetadata::get(Builder.getInt32(attribute->getX())), + ConstantAsMetadata::get(Builder.getInt32(attribute->getY())), + ConstantAsMetadata::get(Builder.getInt32(attribute->getZ()))}; + Fn->setMetadata("reqd_work_group_size", llvm::MDNode::get(Ctx, AttrMDArgs)); + return; + } + + const StringRef NumThreadsKindStr = "hlsl.numthreads"; + std::string NumThreadsStr = formatv("{0},{1},{2}", attribute->getX(), + attribute->getY(), attribute->getZ()); + Fn->addFnAttr(NumThreadsKindStr, NumThreadsStr); +} + void clang::CodeGen::CGHLSLRuntime::setHLSLEntryAttributes( const FunctionDecl *FD, llvm::Function *Fn) { const auto *ShaderAttr = FD->getAttr(); @@ -316,12 +340,9 @@ const StringRef ShaderAttrKindStr = "hlsl.shader"; Fn->addFnAttr(ShaderAttrKindStr, ShaderAttr->ConvertShaderTypeToStr(ShaderAttr->getType())); + if (HLSLNumThreadsAttr *NumThreadsAttr = FD->getAttr()) { - const StringRef NumThreadsKindStr = "hlsl.numthreads"; - std::string NumThreadsStr = - formatv("{0},{1},{2}", NumThreadsAttr->getX(), NumThreadsAttr->getY(), - NumThreadsAttr->getZ()); - Fn->addFnAttr(NumThreadsKindStr, NumThreadsStr); + setHLSLEntryNumThreadsAttribute(NumThreadsAttr, Fn); } } diff --git a/clang/test/CodeGenHLSL/entry.hlsl b/clang/test/CodeGenHLSL/entry.hlsl --- a/clang/test/CodeGenHLSL/entry.hlsl +++ b/clang/test/CodeGenHLSL/entry.hlsl @@ -1,12 +1,23 @@ // RUN: %clang_cc1 -std=hlsl2021 -finclude-default-header -x hlsl -triple \ // RUN: dxil-pc-shadermodel6.3-compute %s -hlsl-entry foo \ -// RUN: -emit-llvm -disable-llvm-passes -o - | FileCheck %s +// RUN: -emit-llvm -disable-llvm-passes -o - \ +// RUN: | FileCheck %s --check-prefixes=DXIL-CHECK + +// RUN: %clang_cc1 -std=hlsl2021 -finclude-default-header -x hlsl -triple \ +// RUN: spirv-unknown-shadermodel6.3-compute %s -hlsl-entry foo \ +// RUN: -emit-llvm -disable-llvm-passes -o - \ +// RUN: | FileCheck %s --check-prefixes=SPIRV-CHECK // Make sure not mangle entry. -// CHECK:define void @foo() +// DXIL-CHECK:define void @foo() // Make sure add function attribute and numthreads attribute. -// CHECK:"hlsl.numthreads"="16,8,1" -// CHECK:"hlsl.shader"="compute" +// DXIL-CHECK:"hlsl.numthreads"="16,8,1" +// DXIL-CHECK:"hlsl.shader"="compute" + +// Make sure not mangle entry and workgroup attribute. +// SPIRV-CHECK: define void @foo() #[[ATTR:[0-9]+]] !reqd_work_group_size ![[MD:[0-9]+]] +// SPIRV-CHECK: attributes #[[ATTR]] = { "hlsl.shader"="compute" "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +// SPIRV-CHECK: [[MD]] = !{i32 16, i32 8, i32 1} [numthreads(16,8,1)] void foo() { diff --git a/clang/test/CodeGenHLSL/shader_type_attr.hlsl b/clang/test/CodeGenHLSL/shader_type_attr.hlsl --- a/clang/test/CodeGenHLSL/shader_type_attr.hlsl +++ b/clang/test/CodeGenHLSL/shader_type_attr.hlsl @@ -1,11 +1,22 @@ // RUN: %clang_cc1 -std=hlsl2021 -finclude-default-header -x hlsl -triple \ // RUN: dxil-pc-shadermodel6.3-library %s -fnative-half-type \ -// RUN: -emit-llvm -disable-llvm-passes -o - | FileCheck %s +// RUN: -emit-llvm -disable-llvm-passes -o - \ +// RUN: | FileCheck %s --check-prefixes=DXIL-CHECK + +// RUN: %clang_cc1 -std=hlsl2021 -finclude-default-header -x hlsl -triple \ +// RUN: spirv-unknown-shadermodel6.3-compute %s \ +// RUN: -emit-llvm -disable-llvm-passes -o - \ +// RUN: | FileCheck %s --check-prefixes=SPIRV-CHECK // Make sure not mangle entry. -// CHECK:define void @foo() +// DXIL-CHECK:define void @foo() // Make sure add function attribute. -// CHECK:"hlsl.shader"="compute" +// DXIL-CHECK:"hlsl.shader"="compute" + +// Make sure not mangle entry and workgroup attribute. +// SPIRV-CHECK: define void @foo() #[[ATTR:[0-9]+]] !reqd_work_group_size !{{[0-9]+}} +// SPIRV-CHECK: attributes #[[ATTR]] = { "hlsl.shader"="compute" "no-trapping-math"="true" "stack-protector-buffer-size"="8" } + [shader("compute")] [numthreads(1,1,1)] void foo() { diff --git a/llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVMCTargetDesc.cpp b/llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVMCTargetDesc.cpp --- a/llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVMCTargetDesc.cpp +++ b/llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVMCTargetDesc.cpp @@ -88,7 +88,8 @@ } extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeSPIRVTargetMC() { - for (Target *T : {&getTheSPIRV32Target(), &getTheSPIRV64Target()}) { + for (Target *T : {&getTheSPIRV32Target(), &getTheSPIRV64Target(), + &getTheSPIRVLogicalTarget()}) { RegisterMCAsmInfo X(*T); TargetRegistry::RegisterMCInstrInfo(*T, createSPIRVMCInstrInfo); TargetRegistry::RegisterMCRegInfo(*T, createSPIRVMCRegisterInfo); diff --git a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp --- a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp +++ b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp @@ -447,7 +447,8 @@ Inst.addOperand(MCOperand::createImm(TypeCode)); outputMCInst(Inst); } - if (!M.getNamedMetadata("spirv.ExecutionMode") && + + if (ST->isOpenCLEnv() && !M.getNamedMetadata("spirv.ExecutionMode") && !M.getNamedMetadata("opencl.enable.FP_CONTRACT")) { MCInst Inst; Inst.setOpcode(SPIRV::OpExecutionMode); @@ -542,4 +543,5 @@ extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeSPIRVAsmPrinter() { RegisterAsmPrinter X(getTheSPIRV32Target()); RegisterAsmPrinter Y(getTheSPIRV64Target()); + RegisterAsmPrinter Z(getTheSPIRVLogicalTarget()); } diff --git a/llvm/lib/Target/SPIRV/SPIRVCallLowering.cpp b/llvm/lib/Target/SPIRV/SPIRVCallLowering.cpp --- a/llvm/lib/Target/SPIRV/SPIRVCallLowering.cpp +++ b/llvm/lib/Target/SPIRV/SPIRVCallLowering.cpp @@ -213,6 +213,41 @@ : StructType::create(F.getContext(), KernelArgTypeStr); } +static bool isEntrypoint(const Function &F) { + // OpenCL handling: any function with the SPIR_KERNEL + // calling convention will be a potential entrypoint. + if (F.getCallingConv() == CallingConv::SPIR_KERNEL) { + return true; + } + + // HLSL handling: special attribute are emitted from the + // front-end. + if (F.getFnAttribute("hlsl.shader").isValid()) { + return true; + } + + return false; +} + +static SPIRV::ExecutionModel::ExecutionModel +getExecutionModel(const SPIRVSubtarget &STI, const Function &F) { + if (STI.isOpenCLEnv()) { + return SPIRV::ExecutionModel::Kernel; + } + + auto attribute = F.getFnAttribute("hlsl.shader"); + if (!attribute.isValid()) { + report_fatal_error( + "This entrypoint lacks mandatory hlsl.shader attribute."); + } + + const auto value = attribute.getValueAsString(); + if (value == "compute") + return SPIRV::ExecutionModel::GLCompute; + + report_fatal_error("This HLSL entrypoint is not supported by this backend."); +} + bool SPIRVCallLowering::lowerFormalArguments(MachineIRBuilder &MIRBuilder, const Function &F, ArrayRef> VRegs, @@ -336,9 +371,11 @@ buildOpName(FuncVReg, F.getName(), MIRBuilder); // Handle entry points and function linkage. - if (F.getCallingConv() == CallingConv::SPIR_KERNEL) { + if (isEntrypoint(F)) { + const auto &STI = MIRBuilder.getMF().getSubtarget(); + auto executionModel = getExecutionModel(STI, F); auto MIB = MIRBuilder.buildInstr(SPIRV::OpEntryPoint) - .addImm(static_cast(SPIRV::ExecutionModel::Kernel)) + .addImm(static_cast(executionModel)) .addUse(FuncVReg); addStringImm(F.getName(), MIB); } else if (F.getLinkage() == GlobalValue::LinkageTypes::ExternalLinkage || diff --git a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.h b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.h --- a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.h +++ b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.h @@ -69,6 +69,9 @@ // recursing through their implicitly declared capabilities too. void pruneCapabilities(const CapabilityList &ToPrune); + void initAvailableCapabilitiesForOpenCL(const SPIRVSubtarget &ST); + void initAvailableCapabilitiesForVulkan(const SPIRVSubtarget &ST); + public: RequirementHandler() : MinVersion(0), MaxVersion(0) {} void clear() { diff --git a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp --- a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp +++ b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp @@ -106,11 +106,18 @@ MAI.Mem = static_cast(getMetadataUInt(MemMD, 1)); } else { - MAI.Mem = SPIRV::MemoryModel::OpenCL; - unsigned PtrSize = ST->getPointerSize(); - MAI.Addr = PtrSize == 32 ? SPIRV::AddressingModel::Physical32 - : PtrSize == 64 ? SPIRV::AddressingModel::Physical64 - : SPIRV::AddressingModel::Logical; + // TODO: Add support for VulkanMemoryModel. + MAI.Mem = ST->isOpenCLEnv() ? SPIRV::MemoryModel::OpenCL + : SPIRV::MemoryModel::GLSL450; + if (MAI.Mem == SPIRV::MemoryModel::OpenCL) { + unsigned PtrSize = ST->getPointerSize(); + MAI.Addr = PtrSize == 32 ? SPIRV::AddressingModel::Physical32 + : PtrSize == 64 ? SPIRV::AddressingModel::Physical64 + : SPIRV::AddressingModel::Logical; + } else { + // TODO: Add support for PhysicalStorageBufferAddress. + MAI.Addr = SPIRV::AddressingModel::Logical; + } } // Get the OpenCL version number from metadata. // TODO: support other source languages. @@ -148,9 +155,12 @@ MAI.Reqs.getAndAddRequirements(SPIRV::OperandCategory::AddressingModelOperand, MAI.Addr, *ST); - // TODO: check if it's required by default. - MAI.ExtInstSetMap[static_cast(SPIRV::InstructionSet::OpenCL_std)] = - Register::index2VirtReg(MAI.getNextID()); + if (ST->isOpenCLEnv()) { + // TODO: check if it's required by default. + MAI.ExtInstSetMap[static_cast( + SPIRV::InstructionSet::OpenCL_std)] = + Register::index2VirtReg(MAI.getNextID()); + } } // Collect MI which defines the register in the given machine function. @@ -516,9 +526,21 @@ namespace llvm { namespace SPIRV { void RequirementHandler::initAvailableCapabilities(const SPIRVSubtarget &ST) { - // TODO: Implemented for other targets other then OpenCL. - if (!ST.isOpenCLEnv()) + if (ST.isOpenCLEnv()) { + initAvailableCapabilitiesForOpenCL(ST); return; + } + + if (ST.isVulkanEnv()) { + initAvailableCapabilitiesForVulkan(ST); + return; + } + + report_fatal_error("Unimplemented environment for SPIR-V generation."); +} + +void RequirementHandler::initAvailableCapabilitiesForOpenCL( + const SPIRVSubtarget &ST) { // Add the min requirements for different OpenCL and SPIR-V versions. addAvailableCaps({Capability::Addresses, Capability::Float16Buffer, Capability::Int16, Capability::Int8, Capability::Kernel, @@ -554,6 +576,12 @@ // TODO: add OpenCL extensions. } + +void RequirementHandler::initAvailableCapabilitiesForVulkan( + const SPIRVSubtarget &ST) { + addAvailableCaps({Capability::Shader, Capability::Linkage}); +} + } // namespace SPIRV } // namespace llvm diff --git a/llvm/lib/Target/SPIRV/SPIRVSubtarget.h b/llvm/lib/Target/SPIRV/SPIRVSubtarget.h --- a/llvm/lib/Target/SPIRV/SPIRVSubtarget.h +++ b/llvm/lib/Target/SPIRV/SPIRVSubtarget.h @@ -24,6 +24,7 @@ #include "llvm/CodeGen/TargetSubtargetInfo.h" #include "llvm/IR/DataLayout.h" #include "llvm/Target/TargetMachine.h" +#include "llvm/TargetParser/Triple.h" #define GET_SUBTARGETINFO_HEADER #include "SPIRVGenSubtargetInfo.inc" @@ -45,6 +46,7 @@ SPIRVInstrInfo InstrInfo; SPIRVFrameLowering FrameLowering; SPIRVTargetLowering TLInfo; + Triple TargetTriple; // GlobalISel related APIs. std::unique_ptr CallLoweringInfo; @@ -70,9 +72,13 @@ unsigned getPointerSize() const { return PointerSize; } bool canDirectlyComparePointers() const; // TODO: this environment is not implemented in Triple, we need to decide - // how to standartize its support. For now, let's assume that we always - // operate with OpenCL. - bool isOpenCLEnv() const { return true; } + // how to standartize its support. For now, let's assume SPIR-V with physical + // addressing is OpenCL, and Logical addressing is Vulkan. + bool isOpenCLEnv() const { + return TargetTriple.getArch() == Triple::spirv32 || + TargetTriple.getArch() == Triple::spirv64; + } + bool isVulkanEnv() const { return TargetTriple.getArch() == Triple::spirv; } uint32_t getSPIRVVersion() const { return SPIRVVersion; }; bool isAtLeastSPIRVVer(uint32_t VerToCompareTo) const; bool isAtLeastOpenCLVer(uint32_t VerToCompareTo) const; diff --git a/llvm/lib/Target/SPIRV/SPIRVSubtarget.cpp b/llvm/lib/Target/SPIRV/SPIRVSubtarget.cpp --- a/llvm/lib/Target/SPIRV/SPIRVSubtarget.cpp +++ b/llvm/lib/Target/SPIRV/SPIRVSubtarget.cpp @@ -36,7 +36,15 @@ const auto Arch = TT.getArch(); // TODO: unify this with pointers legalization. assert(TT.isSPIRV()); - return Arch == Triple::spirv32 ? 32 : 64; + + if (Arch == Triple::spirv64) + return 64; + + // TODO: this probably needs to be revisited: + // AFAIU Logical SPIR-V has no pointer size. So falling-back on ID size. + // Addressing mode can change how some pointers are handled + // (PhysicalStorageBuffer64). + return 32; } SPIRVSubtarget::SPIRVSubtarget(const Triple &TT, const std::string &CPU, @@ -45,7 +53,7 @@ : SPIRVGenSubtargetInfo(TT, CPU, /*TuneCPU=*/CPU, FS), PointerSize(computePointerSize(TT)), SPIRVVersion(0), OpenCLVersion(0), InstrInfo(), FrameLowering(initSubtargetDependencies(CPU, FS)), - TLInfo(TM, *this) { + TLInfo(TM, *this), TargetTriple(TT) { // The order of initialization is important. initAvailableExtensions(); initAvailableExtInstSets(); @@ -82,6 +90,10 @@ } bool SPIRVSubtarget::isAtLeastOpenCLVer(uint32_t VerToCompareTo) const { + if (!isOpenCLEnv()) { + return false; + } + return isAtLeastVer(OpenCLVersion, VerToCompareTo); } diff --git a/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp b/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp --- a/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp +++ b/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp @@ -37,6 +37,7 @@ // Register the target. RegisterTargetMachine X(getTheSPIRV32Target()); RegisterTargetMachine Y(getTheSPIRV64Target()); + RegisterTargetMachine Z(getTheSPIRVLogicalTarget()); PassRegistry &PR = *PassRegistry::getPassRegistry(); initializeGlobalISel(PR); @@ -48,6 +49,9 @@ if (Arch == Triple::spirv32) return "e-p:32:32-i64:64-v16:16-v24:32-v32:32-v48:64-" "v96:128-v192:256-v256:256-v512:512-v1024:1024"; + if (Arch == Triple::spirv) + return "e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-" + "v192:256-v256:256-v512:512-v1024:1024"; return "e-i64:64-v16:16-v24:32-v32:32-v48:64-" "v96:128-v192:256-v256:256-v512:512-v1024:1024"; } diff --git a/llvm/lib/Target/SPIRV/TargetInfo/SPIRVTargetInfo.h b/llvm/lib/Target/SPIRV/TargetInfo/SPIRVTargetInfo.h --- a/llvm/lib/Target/SPIRV/TargetInfo/SPIRVTargetInfo.h +++ b/llvm/lib/Target/SPIRV/TargetInfo/SPIRVTargetInfo.h @@ -15,6 +15,7 @@ Target &getTheSPIRV32Target(); Target &getTheSPIRV64Target(); +Target &getTheSPIRVLogicalTarget(); } // namespace llvm diff --git a/llvm/lib/Target/SPIRV/TargetInfo/SPIRVTargetInfo.cpp b/llvm/lib/Target/SPIRV/TargetInfo/SPIRVTargetInfo.cpp --- a/llvm/lib/Target/SPIRV/TargetInfo/SPIRVTargetInfo.cpp +++ b/llvm/lib/Target/SPIRV/TargetInfo/SPIRVTargetInfo.cpp @@ -19,10 +19,16 @@ static Target TheSPIRV64Target; return TheSPIRV64Target; } +Target &llvm::getTheSPIRVLogicalTarget() { + static Target TheSPIRVLogicalTarget; + return TheSPIRVLogicalTarget; +} extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeSPIRVTargetInfo() { RegisterTarget X(getTheSPIRV32Target(), "spirv32", "SPIR-V 32-bit", "SPIRV"); RegisterTarget Y(getTheSPIRV64Target(), "spirv64", "SPIR-V 64-bit", "SPIRV"); + RegisterTarget Z(getTheSPIRVLogicalTarget(), "spirv", + "SPIR-V Logical", "SPIRV"); } diff --git a/llvm/test/CodeGen/SPIRV/ExecutionMode_GLCompute.ll b/llvm/test/CodeGen/SPIRV/ExecutionMode_GLCompute.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/SPIRV/ExecutionMode_GLCompute.ll @@ -0,0 +1,12 @@ +; RUN: llc -O0 -mtriple=spirv-unknown-unknown %s -o - | FileCheck %s + +; CHECK-DAG: OpEntryPoint GLCompute %[[#entry:]] "main" +; CHECK-DAG: OpExecutionMode %[[#entry]] LocalSize 4 8 16 + +define void @main() #0 !reqd_work_group_size !3 { +entry: + ret void +} + +attributes #0 = { norecurse "hlsl.shader"="compute" "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +!3 = !{i32 4, i32 8, i32 16} diff --git a/llvm/test/CodeGen/SPIRV/capability-Shader.ll b/llvm/test/CodeGen/SPIRV/capability-Shader.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/SPIRV/capability-Shader.ll @@ -0,0 +1,13 @@ +; RUN: llc -O0 -mtriple=spirv-unknown-unknown %s -o - | FileCheck %s + +; CHECK-DAG: OpCapability Shader +;; Ensure no other capability is listed. +; CHECK-NOT: OpCapability + +define void @main() #0 !reqd_work_group_size !3 { +entry: + ret void +} + +attributes #0 = { norecurse "hlsl.shader"="compute" "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +!3 = !{i32 4, i32 8, i32 16} diff --git a/llvm/test/CodeGen/SPIRV/empty-logical.ll b/llvm/test/CodeGen/SPIRV/empty-logical.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/SPIRV/empty-logical.ll @@ -0,0 +1,10 @@ +; RUN: llc -O0 -mtriple=spirv-unknown-unknown %s -o - | FileCheck %s + +;; Ensure the required Capabilities are listed. +; CHECK-DAG: OpCapability Shader +; CHECK-DAG: OpCapability Linkage + +;; Ensure one, and only one, OpMemoryModel is defined. +; CHECK: OpMemoryModel Logical GLSL450 +; CHECK-NOT: OpMemoryModel +