diff --git a/flang/lib/semantics/CMakeLists.txt b/flang/lib/semantics/CMakeLists.txt
index ad020e61b654..d797cfb2eb44 100644
--- a/flang/lib/semantics/CMakeLists.txt
+++ b/flang/lib/semantics/CMakeLists.txt
@@ -15,6 +15,7 @@
 
 add_library(FortranSemantics
   attr.cc
+  canonicalize-do.cc
   expression.cc
   mod-file.cc
   resolve-labels.cc
diff --git a/flang/lib/semantics/canonicalize-do.cc b/flang/lib/semantics/canonicalize-do.cc
new file mode 100644
index 000000000000..c84b30b7780e
--- /dev/null
+++ b/flang/lib/semantics/canonicalize-do.cc
@@ -0,0 +1,112 @@
+// Copyright (c) 2018, NVIDIA CORPORATION.  All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "canonicalize-do.h"
+#include "../parser/parse-tree-visitor.h"
+
+namespace Fortran::semantics {
+
+class CanonicalizationOfDoLoops {
+public:
+  CanonicalizationOfDoLoops() = default;
+
+  template<typename T> bool Pre(T &) { return true; }
+  template<typename T> void Post(T &) {}
+  bool Pre(parser::ExecutionPart &executionPart) {
+    const auto &endIter{executionPart.v.end()};
+    currentList_ = &executionPart.v;
+    for (auto iter{executionPart.v.begin()}; iter != endIter; ++iter) {
+      iter = CheckStatement(iter);
+    }
+    return false;
+  }
+  template<typename T> bool Pre(parser::Statement<T> &statement) {
+    if (!labels_.empty() && statement.label.has_value() &&
+        labels_.back() == *statement.label) {
+      auto currentLabel{labels_.back()};
+      do {
+        currentIter_ = MakeCanonicalForm(labelDoIters_.back(), currentIter_);
+        labelDoIters_.pop_back();
+        labels_.pop_back();
+      } while (!labels_.empty() && labels_.back() == currentLabel);
+    }
+    return false;
+  }
+
+private:
+  parser::Block SpliceBlock(
+      parser::Block::iterator beginLoop, parser::Block::iterator endLoop) {
+    parser::Block block;
+    block.splice(block.begin(), *currentList_, ++beginLoop, ++endLoop);
+    return block;
+  }
+  std::optional<parser::LoopControl> CreateLoopControl(
+      std::optional<parser::LoopControl> &loopControlOpt) {
+    if (loopControlOpt.has_value()) {
+      return std::optional<parser::LoopControl>(
+          parser::LoopControl{loopControlOpt->u});
+    }
+    return std::optional<parser::LoopControl>{};
+  }
+  std::optional<parser::LoopControl> ExtractLoopControl(
+      const parser::Block::iterator &startLoop) {
+    return CreateLoopControl(std::get<std::optional<parser::LoopControl>>(
+        std::get<parser::Statement<common::Indirection<parser::LabelDoStmt>>>(
+            std::get<parser::ExecutableConstruct>(startLoop->u).u)
+            .statement->t));
+  }
+  parser::Block::iterator MakeCanonicalForm(
+      const parser::Block::iterator &startLoop,
+      const parser::Block::iterator &endLoop) {
+    std::get<parser::ExecutableConstruct>(startLoop->u).u =
+        common::Indirection<parser::DoConstruct>{std::make_tuple(
+            parser::Statement<parser::NonLabelDoStmt>{
+                std::optional<parser::Label>{},
+                parser::NonLabelDoStmt{
+                    std::make_tuple(std::optional<parser::Name>{},
+                        ExtractLoopControl(startLoop))}},
+            SpliceBlock(startLoop, endLoop),
+            parser::Statement<parser::EndDoStmt>{std::optional<parser::Label>{},
+                parser::EndDoStmt{std::optional<parser::Name>{}}})};
+    return startLoop;
+  }
+  parser::Block::iterator CheckStatement(const parser::Block::iterator &iter) {
+    currentIter_ = iter;
+    parser::ExecutionPartConstruct &executionPartConstruct{*iter};
+    if (auto *executableConstruct = std::get_if<parser::ExecutableConstruct>(
+            &executionPartConstruct.u)) {
+      if (auto *labelDoLoop = std::get_if<
+              parser::Statement<common::Indirection<parser::LabelDoStmt>>>(
+              &executableConstruct->u)) {
+        labelDoIters_.push_back(iter);
+        labels_.push_back(std::get<parser::Label>(labelDoLoop->statement->t));
+      } else if (!labels_.empty()) {
+        parser::Walk(executableConstruct->u, *this);
+      }
+    }
+    return currentIter_;
+  }
+
+  std::vector<parser::Block::iterator> labelDoIters_;
+  std::vector<parser::Label> labels_;
+  parser::Block::iterator currentIter_;
+  parser::Block *currentList_;
+};
+
+void CanonicalizeDo(parser::Program &program) {
+  CanonicalizationOfDoLoops canonicalizationOfDoLoops;
+  parser::Walk(program, canonicalizationOfDoLoops);
+}
+
+}  // namespace Fortran::semantics
diff --git a/flang/lib/semantics/canonicalize-do.h b/flang/lib/semantics/canonicalize-do.h
new file mode 100644
index 000000000000..0f1ac987d016
--- /dev/null
+++ b/flang/lib/semantics/canonicalize-do.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2018, NVIDIA CORPORATION.  All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef FORTRAN_SEMANTICS_CANONICALIZE_DO_H_
+#define FORTRAN_SEMANTICS_CANONICALIZE_DO_H_
+
+namespace Fortran::parser {
+struct Program;
+}  // namespace Fortran::parser
+
+namespace Fortran::semantics {
+void CanonicalizeDo(parser::Program &program);
+}  // namespace Fortran::semantics
+#endif  // FORTRAN_SEMANTICS_CANONICALIZE_DO_H_
diff --git a/flang/lib/semantics/semantics.cc b/flang/lib/semantics/semantics.cc
index bd9f81e31c62..663071bcca09 100644
--- a/flang/lib/semantics/semantics.cc
+++ b/flang/lib/semantics/semantics.cc
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "semantics.h"
+#include "canonicalize-do.h"
 #include "mod-file.h"
 #include "resolve-labels.h"
 #include "resolve-names.h"
@@ -52,6 +53,7 @@ bool Semantics::Perform(parser::Program &program) {
   if (AnyFatalError()) {
     return false;
   }
+  CanonicalizeDo(program);
   ModFileWriter writer;
   writer.set_directory(moduleDirectory_);
   if (!writer.WriteAll(globalScope_)) {
diff --git a/flang/test/semantics/CMakeLists.txt b/flang/test/semantics/CMakeLists.txt
index b132a463353c..f6425b1ed5dc 100644
--- a/flang/test/semantics/CMakeLists.txt
+++ b/flang/test/semantics/CMakeLists.txt
@@ -91,6 +91,10 @@ set(LABEL_TESTS
   label*.[Ff]90
 )
 
+set(CANONDO_TESTS
+  canondo*.[Ff]90
+)
+
 foreach(test ${ERROR_TESTS})
   add_test(NAME ${test} COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test_errors.sh ${test})
 endforeach()
@@ -106,3 +110,7 @@ endforeach()
 foreach(test ${LABEL_TESTS})
   add_test(NAME ${test} COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test_any.sh ${test})
 endforeach()
+
+foreach(test ${CANONDO_TESTS})
+  add_test(NAME ${test} COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test_any.sh ${test})
+endforeach()
diff --git a/flang/test/semantics/canondo01.f90 b/flang/test/semantics/canondo01.f90
new file mode 100644
index 000000000000..5cbb0ce29de6
--- /dev/null
+++ b/flang/test/semantics/canondo01.f90
@@ -0,0 +1,28 @@
+! Copyright (c) 2018, NVIDIA CORPORATION.  All rights reserved.
+!
+! Licensed under the Apache License, Version 2.0 (the "License");
+! you may not use this file except in compliance with the License.
+! You may obtain a copy of the License at
+!
+!     http://www.apache.org/licenses/LICENSE-2.0
+!
+! Unless required by applicable law or agreed to in writing, software
+! distributed under the License is distributed on an "AS IS" BASIS,
+! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+! See the License for the specific language governing permissions and
+! limitations under the License.
+
+! negative test -- invalid labels, out of range
+
+! RUN: ${F18} -funparse-with-symbols %s 2>&1 | ${FileCheck} %s
+! CHECK: end do
+
+SUBROUTINE sub00(a,b,n,m)
+  INTEGER n,m
+  REAL a(n,m), b(n,m)
+
+  DO 10 j = 1,m
+     DO 10 i = 1,n
+        g = a(i,j) - b(i,j)
+10      PRINT *, g
+END SUBROUTINE sub00
diff --git a/flang/test/semantics/canondo02.f90 b/flang/test/semantics/canondo02.f90
new file mode 100644
index 000000000000..a54e4512b0e0
--- /dev/null
+++ b/flang/test/semantics/canondo02.f90
@@ -0,0 +1,28 @@
+! Copyright (c) 2018, NVIDIA CORPORATION.  All rights reserved.
+!
+! Licensed under the Apache License, Version 2.0 (the "License");
+! you may not use this file except in compliance with the License.
+! You may obtain a copy of the License at
+!
+!     http://www.apache.org/licenses/LICENSE-2.0
+!
+! Unless required by applicable law or agreed to in writing, software
+! distributed under the License is distributed on an "AS IS" BASIS,
+! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+! See the License for the specific language governing permissions and
+! limitations under the License.
+
+! negative test -- invalid labels, out of range
+
+! RUN: ${F18} -funparse-with-symbols %s 2>&1 | ${FileCheck} %s
+! CHECK: end do
+
+SUBROUTINE sub00(a,b,n,m)
+  INTEGER n,m
+  REAL a(n,m), b(n,m)
+
+  i = n-1
+  DO 10 j = 1,m
+     g = a(i,j) - b(i,j)
+10   PRINT *, g
+END SUBROUTINE sub00