From 28dcc4fbc1c92250e106ba98ac429b95e713b9b1 Mon Sep 17 00:00:00 2001 From: Nicolas Vasilache Date: Mon, 16 Jul 2018 08:34:06 -0700 Subject: [PATCH 1/3] [DoNotMerge] Create temporary Halide_Experimental building script This PR temporarily introduces a Halide hack to make progress. After a quick exchange with @abadams this seems unnecessary as the use case for sequential dependencies probably only requires an RDom instead of a Var for the `t` for-loop index. Punting for now. --- .circleci/config.yml | 2 +- .jenkins/build.sh | 2 +- conda_recipes/conda_build_tc.sh | 110 +++++++++++--------- conda_recipes/halide/build.sh | 10 +- conda_recipes/halide_experimental/build.sh | 71 +++++++++++++ conda_recipes/halide_experimental/meta.yaml | 34 ++++++ 6 files changed, 179 insertions(+), 50 deletions(-) create mode 100644 conda_recipes/halide_experimental/build.sh create mode 100644 conda_recipes/halide_experimental/meta.yaml diff --git a/.circleci/config.yml b/.circleci/config.yml index ae07bae7c..1c1e64540 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,7 +13,7 @@ jobs: command: | . /opt/conda/anaconda/bin/activate source activate tc_build - conda install -y -c nicolasvasilache llvm-trunk halide + conda install -y -c nicolasvasilache llvm-trunk halide_experimental - run: name: check_formatting diff --git a/.jenkins/build.sh b/.jenkins/build.sh index 1aeba7681..8c13b06ac 100755 --- a/.jenkins/build.sh +++ b/.jenkins/build.sh @@ -60,7 +60,7 @@ cd /var/lib/jenkins/workspace git submodule update --init --recursive source activate tc_build -conda install -y -c nicolasvasilache llvm-trunk halide +conda install -y -c nicolasvasilache llvm-trunk halide_experimental conda install -y -c conda-forge eigen conda install -y -c nicolasvasilache caffe2 diff --git a/conda_recipes/conda_build_tc.sh b/conda_recipes/conda_build_tc.sh index aeb682bef..2b9a82612 100755 --- a/conda_recipes/conda_build_tc.sh +++ b/conda_recipes/conda_build_tc.sh @@ -9,60 +9,76 @@ ANACONDA_USER=nicolasvasilache # set the anaconda upload to NO for now conda config --set anaconda_upload no -echo "Packaging Caffe2" - -############################################################################### -# Caffe2 settings -CAFFE2_BUILD_VERSION="1.0.0" -CAFFE2_BUILD_NUMBER=1 -PYTORCH_GIT_HASH="8d91a602cc2beab090715bb6bd63ab108db5fa36" - -echo "Packaging Caffe2 ==> CAFFE2_BUILD_VERSION: ${CAFFE2_BUILD_VERSION} CAFFE2_BUILD_NUMBER: ${CAFFE2_BUILD_NUMBER}" - -export CAFFE2_BUILD_VERSION=$CAFFE2_BUILD_VERSION -export CAFFE2_BUILD_NUMBER=$CAFFE2_BUILD_NUMBER -export PYTORCH_GIT_HASH=$CAFFE2PYTORCH_GIT_HASH - -time conda build -c $ANACONDA_USER --python 3.6 caffe2 - -echo "Caffe2 packaged Successfully" - +#echo "Packaging Caffe2" +# +################################################################################ +## Caffe2 settings +#CAFFE2_BUILD_VERSION="1.0.0" +#CAFFE2_BUILD_NUMBER=1 +#PYTORCH_GIT_HASH="8d91a602cc2beab090715bb6bd63ab108db5fa36" +# +#echo "Packaging Caffe2 ==> CAFFE2_BUILD_VERSION: ${CAFFE2_BUILD_VERSION} CAFFE2_BUILD_NUMBER: ${CAFFE2_BUILD_NUMBER}" +# +#export CAFFE2_BUILD_VERSION=$CAFFE2_BUILD_VERSION +#export CAFFE2_BUILD_NUMBER=$CAFFE2_BUILD_NUMBER +#export PYTORCH_GIT_HASH=$CAFFE2PYTORCH_GIT_HASH +# +#time conda build -c $ANACONDA_USER --python 3.6 caffe2 +# +#echo "Caffe2 packaged Successfully" +# +################################################################################ +## LLVM_TRUNK settings +#LLVM_TRUNK_BUILD_VERSION="1.0.0" +#LLVM_TRUNK_BUILD_NUMBER=1 +#LLVM_TRUNK_SOURCE_DIR=$(mktemp -d /tmp/d.XXXXXX) +#trap 'rm -rf "${LLVM_TRUNK_SOURCE_DIR}"' EXIT +# +#svn co http://llvm.org/svn/llvm-project/llvm/trunk ${LLVM_TRUNK_SOURCE_DIR} +#svn co http://llvm.org/svn/llvm-project/cfe/trunk ${LLVM_TRUNK_SOURCE_DIR}/tools/clang +# +#echo "Building llvm-trunk" +#echo "LLVM_TRUNK_BUILD_VERSION: $LLVM_TRUNK_BUILD_VERSION LLVM_TRUNK_BUILD_NUMBER: ${LLVM_TRUNK_BUILD_NUMBER}" +# +#export LLVM_TRUNK_BUILD_VERSION=$LLVM_TRUNK_BUILD_VERSION +#export LLVM_TRUNK_BUILD_NUMBER=$LLVM_TRUNK_BUILD_NUMBER +#export LLVM_TRUNK_SOURCE_DIR=$LLVM_TRUNK_SOURCE_DIR +# +#time conda build -c $ANACONDA_USER --python 3.6 llvm-trunk +# +#echo "llvm-trunk packaged Successfully" +# ############################################################################### -# LLVM_TRUNK settings -LLVM_TRUNK_BUILD_VERSION="1.0.0" -LLVM_TRUNK_BUILD_NUMBER=1 -LLVM_TRUNK_SOURCE_DIR=$(mktemp -d /tmp/d.XXXXXX) -trap 'rm -rf "${LLVM_TRUNK_SOURCE_DIR}"' EXIT - -svn co http://llvm.org/svn/llvm-project/llvm/trunk ${LLVM_TRUNK_SOURCE_DIR} -svn co http://llvm.org/svn/llvm-project/cfe/trunk ${LLVM_TRUNK_SOURCE_DIR}/tools/clang - -echo "Building llvm-trunk" -echo "LLVM_TRUNK_BUILD_VERSION: $LLVM_TRUNK_BUILD_VERSION LLVM_TRUNK_BUILD_NUMBER: ${LLVM_TRUNK_BUILD_NUMBER}" - -export LLVM_TRUNK_BUILD_VERSION=$LLVM_TRUNK_BUILD_VERSION -export LLVM_TRUNK_BUILD_NUMBER=$LLVM_TRUNK_BUILD_NUMBER -export LLVM_TRUNK_SOURCE_DIR=$LLVM_TRUNK_SOURCE_DIR - -time conda build -c $ANACONDA_USER --python 3.6 llvm-trunk - -echo "llvm-trunk packaged Successfully" +## Halide settings +#HALIDE_BUILD_VERSION="1.0.0" +#HALIDE_BUILD_NUMBER=1 +#HALIDE_GIT_HASH="0b29cacf636852933892bbaa61dd2050c8dcaff2" +# +#echo "Packaging HALIDE ==> HALIDE_BUILD_VERSION: ${HALIDE_BUILD_VERSION} HALIDE_BUILD_NUMBER: ${HALIDE_BUILD_NUMBER}" +# +#export HALIDE_BUILD_VERSION=$HALIDE_BUILD_VERSION +#export HALIDE_BUILD_NUMBER=$HALIDE_BUILD_NUMBER +#export HALIDE_GIT_HASH=$HALIDE_GIT_HASH +# +#time conda build -c $ANACONDA_USER --python 3.6 halide +# +#echo "HALIDE packaged Successfully" ############################################################################## -# Halide settings -HALIDE_BUILD_VERSION="1.0.0" -HALIDE_BUILD_NUMBER=1 -HALIDE_GIT_HASH="0b29cacf636852933892bbaa61dd2050c8dcaff2" +# Halide_Experimental settings +HALIDE_EXPERIMENTAL_BUILD_VERSION="1.0.0" +HALIDE_EXPERIMENTAL_BUILD_NUMBER=1 +HALIDE_EXPERIMENTAL_GIT_HASH="0c6f23c9c7b17a82718ffee69b6360483302d63a" -echo "Packaging HALIDE ==> HALIDE_BUILD_VERSION: ${HALIDE_BUILD_VERSION} HALIDE_BUILD_NUMBER: ${HALIDE_BUILD_NUMBER}" +echo "Packaging HALIDE_EXPERIMENTAL ==> HALIDE_EXPERIMENTAL_BUILD_VERSION: ${HALIDE_EXPERIMENTAL_BUILD_VERSION} HALIDE_EXPERIMENTAL_BUILD_NUMBER: ${HALIDE_EXPERIMENTAL_BUILD_NUMBER}" -export HALIDE_BUILD_VERSION=$HALIDE_BUILD_VERSION -export HALIDE_BUILD_NUMBER=$HALIDE_BUILD_NUMBER -export HALIDE_GIT_HASH=$HALIDE_GIT_HASH +export HALIDE_EXPERIMENTAL_BUILD_VERSION=$HALIDE_EXPERIMENTAL_BUILD_VERSION +export HALIDE_EXPERIMENTAL_BUILD_NUMBER=$HALIDE_EXPERIMENTAL_BUILD_NUMBER +export HALIDE_EXPERIMENTAL_GIT_HASH=$HALIDE_EXPERIMENTAL_GIT_HASH -time conda build -c $ANACONDA_USER --python 3.6 halide +time conda build -c $ANACONDA_USER --python 3.6 halide_experimental -echo "HALIDE packaged Successfully" +echo "HALIDE_EXPERIMENTAL packaged Successfully" ################################################################################ ## Tensor Comprehensions settings diff --git a/conda_recipes/halide/build.sh b/conda_recipes/halide/build.sh index da5dd1b0e..09b71ac77 100644 --- a/conda_recipes/halide/build.sh +++ b/conda_recipes/halide/build.sh @@ -52,9 +52,17 @@ LLVM_CONFIG=${LLVM_CONFIG} \ VERBOSE=${VERBOSE} \ PREFIX=${INSTALL_PREFIX} \ WITH_LLVM_INSIDE_SHARED_LIBHALIDE= \ +WITH_X86=1 \ +WITH_ARM= \ +WITH_HEXAGON= \ +WITH_MIPS= \ +WITH_AARCH64= \ +WITH_POWERPC= \ +WITH_PTX=1 \ +WITH_AMDGPU= \ WITH_OPENCL= \ -WITH_OPENGL= \ WITH_METAL= \ +WITH_OPENGL= \ WITH_EXCEPTIONS=1 \ make -f ../Makefile -j"$(nproc)" install || exit 1 mkdir -p ${INSTALL_PREFIX}/include/Halide diff --git a/conda_recipes/halide_experimental/build.sh b/conda_recipes/halide_experimental/build.sh new file mode 100644 index 000000000..ded935aa8 --- /dev/null +++ b/conda_recipes/halide_experimental/build.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash + +set -e + +# code gets checked out at below: +# $SOURCE_DIR ===> /conda-bld/halide_experimental_/work +# +# build directory gets created at $SOURCE_DIR/build +# +# CONDA environment for debugging: +# cd /conda-bld/halide_experimental_ +# source activate ./_h_env_...... # long placeholders +# +# $CONDA_PREFIX and $PREFIX are set to the same value i.e. the environment value +# +# Installation happens in the $PREFIX which is the environment and rpath is set +# to that +# +# For tests, a new environment _test_env_.... is created +# During the tests, you will see that the halide_experimental package gets checked out + +CMAKE_VERSION=${CMAKE_VERSION:="`which cmake3 || which cmake`"} +VERBOSE=${VERBOSE:=0} + +CC=${CC:="`which gcc`"} +CXX=${CXX:="`which g++`"} + +export INSTALL_PREFIX=$PREFIX +echo "CONDA_PREFIX: ${CONDA_PREFIX}" +echo "PREFIX: ${PREFIX}" + +export CLANG_PREFIX=$($PREFIX/bin/llvm-config --prefix) +echo "CLANG_PREFIX: ${CLANG_PREFIX}" + +echo "Building Halide_Experimental conda package" + +echo "Clean up existing build packages if any" +rm -rf build || true + +mkdir -p build +cd build + +echo "Configuring Halide_Experimental" + +LLVM_CONFIG_FROM_PREFIX=${CLANG_PREFIX}/bin/llvm-config +LLVM_CONFIG=$( which $LLVM_CONFIG_FROM_PREFIX || which llvm-config-4.0 || which llvm-config ) +CLANG_FROM_PREFIX=${CLANG_PREFIX}/bin/clang +CLANG=$( which $CLANG_FROM_PREFIX || which clang-4.0 || which clang ) + +CLANG=${CLANG} \ +LLVM_CONFIG=${LLVM_CONFIG} \ +VERBOSE=${VERBOSE} \ +PREFIX=${INSTALL_PREFIX} \ +WITH_LLVM_INSIDE_SHARED_LIBHALIDE= \ +WITH_X86=1 \ +WITH_ARM= \ +WITH_HEXAGON= \ +WITH_MIPS= \ +WITH_AARCH64= \ +WITH_POWERPC= \ +WITH_PTX=1 \ +WITH_AMDGPU= \ +WITH_OPENCL= \ +WITH_METAL= \ +WITH_OPENGL= \ +WITH_EXCEPTIONS=1 \ +make -f ../Makefile -j"$(nproc)" install || exit 1 +mkdir -p ${INSTALL_PREFIX}/include/Halide +mv ${INSTALL_PREFIX}/include/Halide*.h ${INSTALL_PREFIX}/include/Halide/ || exit 1 + +echo "Successfully built Halide_Experimental conda package" diff --git a/conda_recipes/halide_experimental/meta.yaml b/conda_recipes/halide_experimental/meta.yaml new file mode 100644 index 000000000..6adef34c4 --- /dev/null +++ b/conda_recipes/halide_experimental/meta.yaml @@ -0,0 +1,34 @@ +package: + name: halide_experimental + version: "{{ environ.get('HALIDE_EXPERIMENTAL_BUILD_VERSION') }}" + +source: + git_url: https://github.com/nicolasvasilache/Halide.git + git_rev: "{{ environ.get('HALIDE_EXPERIMENTAL_GIT_HASH') }}" + +requirements: + build: + - llvm-trunk==1.0.0 + - cmake + run: + - llvm-trunk==1.0.0 + - cmake + +build: + number: {{ environ.get('HALIDE_EXPERIMENTAL_BUILD_NUMBER') }} + skip: True # [win] + +test: + commands: + - test -f $PREFIX/lib/libHalide.so + - test -f $PREFIX/lib/libHalide.a + - test -d $PREFIX/include/Halide + +about: + home: http://halide-lang.org + license: MIT + summary: A language for image processing and computational photography + +extra: + recipe-maintainers: + - nicolasvasilache From b7359f4e31c6e771936e31b702dd3d507c096d4f Mon Sep 17 00:00:00 2001 From: Nicolas Vasilache Date: Mon, 16 Jul 2018 11:07:42 -0700 Subject: [PATCH 2/3] Adding experimental support for "for" loops in the language This commit adds a `for t in 0:T { ... }` syntax to the TC language. Because it introduces an extension to the grammar and type changes, modifications need to propagate all the way to `tc2halide`. Support for loops is only implemented in the parser and semantic checker. Emitting proper HalideIR and ScheduleTree will be implemented in follow-up commits. The commit should be reviewed in this order: 1. add support to the lexer 2. add the `For` compound type in `tree_views.h` 3. support `parseStmt` in the parser (in particular see how we `parseRangeConstraint` first and reuse its index) 4. perform semantic checks that guarantee only a single nested `For` looFor` is allowed (in parsee how we `checkRangeConstraint` after checking all comprehensions) 5. update `tc2halide` 6. add new tests This commit would crash if trying to emit HalideIR with `for` loops but in order to compile we still needs to update `tc2halide`. --- tc/core/tc2halide.cc | 6 +- tc/lang/lexer.h | 1 + tc/lang/parser.h | 21 +++++ tc/lang/sema.h | 95 ++++++++++++-------- tc/lang/tc_format.cc | 30 ++++++- tc/lang/test_expected/for1.expected | 79 +++++++++++++++++ tc/lang/test_expected/for2.expected | 131 ++++++++++++++++++++++++++++ tc/lang/test_expected/for3.expected | 55 ++++++++++++ tc/lang/tree_views.h | 34 +++++++- test/test_lang.cc | 34 ++++++++ 10 files changed, 445 insertions(+), 41 deletions(-) create mode 100644 tc/lang/test_expected/for1.expected create mode 100644 tc/lang/test_expected/for2.expected create mode 100644 tc/lang/test_expected/for3.expected diff --git a/tc/core/tc2halide.cc b/tc/core/tc2halide.cc index 379add821..ecde9aadd 100644 --- a/tc/core/tc2halide.cc +++ b/tc/core/tc2halide.cc @@ -765,7 +765,11 @@ HalideComponents translateDef(const lang::Def& def, bool throwWarnings) { } for (auto c : def.statements()) { translateComprehension( - c, components.params, throwWarnings, &funcs, &bounds); + lang::Comprehension(c), + components.params, + throwWarnings, + &funcs, + &bounds); } vector outputs; for (auto p : def.returns()) { diff --git a/tc/lang/lexer.h b/tc/lang/lexer.h index 2db8bdc79..82fc30742 100644 --- a/tc/lang/lexer.h +++ b/tc/lang/lexer.h @@ -40,6 +40,7 @@ namespace lang { _(TK_BOOL_VALUE, "bool_value", "") \ _(TK_MIN, "min", "min") \ _(TK_MAX, "max", "max") \ + _(TK_FOR, "for", "for") \ _(TK_WHERE, "where", "where") \ _(TK_DEF, "def", "def") \ _(TK_ARROW, "arrow", "->") \ diff --git a/tc/lang/parser.h b/tc/lang/parser.h index 4083771f7..5e08dd40c 100644 --- a/tc/lang/parser.h +++ b/tc/lang/parser.h @@ -225,6 +225,27 @@ struct Parser { } } TreeRef parseStmt() { + if (L.cur().kind == TK_FOR) { + auto r = L.cur().range; + L.expect(TK_FOR); + // parseRangeConstraint and reuse its ident allows us to write: + // "for t in A:B { ... }" + // instead of "for t where t in A:B { ... }" and + // ~~~~~~~~~~~~~~~~~~~~~~ + // WhereClause of 1 of 3 types + // instead of "for t t in A:B { ... }" and + // ~~~~~~~~~~~~~~~~ + // RangeConstraints with index duplication + auto rangeConstraint = parseRangeConstraint(); + auto index = RangeConstraint(rangeConstraint).ident(); + L.expect('{'); + TreeList stmts; + while (!L.nextIf('}')) { + stmts.push_back(parseStmt()); + } + auto stmts_list = List::create(r, std::move(stmts)); + return For::create(r, index, rangeConstraint, stmts_list); + } auto ident = parseIdent(); TreeRef list = parseOptionalIdentList(); auto assign = parseAssignment(); diff --git a/tc/lang/sema.h b/tc/lang/sema.h index 3426c0208..5606232b1 100644 --- a/tc/lang/sema.h +++ b/tc/lang/sema.h @@ -493,55 +493,82 @@ struct Sema { // Semantic checking for the statements/comprehensions in a TC Def. TreeRef checkStmt(TreeRef stmt_) { - auto stmt = Comprehension(stmt_); + if (stmt_->kind() == TK_COMPREHENSION) { + return checkComprehension(Comprehension(stmt_)); + } + return checkFor(For(stmt_)); + } + TreeRef checkFor(For f) { + if (lookup(f.index(), false)) { + throw ErrorReport(f) << "For loop index already defined"; + } + TreeList stmts; + for (auto s : f.statements()) { + if (s->kind() != TK_COMPREHENSION) { + throw ErrorReport(s) << "Nested \"for\" loops NYI"; + } + stmts.push_back(checkComprehension(Comprehension(s))); + } + // Check the range constraint after all statements + // This way we don't need extra state to track indices coming from loops + // that may have already been defined. + checkRangeConstraint(f.rangeConstraint()); + return For::create( + f.range(), + f.index(), + f.rangeConstraint(), + List::create(f.range(), std::move(stmts))); + } + + TreeRef checkComprehension(Comprehension comp) { // register index variables (non-reductions) - for (const auto& index : stmt.indices()) { + for (const auto& index : comp.indices()) { std::string idx = index.name(); auto typ = indexType(index); insert(index_env, index, typ, true); } // check that the input is not used for output - inputs are immutable - std::string name = stmt.ident().name(); + std::string name = comp.ident().name(); if (inputParameters.count(name) > 0) { - throw ErrorReport(stmt_) << "TC inputs are immutable"; + throw ErrorReport(comp) << "TC inputs are immutable"; } // make dimension variables for each dimension of the output tensor TreeList output_indices; - int n = stmt.indices().size(); + int n = comp.indices().size(); for (int i = 0; i < n; ++i) { auto new_var = - Ident::create(stmt.range(), name + "." + std::to_string(i)); + Ident::create(comp.range(), name + "." + std::to_string(i)); output_indices.push_back(new_var); } // where clauses are checked _before_ the rhs because they // introduce let bindings that are in scope for the rhs - auto where_clauses_ = stmt.whereClauses().map( + auto where_clauses_ = comp.whereClauses().map( [&](TreeRef rc) { return checkWhereClause(rc); }); - TreeRef rhs_ = checkExp(stmt.rhs(), true); + TreeRef rhs_ = checkExp(comp.rhs(), true); TreeRef scalar_type = typeOfExpr(rhs_); // if this statement will be returned and it is annotated in the return list // with a type (e.g. float(A,B)) then force the tensor to be that type // and check that the number of dimensions are consistent - auto output_annotation = annotated_output_types.find(stmt.ident().name()); + auto output_annotation = annotated_output_types.find(comp.ident().name()); if (output_annotation != annotated_output_types.end()) { auto tt = TensorType(output_annotation->second); auto matched_type = match_types(scalar_type, tt.scalarTypeTree()); if (tt.scalarTypeTree()->kind() != matched_type->kind()) { - throw ErrorReport(stmt) + throw ErrorReport(comp) << " attempting to assign type " << kindToString(scalar_type->kind()) << " to narrower type " << kindToString(tt.scalarTypeTree()->kind()) << " without an explicit cast"; } - if (tt.dims().size() != stmt.indices().size()) { - throw ErrorReport(stmt) - << " tensor defined with " << stmt.indices().size() + if (tt.dims().size() != comp.indices().size()) { + throw ErrorReport(comp) + << " tensor defined with " << comp.indices().size() << " dimensions but declared as an output with " << tt.dims().size() << " dimensions."; } @@ -550,11 +577,11 @@ struct Sema { // After checking rhs and before creating lhs, we check if it is a reduction // without initialization (i.e., reduction operator without "!" suffix, and // lhs not defined previously). - if (isUninitializedReductionOperation(stmt.assignment()) && - nullptr == lookup(stmt.ident(), false)) { - ErrorReport err(stmt); - std::string tk = kindToToken(stmt.assignment()->kind()); - err << "Reduction without initialization. If " << stmt.ident().name() + if (isUninitializedReductionOperation(comp.assignment()) && + nullptr == lookup(comp.ident(), false)) { + ErrorReport err(comp); + std::string tk = kindToToken(comp.assignment()->kind()); + err << "Reduction without initialization. If " << comp.ident().name() << " is not pre-initialized before calling the TC function," << " consider using the !-suffixed reduction operator " << tk << "! instead of " << tk; @@ -562,21 +589,21 @@ struct Sema { } auto type = TensorType::create( - stmt.range(), + comp.range(), scalar_type, - List::create(stmt.range(), std::move(output_indices))); - insert(env, stmt.ident(), type, false); + List::create(comp.range(), std::move(output_indices))); + insert(env, comp.ident(), type, false); // if we redefined an input, it is no longer valid for range expressions - live_input_names.erase(stmt.ident().name()); + live_input_names.erase(comp.ident().name()); - auto equivalent_statement_ = stmt.equivalent().map([&](Equivalent eq) { + auto equivalent_statement_ = comp.equivalent().map([&](Equivalent eq) { auto indices_ = eq.accesses().map( [&](TreeRef index) { return checkExp(index, true); }); return Equivalent::create(eq.range(), eq.name(), indices_); }); - TreeRef assignment = stmt.assignment(); + TreeRef assignment = comp.assignment(); // For semantic consistency we allow overwriting reductions like +=! // to be used in the language when there are no actual reduction dimensions. // Later compile stages assume that there is at least one reduction @@ -586,26 +613,26 @@ struct Sema { assignment = Compound::create('=', assignment->range(), {}); } - if (reduction_variables.size() > 0 && stmt.assignment()->kind() == '=') { - throw ErrorReport(stmt) << "this statement includes reduction variable '" + if (reduction_variables.size() > 0 && comp.assignment()->kind() == '=') { + throw ErrorReport(comp) << "this statement includes reduction variable '" << Ident(reduction_variables.back()).name() << "' but does not specify a reduction."; } TreeRef reduction_variable_list = - List::create(stmt.ident().range(), std::move(reduction_variables)); + List::create(comp.ident().range(), std::move(reduction_variables)); TreeRef result = Comprehension::create( - stmt.range(), - stmt.ident(), - stmt.indices(), - stmt.assignment(), + comp.range(), + comp.ident(), + comp.indices(), + comp.assignment(), rhs_, where_clauses_, equivalent_statement_, reduction_variable_list); - if (nonTemporaries.count(stmt.ident().name()) == 0) { - throw ErrorReport(stmt) - << stmt.ident().name() + if (nonTemporaries.count(comp.ident().name()) == 0) { + throw ErrorReport(comp) + << comp.ident().name() << " is not listed as an input or output to this function. Temporaries tensors are not yet implemented"; } diff --git a/tc/lang/tc_format.cc b/tc/lang/tc_format.cc index 8f1fbe8f1..56a74b21b 100644 --- a/tc/lang/tc_format.cc +++ b/tc/lang/tc_format.cc @@ -21,6 +21,7 @@ namespace lang { namespace { void showExpr(std::ostream& s, const TreeRef& expr); +void showStmt(std::ostream& s, const TreeRef& stmt); template void show(std::ostream& s, T x) { @@ -59,6 +60,16 @@ std::ostream& operator<<(std::ostream& s, const Param& p) { return s << p.ident(); } +std::ostream& operator<<(std::ostream& s, const For& f) { + s << "for " << f.index() << " in " << f.range().start() << ":" + << f.range().end() << " {"; + for (const TreeRef& stmt : f.statements()) { + showStmt(s, stmt); + } + s << "}"; + return s; +} + std::ostream& operator<<(std::ostream& s, const Comprehension& comp) { s << comp.ident() << "(" << comp.indices() << ") " << kindToToken(comp.assignment()->kind()) << " "; @@ -71,6 +82,21 @@ std::ostream& operator<<(std::ostream& s, const Comprehension& comp) { return s; } +void showStmt(std::ostream& s, const TreeRef& stmt) { + switch (stmt->kind()) { + case TK_FOR: + s << " " << For(stmt) << "\n"; + break; + case TK_COMPREHENSION: + s << " " << Comprehension(stmt) << "\n"; + break; + default: + std::stringstream ss; + ss << "Incorrect statement kind: " << stmt->kind(); + throw std::runtime_error(ss.str()); + } +} + void showExpr(std::ostream& s, const TreeRef& expr) { switch (expr->kind()) { case TK_IDENT: { @@ -174,8 +200,8 @@ void tcFormat(std::ostream& s, TreeRef _def) { Def def{_def}; s << "def " << def.name() << "(" << def.params() << ")" << " -> (" << def.returns() << ") {\n"; - for (const Comprehension& c : def.statements()) { - s << " " << c << "\n"; + for (const TreeRef& stmt : def.statements()) { + showStmt(s, stmt); } s << "}"; } diff --git a/tc/lang/test_expected/for1.expected b/tc/lang/test_expected/for1.expected new file mode 100644 index 000000000..3db6116b2 --- /dev/null +++ b/tc/lang/test_expected/for1.expected @@ -0,0 +1,79 @@ +(def + (ident fun) + (list + (param + (ident X) + (tensor_type + (float) + (list (ident M) (ident N)))) + (param + (ident Meta) + (tensor_type + (float) + (list (ident T))))) + (list + (param (ident R1) (inferred)) + (param (ident R2) (inferred))) + (list + (for + (ident t) + (range_constraint + (ident t) + (const 0 (int32)) + (ident T)) + (list + (comprehension + (ident R1) + (list + (ident t) + (ident m) + (ident n)) + (=) + (? + (eq + (ident t) + (const 0 (int32))) + (access + (ident X) + (list + (ident m) + (ident n))) + (const 0 (float))) + (list) + (option) + (list)) + (comprehension + (ident R2) + (list + (ident t) + (ident m) + (ident n)) + (=) + (access + (ident R1) + (list + (ident t) + (ident m) + (ident n))) + (list) + (option) + (list)))))) +M: (int32) +Meta: (tensor_type (float) (list (ident T))) +N: (int32) +R1: (tensor_type + (float) + (list + (ident R1.0) + (ident R1.1) + (ident R1.2))) +R2: (tensor_type + (float) + (list + (ident R2.0) + (ident R2.1) + (ident R2.2))) +T: (int32) +X: (tensor_type + (float) + (list (ident M) (ident N))) diff --git a/tc/lang/test_expected/for2.expected b/tc/lang/test_expected/for2.expected new file mode 100644 index 000000000..7500df811 --- /dev/null +++ b/tc/lang/test_expected/for2.expected @@ -0,0 +1,131 @@ +(def + (ident fun) + (list + (param + (ident X) + (tensor_type + (float) + (list (ident M) (ident N)))) + (param + (ident Meta) + (tensor_type + (float) + (list (ident T))))) + (list + (param (ident R1) (inferred)) + (param (ident R2) (inferred))) + (list + (comprehension + (ident R1) + (list + (ident t) + (ident m) + (ident n)) + (=) + (? + (eq + (ident t) + (const 0 (int32))) + (access + (ident X) + (list (ident m) (ident n))) + (const 0 (float))) + (list + (range_constraint + (ident t) + (const 0 (int32)) + (ident T))) + (option) + (list)) + (comprehension + (ident R2) + (list + (ident t) + (ident m) + (ident n)) + (=) + (const 0 (float)) + (list + (range_constraint + (ident t) + (const 0 (int32)) + (ident T)) + (range_constraint + (ident m) + (const 0 (int32)) + (ident M)) + (range_constraint + (ident n) + (const 0 (int32)) + (ident N))) + (option) + (list)) + (for + (ident t) + (range_constraint + (ident t) + (const 0 (int32)) + (ident T)) + (list + (comprehension + (ident R1) + (list + (ident t) + (ident m) + (ident n)) + (plus_eq) + (? + (eq + (ident t) + (const 0 (int32))) + (access + (ident X) + (list + (ident m) + (ident n))) + (access + (ident R1) + (list + (- + (ident t) + (const 1 (int32))) + (ident m) + (ident n)))) + (list) + (option) + (list)) + (comprehension + (ident R2) + (list + (ident t) + (ident m) + (ident n)) + (plus_eq) + (access + (ident R1) + (list + (ident t) + (ident m) + (ident n))) + (list) + (option) + (list)))))) +M: (int32) +Meta: (tensor_type (float) (list (ident T))) +N: (int32) +R1: (tensor_type + (float) + (list + (ident R1.0) + (ident R1.1) + (ident R1.2))) +R2: (tensor_type + (float) + (list + (ident R2.0) + (ident R2.1) + (ident R2.2))) +T: (int32) +X: (tensor_type + (float) + (list (ident M) (ident N))) diff --git a/tc/lang/test_expected/for3.expected b/tc/lang/test_expected/for3.expected new file mode 100644 index 000000000..ad7fda16f --- /dev/null +++ b/tc/lang/test_expected/for3.expected @@ -0,0 +1,55 @@ +(def + (ident fun) + (list + (param + (ident X) + (tensor_type + (float) + (list (ident M) (ident N)))) + (param + (ident Meta) + (tensor_type + (float) + (list (ident T))))) + (list (param (ident R1) (inferred))) + (list + (for + (ident t) + (range_constraint + (ident t) + (const 0 (int32)) + (const 123 (int32))) + (list + (comprehension + (ident R1) + (list + (ident t) + (ident m) + (ident n)) + (=) + (? + (eq + (ident t) + (const 0 (int32))) + (access + (ident X) + (list + (ident m) + (ident n))) + (const 0 (float))) + (list) + (option) + (list)))))) +M: (int32) +Meta: (tensor_type (float) (list (ident T))) +N: (int32) +R1: (tensor_type + (float) + (list + (ident R1.0) + (ident R1.1) + (ident R1.2))) +T: (int32) +X: (tensor_type + (float) + (list (ident M) (ident N))) diff --git a/tc/lang/tree_views.h b/tc/lang/tree_views.h index 1e26b8437..0052bcb09 100644 --- a/tc/lang/tree_views.h +++ b/tc/lang/tree_views.h @@ -36,8 +36,11 @@ namespace lang { // // Def = Def(Ident name, List params, List returns, List body) TK_DEF // -// -- NB: reduction_variables are only filled during semantic analysis -// Stmt = Comprehension(Ident lhs_ident, List lhs_indices, TK_COMPREHENSION +// Stmt = For(Ident iter, TK_FOR +// RangeConstraint range_constraints, +// List statements) +// -- NB: reduction_variables are only filled during semantic analysis +// | Comprehension(Ident lhs_ident, List lhs_indices, TK_COMPREHENSION // AssignKind assignment, Expr rhs, // List range_constraints, // Option eqiuvalent_stmt, @@ -354,6 +357,29 @@ struct RangeConstraint : public TreeView { } }; +struct For : public TreeView { + explicit For(const TreeRef& tree) : TreeView(tree) { + tree_->expect(TK_FOR, 3); + } + static TreeRef create( + const SourceRange& range, + TreeRef index, + TreeRef range_constraints, + TreeRef stmt_list) { + return Compound::create( + TK_FOR, range, {index, range_constraints, stmt_list}); + } + Ident index() const { + return Ident(subtree(0)); + } + RangeConstraint rangeConstraint() const { + return RangeConstraint(subtree(1)); + } + ListView statements() const { + return ListView(subtree(2)); + } +}; + struct Comprehension : public TreeView { explicit Comprehension(const TreeRef& tree) : TreeView(tree) { tree_->expect(TK_COMPREHENSION, 7); @@ -424,8 +450,8 @@ struct Def : public TreeView { ListView returns() const { return ListView(subtree(2)); } - ListView statements() const { - return ListView(subtree(3)); + ListView statements() const { + return ListView(subtree(3)); } static TreeRef create( const SourceRange& range, diff --git a/test/test_lang.cc b/test/test_lang.cc index c1c2b1902..2922cc2e2 100644 --- a/test/test_lang.cc +++ b/test/test_lang.cc @@ -398,6 +398,40 @@ int main(int argc, char** argv) { )"; ASSERT(lang::canonicalTc(option_one) == lang::canonicalTc(option_two)); + assertSemaEqual( + "for1.expected", + R"( + def fun(float(M, N) X, float(T) Meta) -> (R1, R2) { + for t in 0:T { + R1(t, m, n) = (t == 0) ? X(m, n) : 0.0 + R2(t, m, n) = R1(t, m, n) + } + } + )"); + + assertSemaEqual( + "for2.expected", + R"( + def fun(float(M, N) X, float(T) Meta) -> (R1, R2) { + R1(t, m, n) = (t == 0) ? X(m, n) : 0.0 where t in 0:T + R2(t, m, n) = 0.0 where t in 0:T, m in 0:M, n in 0:N + for t in 0:T { + R1(t, m, n) += (t == 0) ? X(m, n) : R1(t-1, m, n) + R2(t, m, n) += R1(t, m, n) + } + } + )"); + + assertSemaEqual( + "for3.expected", + R"( + def fun(float(M, N) X, float(T) Meta) -> (R1) { + for t in 0:123 { + R1(t, m, n) = (t == 0) ? X(m, n) : 0.0 + } + } + )"); + testTcFormat(); // assertSemaEqual( From 1f601106f91f312a0333308838654c4fb4d14e89 Mon Sep 17 00:00:00 2001 From: Nicolas Vasilache Date: Mon, 16 Jul 2018 15:13:44 -0700 Subject: [PATCH 3/3] [Broken][WIP] Add Halide support for "for" loops Disclaimer: this is WIP and does not work yet. In particular the ScheduleTree generated is incorrect (not yet properly scoped under for loop). Additionally, the example `For4InvalidHalide` seems to hit deeper structural issues of the high-level HalideIR that traditional polyhedral dependence analysis has no issue with (cc @abadams). This commit tests that the for-loop language construct properly propagates to the Halide bounds inference. This commit also explicitly adds checks that only a single RangeConstraint s`traiindex in min:max`) is used for each index. This requirement is added because it would be very surprising to mix explicit ranges coming from for loops with where clauses in a comprehension. In particular, the current behavior in Halide inference is to only keep the last RangeConstraint which systematically discards the information specified in the loop. `tc2halide` even says: ```// TODO: What if subsequent updates have incompatible bounds // (e.g. an in-place stencil)?. The .bound directive will use the // bounds of the last stage for all stages. ``` As a consequence we add an explicit check that multiple bounds specifications may not coexist. --- tc/core/tc2halide.cc | 92 +++++++++++++++++++++++++++++++----------- test/test_inference.cc | 92 +++++++++++++++++++++++++++++++++++++++++- test/test_tc2halide.cc | 53 ++++++++++++++++++++++++ 3 files changed, 211 insertions(+), 26 deletions(-) diff --git a/tc/core/tc2halide.cc b/tc/core/tc2halide.cc index ecde9aadd..8ac66331a 100644 --- a/tc/core/tc2halide.cc +++ b/tc/core/tc2halide.cc @@ -33,6 +33,21 @@ using std::vector; namespace { +using FunctionBounds = map, Function::Compare>; + +struct TranslationUnit { + HalideComponents components; + Scope enclosingLoopIndices; + map funcs; + FunctionBounds bounds; + bool throwWarnings; +}; + +void translateFor(const lang::For& f, TranslationUnit* tu); +void translateComprehension( + const lang::Comprehension& comprehension, + TranslationUnit* tu); + Type translateScalarType(int tcType) { switch (tcType) { case lang::TK_BOOL: @@ -264,8 +279,6 @@ vector unboundVariables(const vector& lhs, Expr rhs) { return finder.result; } -typedef map, Function::Compare> FunctionBounds; - void forwardBoundsInference( const std::vector& exprs, const FunctionBounds& bounds, @@ -500,6 +513,29 @@ Expr reductionUpdate(Expr e) { return Call::make(e.type(), kReductionUpdate, {e}, Call::Intrinsic); } +void translateStatement(const lang::TreeRef& stmt, TranslationUnit* tu) { + if (stmt->kind() == lang::TK_COMPREHENSION) { + translateComprehension(lang::Comprehension(stmt), tu); + } else { + CHECK_EQ(stmt->kind(), lang::TK_FOR); + translateFor(lang::For(stmt), tu); + } +} + +void translateFor(const lang::For& f, TranslationUnit* pTU) { + const map& params = pTU->components.params; + auto constraint = lang::RangeConstraint(f.rangeConstraint()); + Interval i; + const map lets; + i.min = translateExpr(constraint.start(), params, pTU->funcs, lets); + i.max = translateExpr(constraint.end(), params, pTU->funcs, lets) - 1; + pTU->enclosingLoopIndices.push(f.index().name(), i); + for (auto stm : f.statements()) { + translateStatement(stm, pTU); + } + pTU->enclosingLoopIndices.pop(f.index().name()); +} + // Translate a single TC comprehension/statement to Halide components: funcs, // bounds, reductions. // @@ -508,10 +544,11 @@ Expr reductionUpdate(Expr e) { // in order to be able to apply internal Halide analysis passes on them. void translateComprehension( const lang::Comprehension& comprehension, - const map& params, - bool throwWarnings, - map* funcs, - FunctionBounds* bounds) { + TranslationUnit* pTU) { + const map& params = pTU->components.params; + bool throwWarnings = pTU->throwWarnings; + map* funcs = &pTU->funcs; + FunctionBounds* bounds = &pTU->bounds; Function f; auto it = funcs->find(comprehension.ident().name()); if (it != funcs->end()) { @@ -647,6 +684,12 @@ void translateComprehension( // demand). Scope solution; + // Copy information from enclosing "for" loops + for (auto entry = pTU->enclosingLoopIndices.cbegin(); + entry != pTU->enclosingLoopIndices.cend(); + ++entry) { + solution.push(entry.name(), entry.value()); + } // Put anything explicitly specified with a 'where' class in the solution for (auto constraint_ : comprehension.whereClauses()) { if (constraint_->kind() != lang::TK_RANGE_CONSTRAINT) @@ -656,6 +699,11 @@ void translateComprehension( i.min = translateExpr(constraint.start(), params, *funcs, lets); i.max = translateExpr(constraint.end(), params, *funcs, lets) - 1; + if (solution.contains(constraint.ident().name())) { + throw lang::ErrorReport(constraint_) + << "Multiple range constraints per index NYI"; + } + // TODO: In the future we'll want to make any non-trivial bounds // into hidden scalar parameters, and just pass variables to the // polyhedral layer instead of potentially complex @@ -755,25 +803,20 @@ void translateComprehension( // Translate a semantically checked TC def to HalideComponents struct. HalideComponents translateDef(const lang::Def& def, bool throwWarnings) { - map funcs; - HalideComponents components; - components.def = def; - FunctionBounds bounds; + TranslationUnit tu; + tu.components.def = def; + tu.throwWarnings = throwWarnings; for (auto p : def.params()) { - translateParam(p, &components.params, &components.inputs); + translateParam(p, &tu.components.params, &tu.components.inputs); } - for (auto c : def.statements()) { - translateComprehension( - lang::Comprehension(c), - components.params, - throwWarnings, - &funcs, - &bounds); + // Semantically valid TCs include at most one outer sequential loop for now + for (auto stm : def.statements()) { + translateStatement(stm, &tu); } vector outputs; for (auto p : def.returns()) { - translateOutput(p, funcs, &outputs); + translateOutput(p, tu.funcs, &outputs); } // Now apply an extremely simplified version of Halide lowering @@ -804,11 +847,12 @@ HalideComponents translateDef(const lang::Def& def, bool throwWarnings) { // used in the pipelines we construct here, so just make a host target. Target target("host"); Stmt s = schedule_functions(outputs, fused_groups, env, target, any_memoized); + LOG_IF(ERROR, tc::FLAGS_debug_halide) << s; // we insert these to allow for inplace mutation of in/out tensors s = remove_undef(s); // Apply forward bounds inference results. This replaces the usual Halide // bounds inference. - for (auto p : bounds) { + for (auto p : tu.bounds) { const Function& f = p.first; for (auto b : p.second) { const string& var = b.first; @@ -893,20 +937,20 @@ HalideComponents translateDef(const lang::Def& def, bool throwWarnings) { }; s = SubstituteAllLets().mutate(s); - components.stmt = s; + tu.components.stmt = s; for (Function f : outputs) { OutputImageParam o = Func(f).output_buffers()[0]; // Apply forward bounds inference results to the output buffers. - const auto& b = bounds[f]; + const auto& b = tu.bounds[f]; for (int i = 0; i < o.dimensions(); i++) { const Interval& bound = b.at(f.args()[i]); o.dim(i).set_bounds(bound.min, simplify(bound.max - bound.min + 1)); } - components.outputs.push_back(o); + tu.components.outputs.push_back(o); } - return components; + return tu.components; } } // namespace diff --git a/test/test_inference.cc b/test/test_inference.cc index 004df7143..87701bd27 100644 --- a/test/test_inference.cc +++ b/test/test_inference.cc @@ -24,9 +24,9 @@ using namespace std; using namespace lang; struct InferenceTest : public ::testing::Test { - void Check(const string& tc, const string& expected) { + void Check(const string& tc, const string& expected, bool warn = true) { auto halideComponents = - tc2halide::translate(isl::with_exceptions::globalIslCtx(), tc, true); + tc2halide::translate(isl::with_exceptions::globalIslCtx(), tc, warn); stringstream ss; // Ordered map for repro @@ -529,6 +529,94 @@ def fun(float(N) size) -> (O) { EXPECT_THROW(Check(tc, {}), ::lang::ErrorReport); } +TEST_F(InferenceTest, For1) { + string tc = R"TC( + def fun(float(M, N) X) -> (R1) { + for t in 0:123 { + R1(t, m, n) = (t == 0) ? X(m, n) : 0.0 + } + } +)TC"; + Check(tc, R"HALIDE(mins: +X@[0; 0; ] +R1@[0; 0; 0; ] +extents: +X@[M; N; ] +R1@[123; M; N; ] +)HALIDE"); +} + +TEST_F(InferenceTest, For2) { + string tc = R"TC( + def fun(float(M, N) X, float(T) Meta) -> (R1, R2) { + for t in 0:T { + R1(t, m, n) = (t == 0) ? X(m, n) : 0.0 + R2(t, m, n) = R1(t, m, n) + } + } +)TC"; + Check( + tc, + R"HALIDE(mins: +X@[0; 0; ] +Meta@[0; ] +R1@[0; 0; 0; ] +R2@[0; 0; 0; ] +extents: +X@[M; N; ] +Meta@[T; ] +R1@[T; M; N; ] +R2@[T; M; N; ] +)HALIDE"); +} + +TEST_F(InferenceTest, For3) { + string tc = R"TC( + def fun(float(M, N) X, float(T) Meta) -> (R1, R2) { + R1(t, m, n) = (t == 0) ? X(m, n) : 0.0 where t in 0:T + R2(t, m, n) = 0.0 where t in 0:T, m in 0:M, n in 0:N + for t in 1:T { + R1(t, m, n) += R1(t-1, m, n) + R2(t, m, n) += R1(t, m, n) + } + } +)TC"; + Check( + tc, + R"HALIDE(mins: +X@[0; 0; ] +Meta@[0; ] +R1@[1; 0; 0; ] +R2@[1; 0; 0; ] +extents: +X@[M; N; ] +Meta@[T; ] +R1@[(T + -1); M; N; ] +R2@[(T + -1); M; N; ] +)HALIDE", + false); // nowarn +} + +TEST_F(InferenceTest, MultipleRangeConstraints) { + string tc = R"TC( +def fun(float(N, M) I) -> (O) { + O(i, j) = I(i, j) where i in 3:N-1, i in 1:N +} +)TC"; + EXPECT_THROW(Check(tc, {}), ::lang::ErrorReport); +} + +TEST_F(InferenceTest, MultipleRangeConstraintsFor) { + string tc = R"TC( + def fun(float(M, N) X) -> (R1) { + for t in 0:123 { + R1(t, m, n) = (t == 0) ? X(m, n) : 0.0 where t in 0:N + } + } +)TC"; + EXPECT_THROW(Check(tc, {}), ::lang::ErrorReport); +} + int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); ::gflags::ParseCommandLineFlags(&argc, &argv, true); diff --git a/test/test_tc2halide.cc b/test/test_tc2halide.cc index 12dfdf7b8..fec890fc5 100644 --- a/test/test_tc2halide.cc +++ b/test/test_tc2halide.cc @@ -38,6 +38,8 @@ struct TC2Isl : public ::testing::Test { isl::with_exceptions::globalIslCtx(), halide); auto scheduleHalide = scop->scheduleRoot(); + LOG_IF(ERROR, FLAGS_debug_tc_mapper) << *scheduleHalide; + polyhedral::detail::validateSchedule(scheduleHalide); } }; @@ -198,6 +200,57 @@ def foo(float(N) A) -> (B) { EXPECT_THROW(Check(tc), ::lang::ErrorReport); } +TEST_F(TC2Isl, For1) { + string tc = R"TC( + def fun(float(M, N) X) -> (R1) { + for t in 0:123 { + R1(t, m, n) = (t == 0) ? X(m, n) : 0.0 + } + } +)TC"; + Check(tc); +} + +TEST_F(TC2Isl, For2) { + string tc = R"TC( + def fun(float(M, N) X, float(T) Meta) -> (R1, R2) { + for t in 0:T { + R1(t, m, n) = (t == 0) ? X(m, n) : 0.0 + R2(t, m, n) = R1(t, m, n) + } + } +)TC"; + Check(tc); +} + +TEST_F(TC2Isl, For3) { + string tc = R"TC( + def fun(float(M, N) X, float(T) Meta) -> (R1, R2) { + R1(t, m, n) = (t == 0) ? X(m, n) : 0.0 where t in 0:T + R2(t, m, n) = 0.0 where t in 0:T, m in 0:M, n in 0:N + for t in 1:T { + R1(t, m, n) += R1(t-1, m, n) + R2(t, m, n) += R1(t, m, n) + } + } +)TC"; + Check(tc); +} + +TEST_F(TC2Isl, For4InvalidHalide) { + string tc = R"TC( + def fun(float(M, N) X, float(T) Meta) -> (R1, R2) { + R1(t, m, n) = (t == 0) ? X(m, n) : 0.0 where t in 0:T + R2(t, m, n) = 0.0 where t in 0:T, m in 0:M, n in 0:N + for t in 1:T { + R1(t, m, n) += R1(t-1, m, n) + R2(t-1, m, n) + R2(t, m, n) += R1(t, m, n) + } + } +)TC"; + EXPECT_THROW(Check(tc), std::exception); +} + TEST_F(TC2Isl, Types) { for (auto type : {"bool", "uint8",