diff --git a/flang/include/flang/Optimizer/Dialect/CMakeLists.txt b/flang/include/flang/Optimizer/Dialect/CMakeLists.txt
--- a/flang/include/flang/Optimizer/Dialect/CMakeLists.txt
+++ b/flang/include/flang/Optimizer/Dialect/CMakeLists.txt
@@ -1,6 +1,12 @@
 # This replicates part of the add_mlir_dialect cmake function from MLIR that
 # cannot be used her because it expects to be run inside MLIR directory which
 # is not the case for FIR.
+set(LLVM_TARGET_DEFINITIONS FIRAttr.td)
+mlir_tablegen(FIREnumAttr.h.inc -gen-enum-decls)
+mlir_tablegen(FIREnumAttr.cpp.inc -gen-enum-defs)
+mlir_tablegen(FIRAttr.h.inc --gen-attrdef-decls)
+mlir_tablegen(FIRAttr.cpp.inc -gen-attrdef-defs)
+
 set(LLVM_TARGET_DEFINITIONS FIROps.td)
 mlir_tablegen(FIROps.h.inc -gen-op-decls)
 mlir_tablegen(FIROps.cpp.inc -gen-op-defs)
diff --git a/flang/include/flang/Optimizer/Dialect/FIRAttr.h b/flang/include/flang/Optimizer/Dialect/FIRAttr.h
--- a/flang/include/flang/Optimizer/Dialect/FIRAttr.h
+++ b/flang/include/flang/Optimizer/Dialect/FIRAttr.h
@@ -142,4 +142,9 @@
 
 } // namespace fir
 
+#include "flang/Optimizer/Dialect/FIREnumAttr.h.inc"
+
+#define GET_ATTRDEF_CLASSES
+#include "flang/Optimizer/Dialect/FIRAttr.h.inc"
+
 #endif // FORTRAN_OPTIMIZER_DIALECT_FIRATTR_H
diff --git a/flang/include/flang/Optimizer/Dialect/FIRAttr.td b/flang/include/flang/Optimizer/Dialect/FIRAttr.td
new file mode 100644
--- /dev/null
+++ b/flang/include/flang/Optimizer/Dialect/FIRAttr.td
@@ -0,0 +1,60 @@
+//===- FIRAttr.td - FIR Attributes -------------------------*- tablegen -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file declares the FIR dialect attributes.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef FIR_DIALECT_FIR_ATTRS
+#define FIR_DIALECT_FIR_ATTRS
+
+include "flang/Optimizer/Dialect/FIRDialect.td"
+include "mlir/IR/EnumAttr.td"
+
+class fir_Attr<string name> : AttrDef<fir_Dialect, name>;
+
+def FIRnoAttributes  : I32BitEnumAttrCaseNone<"None">;
+def FIRallocatable  : I32BitEnumAttrCaseBit<"allocatable", 0>;
+def FIRasynchronous : I32BitEnumAttrCaseBit<"asynchronous", 1>;
+def FIRbind_c       : I32BitEnumAttrCaseBit<"bind_c", 2>;
+def FIRcontiguous   : I32BitEnumAttrCaseBit<"contiguous", 3>;
+def FIRintent_in    : I32BitEnumAttrCaseBit<"intent_in", 4>;
+def FIRintent_inout : I32BitEnumAttrCaseBit<"intent_inout", 5>;
+def FIRintent_out   : I32BitEnumAttrCaseBit<"intent_out", 6>;
+def FIRoptional     : I32BitEnumAttrCaseBit<"optional", 7>;
+def FIRparameter    : I32BitEnumAttrCaseBit<"parameter", 8>;
+def FIRpointer      : I32BitEnumAttrCaseBit<"pointer", 9>;
+def FIRtarget       : I32BitEnumAttrCaseBit<"target", 10>;
+def FIRvalue        : I32BitEnumAttrCaseBit<"value", 11>;
+def FIRvolatile     : I32BitEnumAttrCaseBit<"fortran_volatile", 12, "volatile">;
+
+def fir_FortranVariableFlagsEnum : I32BitEnumAttr<
+    "FortranVariableFlagsEnum",
+    "Fortran variable attributes",
+    [FIRnoAttributes, FIRallocatable, FIRasynchronous, FIRbind_c, FIRcontiguous,
+     FIRintent_in, FIRintent_inout, FIRintent_out, FIRoptional, FIRparameter,
+     FIRpointer, FIRtarget, FIRvalue, FIRvolatile]> {
+  let separator = ", ";
+  let cppNamespace = "::fir";
+  let printBitEnumPrimaryGroups = 1;
+}
+
+def fir_FortranVariableFlagsAttr : fir_Attr<"FortranVariableFlags"> {
+  let mnemonic = "var_attrs";
+
+  let parameters = (ins
+    "FortranVariableFlagsEnum":$flags
+  );
+  let hasCustomAssemblyFormat = 1;
+  let returnType = "::fir::FortranVariableFlagsEnum";
+  let convertFromStorage = "$_self.getFlags()";
+  let constBuilderCall =
+          "::fir::FortranVariableFlagsAttr::get($_builder.getContext(), $0)";
+}
+
+#endif // FIR_DIALECT_FIR_ATTRS
diff --git a/flang/lib/Optimizer/Dialect/FIRAttr.cpp b/flang/lib/Optimizer/Dialect/FIRAttr.cpp
--- a/flang/lib/Optimizer/Dialect/FIRAttr.cpp
+++ b/flang/lib/Optimizer/Dialect/FIRAttr.cpp
@@ -17,6 +17,12 @@
 #include "mlir/IR/BuiltinTypes.h"
 #include "mlir/IR/DialectImplementation.h"
 #include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/TypeSwitch.h"
+
+#include "flang/Optimizer/Dialect/FIREnumAttr.cpp.inc"
+#define GET_ATTRDEF_CLASSES
+#include "flang/Optimizer/Dialect/FIRAttr.cpp.inc"
 
 using namespace fir;
 
@@ -171,15 +177,47 @@
   return RealAttr::get(dialect->getContext(), {kind, value});
 }
 
+mlir::Attribute fir::FortranVariableFlagsAttr::parse(mlir::AsmParser &parser,
+                                                     mlir::Type type) {
+  if (mlir::failed(parser.parseLess()))
+    return {};
+
+  fir::FortranVariableFlagsEnum flags = {};
+  if (mlir::failed(parser.parseOptionalGreater())) {
+    auto parseFlags = [&]() -> mlir::ParseResult {
+      llvm::StringRef elemName;
+      if (mlir::failed(parser.parseKeyword(&elemName)))
+        return mlir::failure();
+
+      auto elem = fir::symbolizeFortranVariableFlagsEnum(elemName);
+      if (!elem)
+        return parser.emitError(parser.getNameLoc(),
+                                "Unknown fortran variable attribute: ")
+               << elemName;
+
+      flags = flags | *elem;
+      return mlir::success();
+    };
+    if (mlir::failed(parser.parseCommaSeparatedList(parseFlags)) ||
+        parser.parseGreater())
+      return {};
+  }
+
+  return FortranVariableFlagsAttr::get(parser.getContext(), flags);
+}
+
 mlir::Attribute fir::parseFirAttribute(FIROpsDialect *dialect,
                                        mlir::DialectAsmParser &parser,
                                        mlir::Type type) {
   auto loc = parser.getNameLoc();
   llvm::StringRef attrName;
-  if (parser.parseKeyword(&attrName)) {
-    parser.emitError(loc, "expected an attribute name");
-    return {};
-  }
+  mlir::Attribute attr;
+  mlir::OptionalParseResult result =
+      generatedAttributeParser(parser, &attrName, type, attr);
+  if (result.has_value())
+    return attr;
+  if (attrName.empty())
+    return {}; // error reported by generatedAttributeParser
 
   if (attrName == ExactTypeAttr::getAttrName()) {
     mlir::Type type;
@@ -216,6 +254,12 @@
 // FIR attribute pretty printer
 //===----------------------------------------------------------------------===//
 
+void fir::FortranVariableFlagsAttr::print(mlir::AsmPrinter &printer) const {
+  printer << "<";
+  printer << fir::stringifyFortranVariableFlagsEnum(this->getFlags());
+  printer << ">";
+}
+
 void fir::printFirAttribute(FIROpsDialect *dialect, mlir::Attribute attr,
                             mlir::DialectAsmPrinter &p) {
   auto &os = p.getStream();
@@ -240,7 +284,7 @@
     llvm::SmallString<40> ss;
     a.getValue().bitcastToAPInt().toStringUnsigned(ss, 16);
     os << ss << '>';
-  } else {
+  } else if (mlir::failed(generatedAttributePrinter(attr, p))) {
     // don't know how to print the attribute, so use a default
     os << "<(unknown attribute)>";
   }
@@ -251,6 +295,7 @@
 //===----------------------------------------------------------------------===//
 
 void FIROpsDialect::registerAttributes() {
-  addAttributes<ClosedIntervalAttr, ExactTypeAttr, LowerBoundAttr,
-                PointIntervalAttr, RealAttr, SubclassAttr, UpperBoundAttr>();
+  addAttributes<ClosedIntervalAttr, ExactTypeAttr, FortranVariableFlagsAttr,
+                LowerBoundAttr, PointIntervalAttr, RealAttr, SubclassAttr,
+                UpperBoundAttr>();
 }
diff --git a/flang/test/Fir/fir-ops.fir b/flang/test/Fir/fir-ops.fir
--- a/flang/test/Fir/fir-ops.fir
+++ b/flang/test/Fir/fir-ops.fir
@@ -850,3 +850,35 @@
   fir.store %4#1 to %0 : !fir.ref<i32>
   return
 }
+
+func.func @test_fortran_var_attrs() {
+  %0 = fir.alloca !fir.heap<f32> {fortran_attrs = #fir.var_attrs<allocatable>}
+  %1 = fir.alloca f32 {fortran_attrs = #fir.var_attrs<asynchronous>}
+  %2 = fir.alloca f32 {fortran_attrs = #fir.var_attrs<bind_c>}
+  %3 = fir.alloca f32 {fortran_attrs = #fir.var_attrs<contiguous>}
+  %4 = fir.alloca f32 {fortran_attrs = #fir.var_attrs<intent_in>}
+  %5 = fir.alloca f32 {fortran_attrs = #fir.var_attrs<intent_inout>}
+  %6 = fir.alloca f32 {fortran_attrs = #fir.var_attrs<intent_out>}
+  %7 = fir.alloca f32 {fortran_attrs = #fir.var_attrs<optional>}
+  %8 = fir.alloca f32 {fortran_attrs = #fir.var_attrs<parameter>}
+  %9 = fir.alloca !fir.ptr<f32> {fortran_attrs = #fir.var_attrs<pointer>}
+  %10 = fir.alloca f32 {fortran_attrs = #fir.var_attrs<target>}
+  %11 = fir.alloca f32 {fortran_attrs = #fir.var_attrs<value>}
+  %12 = fir.alloca f32 {fortran_attrs = #fir.var_attrs<volatile>}
+  %13 = fir.alloca !fir.box<!fir.ptr<!fir.array<?xf32>>> {fortran_attrs = #fir.var_attrs<pointer, contiguous, volatile>}
+  return
+  // CHECK: fir.alloca !fir.heap<f32> {fortran_attrs = #fir.var_attrs<allocatable>}
+  // CHECK: fir.alloca f32 {fortran_attrs = #fir.var_attrs<asynchronous>}
+  // CHECK: fir.alloca f32 {fortran_attrs = #fir.var_attrs<bind_c>}
+  // CHECK: fir.alloca f32 {fortran_attrs = #fir.var_attrs<contiguous>}
+  // CHECK: fir.alloca f32 {fortran_attrs = #fir.var_attrs<intent_in>}
+  // CHECK: fir.alloca f32 {fortran_attrs = #fir.var_attrs<intent_inout>}
+  // CHECK: fir.alloca f32 {fortran_attrs = #fir.var_attrs<intent_out>}
+  // CHECK: fir.alloca f32 {fortran_attrs = #fir.var_attrs<optional>}
+  // CHECK: fir.alloca f32 {fortran_attrs = #fir.var_attrs<parameter>}
+  // CHECK: fir.alloca !fir.ptr<f32> {fortran_attrs = #fir.var_attrs<pointer>}
+  // CHECK: fir.alloca f32 {fortran_attrs = #fir.var_attrs<target>}
+  // CHECK: fir.alloca f32 {fortran_attrs = #fir.var_attrs<value>}
+  // CHECK: fir.alloca f32 {fortran_attrs = #fir.var_attrs<volatile>}
+  // CHECK: fir.alloca !fir.box<!fir.ptr<!fir.array<?xf32>>> {fortran_attrs = #fir.var_attrs<contiguous, pointer, volatile>}
+}
diff --git a/flang/test/Fir/invalid.fir b/flang/test/Fir/invalid.fir
--- a/flang/test/Fir/invalid.fir
+++ b/flang/test/Fir/invalid.fir
@@ -802,3 +802,9 @@
   fir.dispatch "proc1"(%arg0 : !fir.class<!fir.type<derived{a:i32,b:i32}>>) (%arg0, %arg1 : !fir.class<!fir.type<derived{a:i32,b:i32}>>, i32) {pass_arg_pos = 1 : i32}
   return
 }
+
+// -----
+func.func @test_fortran_var_attrs() {
+  // expected-error@+1 {{Unknown fortran variable attribute: volatypo}}
+  %0 = fir.alloca f32 {fortran_attrs = #fir.var_attrs<volatypo>}
+}