Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
steven-johnson committed Apr 29, 2016
1 parent 00de774 commit 46272ff
Show file tree
Hide file tree
Showing 5 changed files with 757 additions and 2 deletions.
41 changes: 41 additions & 0 deletions BUILD.halide_distrib.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package(
default_visibility=["//visibility:public"]
)

load(":halide_library.bzl", "internal_halide_config_settings")
internal_halide_config_settings()

# Some useful utilities for dealing with Images in Halide at runtime.
# (Generators should never need to use this library.)
cc_library(
name = "halide_image",
hdrs = glob(["distrib/tools/halide_image*.h"]),
includes = ["distrib/tools"]
)

# You should rarely need to add an explicit dep on this library
# (the halide_library() rule will add it for you), but there are
# unusual circumstances where it is necessary.
cc_library(
name="halide_runtime",
hdrs = glob(["distrib/include/HalideRuntime*.h"]),
includes = ["distrib/include"]
)

cc_library(
name="lib_halide_static",
srcs = ["distrib/lib/libHalide.a"],
hdrs = ["distrib/include/Halide.h"],
includes = ["distrib/include"],
)

# This library is visibility:public, because any package that uses the
# halide_library() rule will implicitly need access to it; that said, it is
# intended only for the private, internal use of the halide_library() rule.
# Please don't depend on it directly; doing so will likely break your code at
# some point in the future.
cc_library(
name="internal_halide_generator_glue",
srcs = ["distrib/tools/GenGen.cpp"],
deps = [":lib_halide_static"],
)
12 changes: 12 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Copyright (c) 2016 MIT CSAIL, Google Inc., and other contributors

Developed by:

The Halide team
http://halide-lang.org

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
312 changes: 310 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,310 @@
# halide_bazel
Experimental Bazel build rules for Halide
# Bazel Halide Build Rules

## Overview

These build rules are used for building Halide Generators with Bazel.

The *Generator* is the fundamental compilable unit of Halide used in Bazel.
(Halide itself provides for other ahead-of-time compilation modes, as well as
just-in-time compilation, but these aren't supported in this rule set, and won't
be discussed here.)

*Generator* has [documentation](http://halide-lang.org/docs/_generator_8h.html),
[tutorials](http://halide-lang.org/tutorials/tutorial_lesson_15_generators.html)
and [examples](https://github.com/halide/Halide/tree/master/test/generator), so
we won't go into too much detail here, other than to recap: a *Generator* is a
C++ class in which you define a Halide pipeline and schedule, as well as
well-defined input and output parameters; it can be compiled into a library of
executable code (referred to as a *Filter*) that efficiently runs the Halide
pipeline.

Note that this rule requires the compiler host to be one of:

* OSX x86-64
* Linux x86-64
* Linux x86-32

It is anticipated that additional hosts (e.g. Windows x86-64) will be supported
at some point in the future.

## Setup

To use the Halide rules, add the following to your `WORKSPACE` file to add the
external repositories for the Halide toolchain:

```python
git_repository(
name = "halide_bazel",
remote = "https://github.com/halide/halide_bazel",
tag = "v0.1.0" # Or whichever version you want
)
load("@halide_bazel//:halide_configure.bzl", "halide_configure")
halide_configure()
```

## Build Rules

```python
halide_library(name, srcs, hdrs, filter_deps, generator_deps, visibility,
function_name, generator_name, generator_args,
halide_target_features, extra_outputs)
```

* **name** *(Name; required)* The name of the build rule.
* **srcs** *(List of labels; required)* source file(s) to compile into the
Generator executable.
* **hdrs** *(List of labels; optional)* additional .h files that will be
exposed to dependents of this rule.
* **generator_deps** *(List of labels; optional)* optional list of extra
dependencies needed to compile and/or link the Generator itself. (These
dependencies are *not* included in the filter produced by
`halide_library()`, nor in any executable that depends on it.)
* **filter_deps** *(List of labels; optional)* optional list of extra
dependencies needed by the Filter. (Generally speaking, you only need these
if you use Halide's `define_extern` directive.)
* **visibility** *(List of labels; optional)* Bazel visibility of result.
* **function_name** *(String; optional)* The name of the generated C function
for the filter. If omitted, defaults to the Bazel rule name.
* **generator_name** *(String; optional)* The registered name of the Halide
Generator (i.e., the name passed as the first argument to RegisterGenerator
in the Generator source file). If empty (or omitted), the srcs must define
exactly one Halide Generator, which will be used. (If the srcs define
multiple Generators, a compile error will result.)
* **generator_args** *(String; optional)* Arguments to pass to the Generator,
used to define the compile-time values of GeneratorParams defined by the
Generator. If any undefined GeneratorParam names (or illegal GeneratorParam
values) are specified, a compile error will result.
* **debug_codegen_level** *(Integer; optional)* Value to use for
HL_DEBUG_CODEGEN when building the Generator; usually useful only for
advanced Halide debugging. Defaults to zero. This attribute should never be
specified in code checked in to google3.
* **trace_level** *(Integer; optional)* Value to use for HL_TRACE when
building the Generator; usually useful only for advanced Halide debugging.
Defaults to zero. This attribute should never be specified in code checked
in to google3.
* **halide_target_features** *(List of strings; optional)* A list of extra
Halide Features to enable in the code. This can be any feature listed in
[feature_name_map]
(https://github.com/halide/Halide/blob/master/src/Target.cpp). The most
useful are generally:
* "debug" (generate code with extra debugging)
* "cuda", "opengl", or "opencl" (generate code for a GPU target)
* "profile" (generate code with Halide's sampling profiler included)
* "user_context" (the generated Filter function to take an arbitrary void*
pointer as the first parameter)
* **extra_outputs** *(List of strings; optional)* A list of extra Halide
outputs to generate at build time; this is exclusively intended for
debugging (e.g. to examine Halide code generation) and currently supports:
* "assembly" (generate assembly listings for the generated functions)
* "bitcode" (emit the LLVM bitcode for generation functions)
* "stmt" (generate Halide .stmt files for generated functions)
* "html" (like "stmt", but generated with HTML-formatted wrapping)

## Example

Suppose you have the following directory structure:

```
[workspace]/
BUILD
WORKSPACE
mandelbrot_generator.cpp
main.cpp
```

`WORKSPACE`:

```python
git_repository(
name = "halide_bazel",
remote = "https://github.com/halide/halide_bazel",
tag = "v0.1.0"
)
load("@halide_bazel//:halide_configure.bzl", "halide_configure")
halide_configure()
```

`BUILD`:

```python
load("@halide_distrib//:halide_library.bzl", "halide_library")

halide_library(
name="mandelbrot",
srcs=["mandelbrot_generator.cpp"]
)

cc_binary(
name = "main",
srcs = ["main.cpp"],
deps = [
":mandelbrot",
"@halide_distrib//:halide_image"
],
)
```

`mandelbrot_generator.cpp`:

```c++
#include "Halide.h"

using namespace Halide;

namespace {

class Complex {
Tuple t;

public:
Complex(Expr real, Expr imag) : t(real, imag) {}
Complex(Tuple tup) : t(tup) {}
Complex(FuncRefExpr f) : t(Tuple(f)) {}
Complex(FuncRefVar f) : t(Tuple(f)) {}
Expr real() const { return t[0]; }
Expr imag() const { return t[1]; }

operator Tuple() const { return t; }
};

Complex operator+(const Complex &a, const Complex &b) {
return Complex(a.real() + b.real(), a.imag() + b.imag());
}

Complex operator*(const Complex &a, const Complex &b) {
return Complex(a.real() * b.real() - a.imag() * b.imag(),
a.real() * b.imag() + a.imag() * b.real());
}

Complex conjugate(const Complex &a) { return Complex(a.real(), -a.imag()); }

Expr magnitude(Complex a) { return (a * conjugate(a)).real(); }

class Mandelbrot : public Generator<Mandelbrot> {
public:
Param<float> x_min{"x_min"};
Param<float> x_max{"x_max"};
Param<float> y_min{"y_min"};
Param<float> y_max{"y_max"};
Param<float> c_real{"c_real"};
Param<float> c_imag{"c_imag"};
Param<int> iters{"iters"};
Param<int> w{"w"};
Param<int> h{"h"};

Func build() {
Var x, y, z;

Complex initial(lerp(x_min, x_max, cast<float>(x) / w),
lerp(y_min, y_max, cast<float>(y) / h));
Complex c(c_real, c_imag);

Func mandelbrot;
mandelbrot(x, y, z) = initial;
RDom t(1, iters);
Complex current = mandelbrot(x, y, t - 1);
mandelbrot(x, y, t) = current * current + c;

// How many iterations until something escapes a circle of radius 2?
Tuple escape = argmin(magnitude(mandelbrot(x, y, t)) < 4);

// If it never escapes, use the value 0
Func count;
count(x, y) = select(escape[1], 0, escape[0]);

Var xi, yi, xo, yo;
mandelbrot.compute_at(count, xo);

count.tile(x, y, xo, yo, xi, yi, 8, 8).parallel(yo).vectorize(xi, 4).unroll(xi).unroll(yi, 2);

return count;
}
};

RegisterGenerator<Mandelbrot> register_mandelbrot{"mandelbrot"};

} // namespace
```
`main.cpp`:
```C++
#include <cmath>
#include <cstdio>
#include "halide_image.h"
#include "mandelbrot.h" // Generated by halide_library() rule
int main(int argc, char **argv) {
Halide::Tools::Image<int> output(100, 30);
const char *code = " .:-~*={}&%#@";
const int iters = strlen(code) - 1;
// Compute a Julia set
float t = 100.0f, fx = cos(t / 10.0f), fy = sin(t / 10.0f);
int result = mandelbrot(-2.0f, 2.0f, -1.4f, 1.4f, fx, fy, iters, output.width(), output.height(),
output);
if (result != 0) {
fprintf(stderr, "Failure: %d\n", result);
return -1;
}
char buf[4096];
char *buf_ptr = buf;
for (int y = 0; y < output.height(); y++) {
for (int x = 0; x < output.width(); x++) {
*buf_ptr++ = code[output(x, y)];
}
*buf_ptr++ = '\n';
}
*buf_ptr++ = 0;
printf("%s", buf);
fflush(stdout);
printf("Success!\n");
return 0;
}
```

## SIMD Support

At present, this rule doesn't default to enabling SIMD support on some architectures;
most notable, it doesn't enable SSE4.1/AVX/etc. on Intel architectures.
You can explicitly opt-in on a per-rule basis by adding halide_target_features, e.g.

```python
halide_library(name = "my_rule", srcs = "my_src.cpp", halide_target_features = [ "sse41", "avx" ])
```

Alternately, you can add defaults for the entire workspace:

```python
halide_configure(default_halide_target_features = [ "sse41", "avx" ])
```

## Configure Rules

```python
halide_configure(default_halide_target_features, http_archive_info, local_repository_path)
```

Both local_repository_path and http_archive_info are optional arguments; if you
specify neither, the rule will attempt to choose the most recent stable version
of Halide for the host architecture and use that.

* **default_halide_target_features** *(List of strings; optional)* If present,
this is a least of Halide::Target Features that should be added to *every*
halide_library() instance.
* **http_archive_info** *(Dict, optional)* If present, this should be a
dictionary that maps each possible host architecture to a url and (optional)
sha256. This allows you to specify a particular release of Halide for
building, rather than relying on the version that is chosen by
halide_configure() and will vary over time. This dict should be of the form:
{ *host-cpu1*: { "url": *url-to-halide-ditribution*, "sha256":
*optional-sha256*}, *host-cpu2*:
{ "url": *url-to-halide-ditribution*, "sha256": *optional-sha256*} }
* **local_repository_path** *(String, optional)* If present, this should point
to the "distrib" folder of a locally-built instance of Halide. Most users
will not ever want to use this option; it is useful primarily for situations
in which you need to experiment with changes to the Halide library itself.
Loading

0 comments on commit 46272ff

Please sign in to comment.