diff options
Diffstat (limited to 'compiler/enco/core')
106 files changed, 10670 insertions, 0 deletions
diff --git a/compiler/enco/core/CMakeLists.txt b/compiler/enco/core/CMakeLists.txt new file mode 100644 index 000000000..f437e687a --- /dev/null +++ b/compiler/enco/core/CMakeLists.txt @@ -0,0 +1,35 @@ +file(GLOB_RECURSE SOURCES "src/*.cpp") +file(GLOB_RECURSE TESTS "src/*.test.cpp") +list(REMOVE_ITEM SOURCES ${TESTS}) + +### +### enco_core is built as a shared library to support "interactive debugging". +### +### interactive debugging helpers are stripped during linking when enco_core is +### built as a static library +### +add_library(enco_core SHARED ${SOURCES}) +target_include_directories(enco_core PRIVATE src) +target_include_directories(enco_core PUBLIC include) +target_link_libraries(enco_core PUBLIC enco_intf_cmdline) +target_link_libraries(enco_core PUBLIC coco_core) +target_link_libraries(enco_core PUBLIC coco_generic) +# These libraries are linked for internal use, and thus does not appear in public headers. +target_link_libraries(enco_core PRIVATE pp) +target_link_libraries(enco_core PRIVATE morph) +target_link_libraries(enco_core PRIVATE stdex) +# Let's use nncc project-wide build options +target_link_libraries(enco_core PRIVATE nncc_common) + +nnas_find_package(GTest QUIET) + +if(NOT GTest_FOUND) + return() +endif(NOT GTest_FOUND) + +add_executable(enco_core_test ${TESTS}) +target_include_directories(enco_core_test PRIVATE src) +target_link_libraries(enco_core_test gtest_main) +target_link_libraries(enco_core_test enco_core) +target_link_libraries(enco_core_test morph) +add_test(enco_core_test enco_core_test) diff --git a/compiler/enco/core/include/enco/Backend.h b/compiler/enco/core/include/enco/Backend.h new file mode 100644 index 000000000..5da903ed2 --- /dev/null +++ b/compiler/enco/core/include/enco/Backend.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ENCO_BACKEND_H__ +#define __ENCO_BACKEND_H__ + +#include "cmdline/View.h" + +#include "coco/IR/Module.h" +#include "coco/IR/Data.h" + +#include <memory> + +namespace enco +{ + +struct Backend +{ + virtual ~Backend() = default; + + virtual void compile(coco::Module *m, coco::Data *d) = 0; +}; + +} // namespace enco + +std::unique_ptr<enco::Backend> make_backend(const cmdline::View &); + +#endif // __ENCO_BACKEND_H__ diff --git a/compiler/enco/core/src/ANN/Binder.h b/compiler/enco/core/src/ANN/Binder.h new file mode 100644 index 000000000..71b95676b --- /dev/null +++ b/compiler/enco/core/src/ANN/Binder.h @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ANN_BINDER_H__ +#define __ANN_BINDER_H__ + +#include "ANN/IR/Module.h" + +#include <coco/IR.h> + +#include <morph/nnapi.h> + +#include <type_traits> + +/** + * @brief A bridge between ann::Module and coco::Block + */ +class ANNBinder +{ +public: + ANNBinder(coco::Block *block, std::unique_ptr<ann::Module> &&module) + : _block{block}, _module{std::move(module)} + { + // DO NOTHING + } + +public: + const coco::Block *block(void) const { return _block; } + coco::Block *block(void) { return _block; } + +public: + const ann::Module *module(void) const { return _module.get(); } + +public: + /** + * @brief Return the set of bags that the current ANN subnet accesses + */ + std::set<coco::Bag *> bags(void) const + { + std::set<coco::Bag *> res; + + for (auto it = _operands.begin(); it != _operands.end(); ++it) + { + res.insert(it->first); + } + + return res; + } + +public: + template <typename T> ann::OperandID addOperand(void) + { + return _module->operand()->create(ann::dtype<T>()); + }; + + template <typename T> ann::OperandID addOperand(const nncc::core::ADT::tensor::Shape &shape) + { + return _module->operand()->create(ann::dtype<T>(), shape); + } + +public: + template <typename T> ann::OperandID addOperand(const coco::FeatureObject *obj) + { + auto bag = obj->bag(); + assert(bag != nullptr); + + auto it = _operands.find(bag); + + if (it != _operands.end()) + { + return it->second; + } + + auto operand = addOperand<T>(morph::nnapi::as_tensor_shape(obj->shape())); + _operands[obj->bag()] = operand; + return operand; + }; + + template <typename T> ann::OperandID addOperand(const coco::KernelObject *obj) + { + auto bag = obj->bag(); + assert(bag != nullptr); + + auto it = _operands.find(bag); + + if (it != _operands.end()) + { + return it->second; + } + + auto operand = addOperand<T>(morph::nnapi::as_tensor_shape(obj->shape())); + _operands[obj->bag()] = operand; + return operand; + }; + +public: + /// @brief Set scalar weight + template <typename T> void setOperand(const ann::OperandID &id, const T &value) + { + static_assert(std::is_arithmetic<T>::value, "T should be arithmetic"); + auto weight = _module->weight()->create(); + weight->fill(value); + _module->operand()->at(id)->weight(weight); + } + + /// @brief Set non-scalar weight + template <typename It> void setOperand(const ann::OperandID &id, It beg, It end) + { + auto weight = _module->weight()->create(); + weight->fill(beg, end); + _module->operand()->at(id)->weight(weight); + } + +public: + void addOperation(ann::Operation::Code code, std::initializer_list<ann::OperandID> inputs, + std::initializer_list<ann::OperandID> outputs) + { + _module->operation()->create(code, inputs, outputs); + } + +public: + /** + * @brief Identify a sequence of coco::Bag * as subnet's inputs + * + * NOTE 1. This method takes input iterator over coco::Bag * values + * NOTE 2. All the identifyInputs class except the last one will be ignored if there are + * multiple identifyInputs calls + */ + template <typename It> void identifyInputs(It beg, It end) + { + _inputs.clear(); + _module->input()->clear(); + + for (auto it = beg; it != end; ++it) + { + auto const bag = *it; + _inputs.emplace_back(*it); + _module->input()->emplace_back(_operands.at(bag)); + } + } + + template <typename T> void identifyInputs(T &&values) + { + identifyInputs(std::begin(values), std::end(values)); + } + +public: + /** + * @brief Identify a sequence of coco::Bag * as subnet's outputs + * + * NOTE 1. This method takes input iterator over coco::Bag * values + * NOTE 2. All the identifyOutputs class except the last one will be ignored if there are + * multiple identifyOutputs calls + */ + template <typename It> void identifyOutputs(It beg, It end) + { + _outputs.clear(); + _module->output()->clear(); + + for (auto it = beg; it != end; ++it) + { + auto const bag = *it; + _outputs.emplace_back(bag); + _module->output()->emplace_back(_operands.at(bag)); + } + } + + template <typename T> void identifyOutputs(T &&values) + { + identifyOutputs(std::begin(values), std::end(values)); + } + +public: + coco::Bag *input(uint32_t n) const { return _inputs.at(n); } + coco::Bag *output(uint32_t n) const { return _outputs.at(n); } + +public: + /** + * @brief Return true if a given bag has an associated operand in ANN IR + */ + bool associated(coco::Bag *b) const { return _operands.find(b) != _operands.end(); } + + /** + * @brief Return operand ID associated with a given bag + * @note The behavior of operand(b) is defined only when associated(b) holds. + */ + ann::OperandID operand(coco::Bag *b) const + { + assert(associated(b)); + return _operands.at(b); + } + +private: + coco::Block *const _block; + std::unique_ptr<ann::Module> _module; + +private: + std::vector<coco::Bag *> _inputs; + std::vector<coco::Bag *> _outputs; + +private: + /// @brief Operand ID assigned for each coco::Bag + std::map<coco::Bag *, ann::OperandID> _operands; +}; + +#endif // __ANN_BINDER_H__ diff --git a/compiler/enco/core/src/ANN/Context.cpp b/compiler/enco/core/src/ANN/Context.cpp new file mode 100644 index 000000000..d4d1882fa --- /dev/null +++ b/compiler/enco/core/src/ANN/Context.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "ANN/Context.h" + +#include <stdex/Memory.h> + +ANNBinder *ANNContext::create(coco::Block *blk) +{ + auto mod = stdex::make_unique<ann::Module>(); + auto obj = stdex::make_unique<ANNBinder>(blk, std::move(mod)); + auto ptr = obj.get(); + + _binders.emplace_back(std::move(obj)); + _map[blk] = ptr; + + return ptr; +} diff --git a/compiler/enco/core/src/ANN/Context.h b/compiler/enco/core/src/ANN/Context.h new file mode 100644 index 000000000..915651eb5 --- /dev/null +++ b/compiler/enco/core/src/ANN/Context.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ANN_CONTEXT_H__ +#define __ANN_CONTEXT_H__ + +#include "ANN/Binder.h" + +#include <map> +#include <vector> + +#include <memory> + +struct ANNContext +{ +public: + ANNBinder *create(coco::Block *blk); + +public: + uint32_t count(void) const { return _binders.size(); } + +public: + ANNBinder *nth(uint32_t n) { return _binders.at(n).get(); } + const ANNBinder *nth(uint32_t n) const { return _binders.at(n).get(); } + +public: + ANNBinder *find(const coco::Block *blk) const + { + auto it = _map.find(blk); + + if (it == _map.end()) + { + return nullptr; + } + + return it->second; + } + +private: + std::vector<std::unique_ptr<ANNBinder>> _binders; + std::map<const coco::Block *, ANNBinder *> _map; +}; + +#endif // __ANN_CONTEXT_H__ diff --git a/compiler/enco/core/src/ANN/Context.test.cpp b/compiler/enco/core/src/ANN/Context.test.cpp new file mode 100644 index 000000000..7fd26f30c --- /dev/null +++ b/compiler/enco/core/src/ANN/Context.test.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "Context.h" + +#include <set> + +#include <gtest/gtest.h> + +namespace +{ +class ANNContextTest : public ::testing::Test +{ +public: + ANNContextTest() { m = coco::Module::create(); } + +public: + virtual ~ANNContextTest() = default; + +protected: + std::unique_ptr<coco::Module> m; +}; +} + +TEST_F(ANNContextTest, constructor) +{ + ANNContext ann_ctx; + + ASSERT_EQ(ann_ctx.count(), 0); +} + +TEST_F(ANNContextTest, create) +{ + ANNContext ann_ctx; + + auto blk = m->entity()->block()->create(); + auto binder = ann_ctx.create(blk); + + ASSERT_NE(binder, nullptr); +} + +TEST_F(ANNContextTest, find) +{ + ANNContext ann_ctx; + + // CASE: Corresponding binder does not exist + { + auto blk = m->entity()->block()->create(); + ASSERT_EQ(ann_ctx.find(blk), nullptr); + } + + // CASE: Corresponding binder does exist + { + auto blk = m->entity()->block()->create(); + auto binder_created = ann_ctx.create(blk); + auto binder_found = ann_ctx.find(blk); + + ASSERT_EQ(binder_created, binder_found); + } +} diff --git a/compiler/enco/core/src/ANN/IR/DType.cpp b/compiler/enco/core/src/ANN/IR/DType.cpp new file mode 100644 index 000000000..7d4585a49 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/DType.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "DType.h" + +namespace ann +{ + +template <> DType dtype<int32_t>(void) { return DType::S32; } +template <> DType dtype<float>(void) { return DType::F32; } + +} // namespace ann diff --git a/compiler/enco/core/src/ANN/IR/DType.h b/compiler/enco/core/src/ANN/IR/DType.h new file mode 100644 index 000000000..b7583b09a --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/DType.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ANN_IR_DTYPE_H__ +#define __ANN_IR_DTYPE_H__ + +#include <cstdint> + +namespace ann +{ + +enum class DType +{ + UNK, + S32, + F32 +}; + +template <typename T> DType dtype(void); + +} // namespace ann + +#endif // __ANN_IR_DTYPE_H__ diff --git a/compiler/enco/core/src/ANN/IR/DType.test.cpp b/compiler/enco/core/src/ANN/IR/DType.test.cpp new file mode 100644 index 000000000..8184ece9b --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/DType.test.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "DType.h" + +#include <gtest/gtest.h> + +TEST(ANN_IR_DTYPE, dtype) +{ + ASSERT_EQ(ann::dtype<int>(), ann::DType::S32); + ASSERT_EQ(ann::dtype<float>(), ann::DType::F32); +} diff --git a/compiler/enco/core/src/ANN/IR/InputList.h b/compiler/enco/core/src/ANN/IR/InputList.h new file mode 100644 index 000000000..51f0fd95a --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/InputList.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ANN_IR_INPUT_LIST_H__ +#define __ANN_IR_INPUT_LIST_H__ + +#include "ANN/IR/OperandID.h" + +#include <vector> + +namespace ann +{ + +using InputList = std::vector<OperandID>; + +} // namespace ann + +#endif // __ANN_IR_INPUT_LIST_H__ diff --git a/compiler/enco/core/src/ANN/IR/Module.h b/compiler/enco/core/src/ANN/IR/Module.h new file mode 100644 index 000000000..b443b4235 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/Module.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ANN_IR_MODULE_H__ +#define __ANN_IR_MODULE_H__ + +#include "ANN/IR/WeightInventory.h" +#include "ANN/IR/OperandInventory.h" +#include "ANN/IR/OperationInventory.h" +#include "ANN/IR/InputList.h" +#include "ANN/IR/OutputList.h" + +namespace ann +{ + +class Module +{ +public: + Module() = default; + +public: + WeightInventory *weight(void) { return &_weight; } + const WeightInventory *weight(void) const { return &_weight; } + + OperandInventory *operand(void) { return &_operand; } + const OperandInventory *operand(void) const { return &_operand; } + + OperationInventory *operation(void) { return &_operation; } + const OperationInventory *operation(void) const { return &_operation; } + + InputList *input(void) { return &_input; } + const InputList *input(void) const { return &_input; } + + OutputList *output(void) { return &_output; } + const OutputList *output(void) const { return &_output; } + +private: + WeightInventory _weight; + OperandInventory _operand; + OperationInventory _operation; + InputList _input; + OutputList _output; +}; + +} // namespace ann + +#endif // __ANN_IR_MODULE_H__ diff --git a/compiler/enco/core/src/ANN/IR/Module.test.cpp b/compiler/enco/core/src/ANN/IR/Module.test.cpp new file mode 100644 index 000000000..4b946c875 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/Module.test.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "Module.h" + +#include <gtest/gtest.h> + +TEST(ANN_IR_MODULE, constructor) +{ + ann::Module m; + + ann::Module *mutable_ptr = &m; + const ann::Module *immutable_ptr = &m; + + ASSERT_NE(mutable_ptr->weight(), nullptr); + ASSERT_EQ(mutable_ptr->weight(), immutable_ptr->weight()); + + ASSERT_NE(mutable_ptr->operand(), nullptr); + ASSERT_EQ(mutable_ptr->operand(), immutable_ptr->operand()); + + ASSERT_NE(mutable_ptr->operation(), nullptr); + ASSERT_EQ(mutable_ptr->operation(), immutable_ptr->operation()); +} diff --git a/compiler/enco/core/src/ANN/IR/Operand.h b/compiler/enco/core/src/ANN/IR/Operand.h new file mode 100644 index 000000000..3b15ed739 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/Operand.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ANN_IR_OPERAND_H__ +#define __ANN_IR_OPERAND_H__ + +#include "ANN/IR/DType.h" +#include "ANN/IR/Weight.h" + +#include <nncc/core/ADT/tensor/Shape.h> + +namespace ann +{ + +class Operand +{ +public: + virtual ~Operand() = default; + +public: + DType dtype(void) const { return _dtype; } + void dtype(const DType &dtype) { _dtype = dtype; } + + const Weight *weight(void) const { return _weight; } + void weight(const Weight *weight) { _weight = weight; } + +private: + DType _dtype = DType::UNK; + const Weight *_weight = nullptr; +}; + +} // namespace ann + +namespace ann +{ + +/** + * @brief Plain (non-qunatized) Scalar Operand + */ +struct ScalarOperand final : public Operand +{ +}; + +} // namespace ann + +namespace ann +{ + +/** + * @brief Plain (non-qunatized) Tensor Operand + */ +struct TensorOperand final : public Operand +{ +public: + TensorOperand(const nncc::core::ADT::tensor::Shape &shape) : _shape{shape} + { + // DO NOTHING + } + +public: + const nncc::core::ADT::tensor::Shape &shape(void) const { return _shape; } + +private: + nncc::core::ADT::tensor::Shape _shape; +}; + +} // namespace ann + +#endif // __ANN_IR_OPERAND_H__ diff --git a/compiler/enco/core/src/ANN/IR/Operand.test.cpp b/compiler/enco/core/src/ANN/IR/Operand.test.cpp new file mode 100644 index 000000000..98ac4ebd0 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/Operand.test.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "Operand.h" + +#include <gtest/gtest.h> + +TEST(ANN_IR_SCALAR_OPERAND, constructor) +{ + const ann::ScalarOperand operand; + + ASSERT_EQ(operand.dtype(), ann::DType::UNK); + ASSERT_EQ(operand.weight(), nullptr); +} + +TEST(ANN_IR_TENSOR_OPERAND, constructor) +{ + const nncc::core::ADT::tensor::Shape shape{1, 2}; + const ann::TensorOperand operand{shape}; + + ASSERT_EQ(operand.dtype(), ann::DType::UNK); + ASSERT_EQ(operand.weight(), nullptr); + ASSERT_EQ(operand.shape(), shape); +} diff --git a/compiler/enco/core/src/ANN/IR/OperandID.h b/compiler/enco/core/src/ANN/IR/OperandID.h new file mode 100644 index 000000000..f1617aacb --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/OperandID.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ANN_IR_OPERAND_ID_H__ +#define __ANN_IR_OPERAND_ID_H__ + +#include <cstdint> + +namespace ann +{ + +class OperandID +{ +public: + OperandID() : _value{0} + { + // DO NOTHING + } + +public: + explicit OperandID(uint32_t value) : _value{value} + { + // DO NOTHING + } + +public: + uint32_t value(void) const { return _value; } + +private: + uint32_t _value; +}; + +} // namespace ann + +#endif // __ANN_IR_OPERAND_ID_H__ diff --git a/compiler/enco/core/src/ANN/IR/OperandID.test.cpp b/compiler/enco/core/src/ANN/IR/OperandID.test.cpp new file mode 100644 index 000000000..04c23b9c8 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/OperandID.test.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "OperandID.h" + +#include <gtest/gtest.h> + +TEST(ANN_IR_OPERAND_ID, default_constructor) +{ + ann::OperandID id; + + ASSERT_EQ(id.value(), 0); +} + +TEST(ANN_IR_OPERAND_ID, explicit_constructor) +{ + ann::OperandID id{4}; + + ASSERT_EQ(id.value(), 4); +} diff --git a/compiler/enco/core/src/ANN/IR/OperandInventory.cpp b/compiler/enco/core/src/ANN/IR/OperandInventory.cpp new file mode 100644 index 000000000..c7ad38811 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/OperandInventory.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "ANN/IR/OperandInventory.h" + +#include <stdex/Memory.h> + +using stdex::make_unique; + +namespace ann +{ + +OperandID OperandInventory::create(const DType &dtype) +{ + uint32_t id = _operands.size(); + + auto operand = make_unique<ScalarOperand>(); + operand->dtype(dtype); + + _operands.emplace_back(std::move(operand)); + + return OperandID{id}; +} + +OperandID OperandInventory::create(const DType &dtype, const nncc::core::ADT::tensor::Shape &shape) +{ + uint32_t id = _operands.size(); + + auto operand = make_unique<TensorOperand>(shape); + operand->dtype(dtype); + + _operands.emplace_back(std::move(operand)); + + return OperandID{id}; +} + +Operand *OperandInventory::at(const OperandID &id) { return _operands.at(id.value()).get(); } + +const Operand *OperandInventory::at(const OperandID &id) const +{ + return _operands.at(id.value()).get(); +} + +} // namespace ann diff --git a/compiler/enco/core/src/ANN/IR/OperandInventory.h b/compiler/enco/core/src/ANN/IR/OperandInventory.h new file mode 100644 index 000000000..23eb08119 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/OperandInventory.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ANN_IR_OPERAND_INVENTORY_H__ +#define __ANN_IR_OPERAND_INVENTORY_H__ + +#include "ANN/IR/OperandID.h" +#include "ANN/IR/Operand.h" + +#include <nncc/core/ADT/tensor/Shape.h> + +#include <memory> +#include <vector> + +namespace ann +{ + +class OperandInventory +{ +public: + OperandID create(const DType &); + OperandID create(const DType &, const nncc::core::ADT::tensor::Shape &); + +public: + template <typename Callable> void each(Callable &&cb) const + { + for (uint32_t n = 0; n < _operands.size(); ++n) + { + cb(OperandID{n}, _operands.at(n).get()); + } + } + +public: + Operand *at(const OperandID &id); + const Operand *at(const OperandID &id) const; + +private: + std::vector<std::unique_ptr<Operand>> _operands; +}; + +} // namespace ann + +#endif // __ANN_IR_OPERAND_INVENTORY_H__ diff --git a/compiler/enco/core/src/ANN/IR/OperandInventory.test.cpp b/compiler/enco/core/src/ANN/IR/OperandInventory.test.cpp new file mode 100644 index 000000000..e576752bc --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/OperandInventory.test.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "OperandInventory.h" + +#include <gtest/gtest.h> + +TEST(ANN_IR_OPERAND_INVENTORY, constructor) +{ + ann::OperandInventory inven; + + uint32_t count = 0; + + inven.each([&](const ann::OperandID &, const ann::Operand *) { ++count; }); + + ASSERT_EQ(count, 0); +} diff --git a/compiler/enco/core/src/ANN/IR/Operation.def b/compiler/enco/core/src/ANN/IR/Operation.def new file mode 100644 index 000000000..68fd394cf --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/Operation.def @@ -0,0 +1,17 @@ +#ifndef ANN_OPERATION +#error Define ANN_OPERATION first +#endif // ANN_OPERATION + +// ANN_OPERATION(TAG, ENUM_VALUE) +ANN_OPERATION(ADD, ANEURALNETWORKS_ADD) +ANN_OPERATION(MUL, ANEURALNETWORKS_MUL) +ANN_OPERATION(CONV_2D, ANEURALNETWORKS_CONV_2D) +ANN_OPERATION(DEPTHWISE_CONV_2D, ANEURALNETWORKS_DEPTHWISE_CONV_2D) +ANN_OPERATION(MAX_POOL_2D, ANEURALNETWORKS_MAX_POOL_2D) +ANN_OPERATION(AVG_POOL_2D, ANEURALNETWORKS_AVERAGE_POOL_2D) +ANN_OPERATION(RELU, ANEURALNETWORKS_RELU) +ANN_OPERATION(RELU6, ANEURALNETWORKS_RELU6) +ANN_OPERATION(PAD, ANEURALNETWORKS_PAD) +ANN_OPERATION(CONCAT, ANEURALNETWORKS_CONCATENATION) +ANN_OPERATION(SUB, ANEURALNETWORKS_SUB) +ANN_OPERATION(DIV, ANEURALNETWORKS_DIV) diff --git a/compiler/enco/core/src/ANN/IR/Operation.h b/compiler/enco/core/src/ANN/IR/Operation.h new file mode 100644 index 000000000..cacc2b794 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/Operation.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ANN_IR_OPERATION_H__ +#define __ANN_IR_OPERATION_H__ + +#include "ANN/IR/OperandID.h" + +#include <initializer_list> +#include <vector> + +namespace ann +{ + +class Operation +{ +public: + enum class Code + { +#define ANN_OPERATION(TAG, VALUE) TAG, +#include "Operation.def" +#undef ANN_OPERATION + }; + +public: + Operation(const Code &code, std::initializer_list<OperandID> inputs, + std::initializer_list<OperandID> outputs) + : _code{code}, _inputs{inputs}, _outputs{outputs} + { + // DO NOTHING + } + +public: + const Code &code(void) const { return _code; } + const std::vector<OperandID> &inputs(void) const { return _inputs; } + const std::vector<OperandID> &outputs(void) const { return _outputs; } + +private: + Code _code; + std::vector<OperandID> _inputs; + std::vector<OperandID> _outputs; +}; + +} // namespace ann + +#endif // __ANN_IR_OPERATION_H__ diff --git a/compiler/enco/core/src/ANN/IR/Operation.test.cpp b/compiler/enco/core/src/ANN/IR/Operation.test.cpp new file mode 100644 index 000000000..d1b716733 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/Operation.test.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "Operation.h" + +#include <gtest/gtest.h> + +TEST(ANN_IR_OPERATION, constructor) +{ + ann::Operation op{ann::Operation::Code::CONV_2D, {}, {}}; + + ASSERT_EQ(op.code(), ann::Operation::Code::CONV_2D); + ASSERT_EQ(op.inputs().size(), 0); + ASSERT_EQ(op.outputs().size(), 0); +} diff --git a/compiler/enco/core/src/ANN/IR/OperationInventory.cpp b/compiler/enco/core/src/ANN/IR/OperationInventory.cpp new file mode 100644 index 000000000..37d48c170 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/OperationInventory.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "OperationInventory.h" + +#include <stdex/Memory.h> + +using stdex::make_unique; + +namespace ann +{ + +void OperationInventory::create(Operation::Code code, std::initializer_list<OperandID> inputs, + std::initializer_list<OperandID> outputs) +{ + _operations.emplace_back(make_unique<Operation>(code, inputs, outputs)); +} + +} // namespace ann diff --git a/compiler/enco/core/src/ANN/IR/OperationInventory.h b/compiler/enco/core/src/ANN/IR/OperationInventory.h new file mode 100644 index 000000000..11c6be98a --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/OperationInventory.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ANN_IR_OPERATION_INVENTORY_H__ +#define __ANN_IR_OPERATION_INVENTORY_H__ + +#include "ANN/IR/Operation.h" +#include "ANN/IR/OperandID.h" + +#include <initializer_list> + +#include <memory> + +namespace ann +{ + +class OperationInventory +{ +public: + void create(Operation::Code code, std::initializer_list<OperandID> inputs, + std::initializer_list<OperandID> outputs); + +public: + uint32_t count(void) const { return _operations.size(); } + +public: + const Operation *at(uint32_t n) const { return _operations.at(n).get(); } + +private: + std::vector<std::unique_ptr<Operation>> _operations; +}; + +} // namespace ann + +#endif // __ANN_IR_OPERATION_INVENTORY_H__ diff --git a/compiler/enco/core/src/ANN/IR/OperationInventory.test.cpp b/compiler/enco/core/src/ANN/IR/OperationInventory.test.cpp new file mode 100644 index 000000000..0e91a4f53 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/OperationInventory.test.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "OperationInventory.h" + +#include <gtest/gtest.h> + +TEST(ANN_IR_OPERATION_INVENTORY, constructor) +{ + ann::OperationInventory inven; + + ASSERT_EQ(inven.count(), 0); +} + +TEST(ANN_IR_OPERATION_INVENTORY, create) +{ + ann::OperationInventory inven; + + inven.create(ann::Operation::Code::CONV_2D, {ann::OperandID{0}}, {ann::OperandID{3}}); + + ASSERT_EQ(inven.count(), 1); + ASSERT_NE(inven.at(0), nullptr); + + ASSERT_EQ(inven.at(0)->code(), ann::Operation::Code::CONV_2D); + ASSERT_EQ(inven.at(0)->inputs().size(), 1); + ASSERT_EQ(inven.at(0)->outputs().size(), 1); +} diff --git a/compiler/enco/core/src/ANN/IR/OutputList.h b/compiler/enco/core/src/ANN/IR/OutputList.h new file mode 100644 index 000000000..2dd891138 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/OutputList.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ANN_IR_OUTPUT_LIST_H__ +#define __ANN_IR_OUTPUT_LIST_H__ + +#include "ANN/IR/OperandID.h" + +#include <vector> + +namespace ann +{ + +using OutputList = std::vector<OperandID>; + +} // namespace ann + +#endif // __ANN_IR_OUTPUT_LIST_H__ diff --git a/compiler/enco/core/src/ANN/IR/Weight.h b/compiler/enco/core/src/ANN/IR/Weight.h new file mode 100644 index 000000000..062aa6d19 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/Weight.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ANN_IR_WEIGHT_H__ +#define __ANN_IR_WEIGHT_H__ + +#include <vector> + +#include <cstdint> +#include <type_traits> + +namespace ann +{ + +class Weight +{ +public: + const uint8_t *base(void) const { return _buffer.data(); } + uint32_t size(void) const { return _buffer.size(); } + +public: + template <typename T> void fill(const T &value) + { + static_assert(std::is_arithmetic<T>::value, "T should be arithmetic"); + _buffer.clear(); + + auto arr = reinterpret_cast<const uint8_t *>(&value); + + for (uint32_t b = 0; b < sizeof(T); ++b) + { + _buffer.emplace_back(arr[b]); + } + } + + template <typename It> void fill(It beg, It end) + { + _buffer.clear(); + + for (auto it = beg; it != end; ++it) + { + const auto value = *it; + auto arr = reinterpret_cast<const uint8_t *>(&value); + + for (uint32_t b = 0; b < sizeof(value); ++b) + { + _buffer.emplace_back(arr[b]); + } + } + } + +private: + std::vector<uint8_t> _buffer; +}; + +} // namespace ann + +#endif // __ANN_IR_WEIGHT_H__ diff --git a/compiler/enco/core/src/ANN/IR/Weight.test.cpp b/compiler/enco/core/src/ANN/IR/Weight.test.cpp new file mode 100644 index 000000000..53532114c --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/Weight.test.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "Weight.h" + +#include <gtest/gtest.h> + +TEST(ANN_IR_WEIGHT, constructor) +{ + ann::Weight weight; + + ASSERT_EQ(weight.base(), nullptr); + ASSERT_EQ(weight.size(), 0); +} + +TEST(ANN_IR_WEIGHT, fill_scalar_int) +{ + ann::Weight weight; + + weight.fill(3); + + ASSERT_NE(weight.base(), nullptr); + ASSERT_EQ(*reinterpret_cast<const int *>(weight.base()), 3); +} + +TEST(ANN_IR_WEIGHT, fill_vector_float) +{ + std::vector<float> values{1.0f, 2.0f}; + + ann::Weight weight; + + weight.fill(values.begin(), values.end()); + + ASSERT_NE(weight.base(), nullptr); + + auto arr = reinterpret_cast<const float *>(weight.base()); + + ASSERT_FLOAT_EQ(arr[0], 1.0f); + ASSERT_FLOAT_EQ(arr[1], 2.0f); +} diff --git a/compiler/enco/core/src/ANN/IR/WeightInventory.cpp b/compiler/enco/core/src/ANN/IR/WeightInventory.cpp new file mode 100644 index 000000000..d8809ac08 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/WeightInventory.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "WeightInventory.h" + +#include <stdex/Memory.h> + +using stdex::make_unique; + +namespace ann +{ + +Weight *WeightInventory::create(void) +{ + auto hnd = make_unique<Weight>(); + auto ptr = hnd.get(); + _weights.push_back(std::move(hnd)); + return ptr; +} + +} // namespace ann diff --git a/compiler/enco/core/src/ANN/IR/WeightInventory.h b/compiler/enco/core/src/ANN/IR/WeightInventory.h new file mode 100644 index 000000000..fd166837f --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/WeightInventory.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __WEIGHT_INVENTORY_H__ +#define __WEIGHT_INVENTORY_H__ + +#include "ANN/IR/Weight.h" + +#include <memory> + +namespace ann +{ + +class WeightInventory +{ +public: + Weight *create(void); + +private: + std::vector<std::unique_ptr<Weight>> _weights; +}; + +} // namespace ann + +#endif // __WEIGHT_INVENTORY_H__ diff --git a/compiler/enco/core/src/ANN/IR/WeightInventory.test.cpp b/compiler/enco/core/src/ANN/IR/WeightInventory.test.cpp new file mode 100644 index 000000000..143bdfddf --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/WeightInventory.test.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "WeightInventory.h" + +#include <gtest/gtest.h> + +TEST(ANN_IR_WEIGHT_INVENTORY, create) +{ + ann::WeightInventory inven; + + auto weight = inven.create(); + + ASSERT_EQ(weight->base(), nullptr); + ASSERT_EQ(weight->size(), 0); +} diff --git a/compiler/enco/core/src/AsmCode.cpp b/compiler/enco/core/src/AsmCode.cpp new file mode 100644 index 000000000..70d6f30b3 --- /dev/null +++ b/compiler/enco/core/src/AsmCode.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "AsmCode.h" + +namespace enco +{ + +void AsmCode::dump(std::ostream &os) const +{ + os << ".section .rodata" << std::endl; + os << ".global " << _varname << std::endl; + // Please refer to https://www.sourceware.org/binutils/docs/as/Type.html#Type for details + os << ".type " << _varname << ", STT_OBJECT" << std::endl; + os << ".align " << 4 << std::endl; + os << _varname << ":" << std::endl; + os << ".incbin " << '"' << _filename << '"' << std::endl; +} + +} // namespace enco diff --git a/compiler/enco/core/src/AsmCode.h b/compiler/enco/core/src/AsmCode.h new file mode 100644 index 000000000..c43892888 --- /dev/null +++ b/compiler/enco/core/src/AsmCode.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ENCO_ASM_CODE_H__ +#define __ENCO_ASM_CODE_H__ + +#include <ostream> +#include <string> + +namespace enco +{ + +class AsmCode +{ +public: + AsmCode(const std::string &filename, const std::string &varname) + : _filename{filename}, _varname{varname} + { + // DO NOTHING + } + +public: + void dump(std::ostream &) const; + +private: + std::string _filename; + std::string _varname; +}; + +} // namespace enco + +static inline std::ostream &operator<<(std::ostream &os, const enco::AsmCode &code) +{ + code.dump(os); + return os; +} + +#endif // __ENCO_ASM_CODE_H__ diff --git a/compiler/enco/core/src/Backend.cpp b/compiler/enco/core/src/Backend.cpp new file mode 100644 index 000000000..d4bec7447 --- /dev/null +++ b/compiler/enco/core/src/Backend.cpp @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "enco/Backend.h" + +#include "IRValidator.h" + +#include "Session.h" +#include "Pipeline.h" + +#include "Code.h" +#include "AsmCode.h" +#include "CppCode.h" + +#include "Transforms/Duplicate.h" +#include "Transforms/FeatureUnification.h" +#include "Transforms/AvgPoolLowering.h" +#include "Transforms/IntrinsicSelection.h" +#include "Transforms/DataLayoutConversion.h" +#include "Transforms/IndirectCopyElimination.h" +#include "Transforms/IdenticalObjectReduction.h" +#include "Transforms/DuplicatedObjectReduction.h" +#include "Transforms/DeadObjectElimination.h" +#include "Transforms/ConstantFolding.h" +#include "Transforms/CopyLowering.h" +#include "Transforms/ConcatLowering.h" +#include "Transforms/FreeInstrElimination.h" +#include "Transforms/FreeOpElimination.h" +#include "Transforms/DeadBagElimination.h" +#include "Transforms/Optimizations.h" +#include "Transforms/Split.h" +#include "Transforms/GlobalDataGeneration.h" + +#include <stdex/Memory.h> + +#include <stdexcept> +#include <iostream> +#include <fstream> + +using stdex::make_unique; +using namespace enco; + +namespace +{ + +// has_inout_bag(m) returns true if there is a pair of coco::Input and coco::Output that share +// the same bag as their backing storage +inline bool has_inout_bag(const coco::Module *m) +{ + for (uint32_t n = 0; n < m->entity()->bag()->size(); ++n) + { + auto bag = m->entity()->bag()->at(n); + + if (bag->isInput() && bag->isOutput()) + { + return true; + } + } + return false; +} + +class BackendImpl final : public enco::Backend +{ +public: + BackendImpl(const std::string &prefix) : _prefix{prefix} + { + // DO NOTHING + } + +public: + void compile(coco::Module *m, coco::Data *d) override; + +private: + std::string _prefix; +}; + +void BackendImpl::compile(coco::Module *m, coco::Data *d) +{ + auto sess = make_session(m, d); + + // validate if IR from frontend is correct + assert(validate(code(sess))); + + enco::Pipeline pipeline; + + // Configure pipeline + + // As explained below, the current implementation does not work if there is a pair of input/output + // that share the same bag as their underlying bag. + // + // BagDuplicationPass creates a copy of such bags in order to eliminate such a pair. + pipeline.append(make_unique<BagDuplicationPass>()); + pipeline.append(make_unique<FeatureUnificationPass>()); + pipeline.append(make_unique<AvgPoolLoweringPass>()); + pipeline.append(make_unique<IntrinsicSelectionPass>()); + // Insert data ordering if necessary + pipeline.append(make_unique<DataLayoutConversionPass>()); + pipeline.append(make_unique<IndirectCopyEliminationPass>()); + pipeline.append(make_unique<IdenticalObjectReductionPass>()); + pipeline.append(make_unique<DuplicatedObjectReductionPass>()); + pipeline.append(make_unique<ConstantFoldingPass>()); + // Eliminate dead object + // + // NOTE Dead Object Elimination (DOE) is performed before Copy lowering + // in order to reduce compilation overhead. + pipeline.append(make_unique<DeadObjectEliminationPass>()); + // Lower Copy as Shuffle + pipeline.append(make_unique<CopyLoweringPass>()); + // Lower ConcatF as Shuffle if it is not delegated to NNAPI yet + pipeline.append(make_unique<ConcatLoweringPass>()); + pipeline.append(make_unique<BypassGenerationPass>()); + pipeline.append(make_unique<FreeInstrEliminationPass>()); + // NOTE Free Op Elimination should be applied after Free Instr Elimination + // - Free Instr Elimination may generate additional free Op(s) + pipeline.append(make_unique<FreeOpEliminationPass>()); + pipeline.append(make_unique<DeadBagEliminationPass>()); + // Split instructions into a set of phases (each block serves as a phase) + pipeline.append(make_unique<PhaseConstructionPass>()); + + // Apply transforms in the pipeline + for (uint32_t n = 0; n < pipeline.size(); ++n) + { + const auto &pass = pipeline.at(n); + + pass.run(sess); + } + + // The current implementation will assign memory region for each bag as follows: + // Bind input bag to the region provided by Network_input_bind + // Bind output bag to the region provided by Network_output_bind + // Bind intermediate bag to the region allocated during execution + // + // Note that this scheme does not work if there is a pair of input/output + // that share the same bag as their underlying bag + assert(!has_inout_bag(code(sess)->module())); + + const std::string data_var = "data"; + const std::string data_filename = _prefix + ".bin"; + + // Generate 'bin' file + { + std::ofstream ofs{data_filename, std::ios::binary}; + generate_global_data(ofs, code(sess)); + } + + // Generate 'embed.S' file + { + std::ofstream ofs{_prefix + ".embed.S"}; + ofs << AsmCode{data_filename, data_var}; + } + + // TODO Run various transforms over enco::Code + + std::ofstream ofs{_prefix + ".cpp"}; + ofs << CppCode{data_var, code(sess)} << std::endl; +} + +} // namespace enco + +#include <iostream> + +std::unique_ptr<enco::Backend> make_backend(const cmdline::View &cmdline) +{ + return make_unique<::BackendImpl>(cmdline.at(0)); +} diff --git a/compiler/enco/core/src/Code.h b/compiler/enco/core/src/Code.h new file mode 100644 index 000000000..91756d5f8 --- /dev/null +++ b/compiler/enco/core/src/Code.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ENCO_CODE_H__ +#define __ENCO_CODE_H__ + +#include "ANN/Context.h" + +#include <coco/IR/Module.h> +#include <coco/IR/Data.h> + +namespace enco +{ + +struct Code +{ +public: + Code(coco::Module *module, coco::Data *data) : _module{module}, _data{data} + { + // DO NOTHING + } + +public: + coco::Module *module(void) const { return _module; } + coco::Data *data(void) const { return _data; } + +private: + coco::Module *const _module; + coco::Data *const _data; +}; + +} // namespace enco + +#endif // __ENCO_CODE_H__ diff --git a/compiler/enco/core/src/Code.test.cpp b/compiler/enco/core/src/Code.test.cpp new file mode 100644 index 000000000..8e96e4751 --- /dev/null +++ b/compiler/enco/core/src/Code.test.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "Code.h" + +#include <gtest/gtest.h> + +TEST(CODE, constructor) +{ + auto m = coco::Module::create(); + auto d = coco::Data::create(); + + enco::Code code{m.get(), d.get()}; + + ASSERT_EQ(code.module(), m.get()); + ASSERT_EQ(code.data(), d.get()); +} diff --git a/compiler/enco/core/src/CodeIndex.h b/compiler/enco/core/src/CodeIndex.h new file mode 100644 index 000000000..7f2da6463 --- /dev/null +++ b/compiler/enco/core/src/CodeIndex.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __CODE_INDEX_H__ +#define __CODE_INDEX_H__ + +#include <coco/IR/Block.h> +#include <coco/IR/Instr.h> + +/** + * @brief A CodeIndex denotes the index of instruction inside the whole module + */ +class CodeIndex +{ +public: + CodeIndex() = default; + +public: + CodeIndex(const coco::BlockIndex &blk_ind, const coco::InstrIndex &ins_ind) + : _blk_ind{blk_ind}, _ins_ind{ins_ind} + { + } + +public: + const coco::BlockIndex &block(void) const { return _blk_ind; } + const coco::InstrIndex &instr(void) const { return _ins_ind; } + +private: + coco::BlockIndex _blk_ind; + coco::InstrIndex _ins_ind; +}; + +static inline coco::BlockIndex block_index(const coco::Block *blk) +{ + if (blk == nullptr) + { + return coco::BlockIndex{}; + } + + return blk->index(); +} + +static inline CodeIndex code_index(const coco::Instr *ins) +{ + return CodeIndex{block_index(ins->parent()), ins->index()}; +} + +static inline bool operator<(const CodeIndex &lhs, const CodeIndex &rhs) +{ + if (lhs.block() < rhs.block()) + { + return true; + } + + if (lhs.block().value() > rhs.block().value()) + { + return false; + } + + return lhs.instr() < rhs.instr(); +} + +#endif // __CODE_INDEX_H__ diff --git a/compiler/enco/core/src/CppCode.cpp b/compiler/enco/core/src/CppCode.cpp new file mode 100644 index 000000000..aa5ef3156 --- /dev/null +++ b/compiler/enco/core/src/CppCode.cpp @@ -0,0 +1,553 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "CppCode.h" + +#include "Transforms/GlobalDataGeneration.h" +#include "Transforms/Split.h" + +#include "CppGen/MemoryContext.h" + +#include "CppGen/Host.h" +#include "CppGen/Subnet.h" + +#include "Dims.h" + +#include <pp/LinearDocument.h> +#include <pp/MultiLineTextUtils.h> + +#include <map> +#include <set> +#include <string> +#include <stdexcept> + +namespace +{ + +struct SubnetInfo +{ + std::string struct_name; + /// @brief The field name (in this subnet struct) of ANeuralNetworksCompilation value + std::string compilation_field; + + /// @brief The field name (in Network struct) for this subnet + std::string field_name; +}; + +struct NetworkStruct +{ + pp::LinearDocument def; +}; + +struct InvokeFunction +{ + pp::LinearDocument head; + pp::LinearDocument body; + pp::LinearDocument tail{pp::LinearDocument::Direction::Reverse}; + +public: + /** @brief Create a (fresh) local variable */ + std::string local(void) { return pp::fmt("v_", ++_var_count); } + +private: + uint32_t _var_count = 0; +}; + +/** + * @brief Enumerate a set of Bag accessed by a given instruction + * + * Supported instruction: + * "Shuffle" + */ +class AccessedBagAccumulator : public coco::Instr::Visitor<void> +{ +public: + AccessedBagAccumulator(std::set<coco::Bag *> *out) : _out{out} + { + // Validate "out" + assert(_out != nullptr); + } + +public: + void visit(const coco::Shuffle *shuffle) override + { + assert(shuffle->from() != nullptr); + assert(shuffle->into() != nullptr); + + _out->insert(shuffle->from()); + _out->insert(shuffle->into()); + } + +private: + std::set<coco::Bag *> *_out; +}; + +/** + * @brief Return a set of bags that SHOULD have a host allocation + */ +std::set<coco::Bag *> hosted(const enco::Code *code) +{ + std::set<coco::Bag *> res; + + auto m = code->module(); + auto ann_ctx = enco::SubnetManager::context(m); + + for (auto blk = m->block()->head(); blk; blk = blk->next()) + { + if (auto ann_binder = ann_ctx->find(blk)) + { + // Case: The current block is ANN-compatible + + // Each ANN input SHOULD have a corresponding host allocation + for (uint32_t n = 0; n < ann_binder->module()->input()->size(); ++n) + { + res.insert(ann_binder->input(n)); + } + + // Each ANN output SHOULD have a corresponding host allocation + for (uint32_t n = 0; n < ann_binder->module()->output()->size(); ++n) + { + res.insert(ann_binder->output(n)); + } + } + else + { + // Every bag that ANN-incompatible block accesses SHOULD have a corresponding host allocation + AccessedBagAccumulator acc{&res}; + + for (auto ins = blk->instr()->head(); ins; ins = ins->next()) + { + ins->accept(acc); + } + } + } + + return res; +} +} // namespace + +namespace enco +{ + +void CppCode::dump(std::ostream &os) const +{ + auto m = _code->module(); + auto d = _code->data(); + auto ann_ctx = enco::SubnetManager::context(m); + + NetworkStruct network; + InvokeFunction invoke; + pp::LinearDocument internal; + + auto data_exp = [this](const GlobalOffset &off) { return pp::fmt(_varname, " + ", off); }; + + // Record the subnet information + std::map<const ANNBinder *, SubnetInfo> subnet_ctx; + + /** + * Create a struct for each android NN network of the following form: + * + * struct [Name] + * { + * ... + * + * [Name]() // constructor + * { + * ... + * } + * + * ~[Name]() // destructor + * { + * ... + * } + * }; + * + */ + for (uint32_t n = 0; n < ann_ctx->count(); ++n) + { + SubnetStructBuilder builder; + + auto subnet_binder = ann_ctx->nth(n); + auto subnet_struct_name = pp::fmt("Subnet_", subnet_ctx.size()); + auto subnet_field_name = pp::fmt("_subnet_", subnet_ctx.size()); + + // Create global data variable + auto emit_weight = [&](const ann::OperandID &, const ann::Operand *info) { + if (info->weight()) + { + auto size = info->weight()->size(); + auto off = enco::GlobalData::data_offset(info); + auto base_exp = pp::fmt("reinterpret_cast<const void *>(", data_exp(off), ")"); + auto size_exp = pp::fmt(size); + + builder.expr(info, base_exp, size_exp); + } + }; + subnet_binder->module()->operand()->each(emit_weight); + + auto subnet_struct_content = builder.build(subnet_binder); + + // Emit C++ declaration + internal.append("struct ", subnet_struct_name); + internal.append("{"); + internal.indent(); + + internal.append(subnet_struct_content->def()); + + internal.append(subnet_struct_name, "()"); + internal.append("{"); + internal.indent(); + internal.append(subnet_struct_content->ctor()); + internal.unindent(); + internal.append("}"); + + internal.append("~", subnet_struct_name, "()"); + internal.append("{"); + internal.indent(); + internal.append(subnet_struct_content->dtor()); + internal.unindent(); + internal.append("}"); + + internal.unindent(); + internal.append("};"); + + // Declare subnet field + network.def.append(subnet_struct_name, " ", subnet_field_name, ";"); + + // Update subnet context + SubnetInfo subnet_info; + + subnet_info.struct_name = subnet_struct_name; + subnet_info.compilation_field = subnet_struct_content->compilation(); + subnet_info.field_name = subnet_field_name; + + assert(subnet_ctx.find(subnet_binder) == subnet_ctx.end()); + subnet_ctx[subnet_binder] = subnet_info; + } + + MemoryContext mem; + + // Set dedicated memory region for network inputs + for (uint32_t n = 0; n < m->input()->size(); ++n) + { + mem.base(m->input()->at(n)->bag(), pp::fmt("net->inputs[", n, "].ptr")); + mem.size(m->input()->at(n)->bag(), pp::fmt("net->inputs[", n, "].len")); + } + + // Set dedicated memory region for network outputs + for (uint32_t n = 0; n < m->output()->size(); ++n) + { + mem.base(m->output()->at(n)->bag(), pp::fmt("net->outputs[", n, "].ptr")); + mem.size(m->output()->at(n)->bag(), pp::fmt("net->outputs[", n, "].len")); + } + + // Set dedicated memory region for constant weight values + // TODO Support non-constant bags with initial values + for (uint32_t n = 0; n < m->entity()->bag()->size(); ++n) + { + auto bag = m->entity()->bag()->at(n); + + if (!d->allocated(bag)) + { + // Skip if no weight exists + continue; + } + + // TODO Support non-float(fp32) weight + auto offset = enco::GlobalData::data_offset(bag); + + auto base_expr = data_exp(offset); + auto size_expr = pp::fmt(bag->size() * sizeof(float)); + + mem.base(bag, base_expr); + mem.size(bag, size_expr); + } + + // Set dedicated memory reigion for intermediate buffer(s) + for (const auto &bag : hosted(_code)) + { + // Skip if a bag is already allocated + if (mem.member(bag)) + { + continue; + } + + auto name = invoke.local(); + + invoke.head.append("auto ", name, " = new uint8_t[", bag->size() * sizeof(float), "];"); + invoke.tail.append("delete[] ", name, ";"); + + mem.base(bag, name); + mem.size(bag, pp::fmt(bag->size() * sizeof(float))); + } + + // Create Code Block Builder + SubnetBlockCompiler subnet_compiler{mem}; + + for (auto it = subnet_ctx.begin(); it != subnet_ctx.end(); ++it) + { + // Specify how to access ANeuralNetworksCompilation + const auto &info = it->second; + subnet_compiler.bind(it->first, pp::fmt("net->", info.field_name, ".", info.compilation_field)); + } + + HostBlockCompiler host_compiler{mem}; + + for (auto blk = m->block()->head(); blk; blk = blk->next()) + { + invoke.body.append("{"); + invoke.body.indent(); + + if (auto binder = ann_ctx->find(blk)) + { + // Generate code that invokes Android NN sub-network + auto lines = subnet_compiler.compile(binder); + invoke.body.append(*lines); + } + else + { + // Generate code on-the-fly for Android NN-incompatible blocks + auto lines = host_compiler.compile(blk); + invoke.body.append(*lines); + } + + invoke.body.unindent(); + invoke.body.append("}"); + } + + // + // Generate full C++ source code with code snippet + // + const std::string name{"Network"}; + + pp::LinearDocument includes; + { + // Include Android NN API header + includes.append("#include <NeuralNetworks.h>"); + includes.append(); + + includes.append("#include <cstdint>"); + includes.append("#include <cassert>"); + includes.append("#include <array>"); + } + + pp::LinearDocument net_def; + { + net_def.append("struct ", name, " {"); + net_def.indent(); + net_def.append("struct Shape { uint32_t rank; const uint32_t *dims; };"); + net_def.append("struct Input {"); + net_def.indent(); + net_def.append("const char *name;"); + net_def.append("const uint8_t *ptr;"); + net_def.append("unsigned len;"); + net_def.append("Shape shape;"); + net_def.unindent(); + net_def.append("};"); + net_def.append("struct Output {"); + net_def.indent(); + net_def.append("const char *name;"); + net_def.append("uint8_t *ptr;"); + net_def.append("unsigned len;"); + net_def.append("Shape shape;"); + net_def.unindent(); + net_def.append("};"); + net_def.append(); + net_def.append(name, "();"); + net_def.append("~", name, "();"); + + net_def.append(); + net_def.append(network.def); + net_def.append(); + + net_def.append("std::array<Input, ", m->input()->size(), "> inputs;"); + net_def.append("std::array<Output, ", m->output()->size(), "> outputs;"); + + net_def.unindent(); + net_def.append("};"); + } + + pp::LinearDocument net_ctor; + { + net_ctor.append("Network::Network() {"); + net_ctor.indent(); + + // Initialize input metadata + for (uint32_t n = 0; n < m->input()->size(); ++n) + { + auto input = m->input()->at(n); + auto dims = as_dims(input->shape()); + + auto name_off = enco::GlobalData::name_offset(input); + auto name_exp = pp::fmt("reinterpret_cast<const char *>(", data_exp(name_off), ")"); + auto dims_off = enco::GlobalData::dims_offset(input); + auto dims_exp = pp::fmt("reinterpret_cast<const unsigned *>(", data_exp(dims_off), ")"); + + net_ctor.append("inputs.at(", n, ").name = ", name_exp, ";"); + net_ctor.append("inputs.at(", n, ").shape.rank = ", dims.size(), ";"); + net_ctor.append("inputs.at(", n, ").shape.dims = ", dims_exp, ";"); + } + + // Initialize output metadata + for (uint32_t n = 0; n < m->output()->size(); ++n) + { + auto output = m->output()->at(n); + auto dims = as_dims(output->shape()); + + auto name_off = enco::GlobalData::name_offset(output); + auto name_exp = pp::fmt("reinterpret_cast<const char *>(", data_exp(name_off), ")"); + auto dims_off = enco::GlobalData::dims_offset(output); + auto dims_exp = pp::fmt("reinterpret_cast<const unsigned *>(", data_exp(dims_off), ")"); + + net_ctor.append("outputs.at(", n, ").name = ", name_exp, ";"); + net_ctor.append("outputs.at(", n, ").shape.rank = ", dims.size(), ";"); + net_ctor.append("outputs.at(", n, ").shape.dims = ", dims_exp, ";"); + } + + // TODO Implement this + net_ctor.unindent(); + net_ctor.append("}"); + } + + pp::LinearDocument net_dtor; + { + net_dtor.append("Network::~Network() {"); + net_dtor.indent(); + // TODO Implement this + net_dtor.unindent(); + net_dtor.append("}"); + } + + pp::LinearDocument source; + + source.append(includes); + source.append(); + source.append("extern uint8_t ", _varname, "[];"); + source.append(); + + source.append("namespace"); + source.append("{"); + source.append(internal); + source.append("} // namespace"); + source.append(); + source.append(net_def); + source.append(); + source.append(net_ctor); + source.append(); + source.append(net_dtor); + + source.append(); + source.append(name, " *", name, "_construct() { return new ", name, "{}; }"); + source.append("void ", name, "_destruct(", name, " *net) { delete net; }"); + + source.append(); + + // Emit Network_input_count function + source.append("unsigned ", name, "_input_count(const ", name, " *net) {"); + source.indent(); + source.append("return net->inputs.size();"); + source.unindent(); + source.append("}"); + + source.append(); + + // Emit Network_input_name function + source.append("const char *", name, "_input_name(const ", name, " *net, unsigned n) {"); + source.indent(); + source.append("return net->inputs.at(n).name;"); + source.unindent(); + source.append("}"); + + // Emit Network_input_rank function + source.append("unsigned ", name, "_input_rank(const ", name, " *net, unsigned n) {"); + source.indent(); + source.append("return net->inputs.at(n).shape.rank;"); + source.unindent(); + source.append("}"); + + // Emit Network_input_dim function + source.append("unsigned ", name, "_input_dim(const ", name, " *net, unsigned n, unsigned axe)"); + source.append("{"); + source.indent(); + source.append("return net->inputs.at(n).shape.dims[axe];"); + source.unindent(); + source.append("}"); + + // Emit Network_input_bind function + source.append("void ", name, "_input_bind(", name, + " *net, unsigned n, const void *ptr, unsigned len) {"); + source.indent(); + source.append("net->inputs.at(n).ptr = reinterpret_cast<const uint8_t *>(ptr);"); + source.append("net->inputs.at(n).len = len;"); + source.unindent(); + source.append("}"); + + source.append(); + + // Emit Network_output_count function + source.append("unsigned ", name, "_output_count(const ", name, " *net) {"); + source.indent(); + source.append("return net->outputs.size();"); + source.unindent(); + source.append("}"); + + source.append(); + + // Emit Network_output_name function + source.append("const char *", name, "_output_name(const ", name, " *net, unsigned n) {"); + source.indent(); + source.append("return net->outputs.at(n).name;"); + source.unindent(); + source.append("}"); + + // Emit Network_output_rank function + source.append("unsigned ", name, "_output_rank(const ", name, " *net, unsigned n) {"); + source.indent(); + source.append("return net->outputs.at(n).shape.rank;"); + source.unindent(); + source.append("}"); + + // Emit Network_output_dim function + source.append("unsigned ", name, "_output_dim(const ", name, " *net, unsigned n, unsigned axe)"); + source.append("{"); + source.indent(); + source.append("return net->outputs.at(n).shape.dims[axe];"); + source.unindent(); + source.append("}"); + + // Emit Network_output_bind function + source.append("void ", name, "_output_bind(", name, + " *net, unsigned n, void *ptr, unsigned len) {"); + source.indent(); + source.append("net->outputs.at(n).ptr = reinterpret_cast<uint8_t *>(ptr);"); + source.append("net->outputs.at(n).len = len;"); + source.unindent(); + source.append("}"); + + source.append(); + + source.append("void ", name, "_invoke(", name, " *net) {"); + source.indent(); + source.append(invoke.head); + source.append(invoke.body); + source.append(invoke.tail); + source.unindent(); + source.append("}"); + + os << source; +} + +} // namespace enco diff --git a/compiler/enco/core/src/CppCode.h b/compiler/enco/core/src/CppCode.h new file mode 100644 index 000000000..c52ea1d5d --- /dev/null +++ b/compiler/enco/core/src/CppCode.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ENCO_CPP_CODE_H__ +#define __ENCO_CPP_CODE_H__ + +#include "Code.h" + +#include <ostream> + +namespace enco +{ + +class CppCode +{ +public: + CppCode(const std::string &varname, const Code *code) : _varname{varname}, _code{code} + { + // DO NOTHING + } + +public: + void dump(std::ostream &) const; + +private: + const std::string _varname; + const Code *_code; +}; + +} // namespace enco + +static inline std::ostream &operator<<(std::ostream &os, const enco::CppCode &code) +{ + code.dump(os); + return os; +} + +#endif // __ENCO_CPP_CODE_H__ diff --git a/compiler/enco/core/src/CppGen/Host.cpp b/compiler/enco/core/src/CppGen/Host.cpp new file mode 100644 index 000000000..37e0583d7 --- /dev/null +++ b/compiler/enco/core/src/CppGen/Host.cpp @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "Host.h" + +#include <pp/EnclosedDocument.h> + +#include <stdex/Memory.h> + +#include <map> +#include <string> + +namespace +{ + +/** + * @brief Data transfer between flat arrays + * + * Transfer(from, into) denotes the following C code: + * dst[into] = src[from]; + */ +class Transfer +{ +public: + Transfer(uint32_t from, uint32_t into) : _from{from}, _into{into} + { + // DO NOTHING + } + +public: + uint32_t from(void) const { return _from; } + uint32_t into(void) const { return _into; } + +private: + uint32_t _from; + uint32_t _into; +}; + +using TransferSequence = std::vector<Transfer>; + +/** + * @brief Convert Shuffle instruction as a sequence of data transfer + */ +TransferSequence as_transfer_sequence(const coco::Shuffle *shuffle) +{ + TransferSequence seq; + + for (const auto &dst : shuffle->range()) + { + const auto src = shuffle->at(dst); + seq.emplace_back(src.value(), dst.value()); + } + + return seq; +} + +/** + * Given a sequence of N data transfers, + * find_loop tries to compute count, src_step, dst_step that satisfies + * the following properties: + * + * First, N should be a multiple of count. + * Below we refer to that multiplier as 'window' (= N / count) + * + * Second, + * for all n in [0, count), + * for all k in [0, window), + * from[n * window + k] == from[k] + src_step, and + * into[n * window + k] == into[k] + dst_step + */ +bool find_loop(TransferSequence::const_iterator beg, TransferSequence::const_iterator end, + uint32_t *p_count, uint32_t *p_src_step, uint32_t *p_dst_step) +{ + assert(p_count != nullptr); + assert(p_src_step != nullptr); + assert(p_dst_step != nullptr); + + const uint32_t size = end - beg; + + for (uint32_t window = 1; window <= size; ++window) + { + if (size % window != 0) + { + continue; + } + + auto src_step_at = [&beg, window](uint32_t n) { + return (beg + n)->from() - (beg + n - window)->from(); + }; + + auto dst_step_at = [&beg, window](uint32_t n) { + return (beg + n)->into() - (beg + n - window)->into(); + }; + + const uint32_t count = size / window; + const uint32_t src_step = src_step_at(window); + const uint32_t dst_step = dst_step_at(window); + + bool consistent = true; + + for (uint32_t n = window + 1; n < size; ++n) + { + if ((src_step_at(n) != src_step) || (dst_step_at(n) != dst_step)) + { + consistent = false; + break; + } + } + + if (consistent) + { + *p_count = count; + *p_src_step = src_step; + *p_dst_step = dst_step; + return true; + } + } + + return false; +} + +/** + * @brief Single transfer loop (a triple of count, source step, detination step) + */ +class TransferLoop +{ +public: + class Step + { + public: + Step(uint32_t src, uint32_t dst) : _src{src}, _dst{dst} + { + // DO NOTHING + } + + public: + uint32_t src(void) const { return _src; } + uint32_t dst(void) const { return _dst; } + + private: + uint32_t _src; + uint32_t _dst; + }; + +public: + TransferLoop(uint32_t count, uint32_t src_step, uint32_t dst_step) + : _count{count}, _step{src_step, dst_step} + { + // DO NOTHING + } + +public: + uint32_t count(void) const { return _count; } + const Step &step(void) const { return _step; } + +private: + uint32_t _count; + Step _step; +}; + +/** + * @brief Nested transfer loops + */ +using TransferNest = std::vector<TransferLoop>; + +/** + * @brief Construct nested transfer loop-nest that correponds to a given Shuffle instruction + */ +TransferNest as_nest(const TransferSequence &seq) +{ + TransferNest nest; + + auto beg = seq.begin(); + auto end = seq.end(); + + uint32_t window = end - beg; + uint32_t count = 0; + uint32_t src_step = 0; + uint32_t dst_step = 0; + + while ((window > 1) && find_loop(beg, end, &count, &src_step, &dst_step)) + { + assert(window % count == 0); + + window /= count; + end = beg + window; + + nest.emplace_back(count, src_step, dst_step); + } + + return nest; +}; + +uint32_t loop_count(const TransferNest &nest) +{ + uint32_t count = 1; + + for (const auto &loop : nest) + { + count *= loop.count(); + } + + return count; +}; + +class InstrPrinter : public coco::Instr::Visitor<pp::LinearDocument> +{ +public: + InstrPrinter(const enco::MemoryContext &mem) : _mem(mem) + { + // DO NOTHING + } + +private: + pp::LinearDocument visit(const coco::Shuffle *shuffle) override + { + auto from = shuffle->from(); + auto into = shuffle->into(); + + // + // Analyze 'Shuffle' pattern, and convert it as nested loops + // + auto tseq = as_transfer_sequence(shuffle); + auto nest = as_nest(tseq); + assert(tseq.size() % loop_count(nest) == 0); + uint32_t window = tseq.size() / loop_count(nest); + + // + // Generate loop body + // + pp::EnclosedDocument loop_body; + + auto var_at = [](uint32_t lv) { return pp::fmt("_", lv); }; + + for (uint32_t lv = 0; lv < nest.size(); ++lv) + { + auto var = var_at(lv); + + loop_body.front().append("for (uint32_t ", var, " = 0; ", var, " < ", nest.at(lv).count(), + "; ++", var, ") {"); + loop_body.front().indent(); + + loop_body.back().append("}"); + loop_body.back().indent(); + } + + std::string src_index = "0"; + std::string dst_index = "0"; + + for (uint32_t lv = 0; lv < nest.size(); ++lv) + { + src_index += pp::fmt(" + ", nest.at(lv).step().src(), " * ", var_at(lv)); + dst_index += pp::fmt(" + ", nest.at(lv).step().dst(), " * ", var_at(lv)); + } + + for (uint32_t n = 0; n < window; ++n) + { + const auto src_base = pp::fmt("reinterpret_cast<const float *>(", _mem.base(from), ")"); + const auto dst_base = pp::fmt("reinterpret_cast<float *>(", _mem.base(into), ")"); + + loop_body.front().append(dst_base, "[", dst_index, " + ", tseq.at(n).into(), "] = ", src_base, + "[", src_index, " + ", tseq.at(n).from(), "];"); + } + + pp::LinearDocument res; + res.append(loop_body); + return res; + } + +private: + const enco::MemoryContext &_mem; +}; + +} // namespace + +namespace enco +{ + +std::unique_ptr<pp::MultiLineText> HostBlockCompiler::compile(const coco::Block *blk) const +{ + InstrPrinter prn{_mem}; + + auto res = stdex::make_unique<pp::LinearDocument>(); + + for (auto ins = blk->instr()->head(); ins; ins = ins->next()) + { + res->append(ins->accept(prn)); + } + + return std::move(res); +} + +} // namespace enco diff --git a/compiler/enco/core/src/CppGen/Host.h b/compiler/enco/core/src/CppGen/Host.h new file mode 100644 index 000000000..0adb7fe1f --- /dev/null +++ b/compiler/enco/core/src/CppGen/Host.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ENCO_CPP_GEN_HOST_H__ +#define __ENCO_CPP_GEN_HOST_H__ + +#include "CppGen/MemoryContext.h" + +#include <coco/IR.h> +#include <pp/MultiLineText.h> + +namespace enco +{ + +/*** + * @brief Generate C++ code that does not depend on Anroid NN API + */ +class HostBlockCompiler +{ +public: + HostBlockCompiler(const enco::MemoryContext &mem) : _mem(mem) + { + // DO NOTHING + } + +public: + std::unique_ptr<pp::MultiLineText> compile(const coco::Block *blk) const; + +private: + const enco::MemoryContext &_mem; +}; + +} // namespace enco + +#endif // __ENCO_CPP_GEN_HOST_H__ diff --git a/compiler/enco/core/src/CppGen/MemoryContext.cpp b/compiler/enco/core/src/CppGen/MemoryContext.cpp new file mode 100644 index 000000000..e522968a8 --- /dev/null +++ b/compiler/enco/core/src/CppGen/MemoryContext.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "MemoryContext.h" + +#include <cassert> + +namespace enco +{ + +bool MemoryContext::member(const coco::Bag *bag) const +{ + // NOTE _base and _size SHOULD BE consistent + if (_base.find(bag) != _base.end()) + { + assert(_size.find(bag) != _size.end()); + return true; + } + + assert(_size.find(bag) == _size.end()); + return false; +} + +void MemoryContext::base(const coco::Bag *bag, const std::string &exp) { _base[bag] = exp; } +void MemoryContext::size(const coco::Bag *bag, const std::string &exp) { _size[bag] = exp; } + +} // namespace enco diff --git a/compiler/enco/core/src/CppGen/MemoryContext.h b/compiler/enco/core/src/CppGen/MemoryContext.h new file mode 100644 index 000000000..99c20f3e8 --- /dev/null +++ b/compiler/enco/core/src/CppGen/MemoryContext.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ENCO_CPP_GEN_MEMORY_CONTEXT_H__ +#define __ENCO_CPP_GEN_MEMORY_CONTEXT_H__ + +#include <coco/IR/Bag.h> + +#include <string> +#include <map> + +namespace enco +{ + +/** + * @brief Record C/C++ expression that denotes the base and size of memory region + * dedicated to each bag + */ +class MemoryContext +{ +public: + /** + * @brief Check whether a base/size expression for a given bag + */ + bool member(const coco::Bag *bag) const; + +public: + void base(const coco::Bag *bag, const std::string &exp); + void size(const coco::Bag *bag, const std::string &exp); + +public: + const std::string &base(const coco::Bag *bag) const { return _base.at(bag); } + const std::string &size(const coco::Bag *bag) const { return _size.at(bag); } + +private: + std::map<const coco::Bag *, std::string> _base; + std::map<const coco::Bag *, std::string> _size; +}; + +} // namespace enco + +#endif // __ENCO_CPP_GEN_MEMORY_CONTEXT_H__ diff --git a/compiler/enco/core/src/CppGen/Subnet.cpp b/compiler/enco/core/src/CppGen/Subnet.cpp new file mode 100644 index 000000000..9a636c6ae --- /dev/null +++ b/compiler/enco/core/src/CppGen/Subnet.cpp @@ -0,0 +1,422 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "CppGen/Subnet.h" + +#include "Dims.h" +#include "String.h" + +#include <pp/LinearDocument.h> + +#include <stdex/Memory.h> + +#include <sstream> + +using stdex::make_unique; +using enco::concat; + +#define S(content) #content + +namespace ann +{ +static std::ostream &operator<<(std::ostream &os, const ann::OperandID &id) +{ + os << id.value(); + return os; +} +} // namespace ann + +namespace +{ + +class SubnetStructImpl final : public enco::SubnetStruct +{ +public: + SubnetStructImpl() : _dtor{pp::LinearDocument::Direction::Reverse} + { + // DO NOTHING + } + +public: + std::string model(void) const override { return "_model"; } + std::string compilation(void) const override { return "_compilation"; } + +public: + const pp::MultiLineText &def(void) const override { return _def; } + pp::LinearDocument *def(void) { return &_def; } + +public: + const pp::MultiLineText &ctor(void) const override { return _ctor; } + pp::LinearDocument *ctor(void) { return &_ctor; } + +public: + const pp::MultiLineText &dtor(void) const override { return _dtor; } + pp::LinearDocument *dtor(void) { return &_dtor; } + +private: + pp::LinearDocument _def; + pp::LinearDocument _ctor; + pp::LinearDocument _dtor; +}; + +struct CodeFragment +{ + virtual ~CodeFragment() = default; + + virtual void dump(pp::LinearDocument *) const = 0; +}; + +pp::LinearDocument *operator<<(pp::LinearDocument *doc, const CodeFragment &fragment) +{ + fragment.dump(doc); + return doc; +} + +const char *scalar_operand_code(const ann::DType &dtype) +{ + switch (dtype) + { + case ann::DType::S32: + return "ANEURALNETWORKS_INT32"; + default: + break; + }; + + throw std::invalid_argument("dtype"); +} + +const char *tensor_operand_code(const ann::DType &dtype) +{ + switch (dtype) + { + case ann::DType::S32: + return "ANEURALNETWORKS_TENSOR_INT32"; + case ann::DType::F32: + return "ANEURALNETWORKS_TENSOR_FLOAT32"; + default: + break; + }; + + throw std::invalid_argument("dtype"); +} + +class ScalarOperandDecl final : public CodeFragment +{ +public: + ScalarOperandDecl(const std::string &model, const ann::DType &dtype) + : _model{model}, _dtype{dtype} + { + // DO NOTHING + } + +public: + void dump(pp::LinearDocument *doc) const override + { + doc->append("{"); + doc->indent(); + doc->append("ANeuralNetworksOperandType t;"); + doc->append(); + doc->append("t.type = ", scalar_operand_code(_dtype), ";"); + doc->append("t.dimensionCount = 0;"); + doc->append("t.dimensions = nullptr;"); + doc->append("t.scale = 1.0f;"); + doc->append("t.zeroPoint = 0;"); + doc->append(); + doc->append("ANeuralNetworksModel_addOperand(", _model, ", &t);"); + doc->unindent(); + doc->append("}"); + } + +private: + std::string _model; + ann::DType _dtype; +}; + +class TensorOperandDecl final : public CodeFragment +{ +public: + TensorOperandDecl(const std::string &model, const ann::DType &dtype, + const nncc::core::ADT::tensor::Shape &shape) + : _model{model}, _dtype{dtype}, _shape{shape} + { + // DO NOTHING + } + +public: + void dump(pp::LinearDocument *doc) const override + { + const auto rank = _shape.rank(); + const auto dims = as_dims(_shape); + + assert(rank == dims.size()); + + doc->append("{"); + doc->indent(); + doc->append("uint32_t d[", rank, "] = { ", concat(", ", dims.begin(), dims.end()), " };"); + doc->append(); + doc->append("ANeuralNetworksOperandType t;"); + doc->append(); + doc->append("t.type = ", tensor_operand_code(_dtype), ";"); + doc->append("t.dimensionCount = ", rank, ";"); + doc->append("t.dimensions = d;"); + doc->append("t.scale = 1.0f;"); + doc->append("t.zeroPoint = 0;"); + doc->append(); + doc->append("ANeuralNetworksModel_addOperand(", _model, ", &t);"); + doc->unindent(); + doc->append("}"); + } + +private: + std::string _model; + ann::DType _dtype; + nncc::core::ADT::tensor::Shape _shape; +}; + +/** + * @brief Code fragment that calls ANeuralNetworksModel_setOperandValue + */ +class WeightDecl final : public CodeFragment +{ +public: + WeightDecl(const std::string &model, const ann::OperandID &id, const std::string &base, + const std::string &size) + : _model{model}, _id{id}, _base{base}, _size{size} + { + // DO NOTHING + } + +public: + void dump(pp::LinearDocument *doc) const override + { + doc->append("ANeuralNetworksModel_setOperandValue(", _model, ", ", _id.value(), ", ", _base, + ", ", _size, ");"); + } + +private: + std::string _model; + ann::OperandID _id; + std::string _base; + std::string _size; +}; + +/** + * @brief Code fragment that calls ANeuralNetworksModel_addOperation + */ +class OperationDecl final : public CodeFragment +{ +public: + OperationDecl(const std::string &model, const ann::Operation *op) : _model{model}, _op{op} + { + // DO NOTHING + } + +private: + static std::string opcode(const ann::Operation::Code &code) + { + switch (code) + { +#define ANN_OPERATION(TAG, ENUM) \ + case ann::Operation::Code::TAG: \ + return #ENUM; +#include "ANN/IR/Operation.def" +#undef ANN_OPERATION + default: + throw std::invalid_argument{"code"}; + }; + } + +public: + void dump(pp::LinearDocument *doc) const override + { + const auto in_count = _op->inputs().size(); + auto in_beg = _op->inputs().begin(); + auto in_end = _op->inputs().end(); + + const auto out_count = _op->outputs().size(); + auto out_beg = _op->outputs().begin(); + auto out_end = _op->outputs().end(); + + auto op = opcode(_op->code()); + + doc->append("{"); + doc->indent(); + doc->append("uint32_t inputs[", in_count, "] = { ", concat(", ", in_beg, in_end), " };"); + doc->append("uint32_t outputs[", out_count, "] = { ", concat(", ", out_beg, out_end), " };"); + doc->append(); + doc->append("ANeuralNetworksModel_addOperation(", _model, ", ", op, ", ", in_count, + ", inputs, ", out_count, ", outputs);"); + doc->unindent(); + doc->append("}"); + } + +private: + std::string _model; + const ann::Operation *_op; +}; + +/** + * @brief Code fragment that calls ANeuralNetworksModel_identifyInputsAndOutputs + */ +class ArgumentDecl final : public CodeFragment +{ +public: + ArgumentDecl(const std::string &mname, const ANNBinder *binder) : _mname{mname}, _binder{binder} + { + // DO NOTHING + } + +public: + void dump(pp::LinearDocument *doc) const override + { + doc->append("{"); + doc->indent(); + + auto module = _binder->module(); + const uint32_t input_count = module->input()->size(); + + doc->append("uint32_t inputs[", input_count, "];"); + for (uint32_t n = 0; n < input_count; ++n) + { + doc->append("inputs[", n, "] = ", module->input()->at(n), ";"); + } + + const uint32_t output_count = module->output()->size(); + + doc->append("uint32_t outputs[", output_count, "];"); + for (uint32_t n = 0; n < output_count; ++n) + { + doc->append("outputs[", n, "] = ", module->output()->at(n), ";"); + } + + doc->append("ANeuralNetworksModel_identifyInputsAndOutputs(", _mname, ", ", input_count, + ", inputs, ", output_count, ", outputs);"); + doc->unindent(); + doc->append("}"); + } + +private: + std::string _mname; + const ANNBinder *_binder; +}; + +} // namespace + +namespace enco +{ + +std::unique_ptr<SubnetStruct> SubnetStructBuilder::build(const ANNBinder *binder) const +{ + auto res = make_unique<SubnetStructImpl>(); + + auto mname = res->model(); + auto cname = res->compilation(); + + res->def()->append("ANeuralNetworksModel *", mname, ";"); + res->def()->append("ANeuralNetworksCompilation *", cname, ";"); + + res->ctor()->append("ANeuralNetworksModel_create(&", mname, ");"); + res->dtor()->append("ANeuralNetworksModel_free(", mname, ");"); + + binder->module()->operand()->each([&](const ann::OperandID &id, const ann::Operand *info) { + // TODO Remove dynamic cast + if (auto scalar = dynamic_cast<const ann::ScalarOperand *>(info)) + { + res->ctor() << ScalarOperandDecl{mname, scalar->dtype()}; + } + else if (auto tensor = dynamic_cast<const ann::TensorOperand *>(info)) + { + res->ctor() << TensorOperandDecl{mname, tensor->dtype(), tensor->shape()}; + } + else + { + throw std::runtime_error{"Unsupported"}; + } + + if (_weighted.find(info) != _weighted.end()) + { + const auto &base_exp = _base_exprs.at(info); + const auto &size_exp = _size_exprs.at(info); + + res->ctor() << WeightDecl{mname, id, base_exp, size_exp}; + } + }); + + for (unsigned n = 0; n < binder->module()->operation()->count(); ++n) + { + auto op = binder->module()->operation()->at(n); + res->ctor() << OperationDecl{mname, op}; + } + + // Emit ANeuralNetworksModel_identifyInputsAndOutputs call + res->ctor() << ArgumentDecl{mname, binder}; + + // Emit ANeuralNetworksModel_finish call + res->ctor()->append("ANeuralNetworksModel_finish(", mname, ");"); + + // Create compilation + res->ctor()->append("ANeuralNetworksCompilation_create(", mname, ", &", cname, ");"); + res->dtor()->append("ANeuralNetworksCompilation_free(", cname, ");"); + + // Finalize compilation + res->ctor()->append("ANeuralNetworksCompilation_finish(", cname, ");"); + + return std::move(res); +} + +std::unique_ptr<pp::MultiLineText> SubnetBlockCompiler::compile(const ANNBinder *binder) const +{ + auto res = make_unique<pp::LinearDocument>(); + + const auto compilation = _compilation_ctx.at(binder); + + res->append("ANeuralNetworksExecution *execution;"); + res->append("ANeuralNetworksEvent *event;"); + res->append(); + res->append("ANeuralNetworksExecution_create(", compilation, ", &execution);"); + + // Emit ANeuralNetworksExecution_setInput call(s) + for (uint32_t n = 0; n < binder->module()->input()->size(); ++n) + { + auto bag = binder->input(n); + auto base = _mem.base(bag); + auto size = _mem.size(bag); + + res->append("ANeuralNetworksExecution_setInput(execution, ", n, ", nullptr, ", base, ", ", size, + ");"); + } + + // Emit ANeuralNetworksExecution_setOutput call(s) + for (uint32_t n = 0; n < binder->module()->output()->size(); ++n) + { + auto bag = binder->output(n); + auto base = _mem.base(bag); + auto size = _mem.size(bag); + + res->append("ANeuralNetworksExecution_setOutput(execution, ", n, ", nullptr, ", base, ", ", + size, ");"); + } + + res->append("ANeuralNetworksExecution_startCompute(execution, &event);"); + res->append("ANeuralNetworksEvent_wait(event);"); + res->append("ANeuralNetworksEvent_free(event);"); + + res->append("ANeuralNetworksExecution_free(execution);"); + + return std::move(res); +} + +} // namespace enco diff --git a/compiler/enco/core/src/CppGen/Subnet.h b/compiler/enco/core/src/CppGen/Subnet.h new file mode 100644 index 000000000..4a5738876 --- /dev/null +++ b/compiler/enco/core/src/CppGen/Subnet.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ENCO_CPP_GEN_SUBNET_H__ +#define __ENCO_CPP_GEN_SUBNET_H__ + +#include "ANN/Binder.h" +#include "CppGen/MemoryContext.h" + +#include <pp/MultiLineText.h> +#include <map> +#include <set> + +namespace enco +{ + +/** + * @brief A C++ struct that provides Android NN model & compilation + */ +struct SubnetStruct +{ + virtual ~SubnetStruct() = default; + + /// @brief Return the field name of ANeuralNetworksModel value + virtual std::string model(void) const = 0; + /// @brief Return the field name of ANeuralNetworksCompilatoin value + virtual std::string compilation(void) const = 0; + + virtual const pp::MultiLineText &def(void) const = 0; + virtual const pp::MultiLineText &ctor(void) const = 0; + virtual const pp::MultiLineText &dtor(void) const = 0; +}; + +class SubnetStructBuilder +{ +public: + std::unique_ptr<SubnetStruct> build(const ANNBinder *binder) const; + +public: + void expr(const ann::Operand *oper, const std::string &base, const std::string &size) + { + _weighted.insert(oper); + _base_exprs[oper] = base; + _size_exprs[oper] = size; + } + +private: + std::set<const ann::Operand *> _weighted; + std::map<const ann::Operand *, std::string> _base_exprs; + std::map<const ann::Operand *, std::string> _size_exprs; +}; + +/** + * @brief Generate C++ code that invokes Android NN subnet + */ +class SubnetBlockCompiler +{ +public: + SubnetBlockCompiler(const enco::MemoryContext &mem) : _mem(mem) + { + // DO NOTHING + } + +public: + /// @brief Specify how to access ANeuralNetworksCompilation value (C expression) + void bind(const ANNBinder *binder, const std::string &exp) { _compilation_ctx[binder] = exp; } + +public: + std::unique_ptr<pp::MultiLineText> compile(const ANNBinder *binder) const; + +private: + const enco::MemoryContext &_mem; + std::map<const ANNBinder *, std::string> _compilation_ctx; +}; + +} // namespace enco + +#endif // __ENCO_CPP_GEN_SUBNET_H__ diff --git a/compiler/enco/core/src/Dims.h b/compiler/enco/core/src/Dims.h new file mode 100644 index 000000000..e0a4fd44d --- /dev/null +++ b/compiler/enco/core/src/Dims.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __DIMS_H__ +#define __DIMS_H__ + +#include <nncc/core/ADT/tensor/Shape.h> + +static inline std::vector<uint32_t> as_dims(const nncc::core::ADT::tensor::Shape &shape) +{ + std::vector<uint32_t> res; + + for (uint32_t axis = 0; axis < shape.rank(); ++axis) + { + res.emplace_back(shape.dim(axis)); + } + + return res; +} + +#endif // __DIMS_H__ diff --git a/compiler/enco/core/src/IRUtils.cpp b/compiler/enco/core/src/IRUtils.cpp new file mode 100644 index 000000000..59f6b0dbe --- /dev/null +++ b/compiler/enco/core/src/IRUtils.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "IRUtils.h" + +#include <cassert> + +namespace enco +{ + +/** + * @brief Substitute all the USE occurrences of an object with another object + * @param from Object to be replaced + * @param into Object to be used instead + * NOTE This maybe used when something like -- 'from' will be removed so we need + * to replace object Consumers that use 'from' to 'into' + * EXAMPLE + * { + * subst(child, bigone); + * m->entity()->object()->destroy(child); + * } + * This code will change all the Consumers that use 'child' to 'bigone' and + * destroy the 'child' object. + */ +void subst(coco::Object *from, coco::Object *into) +{ + assert(from != into); + + while (!from->uses()->empty()) + { + auto use = *(from->uses()->begin()); + + use->value(into); + } +} + +std::vector<coco::Instr *> instr_sequence(coco::Module *m) +{ + std::vector<coco::Instr *> res; + + for (auto B = m->block()->head(); B; B = B->next()) + { + for (auto I = B->instr()->head(); I; I = I->next()) + { + res.emplace_back(I); + } + } + + return res; +} + +} // namespace enco diff --git a/compiler/enco/core/src/IRUtils.h b/compiler/enco/core/src/IRUtils.h new file mode 100644 index 000000000..da0754303 --- /dev/null +++ b/compiler/enco/core/src/IRUtils.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ENCO_IR_UTILS_H__ +#define __ENCO_IR_UTILS_H__ + +#include <coco/IR.h> + +#include <vector> + +namespace enco +{ + +/** + * @brief Replace all the "USE" of 'from' with 'into' + * + * NOTE subst(from, into) WILL NOT update 'DEF' + */ +void subst(coco::Object *from, coco::Object *into); + +/** + * @brief Return instructions in execution order + */ +std::vector<coco::Instr *> instr_sequence(coco::Module *m); + +} // namespace enco + +#endif // __ENCO_IR_UTILS_H__ diff --git a/compiler/enco/core/src/IRValidator.cpp b/compiler/enco/core/src/IRValidator.cpp new file mode 100644 index 000000000..1337b88e4 --- /dev/null +++ b/compiler/enco/core/src/IRValidator.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "IRValidator.h" + +#include <cassert> + +namespace enco +{ + +coco::FeatureShape output_shape(coco::Conv2D *conv2D) +{ + auto load = conv2D->arg()->asLoad(); + assert(load); + + auto ifm = load->object()->asFeature(); + assert(ifm); + + auto ker = conv2D->ker(); + auto stride = conv2D->stride(); + auto pad = conv2D->pad(); + + auto striding_width = ifm->shape().width() + pad->left() + pad->right() - ker->shape().width(); + auto striding_height = ifm->shape().height() + pad->top() + pad->bottom() - ker->shape().height(); + + // Normally the formula is round(striding_width)/stride->horizontal. + // in coco IR, striding_width should be a multiple of stride->horizontal(), so round(...) was + // removed. So does striding_height. + assert(striding_width % stride->horizontal() == 0); + assert(striding_height % stride->vertical() == 0); + + auto ofm_width = striding_width / stride->horizontal() + 1; + auto ofm_height = striding_height / stride->vertical() + 1; + + return coco::FeatureShape(ifm->shape().batch(), ker->shape().count(), ofm_height, ofm_width); +} + +bool validate_output_shape(Code *code) +{ + auto module = code->module(); + + // for each eval ( conv2d ( ... ) ), check the output shape of conv2D matches output of eval + for (auto blk = module->block()->head(); blk; blk = blk->next()) + { + for (auto instr = blk->instr()->head(); instr; instr = instr->next()) + { + auto eval = instr->asEval(); + if (eval == nullptr) + continue; + + auto op = eval->op(); + if (!op->asConv2D()) + continue; + + auto conv2D = op->asConv2D(); + auto expected_shape = output_shape(conv2D); + + auto eval_out = eval->out()->asFeature(); + assert(eval_out); + + auto actual_shape = eval_out->shape(); + + if (actual_shape != expected_shape) + return false; + } + } + return true; +} + +bool validate(Code *code) { return validate_output_shape(code); } + +} // namespace enco diff --git a/compiler/enco/core/src/IRValidator.h b/compiler/enco/core/src/IRValidator.h new file mode 100644 index 000000000..f4adb0a5e --- /dev/null +++ b/compiler/enco/core/src/IRValidator.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ENCO_IR_VALIDATOR_H__ +#define __ENCO_IR_VALIDATOR_H__ + +#include "Code.h" + +namespace enco +{ + +bool validate(Code *code); + +} // namespace enco + +#endif // __ENCO_IR_VALIDATOR_H__ diff --git a/compiler/enco/core/src/IRValidator.test.cpp b/compiler/enco/core/src/IRValidator.test.cpp new file mode 100644 index 000000000..14cda6173 --- /dev/null +++ b/compiler/enco/core/src/IRValidator.test.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "IRValidator.h" + +#include "Code.h" + +#include <gtest/gtest.h> + +#include <array> + +namespace +{ + +using IntList4 = std::array<int, 4>; +using IntList2 = std::array<int, 2>; + +} // namespace + +// The layout of ifm, ker, ofm is NHWC, pad == {top, bottom, left, right}, and stride == {vertical, +// horizontal}. +std::unique_ptr<coco::Module> get_conv2D(IntList4 ifm, IntList4 ker, IntList4 ofm, IntList4 pad, + IntList2 stride) +{ + auto module = coco::Module::create(); + auto block = module->entity()->block()->create(); + auto eval = module->entity()->instr()->create<coco::Eval>(); + auto load = module->entity()->op()->create<coco::Load>(); + auto conv2D = module->entity()->op()->create<coco::Conv2D>(); + + auto ifm_obj = module->entity()->object()->create<coco::FeatureObject>(); + coco::FeatureShape ifm_shape(ifm[0], ifm[3], ifm[1], ifm[2]); + ifm_obj->layout(coco::FeatureLayouts::BHWC::create(ifm_shape)); + + auto ofm_obj = module->entity()->object()->create<coco::FeatureObject>(); + coco::FeatureShape ofm_shape(ofm[0], ofm[3], ofm[1], ofm[2]); + ofm_obj->layout(coco::FeatureLayouts::BHWC::create(ofm_shape)); + + auto ker_obj = module->entity()->object()->create<coco::KernelObject>(); + nncc::core::ADT::kernel::Shape ker_shape(ker[0], ker[3], ker[1], ker[2]); + ker_obj->layout(coco::KernelLayouts::NHWC::create(ker_shape)); + + // linking entities + module->block()->append(block); + block->instr()->append(eval); + eval->op(conv2D); + eval->out(ofm_obj); + load->object(ifm_obj); + conv2D->ker(ker_obj); + conv2D->arg(load); + + // param setting + conv2D->pad()->top(pad[0]).bottom(pad[1]).left(pad[2]).right(pad[3]); + conv2D->stride()->vertical(stride[0]).horizontal(stride[1]); + + return std::move(module); +} + +TEST(IRValidatorTest, conv2D_simple) +{ + auto ifm_nhwc = IntList4{1, 3, 3, 2}; + auto ker_nhwc = IntList4{1, 1, 1, 2}; + auto ofm_nhwc = IntList4{1, 3, 3, 1}; + + auto pad_tblr = IntList4{0, 0, 0, 0}; + auto stride_vh = IntList2{1, 1}; + + auto module = get_conv2D(ifm_nhwc, ker_nhwc, ofm_nhwc, pad_tblr, stride_vh); + enco::Code code{module.get(), nullptr}; + + ASSERT_TRUE(enco::validate(&code)); +} + +TEST(IRValidatorTest, conv2D_stride_2) +{ + auto ifm_nhwc = IntList4{1, 4, 4, 3}; + auto ker_nhwc = IntList4{2, 2, 2, 3}; + auto ofm_nhwc = IntList4{1, 3, 3, 2}; + + auto pad_tblr = IntList4{1, 1, 1, 1}; + auto stride_vh = IntList2{2, 2}; + + auto module = get_conv2D(ifm_nhwc, ker_nhwc, ofm_nhwc, pad_tblr, stride_vh); + enco::Code code{module.get(), nullptr}; + + ASSERT_TRUE(enco::validate(&code)); +} + +TEST(IRValidatorTest, conv2D_output_batch_check) +{ + auto ifm_nhwc = IntList4{1, 2, 2, 2}; + auto ker_nhwc = IntList4{3, 1, 1, 2}; // expected output depth is 3 + auto ofm_nhwc = IntList4{1, 2, 2, 1}; // but 1 + + auto pad_tblr = IntList4{0, 0, 0, 0}; + auto stride_vh = IntList2{1, 1}; + + auto module = get_conv2D(ifm_nhwc, ker_nhwc, ofm_nhwc, pad_tblr, stride_vh); + enco::Code code{module.get(), nullptr}; + + ASSERT_FALSE(enco::validate(&code)); +} + +TEST(IRValidatorTest, conv2D_wrong_HW) +{ + auto ifm_nhwc = IntList4{1, 2, 2, 1}; + auto ker_nhwc = IntList4{1, 2, 2, 1}; + auto ofm_nhwc = IntList4{1, 1, 1, 1}; // HW should be 2, 2 + + auto pad_tblr = IntList4{1, 1, 1, 1}; + auto stride_vh = IntList2{2, 2}; + + auto module = get_conv2D(ifm_nhwc, ker_nhwc, ofm_nhwc, pad_tblr, stride_vh); + enco::Code code{module.get(), nullptr}; + + ASSERT_FALSE(enco::validate(&code)); +} diff --git a/compiler/enco/core/src/Pass.h b/compiler/enco/core/src/Pass.h new file mode 100644 index 000000000..d78cfaad3 --- /dev/null +++ b/compiler/enco/core/src/Pass.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. 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 __ENCO_PASS_H__ +#define __ENCO_PASS_H__ + +#include "Session.h" + +#include <string> + +namespace enco +{ + +class Pass +{ +public: + class Name + { + public: + Name(const std::string &content) : _content{content} + { + // DO NOTHING + } + + Name(const Name &) = default; + Name(Name &&) = default; + + ~Name() = default; + + public: + const std::string &content(void) const { return _content; } + + private: + std::string _content; + }; + +public: + Pass(const Name &name) : _name{name} + { + // DO NOTHING + } + + Pass(const Pass &) = delete; + Pass(Pass &&) = delete; + + virtual ~Pass() = default; + +public: + const Name &name(void) const { return _name; } + +public: + virtual void run(const SessionID &) const = 0; + +private: + Name _name; +}; + +static inline Pass::Name pass_name(const std::string &name) { return Pass::Name{name}; } + +} // namespace enco + +#define PASS_CTOR(NAME) \ + NAME() : enco::Pass { enco::pass_name(#NAME) } + +#endif // __ENCO_PASS_H__ diff --git a/compiler/enco/core/src/Pass.test.cpp b/compiler/enco/core/src/Pass.test.cpp new file mode 100644 index 000000000..112bd7478 --- /dev/null +++ b/compiler/enco/core/src/Pass.test.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. 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 "Pass.h" + +#include <gtest/gtest.h> + +namespace +{ + +struct ExamplePass final : public enco::Pass +{ + PASS_CTOR(ExamplePass) + { + // DO NOTHING + } + + void run(const enco::SessionID &) const override { return; } +}; + +} // namespace + +TEST(PASS, ctor) +{ + ExamplePass pass; + + ASSERT_EQ(pass.name().content(), "ExamplePass"); +} diff --git a/compiler/enco/core/src/Pipeline.h b/compiler/enco/core/src/Pipeline.h new file mode 100644 index 000000000..8ab43c16a --- /dev/null +++ b/compiler/enco/core/src/Pipeline.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. 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 __ENCO_PIPELINE_H__ +#define __ENCO_PIPELINE_H__ + +#include "Pass.h" + +#include <memory> +#include <vector> +#include <cstdint> + +namespace enco +{ + +class Pipeline +{ +public: + uint32_t size(void) const { return _passes.size(); } + +public: + const Pass &at(uint32_t n) const { return *(_passes.at(n)); } + +public: + void append(std::unique_ptr<Pass> &&pass) { _passes.emplace_back(std::move(pass)); } + +private: + std::vector<std::unique_ptr<Pass>> _passes; +}; + +} // namespace enco + +#endif // __ENCO_PIPELINE_H__ diff --git a/compiler/enco/core/src/Pipeline.test.cpp b/compiler/enco/core/src/Pipeline.test.cpp new file mode 100644 index 000000000..1cd730e98 --- /dev/null +++ b/compiler/enco/core/src/Pipeline.test.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. 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 "Pipeline.h" + +#include <gtest/gtest.h> + +TEST(PIPELINE, default_ctor) +{ + enco::Pipeline pipeline; + + ASSERT_EQ(pipeline.size(), 0); +} diff --git a/compiler/enco/core/src/Session.cpp b/compiler/enco/core/src/Session.cpp new file mode 100644 index 000000000..034f23892 --- /dev/null +++ b/compiler/enco/core/src/Session.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "Session.h" + +#include <stdex/Memory.h> + +#include <map> +#include <memory> + +using stdex::make_unique; + +namespace +{ + +std::map<enco::SessionID, std::unique_ptr<enco::Code>> sess_to_code; +std::map<const coco::Module *, enco::SessionID> module_to_sess; +std::map<const coco::Data *, enco::SessionID> data_to_sess; + +} // namespace + +namespace enco +{ + +SessionID make_session(coco::Module *m, coco::Data *d) +{ + static uint32_t sess = 0; + SessionID curr{sess++}; + + sess_to_code[curr] = make_unique<Code>(m, d); + module_to_sess[m] = curr; + data_to_sess[d] = curr; + + return curr; +} + +SessionID session(const coco::Module *m) { return module_to_sess.at(m); } +SessionID session(const coco::Data *d) { return data_to_sess.at(d); } + +coco::Module *module(const SessionID &sess) { return sess_to_code.at(sess)->module(); } +coco::Data *data(const SessionID &sess) { return sess_to_code.at(sess)->data(); } + +Code *code(const SessionID &sess) { return sess_to_code.at(sess).get(); } + +} // namespace enco diff --git a/compiler/enco/core/src/Session.h b/compiler/enco/core/src/Session.h new file mode 100644 index 000000000..b6d502f3b --- /dev/null +++ b/compiler/enco/core/src/Session.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ENCO_SESSION_H__ +#define __ENCO_SESSION_H__ + +#include "Code.h" + +namespace enco +{ + +// TODO Rewrite this definition +using SessionID = uint32_t; + +SessionID make_session(coco::Module *m, coco::Data *d); + +SessionID session(const coco::Module *m); +SessionID session(const coco::Data *d); + +coco::Module *module(const SessionID &); +coco::Data *data(const SessionID &); + +static inline coco::Module *module(const coco::Data *d) { return module(session(d)); } +static inline coco::Data *data(const coco::Module *m) { return data(session(m)); } + +// WARN This API is introduced just for backward compatibility +// Do NOT use this anymore as it will be removed +Code *code(const SessionID &); + +} // namespace enco + +#endif // __ENCO_SESSION_H__ diff --git a/compiler/enco/core/src/String.h b/compiler/enco/core/src/String.h new file mode 100644 index 000000000..0f04f1ffe --- /dev/null +++ b/compiler/enco/core/src/String.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ENCO_STRING_H__ +#define __ENCO_STRING_H__ + +// +// String-manipulating routines +// +#include <ostream> +#include <sstream> + +#include <string> + +namespace enco +{ + +template <typename It> void concat(std::ostream &os, const std::string &sep, It beg, It end) +{ + uint32_t count = 0; + + for (auto it = beg; it != end; ++it, ++count) + { + if (count == 0) + { + os << *it; + } + else + { + os << sep << *it; + } + } +} + +template <typename It> std::string concat(const std::string &sep, It beg, It end) +{ + std::stringstream ss; + concat(ss, sep, beg, end); + return ss.str(); +} + +} // namespace enco + +#endif // __ENCO_STRING_H__ diff --git a/compiler/enco/core/src/Support/Debugging.cpp b/compiler/enco/core/src/Support/Debugging.cpp new file mode 100644 index 000000000..bd65a27d8 --- /dev/null +++ b/compiler/enco/core/src/Support/Debugging.cpp @@ -0,0 +1,533 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "Debugging.h" + +#include <pp/LinearDocument.h> +#include <pp/MultiLineTextUtils.h> + +#include <stack> + +#include <iostream> + +#define DEBUGGING_API_P(NAME, TYPE, VAR) \ + static void _##NAME(const TYPE *); \ + void NAME(long p) { NAME(reinterpret_cast<const TYPE *>(p)); } \ + void NAME(const TYPE *p) \ + { \ + if (p == nullptr) \ + { \ + std::cout << "(nullptr)" << std::endl; \ + } \ + else \ + { \ + _##NAME(p); \ + } \ + } \ + void _##NAME(const TYPE *VAR) + +namespace +{ + +class SectionBuilder +{ +public: + SectionBuilder(const std::string &tag) : _tag{tag} + { + // DO NOTHING + } + +public: + template <typename Callback> pp::LinearDocument build(Callback cb) const + { + pp::LinearDocument res; + + res.append(_tag, " {"); + res.indent(); + + cb(res); + + res.unindent(); + res.append("}"); + + return res; + } + +private: + std::string _tag; +}; + +template <typename Callback> +pp::LinearDocument operator<<(const SectionBuilder &builder, Callback cb) +{ + return builder.build(std::forward<Callback>(cb)); +} + +SectionBuilder section(const std::string &tag) { return SectionBuilder{tag}; } +} + +/** + * SECTION: Bag + */ +namespace +{ + +pp::LinearDocument describe(const coco::Bag *bag) +{ + pp::LinearDocument doc; + + doc.append("addr: ", bag); + doc.append("size: ", bag->size()); + // TODO Print Read + // TODO Print Update + // TODO Print Dep + return doc; +} + +} // namespace + +DEBUGGING_API_P(enco_dump_all_bags, coco::Module, m) +{ + for (uint32_t n = 0; n < m->entity()->bag()->size(); ++n) + { + auto bag = m->entity()->bag()->at(n); + assert(bag != nullptr); + + auto set = [bag](pp::LinearDocument &doc) { doc.append(describe(bag)); }; + auto desc = section("bag").build(set); + + std::cout << desc << std::endl; + } +} + +/** + * SECTION: Object + */ +namespace +{ +std::string op_kind(const coco::Op *op); + +/** + * @brief Return the def(producer) type of object + */ +std::string def_kind(const coco::Def *def) +{ + if (def) + { + if (auto instr = dynamic_cast<coco::Instr *>(def->producer())) + { + std::stringstream ss; + + if (auto eval = instr->asEval()) + { + ss << op_kind(eval->op()) << "(" << instr << ")"; + return ss.str(); + } + else if (instr->asCopy()) + { + ss << "Copy(" << instr << ")"; + return ss.str(); + } + else if (instr->asShuffle()) + { + ss << "Shuffle(" << instr << ")"; + return ss.str(); + } + } + else + { + return "(unknown)"; + } + } + + return "(none)"; +} + +pp::LinearDocument describe(const coco::Object *obj) +{ + pp::LinearDocument doc; + + doc.append("addr: ", obj); + doc.append("bag: ", obj->bag()); + doc.append("producer: ", def_kind(obj->def())); + // TODO Show Uses + // TODO Show FeatureObject/KernelObect info + + return doc; +} + +} // namespace + +DEBUGGING_API_P(enco_dump_all_objects, coco::Module, m) +{ + for (uint32_t n = 0; n < m->entity()->object()->size(); ++n) + { + auto obj = m->entity()->object()->at(n); + assert(obj != nullptr); + + auto set = [obj](pp::LinearDocument &doc) { doc.append(describe(obj)); }; + auto desc = section("object").build(set); + + std::cout << desc << std::endl; + } +} + +/** + * SECTION: Op + */ +namespace +{ + +struct OpTree +{ +public: + OpTree(const coco::Op *op) : _op{op} + { + // DO NOTHING + } + +public: + const coco::Op *root(void) const { return _op; } + +private: + const coco::Op *_op; +}; + +std::string op_kind(const coco::Op *op) +{ + struct OpKind : public coco::Op::Visitor<std::string> + { + std::string visit(const coco::Load *) override { return "Load"; } + std::string visit(const coco::Conv2D *) override { return "Conv2D"; } + std::string visit(const coco::MaxPool2D *) override { return "MaxPool2D"; } + std::string visit(const coco::AvgPool2D *) override { return "AvgPool2D"; } + std::string visit(const coco::PadF *) override { return "PadF"; } + std::string visit(const coco::ReLU *) override { return "ReLU"; } + std::string visit(const coco::Add *) override { return "Add"; } + std::string visit(const coco::Mul *) override { return "Mul"; } + std::string visit(const coco::ConcatF *) override { return "ConcatF"; } + std::string visit(const coco::Sub *) override { return "Sub"; } + std::string visit(const coco::Sqrt *) override { return "Sqrt"; } + std::string visit(const coco::Div *) override { return "Div"; } + }; + + OpKind v; + + return op->accept(v); +} + +pp::LinearDocument describe(const coco::Padding2D *pad) +{ + pp::LinearDocument doc; + + doc.append("top: ", pad->top()); + doc.append("bottom: ", pad->bottom()); + doc.append("left: ", pad->left()); + doc.append("right: ", pad->right()); + + return doc; +} + +pp::LinearDocument describe(const coco::Stride2D *stride) +{ + pp::LinearDocument doc; + + doc.append("vertical: ", stride->vertical()); + doc.append("horizontal ", stride->horizontal()); + + return doc; +} + +pp::LinearDocument describe(const coco::Conv2D *conv) +{ + pp::LinearDocument doc; + + doc.append("arg: ", conv->arg()); + doc.append("ker: ", conv->ker()); + doc.append("group: ", conv->group()); + + if (auto pad = conv->pad()) + { + auto set = [pad](pp::LinearDocument &doc) { doc.append(describe(pad)); }; + auto desc = section("pad").build(set); + doc.append(desc); + } + + if (auto stride = conv->stride()) + { + auto set = [stride](pp::LinearDocument &doc) { doc.append(describe(stride)); }; + auto desc = section("stride").build(set); + doc.append(desc); + } + + return doc; +} + +pp::LinearDocument describe(const coco::Op *op) +{ + pp::LinearDocument doc; + + doc.append("addr: ", op); + doc.append("kind: ", op_kind(op)); + doc.append("parent(instr): ", op->parent()); + doc.append("up(op): ", op->up()); + + if (auto conv = op->asConv2D()) + { + auto set = [conv](pp::LinearDocument &doc) { doc.append(describe(conv)); }; + auto desc = section("conv2d").build(set); + doc.append(desc); + } + else if (auto load = op->asLoad()) + { + auto set = [load](pp::LinearDocument &doc) { doc.append(describe(load->object())); }; + auto desc = section("load").build(set); + doc.append(desc); + } + + return doc; +} + +pp::LinearDocument describe(const OpTree &t, bool verbose = false) +{ + pp::LinearDocument doc; + + struct Frame + { + public: + Frame(const coco::Op *op) : _op{op}, _indicator{0} + { + // op SHOULD BE valid + assert(_op != nullptr); + } + + public: + /** + * @brief Return a pointer to coco::Op of interest + */ + const coco::Op *op(void) const { return _op; } + + /** + * @brief Return the indicator + * + * Let's assume that the arity of a coco::Op of interest is N + * INDICATOR 0 -> Print the op itself + * INDICATOR 1 -> Print the first argument + * ... + * INDICATOR N -> Print the N-th argument + * INDICATOR N + 1 -> Done + */ + uint32_t indicator(void) const { return _indicator; } + + public: + void advance(void) { _indicator += 1; } + + private: + const coco::Op *_op; + uint32_t _indicator; + }; + + std::stack<Frame> stack; + + stack.emplace(t.root()); + + while (stack.size() > 0) + { + auto op = stack.top().op(); + uint32_t indicator = stack.top().indicator(); + + if (indicator == 0) + { + doc.append(op_kind(op), " (", op, ")"); + + doc.indent(); + stack.top().advance(); + + // TODO Need to update it to better design for verbose flag + if (verbose) + { + auto set = [op](pp::LinearDocument &doc) { doc.append(describe(op)); }; + auto desc = section("op").build(set); + doc.append(desc); + } + } + else if (indicator < op->arity() + 1) + { + stack.top().advance(); + stack.emplace(op->arg(indicator - 1)); + } + else + { + assert(indicator == op->arity() + 1); + doc.unindent(); + stack.pop(); + } + } + + return doc; +} + +} // namespace + +DEBUGGING_API_P(enco_dump_op, coco::Op, op) +{ + { + std::cout << describe(op) << std::endl; + } +} + +DEBUGGING_API_P(enco_dump_op_tree, coco::Op, op) +{ + { + std::cout << describe(OpTree(op)) << std::endl; + } +} + +DEBUGGING_API_P(enco_dump_all_ops, coco::Module, m) +{ + SectionBuilder section_builder{"op"}; + + for (uint32_t n = 0; n < m->entity()->op()->size(); ++n) + { + auto op = m->entity()->op()->at(n); + assert(op != nullptr); + + auto desc = section("op").build([op](pp::LinearDocument &doc) { doc.append(describe(op)); }); + + std::cout << desc << std::endl; + } +} + +/** + * SECTION: Instr + */ +namespace +{ + +std::string kind(const coco::Instr *ins) +{ + struct InstrKind : public coco::Instr::Visitor<std::string> + { + std::string visit(const coco::Eval *) override { return "Eval"; } + std::string visit(const coco::Copy *) override { return "Copy"; } + std::string visit(const coco::Shuffle *) override { return "Shuffle"; } + }; + + InstrKind v; + + return ins->accept(v); +} + +pp::LinearDocument describe(const coco::Instr *ins, bool verbose = false) +{ + pp::LinearDocument doc; + + doc.append("addr: ", ins); + doc.append("kind: ", kind(ins)); + doc.append("parent: ", ins->parent()); + + // TODO Need to update it to better design for verbose flag + if (verbose) + { + if (auto eval = ins->asEval()) + { + auto optset = [eval, verbose](pp::LinearDocument &doc) { + doc.append(describe(OpTree(eval->op()), verbose)); + }; + auto optdesc = section("op").build(optset); + doc.append(optdesc); + + auto outset = [eval](pp::LinearDocument &doc) { doc.append(describe(eval->out())); }; + auto outdesc = section("out").build(outset); + doc.append(outdesc); + } + else if (auto copy = ins->asCopy()) + { + auto from = [copy](pp::LinearDocument &doc) { doc.append(describe(copy->from())); }; + auto into = [copy](pp::LinearDocument &doc) { doc.append(describe(copy->into())); }; + + auto fdesc = section("from").build(from); + doc.append(fdesc); + + auto idesc = section("into").build(into); + doc.append(idesc); + } + } + + return doc; +} + +} // namespace + +DEBUGGING_API_P(enco_dump_all_instrs, coco::Module, m) +{ + for (uint32_t n = 0; n < m->entity()->instr()->size(); ++n) + { + auto ins = m->entity()->instr()->at(n); + assert(ins != nullptr); + + auto setter = [ins](pp::LinearDocument &doc) { doc.append(describe(ins)); }; + auto desc = section("instr").build(setter); + + std::cout << desc << std::endl; + } +} + +DEBUGGING_API_P(enco_dump_all_instrs_v, coco::Module, m) +{ + for (uint32_t n = 0; n < m->entity()->instr()->size(); ++n) + { + auto ins = m->entity()->instr()->at(n); + assert(ins != nullptr); + + auto setter = [ins](pp::LinearDocument &doc) { doc.append(describe(ins, true)); }; + auto desc = section("instr").build(setter); + + std::cout << desc << std::endl; + } +} + +DEBUGGING_API_P(enco_dump_instr, coco::Instr, ins) +{ + auto setter = [ins](pp::LinearDocument &doc) { doc.append(describe(ins, true)); }; + auto desc = section("instr").build(setter); + + std::cout << desc << std::endl; +} + +/** + * SECTION: Block + */ +namespace +{ + +pp::LinearDocument describe(const coco::Block *blk) +{ + pp::LinearDocument doc; + + for (auto ins = blk->instr()->head(); ins; ins = ins->next()) + { + auto setter = [ins](pp::LinearDocument &doc) { doc.append(describe(ins)); }; + auto desc = section("instr").build(setter); + doc.append(desc); + } + + return doc; +} + +} // namespace + +DEBUGGING_API_P(enco_dump_block, coco::Block, blk) { std::cout << describe(blk) << std::endl; } diff --git a/compiler/enco/core/src/Support/Debugging.h b/compiler/enco/core/src/Support/Debugging.h new file mode 100644 index 000000000..c28356e76 --- /dev/null +++ b/compiler/enco/core/src/Support/Debugging.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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. + */ + +/** + * @file Debugging.h + * @brief This file includes various interactive debugging helpers + */ + +#ifndef __ENCO_SUPPORT_DEBUGGING_H__ +#define __ENCO_SUPPORT_DEBUGGING_H__ + +#include <coco/IR.h> + +static_assert(sizeof(long) == sizeof(void *), "sizeof(long) == sizeof(pointer)"); + +/** + * Debugging API with a single pointer argument + */ +#define DEBUGGING_API_P(NAME, TYPE) \ + void NAME(const TYPE *); \ + void NAME(long); + +/** + * Print the details of all the allocated coco::Bag in coco::Module + * + * (gdb) call enco_dump_all_bags(bag->module()) + * (gdb) call enco_dump_all_bags(0x...) + */ +DEBUGGING_API_P(enco_dump_all_bags, coco::Module); + +/** + * Print the details of all the allocated coco::Object in coco::Module + * + * (gdb) call enco_dump_all_objects(obj->module()) + * (gdb) call enco_dump_all_objects(0x...) + */ +DEBUGGING_API_P(enco_dump_all_objects, coco::Module); + +/** + * Print the details of coco::Op + * + * (gdb) call enco_dump_op(op) + * (gdb) call enco_dump_op(0x....) + */ +DEBUGGING_API_P(enco_dump_op, coco::Op); + +/** + * Print the (simplified) tree layout of coco::Op + * + * (gdb) call enco_dump_op_tree(op) + * (gdb) call enco_dump_op_tree(0x....) + */ +DEBUGGING_API_P(enco_dump_op_tree, coco::Op); + +/** + * Print the details of all the allocated coco::Op in coco::Module + * + * (gdb) call enco_dump_all_ops(op->module()) + * (gdb) call enco_dump_all_ops(0x....) + */ +DEBUGGING_API_P(enco_dump_all_ops, coco::Module); + +/** + * Print the details of all the allocated coco::Instr in coco::Module + * + * (gdb) call enco_dump_all_instrs(instr->module()) + * (gdb) call enco_dump_all_instrs(0x...) + */ +DEBUGGING_API_P(enco_dump_all_instrs, coco::Module); + +/** + * Print the more details of all the allocated coco::Instr in coco::Module + * + * (gdb) call enco_dump_all_instrs_v(instr->module()) + * (gdb) call enco_dump_all_instrs_v(0x...) + */ +DEBUGGING_API_P(enco_dump_all_instrs_v, coco::Module); + +/** + * Print the details of a given coco::Instr + * + * (gdb) call enco_dump_instr(instr) + * (gdb) call enco_dump_instr(0x...) + */ +DEBUGGING_API_P(enco_dump_instr, coco::Instr); + +/** + * Print the details of all the instruction in a given block + * + * (gdb) call enco_dump_block(b) + * (gdb) call enco_dump_block(0x...) + */ +DEBUGGING_API_P(enco_dump_block, coco::Block); + +#undef DEBUGGING_API_P + +#endif // __ENCO_SUPPORT_DEBUGGING_H__ diff --git a/compiler/enco/core/src/Support/Debugging.test.cpp b/compiler/enco/core/src/Support/Debugging.test.cpp new file mode 100644 index 000000000..49a2ad162 --- /dev/null +++ b/compiler/enco/core/src/Support/Debugging.test.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "Debugging.h" + +#include <gtest/gtest.h> + +// This test aims to check whether debugging API is actually defined +TEST(DebuggingTest, defined) +{ + enco_dump_op(nullptr); + enco_dump_all_ops(nullptr); +} diff --git a/compiler/enco/core/src/Transforms/AvgPoolLowering.cpp b/compiler/enco/core/src/Transforms/AvgPoolLowering.cpp new file mode 100644 index 000000000..17502fb1f --- /dev/null +++ b/compiler/enco/core/src/Transforms/AvgPoolLowering.cpp @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "AvgPoolLowering.h" +#include "IRUtils.h" + +#include <coco/IR/FeatureLayouts.h> + +#include <nncc/core/ADT/feature/Shape.h> +#include <nncc/core/ADT/feature/HWCLayout.h> + +#include <set> +#include <cassert> + +using namespace nncc::core::ADT; +using nncc::core::ADT::feature::num_elements; + +namespace +{ + +bool empty(coco::Padding2D *pad) +{ + return (pad->top() == 0) && (pad->bottom() == 0) && (pad->left() == 0) && (pad->right() == 0); +} + +/** + * @brief Return a set of AvgPool2D operations (in Eval instruction) that SHOULD be lowered + */ +std::set<coco::AvgPool2D *> candidates(coco::Module *m) +{ + std::set<coco::AvgPool2D *> res; + + for (auto I : enco::instr_sequence(m)) + { + if (auto eval = I->asEval()) + { + if (auto avgpool = eval->op()->asAvgPool2D()) + { + /* Originally it was preferred to use `auto load = avgpool->arg()->asLoad()' for + * consitent style with other if statements. + * Someone may think compiler will be happy because `load` in `if` statement can + * be considered as a use, however, it turend out that it is not the case. + */ + if (avgpool->arg()->asLoad()) + { + if (avgpool->divisor() == coco::AvgPool2D::Divisor::Static) + { + res.insert(avgpool); + } + } + } + } + } + + return res; +} + +} // namespace + +namespace +{ +namespace ShapeTransform +{ + +class Pad +{ +public: + Pad(const coco::Padding2D *pad) : _pad{pad} + { + // DO NOTHING + } + +public: + /// @brief Return the expected OFM shape for a given IFM shape + feature::Shape forward(const feature::Shape &ifm_shape) const + { + const uint32_t OFM_C = ifm_shape.depth(); + const uint32_t OFM_H = ifm_shape.height() + _pad->top() + _pad->bottom(); + const uint32_t OFM_W = ifm_shape.width() + _pad->left() + _pad->right(); + + return feature::Shape{OFM_C, OFM_H, OFM_W}; + } + +private: + const coco::Padding2D *_pad; +}; + +} // namespace ShapeTransform + +ShapeTransform::Pad shape_xform(const coco::Padding2D *pad) { return ShapeTransform::Pad{pad}; } + +} // namespace + +namespace +{ + +class PadInstrBuilder final +{ +public: + PadInstrBuilder(const coco::Padding2D *pad) : _pad{pad} + { + // DO NOTHING + } + +public: + coco::Instr *build(coco::FeatureObject *ifm_obj, coco::FeatureObject *ofm_obj) const + { + assert(ifm_obj->module() == ofm_obj->module()); + auto m = ifm_obj->module(); + assert(m != nullptr); + + auto load_op = m->entity()->op()->create<coco::Load>(); + + load_op->object(ifm_obj); + + auto pad_op = m->entity()->op()->create<coco::PadF>(); + + pad_op->arg(load_op); + + pad_op->pad()->top(_pad->top()); + pad_op->pad()->bottom(_pad->bottom()); + pad_op->pad()->left(_pad->left()); + pad_op->pad()->right(_pad->right()); + + auto pad_instr = m->entity()->instr()->create<coco::Eval>(); + + pad_instr->out(ofm_obj); + pad_instr->op(pad_op); + + return pad_instr; + } + +private: + const coco::Padding2D *_pad; +}; + +PadInstrBuilder pad_instr_builder(const coco::Padding2D *pad) { return PadInstrBuilder{pad}; } + +} // namespace + +namespace +{ + +class AvgPoolRewritePass +{ +private: + void runOnModule(coco::Module *m) const; + +public: + void runOnCode(enco::Code *) const; +}; + +void AvgPoolRewritePass::runOnModule(coco::Module *m) const +{ + // Lower AvgPool2D op that resides in Eval instruction + for (auto avgpool : candidates(m)) + { + auto ins = avgpool->parent(); + auto load = avgpool->arg()->asLoad(); + + assert(ins != nullptr); + assert(load != nullptr); + assert(avgpool->divisor() == coco::AvgPool2D::Divisor::Static); + + if (empty(avgpool->pad())) + { + // NOTE If there is no padding, Static and PaddingExcluded schemes are equivalent + avgpool->divisor(coco::AvgPool2D::Divisor::PaddingExcluded); + } + else + { + // Before: Static AvgPool2D with Padding + // After: PadF; PaddingExcluded AvgPool2D without Padding + + // Create PadF + auto ifm_obj = load->object()->asFeature(); + assert(ifm_obj != nullptr); + + auto pad_shape = shape_xform(avgpool->pad()).forward(ifm_obj->shape()); + auto pad_bag = m->entity()->bag()->create(num_elements(pad_shape)); + auto pad_obj = m->entity()->object()->create<coco::FeatureObject>(); + + pad_obj->bag(pad_bag); + pad_obj->layout(coco::FeatureLayouts::BHWC::create(pad_shape)); + + auto pad_instr = pad_instr_builder(avgpool->pad()).build(ifm_obj, pad_obj); + + // Insert PadF before AvgPool2D + pad_instr->insertBefore(ins); + + // Rewrite AvgPool2D as PaddingExcluded AvgPool2D without Padding + load->object(pad_obj); + + avgpool->divisor(coco::AvgPool2D::Divisor::PaddingExcluded); + avgpool->pad()->top(0); + avgpool->pad()->bottom(0); + avgpool->pad()->left(0); + avgpool->pad()->right(0); + } + } +} + +void AvgPoolRewritePass::runOnCode(enco::Code *code) const { runOnModule(code->module()); } + +} // namespace + +namespace enco +{ + +void lower_avgpool(enco::Code *code) +{ + AvgPoolRewritePass pass; + pass.runOnCode(code); +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/AvgPoolLowering.h b/compiler/enco/core/src/Transforms/AvgPoolLowering.h new file mode 100644 index 000000000..71a5253df --- /dev/null +++ b/compiler/enco/core/src/Transforms/AvgPoolLowering.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __REWRITE_H__ +#define __REWRITE_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Rewrite NN API-incompatible average pooling + */ +void lower_avgpool(enco::Code *); + +struct AvgPoolLoweringPass final : public Pass +{ + PASS_CTOR(AvgPoolLoweringPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { lower_avgpool(code(sess)); } +}; + +} // namespace enco + +#endif // __REWRITE_H__ diff --git a/compiler/enco/core/src/Transforms/ConcatLowering.cpp b/compiler/enco/core/src/Transforms/ConcatLowering.cpp new file mode 100644 index 000000000..bf613c983 --- /dev/null +++ b/compiler/enco/core/src/Transforms/ConcatLowering.cpp @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "CopyLowering.h" +#include "IRUtils.h" + +#include <nncc/core/ADT/tensor/IndexEnumerator.h> + +#include <set> +#include <cassert> + +using namespace nncc::core::ADT; + +namespace +{ + +inline uint32_t as_tensor_axis(const coco::ConcatF::Axis &axis) +{ + switch (axis) + { + case coco::ConcatF::Axis::Batch: + return 0; + case coco::ConcatF::Axis::Depth: + return 1; + case coco::ConcatF::Axis::Height: + return 2; + case coco::ConcatF::Axis::Width: + return 3; + default: + break; + }; + + throw std::invalid_argument{"axis is unknown value"}; +} + +tensor::Shape as_tensor_shape(const coco::FeatureLayout *l) +{ + assert(l != nullptr); + + tensor::Shape res; + + res.resize(4); + + res.dim(as_tensor_axis(coco::ConcatF::Axis::Batch)) = l->batch(); + res.dim(as_tensor_axis(coco::ConcatF::Axis::Depth)) = l->depth(); + res.dim(as_tensor_axis(coco::ConcatF::Axis::Height)) = l->height(); + res.dim(as_tensor_axis(coco::ConcatF::Axis::Width)) = l->width(); + + return res; +} + +coco::ElemID as_element_index(const coco::FeatureLayout *l, const tensor::Index &idx) +{ + assert(l != nullptr); + assert(idx.rank() == 4); + + const auto b = idx.at(as_tensor_axis(coco::ConcatF::Axis::Batch)); + const auto ch = idx.at(as_tensor_axis(coco::ConcatF::Axis::Depth)); + const auto row = idx.at(as_tensor_axis(coco::ConcatF::Axis::Height)); + const auto col = idx.at(as_tensor_axis(coco::ConcatF::Axis::Width)); + + return l->at(b, ch, row, col); +} + +std::set<coco::Eval *> candidates(coco::Module *m) +{ + std::set<coco::Eval *> res; + + for (auto ins : enco::instr_sequence(m)) + { + if (auto eval = ins->asEval()) + { + if (eval->op()->asConcatF()) + { + res.insert(eval); + } + } + } + + return res; +} + +} // namespace + +namespace enco +{ + +void lower_concat(enco::Code *code) +{ + auto m = code->module(); + + for (auto eval : candidates(m)) + { + auto concat_f = eval->op()->asConcatF(); + assert(concat_f != nullptr); + + auto left_feature = concat_f->left()->asLoad()->object()->asFeature(); + assert(left_feature != nullptr); + auto left_shape = as_tensor_shape(left_feature->layout()); + + auto right_feature = concat_f->right()->asLoad()->object()->asFeature(); + assert(right_feature != nullptr); + auto right_shape = as_tensor_shape(right_feature->layout()); + + auto out_feature = eval->out()->asFeature(); + assert(out_feature != nullptr); + auto out_shape = as_tensor_shape(out_feature->layout()); + + auto concat_axe = as_tensor_axis(concat_f->axis()); + + // Lower: Left -> Output + { + auto src_feature = left_feature; + auto src_shape = left_shape; + + auto ins = m->entity()->instr()->create<coco::Shuffle>(); + + assert(src_feature->bag() != nullptr); + assert(out_feature->bag() != nullptr); + + ins->from(src_feature->bag()); + ins->into(out_feature->bag()); + + for (tensor::IndexEnumerator e{src_shape}; e.valid(); e.advance()) + { + tensor::Index src_index = e.current(); + tensor::Index out_index = e.current(); + + auto from = as_element_index(src_feature->layout(), src_index); + auto into = as_element_index(out_feature->layout(), out_index); + + ins->insert(from, into); + } + + ins->insertAfter(eval); + } + + // Lower: Right -> Output + { + auto src_feature = right_feature; + auto src_shape = right_shape; + + auto ins = m->entity()->instr()->create<coco::Shuffle>(); + + assert(src_feature->bag() != nullptr); + assert(out_feature->bag() != nullptr); + + ins->from(src_feature->bag()); + ins->into(out_feature->bag()); + + for (tensor::IndexEnumerator e{src_shape}; e.valid(); e.advance()) + { + tensor::Index src_index = e.current(); + tensor::Index out_index = e.current(); + + out_index.at(concat_axe) = out_index.at(concat_axe) + left_shape.dim(concat_axe); + + auto from = as_element_index(src_feature->layout(), src_index); + auto into = as_element_index(out_feature->layout(), out_index); + + ins->insert(from, into); + } + + ins->insertAfter(eval); + } + + // Unlink "Eval" and "ConcatF" op tree + eval->op(nullptr); + + // Delete "Concat" op tree + m->entity()->op()->destroy(concat_f->left()); + m->entity()->op()->destroy(concat_f->right()); + m->entity()->op()->destroy(concat_f); + + // Deatch "Eval" instruction from the block + eval->detach(); + + // Delete "Eval" instruction + m->entity()->instr()->destroy(eval); + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/ConcatLowering.h b/compiler/enco/core/src/Transforms/ConcatLowering.h new file mode 100644 index 000000000..5d20e627b --- /dev/null +++ b/compiler/enco/core/src/Transforms/ConcatLowering.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ENCO_CONCAT_LOWERING_H__ +#define __ENCO_CONCAT_LOWERING_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Lower eval(Concat(...)) as a sequence of shuffle instructions + */ +void lower_concat(enco::Code *code); + +struct ConcatLoweringPass final : public Pass +{ + PASS_CTOR(ConcatLoweringPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { lower_concat(code(sess)); } +}; + +} // namespace enco + +#endif // __ENCO_CONCAT_LOWERING_H__ diff --git a/compiler/enco/core/src/Transforms/ConstantFolding.cpp b/compiler/enco/core/src/Transforms/ConstantFolding.cpp new file mode 100644 index 000000000..cd6f22351 --- /dev/null +++ b/compiler/enco/core/src/Transforms/ConstantFolding.cpp @@ -0,0 +1,442 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "ConstantFolding.h" +#include "Session.h" + +#include <queue> +#include <cmath> +#include <cassert> + +namespace +{ + +/** + * @brief is_constant_bag(b) returns true if the bag "b" has corresponding weight + */ +bool is_constant_bag(coco::Bag *b) +{ + auto m = b->module(); + auto d = enco::data(m); + return d->allocated(b); +} + +class ConstantBagEnumerator +{ +public: + ConstantBagEnumerator(enco::Code *code) : _code{code} + { + // DO NOTHING + } + +public: + template <typename Callable> void enumerate(Callable cb) const + { + auto m = _code->module(); + + for (uint32_t n = 0; n < m->entity()->bag()->size(); ++n) + { + auto b = m->entity()->bag()->at(n); + + if (is_constant_bag(b)) + { + cb(b); + } + } + } + +private: + enco::Code *_code; +}; + +template <typename Callable> void operator<<(const ConstantBagEnumerator &e, Callable &&cb) +{ + e.enumerate(std::forward<Callable>(cb)); +} + +ConstantBagEnumerator constant_bag_enumerator(enco::Code *code) +{ + return ConstantBagEnumerator{code}; +} + +} // namespace + +namespace +{ + +/** + * @brief Take the first element from the queue + * @note The queue SHOULD have at least one element. + */ +template <typename T> T take(std::queue<T> &q) +{ + assert(q.size() > 0); + auto res = q.front(); + q.pop(); + return res; +} + +} // namespace + +namespace +{ + +void fold_constant(std::queue<coco::Bag *> &q, coco::Copy *copy) +{ + auto m = copy->module(); + auto d = enco::data(m); + + auto src_obj = copy->from(); + auto src_bag = src_obj->bag(); + + auto dst_obj = copy->into(); + auto dst_bag = dst_obj->bag(); + + // Output calculation should not be folded + // TODO Reduce code duplication of this kind + if (dst_bag->isOutput()) + { + return; + } + + // NOTE d->allocated(bag) returns true if bag has corresponding initial + // values (e.g. convolution kernel) + assert(d->allocated(src_bag)); + assert(!d->allocated(dst_bag)); + + // TODO Support other data type + auto src_span = d->f32()->weight(src_bag); + + assert(src_span.data() != nullptr); + + auto src_feature = src_obj->asFeature(); + auto dst_feature = dst_obj->asFeature(); + + // TODO Support other object type + if (src_feature == nullptr || dst_feature == nullptr) + { + return; + } + + assert(src_feature != nullptr); + assert(dst_feature != nullptr); + + // Allocate weight for destination + d->f32()->allocate(dst_bag); + + auto dst_span = d->f32()->weight(dst_bag); + + assert(src_feature->layout()->batch() == dst_feature->layout()->batch()); + assert(src_feature->layout()->depth() == dst_feature->layout()->depth()); + assert(src_feature->layout()->height() == dst_feature->layout()->height()); + assert(src_feature->layout()->width() == dst_feature->layout()->width()); + + uint32_t const B = src_feature->layout()->batch(); + uint32_t const C = src_feature->layout()->depth(); + uint32_t const H = src_feature->layout()->height(); + uint32_t const W = src_feature->layout()->width(); + + for (uint32_t b = 0; b < B; ++b) + { + for (uint32_t ch = 0; ch < C; ++ch) + { + for (uint32_t row = 0; row < H; ++row) + { + for (uint32_t col = 0; col < W; ++col) + { + auto src_ind = src_feature->layout()->at(b, ch, row, col); + auto dst_ind = dst_feature->layout()->at(b, ch, row, col); + + dst_span[dst_ind.value()] = src_span[src_ind.value()]; + } + } + } + } + + // Let's detach copy + copy->from(nullptr); + copy->into(nullptr); + copy->detach(); + + // Let's visit destination bag! + q.push(dst_bag); +} + +template <typename Callable> +void fold_constant_op(std::queue<coco::Bag *> &q, coco::UnaryOp *op, Callable evaluate) +{ + auto m = op->module(); + auto d = enco::data(m); + + auto ins = op->parent(); + auto eval = ins->asEval(); + + // UnaryOp has only one arg + auto src_obj = *(op->uses().begin()); + auto src_bag = src_obj->bag(); + + auto dst_obj = eval->out(); + auto dst_bag = dst_obj->bag(); + + // Output calculation should not be folded + // TODO Reduce code duplication of this kind + if (dst_bag->isOutput()) + { + return; + } + + assert(d->allocated(src_bag)); + assert(!d->allocated(dst_bag)); + + // TODO Support other data type + auto src_span = d->f32()->weight(src_bag); + assert(src_span.data() != nullptr); + + auto src_feature = src_obj->asFeature(); + auto dst_feature = dst_obj->asFeature(); + + // TODO Support other object type + if (src_feature == nullptr || dst_feature == nullptr) + { + return; + } + + assert(src_feature != nullptr); + assert(dst_feature != nullptr); + + // Allocate weight for destination + d->f32()->allocate(dst_bag); + auto dst_span = d->f32()->weight(dst_bag); + + assert(src_feature->layout()->batch() == dst_feature->layout()->batch()); + assert(src_feature->layout()->depth() == dst_feature->layout()->depth()); + assert(src_feature->layout()->height() == dst_feature->layout()->height()); + assert(src_feature->layout()->width() == dst_feature->layout()->width()); + + uint32_t const B = src_feature->layout()->batch(); + uint32_t const C = src_feature->layout()->depth(); + uint32_t const H = src_feature->layout()->height(); + uint32_t const W = src_feature->layout()->width(); + + for (uint32_t b = 0; b < B; ++b) + { + for (uint32_t ch = 0; ch < C; ++ch) + { + for (uint32_t row = 0; row < H; ++row) + { + for (uint32_t col = 0; col < W; ++col) + { + auto src_ind = src_feature->layout()->at(b, ch, row, col); + auto dst_ind = dst_feature->layout()->at(b, ch, row, col); + + evaluate(&dst_span[dst_ind.value()], src_span[src_ind.value()]); + } + } + } + } + + // Let's detach eval + eval->out(nullptr); + eval->detach(); + + // Let's visit destination bag! + q.push(dst_bag); +} + +template <typename Callable> +void fold_constant_op(std::queue<coco::Bag *> &q, coco::BinaryOp *op, Callable evaluate) +{ + auto m = op->module(); + auto d = enco::data(m); + + auto ins = op->parent(); + auto eval = ins->asEval(); + + // Already folded by the other bag + if (!eval->out()) + { + return; + } + + auto lhs_load = op->left()->asLoad(); + auto lhs_obj = lhs_load->object(); + auto lhs_bag = lhs_obj->bag(); + + auto rhs_load = op->right()->asLoad(); + auto rhs_obj = rhs_load->object(); + auto rhs_bag = rhs_obj->bag(); + + auto dst_obj = eval->out(); + auto dst_bag = dst_obj->bag(); + + // Output calculation should not be folded + // TODO Reduce code duplication of this kind + if (dst_bag->isOutput()) + { + return; + } + + // The other bag is non-constant + if (!d->allocated(lhs_bag) || !d->allocated(rhs_bag)) + { + return; + } + + assert(d->allocated(lhs_bag)); + assert(d->allocated(rhs_bag)); + assert(!d->allocated(dst_bag)); + + // TODO Support other data type + auto lhs_span = d->f32()->weight(lhs_bag); + auto rhs_span = d->f32()->weight(rhs_bag); + assert(lhs_span.data() != nullptr); + assert(rhs_span.data() != nullptr); + + auto lhs_feature = lhs_obj->asFeature(); + auto rhs_feature = rhs_obj->asFeature(); + auto dst_feature = dst_obj->asFeature(); + + // TODO Support other object type + if (lhs_feature == nullptr || rhs_feature == nullptr || dst_feature == nullptr) + { + return; + } + + assert(lhs_feature != nullptr); + assert(rhs_feature != nullptr); + assert(dst_feature != nullptr); + + // Allocate weight for destination + d->f32()->allocate(dst_bag); + auto dst_span = d->f32()->weight(dst_bag); + + assert(lhs_feature->layout()->batch() == rhs_feature->layout()->batch()); + assert(lhs_feature->layout()->depth() == rhs_feature->layout()->depth()); + assert(lhs_feature->layout()->height() == rhs_feature->layout()->height()); + assert(lhs_feature->layout()->width() == rhs_feature->layout()->width()); + + assert(lhs_feature->layout()->batch() == dst_feature->layout()->batch()); + assert(lhs_feature->layout()->depth() == dst_feature->layout()->depth()); + assert(lhs_feature->layout()->height() == dst_feature->layout()->height()); + assert(lhs_feature->layout()->width() == dst_feature->layout()->width()); + + uint32_t const B = lhs_feature->layout()->batch(); + uint32_t const C = lhs_feature->layout()->depth(); + uint32_t const H = lhs_feature->layout()->height(); + uint32_t const W = lhs_feature->layout()->width(); + + for (uint32_t b = 0; b < B; ++b) + { + for (uint32_t ch = 0; ch < C; ++ch) + { + for (uint32_t row = 0; row < H; ++row) + { + for (uint32_t col = 0; col < W; ++col) + { + auto lhs_ind = lhs_feature->layout()->at(b, ch, row, col); + auto rhs_ind = rhs_feature->layout()->at(b, ch, row, col); + auto dst_ind = dst_feature->layout()->at(b, ch, row, col); + + evaluate(&dst_span[dst_ind.value()], lhs_span[lhs_ind.value()], + rhs_span[rhs_ind.value()]); + } + } + } + } + + // Let's detach eval + eval->out(nullptr); + eval->detach(); + + // Let's visit destination bag! + q.push(dst_bag); +} + +void fold_constant(std::queue<coco::Bag *> &q, coco::Eval *eval) +{ + // TODO Support other data types + if (auto op = eval->op()->asSqrt()) + { + fold_constant_op(q, op, [](float *dst, float value) { *dst = std::sqrt(value); }); + } + else if (auto op = eval->op()->asAdd()) + { + fold_constant_op(q, op, [](float *dst, float lhs, float rhs) { *dst = lhs + rhs; }); + } + else if (auto op = eval->op()->asSub()) + { + fold_constant_op(q, op, [](float *dst, float lhs, float rhs) { *dst = lhs - rhs; }); + } + else if (auto op = eval->op()->asMul()) + { + fold_constant_op(q, op, [](float *dst, float lhs, float rhs) { *dst = lhs * rhs; }); + } + else if (auto op = eval->op()->asDiv()) + { + fold_constant_op(q, op, [](float *dst, float lhs, float rhs) { *dst = lhs / rhs; }); + } + else + { + // Not supported opteration, do nothing + // TODO Support other operations + } +} + +void fold_constant(std::queue<coco::Bag *> &q, coco::Instr *ins) +{ + if (auto copy = coco::safe_cast<coco::Copy>(ins)) + { + fold_constant(q, copy); + return; + } + if (auto eval = coco::safe_cast<coco::Eval>(ins)) + { + fold_constant(q, eval); + return; + } + + // TODO Add more cases for constant folding +} + +} // namespace + +namespace enco +{ + +void fold_constants(enco::Code *code) +{ + std::queue<coco::Bag *> q; + + // Collect the initial set of "constant" bag + constant_bag_enumerator(code) << [&q](coco::Bag *bag) { q.push(bag); }; + + while (!q.empty()) + { + auto candidate_bag = take(q); + + // Scan the readers of each candidate bag + for (auto reader : coco::readers(candidate_bag)) + { + // TODO Decide how to handle the reader with unknown instruction + if (auto ins = reader->loc()) + { + fold_constant(q, ins); + } + } + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/ConstantFolding.h b/compiler/enco/core/src/Transforms/ConstantFolding.h new file mode 100644 index 000000000..6faa9c876 --- /dev/null +++ b/compiler/enco/core/src/Transforms/ConstantFolding.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __CONSTANT_FOLDING_H__ +#define __CONSTANT_FOLDING_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Evaluate "constant" expressions at compile time + */ +void fold_constants(enco::Code *); + +struct ConstantFoldingPass final : public Pass +{ + PASS_CTOR(ConstantFoldingPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { fold_constants(code(sess)); } +}; + +} // namespace enco + +#endif // __CONSTANT_FOLDING_H__ diff --git a/compiler/enco/core/src/Transforms/ConstantFolding.test.cpp b/compiler/enco/core/src/Transforms/ConstantFolding.test.cpp new file mode 100644 index 000000000..5ac71ac14 --- /dev/null +++ b/compiler/enco/core/src/Transforms/ConstantFolding.test.cpp @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. 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 "ConstantFolding.h" +#include "Session.h" + +#include <cmath> +#include <gtest/gtest.h> + +namespace +{ + +class BinaryNetwork +{ +public: + BinaryNetwork(coco::Module *module, coco::Data *data) : _module{module}, _data{data} + { + // DO NOTHING + } + + template <typename Op> void build(void); + + void fold(void) + { + // Execute constant folding + enco::make_session(_module, _data); + enco::Code code{_module, _data}; + enco::fold_constants(&code); + } + +public: + coco::Bag *out; + coco::Bag *lhs; + coco::Bag *rhs; + + coco::Eval *eval; + +private: + coco::Module *_module; + coco::Data *_data; +}; + +template <typename Op> void BinaryNetwork::build(void) +{ + // Create lhs bag and object + auto lhs_bag = _module->entity()->bag()->create(12); + auto lhs_obj = _module->entity()->object()->template create<coco::FeatureObject>(); + coco::FeatureShape lhs_shape(1, 2, 2, 3); + lhs_obj->bag(lhs_bag); + lhs_obj->layout(coco::FeatureLayouts::BHWC::create(lhs_shape)); + + // Create rhs bag and object + auto rhs_bag = _module->entity()->bag()->create(12); + auto rhs_obj = _module->entity()->object()->template create<coco::FeatureObject>(); + coco::FeatureShape rhs_shape(1, 2, 2, 3); + rhs_obj->bag(rhs_bag); + rhs_obj->layout(coco::FeatureLayouts::BHWC::create(rhs_shape)); + + // Create output bag and object + auto output_bag = _module->entity()->bag()->create(12); + auto output_obj = _module->entity()->object()->template create<coco::FeatureObject>(); + coco::FeatureShape ofm_shape(1, 2, 2, 3); + output_obj->bag(output_bag); + output_obj->layout(coco::FeatureLayouts::BHWC::create(ofm_shape)); + + // Create instruction and operations + auto block = _module->entity()->block()->create(); + auto eval = _module->entity()->instr()->template create<coco::Eval>(); + auto load_lhs = _module->entity()->op()->template create<coco::Load>(); + auto load_rhs = _module->entity()->op()->template create<coco::Load>(); + auto add_op = _module->entity()->op()->template create<Op>(); + + _module->block()->append(block); + block->instr()->append(eval); + + load_lhs->object(lhs_obj); + load_rhs->object(rhs_obj); + add_op->left(load_lhs); + add_op->right(load_rhs); + + eval->op(add_op); + eval->out(output_obj); + + // Create a handle + this->lhs = lhs_bag; + this->rhs = rhs_bag; + this->out = output_bag; + + this->eval = eval; +} + +} // namespace + +TEST(ConstantFoldingTest, sqrt) +{ + auto module = coco::Module::create(); + auto data = coco::Data::create(); + + // Create input bag and object + auto input_bag = module->entity()->bag()->create(12); + auto input_obj = module->entity()->object()->create<coco::FeatureObject>(); + coco::FeatureShape ifm_shape(1, 2, 2, 3); + input_obj->bag(input_bag); + input_obj->layout(coco::FeatureLayouts::BHWC::create(ifm_shape)); + + // Create output bag and object + auto output_bag = module->entity()->bag()->create(12); + auto output_obj = module->entity()->object()->create<coco::FeatureObject>(); + coco::FeatureShape ofm_shape(1, 2, 2, 3); + output_obj->bag(output_bag); + output_obj->layout(coco::FeatureLayouts::BHWC::create(ofm_shape)); + + // Insert values into input bag + data->f32()->allocate(input_bag); + auto input = data->f32()->weight(input_bag); + for (uint32_t idx = 0; idx < input.size(); ++idx) + { + input[idx] = (float)idx; + } + + // Create instruction and operations + auto block = module->entity()->block()->create(); + auto eval = module->entity()->instr()->create<coco::Eval>(); + auto load = module->entity()->op()->create<coco::Load>(); + auto sqrt_op = module->entity()->op()->create<coco::Sqrt>(); + + module->block()->append(block); + block->instr()->append(eval); + + load->object(input_obj); + sqrt_op->arg(load); + + eval->op(sqrt_op); + eval->out(output_obj); + + // Execute constant folding + enco::make_session(module.get(), data.get()); + enco::Code code{module.get(), data.get()}; + enco::fold_constants(&code); + + // Validate the result + ASSERT_EQ(data->allocated(output_bag), true); + ASSERT_EQ(eval->out(), nullptr); + + auto output = data->f32()->weight(output_bag); + for (uint32_t idx = 0; idx < output.size(); ++idx) + { + ASSERT_FLOAT_EQ(output[idx], std::sqrt(input[idx])); + } +} + +TEST(ConstantFoldingTest, element_wise_add) +{ + auto module = coco::Module::create(); + auto data = coco::Data::create(); + + BinaryNetwork net{module.get(), data.get()}; + + // Build a network + net.build<coco::Add>(); + + // Create alises + auto lhs_bag = net.lhs; + auto rhs_bag = net.rhs; + auto output_bag = net.out; + auto eval = net.eval; + + // Insert values into lhs and rhs bag + data->f32()->allocate(lhs_bag); + data->f32()->allocate(rhs_bag); + auto lhs = data->f32()->weight(lhs_bag); + auto rhs = data->f32()->weight(rhs_bag); + for (uint32_t idx = 0; idx < lhs.size(); ++idx) + { + lhs[idx] = (float)idx; + rhs[idx] = 1.5; + } + + // Execute constant folding + net.fold(); + + // Validate the result + ASSERT_EQ(data->allocated(output_bag), true); + ASSERT_EQ(eval->out(), nullptr); + + auto output = data->f32()->weight(output_bag); + for (uint32_t idx = 0; idx < output.size(); ++idx) + { + ASSERT_FLOAT_EQ(output[idx], lhs[idx] + rhs[idx]); + } +} + +TEST(ConstantFoldingTest, element_wise_sub) +{ + auto module = coco::Module::create(); + auto data = coco::Data::create(); + + BinaryNetwork net{module.get(), data.get()}; + + // Build a network + net.build<coco::Sub>(); + + // Create alises + auto lhs_bag = net.lhs; + auto rhs_bag = net.rhs; + auto output_bag = net.out; + auto eval = net.eval; + + // Insert values into lhs and rhs bag + data->f32()->allocate(lhs_bag); + data->f32()->allocate(rhs_bag); + auto lhs = data->f32()->weight(lhs_bag); + auto rhs = data->f32()->weight(rhs_bag); + for (uint32_t idx = 0; idx < lhs.size(); ++idx) + { + lhs[idx] = (float)idx; + rhs[idx] = 1.5; + } + + // Execute constant folding + net.fold(); + + // Validate the result + ASSERT_EQ(data->allocated(output_bag), true); + ASSERT_EQ(eval->out(), nullptr); + + auto output = data->f32()->weight(output_bag); + for (uint32_t idx = 0; idx < output.size(); ++idx) + { + ASSERT_FLOAT_EQ(output[idx], lhs[idx] - rhs[idx]); + } +} + +TEST(ConstantFoldingTest, element_wise_mul) +{ + auto module = coco::Module::create(); + auto data = coco::Data::create(); + + BinaryNetwork net{module.get(), data.get()}; + + // Build a network + net.build<coco::Mul>(); + + // Create alises + auto lhs_bag = net.lhs; + auto rhs_bag = net.rhs; + auto output_bag = net.out; + auto eval = net.eval; + + // Insert values into lhs and rhs bag + data->f32()->allocate(lhs_bag); + data->f32()->allocate(rhs_bag); + auto lhs = data->f32()->weight(lhs_bag); + auto rhs = data->f32()->weight(rhs_bag); + for (uint32_t idx = 0; idx < lhs.size(); ++idx) + { + lhs[idx] = (float)idx; + rhs[idx] = 1.5; + } + + // Execute constant folding + net.fold(); + + // Validate the result + ASSERT_EQ(data->allocated(output_bag), true); + ASSERT_EQ(eval->out(), nullptr); + + auto output = data->f32()->weight(output_bag); + for (uint32_t idx = 0; idx < output.size(); ++idx) + { + ASSERT_FLOAT_EQ(output[idx], lhs[idx] * rhs[idx]); + } +} + +TEST(ConstantFoldingTest, element_wise_div) +{ + auto module = coco::Module::create(); + auto data = coco::Data::create(); + + BinaryNetwork net{module.get(), data.get()}; + + // Build a network + net.build<coco::Div>(); + + // Create alises + auto lhs_bag = net.lhs; + auto rhs_bag = net.rhs; + auto output_bag = net.out; + auto eval = net.eval; + + // Insert values into lhs and rhs bag + data->f32()->allocate(lhs_bag); + data->f32()->allocate(rhs_bag); + auto lhs = data->f32()->weight(lhs_bag); + auto rhs = data->f32()->weight(rhs_bag); + for (uint32_t idx = 0; idx < lhs.size(); ++idx) + { + lhs[idx] = (float)idx; + rhs[idx] = 1.5; + } + + // Execute constant folding + net.fold(); + + // Validate the result + ASSERT_EQ(data->allocated(output_bag), true); + ASSERT_EQ(eval->out(), nullptr); + + auto output = data->f32()->weight(output_bag); + for (uint32_t idx = 0; idx < output.size(); ++idx) + { + ASSERT_FLOAT_EQ(output[idx], lhs[idx] / rhs[idx]); + } +} diff --git a/compiler/enco/core/src/Transforms/CopyLowering.cpp b/compiler/enco/core/src/Transforms/CopyLowering.cpp new file mode 100644 index 000000000..ceb3bbd5c --- /dev/null +++ b/compiler/enco/core/src/Transforms/CopyLowering.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "CopyLowering.h" + +#include <set> +#include <cassert> + +// +// Lower Copy as Shuffle +// +namespace enco +{ + +void lower_copy(enco::Code *code) +{ + auto m = code->module(); + + std::set<coco::Copy *> lowered_copies; + + for (uint32_t n = 0; n < m->entity()->instr()->size(); ++n) + { + auto ins = m->entity()->instr()->at(n); + + assert(ins != nullptr); + + if (ins->parent() == nullptr) + { + // Skip if instruction does not belong to a list + continue; + } + + auto copy = ins->asCopy(); + + if (copy == nullptr) + { + // Skip if instruction is not a copy + continue; + } + + // TODO Support non-Feature objects + auto ifm = copy->from()->asFeature(); + auto ofm = copy->into()->asFeature(); + + if ((ifm == nullptr) || (ofm == nullptr)) + { + continue; + } + + assert(ifm->layout()->batch() == ofm->layout()->batch()); + assert(ifm->layout()->shape() == ofm->layout()->shape()); + + auto shuffle = m->entity()->instr()->create<coco::Shuffle>(); + + shuffle->from(ifm->bag()); + shuffle->into(ofm->bag()); + + const uint32_t B = ifm->layout()->batch(); + const uint32_t C = ifm->layout()->shape().depth(); + const uint32_t H = ifm->layout()->shape().height(); + const uint32_t W = ifm->layout()->shape().width(); + + for (uint32_t b = 0; b < B; ++b) + { + for (uint32_t ch = 0; ch < C; ++ch) + { + for (uint32_t row = 0; row < H; ++row) + { + for (uint32_t col = 0; col < W; ++col) + { + const auto from = ifm->layout()->at(b, ch, row, col); + const auto into = ofm->layout()->at(b, ch, row, col); + + shuffle->insert(from, into); + } + } + } + } + + shuffle->insertBefore(copy); + lowered_copies.insert(copy); + } + + // Destroy lowered copy + for (const auto © : lowered_copies) + { + copy->detach(); + m->entity()->instr()->destroy(copy); + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/CopyLowering.h b/compiler/enco/core/src/Transforms/CopyLowering.h new file mode 100644 index 000000000..51f0f83e2 --- /dev/null +++ b/compiler/enco/core/src/Transforms/CopyLowering.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ENCO_LOWER_H__ +#define __ENCO_LOWER_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Lower copy(...) instruction into shuffle(...) + */ +void lower_copy(enco::Code *code); + +struct CopyLoweringPass final : public Pass +{ + PASS_CTOR(CopyLoweringPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { lower_copy(code(sess)); } +}; + +} // namespace enco + +#endif // __ENCO_LOWER_H__ diff --git a/compiler/enco/core/src/Transforms/DataLayoutConversion.cpp b/compiler/enco/core/src/Transforms/DataLayoutConversion.cpp new file mode 100644 index 000000000..9d65d1c0b --- /dev/null +++ b/compiler/enco/core/src/Transforms/DataLayoutConversion.cpp @@ -0,0 +1,383 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "DataLayoutConversion.h" +#include "Session.h" +#include "IRUtils.h" + +#include "coex/IR.h" + +#include <coco/IR/FeatureLayouts.h> +#include <coco/IR/KernelLayouts.h> + +#include <nncc/core/ADT/feature/Layout.h> +#include <nncc/core/ADT/kernel/Layout.h> + +#include <nncc/core/ADT/feature/HWCLayout.h> +#include <nncc/core/ADT/kernel/NHWCLayout.h> + +#include <set> + +namespace +{ + +coco::Copy *make_copy(coco::FeatureObject *from, coco::FeatureObject *into) +{ + auto m = from->module(); + assert(m != nullptr); + assert(from->module() == into->module()); + + auto copy = m->entity()->instr()->create<coco::Copy>(); + + copy->from(from); + copy->into(into); + + return copy; +} + +coco::FeatureObject *clone_feature(const coco::FeatureObject *oldobj) +{ + auto module = oldobj->module(); + auto newobj = module->entity()->object()->create<coco::FeatureObject>(); + newobj->layout(coco::FeatureLayouts::BHWC::create(oldobj->shape())); + + if (oldobj->bag() != nullptr) + { + using nncc::core::ADT::feature::num_elements; + + // NOTE The size of bag should be at least "BxHxWxC" as "newobj" uses BHWC layout + const uint32_t batch = newobj->layout()->batch(); + const uint32_t count = num_elements(newobj->layout()->shape()); + const uint32_t bag_size = batch * count; + + // Clone bag only when there is a backing bag for a given feature object + auto newbag = module->entity()->bag()->create(bag_size); + newobj->bag(newbag); + } + + return newobj; +} + +/** + * @brief Insert Copy before Load if necessary + * + * @require "load" should be bounded + */ +void insert_copy_before_load(coco::Load *load) +{ + assert(load->parent() != nullptr); + assert(load->parent()->parent() != nullptr); + + if (auto obj = load->object()) + { + if (auto ifm = obj->asFeature()) + { + if (ifm->layout()->id() != coco::FeatureLayouts::BHWC::uid()) + { + auto oldobj = ifm; + auto newobj = clone_feature(oldobj); + + load->object(newobj); + + auto copy = make_copy(oldobj, newobj); + copy->insertBefore(load->parent()); + } + } + } +} + +/** + * @brief Insert Copy after Eval if necessary + */ +void insert_copy_after_eval(coco::Eval *eval) +{ + if (auto out = eval->out()) + { + if (auto ofm = out->asFeature()) + { + if (ofm->layout()->id() != coco::FeatureLayouts::BHWC::uid()) + { + auto oldobj = ofm; + auto newobj = clone_feature(oldobj); + + eval->out(newobj); + + auto copy = make_copy(newobj, oldobj); + copy->insertAfter(eval); + } + } + } +} + +/** + * @brief Insert copy (for data layout change) before/after ANNDepthConcatF if necessary + */ +void convert_data_layout(ANNDepthConcatF *concat) +{ + if (auto out = concat->out()) + { + if (auto ofm = out->asFeature()) + { + if (ofm->layout()->id() != coco::FeatureLayouts::BHWC::uid()) + { + auto oldobj = ofm; + auto newobj = clone_feature(oldobj); + + concat->out(newobj); + + auto copy = make_copy(newobj, oldobj); + copy->insertAfter(concat); + } + } + } + + if (auto obj = concat->fst()) + { + if (auto ifm = obj->asFeature()) + { + if (ifm->layout()->id() != coco::FeatureLayouts::BHWC::uid()) + { + auto oldobj = ifm; + auto newobj = clone_feature(oldobj); + + concat->fst(newobj); + + auto copy = make_copy(oldobj, newobj); + copy->insertBefore(concat); + } + } + } + + if (auto obj = concat->snd()) + { + if (auto ifm = obj->asFeature()) + { + if (ifm->layout()->id() != coco::FeatureLayouts::BHWC::uid()) + { + auto oldobj = ifm; + auto newobj = clone_feature(oldobj); + + concat->snd(newobj); + + auto copy = make_copy(oldobj, newobj); + copy->insertBefore(concat); + } + } + } +} + +/** + * @brief Update convolution kernel data layout + */ +void change_conv2d_kernel_layout(coco::Conv2D *conv) +{ + auto m = conv->module(); + assert(m != nullptr); + auto d = enco::data(enco::session(m)); + assert(d != nullptr); + + auto old_obj = conv->ker(); + assert(old_obj != nullptr); + auto old_bag = old_obj->bag(); + assert(old_bag != nullptr); + + if (old_obj->layout()->id() == coco::KernelLayouts::NHWC::uid()) + { + // Skip if kernel already uses NHWC layout + return; + } + + const auto &ker_shape = old_obj->shape(); + + assert(d->allocated(old_bag)); + + auto new_bag = m->entity()->bag()->create(old_bag->size()); + auto new_obj = m->entity()->object()->create<coco::KernelObject>(); + + new_obj->bag(new_bag); + new_obj->layout(coco::KernelLayouts::NHWC::create(ker_shape)); + + d->f32()->allocate(new_bag); + + auto src = d->f32()->read(old_obj); + auto dst = d->f32()->access(new_obj); + + const auto ker_N = ker_shape.count(); + const auto ker_C = ker_shape.depth(); + const auto ker_H = ker_shape.height(); + const auto ker_W = ker_shape.width(); + + for (uint32_t n = 0; n < ker_N; ++n) + { + for (uint32_t ch = 0; ch < ker_C; ++ch) + { + for (uint32_t row = 0; row < ker_H; ++row) + { + for (uint32_t col = 0; col < ker_W; ++col) + { + dst->at(n, ch, row, col) = src->at(n, ch, row, col); + } + } + } + } + + conv->ker(new_obj); + d->release(old_bag); +} + +} // namespace + +namespace +{ + +/** + * @brief Return the set of all of bounded Load Op(s) in a given module + * + * @note 'bounded' means it will be exectuted + */ +std::set<coco::Load *> loads(coco::Module *m) +{ + std::set<coco::Load *> res; + + for (uint32_t n = 0; n < m->entity()->op()->size(); ++n) + { + auto op = m->entity()->op()->at(n); + + // Skip if this op is dangling + if (op->parent() == nullptr) + { + continue; + } + + // Skip if eval instruction of this op is dangling + if (op->parent()->parent() == nullptr) + { + continue; + } + + if (auto load = m->entity()->op()->at(n)->asLoad()) + { + res.insert(load); + } + } + + return res; +} + +/** + * @brief Return the set of every (allocated) Eval instruction in a given module + */ +std::set<coco::Eval *> evals(coco::Module *m) +{ + std::set<coco::Eval *> res; + + for (uint32_t n = 0; n < m->entity()->instr()->size(); ++n) + { + if (auto eval = m->entity()->instr()->at(n)->asEval()) + { + res.insert(eval); + } + } + + return res; +} + +/** + * @brief Return the set of allocated Conv2D op in a given module + */ +std::set<coco::Conv2D *> convs(coco::Module *m) +{ + std::set<coco::Conv2D *> res; + + for (uint32_t n = 0; n < m->entity()->op()->size(); ++n) + { + if (auto op = m->entity()->op()->at(n)->asConv2D()) + { + res.insert(op); + } + } + + return res; +} + +/** + * @brief Return the set of "bounded" ANNDepthConcatF instructions + */ +std::set<ANNDepthConcatF *> depth_concats(coco::Module *m) +{ + std::set<ANNDepthConcatF *> res; + + for (auto ins : enco::instr_sequence(m)) + { + if (auto depth_concat_f = coco::safe_cast<ANNDepthConcatF>(ins)) + { + res.insert(depth_concat_f); + } + } + + return res; +} + +class NormalizePass +{ +private: + void runOnModule(coco::Module *m) const; + +public: + void runOnCode(enco::Code *) const; +}; + +void NormalizePass::runOnModule(coco::Module *m) const +{ + // Insert Copy before all Load Op (if necessary) + for (auto load : loads(m)) + { + insert_copy_before_load(load); + } + + // Insert Copy after all Eval Instr (if necessary) + for (auto eval : evals(m)) + { + insert_copy_after_eval(eval); + } + + // Change Kernel Layout of Conv2D opertion (if necessary) + for (auto conv : convs(m)) + { + change_conv2d_kernel_layout(conv); + } + + // Insert Copy (for Layout Conversion) before/after ANNDepthConcatF instructions (if necessary) + for (auto depth_concat : depth_concats(m)) + { + convert_data_layout(depth_concat); + } +} + +void NormalizePass::runOnCode(enco::Code *code) const { runOnModule(code->module()); } + +} // namespace + +namespace enco +{ + +void convert_data_layout(enco::Code *code) +{ + NormalizePass pass; + pass.runOnCode(code); +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/DataLayoutConversion.h b/compiler/enco/core/src/Transforms/DataLayoutConversion.h new file mode 100644 index 000000000..ac4052c8b --- /dev/null +++ b/compiler/enco/core/src/Transforms/DataLayoutConversion.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ENCO_TRANSFORM_DATA_LAYOUT_CONVERSION_H__ +#define __ENCO_TRANSFORM_DATA_LAYOUT_CONVERSION_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Insert data reordering if necessary + */ +void convert_data_layout(enco::Code *code); + +struct DataLayoutConversionPass final : public enco::Pass +{ + PASS_CTOR(DataLayoutConversionPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { convert_data_layout(code(sess)); } +}; + +} // namespace enco + +#endif // __ENCO_TRANSFORM_DATA_LAYOUT_CONVERSION_H__ diff --git a/compiler/enco/core/src/Transforms/DataLayoutConversion.test.cpp b/compiler/enco/core/src/Transforms/DataLayoutConversion.test.cpp new file mode 100644 index 000000000..812e38a78 --- /dev/null +++ b/compiler/enco/core/src/Transforms/DataLayoutConversion.test.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "DataLayoutConversion.h" + +#include <gtest/gtest.h> + +TEST(DataLayoutConversionTest, case_000) +{ + auto m = coco::Module::create(); + + // Create a "free" Load op + m->entity()->instr()->create<coco::Eval>(); + + enco::Code code{m.get(), nullptr}; + ASSERT_EQ(m->entity()->instr()->size(), 1); + + // "conver_data_layout" SHOULD NOT crash even if there is a "free" Load op + enco::convert_data_layout(&code); +} diff --git a/compiler/enco/core/src/Transforms/DeadBagElimination.cpp b/compiler/enco/core/src/Transforms/DeadBagElimination.cpp new file mode 100644 index 000000000..b3c598a55 --- /dev/null +++ b/compiler/enco/core/src/Transforms/DeadBagElimination.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "DeadBagElimination.h" + +#include <set> + +namespace +{ + +/// @brief Return true if a given bag is marked as either input or output +bool is_public(const coco::Bag *b) { return b->isInput() || b->isOutput(); } + +/// @brief Return the set of "dead" bags in a given module +std::set<coco::Bag *> dead_bags(const coco::Module *m) +{ + std::set<coco::Bag *> res; + + for (uint32_t n = 0; n < m->entity()->bag()->size(); ++n) + { + auto bag = m->entity()->bag()->at(n); + + if (coco::readers(bag).empty() && !is_public(bag)) + { + res.insert(bag); + } + } + + return res; +} + +} // namespace + +namespace enco +{ + +void eliminate_dead_bag(enco::Code *code) +{ + auto m = code->module(); + + // Destroy a dead bag and its updaters + for (auto bag : dead_bags(m)) + { + for (auto updater : coco::updaters(bag)) + { + auto ins = updater->loc(); + + assert(ins != nullptr); + + ins->detach(); + m->entity()->instr()->destroy(ins); + } + + bag->replaceWith(nullptr); + m->entity()->bag()->destroy(bag); + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/DeadBagElimination.h b/compiler/enco/core/src/Transforms/DeadBagElimination.h new file mode 100644 index 000000000..87e03e8ac --- /dev/null +++ b/compiler/enco/core/src/Transforms/DeadBagElimination.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ENCO_TRANSFORM_DEAD_BAG_ELIMINATION_H__ +#define __ENCO_TRANSFORM_DEAD_BAG_ELIMINATION_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Eliminate dead bags + * + * A bag is referred to as dead if it is neither input nor output, and has no read. If a bag is + * dead, it is unnecessary to updates its values as these values are never used. + * + * "eliminate_dead_bag" removes all the dead bags and its updaters from IR. + */ +void eliminate_dead_bag(enco::Code *code); + +struct DeadBagEliminationPass final : public Pass +{ + PASS_CTOR(DeadBagEliminationPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { eliminate_dead_bag(code(sess)); } +}; + +} // namespace enco + +#endif // __ENCO_TRANSFORM_DEAD_BAG_ELIMINATION_H__ diff --git a/compiler/enco/core/src/Transforms/DeadObjectElimination.cpp b/compiler/enco/core/src/Transforms/DeadObjectElimination.cpp new file mode 100644 index 000000000..df8cc628a --- /dev/null +++ b/compiler/enco/core/src/Transforms/DeadObjectElimination.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "DeadObjectElimination.h" + +#include <set> + +namespace +{ + +std::set<coco::Object *> dead_objects(const coco::Module *m) +{ + std::set<coco::Object *> res; + + for (uint32_t n = 0; n < m->entity()->object()->size(); ++n) + { + auto obj = m->entity()->object()->at(n); + + if (auto bag = obj->bag()) + { + if (coco::readers(bag).empty() && !(bag->isOutput())) + { + res.insert(obj); + } + } + else + { + // NOTE Just in case if there are Objects not related to Bags + if (obj->uses()->size() == 0) + { + res.insert(obj); + } + } + } + + return res; +} + +} // namespace + +namespace enco +{ + +void eliminate_dead_object(enco::Code *code) +{ + auto m = code->module(); + + // Destroy a dead object and its producer + for (auto obj : dead_objects(m)) + { + if (auto producer = coco::producer(obj)) + { + auto ins = producer->loc(); + assert(ins != nullptr); + + ins->detach(); + m->entity()->instr()->destroy(ins); + } + + m->entity()->object()->destroy(obj); + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/DeadObjectElimination.h b/compiler/enco/core/src/Transforms/DeadObjectElimination.h new file mode 100644 index 000000000..4923e56fd --- /dev/null +++ b/compiler/enco/core/src/Transforms/DeadObjectElimination.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ENCO_TRANSFORM_DEAD_OBJECT_ELIMINATION_H__ +#define __ENCO_TRANSFORM_DEAD_OBJECT_ELIMINATION_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Eliminate dead objects in IR + * + * An object whose backing bag is unused is referred to as a dead object. + * + * Dead Object Elimination (DOE) eliminates such dead objects along with their producer. + */ +void eliminate_dead_object(enco::Code *code); + +struct DeadObjectEliminationPass final : public Pass +{ + PASS_CTOR(DeadObjectEliminationPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { eliminate_dead_object(code(sess)); } +}; + +} // namespace enco + +#endif // __ENCO_TRANSFORM_DEAD_OBJECT_ELIMINATION_H__ diff --git a/compiler/enco/core/src/Transforms/Duplicate.cpp b/compiler/enco/core/src/Transforms/Duplicate.cpp new file mode 100644 index 000000000..91f64a0ad --- /dev/null +++ b/compiler/enco/core/src/Transforms/Duplicate.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "Duplicate.h" + +#include <map> +#include <set> + +#include <cassert> + +namespace +{ + +coco::Block *find_or_create_first_block(coco::Module *m) +{ + if (m->block()->empty()) + { + auto blk = m->entity()->block()->create(); + m->block()->append(blk); + return blk; + } + + return m->block()->head(); +} + +} // namespace + +namespace +{ + +class DuplicatePass +{ +private: + void runOnModule(coco::Module *m) const; + +public: + void runOnCode(enco::Code *) const; +}; + +void DuplicatePass::runOnModule(coco::Module *m) const +{ + // Let's find candidates + std::set<coco::Bag *> candidates; + + for (uint32_t n = 0; n < m->entity()->bag()->size(); ++n) + { + auto bag = m->entity()->bag()->at(n); + + if (bag->isInput() && bag->isOutput()) + { + candidates.insert(bag); + } + } + + // Return if there is no candidate + if (candidates.empty()) + { + return; + } + + std::map<const coco::Bag *, coco::Input *> input_map; + std::map<const coco::Bag *, coco::Output *> output_map; + + for (uint32_t n = 0; n < m->input()->size(); ++n) + { + auto input = m->input()->at(n); + assert(input->bag() != nullptr); + input_map[input->bag()] = input; + } + + for (uint32_t n = 0; n < m->output()->size(); ++n) + { + auto output = m->output()->at(n); + assert(output->bag() != nullptr); + output_map[output->bag()] = output; + } + + // For each in/out bag, + // 1. Create a new bag of the same size + // 2. Copy the content from the original bag + // 3. Mark the newly created bag as an output + for (const auto &candidate : candidates) + { + assert(coco::updaters(candidate).empty()); + assert(input_map.find(candidate) != input_map.end()); + assert(output_map.find(candidate) != output_map.end()); + + auto src = candidate; + auto dst = m->entity()->bag()->create(src->size()); + + // Create a copy instruction + auto shuffle = m->entity()->instr()->create<coco::Shuffle>(); + + shuffle->from(src); + shuffle->into(dst); + + for (uint32_t n = 0; n < src->size(); ++n) + { + shuffle->insert(coco::ElemID{n} /* FROM */, coco::ElemID{n} /* INTO */); + } + + find_or_create_first_block(m)->instr()->prepend(shuffle); + + // Let's use the new bag as an output + output_map.at(src)->bag(dst); + } +} + +void DuplicatePass::runOnCode(enco::Code *code) const { runOnModule(code->module()); } + +} // namespace + +namespace enco +{ + +void duplicate_inout_bag(enco::Code *code) +{ + DuplicatePass duplicate; + duplicate.runOnCode(code); +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/Duplicate.h b/compiler/enco/core/src/Transforms/Duplicate.h new file mode 100644 index 000000000..93baa4589 --- /dev/null +++ b/compiler/enco/core/src/Transforms/Duplicate.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __DUPLICATE_H__ +#define __DUPLICATE_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Eliminate in/out bags by duplication + */ +void duplicate_inout_bag(enco::Code *code); + +struct BagDuplicationPass final : public Pass +{ + PASS_CTOR(BagDuplicationPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { duplicate_inout_bag(code(sess)); } +}; + +} // namespace enco + +#endif // __DUPLICATE_H__ diff --git a/compiler/enco/core/src/Transforms/DuplicatedObjectReduction.cpp b/compiler/enco/core/src/Transforms/DuplicatedObjectReduction.cpp new file mode 100644 index 000000000..fa84c005c --- /dev/null +++ b/compiler/enco/core/src/Transforms/DuplicatedObjectReduction.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "DuplicatedObjectReduction.h" + +#include "CodeIndex.h" +#include "IRUtils.h" + +#include <set> + +namespace +{ + +/** + * @brief Collect feature objects in coco IR + */ +std::set<coco::FeatureObject *> features(const coco::Module *m) +{ + std::set<coco::FeatureObject *> res; + + for (uint32_t n = 0; n < m->entity()->object()->size(); ++n) + { + if (auto feature = m->entity()->object()->at(n)->asFeature()) + { + res.insert(feature); + } + } + + return res; +} + +std::set<coco::FeatureObject *> candidates(const coco::FeatureObject *src) +{ + std::set<coco::FeatureObject *> res; + + for (auto consumer : coco::consumers(src)) + { + if (auto copy = consumer->loc()->asCopy()) + { + auto dst = copy->into()->asFeature(); + assert(dst != nullptr); + + if (dst->layout()->id() == coco::FeatureLayouts::BHWC::uid()) + { + res.insert(dst); + } + } + } + + return res; +} + +CodeIndex code_index(coco::Object::Producer *p) +{ + if (auto ins = p->loc()) + { + return ::code_index(ins); + } + + return CodeIndex{}; +} + +} // namespace + +namespace enco +{ + +void reduce_duplicated_object(enco::Code *code) +{ + auto m = code->module(); + + for (const auto &src : features(m)) + { + auto copied = candidates(src); + + if (copied.size() <= 1) + { + continue; + } + + // Find the dominator + coco::FeatureObject *dominator = nullptr; + + for (auto candidate : copied) + { + if (dominator == nullptr) + { + dominator = candidate; + } + else if (code_index(coco::producer(candidate)) < code_index(coco::producer(dominator))) + { + dominator = candidate; + } + } + + // Replace all the occurunce of dominated objects with its dominator + copied.erase(dominator); + + for (auto dominatee : copied) + { + subst(dominatee, dominator); + } + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/DuplicatedObjectReduction.h b/compiler/enco/core/src/Transforms/DuplicatedObjectReduction.h new file mode 100644 index 000000000..3aa20058e --- /dev/null +++ b/compiler/enco/core/src/Transforms/DuplicatedObjectReduction.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ENCO_TRANSFORM_DUPLICATED_OBJECT_REDUCTION_H__ +#define __ENCO_TRANSFORM_DUPLICATED_OBJECT_REDUCTION_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Reduce duplicated feature objects as its dominating feature object + * + * >>> BEFORE <<< + * %obj_0 = Feature(layout: ???) at ... + * %obj_1 = Feature(layout: BHWC) at ... + * %obj_2 = Feature(layout: BHWC) at ... + * + * copy(from: %obj_0, into: %obj_1) + * copy(from: %obj_0, into: %obj_2) + * + * ... + * Use(%obj_1) + * Use(%obj_2) + * ... + * + * >>> AFTER <<< + * %obj_0 = Feature(layout: ???) at ... + * %obj_1 = Feature(layout: BHWC) at ... + * %obj_2 = Feature(layout: BHWC) at ... + * + * copy(from: %obj_0, into: %obj_1) + * copy(from: %obj_0, into: %obj_2) + * + * ... + * Use(%obj_1) + * Use(%obj_1) <-- CHANGED + * ... + * + * NOTE Given a set of feature objects, a feature object referred to as a dominating + * feature object if its producer proceeds the producer of every feature object + * in the given set + */ +void reduce_duplicated_object(enco::Code *code); + +struct DuplicatedObjectReductionPass final : public Pass +{ + PASS_CTOR(DuplicatedObjectReductionPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { reduce_duplicated_object(code(sess)); } +}; + +} // namespace enco + +#endif // __ENCO_TRANSFORM_DUPLICATED_OBJECT_REDUCTION_H__ diff --git a/compiler/enco/core/src/Transforms/FeatureUnification.cpp b/compiler/enco/core/src/Transforms/FeatureUnification.cpp new file mode 100644 index 000000000..1a7a0a8a4 --- /dev/null +++ b/compiler/enco/core/src/Transforms/FeatureUnification.cpp @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "FeatureUnification.h" +#include "IRUtils.h" + +#include <stdex/Memory.h> + +#include <set> +#include <vector> + +#include <cassert> + +using stdex::make_unique; + +namespace +{ + +bool is_static_layout(const coco::FeatureLayout::ID *id) +{ + if (id == coco::FeatureLayouts::BHWC::uid()) + { + return true; + } + + if (id == coco::FeatureLayouts::BCHW::uid()) + { + return true; + } + + return false; +} + +bool is_static_layout(const coco::FeatureLayout *l) { return is_static_layout(l->id()); } +bool is_static_layout(const coco::FeatureObject *f) { return is_static_layout(f->layout()); } + +/** + * @brief Return ture if a given 'feature' is the candidate of unification + */ +bool candidate(const coco::FeatureObject *f) { return is_static_layout(f); } + +/** + * @brief Return true if two features are compatible + * + * Two features are referred to as compatible if these feature are interchangeable. + * + * NOTE The current implementation of "compatible" is sound, but incomplete. + * + * Soundness: + * For all feature objects "lhs" and "rhs" that "compatible(lhs, rhs)" returns true, + * "lhs" and "rhs" are interchangeable. + * + * Completeness: + * For all interchangeable feature objects "lhs" and "rhs", "compatible(lhs, rhs)" returns true. + */ +bool compatible(const coco::FeatureObject *lhs, const coco::FeatureObject *rhs) +{ + assert(candidate(lhs) && candidate(rhs)); + + if (lhs->layout()->id() != rhs->layout()->id()) + { + return false; + } + + if (lhs->layout()->batch() != rhs->layout()->batch()) + { + return false; + } + + if (!(lhs->layout()->shape() == rhs->layout()->shape())) + { + return false; + } + + return true; +} + +/** + * @brief A FeatureGroup denotes a group of FeatureObject(s) + * + * Each FeatureGroup includes at most 1 DEF FeatureObject (a FeatureObject that has a producer), + * and may include multiple USE FeatureObject(s) (a FeatureObject that has no producer). + * + * NOTE FeatureUnification pass internally uses this FeatureGroup to store a group of compatible + * FeatureObject(s) + */ +class FeatureGroup +{ +public: + explicit FeatureGroup(coco::FeatureObject *feature) { insert(feature); } + +public: + uint32_t size(void) const { return _uses.size() + (_def ? 1 : 0); } + +public: + void insert(coco::FeatureObject *feature) + { + if (feature->def() != nullptr) + { + assert(_def == nullptr); + _def = feature; + } + else + { + _uses.insert(feature); + } + } + +public: + coco::FeatureObject *parent(void) const + { + if (_def) + { + return _def; + } + + assert(_uses.size() > 0); + return *(_uses.begin()); + } + +public: + std::set<coco::FeatureObject *> children(void) const + { + auto res = _uses; + res.erase(parent()); + return res; + } + +private: + coco::FeatureObject *_def = nullptr; + std::set<coco::FeatureObject *> _uses; +}; + +} // namespace + +namespace enco +{ + +void unify_feature(enco::Code *code) +{ + auto m = code->module(); + + for (uint32_t n = 0; n < m->entity()->bag()->size(); ++n) + { + std::vector<std::unique_ptr<FeatureGroup>> groups; + + auto assign_group = [&](coco::FeatureObject *feature) { + // Find a compatible FeatureGroup + FeatureGroup *group = nullptr; + + for (const auto &g : groups) + { + FeatureGroup *candidate = g.get(); + + if (!compatible(candidate->parent(), feature)) + { + continue; + } + + group = candidate; + break; + } + + if (group == nullptr) + { + // Insert FeatureObject into a new FeatureGroup + groups.emplace_back(make_unique<FeatureGroup>(feature)); + } + else + { + // Insert FeatureObject into the compatible FeatureGroup + group->insert(feature); + } + }; + + auto bag = m->entity()->bag()->at(n); + + for (auto o : coco::dependent_objects(bag)) + { + if (auto feature = o->asFeature()) + { + if (candidate(feature)) + { + assign_group(feature); + } + } + } + + for (const auto &g : groups) + { + auto group = g.get(); + for (const auto child : group->children()) + { + subst(child, group->parent()); + assert(child->def() == nullptr); + assert(child->uses()->size() == 0); + m->entity()->object()->destroy(child); + } + } + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/FeatureUnification.h b/compiler/enco/core/src/Transforms/FeatureUnification.h new file mode 100644 index 000000000..5ab0f9d7a --- /dev/null +++ b/compiler/enco/core/src/Transforms/FeatureUnification.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ENCO_TRANSFORM_FEATURE_UNIFICATION_H__ +#define __ENCO_TRANSFORM_FEATURE_UNIFICATION_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Remove duplicated feature objects inside each bag + * + * >>> BEFORE <<< + * %b = Bag(...) + * + * %feature_0 = Feature(...) at %b + * %feature_1 = Feature(...) at %b + * + * ... + * Use(%feature_0) + * ... + * Use(%feature_1) + * ... + * + * >>> AFTER <<< + * %b = Bag(...) + * + * %feature_0 = Feature(...) at %b + * ~~%feature_1 = Feature(...) at %b~~ <- REMOVED + * + * ... + * Use(%feature_0) + * ... + * Use(%feature_0) + * ... + * + * Note that all the occurrences of "%feature_1" are replaced with "%feature_0" + */ +void unify_feature(enco::Code *code); + +struct FeatureUnificationPass final : public Pass +{ + PASS_CTOR(FeatureUnificationPass) + { + // DO NOTHING + } + void run(const SessionID &sess) const override { unify_feature(code(sess)); } +}; + +} // namespace enco + +#endif // __ENCO_TRANSFORM_FEATURE_UNIFICATION_H__ diff --git a/compiler/enco/core/src/Transforms/FreeInstrElimination.cpp b/compiler/enco/core/src/Transforms/FreeInstrElimination.cpp new file mode 100644 index 000000000..a62324b28 --- /dev/null +++ b/compiler/enco/core/src/Transforms/FreeInstrElimination.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "FreeInstrElimination.h" + +#include <cassert> +#include <set> + +namespace +{ + +/** + * @brief Return the set of "free" instructions in a given module + */ +std::set<coco::Instr *> free_instrs(const coco::Module *m) +{ + std::set<coco::Instr *> res; + + for (uint32_t n = 0; n < m->entity()->instr()->size(); ++n) + { + if (auto ins = m->entity()->instr()->at(n)) + { + if (ins->parent() == nullptr) + { + res.insert(ins); + } + } + } + + return res; +} + +void destroy(coco::Instr *ins) +{ + auto m = ins->module(); + m->entity()->instr()->destroy(ins); +} + +} // namespace + +namespace enco +{ + +void eliminate_free_instr(coco::Module *m) +{ + for (auto ins : free_instrs(m)) + { + destroy(ins); + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/FreeInstrElimination.h b/compiler/enco/core/src/Transforms/FreeInstrElimination.h new file mode 100644 index 000000000..1d311cd35 --- /dev/null +++ b/compiler/enco/core/src/Transforms/FreeInstrElimination.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ENCO_TRANSFORM_FREE_INSTR_ELIMINATION_H__ +#define __ENCO_TRANSFORM_FREE_INSTR_ELIMINATION_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Eliminate free instructions + * + * An instruction is referred to as "free" if it is not bound to any "block" + */ +void eliminate_free_instr(coco::Module *mod); + +/** + * @brief Eliminate free instructions + */ +static inline void eliminate_free_instr(enco::Code *code) +{ + // This function is just a wrapper of the above "void eliminate_free_instr(coco::Module *mod)" + eliminate_free_instr(code->module()); +} + +struct FreeInstrEliminationPass final : public Pass +{ + PASS_CTOR(FreeInstrEliminationPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { eliminate_free_instr(code(sess)); } +}; + +} // namespace enco + +#endif // __ENCO_TRANSFORM_FREE_INSTR_ELIMINATION_H__ diff --git a/compiler/enco/core/src/Transforms/FreeInstrElimination.test.cpp b/compiler/enco/core/src/Transforms/FreeInstrElimination.test.cpp new file mode 100644 index 000000000..c15f32e7d --- /dev/null +++ b/compiler/enco/core/src/Transforms/FreeInstrElimination.test.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "FreeInstrElimination.h" + +#include <gtest/gtest.h> + +TEST(FreeInstrEliminationTest, case_000) +{ + auto m = coco::Module::create(); + + // Create a "free" Eval instruction + m->entity()->instr()->create<coco::Eval>(); + + ASSERT_EQ(m->entity()->instr()->size(), 1); + + // Apply "Free Instruction Elimination" + enco::eliminate_free_instr(m.get()); + + ASSERT_EQ(m->entity()->instr()->size(), 0); +} diff --git a/compiler/enco/core/src/Transforms/FreeOpElimination.cpp b/compiler/enco/core/src/Transforms/FreeOpElimination.cpp new file mode 100644 index 000000000..25f2f44d0 --- /dev/null +++ b/compiler/enco/core/src/Transforms/FreeOpElimination.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "FreeOpElimination.h" + +#include <cassert> +#include <set> + +namespace +{ + +/** + * @brief Return the set of Free Op Elimination candidates + */ +std::set<coco::Op *> candidates(const coco::Module *m) +{ + std::set<coco::Op *> res; + + for (uint32_t n = 0; n < m->entity()->op()->size(); ++n) + { + if (auto op = m->entity()->op()->at(n)) + { + if ((op->parent() == nullptr) && (op->up() == nullptr)) + { + res.insert(op); + } + } + } + + return res; +} + +} // namespace + +namespace enco +{ + +void eliminate_free_op(coco::Module *m) +{ + for (auto op : candidates(m)) + { + m->entity()->op()->destroy_all(op); + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/FreeOpElimination.h b/compiler/enco/core/src/Transforms/FreeOpElimination.h new file mode 100644 index 000000000..3aeacada5 --- /dev/null +++ b/compiler/enco/core/src/Transforms/FreeOpElimination.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ENCO_TRANSFORM_FREE_OP_ELIMINATION_H__ +#define __ENCO_TRANSFORM_FREE_OP_ELIMINATION_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Eliminate free op + * + * An op is referred to as "free" if it is not bound to any "instruction" + */ +void eliminate_free_op(coco::Module *mod); + +/** + * @brief Eliminate free op + */ +static inline void eliminate_free_op(enco::Code *code) +{ + // This function is just a wrapper of the above "void eliminate_free_op(coco::Module *mod)" + eliminate_free_op(code->module()); +} + +struct FreeOpEliminationPass final : public Pass +{ + PASS_CTOR(FreeOpEliminationPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { eliminate_free_op(code(sess)); } +}; + +} // namespace enco + +#endif // __ENCO_TRANSFORM_FREE_OP_ELIMINATION_H__ diff --git a/compiler/enco/core/src/Transforms/FreeOpElimination.test.cpp b/compiler/enco/core/src/Transforms/FreeOpElimination.test.cpp new file mode 100644 index 000000000..41600526b --- /dev/null +++ b/compiler/enco/core/src/Transforms/FreeOpElimination.test.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "FreeOpElimination.h" + +#include <gtest/gtest.h> + +TEST(FreeOpEliminationTest, case_000) +{ + auto m = coco::Module::create(); + + // Create a "free" Load op + m->entity()->op()->create<coco::Load>(); + + ASSERT_EQ(m->entity()->op()->size(), 1); + + // Apply "Free Op Elimination" + enco::eliminate_free_op(m.get()); + + ASSERT_EQ(m->entity()->op()->size(), 0); +} diff --git a/compiler/enco/core/src/Transforms/GlobalDataGeneration.cpp b/compiler/enco/core/src/Transforms/GlobalDataGeneration.cpp new file mode 100644 index 000000000..152477a51 --- /dev/null +++ b/compiler/enco/core/src/Transforms/GlobalDataGeneration.cpp @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "GlobalDataGeneration.h" +#include "Split.h" +#include "Dims.h" + +#include <stdex/Memory.h> + +#include <map> + +using stdex::make_unique; + +namespace +{ + +/** + * @brief Manage global variable declarations + */ +class Global +{ +public: + Global(std::ostream &os) : _os(os) + { + // DO NOTHING + } + +public: + /// @brief Create a global constant string (const char *) literal, and return variable name + enco::GlobalOffset constant(const std::string &value); + + /// @brief Create a global constant array variable of type T + template <typename T> enco::GlobalOffset constant(const std::vector<T> &values); + + /// @brief Create a global constant array variable of byte (uint8_t) type + enco::GlobalOffset constant(const uint8_t *base, uint32_t size); + +private: + uint32_t _offset = 0; + std::ostream &_os; +}; + +enco::GlobalOffset Global::constant(const std::string &s) +{ + auto const base = reinterpret_cast<const uint8_t *>(s.c_str()); + auto const size = s.size() + 1 /* NUL */; + return constant(base, size); +} + +template <> enco::GlobalOffset Global::constant(const std::vector<uint32_t> &values) +{ + auto const base = reinterpret_cast<const uint8_t *>(values.data()); + auto const size = sizeof(uint32_t) * values.size(); + return constant(base, size); +} + +enco::GlobalOffset Global::constant(const uint8_t *base, uint32_t size) +{ + auto pos = _os.tellp(); + assert(pos != -1); + + _os.write(reinterpret_cast<const char *>(base), size); + + return static_cast<enco::GlobalOffset>(pos); +} + +} // namespace + +namespace +{ + +std::map<const ann::Operand *, enco::GlobalOffset> data_offset_ctx; +std::map<const coco::Bag *, enco::GlobalOffset> bag_data_offset_ctx; + +std::map<const coco::Arg *, enco::GlobalOffset> name_offset_ctx; +std::map<const coco::Arg *, enco::GlobalOffset> dims_offset_ctx; + +} // namespace + +namespace enco +{ + +GlobalOffset GlobalData::data_offset(const ann::Operand *o) { return data_offset_ctx.at(o); } + +GlobalOffset GlobalData::data_offset(const coco::Bag *bag) +{ + assert(bag_data_offset_ctx.find(bag) != bag_data_offset_ctx.end()); + return bag_data_offset_ctx.at(bag); +} + +GlobalOffset GlobalData::name_offset(const coco::Input *in) { return name_offset_ctx.at(in); } +GlobalOffset GlobalData::dims_offset(const coco::Input *in) { return dims_offset_ctx.at(in); } + +GlobalOffset GlobalData::name_offset(const coco::Output *out) { return name_offset_ctx.at(out); } +GlobalOffset GlobalData::dims_offset(const coco::Output *out) { return dims_offset_ctx.at(out); } + +void generate_global_data(std::ostream &os, enco::Code *code) +{ + auto m = code->module(); + auto d = code->data(); + + auto ann_ctx = enco::SubnetManager::context(m); + + auto global = make_unique<Global>(os); + + // + // Emit Bag's weight + // + for (uint32_t n = 0; n < m->entity()->bag()->size(); ++n) + { + auto bag = m->entity()->bag()->at(n); + + if (!d->allocated(bag)) + { + // Skip if the weight value does not exist for a given bag + continue; + } + + // NOTE The current implementation assumes that all the values are of float(fp32) type + // TODO Support non-float values + auto span = d->f32()->weight(bag); + + assert(span.data() != nullptr); + assert(span.size() > 0); + + auto const base = reinterpret_cast<const uint8_t *>(span.data()); + uint32_t const size = span.size() * sizeof(float); + + assert(bag_data_offset_ctx.find(bag) == bag_data_offset_ctx.end()); + bag_data_offset_ctx[bag] = global->constant(base, size); + } + + for (uint32_t n = 0; n < ann_ctx->count(); ++n) + { + auto binder = ann_ctx->nth(n); + + auto emit = [&](const ann::OperandID & /*id*/, const ann::Operand *info) { + if (info->weight()) + { + auto base = info->weight()->base(); + auto size = info->weight()->size(); + + data_offset_ctx[info] = global->constant(base, size); + } + }; + binder->module()->operand()->each(emit); + } + + for (uint32_t n = 0; n < m->input()->size(); ++n) + { + auto input = m->input()->at(n); + auto dims = as_dims(input->shape()); + + name_offset_ctx[input] = global->constant(input->name()); + dims_offset_ctx[input] = global->constant<uint32_t>(dims); + } + + for (uint32_t n = 0; n < m->output()->size(); ++n) + { + auto output = m->output()->at(n); + auto dims = as_dims(output->shape()); + + name_offset_ctx[output] = global->constant(output->name()); + dims_offset_ctx[output] = global->constant<uint32_t>(dims); + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/GlobalDataGeneration.h b/compiler/enco/core/src/Transforms/GlobalDataGeneration.h new file mode 100644 index 000000000..433431401 --- /dev/null +++ b/compiler/enco/core/src/Transforms/GlobalDataGeneration.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ENCO_TRANSFORM_GLOBAL_DATA_GENERATION_H__ +#define __ENCO_TRANSFORM_GLOBAL_DATA_GENERATION_H__ + +#include "Code.h" + +#include <ostream> + +namespace enco +{ + +using GlobalOffset = uint32_t; + +struct GlobalData +{ + static GlobalOffset data_offset(const ann::Operand *); + /** + * @brief Return the weight offset of a given bag + * + * @note The behavior of "data_offset" is undefined if a bag has no weight. + */ + static GlobalOffset data_offset(const coco::Bag *); + + static GlobalOffset name_offset(const coco::Input *); + static GlobalOffset dims_offset(const coco::Input *); + static GlobalOffset name_offset(const coco::Output *); + static GlobalOffset dims_offset(const coco::Output *); +}; + +/** + * @brief Generate 'Global' weight array. + * + * NOTE Succeeding passes can access offsets via "GlobalData" + */ +void generate_global_data(std::ostream &, enco::Code *); + +} // namespace enco + +#endif // __ENCO_TRANSFORM_GLOBAL_DATA_GENERATION_H__ diff --git a/compiler/enco/core/src/Transforms/IdenticalObjectReduction.cpp b/compiler/enco/core/src/Transforms/IdenticalObjectReduction.cpp new file mode 100644 index 000000000..cb996d2ac --- /dev/null +++ b/compiler/enco/core/src/Transforms/IdenticalObjectReduction.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "IdenticalObjectReduction.h" +#include "IRUtils.h" + +#include <set> + +namespace enco +{ + +void reduce_identical_object(enco::Code *code) +{ + auto m = code->module(); + + std::set<coco::Copy *> detached; + + // Preceding optimizations may generate "free" instructions. + // - i.e. an instruction not linked to a block + // + // Let's iterate over only a sequence of "bounded" instructions. + for (auto ins : instr_sequence(m)) + { + assert(ins != nullptr); + assert(ins->parent() != nullptr); + + auto copy = ins->asCopy(); + + if (copy == nullptr) + { + // Skip if instruction is not a copy + continue; + } + + // TODO Support non-Feature Objects + auto ifm = copy->from()->asFeature(); + auto ofm = copy->into()->asFeature(); + + assert(ofm->bag() != nullptr); + + if (ifm->layout()->id() != ofm->layout()->id()) + { + continue; + } + + if (ifm->layout()->id() != coco::FeatureLayouts::BHWC::uid()) + { + continue; + } + + // Skip if this copy produces network output + if (ofm->bag()->output()) + { + // TODO Optimize this case + // + // Note that the code under optimization is of the following form: + // + // %ifm <- Instr(...) + // %ofm <- Copy(%ifm) + // + // Let's assume that "Copy" is the only reader of %ifm (to be precise, its bag). + // + // Then, it is possible to rewrite the above fragment as follows: + // + // %ofm <- Instr(...) + // + continue; + } + + if (ofm->bag()->reads()->size() > 0) + { + // Let us consider the following code: + // + // Bag: + // %bag_0 = Bag(...) + // %bag_1 = Bag(...) + // %bag_2 = Bag(...) + // + // Object: + // %obj_0 = FeatureObject(bag: %bag_0) + // %obj_1 = FeatureObject(bag: %bag_1) + // + // Instr: + // copy an object from %obj_0 into %obj_1 + // shuffle values from %bag_1 into %bag_2 + // eval Conv2D with %obj_1 + // + // Identical Object Reduction (IOR) tries to eliminate the first copy via + // substitution (substitute all the occurrence of %obj_1 as use with %obj_0). + // + // Here is the code transformed by IOR: + // + // Bag: + // %bag_0 = Bag(...) + // %bag_1 = Bag(...) + // %bag_2 = Bag(...) + // + // Object: + // %obj_0 = FeatureObject(bag: %bag_0) + // %obj_1 = FeatureObject(bag: %bag_1) + // + // Instr: + // shuffle values from %bag_1 into %bag_2 + // eval Conv2D with %obj_0 + // + // Note that there is no updater of %bag_1 after IOR, and thus the behavior + // of the first shuffle instruction has changed. + // + // This examples shows that it is impossible to simply substitute %obj_1 + // with %obj_0 in the presence of readers over its backing bag. + continue; + } + + subst(copy->into(), copy->from()); + + copy->detach(); + detached.insert(copy); + } + + for (auto copy : detached) + { + m->entity()->instr()->destroy(copy); + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/IdenticalObjectReduction.h b/compiler/enco/core/src/Transforms/IdenticalObjectReduction.h new file mode 100644 index 000000000..b5bb25d7c --- /dev/null +++ b/compiler/enco/core/src/Transforms/IdenticalObjectReduction.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ENCO_TRANSFORM_IDENTICAL_OBJECT_REDUCTION_H__ +#define __ENCO_TRANSFORM_IDENTICAL_OBJECT_REDUCTION_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Reduce identically copied objects as its original object + * + * >>> BEFORE <<< + * %bag_0 = Bag(size: N) + * %bag_1 = Bag(size: N) + * + * %obj_0 = Feature(layout: BHWC) at %bag_0 + * %obj_1 = Feature(layout: BHWC) at %bag_1 + * + * copy(from: %obj_0, into: %obj_1) + * ... + * Use(%obj_0) + * Use(%obj_1) + * ... + * + * >>> AFTER <<< + * %bag_0 = Bag(size: N) + * %bag_1 = Bag(size: N) + * + * %obj_0 = Feature(layout: BHWC) at %bag_0 + * %obj_1 = Feature(layout: BHWC) at %bag_1 + * + * copy(from: %obj_0, into: %obj_1) + * ... + * Use(%obj_0) + * Use(%obj_0) <- %obj_1 is replaced + * ... + */ +void reduce_identical_object(enco::Code *code); + +struct IdenticalObjectReductionPass final : public Pass +{ + PASS_CTOR(IdenticalObjectReductionPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { reduce_identical_object(code(sess)); } +}; + +} // namespace enco + +#endif // __ENCO_TRANSFORM_IDENTICAL_OBJECT_REDUCTION_H__ diff --git a/compiler/enco/core/src/Transforms/IdenticalObjectReduction.test.cpp b/compiler/enco/core/src/Transforms/IdenticalObjectReduction.test.cpp new file mode 100644 index 000000000..772bea08e --- /dev/null +++ b/compiler/enco/core/src/Transforms/IdenticalObjectReduction.test.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "IdenticalObjectReduction.h" + +#include <gtest/gtest.h> + +TEST(IdenticalObjectReductionTest, case_000) +{ + auto m = coco::Module::create(); + + // Create a "free" Eval instruction + m->entity()->instr()->create<coco::Eval>(); + + enco::Code code{m.get(), nullptr}; + + // NOTE This code SHOULD NOT crash + enco::reduce_identical_object(&code); +} diff --git a/compiler/enco/core/src/Transforms/IndirectCopyElimination.cpp b/compiler/enco/core/src/Transforms/IndirectCopyElimination.cpp new file mode 100644 index 000000000..b36620f61 --- /dev/null +++ b/compiler/enco/core/src/Transforms/IndirectCopyElimination.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "IndirectCopyElimination.h" + +#include <cassert> + +namespace +{ + +coco::Copy *as_copy(coco::Instr *ins) { return ins ? ins->asCopy() : nullptr; } + +/** + * @brief Return a set of copy instructions that are accessible from top-level module + */ +std::set<coco::Copy *> linked_copy_instrs(coco::Module *m) +{ + std::set<coco::Copy *> res; + + for (uint32_t n = 0; n < m->entity()->instr()->size(); ++n) + { + auto ins = m->entity()->instr()->at(n); + assert(ins != nullptr); + + if (ins->parent() && ins->parent()->parent()) + { + if (auto copy = ins->asCopy()) + { + res.insert(copy); + } + } + } + + return res; +} + +} // namespace + +namespace enco +{ + +void eliminate_indirect_copy(enco::Code *code) +{ + auto m = code->module(); + + for (auto child : linked_copy_instrs(m)) + { + auto from = child->from(); + assert(from != nullptr); + + // Find the irreducible origin + while (true) + { + if (auto producer = coco::producer(from)) + { + if (auto parent = as_copy(producer->loc())) + { + assert(parent->from() != nullptr); + from = parent->from(); + continue; + } + } + + break; + } + + child->from(from); + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/IndirectCopyElimination.h b/compiler/enco/core/src/Transforms/IndirectCopyElimination.h new file mode 100644 index 000000000..acfdf569b --- /dev/null +++ b/compiler/enco/core/src/Transforms/IndirectCopyElimination.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ENCO_TRANSFORM_INDIRECT_COPY_ELIMINATION_H__ +#define __ENCO_TRANSFORM_INDIRECT_COPY_ELIMINATION_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Convert all the indirect copies as a direct copy + * + * >>> BEFORE <<< + * %obj_0 = ... + * %obj_1 = ... + * %obj_2 = ... + * + * copy(from: %obj_0, into: %obj_1) + * copy(from: %obj_1, into: %obj_2) + * + * >>> AFTER <<< + * %obj_0 = ... + * %obj_1 = ... + * %obj_2 = ... + * + * copy(from: %obj_0, into: %obj_1) + * copy(from: %obj_0, into: %obj_2) + * + */ +void eliminate_indirect_copy(enco::Code *code); + +struct IndirectCopyEliminationPass final : public enco::Pass +{ + PASS_CTOR(IndirectCopyEliminationPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { eliminate_indirect_copy(code(sess)); } +}; + +} // namespace enco + +#endif // __ENCO_TRANSFORM_INDIRECT_COPY_ELIMINATION_H__ diff --git a/compiler/enco/core/src/Transforms/IntrinsicSelection.cpp b/compiler/enco/core/src/Transforms/IntrinsicSelection.cpp new file mode 100644 index 000000000..7bf1c4926 --- /dev/null +++ b/compiler/enco/core/src/Transforms/IntrinsicSelection.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "IntrinsicSelection.h" + +#include "coex/IR.h" + +namespace +{ + +/** + * @brief Return a backend-speicific coco (extend) instruction + * + * @note rewrite(ins) returns nullptr if selection fails + */ +coco::Instr *rewrite(coco::Instr *curr) +{ + auto m = curr->module(); + assert(m != nullptr); + + if (auto eval = coco::safe_cast<coco::Eval>(curr)) + { + if (auto concat_f = eval->op()->asConcatF()) + { + auto fst_load = concat_f->left()->asLoad(); + auto snd_load = concat_f->right()->asLoad(); + + if (fst_load && snd_load && (concat_f->axis() == coco::ConcatF::Axis::Depth)) + { + // Here is the pattern of interest + // + // %ofm = eval(ConcatF(Depth, Load(%left), Load(%right))) + // + auto fst_feature = fst_load->object()->asFeature(); + auto snd_feature = snd_load->object()->asFeature(); + assert((fst_feature != nullptr) && (snd_feature != nullptr)); + + auto out_feature = eval->out()->asFeature(); + assert(out_feature != nullptr); + + eval->out(nullptr); + + auto depth_concat = m->entity()->instr()->create<ANNDepthConcatF>(); + + depth_concat->out(out_feature); + depth_concat->fst(fst_feature); + depth_concat->snd(snd_feature); + + return depth_concat; + } + + return nullptr; + } + } + + return nullptr; +} + +} // namespace + +namespace enco +{ + +void select_intrinsic(enco::Code *code) +{ + auto m = code->module(); + + for (auto blk = m->block()->head(); blk; blk = blk->next()) + { + auto ins = blk->instr()->head(); + + while (ins) + { + if (auto rewritten_ins = rewrite(ins)) + { + rewritten_ins->insertBefore(ins); + ins->detach(); + + ins = rewritten_ins; + } + + ins = ins->next(); + } + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/IntrinsicSelection.h b/compiler/enco/core/src/Transforms/IntrinsicSelection.h new file mode 100644 index 000000000..67d38eaeb --- /dev/null +++ b/compiler/enco/core/src/Transforms/IntrinsicSelection.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __INTRINSIC_SELECTION_H__ +#define __INTRINSIC_SELECTION_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Select Intricsic (API) to be used + * + * This pass is analogue of "Instruction Selection" pass. This "Intrisic Selection" pass + * will replace a general coco IR instruction into a backend-specific coco (extended) IR + * instruction. + */ +void select_intrinsic(enco::Code *); + +struct IntrinsicSelectionPass final : public Pass +{ + PASS_CTOR(IntrinsicSelectionPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { select_intrinsic(code(sess)); } +}; + +} // namespace enco + +#endif // __INTRINSIC_SELECTION_H__ diff --git a/compiler/enco/core/src/Transforms/Optimizations.cpp b/compiler/enco/core/src/Transforms/Optimizations.cpp new file mode 100644 index 000000000..7f0974dd0 --- /dev/null +++ b/compiler/enco/core/src/Transforms/Optimizations.cpp @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "Optimizations.h" +#include "CodeIndex.h" + +#include <cassert> + +namespace enco +{ + +void generate_bypass_shuffle(enco::Code *code) +{ + auto m = code->module(); + + for (uint32_t n = 0; n < m->entity()->bag()->size(); ++n) + { + auto bag = m->entity()->bag()->at(n); + + // NOTE The current implementation assumes that all the updates occurs before the first read + // TODO Remove this assumption + for (auto u : coco::updaters(bag)) + { + if ((u->loc() == nullptr) || (u->loc()->asShuffle() == nullptr)) + { + // Skip if updater is not a Shuffle instruction + continue; + } + + for (auto r : coco::readers(bag)) + { + if ((r->loc() == nullptr) || (r->loc()->asShuffle() == nullptr)) + { + // Skip if reader is not a Shuffle instruction + continue; + } + + auto shuffle_1 = u->loc()->asShuffle(); + auto shuffle_2 = r->loc()->asShuffle(); + + // Construct a shuffle instruction + auto shuffle_3 = m->entity()->instr()->create<coco::Shuffle>(); + + shuffle_3->from(shuffle_1->from()); + shuffle_3->into(shuffle_2->into()); + + // Attempt to construct a valid bypass shuffle instruction + bool valid = true; + + for (const auto &C : shuffle_2->range()) + { + auto B = shuffle_2->at(C); + + if (!shuffle_1->defined(B)) + { + valid = false; + break; + } + + auto A = shuffle_1->at(B); + + shuffle_3->insert(A, C); + } + + if (valid) + { + // Insert shuffle_3 before shuffle_2 if shuffle_3 is a valid bypass of shuffle_2 + shuffle_3->insertBefore(shuffle_2); + + // NOTE shuffle_2 SHOULD BE detached and destroyed after shuffle_3 is inserted + shuffle_2->detach(); + m->entity()->instr()->destroy(shuffle_2); + } + else + { + // Destroy shuffle_3 (bypass shuffle) if it is invalid + m->entity()->instr()->destroy(shuffle_3); + } + } + } + } +} + +} // namespace enco + +// +// Hoist Object +// +namespace +{ + +bool hoistable(const coco::Shuffle *shuffle) +{ + auto range = shuffle->range(); + + if (range.size() != shuffle->into()->size()) + { + return false; + } + + for (const auto &dst : range) + { + if (shuffle->at(dst).value() != dst.value()) + { + return false; + } + } + + return true; +} + +bool complete(const coco::Shuffle *s) { return s->range().size() == s->into()->size(); } + +bool compatible(const coco::Shuffle *s1, const coco::Shuffle *s2) +{ + if (s1->from() != s2->from()) + { + return false; + } + + if (s1->into()->size() != s2->into()->size()) + { + return false; + } + + auto range_1 = s1->range(); + auto range_2 = s2->range(); + + if (range_1.size() != range_2.size()) + { + return false; + } + + bool res = true; + + for (const auto &dst : range_2) + { + if (!s1->defined(dst)) + { + res = false; + break; + } + + auto src_1 = s1->at(dst); + auto src_2 = s2->at(dst); + + if (src_1.value() != src_2.value()) + { + res = false; + break; + } + } + + return res; +} + +} // namespace + +namespace enco +{ + +void hoist_object(enco::Code *code) +{ + auto m = code->module(); + + // + // Case 1 + // + for (uint32_t n = 0; n < m->entity()->instr()->size(); ++n) + { + if (auto shuffle = m->entity()->instr()->at(n)->asShuffle()) + { + if (shuffle->parent() == nullptr) + { + continue; + } + + if (hoistable(shuffle)) + { + auto from = shuffle->from(); + auto into = shuffle->into(); + + into->replaceAllDepsWith(from); + } + } + } + + // + // Case 2 + // + for (uint32_t n = 0; n < m->entity()->bag()->size(); ++n) + { + auto bag = m->entity()->bag()->at(n); + + std::map<CodeIndex, coco::Shuffle *> collected; + + for (auto reader : coco::readers(bag)) + { + if (auto ins = reader->loc()) + { + if (auto shuffle = ins->asShuffle()) + { + collected[code_index(shuffle)] = shuffle; + } + } + } + + std::vector<coco::Shuffle *> sorted; + + for (auto it = collected.begin(); it != collected.end(); ++it) + { + sorted.emplace_back(it->second); + } + + for (uint32_t curr = 0; curr < sorted.size(); ++curr) + { + auto const curr_ins = sorted.at(curr); + auto const curr_bag = curr_ins->into(); + + if (!complete(curr_ins)) + { + continue; + } + + for (uint32_t next = curr + 1; next < sorted.size(); ++next) + { + auto const next_ins = sorted.at(next); + auto const next_bag = next_ins->into(); + + if (!complete(next_ins)) + { + continue; + } + + if (compatible(curr_ins, next_ins)) + { + next_bag->replaceAllDepsWith(curr_bag); + } + } + } + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/Optimizations.h b/compiler/enco/core/src/Transforms/Optimizations.h new file mode 100644 index 000000000..7cfc2305c --- /dev/null +++ b/compiler/enco/core/src/Transforms/Optimizations.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ENCO_OPTIMIZATIONS_H__ +#define __ENCO_OPTIMIZATIONS_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Add a bypass Shuffle if two continued Shuffles map same from-into + * + * %bag_1 = Bag(size: N) + * %bag_2 = Bag(size: N) + * %bag_3 = Bag(size: N) + * + * >>> BEFORE <<< + * Shuffle(from: %bag_1, into: %bag_2, [0 -> 0]) + * Shuffle(from: %bag_2, into: %bag_3, [0 -> 0]) + * + * Let's refer to the former shuffle as Shuffle 1 and the latter one as Shuffle 2. + * We can replace Shuffle 2 with new Shuffle 3 as follows when Shuffle 1 and + * Shuffle 2 map to the same position. + * + * >>> AFTER <<< + * Shuffle(from: %bag_1, into: %bag_2, [0 -> 0]) <- Shuffle 1 + * Shuffle(from: %bag_1, into: %bag_3, [0 -> 0]) <- Shuffle 3 + * + * Note that Shuffle 1 can be eliminated when %bag_2 is not used + */ +void generate_bypass_shuffle(enco::Code *code); + +struct BypassGenerationPass final : public Pass +{ + PASS_CTOR(BypassGenerationPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { generate_bypass_shuffle(code(sess)); } +}; + +/** + * @brief Update the base bag of each object if possible + * + * --- Case 1 --- + * Let us consider the following code: + * + * %bag_1 = Bag(size: 4) + * %bag_2 = Bag(size: 1) + * + * %obj_1 = ... at %bag_1 + * %obj_2 = ... at %bag_2 + * + * ... + * Shuffle(from: %bag_1, into: %bag_2, [0 -> 0]) <- shuffle + * ... + * + * Note that the content of %bag_2 after shuffle is identical to a part of %bag_1, so + * the following code is identical to the above code + * + * %bag_1 = Bag(size: 4) + * %bag_2 = Bag(size: 1) + * + * %obj_1 = ... at %bag_1 + * %obj_2 = ... at %bag_1 + * + * ... + * Shuffle(from: %bag_1, into: %bag_2, [0 -> 0]) + * ... + * + * --- Case 2 --- + * Let us consider the following code: + * + * %bag_1 = Bag(size: 4) + * %bag_2 = Bag(size: 1) + * %bag_3 = Bag(size: 1) + * + * %obj_1 = ... at %bag_2 + * %obj_2 = ... at %bag_3 + * + * Shuffle(from: %bag_1, into: %bag_2, [0 -> 0]) <- shuffle_1 + * Shuffle(from: %bag_1, into: %bag_3, [0 -> 0]) <- shuffle_2 + * + * Note that the content of %bag_3 after shuffle_2 is identical to that of %bag_2 after shuffle_1, + * so the following code is identical to the above one: + * + * %bag_1 = Bag(size: 4) + * %bag_2 = Bag(size: 1) + * %bag_3 = Bag(size: 1) + * + * %obj_1 = ... at %bag_2 + * %obj_2 = ... at %bag_2 <- HERE + * + * Shuffle(from: %bag_1, into: %bag_2, [0 -> 0]) <- shuffle_1 + * Shuffle(from: %bag_1, into: %bag_3, [0 -> 0]) <- shuffle_2 + * + * "hoist_object" optimization rewrites the former code as the latter one. + * + * NOTE "hoist_object" DOES NOT change any instruction. It just updates the base bag of objects of + * interest. + */ +void hoist_object(enco::Code *code); + +} // namespace enco + +#endif // __ENCO_OPTIMIZATIONS_H__ diff --git a/compiler/enco/core/src/Transforms/Split.cpp b/compiler/enco/core/src/Transforms/Split.cpp new file mode 100644 index 000000000..b57b8f882 --- /dev/null +++ b/compiler/enco/core/src/Transforms/Split.cpp @@ -0,0 +1,1233 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "Split.h" +#include "Usage.h" +#include "Session.h" +#include "coex/IR.h" + +#include <coco/IR.h> + +#include <nncc/core/ADT/kernel/NHWCLayout.h> +#include <stdex/Memory.h> + +#include <map> +#include <stdexcept> +#include <functional> + +using stdex::make_unique; + +namespace +{ + +std::map<const coco::Module *, std::unique_ptr<ANNContext>> _subnet_contexts; + +} // namespace + +namespace enco +{ + +const ANNContext *SubnetManager::context(const coco::Module *m) +{ + return _subnet_contexts.at(m).get(); +} + +} // namespace enco + +namespace +{ + +using Appender = std::function<void(ANNBinder *binder)>; + +struct ANNOpAppender +{ + virtual ~ANNOpAppender() = default; + + virtual void append(ANNBinder *binder) const = 0; +}; + +class ANNAddAppender final : public ANNOpAppender +{ +public: + void left(coco::FeatureObject *o) { _left = o; } + void right(coco::FeatureObject *o) { _right = o; } + void out(coco::FeatureObject *o) { _out = o; } + +public: + void append(ANNBinder *binder) const override + { + auto left = binder->addOperand<float>(_left); + auto right = binder->addOperand<float>(_right); + auto fuse = binder->addOperand<int32_t>(); + binder->setOperand(fuse, 0); + + auto out = binder->addOperand<float>(_out); + + binder->addOperation(ann::Operation::Code::ADD, {left, right, fuse}, {out}); + } + +private: + coco::FeatureObject *_left = nullptr; + coco::FeatureObject *_right = nullptr; + coco::FeatureObject *_out = nullptr; +}; + +class ANNMulAppender final : public ANNOpAppender +{ +public: + void left(coco::FeatureObject *o) { _left = o; } + void right(coco::FeatureObject *o) { _right = o; } + void out(coco::FeatureObject *o) { _out = o; } + +public: + void append(ANNBinder *binder) const override + { + auto left = binder->addOperand<float>(_left); + auto right = binder->addOperand<float>(_right); + auto fuse = binder->addOperand<int32_t>(); + binder->setOperand(fuse, 0); + + auto out = binder->addOperand<float>(_out); + + binder->addOperation(ann::Operation::Code::MUL, {left, right, fuse}, {out}); + } + +private: + coco::FeatureObject *_left = nullptr; + coco::FeatureObject *_right = nullptr; + coco::FeatureObject *_out = nullptr; +}; + +/** + * WARN The current implementation supports concatenation along depth only + */ +class ANNConcatAppender final : public ANNOpAppender +{ +public: + void left(coco::FeatureObject *o) { _left = o; } + void right(coco::FeatureObject *o) { _right = o; } + void out(coco::FeatureObject *o) { _out = o; } + +public: + void append(ANNBinder *binder) const override + { + auto left = binder->addOperand<float>(_left); + auto right = binder->addOperand<float>(_right); + auto axis = binder->addOperand<int32_t>(); + binder->setOperand(axis, 3 /* DEPTH */); + + auto out = binder->addOperand<float>(_out); + + binder->addOperation(ann::Operation::Code::CONCAT, {left, right, axis}, {out}); + } + +private: + coco::FeatureObject *_left = nullptr; + coco::FeatureObject *_right = nullptr; + coco::FeatureObject *_out = nullptr; +}; + +class ANNConv2DAppender final : public ANNOpAppender +{ +public: + void session(const enco::SessionID &sess) { _sess = sess; } + + void pad(const coco::Padding2D *pad) { _pad = *pad; } + void stride(const coco::Stride2D *stride) { _stride = *stride; } + + void ifm(coco::FeatureObject *ifm) { _ifm = ifm; } + void ker(coco::KernelObject *ker) { _ker = ker; } + // Q: Should we take a bias as a feature object? + // NOTE This interface is subject to change + void bias(coco::FeatureObject *bias) { _bias = bias; } + void ofm(coco::FeatureObject *ofm) { _ofm = ofm; } + +public: + void append(ANNBinder *binder) const override + { + auto data = enco::data(_sess); + + auto ifm = binder->addOperand<float>(_ifm); + auto ker = binder->addOperand<float>(_ker); + + // Fill kernel data + { + auto ker_bag = _ker->bag(); + auto ker_weight = data->f32()->weight(ker_bag); + + assert(ker_weight.data() != nullptr); + + binder->setOperand(ker, ker_weight.data(), ker_weight.data() + ker_weight.size()); + } + + // Conv2D in coco IR has no bias, but bias is mandatory in Android NN API + auto bias = binder->addOperand<float>(nncc::core::ADT::tensor::Shape{_ker->shape().count()}); + + // Fill bias data + if (_bias == nullptr) + { + // Use a fresh empty bias if "bias" is not specified + auto length = _ker->shape().count(); + + std::vector<float> values; + values.resize(length, 0.0f); + + binder->setOperand(bias, values.begin(), values.end()); + } + else + { + // Use specified "bias" + auto bias_bag = _bias->bag(); + auto bias_weight = data->f32()->weight(bias_bag); + + assert(bias_weight.data() != nullptr); + assert(bias_weight.size() == _ker->shape().count()); + + binder->setOperand(bias, bias_weight.data(), bias_weight.data() + bias_weight.size()); + } + + auto left = binder->addOperand<int32_t>(); + binder->setOperand(left, _pad.left()); + auto right = binder->addOperand<int32_t>(); + binder->setOperand(right, _pad.right()); + auto top = binder->addOperand<int32_t>(); + binder->setOperand(top, _pad.top()); + auto bottom = binder->addOperand<int32_t>(); + binder->setOperand(bottom, _pad.bottom()); + auto hstride = binder->addOperand<int32_t>(); + binder->setOperand(hstride, _stride.horizontal()); + auto vstride = binder->addOperand<int32_t>(); + binder->setOperand(vstride, _stride.vertical()); + auto fuse = binder->addOperand<int32_t>(); + binder->setOperand(fuse, 0); + + auto ofm = binder->addOperand<float>(_ofm); + + binder->addOperation(ann::Operation::Code::CONV_2D, + {ifm, ker, bias, left, right, top, bottom, hstride, vstride, fuse}, {ofm}); + } + +private: + enco::SessionID _sess; + +private: + coco::Padding2D _pad; + coco::Stride2D _stride; + +private: + coco::FeatureObject *_ifm = nullptr; + coco::KernelObject *_ker = nullptr; + coco::FeatureObject *_bias = nullptr; + coco::FeatureObject *_ofm = nullptr; +}; + +class ANNDepthwiseConv2DAppender final : public ANNOpAppender +{ +public: + void session(const enco::SessionID &sess) { _sess = sess; } + + void multiplier(const uint32_t &multiplier) { _multiplier = multiplier; } + void pad(const coco::Padding2D *pad) { _pad = *pad; } + void stride(const coco::Stride2D *stride) { _stride = *stride; } + + void ifm(coco::FeatureObject *ifm) { _ifm = ifm; } + void ker(coco::KernelObject *ker) { _ker = ker; } + void ofm(coco::FeatureObject *ofm) { _ofm = ofm; } + +public: + void append(ANNBinder *binder) const override + { + using namespace nncc::core::ADT; + + auto data = enco::data(_sess); + + const uint32_t ker_N = _ker->shape().count(); + const uint32_t ker_H = _ker->shape().height(); + const uint32_t ker_W = _ker->shape().width(); + + assert(ker_N % _multiplier == 0); + const uint32_t group = ker_N / _multiplier; + + auto ifm = binder->addOperand<float>(_ifm); + auto ker = binder->addOperand<float>(tensor::Shape{1, ker_H, ker_W, ker_N}); + + // Fill kernel data + { + auto obj = _ker; + auto shape = obj->shape(); + + auto ovl = data->f32()->read(obj); + assert(ovl != nullptr); + + // Flatten? + std::vector<float> values; + + /** + * Android NN computes DEPTHWISE_CONV_2D as follows: + * + * output[b, i, j, k * channel_multiplier + q] = + * sum_{di, dj} ( + * input[b, strides[1] * i + di, strides[2] * j + dj, k] * + * filter[1, di, dj, k * channel_multiplier + q] + * ) + bias[k * channel_multiplier + q] + * + */ + for (uint32_t row = 0; row < shape.height(); ++row) + { + for (uint32_t col = 0; col < shape.width(); ++col) + { + for (uint32_t g = 0; g < group; ++g) + { + for (uint32_t m = 0; m < _multiplier; ++m) + { + const auto value = ovl->at(g * _multiplier + m, 0, row, col); + values.emplace_back(value); + } + } + } + } + + assert(values.size() == nncc::core::ADT::kernel::num_elements(shape)); + binder->setOperand(ker, values.begin(), values.end()); + } + + // Conv2D in coco IR has no bias, but bias is mandatory in Android NN API + auto bias = binder->addOperand<float>(nncc::core::ADT::tensor::Shape{_ker->shape().count()}); + + // Fill bias data + { + auto length = _ker->shape().count(); + + std::vector<float> values; + values.resize(length, 0.0f); + + binder->setOperand(bias, values.begin(), values.end()); + } + + auto left = binder->addOperand<int32_t>(); + binder->setOperand(left, _pad.left()); + auto right = binder->addOperand<int32_t>(); + binder->setOperand(right, _pad.right()); + auto top = binder->addOperand<int32_t>(); + binder->setOperand(top, _pad.top()); + auto bottom = binder->addOperand<int32_t>(); + binder->setOperand(bottom, _pad.bottom()); + auto hstride = binder->addOperand<int32_t>(); + binder->setOperand(hstride, _stride.horizontal()); + auto vstride = binder->addOperand<int32_t>(); + binder->setOperand(vstride, _stride.vertical()); + auto multiplier = binder->addOperand<int32_t>(); + binder->setOperand(multiplier, _multiplier); + auto fuse = binder->addOperand<int32_t>(); + binder->setOperand(fuse, 0); + + auto ofm = binder->addOperand<float>(_ofm); + + binder->addOperation( + ann::Operation::Code::DEPTHWISE_CONV_2D, + {ifm, ker, bias, left, right, top, bottom, hstride, vstride, multiplier, fuse}, {ofm}); + } + +private: + enco::SessionID _sess; + +private: + uint32_t _multiplier; + coco::Padding2D _pad; + coco::Stride2D _stride; + +private: + coco::FeatureObject *_ifm = nullptr; + coco::KernelObject *_ker = nullptr; + coco::FeatureObject *_ofm = nullptr; +}; + +class ANNReLUAppender final : public ANNOpAppender +{ +public: + void ifm(coco::FeatureObject *ifm) { _ifm = ifm; } + void ofm(coco::FeatureObject *ofm) { _ofm = ofm; } + +public: + void append(ANNBinder *binder) const override + { + auto ifm = binder->addOperand<float>(_ifm); + auto ofm = binder->addOperand<float>(_ofm); + + binder->addOperation(ann::Operation::Code::RELU, {ifm}, {ofm}); + } + +private: + coco::FeatureObject *_ifm = nullptr; + coco::FeatureObject *_ofm = nullptr; +}; + +class ANNReLU6Appender final : public ANNOpAppender +{ +public: + void ifm(coco::FeatureObject *ifm) { _ifm = ifm; } + void ofm(coco::FeatureObject *ofm) { _ofm = ofm; } + +public: + void append(ANNBinder *binder) const override + { + auto ifm = binder->addOperand<float>(_ifm); + auto ofm = binder->addOperand<float>(_ofm); + + binder->addOperation(ann::Operation::Code::RELU6, {ifm}, {ofm}); + } + +private: + coco::FeatureObject *_ifm = nullptr; + coco::FeatureObject *_ofm = nullptr; +}; + +class ANNMaxPool2DAppender final : public ANNOpAppender +{ +public: + void pad(const coco::Padding2D *pad) { _pad = *pad; } + void stride(const coco::Stride2D *stride) { _stride = *stride; } + void window(const coco::Window2D *window) { _window = *window; } + + void ifm(coco::FeatureObject *ifm) { _ifm = ifm; } + void ofm(coco::FeatureObject *ofm) { _ofm = ofm; } + +public: + void append(ANNBinder *binder) const override + { + auto ifm = binder->addOperand<float>(_ifm); + + // Set padding + auto left = binder->addOperand<int32_t>(); + binder->setOperand(left, _pad.left()); + auto right = binder->addOperand<int32_t>(); + binder->setOperand(right, _pad.right()); + auto top = binder->addOperand<int32_t>(); + binder->setOperand(top, _pad.top()); + auto bottom = binder->addOperand<int32_t>(); + binder->setOperand(bottom, _pad.bottom()); + + // Set horizontal/vertical stride + auto hstride = binder->addOperand<int32_t>(); + binder->setOperand(hstride, _stride.horizontal()); + auto vstride = binder->addOperand<int32_t>(); + binder->setOperand(vstride, _stride.vertical()); + + // Set receptive field size + auto width = binder->addOperand<int32_t>(); + binder->setOperand(width, _window.width()); + auto height = binder->addOperand<int32_t>(); + binder->setOperand(height, _window.height()); + + // Set fuse code + // TODO Suport operation fusion + auto fuse = binder->addOperand<int32_t>(); + binder->setOperand(fuse, 0); + + auto ofm = binder->addOperand<float>(_ofm); + + binder->addOperation(ann::Operation::Code::MAX_POOL_2D, + {ifm, left, right, top, bottom, hstride, vstride, width, height, fuse}, + {ofm}); + } + +private: + coco::Padding2D _pad; + coco::Stride2D _stride; + coco::Window2D _window; + +private: + coco::FeatureObject *_ifm = nullptr; + coco::FeatureObject *_ofm = nullptr; +}; + +class ANNAvgPool2DAppender final : public ANNOpAppender +{ +public: + void pad(const coco::Padding2D *pad) { _pad = *pad; } + void stride(const coco::Stride2D *stride) { _stride = *stride; } + void window(const coco::Window2D *window) { _window = *window; } + + void ifm(coco::FeatureObject *ifm) { _ifm = ifm; } + void ofm(coco::FeatureObject *ofm) { _ofm = ofm; } + +public: + void append(ANNBinder *binder) const override + { + auto ifm = binder->addOperand<float>(_ifm); + + // Set padding + auto left = binder->addOperand<int32_t>(); + binder->setOperand(left, _pad.left()); + auto right = binder->addOperand<int32_t>(); + binder->setOperand(right, _pad.right()); + auto top = binder->addOperand<int32_t>(); + binder->setOperand(top, _pad.top()); + auto bottom = binder->addOperand<int32_t>(); + binder->setOperand(bottom, _pad.bottom()); + + // Set horizontal/vertical stride + auto hstride = binder->addOperand<int32_t>(); + binder->setOperand(hstride, _stride.horizontal()); + auto vstride = binder->addOperand<int32_t>(); + binder->setOperand(vstride, _stride.vertical()); + + // Set receptive field size + auto width = binder->addOperand<int32_t>(); + binder->setOperand(width, _window.width()); + auto height = binder->addOperand<int32_t>(); + binder->setOperand(height, _window.height()); + + // Set fuse code + // TODO Suport operation fusion + auto fuse = binder->addOperand<int32_t>(); + binder->setOperand(fuse, 0); + + auto ofm = binder->addOperand<float>(_ofm); + + binder->addOperation(ann::Operation::Code::AVG_POOL_2D, + {ifm, left, right, top, bottom, hstride, vstride, width, height, fuse}, + {ofm}); + } + +private: + coco::Padding2D _pad; + coco::Stride2D _stride; + coco::Window2D _window; + +private: + coco::FeatureObject *_ifm = nullptr; + coco::FeatureObject *_ofm = nullptr; +}; + +class ANNPadFAppender final : public ANNOpAppender +{ +public: + void pad(const coco::Padding2D *pad) { _pad = *pad; } + +public: + void ifm(coco::FeatureObject *ifm) { _ifm = ifm; } + void ofm(coco::FeatureObject *ofm) { _ofm = ofm; } + +public: + void append(ANNBinder *binder) const override + { + using nncc::core::ADT::tensor::Shape; + + auto ifm = binder->addOperand<float>(_ifm); + auto pad = binder->addOperand<int32_t>(Shape{4, 2}); + { + std::vector<int32_t> values; + values.resize(8); + // For 'N' + values.at(0) = values.at(1) = 0; + // For 'H' + values.at(2) = _pad.top(); + values.at(3) = _pad.bottom(); + // For 'W' + values.at(4) = _pad.left(); + values.at(5) = _pad.right(); + // For 'C' + values.at(6) = values.at(7) = 0; + + binder->setOperand(pad, values.begin(), values.end()); + } + + auto ofm = binder->addOperand<float>(_ofm); + + binder->addOperation(ann::Operation::Code::PAD, {ifm, pad}, {ofm}); + } + +private: + coco::Padding2D _pad; + +private: + coco::FeatureObject *_ifm = nullptr; + coco::FeatureObject *_ofm = nullptr; +}; + +class ANNOpFunctionalAppender final : public ANNOpAppender +{ +public: + ANNOpFunctionalAppender(const Appender &fun) : _fun{fun} + { + // DO NOTHING + } + +public: + void append(ANNBinder *binder) const { _fun(binder); } + +private: + Appender _fun; +}; + +class ANNSubAppender final : public ANNOpAppender +{ +public: + void left(coco::FeatureObject *o) { _left = o; } + void right(coco::FeatureObject *o) { _right = o; } + void out(coco::FeatureObject *o) { _out = o; } + +public: + void append(ANNBinder *binder) const override + { + auto left = binder->addOperand<float>(_left); + auto right = binder->addOperand<float>(_right); + auto fuse = binder->addOperand<int32_t>(); + binder->setOperand(fuse, 0); + + auto out = binder->addOperand<float>(_out); + + binder->addOperation(ann::Operation::Code::SUB, {left, right, fuse}, {out}); + } + +private: + coco::FeatureObject *_left = nullptr; + coco::FeatureObject *_right = nullptr; + coco::FeatureObject *_out = nullptr; +}; + +class ANNDivAppender final : public ANNOpAppender +{ +public: + void left(coco::FeatureObject *o) { _left = o; } + void right(coco::FeatureObject *o) { _right = o; } + void out(coco::FeatureObject *o) { _out = o; } + +public: + void append(ANNBinder *binder) const override + { + auto left = binder->addOperand<float>(_left); + auto right = binder->addOperand<float>(_right); + auto fuse = binder->addOperand<int32_t>(); + binder->setOperand(fuse, 0); + + auto out = binder->addOperand<float>(_out); + + binder->addOperation(ann::Operation::Code::DIV, {left, right, fuse}, {out}); + } + +private: + coco::FeatureObject *_left = nullptr; + coco::FeatureObject *_right = nullptr; + coco::FeatureObject *_out = nullptr; +}; + +class ANNOpBuilder : public coco::Instr::Visitor<std::unique_ptr<ANNOpAppender>> +{ +public: + std::unique_ptr<ANNOpAppender> visit(const coco::Eval *eval) + { + if (auto conv = eval->op()->asConv2D()) + { + if (auto load = conv->arg()->asLoad()) + { + auto sess = enco::session(eval->module()); + + auto ifm = load->object()->asFeature(); + auto ker = conv->ker(); + auto ofm = eval->out()->asFeature(); + + const auto group = conv->group(); + + if (group == 1) + { + auto app = make_unique<ANNConv2DAppender>(); + + app->session(sess); + + app->pad(conv->pad()); + app->stride(conv->stride()); + + app->ifm(ifm); + app->ofm(ofm); + app->ker(ker); + + return std::move(app); + } + else + { + assert(ifm->shape().depth() == group); + assert(ker->shape().count() % group == 0); + assert(ker->shape().depth() == 1); + + auto app = make_unique<ANNDepthwiseConv2DAppender>(); + + app->session(sess); + + app->multiplier(ker->shape().count() / group); + app->pad(conv->pad()); + app->stride(conv->stride()); + + app->ifm(ifm); + app->ofm(ofm); + app->ker(ker); + + return std::move(app); + } + } + } + else if (auto op = eval->op()->asAdd()) + { + auto left_load = op->left()->asLoad(); + auto right_load = op->right()->asLoad(); + + if (left_load && right_load) + { + // Let's compile the following code fragment: + // + // %ofm = eval(Add(Load(%left), Load(%right))) + // + auto left = left_load->object()->asFeature(); + auto right = right_load->object()->asFeature(); + assert(left != nullptr && right != nullptr); + + auto out = eval->out()->asFeature(); + assert(out != nullptr); + + auto app = make_unique<ANNAddAppender>(); + + app->left(left); + app->right(right); + app->out(out); + + return std::move(app); + } + } + else if (auto op = eval->op()->asMul()) + { + auto left_load = op->left()->asLoad(); + auto right_load = op->right()->asLoad(); + + if (left_load && right_load) + { + // Let's compile the following code fragment: + // + // %ofm = eval(Mul(Load(%left), Load(%right))) + // + auto left = left_load->object()->asFeature(); + auto right = right_load->object()->asFeature(); + assert(left != nullptr && right != nullptr); + + auto out = eval->out()->asFeature(); + assert(out != nullptr); + + auto app = make_unique<ANNMulAppender>(); + + app->left(left); + app->right(right); + app->out(out); + + return std::move(app); + } + } + else if (auto op = eval->op()->asPadF()) + { + if (auto load = op->arg()->asLoad()) + { + // Let's compile the following code fragment: + // + // %ofm = eval(PadF(Load(%ifm)) + // + auto ifm = load->object()->asFeature(); + auto ofm = eval->out()->asFeature(); + + assert(ifm != nullptr && ofm != nullptr); + + auto app = make_unique<ANNPadFAppender>(); + + app->pad(op->pad()); + + app->ifm(ifm); + app->ofm(ofm); + + return std::move(app); + } + } + else if (auto maxpool = eval->op()->asMaxPool2D()) + { + if (auto load = maxpool->arg()->asLoad()) + { + // Let's compile the following code fragment: + // + // %ofm = eval(MaxPool2D(Load(%ifm)) + // + auto ifm = load->object()->asFeature(); + auto ofm = eval->out()->asFeature(); + + assert(ifm != nullptr && ofm != nullptr); + + auto app = make_unique<ANNMaxPool2DAppender>(); + + app->pad(maxpool->pad()); + app->stride(maxpool->stride()); + app->window(maxpool->window()); + + app->ifm(ifm); + app->ofm(ofm); + + return std::move(app); + } + } + else if (auto avgpool = eval->op()->asAvgPool2D()) + { + if (auto load = avgpool->arg()->asLoad()) + { + // Let's compile the following code fragment: + // + // %ofm = eval(AvgPool2D(Load(%ifm)) + // + if (avgpool->divisor() == coco::AvgPool2D::Divisor::PaddingExcluded) + { + // When ANN runtime computes the average of each receptive field, + // it uses the number of valid(=non-padding) elements as a divisor. + auto ifm = load->object()->asFeature(); + auto ofm = eval->out()->asFeature(); + + assert(ifm != nullptr && ofm != nullptr); + + auto app = make_unique<ANNAvgPool2DAppender>(); + + app->pad(avgpool->pad()); + app->stride(avgpool->stride()); + app->window(avgpool->window()); + + app->ifm(ifm); + app->ofm(ofm); + + return std::move(app); + } + } + } + else if (auto relu = eval->op()->asReLU()) + { + if (auto load = relu->arg()->asLoad()) + { + // Let's compile the following code fragment: + // + // %ofm = eval(ReLU(Load(%ifm)) + // + // TODO Support objects of other kinds, such as Tensor + auto ifm = load->object()->asFeature(); + auto ofm = eval->out()->asFeature(); + + assert(ifm != nullptr && ofm != nullptr); + + auto app = make_unique<ANNReLUAppender>(); + + app->ifm(ifm); + app->ofm(ofm); + + return std::move(app); + } + } + else if (auto relu6 = eval->op()->asReLU6()) + { + if (auto load = relu6->arg()->asLoad()) + { + // Let's compile the following code fragment: + // + // %ofm = eval(ReLU6(Load(%ifm)) + // + // TODO Support objects of other kinds, such as Tensor + auto ifm = load->object()->asFeature(); + auto ofm = eval->out()->asFeature(); + + assert(ifm != nullptr && ofm != nullptr); + + auto app = make_unique<ANNReLU6Appender>(); + + app->ifm(ifm); + app->ofm(ofm); + + return std::move(app); + } + } + else if (auto op = eval->op()->asConcatF()) + { + auto left_load = op->left()->asLoad(); + auto right_load = op->right()->asLoad(); + + if (left_load && right_load && (op->axis() == coco::ConcatF::Axis::Depth)) + { + // Let's compile the following code fragment: + // + // %ofm = eval(ConcatF(Depth, Load(%left), Load(%right))) + // + auto left = left_load->object()->asFeature(); + auto right = right_load->object()->asFeature(); + assert(left != nullptr && right != nullptr); + + auto out = eval->out()->asFeature(); + assert(out != nullptr); + + auto app = make_unique<ANNConcatAppender>(); + + app->left(left); + app->right(right); + app->out(out); + + return std::move(app); + } + } + else if (auto op = eval->op()->asSub()) + { + auto left_load = op->left()->asLoad(); + auto right_load = op->right()->asLoad(); + + if (left_load && right_load) + { + // Let's compile the following code fragment: + // + // %out = eval(Sub(Load(%left), Load(%right))) + // + auto left = left_load->object()->asFeature(); + auto right = right_load->object()->asFeature(); + assert(left != nullptr && right != nullptr); + + auto out = eval->out()->asFeature(); + assert(out != nullptr); + + auto app = make_unique<ANNSubAppender>(); + + app->left(left); + app->right(right); + app->out(out); + + return std::move(app); + } + } + else if (auto op = eval->op()->asDiv()) + { + auto left_load = op->left()->asLoad(); + auto right_load = op->right()->asLoad(); + + if (left_load && right_load) + { + // Let's compile the following code fragment: + // + // %out = eval(Div(Load(%left), Load(%right))) + // + auto left = left_load->object()->asFeature(); + auto right = right_load->object()->asFeature(); + assert(left != nullptr && right != nullptr); + + auto out = eval->out()->asFeature(); + assert(out != nullptr); + + auto app = make_unique<ANNDivAppender>(); + + app->left(left); + app->right(right); + app->out(out); + + return std::move(app); + } + } + + // Return nullptr if a given Eval instruction is incompatible + return nullptr; + } + +public: + std::unique_ptr<ANNOpAppender> visit(const coco::Shuffle *) { return nullptr; } +}; + +namespace +{ + +std::unique_ptr<ANNOpAppender> make_appender(coco::Instr *ins) +{ + ANNOpBuilder op_builder; + + if (auto eval = coco::safe_cast<coco::Eval>(ins)) + { + return eval->accept(op_builder); + } + + if (auto depth_concat = coco::safe_cast<ANNDepthConcatF>(ins)) + { + auto app = make_unique<ANNConcatAppender>(); + + app->out(depth_concat->out()->asFeature()); + + app->left(depth_concat->fst()->asFeature()); + app->right(depth_concat->snd()->asFeature()); + + return std::move(app); + } + + // Build ANN IR from ANNConv2D instruction + if (auto conv2d = coco::safe_cast<ANNConv2D>(ins)) + { + auto sess = enco::session(conv2d->module()); + auto app = make_unique<ANNConv2DAppender>(); + + app->session(sess); + + app->pad(conv2d->pad()); + app->stride(conv2d->stride()); + + app->ofm(conv2d->ofm()->asFeature()); + app->ifm(conv2d->ifm()->asFeature()); + app->ker(conv2d->ker()->asKernel()); + app->bias(coco::safe_cast<coco::FeatureObject>(conv2d->bias())); + + return std::move(app); + } + + return nullptr; +} + +enum Compatibility +{ + COMPATIBLE, + INCOMPATIBLE +}; + +class ANNGroupBuilder +{ +public: + ANNGroupBuilder(ANNContext *ctx) : _ctx{ctx} + { + // DO NOTHING + } + +public: + Compatibility kind(const coco::Block *blk) const; + Compatibility kind(const std::unique_ptr<ANNOpAppender> &appender) const; + +public: + void build(enco::Code *code) const; + +private: + ANNContext *_ctx; +}; + +Compatibility ANNGroupBuilder::kind(const std::unique_ptr<ANNOpAppender> &app) const +{ + return app ? COMPATIBLE : INCOMPATIBLE; +} + +Compatibility ANNGroupBuilder::kind(const coco::Block *blk) const +{ + return (_ctx->find(blk) != nullptr) ? COMPATIBLE : INCOMPATIBLE; +} + +void ANNGroupBuilder::build(enco::Code *code) const +{ + auto m = code->module(); + + // ANNGroupBuilder will construct a sequence of blocks from the original block sequence, and + // a destination block (that dst_blk points to) is the tail of the generated sequence. + coco::Block *dst_blk = nullptr; + + auto append = [&](const Compatibility &t) { + auto blk = m->entity()->block()->create(); + + if (dst_blk == nullptr) + { + m->block()->prepend(blk); + } + else + { + blk->insertAfter(dst_blk); + } + + dst_blk = blk; + + if (COMPATIBLE == t) + { + _ctx->create(blk); + } + }; + + for (auto blk = m->block()->head(); blk;) + { + // Let's move instructions from a block of interest (referred to as source block) into + // a destination block + auto src_blk = blk; + blk = src_blk->next(); + src_blk->detach(); + + for (auto ins = src_blk->instr()->head(); ins;) + { + auto cur_ins = ins; + ins = cur_ins->next(); + cur_ins->detach(); + + auto cur_append = make_appender(cur_ins); + + // Create a new compatible block and use it as a destination block if the current + // destination block is absent or incompatible with the instruction of intereset. + if ((dst_blk == nullptr) || (kind(cur_append) != kind(dst_blk))) + { + append(kind(cur_append)); + } + + assert(dst_blk != nullptr); + assert(kind(cur_append) == kind(dst_blk)); + + // Append ins to the dst_blk block + dst_blk->instr()->append(cur_ins); + + if (cur_append) + { + // Update Android NN IR if the current instruction is compatible + auto binder = _ctx->find(dst_blk); + assert(binder != nullptr); + cur_append->append(binder); + } + } + + // Destroy the source block + assert(src_blk->instr()->empty()); + m->entity()->block()->destroy(src_blk); + } +} + +} // namespace + +class ANNModuleBuilder +{ +private: + std::set<coco::Bag *> inputs(ANNBinder *binder) const; + std::set<coco::Bag *> outputs(ANNBinder *binder) const; + +public: + void build(ANNContext *ann_ctx) const; +}; + +std::set<coco::Bag *> ANNModuleBuilder::inputs(ANNBinder *binder) const +{ + std::set<coco::Bag *> res; + + for (auto bag : binder->bags()) + { + auto u = enco::updaters(bag); + u.erase(binder->block()); + + /** + * A bag is the input of this block if + * 1. it is an input of the whole network, or + * 2. it is updated by preceding blocks during execution + */ + if (bag->isInput() || (u.size() > 0)) + { + res.insert(bag); + } + } + + return res; +} + +std::set<coco::Bag *> ANNModuleBuilder::outputs(ANNBinder *binder) const +{ + std::set<coco::Bag *> res; + + for (auto bag : binder->bags()) + { + auto u = enco::updaters(bag); + auto r = enco::readers(bag); + r.erase(binder->block()); + + /** + * Only a bag that this block updates can be the output of this block + */ + if (u.find(binder->block()) == u.end()) + { + continue; + } + + /** + * A bag is the output of this block if + * 1. it is an output of the whole network, or + * 2. it is read by following blocks during execution + */ + if (bag->isOutput() || (r.size() > 0)) + { + res.insert(bag); + } + } + + return res; +} + +void ANNModuleBuilder::build(ANNContext *ann_ctx) const +{ + for (uint32_t n = 0; n < ann_ctx->count(); ++n) + { + auto binder = ann_ctx->nth(n); + + // NOTE binder->module() returns an ANN IR module (not coco IR module) + auto m = binder->block()->module(); + auto d = enco::data(m); + + // Let's identify operands with initial values + for (auto bag : binder->bags()) + { + if (binder->associated(bag) && d->allocated(bag)) + { + // TODO Support other datatype + auto span = d->f32()->weight(bag); + assert(span.data() != nullptr); + + binder->setOperand(binder->operand(bag), span.data(), span.data() + span.size()); + } + } + + // Let's identify input/output bags + binder->identifyInputs(inputs(binder)); + binder->identifyOutputs(outputs(binder)); + } +} + +} // namespace + +namespace +{ + +class SplitPass +{ +public: + void runOnCode(enco::Code *code) const; +}; + +void SplitPass::runOnCode(enco::Code *code) const +{ + auto ann_ctx = make_unique<ANNContext>(); + + ANNGroupBuilder group_builder{ann_ctx.get()}; + group_builder.build(code); + + ANNModuleBuilder module_builder; + module_builder.build(ann_ctx.get()); + + _subnet_contexts[code->module()] = std::move(ann_ctx); +} + +} // namespace + +namespace enco +{ + +void split_into_phases(enco::Code *code) +{ + SplitPass split; + split.runOnCode(code); +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/Split.h b/compiler/enco/core/src/Transforms/Split.h new file mode 100644 index 000000000..b4e1d7baf --- /dev/null +++ b/compiler/enco/core/src/Transforms/Split.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __SPLIT_H__ +#define __SPLIT_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +struct SubnetManager +{ + static const ANNContext *context(const coco::Module *m); +}; + +/** + * @brief Split instructions into a set of phases + */ +void split_into_phases(enco::Code *code); + +struct PhaseConstructionPass final : public Pass +{ + PASS_CTOR(PhaseConstructionPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { split_into_phases(code(sess)); } +}; + +} // namespace enco; + +#endif // __SPLIT_H__ diff --git a/compiler/enco/core/src/Usage.cpp b/compiler/enco/core/src/Usage.cpp new file mode 100644 index 000000000..92ccba5a0 --- /dev/null +++ b/compiler/enco/core/src/Usage.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "Usage.h" + +namespace enco +{ + +std::set<coco::Block *> readers(const coco::Bag *bag) +{ + std::set<coco::Block *> res; + + for (auto read : coco::readers(bag)) + { + assert(read != nullptr); + auto instr = read->loc(); + assert(instr != nullptr); + auto block = instr->parent(); + assert(block != nullptr); + + res.insert(block); + } + + return res; +} + +std::set<coco::Block *> updaters(const coco::Bag *bag) +{ + std::set<coco::Block *> res; + + for (auto update : coco::updaters(bag)) + { + assert(update != nullptr); + auto instr = update->loc(); + assert(instr != nullptr); + auto block = instr->parent(); + assert(block != nullptr); + + res.insert(block); + } + + return res; +} + +} // namespace enco diff --git a/compiler/enco/core/src/Usage.h b/compiler/enco/core/src/Usage.h new file mode 100644 index 000000000..8fa05f9b9 --- /dev/null +++ b/compiler/enco/core/src/Usage.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ENCO_USAGE_H__ +#define __ENCO_USAGE_H__ + +#include "coco/IR.h" + +#include <set> + +namespace enco +{ + +/// @brief Returns the set of blocks that reads a given bag +std::set<coco::Block *> readers(const coco::Bag *bag); +/// @brief Return the set of blocks that updates a given bag +std::set<coco::Block *> updaters(const coco::Bag *bag); + +} // namespace enco + +#endif // __ENCO_USAGE_H__ diff --git a/compiler/enco/core/src/coex/IR.h b/compiler/enco/core/src/coex/IR.h new file mode 100644 index 000000000..e81943f18 --- /dev/null +++ b/compiler/enco/core/src/coex/IR.h @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 __ENCO_COEX_IR_H__ +#define __ENCO_COEX_IR_H__ + +#include <coco/IR.h> + +/** + * @brief 2D Convolution through Andoird NN API + * + * TODO Support FusedActivation + */ +class ANNConv2D : public coco::Instr, public coco::Object::Producer, public coco::Object::Consumer +{ +public: + ANNConv2D() : _ofm{this}, _ifm{this}, _ker{this}, _bias{this} + { + // DO NOTHING + } + +public: + coco::Instr *loc(void) override { return this; } + +public: + coco::Object *ofm(void) const { return _ofm.value(); } + void ofm(coco::Object *o) { _ofm.value(o); } + + coco::Object *ifm(void) const { return _ifm.value(); } + void ifm(coco::Object *o) { _ifm.value(o); } + + coco::Object *ker(void) const { return _ker.value(); } + void ker(coco::Object *o) { _ker.value(o); } + + /** + * Currently, this "bias" is a Feature object with channel-wise layout + * + * NOTE This design is subject to change + */ + coco::Object *bias(void) const { return _bias.value(); } + void bias(coco::Object *o) { _bias.value(o); } + +public: + coco::Padding2D *pad(void) { return &_pad; } + const coco::Padding2D *pad(void) const { return &_pad; } + + coco::Stride2D *stride(void) { return &_stride; } + const coco::Stride2D *stride(void) const { return &_stride; } + +private: + coco::Def _ofm; + + coco::Use _ifm; + coco::Use _ker; + coco::Use _bias; + +private: + coco::Padding2D _pad; + coco::Stride2D _stride; +}; + +/** + * @brief Concatenate feature maps along "depth" dimension through Andoird NN API + */ +class ANNDepthConcatF : public coco::Instr, + public coco::Object::Producer, + public coco::Object::Consumer +{ +public: + ANNDepthConcatF() : _out{this}, _fst{this}, _snd{this} + { + // DO NOTHING + } + +public: + coco::Instr *loc(void) override { return this; } + +public: + coco::Object *out(void) const { return _out.value(); } + void out(coco::Object *o) { _out.value(o); } + + coco::Object *fst(void) const { return _fst.value(); } + void fst(coco::Object *o) { _fst.value(o); } + + coco::Object *snd(void) const { return _snd.value(); } + void snd(coco::Object *o) { _snd.value(o); } + +private: + coco::Def _out; + + // TODO Support variadic-length inputs + coco::Use _fst; + coco::Use _snd; +}; + +#endif // __ENCO_COEX_IR_H__ diff --git a/compiler/enco/core/src/coex/IR.test.cpp b/compiler/enco/core/src/coex/IR.test.cpp new file mode 100644 index 000000000..e20cbe4fd --- /dev/null +++ b/compiler/enco/core/src/coex/IR.test.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. 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 "IR.h" + +#include <gtest/gtest.h> + +TEST(IRTest, ANNConv2D_default_constructor) +{ + ANNConv2D ins; + + ASSERT_EQ(ins.ofm(), nullptr); + ASSERT_EQ(ins.ifm(), nullptr); + ASSERT_EQ(ins.ker(), nullptr); + ASSERT_EQ(ins.bias(), nullptr); +} + +TEST(IRTest, ANNDepthConcatF_default_constructor) +{ + ANNDepthConcatF ins; + + ASSERT_EQ(ins.out(), nullptr); + ASSERT_EQ(ins.fst(), nullptr); + ASSERT_EQ(ins.snd(), nullptr); +} |