https://github.com/kparzysz created https://github.com/llvm/llvm-project/pull/170735
For an OpenMP loop construct, count how many loops will effectively be contained in its associated block. For constructs that are loop-nest associated this number should be 1. Report cases where this number is different. Take into account that the block associated with a loop construct can contain compiler directives. >From 9a2d3dca08ab237e7e949fd5642c96cf0fba89b8 Mon Sep 17 00:00:00 2001 From: Krzysztof Parzyszek <[email protected]> Date: Tue, 2 Dec 2025 14:59:34 -0600 Subject: [PATCH] [flang][OpenMP] Generalize checks of loop construct structure For an OpenMP loop construct, count how many loops will effectively be contained in its associated block. For constructs that are loop-nest associated this number should be 1. Report cases where this number is different. Take into account that the block associated with a loop construct can contain compiler directives. --- flang/lib/Semantics/check-omp-loop.cpp | 201 +++++++++++------- flang/lib/Semantics/check-omp-structure.h | 3 +- flang/test/Parser/OpenMP/tile-fail.f90 | 8 +- flang/test/Semantics/OpenMP/do21.f90 | 10 +- .../Semantics/OpenMP/loop-association.f90 | 6 +- .../OpenMP/loop-transformation-clauses01.f90 | 16 +- .../loop-transformation-construct01.f90 | 4 +- .../loop-transformation-construct02.f90 | 8 +- .../loop-transformation-construct04.f90 | 4 +- 9 files changed, 156 insertions(+), 104 deletions(-) diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp index fc4b9222d91b3..6414f0028e008 100644 --- a/flang/lib/Semantics/check-omp-loop.cpp +++ b/flang/lib/Semantics/check-omp-loop.cpp @@ -37,6 +37,14 @@ #include <tuple> #include <variant> +namespace Fortran::semantics { +static bool IsLoopTransforming(llvm::omp::Directive dir); +static bool IsFullUnroll(const parser::OpenMPLoopConstruct &x); +static std::optional<size_t> CountGeneratedLoops( + const parser::ExecutionPartConstruct &epc); +static std::optional<size_t> CountGeneratedLoops(const parser::Block &block); +} // namespace Fortran::semantics + namespace { using namespace Fortran; @@ -263,22 +271,19 @@ static bool IsLoopTransforming(llvm::omp::Directive dir) { } void OmpStructureChecker::CheckNestedBlock(const parser::OpenMPLoopConstruct &x, - const parser::Block &body, size_t &nestedCount) { + const parser::Block &body) { for (auto &stmt : body) { if (auto *dir{parser::Unwrap<parser::CompilerDirective>(stmt)}) { context_.Say(dir->source, "Compiler directives are not allowed inside OpenMP loop constructs"_warn_en_US); - } else if (parser::Unwrap<parser::DoConstruct>(stmt)) { - ++nestedCount; } else if (auto *omp{parser::Unwrap<parser::OpenMPLoopConstruct>(stmt)}) { if (!IsLoopTransforming(omp->BeginDir().DirName().v)) { context_.Say(omp->source, "Only loop-transforming OpenMP constructs are allowed inside OpenMP loop constructs"_err_en_US); } - ++nestedCount; } else if (auto *block{parser::Unwrap<parser::BlockConstruct>(stmt)}) { - CheckNestedBlock(x, std::get<parser::Block>(block->t), nestedCount); - } else { + CheckNestedBlock(x, std::get<parser::Block>(block->t)); + } else if (!parser::Unwrap<parser::DoConstruct>(stmt)) { parser::CharBlock source{parser::GetSource(stmt).value_or(x.source)}; context_.Say(source, "OpenMP loop construct can only contain DO loops or loop-nest-generating OpenMP constructs"_err_en_US); @@ -286,16 +291,96 @@ void OmpStructureChecker::CheckNestedBlock(const parser::OpenMPLoopConstruct &x, } } +static bool IsFullUnroll(const parser::OpenMPLoopConstruct &x) { + const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()}; + + if (beginSpec.DirName().v == llvm::omp::Directive::OMPD_unroll) { + return llvm::none_of(beginSpec.Clauses().v, [](const parser::OmpClause &c) { + return c.Id() == llvm::omp::Clause::OMPC_partial; + }); + } + return false; +} + +static std::optional<size_t> CountGeneratedLoops( + const parser::ExecutionPartConstruct &epc) { + if (parser::Unwrap<parser::DoConstruct>(epc)) { + return 1; + } + + auto &omp{DEREF(parser::Unwrap<parser::OpenMPLoopConstruct>(epc))}; + const parser::OmpDirectiveSpecification &beginSpec{omp.BeginDir()}; + llvm::omp::Directive dir{beginSpec.DirName().v}; + + // TODO: Handle split, apply. + if (IsFullUnroll(omp)) { + return std::nullopt; + } + if (dir == llvm::omp::Directive::OMPD_fuse) { + auto rangeAt{ + llvm::find_if(beginSpec.Clauses().v, [](const parser::OmpClause &c) { + return c.Id() == llvm::omp::Clause::OMPC_looprange; + })}; + if (rangeAt == beginSpec.Clauses().v.end()) { + return std::nullopt; + } + + auto *loopRange{parser::Unwrap<parser::OmpLooprangeClause>(*rangeAt)}; + std::optional<int64_t> count{GetIntValue(std::get<1>(loopRange->t))}; + if (!count || *count <= 0) { + return std::nullopt; + } + if (auto nestedCount{CountGeneratedLoops(std::get<parser::Block>(omp.t))}) { + return 1 + *nestedCount - static_cast<size_t>(*count); + } else { + return std::nullopt; + } + } + + // For every other loop construct return 1. + return 1; +} + +static std::optional<size_t> CountGeneratedLoops(const parser::Block &block) { + // Count the number of loops in the associated block. If there are any + // malformed construct in there, getting the number may be meaningless. + // These issues will be diagnosed elsewhere, and we should not emit any + // messages about a potentially incorrect loop count. + // In such cases reset the count to nullopt. Once it becomes nullopt, + // keep it that way. + std::optional<size_t> numLoops{0}; + for (auto &epc : parser::omp::LoopRange(block)) { + if (auto genCount{CountGeneratedLoops(epc)}) { + *numLoops += *genCount; + } else { + numLoops = std::nullopt; + break; + } + } + return numLoops; +} + void OmpStructureChecker::CheckNestedConstruct( const parser::OpenMPLoopConstruct &x) { - size_t nestedCount{0}; - + const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()}; auto &body{std::get<parser::Block>(x.t)}; - if (body.empty()) { - context_.Say(x.source, - "OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct"_err_en_US); - } else { - CheckNestedBlock(x, body, nestedCount); + + CheckNestedBlock(x, body); + + // Check if a loop-nest-associated construct has only one top-level loop + // in it. + if (std::optional<size_t> numLoops{CountGeneratedLoops(body)}) { + if (*numLoops == 0) { + context_.Say(beginSpec.DirName().source, + "This construct should contain a DO-loop or a loop-nest-generating OpenMP construct"_err_en_US); + } else { + auto assoc{llvm::omp::getDirectiveAssociation(beginSpec.DirName().v)}; + if (*numLoops > 1 && assoc == llvm::omp::Association::LoopNest) { + context_.Say(beginSpec.DirName().source, + "This construct applies to a loop nest, but has a loop sequence of length %zu"_err_en_US, + *numLoops); + } + } } } @@ -304,16 +389,9 @@ void OmpStructureChecker::CheckFullUnroll( // If the nested construct is a full unroll, then this construct is invalid // since it won't contain a loop. if (const parser::OpenMPLoopConstruct *nested{x.GetNestedConstruct()}) { - auto &nestedSpec{nested->BeginDir()}; - if (nestedSpec.DirName().v == llvm::omp::Directive::OMPD_unroll) { - bool isPartial{ - llvm::any_of(nestedSpec.Clauses().v, [](const parser::OmpClause &c) { - return c.Id() == llvm::omp::Clause::OMPC_partial; - })}; - if (!isPartial) { - context_.Say(x.source, - "OpenMP loop construct cannot apply to a fully unrolled loop"_err_en_US); - } + if (IsFullUnroll(*nested)) { + context_.Say(x.source, + "OpenMP loop construct cannot apply to a fully unrolled loop"_err_en_US); } } } @@ -387,11 +465,6 @@ void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) { beginName.v == llvm::omp::Directive::OMPD_distribute_simd) { CheckDistLinear(x); } - if (beginName.v == llvm::omp::Directive::OMPD_fuse) { - CheckLooprangeBounds(x); - } else { - CheckNestedFuse(x); - } } const parser::Name OmpStructureChecker::GetLoopIndex( @@ -531,57 +604,20 @@ void OmpStructureChecker::CheckDistLinear( void OmpStructureChecker::CheckLooprangeBounds( const parser::OpenMPLoopConstruct &x) { - const parser::OmpClauseList &clauseList{x.BeginDir().Clauses()}; - if (clauseList.v.empty()) { - return; - } - for (auto &clause : clauseList.v) { - if (const auto *lrClause{ - std::get_if<parser::OmpClause::Looprange>(&clause.u)}) { - auto first{GetIntValue(std::get<0>((lrClause->v).t))}; - auto count{GetIntValue(std::get<1>((lrClause->v).t))}; - if (!first || !count) { - return; - } - auto &loopConsList{std::get<parser::Block>(x.t)}; - if (*first > 0 && *count > 0 && - loopConsList.size() < (unsigned)(*first + *count - 1)) { - context_.Say(clause.source, - "The loop range indicated in the %s clause must not be out of the bounds of the Loop Sequence following the construct."_err_en_US, - parser::ToUpperCaseLetters(clause.source.ToString())); - } - return; - } - } -} - -void OmpStructureChecker::CheckNestedFuse( - const parser::OpenMPLoopConstruct &x) { - auto &loopConsList{std::get<parser::Block>(x.t)}; - if (loopConsList.empty()) { - return; - } - const auto *ompConstruct{parser::omp::GetOmpLoop(loopConsList.front())}; - if (!ompConstruct) { - return; - } - const parser::OmpClauseList &clauseList{ompConstruct->BeginDir().Clauses()}; - if (clauseList.v.empty()) { - return; - } - for (auto &clause : clauseList.v) { - if (const auto *lrClause{ - std::get_if<parser::OmpClause::Looprange>(&clause.u)}) { - auto count{GetIntValue(std::get<1>((lrClause->v).t))}; - if (!count) { + for (const parser::OmpClause &clause : x.BeginDir().Clauses().v) { + if (auto *lrClause{parser::Unwrap<parser::OmpLooprangeClause>(clause)}) { + auto first{GetIntValue(std::get<0>(lrClause->t))}; + auto count{GetIntValue(std::get<1>(lrClause->t))}; + if (!first || !count || *first <= 0 || *count <= 0) { return; } - auto &nestedLoopConsList{std::get<parser::Block>(ompConstruct->t)}; - if (nestedLoopConsList.size() > (unsigned)(*count)) { - context_.Say(x.BeginDir().DirName().source, - "The loop sequence following the %s construct must be fully fused first."_err_en_US, - parser::ToUpperCaseLetters( - x.BeginDir().DirName().source.ToString())); + auto requiredCount{static_cast<size_t>(*first + *count - 1)}; + if (auto loopCount{CountGeneratedLoops(std::get<parser::Block>(x.t))}) { + if (*loopCount < requiredCount) { + context_.Say(clause.source, + "The specified loop range requires %zu loops, but the loop sequence has a length of %zu"_err_en_US, + requiredCount, *loopCount); + } } return; } @@ -625,18 +661,21 @@ void OmpStructureChecker::CheckScanModifier( } void OmpStructureChecker::Leave(const parser::OpenMPLoopConstruct &x) { - const parser::OmpClauseList &clauseList{x.BeginDir().Clauses()}; + const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()}; // A few semantic checks for InScan reduction are performed below as SCAN // constructs inside LOOP may add the relevant information. Scan reduction is // supported only in loop constructs, so same checks are not applicable to // other directives. - for (const auto &clause : clauseList.v) { + for (const auto &clause : beginSpec.Clauses().v) { if (auto *reduction{std::get_if<parser::OmpClause::Reduction>(&clause.u)}) { CheckScanModifier(*reduction); } } - if (llvm::omp::allSimdSet.test(GetContext().directive)) { + if (beginSpec.DirName().v == llvm::omp::Directive::OMPD_fuse) { + CheckLooprangeBounds(x); + } + if (llvm::omp::allSimdSet.test(beginSpec.DirName().v)) { ExitDirectiveNest(SIMDNest); } dirContext_.pop_back(); @@ -782,8 +821,8 @@ void OmpStructureChecker::Enter(const parser::OmpClause::Sizes &c) { void OmpStructureChecker::Enter(const parser::OmpClause::Looprange &x) { CheckAllowedClause(llvm::omp::Clause::OMPC_looprange); auto &[first, count]{x.v.t}; - RequiresConstantPositiveParameter(llvm::omp::Clause::OMPC_looprange, count); RequiresConstantPositiveParameter(llvm::omp::Clause::OMPC_looprange, first); + RequiresConstantPositiveParameter(llvm::omp::Clause::OMPC_looprange, count); } void OmpStructureChecker::Enter(const parser::DoConstruct &x) { diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h index 5bd5ae050be64..267362b6325f1 100644 --- a/flang/lib/Semantics/check-omp-structure.h +++ b/flang/lib/Semantics/check-omp-structure.h @@ -323,11 +323,10 @@ class OmpStructureChecker : public OmpStructureCheckerBase { void CheckScanModifier(const parser::OmpClause::Reduction &x); void CheckLooprangeBounds(const parser::OpenMPLoopConstruct &x); - void CheckNestedFuse(const parser::OpenMPLoopConstruct &x); void CheckDistLinear(const parser::OpenMPLoopConstruct &x); void CheckSIMDNest(const parser::OpenMPConstruct &x); void CheckNestedBlock(const parser::OpenMPLoopConstruct &x, - const parser::Block &body, size_t &nestedCount); + const parser::Block &body); void CheckNestedConstruct(const parser::OpenMPLoopConstruct &x); void CheckFullUnroll(const parser::OpenMPLoopConstruct &x); void CheckTargetNest(const parser::OpenMPConstruct &x); diff --git a/flang/test/Parser/OpenMP/tile-fail.f90 b/flang/test/Parser/OpenMP/tile-fail.f90 index a69261a927961..d5ff39cd1037c 100644 --- a/flang/test/Parser/OpenMP/tile-fail.f90 +++ b/flang/test/Parser/OpenMP/tile-fail.f90 @@ -1,7 +1,7 @@ ! RUN: split-file %s %t -! RUN: not %flang_fc1 -fsyntax-only -fopenmp %t/stray_end1.f90 2>&1 | FileCheck %t/stray_end1.f90 -! RUN: not %flang_fc1 -fsyntax-only -fopenmp %t/stray_end2.f90 2>&1 | FileCheck %t/stray_end2.f90 -! RUN: not %flang_fc1 -fsyntax-only -fopenmp %t/stray_begin.f90 2>&1 | FileCheck %t/stray_begin.f90 +! RUN: not %flang_fc1 -fsyntax-only -fopenmp -fopenmp-version=60 %t/stray_end1.f90 2>&1 | FileCheck %t/stray_end1.f90 +! RUN: not %flang_fc1 -fsyntax-only -fopenmp -fopenmp-version=60 %t/stray_end2.f90 2>&1 | FileCheck %t/stray_end2.f90 +! RUN: not %flang_fc1 -fsyntax-only -fopenmp -fopenmp-version=60 %t/stray_begin.f90 2>&1 | FileCheck %t/stray_begin.f90 !--- stray_end1.f90 @@ -25,7 +25,7 @@ subroutine stray_end2 !--- stray_begin.f90 subroutine stray_begin - !CHECK: error: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct + !CHECK: error: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct !$omp tile sizes(2) end subroutine diff --git a/flang/test/Semantics/OpenMP/do21.f90 b/flang/test/Semantics/OpenMP/do21.f90 index e6fe7dd39dd3e..683118a5b2182 100644 --- a/flang/test/Semantics/OpenMP/do21.f90 +++ b/flang/test/Semantics/OpenMP/do21.f90 @@ -2,26 +2,26 @@ ! Check for existence of loop following a DO directive subroutine do1 - !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct + !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct !$omp do end subroutine subroutine do2 - !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct + !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct !$omp parallel do end subroutine subroutine do3 - !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct + !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct !$omp simd end subroutine subroutine do4 - !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct + !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct !$omp do simd end subroutine subroutine do5 - !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct + !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct !$omp loop end subroutine diff --git a/flang/test/Semantics/OpenMP/loop-association.f90 b/flang/test/Semantics/OpenMP/loop-association.f90 index 4e63cafb3fda1..4e6cf4ce13486 100644 --- a/flang/test/Semantics/OpenMP/loop-association.f90 +++ b/flang/test/Semantics/OpenMP/loop-association.f90 @@ -103,7 +103,7 @@ !$omp parallel do private(c) do i = 1, N do j = 1, N - !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct + !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct !$omp parallel do shared(b) a = 3.14 enddo @@ -123,7 +123,7 @@ !ERROR: Misplaced OpenMP end-directive !$omp end parallel do - !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct + !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct !$omp parallel do private(c) 5 FORMAT (1PE12.4, I10) do i=1, N @@ -140,7 +140,7 @@ !ERROR: Misplaced OpenMP end-directive !$omp end parallel do simd - !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct + !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct !$omp simd a = i + 1 !ERROR: Misplaced OpenMP end-directive diff --git a/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90 b/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90 index 9ca0e8cfc9af1..5e3d32d7c6eff 100644 --- a/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90 +++ b/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90 @@ -20,7 +20,7 @@ subroutine loop_transformation_construct1 end do !$omp end fuse - !ERROR: The loop range indicated in the LOOPRANGE(5,2) clause must not be out of the bounds of the Loop Sequence following the construct. + !ERROR: The specified loop range requires 6 loops, but the loop sequence has a length of 2 !$omp fuse looprange(5,2) do x = 1, i v(x) = x * 2 @@ -63,4 +63,18 @@ subroutine loop_transformation_construct1 v(x) = x * 2 end do !$omp end fuse + + ! This is ok aside from the warnings about compiler directives + !$omp fuse looprange(1,3) + do x = 1, 10; end do ! 1 loop + !WARNING: Compiler directives are not allowed inside OpenMP loop constructs + !dir$ novector + !$omp fuse looprange(1,2) ! 2 loops + do x = 1, 10; end do + !WARNING: Compiler directives are not allowed inside OpenMP loop constructs + !dir$ nounroll + do x = 1, 10; end do + do x = 1, 10; end do + !$omp end fuse + !$omp end fuse end subroutine diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90 index caa8f3f216fec..4eeb7330ea589 100644 --- a/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90 +++ b/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90 @@ -7,7 +7,7 @@ subroutine loop_transformation_construct1 !ERROR: OpenMP loop construct cannot apply to a fully unrolled loop !$omp do - !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct + !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct !$omp unroll end subroutine @@ -51,7 +51,7 @@ subroutine loop_transformation_construct4 do x = 1, i v(x) = v(x) * 2 end do - !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct + !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct !ERROR: At least one of SIZES clause must appear on the TILE directive !$omp tile end subroutine diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90 index 1b15c938915cd..25247c3896cae 100644 --- a/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90 +++ b/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90 @@ -7,7 +7,7 @@ subroutine loop_transformation_construct1 implicit none !$omp do - !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct + !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct !$omp fuse end subroutine @@ -15,7 +15,7 @@ subroutine loop_transformation_construct2 implicit none !$omp do - !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct + !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct !$omp fuse !$omp end fuse end subroutine @@ -50,7 +50,7 @@ subroutine loop_transformation_construct4 do x = 1, i v(x) = v(x) * 2 end do - !ERROR: OpenMP loop construct should contain a DO-loop or a loop-nest-generating OpenMP construct + !ERROR: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct !$omp fuse !$omp end fuse end subroutine @@ -80,7 +80,7 @@ subroutine loop_transformation_construct6 integer :: x integer :: v(i) - !ERROR: The loop sequence following the DO construct must be fully fused first. + !ERROR: This construct applies to a loop nest, but has a loop sequence of length 2 !$omp do !$omp fuse looprange(1,1) !$omp unroll partial(2) diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct04.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct04.f90 index 2856247329f3b..158b030906e07 100644 --- a/flang/test/Semantics/OpenMP/loop-transformation-construct04.f90 +++ b/flang/test/Semantics/OpenMP/loop-transformation-construct04.f90 @@ -8,7 +8,7 @@ subroutine loop_transformation_construct3 integer :: x integer :: v(i) - !ERROR: The loop sequence following the DO construct must be fully fused first. + !ERROR: This construct applies to a loop nest, but has a loop sequence of length 2 !$omp do !$omp fuse looprange(1,2) do x = 1, i @@ -30,7 +30,7 @@ subroutine loop_transformation_construct4 integer :: x integer :: v(i) - !ERROR: The loop sequence following the TILE construct must be fully fused first. + !ERROR: This construct applies to a loop nest, but has a loop sequence of length 2 !$omp tile sizes(2) !$omp fuse looprange(1,2) do x = 1, i _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
