Compute Library: Contribution Guidelines (original) (raw)

If you want to contribute to Arm Compute Library, be sure to review the following guidelines.

Contributions are accepted at arm-software/ComputeLibrary.

Inclusive language guideline

As part of the initiative to use inclusive language, there are certain phrases and words that were removed or replaced by more inclusive ones. Examples include but not limited to:

Please also follow this guideline when committing changes to Compute Library. It is worth mentioning that the term "master" is still used in some comments but only in reference to external code links that Arm has no governance on.

Futhermore, starting from release (22.05), 'master' branch is no longer being used, it has been replaced by 'main'. Please update your clone jobs accordingly.

Coding standards and guidelines

Best practices (as suggested by clang-tidy):

Helps to prevent undefined behaviour and allows to declare variables const if they are not changed after initialisation. See http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-type-member-init.html

const float32x4_t foo = vdupq_n_f32(0.f);

const float32x4_t bar = foo;

const int32x4x2_t i_foo = {{

vconvq_s32_f32(foo),

vconvq_s32_f32(foo)

}};

const int32x4x2_t i_bar = i_foo;

Only use static_cast, dynamic_cast, and (if required) reinterpret_cast and const_cast. See http://en.cppreference.com/w/cpp/language/explicit_cast for more information when to use which type of cast. C-style casts do not differentiate between the different cast types and thus make it easy to violate type safety. Also, due to the prefix notation it is less clear which part of an expression is going to be casted. See http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-type-cstyle-cast.html

Helps to increase readability and might help to catch bugs during refactoring. See http://clang.llvm.org/extra/clang-tidy/checks/readability-implicit-bool-cast.html

extern int *ptr;

if(ptr){}

if(ptr != nullptr) {}

extern int foo;

if(foo) {}

if(foo != 0) {}

The nullptr literal is type-checked and is therefore safer to use. See http://clang.llvm.org/extra/clang-tidy/checks/modernize-use-nullptr.html

The default constructor of std::string creates an empty string. In general it is therefore not necessary to specify it explicitly. See http://clang.llvm.org/extra/clang-tidy/checks/readability-redundant-string-init.html

std::string foo("");

std::string bar = "";

std::string foo;

std::string bar;

To increase readability and protect against refactoring errors the body of control block and loops must be wrapped in braces. See http://clang.llvm.org/extra/clang-tidy/checks/readability-braces-around-statements.html

For now loops for which the body is empty do not have to add empty braces. This exception might be revoked in the future. Anyway, situations in which this exception applies should be rare.

Iterator it;

while(it.next());

Increase readability and thus prevent errors.

int a, b;

int c, *d;

int e = 0;

int *p = nullptr;

For primitive types it is more efficient to pass them by value instead of by const reference because:

This advice also applies to non-primitive types that have cheap copy or move operations and the function needs a local copy of the argument anyway.

More information:

void foo(int i, long l, float32x4_t f);

void bar(const float32x4x4_t &f);

void foobar(const MyLargeCustomTypeClass &m);

Unions cannot be used to convert values between different types because (in C++) it is undefined behaviour to read from a member other than the last one that has been assigned to. This limits the use of unions to a few corner cases and therefore the general advice is not to use unions. See http://releases.llvm.org/3.8.0/tools/clang/tools/extra/docs/clang-tidy/checks/cppcoreguidelines-pro-type-union-access.html

In contrast to the pre-increment the post-increment has to make a copy of the incremented object. This might not be a problem for primitive types like int but for class like objects that overload the operators, like iterators, it can have a huge impact on the performance. See http://stackoverflow.com/a/9205011

To be consistent across the different cases the general advice is to use the pre-increment operator unless post-increment is explicitly required. The same rules apply for the decrement operator.

for(size_t i = 0; i < 9; i++);

for(size_t i = 0; i < 9; ++i);

The C and C++ standards don't define a uint type. Though some compilers seem to support it by default it would require to include the header sys/types.h. Instead we use the slightly more verbose unsigned int type.

Unsigned integers are good for representing bitfields and modular arithmetic. The fact that unsigned arithmetic doesn't model the behavior of a simple integer, but is instead defined by the standard to model modular arithmetic (wrapping around on overflow/underflow), means that a significant class of bugs cannot be diagnosed by the compiler. Mixing signedness of integer types is responsible for an equally large class of problems.

As compilers are now able to warn about accidental assignments if it is likely that the intention has been to compare values it is no longer required to place literals on the left-hand side of the comparison operator. Sticking to the natural order increases the readability and thus prevents logical errors (which cannot be spotted by the compiler). In the rare case that the desired result is to assign a value and check it the expression has to be surrounded by parentheses.

if(nullptr == ptr || false == cond)

{

}

if(ptr == nullptr || cond == false)

{

}

if(ptr = nullptr || cond = false)

{

}

if((ptr = nullptr) || (cond = false))

{

}

Rules

for(int i = 0; i < width * height; ++i)

{

void *d = foo(ptr, i, &addr);

static_cast<uint8_t *>(data)[i] = static_cast<uint8_t *>(d)[0];

}

namespace mali

{

#ifdef MALI_DEBUG

...

#else

...

#endif

}

class ClassName

{

public:

void my_function();

int my_attribute() const;

private:

int _my_attribute;

};

#ifndef ACL_ARM_COMPUTE_RUNTIME_NEON_FUNCTIONS_NEBATCHNORMALIZATIONLAYER

#define ACL_ARM_COMPUTE_RUNTIME_NEON_FUNCTIONS_NEBATCHNORMALIZATIONLAYER

.

.

.

#endif

#include "MyClass.h"

#include "arm_cv/core/Helpers.h"

#include "arm_cv/core/Types.h"

#include

#include

auto a = static_cast<float*>(bar);

auto b = std::make_unique(foo);

auto c = img.ptr();

auto d = vdup_n_u8(0);

void configure(ITensor *input, ITensor *output, ActivationLayerInfo activation_info);

How to check the rules

We check the rules using pre-commit (https://pre-commit.com/) framework. pre-commit can be installed via pip. After installing, run the following command in the root directory of the repository:

pre-commit install

This will create the hooks that perform the required formatting checks and will automatically run just before committing to flag issues.

Following conventional commits https://www.conventionalcommits.org/en/v1.0.0/#specification each commit must have a type from the following: build, ci, docs, feat, fix, perf, refactor, style, test, chore, revert, bump. In this format: <type>[optional scope]: <description>.

Commitizen (https://commitizen-tools.github.io/commitizen/) is used to do check that this standard is followed and is used in the pre-commit framework: pre-commit install –hook-type pre-push

This will create the pre-push hook that is going to check the commit message format before pushing to remote.

Library size: best practices and guidelines

Template suggestions

When writing a new patch we should also have in mind the effect it will have in the final library size. We can try some of the following things:

template

class Foo

{

public:

enum { v1, v2 };

};

can be converted to:

struct Foo_base

{

enum { v1, v2 };

};

template

class Foo : public Foo_base

{

public:

};

template

void NETemplatedKernel::run(const Window &window)

{

...

*(reinterpret_cast<T *>(out.ptr())) = *(reinterpret_cast<const T *>(in.ptr()));

...

}

The above snippet can be transformed to:

void NENonTemplatedKernel::run(const Window &window)

{

...

std::memcpy(out.ptr(), in.ptr(), element_size);

...

}

Secure coding practices

General Coding Practices

Secure Coding Best Practices

Guidelines for stable API/ABI

The Application Programming Interface (API) and Application Binary Interface (ABI) are the interfaces exposed to users so their programs can interact with the library efficiently and effectively. Even though changing API/ABI in a way that does not give backward compatibility is not necessarily bad if it can improve other users' experience and the library, contributions should be made with the awareness of API/ABI stability. If you'd like to make changes that affects the library's API/ABI, please review and follow the guidelines shown in this section. Also, please note that these guidelines are not exhaustive list but discussing things that might be easily overlooked.

Guidelines for API

Guidelines for ABI

We recommend to read through this page and double check your contributions to see if they include the changes listed.

Also, for classes that requires strong ABI stability, consider using pImpl idiom.

API deprecation process

In order to deprecate an existing API, these rules should be followed.

Also, it is recommended to use the following utility macros which is designed to work with both clang and gcc using C++14 and later.

void DoOldThing();

void DoNewThing();

#define ARM_COMPUTE_DEPRECATED_REL_REPLACE(rel, replace)

Definition: Macros.h:48

How to submit a patch

To be able to submit a patch to our development repository you need to raise a pull request on GitHub.

Keep your patch in a single commit, with a message that adheres to Conventional Commits. When your patch is ready, remember to sign off your contribution by adding a line with your name and e-mail address to every git commit message:

Signed-off-by: John Doe john.doe@example.org

You must use your real name, no pseudonyms or anonymous contributions are accepted.

You can add this to your patch with:

git commit -s --amend

You are now ready to submit your patch for review in a pull request.

Patch acceptance and code review

Once a patch is uploaded for review, there is a pre-commit test that runs on a Jenkins server for continuous integration tests. In order to be merged, a patch needs to:

At the moment, the Jenkins server is not publicly accessible and for security reasons patches submitted by non-allowlisted committers do not trigger the pre-commit tests. For this reason, one of the maintainers has to manually trigger the job.

Patch reversion policy

If the contributed patch breaks our regression suite, we either

to keep our main branch healthy.

Please remember that this is done to keep the tip of the main healthy, and such reverts are normal. Our regression suite is comprehensive and might find out issues that might not have been detected before merging. It doesn't mean you did anything wrong, and we welcome your fix.